Compare commits
No commits in common. "0bf60774040fea8f45e8b3203f684ea05a26f0cc" and "abb783219aba4b492c1dff03c2148acf1f51a55d" have entirely different histories.
0bf6077404
...
abb783219a
44 changed files with 2875 additions and 899 deletions
|
@ -10,7 +10,7 @@ keywords.workspace = true
|
||||||
categories.workspace = true
|
categories.workspace = true
|
||||||
description = "Client for the public YouTube / YouTube Music API (Innertube), inspired by NewPipe"
|
description = "Client for the public YouTube / YouTube Music API (Innertube), inspired by NewPipe"
|
||||||
|
|
||||||
include = ["/src", "README.md", "CHANGELOG.md", "LICENSE", "!snapshots"]
|
include = ["/src", "README.md", "LICENSE", "!snapshots"]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [".", "codegen", "downloader", "cli"]
|
members = [".", "codegen", "downloader", "cli"]
|
||||||
|
|
2
Justfile
2
Justfile
|
@ -1,6 +1,6 @@
|
||||||
test:
|
test:
|
||||||
# cargo test --features=rss
|
# cargo test --features=rss
|
||||||
cargo nextest run --workspace --features=rss --no-fail-fast --failure-output final --retries 1
|
cargo nextest run --features=rss --no-fail-fast --failure-output final --retries 1
|
||||||
|
|
||||||
unittest:
|
unittest:
|
||||||
cargo nextest run --features=rss --no-fail-fast --failure-output final --lib
|
cargo nextest run --features=rss --no-fail-fast --failure-output final --lib
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
# ![RustyPipe](https://code.thetadev.de/ThetaDev/rustypipe/raw/branch/main/notes/logo.svg)
|
# ![RustyPipe](https://code.thetadev.de/ThetaDev/rustypipe/raw/branch/main/notes/logo.svg)
|
||||||
|
|
||||||
[![Current crates.io version](https://img.shields.io/crates/v/rustypipe.svg)](https://crates.io/crates/rustypipe)
|
[![Current crates.io version](https://img.shields.io/crates/v/smartcrop2.svg)](https://crates.io/crates/smartcrop2)
|
||||||
[![License](https://img.shields.io/badge/License-GPL--3-blue.svg?style=flat)](http://opensource.org/licenses/MIT)
|
[![License](https://img.shields.io/badge/License-GPL--3-blue.svg?style=flat)](http://opensource.org/licenses/MIT)
|
||||||
[![CI status](https://code.thetadev.de/ThetaDev/rustypipe/actions/workflows/ci.yaml/badge.svg?style=flat&label=CI)](https://code.thetadev.de/ThetaDev/rustypipe/actions/?workflow=ci.yaml)
|
[![CI status](https://code.thetadev.de/ThetaDev/rustypipe/actions/workflows/ci.yaml/badge.svg?style=flat&label=CI)](https://code.thetadev.de/ThetaDev/rustypipe/actions/?workflow=ci.yaml)
|
||||||
|
|
||||||
RustyPipe is a fully featured Rust client for the public YouTube / YouTube Music API
|
Rust client for the public YouTube / YouTube Music API (Innertube), inspired by
|
||||||
(Innertube), inspired by [NewPipe](https://github.com/TeamNewPipe/NewPipeExtractor).
|
[NewPipe](https://github.com/TeamNewPipe/NewPipeExtractor).
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
|
|
@ -1,94 +1 @@
|
||||||
# ![RustyPipe](https://code.thetadev.de/ThetaDev/rustypipe/raw/branch/main/notes/logo.svg) CLI
|
# RustyPipe CLI
|
||||||
|
|
||||||
[![Current crates.io version](https://img.shields.io/crates/v/rustypipe-cli.svg)](https://crates.io/crates/rustypipe-cli)
|
|
||||||
[![License](https://img.shields.io/badge/License-GPL--3-blue.svg?style=flat)](http://opensource.org/licenses/MIT)
|
|
||||||
[![CI status](https://code.thetadev.de/ThetaDev/rustypipe/actions/workflows/ci.yaml/badge.svg?style=flat&label=CI)](https://code.thetadev.de/ThetaDev/rustypipe/actions/?workflow=ci.yaml)
|
|
||||||
|
|
||||||
The RustyPipe CLI is a powerful YouTube client for the command line. It allows you to
|
|
||||||
access most of the features of the RustyPipe crate: getting data from YouTube and
|
|
||||||
downloading videos.
|
|
||||||
|
|
||||||
The following subcommands are included:
|
|
||||||
|
|
||||||
## `get`: Fetch information
|
|
||||||
|
|
||||||
You can call the get command with any YouTube entity ID or URL and RustyPipe will fetch
|
|
||||||
the associated metadata. It can fetch channels, playlists, albums and videos.
|
|
||||||
|
|
||||||
**Usage:** `rustypipe get UC2TXq_t06Hjdr2g_KdKpHQg`
|
|
||||||
|
|
||||||
- `-l`, `--limit` Limit the number of list items to fetch
|
|
||||||
- ``-t, --tab` Channel tab (options: **videos**, shorts, live, playlists, info)
|
|
||||||
- `-m, --music` Use the YouTube Music API
|
|
||||||
- `--rss`Fetch the RSS feed of a channel
|
|
||||||
- `--comments` Get comments (options: top, latest)
|
|
||||||
- `--lyrics` Get the lyrics for YTM tracks
|
|
||||||
- `--player` Get the player data instead of the video details when fetching videos
|
|
||||||
- `-c, --client-type` YT clients used to fetch player data (options: desktop, tv,
|
|
||||||
tv-embed, android, ios; if multiple clients are specified, they are attempted in
|
|
||||||
order)
|
|
||||||
|
|
||||||
## `search`: Search YouTube
|
|
||||||
|
|
||||||
With the search command you can search the entire YouTube platform or individual
|
|
||||||
channels. YouTube Music search is also supported.
|
|
||||||
|
|
||||||
Note that search filters are only supported when searching YouTube. They have no effect
|
|
||||||
when searching YTM or individual channels.
|
|
||||||
|
|
||||||
**Usage:** `rustypipe search "query"`
|
|
||||||
|
|
||||||
### Options
|
|
||||||
|
|
||||||
- `-l`, `--limit` Limit the number of list items to fetch
|
|
||||||
|
|
||||||
- `--item-type` Filter results by item type
|
|
||||||
- `--length` Filter results by video length
|
|
||||||
- `--date` Filter results by upload date (options: hour, day, week, month, year)
|
|
||||||
- `--order` Sort search results (options: rating, date, views)
|
|
||||||
- `--channel` Channel ID for searching channel videos
|
|
||||||
- `-m, --music` Search YouTube Music in the given category (options: all, tracks,
|
|
||||||
videos, artists, albums, playlists-ytm, playlists-community)
|
|
||||||
|
|
||||||
## `dl`: Download videos
|
|
||||||
|
|
||||||
The downloader can download individual videos, playlists, albums and channels. Multiple
|
|
||||||
videos can be downloaded in parallel for improved performance.
|
|
||||||
|
|
||||||
**Usage:** `rustypipe dl eRsGyueVLvQ`
|
|
||||||
|
|
||||||
### Options
|
|
||||||
|
|
||||||
- `-o`, `--output` Download to the given directory
|
|
||||||
- `--output-file` Download to the given file
|
|
||||||
- `--template` Download to a path determined by a template
|
|
||||||
|
|
||||||
- `-r`, `--resolution` Video resolution (e.g. 720, 1080). Set to 0 for audio-only
|
|
||||||
- `-a`, `--audio` Download only the audio track and write track metadata + album cover
|
|
||||||
- `-p`, `--parallel` Number of videos downloaded in parallel (default: 8)
|
|
||||||
- `-m, --music` Use YouTube Music for downloading playlists
|
|
||||||
- `-l`, `--limit` Limit the number of videos to download (default: 1000)
|
|
||||||
- `-c`, `--client-type` YT clients used to fetch player data (options: desktop, tv,
|
|
||||||
tv-embed, android, ios; if multiple clients are specified, they are attempted in
|
|
||||||
order)
|
|
||||||
- `--pot` token to circumvent bot detection
|
|
||||||
|
|
||||||
## `vdata`: Get visitor data
|
|
||||||
|
|
||||||
You can use the vdata command to get a new visitor data cookie. This feature may come in
|
|
||||||
handy for testing and reproducing A/B tests.
|
|
||||||
|
|
||||||
## Global options
|
|
||||||
|
|
||||||
- **Proxy:** RustyPipe respects the environment variables `HTTP_PROXY`, `HTTPS_PROXY`
|
|
||||||
and `ALL_PROXY`
|
|
||||||
- **Logging:** You can change the log level with the `RUST_LOG` environment variable, it
|
|
||||||
is set to `info` by default
|
|
||||||
- **Visitor data:** A custom visitor data cookie can be used with the `--vdata` flag
|
|
||||||
- `--report`
|
|
||||||
|
|
||||||
### Output format
|
|
||||||
|
|
||||||
By default, the CLI outputs YouTube data in a human-readable text format. If you want to
|
|
||||||
store the data or process it with a script, you should choose a machine readable output
|
|
||||||
format. You can choose both JSON and YAML with the `-f, --format` flag.
|
|
||||||
|
|
725
cli/src/main.rs
725
cli/src/main.rs
|
@ -19,7 +19,6 @@ use rustypipe::{
|
||||||
Verification, YouTubeItem,
|
Verification, YouTubeItem,
|
||||||
},
|
},
|
||||||
param::{search_filter, ChannelVideoTab, Country, Language, StreamFilter},
|
param::{search_filter, ChannelVideoTab, Country, Language, StreamFilter},
|
||||||
report::FileReporter,
|
|
||||||
};
|
};
|
||||||
use rustypipe_downloader::{
|
use rustypipe_downloader::{
|
||||||
DownloadError, DownloadQuery, DownloadVideo, Downloader, DownloaderBuilder,
|
DownloadError, DownloadQuery, DownloadVideo, Downloader, DownloaderBuilder,
|
||||||
|
@ -50,13 +49,10 @@ struct Cli {
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[group(multiple = false)]
|
#[group(multiple = false)]
|
||||||
struct DownloadTarget {
|
struct DownloadTarget {
|
||||||
/// Download to the given directory
|
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
output: Option<PathBuf>,
|
output: Option<PathBuf>,
|
||||||
/// Download to the given file
|
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
output_file: Option<PathBuf>,
|
output_file: Option<PathBuf>,
|
||||||
/// Download to a path determined by a template
|
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
template: Option<String>,
|
template: Option<String>,
|
||||||
}
|
}
|
||||||
|
@ -97,7 +93,7 @@ enum Commands {
|
||||||
/// Video resolution (e.g. 720, 1080). Set to 0 for audio-only.
|
/// Video resolution (e.g. 720, 1080). Set to 0 for audio-only.
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
resolution: Option<u32>,
|
resolution: Option<u32>,
|
||||||
/// Download only the audio track and write track information
|
/// Download only the audio track
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
audio: bool,
|
audio: bool,
|
||||||
/// Number of videos downloaded in parallel
|
/// Number of videos downloaded in parallel
|
||||||
|
@ -121,21 +117,24 @@ enum Commands {
|
||||||
/// ID or URL
|
/// ID or URL
|
||||||
id: String,
|
id: String,
|
||||||
/// Output format
|
/// Output format
|
||||||
#[clap(short, long, value_parser)]
|
#[clap(long, value_parser, default_value = "json")]
|
||||||
format: Option<Format>,
|
format: Format,
|
||||||
/// Pretty-print output
|
/// Pretty-print output
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pretty: bool,
|
pretty: bool,
|
||||||
|
/// Output as text
|
||||||
|
#[clap(short, long)]
|
||||||
|
txt: bool,
|
||||||
/// Limit the number of items to fetch
|
/// Limit the number of items to fetch
|
||||||
#[clap(short, long, default_value_t = 20)]
|
#[clap(short, long, default_value_t = 20)]
|
||||||
limit: usize,
|
limit: usize,
|
||||||
/// Channel tab
|
/// Channel tab
|
||||||
#[clap(short, long, default_value = "videos")]
|
#[clap(long, default_value = "videos")]
|
||||||
tab: ChannelTab,
|
tab: ChannelTab,
|
||||||
/// Use YouTube Music
|
/// Use YouTube Music
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
music: bool,
|
music: bool,
|
||||||
/// Fetch the RSS feed of a channel
|
/// Use the RSS feed of a channel
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
rss: bool,
|
rss: bool,
|
||||||
/// Get comments
|
/// Get comments
|
||||||
|
@ -149,18 +148,21 @@ enum Commands {
|
||||||
player: bool,
|
player: bool,
|
||||||
/// YT Client used to fetch player data
|
/// YT Client used to fetch player data
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
client_type: Option<Vec<ClientTypeArg>>,
|
client_type: Option<ClientTypeArg>,
|
||||||
},
|
},
|
||||||
/// Search YouTube
|
/// Search YouTube
|
||||||
Search {
|
Search {
|
||||||
/// Search query
|
/// Search query
|
||||||
query: String,
|
query: String,
|
||||||
/// Output format
|
/// Output format
|
||||||
#[clap(short, long, value_parser)]
|
#[clap(long, value_parser, default_value = "json")]
|
||||||
format: Option<Format>,
|
format: Format,
|
||||||
/// Pretty-print output
|
/// Pretty-print output
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pretty: bool,
|
pretty: bool,
|
||||||
|
/// Output as text
|
||||||
|
#[clap(short, long)]
|
||||||
|
txt: bool,
|
||||||
/// Limit the number of items to fetch
|
/// Limit the number of items to fetch
|
||||||
#[clap(short, long, default_value_t = 20)]
|
#[clap(short, long, default_value_t = 20)]
|
||||||
limit: usize,
|
limit: usize,
|
||||||
|
@ -179,7 +181,7 @@ enum Commands {
|
||||||
/// Channel ID for searching channel videos
|
/// Channel ID for searching channel videos
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
channel: Option<String>,
|
channel: Option<String>,
|
||||||
/// Search YouTube Music in the given category
|
/// YouTube Music search filter
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
music: Option<MusicSearchCategory>,
|
music: Option<MusicSearchCategory>,
|
||||||
},
|
},
|
||||||
|
@ -187,9 +189,8 @@ enum Commands {
|
||||||
Vdata,
|
Vdata,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Copy, Clone, ValueEnum)]
|
#[derive(Copy, Clone, ValueEnum)]
|
||||||
enum Format {
|
enum Format {
|
||||||
#[default]
|
|
||||||
Json,
|
Json,
|
||||||
Yaml,
|
Yaml,
|
||||||
}
|
}
|
||||||
|
@ -387,17 +388,17 @@ fn print_duration(duration: Option<u32>) {
|
||||||
|
|
||||||
fn print_music_search<T: Serialize + YtEntity>(
|
fn print_music_search<T: Serialize + YtEntity>(
|
||||||
data: &MusicSearchResult<T>,
|
data: &MusicSearchResult<T>,
|
||||||
format: Option<Format>,
|
format: Format,
|
||||||
pretty: bool,
|
pretty: bool,
|
||||||
|
txt: bool,
|
||||||
) {
|
) {
|
||||||
match format {
|
if txt {
|
||||||
Some(format) => print_data(data, format, pretty),
|
if let Some(corr) = &data.corrected_query {
|
||||||
None => {
|
anstream::println!("Did you mean `{}`?", corr.magenta());
|
||||||
if let Some(corr) = &data.corrected_query {
|
|
||||||
anstream::println!("Did you mean `{}`?", corr.magenta());
|
|
||||||
}
|
|
||||||
print_entities(&data.items.items);
|
|
||||||
}
|
}
|
||||||
|
print_entities(&data.items.items);
|
||||||
|
} else {
|
||||||
|
print_data(data, format, pretty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,7 +406,7 @@ fn print_description(desc: Option<String>) {
|
||||||
if let Some(desc) = desc {
|
if let Some(desc) = desc {
|
||||||
if !desc.is_empty() {
|
if !desc.is_empty() {
|
||||||
print_h2("Description");
|
print_h2("Description");
|
||||||
println!("{}", desc.trim());
|
println!("{}", desc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -536,18 +537,16 @@ async fn run() -> anyhow::Result<()> {
|
||||||
.with_writer(ProgWriter(multi.clone()))
|
.with_writer(ProgWriter(multi.clone()))
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
let mut storage_dir = dirs::data_dir().expect("no data dir");
|
|
||||||
storage_dir.push("rustypipe");
|
|
||||||
std::fs::create_dir_all(&storage_dir).expect("could not create data dir");
|
|
||||||
|
|
||||||
let mut rp = RustyPipe::builder()
|
let mut rp = RustyPipe::builder()
|
||||||
.storage_dir(storage_dir)
|
|
||||||
.visitor_data_opt(cli.vdata)
|
.visitor_data_opt(cli.vdata)
|
||||||
.timeout(Duration::from_secs(15));
|
.timeout(Duration::from_secs(15));
|
||||||
if cli.report {
|
if cli.report {
|
||||||
rp = rp
|
rp = rp.report();
|
||||||
.report()
|
} else {
|
||||||
.reporter(Box::new(FileReporter::new("rustypipe_reports")));
|
let mut storage_dir = dirs::data_dir().expect("no data dir");
|
||||||
|
storage_dir.push("rustypipe");
|
||||||
|
std::fs::create_dir_all(&storage_dir).expect("could not create data dir");
|
||||||
|
rp = rp.storage_dir(storage_dir);
|
||||||
}
|
}
|
||||||
if let Some(lang) = cli.lang {
|
if let Some(lang) = cli.lang {
|
||||||
rp = rp.lang(Language::from_str(&lang.to_ascii_lowercase()).expect("invalid language"));
|
rp = rp.lang(Language::from_str(&lang.to_ascii_lowercase()).expect("invalid language"));
|
||||||
|
@ -652,6 +651,7 @@ async fn run() -> anyhow::Result<()> {
|
||||||
Commands::Get {
|
Commands::Get {
|
||||||
id,
|
id,
|
||||||
format,
|
format,
|
||||||
|
txt,
|
||||||
pretty,
|
pretty,
|
||||||
limit,
|
limit,
|
||||||
tab,
|
tab,
|
||||||
|
@ -671,60 +671,56 @@ async fn run() -> anyhow::Result<()> {
|
||||||
match details.lyrics_id {
|
match details.lyrics_id {
|
||||||
Some(lyrics_id) => {
|
Some(lyrics_id) => {
|
||||||
let lyrics = rp.query().music_lyrics(lyrics_id).await?;
|
let lyrics = rp.query().music_lyrics(lyrics_id).await?;
|
||||||
match format {
|
if txt {
|
||||||
Some(format) => print_data(&lyrics, format, pretty),
|
println!("{}\n\n{}", lyrics.body, lyrics.footer.blue());
|
||||||
None => println!("{}\n\n{}", lyrics.body, lyrics.footer.blue()),
|
} else {
|
||||||
|
print_data(&lyrics, format, pretty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => eprintln!("no lyrics found"),
|
None => eprintln!("no lyrics found"),
|
||||||
}
|
}
|
||||||
} else if music {
|
} else if music {
|
||||||
let details = rp.query().music_details(&id).await?;
|
let details = rp.query().music_details(&id).await?;
|
||||||
match format {
|
if txt {
|
||||||
Some(format) => print_data(&details, format, pretty),
|
if details.track.is_video {
|
||||||
None => {
|
anstream::println!("{}", "[MV]".on_green().black());
|
||||||
if details.track.is_video {
|
} else {
|
||||||
anstream::println!("{}", "[MV]".on_green().black());
|
anstream::println!("{}", "[Track]".on_green().black());
|
||||||
} else {
|
|
||||||
anstream::println!("{}", "[Track]".on_green().black());
|
|
||||||
}
|
|
||||||
anstream::print!(
|
|
||||||
"{} [{}]",
|
|
||||||
details.track.name.green().bold(),
|
|
||||||
details.track.id
|
|
||||||
);
|
|
||||||
print_duration(details.track.duration);
|
|
||||||
println!();
|
|
||||||
print_artists(&details.track.artists);
|
|
||||||
println!();
|
|
||||||
if !details.track.is_video {
|
|
||||||
anstream::println!(
|
|
||||||
"{} {}",
|
|
||||||
"Album:".blue(),
|
|
||||||
details
|
|
||||||
.track
|
|
||||||
.album
|
|
||||||
.as_ref()
|
|
||||||
.map(|b| b.id.as_str())
|
|
||||||
.unwrap_or("None")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if let Some(view_count) = details.track.view_count {
|
|
||||||
anstream::println!("{} {}", "Views:".blue(), view_count);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
anstream::print!(
|
||||||
|
"{} [{}]",
|
||||||
|
details.track.name.green().bold(),
|
||||||
|
details.track.id
|
||||||
|
);
|
||||||
|
print_duration(details.track.duration);
|
||||||
|
println!();
|
||||||
|
print_artists(&details.track.artists);
|
||||||
|
println!();
|
||||||
|
if !details.track.is_video {
|
||||||
|
anstream::println!(
|
||||||
|
"{} {}",
|
||||||
|
"Album:".blue(),
|
||||||
|
details
|
||||||
|
.track
|
||||||
|
.album
|
||||||
|
.as_ref()
|
||||||
|
.map(|b| b.id.as_str())
|
||||||
|
.unwrap_or("None")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if let Some(view_count) = details.track.view_count {
|
||||||
|
anstream::println!("{} {}", "Views:".blue(), view_count);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print_data(&details, format, pretty);
|
||||||
}
|
}
|
||||||
} else if player {
|
} else if player {
|
||||||
let player = if let Some(client_types) = client_type {
|
let player = if let Some(client_type) = client_type {
|
||||||
let cts = client_types
|
rp.query().player_from_client(&id, client_type.into()).await
|
||||||
.into_iter()
|
|
||||||
.map(ClientType::from)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
rp.query().player_from_clients(&id, &cts).await
|
|
||||||
} else {
|
} else {
|
||||||
rp.query().player(&id).await
|
rp.query().player(&id).await
|
||||||
}?;
|
}?;
|
||||||
print_data(&player, format.unwrap_or_default(), pretty);
|
print_data(&player, format, pretty);
|
||||||
} else {
|
} else {
|
||||||
let mut details = rp.query().video_details(&id).await?;
|
let mut details = rp.query().video_details(&id).await?;
|
||||||
|
|
||||||
|
@ -741,160 +737,153 @@ async fn run() -> anyhow::Result<()> {
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
match format {
|
if txt {
|
||||||
Some(format) => print_data(&details, format, pretty),
|
anstream::println!(
|
||||||
None => {
|
"{}\n{} [{}]",
|
||||||
anstream::println!(
|
"[Video]".on_green().black(),
|
||||||
"{}\n{} [{}]",
|
details.name.green().bold(),
|
||||||
"[Video]".on_green().black(),
|
details.id
|
||||||
details.name.green().bold(),
|
);
|
||||||
details.id
|
anstream::println!(
|
||||||
);
|
"{} {} [{}]",
|
||||||
anstream::println!(
|
"Channel:".blue(),
|
||||||
"{} {} [{}]",
|
details.channel.name,
|
||||||
"Channel:".blue(),
|
details.channel.id
|
||||||
details.channel.name,
|
);
|
||||||
details.channel.id
|
if let Some(subs) = details.channel.subscriber_count {
|
||||||
);
|
anstream::println!("{} {}", "Subscribers:".blue(), subs);
|
||||||
if let Some(subs) = details.channel.subscriber_count {
|
}
|
||||||
anstream::println!("{} {}", "Subscribers:".blue(), subs);
|
if let Some(date) = details.publish_date {
|
||||||
}
|
anstream::println!("{} {}", "Date:".blue(), date);
|
||||||
if let Some(date) = details.publish_date {
|
}
|
||||||
anstream::println!("{} {}", "Date:".blue(), date);
|
anstream::println!("{} {}", "Views:".blue(), details.view_count);
|
||||||
}
|
if let Some(likes) = details.like_count {
|
||||||
anstream::println!("{} {}", "Views:".blue(), details.view_count);
|
anstream::println!("{} {}", "Likes:".blue(), likes);
|
||||||
if let Some(likes) = details.like_count {
|
}
|
||||||
anstream::println!("{} {}", "Likes:".blue(), likes);
|
if let Some(comments) = details.top_comments.count {
|
||||||
}
|
anstream::println!("{} {}", "Comments:".blue(), comments);
|
||||||
if let Some(comments) = details.top_comments.count {
|
}
|
||||||
anstream::println!("{} {}", "Comments:".blue(), comments);
|
if details.is_ccommons {
|
||||||
}
|
anstream::println!("{}", "Creative Commons".green());
|
||||||
if details.is_ccommons {
|
}
|
||||||
anstream::println!("{}", "Creative Commons".green());
|
if details.is_live {
|
||||||
}
|
anstream::println!("{}", "Livestream".red());
|
||||||
if details.is_live {
|
}
|
||||||
anstream::println!("{}", "Livestream".red());
|
print_description(Some(details.description.to_plaintext()));
|
||||||
}
|
if !details.recommended.is_empty() {
|
||||||
print_description(Some(details.description.to_plaintext()));
|
print_h2("Recommended");
|
||||||
if !details.recommended.is_empty() {
|
print_entities(&details.recommended.items);
|
||||||
print_h2("Recommended");
|
}
|
||||||
print_entities(&details.recommended.items);
|
let comment_list = comments.map(|c| match c {
|
||||||
}
|
CommentsOrder::Top => &details.top_comments.items,
|
||||||
let comment_list = comments.map(|c| match c {
|
CommentsOrder::Latest => &details.latest_comments.items,
|
||||||
CommentsOrder::Top => &details.top_comments.items,
|
});
|
||||||
CommentsOrder::Latest => &details.latest_comments.items,
|
if let Some(comment_list) = comment_list {
|
||||||
});
|
print_h2("Comments");
|
||||||
if let Some(comment_list) = comment_list {
|
for c in comment_list {
|
||||||
print_h2("Comments");
|
if let Some(author) = &c.author {
|
||||||
for c in comment_list {
|
anstream::print!("{} [{}]", author.name.cyan(), author.id);
|
||||||
if let Some(author) = &c.author {
|
print_verification(author.verification);
|
||||||
anstream::print!(
|
} else {
|
||||||
"{} [{}]",
|
anstream::print!("{}", "Unknown author".magenta());
|
||||||
author.name.cyan(),
|
|
||||||
author.id
|
|
||||||
);
|
|
||||||
print_verification(author.verification);
|
|
||||||
} else {
|
|
||||||
anstream::print!("{}", "Unknown author".magenta());
|
|
||||||
}
|
|
||||||
if c.by_owner {
|
|
||||||
print!(" (Owner)");
|
|
||||||
}
|
|
||||||
println!();
|
|
||||||
println!("{}", c.text.to_plaintext());
|
|
||||||
anstream::print!(
|
|
||||||
"{} {}",
|
|
||||||
"Likes:".blue(),
|
|
||||||
c.like_count.unwrap_or_default()
|
|
||||||
);
|
|
||||||
if c.hearted {
|
|
||||||
anstream::print!(" {}", "♥".red());
|
|
||||||
}
|
|
||||||
println!("\n");
|
|
||||||
}
|
}
|
||||||
|
if c.by_owner {
|
||||||
|
print!(" (Owner)");
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
println!("{}", c.text.to_plaintext());
|
||||||
|
anstream::print!(
|
||||||
|
"{} {}",
|
||||||
|
"Likes:".blue(),
|
||||||
|
c.like_count.unwrap_or_default()
|
||||||
|
);
|
||||||
|
if c.hearted {
|
||||||
|
anstream::print!(" {}", "♥".red());
|
||||||
|
}
|
||||||
|
println!("\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
print_data(&details, format, pretty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UrlTarget::Channel { id } => {
|
UrlTarget::Channel { id } => {
|
||||||
if music {
|
if music {
|
||||||
let artist = rp.query().music_artist(&id, true).await?;
|
let artist = rp.query().music_artist(&id, true).await?;
|
||||||
match format {
|
if txt {
|
||||||
Some(format) => print_data(&artist, format, pretty),
|
anstream::println!(
|
||||||
None => {
|
"{}\n{} [{}]",
|
||||||
anstream::println!(
|
"[Artist]".on_green().black(),
|
||||||
"{}\n{} [{}]",
|
artist.name.green().bold(),
|
||||||
"[Artist]".on_green().black(),
|
artist.id
|
||||||
artist.name.green().bold(),
|
);
|
||||||
artist.id
|
if let Some(subs) = artist.subscriber_count {
|
||||||
);
|
anstream::println!("{} {}", "Subscribers:".blue(), subs);
|
||||||
if let Some(subs) = artist.subscriber_count {
|
}
|
||||||
anstream::println!("{} {}", "Subscribers:".blue(), subs);
|
if let Some(url) = artist.wikipedia_url {
|
||||||
}
|
anstream::println!("{} {}", "Wikipedia:".blue(), url);
|
||||||
if let Some(url) = artist.wikipedia_url {
|
}
|
||||||
anstream::println!("{} {}", "Wikipedia:".blue(), url);
|
if let Some(id) = artist.tracks_playlist_id {
|
||||||
}
|
anstream::println!("{} {}", "All tracks:".blue(), id);
|
||||||
if let Some(id) = artist.tracks_playlist_id {
|
}
|
||||||
anstream::println!("{} {}", "All tracks:".blue(), id);
|
if let Some(id) = artist.videos_playlist_id {
|
||||||
}
|
anstream::println!("{} {}", "All videos:".blue(), id);
|
||||||
if let Some(id) = artist.videos_playlist_id {
|
}
|
||||||
anstream::println!("{} {}", "All videos:".blue(), id);
|
if let Some(id) = artist.radio_id {
|
||||||
}
|
anstream::println!("{} {}", "Radio:".blue(), id);
|
||||||
if let Some(id) = artist.radio_id {
|
}
|
||||||
anstream::println!("{} {}", "Radio:".blue(), id);
|
print_description(artist.description);
|
||||||
}
|
if !artist.albums.is_empty() {
|
||||||
print_description(artist.description);
|
print_h2("Albums");
|
||||||
if !artist.albums.is_empty() {
|
for b in artist.albums {
|
||||||
print_h2("Albums");
|
anstream::print!(
|
||||||
for b in artist.albums {
|
"[{}] {} ({:?}",
|
||||||
anstream::print!(
|
b.id,
|
||||||
"[{}] {} ({:?}",
|
b.name.bold(),
|
||||||
b.id,
|
b.album_type
|
||||||
b.name.bold(),
|
);
|
||||||
b.album_type
|
if let Some(y) = b.year {
|
||||||
);
|
print!(", {y}");
|
||||||
if let Some(y) = b.year {
|
|
||||||
print!(", {y}");
|
|
||||||
}
|
|
||||||
println!(")");
|
|
||||||
}
|
}
|
||||||
}
|
println!(")");
|
||||||
if !artist.playlists.is_empty() {
|
|
||||||
print_h2("Playlists");
|
|
||||||
print_entities(&artist.playlists);
|
|
||||||
}
|
|
||||||
if !artist.similar_artists.is_empty() {
|
|
||||||
print_h2("Similar artists");
|
|
||||||
print_entities(&artist.similar_artists);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !artist.playlists.is_empty() {
|
||||||
|
print_h2("Playlists");
|
||||||
|
print_entities(&artist.playlists);
|
||||||
|
}
|
||||||
|
if !artist.similar_artists.is_empty() {
|
||||||
|
print_h2("Similar artists");
|
||||||
|
print_entities(&artist.similar_artists);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print_data(&artist, format, pretty);
|
||||||
}
|
}
|
||||||
} else if rss {
|
} else if rss {
|
||||||
let rss = rp.query().channel_rss(&id).await?;
|
let rss = rp.query().channel_rss(&id).await?;
|
||||||
|
|
||||||
match format {
|
if txt {
|
||||||
Some(format) => print_data(&rss, format, pretty),
|
anstream::println!(
|
||||||
None => {
|
"{}\n{} [{}]\n{} {}",
|
||||||
|
"[Channel RSS]".on_green().black(),
|
||||||
|
rss.name.green().bold(),
|
||||||
|
rss.id,
|
||||||
|
"Created on:".blue(),
|
||||||
|
rss.create_date,
|
||||||
|
);
|
||||||
|
if let Some(v) = rss.videos.first() {
|
||||||
anstream::println!(
|
anstream::println!(
|
||||||
"{}\n{} [{}]\n{} {}",
|
"{} {} [{}]",
|
||||||
"[Channel RSS]".on_green().black(),
|
"Latest video:".blue(),
|
||||||
rss.name.green().bold(),
|
v.publish_date,
|
||||||
rss.id,
|
v.id
|
||||||
"Created on:".blue(),
|
|
||||||
rss.create_date,
|
|
||||||
);
|
);
|
||||||
if let Some(v) = rss.videos.first() {
|
|
||||||
anstream::println!(
|
|
||||||
"{} {} [{}]",
|
|
||||||
"Latest video:".blue(),
|
|
||||||
v.publish_date,
|
|
||||||
v.id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
println!();
|
|
||||||
print_entities(&rss.videos);
|
|
||||||
}
|
}
|
||||||
|
println!();
|
||||||
|
print_entities(&rss.videos);
|
||||||
|
} else {
|
||||||
|
print_data(&rss, format, pretty);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match tab {
|
match tab {
|
||||||
|
@ -910,105 +899,75 @@ async fn run() -> anyhow::Result<()> {
|
||||||
|
|
||||||
channel.content.extend_limit(rp.query(), limit).await?;
|
channel.content.extend_limit(rp.query(), limit).await?;
|
||||||
|
|
||||||
match format {
|
if txt {
|
||||||
Some(format) => print_data(&channel, format, pretty),
|
anstream::print!(
|
||||||
None => {
|
"{}\n{} [{}]",
|
||||||
anstream::print!(
|
format!("[Channel {tab:?}]").on_green().black(),
|
||||||
"{}\n{} {} [{}]",
|
channel.name.green().bold(),
|
||||||
format!("[Channel {tab:?}]").on_green().black(),
|
channel.id
|
||||||
channel.name.green().bold(),
|
);
|
||||||
channel.handle.unwrap_or_default(),
|
print_verification(channel.verification);
|
||||||
channel.id
|
println!();
|
||||||
);
|
if let Some(subs) = channel.subscriber_count {
|
||||||
print_verification(channel.verification);
|
anstream::println!("{} {}", "Subscribers:".blue(), subs);
|
||||||
println!();
|
|
||||||
if let Some(subs) = channel.subscriber_count {
|
|
||||||
anstream::println!(
|
|
||||||
"{} {}",
|
|
||||||
"Subscribers:".blue(),
|
|
||||||
subs
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(vids) = channel.video_count {
|
|
||||||
anstream::println!("{} {}", "Videos:".blue(), vids);
|
|
||||||
}
|
|
||||||
print_description(Some(channel.description));
|
|
||||||
println!();
|
|
||||||
print_entities(&channel.content.items);
|
|
||||||
}
|
}
|
||||||
|
print_description(Some(channel.description));
|
||||||
|
println!();
|
||||||
|
print_entities(&channel.content.items);
|
||||||
|
} else {
|
||||||
|
print_data(&channel, format, pretty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ChannelTab::Playlists => {
|
ChannelTab::Playlists => {
|
||||||
let channel = rp.query().channel_playlists(&id).await?;
|
let channel = rp.query().channel_playlists(&id).await?;
|
||||||
|
|
||||||
match format {
|
if txt {
|
||||||
Some(format) => print_data(&channel, format, pretty),
|
anstream::println!(
|
||||||
None => {
|
"{}\n{} [{}]",
|
||||||
anstream::println!(
|
format!("[Channel {tab:?}]").on_green().black(),
|
||||||
"{}\n{} {} [{}]",
|
channel.name.green().bold(),
|
||||||
format!("[Channel {tab:?}]").on_green().black(),
|
channel.id
|
||||||
channel.name.green().bold(),
|
);
|
||||||
channel.handle.unwrap_or_default(),
|
print_description(Some(channel.description));
|
||||||
channel.id
|
if let Some(subs) = channel.subscriber_count {
|
||||||
);
|
anstream::println!("{} {}", "Subscribers:".blue(), subs);
|
||||||
print_description(Some(channel.description));
|
|
||||||
if let Some(subs) = channel.subscriber_count {
|
|
||||||
anstream::println!(
|
|
||||||
"{} {}",
|
|
||||||
"Subscribers:".blue(),
|
|
||||||
subs
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(vids) = channel.video_count {
|
|
||||||
anstream::println!("{} {}", "Videos:".blue(), vids);
|
|
||||||
}
|
|
||||||
println!();
|
|
||||||
print_entities(&channel.content.items);
|
|
||||||
}
|
}
|
||||||
|
println!();
|
||||||
|
print_entities(&channel.content.items);
|
||||||
|
} else {
|
||||||
|
print_data(&channel, format, pretty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ChannelTab::Info => {
|
ChannelTab::Info => {
|
||||||
let info = rp.query().channel_info(&id).await?;
|
let info = rp.query().channel_info(&id).await?;
|
||||||
|
|
||||||
match format {
|
if txt {
|
||||||
Some(format) => print_data(&info, format, pretty),
|
anstream::println!(
|
||||||
None => {
|
"{}\n<b>ID:</b>{}",
|
||||||
anstream::println!(
|
"[Channel info]".on_green().black(),
|
||||||
"{}\n<b>ID:</b>{}",
|
info.id
|
||||||
"[Channel info]".on_green().black(),
|
);
|
||||||
info.id
|
print_description(Some(info.description));
|
||||||
);
|
if let Some(subs) = info.subscriber_count {
|
||||||
print_description(Some(info.description));
|
anstream::println!("{} {}", "Subscribers:".blue(), subs);
|
||||||
if let Some(subs) = info.subscriber_count {
|
}
|
||||||
anstream::println!(
|
if let Some(vids) = info.video_count {
|
||||||
"{} {}",
|
anstream::println!("{} {}", "Videos:".blue(), vids);
|
||||||
"Subscribers:".blue(),
|
}
|
||||||
subs
|
if let Some(views) = info.view_count {
|
||||||
);
|
anstream::println!("{} {}", "Views:".blue(), views);
|
||||||
}
|
}
|
||||||
if let Some(vids) = info.video_count {
|
if let Some(created) = info.create_date {
|
||||||
anstream::println!("{} {}", "Videos:".blue(), vids);
|
anstream::println!("{} {}", "Created on:".blue(), created);
|
||||||
}
|
}
|
||||||
if let Some(views) = info.view_count {
|
if !info.links.is_empty() {
|
||||||
anstream::println!("{} {}", "Views:".blue(), views);
|
print_h2("Links");
|
||||||
}
|
for (name, url) in &info.links {
|
||||||
if let Some(created) = info.create_date {
|
anstream::println!("{} {}", name.blue(), url);
|
||||||
anstream::println!(
|
|
||||||
"{} {}",
|
|
||||||
"Created on:".blue(),
|
|
||||||
created
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(country) = info.country {
|
|
||||||
anstream::println!("{} {}", "Country:".blue(), country);
|
|
||||||
}
|
|
||||||
if !info.links.is_empty() {
|
|
||||||
print_h2("Links");
|
|
||||||
for (name, url) in &info.links {
|
|
||||||
anstream::println!("{} {}", name.blue(), url);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
print_data(&info, format, pretty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1018,86 +977,83 @@ async fn run() -> anyhow::Result<()> {
|
||||||
if music {
|
if music {
|
||||||
let mut playlist = rp.query().music_playlist(&id).await?;
|
let mut playlist = rp.query().music_playlist(&id).await?;
|
||||||
playlist.tracks.extend_limit(rp.query(), limit).await?;
|
playlist.tracks.extend_limit(rp.query(), limit).await?;
|
||||||
match format {
|
if txt {
|
||||||
Some(format) => print_data(&playlist, format, pretty),
|
anstream::println!(
|
||||||
None => {
|
"{}\n{} [{}]\n{} {}",
|
||||||
anstream::println!(
|
"[MusicPlaylist]".on_green().black(),
|
||||||
"{}\n{} [{}]\n{} {}",
|
playlist.name.green().bold(),
|
||||||
"[MusicPlaylist]".on_green().black(),
|
playlist.id,
|
||||||
playlist.name.green().bold(),
|
"Tracks:".blue(),
|
||||||
playlist.id,
|
playlist.track_count.unwrap_or_default(),
|
||||||
"Tracks:".blue(),
|
);
|
||||||
playlist.track_count.unwrap_or_default(),
|
if let Some(n) = playlist.channel_name() {
|
||||||
);
|
anstream::print!("{} {}", "Author:".blue(), n.bold());
|
||||||
if let Some(n) = playlist.channel_name() {
|
if let Some(id) = playlist.channel_id() {
|
||||||
anstream::print!("{} {}", "Author:".blue(), n.bold());
|
print!(" [{id}]");
|
||||||
if let Some(id) = playlist.channel_id() {
|
|
||||||
print!(" [{id}]");
|
|
||||||
}
|
|
||||||
println!();
|
|
||||||
}
|
}
|
||||||
print_description(playlist.description.map(|d| d.to_plaintext()));
|
|
||||||
println!();
|
println!();
|
||||||
print_tracks(&playlist.tracks.items);
|
|
||||||
}
|
}
|
||||||
|
print_description(playlist.description.map(|d| d.to_plaintext()));
|
||||||
|
println!();
|
||||||
|
print_tracks(&playlist.tracks.items);
|
||||||
|
} else {
|
||||||
|
print_data(&playlist, format, pretty);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut playlist = rp.query().playlist(&id).await?;
|
let mut playlist = rp.query().playlist(&id).await?;
|
||||||
playlist.videos.extend_limit(rp.query(), limit).await?;
|
playlist.videos.extend_limit(rp.query(), limit).await?;
|
||||||
match format {
|
if txt {
|
||||||
Some(format) => print_data(&playlist, format, pretty),
|
anstream::println!(
|
||||||
None => {
|
"{}\n{} [{}]\n{} {}",
|
||||||
anstream::println!(
|
"[Playlist]".on_green().black(),
|
||||||
"{}\n{} [{}]\n{} {}",
|
playlist.name.green().bold(),
|
||||||
"[Playlist]".on_green().black(),
|
playlist.id,
|
||||||
playlist.name.green().bold(),
|
"Videos:".blue(),
|
||||||
playlist.id,
|
playlist.video_count,
|
||||||
"Videos:".blue(),
|
);
|
||||||
playlist.video_count,
|
if let Some(n) = playlist.channel_name() {
|
||||||
);
|
anstream::print!("{} {}", "Author:".blue(), n.bold());
|
||||||
if let Some(n) = playlist.channel_name() {
|
if let Some(id) = playlist.channel_id() {
|
||||||
anstream::print!("{} {}", "Author:".blue(), n.bold());
|
print!(" [{id}]");
|
||||||
if let Some(id) = playlist.channel_id() {
|
|
||||||
print!(" [{id}]");
|
|
||||||
}
|
|
||||||
println!();
|
|
||||||
}
|
}
|
||||||
if let Some(last_update) = playlist.last_update {
|
|
||||||
anstream::println!("{} {}", "Last update:".blue(), last_update);
|
|
||||||
}
|
|
||||||
print_description(playlist.description.map(|d| d.to_plaintext()));
|
|
||||||
println!();
|
println!();
|
||||||
print_entities(&playlist.videos.items);
|
|
||||||
}
|
}
|
||||||
|
if let Some(last_update) = playlist.last_update {
|
||||||
|
anstream::println!("{} {}", "Last update:".blue(), last_update);
|
||||||
|
}
|
||||||
|
print_description(playlist.description.map(|d| d.to_plaintext()));
|
||||||
|
println!();
|
||||||
|
print_entities(&playlist.videos.items);
|
||||||
|
} else {
|
||||||
|
print_data(&playlist, format, pretty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UrlTarget::Album { id } => {
|
UrlTarget::Album { id } => {
|
||||||
let album = rp.query().music_album(&id).await?;
|
let album = rp.query().music_album(&id).await?;
|
||||||
match format {
|
if txt {
|
||||||
Some(format) => print_data(&album, format, pretty),
|
anstream::print!(
|
||||||
None => {
|
"{}\n{} [{}] ({:?}",
|
||||||
anstream::print!(
|
"[Album]".on_green().black(),
|
||||||
"{}\n{} [{}] ({:?}",
|
album.name.green().bold(),
|
||||||
"[Album]".on_green().black(),
|
album.id,
|
||||||
album.name.green().bold(),
|
album.album_type
|
||||||
album.id,
|
);
|
||||||
album.album_type
|
if let Some(year) = album.year {
|
||||||
);
|
print!(", {year}");
|
||||||
if let Some(year) = album.year {
|
|
||||||
print!(", {year}");
|
|
||||||
}
|
|
||||||
println!(")");
|
|
||||||
if let Some(n) = album.channel_name() {
|
|
||||||
anstream::print!("{} {}", "Artist:".blue(), n);
|
|
||||||
if let Some(id) = album.channel_id() {
|
|
||||||
print!(" [{id}]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
print_description(album.description.map(|d| d.to_plaintext()));
|
|
||||||
println!();
|
|
||||||
print_tracks(&album.tracks);
|
|
||||||
}
|
}
|
||||||
|
println!(")");
|
||||||
|
if let Some(n) = album.channel_name() {
|
||||||
|
anstream::print!("{} {}", "Artist:".blue(), n);
|
||||||
|
if let Some(id) = album.channel_id() {
|
||||||
|
print!(" [{id}]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print_description(album.description.map(|d| d.to_plaintext()));
|
||||||
|
println!();
|
||||||
|
print_tracks(&album.tracks);
|
||||||
|
} else {
|
||||||
|
print_data(&album, format, pretty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1106,6 +1062,7 @@ async fn run() -> anyhow::Result<()> {
|
||||||
query,
|
query,
|
||||||
format,
|
format,
|
||||||
pretty,
|
pretty,
|
||||||
|
txt,
|
||||||
limit,
|
limit,
|
||||||
item_type,
|
item_type,
|
||||||
length,
|
length,
|
||||||
|
@ -1115,29 +1072,10 @@ async fn run() -> anyhow::Result<()> {
|
||||||
music,
|
music,
|
||||||
} => match music {
|
} => match music {
|
||||||
None => match channel {
|
None => match channel {
|
||||||
Some(channel_id) => {
|
Some(channel) => {
|
||||||
rustypipe::validate::channel_id(&channel_id)?;
|
rustypipe::validate::channel_id(&channel)?;
|
||||||
let channel = rp.query().channel_search(&channel_id, &query).await?;
|
let res = rp.query().channel_search(&channel, &query).await?;
|
||||||
|
print_data(&res, format, pretty);
|
||||||
match format {
|
|
||||||
Some(format) => print_data(&channel, format, pretty),
|
|
||||||
None => {
|
|
||||||
anstream::print!(
|
|
||||||
"{}\n{} [{}]",
|
|
||||||
"[Channel search]".on_green().black(),
|
|
||||||
channel.name.green().bold(),
|
|
||||||
channel.id
|
|
||||||
);
|
|
||||||
print_verification(channel.verification);
|
|
||||||
println!();
|
|
||||||
if let Some(subs) = channel.subscriber_count {
|
|
||||||
anstream::println!("{} {}", "Subscribers:".blue(), subs);
|
|
||||||
}
|
|
||||||
print_description(Some(channel.description));
|
|
||||||
println!();
|
|
||||||
print_entities(&channel.content.items);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let filter = search_filter::SearchFilter::new()
|
let filter = search_filter::SearchFilter::new()
|
||||||
|
@ -1151,40 +1089,39 @@ async fn run() -> anyhow::Result<()> {
|
||||||
.await?;
|
.await?;
|
||||||
res.items.extend_limit(rp.query(), limit).await?;
|
res.items.extend_limit(rp.query(), limit).await?;
|
||||||
|
|
||||||
match format {
|
if txt {
|
||||||
Some(format) => print_data(&res, format, pretty),
|
if let Some(corr) = res.corrected_query {
|
||||||
None => {
|
anstream::println!("Did you mean `{}`?", corr.magenta());
|
||||||
if let Some(corr) = res.corrected_query {
|
|
||||||
anstream::println!("Did you mean `{}`?", corr.magenta());
|
|
||||||
}
|
|
||||||
print_entities(&res.items.items);
|
|
||||||
}
|
}
|
||||||
|
print_entities(&res.items.items);
|
||||||
|
} else {
|
||||||
|
print_data(&res, format, pretty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(MusicSearchCategory::All) => {
|
Some(MusicSearchCategory::All) => {
|
||||||
let res = rp.query().music_search_main(&query).await?;
|
let res = rp.query().music_search_main(&query).await?;
|
||||||
print_music_search(&res, format, pretty);
|
print_music_search(&res, format, pretty, txt);
|
||||||
}
|
}
|
||||||
Some(MusicSearchCategory::Tracks) => {
|
Some(MusicSearchCategory::Tracks) => {
|
||||||
let mut res = rp.query().music_search_tracks(&query).await?;
|
let mut res = rp.query().music_search_tracks(&query).await?;
|
||||||
res.items.extend_limit(rp.query(), limit).await?;
|
res.items.extend_limit(rp.query(), limit).await?;
|
||||||
print_music_search(&res, format, pretty);
|
print_music_search(&res, format, pretty, txt);
|
||||||
}
|
}
|
||||||
Some(MusicSearchCategory::Videos) => {
|
Some(MusicSearchCategory::Videos) => {
|
||||||
let mut res = rp.query().music_search_videos(&query).await?;
|
let mut res = rp.query().music_search_videos(&query).await?;
|
||||||
res.items.extend_limit(rp.query(), limit).await?;
|
res.items.extend_limit(rp.query(), limit).await?;
|
||||||
print_music_search(&res, format, pretty);
|
print_music_search(&res, format, pretty, txt);
|
||||||
}
|
}
|
||||||
Some(MusicSearchCategory::Artists) => {
|
Some(MusicSearchCategory::Artists) => {
|
||||||
let mut res = rp.query().music_search_artists(&query).await?;
|
let mut res = rp.query().music_search_artists(&query).await?;
|
||||||
res.items.extend_limit(rp.query(), limit).await?;
|
res.items.extend_limit(rp.query(), limit).await?;
|
||||||
print_music_search(&res, format, pretty);
|
print_music_search(&res, format, pretty, txt);
|
||||||
}
|
}
|
||||||
Some(MusicSearchCategory::Albums) => {
|
Some(MusicSearchCategory::Albums) => {
|
||||||
let mut res = rp.query().music_search_albums(&query).await?;
|
let mut res = rp.query().music_search_albums(&query).await?;
|
||||||
res.items.extend_limit(rp.query(), limit).await?;
|
res.items.extend_limit(rp.query(), limit).await?;
|
||||||
print_music_search(&res, format, pretty);
|
print_music_search(&res, format, pretty, txt);
|
||||||
}
|
}
|
||||||
Some(MusicSearchCategory::PlaylistsYtm | MusicSearchCategory::PlaylistsCommunity) => {
|
Some(MusicSearchCategory::PlaylistsYtm | MusicSearchCategory::PlaylistsCommunity) => {
|
||||||
let mut res = rp
|
let mut res = rp
|
||||||
|
@ -1195,7 +1132,7 @@ async fn run() -> anyhow::Result<()> {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
res.items.extend_limit(rp.query(), limit).await?;
|
res.items.extend_limit(rp.query(), limit).await?;
|
||||||
print_music_search(&res, format, pretty);
|
print_music_search(&res, format, pretty, txt);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Commands::Vdata => {
|
Commands::Vdata => {
|
||||||
|
|
|
@ -179,7 +179,7 @@ pub async fn channel_handles_in_search_results(rp: &RustyPipeQuery) -> Result<bo
|
||||||
Ok(search.items.items.iter().any(|itm| match itm {
|
Ok(search.items.items.iter().any(|itm| match itm {
|
||||||
YouTubeItem::Channel(channel) => channel
|
YouTubeItem::Channel(channel) => channel
|
||||||
.subscriber_count
|
.subscriber_count
|
||||||
.map(|sc| sc > 100 && channel.handle.is_some())
|
.map(|sc| sc > 100 && channel.video_count.is_none())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
_ => false,
|
_ => false,
|
||||||
}))
|
}))
|
||||||
|
@ -327,7 +327,7 @@ pub async fn channel_page_header(rp: &RustyPipeQuery) -> Result<bool> {
|
||||||
let channel = rp
|
let channel = rp
|
||||||
.channel_videos_tab("UCh8gHdtzO2tXd593_bjErWg", ChannelVideoTab::Shorts)
|
.channel_videos_tab("UCh8gHdtzO2tXd593_bjErWg", ChannelVideoTab::Shorts)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(channel.video_count.is_some())
|
Ok(channel.mobile_banner.is_empty() && channel.tv_banner.is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn music_playlist_two_column(rp: &RustyPipeQuery) -> Result<bool> {
|
pub async fn music_playlist_two_column(rp: &RustyPipeQuery) -> Result<bool> {
|
||||||
|
|
|
@ -38,6 +38,8 @@ pub async fn download_testfiles() {
|
||||||
search_cont().await;
|
search_cont().await;
|
||||||
search_playlists().await;
|
search_playlists().await;
|
||||||
search_empty().await;
|
search_empty().await;
|
||||||
|
startpage().await;
|
||||||
|
startpage_cont().await;
|
||||||
trending().await;
|
trending().await;
|
||||||
|
|
||||||
music_playlist().await;
|
music_playlist().await;
|
||||||
|
@ -446,6 +448,29 @@ async fn search_empty() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn startpage() {
|
||||||
|
let json_path = path!(*TESTFILES_DIR / "trends" / "startpage.json");
|
||||||
|
if json_path.exists() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let rp = rp_testfile(&json_path);
|
||||||
|
rp.query().startpage().await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn startpage_cont() {
|
||||||
|
let json_path = path!(*TESTFILES_DIR / "trends" / "startpage_cont.json");
|
||||||
|
if json_path.exists() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let rp = RustyPipe::new();
|
||||||
|
let startpage = rp.query().startpage().await.unwrap();
|
||||||
|
|
||||||
|
let rp = rp_testfile(&json_path);
|
||||||
|
startpage.next(rp.query()).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
async fn trending() {
|
async fn trending() {
|
||||||
let json_path = path!(*TESTFILES_DIR / "trends" / "trending_videos.json");
|
let json_path = path!(*TESTFILES_DIR / "trends" / "trending_videos.json");
|
||||||
if json_path.exists() {
|
if json_path.exists() {
|
||||||
|
|
|
@ -48,9 +48,3 @@ time.workspace = true
|
||||||
lofty = { version = "0.21.0", optional = true }
|
lofty = { version = "0.21.0", optional = true }
|
||||||
image = { version = "0.25.0", optional = true }
|
image = { version = "0.25.0", optional = true }
|
||||||
smartcrop2 = { version = "0.3.0", optional = true }
|
smartcrop2 = { version = "0.3.0", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
path_macro.workspace = true
|
|
||||||
rstest.workspace = true
|
|
||||||
serde_json.workspace = true
|
|
||||||
temp_testdir = "0.2.3"
|
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
# ![RustyPipe](https://code.thetadev.de/ThetaDev/rustypipe/raw/branch/main/notes/logo.svg) Downloader
|
# RustyPipe downloader
|
||||||
|
|
||||||
[![Current crates.io version](https://img.shields.io/crates/v/rustypipe-downloader.svg)](https://crates.io/crates/rustypipe-downloader)
|
|
||||||
[![License](https://img.shields.io/badge/License-GPL--3-blue.svg?style=flat)](http://opensource.org/licenses/MIT)
|
|
||||||
[![CI status](https://code.thetadev.de/ThetaDev/rustypipe/actions/workflows/ci.yaml/badge.svg?style=flat&label=CI)](https://code.thetadev.de/ThetaDev/rustypipe/actions/?workflow=ci.yaml)
|
|
||||||
|
|
||||||
The downloader is a companion crate for RustyPipe that allows for easy and fast
|
The downloader is a companion crate for RustyPipe that allows for easy and fast
|
||||||
downloading of video and audio files.
|
downloading of video and audio files.
|
||||||
|
@ -39,8 +35,8 @@ let dl = DownloaderBuilder::new()
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let filter_audio = StreamFilter::new().no_video();
|
let filter_audio = StreamFilter::new().no_video();
|
||||||
dl.id("eRsGyueVLvQ").stream_filter(filter_audio).to_file("audio.opus").download().await;
|
dl.id("ZeerrnuLi5E").stream_filter(filter_audio).to_file("audio.opus").download().await;
|
||||||
|
|
||||||
let filter_video = StreamFilter::new().video_max_res(720);
|
let filter_video = StreamFilter::new().video_max_res(720);
|
||||||
dl.id("eRsGyueVLvQ").stream_filter(filter_video).to_file("video.mp4").download().await;
|
dl.id("ZeerrnuLi5E").stream_filter(filter_video).to_file("video.mp4").download().await;
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
use std::{borrow::Cow, path::PathBuf};
|
|
||||||
|
|
||||||
use rustypipe::client::ClientType;
|
|
||||||
|
|
||||||
/// Error from the video downloader
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum DownloadError {
|
|
||||||
/// RustyPipe error
|
|
||||||
#[error("{0}")]
|
|
||||||
RustyPipe(#[from] rustypipe::error::Error),
|
|
||||||
/// Error from the HTTP client
|
|
||||||
#[error("http error: {0}")]
|
|
||||||
Http(#[from] reqwest::Error),
|
|
||||||
/// 403 error trying to download video
|
|
||||||
#[error("YouTube returned 403 error")]
|
|
||||||
Forbidden(ClientType),
|
|
||||||
/// File IO error
|
|
||||||
#[error(transparent)]
|
|
||||||
Io(#[from] std::io::Error),
|
|
||||||
/// FFmpeg returned an error
|
|
||||||
#[error("FFmpeg error: {0}")]
|
|
||||||
Ffmpeg(Cow<'static, str>),
|
|
||||||
/// Error parsing ranges for progressive download
|
|
||||||
#[error("Progressive download error: {0}")]
|
|
||||||
Progressive(Cow<'static, str>),
|
|
||||||
/// Video could not be downloaded because of invalid player data
|
|
||||||
#[error("input error: {0}")]
|
|
||||||
Input(Cow<'static, str>),
|
|
||||||
/// Download target already exists
|
|
||||||
#[error("file {0} already exists")]
|
|
||||||
Exists(PathBuf),
|
|
||||||
#[cfg(feature = "audiotag")]
|
|
||||||
/// Audio tagging error
|
|
||||||
#[error("Audio tag error: {0}")]
|
|
||||||
AudioTag(Cow<'static, str>),
|
|
||||||
/// Other error
|
|
||||||
#[error("error: {0}")]
|
|
||||||
Other(Cow<'static, str>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "audiotag")]
|
|
||||||
impl From<lofty::error::LoftyError> for DownloadError {
|
|
||||||
fn from(value: lofty::error::LoftyError) -> Self {
|
|
||||||
Self::AudioTag(value.to_string().into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "audiotag")]
|
|
||||||
impl From<image::ImageError> for DownloadError {
|
|
||||||
fn from(value: image::ImageError) -> Self {
|
|
||||||
Self::AudioTag(value.to_string().into())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,6 @@
|
||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
#![warn(missing_docs, clippy::todo, clippy::dbg_macro)]
|
#![warn(missing_docs, clippy::todo, clippy::dbg_macro)]
|
||||||
|
|
||||||
mod error;
|
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -43,7 +42,7 @@ use rustypipe::model::{richtext::ToPlaintext, VideoDetails, VideoPlayerDetails};
|
||||||
#[cfg(feature = "audiotag")]
|
#[cfg(feature = "audiotag")]
|
||||||
use time::{Date, OffsetDateTime};
|
use time::{Date, OffsetDateTime};
|
||||||
|
|
||||||
pub use error::DownloadError;
|
pub use util::DownloadError;
|
||||||
|
|
||||||
type Result<T> = core::result::Result<T, DownloadError>;
|
type Result<T> = core::result::Result<T, DownloadError>;
|
||||||
|
|
||||||
|
@ -159,7 +158,7 @@ impl DownloadVideo {
|
||||||
channel_id: video.channel_id().map(str::to_owned),
|
channel_id: video.channel_id().map(str::to_owned),
|
||||||
channel_name: video
|
channel_name: video
|
||||||
.channel_name()
|
.channel_name()
|
||||||
.map(|n| n.strip_suffix("- Topic").unwrap_or(n).trim().to_owned()),
|
.map(|n| n.strip_suffix(" - Topic").unwrap_or(n).to_owned()),
|
||||||
album_id: None,
|
album_id: None,
|
||||||
album_name: None,
|
album_name: None,
|
||||||
track_nr: None,
|
track_nr: None,
|
||||||
|
|
|
@ -1,8 +1,58 @@
|
||||||
use std::collections::BTreeMap;
|
use std::{borrow::Cow, collections::BTreeMap, path::PathBuf};
|
||||||
|
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
|
use rustypipe::client::ClientType;
|
||||||
|
|
||||||
use crate::DownloadError;
|
/// Error from the video downloader
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum DownloadError {
|
||||||
|
/// RustyPipe error
|
||||||
|
#[error("{0}")]
|
||||||
|
RustyPipe(#[from] rustypipe::error::Error),
|
||||||
|
/// Error from the HTTP client
|
||||||
|
#[error("http error: {0}")]
|
||||||
|
Http(#[from] reqwest::Error),
|
||||||
|
/// 403 error trying to download video
|
||||||
|
#[error("YouTube returned 403 error")]
|
||||||
|
Forbidden(ClientType),
|
||||||
|
/// File IO error
|
||||||
|
#[error(transparent)]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
/// FFmpeg returned an error
|
||||||
|
#[error("FFmpeg error: {0}")]
|
||||||
|
Ffmpeg(Cow<'static, str>),
|
||||||
|
/// Error parsing ranges for progressive download
|
||||||
|
#[error("Progressive download error: {0}")]
|
||||||
|
Progressive(Cow<'static, str>),
|
||||||
|
/// Video could not be downloaded because of invalid player data
|
||||||
|
#[error("input error: {0}")]
|
||||||
|
Input(Cow<'static, str>),
|
||||||
|
/// Download target already exists
|
||||||
|
#[error("file {0} already exists")]
|
||||||
|
Exists(PathBuf),
|
||||||
|
#[cfg(feature = "audiotag")]
|
||||||
|
/// Audio tagging error
|
||||||
|
#[error("Audio tag error: {0}")]
|
||||||
|
AudioTag(Cow<'static, str>),
|
||||||
|
/// Other error
|
||||||
|
#[error("error: {0}")]
|
||||||
|
Other(Cow<'static, str>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "audiotag")]
|
||||||
|
impl From<lofty::error::LoftyError> for DownloadError {
|
||||||
|
fn from(value: lofty::error::LoftyError) -> Self {
|
||||||
|
Self::AudioTag(value.to_string().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "audiotag")]
|
||||||
|
impl From<image::ImageError> for DownloadError {
|
||||||
|
fn from(value: image::ImageError) -> Self {
|
||||||
|
Self::AudioTag(value.to_string().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Split an URL into its base string and parameter map
|
/// Split an URL into its base string and parameter map
|
||||||
///
|
///
|
||||||
|
|
|
@ -1,113 +0,0 @@
|
||||||
use std::{fs, os::unix::fs::MetadataExt, path::Path, process::Command};
|
|
||||||
|
|
||||||
use path_macro::path;
|
|
||||||
use rstest::{fixture, rstest};
|
|
||||||
use rustypipe::{client::RustyPipe, model::AudioCodec, param::StreamFilter};
|
|
||||||
use rustypipe_downloader::Downloader;
|
|
||||||
use temp_testdir::TempDir;
|
|
||||||
|
|
||||||
/// Get a new RusttyPipe instance
|
|
||||||
#[fixture]
|
|
||||||
fn rp() -> RustyPipe {
|
|
||||||
let vdata = std::env::var("YT_VDATA").ok();
|
|
||||||
RustyPipe::builder()
|
|
||||||
.strict()
|
|
||||||
.storage_dir(path!(env!("CARGO_MANIFEST_DIR") / ".."))
|
|
||||||
.visitor_data_opt(vdata)
|
|
||||||
.build()
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
|
||||||
async fn download_video(rp: RustyPipe) {
|
|
||||||
let td = TempDir::default();
|
|
||||||
let td_path = td.to_path_buf();
|
|
||||||
|
|
||||||
let dl = Downloader::builder().rustypipe(&rp).build();
|
|
||||||
|
|
||||||
let res = dl
|
|
||||||
.id("UXqq0ZvbOnk")
|
|
||||||
.to_dir(&td_path)
|
|
||||||
.stream_filter(StreamFilter::new().video_max_res(480))
|
|
||||||
.download()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
res.dest,
|
|
||||||
path!(td_path / "CHARGE - Blender Open Movie [UXqq0ZvbOnk].mp4")
|
|
||||||
);
|
|
||||||
assert_eq!(res.player_data.details.id, "UXqq0ZvbOnk");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[tokio::test]
|
|
||||||
async fn download_music(rp: RustyPipe) {
|
|
||||||
let td = TempDir::default();
|
|
||||||
let td_path = td.to_path_buf();
|
|
||||||
|
|
||||||
let dl = Downloader::builder()
|
|
||||||
.audio_tag()
|
|
||||||
.crop_cover()
|
|
||||||
.rustypipe(&rp)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let res = dl
|
|
||||||
.id("bVtv3st8bgc")
|
|
||||||
.to_dir(&td_path)
|
|
||||||
.stream_filter(
|
|
||||||
StreamFilter::new()
|
|
||||||
.no_video()
|
|
||||||
.audio_codecs([AudioCodec::Opus]),
|
|
||||||
)
|
|
||||||
.download()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
res.dest,
|
|
||||||
path!(td_path / "Lord of the Riffs [bVtv3st8bgc].opus")
|
|
||||||
);
|
|
||||||
assert_eq!(res.player_data.details.id, "bVtv3st8bgc");
|
|
||||||
let fm = fs::metadata(&res.dest).unwrap();
|
|
||||||
assert_gte(fm.size(), 6_000_000, "file size");
|
|
||||||
assert_audio_meta(
|
|
||||||
&res.dest,
|
|
||||||
"Lord of the Riffs",
|
|
||||||
"Alexander Nakarada - CreatorChords",
|
|
||||||
"Lord of the Riffs",
|
|
||||||
"2022-02-05",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Assert that number A is greater than or equal to number B
|
|
||||||
#[track_caller]
|
|
||||||
fn assert_gte<T: PartialOrd + std::fmt::Display>(a: T, b: T, msg: &str) {
|
|
||||||
assert!(a >= b, "expected >= {b} {msg}, got {a}");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[track_caller]
|
|
||||||
fn assert_audio_meta(p: &Path, title: &str, artist: &str, album: &str, date: &str) {
|
|
||||||
let res = Command::new("ffprobe")
|
|
||||||
.args([
|
|
||||||
"-loglevel",
|
|
||||||
"error",
|
|
||||||
"-show_entries",
|
|
||||||
"stream_tags",
|
|
||||||
"-of",
|
|
||||||
"json",
|
|
||||||
])
|
|
||||||
.arg(p)
|
|
||||||
.output()
|
|
||||||
.unwrap();
|
|
||||||
if !res.status.success() {
|
|
||||||
panic!("ffprobe error\n{}", String::from_utf8_lossy(&res.stderr))
|
|
||||||
}
|
|
||||||
let res_json = serde_json::from_slice::<serde_json::Value>(&res.stdout).unwrap();
|
|
||||||
let tags = &res_json["streams"][0]["tags"];
|
|
||||||
assert_eq!(tags["TITLE"].as_str(), Some(title));
|
|
||||||
assert_eq!(tags["ARTIST"].as_str(), Some(artist));
|
|
||||||
assert_eq!(tags["ALBUM"].as_str(), Some(album));
|
|
||||||
assert_eq!(tags["DATE"].as_str(), Some(date));
|
|
||||||
}
|
|
|
@ -353,6 +353,18 @@ impl MapResponse<ChannelInfo> for response::ChannelAbout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn map_vanity_url(url: &str, id: &str) -> Option<String> {
|
||||||
|
if url.contains(id) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Url::parse(url).ok().map(|mut parsed_url| {
|
||||||
|
// The vanity URL from YouTube is http for some reason
|
||||||
|
_ = parsed_url.set_scheme("https");
|
||||||
|
parsed_url.to_string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
struct MapChannelData {
|
struct MapChannelData {
|
||||||
header: Option<response::channel::Header>,
|
header: Option<response::channel::Header>,
|
||||||
metadata: Option<response::channel::Metadata>,
|
metadata: Option<response::channel::Metadata>,
|
||||||
|
@ -389,16 +401,10 @@ fn map_channel(
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let handle = metadata
|
let vanity_url = metadata
|
||||||
.vanity_channel_url
|
.vanity_channel_url
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|url| Url::parse(url).ok())
|
.and_then(|url| map_vanity_url(url, ctx.id));
|
||||||
.and_then(|url| {
|
|
||||||
url.path()
|
|
||||||
.strip_prefix('/')
|
|
||||||
.filter(|handle| util::CHANNEL_HANDLE_REGEX.is_match(handle))
|
|
||||||
.map(str::to_owned)
|
|
||||||
});
|
|
||||||
let mut warnings = Vec::new();
|
let mut warnings = Vec::new();
|
||||||
|
|
||||||
Ok(MapResult {
|
Ok(MapResult {
|
||||||
|
@ -406,16 +412,17 @@ fn map_channel(
|
||||||
response::channel::Header::C4TabbedHeaderRenderer(header) => Channel {
|
response::channel::Header::C4TabbedHeaderRenderer(header) => Channel {
|
||||||
id: metadata.external_id,
|
id: metadata.external_id,
|
||||||
name: metadata.title,
|
name: metadata.title,
|
||||||
handle,
|
|
||||||
subscriber_count: header.subscriber_count_text.and_then(|txt| {
|
subscriber_count: header.subscriber_count_text.and_then(|txt| {
|
||||||
util::parse_large_numstr_or_warn(&txt, ctx.lang, &mut warnings)
|
util::parse_large_numstr_or_warn(&txt, ctx.lang, &mut warnings)
|
||||||
}),
|
}),
|
||||||
video_count: None,
|
|
||||||
avatar: header.avatar.into(),
|
avatar: header.avatar.into(),
|
||||||
verification: header.badges.into(),
|
verification: header.badges.into(),
|
||||||
description: metadata.description,
|
description: metadata.description,
|
||||||
tags: microformat.microformat_data_renderer.tags,
|
tags: microformat.microformat_data_renderer.tags,
|
||||||
|
vanity_url,
|
||||||
banner: header.banner.into(),
|
banner: header.banner.into(),
|
||||||
|
mobile_banner: header.mobile_banner.into(),
|
||||||
|
tv_banner: header.tv_banner.into(),
|
||||||
has_shorts: d.has_shorts,
|
has_shorts: d.has_shorts,
|
||||||
has_live: d.has_live,
|
has_live: d.has_live,
|
||||||
visitor_data: d.visitor_data,
|
visitor_data: d.visitor_data,
|
||||||
|
@ -436,20 +443,21 @@ fn map_channel(
|
||||||
Channel {
|
Channel {
|
||||||
id: metadata.external_id,
|
id: metadata.external_id,
|
||||||
name: metadata.title,
|
name: metadata.title,
|
||||||
handle,
|
|
||||||
subscriber_count: hdata.as_ref().and_then(|hdata| {
|
subscriber_count: hdata.as_ref().and_then(|hdata| {
|
||||||
hdata.0.as_ref().and_then(|txt| {
|
hdata.0.as_ref().and_then(|txt| {
|
||||||
util::parse_large_numstr_or_warn(txt, ctx.lang, &mut warnings)
|
util::parse_large_numstr_or_warn(txt, ctx.lang, &mut warnings)
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
video_count: None,
|
|
||||||
avatar: hdata.map(|hdata| hdata.1.into()).unwrap_or_default(),
|
avatar: hdata.map(|hdata| hdata.1.into()).unwrap_or_default(),
|
||||||
// Since the carousel header is only used for YT-internal channels or special events
|
// Since the carousel header is only used for YT-internal channels or special events
|
||||||
// (World Cup, Coachella, etc.) we can assume the channel to be verified
|
// (World Cup, Coachella, etc.) we can assume the channel to be verified
|
||||||
verification: crate::model::Verification::Verified,
|
verification: crate::model::Verification::Verified,
|
||||||
description: metadata.description,
|
description: metadata.description,
|
||||||
tags: microformat.microformat_data_renderer.tags,
|
tags: microformat.microformat_data_renderer.tags,
|
||||||
|
vanity_url,
|
||||||
banner: Vec::new(),
|
banner: Vec::new(),
|
||||||
|
mobile_banner: Vec::new(),
|
||||||
|
tv_banner: Vec::new(),
|
||||||
has_shorts: d.has_shorts,
|
has_shorts: d.has_shorts,
|
||||||
has_live: d.has_live,
|
has_live: d.has_live,
|
||||||
visitor_data: d.visitor_data,
|
visitor_data: d.visitor_data,
|
||||||
|
@ -460,33 +468,19 @@ fn map_channel(
|
||||||
let hdata = header.content.page_header_view_model;
|
let hdata = header.content.page_header_view_model;
|
||||||
// channel handle - subscriber count - video count
|
// channel handle - subscriber count - video count
|
||||||
let md_rows = hdata.metadata.content_metadata_view_model.metadata_rows;
|
let md_rows = hdata.metadata.content_metadata_view_model.metadata_rows;
|
||||||
let (sub_part, vc_part) = if md_rows.len() > 1 {
|
let sub_part = if md_rows.len() > 1 {
|
||||||
let mp = &md_rows[1].metadata_parts;
|
md_rows.get(1).and_then(|md| md.metadata_parts.first())
|
||||||
(mp.first(), mp.get(1))
|
|
||||||
} else {
|
} else {
|
||||||
(
|
md_rows.first().and_then(|md| md.metadata_parts.get(1))
|
||||||
md_rows.first().and_then(|md| md.metadata_parts.get(1)),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
let subscriber_count = sub_part.and_then(|t| {
|
let subscriber_count = sub_part.and_then(|t| {
|
||||||
util::parse_large_numstr_or_warn::<u64>(&t.text, ctx.lang, &mut warnings)
|
util::parse_large_numstr_or_warn::<u64>(&t.text, ctx.lang, &mut warnings)
|
||||||
});
|
});
|
||||||
let video_count =
|
|
||||||
vc_part.and_then(|t| util::parse_numeric_or_warn(&t.text, &mut warnings));
|
|
||||||
|
|
||||||
Channel {
|
Channel {
|
||||||
id: metadata.external_id,
|
id: metadata.external_id,
|
||||||
name: metadata.title,
|
name: metadata.title,
|
||||||
handle: handle.or_else(|| {
|
|
||||||
md_rows
|
|
||||||
.first()
|
|
||||||
.and_then(|md| md.metadata_parts.get(1))
|
|
||||||
.map(|txt| txt.text.to_owned())
|
|
||||||
.filter(|txt| util::CHANNEL_HANDLE_REGEX.is_match(txt))
|
|
||||||
}),
|
|
||||||
subscriber_count,
|
subscriber_count,
|
||||||
video_count,
|
|
||||||
avatar: hdata
|
avatar: hdata
|
||||||
.image
|
.image
|
||||||
.decorated_avatar_view_model
|
.decorated_avatar_view_model
|
||||||
|
@ -497,7 +491,10 @@ fn map_channel(
|
||||||
verification: hdata.title.into(),
|
verification: hdata.title.into(),
|
||||||
description: metadata.description,
|
description: metadata.description,
|
||||||
tags: microformat.microformat_data_renderer.tags,
|
tags: microformat.microformat_data_renderer.tags,
|
||||||
|
vanity_url,
|
||||||
banner: hdata.banner.image_banner_view_model.image.into(),
|
banner: hdata.banner.image_banner_view_model.image.into(),
|
||||||
|
mobile_banner: Vec::new(),
|
||||||
|
tv_banner: Vec::new(),
|
||||||
has_shorts: d.has_shorts,
|
has_shorts: d.has_shorts,
|
||||||
has_live: d.has_live,
|
has_live: d.has_live,
|
||||||
visitor_data: d.visitor_data,
|
visitor_data: d.visitor_data,
|
||||||
|
@ -607,14 +604,15 @@ fn combine_channel_data<T>(channel_data: Channel<()>, content: T) -> Channel<T>
|
||||||
Channel {
|
Channel {
|
||||||
id: channel_data.id,
|
id: channel_data.id,
|
||||||
name: channel_data.name,
|
name: channel_data.name,
|
||||||
handle: channel_data.handle,
|
|
||||||
subscriber_count: channel_data.subscriber_count,
|
subscriber_count: channel_data.subscriber_count,
|
||||||
video_count: channel_data.video_count,
|
|
||||||
avatar: channel_data.avatar,
|
avatar: channel_data.avatar,
|
||||||
verification: channel_data.verification,
|
verification: channel_data.verification,
|
||||||
description: channel_data.description,
|
description: channel_data.description,
|
||||||
tags: channel_data.tags,
|
tags: channel_data.tags,
|
||||||
|
vanity_url: channel_data.vanity_url,
|
||||||
banner: channel_data.banner,
|
banner: channel_data.banner,
|
||||||
|
mobile_banner: channel_data.mobile_banner,
|
||||||
|
tv_banner: channel_data.tv_banner,
|
||||||
has_shorts: channel_data.has_shorts,
|
has_shorts: channel_data.has_shorts,
|
||||||
has_live: channel_data.has_live,
|
has_live: channel_data.has_live,
|
||||||
visitor_data: channel_data.visitor_data,
|
visitor_data: channel_data.visitor_data,
|
||||||
|
|
|
@ -203,9 +203,9 @@ const DISABLE_PRETTY_PRINT_PARAMETER: &str = "prettyPrint=false";
|
||||||
|
|
||||||
// Desktop client
|
// Desktop client
|
||||||
const DESKTOP_CLIENT_VERSION: &str = "2.20230126.00.00";
|
const DESKTOP_CLIENT_VERSION: &str = "2.20230126.00.00";
|
||||||
const DESKTOP_MUSIC_CLIENT_VERSION: &str = "1.20230123.01.01";
|
|
||||||
const TV_CLIENT_VERSION: &str = "7.20240724.13.00";
|
|
||||||
const TVHTML5_CLIENT_VERSION: &str = "2.0";
|
const TVHTML5_CLIENT_VERSION: &str = "2.0";
|
||||||
|
const TV_CLIENT_VERSION: &str = "7.20240724.13.00";
|
||||||
|
const DESKTOP_MUSIC_CLIENT_VERSION: &str = "1.20230123.01.01";
|
||||||
|
|
||||||
// Mobile client
|
// Mobile client
|
||||||
const MOBILE_CLIENT_VERSION: &str = "18.03.33";
|
const MOBILE_CLIENT_VERSION: &str = "18.03.33";
|
||||||
|
@ -220,8 +220,12 @@ static VISITOR_DATA_REGEX: Lazy<Regex> =
|
||||||
///
|
///
|
||||||
/// The order may change in the future in case YouTube applies changes to their
|
/// The order may change in the future in case YouTube applies changes to their
|
||||||
/// platform that disable a client or make it less reliable.
|
/// platform that disable a client or make it less reliable.
|
||||||
pub const DEFAULT_PLAYER_CLIENT_ORDER: &[ClientType] =
|
pub const DEFAULT_PLAYER_CLIENT_ORDER: &[ClientType] = &[
|
||||||
&[ClientType::Tv, ClientType::Android, ClientType::Ios];
|
ClientType::Tv,
|
||||||
|
ClientType::TvHtml5Embed,
|
||||||
|
ClientType::Android,
|
||||||
|
ClientType::Ios,
|
||||||
|
];
|
||||||
|
|
||||||
/// The RustyPipe client used to access YouTube's API
|
/// The RustyPipe client used to access YouTube's API
|
||||||
///
|
///
|
||||||
|
@ -372,20 +376,14 @@ impl Default for RustyPipeOpts {
|
||||||
struct CacheHolder {
|
struct CacheHolder {
|
||||||
desktop_client: RwLock<CacheEntry<ClientData>>,
|
desktop_client: RwLock<CacheEntry<ClientData>>,
|
||||||
music_client: RwLock<CacheEntry<ClientData>>,
|
music_client: RwLock<CacheEntry<ClientData>>,
|
||||||
tv_client: RwLock<CacheEntry<ClientData>>,
|
|
||||||
deobf: RwLock<CacheEntry<DeobfData>>,
|
deobf: RwLock<CacheEntry<DeobfData>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
struct CacheData {
|
struct CacheData {
|
||||||
#[serde(skip_serializing_if = "CacheEntry::is_none")]
|
|
||||||
desktop_client: CacheEntry<ClientData>,
|
desktop_client: CacheEntry<ClientData>,
|
||||||
#[serde(skip_serializing_if = "CacheEntry::is_none")]
|
|
||||||
music_client: CacheEntry<ClientData>,
|
music_client: CacheEntry<ClientData>,
|
||||||
#[serde(skip_serializing_if = "CacheEntry::is_none")]
|
|
||||||
tv_client: CacheEntry<ClientData>,
|
|
||||||
#[serde(skip_serializing_if = "CacheEntry::is_none")]
|
|
||||||
deobf: CacheEntry<DeobfData>,
|
deobf: CacheEntry<DeobfData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -436,10 +434,6 @@ impl<T> CacheEntry<T> {
|
||||||
CacheEntry::None => None,
|
CacheEntry::None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_none(&self) -> bool {
|
|
||||||
matches!(self, Self::None)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<T> for CacheEntry<T> {
|
impl<T> From<T> for CacheEntry<T> {
|
||||||
|
@ -531,7 +525,6 @@ impl RustyPipeBuilder {
|
||||||
cache: CacheHolder {
|
cache: CacheHolder {
|
||||||
desktop_client: RwLock::new(cdata.desktop_client),
|
desktop_client: RwLock::new(cdata.desktop_client),
|
||||||
music_client: RwLock::new(cdata.music_client),
|
music_client: RwLock::new(cdata.music_client),
|
||||||
tv_client: RwLock::new(cdata.tv_client),
|
|
||||||
deobf: RwLock::new(cdata.deobf),
|
deobf: RwLock::new(cdata.deobf),
|
||||||
},
|
},
|
||||||
default_opts: self.default_opts,
|
default_opts: self.default_opts,
|
||||||
|
@ -826,12 +819,6 @@ impl RustyPipe {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract the current version of the YouTube TV client from the website.
|
|
||||||
async fn extract_tv_client_version(&self) -> Result<String, Error> {
|
|
||||||
self.extract_client_version(None, YOUTUBE_TV_URL, YOUTUBE_TV_URL, Some(TV_UA))
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn extract_client_version(
|
async fn extract_client_version(
|
||||||
&self,
|
&self,
|
||||||
sw_url: Option<&str>,
|
sw_url: Option<&str>,
|
||||||
|
@ -950,37 +937,6 @@ impl RustyPipe {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current version of the YouTube TV client from the following sources
|
|
||||||
///
|
|
||||||
/// 1. from cache
|
|
||||||
/// 2. from the YouTube TV website
|
|
||||||
/// 3. fall back to the hardcoded version
|
|
||||||
async fn get_tv_client_version(&self) -> String {
|
|
||||||
// Write lock here to prevent concurrent tasks from fetching the same data
|
|
||||||
let mut tv_client = self.inner.cache.tv_client.write().await;
|
|
||||||
|
|
||||||
match tv_client.get() {
|
|
||||||
Some(cdata) => cdata.version.clone(),
|
|
||||||
None => {
|
|
||||||
tracing::debug!("getting TV client version");
|
|
||||||
match self.extract_tv_client_version().await {
|
|
||||||
Ok(version) => {
|
|
||||||
*tv_client = CacheEntry::from(ClientData {
|
|
||||||
version: version.clone(),
|
|
||||||
});
|
|
||||||
drop(tv_client);
|
|
||||||
self.store_cache().await;
|
|
||||||
version
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
tracing::warn!("{}, falling back to hardcoded TV client version", e);
|
|
||||||
DESKTOP_MUSIC_CLIENT_VERSION.to_owned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get deobfuscation data (either from cache or extracted from YouTube's JavaScript code)
|
/// Get deobfuscation data (either from cache or extracted from YouTube's JavaScript code)
|
||||||
async fn get_deobf_data(&self) -> Result<DeobfData, Error> {
|
async fn get_deobf_data(&self) -> Result<DeobfData, Error> {
|
||||||
// Write lock here to prevent concurrent tasks from fetching the same data
|
// Write lock here to prevent concurrent tasks from fetching the same data
|
||||||
|
@ -1022,7 +978,6 @@ impl RustyPipe {
|
||||||
let cdata = CacheData {
|
let cdata = CacheData {
|
||||||
desktop_client: self.inner.cache.desktop_client.read().await.clone(),
|
desktop_client: self.inner.cache.desktop_client.read().await.clone(),
|
||||||
music_client: self.inner.cache.music_client.read().await.clone(),
|
music_client: self.inner.cache.music_client.read().await.clone(),
|
||||||
tv_client: self.inner.cache.tv_client.read().await.clone(),
|
|
||||||
deobf: self.inner.cache.deobf.read().await.clone(),
|
deobf: self.inner.cache.deobf.read().await.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1246,7 +1201,7 @@ impl RustyPipeQuery {
|
||||||
ClientType::Tv => YTContext {
|
ClientType::Tv => YTContext {
|
||||||
client: ClientInfo {
|
client: ClientInfo {
|
||||||
client_name: "TVHTML5",
|
client_name: "TVHTML5",
|
||||||
client_version: Cow::Owned(self.client.get_tv_client_version().await),
|
client_version: Cow::Borrowed(TV_CLIENT_VERSION),
|
||||||
client_screen: Some("WATCH"),
|
client_screen: Some("WATCH"),
|
||||||
platform: "TV",
|
platform: "TV",
|
||||||
device_model: Some("SmartTV"),
|
device_model: Some("SmartTV"),
|
||||||
|
@ -1738,28 +1693,21 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn extract_desktop_client_version() {
|
async fn t_extract_desktop_client_version() {
|
||||||
let rp = RustyPipe::new();
|
let rp = RustyPipe::new();
|
||||||
let version = rp.extract_desktop_client_version().await.unwrap();
|
let version = rp.extract_desktop_client_version().await.unwrap();
|
||||||
assert!(get_major_version(&version) >= 2);
|
assert!(get_major_version(&version) >= 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn extract_music_client_version() {
|
async fn t_extract_music_client_version() {
|
||||||
let rp = RustyPipe::new();
|
let rp = RustyPipe::new();
|
||||||
let version = rp.extract_music_client_version().await.unwrap();
|
let version = rp.extract_music_client_version().await.unwrap();
|
||||||
assert!(get_major_version(&version) >= 1);
|
assert!(get_major_version(&version) >= 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn extract_tv_client_version() {
|
async fn t_get_visitor_data() {
|
||||||
let rp = RustyPipe::new();
|
|
||||||
let version = rp.extract_tv_client_version().await.unwrap();
|
|
||||||
assert!(get_major_version(&version) >= 7);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn get_visitor_data() {
|
|
||||||
let rp = RustyPipe::new();
|
let rp = RustyPipe::new();
|
||||||
let visitor_data = rp.get_visitor_data().await.unwrap();
|
let visitor_data = rp.get_visitor_data().await.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -355,6 +355,7 @@ mod tests {
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case::search("search", path!("search" / "cont.json"))]
|
#[case::search("search", path!("search" / "cont.json"))]
|
||||||
|
#[case::startpage("startpage", path!("trends" / "startpage_cont.json"))]
|
||||||
#[case::recommendations("recommendations", path!("video_details" / "recommendations.json"))]
|
#[case::recommendations("recommendations", path!("video_details" / "recommendations.json"))]
|
||||||
fn map_continuation_items(#[case] name: &str, #[case] path: PathBuf) {
|
fn map_continuation_items(#[case] name: &str, #[case] path: PathBuf) {
|
||||||
let json_path = path!(*TESTFILES / path);
|
let json_path = path!(*TESTFILES / path);
|
||||||
|
|
|
@ -74,9 +74,6 @@ impl RustyPipeQuery {
|
||||||
///
|
///
|
||||||
/// The clients are used in the given order. If a client cannot fetch the requested video,
|
/// The clients are used in the given order. If a client cannot fetch the requested video,
|
||||||
/// an attempt is made with the next one.
|
/// an attempt is made with the next one.
|
||||||
///
|
|
||||||
/// If an age-restricted video is detected, it will automatically use the [`ClientType::TvHtml5Embed`]
|
|
||||||
/// since it is the only one that can circumvent age restrictions.
|
|
||||||
pub async fn player_from_clients<S: AsRef<str> + Debug>(
|
pub async fn player_from_clients<S: AsRef<str> + Debug>(
|
||||||
&self,
|
&self,
|
||||||
video_id: S,
|
video_id: S,
|
||||||
|
@ -84,6 +81,9 @@ impl RustyPipeQuery {
|
||||||
) -> Result<VideoPlayer, Error> {
|
) -> Result<VideoPlayer, Error> {
|
||||||
let video_id = video_id.as_ref();
|
let video_id = video_id.as_ref();
|
||||||
let mut last_e = Error::Other("no clients".into());
|
let mut last_e = Error::Other("no clients".into());
|
||||||
|
// Prefer to output age restriction error (e.g. if video cannot be played
|
||||||
|
// by Desktop because of age restriction and by TvHtml5Embed because it is non-embeddable)
|
||||||
|
let mut age_restricted_e = None;
|
||||||
|
|
||||||
for client in clients {
|
for client in clients {
|
||||||
let res = self.player_from_client(video_id, *client).await;
|
let res = self.player_from_client(video_id, *client).await;
|
||||||
|
@ -96,17 +96,11 @@ impl RustyPipeQuery {
|
||||||
msg,
|
msg,
|
||||||
} = &e
|
} = &e
|
||||||
{
|
{
|
||||||
if let Ok(res) = self
|
age_restricted_e =
|
||||||
.player_from_client(video_id, ClientType::TvHtml5Embed)
|
Some(Error::Extraction(ExtractionError::Unavailable {
|
||||||
.await
|
|
||||||
{
|
|
||||||
return Ok(res);
|
|
||||||
} else {
|
|
||||||
return Err(Error::Extraction(ExtractionError::Unavailable {
|
|
||||||
reason: UnavailabilityReason::AgeRestricted,
|
reason: UnavailabilityReason::AgeRestricted,
|
||||||
msg: msg.to_owned(),
|
msg: msg.to_owned(),
|
||||||
}));
|
}));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
last_e = Error::Extraction(e);
|
last_e = Error::Extraction(e);
|
||||||
} else {
|
} else {
|
||||||
|
@ -116,7 +110,7 @@ impl RustyPipeQuery {
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(last_e)
|
Err(age_restricted_e.unwrap_or(last_e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get YouTube player data (video/audio streams + basic metadata) using the specified client
|
/// Get YouTube player data (video/audio streams + basic metadata) using the specified client
|
||||||
|
|
|
@ -95,6 +95,11 @@ pub(crate) struct HeaderRenderer {
|
||||||
pub badges: Vec<ChannelBadge>,
|
pub badges: Vec<ChannelBadge>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub banner: Thumbnails,
|
pub banner: Thumbnails,
|
||||||
|
#[serde(default)]
|
||||||
|
pub mobile_banner: Thumbnails,
|
||||||
|
/// Fullscreen (16:9) channel banner
|
||||||
|
#[serde(default)]
|
||||||
|
pub tv_banner: Thumbnails,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
|
@ -120,35 +125,29 @@ pub(crate) struct PageHeaderRenderer {
|
||||||
pub page_header_view_model: PageHeaderRendererInner,
|
pub page_header_view_model: PageHeaderRendererInner,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub(crate) struct PageHeaderRendererInner {
|
pub(crate) struct PageHeaderRendererInner {
|
||||||
/// Channel title (only used to extract verification badges)
|
|
||||||
#[serde_as(as = "DefaultOnError")]
|
|
||||||
pub title: PhTitleView,
|
pub title: PhTitleView,
|
||||||
/// Channel avatar
|
|
||||||
pub image: PhAvatarView,
|
pub image: PhAvatarView,
|
||||||
/// Channel metadata (subscribers, video count)
|
|
||||||
pub metadata: PhMetadataView,
|
pub metadata: PhMetadataView,
|
||||||
#[serde(default)]
|
|
||||||
pub banner: PhBannerView,
|
pub banner: PhBannerView,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub(crate) struct PhTitleView {
|
pub(crate) struct PhTitleView {
|
||||||
pub dynamic_text_view_model: PhTitleView2,
|
pub dynamic_text_view_model: PhTitleView2,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub(crate) struct PhTitleView2 {
|
pub(crate) struct PhTitleView2 {
|
||||||
pub text: PhTitleView3,
|
pub text: PhTitleView3,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Default, Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub(crate) struct PhTitleView3 {
|
pub(crate) struct PhTitleView3 {
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
#[serde_as(as = "VecSkipError<_>")]
|
||||||
|
@ -243,7 +242,7 @@ pub(crate) struct PhMetadataRow {
|
||||||
pub metadata_parts: Vec<TextWrap>,
|
pub metadata_parts: Vec<TextWrap>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub(crate) struct PhBannerView {
|
pub(crate) struct PhBannerView {
|
||||||
pub image_banner_view_model: ImageView,
|
pub image_banner_view_model: ImageView,
|
||||||
|
|
|
@ -34,6 +34,7 @@ pub(crate) use player::Player;
|
||||||
pub(crate) use playlist::Playlist;
|
pub(crate) use playlist::Playlist;
|
||||||
pub(crate) use search::Search;
|
pub(crate) use search::Search;
|
||||||
pub(crate) use search::SearchSuggestion;
|
pub(crate) use search::SearchSuggestion;
|
||||||
|
pub(crate) use trends::Startpage;
|
||||||
pub(crate) use trends::Trending;
|
pub(crate) use trends::Trending;
|
||||||
pub(crate) use url_endpoint::ResolvedUrl;
|
pub(crate) use url_endpoint::ResolvedUrl;
|
||||||
pub(crate) use video_details::VideoComments;
|
pub(crate) use video_details::VideoComments;
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use super::{video_item::YouTubeListRendererWrap, Tab, TwoColumnBrowseResults};
|
use super::{video_item::YouTubeListRendererWrap, ResponseContext, Tab, TwoColumnBrowseResults};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub(crate) struct Startpage {
|
||||||
|
pub contents: Contents,
|
||||||
|
pub response_context: ResponseContext,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|
|
@ -610,26 +610,28 @@ impl<T> YouTubeListMapper<T> {
|
||||||
|
|
||||||
fn map_channel(&mut self, channel: ChannelRenderer) -> ChannelItem {
|
fn map_channel(&mut self, channel: ChannelRenderer) -> ChannelItem {
|
||||||
// channel handle instead of subscriber count (A/B test 3)
|
// channel handle instead of subscriber count (A/B test 3)
|
||||||
let (handle, sc_txt) = if channel
|
let (sc_txt, vc_text) = if channel
|
||||||
.subscriber_count_text
|
.subscriber_count_text
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|txt| txt.starts_with('@'))
|
.map(|txt| txt.starts_with('@'))
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
{
|
{
|
||||||
(channel.subscriber_count_text, channel.video_count_text)
|
(channel.video_count_text, None)
|
||||||
} else {
|
} else {
|
||||||
(None, channel.subscriber_count_text)
|
(channel.subscriber_count_text, channel.video_count_text)
|
||||||
};
|
};
|
||||||
|
|
||||||
ChannelItem {
|
ChannelItem {
|
||||||
id: channel.channel_id,
|
id: channel.channel_id,
|
||||||
name: channel.title,
|
name: channel.title,
|
||||||
handle,
|
|
||||||
avatar: channel.thumbnail.into(),
|
avatar: channel.thumbnail.into(),
|
||||||
verification: channel.owner_badges.into(),
|
verification: channel.owner_badges.into(),
|
||||||
subscriber_count: sc_txt.and_then(|txt| {
|
subscriber_count: sc_txt.and_then(|txt| {
|
||||||
util::parse_large_numstr_or_warn(&txt, self.lang, &mut self.warnings)
|
util::parse_large_numstr_or_warn(&txt, self.lang, &mut self.warnings)
|
||||||
}),
|
}),
|
||||||
|
video_count: vc_text.and_then(|txt| {
|
||||||
|
util::parse_large_numstr_or_warn(&txt, self.lang, &mut self.warnings)
|
||||||
|
}),
|
||||||
short_description: channel.description_snippet,
|
short_description: channel.description_snippet,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,7 @@ expression: map_res.c
|
||||||
Channel(
|
Channel(
|
||||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||||
name: "EEVblog",
|
name: "EEVblog",
|
||||||
handle: None,
|
|
||||||
subscriber_count: Some(884000),
|
subscriber_count: Some(884000),
|
||||||
video_count: None,
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.ggpht.com/ytc/AMLnZu9eKk4Nd16fX4Rn1TF1G7ReluwOl6M5558FTYAM=s48-c-k-c0x00ffffff-no-rj",
|
url: "https://yt3.ggpht.com/ytc/AMLnZu9eKk4Nd16fX4Rn1TF1G7ReluwOl6M5558FTYAM=s48-c-k-c0x00ffffff-no-rj",
|
||||||
|
@ -57,6 +55,7 @@ Channel(
|
||||||
"dumpster diving",
|
"dumpster diving",
|
||||||
"debunking",
|
"debunking",
|
||||||
],
|
],
|
||||||
|
vanity_url: Some("https://www.youtube.com/c/EevblogDave"),
|
||||||
banner: [
|
banner: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
@ -89,6 +88,60 @@ Channel(
|
||||||
height: 424,
|
height: 424,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
mobile_banner: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w320-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 320,
|
||||||
|
height: 88,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w640-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 640,
|
||||||
|
height: 175,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w960-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 960,
|
||||||
|
height: 263,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1280-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1280,
|
||||||
|
height: 351,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1440-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1440,
|
||||||
|
height: 395,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tv_banner: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w320-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 320,
|
||||||
|
height: 180,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w854-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 854,
|
||||||
|
height: 480,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1280-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1920-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w2120-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 2120,
|
||||||
|
height: 1192,
|
||||||
|
),
|
||||||
|
],
|
||||||
has_shorts: false,
|
has_shorts: false,
|
||||||
has_live: true,
|
has_live: true,
|
||||||
visitor_data: None,
|
visitor_data: None,
|
||||||
|
|
|
@ -5,9 +5,7 @@ expression: map_res.c
|
||||||
Channel(
|
Channel(
|
||||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||||
name: "EEVblog",
|
name: "EEVblog",
|
||||||
handle: None,
|
|
||||||
subscriber_count: Some(881000),
|
subscriber_count: Some(881000),
|
||||||
video_count: None,
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.ggpht.com/ytc/AMLnZu9eKk4Nd16fX4Rn1TF1G7ReluwOl6M5558FTYAM=s48-c-k-c0x00ffffff-no-rj",
|
url: "https://yt3.ggpht.com/ytc/AMLnZu9eKk4Nd16fX4Rn1TF1G7ReluwOl6M5558FTYAM=s48-c-k-c0x00ffffff-no-rj",
|
||||||
|
@ -57,6 +55,7 @@ Channel(
|
||||||
"dumpster diving",
|
"dumpster diving",
|
||||||
"debunking",
|
"debunking",
|
||||||
],
|
],
|
||||||
|
vanity_url: Some("https://www.youtube.com/c/EevblogDave"),
|
||||||
banner: [
|
banner: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
@ -89,6 +88,60 @@ Channel(
|
||||||
height: 424,
|
height: 424,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
mobile_banner: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w320-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 320,
|
||||||
|
height: 88,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w640-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 640,
|
||||||
|
height: 175,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w960-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 960,
|
||||||
|
height: 263,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1280-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1280,
|
||||||
|
height: 351,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1440-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1440,
|
||||||
|
height: 395,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tv_banner: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w320-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 320,
|
||||||
|
height: 180,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w854-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 854,
|
||||||
|
height: 480,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1280-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1920-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w2120-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 2120,
|
||||||
|
height: 1192,
|
||||||
|
),
|
||||||
|
],
|
||||||
has_shorts: false,
|
has_shorts: false,
|
||||||
has_live: false,
|
has_live: false,
|
||||||
visitor_data: Some("CgttaWpyTVpUN1AyZyioqr2ZBg%3D%3D"),
|
visitor_data: Some("CgttaWpyTVpUN1AyZyioqr2ZBg%3D%3D"),
|
||||||
|
|
|
@ -5,9 +5,7 @@ expression: map_res.c
|
||||||
Channel(
|
Channel(
|
||||||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||||
name: "Doobydobap",
|
name: "Doobydobap",
|
||||||
handle: Some("@Doobydobap"),
|
|
||||||
subscriber_count: Some(3360000),
|
subscriber_count: Some(3360000),
|
||||||
video_count: None,
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.googleusercontent.com/dm5Aq93xvVJz0NoVO88ieBkDXmuShCujGPlZ7qETMEPTrXvPUCFI3-BB6Xs_P-r6Uk3mnBy9zA=s48-c-k-c0x00ffffff-no-rj",
|
url: "https://yt3.googleusercontent.com/dm5Aq93xvVJz0NoVO88ieBkDXmuShCujGPlZ7qETMEPTrXvPUCFI3-BB6Xs_P-r6Uk3mnBy9zA=s48-c-k-c0x00ffffff-no-rj",
|
||||||
|
@ -28,6 +26,7 @@ Channel(
|
||||||
verification: Verified,
|
verification: Verified,
|
||||||
description: "Hi, I’m Tina, aka Doobydobap!\n\nFood is the medium I use to tell stories and connect with people who share the same passion as I do. Whether it’s because you’re hungry at midnight or trying to learn how to cook, I hope you enjoy watching my content and recipes. Don\'t yuck my yum!\n\nwww.doobydobap.com\n",
|
description: "Hi, I’m Tina, aka Doobydobap!\n\nFood is the medium I use to tell stories and connect with people who share the same passion as I do. Whether it’s because you’re hungry at midnight or trying to learn how to cook, I hope you enjoy watching my content and recipes. Don\'t yuck my yum!\n\nwww.doobydobap.com\n",
|
||||||
tags: [],
|
tags: [],
|
||||||
|
vanity_url: Some("https://www.youtube.com/@Doobydobap"),
|
||||||
banner: [
|
banner: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.googleusercontent.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
url: "https://yt3.googleusercontent.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
@ -60,6 +59,60 @@ Channel(
|
||||||
height: 424,
|
height: 424,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
mobile_banner: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.googleusercontent.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w320-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 320,
|
||||||
|
height: 88,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.googleusercontent.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w640-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 640,
|
||||||
|
height: 175,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.googleusercontent.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w960-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 960,
|
||||||
|
height: 263,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.googleusercontent.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w1280-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1280,
|
||||||
|
height: 351,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.googleusercontent.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w1440-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1440,
|
||||||
|
height: 395,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tv_banner: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.googleusercontent.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w320-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 320,
|
||||||
|
height: 180,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.googleusercontent.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w854-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 854,
|
||||||
|
height: 480,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.googleusercontent.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w1280-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.googleusercontent.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w1920-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.googleusercontent.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w2120-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 2120,
|
||||||
|
height: 1192,
|
||||||
|
),
|
||||||
|
],
|
||||||
has_shorts: true,
|
has_shorts: true,
|
||||||
has_live: false,
|
has_live: false,
|
||||||
visitor_data: Some("CgtHU1dvWkR4cGRfdyjMpt6iBg%3D%3D"),
|
visitor_data: Some("CgtHU1dvWkR4cGRfdyjMpt6iBg%3D%3D"),
|
||||||
|
|
|
@ -5,9 +5,7 @@ expression: map_res.c
|
||||||
Channel(
|
Channel(
|
||||||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||||
name: "Doobydobap",
|
name: "Doobydobap",
|
||||||
handle: Some("@Doobydobap"),
|
|
||||||
subscriber_count: Some(3740000),
|
subscriber_count: Some(3740000),
|
||||||
video_count: None,
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.googleusercontent.com/dm5Aq93xvVJz0NoVO88ieBkDXmuShCujGPlZ7qETMEPTrXvPUCFI3-BB6Xs_P-r6Uk3mnBy9zA=s72-c-k-c0x00ffffff-no-rj",
|
url: "https://yt3.googleusercontent.com/dm5Aq93xvVJz0NoVO88ieBkDXmuShCujGPlZ7qETMEPTrXvPUCFI3-BB6Xs_P-r6Uk3mnBy9zA=s72-c-k-c0x00ffffff-no-rj",
|
||||||
|
@ -28,6 +26,7 @@ Channel(
|
||||||
verification: Verified,
|
verification: Verified,
|
||||||
description: "Hi, I’m Tina, aka Doobydobap!\n\nFood is the medium I use to tell stories and connect with people who share the same passion as I do. Whether it’s because you’re hungry at midnight or trying to learn how to cook, I hope you enjoy watching my content and recipes. Don\'t yuck my yum!\n\nwww.doobydobap.com\n",
|
description: "Hi, I’m Tina, aka Doobydobap!\n\nFood is the medium I use to tell stories and connect with people who share the same passion as I do. Whether it’s because you’re hungry at midnight or trying to learn how to cook, I hope you enjoy watching my content and recipes. Don\'t yuck my yum!\n\nwww.doobydobap.com\n",
|
||||||
tags: [],
|
tags: [],
|
||||||
|
vanity_url: Some("https://www.youtube.com/@Doobydobap"),
|
||||||
banner: [
|
banner: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.googleusercontent.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
url: "https://yt3.googleusercontent.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
@ -60,6 +59,8 @@ Channel(
|
||||||
height: 424,
|
height: 424,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
mobile_banner: [],
|
||||||
|
tv_banner: [],
|
||||||
has_shorts: true,
|
has_shorts: true,
|
||||||
has_live: false,
|
has_live: false,
|
||||||
visitor_data: None,
|
visitor_data: None,
|
||||||
|
|
|
@ -5,9 +5,7 @@ expression: map_res.c
|
||||||
Channel(
|
Channel(
|
||||||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||||
name: "Doobydobap",
|
name: "Doobydobap",
|
||||||
handle: None,
|
|
||||||
subscriber_count: Some(2930000),
|
subscriber_count: Some(2930000),
|
||||||
video_count: None,
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.ggpht.com/dm5Aq93xvVJz0NoVO88ieBkDXmuShCujGPlZ7qETMEPTrXvPUCFI3-BB6Xs_P-r6Uk3mnBy9zA=s48-c-k-c0x00ffffff-no-rj",
|
url: "https://yt3.ggpht.com/dm5Aq93xvVJz0NoVO88ieBkDXmuShCujGPlZ7qETMEPTrXvPUCFI3-BB6Xs_P-r6Uk3mnBy9zA=s48-c-k-c0x00ffffff-no-rj",
|
||||||
|
@ -28,6 +26,7 @@ Channel(
|
||||||
verification: Verified,
|
verification: Verified,
|
||||||
description: "Hi, I’m Tina, aka Doobydobap!\n\nFood is the medium I use to tell stories and connect with people who share the same passion as I do. Whether it’s because you’re hungry at midnight or trying to learn how to cook, I hope you enjoy watching my content and recipes. Don\'t yuck my yum!\n\nwww.doobydobap.com\n",
|
description: "Hi, I’m Tina, aka Doobydobap!\n\nFood is the medium I use to tell stories and connect with people who share the same passion as I do. Whether it’s because you’re hungry at midnight or trying to learn how to cook, I hope you enjoy watching my content and recipes. Don\'t yuck my yum!\n\nwww.doobydobap.com\n",
|
||||||
tags: [],
|
tags: [],
|
||||||
|
vanity_url: Some("https://www.youtube.com/c/Doobydobap"),
|
||||||
banner: [
|
banner: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
@ -60,6 +59,60 @@ Channel(
|
||||||
height: 424,
|
height: 424,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
mobile_banner: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w320-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 320,
|
||||||
|
height: 88,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w640-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 640,
|
||||||
|
height: 175,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w960-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 960,
|
||||||
|
height: 263,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w1280-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1280,
|
||||||
|
height: 351,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w1440-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1440,
|
||||||
|
height: 395,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tv_banner: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w320-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 320,
|
||||||
|
height: 180,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w854-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 854,
|
||||||
|
height: 480,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w1280-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w1920-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w2120-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 2120,
|
||||||
|
height: 1192,
|
||||||
|
),
|
||||||
|
],
|
||||||
has_shorts: true,
|
has_shorts: true,
|
||||||
has_live: false,
|
has_live: false,
|
||||||
visitor_data: Some("CgtQdE9zVVR3NVBDbyjz0ZKaBg%3D%3D"),
|
visitor_data: Some("CgtQdE9zVVR3NVBDbyjz0ZKaBg%3D%3D"),
|
||||||
|
|
|
@ -5,9 +5,7 @@ expression: map_res.c
|
||||||
Channel(
|
Channel(
|
||||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||||
name: "EEVblog",
|
name: "EEVblog",
|
||||||
handle: None,
|
|
||||||
subscriber_count: Some(883000),
|
subscriber_count: Some(883000),
|
||||||
video_count: None,
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.ggpht.com/ytc/AMLnZu9eKk4Nd16fX4Rn1TF1G7ReluwOl6M5558FTYAM=s48-c-k-c0x00ffffff-no-rj",
|
url: "https://yt3.ggpht.com/ytc/AMLnZu9eKk4Nd16fX4Rn1TF1G7ReluwOl6M5558FTYAM=s48-c-k-c0x00ffffff-no-rj",
|
||||||
|
@ -57,6 +55,7 @@ Channel(
|
||||||
"dumpster diving",
|
"dumpster diving",
|
||||||
"debunking",
|
"debunking",
|
||||||
],
|
],
|
||||||
|
vanity_url: Some("https://www.youtube.com/c/EevblogDave"),
|
||||||
banner: [
|
banner: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
@ -89,6 +88,60 @@ Channel(
|
||||||
height: 424,
|
height: 424,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
mobile_banner: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w320-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 320,
|
||||||
|
height: 88,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w640-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 640,
|
||||||
|
height: 175,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w960-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 960,
|
||||||
|
height: 263,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1280-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1280,
|
||||||
|
height: 351,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1440-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1440,
|
||||||
|
height: 395,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tv_banner: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w320-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 320,
|
||||||
|
height: 180,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w854-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 854,
|
||||||
|
height: 480,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1280-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1920-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w2120-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 2120,
|
||||||
|
height: 1192,
|
||||||
|
),
|
||||||
|
],
|
||||||
has_shorts: false,
|
has_shorts: false,
|
||||||
has_live: true,
|
has_live: true,
|
||||||
visitor_data: Some("Cgs4ZFVmMzVlU1dxbyiBqpeaBg%3D%3D"),
|
visitor_data: Some("Cgs4ZFVmMzVlU1dxbyiBqpeaBg%3D%3D"),
|
||||||
|
|
|
@ -5,9 +5,7 @@ expression: map_res.c
|
||||||
Channel(
|
Channel(
|
||||||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||||
name: "Coachella",
|
name: "Coachella",
|
||||||
handle: Some("@Coachella"),
|
|
||||||
subscriber_count: Some(2710000),
|
subscriber_count: Some(2710000),
|
||||||
video_count: None,
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "//yt3.googleusercontent.com/RDZ6VWFjHEMFm_QcmCCf-yG_UiGo9YWXEmVRuiHSC8SvP02dgeBEtAjd4CnEKGLo0V2gGdIRDQ=s88-c-k-c0x00ffffff-no-rj-mo",
|
url: "//yt3.googleusercontent.com/RDZ6VWFjHEMFm_QcmCCf-yG_UiGo9YWXEmVRuiHSC8SvP02dgeBEtAjd4CnEKGLo0V2gGdIRDQ=s88-c-k-c0x00ffffff-no-rj-mo",
|
||||||
|
@ -33,7 +31,10 @@ Channel(
|
||||||
"indio",
|
"indio",
|
||||||
"california",
|
"california",
|
||||||
],
|
],
|
||||||
|
vanity_url: Some("https://www.youtube.com/@Coachella"),
|
||||||
banner: [],
|
banner: [],
|
||||||
|
mobile_banner: [],
|
||||||
|
tv_banner: [],
|
||||||
has_shorts: true,
|
has_shorts: true,
|
||||||
has_live: true,
|
has_live: true,
|
||||||
visitor_data: Some("CgtjSUhDeVJ6SU5wNCj75uyhBg%3D%3D"),
|
visitor_data: Some("CgtjSUhDeVJ6SU5wNCj75uyhBg%3D%3D"),
|
||||||
|
|
|
@ -5,9 +5,7 @@ expression: map_res.c
|
||||||
Channel(
|
Channel(
|
||||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||||
name: "EEVblog",
|
name: "EEVblog",
|
||||||
handle: Some("@EEVblog"),
|
|
||||||
subscriber_count: Some(933000),
|
subscriber_count: Some(933000),
|
||||||
video_count: Some(19),
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.googleusercontent.com/ytc/AIdro_lagjGDfXbXlQXhznx3CDRitOBdxvebllQd_YP1ag=s72-c-k-c0x00ffffff-no-rj",
|
url: "https://yt3.googleusercontent.com/ytc/AIdro_lagjGDfXbXlQXhznx3CDRitOBdxvebllQd_YP1ag=s72-c-k-c0x00ffffff-no-rj",
|
||||||
|
@ -57,6 +55,7 @@ Channel(
|
||||||
"dumpster diving",
|
"dumpster diving",
|
||||||
"debunking",
|
"debunking",
|
||||||
],
|
],
|
||||||
|
vanity_url: Some("https://www.youtube.com/@EEVblog"),
|
||||||
banner: [
|
banner: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.googleusercontent.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
url: "https://yt3.googleusercontent.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
@ -89,6 +88,8 @@ Channel(
|
||||||
height: 424,
|
height: 424,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
mobile_banner: [],
|
||||||
|
tv_banner: [],
|
||||||
has_shorts: true,
|
has_shorts: true,
|
||||||
has_live: true,
|
has_live: true,
|
||||||
visitor_data: None,
|
visitor_data: None,
|
||||||
|
|
|
@ -5,9 +5,7 @@ expression: map_res.c
|
||||||
Channel(
|
Channel(
|
||||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||||
name: "EEVblog",
|
name: "EEVblog",
|
||||||
handle: None,
|
|
||||||
subscriber_count: Some(880000),
|
subscriber_count: Some(880000),
|
||||||
video_count: None,
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.ggpht.com/ytc/AMLnZu9eKk4Nd16fX4Rn1TF1G7ReluwOl6M5558FTYAM=s48-c-k-c0x00ffffff-no-rj",
|
url: "https://yt3.ggpht.com/ytc/AMLnZu9eKk4Nd16fX4Rn1TF1G7ReluwOl6M5558FTYAM=s48-c-k-c0x00ffffff-no-rj",
|
||||||
|
@ -57,6 +55,7 @@ Channel(
|
||||||
"dumpster diving",
|
"dumpster diving",
|
||||||
"debunking",
|
"debunking",
|
||||||
],
|
],
|
||||||
|
vanity_url: Some("https://www.youtube.com/c/EevblogDave"),
|
||||||
banner: [
|
banner: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
@ -89,6 +88,60 @@ Channel(
|
||||||
height: 424,
|
height: 424,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
mobile_banner: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w320-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 320,
|
||||||
|
height: 88,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w640-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 640,
|
||||||
|
height: 175,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w960-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 960,
|
||||||
|
height: 263,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1280-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1280,
|
||||||
|
height: 351,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1440-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1440,
|
||||||
|
height: 395,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tv_banner: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w320-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 320,
|
||||||
|
height: 180,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w854-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 854,
|
||||||
|
height: 480,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1280-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1920-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w2120-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 2120,
|
||||||
|
height: 1192,
|
||||||
|
),
|
||||||
|
],
|
||||||
has_shorts: false,
|
has_shorts: false,
|
||||||
has_live: false,
|
has_live: false,
|
||||||
visitor_data: Some("CgszNU5rbDVZS2hMcyim4K2ZBg%3D%3D"),
|
visitor_data: Some("CgszNU5rbDVZS2hMcyim4K2ZBg%3D%3D"),
|
||||||
|
|
|
@ -5,9 +5,7 @@ expression: map_res.c
|
||||||
Channel(
|
Channel(
|
||||||
id: "UCxBa895m48H5idw5li7h-0g",
|
id: "UCxBa895m48H5idw5li7h-0g",
|
||||||
name: "Sebastian Figurroa",
|
name: "Sebastian Figurroa",
|
||||||
handle: None,
|
|
||||||
subscriber_count: None,
|
subscriber_count: None,
|
||||||
video_count: None,
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.ggpht.com/ytc/AMLnZu_hsZ1XlUXHzXsGNHJw0np79WhWZcC4j8eFdy-tiUCDBKAjJyJOzE5kXFRiqL2S=s48-c-k-c0x00ffffff-no-rj",
|
url: "https://yt3.ggpht.com/ytc/AMLnZu_hsZ1XlUXHzXsGNHJw0np79WhWZcC4j8eFdy-tiUCDBKAjJyJOzE5kXFRiqL2S=s48-c-k-c0x00ffffff-no-rj",
|
||||||
|
@ -28,7 +26,10 @@ Channel(
|
||||||
verification: None,
|
verification: None,
|
||||||
description: "",
|
description: "",
|
||||||
tags: [],
|
tags: [],
|
||||||
|
vanity_url: None,
|
||||||
banner: [],
|
banner: [],
|
||||||
|
mobile_banner: [],
|
||||||
|
tv_banner: [],
|
||||||
has_shorts: false,
|
has_shorts: false,
|
||||||
has_live: false,
|
has_live: false,
|
||||||
visitor_data: Some("Cgtvc2s4UllvTGl6byigxseZBg%3D%3D"),
|
visitor_data: Some("Cgtvc2s4UllvTGl6byigxseZBg%3D%3D"),
|
||||||
|
|
|
@ -5,9 +5,7 @@ expression: map_res.c
|
||||||
Channel(
|
Channel(
|
||||||
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
||||||
name: "The Good Life Radio x Sensual Musique",
|
name: "The Good Life Radio x Sensual Musique",
|
||||||
handle: None,
|
|
||||||
subscriber_count: Some(760000),
|
subscriber_count: Some(760000),
|
||||||
video_count: None,
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.ggpht.com/ytc/AMLnZu_V9mOdHaorjNFqGXCecFeOBZhDWB8tVYG_I8gJwA=s48-c-k-c0x00ffffff-no-rj",
|
url: "https://yt3.ggpht.com/ytc/AMLnZu_V9mOdHaorjNFqGXCecFeOBZhDWB8tVYG_I8gJwA=s48-c-k-c0x00ffffff-no-rj",
|
||||||
|
@ -41,6 +39,7 @@ Channel(
|
||||||
"tropical house",
|
"tropical house",
|
||||||
"house music",
|
"house music",
|
||||||
],
|
],
|
||||||
|
vanity_url: Some("https://www.youtube.com/c/TheGoodLiferadio"),
|
||||||
banner: [
|
banner: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.ggpht.com/fL4x31Q80O_BvnhVIMI9YlV3apsiFvBENwGiSA-Hw9An6djAGw92RSOFax6z2r_rJNbRWPMA=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
url: "https://yt3.ggpht.com/fL4x31Q80O_BvnhVIMI9YlV3apsiFvBENwGiSA-Hw9An6djAGw92RSOFax6z2r_rJNbRWPMA=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
@ -73,6 +72,60 @@ Channel(
|
||||||
height: 424,
|
height: 424,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
mobile_banner: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/fL4x31Q80O_BvnhVIMI9YlV3apsiFvBENwGiSA-Hw9An6djAGw92RSOFax6z2r_rJNbRWPMA=w320-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 320,
|
||||||
|
height: 88,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/fL4x31Q80O_BvnhVIMI9YlV3apsiFvBENwGiSA-Hw9An6djAGw92RSOFax6z2r_rJNbRWPMA=w640-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 640,
|
||||||
|
height: 175,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/fL4x31Q80O_BvnhVIMI9YlV3apsiFvBENwGiSA-Hw9An6djAGw92RSOFax6z2r_rJNbRWPMA=w960-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 960,
|
||||||
|
height: 263,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/fL4x31Q80O_BvnhVIMI9YlV3apsiFvBENwGiSA-Hw9An6djAGw92RSOFax6z2r_rJNbRWPMA=w1280-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1280,
|
||||||
|
height: 351,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/fL4x31Q80O_BvnhVIMI9YlV3apsiFvBENwGiSA-Hw9An6djAGw92RSOFax6z2r_rJNbRWPMA=w1440-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1440,
|
||||||
|
height: 395,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tv_banner: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/fL4x31Q80O_BvnhVIMI9YlV3apsiFvBENwGiSA-Hw9An6djAGw92RSOFax6z2r_rJNbRWPMA=w320-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 320,
|
||||||
|
height: 180,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/fL4x31Q80O_BvnhVIMI9YlV3apsiFvBENwGiSA-Hw9An6djAGw92RSOFax6z2r_rJNbRWPMA=w854-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 854,
|
||||||
|
height: 480,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/fL4x31Q80O_BvnhVIMI9YlV3apsiFvBENwGiSA-Hw9An6djAGw92RSOFax6z2r_rJNbRWPMA=w1280-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/fL4x31Q80O_BvnhVIMI9YlV3apsiFvBENwGiSA-Hw9An6djAGw92RSOFax6z2r_rJNbRWPMA=w1920-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/fL4x31Q80O_BvnhVIMI9YlV3apsiFvBENwGiSA-Hw9An6djAGw92RSOFax6z2r_rJNbRWPMA=w2120-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 2120,
|
||||||
|
height: 1192,
|
||||||
|
),
|
||||||
|
],
|
||||||
has_shorts: false,
|
has_shorts: false,
|
||||||
has_live: false,
|
has_live: false,
|
||||||
visitor_data: Some("CgtkYXJITElwYmd4OCj85a2ZBg%3D%3D"),
|
visitor_data: Some("CgtkYXJITElwYmd4OCj85a2ZBg%3D%3D"),
|
||||||
|
|
|
@ -5,9 +5,7 @@ expression: map_res.c
|
||||||
Channel(
|
Channel(
|
||||||
id: "UC_vmjW5e1xEHhYjY2a0kK1A",
|
id: "UC_vmjW5e1xEHhYjY2a0kK1A",
|
||||||
name: "Oonagh - Topic",
|
name: "Oonagh - Topic",
|
||||||
handle: None,
|
|
||||||
subscriber_count: None,
|
subscriber_count: None,
|
||||||
video_count: None,
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.ggpht.com/pqKv4iqSjmMKPxsMCeyklTbpROSyInGNR4XvD1DqKD0AlROlsHzvoAlTvtMTO1g1x2WxaQ2Enxw=s48-c-k-c0x00ffffff-no-rj",
|
url: "https://yt3.ggpht.com/pqKv4iqSjmMKPxsMCeyklTbpROSyInGNR4XvD1DqKD0AlROlsHzvoAlTvtMTO1g1x2WxaQ2Enxw=s48-c-k-c0x00ffffff-no-rj",
|
||||||
|
@ -28,6 +26,7 @@ Channel(
|
||||||
verification: None,
|
verification: None,
|
||||||
description: "",
|
description: "",
|
||||||
tags: [],
|
tags: [],
|
||||||
|
vanity_url: None,
|
||||||
banner: [
|
banner: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.ggpht.com/EDatBjgcL94-qSfQa5Twr8l88hYcAXQJksDrwARWbotrWzJhG03gRyZLKV1mk1a1tMI_LSg4qg=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
url: "https://yt3.ggpht.com/EDatBjgcL94-qSfQa5Twr8l88hYcAXQJksDrwARWbotrWzJhG03gRyZLKV1mk1a1tMI_LSg4qg=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
@ -60,6 +59,60 @@ Channel(
|
||||||
height: 424,
|
height: 424,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
mobile_banner: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/EDatBjgcL94-qSfQa5Twr8l88hYcAXQJksDrwARWbotrWzJhG03gRyZLKV1mk1a1tMI_LSg4qg=w320-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 320,
|
||||||
|
height: 88,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/EDatBjgcL94-qSfQa5Twr8l88hYcAXQJksDrwARWbotrWzJhG03gRyZLKV1mk1a1tMI_LSg4qg=w640-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 640,
|
||||||
|
height: 175,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/EDatBjgcL94-qSfQa5Twr8l88hYcAXQJksDrwARWbotrWzJhG03gRyZLKV1mk1a1tMI_LSg4qg=w960-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 960,
|
||||||
|
height: 263,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/EDatBjgcL94-qSfQa5Twr8l88hYcAXQJksDrwARWbotrWzJhG03gRyZLKV1mk1a1tMI_LSg4qg=w1280-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1280,
|
||||||
|
height: 351,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/EDatBjgcL94-qSfQa5Twr8l88hYcAXQJksDrwARWbotrWzJhG03gRyZLKV1mk1a1tMI_LSg4qg=w1440-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1440,
|
||||||
|
height: 395,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tv_banner: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/EDatBjgcL94-qSfQa5Twr8l88hYcAXQJksDrwARWbotrWzJhG03gRyZLKV1mk1a1tMI_LSg4qg=w320-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 320,
|
||||||
|
height: 180,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/EDatBjgcL94-qSfQa5Twr8l88hYcAXQJksDrwARWbotrWzJhG03gRyZLKV1mk1a1tMI_LSg4qg=w854-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 854,
|
||||||
|
height: 480,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/EDatBjgcL94-qSfQa5Twr8l88hYcAXQJksDrwARWbotrWzJhG03gRyZLKV1mk1a1tMI_LSg4qg=w1280-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/EDatBjgcL94-qSfQa5Twr8l88hYcAXQJksDrwARWbotrWzJhG03gRyZLKV1mk1a1tMI_LSg4qg=w1920-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/EDatBjgcL94-qSfQa5Twr8l88hYcAXQJksDrwARWbotrWzJhG03gRyZLKV1mk1a1tMI_LSg4qg=w2120-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 2120,
|
||||||
|
height: 1192,
|
||||||
|
),
|
||||||
|
],
|
||||||
has_shorts: false,
|
has_shorts: false,
|
||||||
has_live: false,
|
has_live: false,
|
||||||
visitor_data: Some("CgtCV1l2R2Rzb2ZSZyiu4a2ZBg%3D%3D"),
|
visitor_data: Some("CgtCV1l2R2Rzb2ZSZyiu4a2ZBg%3D%3D"),
|
||||||
|
|
|
@ -5,9 +5,7 @@ expression: map_res.c
|
||||||
Channel(
|
Channel(
|
||||||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||||
name: "Doobydobap",
|
name: "Doobydobap",
|
||||||
handle: None,
|
|
||||||
subscriber_count: Some(2840000),
|
subscriber_count: Some(2840000),
|
||||||
video_count: None,
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.ggpht.com/dm5Aq93xvVJz0NoVO88ieBkDXmuShCujGPlZ7qETMEPTrXvPUCFI3-BB6Xs_P-r6Uk3mnBy9zA=s48-c-k-c0x00ffffff-no-rj",
|
url: "https://yt3.ggpht.com/dm5Aq93xvVJz0NoVO88ieBkDXmuShCujGPlZ7qETMEPTrXvPUCFI3-BB6Xs_P-r6Uk3mnBy9zA=s48-c-k-c0x00ffffff-no-rj",
|
||||||
|
@ -28,6 +26,7 @@ Channel(
|
||||||
verification: Verified,
|
verification: Verified,
|
||||||
description: "Hi, I’m Tina, aka Doobydobap!\n\nFood is the medium I use to tell stories and connect with people who share the same passion as I do. Whether it’s because you’re hungry at midnight or trying to learn how to cook, I hope you enjoy watching my content and recipes. Don\'t yuck my yum!\n\nwww.doobydobap.com\n",
|
description: "Hi, I’m Tina, aka Doobydobap!\n\nFood is the medium I use to tell stories and connect with people who share the same passion as I do. Whether it’s because you’re hungry at midnight or trying to learn how to cook, I hope you enjoy watching my content and recipes. Don\'t yuck my yum!\n\nwww.doobydobap.com\n",
|
||||||
tags: [],
|
tags: [],
|
||||||
|
vanity_url: Some("https://www.youtube.com/c/Doobydobap"),
|
||||||
banner: [
|
banner: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
@ -60,6 +59,60 @@ Channel(
|
||||||
height: 424,
|
height: 424,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
mobile_banner: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w320-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 320,
|
||||||
|
height: 88,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w640-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 640,
|
||||||
|
height: 175,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w960-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 960,
|
||||||
|
height: 263,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w1280-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1280,
|
||||||
|
height: 351,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w1440-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1440,
|
||||||
|
height: 395,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tv_banner: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w320-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 320,
|
||||||
|
height: 180,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w854-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 854,
|
||||||
|
height: 480,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w1280-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w1920-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/BvnAqgiursrXpmS9AgDLtkOSTQfOG_Dqn0KzY5hcwO9XrHTEQTVgaflI913f9KRp7d0U2qBp=w2120-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 2120,
|
||||||
|
height: 1192,
|
||||||
|
),
|
||||||
|
],
|
||||||
has_shorts: false,
|
has_shorts: false,
|
||||||
has_live: false,
|
has_live: false,
|
||||||
visitor_data: Some("CgtneXVRbGtSMWtlYyj75a2ZBg%3D%3D"),
|
visitor_data: Some("CgtneXVRbGtSMWtlYyj75a2ZBg%3D%3D"),
|
||||||
|
|
|
@ -5,9 +5,7 @@ expression: map_res.c
|
||||||
Channel(
|
Channel(
|
||||||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||||
name: "Adam Something",
|
name: "Adam Something",
|
||||||
handle: None,
|
|
||||||
subscriber_count: Some(947000),
|
subscriber_count: Some(947000),
|
||||||
video_count: None,
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.ggpht.com/FzV47fzr2nc8_KOeUO2FSIH-daaxCZaPDGqrgC1_Qp0_zEn0DnKmi7PiMwcssTG4IEDL1XfdTIk=s48-c-k-c0x00ffffff-no-rj",
|
url: "https://yt3.ggpht.com/FzV47fzr2nc8_KOeUO2FSIH-daaxCZaPDGqrgC1_Qp0_zEn0DnKmi7PiMwcssTG4IEDL1XfdTIk=s48-c-k-c0x00ffffff-no-rj",
|
||||||
|
@ -45,6 +43,7 @@ Channel(
|
||||||
"budapest",
|
"budapest",
|
||||||
"eu",
|
"eu",
|
||||||
],
|
],
|
||||||
|
vanity_url: Some("https://www.youtube.com/c/AdamSomething"),
|
||||||
banner: [
|
banner: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.ggpht.com/Bk54VHh5FsxlwAAEltJp6rgx3VzBgxbi8naNngh5C4zQni1ijUhgTmVmDrE_I9M95SxtXTnd=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
url: "https://yt3.ggpht.com/Bk54VHh5FsxlwAAEltJp6rgx3VzBgxbi8naNngh5C4zQni1ijUhgTmVmDrE_I9M95SxtXTnd=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
@ -77,6 +76,60 @@ Channel(
|
||||||
height: 424,
|
height: 424,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
mobile_banner: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/Bk54VHh5FsxlwAAEltJp6rgx3VzBgxbi8naNngh5C4zQni1ijUhgTmVmDrE_I9M95SxtXTnd=w320-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 320,
|
||||||
|
height: 88,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/Bk54VHh5FsxlwAAEltJp6rgx3VzBgxbi8naNngh5C4zQni1ijUhgTmVmDrE_I9M95SxtXTnd=w640-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 640,
|
||||||
|
height: 175,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/Bk54VHh5FsxlwAAEltJp6rgx3VzBgxbi8naNngh5C4zQni1ijUhgTmVmDrE_I9M95SxtXTnd=w960-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 960,
|
||||||
|
height: 263,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/Bk54VHh5FsxlwAAEltJp6rgx3VzBgxbi8naNngh5C4zQni1ijUhgTmVmDrE_I9M95SxtXTnd=w1280-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1280,
|
||||||
|
height: 351,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/Bk54VHh5FsxlwAAEltJp6rgx3VzBgxbi8naNngh5C4zQni1ijUhgTmVmDrE_I9M95SxtXTnd=w1440-fcrop64=1,32b75a57cd48a5a8-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1440,
|
||||||
|
height: 395,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tv_banner: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/Bk54VHh5FsxlwAAEltJp6rgx3VzBgxbi8naNngh5C4zQni1ijUhgTmVmDrE_I9M95SxtXTnd=w320-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 320,
|
||||||
|
height: 180,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/Bk54VHh5FsxlwAAEltJp6rgx3VzBgxbi8naNngh5C4zQni1ijUhgTmVmDrE_I9M95SxtXTnd=w854-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 854,
|
||||||
|
height: 480,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/Bk54VHh5FsxlwAAEltJp6rgx3VzBgxbi8naNngh5C4zQni1ijUhgTmVmDrE_I9M95SxtXTnd=w1280-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/Bk54VHh5FsxlwAAEltJp6rgx3VzBgxbi8naNngh5C4zQni1ijUhgTmVmDrE_I9M95SxtXTnd=w1920-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/Bk54VHh5FsxlwAAEltJp6rgx3VzBgxbi8naNngh5C4zQni1ijUhgTmVmDrE_I9M95SxtXTnd=w2120-fcrop64=1,00000000ffffffff-k-c0xffffffff-no-nd-rj",
|
||||||
|
width: 2120,
|
||||||
|
height: 1192,
|
||||||
|
),
|
||||||
|
],
|
||||||
has_shorts: false,
|
has_shorts: false,
|
||||||
has_live: false,
|
has_live: false,
|
||||||
visitor_data: Some("Cgs4Ri1tLW1KNWozNCjGk8yZBg%3D%3D"),
|
visitor_data: Some("Cgs4Ri1tLW1KNWozNCjGk8yZBg%3D%3D"),
|
||||||
|
|
|
@ -0,0 +1,884 @@
|
||||||
|
---
|
||||||
|
source: src/client/pagination.rs
|
||||||
|
expression: map_res.c
|
||||||
|
---
|
||||||
|
Paginator(
|
||||||
|
count: None,
|
||||||
|
items: [
|
||||||
|
Video(VideoItem(
|
||||||
|
id: "mRmlXh7Hams",
|
||||||
|
name: "Extra 3 vom 12.10.2022 im NDR | extra 3 | NDR",
|
||||||
|
duration: Some(1839),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/mRmlXh7Hams/hqdefault.jpg?sqp=-oaymwEcCOADEI4CSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAbO4lI0dDo_r85A1fi9XQS0rNiOQ",
|
||||||
|
width: 480,
|
||||||
|
height: 270,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCjhkuC_Pi85wGjnB0I1ydxw",
|
||||||
|
name: "extra 3",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/N2TrlnZnU3cYFrRcXmQhQ77IriCxoEl-XTapCJQ9UkEHEkb0gMYVASjewV5Rg1P0HPUOebRoYw=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: None,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("2 days ago"),
|
||||||
|
view_count: Some(585257),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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",
|
||||||
|
name: "Most Rarest Plays In Baseball History",
|
||||||
|
duration: Some(1975),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/LsXC5r64Pvc/hqdefault.jpg?sqp=-oaymwEcCOADEI4CSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLB2KXmgKxrJVUy3Naqi_R-R2X92FA",
|
||||||
|
width: 480,
|
||||||
|
height: 270,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCRfKJZ7LHueFudiDgAJDr9Q",
|
||||||
|
name: "Top All Sports",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/ytc/AMLnZu_dYWlP21FumM8m8ZxkKiTNaF9E68a2fnFnBo_q=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: None,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("3 weeks ago"),
|
||||||
|
view_count: Some(985521),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
is_upcoming: false,
|
||||||
|
short_description: Some("#baseball #mlb #mlbb"),
|
||||||
|
)),
|
||||||
|
Video(VideoItem(
|
||||||
|
id: "dwPmd1GqQHE",
|
||||||
|
name: "90S RAP & HIPHOP MIX - Notorious B I G , Dr Dre, 50 Cent, Snoop Dogg, 2Pac, DMX, Lil Jon and more",
|
||||||
|
duration: Some(5457),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/dwPmd1GqQHE/hqdefault.jpg?sqp=-oaymwEcCOADEI4CSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAAyGcLGzFkfdEmqqohpxZsGOM9Kw",
|
||||||
|
width: 480,
|
||||||
|
height: 270,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCKICAAGtBLJJ5zRdIxn_B4g",
|
||||||
|
name: "#Hip Hop 2022",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/fD5u3Lvkxe7oD0J3VlZ_Ih9BWtxT10wc68XWzSbVt02L88J2QrqO4FaK2xrsOoejD1GpBE7VAaA=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: None,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("5 months ago"),
|
||||||
|
view_count: Some(1654055),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
is_upcoming: false,
|
||||||
|
short_description: None,
|
||||||
|
)),
|
||||||
|
Video(VideoItem(
|
||||||
|
id: "qxI-Ob8lpLE",
|
||||||
|
name: "Schlatt\'s Chips Tier List",
|
||||||
|
duration: Some(1071),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/qxI-Ob8lpLE/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLBtEO5eB17tODb5Ek9GRoQwwVGtvA",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/qxI-Ob8lpLE/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAwDt0sa98qoI5O8u0kHJY7FbTrZg",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UC2mP7il3YV7TxM_3m6U0bwA",
|
||||||
|
name: "jschlattLIVE",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/Rr0aOvzRYLCyIDtIhIgkAYdQeagRlGDPzRuWoLrwGakM4VdnHPZHeSfUbiV-pJKmFbJ8LL9r5g=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("1 year ago"),
|
||||||
|
view_count: Some(9029628),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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",
|
||||||
|
name: "850€ für den Verrat am System - UCS AT-AT LEGO® Star Wars 75313",
|
||||||
|
duration: Some(2043),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/qmrzTUmZ4UU/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAsI3VS-wxnt1s_zS4M_YbVrV1pAg",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/qmrzTUmZ4UU/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLBYk7w0qGeW4kZchFr-tbydELUChQ",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UC_EZd3lsmxudu3IQzpTzOgw",
|
||||||
|
name: "Held der Steine Inh. Thomas Panke",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/ytc/AMLnZu8g9hFxZ2HD4P9pDsUxoAvkHwbZoTVNr3yw12i8YA=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("6 days ago"),
|
||||||
|
view_count: Some(600516),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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",
|
||||||
|
name: "🌉 Manhattan Jazz 💖 l Relaxing Jazz Piano Music l Background Music",
|
||||||
|
duration: Some(23229),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/4q4vpQCIZ6w/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLD4DKjgt5VJBRX2pH_KzI4Ru9AMaQ",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/4q4vpQCIZ6w/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLDMm9yeUF-9LH2rhU7jaQ6td05cMg",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCBnMxlW70f0SB4ZTJx124lw",
|
||||||
|
name: "몽키비지엠 MONKEYBGM",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/x8_XLvrLdd-Cs6z7Cmob2eZmqvbzmYdOdf6b7jLMry1z1YhdExnuqEhwRrYveu4X2airLfbv=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("6 months ago"),
|
||||||
|
view_count: Some(2343407),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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",
|
||||||
|
name: "1 in 1,000,000 NBA Moments",
|
||||||
|
duration: Some(567),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/Z_k31kqZxaE/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLCovxnIKW7TCP3XBcG4x-Acw10OBA",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/Z_k31kqZxaE/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLBa52Ie0cfnzg44jnkfTGzrCsVfOw",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCpyoYVlp67N16Lg1_N4VnVw",
|
||||||
|
name: "dime",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/HwpHaCaatHTI3N1imp5ZszL8_raSsxBq60UHScSpXC6e6VySeOlZ8Y3msYgum4vzCH5jmCxLvEU=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("1 month ago"),
|
||||||
|
view_count: Some(4334298),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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",
|
||||||
|
name: "Dua Lipa, Coldplay, Martin Garrix & Kygo, The Chainsmokers Style - Feeling Me",
|
||||||
|
duration: None,
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/zE-a5eqvlv8/hq720_live.jpg?sqp=COz4qZoG-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAbIAO-SIuWTC9f2AKu6Yp9nB0BwQ",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/zE-a5eqvlv8/hq720_live.jpg?sqp=COz4qZoG-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLDHdbRp6yOt4qkQk31BoFv6keTBYQ",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCX-USfenzQlhrEJR1zD5IYw",
|
||||||
|
name: "Deep Mood.",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/8WO05hff9bGjmlyPFo_PJRMIfHEoUvN_KbTcWRVX2yqeUO3fLgkz0K4MA6W95s3_NKdNUAwjow=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: None,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: None,
|
||||||
|
view_count: Some(889),
|
||||||
|
is_live: true,
|
||||||
|
is_short: false,
|
||||||
|
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",
|
||||||
|
name: "Soll ich dir 1g GOLD schenken? oder JEMAND anderen DOPPELT?",
|
||||||
|
duration: Some(704),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/gNlOk0LXi5M/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAy3JbiDcqUTwF6NS69UnX715q90w",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/gNlOk0LXi5M/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLDICPl-Jsul5nnhrac2s01gueUCDA",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCqcWNPTUVATZt0Dlr2jV0Wg",
|
||||||
|
name: "Mois",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/uHDIV2MwZnJRX8guX2KfFr4-gdxXK5x9nH0tz456hcBn0DH7LurNQbkAPjP5tSKg1Tqu07y9nKw=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("8 days ago"),
|
||||||
|
view_count: Some(463834),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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",
|
||||||
|
name: "Brad Pitt- Die Revanche eines Sexsymbols | Doku HD | ARTE",
|
||||||
|
duration: Some(3137),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/dbMvZjs8Yc8/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLB6HnYSCQFmEQ1V5qlFf5fblOpv-g",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/dbMvZjs8Yc8/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLD-AoMr1H_6EvzuWvg2whMDmbtY4A",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCsygZtQQSplGF6JA3XWvsdg",
|
||||||
|
name: "Irgendwas mit ARTE und Kultur",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/ytc/AMLnZu9_FXs7hsEndpcy9C4D_ZsM1xZzbLLThDQIL4-Dxg=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: None,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("5 days ago"),
|
||||||
|
view_count: Some(293878),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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",
|
||||||
|
name: "Craziest Soviet Machines You Won\'t Believe Exist - Part 1",
|
||||||
|
duration: Some(1569),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/mFxi3lOAcFs/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLCgPz_lsa3ENFNi2sC_uraWrUIuBQ",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/mFxi3lOAcFs/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLA2u97RbHNrNVp_Cb5m0DSvA0P02g",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCkQO3QsgTpNTsOw6ujimT5Q",
|
||||||
|
name: "BE AMAZED",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/ytc/AMLnZu_vmgpzJxLlR_1RA68cz8iITuzYLFFbPBvg5ULJlQ=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("1 year ago"),
|
||||||
|
view_count: Some(14056843),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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",
|
||||||
|
name: "People Hated Me For Using This Slab",
|
||||||
|
duration: Some(1264),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/eu7ubm7g59E/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLCg_b-6U2Pux_tZqAY8jkIa1JoTew",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/eu7ubm7g59E/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLA9WwjUr_EpS3PPYNG3e4N8EEr9oA",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UC6I0KzAD7uFTL1qzxyunkvA",
|
||||||
|
name: "Blacktail Studio",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/ytc/AMLnZu8jg6Uevc1qmfbksQ_xdJ0dF37PmZVFHkyNhouBTA=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("3 months ago"),
|
||||||
|
view_count: Some(2845035),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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",
|
||||||
|
name: "Christian Bale Breaks Down His Most Iconic Characters | GQ",
|
||||||
|
duration: Some(1381),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/TRGHIN2PGIA/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAMxhmIbADGzAlH1jNl6RN-ZU0eEQ",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/TRGHIN2PGIA/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLDxo3aBHktmxUOEuSdXJVHmlcR4-Q",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCsEukrAd64fqA7FjwkmZ_Dw",
|
||||||
|
name: "GQ",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/ytc/AMLnZu-gTmA2HcJO9Y5kYl4IUKG-jZ8QtojL8qaQiyW9kA=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("9 days ago"),
|
||||||
|
view_count: Some(8044465),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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",
|
||||||
|
name: "NFL Trick Plays But They Get Increasingly Higher IQ",
|
||||||
|
duration: Some(599),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/w3tENzcssDU/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLCZHp6o6cV9HNNJXPlI1FKi6S58qg",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/w3tENzcssDU/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLBH4K8b0AfAgX0MvL4oHlbianG8xQ",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCJka5SDh36_N4pjJd69efkg",
|
||||||
|
name: "Savage Brick Sports",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/ytc/AMLnZu_s0H6HPGb4LYTxkE6fH1Cp5Mp8jfeOaMluW2A03Q=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("3 months ago"),
|
||||||
|
view_count: Some(1172372),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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",
|
||||||
|
name: "⚓\u{fe0f}Found ABANDONED SHIP!!! Big CRUISE SHIP on a desert island☠\u{fe0f} Where did the people go?!?",
|
||||||
|
duration: Some(2949),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/gUAd2XXzH7w/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLDaBSyUxw88zjCr_Az868dEnhMrug",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/gUAd2XXzH7w/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAvfP1QR12y5cY8mvtg7Qqvl2XuTA",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UClUZos7yKYtrmr0-azaD8pw",
|
||||||
|
name: "Kreosan English",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/Rzi1oOWYL20M028wSLcD4eEkByC7kWGcBpr6WBAx0aGC9UAlIcGB_-D4rI_wkMsOHe9VnRWL3Q=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("1 month ago"),
|
||||||
|
view_count: Some(1883533),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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",
|
||||||
|
name: "[Working BGM] Comfortable music that makes you feel positive -- Morning Mood -- Daily Routine",
|
||||||
|
duration: Some(3651),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/YpGjaJ1ettI/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLDjAMJifo4Bg-vXUdHXyWYRHSf-Sw",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/YpGjaJ1ettI/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAx95bizFu4fxePN4qbMdKIoNDCug",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCpxY9-3iB5Hyho31uBgzh7w",
|
||||||
|
name: "Daily Routine",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/uci2aPM5XOEgdMt2h9aHMiN-K1-TmJQQPRdWvprNrpJpyZSLI9z0zFzyXQeQ1mNIQWl2QrjX3Rc=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: None,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("2 months ago"),
|
||||||
|
view_count: Some(1465389),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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",
|
||||||
|
name: "Survival Camping 9ft/3m Under Snow - Giant Winter Bushcraft Shelter and Quinzee",
|
||||||
|
duration: Some(1301),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/rPAhFD8hKxQ/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLCY0Xhznr6RKZ-EG1G5C1M34h8ugA",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/rPAhFD8hKxQ/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLBiANoEaNfk7eMjCAxapIK5NiYmmQ",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCfpCQ89W9wjkHc8J_6eTbBg",
|
||||||
|
name: "Outdoor Boys",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/ytc/AMLnZu8v_ZMJTqxqU7M__w8nHHaygAyOvsqCnFeIhjQxFw=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("6 months ago"),
|
||||||
|
view_count: Some(20488431),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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",
|
||||||
|
name: "Pink Panther Fights Off Pests | 54 Minute Compilation | The Pink Panther Show",
|
||||||
|
duration: Some(3158),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/2rye4u-cCNk/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLCi4Tt2tz-kk-cumb7SEfzzgixj5A",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/2rye4u-cCNk/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLD4QbHfCufvmol1UNj5wqmOtjZNvw",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCFeUyPY6W8qX8w2o6oSiRmw",
|
||||||
|
name: "Official Pink Panther",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/ytc/AMLnZu-htKBt4jUDwmnm0r-ojGjHZMy9-H92Q1pRoAfkgw=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("11 months ago"),
|
||||||
|
view_count: Some(27357653),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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",
|
||||||
|
name: "FC Nantes vs. SC Freiburg – Highlights & Tore | UEFA Europa League",
|
||||||
|
duration: Some(326),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/O0xAlfSaBNQ/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLDe-1NUODMNivJw5r5J5Wd16PMsqA",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/O0xAlfSaBNQ/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAMD0BFcC-x_UYe-F5q5y4GPcGnWA",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UC8WYi3XQXsf-6FNvqoEvxag",
|
||||||
|
name: "RTL Sport",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/E1ZL4Cnc8ej3MeHR0To12hetHWrlhcupsz0nFyZmEJoWvLvJo9aOXvPOWmNMWn9tJLoMB3duRg=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("11 hours ago"),
|
||||||
|
view_count: Some(117395),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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",
|
||||||
|
name: "Dramatisches Duell: 400 Jahre altes Kästchen erzielt zig-fachen Wunschpreis! | Bares für Rares XXL",
|
||||||
|
duration: Some(744),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/Mhs9Sbnw19o/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLBkxXdE8JNS0S6_Dhl-aY7FRmbL9g",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/Mhs9Sbnw19o/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAqbRhx4fQfK_2mVGNX_0_dZQt0YQ",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UC53bIpnef1pwAx69ERmmOLA",
|
||||||
|
name: "Bares für Rares",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/ytc/AMLnZu-ZyE4lblLYyk8iis1xoH_v64_tmhWca2Z6wmsVexk=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("11 days ago"),
|
||||||
|
view_count: Some(836333),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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",
|
||||||
|
name: "Sweet Jazz - Cool autumn Bossa Nova & October Jazz Positive Mood",
|
||||||
|
duration: None,
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/Bzzp5Cay7DI/hq720_live.jpg?sqp=COz4qZoG-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAKcYaDyG1yocH1e2_BIyl5FGKWPw",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/Bzzp5Cay7DI/hq720_live.jpg?sqp=COz4qZoG-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLBOaXPCJec4XuaFyJ1-6dcnJWEmrg",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCoGlllJE7aYe_VzIGP3s_wA",
|
||||||
|
name: "Smooth Jazz Music",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/babJ-iwY1cNs3mE2CnDiBSf0IjePgGuCLNLvLGcepj6tzXNLbSAQA7rQho35fKv9qFxEVIWdCw=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: None,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: None,
|
||||||
|
view_count: Some(1216),
|
||||||
|
is_live: true,
|
||||||
|
is_short: false,
|
||||||
|
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",
|
||||||
|
name: "The Chick-Fil-A Full Menu Challenge",
|
||||||
|
duration: Some(613),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/SlskTqc9CEc/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLBjDpJq0J5r8jvLwIQG2HCvsoj8nw",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/SlskTqc9CEc/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLCnwo-jiD8xsP29kf6a5jMwIqHPEA",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCd1fLoVFooPeWqCEYVUJZqg",
|
||||||
|
name: "Matt Stonie",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/ytc/AMLnZu9Of1-RwNeaBY6nulF3DECzDcAdZRbC_aOvZHPedw=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("3 years ago"),
|
||||||
|
view_count: Some(39286403),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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",
|
||||||
|
name: "Gentle healing music of health and to calm the nervous system, deep relaxation! Say Life Yes",
|
||||||
|
duration: None,
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/CwRvM2TfYbs/hq720_live.jpg?sqp=COz4qZoG-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLCj3HTq1K0KCuiuZdyh_by4VUZWeA",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/CwRvM2TfYbs/hq720_live.jpg?sqp=COz4qZoG-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLA-rjU_R19afFlCk22vmfHEtfFKcA",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UC6jH5GNi0iOR17opA1Vowhw",
|
||||||
|
name: "Lucid Dream",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/QlTKeA9Cx-4qajm4VaLGGGH0cCVe8Fda_c6SScCLPy8fsu0ZQkDhtBB3qcZastIZPQNew5vi-LM=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: None,
|
||||||
|
view_count: Some(1416),
|
||||||
|
is_live: true,
|
||||||
|
is_short: false,
|
||||||
|
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",
|
||||||
|
name: "Craziest \"Fine...I\'ll Do it Myself\" Moments in Sports History (PART 2)",
|
||||||
|
duration: Some(1822),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/7jz0pXSe_kI/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLDEUQzJHcD0s2BgP1znPupwsxf48w",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/7jz0pXSe_kI/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLB1yzi-24jCXlAki1xIq0aDMqQY3A",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCd5hdemikI6GxwGKhJCwzww",
|
||||||
|
name: "Highlight Reel",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/NETjJS3cNlblrg70CD4LH_Mma5lYmZSO3NlUnzi5Vd_cRD3XkVyaO1UCFTq6acK52g9XDly9-A=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("10 months ago"),
|
||||||
|
view_count: Some(11601863),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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,
|
||||||
|
)
|
|
@ -9,7 +9,6 @@ SearchResult(
|
||||||
Channel(ChannelItem(
|
Channel(ChannelItem(
|
||||||
id: "UCMwePVHRpDdfeUcwtDZu2Dw",
|
id: "UCMwePVHRpDdfeUcwtDZu2Dw",
|
||||||
name: "Monstafluff Music",
|
name: "Monstafluff Music",
|
||||||
handle: Some("@MonstafluffMusic"),
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "//yt3.ggpht.com/ytc/AMLnZu9YhTzdAoL6P4PYq51PCF076ITDrgLitxSDPqv6sw=s88-c-k-c0x00ffffff-no-rj-mo",
|
url: "//yt3.ggpht.com/ytc/AMLnZu9YhTzdAoL6P4PYq51PCF076ITDrgLitxSDPqv6sw=s88-c-k-c0x00ffffff-no-rj-mo",
|
||||||
|
@ -24,12 +23,12 @@ SearchResult(
|
||||||
],
|
],
|
||||||
verification: Verified,
|
verification: Verified,
|
||||||
subscriber_count: Some(582000),
|
subscriber_count: Some(582000),
|
||||||
|
video_count: None,
|
||||||
short_description: "Music Submissions: https://monstafluff.edmdistrict.com/",
|
short_description: "Music Submissions: https://monstafluff.edmdistrict.com/",
|
||||||
)),
|
)),
|
||||||
Channel(ChannelItem(
|
Channel(ChannelItem(
|
||||||
id: "UCLxAS02eWvfZK4icRNzWD_g",
|
id: "UCLxAS02eWvfZK4icRNzWD_g",
|
||||||
name: "Music Travel Love",
|
name: "Music Travel Love",
|
||||||
handle: Some("@MusicTravelLove"),
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "https://yt3.ggpht.com/ytc/AMLnZu9njNDLU_VtFjfGUaTArBp4AJFhJIxb_CxP7knf3A=s88-c-k-c0x00ffffff-no-rj-mo",
|
url: "https://yt3.ggpht.com/ytc/AMLnZu9njNDLU_VtFjfGUaTArBp4AJFhJIxb_CxP7knf3A=s88-c-k-c0x00ffffff-no-rj-mo",
|
||||||
|
@ -44,12 +43,12 @@ SearchResult(
|
||||||
],
|
],
|
||||||
verification: Artist,
|
verification: Artist,
|
||||||
subscriber_count: Some(4030000),
|
subscriber_count: Some(4030000),
|
||||||
|
video_count: None,
|
||||||
short_description: "Welcome to the official Music Travel Love YouTube channel! We travel the world making music, friends, videos and memories!",
|
short_description: "Welcome to the official Music Travel Love YouTube channel! We travel the world making music, friends, videos and memories!",
|
||||||
)),
|
)),
|
||||||
Channel(ChannelItem(
|
Channel(ChannelItem(
|
||||||
id: "UCxKxjNPyL9UO5LRWHzp5JxA",
|
id: "UCxKxjNPyL9UO5LRWHzp5JxA",
|
||||||
name: "Black&White Music",
|
name: "Black&White Music",
|
||||||
handle: Some("@blackwhitemusic5836"),
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "//yt3.ggpht.com/FDjW2-Cb6tFbtNv02D1UX4XtvP7P3eEWB93hGimeP4pb2TadVhAgxSVMZLZDp5NiBWGLT5eprA=s88-c-k-c0x00ffffff-no-rj-mo",
|
url: "//yt3.ggpht.com/FDjW2-Cb6tFbtNv02D1UX4XtvP7P3eEWB93hGimeP4pb2TadVhAgxSVMZLZDp5NiBWGLT5eprA=s88-c-k-c0x00ffffff-no-rj-mo",
|
||||||
|
@ -64,12 +63,12 @@ SearchResult(
|
||||||
],
|
],
|
||||||
verification: Verified,
|
verification: Verified,
|
||||||
subscriber_count: Some(167000),
|
subscriber_count: Some(167000),
|
||||||
|
video_count: None,
|
||||||
short_description: "MUSIC IN HARMONY WITH YOUR LIFE!!! If any producer, label, artist or photographer has an issue with any of the music or\u{a0}...",
|
short_description: "MUSIC IN HARMONY WITH YOUR LIFE!!! If any producer, label, artist or photographer has an issue with any of the music or\u{a0}...",
|
||||||
)),
|
)),
|
||||||
Channel(ChannelItem(
|
Channel(ChannelItem(
|
||||||
id: "UCGIygiYkKxn7g7fFNFdXskg",
|
id: "UCGIygiYkKxn7g7fFNFdXskg",
|
||||||
name: "HAEVN MUSIC",
|
name: "HAEVN MUSIC",
|
||||||
handle: Some("@HAEVNMUSIC"),
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "//yt3.ggpht.com/EYlGIfqhvwtfkCyi5vpqfY_kDHr6L3OeCmkudNiAyhvz6UCnTZQOQaM-8PelFDGofdIqeF7Mb4E=s88-c-k-c0x00ffffff-no-rj-mo",
|
url: "//yt3.ggpht.com/EYlGIfqhvwtfkCyi5vpqfY_kDHr6L3OeCmkudNiAyhvz6UCnTZQOQaM-8PelFDGofdIqeF7Mb4E=s88-c-k-c0x00ffffff-no-rj-mo",
|
||||||
|
@ -84,12 +83,12 @@ SearchResult(
|
||||||
],
|
],
|
||||||
verification: Artist,
|
verification: Artist,
|
||||||
subscriber_count: Some(411000),
|
subscriber_count: Some(411000),
|
||||||
|
video_count: None,
|
||||||
short_description: "The official YouTube channel of HAEVN Music. Receiving a piano from his grandfather had a great impact on Jorrit\'s life.",
|
short_description: "The official YouTube channel of HAEVN Music. Receiving a piano from his grandfather had a great impact on Jorrit\'s life.",
|
||||||
)),
|
)),
|
||||||
Channel(ChannelItem(
|
Channel(ChannelItem(
|
||||||
id: "UClvNJkDHdc1gvFGN_Fr_qPw",
|
id: "UClvNJkDHdc1gvFGN_Fr_qPw",
|
||||||
name: "Artemis Music",
|
name: "Artemis Music",
|
||||||
handle: Some("@artemismusic1000"),
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "//yt3.ggpht.com/rGXIwYAhI49rKBQmw_pKFMv9yEt4euHnmXOE0OOCD6ApdQXGnuPmEv7TK7cDjrjt0rUXYHuw=s88-c-k-c0x00ffffff-no-rj-mo",
|
url: "//yt3.ggpht.com/rGXIwYAhI49rKBQmw_pKFMv9yEt4euHnmXOE0OOCD6ApdQXGnuPmEv7TK7cDjrjt0rUXYHuw=s88-c-k-c0x00ffffff-no-rj-mo",
|
||||||
|
@ -104,12 +103,12 @@ SearchResult(
|
||||||
],
|
],
|
||||||
verification: None,
|
verification: None,
|
||||||
subscriber_count: Some(31200),
|
subscriber_count: Some(31200),
|
||||||
|
video_count: None,
|
||||||
short_description: "Hello and welcome to \"Artemis Music\"! Music can play an effective role in helping us lead a better and more productive life.",
|
short_description: "Hello and welcome to \"Artemis Music\"! Music can play an effective role in helping us lead a better and more productive life.",
|
||||||
)),
|
)),
|
||||||
Channel(ChannelItem(
|
Channel(ChannelItem(
|
||||||
id: "UC5r3j8tQsB3MYZiwQFGKrdA",
|
id: "UC5r3j8tQsB3MYZiwQFGKrdA",
|
||||||
name: "Disco Music",
|
name: "Disco Music",
|
||||||
handle: Some("@discomusic9273"),
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "//yt3.ggpht.com/5nqhAdf26KoSKbfUB8kvhJo6rpMQw3XS345h8ZNmeXScqlB1KjJAM0T371r3QcS1mA1LZg9B1Po=s88-c-k-c0x00ffffff-no-rj-mo",
|
url: "//yt3.ggpht.com/5nqhAdf26KoSKbfUB8kvhJo6rpMQw3XS345h8ZNmeXScqlB1KjJAM0T371r3QcS1mA1LZg9B1Po=s88-c-k-c0x00ffffff-no-rj-mo",
|
||||||
|
@ -124,12 +123,12 @@ SearchResult(
|
||||||
],
|
],
|
||||||
verification: Verified,
|
verification: Verified,
|
||||||
subscriber_count: Some(372000),
|
subscriber_count: Some(372000),
|
||||||
|
video_count: None,
|
||||||
short_description: "Music is the only language in which you cannot say a mean or sarcastic thing. Have fun listening to music.",
|
short_description: "Music is the only language in which you cannot say a mean or sarcastic thing. Have fun listening to music.",
|
||||||
)),
|
)),
|
||||||
Channel(ChannelItem(
|
Channel(ChannelItem(
|
||||||
id: "UCNZYpcqym8gHcNg2GWcC6nQ",
|
id: "UCNZYpcqym8gHcNg2GWcC6nQ",
|
||||||
name: "S!X - Music",
|
name: "S!X - Music",
|
||||||
handle: Some("@s1x-music"),
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "//yt3.googleusercontent.com/ytc/AMLnZu_1NOzbZUJWZjtmD4NTsb9BR-TNIAzNoajv0TisvQ=s88-c-k-c0x00ffffff-no-rj-mo",
|
url: "//yt3.googleusercontent.com/ytc/AMLnZu_1NOzbZUJWZjtmD4NTsb9BR-TNIAzNoajv0TisvQ=s88-c-k-c0x00ffffff-no-rj-mo",
|
||||||
|
@ -144,12 +143,12 @@ SearchResult(
|
||||||
],
|
],
|
||||||
verification: Verified,
|
verification: Verified,
|
||||||
subscriber_count: Some(178000),
|
subscriber_count: Some(178000),
|
||||||
|
video_count: None,
|
||||||
short_description: "S!X - Music is an independent Hip-Hop label. Soundcloud : https://soundcloud.com/s1xmusic Facebook\u{a0}...",
|
short_description: "S!X - Music is an independent Hip-Hop label. Soundcloud : https://soundcloud.com/s1xmusic Facebook\u{a0}...",
|
||||||
)),
|
)),
|
||||||
Channel(ChannelItem(
|
Channel(ChannelItem(
|
||||||
id: "UCoEryX-WO7IHBGqTAC5r9Zw",
|
id: "UCoEryX-WO7IHBGqTAC5r9Zw",
|
||||||
name: "Shake Music",
|
name: "Shake Music",
|
||||||
handle: Some("@ShakeMusic"),
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "//yt3.googleusercontent.com/ytc/AMLnZu9fMXUALsloNUJ_wLpqCS0ovprvc5W-XwfrpmWqIw=s88-c-k-c0x00ffffff-no-rj-mo",
|
url: "//yt3.googleusercontent.com/ytc/AMLnZu9fMXUALsloNUJ_wLpqCS0ovprvc5W-XwfrpmWqIw=s88-c-k-c0x00ffffff-no-rj-mo",
|
||||||
|
@ -164,12 +163,12 @@ SearchResult(
|
||||||
],
|
],
|
||||||
verification: Verified,
|
verification: Verified,
|
||||||
subscriber_count: Some(1040000),
|
subscriber_count: Some(1040000),
|
||||||
|
video_count: None,
|
||||||
short_description: "Welcome to Shake Music, a Trap & Bass Channel / Record Label dedicated to bringing you the best tracks. All tracks on Shake\u{a0}...",
|
short_description: "Welcome to Shake Music, a Trap & Bass Channel / Record Label dedicated to bringing you the best tracks. All tracks on Shake\u{a0}...",
|
||||||
)),
|
)),
|
||||||
Channel(ChannelItem(
|
Channel(ChannelItem(
|
||||||
id: "UCTJ9Qg-1vBu2pP_YrWUfGnQ",
|
id: "UCTJ9Qg-1vBu2pP_YrWUfGnQ",
|
||||||
name: "Miracle Music",
|
name: "Miracle Music",
|
||||||
handle: Some("@miraclemusic2328"),
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "//yt3.ggpht.com/3RMarDSmUSIexCXWCpMUkqV64uiHDXTidBLwsObHstx5-AbB8h_n8Zy1W9JymURd7ivzlDEGFw=s88-c-k-c0x00ffffff-no-rj-mo",
|
url: "//yt3.ggpht.com/3RMarDSmUSIexCXWCpMUkqV64uiHDXTidBLwsObHstx5-AbB8h_n8Zy1W9JymURd7ivzlDEGFw=s88-c-k-c0x00ffffff-no-rj-mo",
|
||||||
|
@ -184,12 +183,12 @@ SearchResult(
|
||||||
],
|
],
|
||||||
verification: Verified,
|
verification: Verified,
|
||||||
subscriber_count: Some(822000),
|
subscriber_count: Some(822000),
|
||||||
|
video_count: None,
|
||||||
short_description: "Welcome to Miracle Music! On this channel you will find a wide variety of different Deep House, Tropical House, Chill Out, EDM,.",
|
short_description: "Welcome to Miracle Music! On this channel you will find a wide variety of different Deep House, Tropical House, Chill Out, EDM,.",
|
||||||
)),
|
)),
|
||||||
Channel(ChannelItem(
|
Channel(ChannelItem(
|
||||||
id: "UCp6_KuNhT0kcFk-jXw9Tivg",
|
id: "UCp6_KuNhT0kcFk-jXw9Tivg",
|
||||||
name: "Magic Music",
|
name: "Magic Music",
|
||||||
handle: Some("@MagicMusicGroup"),
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "//yt3.googleusercontent.com/ytc/AMLnZu-fgSc_lceD4fRL_y0b3MKd2k54DF-laDAR3Avbuw=s88-c-k-c0x00ffffff-no-rj-mo",
|
url: "//yt3.googleusercontent.com/ytc/AMLnZu-fgSc_lceD4fRL_y0b3MKd2k54DF-laDAR3Avbuw=s88-c-k-c0x00ffffff-no-rj-mo",
|
||||||
|
@ -204,12 +203,12 @@ SearchResult(
|
||||||
],
|
],
|
||||||
verification: Verified,
|
verification: Verified,
|
||||||
subscriber_count: Some(4620000),
|
subscriber_count: Some(4620000),
|
||||||
|
video_count: None,
|
||||||
short_description: "",
|
short_description: "",
|
||||||
)),
|
)),
|
||||||
Channel(ChannelItem(
|
Channel(ChannelItem(
|
||||||
id: "UCe55Gy-hFDvLZp8C8BZhBnw",
|
id: "UCe55Gy-hFDvLZp8C8BZhBnw",
|
||||||
name: "Nightblue Music",
|
name: "Nightblue Music",
|
||||||
handle: Some("@NightblueMusic"),
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "//yt3.googleusercontent.com/ytc/AMLnZu-29SYt5qpqMP9Xi2A98mqL8ymI5Lg7Vzx-qpY09w=s88-c-k-c0x00ffffff-no-rj-mo",
|
url: "//yt3.googleusercontent.com/ytc/AMLnZu-29SYt5qpqMP9Xi2A98mqL8ymI5Lg7Vzx-qpY09w=s88-c-k-c0x00ffffff-no-rj-mo",
|
||||||
|
@ -224,12 +223,12 @@ SearchResult(
|
||||||
],
|
],
|
||||||
verification: Verified,
|
verification: Verified,
|
||||||
subscriber_count: Some(1050000),
|
subscriber_count: Some(1050000),
|
||||||
|
video_count: None,
|
||||||
short_description: "BRINGING YOU ONLY THE BEST EDM - TRAP Submit your own track for promotion here:\u{a0}...",
|
short_description: "BRINGING YOU ONLY THE BEST EDM - TRAP Submit your own track for promotion here:\u{a0}...",
|
||||||
)),
|
)),
|
||||||
Channel(ChannelItem(
|
Channel(ChannelItem(
|
||||||
id: "UC2fVSthyWxWSjsiEAHPzriQ",
|
id: "UC2fVSthyWxWSjsiEAHPzriQ",
|
||||||
name: "Mr_MoMo Music",
|
name: "Mr_MoMo Music",
|
||||||
handle: Some("@MrMoMoMusic"),
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "//yt3.ggpht.com/7YG4jSrhx_Mfi2TsV0rJFlFARaR8kl7ilcIyzs6gSeNjwn-J88DvDWD8PSNd5o03qJRzpvhs=s88-c-k-c0x00ffffff-no-rj-mo",
|
url: "//yt3.ggpht.com/7YG4jSrhx_Mfi2TsV0rJFlFARaR8kl7ilcIyzs6gSeNjwn-J88DvDWD8PSNd5o03qJRzpvhs=s88-c-k-c0x00ffffff-no-rj-mo",
|
||||||
|
@ -244,12 +243,12 @@ SearchResult(
|
||||||
],
|
],
|
||||||
verification: Verified,
|
verification: Verified,
|
||||||
subscriber_count: Some(709000),
|
subscriber_count: Some(709000),
|
||||||
|
video_count: None,
|
||||||
short_description: "Hey there! I am Mr MoMo My channel focus on Japan music, lofi, trap & bass type beat and Japanese instrumental. I mindfully\u{a0}...",
|
short_description: "Hey there! I am Mr MoMo My channel focus on Japan music, lofi, trap & bass type beat and Japanese instrumental. I mindfully\u{a0}...",
|
||||||
)),
|
)),
|
||||||
Channel(ChannelItem(
|
Channel(ChannelItem(
|
||||||
id: "UCN31w7dRjjz8CeP0GfSIo8A",
|
id: "UCN31w7dRjjz8CeP0GfSIo8A",
|
||||||
name: "Danit Music Official",
|
name: "Danit Music Official",
|
||||||
handle: Some("@danitmusicofficial5734"),
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "//yt3.ggpht.com/ytc/AMLnZu9rUKtDsY-aSoE5WEwAQxvQTXiuAPYMBoJQ2mYTUA=s88-c-k-c0x00ffffff-no-rj-mo",
|
url: "//yt3.ggpht.com/ytc/AMLnZu9rUKtDsY-aSoE5WEwAQxvQTXiuAPYMBoJQ2mYTUA=s88-c-k-c0x00ffffff-no-rj-mo",
|
||||||
|
@ -264,12 +263,12 @@ SearchResult(
|
||||||
],
|
],
|
||||||
verification: None,
|
verification: None,
|
||||||
subscriber_count: Some(54400),
|
subscriber_count: Some(54400),
|
||||||
|
video_count: None,
|
||||||
short_description: "",
|
short_description: "",
|
||||||
)),
|
)),
|
||||||
Channel(ChannelItem(
|
Channel(ChannelItem(
|
||||||
id: "UCpEHWiTMk1eEBAdzBnAb3rA",
|
id: "UCpEHWiTMk1eEBAdzBnAb3rA",
|
||||||
name: "Energy Transformation Relaxing Music ",
|
name: "Energy Transformation Relaxing Music ",
|
||||||
handle: Some("@energytransformationrelaxi5596"),
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "//yt3.ggpht.com/RR7upyAvT7N0_qlZWfLlDSRPhLufX4W4X6-qahWvuvDCLn2cWCs0yh_HXB2iwGbk_MTwSqwWEQ=s88-c-k-c0x00ffffff-no-rj-mo",
|
url: "//yt3.ggpht.com/RR7upyAvT7N0_qlZWfLlDSRPhLufX4W4X6-qahWvuvDCLn2cWCs0yh_HXB2iwGbk_MTwSqwWEQ=s88-c-k-c0x00ffffff-no-rj-mo",
|
||||||
|
@ -284,12 +283,12 @@ SearchResult(
|
||||||
],
|
],
|
||||||
verification: None,
|
verification: None,
|
||||||
subscriber_count: Some(3590),
|
subscriber_count: Some(3590),
|
||||||
|
video_count: None,
|
||||||
short_description: "Welcome to our Energy Transformation Relaxing Music . This chakra music channel will focus on developing the best chakra\u{a0}...",
|
short_description: "Welcome to our Energy Transformation Relaxing Music . This chakra music channel will focus on developing the best chakra\u{a0}...",
|
||||||
)),
|
)),
|
||||||
Channel(ChannelItem(
|
Channel(ChannelItem(
|
||||||
id: "UCqswUMaC5yWUrkQszr8fuBA",
|
id: "UCqswUMaC5yWUrkQszr8fuBA",
|
||||||
name: "Nonstop Music",
|
name: "Nonstop Music",
|
||||||
handle: Some("@nonstopmusic9993"),
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "//yt3.googleusercontent.com/ytc/AMLnZu9vLN62RxNbnpa20r5XreWRlVjHXbHf7BMcvSBxoQ=s88-c-k-c0x00ffffff-no-rj-mo",
|
url: "//yt3.googleusercontent.com/ytc/AMLnZu9vLN62RxNbnpa20r5XreWRlVjHXbHf7BMcvSBxoQ=s88-c-k-c0x00ffffff-no-rj-mo",
|
||||||
|
@ -304,12 +303,12 @@ SearchResult(
|
||||||
],
|
],
|
||||||
verification: Verified,
|
verification: Verified,
|
||||||
subscriber_count: Some(416000),
|
subscriber_count: Some(416000),
|
||||||
|
video_count: None,
|
||||||
short_description: "Nonstop Music - Home of 1h videos of your favourite songs and mixes. Nonstop Genres: Pop • Chillout • Tropical House • Deep\u{a0}...",
|
short_description: "Nonstop Music - Home of 1h videos of your favourite songs and mixes. Nonstop Genres: Pop • Chillout • Tropical House • Deep\u{a0}...",
|
||||||
)),
|
)),
|
||||||
Channel(ChannelItem(
|
Channel(ChannelItem(
|
||||||
id: "UChO8h2G8UjOVc081rgYU8XQ",
|
id: "UChO8h2G8UjOVc081rgYU8XQ",
|
||||||
name: "Vibe Music",
|
name: "Vibe Music",
|
||||||
handle: Some("@vibemusic."),
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "//yt3.googleusercontent.com/ytc/AMLnZu9Br5pt87kuDLRFbh1MqMXeFlCLbUrwFlDIzU4s=s88-c-k-c0x00ffffff-no-rj-mo",
|
url: "//yt3.googleusercontent.com/ytc/AMLnZu9Br5pt87kuDLRFbh1MqMXeFlCLbUrwFlDIzU4s=s88-c-k-c0x00ffffff-no-rj-mo",
|
||||||
|
@ -324,12 +323,12 @@ SearchResult(
|
||||||
],
|
],
|
||||||
verification: Verified,
|
verification: Verified,
|
||||||
subscriber_count: Some(3000000),
|
subscriber_count: Some(3000000),
|
||||||
|
video_count: None,
|
||||||
short_description: "Vibe Music strives to bring the best lyric videos of popular Rap & Hip Hop songs. Be sure to Subscribe to see new videos we\u{a0}...",
|
short_description: "Vibe Music strives to bring the best lyric videos of popular Rap & Hip Hop songs. Be sure to Subscribe to see new videos we\u{a0}...",
|
||||||
)),
|
)),
|
||||||
Channel(ChannelItem(
|
Channel(ChannelItem(
|
||||||
id: "UClV8b2EhIhIASKw-etzegyw",
|
id: "UClV8b2EhIhIASKw-etzegyw",
|
||||||
name: "Suits Music",
|
name: "Suits Music",
|
||||||
handle: Some("@SuitsMusic"),
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "//yt3.googleusercontent.com/ytc/AMLnZu9Aj5RtZZMdK_B_YD-8rOfi9c5ddFw5t1s4GYEeOQ=s88-c-k-c0x00ffffff-no-rj-mo",
|
url: "//yt3.googleusercontent.com/ytc/AMLnZu9Aj5RtZZMdK_B_YD-8rOfi9c5ddFw5t1s4GYEeOQ=s88-c-k-c0x00ffffff-no-rj-mo",
|
||||||
|
@ -344,12 +343,12 @@ SearchResult(
|
||||||
],
|
],
|
||||||
verification: None,
|
verification: None,
|
||||||
subscriber_count: Some(120000),
|
subscriber_count: Some(120000),
|
||||||
|
video_count: None,
|
||||||
short_description: "",
|
short_description: "",
|
||||||
)),
|
)),
|
||||||
Channel(ChannelItem(
|
Channel(ChannelItem(
|
||||||
id: "UCI2hwz3r5phXpOtViIA5inA",
|
id: "UCI2hwz3r5phXpOtViIA5inA",
|
||||||
name: "Rock Music Collection",
|
name: "Rock Music Collection",
|
||||||
handle: Some("@rockmusiccollection4332"),
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "//yt3.ggpht.com/kB4gWvROUIWFuJN8xwIqmPl1QV2_gXMat6COAJjXZT07E3xomc4b2JwGtDg05t1MmhgqImSifhc=s88-c-k-c0x00ffffff-no-rj-mo",
|
url: "//yt3.ggpht.com/kB4gWvROUIWFuJN8xwIqmPl1QV2_gXMat6COAJjXZT07E3xomc4b2JwGtDg05t1MmhgqImSifhc=s88-c-k-c0x00ffffff-no-rj-mo",
|
||||||
|
@ -364,12 +363,12 @@ SearchResult(
|
||||||
],
|
],
|
||||||
verification: None,
|
verification: None,
|
||||||
subscriber_count: Some(81700),
|
subscriber_count: Some(81700),
|
||||||
|
video_count: None,
|
||||||
short_description: "",
|
short_description: "",
|
||||||
)),
|
)),
|
||||||
Channel(ChannelItem(
|
Channel(ChannelItem(
|
||||||
id: "UC9w8My3S7h-bQZ-4R-0ZPsw",
|
id: "UC9w8My3S7h-bQZ-4R-0ZPsw",
|
||||||
name: "Helios Music",
|
name: "Helios Music",
|
||||||
handle: Some("@heliosmusic55"),
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "//yt3.ggpht.com/bi08T8zuYI1PlbM8M5fyZzjVvNJRJFFcQoonRQvS30opJ-OqGIq5OPrZ19qga29PIAit7OO3=s88-c-k-c0x00ffffff-no-rj-mo",
|
url: "//yt3.ggpht.com/bi08T8zuYI1PlbM8M5fyZzjVvNJRJFFcQoonRQvS30opJ-OqGIq5OPrZ19qga29PIAit7OO3=s88-c-k-c0x00ffffff-no-rj-mo",
|
||||||
|
@ -384,12 +383,12 @@ SearchResult(
|
||||||
],
|
],
|
||||||
verification: None,
|
verification: None,
|
||||||
subscriber_count: Some(53000),
|
subscriber_count: Some(53000),
|
||||||
|
video_count: None,
|
||||||
short_description: "Welcome to my channel - Helios Music. I created this channel to help people have the most relaxing, refreshing and comfortable\u{a0}...",
|
short_description: "Welcome to my channel - Helios Music. I created this channel to help people have the most relaxing, refreshing and comfortable\u{a0}...",
|
||||||
)),
|
)),
|
||||||
Channel(ChannelItem(
|
Channel(ChannelItem(
|
||||||
id: "UC_ODKC5gTs2LvdHXDRdDm0w",
|
id: "UC_ODKC5gTs2LvdHXDRdDm0w",
|
||||||
name: "Music On",
|
name: "Music On",
|
||||||
handle: Some("@MilanPavlovic91"),
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "//yt3.googleusercontent.com/ytc/AMLnZu8lUOYw4RdRwQf2Kz8RCExSmuWC78oetXF7VL67SA=s88-c-k-c0x00ffffff-no-rj-mo",
|
url: "//yt3.googleusercontent.com/ytc/AMLnZu8lUOYw4RdRwQf2Kz8RCExSmuWC78oetXF7VL67SA=s88-c-k-c0x00ffffff-no-rj-mo",
|
||||||
|
@ -404,6 +403,7 @@ SearchResult(
|
||||||
],
|
],
|
||||||
verification: None,
|
verification: None,
|
||||||
subscriber_count: Some(129000),
|
subscriber_count: Some(129000),
|
||||||
|
video_count: None,
|
||||||
short_description: "Music On (UNOFFICIAL CHANNEL)",
|
short_description: "Music On (UNOFFICIAL CHANNEL)",
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
|
|
|
@ -9,7 +9,6 @@ SearchResult(
|
||||||
Channel(ChannelItem(
|
Channel(ChannelItem(
|
||||||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||||
name: "Doobydobap",
|
name: "Doobydobap",
|
||||||
handle: None,
|
|
||||||
avatar: [
|
avatar: [
|
||||||
Thumbnail(
|
Thumbnail(
|
||||||
url: "//yt3.ggpht.com/dm5Aq93xvVJz0NoVO88ieBkDXmuShCujGPlZ7qETMEPTrXvPUCFI3-BB6Xs_P-r6Uk3mnBy9zA=s88-c-k-c0x00ffffff-no-rj-mo",
|
url: "//yt3.ggpht.com/dm5Aq93xvVJz0NoVO88ieBkDXmuShCujGPlZ7qETMEPTrXvPUCFI3-BB6Xs_P-r6Uk3mnBy9zA=s88-c-k-c0x00ffffff-no-rj-mo",
|
||||||
|
@ -24,6 +23,7 @@ SearchResult(
|
||||||
],
|
],
|
||||||
verification: Verified,
|
verification: Verified,
|
||||||
subscriber_count: Some(2920000),
|
subscriber_count: Some(2920000),
|
||||||
|
video_count: Some(219),
|
||||||
short_description: "Hi, I\'m Tina, aka Doobydobap! Food is the medium I use to tell stories and connect with people who share the same passion as I\u{a0}...",
|
short_description: "Hi, I\'m Tina, aka Doobydobap! Food is the medium I use to tell stories and connect with people who share the same passion as I\u{a0}...",
|
||||||
)),
|
)),
|
||||||
Video(VideoItem(
|
Video(VideoItem(
|
||||||
|
|
|
@ -0,0 +1,784 @@
|
||||||
|
---
|
||||||
|
source: src/client/trends.rs
|
||||||
|
expression: map_res.c
|
||||||
|
---
|
||||||
|
Paginator(
|
||||||
|
count: None,
|
||||||
|
items: [
|
||||||
|
VideoItem(
|
||||||
|
id: "_cyJhGsXDDM",
|
||||||
|
name: "Ultimate Criminal Canal Found Magnet Fishing! Police on the Hunt",
|
||||||
|
duration: Some(1096),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/_cyJhGsXDDM/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLBBz_ErMMfhKLRZRfcAPTlMTujziw",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/_cyJhGsXDDM/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLDaUGJ6GyTv5vwllztR6mN43dlmxA",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCMLXec9-wpON8tZegnDsYLw",
|
||||||
|
name: "Bondi Treasure Hunter",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/ytc/AMLnZu91VHy_3HvCaMLthYyMSol6zwqxebNQ9GXc7NUB=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("1 day ago"),
|
||||||
|
view_count: Some(700385),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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..."),
|
||||||
|
),
|
||||||
|
VideoItem(
|
||||||
|
id: "36YnV9STBqc",
|
||||||
|
name: "The Good Life Radio\u{a0}•\u{a0}24/7 Live Radio | Best Relax House, Chillout, Study, Running, Gym, Happy Music",
|
||||||
|
duration: None,
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/36YnV9STBqc/hq720_live.jpg?sqp=COjxqZoG-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLASUZkzmRJDiyIJmcsAdcDGan805Q",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/36YnV9STBqc/hq720_live.jpg?sqp=COjxqZoG-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLBDrl0k5nr9wH-_aosqOimodx0b-w",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
||||||
|
name: "The Good Life Radio x Sensual Musique",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/ytc/AMLnZu_V9mOdHaorjNFqGXCecFeOBZhDWB8tVYG_I8gJwA=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: None,
|
||||||
|
view_count: Some(7202),
|
||||||
|
is_live: true,
|
||||||
|
is_short: false,
|
||||||
|
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..."),
|
||||||
|
),
|
||||||
|
VideoItem(
|
||||||
|
id: "YYD1qgH5qC4",
|
||||||
|
name: "چند شنبه با سینــا | فصل چهـارم | قسمت 5 | با حضور نازنین انصاری مدیر روزنامه کیهان لندن",
|
||||||
|
duration: Some(3261),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/YYD1qgH5qC4/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLBkvD-kVL12hteMVVLRZvJHOdlPzQ",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/YYD1qgH5qC4/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLDpO5WCJiLDPHrXOWH-xk2hTG_S3A",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCzH_7hfL6Jd1H0WpNO_eryQ",
|
||||||
|
name: "MBC PERSIA",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/ytc/AMLnZu9lP4dhb_R_Y7e8Q4sb6dj7ve-YtalnMd2t1qP05A=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: None,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("14 hours ago"),
|
||||||
|
view_count: Some(104344),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
is_upcoming: false,
|
||||||
|
short_description: Some("#mbcpersia\n#chandshanbeh\n#چندشنبه\n\nشبكه ام بى سى پرشيا را از حساب هاى مختلف در شبكه هاى اجتماعى دنبال كنيد\n►MBCPERSIA on Facebook:..."),
|
||||||
|
),
|
||||||
|
VideoItem(
|
||||||
|
id: "BeJqgI6rw9k",
|
||||||
|
name: "your city is full of fake buildings, here\'s why",
|
||||||
|
duration: Some(725),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/BeJqgI6rw9k/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAvkJGHa6h2vzXrG1ueGQA8JysqEg",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/BeJqgI6rw9k/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLDEJWMD2gUA572p12E7fZ1VX8qJ3A",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCqVEHtQoXHmUCfJ-9smpTSg",
|
||||||
|
name: "Answer in Progress",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/b4TIQdFmoHYvQmcMt1XGH40m8-P5VdjyaZKb2C6nmkezGVk2Ln1csqe1PWg5aefEyk-NEFWhzg=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("7 days ago"),
|
||||||
|
view_count: Some(1447008),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
is_upcoming: false,
|
||||||
|
short_description: Some("Save 33% on your first Native Deodorant Pack - normally $39, you’ll get it for $26! Click here https://bit.ly/nativeanswer1 and use my code ANSWER #AD\n\nSomewhere on your street there may..."),
|
||||||
|
),
|
||||||
|
VideoItem(
|
||||||
|
id: "ma28eWd1oyA",
|
||||||
|
name: "Post Malone, Maroon 5, Adele, Taylor Swift, Ed Sheeran, Shawn Mendes, Pop Hits 2020 Part 6",
|
||||||
|
duration: Some(29989),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/ma28eWd1oyA/hqdefault.jpg?sqp=-oaymwEcCOADEI4CSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLCznoPDMo_F1NCRBWoD4Ps5IjctxQ",
|
||||||
|
width: 480,
|
||||||
|
height: 270,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCldQuUMYTUGrjvcU2vaPSFQ",
|
||||||
|
name: "Music Library",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/ytc/AMLnZu-4BJEmOMTfX96bjwu9AQS02gbODk5YQpZWVi5P=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: None,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("Streamed 2 years ago"),
|
||||||
|
view_count: Some(1861814),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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..."),
|
||||||
|
),
|
||||||
|
VideoItem(
|
||||||
|
id: "mL2LBRM5GBI",
|
||||||
|
name: "Salahs 6-Minuten-Hattrick & Firmino-Gala: Rangers - FC Liverpool 1:7 | UEFA Champions League | DAZN",
|
||||||
|
duration: Some(355),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/mL2LBRM5GBI/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLBhsDaEALJodPurmS3DywUoRRwzwg",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/mL2LBRM5GBI/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLDkvWkbocujg95phnyfNzBB9dhEYA",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCB-GdMjyokO9lZkKU_oIK6g",
|
||||||
|
name: "DAZN UEFA Champions League",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/ytc/AMLnZu-D8LIEj-klO1gvUWMOA987HqMBBX9nn_WJS9Ka=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("2 days ago"),
|
||||||
|
view_count: Some(1471667),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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..."),
|
||||||
|
),
|
||||||
|
VideoItem(
|
||||||
|
id: "Ang18qz2IeQ",
|
||||||
|
name: "Satisfying Videos of Workers Doing Their Job Perfectly",
|
||||||
|
duration: Some(1186),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/Ang18qz2IeQ/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLA3Cd49wYUuSEXz2MwhO2aqCMq5ZA",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/Ang18qz2IeQ/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAWQAks0vkJyJXSiQFIs9zhc2qyTg",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCYenDLnIHsoqQ6smwKXQ7Hg",
|
||||||
|
name: "#Mind Warehouse",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/ytc/AMLnZu8zB2zV3yx2fSYn5zDbv47rZCBr90wX3jW8EC6NBw=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("2 days ago"),
|
||||||
|
view_count: Some(173121),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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,..."),
|
||||||
|
),
|
||||||
|
VideoItem(
|
||||||
|
id: "fjHN4jsJnEU",
|
||||||
|
name: "I Made 200 Players Simulate Survival Island in Minecraft...",
|
||||||
|
duration: Some(2361),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/fjHN4jsJnEU/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLDwTosIfmAhNHIzU1sSXrTKT8vjNQ",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/fjHN4jsJnEU/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLA4aFygGqUcm7-Hrkys95U0EAV9xA",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCqt4mmAqLmH-AwXz31URJsw",
|
||||||
|
name: "Sword4000",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/ytc/AMLnZu_q3--WCh9Oc5o4XxAVVxxUz2narAtLR2QKuEw2lQ=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("7 days ago"),
|
||||||
|
view_count: Some(751909),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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..."),
|
||||||
|
),
|
||||||
|
VideoItem(
|
||||||
|
id: "FI1XrdBJIUI",
|
||||||
|
name: "Epic Construction Fails | Expensive Fails Compilation | FailArmy",
|
||||||
|
duration: Some(631),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/FI1XrdBJIUI/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLBe2jCnLhTsXmZQefyAe-WqImk6-g",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/FI1XrdBJIUI/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLD01TnIh1pH7TObDgKzx0GupXXVzw",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCPDis9pjXuqyI7RYLJ-TTSA",
|
||||||
|
name: "FailArmy",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/PLsX6LIg5JbMJR9v7eTD7nQOPmZN16_X7h_uACw5qeWLAewiNfasZFsxQ48Dn8wZ_4McKUPZSA=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("2 days ago"),
|
||||||
|
view_count: Some(2226471),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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..."),
|
||||||
|
),
|
||||||
|
VideoItem(
|
||||||
|
id: "MXdplejK8vU",
|
||||||
|
name: "Chilly autumn Jazz ☕ Smooth September Jazz & Bossa Nova for a great relaxing weekend",
|
||||||
|
duration: Some(86403),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/MXdplejK8vU/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAIOe93l-1elIK0DfMLk0f3nDWgSA",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/MXdplejK8vU/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLByGLefQ3I9p2VQ5oZDmc5G_pCTlQ",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCeGJ6v6KQt0s88hGKMfybuw",
|
||||||
|
name: "Cozy Jazz Music",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/tU7x6wNqEM_OIeU-jaaPcdhX3adNhnAY7WaGHsjEMfTLSzVHxm8VVBfaXRjDbf3y_LftGNJ83A=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: None,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("1 month ago"),
|
||||||
|
view_count: Some(148743),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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..."),
|
||||||
|
),
|
||||||
|
VideoItem(
|
||||||
|
id: "Jri4_9vBFiQ",
|
||||||
|
name: "Top 100 Best Classic Rock Songs Of All Time 🔥 R.E.M, Queen, Metallica,Guns N’ Roses,Bon Jovi, U2,CCR",
|
||||||
|
duration: None,
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/Jri4_9vBFiQ/hq720_live.jpg?sqp=COjxqZoG-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLA1ZqDfSLi3Mf5qvpUFSYyDIODNQw",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/Jri4_9vBFiQ/hq720_live.jpg?sqp=COjxqZoG-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLDtwgV7RdHmgDlAESZqSYbuZtFrvw",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCiIWdzEVNH8okhlapR9a-xA",
|
||||||
|
name: "Rock Music",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/QIEcTVdBg9A2kE3un-IfjgTPiglDGMBbh9vMSXo2J5ZRICmunnVQkfpbMWNP8Kueac09DZrn=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: None,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: None,
|
||||||
|
view_count: Some(192),
|
||||||
|
is_live: true,
|
||||||
|
is_short: false,
|
||||||
|
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’..."),
|
||||||
|
),
|
||||||
|
VideoItem(
|
||||||
|
id: "ll4d5Lt-Ie8",
|
||||||
|
name: "Relaxing Music Healing Stress, Anxiety and Depressive States Heal Mind, Body and Soul | Sleep music",
|
||||||
|
duration: Some(42896),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/ll4d5Lt-Ie8/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAqdY2bQaQ3JHl5FYoTPuZFxXRKIQ",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/ll4d5Lt-Ie8/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLA6xc8r38_2ygARU0vOR4kI6ZNz5w",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCNS3dqFGBPhxHmOigehpBeg",
|
||||||
|
name: "Love YourSelf",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/fkgfEL2OtY2mhhyCV3xSOc3OsVK5ylQJmBev7XlBGE548dM6dqS2Z66YF-pdnbQOQpCuvZOlAdk=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("Streamed 5 months ago"),
|
||||||
|
view_count: Some(5363904),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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..."),
|
||||||
|
),
|
||||||
|
VideoItem(
|
||||||
|
id: "Dx2wbKLokuQ",
|
||||||
|
name: "W. Putin: Die Sehnsucht nach dem Imperium | Mit offenen Karten | ARTE",
|
||||||
|
duration: Some(729),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/Dx2wbKLokuQ/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLBHQXnaEYo6frjkJ3FFuAPkAyOCKQ",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/Dx2wbKLokuQ/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLDFtWV_wy25ohVyBthH8a5HwSj6Kw",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCLLibJTCy3sXjHLVaDimnpQ",
|
||||||
|
name: "ARTEde",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/ytc/AMLnZu-1i2jxeXFISJhBbpWWv5vVX2xE5yQbjpaZZP3HPg=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("2 weeks ago"),
|
||||||
|
view_count: Some(539838),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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..."),
|
||||||
|
),
|
||||||
|
VideoItem(
|
||||||
|
id: "jfKfPfyJRdk",
|
||||||
|
name: "lofi hip hop radio - beats to relax/study to",
|
||||||
|
duration: None,
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/jfKfPfyJRdk/hq720_live.jpg?sqp=COjxqZoG-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLCR-bHqcvOP14sSUsNt9PTuf3ZI4Q",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/jfKfPfyJRdk/hq720_live.jpg?sqp=COjxqZoG-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLBBVEQQnwSLJFllntNgv2JAAlvSMQ",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCSJ4gkVC6NrvII8umztf0Ow",
|
||||||
|
name: "Lofi Girl",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/KNYElmLFGAOSZoBmxYGKKXhGHrT2e7Hmz3WsBerbam5uaDXFADAmT7htj3OcC-uK1O88lC9fQg=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: None,
|
||||||
|
view_count: Some(21262),
|
||||||
|
is_live: true,
|
||||||
|
is_short: false,
|
||||||
|
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..."),
|
||||||
|
),
|
||||||
|
VideoItem(
|
||||||
|
id: "qmrzTUmZ4UU",
|
||||||
|
name: "850€ für den Verrat am System - UCS AT-AT LEGO® Star Wars 75313",
|
||||||
|
duration: Some(2043),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/qmrzTUmZ4UU/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAsI3VS-wxnt1s_zS4M_YbVrV1pAg",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/qmrzTUmZ4UU/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLBYk7w0qGeW4kZchFr-tbydELUChQ",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UC_EZd3lsmxudu3IQzpTzOgw",
|
||||||
|
name: "Held der Steine Inh. Thomas Panke",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/ytc/AMLnZu8g9hFxZ2HD4P9pDsUxoAvkHwbZoTVNr3yw12i8YA=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("6 days ago"),
|
||||||
|
view_count: Some(600150),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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..."),
|
||||||
|
),
|
||||||
|
VideoItem(
|
||||||
|
id: "t0Q2otsqC4I",
|
||||||
|
name: "Tom & Jerry | Tom & Jerry in Full Screen | Classic Cartoon Compilation | WB Kids",
|
||||||
|
duration: Some(1298),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/t0Q2otsqC4I/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLCFcrz2zM6mPUmJiCsC7c7suOzSug",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/t0Q2otsqC4I/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLCVANFKKXmrdehkf7aM9issiuph5A",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UC9trsD1jCTXXtN3xIOIU8gg",
|
||||||
|
name: "WB Kids",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/ytc/AMLnZu80jIF6oehgpUILTaUbqSM5xYHWbPoc_Bz7wddxzg=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: Verified,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("10 months ago"),
|
||||||
|
view_count: Some(252381571),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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..."),
|
||||||
|
),
|
||||||
|
VideoItem(
|
||||||
|
id: "zE-a5eqvlv8",
|
||||||
|
name: "Dua Lipa, Coldplay, Martin Garrix & Kygo, The Chainsmokers Style - Feeling Me",
|
||||||
|
duration: None,
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/zE-a5eqvlv8/hq720_live.jpg?sqp=COjxqZoG-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLCDyvujcpz62sEsL9Ke4ADBpXWqOA",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/zE-a5eqvlv8/hq720_live.jpg?sqp=COjxqZoG-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLCyJ-QdgAD1F-DqcLKivIcalBJOEg",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCX-USfenzQlhrEJR1zD5IYw",
|
||||||
|
name: "Deep Mood.",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/8WO05hff9bGjmlyPFo_PJRMIfHEoUvN_KbTcWRVX2yqeUO3fLgkz0K4MA6W95s3_NKdNUAwjow=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: None,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: None,
|
||||||
|
view_count: Some(955),
|
||||||
|
is_live: true,
|
||||||
|
is_short: false,
|
||||||
|
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..."),
|
||||||
|
),
|
||||||
|
VideoItem(
|
||||||
|
id: "HxCcKzRAGWk",
|
||||||
|
name: "(Music for Man ) Relaxing Whiskey Blues Music - Modern Electric Guitar Blues - JAZZ & BLUES",
|
||||||
|
duration: Some(42899),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/HxCcKzRAGWk/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLD5CNX5XaQAKrLpPq0nxmyUjP5yUw",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/HxCcKzRAGWk/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLANuDaGE9jI_-go6cS_nU3qCu6LRg",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCGr-rTYtP1m-r_-ncspdVQQ",
|
||||||
|
name: "JAZZ & BLUES",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/zqAxVISjt1hyzRzZKxRTvJfgEc5k2Luf-aEE55ohjUvt0QvqIRvmFBNC6UKj2TxlZrzGo8QMNA=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: None,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("Streamed 3 months ago"),
|
||||||
|
view_count: Some(3156236),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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..."),
|
||||||
|
),
|
||||||
|
VideoItem(
|
||||||
|
id: "HlHYOdZePSE",
|
||||||
|
name: "Healing Music for Anxiety Disorders, Fears, Depression and Eliminate Negative Thoughts",
|
||||||
|
duration: None,
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/HlHYOdZePSE/hq720_live.jpg?sqp=COjxqZoG-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLBeqmmnli6rVdK1k7vcHlwE3kiNaw",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/HlHYOdZePSE/hq720_live.jpg?sqp=COjxqZoG-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAk9H5lapp7KBhJCER7uRCr0fDRgg",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCqNYK5QArQRZSIR8v6_FCfA",
|
||||||
|
name: "Tranquil Music",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/YJUUVEayRZKNtFzWEiYgvxp9XOBw9-ioxiYErE0cNDTYNvkxHBCiuUXse4-a_yaYfSS-GfT-MQ=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: None,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: None,
|
||||||
|
view_count: Some(1585),
|
||||||
|
is_live: true,
|
||||||
|
is_short: false,
|
||||||
|
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...."),
|
||||||
|
),
|
||||||
|
VideoItem(
|
||||||
|
id: "CJ2AH3LJeic",
|
||||||
|
name: "Coldplay Greatest Hits Full Album 2022 New Songs of Coldplay 2022",
|
||||||
|
duration: Some(7781),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/CJ2AH3LJeic/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLC3A9sBlWQZmFUI9BYe5KzvATqiqw",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/CJ2AH3LJeic/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLBaKSeSRdcDjEqQxrAfPaQmDJecvg",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCdK2lzwelugXGhR9SCWuEew",
|
||||||
|
name: "PLAY MUSIC",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/ytc/AMLnZu8fIT4MTyobgM_deRkvcWBMIhKpAeIGfgqqob5p=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: None,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("7 months ago"),
|
||||||
|
view_count: Some(5595965),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
is_upcoming: false,
|
||||||
|
short_description: Some("▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\nSubscribe channel for more videos:\n🔔Subscribe: https://bit.ly/2UbIZFv\n⚡Facebook: https://bitly.com.vn/gXDsC..."),
|
||||||
|
),
|
||||||
|
VideoItem(
|
||||||
|
id: "KJwzKxQ81iA",
|
||||||
|
name: "Handmade Candy Making Collection / 수제 사탕 만들기 모음 / Korean Candy Store",
|
||||||
|
duration: Some(3152),
|
||||||
|
thumbnail: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/KJwzKxQ81iA/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLCtm3YNbp3mK6RjsACZuz7fs-TUYA",
|
||||||
|
width: 360,
|
||||||
|
height: 202,
|
||||||
|
),
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://i.ytimg.com/vi/KJwzKxQ81iA/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAVzCHCFbAyBRebsCKcSDxaWq0x6A",
|
||||||
|
width: 720,
|
||||||
|
height: 404,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
channel: Some(ChannelTag(
|
||||||
|
id: "UCdGwDjTgbSwQDZ8dYOdrplg",
|
||||||
|
name: "Soon Films 순필름",
|
||||||
|
avatar: [
|
||||||
|
Thumbnail(
|
||||||
|
url: "https://yt3.ggpht.com/ytc/AMLnZu_eXMJm3sINr84rGTr3aiXD-OZ43aqx4yuNq9wjXw=s68-c-k-c0x00ffffff-no-rj",
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verification: None,
|
||||||
|
subscriber_count: None,
|
||||||
|
)),
|
||||||
|
publish_date: "[date]",
|
||||||
|
publish_date_txt: Some("1 month ago"),
|
||||||
|
view_count: Some(3127238),
|
||||||
|
is_live: false,
|
||||||
|
is_short: false,
|
||||||
|
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"),
|
||||||
|
visitor_data: Some("CgtjTXNGWnhNcjdORSiq8qmaBg%3D%3D"),
|
||||||
|
endpoint: browse,
|
||||||
|
)
|
|
@ -2,13 +2,38 @@ use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{Error, ExtractionError},
|
error::{Error, ExtractionError},
|
||||||
model::VideoItem,
|
model::{
|
||||||
|
paginator::{ContinuationEndpoint, Paginator},
|
||||||
|
VideoItem,
|
||||||
|
},
|
||||||
|
param::Language,
|
||||||
serializer::MapResult,
|
serializer::MapResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{response, ClientType, MapRespCtx, MapResponse, QBrowseParams, RustyPipeQuery};
|
use super::{
|
||||||
|
response, ClientType, MapRespCtx, MapResponse, QBrowse, QBrowseParams, RustyPipeQuery,
|
||||||
|
};
|
||||||
|
|
||||||
impl RustyPipeQuery {
|
impl RustyPipeQuery {
|
||||||
|
/// Get the videos from the YouTube startpage
|
||||||
|
#[tracing::instrument(skip(self), level = "error")]
|
||||||
|
pub async fn startpage(&self) -> Result<Paginator<VideoItem>, Error> {
|
||||||
|
let context = self.get_context(ClientType::Desktop, true, None).await;
|
||||||
|
let request_body = QBrowse {
|
||||||
|
context,
|
||||||
|
browse_id: "FEwhat_to_watch",
|
||||||
|
};
|
||||||
|
|
||||||
|
self.execute_request::<response::Startpage, _, _>(
|
||||||
|
ClientType::Desktop,
|
||||||
|
"startpage",
|
||||||
|
"",
|
||||||
|
"browse",
|
||||||
|
&request_body,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the videos from the YouTube trending page
|
/// Get the videos from the YouTube trending page
|
||||||
#[tracing::instrument(skip(self), level = "error")]
|
#[tracing::instrument(skip(self), level = "error")]
|
||||||
pub async fn trending(&self) -> Result<Vec<VideoItem>, Error> {
|
pub async fn trending(&self) -> Result<Vec<VideoItem>, Error> {
|
||||||
|
@ -30,6 +55,33 @@ impl RustyPipeQuery {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MapResponse<Paginator<VideoItem>> for response::Startpage {
|
||||||
|
fn map_response(
|
||||||
|
self,
|
||||||
|
ctx: &MapRespCtx<'_>,
|
||||||
|
) -> Result<MapResult<Paginator<VideoItem>>, ExtractionError> {
|
||||||
|
let grid = self
|
||||||
|
.contents
|
||||||
|
.two_column_browse_results_renderer
|
||||||
|
.contents
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.ok_or(ExtractionError::InvalidData(Cow::Borrowed("no contents")))?
|
||||||
|
.tab_renderer
|
||||||
|
.content
|
||||||
|
.section_list_renderer
|
||||||
|
.contents;
|
||||||
|
|
||||||
|
Ok(map_startpage_videos(
|
||||||
|
grid,
|
||||||
|
ctx.lang,
|
||||||
|
self.response_context
|
||||||
|
.visitor_data
|
||||||
|
.or_else(|| ctx.visitor_data.map(str::to_owned)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl MapResponse<Vec<VideoItem>> for response::Trending {
|
impl MapResponse<Vec<VideoItem>> for response::Trending {
|
||||||
fn map_response(
|
fn map_response(
|
||||||
self,
|
self,
|
||||||
|
@ -57,6 +109,26 @@ impl MapResponse<Vec<VideoItem>> for response::Trending {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn map_startpage_videos(
|
||||||
|
videos: MapResult<Vec<response::YouTubeListItem>>,
|
||||||
|
lang: Language,
|
||||||
|
visitor_data: Option<String>,
|
||||||
|
) -> MapResult<Paginator<VideoItem>> {
|
||||||
|
let mut mapper = response::YouTubeListMapper::<VideoItem>::new(lang);
|
||||||
|
mapper.map_response(videos);
|
||||||
|
|
||||||
|
MapResult {
|
||||||
|
c: Paginator::new_ext(
|
||||||
|
None,
|
||||||
|
mapper.items,
|
||||||
|
mapper.ctoken,
|
||||||
|
visitor_data,
|
||||||
|
ContinuationEndpoint::Browse,
|
||||||
|
),
|
||||||
|
warnings: mapper.warnings,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::{fs::File, io::BufReader};
|
use std::{fs::File, io::BufReader};
|
||||||
|
@ -66,11 +138,32 @@ mod tests {
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
client::{response, MapRespCtx, MapResponse},
|
client::{response, MapRespCtx, MapResponse},
|
||||||
model::VideoItem,
|
model::{paginator::Paginator, VideoItem},
|
||||||
serializer::MapResult,
|
serializer::MapResult,
|
||||||
util::tests::TESTFILES,
|
util::tests::TESTFILES,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn map_startpage() {
|
||||||
|
let json_path = path!(*TESTFILES / "trends" / "startpage.json");
|
||||||
|
let json_file = File::open(json_path).unwrap();
|
||||||
|
|
||||||
|
let startpage: response::Startpage =
|
||||||
|
serde_json::from_reader(BufReader::new(json_file)).unwrap();
|
||||||
|
let map_res: MapResult<Paginator<VideoItem>> =
|
||||||
|
startpage.map_response(&MapRespCtx::test("")).unwrap();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
map_res.warnings.is_empty(),
|
||||||
|
"deserialization/mapping warnings: {:?}",
|
||||||
|
map_res.warnings
|
||||||
|
);
|
||||||
|
|
||||||
|
insta::assert_ron_snapshot!("map_startpage", map_res.c, {
|
||||||
|
".items[].publish_date" => "[date]",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case::base("videos")]
|
#[case::base("videos")]
|
||||||
#[case::page_header_renderer("20230501_page_header_renderer")]
|
#[case::page_header_renderer("20230501_page_header_renderer")]
|
||||||
|
@ -78,10 +171,10 @@ mod tests {
|
||||||
let json_path = path!(*TESTFILES / "trends" / format!("trending_{name}.json"));
|
let json_path = path!(*TESTFILES / "trends" / format!("trending_{name}.json"));
|
||||||
let json_file = File::open(json_path).unwrap();
|
let json_file = File::open(json_path).unwrap();
|
||||||
|
|
||||||
let trending: response::Trending =
|
let startpage: response::Trending =
|
||||||
serde_json::from_reader(BufReader::new(json_file)).unwrap();
|
serde_json::from_reader(BufReader::new(json_file)).unwrap();
|
||||||
let map_res: MapResult<Vec<VideoItem>> =
|
let map_res: MapResult<Vec<VideoItem>> =
|
||||||
trending.map_response(&MapRespCtx::test("")).unwrap();
|
startpage.map_response(&MapRespCtx::test("")).unwrap();
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
map_res.warnings.is_empty(),
|
map_res.warnings.is_empty(),
|
||||||
|
|
|
@ -700,15 +700,11 @@ pub struct Channel<T> {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
/// Channel name
|
/// Channel name
|
||||||
pub name: String,
|
pub name: String,
|
||||||
/// YouTube channel handle (e.g. `@EEVblog`)
|
|
||||||
pub handle: Option<String>,
|
|
||||||
/// Channel subscriber count
|
/// 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.
|
/// or could not be parsed.
|
||||||
pub subscriber_count: Option<u64>,
|
pub subscriber_count: Option<u64>,
|
||||||
/// Number of videos
|
|
||||||
pub video_count: Option<u64>,
|
|
||||||
/// Channel avatar / profile picture
|
/// Channel avatar / profile picture
|
||||||
pub avatar: Vec<Thumbnail>,
|
pub avatar: Vec<Thumbnail>,
|
||||||
/// Channel verification mark
|
/// Channel verification mark
|
||||||
|
@ -717,8 +713,15 @@ pub struct Channel<T> {
|
||||||
pub description: String,
|
pub description: String,
|
||||||
/// List of words to describe the topic of the channel
|
/// List of words to describe the topic of the channel
|
||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
|
/// Custom URL set by the channel owner
|
||||||
|
/// (e.g. <https://www.youtube.com/c/EevblogDave>)
|
||||||
|
pub vanity_url: Option<String>,
|
||||||
/// Banner image shown above the channel
|
/// Banner image shown above the channel
|
||||||
pub banner: Vec<Thumbnail>,
|
pub banner: Vec<Thumbnail>,
|
||||||
|
/// Banner image shown above the channel (small format for mobile)
|
||||||
|
pub mobile_banner: Vec<Thumbnail>,
|
||||||
|
/// Banner image shown above the channel (16:9 fullscreen format for TV)
|
||||||
|
pub tv_banner: Vec<Thumbnail>,
|
||||||
/// Does the channel have a *Shorts* tab?
|
/// Does the channel have a *Shorts* tab?
|
||||||
pub has_shorts: bool,
|
pub has_shorts: bool,
|
||||||
/// Does the channel have a *Live* tab?
|
/// Does the channel have a *Live* tab?
|
||||||
|
@ -870,8 +873,6 @@ pub struct ChannelItem {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
/// Channel name
|
/// Channel name
|
||||||
pub name: String,
|
pub name: String,
|
||||||
/// YouTube channel handle (e.g. `@EEVblog`)
|
|
||||||
pub handle: Option<String>,
|
|
||||||
/// Channel avatar/profile picture
|
/// Channel avatar/profile picture
|
||||||
pub avatar: Vec<Thumbnail>,
|
pub avatar: Vec<Thumbnail>,
|
||||||
/// Channel verification mark
|
/// Channel verification mark
|
||||||
|
@ -880,6 +881,8 @@ pub struct ChannelItem {
|
||||||
///
|
///
|
||||||
/// [`None`] if hidden by the owner or not present.
|
/// [`None`] if hidden by the owner or not present.
|
||||||
pub subscriber_count: Option<u64>,
|
pub subscriber_count: Option<u64>,
|
||||||
|
/// Number of videos from the channel
|
||||||
|
pub video_count: Option<u64>,
|
||||||
/// Abbreviated channel description
|
/// Abbreviated channel description
|
||||||
pub short_description: String,
|
pub short_description: String,
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,10 +32,9 @@ pub static PLAYLIST_ID_REGEX: Lazy<Regex> =
|
||||||
Lazy::new(|| Regex::new(r"^(?:PL|RD|OLAK|UU)[A-Za-z0-9_-]{5,50}$").unwrap());
|
Lazy::new(|| Regex::new(r"^(?:PL|RD|OLAK|UU)[A-Za-z0-9_-]{5,50}$").unwrap());
|
||||||
pub static ALBUM_ID_REGEX: Lazy<Regex> =
|
pub static ALBUM_ID_REGEX: Lazy<Regex> =
|
||||||
Lazy::new(|| Regex::new(r"^MPREb_[A-Za-z0-9_-]{11}$").unwrap());
|
Lazy::new(|| Regex::new(r"^MPREb_[A-Za-z0-9_-]{11}$").unwrap());
|
||||||
pub static VANITY_PATH_REGEX: Lazy<Regex> =
|
pub static VANITY_PATH_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||||
Lazy::new(|| Regex::new(r"^/?(?:(?:c/|user/)?[A-z0-9]{1,100})|(?:@[\w\-\.·]{1,30})$").unwrap());
|
Regex::new(r"^/?(?:(?:c/|user/)?[A-z0-9]{1,100})|(?:@[A-z0-9-_.]{1,100})$").unwrap()
|
||||||
pub static CHANNEL_HANDLE_REGEX: Lazy<Regex> =
|
});
|
||||||
Lazy::new(|| Regex::new(r#"^@[\w\-\.·]{1,30}$"#).unwrap());
|
|
||||||
|
|
||||||
/// Separator string for YouTube Music subtitles
|
/// Separator string for YouTube Music subtitles
|
||||||
pub const DOT_SEPARATOR: &str = " • ";
|
pub const DOT_SEPARATOR: &str = " • ";
|
||||||
|
|
|
@ -11,10 +11,7 @@
|
||||||
//! - The validation functions of this module are meant vor validating specific data (video IDs,
|
//! - The validation functions of this module are meant vor validating specific data (video IDs,
|
||||||
//! channel IDs, playlist IDs) and return [`true`] if the given input is valid
|
//! channel IDs, playlist IDs) and return [`true`] if the given input is valid
|
||||||
|
|
||||||
use crate::{
|
use crate::{error::Error, util};
|
||||||
error::Error,
|
|
||||||
util::{self, CHANNEL_HANDLE_REGEX},
|
|
||||||
};
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
|
@ -205,32 +202,6 @@ pub fn track_lyrics_id<S: AsRef<str>>(lyrics_id: S) -> Result<(), Error> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validate the given channel handle
|
|
||||||
///
|
|
||||||
/// YouTube channel handles can be up to 30 characters long and start with an `@`.
|
|
||||||
/// Allowed characters are letters and numbers (Unicode), underscores (`_`), hyphens (`-`),
|
|
||||||
/// full stops (`.`) and middle dots (`· U+00B7`)
|
|
||||||
///
|
|
||||||
/// There are more fine-grained rules for specific scripts. Verifying these is not implemented.
|
|
||||||
///
|
|
||||||
/// Reference: <https://support.google.com/youtube/answer/11585688>
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # use rustypipe::validate;
|
|
||||||
/// assert!(validate::channel_handle("@EEVBlog").is_ok());
|
|
||||||
/// assert!(validate::channel_handle("@Āll·._-").is_ok());
|
|
||||||
/// assert!(validate::channel_handle("@한국").is_ok());
|
|
||||||
///
|
|
||||||
/// assert!(validate::channel_handle("noat").is_err());
|
|
||||||
/// assert!(validate::channel_handle("@no space").is_err());
|
|
||||||
/// ```
|
|
||||||
pub fn channel_handle<S: AsRef<str>>(channel_handle: S) -> Result<(), Error> {
|
|
||||||
check(
|
|
||||||
CHANNEL_HANDLE_REGEX.is_match(channel_handle.as_ref()),
|
|
||||||
"invalid channel handle",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check(res: bool, msg: &'static str) -> Result<(), Error> {
|
fn check(res: bool, msg: &'static str) -> Result<(), Error> {
|
||||||
if res {
|
if res {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -885,13 +885,16 @@ async fn channel_shorts(rp: RustyPipe) {
|
||||||
// dbg!(&channel);
|
// dbg!(&channel);
|
||||||
assert_eq!(channel.id, "UCh8gHdtzO2tXd593_bjErWg");
|
assert_eq!(channel.id, "UCh8gHdtzO2tXd593_bjErWg");
|
||||||
assert_eq!(channel.name, "Doobydobap");
|
assert_eq!(channel.name, "Doobydobap");
|
||||||
assert_eq!(channel.handle.as_deref(), Some("@Doobydobap"));
|
|
||||||
assert_gteo(channel.subscriber_count, 2_800_000, "subscribers");
|
assert_gteo(channel.subscriber_count, 2_800_000, "subscribers");
|
||||||
assert!(!channel.avatar.is_empty(), "got no thumbnails");
|
assert!(!channel.avatar.is_empty(), "got no thumbnails");
|
||||||
assert_eq!(channel.verification, Verification::Verified);
|
assert_eq!(channel.verification, Verification::Verified);
|
||||||
assert!(channel
|
assert!(channel
|
||||||
.description
|
.description
|
||||||
.contains("Hi, I\u{2019}m Tina, aka Doobydobap"));
|
.contains("Hi, I\u{2019}m Tina, aka Doobydobap"));
|
||||||
|
assert_eq!(
|
||||||
|
channel.vanity_url.as_deref(),
|
||||||
|
Some("https://www.youtube.com/@Doobydobap")
|
||||||
|
);
|
||||||
assert!(!channel.banner.is_empty(), "got no banners");
|
assert!(!channel.banner.is_empty(), "got no banners");
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
|
@ -991,12 +994,15 @@ async fn channel_search(rp: RustyPipe) {
|
||||||
fn assert_channel_eevblog<T>(channel: &Channel<T>) {
|
fn assert_channel_eevblog<T>(channel: &Channel<T>) {
|
||||||
assert_eq!(channel.id, "UC2DjFE7Xf11URZqWBigcVOQ");
|
assert_eq!(channel.id, "UC2DjFE7Xf11URZqWBigcVOQ");
|
||||||
assert_eq!(channel.name, "EEVblog");
|
assert_eq!(channel.name, "EEVblog");
|
||||||
assert_eq!(channel.handle.as_deref(), Some("@EEVblog"));
|
|
||||||
assert_gteo(channel.subscriber_count, 880_000, "subscribers");
|
assert_gteo(channel.subscriber_count, 880_000, "subscribers");
|
||||||
assert!(!channel.avatar.is_empty(), "got no thumbnails");
|
assert!(!channel.avatar.is_empty(), "got no thumbnails");
|
||||||
assert_eq!(channel.verification, Verification::Verified);
|
assert_eq!(channel.verification, Verification::Verified);
|
||||||
assert_eq!(channel.description, "NO SCRIPT, NO FEAR, ALL OPINION\nAn off-the-cuff Video Blog about Electronics Engineering, for engineers, hobbyists, enthusiasts, hackers and Makers\nHosted by Dave Jones from Sydney Australia\n\nDONATIONS:\nBitcoin: 3KqyH1U3qrMPnkLufM2oHDU7YB4zVZeFyZ\nEthereum: 0x99ccc4d2654ba40744a1f678d9868ecb15e91206\nPayPal: david@alternatezone.com\n\nPatreon: https://www.patreon.com/eevblog\n\nEEVblog2: http://www.youtube.com/EEVblog2\nEEVdiscover: https://www.youtube.com/channel/UCkGvUEt8iQLmq3aJIMjT2qQ\n\nEMAIL:\nAdvertising/Commercial: eevblog+business@gmail.com\nFan mail: eevblog+fan@gmail.com\nHate Mail: eevblog+hate@gmail.com\n\nI DON'T DO PAID VIDEO SPONSORSHIPS, DON'T ASK!\n\nPLEASE:\nDo NOT ask for personal advice on something, post it in the EEVblog forum.\nI read ALL email, but please don't be offended if I don't have time to reply, I get a LOT of email.\n\nMailbag\nPO Box 7949\nBaulkham Hills NSW 2153\nAUSTRALIA");
|
assert_eq!(channel.description, "NO SCRIPT, NO FEAR, ALL OPINION\nAn off-the-cuff Video Blog about Electronics Engineering, for engineers, hobbyists, enthusiasts, hackers and Makers\nHosted by Dave Jones from Sydney Australia\n\nDONATIONS:\nBitcoin: 3KqyH1U3qrMPnkLufM2oHDU7YB4zVZeFyZ\nEthereum: 0x99ccc4d2654ba40744a1f678d9868ecb15e91206\nPayPal: david@alternatezone.com\n\nPatreon: https://www.patreon.com/eevblog\n\nEEVblog2: http://www.youtube.com/EEVblog2\nEEVdiscover: https://www.youtube.com/channel/UCkGvUEt8iQLmq3aJIMjT2qQ\n\nEMAIL:\nAdvertising/Commercial: eevblog+business@gmail.com\nFan mail: eevblog+fan@gmail.com\nHate Mail: eevblog+hate@gmail.com\n\nI DON'T DO PAID VIDEO SPONSORSHIPS, DON'T ASK!\n\nPLEASE:\nDo NOT ask for personal advice on something, post it in the EEVblog forum.\nI read ALL email, but please don't be offended if I don't have time to reply, I get a LOT of email.\n\nMailbag\nPO Box 7949\nBaulkham Hills NSW 2153\nAUSTRALIA");
|
||||||
assert!(!channel.tags.is_empty(), "got no tags");
|
assert!(!channel.tags.is_empty(), "got no tags");
|
||||||
|
assert_eq!(
|
||||||
|
channel.vanity_url.as_deref(),
|
||||||
|
Some("https://www.youtube.com/@EEVblog")
|
||||||
|
);
|
||||||
assert!(!channel.banner.is_empty(), "got no banners");
|
assert!(!channel.banner.is_empty(), "got no banners");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1449,6 +1455,18 @@ async fn resolve_channel_not_found(rp: RustyPipe) {
|
||||||
|
|
||||||
//#TRENDS
|
//#TRENDS
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[tokio::test]
|
||||||
|
#[ignore]
|
||||||
|
async fn startpage(rp: RustyPipe) {
|
||||||
|
let startpage = rp.query().startpage().await.unwrap();
|
||||||
|
|
||||||
|
// The startpage requires visitor data to fetch continuations
|
||||||
|
assert!(startpage.visitor_data.is_some());
|
||||||
|
|
||||||
|
assert_next(startpage, rp.query(), 8, 2, true).await;
|
||||||
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn trending(rp: RustyPipe) {
|
async fn trending(rp: RustyPipe) {
|
||||||
|
@ -2685,7 +2703,6 @@ fn rp(lang: Language) -> RustyPipe {
|
||||||
let vdata = std::env::var("YT_VDATA").ok();
|
let vdata = std::env::var("YT_VDATA").ok();
|
||||||
RustyPipe::builder()
|
RustyPipe::builder()
|
||||||
.strict()
|
.strict()
|
||||||
.storage_dir(env!("CARGO_MANIFEST_DIR"))
|
|
||||||
.lang(lang)
|
.lang(lang)
|
||||||
.visitor_data_opt(vdata)
|
.visitor_data_opt(vdata)
|
||||||
.build()
|
.build()
|
||||||
|
|
Loading…
Reference in a new issue