diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..568052c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,157 @@ +# AGENTS.md — Coding Agent Guidelines + +## Project Overview + +**scrbblr** is a Rust CLI tool that scrobbles music from MPRIS-compatible +players and MPD on Linux. It stores data in SQLite, generates terminal/JSON/HTML +reports, and enriches album metadata from MusicBrainz/Cover Art Archive. + +Single binary, no async, flat module structure. Rust 2024 edition. + +## Build / Test / Lint + +```bash +cargo build # debug build +cargo build --release # release build +cargo test # run all tests (currently 63) +cargo test # single test, e.g. cargo test test_top_artists +cargo test --lib db::tests # all tests in one module +cargo test -- --nocapture # show println/eprintln output +cargo fmt # format (default rustfmt, no config file) +cargo clippy # lint (no clippy.toml) +``` + +Always run `cargo fmt`, `cargo test`, and `cargo build` before committing. +There is no CI pipeline — local verification is the only gate. + +## Module Structure + +All source lives in `src/`. Flat layout, no nested modules. + +| File | Role | +|--------------|-------------------------------------------------------------| +| `main.rs` | CLI (clap derive), subcommand dispatch, process management | +| `db.rs` | SQLite schema, all queries, data structs | +| `watcher.rs` | playerctl event parsing, ScrobbleTracker state machine | +| `report.rs` | Terminal tables, JSON output, HTML report + CSS generation | +| `enrich.rs` | MusicBrainz/CAA API, cover downloads, genre extraction | + +Database access goes through `db.rs` public functions only — no raw SQL elsewhere. + +## Language & Spelling + +Use **British English** everywhere: comments, docs, README, variable names. + +- `normalise` not `normalize` +- `behaviour` not `behavior` +- `licence` not `license` +- `colour` not `color` (in prose; CSS property names stay American per spec) + +## Code Style + +### Formatting + +Default `rustfmt` (no `rustfmt.toml`). 4-space indent, trailing commas in +multi-line expressions. Run `cargo fmt` before every commit. + +### Naming + +- Functions: `snake_case` — `open_memory_db`, `parse_metadata_line` +- Types/Structs: `PascalCase` — `ScrobbleTracker`, `TopArtist` +- Enums: `PascalCase` type + variants — `Event::Metadata`, `PlayerStatus::Playing` +- Constants: `SCREAMING_SNAKE_CASE` — `DEFAULT_PLAYER`, `RATE_LIMIT_DELAY` +- Tests: `test_` prefix — `test_top_artists`, `test_schema_creation` + +### Imports + +No strict grouping enforced. General pattern: `crate::` imports, external crates, +then `std`. Let `cargo fmt` handle ordering within groups. + +```rust +use crate::db; +use rusqlite::Connection; +use serde::Serialize; +use std::fmt::Write as _; +``` + +### Section Separators + +Use box-drawing comment blocks between major sections: + +```rust +// --------------------------------------------------------------------------- +// Section Name +// --------------------------------------------------------------------------- +``` + +### Documentation + +- Module-level `//!` doc comments at top of every `.rs` file. +- `///` doc comments on all public functions and important private functions. +- Inline `//` comments explaining "why" not just "what". +- Comments should be thorough — this codebase favours extensive documentation. + +## Error Handling + +**Layered approach — no panics in production code paths:** + +1. **`Result` + `?`** in library functions (all `db.rs` queries, `gather_report`, etc.) +2. **`eprintln!` + `std::process::exit(1)`** for fatal CLI errors in `main.rs` +3. **`.expect("descriptive message")`** only for truly unrecoverable cases + (missing `HOME`, failed dir creation) +4. **`.unwrap()` only in test code** — never in production paths +5. **`match` on `Result`** with `eprintln!` for non-fatal errors (failed cache writes, etc.) + +All user-facing progress/error output goes to **stderr** via `eprintln!`. +Only report content (tables, JSON, HTML) goes to stdout. + +Logging prefixes: `[scrobbled]`, `[error]`, `[warn]`, or unprefixed for progress. + +## Rust 2024 Edition Features + +The codebase uses edition 2024 features freely: + +- Let chains: `if let Some(x) = foo && condition { ... }` +- `is_none_or()` method +- Other recently stabilised APIs + +## Test Patterns + +Every source file has `#[cfg(test)] mod tests` at the bottom. + +### Key test helpers + +- `db::open_memory_db()` — in-memory SQLite with schema. Used by all modules. +- `db::tests::seed_db(conn)` — populates 5 test scrobbles across two artists/days. +- `watcher::TestableTracker` — simulated clock (`advance_time(secs)`) + scrobble + collection for unit-testing the state machine without real time. + +### Test conventions + +- Each test creates its own `open_memory_db()` — no shared state. +- `.unwrap()` is fine in tests (panic = test failure). +- Use `.into()` for string literals in test data: `artist: "Deftones".into()`. +- Descriptive names: `test_top_genres_merges_hyphen_and_space_variants`. + +## Key Architectural Notes + +- **No async** — uses `reqwest::blocking` and `std::thread`. +- **No logging crate** — direct `eprintln!` calls throughout. +- **No template engine** — HTML built via custom `HtmlWriter` helper in `report.rs`. +- **No unsafe code** except one `libc::ioctl` for terminal width (`#[cfg(unix)]`). +- **XDG paths** — DB at `$XDG_DATA_HOME/scrbblr/scrobbles.db`, covers in + `covers/` subdirectory. State marker for publish in `$XDG_STATE_HOME`. +- MusicBrainz rate limiting: 1-second delay between API calls (`RATE_LIMIT_DELAY`). +- HTML report is fully self-contained (relative `covers/` paths, no external JS). +- Font loaded from Google Fonts CDN with monospace fallback chain for offline use. + +## Files Outside src/ + +| File | Purpose | +|-------------------------------|--------------------------------------------| +| `install.sh` | Interactive installer (binary + service) | +| `uninstall.sh` | Interactive uninstaller | +| `scrbblr-publish.sh` | Incremental report publish (rsync) | +| `contrib/examples/publish.conf.example` | Config template for publish | +| `contrib/systemd/user/` | systemd user service unit | +| `FUTURE.md` | Parking lot for future feature ideas | diff --git a/Cargo.lock b/Cargo.lock index 616eda4..1ec6887 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1273,21 +1273,6 @@ dependencies = [ "pxfm", ] -[[package]] -name = "mpris-scrobbler" -version = "0.1.0" -dependencies = [ - "chrono", - "clap", - "ctrlc", - "image", - "libc", - "reqwest", - "rusqlite", - "serde", - "serde_json", -] - [[package]] name = "native-tls" version = "0.2.18" @@ -1848,6 +1833,21 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "scrbblr" +version = "0.1.0" +dependencies = [ + "chrono", + "clap", + "ctrlc", + "image", + "libc", + "reqwest", + "rusqlite", + "serde", + "serde_json", +] + [[package]] name = "security-framework" version = "3.7.0" diff --git a/Cargo.toml b/Cargo.toml index 1e0bb92..7ed5d2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mpris-scrobbler" +name = "scrbblr" version = "0.1.0" edition = "2024" diff --git a/README.md b/README.md index 17d5aa9..9243344 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# mpris-scrobbler +# scrbblr A local music scrobbler for MPRIS-compatible players, built in Rust. It tracks what you listen to via `playerctl`, stores scrobble data in a local SQLite database, and generates listening reports. @@ -42,13 +42,13 @@ Build and install the binary into `~/.local/bin`: ```bash cargo build --release -install -Dm755 target/release/mpris-scrobbler ~/.local/bin/mpris-scrobbler +install -Dm755 target/release/scrbblr ~/.local/bin/scrbblr ``` Make sure `~/.local/bin` is in your `PATH`: ```bash -command -v mpris-scrobbler +command -v scrbblr ``` If that prints nothing, add this to your shell profile (`~/.bashrc`, `~/.zshrc`, etc.): @@ -60,7 +60,7 @@ export PATH="$HOME/.local/bin:$PATH" Then open a new shell and verify: ```bash -mpris-scrobbler --help +scrbblr --help ``` ## Autostart (recommended) @@ -73,7 +73,7 @@ Copy the provided unit file from the repo: ```bash mkdir -p ~/.config/systemd/user -cp contrib/systemd/user/mpris-scrobbler.service ~/.config/systemd/user/ +cp contrib/systemd/user/scrbblr.service ~/.config/systemd/user/ ``` If needed, edit the player in the service (`--player com.blitzfc.qbz`). @@ -82,14 +82,14 @@ If needed, edit the player in the service (`--player com.blitzfc.qbz`). ```bash systemctl --user daemon-reload -systemctl --user enable --now mpris-scrobbler.service +systemctl --user enable --now scrbblr.service ``` ### 3) Verify ```bash -systemctl --user status mpris-scrobbler.service -journalctl --user -u mpris-scrobbler.service -f +systemctl --user status scrbblr.service +journalctl --user -u scrbblr.service -f ``` ### Optional: keep running without active login @@ -112,7 +112,7 @@ Recommended workflow: - Generate HTML reports manually whenever you want: ```bash -mpris-scrobbler report --html --output ~/music-report +scrbblr report --html --output ~/music-report ``` This creates a self-contained directory: @@ -134,7 +134,7 @@ When `--html` is used, the tool automatically runs enrichment first (for missing cargo build --release ``` -The binary will be at `target/release/mpris-scrobbler`. +The binary will be at `target/release/scrbblr`. ## Usage @@ -144,16 +144,16 @@ Start the watcher to begin recording what you listen to: ```bash # Default player (com.blitzfc.qbz) -mpris-scrobbler watch +scrbblr watch # Specify a different player -mpris-scrobbler watch --player spotify +scrbblr watch --player spotify ``` The watcher runs in the foreground and logs scrobbles to stderr: ``` -Database: /home/user/.local/share/mpris-scrobbler/scrobbles.db +Database: /home/user/.local/share/scrbblr/scrobbles.db Watching player: com.blitzfc.qbz [scrobbled] ††† (Crosses) - This Is a Trick (186s) [scrobbled] ††† (Crosses) - Telepathy (200s) @@ -179,25 +179,25 @@ The HTML head loads JetBrains Mono from Google Fonts and falls back to local mon ```bash # All-time summary with top artists, albums, genres, tracks -mpris-scrobbler report +scrbblr report # Filter by period -mpris-scrobbler report --period today -mpris-scrobbler report --period week -mpris-scrobbler report --period month -mpris-scrobbler report --period year +scrbblr report --period today +scrbblr report --period week +scrbblr report --period month +scrbblr report --period year # JSON output -mpris-scrobbler report --json +scrbblr report --json # HTML output to stdout (no covers) -mpris-scrobbler report --html +scrbblr report --html # HTML output to directory (with covers) -mpris-scrobbler report --html --output ~/music-report +scrbblr report --html --output ~/music-report # Change the number of entries in top-N lists (default: 20) -mpris-scrobbler report --limit 20 +scrbblr report --limit 20 ``` Example terminal output: @@ -227,11 +227,11 @@ Top Artists ### Options ``` -mpris-scrobbler watch [OPTIONS] +scrbblr watch [OPTIONS] --player Player name for playerctl [default: com.blitzfc.qbz] --db-path Path to the SQLite database -mpris-scrobbler report [OPTIONS] +scrbblr report [OPTIONS] --period today, week, month, year, all [default: all] --json Output as JSON --html Output as standalone HTML @@ -240,11 +240,11 @@ mpris-scrobbler report [OPTIONS] --all-time-limit Override all-time top-N limit [default: 2.5x --limit] --db-path Path to the SQLite database -mpris-scrobbler enrich [OPTIONS] +scrbblr enrich [OPTIONS] --force Re-fetch metadata for all albums --db-path Path to the SQLite database -mpris-scrobbler last-scrobble [OPTIONS] +scrbblr last-scrobble [OPTIONS] --db-path Path to the SQLite database ``` @@ -253,7 +253,7 @@ mpris-scrobbler last-scrobble [OPTIONS] Enrichment is automatic for `report --html`, but you can still run it manually if you want to prefetch metadata: ```bash -mpris-scrobbler enrich +scrbblr enrich ``` When MusicBrainz matching is tricky, enrichment now retries with normalised @@ -275,7 +275,7 @@ re-tried after 7 days, not on every report run. Use force mode when you want immediate backfill/refresh for everything: ```bash -mpris-scrobbler enrich --force +scrbblr enrich --force ``` Genre normalisation notes: @@ -289,7 +289,7 @@ Genre normalisation notes: Downloaded covers are stored in: -`~/.local/share/mpris-scrobbler/covers/` +`~/.local/share/scrbblr/covers/` #### Manually pinning an album (`pin-album`) @@ -311,7 +311,7 @@ When that happens you can pin the correct MusicBrainz release manually: `[N/M] Artist - Album` line) and run: ```bash -mpris-scrobbler pin-album \ +scrbblr pin-album \ --artist "Coro della Radiotelevisione Svizzera" \ --album "Vivaldi: Gloria; Nisi Dominus; Nulla in mundo pax" \ --mbid "f2ff907a-0355-451b-9c68-f0b7c09bb145" @@ -325,7 +325,7 @@ If the Cover Art Archive has no image for the release (the command prints "No cover art available"), supply one with `--cover-url`: ```bash -mpris-scrobbler pin-album \ +scrbblr pin-album \ --artist "Coro della Radiotelevisione Svizzera" \ --album "Vivaldi: Gloria; Nisi Dominus; Nulla in mundo pax" \ --mbid "f2ff907a-0355-451b-9c68-f0b7c09bb145" \ @@ -341,17 +341,17 @@ Bandcamp, etc. If you publish the report to a remote host, use the included helper script: ```bash -./mpris-scrobbler-publish.sh +./scrbblr-publish.sh ``` It runs `report --html` and `rsync` only when a newer scrobble exists. The script tracks the last published scrobble timestamp in: -`$XDG_STATE_HOME/mpris-scrobbler/last-published-scrobble.txt` +`$XDG_STATE_HOME/scrbblr/last-published-scrobble.txt` Set defaults in a config file so you can run the script without passing flags: -`~/.config/mpris-scrobbler/publish.conf` +`~/.config/scrbblr/publish.conf` If you use `./install.sh`, an example config is installed there automatically when the file does not already exist. @@ -366,31 +366,31 @@ DB_PATH="" Legacy fallback is also supported: -`~/.mpris-scrobbler-publish.conf` +`~/.scrbblr-publish.conf` Flags: ```bash -./mpris-scrobbler-publish.sh --output ~/music-report --remote user@host:/var/www/music-report -./mpris-scrobbler-publish.sh --db-path /custom/path/scrobbles.db +./scrbblr-publish.sh --output ~/music-report --remote user@host:/var/www/music-report +./scrbblr-publish.sh --db-path /custom/path/scrobbles.db # Keep running and check every 5 minutes (default interval): -./mpris-scrobbler-publish.sh --watch +./scrbblr-publish.sh --watch # Custom interval (seconds): -./mpris-scrobbler-publish.sh --watch --interval 600 +./scrbblr-publish.sh --watch --interval 600 # Force regeneration even when no new scrobbles exist: -./mpris-scrobbler-publish.sh --force +./scrbblr-publish.sh --force ``` The installer also places this helper in `~/.local/bin` as: -`mpris-scrobbler-publish` +`scrbblr-publish` ## Data storage -Scrobbles are stored in SQLite at `~/.local/share/mpris-scrobbler/scrobbles.db` (respects `$XDG_DATA_HOME`). +Scrobbles are stored in SQLite at `~/.local/share/scrbblr/scrobbles.db` (respects `$XDG_DATA_HOME`). Each scrobble records: @@ -422,11 +422,11 @@ Check the player name: playerctl -l ``` -If needed, edit `~/.config/systemd/user/mpris-scrobbler.service` and change `--player ...`, then reload/restart: +If needed, edit `~/.config/systemd/user/scrbblr.service` and change `--player ...`, then reload/restart: ```bash systemctl --user daemon-reload -systemctl --user restart mpris-scrobbler.service +systemctl --user restart scrbblr.service ``` ### Player is in another account/session @@ -436,14 +436,14 @@ MPRIS is session-scoped. The service must run in the same account/session as the ### Check logs ```bash -journalctl --user -u mpris-scrobbler.service -n 200 -journalctl --user -u mpris-scrobbler.service -f +journalctl --user -u scrbblr.service -n 200 +journalctl --user -u scrbblr.service -f ``` ### Verify database is being written ```bash -mpris-scrobbler report --period today +scrbblr report --period today ``` ## Licence diff --git a/contrib/examples/publish.conf.example b/contrib/examples/publish.conf.example index ada3cd8..6ec64f0 100644 --- a/contrib/examples/publish.conf.example +++ b/contrib/examples/publish.conf.example @@ -1,7 +1,7 @@ -# mpris-scrobbler publish helper configuration +# scrbblr publish helper configuration # # Copy to: -# ~/.config/mpris-scrobbler/publish.conf +# ~/.config/scrbblr/publish.conf # # Or let install.sh install it automatically if missing. diff --git a/contrib/systemd/user/mpris-scrobbler.service b/contrib/systemd/user/scrbblr.service similarity index 55% rename from contrib/systemd/user/mpris-scrobbler.service rename to contrib/systemd/user/scrbblr.service index 590c16b..f163ccb 100644 --- a/contrib/systemd/user/mpris-scrobbler.service +++ b/contrib/systemd/user/scrbblr.service @@ -1,11 +1,11 @@ [Unit] Description=MPRIS Scrobbler watcher -Documentation=https://github.com/arturmeski/mpris_scrobbler +Documentation=https://github.com/arturmeski/scrbblr After=graphical-session.target [Service] Type=simple -ExecStart=%h/.local/bin/mpris-scrobbler watch --player com.blitzfc.qbz +ExecStart=%h/.local/bin/scrbblr watch --player com.blitzfc.qbz Restart=on-failure RestartSec=5 diff --git a/install.sh b/install.sh index 20c0559..004407d 100755 --- a/install.sh +++ b/install.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# Interactive installer for mpris-scrobbler. +# Interactive installer for scrbblr. # # Steps: # 1. Build release binary @@ -14,16 +14,16 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -BIN_NAME="mpris-scrobbler" -PUBLISH_SCRIPT_NAME="mpris-scrobbler-publish.sh" -PUBLISH_INSTALLED_NAME="mpris-scrobbler-publish" +BIN_NAME="scrbblr" +PUBLISH_SCRIPT_NAME="scrbblr-publish.sh" +PUBLISH_INSTALLED_NAME="scrbblr-publish" PUBLISH_CONFIG_EXAMPLE_SRC="$SCRIPT_DIR/contrib/examples/publish.conf.example" -PUBLISH_CONFIG_DIR="$HOME/.config/mpris-scrobbler" +PUBLISH_CONFIG_DIR="$HOME/.config/scrbblr" PUBLISH_CONFIG_FILE="$PUBLISH_CONFIG_DIR/publish.conf" INSTALL_DIR="$HOME/.local/bin" -SERVICE_SRC="$SCRIPT_DIR/contrib/systemd/user/mpris-scrobbler.service" +SERVICE_SRC="$SCRIPT_DIR/contrib/systemd/user/scrbblr.service" SERVICE_DIR="$HOME/.config/systemd/user" -SERVICE_NAME="mpris-scrobbler.service" +SERVICE_NAME="scrbblr.service" GREEN='\033[0;32m' YELLOW='\033[1;33m' @@ -183,4 +183,4 @@ info " Stop service: systemctl --user stop $SERVICE_NAME" info " Restart: systemctl --user restart $SERVICE_NAME" info " Generate report: $BIN_NAME report --html --output ~/music-report" info " Publish report: $PUBLISH_INSTALLED_NAME" -info " Configure defaults in: ~/.config/mpris-scrobbler/publish.conf" +info " Configure defaults in: ~/.config/scrbblr/publish.conf" diff --git a/mpris-scrobbler-publish.sh b/scrbblr-publish.sh similarity index 85% rename from mpris-scrobbler-publish.sh rename to scrbblr-publish.sh index 29c14f3..44b4d0e 100755 --- a/mpris-scrobbler-publish.sh +++ b/scrbblr-publish.sh @@ -6,9 +6,9 @@ set -euo pipefail # Defaults can be overridden by config file and then by flags: # # Config file precedence: -# 1) $XDG_CONFIG_HOME/mpris-scrobbler/publish.conf -# 2) ~/.config/mpris-scrobbler/publish.conf -# 3) ~/.mpris-scrobbler-publish.conf (legacy fallback) +# 1) $XDG_CONFIG_HOME/scrbblr/publish.conf +# 2) ~/.config/scrbblr/publish.conf +# 3) ~/.scrbblr-publish.conf (legacy fallback) # # Supported config variables: # OUTPUT_DIR @@ -30,8 +30,8 @@ INTERVAL=300 FORCE=0 XDG_CONFIG_BASE="${XDG_CONFIG_HOME:-${HOME}/.config}" -PRIMARY_CONFIG="${XDG_CONFIG_BASE}/mpris-scrobbler/publish.conf" -LEGACY_CONFIG="${HOME}/.mpris-scrobbler-publish.conf" +PRIMARY_CONFIG="${XDG_CONFIG_BASE}/scrbblr/publish.conf" +LEGACY_CONFIG="${HOME}/.scrbblr-publish.conf" if [[ -f "${PRIMARY_CONFIG}" ]]; then # shellcheck disable=SC1090 @@ -79,8 +79,8 @@ if [[ ! "${INTERVAL}" =~ ^[1-9][0-9]*$ ]]; then exit 2 fi -if ! command -v mpris-scrobbler >/dev/null 2>&1; then - printf 'mpris-scrobbler not found in PATH\n' >&2 +if ! command -v scrbblr >/dev/null 2>&1; then + printf 'scrbblr not found in PATH\n' >&2 exit 1 fi @@ -90,7 +90,7 @@ if ! command -v rsync >/dev/null 2>&1; then fi STATE_HOME="${XDG_STATE_HOME:-${HOME}/.local/state}" -STATE_DIR="${STATE_HOME}/mpris-scrobbler" +STATE_DIR="${STATE_HOME}/scrbblr" MARKER_FILE="${STATE_DIR}/last-published-scrobble.txt" mkdir -p "${STATE_DIR}" @@ -101,7 +101,7 @@ do_publish() { fi local latest_scrobble - latest_scrobble="$(mpris-scrobbler last-scrobble "${last_args[@]}")" + latest_scrobble="$(scrbblr last-scrobble "${last_args[@]}")" if [[ -z "${latest_scrobble}" ]]; then printf 'No scrobbles yet. Nothing to publish.\n' return 0 @@ -127,7 +127,7 @@ do_publish() { if [[ -n "${DB_PATH}" ]]; then report_args+=(--db-path "${DB_PATH}") fi - mpris-scrobbler "${report_args[@]}" + scrbblr "${report_args[@]}" printf 'Publishing via rsync to %s...\n' "${REMOTE_TARGET}" rsync -Pavz "${OUTPUT_DIR}" "${REMOTE_TARGET}" diff --git a/src/db.rs b/src/db.rs index 428e092..4a77d8d 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,8 +1,8 @@ -//! Database module for the MPRIS scrobbler. +//! Database module for scrbblr. //! //! This module handles all SQLite interactions: schema creation, inserting //! scrobble records, and querying data for reports. The database is stored -//! as a single SQLite file (by default at `~/.local/share/mpris-scrobbler/scrobbles.db`). +//! as a single SQLite file (by default at `~/.local/share/scrbblr/scrobbles.db`). //! //! ## Tables //! diff --git a/src/enrich.rs b/src/enrich.rs index 51de8f2..37aefc0 100644 --- a/src/enrich.rs +++ b/src/enrich.rs @@ -41,7 +41,7 @@ //! ## User-Agent //! //! MusicBrainz requires a descriptive User-Agent header. We use: -//! `mpris-scrobbler/0.1.0 (https://github.com/arturmeski/mpris_scrobbler)` +//! `scrbblr/0.1.0 (https://github.com/arturmeski/scrbblr)` use crate::db::{self, AlbumCacheEntry}; use image::codecs::jpeg::JpegEncoder; @@ -57,7 +57,7 @@ use std::time::Duration; /// User-Agent string sent with all HTTP requests to MusicBrainz. /// Required by MusicBrainz API policy: /// https://musicbrainz.org/doc/MusicBrainz_API/Rate_Limiting -const USER_AGENT: &str = "mpris-scrobbler/0.1.0 (https://github.com/arturmeski/mpris_scrobbler)"; +const USER_AGENT: &str = "scrbblr/0.1.0 (https://github.com/arturmeski/scrbblr)"; /// Build the shared HTTP client used for all MusicBrainz and Cover Art Archive requests. fn build_client() -> Client { @@ -188,14 +188,14 @@ struct MbTag { /// Get the directory where cover art images are stored, creating it if needed. /// -/// Default: `~/.local/share/mpris-scrobbler/covers/` +/// Default: `~/.local/share/scrbblr/covers/` /// (respects `$XDG_DATA_HOME`) pub fn covers_dir() -> PathBuf { let data_dir = std::env::var("XDG_DATA_HOME").unwrap_or_else(|_| { let home = std::env::var("HOME").expect("HOME not set"); format!("{}/.local/share", home) }); - let dir = PathBuf::from(format!("{}/mpris-scrobbler/covers", data_dir)); + let dir = PathBuf::from(format!("{}/scrbblr/covers", data_dir)); fs::create_dir_all(&dir).expect("Failed to create covers directory"); dir } diff --git a/src/main.rs b/src/main.rs index 74c38ca..1a5a906 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -//! MPRIS + MPD Scrobbler — a local music scrobbler for Linux. +//! scrbblr — a local music scrobbler for MPRIS and MPD on Linux. //! //! This is the CLI entry point. It provides five subcommands: //! @@ -69,7 +69,7 @@ const DEFAULT_PLAYER: &str = "com.blitzfc.qbz"; // --------------------------------------------------------------------------- #[derive(Parser)] -#[command(name = "mpris-scrobbler", about = "MPRIS scrobbler using playerctl")] +#[command(name = "scrbblr", about = "Local music scrobbler for MPRIS and MPD")] struct Cli { #[command(subcommand)] command: Commands, @@ -110,7 +110,7 @@ enum Commands { mpd_port: u16, /// Path to the SQLite database file. If not specified, defaults to - /// ~/.local/share/mpris-scrobbler/scrobbles.db (respects $XDG_DATA_HOME). + /// ~/.local/share/scrbblr/scrobbles.db (respects $XDG_DATA_HOME). #[arg(long)] db_path: Option, }, @@ -228,8 +228,8 @@ enum Commands { /// Determine the default database path, following the XDG Base Directory spec. /// -/// Path: $XDG_DATA_HOME/mpris-scrobbler/scrobbles.db -/// Falls back to: ~/.local/share/mpris-scrobbler/scrobbles.db +/// Path: $XDG_DATA_HOME/scrbblr/scrobbles.db +/// Falls back to: ~/.local/share/scrbblr/scrobbles.db /// /// Creates the parent directory if it doesn't exist. fn default_db_path() -> String { @@ -237,7 +237,7 @@ fn default_db_path() -> String { let home = std::env::var("HOME").expect("HOME not set"); format!("{}/.local/share", home) }); - let dir = format!("{}/mpris-scrobbler", data_dir); + let dir = format!("{}/scrbblr", data_dir); std::fs::create_dir_all(&dir).expect("Failed to create data directory"); format!("{}/scrobbles.db", dir) } diff --git a/src/report.rs b/src/report.rs index 0698170..4fd7bff 100644 --- a/src/report.rs +++ b/src/report.rs @@ -784,7 +784,7 @@ pub fn render_html_report(conn: &Connection, limit: i64, all_time_limit: i64) -> h.open("
"); h.line("

Listening Report

"); h.linef(format_args!( - "
Generated by mpris-scrobbler on {}
", + "
Generated by scrbblr on {}
", html_escape(&generated_at) )); h.close("
"); @@ -1477,7 +1477,7 @@ fn pin_album_cmd(artist: &str, album: &str, mbid: Option<&str>) -> String { let a = artist.replace('"', "\\\""); let b = album.replace('"', "\\\""); let m = mbid.unwrap_or("MBID_HERE"); - format!("mpris-scrobbler pin-album --artist \"{a}\" --album \"{b}\" --mbid \"{m}\"") + format!("scrbblr pin-album --artist \"{a}\" --album \"{b}\" --mbid \"{m}\"") } fn html_escape(s: &str) -> String { diff --git a/uninstall.sh b/uninstall.sh index b6de9cb..b5f69ef 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# Interactive uninstaller for mpris-scrobbler. +# Interactive uninstaller for scrbblr. # # Steps: # 1. Stop and disable the systemd user service @@ -12,13 +12,13 @@ set -euo pipefail -BIN_NAME="mpris-scrobbler" -PUBLISH_BIN_NAME="mpris-scrobbler-publish" +BIN_NAME="scrbblr" +PUBLISH_BIN_NAME="scrbblr-publish" INSTALL_DIR="$HOME/.local/bin" SERVICE_DIR="$HOME/.config/systemd/user" -SERVICE_NAME="mpris-scrobbler.service" +SERVICE_NAME="scrbblr.service" -DATA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/mpris-scrobbler" +DATA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/scrbblr" GREEN='\033[0;32m' YELLOW='\033[1;33m'