1use crate::utils::{parse_date, parse_multi_language};
2use crate::{Result, VGMClient, VGMError};
3use select::document::Document;
4use select::predicate::{Attr, Class, Name, Predicate};
5use std::collections::HashMap;
6use std::fmt::{Debug, Formatter};
7use std::str::FromStr;
8
9pub struct SearchResponse<'client> {
10 client: &'client VGMClient,
11 inner: SearchResult,
12}
13
14impl<'client> SearchResponse<'client> {
15 pub(crate) fn new(client: &'client VGMClient, inner: SearchResult) -> Self {
16 SearchResponse { client, inner }
17 }
18
19 pub fn is_empty(&self) -> bool {
20 self.len() == 0
21 }
22
23 pub fn len(&self) -> usize {
24 match &self.inner {
25 SearchResult::Album(_) => 1,
26 SearchResult::List(list) => list.len(),
27 }
28 }
29
30 pub fn albums(&self) -> Vec<&AlbumInfo> {
32 match &self.inner {
33 SearchResult::Album(album) => vec![&album.info],
34 SearchResult::List(list) => list.iter().collect(),
35 }
36 }
37
38 pub async fn into_album(self, index: Option<usize>) -> Result<AlbumDetail> {
39 match self.inner {
40 SearchResult::Album(data) => Ok(data),
41 SearchResult::List(list) => {
42 let index = index.unwrap_or(0);
43 if list.len() > index {
44 let id = &list[index].id;
45 let album_detail = self.client.album(id).await?;
46 Ok(album_detail)
47 } else {
48 Err(VGMError::NoAlbumFound)
49 }
50 }
51 }
52 }
53}
54
55impl<'client> Debug for SearchResponse<'client> {
56 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
57 self.inner.fmt(f)
58 }
59}
60
61#[derive(Debug)]
62pub enum SearchResult {
63 Album(AlbumDetail),
64 List(Vec<AlbumInfo>),
65}
66
67#[derive(Debug)]
68pub struct AlbumInfo {
69 pub id: String,
70
71 pub title: MultiLanguageString,
72 pub catalog: Option<String>,
73 pub release_date: String,
74}
75
76#[derive(Debug)]
77pub struct AlbumDetail {
78 pub link: String,
79 info: AlbumInfo,
80 pub discs: Vec<Disc>,
81}
82
83impl FromStr for AlbumDetail {
84 type Err = VGMError;
85
86 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
87 let document = Document::from(s);
88
89 let title = document.find(Name("h1")).next().unwrap();
91 let title = parse_multi_language(&title);
92
93 let info = document
94 .find(Attr("id", "album_infobit_large"))
95 .nth(0)
96 .unwrap();
97
98 let mut catalog = None;
100 let mut release_date = None;
102
103 for line in info.find(Name("tr")) {
104 if let Some(key) = line
105 .find(Name("span").and(Class("label")).descendant(Name("b")))
106 .next()
107 {
108 let key = key.text();
109 if key == "Catalog Number" {
110 let value = line.last_child().unwrap();
111 let value = if let Some(value) = value
112 .find(Attr("id", "childbrowse").descendant(Name("a")))
113 .next()
114 {
115 value.text()
116 } else {
117 value.text()
118 };
119 catalog = Some(value.trim().to_string());
120 if let Some("N/A") = catalog.as_deref() {
121 catalog = None;
122 }
123 } else if key == "Release Date" {
124 let value = line.last_child().unwrap().text();
125 release_date = parse_date(value.trim()).ok();
126 }
127 }
128 }
129
130 let mut album = AlbumDetail {
131 link: "".to_string(), info: AlbumInfo {
133 id: "".to_string(), title,
135 catalog,
136 release_date: release_date.unwrap(),
137 },
138 discs: vec![],
139 };
140
141 let track_list_nav = document.find(Attr("id", "tlnav")).next().unwrap();
143 let track_list = document.find(Attr("id", "tracklist")).next().unwrap();
144 for list in track_list.find(Attr("class", "tl")) {
145 let reference = list.attr("id").unwrap();
146 let language = track_list_nav
147 .find(Attr("rel", reference))
148 .next()
149 .unwrap()
150 .text();
151
152 let mut discs = Vec::new();
153 for disc in list.find(Attr("style", "font-size:8pt").descendant(Name("b"))) {
154 let disc_title = disc.text();
155 let mut table = disc.parent().unwrap();
156 loop {
157 table = table.next().unwrap();
158 if let Some("table") = table.name() {
159 break;
160 }
161 }
162 let mut tracks = Vec::new();
163 for track in table.find(Name("tr")) {
164 let track_name = track
165 .find(Name("td").and(Attr("width", "100%")))
166 .next()
167 .unwrap()
168 .text();
169 let track_name = track_name.trim().to_string();
170 tracks.push(track_name);
171 }
172 discs.push((disc_title, tracks));
173 }
174
175 if album.discs.is_empty() {
176 album.discs.append(
178 &mut discs
179 .into_iter()
180 .map(|(title, tracks)| {
181 let tracks = tracks
182 .into_iter()
183 .map(|track| {
184 let mut tracks = MultiLanguageString::default();
185 tracks.insert(language.to_string(), track);
186 tracks
187 })
188 .collect();
189 Disc { title, tracks }
190 })
191 .collect::<Vec<_>>(),
192 );
193 } else {
194 for (disc, (_, tracks)) in album.discs.iter_mut().zip(discs.into_iter()) {
195 for (track, new_track) in disc.tracks.iter_mut().zip(tracks.into_iter()) {
196 track.insert(language.to_string(), new_track);
197 }
198 }
199 }
200 }
201
202 Ok(album)
203 }
204}
205
206impl AlbumDetail {
207 pub fn title(&self) -> Option<&str> {
208 self.info.title.get()
209 }
210
211 pub fn catalog(&self) -> Option<&str> {
212 self.info.catalog.as_deref()
213 }
214
215 pub fn release_date(&self) -> &str {
216 &self.info.release_date
217 }
218}
219
220#[derive(Debug)]
221pub struct Disc {
222 pub title: String,
223 pub tracks: Vec<MultiLanguageString>,
224}
225
226#[derive(Debug, Default)]
227pub struct MultiLanguageString(HashMap<String, String>);
228
229impl MultiLanguageString {
230 pub fn insert(&mut self, language: String, value: String) {
231 self.0.insert(language, value);
232 }
233
234 pub fn get(&self) -> Option<&str> {
235 self.0
236 .get("ja")
237 .or_else(|| self.0.get("Japanese"))
238 .or_else(|| self.0.get("English"))
239 .or_else(|| self.0.values().next())
240 .map(|s| s.as_str())
241 }
242}