mirror of
https://github.com/FluxaMedia/fluxa-core.git
synced 2026-08-18 13:05:54 +00:00
Read removed/_mtime from Stremio library items; add watchlist extraction
Kotlin's LibraryItem DTO never declared removed/_mtime, so even though this file already worked on raw JSON Values, those fields never survived the trip from the Stremio datastore response. Adds libraryWatchlistItems (id/name/type/poster/background/updatedAtMs, excluding removed items) so the pull side can finally see Stremio library membership as a timestamped list, matching the shape the mergeWatchlistTimestamped bridge expects. Also fixes libraryContinueWatchingItems, which never checked "removed" at all -- a removed item with leftover progress state could still surface as continue-watching.
This commit is contained in:
parent
5f43a8c11a
commit
25b3ca73a0
3 changed files with 61 additions and 3 deletions
11
src/ffi.rs
11
src/ffi.rs
|
|
@ -2,12 +2,14 @@ use serde_json::{json, Value};
|
|||
|
||||
use crate::{
|
||||
addon_protocol, addon_resource, addon_store, anime_detection, app_state, calendar_plan,
|
||||
content_identity, core_contract, data_policy, discovery_plan, dolby_vision_rpu,
|
||||
external_sync, headless_adapter_plan, headless_engine, home_ranking, intro_segments,
|
||||
content_identity, core_contract, data_policy, discovery_plan, external_sync,
|
||||
headless_adapter_plan, headless_engine, home_ranking, intro_segments,
|
||||
library_state, nuvio_sync, offline_download, platform_plan, player_flow, player_policy,
|
||||
player_scrobble, plugins, profile_contract, profile_prefs, repository_flow, search_plan,
|
||||
stream_policy, tmdb_plan, watchlist_plan,
|
||||
};
|
||||
#[cfg(feature = "native")]
|
||||
use crate::dolby_vision_rpu;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ErrorKind {
|
||||
|
|
@ -96,6 +98,7 @@ const ROUTERS: &[fn(&str, &str) -> Outcome] = &[
|
|||
route_headless_adapter_plan,
|
||||
route_discovery_plan,
|
||||
route_data_policy,
|
||||
#[cfg(feature = "native")]
|
||||
route_dolby_vision_rpu,
|
||||
route_player_flow,
|
||||
route_player_scrobble,
|
||||
|
|
@ -1353,6 +1356,9 @@ fn route_library_state(method: &str, args_json: &str) -> Outcome {
|
|||
"libraryContinueWatchingItems" => opt_json(
|
||||
library_state::library_continue_watching_items_json(args_json),
|
||||
),
|
||||
"libraryWatchlistItems" => opt_json(
|
||||
library_state::library_watchlist_items_json(args_json),
|
||||
),
|
||||
"normalizeLibraryDocument" => {
|
||||
into_json(library_state::normalize_library_document_json(args_json))
|
||||
}
|
||||
|
|
@ -1850,6 +1856,7 @@ fn route_data_policy(method: &str, args_json: &str) -> Outcome {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "native")]
|
||||
fn route_dolby_vision_rpu(method: &str, args_json: &str) -> Outcome {
|
||||
match method {
|
||||
// args_json IS the request object for both of these
|
||||
|
|
|
|||
|
|
@ -116,7 +116,9 @@ pub(crate) fn library_continue_watching_items_json(items_json: &str) -> Option<S
|
|||
let mut items: Vec<Value> = serde_json::from_str(items_json).ok()?;
|
||||
items.retain(|item| {
|
||||
let state = item.get("state").unwrap_or(&Value::Null);
|
||||
!state.is_null()
|
||||
let removed = item.get("removed").and_then(Value::as_bool).unwrap_or(false);
|
||||
!removed
|
||||
&& !state.is_null()
|
||||
&& number(state, "timeOffset").unwrap_or(0) > 0
|
||||
&& number(state, "flaggedWatched").unwrap_or(0) == 0
|
||||
});
|
||||
|
|
@ -153,6 +155,29 @@ pub(crate) fn library_continue_watching_items_json(items_json: &str) -> Option<S
|
|||
serde_json::to_string(&metas).ok()
|
||||
}
|
||||
|
||||
pub(crate) fn library_watchlist_items_json(items_json: &str) -> Option<String> {
|
||||
let items: Vec<Value> = serde_json::from_str(items_json).ok()?;
|
||||
let entries: Vec<Value> = items
|
||||
.iter()
|
||||
.filter(|item| !item.get("removed").and_then(Value::as_bool).unwrap_or(false))
|
||||
.filter_map(|item| {
|
||||
let id = text(item, "_id").filter(|s| !s.is_empty())?.to_string();
|
||||
let updated_at_ms = text(item, "_mtime")
|
||||
.and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
|
||||
.map(|dt: chrono::DateTime<chrono::FixedOffset>| dt.timestamp_millis())?;
|
||||
Some(json!({
|
||||
"id": id,
|
||||
"name": text(item, "name").unwrap_or(""),
|
||||
"type": text(item, "type").unwrap_or(""),
|
||||
"poster": item.get("poster").cloned().unwrap_or(Value::Null),
|
||||
"background": item.get("background").cloned().unwrap_or(Value::Null),
|
||||
"updatedAtMs": updated_at_ms
|
||||
}))
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_string(&entries).ok()
|
||||
}
|
||||
|
||||
pub(crate) fn filter_home_continue_watching_json(
|
||||
items_json: &str,
|
||||
trakt_watched_json: &str,
|
||||
|
|
@ -1027,6 +1052,31 @@ mod tests {
|
|||
use super::*;
|
||||
use serde_json::Value;
|
||||
|
||||
#[test]
|
||||
fn library_watchlist_items_excludes_removed_and_undated_entries() {
|
||||
let items = r#"[
|
||||
{"_id":"tt1","name":"Active","type":"movie","_mtime":"2026-01-01T00:00:00.000Z"},
|
||||
{"_id":"tt2","name":"Removed","type":"movie","removed":true,"_mtime":"2026-01-01T00:00:00.000Z"},
|
||||
{"_id":"tt3","name":"NoTimestamp","type":"movie"}
|
||||
]"#;
|
||||
let result: Value = serde_json::from_str(&library_watchlist_items_json(items).unwrap()).unwrap();
|
||||
let ids: Vec<&str> = result.as_array().unwrap().iter().map(|i| i["id"].as_str().unwrap()).collect();
|
||||
assert_eq!(ids, vec!["tt1"]);
|
||||
assert_eq!(result[0]["updatedAtMs"], json!(1767225600000i64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn continue_watching_items_exclude_removed_entries() {
|
||||
let items = r#"[
|
||||
{"_id":"tt1","name":"Active","type":"movie","state":{"timeOffset":100,"duration":1000,"flaggedWatched":0}},
|
||||
{"_id":"tt2","name":"Removed","type":"movie","removed":true,"state":{"timeOffset":100,"duration":1000,"flaggedWatched":0}}
|
||||
]"#;
|
||||
let result: Value =
|
||||
serde_json::from_str(&library_continue_watching_items_json(items).unwrap()).unwrap();
|
||||
assert_eq!(result.as_array().unwrap().len(), 1);
|
||||
assert_eq!(result[0]["id"], "tt1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn watched_state_items_build_series_episode_payloads() {
|
||||
let items = watched_state_items_json(
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ libraryExternalMergePlan
|
|||
libraryLocalStatePlan
|
||||
libraryOfflineGrouping
|
||||
librarySortPlan
|
||||
libraryWatchlistItems
|
||||
manifestCandidates
|
||||
manifestFetchDecision
|
||||
manifestFetchPlan
|
||||
|
|
|
|||
Loading…
Reference in a new issue