lib.rs 12 KB

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