Compare commits
	
		
			No commits in common. "db6a479bcfab96deed27be9b6c3a00f41ab3ef0b" and "a3e3269fb322712bc746b88146553d3d86d930ce" have entirely different histories.
		
	
	
		
			
				db6a479bcf
			
			...
			
				a3e3269fb3
			
		
	
		
					 24 changed files with 280 additions and 887 deletions
				
			
		| 
						 | 
				
			
			@ -11,4 +11,3 @@ repos:
 | 
			
		|||
      - id: cargo-fmt
 | 
			
		||||
      - id: cargo-check
 | 
			
		||||
      - id: cargo-clippy
 | 
			
		||||
        args: ["--", "-D", "warnings"]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,9 +19,9 @@ use super::{
 | 
			
		|||
 | 
			
		||||
#[derive(Debug, Serialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
struct QChannel<'a> {
 | 
			
		||||
struct QChannel {
 | 
			
		||||
    context: YTContext,
 | 
			
		||||
    browse_id: &'a str,
 | 
			
		||||
    browse_id: String,
 | 
			
		||||
    params: Params,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -56,7 +56,7 @@ impl RustyPipeQuery {
 | 
			
		|||
        let context = self.get_context(ClientType::Desktop, true).await;
 | 
			
		||||
        let request_body = QChannel {
 | 
			
		||||
            context,
 | 
			
		||||
            browse_id: channel_id,
 | 
			
		||||
            browse_id: channel_id.to_owned(),
 | 
			
		||||
            params: match order {
 | 
			
		||||
                ChannelOrder::Latest => Params::VideosLatest,
 | 
			
		||||
                ChannelOrder::Oldest => Params::VideosOldest,
 | 
			
		||||
| 
						 | 
				
			
			@ -81,7 +81,7 @@ impl RustyPipeQuery {
 | 
			
		|||
        let context = self.get_context(ClientType::Desktop, true).await;
 | 
			
		||||
        let request_body = QContinuation {
 | 
			
		||||
            context,
 | 
			
		||||
            continuation: ctoken,
 | 
			
		||||
            continuation: ctoken.to_owned(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        self.execute_request::<response::ChannelCont, _, _>(
 | 
			
		||||
| 
						 | 
				
			
			@ -101,7 +101,7 @@ impl RustyPipeQuery {
 | 
			
		|||
        let context = self.get_context(ClientType::Desktop, true).await;
 | 
			
		||||
        let request_body = QChannel {
 | 
			
		||||
            context,
 | 
			
		||||
            browse_id: channel_id,
 | 
			
		||||
            browse_id: channel_id.to_owned(),
 | 
			
		||||
            params: Params::Playlists,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -122,7 +122,7 @@ impl RustyPipeQuery {
 | 
			
		|||
        let context = self.get_context(ClientType::Desktop, true).await;
 | 
			
		||||
        let request_body = QContinuation {
 | 
			
		||||
            context,
 | 
			
		||||
            continuation: ctoken,
 | 
			
		||||
            continuation: ctoken.to_owned(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        self.execute_request::<response::ChannelCont, _, _>(
 | 
			
		||||
| 
						 | 
				
			
			@ -139,7 +139,7 @@ impl RustyPipeQuery {
 | 
			
		|||
        let context = self.get_context(ClientType::Desktop, true).await;
 | 
			
		||||
        let request_body = QChannel {
 | 
			
		||||
            context,
 | 
			
		||||
            browse_id: channel_id,
 | 
			
		||||
            browse_id: channel_id.to_owned(),
 | 
			
		||||
            params: Params::Info,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -180,7 +180,7 @@ impl MapResponse<Channel<Paginator<ChannelVideo>>> for response::Channel {
 | 
			
		|||
                id,
 | 
			
		||||
                lang,
 | 
			
		||||
            )?,
 | 
			
		||||
            warnings,
 | 
			
		||||
            warnings: warnings,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -211,7 +211,7 @@ impl MapResponse<Channel<Paginator<ChannelPlaylist>>> for response::Channel {
 | 
			
		|||
                id,
 | 
			
		||||
                lang,
 | 
			
		||||
            )?,
 | 
			
		||||
            warnings,
 | 
			
		||||
            warnings: warnings,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,7 +31,7 @@ impl RustyPipeQuery {
 | 
			
		|||
                        msgs: Vec::new(),
 | 
			
		||||
                        deobf_data: None,
 | 
			
		||||
                        http_request: crate::report::HTTPRequest {
 | 
			
		||||
                            url,
 | 
			
		||||
                            url: url,
 | 
			
		||||
                            method: "GET".to_owned(),
 | 
			
		||||
                            req_header: BTreeMap::new(),
 | 
			
		||||
                            req_body: String::new(),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,6 @@ mod pagination;
 | 
			
		|||
mod player;
 | 
			
		||||
mod playlist;
 | 
			
		||||
mod response;
 | 
			
		||||
mod search;
 | 
			
		||||
mod video_details;
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "rss")]
 | 
			
		||||
| 
						 | 
				
			
			@ -125,9 +124,9 @@ struct ThirdParty {
 | 
			
		|||
 | 
			
		||||
#[derive(Debug, Serialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
struct QContinuation<'a> {
 | 
			
		||||
struct QContinuation {
 | 
			
		||||
    context: YTContext,
 | 
			
		||||
    continuation: &'a str,
 | 
			
		||||
    continuation: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const DEFAULT_UA: &str = "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0";
 | 
			
		||||
| 
						 | 
				
			
			@ -515,11 +514,11 @@ impl RustyPipe {
 | 
			
		|||
                )
 | 
			
		||||
                .await?;
 | 
			
		||||
 | 
			
		||||
            util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &swjs, 1).ok_or_else(|| {
 | 
			
		||||
                Error::from(ExtractionError::InvalidData(
 | 
			
		||||
            util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &swjs, 1).ok_or(Error::from(
 | 
			
		||||
                ExtractionError::InvalidData(
 | 
			
		||||
                    "Could not find desktop client version in sw.js".into(),
 | 
			
		||||
                ))
 | 
			
		||||
            })
 | 
			
		||||
                ),
 | 
			
		||||
            ))
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let from_html = async {
 | 
			
		||||
| 
						 | 
				
			
			@ -533,11 +532,11 @@ impl RustyPipe {
 | 
			
		|||
                )
 | 
			
		||||
                .await?;
 | 
			
		||||
 | 
			
		||||
            util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or_else(|| {
 | 
			
		||||
                Error::from(ExtractionError::InvalidData(
 | 
			
		||||
            util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or(Error::from(
 | 
			
		||||
                ExtractionError::InvalidData(
 | 
			
		||||
                    "Could not find desktop client version in sw.js".into(),
 | 
			
		||||
                ))
 | 
			
		||||
            })
 | 
			
		||||
                ),
 | 
			
		||||
            ))
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        match from_swjs.await {
 | 
			
		||||
| 
						 | 
				
			
			@ -562,11 +561,9 @@ impl RustyPipe {
 | 
			
		|||
                )
 | 
			
		||||
                .await?;
 | 
			
		||||
 | 
			
		||||
            util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &swjs, 1).ok_or_else(|| {
 | 
			
		||||
                Error::from(ExtractionError::InvalidData(
 | 
			
		||||
                    "Could not find music client version in sw.js".into(),
 | 
			
		||||
                ))
 | 
			
		||||
            })
 | 
			
		||||
            util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &swjs, 1).ok_or(Error::from(
 | 
			
		||||
                ExtractionError::InvalidData("Could not find music client version in sw.js".into()),
 | 
			
		||||
            ))
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let from_html = async {
 | 
			
		||||
| 
						 | 
				
			
			@ -580,11 +577,11 @@ impl RustyPipe {
 | 
			
		|||
                )
 | 
			
		||||
                .await?;
 | 
			
		||||
 | 
			
		||||
            util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or_else(|| {
 | 
			
		||||
                Error::from(ExtractionError::InvalidData(
 | 
			
		||||
            util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or(Error::from(
 | 
			
		||||
                ExtractionError::InvalidData(
 | 
			
		||||
                    "Could not find music client version on html page".into(),
 | 
			
		||||
                ))
 | 
			
		||||
            })
 | 
			
		||||
                ),
 | 
			
		||||
            ))
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        match from_swjs.await {
 | 
			
		||||
| 
						 | 
				
			
			@ -994,14 +991,12 @@ impl RustyPipeQuery {
 | 
			
		|||
                    if !mapres.warnings.is_empty() {
 | 
			
		||||
                        create_report(
 | 
			
		||||
                            Level::WRN,
 | 
			
		||||
                            Some(ExtractionError::DeserializationWarnings.to_string()),
 | 
			
		||||
                            Some(ExtractionError::Warnings.to_string()),
 | 
			
		||||
                            mapres.warnings,
 | 
			
		||||
                        );
 | 
			
		||||
 | 
			
		||||
                        if self.opts.strict {
 | 
			
		||||
                            return Err(Error::Extraction(
 | 
			
		||||
                                ExtractionError::DeserializationWarnings,
 | 
			
		||||
                            ));
 | 
			
		||||
                            return Err(Error::Extraction(ExtractionError::Warnings));
 | 
			
		||||
                        }
 | 
			
		||||
                    } else if self.opts.report {
 | 
			
		||||
                        create_report(Level::DBG, None, vec![]);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
use crate::error::Result;
 | 
			
		||||
 | 
			
		||||
use crate::model::{
 | 
			
		||||
    ChannelPlaylist, ChannelVideo, Comment, Paginator, PlaylistVideo, RecommendedVideo, SearchItem,
 | 
			
		||||
    ChannelPlaylist, ChannelVideo, Comment, Paginator, PlaylistVideo, RecommendedVideo,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::RustyPipeQuery;
 | 
			
		||||
| 
						 | 
				
			
			@ -225,47 +225,3 @@ impl Paginator<Comment> {
 | 
			
		|||
        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(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,7 +26,7 @@ use super::{
 | 
			
		|||
 | 
			
		||||
#[derive(Debug, Serialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
struct QPlayer<'a> {
 | 
			
		||||
struct QPlayer {
 | 
			
		||||
    context: YTContext,
 | 
			
		||||
    /// Website playback context
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
| 
						 | 
				
			
			@ -35,7 +35,7 @@ struct QPlayer<'a> {
 | 
			
		|||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    cpn: Option<String>,
 | 
			
		||||
    /// YouTube video ID
 | 
			
		||||
    video_id: &'a str,
 | 
			
		||||
    video_id: String,
 | 
			
		||||
    /// Set to true to allow extraction of streams with sensitive content
 | 
			
		||||
    content_check_ok: bool,
 | 
			
		||||
    /// Probably refers to allowing sensitive content, too
 | 
			
		||||
| 
						 | 
				
			
			@ -82,7 +82,7 @@ impl RustyPipeQuery {
 | 
			
		|||
                    },
 | 
			
		||||
                }),
 | 
			
		||||
                cpn: None,
 | 
			
		||||
                video_id,
 | 
			
		||||
                video_id: video_id.to_owned(),
 | 
			
		||||
                content_check_ok: true,
 | 
			
		||||
                racy_check_ok: true,
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -91,7 +91,7 @@ impl RustyPipeQuery {
 | 
			
		|||
                context,
 | 
			
		||||
                playback_context: None,
 | 
			
		||||
                cpn: Some(util::generate_content_playback_nonce()),
 | 
			
		||||
                video_id,
 | 
			
		||||
                video_id: video_id.to_owned(),
 | 
			
		||||
                content_check_ok: true,
 | 
			
		||||
                racy_check_ok: true,
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -46,7 +46,7 @@ impl RustyPipeQuery {
 | 
			
		|||
        let context = self.get_context(ClientType::Desktop, true).await;
 | 
			
		||||
        let request_body = QContinuation {
 | 
			
		||||
            context,
 | 
			
		||||
            continuation: ctoken,
 | 
			
		||||
            continuation: ctoken.to_owned(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        self.execute_request::<response::PlaylistCont, _, _>(
 | 
			
		||||
| 
						 | 
				
			
			@ -143,7 +143,7 @@ impl MapResponse<Playlist> for response::Playlist {
 | 
			
		|||
                    Err(ExtractionError::InvalidData("no video count".into()))
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
            None => videos.len() as u64,
 | 
			
		||||
            None => videos.len() as u32,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let playlist_id = self.header.playlist_header_renderer.playlist_id;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,7 @@ use crate::serializer::ignore_any;
 | 
			
		|||
use crate::serializer::{text::Text, MapResult, VecLogError};
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Channel {
 | 
			
		||||
    pub header: Header,
 | 
			
		||||
| 
						 | 
				
			
			@ -19,13 +19,13 @@ pub struct Channel {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ChannelCont {
 | 
			
		||||
    pub on_response_received_actions: Vec<OnResponseReceivedAction>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Contents {
 | 
			
		||||
    pub two_column_browse_results_renderer: TabsRenderer,
 | 
			
		||||
| 
						 | 
				
			
			@ -34,26 +34,26 @@ pub struct Contents {
 | 
			
		|||
/// YouTube channel tab view. Contains multiple tabs
 | 
			
		||||
/// (Home, Videos, Playlists, About...). We can ignore unknown tabs.
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct TabsRenderer {
 | 
			
		||||
    #[serde_as(as = "VecSkipError<_>")]
 | 
			
		||||
    pub tabs: Vec<TabRendererWrap>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct TabRendererWrap {
 | 
			
		||||
    pub tab_renderer: ContentRenderer<SectionListRendererWrap>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct SectionListRendererWrap {
 | 
			
		||||
    pub section_list_renderer: SectionListRenderer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct SectionListRenderer {
 | 
			
		||||
    pub contents: Vec<ItemSectionRendererWrap>,
 | 
			
		||||
| 
						 | 
				
			
			@ -63,14 +63,14 @@ pub struct SectionListRenderer {
 | 
			
		|||
    pub target_id: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ItemSectionRendererWrap {
 | 
			
		||||
    pub item_section_renderer: ContentsRenderer<ChannelContent>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Default, Debug, Deserialize)]
 | 
			
		||||
#[derive(Default, Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub enum ChannelContent {
 | 
			
		||||
    GridRenderer {
 | 
			
		||||
| 
						 | 
				
			
			@ -83,14 +83,14 @@ pub enum ChannelContent {
 | 
			
		|||
    None,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Header {
 | 
			
		||||
    pub c4_tabbed_header_renderer: HeaderRenderer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct HeaderRenderer {
 | 
			
		||||
    pub channel_id: String,
 | 
			
		||||
| 
						 | 
				
			
			@ -114,26 +114,26 @@ pub struct HeaderRenderer {
 | 
			
		|||
    pub tv_banner: Thumbnails,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Metadata {
 | 
			
		||||
    pub channel_metadata_renderer: ChannelMetadataRenderer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ChannelMetadataRenderer {
 | 
			
		||||
    pub description: String,
 | 
			
		||||
    pub vanity_channel_url: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Microformat {
 | 
			
		||||
    pub microformat_data_renderer: MicroformatDataRenderer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct MicroformatDataRenderer {
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
| 
						 | 
				
			
			@ -141,7 +141,7 @@ pub struct MicroformatDataRenderer {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ChannelFullMetadata {
 | 
			
		||||
    #[serde_as(as = "Text")]
 | 
			
		||||
| 
						 | 
				
			
			@ -153,7 +153,7 @@ pub struct ChannelFullMetadata {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct PrimaryLink {
 | 
			
		||||
    #[serde_as(as = "Text")]
 | 
			
		||||
| 
						 | 
				
			
			@ -161,26 +161,26 @@ pub struct PrimaryLink {
 | 
			
		|||
    pub navigation_endpoint: NavigationEndpoint,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct NavigationEndpoint {
 | 
			
		||||
    pub url_endpoint: UrlEndpoint,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UrlEndpoint {
 | 
			
		||||
    pub url: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct OnResponseReceivedAction {
 | 
			
		||||
    pub append_continuation_items_action: AppendAction,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct AppendAction {
 | 
			
		||||
    #[serde_as(as = "VecLogError<_>")]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ use serde::Deserialize;
 | 
			
		|||
 | 
			
		||||
use super::Thumbnail;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
pub struct ChannelRss {
 | 
			
		||||
    #[serde(rename = "$unflatten=yt:channelId")]
 | 
			
		||||
    pub channel_id: String,
 | 
			
		||||
| 
						 | 
				
			
			@ -14,7 +14,7 @@ pub struct ChannelRss {
 | 
			
		|||
    pub entry: Vec<Entry>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
pub struct Entry {
 | 
			
		||||
    #[serde(rename = "$unflatten=yt:videoId")]
 | 
			
		||||
    pub video_id: String,
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +28,7 @@ pub struct Entry {
 | 
			
		|||
    pub media_group: MediaGroup,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
pub struct MediaGroup {
 | 
			
		||||
    #[serde(rename = "$unflatten=media:thumbnail")]
 | 
			
		||||
    pub thumbnail: Thumbnail,
 | 
			
		||||
| 
						 | 
				
			
			@ -38,7 +38,7 @@ pub struct MediaGroup {
 | 
			
		|||
    pub community: Community,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
pub struct Community {
 | 
			
		||||
    #[serde(rename = "$unflatten=media:starRating")]
 | 
			
		||||
    pub rating: Rating,
 | 
			
		||||
| 
						 | 
				
			
			@ -46,12 +46,12 @@ pub struct Community {
 | 
			
		|||
    pub statistics: Statistics,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
pub struct Rating {
 | 
			
		||||
    pub count: u64,
 | 
			
		||||
    pub count: u32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
pub struct Statistics {
 | 
			
		||||
    pub views: u64,
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,6 @@ pub mod channel;
 | 
			
		|||
pub mod player;
 | 
			
		||||
pub mod playlist;
 | 
			
		||||
pub mod playlist_music;
 | 
			
		||||
pub mod search;
 | 
			
		||||
pub mod video_details;
 | 
			
		||||
 | 
			
		||||
pub use channel::Channel;
 | 
			
		||||
| 
						 | 
				
			
			@ -11,8 +10,6 @@ pub use player::Player;
 | 
			
		|||
pub use playlist::Playlist;
 | 
			
		||||
pub use playlist::PlaylistCont;
 | 
			
		||||
pub use playlist_music::PlaylistMusic;
 | 
			
		||||
pub use search::Search;
 | 
			
		||||
pub use search::SearchCont;
 | 
			
		||||
pub use video_details::VideoComments;
 | 
			
		||||
pub use video_details::VideoDetails;
 | 
			
		||||
pub use video_details::VideoRecommendations;
 | 
			
		||||
| 
						 | 
				
			
			@ -30,20 +27,20 @@ use crate::serializer::{
 | 
			
		|||
    text::{Text, TextComponent},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ContentRenderer<T> {
 | 
			
		||||
    pub content: T,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ContentsRenderer<T> {
 | 
			
		||||
    #[serde(alias = "tabs")]
 | 
			
		||||
    pub contents: Vec<T>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ThumbnailsWrap {
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
| 
						 | 
				
			
			@ -52,14 +49,14 @@ pub struct ThumbnailsWrap {
 | 
			
		|||
 | 
			
		||||
/// List of images in different resolutions.
 | 
			
		||||
/// Not only used for thumbnails, but also for avatars and banners.
 | 
			
		||||
#[derive(Default, Debug, Deserialize)]
 | 
			
		||||
#[derive(Default, Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Thumbnails {
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    pub thumbnails: Vec<Thumbnail>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Thumbnail {
 | 
			
		||||
    pub url: String,
 | 
			
		||||
| 
						 | 
				
			
			@ -67,17 +64,13 @@ pub struct Thumbnail {
 | 
			
		|||
    pub height: u32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub enum VideoListItem {
 | 
			
		||||
    /// Video on channel page
 | 
			
		||||
    GridVideoRenderer(GridVideoRenderer),
 | 
			
		||||
    /// Video in recommendations
 | 
			
		||||
    CompactVideoRenderer(CompactVideoRenderer),
 | 
			
		||||
    /// Video in playlist
 | 
			
		||||
    PlaylistVideoRenderer(PlaylistVideoRenderer),
 | 
			
		||||
 | 
			
		||||
    /// Playlist on channel page
 | 
			
		||||
    GridPlaylistRenderer(GridPlaylistRenderer),
 | 
			
		||||
 | 
			
		||||
    /// Continauation items are located at the end of a list
 | 
			
		||||
| 
						 | 
				
			
			@ -97,7 +90,7 @@ pub enum VideoListItem {
 | 
			
		|||
 | 
			
		||||
/// Video displayed on a channel page
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct GridVideoRenderer {
 | 
			
		||||
    pub video_id: String,
 | 
			
		||||
| 
						 | 
				
			
			@ -118,7 +111,7 @@ pub struct GridVideoRenderer {
 | 
			
		|||
 | 
			
		||||
/// Video displayed in recommendations
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct CompactVideoRenderer {
 | 
			
		||||
    pub video_id: String,
 | 
			
		||||
| 
						 | 
				
			
			@ -152,7 +145,7 @@ pub struct CompactVideoRenderer {
 | 
			
		|||
 | 
			
		||||
/// Video displayed in a playlist
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct PlaylistVideoRenderer {
 | 
			
		||||
    pub video_id: String,
 | 
			
		||||
| 
						 | 
				
			
			@ -167,7 +160,7 @@ pub struct PlaylistVideoRenderer {
 | 
			
		|||
 | 
			
		||||
/// Playlist displayed on a channel page
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct GridPlaylistRenderer {
 | 
			
		||||
    pub playlist_id: String,
 | 
			
		||||
| 
						 | 
				
			
			@ -179,7 +172,7 @@ pub struct GridPlaylistRenderer {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UpcomingEventData {
 | 
			
		||||
    /// Unixtime in seconds
 | 
			
		||||
| 
						 | 
				
			
			@ -187,26 +180,26 @@ pub struct UpcomingEventData {
 | 
			
		|||
    pub start_time: i64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ContinuationItemRenderer {
 | 
			
		||||
    pub continuation_endpoint: ContinuationEndpoint,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ContinuationEndpoint {
 | 
			
		||||
    pub continuation_command: ContinuationCommand,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ContinuationCommand {
 | 
			
		||||
    pub token: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Icon {
 | 
			
		||||
    pub icon_type: IconType,
 | 
			
		||||
| 
						 | 
				
			
			@ -223,14 +216,14 @@ pub enum IconType {
 | 
			
		|||
    Like,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct VideoOwner {
 | 
			
		||||
    pub video_owner_renderer: VideoOwnerRenderer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct VideoOwnerRenderer {
 | 
			
		||||
    pub title: TextComponent,
 | 
			
		||||
| 
						 | 
				
			
			@ -242,13 +235,13 @@ pub struct VideoOwnerRenderer {
 | 
			
		|||
    pub badges: Vec<ChannelBadge>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ChannelBadge {
 | 
			
		||||
    pub metadata_badge_renderer: ChannelBadgeRenderer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ChannelBadgeRenderer {
 | 
			
		||||
    pub style: ChannelBadgeStyle,
 | 
			
		||||
| 
						 | 
				
			
			@ -261,19 +254,19 @@ pub enum ChannelBadgeStyle {
 | 
			
		|||
    BadgeStyleTypeVerifiedArtist,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct TimeOverlay {
 | 
			
		||||
    pub thumbnail_overlay_time_status_renderer: TimeOverlayRenderer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct TimeOverlayRenderer {
 | 
			
		||||
    /// `29:54`
 | 
			
		||||
    ///
 | 
			
		||||
    /// Is `LIVE` in case of a livestream and `SHORTS` in case of a short video
 | 
			
		||||
    /// Is `LIVE` in case of a livestream
 | 
			
		||||
    #[serde_as(as = "Text")]
 | 
			
		||||
    pub text: String,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
| 
						 | 
				
			
			@ -281,7 +274,7 @@ pub struct TimeOverlayRenderer {
 | 
			
		|||
    pub style: TimeOverlayStyle,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
 | 
			
		||||
#[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)]
 | 
			
		||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
 | 
			
		||||
pub enum TimeOverlayStyle {
 | 
			
		||||
    #[default]
 | 
			
		||||
| 
						 | 
				
			
			@ -292,7 +285,7 @@ pub enum TimeOverlayStyle {
 | 
			
		|||
 | 
			
		||||
/// Badges are displayed on the video thumbnail and
 | 
			
		||||
/// show certain video properties (e.g. active livestream)
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct VideoBadge {
 | 
			
		||||
    pub metadata_badge_renderer: VideoBadgeRenderer,
 | 
			
		||||
| 
						 | 
				
			
			@ -300,7 +293,7 @@ pub struct VideoBadge {
 | 
			
		|||
 | 
			
		||||
/// Badges are displayed on the video thumbnail and
 | 
			
		||||
/// show certain video properties (e.g. active livestream)
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct VideoBadgeRenderer {
 | 
			
		||||
    pub style: VideoBadgeStyle,
 | 
			
		||||
| 
						 | 
				
			
			@ -316,7 +309,7 @@ pub enum VideoBadgeStyle {
 | 
			
		|||
// YouTube Music
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct MusicItem {
 | 
			
		||||
    pub thumbnail: MusicThumbnailRenderer,
 | 
			
		||||
| 
						 | 
				
			
			@ -329,21 +322,21 @@ pub struct MusicItem {
 | 
			
		|||
    pub fixed_columns: Vec<MusicColumn>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct MusicThumbnailRenderer {
 | 
			
		||||
    #[serde(alias = "croppedSquareThumbnailRenderer")]
 | 
			
		||||
    pub music_thumbnail_renderer: ThumbnailsWrap,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct PlaylistItemData {
 | 
			
		||||
    pub video_id: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct MusicContentsRenderer<T> {
 | 
			
		||||
    pub contents: Vec<T>,
 | 
			
		||||
| 
						 | 
				
			
			@ -351,7 +344,7 @@ pub struct MusicContentsRenderer<T> {
 | 
			
		|||
    pub continuations: Option<Vec<MusicContinuation>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
pub struct MusicColumn {
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename = "musicResponsiveListItemFlexColumnRenderer",
 | 
			
		||||
| 
						 | 
				
			
			@ -361,18 +354,18 @@ pub struct MusicColumn {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
pub struct MusicColumnRenderer {
 | 
			
		||||
    pub text: TextComponent,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct MusicContinuation {
 | 
			
		||||
    pub next_continuation_data: MusicContinuationData,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct MusicContinuationData {
 | 
			
		||||
    pub continuation: String,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,7 @@ use serde_with::{json::JsonString, DefaultOnError};
 | 
			
		|||
use super::Thumbnails;
 | 
			
		||||
use crate::serializer::{text::Text, MapResult, VecLogError};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Player {
 | 
			
		||||
    pub playability_status: PlayabilityStatus,
 | 
			
		||||
| 
						 | 
				
			
			@ -18,7 +18,7 @@ pub struct Player {
 | 
			
		|||
    pub microformat: Option<Microformat>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(tag = "status", rename_all = "SCREAMING_SNAKE_CASE")]
 | 
			
		||||
pub enum PlayabilityStatus {
 | 
			
		||||
    #[serde(rename_all = "camelCase")]
 | 
			
		||||
| 
						 | 
				
			
			@ -36,11 +36,11 @@ pub enum PlayabilityStatus {
 | 
			
		|||
    Error { reason: String },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
pub struct Empty {}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct StreamingData {
 | 
			
		||||
    #[serde_as(as = "JsonString")]
 | 
			
		||||
| 
						 | 
				
			
			@ -58,7 +58,7 @@ pub struct StreamingData {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Format {
 | 
			
		||||
    pub itag: u32,
 | 
			
		||||
| 
						 | 
				
			
			@ -152,7 +152,7 @@ pub enum FormatType {
 | 
			
		|||
    FormatStreamTypeOtf,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Default, Debug, Deserialize)]
 | 
			
		||||
#[derive(Default, Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(default, rename_all = "camelCase")]
 | 
			
		||||
pub struct ColorInfo {
 | 
			
		||||
    pub primaries: Primaries,
 | 
			
		||||
| 
						 | 
				
			
			@ -166,7 +166,7 @@ pub enum Primaries {
 | 
			
		|||
    ColorPrimariesBt2020,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Default, Debug, Deserialize)]
 | 
			
		||||
#[derive(Default, Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(default, rename_all = "camelCase")]
 | 
			
		||||
pub struct AudioTrack {
 | 
			
		||||
    pub id: String,
 | 
			
		||||
| 
						 | 
				
			
			@ -174,20 +174,20 @@ pub struct AudioTrack {
 | 
			
		|||
    pub audio_is_default: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Captions {
 | 
			
		||||
    pub player_captions_tracklist_renderer: PlayerCaptionsTracklistRenderer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct PlayerCaptionsTracklistRenderer {
 | 
			
		||||
    pub caption_tracks: Vec<CaptionTrack>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct CaptionTrack {
 | 
			
		||||
    pub base_url: String,
 | 
			
		||||
| 
						 | 
				
			
			@ -197,7 +197,7 @@ pub struct CaptionTrack {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct VideoDetails {
 | 
			
		||||
    pub video_id: String,
 | 
			
		||||
| 
						 | 
				
			
			@ -215,7 +215,7 @@ pub struct VideoDetails {
 | 
			
		|||
    pub is_live_content: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Microformat {
 | 
			
		||||
    #[serde(alias = "microformatDataRenderer")]
 | 
			
		||||
| 
						 | 
				
			
			@ -223,7 +223,7 @@ pub struct Microformat {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct PlayerMicroformatRenderer {
 | 
			
		||||
    #[serde(alias = "familySafe")]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,7 @@ use crate::serializer::{MapResult, VecLogError};
 | 
			
		|||
 | 
			
		||||
use super::{ContentRenderer, ContentsRenderer, ThumbnailsWrap, VideoListItem};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Playlist {
 | 
			
		||||
    pub contents: Contents,
 | 
			
		||||
| 
						 | 
				
			
			@ -16,59 +16,59 @@ pub struct Playlist {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct PlaylistCont {
 | 
			
		||||
    #[serde_as(as = "VecSkipError<_>")]
 | 
			
		||||
    pub on_response_received_actions: Vec<OnResponseReceivedAction>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Contents {
 | 
			
		||||
    pub two_column_browse_results_renderer: ContentsRenderer<Tab>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Tab {
 | 
			
		||||
    pub tab_renderer: ContentRenderer<SectionList>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct SectionList {
 | 
			
		||||
    pub section_list_renderer: ContentsRenderer<ItemSection>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ItemSection {
 | 
			
		||||
    pub item_section_renderer: ContentsRenderer<PlaylistVideoListRenderer>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct PlaylistVideoListRenderer {
 | 
			
		||||
    pub playlist_video_list_renderer: PlaylistVideoList,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct PlaylistVideoList {
 | 
			
		||||
    #[serde_as(as = "VecLogError<_>")]
 | 
			
		||||
    pub contents: MapResult<Vec<VideoListItem>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Header {
 | 
			
		||||
    pub playlist_header_renderer: HeaderRenderer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct HeaderRenderer {
 | 
			
		||||
    pub playlist_id: String,
 | 
			
		||||
| 
						 | 
				
			
			@ -87,48 +87,48 @@ pub struct HeaderRenderer {
 | 
			
		|||
    pub byline: Vec<Byline>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct PlaylistHeaderBanner {
 | 
			
		||||
    pub hero_playlist_thumbnail_renderer: ThumbnailsWrap,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Byline {
 | 
			
		||||
    pub playlist_byline_renderer: BylineRenderer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct BylineRenderer {
 | 
			
		||||
    #[serde_as(as = "Text")]
 | 
			
		||||
    pub text: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Sidebar {
 | 
			
		||||
    pub playlist_sidebar_renderer: SidebarRenderer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct SidebarRenderer {
 | 
			
		||||
    #[serde_as(as = "VecSkipError<_>")]
 | 
			
		||||
    pub items: Vec<SidebarItemPrimary>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct SidebarItemPrimary {
 | 
			
		||||
    pub playlist_sidebar_primary_info_renderer: SidebarPrimaryInfoRenderer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct SidebarPrimaryInfoRenderer {
 | 
			
		||||
    pub thumbnail_renderer: PlaylistThumbnailRenderer,
 | 
			
		||||
| 
						 | 
				
			
			@ -139,7 +139,7 @@ pub struct SidebarPrimaryInfoRenderer {
 | 
			
		|||
    pub stats: Vec<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct PlaylistThumbnailRenderer {
 | 
			
		||||
    // the alternative field name is used by YTM playlists
 | 
			
		||||
| 
						 | 
				
			
			@ -147,14 +147,14 @@ pub struct PlaylistThumbnailRenderer {
 | 
			
		|||
    pub playlist_video_thumbnail_renderer: ThumbnailsWrap,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct OnResponseReceivedAction {
 | 
			
		||||
    pub append_continuation_items_action: AppendAction,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct AppendAction {
 | 
			
		||||
    #[serde_as(as = "VecLogError<_>")]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,33 +9,33 @@ use super::{
 | 
			
		|||
    ContentRenderer, ContentsRenderer, MusicContentsRenderer, MusicContinuation, MusicItem,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct PlaylistMusic {
 | 
			
		||||
    pub contents: Contents,
 | 
			
		||||
    pub header: Header,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Contents {
 | 
			
		||||
    pub single_column_browse_results_renderer: ContentsRenderer<Tab>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Tab {
 | 
			
		||||
    pub tab_renderer: ContentRenderer<SectionList>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct SectionList {
 | 
			
		||||
    /// Includes a continuation token for fetching recommendations
 | 
			
		||||
    pub section_list_renderer: MusicContentsRenderer<ItemSection>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ItemSection {
 | 
			
		||||
    #[serde(alias = "musicPlaylistShelfRenderer")]
 | 
			
		||||
| 
						 | 
				
			
			@ -43,7 +43,7 @@ pub struct ItemSection {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct MusicShelf {
 | 
			
		||||
    /// Playlist ID (only for playlists)
 | 
			
		||||
| 
						 | 
				
			
			@ -55,20 +55,20 @@ pub struct MusicShelf {
 | 
			
		|||
    pub continuations: Option<Vec<MusicContinuation>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct PlaylistMusicItem {
 | 
			
		||||
    pub music_responsive_list_item_renderer: MusicItem,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Header {
 | 
			
		||||
    pub music_detail_header_renderer: HeaderRenderer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct HeaderRenderer {
 | 
			
		||||
    #[serde_as(as = "crate::serializer::text::Text")]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,215 +0,0 @@
 | 
			
		|||
use serde::Deserialize;
 | 
			
		||||
use serde_with::json::JsonString;
 | 
			
		||||
use serde_with::{serde_as, VecSkipError};
 | 
			
		||||
 | 
			
		||||
use crate::serializer::ignore_any;
 | 
			
		||||
use crate::serializer::{
 | 
			
		||||
    text::{Text, TextComponent},
 | 
			
		||||
    MapResult, VecLogError,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::{ChannelBadge, ContentsRenderer, ContinuationEndpoint, Thumbnails, TimeOverlay};
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Search {
 | 
			
		||||
    #[serde_as(as = "Option<JsonString>")]
 | 
			
		||||
    pub estimated_results: Option<u64>,
 | 
			
		||||
    pub contents: Contents,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct SearchCont {
 | 
			
		||||
    #[serde_as(as = "Option<JsonString>")]
 | 
			
		||||
    pub estimated_results: Option<u64>,
 | 
			
		||||
    pub on_response_received_commands: Vec<SearchContCommand>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct SearchContCommand {
 | 
			
		||||
    pub append_continuation_items_action: SearchContAction,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct SearchContAction {
 | 
			
		||||
    pub continuation_items: Vec<SectionListItem>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Contents {
 | 
			
		||||
    pub two_column_search_results_renderer: TwoColumnSearchResultsRenderer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct TwoColumnSearchResultsRenderer {
 | 
			
		||||
    pub primary_contents: PrimaryContents,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct PrimaryContents {
 | 
			
		||||
    pub section_list_renderer: ContentsRenderer<SectionListItem>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub enum SectionListItem {
 | 
			
		||||
    #[serde(rename_all = "camelCase")]
 | 
			
		||||
    ItemSectionRenderer {
 | 
			
		||||
        #[serde_as(as = "VecLogError<_>")]
 | 
			
		||||
        contents: MapResult<Vec<SearchItem>>,
 | 
			
		||||
    },
 | 
			
		||||
    /// Continuation token to fetch more search results
 | 
			
		||||
    #[serde(rename_all = "camelCase")]
 | 
			
		||||
    ContinuationItemRenderer {
 | 
			
		||||
        continuation_endpoint: ContinuationEndpoint,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub enum SearchItem {
 | 
			
		||||
    /// Video in search results
 | 
			
		||||
    VideoRenderer(VideoRenderer),
 | 
			
		||||
    /// Playlist in search results
 | 
			
		||||
    PlaylistRenderer(PlaylistRenderer),
 | 
			
		||||
    /// Channel displayed in search results
 | 
			
		||||
    ChannelRenderer(ChannelRenderer),
 | 
			
		||||
 | 
			
		||||
    /// Corrected search query
 | 
			
		||||
    #[serde(rename_all = "camelCase")]
 | 
			
		||||
    ShowingResultsForRenderer {
 | 
			
		||||
        #[serde_as(as = "Text")]
 | 
			
		||||
        corrected_query: String,
 | 
			
		||||
    },
 | 
			
		||||
    /// No search result item (e.g. ad) or unimplemented item
 | 
			
		||||
    ///
 | 
			
		||||
    /// Unimplemented:
 | 
			
		||||
    /// - shelfRenderer (e.g. Latest from channel, For you)
 | 
			
		||||
    #[serde(other, deserialize_with = "ignore_any")]
 | 
			
		||||
    None,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Video displayed in search results
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct VideoRenderer {
 | 
			
		||||
    pub video_id: String,
 | 
			
		||||
    pub thumbnail: Thumbnails,
 | 
			
		||||
    #[serde_as(as = "Text")]
 | 
			
		||||
    pub title: String,
 | 
			
		||||
    #[serde(rename = "shortBylineText")]
 | 
			
		||||
    pub channel: TextComponent,
 | 
			
		||||
    pub channel_thumbnail_supported_renderers: ChannelThumbnailSupportedRenderers,
 | 
			
		||||
    #[serde_as(as = "Option<Text>")]
 | 
			
		||||
    pub published_time_text: Option<String>,
 | 
			
		||||
    #[serde_as(as = "Option<Text>")]
 | 
			
		||||
    pub length_text: Option<String>,
 | 
			
		||||
    /// Contains `No views` if the view count is zero
 | 
			
		||||
    #[serde_as(as = "Option<Text>")]
 | 
			
		||||
    pub view_count_text: Option<String>,
 | 
			
		||||
    /// Channel verification badge
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    #[serde_as(as = "VecSkipError<_>")]
 | 
			
		||||
    pub owner_badges: Vec<ChannelBadge>,
 | 
			
		||||
    /// Contains Short/Live tag
 | 
			
		||||
    #[serde_as(as = "VecSkipError<_>")]
 | 
			
		||||
    pub thumbnail_overlays: Vec<TimeOverlay>,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    #[serde_as(as = "VecSkipError<_>")]
 | 
			
		||||
    pub detailed_metadata_snippets: Vec<DetailedMetadataSnippet>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Playlist displayed in search results
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct PlaylistRenderer {
 | 
			
		||||
    pub playlist_id: String,
 | 
			
		||||
    #[serde_as(as = "Text")]
 | 
			
		||||
    pub title: String,
 | 
			
		||||
    /// The first item of this list contains the playlist thumbnail,
 | 
			
		||||
    /// subsequent items contain very small thumbnails of the next playlist videos
 | 
			
		||||
    pub thumbnails: Vec<Thumbnails>,
 | 
			
		||||
    #[serde_as(as = "JsonString")]
 | 
			
		||||
    pub video_count: u64,
 | 
			
		||||
    #[serde(rename = "shortBylineText")]
 | 
			
		||||
    pub channel: TextComponent,
 | 
			
		||||
    /// Channel verification badge
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    #[serde_as(as = "VecSkipError<_>")]
 | 
			
		||||
    pub owner_badges: Vec<ChannelBadge>,
 | 
			
		||||
    /// First 2 videos
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    #[serde_as(as = "VecSkipError<_>")]
 | 
			
		||||
    pub videos: Vec<ChildVideoRendererWrap>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Channel displayed in search results
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ChannelRenderer {
 | 
			
		||||
    pub channel_id: String,
 | 
			
		||||
    #[serde_as(as = "Text")]
 | 
			
		||||
    pub title: String,
 | 
			
		||||
    pub thumbnail: Thumbnails,
 | 
			
		||||
    /// Abbreviated channel description
 | 
			
		||||
    #[serde_as(as = "Text")]
 | 
			
		||||
    pub description_snippet: String,
 | 
			
		||||
    #[serde_as(as = "Text")]
 | 
			
		||||
    pub video_count_text: String,
 | 
			
		||||
    #[serde_as(as = "Option<Text>")]
 | 
			
		||||
    pub subscriber_count_text: Option<String>,
 | 
			
		||||
    /// Channel verification badge
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    #[serde_as(as = "VecSkipError<_>")]
 | 
			
		||||
    pub owner_badges: Vec<ChannelBadge>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ChannelThumbnailSupportedRenderers {
 | 
			
		||||
    pub channel_thumbnail_with_link_renderer: ChannelThumbnailWithLinkRenderer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ChannelThumbnailWithLinkRenderer {
 | 
			
		||||
    pub thumbnail: Thumbnails,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ChildVideoRendererWrap {
 | 
			
		||||
    pub child_video_renderer: ChildVideoRenderer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ChildVideoRenderer {
 | 
			
		||||
    pub video_id: String,
 | 
			
		||||
    #[serde_as(as = "Text")]
 | 
			
		||||
    pub title: String,
 | 
			
		||||
    #[serde_as(as = "Option<Text>")]
 | 
			
		||||
    pub length_text: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct DetailedMetadataSnippet {
 | 
			
		||||
    #[serde_as(as = "Text")]
 | 
			
		||||
    pub snippet_text: String,
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -4,10 +4,12 @@ use serde::Deserialize;
 | 
			
		|||
use serde_with::serde_as;
 | 
			
		||||
use serde_with::{DefaultOnError, VecSkipError};
 | 
			
		||||
 | 
			
		||||
use crate::serializer::text::TextComponents;
 | 
			
		||||
use crate::serializer::MapResult;
 | 
			
		||||
use crate::serializer::{
 | 
			
		||||
    ignore_any,
 | 
			
		||||
    text::{AccessibilityText, AttributedText, Text, TextComponents},
 | 
			
		||||
    MapResult, VecLogError,
 | 
			
		||||
    text::{AccessibilityText, AttributedText, Text},
 | 
			
		||||
    VecLogError,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::{
 | 
			
		||||
| 
						 | 
				
			
			@ -20,26 +22,26 @@ use super::{
 | 
			
		|||
 | 
			
		||||
/// Video details response
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct VideoDetails {
 | 
			
		||||
    /// Video metadata + recommended videos
 | 
			
		||||
    pub contents: Contents,
 | 
			
		||||
    /// Video ID
 | 
			
		||||
    pub current_video_endpoint: CurrentVideoEndpoint,
 | 
			
		||||
    /// Video chapters + comment section
 | 
			
		||||
    #[serde_as(as = "VecLogError<_>")]
 | 
			
		||||
    /// Video chapters + comment section
 | 
			
		||||
    pub engagement_panels: MapResult<Vec<EngagementPanel>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Video details main object, contains video metadata and recommended videos
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Contents {
 | 
			
		||||
    pub two_column_watch_next_results: TwoColumnWatchNextResults,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct TwoColumnWatchNextResults {
 | 
			
		||||
    /// Metadata about the video
 | 
			
		||||
| 
						 | 
				
			
			@ -51,7 +53,7 @@ pub struct TwoColumnWatchNextResults {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// Metadata about the video
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct VideoResultsWrap {
 | 
			
		||||
    pub results: VideoResults,
 | 
			
		||||
| 
						 | 
				
			
			@ -59,7 +61,7 @@ pub struct VideoResultsWrap {
 | 
			
		|||
 | 
			
		||||
/// Video metadata items
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct VideoResults {
 | 
			
		||||
    #[serde_as(as = "VecLogError<_>")]
 | 
			
		||||
| 
						 | 
				
			
			@ -68,7 +70,7 @@ pub struct VideoResults {
 | 
			
		|||
 | 
			
		||||
/// Video metadata item
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub enum VideoResultsItem {
 | 
			
		||||
    #[serde(rename_all = "camelCase")]
 | 
			
		||||
| 
						 | 
				
			
			@ -102,14 +104,14 @@ pub enum VideoResultsItem {
 | 
			
		|||
    None,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ViewCount {
 | 
			
		||||
    pub video_view_count_renderer: ViewCountRenderer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ViewCountRenderer {
 | 
			
		||||
    /// View count (`232,975,196 views`)
 | 
			
		||||
| 
						 | 
				
			
			@ -120,7 +122,7 @@ pub struct ViewCountRenderer {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// Like/Dislike buttons
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct VideoActions {
 | 
			
		||||
    pub menu_renderer: VideoActionsMenu,
 | 
			
		||||
| 
						 | 
				
			
			@ -128,7 +130,7 @@ pub struct VideoActions {
 | 
			
		|||
 | 
			
		||||
/// Like/Dislike buttons
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct VideoActionsMenu {
 | 
			
		||||
    #[serde_as(as = "VecSkipError<_>")]
 | 
			
		||||
| 
						 | 
				
			
			@ -141,7 +143,7 @@ pub struct VideoActionsMenu {
 | 
			
		|||
///
 | 
			
		||||
/// See: https://github.com/TeamNewPipe/NewPipeExtractor/pull/926
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub enum TopLevelButton {
 | 
			
		||||
    ToggleButtonRenderer(ToggleButton),
 | 
			
		||||
| 
						 | 
				
			
			@ -152,7 +154,7 @@ pub enum TopLevelButton {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// Like/Dislike button
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ToggleButtonWrap {
 | 
			
		||||
    pub toggle_button_renderer: ToggleButton,
 | 
			
		||||
| 
						 | 
				
			
			@ -160,7 +162,7 @@ pub struct ToggleButtonWrap {
 | 
			
		|||
 | 
			
		||||
/// Like/Dislike button
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ToggleButton {
 | 
			
		||||
    /// Icon type: `LIKE` / `DISLIKE`
 | 
			
		||||
| 
						 | 
				
			
			@ -174,7 +176,7 @@ pub struct ToggleButton {
 | 
			
		|||
 | 
			
		||||
/// Shows additional video metadata. Its only known use is for
 | 
			
		||||
/// the Creative Commonse License.
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct MetadataRowContainer {
 | 
			
		||||
    pub metadata_row_container_renderer: MetadataRowContainerRenderer,
 | 
			
		||||
| 
						 | 
				
			
			@ -182,14 +184,14 @@ pub struct MetadataRowContainer {
 | 
			
		|||
 | 
			
		||||
/// Shows additional video metadata. Its only known use is for
 | 
			
		||||
/// the Creative Commonse License.
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct MetadataRowContainerRenderer {
 | 
			
		||||
    pub rows: Vec<MetadataRow>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Additional video metadata item (Creative Commons License)
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct MetadataRow {
 | 
			
		||||
    pub metadata_row_renderer: MetadataRowRenderer,
 | 
			
		||||
| 
						 | 
				
			
			@ -197,7 +199,7 @@ pub struct MetadataRow {
 | 
			
		|||
 | 
			
		||||
/// Additional video metadata item (Creative Commons License)
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct MetadataRowRenderer {
 | 
			
		||||
    // `License`
 | 
			
		||||
| 
						 | 
				
			
			@ -212,13 +214,13 @@ pub struct MetadataRowRenderer {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// Contains current video ID
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct CurrentVideoEndpoint {
 | 
			
		||||
    pub watch_endpoint: CurrentVideoWatchEndpoint,
 | 
			
		||||
}
 | 
			
		||||
/// Contains current video ID
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct CurrentVideoWatchEndpoint {
 | 
			
		||||
    pub video_id: String,
 | 
			
		||||
| 
						 | 
				
			
			@ -229,7 +231,7 @@ pub struct CurrentVideoWatchEndpoint {
 | 
			
		|||
/// 1. CommentsEntryPointHeaderRenderer: contains number of comments
 | 
			
		||||
/// 2. ContinuationItemRenderer: contains continuation token
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Default, Debug, Deserialize)]
 | 
			
		||||
#[derive(Default, Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "kebab-case", tag = "sectionIdentifier")]
 | 
			
		||||
pub enum ItemSection {
 | 
			
		||||
    CommentsEntryPoint {
 | 
			
		||||
| 
						 | 
				
			
			@ -245,7 +247,7 @@ pub enum ItemSection {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// Item section containing comment count
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ItemSectionCommentCount {
 | 
			
		||||
    pub comments_entry_point_header_renderer: CommentsEntryPointHeaderRenderer,
 | 
			
		||||
| 
						 | 
				
			
			@ -253,7 +255,7 @@ pub struct ItemSectionCommentCount {
 | 
			
		|||
 | 
			
		||||
/// Renderer of item section containing comment count
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct CommentsEntryPointHeaderRenderer {
 | 
			
		||||
    #[serde_as(as = "Text")]
 | 
			
		||||
| 
						 | 
				
			
			@ -261,14 +263,14 @@ pub struct CommentsEntryPointHeaderRenderer {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// Item section containing comments ctoken
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ItemSectionComments {
 | 
			
		||||
    pub continuation_item_renderer: ContinuationItemRenderer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Video recommendations
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct RecommendationResultsWrap {
 | 
			
		||||
    pub secondary_results: RecommendationResults,
 | 
			
		||||
| 
						 | 
				
			
			@ -276,7 +278,7 @@ pub struct RecommendationResultsWrap {
 | 
			
		|||
 | 
			
		||||
/// Video recommendations
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct RecommendationResults {
 | 
			
		||||
    /// Can be `None` for age-restricted videos
 | 
			
		||||
| 
						 | 
				
			
			@ -286,7 +288,7 @@ pub struct RecommendationResults {
 | 
			
		|||
 | 
			
		||||
/// The engagement panels are displayed below the video and contain chapter markers
 | 
			
		||||
/// and the comment section.
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct EngagementPanel {
 | 
			
		||||
    pub engagement_panel_section_list_renderer: EngagementPanelRenderer,
 | 
			
		||||
| 
						 | 
				
			
			@ -294,7 +296,7 @@ pub struct EngagementPanel {
 | 
			
		|||
 | 
			
		||||
/// The engagement panels are displayed below the video and contain chapter markers
 | 
			
		||||
/// and the comment section.
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "kebab-case", tag = "targetId")]
 | 
			
		||||
pub enum EngagementPanelRenderer {
 | 
			
		||||
    /// Chapter markers
 | 
			
		||||
| 
						 | 
				
			
			@ -313,7 +315,7 @@ pub enum EngagementPanelRenderer {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// Chapter markers
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ChapterMarkersContent {
 | 
			
		||||
    pub macro_markers_list_renderer: MacroMarkersListRenderer,
 | 
			
		||||
| 
						 | 
				
			
			@ -321,7 +323,7 @@ pub struct ChapterMarkersContent {
 | 
			
		|||
 | 
			
		||||
/// Chapter markers
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct MacroMarkersListRenderer {
 | 
			
		||||
    #[serde_as(as = "VecLogError<_>")]
 | 
			
		||||
| 
						 | 
				
			
			@ -329,7 +331,7 @@ pub struct MacroMarkersListRenderer {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// Chapter marker
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct MacroMarkersListItem {
 | 
			
		||||
    pub macro_markers_list_item_renderer: MacroMarkersListItemRenderer,
 | 
			
		||||
| 
						 | 
				
			
			@ -337,7 +339,7 @@ pub struct MacroMarkersListItem {
 | 
			
		|||
 | 
			
		||||
/// Chapter marker
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct MacroMarkersListItemRenderer {
 | 
			
		||||
    /// Contains chapter start time in seconds
 | 
			
		||||
| 
						 | 
				
			
			@ -350,13 +352,13 @@ pub struct MacroMarkersListItemRenderer {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// Contains chapter start time in seconds
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct MacroMarkersListItemOnTap {
 | 
			
		||||
    pub watch_endpoint: MacroMarkersListItemWatchEndpoint,
 | 
			
		||||
}
 | 
			
		||||
/// Contains chapter start time in seconds
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct MacroMarkersListItemWatchEndpoint {
 | 
			
		||||
    /// Chapter start time in seconds
 | 
			
		||||
| 
						 | 
				
			
			@ -365,7 +367,7 @@ pub struct MacroMarkersListItemWatchEndpoint {
 | 
			
		|||
 | 
			
		||||
/// Comment section header
 | 
			
		||||
/// (contains continuation tokens for fetching top/latest comments)
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct CommentItemSectionHeader {
 | 
			
		||||
    pub engagement_panel_title_header_renderer: CommentItemSectionHeaderRenderer,
 | 
			
		||||
| 
						 | 
				
			
			@ -374,7 +376,7 @@ pub struct CommentItemSectionHeader {
 | 
			
		|||
/// Comment section header
 | 
			
		||||
/// (contains continuation tokens for fetching top/latest comments)
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct CommentItemSectionHeaderRenderer {
 | 
			
		||||
    /// Approximate comment count (e.g. `81`, `2.2K`, `705K`)
 | 
			
		||||
| 
						 | 
				
			
			@ -388,7 +390,7 @@ pub struct CommentItemSectionHeaderRenderer {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// Comment section menu
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct CommentItemSectionHeaderMenu {
 | 
			
		||||
    pub sort_filter_sub_menu_renderer: CommentItemSectionHeaderMenuRenderer,
 | 
			
		||||
| 
						 | 
				
			
			@ -399,14 +401,14 @@ pub struct CommentItemSectionHeaderMenu {
 | 
			
		|||
/// Items:
 | 
			
		||||
/// - Top comments
 | 
			
		||||
/// - Latest comments
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct CommentItemSectionHeaderMenuRenderer {
 | 
			
		||||
    pub sub_menu_items: Vec<CommentItemSectionHeaderMenuItem>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Comment section menu item
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct CommentItemSectionHeaderMenuItem {
 | 
			
		||||
    /// Continuation token for fetching comments
 | 
			
		||||
| 
						 | 
				
			
			@ -418,14 +420,14 @@ pub struct CommentItemSectionHeaderMenuItem {
 | 
			
		|||
*/
 | 
			
		||||
 | 
			
		||||
/// Video recommendations continuation response
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct VideoRecommendations {
 | 
			
		||||
    pub on_response_received_endpoints: Vec<RecommendationsContItem>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Video recommendations continuation
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct RecommendationsContItem {
 | 
			
		||||
    pub append_continuation_items_action: AppendRecommendations,
 | 
			
		||||
| 
						 | 
				
			
			@ -433,7 +435,7 @@ pub struct RecommendationsContItem {
 | 
			
		|||
 | 
			
		||||
/// Video recommendations continuation
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct AppendRecommendations {
 | 
			
		||||
    #[serde_as(as = "VecLogError<_>")]
 | 
			
		||||
| 
						 | 
				
			
			@ -446,7 +448,7 @@ pub struct AppendRecommendations {
 | 
			
		|||
 | 
			
		||||
/// Video comments continuation response
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct VideoComments {
 | 
			
		||||
    /// - Initial response: 2*reloadContinuationItemsCommand
 | 
			
		||||
| 
						 | 
				
			
			@ -464,7 +466,7 @@ pub struct VideoComments {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// Video comments continuation
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct CommentsContItem {
 | 
			
		||||
    #[serde(alias = "reloadContinuationItemsCommand")]
 | 
			
		||||
| 
						 | 
				
			
			@ -473,7 +475,7 @@ pub struct CommentsContItem {
 | 
			
		|||
 | 
			
		||||
/// Video comments continuation action
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct AppendComments {
 | 
			
		||||
    #[serde_as(as = "VecLogError<_>")]
 | 
			
		||||
| 
						 | 
				
			
			@ -481,7 +483,7 @@ pub struct AppendComments {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub enum CommentListItem {
 | 
			
		||||
    /// Top-level comment
 | 
			
		||||
| 
						 | 
				
			
			@ -511,14 +513,14 @@ pub enum CommentListItem {
 | 
			
		|||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Comment {
 | 
			
		||||
    pub comment_renderer: CommentRenderer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct CommentRenderer {
 | 
			
		||||
    /// Author name
 | 
			
		||||
| 
						 | 
				
			
			@ -544,24 +546,24 @@ pub struct CommentRenderer {
 | 
			
		|||
    // pub vote_count: Option<String>,
 | 
			
		||||
    pub author_comment_badge: Option<AuthorCommentBadge>,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    pub reply_count: u64,
 | 
			
		||||
    pub reply_count: u32,
 | 
			
		||||
    /// Buttons for comment interaction (Like/Dislike/Reply)
 | 
			
		||||
    pub action_buttons: CommentActionButtons,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct AuthorEndpoint {
 | 
			
		||||
    pub browse_endpoint: BrowseEndpoint,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct BrowseEndpoint {
 | 
			
		||||
    pub browse_id: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
 | 
			
		||||
#[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
 | 
			
		||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
 | 
			
		||||
pub enum CommentPriority {
 | 
			
		||||
    /// Default rendering priority
 | 
			
		||||
| 
						 | 
				
			
			@ -573,7 +575,7 @@ pub enum CommentPriority {
 | 
			
		|||
 | 
			
		||||
/// Does not contain replies directly but a continuation token
 | 
			
		||||
/// for fetching them.
 | 
			
		||||
#[derive(Default, Debug, Deserialize)]
 | 
			
		||||
#[derive(Default, Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct Replies {
 | 
			
		||||
    pub comment_replies_renderer: RepliesRenderer,
 | 
			
		||||
| 
						 | 
				
			
			@ -582,7 +584,7 @@ pub struct Replies {
 | 
			
		|||
/// Does not contain replies directly but a continuation token
 | 
			
		||||
/// for fetching them.
 | 
			
		||||
#[serde_as]
 | 
			
		||||
#[derive(Default, Debug, Deserialize)]
 | 
			
		||||
#[derive(Default, Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct RepliesRenderer {
 | 
			
		||||
    #[serde_as(as = "VecSkipError<_>")]
 | 
			
		||||
| 
						 | 
				
			
			@ -591,7 +593,7 @@ pub struct RepliesRenderer {
 | 
			
		|||
 | 
			
		||||
/// These are the buttons for comment interaction (Like/Dislike/Reply).
 | 
			
		||||
/// Contains the CreatorHeart.
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct CommentActionButtons {
 | 
			
		||||
    pub comment_action_buttons_renderer: CommentActionButtonsRenderer,
 | 
			
		||||
| 
						 | 
				
			
			@ -599,7 +601,7 @@ pub struct CommentActionButtons {
 | 
			
		|||
 | 
			
		||||
/// These are the buttons for comment interaction (Like/Dislike/Reply).
 | 
			
		||||
/// Contains the CreatorHeart.
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct CommentActionButtonsRenderer {
 | 
			
		||||
    pub like_button: ToggleButtonWrap,
 | 
			
		||||
| 
						 | 
				
			
			@ -607,27 +609,27 @@ pub struct CommentActionButtonsRenderer {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// Video creators can endorse comments by marking them with a ❤️.
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct CreatorHeart {
 | 
			
		||||
    pub creator_heart_renderer: CreatorHeartRenderer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Video creators can endorse comments by marking them with a ❤️.
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct CreatorHeartRenderer {
 | 
			
		||||
    pub is_hearted: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct AuthorCommentBadge {
 | 
			
		||||
    pub author_comment_badge_renderer: AuthorCommentBadgeRenderer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// YouTube channel badge (verified) of the comment author
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct AuthorCommentBadgeRenderer {
 | 
			
		||||
    /// Verified: `CHECK`
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,265 +0,0 @@
 | 
			
		|||
use serde::Serialize;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    deobfuscate::Deobfuscator,
 | 
			
		||||
    error::{Error, ExtractionError},
 | 
			
		||||
    model::{
 | 
			
		||||
        ChannelId, ChannelTag, Language, Paginator, SearchChannel, SearchItem, SearchPlaylist,
 | 
			
		||||
        SearchPlaylistVideo, SearchResult, SearchVideo,
 | 
			
		||||
    },
 | 
			
		||||
    timeago,
 | 
			
		||||
    util::{self, TryRemove},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::{
 | 
			
		||||
    response::{self, IsLive, IsShort},
 | 
			
		||||
    ClientType, MapResponse, MapResult, QContinuation, RustyPipeQuery, YTContext,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
struct QSearch<'a> {
 | 
			
		||||
    context: YTContext,
 | 
			
		||||
    query: &'a str,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    params: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RustyPipeQuery {
 | 
			
		||||
    pub async fn search(self, query: &str) -> Result<SearchResult, Error> {
 | 
			
		||||
        let context = self.get_context(ClientType::Desktop, true).await;
 | 
			
		||||
        let request_body = QSearch {
 | 
			
		||||
            context,
 | 
			
		||||
            query,
 | 
			
		||||
            params: None,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        self.execute_request::<response::Search, _, _>(
 | 
			
		||||
            ClientType::Desktop,
 | 
			
		||||
            "search",
 | 
			
		||||
            query,
 | 
			
		||||
            "search",
 | 
			
		||||
            &request_body,
 | 
			
		||||
        )
 | 
			
		||||
        .await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn search_continuation(self, ctoken: &str) -> Result<Paginator<SearchItem>, Error> {
 | 
			
		||||
        let context = self.get_context(ClientType::Desktop, true).await;
 | 
			
		||||
        let request_body = QContinuation {
 | 
			
		||||
            context,
 | 
			
		||||
            continuation: ctoken,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        self.execute_request::<response::SearchCont, _, _>(
 | 
			
		||||
            ClientType::Desktop,
 | 
			
		||||
            "search",
 | 
			
		||||
            ctoken,
 | 
			
		||||
            "search",
 | 
			
		||||
            &request_body,
 | 
			
		||||
        )
 | 
			
		||||
        .await
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl MapResponse<SearchResult> for response::Search {
 | 
			
		||||
    fn map_response(
 | 
			
		||||
        self,
 | 
			
		||||
        _id: &str,
 | 
			
		||||
        lang: Language,
 | 
			
		||||
        _deobf: Option<&Deobfuscator>,
 | 
			
		||||
    ) -> Result<MapResult<SearchResult>, ExtractionError> {
 | 
			
		||||
        let section_list_items = self
 | 
			
		||||
            .contents
 | 
			
		||||
            .two_column_search_results_renderer
 | 
			
		||||
            .primary_contents
 | 
			
		||||
            .section_list_renderer
 | 
			
		||||
            .contents;
 | 
			
		||||
 | 
			
		||||
        let (items, ctoken) = map_section_list_items(section_list_items)?;
 | 
			
		||||
 | 
			
		||||
        let mut warnings = items.warnings;
 | 
			
		||||
        let (mut mapped, corrected_query) = map_search_items(items.c, lang);
 | 
			
		||||
        warnings.append(&mut mapped.warnings);
 | 
			
		||||
 | 
			
		||||
        Ok(MapResult {
 | 
			
		||||
            c: SearchResult {
 | 
			
		||||
                items: Paginator::new(self.estimated_results, mapped.c, ctoken),
 | 
			
		||||
                corrected_query,
 | 
			
		||||
            },
 | 
			
		||||
            warnings,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl MapResponse<Paginator<SearchItem>> for response::SearchCont {
 | 
			
		||||
    fn map_response(
 | 
			
		||||
        self,
 | 
			
		||||
        _id: &str,
 | 
			
		||||
        lang: Language,
 | 
			
		||||
        _deobf: Option<&Deobfuscator>,
 | 
			
		||||
    ) -> Result<MapResult<Paginator<SearchItem>>, ExtractionError> {
 | 
			
		||||
        let mut commands = self.on_response_received_commands;
 | 
			
		||||
        let cont_command = some_or_bail!(
 | 
			
		||||
            commands.try_swap_remove(0),
 | 
			
		||||
            Err(ExtractionError::InvalidData(
 | 
			
		||||
                "no item section renderer".into()
 | 
			
		||||
            ))
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let (items, ctoken) = map_section_list_items(
 | 
			
		||||
            cont_command
 | 
			
		||||
                .append_continuation_items_action
 | 
			
		||||
                .continuation_items,
 | 
			
		||||
        )?;
 | 
			
		||||
 | 
			
		||||
        let mut warnings = items.warnings;
 | 
			
		||||
        let (mut mapped, _) = map_search_items(items.c, lang);
 | 
			
		||||
        warnings.append(&mut mapped.warnings);
 | 
			
		||||
 | 
			
		||||
        Ok(MapResult {
 | 
			
		||||
            c: Paginator::new(self.estimated_results, mapped.c, ctoken),
 | 
			
		||||
            warnings,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn map_section_list_items(
 | 
			
		||||
    section_list_items: Vec<response::search::SectionListItem>,
 | 
			
		||||
) -> Result<(MapResult<Vec<response::search::SearchItem>>, Option<String>), ExtractionError> {
 | 
			
		||||
    let mut items = None;
 | 
			
		||||
    let mut ctoken = None;
 | 
			
		||||
    section_list_items.into_iter().for_each(|item| match item {
 | 
			
		||||
        response::search::SectionListItem::ItemSectionRenderer { contents } => {
 | 
			
		||||
            items = Some(contents);
 | 
			
		||||
        }
 | 
			
		||||
        response::search::SectionListItem::ContinuationItemRenderer {
 | 
			
		||||
            continuation_endpoint,
 | 
			
		||||
        } => {
 | 
			
		||||
            ctoken = Some(continuation_endpoint.continuation_command.token);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let items = some_or_bail!(
 | 
			
		||||
        items,
 | 
			
		||||
        Err(ExtractionError::InvalidData(
 | 
			
		||||
            "no item section renderer".into()
 | 
			
		||||
        ))
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    Ok((items, ctoken))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn map_search_items(
 | 
			
		||||
    items: Vec<response::search::SearchItem>,
 | 
			
		||||
    lang: Language,
 | 
			
		||||
) -> (MapResult<Vec<SearchItem>>, Option<String>) {
 | 
			
		||||
    let mut warnings = Vec::new();
 | 
			
		||||
 | 
			
		||||
    let mut c_query = None;
 | 
			
		||||
    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_or_warn(&txt, &mut warnings)),
 | 
			
		||||
                        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(),
 | 
			
		||||
                    })),
 | 
			
		||||
                    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::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)),
 | 
			
		||||
                    short_description: channel.description_snippet,
 | 
			
		||||
                }))
 | 
			
		||||
            }
 | 
			
		||||
            response::search::SearchItem::ShowingResultsForRenderer { corrected_query } => {
 | 
			
		||||
                c_query = Some(corrected_query);
 | 
			
		||||
                None
 | 
			
		||||
            }
 | 
			
		||||
            response::search::SearchItem::None => None,
 | 
			
		||||
        })
 | 
			
		||||
        .collect();
 | 
			
		||||
    (
 | 
			
		||||
        MapResult {
 | 
			
		||||
            c: mapped_items,
 | 
			
		||||
            warnings,
 | 
			
		||||
        },
 | 
			
		||||
        c_query,
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use crate::client::RustyPipe;
 | 
			
		||||
 | 
			
		||||
    #[tokio::test]
 | 
			
		||||
    async fn t1() {
 | 
			
		||||
        let rp = RustyPipe::builder().strict().build();
 | 
			
		||||
        let result = rp.query().search("doobydoobap").await.unwrap();
 | 
			
		||||
        dbg!(&result);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -19,10 +19,10 @@ use super::{
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize)]
 | 
			
		||||
struct QVideo<'a> {
 | 
			
		||||
struct QVideo {
 | 
			
		||||
    context: YTContext,
 | 
			
		||||
    /// YouTube video ID
 | 
			
		||||
    video_id: &'a str,
 | 
			
		||||
    video_id: String,
 | 
			
		||||
    /// Set to true to allow extraction of streams with sensitive content
 | 
			
		||||
    content_check_ok: bool,
 | 
			
		||||
    /// Probably refers to allowing sensitive content, too
 | 
			
		||||
| 
						 | 
				
			
			@ -34,7 +34,7 @@ impl RustyPipeQuery {
 | 
			
		|||
        let context = self.get_context(ClientType::Desktop, true).await;
 | 
			
		||||
        let request_body = QVideo {
 | 
			
		||||
            context,
 | 
			
		||||
            video_id,
 | 
			
		||||
            video_id: video_id.to_owned(),
 | 
			
		||||
            content_check_ok: true,
 | 
			
		||||
            racy_check_ok: true,
 | 
			
		||||
        };
 | 
			
		||||
| 
						 | 
				
			
			@ -56,7 +56,7 @@ impl RustyPipeQuery {
 | 
			
		|||
        let context = self.get_context(ClientType::Desktop, true).await;
 | 
			
		||||
        let request_body = QContinuation {
 | 
			
		||||
            context,
 | 
			
		||||
            continuation: ctoken,
 | 
			
		||||
            continuation: ctoken.to_owned(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        self.execute_request::<response::VideoRecommendations, _, _>(
 | 
			
		||||
| 
						 | 
				
			
			@ -73,7 +73,7 @@ impl RustyPipeQuery {
 | 
			
		|||
        let context = self.get_context(ClientType::Desktop, true).await;
 | 
			
		||||
        let request_body = QContinuation {
 | 
			
		||||
            context,
 | 
			
		||||
            continuation: ctoken,
 | 
			
		||||
            continuation: ctoken.to_owned(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        self.execute_request::<response::VideoComments, _, _>(
 | 
			
		||||
| 
						 | 
				
			
			@ -181,7 +181,7 @@ impl MapResponse<VideoDetails> for response::VideoDetails {
 | 
			
		|||
            };
 | 
			
		||||
 | 
			
		||||
        let comment_count = comment_count_section.and_then(|s| {
 | 
			
		||||
            util::parse_large_numstr::<u64>(
 | 
			
		||||
            util::parse_large_numstr::<u32>(
 | 
			
		||||
                &s.comments_entry_point_header_renderer.comment_count,
 | 
			
		||||
                lang,
 | 
			
		||||
            )
 | 
			
		||||
| 
						 | 
				
			
			@ -411,7 +411,7 @@ impl MapResponse<Paginator<Comment>> for response::VideoComments {
 | 
			
		|||
                        count_text,
 | 
			
		||||
                    } => {
 | 
			
		||||
                        comment_count = count_text.and_then(|txt| {
 | 
			
		||||
                            util::parse_numeric_or_warn::<u64>(&txt, &mut warnings)
 | 
			
		||||
                            util::parse_numeric_or_warn::<u32>(&txt, &mut warnings)
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -56,12 +56,22 @@ fn parse_cr_header(cr_header: &str) -> Result<(u64, u64)> {
 | 
			
		|||
    );
 | 
			
		||||
 | 
			
		||||
    Ok((
 | 
			
		||||
        captures.get(2).unwrap().as_str().parse().map_err(|_| {
 | 
			
		||||
            DownloadError::Progressive("could not parse range header number".into())
 | 
			
		||||
        })?,
 | 
			
		||||
        captures.get(3).unwrap().as_str().parse().map_err(|_| {
 | 
			
		||||
            DownloadError::Progressive("could not parse range header number".into())
 | 
			
		||||
        })?,
 | 
			
		||||
        captures
 | 
			
		||||
            .get(2)
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .as_str()
 | 
			
		||||
            .parse()
 | 
			
		||||
            .or(Err(DownloadError::Progressive(
 | 
			
		||||
                "could not parse range header number".into(),
 | 
			
		||||
            )))?,
 | 
			
		||||
        captures
 | 
			
		||||
            .get(3)
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .as_str()
 | 
			
		||||
            .parse()
 | 
			
		||||
            .or(Err(DownloadError::Progressive(
 | 
			
		||||
                "could not parse range header number".into(),
 | 
			
		||||
            )))?,
 | 
			
		||||
    ))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -86,7 +96,7 @@ async fn download_single_file<P: Into<PathBuf>>(
 | 
			
		|||
 | 
			
		||||
    // If the url is from googlevideo, extract file size from clen parameter
 | 
			
		||||
    let (url_base, url_params) =
 | 
			
		||||
        util::url_to_params(url).map_err(|e| DownloadError::Other(e.to_string().into()))?;
 | 
			
		||||
        util::url_to_params(url).or_else(|e| Err(DownloadError::Other(e.to_string().into())))?;
 | 
			
		||||
    let is_gvideo = url_base.ends_with(".googlevideo.com/videoplayback");
 | 
			
		||||
    if is_gvideo {
 | 
			
		||||
        size = url_params.get("clen").and_then(|s| s.parse::<u64>().ok());
 | 
			
		||||
| 
						 | 
				
			
			@ -110,9 +120,9 @@ async fn download_single_file<P: Into<PathBuf>>(
 | 
			
		|||
            ))
 | 
			
		||||
        )
 | 
			
		||||
        .to_str()
 | 
			
		||||
        .map_err(|_| {
 | 
			
		||||
            DownloadError::Progressive("could not convert Content-Range header to string".into())
 | 
			
		||||
        })?;
 | 
			
		||||
        .or(Err(DownloadError::Progressive(
 | 
			
		||||
            "could not convert Content-Range header to string".into(),
 | 
			
		||||
        )))?;
 | 
			
		||||
 | 
			
		||||
        let (_, original_size) = parse_cr_header(cr_header)?;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -197,9 +207,9 @@ async fn download_chunks_by_header(
 | 
			
		|||
            ))
 | 
			
		||||
        )
 | 
			
		||||
        .to_str()
 | 
			
		||||
        .map_err(|_| {
 | 
			
		||||
            DownloadError::Progressive("could not convert Content-Range header to string".into())
 | 
			
		||||
        })?;
 | 
			
		||||
        .or(Err(DownloadError::Progressive(
 | 
			
		||||
            "could not convert Content-Range header to string".into(),
 | 
			
		||||
        )))?;
 | 
			
		||||
 | 
			
		||||
        let (parsed_offset, parsed_size) = parse_cr_header(cr_header)?;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -81,8 +81,8 @@ pub enum ExtractionError {
 | 
			
		|||
    InvalidData(Cow<'static, str>),
 | 
			
		||||
    #[error("got wrong result from YT: {0}")]
 | 
			
		||||
    WrongResult(String),
 | 
			
		||||
    #[error("Warnings during deserialization/mapping in strict mode")]
 | 
			
		||||
    DeserializationWarnings,
 | 
			
		||||
    #[error("Warnings during deserialization/mapping")]
 | 
			
		||||
    Warnings,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Internal error
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										127
									
								
								src/model/mod.rs
									
										
									
									
									
								
							
							
						
						
									
										127
									
								
								src/model/mod.rs
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -393,7 +393,7 @@ pub struct Playlist {
 | 
			
		|||
    /// Playlist videos
 | 
			
		||||
    pub videos: Paginator<PlaylistVideo>,
 | 
			
		||||
    /// Number of videos in the playlist
 | 
			
		||||
    pub video_count: u64,
 | 
			
		||||
    pub video_count: u32,
 | 
			
		||||
    /// Playlist thumbnail
 | 
			
		||||
    pub thumbnail: Vec<Thumbnail>,
 | 
			
		||||
    /// Playlist description in plaintext format
 | 
			
		||||
| 
						 | 
				
			
			@ -453,11 +453,11 @@ pub struct VideoDetails {
 | 
			
		|||
    pub view_count: u64,
 | 
			
		||||
    /// Number of likes
 | 
			
		||||
    ///
 | 
			
		||||
    /// [`None`] if the like count was hidden by the creator.
 | 
			
		||||
    /// `None` if the like count was hidden by the creator.
 | 
			
		||||
    pub like_count: Option<u32>,
 | 
			
		||||
    /// Video publishing date. Start date in case of a livestream.
 | 
			
		||||
    ///
 | 
			
		||||
    /// [`None`] if the date could not be parsed.
 | 
			
		||||
    /// `None` if the date could not be parsed.
 | 
			
		||||
    pub publish_date: Option<DateTime<Local>>,
 | 
			
		||||
    /// Textual video publishing date (e.g. `Aug 2, 2013`, depends on language)
 | 
			
		||||
    pub publish_date_txt: String,
 | 
			
		||||
| 
						 | 
				
			
			@ -516,7 +516,7 @@ pub struct RecommendedVideo {
 | 
			
		|||
    pub title: String,
 | 
			
		||||
    /// Video length in seconds.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Is [`None`] for livestreams.
 | 
			
		||||
    /// Is `None` for livestreams.
 | 
			
		||||
    pub length: Option<u32>,
 | 
			
		||||
    /// Video thumbnail
 | 
			
		||||
    pub thumbnail: Vec<Thumbnail>,
 | 
			
		||||
| 
						 | 
				
			
			@ -524,15 +524,15 @@ pub struct RecommendedVideo {
 | 
			
		|||
    pub channel: ChannelTag,
 | 
			
		||||
    /// Video publishing date.
 | 
			
		||||
    ///
 | 
			
		||||
    /// [`None`] if the date could not be parsed.
 | 
			
		||||
    /// `None` if the date could not be parsed.
 | 
			
		||||
    pub publish_date: Option<DateTime<Local>>,
 | 
			
		||||
    /// Textual video publish date (e.g. `11 months ago`, depends on language)
 | 
			
		||||
    ///
 | 
			
		||||
    /// Is [`None`] for livestreams.
 | 
			
		||||
    /// Is `None` for livestreams.
 | 
			
		||||
    pub publish_date_txt: Option<String>,
 | 
			
		||||
    /// View count
 | 
			
		||||
    ///
 | 
			
		||||
    /// [`None`] if it could not be extracted.
 | 
			
		||||
    /// `None` if it could not be extracted.
 | 
			
		||||
    pub view_count: Option<u64>,
 | 
			
		||||
    /// Is the video an active livestream?
 | 
			
		||||
    pub is_live: bool,
 | 
			
		||||
| 
						 | 
				
			
			@ -554,7 +554,7 @@ pub struct ChannelTag {
 | 
			
		|||
    pub verification: Verification,
 | 
			
		||||
    /// Approximate number of subscribers
 | 
			
		||||
    ///
 | 
			
		||||
    /// [`None`] if hidden by the owner or not present.
 | 
			
		||||
    /// `None` if hidden by the owner or not present.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Info: This is only present in the `VideoDetails` response
 | 
			
		||||
    pub subscriber_count: Option<u64>,
 | 
			
		||||
| 
						 | 
				
			
			@ -600,14 +600,14 @@ pub struct Comment {
 | 
			
		|||
    pub author: Option<ChannelTag>,
 | 
			
		||||
    /// Comment publishing date.
 | 
			
		||||
    ///
 | 
			
		||||
    /// [`None`] if the date could not be parsed.
 | 
			
		||||
    /// `None` if the date could not be parsed.
 | 
			
		||||
    pub publish_date: Option<DateTime<Local>>,
 | 
			
		||||
    /// Textual comment publish date (e.g. `14 hours ago`), depends on language setting
 | 
			
		||||
    pub publish_date_txt: String,
 | 
			
		||||
    /// Number of comment likes
 | 
			
		||||
    pub like_count: Option<u32>,
 | 
			
		||||
    /// Number of replies
 | 
			
		||||
    pub reply_count: u64,
 | 
			
		||||
    pub reply_count: u32,
 | 
			
		||||
    /// Paginator to fetch comment replies
 | 
			
		||||
    pub replies: Paginator<Comment>,
 | 
			
		||||
    /// Is the comment from the channel owner?
 | 
			
		||||
| 
						 | 
				
			
			@ -635,7 +635,7 @@ pub struct Channel<T> {
 | 
			
		|||
    pub name: String,
 | 
			
		||||
    /// Channel subscriber count
 | 
			
		||||
    ///
 | 
			
		||||
    /// [`None`] if the subscriber count was hidden by the owner
 | 
			
		||||
    /// `None` if the subscriber count was hidden by the owner
 | 
			
		||||
    /// or could not be parsed.
 | 
			
		||||
    pub subscriber_count: Option<u64>,
 | 
			
		||||
    /// Channel avatar / profile picture
 | 
			
		||||
| 
						 | 
				
			
			@ -667,22 +667,22 @@ pub struct ChannelVideo {
 | 
			
		|||
    pub title: String,
 | 
			
		||||
    /// Video length in seconds.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Is [`None`] for livestreams.
 | 
			
		||||
    /// Is `None` for livestreams.
 | 
			
		||||
    pub length: Option<u32>,
 | 
			
		||||
    /// Video thumbnail
 | 
			
		||||
    pub thumbnail: Vec<Thumbnail>,
 | 
			
		||||
    /// Video publishing date.
 | 
			
		||||
    ///
 | 
			
		||||
    /// [`None`] if the date could not be parsed.
 | 
			
		||||
    /// `None` if the date could not be parsed.
 | 
			
		||||
    /// May be in the future for upcoming videos
 | 
			
		||||
    pub publish_date: Option<DateTime<Local>>,
 | 
			
		||||
    /// Textual video publish date (e.g. `11 months ago`, depends on language)
 | 
			
		||||
    ///
 | 
			
		||||
    /// Is [`None`] for livestreams and upcoming videos.
 | 
			
		||||
    /// Is `None` for livestreams and upcoming videos.
 | 
			
		||||
    pub publish_date_txt: Option<String>,
 | 
			
		||||
    /// Number of views / current viewers in case of a livestream.
 | 
			
		||||
    ///
 | 
			
		||||
    /// [`None`] if it could not be extracted.
 | 
			
		||||
    /// `None` if it could not be extracted.
 | 
			
		||||
    pub view_count: Option<u64>,
 | 
			
		||||
    /// Is the video an active livestream?
 | 
			
		||||
    pub is_live: bool,
 | 
			
		||||
| 
						 | 
				
			
			@ -703,7 +703,7 @@ pub struct ChannelPlaylist {
 | 
			
		|||
    /// Playlist thumbnail
 | 
			
		||||
    pub thumbnail: Vec<Thumbnail>,
 | 
			
		||||
    /// Number of playlist videos
 | 
			
		||||
    pub video_count: Option<u64>,
 | 
			
		||||
    pub video_count: Option<u32>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Additional channel metadata fetched from the "About" tab.
 | 
			
		||||
| 
						 | 
				
			
			@ -753,98 +753,5 @@ pub struct ChannelRssVideo {
 | 
			
		|||
    /// Number of likes
 | 
			
		||||
    ///
 | 
			
		||||
    /// Zero if the like count was hidden by the creator.
 | 
			
		||||
    pub like_count: u64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
 | 
			
		||||
pub struct SearchResult {
 | 
			
		||||
    pub items: Paginator<SearchItem>,
 | 
			
		||||
    pub corrected_query: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
 | 
			
		||||
pub enum SearchItem {
 | 
			
		||||
    Video(SearchVideo),
 | 
			
		||||
    Playlist(SearchPlaylist),
 | 
			
		||||
    Channel(SearchChannel),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// YouTube video from the search results
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
 | 
			
		||||
#[non_exhaustive]
 | 
			
		||||
pub struct SearchVideo {
 | 
			
		||||
    /// Unique YouTube video ID
 | 
			
		||||
    pub id: String,
 | 
			
		||||
    /// Video title
 | 
			
		||||
    pub title: String,
 | 
			
		||||
    /// Video length in seconds.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Is [`None`] for livestreams.
 | 
			
		||||
    pub length: Option<u32>,
 | 
			
		||||
    /// Video thumbnail
 | 
			
		||||
    pub thumbnail: Vec<Thumbnail>,
 | 
			
		||||
    /// Channel of the video
 | 
			
		||||
    pub channel: ChannelTag,
 | 
			
		||||
    /// Video publishing date.
 | 
			
		||||
    ///
 | 
			
		||||
    /// [`None`] if the date could not be parsed.
 | 
			
		||||
    pub publish_date: Option<DateTime<Local>>,
 | 
			
		||||
    /// Textual video publish date (e.g. `11 months ago`, depends on language)
 | 
			
		||||
    ///
 | 
			
		||||
    /// Is [`None`] for livestreams.
 | 
			
		||||
    pub publish_date_txt: Option<String>,
 | 
			
		||||
    /// View count
 | 
			
		||||
    ///
 | 
			
		||||
    /// [`None`] if it could not be extracted.
 | 
			
		||||
    pub view_count: Option<u64>,
 | 
			
		||||
    /// Is the video an active livestream?
 | 
			
		||||
    pub is_live: bool,
 | 
			
		||||
    /// Is the video a YouTube Short video (vertical and <60s)?
 | 
			
		||||
    pub is_short: bool,
 | 
			
		||||
    /// Abbreviated video description
 | 
			
		||||
    pub short_description: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Playlist from the search results
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
 | 
			
		||||
#[non_exhaustive]
 | 
			
		||||
pub struct SearchPlaylist {
 | 
			
		||||
    /// Unique YouTube Playlist-ID (e.g. `PL5dDx681T4bR7ZF1IuWzOv1omlRbE7PiJ`)
 | 
			
		||||
    pub id: String,
 | 
			
		||||
    /// Playlist name
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    /// Playlist thumbnail
 | 
			
		||||
    pub thumbnail: Vec<Thumbnail>,
 | 
			
		||||
    /// Number of playlist videos
 | 
			
		||||
    pub video_count: u64,
 | 
			
		||||
    /// First 2 videos
 | 
			
		||||
    pub first_videos: Vec<SearchPlaylistVideo>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
 | 
			
		||||
#[non_exhaustive]
 | 
			
		||||
pub struct SearchPlaylistVideo {
 | 
			
		||||
    pub id: String,
 | 
			
		||||
    pub title: String,
 | 
			
		||||
    pub length: Option<u32>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Channel from the search results
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
 | 
			
		||||
#[non_exhaustive]
 | 
			
		||||
pub struct SearchChannel {
 | 
			
		||||
    /// Unique YouTube channel ID
 | 
			
		||||
    pub id: String,
 | 
			
		||||
    /// Channel name
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    /// Channel avatar/profile picture
 | 
			
		||||
    pub avatar: Vec<Thumbnail>,
 | 
			
		||||
    /// Channel verification mark
 | 
			
		||||
    pub verification: Verification,
 | 
			
		||||
    /// Approximate number of subscribers
 | 
			
		||||
    ///
 | 
			
		||||
    /// [`None`] if hidden by the owner or not present.
 | 
			
		||||
    pub subscriber_count: Option<u64>,
 | 
			
		||||
    /// Abbreviated channel description
 | 
			
		||||
    pub short_description: String,
 | 
			
		||||
    pub like_count: u32,
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ pub struct Paginator<T> {
 | 
			
		|||
    ///
 | 
			
		||||
    /// Don't use this number to check if all items were fetched or for
 | 
			
		||||
    /// iterating over the items.
 | 
			
		||||
    pub count: Option<u64>,
 | 
			
		||||
    pub count: Option<u32>,
 | 
			
		||||
    /// Content of the paginator
 | 
			
		||||
    pub items: Vec<T>,
 | 
			
		||||
    /// The continuation token is passed to the YouTube API to fetch
 | 
			
		||||
| 
						 | 
				
			
			@ -41,7 +41,7 @@ impl<T> Default for Paginator<T> {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
impl<T> Paginator<T> {
 | 
			
		||||
    pub(crate) fn new(count: Option<u64>, items: Vec<T>, ctoken: Option<String>) -> Self {
 | 
			
		||||
    pub(crate) fn new(count: Option<u32>, items: Vec<T>, ctoken: Option<String>) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            count: match ctoken {
 | 
			
		||||
                Some(_) => count,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -98,8 +98,11 @@ impl FileReporter {
 | 
			
		|||
 | 
			
		||||
    fn _report(&self, report: &Report) -> Result<()> {
 | 
			
		||||
        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()))?;
 | 
			
		||||
        serde_json::to_writer_pretty(&File::create(report_path)?, &report).or_else(|e| {
 | 
			
		||||
            Err(Error::Other(
 | 
			
		||||
                format!("could not serialize report. err: {}", e).into(),
 | 
			
		||||
            ))
 | 
			
		||||
        })?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -305,18 +305,23 @@ impl<'de> DeserializeAs<'de, TextComponents> for AttributedText {
 | 
			
		|||
            }
 | 
			
		||||
 | 
			
		||||
            let mut buf = String::with_capacity(until - i_utf16);
 | 
			
		||||
            for c in chars.by_ref() {
 | 
			
		||||
                buf.push(c);
 | 
			
		||||
            loop {
 | 
			
		||||
                match chars.next() {
 | 
			
		||||
                    Some(c) => {
 | 
			
		||||
                        buf.push(c);
 | 
			
		||||
 | 
			
		||||
                // is character on Basic Multilingual Plane -> 16bit in UTF-16,
 | 
			
		||||
                // counts as 1 JS character, otherwise 32bit, counts as 2 JS characters
 | 
			
		||||
                if (c as u32) > 0xffff {
 | 
			
		||||
                    i_utf16 += 1;
 | 
			
		||||
                };
 | 
			
		||||
                i_utf16 += 1;
 | 
			
		||||
                        // is character on Basic Multilingual Plane -> 16bit in UTF-16,
 | 
			
		||||
                        // counts as 1 JS character, otherwise 32bit, counts as 2 JS characters
 | 
			
		||||
                        if (c as u32) > 0xffff {
 | 
			
		||||
                            i_utf16 += 1;
 | 
			
		||||
                        };
 | 
			
		||||
                        i_utf16 += 1;
 | 
			
		||||
 | 
			
		||||
                if i_utf16 >= until {
 | 
			
		||||
                    break;
 | 
			
		||||
                        if i_utf16 >= until {
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    None => break,
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            buf
 | 
			
		||||
| 
						 | 
				
			
			@ -334,7 +339,7 @@ impl<'de> DeserializeAs<'de, TextComponents> for AttributedText {
 | 
			
		|||
 | 
			
		||||
            // Replace no-break spaces, trim off whitespace and prefix character
 | 
			
		||||
            let txt_link = txt_link.trim();
 | 
			
		||||
            let txt_link = txt_link.replace('\u{a0}', " ");
 | 
			
		||||
            let txt_link = txt_link.replace("\u{a0}", " ");
 | 
			
		||||
 | 
			
		||||
            static LINK_PREFIX: Lazy<Regex> = Lazy::new(|| Regex::new("^[/•] *").unwrap());
 | 
			
		||||
            let txt_link = LINK_PREFIX.replace(&txt_link, "");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,8 +43,11 @@ pub fn generate_content_playback_nonce() -> String {
 | 
			
		|||
///
 | 
			
		||||
/// `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>)> {
 | 
			
		||||
    let mut parsed_url = Url::parse(url)
 | 
			
		||||
        .map_err(|e| Error::Other(format!("could not parse url `{}` err: {}", url, e).into()))?;
 | 
			
		||||
    let mut parsed_url = Url::parse(url).or_else(|e| {
 | 
			
		||||
        Err(Error::Other(
 | 
			
		||||
            format!("could not parse url `{}` err: {}", url, e).into(),
 | 
			
		||||
        ))
 | 
			
		||||
    })?;
 | 
			
		||||
    let url_params: BTreeMap<String, String> = parsed_url
 | 
			
		||||
        .query_pairs()
 | 
			
		||||
        .map(|(k, v)| (k.to_string(), v.to_string()))
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue