fix: A/B test 17: channel playlists lockupViewModel

This commit is contained in:
ThetaDev 2024-11-09 05:11:41 +01:00
parent 0919cbd0df
commit 342119dba6
No known key found for this signature in database
GPG key ID: E319D3C5148D65B6
13 changed files with 8770 additions and 49 deletions

View file

@ -776,8 +776,10 @@ mod tests {
}
#[rstest]
fn map_channel_playlists() {
let json_path = path!(*TESTFILES / "channel" / "channel_playlists.json");
#[case::base("base")]
#[case::lockup("20241109_lockup")]
fn map_channel_playlists(#[case] name: &str) {
let json_path = path!(*TESTFILES / "channel" / format!("channel_playlists_{name}.json"));
let json_file = File::open(json_path).unwrap();
let channel: response::Channel =
@ -791,7 +793,7 @@ mod tests {
"deserialization/mapping warnings: {:?}",
map_res.warnings
);
insta::assert_ron_snapshot!("map_channel_playlists", map_res.c);
insta::assert_ron_snapshot!(format!("map_channel_playlists_{name}"), map_res.c);
}
#[rstest]

View file

@ -188,13 +188,13 @@ pub(crate) enum ChannelBadgeStyle {
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Alert {
pub alert_renderer: AlertRenderer,
pub alert_renderer: TextBox,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct AlertRenderer {
pub(crate) struct TextBox {
#[serde_as(as = "Text")]
pub text: String,
}
@ -523,3 +523,50 @@ pub(crate) struct AvatarStackViewModel {
#[serde_as(deserialize_as = "AttributedText")]
pub text: TextComponent,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ContentImage {
pub collection_thumbnail_view_model: CollectionThumbnailViewModel,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CollectionThumbnailViewModel {
pub primary_thumbnail: ThumbnailViewModelWrap,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ThumbnailViewModelWrap {
pub thumbnail_view_model: ImageViewOl,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ImageViewOl {
pub image: Thumbnails,
#[serde_as(as = "VecSkipError<_>")]
pub overlays: Vec<ImageViewOverlay>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ImageViewOverlay {
pub thumbnail_overlay_badge_view_model: ThumbnailOverlayBadgeViewModel,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ThumbnailOverlayBadgeViewModel {
#[serde_as(as = "VecSkipError<_>")]
pub thumbnail_badges: Vec<ThumbnailBadges>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ThumbnailBadges {
pub thumbnail_badge_view_model: TextBox,
}

View file

@ -3,8 +3,8 @@ use serde_with::{serde_as, DefaultOnError, VecSkipError};
use crate::serializer::text::Text;
use super::AlertRenderer;
use super::ContentsRenderer;
use super::TextBox;
use super::{
music_item::{ItemSection, PlaylistPanelRenderer},
ContentRenderer,
@ -115,7 +115,7 @@ pub(crate) struct MusicLyrics {
#[serde(rename_all = "camelCase")]
pub(crate) enum ListOrMessage<T> {
SectionListRenderer(ContentsRenderer<T>),
MessageRenderer(AlertRenderer),
MessageRenderer(TextBox),
}
#[derive(Debug, Deserialize)]

View file

@ -4,9 +4,9 @@ use serde_with::{serde_as, DefaultOnError, VecSkipError};
use crate::serializer::text::{AttributedText, Text, TextComponent, TextComponents};
use super::{
url_endpoint::NavigationEndpoint, video_item::YouTubeListRenderer, Alert, ContentRenderer,
ContentsRenderer, ImageView, PageHeaderRendererContent, PhMetadataView, ResponseContext,
SectionList, Tab, ThumbnailsWrap, TwoColumnBrowseResults,
url_endpoint::OnTap, video_item::YouTubeListRenderer, Alert, ContentRenderer, ContentsRenderer,
ImageView, PageHeaderRendererContent, PhMetadataView, ResponseContext, SectionList, Tab,
TextBox, ThumbnailsWrap, TwoColumnBrowseResults,
};
#[serde_as]
@ -70,15 +70,7 @@ pub(crate) struct PlaylistHeaderBanner {
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Byline {
pub playlist_byline_renderer: BylineRenderer,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct BylineRenderer {
#[serde_as(as = "Text")]
pub text: String,
pub playlist_byline_renderer: TextBox,
}
#[derive(Debug, Deserialize)]
@ -187,11 +179,5 @@ pub(crate) struct ButtonAction {
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ButtonViewModel {
pub on_tap: ActionOnTap,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ActionOnTap {
pub innertube_command: NavigationEndpoint,
pub on_tap: OnTap,
}

View file

@ -157,6 +157,12 @@ pub(crate) struct WatchEndpointConfig {
pub music_video_type: MusicVideoType,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct OnTap {
pub innertube_command: NavigationEndpoint,
}
#[derive(Default, Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
pub(crate) enum MusicVideoType {
#[default]

View file

@ -4,7 +4,7 @@ use serde_with::{
};
use time::OffsetDateTime;
use super::{ChannelBadge, ContinuationEndpoint, Thumbnails};
use super::{ChannelBadge, ContentImage, ContinuationEndpoint, Thumbnails};
use crate::{
model::{
Channel, ChannelId, ChannelItem, ChannelTag, PlaylistItem, Verification, VideoItem,
@ -33,6 +33,8 @@ pub(crate) enum YouTubeListItem {
ChannelRenderer(ChannelRenderer),
LockupViewModel(LockupViewModel),
/// Continauation items are located at the end of a list
/// and contain the continuation token for progressive loading
#[serde(rename_all = "camelCase")]
@ -165,6 +167,41 @@ pub(crate) struct ShortsOverlayMetadata {
pub secondary_text: Option<String>,
}
/// Generalized list item, currently only used for playlists
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct LockupViewModel {
pub content_image: ContentImage,
pub metadata: LockupViewModelMetadata,
pub content_id: String,
#[serde(default)]
#[serde_as(deserialize_as = "DefaultOnError")]
pub content_type: LockupContentType,
}
#[derive(Default, Debug, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub(crate) enum LockupContentType {
LockupContentTypePlaylist,
#[default]
Unknown,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct LockupViewModelMetadata {
pub lockup_metadata_view_model: LockupViewModelMetadataInner,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct LockupViewModelMetadataInner {
#[serde_as(as = "AttributedText")]
pub title: String,
}
/// Video displayed in a playlist
#[serde_as]
#[derive(Debug, Deserialize)]
@ -681,6 +718,38 @@ impl<T> YouTubeListMapper<T> {
short_description: channel.description_snippet,
}
}
fn map_lockup(&mut self, lockup: LockupViewModel) -> Option<PlaylistItem> {
let md = lockup.metadata.lockup_metadata_view_model;
let tn = lockup
.content_image
.collection_thumbnail_view_model
.primary_thumbnail
.thumbnail_view_model;
match lockup.content_type {
LockupContentType::LockupContentTypePlaylist => Some(PlaylistItem {
id: lockup.content_id,
name: md.title,
thumbnail: tn.image.into(),
channel: self.channel.clone(),
video_count: tn
.overlays
.first()
.and_then(|ol| {
ol.thumbnail_overlay_badge_view_model
.thumbnail_badges
.first()
})
.and_then(|badge| {
util::parse_numeric_or_warn(
&badge.thumbnail_badge_view_model.text,
&mut self.warnings,
)
}),
}),
LockupContentType::Unknown => None,
}
}
}
impl YouTubeListMapper<YouTubeItem> {
@ -711,6 +780,11 @@ impl YouTubeListMapper<YouTubeItem> {
let mapped = YouTubeItem::Channel(self.map_channel(channel));
self.items.push(mapped);
}
YouTubeListItem::LockupViewModel(lockup) => {
if let Some(mapped) = self.map_lockup(lockup) {
self.items.push(YouTubeItem::Playlist(mapped));
}
}
YouTubeListItem::ContinuationItemRenderer {
continuation_endpoint,
} => self.ctoken = Some(continuation_endpoint.continuation_command.token),
@ -784,6 +858,11 @@ impl YouTubeListMapper<PlaylistItem> {
let mapped = self.map_playlist(playlist);
self.items.push(mapped);
}
YouTubeListItem::LockupViewModel(lockup) => {
if let Some(mapped) = self.map_lockup(lockup) {
self.items.push(mapped);
}
}
YouTubeListItem::ContinuationItemRenderer {
continuation_endpoint,
} => self.ctoken = Some(continuation_endpoint.continuation_command.token),

View file

@ -0,0 +1,672 @@
---
source: src/client/channel.rs
expression: map_res.c
---
Channel(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
handle: Some("@EEVblog"),
subscriber_count: Some(952000),
video_count: Some(2),
avatar: [
Thumbnail(
url: "https://yt3.googleusercontent.com/ytc/AIdro_l17lYcTcRSydZeQK-RuiSfEeH5eX9m4irSNQj6109v5MQ=s72-c-k-c0x00ffffff-no-rj",
width: 72,
height: 72,
),
Thumbnail(
url: "https://yt3.googleusercontent.com/ytc/AIdro_l17lYcTcRSydZeQK-RuiSfEeH5eX9m4irSNQj6109v5MQ=s120-c-k-c0x00ffffff-no-rj",
width: 120,
height: 120,
),
Thumbnail(
url: "https://yt3.googleusercontent.com/ytc/AIdro_l17lYcTcRSydZeQK-RuiSfEeH5eX9m4irSNQj6109v5MQ=s160-c-k-c0x00ffffff-no-rj",
width: 160,
height: 160,
),
],
verification: Verified,
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",
tags: [
"electronics",
"engineering",
"maker",
"hacker",
"design",
"circuit",
"hardware",
"pic",
"atmel",
"oscilloscope",
"multimeter",
"diy",
"hobby",
"review",
"teardown",
"microcontroller",
"arduino",
"video",
"blog",
"tutorial",
"how-to",
"interview",
"rant",
"industry",
"news",
"mailbag",
"dumpster diving",
"debunking",
],
banner: [
Thumbnail(
url: "https://yt3.googleusercontent.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
width: 1060,
height: 175,
),
Thumbnail(
url: "https://yt3.googleusercontent.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1138-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
width: 1138,
height: 188,
),
Thumbnail(
url: "https://yt3.googleusercontent.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1707-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
width: 1707,
height: 283,
),
Thumbnail(
url: "https://yt3.googleusercontent.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w2120-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
width: 2120,
height: 351,
),
Thumbnail(
url: "https://yt3.googleusercontent.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w2276-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
width: 2276,
height: 377,
),
Thumbnail(
url: "https://yt3.googleusercontent.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w2560-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
width: 2560,
height: 424,
),
],
has_shorts: true,
has_live: true,
visitor_data: None,
content: Paginator(
count: None,
items: [
PlaylistItem(
id: "PLvOlSehNtuHv268f0mW5m1t_hq_RVGRSA",
name: "Jellybean Components Series",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/XYdmX8w8xwI/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLCqmf6TGfDinNXhgU29ZxOkv2u9sQ",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(5),
),
PlaylistItem(
id: "PLvOlSehNtuHu46I7nFuUg3LC3PpiWTR4f",
name: "Tandy Electronics / Radio Shack & Computers",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/uUXxY6gA-7g/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLAlIVvQ4Axx40Xa_i8F56qmppXEXg",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(11),
),
PlaylistItem(
id: "PLvOlSehNtuHuS01_RNCnvpzyk7bycYCmM",
name: "Open Source Hardware",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/m_8jh_MpWBE/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLBx6U5iikp5rSO78dIWdy1RQ_BLNQ",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(4),
),
PlaylistItem(
id: "PLvOlSehNtuHuwwQ1fpquOJuA5MSfD4iD6",
name: "Fluke Multimeters",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/ymJc5oxthlw/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLDAOiw39aJajjAdroLnuj_fh60Ryw",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(22),
),
PlaylistItem(
id: "PLvOlSehNtuHs2LwEdDwTp3n7mxb-MyBbo",
name: "EEVacademy Digital Design Tutorial Series",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/lJ3q9RHIatU/hqdefault.jpg?sqp=-oaymwExCOADEI4CSFryq4qpAyMIARUAAIhCGAHwAQH4Af4JgALQBYoCDAgAEAEYQyBXKGUwDw==&rs=AOn4CLBaaQaTJzi7H-zjwSsTlNJdBsyqvQ",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(5),
),
PlaylistItem(
id: "PLvOlSehNtuHu2v8THrRMt8E9ziHtRXPm7",
name: "AI / ChatGPT",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/g5_Ts9SWbYs/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLBmZPW6EiAvTCsI86BFg4BxXLj66A",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(3),
),
PlaylistItem(
id: "PLvOlSehNtuHvXuXRmoBUys09Dwi1heNii",
name: "Shorts",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/ndvJtQ8nxV4/hqdefault.jpg?sqp=-oaymwExCOADEI4CSFryq4qpAyMIARUAAIhCGAHwAQH4AbYIgAKAD4oCDAgAEAEYNyBTKH8wDw==&rs=AOn4CLDD0qOLs38KPJtqdG6zCeVLQMf62Q",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(1),
),
PlaylistItem(
id: "PLvOlSehNtuHv3gxNg5BGoZJJu9htoAGB6",
name: "Microcontrollers",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/L9Wrv7nW-S8/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLDiAT5izyig1ntMSUhvSOVuYSsG1Q",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(1),
),
PlaylistItem(
id: "PLvOlSehNtuHvllTQ-vwvY26E3Bvrov93Y",
name: "Bypass Capacitors",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/1xicZF9glH0/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLAFb2FcbpdtAG1xLjmdkdIm1hFvgA",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(4),
),
PlaylistItem(
id: "PLvOlSehNtuHtOV3AEwhuea4TnviddKfAj",
name: "MacGyver Project",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/4yosozyeIP4/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLAkwsCiJjFkWhYxtcg5NgfnQbkZrA",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(3),
),
PlaylistItem(
id: "PLvOlSehNtuHuvHE5GQrQJxWXHdmW2l5IF",
name: "Calculators",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/S3R4r2xvVYQ/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLB7HH5drG-33c1SyRe9kyZBrXvm3A",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(1),
),
PlaylistItem(
id: "PLvOlSehNtuHs6wRwVSaErU0BEnLiHfnKJ",
name: "BM235",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/WPyEFB4cHkA/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLAzBuQFV8T9hM8adlPvv58C9TeDug",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(9),
),
PlaylistItem(
id: "PLvOlSehNtuHu4k0ZkKFLsysSB5iava6Qu",
name: "Vibration Measurement",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/uus_cpZiqsU/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLCqdsjWVFaLOkEcXgbZD2Eca8MnuQ",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(3),
),
PlaylistItem(
id: "PLvOlSehNtuHtdQF-m5UFZ5GEjABadI3kI",
name: "Component Selection",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/uq1DMWtjL2U/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLAbgb1Jdb5P69JGdZQ-a8asLLyYdA",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(6),
),
PlaylistItem(
id: "PLvOlSehNtuHtlndPUSOPgsujUdq1c5Mr9",
name: "Solar Roadways",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/oIImmlfCyzo/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLBxApgyGu3dNXRGoqLctVUnESpEIA",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(23),
),
PlaylistItem(
id: "PLvOlSehNtuHvD6M_7WeN071OVsZFE0_q-",
name: "Electronics Tutorials - AC Circuit Theory Series",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/rrPtvYYJ2-g/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLBEVc71xxSjJ-xlA_dDQaYIjdHyUw",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(3),
),
PlaylistItem(
id: "PLvOlSehNtuHtVLq2MDPIz82BWMIZcuwhK",
name: "Electronics Tutorial - DC Fundamentals",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/xSRe_4TQbuo/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLDP4V24_MG6vzvUZsHep9WFSCCY6Q",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(8),
),
PlaylistItem(
id: "PLvOlSehNtuHvIDfW3x2p4BY6l4RYgfBJE",
name: "Oscilloscope Probing",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/OiAmER1OJh4/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLAXeGAvEc8y3pEsPUxWdsNIP9UmPw",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(14),
),
PlaylistItem(
id: "PLvOlSehNtuHu6Jjb8U82eKQfvKhJVl0Bu",
name: "Thermal Design",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/8ruFVmxf0zs/hqdefault.jpg?sqp=-oaymwExCOADEI4CSFryq4qpAyMIARUAAIhCGAHwAQH4Af4JgALQBYoCDAgAEAEYfyA1KDUwDw==&rs=AOn4CLD6PMawyYXKe8KT1-Y6vWjQc2xIDw",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(9),
),
PlaylistItem(
id: "PLvOlSehNtuHs-X2Awg33PCBNrP2BGFVhC",
name: "Electric Cars",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/CPcZm1Tu5VI/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLCsm8De0QaHPaeCZqxMp_F464fWzg",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(9),
),
PlaylistItem(
id: "PLvOlSehNtuHuLODLTeq3PM-OJRP2nzNUa",
name: "Designing a better uCurrent",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/0AEVilxXAAo/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLCjotFuRjPPBHd2LWzt3lviPj9HaA",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(3),
),
PlaylistItem(
id: "PLvOlSehNtuHtvTKP4RTNW1-08Kmzy1pvA",
name: "EMC Compliance & Measurement",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/lYmfVMWbIHQ/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLBtygEqMXx7Lwe5SuBWt2q0CSahYA",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(8),
),
PlaylistItem(
id: "PLvOlSehNtuHuUTpCrTVX7BdU68l2aVqMv",
name: "Power Counter Display Project",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/nTpE1Nw3Yy4/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLAbPl28_i7isizY6A1t2_c6gV8BAQ",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(2),
),
PlaylistItem(
id: "PLvOlSehNtuHvm120Tq40nKrM5SUBlolN3",
name: "Live - Ask Dave",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/gQ7TTuiDH1M/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLBMnucUil90WeDSIeFz8mZCOtEv9g",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(3),
),
PlaylistItem(
id: "PLvOlSehNtuHsiF93KOLoF1KAHArmIW9lC",
name: "Padauk Microcontroller",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/r45r4rV5JOI/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLCn4kGWcjBOhk3vN8QPMDa9L3mkKA",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(10),
),
PlaylistItem(
id: "PLvOlSehNtuHvxTzBLwUFw4My4rtrNFzED",
name: "Other Debunking Videos",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/WopuF9vD7KE/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLBv5buh3qMs4feQaPj6Fy6bxl_vuA",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(1),
),
PlaylistItem(
id: "PLvOlSehNtuHt2pJ7X5tumuM4Wa3r1OC7Q",
name: "Audio & Speakers",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/qHbkw0Gm7pk/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLCJBYXTDttGHTm51j3bfwqxOqVFig",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(9),
),
PlaylistItem(
id: "PLvOlSehNtuHtX7OearWdmqGzqiu4DHKWi",
name: "Cameras",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/g9umAQ1-an4/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLCB5jNm9U-rypnpthK_N321LpYWew",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(16),
),
PlaylistItem(
id: "PLvOlSehNtuHu-TaNRp27_PiXjBG5wY9Gv",
name: "Cryptocurrency",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/ibPgfzd9zd8/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLDe3IXT88HR3XxnxfqrpAxh6pfYMg",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(7),
),
PlaylistItem(
id: "PLvOlSehNtuHvmK-VGcZ33ZuATmcNB8tvH",
name: "LCD Tutorial",
thumbnail: [
Thumbnail(
url: "https://i.ytimg.com/vi/ZYvxgl-9tNM/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLDv2WT4Chl1_H2G43AjfSFpPcKVoA",
width: 480,
height: 270,
),
],
channel: Some(ChannelTag(
id: "UC2DjFE7Xf11URZqWBigcVOQ",
name: "EEVblog",
avatar: [],
verification: Verified,
subscriber_count: Some(952000),
)),
video_count: Some(6),
),
],
ctoken: Some("4qmFsgLCARIYVUMyRGpGRTdYZjExVVJacVdCaWdjVk9RGnRFZ2x3YkdGNWJHbHpkSE1ZQXlBQk1BRTRBZW9EUEVOblRrUlJhbEZUU2tKSmFWVkZlREpVTW5oVVdsZG9UMlJJVmtsa2JURk1URlphU0ZreGIzcE5NWEF4VVZaU2RGa3dOVU5QU0ZJeVUwTm5PQSUzRCUzRJoCL2Jyb3dzZS1mZWVkVUMyRGpGRTdYZjExVVJacVdCaWdjVk9RcGxheWxpc3RzMTA0"),
endpoint: browse,
),
)

View file

@ -5,7 +5,7 @@ use serde_with::{serde_as, DefaultOnError, DeserializeAs, VecSkipError};
use crate::{
client::response::url_endpoint::{
MusicPage, MusicPageType, MusicVideoType, NavigationEndpoint, PageType,
MusicPage, MusicPageType, MusicVideoType, NavigationEndpoint, OnTap, PageType,
},
model::{richtext::Style, UrlTarget},
util,
@ -159,7 +159,7 @@ pub(crate) struct AttributedText {
struct CommandRun {
start_index: usize,
length: usize,
on_tap: AttributedTextOnTap,
on_tap: OnTap,
#[serde(default)]
#[serde_as(as = "DefaultOnError<_>")]
on_tap_options: Option<AttributedTextOnTapOptions>,
@ -196,12 +196,6 @@ enum Strikethrough {
None,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct AttributedTextOnTap {
innertube_command: NavigationEndpoint,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct AttributedTextOnTapOptions {