tgbot: показывать запись матча метабазы (провайдер+id+ссылка)

В карточке подтверждения и уведомлении о готовности бот теперь показывает
запись матча метабазы — провайдер, id и кликабельную ссылку на страницу
записи, — как веб-страница /download/{id} и экран ревью. Так ошибочную
привязку видно и из Telegram.

Билдер URL записи (providerURL/matchURL) вынесен из internal/httpapi в ядро
internal/worker (worker.ProviderURL + метод (*ReviewData).MatchURL()), чтобы
оба транспорта строили ссылку одинаково; httpapi делегирует туда. baseLine
переведён на эффективные provider/id (с учётом ручных правок), URL в href
экранируется escHref (сверх esc закрывает кавычку — иначе изготовленный id
разорвал бы атрибут и Telegram отклонил бы сообщение). При отсутствии матча
карточка ревью показывает «нет матча», уведомление о готовности строку
опускает.

Capability notifications: ADDED «Показ записи матча метабазы» + MODIFIED
требования об экранировании (id матча и URL, контекст href).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-07-18 15:41:28 +03:00
co-authored by Claude Opus 4.8
parent 4a58b0bda0
commit 10a6348d39
15 changed files with 614 additions and 123 deletions
@@ -0,0 +1,58 @@
## Why
Веб уже показывает, с какой именно записью метабазы сматчилась загрузка:
страница `/download/{id}` и экран ревью выводят provider, id и — если URL
строится — ссылку на страницу записи (`internal/httpapi`: `matchURL`/
`providerURL`, шаблон `download_main.html`). В Telegram матч показан беднее:
карточка ревью (`internal/tgbot/render.go`, `baseLine`) выводит только
`provider id` из **сырого** распознавания и **без ссылки**, а уведомление о
готовности матч не показывает вовсе. Из-за этого ошибочную привязку (не тот
фильм/сезон) из бота не видно — приходится открывать веб.
## What Changes
- **Карточка подтверждения (review)** в боте показывает матч со **ссылкой** на
запись метабазы (когда URL строится) — как веб: `provider id ↗`. Без URL —
тем же текстом, что и раньше.
- **Уведомление о готовности** (`renderDone`) показывает строку матча (provider,
id, ссылка) — чтобы ошибочную привязку было видно и в финальном пинге.
- **Provider/id берутся эффективные** (`ReviewData.Provider`/`ProviderID`, с
учётом ручных правок), консистентно с веб-страницей и экраном ревью, а не из
сырого распознавания.
- **Единый билдер URL:** канонический `providerURL` и выбор ссылки матча
`matchURL` переезжают из `internal/httpapi` в ядро `internal/worker`
(`worker.ProviderURL` + метод `(*ReviewData).MatchURL()`), чтобы оба
транспорта (веб и Telegram) строили ссылку одинаково. httpapi делегирует туда.
- **Экранирование:** provider, id и URL — недоверенные, экранируются перед
вставкой в HTML-сообщение (инвариант «выход LLM недоверенный»); ссылка
рисуется только при непустом URL. URL — в контексте атрибута `href`: помимо
`<`/`>`/`&` экранируется и кавычка (иначе изготовленный id разорвёт атрибут и
Telegram отклонит сообщение).
## Capabilities
### New Capabilities
Нет.
### Modified Capabilities
- `notifications`: добавляется требование к **содержанию** уведомлений/
подтверждений — показ записи матча метабазы (provider, id, ссылка) в карточке
ревью и уведомлении о готовности, эффективным провайдером, с экранированием.
Условия и события доставки (падение, review, готовность, рассинхрон) без
изменений.
## Impact
- **Спеки:** дельта `notifications` — ADDED «Показ записи матча метабазы» +
MODIFIED «Экранирование внешнего текста» (в перечень добавлены id матча и URL,
экранирование учитывает контекст `href`/кавычку).
- **Код:** `internal/worker/review.go` (новые `ProviderURL` + метод `MatchURL`),
`internal/httpapi/review.go`/`download.go` (делегируют в worker; локальные
`providerURL`/`matchURL` удаляются, `sourceMatchURL` зовёт `worker.ProviderURL`),
`internal/tgbot/render.go` (`baseLine` по `ReviewData` со ссылкой; строка матча
в `renderDone`).
- **Тесты:** тесты `providerURL`/`matchURL` переезжают в `internal/worker`;
`internal/tgbot` — проверка ссылки и экранирования в строке матча.
- **Миграции БД:** нет (данные матча уже в БД).