use crate::{API_URL, Extended, MediaIds};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
pub const USER_ACTIVITIES: &str = "https://api.simkl.com/sync/activities";
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
pub struct SyncSettings {
pub all: DateTime<Utc>,
}
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
pub struct MediaActivity {
pub all: DateTime<Utc>,
pub rated_at: DateTime<Utc>,
#[serde(rename = "plantowatch")]
pub plan_to_watch: DateTime<Utc>,
pub watching: Option<DateTime<Utc>>,
pub completed: DateTime<Utc>,
pub hold: Option<DateTime<Utc>>,
pub dropped: DateTime<Utc>,
pub removed_from_list: DateTime<Utc>,
}
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
pub struct Activities {
pub all: DateTime<Utc>,
pub settings: SyncSettings,
pub tv_shows: MediaActivity,
pub anime: MediaActivity,
pub movies: MediaActivity,
}
pub const USER_ITEMS: &str = "https://api.simkl.com/sync/all-items/";
pub fn get_all_items_request(
what: Option<String>,
from: Option<DateTime<Utc>>,
_extended: Option<Extended>,
) -> String {
let mut result = String::from(USER_ITEMS);
if let Some(w) = what {
result.push_str(&w);
result.push('/');
}
if let Some(d) = from {
result.push_str("?date_from=");
result.push_str(&d.to_rfc3339());
}
result
}
pub fn get_add_to_history_request() -> String {
let mut result = String::from(API_URL);
result.push_str("/sync/history");
result
}
pub fn get_remove_from_history_request() -> String {
let mut result = String::from(API_URL);
result.push_str("/sync/history/remove");
result
}
pub fn get_add_ratings_request() -> String {
let mut result = String::from(API_URL);
result.push_str("/sync/ratings");
result
}
pub fn get_remove_ratings_request() -> String {
let mut result = String::from(API_URL);
result.push_str("/sync/ratings/remove");
result
}
pub fn get_add_to_list_request() -> String {
let mut result = String::from(API_URL);
result.push_str("/sync/add-to-list");
result
}
pub fn get_check_if_watched_request() -> String {
let mut result = String::from(API_URL);
result.push_str("/sync/watched");
result
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct WatchedSeason {
pub number: u32,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub episodes: Vec<WatchedEpisode>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WatchedEpisode {
pub number: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub watched_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MovieHistoryItem {
pub ids: MediaIds,
#[serde(skip_serializing_if = "Option::is_none")]
pub watched_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShowHistoryItem {
pub ids: MediaIds,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub seasons: Vec<WatchedSeason>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub episodes: Vec<WatchedEpisode>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnimeHistoryItem {
pub ids: MediaIds,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub seasons: Vec<WatchedSeason>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub episodes: Vec<WatchedEpisode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub watched_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SyncHistoryPayload {
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub movies: Vec<MovieHistoryItem>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub shows: Vec<ShowHistoryItem>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub anime: Vec<AnimeHistoryItem>,
}
impl SyncHistoryPayload {
pub fn new() -> Self {
Self::default()
}
pub fn with_movie(mut self, item: MovieHistoryItem) -> Self {
self.movies.push(item);
self
}
pub fn with_show(mut self, item: ShowHistoryItem) -> Self {
self.shows.push(item);
self
}
pub fn with_anime(mut self, item: AnimeHistoryItem) -> Self {
self.anime.push(item);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MovieRatingItem {
pub ids: MediaIds,
pub rating: u8,
#[serde(skip_serializing_if = "Option::is_none")]
pub rated_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShowRatingItem {
pub ids: MediaIds,
pub rating: u8,
#[serde(skip_serializing_if = "Option::is_none")]
pub rated_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnimeRatingItem {
pub ids: MediaIds,
pub rating: u8,
#[serde(skip_serializing_if = "Option::is_none")]
pub rated_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SyncRatingPayload {
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub movies: Vec<MovieRatingItem>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub shows: Vec<ShowRatingItem>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub anime: Vec<AnimeRatingItem>,
}
impl SyncRatingPayload {
pub fn new() -> Self {
Self::default()
}
pub fn with_movie(mut self, item: MovieRatingItem) -> Self {
self.movies.push(item);
self
}
pub fn with_show(mut self, item: ShowRatingItem) -> Self {
self.shows.push(item);
self
}
pub fn with_anime(mut self, item: AnimeRatingItem) -> Self {
self.anime.push(item);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MovieListItem {
pub ids: MediaIds,
pub to: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShowListItem {
pub ids: MediaIds,
pub to: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnimeListItem {
pub ids: MediaIds,
pub to: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SyncListPayload {
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub movies: Vec<MovieListItem>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub shows: Vec<ShowListItem>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub anime: Vec<AnimeListItem>,
}
impl SyncListPayload {
pub fn new() -> Self {
Self::default()
}
pub fn with_movie(mut self, item: MovieListItem) -> Self {
self.movies.push(item);
self
}
pub fn with_show(mut self, item: ShowListItem) -> Self {
self.shows.push(item);
self
}
pub fn with_anime(mut self, item: AnimeListItem) -> Self {
self.anime.push(item);
self
}
}
#[derive(Debug, Clone, Deserialize, Default)]
pub struct SyncAddedCounts {
pub movies: Option<u32>,
pub shows: Option<u32>,
pub episodes: Option<u32>,
pub anime: Option<u32>,
}
#[derive(Debug, Clone, Deserialize, Default)]
pub struct SyncNotFound {
pub movies: Option<Vec<MediaIds>>,
pub shows: Option<Vec<MediaIds>>,
pub episodes: Option<Vec<MediaIds>>,
pub anime: Option<Vec<MediaIds>>,
}
#[derive(Debug, Clone, Deserialize, Default)]
pub struct SyncResult {
pub added: Option<SyncAddedCounts>,
pub deleted: Option<SyncAddedCounts>,
pub not_found: Option<SyncNotFound>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sync_history_payload_serialise_movie() {
let payload = SyncHistoryPayload::new().with_movie(MovieHistoryItem {
ids: MediaIds::new().with_imdb("tt0816692".to_string()),
watched_at: None,
});
let json = serde_json::to_string(&payload).unwrap();
assert!(json.contains("\"movies\""));
assert!(json.contains("tt0816692"));
assert!(!json.contains("\"shows\""));
assert!(!json.contains("\"anime\""));
}
#[test]
fn test_sync_history_payload_serialise_show_with_seasons() {
let payload = SyncHistoryPayload::new().with_show(ShowHistoryItem {
ids: MediaIds::new().with_simkl(12345),
seasons: vec![WatchedSeason {
number: 1,
episodes: vec![WatchedEpisode {
number: 1,
watched_at: None,
}],
}],
episodes: vec![],
});
let json = serde_json::to_string(&payload).unwrap();
assert!(json.contains("\"shows\""));
assert!(json.contains("\"seasons\""));
assert!(json.contains("\"episodes\""));
}
#[test]
fn test_sync_rating_payload_serialise() {
let payload = SyncRatingPayload::new().with_movie(MovieRatingItem {
ids: MediaIds::new().with_imdb("tt0111161".to_string()),
rating: 10,
rated_at: None,
});
let json = serde_json::to_string(&payload).unwrap();
assert!(json.contains("\"rating\":10"));
assert!(json.contains("tt0111161"));
assert!(!json.contains("\"shows\""));
}
#[test]
fn test_sync_list_payload_serialise() {
let payload = SyncListPayload::new().with_show(ShowListItem {
ids: MediaIds::new().with_simkl(999),
to: "plantowatch".to_string(),
});
let json = serde_json::to_string(&payload).unwrap();
assert!(json.contains("\"plantowatch\""));
assert!(json.contains("\"shows\""));
assert!(!json.contains("\"movies\""));
}
#[test]
fn test_get_add_to_history_url() {
assert_eq!(
get_add_to_history_request(),
"https://api.simkl.com/sync/history"
);
}
#[test]
fn test_get_remove_from_history_url() {
assert_eq!(
get_remove_from_history_request(),
"https://api.simkl.com/sync/history/remove"
);
}
#[test]
fn test_get_add_ratings_url() {
assert_eq!(
get_add_ratings_request(),
"https://api.simkl.com/sync/ratings"
);
}
#[test]
fn test_get_add_to_list_url() {
assert_eq!(
get_add_to_list_request(),
"https://api.simkl.com/sync/add-to-list"
);
}
}