1use crate::auth::AuthToken;
24use crate::common::{AlbumID, ArtistChannelID, Thumbnail};
25use crate::json::Json;
26use crate::nav_consts::*;
27use crate::{RawResult, Result, error};
28use json_crawler::{JsonCrawler, JsonCrawlerOwned};
29use serde::de::DeserializeOwned;
30use serde::{Deserialize, Serialize};
31use std::fmt::Debug;
32
33mod album;
34pub use album::*;
35mod artist;
36pub use artist::*;
37mod history;
38pub use history::*;
39mod library;
40pub use library::*;
41mod playlist;
42pub use playlist::*;
43mod podcasts;
44pub use podcasts::*;
45mod rate;
46#[allow(unused_imports)]
49pub use rate::*;
50mod recommendations;
51pub use recommendations::*;
52mod search;
53pub use search::*;
54mod song;
55pub use song::*;
56mod upload;
57pub use upload::*;
58mod user;
59pub use user::*;
60
61pub trait ParseFrom<Q>: Debug + Sized {
66 fn parse_from(p: ProcessedResult<Q>) -> crate::Result<Self>;
67}
68
69#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
70pub enum EpisodeDate {
71 Live,
72 Recorded { date: String },
73}
74
75#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
76pub enum EpisodeDuration {
77 Live,
78 Recorded { duration: String },
79}
80
81#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
82pub struct ParsedSongArtist {
84 pub name: String,
85 pub id: Option<ArtistChannelID<'static>>,
86}
87#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
88pub struct ParsedSongAlbum {
90 pub name: String,
91 pub id: AlbumID<'static>,
92}
93
94pub struct ProcessedResult<'a, Q> {
97 pub query: &'a Q,
98 pub source: String,
100 pub json: Json,
103}
104
105impl<'a, Q, A: AuthToken> TryFrom<RawResult<'a, Q, A>> for ProcessedResult<'a, Q> {
106 type Error = crate::Error;
107 fn try_from(value: RawResult<'a, Q, A>) -> Result<Self> {
108 let RawResult {
109 json: source,
110 query,
111 ..
112 } = value;
113 let json = match source.as_str() {
114 "" => serde_json::Value::Null,
116 other => {
117 serde_json::from_str(other).map_err(|e| error::Error::response(format!("{e:?}")))?
118 }
119 };
120 let json = Json::new(json);
121 Ok(Self {
122 query,
123 source,
124 json,
125 })
126 }
127}
128
129impl<'a, Q> ProcessedResult<'a, Q> {
130 pub(crate) fn destructure(self) -> (&'a Q, String, serde_json::Value) {
131 let ProcessedResult {
132 query,
133 source,
134 json,
135 } = self;
136 (query, source, json.inner)
137 }
138 pub(crate) fn get_json(&self) -> &serde_json::Value {
139 &self.json.inner
140 }
141}
142
143impl<Q> ProcessedResult<'_, Q> {
144 pub fn parse_into<O: ParseFrom<Q>>(self) -> Result<O> {
145 O::parse_from(self)
146 }
147}
148
149impl<Q> From<ProcessedResult<'_, Q>> for JsonCrawlerOwned {
150 fn from(value: ProcessedResult<Q>) -> Self {
151 let (_, source, crawler) = value.destructure();
152 JsonCrawlerOwned::new(source, crawler)
153 }
154}
155
156fn fixed_column_item_pointer(col_idx: usize) -> String {
157 format!("/fixedColumns/{col_idx}/musicResponsiveListItemFixedColumnRenderer")
158}
159
160fn flex_column_item_pointer(col_idx: usize) -> String {
161 format!("/flexColumns/{col_idx}/musicResponsiveListItemFlexColumnRenderer")
162}
163
164fn parse_song_artists(
168 data: &mut impl JsonCrawler,
169 col_idx: usize,
170) -> Result<Vec<ParsedSongArtist>> {
171 data.borrow_pointer(format!("{}/text/runs", flex_column_item_pointer(col_idx)))?
172 .try_into_iter()?
173 .step_by(2)
174 .map(|mut item| parse_song_artist(&mut item))
175 .collect()
176}
177
178fn parse_song_artist(data: &mut impl JsonCrawler) -> Result<ParsedSongArtist> {
179 Ok(ParsedSongArtist {
180 name: data.take_value_pointer("/text")?,
181 id: data.take_value_pointer(NAVIGATION_BROWSE_ID).ok(),
182 })
183}
184
185fn parse_song_album(data: &mut impl JsonCrawler, col_idx: usize) -> Result<ParsedSongAlbum> {
186 Ok(ParsedSongAlbum {
187 name: parse_flex_column_item(data, col_idx, 0)?,
188 id: data.take_value_pointer(format!(
189 "{}/text/runs/0{}",
190 flex_column_item_pointer(col_idx),
191 NAVIGATION_BROWSE_ID
192 ))?,
193 })
194}
195
196fn parse_flex_column_item<T: DeserializeOwned>(
197 item: &mut impl JsonCrawler,
198 col_idx: usize,
199 run_idx: usize,
200) -> Result<T> {
201 let pointer = format!(
202 "{}/text/runs/{run_idx}/text",
203 flex_column_item_pointer(col_idx)
204 );
205 Ok(item.take_value_pointer(pointer)?)
206}
207
208fn parse_fixed_column_item<T: DeserializeOwned>(
209 item: &mut impl JsonCrawler,
210 col_idx: usize,
211) -> Result<T> {
212 let pointer = format!("{}/text/runs/0/text", fixed_column_item_pointer(col_idx));
213 Ok(item.take_value_pointer(pointer)?)
214}