diff options
Diffstat (limited to 'yt/src/comments')
-rw-r--r-- | yt/src/comments/comment.rs | 89 | ||||
-rw-r--r-- | yt/src/comments/description.rs | 8 | ||||
-rw-r--r-- | yt/src/comments/mod.rs | 26 |
3 files changed, 107 insertions, 16 deletions
diff --git a/yt/src/comments/comment.rs b/yt/src/comments/comment.rs index 6b8cf73..5bc939c 100644 --- a/yt/src/comments/comment.rs +++ b/yt/src/comments/comment.rs @@ -9,7 +9,94 @@ // You should have received a copy of the License along with this program. // If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -use yt_dlp::wrapper::info_json::Comment; +use serde::{Deserialize, Deserializer, Serialize}; +use url::Url; + +#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq, PartialOrd, Ord)] +#[serde(from = "String")] +#[serde(deny_unknown_fields)] +pub enum Parent { + Root, + Id(String), +} + +impl Parent { + #[must_use] + pub fn id(&self) -> Option<&str> { + if let Self::Id(id) = self { + Some(id) + } else { + None + } + } +} + +impl From<String> for Parent { + fn from(value: String) -> Self { + if value == "root" { + Self::Root + } else { + Self::Id(value) + } + } +} + +#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq, PartialOrd, Ord)] +#[serde(from = "String")] +#[serde(deny_unknown_fields)] +pub struct Id { + pub id: String, +} +impl From<String> for Id { + fn from(value: String) -> Self { + Self { + // Take the last element if the string is split with dots, otherwise take the full id + id: value.split('.').last().unwrap_or(&value).to_owned(), + } + } +} + +#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq, PartialOrd, Ord)] +#[allow(clippy::struct_excessive_bools)] +pub struct Comment { + pub id: Id, + pub text: String, + #[serde(default = "zero")] + pub like_count: u32, + pub is_pinned: bool, + pub author_id: String, + #[serde(default = "unknown")] + pub author: String, + pub author_is_verified: bool, + pub author_thumbnail: Url, + pub parent: Parent, + #[serde(deserialize_with = "edited_from_time_text", alias = "_time_text")] + pub edited: bool, + // Can't also be deserialized, as it's already used in 'edited' + // _time_text: String, + pub timestamp: i64, + pub author_url: Option<Url>, + pub author_is_uploader: bool, + pub is_favorited: bool, +} + +fn unknown() -> String { + "<Unknown>".to_string() +} +fn zero() -> u32 { + 0 +} +fn edited_from_time_text<'de, D>(d: D) -> Result<bool, D::Error> +where + D: Deserializer<'de>, +{ + let s = String::deserialize(d)?; + if s.contains(" (edited)") { + Ok(true) + } else { + Ok(false) + } +} #[derive(Debug, Clone)] #[allow(clippy::module_name_repetitions)] diff --git a/yt/src/comments/description.rs b/yt/src/comments/description.rs index d22a40f..e8cb29d 100644 --- a/yt/src/comments/description.rs +++ b/yt/src/comments/description.rs @@ -17,7 +17,7 @@ use crate::{ }; use anyhow::{Result, bail}; -use yt_dlp::wrapper::info_json::InfoJson; +use yt_dlp::{InfoJson, json_cast}; pub async fn description(app: &App) -> Result<()> { let description = get(app).await?; @@ -39,6 +39,8 @@ pub async fn get(app: &App) -> Result<String> { ); Ok(info_json - .description - .unwrap_or("<No description>".to_owned())) + .get("description") + .map(|val| json_cast!(val, as_str)) + .unwrap_or("<No description>") + .to_owned()) } diff --git a/yt/src/comments/mod.rs b/yt/src/comments/mod.rs index daecf8d..876146d 100644 --- a/yt/src/comments/mod.rs +++ b/yt/src/comments/mod.rs @@ -11,11 +11,11 @@ use std::mem; -use anyhow::{Context, Result, bail}; -use comment::{CommentExt, Comments}; +use anyhow::{Result, bail}; +use comment::{Comment, CommentExt, Comments, Parent}; use output::display_fmt_and_less; use regex::Regex; -use yt_dlp::wrapper::info_json::{Comment, InfoJson, Parent}; +use yt_dlp::{InfoJson, json_cast}; use crate::{ app::App, @@ -39,23 +39,25 @@ pub async fn get(app: &App) -> Result<Comments> { bail!("Could not find a currently playing video!"); }; - let mut info_json: InfoJson = get::video_info_json(¤tly_playing_video)?.unreachable( - "A currently *playing* must be cached. And thus the info.json should be available", + let info_json: InfoJson = get::video_info_json(¤tly_playing_video)?.unreachable( + "A currently *playing* video must be cached. And thus the info.json should be available", ); - let base_comments = mem::take(&mut info_json.comments).with_context(|| { - format!( + let base_comments = if let Some(comments) = info_json.get("comments") { + json_cast!(comments, as_array) + } else { + bail!( "The video ('{}') does not have comments!", info_json - .title - .as_ref() - .unwrap_or(&("<No Title>".to_owned())) + .get("title") + .map(|val| json_cast!(val, as_str)) + .unwrap_or("<No Title>") ) - })?; - drop(info_json); + }; let mut comments = Comments::new(); for c in base_comments { + let c: Comment = serde_json::from_value(c.to_owned())?; if let Parent::Id(id) = &c.parent { comments.insert(&(id.clone()), CommentExt::from(c)); } else { |