M4 partial — Search shipped, browse UI live on the Pi

Sidecar gains the 'search' op via rustypipe's
query().search::<VideoItem,_>() — returns id, title, channel, duration,
thumbnails, view_count. Default limit 25.

Addon root directory is no longer a placeholder notification:
- 'Search' entry → ?action=search → keyboard input → result list →
  tap a result to play (each result is a play-action plugin URL).
- 'Play by URL' entry → ?action=play_by_url → keyboard input → PlayMedia.
- ?action=search also accepts inline 'q=…' so JSON-RPC clients can
  drive search without going through the on-TV keyboard (useful for
  share-to-TV from phone + tests).
- Result labels formatted as 'Title  ·  Channel  ·  Duration  ·  Views',
  with thumbnail + Kodi InfoLabels for richer skin views.

Verified via Files.GetDirectory JSON-RPC: 19 well-formatted LTT results
returned for query 'linus tech tips'.

Pending M4: channel browse, playlist browse, pagination, search history.
Addon version 0.0.6.
This commit is contained in:
Sulkta 2026-05-23 11:20:41 -07:00
parent d0316ba311
commit 6a10c0045c
5 changed files with 211 additions and 13 deletions

View file

@ -43,6 +43,17 @@ enum Request {
#[serde(default = "sponsor::default_categories")]
categories: Vec<String>,
},
/// Search YouTube for videos. Returns a list of VideoItem entries
/// (id, name, channel_name, channel_id, duration, thumbnail, views).
Search {
query: String,
#[serde(default = "default_search_limit")]
limit: u32,
},
}
fn default_search_limit() -> u32 {
25
}
#[derive(Debug, Serialize)]
@ -148,6 +159,10 @@ async fn handle_line(line: &str) -> Response {
Ok(v) => Response::ok(v),
Err(e) => Response::err(ErrorKind::Network, format!("sponsorblock: {e}")),
},
Request::Search { query, limit } => match resolve::search(&query, limit).await {
Ok(v) => Response::ok(v),
Err(e) => e.into(),
},
}
}

View file

@ -5,6 +5,40 @@ use serde_json::Value;
use crate::{run_yt_dlp, HandlerError};
/// Search YouTube via rustypipe. Returns `SearchResult<VideoItem>` as JSON —
/// the Python addon picks the fields it needs (id, name, channel, duration,
/// thumbnail, view_count, …) to build a Kodi directory listing.
pub(crate) async fn search(query: &str, limit: u32) -> Result<Value, HandlerError> {
use rustypipe::client::RustyPipe;
use rustypipe::model::VideoItem;
let rp = RustyPipe::new();
let result = rp
.query()
.search::<VideoItem, _>(query)
.await
.map_err(|e| classify_rustypipe_error(&e))?;
// SearchResult<VideoItem>.items is a Paginator<VideoItem> — take the first
// page truncated to `limit` items.
let items_json: Vec<Value> = result
.items
.items
.iter()
.take(limit as usize)
.filter_map(|v| serde_json::to_value(v).ok())
.collect();
tracing::info!(query, count = items_json.len(), "search ok via rustypipe");
Ok(serde_json::json!({
"source": "rustypipe",
"query": query,
"items": items_json,
"corrected_query": result.corrected_query,
}))
}
/// DASH-ready resolve: returns rustypipe's full `video_only_streams` +
/// `audio_streams` arrays + `details`. The Python addon builds an MPD
/// from these and hands it to inputstream.adaptive — unlocks 1080p+ via