// Phase 7 — search via Sulkta-Coop/strawcore-core. Exposed to Kotlin // as a suspend fun. SearchItem field shape is unchanged from Phase U-2 // so Kotlin callers (SearchViewModel) keep working with no code // changes. use strawcore_core::stream::StreamInfoItem; use strawcore_core::youtube::linkhandler::search::SearchFilter; use strawcore_core::youtube::search_extractor; use crate::error::StrawcoreError; #[derive(Debug, Clone, uniffi::Record)] pub struct SearchItem { pub url: String, pub title: String, pub uploader: String, pub uploader_url: Option, pub thumbnail: Option, /// Duration in seconds. 0 = live/unknown. pub duration_seconds: i64, /// Reported view count. 0 = unknown. pub view_count: i64, /// Relative upload date as YT renders it ("2 days ago", "3 weeks /// ago"). Empty if not extracted. Strawcore-core already populates /// this on StreamInfoItem; we just pass it through. pub upload_date_relative: String, } pub(crate) fn from_core(item: StreamInfoItem) -> SearchItem { let uploader_url = if item.uploader_url.is_empty() { None } else { Some(item.uploader_url) }; let thumbnail = item .thumbnails .last() .map(|i| i.url().to_string()); SearchItem { url: item.url, title: item.name, uploader: item.uploader_name, uploader_url, thumbnail, duration_seconds: item.duration_seconds, view_count: if item.view_count < 0 { 0 } else { item.view_count }, upload_date_relative: item.upload_date_relative, } } #[uniffi::export(async_runtime = "tokio")] pub async fn search(query: String) -> Result, StrawcoreError> { log::info!("strawcore::search query={}", query); let result = tokio::task::spawn_blocking(move || { search_extractor::search(&query, SearchFilter::Videos) }) .await .map_err(|e| StrawcoreError::Extractor { msg: format!("join: {e}"), })??; Ok(result.videos.into_iter().map(from_core).collect()) }