nekosbest 0.6.0

nekos.best API wrapper
Documentation
type E = Box<dyn std::error::Error>;

#[tokio::main]
async fn main() -> Result<(), E> {
    #[allow(unused_variables)]
    let client = reqwest::Client::new();
    #[cfg(feature = "local")]
    {
        use std::{collections::HashMap, convert::TryFrom, path::PathBuf, str::FromStr};

        use proc_macro2::TokenStream;
        use quote::{format_ident, quote, ToTokens, TokenStreamExt};

        #[derive(serde::Deserialize)]
        struct EndpointDescInternal {
            min: String,
            max: String,
            format: String,
        }

        #[derive(serde::Deserialize)]
        #[serde(try_from = "EndpointDescInternal")]
        struct EndpointDesc {
            min: usize,
            max: usize,
            format: String,
            with_padding: usize,
        }

        impl TryFrom<EndpointDescInternal> for EndpointDesc {
            type Error = <usize as FromStr>::Err;
        
            fn try_from(d: EndpointDescInternal) -> Result<Self, Self::Error> {
                Ok(Self {
                    min: d.min.parse()?,
                    max: d.max.parse()?,
                    format: d.format,
                    with_padding: d.max.len(),
                })
            }
        }
        

        const BASE_URL: &str = "https://nekos.best";

        async fn get_endpoints_data(
            client: &reqwest::Client,
        ) -> Result<HashMap<String, EndpointDesc>, E> {
            if let Ok(_) = std::env::var("DOCS_RS") {
                // we are on docs.rs

                let endpoints_json = r#"{"baka":{"min":"001","max":"014","format":"gif"},"cry":{"min":"001","max":"032","format":"gif"},"cuddle":{"min":"001","max":"026","format":"gif"},"dance":{"min":"001","max":"019","format":"gif"},"feed":{"min":"001","max":"017","format":"gif"},"hug":{"min":"001","max":"022","format":"gif"},"kiss":{"min":"001","max":"025","format":"gif"},"laugh":{"min":"001","max":"013","format":"gif"},"nekos":{"min":"0001","max":"0476","format":"jpg"},"pat":{"min":"001","max":"029","format":"gif"},"poke":{"min":"001","max":"017","format":"gif"},"slap":{"min":"001","max":"027","format":"gif"},"smile":{"min":"001","max":"016","format":"gif"},"smug":{"min":"001","max":"012","format":"gif"},"tickle":{"min":"001","max":"017","format":"gif"},"wave":{"min":"001","max":"017","format":"gif"}}"#;

                return Ok(serde_json::from_str(endpoints_json)?);
            }

            Ok(client
                .get(format!("{}/endpoints", BASE_URL))
                .send()
                .await?
                .json()
                .await?)
        }

        let endpoints = get_endpoints_data(&client).await?;

        struct CategoryData<'a>(&'a str, &'a EndpointDesc);

        impl<'a> ToTokens for CategoryData<'a> {
            fn to_tokens(&self, tokens: &mut TokenStream) {
                let name = format_ident!("{}{}", self.0[..1].to_uppercase(), &self.0[1..]);
                let min = self.1.min;
                let max = self.1.max;
                let format = &self.1.format;
                let with_padding = self.1.with_padding;

                tokens.append_all(quote! {
                    pub struct #name;

                    impl super::LocalNekosBestCategory for #name {
                        const CATEGORY: crate::Category = crate::Category::#name;
                        const MIN: usize = #min;
                        const MAX: usize = #max;
                        const WITH_PADDING: usize = #with_padding;
                        const FORMAT: &'static str = #format;
                    }

                    impl #name {
                        pub fn get_random(&self, random: usize) -> String {
                            <Self as super::LocalNekosBestCategory>::get_random(self, random)
                        }

                        pub fn get(&self) -> String {
                            <Self as super::LocalNekosBestCategory>::get(self)
                        }
                    }
                })
            }
        }

        let implementations = endpoints
            .into_iter()
            .map(|(name, endpoint)| {
                let category_data = CategoryData(&name, &endpoint);

                quote! {#category_data}
            })
            .collect::<TokenStream>();

        let implementations = implementations.to_string();

        std::fs::write(
            PathBuf::from(std::env::var("OUT_DIR")?).join("local_implementation.rs"),
            implementations,
        )?;
    }

    Ok(())
}