1use super::ParseFrom;
2use crate::Result;
3use crate::common::{PlaylistID, Thumbnail, UserPlaylistsParams, UserVideosParams, VideoID};
4use crate::nav_consts::{
5 CAROUSEL, CAROUSEL_TITLE, FOREGROUND_THUMBNAIL_RENDERER, GRID_ITEMS, MTRIR, NAVIGATION_BROWSE,
6 NAVIGATION_BROWSE_ID, NAVIGATION_VIDEO_ID, SECTION_LIST, SECTION_LIST_ITEM, SINGLE_COLUMN_TAB,
7 SUBTITLE2, SUBTITLE3, THUMBNAIL_RENDERER, TITLE_TEXT, VISUAL_HEADER,
8};
9use crate::query::{GetUserPlaylistsQuery, GetUserQuery, GetUserVideosQuery};
10use const_format::concatcp;
11use json_crawler::{JsonCrawler, JsonCrawlerOwned};
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14
15#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16#[non_exhaustive]
17pub struct GetUser {
18 pub name: String,
19 pub videos: Vec<UserVideo>,
20 pub thumbnails: Vec<Thumbnail>,
21 pub all_videos_params: Option<UserVideosParams<'static>>,
22 pub playlists: Vec<UserPlaylist>,
23 pub all_playlists_params: Option<UserPlaylistsParams<'static>>,
24}
25#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
26#[non_exhaustive]
27pub struct UserVideo {
28 pub title: String,
29 pub views: String,
30 pub thumbnails: Vec<Thumbnail>,
31 pub id: VideoID<'static>,
32}
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
34#[non_exhaustive]
35pub struct UserPlaylist {
36 pub title: String,
37 pub views: String,
38 pub thumbnails: Vec<Thumbnail>,
39 pub id: PlaylistID<'static>,
40}
41
42impl ParseFrom<GetUserQuery<'_>> for GetUser {
43 fn parse_from(p: super::ProcessedResult<GetUserQuery>) -> Result<Self> {
44 let mut json_crawler: JsonCrawlerOwned = p.into();
45 let mut header = json_crawler.borrow_pointer(VISUAL_HEADER)?;
46 let name = header.take_value_pointer(TITLE_TEXT)?;
47 let thumbnails = header.take_value_pointer(FOREGROUND_THUMBNAIL_RENDERER)?;
48 let contents = json_crawler.navigate_pointer(concatcp!(SINGLE_COLUMN_TAB, SECTION_LIST))?;
49 let mut carousels: HashMap<String, _> = contents
51 .try_into_iter()?
52 .map(|crawler| {
53 let mut carousel = crawler.navigate_pointer(CAROUSEL)?;
54 let title = carousel.take_value_pointer(concatcp!(CAROUSEL_TITLE, "/text"))?;
55 Ok((title, carousel))
56 })
57 .collect::<Result<_>>()?;
58 let mut maybe_playlists_carousel = carousels.get_mut("Playlists");
59 let all_playlists_params =
60 maybe_playlists_carousel
61 .as_mut()
62 .and_then(|playlists_carousel| {
63 playlists_carousel
64 .take_value_pointer(concatcp!(CAROUSEL_TITLE, NAVIGATION_BROWSE, "/params"))
65 .ok()
66 });
67 let playlists = match maybe_playlists_carousel {
68 Some(playlists_carousel) => playlists_carousel
69 .borrow_pointer("/contents")?
70 .try_into_iter()?
71 .map(parse_user_playlist)
72 .collect::<Result<_>>()?,
73 None => vec![],
74 };
75 let mut maybe_videos_carousel = carousels.get_mut("Videos");
76 let all_videos_params = maybe_videos_carousel.as_mut().and_then(|videos_carousel| {
77 videos_carousel
78 .take_value_pointer(concatcp!(CAROUSEL_TITLE, NAVIGATION_BROWSE, "/params"))
79 .ok()
80 });
81 let videos = match maybe_videos_carousel {
82 Some(videos_carousel) => videos_carousel
83 .borrow_pointer("/contents")?
84 .try_into_iter()?
85 .map(parse_user_video)
86 .collect::<Result<_>>()?,
87 None => vec![],
88 };
89
90 Ok(Self {
91 name,
92 thumbnails,
93 all_videos_params,
94 playlists,
95 videos,
96 all_playlists_params,
97 })
98 }
99}
100impl ParseFrom<GetUserPlaylistsQuery<'_>> for Vec<UserPlaylist> {
101 fn parse_from(p: super::ProcessedResult<GetUserPlaylistsQuery>) -> Result<Self> {
102 let json_crawler: JsonCrawlerOwned = p.into();
103 let results = json_crawler
104 .navigate_pointer(concatcp!(SINGLE_COLUMN_TAB, SECTION_LIST_ITEM, GRID_ITEMS))?
105 .try_into_iter()?
106 .map(parse_user_playlist)
107 .collect::<Result<_>>()?;
108 Ok(results)
109 }
110}
111impl ParseFrom<GetUserVideosQuery<'_>> for Vec<UserVideo> {
112 fn parse_from(p: super::ProcessedResult<GetUserVideosQuery>) -> Result<Self> {
113 let json_crawler: JsonCrawlerOwned = p.into();
114 let results = json_crawler
115 .navigate_pointer(concatcp!(SINGLE_COLUMN_TAB, SECTION_LIST_ITEM, GRID_ITEMS))?
116 .try_into_iter()?
117 .map(parse_user_video)
118 .collect::<Result<_>>()?;
119 Ok(results)
120 }
121}
122fn parse_user_video(c: impl JsonCrawler) -> Result<UserVideo> {
123 let mut item = c.navigate_pointer(MTRIR)?;
124 let title = item.take_value_pointer(TITLE_TEXT)?;
125 let views = item.take_value_pointer(SUBTITLE2)?;
126 let id = item.take_value_pointer(NAVIGATION_VIDEO_ID)?;
127 let thumbnails = item.take_value_pointer(THUMBNAIL_RENDERER)?;
128 Ok(UserVideo {
129 title,
130 views,
131 thumbnails,
132 id,
133 })
134}
135fn parse_user_playlist(c: impl JsonCrawler) -> Result<UserPlaylist> {
136 let mut item = c.navigate_pointer(MTRIR)?;
137 let title = item.take_value_pointer(TITLE_TEXT)?;
138 let views = item.take_value_pointer(SUBTITLE3)?;
139 let id = item.take_value_pointer(NAVIGATION_BROWSE_ID)?;
140 let thumbnails = item.take_value_pointer(THUMBNAIL_RENDERER)?;
141 Ok(UserPlaylist {
142 title,
143 views,
144 thumbnails,
145 id,
146 })
147}
148
149#[cfg(test)]
150mod tests {
151 use crate::auth::BrowserToken;
152 use crate::common::{UserChannelID, UserPlaylistsParams, UserVideosParams, YoutubeID};
153
154 #[tokio::test]
155 async fn test_get_user() {
156 parse_test!(
157 "./test_json/get_user_20250707.json",
158 "./test_json/get_user_20250707_output.txt",
159 crate::query::GetUserQuery::new(UserChannelID::from_raw("")),
160 BrowserToken
161 );
162 }
163 #[tokio::test]
164 async fn test_get_user_playlists() {
165 parse_test!(
166 "./test_json/get_user_playlists_20250707.json",
167 "./test_json/get_user_playlists_20250707_output.txt",
168 crate::query::GetUserPlaylistsQuery::new(
169 UserChannelID::from_raw(""),
170 UserPlaylistsParams::from_raw("")
171 ),
172 BrowserToken
173 );
174 }
175 #[tokio::test]
176 async fn test_get_user_videos() {
177 parse_test!(
178 "./test_json/get_user_videos_20250707.json",
179 "./test_json/get_user_videos_20250707_output.txt",
180 crate::query::GetUserVideosQuery::new(
181 UserChannelID::from_raw(""),
182 UserVideosParams::from_raw("")
183 ),
184 BrowserToken
185 );
186 }
187}