tgbot: выбор кандидата метабазы inline-кнопками в карточке ревью
Когда для распознавания сохранены кандидаты метабазы, карточка подтверждения бота показывает кнопку «🗂 База (N)». По ней двухшагово (как delete/dismiss) разворачивается список кандидатов inline-кнопками; выбор пиннит источник через worker.ChooseCandidate (ручной матч, без авто-раскладки) и обновляет карточку. Веб остаётся точкой точных правок (ручной ввод id/URL, «без базы»). Безопасность границы: id кандидата из callback_data валидируется как ULID (ident.Parse) до доменного вызова, как в вебе. Текст inline-кнопок Telegram не парсится как HTML — название кандидата в подписи не экранируется. SDD: change telegram-vybor-nahodok — дельта notifications (ADDED «Выбор кандидата метабазы из карточки подтверждения бота») + review (MODIFIED «Разделение труда транспортов»: быстрый выбор кандидата — Telegram-действие). Влито в specs, change заархивирован. Миграций БД нет (кандидаты уже в БД). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
## 1. Интерфейс транспорта
|
||||
|
||||
- [x] 1.1 `internal/tgbot/bot.go`: добавить в интерфейс `Reviewer` метод
|
||||
`ChooseCandidate(ctx context.Context, id, candidateID string) error`.
|
||||
- [x] 1.2 `internal/tgbot/bot_test.go`: дополнить `fakeReviewer` методом
|
||||
`ChooseCandidate` (запоминает выбор для проверки).
|
||||
|
||||
## 2. Клавиатуры (render.go)
|
||||
|
||||
- [x] 2.1 `reviewKeyboard`: когда `len(rd.Candidates) > 0`, добавить кнопку
|
||||
«🗂 База (N)» с callback `sources:<id>`.
|
||||
- [x] 2.2 Новая `candidatesKeyboard(rd)`: по кнопке на кандидата (дедуп по
|
||||
`provider:provider_id`, потолок), текст `provider · Title (Year)`, у `Chosen` —
|
||||
префикс «✓ », callback `pick:<id>:<candidateID>`; нижняя кнопка «← Назад» с
|
||||
callback `srcback:<id>`.
|
||||
|
||||
## 3. Обработка callback (bot.go)
|
||||
|
||||
- [x] 3.1 `handleCallback`: ветки `sources` (answer + `editMarkup`
|
||||
списком кандидатов, перечитав `ReviewData`; домен не трогаем) и `srcback`
|
||||
(answer «Отменено» + `refreshCard`).
|
||||
- [x] 3.2 Ветка `pick`: валидировать `val` (id кандидата) через `ident.Parse`
|
||||
(невалидный → answer «Кнопка устарела…», состояние не меняем), затем
|
||||
`reviewer.ChooseCandidate(ctx, id, candidateID)`; успех → answer «Источник
|
||||
выбран» + `refreshCard`; ошибка — общая ветка ошибки.
|
||||
|
||||
## 4. Тесты
|
||||
|
||||
- [x] 4.1 `internal/tgbot`: карточка ревью с кандидатами содержит кнопку
|
||||
`sources:<id>`; без кандидатов — не содержит.
|
||||
- [x] 4.2 `sources` разворачивает клавиатуру с кнопками `pick:<id>:<candID>` и
|
||||
«← Назад»; выбранный кандидат помечен «✓ ».
|
||||
- [x] 4.3 `pick` с валидным id зовёт `ChooseCandidate` с этим id и обновляет
|
||||
карточку; `pick` с невалидным id — не зовёт домен.
|
||||
|
||||
## 5. Спека и проверки
|
||||
|
||||
- [x] 5.1 `openspec validate --strict telegram-vybor-nahodok`.
|
||||
- [x] 5.2 `task test` и `task lint` — зелёные.
|
||||
Reference in New Issue
Block a user