use crate::client::{params, PixivAppClient};
use crate::models::{Comment, Illustration, PixivResult};
use crate::PixivAppError;
use chrono::{DateTime, Utc};
use reqwest::header::CONTENT_TYPE;
use reqwest::Method;
use std::error::Error;
impl PixivAppClient {
pub async fn illust_details(
&self,
illust_id: u32,
) -> Result<Illustration, Box<dyn Error + Send + Sync>> {
let url = format!("{}/v1/illust/detail", self.host);
let req = self
.auth_request(Method::GET, url)
.await
.query(&[("illust_id", illust_id)]);
let res = self.process_request::<PixivResult>(req).await?;
res.illust.ok_or(res.error.map_or_else(
|| Box::<dyn Error + Sync + Send>::from(PixivAppError::Unknown),
|v| Box::new(v),
))
}
pub async fn illust_follow(
&self,
restrict: params::Visibility,
offset: u32,
) -> Result<Vec<Illustration>, Box<dyn Error + Send + Sync>> {
let url = format!("{}/v2/illust/follow", self.host);
let req = self.auth_request(Method::GET, url).await.query(&[
("restrict", restrict.to_string()),
("offset", offset.to_string()),
]);
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),
))
}
pub async fn illust_ranking(
&self,
mode: params::RankingMode,
date: Option<DateTime<Utc>>,
offset: Option<u32>,
) -> Result<Vec<Illustration>, Box<dyn Error + Send + Sync>> {
let url = format!("{}/v1/illust/ranking", self.host);
let mut params = vec![
("mode", mode.to_string()),
("offset", offset.unwrap_or(0).to_string()),
("filter", format!("for_{}", self.platform)),
];
if let Some(date) = date {
params.push(("date", date.format("%Y-%M-%d").to_string()))
}
let req = self.auth_request(Method::GET, url).await.query(¶ms);
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),
))
}
pub async fn illust_related(
&self,
illust_id: u32,
seed_illust_ids: Option<Vec<u32>>,
viewed: Option<Vec<u32>>,
offset: Option<u32>,
) -> Result<Vec<Illustration>, Box<dyn Error + Send + Sync>> {
let url = format!("{}/v2/illust/related", self.host);
let mut params = vec![
("illust_id", illust_id.to_string()),
("offset", offset.unwrap_or(0).to_string()),
("filter", format!("for_{}", self.platform)),
];
if let Some(seed_illust_ids) = seed_illust_ids {
for id in seed_illust_ids {
params.push(("seed_illust_ids[]", id.to_string()));
}
}
if let Some(viewed) = viewed {
for id in viewed {
params.push(("viewed[]", id.to_string()));
}
}
let req = self.auth_request(Method::GET, url).await.query(¶ms);
let res = self.process_request::<PixivResult>(req).await?;
res.illusts.ok_or(res.error.clone().map_or_else(
|| Box::<dyn Error + Sync + Send>::from(PixivAppError::Unknown),
|v| Box::new(v),
))
}
pub async fn illust_comments(
&self,
illust_id: u32,
include_total_comments: Option<bool>,
offset: Option<u32>,
) -> Result<Vec<Comment>, Box<dyn Error + Send + Sync>> {
let url = format!("{}/v3/illust/comments", self.host);
let mut params = vec![
("illust_id", illust_id.to_string()),
("offset", offset.unwrap_or(0).to_string()),
];
if let Some(include_total_comments) = include_total_comments {
params.push(("include_total_comments", include_total_comments.to_string()));
}
let req = self.auth_request(Method::GET, url).await.query(¶ms);
let res = self.process_request::<PixivResult>(req).await?;
res.comments.ok_or(res.error.clone().map_or_else(
|| Box::<dyn Error + Sync + Send>::from(PixivAppError::Unknown),
|v| Box::new(v),
))
}
pub async fn illust_recommended(
&self,
content_type: &'static str,
include_ranking_label: Option<bool>,
offset: Option<u32>,
) -> Result<Vec<Illustration>, Box<dyn Error + Send + Sync>> {
let url = format!("{}/v1/illust/recommended", self.host);
let params = vec![
("content_type", content_type.to_string()),
(
"include_ranking_label",
include_ranking_label.unwrap_or(false).to_string(),
),
("filter", format!("for_{}", self.platform)),
("offset", offset.unwrap_or(0).to_string()),
];
let req = self.auth_request(Method::GET, url).await.query(¶ms);
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),
))
}
pub async fn illust_bookmark_add(
&self,
illust_id: u32,
restrict: params::Visibility,
tags: Option<Vec<String>>,
) -> Result<(), Box<dyn Error + Send + Sync>> {
let url = format!("{}/v2/illust/bookmark/add", self.host);
let mut params = vec![
("illust_id", illust_id.to_string()),
("restrict", restrict.to_string()),
];
if let Some(tags) = tags {
params.push(("tags", tags.join(" ")));
}
let body = params
.iter()
.map(|e| format!("{}={}", e.0, e.1))
.collect::<Vec<_>>()
.join("&");
let req = self
.auth_request(Method::POST, url)
.await
.header(CONTENT_TYPE, "application/x-www-form-urlencoded")
.body(body);
let res = self.process_request::<PixivResult>(req).await?;
if let Some(error) = res.error {
Err(Box::new(error))
} else {
Ok(())
}
}
pub async fn illust_bookmark_delete(
&self,
illust_id: u32,
) -> Result<(), Box<dyn Error + Send + Sync>> {
let url = format!("{}/v1/illust/bookmark/delete", self.host);
let req = self
.auth_request(Method::POST, url)
.await
.header(CONTENT_TYPE, "application/x-www-form-urlencoded")
.body(format!("illust_id={}", illust_id));
let res = self.process_request::<PixivResult>(req).await?;
if let Some(error) = res.error {
Err(Box::new(error))
} else {
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use crate::client::tests::*;
use serial_test::serial;
use tokio::test;
use super::*;
#[test]
async fn test_details() {
let _illust = get_client().await.illust_details(ILLUST_ID).await;
let Ok(illust) = _illust else {
panic!(
"Failed to fetch details for illustration ID {}: {}",
ILLUST_ID,
_illust.err().unwrap()
);
};
assert_eq!(illust.id, ILLUST_ID);
}
#[test]
async fn test_follow() {
let result = get_client()
.await
.illust_follow(params::Visibility::Public, 0)
.await;
assert!(
result.is_ok(),
"Failed to fetch illustrations of followed users: {}",
result.err().unwrap()
);
}
#[test]
async fn test_ranking() {
let result = get_client()
.await
.illust_ranking(params::RankingMode::Day, None, None)
.await;
assert!(
result.is_ok(),
"Failed to fetch ranked illustrations: {}",
result.err().unwrap()
);
}
#[test]
async fn test_related() {
let result = get_client()
.await
.illust_related(ILLUST_ID, None, None, None)
.await;
assert!(
result.is_ok(),
"Failed to fetch related illustrations: {}",
result.err().unwrap()
)
}
#[test]
async fn test_recommended() {
let result = get_client()
.await
.illust_recommended("illust", None, None)
.await;
assert!(
result.is_ok(),
"Failed to fetch recommended illustrations: {}",
result.err().unwrap()
)
}
#[test]
#[serial]
async fn test_adding_bookmarks() {
let result = get_client()
.await
.illust_bookmark_add(ILLUST_ID, params::Visibility::Public, None)
.await;
assert!(
result.is_ok(),
"Failed to bookmark: {}",
result.err().unwrap()
)
}
#[test]
#[serial]
async fn test_removing_bookmarks() {
let result = get_client().await.illust_bookmark_delete(ILLUST_ID).await;
assert!(
result.is_ok(),
"Failed to remove bookmark: {}",
result.err().unwrap()
)
}
#[test]
async fn test_fetching_comments() {
let result = get_client()
.await
.illust_comments(134633764, None, None)
.await;
assert!(
result.is_ok(),
"Failed to remove bookmark: {}",
result.err().unwrap()
);
}
}