listenbrainz_rust/
lib.rs

1use regex::Regex;
2use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_TYPE};
3use reqwest::StatusCode;
4use serde::Serialize;
5use std::error::Error;
6use std::time::SystemTime;
7
8mod constants;
9
10pub struct Listen<'a> {
11    pub artist: &'a str,
12    pub track: &'a str,
13    pub album: &'a str,
14}
15
16impl Listen<'_> {
17    pub fn single(&self, uuid_user: &str) -> Result<StatusCode, Box<dyn Error>> {
18        let time = SystemTime::now()
19            .duration_since(SystemTime::UNIX_EPOCH)
20            .unwrap()
21            .as_secs();
22        let song = Submission {
23            listen_type: ListenType::Single,
24            payload: vec![Payload {
25                listened_at: time,
26                track_metadata: TrackMetadata {
27                    artist_name: self.artist,
28                    track_name: self.track,
29                    release_name: self.album,
30                },
31            }],
32        };
33        match submit_single(uuid_user, song) {
34            Ok(status_code) => Ok(status_code),
35            Err(e) => Err(e),
36        }
37    }
38    pub fn playing_now(&self, uuid_user: &str) -> Result<StatusCode, Box<dyn Error>> {
39        let song = Submission {
40            listen_type: ListenType::PlayingNow,
41            payload: vec![Payload {
42                listened_at: 0,
43                track_metadata: TrackMetadata {
44                    artist_name: self.artist,
45                    track_name: self.track,
46                    release_name: self.album,
47                },
48            }],
49        };
50        match submit_playing_now(uuid_user, song) {
51            Ok(status_code) => Ok(status_code),
52            Err(e) => Err(e),
53        }
54    }
55}
56#[derive(Serialize, Default)]
57pub struct Submission<'a> {
58    pub listen_type: ListenType,
59    pub payload: Vec<Payload<'a>>,
60}
61
62#[derive(Serialize, PartialEq)]
63pub enum ListenType {
64    #[serde(rename(serialize = "single"))]
65    Single,
66    #[serde(rename(serialize = "playing_now"))]
67    PlayingNow,
68    #[serde(rename(serialize = "import"))]
69    Import,
70}
71
72impl Default for ListenType {
73    fn default() -> Self {
74        ListenType::Single
75    }
76}
77
78#[derive(Serialize, Default)]
79pub struct Payload<'a> {
80    pub listened_at: u64,
81    pub track_metadata: TrackMetadata<'a>,
82}
83
84#[derive(Serialize, Default)]
85pub struct TrackMetadata<'a> {
86    pub artist_name: &'a str,
87    pub track_name: &'a str,
88    pub release_name: &'a str,
89}
90
91pub fn submit_import(user_uuid: &str, sub: Submission) -> Result<StatusCode, Box<dyn Error>> {
92    if sub.listen_type == ListenType::Import {
93        let mut headers = HeaderMap::new();
94        let mut auth = String::from("Token ");
95        auth.push_str(user_uuid);
96        headers.insert(AUTHORIZATION, HeaderValue::from_str(auth.as_str()).unwrap());
97        headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
98        let j = serde_json::to_string(&sub).unwrap();
99        let res = reqwest::Client::new()
100            .post((constants::API_ROOT_URL.to_string() + "/1/submit-listens").as_str())
101            .headers(headers)
102            .body(j)
103            .send()?;
104        return Ok(res.status());
105    } else {
106        return Err("error submitting import".into());
107    }
108}
109
110pub fn submit_playing_now(user_uuid: &str, sub: Submission) -> Result<StatusCode, Box<dyn Error>> {
111    if sub.listen_type == ListenType::PlayingNow && sub.payload.len() == 1 {
112        let mut headers = HeaderMap::new();
113        let mut auth = String::from("Token ");
114        auth.push_str(user_uuid);
115        headers.insert(AUTHORIZATION, HeaderValue::from_str(auth.as_str()).unwrap());
116        headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
117        let j = serde_json::to_string(&sub).unwrap();
118        let re = Regex::new(r#""listened_at":\d+,"#).unwrap();
119        let result = re.replace_all(j.as_str(), "");
120        let res = reqwest::Client::new()
121            .post((constants::API_ROOT_URL.to_string() + "/1/submit-listens").as_str())
122            .headers(headers)
123            .body(format!("{}", result))
124            .send()?;
125        return Ok(res.status());
126    } else {
127        return Err("error submitting playing_now: payload.len() must be 1".into());
128    }
129}
130
131pub fn submit_single(user_uuid: &str, sub: Submission) -> Result<StatusCode, Box<dyn Error>> {
132    if sub.listen_type == ListenType::Single
133        && sub.payload.len() == 1
134        && sub.payload.first().unwrap().listened_at != 0
135    {
136        let mut headers = HeaderMap::new();
137        let mut auth = String::from("Token ");
138        auth.push_str(user_uuid);
139        headers.insert(AUTHORIZATION, HeaderValue::from_str(auth.as_str()).unwrap());
140        headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
141        let j = serde_json::to_string(&sub).unwrap();
142        let res = reqwest::Client::new()
143            .post((constants::API_ROOT_URL.to_string() + "/1/submit-listens").as_str())
144            .headers(headers)
145            .body(j)
146            .send()?;
147        return Ok(res.status());
148    } else {
149        return Err(
150            "error submitting single: payload.len() must be 1 and listening_at field must be 0"
151                .into(),
152        );
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    #[test]
159    fn it_works() {
160        assert_eq!(2 + 2, 4);
161    }
162}