use std::{
ops::Deref,
sync::{Arc, Mutex},
};
use chrono::prelude::*;
use const_format::formatcp;
use reqwest::multipart;
use crate::{
errors::ApiError,
objects::{Action, AutoScores, ListAnime, ListAnimePut, ListStatus, UserListAnime},
rate_limit::RateLimit,
AnimeScheduleClient, API_URL, RUNTIME,
};
const API_ANIMELISTS_USERID_ROUTE: &str = formatcp!("{API_URL}/animelists/{{userId}}/{{route}}");
const API_ANIMELISTS_ROUTE: &str = formatcp!("{API_URL}/animelists/oauth/{{route}}");
const API_ANIMELISTS_USERID: &str = formatcp!("{API_URL}/animelists/{{userId}}");
const API_ANIMELISTS: &str = formatcp!("{API_URL}/animelists/oauth");
pub struct AnimeListsApi {
client: AnimeScheduleClient,
}
impl AnimeListsApi {
pub(crate) fn new(client: AnimeScheduleClient) -> Self {
Self { client }
}
pub fn get(&self) -> AnimeListsGet {
AnimeListsGet {
client: self.client.clone(),
user_id: None,
}
}
pub fn put(&self) -> AnimeListsPut {
AnimeListsPut {
client: self.client.clone(),
user_id: None,
overwrite_mal_list: false,
xml: None,
}
}
pub fn delete(&self) -> AnimeListsDelete {
AnimeListsDelete {
client: self.client.clone(),
route: None,
user_id: None,
}
}
}
pub struct AnimeListsGet {
client: AnimeScheduleClient,
user_id: Option<String>,
}
impl AnimeListsGet {
pub fn user_id(mut self, user_id: &str) -> Self {
self.user_id = Some(user_id.to_owned());
self
}
pub fn route(self, route: &str) -> AnimeListsGetRoute {
AnimeListsGetRoute {
client: self.client.clone(),
user_id: self.user_id,
route: route.to_owned(),
}
}
pub async fn send(mut self) -> Result<(RateLimit, UserListAnime), ApiError> {
let is_self = self.user_id.is_none();
let url = if let Some(user_id) = self.user_id {
API_ANIMELISTS_USERID.replace("{userId}", &user_id)
} else {
API_ANIMELISTS.to_owned()
};
self.client.http.get(url, is_self).await
}
pub fn send_blocking(self) -> Result<(RateLimit, UserListAnime), ApiError> {
RUNTIME.block_on(self.send())
}
}
#[derive(Debug)]
pub struct ETag(pub String);
impl Deref for ETag {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub struct AnimeListsGetRoute {
client: AnimeScheduleClient,
user_id: Option<String>,
route: String,
}
impl AnimeListsGetRoute {
pub fn user_id(mut self, user_id: &str) -> Self {
self.user_id = Some(user_id.to_owned());
self
}
pub async fn send(mut self) -> Result<(RateLimit, ETag, ListAnime), ApiError> {
let is_self = self.user_id.is_none();
let url = if let Some(user_id) = self.user_id {
API_ANIMELISTS_USERID_ROUTE
.replace("{userId}", &user_id)
.replace("{route}", &self.route)
} else {
API_ANIMELISTS_ROUTE.replace("{route}", &self.route)
};
let etag = Arc::new(Mutex::new(None));
let etag_clone = etag.clone();
self.client.http.response_cb(move |headers| {
let mut lock = etag_clone.lock().unwrap();
*lock = Some(ETag(
headers
.get("etag")
.and_then(|h| h.to_str().ok())
.unwrap_or_default()
.to_owned(),
));
});
let (limit, listanime) = self.client.http.get(url, is_self).await?;
let mut lock = etag.lock().unwrap();
Ok((limit, lock.take().unwrap(), listanime))
}
pub fn send_blocking(self) -> Result<(RateLimit, ETag, ListAnime), ApiError> {
RUNTIME.block_on(self.send())
}
}
pub struct AnimeListsPut {
client: AnimeScheduleClient,
user_id: Option<String>,
overwrite_mal_list: bool,
xml: Option<String>,
}
impl AnimeListsPut {
pub fn route(self, route: &str) -> AnimeListsPutRoute {
AnimeListsPutRoute {
client: self.client,
user_id: self.user_id,
route: route.to_owned(),
etag: None,
list: ListAnimePut::default(),
}
}
pub fn user_id(mut self, user_id: &str) -> Self {
self.user_id = Some(user_id.to_owned());
self
}
pub fn overwrite_mal_list(mut self, overwrite: bool) -> Self {
self.overwrite_mal_list = overwrite;
self
}
pub fn xml<S: Into<String>>(mut self, data: S) -> Self {
let data = data.into();
self.xml = Some(data);
self
}
pub async fn send(mut self) -> Result<RateLimit, ApiError> {
let url = if let Some(user_id) = self.user_id {
API_ANIMELISTS_USERID.replace("{userId}", &user_id)
} else {
API_ANIMELISTS.to_owned()
};
let Some(xml) = self.xml else {
return Err(ApiError::Xml);
};
self.client.http.request_cb(move |request| {
let part = multipart::Part::bytes(xml.clone().into_bytes())
.file_name("list.xml")
.mime_str("text/xml")
.unwrap();
let mut form = multipart::Form::new();
if self.overwrite_mal_list {
form = form.text("overwrite-mal-list", "on");
}
form = form.part("mal-list", part);
request.multipart(form)
});
let (limit, _) = self.client.http.put::<()>(url, true).await?;
Ok(limit)
}
pub fn send_blocking(self) -> Result<RateLimit, ApiError> {
RUNTIME.block_on(self.send())
}
}
pub struct AnimeListsPutRoute {
client: AnimeScheduleClient,
user_id: Option<String>,
etag: Option<String>,
route: String,
list: ListAnimePut,
}
impl AnimeListsPutRoute {
pub fn user_id(mut self, user_id: &str) -> Self {
self.user_id = Some(user_id.to_owned());
self
}
pub fn etag(mut self, etag: &str) -> Self {
self.etag = Some(etag.to_owned());
self
}
pub fn list_status(mut self, status: ListStatus) -> Self {
self.list.list_status = Some(status);
self
}
pub fn episodes_seen(mut self, seen: u64) -> Self {
self.list.episodes_seen = Some(seen);
self
}
pub fn manual_score(mut self, score: u8) -> Self {
self.list.manual_score = Some(score.clamp(0, 100));
self
}
pub fn use_auto_scores(mut self, use_auto_scores: bool) -> Self {
self.list.use_auto_scores = Some(use_auto_scores);
self
}
pub fn auto_scores(mut self, scores: AutoScores) -> Self {
self.list.auto_scores = Some(scores);
self
}
pub fn start_date<Tz: TimeZone>(mut self, datetime: DateTime<Tz>) -> Self {
let datetime = datetime.with_timezone(&datetime.offset().fix());
self.list.start_date = Some(datetime);
self
}
pub fn end_date<Tz: TimeZone>(mut self, datetime: DateTime<Tz>) -> Self {
let datetime = datetime.with_timezone(&datetime.offset().fix());
self.list.end_date = Some(datetime);
self
}
pub fn note(mut self, note: &str) -> Self {
let mut note = note.to_owned();
note.truncate(1000);
self.list.note = Some(note);
self
}
pub fn action(mut self, action: Action) -> Self {
self.list.action = Some(action);
self
}
pub async fn send(mut self) -> Result<RateLimit, ApiError> {
if self.etag.is_none() {
return Err(ApiError::Etag);
}
let url = if let Some(user_id) = self.user_id {
API_ANIMELISTS_USERID_ROUTE
.replace("{userId}", &user_id)
.replace("{route}", &self.route)
} else {
API_ANIMELISTS_ROUTE.replace("{route}", &self.route)
};
self.client.http.request_cb(move |request| {
request
.json(&self.list)
.header("ETag", self.etag.as_ref().unwrap())
});
let (limit, _) = self.client.http.put::<()>(url, true).await?;
Ok(limit)
}
pub fn send_blocking(self) -> Result<RateLimit, ApiError> {
RUNTIME.block_on(self.send())
}
}
pub struct AnimeListsDelete {
client: AnimeScheduleClient,
route: Option<String>,
user_id: Option<String>,
}
impl AnimeListsDelete {
pub fn user_id(mut self, user_id: &str) -> Self {
self.user_id = Some(user_id.to_owned());
self
}
pub fn route(mut self, route: &str) -> Self {
self.route = Some(route.to_owned());
self
}
pub async fn send(mut self) -> Result<RateLimit, ApiError> {
let Some(route) = self.route else {
return Err(ApiError::Route);
};
let url = if let Some(user_id) = self.user_id {
API_ANIMELISTS_USERID_ROUTE
.replace("{userId}", &user_id)
.replace("{route}", &route)
} else {
API_ANIMELISTS_ROUTE.replace("{route}", &route)
};
let (limit, _) = self.client.http.delete::<()>(url, true).await?;
Ok(limit)
}
pub fn send_blocking(self) -> Result<RateLimit, ApiError> {
RUNTIME.block_on(self.send())
}
}