lib.rs 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  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, 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. pub trait Metadata: Send + Sized + 'static {
  43. type Message: protobuf::Message;
  44. fn request_url(id: SpotifyId) -> String;
  45. fn parse(msg: &Self::Message, session: &Session) -> Self;
  46. fn get(session: &Session, id: SpotifyId) -> Box<Future<Item = Self, Error = MercuryError>> {
  47. let uri = Self::request_url(id);
  48. let request = session.mercury().get(uri);
  49. let session = session.clone();
  50. Box::new(request.and_then(move |response| {
  51. let data = response.payload.first().expect("Empty payload");
  52. let msg: Self::Message = protobuf::parse_from_bytes(data).unwrap();
  53. Ok(Self::parse(&msg, &session))
  54. }))
  55. }
  56. }
  57. pub trait PlaylistMeta: Send + Sized + 'static {
  58. type Message: protobuf::Message;
  59. fn base_url() -> &'static str;
  60. fn parse(msg: &Self::Message, session: &Session) -> Self;
  61. fn get(session: &Session, id: SpotifyId, user: String, start: i32, len: i32) -> Box<Future<Item = Self, Error = MercuryError>> {
  62. //let uri = format!("hm://playlist/{}?from={}&length={}",id.to_base62(), 0, 100);
  63. let uri = format!("hm://playlist/user/{}/playlist/{}?from={}&length={}", user, id.to_base62(), start, len);
  64. println!("request uri: {}", uri);
  65. let request = session.mercury().get(uri);
  66. println!("a");
  67. let session = session.clone();
  68. Box::new(request.and_then(move |response| {
  69. println!("{:?}", response);
  70. let data = response.payload.first().expect("Empty payload");
  71. let msg: Self::Message = protobuf::parse_from_bytes(data).unwrap();
  72. println!("{:?}", msg);
  73. Ok(Self::parse(&msg, &session))
  74. }))
  75. }
  76. }
  77. #[derive(Debug, Clone)]
  78. pub struct Track {
  79. pub id: SpotifyId,
  80. pub name: String,
  81. pub duration: i32,
  82. pub album: SpotifyId,
  83. pub artists: Vec<SpotifyId>,
  84. pub files: LinearMap<FileFormat, FileId>,
  85. pub alternatives: Vec<SpotifyId>,
  86. pub available: bool,
  87. }
  88. #[derive(Debug, Clone)]
  89. pub struct Album {
  90. pub id: SpotifyId,
  91. pub name: String,
  92. pub artists: Vec<SpotifyId>,
  93. pub tracks: Vec<SpotifyId>,
  94. pub covers: Vec<FileId>,
  95. }
  96. #[derive(Debug, Clone)]
  97. pub struct Playlist {
  98. //pub id: SpotifyId,
  99. pub length: i32,
  100. pub name: String,
  101. pub tracks: Vec<SpotifyId>,
  102. }
  103. #[derive(Debug, Clone)]
  104. pub struct Artist {
  105. pub id: SpotifyId,
  106. pub name: String,
  107. pub top_tracks: Vec<SpotifyId>,
  108. }
  109. impl Metadata for Track {
  110. type Message = protocol::metadata::Track;
  111. fn request_url(id: SpotifyId) -> String {
  112. format!("hm://metadata/3/track/{}", id.to_base16())
  113. }
  114. fn parse(msg: &Self::Message, session: &Session) -> Self {
  115. let country = session.country();
  116. let artists = msg
  117. .get_artist()
  118. .iter()
  119. .filter(|artist| artist.has_gid())
  120. .map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
  121. .collect::<Vec<_>>();
  122. let files = msg
  123. .get_file()
  124. .iter()
  125. .filter(|file| file.has_file_id())
  126. .map(|file| {
  127. let mut dst = [0u8; 20];
  128. dst.clone_from_slice(file.get_file_id());
  129. (file.get_format(), FileId(dst))
  130. })
  131. .collect();
  132. Track {
  133. id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
  134. name: msg.get_name().to_owned(),
  135. duration: msg.get_duration(),
  136. album: SpotifyId::from_raw(msg.get_album().get_gid()).unwrap(),
  137. artists: artists,
  138. files: files,
  139. alternatives: msg
  140. .get_alternative()
  141. .iter()
  142. .map(|alt| SpotifyId::from_raw(alt.get_gid()).unwrap())
  143. .collect(),
  144. available: parse_restrictions(msg.get_restriction(), &country, "premium"),
  145. }
  146. }
  147. }
  148. impl Metadata for Album {
  149. type Message = protocol::metadata::Album;
  150. fn request_url(id: SpotifyId) -> String {
  151. format!("hm://metadata/3/album/{}", id.to_base16())
  152. }
  153. fn parse(msg: &Self::Message, _: &Session) -> Self {
  154. let artists = msg
  155. .get_artist()
  156. .iter()
  157. .filter(|artist| artist.has_gid())
  158. .map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
  159. .collect::<Vec<_>>();
  160. let tracks = msg
  161. .get_disc()
  162. .iter()
  163. .flat_map(|disc| disc.get_track())
  164. .filter(|track| track.has_gid())
  165. .map(|track| SpotifyId::from_raw(track.get_gid()).unwrap())
  166. .collect::<Vec<_>>();
  167. let covers = msg
  168. .get_cover_group()
  169. .get_image()
  170. .iter()
  171. .filter(|image| image.has_file_id())
  172. .map(|image| {
  173. let mut dst = [0u8; 20];
  174. dst.clone_from_slice(image.get_file_id());
  175. FileId(dst)
  176. })
  177. .collect::<Vec<_>>();
  178. Album {
  179. id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
  180. name: msg.get_name().to_owned(),
  181. artists: artists,
  182. tracks: tracks,
  183. covers: covers,
  184. }
  185. }
  186. }
  187. impl PlaylistMeta for Playlist {
  188. type Message = protocol::playlist4changes::SelectedListContent;
  189. fn base_url() -> &'static str {
  190. "hm://playlist/'?from=' + from + '&length=' + length"
  191. }
  192. fn parse(msg: &Self::Message, _: &Session) -> Self {
  193. let tracks = msg
  194. .get_contents()
  195. .get_items()
  196. .iter()
  197. .map(|item| {
  198. let uri_split = item.get_uri().split(":");
  199. let uri_parts: Vec<&str> = uri_split.collect();
  200. SpotifyId::from_base62(uri_parts[2]).unwrap()
  201. })
  202. .collect::<Vec<_>>();
  203. println!("parse Message: {:?}", msg);
  204. Playlist {
  205. //id: SpotifyId::from_raw(msg.get_attributes().get_id()).unwrap(),
  206. name: msg.get_attributes().get_name().to_owned(),
  207. length: msg.get_length(),
  208. tracks: tracks,
  209. }
  210. }
  211. }
  212. impl Metadata for Artist {
  213. type Message = protocol::metadata::Artist;
  214. fn request_url(id: SpotifyId) -> String {
  215. format!("hm://metadata/3/artist/{}", id.to_base16())
  216. }
  217. fn parse(msg: &Self::Message, session: &Session) -> Self {
  218. let country = session.country();
  219. let top_tracks: Vec<SpotifyId> = match msg
  220. .get_top_track()
  221. .iter()
  222. .find(|tt| !tt.has_country() || countrylist_contains(tt.get_country(), &country))
  223. {
  224. Some(tracks) => tracks
  225. .get_track()
  226. .iter()
  227. .filter(|track| track.has_gid())
  228. .map(|track| SpotifyId::from_raw(track.get_gid()).unwrap())
  229. .collect::<Vec<_>>(),
  230. None => Vec::new(),
  231. };
  232. Artist {
  233. id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
  234. name: msg.get_name().to_owned(),
  235. top_tracks: top_tracks,
  236. }
  237. }
  238. }
  239. struct StrChunks<'s>(&'s str, usize);
  240. trait StrChunksExt {
  241. fn chunks(&self, size: usize) -> StrChunks;
  242. }
  243. impl StrChunksExt for str {
  244. fn chunks(&self, size: usize) -> StrChunks {
  245. StrChunks(self, size)
  246. }
  247. }
  248. impl<'s> Iterator for StrChunks<'s> {
  249. type Item = &'s str;
  250. fn next(&mut self) -> Option<&'s str> {
  251. let &mut StrChunks(data, size) = self;
  252. if data.is_empty() {
  253. None
  254. } else {
  255. let ret = Some(&data[..size]);
  256. self.0 = &data[size..];
  257. ret
  258. }
  259. }
  260. }