Skip to content

Conversation

@martpie
Copy link
Owner

@martpie martpie commented Nov 26, 2025

fixes #231

Thank you Claude, because you've literally done it all.

  • Probably not ok to put the API key / Secret there? Maybe yes
  • Play more with it
  • Review the code
  • Make it extensible for other scrobblers
image

Copilot AI review requested due to automatic review settings December 7, 2025 19:44
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements Last.fm scrobbling integration for Museeks, allowing users to automatically share their listening history with Last.fm. The implementation follows Last.fm's scrobbling specifications (tracks must be played for at least 50% of their duration or 4 minutes, and be longer than 30 seconds).

Key changes:

  • Backend Rust plugin for Last.fm API authentication and scrobbling
  • Frontend scrobbler service that monitors playback and triggers scrobbles
  • New settings page for connecting/disconnecting Last.fm accounts
  • Configuration storage for Last.fm credentials and enabled state

Reviewed changes

Copilot reviewed 19 out of 22 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src-tauri/src/plugins/lastfm.rs New plugin handling Last.fm API communication, authentication flow, and scrobbling operations
src-tauri/src/plugins/config.rs Added Last.fm config fields (enabled, session_key, username)
src-tauri/src/plugins/mod.rs Registered lastfm plugin module
src-tauri/src/main.rs Added lastfm plugin initialization
src-tauri/src/libs/error.rs Added LastFm error variant
src-tauri/build.rs Registered lastfm commands for build system (contains duplicate plugin registration)
src-tauri/capabilities/main.json Added Last.fm command permissions (missing test_connection permission)
src-tauri/Cargo.toml Added reqwest and md5 dependencies for HTTP and API signature generation
src-tauri/Cargo.lock Dependency lock file updates
src/lib/scrobbler.ts New scrobbler service implementing Last.fm scrobbling rules and timing logic
src/lib/bridge-lastfm.ts Bridge layer for frontend-backend Last.fm communication
src/routes/settings.scrobbling.tsx New settings page UI for Last.fm connection management
src/routes/settings.tsx Added navigation link to scrobbling settings
src/components/PlayerEvents.tsx Initialize scrobbler to track playback events
src/generated/typings.ts TypeScript type definitions for Last.fm data structures and updated Config type
src/generated/route-tree.ts Auto-generated routing configuration including scrobbling route
src/translations/*.po Translation entries for Last.fm UI strings (empty translations for non-English locales)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +15 to +19

// Last.fm API credentials - these are public identifiers for the Museeks app
// Register your app at https://www.last.fm/api/account/create
const API_KEY: &str = "6496d20a201157d8c0c86cde0f2df5db"; // TODO: Replace with actual API key
const API_SECRET: &str = "d4f4b99472e12f395744134fba2c6d27"; // TODO: Replace with actual API secret
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These API credentials appear to be placeholders (as indicated by the TODO comments). Before merging to production:

  1. Replace these with actual Last.fm API credentials for the Museeks application
  2. Consider using environment variables or a config file rather than hardcoding them
  3. Note that Last.fm API keys are typically public knowledge (the secret is used for signing), but it's best practice to obtain your own credentials at https://www.last.fm/api/account/create
Suggested change
// Last.fm API credentials - these are public identifiers for the Museeks app
// Register your app at https://www.last.fm/api/account/create
const API_KEY: &str = "6496d20a201157d8c0c86cde0f2df5db"; // TODO: Replace with actual API key
const API_SECRET: &str = "d4f4b99472e12f395744134fba2c6d27"; // TODO: Replace with actual API secret
use std::env;
// Last.fm API credentials - loaded from environment variables for security
// Register your app at https://www.last.fm/api/account/create
// Set LASTFM_API_KEY and LASTFM_API_SECRET in your environment
fn api_key() -> String {
std::env::var("LASTFM_API_KEY").expect("LASTFM_API_KEY environment variable not set")
}
fn api_secret() -> String {
std::env::var("LASTFM_API_SECRET").expect("LASTFM_API_SECRET environment variable not set")
}

Copilot uses AI. Check for mistakes.
"lastfm_get_session",
"lastfm_disconnect",
"lastfm_now_playing",
"lastfm_scrobble",
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lastfm_test_connection command is defined in the plugin handler (lastfm.rs line 213) but not registered here. Add "lastfm_test_connection" to this commands list. Similarly, update src-tauri/capabilities/main.json to include "lastfm:allow-lastfm-test-connection" permission.

Suggested change
"lastfm_scrobble",
"lastfm_scrobble",
"lastfm_test_connection",

Copilot uses AI. Check for mistakes.
"lastfm:allow-lastfm-get-session",
"lastfm:allow-lastfm-disconnect",
"lastfm:allow-lastfm-now-playing",
"lastfm:allow-lastfm-scrobble",
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lastfm_test_connection command exists in the backend (src-tauri/src/plugins/lastfm.rs line 213) and is exported in the bridge (src/lib/bridge-lastfm.ts line 53), but the permission is missing here. Add "lastfm:allow-lastfm-test-connection" to allow this command to be invoked from the frontend.

Suggested change
"lastfm:allow-lastfm-scrobble",
"lastfm:allow-lastfm-scrobble",
"lastfm:allow-lastfm-test-connection",

Copilot uses AI. Check for mistakes.
Comment on lines +84 to +101
private handleTimeUpdate(currentTime: number) {
if (!this.state.track || this.state.hasScrobbled || !this.state.startTime) {
return;
}

const track = this.state.track;
const playedDuration = currentTime;

// Calculate scrobble threshold
// Scrobble at 50% of track duration OR 4 minutes, whichever comes first
const halfDuration = track.duration / 2;
const scrobbleThreshold = Math.min(halfDuration, SCROBBLE_TIME_THRESHOLD);

// Check if we've reached the scrobble point
if (playedDuration >= scrobbleThreshold && !this.state.scrobbleTimeReached) {
this.state.scrobbleTimeReached = true;
this.scrobbleTrack(track);
}
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scrobbling logic uses currentTime (playback position) instead of actual played duration. This allows users to cheat the scrobble requirement by seeking forward. According to Last.fm scrobbling rules, a track should be scrobbled after being played for 50% of its duration or 4 minutes, not when the playback position reaches that point.

To fix this:

  1. Track the actual cumulative played time (not just position)
  2. Handle seek events to prevent seek-based scrobbling
  3. Or, accept this simpler implementation as a reasonable approximation (many scrobblers use position-based logic for simplicity)

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +33
// Initialize the scrobbler to track playback and send to Last.fm
scrobbler.init();
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scrobbler initialization adds event listeners to the player but there's no cleanup function to remove them. This could lead to duplicate event listeners if the component re-renders or remounts.

Consider one of these solutions:

  1. Add a cleanup() method to the Scrobbler class and call it in the useEffect cleanup
  2. Ensure scrobbler.init() is idempotent by checking if listeners are already attached before adding them
  3. Move scrobbler initialization outside of React (e.g., in main.tsx) if it should only happen once per app lifecycle

Copilot uses AI. Check for mistakes.
Comment on lines +47 to +50
"default-view",
tauri_build::InlinedPlugin::new().commands(&["set"]),
)
.plugin(
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate plugin registration for "default-view". This plugin is already registered at lines 42-45. Remove this duplicate entry.

Suggested change
"default-view",
tauri_build::InlinedPlugin::new().commands(&["set"]),
)
.plugin(

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

last.fm integration

2 participants