Compare commits
1
Commits
888996f4f3
...
289f4a4ff5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
289f4a4ff5 |
Generated
+1
-1
@@ -1460,7 +1460,7 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "jellyfin-kiosk-rs"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"eframe",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "jellyfin-kiosk-rs"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
Binary file not shown.
@@ -217,6 +217,58 @@ pub fn normalize_playback_url(file: &str) -> String {
|
||||
trimmed.to_string()
|
||||
}
|
||||
|
||||
pub fn extract_addon_share_url(params: &Value) -> Option<String> {
|
||||
// 1. Direct fields
|
||||
for key in ["data", "url", "file"] {
|
||||
if let Some(v) = params.get(key).and_then(|v| v.as_str()) {
|
||||
return Some(v.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Nested under "params"
|
||||
if let Some(inner) = params.get("params") {
|
||||
if let Some(obj) = inner.as_object() {
|
||||
for key in ["data", "url", "file"] {
|
||||
if let Some(v) = obj.get(key).and_then(|v| v.as_str()) {
|
||||
return Some(v.to_string());
|
||||
}
|
||||
}
|
||||
} else if let Some(s) = inner.as_str() {
|
||||
let q = s.trim_start_matches('?');
|
||||
for pair in q.split('&') {
|
||||
let mut kv = pair.splitn(2, '=');
|
||||
if let (Some(k), Some(v)) = (kv.next(), kv.next()) {
|
||||
if k == "data" || k == "url" || k == "file" {
|
||||
return Some(v.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
if s.contains("http://") || s.contains("https://") || s.contains("plugin://") {
|
||||
return Some(s.to_string());
|
||||
}
|
||||
} else if let Some(arr) = inner.as_array() {
|
||||
for item in arr {
|
||||
if let Some(s) = item.as_str() {
|
||||
let q = s.trim_start_matches('?');
|
||||
for pair in q.split('&') {
|
||||
let mut kv = pair.splitn(2, '=');
|
||||
if let (Some(k), Some(v)) = (kv.next(), kv.next()) {
|
||||
if k == "data" || k == "url" || k == "file" {
|
||||
return Some(v.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
if s.contains("http://") || s.contains("https://") || s.contains("plugin://") {
|
||||
return Some(s.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
async fn handle_jsonrpc_get(
|
||||
State(state): State<YatseServerState>,
|
||||
uri: axum::http::Uri,
|
||||
@@ -1009,8 +1061,49 @@ async fn dispatch_rpc(state: &YatseServerState, req: JsonRpcRequest) -> Value {
|
||||
})
|
||||
}
|
||||
"Addons.ExecuteAddon" => {
|
||||
if let Some(ref params) = req.params {
|
||||
if let Some(raw_url) = extract_addon_share_url(params) {
|
||||
let decoded_url = percent_decode(&raw_url);
|
||||
let norm_url = normalize_playback_url(&decoded_url);
|
||||
tracing::info!("Addons.ExecuteAddon share requested: raw='{}' -> norm='{}'", raw_url, norm_url);
|
||||
let p = state.player.clone();
|
||||
tokio::spawn(async move {
|
||||
let _ = p.play(&norm_url, "YouTube via Yatse").await;
|
||||
});
|
||||
} else {
|
||||
tracing::warn!("Addons.ExecuteAddon received without resolvable URL: {:?}", params);
|
||||
}
|
||||
}
|
||||
json!({"jsonrpc": "2.0", "result": "OK", "id": id})
|
||||
}
|
||||
"Playlist.Add" | "Playlist.Insert" => {
|
||||
if let Some(ref params) = req.params {
|
||||
if let Some(item) = params.get("item") {
|
||||
if let Some(file) = item.get("file").and_then(|f| f.as_str()) {
|
||||
let norm_url = normalize_playback_url(file);
|
||||
tracing::info!("Playlist.Add requested file: '{}' -> '{}'", file, norm_url);
|
||||
let p = state.player.clone();
|
||||
tokio::spawn(async move {
|
||||
let _ = p.play(&norm_url, "Media from Yatse").await;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
json!({"jsonrpc": "2.0", "result": "OK", "id": id})
|
||||
}
|
||||
"Playlist.Clear" => {
|
||||
json!({"jsonrpc": "2.0", "result": "OK", "id": id})
|
||||
}
|
||||
"Playlist.GetItems" => {
|
||||
json!({
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"items": [],
|
||||
"limits": {"start": 0, "end": 0, "total": 0}
|
||||
},
|
||||
"id": id
|
||||
})
|
||||
}
|
||||
"Files.GetSources" => {
|
||||
let media = req.params.as_ref()
|
||||
.and_then(|p| p.get("media").or_else(|| p.as_array().and_then(|a| a.first())))
|
||||
@@ -1854,4 +1947,22 @@ mod tests {
|
||||
assert_eq!(eps[1]["title"], "Cat's in the Bag...");
|
||||
assert_eq!(eps[1]["episode"], 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_addon_share_url() {
|
||||
let payload = json!({
|
||||
"addonid": "script.yatse.kodi",
|
||||
"params": {
|
||||
"action": "share",
|
||||
"data": "https%3A%2F%2Fyoutube.com%2Fwatch%3Fv%3DeMEIt6_mGSI%26is%3DhEdy88z6Nhf7gRSE",
|
||||
"queue": "false",
|
||||
"type": "unresolvedurl"
|
||||
}
|
||||
});
|
||||
let extracted = extract_addon_share_url(&payload).expect("extracted url");
|
||||
assert_eq!(
|
||||
percent_decode(&extracted),
|
||||
"https://youtube.com/watch?v=eMEIt6_mGSI&is=hEdy88z6Nhf7gRSE"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user