mirror of
https://github.com/v2fly/domain-list-community.git
synced 2026-03-22 19:37:53 +07:00
Compare commits
22 Commits
2026031313
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa76e186cb | ||
|
|
c9348f1db4 | ||
|
|
d62599c8db | ||
|
|
fab6275217 | ||
|
|
4c7afec5a9 | ||
|
|
330c30eb23 | ||
|
|
f34f22819e | ||
|
|
baa1409cfb | ||
|
|
a22d247c5a | ||
|
|
d311bbe50b | ||
|
|
1db558b165 | ||
|
|
9ee0757263 | ||
|
|
714a061ba3 | ||
|
|
5ff8142411 | ||
|
|
becbd7a8ad | ||
|
|
cd2d66eb72 | ||
|
|
4c4ad053ef | ||
|
|
6544f6d3a6 | ||
|
|
673a70c380 | ||
|
|
be078767c4 | ||
|
|
15fde0da4b | ||
|
|
5dd4779425 |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -4,9 +4,9 @@
|
||||
/domain-list-community
|
||||
/domain-list-community.exe
|
||||
|
||||
# Generated dat file.
|
||||
dlc.dat
|
||||
# Generated dat files.
|
||||
/*.dat
|
||||
|
||||
# Exported plaintext lists.
|
||||
/*.yml
|
||||
/*.txt
|
||||
/*.yml
|
||||
|
||||
@@ -86,6 +86,8 @@ Each file in the `data` directory can be used as a rule in this format: `geosite
|
||||
|
||||
Run `go run ./ --help` for more usage information.
|
||||
|
||||
For anyone who wants to generate custom `.dat` files, you may read [#3370](https://github.com/v2fly/domain-list-community/discussions/3370).
|
||||
|
||||
## Structure of data
|
||||
|
||||
All data are under `data` directory. Each file in the directory represents a sub-list of domains, named by the file name. File content is in the following format.
|
||||
@@ -105,7 +107,7 @@ regexp:^odd[1-7]\.example\.org(\.[a-z]{2})?$
|
||||
> Adding new `regexp` and `keyword` rules is discouraged because it is easy to use them incorrectly, and proxy software cannot efficiently match these types of rules.
|
||||
|
||||
> [!NOTE]
|
||||
> The following types of rules are **NOT** fully compatible with the ones that defined by user in V2Ray config file. Do **Not** copy and paste directly.
|
||||
> The following types of rules are **NOT** fully compatible with the ones that defined by user in V2Ray config file. Do **NOT** copy and paste directly.
|
||||
|
||||
- Comment begins with `#`. It may begin anywhere in the file. The content in the line after `#` is treated as comment and ignored in production.
|
||||
- Subdomain begins with `domain:`, followed by a valid domain name. The prefix `domain:` may be omitted.
|
||||
@@ -114,7 +116,7 @@ regexp:^odd[1-7]\.example\.org(\.[a-z]{2})?$
|
||||
- Regular expression begins with `regexp:`, followed by a valid regular expression (per Golang's standard).
|
||||
- Domain rules (including `domain`, `full`, `keyword`, and `regexp`) may have none, one or more attributes. Each attribute begins with `@` and followed by the name of the attribute. Attributes will remain available in final lists and `dlc.dat`.
|
||||
- Domain rules may have none, one or more affiliations, which additionally adds the domain rule into the affiliated target list. Each affiliation begins with `&` and followed by the name of the target list (nomatter whether the target has a dedicated file in data path). This is a method for data management, and will not remain in the final lists or `dlc.dat`.
|
||||
- Inclusion begins with `include:`, followed by the name of another valid domain list. A simple `include:listb` in file `lista` means adding all domain rules of `listb` into `lista`. Inclusions with attributes stands for selective inclusion. `include:listb @attr1 @-attr2` means only adding those domain rules *with* `@attr1` **and** *without* `@attr2`. This is a special type for data management, and will not remain in the final lists or `dlc.dat`.
|
||||
- Inclusion begins with `include:`, followed by the name of another valid domain list. `include:listb` in file `lista` means adding all domain rules of `listb` into `lista`. Inclusions with attributes stand for selective inclusion. `include:listb @attr1 @-attr2` means only adding those domain rules *with* `@attr1` **and** *without* `@attr2`. This is a special type for data management, and will not remain in the final lists or `dlc.dat`.
|
||||
|
||||
## How it works
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ shifen.com
|
||||
smartapps.cn
|
||||
tieba.com
|
||||
tiebaimg.com
|
||||
xdrtc.com
|
||||
xianfae.com
|
||||
xiaodutv.com
|
||||
yoojia.com
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# Game
|
||||
bestdori.com
|
||||
colorfulstage.com
|
||||
|
||||
# Idol
|
||||
|
||||
@@ -46,3 +46,4 @@ mistral.ai
|
||||
openart.ai
|
||||
openclaw.ai
|
||||
openrouter.ai
|
||||
spicywriter.com
|
||||
|
||||
@@ -54,6 +54,7 @@ include:stackexchange
|
||||
include:strikingly
|
||||
include:termux
|
||||
include:thelinuxfoundation
|
||||
include:tilda
|
||||
include:unity
|
||||
include:v8
|
||||
|
||||
@@ -152,4 +153,5 @@ wireshark.org
|
||||
x.org
|
||||
xposed.info
|
||||
yarnpkg.com
|
||||
ziglang.org
|
||||
zsh.org
|
||||
|
||||
@@ -10,6 +10,8 @@ include:shanbay
|
||||
include:xueersi
|
||||
include:yuanfudao
|
||||
|
||||
edu.cn
|
||||
|
||||
# 雨课堂
|
||||
include:yuketang
|
||||
## 雨豆课堂
|
||||
@@ -19,8 +21,6 @@ yushiyan.net
|
||||
## 学堂在线
|
||||
xuetangx.com
|
||||
|
||||
edu.cn
|
||||
|
||||
# 国家智慧教育公共服务平台
|
||||
cbern.com.cn
|
||||
smartedu.cn
|
||||
@@ -91,6 +91,7 @@ eoffcn.com
|
||||
jinrongren.net
|
||||
offcn.com
|
||||
zggqzp.com
|
||||
zgjsks.com
|
||||
zgsydw.com
|
||||
# 高考100
|
||||
gk100.com
|
||||
@@ -105,6 +106,9 @@ xir.cn
|
||||
# 日语测试
|
||||
j-test.com
|
||||
nattest-china.com
|
||||
# 菁优网
|
||||
jyeoo.com
|
||||
jyeoo.net
|
||||
# 课播云校
|
||||
keboyunxiao.com
|
||||
# 课堂派
|
||||
@@ -132,6 +136,8 @@ oldboyedu.com
|
||||
pigai.org
|
||||
# 公考知识库
|
||||
saduck.top
|
||||
# 外研在线
|
||||
unipus.cn
|
||||
# 未来云校
|
||||
weilaiyunxiao.com
|
||||
# 北京嘉瑞新创教育咨询有限公司
|
||||
|
||||
@@ -17,11 +17,10 @@ bouffalolab.com
|
||||
cxmt.com
|
||||
|
||||
# 乐鑫信息科技
|
||||
#include:espressif
|
||||
esp8266.cn
|
||||
esp8266.com
|
||||
esp8266.com.cn
|
||||
espressif.cn
|
||||
espressif.com
|
||||
espressif.com.cn
|
||||
|
||||
# 华秋电子
|
||||
|
||||
@@ -57,10 +57,14 @@ anitabi.cn
|
||||
# 暴风影音
|
||||
baofeng.com
|
||||
baofeng.net
|
||||
# 街机地图
|
||||
bemanicn.com
|
||||
# 布咕阅读
|
||||
bgwxc.com
|
||||
# B站空降助手
|
||||
bsbsb.top
|
||||
# CdkeyNoGap
|
||||
cdkeynogap.com
|
||||
# 动漫之家 #703
|
||||
dmzj.com
|
||||
muwai.com
|
||||
|
||||
@@ -11,6 +11,7 @@ include:okko
|
||||
include:wink
|
||||
24h.tv
|
||||
amediateka.ru
|
||||
beeline.tv
|
||||
ivi.ru
|
||||
premier.one
|
||||
smotreshka.tv
|
||||
|
||||
@@ -97,6 +97,7 @@ myexternalip.com
|
||||
myip.com
|
||||
myip.ms
|
||||
myip.ru
|
||||
myip.wtf
|
||||
myipaddress.com
|
||||
myiplookup.com
|
||||
mylocation.org
|
||||
@@ -131,7 +132,10 @@ wtfismyip.com
|
||||
|
||||
# Subdomains/internal api used for ip-geo-detect
|
||||
full:checkip.amazonaws.com
|
||||
full:ipv4-check-perf.radar.cloudflare.com
|
||||
full:ipv6-check-perf.radar.cloudflare.com
|
||||
geoip.noc.gov.ru
|
||||
ip.hetzner.com
|
||||
ip.mail.ru
|
||||
ip.nic.ru
|
||||
ip.tyk.nu
|
||||
|
||||
@@ -135,6 +135,10 @@ kksmg.com
|
||||
statickksmg.com
|
||||
# 晚点
|
||||
latepost.com
|
||||
# 雷科技
|
||||
leikeji.com
|
||||
# 雷峰网
|
||||
leiphone.com
|
||||
# 今日观察网
|
||||
miercn.com
|
||||
# 快科技
|
||||
@@ -206,14 +210,21 @@ techweb.com.cn
|
||||
thecover.cn
|
||||
# 澎湃新闻
|
||||
thepaper.cn
|
||||
# 潮新闻/浙江日报
|
||||
8531.cn
|
||||
tidenews.com.cn
|
||||
# 人人都是产品经理
|
||||
woshipm.com
|
||||
# 新华日报
|
||||
xhby.net
|
||||
# 西陆网
|
||||
xilu.com
|
||||
# 新快报
|
||||
xkb.com.cn
|
||||
# 第一财经
|
||||
yicai.com
|
||||
# 扬子晚报
|
||||
yzwb.net
|
||||
# ZEALER
|
||||
zealer.com
|
||||
# 知产力
|
||||
|
||||
@@ -3,33 +3,62 @@ include:openspeedtest
|
||||
|
||||
cnspeedtest.cn @cn
|
||||
fast.com
|
||||
fastspeedtest.com
|
||||
linkmeter.net
|
||||
measurementlab.net
|
||||
meter.net
|
||||
nperf.com
|
||||
openspeedtest.ru
|
||||
speed.cloudflare.com
|
||||
speed.dler.io
|
||||
speed.ee
|
||||
speed.hinet.net
|
||||
speed.nccu.edu.tw
|
||||
speed.neu6.edu.cn @cn
|
||||
speed.nju.edu.cn @cn
|
||||
speed.nuaa.edu.cn @cn
|
||||
speed.qlu.edu.cn @cn
|
||||
speed.ujs.edu.cn @cn
|
||||
speed6.ujs.edu.cn @cn
|
||||
speed2.hinet.net
|
||||
speed5.ntu.edu.tw
|
||||
speed6.ujs.edu.cn @cn
|
||||
speedcheck.org
|
||||
speedgeo.net
|
||||
speedof.me
|
||||
speedtest.cesnet.cz
|
||||
speedtest.ch
|
||||
speedtest.citylink.pro
|
||||
speedtest.cn @cn
|
||||
speedtest.co.za
|
||||
speedtest.de
|
||||
speedtest.dno-it.ru
|
||||
speedtest.frontier.com
|
||||
speedtest.im
|
||||
speedtest.mail.ru
|
||||
speedtest.mfcyun.com @cn
|
||||
speedtest.net.in
|
||||
speedtest.net.ua
|
||||
speedtest.net.uk
|
||||
speedtest.org
|
||||
speedtest.rt.ru
|
||||
speedtest.ru
|
||||
speedtest.shaw.ca
|
||||
speedtest.shu.edu.cn @cn
|
||||
speedtest6.shu.edu.cn @cn
|
||||
speedtest.su
|
||||
speedtest.uz
|
||||
speedtest.volia.com
|
||||
speedtest.xaut.edu.cn @cn
|
||||
speedtest.xfinity.com
|
||||
speedtestcustom.com
|
||||
test.ustc.edu.cn @cn
|
||||
test6.ustc.edu.cn @cn
|
||||
speedtest.xyz
|
||||
speedtest24.ru
|
||||
speedtest6.shu.edu.cn @cn
|
||||
test.nju.edu.cn @cn
|
||||
test.ustc.edu.cn @cn
|
||||
test6.nju.edu.cn @cn
|
||||
speed.nju.edu.cn @cn
|
||||
test6.ustc.edu.cn @cn
|
||||
testmy.net
|
||||
testmyspeed.com
|
||||
testskorosti.ru
|
||||
xnfz.seu.edu.cn @cn
|
||||
|
||||
full:hk-global-bgp.hkg.speedtest.yecaoyun.com @!cn
|
||||
|
||||
@@ -222,8 +222,12 @@ mingxuan.store
|
||||
gov.cn
|
||||
## 中国气象局
|
||||
cma.cn
|
||||
## 政务和公益机构域名注册管理中心
|
||||
conac.cn
|
||||
## 中央气象台
|
||||
nmc.cn
|
||||
## 中国互联网联合辟谣平台
|
||||
piyao.org.cn
|
||||
|
||||
# Public transportation
|
||||
## 中国国际航空
|
||||
@@ -321,10 +325,14 @@ webterren.com
|
||||
## 国学大师网
|
||||
guoxuedashi.com
|
||||
guoxuemi.com
|
||||
## 志愿汇
|
||||
zyh365.com
|
||||
## 温州市图书馆
|
||||
wzlib.cn
|
||||
## 中国大百科全书
|
||||
zgbk.com
|
||||
## 浙江图书馆
|
||||
zjlib.cn
|
||||
## 志愿汇
|
||||
zyh365.com
|
||||
|
||||
# Services & Softwares
|
||||
include:category-ai-cn
|
||||
@@ -1112,8 +1120,6 @@ lascn.net
|
||||
lawxp.com
|
||||
leangoo.com
|
||||
leanote.com
|
||||
leikeji.com
|
||||
leiphone.com
|
||||
leiting.com
|
||||
leju.com
|
||||
leturich.org
|
||||
@@ -1394,7 +1400,6 @@ weand.com
|
||||
weavatar.com
|
||||
weicaifu.com
|
||||
weilaicaijing.com
|
||||
weiphone.net
|
||||
weixing.com
|
||||
weiyangx.com
|
||||
welltrend-edu.com
|
||||
@@ -1523,7 +1528,6 @@ zastatic.com
|
||||
zczj.com
|
||||
zdfans.com
|
||||
zgjm.org
|
||||
zgjsks.com
|
||||
zglxw.com
|
||||
zgzcw.com
|
||||
zhanbuba.com
|
||||
@@ -1621,3 +1625,6 @@ ao-x.ac.cn
|
||||
|
||||
# 万集科技 京ICP备18036282号-2
|
||||
wanji.net.cn
|
||||
|
||||
# 广州市雅望互联网服务有限公司
|
||||
gzyowin.com
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
22112211.xyz
|
||||
deepflood.com
|
||||
nodeget.com
|
||||
nodeimage.com
|
||||
nodequality.com
|
||||
nodeseek.com
|
||||
|
||||
@@ -59,6 +59,7 @@ tegrazone.com
|
||||
tegrazone.jp
|
||||
tegrazone.kr
|
||||
|
||||
full:nvidia.custhelp.com
|
||||
full:nvidia.tt.omtrdc.net
|
||||
|
||||
# NVIDIA 文件下载服务器中国镜像
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
# All .oracle domains
|
||||
oracle
|
||||
|
||||
include:addthis
|
||||
include:java
|
||||
|
||||
ateam-oracle.com
|
||||
bronto.com
|
||||
covid19-rx.org
|
||||
covid19rx.org
|
||||
custhelp.com
|
||||
oracle.com
|
||||
oraclecloud.com
|
||||
oraclefoundation.org
|
||||
@@ -12,6 +16,3 @@ oracleimg.com
|
||||
oracleinfinity.io
|
||||
sun.com
|
||||
virtualbox.org
|
||||
|
||||
include:addthis
|
||||
include:java
|
||||
|
||||
30
data/qcloud
30
data/qcloud
@@ -258,13 +258,14 @@ tdnsv14.net
|
||||
tdnsv15.net
|
||||
|
||||
# myqcloud inside mainland China
|
||||
regexp:\.(.+-)?ap-beijing(-.+)?\.myqcloud\.com$ #北京
|
||||
regexp:\.(.+-)?ap-nanjing(-.+)?\.myqcloud\.com$ #南京
|
||||
regexp:\.(.+-)?ap-shanghai(-.+)?\.myqcloud\.com$ #上海
|
||||
regexp:\.(.+-)?ap-guangzhou(-.+)?\.myqcloud\.com$ #广州
|
||||
regexp:\.(.+-)?ap-chengdu(-.+)?\.myqcloud\.com$ #成都
|
||||
regexp:\.(.+-)?ap-chongqing(-.+)?\.myqcloud\.com$ #重庆
|
||||
regexp:\.(.+-)?ap-shenzhen(-.+)?\.myqcloud\.com$ #深圳
|
||||
# overrided by myqcloud.com
|
||||
#regexp:\.(.+-)?ap-beijing(-.+)?\.myqcloud\.com$ #北京
|
||||
#regexp:\.(.+-)?ap-nanjing(-.+)?\.myqcloud\.com$ #南京
|
||||
#regexp:\.(.+-)?ap-shanghai(-.+)?\.myqcloud\.com$ #上海
|
||||
#regexp:\.(.+-)?ap-guangzhou(-.+)?\.myqcloud\.com$ #广州
|
||||
#regexp:\.(.+-)?ap-chengdu(-.+)?\.myqcloud\.com$ #成都
|
||||
#regexp:\.(.+-)?ap-chongqing(-.+)?\.myqcloud\.com$ #重庆
|
||||
#regexp:\.(.+-)?ap-shenzhen(-.+)?\.myqcloud\.com$ #深圳
|
||||
|
||||
# COS 使用到的非中国大陆的地域与可用区,参见 https://cloud.tencent.com/document/product/436/6224
|
||||
ap-hongkong.myqcloud.com @!cn #中国香港
|
||||
@@ -282,13 +283,14 @@ eu-frankfurt.myqcloud.com @!cn #法兰克福
|
||||
eu-moscow.myqcloud.com @!cn #莫斯科
|
||||
|
||||
# tencentcos inside mainland China
|
||||
regexp:\.(.+-)?ap-beijing(-.+)?\.tencentcos\.(cn|com(\.cn)?)$ #北京
|
||||
regexp:\.(.+-)?ap-nanjing(-.+)?\.tencentcos\.(cn|com(\.cn)?)$ #南京
|
||||
regexp:\.(.+-)?ap-shanghai(-.+)?\.tencentcos\.(cn|com(\.cn)?)$ #上海
|
||||
regexp:\.(.+-)?ap-guangzhou(-.+)?\.tencentcos\.(cn|com(\.cn)?)$ #广州
|
||||
regexp:\.(.+-)?ap-chengdu(-.+)?\.tencentcos\.(cn|com(\.cn)?)$ #成都
|
||||
regexp:\.(.+-)?ap-chongqing(-.+)?\.tencentcos\.(cn|com(\.cn)?)$ #重庆
|
||||
regexp:\.(.+-)?ap-shenzhen(-.+)?\.tencentcos\.(cn|com(\.cn)?)$ #深圳
|
||||
# overrided by tencentcos.cn, tencentcos.com, tencentcos.com.cn
|
||||
#regexp:\.(.+-)?ap-beijing(-.+)?\.tencentcos\.(cn|com(\.cn)?)$ #北京
|
||||
#regexp:\.(.+-)?ap-nanjing(-.+)?\.tencentcos\.(cn|com(\.cn)?)$ #南京
|
||||
#regexp:\.(.+-)?ap-shanghai(-.+)?\.tencentcos\.(cn|com(\.cn)?)$ #上海
|
||||
#regexp:\.(.+-)?ap-guangzhou(-.+)?\.tencentcos\.(cn|com(\.cn)?)$ #广州
|
||||
#regexp:\.(.+-)?ap-chengdu(-.+)?\.tencentcos\.(cn|com(\.cn)?)$ #成都
|
||||
#regexp:\.(.+-)?ap-chongqing(-.+)?\.tencentcos\.(cn|com(\.cn)?)$ #重庆
|
||||
#regexp:\.(.+-)?ap-shenzhen(-.+)?\.tencentcos\.(cn|com(\.cn)?)$ #深圳
|
||||
|
||||
# tencentcos outside mainland China
|
||||
# regexp:.+\.ap-hongkong\.tencentcos\.(cn|com(\.cn)?)$ @!cn #中国香港
|
||||
|
||||
5
data/tilda
Normal file
5
data/tilda
Normal file
@@ -0,0 +1,5 @@
|
||||
tilda.cc
|
||||
tilda.ru
|
||||
tilda.ws
|
||||
tildaapi.com
|
||||
tildacdn.com
|
||||
@@ -1,3 +1,4 @@
|
||||
feng.com
|
||||
fengimg.com
|
||||
weiphone.net
|
||||
wfdata.club
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
videopress.com
|
||||
w.org
|
||||
wordpress.com
|
||||
wordpress.net
|
||||
wordpress.org
|
||||
wordpress.tv
|
||||
wp-themes.com
|
||||
|
||||
332
main.go
332
main.go
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -19,6 +20,7 @@ var (
|
||||
dataPath = flag.String("datapath", "./data", "Path to your custom 'data' directory")
|
||||
outputName = flag.String("outputname", "dlc.dat", "Name of the generated dat file")
|
||||
outputDir = flag.String("outputdir", "./", "Directory to place all generated files")
|
||||
datProfile = flag.String("datprofile", "", "Path of config file used to assemble custom dats")
|
||||
exportLists = flag.String("exportlists", "", "Lists to be flattened and exported in plaintext format, separated by ',' comma")
|
||||
)
|
||||
|
||||
@@ -47,7 +49,24 @@ type Processor struct {
|
||||
cirIncMap map[string]bool
|
||||
}
|
||||
|
||||
func makeProtoList(listName string, entries []*Entry) (*router.GeoSite, error) {
|
||||
type GeoSites struct {
|
||||
Sites []*router.GeoSite
|
||||
SiteIdx map[string]int
|
||||
}
|
||||
|
||||
type DatTask struct {
|
||||
Name string `json:"name"`
|
||||
Mode string `json:"mode"`
|
||||
Lists []string `json:"lists"`
|
||||
}
|
||||
|
||||
const (
|
||||
ModeAll string = "all"
|
||||
ModeAllowlist string = "allowlist"
|
||||
ModeDenylist string = "denylist"
|
||||
)
|
||||
|
||||
func makeProtoList(listName string, entries []*Entry) *router.GeoSite {
|
||||
site := &router.GeoSite{
|
||||
CountryCode: listName,
|
||||
Domain: make([]*router.Domain, 0, len(entries)),
|
||||
@@ -73,7 +92,91 @@ func makeProtoList(listName string, entries []*Entry) (*router.GeoSite, error) {
|
||||
}
|
||||
site.Domain = append(site.Domain, pdomain)
|
||||
}
|
||||
return site, nil
|
||||
return site
|
||||
}
|
||||
|
||||
func loadTasks(path string) ([]DatTask, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var tasks []DatTask
|
||||
dec := json.NewDecoder(f)
|
||||
if err := dec.Decode(&tasks); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode json: %w", err)
|
||||
}
|
||||
for i, t := range tasks {
|
||||
if t.Name == "" {
|
||||
return nil, fmt.Errorf("task[%d]: name is required", i)
|
||||
}
|
||||
switch t.Mode {
|
||||
case ModeAll, ModeAllowlist, ModeDenylist:
|
||||
default:
|
||||
return nil, fmt.Errorf("task[%d] %q: invalid mode %q", i, t.Name, t.Mode)
|
||||
}
|
||||
}
|
||||
return tasks, nil
|
||||
}
|
||||
|
||||
func (gs *GeoSites) assembleDat(task DatTask) error {
|
||||
datFileName := strings.ToLower(filepath.Base(task.Name))
|
||||
geoSiteList := new(router.GeoSiteList)
|
||||
|
||||
switch task.Mode {
|
||||
case ModeAll:
|
||||
geoSiteList.Entry = gs.Sites
|
||||
case ModeAllowlist:
|
||||
allowedIdxes := make([]int, 0, len(task.Lists))
|
||||
for _, list := range task.Lists {
|
||||
if idx, ok := gs.SiteIdx[strings.ToUpper(list)]; ok {
|
||||
allowedIdxes = append(allowedIdxes, idx)
|
||||
} else {
|
||||
return fmt.Errorf("list %q not found for allowlist task", list)
|
||||
}
|
||||
}
|
||||
slices.Sort(allowedIdxes)
|
||||
allowedlen := len(allowedIdxes)
|
||||
if allowedlen == 0 {
|
||||
return fmt.Errorf("allowlist needs at least one valid list")
|
||||
}
|
||||
geoSiteList.Entry = make([]*router.GeoSite, allowedlen)
|
||||
for i, idx := range allowedIdxes {
|
||||
geoSiteList.Entry[i] = gs.Sites[idx]
|
||||
}
|
||||
case ModeDenylist:
|
||||
deniedMap := make(map[int]bool, len(task.Lists))
|
||||
for _, list := range task.Lists {
|
||||
if idx, ok := gs.SiteIdx[strings.ToUpper(list)]; ok {
|
||||
deniedMap[idx] = true
|
||||
} else {
|
||||
fmt.Printf("[Warn] list %q not found in denylist task %q", list, task.Name)
|
||||
}
|
||||
}
|
||||
deniedlen := len(deniedMap)
|
||||
if deniedlen == 0 {
|
||||
fmt.Printf("[Warn] nothing to deny in task %q", task.Name)
|
||||
geoSiteList.Entry = gs.Sites
|
||||
} else {
|
||||
geoSiteList.Entry = make([]*router.GeoSite, 0, len(gs.Sites)-deniedlen)
|
||||
for i, site := range gs.Sites {
|
||||
if !deniedMap[i] {
|
||||
geoSiteList.Entry = append(geoSiteList.Entry, site)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protoBytes, err := proto.Marshal(geoSiteList)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(*outputDir, datFileName), protoBytes, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write file %q: %w", datFileName, err)
|
||||
}
|
||||
fmt.Printf("dat %q has been generated successfully\n", datFileName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func writePlainList(listname string, entries []*Entry) error {
|
||||
@@ -89,46 +192,28 @@ func writePlainList(listname string, entries []*Entry) error {
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
func parseEntry(line string) (*Entry, []string, error) {
|
||||
entry := new(Entry)
|
||||
parts := strings.Fields(line)
|
||||
func parseEntry(typ, rule string) (*Entry, []string, error) {
|
||||
entry := &Entry{Type: typ}
|
||||
parts := strings.Fields(rule)
|
||||
if len(parts) == 0 {
|
||||
return entry, nil, fmt.Errorf("empty line")
|
||||
return entry, nil, fmt.Errorf("empty domain rule")
|
||||
}
|
||||
|
||||
// Parse type and value
|
||||
typ, val, isTypeSpecified := strings.Cut(parts[0], ":")
|
||||
typ = strings.ToLower(typ)
|
||||
if !isTypeSpecified { // Default RuleType
|
||||
if !validateDomainChars(typ) {
|
||||
return entry, nil, fmt.Errorf("invalid domain: %q", typ)
|
||||
// Parse value
|
||||
switch entry.Type {
|
||||
case dlc.RuleTypeRegexp:
|
||||
if _, err := regexp.Compile(parts[0]); err != nil {
|
||||
return entry, nil, fmt.Errorf("invalid regexp %q: %w", parts[0], err)
|
||||
}
|
||||
entry.Type = dlc.RuleTypeDomain
|
||||
entry.Value = typ
|
||||
} else {
|
||||
switch typ {
|
||||
case dlc.RuleTypeRegexp:
|
||||
if _, err := regexp.Compile(val); err != nil {
|
||||
return entry, nil, fmt.Errorf("invalid regexp %q: %w", val, err)
|
||||
}
|
||||
entry.Type = dlc.RuleTypeRegexp
|
||||
entry.Value = val
|
||||
case dlc.RuleTypeInclude:
|
||||
entry.Type = dlc.RuleTypeInclude
|
||||
entry.Value = strings.ToUpper(val)
|
||||
if !validateSiteName(entry.Value) {
|
||||
return entry, nil, fmt.Errorf("invalid included list name: %q", entry.Value)
|
||||
}
|
||||
case dlc.RuleTypeDomain, dlc.RuleTypeFullDomain, dlc.RuleTypeKeyword:
|
||||
entry.Type = typ
|
||||
entry.Value = strings.ToLower(val)
|
||||
if !validateDomainChars(entry.Value) {
|
||||
return entry, nil, fmt.Errorf("invalid domain: %q", entry.Value)
|
||||
}
|
||||
default:
|
||||
return entry, nil, fmt.Errorf("invalid type: %q", typ)
|
||||
entry.Value = parts[0]
|
||||
case dlc.RuleTypeDomain, dlc.RuleTypeFullDomain, dlc.RuleTypeKeyword:
|
||||
entry.Value = strings.ToLower(parts[0])
|
||||
if !validateDomainChars(entry.Value) {
|
||||
return entry, nil, fmt.Errorf("invalid domain: %q", entry.Value)
|
||||
}
|
||||
default:
|
||||
return entry, nil, fmt.Errorf("unknown rule type: %q", entry.Type)
|
||||
}
|
||||
plen := len(entry.Type) + len(entry.Value) + 1
|
||||
|
||||
// Parse attributes and affiliations
|
||||
var affs []string
|
||||
@@ -140,6 +225,7 @@ func parseEntry(line string) (*Entry, []string, error) {
|
||||
return entry, affs, fmt.Errorf("invalid attribute: %q", attr)
|
||||
}
|
||||
entry.Attrs = append(entry.Attrs, attr)
|
||||
plen += 2 + len(attr)
|
||||
case '&':
|
||||
aff := strings.ToUpper(part[1:])
|
||||
if !validateSiteName(aff) {
|
||||
@@ -147,33 +233,70 @@ func parseEntry(line string) (*Entry, []string, error) {
|
||||
}
|
||||
affs = append(affs, aff)
|
||||
default:
|
||||
return entry, affs, fmt.Errorf("invalid attribute/affiliation: %q", part)
|
||||
return entry, affs, fmt.Errorf("unknown field: %q", part)
|
||||
}
|
||||
}
|
||||
|
||||
if entry.Type != dlc.RuleTypeInclude {
|
||||
slices.Sort(entry.Attrs) // Sort attributes
|
||||
// Formated plain entry: type:domain.tld:@attr1,@attr2
|
||||
var plain strings.Builder
|
||||
plain.Grow(len(entry.Type) + len(entry.Value) + 10)
|
||||
plain.WriteString(entry.Type)
|
||||
plain.WriteByte(':')
|
||||
plain.WriteString(entry.Value)
|
||||
for i, attr := range entry.Attrs {
|
||||
if i == 0 {
|
||||
plain.WriteByte(':')
|
||||
} else {
|
||||
plain.WriteByte(',')
|
||||
}
|
||||
plain.WriteByte('@')
|
||||
plain.WriteString(attr)
|
||||
slices.Sort(entry.Attrs) // Sort attributes
|
||||
// Formated plain entry: type:domain.tld:@attr1,@attr2
|
||||
var plain strings.Builder
|
||||
plain.Grow(plen)
|
||||
plain.WriteString(entry.Type)
|
||||
plain.WriteByte(':')
|
||||
plain.WriteString(entry.Value)
|
||||
for i, attr := range entry.Attrs {
|
||||
if i == 0 {
|
||||
plain.WriteByte(':')
|
||||
} else {
|
||||
plain.WriteByte(',')
|
||||
}
|
||||
entry.Plain = plain.String()
|
||||
plain.WriteByte('@')
|
||||
plain.WriteString(attr)
|
||||
}
|
||||
entry.Plain = plain.String()
|
||||
return entry, affs, nil
|
||||
}
|
||||
|
||||
func parseInclusion(rule string) (*Inclusion, error) {
|
||||
parts := strings.Fields(rule)
|
||||
if len(parts) == 0 {
|
||||
return nil, fmt.Errorf("empty inclusion")
|
||||
}
|
||||
inc := &Inclusion{Source: strings.ToUpper(parts[0])}
|
||||
if !validateSiteName(inc.Source) {
|
||||
return inc, fmt.Errorf("invalid included list name: %q", inc.Source)
|
||||
}
|
||||
|
||||
// Parse attributes
|
||||
for _, part := range parts[1:] {
|
||||
switch part[0] {
|
||||
case '@':
|
||||
attr := strings.ToLower(part[1:])
|
||||
if attr[0] == '-' {
|
||||
battr := attr[1:]
|
||||
if !validateAttrChars(battr) {
|
||||
return inc, fmt.Errorf("invalid ban attribute: %q", battr)
|
||||
}
|
||||
inc.BanAttrs = append(inc.BanAttrs, battr)
|
||||
} else {
|
||||
if !validateAttrChars(attr) {
|
||||
return inc, fmt.Errorf("invalid must attribute: %q", attr)
|
||||
}
|
||||
inc.MustAttrs = append(inc.MustAttrs, attr)
|
||||
}
|
||||
case '&':
|
||||
return inc, fmt.Errorf("affiliation is not allowed for inclusion")
|
||||
default:
|
||||
return inc, fmt.Errorf("unknown field: %q", part)
|
||||
}
|
||||
}
|
||||
return inc, nil
|
||||
}
|
||||
|
||||
func validateDomainChars(domain string) bool {
|
||||
if domain == "" {
|
||||
return false
|
||||
}
|
||||
for i := range domain {
|
||||
c := domain[i]
|
||||
if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.' || c == '-' {
|
||||
@@ -185,9 +308,12 @@ func validateDomainChars(domain string) bool {
|
||||
}
|
||||
|
||||
func validateAttrChars(attr string) bool {
|
||||
if attr == "" {
|
||||
return false
|
||||
}
|
||||
for i := range attr {
|
||||
c := attr[i]
|
||||
if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '!' || c == '-' {
|
||||
if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '!' {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
@@ -196,6 +322,9 @@ func validateAttrChars(attr string) bool {
|
||||
}
|
||||
|
||||
func validateSiteName(name string) bool {
|
||||
if name == "" {
|
||||
return false
|
||||
}
|
||||
for i := range name {
|
||||
c := name[i]
|
||||
if (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '!' || c == '-' {
|
||||
@@ -232,26 +361,23 @@ func (p *Processor) loadData(listName string, path string) error {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
entry, affs, err := parseEntry(line)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in %q at line %d: %w", path, lineIdx, err)
|
||||
typ, rule, isTypeSpecified := strings.Cut(line, ":")
|
||||
if !isTypeSpecified { // Default RuleType
|
||||
typ, rule = dlc.RuleTypeDomain, typ
|
||||
} else {
|
||||
typ = strings.ToLower(typ)
|
||||
}
|
||||
|
||||
if entry.Type == dlc.RuleTypeInclude {
|
||||
inc := &Inclusion{Source: entry.Value}
|
||||
for _, attr := range entry.Attrs {
|
||||
if attr[0] == '-' {
|
||||
inc.BanAttrs = append(inc.BanAttrs, attr[1:])
|
||||
} else {
|
||||
inc.MustAttrs = append(inc.MustAttrs, attr)
|
||||
}
|
||||
}
|
||||
for _, aff := range affs {
|
||||
apl := p.getOrCreateParsedList(aff)
|
||||
apl.Inclusions = append(apl.Inclusions, inc)
|
||||
if typ == dlc.RuleTypeInclude {
|
||||
inc, err := parseInclusion(rule)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in %q at line %d: %w", path, lineIdx, err)
|
||||
}
|
||||
pl.Inclusions = append(pl.Inclusions, inc)
|
||||
} else {
|
||||
entry, affs, err := parseEntry(typ, rule)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in %q at line %d: %w", path, lineIdx, err)
|
||||
}
|
||||
for _, aff := range affs {
|
||||
apl := p.getOrCreateParsedList(aff)
|
||||
apl.Entries = append(apl.Entries, entry)
|
||||
@@ -259,7 +385,7 @@ func (p *Processor) loadData(listName string, path string) error {
|
||||
pl.Entries = append(pl.Entries, entry)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
func isMatchAttrFilters(entry *Entry, incFilter *Inclusion) bool {
|
||||
@@ -360,6 +486,9 @@ func (p *Processor) resolveList(plname string) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(roughMap) == 0 {
|
||||
return fmt.Errorf("empty list")
|
||||
}
|
||||
p.finalMap[plname] = polishList(roughMap)
|
||||
return nil
|
||||
}
|
||||
@@ -387,13 +516,15 @@ func run() error {
|
||||
return fmt.Errorf("failed to loadData: %w", err)
|
||||
}
|
||||
// Generate finalMap
|
||||
processor.finalMap = make(map[string][]*Entry, len(processor.plMap))
|
||||
sitesCount := len(processor.plMap)
|
||||
processor.finalMap = make(map[string][]*Entry, sitesCount)
|
||||
processor.cirIncMap = make(map[string]bool)
|
||||
for plname := range processor.plMap {
|
||||
if err := processor.resolveList(plname); err != nil {
|
||||
return fmt.Errorf("failed to resolveList %q: %w", plname, err)
|
||||
}
|
||||
}
|
||||
processor.plMap = nil
|
||||
|
||||
// Make sure output directory exists
|
||||
if err := os.MkdirAll(*outputDir, 0755); err != nil {
|
||||
@@ -403,47 +534,58 @@ func run() error {
|
||||
for rawEpList := range strings.SplitSeq(*exportLists, ",") {
|
||||
if epList := strings.TrimSpace(rawEpList); epList != "" {
|
||||
entries, exist := processor.finalMap[strings.ToUpper(epList)]
|
||||
if !exist || len(entries) == 0 {
|
||||
fmt.Printf("list %q does not exist or is empty\n", epList)
|
||||
if !exist {
|
||||
fmt.Printf("[Warn] list %q does not exist\n", epList)
|
||||
continue
|
||||
}
|
||||
if err := writePlainList(epList, entries); err != nil {
|
||||
fmt.Printf("failed to write list %q: %v\n", epList, err)
|
||||
fmt.Printf("[Error] failed to write list %q: %v\n", epList, err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("list %q has been generated successfully.\n", epList)
|
||||
fmt.Printf("list %q has been generated successfully\n", epList)
|
||||
}
|
||||
}
|
||||
|
||||
// Generate dat file
|
||||
protoList := new(router.GeoSiteList)
|
||||
for siteName, siteEntries := range processor.finalMap {
|
||||
site, err := makeProtoList(siteName, siteEntries)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to makeProtoList %q: %w", siteName, err)
|
||||
}
|
||||
protoList.Entry = append(protoList.Entry, site)
|
||||
// Generate proto sites
|
||||
gs := &GeoSites{
|
||||
Sites: make([]*router.GeoSite, 0, sitesCount),
|
||||
SiteIdx: make(map[string]int, sitesCount),
|
||||
}
|
||||
// Sort protoList so the marshaled list is reproducible
|
||||
slices.SortFunc(protoList.Entry, func(a, b *router.GeoSite) int {
|
||||
for siteName, siteEntries := range processor.finalMap {
|
||||
gs.Sites = append(gs.Sites, makeProtoList(siteName, siteEntries))
|
||||
}
|
||||
processor = nil
|
||||
// Sort proto sites so the generated file is reproducible
|
||||
slices.SortFunc(gs.Sites, func(a, b *router.GeoSite) int {
|
||||
return strings.Compare(a.CountryCode, b.CountryCode)
|
||||
})
|
||||
for i := range sitesCount {
|
||||
gs.SiteIdx[gs.Sites[i].CountryCode] = i
|
||||
}
|
||||
|
||||
protoBytes, err := proto.Marshal(protoList)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal: %w", err)
|
||||
// Load tasks and generate dat files
|
||||
var tasks []DatTask
|
||||
if *datProfile == "" {
|
||||
tasks = []DatTask{{Name: *outputName, Mode: ModeAll}}
|
||||
} else {
|
||||
var err error
|
||||
tasks, err = loadTasks(*datProfile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to loadTasks %q: %v", *datProfile, err)
|
||||
}
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(*outputDir, *outputName), protoBytes, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write output: %w", err)
|
||||
for _, task := range tasks {
|
||||
if err := gs.assembleDat(task); err != nil {
|
||||
fmt.Printf("[Error] failed to assembleDat %q: %v", task.Name, err)
|
||||
}
|
||||
}
|
||||
fmt.Printf("%q has been generated successfully.\n", *outputName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if err := run(); err != nil {
|
||||
fmt.Printf("Fatal error: %v\n", err)
|
||||
fmt.Printf("[Fatal] critical error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user