lib.rs 13 KB

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