1pub type SearchSongResp = ResultResp<SearchResultSong>;
4pub type SearchArtistResp = ResultResp<SearchResultArtist>;
5pub type SearchPodcastResp = ResultResp<SearchResultPodcast>;
6pub type SearchPlaylistResp = ResultResp<SearchResultPlaylist>;
7pub type SearchAlbumResp = ResultResp<SearchResultAlbum>;
8
9#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
10#[serde(rename_all = "camelCase")]
11pub struct ResultResp<T> {
12 pub code: usize,
13 pub result: Option<T>,
14}
15
16#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
17#[serde(rename_all = "camelCase", default)]
18pub struct SearchResultSong {
19 pub songs: Vec<Song>,
20 pub has_more: bool,
21}
22
23#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
24#[serde(rename_all = "camelCase")]
25pub struct Song {
26 pub id: usize,
27 pub name: String,
28 #[serde(alias = "ar")]
29 pub artists: Vec<Artist>,
30 #[serde(alias = "al")]
31 pub album: Album,
32 #[serde(alias = "dt")]
33 pub duration: usize,
34 pub fee: usize,
35 #[serde(alias = "popularity")]
36 pub pop: f32,
37 }
40
41#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
42#[serde(rename_all = "camelCase")]
43pub struct Artist {
44 pub id: usize,
45 pub name: Option<String>,
46}
47
48#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
49#[serde(rename_all = "camelCase")]
50pub struct Album {
51 pub id: usize,
52 pub name: Option<String>,
53 #[serde(default)]
54 pub pic_url: String,
55 pub pic: usize,
56}
57
58#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
59#[serde(rename_all = "camelCase")]
60pub struct PodcastAudio {
61 pub main_song: Song,
62 pub dj: UserProfile,
63 pub liked_count: usize,
64 pub comment_count: usize,
65}
66
67#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
69#[serde(rename_all = "camelCase")]
70pub struct UserPodcastsResp {
71 pub code: usize,
72 #[serde(default)]
73 pub dj_radios: Vec<Podcast>,
74 #[serde(default)]
75 pub has_more: bool,
76}
77
78#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
79#[serde(rename_all = "camelCase")]
80pub struct PodcastAudiosResp {
81 pub code: usize,
82 #[serde(default)]
83 pub programs: Vec<PodcastAudio>,
84 #[serde(default)]
85 pub more: bool,
86}
87
88#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
89#[serde(rename_all = "camelCase")]
90pub struct UserProfile {
91 pub user_id: usize,
92 pub nickname: String,
93}
94
95#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
96#[serde(rename_all = "camelCase")]
97pub struct UserAccountResp {
98 pub code: usize,
99 pub profile: Option<UserProfile>,
100}
101
102#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
103pub struct UserPlaylistResp {
104 pub code: usize,
105 #[serde(default)]
106 pub playlist: Vec<Playlist>,
107}
108
109#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
110pub struct PlaylistDetailResp {
111 pub code: usize,
112 pub playlist: Option<PlaylistDetail>,
113}
114
115#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
116#[serde(rename_all = "camelCase")]
117pub struct Playlist {
118 pub id: usize,
119 pub name: String,
120 pub description: Option<String>,
121}
122
123#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
124#[serde(rename_all = "camelCase")]
125pub struct PlaylistDetail {
126 pub id: usize,
127 pub name: String,
128 pub description: Option<String>,
129 #[serde(default)]
130 pub tracks: Vec<Song>,
131 #[serde(default)]
132 pub track_ids: Vec<Id>,
133 pub user_id: usize,
134}
135
136#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
137pub struct Id {
138 pub id: usize,
139}
140
141#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
142pub struct SongUrlResp {
143 pub code: usize,
144 #[serde(default)]
145 pub data: Vec<SongUrl>,
146}
147
148#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
149pub struct SongUrl {
150 pub id: usize,
151 pub url: String,
152 pub br: usize,
153}
154
155#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
156#[serde(rename_all = "camelCase")]
157pub struct UserCloudResp {
158 pub code: usize,
159 #[serde(default)]
160 pub has_more: bool,
161 #[serde(default)]
162 pub data: Vec<CloudSongMeta>,
163}
164
165#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
166#[serde(rename_all = "camelCase")]
167pub struct CloudSongMeta {
168 pub simple_song: Song,
169 pub song_id: usize,
170 pub song_name: String,
171 pub add_time: i128,
172 pub file_size: usize,
173 pub bitrate: usize,
174 pub file_name: String,
175}
176
177#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
178#[serde(rename_all = "camelCase")]
179pub struct RecommendedSongs {
180 #[serde(default)]
181 pub daily_songs: Vec<Song>,
182 #[serde(default)]
183 pub order_songs: Vec<Song>,
184}
185
186#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
187pub struct RecommendedSongsResp {
188 pub code: usize,
189 pub data: RecommendedSongs,
190}
191
192#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
193#[serde(rename_all = "camelCase")]
194pub struct Comment {
195 pub user: UserProfile,
196 #[serde(default)]
197 pub content: String,
198 pub time: u64,
199 pub liked_count: usize,
200 pub liked: bool,
201}
202
203#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
204#[serde(rename_all = "camelCase")]
205pub struct ResourceComments {
206 #[serde(default)]
207 pub comments: Vec<Comment>,
208 pub total_count: usize,
209 pub has_more: bool,
210}
211
212#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
213#[serde(rename_all = "camelCase")]
214pub struct ResourceCommentsResp {
215 pub code: usize,
216 pub data: ResourceComments,
217}
218
219#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
220#[serde(rename_all = "camelCase")]
221pub struct HotCommentsResp {
222 pub code: usize,
223 #[serde(default)]
224 pub hot_comments: Vec<Comment>,
225 pub has_more: bool,
226 pub total: usize,
227}
228
229#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
230#[serde(rename_all = "camelCase")]
231pub struct LyricResp {
232 pub code: usize,
233 pub sgc: bool,
234 pub sfy: bool,
235 pub qfy: bool,
236 pub lrc: Option<Lyric>,
237 pub klyric: Option<Lyric>,
238 pub tlyric: Option<Lyric>,
239}
240
241#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
242#[serde(rename_all = "camelCase")]
243pub struct Lyric {
244 #[serde(default)]
245 pub version: usize,
246 #[serde(default)]
247 pub lyric: String,
248}
249
250#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
251#[serde(rename_all = "camelCase")]
252pub struct PersonalFmResp {
253 pub code: usize,
254 #[serde(default)]
255 pub data: Vec<Song>,
256}
257
258#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
259#[serde(rename_all = "camelCase")]
260pub struct RecommendedPlaylistsResp {
261 pub code: usize,
262 #[serde(default)]
263 pub recommend: Vec<Playlist>,
264}
265
266#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
267#[serde(rename_all = "camelCase")]
268pub struct SimiSongsResp {
269 pub code: usize,
270 #[serde(default)]
271 pub songs: Vec<Song>,
272}
273
274#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
275#[serde(rename_all = "camelCase")]
276pub struct ArtistSongsResp {
277 pub code: usize,
278 #[serde(default)]
279 pub songs: Vec<Song>,
280 #[serde(default)]
281 pub more: bool,
282 #[serde(default)]
283 pub total: usize,
284}
285
286#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
287#[serde(rename_all = "camelCase")]
288pub struct ArtistSublistResp {
289 pub code: usize,
290 #[serde(default)]
291 pub data: Vec<Artist>,
292 #[serde(default)]
293 pub has_more: bool,
294}
295
296#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
297#[serde(rename_all = "camelCase")]
298pub struct Podcast {
299 pub id: usize,
300 pub name: String,
301 pub desc: String,
302 pub sub_count: usize,
303 pub category: String,
304 pub dj: UserProfile,
305}
306
307#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
308#[serde(rename_all = "camelCase")]
309pub struct SearchResultArtist {
310 #[serde(default)]
311 pub artists: Vec<Artist>,
312}
313
314#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
315#[serde(rename_all = "camelCase")]
316pub struct SearchResultPodcast {
317 #[serde(default)]
318 pub dj_radios: Vec<Podcast>,
319}
320
321#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
322#[serde(rename_all = "camelCase")]
323pub struct SearchResultPlaylist {
324 #[serde(default)]
325 pub playlists: Vec<Playlist>,
326}
327
328#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
329#[serde(rename_all = "camelCase")]
330pub struct SearchResultAlbum {
331 #[serde(default)]
332 pub albums: Vec<Album>,
333}
334
335#[cfg(test)]
336mod tests {
337
338 use serde_json::json;
339
340 use crate::{
341 types::{
342 ArtistSongsResp, ArtistSublistResp, HotCommentsResp, LyricResp, PersonalFmResp,
343 PlaylistDetailResp, PodcastAudiosResp, RecommendedPlaylistsResp, RecommendedSongsResp,
344 ResourceCommentsResp, SearchAlbumResp, SearchArtistResp, SearchPlaylistResp,
345 SearchPodcastResp, SearchSongResp, SimiSongsResp, SongUrlResp, UserAccountResp,
346 UserCloudResp, UserPlaylistResp, UserPodcastsResp,
347 },
348 NcmApi, SearchType,
349 };
350
351 #[tokio::test]
360 async fn test_de_search_song() {
361 let api = NcmApi::default();
362 let resp = api.search("xusong", None).await;
363 assert!(resp.is_ok());
364
365 let res = serde_json::from_slice::<SearchSongResp>(resp.unwrap().data()).unwrap();
366 assert_eq!(res.code, 200);
367 }
368
369 #[tokio::test]
370 async fn test_de_search_artist() {
371 let api = NcmApi::default();
372 let resp = api
373 .search("xusong", Some(json!({ "type": SearchType::Artist })))
374 .await;
375 assert!(resp.is_ok());
376
377 let res = serde_json::from_slice::<SearchArtistResp>(resp.unwrap().data()).unwrap();
378 assert_eq!(res.code, 200);
379 }
380
381 #[tokio::test]
382 async fn test_de_search_playlist() {
383 let api = NcmApi::default();
384 let resp = api
385 .search("ost", Some(json!({ "type": SearchType::Collection })))
386 .await;
387 assert!(resp.is_ok());
388
389 let res = serde_json::from_slice::<SearchPlaylistResp>(resp.unwrap().data()).unwrap();
390 assert_eq!(res.code, 200);
391 }
392
393 #[tokio::test]
394 async fn test_de_search_podcast() {
395 let api = NcmApi::default();
396 let resp = api
397 .search("asmr", Some(json!({ "type": SearchType::Podcast })))
398 .await;
399 assert!(resp.is_ok());
400
401 let res = serde_json::from_slice::<SearchPodcastResp>(resp.unwrap().data()).unwrap();
402 assert_eq!(res.code, 200);
403 }
404
405 #[tokio::test]
406 async fn test_de_search_album() {
407 let api = NcmApi::default();
408 let resp = api
409 .search("Mota", Some(json!({ "type": SearchType::Album })))
410 .await;
411 assert!(resp.is_ok());
412
413 let res = serde_json::from_slice::<SearchAlbumResp>(resp.unwrap().data()).unwrap();
414 assert_eq!(res.code, 200);
415 }
416
417 #[tokio::test]
418 async fn test_de_user_account() {
419 let api = NcmApi::default();
420 let resp = api.user_account().await;
421 assert!(resp.is_ok());
422
423 let res = serde_json::from_slice::<UserAccountResp>(resp.unwrap().data()).unwrap();
424 assert_eq!(res.code, 200);
425 }
426
427 #[tokio::test]
428 async fn test_de_user_playlist() {
429 let api = NcmApi::default();
430 let uid = 49668844;
431 let resp = api.user_playlist(uid, None).await;
432 assert!(resp.is_ok());
433
434 let res = serde_json::from_slice::<UserPlaylistResp>(resp.unwrap().data()).unwrap();
435 assert_eq!(res.code, 200);
436 }
437
438 #[tokio::test]
439 async fn test_de_playlist_detail() {
440 let api = NcmApi::default();
441 const COLLECTION_ID: usize = 2484967117;
442 let resp = api.playlist_detail(COLLECTION_ID, None).await;
443 assert!(resp.is_ok());
444
445 let res = serde_json::from_slice::<PlaylistDetailResp>(resp.unwrap().data()).unwrap();
446 assert_eq!(res.code, 200);
447 }
448
449 #[tokio::test]
450 async fn test_de_song_url() {
451 let api = NcmApi::default();
452 let resp = api.song_url(&vec![28497094, 28497093]).await;
453 assert!(resp.is_ok());
454
455 let res = serde_json::from_slice::<SongUrlResp>(resp.unwrap().data()).unwrap();
456 assert_eq!(res.code, 200);
457 }
458
459 #[tokio::test]
460 async fn test_de_user_cloud() {
461 let api = NcmApi::default();
462 let resp = api.user_cloud(None).await;
463 assert!(resp.is_ok());
464
465 let res = serde_json::from_slice::<UserCloudResp>(resp.unwrap().data()).unwrap();
466 assert_eq!(res.code, 200);
467 }
468
469 #[tokio::test]
470 async fn test_de_recommended_songs() {
471 let api = NcmApi::default();
472 let resp = api.recommend_songs().await;
473 assert!(resp.is_ok());
474
475 let res = serde_json::from_slice::<RecommendedSongsResp>(resp.unwrap().data()).unwrap();
476 assert_eq!(res.code, 200);
477 }
478
479 #[tokio::test]
480 async fn test_de_comments() {
481 let api = NcmApi::default();
482 let resp = api
483 .comment(32977061, crate::api::ResourceType::Song, 10, 1, 1, 0, false)
484 .await;
485 assert!(resp.is_ok());
486
487 let res = serde_json::from_slice::<ResourceCommentsResp>(resp.unwrap().data()).unwrap();
488 assert_eq!(res.code, 200);
489 }
490
491 #[tokio::test]
492 async fn test_de_hot_comments() {
493 let api = NcmApi::default();
494 let resp = api
495 .comment_hot(32977061, crate::ResourceType::Song, None)
496 .await;
497 assert!(resp.is_ok());
498
499 let res = serde_json::from_slice::<HotCommentsResp>(resp.unwrap().data()).unwrap();
500 assert_eq!(res.code, 200);
501 }
502
503 #[tokio::test]
504 async fn test_de_lyric() {
505 let api = NcmApi::default();
506 let resp = api.lyric(17346999).await;
507 assert!(resp.is_ok());
508
509 let res = serde_json::from_slice::<LyricResp>(resp.unwrap().data()).unwrap();
510 assert_eq!(res.code, 200);
511 }
512
513 #[tokio::test]
514 async fn test_de_personal_fm() {
515 let api = NcmApi::default();
516 let resp = api.personal_fm().await;
517 assert!(resp.is_ok());
518
519 let res = serde_json::from_slice::<PersonalFmResp>(resp.unwrap().data()).unwrap();
520 assert_eq!(res.code, 200);
521 }
522
523 #[tokio::test]
524 async fn test_de_recommended_playlists() {
525 let api = NcmApi::default();
526 let resp = api.recommend_resource().await;
527 assert!(resp.is_ok());
528
529 let res = serde_json::from_slice::<RecommendedPlaylistsResp>(resp.unwrap().data()).unwrap();
530 assert_eq!(res.code, 200);
531 }
532
533 #[tokio::test]
534 async fn test_de_simi_songs() {
535 let api = NcmApi::default();
536 let resp = api.simi_song(347230, None).await;
537 assert!(resp.is_ok());
538
539 let res = serde_json::from_slice::<SimiSongsResp>(resp.unwrap().data()).unwrap();
540 assert_eq!(res.code, 200);
541 }
542
543 #[tokio::test]
544 async fn test_de_artist_songs() {
545 let api = NcmApi::default();
546 let resp = api.artist_songs(6452, None).await;
547 assert!(resp.is_ok());
548
549 let res = serde_json::from_slice::<ArtistSongsResp>(resp.unwrap().data()).unwrap();
550 assert_eq!(res.code, 200);
551 }
552
553 #[tokio::test]
554 async fn test_de_artist_sublist() {
555 let api = NcmApi::default();
556 let resp = api.artist_sublist(None).await;
557 assert!(resp.is_ok());
558
559 let res = serde_json::from_slice::<ArtistSublistResp>(resp.unwrap().data()).unwrap();
560 assert_eq!(res.code, 200);
561 }
562
563 #[tokio::test]
564 async fn test_de_user_podcast() {
565 let api = NcmApi::default();
566 let resp = api.user_podcast(1398995370).await;
567 assert!(resp.is_ok());
568
569 let res = serde_json::from_slice::<UserPodcastsResp>(resp.unwrap().data()).unwrap();
570 assert_eq!(res.code, 200);
571 }
572
573 #[tokio::test]
574 async fn test_de_podcast_audio() {
575 let api = NcmApi::default();
576 let resp = api.podcast_audio(965114264, None).await;
577 assert!(resp.is_ok());
578
579 let res = serde_json::from_slice::<PodcastAudiosResp>(resp.unwrap().data()).unwrap();
580 assert_eq!(res.code, 200);
581 }
582}