fix: clean stuff up
This commit is contained in:
parent
62853d50bf
commit
01b9c8e310
11 changed files with 185 additions and 393 deletions
|
|
@ -39,7 +39,7 @@ enum Params {
|
|||
|
||||
impl RustyPipeQuery {
|
||||
pub async fn channel_videos(
|
||||
&self,
|
||||
self,
|
||||
channel_id: &str,
|
||||
) -> Result<Channel<Paginator<ChannelVideo>>, Error> {
|
||||
self.channel_videos_ordered(channel_id, ChannelOrder::default())
|
||||
|
|
@ -47,7 +47,7 @@ impl RustyPipeQuery {
|
|||
}
|
||||
|
||||
pub async fn channel_videos_ordered(
|
||||
&self,
|
||||
self,
|
||||
channel_id: &str,
|
||||
order: ChannelOrder,
|
||||
) -> Result<Channel<Paginator<ChannelVideo>>, Error> {
|
||||
|
|
@ -73,7 +73,7 @@ impl RustyPipeQuery {
|
|||
}
|
||||
|
||||
pub async fn channel_videos_continuation(
|
||||
&self,
|
||||
self,
|
||||
ctoken: &str,
|
||||
) -> Result<Paginator<ChannelVideo>, Error> {
|
||||
let context = self.get_context(ClientType::Desktop, true).await;
|
||||
|
|
@ -93,7 +93,7 @@ impl RustyPipeQuery {
|
|||
}
|
||||
|
||||
pub async fn channel_playlists(
|
||||
&self,
|
||||
self,
|
||||
channel_id: &str,
|
||||
) -> Result<Channel<Paginator<ChannelPlaylist>>, Error> {
|
||||
let context = self.get_context(ClientType::Desktop, true).await;
|
||||
|
|
@ -114,7 +114,7 @@ impl RustyPipeQuery {
|
|||
}
|
||||
|
||||
pub async fn channel_playlists_continuation(
|
||||
&self,
|
||||
self,
|
||||
ctoken: &str,
|
||||
) -> Result<Paginator<ChannelPlaylist>, Error> {
|
||||
let context = self.get_context(ClientType::Desktop, true).await;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use crate::{
|
|||
use super::{response, RustyPipeQuery};
|
||||
|
||||
impl RustyPipeQuery {
|
||||
pub async fn channel_rss(&self, channel_id: &str) -> Result<ChannelRss, Error> {
|
||||
pub async fn channel_rss(self, channel_id: &str) -> Result<ChannelRss, Error> {
|
||||
let url = format!(
|
||||
"https://www.youtube.com/feeds/videos.xml?channel_id={}",
|
||||
channel_id
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ use tokio::sync::RwLock;
|
|||
use crate::{
|
||||
cache::{CacheStorage, FileStorage},
|
||||
deobfuscate::{DeobfData, Deobfuscator},
|
||||
error::{Error, ExtractionError, Result},
|
||||
error::{Error, ExtractionError},
|
||||
param::{Country, Language},
|
||||
report::{FileReporter, Level, Report, Reporter},
|
||||
serializer::MapResult,
|
||||
|
|
@ -467,10 +467,7 @@ impl RustyPipe {
|
|||
}
|
||||
|
||||
/// Execute the given http request.
|
||||
async fn http_request(
|
||||
&self,
|
||||
request: Request,
|
||||
) -> core::result::Result<Response, reqwest::Error> {
|
||||
async fn http_request(&self, request: Request) -> Result<Response, reqwest::Error> {
|
||||
let mut last_res = None;
|
||||
for n in 0..self.inner.n_http_retries {
|
||||
let res = self.inner.http.execute(request.try_clone().unwrap()).await;
|
||||
|
|
@ -504,7 +501,7 @@ impl RustyPipe {
|
|||
|
||||
/// Execute the given http request, returning an error in case of a
|
||||
/// non-successful status code.
|
||||
async fn http_request_estatus(&self, request: Request) -> Result<Response> {
|
||||
async fn http_request_estatus(&self, request: Request) -> Result<Response, Error> {
|
||||
let res = self.http_request(request).await?;
|
||||
let status = res.status();
|
||||
|
||||
|
|
@ -516,12 +513,12 @@ impl RustyPipe {
|
|||
}
|
||||
|
||||
/// Execute the given http request, returning the response body as a string.
|
||||
async fn http_request_txt(&self, request: Request) -> Result<String> {
|
||||
async fn http_request_txt(&self, request: Request) -> Result<String, Error> {
|
||||
Ok(self.http_request_estatus(request).await?.text().await?)
|
||||
}
|
||||
|
||||
/// Extract the current version of the YouTube desktop client from the website.
|
||||
async fn extract_desktop_client_version(&self) -> Result<String> {
|
||||
async fn extract_desktop_client_version(&self) -> Result<String, Error> {
|
||||
let from_swjs = async {
|
||||
let swjs = self
|
||||
.http_request_txt(
|
||||
|
|
@ -568,7 +565,7 @@ impl RustyPipe {
|
|||
}
|
||||
|
||||
/// Extract the current version of the YouTube Music desktop client from the website.
|
||||
async fn extract_music_client_version(&self) -> Result<String> {
|
||||
async fn extract_music_client_version(&self) -> Result<String, Error> {
|
||||
let from_swjs = async {
|
||||
let swjs = self
|
||||
.http_request_txt(
|
||||
|
|
@ -679,7 +676,7 @@ impl RustyPipe {
|
|||
}
|
||||
|
||||
/// Instantiate a new deobfuscator from either cached or extracted YouTube JavaScript code.
|
||||
async fn get_deobf(&self) -> Result<Deobfuscator> {
|
||||
async fn get_deobf(&self) -> Result<Deobfuscator, Error> {
|
||||
// Write lock here to prevent concurrent tasks from fetching the same data
|
||||
let mut deobf = self.inner.cache.deobf.write().await;
|
||||
|
||||
|
|
@ -960,7 +957,7 @@ impl RustyPipeQuery {
|
|||
endpoint: &str,
|
||||
body: &B,
|
||||
deobf: Option<&Deobfuscator>,
|
||||
) -> Result<M> {
|
||||
) -> Result<M, Error> {
|
||||
for n in 0..self.client.inner.n_query_retries.saturating_sub(1) {
|
||||
let res = self
|
||||
._try_execute_request_deobf::<R, M, B>(
|
||||
|
|
@ -1016,7 +1013,7 @@ impl RustyPipeQuery {
|
|||
body: &B,
|
||||
deobf: Option<&Deobfuscator>,
|
||||
report: bool,
|
||||
) -> Result<M> {
|
||||
) -> Result<M, Error> {
|
||||
let request = self
|
||||
.request_builder(ctype, endpoint)
|
||||
.await
|
||||
|
|
@ -1132,7 +1129,7 @@ impl RustyPipeQuery {
|
|||
id: &str,
|
||||
endpoint: &str,
|
||||
body: &B,
|
||||
) -> Result<M> {
|
||||
) -> Result<M, Error> {
|
||||
self.execute_request_deobf::<R, M, B>(ctype, operation, id, endpoint, body, None)
|
||||
.await
|
||||
}
|
||||
|
|
@ -1158,7 +1155,7 @@ trait MapResponse<T> {
|
|||
id: &str,
|
||||
lang: Language,
|
||||
deobf: Option<&Deobfuscator>,
|
||||
) -> core::result::Result<MapResult<T>, crate::error::ExtractionError>;
|
||||
) -> Result<MapResult<T>, ExtractionError>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -1,271 +1,72 @@
|
|||
use crate::error::Result;
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::model::{
|
||||
ChannelPlaylist, ChannelVideo, Comment, Paginator, PlaylistVideo, RecommendedVideo, SearchItem,
|
||||
};
|
||||
|
||||
use super::RustyPipeQuery;
|
||||
|
||||
impl Paginator<PlaylistVideo> {
|
||||
pub async fn next(&self, query: RustyPipeQuery) -> Result<Option<Self>> {
|
||||
Ok(match &self.ctoken {
|
||||
Some(ctoken) => Some(query.playlist_continuation(ctoken).await?),
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn extend(&mut self, query: RustyPipeQuery) -> Result<bool> {
|
||||
match self.next(query).await {
|
||||
Ok(Some(paginator)) => {
|
||||
let mut items = paginator.items;
|
||||
self.items.append(&mut items);
|
||||
self.ctoken = paginator.ctoken;
|
||||
Ok(true)
|
||||
macro_rules! paginator {
|
||||
($entity_type:ty, $cont_function:path) => {
|
||||
impl Paginator<$entity_type> {
|
||||
pub async fn next(&self, query: RustyPipeQuery) -> Result<Option<Self>, Error> {
|
||||
Ok(match &self.ctoken {
|
||||
Some(ctoken) => Some($cont_function(query, ctoken).await?),
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
Ok(None) => Ok(false),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn extend_pages(&mut self, query: RustyPipeQuery, n_pages: usize) -> Result<()> {
|
||||
for _ in 0..n_pages {
|
||||
match self.extend(query.clone()).await {
|
||||
Ok(false) => break,
|
||||
Err(e) => return Err(e),
|
||||
_ => {}
|
||||
pub async fn extend(&mut self, query: RustyPipeQuery) -> Result<bool, Error> {
|
||||
match self.next(query).await {
|
||||
Ok(Some(paginator)) => {
|
||||
let mut items = paginator.items;
|
||||
self.items.append(&mut items);
|
||||
self.ctoken = paginator.ctoken;
|
||||
Ok(true)
|
||||
}
|
||||
Ok(None) => Ok(false),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn extend_pages(
|
||||
&mut self,
|
||||
query: RustyPipeQuery,
|
||||
n_pages: usize,
|
||||
) -> Result<(), Error> {
|
||||
for _ in 0..n_pages {
|
||||
match self.extend(query.clone()).await {
|
||||
Ok(false) => break,
|
||||
Err(e) => return Err(e),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn extend_limit(
|
||||
&mut self,
|
||||
query: RustyPipeQuery,
|
||||
n_items: usize,
|
||||
) -> Result<(), Error> {
|
||||
while self.items.len() < n_items {
|
||||
match self.extend(query.clone()).await {
|
||||
Ok(false) => break,
|
||||
Err(e) => return Err(e),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn extend_limit(&mut self, query: RustyPipeQuery, n_items: usize) -> Result<()> {
|
||||
while self.items.len() < n_items {
|
||||
match self.extend(query.clone()).await {
|
||||
Ok(false) => break,
|
||||
Err(e) => return Err(e),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl Paginator<RecommendedVideo> {
|
||||
pub async fn next(&self, query: RustyPipeQuery) -> Result<Option<Self>> {
|
||||
Ok(match &self.ctoken {
|
||||
Some(ctoken) => Some(query.video_recommendations(ctoken).await?),
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn extend(&mut self, query: RustyPipeQuery) -> Result<bool> {
|
||||
match self.next(query).await {
|
||||
Ok(Some(paginator)) => {
|
||||
let mut items = paginator.items;
|
||||
self.items.append(&mut items);
|
||||
self.ctoken = paginator.ctoken;
|
||||
Ok(true)
|
||||
}
|
||||
Ok(None) => Ok(false),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn extend_pages(&mut self, query: RustyPipeQuery, n_pages: usize) -> Result<()> {
|
||||
for _ in 0..n_pages {
|
||||
match self.extend(query.clone()).await {
|
||||
Ok(false) => break,
|
||||
Err(e) => return Err(e),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn extend_limit(&mut self, query: RustyPipeQuery, n_items: usize) -> Result<()> {
|
||||
while self.items.len() < n_items {
|
||||
match self.extend(query.clone()).await {
|
||||
Ok(false) => break,
|
||||
Err(e) => return Err(e),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Paginator<ChannelVideo> {
|
||||
pub async fn next(&self, query: RustyPipeQuery) -> Result<Option<Self>> {
|
||||
Ok(match &self.ctoken {
|
||||
Some(ctoken) => Some(query.channel_videos_continuation(ctoken).await?),
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn extend(&mut self, query: RustyPipeQuery) -> Result<bool> {
|
||||
match self.next(query).await {
|
||||
Ok(Some(paginator)) => {
|
||||
let mut items = paginator.items;
|
||||
self.items.append(&mut items);
|
||||
self.ctoken = paginator.ctoken;
|
||||
Ok(true)
|
||||
}
|
||||
Ok(None) => Ok(false),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn extend_pages(&mut self, query: RustyPipeQuery, n_pages: usize) -> Result<()> {
|
||||
for _ in 0..n_pages {
|
||||
match self.extend(query.clone()).await {
|
||||
Ok(false) => break,
|
||||
Err(e) => return Err(e),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn extend_limit(&mut self, query: RustyPipeQuery, n_items: usize) -> Result<()> {
|
||||
while self.items.len() < n_items {
|
||||
match self.extend(query.clone()).await {
|
||||
Ok(false) => break,
|
||||
Err(e) => return Err(e),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Paginator<ChannelPlaylist> {
|
||||
pub async fn next(&self, query: RustyPipeQuery) -> Result<Option<Self>> {
|
||||
Ok(match &self.ctoken {
|
||||
Some(ctoken) => Some(query.channel_playlists_continuation(ctoken).await?),
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn extend(&mut self, query: RustyPipeQuery) -> Result<bool> {
|
||||
match self.next(query).await {
|
||||
Ok(Some(paginator)) => {
|
||||
let mut items = paginator.items;
|
||||
self.items.append(&mut items);
|
||||
self.ctoken = paginator.ctoken;
|
||||
Ok(true)
|
||||
}
|
||||
Ok(None) => Ok(false),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn extend_pages(&mut self, query: RustyPipeQuery, n_pages: usize) -> Result<()> {
|
||||
for _ in 0..n_pages {
|
||||
match self.extend(query.clone()).await {
|
||||
Ok(false) => break,
|
||||
Err(e) => return Err(e),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn extend_limit(&mut self, query: RustyPipeQuery, n_items: usize) -> Result<()> {
|
||||
while self.items.len() < n_items {
|
||||
match self.extend(query.clone()).await {
|
||||
Ok(false) => break,
|
||||
Err(e) => return Err(e),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Paginator<Comment> {
|
||||
pub async fn next(&self, query: RustyPipeQuery) -> Result<Option<Self>> {
|
||||
Ok(match &self.ctoken {
|
||||
Some(ctoken) => Some(query.video_comments(ctoken).await?),
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn extend(&mut self, query: RustyPipeQuery) -> Result<bool> {
|
||||
match self.next(query).await {
|
||||
Ok(Some(paginator)) => {
|
||||
let mut items = paginator.items;
|
||||
self.items.append(&mut items);
|
||||
self.ctoken = paginator.ctoken;
|
||||
Ok(true)
|
||||
}
|
||||
Ok(None) => Ok(false),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn extend_pages(&mut self, query: RustyPipeQuery, n_pages: usize) -> Result<()> {
|
||||
for _ in 0..n_pages {
|
||||
match self.extend(query.clone()).await {
|
||||
Ok(false) => break,
|
||||
Err(e) => return Err(e),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn extend_limit(&mut self, query: RustyPipeQuery, n_items: usize) -> Result<()> {
|
||||
while self.items.len() < n_items {
|
||||
match self.extend(query.clone()).await {
|
||||
Ok(false) => break,
|
||||
Err(e) => return Err(e),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Paginator<SearchItem> {
|
||||
pub async fn next(&self, query: RustyPipeQuery) -> Result<Option<Self>> {
|
||||
Ok(match &self.ctoken {
|
||||
Some(ctoken) => Some(query.search_continuation(ctoken).await?),
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn extend(&mut self, query: RustyPipeQuery) -> Result<bool> {
|
||||
match self.next(query).await {
|
||||
Ok(Some(paginator)) => {
|
||||
let mut items = paginator.items;
|
||||
self.items.append(&mut items);
|
||||
self.ctoken = paginator.ctoken;
|
||||
Ok(true)
|
||||
}
|
||||
Ok(None) => Ok(false),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn extend_pages(&mut self, query: RustyPipeQuery, n_pages: usize) -> Result<()> {
|
||||
for _ in 0..n_pages {
|
||||
match self.extend(query.clone()).await {
|
||||
Ok(false) => break,
|
||||
Err(e) => return Err(e),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn extend_limit(&mut self, query: RustyPipeQuery, n_items: usize) -> Result<()> {
|
||||
while self.items.len() < n_items {
|
||||
match self.extend(query.clone()).await {
|
||||
Ok(false) => break,
|
||||
Err(e) => return Err(e),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
paginator!(PlaylistVideo, RustyPipeQuery::playlist_continuation);
|
||||
paginator!(RecommendedVideo, RustyPipeQuery::video_recommendations);
|
||||
paginator!(Comment, RustyPipeQuery::video_comments);
|
||||
paginator!(ChannelVideo, RustyPipeQuery::channel_videos_continuation);
|
||||
paginator!(
|
||||
ChannelPlaylist,
|
||||
RustyPipeQuery::channel_playlists_continuation
|
||||
);
|
||||
paginator!(SearchItem, RustyPipeQuery::search_continuation);
|
||||
|
|
|
|||
|
|
@ -38,6 +38,10 @@ use crate::serializer::{
|
|||
use crate::timeago;
|
||||
use crate::util::{self, TryRemove};
|
||||
|
||||
use self::search::ChannelRenderer;
|
||||
use self::search::PlaylistRenderer;
|
||||
use self::search::VideoRenderer;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ContentRenderer<T> {
|
||||
|
|
@ -518,7 +522,7 @@ pub trait FromWLang<T> {
|
|||
}
|
||||
|
||||
pub trait TryFromWLang<T>: Sized {
|
||||
fn from_w_lang(from: T, lang: Language) -> core::result::Result<Self, util::MappingError>;
|
||||
fn from_w_lang(from: T, lang: Language) -> Result<Self, util::MappingError>;
|
||||
}
|
||||
|
||||
impl FromWLang<GridVideoRenderer> for model::ChannelVideo {
|
||||
|
|
@ -578,7 +582,7 @@ impl TryFromWLang<CompactVideoRenderer> for model::RecommendedVideo {
|
|||
fn from_w_lang(
|
||||
video: CompactVideoRenderer,
|
||||
lang: Language,
|
||||
) -> core::result::Result<Self, util::MappingError> {
|
||||
) -> Result<Self, util::MappingError> {
|
||||
let channel = model::ChannelId::try_from(video.channel)?;
|
||||
|
||||
Ok(Self {
|
||||
|
|
@ -609,3 +613,89 @@ impl TryFromWLang<CompactVideoRenderer> for model::RecommendedVideo {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromWLang<VideoRenderer> for model::SearchVideo {
|
||||
fn from_w_lang(video: VideoRenderer, lang: Language) -> Result<Self, util::MappingError> {
|
||||
let channel = model::ChannelId::try_from(video.channel)?;
|
||||
let mut metadata_snippets = video.detailed_metadata_snippets;
|
||||
|
||||
Ok(Self {
|
||||
id: video.video_id,
|
||||
title: video.title,
|
||||
length: video
|
||||
.length_text
|
||||
.and_then(|txt| util::parse_video_length(&txt)),
|
||||
thumbnail: video.thumbnail.into(),
|
||||
channel: model::ChannelTag {
|
||||
id: channel.id,
|
||||
name: channel.name,
|
||||
avatar: video
|
||||
.channel_thumbnail_supported_renderers
|
||||
.channel_thumbnail_with_link_renderer
|
||||
.thumbnail
|
||||
.into(),
|
||||
verification: video.owner_badges.into(),
|
||||
subscriber_count: None,
|
||||
},
|
||||
publish_date: video
|
||||
.published_time_text
|
||||
.as_ref()
|
||||
.and_then(|txt| timeago::parse_timeago_to_dt(lang, txt)),
|
||||
publish_date_txt: video.published_time_text,
|
||||
view_count: video
|
||||
.view_count_text
|
||||
.and_then(|txt| util::parse_numeric(&txt).ok())
|
||||
.unwrap_or_default(),
|
||||
is_live: video.thumbnail_overlays.is_live(),
|
||||
is_short: video.thumbnail_overlays.is_short(),
|
||||
short_description: metadata_snippets
|
||||
.try_swap_remove(0)
|
||||
.map(|s| s.snippet_text)
|
||||
.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PlaylistRenderer> for model::SearchPlaylist {
|
||||
fn from(playlist: PlaylistRenderer) -> Self {
|
||||
let mut thumbnails = playlist.thumbnails;
|
||||
|
||||
Self {
|
||||
id: playlist.playlist_id,
|
||||
name: playlist.title,
|
||||
thumbnail: thumbnails.try_swap_remove(0).unwrap_or_default().into(),
|
||||
video_count: playlist.video_count,
|
||||
first_videos: playlist
|
||||
.videos
|
||||
.into_iter()
|
||||
.map(|v| model::SearchPlaylistVideo {
|
||||
id: v.child_video_renderer.video_id,
|
||||
title: v.child_video_renderer.title,
|
||||
length: v
|
||||
.child_video_renderer
|
||||
.length_text
|
||||
.and_then(|txt| util::parse_video_length(&txt)),
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChannelRenderer> for model::SearchChannel {
|
||||
fn from(channel: ChannelRenderer) -> Self {
|
||||
Self {
|
||||
id: channel.channel_id,
|
||||
name: channel.title,
|
||||
avatar: channel.thumbnail.into(),
|
||||
verification: channel.owner_badges.into(),
|
||||
subscriber_count: channel
|
||||
.subscriber_count_text
|
||||
.and_then(|txt| util::parse_numeric(&txt).ok()),
|
||||
video_count: channel
|
||||
.video_count_text
|
||||
.and_then(|txt| util::parse_numeric(&txt).ok())
|
||||
.unwrap_or_default(),
|
||||
short_description: channel.description_snippet,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,17 +3,13 @@ use serde::Serialize;
|
|||
use crate::{
|
||||
deobfuscate::Deobfuscator,
|
||||
error::{Error, ExtractionError},
|
||||
model::{
|
||||
ChannelId, ChannelTag, Paginator, SearchChannel, SearchItem, SearchPlaylist,
|
||||
SearchPlaylistVideo, SearchResult, SearchVideo,
|
||||
},
|
||||
model::{Paginator, SearchItem, SearchResult, SearchVideo},
|
||||
param::{search_filter::SearchFilter, Language},
|
||||
timeago,
|
||||
util::{self, TryRemove},
|
||||
util::TryRemove,
|
||||
};
|
||||
|
||||
use super::{
|
||||
response::{self, IsLive, IsShort},
|
||||
response::{self, TryFromWLang},
|
||||
ClientType, MapResponse, MapResult, QContinuation, RustyPipeQuery, YTContext,
|
||||
};
|
||||
|
||||
|
|
@ -183,86 +179,20 @@ fn map_search_items(
|
|||
let mapped_items = items
|
||||
.into_iter()
|
||||
.filter_map(|item| match item {
|
||||
response::search::SearchItem::VideoRenderer(mut video) => {
|
||||
match ChannelId::try_from(video.channel) {
|
||||
Ok(channel) => Some(SearchItem::Video(SearchVideo {
|
||||
id: video.video_id,
|
||||
title: video.title,
|
||||
length: video
|
||||
.length_text
|
||||
.and_then(|txt| util::parse_video_length_or_warn(&txt, &mut warnings)),
|
||||
thumbnail: video.thumbnail.into(),
|
||||
channel: ChannelTag {
|
||||
id: channel.id,
|
||||
name: channel.name,
|
||||
avatar: video
|
||||
.channel_thumbnail_supported_renderers
|
||||
.channel_thumbnail_with_link_renderer
|
||||
.thumbnail
|
||||
.into(),
|
||||
verification: video.owner_badges.into(),
|
||||
subscriber_count: None,
|
||||
},
|
||||
publish_date: video.published_time_text.as_ref().and_then(|txt| {
|
||||
timeago::parse_timeago_or_warn(lang, txt, &mut warnings)
|
||||
}),
|
||||
publish_date_txt: video.published_time_text,
|
||||
view_count: video
|
||||
.view_count_text
|
||||
.and_then(|txt| util::parse_numeric(&txt).ok())
|
||||
.unwrap_or_default(),
|
||||
is_live: video.thumbnail_overlays.is_live(),
|
||||
is_short: video.thumbnail_overlays.is_short(),
|
||||
short_description: video
|
||||
.detailed_metadata_snippets
|
||||
.try_swap_remove(0)
|
||||
.map(|s| s.snippet_text)
|
||||
.unwrap_or_default(),
|
||||
})),
|
||||
response::search::SearchItem::VideoRenderer(video) => {
|
||||
match SearchVideo::from_w_lang(video, lang) {
|
||||
Ok(video) => Some(SearchItem::Video(video)),
|
||||
Err(e) => {
|
||||
warnings.push(e.to_string());
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
response::search::SearchItem::PlaylistRenderer(mut playlist) => {
|
||||
Some(SearchItem::Playlist(SearchPlaylist {
|
||||
id: playlist.playlist_id,
|
||||
name: playlist.title,
|
||||
thumbnail: playlist
|
||||
.thumbnails
|
||||
.try_swap_remove(0)
|
||||
.unwrap_or_default()
|
||||
.into(),
|
||||
video_count: playlist.video_count,
|
||||
first_videos: playlist
|
||||
.videos
|
||||
.into_iter()
|
||||
.map(|v| SearchPlaylistVideo {
|
||||
id: v.child_video_renderer.video_id,
|
||||
title: v.child_video_renderer.title,
|
||||
length: v.child_video_renderer.length_text.and_then(|txt| {
|
||||
util::parse_video_length_or_warn(&txt, &mut warnings)
|
||||
}),
|
||||
})
|
||||
.collect(),
|
||||
}))
|
||||
response::search::SearchItem::PlaylistRenderer(playlist) => {
|
||||
Some(SearchItem::Playlist(playlist.into()))
|
||||
}
|
||||
response::search::SearchItem::ChannelRenderer(channel) => {
|
||||
Some(SearchItem::Channel(SearchChannel {
|
||||
id: channel.channel_id,
|
||||
name: channel.title,
|
||||
avatar: channel.thumbnail.into(),
|
||||
verification: channel.owner_badges.into(),
|
||||
subscriber_count: channel
|
||||
.subscriber_count_text
|
||||
.and_then(|txt| util::parse_numeric_or_warn(&txt, &mut warnings)),
|
||||
video_count: channel
|
||||
.video_count_text
|
||||
.and_then(|txt| util::parse_numeric(&txt).ok())
|
||||
.unwrap_or_default(),
|
||||
short_description: channel.description_snippet,
|
||||
}))
|
||||
Some(SearchItem::Channel(channel.into()))
|
||||
}
|
||||
response::search::SearchItem::ShowingResultsForRenderer { corrected_query } => {
|
||||
c_query = Some(corrected_query);
|
||||
|
|
|
|||
|
|
@ -516,11 +516,7 @@ fn map_comment(
|
|||
}),
|
||||
_ => None,
|
||||
},
|
||||
publish_date: timeago::parse_timeago_or_warn(
|
||||
lang,
|
||||
&c.published_time_text,
|
||||
&mut warnings,
|
||||
),
|
||||
publish_date: timeago::parse_timeago_to_dt(lang, &c.published_time_text),
|
||||
publish_date_txt: c.published_time_text,
|
||||
like_count: util::parse_numeric_or_warn(
|
||||
&c.action_buttons
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
pub(crate) type Result<T> = core::result::Result<T, Error>;
|
||||
|
||||
/// Custom error type for the RustyPipe library
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[non_exhaustive]
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use log::error;
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::deobfuscate::DeobfData;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::error::Error;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[non_exhaustive]
|
||||
|
|
@ -96,7 +96,7 @@ impl FileReporter {
|
|||
}
|
||||
}
|
||||
|
||||
fn _report(&self, report: &Report) -> Result<()> {
|
||||
fn _report(&self, report: &Report) -> Result<(), Error> {
|
||||
let report_path = get_report_path(&self.path, report, "json")?;
|
||||
serde_json::to_writer_pretty(&File::create(report_path)?, &report)
|
||||
.map_err(|e| Error::Other(format!("could not serialize report. err: {}", e).into()))?;
|
||||
|
|
@ -119,7 +119,7 @@ impl Reporter for FileReporter {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_report_path(root: &Path, report: &Report, ext: &str) -> Result<PathBuf> {
|
||||
fn get_report_path(root: &Path, report: &Report, ext: &str) -> Result<PathBuf, Error> {
|
||||
if !root.is_dir() {
|
||||
std::fs::create_dir_all(root)?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -184,18 +184,6 @@ pub fn parse_timeago_to_dt(lang: Language, textual_date: &str) -> Option<DateTim
|
|||
parse_timeago(lang, textual_date).map(|ta| ta.into())
|
||||
}
|
||||
|
||||
pub(crate) fn parse_timeago_or_warn(
|
||||
lang: Language,
|
||||
textual_date: &str,
|
||||
warnings: &mut Vec<String>,
|
||||
) -> Option<DateTime<Local>> {
|
||||
let res = parse_timeago_to_dt(lang, textual_date);
|
||||
if res.is_none() {
|
||||
warnings.push(format!("could not parse timeago `{}`", textual_date));
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
/// Parse a textual date (e.g. "29 minutes ago" or "Jul 2, 2014") into a ParsedDate object.
|
||||
///
|
||||
/// Returns None if the date could not be parsed.
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use once_cell::sync::Lazy;
|
|||
use rand::Rng;
|
||||
use url::Url;
|
||||
|
||||
use crate::{error::Error, error::Result, param::Language};
|
||||
use crate::{error::Error, param::Language};
|
||||
|
||||
const CONTENT_PLAYBACK_NONCE_ALPHABET: &[u8; 64] =
|
||||
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||
|
|
@ -57,7 +57,7 @@ pub fn generate_content_playback_nonce() -> String {
|
|||
/// Example:
|
||||
///
|
||||
/// `example.com/api?k1=v1&k2=v2 => example.com/api; {k1: v1, k2: v2}`
|
||||
pub fn url_to_params(url: &str) -> Result<(String, BTreeMap<String, String>)> {
|
||||
pub fn url_to_params(url: &str) -> Result<(String, BTreeMap<String, String>), Error> {
|
||||
let mut parsed_url = Url::parse(url)
|
||||
.map_err(|e| Error::Other(format!("could not parse url `{}` err: {}", url, e).into()))?;
|
||||
let url_params: BTreeMap<String, String> = parsed_url
|
||||
|
|
@ -77,7 +77,7 @@ pub fn urlencode(string: &str) -> String {
|
|||
}
|
||||
|
||||
/// Parse a string after removing all non-numeric characters
|
||||
pub fn parse_numeric<F>(string: &str) -> core::result::Result<F, F::Err>
|
||||
pub fn parse_numeric<F>(string: &str) -> Result<F, F::Err>
|
||||
where
|
||||
F: FromStr,
|
||||
{
|
||||
|
|
@ -147,14 +147,6 @@ where
|
|||
res.ok()
|
||||
}
|
||||
|
||||
pub fn parse_video_length_or_warn(text: &str, warnings: &mut Vec<String>) -> Option<u32> {
|
||||
let res = parse_video_length(text);
|
||||
if res.is_none() {
|
||||
warnings.push(format!("could not parse video length `{}`", text));
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
pub fn retry_delay(
|
||||
n_past_retries: u32,
|
||||
min_retry_interval: u32,
|
||||
|
|
|
|||
Reference in a new issue