lib.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. extern crate byteorder;
  2. extern crate futures;
  3. extern crate linear_map;
  4. extern crate protobuf;
  5. extern crate librespot_core;
  6. extern crate librespot_protocol as protocol;
  7. pub mod cover;
  8. use futures::future;
  9. use futures::Future;
  10. use linear_map::LinearMap;
  11. use librespot_core::mercury::MercuryError;
  12. use librespot_core::session::Session;
  13. use librespot_core::spotify_id::{FileId, SpotifyAudioType, SpotifyId};
  14. pub use protocol::metadata::AudioFile_Format as FileFormat;
  15. fn countrylist_contains(list: &str, country: &str) -> bool {
  16. list.chunks(2).any(|cc| cc == country)
  17. }
  18. fn parse_restrictions<'s, I>(restrictions: I, country: &str, catalogue: &str) -> bool
  19. where
  20. I: IntoIterator<Item = &'s protocol::metadata::Restriction>,
  21. {
  22. let mut forbidden = "".to_string();
  23. let mut has_forbidden = false;
  24. let mut allowed = "".to_string();
  25. let mut has_allowed = false;
  26. let rs = restrictions
  27. .into_iter()
  28. .filter(|r| r.get_catalogue_str().contains(&catalogue.to_owned()));
  29. for r in rs {
  30. if r.has_countries_forbidden() {
  31. forbidden.push_str(r.get_countries_forbidden());
  32. has_forbidden = true;
  33. }
  34. if r.has_countries_allowed() {
  35. allowed.push_str(r.get_countries_allowed());
  36. has_allowed = true;
  37. }
  38. }
  39. (has_forbidden || has_allowed)
  40. && (!has_forbidden || !countrylist_contains(forbidden.as_str(), country))
  41. && (!has_allowed || countrylist_contains(allowed.as_str(), country))
  42. }
  43. // A wrapper with fields the player needs
  44. #[derive(Debug, Clone)]
  45. pub struct AudioItem {
  46. pub id: SpotifyId,
  47. pub uri: String,
  48. pub files: LinearMap<FileFormat, FileId>,
  49. pub name: String,
  50. pub available: bool,
  51. pub alternatives: Option<Vec<SpotifyId>>,
  52. }
  53. impl AudioItem {
  54. pub fn get_audio_item(
  55. session: &Session,
  56. id: SpotifyId,
  57. ) -> Box<dyn Future<Item = AudioItem, Error = MercuryError>> {
  58. match id.audio_type {
  59. SpotifyAudioType::Track => Track::get_audio_item(session, id),
  60. SpotifyAudioType::Podcast => Episode::get_audio_item(session, id),
  61. SpotifyAudioType::NonPlayable => {
  62. Box::new(future::err::<AudioItem, MercuryError>(MercuryError))
  63. }
  64. }
  65. }
  66. }
  67. trait AudioFiles {
  68. fn get_audio_item(
  69. session: &Session,
  70. id: SpotifyId,
  71. ) -> Box<dyn Future<Item = AudioItem, Error = MercuryError>>;
  72. }
  73. impl AudioFiles for Track {
  74. fn get_audio_item(
  75. session: &Session,
  76. id: SpotifyId,
  77. ) -> Box<dyn Future<Item = AudioItem, Error = MercuryError>> {
  78. Box::new(Self::get(session, id).and_then(move |item| {
  79. Ok(AudioItem {
  80. id: id,
  81. uri: format!("spotify:track:{}", id.to_base62()),
  82. files: item.files,
  83. name: item.name,
  84. available: item.available,
  85. alternatives: Some(item.alternatives),
  86. })
  87. }))
  88. }
  89. }
  90. impl AudioFiles for Episode {
  91. fn get_audio_item(
  92. session: &Session,
  93. id: SpotifyId,
  94. ) -> Box<dyn Future<Item = AudioItem, Error = MercuryError>> {
  95. Box::new(Self::get(session, id).and_then(move |item| {
  96. Ok(AudioItem {
  97. id: id,
  98. uri: format!("spotify:episode:{}", id.to_base62()),
  99. files: item.files,
  100. name: item.name,
  101. available: item.available,
  102. alternatives: None,
  103. })
  104. }))
  105. }
  106. }
  107. pub trait Metadata: Send + Sized + 'static {
  108. type Message: protobuf::Message;
  109. fn request_url(id: SpotifyId) -> String;
  110. fn parse(msg: &Self::Message, session: &Session) -> Self;
  111. <<<<<<< HEAD
  112. fn get(session: &Session, id: SpotifyId) -> Box<dyn Future<Item = Self, Error = MercuryError>> {
  113. let uri = format!("{}/{}", Self::base_url(), id.to_base16());
  114. =======
  115. fn get(session: &Session, id: SpotifyId) -> Box<Future<Item = Self, Error = MercuryError>> {
  116. let uri = Self::request_url(id);
  117. >>>>>>> Make standard metadata get handle also playlists
  118. let request = session.mercury().get(uri);
  119. let session = session.clone();
  120. Box::new(request.and_then(move |response| {
  121. let data = response.payload.first().expect("Empty payload");
  122. let msg: Self::Message = protobuf::parse_from_bytes(data).unwrap();
  123. Ok(Self::parse(&msg, &session))
  124. }))
  125. }
  126. }
  127. pub trait PlaylistMeta: Send + Sized + 'static {
  128. type Message: protobuf::Message;
  129. fn base_url() -> &'static str;
  130. fn parse(msg: &Self::Message, session: &Session) -> Self;
  131. fn get(session: &Session, id: SpotifyId, user: String, start: i32, len: i32) -> Box<Future<Item = Self, Error = MercuryError>> {
  132. //let uri = format!("hm://playlist/{}?from={}&length={}",id.to_base62(), 0, 100);
  133. let uri = format!("hm://playlist/user/{}/playlist/{}?from={}&length={}", user, id.to_base62(), start, len);
  134. println!("request uri: {}", uri);
  135. let request = session.mercury().get(uri);
  136. println!("a");
  137. let session = session.clone();
  138. Box::new(request.and_then(move |response| {
  139. println!("{:?}", response);
  140. let data = response.payload.first().expect("Empty payload");
  141. let msg: Self::Message = protobuf::parse_from_bytes(data).unwrap();
  142. println!("{:?}", msg);
  143. Ok(Self::parse(&msg, &session))
  144. }))
  145. }
  146. }
  147. #[derive(Debug, Clone)]
  148. pub struct Track {
  149. pub id: SpotifyId,
  150. pub name: String,
  151. pub duration: i32,
  152. pub album: SpotifyId,
  153. pub artists: Vec<SpotifyId>,
  154. pub files: LinearMap<FileFormat, FileId>,
  155. pub alternatives: Vec<SpotifyId>,
  156. pub available: bool,
  157. }
  158. #[derive(Debug, Clone)]
  159. pub struct Album {
  160. pub id: SpotifyId,
  161. pub name: String,
  162. pub artists: Vec<SpotifyId>,
  163. pub tracks: Vec<SpotifyId>,
  164. pub covers: Vec<FileId>,
  165. }
  166. #[derive(Debug, Clone)]
  167. pub struct Episode {
  168. pub id: SpotifyId,
  169. pub name: String,
  170. pub external_url: String,
  171. pub duration: i32,
  172. pub language: String,
  173. pub show: SpotifyId,
  174. pub files: LinearMap<FileFormat, FileId>,
  175. pub covers: Vec<FileId>,
  176. pub available: bool,
  177. pub explicit: bool,
  178. }
  179. #[derive(Debug, Clone)]
  180. pub struct Show {
  181. pub id: SpotifyId,
  182. pub name: String,
  183. pub publisher: String,
  184. pub episodes: Vec<SpotifyId>,
  185. pub covers: Vec<FileId>,
  186. }
  187. #[derive(Debug, Clone)]
  188. pub struct Playlist {
  189. //pub id: SpotifyId,
  190. pub length: i32,
  191. pub name: String,
  192. pub tracks: Vec<SpotifyId>,
  193. }
  194. #[derive(Debug, Clone)]
  195. pub struct Artist {
  196. pub id: SpotifyId,
  197. pub name: String,
  198. pub top_tracks: Vec<SpotifyId>,
  199. }
  200. impl Metadata for Track {
  201. type Message = protocol::metadata::Track;
  202. fn request_url(id: SpotifyId) -> String {
  203. format!("hm://metadata/3/track/{}", id.to_base16())
  204. }
  205. fn parse(msg: &Self::Message, session: &Session) -> Self {
  206. let country = session.country();
  207. let artists = msg
  208. .get_artist()
  209. .iter()
  210. .filter(|artist| artist.has_gid())
  211. .map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
  212. .collect::<Vec<_>>();
  213. let files = msg
  214. .get_file()
  215. .iter()
  216. .filter(|file| file.has_file_id())
  217. .map(|file| {
  218. let mut dst = [0u8; 20];
  219. dst.clone_from_slice(file.get_file_id());
  220. (file.get_format(), FileId(dst))
  221. })
  222. .collect();
  223. Track {
  224. id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
  225. name: msg.get_name().to_owned(),
  226. duration: msg.get_duration(),
  227. album: SpotifyId::from_raw(msg.get_album().get_gid()).unwrap(),
  228. artists: artists,
  229. files: files,
  230. alternatives: msg
  231. .get_alternative()
  232. .iter()
  233. .map(|alt| SpotifyId::from_raw(alt.get_gid()).unwrap())
  234. .collect(),
  235. available: parse_restrictions(msg.get_restriction(), &country, "premium"),
  236. }
  237. }
  238. }
  239. impl Metadata for Album {
  240. type Message = protocol::metadata::Album;
  241. fn request_url(id: SpotifyId) -> String {
  242. format!("hm://metadata/3/album/{}", id.to_base16())
  243. }
  244. fn parse(msg: &Self::Message, _: &Session) -> Self {
  245. let artists = msg
  246. .get_artist()
  247. .iter()
  248. .filter(|artist| artist.has_gid())
  249. .map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
  250. .collect::<Vec<_>>();
  251. let tracks = msg
  252. .get_disc()
  253. .iter()
  254. .flat_map(|disc| disc.get_track())
  255. .filter(|track| track.has_gid())
  256. .map(|track| SpotifyId::from_raw(track.get_gid()).unwrap())
  257. .collect::<Vec<_>>();
  258. let covers = msg
  259. .get_cover_group()
  260. .get_image()
  261. .iter()
  262. .filter(|image| image.has_file_id())
  263. .map(|image| {
  264. let mut dst = [0u8; 20];
  265. dst.clone_from_slice(image.get_file_id());
  266. FileId(dst)
  267. })
  268. .collect::<Vec<_>>();
  269. Album {
  270. id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
  271. name: msg.get_name().to_owned(),
  272. artists: artists,
  273. tracks: tracks,
  274. covers: covers,
  275. }
  276. }
  277. }
  278. impl PlaylistMeta for Playlist {
  279. type Message = protocol::playlist4changes::SelectedListContent;
  280. fn base_url() -> &'static str {
  281. "hm://playlist/'?from=' + from + '&length=' + length"
  282. }
  283. fn parse(msg: &Self::Message, _: &Session) -> Self {
  284. let tracks = msg
  285. .get_contents()
  286. .get_items()
  287. .iter()
  288. .map(|item| {
  289. let uri_split = item.get_uri().split(":");
  290. let uri_parts: Vec<&str> = uri_split.collect();
  291. SpotifyId::from_base62(uri_parts[2]).unwrap()
  292. })
  293. .collect::<Vec<_>>();
  294. println!("parse Message: {:?}", msg);
  295. Playlist {
  296. //id: SpotifyId::from_raw(msg.get_attributes().get_id()).unwrap(),
  297. name: msg.get_attributes().get_name().to_owned(),
  298. length: msg.get_length(),
  299. tracks: tracks,
  300. }
  301. }
  302. }
  303. impl Metadata for Artist {
  304. type Message = protocol::metadata::Artist;
  305. fn request_url(id: SpotifyId) -> String {
  306. format!("hm://metadata/3/artist/{}", id.to_base16())
  307. }
  308. fn parse(msg: &Self::Message, session: &Session) -> Self {
  309. let country = session.country();
  310. let top_tracks: Vec<SpotifyId> = match msg
  311. .get_top_track()
  312. .iter()
  313. .find(|tt| !tt.has_country() || countrylist_contains(tt.get_country(), &country))
  314. {
  315. Some(tracks) => tracks
  316. .get_track()
  317. .iter()
  318. .filter(|track| track.has_gid())
  319. .map(|track| SpotifyId::from_raw(track.get_gid()).unwrap())
  320. .collect::<Vec<_>>(),
  321. None => Vec::new(),
  322. };
  323. Artist {
  324. id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
  325. name: msg.get_name().to_owned(),
  326. top_tracks: top_tracks,
  327. }
  328. }
  329. }
  330. // Podcast
  331. impl Metadata for Episode {
  332. type Message = protocol::metadata::Episode;
  333. fn base_url() -> &'static str {
  334. "hm://metadata/3/episode"
  335. }
  336. fn parse(msg: &Self::Message, session: &Session) -> Self {
  337. let country = session.country();
  338. let files = msg
  339. .get_file()
  340. .iter()
  341. .filter(|file| file.has_file_id())
  342. .map(|file| {
  343. let mut dst = [0u8; 20];
  344. dst.clone_from_slice(file.get_file_id());
  345. (file.get_format(), FileId(dst))
  346. })
  347. .collect();
  348. let covers = msg
  349. .get_covers()
  350. .get_image()
  351. .iter()
  352. .filter(|image| image.has_file_id())
  353. .map(|image| {
  354. let mut dst = [0u8; 20];
  355. dst.clone_from_slice(image.get_file_id());
  356. FileId(dst)
  357. })
  358. .collect::<Vec<_>>();
  359. Episode {
  360. id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
  361. name: msg.get_name().to_owned(),
  362. external_url: msg.get_external_url().to_owned(),
  363. duration: msg.get_duration().to_owned(),
  364. language: msg.get_language().to_owned(),
  365. show: SpotifyId::from_raw(msg.get_show().get_gid()).unwrap(),
  366. covers: covers,
  367. files: files,
  368. available: parse_restrictions(msg.get_restriction(), &country, "premium"),
  369. explicit: msg.get_explicit().to_owned(),
  370. }
  371. }
  372. }
  373. impl Metadata for Show {
  374. type Message = protocol::metadata::Show;
  375. fn base_url() -> &'static str {
  376. "hm://metadata/3/show"
  377. }
  378. fn parse(msg: &Self::Message, _: &Session) -> Self {
  379. let episodes = msg
  380. .get_episode()
  381. .iter()
  382. .filter(|episode| episode.has_gid())
  383. .map(|episode| SpotifyId::from_raw(episode.get_gid()).unwrap())
  384. .collect::<Vec<_>>();
  385. let covers = msg
  386. .get_covers()
  387. .get_image()
  388. .iter()
  389. .filter(|image| image.has_file_id())
  390. .map(|image| {
  391. let mut dst = [0u8; 20];
  392. dst.clone_from_slice(image.get_file_id());
  393. FileId(dst)
  394. })
  395. .collect::<Vec<_>>();
  396. Show {
  397. id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
  398. name: msg.get_name().to_owned(),
  399. publisher: msg.get_publisher().to_owned(),
  400. episodes: episodes,
  401. covers: covers,
  402. }
  403. }
  404. }
  405. struct StrChunks<'s>(&'s str, usize);
  406. trait StrChunksExt {
  407. fn chunks(&self, size: usize) -> StrChunks;
  408. }
  409. impl StrChunksExt for str {
  410. fn chunks(&self, size: usize) -> StrChunks {
  411. StrChunks(self, size)
  412. }
  413. }
  414. impl<'s> Iterator for StrChunks<'s> {
  415. type Item = &'s str;
  416. fn next(&mut self) -> Option<&'s str> {
  417. let &mut StrChunks(data, size) = self;
  418. if data.is_empty() {
  419. None
  420. } else {
  421. let ret = Some(&data[..size]);
  422. self.0 = &data[size..];
  423. ret
  424. }
  425. }
  426. }