From 0008e305c222f6f2a7dffd7aed403f6c46b69228 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sun, 7 May 2023 18:00:49 +0200 Subject: [PATCH 1/3] refactor: add iterators for parsing tokens --- README.md | 40 +++++++------- src/util/mod.rs | 112 +++++++++++++++++++++++++++++---------- src/util/timeago.rs | 125 +++++++++++++++++--------------------------- 3 files changed, 153 insertions(+), 124 deletions(-) diff --git a/README.md b/README.md index 62df601..a768901 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # RustyPipe +[![CI status](https://ci.thetadev.de/api/badges/ThetaDev/rustypipe/status.svg)](https://ci.thetadev.de/ThetaDev/rustypipe) + Client for the public YouTube / YouTube Music API (Innertube), inspired by [NewPipe](https://github.com/TeamNewPipe/NewPipeExtractor). @@ -7,25 +9,25 @@ inspired by [NewPipe](https://github.com/TeamNewPipe/NewPipeExtractor). ### YouTube -- [X] **Player** (video/audio streams, subtitles) -- [X] **Playlist** -- [X] **VideoDetails** (metadata, comments, recommended videos) -- [X] **Channel** (videos, shorts, livestreams, playlists, info, search) -- [X] **ChannelRSS** -- [X] **Search** (with filters) -- [X] **Search suggestions** -- [X] **Trending** -- [X] **URL resolver** +- **Player** (video/audio streams, subtitles) +- **Playlist** +- **VideoDetails** (metadata, comments, recommended videos) +- **Channel** (videos, shorts, livestreams, playlists, info, search) +- **ChannelRSS** +- **Search** (with filters) +- **Search suggestions** +- **Trending** +- **URL resolver** ### YouTube Music -- [X] **Playlist** -- [X] **Album** -- [X] **Artist** -- [X] **Search** -- [X] **Search suggestions** -- [X] **Radio** -- [X] **Track details** (lyrics, recommendations) -- [X] **Moods/Genres** -- [X] **Charts** -- [X] **New** +- **Playlist** +- **Album** +- **Artist** +- **Search** +- **Search suggestions** +- **Radio** +- **Track details** (lyrics, recommendations) +- **Moods/Genres** +- **Charts** +- **New** (albums, music videos) diff --git a/src/util/mod.rs b/src/util/mod.rs index b005caf..a91375f 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -10,7 +10,7 @@ pub use protobuf::{string_from_pb, ProtoBuilder}; use std::{ borrow::{Borrow, Cow}, collections::BTreeMap, - str::FromStr, + str::{FromStr, SplitWhitespace}, }; use base64::Engine; @@ -331,36 +331,18 @@ where } if digits.is_empty() { - if by_char { - filtered - .chars() - .find_map(|c| dict_entry.number_nd_tokens.get(&c.to_string())) - .and_then(|n| (*n as u64).try_into().ok()) - } else { - filtered - .split_whitespace() - .find_map(|token| dict_entry.number_nd_tokens.get(token)) - .and_then(|n| (*n as u64).try_into().ok()) - } + SplitTokens::new(&filtered, by_char) + .find_map(|token| dict_entry.number_nd_tokens.get(token)) + .and_then(|n| (*n as u64).try_into().ok()) } else { let num = digits.parse::().ok()?; - let lookup_token = |token: &str| match token { - "k" => Some(3), - _ => dict_entry.number_tokens.get(token).map(|t| *t as i32), - }; - - if by_char { - exp += filtered - .chars() - .filter_map(|token| lookup_token(&token.to_string())) - .sum::(); - } else { - exp += filtered - .split_whitespace() - .filter_map(lookup_token) - .sum::(); - } + exp += SplitTokens::new(&filtered, by_char) + .filter_map(|token| match token { + "k" => Some(3), + _ => dict_entry.number_tokens.get(token).map(|t| *t as i32), + }) + .sum::(); F::try_from(num.checked_mul((10_u64).checked_pow(exp.try_into().ok()?)?)?).ok() } @@ -415,6 +397,62 @@ pub fn b64_decode>(input: T) -> Result, base64::DecodeErr base64::engine::general_purpose::STANDARD.decode(input) } +/// An iterator over the chars in a string (in str format) +pub struct SplitChar<'a> { + txt: &'a str, + index: usize, +} + +impl<'a> From<&'a str> for SplitChar<'a> { + fn from(value: &'a str) -> Self { + Self { + txt: value, + index: 0, + } + } +} + +impl<'a> Iterator for SplitChar<'a> { + type Item = &'a str; + + fn next(&mut self) -> Option { + self.txt + .get(self.index..) + .and_then(|txt| txt.chars().next()) + .map(|c| { + let start = self.index; + self.index += c.len_utf8(); + &self.txt[start..self.index] + }) + } +} + +/// An iterator for parsing strings. It can either iterate over words or characters. +pub enum SplitTokens<'a> { + Word(SplitWhitespace<'a>), + Char(SplitChar<'a>), +} + +impl<'a> SplitTokens<'a> { + pub fn new(s: &'a str, by_char: bool) -> Self { + match by_char { + true => Self::Char(SplitChar::from(s)), + false => Self::Word(s.split_whitespace()), + } + } +} + +impl<'a> Iterator for SplitTokens<'a> { + type Item = &'a str; + + fn next(&mut self) -> Option { + match self { + SplitTokens::Word(iter) => iter.next(), + SplitTokens::Char(iter) => iter.next(), + } + } +} + #[cfg(test)] pub(crate) mod tests { use std::{fs::File, io::BufReader, path::PathBuf}; @@ -550,4 +588,22 @@ pub(crate) mod tests { let res = parse_large_numstr::(string, lang).expect(&emsg); assert_eq!(res, rounded, "{emsg}"); } + + #[test] + fn split_char() { + let teststr = "abc今天更新def"; + let res = SplitTokens::new(teststr, true).collect::>(); + assert_eq!(res.len(), 10); + let res_str = res.into_iter().collect::(); + assert_eq!(res_str, teststr) + } + + #[test] + fn split_words() { + let teststr = "abc 今天更新 ghi"; + let res = SplitTokens::new(teststr, false).collect::>(); + assert_eq!(res.len(), 3); + let res_str = res.join(" "); + assert_eq!(res_str, teststr) + } } diff --git a/src/util/timeago.rs b/src/util/timeago.rs index a384d9a..f5c368e 100644 --- a/src/util/timeago.rs +++ b/src/util/timeago.rs @@ -17,7 +17,7 @@ use time::{Date, Duration, Month, OffsetDateTime}; use crate::{ param::Language, - util::{self, dictionary}, + util::{self, dictionary, SplitTokens}, }; /// Parsed TimeAgo string, contains amount and time unit. @@ -149,79 +149,39 @@ fn filter_str(string: &str) -> String { .collect() } -fn parse_ta_token( - entry: &dictionary::Entry, - by_char: bool, - nd: bool, - filtered_str: &str, -) -> Option { - let tokens = match nd { - true => &entry.timeago_nd_tokens, - false => &entry.timeago_tokens, - }; - let mut qu = 1; +struct TaTokenParser<'a> { + iter: SplitTokens<'a>, + tokens: &'a phf::Map<&'static str, TaToken>, +} - if by_char { - filtered_str.chars().find_map(|word| { - tokens.get(&word.to_string()).and_then(|t| match t.unit { - Some(unit) => Some(TimeAgo { n: t.n * qu, unit }), - None => { - qu = t.n; - None - } - }) - }) - } else { - filtered_str.split_whitespace().find_map(|word| { - tokens.get(word).and_then(|t| match t.unit { - Some(unit) => Some(TimeAgo { n: t.n * qu, unit }), - None => { - qu = t.n; - None - } - }) - }) +impl<'a> TaTokenParser<'a> { + fn new(entry: &'a dictionary::Entry, by_char: bool, nd: bool, filtered_str: &'a str) -> Self { + let tokens = match nd { + true => &entry.timeago_nd_tokens, + false => &entry.timeago_tokens, + }; + Self { + iter: SplitTokens::new(filtered_str, by_char), + tokens, + } } } -fn parse_ta_tokens( - entry: &dictionary::Entry, - by_char: bool, - nd: bool, - filtered_str: &str, -) -> Vec { - let tokens = match nd { - true => &entry.timeago_nd_tokens, - false => &entry.timeago_tokens, - }; - let mut qu = 1; +impl<'a> Iterator for TaTokenParser<'a> { + type Item = TimeAgo; - if by_char { - filtered_str - .chars() - .filter_map(|word| { - tokens.get(&word.to_string()).and_then(|t| match t.unit { - Some(unit) => Some(TimeAgo { n: t.n * qu, unit }), - None => { - qu = t.n; - None - } - }) + fn next(&mut self) -> Option { + // Quantity for parsing separate quantity + unit tokens + let mut qu = 1; + self.iter.find_map(|word| { + self.tokens.get(word).and_then(|t| match t.unit { + Some(unit) => Some(TimeAgo { n: t.n * qu, unit }), + None => { + qu = t.n; + None + } }) - .collect() - } else { - filtered_str - .split_whitespace() - .filter_map(|word| { - tokens.get(word).and_then(|t| match t.unit { - Some(unit) => Some(TimeAgo { n: t.n * qu, unit }), - None => { - qu = t.n; - None - } - }) - }) - .collect() + }) } } @@ -240,7 +200,9 @@ pub fn parse_timeago(lang: Language, textual_date: &str) -> Option { let qu: u8 = util::parse_numeric(textual_date).unwrap_or(1); - parse_ta_token(&entry, util::lang_by_char(lang), false, &filtered_str).map(|ta| ta * qu) + TaTokenParser::new(&entry, util::lang_by_char(lang), false, &filtered_str) + .next() + .map(|ta| ta * qu) } /// Parse a TimeAgo string (e.g. "29 minutes ago") into a Chrono DateTime object. @@ -273,11 +235,14 @@ pub fn parse_textual_date(lang: Language, textual_date: &str) -> Option(textual_date); match nums.len() { - 0 => match parse_ta_token(&entry, by_char, true, &filtered_str) { + 0 => match TaTokenParser::new(&entry, by_char, true, &filtered_str).next() { Some(timeago) => Some(ParsedDate::Relative(timeago)), - None => parse_ta_token(&entry, by_char, false, &filtered_str).map(ParsedDate::Relative), + None => TaTokenParser::new(&entry, by_char, false, &filtered_str) + .next() + .map(ParsedDate::Relative), }, - 1 => parse_ta_token(&entry, by_char, false, &filtered_str) + 1 => TaTokenParser::new(&entry, by_char, false, &filtered_str) + .next() .map(|timeago| ParsedDate::Relative(timeago * nums[0] as u8)), 2..=3 => { if nums.len() == entry.date_order.len() { @@ -348,12 +313,10 @@ pub fn parse_video_duration(lang: Language, video_duration: &str) -> Option } else { part.digits.parse::().ok()? }; - let tokens = parse_ta_tokens(&entry, by_char, false, &part.word); - if tokens.is_empty() { - return None; - } + let mut tokens = TaTokenParser::new(&entry, by_char, false, &part.word).peekable(); + tokens.peek()?; - tokens.iter().for_each(|ta| { + tokens.for_each(|ta| { secs += n * ta.secs() as u32; n = 1; }); @@ -805,4 +768,12 @@ mod tests { let now = OffsetDateTime::now_utc(); assert_eq!(date.year(), now.year() - 1); } + + #[test] + fn tx() { + let s = "Abcdef"; + let lc: (usize, char) = s.char_indices().last().unwrap(); + let t = &s[(lc.0 + lc.1.len_utf8())..]; + dbg!(&t); + } } From 29ad2f99d4274e12c3a7e3b73ed853eb10ed8276 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sun, 7 May 2023 18:15:13 +0200 Subject: [PATCH 2/3] refactor: replace try_swap_remove --- src/client/music_artist.rs | 11 ++++++---- src/client/music_playlist.rs | 34 ++++++++++++++++++++----------- src/client/music_search.rs | 27 +++++++++++++----------- src/client/pagination.rs | 9 ++++---- src/client/playlist.rs | 20 ++++++++++-------- src/client/response/music_item.rs | 18 ++++++++-------- src/client/response/video_item.rs | 2 +- src/client/trends.rs | 19 ++++++++++------- src/client/video_details.rs | 8 ++++---- 9 files changed, 87 insertions(+), 61 deletions(-) diff --git a/src/client/music_artist.rs b/src/client/music_artist.rs index e9d2eae..2c8ae8a 100644 --- a/src/client/music_artist.rs +++ b/src/client/music_artist.rs @@ -8,7 +8,7 @@ use crate::{ error::{Error, ExtractionError}, model::{AlbumItem, ArtistId, MusicArtist}, serializer::MapResult, - util::{self, TryRemove}, + util, }; use super::{ @@ -331,9 +331,12 @@ impl MapResponse> for response::MusicArtistAlbums { ) -> Result>, ExtractionError> { // dbg!(&self); - let mut content = self.contents.single_column_browse_results_renderer.contents; - let grids = content - .try_swap_remove(0) + let grids = self + .contents + .single_column_browse_results_renderer + .contents + .into_iter() + .next() .ok_or(ExtractionError::InvalidData(Cow::Borrowed("no content")))? .tab_renderer .content diff --git a/src/client/music_playlist.rs b/src/client/music_playlist.rs index 5369e42..953a569 100644 --- a/src/client/music_playlist.rs +++ b/src/client/music_playlist.rs @@ -125,14 +125,17 @@ impl MapResponse for response::MusicPlaylist { ) -> Result, ExtractionError> { // dbg!(&self); - let mut content = self.contents.single_column_browse_results_renderer.contents; - let mut music_contents = content - .try_swap_remove(0) + let music_contents = self + .contents + .single_column_browse_results_renderer + .contents + .into_iter() + .next() .ok_or(ExtractionError::InvalidData(Cow::Borrowed("no content")))? .tab_renderer .content .section_list_renderer; - let mut shelf = music_contents + let shelf = music_contents .contents .into_iter() .find_map(|section| match section { @@ -157,7 +160,8 @@ impl MapResponse for response::MusicPlaylist { let ctoken = shelf .continuations - .try_swap_remove(0) + .into_iter() + .next() .map(|cont| cont.next_continuation_data.continuation); let track_count = if ctoken.is_some() { @@ -177,7 +181,8 @@ impl MapResponse for response::MusicPlaylist { let related_ctoken = music_contents .continuations - .try_swap_remove(0) + .into_iter() + .next() .map(|c| c.next_continuation_data.continuation); let (from_ytm, channel, name, thumbnail, description) = match self.header { @@ -269,9 +274,12 @@ impl MapResponse for response::MusicPlaylist { .ok_or(ExtractionError::InvalidData(Cow::Borrowed("no header")))? .music_detail_header_renderer; - let mut content = self.contents.single_column_browse_results_renderer.contents; - let sections = content - .try_swap_remove(0) + let sections = self + .contents + .single_column_browse_results_renderer + .contents + .into_iter() + .next() .ok_or(ExtractionError::InvalidData(Cow::Borrowed("no content")))? .tab_renderer .content @@ -320,7 +328,8 @@ impl MapResponse for response::MusicPlaylist { let (artists, by_va) = map_artists(artists_p); let album_type_txt = subtitle_split - .try_swap_remove(0) + .into_iter() + .next() .map(|part| part.to_string()) .unwrap_or_default(); @@ -329,12 +338,13 @@ impl MapResponse for response::MusicPlaylist { let (artist_id, playlist_id) = header .menu - .map(|mut menu| { + .map(|menu| { ( map_artist_id(menu.menu_renderer.items), menu.menu_renderer .top_level_buttons - .try_swap_remove(0) + .into_iter() + .next() .map(|btn| { btn.button_renderer .navigation_endpoint diff --git a/src/client/music_search.rs b/src/client/music_search.rs index aeb98ba..be55c12 100644 --- a/src/client/music_search.rs +++ b/src/client/music_search.rs @@ -10,7 +10,6 @@ use crate::{ MusicSearchFiltered, MusicSearchResult, MusicSearchSuggestion, TrackItem, }, serializer::MapResult, - util::TryRemove, }; use super::{response, ClientType, MapResponse, RustyPipeQuery, YTContext}; @@ -234,9 +233,12 @@ impl MapResponse for response::MusicSearch { ) -> Result, crate::error::ExtractionError> { // dbg!(&self); - let mut tabs = self.contents.tabbed_search_results_renderer.contents; - let sections = tabs - .try_swap_remove(0) + let sections = self + .contents + .tabbed_search_results_renderer + .contents + .into_iter() + .next() .ok_or(ExtractionError::InvalidData(Cow::Borrowed("no tab")))? .tab_renderer .content @@ -262,8 +264,8 @@ impl MapResponse for response::MusicSearch { } } } - response::music_search::ItemSection::ItemSectionRenderer { mut contents } => { - if let Some(corrected) = contents.try_swap_remove(0) { + response::music_search::ItemSection::ItemSectionRenderer { contents } => { + if let Some(corrected) = contents.into_iter().next() { corrected_query = Some(corrected.showing_results_for_renderer.corrected_query) } } @@ -295,9 +297,10 @@ impl MapResponse> for response::MusicSearc ) -> Result>, ExtractionError> { // dbg!(&self); - let mut tabs = self.contents.tabbed_search_results_renderer.contents; + let tabs = self.contents.tabbed_search_results_renderer.contents; let sections = tabs - .try_swap_remove(0) + .into_iter() + .next() .ok_or(ExtractionError::InvalidData(Cow::Borrowed("no tab")))? .tab_renderer .content @@ -309,17 +312,17 @@ impl MapResponse> for response::MusicSearc let mut mapper = MusicListMapper::new(lang); sections.into_iter().for_each(|section| match section { - response::music_search::ItemSection::MusicShelfRenderer(mut shelf) => { + response::music_search::ItemSection::MusicShelfRenderer(shelf) => { mapper.map_response(shelf.contents); - if let Some(cont) = shelf.continuations.try_swap_remove(0) { + if let Some(cont) = shelf.continuations.into_iter().next() { ctoken = Some(cont.next_continuation_data.continuation); } } response::music_search::ItemSection::MusicCardShelfRenderer(card) => { mapper.map_card(card); } - response::music_search::ItemSection::ItemSectionRenderer { mut contents } => { - if let Some(corrected) = contents.try_swap_remove(0) { + response::music_search::ItemSection::ItemSectionRenderer { contents } => { + if let Some(corrected) = contents.into_iter().next() { corrected_query = Some(corrected.showing_results_for_renderer.corrected_query) } } diff --git a/src/client/pagination.rs b/src/client/pagination.rs index 42ee8d9..bfb07cd 100644 --- a/src/client/pagination.rs +++ b/src/client/pagination.rs @@ -5,7 +5,6 @@ use crate::model::{ Comment, MusicItem, PlaylistVideo, YouTubeItem, }; use crate::serializer::MapResult; -use crate::util::TryRemove; use super::response::music_item::{map_queue_item, MusicListMapper, PlaylistPanelVideo}; use super::{response, ClientType, MapResponse, QContinuation, RustyPipeQuery}; @@ -100,9 +99,10 @@ impl MapResponse> for response::Continuation { ) -> Result>, ExtractionError> { let items = self .on_response_received_actions - .and_then(|mut actions| { + .and_then(|actions| { actions - .try_swap_remove(0) + .into_iter() + .next() .map(|action| action.append_continuation_items_action.continuation_items) }) .or_else(|| { @@ -168,7 +168,8 @@ impl MapResponse> for response::MusicContinuation { let map_res = mapper.items(); let ctoken = continuations - .try_swap_remove(0) + .into_iter() + .next() .map(|cont| cont.next_continuation_data.continuation); Ok(MapResult { diff --git a/src/client/playlist.rs b/src/client/playlist.rs index 2130f6a..5fed815 100644 --- a/src/client/playlist.rs +++ b/src/client/playlist.rs @@ -65,10 +65,11 @@ impl MapResponse for response::Playlist { _ => return Err(response::alerts_to_err(self.alerts)), }; - let mut tcbr_contents = contents.two_column_browse_results_renderer.contents; - - let video_items = tcbr_contents - .try_swap_remove(0) + let video_items = contents + .two_column_browse_results_renderer + .contents + .into_iter() + .next() .ok_or(ExtractionError::InvalidData(Cow::Borrowed( "twoColumnBrowseResultsRenderer empty", )))? @@ -76,13 +77,15 @@ impl MapResponse for response::Playlist { .content .section_list_renderer .contents - .try_swap_remove(0) + .into_iter() + .next() .ok_or(ExtractionError::InvalidData(Cow::Borrowed( "sectionListRenderer empty", )))? .item_section_renderer .contents - .try_swap_remove(0) + .into_iter() + .next() .ok_or(ExtractionError::InvalidData(Cow::Borrowed( "itemSectionRenderer empty", )))? @@ -93,10 +96,11 @@ impl MapResponse for response::Playlist { let (thumbnails, last_update_txt) = match self.sidebar { Some(sidebar) => { - let mut sidebar_items = sidebar.playlist_sidebar_renderer.contents; + let sidebar_items = sidebar.playlist_sidebar_renderer.contents; let mut primary = sidebar_items - .try_swap_remove(0) + .into_iter() + .next() .ok_or(ExtractionError::InvalidData(Cow::Borrowed( "no primary sidebar", )))?; diff --git a/src/client/response/music_item.rs b/src/client/response/music_item.rs index b6bb9b6..7d9f6ea 100644 --- a/src/client/response/music_item.rs +++ b/src/client/response/music_item.rs @@ -11,7 +11,7 @@ use crate::{ text::{Text, TextComponents}, MapResult, }, - util::{self, dictionary, TryRemove}, + util::{self, dictionary}, }; use super::{ @@ -587,14 +587,14 @@ impl MusicListMapper { } } // Playlist item - FlexColumnDisplayStyle::Default => { - let mut fixed_columns = item.fixed_columns; - ( - c2.map(TextComponents::from), - c3.map(TextComponents::from), - fixed_columns.try_swap_remove(0).map(TextComponents::from), - ) - } + FlexColumnDisplayStyle::Default => ( + c2.map(TextComponents::from), + c3.map(TextComponents::from), + item.fixed_columns + .into_iter() + .next() + .map(TextComponents::from), + ), }; let duration = diff --git a/src/client/response/video_item.rs b/src/client/response/video_item.rs index 29df448..52f6491 100644 --- a/src/client/response/video_item.rs +++ b/src/client/response/video_item.rs @@ -477,7 +477,7 @@ impl YouTubeListMapper { is_upcoming: video.upcoming_event_data.is_some(), short_description: video .detailed_metadata_snippets - .and_then(|mut snippets| snippets.try_swap_remove(0).map(|s| s.snippet_text)) + .and_then(|snippets| snippets.into_iter().next().map(|s| s.snippet_text)) .or(video.description_snippet), } } diff --git a/src/client/trends.rs b/src/client/trends.rs index cc62032..cc7408f 100644 --- a/src/client/trends.rs +++ b/src/client/trends.rs @@ -5,7 +5,6 @@ use crate::{ model::{paginator::Paginator, VideoItem}, param::Language, serializer::MapResult, - util::TryRemove, }; use super::{response, ClientType, MapResponse, QBrowse, QBrowseParams, RustyPipeQuery}; @@ -56,9 +55,12 @@ impl MapResponse> for response::Startpage { lang: crate::param::Language, _deobf: Option<&crate::deobfuscate::DeobfData>, ) -> Result>, ExtractionError> { - let mut contents = self.contents.two_column_browse_results_renderer.contents; - let grid = contents - .try_swap_remove(0) + let grid = self + .contents + .two_column_browse_results_renderer + .contents + .into_iter() + .next() .ok_or(ExtractionError::InvalidData(Cow::Borrowed("no contents")))? .tab_renderer .content @@ -80,9 +82,12 @@ impl MapResponse> for response::Trending { lang: crate::param::Language, _deobf: Option<&crate::deobfuscate::DeobfData>, ) -> Result>, ExtractionError> { - let mut contents = self.contents.two_column_browse_results_renderer.contents; - let items = contents - .try_swap_remove(0) + let items = self + .contents + .two_column_browse_results_renderer + .contents + .into_iter() + .next() .ok_or(ExtractionError::InvalidData(Cow::Borrowed("no contents")))? .tab_renderer .content diff --git a/src/client/video_details.rs b/src/client/video_details.rs index 0c20594..68754f1 100644 --- a/src/client/video_details.rs +++ b/src/client/video_details.rs @@ -129,11 +129,11 @@ impl MapResponse for response::VideoDetails { } 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::CommentsEntryPoint { contents } => { + comment_count_section = contents.into_iter().next(); } - response::video_details::ItemSection::CommentItemSection { mut contents } => { - comment_ctoken_section = contents.try_swap_remove(0); + response::video_details::ItemSection::CommentItemSection { contents } => { + comment_ctoken_section = contents.into_iter().next(); } response::video_details::ItemSection::None => {} } From c3f82f765bf1e957e8efad10acd0d050f4f0cf17 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sun, 7 May 2023 19:29:19 +0200 Subject: [PATCH 3/3] fix: add "1 video" tokens to dict --- src/client/music_search.rs | 2 +- src/util/dictionary.rs | 8 ++++++-- testfiles/dict/dictionary.json | 10 +++++++--- testfiles/dict/dictionary_override.json | 12 ++++++++++-- tests/youtube.rs | 3 ++- 5 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/client/music_search.rs b/src/client/music_search.rs index be55c12..bddaf11 100644 --- a/src/client/music_search.rs +++ b/src/client/music_search.rs @@ -407,7 +407,7 @@ mod tests { #[case::default("default")] #[case::typo("typo")] #[case::radio("radio")] - #[case::radio("artist")] + #[case::artist("artist")] fn map_music_search_main(#[case] name: &str) { let json_path = path!(*TESTFILES / "music_search" / format!("main_{name}.json")); let json_file = File::open(json_path).unwrap(); diff --git a/src/util/dictionary.rs b/src/util/dictionary.rs index 7d2d2b3..e0049a8 100644 --- a/src/util/dictionary.rs +++ b/src/util/dictionary.rs @@ -393,13 +393,14 @@ pub(crate) fn entry(lang: Language) -> Entry { ], }, number_nd_tokens: ::phf::Map { - key: 15467950696543387533, + key: 12913932095322966823, disps: &[ (0, 0), ], entries: &[ - ("ন\u{9be}ই", 0), ("১", 1), + ("ন\u{9be}ই", 0), + ("১ট\u{9be}", 1), ], }, album_types: ::phf::Map { @@ -4659,6 +4660,7 @@ pub(crate) fn entry(lang: Language) -> Entry { ], entries: &[ ("ingen", 0), + ("én", 1), ], }, album_types: ::phf::Map { @@ -5032,8 +5034,10 @@ pub(crate) fn entry(lang: Language) -> Entry { number_nd_tokens: ::phf::Map { key: 12913932095322966823, disps: &[ + (0, 0), ], entries: &[ + ("um", 1), ], }, album_types: ::phf::Map { diff --git a/testfiles/dict/dictionary.json b/testfiles/dict/dictionary.json index 8487db5..1f62381 100644 --- a/testfiles/dict/dictionary.json +++ b/testfiles/dict/dictionary.json @@ -201,7 +201,8 @@ }, "number_nd_tokens": { "নাই": 0, - "১": 1 + "১": 1, + "১টা": 1 }, "album_types": { "ep": "Ep", @@ -2662,7 +2663,8 @@ "mrd": 9 }, "number_nd_tokens": { - "ingen": 0 + "ingen": 0, + "én": 1 }, "album_types": { "album": "Album", @@ -2885,7 +2887,9 @@ "mi": 6, "mil": 3 }, - "number_nd_tokens": {}, + "number_nd_tokens": { + "um": 1 + }, "album_types": { "audiolivro": "Audiobook", "ep": "Ep", diff --git a/testfiles/dict/dictionary_override.json b/testfiles/dict/dictionary_override.json index e162a96..bc02b05 100644 --- a/testfiles/dict/dictionary_override.json +++ b/testfiles/dict/dictionary_override.json @@ -16,7 +16,9 @@ "শঃ": null }, "number_nd_tokens": { - "কোনো": null + "কোনো": null, + "ভিডিঅ’": null, + "১টা": 1 } }, "bn": { @@ -111,7 +113,8 @@ }, "no": { "number_nd_tokens": { - "avspillinger": null + "avspillinger": null, + "én": 1 } }, "or": { @@ -129,6 +132,11 @@ "ਨੇ": null } }, + "pt": { + "number_nd_tokens": { + "um": 1 + } + }, "ro": { "number_nd_tokens": { "abonat": null, diff --git a/tests/youtube.rs b/tests/youtube.rs index 1d5abe5..706c257 100644 --- a/tests/youtube.rs +++ b/tests/youtube.rs @@ -1108,7 +1108,8 @@ fn search_empty(rp: RustyPipe) { fn search_suggestion(rp: RustyPipe) { let result = tokio_test::block_on(rp.query().search_suggestion("hunger ga")).unwrap(); - assert!(result.contains(&"hunger games".to_owned())); + assert!(result.iter().any(|s| s.starts_with("hunger games "))); + assert_gte(result.len(), 10, "search suggestions"); } #[rstest]