Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
117a97ba9e |
@@ -1,141 +0,0 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "data/**"
|
||||
- "tools/**"
|
||||
- ".gitea/workflows/build.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "data/**"
|
||||
- "tools/**"
|
||||
- ".gitea/workflows/build.yml"
|
||||
schedule:
|
||||
- cron: "17 3 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GITEA_URL: ${{ github.server_url }}
|
||||
GITEA_REPO: ${{ github.repository }}
|
||||
# Автоматического токена обычно хватает. Если у раннера нет прав на push
|
||||
# или на релизы — заведите секрет RELEASE_TOKEN с правами repo:write.
|
||||
AUTO_TOKEN: ${{ github.token }}
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Окружение
|
||||
run: |
|
||||
set -eu
|
||||
missing=""
|
||||
for cmd in python3 curl unzip tar git; do
|
||||
command -v "$cmd" >/dev/null 2>&1 || missing="$missing $cmd"
|
||||
done
|
||||
if [ -n "$missing" ]; then
|
||||
echo "не хватает:$missing — доустанавливаем"
|
||||
sudo=""; [ "$(id -u)" -eq 0 ] || sudo=sudo
|
||||
$sudo apt-get update -qq
|
||||
$sudo apt-get install -y -qq python3 curl unzip tar git ca-certificates
|
||||
fi
|
||||
python3 --version
|
||||
|
||||
- name: Проверка исходников
|
||||
run: python3 tools/build.py --check
|
||||
|
||||
- name: Тесты
|
||||
run: python3 -m unittest discover -s tools -t tools
|
||||
|
||||
- name: Внешние инструменты
|
||||
run: |
|
||||
sh tools/fetch-tools.sh "$GITHUB_WORKSPACE/bin"
|
||||
echo "$GITHUB_WORKSPACE/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Сборка
|
||||
run: |
|
||||
SOURCE_DATE_EPOCH=$(git log -1 --format=%ct) python3 tools/build.py
|
||||
|
||||
- name: Проверка .dat настоящим Xray
|
||||
run: |
|
||||
# Роутинг-правило заставляет Xray реально распарсить файл:
|
||||
# битый .dat или отсутствующий тег дают ненулевой код возврата.
|
||||
cat > /tmp/xray-test.json <<'EOF'
|
||||
{
|
||||
"inbounds": [{"port": 10800, "protocol": "socks"}],
|
||||
"outbounds": [{"protocol": "freedom", "tag": "direct"}],
|
||||
"routing": {
|
||||
"rules": [
|
||||
{"type": "field", "domain": ["ext:ipcheck.dat:ipcheck"], "outboundTag": "direct"},
|
||||
{"type": "field", "domain": ["ext:geosite.dat:leak-test"], "outboundTag": "direct"}
|
||||
]
|
||||
}
|
||||
}
|
||||
EOF
|
||||
XRAY_LOCATION_ASSET="$GITHUB_WORKSPACE/dist" xray run -test -c /tmp/xray-test.json
|
||||
|
||||
- name: Проверка rule-set для sing-box
|
||||
run: |
|
||||
sing-box rule-set decompile --output /tmp/ipcheck.json dist/sing-box/ipcheck.srs
|
||||
grep -q '"2ip.ru"' /tmp/ipcheck.json
|
||||
|
||||
- name: Описание релиза
|
||||
id: notes
|
||||
run: |
|
||||
set -eu
|
||||
tag=$(date -u '+%Y.%m.%d-%H%M')
|
||||
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "Собрано из \`$(git rev-parse --short HEAD)\`."
|
||||
echo
|
||||
echo '| Категория | Правил |'
|
||||
echo '| --- | ---: |'
|
||||
python3 -c "import json; [print(f'| {k} | {v} |') for k, v in json.load(open('dist/metadata.json'))['categories'].items()]"
|
||||
echo
|
||||
echo "sha256(ipcheck.dat) = \`$(cut -d' ' -f1 dist/ipcheck.dat.sha256sum)\`"
|
||||
} > /tmp/notes.md
|
||||
cat /tmp/notes.md
|
||||
|
||||
- name: Публикация в ветку release
|
||||
if: github.event_name != 'pull_request'
|
||||
run: |
|
||||
set -eu
|
||||
token="${RELEASE_TOKEN:-$AUTO_TOKEN}"
|
||||
[ -n "$token" ] || { echo "нет токена для push" >&2; exit 1; }
|
||||
host="${GITEA_URL#*://}"
|
||||
scheme="${GITEA_URL%%://*}"
|
||||
|
||||
cp README.md LICENSE dist/
|
||||
cd dist
|
||||
git init -q -b release
|
||||
git config user.name "gitea-actions"
|
||||
git config user.email "gitea-actions@noreply.local"
|
||||
git add .
|
||||
git commit -q -m "build $(date -u '+%Y-%m-%d %H:%M UTC') ($GITHUB_SHA)"
|
||||
git push -qf "$scheme://gitea-actions:$token@$host/$GITEA_REPO.git" release
|
||||
cd ..
|
||||
rm -f dist/README.md dist/LICENSE
|
||||
echo "ветка release обновлена"
|
||||
|
||||
- name: Релиз в Gitea
|
||||
if: github.event_name != 'pull_request'
|
||||
run: |
|
||||
set -eu
|
||||
export GITEA_TOKEN="${RELEASE_TOKEN:-$AUTO_TOKEN}"
|
||||
# Собираем архив вне dist/, иначе tar пакует сам себя на лету.
|
||||
tar -czf /tmp/ipcheck-domains-all.tar.gz -C dist .
|
||||
mv /tmp/ipcheck-domains-all.tar.gz dist/
|
||||
sh tools/gitea-release.sh "${{ steps.notes.outputs.tag }}" /tmp/notes.md \
|
||||
dist/ipcheck.dat \
|
||||
dist/ipcheck.dat.sha256sum \
|
||||
dist/geosite.dat \
|
||||
dist/geosite.dat.sha256sum \
|
||||
dist/sing-box/ipcheck.srs \
|
||||
dist/mihomo/ipcheck.mrs \
|
||||
dist/metadata.json \
|
||||
dist/SHA256SUMS \
|
||||
dist/ipcheck-domains-all.tar.gz
|
||||
@@ -1,4 +0,0 @@
|
||||
dist/
|
||||
bin/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
1c1c3f6ca2303141ecbc62b7eb948a62c173a8b8b28f2b4b86cf737a9aa52427 adguard/fraud-score.txt
|
||||
5ab46aa0ca887111b61135b66f9447f51b55c93d6588252ea3490c93919aae97 adguard/geoip-db.txt
|
||||
ec0e452c5a359e2156056da21330a0440b0e817aff1b81b559a80ad39b85e1a9 adguard/ip-lookup.txt
|
||||
9412216ee6701726a4f2c8fe4d57546d78aa0be624adc69c0bdfc902749edef7 adguard/ipcheck.txt
|
||||
1b5c5b3341f2e95adb7c7664bab3b8a02270bb646e7045196ca584511087f4e1 adguard/leak-test.txt
|
||||
c475f3d485e2830b904621857958908345e2baafe0e410207c61163ad5b9fd64 clash/fraud-score.list
|
||||
faddbb13d3c9a2a47c559d23aa0fadba0018d461dc292e60219b8a56634877b7 clash/fraud-score.yaml
|
||||
c7913328a44f93b61a41545aceb6c60557f9ee1eb2d73b3d6180a05ec12dbd72 clash/geoip-db.list
|
||||
77dfcb3cfa6efad45adfd2e5f5c310fae148099907f783336f77451f2309e1e6 clash/geoip-db.yaml
|
||||
664e02852f5b132103d475055ddc8af2f0782d02cba607e5d1444ef8d8770cb8 clash/ip-lookup.list
|
||||
d653aa6b6c8b9fcc980489b3a65c10cad3799c1260a98488abae49026bab450f clash/ip-lookup.yaml
|
||||
690fcdab44b059c31be6c2656de8cf27b7568d7868cedcb1542adfa6d958a6fe clash/ipcheck.list
|
||||
896904b80a6fb32b64fc98fa36e3f3018dcdc8bf5c493e3151478bbfdeff6cd4 clash/ipcheck.yaml
|
||||
2ce43be4508d5b44c9283f4c9831ddb541ba6386293d50f752f7e3eb6c740638 clash/leak-test.list
|
||||
4c73658860aacb7597d979c335b0b91eb0fed567789c746bb413ac846cca6bea clash/leak-test.yaml
|
||||
40ac1b41d51aecabf178ff98ade8dfe2587bd6b49c66260331990774588b040c geosite.dat
|
||||
67df6d4260d20cc0a77588ff264a6672293b30cbfd406fd0852cd10e07c2bbfa geosite.dat.sha256sum
|
||||
40ac1b41d51aecabf178ff98ade8dfe2587bd6b49c66260331990774588b040c ipcheck.dat
|
||||
70ee904b3608e34b985598f8c4d9d70b4deca9778fe1d709fff038688efecb6b ipcheck.dat.sha256sum
|
||||
817556a7f8f3d74499d8d77459ead2e9d84a42c45e3ba2ac107928c79482e309 json/fraud-score.json
|
||||
e5172358f352144b1f13ec8442e1e9b3adf490c86dd3411617be408b524179f3 json/geoip-db.json
|
||||
60b9e442f55454c3d168c0f0fec7ff768be54d04adada068d294f71994d8cbd3 json/ip-lookup.json
|
||||
22dcc71ef7c7cdd92992037022225f390e00d35c75c49151d3c09fa0abaf1d3a json/ipcheck.json
|
||||
a4ebdd1db3795f1726d2c4a9bbe3564bf8f2d1ccb575f93e0799ee3a6a1fae9b json/leak-test.json
|
||||
40ad4fdba0c2709b6240910756ad59337598dc7482c08dbfb95a66130d0ec8cf metadata.json
|
||||
4909af8cff1593c6e264ecad0e69887bcb881ba3870fa47b0935c2c33a4de63f mihomo/fraud-score.mrs
|
||||
467e31f4829571d49360f081ab6fbf7453714ee3339fc96e69fc9409b40cc0b3 mihomo/geoip-db.mrs
|
||||
4a3fe564d7e22ce2e7e317b1727edea9503f08f779ee8969e3eca4b1bcd07d9d mihomo/ip-lookup.mrs
|
||||
37340c6a9d3e3039eb34d74776d76c88393588cbfc8f273351bc5b7466431fd9 mihomo/ipcheck.mrs
|
||||
b3e409367c2240fb61f88867686e0708ef9ef3761da1c2b2d5afefa10853ed3c mihomo/leak-test.mrs
|
||||
f37da541dfebd9389d911b899485e38cd85843a4018733caa4959c5853692662 sing-box/fraud-score.json
|
||||
68bd460e9119e4b792254ca79b40267a33968d501f40c1841cbad2e2a8448318 sing-box/fraud-score.srs
|
||||
a08238aa218a4dbe0949b2aad1b5400d447b75fd09c1dc825189c3d657043585 sing-box/geoip-db.json
|
||||
8d3a1c06cec32a290b868ed27c5432650e11b253b120e293423070a7d8146aeb sing-box/geoip-db.srs
|
||||
0bc4678629314106f6f4b2d8bcc58c03ea1b55f0971306a9a77f8e5d93aa81b0 sing-box/ip-lookup.json
|
||||
76a4e6ee48b47c0b25522f6c203873a8560f1fe55dd99d381ff46f507e87e5e3 sing-box/ip-lookup.srs
|
||||
538890422358a2d094f15ff24e3e21835ab0e7a3ee6d3abd978c4ab4aeb316a8 sing-box/ipcheck.json
|
||||
feffddcb48734fc5bbfd72d8d85d3d533ae225edae935ea45c9a8b475907c86a sing-box/ipcheck.srs
|
||||
01bd53410b788ca85e0b09ce729da1355681e422c3c49068da44c9284e457742 sing-box/leak-test.json
|
||||
c90764bb5463108ff734f531d7c1f7288fa4a1672c6c79a53d769e63f85a5763 sing-box/leak-test.srs
|
||||
b67ff3c4ea1c371be2d777dcf2b88614673ffd7532f2f8cff7377e7993aa91b6 src/fraud-score.txt
|
||||
0514a090c68e117030b2eb478360f0eba79740db1c02d5ade6ea7c96e8c864e2 src/geoip-db.txt
|
||||
0bb286f849b93defebceaec7ec6f3f820aaf3821f7954590b5e7d46a7b559726 src/ip-lookup.txt
|
||||
c6f4b98ddcdafcafe7bd2c4c580a1ee020d10841e41e9be9404115243bbfdcf8 src/ipcheck.txt
|
||||
eaffb62f3e7edb9746ca12e15478091127d3891589c3f2cdc15d47fa326df238 src/leak-test.txt
|
||||
ae44fea51df8eb856c094068205d2b574dea2e54abd2c9832e6cf1b92c916fb4 txt/fraud-score.txt
|
||||
903d74a61b3832168e793ca3eb940d162de64f965f8df247f4a561791e8690b9 txt/geoip-db.txt
|
||||
a9f7ee701e36a0b5210945f6d5799447be35a8fe3b412781431b4d2660542aa0 txt/ip-lookup.txt
|
||||
1fad7449ab2f8beb6f46a7936c2b53d045eba34945bed9e5e36b658eb09338ad txt/ipcheck.txt
|
||||
5f3438791d5c49b345d26fd8c9bfdba582bed80c3c314fa572e0cf66135e626e txt/leak-test.txt
|
||||
ff655b22d14d5c0a267ca8fea34ad6dcc18d40a8aef1c0b6a1c6e8a34e2238c2 update-geosite.sh
|
||||
@@ -0,0 +1,12 @@
|
||||
! ipcheck-domains — категория: fraud-score
|
||||
! Правил: 9. Файл генерируется автоматически, не редактируйте вручную.
|
||||
! Источник: data/fraud-score.txt
|
||||
||criminalip.io^
|
||||
||fraudguard.io^
|
||||
||getipintel.net^
|
||||
||iphub.info^
|
||||
||ipqualityscore.com^
|
||||
||proxycheck.io^
|
||||
||scamalytics.com^
|
||||
||spur.us^
|
||||
||vpnapi.io^
|
||||
@@ -0,0 +1,12 @@
|
||||
! ipcheck-domains — категория: geoip-db
|
||||
! Правил: 9. Файл генерируется автоматически, не редактируйте вручную.
|
||||
! Источник: data/geoip-db.txt
|
||||
||bigdatacloud.net^
|
||||
||db-ip.com^
|
||||
||ip2location.com^
|
||||
||ipbase.com^
|
||||
||ipdata.co^
|
||||
||ipgeolocation.io^
|
||||
||ipregistry.co^
|
||||
||ipwhois.io^
|
||||
||maxmind.com^
|
||||
@@ -0,0 +1,21 @@
|
||||
! ipcheck-domains — категория: ip-lookup
|
||||
! Правил: 18. Файл генерируется автоматически, не редактируйте вручную.
|
||||
! Источник: data/ip-lookup.txt
|
||||
||2ip.io^
|
||||
||2ip.ru^
|
||||
||icanhazip.com^
|
||||
||ifconfig.co^
|
||||
||ifconfig.io^
|
||||
||ifconfig.me^
|
||||
||ip-api.com^
|
||||
||ip.sb^
|
||||
||ipaddress.is^
|
||||
||ipapi.co^
|
||||
||ipapi.is^
|
||||
||ipify.org^
|
||||
||ipinfo.io^
|
||||
||myip.com^
|
||||
||whatismyip.com^
|
||||
||whatismyipaddress.com^
|
||||
||whoer.com^
|
||||
||whoer.net^
|
||||
@@ -0,0 +1,42 @@
|
||||
! ipcheck-domains — категория: ipcheck
|
||||
! Правил: 39. Файл генерируется автоматически, не редактируйте вручную.
|
||||
! Источник: data/ipcheck.txt
|
||||
||2ip.io^
|
||||
||2ip.ru^
|
||||
||bigdatacloud.net^
|
||||
||browserleaks.com^
|
||||
||criminalip.io^
|
||||
||db-ip.com^
|
||||
||dnsleaktest.com^
|
||||
||fraudguard.io^
|
||||
||getipintel.net^
|
||||
||icanhazip.com^
|
||||
||ifconfig.co^
|
||||
||ifconfig.io^
|
||||
||ifconfig.me^
|
||||
||ip-api.com^
|
||||
||ip.sb^
|
||||
||ip2location.com^
|
||||
||ipaddress.is^
|
||||
||ipapi.co^
|
||||
||ipapi.is^
|
||||
||ipbase.com^
|
||||
||ipdata.co^
|
||||
||ipgeolocation.io^
|
||||
||iphub.info^
|
||||
||ipify.org^
|
||||
||ipinfo.io^
|
||||
||ipleak.net^
|
||||
||ipqualityscore.com^
|
||||
||ipregistry.co^
|
||||
||ipwhois.io^
|
||||
||maxmind.com^
|
||||
||myip.com^
|
||||
||proxycheck.io^
|
||||
||scamalytics.com^
|
||||
||spur.us^
|
||||
||vpnapi.io^
|
||||
||whatismyip.com^
|
||||
||whatismyipaddress.com^
|
||||
||whoer.com^
|
||||
||whoer.net^
|
||||
@@ -0,0 +1,6 @@
|
||||
! ipcheck-domains — категория: leak-test
|
||||
! Правил: 3. Файл генерируется автоматически, не редактируйте вручную.
|
||||
! Источник: data/leak-test.txt
|
||||
||browserleaks.com^
|
||||
||dnsleaktest.com^
|
||||
||ipleak.net^
|
||||
@@ -0,0 +1,12 @@
|
||||
# ipcheck-domains — категория: fraud-score
|
||||
# Правил: 9. Файл генерируется автоматически, не редактируйте вручную.
|
||||
# Источник: data/fraud-score.txt
|
||||
DOMAIN-SUFFIX,criminalip.io
|
||||
DOMAIN-SUFFIX,fraudguard.io
|
||||
DOMAIN-SUFFIX,getipintel.net
|
||||
DOMAIN-SUFFIX,iphub.info
|
||||
DOMAIN-SUFFIX,ipqualityscore.com
|
||||
DOMAIN-SUFFIX,proxycheck.io
|
||||
DOMAIN-SUFFIX,scamalytics.com
|
||||
DOMAIN-SUFFIX,spur.us
|
||||
DOMAIN-SUFFIX,vpnapi.io
|
||||
@@ -0,0 +1,13 @@
|
||||
# ipcheck-domains — категория: fraud-score
|
||||
# Правил: 9. Файл генерируется автоматически, не редактируйте вручную.
|
||||
# Источник: data/fraud-score.txt
|
||||
payload:
|
||||
- '+.criminalip.io'
|
||||
- '+.fraudguard.io'
|
||||
- '+.getipintel.net'
|
||||
- '+.iphub.info'
|
||||
- '+.ipqualityscore.com'
|
||||
- '+.proxycheck.io'
|
||||
- '+.scamalytics.com'
|
||||
- '+.spur.us'
|
||||
- '+.vpnapi.io'
|
||||
@@ -0,0 +1,12 @@
|
||||
# ipcheck-domains — категория: geoip-db
|
||||
# Правил: 9. Файл генерируется автоматически, не редактируйте вручную.
|
||||
# Источник: data/geoip-db.txt
|
||||
DOMAIN-SUFFIX,bigdatacloud.net
|
||||
DOMAIN-SUFFIX,db-ip.com
|
||||
DOMAIN-SUFFIX,ip2location.com
|
||||
DOMAIN-SUFFIX,ipbase.com
|
||||
DOMAIN-SUFFIX,ipdata.co
|
||||
DOMAIN-SUFFIX,ipgeolocation.io
|
||||
DOMAIN-SUFFIX,ipregistry.co
|
||||
DOMAIN-SUFFIX,ipwhois.io
|
||||
DOMAIN-SUFFIX,maxmind.com
|
||||
@@ -0,0 +1,13 @@
|
||||
# ipcheck-domains — категория: geoip-db
|
||||
# Правил: 9. Файл генерируется автоматически, не редактируйте вручную.
|
||||
# Источник: data/geoip-db.txt
|
||||
payload:
|
||||
- '+.bigdatacloud.net'
|
||||
- '+.db-ip.com'
|
||||
- '+.ip2location.com'
|
||||
- '+.ipbase.com'
|
||||
- '+.ipdata.co'
|
||||
- '+.ipgeolocation.io'
|
||||
- '+.ipregistry.co'
|
||||
- '+.ipwhois.io'
|
||||
- '+.maxmind.com'
|
||||
@@ -0,0 +1,21 @@
|
||||
# ipcheck-domains — категория: ip-lookup
|
||||
# Правил: 18. Файл генерируется автоматически, не редактируйте вручную.
|
||||
# Источник: data/ip-lookup.txt
|
||||
DOMAIN-SUFFIX,2ip.io
|
||||
DOMAIN-SUFFIX,2ip.ru
|
||||
DOMAIN-SUFFIX,icanhazip.com
|
||||
DOMAIN-SUFFIX,ifconfig.co
|
||||
DOMAIN-SUFFIX,ifconfig.io
|
||||
DOMAIN-SUFFIX,ifconfig.me
|
||||
DOMAIN-SUFFIX,ip-api.com
|
||||
DOMAIN-SUFFIX,ip.sb
|
||||
DOMAIN-SUFFIX,ipaddress.is
|
||||
DOMAIN-SUFFIX,ipapi.co
|
||||
DOMAIN-SUFFIX,ipapi.is
|
||||
DOMAIN-SUFFIX,ipify.org
|
||||
DOMAIN-SUFFIX,ipinfo.io
|
||||
DOMAIN-SUFFIX,myip.com
|
||||
DOMAIN-SUFFIX,whatismyip.com
|
||||
DOMAIN-SUFFIX,whatismyipaddress.com
|
||||
DOMAIN-SUFFIX,whoer.com
|
||||
DOMAIN-SUFFIX,whoer.net
|
||||
@@ -0,0 +1,22 @@
|
||||
# ipcheck-domains — категория: ip-lookup
|
||||
# Правил: 18. Файл генерируется автоматически, не редактируйте вручную.
|
||||
# Источник: data/ip-lookup.txt
|
||||
payload:
|
||||
- '+.2ip.io'
|
||||
- '+.2ip.ru'
|
||||
- '+.icanhazip.com'
|
||||
- '+.ifconfig.co'
|
||||
- '+.ifconfig.io'
|
||||
- '+.ifconfig.me'
|
||||
- '+.ip-api.com'
|
||||
- '+.ip.sb'
|
||||
- '+.ipaddress.is'
|
||||
- '+.ipapi.co'
|
||||
- '+.ipapi.is'
|
||||
- '+.ipify.org'
|
||||
- '+.ipinfo.io'
|
||||
- '+.myip.com'
|
||||
- '+.whatismyip.com'
|
||||
- '+.whatismyipaddress.com'
|
||||
- '+.whoer.com'
|
||||
- '+.whoer.net'
|
||||
@@ -0,0 +1,42 @@
|
||||
# ipcheck-domains — категория: ipcheck
|
||||
# Правил: 39. Файл генерируется автоматически, не редактируйте вручную.
|
||||
# Источник: data/ipcheck.txt
|
||||
DOMAIN-SUFFIX,2ip.io
|
||||
DOMAIN-SUFFIX,2ip.ru
|
||||
DOMAIN-SUFFIX,bigdatacloud.net
|
||||
DOMAIN-SUFFIX,browserleaks.com
|
||||
DOMAIN-SUFFIX,criminalip.io
|
||||
DOMAIN-SUFFIX,db-ip.com
|
||||
DOMAIN-SUFFIX,dnsleaktest.com
|
||||
DOMAIN-SUFFIX,fraudguard.io
|
||||
DOMAIN-SUFFIX,getipintel.net
|
||||
DOMAIN-SUFFIX,icanhazip.com
|
||||
DOMAIN-SUFFIX,ifconfig.co
|
||||
DOMAIN-SUFFIX,ifconfig.io
|
||||
DOMAIN-SUFFIX,ifconfig.me
|
||||
DOMAIN-SUFFIX,ip-api.com
|
||||
DOMAIN-SUFFIX,ip.sb
|
||||
DOMAIN-SUFFIX,ip2location.com
|
||||
DOMAIN-SUFFIX,ipaddress.is
|
||||
DOMAIN-SUFFIX,ipapi.co
|
||||
DOMAIN-SUFFIX,ipapi.is
|
||||
DOMAIN-SUFFIX,ipbase.com
|
||||
DOMAIN-SUFFIX,ipdata.co
|
||||
DOMAIN-SUFFIX,ipgeolocation.io
|
||||
DOMAIN-SUFFIX,iphub.info
|
||||
DOMAIN-SUFFIX,ipify.org
|
||||
DOMAIN-SUFFIX,ipinfo.io
|
||||
DOMAIN-SUFFIX,ipleak.net
|
||||
DOMAIN-SUFFIX,ipqualityscore.com
|
||||
DOMAIN-SUFFIX,ipregistry.co
|
||||
DOMAIN-SUFFIX,ipwhois.io
|
||||
DOMAIN-SUFFIX,maxmind.com
|
||||
DOMAIN-SUFFIX,myip.com
|
||||
DOMAIN-SUFFIX,proxycheck.io
|
||||
DOMAIN-SUFFIX,scamalytics.com
|
||||
DOMAIN-SUFFIX,spur.us
|
||||
DOMAIN-SUFFIX,vpnapi.io
|
||||
DOMAIN-SUFFIX,whatismyip.com
|
||||
DOMAIN-SUFFIX,whatismyipaddress.com
|
||||
DOMAIN-SUFFIX,whoer.com
|
||||
DOMAIN-SUFFIX,whoer.net
|
||||
@@ -0,0 +1,43 @@
|
||||
# ipcheck-domains — категория: ipcheck
|
||||
# Правил: 39. Файл генерируется автоматически, не редактируйте вручную.
|
||||
# Источник: data/ipcheck.txt
|
||||
payload:
|
||||
- '+.2ip.io'
|
||||
- '+.2ip.ru'
|
||||
- '+.bigdatacloud.net'
|
||||
- '+.browserleaks.com'
|
||||
- '+.criminalip.io'
|
||||
- '+.db-ip.com'
|
||||
- '+.dnsleaktest.com'
|
||||
- '+.fraudguard.io'
|
||||
- '+.getipintel.net'
|
||||
- '+.icanhazip.com'
|
||||
- '+.ifconfig.co'
|
||||
- '+.ifconfig.io'
|
||||
- '+.ifconfig.me'
|
||||
- '+.ip-api.com'
|
||||
- '+.ip.sb'
|
||||
- '+.ip2location.com'
|
||||
- '+.ipaddress.is'
|
||||
- '+.ipapi.co'
|
||||
- '+.ipapi.is'
|
||||
- '+.ipbase.com'
|
||||
- '+.ipdata.co'
|
||||
- '+.ipgeolocation.io'
|
||||
- '+.iphub.info'
|
||||
- '+.ipify.org'
|
||||
- '+.ipinfo.io'
|
||||
- '+.ipleak.net'
|
||||
- '+.ipqualityscore.com'
|
||||
- '+.ipregistry.co'
|
||||
- '+.ipwhois.io'
|
||||
- '+.maxmind.com'
|
||||
- '+.myip.com'
|
||||
- '+.proxycheck.io'
|
||||
- '+.scamalytics.com'
|
||||
- '+.spur.us'
|
||||
- '+.vpnapi.io'
|
||||
- '+.whatismyip.com'
|
||||
- '+.whatismyipaddress.com'
|
||||
- '+.whoer.com'
|
||||
- '+.whoer.net'
|
||||
@@ -0,0 +1,6 @@
|
||||
# ipcheck-domains — категория: leak-test
|
||||
# Правил: 3. Файл генерируется автоматически, не редактируйте вручную.
|
||||
# Источник: data/leak-test.txt
|
||||
DOMAIN-SUFFIX,browserleaks.com
|
||||
DOMAIN-SUFFIX,dnsleaktest.com
|
||||
DOMAIN-SUFFIX,ipleak.net
|
||||
@@ -0,0 +1,7 @@
|
||||
# ipcheck-domains — категория: leak-test
|
||||
# Правил: 3. Файл генерируется автоматически, не редактируйте вручную.
|
||||
# Источник: data/leak-test.txt
|
||||
payload:
|
||||
- '+.browserleaks.com'
|
||||
- '+.dnsleaktest.com'
|
||||
- '+.ipleak.net'
|
||||
@@ -1,22 +0,0 @@
|
||||
# ip-lookup — сервисы "какой у меня IP" и общая информация об IP.
|
||||
# Формат строки: [type:]value [@attr ...] Комментарии — от символа "#".
|
||||
# Без префикса значение трактуется как domain: (сам домен + все поддомены).
|
||||
|
||||
2ip.ru
|
||||
2ip.io
|
||||
whoer.net
|
||||
whoer.com
|
||||
whatismyipaddress.com
|
||||
whatismyip.com
|
||||
myip.com
|
||||
ipinfo.io
|
||||
ip-api.com
|
||||
ipapi.co
|
||||
ipapi.is
|
||||
ipify.org
|
||||
ifconfig.me
|
||||
ifconfig.co
|
||||
ifconfig.io
|
||||
icanhazip.com
|
||||
ip.sb
|
||||
ipaddress.is
|
||||
@@ -1,7 +0,0 @@
|
||||
# ipcheck — сводная категория: всё, что определяет или проверяет IP-адрес.
|
||||
# Именно её используют в роутинге как geosite:ipcheck.
|
||||
|
||||
include:ip-lookup
|
||||
include:leak-test
|
||||
include:geoip-db
|
||||
include:fraud-score
|
||||
@@ -1,5 +0,0 @@
|
||||
# leak-test — тесты утечек: WebRTC, DNS, отпечаток браузера.
|
||||
|
||||
browserleaks.com
|
||||
ipleak.net
|
||||
dnsleaktest.com
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
|
||||
³
|
||||
FRAUD-SCORE␍criminalip.io␍fraudguard.iogetipintel.net
|
||||
iphub.infoipqualityscore.com␍proxycheck.ioscamalytics.comspur.us␍ vpnapi.io
|
||||
|
||||
GEOIP-DBbigdatacloud.net␍ db-ip.comip2location.com
|
||||
ipbase.com␍ ipdata.coipgeolocation.io␍ipregistry.co
|
||||
ipwhois.iomaxmind.com
|
||||
«
|
||||
IP-LOOKUP
|
||||
2ip.io
|
||||
2ip.ru␍icanhazip.comifconfig.coifconfig.ioifconfig.me
|
||||
ip-api.com ip.sbipaddress.isipapi.coipapi.is␍ ipify.org␍ ipinfo.iomyip.comwhatismyip.comwhatismyipaddress.com␍ whoer.com␍ whoer.net
|
||||
|
||||
IPCHECK
|
||||
2ip.io
|
||||
2ip.rubigdatacloud.netbrowserleaks.com␍criminalip.io␍ db-ip.comdnsleaktest.com␍fraudguard.iogetipintel.net␍icanhazip.comifconfig.coifconfig.ioifconfig.me
|
||||
ip-api.com ip.sbip2location.comipaddress.isipapi.coipapi.is
|
||||
ipbase.com␍ ipdata.coipgeolocation.io
|
||||
iphub.info␍ ipify.org␍ ipinfo.io
|
||||
ipleak.netipqualityscore.com␍ipregistry.co
|
||||
ipwhois.iomaxmind.commyip.com␍proxycheck.ioscamalytics.comspur.us␍ vpnapi.iowhatismyip.comwhatismyipaddress.com␍ whoer.com␍ whoer.net
|
||||
F
|
||||
LEAK-TESTbrowserleaks.comdnsleaktest.com
|
||||
ipleak.net
|
||||
@@ -0,0 +1 @@
|
||||
40ac1b41d51aecabf178ff98ade8dfe2587bd6b49c66260331990774588b040c geosite.dat
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
|
||||
³
|
||||
FRAUD-SCORE␍criminalip.io␍fraudguard.iogetipintel.net
|
||||
iphub.infoipqualityscore.com␍proxycheck.ioscamalytics.comspur.us␍ vpnapi.io
|
||||
|
||||
GEOIP-DBbigdatacloud.net␍ db-ip.comip2location.com
|
||||
ipbase.com␍ ipdata.coipgeolocation.io␍ipregistry.co
|
||||
ipwhois.iomaxmind.com
|
||||
«
|
||||
IP-LOOKUP
|
||||
2ip.io
|
||||
2ip.ru␍icanhazip.comifconfig.coifconfig.ioifconfig.me
|
||||
ip-api.com ip.sbipaddress.isipapi.coipapi.is␍ ipify.org␍ ipinfo.iomyip.comwhatismyip.comwhatismyipaddress.com␍ whoer.com␍ whoer.net
|
||||
|
||||
IPCHECK
|
||||
2ip.io
|
||||
2ip.rubigdatacloud.netbrowserleaks.com␍criminalip.io␍ db-ip.comdnsleaktest.com␍fraudguard.iogetipintel.net␍icanhazip.comifconfig.coifconfig.ioifconfig.me
|
||||
ip-api.com ip.sbip2location.comipaddress.isipapi.coipapi.is
|
||||
ipbase.com␍ ipdata.coipgeolocation.io
|
||||
iphub.info␍ ipify.org␍ ipinfo.io
|
||||
ipleak.netipqualityscore.com␍ipregistry.co
|
||||
ipwhois.iomaxmind.commyip.com␍proxycheck.ioscamalytics.comspur.us␍ vpnapi.iowhatismyip.comwhatismyipaddress.com␍ whoer.com␍ whoer.net
|
||||
F
|
||||
LEAK-TESTbrowserleaks.comdnsleaktest.com
|
||||
ipleak.net
|
||||
@@ -0,0 +1 @@
|
||||
40ac1b41d51aecabf178ff98ade8dfe2587bd6b49c66260331990774588b040c ipcheck.dat
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"category": "fraud-score",
|
||||
"count": 9,
|
||||
"rules": [
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "criminalip.io"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "fraudguard.io"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "getipintel.net"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "iphub.info"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipqualityscore.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "proxycheck.io"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "scamalytics.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "spur.us"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "vpnapi.io"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"category": "geoip-db",
|
||||
"count": 9,
|
||||
"rules": [
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "bigdatacloud.net"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "db-ip.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ip2location.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipbase.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipdata.co"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipgeolocation.io"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipregistry.co"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipwhois.io"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "maxmind.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"category": "ip-lookup",
|
||||
"count": 18,
|
||||
"rules": [
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "2ip.io"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "2ip.ru"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "icanhazip.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ifconfig.co"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ifconfig.io"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ifconfig.me"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ip-api.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ip.sb"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipaddress.is"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipapi.co"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipapi.is"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipify.org"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipinfo.io"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "myip.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "whatismyip.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "whatismyipaddress.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "whoer.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "whoer.net"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
{
|
||||
"category": "ipcheck",
|
||||
"count": 39,
|
||||
"rules": [
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "2ip.io"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "2ip.ru"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "bigdatacloud.net"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "browserleaks.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "criminalip.io"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "db-ip.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "dnsleaktest.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "fraudguard.io"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "getipintel.net"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "icanhazip.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ifconfig.co"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ifconfig.io"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ifconfig.me"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ip-api.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ip.sb"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ip2location.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipaddress.is"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipapi.co"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipapi.is"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipbase.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipdata.co"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipgeolocation.io"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "iphub.info"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipify.org"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipinfo.io"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipleak.net"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipqualityscore.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipregistry.co"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipwhois.io"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "maxmind.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "myip.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "proxycheck.io"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "scamalytics.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "spur.us"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "vpnapi.io"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "whatismyip.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "whatismyipaddress.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "whoer.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "whoer.net"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"category": "leak-test",
|
||||
"count": 3,
|
||||
"rules": [
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "browserleaks.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "dnsleaktest.com"
|
||||
},
|
||||
{
|
||||
"type": "domain",
|
||||
"value": "ipleak.net"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"built_at": "2026-08-07T11:33:25Z",
|
||||
"geosite_dat_sha256": "40ac1b41d51aecabf178ff98ade8dfe2587bd6b49c66260331990774588b040c",
|
||||
"geosite_dat_size": 1420,
|
||||
"total_rules": 78,
|
||||
"categories": {
|
||||
"fraud-score": 9,
|
||||
"geoip-db": 9,
|
||||
"ip-lookup": 18,
|
||||
"ipcheck": 39,
|
||||
"leak-test": 3
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"version": 1,
|
||||
"rules": [
|
||||
{
|
||||
"domain": [
|
||||
"criminalip.io",
|
||||
"fraudguard.io",
|
||||
"getipintel.net",
|
||||
"iphub.info",
|
||||
"ipqualityscore.com",
|
||||
"proxycheck.io",
|
||||
"scamalytics.com",
|
||||
"spur.us",
|
||||
"vpnapi.io"
|
||||
],
|
||||
"domain_suffix": [
|
||||
".criminalip.io",
|
||||
".fraudguard.io",
|
||||
".getipintel.net",
|
||||
".iphub.info",
|
||||
".ipqualityscore.com",
|
||||
".proxycheck.io",
|
||||
".scamalytics.com",
|
||||
".spur.us",
|
||||
".vpnapi.io"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"version": 1,
|
||||
"rules": [
|
||||
{
|
||||
"domain": [
|
||||
"bigdatacloud.net",
|
||||
"db-ip.com",
|
||||
"ip2location.com",
|
||||
"ipbase.com",
|
||||
"ipdata.co",
|
||||
"ipgeolocation.io",
|
||||
"ipregistry.co",
|
||||
"ipwhois.io",
|
||||
"maxmind.com"
|
||||
],
|
||||
"domain_suffix": [
|
||||
".bigdatacloud.net",
|
||||
".db-ip.com",
|
||||
".ip2location.com",
|
||||
".ipbase.com",
|
||||
".ipdata.co",
|
||||
".ipgeolocation.io",
|
||||
".ipregistry.co",
|
||||
".ipwhois.io",
|
||||
".maxmind.com"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"version": 1,
|
||||
"rules": [
|
||||
{
|
||||
"domain": [
|
||||
"2ip.io",
|
||||
"2ip.ru",
|
||||
"icanhazip.com",
|
||||
"ifconfig.co",
|
||||
"ifconfig.io",
|
||||
"ifconfig.me",
|
||||
"ip-api.com",
|
||||
"ip.sb",
|
||||
"ipaddress.is",
|
||||
"ipapi.co",
|
||||
"ipapi.is",
|
||||
"ipify.org",
|
||||
"ipinfo.io",
|
||||
"myip.com",
|
||||
"whatismyip.com",
|
||||
"whatismyipaddress.com",
|
||||
"whoer.com",
|
||||
"whoer.net"
|
||||
],
|
||||
"domain_suffix": [
|
||||
".2ip.io",
|
||||
".2ip.ru",
|
||||
".icanhazip.com",
|
||||
".ifconfig.co",
|
||||
".ifconfig.io",
|
||||
".ifconfig.me",
|
||||
".ip-api.com",
|
||||
".ip.sb",
|
||||
".ipaddress.is",
|
||||
".ipapi.co",
|
||||
".ipapi.is",
|
||||
".ipify.org",
|
||||
".ipinfo.io",
|
||||
".myip.com",
|
||||
".whatismyip.com",
|
||||
".whatismyipaddress.com",
|
||||
".whoer.com",
|
||||
".whoer.net"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"version": 1,
|
||||
"rules": [
|
||||
{
|
||||
"domain": [
|
||||
"2ip.io",
|
||||
"2ip.ru",
|
||||
"bigdatacloud.net",
|
||||
"browserleaks.com",
|
||||
"criminalip.io",
|
||||
"db-ip.com",
|
||||
"dnsleaktest.com",
|
||||
"fraudguard.io",
|
||||
"getipintel.net",
|
||||
"icanhazip.com",
|
||||
"ifconfig.co",
|
||||
"ifconfig.io",
|
||||
"ifconfig.me",
|
||||
"ip-api.com",
|
||||
"ip.sb",
|
||||
"ip2location.com",
|
||||
"ipaddress.is",
|
||||
"ipapi.co",
|
||||
"ipapi.is",
|
||||
"ipbase.com",
|
||||
"ipdata.co",
|
||||
"ipgeolocation.io",
|
||||
"iphub.info",
|
||||
"ipify.org",
|
||||
"ipinfo.io",
|
||||
"ipleak.net",
|
||||
"ipqualityscore.com",
|
||||
"ipregistry.co",
|
||||
"ipwhois.io",
|
||||
"maxmind.com",
|
||||
"myip.com",
|
||||
"proxycheck.io",
|
||||
"scamalytics.com",
|
||||
"spur.us",
|
||||
"vpnapi.io",
|
||||
"whatismyip.com",
|
||||
"whatismyipaddress.com",
|
||||
"whoer.com",
|
||||
"whoer.net"
|
||||
],
|
||||
"domain_suffix": [
|
||||
".2ip.io",
|
||||
".2ip.ru",
|
||||
".bigdatacloud.net",
|
||||
".browserleaks.com",
|
||||
".criminalip.io",
|
||||
".db-ip.com",
|
||||
".dnsleaktest.com",
|
||||
".fraudguard.io",
|
||||
".getipintel.net",
|
||||
".icanhazip.com",
|
||||
".ifconfig.co",
|
||||
".ifconfig.io",
|
||||
".ifconfig.me",
|
||||
".ip-api.com",
|
||||
".ip.sb",
|
||||
".ip2location.com",
|
||||
".ipaddress.is",
|
||||
".ipapi.co",
|
||||
".ipapi.is",
|
||||
".ipbase.com",
|
||||
".ipdata.co",
|
||||
".ipgeolocation.io",
|
||||
".iphub.info",
|
||||
".ipify.org",
|
||||
".ipinfo.io",
|
||||
".ipleak.net",
|
||||
".ipqualityscore.com",
|
||||
".ipregistry.co",
|
||||
".ipwhois.io",
|
||||
".maxmind.com",
|
||||
".myip.com",
|
||||
".proxycheck.io",
|
||||
".scamalytics.com",
|
||||
".spur.us",
|
||||
".vpnapi.io",
|
||||
".whatismyip.com",
|
||||
".whatismyipaddress.com",
|
||||
".whoer.com",
|
||||
".whoer.net"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"version": 1,
|
||||
"rules": [
|
||||
{
|
||||
"domain": [
|
||||
"browserleaks.com",
|
||||
"dnsleaktest.com",
|
||||
"ipleak.net"
|
||||
],
|
||||
"domain_suffix": [
|
||||
".browserleaks.com",
|
||||
".dnsleaktest.com",
|
||||
".ipleak.net"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,13 @@
|
||||
# ipcheck-domains — категория: fraud-score
|
||||
# Правил: 9. Файл генерируется автоматически, не редактируйте вручную.
|
||||
# Источник: data/fraud-score.txt
|
||||
|
||||
criminalip.io
|
||||
fraudguard.io
|
||||
getipintel.net
|
||||
iphub.info
|
||||
ipqualityscore.com
|
||||
proxycheck.io
|
||||
scamalytics.com
|
||||
spur.us
|
||||
vpnapi.io
|
||||
@@ -0,0 +1,13 @@
|
||||
# ipcheck-domains — категория: geoip-db
|
||||
# Правил: 9. Файл генерируется автоматически, не редактируйте вручную.
|
||||
# Источник: data/geoip-db.txt
|
||||
|
||||
bigdatacloud.net
|
||||
db-ip.com
|
||||
ip2location.com
|
||||
ipbase.com
|
||||
ipdata.co
|
||||
ipgeolocation.io
|
||||
ipregistry.co
|
||||
ipwhois.io
|
||||
maxmind.com
|
||||
@@ -0,0 +1,22 @@
|
||||
# ipcheck-domains — категория: ip-lookup
|
||||
# Правил: 18. Файл генерируется автоматически, не редактируйте вручную.
|
||||
# Источник: data/ip-lookup.txt
|
||||
|
||||
2ip.io
|
||||
2ip.ru
|
||||
icanhazip.com
|
||||
ifconfig.co
|
||||
ifconfig.io
|
||||
ifconfig.me
|
||||
ip-api.com
|
||||
ip.sb
|
||||
ipaddress.is
|
||||
ipapi.co
|
||||
ipapi.is
|
||||
ipify.org
|
||||
ipinfo.io
|
||||
myip.com
|
||||
whatismyip.com
|
||||
whatismyipaddress.com
|
||||
whoer.com
|
||||
whoer.net
|
||||
@@ -0,0 +1,43 @@
|
||||
# ipcheck-domains — категория: ipcheck
|
||||
# Правил: 39. Файл генерируется автоматически, не редактируйте вручную.
|
||||
# Источник: data/ipcheck.txt
|
||||
|
||||
2ip.io
|
||||
2ip.ru
|
||||
bigdatacloud.net
|
||||
browserleaks.com
|
||||
criminalip.io
|
||||
db-ip.com
|
||||
dnsleaktest.com
|
||||
fraudguard.io
|
||||
getipintel.net
|
||||
icanhazip.com
|
||||
ifconfig.co
|
||||
ifconfig.io
|
||||
ifconfig.me
|
||||
ip-api.com
|
||||
ip.sb
|
||||
ip2location.com
|
||||
ipaddress.is
|
||||
ipapi.co
|
||||
ipapi.is
|
||||
ipbase.com
|
||||
ipdata.co
|
||||
ipgeolocation.io
|
||||
iphub.info
|
||||
ipify.org
|
||||
ipinfo.io
|
||||
ipleak.net
|
||||
ipqualityscore.com
|
||||
ipregistry.co
|
||||
ipwhois.io
|
||||
maxmind.com
|
||||
myip.com
|
||||
proxycheck.io
|
||||
scamalytics.com
|
||||
spur.us
|
||||
vpnapi.io
|
||||
whatismyip.com
|
||||
whatismyipaddress.com
|
||||
whoer.com
|
||||
whoer.net
|
||||
@@ -0,0 +1,7 @@
|
||||
# ipcheck-domains — категория: leak-test
|
||||
# Правил: 3. Файл генерируется автоматически, не редактируйте вручную.
|
||||
# Источник: data/leak-test.txt
|
||||
|
||||
browserleaks.com
|
||||
dnsleaktest.com
|
||||
ipleak.net
|
||||
-529
@@ -1,529 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Собирает geosite-артефакты из data/ в несколько форматов.
|
||||
|
||||
Основной формат — geosite.dat (protobuf v2ray/Xray, подходит для 3x-ui).
|
||||
Дополнительно: plain text, JSON, sing-box rule-set, Clash/mihomo, AdGuard.
|
||||
|
||||
Зависимостей нет — только стандартная библиотека. Внешние бинарники
|
||||
(sing-box, mihomo) нужны лишь для компиляции .srs/.mrs и опциональны.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
DATA_DIR = ROOT / "data"
|
||||
|
||||
TYPE_DOMAIN = "domain"
|
||||
TYPE_FULL = "full"
|
||||
TYPE_KEYWORD = "keyword"
|
||||
TYPE_REGEXP = "regexp"
|
||||
|
||||
# Значения enum Domain.Type из v2ray router protobuf.
|
||||
PB_TYPE = {TYPE_KEYWORD: 0, TYPE_REGEXP: 1, TYPE_DOMAIN: 2, TYPE_FULL: 3}
|
||||
# Порядок вывода: сначала суффиксы, потом точные, потом keyword/regexp.
|
||||
TYPE_ORDER = {TYPE_DOMAIN: 0, TYPE_FULL: 1, TYPE_KEYWORD: 2, TYPE_REGEXP: 3}
|
||||
|
||||
LABEL_RE = r"(?!-)[a-z0-9_-]{1,63}(?<!-)"
|
||||
DOMAIN_RE = re.compile(rf"^{LABEL_RE}(\.{LABEL_RE})+$")
|
||||
ATTR_RE = re.compile(r"^[a-z0-9_-]+$")
|
||||
CATEGORY_RE = re.compile(r"^[a-z0-9][a-z0-9-]*$")
|
||||
|
||||
|
||||
class BuildError(Exception):
|
||||
"""Ошибка в исходных данных — сборка должна упасть."""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Rule:
|
||||
type: str
|
||||
value: str
|
||||
attrs: tuple[str, ...] = ()
|
||||
|
||||
@property
|
||||
def sort_key(self) -> tuple[int, str]:
|
||||
return (TYPE_ORDER[self.type], self.value)
|
||||
|
||||
def __str__(self) -> str:
|
||||
base = self.value if self.type == TYPE_DOMAIN else f"{self.type}:{self.value}"
|
||||
return " ".join([base, *(f"@{a}" for a in self.attrs)])
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Разбор исходников
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
def source_path(category: str) -> Path:
|
||||
return DATA_DIR / f"{category}.txt"
|
||||
|
||||
|
||||
def list_categories() -> list[str]:
|
||||
return sorted(p.stem for p in DATA_DIR.glob("*.txt"))
|
||||
|
||||
|
||||
def parse_token(token: str, where: str) -> Rule:
|
||||
prefix, sep, value = token.partition(":")
|
||||
if sep and prefix.lower() in (TYPE_DOMAIN, TYPE_FULL, TYPE_KEYWORD, TYPE_REGEXP):
|
||||
rtype = prefix.lower()
|
||||
else:
|
||||
rtype, value = TYPE_DOMAIN, token
|
||||
|
||||
if not value:
|
||||
raise BuildError(f"{where}: пустое значение в '{token}'")
|
||||
|
||||
if rtype in (TYPE_DOMAIN, TYPE_FULL):
|
||||
try:
|
||||
value = value.encode("idna").decode("ascii")
|
||||
except UnicodeError:
|
||||
pass # ниже отловит DOMAIN_RE
|
||||
value = value.lower().strip(".")
|
||||
if not DOMAIN_RE.match(value):
|
||||
raise BuildError(f"{where}: невалидный домен '{value}'")
|
||||
elif rtype == TYPE_KEYWORD:
|
||||
value = value.lower()
|
||||
if any(c.isspace() for c in value):
|
||||
raise BuildError(f"{where}: keyword не может содержать пробелы")
|
||||
elif rtype == TYPE_REGEXP:
|
||||
try:
|
||||
re.compile(value)
|
||||
except re.error as exc:
|
||||
raise BuildError(f"{where}: невалидный regexp '{value}': {exc}") from exc
|
||||
|
||||
return Rule(rtype, value)
|
||||
|
||||
|
||||
def parse_category(category: str) -> list[tuple[str, Rule | str]]:
|
||||
"""Возвращает список ('rule', Rule) / ('include', category) в порядке файла."""
|
||||
path = source_path(category)
|
||||
if not path.is_file():
|
||||
raise BuildError(f"категория '{category}' не найдена: {path}")
|
||||
|
||||
entries: list[tuple[str, Rule | str]] = []
|
||||
for lineno, raw in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
|
||||
line = raw.split("#", 1)[0].strip()
|
||||
if not line:
|
||||
continue
|
||||
where = f"{path.name}:{lineno}"
|
||||
|
||||
token, *rest = line.split()
|
||||
attrs = []
|
||||
for part in rest:
|
||||
if not part.startswith("@"):
|
||||
raise BuildError(f"{where}: ожидался атрибут '@name', получено '{part}'")
|
||||
attr = part[1:].lower()
|
||||
if not ATTR_RE.match(attr):
|
||||
raise BuildError(f"{where}: невалидный атрибут '@{attr}'")
|
||||
attrs.append(attr)
|
||||
|
||||
if token.startswith("include:"):
|
||||
if attrs:
|
||||
raise BuildError(f"{where}: include не поддерживает атрибуты")
|
||||
target = token[len("include:") :]
|
||||
if not CATEGORY_RE.match(target):
|
||||
raise BuildError(f"{where}: невалидное имя категории '{target}'")
|
||||
entries.append(("include", target))
|
||||
continue
|
||||
|
||||
rule = parse_token(token, where)
|
||||
entries.append(("rule", Rule(rule.type, rule.value, tuple(sorted(set(attrs))))))
|
||||
|
||||
return entries
|
||||
|
||||
|
||||
def resolve(category: str, stack: tuple[str, ...] = ()) -> list[Rule]:
|
||||
"""Разворачивает include-и, убирает дубликаты, сортирует детерминированно."""
|
||||
if category in stack:
|
||||
chain = " -> ".join([*stack, category])
|
||||
raise BuildError(f"циклический include: {chain}")
|
||||
|
||||
collected: dict[Rule, None] = {}
|
||||
for kind, payload in parse_category(category):
|
||||
if kind == "include":
|
||||
for rule in resolve(str(payload), (*stack, category)):
|
||||
collected.setdefault(rule, None)
|
||||
else:
|
||||
collected.setdefault(payload, None) # type: ignore[arg-type]
|
||||
|
||||
return sorted(collected, key=lambda r: r.sort_key)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# protobuf: geosite.dat
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
def _varint(value: int) -> bytes:
|
||||
out = bytearray()
|
||||
while True:
|
||||
chunk = value & 0x7F
|
||||
value >>= 7
|
||||
if value:
|
||||
out.append(chunk | 0x80)
|
||||
else:
|
||||
out.append(chunk)
|
||||
return bytes(out)
|
||||
|
||||
|
||||
def _len_field(field: int, payload: bytes) -> bytes:
|
||||
return _varint(field << 3 | 2) + _varint(len(payload)) + payload
|
||||
|
||||
|
||||
def _str_field(field: int, value: str) -> bytes:
|
||||
return _len_field(field, value.encode("utf-8"))
|
||||
|
||||
|
||||
def _varint_field(field: int, value: int) -> bytes:
|
||||
return _varint(field << 3 | 0) + _varint(value)
|
||||
|
||||
|
||||
def encode_geosite_dat(categories: dict[str, list[Rule]]) -> bytes:
|
||||
"""GeoSiteList{ repeated GeoSite entry = 1 }."""
|
||||
out = bytearray()
|
||||
for name in sorted(categories):
|
||||
entry = bytearray(_str_field(1, name.upper())) # GeoSite.country_code
|
||||
for rule in categories[name]:
|
||||
domain = bytearray()
|
||||
pb_type = PB_TYPE[rule.type]
|
||||
if pb_type: # proto3 опускает значение по умолчанию (0 == Plain)
|
||||
domain += _varint_field(1, pb_type)
|
||||
domain += _str_field(2, rule.value)
|
||||
for attr in rule.attrs:
|
||||
# Attribute{ key = 1; bool_value = 2 }
|
||||
domain += _len_field(3, _str_field(1, attr) + _varint_field(2, 1))
|
||||
entry += _len_field(2, bytes(domain)) # GeoSite.domain
|
||||
out += _len_field(1, bytes(entry))
|
||||
return bytes(out)
|
||||
|
||||
|
||||
def decode_geosite_dat(blob: bytes) -> dict[str, list[Rule]]:
|
||||
"""Обратный разбор — используется для самопроверки собранного файла."""
|
||||
pb_to_type = {v: k for k, v in PB_TYPE.items()}
|
||||
|
||||
def fields(buf: bytes):
|
||||
pos = 0
|
||||
while pos < len(buf):
|
||||
key, pos = _read_varint(buf, pos)
|
||||
field, wire = key >> 3, key & 7
|
||||
if wire == 0:
|
||||
value, pos = _read_varint(buf, pos)
|
||||
yield field, value
|
||||
elif wire == 2:
|
||||
length, pos = _read_varint(buf, pos)
|
||||
yield field, buf[pos : pos + length]
|
||||
pos += length
|
||||
else:
|
||||
raise BuildError(f"неподдерживаемый wire type {wire}")
|
||||
|
||||
result: dict[str, list[Rule]] = {}
|
||||
for field, payload in fields(blob):
|
||||
if field != 1:
|
||||
continue
|
||||
name, rules = "", []
|
||||
for f, p in fields(payload):
|
||||
if f == 1:
|
||||
name = p.decode("utf-8")
|
||||
elif f == 2:
|
||||
rtype, value, attrs = TYPE_KEYWORD, "", []
|
||||
for df, dp in fields(p):
|
||||
if df == 1:
|
||||
rtype = pb_to_type[dp]
|
||||
elif df == 2:
|
||||
value = dp.decode("utf-8")
|
||||
elif df == 3:
|
||||
for af, ap in fields(dp):
|
||||
if af == 1:
|
||||
attrs.append(ap.decode("utf-8"))
|
||||
rules.append(Rule(rtype, value, tuple(attrs)))
|
||||
result[name] = rules
|
||||
return result
|
||||
|
||||
|
||||
def _read_varint(buf: bytes, pos: int) -> tuple[int, int]:
|
||||
value = shift = 0
|
||||
while True:
|
||||
byte = buf[pos]
|
||||
pos += 1
|
||||
value |= (byte & 0x7F) << shift
|
||||
if not byte & 0x80:
|
||||
return value, pos
|
||||
shift += 7
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Текстовые форматы
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
def write(path: Path, content: str) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(content.replace("\r\n", "\n"), encoding="utf-8", newline="\n")
|
||||
|
||||
|
||||
def header(category: str, count: int, comment: str = "#") -> str:
|
||||
return (
|
||||
f"{comment} ipcheck-domains — категория: {category}\n"
|
||||
f"{comment} Правил: {count}. Файл генерируется автоматически, не редактируйте вручную.\n"
|
||||
f"{comment} Источник: data/{category}.txt\n"
|
||||
)
|
||||
|
||||
|
||||
def render_plain(rules: list[Rule]) -> str:
|
||||
"""Только «голые» домены — для скриптов и ручной вставки."""
|
||||
seen = {r.value for r in rules if r.type in (TYPE_DOMAIN, TYPE_FULL)}
|
||||
return "".join(f"{value}\n" for value in sorted(seen))
|
||||
|
||||
|
||||
def render_source(category: str, rules: list[Rule]) -> str:
|
||||
return header(category, len(rules)) + "\n" + "".join(f"{r}\n" for r in rules)
|
||||
|
||||
|
||||
def render_json(category: str, rules: list[Rule]) -> str:
|
||||
payload = {
|
||||
"category": category,
|
||||
"count": len(rules),
|
||||
"rules": [
|
||||
{"type": r.type, "value": r.value, **({"attrs": list(r.attrs)} if r.attrs else {})}
|
||||
for r in rules
|
||||
],
|
||||
}
|
||||
return json.dumps(payload, ensure_ascii=False, indent=2) + "\n"
|
||||
|
||||
|
||||
def render_singbox(rules: list[Rule]) -> str:
|
||||
"""sing-box rule-set (source). domain: apex + domain_suffix: .apex == v2ray domain:."""
|
||||
rule: dict[str, list[str]] = {}
|
||||
for kind, values in (
|
||||
("domain", sorted({r.value for r in rules if r.type in (TYPE_DOMAIN, TYPE_FULL)})),
|
||||
("domain_suffix", sorted({f".{r.value}" for r in rules if r.type == TYPE_DOMAIN})),
|
||||
("domain_keyword", sorted({r.value for r in rules if r.type == TYPE_KEYWORD})),
|
||||
("domain_regex", sorted({r.value for r in rules if r.type == TYPE_REGEXP})),
|
||||
):
|
||||
if values:
|
||||
rule[kind] = values
|
||||
return json.dumps({"version": 1, "rules": [rule] if rule else []}, indent=2) + "\n"
|
||||
|
||||
|
||||
def render_clash_yaml(category: str, rules: list[Rule]) -> str:
|
||||
"""rule-provider с behavior: domain — keyword/regexp этим форматом не поддерживаются."""
|
||||
lines = [header(category, len(rules)), "payload:\n"]
|
||||
for rule in rules:
|
||||
if rule.type == TYPE_DOMAIN:
|
||||
lines.append(f" - '+.{rule.value}'\n")
|
||||
elif rule.type == TYPE_FULL:
|
||||
lines.append(f" - '{rule.value}'\n")
|
||||
return "".join(lines)
|
||||
|
||||
|
||||
def render_clash_list(category: str, rules: list[Rule]) -> str:
|
||||
"""rule-provider с behavior: classical."""
|
||||
mapping = {
|
||||
TYPE_DOMAIN: "DOMAIN-SUFFIX",
|
||||
TYPE_FULL: "DOMAIN",
|
||||
TYPE_KEYWORD: "DOMAIN-KEYWORD",
|
||||
TYPE_REGEXP: "DOMAIN-REGEX",
|
||||
}
|
||||
body = "".join(f"{mapping[r.type]},{r.value}\n" for r in rules)
|
||||
return header(category, len(rules)) + body
|
||||
|
||||
|
||||
def render_adguard(category: str, rules: list[Rule]) -> str:
|
||||
lines = [header(category, len(rules), comment="!")]
|
||||
for rule in rules:
|
||||
if rule.type == TYPE_DOMAIN:
|
||||
lines.append(f"||{rule.value}^\n")
|
||||
elif rule.type == TYPE_FULL:
|
||||
lines.append(f"||{rule.value}^$important\n")
|
||||
return "".join(lines)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Компиляция бинарных rule-set (опционально)
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
def compile_binaries(out_dir: Path, categories: list[str], singbox: str | None, mihomo: str | None) -> list[str]:
|
||||
produced = []
|
||||
|
||||
if singbox:
|
||||
for category in categories:
|
||||
src = out_dir / "sing-box" / f"{category}.json"
|
||||
dst = out_dir / "sing-box" / f"{category}.srs"
|
||||
subprocess.run([singbox, "rule-set", "compile", "--output", str(dst), str(src)], check=True)
|
||||
produced.append(str(dst.relative_to(out_dir)))
|
||||
else:
|
||||
print(" sing-box не найден — .srs пропущены", file=sys.stderr)
|
||||
|
||||
if mihomo:
|
||||
for category in categories:
|
||||
src = out_dir / "clash" / f"{category}.yaml"
|
||||
dst = out_dir / "mihomo" / f"{category}.mrs"
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
subprocess.run([mihomo, "convert-ruleset", "domain", "yaml", str(src), str(dst)], check=True)
|
||||
produced.append(str(dst.relative_to(out_dir)))
|
||||
else:
|
||||
print(" mihomo не найден — .mrs пропущены", file=sys.stderr)
|
||||
|
||||
return produced
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Точка входа
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
def build(out_dir: Path, singbox: str | None, mihomo: str | None) -> int:
|
||||
categories = list_categories()
|
||||
if not categories:
|
||||
raise BuildError(f"в {DATA_DIR} нет ни одного .txt")
|
||||
|
||||
resolved = {name: resolve(name) for name in categories}
|
||||
|
||||
if out_dir.exists():
|
||||
shutil.rmtree(out_dir)
|
||||
out_dir.mkdir(parents=True)
|
||||
|
||||
dat = encode_geosite_dat(resolved)
|
||||
(out_dir / "geosite.dat").write_bytes(dat)
|
||||
# Копия под отдельным именем: её можно положить рядом со штатным
|
||||
# geosite.dat и адресовать как ext:ipcheck.dat:ipcheck, ничего не перезаписывая.
|
||||
(out_dir / "ipcheck.dat").write_bytes(dat)
|
||||
|
||||
# Самопроверка: файл должен читаться обратно ровно в то, что мы записали.
|
||||
decoded = decode_geosite_dat(dat)
|
||||
expected = {name.upper(): rules for name, rules in resolved.items()}
|
||||
if decoded != expected:
|
||||
raise BuildError("geosite.dat не проходит round-trip проверку")
|
||||
|
||||
digest = hashlib.sha256(dat).hexdigest()
|
||||
write(out_dir / "geosite.dat.sha256sum", f"{digest} geosite.dat\n")
|
||||
write(out_dir / "ipcheck.dat.sha256sum", f"{digest} ipcheck.dat\n")
|
||||
|
||||
for name, rules in resolved.items():
|
||||
write(out_dir / "txt" / f"{name}.txt", render_plain(rules))
|
||||
write(out_dir / "src" / f"{name}.txt", render_source(name, rules))
|
||||
write(out_dir / "json" / f"{name}.json", render_json(name, rules))
|
||||
write(out_dir / "sing-box" / f"{name}.json", render_singbox(rules))
|
||||
write(out_dir / "clash" / f"{name}.yaml", render_clash_yaml(name, rules))
|
||||
write(out_dir / "clash" / f"{name}.list", render_clash_list(name, rules))
|
||||
write(out_dir / "adguard" / f"{name}.txt", render_adguard(name, rules))
|
||||
|
||||
compile_binaries(out_dir, categories, singbox, mihomo)
|
||||
|
||||
epoch = os.environ.get("SOURCE_DATE_EPOCH")
|
||||
built_at = (
|
||||
datetime.fromtimestamp(int(epoch), tz=timezone.utc)
|
||||
if epoch
|
||||
else datetime.now(tz=timezone.utc)
|
||||
)
|
||||
write(
|
||||
out_dir / "metadata.json",
|
||||
json.dumps(
|
||||
{
|
||||
"built_at": built_at.replace(microsecond=0).isoformat().replace("+00:00", "Z"),
|
||||
"geosite_dat_sha256": digest,
|
||||
"geosite_dat_size": len(dat),
|
||||
"total_rules": sum(len(r) for r in resolved.values()),
|
||||
"categories": {name: len(rules) for name, rules in resolved.items()},
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
+ "\n",
|
||||
)
|
||||
|
||||
# Скрипт обновления едет вместе с артефактами: на сервере тогда нужен
|
||||
# ровно один базовый URL — и для .dat, и для самого апдейтера.
|
||||
updater = Path(__file__).with_name("update-geosite.sh")
|
||||
if updater.is_file():
|
||||
write(out_dir / updater.name, updater.read_text(encoding="utf-8"))
|
||||
|
||||
checksums = "".join(
|
||||
f"{hashlib.sha256(path.read_bytes()).hexdigest()} {path.relative_to(out_dir).as_posix()}\n"
|
||||
for path in sorted(out_dir.rglob("*"))
|
||||
if path.is_file()
|
||||
)
|
||||
write(out_dir / "SHA256SUMS", checksums)
|
||||
|
||||
print(f"geosite.dat: {len(dat)} байт, sha256 {digest[:16]}…")
|
||||
for name in categories:
|
||||
print(f" {name:<12} {len(resolved[name]):>4} правил")
|
||||
print(f"Готово: {out_dir}")
|
||||
return 0
|
||||
|
||||
|
||||
def check() -> int:
|
||||
categories = list_categories()
|
||||
if not categories:
|
||||
raise BuildError(f"в {DATA_DIR} нет ни одного .txt")
|
||||
|
||||
problems = []
|
||||
for name in categories:
|
||||
if not CATEGORY_RE.match(name):
|
||||
problems.append(f"{name}: имя категории должно быть [a-z0-9-]")
|
||||
entries = parse_category(name)
|
||||
seen: dict[Rule, int] = {}
|
||||
for index, (kind, payload) in enumerate(entries):
|
||||
if kind != "rule":
|
||||
continue
|
||||
rule: Rule = payload # type: ignore[assignment]
|
||||
if rule in seen:
|
||||
problems.append(f"{name}.txt: дубликат '{rule}'")
|
||||
seen[rule] = index
|
||||
suffixes = {r.value for r in seen if r.type == TYPE_DOMAIN}
|
||||
for rule in seen:
|
||||
if rule.type != TYPE_DOMAIN:
|
||||
continue
|
||||
parent = rule.value.split(".", 1)[1] if "." in rule.value else ""
|
||||
while parent:
|
||||
if parent in suffixes:
|
||||
problems.append(f"{name}.txt: '{rule.value}' уже покрыт '{parent}'")
|
||||
break
|
||||
parent = parent.split(".", 1)[1] if "." in parent else ""
|
||||
resolve(name) # ловит циклы и битые include
|
||||
|
||||
for problem in problems:
|
||||
print(f"error: {problem}", file=sys.stderr)
|
||||
if problems:
|
||||
return 1
|
||||
print(f"ok: {len(categories)} категорий, замечаний нет")
|
||||
return 0
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
for stream in (sys.stdout, sys.stderr):
|
||||
stream.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
|
||||
|
||||
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
parser.add_argument("--out", type=Path, default=ROOT / "dist", help="каталог сборки (по умолчанию dist/)")
|
||||
parser.add_argument("--check", action="store_true", help="только проверить исходники, ничего не собирать")
|
||||
parser.add_argument("--sing-box", dest="singbox", help="путь к бинарю sing-box для сборки .srs")
|
||||
parser.add_argument("--mihomo", help="путь к бинарю mihomo для сборки .mrs")
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
try:
|
||||
if args.check:
|
||||
return check()
|
||||
return build(
|
||||
args.out.resolve(),
|
||||
args.singbox or shutil.which("sing-box"),
|
||||
args.mihomo or shutil.which("mihomo"),
|
||||
)
|
||||
except BuildError as exc:
|
||||
print(f"error: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -1,48 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Кладёт sing-box, mihomo и xray в каталог (по умолчанию ./bin).
|
||||
# Нужен только для сборки .srs/.mrs и для проверки .dat настоящим Xray.
|
||||
#
|
||||
# ./tools/fetch-tools.sh [каталог]
|
||||
|
||||
set -eu
|
||||
|
||||
BIN="${1:-./bin}"
|
||||
mkdir -p "$BIN"
|
||||
BIN=$(cd "$BIN" && pwd)
|
||||
|
||||
case "$(uname -m)" in
|
||||
x86_64 | amd64) sb_arch=amd64; mh_arch=amd64; xr_arch=64 ;;
|
||||
aarch64 | arm64) sb_arch=arm64; mh_arch=arm64; xr_arch=arm64-v8a ;;
|
||||
*) echo "неподдерживаемая архитектура: $(uname -m)" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
# Печатает ссылку на ассет последнего релиза, имя которого совпало с regex.
|
||||
asset_url() {
|
||||
curl -fsSL "https://api.github.com/repos/$1/releases/latest" | python3 -c '
|
||||
import json, re, sys
|
||||
assets = json.load(sys.stdin)["assets"]
|
||||
match = next((a for a in assets if re.search(sys.argv[1], a["name"])), None)
|
||||
if match is None:
|
||||
sys.exit(f"в релизе нет ассета по шаблону {sys.argv[1]}")
|
||||
print(match["browser_download_url"])
|
||||
' "$2"
|
||||
}
|
||||
|
||||
echo "sing-box…"
|
||||
curl -fsSL "$(asset_url SagerNet/sing-box "-linux-$sb_arch\\.tar\\.gz\$")" |
|
||||
tar -xz --strip-components=1 -C "$BIN" --wildcards '*/sing-box'
|
||||
|
||||
echo "mihomo…"
|
||||
curl -fsSL "$(asset_url MetaCubeX/mihomo "^mihomo-linux-$mh_arch-v[0-9.]+\\.gz\$")" |
|
||||
gunzip > "$BIN/mihomo"
|
||||
|
||||
echo "xray…"
|
||||
curl -fsSL -o "$BIN/xray.zip" "$(asset_url XTLS/Xray-core "^Xray-linux-$xr_arch\\.zip\$")"
|
||||
unzip -oqj "$BIN/xray.zip" xray -d "$BIN"
|
||||
rm -f "$BIN/xray.zip"
|
||||
|
||||
chmod +x "$BIN/sing-box" "$BIN/mihomo" "$BIN/xray"
|
||||
echo "готово: $BIN"
|
||||
"$BIN/sing-box" version | head -1
|
||||
"$BIN/mihomo" -v | head -1
|
||||
"$BIN/xray" version | head -1
|
||||
@@ -1,50 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Создаёт (или обновляет) релиз в Gitea и заливает в него файлы.
|
||||
#
|
||||
# GITEA_URL=https://gitea.hsrv.site GITEA_REPO=mrleo1nid/ipcheck-domains GITEA_TOKEN=… \
|
||||
# ./tools/gitea-release.sh <тег> <файл-с-описанием> <файл> [файл …]
|
||||
#
|
||||
# Имя ассета — путь относительно dist/ с заменой "/" на "-", чтобы
|
||||
# sing-box/ipcheck.json и json/ipcheck.json не конфликтовали.
|
||||
|
||||
set -eu
|
||||
|
||||
[ $# -ge 3 ] || { echo "использование: $0 <тег> <notes.md> <файл...>" >&2; exit 1; }
|
||||
|
||||
TAG="$1"; NOTES="$2"; shift 2
|
||||
: "${GITEA_URL:?не задан GITEA_URL}"
|
||||
: "${GITEA_REPO:?не задан GITEA_REPO}"
|
||||
: "${GITEA_TOKEN:?не задан GITEA_TOKEN}"
|
||||
|
||||
API="$GITEA_URL/api/v1/repos/$GITEA_REPO/releases"
|
||||
auth="Authorization: token $GITEA_TOKEN"
|
||||
|
||||
json_field() { python3 -c 'import json,sys; print(json.load(sys.stdin).get(sys.argv[1],""))' "$1"; }
|
||||
|
||||
payload=$(NOTES_FILE="$NOTES" TAG="$TAG" python3 -c '
|
||||
import json, os
|
||||
print(json.dumps({
|
||||
"tag_name": os.environ["TAG"],
|
||||
"name": os.environ["TAG"],
|
||||
"body": open(os.environ["NOTES_FILE"], encoding="utf-8").read(),
|
||||
"draft": False,
|
||||
"prerelease": False,
|
||||
}))')
|
||||
|
||||
# Релиз с таким тегом мог остаться от прошлого прогона — тогда переиспользуем его.
|
||||
id=$(curl -fsS -X POST "$API" -H "$auth" -H 'Content-Type: application/json' \
|
||||
-d "$payload" 2>/dev/null | json_field id || true)
|
||||
if [ -z "$id" ]; then
|
||||
id=$(curl -fsS "$API/tags/$TAG" -H "$auth" | json_field id) ||
|
||||
{ echo "не удалось ни создать, ни найти релиз $TAG" >&2; exit 1; }
|
||||
echo "релиз $TAG уже существует (id $id), дозаливаем ассеты"
|
||||
fi
|
||||
echo "релиз $TAG: id $id"
|
||||
|
||||
for file in "$@"; do
|
||||
[ -f "$file" ] || { echo "нет файла: $file" >&2; exit 1; }
|
||||
name=$(printf '%s' "${file#dist/}" | tr '/' '-')
|
||||
curl -fsS -X POST "$API/$id/assets?name=$name" -H "$auth" \
|
||||
-F "attachment=@$file" >/dev/null
|
||||
echo " + $name"
|
||||
done
|
||||
@@ -1,174 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Тесты сборщика: python -m unittest discover -s tools -t tools"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import build
|
||||
from build import BuildError, Rule
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def data(**files: str):
|
||||
"""Подменяет data/ временным каталогом с заданными категориями."""
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
for name, content in files.items():
|
||||
(root / f"{name.replace('_', '-')}.txt").write_text(content, encoding="utf-8")
|
||||
original = build.DATA_DIR
|
||||
build.DATA_DIR = root
|
||||
try:
|
||||
yield root
|
||||
finally:
|
||||
build.DATA_DIR = original
|
||||
|
||||
|
||||
class ParseTest(unittest.TestCase):
|
||||
def test_default_type_is_domain(self):
|
||||
with data(a="example.com\n"):
|
||||
self.assertEqual(build.resolve("a"), [Rule("domain", "example.com")])
|
||||
|
||||
def test_all_prefixes(self):
|
||||
source = "domain:a.com\nfull:b.com\nkeyword:cc\nregexp:^d[0-9]+\\.com$\n"
|
||||
with data(a=source):
|
||||
self.assertEqual(
|
||||
build.resolve("a"),
|
||||
[
|
||||
Rule("domain", "a.com"),
|
||||
Rule("full", "b.com"),
|
||||
Rule("keyword", "cc"),
|
||||
Rule("regexp", r"^d[0-9]+\.com$"),
|
||||
],
|
||||
)
|
||||
|
||||
def test_comments_blank_lines_and_attributes(self):
|
||||
with data(a="# заголовок\n\na.com @ads @cn # хвост\n"):
|
||||
self.assertEqual(build.resolve("a"), [Rule("domain", "a.com", ("ads", "cn"))])
|
||||
|
||||
def test_normalisation(self):
|
||||
with data(a="Example.COM.\nDOMAIN:пример.рф\n"):
|
||||
values = [r.value for r in build.resolve("a")]
|
||||
self.assertIn("example.com", values)
|
||||
self.assertIn("xn--e1afmkfd.xn--p1ai", values)
|
||||
|
||||
def test_include_dedupes_and_sorts(self):
|
||||
with data(a="include:b\nz.com\na.com\n", b="a.com\nfull:m.com\n"):
|
||||
self.assertEqual(
|
||||
build.resolve("a"),
|
||||
[Rule("domain", "a.com"), Rule("domain", "z.com"), Rule("full", "m.com")],
|
||||
)
|
||||
|
||||
def test_rejects_bad_input(self):
|
||||
cases = {
|
||||
"битый домен": "not_a_domain\n",
|
||||
"битый regexp": "regexp:[unclosed\n",
|
||||
"атрибут без @": "a.com ads\n",
|
||||
"неизвестный include": "include:missing\n",
|
||||
}
|
||||
for label, source in cases.items():
|
||||
with self.subTest(label), data(a=source):
|
||||
with self.assertRaises(BuildError):
|
||||
build.resolve("a")
|
||||
|
||||
def test_detects_include_cycle(self):
|
||||
with data(a="include:b\n", b="include:a\n"):
|
||||
with self.assertRaisesRegex(BuildError, "цикл"):
|
||||
build.resolve("a")
|
||||
|
||||
|
||||
class ProtobufTest(unittest.TestCase):
|
||||
def test_known_byte_layout(self):
|
||||
blob = build.encode_geosite_dat({"t": [Rule("domain", "a.com")]})
|
||||
# 0a 0e GeoSiteList.entry, 14 байт
|
||||
# 0a 01 54 country_code = "T" (в .dat теги в верхнем регистре)
|
||||
# 12 09 GeoSite.domain, 9 байт
|
||||
# 08 02 type = Domain(2)
|
||||
# 12 05 612e636f6d value = "a.com"
|
||||
self.assertEqual(blob.hex(), "0a0e" "0a0154" "1209" "0802" "1205" "612e636f6d")
|
||||
|
||||
def test_keyword_type_is_omitted_as_proto3_default(self):
|
||||
blob = build.encode_geosite_dat({"t": [Rule("keyword", "ip")]})
|
||||
self.assertNotIn(b"\x08\x00", blob)
|
||||
self.assertEqual(build.decode_geosite_dat(blob)["T"], [Rule("keyword", "ip")])
|
||||
|
||||
def test_round_trip_with_attributes(self):
|
||||
categories = {
|
||||
"one": [Rule("domain", "a.com", ("ads",)), Rule("full", "b.com")],
|
||||
"two": [Rule("keyword", "ip"), Rule("regexp", r"^x\d+$")],
|
||||
}
|
||||
decoded = build.decode_geosite_dat(build.encode_geosite_dat(categories))
|
||||
self.assertEqual(decoded, {k.upper(): v for k, v in categories.items()})
|
||||
|
||||
|
||||
class RenderTest(unittest.TestCase):
|
||||
def test_singbox_expands_suffix_to_apex_plus_dot(self):
|
||||
rendered = json.loads(build.render_singbox([Rule("domain", "a.com"), Rule("full", "b.com")]))
|
||||
self.assertEqual(rendered["rules"][0]["domain"], ["a.com", "b.com"])
|
||||
self.assertEqual(rendered["rules"][0]["domain_suffix"], [".a.com"])
|
||||
|
||||
def test_clash_yaml_skips_unsupported_types(self):
|
||||
rules = [Rule("domain", "a.com"), Rule("full", "b.com"), Rule("keyword", "ip")]
|
||||
payload = build.render_clash_yaml("t", rules)
|
||||
self.assertIn("- '+.a.com'", payload)
|
||||
self.assertIn("- 'b.com'", payload)
|
||||
self.assertNotIn("ip", payload.split("payload:")[1])
|
||||
|
||||
def test_clash_list_keeps_all_types(self):
|
||||
rules = [Rule("domain", "a.com"), Rule("keyword", "ip"), Rule("regexp", "^x$")]
|
||||
body = build.render_clash_list("t", rules)
|
||||
self.assertIn("DOMAIN-SUFFIX,a.com", body)
|
||||
self.assertIn("DOMAIN-KEYWORD,ip", body)
|
||||
self.assertIn("DOMAIN-REGEX,^x$", body)
|
||||
|
||||
|
||||
class CheckTest(unittest.TestCase):
|
||||
def test_clean_sources_pass(self):
|
||||
with data(a="a.com\nb.com\n"):
|
||||
self.assertEqual(build.check(), 0)
|
||||
|
||||
def test_duplicate_is_reported(self):
|
||||
with data(a="a.com\na.com\n"):
|
||||
self.assertEqual(build.check(), 1)
|
||||
|
||||
def test_subdomain_covered_by_parent_is_reported(self):
|
||||
with data(a="a.com\ndeep.sub.a.com\n"):
|
||||
self.assertEqual(build.check(), 1)
|
||||
|
||||
def test_real_repository_data_is_clean(self):
|
||||
self.assertEqual(build.check(), 0)
|
||||
|
||||
|
||||
class BuildTest(unittest.TestCase):
|
||||
def test_produces_every_format(self):
|
||||
expected = [
|
||||
"geosite.dat",
|
||||
"ipcheck.dat",
|
||||
"geosite.dat.sha256sum",
|
||||
"metadata.json",
|
||||
"txt/b.txt",
|
||||
"src/b.txt",
|
||||
"json/b.json",
|
||||
"sing-box/b.json",
|
||||
"clash/b.yaml",
|
||||
"clash/b.list",
|
||||
"adguard/b.txt",
|
||||
]
|
||||
with data(a="a.com\n", b="include:a\nfull:c.com\n"), tempfile.TemporaryDirectory() as out:
|
||||
out_dir = Path(out) / "dist"
|
||||
self.assertEqual(build.build(out_dir, None, None), 0)
|
||||
for rel in expected:
|
||||
self.assertTrue((out_dir / rel).is_file(), rel)
|
||||
self.assertEqual(
|
||||
(out_dir / "geosite.dat").read_bytes(), (out_dir / "ipcheck.dat").read_bytes()
|
||||
)
|
||||
meta = json.loads((out_dir / "metadata.json").read_text(encoding="utf-8"))
|
||||
self.assertEqual(meta["categories"], {"a": 1, "b": 2})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,11 +1,9 @@
|
||||
# fraud-score — детекторы прокси/VPN и скоринг "качества" IP.
|
||||
|
||||
spur.us
|
||||
scamalytics.com
|
||||
criminalip.io
|
||||
fraudguard.io
|
||||
getipintel.net
|
||||
iphub.info
|
||||
ipqualityscore.com
|
||||
proxycheck.io
|
||||
scamalytics.com
|
||||
spur.us
|
||||
vpnapi.io
|
||||
iphub.info
|
||||
getipintel.net
|
||||
fraudguard.io
|
||||
criminalip.io
|
||||
@@ -1,11 +1,9 @@
|
||||
# geoip-db — провайдеры GeoIP-баз и справочных данных об IP.
|
||||
|
||||
ipdata.co
|
||||
ipbase.com
|
||||
ipwhois.io
|
||||
ipgeolocation.io
|
||||
ipregistry.co
|
||||
bigdatacloud.net
|
||||
db-ip.com
|
||||
maxmind.com
|
||||
ip2location.com
|
||||
ipbase.com
|
||||
ipdata.co
|
||||
ipgeolocation.io
|
||||
ipregistry.co
|
||||
ipwhois.io
|
||||
maxmind.com
|
||||
@@ -0,0 +1,18 @@
|
||||
2ip.io
|
||||
2ip.ru
|
||||
icanhazip.com
|
||||
ifconfig.co
|
||||
ifconfig.io
|
||||
ifconfig.me
|
||||
ip-api.com
|
||||
ip.sb
|
||||
ipaddress.is
|
||||
ipapi.co
|
||||
ipapi.is
|
||||
ipify.org
|
||||
ipinfo.io
|
||||
myip.com
|
||||
whatismyip.com
|
||||
whatismyipaddress.com
|
||||
whoer.com
|
||||
whoer.net
|
||||
@@ -0,0 +1,39 @@
|
||||
2ip.io
|
||||
2ip.ru
|
||||
bigdatacloud.net
|
||||
browserleaks.com
|
||||
criminalip.io
|
||||
db-ip.com
|
||||
dnsleaktest.com
|
||||
fraudguard.io
|
||||
getipintel.net
|
||||
icanhazip.com
|
||||
ifconfig.co
|
||||
ifconfig.io
|
||||
ifconfig.me
|
||||
ip-api.com
|
||||
ip.sb
|
||||
ip2location.com
|
||||
ipaddress.is
|
||||
ipapi.co
|
||||
ipapi.is
|
||||
ipbase.com
|
||||
ipdata.co
|
||||
ipgeolocation.io
|
||||
iphub.info
|
||||
ipify.org
|
||||
ipinfo.io
|
||||
ipleak.net
|
||||
ipqualityscore.com
|
||||
ipregistry.co
|
||||
ipwhois.io
|
||||
maxmind.com
|
||||
myip.com
|
||||
proxycheck.io
|
||||
scamalytics.com
|
||||
spur.us
|
||||
vpnapi.io
|
||||
whatismyip.com
|
||||
whatismyipaddress.com
|
||||
whoer.com
|
||||
whoer.net
|
||||
@@ -0,0 +1,3 @@
|
||||
browserleaks.com
|
||||
dnsleaktest.com
|
||||
ipleak.net
|
||||
Executable → Regular
Reference in New Issue
Block a user