refactor: delegate Miruro playback resolution to Terebi

This commit is contained in:
2026-10-22 09:45:03 +02:00
parent f21a1fbe63
commit 3fda432dbd
15 changed files with 248 additions and 825 deletions
+7 -129
View File
@@ -1,9 +1,8 @@
//! Live read-only smoke: cargo run --locked --example miruro_probe
//! Uses the documented research title and direct Sun provider; no media segments.
use std::{io, time::Duration};
//! Exercises native catalog metadata and stable episode URLs; no playback requests.
use std::io;
use serde_json::{Map, Value};
use tubular::miruro::Client;
use tubular::miruro::{Client, Identity};
fn invalid(message: &str) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, message)
@@ -28,132 +27,11 @@ fn main() -> io::Result<()> {
.and_then(|number| u32::try_from(number).ok())
.ok_or_else(|| invalid("No episode metadata available"))?;
println!(
"detail: {} episodes; selected episode {number}",
"detail: {} returned episodes; selected episode {number}",
detail.episodes.len()
);
let sources = client.sources(id, number)?;
let tracks = sources["tracks"]
.as_array()
.ok_or_else(|| invalid("Missing tracks"))?;
let provider = tracks
.iter()
.find(|track| track["track"] == "ssub")
.and_then(|track| track["providers"].as_array())
.and_then(|providers| {
providers
.iter()
.find(|provider| provider["provider"] == "sun")
})
.ok_or_else(|| invalid("Direct Sun soft-subtitle provider not returned"))?;
let server = provider["servers"]
.as_array()
.and_then(|servers| {
servers.iter().find(|server| {
server["streams"]
.as_array()
.is_some_and(|streams| !streams.is_empty())
})
})
.ok_or_else(|| invalid("No direct Sun stream returned"))?;
let headers = server["headers"]
.as_object()
.ok_or_else(|| invalid("Missing server headers"))?;
println!(
"sources: {} tracks; selected ssub/sun; {} server headers retained",
tracks.len(),
headers.len()
);
let agent = ureq::Agent::config_builder()
.timeout_global(Some(Duration::from_secs(25)))
.http_status_as_error(false)
.max_redirects(0)
.build()
.new_agent();
let master = resource(
&agent,
"hls_master",
server["streams"][0]["url"]
.as_str()
.ok_or_else(|| invalid("Missing stream URL"))?,
headers,
)?;
if !master.starts_with("#EXTM3U") || !master.contains("#EXT-X-STREAM-INF:") {
return Err(invalid("Expected an HLS master playlist"));
}
let subtitle = provider["subtitles"]
.as_array()
.and_then(|subtitles| {
subtitles
.iter()
.find(|subtitle| subtitle["default"] == true)
})
.ok_or_else(|| invalid("No default subtitle returned"))?;
let subtitle = resource(
&agent,
"subtitle",
subtitle["file"]
.as_str()
.ok_or_else(|| invalid("Missing subtitle URL"))?,
headers,
)?;
if !subtitle.starts_with("WEBVTT") || !subtitle.contains(" --> ") {
return Err(invalid("Expected timed VTT subtitles"));
}
println!(
"PASS: native search -> detail/episodes -> sources -> HLS master + subtitle; no media segments or tokens saved"
);
let identity = Identity::new(id.into(), number)?;
println!("watch URL: {}", identity.watch_url()?);
println!("PASS: native search -> detail/episodes -> stable watch URL; no playback requests");
Ok(())
}
fn resource(
agent: &ureq::Agent,
label: &str,
url: &str,
headers: &Map<String, Value>,
) -> io::Result<String> {
let uri: ureq::http::Uri = url
.parse()
.map_err(|_| invalid("Invalid Sun resource URL"))?;
if uri.scheme_str() != Some("https")
|| !uri.host().is_some_and(|host| host.ends_with(".keeply.top"))
|| uri
.authority()
.is_some_and(|authority| authority.as_str().contains('@'))
|| uri.port_u16().is_some_and(|port| port != 443)
{
return Err(invalid(
"Smoke only permits the verified Sun HTTPS CDN; no redirects",
));
}
let mut request = agent.get(url);
for (name, value) in headers {
request = request.header(
name.as_str(),
value
.as_str()
.ok_or_else(|| invalid("Invalid server header"))?,
);
}
let mut response = request
.call()
.map_err(|_| io::Error::other("Sun resource HTTP transport failed"))?;
let status = response.status().as_u16();
let mime = response.body().mime_type().unwrap_or("missing");
println!("{label}: HTTP {status}; Content-Type {mime}");
let expected = if label == "subtitle" {
"text/vtt"
} else {
"application/vnd.apple.mpegurl"
};
if status != 200 || mime != expected {
return Err(invalid(
"Resource failed or returned unexpected content; stopping without retry",
));
}
response
.body_mut()
.with_config()
.limit(2 * 1024 * 1024)
.read_to_string()
.map_err(|_| invalid("Resource response unreadable, timed out, or exceeded 2 MiB"))
}