diff --git a/Cargo.lock b/Cargo.lock index da5d416..ea6e489 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,6 +45,17 @@ dependencies = [ "tokio 1.19.2", ] +[[package]] +name = "async-recursion" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atom_syndication" version = "0.11.0" @@ -191,6 +202,7 @@ name = "cross-seed" version = "0.1.0" dependencies = [ "argmap", + "async-recursion", "bytes 1.1.0", "figment", "futures 0.3.21", @@ -820,8 +832,7 @@ dependencies = [ [[package]] name = "magnet-url" version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b4c4004e88aca00cc0c60782e5642c8fc628deca19e530ce58aa76e737d74" +source = "git+https://github.com/SeanOMik/magnet-url-rs.git?branch=main#9e1bf9a9a85e83b99b10c8475b0a4a10e6d897e8" dependencies = [ "lazy_static", "regex", diff --git a/Cargo.toml b/Cargo.toml index 5f929e2..b3ec59e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,12 +13,13 @@ futures = "0.3.21" toml = "0.5.9" lava_torrent = "0.7.0" # https://docs.rs/lava_torrent/0.7.0/lava_torrent/ torznab = "0.7.2" # https://docs.rs/torznab/0.7.2/torznab/ -magnet-url = "2.0.0" +magnet-url = { git = "https://github.com/SeanOMik/magnet-url-rs.git", branch = "main" } serde_with = "1.14.0" serde = { version = "1.0", features = ["derive"] } figment = { version = "0.10", features = ["toml", "env"] } wild = "2.0.4" argmap = "1.1.2" +async-recursion = "1.0.0" reqwest = {version = "0.11", default_features = false, features = ["gzip", "json", "rustls-tls"]} urlencoding = "2.1.0" diff --git a/src/main.rs b/src/main.rs index b7027cb..df17545 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use tracing::{info, Level, debug}; use std::path::{Path, PathBuf}; use std::error::Error; -use lava_torrent::torrent::v1::Torrent; +use lava_torrent::torrent::v1::{Torrent, AnnounceList}; use crate::torznab::{GenericSearchParameters, SearchFunction}; use crate::torznab::search_parameters::{GenericSearchParametersBuilder, MovieSearchParametersBuilder}; @@ -46,7 +46,7 @@ async fn main() { // Get config and debug the torrents let config = Config::new(); - info!("Searching torrents in: {}", config.torrents_path_str()); + info!("Searching for torrents in: {}", config.torrents_path_str()); let mut indexers = config.indexers.clone(); @@ -62,6 +62,7 @@ async fn main() { debug!(" Can Search: {:?}", indexer.client.as_ref().unwrap().capabilities.searching_capabilities); } + // Log the amount of torrents. let torrent_files = read_torrents(config.torrents_path()).unwrap(); info!("Found {} torrents", torrent_files.len()); @@ -77,6 +78,8 @@ async fn main() { let torrent = Arc::new(torrent); for indexer in indexers.iter() { + info!("Checking for \"{}\"", torrent.name); + let mut indexer = Arc::clone(indexer); let torrent = Arc::clone(&torrent); indexer_handles.push(tokio::spawn(async move { @@ -88,7 +91,46 @@ async fn main() { .build(); let results = client.search(SearchFunction::Search, generic).await.unwrap(); - //println!("Results: {:?}", results); + // The first result should be the correct one. + if let Some(result) = results.first() { + let found_torrent = result.download_torrent().await.unwrap(); + + if let Some(found_announces) = &found_torrent.announce_list { + // Some urls can be encoded so we need to decode to compare them. + let found_announces: Vec> = found_announces.iter() + .map(|a_list| a_list.iter().map(|a| urlencoding::decode(a).unwrap().to_string()).collect::>()) + .collect(); + + if let Some(torrent_announces) = &torrent.announce_list { + let mut found_announces_flat: Vec<&String> = Vec::new(); + for i in found_announces.iter() { + for j in i.iter() { + found_announces_flat.push(j); + } + } + + let mut flat_announces: Vec<&String> = Vec::new(); + for i in torrent_announces.iter() { + for j in i.iter() { + flat_announces.push(j); + } + } + + // Check if the announce urls from the found torrent are in the one + // that is on the file system. + let mut in_tracker = true; + for found_url in found_announces_flat.iter() { + in_tracker = in_tracker && flat_announces.contains(found_url); + } + + if !in_tracker { + info!("Found a cross-seedable torrent for {}", found_torrent.name); + } else { + debug!("Found the torrent in its original indexer, skipping..."); + } + } + } + } }, None => { panic!("idfk"); diff --git a/src/torznab/error.rs b/src/torznab/error.rs index e9c489d..ab252d4 100644 --- a/src/torznab/error.rs +++ b/src/torznab/error.rs @@ -1,7 +1,9 @@ #[derive(Debug)] pub enum ClientError { HttpError(reqwest::Error), - SearchResultError(super::ResultError) + SearchResultError(super::ResultError), + InvalidRedirect, + TorrentError(lava_torrent::LavaTorrentError), } impl From for ClientError { @@ -14,4 +16,10 @@ impl From for ClientError { fn from(e: super::ResultError) -> Self { ClientError::SearchResultError(e) } +} + +impl From for ClientError { + fn from(e: lava_torrent::LavaTorrentError) -> Self { + ClientError::TorrentError(e) + } } \ No newline at end of file diff --git a/src/torznab/torrent_result.rs b/src/torznab/torrent_result.rs index ddbf9bc..83710e2 100644 --- a/src/torznab/torrent_result.rs +++ b/src/torznab/torrent_result.rs @@ -1,15 +1,21 @@ +use lava_torrent::torrent::v1::Torrent; use rss::Item; +use async_recursion::async_recursion; + +use super::ClientError; + #[derive(Debug, Clone, PartialEq, Eq)] pub enum ResultError { MissingTitle, MissingLink, + InvalidRedirect, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct TorrentResult { - name: String, - link: String, + pub name: String, + pub link: String, /* size: u64, categories: Vec, */ } @@ -28,6 +34,40 @@ impl TorrentResult { categories, */ }) } + + #[async_recursion] + async fn download_impl(&self, client: &reqwest::Client, url: &str) -> Result { + let res = client + .get(&self.link) + .send().await?; + + if res.status() == 301 { + let headers = res.headers(); + if let Some(location) = headers.get(reqwest::header::LOCATION) { + let location = location.to_str().unwrap(); + + self.download_impl(client, location).await + } else { + Err(ClientError::InvalidRedirect) + } + } else { + let bytes = res.bytes().await?; + + if url.starts_with("magnet:?") { + let magnet = magnet_url::Magnet::new(url).unwrap(); + + todo!() // TODO + } else { + let torrent = Torrent::read_from_bytes(bytes)?; + + Ok(torrent) + } + } + } + + pub async fn download_torrent(&self) -> Result { + self.download_impl(&reqwest::Client::default(), &self.link).await + } } /* impl<'a> From for TorrentResult<'a> {