Verify next-episode stream matches requested episode before selecting it

select_next_episode_stream_json picked a stream by bingeGroup equality
(which Torrentio derives from the torrent infoHash alone, so every file
in a season-pack torrent shares one), falling back to the first stream
in the list with no title/filename check either way. A duplicate or
mis-scoped addon entry could win and play the wrong episode.
This commit is contained in:
KhooLy 2026-07-11 04:20:26 +03:00
parent 5834189d90
commit 403b413a11
2 changed files with 31 additions and 2 deletions

View file

@ -442,6 +442,7 @@ fn route_player_policy(method: &str, args_json: &str) -> Outcome {
field_str(&args, "streamsJson")?,
field_str(&args, "currentStreamJson")?,
field_str(&args, "prefsJson")?,
field_str(&args, "nextVideoId")?,
))
}

View file

@ -941,6 +941,7 @@ pub(crate) fn select_next_episode_stream_json(
streams_json: &str,
current_stream_json: &str,
prefs_json: &str,
next_video_id: &str,
) -> Option<String> {
let streams: Vec<Value> = serde_json::from_str(streams_json).ok()?;
if streams.is_empty() {
@ -949,6 +950,25 @@ pub(crate) fn select_next_episode_stream_json(
let current: Value = serde_json::from_str(current_stream_json).ok()?;
let prefs: Value = serde_json::from_str(prefs_json).unwrap_or(Value::Null);
let episode_ok = |s: &Value| -> bool {
let field = |key: &str| -> String {
s.get(key)
.and_then(Value::as_str)
.unwrap_or_default()
.to_string()
};
let behavior_hints = s.get("behaviorHints");
let filename = behavior_hints
.and_then(|h| h.get("filename"))
.and_then(Value::as_str)
.unwrap_or_default()
.to_string();
crate::content_identity::stream_matches_episode(
next_video_id,
&[field("title"), field("name"), field("description"), filename],
)
};
let try_binge = prefs
.get("tryBingeGroup")
.and_then(Value::as_bool)
@ -974,6 +994,7 @@ pub(crate) fn select_next_episode_stream_json(
.and_then(|h| h.get("bingeGroup"))
.and_then(Value::as_str)
== Some(group)
&& episode_ok(s)
});
if let Some(s) = matched {
return serde_json::to_string(s).ok();
@ -1001,13 +1022,20 @@ pub(crate) fn select_next_episode_stream_json(
.collect::<Vec<_>>()
.join(" ")
};
if let Some(matched) = streams.iter().find(|s| re.is_match(&stream_text(s))) {
if let Some(matched) = streams
.iter()
.find(|s| re.is_match(&stream_text(s)) && episode_ok(s))
{
return serde_json::to_string(matched).ok();
}
}
}
streams.first().and_then(|s| serde_json::to_string(s).ok())
streams
.iter()
.find(|s| episode_ok(s))
.or_else(|| streams.first())
.and_then(|s| serde_json::to_string(s).ok())
}
#[cfg(test)]