summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2018-11-06 01:50:59 -0500
committerJesse Luehrs <doy@tozt.net>2018-11-06 01:50:59 -0500
commita7cceb1aa5afb13de9f51c10670757d39d50e2a3 (patch)
tree826cec321541a71aa5c5b784672772b40f491412 /src
parent93f6e7083af1db40b16db049b62025f8cdcbbc5a (diff)
downloadlastfm-query-a7cceb1aa5afb13de9f51c10670757d39d50e2a3.tar.gz
lastfm-query-a7cceb1aa5afb13de9f51c10670757d39d50e2a3.zip
add some basic last.fm api querying
Diffstat (limited to 'src')
-rw-r--r--src/lastfm/api_types.rs44
-rw-r--r--src/lastfm/mod.rs120
-rw-r--r--src/main.rs20
3 files changed, 184 insertions, 0 deletions
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<track>,
+ #[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<Track>,
+}
+
+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::<Result<Vec<Track>>>()?;
+ 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<Track> {
+ 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<u64> {
+ 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);
+ }
}