+34
-7
Diff
round #0
+2
-2
Cargo.toml
+2
-2
Cargo.toml
···
4
4
edition = "2024"
5
5
6
6
[dependencies]
7
-
chrono = "0.4.43"
7
+
chrono = { version = "0.4.43", features = ["serde"] }
8
8
clap = { version = "4.5.54", features = ["derive"] }
9
9
dirs = "6.0.0"
10
10
http = "1.4.0"
···
14
14
jacquard-oauth = { version = "0.9.6", features = ["browser-open", "loopback"] }
15
15
keyring = { version = "3.6.3", features = ["linux-native-sync-persistent", "apple-native", "windows-native", "crypto-rust", "vendored"] }
16
16
owo-colors = "4.2.3"
17
-
serde = "1.0.228"
17
+
serde = { version = "1.0.228", features = ["derive"] }
18
18
serde_json = "1.0.149"
19
19
thiserror = "2.0.18"
20
20
tokio = { version = "1.49.0", features = ["macros", "rt", "rt-multi-thread"] }
+1
flake.nix
+1
flake.nix
+26
-1
src/main.rs
+26
-1
src/main.rs
···
1
1
use owo_colors::OwoColorize;
2
2
use serde::{Deserialize, Serialize};
3
-
use std::path::PathBuf;
3
+
use std::{io::BufRead, path::PathBuf};
4
4
5
5
use crate::{
6
6
auth::{AuthMethod, Authenticator, GenericSession},
···
163
163
#[arg(short, long, action)]
164
164
delete: bool,
165
165
},
166
+
167
+
/// Scrobble tracks interactively from standard input
168
+
Interactive,
166
169
}
167
170
168
171
#[derive(Debug, Clone, ValueEnum)]
···
447
450
);
448
451
}
449
452
}
453
+
ScrobbleCommands::Interactive => {
454
+
let version = generate_client_version();
455
+
let session = get_session().await?;
456
+
let scrobbler = Scrobbler::new("onyx", &version, session);
457
+
458
+
println!("{}", "waiting for tracks...".dimmed());
459
+
460
+
let stdin = std::io::stdin();
461
+
let reader = std::io::BufReader::new(stdin);
462
+
463
+
for msg in reader.lines() {
464
+
let msg = msg?;
465
+
466
+
if msg.trim().is_empty() {
467
+
// skip empty messages
468
+
continue;
469
+
}
470
+
471
+
let msg: record::Play = serde_json::from_str(&msg)?;
472
+
scrobbler.scrobble_track(msg).await?;
473
+
}
474
+
}
450
475
},
451
476
Commands::Status { command } => match command {
452
477
StatusCommands::Show { handle, raw, full } => {
+5
-4
src/record.rs
+5
-4
src/record.rs
···
1
1
use chrono::{DateTime, FixedOffset};
2
2
use jacquard::{CowStr, smol_str::ToSmolStr, types::string::Datetime};
3
+
use serde::{Deserialize, Serialize};
3
4
4
-
#[derive(Debug, Default)]
5
+
#[derive(Debug, Default, Serialize, Deserialize)]
5
6
pub struct Artist {
6
7
pub artist_name: String,
7
8
pub artist_mb_id: Option<String>,
8
9
}
9
10
10
-
#[derive(Debug, Default)]
11
+
#[derive(Debug, Default, Serialize, Deserialize)]
11
12
pub struct Play {
12
13
pub track_name: String,
13
14
pub track_mb_id: Option<String>,
···
27
28
pub release_discriminant: Option<String>,
28
29
}
29
30
30
-
#[derive(Debug, Default)]
31
+
#[derive(Debug, Default, Serialize, Deserialize)]
31
32
pub struct PlayView {
32
33
pub track_name: String,
33
34
pub track_mb_id: Option<String>,
···
43
44
pub played_time: Option<DateTime<FixedOffset>>,
44
45
}
45
46
46
-
#[derive(Debug, Default)]
47
+
#[derive(Debug, Default, Serialize, Deserialize)]
47
48
pub struct Status {
48
49
pub time: DateTime<FixedOffset>,
49
50
pub expiry: Option<DateTime<FixedOffset>>,