Compare commits

...

2 commits

Author SHA1 Message Date
999ebf7c36 refactor: clean up response models 2022-10-17 11:26:00 +02:00
db6b1ab8a7 refactor: update trends response model 2022-10-17 11:04:18 +02:00
12 changed files with 959 additions and 1219 deletions

View file

@ -280,10 +280,7 @@ async fn channel_videos_cont(testfiles: &Path) {
.unwrap();
let rp = rp_testfile(&json_path);
rp.query()
.channel_videos_continuation(&videos.content.ctoken.unwrap())
.await
.unwrap();
videos.content.next(rp.query()).await.unwrap().unwrap();
}
async fn channel_playlists_cont(testfiles: &Path) {
@ -302,10 +299,7 @@ async fn channel_playlists_cont(testfiles: &Path) {
.unwrap();
let rp = rp_testfile(&json_path);
rp.query()
.channel_playlists_continuation(&playlists.content.ctoken.unwrap())
.await
.unwrap();
playlists.content.next(rp.query()).await.unwrap().unwrap();
}
async fn search(testfiles: &Path) {

View file

@ -1,7 +1,6 @@
use crate::error::{Error, ExtractionError};
use crate::model::{
Comment, ContinuationEndpoint, Paginator, PlaylistVideo, RecommendedVideo, SearchVideo,
YouTubeItem,
Comment, ContinuationEndpoint, Paginator, PlaylistVideo, RecommendedVideo, YouTubeItem,
};
use crate::serializer::MapResult;
use crate::util::TryRemove;
@ -13,8 +12,10 @@ impl RustyPipeQuery {
self,
ctoken: &str,
endpoint: ContinuationEndpoint,
visitor_data: Option<&str>,
) -> Result<Paginator<T>, Error> {
let context = self.get_context(ClientType::Desktop, true).await;
let mut context = self.get_context(ClientType::Desktop, true).await;
context.client.visitor_data = visitor_data.map(str::to_owned);
let request_body = QContinuation {
context,
continuation: ctoken,
@ -140,64 +141,14 @@ paginator!(Comment, RustyPipeQuery::video_comments);
paginator!(PlaylistVideo, RustyPipeQuery::playlist_continuation);
paginator!(RecommendedVideo, RustyPipeQuery::video_recommendations);
impl Paginator<SearchVideo> {
pub async fn next(&self, query: RustyPipeQuery) -> Result<Option<Self>, Error> {
Ok(match (&self.ctoken, &self.visitor_data) {
(Some(ctoken), Some(visitor_data)) => {
Some(query.startpage_continuation(ctoken, visitor_data).await?)
}
_ => None,
})
}
pub async fn extend(&mut self, query: RustyPipeQuery) -> Result<bool, Error> {
match self.next(query).await {
Ok(Some(paginator)) => {
let mut items = paginator.items;
self.items.append(&mut items);
self.ctoken = paginator.ctoken;
Ok(true)
}
Ok(None) => Ok(false),
Err(e) => Err(e),
}
}
pub async fn extend_pages(
&mut self,
query: RustyPipeQuery,
n_pages: usize,
) -> Result<(), Error> {
for _ in 0..n_pages {
match self.extend(query.clone()).await {
Ok(false) => break,
Err(e) => return Err(e),
_ => {}
}
}
Ok(())
}
pub async fn extend_limit(
&mut self,
query: RustyPipeQuery,
n_items: usize,
) -> Result<(), Error> {
while self.items.len() < n_items {
match self.extend(query.clone()).await {
Ok(false) => break,
Err(e) => return Err(e),
_ => {}
}
}
Ok(())
}
}
impl<T: TryFrom<YouTubeItem>> Paginator<T> {
pub async fn next(&self, query: RustyPipeQuery) -> Result<Option<Self>, Error> {
Ok(match &self.ctoken {
Some(ctoken) => Some(query.continuation(ctoken, self.endpoint).await?),
Some(ctoken) => Some(
query
.continuation(ctoken, self.endpoint, self.visitor_data.as_deref())
.await?,
),
_ => None,
})
}
@ -261,6 +212,7 @@ mod tests {
#[rstest]
#[case("search", "search/cont")]
#[case("startpage", "trends/startpage_cont")]
fn map_continuation_items(#[case] name: &str, #[case] path: &str) {
let filename = format!("testfiles/{}.json", path);
let json_path = Path::new(&filename);

View file

@ -23,15 +23,6 @@ pub struct Channel {
pub alerts: Option<Vec<Alert>>,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChannelCont {
#[serde(default)]
#[serde_as(as = "VecSkipError<_>")]
pub on_response_received_actions: Vec<OnResponseReceivedAction>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Contents {
@ -205,17 +196,3 @@ pub struct PrimaryLink {
pub title: String,
pub navigation_endpoint: NavigationEndpoint,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OnResponseReceivedAction {
pub append_continuation_items_action: AppendAction,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AppendAction {
#[serde_as(as = "VecLogError<_>")]
pub continuation_items: MapResult<Vec<YouTubeListItem>>,
}

View file

@ -9,15 +9,12 @@ pub mod video_details;
pub mod video_item;
pub use channel::Channel;
pub use channel::ChannelCont;
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 trends::Startpage;
pub use trends::StartpageCont;
pub use trends::Trending;
pub use url_endpoint::ResolvedUrl;
pub use video_details::VideoComments;

View file

@ -1,14 +1,7 @@
use serde::Deserialize;
use serde_with::json::JsonString;
use serde_with::{serde_as, VecSkipError};
use serde_with::{json::JsonString, serde_as};
use crate::serializer::ignore_any;
use crate::serializer::{text::Text, MapResult, VecLogError};
use super::video_item::YouTubeListItem;
use super::{
ChannelRenderer, ContentsRenderer, ContinuationEndpoint, PlaylistRenderer, VideoRenderer,
};
use super::video_item::YouTubeListRendererWrap;
#[serde_as]
#[derive(Debug, Deserialize)]
@ -19,28 +12,6 @@ pub struct Search {
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>,
#[serde_as(as = "VecSkipError<_>")]
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 {
@ -50,52 +21,5 @@ pub struct Contents {
#[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<YouTubeListItem>>,
},
/// 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,
pub primary_contents: YouTubeListRendererWrap,
}

View file

@ -1,46 +1,33 @@
use serde::Deserialize;
use serde_with::{serde_as, VecSkipError};
use crate::serializer::{ignore_any, MapResult, VecLogError};
use super::{ContentRenderer, ContentsRenderer, VideoListItem, VideoRenderer};
use super::{video_item::YouTubeListRendererWrap, ContentRenderer};
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Startpage {
pub contents: Contents<BrowseResultsStartpage>,
pub contents: Contents,
pub response_context: ResponseContext,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StartpageCont {
#[serde(default)]
#[serde_as(as = "VecSkipError<_>")]
pub on_response_received_actions: Vec<OnResponseReceivedAction>,
pub struct Trending {
pub contents: Contents,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Contents<T> {
pub two_column_browse_results_renderer: T,
pub struct Contents {
pub two_column_browse_results_renderer: BrowseResults,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BrowseResultsStartpage {
pub struct BrowseResults {
#[serde_as(as = "VecSkipError<_>")]
pub tabs: Vec<Tab<StartpageTabContent>>,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BrowseResultsTrends {
#[serde_as(as = "VecSkipError<_>")]
pub tabs: Vec<Tab<TrendingTabContent>>,
pub tabs: Vec<Tab<YouTubeListRendererWrap>>,
}
#[derive(Debug, Deserialize)]
@ -49,85 +36,8 @@ pub struct Tab<T> {
pub tab_renderer: ContentRenderer<T>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StartpageTabContent {
pub rich_grid_renderer: RichGridRenderer,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RichGridRenderer {
#[serde_as(as = "VecLogError<_>")]
pub contents: MapResult<Vec<VideoListItem>>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Trending {
pub contents: Contents<BrowseResultsTrends>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TrendingTabContent {
pub section_list_renderer: ContentsRenderer<ItemSectionRenderer>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ItemSectionRenderer {
pub item_section_renderer: ContentsRenderer<ShelfRenderer>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ShelfRenderer {
pub shelf_renderer: ContentRenderer<ShelfContents>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ShelfContents {
pub expanded_shelf_contents_renderer: Option<ShelfContentsRenderer>,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ShelfContentsRenderer {
#[serde_as(as = "VecLogError<_>")]
pub items: MapResult<Vec<TrendingListItem>>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResponseContext {
pub visitor_data: Option<String>,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
#[allow(clippy::large_enum_variant)]
pub enum TrendingListItem {
VideoRenderer(VideoRenderer),
#[serde(other, deserialize_with = "ignore_any")]
None,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OnResponseReceivedAction {
pub append_continuation_items_action: AppendAction,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AppendAction {
#[serde_as(as = "VecLogError<_>")]
pub continuation_items: MapResult<Vec<VideoListItem>>,
}

View file

@ -49,6 +49,7 @@ pub enum YouTubeListItem {
///
/// Seems to be currently A/B tested on the channel page,
/// as of 11.10.2022
#[serde(alias = "shelfRenderer")]
RichItemRenderer {
content: Box<YouTubeListItem>,
},
@ -57,7 +58,9 @@ pub enum YouTubeListItem {
///
/// Seems to be currently A/B tested on the video details page,
/// as of 11.10.2022
#[serde(alias = "expandedShelfContentsRenderer")]
ItemSectionRenderer {
#[serde(alias = "items")]
#[serde_as(as = "VecLogError<_>")]
contents: MapResult<Vec<YouTubeListItem>>,
},
@ -162,6 +165,21 @@ pub struct ChannelRenderer {
pub owner_badges: Vec<ChannelBadge>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct YouTubeListRendererWrap {
#[serde(alias = "richGridRenderer")]
pub section_list_renderer: YouTubeListRenderer,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct YouTubeListRenderer {
#[serde_as(as = "VecLogError<_>")]
pub contents: MapResult<Vec<YouTubeListItem>>,
}
/// Result of mapping a list of different YouTube enities
/// (videos, channels, playlists)
#[derive(Debug)]
@ -375,32 +393,3 @@ impl YouTubeListMapper<PlaylistItem> {
res.c.into_iter().for_each(|item| self.map_item(item));
}
}
impl YouTubeListMapper<ChannelItem> {
fn map_item(&mut self, item: YouTubeListItem) {
match item {
YouTubeListItem::ChannelRenderer(channel) => {
self.items.push(Self::map_channel(channel));
}
YouTubeListItem::ContinuationItemRenderer {
continuation_endpoint,
} => self.ctoken = Some(continuation_endpoint.continuation_command.token),
YouTubeListItem::ShowingResultsForRenderer { corrected_query } => {
self.corrected_query = Some(corrected_query);
}
YouTubeListItem::RichItemRenderer { content } => {
self.map_item(*content);
}
YouTubeListItem::ItemSectionRenderer { mut contents } => {
self.warnings.append(&mut contents.warnings);
contents.c.into_iter().for_each(|it| self.map_item(it));
}
_ => {}
}
}
pub fn map_response(&mut self, mut res: MapResult<Vec<YouTubeListItem>>) {
self.warnings.append(&mut res.warnings);
res.c.into_iter().for_each(|item| self.map_item(item));
}
}

View file

@ -93,21 +93,19 @@ impl MapResponse<SearchResult> for response::Search {
lang: Language,
_deobf: Option<&Deobfuscator>,
) -> Result<MapResult<SearchResult>, ExtractionError> {
let section_list_items = self
let 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 mapper = response::YouTubeListMapper::<YouTubeItem>::new(lang);
mapper.map_response(items);
Ok(MapResult {
c: SearchResult {
items: Paginator::new(self.estimated_results, mapper.items, ctoken),
items: Paginator::new(self.estimated_results, mapper.items, mapper.ctoken),
corrected_query: mapper.corrected_query,
},
warnings: mapper.warnings,
@ -115,32 +113,6 @@ impl MapResponse<SearchResult> for response::Search {
}
}
fn map_section_list_items(
section_list_items: Vec<response::search::SectionListItem>,
) -> Result<(MapResult<Vec<response::YouTubeListItem>>, 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))
}
#[cfg(test)]
mod tests {
use std::{fs::File, io::BufReader, path::Path};

View file

@ -1,11 +1,11 @@
---
source: src/client/trends.rs
source: src/client/pagination.rs
expression: map_res.c
---
Paginator(
count: None,
items: [
SearchVideo(
Video(VideoItem(
id: "mRmlXh7Hams",
title: "Extra 3 vom 12.10.2022 im NDR | extra 3 | NDR",
length: Some(1839),
@ -16,7 +16,7 @@ Paginator(
height: 270,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCjhkuC_Pi85wGjnB0I1ydxw",
name: "extra 3",
avatar: [
@ -28,15 +28,16 @@ Paginator(
],
verification: none,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("2 days ago"),
view_count: 585257,
view_count: Some(585257),
is_live: false,
is_short: false,
short_description: "Niedersachsen nach der Wahl: Schuld ist immer die Ampel | Die Grünen: Partei der erneuerbaren Prinzipien | Verhütung? Ist Frauensache! | Youtube: Handwerk mit goldenem Boden - Christian Ehring...",
),
SearchVideo(
is_upcoming: false,
short_description: Some("Niedersachsen nach der Wahl: Schuld ist immer die Ampel | Die Grünen: Partei der erneuerbaren Prinzipien | Verhütung? Ist Frauensache! | Youtube: Handwerk mit goldenem Boden - Christian Ehring..."),
)),
Video(VideoItem(
id: "LsXC5r64Pvc",
title: "Most Rarest Plays In Baseball History",
length: Some(1975),
@ -47,7 +48,7 @@ Paginator(
height: 270,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCRfKJZ7LHueFudiDgAJDr9Q",
name: "Top All Sports",
avatar: [
@ -59,15 +60,16 @@ Paginator(
],
verification: none,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("3 weeks ago"),
view_count: 985521,
view_count: Some(985521),
is_live: false,
is_short: false,
short_description: "#baseball #mlb #mlbb",
),
SearchVideo(
is_upcoming: false,
short_description: Some("#baseball #mlb #mlbb"),
)),
Video(VideoItem(
id: "dwPmd1GqQHE",
title: "90S RAP & HIPHOP MIX - Notorious B I G , Dr Dre, 50 Cent, Snoop Dogg, 2Pac, DMX, Lil Jon and more",
length: Some(5457),
@ -78,7 +80,7 @@ Paginator(
height: 270,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCKICAAGtBLJJ5zRdIxn_B4g",
name: "#Hip Hop 2022",
avatar: [
@ -90,15 +92,16 @@ Paginator(
],
verification: none,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("5 months ago"),
view_count: 1654055,
view_count: Some(1654055),
is_live: false,
is_short: false,
short_description: "",
),
SearchVideo(
is_upcoming: false,
short_description: None,
)),
Video(VideoItem(
id: "qxI-Ob8lpLE",
title: "Schlatt\'s Chips Tier List",
length: Some(1071),
@ -114,7 +117,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UC2mP7il3YV7TxM_3m6U0bwA",
name: "jschlattLIVE",
avatar: [
@ -126,15 +129,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("1 year ago"),
view_count: 9029628,
view_count: Some(9029628),
is_live: false,
is_short: false,
short_description: "Schlatt ranks every chip ever made.\nCREATE YOUR OWN TIER LIST: https://tiermaker.com/create/chips-for-big-guy-1146620\n\nSubscribe to me on Twitch:\nhttps://twitch.tv/jschlatt\n\nFollow me on Twitter:...",
),
SearchVideo(
is_upcoming: false,
short_description: Some("Schlatt ranks every chip ever made.\nCREATE YOUR OWN TIER LIST: https://tiermaker.com/create/chips-for-big-guy-1146620\n\nSubscribe to me on Twitch:\nhttps://twitch.tv/jschlatt\n\nFollow me on Twitter:..."),
)),
Video(VideoItem(
id: "qmrzTUmZ4UU",
title: "850€ für den Verrat am System - UCS AT-AT LEGO® Star Wars 75313",
length: Some(2043),
@ -150,7 +154,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UC_EZd3lsmxudu3IQzpTzOgw",
name: "Held der Steine Inh. Thomas Panke",
avatar: [
@ -162,15 +166,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("6 days ago"),
view_count: 600516,
view_count: Some(600516),
is_live: false,
is_short: false,
short_description: "Star Wars - erschienen 2021 - 6749 Teile\n\nDieses Set bei Amazon*:\nhttps://amzn.to/3yu9dHX\n\nErwähnt im Video*:\nTassen https://bit.ly/HdSBausteinecke\nBig Boy https://bit.ly/BBLokBigBoy\nBurg...",
),
SearchVideo(
is_upcoming: false,
short_description: Some("Star Wars - erschienen 2021 - 6749 Teile\n\nDieses Set bei Amazon*:\nhttps://amzn.to/3yu9dHX\n\nErwähnt im Video*:\nTassen https://bit.ly/HdSBausteinecke\nBig Boy https://bit.ly/BBLokBigBoy\nBurg..."),
)),
Video(VideoItem(
id: "4q4vpQCIZ6w",
title: "🌉 Manhattan Jazz 💖 l Relaxing Jazz Piano Music l Background Music",
length: Some(23229),
@ -186,7 +191,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCBnMxlW70f0SB4ZTJx124lw",
name: "몽키비지엠 MONKEYBGM",
avatar: [
@ -198,15 +203,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("6 months ago"),
view_count: 2343407,
view_count: Some(2343407),
is_live: false,
is_short: false,
short_description: "- Please Subscribe!\n\n🔺Disney OST Collection part 1 \n ➡\u{fe0f} https://youtu.be/lrzKFu85nhE\n\n🔺Disney OST Collection part 2 \n ➡\u{fe0f} https://youtu.be/EtE09lowIbk\n\n🔺Studio Ghibli...",
),
SearchVideo(
is_upcoming: false,
short_description: Some("- Please Subscribe!\n\n🔺Disney OST Collection part 1 \n ➡\u{fe0f} https://youtu.be/lrzKFu85nhE\n\n🔺Disney OST Collection part 2 \n ➡\u{fe0f} https://youtu.be/EtE09lowIbk\n\n🔺Studio Ghibli..."),
)),
Video(VideoItem(
id: "Z_k31kqZxaE",
title: "1 in 1,000,000 NBA Moments",
length: Some(567),
@ -222,7 +228,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCpyoYVlp67N16Lg1_N4VnVw",
name: "dime",
avatar: [
@ -234,15 +240,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("1 month ago"),
view_count: 4334298,
view_count: Some(4334298),
is_live: false,
is_short: false,
short_description: "• Instagram - https://instagram.com/dime_nba\n• TikTok - https://tiktok.com/@dime_nba\n\ndime is a Swedish brand, founded in 2022. We produce some of the most entertaining NBA content on YouTube...",
),
SearchVideo(
is_upcoming: false,
short_description: Some("• Instagram - https://instagram.com/dime_nba\n• TikTok - https://tiktok.com/@dime_nba\n\ndime is a Swedish brand, founded in 2022. We produce some of the most entertaining NBA content on YouTube..."),
)),
Video(VideoItem(
id: "zE-a5eqvlv8",
title: "Dua Lipa, Coldplay, Martin Garrix & Kygo, The Chainsmokers Style - Feeling Me",
length: None,
@ -258,7 +265,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCX-USfenzQlhrEJR1zD5IYw",
name: "Deep Mood.",
avatar: [
@ -270,15 +277,16 @@ Paginator(
],
verification: none,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: None,
view_count: 889,
is_live: false,
view_count: Some(889),
is_live: true,
is_short: false,
short_description: "#Summermix #DeepHouse #DeepHouseSummerMix\nDua Lipa, Coldplay, Martin Garrix & Kygo, The Chainsmokers Style - Feeling Me\n\n🎵 All songs in this spotify playlist: https://spoti.fi/2TJ4Dyj\nSubmit...",
),
SearchVideo(
is_upcoming: false,
short_description: Some("#Summermix #DeepHouse #DeepHouseSummerMix\nDua Lipa, Coldplay, Martin Garrix & Kygo, The Chainsmokers Style - Feeling Me\n\n🎵 All songs in this spotify playlist: https://spoti.fi/2TJ4Dyj\nSubmit..."),
)),
Video(VideoItem(
id: "gNlOk0LXi5M",
title: "Soll ich dir 1g GOLD schenken? oder JEMAND anderen DOPPELT?",
length: Some(704),
@ -294,7 +302,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCqcWNPTUVATZt0Dlr2jV0Wg",
name: "Mois",
avatar: [
@ -306,15 +314,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("8 days ago"),
view_count: 463834,
view_count: Some(463834),
is_live: false,
is_short: false,
short_description: "Je mehr Menschen mich abonnieren desto mehr Menschen werde ich glücklich machen \n\n24 std ab, viel Glück \n\nhttps://I-Clip.com/?sPartner=Mois",
),
SearchVideo(
is_upcoming: false,
short_description: Some("Je mehr Menschen mich abonnieren desto mehr Menschen werde ich glücklich machen \n\n24 std ab, viel Glück \n\nhttps://I-Clip.com/?sPartner=Mois"),
)),
Video(VideoItem(
id: "dbMvZjs8Yc8",
title: "Brad Pitt- Die Revanche eines Sexsymbols | Doku HD | ARTE",
length: Some(3137),
@ -330,7 +339,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCsygZtQQSplGF6JA3XWvsdg",
name: "Irgendwas mit ARTE und Kultur",
avatar: [
@ -342,15 +351,16 @@ Paginator(
],
verification: none,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("5 days ago"),
view_count: 293878,
view_count: Some(293878),
is_live: false,
is_short: false,
short_description: "Vom „People“-Magazin wurde er mehrfach zum „Sexiest Man Alive“ gekrönt. Aber sein Aussehen ist nicht alles: In 30 Jahren Karriere drehte Brad Pitt eine Vielzahl herausragender Filme....",
),
SearchVideo(
is_upcoming: false,
short_description: Some("Vom „People“-Magazin wurde er mehrfach zum „Sexiest Man Alive“ gekrönt. Aber sein Aussehen ist nicht alles: In 30 Jahren Karriere drehte Brad Pitt eine Vielzahl herausragender Filme...."),
)),
Video(VideoItem(
id: "mFxi3lOAcFs",
title: "Craziest Soviet Machines You Won\'t Believe Exist - Part 1",
length: Some(1569),
@ -366,7 +376,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCkQO3QsgTpNTsOw6ujimT5Q",
name: "BE AMAZED",
avatar: [
@ -378,15 +388,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("1 year ago"),
view_count: 14056843,
view_count: Some(14056843),
is_live: false,
is_short: false,
short_description: "Coming up are some crazy Soviet-era machines you won\'t believe exist!\nPart 2: https://youtu.be/MBZVOJrhuHY\nSuggest a topic here to be turned into a video: http://bit.ly/2kwqhuh\nSubscribe for...",
),
SearchVideo(
is_upcoming: false,
short_description: Some("Coming up are some crazy Soviet-era machines you won\'t believe exist!\nPart 2: https://youtu.be/MBZVOJrhuHY\nSuggest a topic here to be turned into a video: http://bit.ly/2kwqhuh\nSubscribe for..."),
)),
Video(VideoItem(
id: "eu7ubm7g59E",
title: "People Hated Me For Using This Slab",
length: Some(1264),
@ -402,7 +413,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UC6I0KzAD7uFTL1qzxyunkvA",
name: "Blacktail Studio",
avatar: [
@ -414,15 +425,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("3 months ago"),
view_count: 2845035,
view_count: Some(2845035),
is_live: false,
is_short: false,
short_description: "Some people were furious I used this slab, and I actually understand why. \nBlacktail bow tie jig (limited first run): https://www.blacktailstudio.com/bowtie-jig\nBlacktail epoxy table workshop:...",
),
SearchVideo(
is_upcoming: false,
short_description: Some("Some people were furious I used this slab, and I actually understand why. \nBlacktail bow tie jig (limited first run): https://www.blacktailstudio.com/bowtie-jig\nBlacktail epoxy table workshop:..."),
)),
Video(VideoItem(
id: "TRGHIN2PGIA",
title: "Christian Bale Breaks Down His Most Iconic Characters | GQ",
length: Some(1381),
@ -438,7 +450,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCsEukrAd64fqA7FjwkmZ_Dw",
name: "GQ",
avatar: [
@ -450,15 +462,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("9 days ago"),
view_count: 8044465,
view_count: Some(8044465),
is_live: false,
is_short: false,
short_description: "Christian Bale breaks down a few of his most iconic characters from \'American Psycho,\' \'The Dark Knight\' Trilogy, \'The Fighter,\' \'The Machinist,\' \'The Big Short,\' \'Vice,\' \'Empire of the Sun,\'...",
),
SearchVideo(
is_upcoming: false,
short_description: Some("Christian Bale breaks down a few of his most iconic characters from \'American Psycho,\' \'The Dark Knight\' Trilogy, \'The Fighter,\' \'The Machinist,\' \'The Big Short,\' \'Vice,\' \'Empire of the Sun,\'..."),
)),
Video(VideoItem(
id: "w3tENzcssDU",
title: "NFL Trick Plays But They Get Increasingly Higher IQ",
length: Some(599),
@ -474,7 +487,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCJka5SDh36_N4pjJd69efkg",
name: "Savage Brick Sports",
avatar: [
@ -486,15 +499,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("3 months ago"),
view_count: 1172372,
view_count: Some(1172372),
is_live: false,
is_short: false,
short_description: "NFL Trick Plays But They Get Increasingly Higher IQ\nCredit to CoshReport for starting this trend.\n\n(if any of the links don\'t work, check most recent video)\nTalkSports Discord: https://discord.gg/n...",
),
SearchVideo(
is_upcoming: false,
short_description: Some("NFL Trick Plays But They Get Increasingly Higher IQ\nCredit to CoshReport for starting this trend.\n\n(if any of the links don\'t work, check most recent video)\nTalkSports Discord: https://discord.gg/n..."),
)),
Video(VideoItem(
id: "gUAd2XXzH7w",
title: "⚓\u{fe0f}Found ABANDONED SHIP!!! Big CRUISE SHIP on a desert island☠\u{fe0f} Where did the people go?!?",
length: Some(2949),
@ -510,7 +524,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UClUZos7yKYtrmr0-azaD8pw",
name: "Kreosan English",
avatar: [
@ -522,15 +536,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("1 month ago"),
view_count: 1883533,
view_count: Some(1883533),
is_live: false,
is_short: false,
short_description: "We are preparing a continuation of the cruise ship for you! Very soon you will be able to see the next part. If you would like to help us make a video:\n\n► Support us - https://www.patreon.com/k...",
),
SearchVideo(
is_upcoming: false,
short_description: Some("We are preparing a continuation of the cruise ship for you! Very soon you will be able to see the next part. If you would like to help us make a video:\n\n► Support us - https://www.patreon.com/k..."),
)),
Video(VideoItem(
id: "YpGjaJ1ettI",
title: "[Working BGM] Comfortable music that makes you feel positive -- Morning Mood -- Daily Routine",
length: Some(3651),
@ -546,7 +561,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCpxY9-3iB5Hyho31uBgzh7w",
name: "Daily Routine",
avatar: [
@ -558,15 +573,16 @@ Paginator(
],
verification: none,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("2 months ago"),
view_count: 1465389,
view_count: Some(1465389),
is_live: false,
is_short: false,
short_description: "Hello everyone. It\'s me again. I will stay at home and study . It\'s full of fun energy today, so it\'s ready to spread to everyone with hilarious music. 🔥🔥🔥\nHave fun together 😊😊😊...",
),
SearchVideo(
is_upcoming: false,
short_description: Some("Hello everyone. It\'s me again. I will stay at home and study . It\'s full of fun energy today, so it\'s ready to spread to everyone with hilarious music. 🔥🔥🔥\nHave fun together 😊😊😊..."),
)),
Video(VideoItem(
id: "rPAhFD8hKxQ",
title: "Survival Camping 9ft/3m Under Snow - Giant Winter Bushcraft Shelter and Quinzee",
length: Some(1301),
@ -582,7 +598,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCfpCQ89W9wjkHc8J_6eTbBg",
name: "Outdoor Boys",
avatar: [
@ -594,15 +610,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("6 months ago"),
view_count: 20488431,
view_count: Some(20488431),
is_live: false,
is_short: false,
short_description: "Solo winter camping and bushcraft 9 feet (3 meters) under the snow. I hiked high up into the mountains during a snow storm with 30 mph/48 kmh winds to build a deep snow bushcraft survival shelter...",
),
SearchVideo(
is_upcoming: false,
short_description: Some("Solo winter camping and bushcraft 9 feet (3 meters) under the snow. I hiked high up into the mountains during a snow storm with 30 mph/48 kmh winds to build a deep snow bushcraft survival shelter..."),
)),
Video(VideoItem(
id: "2rye4u-cCNk",
title: "Pink Panther Fights Off Pests | 54 Minute Compilation | The Pink Panther Show",
length: Some(3158),
@ -618,7 +635,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCFeUyPY6W8qX8w2o6oSiRmw",
name: "Official Pink Panther",
avatar: [
@ -630,15 +647,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("11 months ago"),
view_count: 27357653,
view_count: Some(27357653),
is_live: false,
is_short: false,
short_description: "(1) Pink Pest Control\n(2) Pink-a-Boo\n(3) Little Beaux Pink\n(4) The Pink Package Plot\n(5) Come On In! The Water\'s Pink\n(6) Psychedelic Pink\n(7) Pink Posies\n(8) G.I. Pink\n\nThe Pink Panther is...",
),
SearchVideo(
is_upcoming: false,
short_description: Some("(1) Pink Pest Control\n(2) Pink-a-Boo\n(3) Little Beaux Pink\n(4) The Pink Package Plot\n(5) Come On In! The Water\'s Pink\n(6) Psychedelic Pink\n(7) Pink Posies\n(8) G.I. Pink\n\nThe Pink Panther is..."),
)),
Video(VideoItem(
id: "O0xAlfSaBNQ",
title: "FC Nantes vs. SC Freiburg Highlights & Tore | UEFA Europa League",
length: Some(326),
@ -654,7 +672,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UC8WYi3XQXsf-6FNvqoEvxag",
name: "RTL Sport",
avatar: [
@ -666,15 +684,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("11 hours ago"),
view_count: 117395,
view_count: Some(117395),
is_live: false,
is_short: false,
short_description: "UEFA Europa League: https://www.rtlplus.com/shows/uefa-europa-league-19818?utm_source=youtube&utm_medium=editorial&utm_campaign=beschreibung&utm_term=rtlsport \nFC Nantes vs. SC Freiburg ...",
),
SearchVideo(
is_upcoming: false,
short_description: Some("UEFA Europa League: https://www.rtlplus.com/shows/uefa-europa-league-19818?utm_source=youtube&utm_medium=editorial&utm_campaign=beschreibung&utm_term=rtlsport \nFC Nantes vs. SC Freiburg ..."),
)),
Video(VideoItem(
id: "Mhs9Sbnw19o",
title: "Dramatisches Duell: 400 Jahre altes Kästchen erzielt zig-fachen Wunschpreis! | Bares für Rares XXL",
length: Some(744),
@ -690,7 +709,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UC53bIpnef1pwAx69ERmmOLA",
name: "Bares für Rares",
avatar: [
@ -702,15 +721,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("11 days ago"),
view_count: 836333,
view_count: Some(836333),
is_live: false,
is_short: false,
short_description: "Du hast Schätze im Keller, die du unseren Expert*innen präsentieren möchtest? Hier geht\'s zum Bewerbungsformular: kurz.zdf.de/lSJ/\n\nEin einmaliges Bieterduell treibt den Preis für dieses...",
),
SearchVideo(
is_upcoming: false,
short_description: Some("Du hast Schätze im Keller, die du unseren Expert*innen präsentieren möchtest? Hier geht\'s zum Bewerbungsformular: kurz.zdf.de/lSJ/\n\nEin einmaliges Bieterduell treibt den Preis für dieses..."),
)),
Video(VideoItem(
id: "Bzzp5Cay7DI",
title: "Sweet Jazz - Cool autumn Bossa Nova & October Jazz Positive Mood",
length: None,
@ -726,7 +746,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCoGlllJE7aYe_VzIGP3s_wA",
name: "Smooth Jazz Music",
avatar: [
@ -738,15 +758,16 @@ Paginator(
],
verification: none,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: None,
view_count: 1216,
is_live: false,
view_count: Some(1216),
is_live: true,
is_short: false,
short_description: "Sweet Jazz - Cool autumn Bossa Nova & October Jazz Positive Mood\nhttps://youtu.be/Bzzp5Cay7DI\n********************************************\nSounds available on: Jazz Bossa Nova\nOFFICIAL VIDEO:...",
),
SearchVideo(
is_upcoming: false,
short_description: Some("Sweet Jazz - Cool autumn Bossa Nova & October Jazz Positive Mood\nhttps://youtu.be/Bzzp5Cay7DI\n********************************************\nSounds available on: Jazz Bossa Nova\nOFFICIAL VIDEO:..."),
)),
Video(VideoItem(
id: "SlskTqc9CEc",
title: "The Chick-Fil-A Full Menu Challenge",
length: Some(613),
@ -762,7 +783,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCd1fLoVFooPeWqCEYVUJZqg",
name: "Matt Stonie",
avatar: [
@ -774,15 +795,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("3 years ago"),
view_count: 39286403,
view_count: Some(39286403),
is_live: false,
is_short: false,
short_description: "Good Video? Like/Fav & Share!!\n\nTBH this is really my 1st time trying Chick-Fil-A, legitimately. My verdict is torn, but that sauce is BOMB!\n\nChallenge\n+ Chick-Fil-A Deluxe\n+ Spicy Deluxe\n+...",
),
SearchVideo(
is_upcoming: false,
short_description: Some("Good Video? Like/Fav & Share!!\n\nTBH this is really my 1st time trying Chick-Fil-A, legitimately. My verdict is torn, but that sauce is BOMB!\n\nChallenge\n+ Chick-Fil-A Deluxe\n+ Spicy Deluxe\n+..."),
)),
Video(VideoItem(
id: "CwRvM2TfYbs",
title: "Gentle healing music of health and to calm the nervous system, deep relaxation! Say Life Yes",
length: None,
@ -798,7 +820,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UC6jH5GNi0iOR17opA1Vowhw",
name: "Lucid Dream",
avatar: [
@ -810,15 +832,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: None,
view_count: 1416,
is_live: false,
view_count: Some(1416),
is_live: true,
is_short: false,
short_description: "🌿 Music for relaxation, meditation, study, reading, massage, spa or sleep. This music is ideal for dealing with anxiety, stress or insomnia as it promotes relaxation and helps eliminate...",
),
SearchVideo(
is_upcoming: false,
short_description: Some("🌿 Music for relaxation, meditation, study, reading, massage, spa or sleep. This music is ideal for dealing with anxiety, stress or insomnia as it promotes relaxation and helps eliminate..."),
)),
Video(VideoItem(
id: "7jz0pXSe_kI",
title: "Craziest \"Fine...I\'ll Do it Myself\" Moments in Sports History (PART 2)",
length: Some(1822),
@ -834,7 +857,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCd5hdemikI6GxwGKhJCwzww",
name: "Highlight Reel",
avatar: [
@ -846,14 +869,15 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("10 months ago"),
view_count: 11601863,
view_count: Some(11601863),
is_live: false,
is_short: false,
short_description: "(PART 2) of 👉🏼 Craziest \"Fine...I\'ll Do It Myself\" Moments in Sports History \n\nBIBLE VERSE OF THE DAY: Luke 12:40",
),
is_upcoming: false,
short_description: Some("(PART 2) of 👉🏼 Craziest \"Fine...I\'ll Do It Myself\" Moments in Sports History \n\nBIBLE VERSE OF THE DAY: Luke 12:40"),
)),
],
ctoken: Some("4qmFsgKxAxIPRkV3aGF0X3RvX3dhdGNoGoADQ0RCNmxnSkhUWFpRYzJOVU1UUm1iME5OWjNOSmQzWjZOM0JPWlZWMldqZDFRVlp3ZEVOdGMwdEhXR3d3V0ROQ2FGb3lWbVpqTWpWb1kwaE9iMkl6VW1aamJWWnVZVmM1ZFZsWGQxTklNVlUwVDFSU1dXUXhUbXhXTTBaeVdsaGtSRkpGYkZCWk0yaDZWbXMxTlV4VmVGbE1XRnBSVlcxallVeFJRVUZhVnpSQlFWWldWRUZCUmtWU1VVRkNRVVZhUm1ReWFHaGtSamt3WWpFNU0xbFlVbXBoUVVGQ1FVRkZRa0ZCUVVKQlFVVkJRVUZGUWtGSFNrSkRRVUZUUlROQ2FGb3lWbVpqTWpWb1kwaE9iMkl6VW1aa1J6bHlXbGMwWVVWM2Ftb3hPRkJGT1dWSU5rRm9WVlpZWlVGTFNGaHVSMEp2ZDJsRmQycERObkZmUlRsbFNEWkJhRmRIZG1RMFMwaGxaMGhDTlZnMmJrMWxPVU5SU1VsTlVRJTNEJTNEmgIaYnJvd3NlLWZlZWRGRXdoYXRfdG9fd2F0Y2g%3D"),
endpoint: browse,

View file

@ -5,7 +5,7 @@ expression: map_res.c
Paginator(
count: None,
items: [
SearchVideo(
VideoItem(
id: "_cyJhGsXDDM",
title: "Ultimate Criminal Canal Found Magnet Fishing! Police on the Hunt",
length: Some(1096),
@ -21,7 +21,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCMLXec9-wpON8tZegnDsYLw",
name: "Bondi Treasure Hunter",
avatar: [
@ -33,15 +33,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("1 day ago"),
view_count: 700385,
view_count: Some(700385),
is_live: false,
is_short: false,
short_description: "Subscribe for more Treasure Hunting videos: https://tinyurl.com/yyl3zerk\n\nMy Magnet! (Use Discount code \'BONDI\'): https://magnetarmagnets.com/\nMy Dive System! (Use Bonus code \'BONDI\'): https://lddy...",
is_upcoming: false,
short_description: Some("Subscribe for more Treasure Hunting videos: https://tinyurl.com/yyl3zerk\n\nMy Magnet! (Use Discount code \'BONDI\'): https://magnetarmagnets.com/\nMy Dive System! (Use Bonus code \'BONDI\'): https://lddy..."),
),
SearchVideo(
VideoItem(
id: "36YnV9STBqc",
title: "The Good Life Radio\u{a0}•\u{a0}24/7 Live Radio | Best Relax House, Chillout, Study, Running, Gym, Happy Music",
length: None,
@ -57,7 +58,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UChs0pSaEoNLV4mevBFGaoKA",
name: "The Good Life Radio x Sensual Musique",
avatar: [
@ -69,15 +70,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: None,
view_count: 7202,
is_live: false,
view_count: Some(7202),
is_live: true,
is_short: false,
short_description: "The Good Life is live streaming the best of Relaxing & Chill House Music, Deep House, Tropical House, EDM, Dance & Pop as well as Music for Sleep, Focus, Study, Workout, Gym, Running etc. in...",
is_upcoming: false,
short_description: Some("The Good Life is live streaming the best of Relaxing & Chill House Music, Deep House, Tropical House, EDM, Dance & Pop as well as Music for Sleep, Focus, Study, Workout, Gym, Running etc. in..."),
),
SearchVideo(
VideoItem(
id: "YYD1qgH5qC4",
title: "چند شنبه با سینــا | فصل چهـارم | قسمت 5 | با حضور نازنین انصاری مدیر روزنامه کیهان لندن",
length: Some(3261),
@ -93,7 +95,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCzH_7hfL6Jd1H0WpNO_eryQ",
name: "MBC PERSIA",
avatar: [
@ -105,15 +107,16 @@ Paginator(
],
verification: none,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("14 hours ago"),
view_count: 104344,
view_count: Some(104344),
is_live: false,
is_short: false,
short_description: "#mbcpersia\n#chandshanbeh\n#چندشنبه\n\nشبكه ام بى سى پرشيا را از حساب هاى مختلف در شبكه هاى اجتماعى دنبال كنيد\n►MBCPERSIA on Facebook:...",
is_upcoming: false,
short_description: Some("#mbcpersia\n#chandshanbeh\n#چندشنبه\n\nشبكه ام بى سى پرشيا را از حساب هاى مختلف در شبكه هاى اجتماعى دنبال كنيد\n►MBCPERSIA on Facebook:..."),
),
SearchVideo(
VideoItem(
id: "BeJqgI6rw9k",
title: "your city is full of fake buildings, here\'s why",
length: Some(725),
@ -129,7 +132,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCqVEHtQoXHmUCfJ-9smpTSg",
name: "Answer in Progress",
avatar: [
@ -141,15 +144,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("7 days ago"),
view_count: 1447008,
view_count: Some(1447008),
is_live: false,
is_short: false,
short_description: "Save 33% on your first Native Deodorant Pack - normally $39, youll get it for $26! Click here https://bit.ly/nativeanswer1 and use my code ANSWER #AD\n\nSomewhere on your street there may...",
is_upcoming: false,
short_description: Some("Save 33% on your first Native Deodorant Pack - normally $39, youll get it for $26! Click here https://bit.ly/nativeanswer1 and use my code ANSWER #AD\n\nSomewhere on your street there may..."),
),
SearchVideo(
VideoItem(
id: "ma28eWd1oyA",
title: "Post Malone, Maroon 5, Adele, Taylor Swift, Ed Sheeran, Shawn Mendes, Pop Hits 2020 Part 6",
length: Some(29989),
@ -160,7 +164,7 @@ Paginator(
height: 270,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCldQuUMYTUGrjvcU2vaPSFQ",
name: "Music Library",
avatar: [
@ -172,15 +176,16 @@ Paginator(
],
verification: none,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("Streamed 2 years ago"),
view_count: 1861814,
view_count: Some(1861814),
is_live: false,
is_short: false,
short_description: "Post Malone, Maroon 5, Adele, Taylor Swift, Ed Sheeran, Shawn Mendes, Charlie Puth Pop Hits 2020\nPost Malone, Maroon 5, Adele, Taylor Swift, Ed Sheeran, Shawn Mendes, Charlie Puth Pop Hits...",
is_upcoming: false,
short_description: Some("Post Malone, Maroon 5, Adele, Taylor Swift, Ed Sheeran, Shawn Mendes, Charlie Puth Pop Hits 2020\nPost Malone, Maroon 5, Adele, Taylor Swift, Ed Sheeran, Shawn Mendes, Charlie Puth Pop Hits..."),
),
SearchVideo(
VideoItem(
id: "mL2LBRM5GBI",
title: "Salahs 6-Minuten-Hattrick & Firmino-Gala: Rangers - FC Liverpool 1:7 | UEFA Champions League | DAZN",
length: Some(355),
@ -196,7 +201,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCB-GdMjyokO9lZkKU_oIK6g",
name: "DAZN UEFA Champions League",
avatar: [
@ -208,15 +213,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("2 days ago"),
view_count: 1471667,
view_count: Some(1471667),
is_live: false,
is_short: false,
short_description: "In der Liga läuft es für die Reds weiterhin nicht rund. Am vergangenen Spieltag gab es gegen Arsenal eine 2:3-Niederlage, am Sonntag trifft man auf Man City. Die Champions League soll für...",
is_upcoming: false,
short_description: Some("In der Liga läuft es für die Reds weiterhin nicht rund. Am vergangenen Spieltag gab es gegen Arsenal eine 2:3-Niederlage, am Sonntag trifft man auf Man City. Die Champions League soll für..."),
),
SearchVideo(
VideoItem(
id: "Ang18qz2IeQ",
title: "Satisfying Videos of Workers Doing Their Job Perfectly",
length: Some(1186),
@ -232,7 +238,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCYenDLnIHsoqQ6smwKXQ7Hg",
name: "#Mind Warehouse",
avatar: [
@ -244,15 +250,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("2 days ago"),
view_count: 173121,
view_count: Some(173121),
is_live: false,
is_short: false,
short_description: "TechZone ► https://goo.gl/Gj3wZs \n\n #incrediblemoments #mindwarehouse #IncredibleMoments #CaughtOnCamera #InterestingFacts \n\nYou can endlessly watch how others work, but in this selection,...",
is_upcoming: false,
short_description: Some("TechZone ► https://goo.gl/Gj3wZs \n\n #incrediblemoments #mindwarehouse #IncredibleMoments #CaughtOnCamera #InterestingFacts \n\nYou can endlessly watch how others work, but in this selection,..."),
),
SearchVideo(
VideoItem(
id: "fjHN4jsJnEU",
title: "I Made 200 Players Simulate Survival Island in Minecraft...",
length: Some(2361),
@ -268,7 +275,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCqt4mmAqLmH-AwXz31URJsw",
name: "Sword4000",
avatar: [
@ -280,15 +287,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("7 days ago"),
view_count: 751909,
view_count: Some(751909),
is_live: false,
is_short: false,
short_description: "200 Players Simulate Survival Island Civilizations in Minecraft...\n-------------------------------------------------------------------\nI invited 200 Players to a Survival Island and let them...",
is_upcoming: false,
short_description: Some("200 Players Simulate Survival Island Civilizations in Minecraft...\n-------------------------------------------------------------------\nI invited 200 Players to a Survival Island and let them..."),
),
SearchVideo(
VideoItem(
id: "FI1XrdBJIUI",
title: "Epic Construction Fails | Expensive Fails Compilation | FailArmy",
length: Some(631),
@ -304,7 +312,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCPDis9pjXuqyI7RYLJ-TTSA",
name: "FailArmy",
avatar: [
@ -316,15 +324,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("2 days ago"),
view_count: 2226471,
view_count: Some(2226471),
is_live: false,
is_short: false,
short_description: "I don\'t think so, Tim. ►►► Submit your videos for the chance to be featured 🔗 https://www.failarmy.com/pages/submit-video ▼ Follow us for more fails! https://linktr.ee/failarmy\n#fails...",
is_upcoming: false,
short_description: Some("I don\'t think so, Tim. ►►► Submit your videos for the chance to be featured 🔗 https://www.failarmy.com/pages/submit-video ▼ Follow us for more fails! https://linktr.ee/failarmy\n#fails..."),
),
SearchVideo(
VideoItem(
id: "MXdplejK8vU",
title: "Chilly autumn Jazz ☕ Smooth September Jazz & Bossa Nova for a great relaxing weekend",
length: Some(86403),
@ -340,7 +349,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCeGJ6v6KQt0s88hGKMfybuw",
name: "Cozy Jazz Music",
avatar: [
@ -352,15 +361,16 @@ Paginator(
],
verification: none,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("1 month ago"),
view_count: 148743,
view_count: Some(148743),
is_live: false,
is_short: false,
short_description: "Chilly autumn Jazz ☕ Smooth September Jazz & Bossa Nova for a great relaxing weekend\nhttps://youtu.be/MXdplejK8vU\n*******************************************\nSounds available on: Jazz Bossa...",
is_upcoming: false,
short_description: Some("Chilly autumn Jazz ☕ Smooth September Jazz & Bossa Nova for a great relaxing weekend\nhttps://youtu.be/MXdplejK8vU\n*******************************************\nSounds available on: Jazz Bossa..."),
),
SearchVideo(
VideoItem(
id: "Jri4_9vBFiQ",
title: "Top 100 Best Classic Rock Songs Of All Time 🔥 R.E.M, Queen, Metallica,Guns N Roses,Bon Jovi, U2,CCR",
length: None,
@ -376,7 +386,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCiIWdzEVNH8okhlapR9a-xA",
name: "Rock Music",
avatar: [
@ -388,15 +398,16 @@ Paginator(
],
verification: none,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: None,
view_count: 192,
is_live: false,
view_count: Some(192),
is_live: true,
is_short: false,
short_description: "Top 100 Best Classic Rock Songs Of All Time 🔥 R.E.M, Queen, Metallica,Guns N Roses,Bon Jovi, U2,CCR\nTop 100 Best Classic Rock Songs Of All Time 🔥 R.E.M, Queen, Metallica,Guns N...",
is_upcoming: false,
short_description: Some("Top 100 Best Classic Rock Songs Of All Time 🔥 R.E.M, Queen, Metallica,Guns N Roses,Bon Jovi, U2,CCR\nTop 100 Best Classic Rock Songs Of All Time 🔥 R.E.M, Queen, Metallica,Guns N..."),
),
SearchVideo(
VideoItem(
id: "ll4d5Lt-Ie8",
title: "Relaxing Music Healing Stress, Anxiety and Depressive States Heal Mind, Body and Soul | Sleep music",
length: Some(42896),
@ -412,7 +423,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCNS3dqFGBPhxHmOigehpBeg",
name: "Love YourSelf",
avatar: [
@ -424,15 +435,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("Streamed 5 months ago"),
view_count: 5363904,
view_count: Some(5363904),
is_live: false,
is_short: false,
short_description: "The study found that listening to relaxing music of the patient\'s choice resulted in \"significant pain relief and increased mobility.\" Researchers believe that music relieves pain because listening...",
is_upcoming: false,
short_description: Some("The study found that listening to relaxing music of the patient\'s choice resulted in \"significant pain relief and increased mobility.\" Researchers believe that music relieves pain because listening..."),
),
SearchVideo(
VideoItem(
id: "Dx2wbKLokuQ",
title: "W. Putin: Die Sehnsucht nach dem Imperium | Mit offenen Karten | ARTE",
length: Some(729),
@ -448,7 +460,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCLLibJTCy3sXjHLVaDimnpQ",
name: "ARTEde",
avatar: [
@ -460,15 +472,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("2 weeks ago"),
view_count: 539838,
view_count: Some(539838),
is_live: false,
is_short: false,
short_description: "Jede Woche untersucht „Mit offenen Karten“ die politischen Kräfteverhältnisse in der ganzen Welt anhand detaillierter geografischer Karten \n\nIm Februar 2022 rechtfertigte Wladimir Putin...",
is_upcoming: false,
short_description: Some("Jede Woche untersucht „Mit offenen Karten“ die politischen Kräfteverhältnisse in der ganzen Welt anhand detaillierter geografischer Karten \n\nIm Februar 2022 rechtfertigte Wladimir Putin..."),
),
SearchVideo(
VideoItem(
id: "jfKfPfyJRdk",
title: "lofi hip hop radio - beats to relax/study to",
length: None,
@ -484,7 +497,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCSJ4gkVC6NrvII8umztf0Ow",
name: "Lofi Girl",
avatar: [
@ -496,15 +509,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: None,
view_count: 21262,
is_live: false,
view_count: Some(21262),
is_live: true,
is_short: false,
short_description: "🤗 Thank you for listening, I hope you will have a good time here\n\n💽 | Get the latest vinyl (limited edition)\n→ https://vinyl-lofirecords.com/\n\n🎼 | Listen on Spotify, Apple music...",
is_upcoming: false,
short_description: Some("🤗 Thank you for listening, I hope you will have a good time here\n\n💽 | Get the latest vinyl (limited edition)\n→ https://vinyl-lofirecords.com/\n\n🎼 | Listen on Spotify, Apple music..."),
),
SearchVideo(
VideoItem(
id: "qmrzTUmZ4UU",
title: "850€ für den Verrat am System - UCS AT-AT LEGO® Star Wars 75313",
length: Some(2043),
@ -520,7 +534,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UC_EZd3lsmxudu3IQzpTzOgw",
name: "Held der Steine Inh. Thomas Panke",
avatar: [
@ -532,15 +546,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("6 days ago"),
view_count: 600150,
view_count: Some(600150),
is_live: false,
is_short: false,
short_description: "Star Wars - erschienen 2021 - 6749 Teile\n\nDieses Set bei Amazon*:\nhttps://amzn.to/3yu9dHX\n\nErwähnt im Video*:\nTassen https://bit.ly/HdSBausteinecke\nBig Boy https://bit.ly/BBLokBigBoy\nBurg...",
is_upcoming: false,
short_description: Some("Star Wars - erschienen 2021 - 6749 Teile\n\nDieses Set bei Amazon*:\nhttps://amzn.to/3yu9dHX\n\nErwähnt im Video*:\nTassen https://bit.ly/HdSBausteinecke\nBig Boy https://bit.ly/BBLokBigBoy\nBurg..."),
),
SearchVideo(
VideoItem(
id: "t0Q2otsqC4I",
title: "Tom & Jerry | Tom & Jerry in Full Screen | Classic Cartoon Compilation | WB Kids",
length: Some(1298),
@ -556,7 +571,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UC9trsD1jCTXXtN3xIOIU8gg",
name: "WB Kids",
avatar: [
@ -568,15 +583,16 @@ Paginator(
],
verification: verified,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("10 months ago"),
view_count: 252381571,
view_count: Some(252381571),
is_live: false,
is_short: false,
short_description: "Did you know that there are only 25 classic Tom & Jerry episodes that were displayed in a widescreen CinemaScope from the 1950s? Enjoy a compilation filled with some of the best moments from...",
is_upcoming: false,
short_description: Some("Did you know that there are only 25 classic Tom & Jerry episodes that were displayed in a widescreen CinemaScope from the 1950s? Enjoy a compilation filled with some of the best moments from..."),
),
SearchVideo(
VideoItem(
id: "zE-a5eqvlv8",
title: "Dua Lipa, Coldplay, Martin Garrix & Kygo, The Chainsmokers Style - Feeling Me",
length: None,
@ -592,7 +608,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCX-USfenzQlhrEJR1zD5IYw",
name: "Deep Mood.",
avatar: [
@ -604,15 +620,16 @@ Paginator(
],
verification: none,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: None,
view_count: 955,
is_live: false,
view_count: Some(955),
is_live: true,
is_short: false,
short_description: "#Summermix #DeepHouse #DeepHouseSummerMix\nDua Lipa, Coldplay, Martin Garrix & Kygo, The Chainsmokers Style - Feeling Me\n\n🎵 All songs in this spotify playlist: https://spoti.fi/2TJ4Dyj\nSubmit...",
is_upcoming: false,
short_description: Some("#Summermix #DeepHouse #DeepHouseSummerMix\nDua Lipa, Coldplay, Martin Garrix & Kygo, The Chainsmokers Style - Feeling Me\n\n🎵 All songs in this spotify playlist: https://spoti.fi/2TJ4Dyj\nSubmit..."),
),
SearchVideo(
VideoItem(
id: "HxCcKzRAGWk",
title: "(Music for Man ) Relaxing Whiskey Blues Music - Modern Electric Guitar Blues - JAZZ & BLUES",
length: Some(42899),
@ -628,7 +645,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCGr-rTYtP1m-r_-ncspdVQQ",
name: "JAZZ & BLUES",
avatar: [
@ -640,15 +657,16 @@ Paginator(
],
verification: none,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("Streamed 3 months ago"),
view_count: 3156236,
view_count: Some(3156236),
is_live: false,
is_short: false,
short_description: "-----------------------------------------------------------------------------------\n✔Thanks for watching! Have a nice day!\n✔Don\'t forget LIKE - SHARE - COMMENT\n#bluesmusic#slowblues#bluesrock...",
is_upcoming: false,
short_description: Some("-----------------------------------------------------------------------------------\n✔Thanks for watching! Have a nice day!\n✔Don\'t forget LIKE - SHARE - COMMENT\n#bluesmusic#slowblues#bluesrock..."),
),
SearchVideo(
VideoItem(
id: "HlHYOdZePSE",
title: "Healing Music for Anxiety Disorders, Fears, Depression and Eliminate Negative Thoughts",
length: None,
@ -664,7 +682,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCqNYK5QArQRZSIR8v6_FCfA",
name: "Tranquil Music",
avatar: [
@ -676,15 +694,16 @@ Paginator(
],
verification: none,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: None,
view_count: 1585,
is_live: false,
view_count: Some(1585),
is_live: true,
is_short: false,
short_description: "Healing Music for Anxiety Disorders, Fears, Depression and Eliminate Negative Thoughts\n#HealingMusic #RelaxingMusic #TranquilMusic\n__________________________________\nMusic for:\nChakra healing....",
is_upcoming: false,
short_description: Some("Healing Music for Anxiety Disorders, Fears, Depression and Eliminate Negative Thoughts\n#HealingMusic #RelaxingMusic #TranquilMusic\n__________________________________\nMusic for:\nChakra healing...."),
),
SearchVideo(
VideoItem(
id: "CJ2AH3LJeic",
title: "Coldplay Greatest Hits Full Album 2022 New Songs of Coldplay 2022",
length: Some(7781),
@ -700,7 +719,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCdK2lzwelugXGhR9SCWuEew",
name: "PLAY MUSIC",
avatar: [
@ -712,15 +731,16 @@ Paginator(
],
verification: none,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("7 months ago"),
view_count: 5595965,
view_count: Some(5595965),
is_live: false,
is_short: false,
short_description: "▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\nSubscribe channel for more videos:\n🔔Subscribe: https://bit.ly/2UbIZFv\n⚡Facebook: https://bitly.com.vn/gXDsC...",
is_upcoming: false,
short_description: Some("▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\nSubscribe channel for more videos:\n🔔Subscribe: https://bit.ly/2UbIZFv\n⚡Facebook: https://bitly.com.vn/gXDsC..."),
),
SearchVideo(
VideoItem(
id: "KJwzKxQ81iA",
title: "Handmade Candy Making Collection / 수제 사탕 만들기 모음 / Korean Candy Store",
length: Some(3152),
@ -736,7 +756,7 @@ Paginator(
height: 404,
),
],
channel: ChannelTag(
channel: Some(ChannelTag(
id: "UCdGwDjTgbSwQDZ8dYOdrplg",
name: "Soon Films 순필름",
avatar: [
@ -748,13 +768,14 @@ Paginator(
],
verification: none,
subscriber_count: None,
),
)),
publish_date: "[date]",
publish_date_txt: Some("1 month ago"),
view_count: 3127238,
view_count: Some(3127238),
is_live: false,
is_short: false,
short_description: "00:00 Handmade Candy Making\n13:43 Delicate Handmade Candy Making\n28:33 Rainbow Lollipop Handmade Candy Making\n39:10 Cute Handmade Candy Making",
is_upcoming: false,
short_description: Some("00:00 Handmade Candy Making\n13:43 Delicate Handmade Candy Making\n28:33 Rainbow Lollipop Handmade Candy Making\n39:10 Cute Handmade Candy Making"),
),
],
ctoken: Some("4qmFsgKbAxIPRkV3aGF0X3RvX3dhdGNoGuoCQ0JoNmlBSk5aMjlKYjB0NmVtOWlTR3hxVFRSdlYyMHdTMkYzYjFwbFdGSm1ZMGRHYmxwV09YcGliVVozWXpKb2RtUkdPWGxhVjJSd1lqSTFhR0pDU1daWFZFSXhUbFpuZDFSV09YSldNRlp0WkRCT1JWTlZPV3BsU0U1WFZHNWtiRXhWY0ZSa1ZrSlRXbmh2ZEVGQlFteGlaMEZDVmxaTlFVRlZVa1pCUVVWQlVtdFdNMkZIUmpCWU0xSjJXRE5rYUdSSFRtOUJRVVZCUVZGRlFVRkJSVUZCVVVGQlFWRkZRVmxyUlVsQlFrbFVZMGRHYmxwV09YcGliVVozWXpKb2RtUkdPVEJpTW5Sc1ltaHZWRU5MVDNJeFpuSjROR1p2UTBaU1YwSm1RVzlrVkZWSlN6RnBTVlJEUzA5eU1XWnllRFJtYjBOR1VsZENaa0Z2WkZSVlNVc3hkbkZqZURjd1NrRm5aMW8lM0SaAhpicm93c2UtZmVlZEZFd2hhdF90b193YXRjaA%3D%3D"),

View file

@ -1,18 +1,15 @@
use crate::{
error::{Error, ExtractionError},
model::{Paginator, SearchVideo},
model::{Paginator, VideoItem},
param::Language,
serializer::MapResult,
util::TryRemove,
};
use super::{
response::{self, TryFromWLang},
ClientType, MapResponse, QBrowse, QContinuation, RustyPipeQuery,
};
use super::{response, ClientType, MapResponse, QBrowse, RustyPipeQuery};
impl RustyPipeQuery {
pub async fn startpage(self) -> Result<Paginator<SearchVideo>, Error> {
pub async fn startpage(self) -> Result<Paginator<VideoItem>, Error> {
let context = self.get_context(ClientType::Desktop, true).await;
let request_body = QBrowse {
context,
@ -29,33 +26,7 @@ impl RustyPipeQuery {
.await
}
pub async fn startpage_continuation(
self,
ctoken: &str,
visitor_data: &str,
) -> Result<Paginator<SearchVideo>, Error> {
let mut context = self.get_context(ClientType::Desktop, true).await;
context.client.visitor_data = Some(visitor_data.to_owned());
let request_body = QContinuation {
context,
continuation: ctoken,
};
self.execute_request::<response::StartpageCont, _, _>(
ClientType::Desktop,
"startpage_continuation",
ctoken,
"browse",
&request_body,
)
.await
.map(|res| Paginator {
visitor_data: Some(visitor_data.to_owned()),
..res
})
}
pub async fn trending(self) -> Result<Vec<SearchVideo>, Error> {
pub async fn trending(self) -> Result<Vec<VideoItem>, Error> {
let context = self.get_context(ClientType::Desktop, true).await;
let request_body = QBrowse {
context,
@ -73,20 +44,20 @@ impl RustyPipeQuery {
}
}
impl MapResponse<Paginator<SearchVideo>> for response::Startpage {
impl MapResponse<Paginator<VideoItem>> for response::Startpage {
fn map_response(
self,
_id: &str,
lang: crate::param::Language,
_deobf: Option<&crate::deobfuscate::Deobfuscator>,
) -> Result<MapResult<Paginator<SearchVideo>>, ExtractionError> {
) -> Result<MapResult<Paginator<VideoItem>>, ExtractionError> {
let mut contents = self.contents.two_column_browse_results_renderer.tabs;
let grid = contents
.try_swap_remove(0)
.ok_or_else(|| ExtractionError::InvalidData("no contents".into()))?
.tab_renderer
.content
.rich_grid_renderer
.section_list_renderer
.contents;
Ok(map_startpage_videos(
@ -97,33 +68,15 @@ impl MapResponse<Paginator<SearchVideo>> for response::Startpage {
}
}
impl MapResponse<Paginator<SearchVideo>> for response::StartpageCont {
impl MapResponse<Vec<VideoItem>> for response::Trending {
fn map_response(
self,
_id: &str,
lang: crate::param::Language,
_deobf: Option<&crate::deobfuscate::Deobfuscator>,
) -> Result<MapResult<Paginator<SearchVideo>>, ExtractionError> {
let mut received_actions = self.on_response_received_actions;
let items = received_actions
.try_swap_remove(0)
.ok_or_else(|| ExtractionError::InvalidData("no contents".into()))?
.append_continuation_items_action
.continuation_items;
Ok(map_startpage_videos(items, lang, None))
}
}
impl MapResponse<Vec<SearchVideo>> for response::Trending {
fn map_response(
self,
_id: &str,
lang: crate::param::Language,
_deobf: Option<&crate::deobfuscate::Deobfuscator>,
) -> Result<MapResult<Vec<SearchVideo>>, ExtractionError> {
) -> Result<MapResult<Vec<VideoItem>>, ExtractionError> {
let mut contents = self.contents.two_column_browse_results_renderer.tabs;
let sections = contents
let items = contents
.try_swap_remove(0)
.ok_or_else(|| ExtractionError::InvalidData("no contents".into()))?
.tab_renderer
@ -131,76 +84,27 @@ impl MapResponse<Vec<SearchVideo>> for response::Trending {
.section_list_renderer
.contents;
let mut items = Vec::new();
let mut warnings = Vec::new();
let mut mapper = response::YouTubeListMapper::<VideoItem>::new(lang);
mapper.map_response(items);
for mut section in sections {
let shelf = section
.item_section_renderer
.contents
.try_swap_remove(0)
.and_then(|shelf| {
shelf
.shelf_renderer
.content
.expanded_shelf_contents_renderer
});
if let Some(mut shelf) = shelf {
warnings.append(&mut shelf.items.warnings);
for item in shelf.items.c {
if let response::trends::TrendingListItem::VideoRenderer(video) = item {
match SearchVideo::from_w_lang(video, lang) {
Ok(video) => {
items.push(video);
}
Err(e) => {
warnings.push(e.to_string());
}
}
}
}
}
}
Ok(MapResult { c: items, warnings })
Ok(MapResult {
c: mapper.items,
warnings: mapper.warnings,
})
}
}
fn map_startpage_videos(
videos: MapResult<Vec<response::VideoListItem>>,
videos: MapResult<Vec<response::YouTubeListItem>>,
lang: Language,
visitor_data: Option<String>,
) -> MapResult<Paginator<SearchVideo>> {
let mut warnings = videos.warnings;
let mut ctoken = None;
let items = videos
.c
.into_iter()
.filter_map(|item| match item {
response::VideoListItem::RichItemRenderer {
content: response::RichItem::VideoRenderer(video),
} => match SearchVideo::from_w_lang(video, lang) {
Ok(video) => Some(video),
Err(e) => {
warnings.push(e.to_string());
None
}
},
response::VideoListItem::ContinuationItemRenderer {
continuation_endpoint,
} => {
ctoken = Some(continuation_endpoint.continuation_command.token);
None
}
_ => None,
})
.collect();
) -> MapResult<Paginator<VideoItem>> {
let mut mapper = response::YouTubeListMapper::<VideoItem>::new(lang);
mapper.map_response(videos);
MapResult {
c: Paginator::new_with_vdata(None, items, ctoken, visitor_data),
warnings,
c: Paginator::new_with_vdata(None, mapper.items, mapper.ctoken, visitor_data),
warnings: mapper.warnings,
}
}
@ -210,7 +114,7 @@ mod tests {
use crate::{
client::{response, MapResponse},
model::{Paginator, SearchVideo},
model::{Paginator, VideoItem},
param::Language,
serializer::MapResult,
};
@ -223,7 +127,7 @@ mod tests {
let startpage: response::Startpage =
serde_json::from_reader(BufReader::new(json_file)).unwrap();
let map_res: MapResult<Paginator<SearchVideo>> =
let map_res: MapResult<Paginator<VideoItem>> =
startpage.map_response("", Language::En, None).unwrap();
assert!(
@ -237,28 +141,6 @@ mod tests {
});
}
#[test]
fn map_startpage_cont() {
let filename = "testfiles/trends/startpage_cont.json";
let json_path = Path::new(&filename);
let json_file = File::open(json_path).unwrap();
let startpage: response::StartpageCont =
serde_json::from_reader(BufReader::new(json_file)).unwrap();
let map_res: MapResult<Paginator<SearchVideo>> =
startpage.map_response("", Language::En, None).unwrap();
assert!(
map_res.warnings.is_empty(),
"deserialization/mapping warnings: {:?}",
map_res.warnings
);
insta::assert_ron_snapshot!("map_startpage_cont", map_res.c, {
".items[].publish_date" => "[date]",
});
}
#[test]
fn map_trending() {
let filename = "testfiles/trends/trending.json";
@ -267,7 +149,7 @@ mod tests {
let startpage: response::Trending =
serde_json::from_reader(BufReader::new(json_file)).unwrap();
let map_res: MapResult<Vec<SearchVideo>> =
let map_res: MapResult<Vec<VideoItem>> =
startpage.map_response("", Language::En, None).unwrap();
assert!(