v0.1.0-W (vc=10): U-4 + U-5 — channels via rustypipe + rip NewPipeExtractor
channel_info(url) UniFFI suspend fn. ChannelViewModel + SubscriptionFeedViewModel both swap. NewPipeExtractor (Java) is OUT — zero org.schabi.newpipe classes in the APK now. Cleanup: - NewPipeDownloader.kt deleted (was the OkHttp adapter) - Thumbnails.kt deleted (rustypipe returns full URLs) - NewPipe.init() dropped from StrawApp.onCreate - libs.newpipe.extractor removed from build.gradle.kts - STRAW_USER_AGENT + strawHttpClient() now live in net/Http.kt - RydClient + SponsorBlockClient + PlayerScreen + PlaybackService all read from net/Http.kt instead of the extractor package rustypipe API quirks beat: - channel_videos(id) is the right method (channel() doesn't exist) - ChannelInfo struct = basic metadata; Channel<T> wrapper carries name/avatar/banner + .content is the paginator of videos - description is String (not Option), subscriber_count is Option<u64> End state: strawApp Kotlin is ~UI + thin glue to strawcore. The Rust core handles search / streamInfo / channel / channel_videos via UniFFI suspend fns. Tokio + reqwest + rustls + rquickjs all packed in libstrawcore.so (~6MB per ABI). APK 40MB total.
This commit is contained in:
parent
7327de2843
commit
a13896f5e9
14 changed files with 213 additions and 257 deletions
113
rust/strawcore/src/channel.rs
Normal file
113
rust/strawcore/src/channel.rs
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
// Phase U-4 — `channel_info(channel_url)` via rustypipe.
|
||||
//
|
||||
// Returns channel metadata + the channel's latest videos (the "Videos" tab).
|
||||
// Used by ChannelScreen (single-channel view) AND
|
||||
// SubscriptionFeedViewModel (which fans out across all subscriptions).
|
||||
|
||||
use crate::error::StrawcoreError;
|
||||
use crate::search::SearchItem;
|
||||
use rustypipe::client::RustyPipe;
|
||||
|
||||
#[derive(Debug, Clone, uniffi::Record)]
|
||||
pub struct ChannelInfo {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub avatar: Option<String>,
|
||||
pub banner: Option<String>,
|
||||
/// -1 = unknown / hidden by the channel.
|
||||
pub subscriber_count: i64,
|
||||
pub description: String,
|
||||
/// Latest videos from the channel (Videos tab, newest first).
|
||||
pub videos: Vec<SearchItem>,
|
||||
}
|
||||
|
||||
fn yt_video_url(id: &str) -> String {
|
||||
format!("https://www.youtube.com/watch?v={}", id)
|
||||
}
|
||||
|
||||
fn yt_channel_url(id: &str) -> String {
|
||||
format!("https://www.youtube.com/channel/{}", id)
|
||||
}
|
||||
|
||||
/// Channel-id extraction. Accepts:
|
||||
/// https://www.youtube.com/channel/UC...
|
||||
/// https://www.youtube.com/@handle
|
||||
/// https://www.youtube.com/c/handle
|
||||
/// https://www.youtube.com/user/handle
|
||||
/// bare channel id (UC..., 24 chars)
|
||||
fn extract_channel_input(input: &str) -> Result<String, StrawcoreError> {
|
||||
let trimmed = input.trim();
|
||||
// Bare channel ID — usually 24 chars starting with UC.
|
||||
if trimmed.starts_with("UC") && trimmed.len() == 24 {
|
||||
return Ok(trimmed.to_string());
|
||||
}
|
||||
let url = url::Url::parse(trimmed).map_err(|e| StrawcoreError::Unsupported {
|
||||
detail: format!("bad URL: {}", e),
|
||||
})?;
|
||||
let path = url.path().trim_start_matches('/').trim_end_matches('/');
|
||||
// /channel/UCxxx — canonical
|
||||
if let Some(rest) = path.strip_prefix("channel/") {
|
||||
let id = rest.split('/').next().unwrap_or("");
|
||||
if !id.is_empty() {
|
||||
return Ok(id.to_string());
|
||||
}
|
||||
}
|
||||
// /@handle — rustypipe takes the handle (with @)
|
||||
if path.starts_with('@') {
|
||||
return Ok(path.split('/').next().unwrap_or(path).to_string());
|
||||
}
|
||||
// /c/name or /user/name
|
||||
for prefix in ["c/", "user/"] {
|
||||
if let Some(rest) = path.strip_prefix(prefix) {
|
||||
let name = rest.split('/').next().unwrap_or("");
|
||||
if !name.is_empty() {
|
||||
// Rustypipe channel() takes the channel id or @handle. For
|
||||
// legacy /c/ and /user/ URLs we prepend @ as a best-effort.
|
||||
return Ok(format!("@{}", name));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(StrawcoreError::Unsupported {
|
||||
detail: format!("unsupported channel URL: {}", input),
|
||||
})
|
||||
}
|
||||
|
||||
#[uniffi::export(async_runtime = "tokio")]
|
||||
pub async fn channel_info(channel_url: String) -> Result<ChannelInfo, StrawcoreError> {
|
||||
let key = extract_channel_input(&channel_url)?;
|
||||
log::info!("strawcore::channel_info key={}", key);
|
||||
let rp = RustyPipe::new();
|
||||
|
||||
// channel_videos(id) returns Channel<Paginator<VideoItem>> — the
|
||||
// Channel<T> wrapper carries name/avatar/banner/etc and `.content`
|
||||
// is the paginator of videos. One round-trip gets us everything.
|
||||
let channel = rp.query().channel_videos(&key).await?;
|
||||
|
||||
let videos: Vec<SearchItem> = channel
|
||||
.content
|
||||
.items
|
||||
.into_iter()
|
||||
.map(|v| SearchItem {
|
||||
url: yt_video_url(&v.id),
|
||||
title: v.name.clone(),
|
||||
uploader: channel.name.clone(),
|
||||
uploader_url: Some(yt_channel_url(&channel.id)),
|
||||
thumbnail: v.thumbnail.last().map(|t| t.url.clone()),
|
||||
duration_seconds: v.duration.unwrap_or(0) as i64,
|
||||
view_count: v.view_count.unwrap_or(0) as i64,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let avatar = channel.avatar.last().map(|t| t.url.clone());
|
||||
let banner = channel.banner.last().map(|t| t.url.clone());
|
||||
|
||||
Ok(ChannelInfo {
|
||||
id: channel.id,
|
||||
name: channel.name,
|
||||
avatar,
|
||||
banner,
|
||||
subscriber_count: channel.subscriber_count.map(|n| n as i64).unwrap_or(-1),
|
||||
description: channel.description,
|
||||
videos,
|
||||
})
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
use std::sync::Once;
|
||||
|
||||
mod channel;
|
||||
mod error;
|
||||
mod runtime;
|
||||
mod search;
|
||||
|
|
@ -17,6 +18,7 @@ mod stream;
|
|||
use runtime::block_on;
|
||||
|
||||
// Re-exports so UniFFI sees the types at the crate root for macro discovery.
|
||||
pub use channel::ChannelInfo;
|
||||
pub use error::StrawcoreError;
|
||||
pub use search::SearchItem;
|
||||
pub use stream::{AudioStreamItem, StreamInfo, VideoStreamItem};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue