Compare commits

...
927 Commits
Author SHA1 Message Date
FoxxMD 232c802b44 chore(release): Bump version for prerelease 2026-08-28 17:54:55 +00:00
FoxxMD e335b44abd feat(ui): Add welcome toast for new ui 2026-08-28 17:36:21 +00:00
FoxxMD 115911c63a feat(ui): add link to new ui on old dashboard 2026-08-28 17:05:57 +00:00
FoxxMD f45493d10e fix(ui): don't hide bulk play actions menu button on mobile 2026-08-28 16:56:32 +00:00
FoxxMD 574b723fa4 fix(spotify): Patch in a hardcoded 5-9 sec timeout for requests 2026-08-28 16:31:23 +00:00
FoxxMD 02f1eca649 fix(ui): hydrate queue states for bulk scrobbling 2026-08-28 15:57:53 +00:00
Matt Foxx 212afaa5e5 Merge pull request #674 from FoxxMD/writeApi
Write api
2026-08-28 11:37:09 -04:00
Matt Foxx 006fbe1079 Merge pull request #685 from FoxxMD/catchConsoleDiff
fix(transform): Catch and log errors during console diff generation
2026-08-28 11:35:51 -04:00
FoxxMD 5cecac937b feat(ui): Implement bulk play retries 2026-08-28 15:28:31 +00:00
FoxxMD 297c7dee95 fix(ui): Change component state values to be lowercase so they pass validation 2026-08-28 15:27:58 +00:00
FoxxMD 42a6887141 fix: fix method for getting last transformed data 2026-08-28 12:34:19 +00:00
FoxxMD cbdb58b36e fix(transform): fix method for getting last transformed play data
Instead of assuming the previous step had data, reverse search the history array for the most current data to use for patching
2026-08-28 00:48:13 +00:00
FoxxMD ae20d46456 one more fix for queue filter state 2026-08-27 20:09:10 +00:00
FoxxMD 6b12bd2ef2 fix(ui): fix dead queue filtering 2026-08-27 19:55:38 +00:00
FoxxMD dde19cfc83 fix stale error on requeue 2026-08-27 19:50:00 +00:00
FoxxMD 0938e95e1d last fix for dead letter queue counts 2026-08-27 19:39:19 +00:00
FoxxMD 3a40478cb1 fix: fix queue status in requeued event 2026-08-27 19:31:19 +00:00
FoxxMD 935845a976 fix: further queue state cleanup before converting to event 2026-08-27 19:11:16 +00:00
FoxxMD b494e8f93f fix: don't need queue context after processing 2026-08-27 18:51:46 +00:00
FoxxMD 9e13ead40f feat: Add more queue context and render context in timeline 2026-08-27 18:35:43 +00:00
FoxxMD 3b2f5ee1c8 fix: Fix queue event names 2026-08-27 17:25:51 +00:00
FoxxMD 51de79b92f fix(ui): Fix missed statt text arrow color 2026-08-27 17:25:35 +00:00
FoxxMD b6f7e0e586 refactor(ui): Simplify and make stats code reusable 2026-08-27 17:10:45 +00:00
FoxxMD c69af47978 fix(transform): Catch and log errors during console diff generation
This functionality is not critical to transforms, just helpful for console debugging. It should not stop transforms from occuring so catch all errors and log.

#684
2026-08-27 15:05:35 +00:00
FoxxMD edde8f9e74 try moving openapi generate into docs script 2026-08-27 00:27:46 +00:00
FoxxMD 72d702acf0 docs: Add stability disclaimer 2026-08-27 00:18:07 +00:00
FoxxMD 67b13ffe40 docs: Add npm scripts to build openapi and generate docs 2026-08-27 00:10:17 +00:00
FoxxMD b464c7507f add openapi.json to gitignore 2026-08-27 00:05:59 +00:00
FoxxMD 3cf5b82247 working poc for openapi doc generation 2026-08-27 00:05:35 +00:00
FoxxMD 1571f06662 docs: Update docusaurs to 3.10.2 2026-08-26 20:05:29 +00:00
FoxxMD b3fb04a6ae docs: generate openapi spec 2026-08-26 20:01:13 +00:00
FoxxMD cb2a6168fd Update openapi doc meta for friendly names, descriptions, and categories 2026-08-26 15:01:39 +00:00
FoxxMD 56fcbedff9 feat: Implement zod validation for endpointlz requests 2026-08-26 14:32:05 +00:00
FoxxMD 548c614b0a test(lastfm): Add smoke tests for 2.0 endpoints 2026-08-26 13:28:10 +00:00
FoxxMD d3469aee7a fix(lastfm): Fix xml builder when fields are undefined 2026-08-26 12:57:35 +00:00
FoxxMD 0109cc6fcd fix(lastfm): Fix urlencoded number string coercion in request validation 2026-08-26 12:57:08 +00:00
FoxxMD 1988fc5f54 mostly figured out lfm validation... 2026-08-26 01:48:50 +00:00
FoxxMD aa93600166 chore: bump express-typed-router 2026-08-26 00:09:35 +00:00
FoxxMD 09e9d0f79a feat: Use new InferSchemaHandler type to simplify route handlers 2026-08-26 00:07:37 +00:00
FoxxMD 9c84e2d8e3 fxi(webscrobbler): Fix test data generation 2026-08-25 23:26:11 +00:00
FoxxMD 4b8d4932aa test: fix signal used to wait for queued played to finish processing before checking queue length 2026-08-25 23:06:47 +00:00
FoxxMD 970e200e76 fix types for matchRequest 2026-08-25 22:39:11 +00:00
FoxxMD 3e05b3f964 feat: Implement tests for listenbrainz and webscrobbler routes 2026-08-25 21:16:30 +00:00
Matt Foxx 86b8b4ad90 Merge pull request #681 from DirazCoder/master
confirm before removing all failed scrobbles
2026-08-25 14:15:08 -04:00
FoxxMD 64d160782b replace webscrobbler types with zod 2026-08-25 17:28:35 +00:00
FoxxMD 39f8367392 fill in the majority of routes with new typed routes for regex 2026-08-25 17:02:52 +00:00
FoxxMD 62905e8010 feat(ui): Implement retry options and delete flow 2026-08-25 13:15:22 +00:00
FoxxMD 203f4c343e feat(ui): Wire up non-form play actions 2026-08-25 13:15:22 +00:00
FoxxMD 3b93073694 chore: Bump express-typed-routes for type fixes 2026-08-25 13:15:22 +00:00
FoxxMD 649280582e feat(ui): Implement play actions 2026-08-25 13:15:22 +00:00
FoxxMD 0ad86ad692 feat: Implement api endpoitns for requeuing, cancelling, and deleting dead states 2026-08-25 13:15:22 +00:00
FoxxMD e80f35f1b1 feat: dead queue to ingress migration 2026-08-25 13:15:22 +00:00
FoxxMD a885079a07 Finish queue implementation for source and various fixes 2026-08-25 13:15:22 +00:00
FoxxMD ab994770ab fix(deezer): Fix missing closest play for some fuzzy scenarios 2026-08-25 13:15:22 +00:00
FoxxMD 17d7882710 feat(client): Unify queue processing for regular and dead plays 2026-08-25 13:15:22 +00:00
FoxxMD 1dcbf92fed fix(ui): Fallback to source for music service icon 2026-08-25 13:15:22 +00:00
FoxxMD 8686309993 fix(ui): Fix music service icon string variable assumption 2026-08-25 13:15:22 +00:00
FoxxMD b83f8872c2 start simplifying queue scrobble for clients 2026-08-25 13:15:22 +00:00
FoxxMD 9f22c56e87 feat(source): Implement queue context option usage during play processing 2026-08-25 13:15:22 +00:00
FoxxMD 6b86ef8629 feat(transform): Implement path to control cache usage 2026-08-25 13:15:22 +00:00
FoxxMD d0001d2f62 feat(source): Implement Play retry logic 2026-08-25 13:15:22 +00:00
FoxxMD e381be2f7e feat: Implement queue context with migration 2026-08-25 13:15:22 +00:00
FoxxMD c1e9757aed feat(ui): Optimize musicbrainz/external links layout in play data 2026-08-25 13:15:22 +00:00
FoxxMD 61fbc39a91 feat(ui): add play source to activity summary 2026-08-25 13:15:22 +00:00
FoxxMD 4c764879d7 more ignore behavior fixes 2026-08-25 13:15:22 +00:00
FoxxMD 692b2160bf fix: Fix auth routes with new typed routing 2026-08-25 13:15:22 +00:00
FoxxMD 344c0a15ad feat: Add now playing behavior for monitoring state 2026-08-25 13:15:22 +00:00
FoxxMD f44835dfba feat: Migrate to typed router 2026-08-25 13:15:22 +00:00
FoxxMD 204cc0bdb8 Improved monitoring behavior
* Discard plays early to avoid unnecessary transforms and existing checks
* Don't throw client scrobble processing, just set play as discarded
* Fix/improve source play processing on failure
2026-08-25 13:15:22 +00:00
FoxxMD 4b7542ddd9 fix: Add missing play update events 2026-08-25 13:15:22 +00:00
FoxxMD 1fc3ad1bd4 fix(source): Missing original data on play create 2026-08-25 13:15:22 +00:00
FoxxMD 9f34929140 create transform events with createdAt as now 2026-08-25 13:15:22 +00:00
FoxxMD 1178b0b6fb fix: Queue event added in repo by explicit parameter 2026-08-25 13:15:22 +00:00
FoxxMD cad42cba04 refactor play event data into factory functions 2026-08-25 13:15:22 +00:00
FoxxMD 68b7385f31 test: Fix useless polling wait slowing tests 2026-08-25 13:15:22 +00:00
FoxxMD 9a93f13a7d fix: fix missing events in apifixtures 2026-08-25 13:15:22 +00:00
FoxxMD 649ccde43d refactor: db retention cleanup for transform data 2026-08-25 13:15:22 +00:00
FoxxMD fc06c6a8a9 feat(ui): Add state change timeline event 2026-08-25 13:15:22 +00:00
FoxxMD d089b960f9 feat: Refactor activity timeline elements into proper event structure
* Implement new database playEvents table/entity
* Refactor usage of lifecycle,scrobbler,etc... with write-only play events
* Refactor/simplify activity timeline to use new event structure
2026-08-25 13:15:21 +00:00
FoxxMD c210590c8f fix: Add queue id to consumeQueue and implement queued id map to prevent processing an already processing play 2026-08-25 13:15:21 +00:00
FoxxMD 78aa418b7d feat: Filter next queued by optional id 2026-08-25 13:15:21 +00:00
FoxxMD 48cc18d373 fix(source): Use playDate SOT when generating temporal range for sql query 2026-08-25 13:15:21 +00:00
FoxxMD 258cd61b4b feat(source): Improved queue prunning for backlog and history sources 2026-08-25 13:15:21 +00:00
FoxxMD 185ccdf71c fix(source): Refactor existing play list used for existing check to use *all* processed plays
Instead of just discovered, since we now dupe/discard plays that persist in db, change the cached existing list logic to include all processed plays
2026-08-25 13:15:21 +00:00
FoxxMD b9dad6149b feat(deezer): Improve documentation on fuzzyDiscovery option usage 2026-08-25 13:15:21 +00:00
FoxxMD 9ae74a193f fix: using incorrect variable for existing 2026-08-25 13:15:21 +00:00
FoxxMD 99b30956ba fix: check for undefined event payload 2026-08-25 13:15:21 +00:00
FoxxMD 93a5775363 feat: add parsed from meta 2026-08-25 13:15:21 +00:00
FoxxMD e8a998ddda finish source queue discovery migration 2026-08-25 13:15:21 +00:00
FoxxMD 1b02dc684e test: Fix app migrations relative folder 2026-08-25 13:15:21 +00:00
FoxxMD 23813a65ef feat(source): Implement discovery queue 2026-08-25 13:15:21 +00:00
FoxxMD 0583c66743 db: Add playHash to to play input 2026-08-25 13:15:21 +00:00
FoxxMD cd4e88b232 refactor: rename queue constant to remove client
will be using for both source and client so name is less restrictive now
2026-08-25 13:15:21 +00:00
FoxxMD 8be739efb4 feat: Simplify component state updates for start/stop/restart and replace error usage 2026-08-25 13:15:21 +00:00
FoxxMD 3f2e29c5c8 feat: Implement convenience functions for replacing/clearning component errors 2026-08-25 13:15:21 +00:00
FoxxMD b109157350 disable ignore actions for now (not yet implemented) 2026-08-25 13:15:21 +00:00
FoxxMD ce15c4049c fix: component start invocation runs heartbeat immediately 2026-08-25 13:15:21 +00:00
FoxxMD 5a7c01a6d9 feat(ui): Implement test auth action 2026-08-25 13:15:21 +00:00
FoxxMD befd1ac043 feat(ui): Implement auth actions 2026-08-25 13:15:21 +00:00
FoxxMD 714678fdab finish implementation of AuthError 2026-08-25 13:15:21 +00:00
FoxxMD 2a58edd9b2 refactor: Strongly type and simplify auth error logic
* Use separate errors for init auth check throws and root auth error throws (to be implemented in each component)
* Consolidate auth issue discovery to one reusable function
* rethrow on testAuth based on auth error type and unrecoverable flag
2026-08-25 13:15:21 +00:00
FoxxMD abfceeb5f8 partial auth api 2026-08-25 13:15:21 +00:00
FoxxMD d7c88d4191 feat: Implement multiple stateful errors/warnings on component 2026-08-25 13:15:21 +00:00
FoxxMD a44fe48314 feat(ui): Implement basic component state change actions #500
Start/Stop/Restart client/sources from the ui
2026-08-25 13:15:21 +00:00
Matt Foxx 29ffc6b804 Merge pull request #680 from FoxxMD/listenbrainzUA
feat: Version and user-agent improvements
2026-08-24 18:10:39 -04:00
FoxxMD 3bdc9447b8 chore(release): Bump version for release 2026-08-24 21:38:46 +00:00
DirazCoder 4a27806825 Add confirmation for removing all dead scrobbles
Added confirmation dialog before removing all failed scrobbles.
2026-08-24 22:23:02 +01:00
FoxxMD b8cd53f554 fix(listenbrainz): Add user-agent to requests #679 2026-08-24 21:05:30 +00:00
Matt Foxx e7218074f8 Merge pull request #678 from halkeye/patch-1
fix(docsite): path.join doesn't really handle http links
2026-08-24 15:20:11 -04:00
Gavin Mogan a48ada9ac3 fix(docsite): path.join doesn't really handle http links 2026-08-22 23:30:43 -07:00
Matt Foxx bc28de66b1 Merge pull request #673 from mmattbtw/feature/teal-production-namespace
feat(teal): migrate play records to production namespace
2026-08-13 10:05:16 -04:00
FoxxMD bc18993cd3 fix(tealfm): Fix AI mistakes
* Remove gitignore entries and remove json lexicon files, we don't redistribute the lexicon
* regenerate lexicon with full definitions so we get functionality non-alpha namespace and re-add alpha to barrel export
* fix type narrowing for musicservice/origin
  * reuse isGenericUri instead of reinventing the regex wheel
* fix property assertions for renamed musicservice/origin(uri)
* use clientresponse type guard in a way typescript can actually understand it, fixes cursor type error
* reuse sortByNewestPlayDate instead of reinventing the sort wheel
2026-08-13 13:48:16 +00:00
mmattbtw d280ca5dd2 feat(teal): migrate play records to production namespace 2026-08-12 20:47:00 -05:00
FoxxMD 155d8a3252 fix(docs): Add zockerId keyword to ajv options 2026-08-12 18:01:08 +00:00
FoxxMD 68d9417cf7 fix(docs): override sharp version to fix node-gyp on arm
An old version of libvips was failing(?) to downloading
causing sharp to use node-gyp rebuild which errors due
to missing python on the base image

This shouldn't happen since we can def use a prebuild binary
so update sharp which updates libvips version used.
2026-08-12 17:24:00 +00:00
FoxxMD 9b7483db91 chore(release): Bump version for release 2026-08-12 16:40:23 +00:00
FoxxMD 7c0797b496 feat: Better validatation for normalized config urls 2026-08-12 16:32:42 +00:00
FoxxMD 1b3cbe9ab3 feat: Normalize domain:port url input 2026-08-12 13:27:10 +00:00
FoxxMD 15e0332ac2 feat: Update json schema to draft-2020-12 2026-08-11 20:37:24 +00:00
FoxxMD d532c85c2a fix: Relax some config url schema requirements with normalization 2026-08-11 20:36:47 +00:00
FoxxMD eecbea4cab add missing error clear when building clients 2026-08-11 18:40:48 +00:00
FoxxMD 823dd5d011 fix: Hand-roll applemusic env fixture data 2026-08-11 18:32:42 +00:00
FoxxMD 0fa9a8df10 fix(discord): Fix discord ENV parsing validation 2026-08-11 18:21:58 +00:00
FoxxMD a76213a7a0 docs: Hand-roll discord ENV updates 2026-08-11 15:56:54 +00:00
FoxxMD a42ab3d8fd fix: Hand-rolled env data fixtures for some edge cases 2026-08-11 15:46:19 +00:00
FoxxMD 475a3f1276 fix: narrow env config key collection to include prefix underscore 2026-08-11 15:14:08 +00:00
FoxxMD 6dcfd6028b fix: Narrow url/IP config zod types from string to purpose-built regex types 2026-08-11 15:06:52 +00:00
FoxxMD 54247e89c2 feat: Add ENV config test suite 2026-08-11 14:53:38 +00:00
Matt Foxx 433ea532ae Merge pull request #670 from FoxxMD/stronglyTypedEvents
feat: strongly type internal events
2026-08-11 08:56:24 -04:00
FoxxMD b615172ed1 feat: strongly-type internal events
* refactor wildcard emitter for better "any" usage
* strongly type emitter with event map
* fix webhook payload accessor #666
2026-08-10 21:03:31 +00:00
FoxxMD aa2beb5846 fix: Check for ENV prefix with startsWith instead of includes
#668
2026-08-10 20:53:53 +00:00
FoxxMD 06d3c6a3a8 docs: Add more guidance for File config example/editor
* Add configureAs callout for ambiguous sources
* Add editor validation limitation for ambiguous components
2026-08-10 18:15:49 +00:00
FoxxMD c0cfc5fa9f feat: relax configureAs requirements for AIO configs
* Do not require configureAs for AIO sources since we can determine from context
* Break out aio/file validation for better DX
2026-08-10 17:42:28 +00:00
FoxxMD 2195ac7057 chore(release): Bump for release 2026-08-10 16:36:23 +00:00
Matt Foxx 3fc67146c7 Merge pull request #669 from FoxxMD/envPrefixUsage
fix: Fix incorrect prefix not being used for env key collection
2026-08-10 12:35:34 -04:00
FoxxMD fe4e625824 docs: Improve scenario guidance for lfm/lz endpoints 2026-08-10 16:21:39 +00:00
FoxxMD b98756a262 fix: Fix incorrect prefix not being used for env key collection
#668
2026-08-10 14:21:59 +00:00
Matt Foxx 2b7521ebac Merge pull request #667 from Exerra/master
fix(AppleMusicSource): include library-songs in recent tracks
2026-08-09 16:55:29 -04:00
Exerra ecd48ca234 fix(AppleMusicSource): include library-songs in recent tracks
Updated the Apple Music recently played tracks query to include 'library-songs' types in addition to 'songs', ensuring a more comprehensive history retrieval.
2026-08-09 23:49:42 +03:00
FoxxMD 41aaaca530 docs: In 0.14.0 upgrade guide add note about ID requirement in 0.16.0+ 2026-08-08 21:06:31 -04:00
FoxxMD ed42da6843 chore(release): Bump version for release 2026-08-08 20:56:01 -04:00
Matt Foxx 34044401d4 Merge pull request #664 from FoxxMD/fixNewPlayRecent
fix(source): Fix missing id/uid on cached recent plays
2026-08-08 20:53:51 -04:00
FoxxMD f61e50fd45 fix(source): Fix missing id/uid on cached recent plays
#662
2026-08-08 17:12:13 -04:00
FoxxMD f5ad2ad29b chore(release): Bump version for release 2026-08-08 19:29:38 +00:00
FoxxMD 2e4acd0f00 fix: Add missing configureAs for ENV parsed components
#661
2026-08-08 19:24:45 +00:00
FoxxMD b9517bd066 docs: Add more type guidance for aio/file config 2026-08-07 17:22:20 +00:00
FoxxMD 335880249f chore(release): Bump version for release 2026-08-07 16:49:36 +00:00
Matt Foxx 5c2917f906 Merge pull request #660 from FoxxMD/ingressSot
Ingress sot
2026-08-07 12:34:15 -04:00
FoxxMD 124e857d9f fix: now playing playerUpdate event data 2026-08-07 14:53:02 +00:00
FoxxMD 5dee499381 test: fix failing tests from np data structure change 2026-08-07 14:53:02 +00:00
FoxxMD 10325fd4e6 refactor(now playing): Use more efficient solution for ingress now playing
Re-queue valid ingress NP update on client rather than spamming all clients with duplicate updates from memory source

This should have the same functional affect with way less log noise
2026-08-07 14:53:02 +00:00
FoxxMD 045890e243 feat(sourc): Add Ingress SOT type and force-push ingress player updates
#659
2026-08-07 14:53:02 +00:00
FoxxMD 685e48a7a6 fear(ui): Implement cache clearing mechanism #634 2026-08-06 20:20:11 +00:00
FoxxMD 7eb5104f0b feat(ui): Implement basic settings page 2026-08-06 19:34:02 +00:00
FoxxMD 4272e8ea58 feat(ui): Improve UX for theme switcher
* Remove three-state toggle
* Revert to system theme only when *user* initiated toggle results in same theme as system
2026-08-06 18:24:57 +00:00
FoxxMD 8e6d35a85b fix(lz endpoint): Fix missing early return when no sources for playing now response 2026-08-05 19:08:08 +00:00
FoxxMD 917d05f295 docs: Update configuration types language 2026-08-05 15:02:21 +00:00
FoxxMD 287c994643 docs: Some corrections and updated language 2026-08-05 14:15:25 +00:00
Matt Foxx 7980a0c856 Merge pull request #657 from FoxxMD/lastfmEndpointResponses
feat(lfm endpoint): Implement more api emulation
2026-08-05 09:28:43 -04:00
FoxxMD 9d2557cf6a feat(lfm endpoint): Handle scrobble payload variants 2026-08-04 22:14:03 +00:00
FoxxMD 6f8debc304 docs: Add lastfm endpoint alternative guidance 2026-08-04 18:50:59 +00:00
FoxxMD b53ae78d55 fix(lfm endpoint): Fix no response on no slug matched 2026-08-04 18:30:16 +00:00
FoxxMD 8723bc8890 fix(lfm endpoint): Improve regex slug matching to handle more variants 2026-08-04 18:29:41 +00:00
FoxxMD eca8ad2ce1 fix(lfm endpoint): Fix slug regex match 2026-08-04 18:16:39 +00:00
FoxxMD b6b8d3423f fix(lfm endpoint): Detect desired json response from accept header 2026-08-04 18:06:45 +00:00
FoxxMD fc027e8554 fix(lfm endpoint): remove duplicated method restrictions 2026-08-04 16:07:11 +00:00
FoxxMD 707da834bb docs: regenerate tealfm source env 2026-08-04 15:10:17 +00:00
FoxxMD 002520cd8a docs: Fix config.json example 2026-08-04 15:10:06 +00:00
FoxxMD 7ca84ce27f feat(lfm endpoint): Implement more api emulation #656
* Add optional username/apiKey config fields
* Identify Source by optional username/sk/api_key
* Emulate auth.getMobileSession response with sk derived from Source uid
* Update docs with guidance for multiple sources and improved setup language
2026-08-04 14:59:05 +00:00
FoxxMD 8af56cb561 revert: rollback tealfm source optional app password for now
This needs to be part of a larger refactor to require app password/permissions only for specific operations in the api client
2026-08-03 18:29:03 +00:00
FoxxMD 0e249b13a9 fix(source): Fix player cleanup for ingress sources
This bool check was backward. The scheduled task should no-op for polling sources and run cleanup for ingress sources

#652
2026-08-03 18:25:12 +00:00
FoxxMD 591b5c6618 fix(tealfm): app password for source is optional 2026-08-03 16:55:16 +00:00
FoxxMD 3c5b9cc402 fix(teal): teal client app password is required 2026-08-03 16:52:32 +00:00
FoxxMD 420a053e25 docs: update now playing for clients 2026-08-03 16:06:23 +00:00
FoxxMD 72f64953f6 chore: remove unused ts-zod config 2026-08-03 15:55:00 +00:00
Matt Foxx 8b8c2765c7 Merge pull request #654 from FoxxMD/configBuildSimplify
Simplify config validation and building
2026-08-03 11:53:23 -04:00
FoxxMD e1324921e9 fix: Fix missing name passed to component classes 2026-08-03 15:38:35 +00:00
FoxxMD 9f71fbef97 fix: prevent creating source/client with same id 2026-08-03 15:21:11 +00:00
FoxxMD dd22418c41 fix(tests): Fix missing required id 2026-08-03 14:56:33 +00:00
FoxxMD d296b825d6 Id logging and schema cleanup
* Guard against undefined name when logging component disabled
* Add id/name hint to config location
* inherit name/id field from common schema
2026-08-03 14:31:24 +00:00
FoxxMD 792ccc3c09 feat: Improve location wording for config errors 2026-08-03 13:39:14 +00:00
FoxxMD 6772e9934d refactor!: Enforce mandatory id for file/aio 2026-08-03 13:38:49 +00:00
FoxxMD 4ce7219214 fix: Skip ambigious configs during source parsing if configureAs is not present 2026-08-02 20:21:55 +00:00
FoxxMD b2ce42fecf simplify jellyfin mediatype usage for now 2026-08-01 19:12:04 +00:00
FoxxMD 46a061a602 docs: Update comma-separated list zod types for envs 2026-08-01 19:07:49 +00:00
FoxxMD ff7360fced docs: More env row generation improvements
* Always use `out` of common envs
* Wrapper function for normalizing descriptions with comma-separate lists
2026-08-01 19:07:35 +00:00
FoxxMD 425a0b2807 Partial fix for flattening complex zod types for docs 2026-08-01 17:47:44 +00:00
FoxxMD ad632f38d8 docs: regenerate env docs 2026-08-01 17:31:37 +00:00
FoxxMD 1d1b3b6f56 docs: Update env descriptions for id, name, and enable 2026-08-01 17:31:28 +00:00
FoxxMD 7575e8bff7 fix: Fix unhandled ZodTransform type generation 2026-08-01 17:31:03 +00:00
FoxxMD 31546ed4c1 fix: Early return on component add to prevent dynamic loading 2026-08-01 15:09:52 +00:00
FoxxMD bf9cdc141c re-add clients to aio json schema 2026-08-01 00:05:07 +00:00
FoxxMD 02ab1571b6 refactor: Convert validation client structures to dynamically import 2026-07-31 23:57:59 +00:00
FoxxMD 699bd02782 refactor: Convert validation structures to dynamically import 2026-07-31 23:41:56 +00:00
FoxxMD 0d45d06966 docs: Italicize required env properties 2026-07-31 23:41:12 +00:00
FoxxMD a93d02e1b7 env doc generation fixes 2026-07-31 20:50:41 +00:00
FoxxMD 513e2dde15 feat: Use env config generated from schema 2026-07-31 16:37:59 -04:00
FoxxMD 2fc90dcfda feat: Generate env markdown for components 2026-07-31 20:15:11 +00:00
FoxxMD fd2a178808 feat: Add env prefix to schema interface 2026-07-31 20:08:00 +00:00
FoxxMD 9f54d45576 fix(source): Fix missing defaults 2026-07-31 19:43:00 +00:00
FoxxMD 78aff32334 refactor: Extract source/client building into a function to reduce verbosity 2026-07-31 15:33:34 -04:00
FoxxMD 0115ccb862 refactor: Simplify ScrobbleSources config builder 2026-07-31 19:01:10 +00:00
FoxxMD 8a71663c9f Implement remaining source env schemas 2026-07-31 18:27:01 +00:00
FoxxMD 40eff0b901 refactor: Simplify ScrobbleClients config builder 2026-07-31 17:27:44 +00:00
FoxxMD 13acc56424 refactor client env schemas to reuse zod shape where possible 2026-07-31 10:39:14 -04:00
FoxxMD 09fc0ce15c Implement majority of source env schemas 2026-07-31 10:32:21 -04:00
FoxxMD 4fbdd71588 add EnvSourceSchema interface 2026-07-31 14:03:04 +00:00
FoxxMD 7cf105935e feat: Implement remaining env schemas for clients 2026-07-31 09:59:57 -04:00
FoxxMD 1bd10326eb add generic client env data validation interface 2026-07-31 13:31:52 +00:00
FoxxMD 74605caf15 add interface for env schemas 2026-07-31 13:10:13 +00:00
FoxxMD 7dec9a39e6 chore(release): Bump version for release 2026-07-30 17:50:57 +00:00
FoxxMD 90a8426748 Proof of concept: generate ENV docs from zod schema 2026-07-30 17:19:32 +00:00
FoxxMD 8469d161e9 feat: Reduce db write frequency when idle by buffering date updates #653 2026-07-30 12:37:38 +00:00
Matt Foxx 941f50bff2 Merge pull request #651 from FoxxMD/mbTrackGuard
fix: Guard against tracks/track in mb recording medium
2026-07-30 00:09:59 -04:00
FoxxMD f8d882cfcf docs: Clarify when activity is scrobbled 2026-07-29 19:37:40 +00:00
FoxxMD 0704cebe72 fix(webscrobbler): Fix bad access of PlayerState #650 2026-07-29 15:49:54 +00:00
FoxxMD e43a18bcbb fix: Guard against tracks/track in mb recording medium
Fixes #649
2026-07-29 15:18:45 +00:00
FoxxMD 0baa399e9f lint: Fix new error instantiation 2026-07-29 15:18:02 +00:00
FoxxMD 05b2e1c116 fix: Fix missing logPath base dir argument
NixOS/nixpkgs#519119
2026-07-28 21:22:56 +00:00
FoxxMD a0cdaeb01f docs: Add missing user type to user transform examples 2026-07-28 18:59:49 +00:00
FoxxMD 73c35751ed chore(release): Bump version for RC1 release 2026-07-28 17:27:57 +00:00
FoxxMD 516a8d2178 chore: Cleanup development comments 2026-07-28 17:27:23 +00:00
FoxxMD d595bf9961 chore: Remove zod development package and re-upgrade typescript to 6 2026-07-28 16:49:51 +00:00
FoxxMD 8345779030 refactor: cleaned up aio validation and client/source map 2026-07-28 16:43:51 +00:00
FoxxMD b58d39c90e feat(ui): Implement subsonic playback reporting indicators 2026-07-28 16:14:01 +00:00
FoxxMD 7074ece0a5 docs: Simplify subsonic playback reporting language 2026-07-28 16:13:49 +00:00
FoxxMD bfb0d6b392 refactor: Move platform constants/types to prevent frontend bundling backend code 2026-07-28 16:10:21 +00:00
Matt Foxx 5c3f49fb2f Merge pull request #647 from FoxxMD/zodExperimentRebase
feat: Replace ts-json-schema-generator + ajv with zod
2026-07-27 22:29:32 -04:00
Matt Foxx f70e680d55 Merge branch 'master' into zodExperimentRebase 2026-07-27 22:22:32 -04:00
Matt Foxx c2fda1e7d9 Merge pull request #640 from JannisPohle/feature/ignore-stale-subsonic-play-now-reports
fix: Ignore stale subsonic playing now reports
2026-07-27 22:18:44 -04:00
FoxxMD 70e4239e52 more cache config cleanup 2026-07-28 01:56:26 +00:00
FoxxMD d96073282c feat(cache): Remove deprecated cache configuration
Remove metadata/scrobble/auth config that was deprecated in 0.14.0
2026-07-28 01:56:26 +00:00
FoxxMD 2887600aa0 docs: Clarify language around id/name 2026-07-28 01:56:26 +00:00
FoxxMD 6f7aa6f567 remove unused interface names 2026-07-28 01:56:26 +00:00
FoxxMD 857f44fa14 docs: Add meta descriptions/titles for objects that become refs 2026-07-28 01:56:26 +00:00
FoxxMD d764b0204e fix(docs): Replace old json viewer links 2026-07-28 01:56:26 +00:00
FoxxMD ee2f988357 fix(docs): add aio json with reused refs to reduce file size 2026-07-28 01:56:26 +00:00
FoxxMD de69977d7e fix cf-json-schema-viz package name type 2026-07-28 01:56:26 +00:00
FoxxMD 6eab629168 remove unused patch 2026-07-28 01:56:26 +00:00
FoxxMD f21572bed0 chore: Remove ts-generator-schema rm from dockerfile layer 2026-07-28 01:56:26 +00:00
FoxxMD 98084665ba chore: remove ts-schema-generator and ajv 2026-07-28 01:56:26 +00:00
FoxxMD e8a91bb6de feat: Replace ajv/ts-schema-generator validation with pure zod 2026-07-28 01:56:26 +00:00
FoxxMD 206d1571ac feat(docs): Use zod for all doc schemas and simplify generation/usage 2026-07-28 01:56:26 +00:00
FoxxMD ebf6ec6957 add postCompare description 2026-07-28 01:56:26 +00:00
FoxxMD 03fc3e3a09 feat(docs): Inline defintions to prevent deduplication erasing descriptions 2026-07-28 01:56:26 +00:00
FoxxMD aa50842444 feat(docs): Don't hide top-level descriptions in json viewer 2026-07-28 01:56:26 +00:00
FoxxMD 03dbbdc2cf feat(docs): Patch schema viewer to show more descriptions 2026-07-28 01:56:26 +00:00
FoxxMD bf0b27924e more config annotation 2026-07-28 01:56:26 +00:00
FoxxMD e02a0cc6ae refactor(transform): Simplify transform config by removing untyped stages 2026-07-28 01:56:26 +00:00
FoxxMD ff4dd25a97 refactor(transform)!: Require user stage is typed 2026-07-28 01:56:26 +00:00
FoxxMD 28e0d03e6f Schema generation cleanup and vscode launch config fix 2026-07-28 01:56:22 +00:00
FoxxMD d7f2d6fa8e use zod for client validation 2026-07-28 01:55:25 +00:00
FoxxMD 8f33dc9f28 POC scrobble source validation at runtime 2026-07-28 01:55:25 +00:00
FoxxMD fe068a9216 refactor: Convert MonitorOptions to zod schema after rebase 2026-07-28 01:55:25 +00:00
FoxxMD 06b6e200f5 convert aioconfig 2026-07-28 01:55:25 +00:00
FoxxMD afea13e681 convert barrel client and source configs 2026-07-28 01:55:25 +00:00
FoxxMD f8401faf61 convert webhooks 2026-07-28 01:55:25 +00:00
FoxxMD 7c3590adf9 convert remaining source configs 2026-07-28 01:55:19 +00:00
FoxxMD 742025fecc feat: Convert source and client configs to zod 2026-07-28 01:54:41 +00:00
Jannis Pohle b983de1a58 docs: update subsonic source docs 2026-07-26 06:40:43 +00:00
Jannis Pohle ea77ea435a feat: add support for the playbackReport extension for OpenSubsonic
If the extension is provided by the server and playback data is reported by the client, make use of the new position and state fields when polling the current play entry from subsonic. If the new fields are not provided, fallback to the old timestamp based tracking listen progress.
2026-07-26 06:24:59 +00:00
FoxxMD ecf3273eb9 add zod packages 2026-07-24 16:51:31 -04:00
Jannis Pohle a79b697496 fix: add configuration key for disabling auto-detection of stale songs from subsonic servers via the minutesAgo field 2026-07-24 14:18:06 +00:00
Matt Foxx 79874c6c20 Merge pull request #645 from FoxxMD/ignoreRefactor
refactor: Expand monitoring behavior
2026-07-23 12:30:50 -04:00
FoxxMD aa91d2cb92 fix: Fix missed autoMonitor variable name change 2026-07-23 16:24:17 +00:00
FoxxMD c727445f76 fix: Fix api test fixtures for new api surface with monitoring 2026-07-23 15:32:57 +00:00
FoxxMD c25ca5297c docs: Replace should scrobble with monitoring behavior 2026-07-23 15:08:04 +00:00
FoxxMD 956054f387 refactor(ui): Update legacy ui to be compatible with new monitoring structure 2026-07-23 14:50:34 +00:00
FoxxMD 86ebd3d673 fix(ui): Only render component details when data is present 2026-07-23 14:36:08 +00:00
FoxxMD 0679937b54 feat!: Implement monitoring/ingore activity for all components
* Replace "manual listening behavior" for azuracast/icecast with monitoring functionality for all sources/clients
* Rename "systemScrobble" to "autoMonitor"
* Implement functionality for all sources/clients
2026-07-23 13:41:12 +00:00
Matt Foxx 61a2ff5180 Merge pull request #644 from FoxxMD/isrcNormalization
fix: Normalize ISRC values #643
2026-07-23 08:27:34 -04:00
Matt Foxx 26db80d8c7 Merge pull request #642 from FoxxMD/rockskyAlbumArtistFix
fix(rocksky): Fix missing albumArtist field #632
2026-07-22 15:23:42 -04:00
FoxxMD fc85790df5 fix: Normalize ISRC values #643
Spotify does not gaurantee non-hyphenated ISRC and musicbrainz won't search for hyphenated, so normalize before ISRC values downstream
2026-07-22 17:19:52 +00:00
FoxxMD 212ac27dd3 fix(rocksky): Fix missing albumArtist field #632 2026-07-22 16:01:36 +00:00
FoxxMD a30d1ae6a5 fix: Move project root dir constant location into backend-only scope
When in core its being pulled into vite bundler and causing build issues. It's only needed by backend anyways so move to backend-scope.
2026-07-22 15:40:44 +00:00
FoxxMD 5c586fb635 fix(storybook): Rename configure file causing ts to fail due to extension 2026-07-22 15:39:07 +00:00
FoxxMD de0d336325 fix(rocksky): Remove undefined keys from payload before sending 2026-07-22 15:38:18 +00:00
Matt Foxx 65d0c7c16b Merge pull request #641 from mattl/patch-1
Update librefm.mdx
2026-07-21 23:54:20 -04:00
Dr. Matt Lee fc6cc68d59 Update librefm.mdx 2026-07-21 18:10:16 -04:00
Matt Foxx 8c83969e95 Merge pull request #638 from FoxxMD/dataDirStandardize
Separate and standardize config/data dirs
2026-07-21 12:53:19 -04:00
Jannis Pohle 084dca73c1 fix: Ignore stale subsonic playing now reports
Ignore playing now entries from subsonic sources when their indicated start time from minutesAgo suggests that playback has been paused or stopped.
2026-07-19 07:01:41 +00:00
FoxxMD 7ac57ca757 feat(ui): Add theme switcher to header
#500
2026-07-17 16:41:24 +00:00
FoxxMD 1dc8c21716 docs: Add preview UI callout to landing 2026-07-17 14:53:57 +00:00
FoxxMD 6f8e7d3862 docs: Update intallation instructions with config/data dir guidance
#616
2026-07-16 16:14:24 -04:00
FoxxMD a6d33fc2ef feat: Move config and data dir creation into app
* only use docker init for fixing permission
  * don't rely on docker init for dir creation as other distros may need this functionality (nix)
* check for and create config/data dirs recursively on app start

#616
2026-07-16 16:14:24 -04:00
FoxxMD 26121ace10 feat: Separate config and data directories and use OS-standard paths, if present
* Break configDir into config and data directories so config and database/cache can be stored in different directories
* Use `env-paths` to fallback to OS-standard paths for config and data if CONFIG_DIR and DATA_DIR envs are not present
* Additionally fallback to process CWD/config if none of the above are present for some reason

#616
2026-07-16 16:14:24 -04:00
FoxxMD 27e5656a04 refactor: Replace projectDir with explicit path to project root, projectRootDir 2026-07-16 16:14:24 -04:00
Matt Foxx f3d7d59af6 Merge pull request #636 from Exerra/feat/apple-music
feat: Apple Music source
2026-07-16 15:45:04 -04:00
FoxxMD 2a4afefe64 feat(applemusic): Make album normalization user configurable and add guidance on usage 2026-07-16 16:41:36 +00:00
FoxxMD 1dba73ccb2 feat(client)!: Overhaul source scrobble filtering
* Use id OR name for matching client to scrobble to, during transition period
* Deprecate using name-only and warn when this is detected
* Consolidate logging filtered clients to one statement to reduce log noise
* Improve filter matching by making strings case and whitepsace insensitive
* On startup log `clients` options for sources
2026-07-16 15:51:54 +00:00
FoxxMD 3e82e035ac feat: Update string normalization with granular options 2026-07-16 15:49:01 +00:00
Exerra b12153fba9 docs(applemusic): improve formatting of developer tools shortcut
Updated the Apple Music configuration documentation to use <kbd> tags for keyboard shortcuts, improving readability.
2026-07-16 18:12:33 +03:00
Exerra 8c82412de4 docs: add detailed Apple Music authorization token extraction guide
Updated Apple Music configuration documentation with step-by-step instructions and screenshots for extracting authorization tokens in Chrome, Edge, Firefox, and Safari.
2026-07-16 17:15:58 +03:00
FoxxMD e804ce33ef fix(listenbrainz): Fix play fetch more indicator
latest_listen_ts is *always* the latest of *all-time*, not for the current min/max_ts range requested.

Instead, use count < than desired (and non-zero) to indicate more results
2026-07-15 20:49:37 +00:00
FoxxMD 3907fa5474 fix: Fix play fetch options logging data for pageless functions 2026-07-15 20:48:32 +00:00
FoxxMD a7b3593314 stage import rule from eslint-plugin-import-x 2026-07-15 19:08:48 +00:00
FoxxMD 76ef994067 fix: Implement unicorn eslint rules and fix them 2026-07-15 18:31:13 +00:00
FoxxMD c3cb3cf3a1 fix(client): Correct SOT scrobble api requests to not have the same from/to value 2026-07-15 17:34:37 +00:00
FoxxMD 8096219526 fix(client): Correct create/update timestamps for queues and scrobbles
* Ensure ingress queue always has an updated timestamp before dead creation
* Don't re-assign createdAt for scrobble after dead queue has already been updated
2026-07-15 17:02:29 +00:00
FoxxMD 67c1823195 feat(ui): Clean up scrobble timeline items
* Use timeline summary formatting to fix spacing
* Provide client name from fetched component data, if present, so scrobble summary is explicit where it sent/response scrobble from
2026-07-15 16:22:04 +00:00
FoxxMD 2014e78ade fix(ui): Stabilize play timeline item order 2026-07-15 14:51:42 +00:00
Exerra b3c332deeb refactor: replace Apple Music headers config with origin field
Replaces the generic `headers` configuration field with a specific `origin` field for Apple Music. Updates the config schema, environment variable handling, documentation, and source implementation to simplify origin header management.
2026-07-15 17:12:21 +03:00
Exerra 93a1042e03 test(applemusic): add tests for stripping EP/Single album suffixes
Added test cases to ensure that ' - EP' and ' - Single' suffixes are correctly stripped from Apple Music album names during the formatting process.
2026-07-15 16:21:10 +03:00
Exerra ba010240c0 feat(AppleMusicSource): include track URL in metadata
Added the web URL to the track metadata in AppleMusicSource to provide a direct link to the track.
2026-07-15 16:20:54 +03:00
FoxxMD 7f491d6974 fix: Fix hash router type 2026-07-15 13:08:04 +00:00
Exerra 74e67e0e41 feat(AppleMusicSource): include ISRC in track metadata
Added the isrc field to the track object in AppleMusicSource to improve track identification accuracy.
2026-07-15 15:59:49 +03:00
FoxxMD 1853f9abba fix(musicbrainz): Catch scenario where all search prereqs fail
#636
2026-07-15 12:58:40 +00:00
Exerra c13e7ebeec feat(applemusic): strip ' - EP' and ' - Single' from album names
Implemented album name normalization in AppleMusicSource to ensure consistency with other sources by removing standard suffixes from EPs and singles. Updated documentation to reflect this behavior.
2026-07-15 15:38:14 +03:00
Exerra 207354e799 docs(docsite): fix heading hierarchy in applemusic.mdx
Adjusted markdown heading levels to ensure proper document structure.
2026-07-15 15:36:51 +03:00
FoxxMD d7f6b17308 update boundary element linting to be more lenient
* allow core-tests from backend
* downgrade linting to warn
2026-07-15 12:23:53 +00:00
Exerra c069020aac docs(README.md): add Apple Music to supported sources list
Updated the README to include Apple Music in the list of supported scrobbling sources with a link to its documentation.
2026-07-15 13:52:53 +03:00
Exerra f84a0f190d test(applemusic): add integration tests for Apple Music module
Add new test suite for Apple Music backend logic
2026-07-15 13:39:27 +03:00
FoxxMD d0f43fbd2d feat(ui): Increase virtual play list height 2026-07-15 01:29:12 +00:00
FoxxMD b38168975e feat(ui): Compact filters
* move refresh/loading indicator into list type header
* Remove filter card title to trim filter real estate
2026-07-15 01:22:40 +00:00
Exerra a845ac10ce feat(applemusic): implement top-rebound recovery for duplicate plays
Added logic to detect and recover 'top-rebound' history patterns where Apple Music deduplicates re-listened tracks by bumping them to the top. This ensures intermediate tracks are captured and re-listened top tracks are processed correctly. Includes a new configuration option `recoverUnchangedTopHistory` to toggle this behavior.
2026-07-15 02:44:41 +03:00
Exerra 56fdb12683 feat(sources): update Apple Music configuration and environment variables
Refactored Apple Music source configuration to support token-based authentication via key files and custom headers. Renamed environment variable prefixes from 'AM' to 'APPLEMUSIC' for consistency.
2026-07-15 00:04:14 +03:00
Exerra 1f40e1e414 docs(applemusic): update configuration guide and add APPLEMUSIC_HEADERS
Added documentation for the APPLEMUSIC_HEADERS environment variable and updated code blocks with titles for better clarity in the Apple Music source configuration.
2026-07-15 00:03:06 +03:00
Exerra c1a260dc56 fix(AppleMusicSource): add authentication verification to connection checks
Implemented status code validation for Apple Music authentication and connection checks. The module now throws errors instead of failing silently when the API is unreachable or auth is invalid.
2026-07-14 21:56:59 +03:00
Exerra 5d34ea68af docs: update Apple Music configuration guide
Reorganized the Apple Music documentation to prioritize the Browser Token (Free) authentication method and clarify requirements for the paid MusicKit Key approach.
2026-07-14 21:27:23 +03:00
FoxxMD 41ea9e0aa7 feat(ui): Make timeline items more compact
#500
2026-07-14 15:23:37 +00:00
FoxxMD a9d91c2c8a fix circular buffer 2026-07-14 14:04:19 +00:00
FoxxMD a696436a75 fix: Fix missing early return for skipped stage 2026-07-14 13:47:57 +00:00
Exerra 4ae388676e chore(package-lock.json): update dependencies
Bump node-musickit-api to 3.1.0-next.5
2026-07-14 14:12:42 +03:00
Exerra 89b698d48d docs: add Apple Music source documentation
Add Apple Music to the sources overview table and the main documentation index.
2026-07-14 14:06:20 +03:00
Exerra b71a4cbbd6 build(package.json): bump node-musickit-api to v3.1.0-next.5
Updated node-musickit-api dependency to the latest next version.
2026-07-14 13:08:36 +03:00
Exerra a870cdc6f4 chore(package.json): update node-musickit-api to v3.1.0-next.4
Switched node-musickit-api dependency from a local path to version 3.1.0-next.4 from the registry.
2026-07-14 01:11:29 +03:00
Exerra c404dd5396 feat(applemusic): add support for custom headers in API requests
Introduced a `headers` configuration option to allow setting custom request headers, such as 'Origin', for Apple Music API requests. Updated configuration schema and source implementation to apply these headers.
2026-07-14 01:03:26 +03:00
Exerra cae845028d refactor(AppleMusicSource): use HistoryConsistencyResult type alias
Update parseRecentAgainstResponse return type signature to use the HistoryConsistencyResult alias instead of an inline object definition.
2026-07-14 00:50:58 +03:00
Exerra 1698c5d71e refactor(AppleMusicSource): clarify timestamp calculation logic
Updated comments in applyCalculatedTimestamps to reflect that Apple Music lacks track timestamps, necessitating a hacky estimation method.
2026-07-14 00:48:29 +03:00
Exerra 765d86e02b refactor(AppleMusicSource): extract timestamp calculation to method
Moved the reduceRight logic for calculating play timestamps into a dedicated private method 'applyCalculatedTimestamps' to improve code readability and maintainability.
2026-07-14 00:46:58 +03:00
Exerra f347b82d33 refactor(AppleMusicSource): remove redundant unknown type assertion
Cleaned up the type casting in the AppleMusicSource fetch logic by removing unnecessary unknown assertion.
2026-07-14 00:44:38 +03:00
Exerra 87aa9c461e refactor(AppleMusicSource): replace magic numbers with class constants
Introduced constants for history limits and track polling constraints to improve maintainability and readability within AppleMusicSource.
2026-07-14 00:44:25 +03:00
Exerra 417ce85bc7 refactor(AppleMusicSource): improve type safety and code readability
Refactored AppleMusicSource to use defined types for history consistency, updated the getRecentlyPlayedTracks limit type, and renamed variable for clarity.
2026-07-14 00:42:01 +03:00
Exerra c8d917cfe6 refactor(AppleMusicSource): replace AppleMusicTrack interface with Song type
Removed local AppleMusicTrack interface and updated formatPlayObj to utilize the shared Song type for improved type consistency.
2026-07-14 00:34:47 +03:00
Exerra 8c789123f9 refactor(AppleMusicSource): remove unused backlogged plays logic
Removed the unused `getBackloggedPlays` method and cleaned up the `getRecentlyDiscoveredPlays` logic in `AppleMusicSource` to streamline track processing.
2026-07-14 00:31:06 +03:00
Exerra 06ed3f4388 feat: add Apple Music source support
Implement Apple Music source, including configuration types, environment variable handling, and source initialization logic. Refactored SourceType definitions to improve maintainability.
2026-07-14 00:24:44 +03:00
FoxxMD fab2e4938d feat(ui): Persist log window state #500
Store opened state, and position/size, to local storage so logs stay where user wants them across refreshes/initial load.

Resets window to default position if any part of log window is outside viewport bounds on open.
2026-07-12 16:19:13 +00:00
FoxxMD fbcd1e1b9a refactor(ui): Fix conditional hook for SSE and add stories for header elements 2026-07-12 15:43:33 +00:00
FoxxMD d48f2bd4ac feat(ui): Replace now playing player with summary with reduced motion #500 2026-07-11 20:28:13 +00:00
FoxxMD e2dad463f6 feat: reduce now playing animation #500 2026-07-11 20:05:20 +00:00
FoxxMD 642af13f3c fix(ui): fix shiki instance creation 2026-07-10 21:26:51 +00:00
FoxxMD 4146b159bd feat(ui): Component list container max width based on selected grid width 2026-07-10 21:11:13 +00:00
FoxxMD 3a3a97132d fix jellyfin import? 2026-07-10 21:03:06 +00:00
FoxxMD 04a5b91694 feat(ui): Implement grid for desktop layout
Allow user-selected max grid width on non-mobile breakpoints

#500
2026-07-10 20:57:03 +00:00
FoxxMD 945f494a2e fix: top-level type import syntax
see eslint.config.js comments
2026-07-10 16:47:38 +00:00
FoxxMD 4b09909b33 update cacheable for per-operation ttl 2026-07-10 15:17:01 +00:00
FoxxMD a5a1365b72 chore: remove tsx from mocha extension 2026-07-10 15:16:42 +00:00
FoxxMD 6bc618c481 always cache mb url so we can log to transform input 2026-07-10 15:16:31 +00:00
FoxxMD 85d1a0e648 chore: fix code styling in musicbrainz transformer 2026-07-10 15:15:57 +00:00
FoxxMD 1ae45c6060 add npm script for generating esbuild bundle metadata 2026-07-10 13:34:15 +00:00
FoxxMD 7daa43a883 tree-shake shiki for chakra usage 2026-07-10 13:34:15 +00:00
FoxxMD f8501b2a2e chore: Remove tsx usage from vscode launch profiles 2026-07-10 13:34:15 +00:00
Matt Foxx 14c35edadf Merge pull request #633 from FoxxMD/node-ts-native
feat: Migrate to native node runtime everywhere
2026-07-09 16:25:27 -04:00
FoxxMD cbb9774a18 chore: remove tsx and usage in npm scripts 2026-07-09 20:24:16 +00:00
FoxxMD 7b1eb7c2c1 disable flaky test 2026-07-09 20:06:13 +00:00
FoxxMD 8c8d1e1673 chore: Configure no-unused-vars 2026-07-09 20:01:08 +00:00
FoxxMD fbdd504453 fxi: Fix linting rules related to functions and error causes 2026-07-09 19:52:13 +00:00
FoxxMD 612a90d7e7 fix: Fix many useless or missed variable assignments 2026-07-09 19:43:11 +00:00
FoxxMD de8ad60db8 allow no-empty-object-type with single extends interfaces 2026-07-09 19:34:24 +00:00
FoxxMD 43fd8ea807 refactor: autofix (eslint) prefer-const prefer-as-const and var rules 2026-07-09 19:33:54 +00:00
FoxxMD ec3a7e2d20 fix: Fix case block declarations 2026-07-09 19:20:26 +00:00
FoxxMD 981a211e56 refactor: remove all unused imports 2026-07-09 19:04:40 +00:00
FoxxMD c42b5cacaa refactor: Move files/imports to implement folder import isolation 2026-07-09 18:31:18 +00:00
FoxxMD 919907f1cb feat: Add eslint rules to enforce folder import boundaries 2026-07-09 18:14:31 +00:00
FoxxMD 9eac9fdfa1 chore: prevent vscode from reporting comments in config json are errors 2026-07-09 18:14:01 +00:00
FoxxMD 86cea49ed4 fix(ci): Use node for stanity run 2026-07-09 13:31:02 +00:00
FoxxMD 8fc892a738 test: Fix db error test using transient db 2026-07-09 13:27:55 +00:00
FoxxMD fd591be3d6 remove tsx from mocha setup 2026-07-09 13:27:39 +00:00
FoxxMD 63b23689a4 remove debug file 2026-07-09 13:27:27 +00:00
FoxxMD 3035d4c5e8 feat(docker): Remove tsx and typescript dependency from build 2026-07-09 12:58:15 +00:00
FoxxMD 7891133002 fix remaining type imports preventing docker container from running with tsx 2026-07-09 12:26:56 +00:00
FoxxMD 66db4d017e more fixes to get all tests passing 2026-07-09 12:17:03 +00:00
FoxxMD 15e55dc6c5 more refactoring to remove server packages from bundled vite build 2026-07-09 04:13:28 +00:00
FoxxMD 239fb5919e fix(ui): Exclude discord.js from vite deps 2026-07-09 02:54:44 +00:00
FoxxMD 7e53553a8e more fixes for file extensions 2026-07-09 02:36:51 +00:00
FoxxMD 4bd0345d1b replace file extensions and more import type fixes 2026-07-09 02:27:41 +00:00
FoxxMD b207109697 add allowImportingTsExtensions compiler option 2026-07-09 02:03:03 +00:00
FoxxMD 77de2a7526 fix remaining erasure issues 2026-07-09 01:59:58 +00:00
FoxxMD 69b19da6aa fix(eslint): ignore no-undef 2026-07-09 01:59:46 +00:00
FoxxMD f2e2d67668 initial work for using erasable types 2026-07-08 21:14:34 +00:00
FoxxMD 9c44ccb663 chore(release): bump version for release 2026-07-08 17:25:23 +00:00
FoxxMD f431f4d5af refactor(ui): Remove non-mobile sidebar and ad header links
* Remove non-mobile sidebar to free up some more screen real estate
* Add quick Links menu to header that hides on mobile breakpoint
2026-07-08 16:49:37 +00:00
FoxxMD be872d5ce1 fix(ui): Fix double mobile menu icon 2026-07-08 16:09:04 +00:00
FoxxMD 0246f7dfa6 fix(now playing): Fix realtime NP state handling
* Revert removing state on "should clear" statuses, causing false positive updates
* Insteat, filter out NP state to api based on "should clear" statuses
2026-07-08 15:43:42 +00:00
FoxxMD b57f325684 feat: Implement error persistence and live updated for components 2026-07-08 15:02:02 +00:00
FoxxMD 153f37e927 standardize props and components used in timeline 2026-07-08 14:01:11 +00:00
FoxxMD de4cb8a5c2 fix(ui): Conditionally render queue timeline items based on timestamp sameness and context 2026-07-08 13:38:18 +00:00
FoxxMD eb133e4f7e fix(ui): Fallback datetime for scrobble timeline item 2026-07-08 13:32:52 +00:00
FoxxMD a3e056e7c5 feat: Implement virtual states to make filtering play state easier
* Convert 'queued' and 'dead queued' play states to queue filters
* Fix relation filter sql generation for count query
2026-07-08 13:25:41 +00:00
FoxxMD 7d8b00ab22 fix(musicbrainz): Fix finding stage inputs in error causes 2026-07-07 20:33:15 +00:00
FoxxMD 8b633dd901 feat(musicbrainz): Store all queries to lifecycle inputs and persist even on errors
Should make MB debugging easier
2026-07-07 20:05:10 +00:00
FoxxMD f055cf683f feat: Add toJSON to custom errors so they serialize correctly automatically 2026-07-07 18:19:05 +00:00
FoxxMD baaa954f31 chore: Add eslint plugin for mocha tests 2026-07-07 17:54:19 +00:00
FoxxMD c727de9c40 refactor(ui): Refactor timeline items to be rendered independently and order by createdAt 2026-07-07 17:42:27 +00:00
FoxxMD e6f4a1dbd2 feat(client): Implement createdAt dates for scrobble actions 2026-07-07 16:37:45 +00:00
FoxxMD 3d6e86f258 feat(ui): Add queue states to timeline 2026-07-07 15:21:29 +00:00
FoxxMD e6130eb5eb feat(ui): Better error/warning indicators for timeline 2026-07-07 14:23:06 +00:00
FoxxMD 97b6042768 feat(ui): Implement basic error summary detection 2026-07-07 03:07:29 +00:00
FoxxMD b3c1ce6d8a feat(ui): Indicate failed scrobble is dead queued 2026-07-07 02:32:35 +00:00
FoxxMD 5daba47ac7 fix(ui): Fix conditional hook rendering 2026-07-06 20:08:18 +00:00
FoxxMD 8d7760df6d chore: Update eslint config to include react hooks rules 2026-07-06 20:07:51 +00:00
FoxxMD 75c95fdaa8 more error boundaries 2026-07-06 18:53:57 +00:00
FoxxMD e874ccc2d3 Add some error boundaries around components 2026-07-06 18:34:32 +00:00
FoxxMD 17e97b3a6a fix(ui): Fix now playing mode for player on client component details 2026-07-06 18:18:58 +00:00
FoxxMD c6389590fe fix api fixture typing for players 2026-07-06 18:05:52 +00:00
FoxxMD 7151d96b59 feat(ui): Implement log copy 2026-07-06 18:03:38 +00:00
FoxxMD b2b2018764 feat(ui): Use custom hook for player queries and fix now-playing forced re-render 2026-07-06 17:07:46 +00:00
FoxxMD 655b97ce1c feat(ui): Implement now playing ui element
* Reuse source player in always-nowplaying mode, reuse api structure
* Add expiration to player data to stop rendering player for non-realtime clients
2026-07-06 16:40:01 +00:00
FoxxMD 5c6edefc56 feat(ui): track virtual play key by uid rather than index
Should prevent pop-in and false update-indicator triggering
2026-07-02 21:32:48 +00:00
FoxxMD bf7fb4f558 feat(storybook): More accurate play list api mocking
* Reusable function for generic behavior
* Append to list and create unique plays on every page request
2026-07-02 21:32:02 +00:00
FoxxMD 32b64ff384 fix update icons not expiring 2026-07-02 20:00:04 +00:00
FoxxMD f6e0f4be4a feat(ui): Use play updatedAt field for determining updated icon 2026-07-02 19:44:19 +00:00
FoxxMD fb66f0ca84 feat(db): update updatedAt fields automatically 2026-07-02 19:43:48 +00:00
FoxxMD c4b1d72f3f feat(ui): Implement plays refresh 2026-07-02 15:55:37 +00:00
FoxxMD fb452eb40b feat(ui): try to constrain date picker size for mobile 2026-07-02 14:37:52 +00:00
FoxxMD d3fd03a78c feat(ui): Wrap all filters 2026-07-02 14:28:57 +00:00
FoxxMD 7b04e76c3b fix(ui): Accessing target/children when no ref, yet 2026-07-02 14:13:54 +00:00
FoxxMD 9f34c45192 fix(ui): Fix n+1 insertion bug on new play and add logging 2026-07-02 14:07:46 +00:00
FoxxMD 7ab0f710ff feat(ui): Better control over ephemeral updates to plays
* Use structuralSharing to add signal that play data was updated from invalidated query
* Make update badge more generic and fix timeout/re-render
* Play update story updates random play
2026-07-02 14:01:28 +00:00
FoxxMD ea1fb54de1 fix(ui): Revert datepicker formatting for now
Causing end date to jump back one day after every change?
2026-07-02 12:29:31 +00:00
FoxxMD 35fd84a1d0 feat(ui): Improve date picker useability
* reduce preset options to make smaller viewports easier to use
* Add icon to mobile users can avoid keyboard popup
* Disable openOnClick to avoid popup annoyance when user is trying to directly type dates
2026-07-02 01:07:20 +00:00
FoxxMD e498db8ad3 feat(ui): Implement flex wrapped hook 2026-07-01 22:44:17 +00:00
FoxxMD 145960ca04 feat(ui): Implement placeholder component state actions 2026-07-01 20:23:16 +00:00
FoxxMD 91ba24e165 feat(ui): Add placeholder primary actions from component state 2026-07-01 19:49:08 +00:00
FoxxMD 66fc7a2b09 feat(ui): Add back button on component details 2026-07-01 18:30:44 +00:00
FoxxMD 97a7c7044c feat(ui): Implement play actions in summary 2026-07-01 17:57:07 +00:00
FoxxMD 53d7de7e8d refactor(ui): Use custom hook for activity fetching 2026-07-01 17:12:55 +00:00
FoxxMD 32da14c08e make entire component header a link to details 2026-07-01 00:33:45 +00:00
FoxxMD c7f370f121 feat(ui): de-emphasize mode badge 2026-07-01 00:31:48 +00:00
FoxxMD 9d105173da refactor(storybook): More re-organizing 2026-06-30 22:47:47 +00:00
FoxxMD 3e7bbab5b0 refactor(storybook): organize storybook file and loglical 2026-06-30 21:03:14 +00:00
FoxxMD e04965eb35 fix(storybook): Fix router wiring for component list stories 2026-06-30 20:41:51 +00:00
FoxxMD f2c5e82665 keep names and sourcemap on build? 2026-06-30 20:28:07 +00:00
FoxxMD 4c41f0976d Add placeholder action buttons to play detail 2026-06-30 20:27:57 +00:00
FoxxMD db333184cc fix(ui): Fix text alignment in timeline titles 2026-06-30 20:27:38 +00:00
FoxxMD a5963a2333 (bad) ignore weird type error on cause unknown 2026-06-25 15:38:38 +00:00
FoxxMD 0fe81d6164 fix(ui): Fix content wrapping in detailed component 2026-06-25 15:13:52 +00:00
FoxxMD 3ad9f5dcac Add api fixture for generating fake errors 2026-06-25 15:13:27 +00:00
FoxxMD d39abe40c6 feat(ui): Hide error stacks at sm and below 2026-06-25 15:12:46 +00:00
FoxxMD 6f8ca72ccf fix(db): Fix accessing undefined lifecycle during retention cleanup 2026-06-24 19:29:35 +00:00
FoxxMD 2ae32ee19f feat(ui): Polish error alert 2026-06-24 19:21:15 +00:00
FoxxMD 2ac89c5db3 fix: Serialize errors at api boundary and fix recursive cause bug in error alert render 2026-06-24 18:27:34 +00:00
FoxxMD b0db1dd8bc feat(ui): Add error boundaries and better error detection for rendering 2026-06-24 17:18:26 +00:00
FoxxMD a603be6e22 fix(ui): Fix total stats count 2026-06-24 16:12:58 +00:00
FoxxMD 7ad23a76de fix(ui): Fix component summary not rendering live updates when no initial players 2026-06-24 15:51:48 +00:00
FoxxMD 4481646153 refactor(ui): Move query functions into factory 2026-06-24 15:51:26 +00:00
FoxxMD 5d2c9b557f refactor(ui): Move component player live add/delete into players container 2026-06-24 15:17:54 +00:00
FoxxMD eb69ae48f1 fix(ui): Ok to ignore empty error when rendering error alert 2026-06-24 14:19:54 +00:00
FoxxMD c7479b2f00 fix(ui): Fix player keys and use of floats 2026-06-24 14:18:46 +00:00
FoxxMD 8ae7a8d338 feat(ui): Implement live play insert 2026-06-24 14:14:17 +00:00
FoxxMD 7573c762c0 fix(db): Include input in created play data since some systems depend on this existing 2026-06-24 14:14:03 +00:00
FoxxMD 6d503a1a15 fix(storybook): Fix duplicate msw workers due to vite hot reload 2026-06-24 14:13:19 +00:00
FoxxMD 94d3ea5da8 fix: Fix typing for play update events 2026-06-24 01:10:05 +00:00
FoxxMD e998006e6c feat(ui): Implement live updates to activity summary and details 2026-06-24 00:52:01 +00:00
FoxxMD 234fc67ab2 refactor(client): Remove unnecessary notifier dependency 2026-06-23 20:05:33 +00:00
FoxxMD 37bb507359 feat(ui): Global sse status indicator 2026-06-23 19:51:14 +00:00
FoxxMD 1237056d59 feat(ui): Implement statuses for clients 2026-06-23 19:50:54 +00:00
FoxxMD 7174702f7b feat(ui): Implement status and state updates for sources 2026-06-23 18:44:19 +00:00
FoxxMD 3b721673c8 refactor(ui): Replace native links with react-router links 2026-06-23 16:10:52 +00:00
FoxxMD b64f0d2dd5 fix(ui): Fix color contrast for log lines 2026-06-23 16:03:20 +00:00
FoxxMD 7738dc004a feat(ui): Animate sleep indicator 2026-06-23 15:54:34 +00:00
FoxxMD 5c5563a917 Fix layout of state badge and status in component details 2026-06-23 15:48:32 +00:00
FoxxMD 7d08939a18 feat(ui): Implement sleeping indicator 2026-06-23 15:38:53 +00:00
FoxxMD fabc50aa62 switch back to directdomupdates for virtuallist 2026-06-23 13:40:42 +00:00
FoxxMD 4c0615d427 finally working infinite query loading 2026-06-23 13:31:16 +00:00
FoxxMD b4dd729150 refactor: Replace hardcoded queryopts
* Convert to json with deepreplacevalue
* hydrate dayjs at api boundary with generic object walker
2026-06-23 12:33:18 +00:00
FoxxMD f336089755 fix(ui): Fix header icon 2026-06-22 20:38:28 +00:00
FoxxMD 4fc6285081 fix: remove import that not longer exists 2026-06-22 20:26:04 +00:00
FoxxMD 0cf3e33fe6 fix artist alignment 2026-06-22 20:24:39 +00:00
FoxxMD ab424aee0d feat(ui): Implement track/artist/album text search 2026-06-22 20:21:54 +00:00
FoxxMD 9fb55d479d feat(ui): Add play count total and per day 2026-06-22 19:14:22 +00:00
FoxxMD c8fb01bbab Improvements to infinite querying 2026-06-22 18:00:00 +00:00
FoxxMD f008c4074d feat(ui): Implement infinite queries 2026-06-22 16:54:47 +00:00
FoxxMD d66922c99a Improve layout for activity summary 2026-06-22 16:16:05 +00:00
FoxxMD c7689199cc fix styling for filters 2026-06-22 15:21:49 +00:00
FoxxMD 9a8b6bfa09 feat(ui): wire up play filters to api 2026-06-22 14:47:44 +00:00
FoxxMD 45c8141137 with card 2026-06-20 02:34:52 +00:00
FoxxMD 4e7b3e9ceb better styling 2026-06-20 02:30:51 +00:00
FoxxMD dfcc9c32a8 partially working layout for filters 2026-06-20 02:17:00 +00:00
FoxxMD a3d891aa2f Add activity list heading 2026-06-19 20:52:46 +00:00
FoxxMD 088ae4a26a default to virtual list 2026-06-19 20:32:16 +00:00
FoxxMD f425b5a906 disable wdyr
causing issues with storybook
2026-06-19 20:11:15 +00:00
FoxxMD a4ddaeff61 install react-compiler 2026-06-19 20:10:55 +00:00
FoxxMD 8932224b57 add why-did-you-render for debugging 2026-06-19 20:00:48 +00:00
FoxxMD ffe2203ba5 implement virtual list with tanstack/virtual 2026-06-19 19:30:08 +00:00
FoxxMD 987819cc7c fix missing key warnings on react nodes 2026-06-19 15:50:39 +00:00
FoxxMD 974a017e5a feat(ui): Implement query-key-factory and activity summary fetchable 2026-06-19 15:39:58 +00:00
FoxxMD 594a59347e refactor activity summary into own component 2026-06-19 14:09:36 +00:00
FoxxMD b0970c6653 chore: standardize component type 2026-06-19 13:47:54 +00:00
FoxxMD d8017dda62 fix: fix typing on api fixture for queues 2026-06-18 20:33:20 +00:00
FoxxMD a8c3e88ec9 fix(ui): Fix progress background when in a container 2026-06-18 20:31:39 +00:00
FoxxMD 7bf31a7fc7 add chevron left button 2026-06-18 20:26:11 +00:00
FoxxMD deb3310e3f fix: Fix preserving play original data until it is persisted 2026-06-18 20:18:15 +00:00
FoxxMD 5450b48eb3 fix(ui): correct query args for showing plays by seen/desc 2026-06-18 20:17:50 +00:00
FoxxMD 70a94cff9b fix(ui): Fix showing error indicator when error is null 2026-06-18 20:17:32 +00:00
FoxxMD ff31259092 feat(ui): Implement fetchable play lists and detailed components 2026-06-18 19:41:02 +00:00
FoxxMD 8b4f1849f6 feat: Implement api endpoints for individual components, plays, and individual plays 2026-06-18 19:40:29 +00:00
FoxxMD 110ffad7d7 docs: Update atproto descriptions 2026-06-18 16:49:35 +00:00
FoxxMD 62a0670b4d Add players to detailed source component 2026-06-18 15:54:00 +00:00
FoxxMD 7a1142fdd5 actions placeholder 2026-06-18 15:20:58 +00:00
FoxxMD 5def3cde99 fix(ui): Fix accordion context 2026-06-18 14:54:27 +00:00
FoxxMD 075fa877ed Move player tooltip to bottom right position 2026-06-18 14:36:22 +00:00
FoxxMD 6ffb46a65a chore: Fix storybook theming, again 2026-06-18 14:12:21 +00:00
FoxxMD ac5f34ea5f fix(ui): Fix width of tooltip popover 2026-06-18 14:09:48 +00:00
FoxxMD 9975f377d0 fix(ui): Fix colors in player buffer tooltip to match system colors with contrast 2026-06-18 14:05:55 +00:00
FoxxMD 76b4606d17 chore: Use system theme for storybook 2026-06-18 14:05:36 +00:00
FoxxMD 1244e0ee3e fix(ui): Use more lenient css selectors for progress bar 2026-06-18 13:53:15 +00:00
FoxxMD c56b16e2e6 fix: Fix auth cache validation and fallback 2026-06-18 13:43:10 +00:00
FoxxMD 831fd420ff feat(ui): Add player buffer tooltip 2026-06-18 13:27:16 +00:00
FoxxMD 2125f92bd8 feat(ui): Implement player buffering real time positiono 2026-06-18 12:42:26 +00:00
FoxxMD 42c9383dda partial buffered player updates working 2026-06-18 03:06:53 +00:00
FoxxMD f5eab006f5 feat(ui): Implement buffering player progress with mui 2026-06-18 02:19:08 +00:00
FoxxMD 5a2c664fe1 fix(atproto): Fix rate limit expiration and display 2026-06-18 00:56:30 +00:00
FoxxMD d7cbb7c9bd fix: Fix missing implementation on test component 2026-06-17 21:22:20 +00:00
FoxxMD 8afe30bfd4 feat(ui): Implement stats, stat change indicator, and iniitial component detail 2026-06-17 21:19:18 +00:00
FoxxMD 24b67fc430 feat(ui): Implement better live stat indicators 2026-06-17 19:59:06 +00:00
FoxxMD d01a00ad13 feat(atproto): Implement rate limit checks 2026-06-17 17:02:19 +00:00
FoxxMD ac250eba39 feat(ui): Implement real state reporting for components 2026-06-17 14:56:47 +00:00
FoxxMD 778a2d6565 feat(ui): add activity indicator to logs panel 2026-06-17 13:57:19 +00:00
FoxxMD 0b9a7b78df feat(ui): Live player add/remove based on sse updates 2026-06-17 13:24:23 +00:00
FoxxMD 6b93e839fa fix(atproto): fix not throwing applogin exception 2026-06-17 01:56:55 +00:00
FoxxMD 7e6040ce7b fix(ui): Fix accessing components before db is ready 2026-06-17 01:46:45 +00:00
FoxxMD d4b2057c41 fix: fix nullish access of dbcomponent during testing 2026-06-17 01:44:26 +00:00
FoxxMD bc04603fb4 fix: closest match needs id to update updatedAt 2026-06-17 01:42:37 +00:00
FoxxMD ba2a23e039 fix: Fix app migrations table name inconsistency 2026-06-17 01:24:06 +00:00
FoxxMD 6bfe203a34 fix(atproto): Fix not throwing on applogin error 2026-06-17 01:08:40 +00:00
FoxxMD 8c6f34ebc7 fix: Guard against undefined synced reason 2026-06-17 01:05:48 +00:00
FoxxMD 18e20123c2 fix(ui): fix z-index on logs panel 2026-06-16 22:07:54 +00:00
FoxxMD dc44f6c921 feat(ui): Implement fetchable logs with filtering 2026-06-16 20:46:51 +00:00
FoxxMD a19fd42a5b feat(ui): Implement float logs 2026-06-16 20:15:17 +00:00
FoxxMD 206fd62f70 feat(ui): Splitter logs 2026-06-16 19:30:12 +00:00
FoxxMD 6123503ee8 feat(ui): Implement logs component 2026-06-16 18:12:54 +00:00
FoxxMD b751c6f5f9 feat(ui): Implement side nav 2026-06-16 15:33:55 +00:00
FoxxMD d1ef09ba0d feat(ui): Implement header 2026-06-15 21:08:29 +00:00
FoxxMD d073642203 feat(ui): Implement live updates with sse 2026-06-15 19:17:50 +00:00
FoxxMD 9bb48fbf13 POC player live update 2026-06-15 17:17:08 +00:00
FoxxMD bf8f1175ce feat(ui): Implement static component list in app entrypoint 2026-06-15 14:43:13 +00:00
FoxxMD 4bfcc40571 poc multi page 2026-06-12 17:57:54 +00:00
FoxxMD 892d035d7a feat(ui): Implement component summary list 2026-06-12 16:03:07 +00:00
FoxxMD d4019e6dea feat(ui): Implement Player and api fixtures 2026-06-11 20:48:10 +00:00
FoxxMD 3ce94f443a feat(ui): Implement component overview and api fixtures 2026-06-11 18:55:11 +00:00
FoxxMD b863a9773a better optimization of activity details 2026-06-11 00:18:46 +00:00
FoxxMD 489d7d12cb improvements to play list and story 2026-06-11 00:18:36 +00:00
FoxxMD 63c7ea7809 fea(ui): Implement tanstack-query for ActivityDetails and mock in storybook 2026-06-10 20:44:51 +00:00
FoxxMD a6a019e9fd activity ui improvements 2026-06-10 19:45:13 +00:00
FoxxMD dbd1c81c90 update timelline component
* make activity optional with fallback loading render
* fix storybook default args with constant
2026-06-10 19:06:13 +00:00
FoxxMD 2b42b40c0a update storybook 2026-06-10 19:05:01 +00:00
FoxxMD 4230674c2c some ui fixes 2026-06-10 17:36:53 +00:00
FoxxMD b0955270ba refactor: Remove indexed prop from PlayMeta
* Remove indexed any prop so meta is stricter (better catching bugs)
* Implement mergeable generic with meta where we do need extra prop
* Fix bad usage of meta now that indexed prop is gone
2026-06-10 01:54:35 +00:00
FoxxMD a194f752bb refactor: Remove lifecycleless types 2026-06-10 01:54:35 +00:00
FoxxMD 4180ae0c6b feat(migrations): Implement play lifecycle location migration 2026-06-10 01:54:35 +00:00
FoxxMD 3227541f34 feat(db): Add unix timestamp to database bakup file and fix test for detecting it 2026-06-10 01:54:35 +00:00
FoxxMD e949da3830 refactor: Move all lifecycle properties to top-level Play
And remove lifecycle from meta
2026-06-10 01:54:35 +00:00
FoxxMD e13d86b1d0 feat: Add more identying data to lifecycle step 2026-06-10 01:54:35 +00:00
FoxxMD f7577c43e0 refactor: Move scrobble to top level of play object 2026-06-10 01:54:35 +00:00
FoxxMD f3531f41ec feat: Add creation timestamps to lifecycle data 2026-06-10 01:54:35 +00:00
FoxxMD d9d37c68fc Implement api fixtures 2026-06-10 01:54:35 +00:00
FoxxMD 3bb887763f refactor: Move original play data to top-level property in play
* Simplifies lifecycle
* Simplifies play generation for testing
2026-06-10 01:54:35 +00:00
FoxxMD c65504ee9f feat(ui): Define initial API contract 2026-06-10 01:54:35 +00:00
FoxxMD 373fdb0dc2 fix(ui): Gaurd against undefined code prop 2026-06-10 01:54:35 +00:00
FoxxMD 825d0da532 fix(ui): Gaurd againast missing duration 2026-06-10 01:54:35 +00:00
FoxxMD 2efdada4c0 fix(musicbrainz): Fix default assignment of weights in stage config
Should not assign a default value so that stage defaults are not overriden in stage config when no explicit config is given.

Fixes #627
2026-06-10 01:41:41 +00:00
Matt Foxx 01afe9006a Merge pull request #626 from FoxxMD/lzImport
fix(listenbrainz endpoint): `import` handling and error responses
2026-06-09 11:48:24 -04:00
FoxxMD da9edb2116 feat(lzendpoint): Improved error handling
* Return error response when no sources found by request, or none configured
* try-catch and return well-formatted error response if source handling fails
2026-06-09 13:42:54 +00:00
FoxxMD d5c886d718 fix(listenbrainz): Handle 'import' listen_type with multiple scrobbles
#625
2026-06-09 13:28:44 +00:00
FoxxMD e99351407e fix(musicbrainz): Fix order of release priority list
Should be reversed since we use index as score, so "first" in the list should have the highest index/score

#624
2026-06-08 15:38:55 +00:00
Matt Foxx d9184fa350 Merge pull request #623 from FoxxMD/mpdArtistAssignment
fix(mpd): Assign empty array if artists not defined
2026-06-07 22:22:38 -04:00
Matt Foxx e4af1e3f18 Merge pull request #619 from FoxxMD/atprotoHistory
feat: Implement historical scrobble querying for atproto clients
2026-06-07 11:51:33 -04:00
FoxxMD 9ca4f3fa62 fix(mpd): Assign empty array if artists not defined
#621
2026-06-07 15:47:43 +00:00
FoxxMD 20e3f22f3f docs(azuracast): Add clearer guidance on station configuration and access 2026-06-05 19:55:14 +00:00
FoxxMD bca9e195c9 feat(scrobblers): Detect sync gap and resolve sync status if filled 2026-06-05 19:10:29 +00:00
FoxxMD 8b1765cb03 fix: Fix missing type in prometheus stats 2026-06-05 15:21:34 +00:00
FoxxMD 0605cf1fd5 feat: Implement compaction prometheus statistics 2026-06-05 15:05:53 +00:00
FoxxMD 07d6487875 feat: Implement database play prometheus stats
* Total plays by component/state
* Total historical plays by client
2026-06-05 14:54:29 +00:00
FoxxMD 6222aa5899 fix(rocksky): Do not persist scrobble payloads as historical plays 2026-06-05 13:33:04 +00:00
FoxxMD 47f61b491e fix: Fix tables used in historical play repository 2026-06-05 13:23:59 +00:00
FoxxMD 42818c51c1 feat(rocksky): Update configuration and document new token 2026-06-04 20:44:24 +00:00
FoxxMD a0fee4184e chore(rocksky): Bump version to get new exports 2026-06-04 20:23:26 +00:00
FoxxMD 470a70f951 feat(rocksky): Implement historical scrobbles 2026-06-04 20:07:08 +00:00
FoxxMD 2d2b3a6d48 feat(rocksky): Implement native sdk 2026-06-04 19:18:14 +00:00
FoxxMD 6f40dd387e chore(atproto): Remove unused @atproto packages 2026-06-04 17:58:12 +00:00
FoxxMD 2445336ec0 refactor(atproto): Remove unused oauth implementation
Oauth will need a larger, ground-up rewrite if/when its eventually done. Remove the old, unusued implementation for now to reduce complexity.
2026-06-04 17:54:41 +00:00
FoxxMD 41d181e02f refactor: Replace @atproto with @atcute for abstract/app api implementation 2026-06-04 17:50:40 +00:00
FoxxMD ff158c99cd refactor: Move some stateless functions out of atproto class 2026-06-04 16:05:58 +00:00
FoxxMD c1cdcd016a refactor: Move more atproto-generic function into abstract api base 2026-06-04 15:37:01 +00:00
FoxxMD fcb9092f8a refactor: Rename bluesky to atproto, classes 2026-06-04 15:14:45 +00:00
FoxxMD ba2bd0cc8a refactor(teal): Refactor atproto api implementation out of tight teal coupling 2026-06-04 15:12:37 +00:00
FoxxMD f224ce6b6a refactor: Rename bluesky to atproto 2026-06-04 14:51:49 +00:00
FoxxMD c74678e9ef feat(scrobbler): Improve historical scrobbler hydration
* Refactor post init behavior into promise so we can fallback to recent sync if full hydration fails
  * This way user can still compare real time scrobble activity without full hydration
* Simplify sync status usage
  * Log reason based on presence of sync error
  * Always try to do full hydration if not synced
* Fix missing logging for hydration error
* Add MS-scrobbled scrobbles to historical database
2026-06-04 13:53:08 +00:00
FoxxMD 1b1770ff29 chore(release): Bump version for release 2026-06-03 19:20:22 +00:00
Matt Foxx 41d3a63c42 Merge pull request #615 from FoxxMD/artistCreditCleanup
fix: Add missing artist credit transforms
2026-06-03 15:07:46 -04:00
FoxxMD 52240aa13b feat(teal): Implement historical scrobble searching for existing scrobbles and auto hydrate, or sync recent, on init 2026-06-03 19:06:26 +00:00
FoxxMD 95d7d276d7 refactor(teal): Generate and use real lexicon definitions for tealfm implementation
Removes hand-rolled interfaces and generates interfaces+validation for official lexicon definitions
2026-06-03 18:14:20 +00:00
FoxxMD 18c520ce2f feat(tealfm): Stream repo CAR to monitor download progress 2026-06-03 16:33:40 +00:00
FoxxMD fb65cb222a fix: fix rebase conflicts 2026-06-03 16:33:33 +00:00
FoxxMD c4f028fd2c feat(tealfm): Implement historical scrobble hydration 2026-06-03 16:33:24 +00:00
FoxxMD b85452f5c6 feat: Implement historical scrobble client abstract class 2026-06-03 16:32:16 +00:00
FoxxMD 6546ee3db2 Add package for atproto repo car handling 2026-06-03 16:29:57 +00:00
FoxxMD f0e798c052 fix: Add missing artist credit transforms
Fixes #613
2026-06-03 13:24:32 +00:00
FoxxMD 44424f2570 fix: more logging and arg fixes after re-generating lock file 2026-06-03 13:24:10 +00:00
FoxxMD 72df520300 chore: re-generate lock file
* Caused by npm/cli#6301
* will make packaging for nix easier NixOS/nixpkgs#519119
2026-06-03 12:54:31 +00:00
FoxxMD 25c783051d fix: Fix argument order for logger labels 2026-06-03 12:50:16 +00:00
FoxxMD 264bc31af6 Merge branch 'storybookRefresh' 2026-06-02 14:11:04 +00:00
FoxxMD e0bdb8fe3c docs: Fix typo in jriver docs 2026-06-02 12:52:09 +00:00
FoxxMD 4d7c93f7dc fix(maloja): Fix source maloja envs
#610
2026-06-01 22:32:15 +00:00
FoxxMD dd54075c52 docs: Fix bad config example for id on aio in 0.14.0 upgrade 2026-06-01 19:37:02 +00:00
FoxxMD ff08b6327d docs: Comment out required upgrade callout, for now
Unnessary information since there is nothing > 0.14.0. May confuse some users. Can uncomment after 0.14.1 or later.
2026-06-01 19:27:09 +00:00
FoxxMD 76392a9668 chore(release): Bump version for release 2026-06-01 18:45:20 +00:00
FoxxMD f47faabf7c docs: Remove example of caching for transform best practices
Example is already in the main cache configuration docs. Consolidate/remove the transform example so there is only one authorative example in the docs.
2026-06-01 17:30:56 +00:00
FoxxMD 120e280fc8 docs: More upgrade guidance for 0.14.0 2026-06-01 15:22:38 +00:00
FoxxMD 6ae80fd881 feat(docs): Update colors for regular important admonition 2026-06-01 14:51:11 +00:00
FoxxMD be70e7074a fix(docs): Fix css for important admonition details important type 2026-06-01 14:35:02 +00:00
Matt Foxx 65841a8c6b Merge pull request #609 from HiiJax/fixMbIdCaps
fix(tealfm): trackMbid -> trackMbId
2026-06-01 08:40:14 -04:00
Jax 82374fc806 fix(tealfm): trackMbid -> trackMbId 2026-05-31 20:02:09 -04:00
FoxxMD 004400d7ea refactor: Replace jsondiffpatch-react with @pierre/diffs
Good ridance.
2026-05-30 20:02:20 +00:00
FoxxMD d54e169a14 fix for transformstep date 2026-05-30 19:05:36 +00:00
FoxxMD 31c35647dd feat(ui): Update artist rendering to use credits 2026-05-30 19:05:36 +00:00
FoxxMD 204ee159d6 fix(ui): Update JSX namespace to fix missing ambient in react 19 2026-05-30 19:05:36 +00:00
FoxxMD 4fe1f8ced9 chore(release): Bump for beta release 2026-05-30 15:12:25 +00:00
Matt Foxx c26e6f77f5 Merge pull request #608 from FoxxMD/nowPlayingGranular
refactor: Now Playing rewrite to handle realtime client updates
2026-05-30 10:59:40 -04:00
FoxxMD e2b59900e5 fix(listenbrainz): Fix artist credit mapping for minimum track data 2026-05-30 14:59:18 +00:00
FoxxMD c9cde11871 fix(tealfm): Missing function call to statusExpiresSoon 2026-05-29 14:45:42 +00:00
FoxxMD ea0c75ccf3 chore: Simplify gitignore pattern for db files 2026-05-29 14:40:57 +00:00
FoxxMD 13a64bd63a refactor(now playing): Improve now playing to generically handle clients that have real-time updates
* Break out player status checks and thresholds into separate functions
* Consolidate all should update functionality into shouldUpdatePlayingNow for easier testing
* Introduce realtime flag for teal/discord and check if clearing is possible/desired
  * Remove equivalent functionality in individual clients
* (tealfm): Correctly set max threshold using track duration
* (tealfm): Skip clearing update if close to/after expiration time
* (tealfm): Increase min threshold to avoid spamming pds updates for pauses/skip track
2026-05-29 14:40:45 +00:00
FoxxMD c3b23271d4 fix(now playing): Fix acessing undefined play when source player state is reported but without any data
598#issuecomment-4566553387
2026-05-28 17:48:05 +00:00
FoxxMD d047c600c2 feat(tealfm): Update lexicon defs with new mbid uri format
teal-fm/teal#102
2026-05-28 16:13:06 +00:00
FoxxMD b6cae4a538 fix(jellyfin): Handle string or object data for album props when parsing to play
#598#issuecomment-4564522030
2026-05-28 15:08:14 +00:00
FoxxMD 1eed48456c feat: Make artist name to credit function more robust
* artistNameToCredit can now handle partial ArtistCredit object
* Re-use artistNameToCredit for mapping in artistNameToCredits
2026-05-28 15:07:19 +00:00
FoxxMD 20834e812e feat: case-insensitive proxy handles spread/rest 2026-05-28 14:44:02 +00:00
FoxxMD d1e5b11598 chore(release): Bump version for release 2026-05-28 13:05:12 +00:00
FoxxMD c7af346daf docs(lastfm): Document 2 week scrobble window
#606
2026-05-28 12:31:28 +00:00
Matt Foxx 3975edeaf3 Merge pull request #603 from HiiJax/tealfmStatus
feat(tealfm): Add now playing support with fm.teal.alpha.actor.status
2026-05-27 20:43:01 -04:00
Jax 119c8d7fd7 fix(tealfm): avoid nested ternary operators in status expiry 2026-05-27 20:30:25 -04:00
Jax 3a96c28b2d fix(tealfm): status clearing, expiry, and state detection per review 2026-05-27 18:04:29 -04:00
Matt Foxx 6905853aa3 Merge pull request #604 from FoxxMD/listenRangeGuard
fix: Guard against old/bad cached listen range data
2026-05-27 15:52:17 -04:00
FoxxMD 3f1a952e44 fix: Guard against old/bad cached listen range data
#601
2026-05-27 19:25:17 +00:00
FoxxMD 405ee4f6d7 fix(scrobbler): Fix try-catch block so queued scrobble is always added, even if existing queued check fails 2026-05-27 19:24:21 +00:00
FoxxMD 34e0eaefd8 fix(scrobbler): Add migrated play to queue
* Utilize existing methods for added queued/dead scrobbled when migrating from cache
  * Using queueScrobble fixes migration not creating queued state
  * Using addDeadLetterScrobble reduces duplicated code in migration
* Drop listenranges to avoid bad cache data issue (potentially)
2026-05-27 19:19:24 +00:00
FoxxMD 6d4b617409 fix: Add missing sort order to failed/scrobbled api calls
#598#issuecomment-4490136917
2026-05-27 18:19:45 +00:00
Matt Foxx 4195bde081 Merge pull request #600 from nabijaczleweli/master
docs: u{p => t}most typo
2026-05-27 08:28:41 -04:00
Jax 925f3b916d feat(tealfm): Add now playing support with fm.teal.alpha.actor.status 2026-05-26 05:20:31 -04:00
наб 770621ba41 docs: u{p => t}most typo 2026-05-22 20:10:45 +02:00
FoxxMD a00e5d0dbc feat(database): Track component last ready and last active at 2026-05-15 17:21:22 +00:00
FoxxMD 0d6d803df3 feat(database): Implement database structures needed for historical plays 2026-05-15 17:03:48 +00:00
FoxxMD 396e4b922e docs: Overhaul kitchensink 2026-05-15 15:14:29 +00:00
FoxxMD 14ecb57210 fix(docs): Fix missing comma in discord config example 2026-05-15 15:14:12 +00:00
FoxxMD 14369836f6 feat(docs): Add more descriptive error logging for config file parsing 2026-05-15 15:13:52 +00:00
FoxxMD 70e5aaaad5 docs: update kitchensink with ids 2026-05-15 13:09:42 +00:00
FoxxMD 366e668c13 fix(docs): Fix json syntax in sample configs 2026-05-14 20:38:12 +00:00
FoxxMD d0e842ede3 docs: Update ENV configs with ID and name 2026-05-14 20:33:34 +00:00
FoxxMD a0c1f71162 docs: Update examples with id 2026-05-14 20:05:29 +00:00
FoxxMD 7ef159448e feat: Improved retention policy
* Add ENVs for compacting
* Default compacting all data types
* Add rention guidance to docs
2026-05-14 19:48:04 +00:00
FoxxMD b4e6ce6e78 chore(release): Bump the version beta 1 release 2026-05-14 17:40:37 +00:00
Matt Foxx abe75869b1 Merge pull request #588 from FoxxMD/database
0.14.0
2026-05-14 13:35:16 -04:00
FoxxMD 38d7a9cfce feat: Improve error type guards and ui rendering
* Use ErrorLike or ErrorIsh for error utilities, rather than requiring object be an Error instance
* Render error summary on dead scrobble page and have copy action include all error data
* serialize errors when sending through api
* fix error assignment for new dead scrobbles
2026-05-14 16:33:30 +00:00
FoxxMD 83a425a3d4 feat(database): Serialize errors with custom data type 2026-05-14 12:58:50 +00:00
FoxxMD 7ae727a8fe feat(cache): Improve scrobble migration
* Granular persistence of successfully migrated scrobbles
* Don't delete cache after processing
* reduce logging noise and improve wording
* update upgrade wording wrt cache migration
2026-05-14 12:54:29 +00:00
FoxxMD fe4b947d26 fix(koito): Use same now playing threshold as listenrbainz 2026-05-14 01:34:26 +00:00
FoxxMD c5cdc465f3 fix(lastfm): Remove scrobble duration when less than 30 seconds to bypass ignored response
Fixes #596
2026-05-14 00:21:04 +00:00
FoxxMD c86819b0b3 feat(database): Implement cached scrobble migration per-component 2026-05-13 21:00:33 +00:00
FoxxMD 5228d4894c refactor: Simplify init process by using abort controller to detect if init is occurring 2026-05-13 19:55:47 +00:00
FoxxMD e6709c442f feat(database): Implement (with patch) migration context
sandrinodimattia/sqlite-up#3
2026-05-13 15:10:48 -04:00
FoxxMD 0a594d5f83 test: Tighter integration with act 2026-05-13 11:41:29 -04:00
FoxxMD 49be95a4d1 fix(database): Add gitkeep for app migrations empty folder 2026-05-13 11:33:40 -04:00
FoxxMD fbf3af8474 feat(database): Implement app migrations 2026-05-13 14:48:57 +00:00
FoxxMD 146916bc6d feat: Enrich artist and albumartist mbids 2026-05-12 19:01:57 +00:00
FoxxMD a410721acf fix(now playing): Don't update now playing if minimum play information is not met 2026-05-12 18:10:41 +00:00
FoxxMD af5aec2ed3 port changes from pgllite branch
Lots of cleaned up methods and better framing if db backend ever becomes async again
2026-05-12 16:28:36 +00:00
FoxxMD 51b7b3fd40 test(database): Add 10k size stats 2026-05-12 14:52:15 +00:00
Matt Foxx c22867a9b1 Merge pull request #594 from philocalyst/database-latest
Updating everything to latest
2026-05-12 10:51:14 -04:00
FoxxMD f93c1fa4db revert: Remove vite optimize for ansi-to-react
Issue was instead solved by 1f199e28ef
2026-05-12 14:46:54 +00:00
FoxxMD 1f199e28ef fix(ui): Correct for bad ansi export 2026-05-12 14:44:11 +00:00
FoxxMD 811615dd06 fix(docs): Remove skipLibCheck and address React 19 namespace issues per package
* Update hast-util-to-jsx-runtime with npm override
* Re-export JSX as namespace for remaining packages with issues

May be addressed by future package updates or upgrading to docusaurus 3.10.x
2026-05-12 14:23:09 +00:00
𝑷𝒉𝒊𝒍𝒐𝒄𝒂𝒍𝒚𝒔𝒕 fc6ec840da vite: include ansi-to-react
for correct ESM module export
2026-05-12 09:35:27 -04:00
𝑷𝒉𝒊𝒍𝒐𝒄𝒂𝒍𝒚𝒔𝒕 eb2159de27 docsite: added skip lib check
to avoid the react 19 moving errors, until these packages update.
2026-05-12 09:34:13 -04:00
𝑷𝒉𝒊𝒍𝒐𝒄𝒂𝒍𝒚𝒔𝒕 d0a490b200 deps: fix the last conflict 2026-05-11 20:49:18 -04:00
𝑷𝒉𝒊𝒍𝒐𝒄𝒂𝒍𝒚𝒔𝒕 53870ac1f2 core: migrated code to new api surface 2026-05-11 20:40:52 -04:00
𝑷𝒉𝒊𝒍𝒐𝒄𝒂𝒍𝒚𝒔𝒕 d2f8717555 client: migrated code to new api surface 2026-05-11 20:40:42 -04:00
𝑷𝒉𝒊𝒍𝒐𝒄𝒂𝒍𝒚𝒔𝒕 756d9b84e1 deps: updated to latest across the board 2026-05-11 20:40:25 -04:00
FoxxMD a1ad250dcc feat: Improve cache testing and cleanup cache serialization
* Remove ListenProgress specific serialization tests and function since its not needed anymore
* postfix tmp dirs with test name hints for easier debugging
* try to prevent filecache from leaving files behind
2026-05-08 17:30:12 +00:00
FoxxMD f36fd16517 chore: Bump with-local-tmp-dir version 2026-05-08 17:28:47 +00:00
FoxxMD 28ade6ac28 fix: More improvements to dead scrobbler handling and rendering
* re-use dead scrobble removal method for clearing all
* temp fix for rendering queue errors in frontend
* properly decrement queue based on dead scrobble queue status
* display total dead on status card
2026-05-08 15:27:12 +00:00
FoxxMD fa1051b29a feat(client): Allow processing any dead scrobble that is not completed successfully 2026-05-08 15:22:45 +00:00
FoxxMD 3cdbfc337e feat: Implement generic play identifier repo method 2026-05-08 15:21:56 +00:00
FoxxMD 80474a96fe feat: Add dead scrobble delay configurable through debug env 2026-05-08 13:45:11 +00:00
FoxxMD 957db35c30 fix(ui): Gaurd against undefined Source in on failed scrobbled page
#588
2026-05-08 13:44:50 +00:00
FoxxMD 90f0bdb5ef fix(client): Fix dead to queued queueName constraint in the wrong part of the query
Was setting all failed states to dead queue when it should have been a where condition ON dead queue name

#588
2026-05-08 13:30:30 +00:00
FoxxMD 640d944b4c test: Add listenbrainz submit listens example requests for reference 2026-05-08 13:28:55 +00:00
FoxxMD c7fce06480 fix(listenbrainz): Maybe fix null artist_name
#588
2026-05-08 03:01:45 +00:00
FoxxMD 8cffd0d068 fix: Fix resolving config globally
#588
2026-05-08 02:57:22 +00:00
FoxxMD b8a9623def fix: Add neotraverse as production dependency 2026-05-07 20:48:23 +00:00
FoxxMD d922f37359 fix(client): Implement artistcredit conversion during cached scrobble migration 2026-05-07 20:40:44 +00:00
FoxxMD dcc53d1656 docs: Fix links and paths 2026-05-07 20:38:21 +00:00
FoxxMD 0cb20b83b4 feat: Add ID and name envs for all sources/clients 2026-05-07 20:08:29 +00:00
FoxxMD e814cf58fd docs: Add upgrade path for 0.14.0 2026-05-07 18:57:08 +00:00
FoxxMD f211a4a4c4 refactor(cache)!: Simplify cache config
* Reduce storage backend to valkey only (or inferred file for auth)
* Remove metadata and scrobble config options
* Simplify cache init with new config
* Add deprecation/warnings for cache ENVs
* Rewrite/simplify cache docs
2026-05-07 16:38:04 +00:00
FoxxMD 4d7162a1a4 docs: Add admonition 'important' type 2026-05-07 14:50:04 +00:00
FoxxMD a5926d71c1 chore: Bump drizzle to lastest beta 2026-05-07 12:47:16 +00:00
FoxxMD d95f323dc0 feat: Enrich listenbrainz and jellyfin source plays with artist mbids 2026-05-07 02:42:53 +00:00
FoxxMD 1b53d22e00 feat: Refactor artists as an object
Refactor from string to artist so we can support adding mbids etc. directly to artists

#425
2026-05-07 01:15:05 +00:00
FoxxMD 75c0230dae refactor(source): Move heartbeat responsibility into source class
* Will allow more flexibility in startup and stopping/silencing component by user (in the future)
* Remove unused tasks
2026-05-06 20:21:26 +00:00
FoxxMD 630edd7b3f feat(client): Make playing now scheduling part of heartbeat 2026-05-06 20:06:31 +00:00
FoxxMD 092cf907a2 refactor(client): Move heartbeat responsibility into client class
Will allow more flexibility in startup and stopping/silencing component by user (in the future)
2026-05-06 19:42:58 +00:00
FoxxMD 562b388e3e feat(now playing): Reduce logging noise
* Refactor should play methods to return result and reason, rather than always logging reason
* Combine reasons when logging to reduce log statements
* Demote "no update" logging to TRACE, promot "update" logging to VERBOSE
2026-05-06 18:16:42 +00:00
FoxxMD f788894494 fix(source): Remove superfluous interval logging 2026-05-06 18:15:35 +00:00
FoxxMD d0379e1bd6 fix: Simplify and fix getting next dead scrobble
* Use same kind of function behavior as scrobble queue to reduce cognitive load
* Pass retries to repo method to actually get queued dead scrobbles
2026-05-06 18:14:55 +00:00
FoxxMD 5eb967f61c test(database): Fix play fixture assignment of updatedAt 2026-05-06 16:12:28 +00:00
FoxxMD 0bf9d5e786 refactor(database): Replace relation select type with simpler built type 2026-05-06 16:04:54 +00:00
FoxxMD 102f9795f8 chore: Bump ts-essentials version 2026-05-06 16:03:50 +00:00
FoxxMD 7a7d7bb975 feat(database): Stub out jobs for later use
Creating the majority of it now, esp with jobId in plays table, will make later migrations/usage easier
2026-05-06 14:42:40 +00:00
FoxxMD 246e9ad7c5 chore(release): Bump version for release 2026-05-06 12:12:52 +00:00
FoxxMD d772c8c8cd test(database): Implement db size stats 2026-05-06 02:56:35 +00:00
FoxxMD 48b6b555c1 feat(source): Replace discovered function with existingPlay function
Normalizes the logic used for detected dupes/existing by using it for both sources and clients
2026-05-06 02:56:35 +00:00
FoxxMD 8be56457e2 remove debugging statement 2026-05-06 02:56:35 +00:00
FoxxMD d9a5aa1ce0 fix: Improved retention behavior
* Use new updatedAt column to determine if plays should be deleted
* Update plays on already discovered to keep them from being delted
* Fix various bugs and missed awaits in retention cleanup function
2026-05-06 02:56:35 +00:00
FoxxMD f6799955a0 fix: Fix parsing duration value from string 2026-05-06 02:56:35 +00:00
FoxxMD 077c3d33b5 fix(database): regenerate migrations with platformId removed 2026-05-06 02:56:35 +00:00
FoxxMD 524afb60a9 refactor: Remove platform-awareness for play discovery and database
multiplatform is still useful for memory players but is not useful for discovery and just complicates code paths.

there is no reason to discover based on platform as the Source is the smallest UoW anyway. If a play should be discovered (or not) it shouldn't matter what platform is was played on, it's all the same Source
2026-05-06 02:56:35 +00:00
FoxxMD 12ba99437c fix: Add discovered plays with platform based on multiplatform source prop 2026-05-06 02:56:35 +00:00
FoxxMD 8882e7ab87 chore: Update to drizzle rc1 2026-05-06 02:56:35 +00:00
FoxxMD 8de382f581 feat(database): Optimize queries for scrobble range ops 2026-05-06 02:56:35 +00:00
FoxxMD 691db9215c remove unused variable 2026-05-06 02:56:35 +00:00
FoxxMD 22c0916544 Fix counts for scrobbles/dead 2026-05-06 02:56:34 +00:00
FoxxMD cbe996db95 feat(database): Add query params for finding plays by queue 2026-05-06 02:56:34 +00:00
FoxxMD 8fd870b9ef Fix failed queueu updates 2026-05-06 02:56:34 +00:00
FoxxMD 272c3cc8be feat(database): Add migration path for cached queued scrobbles 2026-05-06 02:56:34 +00:00
FoxxMD 54da8b0add remove unused queue package 2026-05-06 02:56:34 +00:00
FoxxMD cfb4131d5d remove scrobbledPlays and prepare queue next query 2026-05-06 02:56:34 +00:00
FoxxMD b8494986d5 chore: Add sqlite browser vscode extension to devcontainer 2026-05-06 02:56:34 +00:00
FoxxMD f9f923041f the implementation continues 2026-05-06 02:56:34 +00:00
FoxxMD 5774be1d22 Implement api response transform to old structure 2026-05-06 02:56:34 +00:00
FoxxMD 4bc460f598 feat(database): Add relational model inferred type 2026-05-06 02:56:34 +00:00
FoxxMD 0f6aa56fe5 add repository api endpoint POC 2026-05-06 02:56:34 +00:00
FoxxMD 439f0e05aa fixes based on testing for client db impl 2026-05-06 02:56:34 +00:00
FoxxMD 952ddbdcc3 more client db impl 2026-05-06 02:56:34 +00:00
FoxxMD 765b13f910 test: Comment out scrobble caching tests
Not used any more with db
2026-05-06 02:56:34 +00:00
FoxxMD 47caf8de8c more scrobble db implementation 2026-05-06 02:56:34 +00:00
FoxxMD 3c3faf22fc more implementation for clients 2026-05-06 02:56:34 +00:00
FoxxMD f94538ea1f first pass on client database implementation
only refactored queue, need to do dead letter
2026-05-06 02:56:34 +00:00
FoxxMD e9b2990e15 partial scrobble queue database implementation 2026-05-06 02:56:34 +00:00
FoxxMD 3ef48547a7 feat(database): Add id awareness to plays and implement basic api GET for sources 2026-05-06 02:56:34 +00:00
FoxxMD 047de49ee6 feat(database): Initial Souce database usage implementation 2026-05-06 02:56:34 +00:00
FoxxMD 2a52d1e050 fix(spotify): Fix device id default should be only device constant, not platform 2026-05-06 02:56:34 +00:00
FoxxMD 8f049a089b feat(database): Implement discovered/scrobble count tracking 2026-05-06 02:56:34 +00:00
FoxxMD adf6b9ed58 feat(database): Implement basic repository 2026-05-06 02:56:34 +00:00
FoxxMD 59d1050b2b feat(database): Implement retention cleanup scheduled task 2026-05-06 02:56:34 +00:00
FoxxMD 561c62b426 fix(database): Unique constraint on uid + mode + type, not just uid 2026-05-06 02:56:34 +00:00
FoxxMD f38f10d2a0 feat(database): Implement retention cleanup 2026-05-06 02:56:34 +00:00
FoxxMD 7aa5595cc9 feat(database): Implement component hydration as component init stage 2026-05-06 02:56:34 +00:00
FoxxMD 46389fd81d feat(database): Add compacting config 2026-05-06 02:56:34 +00:00
FoxxMD c0384317b1 feat(database): Implement retention options and configuration parsing 2026-05-06 02:56:34 +00:00
FoxxMD 6e66cb06a9 refactor: Remove duplicated regex functions 2026-05-06 02:56:34 +00:00
FoxxMD cb1ff7d030 feat(database): Init and migrate database on startup 2026-05-06 02:56:34 +00:00
FoxxMD 3b5468499a feat(database): Use asyncstorage for query logging 2026-05-06 02:56:34 +00:00
FoxxMD c7361a8976 feat(database): Add full backup-and-migrate logic 2026-05-06 02:56:34 +00:00
FoxxMD 60a6ddbc41 feat(database): Implement drizzle logger 2026-05-06 02:56:34 +00:00
FoxxMD 92e027bfc4 feat(database): Implement finding purgable play ids 2026-05-06 02:56:34 +00:00
FoxxMD b388ca6106 feat(database): Cascade parent id to null on play deletes 2026-05-06 02:56:34 +00:00
FoxxMD 84b43d7c39 feat(database): Implement play deletes
* Implement repository method
* Test that deletes cascade to relations
2026-05-06 02:56:34 +00:00
FoxxMD 99ebb53fc6 chore: Remove unusued patch for kysely 2026-05-06 02:56:34 +00:00
FoxxMD cda33ba04f test(database): Add component query test 2026-05-06 02:56:34 +00:00
FoxxMD dda73cd96a feat(database): Implement query plays by date range 2026-05-06 02:56:34 +00:00
FoxxMD 5771fbf496 feat(database): Implement repository querying 2026-05-06 02:56:34 +00:00
FoxxMD e9d1c81380 chore: Add ts pretty errors to devcontainer extensions 2026-05-06 02:56:34 +00:00
FoxxMD d59cb2668d feat(database): Add basic repository and transaction support 2026-05-06 02:56:34 +00:00
FoxxMD 3c51149d9a feat(database): More database implementation
* add components table
* improve typing for json columns
* fix non-nullable columns
* implement basic entity generation functions
2026-05-06 02:56:34 +00:00
FoxxMD 40234e64b3 refactor(database): Use integer for primary key and make string id as vanity id 2026-05-06 02:56:34 +00:00
FoxxMD 2df8bfe6aa test(database): More migration tests 2026-05-06 02:56:34 +00:00
FoxxMD fec39b2e2e test(database): Craete pending migration and no-db test cases 2026-05-06 02:56:34 +00:00
FoxxMD 83f501df0d add FK For parent play 2026-05-06 02:56:34 +00:00
FoxxMD c40add7d70 feat(database): Initial database POC implementation
Using drizzle
2026-05-06 02:56:34 +00:00
FoxxMD 569c5a8690 fix(spotify): Fix stale state updated at timestamp 2026-05-06 02:56:21 +00:00
FoxxMD 11f7299c0e fix(transform): Fix using cache to return transformed play
Should be caching transformed data instead of play so that "old" plays aren't returned for repeated tracks
2026-05-06 02:40:58 +00:00
Matt Foxx 8a6ae698a1 Merge pull request #587 from FoxxMD/lastfmConnectTimeout
fix(lastfm): Increase connection test timeout for slow networks
2026-05-05 17:49:39 -04:00
FoxxMD d6ed03028d fix(lastfm): Increase connection test timeout for slow networks 2026-05-05 20:59:05 +00:00
FoxxMD 2818d1d275 chore: add lib folder and future db files to gitignore 2026-05-05 16:54:33 +00:00
FoxxMD 8127e4b6ce fix: Use correct gh pages link for replacement 2026-05-05 14:44:00 +00:00
FoxxMD 8e829a53b5 ci: Replace canonical bash command with js script that is more readable and maintainable 2026-05-05 14:39:09 +00:00
FoxxMD 1562f8bffc chore: Fix contributing link in PR template 2026-05-05 12:19:57 +00:00
FoxxMD e04514f6a1 chore(release): Bump version for release 2026-05-05 11:46:16 +00:00
FoxxMD 00e9755fb1 chore: Override rate-limit-threshold to use new version with reduced dependencies
Borewit/rate-limit-threshold#110
2026-05-05 11:46:16 +00:00
Matt Foxx 5ba4729f83 Merge pull request #585 from FoxxMD/webscrobblerPathFix
fix(webscrobbler): Convert express wildcard path to regex
2026-05-04 16:54:13 -04:00
FoxxMD e5df4f2752 docs: Remove extra dupe detection section from client overview
Direct to new dupe guidance page instead
2026-05-04 18:28:08 +00:00
FoxxMD d11db4cb8d docs: Add duplicate scrobble guidance
#582
2026-05-04 18:18:09 +00:00
FoxxMD 14f5f60c11 ci: add canonical search-replace reference comment 2026-05-04 18:15:49 +00:00
FoxxMD 4d7eadbd38 fix(webscrobbler): Convert express wildcard path to regex
New path wildcard from expressjs 5 doesn't seem to work. Use tried and try regex instead

Fixes #583
2026-05-04 14:01:02 +00:00
Matt Foxx 747b62289a Merge pull request #577 from FoxxMD/stalePlayerImprovement
feat: Improve stale player behavior
2026-05-04 09:05:54 -04:00
Matt Foxx 1eeb5e6b08 Merge pull request #580 from FoxxMD/lzEndpointRouteImprovements
Lz endpoint route improvements
2026-05-02 16:24:21 -04:00
FoxxMD ac01348266 docs: try to replace canonical urls for github.io with new site 2026-05-02 04:08:03 +00:00
FoxxMD 3028f83d47 fix(discord): Workaround activity minimum character limit
#578
2026-05-01 18:56:36 +00:00
FoxxMD a02d43acea feat(discord): Implement fallback to release group art if present
#578
2026-05-01 18:43:09 +00:00
FoxxMD c7679a0a62 docs: Fix missing backticks causing template errors on version equality 2026-05-01 16:56:01 +00:00
Gabe Farrell d11ac3d6ed chore: Remove references to KOITO_ALLOWED_HOSTS (#581)
* chore: update and correct references to KOITO_ALLOWED_HOSTS
* remove protocol from host
* make it clear env belongs to koito
2026-05-01 12:47:17 -04:00
Matt Foxx 8a09c91713 Merge pull request #565 from FoxxMD/lastfmCleanup
Lastfm cleanup
2026-05-01 12:39:19 -04:00
FoxxMD 81d24f1753 fix(transformer): try-catch cached steps and fallback to non-cache generation
#578
2026-05-01 15:14:35 +00:00
FoxxMD 5be12aab15 fix(cache): Explicitly define auth/metadata as memory when testing file caching 2026-05-01 15:14:11 +00:00
FoxxMD 47c600ed8d feat(endpointlz): Implement proper username handling for validate and now-playing routes 2026-05-01 14:07:43 +00:00
FoxxMD 5f84fefd5d feat(endpointlz): Add stubbed playing-now route to make some clients happy(er)
#579
2026-05-01 13:11:39 +00:00
FoxxMD a143aa4193 fix(endpointlz): Fix fallback route not returning a response
#579
2026-05-01 13:10:41 +00:00
FoxxMD 23a3dfa5d1 fix: Workaround for old play structure 2026-05-01 00:01:50 +00:00
Matt Foxx 3005d36db0 Merge pull request #575 from FoxxMD/mpdPosition
fix(mpd): Improve position tracking
2026-04-30 18:58:45 -04:00
FoxxMD ada9d70be9 docs(mpd): Add permission guidance 2026-04-30 22:54:57 +00:00
FoxxMD 9d839db2e1 feat(mpd): Rewrite with newer MPD client for better payload parsing #574
mpd-api was missing fields and had inconsistent data.
mpd-js is actually typed and returns better parsed data.
2026-04-30 22:50:09 +00:00
FoxxMD ca20b23fbc fix(now playing): Prefer fresher players
* Don't keep sticky now playing if the player has gone stale/orphaned
* When multiple players are present, prefer those that are not stale/orphaned

#480
2026-04-30 17:39:31 +00:00
FoxxMD 577cff3abf test(player): Fix incorrect variable used for advanced timestamp 2026-04-30 17:20:27 +00:00
FoxxMD c4786bdfe2 fix(player): Initialize player state in the past to prevent false positive on stale update check if update happens at the same time player is initialized 2026-04-30 17:19:48 +00:00
FoxxMD df0da7054c feat(player): Skip stale updates based on state timestamp and prevent dead players from being created from stale updates
#480
2026-04-30 16:39:03 +00:00
FoxxMD 272a336a22 feat(source): Add timestamps to play state to track player and play last updated, if present 2026-04-30 16:38:04 +00:00
Matt Foxx ec4a51074e Merge pull request #570 from FoxxMD/fixListenRange
Fix listen range
2026-04-22 13:27:32 -04:00
FoxxMD 5a4bcb2949 fix(discord): Include known spotify cdn for media providers #569 2026-04-22 13:46:38 +00:00
FoxxMD 77387c292b refactor: Replace fast-deep-equal with fast-equals
fast-deep-equal is abandoned and has bugs that are fixed in fast-equals

Potentially fixes #569
2026-04-22 13:43:14 +00:00
FoxxMD 4caf1c8cdb fix: Fix missing await on stagger mapper initial sleep 2026-04-22 13:40:29 +00:00
FoxxMD 981805aa96 fix: Simplify listenrange usage
* Use and return only plain PlayProgress on player state finish
  * Removes the need to serialize/deserialize ListenProgress class and compare/persist toad scheduler or timeout functions
* Potentially fixes timeout comparison stack error #569
2026-04-22 13:40:00 +00:00
FoxxMD 60e3242c44 fix: Check undefined on correct property
#567
2026-04-19 21:34:42 +00:00
FoxxMD e7e1417b0b ci: Add Cloudflare Pages hook trigger to docs deployment 2026-04-17 18:56:47 +00:00
FoxxMD bf765be174 docs: order BASE_SITE first to allow overriding other envs 2026-04-17 18:30:22 +00:00
FoxxMD e27c301932 docs: Add base site env detection for cloudflare pages 2026-04-17 18:22:45 +00:00
FoxxMD fc4606585d fix(lastfm): Fix total results reporting for librefm 2026-04-17 16:14:30 +00:00
FoxxMD fd80101a7a feat(lastfm): Add more logging to auth process and clean up host detection 2026-04-17 16:05:39 +00:00
FoxxMD 2cbc5542d1 chore(release): Bump version for release 2026-04-17 13:24:45 +00:00
Matt Foxx 6f15f15722 Merge pull request #563 from FoxxMD/middlewareFix
fix: Web endpoint fix
2026-04-17 09:22:25 -04:00
FoxxMD ff1d9248c6 fix: Add missing next() invocation
#561
2026-04-17 12:52:48 +00:00
FoxxMD 218ece8f5c fix(vite): Allow requests from anywhere during dev 2026-04-17 12:49:55 +00:00
FoxxMD 3adac7c6e0 docs: Improve updating guidance 2026-04-16 13:29:34 +00:00
FoxxMD 1c78da7985 chore(release): Bump verion for release 2026-04-16 12:26:39 +00:00
FoxxMD 457279465c ci: Add build date to image build workflow 2026-04-16 02:05:53 +00:00
FoxxMD d551c8563f feat(docker): Add proper opencontainer labels 2026-04-16 02:05:38 +00:00
FoxxMD 75bd4a04d6 fix(transform): Fix uninitialized logger 2026-04-15 20:34:40 +00:00
FoxxMD 339232c368 perf(transform): Defer logger creation until after it is determined a hook will be used 2026-04-15 18:40:21 +00:00
Matt Foxx 8741ff25ea Merge pull request #557 from Druidblack/master
update for Yandex music
2026-04-11 00:04:24 -04:00
Druidblack 8a952cb576 error correction at startup. 2026-04-09 22:32:19 +03:00
Druidblack eeb55aa02e added multiPlatform
added multiPlatform and support for receiving data from Yandex smart speakers
2026-04-09 22:31:18 +03:00
Druidblack 6a81fc1be2 Update ymbridge.json.example 2026-04-09 22:28:32 +03:00
FoxxMD a47e2a6242 docs: Update editUrl 2026-04-09 15:45:29 +00:00
FoxxMD dadaa6df35 ci: reduce dependabot npm updates to security only
Version updates are too big and unhelpful
2026-04-09 14:35:51 +00:00
FoxxMD fb19191209 ci: Update node version for sanity test 2026-04-09 14:08:58 +00:00
Matt Foxx 2f53a60da1 Merge pull request #548 from FoxxMD/express5
chore: Update to expressjs 5
2026-04-09 09:34:29 -04:00
FoxxMD ab2f397fab refactor: update express types related packages 2026-04-09 13:21:50 +00:00
FoxxMD db2dd8fb8b refactor: Refactor wildcard path usage for express5 2026-04-09 13:16:56 +00:00
FoxxMD 135439f509 fix(scrobbler): Fix stagger map generation variable usage 2026-04-09 13:16:33 +00:00
FoxxMD d743b849fe refactor: replace awaitjs/express with express usage 2026-04-09 13:04:47 +00:00
FoxxMD 9885768cad refactor: Install expressjs@5 2026-04-09 12:58:09 +00:00
FoxxMD c6e6bbb87b chore: Override awaitjs/express types to fix peer dependency conflict 2026-04-09 12:53:53 +00:00
FoxxMD bbc2351ebc ci: Fix npm groups to apply only by version/security
Some @types dev packages are tied to prod packages, cant really spearate them.
2026-04-09 12:30:52 +00:00
FoxxMD b547db9045 ci: group gha version updates 2026-04-09 12:25:09 +00:00
Matt Foxx 28c2e9873a Merge pull request #537 from FoxxMD/dependabot/github_actions/actions/download-artifact-8.0.1
chore(deps): bump actions/download-artifact from 4.3.0 to 8.0.1
2026-04-09 08:20:39 -04:00
Matt Foxx 6bcd3f9516 Merge pull request #536 from FoxxMD/dependabot/github_actions/actions/upload-artifact-7.0.0
chore(deps): bump actions/upload-artifact from 4.6.2 to 7.0.0
2026-04-09 08:16:49 -04:00
Matt Foxx 11b2632d02 Merge pull request #538 from FoxxMD/dependabot/github_actions/docker/metadata-action-6.0.0
chore(deps): bump docker/metadata-action from 5.9.0 to 6.0.0
2026-04-08 23:51:10 -04:00
Matt Foxx fb6dbe307d Merge pull request #539 from FoxxMD/dependabot/github_actions/marocchino/sticky-pull-request-comment-3.0.3
chore(deps): bump marocchino/sticky-pull-request-comment from 2.9.4 to 3.0.3
2026-04-08 23:50:13 -04:00
FoxxMD 2474b792c3 ci: try wildcard on github actions 2026-04-09 03:39:12 +00:00
FoxxMD 3007c949a0 ci: Group github action dependabot updates 2026-04-09 03:35:21 +00:00
dependabot[bot] 63533534df chore(deps): bump marocchino/sticky-pull-request-comment
Bumps [marocchino/sticky-pull-request-comment](https://github.com/marocchino/sticky-pull-request-comment) from 2.9.4 to 3.0.3.
- [Release notes](https://github.com/marocchino/sticky-pull-request-comment/releases)
- [Commits](https://github.com/marocchino/sticky-pull-request-comment/compare/773744901bac0e8cbb5a0dc842800d45e9b2b405...d4d6b0936434b21bc8345ad45a440c5f7d2c40ff)

---
updated-dependencies:
- dependency-name: marocchino/sticky-pull-request-comment
  dependency-version: 3.0.3
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-09 03:32:57 +00:00
dependabot[bot] 5c03062976 chore(deps): bump docker/metadata-action from 5.9.0 to 6.0.0
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5.9.0 to 6.0.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/318604b99e75e41977312d83839a89be02ca4893...030e881283bb7a6894de51c315a6bfe6a94e05cf)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-09 03:32:53 +00:00
dependabot[bot] dbe41fa5ab chore(deps): bump actions/download-artifact from 4.3.0 to 8.0.1
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.3.0 to 8.0.1.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/d3f86a106a0bac45b974a628896c90dbdf5c8093...3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: 8.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-09 03:32:49 +00:00
dependabot[bot] 5454f817e5 chore(deps): bump actions/upload-artifact from 4.6.2 to 7.0.0
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.2 to 7.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/ea165f8d65b6e75b540449e92b4886f43607fa02...bbbca2ddaa5d8feaa63e36b76fdaad77386f024f)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-09 03:32:44 +00:00
FoxxMD 8d8f8260b5 ci: Back to one object with multiple directories 2026-04-09 03:31:53 +00:00
FoxxMD 62bb00571f ci: Group dependabot updates 2026-04-09 03:12:32 +00:00
FoxxMD c10537d658 sync package-lock.json 2026-04-09 02:55:47 +00:00
FoxxMD e9ccc319e9 try to get dependabot to use a specific npm version 2026-04-09 02:55:02 +00:00
FoxxMD 54803c6ef9 feat(docker): Make colored console output the default
Can be turned off by setting COLORED_STD=false in any environment
2026-04-09 02:35:40 +00:00
FoxxMD a3d37a864a chore: Update tsx dependency 2026-04-09 02:34:48 +00:00
Matt Foxx 394570e5a3 Merge pull request #529 from FoxxMD/fontendDevPackages
refactor: Reduce docker image size by moving frontend packages to devDependencies
2026-04-08 22:33:37 -04:00
FoxxMD 2bbfdf424f refactor: further removal of frontend package to devDependencies 2026-04-09 02:20:01 +00:00
FoxxMD 9785ad6ba7 refactor: Remove unused react-dom imports 2026-04-09 02:19:20 +00:00
FoxxMD fe7d77c03d fix(docker): Ignore dist folder 2026-04-09 02:19:00 +00:00
FoxxMD 0e7aa92f5b refactor: Reduce docker image by moving frontend packages to devDependencies
They are bundled by vite/esbuild into /dist folder, shouldn't need to keep them in node_modules
2026-04-09 01:48:08 +00:00
FoxxMD 2ff80283b9 fix: Build vite-express config based on production env to avoid making vite fallback to development (somehow) 2026-04-09 01:27:22 +00:00
FoxxMD 4f34b1a055 feat(docker): use prune-mod to remove unneeded files in image 2026-04-08 20:58:18 +00:00
FoxxMD 4effdd6df5 refactor(docker): Improve image size by removing some icon folders 2026-04-08 20:39:56 +00:00
FoxxMD af3ce9dea7 fix(docker): Fix missing patches/tsconfig for docs on docker build 2026-04-08 20:39:56 +00:00
Matt Foxx f0c5cb524f Merge pull request #527 from FoxxMD/docusaurusSocialCardNew
docs: Generate social cards per page
2026-04-08 15:48:04 -04:00
FoxxMD 46883384b6 docs: Use netlify deploy url if present 2026-04-08 19:46:00 +00:00
FoxxMD 7bc5ab3d0f docs: Specify source for ambiguous components 2026-04-08 19:31:50 +00:00
FoxxMD fd15d15124 dcs: First pass at more descriptive social card text 2026-04-08 19:30:02 +00:00
FoxxMD 14da0795f1 docs: Finalize a working social card strategy
* Helper script to build a static font
* Move all image rendering code into own folder
* Simplify generated text to description OR title
* Use text-wrap balance to make it look prettier
* Add tagline and increase font sizes to fill card dimensions
2026-04-08 19:14:43 +00:00
FoxxMD 3faf0ee7f9 social cards working but need descriptions in each front matter 2026-04-07 15:13:23 +00:00
FoxxMD 78ddb15dcf almost working
svg still doesn't render
2026-04-07 14:14:25 +00:00
FoxxMD 760373c227 docs: Replace instances of foxxmd.github.io 2026-04-07 12:44:41 +00:00
FoxxMD 6bb7b17e97 docs: Update readme with new doc site base url 2026-04-07 12:39:41 +00:00
FoxxMD 231156d452 docs: fix base site env usage 2026-04-07 12:32:31 +00:00
FoxxMD d85628b4c9 docs: Add BASE_SITE env 2026-04-07 12:27:33 +00:00
FoxxMD db0e74aec3 feat(client): Strict ENV types 2026-04-06 20:10:25 +00:00
FoxxMD a918a96d34 feat(sources): Better type enforcement 2026-04-06 20:07:30 +00:00
FoxxMD bb51915085 feat: Increase transform play concurrency
Since MB rate limiting is done by api calient we can safely increase number of plays transformed without risking MB errors from rate limits
2026-04-06 19:12:19 +00:00
Matt Foxx 7c177dfe03 Merge pull request #521 from FoxxMD/processingOverhaul
refactor: Overhaul long-running processes
2026-04-06 15:11:11 -04:00
Matt Foxx cf5e7ca1fe Merge pull request #523 from Druidblack/master
feat: Add Yandex Music source
2026-04-06 14:58:11 -04:00
Druidblack fd43ff86c8 added NO_DEVICE and NO_USER 2026-04-06 21:36:21 +03:00
FoxxMD 194a0e78ff ci: Update checkout and setup-node versions due to node deprecation 2026-04-06 18:28:00 +00:00
FoxxMD 04027e87e7 ci: fix step if statement 2026-04-06 18:04:22 +00:00
FoxxMD a998060bca ci: Add docs repo dispatch 2026-04-06 18:01:10 +00:00
Druidblack 5b16e8857e Removed optional settings 2026-04-06 20:57:36 +03:00
FoxxMD c53ce7c517 docs: Update analytics 2026-04-06 17:03:43 +00:00
FoxxMD 7897dc9503 docs: Add Yandex Music Source docs 2026-04-06 15:04:56 +00:00
Matt Foxx 160ddac3e4 Merge pull request #524 from FoxxMD/docusaursUpdateAndFix
Docusaurs update and fix
2026-04-06 10:35:58 -04:00
FoxxMD 59b4f27294 fix dep changes in wrong file 2026-04-06 14:34:33 +00:00
FoxxMD 21f42bf45b try to trigger netlify 2026-04-06 14:28:08 +00:00
FoxxMD 3e8f170f9a chore(docs): Move markdown link warn into non-deprecated setting 2026-04-06 14:24:27 +00:00
FoxxMD 15fbed09a5 fix(docs): Override lodash to fix assignWith error
https://github.com/lodash/lodash/issues/6167#issuecomment-4172977775
2026-04-06 14:24:10 +00:00
FoxxMD 52b301e729 feat(docs): Update docusaurus to 3.9.2 2026-04-06 14:23:40 +00:00
FoxxMD 0bef197521 feat(yandex): Add env source config 2026-04-06 14:06:05 +00:00
FoxxMD f16750609a feat(yandex): Handle bridge errors as upstream with some response context 2026-04-06 13:58:43 +00:00
FoxxMD c39ef29597 fix: Add missing type to sourcestatusdata 2026-04-06 13:48:49 +00:00
FoxxMD 72639616b7 fix(jellyfin): Fix typo on devicesBlock env assignment #522 2026-04-06 12:53:04 +00:00
Druidblack effe870104 fix time track
fixed the calculation of the track playback if it has ended and the new ones have not started.
2026-04-05 20:56:18 +03:00
Druidblack ee753389f7 Add files via upload 2026-04-05 19:36:13 +03:00
Druidblack 5f4a5cb815 add yandex music 2026-04-05 19:35:00 +03:00
FoxxMD 0607aedf04 feat(musicbrainz): Handle rate limiting within api calls
Instead of using staggerMap with MB transformer settings, handle at api call level for each host
2026-04-02 21:23:26 +00:00
FoxxMD 1f165096b3 feat(client): make scrobble processing abortable 2026-04-02 19:42:59 +00:00
FoxxMD fb6f6a18c8 feat(source): Make discover abortable 2026-04-02 18:41:45 +00:00
FoxxMD 03f8b20704 refactor: Implement abortable source polling
Use abort-controller-x with signals to interrupt polling process

#483
2026-04-02 18:36:20 +00:00
FoxxMD c49cdba718 fix: Initialize plays in correct scope 2026-04-02 01:29:56 +00:00
FoxxMD de67879af5 test: Make loop stop wait time configurable so tests run faster/more stable 2026-04-01 17:10:59 +00:00
FoxxMD 9d0cf8f845 feat: Implement dispose methods for classes with schedulers and long running async tasks 2026-04-01 17:10:25 +00:00
Matt Foxx 8d9fd11c79 Merge pull request #519 from FoxxMD/nodeVersionBump
chore!: Bump node to 24
2026-04-01 11:30:57 -04:00
FoxxMD 745343c589 chore!: Bump node to 24
No breaking changes for MS but local installs will need to update node version
2026-04-01 15:23:40 +00:00
FoxxMD e5935c38da ci: try to force dependabot to use npm 11.12.1 2026-04-01 14:30:44 +00:00
FoxxMD cac13fa3da fix: Align dockefile and devcontainer node/npm versions to fix lockfile mismatch
Mismatch causes npm ci in docker build to error
2026-04-01 13:57:04 +00:00
FoxxMD ca4803713e chore: Update node/npm engine constraints in package.json 2026-04-01 13:16:49 +00:00
FoxxMD e53dd55ad8 chore: Remove unused testing libraries and fix storybook version
* Unusued libraries adding a ton of dependencies for functionality that is not used (yet)
* Need to fix storybook version because 10.3 is unstable
2026-04-01 12:59:08 +00:00
FoxxMD 1f2772353a fix: Fix circular dependency in error utils 2026-04-01 12:59:08 +00:00
Matt Foxx 78b48107a0 Merge pull request #517 from FoxxMD/dependabot/npm_and_yarn/npm_and_yarn-369bcfe21d
chore(deps): bump the npm_and_yarn group across 2 directories with 25 updates
2026-04-01 08:22:07 -04:00
dependabot[bot] bd00055ae5 chore(deps): bump the npm_and_yarn group across 2 directories with 25 updates
Bumps the npm_and_yarn group with 21 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [ajv](https://github.com/ajv-validator/ajv) | `8.17.1` | `8.18.0` |
| [ajv](https://github.com/ajv-validator/ajv) | `6.12.6` | `6.14.0` |
| [formidable](https://github.com/node-formidable/formidable) | `3.5.2` | `3.5.4` |
| [formidable](https://github.com/node-formidable/formidable) | `2.1.2` | `2.1.5` |
| [glob](https://github.com/isaacs/node-glob) | `11.0.0` | `11.1.0` |
| [glob](https://github.com/isaacs/node-glob) | `10.4.5` | `10.5.0` |
| [undici](https://github.com/nodejs/undici) | `6.22.0` | `6.24.1` |
| [undici](https://github.com/nodejs/undici) | `7.10.0` | `7.24.6` |
| [undici](https://github.com/nodejs/undici) | `6.21.3` | `6.24.1` |
| [storybook](https://github.com/storybookjs/storybook/tree/HEAD/code/core) | `10.2.0` | `10.3.3` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `5.4.10` | `5.4.21` |
| [brace-expansion](https://github.com/juliangruber/brace-expansion) | `1.1.11` | `1.1.13` |
| [minimatch](https://github.com/isaacs/minimatch) | `3.1.2` | `3.1.5` |
| [picomatch](https://github.com/micromatch/picomatch) | `2.3.1` | `2.3.2` |
| [axios](https://github.com/axios/axios) | `1.11.0` | `1.14.0` |
| [qs](https://github.com/ljharb/qs) | `6.13.0` | `6.15.0` |
| [yaml](https://github.com/eemeli/yaml) | `1.10.2` | `1.10.3` |
| [flatted](https://github.com/WebReflection/flatted) | `3.3.3` | `3.4.2` |
| [immutable](https://github.com/immutable-js/immutable-js) | `5.1.4` | `5.1.5` |
| [js-yaml](https://github.com/nodeca/js-yaml) | `4.1.0` | `4.1.1` |
| [jws](https://github.com/brianloveswords/node-jws) | `4.0.0` | `4.0.1` |
| [lodash](https://github.com/lodash/lodash) | `4.17.21` | `4.18.0` |
| [on-headers](https://github.com/jshttp/on-headers) | `1.0.2` | `1.1.0` |
| [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) | `6.27.0` | `6.30.3` |
| [rollup](https://github.com/rollup/rollup) | `4.24.3` | `4.60.1` |
| [tmp](https://github.com/raszi/node-tmp) | `0.0.33` | `0.2.5` |

Bumps the npm_and_yarn group with 13 updates in the /docsite directory:

| Package | From | To |
| --- | --- | --- |
| [undici](https://github.com/nodejs/undici) | `6.21.1` | `6.24.1` |
| [brace-expansion](https://github.com/juliangruber/brace-expansion) | `1.1.11` | `1.1.13` |
| [minimatch](https://github.com/isaacs/minimatch) | `3.1.2` | `3.1.5` |
| [picomatch](https://github.com/micromatch/picomatch) | `2.3.1` | `2.3.2` |
| [yaml](https://github.com/eemeli/yaml) | `2.8.2` | `2.8.3` |
| [immutable](https://github.com/immutable-js/immutable-js) | `5.1.4` | `5.1.5` |
| [js-yaml](https://github.com/nodeca/js-yaml) | `3.14.1` | `3.14.2` |
| [lodash](https://github.com/lodash/lodash) | `4.17.21` | `4.18.0` |
| [mdast-util-to-hast](https://github.com/syntax-tree/mdast-util-to-hast) | `13.2.0` | `13.2.1` |
| [on-headers](https://github.com/jshttp/on-headers) | `1.0.2` | `1.1.0` |
| [node-forge](https://github.com/digitalbazaar/forge) | `1.3.1` | `1.4.0` |
| [svgo](https://github.com/svg/svgo) | `3.3.2` | `3.3.3` |
| [webpack](https://github.com/webpack/webpack) | `5.97.1` | `5.105.4` |



Updates `ajv` from 8.17.1 to 8.18.0
- [Release notes](https://github.com/ajv-validator/ajv/releases)
- [Commits](https://github.com/ajv-validator/ajv/compare/v8.17.1...v8.18.0)

Updates `ajv` from 6.12.6 to 6.14.0
- [Release notes](https://github.com/ajv-validator/ajv/releases)
- [Commits](https://github.com/ajv-validator/ajv/compare/v8.17.1...v8.18.0)

Updates `formidable` from 3.5.2 to 3.5.4
- [Release notes](https://github.com/node-formidable/formidable/releases)
- [Changelog](https://github.com/node-formidable/formidable/blob/master/CHANGELOG.md)
- [Commits](https://github.com/node-formidable/formidable/commits)

Updates `formidable` from 2.1.2 to 2.1.5
- [Release notes](https://github.com/node-formidable/formidable/releases)
- [Changelog](https://github.com/node-formidable/formidable/blob/master/CHANGELOG.md)
- [Commits](https://github.com/node-formidable/formidable/commits)

Updates `glob` from 11.0.0 to 11.1.0
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v11.0.0...v11.1.0)

Updates `glob` from 10.4.5 to 10.5.0
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v11.0.0...v11.1.0)

Updates `undici` from 6.22.0 to 6.24.1
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v6.22.0...v6.24.1)

Updates `undici` from 7.10.0 to 7.24.6
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v6.22.0...v6.24.1)

Updates `undici` from 6.21.3 to 6.24.1
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v6.22.0...v6.24.1)

Updates `storybook` from 10.2.0 to 10.3.3
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v10.3.3/code/core)

Updates `vite` from 5.4.10 to 5.4.21
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.21/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.21/packages/vite)

Updates `brace-expansion` from 1.1.11 to 1.1.13
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.13)

Updates `minimatch` from 3.1.2 to 3.1.5
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.5)

Updates `picomatch` from 2.3.1 to 2.3.2
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/2.3.1...2.3.2)

Updates `axios` from 1.11.0 to 1.14.0
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.11.0...v1.14.0)

Updates `qs` from 6.13.0 to 6.15.0
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.13.0...v6.15.0)

Updates `yaml` from 1.10.2 to 1.10.3
- [Release notes](https://github.com/eemeli/yaml/releases)
- [Commits](https://github.com/eemeli/yaml/compare/v1.10.2...v1.10.3)

Updates `flatted` from 3.3.3 to 3.4.2
- [Commits](https://github.com/WebReflection/flatted/compare/v3.3.3...v3.4.2)

Updates `immutable` from 5.1.4 to 5.1.5
- [Release notes](https://github.com/immutable-js/immutable-js/releases)
- [Changelog](https://github.com/immutable-js/immutable-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/immutable-js/immutable-js/compare/v5.1.4...v5.1.5)

Updates `js-yaml` from 4.1.0 to 4.1.1
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

Updates `jws` from 4.0.0 to 4.0.1
- [Release notes](https://github.com/brianloveswords/node-jws/releases)
- [Changelog](https://github.com/auth0/node-jws/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianloveswords/node-jws/compare/v4.0.0...v4.0.1)

Updates `lodash` from 4.17.21 to 4.18.0
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.18.0)

Updates `on-headers` from 1.0.2 to 1.1.0
- [Release notes](https://github.com/jshttp/on-headers/releases)
- [Changelog](https://github.com/jshttp/on-headers/blob/master/HISTORY.md)
- [Commits](https://github.com/jshttp/on-headers/compare/v1.0.2...v1.1.0)

Updates `react-router` from 6.27.0 to 6.30.3
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/react-router@6.30.3/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@6.30.3/packages/react-router)

Updates `rollup` from 4.24.3 to 4.60.1
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.24.3...v4.60.1)

Updates `tmp` from 0.0.33 to 0.2.5
- [Changelog](https://github.com/raszi/node-tmp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/raszi/node-tmp/compare/v0.0.33...v0.2.5)

Updates `undici` from 6.21.1 to 6.24.1
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v6.22.0...v6.24.1)

Updates `brace-expansion` from 1.1.11 to 1.1.13
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.13)

Updates `minimatch` from 3.1.2 to 3.1.5
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.5)

Updates `picomatch` from 2.3.1 to 2.3.2
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/2.3.1...2.3.2)

Updates `yaml` from 2.8.2 to 2.8.3
- [Release notes](https://github.com/eemeli/yaml/releases)
- [Commits](https://github.com/eemeli/yaml/compare/v1.10.2...v1.10.3)

Updates `immutable` from 5.1.4 to 5.1.5
- [Release notes](https://github.com/immutable-js/immutable-js/releases)
- [Changelog](https://github.com/immutable-js/immutable-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/immutable-js/immutable-js/compare/v5.1.4...v5.1.5)

Updates `js-yaml` from 3.14.1 to 3.14.2
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

Updates `lodash` from 4.17.21 to 4.18.0
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.18.0)

Updates `mdast-util-to-hast` from 13.2.0 to 13.2.1
- [Release notes](https://github.com/syntax-tree/mdast-util-to-hast/releases)
- [Commits](https://github.com/syntax-tree/mdast-util-to-hast/compare/13.2.0...13.2.1)

Updates `on-headers` from 1.0.2 to 1.1.0
- [Release notes](https://github.com/jshttp/on-headers/releases)
- [Changelog](https://github.com/jshttp/on-headers/blob/master/HISTORY.md)
- [Commits](https://github.com/jshttp/on-headers/compare/v1.0.2...v1.1.0)

Updates `node-forge` from 1.3.1 to 1.4.0
- [Changelog](https://github.com/digitalbazaar/forge/blob/main/CHANGELOG.md)
- [Commits](https://github.com/digitalbazaar/forge/compare/v1.3.1...v1.4.0)

Updates `svgo` from 3.3.2 to 3.3.3
- [Release notes](https://github.com/svg/svgo/releases)
- [Commits](https://github.com/svg/svgo/compare/v3.3.2...v3.3.3)

Updates `webpack` from 5.97.1 to 5.105.4
- [Release notes](https://github.com/webpack/webpack/releases)
- [Changelog](https://github.com/webpack/webpack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack/compare/v5.97.1...v5.105.4)

---
updated-dependencies:
- dependency-name: ajv
  dependency-version: 8.18.0
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: ajv
  dependency-version: 6.14.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: formidable
  dependency-version: 3.5.4
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: formidable
  dependency-version: 2.1.5
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: glob
  dependency-version: 11.1.0
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: glob
  dependency-version: 10.5.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: undici
  dependency-version: 6.24.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: undici
  dependency-version: 7.24.6
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: undici
  dependency-version: 6.24.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: storybook
  dependency-version: 10.3.3
  dependency-type: direct:development
  dependency-group: npm_and_yarn
- dependency-name: vite
  dependency-version: 5.4.21
  dependency-type: direct:development
  dependency-group: npm_and_yarn
- dependency-name: brace-expansion
  dependency-version: 1.1.13
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: minimatch
  dependency-version: 3.1.5
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: picomatch
  dependency-version: 2.3.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: axios
  dependency-version: 1.14.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: qs
  dependency-version: 6.15.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: yaml
  dependency-version: 1.10.3
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: flatted
  dependency-version: 3.4.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: immutable
  dependency-version: 5.1.5
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: jws
  dependency-version: 4.0.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: lodash
  dependency-version: 4.18.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: on-headers
  dependency-version: 1.1.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: react-router
  dependency-version: 6.30.3
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: rollup
  dependency-version: 4.60.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: tmp
  dependency-version: 0.2.5
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: undici
  dependency-version: 6.24.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: brace-expansion
  dependency-version: 1.1.13
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: minimatch
  dependency-version: 3.1.5
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: picomatch
  dependency-version: 2.3.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: yaml
  dependency-version: 2.8.3
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: immutable
  dependency-version: 5.1.5
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: js-yaml
  dependency-version: 3.14.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: lodash
  dependency-version: 4.18.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: mdast-util-to-hast
  dependency-version: 13.2.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: on-headers
  dependency-version: 1.1.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: node-forge
  dependency-version: 1.4.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: svgo
  dependency-version: 3.3.3
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: webpack
  dependency-version: 5.105.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-31 20:31:04 +00:00
FoxxMD b174b23b3b fix: re-install missing dependencies introduced by dependabot 2026-03-31 20:07:34 +00:00
Matt Foxx 9e6319ee73 Configure Dependabot for npm and docker updates
Updated package ecosystems for Dependabot to include npm and docker.
2026-03-31 15:59:51 -04:00
Matt Foxx 6b5b3990cd Merge pull request #511 from FoxxMD/dependabot/npm_and_yarn/npm_and_yarn-6582722f31
chore(deps): bump the npm_and_yarn group across 2 directories with 2 updates
2026-03-31 15:53:42 -04:00
dependabot[bot] f2465f0f88 chore(deps): bump the npm_and_yarn group across 2 directories with 2 updates
Bumps the npm_and_yarn group with 1 update in the / directory: [express](https://github.com/expressjs/express).
Bumps the npm_and_yarn group with 2 updates in the /docsite directory: [express](https://github.com/expressjs/express) and [path-to-regexp](https://github.com/pillarjs/path-to-regexp).


Updates `express` from 4.21.1 to 4.22.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/v4.22.1/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.21.1...v4.22.1)

Updates `path-to-regexp` from 0.1.10 to 0.1.13
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/v.0.1.13/History.md)
- [Commits](https://github.com/pillarjs/path-to-regexp/compare/v0.1.12...v.0.1.13)

Updates `express` from 4.21.1 to 4.22.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/v4.22.1/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.21.1...v4.22.1)

Updates `path-to-regexp` from 0.1.10 to 0.1.13
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/v.0.1.13/History.md)
- [Commits](https://github.com/pillarjs/path-to-regexp/compare/v0.1.12...v.0.1.13)

Updates `express` from 4.21.2 to 4.22.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/v4.22.1/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.21.1...v4.22.1)

Updates `path-to-regexp` from 0.1.12 to 0.1.13
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/v.0.1.13/History.md)
- [Commits](https://github.com/pillarjs/path-to-regexp/compare/v0.1.12...v.0.1.13)

Updates `express` from 4.21.2 to 4.22.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/v4.22.1/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.21.1...v4.22.1)

Updates `path-to-regexp` from 0.1.12 to 0.1.13
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/v.0.1.13/History.md)
- [Commits](https://github.com/pillarjs/path-to-regexp/compare/v0.1.12...v.0.1.13)

---
updated-dependencies:
- dependency-name: express
  dependency-version: 4.22.1
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: path-to-regexp
  dependency-version: 0.1.13
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: express
  dependency-version: 4.22.1
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: path-to-regexp
  dependency-version: 0.1.13
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: express
  dependency-version: 4.22.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: path-to-regexp
  dependency-version: 0.1.13
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: express
  dependency-version: 4.22.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: path-to-regexp
  dependency-version: 0.1.13
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-31 19:50:21 +00:00
FoxxMD c68ec8dbf6 chore: Remove unused nyc dependency 2026-03-31 19:46:03 +00:00
687 changed files with 63768 additions and 25595 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ services:
# context: .
# dockerfile: .devcontainer/Dockerfile
image: mcr.microsoft.com/devcontainers/javascript-node:1-20-bookworm
image: mcr.microsoft.com/devcontainers/javascript-node:24-bookworm
volumes:
# Update this to wherever you want VS Code to mount the folder of your project
+4 -2
View File
@@ -20,7 +20,7 @@
"forwardPorts": [9078,3000],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "npm install",
"postCreateCommand": ".devcontainer/postCreate.sh",
// Configure tool-specific properties.
"customizations": {
@@ -30,7 +30,9 @@
"dbaeumer.vscode-eslint",
"unifiedjs.vscode-mdx",
"bradlc.vscode-tailwindcss",
"TakumiI.markdowntable"
"TakumiI.markdowntable",
"YoavBls.pretty-ts-errors",
"bowlerr.sqlite-intelliview-vscode"
]
}
},
+12
View File
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
echo 'Adding node_modules to path'
echo 'export PATH=/workspaces/node_modules/.bin:/workspaces/docsite/node_modules/.bin:$PATH' >> /home/node/.bashrc
echo 'Updating npm...'
npm install -g npm@11.12.1
echo 'Installing concurrently...'
npm install -g concurrently
echo 'If you have freshly cloned this project or node_modules folder does not exist then you should run "npm run install:parallel" now'
+3 -1
View File
@@ -13,6 +13,7 @@ config/*.json
config/*.bak
config/*.p8
/docs
dist
/logs
.flatpak-builder
**/generated-sources.*
@@ -30,4 +31,5 @@ tmp-*
.env
*.env
.storybook
.stories
.stories
**/lexicons/**/*.json
+37
View File
@@ -0,0 +1,37 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "npm" # See documentation for possible values
groups:
sec-updates:
applies-to: "security-updates"
patterns:
- "*"
directories:
- "/"
- "/docsite"
schedule:
interval: "weekly"
- package-ecosystem: "docker"
# Look for a `Dockerfile` in the `root` directory
directory: "/"
# Check for updates once a week
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
groups:
sec-updates:
applies-to: "security-updates"
patterns:
- "*"
version-updates:
applies-to: "version-updates"
patterns:
- "*"
schedule:
interval: "weekly"
+1 -1
View File
@@ -1,6 +1,6 @@
## Checklist before requesting a review
- [ ] I have read the [contributing guidelines.](../CONTRIBUTING.md)
- [ ] I have read the [contributing guidelines.](../blob/master/CONTRIBUTING.md)
## Type of change
+30 -7
View File
@@ -12,12 +12,12 @@ jobs:
name: Deploy to GitHub Pages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 #v4.3.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
with:
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 #4.4.0
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f #6.3.0
with:
node-version: 20
node-version: 24
cache: npm
- name: Install app dev dependencies
@@ -28,22 +28,24 @@ jobs:
- name: Install docusaurus dependencies
run: npm ci
working-directory: ./docsite
- name: Build website
env:
ANALYTICS: ${{ vars.ANALYTICS }}
ANALYTICS_DOMAIN: ${{ vars.ANALYTICS_DOMAIN }}
RY_ANALYTICS: ${{ vars.RY_ANALYTICS }}
RY_ANALYTICS_SITEID: ${{ vars.RY_ANALYTICS_SITEID }}
RY_ANALYTICS_REPLAY: ${{ vars.RY_ANALYTICS_REPLAY }}
GSITEVERIFICATION: ${{ vars.GSITEVERIFICATION }}
DOCS_BASE: '/multi-scrobbler'
run: npm run build
working-directory: ./docsite
- name: Replace canonical
working-directory: ./docsite
run: node --experimental-strip-types canonical-replace.ts ./build
# Popular action to deploy to GitHub Pages:
# Docs: https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-docusaurus
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@373f7f263a76c20808c831209c920827a82a2847 #3.9.3
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e #4.0.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
# Build output to publish to the `gh-pages` branch:
@@ -55,3 +57,24 @@ jobs:
# You can swap them out with your own user credentials.
user_name: github-actions[bot]
user_email: 41898282+github-actions[bot]@users.noreply.github.com
- name: Trigger dependent build
if: ${{ vars.DOCS_REPO != ''}}
run: |
# Trigger builds in dependent repositories
curl -X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.DOCS_DISPATCH_TOKEN }}" \
"https://api.github.com/repos/${{ vars.DOCS_REPO }}/dispatches" \
-d "{
\"event_type\": \"release\",
\"client_payload\": {
\"commit\": \"${{ github.sha }}\"
}
}"
- name: Trigger CF Pages build
if: ${{ vars.CF_PAGES == 'true' }}
run: |
# trigger CF pages hook deployment
curl -d "" \
"${{ secrets.CF_PAGES_HOOK }}"
+8 -5
View File
@@ -51,7 +51,9 @@ jobs:
strings=("${VARS_DOCKERHUB_SLUG}" "${VARS_GHCR_SLUG}")
for i in ${!strings[@]}; do [[ -z ${strings[i]} ]] && unset strings[i]; done
joined_string=$(IFS=, ; echo "${strings[*]}")
echo "REGISTRIES_JOINED=$joined_string" >> $GITHUB_ENV
echo "REGISTRIES_JOINED=$joined_string" >> $GITHUB_ENV
date=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
echo "BUILD_DATE=$date" >> $GITHUB_ENV
env:
VARS_DOCKERHUB_SLUG: ${{ vars.DOCKERHUB_SLUG }}
VARS_GHCR_SLUG: ${{vars.GHCR_SLUG}}
@@ -104,6 +106,7 @@ jobs:
context: ${{github.event.pull_request.head.repo.clone_url}}#${{github.event.pull_request.head.sha}}
build-args: |
APP_BUILD_VERSION=${{steps.appversion.outputs.appversion}}
BUILD_DATE=${{ env.BUILD_DATE }}
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,"name=${{ env.REGISTRIES_JOINED }}",push-by-digest=true,name-canonical=true,push=true
@@ -117,7 +120,7 @@ jobs:
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 #4.6.2
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f #7.0.0
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
@@ -136,7 +139,7 @@ jobs:
- test
steps:
- name: Download digests
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 #4.3.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #8.0.1
with:
path: /tmp/digests
pattern: digests-*
@@ -162,7 +165,7 @@ jobs:
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 #5.9.0
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf #6.0.0
with:
images: |
${{ vars.DOCKERHUB_SLUG }}
@@ -220,7 +223,7 @@ jobs:
if: contains(github.event.pull_request.labels.*.name, 'safe to test')
steps:
- name: Create comment
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 #2.9.4
uses: marocchino/sticky-pull-request-comment@d4d6b0936434b21bc8345ad45a440c5f7d2c40ff #3.0.3
with:
recreate: true
header: "pr-release"
+7 -4
View File
@@ -66,13 +66,15 @@ jobs:
for i in ${!strings[@]}; do [[ -z ${strings[i]} ]] && unset strings[i]; done
joined_string=$(IFS=, ; echo "${strings[*]}")
echo "REGISTRIES_JOINED=$joined_string" >> $GITHUB_ENV
date=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
echo "BUILD_DATE=$date" >> $GITHUB_ENV
env:
VARS_DOCKERHUB_SLUG: ${{ vars.DOCKERHUB_SLUG }}
VARS_GHCR_SLUG: ${{vars.GHCR_SLUG}}
- name: Check out the repo
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 #v4.3.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
with:
persist-credentials: false
@@ -126,6 +128,7 @@ jobs:
with:
build-args: |
APP_BUILD_VERSION=${{steps.appversion.outputs.appversion}}
BUILD_DATE=${{ env.BUILD_DATE }}
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,"name=${{ env.REGISTRIES_JOINED }}",push-by-digest=true,name-canonical=true,push=true
@@ -140,7 +143,7 @@ jobs:
env:
VARS_DIGEST: ${{ steps.build.outputs.digest }}
- name: Upload digest
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 #4.6.2
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f #7.0.0
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
@@ -159,7 +162,7 @@ jobs:
- test
steps:
- name: Download digests
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 #4.3.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #8.0.1
with:
path: /tmp/digests
pattern: digests-*
@@ -185,7 +188,7 @@ jobs:
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 #5.9.0
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf #6.0.0
with:
images: |
${{ vars.DOCKERHUB_SLUG }}
+10 -5
View File
@@ -6,7 +6,7 @@ on:
node-version:
description: "Node version"
required: false
default: '20.x'
default: '24.x'
type: string
ref:
description: "Ref to checkout"
@@ -18,13 +18,18 @@ jobs:
name: Tests / Build / Sanity Run
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 #v4.3.0
- name: Check out the repo (gha)
if: ${{ !env.ACT }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
with:
ref: ${{ inputs.ref }}
persist-credentials: false
- name: Check out the repo (act)
if: ${{ env.ACT }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
- name: Use Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 #4.4.0
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f #6.3.0
with:
node-version: ${{ inputs.node-version }}
cache: 'npm'
@@ -55,7 +60,7 @@ jobs:
run: |
set +e
export NODE_ENV=production
timeout --preserve-status 10s node node_modules/.bin/tsx src/backend/index.ts
timeout --preserve-status 10s node src/backend/index.ts
exitcode="$?"
if [[ "$exitcode" -eq 143 ]] || [[ "$exitcode" -eq 137 ]]; then
echo "App stayed up long enough and exited with expected status"
+9
View File
@@ -151,3 +151,12 @@ tmp-*
*storybook.log
storybook-static
*.db
*.db*
**/lexicons/**/*.json
bun.lock
.DS_Store
**/.DS_Store
-1
View File
@@ -1,7 +1,6 @@
{
"reporter": "dot",
"extension": "ts",
"import": "tsx/esm",
"spec": "./src/backend/tests/**/*.test.ts",
"file": [
"./src/backend/tests/setup.ts"
+1 -1
View File
@@ -1 +1 @@
v18.19.1
v24.14.0
-24
View File
@@ -1,24 +0,0 @@
{
"extends": "@istanbuljs/nyc-config-typescript",
"exclude": [
"node_modules/",
"**/src/backend/common/schema/**",
"**/src/backend/tests/**",
"register.js",
"**/src/server/**/*.d.ts",
"**/src/client/**"
],
"include": [
"**/src/backend/**/*.ts",
"**/src/**/backend/*.js",
"**/src/**/backend/*.js.map"
],
"extension": [
".ts"
],
"reporter": [
"text-summary",
"html"
],
"report-dir": "./coverage"
}
+4 -2
View File
@@ -9,15 +9,17 @@ export default defineMain({
],
"addons": [
"@chromatic-com/storybook",
"@storybook/addon-vitest",
// "@storybook/addon-vitest",
"@storybook/addon-a11y",
"@storybook/addon-docs",
"@storybook/addon-onboarding"
'storybook-addon-remix-react-router'
// "@storybook/addon-onboarding"
],
features: {
// https://github.com/storybookjs/storybook/discussions/33279
sidebarOnboardingChecklist: false,
},
staticDirs: ['../public'],
"framework": "@storybook/react-vite",
viteFinal: async (config) => {
return mergeConfig(config, {
+1 -1
View File
@@ -2,5 +2,5 @@ import { addons } from 'storybook/manager-api';
import { themes } from 'storybook/theming';
addons.setConfig({
theme: themes.dark,
theme: themes.normal,
});
+10 -1
View File
@@ -3,8 +3,17 @@ import addonA11y from "@storybook/addon-a11y";
import { definePreview } from "@storybook/react-vite";
import { themes } from 'storybook/theming';
import { initialize, mswLoader, getWorker } from 'msw-storybook-addon';
// https://github.com/mswjs/msw-storybook-addon/issues/82#issuecomment-3894302575
try {
getWorker();
} catch (_) {
initialize();
}
export default definePreview({
loaders: [mswLoader],
parameters: {
controls: {
matchers: {
@@ -20,7 +29,7 @@ export default definePreview({
test: 'todo'
},
docs: {
theme: themes.dark,
theme: themes.normal,
},
},
+6
View File
@@ -0,0 +1,6 @@
{
"recommendations": [
"sanjulaganepola.github-local-actions",
"github.vscode-github-actions"
]
}
+37 -21
View File
@@ -1,13 +1,44 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Zod Conversion",
"type": "node",
"request": "launch",
// Debug app in VSCode
"program": "${workspaceFolder}/ts-zod.ts",
"args": [
"${file}"
],
"runtimeExecutable": "node",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"skipFiles": [
"<node_internals>/**",
// Ignore all dependencies (optional)
"${workspaceFolder}/node_modules/**",
],
},
{
"name": "Run Canonical Replace",
"program": "${workspaceFolder}/docsite/canonical-replace.ts",
"runtimeArgs": ["--experimental-strip-types"],
"args": [
"${workspaceFolder}/docsite/build"
],
"request": "launch",
"skipFiles": [
"<node_internals>/**"
],
"type": "node"
},
{
"name": "dev",
"type": "node",
"request": "launch",
// Debug app in VSCode
"program": "${workspaceFolder}/src/backend/index.ts",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/tsx",
"runtimeExecutable": "node",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"skipFiles": [
@@ -22,7 +53,7 @@
"request": "launch",
// Debug app in VSCode
"program": "${workspaceFolder}/src/backend/index.ts",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/tsx",
"runtimeExecutable": "node",
"console": "integratedTerminal",
"envFile": "${workspaceFolder}/.env",
"env": {
@@ -35,28 +66,13 @@
"${workspaceFolder}/node_modules/**",
],
},
{
"name": "tsx",
"type": "node",
"request": "launch",
// Debug current file in VSCode
"program": "${file}",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/tsx",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"skipFiles": [
"<node_internals>/**",
// Ignore all dependencies (optional)
"${workspaceFolder}/node_modules/**",
],
},
{
"name": "schema",
"type": "node",
"request": "launch",
// Debug app in VSCode
"program": "${workspaceFolder}/src/backend/utils/SchemaStaticUtil.ts",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/tsx",
"program": "${workspaceFolder}/src/backend/utils/SchemaDocsStaticUtils.ts",
"runtimeExecutable": "node",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"skipFiles": [
@@ -83,7 +99,7 @@
"--recursive",
"${workspaceFolder}/src/backend/tests/**/*.test.ts"
],
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/tsx",
"runtimeExecutable": "node",
"internalConsoleOptions": "openOnSessionStart",
"name": "Mocha Tests",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
@@ -107,7 +123,7 @@
"--config", "${workspaceRoot}/.mocharc.json",
"${file}"
],
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/tsx",
"runtimeExecutable": "node",
"internalConsoleOptions": "openOnSessionStart",
"name": "Mocha Test on File",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
+4 -3
View File
@@ -1,5 +1,4 @@
{
"mochaExplorer.require": "tsx/esm",
"mochaExplorer.timeout": 1200000,
"mochaExplorer.exit": true,
"search.exclude": {
@@ -11,7 +10,9 @@
"docsite/**"
],
"files.associations": {
"*.css": "tailwindcss"
"*.css": "tailwindcss",
"**/config/*.json": "jsonc"
},
"tailwindCSS.experimental.configFile": "src/client/index.css"
"tailwindCSS.experimental.configFile": "src/client/index.css",
}
+28 -7
View File
@@ -1,7 +1,7 @@
FROM ghcr.io/linuxserver/baseimage-debian:bookworm AS base
ENV TZ=Etc/GMT
ENV NODE_VERSION=20.18.1
ENV NODE_VERSION=24.14.0
# borrowing openssl header removal trick from offical docker-node
# https://github.com/nodejs/docker-node/blob/main/18/bookworm-slim/Dockerfile#L8
@@ -49,11 +49,14 @@ RUN \
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN echo "Node: $(node -v)\nNPM: $(npm -v)"
RUN npm install -g concurrently
ARG data_dir=/config
VOLUME $data_dir
ENV CONFIG_DIR=$data_dir
ENV DATA_DIR=$data_dir
COPY docker/root /
@@ -63,7 +66,8 @@ FROM base AS build
COPY --chown=abc:abc package*.json tsconfig.json ./
COPY --chown=abc:abc patches ./patches
COPY --chown=abc:abc docsite/package*.json tsconfig.json ./docsite/
COPY --chown=abc:abc docsite/patches ./docsite/patches
COPY --chown=abc:abc docsite/package*.json docsite/tsconfig* ./docsite/
# for debugging, so the build fails faster when timing out (arm64)
#RUN npm config set fetch-retries 1 && \
@@ -90,30 +94,47 @@ RUN npm run build:parallel && rm -rf node_modules && rm -rf docsite/node_modules
FROM base AS app
LABEL org.opencontainers.image.source="https://github.com/FoxxMD/multi-scrobbler"
COPY --chown=abc:abc *.json *.js *.ts index.html ./
COPY --chown=abc:abc patches ./patches
# frontend build from vite/esbuild
COPY --from=build --chown=abc:abc /app/dist /app/dist
COPY --from=build --chown=abc:abc /app/src /app/src
# backend
COPY --from=build --chown=abc:abc /app/src/backend /app/src/backend
COPY --from=build --chown=abc:abc /app/src/core /app/src/core
# docusaurus docs
COPY --from=build --chown=abc:abc /app/docsite /app/docsite
COPY --from=base /usr/bin /usr/bin
COPY --from=base /usr/lib /usr/lib
ENV NODE_ENV=production
ENV IS_DOCKER=true
ENV COLORED_STD=true
# https://stackoverflow.com/a/63640896/1469797
ARG APP_BUILD_VERSION
ENV APP_VERSION=$APP_BUILD_VERSION
RUN npm ci --omit=dev --no-audit \
# we used compiled schema json for production so this isn't needed
&& npm uninstall ts-json-schema-generator \
&& npm cache clean --force \
&& chown -R abc:abc node_modules \
## peerDependency of validbot and can(?) be removed for production
&& rm -r node_modules/typescript \
&& npx @usex/prune-mod -w \
&& rm -rf /root/.cache
ARG BUILD_DATE=0
LABEL org.opencontainers.image.version="$APP_BUILD_VERSION" \
org.opencontainers.image.source="https://github.com/FoxxMD/multi-scrobbler" \
org.opencontainers.image.documentation="https://docs.multi-scrobbler.app" \
org.opencontainers.image.description="Scrobble from multiple sources to multiple clients" \
org.opencontainers.image.title="Multi-Scrobbler" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.authors="FoxxMD" \
org.opencontainers.image.created="$BUILD_DATE" \
org.opencontainers.image.url="https://docs.multi-scrobbler.app" \
maintainer="FoxxMD" \
build_version="$APP_BUILD_VERSION"
ARG webPort=9078
ENV PORT=$webPort
+55 -54
View File
@@ -3,7 +3,7 @@
[![Latest Release](https://img.shields.io/github/v/release/foxxmd/multi-scrobbler)](https://github.com/FoxxMD/multi-scrobbler/releases)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Docker Pulls](https://img.shields.io/docker/pulls/foxxmd/multi-scrobbler)](https://hub.docker.com/r/foxxmd/multi-scrobbler)
[![Docs](https://img.shields.io/badge/Read%20The%20Docs-1082c2)](https://foxxmd.github.io/multi-scrobbler/)
[![Docs](https://img.shields.io/badge/Read%20The%20Docs-1082c2)](https://docs.multi-scrobbler.app/)
<img src="/assets/icon.png" align="right"
@@ -11,56 +11,57 @@ alt="multi-scrobbler logo" width="180" height="180">
A dockerized app that monitors your music listening activity from *everywhere* and scrobbles it *anywhere*.
* Supports scrobbling from many [**Sources**](https://foxxmd.github.io/multi-scrobbler/configuration/sources)
* [Azuracast](https://foxxmd.github.io/multi-scrobbler/configuration/sources/azuracast)
* [Deezer](https://foxxmd.github.io/multi-scrobbler/configuration/sources/deezer)
* [Google Cast (Chromecast)](https://foxxmd.github.io/multi-scrobbler/configuration/sources/google-cast)
* [Icecast](https://foxxmd.github.io/multi-scrobbler/configuration/sources/icecast)
* [Jellyfin](https://foxxmd.github.io/multi-scrobbler/configuration/sources/jellyfin)
* [JRiver](https://foxxmd.github.io/multi-scrobbler/configuration/sources/jriver)
* [Kodi](https://foxxmd.github.io/multi-scrobbler/configuration/sources/kodi)
* [Koito](https://foxxmd.github.io/multi-scrobbler/configuration/sources/koito-source)
* [Last.fm](https://foxxmd.github.io/multi-scrobbler/configuration/sources/lastfm-source)
* [Last.fm (Endpoint)](https://foxxmd.github.io/multi-scrobbler/configuration/sources/lastfm-endpoint)
* [Libre.fm](https://foxxmd.github.io/multi-scrobbler/configuration/sources/librefm-source)
* [ListenBrainz](https://foxxmd.github.io/multi-scrobbler/configuration/sources/listenbrainz-source)
* [ListenBrainz (Endpoint)](https://foxxmd.github.io/multi-scrobbler/configuration/sources/listenbrainz-endpoint)
* [Maloja](https://foxxmd.github.io/multi-scrobbler/configuration/sources/maloja)
* [Mopidy](https://foxxmd.github.io/multi-scrobbler/configuration/sources/mopidy)
* [MPD (Music Player Daemon)](https://foxxmd.github.io/multi-scrobbler/configuration/sources/mpd)
* [MPRIS (Linux Desktop)](https://foxxmd.github.io/multi-scrobbler/configuration/sources/mpris)
* [Musikcube](https://foxxmd.github.io/multi-scrobbler/configuration/sources/musikcube)
* [Music Assistant](https://foxxmd.github.io/multi-scrobbler/configuration/sources/listenbrainz-endpoint#music-assistant)
* [Plex](https://foxxmd.github.io/multi-scrobbler/configuration/sources/plex)
* [Rocksky](https://foxxmd.github.io/multi-scrobbler/configuration/sources/rocksky-source)
* [Sonos](https://foxxmd.github.io/multi-scrobbler/configuration/sources/sonos)
* [Spotify](https://foxxmd.github.io/multi-scrobbler/configuration/sources/spotify)
* [Subsonic-compatible APIs](https://foxxmd.github.io/multi-scrobbler/configuration/sources/subsonic) (like [Airsonic](https://airsonic.github.io/) and [Navidrome](https://www.navidrome.org/))
* [teal.fm](https://foxxmd.github.io/multi-scrobbler/configuration/sources/tealfm-source)
* [WebScrobbler](https://foxxmd.github.io/multi-scrobbler/configuration/sources/webscrobbler)
* [VLC](https://foxxmd.github.io/multi-scrobbler/configuration/sources/vlc)
* [Yamaha MusicCast](https://foxxmd.github.io/multi-scrobbler/configuration/sources/yamaha-musiccast)
* [Youtube Music](https://foxxmd.github.io/multi-scrobbler/configuration/sources/youtube-music)
* Supports scrobbling to many [**Clients**](https://foxxmd.github.io/multi-scrobbler/configuration/clients)
* [Discord](https://foxxmd.github.io/multi-scrobbler/configuration/clients/discord) (Now Playing)
* [Koito](https://foxxmd.github.io/multi-scrobbler/configuration/clients/koito)
* [Last.fm](https://foxxmd.github.io/multi-scrobbler/configuration/clients/lastfm)
* [Libre.fm](https://foxxmd.github.io/multi-scrobbler/configuration/clients/librefm)
* [ListenBrainz](https://foxxmd.github.io/multi-scrobbler/configuration/clients/listenbrainz)
* [Maloja](https://foxxmd.github.io/multi-scrobbler/configuration/clients/maloja)
* [Rocksky](https://foxxmd.github.io/multi-scrobbler/configuration/clients/rocksky)
* [teal.fm](https://foxxmd.github.io/multi-scrobbler/configuration/clients/tealfm)
* Monitor status of Sources and Clients using [webhooks (Gotify, Ntfy, Apprise)](https://foxxmd.github.io/multi-scrobbler/configuration#webhook-configurations), [healthcheck endpoints](https://foxxmd.github.io/multi-scrobbler/configuration#health-endpoint), or [prometheus metrics](https://foxxmd.github.io/multi-scrobbler/configuration/#prometheus).
* Supports [Now Playing](https://foxxmd.github.io/multi-scrobbler/configuration/clients#now-playing) for scrobble Clients
* Supports scrobbling from many [**Sources**](https://docs.multi-scrobbler.app/configuration/sources)
* [Apple Music](https://docs.multi-scrobbler.app/configuration/sources/applemusic/?configType=file)
* [Azuracast](https://docs.multi-scrobbler.app/configuration/sources/azuracast)
* [Deezer](https://docs.multi-scrobbler.app/configuration/sources/deezer)
* [Google Cast (Chromecast)](https://docs.multi-scrobbler.app/configuration/sources/google-cast)
* [Icecast](https://docs.multi-scrobbler.app/configuration/sources/icecast)
* [Jellyfin](https://docs.multi-scrobbler.app/configuration/sources/jellyfin)
* [JRiver](https://docs.multi-scrobbler.app/configuration/sources/jriver)
* [Kodi](https://docs.multi-scrobbler.app/configuration/sources/kodi)
* [Koito](https://docs.multi-scrobbler.app/configuration/sources/koito-source)
* [Last.fm](https://docs.multi-scrobbler.app/configuration/sources/lastfm-source)
* [Last.fm (Endpoint)](https://docs.multi-scrobbler.app/configuration/sources/lastfm-endpoint)
* [Libre.fm](https://docs.multi-scrobbler.app/configuration/sources/librefm-source)
* [ListenBrainz](https://docs.multi-scrobbler.app/configuration/sources/listenbrainz-source)
* [ListenBrainz (Endpoint)](https://docs.multi-scrobbler.app/configuration/sources/listenbrainz-endpoint)
* [Maloja](https://docs.multi-scrobbler.app/configuration/sources/maloja)
* [Mopidy](https://docs.multi-scrobbler.app/configuration/sources/mopidy)
* [MPD (Music Player Daemon)](https://docs.multi-scrobbler.app/configuration/sources/mpd)
* [MPRIS (Linux Desktop)](https://docs.multi-scrobbler.app/configuration/sources/mpris)
* [Musikcube](https://docs.multi-scrobbler.app/configuration/sources/musikcube)
* [Music Assistant](https://docs.multi-scrobbler.app/configuration/sources/listenbrainz-endpoint#music-assistant)
* [Plex](https://docs.multi-scrobbler.app/configuration/sources/plex)
* [Rocksky](https://docs.multi-scrobbler.app/configuration/sources/rocksky-source)
* [Sonos](https://docs.multi-scrobbler.app/configuration/sources/sonos)
* [Spotify](https://docs.multi-scrobbler.app/configuration/sources/spotify)
* [Subsonic-compatible APIs](https://docs.multi-scrobbler.app/configuration/sources/subsonic) (like [Airsonic](https://airsonic.github.io/) and [Navidrome](https://www.navidrome.org/))
* [teal.fm](https://docs.multi-scrobbler.app/configuration/sources/tealfm-source)
* [WebScrobbler](https://docs.multi-scrobbler.app/configuration/sources/webscrobbler)
* [VLC](https://docs.multi-scrobbler.app/configuration/sources/vlc)
* [Yamaha MusicCast](https://docs.multi-scrobbler.app/configuration/sources/yamaha-musiccast)
* [Youtube Music](https://docs.multi-scrobbler.app/configuration/sources/youtube-music)
* Supports scrobbling to many [**Clients**](https://docs.multi-scrobbler.app/configuration/clients)
* [Discord](https://docs.multi-scrobbler.app/configuration/clients/discord) (Now Playing)
* [Koito](https://docs.multi-scrobbler.app/configuration/clients/koito)
* [Last.fm](https://docs.multi-scrobbler.app/configuration/clients/lastfm)
* [Libre.fm](https://docs.multi-scrobbler.app/configuration/clients/librefm)
* [ListenBrainz](https://docs.multi-scrobbler.app/configuration/clients/listenbrainz)
* [Maloja](https://docs.multi-scrobbler.app/configuration/clients/maloja)
* [Rocksky](https://docs.multi-scrobbler.app/configuration/clients/rocksky)
* [teal.fm](https://docs.multi-scrobbler.app/configuration/clients/tealfm)
* Monitor status of Sources and Clients using [webhooks (Gotify, Ntfy, Apprise)](https://docs.multi-scrobbler.app/configuration#webhook-configurations), [healthcheck endpoints](https://docs.multi-scrobbler.app/configuration#health-endpoint), or [prometheus metrics](https://docs.multi-scrobbler.app/configuration/#prometheus).
* Supports [Now Playing](https://docs.multi-scrobbler.app/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](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)
* Modify data before scrobbling with [regular expression or search patterns](https://docs.multi-scrobbler.app/configuration/transforms)
* Install using [Docker images for x86/ARM](https://docs.multi-scrobbler.app/installation#docker) or [locally with NodeJS](https://docs.multi-scrobbler.app/installation#nodejs)
[**Quick Start Guide**](https://foxxmd.github.io/multi-scrobbler//quickstart)
[**Quick Start Guide**](https://docs.multi-scrobbler.app//quickstart)
<img src="/docsite/static/img/status-ui.png" width="800">
@@ -73,15 +74,15 @@ A dockerized app that monitors your music listening activity from *everywhere* a
**But I already scrobble my music to Last.fm/ListenBrainz, is multi-scrobbler for me?**
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.
Yes! You can use [Last.fm as a **Source**](https://docs.multi-scrobbler.app/configuration/sources/lastfm-source) or [Listenbrainz as a **Source**](https://docs.multi-scrobbler.app/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](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.
You set up [configurations](https://docs.multi-scrobbler.app/configuration) for one or more [**Sources**](https://docs.multi-scrobbler.app/configuration/sources) and one or more [**Clients**](https://docs.multi-scrobbler.app/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**](https://foxxmd.github.io/multi-scrobbler/configuration/sources) is a data source that contains information about music you are playing or have listened to, such as: a desktop player, web music player, or cloud music service. Examples are **Spotify, Jellyfin, Plex, Youtube Music, Navidrome**, etc...
A [**Source**](https://docs.multi-scrobbler.app/configuration/sources) is a data source that contains information about music you are playing or have listened to, such as: a desktop player, web music player, or cloud music service. Examples are **Spotify, Jellyfin, Plex, Youtube Music, Navidrome**, etc...
Source configurations consist of:
@@ -91,7 +92,7 @@ Source configurations consist of:
### Client
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 **Koito, Last.fm, Listenbrainz**...
A [**Client**](https://docs.multi-scrobbler.app/configuration/clients) is an application that stores the historical information about what music you have played (scrobbles). Examples are **Koito, Last.fm, Listenbrainz**...
Client configurations consist of:
@@ -100,15 +101,15 @@ Client configurations consist of:
## Quick Start
[See the **Quick Start Guide**](https://foxxmd.github.io/multi-scrobbler/quickstart)
[See the **Quick Start Guide**](https://docs.multi-scrobbler.app/quickstart)
## Installation
[See the **Installation** documentation](https://foxxmd.github.io/multi-scrobbler/installation)
[See the **Installation** documentation](https://docs.multi-scrobbler.app/installation)
## Configuration
[See the **Configuration** documentation](https://foxxmd.github.io/multi-scrobbler/configuration)
[See the **Configuration** documentation](https://docs.multi-scrobbler.app/configuration)
## Usage
@@ -123,11 +124,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](https://foxxmd.github.io/multi-scrobbler/FAQ) before creating an issue!
Having issues with connections or configuration? Check the [FAQ](https://docs.multi-scrobbler.app/FAQ) before creating an issue!
## Development
[Detailed architecture and development guides for Sources/Clients](https://foxxmd.github.io/multi-scrobbler/development/dev-common)
[Detailed architecture and development guides for Sources/Clients](https://docs.multi-scrobbler.app/development/dev-common)
## License
+29
View File
@@ -0,0 +1,29 @@
[
{
"id": "myAppleMusic",
"name": "My Apple Music",
"enable": true,
"clients": [],
"data": {
// either key or token needs to be provided
"key": {
"id": "2HPSNJZ88N",
"teamId": "SN6YASW8G4",
"p8": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEG...-----END PRIVATE KEY-----"
},
"token": "your-jwt-token-here",
"mediaUserToken": "your-media-user-token-here",
// optional: Origin header for API requests (required when using a browser token)
"origin": "https://music.apple.com",
// optional: polling interval in seconds (default 60)
"interval": 60
},
"options": {
"logDiff": true,
"recoverUnchangedTopHistory": true,
"normalizeAlbum": true
}
}
]
+2 -1
View File
@@ -2,7 +2,8 @@
{
"type": "azuracast",
"enable": true,
"name": "azura",
"name": "Cool Station",
"id": "myAzura",
"data": {
"url": "ws://192.168.0.101",
"station": "my-station-name",
+2 -1
View File
@@ -1,6 +1,7 @@
[
{
"name": "MyCast",
"name": "My Cast",
"id": "myCast",
"data": {
"blacklistedDevices": [],
"whitelistedDevices": [],
+4 -2
View File
@@ -22,7 +22,8 @@
"type": "spotify",
"enable": true,
"clients": ["myConfig"],
"name": "mySpotifySource",
"name": "Cool Spotify Name",
"id": "mySpotifyId",
"data": {
"clientId": "a89cba1569901a0671d5a9875fed4be1",
"clientSecret": "ec42e09d5ae0ee0f0816ca151008412a",
@@ -34,7 +35,8 @@
{
"type": "maloja",
"enable": true,
"name": "myConfig",
"name": "Foxx Maloja",
"id": "myMalojaId",
"data": {
"url": "http://localhost:42010",
"apiKey": "myMalojaKey"
+2 -1
View File
@@ -1,6 +1,7 @@
[
{
"name": "DeezerARL",
"name": "Family Account",
"id": "myDeezerId",
"enable": true,
"clients": [],
"data": {
+2 -1
View File
@@ -1,7 +1,8 @@
[
{
"name": "MS",
"name": "Desktop",
"enable": true,
"id": "myDiscordLocal",
"data": {
"applicationId": "8190211179716453570",
"ipcLocations": ["192.168.0.105:6655"]
+9 -9
View File
@@ -1,11 +1,11 @@
[
{
"name": "MS",
"enable": true,
"data": {
"token": "Ma7XBvzRERK3gQdkX4dkb3OQ.6aaGmJ.rvFiiVrzG4VXHHL3LyhZYhpWdG3Ph_if9aKz5E",
"applicationId": "8190211179716453570"
}
{
"name": "Discord",
"enable": true,
"id": "myDiscordRemote",
"data": {
"token": "Ma7XBvzRERK3gQdkX4dkb3OQ.6aaGmJ.rvFiiVrzG4VXHHL3LyhZYhpWdG3Ph_if9aKz5E",
"applicationId": "8190211179716453570"
}
]
}
]
+2 -1
View File
@@ -1,7 +1,8 @@
[
{
"name": "myLfm",
"name": "LFM For App",
"enable": true,
"id": "myLfmApp",
"data": {
"slug": "mySlug"
}
+2 -1
View File
@@ -1,6 +1,7 @@
[
{
"name": "myLz",
"name": "LZ for Navidrome",
"id": "myLzNavidrome",
"enable": true,
"data": {
"token": "myToken"
+2 -1
View File
@@ -1,7 +1,8 @@
[
{
"name": "myIcy",
"name": "Station 1",
"enable": true,
"id": "myIceStation1",
"data": {
"url": "http://myIcecast.com/radio"
}
+2 -1
View File
@@ -1,7 +1,8 @@
[
{
"name": "MyJellyfin",
"name": "Cool JF Server",
"enable": true,
"id": "myJellyfin",
"clients": [],
"data": {
"url": "http://localhost:8096",
+2 -1
View File
@@ -1,6 +1,7 @@
[
{
"name": "MyJriver",
"name": "My JRiver Server",
"id": "myRiver",
"enable": true,
"data": {
"url": "0.0.0.0",
+2 -1
View File
@@ -1,6 +1,7 @@
[
{
"name": "MyKodi",
"name": "My Kodi Server",
"id": "myKodi",
"enable": true,
"data": {
"url": "http://localhost:8080/jsonrpc",
+5 -3
View File
@@ -1,6 +1,7 @@
[
{
"name": "koito-client",
{
"name": "Koito Client A",
"id": "myKoitoClient",
"configureAs": "client",
"data": {
"token": "029b081ba-9156-4pe7-88e5-3be671f5ea2b",
@@ -9,7 +10,8 @@
}
},
{
"name": "koito-source",
"name": "Koito Source B",
"id": "myKoitoSource",
"configureAs": "source",
"data": {
"token": "029b081ba-9156-4pe7-88e5-3be671f5ea2b",
+4 -2
View File
@@ -1,6 +1,7 @@
[
{
"name": "myLastFmClient",
"name": "Foxx LFM Client",
"id": "myLastFmClient",
"enable": true,
"configureAs": "client",
"data": {
@@ -10,7 +11,8 @@
}
},
{
"name": "myLastFmSource",
"name": "Foxx LFM Source",
"id": "myLastFmSource",
"enable": true,
"configureAs": "source",
"data": {
+4 -2
View File
@@ -1,6 +1,7 @@
[
{
"name": "myLibreFmClient",
"name": "Foxx Libre Client",
"id": "myLibreFmClient",
"enable": true,
"configureAs": "client",
"data": {
@@ -10,7 +11,8 @@
}
},
{
"name": "myLibreFmSource",
"name": "Foxx Libre Source",
"id": "myLibreFmSource",
"enable": true,
"configureAs": "source",
"data": {
+4 -2
View File
@@ -1,6 +1,7 @@
[
{
"name": "brainzClient",
"name": "Foxx LZ Client",
"id": "brainzClient",
"enable": true,
"configureAs": "client",
"data": {
@@ -10,7 +11,8 @@
}
},
{
"name": "brainzSource",
"name": "Foxx LZ Source",
"id": "brainzSource",
"enable": true,
"configureAs": "source",
"data": {
+4 -2
View File
@@ -1,6 +1,7 @@
[
{
"name": "myMaloja-client",
"name": "Maloja Client",
"id": "myMalojaClient",
"enable": true,
"configureAs": "client",
"data": {
@@ -9,7 +10,8 @@
}
},
{
"name": "myMaloja-source",
"name": "Maloja Source",
"id": "myMalojaSource",
"enable": true,
"configureAs": "source",
"data": {
+2 -1
View File
@@ -1,6 +1,7 @@
[
{
"name": "MyMopidy",
"name": "Mopidy Desktop",
"id": "MyMopidy",
"enable": true,
"data": {
"url": "localhost",
+2 -1
View File
@@ -1,7 +1,8 @@
[
{
"enable": true,
"name": "MyMPD",
"id": "myMPD",
"name": "Desktop",
"data": {
"url": "192.168.0.100:6600",
"password": "MY_PASSWORD"
+2 -1
View File
@@ -1,6 +1,7 @@
[
{
"name": "ubuntu",
"name": "Desktop",
"id": "myMprisUbuntu",
"enable": true,
"data": {
"whitelist": ["vlc", "mpd"],
+2 -1
View File
@@ -1,6 +1,7 @@
[
{
"name": "myYamaha",
"name": "Livingroom Reciever",
"id": "myYamaha",
"enable": true,
"data": {
"url": "192.168.0.101"
+2 -1
View File
@@ -2,7 +2,8 @@
{
"type": "musikcube",
"enable": true,
"name": "musikcube",
"name": "Desktop",
"id": "myMusikcube",
"data": {
"url": "ws://localhost:7905",
"password": "MY_PASSWORD"
+2 -1
View File
@@ -1,6 +1,7 @@
[
{
"name": "MyPlexApi",
"name": "Foxx Plex",
"id": "MyPlexApi",
"enable": true,
"clients": [],
"data": {
+6 -4
View File
@@ -1,17 +1,19 @@
[
{
"name": "myRockskyClient",
"id": "myRockskyClient",
"name": "FoxxMD Rocksky Client",
"configureAs": "client",
"data": {
"key": "7cdr86vis3mpq5b1pi01md0hkm7ykzzxjy81r",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkaWQiOiJkaWQ6cGxjOng0NjVwZmtyeW5zaDZ...",
"handle": "alice.bsky.social"
}
},
{
"name": "myRockskySource",
"id": "myRockskySource",
"name": "FoxxMD Rocksky Source",
"configureAs": "source",
"data": {
"key": "e4xwktm7jxm607x8el1fcda30eu14dzb64h3j",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkaWQiOiJkaWQ6cGxjOng0NjVwZmtyeW5zaDZ...",
"handle": "alice.bsky.social"
}
}
+2 -1
View File
@@ -1,7 +1,8 @@
[
{
"enable": true,
"name": "MySonos",
"id": "MySonos",
"name": "Boom 2",
"data": {
"host": "192.168.0.150",
"devicesAllow": ["move","beam"],
+2 -1
View File
@@ -1,6 +1,7 @@
[
{
"name": "MySpotify",
"id": "MySpotify",
"name": "Foxx Spotify",
"enable": true,
"clients": [],
"data": {
+4 -2
View File
@@ -1,12 +1,14 @@
[
{
"name": "MySubsonic",
"id": "mySubsonic",
"name": "Airsonic Server",
"enable": true,
"data": {
"url": "http://localhost:4040/airsonic",
"user": "yourUser",
"password": "yourPassword",
"usersAllow": ["yourUser"]
"usersAllow": ["yourUser"],
"detectStaleNowPlayingFromMinutesAgo": true
}
}
]
+4 -2
View File
@@ -1,6 +1,7 @@
[
{
"name": "myTeal",
"id": "myTealClient",
"name": "Foxx Teal Client",
"configureAs": "client",
"data": {
"identifier": "alice.bsky.social",
@@ -8,7 +9,8 @@
}
},
{
"name": "myTealSource",
"id": "myTealSource",
"name": "Foxx Teal Source",
"configureAs": "source",
"data": {
"identifier": "alice.bsky.social",
+2 -1
View File
@@ -1,7 +1,8 @@
[
{
"enable": true,
"name": "MyVlc",
"id": "myVlc",
"name": "Desktop",
"data": {
"url": "localhost:8080",
"password": "changeme"
+2 -1
View File
@@ -1,6 +1,7 @@
[
{
"name": "MyWebScrobbler",
"id": "MyWebScrobbler",
"name": "Firefox Laptop",
"data": {
"slug": "MyOptionalSlug",
"whitelist": [],
+12
View File
@@ -0,0 +1,12 @@
[
{
"id": "myYandex",
"name": "Yandex Music",
"type": "ymbridge",
"data": {
"url": "http://192.168.1.161:9980",
"apiKey": "change-me",
"interval": 3
}
}
]
+2 -1
View File
@@ -1,6 +1,7 @@
[
{
"name": "MyYTMusic",
"id": "myYtMusic",
"name": "Foxx YT Music",
"enable": true,
"clients": [],
"data": {
+2
View File
@@ -4,6 +4,8 @@ services:
multi-scrobbler:
build:
context: .
args:
BUILD_DATE: "${BUILD_DATE}"
env_file:
- path: ./.env
required: false
@@ -1,33 +1,19 @@
#!/usr/bin/with-contenv bash
# used https://github.com/linuxserver/docker-plex as a template
POPULATE_EXAMPLES=false
echo "-------------------------------------"
echo -e "Setting up multi-scrobbler config directory based on CONFIG_DIR env: ${CONFIG_DIR}\n"
echo -e "Verifying multi-scrobbler directory permissions based on CONFIG_DIR env: ${CONFIG_DIR} and DATA_DIR env: ${DATA_DIR}\n"
# make config folder if it does not exist
if [ ! -d "${CONFIG_DIR}" ]; then
echo "Directory does not exist! Creating..."
POPULATE_EXAMPLES=true
mkdir -p "${CONFIG_DIR}"
else
if [ "$(ls -A ${CONFIG_DIR})" ]; then
echo "Directory is not empty, not creating examples."
else
POPULATE_EXAMPLES=true
fi
if [ -d "${CONFIG_DIR}" ]; then
echo "chown'ing config directory to ensure correct permissions."
chown -R abc:abc "${CONFIG_DIR}"
echo "Done!"
fi
# add example configs
if [ "$POPULATE_EXAMPLES" = true ]; then
echo "Directory is empty, adding examples..."
cp -r /app/config/. "${CONFIG_DIR}"/
if [ -d "${DATA_DIR}" ]; then
echo "chown'ing data directory to ensure correct permissions."
chown -R abc:abc "${DATA_DIR}"
echo "Done!"
fi
# permissions
echo "chown'ing directory to ensure correct permissions."
chown -R abc:abc "${CONFIG_DIR}"
echo "Done!"
echo -e "-------------------------------------\n"
@@ -7,4 +7,4 @@ echo -e "\nmulti-scrobbler is starting!"
export NODE_ENV=production
cd /app || exit
exec \
s6-setuidgid abc /usr/bin/node $NODE_ARGS /app/node_modules/.bin/tsx /app/src/backend/index.ts
s6-setuidgid abc /usr/bin/node $NODE_ARGS /app/src/backend/index.ts
+4
View File
@@ -18,3 +18,7 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lib
docs/api/
static/openapi.json
+42
View File
@@ -0,0 +1,42 @@
import fs from 'node:fs/promises';
import path from 'node:path';
var args = process.argv.slice(2);
const linkRegex = new RegExp(/<link data-rh="true" rel="\w+"\shref="(https:\/\/foxxmd\.github\.io\/multi-scrobbler)/g,);
const replacement = process.env.CANONICAL_HREF ?? 'https://docs.multi-scrobbler.app';
/**
* Replace canonical and alternative <link> nodes with the "real" domain of the site
* so that SEO (google) chooses the correct domain when showing search results
*
*/
(async function () {
if(replacement === undefined || replacement === '') {
console.warn('No replacement value found in process.env.CANONICAL_HREF');
return;
}
const buildDir = path.resolve(args[0]);
console.log(`Reading dir recursively ${buildDir}`);
const files = (await fs.readdir(buildDir, { recursive: true })).filter(x => x.includes('.html'));
console.log(`Found ${files.length} files with .html extensions`);
let modifications = 0;
await Promise.all(files.map(async (x) => {
const filePath = path.resolve(path.join(buildDir, x));
//console.log(`Replacing at ${filePath}`);
await fs.writeFile(filePath, (await fs.readFile(filePath)).toString().replace(linkRegex, (match, capture, offset, string, groups) => {
// this may not be fully accurate since we're mutating concurrently/async
// but its a good enough signal for logging to tell if replacements happened
modifications++;
return match.replace(capture, replacement);
}));
}));
console.log(`Done with ${modifications} replacements`);
}());
+7
View File
@@ -1,6 +1,7 @@
---
toc_min_heading_level: 2
toc_max_heading_level: 5
title: FAQ
---
## Connection Issues
@@ -113,6 +114,12 @@ It means the JSON in your configuration file is not valid. Copy and paste your c
## Scrobbling Issues
### How do I prevent duplicate scrobbles?
Multi-scrobbler has a [robust duplicate scrobble detection system](/configuration/duplicates#how-duplicates-are-detected) that should handle this for you in the majority of usecases. If you have a [simple setup without any circular dependencies](/configuration/duplicates#valid-configs) then you are already protected against duplicates.
If your scenario is more complex or you have a service(s) acting as **both** a **Client** and **Source** you should refer to the [**Duplicate Scrobble Guidance**](/configuration/duplicates#valid-configs) page to determine if you can avoid a configuration that may result in duplicate scrobbles.
### 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.
@@ -0,0 +1,177 @@
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<Tabs groupId="configType" queryString>
<TabItem value="file" label="File">
Using [File Config](/configuration/?configType=file#configuration-types)
```json5 title="CONFIG_DIR/jellyin.json"
[
{
"id": "fredJf",
"name": "Freds Jellyfin",
"clients": ["fredLFM"],
"data": {
"url": "https://jellyfin.fred.com",
"user": "Fred",
"apiKey": "c9fae8756fbf481ebd9c5bb56bd6540c"
}
},
{
"id": "maryJf",
"name": "Marys Jellyfin",
"clients": ["maryLFM"],
"data": {
"url": "https://jellyfin.mary.com",
"user": "Mary",
"apiKey": "26fe9c5dc6374542adc4f4d5e8352838"
}
}
]
```
```json5 title="CONFIG_DIR/plex.json"
[
{
"id": "foxxPlex",
"name": "Foxx Plex",
"clients": ["foxxKoi"],
"data": {
"token": "JtRnwQWD__XMJF8sT3jc",
"url": "192.168.0.233:32400"
}
}
]
```
```json5 title="CONFIG_DIR/lastfm.json"
[
{
"id": "maryLFM",
"name": "Marys Lastfm",
"configureAs": "client",
"data": {
"apiKey": "maryApiKey",
"secret": "marySecret",
"redirectUri": "http://localhost:9078/lastfm/callback"
}
},
{
"id": "fredLFM",
"name": "Freds Lastfm",
"configureAs": "client",
"data": {
"apiKey": "fredApiKey",
"secret": "fredSecret",
"redirectUri": "http://localhost:9078/lastfm/callback"
}
}
]
```
```json5 title="CONFIG_DIR/koito.json"
[
{
"id": "foxxKoi",
"name": "Foxx Koito",
"configureAs": "client",
"data": {
"token": "029b081ba-9156-4pe7-88e5-3be671f5ea2b",
"username": "admin",
"url": "http://192.168.0.100:4110"
}
}
]
```
</TabItem>
<TabItem value="aio" label="AIO">
Using [AIO Config](/configuration/?configType=aio#configuration-types)
```json5 title="CONFIG_DIR/config.json"
{
"sources": [
{
"type": "jellyfin",
"id": "fredJf",
"name": "Freds Jellyfin",
"clients": [
"fredLFM"
],
"data": {
"url": "https://jellyfin.fred.com",
"user": "Fred",
"apiKey": "c9fae8756fbf481ebd9c5bb56bd6540c"
}
},
{
"type": "jellyfin",
"id": "maryJf",
"name": "Marys Jellyfin",
"clients": [
"maryLFM"
],
"data": {
"url": "https://jellyfin.mary.com",
"user": "Mary",
"apiKey": "26fe9c5dc6374542adc4f4d5e8352838"
}
},
{
"type": "plex",
"id": "foxxPlex",
"name": "Foxx Plex",
"clients": [
"foxxKoi"
],
"data": {
"token": "JtRnwQWD__XMJF8sT3jc",
"url": "192.168.0.233:32400",
"usersallow": [
"foxx"
]
}
}
],
"clients": [
{
"type": "lastfm",
"id": "maryLFM",
"name": "Marys Lastfm",
"configureAs": "client",
"data": {
"apiKey": "maryApiKey",
"secret": "marySecret",
"redirectUri": "http://localhost:9078/lastfm/callback"
}
},
{
"type": "lastfm",
"id": "fredLFM",
"name": "Freds Lastfm",
"configureAs": "client",
"data": {
"apiKey": "fredApiKey",
"secret": "fredSecret",
"redirectUri": "http://localhost:9078/lastfm/callback"
}
},
{
"type": "koito",
"id": "foxxKoi",
"name": "Foxx Koito",
"configureAs": "client",
"data": {
"token": "029b081ba-9156-4pe7-88e5-3be671f5ea2b",
"username": "admin",
"url": "http://192.168.0.100:4110"
}
}
]
}
```
</TabItem>
</Tabs>
@@ -0,0 +1,196 @@
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<Tabs groupId="configType" queryString>
<TabItem value="env" label="ENV">
Using [ENV Config](/configuration/?configType=env#configuration-types)
```yaml title="compose.yaml"
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
# spotify
- SPOTIFY_ID=foxxSpotify
- SPOTIFY_CLIENT_ID=H2UvGv8okrFlv2OUoc1lAtK7Xe5EGDgs
- SPOTIFY_CLIENT_SECRET=HvoFE5Ce2hdblNE6vaFBEx6dRVcDdo43
- SPOTIFY_REDIRECT_URI=https://multiscrobbler.example.com/callback
# navidrome listenbrainz endpoint
- LZE_ID=myLzNavidrome
- LZE_TOKEN=myToken
# plex
- PLEX_ID=foxxPlex
- PLEX_URL=192.168.0.233:32400
- PLEX_TOKEN=JtRnwQWD__XMJF8sT3jc
- PLEX_USERS_ALLOW=foxx
- PLEX_LIBRARIES_ALLOW=oldies
# lastfm
- LASTFM_ID=myLFM
- LASTFM_API_KEY=myApiKey
- LASTFM_SECRET=mySecret
- LASTFM_REDIRECT_URI=https://multiscrobbler.example.com/lastfm/callback
# koito
- KOITO_ID=foxxKoi
- KOITO_TOKEN=029b081ba-9156-4pe7-88e5-3be671f5ea2b
- KOITO_USER=admin
- KOITO_URL=http://192.168.0.100:4110
volumes:
- "./config:/config"
ports:
- "9078:9078"
restart: unless-stopped
```
</TabItem>
<TabItem value="file" label="File">
Using [File Config](/configuration/?configType=file#configuration-types)
```json5 title="CONFIG_DIR/spotify.json"
[
{
"id": "foxxSpotify",
"name": "Foxx Spotify",
"data": {
"clientId": "a89cba1569901a0671d5a9875fed4be1",
"clientSecret": "ec42e09d5ae0ee0f0816ca151008412a",
"redirectUri": "http://localhost:9078/callback",
"interval": 60
}
}
]
```
```json5 title="CONFIG_DIR/endpointlz.json"
[
{
"id": "myLzNavidrome",
"name": "LZ for Navidrome",
"data": {
"token": "myToken"
}
}
]
```
```json5 title="CONFIG_DIR/plex.json"
[
{
"id": "foxxPlex",
"name": "Foxx Plex",
"data": {
"token": "JtRnwQWD__XMJF8sT3jc",
"url": "192.168.0.233:32400",
"usersallow": ["foxx"],
"librariesAllow": ["oldies"],
}
}
]
```
```json5 title="CONFIG_DIR/lastfm.json"
[
{
"id": "myLFM",
"name": "Old Lastfm",
"configureAs": "client",
"data": {
"apiKey": "myApiKey",
"secret": "mySecret",
"redirectUri": "http://localhost:9078/lastfm/callback"
}
}
]
```
```json5 title="CONFIG_DIR/koito.json"
[
{
"id": "foxxKoi",
"name": "Foxx Koito",
"configureAs": "client",
"data": {
"token": "029b081ba-9156-4pe7-88e5-3be671f5ea2b",
"username": "admin",
"url": "http://192.168.0.100:4110"
}
}
]
```
</TabItem>
<TabItem value="aio" label="AIO">
Using [AIO Config](/configuration/?configType=aio#configuration-types)
```json5 title="CONFIG_DIR/config.json"
{
"sources": [
{
"type": "spotify",
"id": "foxxSpotify",
"name": "Foxx Spotify",
"data": {
"clientId": "a89cba1569901a0671d5a9875fed4be1",
"clientSecret": "ec42e09d5ae0ee0f0816ca151008412a",
"redirectUri": "http://localhost:9078/callback",
"interval": 60
}
},
{
"type": "endpointlz",
"name": "LZ for Navidrome",
"id": "myLzNavidrome",
"data": {
"token": "myToken"
}
},
{
"type": "plex",
"id": "foxxPlex",
"name": "Foxx Plex",
"data": {
"token": "JtRnwQWD__XMJF8sT3jc",
"url": "192.168.0.233:32400",
"usersallow": ["foxx"],
"librariesAllow": ["oldies"]
}
}
],
"clients": [
{
"type": "lastfm",
"id": "myLFM",
"name": "Old Lastfm",
"configureAs": "client",
"data": {
"apiKey": "myApiKey",
"secret": "mySecret",
"redirectUri": "http://localhost:9078/lastfm/callback"
}
},
{
"type": "koito",
"id": "foxxKoi",
"name": "Foxx Koito",
"configureAs": "client",
"data": {
"token": "029b081ba-9156-4pe7-88e5-3be671f5ea2b",
"username": "admin",
"url": "http://192.168.0.100:4110"
}
}
]
}
```
</TabItem>
</Tabs>
@@ -0,0 +1,226 @@
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<Tabs groupId="configType" queryString>
<TabItem value="env" label="ENV">
Using [ENV Config](/configuration/?configType=env#configuration-types)
```yaml title="compose.yaml"
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
# required setup for musicbrainz
- MB_CONTACT=contact@mydomain.com # a real email
# searches with sensible release sorting with the searchOrder
# isrc => basic => artist (native mode)
- MB_PRESETS=default,sensible,native
# spotify
- SPOTIFY_ID=foxxSpotify
- SPOTIFY_CLIENT_ID=H2UvGv8okrFlv2OUoc1lAtK7Xe5EGDgs
- SPOTIFY_CLIENT_SECRET=HvoFE5Ce2hdblNE6vaFBEx6dRVcDdo43
- SPOTIFY_REDIRECT_URI=https://multiscrobbler.example.com/callback
# navidrome listenbrainz endpoint
- LZE_ID=myLzNavidrome
- LZE_TOKEN=myToken
# applies musicbrainz Stage to preTransform of navidrome source
- LZE_TRANSFORMS=musicbrainz
# lastfm
- LASTFM_ID=myLFM
- LASTFM_API_KEY=myApiKey
- LASTFM_SECRET=mySecret
- LASTFM_REDIRECT_URI=https://multiscrobbler.example.com/lastfm/callback
volumes:
- "./config:/config"
ports:
- "9078:9078"
restart: unless-stopped
```
</TabItem>
<TabItem value="file" label="File">
Using [File Config](/configuration/?configType=file#configuration-types)
For non-ENV config, [Musicbrainz Transformer](/configuration/transforms/musicbrainz/#api-setup) must be setup using the [AIO Config](/configuration/?configType=aio#configuration-types) `config.json` file. This configuration uses the [sensible defaults](/configuration/transforms/musicbrainz/#sensible-default) in the `defaults` property. This is a suggestion and can be removed if not desired.
```json5 title="CONFIG_DIR/config.json"
{
"transformers": [
{
"type": "musicbrainz",
"name": "MyMB",
"data": {
"apis": [
{
"contact": "contact@mydomain.com"
}
]
},
"defaults": {
"releaseStatusPriority": ["official"],
"releaseGroupPrimaryTypePriority": [
"album",
"single",
"ep"
],
"releaseCountryPriority": ["XW"],
"searchArtistMethod": "native",
"searchOrder": [
"isrc",
"basic",
"artist"
]
}
}
]
}
```
```json5 title="CONFIG_DIR/spotify.json"
[
{
"id": "foxxSpotify",
"name": "Foxx Spotify",
"data": {
"clientId": "a89cba1569901a0671d5a9875fed4be1",
"clientSecret": "ec42e09d5ae0ee0f0816ca151008412a",
"redirectUri": "http://localhost:9078/callback",
"interval": 60
}
}
]
```
```json5 title="CONFIG_DIR/endpointlz.json"
[
{
"id": "myLzNavidrome",
"name": "LZ for Navidrome",
"data": {
"token": "myToken"
},
"options": {
"playTransform": {
"preCompare": [
{
"type": "musicbrainz",
"name": "MyMB"
}
]
}
}
}
]
```
```json5 title="CONFIG_DIR/lastfm.json"
[
{
"id": "myLFM",
"name": "Old Lastfm",
"configureAs": "client",
"data": {
"apiKey": "myApiKey",
"secret": "mySecret",
"redirectUri": "http://localhost:9078/lastfm/callback"
}
}
]
```
</TabItem>
<TabItem value="aio" label="AIO">
Using [AIO Config](/configuration/?configType=aio#configuration-types)
This configuration uses the [sensible defaults](/configuration/transforms/musicbrainz/#sensible-default) in the `defaults` property. This is a suggestion and can be removed if not desired.
```json5 title="CONFIG_DIR/config.json"
{
"transformers": [
{
"type": "musicbrainz",
"name": "MyMB",
"data": {
"apis": [
{
"contact": "contact@mydomain.com"
}
]
},
"defaults": {
"releaseStatusPriority": ["official"],
"releaseGroupPrimaryTypePriority": [
"album",
"single",
"ep"
],
"releaseCountryPriority": ["XW"],
"searchArtistMethod": "native",
"searchOrder": [
"isrc",
"basic",
"artist"
]
}
}
],
"sources": [
{
"type": "spotify",
"id": "foxxSpotify",
"name": "Foxx Spotify",
"data": {
"clientId": "a89cba1569901a0671d5a9875fed4be1",
"clientSecret": "ec42e09d5ae0ee0f0816ca151008412a",
"redirectUri": "http://localhost:9078/callback",
"interval": 60
}
},
{
"type": "endpointlz",
"name": "LZ for Navidrome",
"id": "myLzNavidrome",
"data": {
"token": "myToken"
},
"options": {
"playTransform": {
"preCompare": [
{
"type": "musicbrainz",
"name": "MyMB"
}
]
}
}
}
],
"clients": [
{
"type": "lastfm",
"id": "myLFM",
"name": "Old Lastfm",
"configureAs": "client",
"data": {
"apiKey": "myApiKey",
"secret": "mySecret",
"redirectUri": "http://localhost:9078/lastfm/callback"
}
}
]
}
```
</TabItem>
</Tabs>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 KiB

@@ -0,0 +1,12 @@
| Environmental Variable | Type | Default | Description |
| ---------------------------------- | -------------------------- | --------------------- | --------------------------------------- |
| _**`DISCORD_ID`**_ | string | | A globally unique ID EX `myComponentId` |
| `DISCORD_NAME` | string | Value of `DISCORD_ID` | A vanity name EX `My Cool Component` |
| `DISCORD_ENABLE` | boolean | true | Should this component be used? |
| `DISCORD_TOKEN` | string | | |
| `DISCORD_ARTWORK` | boolean OR string OR array | | |
| `DISCORD_APPLICATION_ID` | string | | |
| `DISCORD_IPC_LOCATIONS` | string | | |
| `DISCORD_ARTWORK_DEFAULT_URL` | string | | |
| `DISCORD_STATUS_OVERRIDE_ALLOW` | string | | |
| `DISCORD_LISTENING_ACTIVITY_ALLOW` | string | | |
@@ -0,0 +1,8 @@
| Environmental Variable | Type | Default | Description |
| ---------------------- | ------- | ------------------- | --------------------------------------- |
| _**`KOITO_ID`**_ | string | | A globally unique ID EX `myComponentId` |
| `KOITO_NAME` | string | Value of `KOITO_ID` | A vanity name EX `My Cool Component` |
| `KOITO_ENABLE` | boolean | true | Should this component be used? |
| _**`KOITO_URL`**_ | string | | URL for the Koito server |
| _**`KOITO_TOKEN`**_ | string | | User token for the user to scrobble for |
| _**`KOITO_USER`**_ | string | | Username of the user to scrobble for |
@@ -0,0 +1,9 @@
| Environmental Variable | Type | Default | Description |
| ---------------------- | ------- | ------------------------------------- | ------------------------------------------------------- |
| _**`LASTFM_ID`**_ | string | | A globally unique ID EX `myComponentId` |
| `LASTFM_NAME` | string | Value of `LASTFM_ID` | A vanity name EX `My Cool Component` |
| `LASTFM_ENABLE` | boolean | true | Should this component be used? |
| _**`LASTFM_API_KEY`**_ | string | | API Key generated from Last.fm/Libre.fm account |
| _**`LASTFM_SECRET`**_ | string | | Secret generated from Last.fm/Libre.fm account |
| `LASTFM_REDIRECT_URI` | string | http://localhost:9078/lastfm/callback | Optional URI to use for callback. |
| `LASTFM_SESSION` | string | | Optional session id returned from a completed auth flow |
@@ -0,0 +1,10 @@
| Environmental Variable | Type | Default | Description |
| ---------------------- | ------- | -------------------------------------- | -------------------------------------------------------------- |
| _**`LIBREFM_ID`**_ | string | | A globally unique ID EX `myComponentId` |
| `LIBREFM_NAME` | string | Value of `LIBREFM_ID` | A vanity name EX `My Cool Component` |
| `LIBREFM_ENABLE` | boolean | true | Should this component be used? |
| `LIBREFM_API_KEY` | string | | Optional Secret for Libre.fm account |
| `LIBREFM_SECRET` | string | | Optional Secret for Libre.fm account |
| `LIBREFM_REDIRECT_URI` | string | http://localhost:9078/librefm/callback | Optional URI to use for callback. |
| `LIBREFM_SESSION` | string | | Optional session id returned from a completed auth flow |
| `LIBREFM_URLBASE` | string | https://libre.fm/2.0/ | (Optional) The host and path prefix for your Libre.fm instance |
@@ -0,0 +1,8 @@
| Environmental Variable | Type | Default | Description |
| ---------------------- | ------- | ----------------------------- | --------------------------------------------------------- |
| _**`LZ_ID`**_ | string | | A globally unique ID EX `myComponentId` |
| `LZ_NAME` | string | Value of `LZ_ID` | A vanity name EX `My Cool Component` |
| `LZ_ENABLE` | boolean | true | Should this component be used? |
| `LZ_URL` | string | https://api.listenbrainz.org/ | URL for the ListenBrainz server, if not using the default |
| _**`LZ_TOKEN`**_ | string | | User token for the user to scrobble for |
| _**`LZ_USER`**_ | string | | Username of the user to scrobble for |
@@ -0,0 +1,7 @@
| Environmental Variable | Type | Default | Description |
| ---------------------- | ------- | -------------------- | --------------------------------------- |
| _**`MALOJA_ID`**_ | string | | A globally unique ID EX `myComponentId` |
| `MALOJA_NAME` | string | Value of `MALOJA_ID` | A vanity name EX `My Cool Component` |
| `MALOJA_ENABLE` | boolean | true | Should this component be used? |
| _**`MALOJA_URL`**_ | string | | URL for maloja server |
| _**`MALOJA_API_KEY`**_ | string | | API Key for Maloja server |
@@ -0,0 +1,8 @@
| Environmental Variable | Type | Default | Description |
| ---------------------- | ------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| _**`ROCKSKY_ID`**_ | string | | A globally unique ID EX `myComponentId` |
| `ROCKSKY_NAME` | string | Value of `ROCKSKY_ID` | A vanity name EX `My Cool Component` |
| `ROCKSKY_ENABLE` | boolean | true | Should this component be used? |
| `ROCKSKY_KEY` | string | | API Key generated from [API Applications](https://docs.rocksky.app/migrating-from-listenbrainz-to-rocksky-1040189m0) in Rocksky for your account |
| `ROCKSKY_TOKEN` | string | | Access Token generated from https://rocksky.app/access-tokens in Rocksky for your account |
| _**`ROCKSKY_HANDLE`**_ | string | | The **fully-qualified** handle for your ATPRoto/Bluesky account |
@@ -0,0 +1,7 @@
| Environmental Variable | Type | Default | Description |
| ------------------------- | ------- | -------------------- | --------------------------------------------------------------------------------------------- |
| _**`TEALFM_ID`**_ | string | | A globally unique ID EX `myComponentId` |
| `TEALFM_NAME` | string | Value of `TEALFM_ID` | A vanity name EX `My Cool Component` |
| `TEALFM_ENABLE` | boolean | true | Should this component be used? |
| _**`TEALFM_IDENTIFIER`**_ | string | | Identify the account to login as |
| _**`TEALFM_APP_PW`**_ | string | | The [App Password](https://atproto.com/specs/xrpc#app-passwords) you created for your account |
+5 -55
View File
@@ -1,6 +1,7 @@
---
sidebar_position: 2
title: Overview
description: Scrobble Clients
toc_max_heading_level: 4
---
@@ -16,10 +17,10 @@ A **Client** is an application that stores the historical information about what
| [Last.fm](/configuration/clients/lastfm) | ✅ |
| [Libre.fm](/configuration/clients/librefm) | ✅ |
| [Listenbrainz](/configuration/clients/listenbrainz) | ✅ |
| [Koito](/configuration/clients/koito) | |
| [Koito](/configuration/clients/koito) | |
| [Maloja](/configuration/clients/maloja) | ❌ |
| [Rocksky](/configuration/clients/rocksky) | ❌ |
| [Teal.fm](/configuration/clients/tealfm) | |
| [Teal.fm](/configuration/clients/tealfm) | |
## Features
@@ -33,60 +34,9 @@ For each Play, MS fetches (cached) scrobbles from the Client in a time range inc
* Temporal closeness of the Play's timestamp to the existing scrobble's timestamp
* Whether MS detected the Play as a repeat (applicable to Sources that report realtime Player data)
Detailed scoring breakdowns against each existing scrobble are logged at the `TRACE` level.
Detailed scoring breakdowns against each existing scrobble are logged at the `TRACE` logging level or in the [debug data](/help#copy-play-debug-data) for a scrobble.
<details>
<summary>Detailed Explanation</summary>
A **candidate** (to be scrobbled) Play is first transformed using the configured [`compare.candidate` Hook](/configuration/transforms/#hook), if any exists.
Next, MS checks (up to) the last 100 scrobbles *in-memory* scrobbles that it has made. These are *not* from the Client but the actual scrobbles MS made while it has been running. The data from these scrobbles is much richer than what is usually parsed from the Client which makes it easier to detect duplicates from.
If no in-memory scrobble matches then MS starts comparing the candidate against historical scrobbles fetched from an inclusive time range of the candidate's timestamp.
<h3>Matching Title/Artist/Album</h3>
For these string-based values MS uses an [token-order-invariant](https://github.com/FoxxMD/multi-scrobbler/blob/0dfa4c7aad6df98e13aee6d395827665c2414adb/src/backend/utils/StringUtils.ts#L299) method that scores string similarity based on a (token count) weighted average of two [similarity](https://foxxmd.github.io/string-sameness/#md:strategies) algorithms, [Levenshtien Distance](https://en.wikipedia.org/wiki/Levenshtein_distance) and [Dice's Coefficient](https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient). The weighted average ensures that very long strings (Track titles) are scored with a confidence proportional to their length.
<h3>Matching Timestamp</h3>
Timestamps are scored on how temporally close they are. There are four possible scores with decreasing value:
* **Exact** - Timestamps are within 1 second of each other
* **Close** - Timestamps are within `threshold` seconds of each other
* This is determined by the smallest update interval of Source
* Some Sources (subsonic) only update every 60 seconds so this is the smallest "close" value possible. Most are 10 seconds. Signified by `(Needed <10s)` in the breakdown example below.
* **Fuzzy** - One timestamp is within `threshold` seconds of the *end* of the other timestamp
* Sources can set the scrobble timestamp at different times. Some do it when the track is started listening to, some when it ends, some when the *player* stops.
* Where possible, MS knows and keeps track of when this timestamp *should* be, for each Source. If it's not possible then Fuzzy may be allowed.
* **None** - There is no correlation between timestamps
<h3>Scoring and Breakdows</h3>
A candidate Play must score >= 1 to be detected as a duplicate of an existing scrobble.
Each score and a breakdown of the scores for its individual components can be see at the `TRACE` logging level or in the [debug data](/help#copy-play-debug-data) for a scrobble. An example:
```
* Artist: 0.06 * 0.3 = 0.02
* Title: 0.04 * 0.4 = 0.02
* Time: (Exact) 1 * 0.5 = 0.50
* Existing: 19:13:33-04:00 - Candidate: 19:13:33-04:00
* Temporal Sameness: Exact
* Play Diff: 0s (Needed <10s)
* Range Comparison N/A
Score 0.54 => No Match
```
In each component equation, the first number is the similarity (or temporal closeness).
* 0 = no correlation
* 1 = exactly the same
The second number is the *weight* of that component in the final score.
</details>
See the [**Duplicate Scrobble Guidance**](/configuration/duplicates#how-duplicates-are-detected) page to learn how duplicates are detected and how you can ensure your configuration avoids possible duplicates.
### Dead Scrobbles
@@ -557,10 +557,12 @@ Remember, this URL need to be accessible on the public internet.
## Configuration
<Config config="DiscordClientConfig" fileContent={JsonConfig} client name="discord">
| Environmental Variable | Required? | Default | Description |
| :---------------------------- | --------- | ------- | :--------------------------------------------------------------------------------------------------------------- |
| `DISCORD_APPLICATION_ID` | Yes | | Application ID |
| `DISCORD_IPC_LOCATIONS` | No | | A commera-separated list of `host:port` network locations or file paths to unix socket for Discord |
| `DISCORD_ARTWORK` | No | | A boolean indicating if external artwork URLs should be used. Or a comma-separated list of allowed domains |
| `DISCORD_ARTWORK_DEFAULT_URL` | No | | A URL of an image to use as a fallback if the album art URL cannot be used. Or `false` to use discord's default. |
| Environmental Variable | Type | Default | Description |
| :----------------------------- | ----------------- | --------------------- | :--------------------------------------------------------------------------------------------------------------- |
| _**`DISCORD_ID`**_ | string | | A globally unique ID EX `myComponentId` |
| `DISCORD_NAME` | string | Value of `DISCORD_ID` | A vanity name EX `My Cool Component` |
| _**`DISCORD_APPLICATION_ID`**_ | string | | Application ID |
| `DISCORD_IPC_LOCATIONS` | string | | A commera-separated list of `host:port` network locations or file paths to unix socket for Discord |
| `DISCORD_ARTWORK` | boolean | | A boolean indicating if external artwork URLs should be used. Or a comma-separated list of allowed domains |
| `DISCORD_ARTWORK_DEFAULT_URL` | string or boolean | | A URL of an image to use as a fallback if the album art URL cannot be used. Or `false` to use discord's default. |
</Config>
@@ -22,7 +22,7 @@ Check the [main Discord docs page](/configuration/clients/discord) for a compari
<DetailsAdmo type="note" summary="How MS Uses Discord API">
Multi-scrobbler does its upmost to implement, to-spec, [Gateway API communication](https://docs.discord.com/developers/events/gateway) and respect usage. It requires no [permissions](https://docs.discord.com/developers/events/gateway#gateway-intents) and reads no data other than monitoring what other user sessions are active.
Multi-scrobbler does its utmost to implement, to-spec, [Gateway API communication](https://docs.discord.com/developers/events/gateway) and respect usage. It requires no [permissions](https://docs.discord.com/developers/events/gateway#gateway-intents) and reads no data other than monitoring what other user sessions are active.
Aside from the on-paper violation of using a user token programmatically (on your behalf), there is no misuse of the API to achieve rich presence.
@@ -323,12 +323,14 @@ The order in which multi-scrobbler determines what image to use is the same as t
## Configuration
<Config config="DiscordClientConfig" fileContent={JsonConfig} client name="discord">
| Environmental Variable | Required? | Default | Description |
| :--------------------------------- | --------- | ----------------- | :---------------------------------------------------------------------------------------------------------------- |
| `DISCORD_TOKEN` | Yes | | User Token acquired from an active Discord sessions |
| `DISCORD_APPLICATION_ID` | No | | Application ID used to display album art |
| `DISCORD_ARTWORK` | No | | A boolean indicating if external artwork URLs should be used. Or a comma-separated list of allowed domains |
| `DISCORD_ARTWORK_DEFAULT_URL` | No | | A URL of an image to use as a fallback if the album art URL cannot be used. Or `false` to use discord's default. |
| `DISCORD_STATUS_OVERRIDE_ALLOW` | No | `online,idle,dnd` | A comma-separated list of statuses are allowed to have presence. |
| `DISCORD_LISTENING_ACTIVITY_ALLOW` | No | | A commera-seperated list of listening activity names MS **is allowed** to broadcast presence at the same time as. |
| Environmental Variable | Type | Default | Description |
| :--------------------------------- | ----------------- | --------------------- | :---------------------------------------------------------------------------------------------------------------- |
| _**`DISCORD_ID`**_ | string | | A globally unique ID EX `myComponentId` |
| `DISCORD_NAME` | string | Value of `DISCORD_ID` | A vanity name EX `My Cool Component` |
| _**`DISCORD_TOKEN`**_ | string | | User Token acquired from an active Discord sessions |
| `DISCORD_APPLICATION_ID` | string | | Application ID used to display album art |
| `DISCORD_ARTWORK` | boolean | | A boolean indicating if external artwork URLs should be used. Or a comma-separated list of allowed domains |
| `DISCORD_ARTWORK_DEFAULT_URL` | string or boolean | | A URL of an image to use as a fallback if the album art URL cannot be used. Or `false` to use discord's default. |
| `DISCORD_STATUS_OVERRIDE_ALLOW` | string | `online,idle,dnd` | A comma-separated list of statuses are allowed to have presence. |
| `DISCORD_LISTENING_ACTIVITY_ALLOW` | string | | A commera-seperated list of listening activity names MS **is allowed** to broadcast presence at the same time as. |
</Config>
@@ -1,6 +1,7 @@
---
sidebar_position: 2
title: Discord
description: Discord Client Overview
toc_max_heading_level: 4
---
+5 -8
View File
@@ -8,6 +8,7 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
import JsonConfig from '!!raw-loader!@site/../config/koito.json.example';
import ENVConfig from "@site/docs/configuration/clients/_env_configs/_koito.md"
:::tip[Other Uses]
@@ -45,16 +46,12 @@ Using the URL path `/apis/listenbrainz` [Koito docs describe](https://koito.io/g
:::tip
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.
For Koito versions `<=0.1.7`, you must have the environment variable `KOITO_ALLOWED_HOSTS` set correctly in your Koito environment/container. Ensure it is set to the IP address or domain name that you use to access Koito with e.g. `koito.mydomain.com`.
:::
## Configuration
<Config config="KoitoClientConfig" fileContent={JsonConfig} client name="koito">
| Environmental Variable | Required? | Default | Description |
| :--------------------- | --------- | ------- | --------------------------------- |
| `KOITO_TOKEN` | Yes | | API Key from your Koito Account |
| `KOITO_USER` | Yes | | Your Koito username |
| `KOITO_URL` | No | | The base URL for the Koito server |
</Config>
<Config config="KoitoClientConfig" fileContent={JsonConfig} client ambiguous name="koito">
<ENVConfig/>
</Config>
+17 -7
View File
@@ -8,6 +8,7 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
import JsonConfig from '!!raw-loader!@site/../config/lastfm.json.example';
import ENVConfig from "@site/docs/configuration/clients/_env_configs/_lastfm.md"
:::tip[Other Users]
@@ -16,6 +17,20 @@ import JsonConfig from '!!raw-loader!@site/../config/lastfm.json.example';
:::
<DetailsAdmo type="warning" summary="Limits for Old Scrobbles">
Last.fm will not accept scrobbles that are timestamped as **older than 2 weeks** from the current time.
This is not officially documented but has been confirmed by multiple people/mods in the last.fm support forums: [[1]](https://support.last.fm/t/importing-listening-history-spotify-apple-music-and-itunes-wmp/1424/3) [[2]](https://support.last.fm/t/suggestion-allow-last-fm-pro-members-to-use-the-api-to-submit-scrobbles-older-than-2-weeks/69190/2) [[3]](https://support.last.fm/t/how-to-scrobble-complete-spotify-history/44558)
Multi-scrobbler can queue scrobbles for you if the Last.fm service is unavailable, or if there is an issue with a specific scrobble. However, you are responsible for making sure these scrobbles are eventually sent to Last.fm (by retrying in the UI or retarting Last.fm client in UI) before the two week window has passed or else they will not be scrobblable *at all* unless the timestamp is changed.
A future update to Multi-scrobbler will enable editing of unsent scrobble data and modifying the timestamp from the UI.
If you have issues with specific scrobbles not being sent and retries/restarts do not fix it, please make sure to [create a new issue](https://github.com/FoxxMD/multi-scrobbler/issues/new?template=01-bug-report.yml) in a timely manner so that it can be resolved before the window has passed.
</DetailsAdmo>
You will need a [Last.fm](https://www.last.fm) account to use the Last.fm API.
After creating your account, [register for an API account here.](https://www.last.fm/api/account/create)
@@ -48,11 +63,6 @@ The Redirect URL can be explicitly specified in the configuration below, or by u
## Configuration
<Config config="LastfmClientConfig" fileContent={JsonConfig} client name="lastfm">
| Environmental Variable | Required? | Default | Description |
| ---------------------- | --------- | --------------------------------------- | ----------------------------------------------------------------------------- |
| `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. |
<Config config="LastfmClientConfig" fileContent={JsonConfig} client ambiguous name="lastfm">
<ENVConfig/>
</Config>
+6 -12
View File
@@ -8,6 +8,7 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
import JsonConfig from '!!raw-loader!@site/../config/librefm.json.example';
import ENVConfig from "@site/docs/configuration/clients/_env_configs/_librefm.md"
:::tip[Other Users]
@@ -15,9 +16,9 @@ import JsonConfig from '!!raw-loader!@site/../config/librefm.json.example';
:::
To use the official [Libre.fm](https://libre.fm/) instance you will need an account. Or, you can run your own Libre.fm instance [using the legacy source code](https://github.com/foocorp/gnu-fm).
To use the official [Libre.fm](https://libre.fm/) website you will need an account. Or, you can run your own GNU FM instance [using the source code](https://github.com/foocorp/gnu-fm).
No API account, [API Key, or Secret](https://github.com/libre-fm/developer/wiki/Libre.fm-fundamentals#api-keys) is needed to use the Libre.fm API. Instead, use **any values** for API Key/Secret in the configuration. Authentication is done by allowing access from your account on redirect.
No API account, [API Key, or Secret](https://github.com/libre-fm/developer/wiki/Libre.fm-fundamentals#api-keys) is currently needed to use the Libre.fm API. Instead, use **any values** for API Key/Secret in the configuration. Authentication is done by allowing access from your account on redirect.
### Callback/Redirect URL
@@ -53,13 +54,6 @@ https://my.cool.libre.com/2.0/
## Configuration
<Config config="LibrefmClientConfig" fileContent={JsonConfig} client name="librefm">
| Environmental Variable | Required? | Default | Description |
| ---------------------- | --------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `LIBREFM_ENABLE` | No | | Set to `true` to enable Libre.fm, if not using any of the below variables |
| `LIBREFM_API_KEY` | No | `anything` | Optional API Key |
| `LIBREFM_SECRET` | No | `anything` | Optional shared secret |
| `LIBREFM_REDIRECT_URI` | No | `http://localhost:9078/librefm/callback` | Url to use for authentication. Must include `librefm/callback` somewhere in it |
| `LIBREFM_SESSION` | No | | Session id. Will be generated by authentication flow if not provided. |
| `LIBREFM_URLBASE` | No | | Use for custom Libre.fm instance only. The host + path prefix EX `https://my.libre.example/2.0/` |
</Config>
<Config config="LibrefmClientConfig" fileContent={JsonConfig} client ambiguous name="librefm">
<ENVConfig/>
</Config>
@@ -8,6 +8,7 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
import JsonConfig from '!!raw-loader!@site/../config/listenbrainz.json.example';
import ENVConfig from "@site/docs/configuration/clients/_env_configs/_listenbrainz.md"
:::tip[Other Users]
@@ -22,10 +23,6 @@ On your [profile page](https://listenbrainz.org/profile/) find your **User Token
## Configuration
<Config config="ListenBrainzClientConfig" fileContent={JsonConfig} client name="listenbrainz">
| Environmental Variable | Required? | Default | Description |
| ---------------------- | --------- | ----------------------------- | ------------------------------- |
| `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 |
<Config config="ListenBrainzClientConfig" fileContent={JsonConfig} client ambiguous name="listenbrainz">
<ENVConfig/>
</Config>
@@ -8,6 +8,7 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
import JsonConfig from '!!raw-loader!@site/../config/maloja.json.example';
import ENVConfig from "@site/docs/configuration/clients/_env_configs/_maloja.md"
Setup a [Maloja server](https://github.com/krateng/maloja?tab=readme-ov-file#how-to-install) if you have not already done this.
@@ -37,9 +38,6 @@ Finally, add the Maloja server URL and API Key to the configuration type you cho
## Configuration
<Config config="MalojaClientConfig" fileContent={JsonConfig} client name="maloja">
| Environmental Variable | Required? | Default | Description |
|------------------------|-----------|---------|-------------------------------|
| `MALOJA_URL` | Yes | | Base URL of your installation |
| `MALOJA_API_KEY` | Yes | | Api Key |
<Config config="MalojaClientConfig" fileContent={JsonConfig} client ambiguous name="maloja">
<ENVConfig/>
</Config>
+27 -18
View File
@@ -6,6 +6,7 @@ toc_max_heading_level: 5
import Bluesky from "@site/src/components/snippets/_bluesky.mdx";
import JsonConfig from '!!raw-loader!@site/../config/rocksky.json.example';
import ENVConfig from "@site/docs/configuration/clients/_env_configs/_rocksky.md"
:::tip[Other Uses]
@@ -13,7 +14,7 @@ To monitor and re-scrobble activity **from** Rocksky create a [Rocksky (Source)]
:::
[**Rocksky**](https://rocksky.app/) is music tracking and discovery platform built on the AT Protocol (Bluesky).
[**Rocksky**](https://rocksky.app/) is music tracking and discovery platform built on the AT Protocol and your your [**Atmosphere Account**](https://atmosphereaccount.com/).
<details>
@@ -23,13 +24,13 @@ To monitor and re-scrobble activity **from** Rocksky create a [Rocksky (Source)]
</details>
<DetailsAdmo type="warning" summary="Your Data Is Public">
<DetailsAdmo type="important" summary="Your Data Is Public">
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.
</DetailsAdmo>
<DetailsAdmo type="warning" summary="No Scrobbling Guarantee">
<DetailsAdmo type="important" summary="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.
@@ -41,22 +42,16 @@ If you need your scrobbles to be 100% captured then you should scrobble to an *a
## Setup
You will need a [Bluesky](https://bsky.app/) account to use Rocksky.
You will need an [**Atmosphere Account**](https://atmosphereaccount.com/) 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
* Navigate to [rocksky.app](https://rocksky.app/) and sign up/logn using your Atmosphere Account
* Then, navigate to the [Access Tokens](https://rocksky.app/access-tokens) in the Rocksky settings dropdown, and create a new token
## Configuration
### Handle
The handle used with multi-scrobbler should be your **full** ATProto handle, including TLD.
The handle used with multi-scrobbler should be your **full** [**Atmosphere Account**](https://atmosphereaccount.com/) 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`
@@ -64,9 +59,23 @@ The handle used with multi-scrobbler should be your **full** ATProto handle, inc
If you do not include a TLD then multi-scrobbler will automatically append `.bsky.social` to your handle value.
<Config config="RockSkyClientConfig" fileContent={JsonConfig} client name="rocksky">
| Environmental Variable | Required? | Default | Description |
| :--------------------- | --------- | ------- | ----------------------------------------------------------------------------------- |
| `ROCKSKY_HANDLE` | Yes | | Your **full** ATProto handle. For Bluesky account this is like `myUser.bsky.social` |
| `ROCKSKY_KEY` | Yes | | API Key obtained from Rocksky developer dashboard |
<DetailsAdmo type="warning" summary="API Applications Deprecated">
In < 0.14.1, Multi-Scrobber used Rocksky's API Appication "API Key" for communication with a Listenbrainz-compatible API from Rocksky.
Going forward, this should be replaced with the official guidance in the [Setup](#setup) section above: use an **Access Token**. This uses the native Rocksky API which has more functionality.
If your configuration is using either
* (File/AIO) `'key': ...`
* (ENV) `ROCKSKY_KEY`
you should redo the [Setup](#setup) section and modify your configuration to use an Access Token instead.
Keys are now **deprecated** and functionality for them will be removed in a future version of Multi-Scrobbler.
</DetailsAdmo>
<Config config="RockSkyClientConfig" fileContent={JsonConfig} client ambiguous name="rocksky">
<ENVConfig/>
</Config>
+13 -8
View File
@@ -9,6 +9,7 @@ import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
import JsonConfig from '!!raw-loader!@site/../config/tealfm.json.example';
import Bluesky from "@site/src/components/snippets/_bluesky.mdx";
import ENVConfig from "@site/docs/configuration/clients/_env_configs/_tealfm.md"
:::tip[Other Uses]
@@ -16,7 +17,7 @@ To monitor and re-scrobble activity from a ATProto Profile create a [teal.fm (So
:::
[**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).
[**teal.fm**](https://teal.fm/) is a social discovery and browsing app for scrobbles stored in your [**Atmosphere Account**](https://atmosphereaccount.com/).
<details>
@@ -46,6 +47,13 @@ To view your teal.fm scrobble data you can:
## Setup
:::note
Multi-scrobbler will work with *any* Atmosphere Account, not just Bluesky. See [Configuration](#configuration) below for provider-agnostic config.
:::
#### Bluesky
* Create a [Bluesky](https://bsky.app) account, if you don't already have one
@@ -57,10 +65,10 @@ To view your teal.fm scrobble data you can:
### Identifier
The ATPRoto identifier used with multi-scrobbler should be either:
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
* Your **full** [Atmosphere Account](https://atmosphereaccount.com/) ATProto handle, including TLD
If using a handle:
@@ -70,9 +78,6 @@ If using a 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.
<Config config="TealClientConfig" fileContent={JsonConfig} client name="tealfm">
| Environmental Variable | Required? | Default | Description |
| :--------------------- | --------- | ------- | ------------------------------------------------------------------------------------------ |
| `TEALFM_IDENTIFIER` | Yes | | Your **full** ATProto handle or DID. For Bluesky account this is like `myUser.bsky.social` |
| `TEALFM_APP_PW` | Yes | | Bluesky/ATProto network App Password |
<Config config="TealClientConfig" fileContent={JsonConfig} client ambiguous name="tealfm">
<ENVConfig/>
</Config>
+449 -210
View File
@@ -1,6 +1,7 @@
---
sidebar_position: 2
title: Overview
description: Configuration
toc_max_heading_level: 3
---
import Tabs from '@theme/Tabs';
@@ -12,7 +13,7 @@ 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';
import SingleConfig from '!!raw-loader!../../../config/jellyfin.json.example';
:::tip
@@ -28,40 +29,35 @@ Check the [**FAQ**](../FAQ.md) if you have any issues after configuration!
* 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.
**MS will parse configuration from all configuration types.** You can mix and match configurations but it is generally better to stick to one type.
<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.**
MS will parse environmental variables present in the OS/container when it is run.
This method means MS **does not require config files** but it will still create and use data files in the [local](/installation/#config-and-data-directories) or [docker](/installation/?dockerSetting=storage#recommended-settings) data directory.
<details>
<summary>Use ENV-based configuration if...</summary>
* You are the only person for whom MS is scrobbling for
* You have a very simple setup for MS such as one scrobble [Client](/configuration/clients) and one [Source](/configuration/sources) IE Plex -> Maloja
* You are the only person for whom MS is scrobbling for or
* You have a very simple setup for MS such as one scrobble [Client](/configuration/clients) and one [Source](/configuration/sources) IE Jellyfin -> Koito, or
* You are not comfortable editing JSON files
</details>
<details>
<summary>Config Example</summary>
For Docker container...
```shell
docker run -e "SPOTIFY_CLIENT_ID=yourId" -e "SPOTIFY_CLIENT_SECRET=yourSecret" ...
```
For Docker Compose
```yaml title="docker-compose.yml"
services:
multi-scrobbler:
image: foxxmd/multi-scrobbler
environment:
- SPOTIFY_CLIENT_ID=yourId
- SPOTIFY_CLIENT_SECRET=yourSecret
- MALOJA_URL=http://domain.tld:42010
- MALOJA_API_KEY=1234
- JELLYFIN_URL=192.168.0.110:8096
- JELLYFIN_APIKEY=c9fae8756fbf481ebd9c5bb56b
- JELLYFIN_USER=MyUser
- JELLYFIN_ID=myJellyin
# ...
# ...
```
@@ -76,77 +72,114 @@ Check the [**FAQ**](../FAQ.md) if you have any issues after configuration!
<TabItem value="file" label="File">
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.mdx#docker) for how to configure this correctly
* Local installation -> See [Config and Data Directories](/installation/#config-and-data-directories)
* Docker -> `/config` (in the container) -- see the [**Storage** tab in the Docker installation docs](/installation/?dockerSetting=storage#recommended-settings) for how to configure this correctly
<details>
<summary>Use File-based configuration if...</summary>
* You have many [Sources](/configuration/sources)
* 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 are comfortable editing JSON files
* You have more than one of the same type of **Source/Client** you want to use EX 2x Last.fm accounts, 3x Jellyfin accounts
* You want activity from specific **Sources** 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
* Most Source/Clients only support basic configuration through ENV, more configuration is possible using File. AIO enables complete configuration.
</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.
* These are the same examples you will find in the **Configuration** section of each Source/Client
* 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.
Each config file is named after the **type** of the Client/Source. Within each file, a JSON **array** defines all of the instances for that type of Client/Source.
Example directory structure:
```
/CONFIG_DIR
plex.json
spotify.json
maloja.json
jellyfin.json
lastfm.json
koito.json
```
<details>
<summary>Config Example</summary>
<CodeBlock title="CONFIG_DIR/spotify.json" language="json5">{SpotifyConfig}</CodeBlock>
```yaml title="jellyfin.json"
[
{
"name": "Foxx JF Server",
"id": "foxxJf",
"data": {
"url": "http://localhost:8096",
"user": "FoxxMD",
"apiKey": "c9fae8756fbf481ebd9c5bb56bd6540c"
}
}
]
```
</details>
<details>
<summary>Multiple of Same Config Example</summary>
```yaml title="jellyfin.json"
[
{
"name": "Foxx JF Server",
"id": "foxxJf",
"data": {
"url": "http://localhost:8096",
"user": "FoxxMD",
"apiKey": "c9fae8756fbf481ebd9c5bb56bd6540c"
}
},
// highlight-start
{
"name": "Foo's JF Server",
"id": "fooJf",
"data": {
"url": "http://192.168.0.150:8096",
"user": "foo",
"apiKey": "9c5bb56bd6540c756fbf481ebd9c5c40"
}
}
// highlight-end
]
```
</details>
You can find an example of the file name that should be used under the **File** Configuration for each Source/Client. Note: this is the same value that is used for `type` in AIO configs.
</TabItem>
<TabItem value="aio" label="File AIO">
MS will parse an **all-in-one** configuration file located in the directory specified by the `CONFIG_DIR` environmental variable. This variable defaults to:
MS will parse an **all-in-one** configuration file (`config.json`) located in the directory specified by the `CONFIG_DIR` environmental variable. The file's location 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
* Local installation -> See [Config and Data Directories](/installation/#config-and-data-directories)
* Docker -> `/config/config.json` (in the container) -- see the [**Storage** tab in the Docker installation docs](/installation/?dockerSetting=storage#recommended-settings) for how to configure this correctly
<details>
<summary>Use AIO-based configuration if...</summary>
* You have many [Sources](/configuration/sources)
* 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
* ...All of the reasons listed in [File](./?configType=file#configuration-types)
* You want to setup [monitoring/webhooks](#monitoring), default settings for all Sources/Clients, or global config options
* You want to use [Transformers](/configuration/transforms) (other than what is provided for [Musicbrainz ENV](/configuration/transforms/musicbrainz/#env-configuration))
* You want complete control over all potential MS configuration
</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.
* **AIO config is the "complete" way to configure MS.**
* All settings available in File/ENV are available in AIO and AIO has *more* exclusive settings.
* AIO enables configuring **default settings** for all Sources/Clients
* There is a [**kitchensink example**](/configuration/kitchensink) that provides examples of using all sources/clients in a complex configuration.
* Use the [**Config Playground**](/playground) to explore the entire config schema with an editable example
* The example used can also be found in the repo directory at [`/config/config.json.example`](https://github.com/FoxxMD/multi-scrobbler/tree/master/config/config.json.example) in the example below
:::
[**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)
<details>
<summary>Config Example</summary>
@@ -154,6 +187,32 @@ Check the [**FAQ**](../FAQ.md) if you have any issues after configuration!
<CodeBlock title="CONFIG_DIR/config.json" language="json5">{AIOConfig}</CodeBlock>
</details>
<DetailsAdmo type="important" summary="Source/Client AIO Configs require a 'type' property">
Compared to their [File](./?configType=file#configuration-types) equivalent, Sources/Clients configured using AIO require an additional `type` property.
```yaml title="config.json"
{
"sources": [
{
"name": "Foxx JF Server",
// highlight-start
"type: "jellyfin",
// highlight-end
"id": "foxxJf",
"data": {
"url": "http://localhost:8096",
"user": "FoxxMD",
"apiKey": "c9fae8756fbf481ebd9c5bb56bd6540c"
}
}
]
}
```
You can find an example of the required `type` value by referring to the **AIO** tab in each Source/Client's Configuration section example. This is the same value that is used as the name of individual [File](./?configType=file#configuration-types) config files.
</DetailsAdmo>
</TabItem>
</Tabs>
@@ -244,13 +303,10 @@ WARN : [App] [Sources] [spotify Secrets] Matched: None | Unmatched: SPOTIFY_SECR
</DetailsAdmo>
## Application Options
These options affect multi-scrobbler's behavior and are not specific to any source/client.
## Base URL
### 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.
Defines the URL that is used to generate default redirect URLs for authentication for services like [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)
@@ -279,79 +335,51 @@ Useful when running with [docker](../installation/installation.mdx#docker) so th
</details>
### Caching
## Caching
Multi-scrobbler caches some activities to persist important data across restarts, reduce external API calls, and make some actions faster.
Multi-scrobbler implements caching to 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.
A default **in-memory** cache store is used so that you always benefit from some caching. An optional, [**secondary** store](#secondary-caching) can be configured for greater caching capabilities.
<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?dockerSetting=storage#recommended-settings) 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:
<Tabs>
<TabItem value="env" label="ENV">
| Environmental Variable | Required? | Default | Description |
| :--------------------- | --------- | --------- | --------------------- |
| `CACHE_SCROBBLE` | No | `file` | The cache type to use |
| `CACHE_SCROBBLE_CONN` | No | `/config` | |
<DetailsAdmo type="note" summary="What is Cached?">
<Tabs groupId="cachedThings">
<TabItem value="auth" label="Auth Data">
Authentication sessions/tokens/etc... are cached for quicker requests and for persistence across restarts.
</TabItem>
<TabItem value="aio" label="AIO">
```json5 title="config.json"
{
"cache": {
"scrobble": {
"provider": "file",
"connection": "/config"
}
},
// ...
}
```
<TabItem value="transform" label="Scrobble Transforms">
The results of [transform rules](/configuration/transforms) are cached so that if a scrobble with identical data (track/artists/album) is identified and it has the same set of transform rules then the cached transform results can be applied.
</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.
</TabItem>
</Tabs>
</TabItem>
<TabItem value="auth" label="Auth Data">
</DetailsAdmo>
Authentication sessions/tokens/etc... are cached for quicker requests and for persistence across restarts.
<DetailsAdmo type="important" summary="Auth Cache Configuration">
:::tip
Auth caching defaults to a **file** that is stored in the `DATA_DIR` directory using the pre-defined file name `ms-auth.cache`.
By default, this data use a [Secondary](#secondary-caching-configuration) [File](./?cacheType=file#secondary-caching-configuration) store, configured for you automatically.
This provides automatic persistence across restarts for long-lived auth data/credentials if you have configured a [persisted volume/bind mount](/installation?dockerSetting=storage#recommended-settings) for configuration (`/config` is mounted in [docker compose](/quickstart#create-docker-compose-file)).
If you have configured a [persisted volume/bind mount](/installation?dockerSetting=storage#recommended-settings) 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.
If you wish to use the [secondary store](#secondary-caching) for caching Auth you must explicitly configure it. This is because valkey can potentially be *ephemeral* if you do not provide a volume for its data directory.
:::
To explicitly configure auth to use the secondary store:
##### Configuration
Use any [Secondary Cache](#secondary-caching-configuration), the config examples below show the default values:
<Tabs>
<Tabs groupId="configType" queryString>
<TabItem value="env" label="ENV">
| Environmental Variable | Required? | Default | Description |
| :--------------------- | --------- | --------- | --------------------- |
| `CACHE_AUTH` | No | `file` | The cache type to use |
| `CACHE_AUTH_CONN` | No | `/config` | |
```yaml title="compose.yaml"
services:
multi-scrobbler:
# ...
environment:
// highlight-start
- CACHE_AUTH=valkey
// highlight-end
# ...
```
</TabItem>
@@ -361,8 +389,7 @@ Use any [Secondary Cache](#secondary-caching-configuration), the config examples
{
"cache": {
"auth": {
"provider": "file",
"connection": "/config"
"provider": "valkey"
}
},
// ...
@@ -371,93 +398,19 @@ Use any [Secondary Cache](#secondary-caching-configuration), the config examples
</TabItem>
</Tabs>
</TabItem>
<TabItem value="metadata" label="Transform API Calls">
</DetailsAdmo>
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.
### Secondary Caching Store {#secondary-caching}
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).
Using a secondary store enables:
##### Configuration
* persistence of cached data across restarts
* a larger store (more data is saved)
* a longer time-to-live in the store (cached data is fetchable for a longer period)
Use any [Secondary Cache](#secondary-caching-configuration), the config examples below show the default values:
These benefits are particularly beneficial when using [transforms](/configuration/transforms) like Musicbrainz and it is **strongly recommended** for these scenarios.
<Tabs>
<TabItem value="env" label="ENV">
| Environmental Variable | Required? | Default | Description |
| :--------------------- | --------- | ------- | --------------------- |
| `CACHE_METADATA` | No | | The cache type to use |
| `CACHE_METADATA_CONN` | No | | |
</TabItem>
<TabItem value="aio" label="AIO">
```json5 title="config.json"
{
"cache": {
"metadata": {
"provider": "valkey",
"connection": "yourConnectionStringHere"
}
},
// ...
}
```
</TabItem>
</Tabs>
</TabItem>
</Tabs>
#### Secondary Caching Configuration
The type of cache used, and its connection properties, can be configured through **ENV** or **AIO** config.
<Tabs groupId="cacheType" queryString>
<TabItem value="file" label="File">
**File** cache is stored in the `CONFIG_DIR` directory using the pre-defined file name `ms-[cacheName].cache`.
<Tabs groupId="configType" queryString>
<TabItem value="env" label="ENV">
Example
| Environmental Variable | Required? | Default | Description |
| :--------------------- | --------- | --------- | ------------------------------------------------------------ |
| `CACHE_SCROBBLE` | No | `file` | The cache type to use |
| `CACHE_SCROBBLE_CONN` | No | `/config` | The directory, within the container, to store the cache file |
</TabItem>
<TabItem value="aio" label="AIO">
Example
```json5 title="config.json"
{
"cache": {
"scrobble": {
"provider": "file",
"connection": "/config"
}
},
// ...
}
```
</TabItem>
</Tabs>
</TabItem>
<TabItem value="valkey" label="Valkey">
[**Valkey**](https://valkey.io/) is an open-source fork of Redis.
Currently, Multi-scrobbler only supports [**Valkey**](https://valkey.io/), an open-source fork of Redis, as a secondary store.
<details>
@@ -465,11 +418,13 @@ Example
A valkey container can be added to the [multi-scrobbler docker compose stack](/installation?runType=docker-compose#docker):
<Tabs>
<TabItem value="named" label="Named Volume">
```yaml title="docker-compose.yml"
services:
multi-scrobbler:
# ...
# adding everything below
// highlight-start
valkey:
image: valkey/valkey
@@ -482,6 +437,25 @@ volumes:
// highlight-end
```
</TabItem>
<TabItem value="bind" label="Bind Mount">
```yaml title="docker-compose.yml"
services:
multi-scrobbler:
# ...
// highlight-start
valkey:
image: valkey/valkey
volumes:
- ./valkeyData:/data
// highlight-end
```
</TabItem>
</Tabs>
Use `redis://valkey:6379` as the connection string in the configurations below.
</details>
@@ -496,12 +470,16 @@ redis://HOST_IP:HOST_PORT
<TabItem value="env" label="ENV">
Example
| Environmental Variable | Required? | Default | Description |
| :--------------------- | --------- | -------- | ------------------------------------------------------------------- |
| `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` |
```yaml title="compose.yaml"
services:
multi-scrobbler:
# ...
environment:
// highlight-start
- CACHE_VALKEY=redis://192.168.0.120:6379
// highlight-end
# ...
```
</TabItem>
@@ -512,10 +490,7 @@ Example
```json5 title="config.json"
{
"cache": {
"metadata": {
"provider": "valkey",
"connection": "redis://192.168.0.120:6379"
}
"valkey": "redis://192.168.0.120:6379"
},
// ...
}
@@ -524,11 +499,275 @@ Example
</TabItem>
</Tabs>
## Database
Multi-scrobbler depends on a SQLite database (`ms.db`) that is created on first run and stored in the [`CONFIG_DIR`](/installation/?dockerSetting=storage#recommended-settings). When upgrading Multi-scrobbler version, if there are any required database changes than this database is [automatically backed up and migrated.](/updating#database)
The database stores *all* Plays for your Sources/Clients as well as metadata and debugging information to help troubleshoot issues. Each Play is associated with a Source/Config in the database based on your configuration.
You **must set IDs for each Source/Client** so that the database can identify these even when the configuration is changed.
### Retention
<DetailsAdmo status="note" summary="How much storage does the database use?">
The amount of data stored for each Play can widely vary based on a few factors:
* how much data the Source service exposes
* how many clients each Source is scrobble to (Plays are duplicated for each Client a Source sends a scrobble to)
* if you are using any [Transforms](/configuration/transforms) the diff of each step is stored, along with any external request/response data (like [Musicbrainz queries](/configuration/transforms/musicbrainz))
The [MS repository contains a benchmark](https://github.com/FoxxMD/multi-scrobbler/blob/master/src/backend/tests/database/drizzle.test.ts#L551) to measure an average database size in a few common scenarios.
<Tabs groupId="dbSize">
<TabItem label="Minimal" value="minimal">
Assuming your Sources send a minimal amount of data or you have compacted all plays:
| Play Count | DB Size |
| ---------- | -------- |
| 100 | `160kb` |
| 1000 | `1MB` |
| 10k | `10.2MB` |
</TabItem>
<TabItem label="With Original Input" value="input">
Assuming your Sources have a non-trivial amount of input data (like Spotify or Listenbrainz) that is not compacted:
| Play Count | DB Size |
| ---------- | -------- |
| 100 | `356kb` |
| 1000 | `3MB` |
| 10k | `29.6MB` |
</TabItem>
<TabItem label="All Debug Deta" value="all">
Assuming your Sources/Clients:
* have a non-trivial amount of input data (like Spotify or Listenbrainz)
* and has many [Transforms](/configuration/transforms) steps that include requests
* nothing is compacted
| Play Count | DB Size |
| ---------- | -------- |
| 100 | `684kb` |
| 1000 | `6.28MB` |
| 10k | `61.3MB` |
</TabItem>
</Tabs>
### Debug Mode
</DetailsAdmo>
A retention policy can be configured to delete Plays, or unused debug data, from the database after a certain amount of time. If no configuration is provided then a default policy is used that should be reasonable for most users.
<Tabs groupId="policy" queryString>
<TabItem label="Compaction" value="compaction">
The **Compaction** Retention Policy is used to delete different types of debug data from your stored Plays.
This is a useful way to reduce used storage space when you are not having problems with your Plays, or iterating on a configuration, that requires referencing all this extra data.
There are two types of data that can be compacted (deleted from the Play):
* `input` - this is the untouched data retrieved by Multi-scrobbler, from a Source, and used to generate a Play/scrobble. This can be used to reconstruct and replay a Play, when used from troubleshooting or reporting an issue
* `transform` - this is all of the steps generated by [transforms](/configuration/transforms), the diff of the Play resulting from the step, and any request/responses used to complete the step
:::note[Defaults]
When no Compact configuration is provided, Multi-scrobbler uses this policy:
* Compact (delete) `input` and `transform` data on all Plays after 3 days
:::
#### Configuring Compaction Policy
<details>
<summary>Details</summary>
Each value in the configuration properties below can be either
* a number of seconds EX `3600` = 10 minutes
* a unit of a common duration with the pattern `X unit` EX
* `30 minutes`
* `5 hours`
* `2 days`
<Tabs groupId="configType" queryString>
<TabItem value="env" label="ENV">
* `COMPACT_PROPERTIES` - which properties to compact
* `RETENTION_COMPACT_AFTER` - Default to use for all Plays
* `RETENTION_COMPACT_COMPLETED_AFTER` - Compact only completed Plays after...
* `RETENTION_COMPACT_FAILED_AFTER` - Compact only failed Plays after...
* `RETENTION_COMPACT_DUPED_AFTER` - Compact only duped/discard Plays after...
Example
```ini
# only delete input when compacting
COMPACT_PROPERTIES=input
# compact all plays after 3 days
RETENTION_COMPACT_AFTER=3 days
# specifically compact completed plays after 30 minutes
RETENTION_COMPACT_COMPLETED_AFTER=30 minutes
```
</TabItem>
<TabItem value="aio" label="AIO">
Compacting all Play types and deleting both input and transform:
```json title="config.json"
{
"database": {
"retention": {
"compactAfter": "3 days",
"compact": [
"input",
"transform"
]
}
}
}
```
* Delete only input during compacting
* Compact all after 3 days except completed which compacts after 30 minutes
```json title="config.json"
{
"database": {
"retention": {
"compactAfter": {
"completed": "30 minutes",
"duped": "3 days",
"failed": "3 days"
},
"compact": [
"input"
]
}
}
}
```
</TabItem>
</Tabs>
</details>
</TabItem>
<TabItem label="Deletion" value="deletion">
The **Deletion** Retention Policy is used to delete different types of stored Plays from Multi-scrobbler database.
**This does not delete Plays from your Clients.** It's only deleting the "in-flight" data MS used to create the scrobble that was eventually sent to your clients.
:::warning[Plays Should be Ephemeral]
**Multi-Scrobbler is not designed to store Plays/Scrobbles indefinitely.**
It should scale fine for thousands of scrobbles but it not meant to store 10's of thousands of scrobbles forever. It is not a scrobbler server.
You **should** set a reasonable deletion policy so that MS stores less than 1000 scrobbles at a time, ideally less.
:::
:::note[Defaults]
When no Deletion policy configuration is provided, Multi-scrobbler uses this policy:
* Delete all Plays after 7 days
:::
#### Configuring Deletion Policy
<details>
<summary>Details</summary>
Each value in the configuration properties below can be either
* a number of seconds EX `3600` = 10 minutes
* a unit of a common duration with the pattern `X unit` EX
* `30 minutes`
* `5 hours`
* `2 days`
<Tabs groupId="configType" queryString>
<TabItem value="env" label="ENV">
* `RETENTION_DELETE_AFTER` - Default to use for all Plays
* `RETENTION_DELETE_COMPLETED_AFTER` - Delete only completed Plays after...
* `RETENTION_DELETE_FAILED_AFTER` - Delete only failed Plays after...
* `RETENTION_DELETE_DUPED_AFTER` - Delete only duped/discard Plays after...
Example
```ini
# delete all plays after 3 days
RETENTION_DELETE_AFTER=3 days
# specifically, delete completed plays after 30 minutes
RETENTIOND_DELETE_COMPLETED_AFTER=30 minutes
```
</TabItem>
<TabItem value="aio" label="AIO">
Deleting all Play types after 3 days:
```json title="config.json"
{
"database": {
"retention": {
"deleteAfter": "3 days"
}
}
}
```
* Delete all after 3 days except completed which are deleted after 30 minutes
```json title="config.json"
{
"database": {
"retention": {
"deleteAfter": {
"completed": "30 minutes",
"duped": "3 days",
"failed": "3 days"
},
}
}
}
```
</TabItem>
</Tabs>
</details>
</TabItem>
</Tabs>
## Debug Mode
Turning on Debug Mode will
@@ -549,7 +788,7 @@ To set debug mode either add it to [AIO `config.json`](./?configType=aio#configu
or set the [ENV](./?configType=env#configuration-types) `DEBUG_MODE=true`
### Disable Web
## 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.
@@ -629,7 +868,7 @@ Webhooks are configured in the AIO [config.json](#configuration-types) file unde
#### [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)
Refer to the [config schema for GotifyConfig](/playground)
multi-scrobbler optionally supports setting message notification priority via `info` `warn` and `error` mappings.
@@ -655,7 +894,7 @@ multi-scrobbler optionally supports setting message notification priority via `i
#### [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)
Refer to the [config schema for NtfyConfig](/playground)
multi-scrobbler optionally supports setting message notification priority via `info` `warn` and `error` mappings.
@@ -683,7 +922,7 @@ multi-scrobbler optionally supports setting message notification priority via `i
#### [Apprise](https://github.com/caronc/apprise-api)
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)
Refer to the [config schema for AppriseConfig](/playground)
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)/
+267
View File
@@ -0,0 +1,267 @@
---
sidebar_position: 4
title: Duplicate Scrobble Guidance
description: Common Scenarios and Solutions for Duplicate Scrobbles
---
Multi-scrobbler's flexibility in configuration allows you to build some complex pipelines for scrobbling but it can also be a footgun for duplicating scrobbles.
## How Duplicates Are Detected
Multi-scrobbler uses multiple techniques for evaluating the **similarity** of text in scrobbles against existing data.
These techniques are used on all text from a scrobble (title, artist, album...):
<details>
<summary>Character and string normalization</summary>
Symbols and multiple whitespaces are removed. All characters are converted to lowercase. Non ascii characters are converted to their ascii counterparts.
```
this string! is the. same => this string is the same
this string is the same => this string is the same
ThIs STRING iS THe SAMe => this string is the same
Dina Ögon => Dina Ogon
Nanã => Nana
```
</details>
<details>
<summary>Word-order invariant text similarity</summary>
Strings with multiple words (tokens) are re-arranged so that tokens are ordered to be as close to other, similar tokens are possible:
Example 1
```
String A: there change is => there change is
String B: there is change => there change is
```
Example 2
```
String A: you, doin hwat are => you doin hwat are
String B: what are you doing => you doing what are
```
Then, the strings are independently compared using [Levenshtein Distance](https://en.wikipedia.org/wiki/Levenshtein_distance) and [Dice's Coefficient](https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient). The algorithm that gives the highest score for similarity is used so that matching is aggressive.
</details>
For **Artists**, Multi-scrobbler attempts to extract unique Artists from common patterns in **single-string** artist values and track **titles**.
<details>
<summary>Matching Common Artist Patterns</summary>
Common joiners and patterns are detected in artist/track strings and the artists are extracted. Example of common joiners are: `ft.` `feat` `vs.` etc...
Example 1
```
Melendi, Ryan Lewis, The Righteous Brothers (featuring Joan Jett & The Blackhearts, Robin Schulz)
becomes
Melendi, Ryan Lewis, The Righteous Brothers, Joan Jett, The Blackhearts, Robin Schulz
```
Example 2
```
Childish Gambino - 12.38 (feat. 21 Savage, Ink & Kadhja Bonet)
becomes
Childish Gambino, 21 Savage, Ink, Kadhja Bonet - 12.38
```
:::note
* MS uses "smart" heuristics to prevent artists with actual punctuation (`Tyler, The Creator`) from accidentally being separated
* The extraction examples above are done **only for matching** other scrobble data (which is also parsed like the above), the resulting scrobble data is never transformed unless the user explicitly configures it via a [native transform](/configuration/transforms/native)
:::
</details>
The **timestamps** for Scrobbles are compared at different levels of granularity based on where they originate from.
<details>
<summary>Temporally Comparing Scrobbles</summary>
Timestamp closeness for two scrobbles can be classified in one of these states:
**Exact**
Timestamps differ by 1 seconds or less.
**Close**
Timestamps differ by less than the known granularity of the Source they are from. For most Sources (and all Clients) this is 10 seconds or less. For some sources (subsonic) the granularity is 60 seconds because they only report "now playing" changes every 60 seconds.
**During**
If the Play was monitoring in real-time by Multi-scrobbler then it knows the time range during which the Play was listened to. If an existing scrobble with similar artist/track/album has a timestamp that occurred *during* the known listening time then it is classified as during.
**Fuzzy**
If the timestamp occurs within a few seconds +/- of (existing scrobble + track duration) then, potentially, the Source timestamped the existing scrobble at the *end* of when the user listened to it, rather than when the user *started* listening to it. This is common for Spotify plays.
___
Each of the above classification results in a different score that is *roughly* descending from the order given above. Exact score is based on the Souce of the scrobble and if it was monitored in real-time by Multi-Scrobbler.
</details>
Finally, all of the above comparison/normalization techniques are combined when comparing a "new" Play/Scrobble against existing data. Each comparison is given a weighted score. If the score is above the match threshold then it is marked as a duplicated.
<details>
<summary>Match Scoring Example</summary>
```
Frédéric Chopin - Ballade No. 4 in F Minor, Op. 52 @ 2026-05-04T13:01:51-04:00 <-- New scrobble
Frédéric Chopin / Krystian Zimerman - Ballade No. 4 in F Minor, Op. 52 @ 2026-05-04T13:01:48-04:00 <-- Existing scrobble from client
```
After comparing against all existing scrobbles from the client, in a timerange around the new scrobble, MS finds a match and logs the match along with the score breakdown of the consistuent parts:
```
Source:
Frédéric Chopin - Ballade No. 4 in F Minor, Op. 52 @ 2026-05-04T13:01:51-04:00 (S)
Closest Scrobble:
Frédéric Chopin / Krystian Zimerman - Ballade No. 4 in F Minor, Op. 52 @ 2026-05-04T13:01:48-04:00 (S)
Artist: (0.30 + Whole Match Bonus 0.52) * (0.3 + Whole Match Bonus 0.05) = 0.29
Title: 1.00 * 0.4 = 0.40
Time: (Close) 1 * 0.5 = 0.50
Time Detail => Existing: 13:01:48-04:00 - Candidate: 13:01:51-04:00 | Temporal Sameness: Close | Play Diff: 3s (Needed <10s) | Range Comparison N/A
Score 1.19 => Matched!
```
</details>
All of the above techniques and scoring gives Multi-scrobbler a *very good chance* of detecting duplicate scrobbles, regardless of where they were originally from. Of Multi-Scrobbler's [400+ unit tests](https://github.com/FoxxMD/multi-scrobbler/tree/master/src/backend/tests), 70+ are related to testing scoring, artist extraction, and [fuzzing](https://en.wikipedia.org/wiki/Fuzzing) string normalization/simiarlity.
The dupe detection is so good that it is virtually gauranteed for [most configurations](#valid-configs) and is used for *backlogging* missed scrobbles for Sources that support fetching history.
**However**, it's not possible to gaurantee this for *all* possible Multi-scrobbler configurations. See the below sections to learn more.
## Configurations Where Duplicates DO NOT Occur {#valid-configs}
* Any Source/Client configuration where there is only one Source and one Client and they are not the same service/account
* Any Source/Client configuration where scrobbles only flow in **one direction**
* IE you *do not* have a Last.fm **Client** Account A that is *also* a Last.fm **Source** Account A
:::tip
**The majority of simple Multi-scrobbler configurations fall into these categories. If your workflow is straightforward you do not need to worry about duplicates at all.**
:::
Multi-scrobbler can handle recieving the same data from a Source multiple times. This is fine because the data is *always the same* and *always unique* to each Source. Since MS scrobbled the data to your clients you can be pretty confident that the *same* cleanup/matching process for that data will result in an indentical/similar match to the existing scrobble.
IE Same inputs in -> same inputs out.
**Even this scenario with multiple, mixed Sources is OK:**
<img className='image-center' src={require('/img/diagrams/unidirectional-flow.png').default}/>
Because the scrobbles coming from each **Source** are **independent** of each other. And they end up in Clients that are not *also* used as Sources.
:::tip
This is a common scenario when scrobbles are *backlogged* from a Source IE on startup MS fetches the last 50 Plays from Spotify/Lastfm/Listenbrainz.
:::
## Configurations Where Duplicates CAN Occur {#invalid-configs}
### Upstream Scrobbles Sent to Multiple Sources
In this scenario your device using Spotify is configured in MS as a Spotify **Source** but it **also** is scrobbling to Last.fm (via Pano Scrobbler, as an example).
<img className='image-center' src={require('/img/diagrams/dupe sources.png').default}/>
❌ **Time Race Condition**: Potentially, the scrobbles arrive in Multi-scrobbler at exactly the same time. There are no existing scrobbles in Koito to check against, yet. So Multi-scrobbler scrobbles both to Koito, resulting in duplicate scrobbles.
❌ **Different Scrobble Data**: Pano Scrobbler, or Last.fm, [may modify the scrobble data.](#lastfm) When MS recieves the scrobble from Last.fm it may be *just different enough* that it does not get detected as a duplicate.
### Sources and Clients are Dependent {#dependent-source-client}
In this scenario, you have a **Client** that is also a **Source** and that Source is feeding scrobbles to a different Client that receives scrobbles from everywhere (or has some overlap with the original Client).
<img className='image-center' src={require('/img/diagrams/duped client source.png').default}/>
❌ **Different Scrobble Data**: [Last.fm may modify the scrobble data.](#lastfm) When MS recieves the scrobble from Last.fm it may be *just different enough* that it does not get detected as a duplicate.
❌ **Consistency Issues**: If the Koito Client has a backed up queue or is not currently scrobbling (imagine it was Last.fm or Listenbrainz, and they had upstream availability issues), then there is no existing scrobble yet and nothing to match against.
## Configuration Solutions to Avoid Duplicates
### Independent Sources
Use a configuration mentioned in [Configurations Where Duplicates DO NOT Occur](#valid-configs): make sure all of your Sources are **independent**. The activity/scrobbles from each Source do not have any upstream duplicates of activity.
<img className='image-center' src={require('/img/diagrams/independent source.png').default}/>
### Alternative Sources
Imagine you have a scenario where a device/player can **only** scrobble to Last.fm. But you want to use MS for all other devices/sources and still have all your scrobbles end up in your "main" Last.fm account for social reasons/stats.
Instead of using the same Last.fm account for all activity and potentially having [dependent client/sources](#dependent-source-client), create an *additional* last.fm account that is used *only for the device that needs it*. This ensures the scrobbles from that device are isolated from the main account and do not prevent duplicates, so you can continue to use the main account as an MS Client:
<img className='image-center' src={require('/img/diagrams/alt source.png').default}/>
### Mirror-Only Clients or Limiting Scrobble Destination {#isolated-sources}
In Multi-scrobbler, by default, all Sources scrobble to all Clients. Using [Limit Scrobble Destinations](/configuration/sources/#limiting-scrobble-destination) configuration can ensure that your dependent Source/Clients only scrobble to destinations where the scrobbles won't be duplicated. And/or limit other Sources to *not* scrobble to dependent Sources/Clients.
Here is an example where all Sources only scrobble to a *dependent* Last.fm Client, and then the *dependent* Last.fm Source only scrobbles to a "mirror-only" (backup) Koito Client:
<img className='image-center' src={require('/img/diagrams/mirror source.png').default}/>
### Normalize All Scrobbles
If using one of the above solutions is not possible you can also try to *aggressively normalize all scrobble data* using [the Musicbrainz transform stage](/configuration/transforms/musicbrainz).
In this scenario you still have [dependent Source/Clients](#dependent-source-client) that are [not isolated](#isolated-sources) but you can *potentially* improve duplicate detection by first using a transform stage to match scrobbles against the Musibrainz database and normalize their track/artist/album data.
:::warning
**There is no gaurantee this will work** but it is better than nothing. You should attempt to re-configure MS to use any of the other solutions before using this one.
:::
<img className='image-center' src={require('/img/diagrams/normalized dupes.png').default}/>
## The Last.fm Problem {#lastfm}
Last.fm will happily accept almost any scrobble data you throw at it.
It can then decide, opaquely, to match or modify that scrobble to some more "official" data it has server-side. This could be modifying the artists, title, or album sent in the scrobble. It can also mean modifying the [`mbid` (unique identifier)](https://musicbrainz.org/doc/MusicBrainz_Identifier) or adding what it _thinks_ is the correct one.
There is nothing in the scrobble history API response from Last.fm that identifies it has done any of this. And [it's not super great at being right about these modifications](https://github.com/FoxxMD/multi-scrobbler/issues/333#issuecomment-3145161339).
What this means for you, the user, is that this is very plausible scenario:
* You scrobble `Artist Foo, Artist Bar - My Cool Track` to Last.fm via Pano Scrobbler (outside of Multi-scrobbler)
* Last.fm modifies the data to `The Artist Foo - My Cool Track ft. Artist Bar (Album Version)`
* You _also_ scrobble `Artist Foo, Artist Bar - My Cool Track` to Multi-scrobbler via Pano LFM endpoint **Source**
* MS forwards this to Koito Client as-is
* Soon after, MS polls your Last.fm **Source** for new scrobbles and finds `The Artist Foo - My Cool Track ft. Artist Bar (Album Version)`
* It queries Koito to see if this was scrobbled
* It sees that a scrobble did happen at almost the same time BUT the track titles and artist strings are _just different enough_ that it decides it's not a duplicate
* MS forwards the scrobble to Koito and you now have a duplicate
This behavior is unique to Last.fm. It's truly the **worst** out of all the scrobbler services in terms of preserving your data as-is, or providing a unqiue identifier for your data.
+76 -299
View File
@@ -1,316 +1,93 @@
---
sidebar_position: 3
sidebar_position: 5
title: Kitchen Sink
description: Kitchen Sink Examples
---
# Example Complex Scenario
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import MultiUsecase from "@site/src/components/snippets/_scenario-config-multi.mdx"
import MultiPersonsServices from "@site/docs/configuration/_kitchensink_configs/_multi-persons-services.mdx"
import MultiSourcesClients from "@site/docs/configuration/_kitchensink_configs/_multi-sources-clients.mdx"
import Musicbrainz from "@site/docs/configuration/_kitchensink_configs/_musicbrainz.mdx"
import SingleUsecase from "@site/src/components/snippets/_scenario-config-single.mdx"
Scenario:
## What's This?
* You want to scrobble plays for yourself (Foxx), Fred, and Mary
* Each person has their own [Maloja](/configuration/clients/maloja) server
* Each person has their own Spotify account
* You have your own Airsonic ([subsonic](/configuration/sources/subsonic)) server you to scrobble from
* You have your own [Youtube Music](/configuration/sources/youtube-music) 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](/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](/configuration/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)
On this page you find contrived, complex usecases for multi-scrobbler that provide examples of equivalent [configuration types](/configuration#configuration-types) to fulfill that usecase.
### All-in-one Config
These example are useful for:
```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
"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": {
"token": "JtRnwQWD__XMJF8sT3jc",
"url": "192.168.0.233:32400",
"usersallow": ["fredUser"]
}
},
{
"type": "plex",
"name": "maryPlex",
"clients": ["maryMaloja"],
"data": {
"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"]
}
},
{
"type": "plex",
"name": "partyPlex",
// omitting clients (or making it empty) will make this Source scrobble to all Clients
"data": {
"token": "JtRnwQWD__XMJF8sT3jc",
"url": "192.168.0.233:32400",
"librariesAllow": ["party"]
}
},
{
"type": "jellyfin",
"name": "FredJelly",
// omitting clients (or making it empty) will make this Source scrobble to all Clients
"data": {
"url": "http://localhost:8096",
"user": "FoxxMD",
"apiKey": "c9fae8756fbf481ebd9c5bb56bd6540c",
"usersAllow": ["fredUser"]
}
},
{
"type": "subsonic",
"name": "foxxAirsonic",
"clients": ["foxxMaloja"],
"data": {
"user": "foxx",
"password": "foxxPassword",
"url": "https://airsonic.foxx.example"
}
},
{
"type": "ytmusic",
"name": "foxxYoutube",
"clients": ["foxxMaloja"],
"data": {
"cookie": "__Secure-3PAPISID=3AxsXpy0MKGu75Qb/AkISXGqOnSDn1jEKn; DEVICE_INFO=ChxOekU0Tmpjek5EWTBPRGd3TlRBMk16QXpNdz09EJbS8Z0GGJbS8Z0G; ...",
}
},
{
"type": "endpointlz",
"name": "listenbrainzfoxx",
"clients": ["foxxMaloja"],
"data": {
"token": "myToken"
}
}
],
"clients": [
{
"type": "maloja",
"name": "foxxMaloja",
"data": {
"url": "https://maloja.foxx.example",
"apiKey": "foxxApiKey"
}
},
{
"type": "maloja",
"name": "fredMaloja",
"data": {
"url": "https://maloja.fred.example",
"apiKey": "fredApiKey"
}
},
{
"type": "maloja",
"name": "maryMaloja",
"data": {
"url": "https://maloja.mary.example",
"apiKey": "maryApiKey"
}
},
{
"type": "lastfm",
"name": "maryLFM",
"data": {
"apiKey": "maryApiKey",
"secret": "marySecret",
"redirectUri": "http://localhost:9078/lastfm/callback"
}
}
]
}
* getting an idea of what you can do with Multi-scrobbler
* using these examples as a starting point for your own configuration
* providing an example of the same configuration across different [configuration types](/configuration#configuration-types)
```
:::tip
### Separate JSON files
More common, straightforward scenarios can be found in the [**quickstart**](/quickstart) guide.
```json5 title="CONFIG_DIR/spotify.json"
[
{
// may omit 'type' property since app knows this is file is for spotify configs
"name": "foxxSpot",
"clients": ["foxxMaloja"],
"data": {
"clientId": "foxxSpotifyAppId",
"clientSecret": "foxxSpotifyAppSecret"
}
},
{
"name": "marySpot",
"clients": ["maryMaloja"],
"data": {
"clientId": "foxxSpotifyAppId",
"clientSecret": "foxxSpotifyAppSecret"
}
},
{
"name": "fredSpot",
"clients": ["fredMaloja"],
"data": {
"accessToken": "fredsToken",
"refreshToken": "fredsRefreshToken",
"interval": 120
}
},
]
```
:::
```json5 title="CONFIG_DIR/plex.json"
[
{
"name": "fredPlex",
"clients": ["fredMaloja"],
"data": {
"token": "JtRnwQWD__XMJF8sT3jc",
"url": "192.168.0.233:32400",
"usersallow": ["fredUser"]
}
},
{
"name": "maryPlex",
"clients": ["maryMaloja"],
"data": {
"token": "JtRnwQWD__XMJF8sT3jc",
"url": "192.168.0.233:32400",
"usersallow": ["maryUser"],
"librariesAllow": ["podcasts"]
}
},
{
"name": "partyPlex",
"data": {
"token": "JtRnwQWD__XMJF8sT3jc",
"url": "192.168.0.233:32400",
"librariesAllow": ["party"]
}
}
]
```
## Examples
```json5 title="CONFIG_DIR/jellyfin.json"
[
{
"name": "FredJelly",
"data": {
"url": "http://localhost:8096",
"user": "FoxxMD",
"apiKey": "c9fae8756fbf481ebd9c5bb56bd6540c",
"usersAllow": ["fredUser"]
}
}
]
```
### Multiple Sources to Multiple Clients {#multi-sources-clients}
```json5 title="CONFIG_DIR/ytmusic.json"
[
{
"name": "foxxYoutube",
"clients": ["foxxMaloja"],
"data": {
"cookie": "__Secure-3PAPISID=3AxsXpy0MKGu75Qb/AkISXGqOnSDn1jEKn; DEVICE_INFO=ChxOekU0Tmpjek5EWTBPRGd3TlRBMk16QXpNdz09EJbS8Z0GGJbS8Z0G; ...",
}
}
]
```
```json5 title="CONFIG_DIR/endpointlz.json"
[
{
"name": "listenbrainzfoxx",
"clients": ["foxxMaloja"],
"data": {
"token": "myToken",
}
}
]
```
You are the only user of Multi-scrobbler. You have many Sources you listen to and want to scrobble them to multiple services.
```json5 title="CONFIG_DIR/maloja.json"
[
{
"name": "foxxMaloja",
"data": {
"url": "https://maloja.foxx.example",
"apiKey": "foxxApiKey"
}
},
{
"name": "fredMaloja",
"data": {
"url": "https://maloja.fred.example",
"apiKey": "fredApiKey"
}
},
{
"name": "maryMaloja",
"data": {
"url": "https://maloja.mary.example",
"apiKey": "maryApiKey"
}
}
]
```
* You want to **scrobble from...**
* A [Spotify](/configuration/sources/spotify) account
* A Navidrome instance that you want to use with the built-in [listenbrainz](/configuration/sources/listenbrainz-endpoint) scrobble function
* A shared [Plex](/configuration/sources/plex) instance that you want to scrobble only from a specific library named `oldies`
* You want to **scrobble to...**
* Your decades-old [Last.fm](/configuration/clients/lastfm) account
* A [Koito](/configuration/clients/koito) server for backup
```json5 title="CONFIG_DIR/lastfm.json"
[
{
"name": "maryLFM",
"data": {
"apiKey": "maryApiKey",
"secret": "marySecret",
"redirectUri": "http://localhost:9078/lastfm/callback"
}
}
]
```
<details>
<summary>Configuration</summary>
<MultiSourcesClients/>
</details>
### Scrobble for Multiple People to Multiple Clients {#multi-persons-services}
<MultiUsecase/>
You want to scrobble plays for yourself (Foxx), Fred, and Mary:
* The three of you want to **scrobble from...**
* Fred and Mary each have their own [Jellyfin servers](/configuration/sources/jellyfin)
* You have a [Plex](/configuration/sources/plex) server
* The three of you want to **scrobble to...**
* Fred and Mary each have their own [Last.fm](/configuration/clients/lastfm) accounts
* You have a [Koito](/configuration/clients/koito) server
<details>
<summary>Configuration</summary>
<MultiPersonsServices/>
</details>
### Use Musicbrainz to Fix Scrobbles from Navidrome
You have multiple Sources of music but your personal, on-filesystem music collection is not well organized.
You want to use the [Musicbrainz Transformer](/configuration/transforms/musicbrainz) to fix any scrobbles that use Navidrome, where your personal collection is located, so that the scrobble artists/album/title are all standardized.
* You want to **scrobble from...**
* A [Spotify](/configuration/sources/spotify) account
* A Navidrome instance that you want to use with the built-in [listenbrainz](/configuration/sources/listenbrainz-endpoint) scrobble function
* Plays from this Source should be fixed by Musicbrainz before going to your scrobble Clients
* You want to **scrobble to...**
* A [Last.fm](/configuration/clients/lastfm) account
<details>
<summary>Configuration</summary>
<Musicbrainz/>
</details>
@@ -0,0 +1,13 @@
| Environmental Variable | Type | Default | Description |
| ------------------------------------------ | ------- | ------------------------ | -------------------------------------------------------------------- |
| _**`APPLEMUSIC_ID`**_ | string | | A globally unique ID EX `myComponentId` |
| `APPLEMUSIC_NAME` | string | Value of `APPLEMUSIC_ID` | A vanity name EX `My Cool Component` |
| `APPLEMUSIC_ENABLE` | boolean | true | Should this component be used? |
| `APPLEMUSIC_KEY_ID` | string | | |
| `APPLEMUSIC_TEAM_ID` | string | | |
| `APPLEMUSIC_KEY_P8` | string | | |
| _**`APPLEMUSIC_MEDIA_USER_TOKEN`**_ | string | | |
| `APPLEMUSIC_TOKEN` | string | | |
| `APPLEMUSIC_ORIGIN_HEADER` | string | | Origin header to include in every Apple Music API request. |
| `APPLEMUSIC_RECOVER_UNCHANGED_TOP_HISTORY` | boolean | true | Fixes a quirk where Apple Music's history API hides duplicate plays. |
| `APPLEMUSIC_NORMALIZE_ALBUM` | boolean | true | Removes extraneous suffixes from album data |
@@ -0,0 +1,10 @@
| Environmental Variable | Type | Default | Description |
| ---------------------- | ------- | ------------------- | ------------------------------------------------- |
| _**`AZURA_ID`**_ | string | | A globally unique ID EX `myComponentId` |
| `AZURA_NAME` | string | Value of `AZURA_ID` | A vanity name EX `My Cool Component` |
| `AZURA_ENABLE` | boolean | true | Should this component be used? |
| _**`AZURA_STATION`**_ | string | | The specific station to monitor |
| _**`AZURA_URL`**_ | string | | Base URL of the Azuracast instance |
| `AZURA_KEY` | string | | API Key used to access data about private streams |
| `AZURA_LISTENERS_NUM` | string | | |
| `AZURA_LIVE` | boolean | | |
@@ -0,0 +1,9 @@
| Environmental Variable | Type | Default | Description |
| ---------------------- | ------- | ---------------- | ----------------------------------------------------------------------------------------------- |
| _**`CC_ID`**_ | string | | A globally unique ID EX `myComponentId` |
| `CC_NAME` | string | Value of `CC_ID` | A vanity name EX `My Cool Component` |
| `CC_ENABLE` | boolean | true | Should this component be used? |
| `CC_BLACKLIST_DEVICES` | string | | DO NOT scrobble from any cast devices on this comma-delimited list that START WITH these values |
| `CC_WHITELIST_DEVICES` | string | | ONLY scrobble from any cast device on this comma-delimited list that START WITH these values |
| `CC_BLACKLIST_APPS` | string | | DO NOT scrobble from any application on this comma-delimited list that START WITH these values |
| `CC_WHITELIST_APPS` | string | | ONLY scrobble from any application on this comma-delimited list that START WITH these values |
@@ -0,0 +1,8 @@
| Environmental Variable | Type | Default | Description |
| ---------------------- | ------- | -------------------------------------------------------------------- | -------------------------------------------------- |
| _**`DEEZER_ID`**_ | string | | A globally unique ID EX `myComponentId` |
| `DEEZER_NAME` | string | Value of `DEEZER_ID` | A vanity name EX `My Cool Component` |
| `DEEZER_ENABLE` | boolean | true | Should this component be used? |
| _**`DEEZER_ARL`**_ | string | | ARL retrieved from Deezer response header |
| `DEEZER_ACCOUNT_ID` | string | | The ID (USER_ID) of the linked account to monitor. |
| `DEEZER_USER_AGENT` | string | Mozilla/5.0 (X11; Linux i686; rv:135.0) Gecko/20100101 Firefox/135.0 | User agent |
@@ -0,0 +1,8 @@
| Environmental Variable | Type | Default | Description |
| ---------------------- | ------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| _**`LFM_ID`**_ | string | | A globally unique ID EX `myComponentId` |
| `LFM_NAME` | string | Value of `LFM_ID` | A vanity name EX `My Cool Component` |
| `LFM_ENABLE` | boolean | true | Should this component be used? |
| `LFM_SLUG` | string | | When using **multiple sources without apikey/user**, or **not** using the standard base URL, this is the URL base path that should be used to identify scrobbles for this source |
| `LFM_USERNAME` | string | | A fake username to differentiate LFM Endpoint Sources |
| `LFM_API_KEY` | string | | A fake api key to differentiate LFM Endpoint Sources |
@@ -0,0 +1,8 @@
| Environmental Variable | Type | Default | Description |
| ---------------------- | ------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| _**`LZE_ID`**_ | string | | A globally unique ID EX `myComponentId` |
| `LZE_NAME` | string | Value of `LZE_ID` | A vanity name EX `My Cool Component` |
| `LZE_ENABLE` | boolean | true | Should this component be used? |
| `LZE_SLUG` | string | | When using **multiple sources without tokens**, or **not** using a standard base URL, this is the URL base path that should be used to identify scrobbles for this source |
| `LZE_TOKEN` | string | | If an LZ submission request contains this token in the Authorization Header it will be used to match the submission with this Source |
| `LZE_USERNAME` | string | | The listenbrainz "username" to associate with this Source |
@@ -0,0 +1,7 @@
| Environmental Variable | Type | Default | Description |
| ---------------------- | ------- | --------------------- | --------------------------------------- |
| _**`ICECAST_ID`**_ | string | | A globally unique ID EX `myComponentId` |
| `ICECAST_NAME` | string | Value of `ICECAST_ID` | A vanity name EX `My Cool Component` |
| `ICECAST_ENABLE` | boolean | true | Should this component be used? |
| _**`ICECAST_URL`**_ | string | | The Icecast stream URL |
| `ICECAST_AUTO_MONITOR` | boolean | | |
@@ -0,0 +1,17 @@
| Environmental Variable | Type | Default | Description |
| -------------------------------- | -------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| _**`JELLYFIN_ID`**_ | string | | A globally unique ID EX `myComponentId` |
| `JELLYFIN_NAME` | string | Value of `JELLYFIN_ID` | A vanity name EX `My Cool Component` |
| `JELLYFIN_ENABLE` | boolean | true | Should this component be used? |
| _**`JELLYFIN_USER`**_ | string | | The username of the user to authenticate for or track scrobbles for |
| `JELLYFIN_PASSWORD` | string | | Password of the username to authenticate for |
| `JELLYFIN_APIKEY` | string | | API Key to authenticate with. |
| _**`JELLYFIN_URL`**_ | string | | HOST:PORT of the Jellyfin server to connect to |
| `JELLYFIN_USERS_ALLOW` | string OR true | | Only scrobble for specific users from this comma-delimited list. If `true`, scrobble for all users. |
| `JELLYFIN_USERS_BLOCK` | string | | Do not scrobble for users from this comma-delimited list |
| `JELLYFIN_DEVICES_ALLOW` | string | | Only scrobble if device or application name contains strings from this comma-delimited list |
| `JELLYFIN_DEVICES_BLOCK` | string | | Do not scrobble if device or application name contains strings from this comma-delimited list |
| `JELLYFIN_LIBRARIES_ALLOW` | string | | Only scrobble if library name contains string from this comma-delimited list |
| `JELLYFIN_LIBRARIES_BLOCK` | string | | Do not scrobble if library name contains strings from this comma-delimited list |
| `JELLYFIN_FRONTEND_URL_OVERRIDE` | string | | HOST:PORT of the Jellyfin server that your browser will be able to access from the frontend (and thus load images and links from) |
| `JELLYFIN_MEDIATYPES_ALLOW` | string | | Allow media types from this comma-delimited list to be scrobbled |
@@ -0,0 +1,8 @@
| Environmental Variable | Type | Default | Description |
| ---------------------- | ------- | ------------------------------- | -------------------------------------------------------- |
| _**`JRIVER_ID`**_ | string | | A globally unique ID EX `myComponentId` |
| `JRIVER_NAME` | string | Value of `JRIVER_ID` | A vanity name EX `My Cool Component` |
| `JRIVER_ENABLE` | boolean | true | Should this component be used? |
| _**`JRIVER_URL`**_ | string | http://localhost:52199/MCWS/v1/ | URL of the JRiver HTTP server to connect to |
| `JRIVER_USER` | string | | If you have enabled authentication, the username you set |
| `JRIVER_PASSWORD` | string | | If you have enabled authentication, the password you set |
@@ -0,0 +1,8 @@
| Environmental Variable | Type | Default | Description |
| ---------------------- | ------- | ----------------------------- | ------------------------------------------------- |
| _**`KODI_ID`**_ | string | | A globally unique ID EX `myComponentId` |
| `KODI_NAME` | string | Value of `KODI_ID` | A vanity name EX `My Cool Component` |
| `KODI_ENABLE` | boolean | true | Should this component be used? |
| _**`KODI_URL`**_ | string | http://localhost:8080/jsonrpc | URL of the Kodi HTTP server to connect to |
| _**`KODI_USER`**_ | string | | The username set for Remote Control via Web Sever |
| _**`KODI_PASSWORD`**_ | string | | The password set for Remote Control via Web Sever |
@@ -0,0 +1,8 @@
| Environmental Variable | Type | Default | Description |
| -------------------------- | ------- | -------------------------- | --------------------------------------- |
| _**`SOURCE_KOITO_ID`**_ | string | | A globally unique ID EX `myComponentId` |
| `SOURCE_KOITO_NAME` | string | Value of `SOURCE_KOITO_ID` | A vanity name EX `My Cool Component` |
| `SOURCE_KOITO_ENABLE` | boolean | true | Should this component be used? |
| _**`SOURCE_KOITO_URL`**_ | string | | URL for the Koito server |
| _**`SOURCE_KOITO_TOKEN`**_ | string | | User token for the user to scrobble for |
| _**`SOURCE_KOITO_USER`**_ | string | | Username of the user to scrobble for |

Some files were not shown because too many files have changed in this diff Show More