## 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: файл-пикер.** Рядом со строкой ввода источника — `` и `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-загрузки не затронуты.