11 KiB
ROADMAP - Loki Blog Bot (Rust kernel)
Цель: не клон
discord-bot(экономика/музыка/левел), а бот для блога/портфолио - чистый, KISS, ≤200 строк/файл, ≤4 файла/папку.
Сервер блога - желаемая структура Discord
📁 INFO #rules, #welcome, #announcements, #blog-feed (RSS → webhook), #changelog
📁 COMMUNITY #general, #ideas, #showcase (юзеры постят проекты), #feedback
📁 SUPPORT #open-ticket, #faq
📁 VOICE create-voice (temp), afk
📁 STAFF (🔒) #mod-logs, #bot-logs
↑ counter 👥 Members: N голосовой канал ВНЕ категории в самом верху (pos 0, bots excluded)
Роли: @Verified (кнопка в #welcome), @Blogger, @Support, @Staff, @Muted.
Phase 1 - Ядро ✅ (скелет готов)
- Rust 1.82, tokio, serenity 0.12 + poise 0.6 (slash + prefix
!) config/loaderdotenv → BotConfig,db/poolWAL + FK=ON (фикс C6 старого бота)db/migrationidempotent транзакции (фикс C5)core/lifecycletrait,core/botReadyEvent + graceful shutdownfeature/help|info|admin-!ping,!serverinfo,!shutdown(owner) → теперь help интерактивный (селект категорий)ds-setupCLI:inspect/bootstrap --dry-run/--apply(BOT token, user-token с ворнингом)
Запуск:
cp .env.example .env # вставь DISCORD_BOT_TOKEN, GUILD_ID
cargo run --bin bot
cargo run --bin ds-setup -- inspect --guild-id 123
cargo run --bin ds-setup -- bootstrap --guild-id 123 --dry-run
Phase 2 - Верификация и доступ (guild 1509503154708811837)
- Verify (embed+✅) -
#✅|verifyread-only, auto-embed на старте,✅→✅ Verified - Help - переписан как в Java:
help/categories.rs+help/handler.rs+ селектhelp:category→UpdateMessage, цвета/эмодзи по категориям, фильтр мод-прав (Info/Mod/Tickets/Utility), какHelpMenuBuilder.java/HelpMenuHandler.java - Counter fix -
feature/counter/task.rs: вне категорииposition(0), каждый старт проверяетparent_idиposition, поднимает вверх; считает только живых (пагинацияmembers(1000, after),!user.bot), fallback наapproximate_member_count !mute/!unmute/!warn/!purge/!slowmode- атомарные транзакции- Logging в #mod-logs (join/leave, edit/delete, mod actions)
- RateLimiter +
core/queueSemaphore 100 (уже заготовка)
Phase 3 - Блог-фичи (главное отличие от старого бота)
- RSS/Telegram → #blog-feed (webhook,
reqwest+feed-rs) !post- анонс поста (embed с cover, линк)- GitHub webhook → #changelog (нативная интеграция Discord, без кода: GitHub webhook URL =
<discord-webhook-url>/github) - Ticket v2 - SelectMenu
ticket_topic+ Modalticket_modal:<topic>(subject/description) → приватный канал в SUPPORT, overwrites за 1 операцию, sanitize имени, claim, blacklist, transcript → hastebin -feature/ticket/handler.rs+ auto-panelensure_ticket_panel()на Ready (как verify)
Phase 3.1 - Ticket hardening (TODO, приоритезировано 22.09.2026)
- Add/Remove user -
/ticket_add @user/ticket_remove @user(staff):MANAGE_CHANNELScheck,channel.permission_overwritesadd/remove Member overwriteVIEW+SEND, лог в ticket-logs, DM добавленному. - Close reason modal + Reopen/Delete -
Close→ modalticket_close_reason(reason 10-500), затемControlPanel:Reopen(снимаетarchived, восстанавливает overwrites),Transcript(haste + file),Delete(транскрипт →ChannelId::delete). КакticketPresentation.js:166buildClosedControlPanelRow()(Reopen secondary, Save secondary, Delete danger). - Cooldown 5 мин + RateLimiter per user - после
cleanup_ticketписать вticket_cooldowns(user_id, guild_id, expires_at); приon_select/on_modalпроверятьnow < expires_at→ ephemeralWait 3m 12s. Plus глобальныйRateLimiterper-user 1 ticket/5m + burst check. - Orphan reconciliation on boot -
ticketReconcile()вready:SELECT tickets→ для каждогоguild.channels().fetch(ticket.channel_id)еслиNone→DELETE FROM tickets/claims, лог embedTicket Manually Deleted(без транскрипта, какthreadDelete.js), dm owner. ИтогоticketRepository.listOpenTickets()аналог. - Presence
watching N tickets+ counter -Context::set_presence(ActivityData::watching(format!("{} tickets", count))), обновлять наcreate/close/reconcile;ticket_countизSELECT COUNT(*) FROM tickets WHERE guild_id=?. - Threads vs Channels - сейчас
ChannelType::Textв SUPPORT (лимит 500 каналов, видно в списке). При >50 тикетов/день → мигрировать наChannelType::PublicThread/PrivateThreadвнутри#open-ticket(лимит 1000 активных тредов, авто-архив 24ч/7д). ФлагTICKET_USE_THREADS=falseв env, абстрагироватьcreate_ticket_channelvscreate_thread. - Anti-spam - в тикет-каналах:
slowmode 5sпо умолчанию,mentionлимит (1 @everyone → warn), бот роль вышеVerified/Muted(иначе403 add_roles),MANAGE_ROLES/MANAGE_CHANNELSпроверке на старте. - Forum/channel tags vs одна категория - см. § Forum Tags ниже.
Phase 4 - Комьюнити
- Welcome embed (
feature/welcome) - Counters (member count в voice-канале,
feature/counter, refresh 10 мин, теперь вне категории наверху) - Polls (
/poll вопрос; вариант1; вариант2, embed + реакции цифрами,feature/poll) - Scheduler / напоминания (
/remind 10m текст, личные, SQLite + фоновый воркер 30с,feature/remind) - Temp voice (
#create-voice→ временный канал,feature/voice)
Phase 5 - Опционально (AI)
- AI FAQ (DeepSeek/GPT) в #faq - только по базе знаний блога
- Auto-mod (спам/ссылки)
Forum / Channel Tags — вместо одной категории SUPPORT
Что это: Discord с 2022г поддерживает Forum-каналы (ChannelType::Forum). Внутри один канал-форум, а каждый тикет — пост-тред с тегами. Теги — это встроенные лейблы Discord (до 20 штук, цвет+эмодзи), например: ❓ General, 🐛 Bug, 🔇 Appeal, 😡 Report. Юзер при создании поста выбирает тег(и), фильтрация по тегам нативная.
Как это соотносится с твоей текущей схемой:
- Сейчас:
SUPPORT— категория, внутри создаютсяticket-bug-usernameканалы (ChannelType::Text,parent_id=SUPPORT_CATEGORY,permissionsна каждого юзера). Минус: жрёт лимит 500 каналов, каждый тикет виден в сайдбаре всем стаффам сразу, надо руками чистить. - Форум-альтернатива: создаёшь один канал
Forum(#support-tickets,ChannelType::Forum,available_tags=[ General, Bug, Appeal, Report, Owners ]+default_reaction_emoji). Панель остаётся (StringSelect→ modal), ноcreate_ticketделаетforum.create_forum_post(title=subject, content=description, tags=[topicTag])→ пост-тред. Права наследуются от форума (private threads видны только@everyonedeny + thread member). Преимущества и лимиты:
| Критерий | Каналы в категории | Forum tags |
|---|---|---|
| Лимит | 500 каналов/гильдия | 1000 активных тредов + неограниченно архивированных |
| Сайдбар | каждый тикет — отдельный канал (шум) | один форум-канал, внутри список постов (чисто) |
| Теги/фильтр | категория одна, топик в имени канала ticket-bug- |
нативные теги (фильтр, поиск, цвет, required/optional) |
| Права | PermissionOverwrite на канал (кастом) |
thread_add_member + VIEW_CHANNEL deny на форум |
| Транскрипт | channel.messages().fetch() |
thread.messages().fetch() аналогично |
| Авто-архив | ручной delete |
нативный default_auto_archive_duration 1ч/24ч/7д |
| Сортировка | вручную | по активности, по тегам |
Рекомендация для Loki Dev: пока <50 тикетов/день — оставляй каналы (проще дебажить, транскрипт/claim привычнее). При росте — мигрировать на форум с тегами без смены UX: селект/ модалка остаются, меняется только service::create_ticket (флаг TICKET_USE_FORUM). В форум-режиме SUPPORT_CATEGORY не нужен, теги создаются один раз на старте (GET/PATCH /channels/{forum.id} с available_tags). Это «канал-теги вместо категории» — один канал с 5 тегами заменяет 5 подканалов/префиксов.
Уроки старого бота - что НЕ повторяем
F1,F2,F4 - экономика: атомарные UPDATE ... WHERE cash>=?, daily WHERE last_daily != today.
F8/C1 - не блокировать gateway: все DB/HTTP вне EventHandler, tokio::spawn, sqlx async.
C6 - foreign_keys=ON через SqliteConnectOptions::foreign_keys(true), не одним PRAGMA.
M1 - destroy() на ScheduledExecutorService → в Rust JoinHandle::abort / pool.close().await.
Качество
cargo fmt --check, cargo clippy -- -D warnings, cargo test, cargo audit. Лимит 200 строк - rustfmt max_width=100.
Каждый feature/* ≤4 файла, >150 строк → дробить (service/, repository/).