feat: geosite/geoip для Discord со сборкой из апстримов

Домены и сети Discord в geosite.dat и geoip.dat для Xray/3x-ui, плюс
sing-box, Clash/mihomo, AdGuard, текст и готовый фрагмент routing.

Рукописная база живёт в data/discord-base.txt и data/ip/discord-base.txt,
остальное tools/sync.py тянет из пяти публичных апстримов: JSON-исходники
парсятся напрямую, скомпилированные .srs распаковываются через sing-box.
Каждый источник кладётся в свою категорию, недоступный источник не
обнуляет данные, файлы без шапки GENERATED скрипт не трогает.

Ограничения по протоколу и портам (директивы !network / !port) доезжают
до sing-box rule-set и до xray/routing.json. Такие категории намеренно
не входят в сводную discord: там сети вроде 172.64.0.0/13, и без привязки
к UDP-портам правило утащило бы в туннель чужой трафик.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Leonid Pershin
2026-08-10 09:24:08 +03:00
co-authored by Claude Opus 5
commit 61c0ea3558
23 changed files with 7278 additions and 0 deletions
+300
View File
@@ -0,0 +1,300 @@
# discord-geodata
Домены и сети Discord, собранные в `geosite.dat` и `geoip.dat` для Xray/3x-ui —
плюс те же списки в форматах sing-box, Clash/mihomo, AdGuard и обычным текстом.
Списки не выдумываются: рукописная база лежит в `data/discord-base.txt` и
`data/ip/discord-base.txt`, всё остальное `tools/sync.py` тянет из публичных
апстримов (см. [Апстримы](#апстримы)) и раскладывает по отдельным категориям.
## Категории
Домены (`geosite.dat`):
| Категория | Что внутри |
| --- | --- |
| `discord` | всё вместе — то, что обычно и нужно в роутинге |
| `discord-base` | рукописный список: домены Discord и его проектов |
| `discord-metacubex` | из MetaCubeX meta-rules-dat |
| `discord-itdoginfo` | из itdoginfo/allow-domains |
| `discord-youoops` | из you-oops-dev/ipranges-singbox, включая имена голосовых узлов |
Сети (`geoip.dat`):
| Категория | Что внутри | Ограничения |
| --- | --- | --- |
| `discord` | все безусловные списки вместе | — |
| `discord-base` | адреса, снятые резолвом доменов | — |
| `discord-legiz` | адреса голосовых узлов (legiz-ru/sb-rule-sets) | — |
| `discord-youoops` | диапазоны сетей (you-oops-dev/ipranges-singbox) | — |
| `discord-vernette-udp` | голосовой трафик (vernette/rulesets) | `udp`, порты 50000-50099 |
| `discord-itdoginfo-udp-*` | голосовой трафик (itdoginfo) | `udp`, свои диапазоны портов |
Категории с ограничениями по протоколу и портам **не входят** в сводную
`discord`: там широкие сети вроде `172.64.0.0/13` и `162.158.0.0/15`, то есть
пол-Cloudflare. Без ограничения по UDP-портам такое правило утащило бы в туннель
кучу чужого трафика. Точные счётчики по каждой категории — в `metadata.json`.
## Скачать
Ветка `release` — распакованный `dist/`, релизы — те же файлы архивом:
- `https://gitea.hsrv.site/mrleo1nid/discord-geodata/raw/branch/release/<файл>`
| Файл | Для чего |
| --- | --- |
| `discord-geosite.dat` | Xray / v2ray / 3x-ui — домены, кладётся рядом со штатным `geosite.dat` |
| `discord-geoip.dat` | то же для сетей, рядом со штатным `geoip.dat` |
| `geosite.dat`, `geoip.dat` | те же данные под именами по умолчанию — если хотите заменить штатные файлы |
| `*.dat.sha256sum` | контрольные суммы, по ним работает автообновление |
| `xray/routing.json` | готовый фрагмент `routing` со всеми правилами |
| `update-geodata.sh` | скрипт обновления для cron (см. ниже) |
| `sing-box/site/<категория>.json`, `.srs` | sing-box, домены: исходный и скомпилированный rule-set |
| `sing-box/ip/<категория>.json`, `.srs` | sing-box, сети — здесь же живут `network` и `port_range` |
| `mihomo/site/<категория>.mrs`, `mihomo/ip/<категория>.mrs` | mihomo, бинарные rule-provider |
| `clash/site/<категория>.yaml`, `.list` | Clash/mihomo, `behavior: domain` и `classical` |
| `clash/ip/<категория>.yaml`, `.list` | Clash/mihomo, `behavior: ipcidr` и `classical` |
| `txt/site/<категория>.txt`, `txt/ip/<категория>.txt` | просто домены и сети, по одной на строку |
| `json/…` | структурированные списки с типами правил и ограничениями |
| `adguard/<категория>.txt` | AdGuard Home / uBlock — если нужно блокировать, а не роутить |
| `metadata.json`, `SHA256SUMS` | время сборки, счётчики, суммы по всем файлам |
## 3x-ui
Положите оба файла рядом с бинарём (обычно `/usr/local/x-ui/bin`):
```sh
cd /usr/local/x-ui/bin
base=https://gitea.hsrv.site/mrleo1nid/discord-geodata/raw/branch/release
curl -fsSLO "$base/discord-geosite.dat"
curl -fsSLO "$base/discord-geoip.dat"
x-ui restart
```
Дальше в панели: **Настройки → Xray → Маршруты** (или прямо в JSON-конфиге).
Готовые правила лежат в `xray/routing.json`, менять там нужно только
`outboundTag`:
```json
{
"type": "field",
"domain": ["ext:discord-geosite.dat:discord"],
"outboundTag": "proxy"
},
{
"type": "field",
"ip": ["ext:discord-geoip.dat:discord"],
"outboundTag": "proxy"
},
{
"type": "field",
"network": "udp",
"port": "50000-50099",
"ip": ["ext:discord-geoip.dat:discord-vernette-udp"],
"outboundTag": "proxy"
}
```
`ext:<файл>:<тег>` работает и для `domain`, и для `ip` — файл ищется в
`XRAY_LOCATION_ASSET` (по умолчанию каталог с бинарём). Тег — имя категории,
регистр не важен.
Порядок правил — это приоритет: Xray берёт первое подошедшее, поэтому
ограниченные по портам правила идут последними, чтобы более общие не
перехватывали их раньше.
## Автообновление на сервере
`update-geodata.sh` качает оба `.dat`, только если их sha256 разошлись с
удалёнными, проверяет сумму до подмены и перезапускает сервис один раз —
и только если что-то реально изменилось.
```sh
base=https://gitea.hsrv.site/mrleo1nid/discord-geodata/raw/branch/release
curl -fsSLo /usr/local/bin/update-geodata.sh "$base/update-geodata.sh"
chmod +x /usr/local/bin/update-geodata.sh
RESTART_CMD='x-ui restart' /usr/local/bin/update-geodata.sh
```
cron раз в сутки:
```cron
23 4 * * * RESTART_CMD='x-ui restart' /usr/local/bin/update-geodata.sh >> /var/log/discord-geodata.log 2>&1
```
| Переменная | По умолчанию | Значение |
| --- | --- | --- |
| `BASE_URL` | `https://gitea.hsrv.site/mrleo1nid/discord-geodata/raw/branch/release` | база, откуда качать |
| `DEST_DIR` | `/usr/local/x-ui/bin` | куда класть файлы |
| `NAMES` | `discord-geosite.dat discord-geoip.dat` | что качать |
| `RESTART_CMD` | пусто | что выполнить после успешного обновления |
| `TIMEOUT` | `30` | таймаут curl, секунды |
Если 3x-ui в Docker — укажите `DEST_DIR` на смонтированный каталог и
`RESTART_CMD='docker restart 3x-ui'`.
## sing-box
```json
{
"route": {
"rule_set": [
{
"type": "remote",
"tag": "discord",
"format": "binary",
"url": "https://gitea.hsrv.site/mrleo1nid/discord-geodata/raw/branch/release/sing-box/site/discord.srs"
},
{
"type": "remote",
"tag": "discord-ip",
"format": "binary",
"url": "https://gitea.hsrv.site/mrleo1nid/discord-geodata/raw/branch/release/sing-box/ip/discord.srs"
},
{
"type": "remote",
"tag": "discord-voice",
"format": "binary",
"url": "https://gitea.hsrv.site/mrleo1nid/discord-geodata/raw/branch/release/sing-box/ip/discord-vernette-udp.srs"
}
],
"rules": [{"rule_set": ["discord", "discord-ip", "discord-voice"], "outbound": "proxy"}]
}
}
```
`network` и `port_range` зашиты внутрь самого rule-set — отдельно в правиле их
указывать не нужно.
## Clash / mihomo
```yaml
rule-providers:
discord:
type: http
behavior: domain
format: yaml
url: https://gitea.hsrv.site/mrleo1nid/discord-geodata/raw/branch/release/clash/site/discord.yaml
path: ./providers/discord.yaml
interval: 86400
discord-ip:
type: http
behavior: ipcidr
format: yaml
url: https://gitea.hsrv.site/mrleo1nid/discord-geodata/raw/branch/release/clash/ip/discord.yaml
path: ./providers/discord-ip.yaml
interval: 86400
rules:
- RULE-SET,discord,PROXY
- RULE-SET,discord-ip,PROXY
```
Ограничения по протоколу и портам в форматы Clash не переносятся — в шапке таких
файлов об этом написано прямо. Нужен голосовой трафик с портами — берите
sing-box или Xray.
## Апстримы
`tools/sources.json` — список источников, `tools/sync.py` их синхронизирует:
| Источник | Что берём |
| --- | --- |
| [MetaCubeX/meta-rules-dat](https://github.com/MetaCubeX/meta-rules-dat) | домены |
| [itdoginfo/allow-domains](https://github.com/itdoginfo/allow-domains) | домены и голосовые сети с портами |
| [you-oops-dev/ipranges-singbox](https://github.com/you-oops-dev/ipranges-singbox) | сети и имена голосовых узлов |
| [legiz-ru/sb-rule-sets](https://github.com/legiz-ru/sb-rule-sets) | адреса голосовых узлов |
| [vernette/rulesets](https://github.com/vernette/rulesets) | голосовой трафик UDP 50000-50099 |
```sh
python3 tools/sync.py # синхронизировать всё
python3 tools/sync.py --only legiz # только один источник
python3 tools/sync.py --strict # падать, если хоть один источник недоступен
```
Что происходит внутри:
- каждый источник кладётся в **свой** файл `data/discord-<источник>.txt` — так
видно, кто что принёс, и можно выключить конкретный список;
- правила с `network`/`port_range` уезжают в отдельную категорию с директивами
`!network` и `!port` в шапке; если у источника таких наборов несколько, в имя
добавляются порты (`discord-itdoginfo-udp-19000-20000`);
- сводные `data/discord.txt` и `data/ip/discord.txt` пересобираются как список
`include:` — руками их править бесполезно;
- недоступный источник **не** обнуляет свой файл: остаются прежние данные и
печатается предупреждение. Устаревший список лучше пустого;
- источники в формате `.srs` скомпилированы, для них нужен бинарь `sing-box`
(`tools/fetch-tools.sh` его ставит). Без него они просто пропускаются.
Файлы, которые пишет `sync.py`, помечены шапкой `# GENERATED` — файл без такой
шапки скрипт не тронет. Поэтому рукописный `discord-base.txt` в безопасности.
## Как добавить домен или сеть руками
Правьте `data/discord-base.txt` (домены) и `data/ip/discord-base.txt` (сети) —
они попадут в сводные категории автоматически.
Формат доменного файла: `example.com` — суффикс (сам домен и все поддомены),
`full:example.com` — точное совпадение, `keyword:`, `regexp:`, `include:другая-категория`.
Атрибуты — через `@`: `example.com @ads`.
Формат IP-файла: `1.2.3.4` или `1.2.3.0/24` (IPv6 тоже), `include:`, и директивы
в шапке:
```
!network: udp
!port: 50000-50099
```
Ограничения действуют на весь файл. `include:` разрешён только между
категориями с **одинаковыми** ограничениями, иначе сборка упадёт: так не выйдет
случайно снять «только UDP на голосовых портах» с половины Cloudflare.
Дальше:
```sh
python3 tools/build.py --check
```
`--check` ругается на дубликаты, на домены, уже покрытые суффиксом, на сети,
вложенные в другие сети того же файла, на циклы в `include` и на конфликт
ограничений.
## Локальная сборка
```sh
python3 tools/build.py --check # проверить исходники
python3 tools/build.py # собрать dist/
python3 -m unittest discover -s tools -t tools
```
Зависимостей нет — только стандартная библиотека Python 3.10+. Для `.srs` и
`.mrs` нужны бинарники sing-box и mihomo; без них сборка проходит, просто эти
файлы не появляются:
```sh
sh tools/fetch-tools.sh ./bin && export PATH="$PWD/bin:$PATH"
```
Полезные флаги: `--out <каталог>`, `--sing-box <путь>`, `--mihomo <путь>`,
`--outbound <тег>` (какой `outboundTag` подставить в `xray/routing.json`).
Сборка детерминирована: одни и те же данные дают побайтово одинаковый `.dat`.
Оба файла после записи читаются обратно и сверяются с исходными правилами.
## CI
`.gitea/workflows/build.yml` на каждый push в `main`, на PR и ежедневно по cron:
ставит внешние инструменты, синхронизирует апстримы, проверяет исходники,
гоняет тесты, собирает `dist/`, скармливает оба `.dat` настоящему Xray и
проверяет, что в скомпилированных `.srs` доехали протокол и порты. После этого
коммитит обновлённый `data/` обратно в `main`, обновляет ветку `release` и
публикует релиз.
Если автокоммит в `main` не нужен — удалите шаг «Обновление data/ в main»:
сборка от этого не сломается, просто списки в репозитории останутся прежними.
## Лицензия
MIT — см. [LICENSE](LICENSE). Списки собраны из публичных источников,
перечисленных выше; лицензии апстримов смотрите в их репозиториях.