fix: A/B test 17: channel playlists lockupViewModel
This commit is contained in:
parent
0919cbd0df
commit
342119dba6
13 changed files with 8770 additions and 49 deletions
|
|
@ -36,6 +36,7 @@ pub enum ABTest {
|
|||
CommentsFrameworkUpdate = 14,
|
||||
ChannelShortsLockup = 15,
|
||||
PlaylistPageHeader = 16,
|
||||
ChannelPlaylistsLockup = 17,
|
||||
}
|
||||
|
||||
/// List of active A/B tests that are run when none is manually specified
|
||||
|
|
@ -112,6 +113,7 @@ pub async fn run_test(
|
|||
ABTest::CommentsFrameworkUpdate => comments_framework_update(&query).await,
|
||||
ABTest::ChannelShortsLockup => channel_shorts_lockup(&query).await,
|
||||
ABTest::PlaylistPageHeader => playlist_page_header_renderer(&query).await,
|
||||
ABTest::ChannelPlaylistsLockup => channel_playlists_lockup(&query).await,
|
||||
}
|
||||
.unwrap();
|
||||
pb.inc(1);
|
||||
|
|
@ -156,7 +158,7 @@ pub async fn attributed_text_description(rp: &RustyPipeQuery) -> Result<bool> {
|
|||
content_check_ok: false,
|
||||
racy_check_ok: false,
|
||||
};
|
||||
let response_txt = rp.raw(ClientType::Desktop, "next", &q).await.unwrap();
|
||||
let response_txt = rp.raw(ClientType::Desktop, "next", &q).await?;
|
||||
|
||||
if !response_txt.contains("\"Black Mamba\"") {
|
||||
bail!("invalid response data");
|
||||
|
|
@ -166,7 +168,7 @@ pub async fn attributed_text_description(rp: &RustyPipeQuery) -> Result<bool> {
|
|||
}
|
||||
|
||||
pub async fn three_tab_channel_layout(rp: &RustyPipeQuery) -> Result<bool> {
|
||||
let channel = rp.channel_videos("UCR-DXc1voovS8nhAvccRZhg").await.unwrap();
|
||||
let channel = rp.channel_videos("UCR-DXc1voovS8nhAvccRZhg").await?;
|
||||
Ok(channel.has_live || channel.has_shorts)
|
||||
}
|
||||
|
||||
|
|
@ -233,8 +235,7 @@ pub async fn discography_page(rp: &RustyPipeQuery) -> Result<bool> {
|
|||
params: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.await?;
|
||||
Ok(res.contains(&format!("\"MPAD{id}\"")))
|
||||
}
|
||||
|
||||
|
|
@ -296,8 +297,7 @@ pub async fn channel_about_modal(rp: &RustyPipeQuery) -> Result<bool> {
|
|||
params: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.await?;
|
||||
Ok(!res.contains("\"EgVhYm91dPIGBAoCEgA%3D\""))
|
||||
}
|
||||
|
||||
|
|
@ -334,8 +334,7 @@ pub async fn music_playlist_two_column(rp: &RustyPipeQuery) -> Result<bool> {
|
|||
params: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.await?;
|
||||
Ok(res.contains("\"musicResponsiveHeaderRenderer\""))
|
||||
}
|
||||
|
||||
|
|
@ -344,8 +343,7 @@ pub async fn comments_framework_update(rp: &RustyPipeQuery) -> Result<bool> {
|
|||
"Eg0SC3dMZHBSN2d1S3k4GAYyJSIRIgt3TGRwUjdndUt5ODAAeAJCEGNvbW1lbnRzLXNlY3Rpb24%3D";
|
||||
let res = rp
|
||||
.raw(ClientType::Desktop, "next", &QCont { continuation })
|
||||
.await
|
||||
.unwrap();
|
||||
.await?;
|
||||
Ok(res.contains("\"frameworkUpdates\""))
|
||||
}
|
||||
|
||||
|
|
@ -360,8 +358,7 @@ pub async fn channel_shorts_lockup(rp: &RustyPipeQuery) -> Result<bool> {
|
|||
params: Some("EgZzaG9ydHPyBgUKA5oBAA%3D%3D"),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.await?;
|
||||
Ok(res.contains("\"shortsLockupViewModel\""))
|
||||
}
|
||||
|
||||
|
|
@ -376,7 +373,21 @@ pub async fn playlist_page_header_renderer(rp: &RustyPipeQuery) -> Result<bool>
|
|||
params: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.await?;
|
||||
Ok(res.contains("\"pageHeaderRenderer\""))
|
||||
}
|
||||
|
||||
pub async fn channel_playlists_lockup(rp: &RustyPipeQuery) -> Result<bool> {
|
||||
let id = "UC2DjFE7Xf11URZqWBigcVOQ";
|
||||
let res = rp
|
||||
.raw(
|
||||
ClientType::Desktop,
|
||||
"browse",
|
||||
&QBrowse {
|
||||
browse_id: id,
|
||||
params: Some("EglwbGF5bGlzdHMgAQ%3D%3D"),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Ok(res.contains("\"lockupViewModel\""))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -899,3 +899,73 @@ YouTube changed the data model for the channel shorts tab
|
|||
}
|
||||
}
|
||||
```
|
||||
|
||||
## [17] Channel playlists: lockupViewModel
|
||||
|
||||
- **Encountered on:** 09.11.2024
|
||||
- **Impact:** 🟢 Low
|
||||
- **Endpoint:** browse
|
||||
- **Status:** Common (50%)
|
||||
|
||||
YouTube changed the data model for the channel playlists / podcasts / albums tab
|
||||
|
||||
```json
|
||||
{
|
||||
"lockupViewModel": {
|
||||
"contentImage": {
|
||||
"collectionThumbnailViewModel": {
|
||||
"primaryThumbnail": {
|
||||
"thumbnailViewModel": {
|
||||
"image": {
|
||||
"sources": [
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/XYdmX8w8xwI/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLCqmf6TGfDinNXhgU29ZxOkv2u9sQ",
|
||||
"width": 480,
|
||||
"height": 270
|
||||
}
|
||||
]
|
||||
},
|
||||
"overlays": [
|
||||
{
|
||||
"thumbnailOverlayBadgeViewModel": {
|
||||
"thumbnailBadges": [
|
||||
{
|
||||
"thumbnailBadgeViewModel": {
|
||||
"icon": {
|
||||
"sources": [
|
||||
{
|
||||
"clientResource": {
|
||||
"imageName": "PLAYLISTS"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"text": "5 videos",
|
||||
"badgeStyle": "THUMBNAIL_OVERLAY_BADGE_STYLE_DEFAULT",
|
||||
"backgroundColor": {
|
||||
"lightTheme": 2370867,
|
||||
"darkTheme": 2370867
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"position": "THUMBNAIL_OVERLAY_BADGE_POSITION_BOTTOM_END"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"lockupMetadataViewModel": {
|
||||
"title": {
|
||||
"content": "Jellybean Components Series"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contentId": "PLvOlSehNtuHv268f0mW5m1t_hq_RVGRSA",
|
||||
"contentType": "LOCKUP_CONTENT_TYPE_PLAYLIST"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
7854
testfiles/channel/channel_playlists_20241109_lockup.json
Normal file
7854
testfiles/channel/channel_playlists_20241109_lockup.json
Normal file
File diff suppressed because it is too large
Load diff
Reference in a new issue