libpixiv 0.2.5

pixiv.net API client library
Documentation
use crate::client::{params, PixivAppClient};
use crate::models::{Illustration, Novel, PixivResult, PixivUserResult, PixivUserStateResult};
use crate::PixivAppError;
use reqwest_middleware::reqwest::{header::CONTENT_TYPE, Method};
use serde_json::Value;
use std::error::Error;

impl PixivAppClient {
    pub async fn user(&self) -> Result<PixivUserStateResult, Box<dyn Error + Send + Sync>> {
        let url = format!("{}/v1/user/me/state", self.host);

        let req = self.http_client.request(Method::GET, url);

        self.process_request::<PixivUserStateResult>(req).await
    }

    /// Fetch Value
    pub async fn user_list(
        &self,
        illust_id: u32,
        offset: Option<u32>,
    ) -> Result<Vec<Value>, Box<dyn Error + Send + Sync>> {
        let url = format!("{}/v2/user/list", self.host);

        let req = self.http_client.request(Method::GET, url).query(&[
            ("user_id", illust_id.to_string()),
            ("filter", format!("for_{}", self.platform)),
            ("offset", offset.unwrap_or(0).to_string()),
        ]);

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

    /// Fetch user data
    pub async fn user_detail(
        &self,
        user_id: u32,
    ) -> Result<PixivUserResult, Box<dyn Error + Send + Sync>> {
        let url = format!("{}/v1/user/detail", self.host);

        let req = self.http_client.request(Method::GET, url).query(&[
            ("user_id", user_id.to_string()),
            ("filter", format!("for_{}", self.platform)),
        ]);

        self.process_request::<PixivUserResult>(req).await
    }

    /// Fetch users following some user
    pub async fn user_followers(
        &self,
        user_id: u32,
        offset: Option<u32>,
    ) -> Result<Vec<PixivUserResult>, Box<dyn Error + Send + Sync>> {
        let url = format!("{}/v1/user/follower", self.host);

        let req = self.http_client.request(Method::GET, url).query(&[
            ("user_id", user_id.to_string()),
            ("filter", format!("for_{}", self.platform)),
            ("offset", offset.unwrap_or(0).to_string()),
        ]);

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

    /// Fetch the users some user is following
    pub async fn user_following(
        &self,
        user_id: u32,
        offset: Option<u32>,
    ) -> Result<Vec<PixivUserResult>, Box<dyn Error + Send + Sync>> {
        let url = format!("{}/v1/user/following", self.host);

        let req = self.http_client.request(Method::GET, url).query(&[
            ("user_id", user_id.to_string()),
            ("filter", format!("for_{}", self.platform)),
            ("offset", offset.unwrap_or(0).to_string()),
        ]);

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

    /// Fetch the novels some user has uploaded
    pub async fn user_novels(
        &self,
        user_id: u32,
        offset: Option<u32>,
    ) -> Result<Vec<Novel>, Box<dyn Error + Send + Sync>> {
        let url = format!("{}/v1/user/novels", self.host);

        let req = self.http_client.request(Method::GET, url).query(&[
            ("user_id", user_id.to_string()),
            ("filter", format!("for_{}", self.platform)),
            ("offset", offset.unwrap_or(0).to_string()),
        ]);

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

    /// Fetch the illustrations some user has uploaded
    pub async fn user_illusts(
        &self,
        user_id: u32,
        r#type: &str,
        offset: Option<u32>,
    ) -> Result<Vec<Illustration>, Box<dyn Error + Send + Sync>> {
        let url = format!("{}/v1/user/illusts", self.host);

        let req = self.http_client.request(Method::GET, url).query(&[
            ("user_id", user_id.to_string()),
            ("type", r#type.to_string()),
            ("filter", format!("for_{}", self.platform)),
            ("offset", offset.unwrap_or(0).to_string()),
        ]);

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

    /// Fetch some mypixiv user
    pub async fn user_mypixiv(
        &self,
        user_id: u32,
        offset: Option<u32>,
    ) -> Result<Value, Box<dyn Error + Send + Sync>> {
        let url = format!("{}/v1/user/mypixiv", self.host);

        let req = self.http_client.request(Method::GET, url).query(&[
            ("user_id", user_id.to_string()),
            ("offset", offset.unwrap_or(0).to_string()),
        ]);

        self.process_request::<Value>(req).await
    }

    /// Fetch users related to some user
    pub async fn user_related(
        &self,
        seed_user_id: u32,
        offset: Option<u32>,
    ) -> Result<Vec<PixivUserResult>, Box<dyn Error + Send + Sync>> {
        let url = format!("{}/v1/user/related", self.host);

        let req = self.http_client.request(Method::GET, url).query(&[
            ("filter", format!("for_{}", self.platform)),
            ("offset", offset.unwrap_or(0).to_string()),
            ("seed_user_id", seed_user_id.to_string()),
        ]);

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

    /// Follow some user
    pub async fn user_follow_add(
        &self,
        user_id: u32,
        restrict: params::Visibility,
    ) -> Result<(), Box<dyn Error + Send + Sync>> {
        let url = format!("{}/v1/user/follow/add", self.host);

        let body = [
            ("user_id", user_id.to_string()),
            ("restrict", restrict.to_string()),
        ]
        .iter()
        .map(|e| format!("{}={}", e.0, e.1))
        .collect::<Vec<_>>()
        .join("&");
        let req = self
            .http_client
            .request(Method::POST, url)
            .header(CONTENT_TYPE, "application/x-www-form-urlencoded")
            .body(body);

        self.process_request::<PixivResult>(req).await?;
        Ok(())
    }

    /// Unfollow some user
    pub async fn user_follow_delete(
        &self,
        user_id: u32,
    ) -> Result<(), Box<dyn Error + Send + Sync>> {
        let url = format!("{}/v1/user/follow/delete", self.host);

        let req = self
            .http_client
            .request(Method::POST, url)
            .header(CONTENT_TYPE, "application/x-www-form-urlencoded")
            .body(format!("user_id={}", user_id));

        self.process_request::<PixivResult>(req).await?;
        Ok(())
    }

    /// Fetch the content some user has bookmarked
    pub async fn user_bookmarks(
        &self,
        user_id: u32,
        restrict: params::Visibility,
        offset: Option<u32>,
        max_bookmark: Option<u32>,
        tag: Option<String>,
    ) -> Result<Vec<Illustration>, Box<dyn Error + Send + Sync>> {
        let url = format!("{}/v1/user/bookmarks/illust", self.host);
        let mut params = vec![
            ("user_id", user_id.to_string()),
            ("restrict", restrict.to_string()),
            ("filter", format!("for_{}", self.platform)),
            ("offset", offset.unwrap_or(0).to_string()),
        ];
        if let Some(max_bookmark) = max_bookmark {
            params.push(("max_bookmark_id", max_bookmark.to_string()));
        }
        if let Some(tag) = tag {
            params.push(("tag", tag));
        }

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

    /// Fetch the available bookmarking tags
    pub async fn user_bookmark_tags(
        &self,
        restrict: params::Visibility,
        offset: Option<u32>,
    ) -> Result<PixivResult, Box<dyn Error + Send + Sync>> {
        let url = format!("{}/v1/user/bookmark-tags/illust", self.host);

        let req = self.http_client.request(Method::GET, url).query(&[
            ("restrict", restrict.to_string()),
            ("offset", offset.unwrap_or(0).to_string()),
        ]);

        self.process_request::<PixivResult>(req).await
    }
}

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

    #[test]
    async fn test_user() {
        let result = get_client().user().await;
        assert!(
            result.is_ok(),
            "Failed to fetch user list: {}",
            result.err().unwrap()
        );
    }

    #[test]
    async fn test_user_listing() {
        let result = get_client().user_list(USER_ID, None).await;
        assert!(
            result.is_ok(),
            "Failed to fetch user list: {}",
            result.err().unwrap()
        );
    }

    #[test]
    async fn test_user_details() {
        let result = get_client().user_detail(USER_ID).await;
        assert!(
            result.is_ok(),
            "Failed to fetch user details: {}",
            result.err().unwrap()
        );
        assert_eq!(u32::try_from(result.unwrap().user.id).unwrap(), USER_ID);
    }

    #[test]
    async fn test_user_followers() {
        let result = get_client().user_followers(USER_ID, None).await;
        assert!(
            result.is_ok(),
            "Failed to fetch user followers: {}",
            result.err().unwrap()
        );
    }

    #[test]
    async fn test_user_following() {
        let result = get_client().user_following(USER_ID, None).await;
        assert!(
            result.is_ok(),
            "Failed to fetch following users for user: {}",
            result.err().unwrap()
        );
    }

    #[test]
    async fn test_user_novels() {
        let result = get_client().user_novels(USER_ID, None).await;
        assert!(
            result.is_ok(),
            "Failed to fetch novels for user: {}",
            result.err().unwrap()
        );
    }

    #[test]
    async fn test_user_illusts() {
        let result = get_client().user_illusts(USER_ID, "illust", None).await;
        assert!(
            result.is_ok(),
            "Failed to fetch user illustrations: {}",
            result.err().unwrap()
        );
    }

    #[test]
    async fn test_related_users() {
        let result = get_client().user_related(USER_ID, None).await;
        assert!(
            result.is_ok(),
            "Failed to fetch related users: {}",
            result.err().unwrap()
        );
    }

    #[test]
    async fn test_following() {
        let result = get_client()
            .user_follow_add(USER_ID, params::Visibility::Public)
            .await;
        assert!(
            result.is_ok(),
            "Failed to follow user: {}",
            result.err().unwrap()
        );
    }

    #[test]
    async fn test_unfollowing() {
        assert!(get_client()
            .user_follow_add(USER_ID, params::Visibility::Public)
            .await
            .is_ok());
        let result = get_client().user_follow_delete(USER_ID).await;
        assert!(
            result.is_ok(),
            "Failed to unfollow user: {}",
            result.err().unwrap()
        );
    }

    #[test]
    async fn test_user_bookmarks() {
        let result = get_client()
            .user_bookmarks(USER_ID, params::Visibility::Public, None, None, None)
            .await;
        assert!(
            result.is_ok(),
            "Failed to follow user: {}",
            result.err().unwrap()
        );
    }
}