libpixiv 0.1.2

pixiv.net API client library
Documentation
use crate::client::params::SearchMode;
use crate::client::{params::SortMode, PixivAppClient};
use crate::models::{Illustration, Novel, PixivResult, PixivUserResult, Tag};
use crate::PixivAppError;
use chrono::{DateTime, Utc};
use reqwest::Method;
use std::error::Error;

impl PixivAppClient {
    /// Search tags
    pub async fn search_tag(&self, word: String) -> Result<Vec<Tag>, Box<dyn Error + Send + Sync>> {
        let url = format!("{}/v2/search/autocomplete", self.host);

        let req = self
            .auth_request(Method::GET, url)
            .await
            .query(&[("word", word), ("filter", format!("for_{}", self.platform))]);
        let res = self.process_request::<PixivResult>(req).await?;
        res.tags.ok_or(res.error.map_or_else(
            || Box::<dyn Error + Sync + Send>::from(PixivAppError::Unknown),
            |v| Box::new(v),
        ))
    }

    /// Search users
    pub async fn search_user(
        &self,
        word: String,
        sort: SortMode,
        duration: Option<&'static str>,
        offset: Option<u32>,
    ) -> Result<Vec<PixivUserResult>, Box<dyn Error + Send + Sync>> {
        let url = format!("{}/v1/search/user", self.host);

        let mut params = vec![
            ("word", word),
            ("sort", sort.to_string()),
            ("filter", format!("for_{}", self.platform)),
            ("offset", offset.unwrap_or(0).to_string()),
        ];

        if let Some(duration) = duration {
            params.push(("duration", duration.to_string()));
        }

        let req = self.auth_request(Method::GET, url).await.query(&params);

        let res = self.process_request::<PixivResult>(req).await?;
        res.user_previews.ok_or(res.error.map_or_else(
            || Box::<dyn Error + Sync + Send>::from(PixivAppError::Unknown),
            |v| Box::new(v),
        ))
    }

    /// Search novels
    pub async fn search_novel(
        &self,
        word: String,
        search_targets: SearchMode,
        sort: SortMode,
        merge_plain_keyword_results: Option<bool>,
        include_translated_tag_results: Option<bool>,
        start_date: Option<DateTime<Utc>>,
        end_date: Option<DateTime<Utc>>,
        offset: Option<u32>,
    ) -> Result<Vec<Novel>, Box<dyn Error + Send + Sync>> {
        let url = format!("{}/v1/search/novel", self.host);

        let mut params = vec![
            ("word", word),
            ("search_targets", search_targets.to_string()),
            ("sort", sort.to_string()),
            (
                "merge_plain_keyword_results",
                merge_plain_keyword_results.unwrap_or(true).to_string(),
            ),
            (
                "include_translated_tag_results",
                include_translated_tag_results.unwrap_or(true).to_string(),
            ),
            ("filter", format!("for_{}", self.platform)),
            ("offset", offset.unwrap_or(0).to_string()),
        ];
        if let Some(start_date) = start_date {
            params.push(("start_date", start_date.format("%Y-%M-%d").to_string()));
        }
        if let Some(end_date) = end_date {
            params.push(("start_date", end_date.format("%Y-%M-%d").to_string()));
        }

        let req = self.auth_request(Method::GET, url).await.query(&params);
        let res = self.process_request::<PixivResult>(req).await?;
        res.novels.ok_or(res.error.map_or_else(
            || Box::<dyn Error + Sync + Send>::from(PixivAppError::Unknown),
            |v| Box::new(v),
        ))
    }

    /// Search illustrations
    ///
    /// An implementation for use of the preview api will follow,
    /// currently only users with premium can search by popularity.
    pub async fn search_illust(
        &self,
        word: String,
        search_targets: SearchMode,
        sort: SortMode,
        duration: Option<&'static str>,
        start_date: Option<DateTime<Utc>>,
        end_date: Option<DateTime<Utc>>,
        offset: Option<u32>,
    ) -> Result<Vec<Illustration>, Box<dyn Error + Send + Sync>> {
        let url = format!("{}/v1/search/illust", self.host);

        let mut params = vec![
            ("word", word),
            ("search_targets", search_targets.to_string()),
            ("sort", sort.to_string()),
            ("filter", format!("for_{}", self.platform)),
            ("offset", offset.unwrap_or(0).to_string()),
        ];
        if let Some(duration) = duration {
            params.push(("duration", duration.to_string()))
        }
        if let Some(start_date) = start_date {
            params.push(("start_date", start_date.format("%Y-%M-%d").to_string()));
        }
        if let Some(end_date) = end_date {
            params.push(("start_date", end_date.format("%Y-%M-%d").to_string()));
        }

        let req = self.auth_request(Method::GET, url).await.query(&params);
        let res = self.process_request::<PixivResult>(req).await?;
        res.illusts.ok_or(res.error.map_or_else(
            || Box::<dyn Error + Sync + Send>::from(PixivAppError::Unknown),
            |v| Box::new(v),
        ))
    }
}

#[cfg(test)]
mod tests {
    use crate::client::{
        params::{SearchMode, SortMode},
        tests::*,
    };
    use tokio::test;

    #[test]
    async fn test_tag_search() {
        let result = get_client()
            .await
            .search_tag(String::from("wallpaper"))
            .await;
        let Ok(_) = result else {
            panic!("Failed to search tag: {}", result.err().unwrap());
        };
    }

    #[test]
    async fn test_illustration_search() {
        let result = get_client()
            .await
            .search_illust(
                String::from("wallpaper"),
                SearchMode::PartialMatchForTags,
                SortMode::PopularDescending,
                None,
                None,
                None,
                None,
            )
            .await;
        let Ok(_) = result else {
            panic!("Failed to search tag: {}", result.err().unwrap());
        };
    }

    #[test]
    async fn test_user_search() {
        let result = get_client()
            .await
            .search_user(String::from(USER), SortMode::DateAscending, None, None)
            .await;
        let Ok(_) = result else {
            panic!("Failed to search user: {}", result.err().unwrap());
        };
    }

    #[test]
    async fn test_novel_search() {
        let result = get_client()
            .await
            .search_novel(
                String::from("foo"),
                SearchMode::PartialMatchForTags,
                SortMode::PopularDescending,
                None,
                None,
                None,
                None,
                None,
            )
            .await;
        let Ok(_) = result else {
            panic!("Failed to search novel: {}", result.err().unwrap());
        };
    }
}