Files
music-agregator/src/magnet_resolver.rs
T
2026-07-19 17:36:38 +02:00

75 lines
2.4 KiB
Rust

//! Magnet-link resolver backed by librqbit.
//!
//! Turns a `magnet:` URI into the same bencoded torrent bytes a `.torrent`
//! download would yield, by resolving metadata over the BitTorrent DHT and
//! the BEP-9 `ut_metadata` peer extension. The bytes are then handed to
//! [`crate::torrent::TorrentParser`] so magnet and torrent sources are
//! parsed identically.
use std::sync::Arc;
use std::time::Duration;
use anyhow::{Context, bail};
use librqbit::{
AddTorrent, AddTorrentOptions, AddTorrentResponse, ListOnlyResponse, Session, SessionOptions,
};
use tempfile::TempDir;
const RESOLVE_TIMEOUT: Duration = Duration::from_secs(90);
pub struct MagnetResolver {
session: Arc<Session>,
_scratch_dir: TempDir,
}
impl std::fmt::Debug for MagnetResolver {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MagnetResolver").finish_non_exhaustive()
}
}
impl MagnetResolver {
pub async fn new() -> anyhow::Result<Self> {
let scratch_dir = tempfile::TempDir::new()?;
let opts = SessionOptions {
disable_dht_persistence: true,
persistence: None,
enable_upnp_port_forwarding: false,
..Default::default()
};
let session = Session::new_with_opts(scratch_dir.path().to_path_buf(), opts).await?;
Ok(Self {
session,
_scratch_dir: scratch_dir,
})
}
pub async fn resolve(&self, magnet: &str) -> anyhow::Result<Vec<u8>> {
let added = tokio::time::timeout(
RESOLVE_TIMEOUT,
self.session.add_torrent(
AddTorrent::from_url(magnet),
Some(AddTorrentOptions {
list_only: true,
..Default::default()
}),
),
)
.await
.context("magnet resolution timed out (>90s, likely no peers)")?
.context("error adding magnet to session")?;
match added {
AddTorrentResponse::ListOnly(ListOnlyResponse { torrent_bytes, .. }) => {
Ok(torrent_bytes.to_vec())
}
AddTorrentResponse::AlreadyManaged(_, handle) => {
handle.with_metadata(|m| m.torrent_bytes.to_vec())
}
AddTorrentResponse::Added(_, _) => {
bail!("torrent unexpectedly added under list_only")
}
}
}
}