From 61c0ea3558ae8281adc0a03e50045f689a36542a Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Mon, 10 Aug 2026 09:24:08 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20geosite/geoip=20=D0=B4=D0=BB=D1=8F=20Di?= =?UTF-8?q?scord=20=D1=81=D0=BE=20=D1=81=D0=B1=D0=BE=D1=80=D0=BA=D0=BE?= =?UTF-8?q?=D0=B9=20=D0=B8=D0=B7=20=D0=B0=D0=BF=D1=81=D1=82=D1=80=D0=B8?= =?UTF-8?q?=D0=BC=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Домены и сети 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 --- .gitea/workflows/build.yml | 187 + .gitignore | 4 + LICENSE | 21 + README.md | 300 ++ data/discord-base.txt | 36 + data/discord-itdoginfo.txt | 25 + data/discord-metacubex.txt | 33 + data/discord-youoops.txt | 4339 +++++++++++++++++ data/discord.txt | 7 + data/ip/discord-base.txt | 106 + data/ip/discord-itdoginfo-udp-19000-20000.txt | 10 + data/ip/discord-itdoginfo-udp-50000-65535.txt | 17 + data/ip/discord-legiz.txt | 49 + data/ip/discord-vernette-udp.txt | 22 + data/ip/discord-youoops.txt | 60 + data/ip/discord.txt | 6 + tools/build.py | 1028 ++++ tools/fetch-tools.sh | 48 + tools/gitea-release.sh | 50 + tools/sources.json | 43 + tools/sync.py | 387 ++ tools/test_build.py | 414 ++ tools/update-geodata.sh | 86 + 23 files changed, 7278 insertions(+) create mode 100644 .gitea/workflows/build.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 data/discord-base.txt create mode 100644 data/discord-itdoginfo.txt create mode 100644 data/discord-metacubex.txt create mode 100644 data/discord-youoops.txt create mode 100644 data/discord.txt create mode 100644 data/ip/discord-base.txt create mode 100644 data/ip/discord-itdoginfo-udp-19000-20000.txt create mode 100644 data/ip/discord-itdoginfo-udp-50000-65535.txt create mode 100644 data/ip/discord-legiz.txt create mode 100644 data/ip/discord-vernette-udp.txt create mode 100644 data/ip/discord-youoops.txt create mode 100644 data/ip/discord.txt create mode 100644 tools/build.py create mode 100644 tools/fetch-tools.sh create mode 100644 tools/gitea-release.sh create mode 100644 tools/sources.json create mode 100644 tools/sync.py create mode 100644 tools/test_build.py create mode 100644 tools/update-geodata.sh diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..65433e9 --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,187 @@ +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 + + # Раньше сборки: sing-box нужен не только для .srs, но и синхронизации — + # три из пяти апстримов отдают уже скомпилированный rule-set. + - name: Внешние инструменты + run: | + sh tools/fetch-tools.sh "$GITHUB_WORKSPACE/bin" + echo "$GITHUB_WORKSPACE/bin" >> "$GITHUB_PATH" + + - name: Синхронизация апстримов + run: python3 tools/sync.py + + - name: Проверка исходников + run: python3 tools/build.py --check + + - name: Тесты + run: python3 -m unittest discover -s tools -t tools + + - 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:discord-geosite.dat:discord"], "outboundTag": "direct"}, + {"type": "field", "ip": ["ext:discord-geoip.dat:discord"], "outboundTag": "direct"}, + {"type": "field", "ip": ["ext:geoip.dat:discord-base"], "outboundTag": "direct"} + ] + } + } + EOF + XRAY_LOCATION_ASSET="$GITHUB_WORKSPACE/dist" xray run -test -c /tmp/xray-test.json + + - name: Проверка rule-set для sing-box + run: | + set -eu + sing-box rule-set decompile --output /tmp/site.json dist/sing-box/site/discord.srs + grep -q '"discord.com"' /tmp/site.json + sing-box rule-set decompile --output /tmp/ip.json dist/sing-box/ip/discord.srs + grep -q 'ip_cidr' /tmp/ip.json + # Ограниченные категории обязаны донести протокол и порты до .srs. + for src in dist/sing-box/ip/*-udp*.json; do + [ -e "$src" ] || continue + sing-box rule-set decompile --output /tmp/udp.json "${src%.json}.srs" + grep -q '"udp"' /tmp/udp.json + grep -q 'port_range' /tmp/udp.json + done + + - name: Обновление data/ в main + if: github.event_name != 'pull_request' + run: | + set -eu + if git diff --quiet -- data; then + echo "апстримы не изменились" + exit 0 + fi + token="${RELEASE_TOKEN:-$AUTO_TOKEN}" + [ -n "$token" ] || { echo "нет токена для push" >&2; exit 1; } + host="${GITEA_URL#*://}" + scheme="${GITEA_URL%%://*}" + git config user.name "gitea-actions" + git config user.email "gitea-actions@noreply.local" + git add data + git commit -q -m "sync: обновление списков из апстримов" + git push -q "$scheme://gitea-actions:$token@$host/$GITEA_REPO.git" HEAD:main + echo "data/ обновлён в main" + + - 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 '| IP-категория | Сетей | Ограничения |' + echo '| --- | ---: | --- |' + python3 -c " +import json +for k, v in json.load(open('dist/metadata.json'))['ip_categories'].items(): + limits = ' '.join(filter(None, ['/'.join(v.get('network', [])), v.get('ports', '')])) or '—' + print(f\"| {k} | {v['count']} | {limits} |\") +" + echo + echo "sha256(discord-geosite.dat) = \`$(cut -d' ' -f1 dist/discord-geosite.dat.sha256sum)\`" + echo "sha256(discord-geoip.dat) = \`$(cut -d' ' -f1 dist/discord-geoip.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/discord-geodata-all.tar.gz -C dist . + mv /tmp/discord-geodata-all.tar.gz dist/ + sh tools/gitea-release.sh "${{ steps.notes.outputs.tag }}" /tmp/notes.md \ + dist/discord-geosite.dat \ + dist/discord-geosite.dat.sha256sum \ + dist/discord-geoip.dat \ + dist/discord-geoip.dat.sha256sum \ + dist/xray/routing.json \ + dist/sing-box/site/discord.srs \ + dist/sing-box/ip/discord.srs \ + dist/metadata.json \ + dist/SHA256SUMS \ + dist/discord-geodata-all.tar.gz diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cfff79e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +dist/ +bin/ +__pycache__/ +*.pyc diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9f75c7f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Leonid Pershin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3b9fb6b --- /dev/null +++ b/README.md @@ -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). Списки собраны из публичных источников, +перечисленных выше; лицензии апстримов смотрите в их репозиториях. diff --git a/data/discord-base.txt b/data/discord-base.txt new file mode 100644 index 0000000..d865647 --- /dev/null +++ b/data/discord-base.txt @@ -0,0 +1,36 @@ +# discord-base — рукописный список доменов Discord. +# Это единственный доменный файл, который правят руками: остальные +# data/discord-*.txt пересобирает tools/sync.py из апстримов. + +dis.gd +discord.co +discord.com +discord.design +discord.dev +discord.gg +discord.gift +discord.gifts +discord.media +discord.new +discord.store +discord.tools +discord-activities.com +discordactivities.com +discordapp.com +discordapp.io +discordapp.net +discordcdn.com +discordmerch.com +discordpartygames.com +discordsays.com +discordstatus.com + +# Проекты Discord под собственными доменами. +airhorn.solutions +airhornbot.com +bigbeans.solutions +watchanimeattheoffice.com + +# Точные имена: суффикс здесь захватил бы чужие сервисы (Google Storage, Zendesk). +full:discord-attachments-uploads-prd.storage.googleapis.com +full:hammerandchisel.ssl.zendesk.com diff --git a/data/discord-itdoginfo.txt b/data/discord-itdoginfo.txt new file mode 100644 index 0000000..8053cb6 --- /dev/null +++ b/data/discord-itdoginfo.txt @@ -0,0 +1,25 @@ +# GENERATED tools/sync.py — не редактируйте вручную. +# Источник: itdoginfo/allow-domains, последний релиз +# URL: https://github.com/itdoginfo/allow-domains/releases/latest/download/discord.srs +# Правил: 20 + +dis.gd +discord-activities.com +discord-attachments-uploads-prd.storage.googleapis.com +discord.co +discord.com +discord.design +discord.dev +discord.gg +discord.gift +discord.gifts +discord.media +discord.new +discord.store +discord.tools +discordactivities.com +discordapp.com +discordapp.net +discordmerch.com +discordpartygames.com +discordsays.com diff --git a/data/discord-metacubex.txt b/data/discord-metacubex.txt new file mode 100644 index 0000000..e92843c --- /dev/null +++ b/data/discord-metacubex.txt @@ -0,0 +1,33 @@ +# GENERATED tools/sync.py — не редактируйте вручную. +# Источник: MetaCubeX meta-rules-dat, ветка sing (домены) +# URL: https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/refs/heads/sing/geo/geosite/discord.json +# Правил: 28 + +airhorn.solutions +airhornbot.com +bigbeans.solutions +dis.gd +discord-activities.com +discord.co +discord.com +discord.design +discord.dev +discord.gg +discord.gift +discord.gifts +discord.media +discord.new +discord.store +discord.tools +discordactivities.com +discordapp.com +discordapp.io +discordapp.net +discordcdn.com +discordmerch.com +discordpartygames.com +discordsays.com +discordstatus.com +watchanimeattheoffice.com +full:discord-attachments-uploads-prd.storage.googleapis.com +full:hammerandchisel.ssl.zendesk.com diff --git a/data/discord-youoops.txt b/data/discord-youoops.txt new file mode 100644 index 0000000..b33ceef --- /dev/null +++ b/data/discord-youoops.txt @@ -0,0 +1,4339 @@ +# GENERATED tools/sync.py — не редактируйте вручную. +# Источник: you-oops-dev/ipranges-singbox (диапазоны сетей) +# URL: https://raw.githubusercontent.com/you-oops-dev/ipranges-singbox/refs/heads/main/discord/discord.srs +# Правил: 4334 + +full:96discord-attachments-uploads-prd.storage.googleapis.com +full:atlanta1068.discord.gg +full:atlanta1212.discord.gg +full:atlanta1271.discord.gg +full:atlanta1350.discord.gg +full:atlanta1383.discord.gg +full:atlanta1392.discord.gg +full:atlanta1569.discord.gg +full:atlanta1572.discord.gg +full:atlanta1634.discord.gg +full:atlanta1716.discord.gg +full:atlanta1731.discord.gg +full:atlanta2018.discord.gg +full:atlanta2021.discord.gg +full:atlanta2155.discord.gg +full:atlanta2242.discord.gg +full:atlanta238.discord.gg +full:atlanta2727.discord.gg +full:atlanta2765.discord.gg +full:atlanta3002.discord.gg +full:atlanta3021.discord.gg +full:atlanta312.discord.gg +full:atlanta3174.discord.gg +full:atlanta3244.discord.gg +full:atlanta3285.discord.gg +full:atlanta3392.discord.gg +full:atlanta3848.discord.gg +full:atlanta3861.discord.gg +full:atlanta4125.discord.gg +full:atlanta4313.discord.gg +full:atlanta4321.discord.gg +full:atlanta4415.discord.gg +full:atlanta4421.discord.gg +full:atlanta4508.discord.gg +full:atlanta4623.discord.gg +full:atlanta4718.discord.gg +full:atlanta4754.discord.gg +full:atlanta4800.discord.gg +full:atlanta4854.discord.gg +full:atlanta4878.discord.gg +full:atlanta4879.discord.gg +full:atlanta4913.discord.gg +full:atlanta5083.discord.gg +full:atlanta523.discord.gg +full:atlanta5263.discord.gg +full:atlanta5288.discord.gg +full:atlanta5347.discord.gg +full:atlanta536.discord.gg +full:atlanta570.discord.gg +full:atlanta5791.discord.gg +full:atlanta5847.discord.gg +full:atlanta5855.discord.gg +full:atlanta586.discord.gg +full:atlanta5870.discord.gg +full:atlanta5960.discord.gg +full:atlanta6120.discord.gg +full:atlanta6132.discord.gg +full:atlanta6144.discord.gg +full:atlanta617.discord.gg +full:atlanta620.discord.gg +full:atlanta6265.discord.gg +full:atlanta643.discord.gg +full:atlanta647.discord.gg +full:atlanta6538.discord.gg +full:atlanta6649.discord.gg +full:atlanta7086.discord.gg +full:atlanta743.discord.gg +full:atlanta7447.discord.gg +full:atlanta7660.discord.gg +full:atlanta7745.discord.gg +full:atlanta7859.discord.gg +full:atlanta7924.discord.gg +full:atlanta8001.discord.gg +full:atlanta8067.discord.gg +full:atlanta8082.discord.gg +full:atlanta8631.discord.gg +full:atlanta8744.discord.gg +full:atlanta8766.discord.gg +full:atlanta8911.discord.gg +full:atlanta8983.discord.gg +full:atlanta9025.discord.gg +full:atlanta9089.discord.gg +full:atlanta9162.discord.gg +full:atlanta9169.discord.gg +full:atlanta9176.discord.gg +full:atlanta9190.discord.gg +full:atlanta9315.discord.gg +full:atlanta9332.discord.gg +full:atlanta9511.discord.gg +full:atlanta9547.discord.gg +full:atlanta9712.discord.gg +full:atlanta9766.discord.gg +full:atlanta9781.discord.gg +full:atlanta9825.discord.gg +full:atlanta9866.discord.gg +full:atlanta9922.discord.gg +full:atlanta9978.discord.gg +full:brazil1084.discord.gg +full:brazil11000.discord.gg +full:brazil11001.discord.gg +full:brazil11002.discord.gg +full:brazil11003.discord.gg +full:brazil11004.discord.gg +full:brazil11005.discord.gg +full:brazil11006.discord.gg +full:brazil11007.discord.gg +full:brazil11008.discord.gg +full:brazil11009.discord.gg +full:brazil11010.discord.gg +full:brazil11011.discord.gg +full:brazil11012.discord.gg +full:brazil11013.discord.gg +full:brazil11014.discord.gg +full:brazil11015.discord.gg +full:brazil11016.discord.gg +full:brazil11017.discord.gg +full:brazil11018.discord.gg +full:brazil11019.discord.gg +full:brazil11020.discord.gg +full:brazil11021.discord.gg +full:brazil11022.discord.gg +full:brazil11023.discord.gg +full:brazil11024.discord.gg +full:brazil11025.discord.gg +full:brazil11026.discord.gg +full:brazil11027.discord.gg +full:brazil11028.discord.gg +full:brazil11029.discord.gg +full:brazil11030.discord.gg +full:brazil11031.discord.gg +full:brazil11032.discord.gg +full:brazil11033.discord.gg +full:brazil11034.discord.gg +full:brazil11035.discord.gg +full:brazil11036.discord.gg +full:brazil11037.discord.gg +full:brazil11038.discord.gg +full:brazil11039.discord.gg +full:brazil11040.discord.gg +full:brazil11041.discord.gg +full:brazil11042.discord.gg +full:brazil11043.discord.gg +full:brazil11044.discord.gg +full:brazil11045.discord.gg +full:brazil11046.discord.gg +full:brazil11047.discord.gg +full:brazil11048.discord.gg +full:brazil11049.discord.gg +full:brazil11050.discord.gg +full:brazil11051.discord.gg +full:brazil11052.discord.gg +full:brazil11053.discord.gg +full:brazil11054.discord.gg +full:brazil11055.discord.gg +full:brazil11056.discord.gg +full:brazil11057.discord.gg +full:brazil11058.discord.gg +full:brazil11059.discord.gg +full:brazil11060.discord.gg +full:brazil11061.discord.gg +full:brazil11062.discord.gg +full:brazil11063.discord.gg +full:brazil11064.discord.gg +full:brazil11065.discord.gg +full:brazil11066.discord.gg +full:brazil11067.discord.gg +full:brazil11068.discord.gg +full:brazil11069.discord.gg +full:brazil11070.discord.gg +full:brazil11071.discord.gg +full:brazil11072.discord.gg +full:brazil11073.discord.gg +full:brazil11074.discord.gg +full:brazil11075.discord.gg +full:brazil11076.discord.gg +full:brazil11077.discord.gg +full:brazil11078.discord.gg +full:brazil11079.discord.gg +full:brazil11080.discord.gg +full:brazil11081.discord.gg +full:brazil11082.discord.gg +full:brazil11083.discord.gg +full:brazil11084.discord.gg +full:brazil11085.discord.gg +full:brazil11086.discord.gg +full:brazil11087.discord.gg +full:brazil11088.discord.gg +full:brazil11089.discord.gg +full:brazil11090.discord.gg +full:brazil11091.discord.gg +full:brazil11092.discord.gg +full:brazil11093.discord.gg +full:brazil11094.discord.gg +full:brazil11095.discord.gg +full:brazil11096.discord.gg +full:brazil11097.discord.gg +full:brazil11098.discord.gg +full:brazil11099.discord.gg +full:brazil11100.discord.gg +full:brazil11101.discord.gg +full:brazil11102.discord.gg +full:brazil11103.discord.gg +full:brazil11104.discord.gg +full:brazil11105.discord.gg +full:brazil11106.discord.gg +full:brazil11107.discord.gg +full:brazil11108.discord.gg +full:brazil11109.discord.gg +full:brazil11110.discord.gg +full:brazil11111.discord.gg +full:brazil11112.discord.gg +full:brazil11113.discord.gg +full:brazil11114.discord.gg +full:brazil11115.discord.gg +full:brazil11116.discord.gg +full:brazil11117.discord.gg +full:brazil11118.discord.gg +full:brazil11119.discord.gg +full:brazil11120.discord.gg +full:brazil11121.discord.gg +full:brazil11122.discord.gg +full:brazil11123.discord.gg +full:brazil11124.discord.gg +full:brazil11125.discord.gg +full:brazil11126.discord.gg +full:brazil11127.discord.gg +full:brazil11128.discord.gg +full:brazil11129.discord.gg +full:brazil11130.discord.gg +full:brazil11131.discord.gg +full:brazil11132.discord.gg +full:brazil11133.discord.gg +full:brazil11134.discord.gg +full:brazil11135.discord.gg +full:brazil11136.discord.gg +full:brazil11137.discord.gg +full:brazil11138.discord.gg +full:brazil11139.discord.gg +full:brazil11140.discord.gg +full:brazil11141.discord.gg +full:brazil11142.discord.gg +full:brazil11143.discord.gg +full:brazil11144.discord.gg +full:brazil11145.discord.gg +full:brazil11146.discord.gg +full:brazil11147.discord.gg +full:brazil11148.discord.gg +full:brazil11149.discord.gg +full:brazil11150.discord.gg +full:brazil11151.discord.gg +full:brazil11152.discord.gg +full:brazil11153.discord.gg +full:brazil11154.discord.gg +full:brazil11155.discord.gg +full:brazil11156.discord.gg +full:brazil11157.discord.gg +full:brazil11158.discord.gg +full:brazil11159.discord.gg +full:brazil11160.discord.gg +full:brazil11161.discord.gg +full:brazil11162.discord.gg +full:brazil11163.discord.gg +full:brazil11164.discord.gg +full:brazil11165.discord.gg +full:brazil11166.discord.gg +full:brazil11167.discord.gg +full:brazil11168.discord.gg +full:brazil11169.discord.gg +full:brazil11170.discord.gg +full:brazil11171.discord.gg +full:brazil11172.discord.gg +full:brazil11173.discord.gg +full:brazil11174.discord.gg +full:brazil11175.discord.gg +full:brazil11176.discord.gg +full:brazil11177.discord.gg +full:brazil11178.discord.gg +full:brazil11179.discord.gg +full:brazil11180.discord.gg +full:brazil11181.discord.gg +full:brazil11182.discord.gg +full:brazil11183.discord.gg +full:brazil11184.discord.gg +full:brazil11185.discord.gg +full:brazil11186.discord.gg +full:brazil11187.discord.gg +full:brazil11188.discord.gg +full:brazil11189.discord.gg +full:brazil11190.discord.gg +full:brazil11191.discord.gg +full:brazil11192.discord.gg +full:brazil11193.discord.gg +full:brazil11194.discord.gg +full:brazil11195.discord.gg +full:brazil11196.discord.gg +full:brazil11197.discord.gg +full:brazil11198.discord.gg +full:brazil11199.discord.gg +full:brazil11200.discord.gg +full:brazil11201.discord.gg +full:brazil11202.discord.gg +full:brazil11203.discord.gg +full:brazil11204.discord.gg +full:brazil11205.discord.gg +full:brazil11206.discord.gg +full:brazil11207.discord.gg +full:brazil11208.discord.gg +full:brazil11209.discord.gg +full:brazil1257.discord.gg +full:brazil1262.discord.gg +full:brazil1444.discord.gg +full:brazil1598.discord.gg +full:brazil1854.discord.gg +full:brazil1914.discord.gg +full:brazil2150.discord.gg +full:brazil2163.discord.gg +full:brazil2168.discord.gg +full:brazil2198.discord.gg +full:brazil221.discord.gg +full:brazil2229.discord.gg +full:brazil2265.discord.gg +full:brazil2270.discord.gg +full:brazil2281.discord.gg +full:brazil2302.discord.gg +full:brazil2367.discord.gg +full:brazil2726.discord.gg +full:brazil2869.discord.gg +full:brazil2889.discord.gg +full:brazil2911.discord.gg +full:brazil2954.discord.gg +full:brazil306.discord.gg +full:brazil3103.discord.gg +full:brazil3202.discord.gg +full:brazil3203.discord.gg +full:brazil3474.discord.gg +full:brazil3617.discord.gg +full:brazil3774.discord.gg +full:brazil3979.discord.gg +full:brazil4000.discord.gg +full:brazil4163.discord.gg +full:brazil4256.discord.gg +full:brazil4265.discord.gg +full:brazil4309.discord.gg +full:brazil4328.discord.gg +full:brazil4364.discord.gg +full:brazil4479.discord.gg +full:brazil45.discord.gg +full:brazil4635.discord.gg +full:brazil4810.discord.gg +full:brazil5095.discord.gg +full:brazil5160.discord.gg +full:brazil5284.discord.gg +full:brazil535.discord.gg +full:brazil5562.discord.gg +full:brazil5600.discord.gg +full:brazil5604.discord.gg +full:brazil5894.discord.gg +full:brazil5948.discord.gg +full:brazil602.discord.gg +full:brazil607.discord.gg +full:brazil6084.discord.gg +full:brazil6100.discord.gg +full:brazil6256.discord.gg +full:brazil6564.discord.gg +full:brazil6613.discord.gg +full:brazil669.discord.gg +full:brazil6833.discord.gg +full:brazil7011.discord.gg +full:brazil706.discord.gg +full:brazil7080.discord.gg +full:brazil7238.discord.gg +full:brazil7358.discord.gg +full:brazil7461.discord.gg +full:brazil7525.discord.gg +full:brazil7559.discord.gg +full:brazil7629.discord.gg +full:brazil7861.discord.gg +full:brazil8003.discord.gg +full:brazil8035.discord.gg +full:brazil8101.discord.gg +full:brazil8114.discord.gg +full:brazil8280.discord.gg +full:brazil8579.discord.gg +full:brazil8774.discord.gg +full:brazil8873.discord.gg +full:brazil899.discord.gg +full:brazil921.discord.gg +full:brazil9375.discord.gg +full:brazil9475.discord.gg +full:brazil963.discord.gg +full:brazil9977.discord.gg +full:bucharest1272.discord.gg +full:bucharest1367.discord.gg +full:bucharest146.discord.gg +full:bucharest1590.discord.gg +full:bucharest1729.discord.gg +full:bucharest1832.discord.gg +full:bucharest1838.discord.gg +full:bucharest1896.discord.gg +full:bucharest1900.discord.gg +full:bucharest2096.discord.gg +full:bucharest2139.discord.gg +full:bucharest2232.discord.gg +full:bucharest2635.discord.gg +full:bucharest2637.discord.gg +full:bucharest2697.discord.gg +full:bucharest2737.discord.gg +full:bucharest2980.discord.gg +full:bucharest303.discord.gg +full:bucharest3157.discord.gg +full:bucharest3476.discord.gg +full:bucharest3592.discord.gg +full:bucharest3770.discord.gg +full:bucharest4031.discord.gg +full:bucharest4132.discord.gg +full:bucharest421.discord.gg +full:bucharest4450.discord.gg +full:bucharest4787.discord.gg +full:bucharest4872.discord.gg +full:bucharest4950.discord.gg +full:bucharest4993.discord.gg +full:bucharest5086.discord.gg +full:bucharest5248.discord.gg +full:bucharest5334.discord.gg +full:bucharest5424.discord.gg +full:bucharest5485.discord.gg +full:bucharest5503.discord.gg +full:bucharest5587.discord.gg +full:bucharest5732.discord.gg +full:bucharest5799.discord.gg +full:bucharest6032.discord.gg +full:bucharest6099.discord.gg +full:bucharest6196.discord.gg +full:bucharest6267.discord.gg +full:bucharest6542.discord.gg +full:bucharest6555.discord.gg +full:bucharest6664.discord.gg +full:bucharest6907.discord.gg +full:bucharest7042.discord.gg +full:bucharest7479.discord.gg +full:bucharest7482.discord.gg +full:bucharest7591.discord.gg +full:bucharest7684.discord.gg +full:bucharest7981.discord.gg +full:bucharest8178.discord.gg +full:bucharest8213.discord.gg +full:bucharest8275.discord.gg +full:bucharest8370.discord.gg +full:bucharest8466.discord.gg +full:bucharest8528.discord.gg +full:bucharest8668.discord.gg +full:bucharest8756.discord.gg +full:bucharest8992.discord.gg +full:bucharest9080.discord.gg +full:bucharest9417.discord.gg +full:bucharest9592.discord.gg +full:bucharest9623.discord.gg +full:bucharest975.discord.gg +full:buenos-aires1073.discord.gg +full:buenos-aires1399.discord.gg +full:buenos-aires2043.discord.gg +full:buenos-aires2072.discord.gg +full:buenos-aires2111.discord.gg +full:buenos-aires2243.discord.gg +full:buenos-aires3213.discord.gg +full:buenos-aires3218.discord.gg +full:buenos-aires3406.discord.gg +full:buenos-aires3661.discord.gg +full:buenos-aires3900.discord.gg +full:buenos-aires3925.discord.gg +full:buenos-aires4053.discord.gg +full:buenos-aires4148.discord.gg +full:buenos-aires4215.discord.gg +full:buenos-aires4484.discord.gg +full:buenos-aires4510.discord.gg +full:buenos-aires4586.discord.gg +full:buenos-aires4802.discord.gg +full:buenos-aires4905.discord.gg +full:buenos-aires4906.discord.gg +full:buenos-aires500.discord.gg +full:buenos-aires5066.discord.gg +full:buenos-aires5831.discord.gg +full:buenos-aires6249.discord.gg +full:buenos-aires6271.discord.gg +full:buenos-aires6374.discord.gg +full:buenos-aires6417.discord.gg +full:buenos-aires6567.discord.gg +full:buenos-aires6724.discord.gg +full:buenos-aires6733.discord.gg +full:buenos-aires6816.discord.gg +full:buenos-aires7087.discord.gg +full:buenos-aires7166.discord.gg +full:buenos-aires7366.discord.gg +full:buenos-aires7387.discord.gg +full:buenos-aires7469.discord.gg +full:buenos-aires7609.discord.gg +full:buenos-aires7866.discord.gg +full:buenos-aires8024.discord.gg +full:buenos-aires8100.discord.gg +full:buenos-aires8151.discord.gg +full:buenos-aires8619.discord.gg +full:buenos-aires9180.discord.gg +full:buenos-aires9315.discord.gg +full:buenos-aires9432.discord.gg +full:buenos-aires9775.discord.gg +full:canary.discord.com +full:cdn.discordapp.com +full:dis.gd +full:discord-activities.com +full:discord-attachments-uploads-prd.storage.googleapis.com +full:discord-devops.storage.googleapis.com +full:discord-staging.storage.googleapis.com +full:discord.co +full:discord.com +full:discord.design +full:discord.dev +full:discord.gg +full:discord.gift +full:discord.gifts +full:discord.media +full:discord.new +full:discord.storage.googleapis.com +full:discord.store +full:discord.tools +full:discordactivities.com +full:discordapp.com +full:discordapp.io +full:discordapp.net +full:discordapp.page.link +full:discordcdn.com +full:discordmerch.com +full:discordpartygames.com +full:discordsays.com +full:discordsez.com +full:discordstatus.com +full:dubai1217.discord.gg +full:dubai1588.discord.gg +full:dubai2333.discord.gg +full:dubai2709.discord.gg +full:dubai4014.discord.gg +full:dubai4544.discord.gg +full:dubai5810.discord.gg +full:dubai6530.discord.gg +full:dubai6771.discord.gg +full:dubai6910.discord.gg +full:dubai6962.discord.gg +full:dubai949.discord.gg +full:finland10000.discord.gg +full:finland10001.discord.gg +full:finland10002.discord.gg +full:finland10003.discord.gg +full:finland10004.discord.gg +full:finland10005.discord.gg +full:finland10006.discord.gg +full:finland10007.discord.gg +full:finland10008.discord.gg +full:finland10009.discord.gg +full:finland10010.discord.gg +full:finland10011.discord.gg +full:finland10012.discord.gg +full:finland10013.discord.gg +full:finland10014.discord.gg +full:finland10015.discord.gg +full:finland10016.discord.gg +full:finland10017.discord.gg +full:finland10018.discord.gg +full:finland10019.discord.gg +full:finland10020.discord.gg +full:finland10021.discord.gg +full:finland10022.discord.gg +full:finland10023.discord.gg +full:finland10024.discord.gg +full:finland10025.discord.gg +full:finland10026.discord.gg +full:finland10027.discord.gg +full:finland10028.discord.gg +full:finland10029.discord.gg +full:finland10030.discord.gg +full:finland10031.discord.gg +full:finland10032.discord.gg +full:finland10033.discord.gg +full:finland10034.discord.gg +full:finland10035.discord.gg +full:finland10036.discord.gg +full:finland10037.discord.gg +full:finland10038.discord.gg +full:finland10039.discord.gg +full:finland10040.discord.gg +full:finland10041.discord.gg +full:finland10042.discord.gg +full:finland10043.discord.gg +full:finland10044.discord.gg +full:finland10045.discord.gg +full:finland10046.discord.gg +full:finland10047.discord.gg +full:finland10048.discord.gg +full:finland10049.discord.gg +full:finland10050.discord.gg +full:finland10051.discord.gg +full:finland10052.discord.gg +full:finland10053.discord.gg +full:finland10054.discord.gg +full:finland10055.discord.gg +full:finland10056.discord.gg +full:finland10057.discord.gg +full:finland10058.discord.gg +full:finland10059.discord.gg +full:finland10060.discord.gg +full:finland10061.discord.gg +full:finland10062.discord.gg +full:finland10063.discord.gg +full:finland10064.discord.gg +full:finland10065.discord.gg +full:finland10066.discord.gg +full:finland10067.discord.gg +full:finland10068.discord.gg +full:finland10069.discord.gg +full:finland10070.discord.gg +full:finland10071.discord.gg +full:finland10072.discord.gg +full:finland10073.discord.gg +full:finland10074.discord.gg +full:finland10075.discord.gg +full:finland10076.discord.gg +full:finland10077.discord.gg +full:finland10078.discord.gg +full:finland10079.discord.gg +full:finland10080.discord.gg +full:finland10081.discord.gg +full:finland10082.discord.gg +full:finland10083.discord.gg +full:finland10084.discord.gg +full:finland10085.discord.gg +full:finland10086.discord.gg +full:finland10087.discord.gg +full:finland10088.discord.gg +full:finland10089.discord.gg +full:finland10090.discord.gg +full:finland10091.discord.gg +full:finland10092.discord.gg +full:finland10093.discord.gg +full:finland10094.discord.gg +full:finland10095.discord.gg +full:finland10096.discord.gg +full:finland10097.discord.gg +full:finland10098.discord.gg +full:finland10099.discord.gg +full:finland10100.discord.gg +full:finland10101.discord.gg +full:finland10102.discord.gg +full:finland10103.discord.gg +full:finland10104.discord.gg +full:finland10105.discord.gg +full:finland10106.discord.gg +full:finland10107.discord.gg +full:finland10108.discord.gg +full:finland10109.discord.gg +full:finland10110.discord.gg +full:finland10111.discord.gg +full:finland10112.discord.gg +full:finland10113.discord.gg +full:finland10114.discord.gg +full:finland10115.discord.gg +full:finland10116.discord.gg +full:finland10117.discord.gg +full:finland10118.discord.gg +full:finland10119.discord.gg +full:finland10120.discord.gg +full:finland10121.discord.gg +full:finland10122.discord.gg +full:finland10123.discord.gg +full:finland10124.discord.gg +full:finland10125.discord.gg +full:finland10126.discord.gg +full:finland10127.discord.gg +full:finland10128.discord.gg +full:finland10129.discord.gg +full:finland10130.discord.gg +full:finland10131.discord.gg +full:finland10132.discord.gg +full:finland10133.discord.gg +full:frankfurt10000.discord.gg +full:frankfurt10001.discord.gg +full:frankfurt10002.discord.gg +full:frankfurt10003.discord.gg +full:frankfurt10004.discord.gg +full:frankfurt10005.discord.gg +full:frankfurt10006.discord.gg +full:frankfurt10007.discord.gg +full:frankfurt10008.discord.gg +full:frankfurt10009.discord.gg +full:frankfurt10010.discord.gg +full:frankfurt10011.discord.gg +full:frankfurt10012.discord.gg +full:frankfurt10013.discord.gg +full:frankfurt10014.discord.gg +full:frankfurt10015.discord.gg +full:frankfurt10016.discord.gg +full:frankfurt10017.discord.gg +full:frankfurt10018.discord.gg +full:frankfurt10019.discord.gg +full:frankfurt10020.discord.gg +full:frankfurt10021.discord.gg +full:frankfurt10022.discord.gg +full:frankfurt10023.discord.gg +full:frankfurt10024.discord.gg +full:frankfurt10025.discord.gg +full:frankfurt10026.discord.gg +full:frankfurt10027.discord.gg +full:frankfurt10028.discord.gg +full:frankfurt10029.discord.gg +full:frankfurt10030.discord.gg +full:frankfurt10031.discord.gg +full:frankfurt10032.discord.gg +full:frankfurt10033.discord.gg +full:frankfurt10034.discord.gg +full:frankfurt10035.discord.gg +full:frankfurt10036.discord.gg +full:frankfurt10037.discord.gg +full:frankfurt10038.discord.gg +full:frankfurt10039.discord.gg +full:frankfurt10040.discord.gg +full:frankfurt10041.discord.gg +full:frankfurt10042.discord.gg +full:frankfurt10043.discord.gg +full:frankfurt10044.discord.gg +full:frankfurt10045.discord.gg +full:frankfurt10046.discord.gg +full:frankfurt10047.discord.gg +full:frankfurt10048.discord.gg +full:frankfurt10049.discord.gg +full:frankfurt10050.discord.gg +full:frankfurt10051.discord.gg +full:frankfurt10052.discord.gg +full:frankfurt10053.discord.gg +full:frankfurt10054.discord.gg +full:frankfurt10055.discord.gg +full:frankfurt10056.discord.gg +full:frankfurt10057.discord.gg +full:frankfurt10058.discord.gg +full:frankfurt10059.discord.gg +full:frankfurt10060.discord.gg +full:frankfurt10061.discord.gg +full:frankfurt10062.discord.gg +full:frankfurt10063.discord.gg +full:frankfurt10064.discord.gg +full:frankfurt10065.discord.gg +full:frankfurt10066.discord.gg +full:frankfurt10067.discord.gg +full:frankfurt10068.discord.gg +full:frankfurt10069.discord.gg +full:frankfurt10070.discord.gg +full:frankfurt10071.discord.gg +full:frankfurt10072.discord.gg +full:frankfurt10073.discord.gg +full:frankfurt10074.discord.gg +full:frankfurt10075.discord.gg +full:frankfurt10076.discord.gg +full:frankfurt10077.discord.gg +full:frankfurt10078.discord.gg +full:frankfurt10079.discord.gg +full:frankfurt10080.discord.gg +full:frankfurt10081.discord.gg +full:frankfurt10082.discord.gg +full:frankfurt10083.discord.gg +full:frankfurt10084.discord.gg +full:frankfurt10085.discord.gg +full:frankfurt10086.discord.gg +full:frankfurt10087.discord.gg +full:frankfurt10088.discord.gg +full:frankfurt10089.discord.gg +full:frankfurt10090.discord.gg +full:frankfurt10091.discord.gg +full:frankfurt10092.discord.gg +full:frankfurt10093.discord.gg +full:frankfurt10094.discord.gg +full:frankfurt10095.discord.gg +full:frankfurt10096.discord.gg +full:frankfurt10097.discord.gg +full:frankfurt10098.discord.gg +full:frankfurt10099.discord.gg +full:frankfurt10100.discord.gg +full:frankfurt10101.discord.gg +full:frankfurt10102.discord.gg +full:frankfurt10103.discord.gg +full:frankfurt10104.discord.gg +full:frankfurt10105.discord.gg +full:frankfurt10106.discord.gg +full:frankfurt10107.discord.gg +full:frankfurt10108.discord.gg +full:frankfurt10109.discord.gg +full:frankfurt10110.discord.gg +full:frankfurt10111.discord.gg +full:frankfurt10112.discord.gg +full:frankfurt10113.discord.gg +full:frankfurt10114.discord.gg +full:frankfurt10115.discord.gg +full:frankfurt10116.discord.gg +full:frankfurt10117.discord.gg +full:frankfurt10118.discord.gg +full:frankfurt10119.discord.gg +full:frankfurt10120.discord.gg +full:frankfurt10121.discord.gg +full:frankfurt10122.discord.gg +full:frankfurt10123.discord.gg +full:frankfurt10124.discord.gg +full:frankfurt10125.discord.gg +full:frankfurt10126.discord.gg +full:frankfurt10127.discord.gg +full:frankfurt10128.discord.gg +full:frankfurt10129.discord.gg +full:frankfurt10130.discord.gg +full:frankfurt10131.discord.gg +full:frankfurt10132.discord.gg +full:frankfurt10133.discord.gg +full:frankfurt10134.discord.gg +full:frankfurt10135.discord.gg +full:frankfurt10136.discord.gg +full:frankfurt10137.discord.gg +full:frankfurt10138.discord.gg +full:frankfurt10139.discord.gg +full:frankfurt10140.discord.gg +full:frankfurt10141.discord.gg +full:frankfurt10142.discord.gg +full:frankfurt10143.discord.gg +full:frankfurt10144.discord.gg +full:frankfurt10145.discord.gg +full:frankfurt10146.discord.gg +full:frankfurt10147.discord.gg +full:frankfurt10148.discord.gg +full:frankfurt10149.discord.gg +full:frankfurt10150.discord.gg +full:frankfurt10151.discord.gg +full:frankfurt10152.discord.gg +full:frankfurt10153.discord.gg +full:frankfurt10154.discord.gg +full:frankfurt10155.discord.gg +full:frankfurt10156.discord.gg +full:frankfurt10157.discord.gg +full:frankfurt10158.discord.gg +full:frankfurt10159.discord.gg +full:frankfurt10160.discord.gg +full:frankfurt10161.discord.gg +full:frankfurt10162.discord.gg +full:frankfurt10163.discord.gg +full:frankfurt10164.discord.gg +full:frankfurt10165.discord.gg +full:frankfurt10166.discord.gg +full:frankfurt10167.discord.gg +full:frankfurt10168.discord.gg +full:frankfurt10169.discord.gg +full:frankfurt10170.discord.gg +full:frankfurt10171.discord.gg +full:frankfurt10172.discord.gg +full:frankfurt10173.discord.gg +full:frankfurt10174.discord.gg +full:frankfurt10175.discord.gg +full:frankfurt10176.discord.gg +full:frankfurt10177.discord.gg +full:frankfurt10178.discord.gg +full:frankfurt10179.discord.gg +full:frankfurt10180.discord.gg +full:frankfurt10181.discord.gg +full:frankfurt1045.discord.gg +full:frankfurt1059.discord.gg +full:frankfurt1078.discord.gg +full:frankfurt1173.discord.gg +full:frankfurt1294.discord.gg +full:frankfurt130.discord.gg +full:frankfurt1311.discord.gg +full:frankfurt1346.discord.gg +full:frankfurt1540.discord.gg +full:frankfurt1591.discord.gg +full:frankfurt1638.discord.gg +full:frankfurt1656.discord.gg +full:frankfurt1696.discord.gg +full:frankfurt1699.discord.gg +full:frankfurt1786.discord.gg +full:frankfurt1820.discord.gg +full:frankfurt1899.discord.gg +full:frankfurt2034.discord.gg +full:frankfurt2157.discord.gg +full:frankfurt2246.discord.gg +full:frankfurt2292.discord.gg +full:frankfurt2336.discord.gg +full:frankfurt2453.discord.gg +full:frankfurt2601.discord.gg +full:frankfurt2704.discord.gg +full:frankfurt2803.discord.gg +full:frankfurt2951.discord.gg +full:frankfurt3081.discord.gg +full:frankfurt3111.discord.gg +full:frankfurt3259.discord.gg +full:frankfurt3379.discord.gg +full:frankfurt3441.discord.gg +full:frankfurt3445.discord.gg +full:frankfurt3498.discord.gg +full:frankfurt3687.discord.gg +full:frankfurt3701.discord.gg +full:frankfurt3885.discord.gg +full:frankfurt3916.discord.gg +full:frankfurt3959.discord.gg +full:frankfurt4038.discord.gg +full:frankfurt4201.discord.gg +full:frankfurt4302.discord.gg +full:frankfurt4327.discord.gg +full:frankfurt4480.discord.gg +full:frankfurt4488.discord.gg +full:frankfurt449.discord.gg +full:frankfurt4492.discord.gg +full:frankfurt4550.discord.gg +full:frankfurt4761.discord.gg +full:frankfurt4785.discord.gg +full:frankfurt487.discord.gg +full:frankfurt4896.discord.gg +full:frankfurt4932.discord.gg +full:frankfurt4955.discord.gg +full:frankfurt4985.discord.gg +full:frankfurt5049.discord.gg +full:frankfurt5081.discord.gg +full:frankfurt5219.discord.gg +full:frankfurt5297.discord.gg +full:frankfurt5322.discord.gg +full:frankfurt5373.discord.gg +full:frankfurt5438.discord.gg +full:frankfurt5519.discord.gg +full:frankfurt5720.discord.gg +full:frankfurt5831.discord.gg +full:frankfurt5842.discord.gg +full:frankfurt5843.discord.gg +full:frankfurt586.discord.gg +full:frankfurt598.discord.gg +full:frankfurt6000.discord.gg +full:frankfurt6047.discord.gg +full:frankfurt6097.discord.gg +full:frankfurt6188.discord.gg +full:frankfurt629.discord.gg +full:frankfurt6391.discord.gg +full:frankfurt6482.discord.gg +full:frankfurt6504.discord.gg +full:frankfurt6526.discord.gg +full:frankfurt6542.discord.gg +full:frankfurt659.discord.gg +full:frankfurt6753.discord.gg +full:frankfurt6763.discord.gg +full:frankfurt6770.discord.gg +full:frankfurt6966.discord.gg +full:frankfurt6969.discord.gg +full:frankfurt7098.discord.gg +full:frankfurt7186.discord.gg +full:frankfurt7254.discord.gg +full:frankfurt7288.discord.gg +full:frankfurt7379.discord.gg +full:frankfurt738.discord.gg +full:frankfurt739.discord.gg +full:frankfurt7479.discord.gg +full:frankfurt7480.discord.gg +full:frankfurt7537.discord.gg +full:frankfurt762.discord.gg +full:frankfurt7715.discord.gg +full:frankfurt7848.discord.gg +full:frankfurt7869.discord.gg +full:frankfurt8005.discord.gg +full:frankfurt8068.discord.gg +full:frankfurt8128.discord.gg +full:frankfurt8139.discord.gg +full:frankfurt8162.discord.gg +full:frankfurt8236.discord.gg +full:frankfurt829.discord.gg +full:frankfurt8408.discord.gg +full:frankfurt8431.discord.gg +full:frankfurt8457.discord.gg +full:frankfurt85.discord.gg +full:frankfurt8611.discord.gg +full:frankfurt8620.discord.gg +full:frankfurt8640.discord.gg +full:frankfurt8647.discord.gg +full:frankfurt8784.discord.gg +full:frankfurt882.discord.gg +full:frankfurt8990.discord.gg +full:frankfurt9188.discord.gg +full:frankfurt9428.discord.gg +full:frankfurt9654.discord.gg +full:frankfurt9728.discord.gg +full:frankfurt9970.discord.gg +full:frankfurt9985.discord.gg +full:hammerandchisel.ssl.zendesk.com +full:hongkong11000.discord.gg +full:hongkong11001.discord.gg +full:hongkong11002.discord.gg +full:hongkong11003.discord.gg +full:hongkong11004.discord.gg +full:hongkong11005.discord.gg +full:hongkong11006.discord.gg +full:hongkong11007.discord.gg +full:hongkong11008.discord.gg +full:hongkong11009.discord.gg +full:hongkong11010.discord.gg +full:hongkong11011.discord.gg +full:hongkong11012.discord.gg +full:hongkong11013.discord.gg +full:hongkong11014.discord.gg +full:hongkong11015.discord.gg +full:hongkong11016.discord.gg +full:hongkong11017.discord.gg +full:hongkong11018.discord.gg +full:hongkong11019.discord.gg +full:hongkong11020.discord.gg +full:hongkong11021.discord.gg +full:hongkong11022.discord.gg +full:hongkong11023.discord.gg +full:hongkong11024.discord.gg +full:hongkong11025.discord.gg +full:hongkong11026.discord.gg +full:hongkong11027.discord.gg +full:hongkong11028.discord.gg +full:hongkong11029.discord.gg +full:hongkong11030.discord.gg +full:hongkong11031.discord.gg +full:hongkong11032.discord.gg +full:hongkong11033.discord.gg +full:hongkong11034.discord.gg +full:hongkong11035.discord.gg +full:hongkong11036.discord.gg +full:hongkong11037.discord.gg +full:hongkong11038.discord.gg +full:hongkong11039.discord.gg +full:hongkong11040.discord.gg +full:hongkong11041.discord.gg +full:hongkong11042.discord.gg +full:hongkong11043.discord.gg +full:hongkong11044.discord.gg +full:hongkong11045.discord.gg +full:hongkong11046.discord.gg +full:hongkong11047.discord.gg +full:hongkong11048.discord.gg +full:hongkong11049.discord.gg +full:hongkong11050.discord.gg +full:hongkong11051.discord.gg +full:hongkong11052.discord.gg +full:hongkong11053.discord.gg +full:hongkong11054.discord.gg +full:hongkong11055.discord.gg +full:hongkong11056.discord.gg +full:hongkong11057.discord.gg +full:hongkong11058.discord.gg +full:hongkong11059.discord.gg +full:hongkong11060.discord.gg +full:hongkong11061.discord.gg +full:hongkong11062.discord.gg +full:hongkong11063.discord.gg +full:hongkong11064.discord.gg +full:hongkong11065.discord.gg +full:hongkong11066.discord.gg +full:hongkong11067.discord.gg +full:hongkong11068.discord.gg +full:hongkong11069.discord.gg +full:hongkong11070.discord.gg +full:hongkong11071.discord.gg +full:hongkong11072.discord.gg +full:hongkong11073.discord.gg +full:hongkong11074.discord.gg +full:hongkong11075.discord.gg +full:hongkong11076.discord.gg +full:hongkong11077.discord.gg +full:hongkong11078.discord.gg +full:hongkong11079.discord.gg +full:hongkong11080.discord.gg +full:hongkong11081.discord.gg +full:hongkong11082.discord.gg +full:hongkong11083.discord.gg +full:hongkong11084.discord.gg +full:hongkong11085.discord.gg +full:hongkong11086.discord.gg +full:hongkong11087.discord.gg +full:hongkong11088.discord.gg +full:hongkong11089.discord.gg +full:hongkong11090.discord.gg +full:hongkong11091.discord.gg +full:hongkong11092.discord.gg +full:hongkong11093.discord.gg +full:hongkong11094.discord.gg +full:hongkong11095.discord.gg +full:hongkong11096.discord.gg +full:hongkong11097.discord.gg +full:hongkong11098.discord.gg +full:hongkong11099.discord.gg +full:hongkong11100.discord.gg +full:hongkong11101.discord.gg +full:hongkong11102.discord.gg +full:hongkong11103.discord.gg +full:hongkong11104.discord.gg +full:hongkong11105.discord.gg +full:hongkong11106.discord.gg +full:hongkong11107.discord.gg +full:hongkong11108.discord.gg +full:hongkong11109.discord.gg +full:hongkong11110.discord.gg +full:hongkong11111.discord.gg +full:hongkong11112.discord.gg +full:hongkong11113.discord.gg +full:hongkong11114.discord.gg +full:hongkong11115.discord.gg +full:hongkong11116.discord.gg +full:hongkong11117.discord.gg +full:hongkong11118.discord.gg +full:hongkong11119.discord.gg +full:hongkong11120.discord.gg +full:hongkong11121.discord.gg +full:hongkong11122.discord.gg +full:hongkong11123.discord.gg +full:hongkong11124.discord.gg +full:hongkong11125.discord.gg +full:hongkong11126.discord.gg +full:hongkong11127.discord.gg +full:hongkong11128.discord.gg +full:hongkong11129.discord.gg +full:hongkong11130.discord.gg +full:hongkong11131.discord.gg +full:hongkong11132.discord.gg +full:hongkong11133.discord.gg +full:hongkong11134.discord.gg +full:hongkong11135.discord.gg +full:hongkong11136.discord.gg +full:hongkong11137.discord.gg +full:hongkong11138.discord.gg +full:hongkong11139.discord.gg +full:hongkong11140.discord.gg +full:hongkong11141.discord.gg +full:hongkong11142.discord.gg +full:hongkong11143.discord.gg +full:hongkong11144.discord.gg +full:hongkong11145.discord.gg +full:hongkong11146.discord.gg +full:hongkong11147.discord.gg +full:hongkong11148.discord.gg +full:hongkong11149.discord.gg +full:hongkong11150.discord.gg +full:hongkong11151.discord.gg +full:hongkong11152.discord.gg +full:hongkong11153.discord.gg +full:hongkong11154.discord.gg +full:hongkong11155.discord.gg +full:hongkong11156.discord.gg +full:hongkong11157.discord.gg +full:hongkong11158.discord.gg +full:hongkong11159.discord.gg +full:hongkong11160.discord.gg +full:hongkong11161.discord.gg +full:hongkong11162.discord.gg +full:hongkong11163.discord.gg +full:hongkong11164.discord.gg +full:hongkong11165.discord.gg +full:hongkong11166.discord.gg +full:hongkong11167.discord.gg +full:hongkong11168.discord.gg +full:hongkong11169.discord.gg +full:hongkong11170.discord.gg +full:hongkong11171.discord.gg +full:hongkong11172.discord.gg +full:hongkong11173.discord.gg +full:hongkong11174.discord.gg +full:hongkong11175.discord.gg +full:hongkong11176.discord.gg +full:hongkong11177.discord.gg +full:hongkong11178.discord.gg +full:hongkong11179.discord.gg +full:hongkong11180.discord.gg +full:hongkong11181.discord.gg +full:hongkong11182.discord.gg +full:hongkong11183.discord.gg +full:hongkong11184.discord.gg +full:hongkong11185.discord.gg +full:hongkong11186.discord.gg +full:hongkong11187.discord.gg +full:hongkong11188.discord.gg +full:hongkong11189.discord.gg +full:hongkong11190.discord.gg +full:hongkong11191.discord.gg +full:hongkong11192.discord.gg +full:hongkong11193.discord.gg +full:hongkong11194.discord.gg +full:hongkong11195.discord.gg +full:hongkong11196.discord.gg +full:hongkong11197.discord.gg +full:hongkong11198.discord.gg +full:hongkong11199.discord.gg +full:hongkong11200.discord.gg +full:hongkong11201.discord.gg +full:hongkong11202.discord.gg +full:images-ext-1.discordapp.net +full:images-ext-2.discordapp.net +full:india10000.discord.gg +full:india10001.discord.gg +full:india10002.discord.gg +full:india10003.discord.gg +full:india10004.discord.gg +full:india10005.discord.gg +full:india10006.discord.gg +full:india10007.discord.gg +full:india10008.discord.gg +full:india10009.discord.gg +full:india10010.discord.gg +full:india10011.discord.gg +full:india10012.discord.gg +full:india10013.discord.gg +full:india10014.discord.gg +full:india10015.discord.gg +full:india10016.discord.gg +full:india10017.discord.gg +full:india10018.discord.gg +full:india10019.discord.gg +full:india10020.discord.gg +full:india10021.discord.gg +full:india10022.discord.gg +full:india10023.discord.gg +full:india10024.discord.gg +full:india10025.discord.gg +full:india10026.discord.gg +full:india10027.discord.gg +full:india10028.discord.gg +full:india10029.discord.gg +full:india10030.discord.gg +full:india10031.discord.gg +full:india10032.discord.gg +full:india10033.discord.gg +full:india10034.discord.gg +full:india10035.discord.gg +full:india10036.discord.gg +full:india10037.discord.gg +full:india10038.discord.gg +full:india10039.discord.gg +full:india10040.discord.gg +full:india10041.discord.gg +full:india10042.discord.gg +full:india10043.discord.gg +full:india10044.discord.gg +full:japan1039.discord.gg +full:japan1040.discord.gg +full:japan1043.discord.gg +full:japan11000.discord.gg +full:japan11001.discord.gg +full:japan11002.discord.gg +full:japan11003.discord.gg +full:japan11004.discord.gg +full:japan11005.discord.gg +full:japan11006.discord.gg +full:japan11007.discord.gg +full:japan11008.discord.gg +full:japan11009.discord.gg +full:japan11010.discord.gg +full:japan11011.discord.gg +full:japan11012.discord.gg +full:japan11013.discord.gg +full:japan11014.discord.gg +full:japan11015.discord.gg +full:japan11016.discord.gg +full:japan11017.discord.gg +full:japan11018.discord.gg +full:japan11019.discord.gg +full:japan11020.discord.gg +full:japan11021.discord.gg +full:japan11022.discord.gg +full:japan11023.discord.gg +full:japan11024.discord.gg +full:japan11025.discord.gg +full:japan11026.discord.gg +full:japan11027.discord.gg +full:japan11028.discord.gg +full:japan11029.discord.gg +full:japan11030.discord.gg +full:japan11031.discord.gg +full:japan11032.discord.gg +full:japan11033.discord.gg +full:japan11034.discord.gg +full:japan11035.discord.gg +full:japan11036.discord.gg +full:japan11037.discord.gg +full:japan11038.discord.gg +full:japan11039.discord.gg +full:japan11040.discord.gg +full:japan11041.discord.gg +full:japan11042.discord.gg +full:japan11043.discord.gg +full:japan11044.discord.gg +full:japan11045.discord.gg +full:japan11046.discord.gg +full:japan11047.discord.gg +full:japan11048.discord.gg +full:japan11049.discord.gg +full:japan11050.discord.gg +full:japan11051.discord.gg +full:japan11052.discord.gg +full:japan11053.discord.gg +full:japan11054.discord.gg +full:japan11055.discord.gg +full:japan11056.discord.gg +full:japan11057.discord.gg +full:japan11058.discord.gg +full:japan11059.discord.gg +full:japan11060.discord.gg +full:japan11061.discord.gg +full:japan11062.discord.gg +full:japan11063.discord.gg +full:japan11064.discord.gg +full:japan11065.discord.gg +full:japan11066.discord.gg +full:japan11067.discord.gg +full:japan11068.discord.gg +full:japan11069.discord.gg +full:japan11070.discord.gg +full:japan11071.discord.gg +full:japan11072.discord.gg +full:japan11073.discord.gg +full:japan11074.discord.gg +full:japan11075.discord.gg +full:japan11076.discord.gg +full:japan11077.discord.gg +full:japan11078.discord.gg +full:japan11079.discord.gg +full:japan1108.discord.gg +full:japan11080.discord.gg +full:japan11081.discord.gg +full:japan11082.discord.gg +full:japan11083.discord.gg +full:japan11084.discord.gg +full:japan11085.discord.gg +full:japan11086.discord.gg +full:japan11087.discord.gg +full:japan11088.discord.gg +full:japan11089.discord.gg +full:japan11090.discord.gg +full:japan11091.discord.gg +full:japan11092.discord.gg +full:japan11093.discord.gg +full:japan11094.discord.gg +full:japan11095.discord.gg +full:japan11096.discord.gg +full:japan11097.discord.gg +full:japan11098.discord.gg +full:japan11099.discord.gg +full:japan11100.discord.gg +full:japan11101.discord.gg +full:japan11102.discord.gg +full:japan11103.discord.gg +full:japan11104.discord.gg +full:japan11105.discord.gg +full:japan11106.discord.gg +full:japan11107.discord.gg +full:japan11108.discord.gg +full:japan11109.discord.gg +full:japan11110.discord.gg +full:japan11111.discord.gg +full:japan11112.discord.gg +full:japan11113.discord.gg +full:japan11114.discord.gg +full:japan11115.discord.gg +full:japan11116.discord.gg +full:japan11117.discord.gg +full:japan11118.discord.gg +full:japan11119.discord.gg +full:japan11120.discord.gg +full:japan11121.discord.gg +full:japan1120.discord.gg +full:japan1125.discord.gg +full:japan1128.discord.gg +full:japan1135.discord.gg +full:japan1192.discord.gg +full:japan1214.discord.gg +full:japan1269.discord.gg +full:japan1346.discord.gg +full:japan1383.discord.gg +full:japan1395.discord.gg +full:japan1408.discord.gg +full:japan1456.discord.gg +full:japan1477.discord.gg +full:japan1514.discord.gg +full:japan1557.discord.gg +full:japan161.discord.gg +full:japan163.discord.gg +full:japan1637.discord.gg +full:japan1661.discord.gg +full:japan1692.discord.gg +full:japan1736.discord.gg +full:japan1828.discord.gg +full:japan1906.discord.gg +full:japan197.discord.gg +full:japan2042.discord.gg +full:japan2049.discord.gg +full:japan2057.discord.gg +full:japan2069.discord.gg +full:japan2141.discord.gg +full:japan2158.discord.gg +full:japan2174.discord.gg +full:japan2184.discord.gg +full:japan2215.discord.gg +full:japan2253.discord.gg +full:japan2254.discord.gg +full:japan2307.discord.gg +full:japan2351.discord.gg +full:japan2362.discord.gg +full:japan2363.discord.gg +full:japan2392.discord.gg +full:japan2394.discord.gg +full:japan2397.discord.gg +full:japan245.discord.gg +full:japan2477.discord.gg +full:japan2493.discord.gg +full:japan2510.discord.gg +full:japan2531.discord.gg +full:japan2551.discord.gg +full:japan2553.discord.gg +full:japan2626.discord.gg +full:japan2729.discord.gg +full:japan2825.discord.gg +full:japan2873.discord.gg +full:japan2894.discord.gg +full:japan2930.discord.gg +full:japan2954.discord.gg +full:japan2958.discord.gg +full:japan3051.discord.gg +full:japan3094.discord.gg +full:japan3138.discord.gg +full:japan3153.discord.gg +full:japan3166.discord.gg +full:japan3174.discord.gg +full:japan3193.discord.gg +full:japan3198.discord.gg +full:japan3231.discord.gg +full:japan3259.discord.gg +full:japan3456.discord.gg +full:japan3460.discord.gg +full:japan3512.discord.gg +full:japan3529.discord.gg +full:japan3540.discord.gg +full:japan3653.discord.gg +full:japan3656.discord.gg +full:japan3746.discord.gg +full:japan3821.discord.gg +full:japan3823.discord.gg +full:japan3856.discord.gg +full:japan3859.discord.gg +full:japan3903.discord.gg +full:japan3979.discord.gg +full:japan4059.discord.gg +full:japan4087.discord.gg +full:japan410.discord.gg +full:japan4109.discord.gg +full:japan4199.discord.gg +full:japan4257.discord.gg +full:japan4458.discord.gg +full:japan446.discord.gg +full:japan4479.discord.gg +full:japan4574.discord.gg +full:japan4591.discord.gg +full:japan4597.discord.gg +full:japan4655.discord.gg +full:japan4677.discord.gg +full:japan4702.discord.gg +full:japan4706.discord.gg +full:japan4765.discord.gg +full:japan4800.discord.gg +full:japan4809.discord.gg +full:japan4819.discord.gg +full:japan4846.discord.gg +full:japan4908.discord.gg +full:japan4926.discord.gg +full:japan4937.discord.gg +full:japan4969.discord.gg +full:japan4998.discord.gg +full:japan5048.discord.gg +full:japan5123.discord.gg +full:japan5164.discord.gg +full:japan5308.discord.gg +full:japan5336.discord.gg +full:japan5338.discord.gg +full:japan5392.discord.gg +full:japan5484.discord.gg +full:japan5547.discord.gg +full:japan5548.discord.gg +full:japan5607.discord.gg +full:japan5616.discord.gg +full:japan5668.discord.gg +full:japan5693.discord.gg +full:japan5711.discord.gg +full:japan5723.discord.gg +full:japan5726.discord.gg +full:japan5741.discord.gg +full:japan5787.discord.gg +full:japan5802.discord.gg +full:japan5883.discord.gg +full:japan5884.discord.gg +full:japan5898.discord.gg +full:japan5903.discord.gg +full:japan5923.discord.gg +full:japan594.discord.gg +full:japan6002.discord.gg +full:japan6029.discord.gg +full:japan6089.discord.gg +full:japan6129.discord.gg +full:japan6139.discord.gg +full:japan6249.discord.gg +full:japan6298.discord.gg +full:japan6299.discord.gg +full:japan6448.discord.gg +full:japan6453.discord.gg +full:japan6454.discord.gg +full:japan6459.discord.gg +full:japan6476.discord.gg +full:japan6500.discord.gg +full:japan6545.discord.gg +full:japan6571.discord.gg +full:japan6584.discord.gg +full:japan660.discord.gg +full:japan6640.discord.gg +full:japan6658.discord.gg +full:japan6701.discord.gg +full:japan675.discord.gg +full:japan6885.discord.gg +full:japan690.discord.gg +full:japan7013.discord.gg +full:japan7036.discord.gg +full:japan7041.discord.gg +full:japan7066.discord.gg +full:japan7069.discord.gg +full:japan7126.discord.gg +full:japan7161.discord.gg +full:japan7205.discord.gg +full:japan7258.discord.gg +full:japan7374.discord.gg +full:japan742.discord.gg +full:japan747.discord.gg +full:japan756.discord.gg +full:japan7562.discord.gg +full:japan7571.discord.gg +full:japan758.discord.gg +full:japan7585.discord.gg +full:japan7589.discord.gg +full:japan7673.discord.gg +full:japan7678.discord.gg +full:japan7688.discord.gg +full:japan7713.discord.gg +full:japan775.discord.gg +full:japan7813.discord.gg +full:japan7824.discord.gg +full:japan787.discord.gg +full:japan7890.discord.gg +full:japan7898.discord.gg +full:japan7945.discord.gg +full:japan7953.discord.gg +full:japan7993.discord.gg +full:japan7999.discord.gg +full:japan80.discord.gg +full:japan8065.discord.gg +full:japan8137.discord.gg +full:japan8168.discord.gg +full:japan8254.discord.gg +full:japan8259.discord.gg +full:japan8296.discord.gg +full:japan8321.discord.gg +full:japan8440.discord.gg +full:japan8446.discord.gg +full:japan8576.discord.gg +full:japan8586.discord.gg +full:japan8604.discord.gg +full:japan8629.discord.gg +full:japan8670.discord.gg +full:japan8687.discord.gg +full:japan8704.discord.gg +full:japan8709.discord.gg +full:japan8718.discord.gg +full:japan873.discord.gg +full:japan8763.discord.gg +full:japan8821.discord.gg +full:japan8894.discord.gg +full:japan892.discord.gg +full:japan8946.discord.gg +full:japan9052.discord.gg +full:japan9065.discord.gg +full:japan9101.discord.gg +full:japan9132.discord.gg +full:japan9235.discord.gg +full:japan9239.discord.gg +full:japan9326.discord.gg +full:japan9328.discord.gg +full:japan9368.discord.gg +full:japan9379.discord.gg +full:japan9415.discord.gg +full:japan9555.discord.gg +full:japan9561.discord.gg +full:japan9565.discord.gg +full:japan9566.discord.gg +full:japan9577.discord.gg +full:japan9661.discord.gg +full:japan9672.discord.gg +full:japan971.discord.gg +full:japan9722.discord.gg +full:japan9723.discord.gg +full:japan9734.discord.gg +full:japan9878.discord.gg +full:japan9887.discord.gg +full:japan9931.discord.gg +full:japan9932.discord.gg +full:japan9946.discord.gg +full:madrid10000.discord.gg +full:madrid10001.discord.gg +full:madrid10002.discord.gg +full:madrid10003.discord.gg +full:madrid10004.discord.gg +full:madrid10005.discord.gg +full:madrid10006.discord.gg +full:madrid10007.discord.gg +full:madrid10008.discord.gg +full:madrid10009.discord.gg +full:madrid10010.discord.gg +full:madrid10011.discord.gg +full:madrid10012.discord.gg +full:madrid10013.discord.gg +full:madrid10014.discord.gg +full:madrid10015.discord.gg +full:madrid10016.discord.gg +full:madrid10017.discord.gg +full:madrid10018.discord.gg +full:madrid10019.discord.gg +full:madrid10020.discord.gg +full:madrid10021.discord.gg +full:madrid10022.discord.gg +full:madrid10023.discord.gg +full:madrid10024.discord.gg +full:madrid10025.discord.gg +full:madrid10026.discord.gg +full:madrid10027.discord.gg +full:madrid10028.discord.gg +full:madrid10029.discord.gg +full:madrid10030.discord.gg +full:madrid10031.discord.gg +full:madrid10032.discord.gg +full:madrid10033.discord.gg +full:madrid10034.discord.gg +full:madrid10035.discord.gg +full:madrid10036.discord.gg +full:madrid10037.discord.gg +full:madrid10038.discord.gg +full:madrid10039.discord.gg +full:madrid10040.discord.gg +full:madrid10041.discord.gg +full:madrid10042.discord.gg +full:madrid10043.discord.gg +full:madrid10044.discord.gg +full:madrid10045.discord.gg +full:madrid10046.discord.gg +full:madrid10047.discord.gg +full:madrid10048.discord.gg +full:madrid10049.discord.gg +full:madrid10050.discord.gg +full:madrid10051.discord.gg +full:madrid10052.discord.gg +full:madrid10053.discord.gg +full:madrid10054.discord.gg +full:madrid10055.discord.gg +full:madrid10056.discord.gg +full:madrid10057.discord.gg +full:madrid10058.discord.gg +full:madrid10059.discord.gg +full:madrid10060.discord.gg +full:madrid10061.discord.gg +full:madrid10062.discord.gg +full:madrid1038.discord.gg +full:madrid1149.discord.gg +full:madrid116.discord.gg +full:madrid1228.discord.gg +full:madrid1246.discord.gg +full:madrid1449.discord.gg +full:madrid15.discord.gg +full:madrid1857.discord.gg +full:madrid1927.discord.gg +full:madrid1944.discord.gg +full:madrid1975.discord.gg +full:madrid2351.discord.gg +full:madrid2843.discord.gg +full:madrid3344.discord.gg +full:madrid3391.discord.gg +full:madrid3753.discord.gg +full:madrid3930.discord.gg +full:madrid4296.discord.gg +full:madrid4770.discord.gg +full:madrid4790.discord.gg +full:madrid53.discord.gg +full:madrid5488.discord.gg +full:madrid6250.discord.gg +full:madrid631.discord.gg +full:madrid6516.discord.gg +full:madrid657.discord.gg +full:madrid719.discord.gg +full:madrid724.discord.gg +full:madrid7256.discord.gg +full:madrid743.discord.gg +full:madrid7764.discord.gg +full:madrid7930.discord.gg +full:madrid8367.discord.gg +full:madrid8671.discord.gg +full:madrid8807.discord.gg +full:madrid9168.discord.gg +full:madrid9635.discord.gg +full:madrid9811.discord.gg +full:madrid9918.discord.gg +full:media.discordapp.net +full:milan10000.discord.gg +full:milan10001.discord.gg +full:milan10002.discord.gg +full:milan10003.discord.gg +full:milan10004.discord.gg +full:milan10005.discord.gg +full:milan10006.discord.gg +full:milan10007.discord.gg +full:milan10008.discord.gg +full:milan10009.discord.gg +full:milan10010.discord.gg +full:milan10011.discord.gg +full:milan10012.discord.gg +full:milan10013.discord.gg +full:milan10014.discord.gg +full:milan10015.discord.gg +full:milan10016.discord.gg +full:milan10017.discord.gg +full:milan10018.discord.gg +full:milan10019.discord.gg +full:milan10020.discord.gg +full:milan10021.discord.gg +full:milan10022.discord.gg +full:milan10023.discord.gg +full:milan10024.discord.gg +full:milan10025.discord.gg +full:milan10026.discord.gg +full:milan10027.discord.gg +full:milan10028.discord.gg +full:milan10029.discord.gg +full:milan10030.discord.gg +full:milan10031.discord.gg +full:milan10032.discord.gg +full:milan10033.discord.gg +full:milan10034.discord.gg +full:milan10035.discord.gg +full:milan10036.discord.gg +full:milan10037.discord.gg +full:milan10038.discord.gg +full:milan10039.discord.gg +full:milan10040.discord.gg +full:milan10041.discord.gg +full:milan10042.discord.gg +full:milan10043.discord.gg +full:milan10044.discord.gg +full:milan10045.discord.gg +full:milan10046.discord.gg +full:milan1069.discord.gg +full:milan1639.discord.gg +full:milan1643.discord.gg +full:milan2028.discord.gg +full:milan2079.discord.gg +full:milan2344.discord.gg +full:milan2380.discord.gg +full:milan2446.discord.gg +full:milan263.discord.gg +full:milan2903.discord.gg +full:milan2954.discord.gg +full:milan3333.discord.gg +full:milan3397.discord.gg +full:milan3411.discord.gg +full:milan3655.discord.gg +full:milan3752.discord.gg +full:milan3960.discord.gg +full:milan4146.discord.gg +full:milan4479.discord.gg +full:milan4539.discord.gg +full:milan4551.discord.gg +full:milan4577.discord.gg +full:milan4945.discord.gg +full:milan5408.discord.gg +full:milan5886.discord.gg +full:milan5936.discord.gg +full:milan5970.discord.gg +full:milan6222.discord.gg +full:milan6292.discord.gg +full:milan6477.discord.gg +full:milan6851.discord.gg +full:milan6945.discord.gg +full:milan7551.discord.gg +full:milan8273.discord.gg +full:milan8328.discord.gg +full:milan8733.discord.gg +full:milan8905.discord.gg +full:milan8997.discord.gg +full:milan9192.discord.gg +full:milan9345.discord.gg +full:milan9516.discord.gg +full:milan9894.discord.gg +full:milan9948.discord.gg +full:newark114.discord.gg +full:newark1154.discord.gg +full:newark1172.discord.gg +full:newark1220.discord.gg +full:newark1244.discord.gg +full:newark1273.discord.gg +full:newark1291.discord.gg +full:newark1297.discord.gg +full:newark1341.discord.gg +full:newark1364.discord.gg +full:newark1418.discord.gg +full:newark1456.discord.gg +full:newark1464.discord.gg +full:newark1508.discord.gg +full:newark1509.discord.gg +full:newark153.discord.gg +full:newark1556.discord.gg +full:newark1566.discord.gg +full:newark1599.discord.gg +full:newark1612.discord.gg +full:newark1637.discord.gg +full:newark1657.discord.gg +full:newark1694.discord.gg +full:newark1771.discord.gg +full:newark1782.discord.gg +full:newark1803.discord.gg +full:newark1838.discord.gg +full:newark1934.discord.gg +full:newark1949.discord.gg +full:newark2049.discord.gg +full:newark2061.discord.gg +full:newark2085.discord.gg +full:newark2112.discord.gg +full:newark2195.discord.gg +full:newark225.discord.gg +full:newark2266.discord.gg +full:newark2270.discord.gg +full:newark2314.discord.gg +full:newark2328.discord.gg +full:newark2354.discord.gg +full:newark248.discord.gg +full:newark2655.discord.gg +full:newark269.discord.gg +full:newark2780.discord.gg +full:newark2829.discord.gg +full:newark294.discord.gg +full:newark2949.discord.gg +full:newark2988.discord.gg +full:newark3003.discord.gg +full:newark3121.discord.gg +full:newark3218.discord.gg +full:newark326.discord.gg +full:newark3400.discord.gg +full:newark3419.discord.gg +full:newark342.discord.gg +full:newark343.discord.gg +full:newark3481.discord.gg +full:newark3500.discord.gg +full:newark352.discord.gg +full:newark3555.discord.gg +full:newark3570.discord.gg +full:newark3571.discord.gg +full:newark3709.discord.gg +full:newark3721.discord.gg +full:newark3822.discord.gg +full:newark3863.discord.gg +full:newark3867.discord.gg +full:newark3888.discord.gg +full:newark3894.discord.gg +full:newark398.discord.gg +full:newark4130.discord.gg +full:newark4159.discord.gg +full:newark4203.discord.gg +full:newark4244.discord.gg +full:newark429.discord.gg +full:newark4298.discord.gg +full:newark435.discord.gg +full:newark4362.discord.gg +full:newark4388.discord.gg +full:newark4400.discord.gg +full:newark4404.discord.gg +full:newark4448.discord.gg +full:newark4539.discord.gg +full:newark4556.discord.gg +full:newark4569.discord.gg +full:newark4587.discord.gg +full:newark4670.discord.gg +full:newark4715.discord.gg +full:newark4788.discord.gg +full:newark4830.discord.gg +full:newark4832.discord.gg +full:newark4833.discord.gg +full:newark4900.discord.gg +full:newark4986.discord.gg +full:newark4997.discord.gg +full:newark5005.discord.gg +full:newark5036.discord.gg +full:newark5109.discord.gg +full:newark514.discord.gg +full:newark5207.discord.gg +full:newark5281.discord.gg +full:newark5360.discord.gg +full:newark5372.discord.gg +full:newark5378.discord.gg +full:newark5410.discord.gg +full:newark5441.discord.gg +full:newark5574.discord.gg +full:newark5599.discord.gg +full:newark5620.discord.gg +full:newark5669.discord.gg +full:newark5728.discord.gg +full:newark5741.discord.gg +full:newark5790.discord.gg +full:newark59.discord.gg +full:newark5917.discord.gg +full:newark5984.discord.gg +full:newark60.discord.gg +full:newark616.discord.gg +full:newark6195.discord.gg +full:newark6206.discord.gg +full:newark6245.discord.gg +full:newark6247.discord.gg +full:newark6340.discord.gg +full:newark6361.discord.gg +full:newark6452.discord.gg +full:newark6468.discord.gg +full:newark6536.discord.gg +full:newark6610.discord.gg +full:newark6612.discord.gg +full:newark662.discord.gg +full:newark6656.discord.gg +full:newark6819.discord.gg +full:newark6823.discord.gg +full:newark6882.discord.gg +full:newark6915.discord.gg +full:newark6959.discord.gg +full:newark6974.discord.gg +full:newark700.discord.gg +full:newark7033.discord.gg +full:newark7034.discord.gg +full:newark7076.discord.gg +full:newark71.discord.gg +full:newark7100.discord.gg +full:newark7137.discord.gg +full:newark7322.discord.gg +full:newark735.discord.gg +full:newark7377.discord.gg +full:newark7378.discord.gg +full:newark7382.discord.gg +full:newark7386.discord.gg +full:newark7395.discord.gg +full:newark7401.discord.gg +full:newark7463.discord.gg +full:newark7495.discord.gg +full:newark750.discord.gg +full:newark7607.discord.gg +full:newark7648.discord.gg +full:newark7755.discord.gg +full:newark7781.discord.gg +full:newark7831.discord.gg +full:newark7837.discord.gg +full:newark7855.discord.gg +full:newark7975.discord.gg +full:newark7987.discord.gg +full:newark8024.discord.gg +full:newark804.discord.gg +full:newark8227.discord.gg +full:newark8350.discord.gg +full:newark8367.discord.gg +full:newark8392.discord.gg +full:newark842.discord.gg +full:newark8481.discord.gg +full:newark8536.discord.gg +full:newark8541.discord.gg +full:newark855.discord.gg +full:newark8583.discord.gg +full:newark8676.discord.gg +full:newark868.discord.gg +full:newark8775.discord.gg +full:newark8809.discord.gg +full:newark8825.discord.gg +full:newark8829.discord.gg +full:newark888.discord.gg +full:newark889.discord.gg +full:newark8916.discord.gg +full:newark8918.discord.gg +full:newark8981.discord.gg +full:newark9071.discord.gg +full:newark9074.discord.gg +full:newark9075.discord.gg +full:newark9126.discord.gg +full:newark9212.discord.gg +full:newark9236.discord.gg +full:newark9239.discord.gg +full:newark9288.discord.gg +full:newark9293.discord.gg +full:newark9299.discord.gg +full:newark9304.discord.gg +full:newark9453.discord.gg +full:newark9535.discord.gg +full:newark9548.discord.gg +full:newark9675.discord.gg +full:newark9746.discord.gg +full:newark9822.discord.gg +full:newark9858.discord.gg +full:newark9866.discord.gg +full:newark9960.discord.gg +full:newark9985.discord.gg +full:ptb.discord.com +full:rotterdam1032.discord.gg +full:rotterdam1060.discord.gg +full:rotterdam1069.discord.gg +full:rotterdam11000.discord.gg +full:rotterdam11001.discord.gg +full:rotterdam11002.discord.gg +full:rotterdam11003.discord.gg +full:rotterdam11004.discord.gg +full:rotterdam11005.discord.gg +full:rotterdam11006.discord.gg +full:rotterdam11007.discord.gg +full:rotterdam11008.discord.gg +full:rotterdam11009.discord.gg +full:rotterdam11010.discord.gg +full:rotterdam11011.discord.gg +full:rotterdam11012.discord.gg +full:rotterdam11013.discord.gg +full:rotterdam11014.discord.gg +full:rotterdam11015.discord.gg +full:rotterdam11016.discord.gg +full:rotterdam11017.discord.gg +full:rotterdam11018.discord.gg +full:rotterdam11019.discord.gg +full:rotterdam11020.discord.gg +full:rotterdam11021.discord.gg +full:rotterdam11022.discord.gg +full:rotterdam11023.discord.gg +full:rotterdam11024.discord.gg +full:rotterdam11025.discord.gg +full:rotterdam11026.discord.gg +full:rotterdam11027.discord.gg +full:rotterdam11028.discord.gg +full:rotterdam11029.discord.gg +full:rotterdam11030.discord.gg +full:rotterdam11031.discord.gg +full:rotterdam11032.discord.gg +full:rotterdam11033.discord.gg +full:rotterdam11034.discord.gg +full:rotterdam11035.discord.gg +full:rotterdam11036.discord.gg +full:rotterdam11037.discord.gg +full:rotterdam11038.discord.gg +full:rotterdam11039.discord.gg +full:rotterdam11040.discord.gg +full:rotterdam11041.discord.gg +full:rotterdam11042.discord.gg +full:rotterdam11043.discord.gg +full:rotterdam11044.discord.gg +full:rotterdam11045.discord.gg +full:rotterdam11046.discord.gg +full:rotterdam11047.discord.gg +full:rotterdam11048.discord.gg +full:rotterdam11049.discord.gg +full:rotterdam11050.discord.gg +full:rotterdam11051.discord.gg +full:rotterdam11052.discord.gg +full:rotterdam11053.discord.gg +full:rotterdam11054.discord.gg +full:rotterdam11055.discord.gg +full:rotterdam11056.discord.gg +full:rotterdam11057.discord.gg +full:rotterdam11058.discord.gg +full:rotterdam11059.discord.gg +full:rotterdam11060.discord.gg +full:rotterdam11061.discord.gg +full:rotterdam11062.discord.gg +full:rotterdam11063.discord.gg +full:rotterdam11064.discord.gg +full:rotterdam11065.discord.gg +full:rotterdam11066.discord.gg +full:rotterdam11067.discord.gg +full:rotterdam11068.discord.gg +full:rotterdam11069.discord.gg +full:rotterdam11070.discord.gg +full:rotterdam11071.discord.gg +full:rotterdam11072.discord.gg +full:rotterdam11073.discord.gg +full:rotterdam11074.discord.gg +full:rotterdam11075.discord.gg +full:rotterdam11076.discord.gg +full:rotterdam11077.discord.gg +full:rotterdam11078.discord.gg +full:rotterdam11079.discord.gg +full:rotterdam11080.discord.gg +full:rotterdam11081.discord.gg +full:rotterdam11082.discord.gg +full:rotterdam11083.discord.gg +full:rotterdam11084.discord.gg +full:rotterdam11085.discord.gg +full:rotterdam11086.discord.gg +full:rotterdam11087.discord.gg +full:rotterdam11088.discord.gg +full:rotterdam11089.discord.gg +full:rotterdam11090.discord.gg +full:rotterdam11091.discord.gg +full:rotterdam11092.discord.gg +full:rotterdam11093.discord.gg +full:rotterdam11094.discord.gg +full:rotterdam11095.discord.gg +full:rotterdam11096.discord.gg +full:rotterdam11097.discord.gg +full:rotterdam11098.discord.gg +full:rotterdam11099.discord.gg +full:rotterdam11100.discord.gg +full:rotterdam11101.discord.gg +full:rotterdam11102.discord.gg +full:rotterdam11103.discord.gg +full:rotterdam11104.discord.gg +full:rotterdam11105.discord.gg +full:rotterdam11106.discord.gg +full:rotterdam11107.discord.gg +full:rotterdam11108.discord.gg +full:rotterdam11109.discord.gg +full:rotterdam11110.discord.gg +full:rotterdam11111.discord.gg +full:rotterdam11112.discord.gg +full:rotterdam11113.discord.gg +full:rotterdam11114.discord.gg +full:rotterdam11115.discord.gg +full:rotterdam11116.discord.gg +full:rotterdam11117.discord.gg +full:rotterdam11118.discord.gg +full:rotterdam11119.discord.gg +full:rotterdam11120.discord.gg +full:rotterdam11121.discord.gg +full:rotterdam11122.discord.gg +full:rotterdam11123.discord.gg +full:rotterdam11124.discord.gg +full:rotterdam11125.discord.gg +full:rotterdam11126.discord.gg +full:rotterdam11127.discord.gg +full:rotterdam11128.discord.gg +full:rotterdam11129.discord.gg +full:rotterdam11130.discord.gg +full:rotterdam11131.discord.gg +full:rotterdam11132.discord.gg +full:rotterdam11133.discord.gg +full:rotterdam11134.discord.gg +full:rotterdam11135.discord.gg +full:rotterdam11136.discord.gg +full:rotterdam11137.discord.gg +full:rotterdam11138.discord.gg +full:rotterdam11139.discord.gg +full:rotterdam11140.discord.gg +full:rotterdam11141.discord.gg +full:rotterdam11142.discord.gg +full:rotterdam11143.discord.gg +full:rotterdam11144.discord.gg +full:rotterdam11145.discord.gg +full:rotterdam11146.discord.gg +full:rotterdam11147.discord.gg +full:rotterdam11148.discord.gg +full:rotterdam11149.discord.gg +full:rotterdam11150.discord.gg +full:rotterdam11151.discord.gg +full:rotterdam11152.discord.gg +full:rotterdam11153.discord.gg +full:rotterdam11154.discord.gg +full:rotterdam11155.discord.gg +full:rotterdam11156.discord.gg +full:rotterdam11157.discord.gg +full:rotterdam11158.discord.gg +full:rotterdam11159.discord.gg +full:rotterdam11160.discord.gg +full:rotterdam11161.discord.gg +full:rotterdam11162.discord.gg +full:rotterdam11163.discord.gg +full:rotterdam11164.discord.gg +full:rotterdam11165.discord.gg +full:rotterdam11166.discord.gg +full:rotterdam11167.discord.gg +full:rotterdam11168.discord.gg +full:rotterdam11169.discord.gg +full:rotterdam11170.discord.gg +full:rotterdam11171.discord.gg +full:rotterdam11172.discord.gg +full:rotterdam11173.discord.gg +full:rotterdam11174.discord.gg +full:rotterdam11175.discord.gg +full:rotterdam11176.discord.gg +full:rotterdam11177.discord.gg +full:rotterdam11178.discord.gg +full:rotterdam11179.discord.gg +full:rotterdam11180.discord.gg +full:rotterdam11181.discord.gg +full:rotterdam11182.discord.gg +full:rotterdam114.discord.gg +full:rotterdam1160.discord.gg +full:rotterdam1192.discord.gg +full:rotterdam1197.discord.gg +full:rotterdam1221.discord.gg +full:rotterdam1225.discord.gg +full:rotterdam1334.discord.gg +full:rotterdam1375.discord.gg +full:rotterdam1402.discord.gg +full:rotterdam1420.discord.gg +full:rotterdam1474.discord.gg +full:rotterdam1573.discord.gg +full:rotterdam1611.discord.gg +full:rotterdam1669.discord.gg +full:rotterdam1715.discord.gg +full:rotterdam1716.discord.gg +full:rotterdam1794.discord.gg +full:rotterdam1902.discord.gg +full:rotterdam1981.discord.gg +full:rotterdam2000.discord.gg +full:rotterdam2002.discord.gg +full:rotterdam2054.discord.gg +full:rotterdam2116.discord.gg +full:rotterdam2119.discord.gg +full:rotterdam2258.discord.gg +full:rotterdam2332.discord.gg +full:rotterdam2414.discord.gg +full:rotterdam249.discord.gg +full:rotterdam2560.discord.gg +full:rotterdam2584.discord.gg +full:rotterdam2657.discord.gg +full:rotterdam2708.discord.gg +full:rotterdam2710.discord.gg +full:rotterdam2744.discord.gg +full:rotterdam2775.discord.gg +full:rotterdam2937.discord.gg +full:rotterdam3046.discord.gg +full:rotterdam3065.discord.gg +full:rotterdam3175.discord.gg +full:rotterdam3384.discord.gg +full:rotterdam341.discord.gg +full:rotterdam3468.discord.gg +full:rotterdam3544.discord.gg +full:rotterdam3563.discord.gg +full:rotterdam3623.discord.gg +full:rotterdam3690.discord.gg +full:rotterdam3775.discord.gg +full:rotterdam3791.discord.gg +full:rotterdam3803.discord.gg +full:rotterdam3860.discord.gg +full:rotterdam3955.discord.gg +full:rotterdam4010.discord.gg +full:rotterdam4022.discord.gg +full:rotterdam4030.discord.gg +full:rotterdam4073.discord.gg +full:rotterdam4193.discord.gg +full:rotterdam4251.discord.gg +full:rotterdam4331.discord.gg +full:rotterdam4358.discord.gg +full:rotterdam4371.discord.gg +full:rotterdam4421.discord.gg +full:rotterdam4461.discord.gg +full:rotterdam4476.discord.gg +full:rotterdam4617.discord.gg +full:rotterdam464.discord.gg +full:rotterdam4652.discord.gg +full:rotterdam4739.discord.gg +full:rotterdam4790.discord.gg +full:rotterdam4795.discord.gg +full:rotterdam4807.discord.gg +full:rotterdam4833.discord.gg +full:rotterdam4858.discord.gg +full:rotterdam4868.discord.gg +full:rotterdam5034.discord.gg +full:rotterdam5042.discord.gg +full:rotterdam5175.discord.gg +full:rotterdam5214.discord.gg +full:rotterdam5299.discord.gg +full:rotterdam5328.discord.gg +full:rotterdam5342.discord.gg +full:rotterdam5403.discord.gg +full:rotterdam5466.discord.gg +full:rotterdam550.discord.gg +full:rotterdam5533.discord.gg +full:rotterdam5589.discord.gg +full:rotterdam5667.discord.gg +full:rotterdam5733.discord.gg +full:rotterdam5737.discord.gg +full:rotterdam5739.discord.gg +full:rotterdam5745.discord.gg +full:rotterdam5819.discord.gg +full:rotterdam5873.discord.gg +full:rotterdam5957.discord.gg +full:rotterdam5999.discord.gg +full:rotterdam605.discord.gg +full:rotterdam6224.discord.gg +full:rotterdam6291.discord.gg +full:rotterdam6392.discord.gg +full:rotterdam6486.discord.gg +full:rotterdam6511.discord.gg +full:rotterdam6563.discord.gg +full:rotterdam6583.discord.gg +full:rotterdam6630.discord.gg +full:rotterdam6661.discord.gg +full:rotterdam6685.discord.gg +full:rotterdam6706.discord.gg +full:rotterdam674.discord.gg +full:rotterdam6958.discord.gg +full:rotterdam6996.discord.gg +full:rotterdam7016.discord.gg +full:rotterdam7100.discord.gg +full:rotterdam7166.discord.gg +full:rotterdam7172.discord.gg +full:rotterdam7210.discord.gg +full:rotterdam7218.discord.gg +full:rotterdam7223.discord.gg +full:rotterdam724.discord.gg +full:rotterdam7386.discord.gg +full:rotterdam7427.discord.gg +full:rotterdam7428.discord.gg +full:rotterdam7437.discord.gg +full:rotterdam7439.discord.gg +full:rotterdam745.discord.gg +full:rotterdam7452.discord.gg +full:rotterdam7602.discord.gg +full:rotterdam7687.discord.gg +full:rotterdam7720.discord.gg +full:rotterdam7844.discord.gg +full:rotterdam7884.discord.gg +full:rotterdam7897.discord.gg +full:rotterdam7898.discord.gg +full:rotterdam7930.discord.gg +full:rotterdam7941.discord.gg +full:rotterdam7992.discord.gg +full:rotterdam8001.discord.gg +full:rotterdam8003.discord.gg +full:rotterdam8065.discord.gg +full:rotterdam8140.discord.gg +full:rotterdam8163.discord.gg +full:rotterdam8278.discord.gg +full:rotterdam8286.discord.gg +full:rotterdam8295.discord.gg +full:rotterdam83.discord.gg +full:rotterdam8323.discord.gg +full:rotterdam8340.discord.gg +full:rotterdam8398.discord.gg +full:rotterdam843.discord.gg +full:rotterdam8489.discord.gg +full:rotterdam8523.discord.gg +full:rotterdam8527.discord.gg +full:rotterdam8540.discord.gg +full:rotterdam8575.discord.gg +full:rotterdam8605.discord.gg +full:rotterdam8609.discord.gg +full:rotterdam8620.discord.gg +full:rotterdam8675.discord.gg +full:rotterdam8738.discord.gg +full:rotterdam8897.discord.gg +full:rotterdam8993.discord.gg +full:rotterdam9022.discord.gg +full:rotterdam9035.discord.gg +full:rotterdam9083.discord.gg +full:rotterdam9095.discord.gg +full:rotterdam9105.discord.gg +full:rotterdam9127.discord.gg +full:rotterdam9255.discord.gg +full:rotterdam9319.discord.gg +full:rotterdam9330.discord.gg +full:rotterdam9347.discord.gg +full:rotterdam9387.discord.gg +full:rotterdam9525.discord.gg +full:rotterdam9550.discord.gg +full:rotterdam9551.discord.gg +full:rotterdam9578.discord.gg +full:rotterdam9694.discord.gg +full:rotterdam9731.discord.gg +full:rotterdam9794.discord.gg +full:rotterdam9863.discord.gg +full:rotterdam9872.discord.gg +full:rotterdam9995.discord.gg +full:russia1109.discord.gg +full:russia1290.discord.gg +full:russia1298.discord.gg +full:russia1302.discord.gg +full:russia1332.discord.gg +full:russia1411.discord.gg +full:russia1461.discord.gg +full:russia1465.discord.gg +full:russia1501.discord.gg +full:russia1700.discord.gg +full:russia1719.discord.gg +full:russia1727.discord.gg +full:russia1740.discord.gg +full:russia1742.discord.gg +full:russia1838.discord.gg +full:russia1860.discord.gg +full:russia1890.discord.gg +full:russia2029.discord.gg +full:russia2188.discord.gg +full:russia2271.discord.gg +full:russia2283.discord.gg +full:russia2352.discord.gg +full:russia2368.discord.gg +full:russia2474.discord.gg +full:russia2501.discord.gg +full:russia2543.discord.gg +full:russia2554.discord.gg +full:russia2652.discord.gg +full:russia2654.discord.gg +full:russia2664.discord.gg +full:russia2686.discord.gg +full:russia2698.discord.gg +full:russia282.discord.gg +full:russia301.discord.gg +full:russia3064.discord.gg +full:russia3105.discord.gg +full:russia3117.discord.gg +full:russia3168.discord.gg +full:russia3342.discord.gg +full:russia34.discord.gg +full:russia3432.discord.gg +full:russia3500.discord.gg +full:russia3525.discord.gg +full:russia3536.discord.gg +full:russia3612.discord.gg +full:russia3618.discord.gg +full:russia3649.discord.gg +full:russia3674.discord.gg +full:russia3717.discord.gg +full:russia3755.discord.gg +full:russia3824.discord.gg +full:russia3872.discord.gg +full:russia4022.discord.gg +full:russia4031.discord.gg +full:russia4037.discord.gg +full:russia4061.discord.gg +full:russia4102.discord.gg +full:russia4142.discord.gg +full:russia4185.discord.gg +full:russia438.discord.gg +full:russia4400.discord.gg +full:russia4419.discord.gg +full:russia442.discord.gg +full:russia4448.discord.gg +full:russia4513.discord.gg +full:russia4522.discord.gg +full:russia4606.discord.gg +full:russia462.discord.gg +full:russia4656.discord.gg +full:russia4657.discord.gg +full:russia469.discord.gg +full:russia4725.discord.gg +full:russia4730.discord.gg +full:russia4842.discord.gg +full:russia4905.discord.gg +full:russia4980.discord.gg +full:russia5095.discord.gg +full:russia5097.discord.gg +full:russia515.discord.gg +full:russia5157.discord.gg +full:russia5162.discord.gg +full:russia5225.discord.gg +full:russia5286.discord.gg +full:russia5414.discord.gg +full:russia5495.discord.gg +full:russia5516.discord.gg +full:russia5544.discord.gg +full:russia5553.discord.gg +full:russia5644.discord.gg +full:russia5689.discord.gg +full:russia5733.discord.gg +full:russia5806.discord.gg +full:russia5957.discord.gg +full:russia5992.discord.gg +full:russia5995.discord.gg +full:russia6044.discord.gg +full:russia62.discord.gg +full:russia6204.discord.gg +full:russia6221.discord.gg +full:russia6237.discord.gg +full:russia6344.discord.gg +full:russia6393.discord.gg +full:russia640.discord.gg +full:russia6424.discord.gg +full:russia6443.discord.gg +full:russia6459.discord.gg +full:russia6897.discord.gg +full:russia7004.discord.gg +full:russia7007.discord.gg +full:russia7011.discord.gg +full:russia7056.discord.gg +full:russia7085.discord.gg +full:russia7099.discord.gg +full:russia720.discord.gg +full:russia7244.discord.gg +full:russia7302.discord.gg +full:russia7343.discord.gg +full:russia7492.discord.gg +full:russia7498.discord.gg +full:russia752.discord.gg +full:russia7726.discord.gg +full:russia7814.discord.gg +full:russia7914.discord.gg +full:russia7917.discord.gg +full:russia7968.discord.gg +full:russia8079.discord.gg +full:russia8095.discord.gg +full:russia8201.discord.gg +full:russia830.discord.gg +full:russia8319.discord.gg +full:russia8346.discord.gg +full:russia8393.discord.gg +full:russia845.discord.gg +full:russia847.discord.gg +full:russia8478.discord.gg +full:russia8510.discord.gg +full:russia8649.discord.gg +full:russia8693.discord.gg +full:russia8703.discord.gg +full:russia8760.discord.gg +full:russia8984.discord.gg +full:russia9060.discord.gg +full:russia9064.discord.gg +full:russia9097.discord.gg +full:russia921.discord.gg +full:russia9229.discord.gg +full:russia9253.discord.gg +full:russia9264.discord.gg +full:russia928.discord.gg +full:russia9345.discord.gg +full:russia9372.discord.gg +full:russia9395.discord.gg +full:russia9423.discord.gg +full:russia9458.discord.gg +full:russia9462.discord.gg +full:russia9482.discord.gg +full:russia9488.discord.gg +full:russia9521.discord.gg +full:russia9548.discord.gg +full:russia9602.discord.gg +full:russia9619.discord.gg +full:russia9658.discord.gg +full:russia9732.discord.gg +full:russia9733.discord.gg +full:russia9771.discord.gg +full:russia9942.discord.gg +full:santa-clara1517.discord.gg +full:santa-clara1603.discord.gg +full:santa-clara1906.discord.gg +full:santa-clara1921.discord.gg +full:santa-clara1966.discord.gg +full:santa-clara2419.discord.gg +full:santa-clara247.discord.gg +full:santa-clara248.discord.gg +full:santa-clara2632.discord.gg +full:santa-clara2636.discord.gg +full:santa-clara2975.discord.gg +full:santa-clara299.discord.gg +full:santa-clara3061.discord.gg +full:santa-clara3251.discord.gg +full:santa-clara332.discord.gg +full:santa-clara3525.discord.gg +full:santa-clara3639.discord.gg +full:santa-clara3692.discord.gg +full:santa-clara3755.discord.gg +full:santa-clara3849.discord.gg +full:santa-clara410.discord.gg +full:santa-clara4145.discord.gg +full:santa-clara4215.discord.gg +full:santa-clara436.discord.gg +full:santa-clara4454.discord.gg +full:santa-clara454.discord.gg +full:santa-clara4710.discord.gg +full:santa-clara5072.discord.gg +full:santa-clara5279.discord.gg +full:santa-clara5504.discord.gg +full:santa-clara5742.discord.gg +full:santa-clara5840.discord.gg +full:santa-clara5937.discord.gg +full:santa-clara6075.discord.gg +full:santa-clara6188.discord.gg +full:santa-clara6201.discord.gg +full:santa-clara6793.discord.gg +full:santa-clara6847.discord.gg +full:santa-clara7269.discord.gg +full:santa-clara7327.discord.gg +full:santa-clara7379.discord.gg +full:santa-clara739.discord.gg +full:santa-clara7400.discord.gg +full:santa-clara7582.discord.gg +full:santa-clara7705.discord.gg +full:santa-clara8257.discord.gg +full:santa-clara8971.discord.gg +full:santa-clara914.discord.gg +full:santa-clara9480.discord.gg +full:santa-clara984.discord.gg +full:santa-clara9905.discord.gg +full:santa-clara996.discord.gg +full:santiago10000.discord.gg +full:santiago10001.discord.gg +full:santiago10002.discord.gg +full:santiago10003.discord.gg +full:santiago10004.discord.gg +full:santiago10005.discord.gg +full:santiago10006.discord.gg +full:santiago10007.discord.gg +full:santiago10008.discord.gg +full:santiago10009.discord.gg +full:santiago10010.discord.gg +full:santiago10011.discord.gg +full:santiago10012.discord.gg +full:santiago10013.discord.gg +full:santiago10014.discord.gg +full:santiago10015.discord.gg +full:santiago10016.discord.gg +full:santiago10017.discord.gg +full:santiago10018.discord.gg +full:santiago10019.discord.gg +full:santiago10020.discord.gg +full:santiago10021.discord.gg +full:santiago10022.discord.gg +full:santiago10023.discord.gg +full:santiago10024.discord.gg +full:santiago10025.discord.gg +full:santiago10026.discord.gg +full:santiago10027.discord.gg +full:santiago10028.discord.gg +full:santiago10029.discord.gg +full:santiago10030.discord.gg +full:santiago10031.discord.gg +full:santiago10032.discord.gg +full:santiago10033.discord.gg +full:santiago10034.discord.gg +full:santiago10035.discord.gg +full:santiago10036.discord.gg +full:santiago10037.discord.gg +full:santiago10038.discord.gg +full:santiago10039.discord.gg +full:santiago10040.discord.gg +full:santiago10041.discord.gg +full:santiago10042.discord.gg +full:santiago10043.discord.gg +full:santiago10044.discord.gg +full:santiago10045.discord.gg +full:santiago10046.discord.gg +full:santiago10047.discord.gg +full:santiago10048.discord.gg +full:santiago10049.discord.gg +full:santiago10050.discord.gg +full:santiago10051.discord.gg +full:santiago1481.discord.gg +full:santiago1789.discord.gg +full:santiago2484.discord.gg +full:santiago2628.discord.gg +full:santiago277.discord.gg +full:santiago2887.discord.gg +full:santiago2999.discord.gg +full:santiago3155.discord.gg +full:santiago324.discord.gg +full:santiago3524.discord.gg +full:santiago4410.discord.gg +full:santiago518.discord.gg +full:santiago5240.discord.gg +full:santiago5759.discord.gg +full:santiago5766.discord.gg +full:santiago6083.discord.gg +full:santiago6225.discord.gg +full:santiago6287.discord.gg +full:santiago6515.discord.gg +full:santiago8523.discord.gg +full:santiago91.discord.gg +full:santiago9531.discord.gg +full:seattle1152.discord.gg +full:seattle1430.discord.gg +full:seattle1475.discord.gg +full:seattle1540.discord.gg +full:seattle164.discord.gg +full:seattle1688.discord.gg +full:seattle1786.discord.gg +full:seattle1913.discord.gg +full:seattle1955.discord.gg +full:seattle1986.discord.gg +full:seattle2004.discord.gg +full:seattle2125.discord.gg +full:seattle2275.discord.gg +full:seattle2344.discord.gg +full:seattle2519.discord.gg +full:seattle2527.discord.gg +full:seattle269.discord.gg +full:seattle2732.discord.gg +full:seattle2780.discord.gg +full:seattle2845.discord.gg +full:seattle2968.discord.gg +full:seattle3087.discord.gg +full:seattle3108.discord.gg +full:seattle3263.discord.gg +full:seattle3332.discord.gg +full:seattle3342.discord.gg +full:seattle3415.discord.gg +full:seattle3662.discord.gg +full:seattle3663.discord.gg +full:seattle3907.discord.gg +full:seattle391.discord.gg +full:seattle3973.discord.gg +full:seattle3989.discord.gg +full:seattle4330.discord.gg +full:seattle4366.discord.gg +full:seattle4650.discord.gg +full:seattle4683.discord.gg +full:seattle4923.discord.gg +full:seattle4972.discord.gg +full:seattle5189.discord.gg +full:seattle521.discord.gg +full:seattle5254.discord.gg +full:seattle5622.discord.gg +full:seattle5805.discord.gg +full:seattle5872.discord.gg +full:seattle5902.discord.gg +full:seattle5906.discord.gg +full:seattle6012.discord.gg +full:seattle6151.discord.gg +full:seattle6184.discord.gg +full:seattle6284.discord.gg +full:seattle6296.discord.gg +full:seattle6300.discord.gg +full:seattle6516.discord.gg +full:seattle6580.discord.gg +full:seattle6601.discord.gg +full:seattle6700.discord.gg +full:seattle6933.discord.gg +full:seattle6973.discord.gg +full:seattle7058.discord.gg +full:seattle7221.discord.gg +full:seattle7241.discord.gg +full:seattle7353.discord.gg +full:seattle7359.discord.gg +full:seattle7364.discord.gg +full:seattle7420.discord.gg +full:seattle7450.discord.gg +full:seattle7551.discord.gg +full:seattle757.discord.gg +full:seattle7789.discord.gg +full:seattle7925.discord.gg +full:seattle8062.discord.gg +full:seattle8250.discord.gg +full:seattle8393.discord.gg +full:seattle8421.discord.gg +full:seattle8447.discord.gg +full:seattle8642.discord.gg +full:seattle8645.discord.gg +full:seattle8685.discord.gg +full:seattle8801.discord.gg +full:seattle8915.discord.gg +full:seattle8939.discord.gg +full:seattle9099.discord.gg +full:seattle9385.discord.gg +full:seattle9546.discord.gg +full:seattle9566.discord.gg +full:seattle9661.discord.gg +full:seattle9793.discord.gg +full:singapore1073.discord.gg +full:singapore1089.discord.gg +full:singapore11000.discord.gg +full:singapore11001.discord.gg +full:singapore11002.discord.gg +full:singapore11003.discord.gg +full:singapore11004.discord.gg +full:singapore11005.discord.gg +full:singapore11006.discord.gg +full:singapore11007.discord.gg +full:singapore11008.discord.gg +full:singapore11009.discord.gg +full:singapore11010.discord.gg +full:singapore11011.discord.gg +full:singapore11012.discord.gg +full:singapore11013.discord.gg +full:singapore11014.discord.gg +full:singapore11015.discord.gg +full:singapore11016.discord.gg +full:singapore11017.discord.gg +full:singapore11018.discord.gg +full:singapore11019.discord.gg +full:singapore11020.discord.gg +full:singapore11021.discord.gg +full:singapore11022.discord.gg +full:singapore11023.discord.gg +full:singapore11024.discord.gg +full:singapore11025.discord.gg +full:singapore11026.discord.gg +full:singapore11027.discord.gg +full:singapore11028.discord.gg +full:singapore11029.discord.gg +full:singapore11030.discord.gg +full:singapore11031.discord.gg +full:singapore11032.discord.gg +full:singapore11033.discord.gg +full:singapore11034.discord.gg +full:singapore11035.discord.gg +full:singapore11036.discord.gg +full:singapore11037.discord.gg +full:singapore11038.discord.gg +full:singapore11039.discord.gg +full:singapore11040.discord.gg +full:singapore11041.discord.gg +full:singapore11042.discord.gg +full:singapore11043.discord.gg +full:singapore11044.discord.gg +full:singapore11045.discord.gg +full:singapore11046.discord.gg +full:singapore11047.discord.gg +full:singapore11048.discord.gg +full:singapore11049.discord.gg +full:singapore11050.discord.gg +full:singapore11051.discord.gg +full:singapore11052.discord.gg +full:singapore11053.discord.gg +full:singapore11054.discord.gg +full:singapore11055.discord.gg +full:singapore11056.discord.gg +full:singapore11057.discord.gg +full:singapore11058.discord.gg +full:singapore11059.discord.gg +full:singapore11060.discord.gg +full:singapore11061.discord.gg +full:singapore11062.discord.gg +full:singapore11063.discord.gg +full:singapore11064.discord.gg +full:singapore11065.discord.gg +full:singapore11066.discord.gg +full:singapore11067.discord.gg +full:singapore11068.discord.gg +full:singapore11069.discord.gg +full:singapore11070.discord.gg +full:singapore11071.discord.gg +full:singapore11072.discord.gg +full:singapore11073.discord.gg +full:singapore11074.discord.gg +full:singapore11075.discord.gg +full:singapore11076.discord.gg +full:singapore11077.discord.gg +full:singapore11078.discord.gg +full:singapore11079.discord.gg +full:singapore11080.discord.gg +full:singapore11081.discord.gg +full:singapore11082.discord.gg +full:singapore11083.discord.gg +full:singapore11084.discord.gg +full:singapore11085.discord.gg +full:singapore11086.discord.gg +full:singapore11087.discord.gg +full:singapore11088.discord.gg +full:singapore11089.discord.gg +full:singapore11090.discord.gg +full:singapore11091.discord.gg +full:singapore11092.discord.gg +full:singapore11093.discord.gg +full:singapore11094.discord.gg +full:singapore11095.discord.gg +full:singapore11096.discord.gg +full:singapore11097.discord.gg +full:singapore11098.discord.gg +full:singapore11099.discord.gg +full:singapore11100.discord.gg +full:singapore11101.discord.gg +full:singapore11102.discord.gg +full:singapore11103.discord.gg +full:singapore11104.discord.gg +full:singapore11105.discord.gg +full:singapore11106.discord.gg +full:singapore11107.discord.gg +full:singapore11108.discord.gg +full:singapore11109.discord.gg +full:singapore11110.discord.gg +full:singapore11111.discord.gg +full:singapore11112.discord.gg +full:singapore11113.discord.gg +full:singapore11114.discord.gg +full:singapore11115.discord.gg +full:singapore11116.discord.gg +full:singapore11117.discord.gg +full:singapore11118.discord.gg +full:singapore11119.discord.gg +full:singapore11120.discord.gg +full:singapore11121.discord.gg +full:singapore11122.discord.gg +full:singapore11123.discord.gg +full:singapore11124.discord.gg +full:singapore11125.discord.gg +full:singapore11126.discord.gg +full:singapore11127.discord.gg +full:singapore11128.discord.gg +full:singapore11129.discord.gg +full:singapore11130.discord.gg +full:singapore11131.discord.gg +full:singapore11132.discord.gg +full:singapore11133.discord.gg +full:singapore11134.discord.gg +full:singapore11135.discord.gg +full:singapore11136.discord.gg +full:singapore11137.discord.gg +full:singapore11138.discord.gg +full:singapore11139.discord.gg +full:singapore11140.discord.gg +full:singapore11141.discord.gg +full:singapore11142.discord.gg +full:singapore11143.discord.gg +full:singapore11144.discord.gg +full:singapore11145.discord.gg +full:singapore11146.discord.gg +full:singapore11147.discord.gg +full:singapore11148.discord.gg +full:singapore11149.discord.gg +full:singapore11150.discord.gg +full:singapore11151.discord.gg +full:singapore11152.discord.gg +full:singapore11153.discord.gg +full:singapore11154.discord.gg +full:singapore11155.discord.gg +full:singapore11156.discord.gg +full:singapore11157.discord.gg +full:singapore11158.discord.gg +full:singapore11159.discord.gg +full:singapore11160.discord.gg +full:singapore11161.discord.gg +full:singapore11162.discord.gg +full:singapore11163.discord.gg +full:singapore11164.discord.gg +full:singapore11165.discord.gg +full:singapore11166.discord.gg +full:singapore11167.discord.gg +full:singapore11168.discord.gg +full:singapore11169.discord.gg +full:singapore11170.discord.gg +full:singapore1136.discord.gg +full:singapore1145.discord.gg +full:singapore1197.discord.gg +full:singapore1327.discord.gg +full:singapore1544.discord.gg +full:singapore1581.discord.gg +full:singapore159.discord.gg +full:singapore1607.discord.gg +full:singapore161.discord.gg +full:singapore1621.discord.gg +full:singapore1639.discord.gg +full:singapore165.discord.gg +full:singapore1667.discord.gg +full:singapore1705.discord.gg +full:singapore1727.discord.gg +full:singapore1810.discord.gg +full:singapore1829.discord.gg +full:singapore184.discord.gg +full:singapore1845.discord.gg +full:singapore1865.discord.gg +full:singapore1913.discord.gg +full:singapore1923.discord.gg +full:singapore1966.discord.gg +full:singapore198.discord.gg +full:singapore2026.discord.gg +full:singapore2138.discord.gg +full:singapore2143.discord.gg +full:singapore2277.discord.gg +full:singapore2306.discord.gg +full:singapore2360.discord.gg +full:singapore2435.discord.gg +full:singapore2440.discord.gg +full:singapore249.discord.gg +full:singapore2554.discord.gg +full:singapore2569.discord.gg +full:singapore2573.discord.gg +full:singapore2614.discord.gg +full:singapore2763.discord.gg +full:singapore2917.discord.gg +full:singapore2974.discord.gg +full:singapore2992.discord.gg +full:singapore2998.discord.gg +full:singapore3063.discord.gg +full:singapore3190.discord.gg +full:singapore3217.discord.gg +full:singapore3231.discord.gg +full:singapore3272.discord.gg +full:singapore330.discord.gg +full:singapore3422.discord.gg +full:singapore3536.discord.gg +full:singapore3572.discord.gg +full:singapore359.discord.gg +full:singapore3651.discord.gg +full:singapore3653.discord.gg +full:singapore3800.discord.gg +full:singapore3864.discord.gg +full:singapore3867.discord.gg +full:singapore3952.discord.gg +full:singapore3966.discord.gg +full:singapore3971.discord.gg +full:singapore4187.discord.gg +full:singapore4197.discord.gg +full:singapore4220.discord.gg +full:singapore4221.discord.gg +full:singapore4224.discord.gg +full:singapore4230.discord.gg +full:singapore4256.discord.gg +full:singapore4380.discord.gg +full:singapore4519.discord.gg +full:singapore4551.discord.gg +full:singapore4699.discord.gg +full:singapore4744.discord.gg +full:singapore4850.discord.gg +full:singapore4907.discord.gg +full:singapore5021.discord.gg +full:singapore5099.discord.gg +full:singapore511.discord.gg +full:singapore5198.discord.gg +full:singapore5320.discord.gg +full:singapore5356.discord.gg +full:singapore5378.discord.gg +full:singapore55.discord.gg +full:singapore5552.discord.gg +full:singapore5559.discord.gg +full:singapore5629.discord.gg +full:singapore5677.discord.gg +full:singapore568.discord.gg +full:singapore5755.discord.gg +full:singapore5758.discord.gg +full:singapore5775.discord.gg +full:singapore5788.discord.gg +full:singapore5790.discord.gg +full:singapore5834.discord.gg +full:singapore6080.discord.gg +full:singapore6112.discord.gg +full:singapore6136.discord.gg +full:singapore615.discord.gg +full:singapore6157.discord.gg +full:singapore6188.discord.gg +full:singapore6259.discord.gg +full:singapore6463.discord.gg +full:singapore6491.discord.gg +full:singapore6505.discord.gg +full:singapore6515.discord.gg +full:singapore6678.discord.gg +full:singapore6699.discord.gg +full:singapore6786.discord.gg +full:singapore6938.discord.gg +full:singapore7040.discord.gg +full:singapore7116.discord.gg +full:singapore7237.discord.gg +full:singapore724.discord.gg +full:singapore7250.discord.gg +full:singapore7289.discord.gg +full:singapore7300.discord.gg +full:singapore7338.discord.gg +full:singapore7362.discord.gg +full:singapore7430.discord.gg +full:singapore7469.discord.gg +full:singapore7569.discord.gg +full:singapore7750.discord.gg +full:singapore777.discord.gg +full:singapore7787.discord.gg +full:singapore7877.discord.gg +full:singapore7893.discord.gg +full:singapore7925.discord.gg +full:singapore7930.discord.gg +full:singapore7990.discord.gg +full:singapore8032.discord.gg +full:singapore8057.discord.gg +full:singapore8275.discord.gg +full:singapore8290.discord.gg +full:singapore8309.discord.gg +full:singapore836.discord.gg +full:singapore8365.discord.gg +full:singapore8385.discord.gg +full:singapore8481.discord.gg +full:singapore8490.discord.gg +full:singapore8571.discord.gg +full:singapore860.discord.gg +full:singapore863.discord.gg +full:singapore8646.discord.gg +full:singapore8703.discord.gg +full:singapore8730.discord.gg +full:singapore8849.discord.gg +full:singapore8862.discord.gg +full:singapore8885.discord.gg +full:singapore8931.discord.gg +full:singapore8958.discord.gg +full:singapore9003.discord.gg +full:singapore9073.discord.gg +full:singapore9107.discord.gg +full:singapore9151.discord.gg +full:singapore9428.discord.gg +full:singapore9436.discord.gg +full:singapore947.discord.gg +full:singapore9592.discord.gg +full:singapore9675.discord.gg +full:singapore9677.discord.gg +full:singapore9735.discord.gg +full:singapore975.discord.gg +full:singapore9854.discord.gg +full:singapore989.discord.gg +full:south-korea1000.discord.gg +full:south-korea1001.discord.gg +full:south-korea1002.discord.gg +full:south-korea1003.discord.gg +full:south-korea1004.discord.gg +full:south-korea1005.discord.gg +full:south-korea1006.discord.gg +full:south-korea1007.discord.gg +full:south-korea1008.discord.gg +full:south-korea1009.discord.gg +full:south-korea1010.discord.gg +full:south-korea1011.discord.gg +full:south-korea1012.discord.gg +full:south-korea1013.discord.gg +full:south-korea1014.discord.gg +full:south-korea1015.discord.gg +full:south-korea1016.discord.gg +full:south-korea1017.discord.gg +full:south-korea1018.discord.gg +full:south-korea1019.discord.gg +full:south-korea1020.discord.gg +full:south-korea1021.discord.gg +full:south-korea1022.discord.gg +full:south-korea1023.discord.gg +full:south-korea1024.discord.gg +full:south-korea1025.discord.gg +full:south-korea1026.discord.gg +full:south-korea1027.discord.gg +full:south-korea1028.discord.gg +full:south-korea1029.discord.gg +full:south-korea1030.discord.gg +full:south-korea1031.discord.gg +full:south-korea1032.discord.gg +full:south-korea1033.discord.gg +full:south-korea1034.discord.gg +full:south-korea1035.discord.gg +full:south-korea1036.discord.gg +full:south-korea1037.discord.gg +full:south-korea1038.discord.gg +full:south-korea1039.discord.gg +full:south-korea1040.discord.gg +full:south-korea1041.discord.gg +full:south-korea1042.discord.gg +full:south-korea1043.discord.gg +full:south-korea1044.discord.gg +full:south-korea1045.discord.gg +full:south-korea1046.discord.gg +full:south-korea1047.discord.gg +full:south-korea1048.discord.gg +full:south-korea1049.discord.gg +full:south-korea1050.discord.gg +full:south-korea1051.discord.gg +full:south-korea1052.discord.gg +full:south-korea1053.discord.gg +full:south-korea1054.discord.gg +full:south-korea1055.discord.gg +full:south-korea1056.discord.gg +full:south-korea1057.discord.gg +full:south-korea1058.discord.gg +full:south-korea1059.discord.gg +full:south-korea1060.discord.gg +full:south-korea1061.discord.gg +full:south-korea1062.discord.gg +full:south-korea1063.discord.gg +full:south-korea1064.discord.gg +full:south-korea1065.discord.gg +full:south-korea1066.discord.gg +full:south-korea1067.discord.gg +full:south-korea1068.discord.gg +full:south-korea1069.discord.gg +full:south-korea1070.discord.gg +full:south-korea1071.discord.gg +full:south-korea1072.discord.gg +full:south-korea1073.discord.gg +full:south-korea1074.discord.gg +full:south-korea1075.discord.gg +full:south-korea1076.discord.gg +full:south-korea1077.discord.gg +full:south-korea1078.discord.gg +full:south-korea1079.discord.gg +full:south-korea1080.discord.gg +full:south-korea1081.discord.gg +full:south-korea1082.discord.gg +full:south-korea1083.discord.gg +full:south-korea1084.discord.gg +full:south-korea1085.discord.gg +full:south-korea1086.discord.gg +full:south-korea1087.discord.gg +full:south-korea1088.discord.gg +full:south-korea1089.discord.gg +full:south-korea1090.discord.gg +full:south-korea1091.discord.gg +full:south-korea1092.discord.gg +full:south-korea1093.discord.gg +full:south-korea1094.discord.gg +full:south-korea1095.discord.gg +full:south-korea1096.discord.gg +full:south-korea1097.discord.gg +full:south-korea1098.discord.gg +full:south-korea1099.discord.gg +full:south-korea1100.discord.gg +full:south-korea1101.discord.gg +full:south-korea1102.discord.gg +full:south-korea1103.discord.gg +full:south-korea1104.discord.gg +full:south-korea1105.discord.gg +full:south-korea1106.discord.gg +full:south-korea1107.discord.gg +full:south-korea1108.discord.gg +full:south-korea1109.discord.gg +full:south-korea1110.discord.gg +full:south-korea1111.discord.gg +full:south-korea1112.discord.gg +full:south-korea1113.discord.gg +full:south-korea1114.discord.gg +full:south-korea1115.discord.gg +full:south-korea1116.discord.gg +full:south-korea1117.discord.gg +full:south-korea1118.discord.gg +full:south-korea1119.discord.gg +full:south-korea1120.discord.gg +full:south-korea1121.discord.gg +full:south-korea1122.discord.gg +full:south-korea1123.discord.gg +full:south-korea1124.discord.gg +full:south-korea1125.discord.gg +full:south-korea1126.discord.gg +full:south-korea1127.discord.gg +full:south-korea1128.discord.gg +full:south-korea1129.discord.gg +full:southafrica10000.discord.gg +full:southafrica10001.discord.gg +full:southafrica10002.discord.gg +full:southafrica154.discord.gg +full:southafrica2917.discord.gg +full:southafrica3297.discord.gg +full:southafrica3910.discord.gg +full:southafrica4514.discord.gg +full:southafrica5887.discord.gg +full:southafrica6172.discord.gg +full:southafrica6740.discord.gg +full:southafrica7392.discord.gg +full:southafrica766.discord.gg +full:southafrica8123.discord.gg +full:southafrica8951.discord.gg +full:southafrica9221.discord.gg +full:southafrica9470.discord.gg +full:southafrica9888.discord.gg +full:stable.dl2.discordapp.net +full:stage-scale10000.discord.gg +full:stage-scale10001.discord.gg +full:stage-scale10002.discord.gg +full:stage-scale10003.discord.gg +full:stage-scale10004.discord.gg +full:stockholm1131.discord.gg +full:stockholm1134.discord.gg +full:stockholm1555.discord.gg +full:stockholm1784.discord.gg +full:stockholm1932.discord.gg +full:stockholm1978.discord.gg +full:stockholm2177.discord.gg +full:stockholm2235.discord.gg +full:stockholm2593.discord.gg +full:stockholm2673.discord.gg +full:stockholm2780.discord.gg +full:stockholm2913.discord.gg +full:stockholm3038.discord.gg +full:stockholm3048.discord.gg +full:stockholm3077.discord.gg +full:stockholm3105.discord.gg +full:stockholm3238.discord.gg +full:stockholm3536.discord.gg +full:stockholm3545.discord.gg +full:stockholm3568.discord.gg +full:stockholm3715.discord.gg +full:stockholm3810.discord.gg +full:stockholm3850.discord.gg +full:stockholm3881.discord.gg +full:stockholm4371.discord.gg +full:stockholm4601.discord.gg +full:stockholm4904.discord.gg +full:stockholm5118.discord.gg +full:stockholm5485.discord.gg +full:stockholm5620.discord.gg +full:stockholm589.discord.gg +full:stockholm6104.discord.gg +full:stockholm6152.discord.gg +full:stockholm6270.discord.gg +full:stockholm6606.discord.gg +full:stockholm6612.discord.gg +full:stockholm6663.discord.gg +full:stockholm7242.discord.gg +full:stockholm7375.discord.gg +full:stockholm7576.discord.gg +full:stockholm7719.discord.gg +full:stockholm7753.discord.gg +full:stockholm7797.discord.gg +full:stockholm7918.discord.gg +full:stockholm7997.discord.gg +full:stockholm8036.discord.gg +full:stockholm8261.discord.gg +full:stockholm8334.discord.gg +full:stockholm8360.discord.gg +full:stockholm8543.discord.gg +full:stockholm86.discord.gg +full:stockholm8635.discord.gg +full:stockholm8789.discord.gg +full:stockholm8901.discord.gg +full:stockholm9000.discord.gg +full:stockholm9058.discord.gg +full:stockholm906.discord.gg +full:stockholm922.discord.gg +full:stockholm9273.discord.gg +full:stockholm9330.discord.gg +full:stockholm9368.discord.gg +full:stockholm9414.discord.gg +full:stockholm9496.discord.gg +full:stockholm9579.discord.gg +full:stockholm9593.discord.gg +full:stockholm9603.discord.gg +full:stockholm9724.discord.gg +full:stockholm9889.discord.gg +full:sydney11000.discord.gg +full:sydney11001.discord.gg +full:sydney11002.discord.gg +full:sydney11003.discord.gg +full:sydney11004.discord.gg +full:sydney11005.discord.gg +full:sydney11006.discord.gg +full:sydney11007.discord.gg +full:sydney11008.discord.gg +full:sydney11009.discord.gg +full:sydney11010.discord.gg +full:sydney11011.discord.gg +full:sydney11012.discord.gg +full:sydney11013.discord.gg +full:sydney11014.discord.gg +full:sydney11015.discord.gg +full:sydney11016.discord.gg +full:sydney11017.discord.gg +full:sydney11018.discord.gg +full:sydney11019.discord.gg +full:sydney11020.discord.gg +full:sydney11021.discord.gg +full:sydney11022.discord.gg +full:sydney11023.discord.gg +full:sydney11024.discord.gg +full:sydney11025.discord.gg +full:sydney11026.discord.gg +full:sydney11027.discord.gg +full:sydney11028.discord.gg +full:sydney11029.discord.gg +full:sydney11030.discord.gg +full:sydney11031.discord.gg +full:sydney11032.discord.gg +full:sydney11033.discord.gg +full:sydney11034.discord.gg +full:sydney11035.discord.gg +full:sydney11036.discord.gg +full:sydney11037.discord.gg +full:sydney11038.discord.gg +full:sydney11039.discord.gg +full:sydney11040.discord.gg +full:sydney11041.discord.gg +full:sydney11042.discord.gg +full:sydney11043.discord.gg +full:sydney11044.discord.gg +full:sydney11045.discord.gg +full:sydney11046.discord.gg +full:sydney11047.discord.gg +full:sydney11048.discord.gg +full:sydney11049.discord.gg +full:sydney11050.discord.gg +full:sydney11051.discord.gg +full:sydney11052.discord.gg +full:sydney11053.discord.gg +full:sydney11054.discord.gg +full:sydney11055.discord.gg +full:sydney11056.discord.gg +full:sydney11057.discord.gg +full:sydney11058.discord.gg +full:sydney11059.discord.gg +full:sydney11060.discord.gg +full:sydney11061.discord.gg +full:sydney11062.discord.gg +full:sydney11063.discord.gg +full:sydney11064.discord.gg +full:sydney11065.discord.gg +full:sydney11066.discord.gg +full:sydney11067.discord.gg +full:sydney11068.discord.gg +full:sydney11069.discord.gg +full:sydney11070.discord.gg +full:sydney11071.discord.gg +full:sydney11072.discord.gg +full:sydney11073.discord.gg +full:sydney11074.discord.gg +full:sydney11075.discord.gg +full:sydney11076.discord.gg +full:sydney11077.discord.gg +full:sydney11078.discord.gg +full:sydney11079.discord.gg +full:sydney11080.discord.gg +full:sydney11081.discord.gg +full:sydney11082.discord.gg +full:sydney11083.discord.gg +full:sydney11084.discord.gg +full:sydney11085.discord.gg +full:sydney11086.discord.gg +full:sydney11087.discord.gg +full:sydney11088.discord.gg +full:sydney11089.discord.gg +full:sydney11090.discord.gg +full:sydney11091.discord.gg +full:sydney11092.discord.gg +full:sydney11093.discord.gg +full:sydney11094.discord.gg +full:sydney11095.discord.gg +full:sydney11096.discord.gg +full:sydney11097.discord.gg +full:sydney11098.discord.gg +full:sydney11099.discord.gg +full:sydney11100.discord.gg +full:tel-aviv10000.discord.gg +full:tel-aviv10001.discord.gg +full:tel-aviv10002.discord.gg +full:tel-aviv10003.discord.gg +full:tel-aviv10004.discord.gg +full:tel-aviv10005.discord.gg +full:tel-aviv10006.discord.gg +full:tel-aviv10007.discord.gg +full:tel-aviv10008.discord.gg +full:tel-aviv10009.discord.gg +full:tel-aviv10010.discord.gg +full:tel-aviv10011.discord.gg +full:tel-aviv10012.discord.gg +full:tel-aviv10013.discord.gg +full:tel-aviv10014.discord.gg +full:us-central1064.discord.gg +full:us-central1067.discord.gg +full:us-central1084.discord.gg +full:us-central1119.discord.gg +full:us-central1139.discord.gg +full:us-central1195.discord.gg +full:us-central1273.discord.gg +full:us-central1356.discord.gg +full:us-central1412.discord.gg +full:us-central142.discord.gg +full:us-central1525.discord.gg +full:us-central1547.discord.gg +full:us-central1585.discord.gg +full:us-central1635.discord.gg +full:us-central1666.discord.gg +full:us-central1710.discord.gg +full:us-central1835.discord.gg +full:us-central1915.discord.gg +full:us-central1925.discord.gg +full:us-central2001.discord.gg +full:us-central2086.discord.gg +full:us-central2150.discord.gg +full:us-central2205.discord.gg +full:us-central2219.discord.gg +full:us-central2448.discord.gg +full:us-central2462.discord.gg +full:us-central2483.discord.gg +full:us-central2529.discord.gg +full:us-central2632.discord.gg +full:us-central2659.discord.gg +full:us-central2661.discord.gg +full:us-central2711.discord.gg +full:us-central2720.discord.gg +full:us-central2733.discord.gg +full:us-central274.discord.gg +full:us-central2775.discord.gg +full:us-central2816.discord.gg +full:us-central2828.discord.gg +full:us-central2859.discord.gg +full:us-central295.discord.gg +full:us-central2976.discord.gg +full:us-central309.discord.gg +full:us-central3164.discord.gg +full:us-central3234.discord.gg +full:us-central3286.discord.gg +full:us-central3358.discord.gg +full:us-central3365.discord.gg +full:us-central3382.discord.gg +full:us-central3466.discord.gg +full:us-central355.discord.gg +full:us-central3634.discord.gg +full:us-central3691.discord.gg +full:us-central3827.discord.gg +full:us-central394.discord.gg +full:us-central3969.discord.gg +full:us-central400.discord.gg +full:us-central4020.discord.gg +full:us-central4063.discord.gg +full:us-central414.discord.gg +full:us-central4177.discord.gg +full:us-central4183.discord.gg +full:us-central4187.discord.gg +full:us-central42.discord.gg +full:us-central4293.discord.gg +full:us-central4361.discord.gg +full:us-central4383.discord.gg +full:us-central4421.discord.gg +full:us-central4436.discord.gg +full:us-central4518.discord.gg +full:us-central4565.discord.gg +full:us-central4590.discord.gg +full:us-central4697.discord.gg +full:us-central4987.discord.gg +full:us-central5033.discord.gg +full:us-central5196.discord.gg +full:us-central5225.discord.gg +full:us-central5260.discord.gg +full:us-central5293.discord.gg +full:us-central5418.discord.gg +full:us-central5460.discord.gg +full:us-central5520.discord.gg +full:us-central5558.discord.gg +full:us-central5594.discord.gg +full:us-central5604.discord.gg +full:us-central5657.discord.gg +full:us-central5664.discord.gg +full:us-central5806.discord.gg +full:us-central586.discord.gg +full:us-central5931.discord.gg +full:us-central6158.discord.gg +full:us-central6390.discord.gg +full:us-central6494.discord.gg +full:us-central6496.discord.gg +full:us-central6509.discord.gg +full:us-central6561.discord.gg +full:us-central6829.discord.gg +full:us-central6957.discord.gg +full:us-central7091.discord.gg +full:us-central7106.discord.gg +full:us-central7111.discord.gg +full:us-central7119.discord.gg +full:us-central7131.discord.gg +full:us-central729.discord.gg +full:us-central7290.discord.gg +full:us-central7295.discord.gg +full:us-central7304.discord.gg +full:us-central7374.discord.gg +full:us-central7376.discord.gg +full:us-central7398.discord.gg +full:us-central7401.discord.gg +full:us-central7447.discord.gg +full:us-central746.discord.gg +full:us-central7530.discord.gg +full:us-central7675.discord.gg +full:us-central7751.discord.gg +full:us-central7799.discord.gg +full:us-central8005.discord.gg +full:us-central8036.discord.gg +full:us-central8165.discord.gg +full:us-central8391.discord.gg +full:us-central8409.discord.gg +full:us-central8503.discord.gg +full:us-central8579.discord.gg +full:us-central8673.discord.gg +full:us-central8819.discord.gg +full:us-central8828.discord.gg +full:us-central8865.discord.gg +full:us-central8880.discord.gg +full:us-central9106.discord.gg +full:us-central9229.discord.gg +full:us-central9340.discord.gg +full:us-central9348.discord.gg +full:us-central9431.discord.gg +full:us-central9471.discord.gg +full:us-central9532.discord.gg +full:us-central9588.discord.gg +full:us-central96.discord.gg +full:us-central9621.discord.gg +full:us-central9709.discord.gg +full:us-central9774.discord.gg +full:us-central9801.discord.gg +full:us-central9911.discord.gg +full:us-central9923.discord.gg +full:us-central9980.discord.gg +full:us-east1032.discord.gg +full:us-east1034.discord.gg +full:us-east1049.discord.gg +full:us-east1069.discord.gg +full:us-east11000.discord.gg +full:us-east11001.discord.gg +full:us-east11002.discord.gg +full:us-east11003.discord.gg +full:us-east11004.discord.gg +full:us-east11005.discord.gg +full:us-east11006.discord.gg +full:us-east11007.discord.gg +full:us-east1142.discord.gg +full:us-east1230.discord.gg +full:us-east124.discord.gg +full:us-east1259.discord.gg +full:us-east1260.discord.gg +full:us-east1352.discord.gg +full:us-east1354.discord.gg +full:us-east136.discord.gg +full:us-east1460.discord.gg +full:us-east148.discord.gg +full:us-east1487.discord.gg +full:us-east1498.discord.gg +full:us-east1513.discord.gg +full:us-east1598.discord.gg +full:us-east1635.discord.gg +full:us-east1642.discord.gg +full:us-east1678.discord.gg +full:us-east170.discord.gg +full:us-east1707.discord.gg +full:us-east1718.discord.gg +full:us-east1726.discord.gg +full:us-east1750.discord.gg +full:us-east1926.discord.gg +full:us-east1951.discord.gg +full:us-east2078.discord.gg +full:us-east2079.discord.gg +full:us-east2097.discord.gg +full:us-east2104.discord.gg +full:us-east2109.discord.gg +full:us-east2248.discord.gg +full:us-east2334.discord.gg +full:us-east2370.discord.gg +full:us-east2371.discord.gg +full:us-east2380.discord.gg +full:us-east2413.discord.gg +full:us-east2423.discord.gg +full:us-east2483.discord.gg +full:us-east2522.discord.gg +full:us-east2568.discord.gg +full:us-east2591.discord.gg +full:us-east2733.discord.gg +full:us-east2781.discord.gg +full:us-east2841.discord.gg +full:us-east2880.discord.gg +full:us-east2929.discord.gg +full:us-east294.discord.gg +full:us-east2953.discord.gg +full:us-east303.discord.gg +full:us-east3034.discord.gg +full:us-east3077.discord.gg +full:us-east3117.discord.gg +full:us-east312.discord.gg +full:us-east3171.discord.gg +full:us-east3196.discord.gg +full:us-east3326.discord.gg +full:us-east3360.discord.gg +full:us-east3450.discord.gg +full:us-east3461.discord.gg +full:us-east3517.discord.gg +full:us-east3546.discord.gg +full:us-east3557.discord.gg +full:us-east3616.discord.gg +full:us-east3662.discord.gg +full:us-east3688.discord.gg +full:us-east3777.discord.gg +full:us-east3807.discord.gg +full:us-east3856.discord.gg +full:us-east3879.discord.gg +full:us-east3906.discord.gg +full:us-east3958.discord.gg +full:us-east3999.discord.gg +full:us-east4013.discord.gg +full:us-east4026.discord.gg +full:us-east4267.discord.gg +full:us-east4293.discord.gg +full:us-east4334.discord.gg +full:us-east4339.discord.gg +full:us-east4353.discord.gg +full:us-east4396.discord.gg +full:us-east4399.discord.gg +full:us-east4446.discord.gg +full:us-east4447.discord.gg +full:us-east4460.discord.gg +full:us-east4556.discord.gg +full:us-east4557.discord.gg +full:us-east4581.discord.gg +full:us-east4730.discord.gg +full:us-east4812.discord.gg +full:us-east4837.discord.gg +full:us-east4899.discord.gg +full:us-east4912.discord.gg +full:us-east5124.discord.gg +full:us-east5154.discord.gg +full:us-east517.discord.gg +full:us-east5218.discord.gg +full:us-east5264.discord.gg +full:us-east5289.discord.gg +full:us-east5290.discord.gg +full:us-east533.discord.gg +full:us-east5344.discord.gg +full:us-east5570.discord.gg +full:us-east5576.discord.gg +full:us-east5639.discord.gg +full:us-east5668.discord.gg +full:us-east5736.discord.gg +full:us-east5765.discord.gg +full:us-east5791.discord.gg +full:us-east5944.discord.gg +full:us-east5967.discord.gg +full:us-east6018.discord.gg +full:us-east6067.discord.gg +full:us-east6118.discord.gg +full:us-east6119.discord.gg +full:us-east6143.discord.gg +full:us-east6145.discord.gg +full:us-east6172.discord.gg +full:us-east6190.discord.gg +full:us-east6329.discord.gg +full:us-east6335.discord.gg +full:us-east638.discord.gg +full:us-east6442.discord.gg +full:us-east6585.discord.gg +full:us-east6594.discord.gg +full:us-east6601.discord.gg +full:us-east6698.discord.gg +full:us-east6716.discord.gg +full:us-east6726.discord.gg +full:us-east6823.discord.gg +full:us-east6825.discord.gg +full:us-east6837.discord.gg +full:us-east687.discord.gg +full:us-east6930.discord.gg +full:us-east6935.discord.gg +full:us-east7003.discord.gg +full:us-east701.discord.gg +full:us-east7023.discord.gg +full:us-east7154.discord.gg +full:us-east7213.discord.gg +full:us-east7263.discord.gg +full:us-east7291.discord.gg +full:us-east7294.discord.gg +full:us-east7328.discord.gg +full:us-east7340.discord.gg +full:us-east7371.discord.gg +full:us-east7388.discord.gg +full:us-east745.discord.gg +full:us-east7474.discord.gg +full:us-east7530.discord.gg +full:us-east7555.discord.gg +full:us-east7654.discord.gg +full:us-east7698.discord.gg +full:us-east7708.discord.gg +full:us-east7753.discord.gg +full:us-east7755.discord.gg +full:us-east7761.discord.gg +full:us-east7795.discord.gg +full:us-east7873.discord.gg +full:us-east7890.discord.gg +full:us-east7901.discord.gg +full:us-east7937.discord.gg +full:us-east7970.discord.gg +full:us-east799.discord.gg +full:us-east8010.discord.gg +full:us-east8123.discord.gg +full:us-east8250.discord.gg +full:us-east8290.discord.gg +full:us-east8332.discord.gg +full:us-east8347.discord.gg +full:us-east8444.discord.gg +full:us-east8455.discord.gg +full:us-east853.discord.gg +full:us-east8582.discord.gg +full:us-east8583.discord.gg +full:us-east8701.discord.gg +full:us-east8702.discord.gg +full:us-east8720.discord.gg +full:us-east8808.discord.gg +full:us-east8900.discord.gg +full:us-east891.discord.gg +full:us-east8912.discord.gg +full:us-east8929.discord.gg +full:us-east8955.discord.gg +full:us-east8983.discord.gg +full:us-east9001.discord.gg +full:us-east9006.discord.gg +full:us-east9017.discord.gg +full:us-east9033.discord.gg +full:us-east9072.discord.gg +full:us-east9093.discord.gg +full:us-east9099.discord.gg +full:us-east911.discord.gg +full:us-east9194.discord.gg +full:us-east9241.discord.gg +full:us-east9253.discord.gg +full:us-east9270.discord.gg +full:us-east9283.discord.gg +full:us-east93.discord.gg +full:us-east9427.discord.gg +full:us-east9430.discord.gg +full:us-east9443.discord.gg +full:us-east9487.discord.gg +full:us-east9496.discord.gg +full:us-east9502.discord.gg +full:us-east9522.discord.gg +full:us-east9541.discord.gg +full:us-east956.discord.gg +full:us-east9591.discord.gg +full:us-east9613.discord.gg +full:us-east9686.discord.gg +full:us-east9737.discord.gg +full:us-east9849.discord.gg +full:us-east9863.discord.gg +full:us-east9870.discord.gg +full:us-east9923.discord.gg +full:us-east9948.discord.gg +full:us-east999.discord.gg +full:us-south10000.discord.gg +full:us-south10001.discord.gg +full:us-south10002.discord.gg +full:us-south10003.discord.gg +full:us-south10004.discord.gg +full:us-south10005.discord.gg +full:us-south10006.discord.gg +full:us-south10007.discord.gg +full:us-south10008.discord.gg +full:us-south10009.discord.gg +full:us-south1001.discord.gg +full:us-south10010.discord.gg +full:us-south10011.discord.gg +full:us-south10012.discord.gg +full:us-south10013.discord.gg +full:us-south10014.discord.gg +full:us-south10015.discord.gg +full:us-south10016.discord.gg +full:us-south10017.discord.gg +full:us-south10018.discord.gg +full:us-south10019.discord.gg +full:us-south10020.discord.gg +full:us-south10021.discord.gg +full:us-south10022.discord.gg +full:us-south10023.discord.gg +full:us-south10024.discord.gg +full:us-south10025.discord.gg +full:us-south10026.discord.gg +full:us-south10027.discord.gg +full:us-south10028.discord.gg +full:us-south10029.discord.gg +full:us-south10030.discord.gg +full:us-south10031.discord.gg +full:us-south10032.discord.gg +full:us-south10033.discord.gg +full:us-south10034.discord.gg +full:us-south10035.discord.gg +full:us-south10036.discord.gg +full:us-south10037.discord.gg +full:us-south10038.discord.gg +full:us-south10039.discord.gg +full:us-south10040.discord.gg +full:us-south10041.discord.gg +full:us-south10042.discord.gg +full:us-south10043.discord.gg +full:us-south10044.discord.gg +full:us-south10045.discord.gg +full:us-south10046.discord.gg +full:us-south1059.discord.gg +full:us-south1116.discord.gg +full:us-south1287.discord.gg +full:us-south132.discord.gg +full:us-south1320.discord.gg +full:us-south1364.discord.gg +full:us-south1387.discord.gg +full:us-south1474.discord.gg +full:us-south1655.discord.gg +full:us-south1676.discord.gg +full:us-south171.discord.gg +full:us-south1817.discord.gg +full:us-south1827.discord.gg +full:us-south1830.discord.gg +full:us-south2054.discord.gg +full:us-south2110.discord.gg +full:us-south2218.discord.gg +full:us-south2367.discord.gg +full:us-south2382.discord.gg +full:us-south2415.discord.gg +full:us-south2458.discord.gg +full:us-south262.discord.gg +full:us-south2654.discord.gg +full:us-south2694.discord.gg +full:us-south2767.discord.gg +full:us-south2775.discord.gg +full:us-south2921.discord.gg +full:us-south2950.discord.gg +full:us-south2969.discord.gg +full:us-south3004.discord.gg +full:us-south3009.discord.gg +full:us-south301.discord.gg +full:us-south3035.discord.gg +full:us-south311.discord.gg +full:us-south3130.discord.gg +full:us-south3194.discord.gg +full:us-south3232.discord.gg +full:us-south3623.discord.gg +full:us-south3627.discord.gg +full:us-south3687.discord.gg +full:us-south3722.discord.gg +full:us-south3755.discord.gg +full:us-south3907.discord.gg +full:us-south3968.discord.gg +full:us-south400.discord.gg +full:us-south4002.discord.gg +full:us-south4106.discord.gg +full:us-south4589.discord.gg +full:us-south48.discord.gg +full:us-south4802.discord.gg +full:us-south4851.discord.gg +full:us-south4958.discord.gg +full:us-south4986.discord.gg +full:us-south5050.discord.gg +full:us-south5056.discord.gg +full:us-south5122.discord.gg +full:us-south5254.discord.gg +full:us-south5290.discord.gg +full:us-south5381.discord.gg +full:us-south5393.discord.gg +full:us-south5418.discord.gg +full:us-south5427.discord.gg +full:us-south5549.discord.gg +full:us-south5552.discord.gg +full:us-south5621.discord.gg +full:us-south5730.discord.gg +full:us-south5756.discord.gg +full:us-south5790.discord.gg +full:us-south5796.discord.gg +full:us-south5823.discord.gg +full:us-south5871.discord.gg +full:us-south595.discord.gg +full:us-south5953.discord.gg +full:us-south6184.discord.gg +full:us-south6322.discord.gg +full:us-south6346.discord.gg +full:us-south6371.discord.gg +full:us-south6373.discord.gg +full:us-south6466.discord.gg +full:us-south6483.discord.gg +full:us-south6654.discord.gg +full:us-south6704.discord.gg +full:us-south6767.discord.gg +full:us-south6817.discord.gg +full:us-south6870.discord.gg +full:us-south696.discord.gg +full:us-south6998.discord.gg +full:us-south7031.discord.gg +full:us-south7191.discord.gg +full:us-south7225.discord.gg +full:us-south7316.discord.gg +full:us-south7330.discord.gg +full:us-south7393.discord.gg +full:us-south7474.discord.gg +full:us-south7506.discord.gg +full:us-south7559.discord.gg +full:us-south7672.discord.gg +full:us-south768.discord.gg +full:us-south7881.discord.gg +full:us-south7939.discord.gg +full:us-south8092.discord.gg +full:us-south8094.discord.gg +full:us-south8104.discord.gg +full:us-south8151.discord.gg +full:us-south8232.discord.gg +full:us-south828.discord.gg +full:us-south832.discord.gg +full:us-south8330.discord.gg +full:us-south8351.discord.gg +full:us-south8367.discord.gg +full:us-south8370.discord.gg +full:us-south8381.discord.gg +full:us-south8387.discord.gg +full:us-south8467.discord.gg +full:us-south8587.discord.gg +full:us-south8589.discord.gg +full:us-south8590.discord.gg +full:us-south8604.discord.gg +full:us-south8792.discord.gg +full:us-south8818.discord.gg +full:us-south8877.discord.gg +full:us-south8917.discord.gg +full:us-south8947.discord.gg +full:us-south9142.discord.gg +full:us-south9419.discord.gg +full:us-south942.discord.gg +full:us-south9430.discord.gg +full:us-south9470.discord.gg +full:us-south964.discord.gg +full:us-south9727.discord.gg +full:us-south9817.discord.gg +full:us-south9952.discord.gg +full:us-south9987.discord.gg +full:us-west1015.discord.gg +full:us-west1032.discord.gg +full:us-west1089.discord.gg +full:us-west11000.discord.gg +full:us-west11001.discord.gg +full:us-west11002.discord.gg +full:us-west11003.discord.gg +full:us-west11004.discord.gg +full:us-west11005.discord.gg +full:us-west11006.discord.gg +full:us-west1119.discord.gg +full:us-west1165.discord.gg +full:us-west1288.discord.gg +full:us-west132.discord.gg +full:us-west1380.discord.gg +full:us-west15.discord.gg +full:us-west1661.discord.gg +full:us-west1764.discord.gg +full:us-west1811.discord.gg +full:us-west1830.discord.gg +full:us-west1835.discord.gg +full:us-west1849.discord.gg +full:us-west1926.discord.gg +full:us-west1929.discord.gg +full:us-west1943.discord.gg +full:us-west1946.discord.gg +full:us-west1948.discord.gg +full:us-west1960.discord.gg +full:us-west1965.discord.gg +full:us-west2024.discord.gg +full:us-west205.discord.gg +full:us-west2115.discord.gg +full:us-west2124.discord.gg +full:us-west2141.discord.gg +full:us-west2194.discord.gg +full:us-west2330.discord.gg +full:us-west2449.discord.gg +full:us-west2456.discord.gg +full:us-west2471.discord.gg +full:us-west2477.discord.gg +full:us-west2548.discord.gg +full:us-west261.discord.gg +full:us-west2626.discord.gg +full:us-west2656.discord.gg +full:us-west2725.discord.gg +full:us-west2757.discord.gg +full:us-west2782.discord.gg +full:us-west2859.discord.gg +full:us-west2885.discord.gg +full:us-west2895.discord.gg +full:us-west295.discord.gg +full:us-west2970.discord.gg +full:us-west3066.discord.gg +full:us-west3069.discord.gg +full:us-west3089.discord.gg +full:us-west310.discord.gg +full:us-west3102.discord.gg +full:us-west3161.discord.gg +full:us-west3187.discord.gg +full:us-west3205.discord.gg +full:us-west3211.discord.gg +full:us-west3323.discord.gg +full:us-west338.discord.gg +full:us-west3432.discord.gg +full:us-west3472.discord.gg +full:us-west3680.discord.gg +full:us-west3694.discord.gg +full:us-west3799.discord.gg +full:us-west3802.discord.gg +full:us-west3820.discord.gg +full:us-west393.discord.gg +full:us-west3982.discord.gg +full:us-west4004.discord.gg +full:us-west4017.discord.gg +full:us-west4052.discord.gg +full:us-west4082.discord.gg +full:us-west4181.discord.gg +full:us-west4227.discord.gg +full:us-west4244.discord.gg +full:us-west4283.discord.gg +full:us-west4373.discord.gg +full:us-west4387.discord.gg +full:us-west4584.discord.gg +full:us-west4600.discord.gg +full:us-west4610.discord.gg +full:us-west4615.discord.gg +full:us-west4671.discord.gg +full:us-west4715.discord.gg +full:us-west4829.discord.gg +full:us-west4851.discord.gg +full:us-west4865.discord.gg +full:us-west4875.discord.gg +full:us-west5032.discord.gg +full:us-west5045.discord.gg +full:us-west5059.discord.gg +full:us-west5118.discord.gg +full:us-west5215.discord.gg +full:us-west5225.discord.gg +full:us-west524.discord.gg +full:us-west5305.discord.gg +full:us-west5378.discord.gg +full:us-west5398.discord.gg +full:us-west5407.discord.gg +full:us-west5484.discord.gg +full:us-west5586.discord.gg +full:us-west5655.discord.gg +full:us-west5668.discord.gg +full:us-west5695.discord.gg +full:us-west5731.discord.gg +full:us-west5771.discord.gg +full:us-west5949.discord.gg +full:us-west5997.discord.gg +full:us-west6040.discord.gg +full:us-west6057.discord.gg +full:us-west6078.discord.gg +full:us-west6119.discord.gg +full:us-west6134.discord.gg +full:us-west6168.discord.gg +full:us-west6197.discord.gg +full:us-west6317.discord.gg +full:us-west6353.discord.gg +full:us-west6363.discord.gg +full:us-west6367.discord.gg +full:us-west6398.discord.gg +full:us-west6399.discord.gg +full:us-west6401.discord.gg +full:us-west6436.discord.gg +full:us-west6501.discord.gg +full:us-west6517.discord.gg +full:us-west6540.discord.gg +full:us-west6541.discord.gg +full:us-west6598.discord.gg +full:us-west6655.discord.gg +full:us-west6679.discord.gg +full:us-west6681.discord.gg +full:us-west6779.discord.gg +full:us-west6800.discord.gg +full:us-west6826.discord.gg +full:us-west6851.discord.gg +full:us-west6862.discord.gg +full:us-west6878.discord.gg +full:us-west6887.discord.gg +full:us-west7060.discord.gg +full:us-west7112.discord.gg +full:us-west7207.discord.gg +full:us-west7239.discord.gg +full:us-west7248.discord.gg +full:us-west7343.discord.gg +full:us-west7445.discord.gg +full:us-west750.discord.gg +full:us-west7504.discord.gg +full:us-west7551.discord.gg +full:us-west7680.discord.gg +full:us-west7683.discord.gg +full:us-west7822.discord.gg +full:us-west7859.discord.gg +full:us-west7866.discord.gg +full:us-west7914.discord.gg +full:us-west7926.discord.gg +full:us-west7943.discord.gg +full:us-west8019.discord.gg +full:us-west8039.discord.gg +full:us-west8070.discord.gg +full:us-west8091.discord.gg +full:us-west8110.discord.gg +full:us-west8230.discord.gg +full:us-west827.discord.gg +full:us-west8275.discord.gg +full:us-west8392.discord.gg +full:us-west8406.discord.gg +full:us-west8416.discord.gg +full:us-west8463.discord.gg +full:us-west8504.discord.gg +full:us-west8524.discord.gg +full:us-west8526.discord.gg +full:us-west8561.discord.gg +full:us-west8596.discord.gg +full:us-west8645.discord.gg +full:us-west8653.discord.gg +full:us-west8680.discord.gg +full:us-west8722.discord.gg +full:us-west8748.discord.gg +full:us-west8827.discord.gg +full:us-west889.discord.gg +full:us-west9004.discord.gg +full:us-west9084.discord.gg +full:us-west9101.discord.gg +full:us-west9222.discord.gg +full:us-west9366.discord.gg +full:us-west9412.discord.gg +full:us-west9707.discord.gg +full:us-west9804.discord.gg +full:us-west9824.discord.gg +full:us-west9842.discord.gg +full:us-west9962.discord.gg +full:us-west9987.discord.gg +full:warsaw10000.discord.gg +full:warsaw10001.discord.gg +full:warsaw10002.discord.gg +full:warsaw10003.discord.gg +full:warsaw10004.discord.gg +full:warsaw10005.discord.gg +full:warsaw10006.discord.gg +full:warsaw10007.discord.gg +full:warsaw10008.discord.gg +full:warsaw10009.discord.gg +full:warsaw10010.discord.gg +full:warsaw10011.discord.gg +full:warsaw10012.discord.gg +full:warsaw10013.discord.gg +full:warsaw10014.discord.gg +full:warsaw10015.discord.gg +full:warsaw10016.discord.gg +full:warsaw10017.discord.gg +full:warsaw10018.discord.gg +full:warsaw10019.discord.gg +full:warsaw10020.discord.gg +full:warsaw10021.discord.gg +full:warsaw10022.discord.gg +full:warsaw10023.discord.gg +full:warsaw10024.discord.gg +full:warsaw10025.discord.gg +full:warsaw10026.discord.gg +full:warsaw10027.discord.gg +full:warsaw10028.discord.gg +full:warsaw10029.discord.gg +full:warsaw10030.discord.gg +full:warsaw10031.discord.gg +full:warsaw10032.discord.gg +full:warsaw10033.discord.gg +full:warsaw10034.discord.gg +full:warsaw10035.discord.gg +full:warsaw10036.discord.gg +full:warsaw10037.discord.gg +full:warsaw10038.discord.gg +full:warsaw10039.discord.gg +full:warsaw10040.discord.gg +full:warsaw10041.discord.gg +full:warsaw10042.discord.gg +full:warsaw10043.discord.gg +full:warsaw10044.discord.gg +full:warsaw10045.discord.gg +full:warsaw10046.discord.gg +full:warsaw10047.discord.gg +full:warsaw10048.discord.gg +full:warsaw10049.discord.gg +full:warsaw10050.discord.gg +full:warsaw10051.discord.gg +full:warsaw10052.discord.gg +full:warsaw10053.discord.gg +full:warsaw10054.discord.gg +full:warsaw10055.discord.gg +full:warsaw10056.discord.gg +full:warsaw10057.discord.gg +full:warsaw10058.discord.gg +full:warsaw10059.discord.gg +full:warsaw10060.discord.gg +full:warsaw10061.discord.gg +full:warsaw10062.discord.gg +full:warsaw10063.discord.gg +full:warsaw10064.discord.gg +full:warsaw10065.discord.gg +full:warsaw10066.discord.gg +full:warsaw10067.discord.gg +full:warsaw10068.discord.gg +full:warsaw10069.discord.gg +full:warsaw10070.discord.gg +full:warsaw10071.discord.gg +full:warsaw10072.discord.gg +full:warsaw10073.discord.gg +full:warsaw10074.discord.gg +full:warsaw10075.discord.gg +full:warsaw10076.discord.gg +full:warsaw10077.discord.gg +full:warsaw10078.discord.gg +full:warsaw10079.discord.gg +full:warsaw10080.discord.gg +full:warsaw10081.discord.gg +full:warsaw10082.discord.gg +full:warsaw10083.discord.gg +full:warsaw10084.discord.gg +full:warsaw10085.discord.gg +full:warsaw10086.discord.gg +full:warsaw10087.discord.gg +full:warsaw10088.discord.gg +full:warsaw10089.discord.gg +full:warsaw10090.discord.gg +full:warsaw10091.discord.gg +full:warsaw10092.discord.gg +full:warsaw10093.discord.gg +full:warsaw10094.discord.gg +full:warsaw10095.discord.gg +full:warsaw10096.discord.gg +full:warsaw10097.discord.gg +full:warsaw10098.discord.gg +full:warsaw10099.discord.gg +full:warsaw10100.discord.gg +full:warsaw10101.discord.gg +full:warsaw10102.discord.gg +full:warsaw10103.discord.gg +full:warsaw10104.discord.gg +full:warsaw10105.discord.gg +full:warsaw10106.discord.gg +full:warsaw10107.discord.gg +full:warsaw10108.discord.gg +full:warsaw10109.discord.gg +full:warsaw10110.discord.gg +full:warsaw10111.discord.gg +full:warsaw10112.discord.gg +full:warsaw10113.discord.gg +full:warsaw10114.discord.gg +full:warsaw10115.discord.gg +full:warsaw10116.discord.gg +full:warsaw10117.discord.gg +full:warsaw10118.discord.gg +full:warsaw10119.discord.gg +full:warsaw10120.discord.gg +full:warsaw10121.discord.gg +full:warsaw10122.discord.gg +full:warsaw10123.discord.gg +full:warsaw10124.discord.gg +full:warsaw10125.discord.gg +full:warsaw10126.discord.gg +full:warsaw10127.discord.gg +full:warsaw10128.discord.gg +full:warsaw10129.discord.gg +full:warsaw10130.discord.gg +full:warsaw10131.discord.gg +full:warsaw10132.discord.gg +full:warsaw10133.discord.gg +full:warsaw10134.discord.gg +full:warsaw10135.discord.gg +full:warsaw10136.discord.gg +full:warsaw10137.discord.gg +full:warsaw10138.discord.gg +full:warsaw10139.discord.gg +full:warsaw10140.discord.gg +full:warsaw10141.discord.gg +full:warsaw10142.discord.gg +full:warsaw10143.discord.gg +full:warsaw10144.discord.gg +full:warsaw10145.discord.gg +full:warsaw10146.discord.gg +full:warsaw10147.discord.gg +full:warsaw10148.discord.gg +full:warsaw10149.discord.gg +full:warsaw10150.discord.gg +full:warsaw10151.discord.gg +full:warsaw10152.discord.gg +full:warsaw10153.discord.gg +full:warsaw10154.discord.gg +full:warsaw10155.discord.gg +full:warsaw10156.discord.gg +full:warsaw10157.discord.gg +full:warsaw10158.discord.gg diff --git a/data/discord.txt b/data/discord.txt new file mode 100644 index 0000000..2299845 --- /dev/null +++ b/data/discord.txt @@ -0,0 +1,7 @@ +# GENERATED tools/sync.py — не редактируйте вручную. +# Сводная категория: рукописный список плюс всё, что притянул sync. + +include:discord-base +include:discord-itdoginfo +include:discord-metacubex +include:discord-youoops diff --git a/data/ip/discord-base.txt b/data/ip/discord-base.txt new file mode 100644 index 0000000..1d09232 --- /dev/null +++ b/data/ip/discord-base.txt @@ -0,0 +1,106 @@ +# discord-base — рукописный список адресов, снятых резолвом доменов Discord. +# +# Внимание: это A-записи, а не сети Discord. Часть адресов принадлежит +# Cloudflare (104.x, 162.159.x, 172.6x.x) и Google (74.125.x, 142.25x.x), +# где они разделяются с чужими сайтами, а часть — Shopify (23.227.38.74, +# магазин) и Vercel (216.198.5x.11). Список стареет: когда апстримы из +# tools/sources.json покрывают нужное, лучше опираться на них. + +23.227.38.74 +34.126.226.51 +64.233.179.207 +64.233.181.207 +74.125.69.207 +74.125.126.207 +74.125.132.207 +74.125.201.207 +74.125.202.207 +104.17.51.93 +104.17.117.93 +104.18.32.174 +104.18.34.249 +104.18.37.76 +104.18.38.75 +104.21.25.51 +104.21.72.221 +104.21.80.173 +104.21.82.160 +104.21.89.187 +108.177.121.207 +142.250.125.207 +142.250.152.207 +142.250.177.91 +142.250.177.251 +142.250.188.187 +142.250.189.123 +142.250.189.155 +142.250.217.123 +142.251.32.27 +142.251.34.251 +142.251.41.91 +142.251.41.155 +142.251.184.207 +142.251.189.207 +142.251.210.123 +142.251.210.219 +142.251.210.251 +142.251.211.27 +142.251.211.33 +142.251.211.59 +142.251.214.91 +142.251.215.155 +162.159.128.232 +162.159.128.233 +162.159.128.234 +162.159.128.235 +162.159.129.232 +162.159.129.233 +162.159.129.234 +162.159.129.235 +162.159.130.232 +162.159.130.233 +162.159.130.234 +162.159.130.235 +162.159.133.232 +162.159.133.233 +162.159.133.234 +162.159.134.232 +162.159.134.233 +162.159.134.234 +162.159.135.232 +162.159.135.233 +162.159.135.234 +162.159.136.232 +162.159.136.233 +162.159.136.234 +162.159.137.232 +162.159.137.233 +162.159.137.234 +162.159.138.232 +162.159.138.233 +162.159.138.234 +172.64.149.181 +172.64.150.180 +172.64.153.7 +172.64.155.82 +172.65.202.19 +172.67.152.113 +172.67.155.163 +172.67.159.89 +172.67.191.5 +172.67.222.182 +172.217.214.207 +172.253.155.207 +173.194.42.207 +173.194.64.207 +173.194.193.207 +173.194.194.207 +173.194.195.207 +173.194.206.207 +192.178.129.207 +192.178.210.207 +192.178.212.207 +209.85.145.207 +209.85.200.207 +216.198.53.11 +216.198.54.11 diff --git a/data/ip/discord-itdoginfo-udp-19000-20000.txt b/data/ip/discord-itdoginfo-udp-19000-20000.txt new file mode 100644 index 0000000..5973f74 --- /dev/null +++ b/data/ip/discord-itdoginfo-udp-19000-20000.txt @@ -0,0 +1,10 @@ +# GENERATED tools/sync.py — не редактируйте вручную. +# Источник: itdoginfo/allow-domains, последний релиз +# URL: https://github.com/itdoginfo/allow-domains/releases/latest/download/discord.srs +# Сетей: 1 +# Ограничения: udp 19000-20000 + +!network: udp +!port: 19000-20000 + +104.16.0.0/12 diff --git a/data/ip/discord-itdoginfo-udp-50000-65535.txt b/data/ip/discord-itdoginfo-udp-50000-65535.txt new file mode 100644 index 0000000..cc03138 --- /dev/null +++ b/data/ip/discord-itdoginfo-udp-50000-65535.txt @@ -0,0 +1,17 @@ +# GENERATED tools/sync.py — не редактируйте вручную. +# Источник: itdoginfo/allow-domains, последний релиз +# URL: https://github.com/itdoginfo/allow-domains/releases/latest/download/discord.srs +# Сетей: 8 +# Ограничения: udp 50000-65535 + +!network: udp +!port: 50000-65535 + +5.200.14.128/25 +34.0.0.0/14 +35.192.0.0/11 +66.22.192.0/18 +104.16.0.0/12 +138.128.136.0/21 +162.158.0.0/15 +172.64.0.0/13 diff --git a/data/ip/discord-legiz.txt b/data/ip/discord-legiz.txt new file mode 100644 index 0000000..4eb5ca8 --- /dev/null +++ b/data/ip/discord-legiz.txt @@ -0,0 +1,49 @@ +# GENERATED tools/sync.py — не редактируйте вручную. +# Источник: legiz-ru/sb-rule-sets, адреса голосовых узлов +# URL: https://raw.githubusercontent.com/legiz-ru/sb-rule-sets/refs/heads/main/discord-voice-ip-list.json +# Сетей: 43 +# Ограничения: без ограничений + +35.213.129.2/32 +35.213.130.27/32 +35.213.130.216/31 +35.213.131.78/32 +35.213.133.191/32 +35.213.135.56/32 +35.213.136.41/32 +35.213.136.207/32 +35.213.139.79/32 +35.213.142.228/32 +35.213.142.249/32 +35.213.143.38/32 +35.213.145.124/32 +35.213.145.149/32 +35.213.146.200/32 +35.213.149.32/32 +35.213.149.99/32 +35.213.149.115/32 +35.213.150.41/32 +35.213.150.163/32 +35.213.152.13/32 +35.213.153.185/32 +35.213.160.90/32 +35.213.162.61/32 +35.213.163.235/32 +35.213.164.163/32 +35.213.165.33/32 +35.213.165.102/32 +35.213.167.165/32 +35.213.168.191/32 +35.213.169.179/32 +35.213.170.10/32 +35.213.172.159/32 +35.213.173.1/32 +35.213.176.117/32 +35.213.177.116/32 +35.213.177.119/32 +35.213.181.44/32 +35.213.181.71/32 +35.213.182.222/32 +35.213.183.188/32 +35.213.185.36/32 +35.213.188.188/32 diff --git a/data/ip/discord-vernette-udp.txt b/data/ip/discord-vernette-udp.txt new file mode 100644 index 0000000..f681cb2 --- /dev/null +++ b/data/ip/discord-vernette-udp.txt @@ -0,0 +1,22 @@ +# GENERATED tools/sync.py — не редактируйте вручную. +# Источник: vernette/rulesets, голосовой трафик UDP 50000-50099 +# URL: https://raw.githubusercontent.com/vernette/rulesets/refs/heads/master/json/discord-voice-chats.json +# Сетей: 13 +# Ограничения: udp 50000-50099 + +!network: udp +!port: 50000-50099 + +5.200.14.128/25 +34.0.0.0/15 +34.2.0.0/16 +34.3.0.0/23 +34.3.2.0/24 +35.192.0.0/12 +35.208.0.0/12 +35.224.0.0/12 +35.240.0.0/13 +66.22.192.0/18 +138.128.136.0/21 +162.158.0.0/15 +172.64.0.0/13 diff --git a/data/ip/discord-youoops.txt b/data/ip/discord-youoops.txt new file mode 100644 index 0000000..3e3247d --- /dev/null +++ b/data/ip/discord-youoops.txt @@ -0,0 +1,60 @@ +# GENERATED tools/sync.py — не редактируйте вручную. +# Источник: you-oops-dev/ipranges-singbox (диапазоны сетей) +# URL: https://raw.githubusercontent.com/you-oops-dev/ipranges-singbox/refs/heads/main/discord/discord.srs +# Сетей: 54 +# Ограничения: без ограничений + +23.227.38.74/32 +34.126.226.51/32 +104.17.51.93/32 +104.17.117.93/32 +104.18.32.174/32 +104.18.34.249/32 +104.18.37.76/32 +104.18.38.75/32 +104.21.25.51/32 +104.21.72.221/32 +104.21.80.173/32 +104.21.82.160/32 +104.21.89.187/32 +142.251.32.187/32 +142.251.34.91/32 +142.251.35.91/32 +142.251.40.123/32 +142.251.41.27/32 +142.251.45.1/32 +142.251.45.27/32 +142.251.210.155/32 +142.251.214.123/32 +142.251.215.91/32 +142.251.215.187/32 +142.251.215.219/32 +142.251.215.251/32 +142.251.218.27/32 +162.159.128.232/30 +162.159.129.232/30 +162.159.130.232/30 +162.159.133.232/31 +162.159.133.234/32 +162.159.134.232/31 +162.159.134.234/32 +162.159.135.232/31 +162.159.135.234/32 +162.159.136.232/31 +162.159.136.234/32 +162.159.137.232/31 +162.159.137.234/32 +162.159.138.232/31 +162.159.138.234/32 +172.64.149.181/32 +172.64.150.180/32 +172.64.153.7/32 +172.64.155.82/32 +172.65.202.19/32 +172.67.152.113/32 +172.67.155.163/32 +172.67.159.89/32 +172.67.191.5/32 +172.67.222.182/32 +216.198.53.11/32 +216.198.54.11/32 diff --git a/data/ip/discord.txt b/data/ip/discord.txt new file mode 100644 index 0000000..4e68de1 --- /dev/null +++ b/data/ip/discord.txt @@ -0,0 +1,6 @@ +# GENERATED tools/sync.py — не редактируйте вручную. +# Сводная категория: рукописный список плюс всё, что притянул sync. + +include:discord-base +include:discord-legiz +include:discord-youoops diff --git a/tools/build.py b/tools/build.py new file mode 100644 index 0000000..ad900d3 --- /dev/null +++ b/tools/build.py @@ -0,0 +1,1028 @@ +#!/usr/bin/env python3 +"""Собирает geosite/geoip-артефакты из data/ в несколько форматов. + +Два независимых трека: + + data/*.txt — домены → geosite.dat (тег = имя файла) + data/ip/*.txt — IP и CIDR → geoip.dat (тег = имя файла) + +Оба .dat — protobuf v2ray/Xray, ровно то, что кладут рядом с x-ui. +Дополнительно: plain text, JSON, sing-box rule-set, Clash/mihomo, AdGuard +и готовый фрагмент routing для Xray/3x-ui. + +Зависимостей нет — только стандартная библиотека. Внешние бинарники +(sing-box, mihomo) нужны лишь для компиляции .srs/.mrs и опциональны. +""" + +from __future__ import annotations + +import argparse +import hashlib +import ipaddress +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" +IP_SUBDIR = "ip" + +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}(? 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)]) + + +@dataclass(frozen=True) +class Net: + """IP-сеть geoip: упакованный адрес + длина префикса.""" + + packed: bytes + prefix: int + + @property + def version(self) -> int: + return 4 if len(self.packed) == 4 else 6 + + @property + def sort_key(self) -> tuple[int, bytes, int]: + return (len(self.packed), self.packed, self.prefix) + + @property + def network(self) -> ipaddress.IPv4Network | ipaddress.IPv6Network: + return ipaddress.ip_network(str(self)) + + def __str__(self) -> str: + return f"{ipaddress.ip_address(self.packed)}/{self.prefix}" + + +@dataclass(frozen=True) +class Options: + """Ограничения категории: протокол и порты. + + Пустые поля означают «без ограничений». Выразимы только в sing-box и в + routing-правилах Xray: ни geoip.dat, ни Clash-ipcidr портов не знают. + """ + + networks: tuple[str, ...] = () + ports: tuple[tuple[int, int], ...] = () + + @property + def restricted(self) -> bool: + return bool(self.networks or self.ports) + + @property + def xray_ports(self) -> str: + return ",".join(f"{a}-{b}" if a != b else str(a) for a, b in self.ports) + + def __str__(self) -> str: + parts = [] + if self.networks: + parts.append("/".join(self.networks)) + if self.ports: + parts.append(self.xray_ports) + return " ".join(parts) or "без ограничений" + + +def merge_ports(ports: list[tuple[int, int]]) -> tuple[tuple[int, int], ...]: + """Схлопывает пересекающиеся и смежные диапазоны — вывод детерминирован.""" + merged: list[tuple[int, int]] = [] + for start, end in sorted(ports): + if merged and start <= merged[-1][1] + 1: + merged[-1] = (merged[-1][0], max(merged[-1][1], end)) + else: + merged.append((start, end)) + return tuple(merged) + + +# --------------------------------------------------------------------------- # +# Разбор исходников: домены +# --------------------------------------------------------------------------- # + + +def source_path(category: str) -> Path: + return DATA_DIR / f"{category}.txt" + + +def ip_source_path(category: str) -> Path: + return DATA_DIR / IP_SUBDIR / f"{category}.txt" + + +def list_categories() -> list[str]: + return sorted(p.stem for p in DATA_DIR.glob("*.txt")) + + +def list_ip_categories() -> list[str]: + return sorted(p.stem for p in (DATA_DIR / IP_SUBDIR).glob("*.txt")) + + +def strip_comment(raw: str) -> str: + return raw.split("#", 1)[0].strip() + + +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_include(token: str, where: str) -> str: + target = token[len("include:") :] + if not CATEGORY_RE.match(target): + raise BuildError(f"{where}: невалидное имя категории '{target}'") + return target + + +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 = strip_comment(raw) + 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 не поддерживает атрибуты") + entries.append(("include", parse_include(token, where))) + continue + + rule = parse_token(token, where) + entries.append(("rule", Rule(rule.type, rule.value, tuple(sorted(set(attrs)))))) + + return entries + + +def collapse_rules(rules: list[Rule]) -> list[Rule]: + """Убирает домены, уже покрытые суффиксом из этого же набора. + + domain:discord.gg покрывает и сам домен, и любой поддомен, поэтому + full:atlanta1068.discord.gg рядом с ним — чистый балласт. Правила с + атрибутами не трогаем: '@attr' выделяет подмножество, и покрывающий + суффикс без атрибута в это подмножество не входит. + """ + suffixes = {r.value for r in rules if r.type == TYPE_DOMAIN and not r.attrs} + kept = [] + for rule in rules: + if rule.type in (TYPE_DOMAIN, TYPE_FULL) and not rule.attrs: + # У domain: свой же value — это он сам, поэтому смотрим только на + # родителей; у full: суффикс может совпасть с ним целиком. + parent = rule.value if rule.type == TYPE_FULL else rule.value.partition(".")[2] + while parent: + if parent in suffixes: + break + parent = parent.partition(".")[2] + if parent: + continue + kept.append(rule) + return sorted(set(kept), key=lambda r: r.sort_key) + + +def collapse_nets(nets: list[Net]) -> list[Net]: + """Убирает сети, вложенные в другие сети этого же набора.""" + kept: list[Net] = [] + for version in (4, 6): + ordered = sorted( + {n for n in nets if n.version == version}, + key=lambda n: (int.from_bytes(n.packed, "big"), n.prefix), + ) + # CIDR-сети либо вложены, либо не пересекаются, поэтому в таком порядке + # покрывающая всегда идёт раньше покрытых — хватает одной «текущей». + current: ipaddress.IPv4Network | ipaddress.IPv6Network | None = None + for net in ordered: + if current is not None and net.network.subnet_of(current): # type: ignore[arg-type] + continue + kept.append(net) + current = net.network + return sorted(kept, key=lambda n: n.sort_key) + + +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 collapse_rules(list(collected)) + + +# --------------------------------------------------------------------------- # +# Разбор исходников: IP +# --------------------------------------------------------------------------- # + + +def parse_net(token: str, where: str) -> Net: + try: + network = ipaddress.ip_network(token, strict=True) + except ValueError as exc: + raise BuildError(f"{where}: невалидная сеть '{token}': {exc}") from exc + return Net(network.network_address.packed, network.prefixlen) + + +def parse_directive(line: str, where: str) -> tuple[str, list[str]]: + key, sep, value = line[1:].partition(":") + if not sep: + raise BuildError(f"{where}: директива без значения, ожидалось '!ключ: значение'") + values = [v for v in value.replace(",", " ").split() if v] + if not values: + raise BuildError(f"{where}: пустое значение директивы '!{key.strip()}'") + return key.strip().lower(), values + + +def parse_ip_category(category: str) -> tuple[list[tuple[str, Net | str]], Options]: + """Возвращает записи файла и его ограничения (!network / !port).""" + path = ip_source_path(category) + if not path.is_file(): + raise BuildError(f"IP-категория '{category}' не найдена: {path}") + + entries: list[tuple[str, Net | str]] = [] + networks: list[str] = [] + ports: list[tuple[int, int]] = [] + + for lineno, raw in enumerate(path.read_text(encoding="utf-8").splitlines(), 1): + line = strip_comment(raw) + if not line: + continue + where = f"{IP_SUBDIR}/{path.name}:{lineno}" + + if line.startswith("!"): + key, values = parse_directive(line, where) + if key == "network": + for value in values: + if value.lower() not in NETWORKS: + raise BuildError(f"{where}: !network принимает tcp/udp, получено '{value}'") + networks.append(value.lower()) + elif key == "port": + for value in values: + match = PORT_RE.match(value) + if not match: + raise BuildError(f"{where}: невалидный порт '{value}', ожидалось N или A-B") + start = int(match.group(1)) + end = int(match.group(2) or start) + if not 0 < start <= end <= 65535: + raise BuildError(f"{where}: порт вне диапазона 1..65535: '{value}'") + ports.append((start, end)) + else: + raise BuildError(f"{where}: неизвестная директива '!{key}'") + continue + + token, *rest = line.split() + if rest: + raise BuildError(f"{where}: лишнее после сети: '{' '.join(rest)}'") + + if token.startswith("include:"): + entries.append(("include", parse_include(token, where))) + continue + + entries.append(("net", parse_net(token, where))) + + options = Options(tuple(sorted(set(networks))), merge_ports(ports)) + return entries, options + + +def resolve_ip(category: str, stack: tuple[str, ...] = ()) -> tuple[list[Net], Options]: + if category in stack: + chain = " -> ".join([*stack, category]) + raise BuildError(f"циклический include: {chain}") + + entries, options = parse_ip_category(category) + collected: dict[Net, None] = {} + for kind, payload in entries: + if kind == "include": + nets, child = resolve_ip(str(payload), (*stack, category)) + # Ограничения не наследуются и не объединяются: слить «udp:50000-50099» + # с безусловным списком можно только расширив одно из них — молча + # менять смысл правила нельзя. + if child != options: + raise BuildError( + f"{IP_SUBDIR}/{category}.txt: include:{payload} — ограничения не совпадают " + f"({options} против {child}); держите такие списки разными категориями" + ) + for net in nets: + collected.setdefault(net, None) + else: + collected.setdefault(payload, None) # type: ignore[arg-type] + + return collapse_nets(list(collected)), options + + +# --------------------------------------------------------------------------- # +# protobuf: geosite.dat / geoip.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 _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 _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}") + + +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()} + + 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 encode_geoip_dat(categories: dict[str, list[Net]]) -> bytes: + """GeoIPList{ repeated GeoIP entry = 1 }, GeoIP{ code = 1; repeated CIDR cidr = 2 }.""" + out = bytearray() + for name in sorted(categories): + entry = bytearray(_str_field(1, name.upper())) # GeoIP.country_code + for net in categories[name]: + cidr = bytearray(_len_field(1, net.packed)) # CIDR.ip + if net.prefix: # proto3 опускает 0 + cidr += _varint_field(2, net.prefix) # CIDR.prefix + entry += _len_field(2, bytes(cidr)) + out += _len_field(1, bytes(entry)) + return bytes(out) + + +def decode_geoip_dat(blob: bytes) -> dict[str, list[Net]]: + result: dict[str, list[Net]] = {} + for field, payload in _fields(blob): + if field != 1: + continue + name, nets = "", [] + for f, p in _fields(payload): + if f == 1: + name = p.decode("utf-8") + elif f == 2: + packed, prefix = b"", 0 + for cf, cp in _fields(p): + if cf == 1: + packed = cp + elif cf == 2: + prefix = cp + nets.append(Net(packed, prefix)) + result[name] = nets + return result + + +# --------------------------------------------------------------------------- # +# Текстовые форматы +# --------------------------------------------------------------------------- # + + +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 = "#", track: str = "") -> str: + source = f"data/{track}{category}.txt" + return ( + f"{comment} discord-geodata — категория: {category}\n" + f"{comment} Правил: {count}. Файл генерируется автоматически, не редактируйте вручную.\n" + f"{comment} Источник: {source}\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) + + +def render_ip_plain(nets: list[Net]) -> str: + return "".join(f"{net}\n" for net in nets) + + +def render_ip_source(category: str, nets: list[Net], options: Options) -> str: + lines = [header(category, len(nets), track=f"{IP_SUBDIR}/"), "\n"] + for network in options.networks: + lines.append(f"!network: {network}\n") + if options.ports: + lines.append(f"!port: {options.xray_ports}\n") + if options.restricted: + lines.append("\n") + lines += [f"{net}\n" for net in nets] + return "".join(lines) + + +def render_ip_json(category: str, nets: list[Net], options: Options) -> str: + payload: dict[str, object] = {"category": category, "count": len(nets)} + if options.networks: + payload["network"] = list(options.networks) + if options.ports: + payload["ports"] = [f"{a}-{b}" if a != b else str(a) for a, b in options.ports] + payload["cidr"] = [str(net) for net in nets] + return json.dumps(payload, ensure_ascii=False, indent=2) + "\n" + + +def render_ip_singbox(nets: list[Net], options: Options) -> str: + rule: dict[str, object] = {} + if options.networks: + rule["network"] = list(options.networks) + if nets: + rule["ip_cidr"] = [str(net) for net in nets] + ports = [a for a, b in options.ports if a == b] + ranges = [f"{a}:{b}" for a, b in options.ports if a != b] + if ports: + rule["port"] = ports + if ranges: + rule["port_range"] = ranges + return json.dumps({"version": 1, "rules": [rule] if nets else []}, indent=2) + "\n" + + +def render_ip_clash_yaml(category: str, nets: list[Net], options: Options) -> str: + lines = [header(category, len(nets), track=f"{IP_SUBDIR}/")] + if options.restricted: + lines.append(f"# Ограничения ({options}) в behavior: ipcidr не выразимы и здесь потеряны.\n") + lines.append("payload:\n") + lines += [f" - '{net}'\n" for net in nets] + return "".join(lines) + + +def render_ip_clash_list(category: str, nets: list[Net], options: Options) -> str: + lines = [header(category, len(nets), track=f"{IP_SUBDIR}/")] + if options.restricted: + lines.append(f"# Ограничения ({options}) в classical-правилах не выразимы и здесь потеряны.\n") + # no-resolve: правило по IP не должно заставлять резолвить домен заранее. + lines += [f"IP-CIDR{'6' if net.version == 6 else ''},{net},no-resolve\n" for net in nets] + return "".join(lines) + + +def essential(items: dict[str, list], collapse) -> list[str]: + """Отбрасывает категории, целиком покрытые другой категорией. + + В routing.json нет смысла перечислять и сводную категорию, и её + составляющие: одно правило Xray просто перекрывает другое. + """ + + def covers(outer: str, inner: str) -> bool: + return collapse(items[outer] + items[inner]) == collapse(items[outer]) + + kept = [] + for name in sorted(items): + if not items[name]: + continue + # При взаимном покрытии (списки совпали) оставляем первый по алфавиту — + # иначе категории вычеркнули бы друг друга. + if any( + other != name + and items[other] + and covers(other, name) + and (not covers(name, other) or other < name) + for other in items + ): + continue + kept.append(name) + return kept + + +def render_xray_routing( + site: dict[str, list[Rule]], + ip: dict[str, tuple[list[Net], Options]], + site_file: str, + ip_file: str, + outbound: str, +) -> str: + """Готовый фрагмент routing для 3x-ui: вставляется в конфиг как есть.""" + rules: list[dict[str, object]] = [] + for name in essential(site, collapse_rules): + rules.append( + { + "type": "field", + "domain": [f"ext:{site_file}:{name}"], + "outboundTag": outbound, + } + ) + + # Сравнивать на покрытие можно только внутри одинаковых ограничений: + # те же сети под «udp 50000-50099» — это совсем другое правило. + by_options: dict[Options, dict[str, list[Net]]] = {} + for name, (nets, options) in ip.items(): + by_options.setdefault(options, {})[name] = nets + + # Сначала безусловные списки, затем ограниченные по протоколу и портам: + # Xray берёт первое подошедшее правило, порядок здесь — это приоритет. + for restricted in (False, True): + for options in sorted(by_options, key=lambda o: (o.networks, o.ports)): + if options.restricted != restricted: + continue + for name in essential(by_options[options], collapse_nets): + rule: dict[str, object] = {"type": "field"} + if options.networks: + rule["network"] = ",".join(options.networks) + if options.ports: + rule["port"] = options.xray_ports + rule["ip"] = [f"ext:{ip_file}:{name}"] + rule["outboundTag"] = outbound + rules.append(rule) + + payload = { + "_note": [ + f"Фрагмент для Xray/3x-ui. Положите {site_file} и {ip_file} рядом с бинарём", + "(обычно /usr/local/x-ui/bin) и подставьте свой outboundTag.", + "Правила ниже перечислены от общих к ограниченным по портам.", + ], + "routing": {"domainStrategy": "IPIfNonMatch", "rules": rules}, + } + return json.dumps(payload, ensure_ascii=False, indent=2) + "\n" + + +# --------------------------------------------------------------------------- # +# Компиляция бинарных rule-set (опционально) +# --------------------------------------------------------------------------- # + + +def compile_binaries( + out_dir: Path, + categories: list[str], + ip_categories: list[str], + singbox: str | None, + mihomo: str | None, +) -> list[str]: + produced = [] + + if singbox: + for track, names in (("site", categories), (IP_SUBDIR, ip_categories)): + for category in names: + src = out_dir / "sing-box" / track / f"{category}.json" + dst = src.with_suffix(".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 track, names, behavior in ( + ("site", categories, "domain"), + (IP_SUBDIR, ip_categories, "ipcidr"), + ): + for category in names: + src = out_dir / "clash" / track / f"{category}.yaml" + dst = out_dir / "mihomo" / track / f"{category}.mrs" + dst.parent.mkdir(parents=True, exist_ok=True) + subprocess.run( + [mihomo, "convert-ruleset", behavior, "yaml", str(src), str(dst)], check=True + ) + produced.append(str(dst.relative_to(out_dir))) + else: + print(" mihomo не найден — .mrs пропущены", file=sys.stderr) + + return produced + + +# --------------------------------------------------------------------------- # +# Точка входа +# --------------------------------------------------------------------------- # + +SITE_DAT = "discord-geosite.dat" +IP_DAT = "discord-geoip.dat" + + +def build(out_dir: Path, singbox: str | None, mihomo: str | None, outbound: str) -> int: + categories = list_categories() + ip_categories = list_ip_categories() + if not categories and not ip_categories: + raise BuildError(f"в {DATA_DIR} нет ни одного .txt") + + site = {name: resolve(name) for name in categories} + ip = {name: resolve_ip(name) for name in ip_categories} + + if out_dir.exists(): + shutil.rmtree(out_dir) + out_dir.mkdir(parents=True) + + site_dat = encode_geosite_dat(site) + ip_dat = encode_geoip_dat({name: nets for name, (nets, _) in ip.items()}) + + # Штатные имена — чтобы файл можно было подсунуть вместо системного, + # и копии с префиксом — чтобы положить рядом и адресовать через ext:. + for name, blob in ( + ("geosite.dat", site_dat), + (SITE_DAT, site_dat), + ("geoip.dat", ip_dat), + (IP_DAT, ip_dat), + ): + (out_dir / name).write_bytes(blob) + digest = hashlib.sha256(blob).hexdigest() + write(out_dir / f"{name}.sha256sum", f"{digest} {name}\n") + + # Самопроверка: файлы должны читаться обратно ровно в то, что мы записали. + if decode_geosite_dat(site_dat) != {n.upper(): r for n, r in site.items()}: + raise BuildError("geosite.dat не проходит round-trip проверку") + if decode_geoip_dat(ip_dat) != {n.upper(): nets for n, (nets, _) in ip.items()}: + raise BuildError("geoip.dat не проходит round-trip проверку") + + for name, rules in site.items(): + write(out_dir / "txt" / "site" / f"{name}.txt", render_plain(rules)) + write(out_dir / "src" / "site" / f"{name}.txt", render_source(name, rules)) + write(out_dir / "json" / "site" / f"{name}.json", render_json(name, rules)) + write(out_dir / "sing-box" / "site" / f"{name}.json", render_singbox(rules)) + write(out_dir / "clash" / "site" / f"{name}.yaml", render_clash_yaml(name, rules)) + write(out_dir / "clash" / "site" / f"{name}.list", render_clash_list(name, rules)) + write(out_dir / "adguard" / f"{name}.txt", render_adguard(name, rules)) + + for name, (nets, options) in ip.items(): + write(out_dir / "txt" / IP_SUBDIR / f"{name}.txt", render_ip_plain(nets)) + write(out_dir / "src" / IP_SUBDIR / f"{name}.txt", render_ip_source(name, nets, options)) + write(out_dir / "json" / IP_SUBDIR / f"{name}.json", render_ip_json(name, nets, options)) + write(out_dir / "sing-box" / IP_SUBDIR / f"{name}.json", render_ip_singbox(nets, options)) + write(out_dir / "clash" / IP_SUBDIR / f"{name}.yaml", render_ip_clash_yaml(name, nets, options)) + write(out_dir / "clash" / IP_SUBDIR / f"{name}.list", render_ip_clash_list(name, nets, options)) + + write(out_dir / "xray" / "routing.json", render_xray_routing(site, ip, SITE_DAT, IP_DAT, outbound)) + + compile_binaries(out_dir, categories, ip_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": hashlib.sha256(site_dat).hexdigest(), + "geosite_dat_size": len(site_dat), + "geoip_dat_sha256": hashlib.sha256(ip_dat).hexdigest(), + "geoip_dat_size": len(ip_dat), + "total_domains": sum(len(r) for r in site.values()), + "total_networks": sum(len(nets) for nets, _ in ip.values()), + "categories": {name: len(rules) for name, rules in site.items()}, + "ip_categories": { + name: { + "count": len(nets), + **({"network": list(options.networks)} if options.networks else {}), + **({"ports": options.xray_ports} if options.ports else {}), + } + for name, (nets, options) in ip.items() + }, + }, + ensure_ascii=False, + indent=2, + ) + + "\n", + ) + + # Скрипт обновления едет вместе с артефактами: на сервере тогда нужен + # ровно один базовый URL — и для .dat, и для самого апдейтера. + updater = Path(__file__).with_name("update-geodata.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"{SITE_DAT}: {len(site_dat)} байт, {len(site)} категорий") + for name in categories: + print(f" {name:<24} {len(site[name]):>5} правил") + print(f"{IP_DAT}: {len(ip_dat)} байт, {len(ip)} категорий") + for name in ip_categories: + nets, options = ip[name] + suffix = "" if not options.restricted else f" [{options}]" + print(f" {name:<24} {len(nets):>5} сетей{suffix}") + print(f"Готово: {out_dir}") + return 0 + + +def check() -> int: + categories = list_categories() + ip_categories = list_ip_categories() + if not categories and not ip_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-]") + seen: dict[Rule, None] = {} + for kind, payload in parse_category(name): + if kind != "rule": + continue + rule: Rule = payload # type: ignore[assignment] + if rule in seen: + problems.append(f"{name}.txt: дубликат '{rule}'") + seen[rule] = None + 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 name in ip_categories: + if not CATEGORY_RE.match(name): + problems.append(f"{IP_SUBDIR}/{name}: имя категории должно быть [a-z0-9-]") + entries, _ = parse_ip_category(name) + seen_nets: dict[Net, None] = {} + for kind, payload in entries: + if kind != "net": + continue + net: Net = payload # type: ignore[assignment] + if net in seen_nets: + problems.append(f"{IP_SUBDIR}/{name}.txt: дубликат '{net}'") + seen_nets[net] = None + by_version: dict[int, list[Net]] = {} + for net in seen_nets: + by_version.setdefault(net.version, []).append(net) + for nets in by_version.values(): + # Сети отсортированы по префиксу: каждую сравниваем только с более + # широкими, поэтому пара «покрывающая — покрытая» находится один раз. + ordered = sorted(nets, key=lambda n: n.prefix) + for index, net in enumerate(ordered): + for wider in ordered[:index]: + if net.network.subnet_of(wider.network): # type: ignore[arg-type] + problems.append(f"{IP_SUBDIR}/{name}.txt: '{net}' уже покрыт '{wider}'") + break + resolve_ip(name) # ловит циклы, битые include и конфликт ограничений + + for problem in problems: + print(f"error: {problem}", file=sys.stderr) + if problems: + return 1 + print(f"ok: {len(categories)} доменных и {len(ip_categories)} IP-категорий, замечаний нет") + 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") + parser.add_argument("--outbound", default="proxy", help="outboundTag в xray/routing.json") + 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"), + args.outbound, + ) + except BuildError as exc: + print(f"error: {exc}", file=sys.stderr) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/fetch-tools.sh b/tools/fetch-tools.sh new file mode 100644 index 0000000..b3e95df --- /dev/null +++ b/tools/fetch-tools.sh @@ -0,0 +1,48 @@ +#!/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 diff --git a/tools/gitea-release.sh b/tools/gitea-release.sh new file mode 100644 index 0000000..022d491 --- /dev/null +++ b/tools/gitea-release.sh @@ -0,0 +1,50 @@ +#!/bin/sh +# Создаёт (или обновляет) релиз в Gitea и заливает в него файлы. +# +# GITEA_URL=https://gitea.hsrv.site GITEA_REPO=mrleo1nid/discord-geodata GITEA_TOKEN=… \ +# ./tools/gitea-release.sh <тег> <файл-с-описанием> <файл> [файл …] +# +# Имя ассета — путь относительно dist/ с заменой "/" на "-", чтобы +# sing-box/site/discord.json и json/site/discord.json не конфликтовали. + +set -eu + +[ $# -ge 3 ] || { echo "использование: $0 <тег> <файл...>" >&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 diff --git a/tools/sources.json b/tools/sources.json new file mode 100644 index 0000000..abc33f2 --- /dev/null +++ b/tools/sources.json @@ -0,0 +1,43 @@ +{ + "_note": [ + "Апстримы, из которых tools/sync.py собирает data/*-.txt.", + "kind определяется по расширению: .json — исходник sing-box rule-set,", + ".srs — скомпилированный, для него нужен бинарь sing-box (rule-set decompile).", + "json_url — необязательный текстовый близнец того же списка: если он есть,", + "sync обходится без sing-box." + ], + "aggregate": "discord", + "sources": [ + { + "name": "metacubex", + "note": "MetaCubeX meta-rules-dat, ветка sing (домены)", + "url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/refs/heads/sing/geo/geosite/discord.srs", + "json_url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/refs/heads/sing/geo/geosite/discord.json", + "target": "discord-metacubex" + }, + { + "name": "itdoginfo", + "note": "itdoginfo/allow-domains, последний релиз", + "url": "https://github.com/itdoginfo/allow-domains/releases/latest/download/discord.srs", + "target": "discord-itdoginfo" + }, + { + "name": "you-oops", + "note": "you-oops-dev/ipranges-singbox (диапазоны сетей)", + "url": "https://raw.githubusercontent.com/you-oops-dev/ipranges-singbox/refs/heads/main/discord/discord.srs", + "target": "discord-youoops" + }, + { + "name": "legiz", + "note": "legiz-ru/sb-rule-sets, адреса голосовых узлов", + "url": "https://raw.githubusercontent.com/legiz-ru/sb-rule-sets/refs/heads/main/discord-voice-ip-list.json", + "target": "discord-legiz" + }, + { + "name": "vernette", + "note": "vernette/rulesets, голосовой трафик UDP 50000-50099", + "url": "https://raw.githubusercontent.com/vernette/rulesets/refs/heads/master/json/discord-voice-chats.json", + "target": "discord-vernette" + } + ] +} diff --git a/tools/sync.py b/tools/sync.py new file mode 100644 index 0000000..86ba6ca --- /dev/null +++ b/tools/sync.py @@ -0,0 +1,387 @@ +#!/usr/bin/env python3 +"""Синхронизирует data/ с апстримными rule-set из tools/sources.json. + +Каждый источник кладётся в свой файл — data/.txt для доменов и +data/ip/.txt для сетей, — а сводные категории (data/discord.txt, +data/ip/discord.txt) пересобираются как список include-ов. Рукописные +списки (data/discord-base.txt) остаются нетронутыми: sync перезаписывает +только файлы со своей шапкой GENERATED. + + python3 tools/sync.py # синхронизировать всё + python3 tools/sync.py --only legiz # только один источник + python3 tools/sync.py --strict # падать, если хоть один источник недоступен + +Источники в формате .srs скомпилированы: для них нужен бинарь sing-box +(`rule-set decompile`). Если его нет — источник пропускается, а ранее +синхронизированный файл остаётся на месте: устаревшие данные лучше пустых. +""" + +from __future__ import annotations + +import argparse +import ipaddress +import json +import shutil +import subprocess +import sys +import tempfile +import urllib.error +import urllib.request +from dataclasses import dataclass, field +from pathlib import Path + +import build +from build import BuildError, Net, Options, Rule + +TOOLS = Path(__file__).resolve().parent +USER_AGENT = "discord-geodata-sync (+https://github.com/)" +TIMEOUT = 60 + +GENERATED_HEADER = f"# {build.GENERATED_MARK} tools/sync.py — не редактируйте вручную." + + +class SyncError(Exception): + """Источник не удалось получить или разобрать.""" + + +@dataclass +class Parsed: + domains: list[Rule] = field(default_factory=list) + groups: dict[Options, list[Net]] = field(default_factory=dict) + skipped: list[str] = field(default_factory=list) + + +# --------------------------------------------------------------------------- # +# Получение +# --------------------------------------------------------------------------- # + + +def fetch(url: str) -> bytes: + request = urllib.request.Request(url, headers={"User-Agent": USER_AGENT}) + try: + with urllib.request.urlopen(request, timeout=TIMEOUT) as response: + return response.read() + except (urllib.error.URLError, OSError, TimeoutError) as exc: + raise SyncError(f"не удалось скачать {url}: {exc}") from exc + + +def decompile_srs(blob: bytes, singbox: str | None) -> dict: + """.srs — это zlib-контейнер sing-box; читать его умеет только сам sing-box.""" + if not singbox: + raise SyncError("формат .srs требует бинаря sing-box (--sing-box или в PATH)") + with tempfile.TemporaryDirectory() as tmp: + src = Path(tmp) / "rule-set.srs" + dst = Path(tmp) / "rule-set.json" + src.write_bytes(blob) + result = subprocess.run( + [singbox, "rule-set", "decompile", "--output", str(dst), str(src)], + capture_output=True, + text=True, + ) + if result.returncode != 0: + raise SyncError(f"sing-box rule-set decompile: {result.stderr.strip() or result.stdout.strip()}") + return json.loads(dst.read_text(encoding="utf-8")) + + +def load_source(source: dict, singbox: str | None) -> dict: + url = source.get("json_url") or source["url"] + blob = fetch(url) + if url.endswith(".srs") or blob[:3] == b"SRS": + return decompile_srs(blob, singbox) + try: + return json.loads(blob.decode("utf-8")) + except (UnicodeDecodeError, json.JSONDecodeError) as exc: + raise SyncError(f"{url}: не похоже на JSON rule-set: {exc}") from exc + + +# --------------------------------------------------------------------------- # +# Разбор sing-box rule-set +# --------------------------------------------------------------------------- # + + +def iter_default_rules(rules: list, where: str): + """Разворачивает logical-правила: вложенные условия нас интересуют как список.""" + for rule in rules: + if not isinstance(rule, dict): + raise SyncError(f"{where}: правило не является объектом") + if rule.get("type") == "logical": + yield from iter_default_rules(rule.get("rules", []), where) + else: + yield rule + + +def listify(value: object, where: str, key: str) -> list: + """В sing-box почти любое поле правила — «listable»: строка или массив строк.""" + if value is None: + return [] + if isinstance(value, (str, int)): + return [value] + if isinstance(value, list): + return value + raise SyncError(f"{where}: поле '{key}' неожиданного типа {type(value).__name__}") + + +def rule_field(rule: dict, key: str, where: str) -> list: + return listify(rule.get(key), where, key) + + +def rule_options(rule: dict, where: str) -> Options: + networks = sorted({str(n).lower() for n in rule_field(rule, "network", where)}) + for network in networks: + if network not in build.NETWORKS: + raise SyncError(f"{where}: неизвестный network '{network}'") + + ports: list[tuple[int, int]] = [] + for port in rule_field(rule, "port", where): + ports.append((int(port), int(port))) + for span in rule_field(rule, "port_range", where): + match = build.PORT_RE.match(str(span)) + if not match: + raise SyncError(f"{where}: невалидный port_range '{span}'") + start = int(match.group(1)) + ports.append((start, int(match.group(2) or start))) + + return Options(tuple(networks), build.merge_ports(ports)) + + +def parse_ruleset(payload: dict, where: str) -> Parsed: + if not isinstance(payload, dict) or "rules" not in payload: + raise SyncError(f"{where}: нет ключа 'rules'") + + parsed = Parsed() + domains: dict[Rule, None] = {} + + for index, rule in enumerate(iter_default_rules(payload["rules"], where)): + spot = f"{where}#{index}" + if rule.get("invert"): + parsed.skipped.append(f"{spot}: invert-правило не выразимо в geosite/geoip") + continue + + for key, template in ( + ("domain", "full:{}"), + ("domain_suffix", "{}"), + ("domain_keyword", "keyword:{}"), + ("domain_regex", "regexp:{}"), + ): + for value in rule_field(rule, key, spot): + # Ведущая точка в sing-box значит «только поддомены», а в geosite + # суффикс всегда покрывает и сам домен — точку просто снимаем. + token = template.format(str(value).lstrip(".") if key == "domain_suffix" else value) + try: + domains.setdefault(build.parse_token(token, spot), None) + except BuildError as exc: + parsed.skipped.append(str(exc)) + + cidrs: list[Net] = [] + for value in rule_field(rule, "ip_cidr", spot): + try: + network = ipaddress.ip_network(str(value), strict=False) + except ValueError as exc: + parsed.skipped.append(f"{spot}: невалидная сеть '{value}': {exc}") + continue + cidrs.append(Net(network.network_address.packed, network.prefixlen)) + + for key in ("source_ip_cidr", "source_port", "source_port_range"): + if rule.get(key): + parsed.skipped.append(f"{spot}: '{key}' игнорируется") + + if cidrs: + parsed.groups.setdefault(rule_options(rule, spot), []).extend(cidrs) + + parsed.domains = sorted(domains, key=lambda r: r.sort_key) + return parsed + + +# --------------------------------------------------------------------------- # +# Чистка +# --------------------------------------------------------------------------- # + + +# Чистка внутри файла — та же, что build применяет к развёрнутой категории: +# покрытые суффиксом домены и вложенные сети до .dat всё равно не доедут. +prune_domains = build.collapse_rules +prune_nets = build.collapse_nets + + +# --------------------------------------------------------------------------- # +# Запись +# --------------------------------------------------------------------------- # + + +def is_generated(path: Path) -> bool: + if not path.is_file(): + return True + head = path.read_text(encoding="utf-8").splitlines()[:3] + return any(build.GENERATED_MARK in line for line in head) + + +def write_generated(path: Path, notes: list[str], body: list[str]) -> None: + if not is_generated(path): + raise SyncError(f"{path} создан вручную — sync его не трогает") + content = "\n".join([GENERATED_HEADER, *(f"# {n}" for n in notes), "", *body, ""]) + build.write(path, content) + + +def short_suffix(options: Options) -> str: + return "-".join(options.networks) or "port" + + +def long_suffix(options: Options) -> str: + """Различает наборы, у которых совпал протокол, но разошлись порты.""" + ports = "-".join(f"{a}" if a == b else f"{a}-{b}" for a, b in options.ports) + return "-".join(part for part in (short_suffix(options), ports) if part) + + +def group_names(groups: list[Options], target: str) -> dict[Options, str]: + """Имя категории на группу: короткое, а при совпадении — с портами.""" + taken = [short_suffix(o) for o in groups] + names = {} + for options in groups: + suffix = short_suffix(options) + if taken.count(suffix) > 1: + suffix = long_suffix(options) + names[options] = f"{target}-{suffix}" + if len(set(names.values())) != len(names): + raise SyncError(f"{target}: не удалось развести имена категорий по ограничениям") + return names + + +def sync_source(source: dict, singbox: str | None) -> tuple[list[Path], list[str]]: + """Возвращает записанные файлы и предупреждения.""" + target = source["target"] + note = source.get("note", source["name"]) + url = source.get("json_url") or source["url"] + + parsed = parse_ruleset(load_source(source, singbox), source["name"]) + + written: list[Path] = [] + domains = prune_domains(parsed.domains) + if domains: + write_generated( + build.DATA_DIR / f"{target}.txt", + [f"Источник: {note}", f"URL: {url}", f"Правил: {len(domains)}"], + [str(rule) for rule in domains], + ) + written.append(build.DATA_DIR / f"{target}.txt") + + ordered = sorted(parsed.groups, key=lambda o: (o.networks, o.ports)) + names = group_names([o for o in ordered if o.restricted], target) + for options in ordered: + nets = prune_nets(parsed.groups[options]) + if not nets: + continue + name = names[options] if options.restricted else target + + directives = [f"!network: {n}" for n in options.networks] + if options.ports: + directives.append(f"!port: {options.xray_ports}") + path = build.DATA_DIR / build.IP_SUBDIR / f"{name}.txt" + write_generated( + path, + [f"Источник: {note}", f"URL: {url}", f"Сетей: {len(nets)}", f"Ограничения: {options}"], + [*directives, *([""] if directives else []), *(str(net) for net in nets)], + ) + written.append(path) + + return written, parsed.skipped + + +def drop_stale(target: str, written: set[Path]) -> list[Path]: + """Убирает файлы источника, которые в этот раз не появились (апстрим сузился).""" + removed = [] + for directory in (build.DATA_DIR, build.DATA_DIR / build.IP_SUBDIR): + for path in sorted(directory.glob(f"{target}*.txt")): + stem = path.stem + if stem != target and not stem.startswith(f"{target}-"): + continue + if path in written or not is_generated(path): + continue + path.unlink() + removed.append(path) + return removed + + +def write_aggregates(aggregate: str) -> None: + """Сводные категории — просто список include-ов по тому, что есть на диске.""" + for directory, restrict in ((build.DATA_DIR, False), (build.DATA_DIR / build.IP_SUBDIR, True)): + names = [] + for path in sorted(directory.glob("*.txt")): + name = path.stem + if name == aggregate: + continue + if restrict: + # В сводную попадают только безусловные списки: подмешать к ним + # «udp + порты 50000-50099» значило бы снять это ограничение с + # чужих сетей (там половина Cloudflare) — такие категории живут + # отдельными тегами. + _, options = build.resolve_ip(name) + if options.restricted: + continue + names.append(name) + if not names: + continue + write_generated( + directory / f"{aggregate}.txt", + ["Сводная категория: рукописный список плюс всё, что притянул sync."], + [f"include:{name}" for name in names], + ) + + +# --------------------------------------------------------------------------- # +# Точка входа +# --------------------------------------------------------------------------- # + + +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("--sources", type=Path, default=TOOLS / "sources.json") + parser.add_argument("--only", action="append", default=[], help="синхронизировать только эти источники") + parser.add_argument("--sing-box", dest="singbox", help="путь к бинарю sing-box для .srs") + parser.add_argument("--strict", action="store_true", help="ненулевой код возврата при любой ошибке") + args = parser.parse_args(argv) + + config = json.loads(args.sources.read_text(encoding="utf-8")) + singbox = args.singbox or shutil.which("sing-box") + sources = [s for s in config["sources"] if not args.only or s["name"] in args.only] + if not sources: + print("error: подходящих источников нет", file=sys.stderr) + return 1 + + failed = [] + for source in sources: + try: + written, skipped = sync_source(source, singbox) + removed = drop_stale(source["target"], set(written)) + files = ", ".join(p.relative_to(build.ROOT).as_posix() for p in written) or "нет данных" + print(f"{source['name']}: {files}") + for path in removed: + print(f" удалён устаревший {path.relative_to(build.ROOT).as_posix()}") + for warning in skipped[:10]: + print(f" пропущено — {warning}") + if len(skipped) > 10: + print(f" … и ещё {len(skipped) - 10} пропущенных записей") + except (SyncError, BuildError) as exc: + failed.append(source["name"]) + print(f"{source['name']}: ОШИБКА — {exc}", file=sys.stderr) + print(" оставлены прежние данные", file=sys.stderr) + + try: + write_aggregates(config["aggregate"]) + except (SyncError, BuildError) as exc: + print(f"error: не удалось пересобрать сводные категории: {exc}", file=sys.stderr) + return 1 + + if failed: + print(f"источников с ошибкой: {len(failed)} из {len(sources)} ({', '.join(failed)})", file=sys.stderr) + if args.strict or len(failed) == len(sources): + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/test_build.py b/tools/test_build.py new file mode 100644 index 0000000..a727dbb --- /dev/null +++ b/tools/test_build.py @@ -0,0 +1,414 @@ +#!/usr/bin/env python3 +"""Тесты сборщика и синхронизатора: python -m unittest discover -s tools -t tools""" + +from __future__ import annotations + +import contextlib +import ipaddress +import json +import tempfile +import unittest +from pathlib import Path + +import build +import sync +from build import BuildError, Net, Options, Rule + + +def net(cidr: str) -> Net: + parsed = ipaddress.ip_network(cidr) + return Net(parsed.network_address.packed, parsed.prefixlen) + + +@contextlib.contextmanager +def data(ip: dict[str, str] | None = None, **files: str): + """Подменяет data/ временным каталогом с заданными категориями.""" + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + (root / build.IP_SUBDIR).mkdir() + for name, content in files.items(): + (root / f"{name.replace('_', '-')}.txt").write_text(content, encoding="utf-8") + for name, content in (ip or {}).items(): + path = root / build.IP_SUBDIR / f"{name.replace('_', '-')}.txt" + path.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 IPParseTest(unittest.TestCase): + def test_bare_address_becomes_host_route(self): + with data(ip={"a": "1.2.3.4\n2001:db8::1\n"}): + nets, options = build.resolve_ip("a") + self.assertEqual([str(n) for n in nets], ["1.2.3.4/32", "2001:db8::1/128"]) + self.assertFalse(options.restricted) + + def test_directives(self): + with data(ip={"a": "!network: udp\n!port: 50000-50099, 443\n1.2.3.0/24\n"}): + _, options = build.resolve_ip("a") + self.assertEqual(options.networks, ("udp",)) + self.assertEqual(options.ports, ((443, 443), (50000, 50099))) + self.assertEqual(options.xray_ports, "443,50000-50099") + + def test_adjacent_port_ranges_merge(self): + self.assertEqual(build.merge_ports([(10, 20), (21, 30), (100, 100)]), ((10, 30), (100, 100))) + + def test_rejects_bad_input(self): + cases = { + "не сеть": "1.2.3.999\n", + "заданы хостовые биты": "1.2.3.4/24\n", + "чужой протокол": "!network: icmp\n1.2.3.4\n", + "порт вне диапазона": "!port: 70000\n1.2.3.4\n", + "неизвестная директива": "!ttl: 5\n1.2.3.4\n", + } + for label, source in cases.items(): + with self.subTest(label), data(ip={"a": source}): + with self.assertRaises(BuildError): + build.resolve_ip("a") + + def test_include_requires_matching_options(self): + with data(ip={"a": "!network: udp\ninclude:b\n", "b": "1.2.3.4\n"}): + with self.assertRaisesRegex(BuildError, "ограничения не совпадают"): + build.resolve_ip("a") + + def test_include_with_same_options_is_allowed(self): + source = "!network: udp\n!port: 100-200\n" + with data(ip={"a": source + "include:b\n1.1.1.1\n", "b": source + "2.2.2.2\n"}): + nets, options = build.resolve_ip("a") + self.assertEqual([str(n) for n in nets], ["1.1.1.1/32", "2.2.2.2/32"]) + self.assertEqual(options, Options(("udp",), ((100, 200),))) + + +class CollapseTest(unittest.TestCase): + def test_subdomains_are_dropped_under_suffix(self): + rules = [Rule("domain", "a.com"), Rule("full", "x.a.com"), Rule("domain", "deep.sub.a.com")] + self.assertEqual(build.collapse_rules(rules), [Rule("domain", "a.com")]) + + def test_attributed_rules_survive(self): + rules = [Rule("domain", "a.com"), Rule("full", "x.a.com", ("ads",))] + self.assertEqual(len(build.collapse_rules(rules)), 2) + + def test_nested_networks_are_dropped(self): + nets = [net("10.0.0.0/8"), net("10.1.2.3/32"), net("11.0.0.0/8"), net("2001:db8::/32")] + collapsed = [str(n) for n in build.collapse_nets(nets)] + self.assertEqual(collapsed, ["10.0.0.0/8", "11.0.0.0/8", "2001:db8::/32"]) + + def test_collapse_happens_across_includes(self): + with data(a="include:b\nfull:node1.a.com\n", b="a.com\n"): + self.assertEqual(build.resolve("a"), [Rule("domain", "a.com")]) + + +class ProtobufTest(unittest.TestCase): + def test_known_geosite_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_known_geoip_byte_layout(self): + blob = build.encode_geoip_dat({"t": [net("1.2.3.0/24")]}) + # 0a 0d GeoIPList.entry, 13 байт + # 0a 01 54 country_code = "T" + # 12 08 GeoIP.cidr, 8 байт + # 0a 04 01020300 ip = 1.2.3.0 + # 10 18 prefix = 24 + self.assertEqual(blob.hex(), "0a0d" "0a0154" "1208" "0a04" "01020300" "1018") + + 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()}) + + def test_geoip_round_trip_mixed_families(self): + categories = {"one": [net("0.0.0.0/0"), net("1.2.3.4/32")], "two": [net("2001:db8::/32")]} + decoded = build.decode_geoip_dat(build.encode_geoip_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) + + def test_ip_singbox_carries_network_and_ports(self): + options = Options(("udp",), ((443, 443), (50000, 50099))) + rendered = json.loads(build.render_ip_singbox([net("1.2.3.0/24")], options)) + rule = rendered["rules"][0] + self.assertEqual(rule["ip_cidr"], ["1.2.3.0/24"]) + self.assertEqual(rule["network"], ["udp"]) + self.assertEqual(rule["port"], [443]) + self.assertEqual(rule["port_range"], ["50000:50099"]) + + def test_ip_clash_list_marks_family_and_no_resolve(self): + body = build.render_ip_clash_list("t", [net("1.2.3.0/24"), net("2001:db8::/32")], Options()) + self.assertIn("IP-CIDR,1.2.3.0/24,no-resolve", body) + self.assertIn("IP-CIDR6,2001:db8::/32,no-resolve", body) + + def test_xray_routing_orders_general_rules_before_restricted(self): + payload = json.loads( + build.render_xray_routing( + {"site": [Rule("domain", "a.com")]}, + { + "voice": ([net("1.2.3.0/24")], Options(("udp",), ((50000, 50099),))), + "plain": ([net("4.4.4.0/24")], Options()), + }, + "site.dat", + "ip.dat", + "proxy", + ) + ) + rules = payload["routing"]["rules"] + self.assertEqual(rules[0]["domain"], ["ext:site.dat:site"]) + self.assertEqual(rules[1]["ip"], ["ext:ip.dat:plain"]) + self.assertEqual(rules[2]["network"], "udp") + self.assertEqual(rules[2]["port"], "50000-50099") + self.assertTrue(all(r["outboundTag"] == "proxy" for r in rules)) + + def test_xray_routing_skips_categories_covered_by_another(self): + payload = json.loads( + build.render_xray_routing( + {"all": [Rule("domain", "a.com")], "part": [Rule("full", "x.a.com")]}, + { + "all": ([net("10.0.0.0/8")], Options()), + "part": ([net("10.1.2.3/32")], Options()), + # Те же сети, но под ограничением — это другое правило. + "voice": ([net("10.0.0.0/8")], Options(("udp",), ((50000, 50099),))), + }, + "site.dat", + "ip.dat", + "proxy", + ) + ) + rules = payload["routing"]["rules"] + self.assertEqual( + [r.get("domain", r.get("ip"))[0] for r in rules], + ["ext:site.dat:all", "ext:ip.dat:all", "ext:ip.dat:voice"], + ) + + def test_essential_keeps_one_of_two_identical_categories(self): + items = {"b": [Rule("domain", "a.com")], "a": [Rule("domain", "a.com")]} + self.assertEqual(build.essential(items, build.collapse_rules), ["a"]) + + +class SyncTest(unittest.TestCase): + def test_listable_fields_are_accepted(self): + payload = { + "version": 3, + "rules": [{"network": "udp", "ip_cidr": "1.2.3.0/24", "port_range": "50000:50099"}], + } + parsed = sync.parse_ruleset(payload, "t") + (options, nets), = parsed.groups.items() + self.assertEqual(options, Options(("udp",), ((50000, 50099),))) + self.assertEqual([str(n) for n in nets], ["1.2.3.0/24"]) + + def test_domain_fields_map_to_geosite_types(self): + payload = { + "version": 1, + "rules": [ + { + "domain": ["exact.com"], + "domain_suffix": [".suffix.com", "bare.com"], + "domain_keyword": ["kw"], + "domain_regex": ["^r$"], + } + ], + } + parsed = sync.parse_ruleset(payload, "t") + self.assertEqual( + sorted(str(r) for r in parsed.domains), + ["bare.com", "full:exact.com", "keyword:kw", "regexp:^r$", "suffix.com"], + ) + + def test_logical_rules_are_flattened_and_invert_skipped(self): + payload = { + "version": 2, + "rules": [ + {"type": "logical", "mode": "or", "rules": [{"domain_suffix": "inner.com"}]}, + {"ip_cidr": "9.9.9.9/32", "invert": True}, + ], + } + parsed = sync.parse_ruleset(payload, "t") + self.assertEqual([str(r) for r in parsed.domains], ["inner.com"]) + self.assertEqual(parsed.groups, {}) + self.assertTrue(any("invert" in s for s in parsed.skipped)) + + def test_group_names_disambiguate_by_ports(self): + first = Options(("udp",), ((50000, 50099),)) + second = Options(("udp",), ((19000, 20000),)) + names = sync.group_names([first, second], "src") + self.assertEqual(names[first], "src-udp-50000-50099") + self.assertEqual(names[second], "src-udp-19000-20000") + + def test_single_group_keeps_short_name(self): + options = Options(("udp",), ((50000, 50099),)) + self.assertEqual(sync.group_names([options], "src"), {options: "src-udp"}) + + def test_handwritten_files_are_never_overwritten(self): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "manual.txt" + path.write_text("# мой список\na.com\n", encoding="utf-8") + self.assertFalse(sync.is_generated(path)) + with self.assertRaisesRegex(sync.SyncError, "вручную"): + sync.write_generated(path, [], ["b.com"]) + sync.write_generated(Path(tmp) / "new.txt", ["nota"], ["b.com"]) + self.assertTrue(sync.is_generated(Path(tmp) / "new.txt")) + + +class CheckTest(unittest.TestCase): + def test_clean_sources_pass(self): + with data(a="a.com\nb.com\n", ip={"a": "1.2.3.0/24\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_duplicate_network_is_reported(self): + with data(a="a.com\n", ip={"a": "1.2.3.4\n1.2.3.4/32\n"}): + self.assertEqual(build.check(), 1) + + def test_network_covered_by_wider_one_is_reported(self): + with data(a="a.com\n", ip={"a": "10.0.0.0/8\n10.1.2.3\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 = [ + "discord-geosite.dat", + "discord-geoip.dat", + "geosite.dat", + "geoip.dat", + "discord-geoip.dat.sha256sum", + "metadata.json", + "xray/routing.json", + "txt/site/b.txt", + "src/site/b.txt", + "json/site/b.json", + "sing-box/site/b.json", + "clash/site/b.yaml", + "clash/site/b.list", + "adguard/b.txt", + "txt/ip/v.txt", + "src/ip/v.txt", + "json/ip/v.json", + "sing-box/ip/v.json", + "clash/ip/v.yaml", + "clash/ip/v.list", + ] + sources = {"a": "a.com\n", "b": "include:a\nfull:c.com\n"} + nets = {"n": "1.2.3.0/24\n", "v": "!network: udp\n!port: 50000-50099\n5.6.7.0/24\n"} + with data(ip=nets, **sources), tempfile.TemporaryDirectory() as out: + out_dir = Path(out) / "dist" + self.assertEqual(build.build(out_dir, None, None, "proxy"), 0) + for rel in expected: + self.assertTrue((out_dir / rel).is_file(), rel) + self.assertEqual( + (out_dir / "geoip.dat").read_bytes(), (out_dir / "discord-geoip.dat").read_bytes() + ) + meta = json.loads((out_dir / "metadata.json").read_text(encoding="utf-8")) + self.assertEqual(meta["categories"], {"a": 1, "b": 2}) + self.assertEqual(meta["ip_categories"]["n"], {"count": 1}) + self.assertEqual( + meta["ip_categories"]["v"], {"count": 1, "network": ["udp"], "ports": "50000-50099"} + ) + + def test_source_dump_round_trips_through_parser(self): + """src/ip/*.txt должен снова читаться сборщиком — вместе с директивами.""" + options = Options(("udp",), ((50000, 50099),)) + dumped = build.render_ip_source("v", [net("5.6.7.0/24")], options) + with data(a="a.com\n", ip={"v": dumped}): + self.assertEqual(build.resolve_ip("v"), ([net("5.6.7.0/24")], options)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/update-geodata.sh b/tools/update-geodata.sh new file mode 100644 index 0000000..75b469c --- /dev/null +++ b/tools/update-geodata.sh @@ -0,0 +1,86 @@ +#!/bin/sh +# Обновляет локальные discord-geosite.dat и discord-geoip.dat из ветки release. +# Рассчитан на запуск по cron. +# +# Каждый файл скачивается, только если его sha256 отличается от локального, +# контрольная сумма проверяется до подмены, а сервис перезапускается один раз +# и только если что-то реально изменилось. Молчит, когда обновлять нечего, — +# cron не будет слать письма впустую. +# +# ./update-geodata.sh +# BASE_URL=https://gitea.hsrv.site/mrleo1nid/discord-geodata/raw/branch/release \ +# DEST_DIR=/usr/local/x-ui/bin RESTART_CMD='x-ui restart' ./update-geodata.sh +# +# NAMES задаёт список файлов; по умолчанию — оба .dat. +# Коды возврата: 0 — обновлено или уже актуально, 1 — ошибка. + +set -eu + +BASE_URL="${BASE_URL:-https://gitea.hsrv.site/mrleo1nid/discord-geodata/raw/branch/release}" +DEST_DIR="${DEST_DIR:-/usr/local/x-ui/bin}" +NAMES="${NAMES:-discord-geosite.dat discord-geoip.dat}" +RESTART_CMD="${RESTART_CMD:-}" +TIMEOUT="${TIMEOUT:-30}" + +log() { printf '%s update-geodata: %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$*"; } +die() { log "ОШИБКА: $*" >&2; exit 1; } + +command -v curl >/dev/null 2>&1 || die "нужен curl" + +if command -v sha256sum >/dev/null 2>&1; then + hash_of() { sha256sum "$1" | cut -d' ' -f1; } +elif command -v shasum >/dev/null 2>&1; then + hash_of() { shasum -a 256 "$1" | cut -d' ' -f1; } +else + die "нужен sha256sum или shasum" +fi + +fetch() { curl -fsSL --retry 3 --retry-delay 2 --max-time "$TIMEOUT" "$1"; } + +mkdir -p "$DEST_DIR" +changed=0 + +for name in $NAMES; do + dest="$DEST_DIR/$name" + + # Без промежуточной переменной статус вернул бы cut, а не curl. + remote_line=$(fetch "$BASE_URL/$name.sha256sum") || + die "не удалось скачать $BASE_URL/$name.sha256sum" + remote_sum=$(printf '%s' "$remote_line" | cut -d' ' -f1) + case "$remote_sum" in + [0-9a-f][0-9a-f]*) [ "${#remote_sum}" -eq 64 ] || die "битая контрольная сумма: $remote_sum" ;; + *) die "битая контрольная сумма: $remote_sum" ;; + esac + + if [ -f "$dest" ] && [ "$(hash_of "$dest")" = "$remote_sum" ]; then + continue # уже актуально — тишина + fi + + tmp=$(mktemp "${TMPDIR:-/tmp}/discord-geodata.XXXXXX") || die "не удалось создать временный файл" + trap 'rm -f "$tmp"' EXIT INT TERM + + fetch "$BASE_URL/$name" > "$tmp" || die "не удалось скачать $BASE_URL/$name" + + got=$(hash_of "$tmp") + [ "$got" = "$remote_sum" ] || die "$name: ожидалось $remote_sum, получено $got" + [ -s "$tmp" ] || die "$name: скачан пустой файл" + + [ ! -f "$dest" ] || cp -p "$dest" "$dest.bak" || die "не удалось сохранить резервную копию" + # mv в пределах одного каталога атомарен: Xray никогда не увидит половину файла. + cat "$tmp" > "$dest.new" && mv -f "$dest.new" "$dest" || die "не удалось записать $dest" + rm -f "$tmp" + trap - EXIT INT TERM + + log "обновлён $dest (sha256 $remote_sum)" + changed=1 +done + +[ "$changed" -eq 1 ] || exit 0 + +if [ -n "$RESTART_CMD" ]; then + if sh -c "$RESTART_CMD"; then + log "выполнено: $RESTART_CMD" + else + die "перезапуск не удался: $RESTART_CMD" + fi +fi