feat: ticket v2 select+modal, counter top+human-only, help interactive

This commit is contained in:
loki5512344 2026-09-21 20:41:06 +02:00
parent 589238570a
commit ac96cfcb59
Signed by: boba
GPG key ID: 253067914055423B
9 changed files with 825 additions and 105 deletions

View file

@ -12,6 +12,7 @@
📁 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`.
@ -23,7 +24,7 @@
- [x] `config/loader` dotenv → BotConfig, `db/pool` WAL + FK=ON (фикс C6 старого бота)
- [x] `db/migration` idempotent транзакции (фикс C5)
- [x] `core/lifecycle` trait, `core/bot` ReadyEvent + graceful shutdown
- [x] `feature/help|info|admin` - `!ping`, `!serverinfo`, `!shutdown` (owner)
- [x] `feature/help|info|admin` - `!ping`, `!serverinfo`, `!shutdown` (owner) → теперь help интерактивный (селект категорий)
- [x] `ds-setup` CLI: `inspect` / `bootstrap --dry-run/--apply` (BOT token, user-token с ворнингом)
**Запуск:**
@ -34,9 +35,11 @@ cargo run --bin ds-setup -- inspect --guild-id 123
cargo run --bin ds-setup -- bootstrap --guild-id 123 --dry-run
```
## Phase 2 - Верификация и доступ (guild 1509503154708811837, next)
- [ ] **Verify (embed+✅)** - `#✅|verify` read-only (`@everyone: VIEW ✅, SEND ❌, ADD_REACTIONS ✅`), embed + `✅` реакция → выдача `✅ Verified` (1509503647480811610), бот роль выше Verified, `GUILD_MESSAGE_REACTIONS` + partials. Кнопка - альтернатива, но делаем реакцию как просил. Фикс F3: проверка `MANAGE_ROLES`.
- [ ] `!mute/!unmute/!warn/!purge/!slowmode` - атомарные транзакции, без `UndeclaredThrowableException` (фикс C1)
## Phase 2 - Верификация и доступ (guild 1509503154708811837)
- [x] **Verify (embed+✅)** - `#✅|verify` read-only, auto-embed на старте, `✅` → `✅ Verified`
- [x] Help - переписан как в Java: `help/categories.rs` + `help/handler.rs` + селект `help:category` → `UpdateMessage`, цвета/эмодзи по категориям, фильтр мод-прав (Info/Mod/Tickets/Utility), как `HelpMenuBuilder.java`/`HelpMenuHandler.java`
- [x] 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/queue` Semaphore 100 (уже заготовка)
@ -44,11 +47,21 @@ cargo run --bin ds-setup -- bootstrap --guild-id 123 --dry-run
- [ ] RSS/Telegram → #blog-feed (webhook, `reqwest` + `feed-rs`)
- [ ] `!post` - анонс поста (embed с cover, линк)
- [x] GitHub webhook → #changelog (нативная интеграция Discord, без кода: GitHub webhook URL = `<discord-webhook-url>/github`)
- [x] Ticket (кнопки в #open-ticket → приватный канал/тред в SUPPORT, overwrites за 1 операцию, claim, blacklist, transcript → hastebin) - `feature/ticket`
- [x] Ticket v2 - SelectMenu `ticket_topic` + Modal `ticket_modal:<topic>` (subject/description) → приватный канал в SUPPORT, overwrites за 1 операцию, sanitize имени, claim, blacklist, transcript → hastebin - `feature/ticket/handler.rs` + auto-panel `ensure_ticket_panel()` на Ready (как verify)
### Phase 3.1 - Ticket hardening (TODO, приоритезировано 22.09.2026)
- [ ] **Add/Remove user** - `/ticket_add @user` `/ticket_remove @user` (staff): `MANAGE_CHANNELS` check, `channel.permission_overwrites` add/remove Member overwrite `VIEW+SEND`, лог в ticket-logs, DM добавленному.
- [ ] **Close reason modal + Reopen/Delete** - `Close` → modal `ticket_close_reason` (reason 10-500), затем `ControlPanel`: `Reopen` (снимает `archived`, восстанавливает overwrites), `Transcript` (haste + file), `Delete` (транскрипт → `ChannelId::delete`). Как `ticketPresentation.js:166` `buildClosedControlPanelRow()` (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` → ephemeral `Wait 3m 12s`. Plus глобальный `RateLimiter` per-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`, лог embed `Ticket 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_channel` vs `create_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 - Комьюнити
- [x] Welcome embed (`feature/welcome`)
- [x] Counters (member count в voice-канале, `feature/counter`, refresh 10 мин)
- [x] Counters (member count в voice-канале, `feature/counter`, refresh 10 мин, теперь вне категории наверху)
- [x] Polls (`/poll вопрос; вариант1; вариант2`, embed + реакции цифрами, `feature/poll`)
- [x] Scheduler / напоминания (`/remind 10m текст`, личные, SQLite + фоновый воркер 30с, `feature/remind`)
- [x] Temp voice (`#create-voice` → временный канал, `feature/voice`)
@ -59,6 +72,28 @@ cargo run --bin ds-setup -- bootstrap --guild-id 123 --dry-run
---
## 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 видны только `@everyone` deny + 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.