magnetease 0.2.0

A library to fetch magnets from the internet
Documentation
use async_trait::async_trait;
use reqwest::Client;
use scraper::{Html, Selector};

use crate::{Magnet, MagneteaseError, Provider};

pub struct Nyaa;

const URL: &str = "https://nyaa.si/";

#[async_trait]
impl Provider for Nyaa {
    async fn search(&self, client: &Client, phrase: &str) -> Result<Vec<Magnet>, MagneteaseError> {
        let params = [("q", phrase)];
        let html = client.get(URL).query(&params).send().await?.text().await?;

        let doc = Html::parse_document(&html);
        let torrent_table_selector = Selector::parse("tbody").unwrap();

        let torrent_table = doc
            .select(&torrent_table_selector)
            .next()
            .ok_or(MagneteaseError::HtmlError)?;

        let anchor_selector = Selector::parse("a").unwrap();
        let torrent_selector = Selector::parse("tr").unwrap();

        let mut magnets = vec![];

        for torrent in torrent_table.select(&torrent_selector) {
            let torrent_info: Vec<_> = torrent.child_elements().collect();

            let name = torrent_info
                .get(1)
                .ok_or(MagneteaseError::HtmlError)?
                .select(&anchor_selector)
                .last()
                .ok_or(MagneteaseError::HtmlError)?
                .inner_html();

            let url = torrent_info
                .get(2)
                .ok_or(MagneteaseError::HtmlError)?
                .select(&anchor_selector)
                .last()
                .ok_or(MagneteaseError::HtmlError)?
                .attr("href")
                .ok_or(MagneteaseError::HtmlError)?
                .to_string();

            let bytes = size_to_bytes(
                &torrent_info
                    .get(3)
                    .ok_or(MagneteaseError::HtmlError)?
                    .inner_html(),
            );

            let seeders: u32 = torrent_info
                .get(5)
                .ok_or(MagneteaseError::HtmlError)?
                .inner_html()
                .parse()
                .map_err(|_| MagneteaseError::HtmlError)?;

            let magnet = NyaaMagnet {
                url,
                name,
                seeders,
                bytes,
            };

            magnets.push(magnet);
        }
        Ok(magnets
            .into_iter()
            .map(|nyaa_magnet| nyaa_magnet.into())
            .collect())
    }
}

pub struct NyaaMagnet {
    url: String,
    name: String,
    seeders: u32,
    bytes: u64,
}

impl From<NyaaMagnet> for Magnet {
    fn from(value: NyaaMagnet) -> Self {
        Magnet {
            title: value.name,
            url: value.url,
            seeders: value.seeders,
            bytes: value.bytes,
        }
    }
}

fn size_to_bytes(size: &str) -> u64 {
    let kb = 1000.0;
    let mb = kb * 1000.0;
    let gb = mb * 1000.0;

    let (size, suffix) = size.split_once(' ').unwrap();

    let size: f64 = size.parse().unwrap();

    if suffix == "GiB" {
        (size * gb) as u64
    } else if suffix == "MiB" {
        (size * mb) as u64
    } else if suffix == "KiB" {
        (size * kb) as u64
    } else {
        todo!()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_search() {
        let client = Client::new();
        let magnets = Nyaa.search(&client, "lain").await.unwrap();
        assert!(magnets.len() == 75);
        assert!(!magnets[15].url.is_empty());
    }
}