Перейти к содержанию

neegde

neegde — десктопное приложение на Tauri 2 для стриминга музыки из двух источников: RuTracker (через BitTorrent) и SoulSeek (P2P). Эта вики документирует внутреннюю реализацию для разработчиков, которые хотят понять, проверить или доработать проект.

Исходный код: github.com/yepIwt/neegde-tauri.


Стек

Слой Технология
Десктопная оболочка Tauri 2
Фронтенд Vue 3 + TypeScript + Vite
Бэкенд Rust (tokio, reqwest, serde)
BitTorrent стриминг vozduxan — C++ библиотека поверх libtorrent
BitTorrent экспорт librqbit (Rust)
SoulSeek Кастомная Rust-реализация бинарного протокола SoulSeek

Как это работает, в одном абзаце

Пользователь вводит запрос. Rust-резолвер канонизирует его — превращает "нон стоп молли" в {artist: "Пошлая Молли", title: "Нон стоп"} — через Brave Search с Argon2id proof-of-work challenge. Поисковый движок отправляет канонический запрос в два провайдера параллельно: провайдер RuTracker получает HTML через аутентифицированную сессию, парсит торрент-топики и извлекает структуру альбомов/треков; провайдер SoulSeek отправляет поисковый пакет на SoulSeek-сервер и стримит ответы пиров по мере прихода. Результаты проходят пайплайн (normalize → dedup → score → filter) и рендерятся реактивно. При нажатии play: путь RuTracker вызывает vozduxan (C++, libtorrent), который открывает торрент, расставляет приоритеты кускам файла через deadline scheduling, поднимает локальный HTTP-сервер и возвращает localhost:… URL элементу <audio>. SoulSeek открывает прямое TCP-соединение с пиром-владельцем файла и делает то же самое.


Структура репозитория

neegde-tauri/
├── src/                         Vue 3 фронтенд (TypeScript)
│   ├── App.vue                  Оболочка приложения, главный layout
│   ├── search/                  Поисковый движок, сессия, провайдеры, пайплайн
│   ├── stores/                  Реактивное состояние (очередь, сущности, поиск, лайки)
│   ├── persistence/             Хелперы для сериализации в localStorage
│   ├── torrent/                 Обёртки Tauri-команд для стриминга
│   ├── soulseek/                SoulSeek API-обёртки для фронтенда
│   ├── rutracker/               Хелперы auth, поиска, сессии RuTracker
│   └── components/              Vue-компоненты (плеер, поиск, вид торрента)
└── src-tauri/
    ├── build.rs                 Вызов CMake для vozduxan
    └── src/
        ├── lib.rs               Tauri setup, managed state, invoke_handler
        ├── app_paths.rs         Канонические хелперы для путей данных
        ├── vozduxan_ffi.rs      Сырые unsafe C-биндинги
        ├── vozduxan_stream.rs   Безопасная Rust-обёртка, token buckets
        ├── resolver/            Резолвер intent-а запроса
        ├── rutracker/           HTTP-сессия, поиск, парсинг топиков
        ├── soulseek/            Реализация протокола SoulSeek
        ├── torrent_stream/      Экспорт + AppDebugLog (на librqbit)
        └── torrent_image.rs     Обложки через транзиентные librqbit-сессии

Основные подсистемы

Стриминг

Всё аудио воспроизводится через один из двух путей: vozduxan для контента RuTracker и прямая загрузка от пира для SoulSeek. Оба предоставляют фронтенду один интерфейс: localhost URL для элемента <audio>.

Путь vozduxan использует три token bucket-а — current_token, prefetch_token, warm_prefetch_token — управляемых в Rust, чтобы не вытеснить нужный закешированный стрим при навигации по очереди.

Резолвер запросов

Пользовательский ввод редко бывает чистым. Резолвер запускает Brave Search с ограничением site:genius.com, парсит заголовки возвращённых страниц для извлечения исполнителя и названия, и возвращает ResolveResult с каноническими парой и лейблом intent (track, artist, album, lyric, raw). Когда Brave отвечает 429 с JSON-телом challenge, резолвер решает Argon2id PoW локально и повторяет запрос.

Поисковый пайплайн

SearchEngine управляет одним SearchSession на уникальный запрос. Сессия запускает два async generator-а (провайдеры) параллельно, собирает их снапшоты в Map<providerKind, PipelineEntity[]>, мержит на каждом animation frame и прогоняет через пайплайн. Пайплайн выполняет дедупликацию (по id сущности, мержит списки источников) и фильтрацию; скоринг является заглушкой.

RuTracker

Auth использует настоящую phpBB-сессию: POST на эндпоинт входа с credentials в Windows-1251, куки персистируются через reqwest_cookie_store. Поиск парсит HTML листинга топиков, затем загружает тело поста каждого топика для извлечения магнет-ссылки, списка файлов, обложки и структурированных метаданных (год, кодек, тип рипа, жанр).

SoulSeek

Rust-реализация бинарного протокола SoulSeek: TCP-соединение с SoulSeek-сервером, хэндшейк логина, поиск файлов через length-prefixed бинарный пакет. Ответы пиров приходят инкрементально. Стриминг — прямая HTTP-загрузка от пира (или альтернативного пира при сбое основного). На каждый трек хранится до пяти запасных пиров.


Ключевые инварианты

Неочевидные ограничения, нарушение которых молча сломает работу:

  1. VozduxanStreamState, TorrentImageState и TorrentStreamState должны разделять один Arc<AppDebugLog>. Они создаются в lib.rs и Arc передаётся явно.

  2. VozduxanConfig.log_userdata — сырой указатель в Arc<AppDebugLog>, который держит живым VozduxanSessionInner._debug_log_arc. Не дропать Arc пока vozduxan-сессия жива.

  3. metadata_received_alert libtorrent не срабатывает когда atp.ti уже установлен (т.е. когда передан .torrent-файл, а не магнет). vozduxan обрабатывает этот случай внутри на C++. Не добавлять workaround на стороне Rust.

  4. build.rs перечисляет C++ исходники поштучно через cargo:rerun-if-changed, потому что macOS не обновляет mtime директории при изменении файла внутри.

  5. Brave source резолвера принудительно проверяет site:genius.com на стороне клиента — Brave трактует оператор нестрого и возвращает посторонние результаты (YouTube, Apple Music, блоги). brave.rs отбрасывает всё, чей заголовок страницы не заканчивается на Genius.


С чего начать