fix: fetching comment count
This commit is contained in:
parent
e800e16c68
commit
8c1e7bf6ac
8 changed files with 113 additions and 46 deletions
|
|
@ -150,7 +150,11 @@ async fn download_playlist(
|
||||||
|
|
||||||
let rp = RustyPipe::default();
|
let rp = RustyPipe::default();
|
||||||
let mut playlist = rp.query().playlist(id).await.unwrap();
|
let mut playlist = rp.query().playlist(id).await.unwrap();
|
||||||
playlist.videos.extend_pages(rp.query(), usize::MAX).await.unwrap();
|
playlist
|
||||||
|
.videos
|
||||||
|
.extend_pages(rp.query(), usize::MAX)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Indicatif setup
|
// Indicatif setup
|
||||||
let multi = MultiProgress::new();
|
let multi = MultiProgress::new();
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,10 @@ async fn comments_top(testfiles: &Path) {
|
||||||
let details = rp.query().video_details("ZeerrnuLi5E").await.unwrap();
|
let details = rp.query().video_details("ZeerrnuLi5E").await.unwrap();
|
||||||
|
|
||||||
let rp = rp_testfile(&json_path);
|
let rp = rp_testfile(&json_path);
|
||||||
rp.query().video_comments(&details.top_comments.ctoken.unwrap()).await.unwrap();
|
rp.query()
|
||||||
|
.video_comments(&details.top_comments.ctoken.unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
// rp.query().video_comments("Eg0SC0lITnpPSGk4c0pzGAYyJSIRIgtJSE56T0hpOHNKczAAeAJCEGNvbW1lbnRzLXNlY3Rpb24%3D").await.unwrap();
|
// rp.query().video_comments("Eg0SC0lITnpPSGk4c0pzGAYyJSIRIgtJSE56T0hpOHNKczAAeAJCEGNvbW1lbnRzLXNlY3Rpb24%3D").await.unwrap();
|
||||||
|
|
||||||
// Desktop 1
|
// Desktop 1
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,12 @@ pub enum VideoListItem<T> {
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ContinuationItemRenderer {
|
||||||
|
pub continuation_endpoint: ContinuationEndpoint,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ContinuationEndpoint {
|
pub struct ContinuationEndpoint {
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ use crate::serializer::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
ChannelBadge, ContentsRenderer, ContinuationEndpoint, Icon, Thumbnails, VideoBadge,
|
ChannelBadge, ContentsRenderer, ContinuationEndpoint, ContinuationItemRenderer, Icon,
|
||||||
VideoListItem, VideoOwner,
|
Thumbnails, VideoBadge, VideoListItem, VideoOwner,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -92,18 +92,11 @@ pub enum VideoResultsItem {
|
||||||
#[serde_as(deserialize_as = "DefaultOnError")]
|
#[serde_as(deserialize_as = "DefaultOnError")]
|
||||||
metadata_row_container: Option<MetadataRowContainer>,
|
metadata_row_container: Option<MetadataRowContainer>,
|
||||||
},
|
},
|
||||||
/*
|
|
||||||
/// The comment section consists of 2 ItemSectionRenderers:
|
/// The comment section consists of 2 ItemSectionRenderers:
|
||||||
///
|
///
|
||||||
/// 1. sectionIdentifier: "comments-entry-point", contains number of comments
|
/// 1. sectionIdentifier: "comments-entry-point", contains number of comments
|
||||||
/// 2. sectionIdentifier: "comment-item-section", contains continuation token
|
/// 2. sectionIdentifier: "comment-item-section", contains continuation token
|
||||||
#[serde(rename_all = "camelCase")]
|
ItemSectionRenderer(ItemSection),
|
||||||
ItemSectionRenderer {
|
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
|
||||||
contents: Vec<ItemSection>,
|
|
||||||
section_identifier: String,
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
#[serde(other, deserialize_with = "ignore_any")]
|
#[serde(other, deserialize_with = "ignore_any")]
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
@ -211,6 +204,47 @@ pub struct CurrentVideoWatchEndpoint {
|
||||||
pub video_id: String,
|
pub video_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The comment section consists of 2 ItemSections:
|
||||||
|
///
|
||||||
|
/// 1. CommentsEntryPointHeaderRenderer: contains number of comments
|
||||||
|
/// 2. ContinuationItemRenderer: contains continuation token
|
||||||
|
#[serde_as]
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case", tag = "sectionIdentifier")]
|
||||||
|
pub enum ItemSection {
|
||||||
|
CommentsEntryPoint {
|
||||||
|
#[serde_as(as = "VecSkipError<_>")]
|
||||||
|
contents: Vec<ItemSectionCommentCount>,
|
||||||
|
},
|
||||||
|
CommentItemSection {
|
||||||
|
#[serde_as(as = "VecSkipError<_>")]
|
||||||
|
contents: Vec<ItemSectionComments>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Item section containing comment count
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ItemSectionCommentCount {
|
||||||
|
pub comments_entry_point_header_renderer: CommentsEntryPointHeaderRenderer,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renderer of item section containing comment count
|
||||||
|
#[serde_as]
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CommentsEntryPointHeaderRenderer {
|
||||||
|
#[serde_as(as = "Text")]
|
||||||
|
pub comment_count: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Item section containing comments ctoken
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ItemSectionComments {
|
||||||
|
pub continuation_item_renderer: ContinuationItemRenderer,
|
||||||
|
}
|
||||||
|
|
||||||
/// Video recommendations
|
/// Video recommendations
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|
@ -461,10 +495,7 @@ pub enum CommentListItem {
|
||||||
rendering_priority: CommentPriority,
|
rendering_priority: CommentPriority,
|
||||||
},
|
},
|
||||||
/// Reply comment
|
/// Reply comment
|
||||||
CommentRenderer {
|
CommentRenderer(CommentRenderer),
|
||||||
#[serde(flatten)]
|
|
||||||
comment: CommentRenderer,
|
|
||||||
},
|
|
||||||
/// Continuation token to fetch more comments
|
/// Continuation token to fetch more comments
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
ContinuationItemRenderer {
|
ContinuationItemRenderer {
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,9 @@ impl MapResponse<VideoDetails> for response::VideoDetails {
|
||||||
|
|
||||||
let mut primary_info = None;
|
let mut primary_info = None;
|
||||||
let mut secondary_info = None;
|
let mut secondary_info = None;
|
||||||
|
let mut comment_count_section = None;
|
||||||
|
let mut comment_ctoken_section = None;
|
||||||
|
|
||||||
primary_results.c.into_iter().for_each(|r| match r {
|
primary_results.c.into_iter().for_each(|r| match r {
|
||||||
response::video_details::VideoResultsItem::VideoPrimaryInfoRenderer { .. } => {
|
response::video_details::VideoResultsItem::VideoPrimaryInfoRenderer { .. } => {
|
||||||
primary_info = Some(r);
|
primary_info = Some(r);
|
||||||
|
|
@ -122,6 +125,16 @@ impl MapResponse<VideoDetails> for response::VideoDetails {
|
||||||
response::video_details::VideoResultsItem::VideoSecondaryInfoRenderer { .. } => {
|
response::video_details::VideoResultsItem::VideoSecondaryInfoRenderer { .. } => {
|
||||||
secondary_info = Some(r);
|
secondary_info = Some(r);
|
||||||
}
|
}
|
||||||
|
response::video_details::VideoResultsItem::ItemSectionRenderer(section) => {
|
||||||
|
match section {
|
||||||
|
response::video_details::ItemSection::CommentsEntryPoint { mut contents } => {
|
||||||
|
comment_count_section = contents.try_swap_remove(0);
|
||||||
|
}
|
||||||
|
response::video_details::ItemSection::CommentItemSection { mut contents } => {
|
||||||
|
comment_ctoken_section = contents.try_swap_remove(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
response::video_details::VideoResultsItem::None => {}
|
response::video_details::VideoResultsItem::None => {}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -153,9 +166,21 @@ impl MapResponse<VideoDetails> for response::VideoDetails {
|
||||||
_ => bail!("could not find primary_info"),
|
_ => bail!("could not find primary_info"),
|
||||||
};
|
};
|
||||||
|
|
||||||
if publish_date.is_none() {
|
/*
|
||||||
warnings.push(format!("could not parse date: {}", publish_date_txt));
|
TODO: use large number parser for this
|
||||||
}
|
let comment_count = comment_count_section.and_then(|s| {
|
||||||
|
util::parse_numeric_or_warn::<u32>(
|
||||||
|
&s.comments_entry_point_header_renderer.comment_count,
|
||||||
|
&mut warnings,
|
||||||
|
)
|
||||||
|
});*/
|
||||||
|
|
||||||
|
let comment_ctoken = comment_ctoken_section.map(|s| {
|
||||||
|
s.continuation_item_renderer
|
||||||
|
.continuation_endpoint
|
||||||
|
.continuation_command
|
||||||
|
.token
|
||||||
|
});
|
||||||
|
|
||||||
let (owner, description, is_ccommons) = match secondary_info {
|
let (owner, description, is_ccommons) = match secondary_info {
|
||||||
Some(response::video_details::VideoResultsItem::VideoSecondaryInfoRenderer {
|
Some(response::video_details::VideoResultsItem::VideoSecondaryInfoRenderer {
|
||||||
|
|
@ -220,23 +245,16 @@ impl MapResponse<VideoDetails> for response::VideoDetails {
|
||||||
response::video_details::EngagementPanelRenderer::None => {},
|
response::video_details::EngagementPanelRenderer::None => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
let (top_comments_ctoken, latest_comments_ctoken) = match comment_panel {
|
let latest_comments_ctoken = comment_panel.and_then(|comments| {
|
||||||
Some(comments) => {
|
let mut items = comments
|
||||||
let mut items = comments
|
.engagement_panel_title_header_renderer
|
||||||
.engagement_panel_title_header_renderer
|
.menu
|
||||||
.menu
|
.sort_filter_sub_menu_renderer
|
||||||
.sort_filter_sub_menu_renderer
|
.sub_menu_items;
|
||||||
.sub_menu_items;
|
items
|
||||||
let latest = items.try_swap_remove(1);
|
.try_swap_remove(1)
|
||||||
let top = items.try_swap_remove(0);
|
.map(|c| c.service_endpoint.continuation_command.token)
|
||||||
|
});
|
||||||
(
|
|
||||||
top.map(|c| c.service_endpoint.continuation_command.token),
|
|
||||||
latest.map(|c| c.service_endpoint.continuation_command.token),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
None => (None, None),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(MapResult {
|
Ok(MapResult {
|
||||||
c: VideoDetails {
|
c: VideoDetails {
|
||||||
|
|
@ -258,12 +276,14 @@ impl MapResponse<VideoDetails> for response::VideoDetails {
|
||||||
is_ccommons,
|
is_ccommons,
|
||||||
recommended: recommended.c,
|
recommended: recommended.c,
|
||||||
top_comments: Paginator {
|
top_comments: Paginator {
|
||||||
ctoken: top_comments_ctoken,
|
count: None,
|
||||||
..Default::default()
|
items: Vec::new(),
|
||||||
|
ctoken: comment_ctoken,
|
||||||
},
|
},
|
||||||
latest_comments: Paginator {
|
latest_comments: Paginator {
|
||||||
|
count: None,
|
||||||
|
items: Vec::new(),
|
||||||
ctoken: latest_comments_ctoken,
|
ctoken: latest_comments_ctoken,
|
||||||
..Default::default()
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
warnings,
|
warnings,
|
||||||
|
|
@ -325,7 +345,7 @@ impl MapResponse<Paginator<Comment>> for response::VideoComments {
|
||||||
comments.push(res.c);
|
comments.push(res.c);
|
||||||
warnings.append(&mut res.warnings)
|
warnings.append(&mut res.warnings)
|
||||||
}
|
}
|
||||||
response::video_details::CommentListItem::CommentRenderer { comment } => {
|
response::video_details::CommentListItem::CommentRenderer(comment) => {
|
||||||
let mut res = map_comment(
|
let mut res = map_comment(
|
||||||
comment,
|
comment,
|
||||||
None,
|
None,
|
||||||
|
|
@ -439,7 +459,7 @@ fn map_comment(
|
||||||
.contents
|
.contents
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|item| match item {
|
.filter_map(|item| match item {
|
||||||
response::video_details::CommentListItem::CommentRenderer { comment } => {
|
response::video_details::CommentListItem::CommentRenderer(comment) => {
|
||||||
let mut res = map_comment(
|
let mut res = map_comment(
|
||||||
comment,
|
comment,
|
||||||
None,
|
None,
|
||||||
|
|
@ -542,7 +562,7 @@ mod tests {
|
||||||
#[test_log::test(tokio::test)]
|
#[test_log::test(tokio::test)]
|
||||||
async fn get_video_comments() {
|
async fn get_video_comments() {
|
||||||
let rp = RustyPipe::builder().strict().build();
|
let rp = RustyPipe::builder().strict().build();
|
||||||
let details = rp.query().video_details("3pv_rHKnwAs").await.unwrap();
|
let details = rp.query().video_details("ZeerrnuLi5E").await.unwrap();
|
||||||
let rec = rp
|
let rec = rp
|
||||||
.query()
|
.query()
|
||||||
.video_comments(&details.top_comments.ctoken.unwrap())
|
.video_comments(&details.top_comments.ctoken.unwrap())
|
||||||
|
|
|
||||||
|
|
@ -269,8 +269,9 @@ pub async fn download_video(
|
||||||
let download_dir = PathBuf::from(output_dir);
|
let download_dir = PathBuf::from(output_dir);
|
||||||
let title = player_data.details.title.to_owned();
|
let title = player_data.details.title.to_owned();
|
||||||
let output_fname_set = output_fname.is_some();
|
let output_fname_set = output_fname.is_some();
|
||||||
let output_fname = output_fname
|
let output_fname = output_fname.unwrap_or_else(|| {
|
||||||
.unwrap_or_else(|| filenamify::filenamify(format!("{} [{}]", title, player_data.details.id)));
|
filenamify::filenamify(format!("{} [{}]", title, player_data.details.id))
|
||||||
|
});
|
||||||
|
|
||||||
// Select streams to download
|
// Select streams to download
|
||||||
let (video, audio) = player_data.select_video_audio_stream(filter);
|
let (video, audio) = player_data.select_video_audio_stream(filter);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
||||||
/// The paginator is a wrapper around a list of items that are fetched
|
/// The paginator is a wrapper around a list of items that are fetched
|
||||||
/// in pages from the YouTube API (e.g. playlist items,
|
/// in pages from the YouTube API (e.g. playlist items,
|
||||||
/// video recommendations or comments).
|
/// video recommendations or comments).
|
||||||
|
|
|
||||||
|
|
@ -253,7 +253,10 @@ impl TryFrom<TextLink> for crate::model::ChannelId {
|
||||||
page_type,
|
page_type,
|
||||||
browse_id,
|
browse_id,
|
||||||
} => match page_type {
|
} => match page_type {
|
||||||
PageType::Channel => Ok(crate::model::ChannelId { id: browse_id, name: text }),
|
PageType::Channel => Ok(crate::model::ChannelId {
|
||||||
|
id: browse_id,
|
||||||
|
name: text,
|
||||||
|
}),
|
||||||
_ => Err(anyhow!("invalid channel link type")),
|
_ => Err(anyhow!("invalid channel link type")),
|
||||||
},
|
},
|
||||||
_ => Err(anyhow!("invalid channel link")),
|
_ => Err(anyhow!("invalid channel link")),
|
||||||
|
|
|
||||||
Reference in a new issue