diff --git a/Cargo.lock b/Cargo.lock index fc83203..d2d59cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -913,6 +913,7 @@ dependencies = [ "chrono", "md5", "reqwest", + "serde", "serde_json", "tokio", ] diff --git a/libpixiv/Cargo.toml b/libpixiv/Cargo.toml index 0602fe4..78d47cf 100644 --- a/libpixiv/Cargo.toml +++ b/libpixiv/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" chrono = "0.4.38" md5 = "0.7.0" reqwest = "0.12.7" +serde = { version = "1.0.210", features = ["derive"] } serde_json = "1.0.128" tokio = { version = "1.40.0", features = ["full"] } diff --git a/libpixiv/src/lib.rs b/libpixiv/src/lib.rs index fb46142..bd4d530 100644 --- a/libpixiv/src/lib.rs +++ b/libpixiv/src/lib.rs @@ -2,18 +2,19 @@ extern crate md5; extern crate reqwest; extern crate serde_json; -use chrono::{Local}; -use serde_json::{Value}; +use chrono::Local; +use serde_json::Value; use std::sync::{Arc, Mutex}; -use reqwest::header::{HeaderName, USER_AGENT, CONTENT_TYPE}; +use reqwest::header::{HeaderName, AUTHORIZATION, CONTENT_TYPE, USER_AGENT}; +mod models; struct PixivAppClient { /// bearer token access_token: Arc>, refresh_token: Arc>, http_client: reqwest::Client, - host: &str, + host: String, } impl PixivAppClient { @@ -22,7 +23,7 @@ impl PixivAppClient { access_token: Arc::new(Mutex::new(String::new())), refresh_token: Arc::new(Mutex::new(String::from(token))), http_client: reqwest::Client::new(), - host: "https://app-api.pixiv.net/" + host: String::from("https://app-api.pixiv.net"), }; client } @@ -53,14 +54,6 @@ impl PixivAppClient { .build() .expect("failed to build login request"); - if let Some(body) = req.body() { - if let Some(bytes) = body.as_bytes() { - println!("Body: {}", String::from_utf8_lossy(bytes)); - } else { - println!("Body: Non-text data or stream"); - } - } - let r = match self.http_client .execute(req) .await { @@ -75,13 +68,52 @@ impl PixivAppClient { self.access_token = Arc::new(Mutex::new(String::from(d["response"]["access_token"].as_str().unwrap()))); self.refresh_token = Arc::new(Mutex::new(String::from(d["response"]["refresh_token"].as_str().unwrap()))); } + + pub async fn illust_details(&self, illust_id: u32) -> Result> { + let url = format!("{}/v1/illust/detail", self.host); + let illust_id_str = illust_id.to_string(); + + let req = self.http_client + .get(url) + .header(AUTHORIZATION, format!("Bearer {}", self.access_token.lock().unwrap())) + .header(HeaderName::from_lowercase(b"app-os").unwrap(), "ios") + .header(HeaderName::from_lowercase(b"app-os-version").unwrap(), "12.2") + .header(HeaderName::from_lowercase(b"app-version").unwrap(), "7.6.2") + .header(USER_AGENT, "PixivIOSApp/7.6.2 (iOS 12.2; iPhone9,1)") + .query(&[("illust_id", illust_id_str.as_str())]) + .build() + .expect("failed to build request"); + + let r = self.http_client + .execute(req) + .await + .unwrap() + .text() + .await + .unwrap(); + + println!("{}", r); + + let deserialized = match serde_json::from_str::(&r) { + Ok(r) => r, + Err(e) => return Err(Box::from(e)), + }; + + + + match deserialized.illust { + Some(r) => Ok(r), + None => Err(Box::from(deserialized.error.unwrap())) + } + } + } #[cfg(test)] mod client_tests { use super::*; - use std::{assert, panic, env}; + use std::{assert, assert_eq, panic, env}; #[tokio::test] async fn login() { @@ -90,10 +122,19 @@ mod client_tests { client.refresh_token().await; let cloned_access_token = Arc::clone(&client.access_token); match cloned_access_token.lock() { - Ok(t) => assert!(!t.is_empty()), + Ok(t) => assert!(!t.is_empty(), "Expected to receive token!"), Err(_) => panic!("No token received!"), }; } - + #[tokio::test] + async fn illust_details() { + let illust_id = 122388293; + let token = env::var("PIXIV_REFRESH_TOKEN"); + let mut client = PixivAppClient::new(&token.expect("expecting PIXIV_REFRESH_TOKEN variable for testing!")); + client.refresh_token().await; + let illust = client.illust_details(illust_id).await; + assert!(illust.is_ok(), "Expected illustration data: {:#?}", illust); + assert_eq!(illust.unwrap().id, illust_id); + } } \ No newline at end of file diff --git a/libpixiv/src/models.rs b/libpixiv/src/models.rs new file mode 100644 index 0000000..6745db0 --- /dev/null +++ b/libpixiv/src/models.rs @@ -0,0 +1,84 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, std::fmt::Debug)] +pub struct Illustration { + pub id: u32, + pub tags: Vec, + pub visible: bool, + pub r#type: String, + pub title: String, + pub caption: String, + pub height: u32, + pub width: u32, + pub page_count: i32, + pub user: User, + pub tools: Vec, + pub series: Option, + pub restrict: i32, + pub x_restrict: i32, + pub image_urls: IllustrationURLs, + pub meta_pages: Vec, + pub total_view: u32, + pub total_bookmarks: u32, + pub is_bookmarked: bool, + pub is_muted: bool, + pub total_comments: u32, + pub illust_ai_type: u32, + pub illust_book_style: u32, + pub comment_access_control: u32, + pub create_date: String, +} + +#[derive(Serialize, Deserialize, std::fmt::Debug)] +pub struct User { + id: i32, + name: String, + account: String, + profile_image_urls: UserProfileImages, + is_followed: bool +} + +#[derive(Serialize, Deserialize, std::fmt::Debug)] +pub struct UserProfileImages { + medium: String, +} + +#[derive(Serialize, Deserialize, std::fmt::Debug)] +pub struct IllustrationURLsWrapper { + image_urls: IllustrationURLs, +} + +#[derive(Serialize, Deserialize, std::fmt::Debug)] +pub struct IllustrationURLs { + square_medium: String, + medium: String, + large: String, + original: Option, +} + +#[derive(Serialize, Deserialize, std::fmt::Debug)] +pub struct Tag { + name: String, + translated_name: Option, +} + +#[derive(Serialize, Deserialize, std::fmt::Debug)] +pub struct PixivResult { + pub illust: Option, + pub illusts: Option>, + pub error: Option, +} + +#[derive(Serialize, Deserialize, std::fmt::Debug)] +pub struct PixivError { + user_message: String, + message: String, + reason: String, +} + +impl std::error::Error for PixivError {} +impl std::fmt::Display for PixivError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}: {}", self.user_message, self.reason) + } +} \ No newline at end of file