mirror of
https://github.com/FluxaMedia/fluxa-core.git
synced 2026-08-19 13:35:53 +00:00
feat: add PublicMetaDB request-plan support alongside mdblist/trakt/simkl
Covers all 58 documented endpoints (resume, watch history, skip timestamps, ratings, episode ratings, highlights, external ID mappings, anime seasons, lists, picks, and voting) as stateless method/url/body plan builders, wired into core_invoke the same way mdblist_plan is.
This commit is contained in:
parent
bc42053405
commit
cd9e3cdba0
16 changed files with 987 additions and 2 deletions
|
|
@ -16,6 +16,7 @@ mod plan_misc_routes;
|
|||
mod player_policy_routes;
|
||||
mod player_scrobble_routes;
|
||||
mod profile_routes;
|
||||
mod publicmetadb_routes;
|
||||
mod resource_plan_routes;
|
||||
mod search_plan_routes;
|
||||
mod stream_policy_routes;
|
||||
|
|
@ -43,6 +44,7 @@ use plan_misc_routes::{
|
|||
use player_policy_routes::route_player_policy;
|
||||
use player_scrobble_routes::route_player_scrobble;
|
||||
use profile_routes::{route_profile_contract, route_profile_prefs};
|
||||
use publicmetadb_routes::route_publicmetadb;
|
||||
use resource_plan_routes::route_resource_plan;
|
||||
use search_plan_routes::route_search_plan;
|
||||
use stream_policy_routes::route_stream_policy;
|
||||
|
|
@ -57,8 +59,8 @@ use crate::{
|
|||
external_sync, headless_adapter_plan, headless_engine, home_ranking, integration_settings,
|
||||
intro_segments, library_state, mdblist_plan, nuvio_sync, offline_download, platform_plan,
|
||||
player_flow, player_policy, player_scrobble, plugins, profile_avatar_pack, profile_contract,
|
||||
profile_prefs, repository_flow, search_plan, stream_policy, tmdb_plan, trailer_subtitles,
|
||||
watchlist_plan,
|
||||
profile_prefs, publicmetadb_plan, repository_flow, search_plan, stream_policy, tmdb_plan,
|
||||
trailer_subtitles, watchlist_plan,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
|
|
@ -137,6 +139,7 @@ const ROUTERS: &[fn(&str, &str) -> Outcome] = &[
|
|||
route_external_sync_simkl,
|
||||
route_external_sync_anilist,
|
||||
route_mdblist,
|
||||
route_publicmetadb,
|
||||
route_anime_detection,
|
||||
route_library_state,
|
||||
route_nuvio_sync,
|
||||
|
|
|
|||
191
src/ffi/publicmetadb_routes.rs
Normal file
191
src/ffi/publicmetadb_routes.rs
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
use super::*;
|
||||
|
||||
pub(super) fn route_publicmetadb(method: &str, args_json: &str) -> Outcome {
|
||||
match method {
|
||||
"publicmetadbBearer" => Ok(Value::String(publicmetadb_plan::publicmetadb_bearer(
|
||||
&arg_str(args_json, "apiKey")?,
|
||||
))),
|
||||
|
||||
"publicmetadbResumeUrl" => Ok(Value::String(publicmetadb_plan::publicmetadb_resume_url(
|
||||
args_json,
|
||||
))),
|
||||
"publicmetadbResumeSavePlan" => {
|
||||
opt_json(publicmetadb_plan::publicmetadb_resume_save_plan(args_json))
|
||||
}
|
||||
"publicmetadbResumeBatchPlan" => {
|
||||
opt_json(publicmetadb_plan::publicmetadb_resume_batch_plan(args_json))
|
||||
}
|
||||
"publicmetadbResumeDeletePlan" => opt_json(
|
||||
publicmetadb_plan::publicmetadb_resume_delete_plan(&arg_str(args_json, "id")?),
|
||||
),
|
||||
|
||||
"publicmetadbWatchedUrl" => Ok(Value::String(publicmetadb_plan::publicmetadb_watched_url(
|
||||
args_json,
|
||||
))),
|
||||
"publicmetadbWatchedMarkPlan" => {
|
||||
let args = object(args_json)?;
|
||||
let dedupe = args.get("dedupe").and_then(Value::as_bool).unwrap_or(false);
|
||||
opt_json(publicmetadb_plan::publicmetadb_watched_mark_plan(
|
||||
args_json, dedupe,
|
||||
))
|
||||
}
|
||||
"publicmetadbWatchedEditDatePlan" => {
|
||||
let args = object(args_json)?;
|
||||
opt_json(publicmetadb_plan::publicmetadb_watched_edit_date_plan(
|
||||
field_str(&args, "id")?,
|
||||
args_json,
|
||||
))
|
||||
}
|
||||
"publicmetadbWatchedDeletePlan" => opt_json(
|
||||
publicmetadb_plan::publicmetadb_watched_delete_plan(&arg_str(args_json, "id")?),
|
||||
),
|
||||
"publicmetadbWatchedBulkDeletePlan" => opt_json(
|
||||
publicmetadb_plan::publicmetadb_watched_bulk_delete_plan(args_json),
|
||||
),
|
||||
|
||||
"publicmetadbSkipsUrl" => opt_str(publicmetadb_plan::publicmetadb_skips_url(args_json)),
|
||||
"publicmetadbSkipsCreatePlan" => {
|
||||
opt_json(publicmetadb_plan::publicmetadb_skips_create_plan(args_json))
|
||||
}
|
||||
"publicmetadbSkipsDeletePlan" => opt_json(
|
||||
publicmetadb_plan::publicmetadb_skips_delete_plan(&arg_str(args_json, "id")?),
|
||||
),
|
||||
|
||||
"publicmetadbRatingsUrl" => opt_str(publicmetadb_plan::publicmetadb_ratings_url(args_json)),
|
||||
"publicmetadbRatingsCreatePlan" => opt_json(
|
||||
publicmetadb_plan::publicmetadb_ratings_create_plan(args_json),
|
||||
),
|
||||
"publicmetadbRatingsDeletePlan" => opt_json(
|
||||
publicmetadb_plan::publicmetadb_ratings_delete_plan(&arg_str(args_json, "id")?),
|
||||
),
|
||||
|
||||
"publicmetadbEpisodeRatingsUrl" => opt_str(
|
||||
publicmetadb_plan::publicmetadb_episode_ratings_url(args_json),
|
||||
),
|
||||
"publicmetadbEpisodeRatingsCreatePlan" => opt_json(
|
||||
publicmetadb_plan::publicmetadb_episode_ratings_create_plan(args_json),
|
||||
),
|
||||
"publicmetadbEpisodeRatingsDeletePlan" => opt_json(
|
||||
publicmetadb_plan::publicmetadb_episode_ratings_delete_plan(&arg_str(args_json, "id")?),
|
||||
),
|
||||
"publicmetadbEpisodeRatingsBatchUrl" => opt_str(
|
||||
publicmetadb_plan::publicmetadb_episode_ratings_batch_url(args_json),
|
||||
),
|
||||
"publicmetadbEpisodeRatingsBatchCreatePlan" => {
|
||||
opt_json(publicmetadb_plan::publicmetadb_episode_ratings_batch_create_plan(args_json))
|
||||
}
|
||||
"publicmetadbEpisodeRatingsBatchDeletePlan" => {
|
||||
opt_json(publicmetadb_plan::publicmetadb_episode_ratings_batch_delete_plan(args_json))
|
||||
}
|
||||
|
||||
"publicmetadbHighlightsUrl" => {
|
||||
opt_str(publicmetadb_plan::publicmetadb_highlights_url(args_json))
|
||||
}
|
||||
"publicmetadbHighlightsCreatePlan" => opt_json(
|
||||
publicmetadb_plan::publicmetadb_highlights_create_plan(args_json),
|
||||
),
|
||||
"publicmetadbHighlightsDeletePlan" => opt_json(
|
||||
publicmetadb_plan::publicmetadb_highlights_delete_plan(&arg_str(args_json, "id")?),
|
||||
),
|
||||
|
||||
"publicmetadbMappingsUrl" => {
|
||||
opt_str(publicmetadb_plan::publicmetadb_mappings_url(args_json))
|
||||
}
|
||||
"publicmetadbMappingsLookupUrl" => opt_str(
|
||||
publicmetadb_plan::publicmetadb_mappings_lookup_url(args_json),
|
||||
),
|
||||
"publicmetadbMappingsCreatePlan" => opt_json(
|
||||
publicmetadb_plan::publicmetadb_mappings_create_plan(args_json),
|
||||
),
|
||||
"publicmetadbMappingsDeletePlan" => opt_json(
|
||||
publicmetadb_plan::publicmetadb_mappings_delete_plan(&arg_str(args_json, "id")?),
|
||||
),
|
||||
|
||||
"publicmetadbAnimeSeasonsUrl" => {
|
||||
opt_str(publicmetadb_plan::publicmetadb_anime_seasons_url(args_json))
|
||||
}
|
||||
"publicmetadbAnimeSeasonsSubmitPlan" => opt_json(
|
||||
publicmetadb_plan::publicmetadb_anime_seasons_submit_plan(args_json),
|
||||
),
|
||||
"publicmetadbAnimeSeasonsDeleteMappingPlan" => {
|
||||
opt_json(publicmetadb_plan::publicmetadb_anime_seasons_delete_mapping_plan(args_json))
|
||||
}
|
||||
"publicmetadbAnimeSeasonsDeleteChunkPlan" => opt_json(
|
||||
publicmetadb_plan::publicmetadb_anime_seasons_delete_chunk_plan(&arg_str(
|
||||
args_json, "id",
|
||||
)?),
|
||||
),
|
||||
|
||||
"publicmetadbListsUrl" => Ok(Value::String(publicmetadb_plan::publicmetadb_lists_url(
|
||||
args_json,
|
||||
))),
|
||||
"publicmetadbListsCreatePlan" => {
|
||||
opt_json(publicmetadb_plan::publicmetadb_lists_create_plan(args_json))
|
||||
}
|
||||
"publicmetadbListsDeletePlan" => opt_json(
|
||||
publicmetadb_plan::publicmetadb_lists_delete_plan(&arg_str(args_json, "listId")?),
|
||||
),
|
||||
"publicmetadbListItemsUrl" => {
|
||||
let args = object(args_json)?;
|
||||
opt_str(publicmetadb_plan::publicmetadb_list_items_url(
|
||||
field_str(&args, "listId")?,
|
||||
args_json,
|
||||
))
|
||||
}
|
||||
"publicmetadbListItemsAddPlan" => {
|
||||
let args = object(args_json)?;
|
||||
opt_json(publicmetadb_plan::publicmetadb_list_items_add_plan(
|
||||
field_str(&args, "listId")?,
|
||||
args_json,
|
||||
))
|
||||
}
|
||||
"publicmetadbListItemsRemovePlan" => {
|
||||
let args = object(args_json)?;
|
||||
opt_json(publicmetadb_plan::publicmetadb_list_items_remove_plan(
|
||||
field_str(&args, "listId")?,
|
||||
field_str(&args, "itemId")?,
|
||||
))
|
||||
}
|
||||
|
||||
"publicmetadbCatalogsUrl" => {
|
||||
Ok(Value::String(publicmetadb_plan::publicmetadb_catalogs_url()))
|
||||
}
|
||||
"publicmetadbCatalogItemsUrl" => {
|
||||
let args = object(args_json)?;
|
||||
opt_str(publicmetadb_plan::publicmetadb_catalog_items_url(
|
||||
field_str(&args, "id")?,
|
||||
args_json,
|
||||
))
|
||||
}
|
||||
|
||||
"publicmetadbVotesUrl" => {
|
||||
let args = object(args_json)?;
|
||||
let all = args.get("all").and_then(Value::as_bool).unwrap_or(false);
|
||||
opt_str(publicmetadb_plan::publicmetadb_votes_url(
|
||||
field_str(&args, "resource")?,
|
||||
field_str(&args, "itemId")?,
|
||||
all,
|
||||
))
|
||||
}
|
||||
"publicmetadbVotesCreatePlan" => {
|
||||
let args = object(args_json)?;
|
||||
opt_json(publicmetadb_plan::publicmetadb_votes_create_plan(
|
||||
field_str(&args, "resource")?,
|
||||
field_str(&args, "itemId")?,
|
||||
field_i64(&args, "vote")?,
|
||||
))
|
||||
}
|
||||
"publicmetadbVotesDeletePlan" => {
|
||||
let args = object(args_json)?;
|
||||
opt_json(publicmetadb_plan::publicmetadb_votes_delete_plan(
|
||||
field_str(&args, "resource")?,
|
||||
field_str(&args, "itemId")?,
|
||||
))
|
||||
}
|
||||
|
||||
_ => Err(fail(
|
||||
ErrorKind::UnknownMethod,
|
||||
format!("no such method `{method}`"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
|
@ -97,6 +97,8 @@ mod profile_contract;
|
|||
#[cfg(any(feature = "full-api", not(feature = "streaming-shared")))]
|
||||
mod profile_prefs;
|
||||
#[cfg(any(feature = "full-api", not(feature = "streaming-shared")))]
|
||||
mod publicmetadb_plan;
|
||||
#[cfg(any(feature = "full-api", not(feature = "streaming-shared")))]
|
||||
mod repository_flow;
|
||||
#[cfg(any(feature = "full-api", not(feature = "streaming-shared")))]
|
||||
mod search_plan;
|
||||
|
|
|
|||
219
src/publicmetadb_plan.rs
Normal file
219
src/publicmetadb_plan.rs
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
mod anime_seasons;
|
||||
mod catalogs;
|
||||
mod episode_ratings;
|
||||
mod helpers;
|
||||
mod highlights;
|
||||
mod lists;
|
||||
mod mappings;
|
||||
mod ratings;
|
||||
mod resume;
|
||||
mod skips;
|
||||
mod votes;
|
||||
mod watched;
|
||||
|
||||
pub(crate) use anime_seasons::{
|
||||
publicmetadb_anime_seasons_delete_chunk_plan, publicmetadb_anime_seasons_delete_mapping_plan,
|
||||
publicmetadb_anime_seasons_submit_plan, publicmetadb_anime_seasons_url,
|
||||
};
|
||||
pub(crate) use catalogs::{publicmetadb_catalog_items_url, publicmetadb_catalogs_url};
|
||||
pub(crate) use episode_ratings::{
|
||||
publicmetadb_episode_ratings_batch_create_plan, publicmetadb_episode_ratings_batch_delete_plan,
|
||||
publicmetadb_episode_ratings_batch_url, publicmetadb_episode_ratings_create_plan,
|
||||
publicmetadb_episode_ratings_delete_plan, publicmetadb_episode_ratings_url,
|
||||
};
|
||||
pub(crate) use helpers::publicmetadb_bearer;
|
||||
pub(crate) use highlights::{
|
||||
publicmetadb_highlights_create_plan, publicmetadb_highlights_delete_plan,
|
||||
publicmetadb_highlights_url,
|
||||
};
|
||||
pub(crate) use lists::{
|
||||
publicmetadb_list_items_add_plan, publicmetadb_list_items_remove_plan,
|
||||
publicmetadb_list_items_url, publicmetadb_lists_create_plan, publicmetadb_lists_delete_plan,
|
||||
publicmetadb_lists_url,
|
||||
};
|
||||
pub(crate) use mappings::{
|
||||
publicmetadb_mappings_create_plan, publicmetadb_mappings_delete_plan,
|
||||
publicmetadb_mappings_lookup_url, publicmetadb_mappings_url,
|
||||
};
|
||||
pub(crate) use ratings::{
|
||||
publicmetadb_ratings_create_plan, publicmetadb_ratings_delete_plan, publicmetadb_ratings_url,
|
||||
};
|
||||
pub(crate) use resume::{
|
||||
publicmetadb_resume_batch_plan, publicmetadb_resume_delete_plan, publicmetadb_resume_save_plan,
|
||||
publicmetadb_resume_url,
|
||||
};
|
||||
pub(crate) use skips::{
|
||||
publicmetadb_skips_create_plan, publicmetadb_skips_delete_plan, publicmetadb_skips_url,
|
||||
};
|
||||
pub(crate) use votes::{
|
||||
publicmetadb_votes_create_plan, publicmetadb_votes_delete_plan, publicmetadb_votes_url,
|
||||
};
|
||||
pub(crate) use watched::{
|
||||
publicmetadb_watched_bulk_delete_plan, publicmetadb_watched_delete_plan,
|
||||
publicmetadb_watched_edit_date_plan, publicmetadb_watched_mark_plan, publicmetadb_watched_url,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
#[test]
|
||||
fn builds_bearer_header() {
|
||||
assert_eq!(publicmetadb_bearer("pm-abc"), "Bearer pm-abc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_resume_requests() {
|
||||
assert_eq!(
|
||||
publicmetadb_resume_url(r#"{"tmdb_id":1399,"media_type":"tv"}"#),
|
||||
"https://publicmetadb.com/api/external/resume?tmdb_id=1399&media_type=tv"
|
||||
);
|
||||
let plan: Value = serde_json::from_str(
|
||||
&publicmetadb_resume_save_plan(
|
||||
r#"{"media_type":"movie","position_ms":1000,"runtime_ms":6000,"tmdb_id":42}"#,
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(plan["method"], "POST");
|
||||
assert_eq!(plan["url"], "https://publicmetadb.com/api/external/resume");
|
||||
assert_eq!(plan["body"]["tmdb_id"], 42);
|
||||
assert!(publicmetadb_resume_save_plan(r#"{"media_type":"movie"}"#).is_none());
|
||||
|
||||
let items = json!({ "items": [{ "tmdb_id": 1, "media_type": "movie", "position_ms": 1, "runtime_ms": 2 }] })
|
||||
.to_string();
|
||||
assert!(publicmetadb_resume_batch_plan(&items).is_some());
|
||||
assert!(publicmetadb_resume_batch_plan(r#"{"items":[]}"#).is_none());
|
||||
assert_eq!(
|
||||
publicmetadb_resume_delete_plan("abc123")
|
||||
.and_then(|p| serde_json::from_str::<Value>(&p).ok())
|
||||
.map(|p| p["url"].clone()),
|
||||
Some(json!("https://publicmetadb.com/api/external/resume/abc123"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_watched_requests() {
|
||||
let mark: Value = serde_json::from_str(
|
||||
&publicmetadb_watched_mark_plan(
|
||||
r#"{"tmdb_id":1,"media_type":"movie","watched_at":null}"#,
|
||||
false,
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(mark["body"]["watched_at"], Value::Null);
|
||||
let deduped: Value = serde_json::from_str(
|
||||
&publicmetadb_watched_mark_plan(r#"{"tmdb_id":1,"media_type":"movie"}"#, true).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(deduped["url"].as_str().unwrap().contains("dedupe=true"));
|
||||
|
||||
assert!(
|
||||
publicmetadb_watched_bulk_delete_plan(r#"{"tmdb_id":1,"media_type":"movie"}"#)
|
||||
.is_some()
|
||||
);
|
||||
assert!(publicmetadb_watched_bulk_delete_plan(r#"{"tmdb_id":1}"#).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_skips_requests() {
|
||||
assert_eq!(
|
||||
publicmetadb_skips_url(r#"{"tmdb_id":1399,"media_type":"tv","season":1,"episode":1}"#),
|
||||
Some(
|
||||
"https://publicmetadb.com/api/external/skips?tmdb_id=1399&media_type=tv&season=1&episode=1"
|
||||
.to_string()
|
||||
)
|
||||
);
|
||||
assert!(publicmetadb_skips_url(r#"{"tmdb_id":1399}"#).is_none());
|
||||
let create: Value = serde_json::from_str(
|
||||
&publicmetadb_skips_create_plan(
|
||||
r#"{"tmdb_id":1,"media_type":"tv","season":1,"episode":1,"intro_end_ms":60000}"#,
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(create["body"]["intro_end_ms"], 60000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_episode_ratings_batch_requests() {
|
||||
let ratings = json!({
|
||||
"tmdb_id": 1,
|
||||
"media_type": "tv",
|
||||
"season": 1,
|
||||
"ratings": [{ "episode": 1, "score": 90 }]
|
||||
})
|
||||
.to_string();
|
||||
assert!(publicmetadb_episode_ratings_batch_create_plan(&ratings).is_some());
|
||||
|
||||
let too_many = json!({
|
||||
"tmdb_id": 1,
|
||||
"media_type": "tv",
|
||||
"season": 1,
|
||||
"ratings": (0..51).map(|i| json!({ "episode": i, "score": 50 })).collect::<Vec<_>>()
|
||||
})
|
||||
.to_string();
|
||||
assert!(publicmetadb_episode_ratings_batch_create_plan(&too_many).is_none());
|
||||
|
||||
assert!(publicmetadb_episode_ratings_batch_delete_plan(r#"{"ids":["a","b"]}"#).is_some());
|
||||
assert!(publicmetadb_episode_ratings_batch_delete_plan(r#"{"ids":[]}"#).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_anime_seasons_requests() {
|
||||
let submit = json!({
|
||||
"tmdb_id": 1,
|
||||
"season_number": 1,
|
||||
"chunks": [{ "tmdb_season": 1 }]
|
||||
})
|
||||
.to_string();
|
||||
assert!(publicmetadb_anime_seasons_submit_plan(&submit).is_some());
|
||||
assert!(
|
||||
publicmetadb_anime_seasons_submit_plan(
|
||||
&json!({ "tmdb_id": 1, "season_number": 1, "chunks": [] }).to_string()
|
||||
)
|
||||
.is_none()
|
||||
);
|
||||
assert!(
|
||||
publicmetadb_anime_seasons_delete_mapping_plan(r#"{"tmdb_id":1,"season_number":1}"#)
|
||||
.is_some()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_votes_requests() {
|
||||
assert_eq!(
|
||||
publicmetadb_votes_url("skips", "skip123", true),
|
||||
Some("https://publicmetadb.com/api/external/skips/skip123/votes?all=true".to_string())
|
||||
);
|
||||
assert!(publicmetadb_votes_url("bogus", "id", false).is_none());
|
||||
assert!(publicmetadb_votes_create_plan("ratings", "r1", 1).is_some());
|
||||
assert!(publicmetadb_votes_create_plan("ratings", "r1", 2).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_lists_and_catalogs_requests() {
|
||||
assert_eq!(
|
||||
publicmetadb_catalogs_url(),
|
||||
"https://publicmetadb.com/api/external/catalogs"
|
||||
);
|
||||
assert_eq!(
|
||||
publicmetadb_catalog_items_url("pick1", r#"{"page":2}"#),
|
||||
Some("https://publicmetadb.com/api/external/catalogs/pick1/items?page=2".to_string())
|
||||
);
|
||||
assert!(
|
||||
publicmetadb_list_items_add_plan("", r#"{"tmdb_id":1,"media_type":"movie"}"#).is_none()
|
||||
);
|
||||
let add: Value = serde_json::from_str(
|
||||
&publicmetadb_list_items_add_plan("list1", r#"{"tmdb_id":1,"media_type":"movie"}"#)
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
add["url"],
|
||||
"https://publicmetadb.com/api/external/lists/list1/items"
|
||||
);
|
||||
}
|
||||
}
|
||||
52
src/publicmetadb_plan/anime_seasons.rs
Normal file
52
src/publicmetadb_plan/anime_seasons.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
use super::helpers::{body_from_keys, build_url, extract_query, parse_args, plan};
|
||||
use serde_json::Value;
|
||||
|
||||
pub(crate) fn publicmetadb_anime_seasons_url(query_json: &str) -> Option<String> {
|
||||
let args = parse_args(query_json);
|
||||
let params = extract_query(&args, &["tmdb_id"]);
|
||||
if params.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(build_url("/anime-seasons", ¶ms))
|
||||
}
|
||||
|
||||
fn chunks_are_valid(chunks: &[Value]) -> bool {
|
||||
!chunks.is_empty()
|
||||
&& chunks
|
||||
.iter()
|
||||
.all(|chunk| chunk.get("tmdb_season").is_some())
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_anime_seasons_submit_plan(args_json: &str) -> Option<String> {
|
||||
let args: Value = serde_json::from_str(args_json).ok()?;
|
||||
let chunks = args.get("chunks")?.as_array()?;
|
||||
if !chunks_are_valid(chunks) {
|
||||
return None;
|
||||
}
|
||||
let body = body_from_keys(
|
||||
&args,
|
||||
&["tmdb_id", "season_number", "chunks"],
|
||||
&["season_name"],
|
||||
)?;
|
||||
plan("POST", build_url("/anime-seasons", &[]), Some(body))
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_anime_seasons_delete_mapping_plan(query_json: &str) -> Option<String> {
|
||||
let args = parse_args(query_json);
|
||||
let params = extract_query(&args, &["tmdb_id", "season_number"]);
|
||||
if params.len() != 2 {
|
||||
return None;
|
||||
}
|
||||
plan("DELETE", build_url("/anime-seasons", ¶ms), None)
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_anime_seasons_delete_chunk_plan(id: &str) -> Option<String> {
|
||||
if id.is_empty() {
|
||||
return None;
|
||||
}
|
||||
plan(
|
||||
"DELETE",
|
||||
build_url(&format!("/anime-seasons/{id}"), &[]),
|
||||
None,
|
||||
)
|
||||
}
|
||||
13
src/publicmetadb_plan/catalogs.rs
Normal file
13
src/publicmetadb_plan/catalogs.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
use super::helpers::{build_url, extract_query, parse_args};
|
||||
|
||||
pub(crate) fn publicmetadb_catalogs_url() -> String {
|
||||
build_url("/catalogs", &[])
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_catalog_items_url(id: &str, query_json: &str) -> Option<String> {
|
||||
if id.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let params = extract_query(&parse_args(query_json), &["page"]);
|
||||
Some(build_url(&format!("/catalogs/{id}/items"), ¶ms))
|
||||
}
|
||||
84
src/publicmetadb_plan/episode_ratings.rs
Normal file
84
src/publicmetadb_plan/episode_ratings.rs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
use super::helpers::{body_from_keys, build_url, extract_query, parse_args, plan};
|
||||
use serde_json::Value;
|
||||
|
||||
pub(crate) fn publicmetadb_episode_ratings_url(query_json: &str) -> Option<String> {
|
||||
let args = parse_args(query_json);
|
||||
let params = extract_query(
|
||||
&args,
|
||||
&[
|
||||
"tmdb_id",
|
||||
"media_type",
|
||||
"season",
|
||||
"episode",
|
||||
"label",
|
||||
"perPage",
|
||||
],
|
||||
);
|
||||
if !params.iter().any(|(key, _)| key == "tmdb_id")
|
||||
|| !params.iter().any(|(key, _)| key == "media_type")
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some(build_url("/episode-ratings", ¶ms))
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_episode_ratings_create_plan(args_json: &str) -> Option<String> {
|
||||
let args: Value = serde_json::from_str(args_json).ok()?;
|
||||
let body = body_from_keys(
|
||||
&args,
|
||||
&["tmdb_id", "media_type", "season", "episode", "score"],
|
||||
&["label"],
|
||||
)?;
|
||||
plan("POST", build_url("/episode-ratings", &[]), Some(body))
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_episode_ratings_delete_plan(id: &str) -> Option<String> {
|
||||
if id.is_empty() {
|
||||
return None;
|
||||
}
|
||||
plan(
|
||||
"DELETE",
|
||||
build_url(&format!("/episode-ratings/{id}"), &[]),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_episode_ratings_batch_url(query_json: &str) -> Option<String> {
|
||||
let args = parse_args(query_json);
|
||||
let params = extract_query(&args, &["tmdb_id", "media_type", "season", "label"]);
|
||||
if !params.iter().any(|(key, _)| key == "tmdb_id")
|
||||
|| !params.iter().any(|(key, _)| key == "media_type")
|
||||
|| !params.iter().any(|(key, _)| key == "season")
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some(build_url("/episode-ratings/batch", ¶ms))
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_episode_ratings_batch_create_plan(args_json: &str) -> Option<String> {
|
||||
let args: Value = serde_json::from_str(args_json).ok()?;
|
||||
let ratings = args.get("ratings")?.as_array()?;
|
||||
if ratings.is_empty() || ratings.len() > 50 {
|
||||
return None;
|
||||
}
|
||||
let body = body_from_keys(
|
||||
&args,
|
||||
&["tmdb_id", "media_type", "season", "ratings"],
|
||||
&["label"],
|
||||
)?;
|
||||
plan("POST", build_url("/episode-ratings/batch", &[]), Some(body))
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_episode_ratings_batch_delete_plan(args_json: &str) -> Option<String> {
|
||||
let args: Value = serde_json::from_str(args_json).ok()?;
|
||||
let ids = args.get("ids")?.as_array()?;
|
||||
if ids.is_empty() || ids.len() > 50 {
|
||||
return None;
|
||||
}
|
||||
let body = body_from_keys(&args, &["ids"], &[])?;
|
||||
plan(
|
||||
"DELETE",
|
||||
build_url("/episode-ratings/batch", &[]),
|
||||
Some(body),
|
||||
)
|
||||
}
|
||||
79
src/publicmetadb_plan/helpers.rs
Normal file
79
src/publicmetadb_plan/helpers.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
use serde_json::{Map, Value, json};
|
||||
|
||||
const PUBLICMETADB_API_BASE_URL: &str = "https://publicmetadb.com/api/external";
|
||||
|
||||
pub(crate) fn publicmetadb_bearer(api_key: &str) -> String {
|
||||
format!("Bearer {api_key}")
|
||||
}
|
||||
|
||||
pub(crate) fn encode_query(value: &str) -> String {
|
||||
let mut out = String::with_capacity(value.len());
|
||||
for byte in value.bytes() {
|
||||
match byte {
|
||||
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
|
||||
out.push(byte as char)
|
||||
}
|
||||
_ => out.push_str(&format!("%{byte:02X}")),
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub(crate) fn build_url(path: &str, params: &[(String, String)]) -> String {
|
||||
let mut url = format!("{PUBLICMETADB_API_BASE_URL}{path}");
|
||||
let mut first = true;
|
||||
for (key, value) in params {
|
||||
if value.is_empty() {
|
||||
continue;
|
||||
}
|
||||
url.push(if first { '?' } else { '&' });
|
||||
first = false;
|
||||
url.push_str(key);
|
||||
url.push('=');
|
||||
url.push_str(&encode_query(value));
|
||||
}
|
||||
url
|
||||
}
|
||||
|
||||
pub(crate) fn plan(method: &str, url: String, body: Option<Value>) -> Option<String> {
|
||||
serde_json::to_string(&json!({ "method": method, "url": url, "body": body })).ok()
|
||||
}
|
||||
|
||||
pub(crate) fn value_to_query_string(value: &Value) -> Option<String> {
|
||||
match value {
|
||||
Value::String(s) if !s.is_empty() => Some(s.clone()),
|
||||
Value::Number(n) => Some(n.to_string()),
|
||||
Value::Bool(b) => Some(b.to_string()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn extract_query(args: &Value, keys: &[&str]) -> Vec<(String, String)> {
|
||||
keys.iter()
|
||||
.filter_map(|&key| {
|
||||
let raw = args.get(key)?;
|
||||
Some((key.to_string(), value_to_query_string(raw)?))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn parse_args(args_json: &str) -> Value {
|
||||
serde_json::from_str(args_json).unwrap_or(json!({}))
|
||||
}
|
||||
|
||||
// Copies `required` keys verbatim (failing the whole body if any is absent)
|
||||
// then any present `optional` keys — including explicit JSON `null`, which
|
||||
// several endpoints here use on purpose (e.g. watched_at: null for "unknown
|
||||
// date"), so this can't be a plain `Value::is_null` filter.
|
||||
pub(crate) fn body_from_keys(args: &Value, required: &[&str], optional: &[&str]) -> Option<Value> {
|
||||
let mut body = Map::new();
|
||||
for &key in required {
|
||||
body.insert(key.to_string(), args.get(key)?.clone());
|
||||
}
|
||||
for &key in optional {
|
||||
if let Some(value) = args.get(key) {
|
||||
body.insert(key.to_string(), value.clone());
|
||||
}
|
||||
}
|
||||
Some(Value::Object(body))
|
||||
}
|
||||
35
src/publicmetadb_plan/highlights.rs
Normal file
35
src/publicmetadb_plan/highlights.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
use super::helpers::{body_from_keys, build_url, extract_query, parse_args, plan};
|
||||
use serde_json::Value;
|
||||
|
||||
pub(crate) fn publicmetadb_highlights_url(query_json: &str) -> Option<String> {
|
||||
let args = parse_args(query_json);
|
||||
let params = extract_query(&args, &["tmdb_id", "media_type", "season", "episode"]);
|
||||
if !params.iter().any(|(key, _)| key == "tmdb_id")
|
||||
|| !params.iter().any(|(key, _)| key == "media_type")
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some(build_url("/highlights", ¶ms))
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_highlights_create_plan(args_json: &str) -> Option<String> {
|
||||
let args: Value = serde_json::from_str(args_json).ok()?;
|
||||
let body = body_from_keys(
|
||||
&args,
|
||||
&[
|
||||
"tmdb_id",
|
||||
"media_type",
|
||||
"highlight_start_ms",
|
||||
"highlight_end_ms",
|
||||
],
|
||||
&["season", "episode", "description"],
|
||||
)?;
|
||||
plan("POST", build_url("/highlights", &[]), Some(body))
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_highlights_delete_plan(id: &str) -> Option<String> {
|
||||
if id.is_empty() {
|
||||
return None;
|
||||
}
|
||||
plan("DELETE", build_url(&format!("/highlights/{id}"), &[]), None)
|
||||
}
|
||||
54
src/publicmetadb_plan/lists.rs
Normal file
54
src/publicmetadb_plan/lists.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
use super::helpers::{body_from_keys, build_url, extract_query, parse_args, plan};
|
||||
use serde_json::Value;
|
||||
|
||||
pub(crate) fn publicmetadb_lists_url(query_json: &str) -> String {
|
||||
build_url(
|
||||
"/lists",
|
||||
&extract_query(&parse_args(query_json), &["page", "perPage"]),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_lists_create_plan(args_json: &str) -> Option<String> {
|
||||
let args: Value = serde_json::from_str(args_json).ok()?;
|
||||
let body = body_from_keys(&args, &["name"], &["description", "is_public", "type"])?;
|
||||
plan("POST", build_url("/lists", &[]), Some(body))
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_lists_delete_plan(list_id: &str) -> Option<String> {
|
||||
if list_id.is_empty() {
|
||||
return None;
|
||||
}
|
||||
plan("DELETE", build_url(&format!("/lists/{list_id}"), &[]), None)
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_list_items_url(list_id: &str, query_json: &str) -> Option<String> {
|
||||
if list_id.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let params = extract_query(&parse_args(query_json), &["page", "perPage"]);
|
||||
Some(build_url(&format!("/lists/{list_id}/items"), ¶ms))
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_list_items_add_plan(list_id: &str, args_json: &str) -> Option<String> {
|
||||
if list_id.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let args: Value = serde_json::from_str(args_json).ok()?;
|
||||
let body = body_from_keys(&args, &["tmdb_id", "media_type"], &[])?;
|
||||
plan(
|
||||
"POST",
|
||||
build_url(&format!("/lists/{list_id}/items"), &[]),
|
||||
Some(body),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_list_items_remove_plan(list_id: &str, item_id: &str) -> Option<String> {
|
||||
if list_id.is_empty() || item_id.is_empty() {
|
||||
return None;
|
||||
}
|
||||
plan(
|
||||
"DELETE",
|
||||
build_url(&format!("/lists/{list_id}/items/{item_id}"), &[]),
|
||||
None,
|
||||
)
|
||||
}
|
||||
41
src/publicmetadb_plan/mappings.rs
Normal file
41
src/publicmetadb_plan/mappings.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
use super::helpers::{body_from_keys, build_url, extract_query, parse_args, plan};
|
||||
use serde_json::Value;
|
||||
|
||||
pub(crate) fn publicmetadb_mappings_url(query_json: &str) -> Option<String> {
|
||||
let args = parse_args(query_json);
|
||||
let params = extract_query(&args, &["tmdb_id", "media_type", "id_type"]);
|
||||
if !params.iter().any(|(key, _)| key == "tmdb_id")
|
||||
|| !params.iter().any(|(key, _)| key == "media_type")
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some(build_url("/mappings", ¶ms))
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_mappings_lookup_url(query_json: &str) -> Option<String> {
|
||||
let args = parse_args(query_json);
|
||||
let params = extract_query(&args, &["id_type", "id_value", "media_type"]);
|
||||
if !params.iter().any(|(key, _)| key == "id_type")
|
||||
|| !params.iter().any(|(key, _)| key == "id_value")
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some(build_url("/mappings/lookup", ¶ms))
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_mappings_create_plan(args_json: &str) -> Option<String> {
|
||||
let args: Value = serde_json::from_str(args_json).ok()?;
|
||||
let body = body_from_keys(
|
||||
&args,
|
||||
&["tmdb_id", "media_type", "id_type", "id_value"],
|
||||
&[],
|
||||
)?;
|
||||
plan("POST", build_url("/mappings", &[]), Some(body))
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_mappings_delete_plan(id: &str) -> Option<String> {
|
||||
if id.is_empty() {
|
||||
return None;
|
||||
}
|
||||
plan("DELETE", build_url(&format!("/mappings/{id}"), &[]), None)
|
||||
}
|
||||
26
src/publicmetadb_plan/ratings.rs
Normal file
26
src/publicmetadb_plan/ratings.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use super::helpers::{body_from_keys, build_url, extract_query, parse_args, plan};
|
||||
use serde_json::Value;
|
||||
|
||||
pub(crate) fn publicmetadb_ratings_url(query_json: &str) -> Option<String> {
|
||||
let args = parse_args(query_json);
|
||||
let params = extract_query(&args, &["tmdb_id", "media_type", "label"]);
|
||||
if !params.iter().any(|(key, _)| key == "tmdb_id")
|
||||
|| !params.iter().any(|(key, _)| key == "media_type")
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some(build_url("/ratings", ¶ms))
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_ratings_create_plan(args_json: &str) -> Option<String> {
|
||||
let args: Value = serde_json::from_str(args_json).ok()?;
|
||||
let body = body_from_keys(&args, &["tmdb_id", "media_type", "score"], &["label"])?;
|
||||
plan("POST", build_url("/ratings", &[]), Some(body))
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_ratings_delete_plan(id: &str) -> Option<String> {
|
||||
if id.is_empty() {
|
||||
return None;
|
||||
}
|
||||
plan("DELETE", build_url(&format!("/ratings/{id}"), &[]), None)
|
||||
}
|
||||
47
src/publicmetadb_plan/resume.rs
Normal file
47
src/publicmetadb_plan/resume.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
use super::helpers::{body_from_keys, build_url, extract_query, parse_args, plan};
|
||||
use serde_json::Value;
|
||||
|
||||
const RESUME_QUERY_KEYS: &[&str] = &[
|
||||
"tmdb_id",
|
||||
"media_type",
|
||||
"season",
|
||||
"episode",
|
||||
"id_type",
|
||||
"id_value",
|
||||
"page",
|
||||
"perPage",
|
||||
];
|
||||
|
||||
pub(crate) fn publicmetadb_resume_url(query_json: &str) -> String {
|
||||
build_url(
|
||||
"/resume",
|
||||
&extract_query(&parse_args(query_json), RESUME_QUERY_KEYS),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_resume_save_plan(args_json: &str) -> Option<String> {
|
||||
let args: Value = serde_json::from_str(args_json).ok()?;
|
||||
let body = body_from_keys(
|
||||
&args,
|
||||
&["media_type", "position_ms", "runtime_ms"],
|
||||
&["tmdb_id", "season", "episode", "id_type", "id_value"],
|
||||
)?;
|
||||
plan("POST", build_url("/resume", &[]), Some(body))
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_resume_batch_plan(args_json: &str) -> Option<String> {
|
||||
let args: Value = serde_json::from_str(args_json).ok()?;
|
||||
let items = args.get("items")?.as_array()?;
|
||||
if items.is_empty() || items.len() > 50 {
|
||||
return None;
|
||||
}
|
||||
let body = body_from_keys(&args, &["items"], &[])?;
|
||||
plan("POST", build_url("/resume/batch", &[]), Some(body))
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_resume_delete_plan(id: &str) -> Option<String> {
|
||||
if id.is_empty() {
|
||||
return None;
|
||||
}
|
||||
plan("DELETE", build_url(&format!("/resume/{id}"), &[]), None)
|
||||
}
|
||||
39
src/publicmetadb_plan/skips.rs
Normal file
39
src/publicmetadb_plan/skips.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
use super::helpers::{body_from_keys, build_url, extract_query, parse_args, plan};
|
||||
use serde_json::Value;
|
||||
|
||||
pub(crate) fn publicmetadb_skips_url(query_json: &str) -> Option<String> {
|
||||
let args = parse_args(query_json);
|
||||
let params = extract_query(
|
||||
&args,
|
||||
&["tmdb_id", "media_type", "season", "episode", "source"],
|
||||
);
|
||||
if !params.iter().any(|(key, _)| key == "tmdb_id")
|
||||
|| !params.iter().any(|(key, _)| key == "media_type")
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some(build_url("/skips", ¶ms))
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_skips_create_plan(args_json: &str) -> Option<String> {
|
||||
let args: Value = serde_json::from_str(args_json).ok()?;
|
||||
let body = body_from_keys(
|
||||
&args,
|
||||
&["tmdb_id", "media_type", "season", "episode"],
|
||||
&[
|
||||
"source",
|
||||
"intro_start_ms",
|
||||
"intro_end_ms",
|
||||
"credits_start_ms",
|
||||
"credits_end_ms",
|
||||
],
|
||||
)?;
|
||||
plan("POST", build_url("/skips", &[]), Some(body))
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_skips_delete_plan(id: &str) -> Option<String> {
|
||||
if id.is_empty() {
|
||||
return None;
|
||||
}
|
||||
plan("DELETE", build_url(&format!("/skips/{id}"), &[]), None)
|
||||
}
|
||||
45
src/publicmetadb_plan/votes.rs
Normal file
45
src/publicmetadb_plan/votes.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use super::helpers::{build_url, plan};
|
||||
use serde_json::json;
|
||||
|
||||
const VOTE_RESOURCES: &[&str] = &[
|
||||
"skips",
|
||||
"ratings",
|
||||
"highlights",
|
||||
"mappings",
|
||||
"anime-seasons",
|
||||
"episode-ratings",
|
||||
];
|
||||
|
||||
fn votes_path(resource: &str, item_id: &str) -> Option<String> {
|
||||
if item_id.is_empty() || !VOTE_RESOURCES.contains(&resource) {
|
||||
return None;
|
||||
}
|
||||
Some(format!("/{resource}/{item_id}/votes"))
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_votes_url(resource: &str, item_id: &str, all: bool) -> Option<String> {
|
||||
let path = votes_path(resource, item_id)?;
|
||||
let params = if all {
|
||||
vec![("all".to_string(), "true".to_string())]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
Some(build_url(&path, ¶ms))
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_votes_create_plan(
|
||||
resource: &str,
|
||||
item_id: &str,
|
||||
vote: i64,
|
||||
) -> Option<String> {
|
||||
let path = votes_path(resource, item_id)?;
|
||||
if !(-1..=1).contains(&vote) {
|
||||
return None;
|
||||
}
|
||||
plan("POST", build_url(&path, &[]), Some(json!({ "vote": vote })))
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_votes_delete_plan(resource: &str, item_id: &str) -> Option<String> {
|
||||
let path = votes_path(resource, item_id)?;
|
||||
plan("DELETE", build_url(&path, &[]), None)
|
||||
}
|
||||
55
src/publicmetadb_plan/watched.rs
Normal file
55
src/publicmetadb_plan/watched.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
use super::helpers::{body_from_keys, build_url, extract_query, parse_args, plan};
|
||||
use serde_json::Value;
|
||||
|
||||
pub(crate) fn publicmetadb_watched_url(query_json: &str) -> String {
|
||||
build_url(
|
||||
"/watched",
|
||||
&extract_query(&parse_args(query_json), &["page", "perPage"]),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_watched_mark_plan(args_json: &str, dedupe: bool) -> Option<String> {
|
||||
let args: Value = serde_json::from_str(args_json).ok()?;
|
||||
let body = body_from_keys(
|
||||
&args,
|
||||
&["tmdb_id", "media_type"],
|
||||
&["season", "episode", "watched_at"],
|
||||
)?;
|
||||
let params = if dedupe {
|
||||
vec![("dedupe".to_string(), "true".to_string())]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
plan("POST", build_url("/watched", ¶ms), Some(body))
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_watched_edit_date_plan(id: &str, args_json: &str) -> Option<String> {
|
||||
if id.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let args: Value = serde_json::from_str(args_json).ok()?;
|
||||
let body = body_from_keys(&args, &["watched_at"], &[])?;
|
||||
plan(
|
||||
"PATCH",
|
||||
build_url(&format!("/watched/{id}"), &[]),
|
||||
Some(body),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_watched_delete_plan(id: &str) -> Option<String> {
|
||||
if id.is_empty() {
|
||||
return None;
|
||||
}
|
||||
plan("DELETE", build_url(&format!("/watched/{id}"), &[]), None)
|
||||
}
|
||||
|
||||
pub(crate) fn publicmetadb_watched_bulk_delete_plan(query_json: &str) -> Option<String> {
|
||||
let args: Value = serde_json::from_str(query_json).ok()?;
|
||||
let params = extract_query(&args, &["tmdb_id", "media_type", "season", "episode"]);
|
||||
if !params.iter().any(|(key, _)| key == "tmdb_id")
|
||||
|| !params.iter().any(|(key, _)| key == "media_type")
|
||||
{
|
||||
return None;
|
||||
}
|
||||
plan("DELETE", build_url("/watched", ¶ms), None)
|
||||
}
|
||||
Loading…
Reference in a new issue