use clap::Subcommand;
use tail_fin_common::TailFinError;
use crate::session::{print_json, print_list, Ctx};
#[derive(Subcommand)]
pub enum YoutubeAction {
Search {
query: String,
#[arg(long, default_value_t = 20)]
count: usize,
},
Video {
video: String,
},
Channel {
channel: String,
},
Comments {
video: String,
#[arg(long, default_value_t = 20)]
count: usize,
},
Trending {
#[arg(long, default_value_t = 20)]
count: usize,
},
Transcript {
video: String,
#[arg(long, default_value_t = false)]
plain: bool,
},
Subscriptions {
#[arg(long, default_value_t = 50)]
count: usize,
},
}
pub async fn run(action: YoutubeAction, ctx: &Ctx) -> Result<(), TailFinError> {
if ctx.cookies.is_some() {
return Err(TailFinError::Api(
"YouTube cookie mode is not yet supported. Use --connect for browser mode.".into(),
));
}
let chrome_host = ctx.connect.as_deref().unwrap_or("127.0.0.1:9222");
let session = crate::session::browser_session(chrome_host, ctx.headed).await?;
let client = tail_fin_youtube::YouTubeClient::new(session);
match action {
YoutubeAction::Search { query, count } => {
let videos = client.search(&query, count).await?;
print_list("videos", &videos, videos.len())?;
}
YoutubeAction::Video { video } => {
let id = tail_fin_youtube::extract_video_id(&video);
let result = client.video(&id).await?;
match result {
Some(v) => print_json(&v)?,
None => {
return Err(TailFinError::Api(format!(
"Could not fetch video details for '{}'",
id
)));
}
}
}
YoutubeAction::Channel { channel } => {
let id = tail_fin_youtube::extract_channel_id(&channel);
let result = client.channel(&id).await?;
match result {
Some(ch) => print_json(&ch)?,
None => {
return Err(TailFinError::Api(format!(
"Could not fetch channel info for '{}'",
id
)));
}
}
}
YoutubeAction::Comments { video, count } => {
let id = tail_fin_youtube::extract_video_id(&video);
let comments = client.comments(&id, count).await?;
print_list("comments", &comments, comments.len())?;
}
YoutubeAction::Trending { count } => {
let videos = client.trending(count).await?;
print_list("videos", &videos, videos.len())?;
}
YoutubeAction::Transcript { video, plain } => {
let id = tail_fin_youtube::extract_video_id(&video);
let segments = client.transcript(&id).await?;
if plain {
let text: String = segments
.iter()
.map(|s| s.text.as_str())
.collect::<Vec<_>>()
.join(" ");
print_json(&serde_json::json!({
"text": text,
"video_id": id,
}))?;
} else {
print_json(&serde_json::json!({
"segments": segments,
"count": segments.len(),
"video_id": id,
}))?;
}
}
YoutubeAction::Subscriptions { count } => {
let channels = client.subscriptions(count).await?;
print_list("channels", &channels, channels.len())?;
}
}
Ok(())
}
pub struct Adapter;
impl crate::adapter::CliAdapter for Adapter {
fn name(&self) -> &'static str {
"youtube"
}
fn about(&self) -> &'static str {
"YouTube operations"
}
fn command(&self) -> clap::Command {
<YoutubeAction as clap::Subcommand>::augment_subcommands(
clap::Command::new("youtube").about("YouTube operations"),
)
}
fn dispatch<'a>(
&'a self,
matches: &'a clap::ArgMatches,
ctx: &'a crate::session::Ctx,
) -> std::pin::Pin<
Box<
dyn std::future::Future<Output = Result<(), tail_fin_common::TailFinError>> + Send + 'a,
>,
> {
Box::pin(async move {
let action = <YoutubeAction as clap::FromArgMatches>::from_arg_matches(matches)
.map_err(|e| tail_fin_common::TailFinError::Api(e.to_string()))?;
run(action, ctx).await
})
}
}