discord-bot-kernel/ARCHITECTURE.md

130 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Architecture - Rust kernel (edition 2024, guild 1509503154708811837)
```
bot-kernel/ # cargo init --edition 2024 --name bot-kernel
├─ Cargo.toml # serenity 0.12.5 + poise 0.7.0 + sqlx 0.9.0 + tokio 1.53 + reqwest 0.13 (cargo add)
├─ .cargo/config.toml / rustfmt.toml / clippy.toml
├─ .env.example # BOT token only; USER token → tools/userbot/.env (isolated)
├─ ROADMAP.md / ARCHITECTURE.md / SETUP.md
├─ staroe/ # архив Java kernel (21→25, build.gradle, src/kernel/loki)
├─ src/ # Rust bot (BOT token, slash + prefix)
│ ├─ main.rs # dotenv → config → pool (WAL/FK) → migration → poise → serenity
│ ├─ config/ # keys.rs, loader.rs (env→BotConfig)
│ ├─ core/ # lifecycle.rs, bot.rs (ReadyHandler + shard), dispatcher.rs, queue.rs, policy.rs
│ ├─ db/ # pool.rs (WAL, FK=ON, busy_timeout 5s), migration.rs (tx idempotent)
│ ├─ feature/ # ≤4 файла/папку, ≤200 строк/файл (см. SOLID)
│ │ ├─ help/command.rs # /help, !help (slash+prefix via poise)
│ │ ├─ info/command.rs # /ping, /serverinfo
│ │ └─ admin/command.rs # /shutdown (owners_only)
│ ├─ util/logger.rs
│ └─ bin/ds_setup.rs # Rust inspector (BOT token) - дублирует JS, но не user-token
└─ tools/userbot/ # ← ОТДЕЛЬНАЯ ПАПКА для юзер-бота (JS, только просмотр)
├─ package.json (dotenv) # node index.js inspect --user-token --guild-id 1509503154708811837
├─ index.js # fetch https://discord.com/api/v10/* с Authorization
├─ .env.example
└─ README.md
```
> **Важно:** папка `tools/userbot` (была `tools/ds-inspector`) - **изолирована** от `src/`. Rust-бот никогда не импортирует user-token. JS-просмотрщик - read-only, не шлёт сообщения.
## Guild 1509503154708811837 - Loki Dev (проверено 09.09.2026, целевой - только он)
- Владелец: ты (loki_boba 1414975197269987471), 22.guilds, bot `kernel:1527966600173457408` уже в гильдии (permissions 18014398509481983). Не приглашать на другие - как просил.
- Каналы 27: INFO (rules, welcome, announcements, releases, github-log, ✅|verify, roles), COMMUNITY (general, ru-chat, en-chat, ideas), SUPPORT (open-ticket, bug-report, ticket-faq), VOICE (work-room-1/2, create-voice, afk), STAFF (staff-chat, mod-logs, bot-commands, ticket-logs).
- Роли 16: dupe `👑 Loki/Staff/Client/Verified/RU/EN/Muted` (две копии - надо чистить), `kernel` (bot), `@everyone`.
- Токены валидны, хранятся в `.env` / `tools/userbot/.env` (gitignore), не коммитим.
## Code Style & Quality (English, Javadoc, no dead code, fmt/clippy)
- **Language:** all docs, comments, commit messages - **English only** (guild multi-language → EN default, RU via role `i18n.rs`). Chat in code = English.
- **Javadoc-style comments:** every `pub` item has `///` doc comment (like Java `/** ... */`): purpose, args, returns, example, `// SAFETY:` for sleeps. No undocumented `pub fn`.
- **Files:** `≤200` lines, `≤4` files per folder (`feature/<domain>/{mod,command,handler,service}.rs`). Split if >150.
- **Dead code forbidden:** `#[allow(dead_code)]` / `#[allow(unused)]` **forbidden** - delete unused code, don't hide it. CI fails on `dead_code`/`unused`. Use `cargo fix`, not `allow`.
- **Formatting mandatory:** `cargo fmt --check` must pass. `rustfmt.toml: edition 2024, max_width 100`. Run `cargo fmt` before commit.
- **Clippy mandatory:** `cargo clippy -- -D warnings` must pass. `clippy.toml: disallowed-methods = ["std::thread::sleep"]`. Fix all `clippy::pedantic`/`nursery` warnings, no `#[allow(clippy::...)]` without `// SAFETY:` + issue link.
- **CI:** `cargo fmt --check && cargo clippy -- -D warnings && cargo test && cargo check` - blocked on failure.
## SOLID / KISS / DRY (≤200 строк/файл, ≤4 файла/папку)
- `Lifecycle` trait - ISP, каждая фича `impl Lifecycle`.
- `BotConfig` SRP (только env), `SqlitePool` DIP (инжект через `feature::Data`).
- `RateLimiter` + `JobQueue` DRY - не копипастим cooldown как в старом `D2` (7 команд копировали `getDeterministicValue`).
- `≤200` строк - `clippy::too_many_lines` + `rustfmt max_width 100`, `≤4` файла → `feature/<domain>/{mod,command,service,repository}`.
## Команды - slash + prefix (poise 0.7)
- Сейчас: `help`, `ping`, `serverinfo`, `shutdown`. Все `#[poise::command(slash_command, prefix_command)]`.
- План блога: `/verify` (кнопка → Verified), `/blog <url>` (RSS→embed), `/ticket` (кнопка), `/post` - все slash-first, prefix как fallback.
- Регистрация: `register_in_guild(GUILD_ID=1509503154708811837)` в dev (мгновенно), `register_globally` в проде (до 1ч). Не используй Global Sync при итерации (антипаттерн).
## Очередь / мультиядерность / анти-задержки (POLICY)
**Запрещено:** `tokio::time::sleep`, `std::thread::sleep`, `std::thread::park` внутри `feature/*/command.rs` **без** комментария `// SAFETY: <причина + длительность + jitter>`.
- **Почему:** блокировка gateway → `Missed heartbeat`, 429, CloudFlare ban. Старый бот падал на `HealthHttpServer single thread` (A07), `TrackScheduler` утечках, `M!play sleep 4с` (M2).
- **Как правильно:**
1. `ctx.defer().await?` → ответ `Thinking...`, затем тяжёлая работа в `tokio::spawn` / `JobQueue`.
2. `core/queue.rs` - bounded `tokio::sync::Semaphore(100)` + `mpsc::channel(256)` (аналог `MaxHandlerConcurrency=100` из go антипаттерна). Burst не спавнит unbounded tasks.
3. `RateLimiter` per-user + global token bucket (429 `Retry-After` + jitter, не хардкод `sleep(1000)`).
4. `sqlx` async, `reqwest` с `timeout 10s + context::withTimeout` - не `context.Background()` бесконечно.
5. `poise::FrameworkError::CommandPanic` - ловим панику handler'а, не крашим весь процесс.
- **Проверка:** `clippy.toml` `disallowed-methods = ["std::thread::sleep"]`, CI `grep -R 'sleep' src/feature --include='*.rs' | grep -v 'SAFETY:' && exit 1`.
```
src/core/queue.rs - BoundedJobQueue (Semaphore 100, channel 256, backpressure)
src/core/policy.rs - документация запрета задержек
src/core/dispatcher.rs - RateLimiter (check per user → Option<Duration>)
```
## Верификация и тикеты - как правильно (нагуглено 09.09.2026)
### Верификация (требование: embed + реакция ✅, чат заблокирован)
- **Канал `#✅|verify` (1509503655487737916):** только чтения для всех. Overwrites: `@everyone` → `VIEW_CHANNEL ✅`, `READ_MESSAGE_HISTORY ✅`, `ADD_REACTIONS ✅`, `SEND_MESSAGES ❌`, `ADD_REACTIONS` (add new) ✅. Остальные каналы скрыты пока нет `✅ Verified`.
- **Embed:** бот `/verify-setup` (admin only) постит embed `Title: Проверка • Description: Нажми ✅ чтобы получить доступ` + `msg.react('✅')`. Храним `message_id` в `guild_config.verify_message_id`.
- **Событие:** `on_raw_reaction_add` (`GUILD_MESSAGE_REACTIONS` intent + `partials: MESSAGE/CHANNEL/REACTION`). Проверяем `payload.message_id == verify_message_id && emoji == ✅ && !bot`. Даём роль `✅ Verified (1509503250997448754 / 1509503647480811610)` через `member.add_roles`. Роль бота **выше** Verified в иерархии, иначе 403 (частая ошибка StackOverflow).
- **Безопасность:** `Verification Level` в Guild Settings → `Medium` (email +5min) или `High` (+10min) + `@everyone` без `SEND_MESSAGES` в остальных каналах. Альтернатива кнопке - `Button` (components V2) современнее, но ты просил реакцию - делаем реакцию (Discord не рекомендует для новых, но работает).
- **Ошибки:** бот должен иметь `MANAGE_ROLES`, роль выше Verified, partials включены, не `reaction.emoji.id` для unicode.
### Тикет-система (SUPPORT)
- **Панель:** embed в `#🎫|open-ticket` с кнопками (`General`, `Bug`, `Support`) - современный способ (не реакции). Старый `🎫` реакция - легаси, кнопки лучше (нет лимита 20 reactions, не надо `ADD_REACTIONS`).
- **Создание:** `on_interaction_create` (button) → `guild.create_text_channel(name=ticket-username-001, parent=SUPPORT, overwrites={@everyone: deny VIEW, user: allow VIEW+SEND, Staff: allow})` **в одну операцию** `overwrites` (не `set_permissions` по очереди - медленно, антипаттерн SO).
- **Варианты:** `channel` (до 500 каналов, видно в списке) vs `thread` (до 1000, не в лимит, авто-архив). Для Loki Dev - `channel` ок, для хайлоада - `thread`.
- **Управление:** `/close`, `/add`, `/remove`, claim, transcript (fetch messages → Hastebin/file), лог в `#🧾|ticket-logs` + DM юзеру. Чёрный список per-guild.
- **Best practices:** категории по типам, claim чтобы не дублировать ответы, транскрипты для сокращения FAQ.
## Анти-принципы (что НЕ делать) - нагуглено 09.09.2026
Собрано из `discord.go/anti-patterns`, `discords.ai` (wiki 2026), `Rank.top` (rate limits), `discord.js`, `discord.py` issues:
### 1. Токены и секреты
- **Коммитить токены/`.env`** → бан, скам. Фикс: `dotenvy`, `.gitignore` + `DISCORD_BOT_TOKEN` из env, `Reset Token` при утечке (у тебя оба токена уже утекли - регенерируй!).
- **Hardcode webhook URL** → спам.
### 2. Блокировка gateway / задержки
- **Blocking event handler** (sleep, тяжёлый CPU/DB/HTTP в handler) → dispatch pipeline стоп, `Missed 2 heartbeats`. Фикс: `defer + tokio::spawn` (см. policy выше).
- **`sleep` без объяснения** → запрещено (policy). Нужен jitter/backoff, не `sleep(200ms)` в цикле.
- **Sync Blocking Loops** (тяжёлые вычисления в async loop) → фриз всех юзеров. Фикс: `spawn_blocking`.
- **Not Setting MaxHandlerConcurrency** → unbounded goroutines/tasks при burst. Фикс: semaphore 100.
### 3. Rate limits (429, CloudFlare)
- **Игнор 429 / хардкод таймер** → каскадный фейл, IP-бан (10k invalid /10min → CloudFlare). Фикс: парсить `X-RateLimit-Remaining/Reset-After/Retry-After`, глобальная очередь, `exponential backoff + jitter` (full/decorrelated). Не `get 429 then retry` без queue.
- **Global REST ~50 req/s, Gateway 1 Identify/5s, 120 events/60s per WS** → шарить `max_concurrency`, sharding при росте.
- **Channel rename 2/10min, message edit 5/5s** → queue, не `msg.edit()` 10 раз подряд.
### 4. Slash vs prefix
- **Глобальный sync при разработке** → ждать 1ч. Фикс: `GuildCommandSync(GUILD_ID)` для Loki Dev.
- **Второй initial response после Defer** → Discord reject. Фикс: после `defer` только `followup`.
- **Reply "invalid command" на несуществующие** → спам при коллизии префиксов. Фикс: silent fail, только `invalid args`.
### 5. Интенты и кэш
- **Все интеты `Intents::all()`** → OOM, верификация. Фикс: `GUILD_MESSAGES|MESSAGE_CONTENT|GUILDS|GUILD_MEMBERS` только нужные (у тебя уже так).
- **Неиспользуемые привилегированные `MESSAGE_CONTENT`, `Presence`** → доп. верификация.
### 6. Прочее
- **Invite scraping** (бот джойнит все сервера по инвайтам) → бан.
- **Greedy prefixes (`!,$,.`)** → коллизии, используй уникальный (`!` ок, но можно `!loki`).
- **Overuse mentions / @everyone** → бот-лупы, мьют.
- **Unbounded listeners / cooldown=0** → спам тяжёлых DB. Фикс: `CooldownManager` + `RateLimiter`.
- **Игнор паник** → краш всего бота. Фикс: `poise::builtins::on_error` + `HandlerPanicError`.
- **Embeds в Components V2** → reject, используй text components.
- **Not handling Promise rejections / unhandled Result** → молчаливый краш.
## От старого бота - что уже пофикшено
- Нет `God Module`/`PrefixCommandRegistrar 22 param` → per-feature `Module + Registrar`.
- Нет блокирующего `JDBC` на gateway → `sqlx::SqlitePool` async.
- `WAL + FK=ON + busy_timeout 5s` через `SqliteConnectOptions` (фикс `C6`), миграции транзакционно (фикс `C5`), `UndeclaredThrowableException` анврап не нужен (Rust `anyhow`).
- `RateLimiter` + `JobQueue` вместо `CachedThreadPool` (A05) и `M2/M3 sleep 4s` на gateway.