fix: fetch YTM playlists with visitor data

feat: add lang/country options to cli
This commit is contained in:
ThetaDev 2023-09-28 01:40:18 +02:00
parent b25e9ebbb7
commit d6de428549
3 changed files with 41 additions and 7 deletions

View file

@ -1,6 +1,6 @@
#![warn(clippy::todo, clippy::dbg_macro)] #![warn(clippy::todo, clippy::dbg_macro)]
use std::{path::PathBuf, time::Duration}; use std::{path::PathBuf, str::FromStr, time::Duration};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use clap::{Parser, Subcommand, ValueEnum}; use clap::{Parser, Subcommand, ValueEnum};
@ -10,7 +10,7 @@ use reqwest::{Client, ClientBuilder};
use rustypipe::{ use rustypipe::{
client::RustyPipe, client::RustyPipe,
model::{UrlTarget, VideoId}, model::{UrlTarget, VideoId},
param::{search_filter, ChannelVideoTab, StreamFilter}, param::{search_filter, ChannelVideoTab, Country, Language, StreamFilter},
}; };
use serde::Serialize; use serde::Serialize;
@ -25,6 +25,12 @@ struct Cli {
/// YouTube visitor data cookie /// YouTube visitor data cookie
#[clap(long, global = true)] #[clap(long, global = true)]
vdata: Option<String>, vdata: Option<String>,
/// YouTube content language
#[clap(long, global = true)]
lang: Option<String>,
/// YouTube content country
#[clap(long, global = true)]
country: Option<String>,
} }
#[derive(Subcommand)] #[derive(Subcommand)]
@ -404,6 +410,12 @@ async fn main() {
std::fs::create_dir_all(&storage_dir).expect("could not create data dir"); std::fs::create_dir_all(&storage_dir).expect("could not create data dir");
rp = rp.storage_dir(storage_dir); rp = rp.storage_dir(storage_dir);
} }
if let Some(lang) = cli.lang {
rp = rp.lang(Language::from_str(&lang.to_ascii_lowercase()).expect("invalid language"));
}
if let Some(country) = cli.country {
rp = rp.country(Country::from_str(&country.to_ascii_uppercase()).expect("invalid country"));
}
let rp = rp.build().unwrap(); let rp = rp.build().unwrap();
match cli.command { match cli.command {

View file

@ -4,7 +4,10 @@ use time::OffsetDateTime;
use crate::{ use crate::{
error::{Error, ExtractionError}, error::{Error, ExtractionError},
model::{paginator::Paginator, ChannelId, Playlist, VideoItem}, model::{
paginator::{ContinuationEndpoint, Paginator},
ChannelId, Playlist, VideoItem,
},
util::{self, timeago, TryRemove}, util::{self, timeago, TryRemove},
}; };
@ -15,18 +18,27 @@ impl RustyPipeQuery {
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
pub async fn playlist<S: AsRef<str> + Debug>(&self, playlist_id: S) -> Result<Playlist, Error> { pub async fn playlist<S: AsRef<str> + Debug>(&self, playlist_id: S) -> Result<Playlist, Error> {
let playlist_id = playlist_id.as_ref(); let playlist_id = playlist_id.as_ref();
let context = self.get_context(ClientType::Desktop, true, None).await; // YTM playlists require visitor data for continuations to work
let visitor_data: Option<String> = if playlist_id.starts_with("RD") {
Some(self.get_visitor_data().await?)
} else {
None
};
let context = self
.get_context(ClientType::Desktop, true, visitor_data.as_deref())
.await;
let request_body = QBrowse { let request_body = QBrowse {
context, context,
browse_id: &format!("VL{playlist_id}"), browse_id: &format!("VL{playlist_id}"),
}; };
self.execute_request::<response::Playlist, _, _>( self.execute_request_vdata::<response::Playlist, _, _>(
ClientType::Desktop, ClientType::Desktop,
"playlist", "playlist",
playlist_id, playlist_id,
"browse", "browse",
&request_body, &request_body,
visitor_data.as_deref(),
) )
.await .await
} }
@ -147,7 +159,13 @@ impl MapResponse<Playlist> for response::Playlist {
c: Playlist { c: Playlist {
id: playlist_id, id: playlist_id,
name, name,
videos: Paginator::new(Some(n_videos), mapper.items, mapper.ctoken), videos: Paginator::new_ext(
Some(n_videos),
mapper.items,
mapper.ctoken,
vdata.map(str::to_owned),
ContinuationEndpoint::Browse,
),
video_count: n_videos, video_count: n_videos,
thumbnail: thumbnails.into(), thumbnail: thumbnails.into(),
description, description,

View file

@ -844,7 +844,11 @@ impl FromStr for Language {
Some(pos) => { Some(pos) => {
sub = &sub[..pos]; sub = &sub[..pos];
} }
None => return Err(Error::Other("could not parse language `{s}`".into())), None => {
return Err(Error::Other(
format!("could not parse language `{s}`").into(),
))
}
} }
} }
} }