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
Docusaurus treats the files as assets and forces them to download when relative links. Instead, use external links to github so the user can stay in browser
For jellyfin/plex/tautulli -- `logFilterFailure` option in config allows controlling if and what log LEVEL user/server filter failures are logged. #85
* Updated schema
* Updated examples
* Also added expected/found detailed logging for filters to jellyfin #84
* Split user value on comma to align with what config describes as acceptable (comma-delim)
* Trim found values to fix any whitespace typos
* On disallowed event logging add expected/found values to help with debugging
Fixes#84
When a track contains multiple artist, only the first artist should be send to Last.fm since it doesn't support multiple artists per track.
Previous behaviour was to combine all artists in a single string.
Issue: #81
* Removes need to override poll() in child class and better defines when logic should be performed
* Fixes app crash when spotify credentials are present but invalid #76
Add a scrobbleThresholds object with 'duration' and 'percent' values to all sources and sourceDefaults. If both are defined then first passing condition trigger scrobble.
* duration, in seconds, tests how long MS has been tracking the play (since discovery)
* percent, only used if source provides track duration, tests time MS has been tracking play (since discovery) is some % of track duration
* Remove npm dev dependencies after app install/build
* Delete typescript files after build
* Provide a default config with file logging disabled and copy examples to config folder if empty
* Update winston-duplex to remove hard winston dependency
* Use scoped @foxxmd/winston instead of git commit
* Replace 'winston' with '@foxxmd/winston' in application
* Log warnings when any specific mpris info for a player isn't able to be parsed rather than failing entire poll attempt
* Fallback to undefined tracking progress if position cannot be parsed
* Add play object data "meta" with a brainz property and common mbid tags
* Parse MBID data from jellyfin webhook
* Add MBID data from play object to 'additional_info' when scrobbling to listenbrainz
Closes#72
don't want to assume we won't find an existing just because there are no scrobbles from the client itself -- we may have found it from recent recorded scrobbles sent
* The response track data is different than submission data so make sure we are accurately parsing that data to PlayObjects
* Add separate method for getting listens as recently played (play objects) and refactor getUserListens to return raw payload
* use child loggers instead of creating new loggers for each part of the app
* cleans up and automates nested labels in log output
* fixes "event emitters may be leaky" node warning
* add ability to specify log levels per output type (console, ui, file) using aioconfig
* improve UI logging experience
* use more color-blind friendly colors (borrowed from context-mod)
* add verbose log level output
* increase log buffer and store all logs, then filter at render time, instead of discarding based on min log level so that we always have logs to display
* fix level selection in ui to work for all levels
* Add a 'recentDiscoveredPlays' class property for abstract source
* recentDiscoveredPlays is platform aware and configured based on source 'multiPlatform' class property
* Consolidate scrobble behavior into abstract class
* Check for existing discovered track before scrobbling
* Add to discovered if not existing and increment discovered tracks counter
* Decouple sources from clients by using event emitter when a newly discovered track should be scrobbled
* Use discovered tracks for UI display instead of api data
Since spotify now uses currently playing with memory we need to poll more aggressively to make sure we catch tracks as they are played. Additionally need to decrease max polling interval so as not to miss activity.
* Use recently-played to scrobble backlogged tracks
* Use currently-playing or current-state to implement spotify as a memory source in order to track plays made from spotify connect devices
* HTTP Status as primary indicator with messages in json response
* No parameters in request aggregates all client/source statuses. Otherwise can use 'type' or 'name' parameters to restrict client/sources to aggregate
* Refactor initial connection info to be more verbose and only log once
* Refactor valid event check logging order of operations and make logging more verbose
In order to make basic troubleshooting easier all ingress-based sources (plex, tautulli, jellyfin) now log initial connections and basic request validation checks:
* Request logging includes remote address and user agent
* Log initial requests before any middleware/body parsing
* Include method for checking if request is valid at a low-level (IE checking request verb)
* Include source-specific methods for checking if request payload is valid
* Output to commonjs using TS (remove type:module from package.json)
* Update tsconfig config
* Downgrade formidable to fix ES module import error
* Remove js assertions and use separate json schema for aio client/source validation to keep MS log output cleaner
Although it was nice to include the option no one is providing their own tokens from a separate flow completed elsewhere. It simplifies readability and usage in MS to remove these and always generate out own.
* Update to alpine 3.17 and node 18 to match the project
* Use LSIO base to enable ui/guid usage through env
* Use build stages to reduce image size and speed up build
* Refactor build to use typescript
* Simplify config directory by using root-level /config as default
* Add bash script on startup to copy example configs if a new config folder is detected
MS incorrectly chooses the first unnamed source (based on built source order) when handling spotify callback. Fixed by specify source type when retrieving source. Fixes#63
* Refactor init state to have an interim value and use getter/setter to control it
* Replace usage of 'ready' class field on maloja client with serverIsHealthy (make it client specific)
* Add some missing functions in abstract client class
* If client is not already initialized (or trying to initialize) then try to initialize client on scrobble attempt, maybe fixes#44
* Replace multer with formidable as it is more up-to-date and has more granular options for tracking progress and parsing data
* Add debug logging for the lifecycle of a plex request
* throws errors if any part of plex request lifecycle is unexpected
* Use server version to determine shape of scrobble data when formatting play object
* Refactor adding new scrobble to recent list to use correct shape based on server version
* Use rebuild status and health reported from maloja api endpoint to determine if server is ready to scrobble/query
* Use readiness as prerequisite for auth test (can be removed if krateng/maloja#92 is merged)
Default state is calculated from initialized and auth status. Allows an additional check for client readiness independent of communication (initialized) status and auth status.
* Use ignored count to determine if warning is logged
* Let user know if no error message included
* Include link to error code reference in last.fm api docs
* Emit individual log statements in winston formatter
* Emit different event when logs should be cleared
* Use window global to keep track of sort order when appending on emit
* Break up into different functions and add properties to concrete classes signalling its capabilities
* More detailed logging based on which fails/succeeds
* Simplify client/source init/auth step (no more need for case switch specifics other than creating object)
* Instead of stopping the whole application for one misconfigured config just log the issue and continue with any valid/working configs.
* Add better logging for type.json issues (add index)
* Add deprecation warnings for single-user mode structures in type.json (to be removed in 0.4)
* Instead of stopping the whole application for one misconfigured config just log the issue and continue with any valid/working configs.
* Add better logging for type.json issues (add index)
* Add deprecation warnings for single-user mode structures in type.json (to be removed in 0.4)
* Should stabilize subsonic as a source and prevents duplicate scrobbles
* Reduce polling interval to 10 seconds for more accurate recently played data and restrict max backoff time to 30 seconds
If sources don't support a scrobble action or don't return a sane "recently played" data source we need to keep track of these plays ourselves. Do it in memory based on currently playing and return that as source "source of truth" for recently played.
* Include some metadata (mbid, url, nowplaying)
* Check for more artist properties since responses seem inconsistent now
* Check for timestamp to handle nowplaying use case
* Handle invalid scrobbles from last.fm on recents refresh WRT nowplaying and missing timestamps #22
* Hopefully can get artist from webhook payload in the future so we can actually use this
* Add basic instructions for using Webhook plugin in configuration docs
* Hierarchical retries and delay options for sources and clients (override general config => individual config)
* Logging for retry attempts
* Respect Retry-After header on responses if present
* Pass config dir to client handler
* Rename auth routes to be source/client specific
* Pass client info to status page to enable displaying client stats and actions (auth)
* Handle auth callback from lastfm
* Refactor scrobble clients to be more granular on error handling for scrobble call
* use initialized param on clients to additionally check if they should be used
* Pass config dir to constructor so we can use it any in the class (for lastfm)
* Use error property to determine if we she keep trying to scrobble plays after caught error
* Authentication is user-interaction required with saved session file
* Use initialized to signal auth is done and client is ready to scrobble/get tracks
* Add some retry attempts based on error returned from api
* Use plain ol async because it gets the job done and i don't need no fancy generator/yield. Can wait until loop is done to detect signal to stop polling
* Refactor polling into AbstractSource, remove duplicated code for Spotify/Subsonic sources
* Add 'canPoll' property to Sources
* Stagger polling invocation on app start so log messages don't get jumbled
* Refactor auth/poll/recent endpoints to be generic (based on source properties)
* Refactor status page to show functionality based on canAuth/canPoll rather than type
* Refactor examples to all be multi-user structured
* Move configuration into its own file
* Separate config approaches into env/json approaches with guidance on which one to use
* Add more comments to example json
* Simplify main readme and provide a more opinionated, minimal example there
* Refactor comparison methodology to use weighted scoring system (psuedo-fuzzy) instead of all-or-nothing if statements
* Use reference scrobbles (playObjs we have submitted while app is running) to check for existing
* Make title cleaning more robust by removing parenthesis and all "feat" strings
* Add artists matching
* Rewrite debugging info to show score/breakdowns and add more granular debugging options (show on match/no match)
* Add scrobbledPlayObjs to so we can keep track of formatted playObjs we *know* we have scrobbled (for more accurate comparisons elsewhere)
* Filter scrobbledPlayObjs based on oldest returned recent scrobble from client
* Add instanced generic formatPlayObj for use in other methods
* Add generic source title cleaning function
* Add generic function to search for scrobbled playObj from known submitted playObjs
* Refactor source building into a new class to hold all sources
* ScrobbleSources supports single and multi user config variant parsing (based on client parsing)
* Refactor client/source abstract classes to accept a type and name -- logger is based off of both
* Refactor config file parsing into ScrobbleSources
* Refactor status page to use array of source data instead of hardcoding
* Status page now uses flex css to layout sources instead of hardcoding
* Refactor spotify endpoints to require a name from querystring in order to determine which source to work on (middleware with this)
* Refactor ScrobbleClients to use optional scrobble filter name, passed by configured source
* Implement library and server configuration params for plex/tautulli to help with multi-user
* Make library, server, and user checks on plex/tautulli case insensitive and include more debug information
* Consolidate validEvent checks for plex/tautulli
* Add sane defaults for single-user mode client configs (naming)
* Refactor client building into a two-stage process of 1) parsing and checking for valid config 2) validating config per client
* Refactor client ENVs config into separate config (don't mix/overwrite env and json configs)
* Check for unique names
* Implement more descriptive and thorough config structure validation including hinting at config source (location)
Relates to #13
* Strip source track of feat and [artist] since Maloja does this as well
* Compare position-agnostic and de-duped tokens from source/scrobble titles for a better match
* Refactor playObj artist (string) => artists (array) so we have more info to work with and less ambiguity in naming
* Use forward slash as artist deliminator for Maloja scrobbling because it parses better
* Track building the artists uses forward slash for less ambiguity as well
* Implement func for finding longest string length (for use with padding plays)
* Implement func for truncating string based on a fixed length (for use with padding plays)
* Implement play string building transformers for all parts of the string for finer control
* Implement "X from now" time formatting
* Move all spotifyApi invocation into a wrapped function that handles token refresh so we can just call the api from anywhere without having to worry about re-authenticating
* Implement "get recent played" function using new wrapper and also support formatting results to playObjs before returning
* add open.spotify.com web url to playObj meta if it exists
2020-12-03 11:06:29 -05:00
312 changed files with 61575 additions and 2394 deletions
[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.
* Displays running status and buffered log through web server
* Spotify - Authorize your app through the web server
* Spotify - Persists obtained credentials to file
*Spotify - Automatically refreshes authorization for unattended use
*Spotify - Implements back off behavior if no listening activity is detected after an interval (after 10 minutes of idle it will back off to a maximum of 5 minutes between checks)
* [Tautulli](https://tautulli.com) - Scrobble endpoint using notification agents
* [Plex](https://plex.tv) - Scrobble endpoint using [Webhooks](https://support.plex.tv/articles/115002267687-webhooks)
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)
* Monitor status of Sources and Clients using [webhooks (Gotify, Ntfy, Apprise)](/docsite/docs/configuration/configuration.md#webhook-configurations) or [healthcheck endpoint](/docsite/docs/configuration/configuration.md#health-endpoint)
* 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)
**Why should I use this over a browser extension and/or mobile app scrobbler?**
* **Platform independent** -- Because multi-scrobbler communicates directly with service APIs it will scrobble everything you play regardless of where you play it. No more need for apps on every platform you use!
* **Open-source** -- Get peace of mind knowing exactly how your personal data is being handled.
* **Track your activity regardless of where you listen** -- Scrobble from many Sources to one Client with ease and without duplicating tracks.
* **Manage scrobbling for others** -- Scrobble for your friends and family without any setup on their part. Easily silo sources to specific clients to keep plays separate.
**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.png" width="800">
## 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.
### 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...
Source configurations consist of:
* A friendly name.
* Any data needed to communicate or authenticate with the Source.
* An optional list of Client names that the Source should scrobble to. If omitted the Source also scrobbles to all configured Clients.
### Client
A **Client** is an application that stores the historical information about what songs 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.
## Installation
[See the **Installation** documentation](/docsite/docs/installation/installation.md)
### Locally
## Configuration
Clone this repository somewhere and then install from the working directory
All configuration is done through json files or environment variables. Reference the [examples in the config folder](config) more detailed explanations and structure.
**A property from a json config will override the corresponding environmental variable.**
### App (General)
[JSON config example](config/config.json.example)
These environmental variables do not have a config file equivalent (to make Docker configuration easier)
| `CONFIG_DIR` | - | `CWD/config` | Directory to look for all other configuration files |
| `LOG_PATH` | - | `CWD/logs` | If `false` no logs will be written. If `string` will be the directory logs are written to |
| `PORT` | - | 9078 | Port to run web server on |
**The app must have permission to write to `CONFIG_DIR` in order to store the current spotify access token.**
### Sources (Where Music Plays Come From)
#### Spotify
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.
| PLEX_USER | - | | The username of the user to scrobble tracks for. No user specified means all tracks by all users will be scrobbled. |
#### [Tautulli](https://tautulli.com)
Check the [instructions](docs/plex.md) on how to setup a notification agent to scrobble your plays.
**Environmental variables and config file is the same as Plex**
### Scrobble Clients
At least one client (the only one right now...) must be setup in order for the app to work. Client configurations can alternatively be configured in the main [`config.json`](https://github.com/FoxxMD/multi-scrobbler/blob/master/config/config.json.example) configuration
| `MALOJA_URL` | Yes | | Base URL of your installation |
| `MALOJA_API_KEY` | Yes | | Api Key |
[See the **Configuration** documentation](/docsite/docs/configuration/configuration.md)
## Usage
A status page with statistics and recent logs can found at
A status page with statistics, recent logs, and some runtime configuration options can be found at
```
https://localhost:9078
http://localhost:9078
```
Output is also provided to stdout/stderr as well as file if specified in configuration.
On first startup you may need to authorize Spotify by visiting the callback URL (which can also be accessed from the status page)
On first startup you may need to authorize Spotify and/or Last.fm by visiting the callback URL (which can also be accessed from the status page). Visit the status page above to find the applicable link to trigger this.
this.logger.debug('Scrobbles with same name detected but play diff and scrobble diffs were too large to consider dups.');
for(constdiffoflargeDiffs){
this.logger.debug(`Scrobble: ${diff.title} | Played At ${playDate.local().format()} | End Diff ${diff.endTimeDiff.toFixed(0)}s | Start Diff ${diff.startTimeDiff===undefined?'N/A':`${diff.startTimeDiff.toFixed(0)}s`}`);
console.log(`${importPathPrefix} Import looks like an alias ${dirExists?'(dir exists, file does not)':'(dir does not exist)'} but import types does specify alias`);
console.warn(`${importPathPrefix}${transformPrefix} => Could not find another file in directory that had same name but different extension. Will not transform.`);
These are **example configurations** for all Source/Client types and AIO config.
These can be used as-is by renaming them to `.json` and filling or replacing sample data.
For docker installations these examples are copied to your configuration directory on first-time use.
These are **NOT** exhaustive examples. You should consult the **configuration** documentation and the **schema explorer links** for each source/config type to see a complete list of options and descriptions for all properties.
- 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:
- /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 and API on
Note: I do not recommend running a container without a `config` volume specified or you will need to reauthorize the app everytime the container is rebuilt.
* [Config could not be parsed](#config-could-not-be-parsed)
* [Scrobbling Issues](#scrobbling-issues)
* [Last.fm does not scrobble tracks with multiple artists correctly](#lastfm-does-not-scrobble-tracks-with-multiple-artists-correctly)
* [Jellyfin does not scrobble tracks with multiple artists correctly](#jellyfin-does-not-scrobble-tracks-with-multiple-artists-correctly)
* [Google Cast track information is missing/incorrect or MS player has weird times](#google-cast-track-information-is-missingincorrect-or-ms-player-has-weird-times)
* [Google Cast device does not track media](#google-cast-device-does-not-track-media)
<!-- TOC -->
# Connection Issues
## Plex/Tautulli/Jellyfin don't connect
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.
multi-scrobbler will log information about any server that connects to it for these three 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
```
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
```
**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.**
This is not something multi-scrobbler can fix and means you have an issue in your network.
### Troubleshooting
Check or try all these steps before submitting an issue:
#### Turn on Debug Logging
First, turn on **debug** logging for multi-scrobbler by setting the environmental variable `LOG_LEVEL=debug`:
* using node `LOG_LEVEL=debug ... node src/index.js`
* using docker `docker run -e LOG_LEVEL=debug ... foxxmd/multi-scrobbler`
Check the output for any additional information.
#### Check Host name and URL
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
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
Plex/Tautulli/Jellyfin all have logs that will log if they cannot connect to multi-scrobbler. Check these for further information.
##### Plex
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.
## Jellyfin has warnings about missing headers
If you experience issues trying to scrobble with Jellyfin and find this in your MS logs
```
[API] Jellyfin is not sending a request with valid headers...
```
A workaround that may fix this:
* In Webhook settings:
* [In the webhook you have already configured...](configuration/configuration.md#jellyfin)
* Add Request Header...
* **Key:** `Content-Type`
* **Value:** `application/json`
* Then Save
## Spotify/Deezer/LastFM won't authenticate
Ensure any **client id** or **secrets** are correct in your configuration.
The callback/redirect URL for these services must be:
* the same address you would use to access the multi-scrobbler web interface
* the web-interface must be accessible from the browser you are completing authentication from.
If multi-scrobbler is not running on the same machine your browser is on then the default/example addresses (`http://localhost...`) **will not work.** You must determine the address you can reach the web interface at (such as `http://192.168.0.140:9078`) then use that in place of `localhost` in the callback URLs.
EX `http://localhost:9078/lastfm/callback` -> `http://192.168.0.220:9078/lastfm/callback`
# Configuration Issues
## Config could not be parsed
If you see something like this in your logs:
```
2023-02-19T10:05:42-06:00 warn : [App] App config file exists but could not be parsed!
2023-02-19T10:05:42-06:00 error : [App] Exited with uncaught error
2023-02-19T10:05:42-06:00 error : [App] Error: config.json could not be parsed
```
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
## 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
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)
## Google Cast track information is missing/incorrect or MS player has weird times
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/configuration.md#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/configuration.md#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/configuration.md#forcing-media-tracking) section to fix this
* You want to scrobble plays for yourself (Foxx), Fred, and Mary
* Each person has their own 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)
* 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
### All-in-one Config
Using just one config file located at `CONFIG_DIR/config.json`:
```json5
{
"sourceDefaults":{
"maxPollRetries":0,// optional, default # of automatic polling restarts on error. can be overridden by property in individual config
"maxRequestRetries":1,// optional, default # of http request retries a source can make before error is thrown. can be overridden by property in individual config
"retryMultiplier":1.5,// optional, default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying). can be overridden by property in individual config
},
"clientDefaults":{
"maxRequestRetries":1,// optional, default # of http request retries a client can make before error is thrown. can be overridden by property in individual config
"retryMultiplier":1.5,// optional, default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying). can be overridden by property in individual config
},
"sources":[
{
"type":"spotify",
"name":"foxxSpot",
"clients":["foxxMaloja"],
"data":{
"clientId":"foxxSpotifyAppId",
"clientSecret":"foxxSpotifyAppSecret",
},
"options":{
"maxRequestRetries":2,// override default max retries because spotify can...spotty
}
},
{
"type":"spotify",
"name":"marySpot",
"clients":["maryMaloja"],
"data":{
"clientId":"foxxSpotifyAppId",// only need one application, it can be used by all users of this multi-scrobbler instance
"clientSecret":"foxxSpotifyAppSecret",
}
},
{
"type":"spotify",
"name":"fredSpot",
"clients":["fredMaloja"],
"data":{
"accessToken":"fredsToken",
"refreshToken":"fredsRefreshToken",
"interval":120,// he also wants a slower check interval because his application already has heavy api usage
}
},
{
"type":"plex",
"name":"fredPlex",
"clients":["fredMaloja"],
"data":{
"user":["fred@email.com"]
}
},
{
"type":"plex",
"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"]
}
},
{
"type":"plex",
"name":"partyPlex",
// omitting clients (or making it empty) will make this Source scrobble to all Clients
"data":{
"libraries":["party"],
}
},
{
"type":"jellyfin",
"name":"FredJelly",
// omitting clients (or making it empty) will make this Source scrobble to all Clients
During building if you encounter an error like: `Your current platform "XXX" and architecture "XXX" combination is not yet supported by the native Rollup build.`
Modify `overrides` in `package.json` to use `@rollup/wasm-node` as a drop-in replacement for rollup:
```json
"overrides":{
"spotify-web-api-node":{
"superagent":"$superagent"
}
"vite":{
"rollup":"npm:@rollup/wasm-node@^4.9.6"
}
}
```
See [this issue](https://github.com/FoxxMD/multi-scrobbler/issues/135#issuecomment-1927080260) for more detail.
### Usage Examples
* The web UI and API is served on port `9078`. This can be modified using the `PORT` environmental variable.
#### Using [file-based](../configuration/configuration.md#file-based-configuration) configuration
```shell
npm run start
```
#### Using [env-based](../configuration/configuration.md#env-based-configuration) configuration
**Note:** Flatpak users have experienced issues when using multi-scrobbler as a long-running process. Due to the relative difficulty in debugging issues with flatpak installations it is recommended:
* to use a [Docker](#docker) installation if possible or
* only if you need access to host-level resources like dbus for [MPRIS](https://foxxmd.github.io/multi-scrobbler/docs/configuration#mpris) and cannot run a [nodejs](#nodejs) installation
### Usage Examples
#### Using [file-based](../configuration/configuration.md#file-based-configuration) configuration
The config directory for multi-scrobbler as a flatpak can be found under `/home/YourUser/.var/app/io.github.foxxmd.multiscrobbler/config`
```shell
flatpak run io.github.foxxmd.multiscrobbler
```
#### Using [env-based](../configuration/configuration.md#env-based-configuration) configuration
There are a few [options for running flatpak applications with temporary or permanent environmental variables.](https://ardasevinc.dev/launch-flatpak-apps-with-custom-args-and-environment-variables)
```shell
flatpak run --env=SPOTIFY_CLIENT_ID=yourId --envSPOTIFY_CLIENT_SECRET=yourSecret --env=MALOJA_URL="http://domain.tld" io.github.foxxmd.multiscrobbler
```
## Docker
Cross-platform images are built for x86 (Intel/AMD) and ARM64 (IE Raspberry Pi)
Or use the provided [docker-compose.yml](../../../docker-compose.yml) after modifying it to fit your configuration.
Recommended configuration steps for docker or docker-compose usage:
#### Storage
You **must** bind a host directory into the container for storing configurations and credentials. Otherwise, these will be lost when the container is updated.
* [Using `-v` method for docker](https://docs.docker.com/storage/bind-mounts/#start-a-container-with-a-bind-mount): `-v /path/on/host/config:/config`
If you are using a [bridge network](https://www.appsdeveloperblog.com/docker-networking-bridging-host-and-overlay/) (default docker setup) you **must** map a port to the container in order to access the dashboard and use MS with some sources (Plex, Jellyfin).
The default container port is `9078`. To map container to host port:
* With [docker](https://docs.docker.com/engine/reference/commandline/run/#publish): `-p 9078:9078` (first port is the port on the host to use)
* With [docker-compose](https://docs.docker.com/compose/compose-file/compose-file-v3/#short-syntax-1): `- "9078:9078"`
##### Base URL
Optionally, when
* using a [Source or Client](../configuration/configuration.md) that has a "Redirect URI" that you have not explicitly defined
* and
* using a bridge network or
* installing MS on a different machine than the one used to view the dashboard
set the [Base URL](../configuration/configuration.md#base-url) as the IP of the host machine. (This is the IP you would use to view the dashboard in a browser)
* With docker: `-e BASE_URL="http://hostMachineIP"` (first port is the port on the host to use)
* With docker-compose: [see comments in docker-compose.yml](../../../docker-compose.yml)
#### Other
* (Optionally) set the [timezone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) for the container using the environmental variable `TZ` ([docker](https://docs.docker.com/engine/reference/commandline/run/#env)) ([docker-compose](https://docs.docker.com/compose/compose-file/compose-file-v3/#environment))
### Linux Host
If you are
* using [rootless containers with Podman](https://developers.redhat.com/blog/2020/09/25/rootless-containers-with-podman-the-basics#why_podman_)
* running docker on MacOS or Windows
this **DOES NOT** apply to you.
If you are running Docker on a **Linux Host** you must specify `user:group` permissions of the user who owns the **configuration directory** on the host to avoid [docker file permission problems.](https://ikriv.com/blog/?p=4698) These can be specified using the [environmental variables **PUID** and **PGID**.](https://docs.linuxserver.io/general/understanding-puid-and-pgid)
To get the UID and GID for the current user run these commands from a terminal:
*`id -u` -- prints UID
*`id -g` -- prints GID
## Docker Usage Examples
If installing on a different machine make sure all redirect URIs are defined or that you have set a [Base URL](#base-url).
### Using [env-based](../configuration/configuration.md#env-based-configuration) configuration
If you have multi-scrobbler installed [locally](installation.md#local) you can enable it to run as a background service when you login.
Before running as a service you should run it at least once in the foreground to ensure it can start up correctly!
# [Systemd](https://systemd.io/)
Systemd is system and service manager that is used by [most popular linux distros](https://en.wikipedia.org/wiki/Systemd#Adoption) including [Ubuntu](https://wiki.ubuntu.com/SystemdForUpstartUsers), [Fedora](https://docs.fedoraproject.org/en-US/quick-docs/understanding-and-administering-systemd/), Pop!_OS, [Debian](https://wiki.debian.org/systemd), and [Arch](https://wiki.archlinux.org/title/systemd).
This setup will create a [user service](https://wiki.archlinux.org/title/systemd/User) that runs on login.
## Create A Unit File
Create a new service file for multi-scrobbler under your HOME config:
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)
* Supports scrobbling from many **Sources**
* [Spotify](docs/configuration#spotify)
* [Plex](docs/configuration#plex) or [Tautulli](docs/configuration#tautulli)
* [Subsonic-compatible APIs](docs/configuration#subsonic) (like [Airsonic](https://airsonic.github.io/) and [Navidrome](https://www.navidrome.org/))
* Monitor status of Sources and Clients using [webhooks (Gotify, Ntfy, Apprise)](docs/configuration#webhook-configurations) or [healthcheck endpoint](docs/configuration#health-endpoint)
* 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](docs/installation#docker), [flatpak](docs/installation#flatpak), or [locally with NodeJS](docs/installation#nodejs)
**Why should I use this over a browser extension and/or mobile app scrobbler?**
* **Platform independent** -- Because multi-scrobbler communicates directly with service APIs it will scrobble everything you play regardless of where you play it. No more need for apps on every platform you use!
* **Open-source** -- Get peace of mind knowing exactly how your personal data is being handled.
* **Track your activity regardless of where you listen** -- Scrobble from many Sources to one Client with ease and without duplicating tracks.
* **Manage scrobbling for others** -- Scrobble for your friends and family without any setup on their part. Easily silo sources to specific clients to keep plays separate.
**But I already scrobble my music to Last.fm/ListenBrainz, is multi-scrobbler for me?**
Yes! You can use [Last.fm as a **Source**](docs/configuration#lastfm--source-) or [Listenbrainz as a **Source**](docs/configuration#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.
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.
### 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...
Source configurations consist of:
* A friendly name.
* Any data needed to communicate or authenticate with the Source.
* An optional list of Client names that the Source should scrobble to. If omitted the Source also scrobbles to all configured Clients.
### Client
A **Client** is an application that stores the historical information about what songs 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.
## Installation
[See the **Installation** documentation](docs/installation)
## Configuration
[See the **Configuration** documentation](docs/configuration)
## Usage
A status page with statistics, recent logs, and some runtime configuration options can be found at
```
http://localhost:9078
```
Output is also provided to stdout/stderr as well as file if specified in configuration.
On first startup you may need to authorize Spotify and/or Last.fm by visiting the callback URL (which can also be accessed from the status page). Visit the status page above to find the applicable link to trigger this.
## Help/FAQ
Having issues with connections or configuration? Check the [FAQ](docs/FAQ) before creating an issue!
NOTE: This steps are for building the flatpak entirely locally, from source. If you want to install the application normally then [get it through flathub](/docs/installation.md#flatpak)
The final build repo for the flathub version can be found at [flathub/io.github.foxxmd.multiscrobbler](https://github.com/flathub/io.github.foxxmd.multiscrobbler)
Set the `branch``tag` or `commit` to use for MS in the `git` source in [`io.github.foxxmd.multiscrobbler.yml`](/flatpak/io.github.foxxmd.multiscrobbler.yml)
# 3. Use `flatpak-node-generator` to generate sources
First, [make sure `node_modules` is deleted or empty.](https://github.com/flatpak/flatpak-builder-tools/issues/354#issuecomment-1478518442)
Then, from MS project root:
```shell
flatpak-node-generator npm package-lock.json
```
Move `generated-sources.json` into [`/flatpak`](/flatpak)
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.