Compare commits
No commits in common. "449fc0128e04c55637e5fd2258913f80ca5660e9" and "79c504954ef856332d7e7ff05e712818f010588d" have entirely different histories.
449fc0128e
...
79c504954e
15 changed files with 138 additions and 18242 deletions
|
@ -1,8 +1,5 @@
|
|||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
Test:
|
||||
|
|
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -2,16 +2,6 @@
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [v0.1.3](https://code.thetadev.de/ThetaDev/rustypipe/compare/rustypipe/v0.1.2..v0.1.3) - 2024-04-01
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Parse new comment model (A/B#14 frameworkUpdates) - ([b0331f7](https://code.thetadev.de/ThetaDev/rustypipe/commit/b0331f7250f5d7d61a45209150739d2cb08b4280))
|
||||
|
||||
### ◀️ Revert
|
||||
|
||||
- "fix: improve VecLogErr messages" (leads to infinite loop) - ([348c852](https://code.thetadev.de/ThetaDev/rustypipe/commit/348c8523fe847f2f6ce98317375a7ab65e778ed2))
|
||||
|
||||
## [v0.1.2](https://code.thetadev.de/ThetaDev/rustypipe/compare/rustypipe/v0.1.1..v0.1.2) - 2024-03-26
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "rustypipe"
|
||||
version = "0.1.3"
|
||||
version = "0.1.2"
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
|
|
|
@ -36,7 +36,7 @@ Client for the public YouTube / YouTube Music API (Innertube), inspired by
|
|||
|
||||
```toml
|
||||
[dependencies]
|
||||
rustypipe = "0.1.3"
|
||||
rustypipe = "0.1.2"
|
||||
tokio = { version = "1.20.0", features = ["macros", "rt-multi-thread"] }
|
||||
```
|
||||
|
||||
|
|
|
@ -13,8 +13,6 @@ use rustypipe::param::ChannelVideoTab;
|
|||
use serde::de::IgnoredAny;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::model::QCont;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, TryFromPrimitive, Serialize, Deserialize,
|
||||
)]
|
||||
|
@ -33,15 +31,10 @@ pub enum ABTest {
|
|||
LikeButtonViewmodel = 11,
|
||||
ChannelPageHeader = 12,
|
||||
MusicPlaylistTwoColumn = 13,
|
||||
CommentsFrameworkUpdate = 14,
|
||||
}
|
||||
|
||||
/// List of active A/B tests that are run when none is manually specified
|
||||
const TESTS_TO_RUN: [ABTest; 3] = [
|
||||
ABTest::ChannelPageHeader,
|
||||
ABTest::MusicPlaylistTwoColumn,
|
||||
ABTest::CommentsFrameworkUpdate,
|
||||
];
|
||||
const TESTS_TO_RUN: [ABTest; 2] = [ABTest::ChannelPageHeader, ABTest::MusicPlaylistTwoColumn];
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ABTestRes {
|
||||
|
@ -111,7 +104,6 @@ pub async fn run_test(
|
|||
ABTest::LikeButtonViewmodel => like_button_viewmodel(&query).await,
|
||||
ABTest::ChannelPageHeader => channel_page_header(&query).await,
|
||||
ABTest::MusicPlaylistTwoColumn => music_playlist_two_column(&query).await,
|
||||
ABTest::CommentsFrameworkUpdate => comments_framework_update(&query).await,
|
||||
}
|
||||
.unwrap();
|
||||
pb.inc(1);
|
||||
|
@ -364,20 +356,3 @@ pub async fn music_playlist_two_column(rp: &RustyPipeQuery) -> Result<bool> {
|
|||
.unwrap();
|
||||
Ok(res.contains("\"musicResponsiveHeaderRenderer\""))
|
||||
}
|
||||
|
||||
pub async fn comments_framework_update(rp: &RustyPipeQuery) -> Result<bool> {
|
||||
let continuation =
|
||||
"Eg0SC3dMZHBSN2d1S3k4GAYyJSIRIgt3TGRwUjdndUt5ODAAeAJCEGNvbW1lbnRzLXNlY3Rpb24%3D";
|
||||
let res = rp
|
||||
.raw(
|
||||
ClientType::Desktop,
|
||||
"next",
|
||||
&QCont {
|
||||
context: rp.get_context(ClientType::Desktop, true, None).await,
|
||||
continuation,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
Ok(res.contains("\"frameworkUpdates\""))
|
||||
}
|
||||
|
|
|
@ -592,6 +592,7 @@ be accomodated. There are also no mobile/TV header images available any more.
|
|||
}
|
||||
```
|
||||
|
||||
|
||||
## [13] Music album/playlist 2-column layout
|
||||
|
||||
- **Encountered on:** 29.02.2024
|
||||
|
@ -601,149 +602,5 @@ be accomodated. There are also no mobile/TV header images available any more.
|
|||
|
||||
![A/B test 13 screenshot](./_img/ab_13.png)
|
||||
|
||||
YouTube Music updated the layout of album and playlist pages. The new layout shows the
|
||||
cover on the left side of the playlist content.
|
||||
|
||||
## [14] Comments Framework update
|
||||
|
||||
- **Encountered on:** 31.01.2024
|
||||
- **Impact:** 🟢 Low
|
||||
- **Endpoint:** next
|
||||
- **Status:** Common (50%)
|
||||
|
||||
YouTube changed the data model for YouTube comments, now putting the content into a
|
||||
seperate framework update object
|
||||
|
||||
```json
|
||||
{
|
||||
"frameworkUpdates": {
|
||||
"onResponseReceivedEndpoints": [
|
||||
{
|
||||
"clickTrackingParams": "CAAQg2ciEwi64q3dmKGFAxWvy0IFHc14BKM=",
|
||||
"reloadContinuationItemsCommand": {
|
||||
"targetId": "comments-section",
|
||||
"continuationItems": [
|
||||
{
|
||||
"commentThreadRenderer": {
|
||||
"replies": {
|
||||
"commentRepliesRenderer": {
|
||||
"contents": [
|
||||
{
|
||||
"continuationItemRenderer": {
|
||||
"trigger": "CONTINUATION_TRIGGER_ON_ITEM_SHOWN",
|
||||
"continuationEndpoint": {
|
||||
"clickTrackingParams": "CHgQvnUiEwi64q3dmKGFAxWvy0IFHc14BKM=",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"sendPost": true,
|
||||
"apiUrl": "/youtubei/v1/next"
|
||||
}
|
||||
},
|
||||
"continuationCommand": {
|
||||
"token": "Eg0SC1FpcDFWa1R1TTcwGAYygwEaUBIaVWd5TlRUOHV4REVqZ1lxeWJJRjRBYUFCQWciAggAKhhVQ3lhZmx6ek9IMEdDNjgzRGxRLWZ6d2cyC1FpcDFWa1R1TTcwQAFICoIBAggBQi9jb21tZW50LXJlcGxpZXMtaXRlbS1VZ3lOVFQ4dXhERWpnWXF5YklGNEFhQUJBZw%3D%3D",
|
||||
"request": "CONTINUATION_REQUEST_TYPE_WATCH_NEXT"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"trackingParams": "CHgQvnUiEwi64q3dmKGFAxWvy0IFHc14BKM=",
|
||||
"viewReplies": {
|
||||
"buttonRenderer": {
|
||||
"text": { "runs": [{ "text": "220 replies" }] },
|
||||
"icon": { "iconType": "ARROW_DROP_DOWN" },
|
||||
"trackingParams": "CHoQosAEIhMIuuKt3ZihhQMVr8tCBR3NeASj",
|
||||
"iconPosition": "BUTTON_ICON_POSITION_TYPE_LEFT_OF_TEXT"
|
||||
}
|
||||
},
|
||||
"hideReplies": {
|
||||
"buttonRenderer": {
|
||||
"text": { "runs": [{ "text": "220 replies" }] },
|
||||
"icon": { "iconType": "ARROW_DROP_UP" },
|
||||
"trackingParams": "CHkQ280EIhMIuuKt3ZihhQMVr8tCBR3NeASj",
|
||||
"iconPosition": "BUTTON_ICON_POSITION_TYPE_LEFT_OF_TEXT"
|
||||
}
|
||||
},
|
||||
"targetId": "comment-replies-item-UgyNTT8uxDEjgYqybIF4AaABAg"
|
||||
}
|
||||
},
|
||||
"trackingParams": "CHYQwnUYywEiEwi64q3dmKGFAxWvy0IFHc14BKM=",
|
||||
"renderingPriority": "RENDERING_PRIORITY_PINNED_COMMENT",
|
||||
"isModeratedElqComment": false,
|
||||
"commentViewModel": {
|
||||
"commentViewModel": {
|
||||
"commentId": "UgyNTT8uxDEjgYqybIF4AaABAg"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"entityBatchUpdate": {
|
||||
"mutations": [
|
||||
{
|
||||
"entityKey": "EhpVZ3lOVFQ4dXhERWpnWXF5YklGNEFhQUJBZyAoKAE%3D",
|
||||
"type": "ENTITY_MUTATION_TYPE_REPLACE",
|
||||
"payload": {
|
||||
"commentEntityPayload": {
|
||||
"key": "EhpVZ3lOVFQ4dXhERWpnWXF5YklGNEFhQUJBZyAoKAE%3D",
|
||||
"properties": {
|
||||
"commentId": "UgyNTT8uxDEjgYqybIF4AaABAg",
|
||||
"content": {
|
||||
"content": "⚠️ Important notice: if you put any symbol immediately after markup, it will not work: *here is the comma*, without space.\n\nYou should leave space before and after , to make it work.\n\nSame for _underscore_, and -hyphen-.\n\nLeave space before opening and after closing underscore and hyphen. Put all dots and commas inside markup.",
|
||||
"styleRuns": [
|
||||
{
|
||||
"startIndex": 135,
|
||||
"length": 28,
|
||||
"weightLabel": "FONT_WEIGHT_MEDIUM"
|
||||
},
|
||||
{
|
||||
"startIndex": 267,
|
||||
"length": 10,
|
||||
"weightLabel": "FONT_WEIGHT_NORMAL",
|
||||
"italic": true
|
||||
},
|
||||
{
|
||||
"startIndex": 282,
|
||||
"length": 7,
|
||||
"weightLabel": "FONT_WEIGHT_NORMAL",
|
||||
"strikethrough": "LINE_STYLE_SINGLE"
|
||||
}
|
||||
]
|
||||
},
|
||||
"publishedTime": "2 years ago (edited)",
|
||||
"replyLevel": 0,
|
||||
"authorButtonA11y": "@kibizoid",
|
||||
"toolbarStateKey": "EhpVZ3lOVFQ4dXhERWpnWXF5YklGNEFhQUJBZyAsKAE%3D",
|
||||
"translateButtonEntityKey": "EhpVZ3lOVFQ4dXhERWpnWXF5YklGNEFhQUJBZyD_ASgB"
|
||||
},
|
||||
"author": {
|
||||
"channelId": "UCUJfyiofeHQTmxKwZ6cCwIg",
|
||||
"displayName": "@kibizoid",
|
||||
"avatarThumbnailUrl": "https://yt3.ggpht.com/ytc/AIdro_nY2PkIyojDqs9Bk5RY6J90-U7wePswTYl799DNJQ=s88-c-k-c0x00ffffff-no-rj",
|
||||
"isVerified": false,
|
||||
"isCurrentUser": false,
|
||||
"isCreator": false,
|
||||
"isArtist": false
|
||||
},
|
||||
"avatar": {
|
||||
"image": {
|
||||
"sources": [
|
||||
{
|
||||
"url": "https://yt3.ggpht.com/ytc/AIdro_nY2PkIyojDqs9Bk5RY6J90-U7wePswTYl799DNJQ=s88-c-k-c0x00ffffff-no-rj",
|
||||
"width": 88,
|
||||
"height": 88
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
YouTube Music updated the layout of album and playlist pages. The new layout shows
|
||||
the cover on the left side of the playlist content.
|
||||
|
|
|
@ -3,7 +3,7 @@ use serde_with::{rust::deserialize_ignore_any, serde_as, DefaultOnError, VecSkip
|
|||
|
||||
use super::{
|
||||
video_item::YouTubeListRenderer, Alert, ChannelBadge, ContentRenderer, ContentsRenderer,
|
||||
ContinuationActionWrap, ImageView, ResponseContext, Thumbnails, TwoColumnBrowseResults,
|
||||
ContinuationActionWrap, ResponseContext, Thumbnails, TwoColumnBrowseResults,
|
||||
};
|
||||
use crate::serializer::text::{AttributedText, Text, TextComponent};
|
||||
|
||||
|
@ -224,6 +224,12 @@ pub(crate) struct PhAvatarView3 {
|
|||
pub avatar_view_model: ImageView,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct ImageView {
|
||||
pub image: Thumbnails,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct PhMetadataView {
|
||||
|
|
|
@ -48,7 +48,6 @@ pub(crate) mod channel_rss;
|
|||
pub(crate) use channel_rss::ChannelRss;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use serde::{
|
||||
|
@ -107,12 +106,6 @@ pub(crate) struct ThumbnailsWrap {
|
|||
pub thumbnail: Thumbnails,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct ImageView {
|
||||
pub image: Thumbnails,
|
||||
}
|
||||
|
||||
/// List of images in different resolutions.
|
||||
/// Not only used for thumbnails, but also for avatars and banners.
|
||||
#[derive(Default, Debug, Deserialize)]
|
||||
|
@ -381,87 +374,3 @@ pub(crate) fn alerts_to_err(id: &str, alerts: Option<Vec<Alert>>) -> ExtractionE
|
|||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
// FRAMEWORK UPDATES
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct FrameworkUpdates<T> {
|
||||
pub entity_batch_update: EntityBatchUpdate<T>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct EntityBatchUpdate<T> {
|
||||
pub mutations: FrameworkUpdateMutations<T>,
|
||||
}
|
||||
|
||||
/// List of update mutations that deserializes into a HashMap (entity_key => payload)
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct FrameworkUpdateMutations<T> {
|
||||
pub items: HashMap<String, T>,
|
||||
pub warnings: Vec<String>,
|
||||
}
|
||||
|
||||
impl<'de, T> Deserialize<'de> for FrameworkUpdateMutations<T>
|
||||
where
|
||||
T: Deserialize<'de>,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
struct SeqVisitor<T>(PhantomData<T>);
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum MutationOrError<T> {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
Good {
|
||||
entity_key: String,
|
||||
payload: T,
|
||||
},
|
||||
Error(serde_json::Value),
|
||||
}
|
||||
|
||||
impl<'de, T> Visitor<'de> for SeqVisitor<T>
|
||||
where
|
||||
T: Deserialize<'de>,
|
||||
{
|
||||
type Value = FrameworkUpdateMutations<T>;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("sequence of entity mutations")
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
let mut items = HashMap::with_capacity(seq.size_hint().unwrap_or_default());
|
||||
let mut warnings = Vec::new();
|
||||
|
||||
while let Some(value) = seq.next_element::<MutationOrError<T>>()? {
|
||||
match value {
|
||||
MutationOrError::Good {
|
||||
entity_key,
|
||||
payload,
|
||||
} => {
|
||||
items.insert(entity_key, payload);
|
||||
}
|
||||
MutationOrError::Error(value) => {
|
||||
warnings.push(format!(
|
||||
"error deserializing item: {}",
|
||||
serde_json::to_string(&value).unwrap_or_default()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(FrameworkUpdateMutations { items, warnings })
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_seq(SeqVisitor(PhantomData::<T>))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
use serde::Deserialize;
|
||||
use serde_with::{rust::deserialize_ignore_any, serde_as, DefaultOnError, VecSkipError};
|
||||
|
||||
use crate::serializer::text::TextComponent;
|
||||
use crate::serializer::{
|
||||
text::{AccessibilityText, AttributedText, Text, TextComponent, TextComponents},
|
||||
text::{AccessibilityText, AttributedText, Text, TextComponents},
|
||||
MapResult,
|
||||
};
|
||||
|
||||
|
@ -12,10 +13,7 @@ use super::{
|
|||
url_endpoint::BrowseEndpointWrap, ContinuationEndpoint, ContinuationItemRenderer, Icon,
|
||||
MusicContinuationData, Thumbnails,
|
||||
};
|
||||
use super::{
|
||||
ChannelBadge, ContentsRendererLogged, FrameworkUpdates, ImageView, ResponseContext,
|
||||
YouTubeListItem,
|
||||
};
|
||||
use super::{ChannelBadge, ContentsRendererLogged, ResponseContext, YouTubeListItem};
|
||||
|
||||
/*
|
||||
#VIDEO DETAILS
|
||||
|
@ -478,7 +476,6 @@ pub(crate) struct VideoComments {
|
|||
/// - n*commentRenderer, continuationItemRenderer:
|
||||
/// replies + continuation
|
||||
pub on_response_received_endpoints: MapResult<Vec<CommentsContItem>>,
|
||||
pub framework_updates: Option<FrameworkUpdates<Payload>>,
|
||||
}
|
||||
|
||||
/// Video comments continuation
|
||||
|
@ -501,13 +498,23 @@ pub(crate) struct AppendComments {
|
|||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) enum CommentListItem {
|
||||
/// Top-level comment
|
||||
CommentThreadRenderer(CommentThreadRenderer),
|
||||
#[serde(rename_all = "camelCase")]
|
||||
CommentThreadRenderer {
|
||||
comment: Comment,
|
||||
/// Continuation token to fetch replies
|
||||
#[serde(default)]
|
||||
replies: Replies,
|
||||
#[serde(default)]
|
||||
#[serde_as(deserialize_as = "DefaultOnError")]
|
||||
rendering_priority: CommentPriority,
|
||||
},
|
||||
/// Reply comment
|
||||
CommentRenderer(CommentRenderer),
|
||||
/// Reply comment (A/B #14)
|
||||
CommentViewModel(CommentViewModel),
|
||||
/// Continuation token to fetch more comments
|
||||
ContinuationItemRenderer(ContinuationItemVariants),
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ContinuationItemRenderer {
|
||||
continuation_endpoint: ContinuationEndpoint,
|
||||
},
|
||||
/// Header of the comment section (contains number of comments)
|
||||
#[serde(rename_all = "camelCase")]
|
||||
CommentsHeaderRenderer {
|
||||
|
@ -517,46 +524,6 @@ pub(crate) enum CommentListItem {
|
|||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub(crate) enum ContinuationItemVariants {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
Ep {
|
||||
continuation_endpoint: ContinuationEndpoint,
|
||||
},
|
||||
Btn {
|
||||
button: ContinuationButton,
|
||||
},
|
||||
}
|
||||
|
||||
impl ContinuationItemVariants {
|
||||
pub fn token(self) -> String {
|
||||
match self {
|
||||
ContinuationItemVariants::Ep {
|
||||
continuation_endpoint,
|
||||
} => continuation_endpoint,
|
||||
ContinuationItemVariants::Btn { button } => button.button_renderer.command,
|
||||
}
|
||||
.continuation_command
|
||||
.token
|
||||
}
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct CommentThreadRenderer {
|
||||
/// Missing on the FrameworkUpdate data model (A/B #14)
|
||||
pub comment: Option<Comment>,
|
||||
pub comment_view_model: Option<CommentViewModelWrap>,
|
||||
/// Continuation token to fetch replies
|
||||
#[serde(default)]
|
||||
pub replies: Replies,
|
||||
#[serde(default)]
|
||||
#[serde_as(deserialize_as = "DefaultOnError")]
|
||||
pub rendering_priority: CommentPriority,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Comment {
|
||||
|
@ -597,7 +564,7 @@ pub(crate) struct CommentRenderer {
|
|||
pub action_buttons: CommentActionButtons,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Copy, Debug, Deserialize)]
|
||||
#[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub(crate) enum CommentPriority {
|
||||
/// Default rendering priority
|
||||
|
@ -607,26 +574,6 @@ pub(crate) enum CommentPriority {
|
|||
RenderingPriorityPinnedComment,
|
||||
}
|
||||
|
||||
impl From<CommentPriority> for bool {
|
||||
fn from(value: CommentPriority) -> Self {
|
||||
matches!(value, CommentPriority::RenderingPriorityPinnedComment)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct CommentViewModelWrap {
|
||||
pub comment_view_model: CommentViewModel,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct CommentViewModel {
|
||||
pub comment_id: String,
|
||||
pub comment_key: String,
|
||||
pub toolbar_state_key: String,
|
||||
}
|
||||
|
||||
/// Does not contain replies directly but a continuation token
|
||||
/// for fetching them.
|
||||
#[derive(Default, Debug, Deserialize)]
|
||||
|
@ -690,85 +637,3 @@ pub(crate) struct AuthorCommentBadgeRenderer {
|
|||
/// Artist: `OFFICIAL_ARTIST_BADGE`
|
||||
pub icon: Icon,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) enum Payload {
|
||||
CommentEntityPayload(CommentEntityPayload),
|
||||
#[serde(rename_all = "camelCase")]
|
||||
EngagementToolbarStateEntityPayload {
|
||||
heart_state: HeartState,
|
||||
},
|
||||
#[serde(other, deserialize_with = "deserialize_ignore_any")]
|
||||
None,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct CommentEntityPayload {
|
||||
pub properties: CommentProperties,
|
||||
#[serde(default)]
|
||||
#[serde_as(as = "DefaultOnError")]
|
||||
pub author: Option<CommentAuthor>,
|
||||
pub toolbar: CommentToolbar,
|
||||
#[serde(default)]
|
||||
pub avatar: ImageView,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct CommentProperties {
|
||||
#[serde_as(as = "AttributedText")]
|
||||
pub content: TextComponents,
|
||||
pub published_time: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct CommentAuthor {
|
||||
pub channel_id: String,
|
||||
pub display_name: String,
|
||||
#[serde(default)]
|
||||
pub is_verified: bool,
|
||||
#[serde(default)]
|
||||
pub is_artist: bool,
|
||||
#[serde(default)]
|
||||
pub is_creator: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct CommentToolbar {
|
||||
pub like_count_notliked: String,
|
||||
pub reply_count: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Deserialize)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub(crate) enum HeartState {
|
||||
ToolbarHeartStateUnhearted,
|
||||
ToolbarHeartStateHearted,
|
||||
}
|
||||
|
||||
impl From<HeartState> for bool {
|
||||
fn from(value: HeartState) -> Self {
|
||||
match value {
|
||||
HeartState::ToolbarHeartStateUnhearted => false,
|
||||
HeartState::ToolbarHeartStateHearted => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct ContinuationButton {
|
||||
pub button_renderer: ContinuationButtonRenderer,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct ContinuationButtonRenderer {
|
||||
pub command: ContinuationEndpoint,
|
||||
}
|
||||
|
|
|
@ -1,691 +0,0 @@
|
|||
---
|
||||
source: src/client/video_details.rs
|
||||
expression: map_res.c
|
||||
---
|
||||
Paginator(
|
||||
count: Some(20617),
|
||||
items: [
|
||||
Comment(
|
||||
id: "UgyNTT8uxDEjgYqybIF4AaABAg",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "⚠\u{fe0f} Important notice: if you put any symbol immediately after markup, it will not work: *here is the comma*, without space.\n\nYou should leave space before and after , to make it work.\n\nSame for _underscore_, and -hyphen-.\n\nLeave space before opening and after closing underscore and hyphen. Put all dots and commas inside markup.",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCUJfyiofeHQTmxKwZ6cCwIg",
|
||||
name: "@kibizoid",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/ytc/AIdro_nY2PkIyojDqs9Bk5RY6J90-U7wePswTYl799DNJQ=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "2 years ago (edited)",
|
||||
like_count: Some(293),
|
||||
reply_count: 220,
|
||||
replies: Paginator(
|
||||
count: Some(220),
|
||||
items: [],
|
||||
ctoken: Some("Eg0SC1FpcDFWa1R1TTcwGAYygwEaUBIaVWd5TlRUOHV4REVqZ1lxeWJJRjRBYUFCQWciAggAKhhVQ3lhZmx6ek9IMEdDNjgzRGxRLWZ6d2cyC1FpcDFWa1R1TTcwQAFICoIBAggBQi9jb21tZW50LXJlcGxpZXMtaXRlbS1VZ3lOVFQ4dXhERWpnWXF5YklGNEFhQUJBZw%3D%3D"),
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: true,
|
||||
hearted: true,
|
||||
),
|
||||
Comment(
|
||||
id: "UgycWgNOoon0A4EV9LZ4AaABAg",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "Me: tests out fonts\nFriend: Why are you doing this?\nMe: my goals are beyond your understanding",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCr0PeEY_am9P-GobbfvKECw",
|
||||
name: "@userfjdrg",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/Zr2PmQsrD4obL2n5HS18X3jKXGJ-HFjIJS_OcZv4I5VAk5HuLRCpzFprY5Hh7n23-FCURVJi=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "2 years ago",
|
||||
like_count: Some(80),
|
||||
reply_count: 34,
|
||||
replies: Paginator(
|
||||
count: Some(34),
|
||||
items: [],
|
||||
ctoken: Some("Eg0SC1FpcDFWa1R1TTcwGAYygwEaUBIaVWd5Y1dnTk9vb24wQTRFVjlMWjRBYUFCQWciAggAKhhVQ3lhZmx6ek9IMEdDNjgzRGxRLWZ6d2cyC1FpcDFWa1R1TTcwQAFICoIBAggBQi9jb21tZW50LXJlcGxpZXMtaXRlbS1VZ3ljV2dOT29vbjBBNEVWOUxaNEFhQUJBZw%3D%3D"),
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: true,
|
||||
),
|
||||
Comment(
|
||||
id: "Ugy5iq4M1c3WS3lGmih4AaABAg",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "To-do list\n• be dumb\n• get kicked out when i can legally live alone\n• spend money on pointless things",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCDB5XvpUB8cEvjbWewlp28w",
|
||||
name: "@T0r0xFan",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/PPL5vj6-pXFpaLa41yet34OHGcEYt06WPQLmruaiFJSM0eLmn9ZQW0QgTtdafDBO-kNy2oukVA=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "2 years ago (edited)",
|
||||
like_count: Some(48),
|
||||
reply_count: 22,
|
||||
replies: Paginator(
|
||||
count: Some(22),
|
||||
items: [],
|
||||
ctoken: Some("Eg0SC1FpcDFWa1R1TTcwGAYygwEaUBIaVWd5NWlxNE0xYzNXUzNsR21paDRBYUFCQWciAggAKhhVQ3lhZmx6ek9IMEdDNjgzRGxRLWZ6d2cyC1FpcDFWa1R1TTcwQAFICoIBAggBQi9jb21tZW50LXJlcGxpZXMtaXRlbS1VZ3k1aXE0TTFjM1dTM2xHbWloNEFhQUJBZw%3D%3D"),
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: true,
|
||||
),
|
||||
Comment(
|
||||
id: "UgxqDIVVcoigjtx4Dtl4AaABAg",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "omg thank you! Ive been looking for this tutorial for a year forever",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCxa4xER0-cFbcIYp0ZIeVaw",
|
||||
name: "@LunasVibe",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/P5Io1kQb7GRwe4pgNsaYFEm30hDl_T7Tp5rZo7aYWFkqbV6Yp_lCYVuaaK7O3SEsnIX_5iC1Hw=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "1 month ago",
|
||||
like_count: Some(0),
|
||||
reply_count: 0,
|
||||
replies: Paginator(
|
||||
count: Some(0),
|
||||
items: [],
|
||||
ctoken: None,
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: false,
|
||||
),
|
||||
Comment(
|
||||
id: "UgxDQfVQdYaWR-VUM-94AaABAg",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "tysm\ni finally learned it\nother channel never go straight to the point",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UC8cojSRuyZT74Bs_b5AecTA",
|
||||
name: "@Bp_bts_skz_for_life",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/0zFBRYv8wo2JqzkyMk29xgC8zD1nKYNSSoD3Zo9XP8t9rHrbTYEEt0gdu0O3XS7Scpza3JJKog=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "1 month ago (edited)",
|
||||
like_count: Some(1),
|
||||
reply_count: 1,
|
||||
replies: Paginator(
|
||||
count: Some(1),
|
||||
items: [],
|
||||
ctoken: Some("Eg0SC1FpcDFWa1R1TTcwGAYygwEaUBIaVWd4RFFmVlFkWWFXUi1WVU0tOTRBYUFCQWciAggAKhhVQ3lhZmx6ek9IMEdDNjgzRGxRLWZ6d2cyC1FpcDFWa1R1TTcwQAFICoIBAggBQi9jb21tZW50LXJlcGxpZXMtaXRlbS1VZ3hEUWZWUWRZYVdSLVZVTS05NEFhQUJBZw%3D%3D"),
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: false,
|
||||
),
|
||||
Comment(
|
||||
id: "UgxFvrmwec-jmfQyGRR4AaABAg",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "I like how this was straight to the point. Unlike other channels lol Thank you!",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCCyIVS_s1-jA48pPft8AifA",
|
||||
name: "@ishouldbesleepingalready",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/N90y_XdoDP6Rd1v6Z5OpDH8jMtvqpU1qnF6DJoIL6qcLiWfZK7ok8u_IxqSxJazaQH6oqhEbqA=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "2 years ago",
|
||||
like_count: Some(241),
|
||||
reply_count: 65,
|
||||
replies: Paginator(
|
||||
count: Some(65),
|
||||
items: [],
|
||||
ctoken: Some("Eg0SC1FpcDFWa1R1TTcwGAYygwEaUBIaVWd4RnZybXdlYy1qbWZReUdSUjRBYUFCQWciAggAKhhVQ3lhZmx6ek9IMEdDNjgzRGxRLWZ6d2cyC1FpcDFWa1R1TTcwQAFICoIBAggBQi9jb21tZW50LXJlcGxpZXMtaXRlbS1VZ3hGdnJtd2VjLWptZlF5R1JSNEFhQUJBZw%3D%3D"),
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: false,
|
||||
),
|
||||
Comment(
|
||||
id: "Ugy-3OYEcwxkvyrrCqN4AaABAg",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "To the person who is reading this: You\'re intelligent and smart, stay safe",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCQklgcA8quxZm5pgNAsVJAQ",
|
||||
name: "@blocking948",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/ytc/AIdro_nAg9bEjW4otWlryJwqAgiDRLzy8ZX-ROqkDY1ksQ=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "3 years ago (edited)",
|
||||
like_count: Some(711),
|
||||
reply_count: 250,
|
||||
replies: Paginator(
|
||||
count: Some(250),
|
||||
items: [],
|
||||
ctoken: Some("Eg0SC1FpcDFWa1R1TTcwGAYygwEaUBIaVWd5LTNPWUVjd3hrdnlyckNxTjRBYUFCQWciAggAKhhVQ3lhZmx6ek9IMEdDNjgzRGxRLWZ6d2cyC1FpcDFWa1R1TTcwQAFICoIBAggBQi9jb21tZW50LXJlcGxpZXMtaXRlbS1VZ3ktM09ZRWN3eGt2eXJyQ3FONEFhQUJBZw%3D%3D"),
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: true,
|
||||
),
|
||||
Comment(
|
||||
id: "Ugylw3ss_xv9svWbRud4AaABAg",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "‘ life could be a dream, life could be a dream ‘",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCSyjdP7Duhns4Ybncy6ObZA",
|
||||
name: "@malarchee0899",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/dSwRzOfoKrN4yc58uhFlIyqBXmbi6B14-On-wEEM_S6Nr6aDHTkG-xVkI1-u-uBwqKqodEgrMro=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "13 days ago",
|
||||
like_count: Some(2),
|
||||
reply_count: 1,
|
||||
replies: Paginator(
|
||||
count: Some(1),
|
||||
items: [],
|
||||
ctoken: Some("Eg0SC1FpcDFWa1R1TTcwGAYygwEaUBIaVWd5bHczc3NfeHY5c3ZXYlJ1ZDRBYUFCQWciAggAKhhVQ3lhZmx6ek9IMEdDNjgzRGxRLWZ6d2cyC1FpcDFWa1R1TTcwQAFICoIBAggBQi9jb21tZW50LXJlcGxpZXMtaXRlbS1VZ3lsdzNzc194djlzdldiUnVkNEFhQUJBZw%3D%3D"),
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: false,
|
||||
),
|
||||
Comment(
|
||||
id: "UgydXobRB0F5dW1KVsF4AaABAg",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "Woah! thank you for showing me this I really needed it!",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UC9f9uJgwsCBBHA4CioIzdkA",
|
||||
name: "@fatimagarcia3162",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/cKmBVKqq9mkW4F355y3UtUw4POwTWKi-0LUYLDx85vffRd7pU-LECXvudUrHH_9qobo6A1kM=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "1 month ago",
|
||||
like_count: Some(0),
|
||||
reply_count: 0,
|
||||
replies: Paginator(
|
||||
count: Some(0),
|
||||
items: [],
|
||||
ctoken: None,
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: false,
|
||||
),
|
||||
Comment(
|
||||
id: "UgwmFn6ejKltcZ_BZvl4AaABAg",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "The fitness gram pacer test is a multistage aerobic capacity test that progressively gets more difficult as it continues.",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCIymYi-_AJ10pYrh8sqTBTg",
|
||||
name: "@No-du9is",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/ytc/AIdro_lPWhGGvIIA08s4u_-Lwyx88rGSRksOFeYHipE=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "2 years ago",
|
||||
like_count: Some(22),
|
||||
reply_count: 12,
|
||||
replies: Paginator(
|
||||
count: Some(12),
|
||||
items: [],
|
||||
ctoken: Some("Eg0SC1FpcDFWa1R1TTcwGAYygwEaUBIaVWd3bUZuNmVqS2x0Y1pfQlp2bDRBYUFCQWciAggAKhhVQ3lhZmx6ek9IMEdDNjgzRGxRLWZ6d2cyC1FpcDFWa1R1TTcwQAFICoIBAggBQi9jb21tZW50LXJlcGxpZXMtaXRlbS1VZ3dtRm42ZWpLbHRjWl9CWnZsNEFhQUJBZw%3D%3D"),
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: true,
|
||||
),
|
||||
Comment(
|
||||
id: "UgxtXH6bWRWm8ahavfR4AaABAg",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "YouTube got a new update(or probably it\'s a bug) and for that it\'s not showing bold/strikethrough/italic on the app but it\'s showing on other places.",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCyaflzzOH0GC683DlQ-fzwg",
|
||||
name: "@HaruXen",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/YMmDD0zp5wT6l5ozqVMEMuqm5W07QFqmMHzOJ9QKGnSf9xpgEQ0rznstfXlBDxlFpLIrltQxRg=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "3 years ago",
|
||||
like_count: Some(167),
|
||||
reply_count: 337,
|
||||
replies: Paginator(
|
||||
count: Some(337),
|
||||
items: [],
|
||||
ctoken: Some("Eg0SC1FpcDFWa1R1TTcwGAYygwEaUBIaVWd4dFhINmJXUldtOGFoYXZmUjRBYUFCQWciAggAKhhVQ3lhZmx6ek9IMEdDNjgzRGxRLWZ6d2cyC1FpcDFWa1R1TTcwQAFICoIBAggBQi9jb21tZW50LXJlcGxpZXMtaXRlbS1VZ3h0WEg2YldSV204YWhhdmZSNEFhQUJBZw%3D%3D"),
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: true,
|
||||
pinned: false,
|
||||
hearted: false,
|
||||
),
|
||||
Comment(
|
||||
id: "UgyHg3XnjBV935da_Lh4AaABAg",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "omg it works i actuallly cant believe this ive been wanting to do this for ages thankyou so much!",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCFL5d8rMCfbxppODSbRLOgQ",
|
||||
name: "@Auf-dem-weg-zum-sieg",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/oi4vbSV3Cx9G97QcHkSMWL98LksC6rnTLoq93T5sOO8MNuZPXWEXq9Nqkp8XYF93L2WklHADmNY=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "4 days ago",
|
||||
like_count: Some(0),
|
||||
reply_count: 0,
|
||||
replies: Paginator(
|
||||
count: Some(0),
|
||||
items: [],
|
||||
ctoken: None,
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: false,
|
||||
),
|
||||
Comment(
|
||||
id: "UgxIL5emXyn42htlfZZ4AaABAg",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "I did know how to do that writing where the text is highlighted before, and now after I watched this video I knew even how to write those styles of text.",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UChkVaXCYN_QcaE50zETAMOg",
|
||||
name: "@CasamTheAnimator",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/ctUbv8QpWPZRZumEBTVhlSSxg0JfiyvJ40nrWj_0ivOy5s6OoPK7iNp01diskRLs1Hig4ZE82w=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "3 weeks ago",
|
||||
like_count: Some(0),
|
||||
reply_count: 0,
|
||||
replies: Paginator(
|
||||
count: Some(0),
|
||||
items: [],
|
||||
ctoken: None,
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: false,
|
||||
),
|
||||
Comment(
|
||||
id: "UgwMKY-89XCdCVB9bXp4AaABAg",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "Nobody asked for, but everyone needed",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCDezbPSXn3awzhxVm7qhGtg",
|
||||
name: "@0_Ed",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/4DWdgRIJ0lEV-e4GZFrdf8MGxQBtML2aix2orKBt3iM6QBrh7Kg1ur1FZlyRmqWpWnRPRIex9w=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "3 years ago",
|
||||
like_count: Some(12),
|
||||
reply_count: 5,
|
||||
replies: Paginator(
|
||||
count: Some(5),
|
||||
items: [],
|
||||
ctoken: Some("Eg0SC1FpcDFWa1R1TTcwGAYygwEaUBIaVWd3TUtZLTg5WENkQ1ZCOWJYcDRBYUFCQWciAggAKhhVQ3lhZmx6ek9IMEdDNjgzRGxRLWZ6d2cyC1FpcDFWa1R1TTcwQAFICoIBAggBQi9jb21tZW50LXJlcGxpZXMtaXRlbS1VZ3dNS1ktODlYQ2RDVkI5YlhwNEFhQUJBZw%3D%3D"),
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: true,
|
||||
),
|
||||
Comment(
|
||||
id: "UgyfuG2sCDvgnRUYHJp4AaABAg",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "me: types bold\n\nHaruTutorial: your bald",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCge96FdHXkARBjzPhdYl8Sg",
|
||||
name: "@stargazeu",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/JoeW_qNuf9b6BusB3E6JShizqRLB4jR3NaTnsnzvpUQ1KW88OcS74_Sx1h6vjZiXK2uOxnrUNeY=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "3 years ago",
|
||||
like_count: Some(42),
|
||||
reply_count: 15,
|
||||
replies: Paginator(
|
||||
count: Some(15),
|
||||
items: [],
|
||||
ctoken: Some("Eg0SC1FpcDFWa1R1TTcwGAYygwEaUBIaVWd5ZnVHMnNDRHZnblJVWUhKcDRBYUFCQWciAggAKhhVQ3lhZmx6ek9IMEdDNjgzRGxRLWZ6d2cyC1FpcDFWa1R1TTcwQAFICoIBAggBQi9jb21tZW50LXJlcGxpZXMtaXRlbS1VZ3lmdUcyc0NEdmduUlVZSEpwNEFhQUJBZw%3D%3D"),
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: true,
|
||||
),
|
||||
Comment(
|
||||
id: "UgwAXndNNEa1h-VVIC94AaABAg",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "the McDonald’s don’t feel like turning the Icecream machine on",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCkmY4kQ8e8gDRllV485Rd9g",
|
||||
name: "@Flowershowrise",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/c2iMsq-wFjKRjTDqPy14UpMI1B9hNms4moW9H7xtPjOMI0vjaHwN94me23upYar-8CE3s6QkFw=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "1 month ago",
|
||||
like_count: Some(2),
|
||||
reply_count: 0,
|
||||
replies: Paginator(
|
||||
count: Some(0),
|
||||
items: [],
|
||||
ctoken: None,
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: false,
|
||||
),
|
||||
Comment(
|
||||
id: "UgwwEBqareQ0tpsW7RR4AaABAg",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "YOOO THIS IS SICK! THANK YOU MAN!",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UChIbg4dGguUwzg7O-xmi57g",
|
||||
name: "@ziaaaaa.",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/HEAdH93oAQu2ScXNmiKIISapv5O9dKSVLuT3gD1zJhSgHqTaptL7JPun6A5GZqg58_C75_OPkQ=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "1 month ago",
|
||||
like_count: Some(0),
|
||||
reply_count: 0,
|
||||
replies: Paginator(
|
||||
count: Some(0),
|
||||
items: [],
|
||||
ctoken: None,
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: false,
|
||||
),
|
||||
Comment(
|
||||
id: "UgxnFMLrpvbCWzHidml4AaABAg",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "Someone must honor him , this man is the best , no , he is a LEGEND . We must all thank him for his video and for getting to the point immediately.",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCeGJuvHZqqebHTE_Kz2zyug",
|
||||
name: "@Dahackabarade",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/csf-cEr2z4uWg8ZpVEPqgS2D2ZUHKBAJWnIbnzQCRtAlioSlUbtQZAyx76tnyfpXpixrsKke6DE=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "2 years ago (edited)",
|
||||
like_count: Some(11),
|
||||
reply_count: 5,
|
||||
replies: Paginator(
|
||||
count: Some(5),
|
||||
items: [],
|
||||
ctoken: Some("Eg0SC1FpcDFWa1R1TTcwGAYygwEaUBIaVWd4bkZNTHJwdmJDV3pIaWRtbDRBYUFCQWciAggAKhhVQ3lhZmx6ek9IMEdDNjgzRGxRLWZ6d2cyC1FpcDFWa1R1TTcwQAFICoIBAggBQi9jb21tZW50LXJlcGxpZXMtaXRlbS1VZ3huRk1McnB2YkNXekhpZG1sNEFhQUJBZw%3D%3D"),
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: false,
|
||||
),
|
||||
Comment(
|
||||
id: "UgwCIwmF6synP7UF_wV4AaABAg",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "Never gonna give you up. Im gonna let u down",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCyrDrBrWvXwIhf2s2F1dq-Q",
|
||||
name: "@imnotjust...2326",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/Clzb1GD_KnRm9u7mOAN165HZO_H0jhXQlRG8YvEjqkDuBUNibGkclRyRZIdhi-yJhC4hHorGLQ=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "2 years ago",
|
||||
like_count: Some(14),
|
||||
reply_count: 0,
|
||||
replies: Paginator(
|
||||
count: Some(0),
|
||||
items: [],
|
||||
ctoken: None,
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: true,
|
||||
),
|
||||
Comment(
|
||||
id: "Ugyb5Wy91Yon69o3wLh4AaABAg",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "Thank you for being A Legend No, The Goat Lets go dude",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCPCgaC_EJlS5RpRRWPHWvKA",
|
||||
name: "@gfghdgfghd6391",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/ytc/AIdro_n0lpwvjOP9HO_XHxzInwQoqQ7qIXeR0SqZVbCE=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "1 month ago",
|
||||
like_count: Some(0),
|
||||
reply_count: 0,
|
||||
replies: Paginator(
|
||||
count: Some(0),
|
||||
items: [],
|
||||
ctoken: None,
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: false,
|
||||
),
|
||||
],
|
||||
ctoken: Some("Eg0SC1FpcDFWa1R1TTcwGAYy4gIKuAJnZXRfcmFua2VkX3N0cmVhbXMtLUNxWUJDSUFFRlJlMzBUZ2Ftd0VLbGdFSTJGOFFnQVFZQnlLTEFSdUNtdFZ5a3dZMFFzVVVvM3I0LUY0OWU2d3RGSGFjbDIxS0Nsd3M4ZFZNaGdDdm9VWFhac2NZNXVncURIaUNiQVpveUczUEh6MTRPQ0tJV1BZTm9PTnN6dlFPVDZkaFZXMGRiSlZNelJXSW5QTm5QY0pyTmhQbzAyT1ZuamlVcHJTTHc1UEZxVHFBRkxlYXEtSHQtdU5uZkp1SzItMXVhQkp2aWE3S183QzgzOURiekJhY2tFeVRzUUFRRkJJRkNJZ2dHQUFTQndpSElCQUJHQUFTQlFpb0lCZ0FFZ1VJaVNBWUFCSUhDSVVnRUFrWUFSSUhDSVFnRUFzWUFSZ0EiESILUWlwMVZrVHVNNzAwAHgBKBRCEGNvbW1lbnRzLXNlY3Rpb24%3D"),
|
||||
endpoint: browse,
|
||||
)
|
|
@ -1,351 +0,0 @@
|
|||
---
|
||||
source: src/client/video_details.rs
|
||||
expression: map_res.c
|
||||
---
|
||||
Paginator(
|
||||
count: None,
|
||||
items: [
|
||||
Comment(
|
||||
id: "Ugzu-t48vV9SjdeWIMh4AaABAg.A-Grr7qN9uaA-GzThFMUcw",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "Fact🙌🏻",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UC4I0-MXGyTRsc1tsJrDMh2A",
|
||||
name: "@Sadaf788",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/uIXOBEBIhYn6Y8cQHlhaGGnCqOqS8PI5YR_Cx28qR_Y_p1qkjHC8V68iwxfeJ20eQ3zp81owJ64=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "2 months ago",
|
||||
like_count: Some(800),
|
||||
reply_count: 0,
|
||||
replies: Paginator(
|
||||
count: Some(0),
|
||||
items: [],
|
||||
ctoken: None,
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: false,
|
||||
),
|
||||
Comment(
|
||||
id: "Ugzu-t48vV9SjdeWIMh4AaABAg.A-Grr7qN9uaA-H295I5iMZ",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "Facts",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCdJ0CAWWa1rRjRbVrQUrU_w",
|
||||
name: "@Biggest_Onceu",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/dHe_Zmr-7EueUz0R-BbuFoGwRyNMXwa3gb_GJMgAie9yU5PM6LbgTlNJ1zivRxnjiFg2nrlF1Es=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "2 months ago",
|
||||
like_count: Some(530),
|
||||
reply_count: 0,
|
||||
replies: Paginator(
|
||||
count: Some(0),
|
||||
items: [],
|
||||
ctoken: None,
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: false,
|
||||
),
|
||||
Comment(
|
||||
id: "Ugzu-t48vV9SjdeWIMh4AaABAg.A-Grr7qN9uaA-H73oLoHkI",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "Faacttts",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCPPhfcNhQ768F0Hhk3-25hA",
|
||||
name: "@neni996",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/t__fCiOIhKCg2BwaxaRog9F_a5uemd8rTEvwzWYl6WeLn-nN9xEW0FvxUtM0fQrh2Dj_6ENsGQ=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "2 months ago",
|
||||
like_count: Some(412),
|
||||
reply_count: 0,
|
||||
replies: Paginator(
|
||||
count: Some(0),
|
||||
items: [],
|
||||
ctoken: None,
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: false,
|
||||
),
|
||||
Comment(
|
||||
id: "Ugzu-t48vV9SjdeWIMh4AaABAg.A-Grr7qN9uaA-H7nBbPD5z",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "REAL",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCQyomFJDEQtC2lbQ6E7QUGA",
|
||||
name: "@momolvs",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/nt6GjcwAs8NPgaF29pn-cHTPmPAwQC_e_lXQHGDjZJGSRKzsH4s3le8Wpg0ByAUvPwTSHWe0OA=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "2 months ago",
|
||||
like_count: Some(312),
|
||||
reply_count: 0,
|
||||
replies: Paginator(
|
||||
count: Some(0),
|
||||
items: [],
|
||||
ctoken: None,
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: false,
|
||||
),
|
||||
Comment(
|
||||
id: "Ugzu-t48vV9SjdeWIMh4AaABAg.A-Grr7qN9uaA-H9s3LvStZ",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "FR!!",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UC9HOPOf3gD3aw6Ej9WZ-rYg",
|
||||
name: "@user-vv9yp1fh8w",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/ytc/AIdro_nqyUCWW7jqWrg_39XNQ18-acPouL6wyHeQnZOMbmlSa9x2YGWINkfU1DLcvaXw=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "2 months ago",
|
||||
like_count: Some(187),
|
||||
reply_count: 0,
|
||||
replies: Paginator(
|
||||
count: Some(0),
|
||||
items: [],
|
||||
ctoken: None,
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: false,
|
||||
),
|
||||
Comment(
|
||||
id: "Ugzu-t48vV9SjdeWIMh4AaABAg.A-Grr7qN9uaA-HB52Dv3SL",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "fato.",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCW6ua0VDEFz7SyVMX01fTCA",
|
||||
name: "@millenatwice",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/rInaNzUF3JLL_pCNfZtZlf2cHipf1yM4grr8VGJRHocwOQiuq1x7kUVi24q3ydtDC0j8bqbw2vA=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "2 months ago",
|
||||
like_count: Some(165),
|
||||
reply_count: 0,
|
||||
replies: Paginator(
|
||||
count: Some(0),
|
||||
items: [],
|
||||
ctoken: None,
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: false,
|
||||
),
|
||||
Comment(
|
||||
id: "Ugzu-t48vV9SjdeWIMh4AaABAg.A-Grr7qN9uaA-HDLrM1OPD",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "For sure!! TWICE is always TWICE!! They always give GOOD MUSIC",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCqDSps4SV0v8Dzf8esr6ScQ",
|
||||
name: "@Its_me_hi_good",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/ytc/AIdro_nfh9rWK7_gae1YkUgKuq13G9OUpxQCqrXAAi1hfPkCvHeHORbq3DUTYm7b5eoy=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "2 months ago",
|
||||
like_count: Some(341),
|
||||
reply_count: 0,
|
||||
replies: Paginator(
|
||||
count: Some(0),
|
||||
items: [],
|
||||
ctoken: None,
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: false,
|
||||
),
|
||||
Comment(
|
||||
id: "Ugzu-t48vV9SjdeWIMh4AaABAg.A-Grr7qN9uaA-HIAbm3Him",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "Fr",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCFA4BaLyvM1DDNsFyE_BHqQ",
|
||||
name: "@amanpreetbrar7836",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/ytc/AIdro_n1M-xxgSLIqe17kDv-i-tPn23FT1ywabpRAQ=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "2 months ago",
|
||||
like_count: Some(84),
|
||||
reply_count: 0,
|
||||
replies: Paginator(
|
||||
count: Some(0),
|
||||
items: [],
|
||||
ctoken: None,
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: false,
|
||||
),
|
||||
Comment(
|
||||
id: "Ugzu-t48vV9SjdeWIMh4AaABAg.A-Grr7qN9uaA-HIrpOKIi7",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "Presave I got you and with youth on spotify",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCxMDESp088wGItVM4xXACgw",
|
||||
name: "@RitaOnce9",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/hVTumbLCpmtJw0c2mq1B-ES5W3kdYPqnNrtzEcUhxCoUN6dAutXc6exaPRnBMLM6Jw1ILPoBDg=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "2 months ago",
|
||||
like_count: Some(112),
|
||||
reply_count: 0,
|
||||
replies: Paginator(
|
||||
count: Some(0),
|
||||
items: [],
|
||||
ctoken: None,
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: false,
|
||||
),
|
||||
Comment(
|
||||
id: "Ugzu-t48vV9SjdeWIMh4AaABAg.A-Grr7qN9uaA-HKMOSBnLK",
|
||||
text: RichText([
|
||||
Text(
|
||||
text: "real",
|
||||
),
|
||||
]),
|
||||
author: Some(ChannelTag(
|
||||
id: "UCqeOr9ddrs_d6OgboKjk6zw",
|
||||
name: "@twiceupremacy",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.ggpht.com/ytc/AIdro_lpKYjxtRm1HSjv3tFvGwrvnRILmJoQrPTBBOFG=s88-c-k-c0x00ffffff-no-rj",
|
||||
width: 88,
|
||||
height: 88,
|
||||
),
|
||||
],
|
||||
verification: None,
|
||||
subscriber_count: None,
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
publish_date_txt: "2 months ago",
|
||||
like_count: Some(75),
|
||||
reply_count: 0,
|
||||
replies: Paginator(
|
||||
count: Some(0),
|
||||
items: [],
|
||||
ctoken: None,
|
||||
endpoint: browse,
|
||||
),
|
||||
by_owner: false,
|
||||
pinned: false,
|
||||
hearted: false,
|
||||
),
|
||||
],
|
||||
ctoken: Some("Eg0SC2hhZjY3ZUtGMHVvGAYy1wEKUGdldF9jb21tZW50X3dpdGhfcmVwbGllc19zdHJlYW0tLUNnZ0lnQVFWRjdmUk9CSUZDS0FnR0FFWUFDSU9DZ3dJeXUzdXJRWVE2Tkt2NGdFGlASGlVnenUtdDQ4dlY5U2pkZVdJTWg0QWFBQkFnIgIIACoYVUNhTzZUWXRsQzhVNXR0ejYyaFRyWmdnMgtoYWY2N2VLRjB1b0AASDKCAQIIASgKQi9jb21tZW50LXJlcGxpZXMtaXRlbS1VZ3p1LXQ0OHZWOVNqZGVXSU1oNEFhQUJBZw%3D%3D"),
|
||||
endpoint: browse,
|
||||
)
|
|
@ -1,4 +1,4 @@
|
|||
use std::{collections::HashMap, fmt::Debug};
|
||||
use std::fmt::Debug;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
|
@ -6,7 +6,7 @@ use crate::{
|
|||
error::{Error, ExtractionError},
|
||||
model::{
|
||||
paginator::{ContinuationEndpoint, Paginator},
|
||||
ChannelTag, Chapter, Comment, Verification, VideoDetails, VideoItem,
|
||||
ChannelTag, Chapter, Comment, VideoDetails, VideoItem,
|
||||
},
|
||||
param::Language,
|
||||
serializer::MapResult,
|
||||
|
@ -14,7 +14,7 @@ use crate::{
|
|||
};
|
||||
|
||||
use super::{
|
||||
response::{self, video_details::Payload, IconType},
|
||||
response::{self, IconType},
|
||||
ClientType, MapResponse, QContinuation, RustyPipeQuery, YTContext,
|
||||
};
|
||||
|
||||
|
@ -391,73 +391,44 @@ impl MapResponse<Paginator<Comment>> for response::VideoComments {
|
|||
_vdata: Option<&str>,
|
||||
) -> Result<MapResult<Paginator<Comment>>, ExtractionError> {
|
||||
let received_endpoints = self.on_response_received_endpoints;
|
||||
let mut warnings = Vec::new();
|
||||
let mut warnings = received_endpoints.warnings;
|
||||
|
||||
let mut comments = Vec::new();
|
||||
let mut comment_count = None;
|
||||
let mut ctoken = None;
|
||||
|
||||
let mut mutations = if let Some(upd) = self.framework_updates {
|
||||
let mut m = upd.entity_batch_update.mutations;
|
||||
warnings.append(&mut m.warnings);
|
||||
m.items
|
||||
} else {
|
||||
HashMap::new()
|
||||
};
|
||||
|
||||
received_endpoints.c.into_iter().for_each(|citem| {
|
||||
let mut items = citem.append_continuation_items_action.continuation_items;
|
||||
warnings.append(&mut items.warnings);
|
||||
items.c.into_iter().for_each(|item| match item {
|
||||
response::video_details::CommentListItem::CommentThreadRenderer(thread) => {
|
||||
if let Some(comment) = thread.comment {
|
||||
comments.push(map_comment(
|
||||
response::video_details::CommentListItem::CommentThreadRenderer {
|
||||
comment,
|
||||
replies,
|
||||
rendering_priority,
|
||||
} => {
|
||||
let mut res = map_comment(
|
||||
comment.comment_renderer,
|
||||
Some(thread.replies),
|
||||
thread.rendering_priority,
|
||||
Some(replies),
|
||||
rendering_priority,
|
||||
lang,
|
||||
&mut warnings,
|
||||
));
|
||||
} else if let Some(vm) = thread.comment_view_model {
|
||||
if let Some(c) = map_comment_vm(
|
||||
vm.comment_view_model,
|
||||
&mut mutations,
|
||||
Some(thread.replies),
|
||||
thread.rendering_priority,
|
||||
lang,
|
||||
&mut warnings,
|
||||
) {
|
||||
comments.push(c);
|
||||
}
|
||||
} else {
|
||||
warnings.push(
|
||||
"comment does not contain comment or commentViewModel field".to_owned(),
|
||||
);
|
||||
}
|
||||
comments.push(res.c);
|
||||
warnings.append(&mut res.warnings);
|
||||
}
|
||||
response::video_details::CommentListItem::CommentRenderer(comment) => {
|
||||
comments.push(map_comment(
|
||||
let mut res = map_comment(
|
||||
comment,
|
||||
None,
|
||||
response::video_details::CommentPriority::RenderingPriorityUnknown,
|
||||
lang,
|
||||
&mut warnings,
|
||||
));
|
||||
);
|
||||
comments.push(res.c);
|
||||
warnings.append(&mut res.warnings);
|
||||
}
|
||||
response::video_details::CommentListItem::CommentViewModel(vm) => {
|
||||
if let Some(c) = map_comment_vm(
|
||||
vm,
|
||||
&mut mutations,
|
||||
None,
|
||||
response::video_details::CommentPriority::RenderingPriorityUnknown,
|
||||
lang,
|
||||
&mut warnings,
|
||||
) {
|
||||
comments.push(c);
|
||||
}
|
||||
}
|
||||
response::video_details::CommentListItem::ContinuationItemRenderer(cont) => {
|
||||
ctoken = Some(cont.token());
|
||||
response::video_details::CommentListItem::ContinuationItemRenderer {
|
||||
continuation_endpoint,
|
||||
} => {
|
||||
ctoken = Some(continuation_endpoint.continuation_command.token);
|
||||
}
|
||||
response::video_details::CommentListItem::CommentsHeaderRenderer { count_text } => {
|
||||
comment_count = count_text
|
||||
|
@ -500,50 +471,44 @@ fn map_recommendations(
|
|||
}
|
||||
}
|
||||
|
||||
fn map_replies(
|
||||
fn map_comment(
|
||||
c: response::video_details::CommentRenderer,
|
||||
replies: Option<response::video_details::Replies>,
|
||||
priority: response::video_details::CommentPriority,
|
||||
lang: Language,
|
||||
warnings: &mut Vec<String>,
|
||||
) -> (Vec<Comment>, Option<String>) {
|
||||
) -> MapResult<Comment> {
|
||||
let mut warnings = Vec::new();
|
||||
|
||||
let mut reply_ctoken = None;
|
||||
let replies = replies
|
||||
.map(|replies| {
|
||||
let replies = replies.map(|replies| {
|
||||
replies
|
||||
.comment_replies_renderer
|
||||
.contents
|
||||
.into_iter()
|
||||
.filter_map(|item| match item {
|
||||
response::video_details::CommentListItem::CommentRenderer(comment) => {
|
||||
Some(map_comment(
|
||||
let mut res = map_comment(
|
||||
comment,
|
||||
None,
|
||||
response::video_details::CommentPriority::default(),
|
||||
lang,
|
||||
warnings,
|
||||
))
|
||||
);
|
||||
warnings.append(&mut res.warnings);
|
||||
Some(res.c)
|
||||
}
|
||||
response::video_details::CommentListItem::ContinuationItemRenderer(cont) => {
|
||||
reply_ctoken = Some(cont.token());
|
||||
response::video_details::CommentListItem::ContinuationItemRenderer {
|
||||
continuation_endpoint,
|
||||
} => {
|
||||
reply_ctoken = Some(continuation_endpoint.continuation_command.token);
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
(replies, reply_ctoken)
|
||||
}
|
||||
});
|
||||
|
||||
fn map_comment(
|
||||
c: response::video_details::CommentRenderer,
|
||||
replies: Option<response::video_details::Replies>,
|
||||
priority: response::video_details::CommentPriority,
|
||||
lang: Language,
|
||||
warnings: &mut Vec<String>,
|
||||
) -> Comment {
|
||||
let (replies, reply_ctoken) = map_replies(replies, lang, warnings);
|
||||
|
||||
Comment {
|
||||
MapResult {
|
||||
c: Comment {
|
||||
id: c.comment_id,
|
||||
text: c.content_text.into(),
|
||||
author: match (c.author_endpoint, c.author_text) {
|
||||
|
@ -559,91 +524,32 @@ fn map_comment(
|
|||
}),
|
||||
_ => None,
|
||||
},
|
||||
publish_date: timeago::parse_timeago_dt_or_warn(lang, &c.published_time_text, warnings),
|
||||
publish_date: timeago::parse_timeago_dt_or_warn(
|
||||
lang,
|
||||
&c.published_time_text,
|
||||
&mut warnings,
|
||||
),
|
||||
publish_date_txt: c.published_time_text,
|
||||
like_count: match c.vote_count {
|
||||
Some(txt) => util::parse_numeric_or_warn(&txt, warnings),
|
||||
Some(txt) => util::parse_numeric_or_warn(&txt, &mut warnings),
|
||||
None => Some(0),
|
||||
},
|
||||
reply_count: c.reply_count as u32,
|
||||
replies: Paginator::new(Some(c.reply_count), replies, reply_ctoken),
|
||||
replies: replies
|
||||
.map(|items| Paginator::new(Some(c.reply_count), items, reply_ctoken))
|
||||
.unwrap_or_default(),
|
||||
by_owner: c.author_is_channel_owner,
|
||||
pinned: priority.into(),
|
||||
pinned: priority
|
||||
== response::video_details::CommentPriority::RenderingPriorityPinnedComment,
|
||||
hearted: c
|
||||
.action_buttons
|
||||
.comment_action_buttons_renderer
|
||||
.creator_heart
|
||||
.map(|h| h.creator_heart_renderer.is_hearted)
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_comment_vm(
|
||||
vm: response::video_details::CommentViewModel,
|
||||
mutations: &mut HashMap<String, response::video_details::Payload>,
|
||||
replies: Option<response::video_details::Replies>,
|
||||
priority: response::video_details::CommentPriority,
|
||||
lang: Language,
|
||||
warnings: &mut Vec<String>,
|
||||
) -> Option<Comment> {
|
||||
let (replies, reply_ctoken) = map_replies(replies, lang, warnings);
|
||||
|
||||
let ce = if let Some(Payload::CommentEntityPayload(ce)) = mutations.remove(&vm.comment_key) {
|
||||
ce
|
||||
} else {
|
||||
warnings.push(format!(
|
||||
"comment `{}` does not have entity payload (key: `{}`)",
|
||||
vm.comment_id, vm.comment_key
|
||||
));
|
||||
return None;
|
||||
};
|
||||
let hearted = if let Some(Payload::EngagementToolbarStateEntityPayload { heart_state }) =
|
||||
mutations.get(&vm.toolbar_state_key)
|
||||
{
|
||||
(*heart_state).into()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let mut parse_num = |s: &str| -> Option<u32> {
|
||||
if s.is_empty() || s == " " {
|
||||
Some(0)
|
||||
} else {
|
||||
util::parse_large_numstr_or_warn(s, lang, warnings)
|
||||
}
|
||||
};
|
||||
|
||||
let reply_count = parse_num(&ce.toolbar.reply_count).unwrap_or_default();
|
||||
|
||||
Some(Comment {
|
||||
id: vm.comment_id,
|
||||
text: ce.properties.content.into(),
|
||||
by_owner: ce.author.as_ref().map(|a| a.is_creator).unwrap_or_default(),
|
||||
author: ce.author.map(|a| ChannelTag {
|
||||
id: a.channel_id,
|
||||
name: a.display_name,
|
||||
avatar: ce.avatar.image.into(),
|
||||
verification: if a.is_artist {
|
||||
Verification::Artist
|
||||
} else if a.is_verified {
|
||||
Verification::Verified
|
||||
} else {
|
||||
Verification::None
|
||||
},
|
||||
subscriber_count: None,
|
||||
}),
|
||||
like_count: parse_num(&ce.toolbar.like_count_notliked),
|
||||
reply_count,
|
||||
replies: Paginator::new(Some(reply_count.into()), replies, reply_ctoken),
|
||||
publish_date: timeago::parse_timeago_dt_or_warn(
|
||||
lang,
|
||||
&ce.properties.published_time,
|
||||
warnings,
|
||||
),
|
||||
publish_date_txt: ce.properties.published_time,
|
||||
pinned: priority.into(),
|
||||
hearted,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -708,8 +614,6 @@ mod tests {
|
|||
#[rstest]
|
||||
#[case::top("top")]
|
||||
#[case::latest("latest")]
|
||||
#[case::frameworkupd("20240401_frameworkupd")]
|
||||
#[case::frameworkupd_reply("20240401_frameworkupd_reply")]
|
||||
fn map_comments(#[case] name: &str) {
|
||||
let json_path = path!(*TESTFILES / "video_details" / format!("comments_{name}.json"));
|
||||
let json_file = File::open(json_path).unwrap();
|
||||
|
|
|
@ -20,13 +20,6 @@ where
|
|||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum GoodOrError<T> {
|
||||
Good(T),
|
||||
Error(serde_json::Value),
|
||||
}
|
||||
|
||||
struct SeqVisitor<T>(PhantomData<T>);
|
||||
|
||||
impl<'de, T> Visitor<'de> for SeqVisitor<T>
|
||||
|
@ -46,16 +39,14 @@ where
|
|||
let mut values = Vec::with_capacity(seq.size_hint().unwrap_or_default());
|
||||
let mut warnings = Vec::new();
|
||||
|
||||
while let Some(value) = seq.next_element()? {
|
||||
match value {
|
||||
GoodOrError::<T>::Good(value) => {
|
||||
values.push(value);
|
||||
}
|
||||
GoodOrError::<T>::Error(value) => {
|
||||
warnings.push(format!(
|
||||
"error deserializing item: {}",
|
||||
serde_json::to_string(&value).unwrap_or_default()
|
||||
));
|
||||
loop {
|
||||
match seq.next_element::<T>() {
|
||||
Ok(val) => match val {
|
||||
Some(val) => values.push(val),
|
||||
None => break,
|
||||
},
|
||||
Err(e) => {
|
||||
warnings.push(format!("error deserializing item: {e}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -186,8 +177,8 @@ mod tests {
|
|||
|
||||
insta::assert_debug_snapshot!(res.items.warnings, @r###"
|
||||
[
|
||||
"error deserializing item: {\"xyz\":\"i2\"}",
|
||||
"error deserializing item: {\"namra\":\"i4\"}",
|
||||
"error deserializing item: missing field `name` at line 1 column 40",
|
||||
"error deserializing item: missing field `name` at line 1 column 73",
|
||||
]
|
||||
"###);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue