## Why

Приём сейчас умеет только magnet-ссылку (`ingest.Request.Source` — строка;
`magnet.Parse` — единственный парсер; при неудаче — ошибка «.torrent/url —
следующий заход»). Но пользователи часто получают с трекера именно `.torrent`-
файл, а не magnet, и у файла есть два преимущества, которых нет у голого
magnet:

- **Работает там, где magnet не резолвится.** Раздачи закрытых трекеров и
  торренты без DHT по magnet-хешу метаданные не докачают (`metaDL` навсегда).
  `.torrent` несёт полный info-словарь — qBittorrent стартует сразу, без
  докачки метаданных.
- **Максимум контекста без сети.** В файле уже лежат реальное имя раздачи,
  дерево файлов с размерами, суммарный размер, трекеры, комментарий — гораздо
  богаче полей magnet. Это прямой сигнал для recognition ещё до старта
  скачивания.

Инфраструктура почти готова: `qbt.AddRequest.Torrents [][]byte` и
`qbt.Client.Add` уже умеют слать `.torrent` байтами (multipart-upload),
константа `store.SourceTorrent` уже объявлена. Не хватает трёх вещей: парсера
байтов в инфохэш+контекст, персистентности байтов между быстрым приёмом
(`catched`) и добавлением воркером, и путей приёма файла в транспортах
(Telegram-документ, файл-пикер в вебе).

## What Changes

- **Новый парсер `internal/torrent`** поверх `github.com/anacrolix/torrent/
  metainfo`: `Parse(data []byte) (Info, error)` вытаскивает инфохэш(и) (v1
  обязательно, v2 при наличии), имя раздачи, список файлов с размерами,
  суммарный размер, трекеры, комментарий. Инфохэш v1 — SHA1 **исходных** байтов
  info-словаря (без переэнкода). `func (Info) Context() string` синтезирует
  человекочитаемый контекст распознавания из этих полей — в том же стиле, что
  `magnet.Info.Context()`.
- **Приём (`ingest`) принимает байты `.torrent`.** `ingest.Request` получает
  путь для байтов торрент-файла; при их наличии источник парсится
  `torrent.Parse` (иначе — как сейчас, `magnet.Parse`). Загрузка заводится с
  `SourceType = SourceTorrent`, инфохэши и дедуп — тем же атомарным путём
  (`CreateDownloadIfNoActive`), контекст — синтез из полей файла, слитый с
  текстом транспорта.
- **Персистентность байтов.** Байты `.torrent` нужны воркеру на шаге добавления
  (быстрый приём лишь сохраняет `catched`). Заводим таблицу-спутник
  `download_torrent(download_id, data)` — байты живут и удаляются вместе с
  загрузкой, не раздувая выборки `download`.
- **Воркер добавляет по типу источника.** `processCatched` ветвится по
  `SourceType`: magnet/url — как сейчас (`URLs`, hint из `magnet.Parse`);
  torrent — грузит байты из `download_torrent`, hint из имени раздачи,
  добавляет через `qbt.AddRequest.Torrents`.
- **Веб-UI: файл-пикер.** Рядом со строкой ввода источника — `<input
  type="file" accept=".torrent">` и `enctype="multipart/form-data"` на форме.
  Выбран файл — приём по байтам; иначе — по тексту. Деградация без JS
  сохраняется (обычный multipart-POST).
- **Telegram: приём документа.** `handleMessage` распознаёт `.torrent`-
  документ (mime `application/x-bittorrent` / расширение), скачивает его байты
  через Bot API и подаёт в приём; подпись сообщения идёт контекстом.

Вне объёма (сознательно):

- **Торрент по http(s)-ссылке** (URL на `.torrent`) — отдельная задача: фетч
  тянет сеть в fast-path приёма, требует SSRF-гарда, таймаутов и обработки
  ошибок загрузки.
- Изменения механизма вывода отображаемого имени и распознавания — не
  трогаем; они лишь получают более богатый контекст.

## Capabilities

### New Capabilities

_Нет._ Изменение укладывается в существующие capability.

### Modified Capabilities

- `ingest`: добавляется приём источника из байтов `.torrent` (парс метаданных,
  извлечение инфохэшей, синтез контекста из полей файла, `SourceType =
  torrent`, персистентность байтов до добавления). Существующий magnet-путь и
  инварианты дедупа/инфохэшей — без изменений.
- `download-tracking`: шаг «добавление пойманной загрузки в qBittorrent»
  ветвится по типу источника — для torrent источник добавляется байтами файла,
  hint отображаемого имени берётся из метаданных торрента.
- `web-ui`: форма добавления получает файловый ввод `.torrent`
  (`multipart/form-data`), с деградацией без JS.

## Impact

- Код: новый `internal/torrent` (парсер + `Context()`); `internal/ingest`
  (ветка байтов, `SourceTorrent`, запись байтов); `internal/store` (таблица
  `download_torrent`, чтение/запись байтов, миграция); `internal/worker`
  (`processCatched` — диспетч по `SourceType`); `internal/httpapi` (multipart в
  `handleUIAdd`, размерный лимит); `internal/tgbot` (обработка `m.Document`,
  скачивание файла).
- Зависимости: `+ github.com/anacrolix/torrent` (пакет `metainfo`) в go.mod.
- Данные: новая таблица `download_torrent` (миграция goose) + обновление
  ER-схемы `docs/specs/database.md`. Таблица `download` не меняется.
- Конфиг: размерный лимит `.torrent` — константа с разумным дефолтом (вынос в
  конфиг — при необходимости, отдельно).
- Совместимость: аддитивно; существующие magnet-загрузки не затронуты.
