From a7cceb1aa5afb13de9f51c10670757d39d50e2a3 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Tue, 6 Nov 2018 01:50:59 -0500 Subject: add some basic last.fm api querying --- src/lastfm/api_types.rs | 44 ++++++++++++++++++ src/lastfm/mod.rs | 120 ++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 20 ++++++++ 3 files changed, 184 insertions(+) create mode 100644 src/lastfm/api_types.rs create mode 100644 src/lastfm/mod.rs (limited to 'src') diff --git a/src/lastfm/api_types.rs b/src/lastfm/api_types.rs new file mode 100644 index 0000000..28b593a --- /dev/null +++ b/src/lastfm/api_types.rs @@ -0,0 +1,44 @@ +#![allow(non_camel_case_types)] + +#[derive(Deserialize)] +pub struct track_artist { + #[serde(rename = "#text")] + pub text: String, +} + +#[derive(Deserialize)] +pub struct track_album { + #[serde(rename = "#text")] + pub text: String, +} + +#[derive(Deserialize)] +pub struct track_date { + pub uts: String, // no idea why this is a string either +} + +#[derive(Deserialize)] +pub struct track { + pub artist: track_artist, + pub name: String, + pub album: track_album, + pub date: track_date, +} + +#[derive(Deserialize)] +pub struct recent_tracks_recenttracks_attr { + pub total: String, // no idea why this is a string +} + +#[derive(Deserialize)] +pub struct recent_tracks_recenttracks { + pub track: Vec, + #[serde(rename = "@attr")] + pub attr: recent_tracks_recenttracks_attr, +} + +#[derive(Deserialize)] +pub struct recent_tracks { + pub recenttracks: recent_tracks_recenttracks, +} + diff --git a/src/lastfm/mod.rs b/src/lastfm/mod.rs new file mode 100644 index 0000000..96f4555 --- /dev/null +++ b/src/lastfm/mod.rs @@ -0,0 +1,120 @@ +use failure; +use reqwest; + +use error::Result; + +mod api_types; + +const API_ROOT: &'static str = "https://ws.audioscrobbler.com/2.0/"; + +pub struct LastFMClient { + client: reqwest::Client, + api_key: String, + user: String, +} + +pub struct Track { + pub artist: String, + pub name: String, + pub album: String, + pub timestamp: i64, +} + +pub struct Tracks<'a> { + client: &'a LastFMClient, + page: u64, + buf: Vec, +} + +impl<'a> Tracks<'a> { + fn new(client: &LastFMClient) -> Tracks { + Tracks { + client, + page: 0, + buf: vec![], + } + } + + fn get_next_page(&mut self) -> Result<()> { + self.page += 1; + + let mut res = self.client.client + .get(API_ROOT) + .query(&[ + ("method", "user.getrecenttracks"), + ("api_key", &self.client.api_key), + ("user", &self.client.user), + ("format", "json"), + ("page", &format!("{}", self.page)), + ("limit", "200"), + ]) + .send()?; + + if res.status().is_success() { + let data: api_types::recent_tracks = res.json()?; + self.buf = data.recenttracks.track + .iter() + .map(|t| { + Ok(Track { + artist: t.artist.text.clone(), + name: t.name.clone(), + album: t.album.text.clone(), + timestamp: t.date.uts.parse()?, + }) + }) + .collect::>>()?; + Ok(()) + } + else { + Err(failure::err_msg(res.status().as_str().to_string())) + } + } +} + +impl<'a> Iterator for Tracks<'a> { + type Item = Track; + + fn next(&mut self) -> Option { + if self.buf.len() == 0 { + let result = self.get_next_page(); + if result.is_err() { + return None; + } + } + self.buf.pop() + } +} + +impl LastFMClient { + pub fn new(api_key: &str, user: &str) -> LastFMClient { + LastFMClient { + client: reqwest::Client::new(), + api_key: api_key.to_string(), + user: user.to_string(), + } + } + + pub fn track_count(&self) -> Result { + let mut res = self.client + .get(API_ROOT) + .query(&[ + ("method", "user.getrecenttracks"), + ("api_key", &self.api_key), + ("user", &self.user), + ("format", "json"), + ]) + .send()?; + + if res.status().is_success() { + let data: api_types::recent_tracks = res.json()?; + Ok(data.recenttracks.attr.total.parse()?) + } + else { + Err(failure::err_msg(res.status().as_str().to_string())) + } + } + + pub fn tracks(&self) -> Tracks { + Tracks::new(&self) + } +} diff --git a/src/main.rs b/src/main.rs index f1018d9..0df7810 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,32 @@ extern crate directories; extern crate failure; +extern crate reqwest; extern crate rusqlite; +extern crate serde; +extern crate serde_json; +#[macro_use] +extern crate serde_derive; mod error; +mod lastfm; mod paths; mod db; fn main() { + let args: Vec<_> = std::env::args().collect(); + if args.len() < 3 { + panic!("usage: {} USERNAME API_KEY", args[0]); + } + let username = &args[1]; + let api_key = &args[2]; + let db = db::DB::new(&paths::dbpath()) .expect("failed to create db"); + let lastfm = lastfm::LastFMClient::new(api_key, username); + + println!("{}", lastfm.track_count().unwrap()); + + for track in lastfm.tracks().take(10) { + println!("{}", track.name); + } } -- cgit v1.2.3-54-g00ecf