--- title: Media Pipeline — Multi-Tracker Support created: '2026-05-17' updated: '2026-05-17' status: active tags: - media-pipeline - rutracker - rutor - nnm-club --- # Media Pipeline — Multi-Tracker Support ## Текущее состояние ✅ Реализовано и задеплоено на Kraken: - `TrackerPageParserRegistry` — dispatch по `url.host`, парсеры: Rutracker, Rutor, NNMClub, PirateBay, Nyaa - `RutrackerPageParser` — CP1251 decode через `/usr/bin/iconv`, SwiftSoup парсинг - Жанры извлекаются из `[...]` в заголовке топика, страны фильтруются - KP/IMDb ID extraction работает - `TorrentSourceStrategy` использует registry вместо прямого вызова `RutrackerAPI` - Fallback NFO при unresolved + `torrentPageMetadata.hasRichData` ## Архитектура ### TrackerPageParser protocol ```swift public protocol TrackerPageParser: Sendable { var supportedHosts: [String] { get } func parse(data: Data, url: URL) throws -> TorrentPageMetadata } ``` ### TrackerPageParserRegistry ```swift public struct TrackerPageParserRegistry: Sendable { public static let defaults: [TrackerPageParser] = [ RutrackerPageParser(), RutorPageParser(), NNMClubPageParser(), PirateBayPageParser(), NyaaPageParser(), ] public func parser(for url: URL) -> TrackerPageParser? } ``` ## Парсеры по трекерам ### RutrackerPageParser ✅ - Encoding: Windows-1251 (через `/usr/bin/iconv` temp files — swift-corelibs-foundation не поддерживает CP1251 на Linux) - Title selector: `#topic-title`, `h1.maintitle`, `a.topictitle` - **Genres: из заголовка топика `[YEAR, жанр1, жанр2, ..., TECH-TAG]`** — не из поста - Страны фильтруются через `countryNames: Set` (30+ стран RU+EN) - Poster: первый img в `div.post_body` не из rutracker UI - IDs: `imdb.com/title/tt\d+`, `kinopoisk.ru/film/\d+` ### RutorPageParser ✅ (структура реализована) - Encoding: UTF-8 - Title: `h1.topic-header` / `div#topic-title` - Genres: паттерн `Жанр:` в тексте ### NNMClubPageParser ✅ (структура реализована) - Encoding: Windows-1251 (тот же iconv путь) - Title: `h1.maintitle` / `a.maintitle` ### PirateBayPageParser ✅ (структура реализована) - Encoding: UTF-8 - Title: `

` - Нет IMDb/KP ссылок обычно → только title/year из названия торрента ### NyaaPageParser ✅ (структура реализована) - Encoding: UTF-8 - Аниме: title из `h3.panel-title`, AniDB ID если есть ## Ключевые питфоллы - `extractGenres` должен парсить из **заголовка топика** (`#topic-title`), а не из тела поста — Rutracker кодирует жанры в `[YEAR, жанр, ...]` в title - CP1251 decode: использовать `/usr/bin/iconv` через temp files — единственный способ на Linux - Кеш хранит сырые байты (binary) — `grep` по кешу не работает для CP1251 файлов - `TrackerParsers.swift` — боевой файл (используется в продакшне); `RutrackerAPI.swift` — legacy, параллельно существует ## Файлы - `Sources/MediaSources/APIs/TrackerParsers.swift` — все парсеры + registry (боевой) - `Sources/MediaSources/APIs/RutrackerAPI.swift` — legacy парсер (используется в части flow) - `Sources/MediaSources/Providers/TorrentSourceStrategy.swift` — dispatch через registry - `Sources/MediaDomain/Facts/TorrentPageMetadataFact.swift` — модель: title, year, genres, posterURL, description, externalLinks ## Roadmap - [ ] Шаг B — новые типы медиа: игры, музыка, книги (следующий) - [ ] Реальные HTML fixture тесты для каждого трекера (rutor, nnm-club) - [ ] Верификация RutorPageParser/NNMClubPageParser на живых торрентах - [ ] `MediaKind` detection по категории торрента (напр. "Игры PC" → `.game`)