Replaces the rustypipe-backed extraction with calls into the new
NPE-port crate. The UniFFI surface Kotlin sees is unchanged:
suspend fun search(query: String): List<SearchItem>
suspend fun streamInfo(input: String): StreamInfo
suspend fun channelInfo(input: String): ChannelInfo
fun initLogging() // also wires the strawcore-core Downloader
fun helloFromRust(name: String): String
rust/strawcore/
* Cargo.toml — dropped rustypipe + rquickjs-sys direct dep;
added strawcore-core path dep (../../../strawcore)
* src/error.rs — From<strawcore_core::ExtractionError>, mapping
ContentUnavailable variants to typed
StrawcoreError cases (AgeRestricted, GeoRestricted,
Private, RequiresLogin) instead of bucketing all
to Extractor
* src/runtime.rs — Once-guarded ReqwestDownloader init via
NewPipe::init_full
* src/search.rs — search() spawn_blocks core search_extractor::search
against SearchFilter::Videos
* src/stream.rs — stream_info() resolves URL → video_id via
strawcore_core::linkhandler::stream, then
spawn_blocks core stream_extractor::stream_info,
then maps StreamInfo → wrapper DTOs (combined/
video_only/audio_only/dash/hls)
* src/channel.rs — channel_info() parses input via
strawcore_core::linkhandler::channel (handle /
custom-url / legacy-user resolution lives in
core), then spawn_blocks core channel::channel_info
Build verified: wrapper compiles linking strawcore-core, uniffi-bindgen
generates Kotlin bindings with the same suspend fun + data class
surface Kotlin already consumes. Android NDK cross-compile + APK + on-
device smoke pending (needs crafting-table container).
This commits onto rollback/vc18-back-to-NPE — the existing Kotlin code
still calls NewPipeExtractor directly. Switching the Kotlin side to
consume the rust wrapper is a separate cutover.
62 lines
2.1 KiB
Rust
62 lines
2.1 KiB
Rust
// Phase 7 — `channel_info(channel_url)` via the new strawcore.
|
|
// Used by ChannelScreen (single-channel view) AND
|
|
// SubscriptionFeedViewModel (which fans out across all subscriptions).
|
|
|
|
use strawcore_core::youtube::channel::{channel_info as core_channel_info, ChannelInfo as CoreInfo};
|
|
use strawcore_core::youtube::linkhandler::channel as core_link;
|
|
|
|
use crate::error::StrawcoreError;
|
|
use crate::search::{from_core as search_from_core, SearchItem};
|
|
|
|
#[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>,
|
|
}
|
|
|
|
#[uniffi::export(async_runtime = "tokio")]
|
|
pub async fn channel_info(input: String) -> Result<ChannelInfo, StrawcoreError> {
|
|
log::info!("strawcore::channel_info input={}", input);
|
|
let identifier = resolve_channel_identifier(&input)?;
|
|
let core = tokio::task::spawn_blocking(move || core_channel_info(identifier))
|
|
.await
|
|
.map_err(|e| StrawcoreError::Extractor {
|
|
msg: format!("join: {e}"),
|
|
})??;
|
|
Ok(map_channel(core))
|
|
}
|
|
|
|
fn resolve_channel_identifier(
|
|
input: &str,
|
|
) -> Result<core_link::ChannelIdentifier, StrawcoreError> {
|
|
let trimmed = input.trim();
|
|
// Bare channel ID — UC..., 24 chars.
|
|
if trimmed.starts_with("UC") && trimmed.len() == 24 {
|
|
return Ok(core_link::ChannelIdentifier::DirectId(trimmed.into()));
|
|
}
|
|
core_link::parse(trimmed).map_err(|e| StrawcoreError::Unsupported {
|
|
detail: e.to_string(),
|
|
})
|
|
}
|
|
|
|
fn map_channel(c: CoreInfo) -> ChannelInfo {
|
|
let avatar = c.avatars.last().map(|i| i.url().to_string());
|
|
let banner = c.banners.last().map(|i| i.url().to_string());
|
|
let videos = c.recent_videos.into_iter().map(search_from_core).collect();
|
|
ChannelInfo {
|
|
id: c.channel_id,
|
|
name: c.name,
|
|
avatar,
|
|
banner,
|
|
subscriber_count: c.subscriber_count,
|
|
description: c.description,
|
|
videos,
|
|
}
|
|
}
|