this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

move large struct conversions into common impl

+245 -184
+5 -4
src/main.rs
··· 5 5 use crate::{ 6 6 auth::{AuthMethod, Authenticator, GenericSession}, 7 7 error::OnyxError, 8 - parser::{ParsedArtist, ParsedTrack}, 8 + record::{Artist, Play}, 9 9 scrobble::Scrobbler, 10 10 status::StatusManager, 11 11 }; ··· 20 20 mod auth; 21 21 mod error; 22 22 mod parser; 23 + mod record; 23 24 mod scrobble; 24 25 mod status; 25 26 ··· 296 297 track_discriminant, 297 298 release_discriminant, 298 299 } => { 299 - let artist = artist_name.map(|a| ParsedArtist { 300 + let artist = artist_name.map(|a| Artist { 300 301 artist_name: a, 301 302 artist_mb_id, 302 303 }); ··· 307 308 None 308 309 }; 309 310 310 - let track = ParsedTrack { 311 + let track = Play { 311 312 track_name, 312 313 track_mb_id, 313 314 recording_mb_id, ··· 321 322 track_discriminant, 322 323 release_discriminant, 323 324 music_service_base_domain: None, 324 - client_id: None, 325 + submission_client_agent: None, 325 326 artist_names: None, 326 327 artist_mb_ids: None, 327 328 };
+8 -5
src/parser/audio_scrobbler.rs
··· 5 5 path::PathBuf, 6 6 }; 7 7 8 - use crate::parser::{LogParser, ParsedArtist, ParsedTrack, ParserError}; 8 + use crate::{ 9 + parser::{LogParser, ParserError}, 10 + record::{Artist, Play}, 11 + }; 9 12 10 13 #[derive(Debug)] 11 14 pub struct AudioScrobblerParser { ··· 171 174 } 172 175 173 176 impl LogParser for AudioScrobblerParser { 174 - fn parse(log: PathBuf) -> Result<Vec<ParsedTrack>, ParserError> { 177 + fn parse(log: PathBuf) -> Result<Vec<Play>, ParserError> { 175 178 let file = File::open(log)?; 176 179 let reader = BufReader::new(file); 177 180 let log = Self::parse(reader)?; ··· 193 196 194 197 let mut artists = Vec::new(); 195 198 196 - let artist = ParsedArtist { 199 + let artist = Artist { 197 200 artist_name: entry.artist_name, 198 201 artist_mb_id: None, 199 202 }; 200 203 201 204 artists.push(artist); 202 205 203 - let track = ParsedTrack { 206 + let track = Play { 204 207 track_name: entry.track_name, 205 208 duration: Some(entry.duration), 206 209 played_time: Some(dt), 207 - client_id: log.client_id.clone(), 210 + submission_client_agent: log.client_id.clone(), 208 211 artists: Some(artists), 209 212 release_name: entry.album_name, 210 213 track_mb_id: entry.mb_track_id,
+8
src/parser/log_parser.rs
··· 1 + use std::path::PathBuf; 2 + 3 + use crate::{parser::ParserError, record::Play}; 4 + 5 + pub trait LogParser { 6 + /// Parse the given log file into a list of tracks 7 + fn parse(log: PathBuf) -> Result<Vec<Play>, ParserError>; 8 + }
-29
src/parser/meta.rs
··· 1 - use chrono::{DateTime, FixedOffset}; 2 - 3 - // See teal.fm lexicons for a description of most of these fields 4 - #[derive(Debug)] 5 - pub struct ParsedArtist { 6 - pub artist_name: String, 7 - pub artist_mb_id: Option<String>, 8 - } 9 - 10 - // See teal.fm lexicons for a description of most of these fields 11 - #[derive(Debug)] 12 - pub struct ParsedTrack { 13 - pub track_name: String, 14 - pub track_mb_id: Option<String>, 15 - pub recording_mb_id: Option<String>, 16 - pub duration: Option<i64>, 17 - pub artist_names: Option<Vec<String>>, 18 - pub artist_mb_ids: Option<Vec<String>>, 19 - pub artists: Option<Vec<ParsedArtist>>, 20 - pub release_name: Option<String>, 21 - pub release_mb_id: Option<String>, 22 - pub isrc: Option<String>, 23 - pub origin_url: Option<String>, 24 - pub music_service_base_domain: Option<String>, 25 - pub client_id: Option<String>, 26 - pub played_time: Option<DateTime<FixedOffset>>, 27 - pub track_discriminant: Option<String>, 28 - pub release_discriminant: Option<String>, 29 - }
+2 -4
src/parser/mod.rs
··· 1 1 pub mod audio_scrobbler; 2 2 3 3 mod error; 4 - mod meta; 5 - mod parser; 4 + mod log_parser; 6 5 7 6 pub use error::ParserError; 8 - pub use meta::{ParsedArtist, ParsedTrack}; 9 - pub use parser::LogParser; 7 + pub use log_parser::LogParser;
-8
src/parser/parser.rs
··· 1 - use std::path::PathBuf; 2 - 3 - use crate::parser::{ParsedTrack, ParserError}; 4 - 5 - pub trait LogParser { 6 - /// Parse the given log file into a list of tracks 7 - fn parse(log: PathBuf) -> Result<Vec<ParsedTrack>, ParserError>; 8 - }
+201
src/record.rs
··· 1 + use chrono::{DateTime, FixedOffset}; 2 + use jacquard::{CowStr, smol_str::ToSmolStr, types::string::Datetime}; 3 + 4 + #[derive(Debug)] 5 + pub struct Artist { 6 + pub artist_name: String, 7 + pub artist_mb_id: Option<String>, 8 + } 9 + 10 + #[derive(Debug)] 11 + pub struct Play { 12 + pub track_name: String, 13 + pub track_mb_id: Option<String>, 14 + pub recording_mb_id: Option<String>, 15 + pub duration: Option<i64>, 16 + pub artist_names: Option<Vec<String>>, 17 + pub artist_mb_ids: Option<Vec<String>>, 18 + pub artists: Option<Vec<Artist>>, 19 + pub release_name: Option<String>, 20 + pub release_mb_id: Option<String>, 21 + pub isrc: Option<String>, 22 + pub origin_url: Option<String>, 23 + pub music_service_base_domain: Option<String>, 24 + pub submission_client_agent: Option<String>, 25 + pub played_time: Option<DateTime<FixedOffset>>, 26 + pub track_discriminant: Option<String>, 27 + pub release_discriminant: Option<String>, 28 + } 29 + 30 + #[derive(Debug)] 31 + pub struct PlayView { 32 + pub track_name: String, 33 + pub track_mb_id: Option<String>, 34 + pub recording_mb_id: Option<String>, 35 + pub duration: Option<i64>, 36 + pub artists: Vec<Artist>, 37 + pub release_name: Option<String>, 38 + pub release_mb_id: Option<String>, 39 + pub isrc: Option<String>, 40 + pub origin_url: Option<String>, 41 + pub music_service_base_domain: Option<String>, 42 + pub submission_client_agent: Option<String>, 43 + pub played_time: Option<DateTime<FixedOffset>>, 44 + } 45 + 46 + #[derive(Debug)] 47 + pub struct Status { 48 + pub time: DateTime<FixedOffset>, 49 + pub expiry: Option<DateTime<FixedOffset>>, 50 + pub item: PlayView, 51 + } 52 + 53 + impl From<jacquard_api::fm_teal::alpha::feed::Artist<'_>> for Artist { 54 + fn from(value: jacquard_api::fm_teal::alpha::feed::Artist) -> Self { 55 + Self { 56 + artist_name: value.artist_name.to_string(), 57 + artist_mb_id: value.artist_mb_id.map(|s| s.to_string()), 58 + } 59 + } 60 + } 61 + 62 + impl From<Artist> for jacquard_api::fm_teal::alpha::feed::Artist<'static> { 63 + fn from(value: Artist) -> Self { 64 + Self { 65 + artist_name: CowStr::Owned(value.artist_name.to_smolstr()), 66 + artist_mb_id: value.artist_mb_id.map(|s| CowStr::Owned(s.to_smolstr())), 67 + extra_data: None, 68 + } 69 + } 70 + } 71 + 72 + impl From<jacquard_api::fm_teal::alpha::feed::play::Play<'_>> for Play { 73 + fn from(value: jacquard_api::fm_teal::alpha::feed::play::Play<'_>) -> Self { 74 + Self { 75 + track_name: value.track_name.to_string(), 76 + track_mb_id: value.track_mb_id.map(|s| s.to_string()), 77 + recording_mb_id: value.recording_mb_id.map(|s| s.to_string()), 78 + duration: value.duration, 79 + artist_names: value 80 + .artist_names 81 + .map(|v| v.iter().map(|a| a.to_string()).collect()), 82 + artist_mb_ids: value 83 + .artist_mb_ids 84 + .map(|v| v.iter().map(|a| a.to_string()).collect()), 85 + artists: value 86 + .artists 87 + .map(|v| v.iter().map(|a| a.clone().into()).collect()), 88 + release_name: value.release_name.map(|s| s.to_string()), 89 + release_mb_id: value.release_mb_id.map(|s| s.to_string()), 90 + isrc: value.isrc.map(|s| s.to_string()), 91 + origin_url: value.origin_url.map(|s| s.to_string()), 92 + music_service_base_domain: value.music_service_base_domain.map(|s| s.to_string()), 93 + submission_client_agent: value.submission_client_agent.map(|s| s.to_string()), 94 + played_time: value.played_time.map(|dt| *dt.as_ref()), 95 + track_discriminant: value.track_discriminant.map(|s| s.to_string()), 96 + release_discriminant: value.release_discriminant.map(|s| s.to_string()), 97 + } 98 + } 99 + } 100 + 101 + impl From<Play> for jacquard_api::fm_teal::alpha::feed::play::Play<'static> { 102 + fn from(val: Play) -> Self { 103 + jacquard_api::fm_teal::alpha::feed::play::Play { 104 + track_name: CowStr::Owned(val.track_name.to_smolstr()), 105 + track_mb_id: val.track_mb_id.map(|s| CowStr::Owned(s.to_smolstr())), 106 + recording_mb_id: val.recording_mb_id.map(|s| CowStr::Owned(s.to_smolstr())), 107 + duration: val.duration, 108 + artist_names: val 109 + .artist_names 110 + .map(|v| v.iter().map(|s| CowStr::Owned(s.to_smolstr())).collect()), 111 + artist_mb_ids: val 112 + .artist_mb_ids 113 + .map(|v| v.iter().map(|s| CowStr::Owned(s.to_smolstr())).collect()), 114 + artists: val 115 + .artists 116 + .map(|v| v.into_iter().map(|a| a.into()).collect()), 117 + release_name: val.release_name.map(|s| CowStr::Owned(s.to_smolstr())), 118 + release_mb_id: val.release_mb_id.map(|s| CowStr::Owned(s.to_smolstr())), 119 + isrc: val.isrc.map(|s| CowStr::Owned(s.to_smolstr())), 120 + origin_url: val.origin_url.map(|s| CowStr::Owned(s.to_smolstr())), 121 + music_service_base_domain: val 122 + .music_service_base_domain 123 + .map(|s| CowStr::Owned(s.to_smolstr())), 124 + submission_client_agent: val 125 + .submission_client_agent 126 + .map(|s| CowStr::Owned(s.to_smolstr())), 127 + played_time: val.played_time.map(Datetime::new), 128 + track_discriminant: val 129 + .track_discriminant 130 + .map(|s| CowStr::Owned(s.to_smolstr())), 131 + release_discriminant: val 132 + .release_discriminant 133 + .map(|s| CowStr::Owned(s.to_smolstr())), 134 + extra_data: None, 135 + } 136 + } 137 + } 138 + 139 + impl From<jacquard_api::fm_teal::alpha::feed::PlayView<'_>> for PlayView { 140 + fn from(value: jacquard_api::fm_teal::alpha::feed::PlayView<'_>) -> Self { 141 + Self { 142 + track_name: value.track_name.to_string(), 143 + track_mb_id: value.track_mb_id.map(|s| s.to_string()), 144 + recording_mb_id: value.recording_mb_id.map(|s| s.to_string()), 145 + duration: value.duration, 146 + artists: value.artists.iter().map(|a| a.clone().into()).collect(), 147 + release_name: value.release_name.map(|s| s.to_string()), 148 + release_mb_id: value.release_mb_id.map(|s| s.to_string()), 149 + isrc: value.isrc.map(|s| s.to_string()), 150 + origin_url: value.origin_url.map(|s| s.to_string()), 151 + music_service_base_domain: value.music_service_base_domain.map(|s| s.to_string()), 152 + submission_client_agent: value.submission_client_agent.map(|s| s.to_string()), 153 + played_time: value.played_time.map(|dt| *dt.as_ref()), 154 + } 155 + } 156 + } 157 + 158 + impl From<PlayView> for jacquard_api::fm_teal::alpha::feed::PlayView<'static> { 159 + fn from(val: PlayView) -> Self { 160 + jacquard_api::fm_teal::alpha::feed::PlayView { 161 + track_name: CowStr::Owned(val.track_name.to_smolstr()), 162 + track_mb_id: val.track_mb_id.map(|s| CowStr::Owned(s.to_smolstr())), 163 + recording_mb_id: val.recording_mb_id.map(|s| CowStr::Owned(s.to_smolstr())), 164 + duration: val.duration, 165 + artists: val.artists.into_iter().map(|a| a.into()).collect(), 166 + release_name: val.release_name.map(|s| CowStr::Owned(s.to_smolstr())), 167 + release_mb_id: val.release_mb_id.map(|s| CowStr::Owned(s.to_smolstr())), 168 + isrc: val.isrc.map(|s| CowStr::Owned(s.to_smolstr())), 169 + origin_url: val.origin_url.map(|s| CowStr::Owned(s.to_smolstr())), 170 + music_service_base_domain: val 171 + .music_service_base_domain 172 + .map(|s| CowStr::Owned(s.to_smolstr())), 173 + submission_client_agent: val 174 + .submission_client_agent 175 + .map(|s| CowStr::Owned(s.to_smolstr())), 176 + played_time: val.played_time.map(Datetime::new), 177 + extra_data: None, 178 + } 179 + } 180 + } 181 + 182 + impl From<jacquard_api::fm_teal::alpha::actor::status::Status<'_>> for Status { 183 + fn from(value: jacquard_api::fm_teal::alpha::actor::status::Status<'_>) -> Self { 184 + Self { 185 + time: *value.time.as_ref(), 186 + expiry: value.expiry.map(|dt| *dt.as_ref()), 187 + item: value.item.into(), 188 + } 189 + } 190 + } 191 + 192 + impl From<Status> for jacquard_api::fm_teal::alpha::actor::status::Status<'static> { 193 + fn from(val: Status) -> Self { 194 + jacquard_api::fm_teal::alpha::actor::status::Status { 195 + time: Datetime::new(val.time), 196 + expiry: val.expiry.map(Datetime::new), 197 + item: val.item.into(), 198 + extra_data: None, 199 + } 200 + } 201 + }
+7 -63
src/scrobble.rs
··· 1 1 use std::path::PathBuf; 2 2 3 3 use jacquard::client::{Agent, AgentSessionExt}; 4 - use jacquard::smol_str::ToSmolStr; 5 - use jacquard::{CowStr, types::string::Datetime}; 6 - use jacquard_api::fm_teal::alpha::feed::{Artist, play::Play}; 4 + use jacquard_api::fm_teal::alpha::feed as fm_teal_feed; 7 5 use owo_colors::OwoColorize; 8 6 9 7 use crate::{ 10 8 LogFormat, 11 9 auth::GenericSession, 12 10 error::OnyxError, 13 - parser::{LogParser, ParsedArtist, ParsedTrack, audio_scrobbler::AudioScrobblerParser}, 11 + parser::{LogParser, audio_scrobbler::AudioScrobblerParser}, 12 + record::Play, 14 13 }; 15 14 16 15 pub struct Scrobbler { ··· 37 36 } 38 37 } 39 38 40 - fn generate_artist(&self, artist: ParsedArtist) -> Artist<'_> { 41 - Artist { 42 - artist_name: CowStr::Owned(artist.artist_name.to_smolstr()), 43 - artist_mb_id: artist.artist_mb_id.map(|s| CowStr::Owned(s.to_smolstr())), 44 - extra_data: None, 45 - } 46 - } 47 - 48 - fn generate_play(&self, track: ParsedTrack) -> Play<'_> { 49 - let artist_names: Option<Vec<CowStr>> = track.artist_names.map(|v| { 50 - v.into_iter() 51 - .map(|s| CowStr::Owned(s.to_smolstr())) 52 - .collect() 53 - }); 54 - 55 - let artist_mb_ids: Option<Vec<CowStr>> = track.artist_mb_ids.map(|v| { 56 - v.into_iter() 57 - .map(|s| CowStr::Owned(s.to_smolstr())) 58 - .collect() 59 - }); 60 - 61 - let artists: Option<Vec<Artist>> = track 62 - .artists 63 - .map(|v| v.into_iter().map(|a| self.generate_artist(a)).collect()); 64 - 65 - Play { 66 - track_name: CowStr::Owned(track.track_name.to_smolstr()), 67 - track_mb_id: track.track_mb_id.map(|s| CowStr::Owned(s.to_smolstr())), 68 - recording_mb_id: track.recording_mb_id.map(|s| CowStr::Owned(s.to_smolstr())), 69 - duration: track.duration, 70 - artist_names, 71 - artist_mb_ids, 72 - artists, 73 - release_name: track.release_name.map(|s| CowStr::Owned(s.to_smolstr())), 74 - release_mb_id: track.release_mb_id.map(|s| CowStr::Owned(s.to_smolstr())), 75 - isrc: track.isrc.map(|s| CowStr::Owned(s.to_smolstr())), 76 - origin_url: track.origin_url.map(|s| CowStr::Owned(s.to_smolstr())), 77 - music_service_base_domain: Some( 78 - track 79 - .music_service_base_domain 80 - .map(|s| CowStr::Owned(s.to_smolstr())) 81 - .unwrap_or(CowStr::Owned("local".to_smolstr())), 82 - ), 83 - submission_client_agent: Some(CowStr::Owned( 84 - self.generate_client_agent(track.client_id).to_smolstr(), 85 - )), 86 - played_time: track.played_time.map(Datetime::new), 87 - track_discriminant: track 88 - .track_discriminant 89 - .map(|s| CowStr::Owned(s.to_smolstr())), 90 - release_discriminant: track 91 - .release_discriminant 92 - .map(|s| CowStr::Owned(s.to_smolstr())), 93 - extra_data: None, 94 - } 95 - } 96 - 97 - pub async fn scrobble_track(&self, track: ParsedTrack) -> Result<(), OnyxError> { 39 + pub async fn scrobble_track(&self, mut track: Play) -> Result<(), OnyxError> { 98 40 let name = track.track_name.clone(); 99 41 100 42 let res = async { 101 - let play = self.generate_play(track); 43 + track.submission_client_agent = 44 + Some(self.generate_client_agent(track.submission_client_agent)); 45 + let play: fm_teal_feed::play::Play = track.into(); 102 46 self.agent.create_record(play, None).await 103 47 } 104 48 .await;
+14 -71
src/status.rs
··· 1 - use chrono::{DateTime, FixedOffset}; 2 1 use jacquard::{ 3 2 client::{AgentSessionExt, BasicClient}, 4 3 prelude::IdentityResolver, ··· 6 5 }; 7 6 use jacquard_api::fm_teal::alpha::actor::status as fm_teal_status; 8 7 use jacquard_identity::{JacquardResolver, PublicResolver}; 9 - use owo_colors::OwoColorize; 10 8 11 - use crate::error::OnyxError; 12 - 13 - #[derive(Debug)] 14 - pub struct ArtistStatus { 15 - pub artist_name: String, 16 - pub artist_mb_id: Option<String>, 17 - } 18 - 19 - #[derive(Debug)] 20 - pub struct TrackStatus { 21 - pub time: DateTime<FixedOffset>, 22 - pub expiry: Option<DateTime<FixedOffset>>, 23 - pub track_name: String, 24 - pub track_mb_id: Option<String>, 25 - pub recording_mb_id: Option<String>, 26 - pub duration: Option<i64>, 27 - pub artists: Vec<ArtistStatus>, 28 - pub release_name: Option<String>, 29 - pub release_mb_id: Option<String>, 30 - pub isrc: Option<String>, 31 - pub origin_url: Option<String>, 32 - pub music_service_base_domain: Option<String>, 33 - pub client_id: Option<String>, 34 - pub played_time: Option<DateTime<FixedOffset>>, 35 - } 9 + use crate::{error::OnyxError, record::Status}; 36 10 37 11 fn get_status_endpoint(did: String) -> String { 38 12 format!("at://{}/fm.teal.alpha.actor.status/self", did) ··· 62 36 Ok(did) 63 37 } 64 38 65 - pub async fn get_status(&self) -> Result<TrackStatus, OnyxError> { 39 + pub async fn get_status(&self) -> Result<Status, OnyxError> { 66 40 let did = self.resolve_did(&self.ident).await?; 67 41 68 42 let endpoint = get_status_endpoint(did.to_string()); ··· 79 53 .map_err(|e| OnyxError::Other(e.to_string().into()))? 80 54 .value; 81 55 82 - let artists: Vec<ArtistStatus> = status_rec 83 - .item 84 - .artists 85 - .iter() 86 - .map(|a| ArtistStatus { 87 - artist_name: a.artist_name.to_string(), 88 - artist_mb_id: a.artist_mb_id.clone().map(|s| s.to_string()), 89 - }) 90 - .collect(); 91 - 92 - Ok(TrackStatus { 93 - time: *status_rec.time.as_ref(), 94 - expiry: status_rec.expiry.map(|t| *t.as_ref()), 95 - track_name: status_rec.item.track_name.to_string(), 96 - track_mb_id: status_rec.item.track_mb_id.map(|s| s.to_string()), 97 - recording_mb_id: status_rec.item.recording_mb_id.map(|s| s.to_string()), 98 - duration: status_rec.item.duration, 99 - artists, 100 - release_name: status_rec.item.release_name.map(|s| s.to_string()), 101 - release_mb_id: status_rec.item.release_mb_id.map(|s| s.to_string()), 102 - isrc: status_rec.item.isrc.map(|s| s.to_string()), 103 - origin_url: status_rec.item.origin_url.map(|s| s.to_string()), 104 - music_service_base_domain: status_rec 105 - .item 106 - .music_service_base_domain 107 - .map(|s| s.to_string()), 108 - client_id: status_rec 109 - .item 110 - .submission_client_agent 111 - .map(|s| s.to_string()), 112 - played_time: status_rec.item.played_time.map(|t| *t.as_ref()), 113 - }) 56 + Ok(status_rec.into()) 114 57 } 115 58 116 - pub fn display_status(&self, status: &TrackStatus, raw: bool, full: bool) { 59 + pub fn display_status(&self, status: &Status, raw: bool, full: bool) { 117 60 // if both track name and artists are blank, probably nothing's playing 118 - if status.track_name.is_empty() && status.artists.is_empty() && !raw { 61 + if status.item.track_name.is_empty() && status.item.artists.is_empty() && !raw { 119 62 println!("nothing playing right now"); 120 63 return; 121 64 } 122 65 123 - println!("track: {}", status.track_name); 66 + println!("track: {}", status.item.track_name); 124 67 125 - if !status.artists.is_empty() || raw { 68 + if !status.item.artists.is_empty() || raw { 126 69 print!("artists: "); 127 70 128 - for i in 0..status.artists.len() { 129 - print!("{}", status.artists[i].artist_name); 71 + for i in 0..status.item.artists.len() { 72 + print!("{}", status.item.artists[i].artist_name); 130 73 131 - if i != status.artists.len() - 1 { 74 + if i != status.item.artists.len() - 1 { 132 75 print!(", "); 133 76 } 134 77 } ··· 136 79 println!(); 137 80 } 138 81 139 - if let Some(release) = &status.release_name { 82 + if let Some(release) = &status.item.release_name { 140 83 println!("release: {}", release); 141 84 } 142 85 143 - if let Some(played_time) = &status.played_time { 86 + if let Some(played_time) = &status.item.played_time { 144 87 if raw { 145 88 println!("played: {}", played_time.format("%Y-%m-%d %H:%M:%S %:z")); 146 89 } else { ··· 149 92 } 150 93 } 151 94 152 - if let Some(duration) = status.duration { 95 + if let Some(duration) = status.item.duration { 153 96 if raw { 154 97 println!("duration: {}", duration); 155 98 } else { ··· 172 115 } 173 116 } 174 117 175 - if let Some(client) = &status.client_id 118 + if let Some(client) = &status.item.submission_client_agent 176 119 && full 177 120 { 178 121 println!("client: {}", client);