* Parse irsc and track number from spotify data
* Add isrc and track number to listenbrainz submission
* Correct spotify ids for listenbrainz submission
#418
* Merge causes if a simple error exists in the stack, reduces stack logging noise
* Log top level message and print error instead of duplicating message in another error
* Fix adding transform history for failed-continue stage
* Implement multiple endpoints for API client with round robin load balancing
* Implement caching responses
* Add transformer options for search triggers
* Implement transformer checks based on search triggers and score
* Aggressively start Now Playing update (based on reported) but aggressibely end, based on stale/orphaned, so updates are early but not often
* Refactor update thresholds to use scrobble client behavior
* LFM and LZ use track duration as "time visible" for Now Playing, don't re-update during that time period
* Add thresholds to logging for easier troubleshooting
#395
* Use vite tailwind plugin instead of postcss
* Update tailwind so it actually works with plugin
* Remove postcss
* Merge vite config into inline vite-express config
* Use typeson for stringified data marshalling
* Use clone for in-memory serializing to preserve dayjs
* Refactor cache backend init into indepedent, testable functions
* Add `false` as cache provider type to allow disabling secondary cache
* Two-tier caching
* primary is always in-memory LRU
* secondary supports file and valkey
* Different caches for metadata and scrobbles
* no file support for metadata (too big)
Maybe recenttracks/track does not exist for totally new accounts, though it should.
Guard against this and log response if something unexpected happens so we can troubleshoot
#353
* Move API code into own class for reusability with future Source
* Remove Maloja V2 compatibility
* Throw on connection if server version is too old or unknown
As of April 2025 Spotify no longer allows insecure redirect URIs which complicates initial Spotify setup.
* Improve spotify App creation instructions
* Add several approaches to creating a valid redirect URI
* Remove Spotify as the default Source example in docs due to complexity of Redirect URI requirements
Closes#330
* Use MS Player to mark Play as a repeat if session repeat is detected
* Remove FUZZY temporal accuracy as acceptable for detecting duplicate if candidate play is marked as repeat
* Add queue for aggregating Now Playing updates
* Filter play based on 1. source priority and 2. player stickiness or deterministic order
* Use toad scheduler to processing NP queue
* Define if scrobble client can update Now Playing based on class property, use this to filter now playing update
* Use thresholds to determine when/if Now Playing should be updated
* Set LastfmScrobbler as supported for Now Playing
* Allow non-positional Sources to still report position (for display only)
* Refactor Azuracast to be non-positional due to reported position not always being accurate (azuracast approximates track length)
* Rename from manualListening to Should Scrobble to better describe functionality
* Move check to Source scrobble function to avoid short circuting Source player for no reason
* Add to Azuracast and make functionality more generic
Clearing pre-stale listening range prevents any future resume (player becomes non-stale) from using old listening range to scrobble. Represents a physical "break" between listening sessions for the same song
Missing arg to pass to getPlayedObject to ensure the timestamp used for playDate is the same as normally discovered tracks. Without the arg two tracks played with the second being scrobbled on stale would cause the latter to have the same TS as the former
fixes#254
Replaces `build-push-action` buildx QEMU building for multiple `arch` with a multi-runner matrix that builds each image on native runners and combines digests before pushing to registries.
* Use arm public preview runner
* Refactor publish image workflow to use multi-runner
* Refactor PR workflow to use multi-runner
* Update to latest node 20.x minor version
* Remove `--verbose` and enable `--no-audit` for npm install to reduce install time
* These were left over from debugging ARM builds from Feb 2024
* Do play cleanup during polling for new plays so that we can discover tracks from abandoned players
* Fixes missed scrobbles from Plex API when Source stops returning a session due to player queue end #223
* Diff type should always be prepend since we are checking for new tracks
* Warn and log if another type is detected
* Warn if more than one track diffed for discovery
* Refactor and and make YTM history parsing testable
#227
* Fixes#241 by using the track artist as the primary artist when album artist is present
* Bug is reported in LukeHagar/plexjs#27 but will need be patched at runtime for the foreeable future due to npm packages not being published since September LukeHagar/plexjs#26
* try-catch and bubble up depending on usage
* replace poll/monitor code on startup with scheduler task usage
* better notification messages with truncated cause chain
* Notifications are not mission critical so its okay if they fail on init because service outage may be transient. Always init notification services and always attempt to push (we catch failures anyway)
* Parse more info from notification URL and test simple reachability of host/port to make troubleshooting easier
Fixes#215
* Patch generator to generate definition titles until vega/ts-json-schema-generator#2127 is resolved
* Add launch config for debugging schema generation
* Disallow topRef generation so docusarus json viewer works
* Write to docsite static assets on generation
Youtube TV seems no longer have scope for reading history or account details?
Cookie may work and custom oauth seems most stable based on reporting from LuanRT/YouTube.js#803
* Identify when session is for the local user (no user is logged in)
* Add Local User as default allowed when no usersAllow settings
* Allow user config to allow/block local user
* Update plex docs
Fixes#218
* Add session key dervied from source data to Play and PlayerState
* Discriminate states based on a session key, if possible
* Log which states are being dropped based on session key (plex)
* Add session key to important logging (new player play, in UI for player info)
* Introduce positional and non-positional data structures
* Refactor state player into types of each
* Refactor listen ranges and progress into types of each
* Implment (internal) real-time player and base positional player seeked/repeat on real-time drift instead of just reported position
* Refactor tests to use (emulated) real-time components
Plex only updates player position every 15 seconds (of played track) so player state needs to be adapted to not detect this as a pause or seek.
* Allow per-source drift allowed before triggering seek
* Allow per-source pause detection
Was not passing stopped state due to undefined play but need to do this to let last play actually be scrobbled.
Refactor jellyfin session validation and types for MemorySource to correctly allow processing player state with no plays
* Default disallow any activity not from a library of collectionType 'music'
* Allow user config for allow/block by library name
* Allow user config for allowing additional library collection types
* Update docs and example config
SDK types say AlbumArtists etc. should be defined but #200 shows that's not always the case, for some reason.
Check for undefined value on playback data that requires additional transformation to prevent errors.
Fixes#200
Fixes a lot of headaches:
* vega is more up to date and extensible
* no more hacky regex for reference names
* complete control over parsing and formatting (const as enum)
* does not duplicate typescript as dependency
* should properly throw errors on schema generation instead of just logging to console
* move schema static files into programmatic creation instead of package scripts
* fix types being removed in prod which caused schema generation errors
Not enough to include it in both dependencies and devDependencies -- it needs to be NOT in devDependencies to prevent `npm ci --omit=dev` from purging it.
* Add suite for testing client/source parsing and validation
* Additionally, use example configs for two-birds-one-stone checking they are valid
* Refactor sources init as separate step from component building
* Use runtime-generated schemas
* Ensures validated schemas always match code
* Can use schemas for individual source/clients without having to generate files
* catch invididual validation errors and log instead of crashing
* refactor validation error output into chained error instead of as individual log statements to improve clarity
* Swtch to devcontainer for node 18.x and remove terminal/launch settings for version (removes nvm dependency in container)
* Update eslint config to use proper config for include/ignore files
* Add spec for mocha and mocha test runner extension + settings
* Fix documentation and jellyfin example to match code
* Add tests for jellyfin api config parsing and valid activities
* Fix jellyin api ENV naming to match code
* Fix config parsing missing lowercase transform
Fixes#196
* All required files in the same file by using .actrc
* Include example .secrets and .env
* Update README with usage
* Refactor actTest to make docker build steps optional with ENV
* Add refreshMinInterval to prevent hammering upstream services
* Refactor refresh logic to be simpler and account for backlogged tracks
* (test): Improve play generation utils for testing
* (test): Refactor testing for upstream scrobble refreshing to use more actual scrobbler class behavior
* Move file parsing to own function
* Add some typings to file parsing to help with development (typings are bad)
* Defensively check structure of formdata for nested arrays since Plex seems to waffle on how it sends data (fixes#191)
* Add logging for all files found to help with future end-user debugging
express-static prefers directories over files when using the extensions
option, meaning an extensionless route will always route to the
directory instead of the file (expressjs/serve-static#138).
* Ensure pre-release tags don't also get latest tag
* Disable alpine builds
* Only increases testing and build complexity and there has been no feedback from users about needing it
* Aligns with "normal" default branch usage better, allows dependabot to actually run
* Change image publishing to push tagged images as 'latest' and master images as `edge`
* Implement new jellyfin source using JF typescript api client library for communication
* Use either API Key or username/password
* Implement real-time scrobble monitoring
* Implement feature parity filters from old jellyfin source (users) and add devices filters
* Refactor documentation for new configuration with api and add migration steps for webhook users
* Add deprecation warning to jellyfin webhook source
* Move recentScrobbles and generic refresh logic into abstract class
* New abstract function for scrobbler children to implement to get scrobbler-specific play lists on refresh
* Implement shouldRefereshScrobble with logging for better insight into why or why not a scrobbler refreshed upstream scrobbles
Since docs are now generated alongside builds and hosted on the app server there is no longer a barrier to using docusaurus docs as the main documentation presentation. Any user looking for docs can use the GH Pages hosted site or a "versioned" docs site hosted on their MS app. Due to this we can fully commit to docusaurus and take advantage of MDX layouts to simplify docs.
* Replace env/file configs headings in configuration with MDX tabs to make reading easier
* Remove superfluous TOCs
* Replace internal links in repo readme with links to docsite
* setup.sh takes care of building generated sources for project/docsite and building flatpak app
* Modify flatpak manifest to include two generated sources files and build docsite files
* Update flatpak docs with script usage and generated sources changes, move to docusauraus doc site
* Add npm scripts for installing/building docusaurus from main project
* Set base url based on ENV
* Build docsite in docker image
* Add link to doc in dashboard and link to github
* Update nodejs install instructions to include doc build command
* Update GH pages workflow to use correct base url
* Add hint page to main app when docs are not built
* Use lower headers so docusaurs sidebar TOC is more accurate
* Specify header levels for config/FAQ to make sidebar more concise
* Wrap inline TOC in details block -- so docusaurs users don't have to see it twice but its still available to github viewers
Sporadic DNS issues with alpine have caused a number of issues #160#126#134
Use debian as default since it has been proven to be stable. The image size increase is worth squashing these issues.
* Use superdiff to diff PlayObject lists and detect changes as well as append/prepend scenarios
* Replace YTM recently played logic with list diffing, only accept prepend-validated lists
* On non-prepend scenarios replace existing recently played and log human readable diff
* Patch youtube-music-ts-api to use updated cookies from response and provide a callback on update
* Implement currentCreds/build init data and read from MS-updated creds if available
* Write to currentCreds when ytm-ts-api invokes auth update callback and optionally log what parts changed based on config options
* Update Vite dep
* Use inlineViteConfig for vite-express middleware and detect correct base string
* Refactor vite.config.ts to detect correct base string
* Move code into testable function
* Fix port erasure when url uses standard port but it is explicitly specified
* Simplify building default url and fix using undefined mainPort variable
* Fix port erasure when https protocol is explicitly specified
* Refactor localUrl usage as URL instead of string
* Implement pathname joining on URL to make handling trailing/leading slashes take place in nodejs lib
* Implements tests for URL building/manipulation
* Increase default backlog limits to maximum pagination supported by each source
* Add scrobbleBacklogCount config property so user can explicitly set # of tracks to backlog
* Move some debug => verbose for non-repeating logs that are relevant to state, notifications, and scrobbling
* Move some verbose => debug that were repeating and making verbose level noisy
If a user does not need the dashboard or has security concerns about ingress and cannot control their hosting environment this enables reducing attack surface.
Closes#150
Error Cause (https://github.com/tc39/proposal-error-cause) was finalized and introduced in ES2022 so we don't need to ponyfill this anymore. Still using the helpful helper functions thought.
* Refactor SOT to be a type (may have more in the future)
* Set player stale/orphan interval based on SOT type
* Add note in log when initializing MemorySource which does not use player as SOT
* Add tooltip to UI player when it is not SOT
Migrating to @spotify/spotify-web-api-ts-sdk requires that the credentials we provide have valid expires_in/expires data and grant type. Make this migration easier by writing that data now so users upgrading later most likely do not need to re-authenticate.
* Actually remove device if it fails after X retries
* Properly close controllers/platform/client to prevent uncaught errors from castv2
* Move manually configured devices to device discovery function so they can be reconnected on heartbeat (if previously removed)
* Implement interfaces for response shape and getters for data
* Parse version data from response and use ping endpoint to return info on connection test
* Fix throwing auth/connection errors to include actual cause along with more human-friendly parsed error info
* Build error causes based on known error response types and expected maloja error body
* Only retry requests if error is network-related
* Set upstream error as NOT showstopping if we find expected error structure and status is less than 500
* Better maloja error parsing and logging
* Switch to lower-level mdns library for more control during debugging
* Use avahi-browse with host avahi-daemon to circumvent musl (alpline) issues with mdns resolution https://johnsiu.com/blog/alpine-linux/
* Add transportId shortname to logger label for better differentiation of "per session" apps
* Fix handling of MS player deletion when pruning apps by checking for other valid apps before deletion
* Refactor credit parsing into two stages: joiner separation and credits parsing
* Break credit parsing into wrapped vs. non-wrapped for simpler regexes
* Implement suffix matching after credits
* Add tests for wrapped vs. non-wrapped credits and with suffixes
* Break up initialize into build data => check connection => check auth
* Implement stub methods in AbstractSource with default return for steps
* Refactor existing initialize overrides into steps for each source
* Move (re)start to same location on status card as client and change verbage to be the same
* Fix api behavior to use correct poll function so that backlog is triggered
* Consolidate playerDelete event emitter to deletePlayer function
* Consolidate logging for player deletion to deletePlayer function
* Add removal reason when transferring player state for single-platform sources
The assumption is that single platform sources (like spotify) can only play from one platform at a time. If they are smart (like Spotify) the "new" platform picks up where the old platform left off WRT playing music. This commit implements the same behavior for MS player state by transferring existing state from the existing (previous) platform to the newly discovered platform.
* Track datetime player "completes" play at
* Add scrobble datetime SOC metadata
* Log SOC used when printing play datetime
* Use hinted SOC scrobble datetime when comparing existing scrobbles
* Use hinted SOC scrobble datetime when building scrobbler client scrobble payload
* Hint Spotify backlog plays use END play date for scrobble SOC
* Refactor using 'close' boolean to 'match' granularity
* Makes using granularity for future logic easier
* Easier logging for granularity in summary
* Remove intermediate temporal functions in classes for DRY and so we can use comparison results
* Add Time Detail to match breakdown for more visibility during logging
* Fix undefined token inclusion caused by token lists with differing lengths
* Always compare longer to shorter string so sameness parameter order is invariant
* Add comments to make logic easier to understand
* Add tests to test new functionality
* Move logging for init/auth into client instead of builder class
* Move startup from builder class to main backend and use initScrobbleMonitoring() instead of calling init/auth individually
Paves the way for manual restarting #114
If player is calculated as playing and tracks have positional data then start and end positions can be fudged to completion if jitter is small enough
* At beginning of new track if position is within 3 seconds of start of track fudge to 1 second
* At end of old track if position is within 3 seconds of end track fudge to completion
This can be achieved with high confidence due to smart polling near end of tracks added in 1d84f07a40
If player has an absolute position and play has duration we can determine if polling interval will occur after play would end. In this scenario we can shorten interval to before play ends to get more accurate data about play state close to end of track and report more accurate play time.
* Wake up timestamp was based on ts AFTER polling finished + interval. This means real-time interval between polling drifted by the amount of time it takes to execute polling loop. Fixed by getting ts at beginning of loop instead of at end.
* Implement interval function that can be overriding by child classes
* Format interval to make floats less ugly
* Implement abstract playToClientPayload function to make future testing easier
* Refactor play object to include album artist in data
* Refactor scrobble client payloads to include album artist, if applicable
* Discriminate between proper errors (permissions) and network errors
* Fix return value and playbackState flag set during post-auth hook
* Improve error logging using cause and upstream error
* Fix undefined destructure when device is not present in playingNow payload
* Implement type/guard for node network error
* Refactor auth in sources & clients to flag if error is network or api based
* Retry auth on heartbeat if auth failure was caused by network
* Implement initial tests for testing client auth
* Fix tests hanging due to running async functions
* Store logs by levels, fixes issue where rarer warn/info are pushed out of buffer by frequent debug/verbose
* Use FixedSizeList instead of homegrown buffer
* Implement API and ui controls for setting log limit
* Implement backend api endpoints for dead letter CRUD
* Include dead letter queue count in client status data
* Implement dead letter client view
* Partially working retry/remove actions for client (request is OK, RTK not working for update yet)
* Fix repeat end position percent test (should be 85% not 15%)
* Fix current play/range not being reset correctly on repeat
* Add reportedTS param to facilitate testing
* Use running/idle for scrobble client to indicate processing status
* Change polling sources status to polling/idle to better reflect behavior
* Add status indicator color to statuses
* Delay scrobble based on last scrobble attempt TS
* rename abstract scrobble func to doScrobble
* Implement scrobble() in abstract client and track last scrobble attempt time, delay based on arg or client property
* This way first scrobble isn't delayed and we only delay if subsequent scrobble attempts are close in time
* Make scrobble processing sleep time on empty queue configurable
* Implemented logic for processing and removing scrobbles from dead letter queue
* Only retry scrobbles under X retries based on config or argument
* Add removal-only function (for later use with UI)
* Move scrobble checking into client (out of client manager) for single responsibility
* Implement scrobble queue/deadletter
* Less chance of duplicate processing since sources can't scrobble concurrently
* Better throttling since multiple sources can't scrobble concurrently
* When scrobbling fails we can retry on delay just like polling and *keep* the failed scrobbles for later retry rather than dropping them
* Deadletter (to be fully implemented) for scrobbles that fail multiple times or are signalled as not upstream api issues by client
Wait until player gets a new play or becomes stale before trying to discover the (previous) play so that we store a more accurate amount of time the user actually listened to that play
When looking for dups and finding a fuzzy timestamp match, close title, and mismatch (but some matched) multiple artist -- we give a bonus to artist score since its likely a dup.
* Add text/* wildcard so any text-prefixed content-type is valid (Jellyfin should always be sending json anyway)
* Add logging WARN when body is empty
Fixes#101
* QoL improvement -- Instead of needing to rename a config file so it is not read by MS a user can now use the 'enable' property in all source/client configs to enable or disable loading the config
* Added property to all config examples
* Use lastfm 'nowPlaying' property in user listens for MS player implementation
* Do not use MS player as source of truth -- continue to use lfm listen history
Will not be used by listenbrainz source for actual scrobbling so make logs less noisy by suppressing what would normally be logging on newely added tracks from player
* Allows users to define a base URL to be used for default redirect URI generation
* Convenience mostly for docker users so they do not have to explicitly define redirect uri for each source/client
* Use in UI start up logging hints
files in node_module can have wierd ownership based on how a package owner published the package. If this occurs a rootless container build system could potentially have issues with subui/gid mapping for non-standard (high number) ids.
During image build recursively chown all node_modules files to normalize ownership and avoid this problem.
Fixes#96
* Due to increased scrobbled thresholds in 590610022d we need to pol more frequently to make sure more of the track is accurately monitored
* Decreased default from 30 to 10 seconds
* Decreased max interval from 60 to 30 seconds
* Better use of header
* Show name on separate line
* Only show client/source if ambiguous
* Move status into right side of header
* Reduce padding/margin on card grid
* Reduce padding on card headers and bodies
* Improve log experience
* Reduce log font size and convert to mono
* Use proper log message parsing to remove dangerous html set
* Check for stale/orphaned/dead in independent loop so they are not dependent on getting source updates to update
* Only emit events if player state has updated
* Create and implement api interface for player state data
* Serialize memory source players to object indexed by platform id
* Refactor event emitters to separate event data better from event metadata
* Update source slice when player update event is broadcast
Addresses #5 and #65 -- in default config use-cases where:
* request/polling retry attempts are low
* source was initially OK (auth OK, initial communication OK)
* and there is a non-auth related *upstream* communication issue
there is no fallback path for restarting source polling after upstream has normalized.
This feat implements a scheduled heartbeat task that attempts to restart polling every 20 minutes if this scenario is valid so that a good source config will recover without user intervention.
* Update flatpak build instructions to use build
* Add run command to readme
* Update default config to keep web port backwards compatible
* Fix flatpak application name in run command in installation docs
* Rename to backend to better reflect what it is
* Move server functionality out of entry file and into own server subdirectory
* Rename 'apis' folder to 'vendor' to reflect usage and move into common
* Rename 'clients' folder to 'scrobblers' reflect usage and prevent ambiguity with client CRA folder
* Changed logo color to be easy to view in both dark/light modes (better color in general, imo)
* Changed and cleaned up head/meta/manifest info and logos for docs and frontend
* If user tried to start polling while polling was already occuring it caused an infinite while loop
* Add more signals for stopping polling for a restart use case
* Result and criteria are returned from methods
* Logging method to summarize results
* Log results for jellyfin if using debug mode or explicit config option
* Will eventually migrate all non-source specific properties to this new property to simplify data config
* Add logPlayerState to allow configurable player state output to DEBUG logging
* Implements generic Player state tracking when using MemorySource
* State tracking will replace processRecentPlays and all its data structures
* Tracks reported player position OR real-time listened when position not available
* Records listened sessions for more exhaustive "listen to" duration and eventually comparing incoming data playedAt for dups
* Uses own tracked data to calculate most likely player state (stopped/started/paused) as well as health (stale/orphaned from no data)
* Tracks player's reported state, if provided
* Move UserDataSaved heuristics into scrobble method implementation with more descriptive logging
* Add more aggressive UserDataSaved filtering by discarding play if its found in memory tracking, regardless of play date
* Attempt to correct bad UTC offset for second UserDataSaved event
* Refactor candidate/discovered Play maps to use tuple id instead of string so user can be compared later
* Add option to check all discovered platform plays (since jellyfin UserDataSaved doesn't include device id in payload)
* Check for UserDataSaved notification with correct reason as well as sanity check last played date (since jellyfin will save as played regardless of duration played)
* Bypass play tracking
label:Please check existing knowledge before opening an issue
options:
- label:I have [checked the FAQ](https://foxxmd.github.io/multi-scrobbler/docs/FAQ/) and [documentation](https://foxxmd.github.io/multi-scrobbler/docs/configuration/)
required:true
- label:I have [searched through existing issues](https://github.com/FoxxMD/multi-scrobbler/issues?q=sort%3Aupdated-desc) and [discussions.](https://github.com/FoxxMD/multi-scrobbler/discussions)
required:true
- type:markdown
attributes:
value:|
If this a how-do-I question, troublehooting, or generally **not** a bug/error with multi-scrobbler please consider [**starting a discussion**](https://github.com/FoxxMD/multi-scrobbler/discussions) instead of opening an issue.
- type:textarea
id:what-happened
attributes:
label:Describe the Bug
description:Describe what you expected to happen and what happened instead. Please provide as much detail as possible.
placeholder:The app should have scrobbled from Spotify but instead it crashed with an error about undefined properties, etc...
validations:
required:true
- type:dropdown
id:platform
attributes:
label:Platform
description:What platform are you running multi-scrobbler on?
options:
- Docker
- Local (NodeJS)
- Flatpak
default:0
validations:
required:true
- type:textarea
attributes:
label:Versions
description:What version of multi-scrobbler and issue-related sources/clients are running?
placeholder:|
- multi-scrobbler: 0.8.3 on docker
- maloja 3.1.4
- jellyfin 10.8.9
validations:
required:true
- type:textarea
id:logs
attributes:
label:Logs
description:|
Please copy and paste any/all relevant from the DEBUG level log output.
Even if your issue does not seem to be shown in the logs you should still add as much detail as possible, a _lack_ of issues in logs may be a good hint during debugging.
Your logs will be automatically formatted into code, do not need use backticks.
If possible reproduce the issue with [**Debug Mode** enabled](https://foxxmd.github.io/multi-scrobbler/docs/configuration#debug-mode).
description:Suggest a new feature or functionality
title:"feat: "
labels:["enhancement"]
body:
- type:checkboxes
attributes:
label:Please check existing knowledge before opening an issue
options:
- label:I have checked the [documentation](https://foxxmd.github.io/multi-scrobbler/docs/configuration/)
required:true
- label:I have [searched through existing issues](https://github.com/FoxxMD/multi-scrobbler/issues?q=sort%3Aupdated-desc) and [discussions.](https://github.com/FoxxMD/multi-scrobbler/discussions)
required:true
- type:textarea
attributes:
label:Describe the new functionality
description:Please describe the feature or functionality you want multi-scrobbler to have. Use as much detail as possible about the exact behavior you wish to see.
validations:
required:true
- type:textarea
attributes:
label:Configuration Example
description:|
If the functionality involves changes to configuration files please provide an example here
render:json5
- type:checkboxes
attributes:
label:Will you help implement it?
options:
- label:I am willing to contribute code towards completing this feature
- label:I am willing to help test preview builds for this feature
description:Request a new Source or Client to be implemented
title:"feat: "
labels:["enhancement"]
body:
- type:checkboxes
attributes:
label:Please check existing knowledge before opening an issue
options:
- label:I have checked the [documentation](https://foxxmd.github.io/multi-scrobbler/docs/configuration/)
required:true
- label:I have [searched through existing issues](https://github.com/FoxxMD/multi-scrobbler/issues?q=sort%3Aupdated-desc) and [discussions.](https://github.com/FoxxMD/multi-scrobbler/discussions)
required:true
- type:dropdown
attributes:
label:What Type?
description:Is this a Source or a Client?
options:
- Source
- Client
default:1
validations:
required:true
- type:input
attributes:
label:Name
description:"What is the name of the new service/software?"
placeholder:"My Cool Player"
validations:
required:true
- type:input
attributes:
label:Website
description:"Website for the service/software. Preferably a github repo or other source-code homepage"
validations:
required:true
- type:dropdown
attributes:
label:API Available?
description:Does the service have an API or other protocol that multi-scrobbler can use to communicate? **Without this the likelyhood of implementation is very low.**
options:
- 'Yes'
- 'No'
default:1
validations:
required:true
- type:dropdown
attributes:
label:Free To Use?
description:Is this service/software freely available or does it require purchasing/subscription?
options:
- 'Yes'
- 'No'
default:1
validations:
required:true
- type:dropdown
attributes:
label:Willing to finance development?
description:If the software is **non-free** are you will to provide a paid account/subscription to multi-scrobbler developer's in order to facilitate development/testing?
options:
- 'Yes'
- 'No'
validations:
required:false
- type:checkboxes
attributes:
label:Will you help implement it?
options:
- label:I am willing to contribute code towards completing this feature
- label:I am willing to help test preview builds for this feature
- type:textarea
attributes:
label:Additional Context
description:Add any other context about the new Source/Client here
[Please check the FAQ](https://github.com/FoxxMD/multi-scrobbler/blob/master/docsite/docs/FAQ.md) before submitting a bug report.
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Logs**
If possible reproduce the issue with [debug logging ON](https://github.com/FoxxMD/multi-scrobbler/blob/master/docsite/docs/FAQ.md#turn-on-debug-logging)
```
Copy and paste as much log data as possible related to this issue here.
```
**Versions (please complete the following information):**
Provide version information for any related sources/clients.
Need to have credentials in a [`.secrets` file](https://nektosact.com/usage/index.html#secrets), copy and rename [`.secrets.example`](./secrets.example) to `.secrets`, then fill out blank fields. Required for docker/metadata-action to read...something. Fails with `Parameter token or opts.auth is required` if they are not supplied.
An ENV file can also be made by copy and renaming [`.env.example`](./env.example). Set `NO_DOCKER_BUILD=true` if you only want to test APP_VERSION and docker tags output.
If running a full docker build for multi-runner workflows you will need to create an [artifact server](https://github.com/nektos/act/issues/329#issuecomment-1905955589) for ACT to work:
# Based on https://docs.docker.com/build/ci/github-actions/multi-platform/#with-bake (https://github.com/docker/build-push-action/issues/671#issuecomment-1609106171)
# with example from here https://github.com/crazy-max/docker-linguist/blob/master/.github/workflows/build.yml that uses caching
#
# may have to use this to enable pushing to multiple registries? https://github.com/docker/build-push-action/discussions/1067#discussioncomment-8677682
"line":"[2024-12-26 01:58:48.261 -0500] DEBUG : [App] [Sources] [Jellyfin - unnamed] Last activity was at 01:58:48-05:00 | Next check in 10.00s | No new tracks discovered",
A javascript app to scrobble music you listened to, to [Maloja](https://github.com/krateng/maloja), [Last.fm](https://www.last.fm), and [ListenBrainz](https://listenbrainz.org)
A javascript app to scrobble music you listen to, to [Maloja](https://github.com/krateng/maloja), [Last.fm](https://www.last.fm), [ListenBrainz](https://listenbrainz.org), and other services.
* Monitor status of Sources and Clients using [webhooks (Gotify or Ntfy)](/docsite/docs/configuration/configuration.md#webhook-configurations) or [healthcheck endpoint](/docsite/docs/configuration/configuration.md#health-endpoint)
* Supports scrobbling from many [**Sources**](https://foxxmd.github.io/multi-scrobbler/configuration/sources)
* Monitor status of Sources and Clients using [webhooks (Gotify, Ntfy, Apprise)](https://foxxmd.github.io/multi-scrobbler/configuration#webhook-configurations) or [healthcheck endpoint](https://foxxmd.github.io/multi-scrobbler/configuration#health-endpoint)
* Supports [Now Playing](https://foxxmd.github.io/multi-scrobbler/configuration/clients#now-playing) for scrobble Clients
* Supports configuring for single or multiple users (scrobbling for your friends and family!)
* Web server interface for stats, basic control, and detailed logs
* Graceful network and client failure handling (queued scrobbles that auto-retry)
* Smart handling of credentials (persistent, authorization through app)
* Easy configuration through ENVs or JSON
*Install using [Docker images for x86/ARM](/docsite/docs/installation/installation.md#docker), [flatpak](/docsite/docs/installation/installation.md#flatpak), or [locally with NodeJS](/docsite/docs/installation/installation.md#nodejs)
* Easy configuration through [ENVs or JSON](ttps://foxxmd.github.io/multi-scrobbler/configuration#configuration-types)
*Modify data before scrobbling with [regular expression or search patterns](https://foxxmd.github.io/multi-scrobbler/configuration/transforms)
* Install using [Docker images for x86/ARM](https://foxxmd.github.io/multi-scrobbler/installation#docker) or [locally with NodeJS](https://foxxmd.github.io/multi-scrobbler/installation#nodejs)
**Why should I use this over a browser extension and/or mobile app scrobbler?**
@@ -42,17 +68,15 @@ A javascript app to scrobble music you listened to, to [Maloja](https://github.c
**But I already scrobble my music to Last.fm/ListenBrainz, is multi-scrobbler for me?**
Yes! You can use [Last.fm as a **Source**](/docsite/docs/configuration/configuration.md#lastfm--source-) or [Listenbrainz as a **Source**](/docsite/docs/configuration/configuration.md#listenbrainz--source-) to forward scrobbles from your profile to any other Client! That way you can keep your current scrobble setup as-is but still get the benefit of capturing your data to a self-hosted location.
<img src="/assets/status-ui.jpg" width="800">
Yes! You can use [Last.fm as a **Source**](https://foxxmd.github.io/multi-scrobbler/configuration/sources/lastfm-source) or [Listenbrainz as a **Source**](https://foxxmd.github.io/multi-scrobbler/configuration/sources/listenbrainz-source) to forward scrobbles from your profile to any other Client! That way you can keep your current scrobble setup as-is but still get the benefit of capturing your data to a self-hosted location.
## How Does multi-scrobbler (MS) Work?
You set up configurations for one or more **Sources** and one or more **Clients**. MS monitors all of your configured **Sources**. When new tracks are played by a Source it grabs that information and then sends it (scrobbles it) to all **Clients** that Source is configured to scrobble to.
You set up [configurations](https://foxxmd.github.io/multi-scrobbler/configuration) for one or more [**Sources**](https://foxxmd.github.io/multi-scrobbler/configuration/sources) and one or more [**Clients**](https://foxxmd.github.io/multi-scrobbler/configuration/clients). MS monitors all of your configured **Sources**. When new tracks are played by a Source MS grabs that information and then sends it (scrobbles it) to all **Clients** that Source is configured to forward to.
### Source
A **Source** is a data source that contains information about tracks you are playing like a music player or platform. Examples are **Spotify, Jellyfin, Plex, Youtube Music, Airsonic**, etc...
A [**Source**](https://foxxmd.github.io/multi-scrobbler/configuration/sources) is a data source that contains information about music you are playing or have listened to, like a desktop player, web music player, or cloud music service. Examples are **Spotify, Jellyfin, Plex, Youtube Music, Navidrome**, etc...
A **Client** is an application that stores the historical information about what songs you have played (scrobbles). Examples are **Maloja, Last.fm, Listenbrainz**...
A [**Client**](https://foxxmd.github.io/multi-scrobbler/configuration/clients) is an application that stores the historical information about what music you have played (scrobbles). Examples are **Maloja, Last.fm, Listenbrainz**...
Client configurations consist of:
* A friendly name.
* Any data needed to communicate or authenticate with the Client.
## Quick Start
[See the **Quick Start Guide**](https://foxxmd.github.io/multi-scrobbler/quickstart)
## Installation
[See the **Installation** documentation](/docsite/docs/installation/installation.md)
[See the **Installation** documentation](https://foxxmd.github.io/multi-scrobbler/installation)
## Configuration
[See the **Configuration** documentation](/docsite/docs/configuration/configuration.md)
[See the **Configuration** documentation](https://foxxmd.github.io/multi-scrobbler/configuration)
## Usage
@@ -90,7 +118,11 @@ On first startup you may need to authorize Spotify and/or Last.fm by visiting th
## Help/FAQ
Having issues with connections or configuration? Check the [FAQ](/docsite/docs/FAQ.md) before creating an issue!
Having issues with connections or configuration? Check the [FAQ](https://foxxmd.github.io/multi-scrobbler/FAQ) before creating an issue!
## Development
[Detailed architecture and development guides for Sources/Clients](https://foxxmd.github.io/multi-scrobbler/development/dev-common)
#- TZ=Etc/GMT # Specify timezone from TZ Database name found here https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
#- SPOTIFY_CLIENT_ID=
#- SPOTIFY_CLIENT_SECRET=
#- SPOTIFY_REDIRECT_URI=http://multi-scrobbler-host-IP:9078/callback ## Need to be whitelisted in Spotify dashboard and is used for creating the connection first time
#- MALOJA_URL=http://maloja:42010
#- MALOJA_API_KEY=
#- PUID=1000 # required if running docker on linux host, see main README Docker setup instructions
#- PGID=1000 # required if running docker on linux host, see main README Docker setup instructions
# set if using a source/client with redirect URI that you have not explicitly set and MS is NOT running on the same machine that you will view the dashboard from
# EX: You will view MS dashboard at 'http://192.168.0.101:9078' -> set BASE_URL=http://192.168.0.101:9078
#- BASE_URL=http://MyHostIP:9078
volumes:
- "./config:/config"
#networks:
# - (optional to add container to the same bridge network that maloja is inside to be able to use docker internal networking & dns to resolve and connect to maloja URL via http://maloja:port)
ports:
- 9078:9078# first port is the HOST port multi-scrobbler will serve UI and API on
- TZ=Etc/GMT# Specify timezone from TZ Database name found here https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
#- SPOTIFY_CLIENT_ID=
#- SPOTIFY_CLIENT_SECRET=
#- SPOTIFY_REDIRECT_URI=http://multi-scrobbler-host-IP:9078/callback ## Need to be whitelisted in Spotify dashboard and is used for creating the connection first time
#- MALOJA_URL=http://maloja:42010
#- MALOJA_API_KEY=
#- PUID=1000 # required if running docker on linux host, see main README Docker setup instructions
#- PGID=1000 # required if running docker on linux host, see main README Docker setup instructions
volumes:
- /path_on_host/multi-scrobbler-config:/config
#networks:
# - (optional to add container to the same bridge network that maloja is inside to be able to use docker internal networking & dns to resolve and connect to maloja URL via http://maloja:port)
ports:
- 9078:9078# first port is the HOST port multi-scrobbler will serve UI on
restart:unless-stopped
services:
multi-scrobbler:
image:foxxmd/multi-scrobbler
container_name:multi-scrobbler
environment:
- TZ=Etc/GMT# Specify timezone from TZ Database name found here https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
#- JELLYFIN_APIKEY=
#- JELLYFIN_URL=
#- JELLYFIN_USER=
#- MALOJA_URL=http://maloja:42010
#- MALOJA_API_KEY=
#- PUID=1000 # required if running docker on linux host, see main README Docker setup instructions
#- PGID=1000 # required if running docker on linux host, see main README Docker setup instructions
# set if using a source/client with redirect URI that you have not explicitly set and MS is NOT running on the same machine that you will view the dashboard from
# EX: You will view MS dashboard at 'http://192.168.0.101:9078' -> set BASE_URL=http://192.168.0.101:9078
#- BASE_URL=http://MyHostIP:9078
volumes:
- "./config:/config"
#networks:
# - (optional to add container to the same bridge network that maloja is inside to be able to use docker internal networking & dns to resolve and connect to maloja URL via http://maloja:port)
ports:
- 9078:9078# first port is the HOST port multi-scrobbler will serve UI and API on
These three [sources](/#source) are **ingress-based** which means that multi-scrobbler waits for the Plex/Tautulli/Jellyfin server to contact multi-scrobbler, as opposed to multi-scrobbler contacting the server.
These sources are **ingress-based** which means that multi-scrobbler waits for the Webscrobbler extension or app using Listenbrainz Endpoint to contact multi-scrobbler, as opposed to multi-scrobbler contacting them.
multi-scrobbler will log information about any server that connects to it for these three services. In the logs it looks something like this:
multi-scrobbler will log information about any server that connects to it for these services. In the logs it looks something like this:
```
2023-02-22T10:55:56-05:00 info : [Ingress - Plex ] Received request from a new remote address: ::ffff:192.168.0.140 (UA: PlexMediaServer/1.24.5.5173-8dcc73a59)
2023-02-22T10:55:56-05:00 info : [Ingress - Plex ] ::ffff:192.168.0.140 (UA: PlexMediaServer/1.24.5.5173-8dcc73a59) Received valid data from server examplePlex for the first time.
2023-02-22T10:55:56-05:00 warn : [Plex Request ] Received valid Plex webhook payload but no Plex sources are configured
[2025-07-10 12:32:21.417 -0400] INFO : [App] [API] [Ingress] [WebScrobbler] [Ingress] [WebScrobbler] Received request from a new remote address: ::ffff:127.0.0.1 (192.168.0.100) (UA: Mozilla/5.0 (X11; Linux x86_64; rv:139.0) Gecko/20100101 Firefox/139.0)
[2025-07-10 12:32:21.418 -0400] INFO : [App] [API] [Ingress] [WebScrobbler] [Ingress] [WebScrobbler] ::ffff:127.0.0.1 (192.168.0.100) (UA: Mozilla/5.0 (X11; Linux x86_64; rv:139.0) Gecko/20100101 Firefox/139.0) Received data for API URL slug '(none)' for the first time.
```
It also logs if a server tries to connect to a URL that it does not recognize:
```
2023-02-22T11:16:12-05:00 debug : [App ] Server received POST request from ::ffff:192.168.0.140 (UA: PlexMediaServer/1.24.5.5173-8dcc73a59) to unknown route: /plkex
2023-02-22T11:16:12-05:00 debug : [App ] Server received POST request from ::ffff:192.168.0.100 (UA: PlexMediaServer/1.24.5.5173-8dcc73a59) to unknown route: /plkex
```
**So, if you do not see either of these in your logs then Plex/Tautulli/Jellyfin is not able to connect to your multi-scrobbler instance at all.**
**So, if you do not see either of these in your logs then the Source is not able to connect to your multi-scrobbler instance at all.**
This is not something multi-scrobbler can fix and means you have an issue in your network.
### Troubleshooting
#### Troubleshooting
Check or try all these steps before submitting an issue:
#### Turn on Debug Logging
##### Turn on Debug Logging
First, turn on **debug** logging for multi-scrobbler by setting the environmental variable `LOG_LEVEL=debug`:
First, turn on **debug** logging for multi-scrobbler by setting the environmental variable `DEBUG_MODE=true`. Then, check the output for any additional information.
* using node `LOG_LEVEL=debug ... node src/index.js`
* using docker `docker run -e LOG_LEVEL=debug ... foxxmd/multi-scrobbler`
##### Check Host name and URL
Check the output for any additional information.
The URLs examples in the [configuration](/configuration#base-url) documentation assume you are running Webscrobbler/LFM Endpoint on the same server as multi-scrobbler. If these are not the same machine then you need to determine the IP address or domain name that multi-scrobbler is reachable at and use that instead of `localhost` when configuring these sources. **This is likely the same host name that you would use to access the web interface for multi-scrobbler.**
#### Check Host name and URL
EX `http://localhost:9078/api/webscrobbler` -> `http://192.168.0.140:9078/api/webscrobbler`
The URLs examples in the [configuration](configuration/configuration.md) documentation assume you are running Plex/Tautulli/Jellyfin on the same server as multi-scrobbler. If these are not the same machine then you need to determine the IP address or domain name that multi-scrobbler is reachable at and use that instead of `localhost` when configuring these sources. **This is likely the same host name that you would use to access the web interface for multi-scrobbler.**
EX `http://localhost:9078/plex` -> `http://192.168.0.140:9078/plex`
#### Check Firewall and Port Forwarding
##### Check Firewall and Port Forwarding
If the machine multi-scrobbler is running on has a firewall ensure that port **9078** is open. Or if it is in another network entirely make sure your router is forwarding this port and it is open to the correct machine.
#### Check Source Service Logs
##### Check Source Service Logs
Plex/Tautulli/Jellyfin all have logs that will log if they cannot connect to multi-scrobbler. Check these for further information.
###### Webscrobbler
##### Plex
See [Debugging the extension](https://github.com/web-scrobbler/web-scrobbler/wiki/Debug-the-extension) to get logs which should have information about failed requests.
Settings -> Manage -> Console
##### Tautulli
Check the command-line output of the application or docker logs.
##### Jellyfin
Administration -> Dashboard -> Advanced -> Logs
## Jellyfin has warnings about undefined or missing data
Make sure you have
* [Configured the webhook plugin correctly](configuration/configuration.md#jellyfin)
* Checked the **Send All Properties(ignores template)** option in the webhook settings and **Saved**
multi-scrobbler is known to work on Jellyfin `10.8.9` with Webhook version `11.0.0.0`.
You can verify the payload sent from the webhook by modifying your jellyfin configuration to include `logPayload: true` which will output the raw payload to DEBUG level logging:
```json
[
{
"name":"MyJellyfin",
"clients":[],
"data":{
"users":["FoxxMD"],
"options":{
"logPayload":true
}
}
}
]
```
If your issue persists and you open an Issue for it please include the raw payload logs in your report.
## Spotify/Deezer/LastFM won't authenticate
### Spotify/Deezer/LastFM won't authenticate
Ensure any **client id** or **secrets** are correct in your configuration.
@@ -109,9 +60,31 @@ If multi-scrobbler is not running on the same machine your browser is on then th
EX `http://localhost:9078/lastfm/callback` -> `http://192.168.0.220:9078/lastfm/callback`
# Configuration Issues
### Youtube Music fails after some time
## Config could not be parsed
If you see errors in multi-scrobbler for YTM that contain **401** or **403** like
```
Error: Could not send the specified request to browse. Status code: 401
```
then YTM has invalidated your authentication.
First, ensure you are NOT using [YoutubeTV authentication.](/configuration/sources/youtube-music?ytmAuth=ytt#authentication) If you completed authentication by entering a "User Code" you are using YoutubeTV which has stopped working. You should reauthenticate using **Cookies** or **Custom OAuth.**
#### When using OAuth Client Authentication
Refresh your authentication by using the **(Re)authenticate** link from MS's web dashboard.
#### When using Cookies Authentication
The library MS uses relies on scraping the YTM site by using cookies from your actual browser to pretend it is a browser. It does its best to keep these up to date but since this is not an official way to access the service YTM may invalidate your access _to the authenticated session_ at any time. How this is triggered is unknown and not something multi-scrobbler can control. You can help limit the chance of your session being invalidated by [getting the cookie from an Incognito/Private Session](https://github.com/LuanRT/YouTube.js/issues/803#issuecomment-2504032666) and then immediately closing the browser afterwards.
To re-authenticate MS [follow the YTM instructions to retrieve a new set of cookies for multi-scrobbler](/configuration/sources/youtube-music?ytmAuth=cookie#authentication) and then restart MS to potentially resolve the problem.
## Configuration Issues
### Config could not be parsed
If you see something like this in your logs:
@@ -123,14 +96,124 @@ If you see something like this in your logs:
It means the JSON in your configuration file is not valid. Copy and paste your configuration into a site like [JSONLint](https://jsonlint.com/) to find out where errors you have and fix them.
# Scrobbling Issues
## Scrobbling Issues
## Last.fm does not scrobble tracks with multiple artists correctly
### Last.fm does not scrobble tracks with multiple artists correctly
This is a limitation of the [Last.fm API](https://www.last.fm/api/show/track.scrobble) where the **artist** field is only one string and Last.fm does not recognize (play well) with "combined" artists.
Multi-scrobbler works the same was the official Spotify-Last.fm integration works -- it only scrobbles the **first** artist on a multi-artist track.
## Jellyfin does not scrobble tracks with multiple artists correctly
### Google Cast track information is missing/incorrect or MS player has weird times
This is a limitation caused by the [Jellyfin webhook plugin](https://github.com/FoxxMD/multi-scrobbler/issues/70#issuecomment-1443804712) only sending the first artist to multi-scrobbler. This issues needs to be [fixed upstream on the Jellyfin webhook repository.](https://github.com/jellyfin/jellyfin-plugin-webhook/issues/166)
The Google Cast integration relies on a few common fields in the data it receives from your casting device. Every platform that can cast (Spotify, Pandora, etc...) *should* use these fields the same but there are slight differences between their implementations that may confuse multi-scrobbler. Specific platforms may also return more information in non-common fields that are undocumented.
To diagnose these issues you [**must enable payload logging**](/configuration/sources/google-cast#cast-troubleshooting) for your google cast Source, run MS, and then include logs with this output from that run. Without the raw data logged from your cast device it will be nearly impossible to resolve your issue.
### Google Cast device does not track media
It is likely the app playing on the cast device is incorrectly reporting the media type as **not music**.
MS logs will tell you what type the media is reported as with lines like:
```
My Artist - Example Track has 'unknown' media type and allowUnknownMedia=false, will not track
```
Refer to [Allow Unknown Media Type](/configuration/sources/google-cast#allow-unknown-media-type) section to fix this
```
My Artist - Example Track has 'movie' media type so will not track
```
Refer to [Force Media Tracking](/configuration/sources/google-cast#forcing-media-tracking) section to fix this
### VLC is not scrobbling fields correctly
Before reporting an issue turn on metadata logging in the MS VLC configuration, [see the VLC documentation.](/configuration/sources/vlc#vlc-information-reporting)
### Youtube Music misses or duplicates scrobbles
<details>
<summary>How MS detects YTM history</summary>
In order for multi-scrobbler to accurately determine if a song has been scrobbled it needs **a source of truth.** For YTM this is a "history" list scraped from the YTM website. Unfortunately, the data in this list can be (often) inconsistent which makes it hard for multi-scrobbler to "trust" that it is correct and determine when/if new scrobbles occur. This inconsistency is not something multi-scrobbler can control -- it is a side-effect of having to use an unofficial method to access YTM (scraping).
To compensate for this multi-scrobbler resets when it considers this list the "source of truth" based on if the list changes in an inconsistent way between consecutive checks. New scrobbles can only be detected when this list is "OK" as a source of truth for N+1 checks. Therefore, any new tracks that appear when the list is inconsistent will be ignored.
Duplicate scrobbles can also occur if the change between two checks is technically consistent. For instance, if you listen to a track twice in some period, separated by other music, YTM will sometimes "remove" the track from the earlier time (further down in your history) and "re-add" it at the top of the history.
</details>
#### Reporting YTM scrobble issues
If you experience scrobble detection issues you can help improve MS's YTM heureistic by providing thorough feedback as [an issue.](https://github.com/FoxxMD/multi-scrobbler/issues/new?assignees=&labels=bug&projects=&template=01-bug-report.yml&title=bug%3A+) **Please do the following to provide the most useful report:**
##### Turn on Change Detection
In your YTM configuration (`ytmusic.json`) add `logDiff` under `options` like this:
```json
{
"type":"ytmusic",
"name":"MyYTM",
"data":{...},
"options":{
"logDiff":true
}
}
```
or set either ENVs:
*`YTM_LOG_DIFF=true`
* [`DEBUG_MODE=true`](/configuration#debug-mode)
This will cause MS to log YTM history changes similar to this:
```
[Ytmusic - MyYTM] Changes from last seen list:
1. (tuhe1CpHRxY) KNOWER - I’m The President --- undefined => Moved - Originally at 6
2. (Mtg8V6Xa2nc) Vulfpeck - Romanian Drinking Song --- Schvitz => Moved - Originally at 1
3. (rxbCaiyYSXM) Nightmares On Wax - You Wish --- In A Space Outta Sound => Moved - Originally at 2
4. (tMt_YXr90AM) Gorillaz - O Green World --- undefined => Moved - Originally at 3
...
```
Which are essential to troubleshooting this behavior.
##### Turn on File Logging
Optionally, if your logs are busy due to many other sources, you can log **only** YTM logs to a file. Turn this on with the `logToFile` config option:
```json
{
"type":"ytmusic",
"name":"MyYTM",
"data":{...},
"options":{
"logDiff":true,
"logToFile":true
}
}
```
This will cause MS to write YTM logs to a file in your `logs` folder named `ytmusic-MyYTM.log` (based on your source name).
##### Provide Detail and Context
Provide a detailed account of how you were using YTM when the issue occurred, including things like:
* the platform listening on (desktop, mobile, 3rd party client, etc...)
* any changes in platform
* > I switched from desktop to listening on my phone...
* how you were listening to music
* > I was playing an album start to finish
* > I listened to two songs in a row, then browsed for a new song in library by artist, then went back to a song in the queue...
Explain the expected behavior (it should have scrobbled songs x, y, then z) and what actually happened (it scrobbled songs x, then y, then x again, then z)
Provide ALL logs from the time when the issue occurred including logs from BEFORE (ideally 2-3 minutes of logs) and AFTER the issue.
import ScrobbleThreshold from "@site/src/components/snippets/_scrobble-threshold.mdx"
A **Client** is an application that stores the historical information about what songs you have played (scrobbles). Examples are **Maloja, Last.fm, Listenbrainz**...
If multi-scrobbler is unable to submit a scrobble to a Client then it places the scrobble into a queue which is retried every 5 minutes for a number of times before it gives up.
After it stops _automatically_ retrying the scrobble still exists and can be retried from the dashboard. Dead scrobbles persist between restart so you will never lose a scrobble that hasn't been successfully submitted yet.
### Now Playing
Multi-scrobbler can report the currently **playing** tracks it is monitoring to some Scrobble Clients via their individual **Now Playing** functionality.
Now Playing is **default enabled** for all Clients that support it.
The behavior multi-scrobbler uses for determining Now Playing reporting:
* Now Playing can be explicitly enabled or disabled globally using ENV `NOW_PLAYING=true` or `NOW_PLAYING=false`
* This only affects Clients that don't have behavior set via File/AIO (below)
* Now Playing will be only be reported to the same Clients a Source is configured to scrobble to
* A Source's Player status must be **Playing**, IE **active**, for reporting to occur
* This status can be verified via Player UI on the Dashboard
* If multiple Sources having active Players then the scrobble Client will default to reporting the track based on Source **configuration** name, alphabetically
* This is the `name` property set in [File or AIO source configs](/configuration?configType=file#configuration-types)
Clients can customize the Now Playing behavior individually using [File or AIO source configs:](/configuration?configType=file#configuration-types)
```json5 title="CONFIG_DIR/lastfm.json"
[
{
"name": "myLastFmClient",
"configureAs": "client",
"data": {
// ...
},
"options": {
// disable or enable
//"nowPlaying": true
//
// OR define a list of Source *config* names that should be allowed to report Now Playing
// order of the list determines priority for reporting Now Playing
import JsonConfig from '!!raw-loader!@site/../config/koito.json.example';
:::tip[Other Uses]
To monitor and re-scrobble activity from a Koito account create a [Koito (Source)](/configuration/sources/koito-source)
:::
Setup a [Koito server](https://koito.io/guides/installation/) if you have not already done this.
* [Create (or get) an API Key](https://koito.io/guides/scrobbler/)
* From your Koito server dashboard navigate to **Settings** (bottom-left icon) => **API Keys**
* Create a new Key (or use an existing) and Copy the key using the copy icon to the right of the key
* EX `pM195xPV98CDpk0QW47FIIOR8AKATAX5DblBF-Jq0t1MbbKL`
* Take note of your Koito username (used below as `KOITO_USER`)
* Determine your Koito URL
* This is the URL you use to access your Koito dashboard OR whatever URL is accessible from your multi-scrobbler instance.
* EX dashboard at `http://192.168.0.100:4110` => `KOITO_URL` is set to `http://192.168.0.100:4110`
:::note[Koito Base URL]
Using the URL path `/apis/listenbrainz` [Koito docs describe](https://koito.io/guides/scrobbler/) **is not required.** The Koito URL used for multi-scrobbler should only be the **base** URL. Only include a URL path if your Koito **base** url is not at `host:port`. See the table below for examples.
<details>
<summary>Base URL Examples</summary>
| `KOITO_URL` | MS Detected Base URL | Example Scrobble Api URL |
Ensure that Koito is configured to allow requests from multi-scrobbler! In Koito config set [`KOITO_ALLOWED_HOSTS`](https://koito.io/reference/configuration/#koito_allowed_hosts) to the IP the multi-scrobbler dashboard is accessible from.
import JsonConfig from '!!raw-loader!@site/../config/lastfm.json.example';
:::tip[Other Users]
* To monitor and re-scrobble activity from your Last.fm account create a [Last.fm (Source)](/configuration/sources/lastfm-source)
* To accept scrobbles from outside applications as if multi-scrobbler was Last.fm, create a [Last.fm (Endpoint)](/configuration/sources/lastfm-endpoint)
:::
[Register for an API account here.](https://www.last.fm/api/account/create)
The Callback URL is actually specified by multi-scrobbler but to keep things consistent you should use
```
http://localhost:9078/lastfm/callback
```
or replace `localhost:9078` with your own base URL.
| `LASTFM_API_KEY` | Yes | | Api Key from your API Account |
| `LASTFM_SECRET` | Yes | | Shared secret from your API Account |
| `LASTFM_REDIRECT_URI` | No | `http://localhost:9078/lastfm/callback` | Url to use for authentication. Must include `lastfm/callback` somewhere in it |
| `LASTFM_SESSION` | No | | Session id. Will be generated by authentication flow if not provided. |
import JsonConfig from '!!raw-loader!@site/../config/listenbrainz.json.example';
:::tip[Other Users]
* To monitor and re-scrobble activity from a Listenbrainz account create a [Listenbrainz (Source)](/configuration/sources/listenbrainz-source)
* To accept scrobbles from outside applications as if multi-scrobbler was a Listenbrainz server, create a [Listenbrainz (Endpoint)](/configuration/sources/listenbrainz-endpoint)
:::
You will need to run your own Listenbrainz server or have an account [on the official instance](https://listenbrainz.org/login/)
On your [profile page](https://listenbrainz.org/profile/) find your **User Token** to use in the configuration.
import Bluesky from "@site/src/components/snippets/_bluesky.mdx";
import JsonConfig from '!!raw-loader!@site/../config/rocksky.json.example';
:::tip[Other Uses]
To monitor and re-scrobble activity **from** Rocksky create a [Rocksky (Source)](/configuration/sources/rocksky-source)
:::
[**Rocksky**](https://rocksky.app/) is music tracking and discovery platform built on the AT Protocol (Bluesky).
<details>
<summary>How does that work?</summary>
<Bluesky name="Rocksky" lexicon="app.rocksky"/>
</details>
:::warning[Public Data]
The default Bluesky PDS is **public**, meaning your scrobbles are also public (read-only). This isn't any different than using Last.fm or Listenbrainz, in terms of privacy.
:::
:::warning[No Scrobbling Guarantee]
Rocksky **does not guarantee** that every scrobble you send will be persisted. Rocksky's design goal is to store scrobbles with **metdata-complete, 3rd party verifiable track information** so that it can be a metadata-rich discovery platform, rather than an exhaustive scrobble data source-of-truth.
To do this it *asynchronously* matches your scrobble data against Musicbrainz's and/or Spotify's catalogs, *after the scrobble has been submitted.* **If the scrobble does not end up matching then it is not persisted.**
If you need your scrobbles to be 100% captured then you should scrobble to an *additional* [Scrobble Client](/configuration/clients).
:::
## Setup
You will need a [Bluesky](https://bsky.app/) account to use Rocksky.
:::note
Usage is adapted from [Rocksky docs](https://docs.rocksky.app/migrating-to-rocksky-scrobble-api-957839m0) on [docs.rocksky.app](https://docs.rocksky.app)
:::
* Navigate to [rocksky.app](https://rocksky.app/) and sign up/logn using your Bluesky account
* Then, navigate to the [RockSky developer dashboard](https://rocksky.app/apikeys) and obtain an API Key
## Configuration
### Handle
The handle used with multi-scrobbler should be your **full** ATProto handle, including TLD.
* For regular Bluesky account this will be like: `alice.bsky.social`
* For Bluesky accounts that [use their website as their account](https://bsky.social/about/blog/4-28-2023-domain-handle-tutorial) this is your domain: `mydomain.com`
* For non-Bluesky users, you probably already know your handle
If you do not include a TLD then multi-scrobbler will automatically append `.bsky.social` to your handle value.
import JsonConfig from '!!raw-loader!@site/../config/tealfm.json.example';
import Bluesky from "@site/src/components/snippets/_bluesky.mdx";
:::tip[Other Uses]
To monitor and re-scrobble activity from a ATProto Profile create a [teal.fm (Source)](/configuration/sources/tealfm-source)
:::
[**teal.fm**](https://teal.fm/) is a social discovery and web viewing service for scrobbles stored/available on the ATProto network/your repository in a [PDS (Personal Data Server)](https://github.com/Bluesky-social/pds).
<details>
<summary>How does that work?</summary>
<Bluesky name="teal.fm" lexicon="fm.teal"/>
</details>
:::warning
The default Bluesky PDS is **public**, meaning your scrobbles are also public (read-only). This isn't any different than using Last.fm or Listenbrainz, in terms of privacy.
:::
:::note
[teal.fm is still in developement](https://notes.teal.fm/3lzlwe6puis2l) and does not have a public website, yet.
To view your teal.fm scrobbles you can:
* use [wrapped.baileytownsend.dev](https://wrapped.baileytownsend.dev/) which gives a simple stats overview of your scrobbles
* use [teal-slice.wisp.place](https://tealfm-slice.wisp.place/) for a preview of teal.fm
* use [atproto.at](https://atproto.at) to view the raw data (convenience URLs are generated in multi-scrobbler for you to do this)
:::
## Setup
#### Bluesky
* Create a [Bluesky](https://bsky.app) account, if you don't already have one
* Navigate to https://bsky.app/settings/app-passwords
* Settings -> Privacy and Security -> App passwords
* Create a new **App Password** and save it somewhere, it will not be shown again
## Configuration
### Identifier
The ATPRoto identifier used with multi-scrobbler should be either:
* A valid [DID](https://atproto.com/specs/did#at-protocol-did-identifier-syntax), starting with `did:plc:...` or `did:web:...`
* Your **full** ATProto handle, including TLD
If using a handle:
* For regular Bluesky account this will be like: `alice.bsky.social`
* For Bluesky accounts that [use their domain as their account](https://bsky.social/about/blog/4-28-2023-domain-handle-tutorial) this is your domain: `mydomain.com`
* For non-Bluesky-PDS users, you probably already know your handle
If your identifier does not look like a DID and does not include a TLD then multi-scrobbler will automatically append `.bsky.social` to your handle value.
[**Sources** and **Clients**](/#how-does-multi-scrobbler-ms-work) are configured using environmental (ENV) variables and/or json files.
**MS will parse configuration from both configuration types.** You can mix and match configurations but it is generally better to stick to one or the other.
TIP: Check the [**FAQ**](../FAQ.md) if you have any issues after configuration!
## ENV-Based Configuration
This is done by passing environmental variables and so does not require any files to run MS.
* Using a docker container EX `docker run -e "SPOTIFY_CLIENT_ID=yourId" -e "SPOTIFY_CLIENT_SECRET=yourSecret" ...`
* Using a local installations by exporting variables before running MS EX `SPOTIFY_CLIENT_ID=yourId SPOTIFY_CLIENT_SECRET=yourSecret node index.js`
Use ENV-based configuration if:
* You are the only person for whom MS is scrobbling for
* You have a very simple setup for MS such as one scrobble [Client](/#client) and one [Source](/#source) IE Plex -> Maloja
## File-Based Configuration
MS will parse configuration files located in the directory specified by the `CONFIG_DIR` environmental variable. This variable defaults to:
* Local installation -> `PROJECT_DIR/config`
* Docker -> `/config` (in the container) -- see the [install docs](../installation/installation.md#docker) for how to configure this correctly
Use File-based configuration if:
* You have many [Sources](/#source)
* You have many of each type of **Source** you want to scrobble from IE 2x Plex accounts, 3x Spotify accounts, 1x
Funkwhale...
* You have more than one scrobble **Client** you want to scrobble to IE multiple Maloja servers
* You want only some **Sources** to scrobble to some **Clients** IE Fred's Spotify account scrobbles to Fred's Maloja
server, but not Mary's Maloja server
File-based configurations located in the `CONFIG_DIR` directory can be parsed from
* an **all-in-one** config file named `config.json` that contains information for all Sources and Clients and/or
* many **specific** files named based on the client/source to configure IE `plex.json``spotify.json`
There are **example configurations** for all Source/Client types and AIO config located in the [/config](https://github.com/FoxxMD/multi-scrobbler/tree/master/config) directory of this project. These can be used as-is by renaming them to `.json`.
For docker installations these examples are copied to your configuration directory on first-time use.
There is also a [**kitchensink example**](kitchensink.md) that provides examples of using all sources/clients in a complex configuration.
### All-in-One File Configuration
[**Explore the schema for this configuration, along with an example generator and validator, here**](https://json-schema.app/view/%23?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fdevelop%2Fsrc%2Fcommon%2Fschema%2Faio.json)
Example directory structure:
```
/CONFIG_DIR
config.json
```
<details>
<summary>Config Example</summary>
```json5 title="config.json"
{
//...
"sources": [
{
"name": "myConfig",
"type": "spotify",
"clients": [
"myMalojaClient"
],
"data": {
"clientId": "anExample"
//...
}
}
],
"clients": [
{
"name": "myFirstMalojaClient",
"type": "maloja",
"data": {
"url": "http://myMalojaServer.example",
// ...
}
}
]
}
```
</details>
`config.json` can also be used to set default behavior for all sources/clients using `sourceDefaults` and `clientDefaults` properties.
See [config.json.example](https://github.com/FoxxMD/multi-scrobbler/blob/master/config/config.json.example) for an annotated example or check out [the kitchen sink example](kitchensink.md).
### Specific File Configuration
Each file is named by the **type** of the Client/Source found in below sections. Each file as an **array** of that type of Client/Source.
Example directory structure:
```
/CONFIG_DIR
plex.json
spotify.json
maloja.json
```
<details>
<summary>Config Example</summary>
```json5
// in maloja.json
[
{
"name": "myFirstMalojaClient",
"data": {
"url": "http://myMalojaServer.example",
"apiKey": "myKey"
}
},
{
"name": "mySecondMalojaClient",
"data": {
"url": "http://my2ndMalojaServer.example",
"apiKey": "myKey"
}
}
]
```
</details>
See the [/config](https://github.com/FoxxMD/multi-scrobbler/blob/master/config) directory of this project for examples of each type of config file or reference specific files below.
# Source Configurations
## [Spotify](https://www.spotify.com)
To access your Spotify history you must [register an application](https://developer.spotify.com/dashboard) to get a
Client ID/Secret. Make sure to also whitelist your redirect URI in the application settings.
| `SPOTIFY_REDIRECT_URI` | No | `http://localhost:9078/callback` | URI must end in `callback` |
### File-Based
See [`spotify.json.example`](https://github.com/FoxxMD/multi-scrobbler/blob/master/config/spotify.json.example) or [explore the schema with an example and live editor/validator](https://json-schema.app/view/%23/%23%2Fdefinitions%2FSpotifySourceConfig?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fdevelop%2Fsrc%2Fcommon%2Fschema%2Fsource.json)
## [Plex](https://plex.tv)
Check the [instructions](plex.md) on how to setup a [webhooks](https://support.plex.tv/articles/115002267687-webhooks) to scrobble your plays.
| `PLEX_USER` | No | | The a comma-delimited list of usernames to scrobble tracks for. No usernames specified means all tracks by all users will be scrobbled. |
### File-Based
See [`plex.json.example`](https://github.com/FoxxMD/multi-scrobbler/blob/master/config/plex.json.example) or [explore the schema with an example and live editor/validator](https://json-schema.app/view/%23/%23%2Fdefinitions%2FPlexSourceConfig?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fdevelop%2Fsrc%2Fcommon%2Fschema%2Fsource.json)
## [Tautulli](https://tautulli.com)
Check the [instructions](plex.md) on how to setup a notification agent.
| `TAUTULLI_USER` | No | | The a comma-delimited list of usernames to scrobble tracks for. No usernames specified means all tracks by all users will be scrobbled. |
### File-Based
See [`tautulli.json.example`](https://github.com/FoxxMD/multi-scrobbler/blob/master/config/tautulli.json.example) or [explore the schema with an example and live editor/validator](https://json-schema.app/view/%23/%23%2Fdefinitions%2FTautulliSourceConfig?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fdevelop%2Fsrc%2Fcommon%2Fschema%2Fsource.json)
## [Subsonic](http://www.subsonic.org/)
Can use this source for any application that implements the [Subsonic API](http://www.subsonic.org/pages/api.jsp) (such as [Airsonic](https://airsonic.github.io/))
**Known Issues:**
* "Time played at" is somewhat inaccurate since the api only reports "played X minutes ago" so...
* All scrobble times are therefore "on the minute" and you may experience occasional duplicate scrobbles
* "played X minutes ago" sometimes is also not reported correctly
* Multiple artists are reported as one value and cannot be separated
* If using [Airsonic Advanced](https://github.com/airsonic-advanced/airsonic-advanced) the password used (under **Credentials**) must be **Decodable**
| `SUBSONIC_URL` | Yes | | Base url of your subsonic-api server |
### File-Based
See [`subsonic.json.example`](https://github.com/FoxxMD/multi-scrobbler/blob/master/config/subsonic.json.example) or [explore the schema with an example and live editor/validator](https://json-schema.app/view/%23/%23%2Fdefinitions%2FSubSonicSourceConfig?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fdevelop%2Fsrc%2Fcommon%2Fschema%2Fsource.json)
## [Jellyfin](https://jellyfin.org/)
Must be using Jellyfin 10.7 or greater
* In the Jellyfin desktop web UI Navigate to -> Administration -> Dashboard -> Plugins -> Catalog
* Under Notifications -> **Webhook** -> Install, then restart your server
* Navigate back to -> Administration -> Dashboard -> Plugins -> My Plugins -> Webhook
| `JELLYFIN_USER` | | | Comma-separated list of usernames (from Jellyfin) to scrobble for |
| `JELLYFIN_SERVER` | | | Comma-separated list of Jellyfin server names to scrobble from |
### File-Based
See [`jellyfin.json.example`](https://github.com/FoxxMD/multi-scrobbler/blob/master/config/jellyfin.json.example) or [explore the schema with an example and live editor/validator](https://json-schema.app/view/%23/%23%2Fdefinitions%2FJellySourceConfig?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fdevelop%2Fsrc%2Fcommon%2Fschema%2Fsource.json)
## [Last.fm (Source)](https://www.last.fm)
See the [Last.fm (Client)](#lastfm) setup for registration instructions. You may need to disable "Hide recent listening information" on your [privacy page](https://www.last.fm/settings/privacy) for this to work.
### ENV-Based
No support for ENV based for Last.fm as a client (only source)
### File-Based
See [`lastfm.json.example`](https://github.com/FoxxMD/multi-scrobbler/blob/master/config/lastfm.json.example), change `configureAs` to `source`. Or [explore the schema with an example and live editor/validator](https://json-schema.app/view/%23/%23%2Fdefinitions%2FLastfmSourceConfig?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fdevelop%2Fsrc%2Fcommon%2Fschema%2Fsource.json)
You will need to run your own Listenbrainz server or have an account [on the official instance](https://listenbrainz.org/login/)
On your [profile page](https://listenbrainz.org/profile/) find your **User Token** to use in the configuration.
**NOTE:** You cannot use ENV variables shown in the [Listenbrainz Client config](#listenbrainz) -- multi-scrobbler assumes Listenbrainz ENVs are always used for the **client** configuration. You must use the file-based config from below to setup Listenbrainz as a Source.
### File-Based
See [`listenbrainz.json.example`](https://github.com/FoxxMD/multi-scrobbler/blob/master/config/listenbrainz.json.example) or [explore the schema with an example and live editor/validator](https://json-schema.app/view/%23%2Fdefinitions%2FListenBrainzSourceConfig?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fdevelop%2Fsrc%2Fcommon%2Fschema%2Fsource.json)
**Change `configureAs` to `source`**
## [Deezer](https://deezer.com/)
Create a new application at [Deezer Developers](https://developers.deezer.com/myapps)
* Application Domain must be the same as your multi-scrobbler domain. Default is `localhost:9078`
* Redirect URL must end in `deezer/callback`
* Default would be `http://localhost:9078/deezer/callback`
After application creation you should have credentials displayed in the "My Apps" dashboard. You will need:
* **Application ID**
* **Secret Key**
* **Redirect URL** (if not the default)
**If no access token is provided...**
After starting multi-scrobbler with credentials in-place open the dashboard (`http://localhost:9078`) and find your Deezer source. Click **(Re)authenticate and (re)start polling** to start the login process. After login is complete polling will begin automatically.
| `DEEZER_CLIENT_ID` | Yes | | Your **Application ID** |
| `DEEZER_CLIENT_SECRET` | Yes | | Your **Secret Key** |
| `DEEZER_REDIRECT_URI` | No | `http://localhost:9078/deezer/callback` | URI must end in `deezer/callback` |
### File-Based
See [`deezer.json.example`](https://github.com/FoxxMD/multi-scrobbler/blob/master/config/deezer.json.example) or [explore the schema with an example and live editor/validator](https://json-schema.app/view/%23/%23%2Fdefinitions%2FDeezerSourceConfig?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fdevelop%2Fsrc%2Fcommon%2Fschema%2Fsource.json)
## [Youtube Music](https://music.youtube.com)
Credentials for YT Music are obtained from a browser request to https://music.youtube.com **once you are logged in.** [Specific requirements are here and summarized below:](https://github.com/nickp10/youtube-music-ts-api/blob/master/DOCUMENTATION.md#authenticate)
* Open a new tab
* Open the developer tools (Ctrl-Shift-I) and select the “Network” tab
* Go to https://music.youtube.com and ensure you are logged in
Then...
1. Find and select an authenticated POST request. The simplest way is to filter by /browse using the search bar of the developer tools. If you don’t see the request, try scrolling down a bit or clicking on the library button in the top bar.
2. **Make sure **Headers** pane is selected and open
3. In the **Request Headers** section find and copy the **entire value** found after `Cookie:` and use this as the `cookie` value in your multi-scrobbler config
4. If present, in the **Request Headers** section find and copy the number found in `X-google-AuthUser` and use this as the value for `authUser` in your multi-scrobbler config

NOTES:
* YT Music authentication is "browser based" which means your credentials may expire after a (long?) period of time OR if you log out of https://music.youtube.com. In the event this happens just repeat the steps above to get new credentials.
* Communication to YT Music is **unofficial** and not supported or endorsed by Google. This means that **this integration may stop working at any time** if Google decides to change how YT Music works in the browser.
### File-Based
See [`ytmusic.json.example`](https://github.com/FoxxMD/multi-scrobbler/blob/master/config/ytmusic.json.example) or [explore the schema with an example and live editor/validator](https://json-schema.app/view/%23/%23%2Fdefinitions%2FYTMusicSourceConfig?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fdevelop%2Fsrc%2Fcommon%2Fschema%2Fsource.json)
MPRIS is a standard interface for communicating with Music Players on **linux operating systems.**
If you run Linux and have a notification tray that shows what media you are listening to, you likely have access to MPRIS.

multi-scrobbler can listen to this interface and scrobble tracks played by **any media player** that communicates to the operating system with MPRIS.
**NOTE:** multi-scrobbler needs to be running as a [**Local Installation**](../installation/installation.md#local) in order to use MPRIS. This cannot be used from docker.
| MPRIS_ENABLE | No | | Use MPRIS as a Source (useful when you don't need any other options) |
| MPRIS_BLACKLIST | No | | Comma-delimited list of player names not to scrobble from |
| MPRIS_WHITELIST | No | | Comma-delimited list of players names to ONLY scrobble from. Overrides blacklist |
### File-Based
See [`mpris.json.example`](https://github.com/FoxxMD/multi-scrobbler/blob/master/config/mpris.json.example) or [explore the schema with an example and live editor/validator](https://json-schema.app/view/%23%2Fdefinitions%2FMPRISSourceConfig?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fdevelop%2Fsrc%2Fcommon%2Fschema%2Fsource.json)
## [Mopidy](https://mopidy.com/)
Mopidy is a headless music server that supports playing music from many [standard and non-standard sources such as Pandora, Bandcamp, and Tunein.](https://mopidy.com/ext/)
multi-scrobbler can scrobble tracks played from any Mopidy backend source, regardless of where you listen to them.
### File-Based
See [`mopidy.json.example`](https://github.com/FoxxMD/multi-scrobbler/blob/master/config/mopidy.json.example) or [explore the schema with an example and live editor/validator](https://json-schema.app/view/%23%2Fdefinitions%2FMopidySourceConfig/%23%2Fdefinitions%2FMopidyData?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fdevelop%2Fsrc%2Fcommon%2Fschema%2Fsource.json)
Configuration Options:
##### `url`
The URL used to connect to the Mopidy server. You MUST have [Mopidy-HTTP extension](https://mopidy.com/ext/http) enabled.
If no `url` is provided a default is used which assumes Mopidy is installed on the same server as multi-scrobbler: `ws://localhost:6680/mopidy/ws/`
Make sure the hostname and port number match what is found in the Mopidy configuration file `mopidy.conf`:
```
...
[http]
hostname = localhost
port = 6680
...
```
The URL used to connect ultimately must be formed like this: `[protocol]://[hostname]:[port]/[path]`
If any part of this URL is missing multi-scrobbler will use a default value, for your convenience. This also means that if any part of your URL is **not** standard you must explicitly define it.
Part => Default Value
* Protocol => `ws://`
* Hostname => `localhost`
* Port => `6680`
* Path => `/mopidy/ws/`
<details>
<summary>URL Transform Examples</summary>
```json
{
"url": "mopidy.mydomain.com"
}
```
MS transforms this to: `ws://mopidy.mydomain.com:6680/mopidy/ws/`
```json
{
"url": "192.168.0.101:3456"
}
```
MS transforms this to: `ws://192.168.0.101:3456/mopidy/ws/`
```json
{
"url": "mopidy.mydomain.com:80/MOPWS"
}
```
MS transforms this to: `ws://mopidy.mydomain.com:80/MOPWS`
</details>
#### URI Blacklist/Whitelist
If you wish to disallow or only allow scrobbling from some sources played through Mopidy you can specify these using `uriBlacklist` or `uriWhitelist` in your config. multi-scrobbler will check the list to see if any string matches the START of the `uri` on a track. If whitelist is used then blacklist is ignored. All strings are case-insensitive.
EX:
```json
{
"uriBlacklist": ["soundcloud"]
}
```
Will prevent multi-scrobbler from scrobbling any Mopidy track that start with a `uri` like `soundcloud:song:MySong-1234`
#### Album Blacklist
For certain sources (Soundcloud) Mopidy does not have all track info (Album) and will instead use "Soundcloud" as the Album name. You can prevent multi-scrobbler from using this bad Album data by adding the fake Album name to this list. Multi-scrobbler will still scrobble the track, just without the bad data. All strings are case-insensitive.
EX:
```json
{
"albumBlacklist": ["SoundCloud", "Mixcloud"]
}
```
If a track would be scrobbled like `Album: Soundcloud, Track: My Cool Track, Artist: A Cool Artist`
then multi-scrobbler will instead scrobble `Track: My Cool Track, Artist: A Cool Artist`
## [JRiver](https://jriver.com/)
In order for multi-scrobbler to communicate with JRiver you must have [Web Server Interface](https://wiki.jriver.com/index.php/Web_Service_Interface#Documentation_of_Functions) enabled. This can can be in the JRiver GUI:
* Tools -> Options -> Media Network
* Check `Use Media Network to share this library...`
* If you have `Authentication` checked you will need to provide the **Username** and **Password** in the ENV/File configuration below.
#### URL
If you do not provide a URL then a default is used which assumes JRiver is installed on the same server as multi-scrobbler: `http://localhost:52199/MCWS/v1/`
* Make sure the port number matches what is found in `Advanced` section in the [Media Network](#jriver) options.
* If your installation is on the same machine but you cannot connect using `localhost` try `0.0.0.0` instead.
The URL used to connect ultimately must be formed like this: `[protocol]://[hostname]:[port]/[path]`
If any part of this URL is missing multi-scrobbler will use a default value, for your convenience. This also means that if any part of your URL is **not** standard you must explicitly define it.
Part => Default Value
* Protocol => `http://`
* Hostname => `localhost`
* Port => `52199`
* Path => `/MCWS/v1/`
<details>
<summary>URL Transform Examples</summary>
```json
{
"url": "jriver.mydomain.com"
}
```
MS transforms this to: `http://jriver.mydomain.com:52199/MCWS/v1/`
```json
{
"url": "192.168.0.101:3456"
}
```
MS transforms this to: `http://192.168.0.101:3456/MCWS/v1/`
```json
{
"url": "mydomain.com:80/jriverReverse/MCWS/v1/"
}
```
MS transforms this to: `http://mydomain.com:80/jriverReverse/MCWS/v1/`
| JRIVER_URL | Yes | http://localhost:52199/MCWS/v1/ | The URL of the JRiver server |
| JRIVER_USERNAME | No | | If authentication is enabled, the username set |
| JRIVER_PASSWORD | No | | If authenticated is enabled, the password set |
### File-Based
See [`jriver.json.example`](https://github.com/FoxxMD/multi-scrobbler/blob/master/config/jriver.json.example) or [explore the schema with an example and live editor/validator](https://json-schema.app/view/%23%2Fdefinitions%2FJRiverSourceConfig/%23%2Fdefinitions%2FJRiverData?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fdevelop%2Fsrc%2Fcommon%2Fschema%2Fsource.json)
## [Kodi](https://kodi.tv/)
In order for multi-scrobbler to communicate with Kodi you must have the [Web Interface](https://kodi.wiki/view/Web_interface) enabled. This can can be in the Kodi GUI:
* Settings -> Services -> Control
* Check `Allow remote control via HTTP`
* Ensure you have a **Username** and **Password** set, you will need to provide them in the ENV/File configuration below.
#### URL
If you do not provide a URL then a default is used which assumes Kodi is installed on the same server as multi-scrobbler: `http://localhost:8080/jsonrpc`
* Make sure the port number matches what is found in **Port** in the [Control](#kodi) section mentioned above.
* If your installation is on the same machine but you cannot connect using `localhost` try `0.0.0.0` instead.
The URL used to connect ultimately must be formed like this: `[protocol]://[hostname]:[port]/[path]`
If any part of this URL is missing multi-scrobbler will use a default value, for your convenience. This also means that if any part of your URL is **not** standard you must explicitly define it.
Part => Default Value
* Protocol => `http://`
* Hostname => `localhost`
* Port => `8080`
* Path => `/jsonrpc`
<details>
<summary>URL Transform Examples</summary>
```json
{
"url": "kodi.mydomain.com"
}
```
MS transforms this to: `http://kodi.mydomain.com:8080/jsonrpc`
```json
{
"url": "192.168.0.101:3456"
}
```
MS transforms this to: `http://192.168.0.101:3456/jsonprc`
```json
{
"url": "mydomain.com:80/kodiReverse/jsonrpc"
}
```
MS transforms this to: `http://mydomain.com:80/kodiReverse/jsonrpc`
| KODI_URL | Yes | http://localhost:8080/jsonrpc | The URL of the Kodi server |
| KODI_USERNAME | No | | The username set |
| KODI_PASSWORD | No | | The password set |
### File-Based
See [`kodi.json.example`](https://github.com/FoxxMD/multi-scrobbler/blob/master/config/kodi.json.example) or [explore the schema with an example and live editor/validator](https://json-schema.app/view/%23%2Fdefinitions%2FKodiSourceConfig/%23%2Fdefinitions%2FKodiData?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fdevelop%2Fsrc%2Fcommon%2Fschema%2Fsource.json)
| `MALOJA_URL` | Yes | | Base URL of your installation |
| `MALOJA_API_KEY` | Yes | | Api Key |
### File-Based
See [`maloja.json.example`](https://github.com/FoxxMD/multi-scrobbler/blob/master/config/maloja.json.example) or [explore the schema with an example and live editor/validator](https://json-schema.app/view/%23/%23%2Fdefinitions%2FMalojaClientConfig?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fdevelop%2Fsrc%2Fcommon%2Fschema%2Fclient.json)
## [Last.fm](https://www.last.fm)
[Register for an API account here.](https://www.last.fm/api/account/create)
The Callback URL is actually specified by multi-scrobbler but to keep things consistent you should use
```
http://localhost:9078/lastfm/callback
```
or replace `localhost:9078` with your own base URL
| `LASTFM_API_KEY` | Yes | | Api Key from your API Account |
| `LASTFM_SECRET` | Yes | | Shared secret from your API Account |
| `LASTFM_REDIRECT_URI` | No | `http://localhost:9078/lastfm/callback` | Url to use for authentication. Must include `lastfm/callback` somewhere in it |
| `LASTFM_SESSION` | No | | Session id. Will be generated by authentication flow if not provided. |
### File-Based
See [`lastfm.json.example`](https://github.com/FoxxMD/multi-scrobbler/blob/master/config/lastfm.json.example) or [explore the schema with an example and live editor/validator](https://json-schema.app/view/%23/%23%2Fdefinitions%2FLastfmClientConfig?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fdevelop%2Fsrc%2Fcommon%2Fschema%2Fclient.json)
## [Listenbrainz](https://listenbrainz.org)
You will need to run your own Listenbrainz server or have an account [on the official instance](https://listenbrainz.org/login/)
On your [profile page](https://listenbrainz.org/profile/) find your **User Token** to use in the configuration.
| LZ_TOKEN | Yes | | User token from your LZ profile |
| LZ_USER | Yes | | Your LZ username |
| LZ_URL | No | https://api.listenbrainz.org/ | The base URL for the LZ server |
### File-Based
See [`listenbrainz.json.example`](https://github.com/FoxxMD/multi-scrobbler/blob/master/config/listenbrainz.json.example) or [explore the schema with an example and live editor/validator](https://json-schema.app/view/%23%2Fdefinitions%2FListenBrainzClientConfig?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fdevelop%2Fsrc%2Fcommon%2Fschema%2Fclient.json)
# Monitoring
multi-scrobbler supports some common webhooks and a healthcheck endpoint in order to monitor Sources and Clients for errors.
## Webhook Configurations
Webhooks will **push** a notification to your configured servers on these events:
* Source polling started
* Source polling retry
* Source polling stopped on error
* Scrobble client scrobble failure
Webhooks are configured in the main [config.json](#all-in-one-file-configuration) file under the `webhook` top-level property. Multiple webhooks may be configured for each webhook type. EX:
```json5
{
"sources": [
//...
],
"clients": [
//...
],
"webhooks": [
{
"name": "FirstGotifyServer",
"type": "gotify",
"url": "http://192.168.0.100:8070",
"token": "abcd"
},
{
"name": "SecondGotifyServer",
"type": "gotify",
//...
},
{
"name": "NtfyServerOne",
"type": "ntfy",
//...
},
//...
]
}
```
### [Gotify](https://gotify.net/)
Refer to the [config schema for GotifyConfig](https://json-schema.app/view/%23/%23%2Fdefinitions%2FGotifyConfig?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fdevelop%2Fsrc%2Fcommon%2Fschema%2Faio.json)
multi-scrobbler optionally supports setting message notification priority via `info` `warn` and `error` mappings.
EX
```json
{
"type": "gotify",
"name": "MyGotifyFriendlyNameForLogs",
"url": "http://192.168.0.100:8070",
"token": "AQZI58fA.rfSZbm",
"priorities": {
"info": 5,
"warn": 7,
"error": 10
}
}
```
### [Ntfy](https://ntfy.sh/)
Refer to the [config schema for NtfyConfig](https://json-schema.app/view/%23/%23%2Fdefinitions%2FNtfyConfig?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fdevelop%2Fsrc%2Fcommon%2Fschema%2Faio.json)
multi-scrobbler optionally supports setting message notification priority via `info` `warn` and `error` mappings.
EX
```json
{
"type": "ntfy",
"name": "MyNtfyFriendlyNameForLogs",
"url": "http://192.168.0.100:9991",
"topic": "RvOwKJ1XtIVMXGLR",
"username": "Optional",
"password": "Optional",
"priorities": {
"info": 3,
"warn": 4,
"error": 5
}
}
```
## Health Endpoint
An endpoint for monitoring the health of sources/clients is available at GET `http://YourMultiScrobblerDomain/health`
* Returns `200 OK` when **everything** is working or `500 Internal Server Error` if **anything** is not
* The plain url (`/health`) aggregates status of **all clients/sources** -- so any failing client/source will make status return 500
* Use query params `type` or `name` to restrict client/sources aggregated IE `/health?type=spotify` or `/health?name=MyMaloja`
* On 500 the response returns a JSON payload with `messages` array that describes any issues
* For any clients/sources that require authentication `/health` will return 500 if they are **not authenticated**
* For sources that poll (spotify, yt music, subsonic) `/health` will 500 if they are **not polling**
import SchemaLink from "../../src/components/SchemaLink";
import AIOExample from "../../src/components/AIOExample";
import FileExample from "../../src/components/FileExample";
import ScrobbleThreshold from "@site/src/components/snippets/_scrobble-threshold.mdx"
import AIOConfig from '!!raw-loader!../../../config/config.json.example';
import SpotifyConfig from '!!raw-loader!../../../config/spotify.json.example';
:::tip
Check the [**FAQ**](../FAQ.md) if you have any issues after configuration!
:::
## Configuration Types
[**Sources**](/configuration/sources) and [**Clients**](/configuration/clients) are configured using:
* environmental (ENV) variables
* client/source specific json config files
* an all-in-one json config file
**MS will parse configuration from all configuration types.** You can mix and match configurations but it is generally better to stick to one or the other.
<Tabs groupId="configType" queryString>
<TabItem value="env" label="ENV">
MS will parse environmental variables present in the OS/container when it is run. **This method means MS does not require files to run.**
* You have many of each type of **Source** you want to scrobble from IE 2x Plex accounts, 3x Spotify accounts, 1x
Funkwhale...
* You have more than one scrobble **Client** you want to scrobble to IE multiple Maloja servers
* You want only to scrobble to specific **Clients**
* You need to setup more advanced configuration for a Source/Client
* Most Source/Clients only support basic configuration through ENV, all configuration is possible using File/AIO
</details>
:::tip
* There are **example configurations** for all Source/Client types and AIO config located in the [`/config`](https://github.com/FoxxMD/multi-scrobbler/tree/master/config) directory of this project. These can be used as-is by renaming them to `.json`.
* For docker installations these examples are copied to your configuration directory on first-time use.
* There is also a [**kitchensink example**](/configuration/kitchensink) that provides examples of using all sources/clients in a complex configuration.
:::
Each file is named by the **type** of the Client/Source found in below sections. Each file as an **array** of that type of Client/Source.
MS will parse an **all-in-one** configuration file located in the directory specified by the `CONFIG_DIR` environmental variable. This variable defaults to:
* Local installation -> `PROJECT_DIR/config/config.json`
* Docker -> `/config/config.json` (in the container) -- see the [install docs](../installation/installation.mdx#docker) for how to configure this correctly
* You have many of each type of **Source** you want to scrobble from IE 2x Plex accounts, 3x Spotify accounts, 1x
Funkwhale...
* You have more than one scrobble **Client** you want to scrobble to IE multiple Maloja servers
* You want only to scrobble to specific **Clients**
* You need to setup [monitoring/webhooks](#monitoring)
* You want to setup defaults for all Sources/Clients
</details>
**The AIO config also enables setting default options for sources/clients as well as global options for MS itself.**
:::tip
* An example AIO config files can be found in the project directory at [`/config/config.json.example`](https://github.com/FoxxMD/multi-scrobbler/tree/master/config/config.json.example)
* For docker installations this example is copied to your configuration directory on first-time use.
* There is also a [**kitchensink example**](/configuration/kitchensink) that provides examples of using all sources/clients in a complex configuration.
:::
[**Explore the schema for this configuration, along with an example generator and validator, here**](https://json-schema.app/view/%23?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fmaster%2Fsrc%2Fbackend%2Fcommon%2Fschema%2Faio.json)
See the [Configuration Types](#configuration-types) above for your options for creating Source and Client configurations.
* Reference [**Scrobble Sources**](/configuration/sources) for what Sources are available
* Reference [**Scrobble Clients**](/configuration/clients) for what Clients are available
Each entry for a Source/Client includes a **Configuration** section that describes how to configure it using a [configuration type](#configuration-types).
## Secrets Interpolation
When using [File](./?configType=file#configuration-types) or [AIO](./?configType=aio#configuration-types) Configuration, Multi-Scrobbler can interpolate Environmental Variables into your json files. This can be used, for example, to keep sensitive data (like [Last.fm Client/Secret](/configurations/clients/lastfm#configuration)) out of your configuration files so that they can be committed to git.
Multi-scrobbler will look for patterns in text fields within _all_ of your json files:
* Some part of the text field matches: `[[MY_ENV]]`
* Is replaced by the value of the Environmental Variable named `MY_ENV`
<details>
<summary>Example</summary>
Given this [Last.fm](/configurations/clients/lastfm#configuration) [File](./?configType=file#configuration-types) config:
And these environmental variables, in this scenario set through `environment` in the [docker compose installation](/quickstart#create-docker-compose-file):
* ENV variable names/interpolation keys are case-insensitive
* Interpolation only works for **string** values within json. This cannot be used for numbers, booleans, objects, etc...
:::
:::warning[Missing ENVS]
Multi-scrobbler **will not** throw an error if the environmental value is not found. Instead, it will leave the string as-is and log a warning (`WARN` level) with the names of the missing environmental variable names like so:
**Verify that interpolation keys/environmental variable names you will use do not collide with existing ENV names used by multi-scrobbler.** Use the docs search to verify the name you want to use is not already used elsewhere by multi-scrobbler.
:::
## Application Options
These options affect multi-scrobbler's behavior and are not specific to any source/client.
### Base URL
Defines the URL that is used to generate default redirect URLs for authentication on [spotify](/configuration/sources/spotify) and [lastfm](/configuration/clients/lastfm) -- as well as some logging hints.
* Default => `http://localhost:9078`
* Set with [ENV](./?configType=env#configuration-types) `BASE_URL` or `baseUrl` [all-in-one configuration](./?configType=aio#configuration-types)
* If protocol is `http` or no protocol is specified MS will try to use port `9078` -- to override this explicitly set the port or use `https`
Useful when running with [docker](../installation/installation.mdx#docker) so that you do not need to specify redirect URLs for each configuration.
<details>
<summary>Example</summary>
EX Lastfm Redirect Url is `BASE_URL:PORT/lastfm/callback` (when no other redirectUri is specified for [lastfm configuration](/configuration/clients/lastfm))
Multi-scrobbler caches some activities to persist important data across restarts, reduce external API calls, and make some actions faster.
All of the activities below are **always** cached **in-memory** with an optional, configurable [**secondary** store](#secondary-caching-configuration) for persistence.
<Tabs groupId="cachedThings" queryString>
<TabItem value="scrobbles" label="Scrobbles">
**Queued** and **Failed** Scrobbles are cached so that any un-scrobbled data you have is persisted across restarts of multi-scrobbler.
:::tip
By default, this data use a [Secondary](#secondary-caching-configuration) [File](./?cacheType=file#secondary-caching-configuration) store, configured for you automatically.
If you have configured a [persisted volume/bind mount](/installation#storage) for configuration (`/config` is mounted in [docker compose](/quickstart#create-docker-compose-file)) then you are already done. If you are not persisting this directory then you should consider setting up [Valkey Cache](./?cacheType=valkey#secondary-caching-configuration) for this.
:::
##### Configuration
Use any [Secondary Cache](#secondary-caching-configuration), the config examples below show the default values:
| `CACHE_SCROBBLE` | No | `file` | The cache type to use |
| `CACHE_SCROBBLE_CONN` | No | `/config` | |
</TabItem>
<TabItem value="aio" label="AIO">
```json5 title="config.json"
{
"cache": {
"scrobble": {
"provider": "file",
"connection": "/config"
}
},
// ...
}
```
</TabItem>
</Tabs>
</TabItem>
<TabItem value="auth" label="Auth Data">
Authentication sessions/tokens/etc... are cached for quicker requests and for persistence across restarts.
:::tip
By default, this data use a [Secondary](#secondary-caching-configuration) [File](./?cacheType=file#secondary-caching-configuration) store, configured for you automatically.
If you have configured a [persisted volume/bind mount](/installation#storage) for configuration (`/config` is mounted in [docker compose](/quickstart#create-docker-compose-file)) then you are already done. If you are not persisting this directory then you should consider setting up [Valkey Cache](./?cacheType=valkey#secondary-caching-configuration) for this.
:::
##### Configuration
Use any [Secondary Cache](#secondary-caching-configuration), the config examples below show the default values:
| `CACHE_AUTH` | No | `file` | The cache type to use |
| `CACHE_AUTH_CONN` | No | `/config` | |
</TabItem>
<TabItem value="aio" label="AIO">
```json5 title="config.json"
{
"cache": {
"auth": {
"provider": "file",
"connection": "/config"
}
},
// ...
}
```
</TabItem>
</Tabs>
</TabItem>
<TabItem value="metadata" label="Transform API Calls">
API Calls to external (metadata) services used to [Enhance Scrobbles](/configuration/transforms), like calls to [Musicbrainz](/configuration/transforms/musicbrainz), can be cached to avoid duplicate calls and speed up scrobble transformations.
By default, these calls are only cached in memory. If you wish for cached calls to be persisted across restarts then setup [Valkey Cache](./?cacheType=valkey#secondary-caching-configuration).
##### Configuration
Use any [Secondary Cache](#secondary-caching-configuration), the config examples below show the default values:
| `CACHE_METADATA` | Yes | `valkey` | The cache type to use |
| `CACHE_METADATA_CONN` | Yes | | The host/IP and port to connect to EX: `redis://192.168.0.120:6379` |
</TabItem>
<TabItem value="aio" label="AIO">
Example
```json5 title="config.json"
{
"cache": {
"metadata": {
"provider": "valkey",
"connection": "redis://192.168.0.120:6379"
}
},
// ...
}
```
</TabItem>
</Tabs>
</TabItem>
</Tabs>
### Debug Mode
Turning on Debug Mode will
* override and enable all optional "debugging" options found in configuration
* set log output to DEBUG
Use this as a shortcut for enabling output that can be used for troubleshooting and issue reporting. Be aware that logs will likely be VERY noisy while Debug Mode is on. You should only have this mode on while gathering logs for troubleshooting and then turn it off afterwards.
To set debug mode either add it to [AIO `config.json`](./?configType=aio#configuration-types)
```json5 title="config.json"
{
"debugMode": true,
"sources": [...],
// ...
}
```
or set the [ENV](./?configType=env#configuration-types) `DEBUG_MODE=true`
### Disable Web
If you do not need the dashboard and/or ingress sources, or have security concerns about ingress and cannot control your hosting environment, the web server and API can be disabled.
:::warning
Any **[ingress-based sources](/configuration/sources#ingress) will be unusable** (Webscrobbler, etc...) if this is disabled.
:::
Disable using either:
* ENV `DISABLE_WEB=true`
* In [All-in-One File](./?configType=aio#configuration-types) use the top-level property `"disableWeb": true`
## Monitoring
Multi-scrobbler supports some common webhooks and a healthcheck endpoint in order to monitor Sources and Clients for errors.
### Webhook Configurations
Webhooks will **push** a notification to your configured servers on these events:
* Source polling started
* Source polling retry
* Source polling stopped on error
* Scrobble client scrobble failure
Webhooks are configured in the AIO [config.json](#configuration-types) file under the `webhook` top-level property. Multiple webhooks may be configured for each webhook type.
<details>
<summary>Example</summary>
```json5 title="config.json"
{
"sources": [
//...
],
"clients": [
//...
],
"webhooks": [
{
"name": "FirstGotifyServer",
"type": "gotify",
"url": "http://192.168.0.100:8070",
"token": "abcd"
},
{
"name": "SecondGotifyServer",
"type": "gotify",
//...
},
{
"name": "NtfyServerOne",
"type": "ntfy",
//...
},
//...
]
}
```
</details>
#### [Gotify](https://gotify.net/)
Refer to the [config schema for GotifyConfig](https://json-schema.app/view/%23/%23%2Fdefinitions%2FGotifyConfig?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fmaster%2Fsrc%2Fbackend%2Fcommon%2Fschema%2Faio.json)
multi-scrobbler optionally supports setting message notification priority via `info` `warn` and `error` mappings.
<details>
<summary>Example</summary>
```json title="config.json"
{
"type": "gotify",
"name": "MyGotifyFriendlyNameForLogs",
"url": "http://192.168.0.100:8070",
"token": "AQZI58fA.rfSZbm",
"priorities": {
"info": 5,
"warn": 7,
"error": 10
}
}
```
</details>
#### [Ntfy](https://ntfy.sh/)
Refer to the [config schema for NtfyConfig](https://json-schema.app/view/%23/%23%2Fdefinitions%2FNtfyConfig?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fmaster%2Fsrc%2Fbackend%2Fcommon%2Fschema%2Faio.json)
multi-scrobbler optionally supports setting message notification priority via `info` `warn` and `error` mappings.
Refer to the [config schema for AppriseConfig](https://json-schema.app/view/%23/%23%2Fdefinitions%2FAppriseConfig?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fmaster%2Fsrc%2Fbackend%2Fcommon%2Fschema%2Faio.json)
multi-scrobbler supports [stateless](https://github.com/caronc/apprise-api?tab=readme-ov-file#stateless-solution) and [persistent storage](https://github.com/caronc/apprise-api?tab=readme-ov-file#persistent-storage-solution) endpoints as well as [tags](https://github.com/caronc/apprise-api?tab=readme-ov-file#tagging)/
* You want to scrobble plays for yourself (Foxx), Fred, and Mary
* Each person has their own Maloja server
* Each person has their own [Maloja](/configuration/clients/maloja) server
* Each person has their own Spotify account
* You have your own Airsonic (subsonic) server you to scrobble from
* You have your own Youtube Music account you want to scrobble from
* Mary has her own Last.fm account she also wants to scrobble to
* Fred has his own Spotify application and provides you with just his access and refresh token because he doesn't trust you (wtf Fred)
* You have your own Airsonic ([subsonic](/configuration/sources/subsonic)) server you to scrobble from
* You have your own [Youtube Music](/configuration/soures/youtube-mysic) account you want to scrobble from
* Mary has her own [Last.fm](/configuration/clients/lastfm) account she also wants to scrobble to
* Fred has his own [Spotify](/configuration/sources/spotify) application and provides you with just his access and refresh token because he doesn't trust you (wtf Fred)
* Fred has a Plex server and wants to scrobble everything he plays
* Mary uses Fred's Plex server but only wants to scrobble her plays from the `podcast` library
* The three of you have a shared library on Plex called `party` that you only play when you are hanging out. You want plays from that library to be scrobbled to everyone's servers.
* Fred also has his own Jellyfin server and wants to scrobble everything he plays
* The three of you have a shared library on [Plex](/configuration/sources/plex) called `party` that you only play when you are hanging out. You want plays from that library to be scrobbled to everyone's servers.
* Fred also has his own [Jellyfin server](/configurion/sources/jellyfin) and wants to scrobble everything he plays
* You have an android music app that can scrobble to a [custom listenbrainz server](/configuration/sources/listenbrainz-endpoint)
### All-in-one Config
Using just one config file located at `CONFIG_DIR/config.json`:
```json5
```json5 title="CONFIG_DIR/config.json"
{
"sourceDefaults": {
"maxPollRetries": 0, // optional, default # of automatic polling restarts on error. can be overridden by property in individual config
@@ -42,6 +41,8 @@ Using just one config file located at `CONFIG_DIR/config.json`:
"data": {
"clientId": "foxxSpotifyAppId",
"clientSecret": "foxxSpotifyAppSecret",
},
"options": {
"maxRequestRetries": 2, // override default max retries because spotify can...spotty
}
},
@@ -69,7 +70,9 @@ Using just one config file located at `CONFIG_DIR/config.json`:
"name": "fredPlex",
"clients": ["fredMaloja"],
"data": {
"user": ["fred@email.com"]
"token": "JtRnwQWD__XMJF8sT3jc",
"url": "192.168.0.233:32400",
"usersallow": ["fredUser"]
}
},
{
@@ -77,8 +80,10 @@ Using just one config file located at `CONFIG_DIR/config.json`:
"name": "maryPlex",
"clients": ["maryMaloja"],
"data": {
"user": ["mary@email.com"], // still need to specify mary as user so not all users who play from 'podcasts' get scrobbled
"libraries": ["podcasts"]
"token": "JtRnwQWD__XMJF8sT3jc",
"url": "192.168.0.233:32400",
"usersallow": ["maryUser"], // still need to specify mary as user so not all users who play from 'podcasts' get scrobbled
"librariesAllow": ["podcasts"]
}
},
{
@@ -86,7 +91,9 @@ Using just one config file located at `CONFIG_DIR/config.json`:
"name": "partyPlex",
// omitting clients (or making it empty) will make this Source scrobble to all Clients
"data": {
"libraries": ["party"],
"token": "JtRnwQWD__XMJF8sT3jc",
"url": "192.168.0.233:32400",
"librariesAllow": ["party"]
}
},
{
@@ -94,7 +101,10 @@ Using just one config file located at `CONFIG_DIR/config.json`:
"name": "FredJelly",
// omitting clients (or making it empty) will make this Source scrobble to all Clients
"data": {
"user": ["fred@email.com"]
"url": "http://localhost:8096",
"user": "FoxxMD",
"apiKey": "c9fae8756fbf481ebd9c5bb56bd6540c",
"usersAllow": ["fredUser"]
}
},
{
@@ -113,9 +123,16 @@ Using just one config file located at `CONFIG_DIR/config.json`:
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.