Compare commits

...
1109 Commits
Author SHA1 Message Date
FoxxMD ede2d9f71a chore: Bump version for release 2025-06-19 16:22:39 +00:00
Matt Foxx acccc32813 Merge pull request #296 from FoxxMD/deezer-internal
feat: Implement Deezer Internal (ARL) Source
2025-06-17 20:41:08 -04:00
FoxxMD 69f73ff09f docs(deezer): Rewrite Deezer Source with ARL implementation
* Add ARL implementation docs and warnings
* Update json examples to include both types of deezer configs
2025-06-17 15:16:13 +00:00
FoxxMD 305f3c42ae fix(deezer): options are optional 2025-06-17 15:14:01 +00:00
FoxxMD d6ac4c34c5 feat: Implement better during/range temporal comparisons
* Refactor temporal comparisons to provide granular "during" thresholds
* Use duration for ignoring discovery with aggressive mode in Deezer #296
2025-06-17 10:10:34 -04:00
FoxxMD 9c6eeaafdb feat: Fuzzy diff threshold configurable
* Refactor fuzzy diff theshold to be configurable
* Configure aggressive fuzzy discovery for Deezer to be 40 seconds #296
2025-06-17 10:10:34 -04:00
FoxxMD 6bd841fea2 feat(deezer internal): Add fuzzy discovery ignore option
Allows user-configurable decision on whether to ignore fuzzy-matched discovered plays
2025-06-17 10:10:34 -04:00
FoxxMD e05c46e9ce fix(deezer official): Fix missing interval interface property 2025-06-17 10:10:34 -04:00
FoxxMD e94109dc75 feat: Implement Deezer Internal (ARL) Source 2025-06-17 10:10:34 -04:00
FoxxMD a68dea5193 chore: Bump version for release 2025-06-16 19:22:43 +00:00
FoxxMD b5d56b75d7 test(listenbrainz): Fix expected client count 2025-06-16 16:06:57 +00:00
FoxxMD c8daffb935 docs(listenbrainz): Add Koito compatibility and config example #300 2025-06-16 16:02:13 +00:00
FoxxMD 18a7f26c54 feat(listenbrainz): Implement limited support for Koito as Listenbrainz Source/Client #300
* Support Koito server detection via public route
  * Warns users of limited support on detection
* Implement play history retrival for Koito server
2025-06-16 15:49:39 +00:00
FoxxMD e7395e79a5 feat(listenbrainz): Detect /1/ version prefix on base URL
Remove /1/ from base URL to further normalize user URL and make config easier #300
2025-06-16 14:15:48 +00:00
FoxxMD 08ee6d379f fix(listenbrainz): Normalize URL and join parts correctly
* Normalize base URL to make user URL configuration easier/more robust
* Use joinPath for concetenating URL to avoid missing slashes #300
2025-06-16 14:04:15 +00:00
Matt Foxx 9c692539ce Merge pull request #298 from FoxxMD/jellyfin-musicbrainz
feat(jellyfin): Add musicbrainz metadata #297
2025-06-04 14:15:09 -04:00
FoxxMD 5cd5061f20 feat(jellyfin): Add musicbrainz metadata #297 2025-06-04 13:44:15 +00:00
FoxxMD 46bcf6d6a6 chore: Bump version for release 2025-05-29 13:20:34 +00:00
FoxxMD 4da931269c fix(plex): On Plex webhook routes filter plex sources to get webhook source only
#291
2025-05-27 17:31:34 +00:00
FoxxMD d19d40d479 test: Fix real time player not triggered during event loop
Running tests synchronously preventing rt player from advancing which cause each player update to be considered an overdrift
2025-05-27 17:07:30 +00:00
FoxxMD b3e1c7f660 chore: Update devcontainer to node 20 to match docker image 2025-05-27 17:05:50 +00:00
FoxxMD 1242a710df fix: Fix NaN and undefined initial states for rt player #292 2025-05-27 15:29:06 +00:00
FoxxMD b724eca9be docs: Add missing var for docs deploy 2025-04-23 15:04:20 +00:00
FoxxMD 376d4936cc docs: Add google site verification 2025-04-23 15:01:42 +00:00
FoxxMD a5da0573c5 docs: Remove flatpak installation method
See #287
2025-03-31 18:31:46 +00:00
FoxxMD 4694ce45a7 chore: Update flatpak description 2025-03-31 12:12:28 -04:00
FoxxMD 7c7d710cd3 fix(ui): Guard against null listenedDuration
Fixes #285
2025-03-28 08:21:04 -04:00
FoxxMD e2cd7e2991 chore: Bump version for release 2025-03-25 01:15:40 +00:00
FoxxMD 92d8eb8d6f chore: Update static config schema 2025-03-25 01:15:26 +00:00
FoxxMD d14e7d9b2e fix(lastfm): Remove duration for scrobble if value is 0 2025-03-25 01:09:31 +00:00
FoxxMD e4befae55a fix(lastfm): Fix endpoint regex to exclude auth callback url 2025-03-20 19:14:34 +00:00
FoxxMD 0e00b425b4 fix(icecast): rename old scrobble behavior variable 2025-03-20 16:27:30 +00:00
FoxxMD fcf6c8eb4c docs: Add some common source options 2025-03-20 16:23:01 +00:00
FoxxMD 242bacc1be feat(source): Optional position for non-positional sources
* Allow non-positional Sources to still report position (for display only)
* Refactor Azuracast to be non-positional due to reported position not always being accurate (azuracast approximates track length)
2025-03-20 15:40:31 +00:00
FoxxMD cbe28f97a0 feat(source): Improve "Should Scrobble" functionality
* Rename from manualListening to Should Scrobble to better describe functionality
* Move check to Source scrobble function to avoid short circuting Source player for no reason
* Add to Azuracast and make functionality more generic
2025-03-20 15:12:00 +00:00
FoxxMD 336972f586 fix(ui): Update status indicator for ingress sources 2025-03-20 14:13:48 +00:00
Matt Foxx 81edb31e7b Merge pull request #283 from FoxxMD/icecast
Icecast
2025-03-19 12:57:38 -04:00
FoxxMD dbde66ad77 docs: Add Icecast docs 2025-03-19 16:50:57 +00:00
FoxxMD efbf2da816 feat(icecast): Switch to metadata-only parsing 2025-03-19 16:34:19 +00:00
FoxxMD 38d1db2f3f feat: Implement Icecast Source MVP 2025-03-19 15:20:44 +00:00
Matt Foxx 265ae82663 Merge pull request #278 from FoxxMD/GH-263/azuracast
feat: Add Azuracast Source
2025-03-19 09:28:27 -04:00
FoxxMD 2bd2972af6 docs: Fix bad merge of azuracast/musiccast config 2025-03-19 13:22:52 +00:00
Matt Foxx 1bdf083e28 Merge branch 'master' into GH-263/azuracast 2025-03-19 09:17:44 -04:00
Matt Foxx 4c233fdded Merge pull request #279 from FoxxMD/GH-274/musiccast
feat(musiccast): Implement MusicCast Source
2025-03-19 08:42:40 -04:00
FoxxMD e0060fe3e5 feat: Add ENVs for default scrobble threshold for duration/percent
* SOURCE_SCROBBLE_DURATION env sets default duration
* SOURCE_SCROBBLE_PERCENT env sets default percent
2025-03-18 17:28:14 +00:00
FoxxMD 2adb3bd052 docs(musiccast): Add docs for Yahama MusicCast 2025-03-17 21:02:26 +00:00
FoxxMD 0dca49ab43 fix(player): Check for 0 duration before finalizing listen session with duration-based calculation 2025-03-17 20:40:06 +00:00
FoxxMD 6a71d4a495 fix(musiccast): Treat 0 total_total as undefined
Prevents some positional player logic from running on a functionally-useless value
2025-03-17 20:40:06 +00:00
FoxxMD 230ddf4a48 fix(source): Do not calculate polling interval decrease if duration does not exist 2025-03-17 20:40:06 +00:00
FoxxMD e4e1797ac5 fix(musiccast): Remove hardocded port for debugging 2025-03-17 20:40:06 +00:00
FoxxMD 1dd3185715 fix: Better handling of 0 duration tracks
* Check for zero duration before using progress bar in debug logging
* Adds tests for scrobble thresholds to make sure 0 duration is handled
2025-03-17 20:40:06 +00:00
FoxxMD 4fee231a7b test(musiccast): Add tests for basic connectivity 2025-03-17 20:36:15 +00:00
FoxxMD b5ead39792 feat(musiccast): Switch play info fetch order
net/usb is more likely to be used since it has more sources. Likely using it will reduce required fetches in most usage scenarios.
2025-03-17 20:36:15 +00:00
FoxxMD 190431b9d4 feat(musiccast): Implement MusicCast Source 2025-03-17 20:36:15 +00:00
FoxxMD 210fa35cb6 docs: Add Last.fm Endpoint source docs 2025-03-14 16:34:43 +00:00
FoxxMD 479af1cd5e docs: Add Listenbrainz Endpoint source docs 2025-03-14 16:29:48 +00:00
Matt Foxx c72831cd12 Merge pull request #281 from FoxxMD/normalScrobbleEndpoints
feat: Implement dummy endpoint Sources for Listenbrainz and Last.fm
2025-03-14 11:24:08 -04:00
FoxxMD f68a9493ce fix(listenbrainz): add dummy validate-token endpoint 2025-03-14 08:16:56 -04:00
Matt Foxx 78a93d0809 Merge pull request #256 from FoxxMD/configurablePlayerStale
feat(source): Stale/orphan scrobble and timestamp improvments
2025-03-13 15:26:46 -04:00
FoxxMD 3680ac8af1 feat(lastm): Implement MVP Lastfm endpoint source 2025-03-13 18:44:08 +00:00
FoxxMD e74ba52c47 fix(listenbrainz): Improved slug and normal lz endpoint detection 2025-03-13 16:53:25 +00:00
FoxxMD d3cd07c5c3 feat(listenbrainz): Implement MVP LZ endpoint source 2025-03-13 16:20:37 +00:00
FoxxMD 2b890d4704 refactor: Pass reportedTS for testing purposes 2025-03-13 11:06:26 -04:00
FoxxMD 47eead2a18 feat: Use timestamp for position MS change on RT player
Makes testing easier
2025-03-13 11:06:26 -04:00
FoxxMD 38ea3115f6 test: Add tests for scrobble behavior when cleaning up players 2025-03-13 11:06:26 -04:00
FoxxMD f11a2b5442 fix: Use last known position for final range when cleaning up stale RT player
Prevents listen duration from being reported as overdrift RT position due to lack of player updates
2025-03-13 11:06:26 -04:00
FoxxMD 818f67d6da feat(source): Add relative time since last activity to logging
Makes logs easier to read when looking for stale source/player
2025-03-13 11:06:26 -04:00
FoxxMD 84f353220d feat(source): Clear listening range on stale to prevent erroneous scrobble on resume
Clearing pre-stale listening range prevents any future resume (player becomes non-stale) from using old listening range to scrobble. Represents a physical "break" between listening sessions for the same song
2025-03-13 11:06:26 -04:00
FoxxMD 47541a449b feat: Use human-friendly timestamp for stale/ophan interva logging 2025-03-13 11:06:26 -04:00
FoxxMD 1a62edab37 feat(source): Add stale/orphaned intervals as user configurable 2025-03-13 11:06:26 -04:00
FoxxMD 5927450840 fix(ytm): Recover artist/album from YTM when not surfaced by youtube.js
Use flex_columns for parsing raw data when musicshelf when artist/album does not have an endpoint LuanRT/YouTube.js#381

Fixes #280
2025-03-13 10:40:47 -04:00
FoxxMD ec9bea70fd chore: Bump YTM dependency version 2025-03-13 10:37:53 -04:00
FoxxMD 2f9a45b1e4 fix: Filter components by name and check for undefined
Fixes #273
2025-03-12 19:16:32 +00:00
FoxxMD 3c649df2e9 docs(azuracast): Add azuracast Source docs 2025-03-11 16:41:42 +00:00
FoxxMD ab1766ca0a feat(azuracast): Add station name hint and fix labels 2025-03-11 16:10:38 +00:00
FoxxMD a42de78ad3 feat(azuracast): Implement manual listening 2025-03-11 16:06:46 +00:00
FoxxMD 81bde3a771 feat(azuracast): Add MVP azuracast source implementation 2025-03-11 15:21:07 +00:00
FoxxMD 982e6655ef chore: Add more log guidance on bug report 2025-02-14 17:51:17 +00:00
FoxxMD 44ce0cba3c feat(azuracast): Implement config and types 2025-02-03 16:53:39 +00:00
FoxxMD c1e1c69b5e fix: Missing optional period on vs. when parsing artist 2025-01-27 21:13:32 +00:00
FoxxMD 142defdd20 docs: Put delimited artist example in expander 2025-01-27 14:57:15 +00:00
FoxxMD aad9f2b4de docs: Add primary artist extraction examples for scrobble modification 2025-01-27 09:46:14 -05:00
FoxxMD 8e92407182 test: Add modification example for parsing a primary artist from delimited string 2025-01-27 09:36:15 -05:00
FoxxMD 94210e29d1 docs: Fix extra array in title modification example
Fixes #258
2025-01-27 09:16:34 -05:00
FoxxMD d2c6194314 ci: Add comments on PR SHA usage 2025-01-21 14:06:16 -05:00
FoxxMD 11ca7a724d try pull SHA directory for build
So that build summary details used SHA
2025-01-21 13:58:47 -05:00
FoxxMD 3057908074 fix(ci): Add context to build-push so it uses checked out PR ref 2025-01-21 13:28:45 -05:00
FoxxMD 6a9e538845 chore: Bump version for release 2025-01-21 15:04:38 +00:00
Matt Foxx 130538cd9a Merge pull request #255 from FoxxMD/GH-254/playCompletedOnCleanup
fix(spotify): Generate played object from cleanup with correct TS
2025-01-18 12:57:51 -05:00
FoxxMD 1486613744 fix(spotify): Generate played object from player cleanup with correct timestamp
Missing arg to pass to getPlayedObject to ensure the timestamp used for playDate is the same as normally discovered tracks. Without the arg two tracks played with the second being scrobbled on stale would cause the latter to have the same TS as the former

fixes #254
2025-01-17 18:55:35 +00:00
Matt Foxx f2516206c0 ci: Refactor OCI image workflows to use multi-runner for cross-arch builds (#246)
Replaces `build-push-action` buildx QEMU building for multiple `arch` with a multi-runner matrix that builds each image on native runners and combines digests before pushing to registries.

* Use arm public preview runner
* Refactor publish image workflow to use multi-runner
* Refactor PR workflow to use multi-runner
2025-01-17 09:35:11 -05:00
FoxxMD f8902e05a6 ci: Update sanity test to install/build concurrently 2025-01-16 16:15:00 -05:00
Matt Foxx c59b47c801 Merge pull request #252 from FoxxMD/paralellBuild
feat(docker): Paralellize npm install and build processes
2025-01-16 16:06:27 -05:00
FoxxMD 0e4845f2e2 feat(docker): Paralellize npm install and build processes 2025-01-16 20:48:05 +00:00
FoxxMD e2775cd9de chore(docker): Update node version and reduce npm verbosity
* Update to latest node 20.x minor version
* Remove `--verbose` and enable `--no-audit` for npm install to reduce install time
  * These were left over from debugging ARM builds from Feb 2024
2025-01-16 14:43:29 -05:00
Matt Foxx 816bec88c8 Merge pull request #251 from FoxxMD/docusaurus36
docs: Update to Docusaurs 3.6 to improve build times
2025-01-16 14:25:19 -05:00
FoxxMD d724bef4e4 docs: Update to Docusaurs 3.6 to improve build times 2025-01-16 14:07:47 -05:00
FoxxMD cac851f120 chore: Bump version for release 2025-01-15 15:16:26 +00:00
FoxxMD c1df74ee27 docs: Strikeout tautulli in readme 2025-01-15 15:16:01 +00:00
FoxxMD 0c7b51c622 docs: Update bug report template to correct debug mode link 2025-01-15 15:12:26 +00:00
FoxxMD 56ecad24d4 docs: Update kitchensink with newer jellyfin/plex examples 2025-01-15 15:09:38 +00:00
FoxxMD 6bb600648a docs: Add final deprecation notices for Plex/Tautull/Jellyfin legacy sources 2025-01-15 15:00:44 +00:00
FoxxMD fe2610c35a docs: Add debug mode information 2025-01-15 14:36:48 +00:00
FoxxMD 84bcbc9cea docs(ytm): Update docs with more debugging options 2025-01-15 14:02:50 +00:00
FoxxMD d741377e5a feat(ytm): Debug mode enables log diffs 2025-01-15 13:50:47 +00:00
Matt Foxx 1ea106970d Merge pull request #248 from FoxxMD/GH-227/ytmMusicalChairs
fix(ytm): Improve history handling
2025-01-15 08:27:55 -05:00
Matt Foxx a879b7aded Merge pull request #249 from FoxxMD/GH-223/plexPlatformCleanup
fix(plex): Improve handling of orphaned API sessions
2025-01-14 08:19:12 -05:00
FoxxMD 30ee000676 fix(plex): Refactor final range position to be configurable
* Keep default 3 seconds grace period
* Set grace period for plex to API viewoffset period (rtdrift) of 17 seconds #223
2025-01-09 18:42:09 +00:00
FoxxMD 703cf80d5a fix(source): Implement player cleanup with play discovery for stale platforms
* Do play cleanup during polling for new plays so that we can discover tracks from abandoned players
* Fixes missed scrobbles from Plex API when Source stops returning a session due to player queue end #223
2025-01-09 18:01:12 +00:00
FoxxMD 34289f0176 docs: defer script load 2025-01-03 19:24:51 +00:00
FoxxMD 8b8f32de02 feat(ytm): Use durations to generate play dates for interim tracks
#226
2025-01-03 18:28:55 +00:00
FoxxMD 095e5cc0d9 feat(ytm): Conservative skip detection
#226
2025-01-03 17:56:07 +00:00
FoxxMD 0c46c0a079 refactor(ytm): Clean up tests and history parsing functions 2025-01-03 16:25:33 +00:00
FoxxMD dd4b2ebb8c fix(ytm): Compare recent changed history to detect outdated responses
Store responses when history changes and then check if YTM returns an outdated response after previously returning new tracks.

#227
2025-01-02 19:03:38 +00:00
FoxxMD d30ee0c35a feat(source): Ensure delay on close-interval for scrobbling is less than interval 2025-01-02 18:29:34 +00:00
FoxxMD 484fb16bba fix(ytm): Use diff type to restrict valid history changes
* Diff type should always be prepend since we are checking for new tracks
  * Warn and log if another type is detected
* Warn if more than one track diffed for discovery
* Refactor and and make YTM history parsing testable

#227
2025-01-02 18:28:59 +00:00
FoxxMD f320e6cef0 feat: Include comment in play diff when present 2025-01-02 18:25:27 +00:00
FoxxMD ea2421bc6d chore: Bump plexjs version
Includes originalTitle spec addition so we can remove patch
2025-01-02 16:59:36 +00:00
FoxxMD f43c31fb9a docs: Add lnav config 2024-12-27 18:49:29 +00:00
FoxxMD 6825ad55e3 feat(ui): Disable minifiying frontend identifiers
No need to minify to save space since this is not a high-traffic site and preserving identifiers helps with debug issues
2024-12-27 18:49:16 +00:00
FoxxMD 92841f8bb3 fix: Default positional listen range to 0 position if none is provided
* doesn't make sense to use undefined as start. if we are providing a position it should be 0, if unknown

#245
2024-12-27 18:43:51 +00:00
FoxxMD 38e5e4c9a9 feat: Add init log message for debug mode status 2024-12-20 13:47:54 -05:00
FoxxMD 1bcfed05af feat: Log player state when using debug mode 2024-12-20 18:28:21 +00:00
FoxxMD e00ec4098a feat: Reduce polling log noise 2024-12-20 18:10:22 +00:00
FoxxMD 5d44953435 revert ts comment causing failing build 2024-12-20 17:46:04 +00:00
FoxxMD 80ad5a3518 feat: Fully implement use of DEBUG_MODE 2024-12-20 14:02:21 +00:00
FoxxMD b5683ba4b8 fix(jellyfin): Fix missing check for undefined play
#239
2024-12-17 20:53:54 +00:00
FoxxMD 884b3963d3 fix(plex): Use track artist from response data when album artist is present
* Fixes #241 by using the track artist as the primary artist when album artist is present
* Bug is reported in LukeHagar/plexjs#27 but will need be patched at runtime for the foreeable future due to npm packages not being published since September LukeHagar/plexjs#26
2024-12-16 20:11:50 +00:00
Matt Foxx 8de2296aaf Merge pull request #221 from FoxxMD/improveInitUsage
refactor: bubble init usage and errors to simplify recovery
2024-12-16 09:43:53 -05:00
Matt Foxx c1869f469c Merge pull request #240 from Fratt/fix-spotify-album-artists
fix(spotify): Album artists no longer missing
2024-12-16 09:34:29 -05:00
FoxxMD 86196b3122 test(spotify): Test album artist parsing 2024-12-16 14:31:10 +00:00
Fratt fd03e42240 fix(spotify): Album artists no longer missing 2024-12-14 13:08:00 +01:00
FoxxMD 3e230b0d68 fix(ui): Fix card header right-side alignment 2024-12-11 20:41:06 +00:00
FoxxMD 133d7f56e5 fix(ui): Wording for non-polling sources start/restart 2024-12-11 20:35:22 +00:00
FoxxMD 78d048ba62 fix: missing implemented methods for test class 2024-12-11 18:24:42 +00:00
FoxxMD 323004a730 feat: Implement start/restart/force init from UI regardless of status 2024-12-11 18:20:12 +00:00
FoxxMD 626bca9951 feat: Initialize usage refactored everywhere
* try-catch and bubble up depending on usage
* replace poll/monitor code on startup with scheduler task usage
* better notification messages with truncated cause chain
2024-12-11 17:30:08 +00:00
FoxxMD 3ae0d4ff45 feat: Add stage Error classes and cause chain for notification 2024-12-10 21:17:44 +00:00
FoxxMD 9d85df54e1 refactor: Normalize all testAuth implementation to throw on error 2024-12-10 20:56:31 +00:00
FoxxMD 0ff1c95fd1 TODO partial refactor of init usage to throw errors 2024-12-10 20:34:33 +00:00
FoxxMD b93274fd53 Merge remote-tracking branch 'origin/master' 2024-12-10 15:19:55 -05:00
FoxxMD 0f0d1d64f9 feat(jellyfin): More robust connection check and logging 2024-12-10 20:19:30 +00:00
FoxxMD d77e6c6f22 feat(jellyfin): Bump client major version 2024-12-10 20:19:10 +00:00
Matt Foxx 83ff5348c6 Merge pull request #216 from FoxxMD/GH-215/notifiersRelax
feat(notifications): Make init more granular and always complete init
2024-12-10 11:54:59 -05:00
FoxxMD 65012d40ed feat(notifications): Make init more granular and always complete init
* Notifications are not mission critical so its okay if they fail on init because service outage may be transient. Always init notification services and always attempt to push (we catch failures anyway)
* Parse more info from notification URL and test simple reachability of host/port to make troubleshooting easier

Fixes #215
2024-12-10 11:33:33 -05:00
FoxxMD feb02bfa85 docs(ytmusic): Clearer wording for troubleshooting content
Also increase non-erroneous diff logging from DEBUG to VERBOSE to raise signal a bit.
2024-12-10 15:55:20 +00:00
FoxxMD 276e51b8ea docs: Fix broken links 2024-12-10 15:40:15 +00:00
FoxxMD c83d77c02a docs: Fix bad transform links 2024-12-10 15:28:25 +00:00
FoxxMD 0cdb48a71a ci: Generate schema files for docsite in GH docs deploy workflow 2024-12-10 10:13:06 -05:00
FoxxMD 1556d7ac69 chore: Generate schema files during docker build for docsite 2024-12-10 10:08:02 -05:00
Matt Foxx 1e65abd962 Merge pull request #236 from FoxxMD/ytOauthRedux
feat(ytm): Re-implement auth with cookie and oauth
2024-12-10 09:16:14 -05:00
FoxxMD 9f11d799ac feat(source): Add experimental file logging 2024-12-09 18:44:43 +00:00
FoxxMD 5c0f419e7e feat(ytm): Implement ENV config 2024-12-06 19:28:32 +00:00
FoxxMD c33d42808a docs(ytmusic): Add new auth docs and update FAQ 2024-12-06 19:09:32 +00:00
FoxxMD e8716a45b2 feat(ytmusic): Custom redirectUri checks 2024-12-06 15:56:32 +00:00
FoxxMD 570dee30e3 fix(ytmusic): Add missing logging for redirect URI 2024-12-06 15:33:02 +00:00
FoxxMD 97222ed233 refactor(ytmusic): Improve auth handling for reauthentication 2024-12-06 15:18:07 +00:00
FoxxMD e2a7fff6f9 chore: Bump youtubei.js major version
* Fixes parsing warning for history response
2024-12-06 15:17:18 +00:00
FoxxMD efc8246a76 chore: Add docker-compose dev file and vscode task 2024-12-05 09:37:58 -05:00
FoxxMD b4a95e0cf9 fix(ytmusic): query string on redirect uri incorrectly encoded 2024-12-05 09:18:05 -05:00
FoxxMD c66d5d4627 docs: Improve schema generation and compatibility
* Patch generator to generate definition titles until vega/ts-json-schema-generator#2127 is resolved
* Add launch config for debugging schema generation
* Disallow topRef generation so docusarus json viewer works
* Write to docsite static assets on generation
2024-12-04 14:46:40 -05:00
FoxxMD 4de28e0390 feat(ytmusic): Add more innertube options to user configuration 2024-12-04 19:38:54 +00:00
FoxxMD f74721b366 fix(ytmusic): Fix missing api prefix for redirectUri 2024-12-04 15:04:36 +00:00
Matt Foxx 4fb16e7098 Merge pull request #232 from CodeWithMa/remove-deprecated-version
fix: remove deprecated version from docker-compose.yml
2024-12-03 16:35:46 -05:00
Matt Foxx a76c351206 Merge pull request #235 from CodeWithMa/fix_case
fix: fix WARN: FromAsCasing: 'as' and 'FROM' keywords' casing do not match
2024-12-03 16:35:03 -05:00
FoxxMD 7faf94cfab feat(ytm): Re-implement auth with cookie and oauth
Youtube TV seems no longer have scope for reading history or account details?

Cookie may work and custom oauth seems most stable based on reporting from LuanRT/YouTube.js#803
2024-12-03 15:58:08 -05:00
Ma 3a1e29f682 fix: fix WARN: FromAsCasing: 'as' and 'FROM' keywords' casing do not match 2024-11-28 15:57:17 +01:00
Ma 88cb287df8 fix: remove deprecated version from docker-compose.yml 2024-11-20 17:16:14 +01:00
dependabot[bot] eb26ee0cd4 Merge pull request #194 from FoxxMD/dependabot/npm_and_yarn/npm_and_yarn-ac4cac0c7a 2024-10-31 12:44:16 +00:00
dependabot[bot] 844833f348 chore(deps): bump the npm_and_yarn group across 2 directories with 3 updates
Bumps the npm_and_yarn group with 1 update in the / directory: [xml2js](https://github.com/Leonidas-from-XIV/node-xml2js).
Bumps the npm_and_yarn group with 2 updates in the /docsite directory: [micromatch](https://github.com/micromatch/micromatch) and [webpack](https://github.com/webpack/webpack).


Updates `xml2js` from 0.6.0 to 0.6.1
- [Commits](https://github.com/Leonidas-from-XIV/node-xml2js/compare/0.6.0...0.6.1)

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

Updates `webpack` from 5.92.1 to 5.95.0
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.92.1...v5.95.0)

---
updated-dependencies:
- dependency-name: xml2js
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: micromatch
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: webpack
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-31 12:40:57 +00:00
FoxxMD 38b02cf771 Update dependencies 2024-10-31 12:38:04 +00:00
Matt Foxx 1706d5cab9 Merge pull request #203 from FoxxMD/GH-195/ytoauth
feat(youtube)!: Use oauth-based api library for better stability
2024-10-31 08:30:48 -04:00
Matt Foxx 640d2ff28b Merge pull request #219 from FoxxMD/GH-218/plexLocal
fix(plex): Handle plex local user
2024-10-30 13:42:55 -04:00
FoxxMD 312b6ce987 fix(plex): Handle plex local user
* Identify when session is for the local user (no user is logged in)
* Add Local User as default allowed when no usersAllow settings
* Allow user config to allow/block local user
* Update plex docs

Fixes #218
2024-10-30 16:44:51 +00:00
FoxxMD 489f70929e docs: Fix spelling mistake 2024-10-29 13:47:38 -04:00
FoxxMD 904ddfa5c6 docs(tautulli): Add deprecation notice and discussion link 2024-10-29 16:57:59 +00:00
FoxxMD 3ed6840607 feat: Improved filtering for platform players with multiple states
* Add session key dervied from source data to Play and PlayerState
* Discriminate states based on a session key, if possible
  * Log which states are being dropped based on session key (plex)
* Add session key to important logging (new player play, in UI for player info)
2024-10-29 14:59:55 +00:00
FoxxMD 93125dd7c8 feat: Reduce drop reason logging noise for jellyfin/plex API sources
Only log the first occurrence of a unique session drop so that subsequent drops don't make logging noisy
2024-10-28 18:33:52 +00:00
FoxxMD 34356c6c56 fix(youtube): Fix signout check 2024-10-28 18:03:10 +00:00
FoxxMD 05466fed2b feat(youtube): Add config option to always log diff 2024-10-28 18:03:10 +00:00
FoxxMD 535b818a22 fix(youtube): Implement bump-only list change detection
Should fix new way YTM orders recent history #195
2024-10-28 18:03:10 +00:00
FoxxMD e62e04dd14 feat(youtube)!: Use oauth-based api library for better stability
* Fixes #195 missing (404) history endpoint which only appear when using cookies
* Might fix #158 due to using a different authentication method
2024-10-28 18:03:10 +00:00
FoxxMD 61cd5fcf7b fix: Bump @foxxmd/logging to fix DST rolling file issue
#214
2024-10-28 18:02:04 +00:00
Matt Foxx f308f71b5d Merge pull request #212 from FoxxMD/playerStateCleanup
feat(player): Real time positional player state
2024-10-25 14:54:23 -04:00
FoxxMD c498bde1fd feat(ui): Show indeterminate state for non-positional players 2024-10-25 15:57:30 +00:00
FoxxMD 0c9b496137 feat(player): Implement real-time positional state tracking
* Introduce positional and non-positional data structures
  * Refactor state player into types of each
  * Refactor listen ranges and progress into types of each
* Implment (internal) real-time player and base positional player seeked/repeat on real-time drift instead of just reported position
* Refactor tests to use (emulated) real-time components
2024-10-24 20:37:41 +00:00
FoxxMD 9682d4f8f9 refactor(player): Cleaner player update signature 2024-10-24 14:53:30 +00:00
Matt Foxx da76945f5e Merge pull request #211 from FoxxMD/plexApi
feat(plex): Initial Plex API Source implementation
2024-10-24 09:45:17 -04:00
FoxxMD 51074a58f4 Add deprecation warning to plex webhook source 2024-10-24 13:41:10 +00:00
FoxxMD 3cc735b9c8 fix(plex): Refactor player state to allow for drift and tracking pause based on Source behavior
Plex only updates player position every 15 seconds (of played track) so player state needs to be adapted to not detect this as a pause or seek.

* Allow per-source drift allowed before triggering seek
* Allow per-source pause detection
2024-10-23 20:30:33 +00:00
FoxxMD b74f31c26d feat(plex): Implement album art for UI 2024-10-23 18:42:55 +00:00
FoxxMD 4a96cbfb0e docs(plex): Clean up api/webhook sections and add migration guide 2024-10-23 15:18:13 +00:00
FoxxMD 77a3c352b1 feat(plex): Implement workaround for getting plex token details
Ignore sdk validation error and get raw value in order to parse authenticated user
2024-10-23 10:59:36 -04:00
FoxxMD 598ae01082 feat(plex): Add track progress tracking to api source 2024-10-23 13:40:50 +00:00
FoxxMD 54e11899a0 Fix missing comma 2024-10-22 18:29:11 +00:00
FoxxMD dc4962ade1 feat(plex): Initial Plex API Source implementation 2024-10-22 18:21:28 +00:00
FoxxMD ccf317c06f docs: Fix Jellyfin API migration version 2024-10-17 10:40:54 -04:00
FoxxMD aff5717f19 fix(ui): Fix album art ordering 2024-10-16 16:26:23 +00:00
FoxxMD fa39209272 feat(ui): Implement album art for Jellyfin and Spotify 2024-10-16 16:09:24 +00:00
FoxxMD d63082a889 fix(ui): Skip lg breakpoint for cleaner interface 2024-10-16 16:09:05 +00:00
FoxxMD a28d46ecbe feat(ui): Add album art support in player 2024-10-16 16:08:41 +00:00
FoxxMD 3bc72ce881 feat(ui): Improve player ui when player is stopping 2024-10-16 13:56:05 +00:00
FoxxMD 314210117b fix(player): Fix missing calculated state on player stop 2024-10-16 13:55:35 +00:00
FoxxMD 57a8e48d3a fix(jellyfin): Fix memory player hanging on end of state
Was not passing stopped state due to undefined play but need to do this to let last play actually be scrobbled.

Refactor jellyfin session validation and types for MemorySource to correctly allow processing player state with no plays
2024-10-16 13:55:05 +00:00
FoxxMD 2d3ab86238 fix: Fix string splitting 2024-10-16 13:03:37 +00:00
FoxxMD b76edbf7de fix: Revert including platform for track dispay by default 2024-10-16 12:47:43 +00:00
Matt Foxx b2efc34b80 Merge pull request #210 from FoxxMD/GH-209/jellyfinCleanup
fix(jellyfin): Enforcing media type and session handling fixes
2024-10-15 14:36:56 -04:00
FoxxMD dee8d6bc61 chore(vscode): Add markdown extension to devcontainer 2024-10-15 17:42:04 +00:00
FoxxMD 3e5576481d feat(jellyfin): Support and enforce library collection type
* Default disallow any activity not from a library of collectionType 'music'
* Allow user config for allow/block by library name
* Allow user config for allowing additional library collection types
* Update docs and example config
2024-10-15 17:41:33 +00:00
FoxxMD ffd74aea6f docs: Enable parsing json5 for AIO examples 2024-10-15 17:39:49 +00:00
FoxxMD 145af33239 feat(jellyfin): More item type validation 2024-10-15 14:25:51 +00:00
FoxxMD 3851b2226a fix(jellyfin): Detect theme song extras play and discard
Fixes #209
2024-10-15 13:13:36 +00:00
FoxxMD bb132101b5 fix(jellyfin): Use correct id for tracking first-time seen media 2024-10-14 17:19:41 -04:00
FoxxMD 1e6251edfb ci: Fix sanity test checkout ref and shorten APP_VERSION sha usage 2024-10-14 13:07:12 -04:00
FoxxMD e251da9b8e test(jellyfin): Update valid activity tests 2024-10-14 15:42:54 +00:00
FoxxMD 335ee0cd60 fix(jellyfin): Set multi-platform in constructor 2024-10-14 11:15:59 -04:00
FoxxMD dc9f6c22ad feat(jellyfin): Add debug options for media data troubleshooting 2024-10-14 11:11:58 -04:00
FoxxMD 5568b54c99 fix(jellyfin): Enforce media type when determining session validity
Fixes #209 for erroneous scrobbling of video
2024-10-14 11:01:48 -04:00
FoxxMD e6e35e052a feat: Implement platform output when building track info 2024-10-14 11:01:11 -04:00
Matt Foxx 9c21db8172 Merge pull request #205 from FoxxMD/GH-200/jfPlayFormat
fix(jellyfin): Gaurd against undefined playback data
2024-10-11 15:57:57 -04:00
FoxxMD 77c9141d92 fix(jellyfin): Gaurd against active session without now playing data 2024-10-11 13:47:39 -04:00
FoxxMD 68e86f4f90 fix(jellyfin): Gaurd against undefined playback data
SDK types say AlbumArtists etc. should be defined but #200 shows that's not always the case, for some reason.

Check for undefined value on playback data that requires additional transformation to prevent errors.

Fixes #200
2024-10-11 13:47:39 -04:00
Matt Foxx 5e95f60461 Merge pull request #208 from FoxxMD/vegaSchema
fix: Replace typescript-json-schema with ts-json-schema-generator
2024-10-11 13:32:52 -04:00
FoxxMD 908d7d4177 fix: Replace typescript-json-schema with ts-json-schema-generator
Fixes a lot of headaches:

* vega is more up to date and extensible
  * no more hacky regex for reference names
  * complete control over parsing and formatting (const as enum)
  * does not duplicate typescript as dependency
  * should properly throw errors on schema generation instead of just logging to console
* move schema static files into programmatic creation instead of package scripts
* fix types being removed in prod which caused schema generation errors
2024-10-11 12:28:55 -04:00
FoxxMD de1a9c35ec fix: Add missing express types for prod dependencies
Required for ts-schema generator to correctly work since we use it in config definitions
2024-10-11 09:28:01 -04:00
FoxxMD f7fa9cd330 fix: Guard against ts-schema generation errors
* Check if generator is null and throw
* Check if schema is null before trying to validate, will make errors less cryptic
2024-10-11 09:27:12 -04:00
FoxxMD ee8b371850 fix: Missing tsconfig and other root files for some reason?? 2024-10-11 09:23:02 -04:00
FoxxMD 601228a369 fix: Fix boolean options in issue template forms 2024-10-10 15:12:24 -04:00
FoxxMD 5dcaefc460 chore: Rename config template 2024-10-10 15:06:21 -04:00
FoxxMD 026e94fc1b chore: Add issue template config 2024-10-10 15:05:24 -04:00
FoxxMD fd7c8467b4 chore: Add templates for feature request and new source/client 2024-10-10 15:03:31 -04:00
FoxxMD 06e651a844 chore: Migrate bug report template to form 2024-10-10 14:41:38 -04:00
FoxxMD 1b0e7a3dfd ci: Make test workflow reusable and add run sanity check
* Add sanity run steps to check for obvious errors when running in production
* Refactor test job to be reusable
2024-10-10 12:05:16 -04:00
FoxxMD abc5d1861a fix: Specify ts-json-schema as NON dev dep
Not enough to include it in both dependencies and devDependencies -- it needs to be NOT in devDependencies to prevent `npm ci --omit=dev` from purging it.
2024-10-10 12:02:06 -04:00
FoxxMD 4b8129fd19 fix: missing schema package in regular deps and freeze ts version 2024-10-10 11:36:23 -04:00
FoxxMD f610f385bd test: Try timeout in test instead of before() 2024-10-09 16:15:21 -04:00
FoxxMD 6994635fe4 test: Try another timeout and destroy scheduler 2024-10-09 16:13:15 -04:00
FoxxMD 61525e1351 test: Add long timeout to first config parse due to slow schema generation
Not a problem on my machine but causing timeout on github actions
2024-10-09 15:55:06 -04:00
FoxxMD a50070e133 ci: Add ACT run for just backend test 2024-10-09 15:51:58 -04:00
Matt Foxx db632424b1 Merge pull request #202 from FoxxMD/validateGranular
feat: Improve client/source schema validation generation and error handling
2024-10-09 12:52:47 -04:00
FoxxMD 6b92920f50 fix: Use silent logger for source config tests 2024-10-09 12:20:10 -04:00
FoxxMD a30d362695 fix: Fix error logging line numbers
Don't use ts-node for json-schema
2024-10-09 12:18:23 -04:00
FoxxMD d105e62f18 feat: Refactor validation errors to emit an event instead of throwing
* Prevents app from crashing due to only one thing going wrong
* Will be used for testing error handling in the future
2024-10-09 12:18:23 -04:00
FoxxMD d5ad796339 fix: Fix example configs so they pass validation tests
* Add missing sources to dual configs
* Fix slug type for webscrobbler example
2024-10-09 12:18:23 -04:00
FoxxMD c0aabefc72 test: Add tests for example configs and test parsing/validation
* Add suite for testing client/source parsing and validation
* Additionally, use example configs for two-birds-one-stone checking they are valid
* Refactor sources init as separate step from component building
2024-10-09 12:18:23 -04:00
FoxxMD 259d321f3d chore: Remove tests folder from tsconfig exclude to get autocomplete 2024-10-09 12:18:23 -04:00
FoxxMD d2375d92e6 test: Force mocha to exit 2024-10-09 12:18:23 -04:00
FoxxMD f8a63c5df1 fix: Add missing glob package 2024-10-09 12:18:23 -04:00
FoxxMD 5004d99473 chore: Add tmp testing folders to ignore files 2024-10-09 12:18:23 -04:00
FoxxMD 5b26cc73e8 feat: Improve client/source schema validation generation and error handling
* Use runtime-generated schemas
  * Ensures validated schemas always match code
  * Can use schemas for individual source/clients without having to generate files
* catch invididual validation errors and log instead of crashing
* refactor validation error output into chained error instead of as individual log statements to improve clarity
2024-10-09 12:18:23 -04:00
FoxxMD 72a4c4542b fix: Fix some source schemas to reflect documentation WRT optional properties 2024-10-09 12:18:23 -04:00
Matt Foxx fdaeb8f6b8 Merge pull request #201 from FoxxMD/GH-199/ytschema
fix(ytmusic): Allow authUser to be number or string
2024-10-09 08:28:25 -04:00
FoxxMD f2f550dae7 Update devcontainer usage with docusarus and docs 2024-10-08 19:53:50 +00:00
FoxxMD 6780d72296 Improve testing, linting, and node usage in devcontainer
* Swtch to devcontainer for node 18.x and remove terminal/launch settings for version (removes nvm dependency in container)
* Update eslint config to use proper config for include/ignore files
* Add spec for mocha and mocha test runner extension + settings
2024-10-08 19:33:47 +00:00
FoxxMD f1784f3c21 fix(ytmusic): Allow authUser to be number or string
Fixes #199
2024-10-07 10:38:55 -04:00
FoxxMD 22d17ba621 docs: Fix link typo for flatpak 2024-10-03 09:18:26 -04:00
FoxxMD 4b4cc1c438 docs: Update/fix docker-compose usage
* Remove bad direct links to non-docsite docker-compose and replace with inline file
* Fix quotes around base url ENV
* Simplify config volume mapping
2024-10-03 09:11:56 -04:00
FoxxMD 7aac664916 docs: Update jellyfin instructions in quickstart to use API Source 2024-10-03 08:55:36 -04:00
FoxxMD 12c4a014f2 fix(jellyfin): Fix tests not exiting 2024-09-30 13:43:15 -04:00
FoxxMD 1eada12714 fix(jellyfin): Fix naming in documentation for allow/disallow options
* Fix documentation and jellyfin example to match code
* Add tests for jellyfin api config parsing and valid activities
* Fix jellyin api ENV naming to match code
* Fix config parsing missing lowercase transform

Fixes #196
2024-09-30 11:07:43 -04:00
FoxxMD 5adb831136 Add multi-runner WIP tests to ACT
Would like to move to this to improve build time on GH Actions but its not working yet. Will also be good when GH Runners release arm nodes publicly.
2024-09-27 16:30:02 -04:00
FoxxMD 5b8a8a7449 ci: Refactor ACT for more thorough usage
* All required files in the same file by using .actrc
* Include example .secrets and .env
* Update README with usage
* Refactor actTest to make docker build steps optional with ENV
2024-09-27 11:41:38 -04:00
FoxxMD 005dae76ab chore: Bump version for release 2024-09-27 09:02:04 -04:00
FoxxMD cb2c7276dd docs: Add scrobble modification logging docs 2024-09-24 11:45:50 -04:00
FoxxMD 54fe61c51c docs: Add when condition docs and examples 2024-09-24 11:43:56 -04:00
FoxxMD bdf27ec421 fix: Freeze typescript to fix weird unused error directive problem 2024-09-23 09:49:31 -04:00
FoxxMD 3addd39fc5 chore(vscode): Add default built task 2024-09-23 09:49:10 -04:00
FoxxMD 19bf456e47 Fix version for yt-music-api 2024-09-23 08:39:46 -04:00
Matt Foxx 66bf889e9f Merge pull request #186 from FoxxMD/GH-185/condition
feat(modifications): Implement "when" conditions
2024-09-23 08:34:53 -04:00
FoxxMD 09ea158734 Update lock file after rebase 2024-09-23 08:34:09 -04:00
FoxxMD 88c1b553e3 feat(modifications): Refactor hooks to accept an array of parts
* Hook can be single object part or array of parts
* All arrays are processed

#185
2024-09-23 08:32:40 -04:00
FoxxMD 1d1fd1b54d feat(modifications): Implement "when" conditions for scrobble modifications
Enable using multiple "when" conditions of regex matching to determine if a hook or individual part-rules should run

#185
2024-09-23 08:32:37 -04:00
FoxxMD 9d3267b1fc Add dockerignore entries for vscode 2024-09-23 08:29:47 -04:00
FoxxMD 22a04adde0 docker: Update dockerfile to ignore devcontainer and vscode settings 2024-09-20 12:33:23 -04:00
FoxxMD 1dba90ceb5 docs: Update development docs for VSCode usage 2024-09-20 12:31:34 -04:00
FoxxMD 4e9794d574 feat: Add VSCode devcontainer 2024-09-20 12:17:37 -04:00
FoxxMD 23a747d3a9 feat: Add VSCode native dev settings
* Add settings.json to ensure terminal runs with correct node version
* Add launch settings for debugging individual files, whole app, and mocha tests
2024-09-20 11:57:37 -04:00
FoxxMD 4678c35453 Merge remote-tracking branch 'origin/master' 2024-09-19 12:56:03 -04:00
FoxxMD c153e53c5b feat: Improve upstream scrobble refresh logic and caching controls
* Add refreshMinInterval to prevent hammering upstream services
* Refactor refresh logic to be simpler and account for backlogged tracks
* (test): Improve play generation utils for testing
* (test): Refactor testing for upstream scrobble refreshing to use more actual scrobbler class behavior
2024-09-19 12:55:50 -04:00
FoxxMD e20ee09bbb chore: Update faker to official release 2024-09-19 12:53:00 -04:00
Matt Foxx 619687472d Merge pull request #192 from FoxxMD/GH-191/plex-formdata-fix
fix(plex): Refactor plex webhook formdata parsing to be more defensive
2024-09-17 08:51:18 -04:00
FoxxMD b74f23f6d1 fix: Don't use es module in vite config because of gross esm/cjs reasons 2024-09-13 17:07:32 -04:00
FoxxMD d073b4c402 fix: Strip wrapping quotes from base url
Compensates for scenarios where a user accidentally encloses value in quotes when it shouldn't be

fixes #187
2024-09-13 16:51:02 -04:00
FoxxMD 258883d88b test: Add missing version prop for test components 2024-09-13 16:49:37 -04:00
FoxxMD 29c8d6236a fix(plex): Refactor plex webhook formdata parsing to be more defensive
* Move file parsing to own function
* Add some typings to file parsing to help with development (typings are bad)
* Defensively check structure of formdata for nested arrays since Plex seems to waffle on how it sends data (fixes #191)
* Add logging for all files found to help with future end-user debugging
2024-09-13 13:54:50 -04:00
FoxxMD a1443594f7 docs: Fix youtube music identifier
Closes #190
2024-09-12 08:46:49 -04:00
Matt Foxx ac2e9570e5 Merge pull request #189 from jackwilsdon/fix-docs-link
docs: fix link
2024-09-11 15:00:04 -04:00
FoxxMD 983b1c67ac fix: Bump @foxxmd/logging version to fix read-only system issues 2024-09-11 14:48:27 -04:00
Jack Wilsdon 764493f5c9 docs: fix link 2024-09-11 19:35:39 +01:00
Matt Foxx d7981e7cc0 Merge pull request #188 from jackwilsdon/fix-local-docs
fix: local documentation
2024-09-11 08:33:03 -04:00
Jack Wilsdon 11fbe97325 fix: local documentation
express-static prefers directories over files when using the extensions
option, meaning an extensionless route will always route to the
directory instead of the file (expressjs/serve-static#138).
2024-09-10 21:09:09 +01:00
Matt Foxx 3f37a63439 Merge pull request #182 from FoxxMD/dependabot/npm_and_yarn/npm_and_yarn-cccb3c3a64
chore(deps): bump the npm_and_yarn group across 2 directories with 7 updates
2024-08-30 11:03:09 -04:00
dependabot[bot] ee78b95617 chore(deps): bump the npm_and_yarn group across 2 directories with 7 updates
Bumps the npm_and_yarn group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [formidable](https://github.com/node-formidable/formidable) | `2.1.2` | `3.5.1` |
| [@types/formidable](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/formidable) | `2.0.6` | `3.4.5` |
| [ntfy](https://github.com/ffflorian/node-packages) | `1.5.4` | `1.6.2` |
| [postcss](https://github.com/postcss/postcss) | `8.4.38` | `8.4.41` |
| [xml2js](https://github.com/Leonidas-from-XIV/node-xml2js) | `0.5.0` | `0.6.0` |
| [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) | `7.24.7` | `7.25.6` |

Bumps the npm_and_yarn group with 2 updates in the /docsite directory: [postcss](https://github.com/postcss/postcss) and [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse).


Updates `formidable` from 2.1.2 to 3.5.1
- [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/v3.5.1)

Updates `@types/formidable` from 2.0.6 to 3.4.5
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/formidable)

Updates `ntfy` from 1.5.4 to 1.6.2
- [Commits](https://github.com/ffflorian/node-packages/compare/ntfy@1.5.4...ntfy@1.6.2)

Updates `postcss` from 8.4.38 to 8.4.41
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.38...8.4.41)

Updates `xml2js` from 0.5.0 to 0.6.0
- [Commits](https://github.com/Leonidas-from-XIV/node-xml2js/compare/0.5.0...0.6.0)

Updates `@babel/traverse` from 7.24.7 to 7.25.6
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.25.6/packages/babel-traverse)

Updates `axios` from 1.7.2 to 1.7.4
- [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.7.2...v1.7.4)

Updates `postcss` from 8.4.39 to 8.4.41
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.38...8.4.41)

Updates `@babel/traverse` from 7.24.7 to 7.25.6
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.25.6/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: formidable
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: "@types/formidable"
  dependency-type: direct:development
  dependency-group: npm_and_yarn
- dependency-name: ntfy
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: postcss
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: xml2js
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: "@babel/traverse"
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: axios
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: postcss
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: "@babel/traverse"
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-30 14:55:40 +00:00
FoxxMD d7091f8a8e chore(ci): Add dependabot PR test gh action 2024-08-30 10:53:09 -04:00
FoxxMD 4b5cdade77 chore(ci): Disable alpine builds for PR image 2024-08-30 10:37:21 -04:00
FoxxMD cb67d1ae40 chore!: Fix latest/tag for images and disable alpine builds
* Ensure pre-release tags don't also get latest tag
* Disable alpine builds
  * Only increases testing and build complexity and there has been no feedback from users about needing it
2024-08-30 10:26:23 -04:00
FoxxMD 676af4a2bf Merge branch 'develop' 2024-08-30 09:54:24 -04:00
FoxxMD 0feb02babe chore: Refactor git/dev workflow to use master as unstable branch
* Aligns with "normal" default branch usage better, allows dependabot to actually run
* Change image publishing to push tagged images as 'latest' and master images as `edge`
2024-08-30 09:53:52 -04:00
FoxxMD 90c481b12b Merge remote-tracking branch 'origin/develop' into develop 2024-08-30 09:23:02 -04:00
FoxxMD 59fed488ba feat(jellyfin): Update schema 2024-08-30 09:22:52 -04:00
Matt Foxx 8431e7b6b2 Merge pull request #172 from FoxxMD/jellyfinApi
feat: Refactor Jellyfin source to use Jellyfin API
2024-08-30 09:13:56 -04:00
FoxxMD be30cdd540 feat(jellyfin): Implement partial WS client for future use 2024-08-29 15:16:49 -04:00
FoxxMD 54927ed77c fix(jellyfin): Map AlbumArtist Name property 2024-08-29 11:30:59 -04:00
FoxxMD 97f974d931 update vlc source building after rebase 2024-08-29 11:29:41 -04:00
FoxxMD 0de2cc0922 feat(jellyfin)!: Refactor Jellyfin source to use Jellyfin API
* Implement new jellyfin source using JF typescript api client library for communication
  * Use either API Key or username/password
* Implement real-time scrobble monitoring
* Implement feature parity filters from old jellyfin source (users) and add devices filters
* Refactor documentation for new configuration with api and add migration steps for webhook users
* Add deprecation warning to jellyfin webhook source
2024-08-29 11:28:27 -04:00
FoxxMD e80ad81f05 Merge branch 'develop' 2024-08-28 09:35:28 -04:00
FoxxMD a3960421d7 chore: Bump version for release 2024-08-28 09:35:13 -04:00
FoxxMD 2f2eeeb13a docs(youtube): Add FAQ entries for YTM auth and scrobble issues 2024-08-28 09:32:46 -04:00
Matt Foxx eeb026abf0 Merge pull request #163 from FoxxMD/ytMusicDebug
Improve YTM experience
2024-08-28 09:11:38 -04:00
FoxxMD 0fed96f59c Merge branch 'develop' 2024-08-28 09:07:12 -04:00
Matt Foxx 8369939135 Merge pull request #181 from FoxxMD/malojaDuplicateHandling
fix(maloja): Improve handling for Maloja warnings-as-errors
2024-08-27 16:54:11 -04:00
FoxxMD 08056a650f fix: Actually store recent scrobbles
Refresh scrobbles refactor missed actually storing scrobbles on refresh
2024-08-27 09:40:30 -04:00
FoxxMD 5b53b7490c fix(maloja): Improve handling for Maloja warnings-as-errors
* Fixes incorrect handling for duplicate scrobble error from Maloja 3.2.2 #180
* More defensive Maloja scrobble parsing
2024-08-26 11:09:49 -04:00
Matt Foxx ce95c3ae8c Merge pull request #179 from FoxxMD/vlc
Implement VLC as Source
2024-08-26 10:55:01 -04:00
FoxxMD 5e0c4e3e77 docs(vlc): Some fixes for VLC docs
tags <> in code block in details causes parsing error
2024-08-23 09:00:14 -04:00
FoxxMD 10bf48df40 docs(vlc): Add VLC docs 2024-08-22 17:30:37 -04:00
FoxxMD ae935798f3 chore: Update schema 2024-08-22 16:44:15 -04:00
FoxxMD bf71f8cf86 feat(vlc): Use regex from configuration to extract metadata from filenames 2024-08-22 16:44:08 -04:00
FoxxMD 4206696f0d feat(vlc): Better metadata parsing 2024-08-22 15:07:47 -04:00
FoxxMD 2903662f8b feat: Implement POC vlc Source #177 2024-08-22 13:11:12 -04:00
FoxxMD 469847e4b7 feat(youtube): Use just-validated history as recently played on new plays #156
Rather than generating our own just use the known "good" history we just compared against to see if this improves consistency.
2024-08-21 13:14:38 -04:00
FoxxMD d7a86b0401 feat(youtube): Increase level of auth update logging when configured to output changes #158
Increasing to INFO makes the signal better for long-running MS processes instead of requiring logging all of DEBUG
2024-08-21 13:14:38 -04:00
FoxxMD 4fdd346c00 Merge branch 'refs/heads/develop' 2024-08-20 09:18:01 -04:00
FoxxMD 4f26b63ab1 chore: Bump version for release 2024-08-20 09:17:28 -04:00
FoxxMD 0fed91302c chore(ci): Update untagged package schedule to run once a week
Doesn't need to run every day.
2024-08-19 11:55:47 -04:00
FoxxMD 831cefd9a7 docs(deezer): Deprecate due to discontinued API support
Deprecate Source with warning message and update docs. #175
2024-08-19 11:25:11 -04:00
FoxxMD 114a3dda8b feat(ui): Add timestamp to recent pages #167 2024-07-26 10:07:25 -04:00
FoxxMD 55ddd2c5a0 docs: Mention data modification in readme 2024-07-26 09:38:18 -04:00
FoxxMD 1d276186f8 feat: Implement removing field if field is an empty string after transforming 2024-07-26 09:30:58 -04:00
FoxxMD fb3886b981 docs: Modification improvements 2024-07-26 09:19:11 -04:00
FoxxMD 2bed7be654 docs: Fix compare example 2024-07-25 17:47:57 -04:00
FoxxMD 5c460ad747 docs: Add Play Transform docs 2024-07-25 17:28:49 -04:00
FoxxMD 32220d7509 feat: Implement play transforms in source/client logic 2024-07-25 16:16:58 -04:00
FoxxMD b0265001f8 feat: Improve logging for play transforms
* Use a proper child logger and error messages on catch
* Log full play string before => after instead of just parts
2024-07-25 15:12:39 -04:00
FoxxMD 9ede4634cb test: Use faker 9.0RC for more plausible artist/album names in generated data 2024-07-25 15:04:37 -04:00
FoxxMD 0e008ecd5c feat: Add album option to play string building function 2024-07-25 15:03:39 -04:00
FoxxMD 2ae82fdd9e feat: Implement common play transform functionality 2024-07-25 13:43:34 -04:00
Matt Foxx 3b430b95ed Merge pull request #174 from FoxxMD/issue-173-scrobbler-improvement
Enable more agressive scrobble checking behavior
2024-07-25 09:18:07 -04:00
FoxxMD e907892c4a refactor(scrobbler): Change refresh force to user-configurable staleness time
Use refreshStaleAfter to force refresh to user can configure the time.

Allows more nuanced refreshing behavior as well as always refresh (0 seconds)
2024-07-24 12:06:20 -04:00
FoxxMD d9cd3764b9 refactor(scrobbler): Consolidate refresh logic and add more logging
* Move recentScrobbles and generic refresh logic into abstract class
* New abstract function for scrobbler children to implement to get scrobbler-specific play lists on refresh
* Implement shouldRefereshScrobble with logging for better insight into why or why not a scrobbler refreshed upstream scrobbles
2024-07-24 11:20:09 -04:00
FoxxMD 9c2bf05a24 refactor(listenbrainz): Simplify scrobble api calls and logging 2024-07-24 10:31:37 -04:00
FoxxMD 91e52c4c5d feat(musikcube): Provide more error logging and example config
* Update iso-websockets to fix typing exports and get node network error from WS error
* Add url example to config
2024-07-23 12:11:19 -04:00
FoxxMD b41d7f539f feat(scrobble): Enable forcing existing scrobbles refresh on every scrobble #173 2024-07-23 10:48:35 -04:00
FoxxMD 9735b980d7 fix(tests): Do not re-use scrobbler between tests 2024-07-19 14:44:39 -04:00
FoxxMD 706483ed06 feat: On polling error retry check source is initialized and reinitialize if not 2024-07-19 14:36:50 -04:00
FoxxMD 7760776a5f feat(mpd): Use idle event to wake up from polling early 2024-07-19 14:36:27 -04:00
FoxxMD 8b9a7893d3 feat(mpd): Implement MPD Source MVP 2024-07-19 12:10:51 -04:00
FoxxMD 729250a2cc Merge branch 'refs/heads/develop' 2024-07-18 14:18:39 -04:00
FoxxMD f14203f85e docs: More improvements to configuration docs 2024-07-18 11:14:40 -04:00
FoxxMD 60e880f7f2 Merge branch 'refs/heads/develop'
# Conflicts:
#	docsite/docs/configuration/configuration.md
2024-07-17 12:47:23 -04:00
FoxxMD 16cef0ae65 chore: Bump versions for release 2024-07-17 12:46:18 -04:00
FoxxMD 12ff1f421e docs: Use relative paths for volume mounting 2024-07-17 11:46:00 -04:00
FoxxMD 61c33da2b0 docs: Fix quickstart link 2024-07-17 11:39:41 -04:00
FoxxMD 8b0c7205b7 docs: Add quick start guide 2024-07-17 11:33:56 -04:00
FoxxMD b02c96ada8 docs: Parse and render config examples from repository 2024-07-17 09:51:44 -04:00
FoxxMD 52b20e4c8e docs: Update kitchensink code block titles 2024-07-16 15:37:09 -04:00
FoxxMD a94ac8284f docs: refactor installation for docusaurus 2024-07-16 15:28:09 -04:00
FoxxMD ee399136a9 docs: Lean 100% into docusaurus documentation
Since docs are now generated alongside builds and hosted on the app server there is no longer a barrier to using docusaurus docs as the main documentation presentation. Any user looking for docs can use the GH Pages hosted site or a "versioned" docs site hosted on their MS app. Due to this we can fully commit to docusaurus and take advantage of MDX layouts to simplify docs.

* Replace env/file configs headings in configuration with MDX tabs to make reading easier
* Remove superfluous TOCs
* Replace internal links in repo readme with links to docsite
2024-07-16 14:11:14 -04:00
FoxxMD ab54422acf docs: Replace manual notes with admonitions 2024-07-16 11:58:09 -04:00
FoxxMD d56c5720eb docs(flatpak): Fix typo in docs website link 2024-07-16 11:46:13 -04:00
FoxxMD 040de4b000 docs(flatpak): Implement bash setup script and move flatpak instructions to docsite
* setup.sh takes care of building generated sources for project/docsite and building flatpak app
* Modify flatpak manifest to include two generated sources files and build docsite files
* Update flatpak docs with script usage and generated sources changes, move to docusauraus doc site
2024-07-16 11:34:15 -04:00
FoxxMD 815a774e3d docs(fix): Fix broken links/anchors 2024-07-15 14:24:00 -04:00
FoxxMD b2e7f58e14 docs(fix): Add missing docsite build steps to alpine docker variant 2024-07-15 13:50:37 -04:00
FoxxMD 8d124a20ac docs(fix): Update package scripts to use npx to invoke docusarus 2024-07-15 13:44:07 -04:00
FoxxMD d1a4a54d2a docs: Implement self-hosted docs
* Add npm scripts for installing/building docusaurus from main project
* Set base url based on ENV
* Build docsite in docker image
* Add link to doc in dashboard and link to github
* Update nodejs install instructions to include doc build command
* Update GH pages workflow to use correct base url
* Add hint page to main app when docs are not built
2024-07-15 13:26:57 -04:00
FoxxMD 049bb06762 docs: Make docs site more prominent in readme 2024-07-12 16:55:48 -04:00
FoxxMD e5dd473981 Merge branch 'refs/heads/develop' 2024-07-12 14:39:53 -04:00
FoxxMD ca5cc3bb30 docs(feat): Add TOC to dev docs and links in readme 2024-07-12 14:37:52 -04:00
FoxxMD 444d75c803 docs(feat): Add architecture overview, common dev structures, and dev guidance for Sources 2024-07-12 14:31:11 -04:00
FoxxMD ccda3a6098 Merge branch 'refs/heads/develop' 2024-07-09 16:06:05 -04:00
FoxxMD ce1c70a4e1 ci: Ignore flatpak and docsite for image deploy 2024-07-09 16:05:15 -04:00
FoxxMD 5fcfa979c5 docs: Improve TOC for docusaurus and github
* Use lower headers so docusaurs sidebar TOC is more accurate
* Specify header levels for config/FAQ to make sidebar more concise
* Wrap inline TOC in details block -- so docusaurs users don't have to see it twice but its still available to github viewers
2024-07-09 15:56:44 -04:00
FoxxMD ec4d609ba1 docs: Update docusaurs playground schema for MS to latest 2024-07-09 15:55:07 -04:00
FoxxMD b42278a372 docs: Update docusaurus to v3 2024-07-09 15:54:45 -04:00
FoxxMD 1f96035ba9 Merge branch 'refs/heads/develop' 2024-07-09 10:25:29 -04:00
FoxxMD 332edb5dca chore: Bump flatpak release history 2024-07-09 10:25:17 -04:00
FoxxMD c5681d31eb Merge branch 'refs/heads/develop' 2024-07-09 09:53:03 -04:00
FoxxMD 685d70c639 chore: Bump version for release 2024-07-09 09:51:43 -04:00
FoxxMD 848878bc5e docs: Update screenshot 2024-07-09 09:51:33 -04:00
FoxxMD 088d063d38 fix: use correct ordering for version priority 2024-07-05 11:26:23 -04:00
FoxxMD 4e493db987 refactor: Use @foxxmd/get-version to render app version 2024-07-05 10:42:41 -04:00
FoxxMD 6d017d4b27 chore(ci): Update setup-node to v4 to squash node deprecation warnings 2024-07-03 10:11:52 -04:00
FoxxMD a34066d650 fix(docker): Typo in Dockerfile name 2024-07-03 10:09:24 -04:00
FoxxMD 0e34d63c84 feat(docker)!: Use debian image as default
Sporadic DNS issues with alpine have caused a number of issues #160 #126 #134

Use debian as default since it has been proven to be stable. The image size increase is worth squashing these issues.
2024-07-03 10:07:35 -04:00
FoxxMD 9dca8ebfa0 fix(chromecast): Add missing avahi-utils package for debian image 2024-07-03 09:55:58 -04:00
FoxxMD c908aea1ae ci: Better package cleanup and manual triggers 2024-06-26 16:39:42 -04:00
FoxxMD c7a58bbce6 ci: Add package cleanup 2024-06-26 16:26:30 -04:00
FoxxMD b26f303479 ci: remove test workflow 2024-06-26 15:52:52 -04:00
FoxxMD 13d64d2ce2 ci: Implement gated automated PR building
Only checkout/build PR branches if labelled first
2024-06-26 15:44:12 -04:00
FoxxMD ddb7fb3d6e ci: Use checked out repo to get git info rather than GH variables 2024-06-26 14:05:33 -04:00
FoxxMD 457967bfb2 Pass default APP_VERSION env as build arg 2024-06-26 13:23:45 -04:00
FoxxMD 734742ecbe Add APP_VERSION parsing to github actions 2024-06-26 12:37:09 -04:00
FoxxMD 26dab3870a Simplify version info on frontend by using api endpoint 2024-06-26 12:37:08 -04:00
FoxxMD ca9acbb089 Add git versioning check on backend 2024-06-26 12:37:08 -04:00
FoxxMD f78d6fec1d fix(youtube): Catch auth issues that occur during polling #158 2024-06-26 12:37:08 -04:00
FoxxMD bafb852545 feat(youtube): Use full-fat list diffing to watch for new plays #156
* Use superdiff to diff PlayObject lists and detect changes as well as append/prepend scenarios
* Replace YTM recently played logic with list diffing, only accept prepend-validated lists
  * On non-prepend scenarios replace existing recently played and log human readable diff
2024-06-26 12:37:08 -04:00
FoxxMD a755db45b9 fix: Fix ntfy imports 2024-06-26 12:37:08 -04:00
FoxxMD b9715f2159 chore: update dependencies 2024-06-26 12:37:07 -04:00
FoxxMD e90dbaf691 chore: npm audit fixes 2024-06-26 12:37:07 -04:00
FoxxMD 08ac55f718 fix(webscrobbler): Fix formatting of slug url in logs 2024-06-26 12:37:07 -04:00
FoxxMD 5bf831e236 feat(youtube): Sync cookies with those returned from YTM responses #158
* Patch youtube-music-ts-api to use updated cookies from response and provide a callback on update
* Implement currentCreds/build init data and read from MS-updated creds if available
* Write to currentCreds when ytm-ts-api invokes auth update callback and optionally log what parts changed based on config options
2024-06-26 12:37:07 -04:00
FoxxMD 1905804f2a fix(webscrobbler): Add preflight route #157 2024-06-26 12:37:07 -04:00
FoxxMD afc931ac8c docs: Use vars instead of secrets 2024-06-26 12:37:07 -04:00
FoxxMD 9fb032e030 docs: Add scripting on deploy for plausible analytics 2024-06-26 12:37:07 -04:00
FoxxMD 09b0c96263 feat(youtube): Surface library history playlist and raw recently played for debugging #156 2024-06-26 12:37:06 -04:00
FoxxMD 7c66816023 Implement musikcube source 2024-06-26 12:35:13 -04:00
FoxxMD 3e7e5df5de fix: Missed package-lock.json commit 2024-06-05 11:01:12 -04:00
FoxxMD 1c84a74034 fix(vite): Use default base string if no other path detected
* Update Vite dep
* Use inlineViteConfig for vite-express middleware and detect correct base string
* Refactor vite.config.ts to detect correct base string
2024-06-05 10:58:52 -04:00
FoxxMD 09969dd9d7 fix(spotify): Convert URL to string for redirectUri 2024-06-05 10:21:18 -04:00
FoxxMD 63b57e1113 docs: Clarify BASE_URL usage and add more examples #155 2024-06-04 15:51:30 -04:00
FoxxMD 585677e515 fix: Refactor Base URL normalization to cover more cases #155
* Move code into testable function
* Fix port erasure when url uses standard port but it is explicitly specified
* Simplify building default url and fix using undefined mainPort variable
* Fix port erasure when https protocol is explicitly specified
* Refactor localUrl usage as URL instead of string
  * Implement pathname joining on URL to make handling trailing/leading slashes take place in nodejs lib
* Implements tests for URL building/manipulation
2024-06-04 15:44:28 -04:00
FoxxMD 57f2b5f7e5 feat: Update normalize-url to prevent port erasure #155 2024-06-04 15:39:47 -04:00
FoxxMD 3597735556 feat(client): Implement initial refresh count and config control #153
* Refresh recent scrobbles with a higher limit after initialization (maximum supported by api call)
* Implement controlling count in client config
2024-05-16 15:22:24 -04:00
FoxxMD 2442cf2286 refactor: Move all init logic into common abstract base class 2024-05-16 15:01:07 -04:00
FoxxMD 97438e13d2 refactor(client): Use same init logic as source 2024-05-16 14:50:26 -04:00
FoxxMD 57dff9d122 feat!(source): Increase backlog limit defaults and add config control #153
* Increase default backlog limits to maximum pagination supported by each source
* Add scrobbleBacklogCount config property so user can explicitly set # of tracks to backlog
2024-05-16 13:43:44 -04:00
FoxxMD 963fc792a0 fix: Fix clientDefaults interface 2024-05-10 12:53:04 -04:00
FoxxMD 35a6418f38 docs!: Update schema 2024-05-10 12:48:08 -04:00
FoxxMD 3c0b951132 refactor(config)!: Move common options out of client config and into new option property 2024-05-10 12:44:16 -04:00
FoxxMD 3dc9e7d8b4 refactor(config)!: Move remaining common options out of source data and into options
* Move logPayload and logFilterFailure to options
* Disallow use of "options" property in common data
2024-05-10 12:38:09 -04:00
FoxxMD ab372b8357 refactor(config)!: Move source retry config to options 2024-05-10 12:07:39 -04:00
FoxxMD c79f04581f refactor(config)!: Move scrobble thresholds to options 2024-05-10 12:01:45 -04:00
FoxxMD 0dcd049453 Merge branch 'refs/heads/develop' 2024-04-11 09:39:08 -04:00
FoxxMD 7170b09586 chore: Bump version for release 2024-04-11 09:38:55 -04:00
FoxxMD 3d893b126f refactor(logging): Balance debug/verbose level logging pt.1
* Move some debug => verbose for non-repeating logs that are relevant to state, notifications, and scrobbling
* Move some verbose => debug that were repeating and making verbose level noisy
2024-04-10 11:25:27 -04:00
FoxxMD fb73b8b106 feat(mpris): Less noisy error handling
* Convert DBus non-standard thrown error data to regular Errors
* Only warn about position errors if error message is unexpected
2024-04-10 10:57:02 -04:00
FoxxMD cede77e3fa refactor: Remove pony-cause dependency
Use built-in error cause and adapt helper functions from pony-cause so we can remove a dependency
2024-04-10 10:56:04 -04:00
FoxxMD 0572d6a308 refactor(logging): Reduce noise for polling interval logging 2024-04-10 10:19:58 -04:00
FoxxMD 5967e3df46 feat: Implement notifications via Apprise 2024-04-05 12:07:41 -04:00
FoxxMD 6b1ee83654 docs: Add config section and config example for disabling web server #150 2024-04-04 09:35:27 -04:00
FoxxMD 0af1496a0f chore: Update proxies 2024-04-04 09:30:42 -04:00
FoxxMD 52b08efd88 feat: Implement option for disabling web server #150
If a user does not need the dashboard or has security concerns about ingress and cannot control their hosting environment this enables reducing attack surface.

Closes #150
2024-04-04 09:30:33 -04:00
FoxxMD 7a3e4e56e2 chore: Remove some unused packages 2024-04-02 10:15:41 -04:00
FoxxMD abe6638f66 refactor: Use updated logger to reduce log file path logic complexity 2024-03-28 12:28:56 -04:00
FoxxMD eb62c2cea9 refactor: optimize imports
* Remove unused imports
* Normalize spaces in curly braces
* Sort by module (npm packages, then relative imports)
2024-03-28 09:20:46 -04:00
FoxxMD 7e6eddce3e refactor: Reduce usage of ErrorWithCause
Error Cause (https://github.com/tc39/proposal-error-cause) was finalized and introduced in ES2022 so we don't need to ponyfill this anymore. Still using the helpful helper functions thought.
2024-03-28 09:20:45 -04:00
FoxxMD bc517e0cb0 Merge branch 'develop' 2024-03-26 09:37:03 -04:00
FoxxMD 41900b0048 chore: Bump version for release 2024-03-26 09:36:37 -04:00
FoxxMD 9ea71b931c fix(flatpak): Fix missing package.json needed to run as esm module 2024-03-26 09:04:31 -04:00
FoxxMD 64f222b5b7 fix: Add binary alias(?) to pass appstream guidelines
flathub/io.github.foxxmd.multiscrobbler#14
2024-03-25 09:49:01 -04:00
FoxxMD 6c27406803 fix: Fix colored output being applied to file logging 2024-03-21 17:08:14 -04:00
FoxxMD 2f51e7ad99 feat: Enable colorizing docker log output 2024-03-21 16:29:33 -04:00
FoxxMD d86dd63236 fix: Fix log name to match existing logs names 2024-03-21 15:50:43 -04:00
FoxxMD f794133915 fix: Fix missing parent logger 2024-03-21 15:33:06 -04:00
FoxxMD c7d2e7acc2 feat(ui): Use ansi-to-react for log coloring
Vastly simplifies log formatting on the dashboard
2024-03-21 15:33:06 -04:00
FoxxMD 2dde3840c6 refactor(logging): Replace winston with @foxxmd/logging 2024-03-21 15:33:06 -04:00
FoxxMD 4675847fec fix(flatpak): Reduce summary length 2024-03-11 16:14:45 -04:00
FoxxMD 830d1a2243 fix(flatpak): Add missing developer name
https://github.com/flathub/io.github.foxxmd.multiscrobbler/pull/13#issuecomment-1949888692
2024-03-06 10:10:13 -05:00
FoxxMD 8a43f0ded5 test arm64 building for debian docker image using updated node version #126
Using latest LTS (20) seems to have fixed TLS timeout/connection reset issue seen when trying to `npm ci` on LTS 18
2024-02-19 15:32:24 -05:00
FoxxMD af24e0ffbe chore(docker): Remove unused dependencies 2024-02-19 15:09:55 -05:00
FoxxMD d3a70bf2c1 fix: Add comment for future typed linting 2024-02-19 13:24:53 -05:00
FoxxMD a62fe705f9 fix: Fix lint errors 2024-02-19 13:02:28 -05:00
FoxxMD 1c6407238c feat: Add initial linting config 2024-02-19 12:27:34 -05:00
FoxxMD ec897b185c fix: Fix logger used for tests to always be 'noop'
Cleans up logging output during tests
2024-02-19 10:50:10 -05:00
FoxxMD 69e15b3797 fix: Refactor static arrow function class fields to normal functions #143
Something about using arrow function form for static class methods causes node debugger to crash.

Closes #143
2024-02-19 10:38:59 -05:00
FoxxMD dae4af070f chore: Bump mocha version 2024-02-15 13:01:22 -05:00
FoxxMD beaf0f3bfd chore(!): Upgrade Node to LTS for --import support
So that we can use tsx with official --import rather than requiring (old-style) which doesn't work with string-sameness esm for some reason

https://github.com/mochajs/mocha/issues/5002
https://github.com/mochajs/mocha-examples/pull/76
2024-02-15 12:40:02 -05:00
FoxxMD 8057982184 chore: Upgrade string-sameness
* Removes dependency on unmaintained dice coeffecient library
* New version is esm-only library
2024-02-15 12:27:59 -05:00
FoxxMD 9a5a808780 fix: Remove alt single quote when normalizing strings 2024-02-15 12:05:50 -05:00
FoxxMD d2d5f4a705 chore: Remove more unused packages 2024-02-15 10:10:01 -05:00
FoxxMD 4aa875660f chore: Remove unused es6-error package 2024-02-15 09:25:32 -05:00
FoxxMD 5ab8f00aeb Fix microsecond conversion 2024-02-15 09:23:26 -05:00
FoxxMD 1e1b5f56ce refactor: Replace dbus-next with dbus-ts #142 2024-02-14 16:32:38 -05:00
FoxxMD 49b9ee96f3 fix: Fix ts schema generation import issue
Changing tsconfig resolution caused generation to generate new (bad?) import lines. Replacing with regex from https://github.com/YousefED/typescript-json-schema/issues/582 returned ref/definitions to normal
2024-02-14 09:47:00 -05:00
FoxxMD aef7716634 fix(webscrobbler): Fix missing wildcard to match slug route #137
Closes #137
2024-02-14 09:45:14 -05:00
FoxxMD 57f2e1d500 feat: Log more URL when route is unknown 2024-02-14 09:44:47 -05:00
FoxxMD 41c696992a feat: Add legacy fallback options for subsonic communication #136
* Add config options for using legacy authentication and ignoring TLS cert errors
* Fallback to legacy auth if subsonic error code 41 is detected
2024-02-13 15:59:17 -05:00
FoxxMD f231d4e1e4 ci: Implement test checks for PRs and image publishing 2024-02-08 13:07:13 -05:00
FoxxMD 0efbe41b7e test: Use tsx through mocha config 2024-02-08 12:14:11 -05:00
FoxxMD be9675bb10 fix: Handle when no existing scrobble match with score greater than 0 #130
https://github.com/FoxxMD/multi-scrobbler/issues/130#issuecomment-1934452275
2024-02-08 11:51:00 -05:00
FoxxMD 4e45d35925 test: Add test that handles no existing scrobble match with score greater than 0 #130
Detects track string error thrown reported via https://github.com/FoxxMD/multi-scrobbler/issues/130#issuecomment-1934452275
2024-02-08 11:47:58 -05:00
FoxxMD a5e293554c Merge branch 'develop' 2024-02-06 09:28:35 -05:00
FoxxMD c76d4822fe docs: Fix which host debian image is available for 2024-02-06 09:28:22 -05:00
FoxxMD 0f8fd5cc0e chore: Forgot to bump version in lock file 2024-02-06 09:28:02 -05:00
FoxxMD 8b27890254 Merge branch 'develop' 2024-02-05 11:53:18 -05:00
FoxxMD 8747c8375e chore: Bump version 2024-02-05 11:52:47 -05:00
FoxxMD 50f27c110c Merge branch 'develop' 2024-02-05 11:47:40 -05:00
FoxxMD c1f8997c01 feat: Implement remaining recently played endpoints 2024-02-05 11:40:06 -05:00
FoxxMD 12ed767961 ci: Bump action versions to fix node deprecations 2024-02-05 10:47:58 -05:00
FoxxMD 335808217b docs: Add debian docker image network workaround to docs #126 #134 2024-02-05 10:38:15 -05:00
FoxxMD b5bb7024fc docs: Add workarounds for rollup issues and flatpak disclaimer 2024-02-05 10:28:40 -05:00
FoxxMD ac2806dc66 fix(frontend): Fix scss division deprecation warnings 2024-02-05 10:14:11 -05:00
FoxxMD 9d149dab49 Try to use direct node binary distribution #126 2024-02-02 12:55:58 -05:00
FoxxMD 451404c0f9 Disable arm64 debian builds and document issues in dockerfile #126 2024-02-02 11:05:09 -05:00
FoxxMD a3f1f077ce Revert fancy default negation
Doesn't work
2024-02-01 12:18:30 -05:00
FoxxMD dcd13833ae Ignore secrets files 2024-02-01 12:16:03 -05:00
FoxxMD 78b732b75c Update metadata-action version 2024-02-01 11:55:57 -05:00
FoxxMD 3152933937 chore: Add matrix for building debian flavor tag 2024-02-01 11:54:40 -05:00
FoxxMD 6c6126bb27 chore: make container image build tags agnostic to default branch name 2024-02-01 11:48:15 -05:00
FoxxMD ad13b7687d feat: Implement debian-based image #126 2024-02-01 11:39:05 -05:00
FoxxMD 633e366e82 feat: Improved logging for last.fm api network errors #115 2024-01-31 15:52:25 -05:00
FoxxMD 69b1786859 fix: Add patch-package as postinstall script so packages actually get patched 2024-01-31 14:09:31 -05:00
FoxxMD 41839a961a chore: Ignore apple cert extensions 2024-01-31 14:09:06 -05:00
FoxxMD ccb50c64bb fix(lastfm): Patch lfm library to actually implement a request timeout #134 2024-01-31 13:15:20 -05:00
FoxxMD d88ac1b424 feat(source): Start implementing recent from API 2024-01-31 12:36:35 -05:00
FoxxMD c807611e55 fix: Revert debug statement 2024-01-31 11:51:20 -05:00
FoxxMD 155ec1403e feat(sources): Improve Source of Truth usage and presentation to user #134
* Refactor SOT to be a type (may have more in the future)
* Set player stale/orphan interval based on SOT type
* Add note in log when initializing MemorySource which does not use player as SOT
* Add tooltip to UI player when it is not SOT
2024-01-31 11:46:22 -05:00
FoxxMD ecde657c3e feat(lastfm): Implement nowPlaying lfm api call 2024-01-31 10:01:12 -05:00
FoxxMD 6aa8374f25 feat: Add debug logging for user api requests 2024-01-31 09:30:58 -05:00
FoxxMD c2f01473fd fix(scrobbler): Fix erasing non-english characters #121 2024-01-30 14:47:19 -05:00
FoxxMD 41e607c995 refactor(scrobbler): Use simpler method for handling queued scrobbles #130 2024-01-30 13:40:52 -05:00
FoxxMD fee754f15f feat(chrome): Used improved library 2024-01-30 13:24:09 -05:00
FoxxMD d62d18232d chore: Remove some unused packages 2024-01-30 10:30:33 -05:00
FoxxMD a1acab0fed Merge branch 'develop' 2024-01-29 10:35:27 -05:00
FoxxMD d39a7099c7 docs(cast): Add disclaimer about status of source support 2024-01-29 10:34:13 -05:00
FoxxMD 7b2c4f99f3 chore: Bump version for release 2024-01-29 10:33:57 -05:00
FoxxMD b941ff08dc Merge branch 'develop' 2024-01-29 10:25:06 -05:00
FoxxMD 210a74c75f chore: Bump version for release 2024-01-29 10:24:50 -05:00
FoxxMD eafce71cd0 chore(flatpak): Update flatpak release version 2024-01-29 10:24:08 -05:00
FoxxMD d5777b501d chore(flatpak): Update flatpak manifest to work with new app structure 2024-01-29 10:21:44 -05:00
FoxxMD dd60e5eb21 fix(flatpak): Fix missing destination when building locally 2024-01-29 09:49:33 -05:00
FoxxMD c57dbf23e1 chore(maloja): Deprecate support for versions below 3.0.0 2024-01-26 10:31:23 -05:00
FoxxMD cb52adb4de feat: Log when heartbeat tasks start 2024-01-26 10:17:17 -05:00
FoxxMD a75b0fc549 feat(chrome): Reduce device rejection logging noise
Change device rejection log to verbose if we've seen the blacklisted device before
2024-01-26 10:12:20 -05:00
FoxxMD 1a546b72d2 docs(chrome): Add more google cast configuration docs and update TOCs 2024-01-26 09:55:50 -05:00
FoxxMD fd13d268d0 fix(chrome): Typo 2024-01-25 16:47:10 -05:00
FoxxMD 5837fc11a5 fix: Better handling of uncaught exceptions and rejections 2024-01-25 10:54:44 -05:00
FoxxMD efdbbe40e8 fix(chromecast): Remove debugging interval 2024-01-25 09:52:39 -05:00
FoxxMD 5103cf1c12 feat(chromecast): Catch buffering media 2024-01-25 09:50:23 -05:00
FoxxMD e855a3de7d feat(chromecast): Try to connect all discovered interfaces in the event one fails 2024-01-25 09:43:15 -05:00
FoxxMD b90e38d595 fix(chromecast): Fix black/white app name variable assign 2024-01-25 09:15:53 -05:00
FoxxMD 1864f39be0 fix(chromecast): Fix unknown play tracking 2024-01-25 09:11:44 -05:00
FoxxMD 89e80b6f8b fix(chromecast): Debounce avahi discovery 2024-01-24 14:47:15 -05:00
FoxxMD 564295ee64 feat(spotify): Prepare for future spotify lib migration by writing completing credential data
Migrating to @spotify/spotify-web-api-ts-sdk requires that the credentials we provide have valid expires_in/expires data and grant type. Make this migration easier by writing that data now so users upgrading later most likely do not need to re-authenticate.
2024-01-23 15:10:33 -05:00
FoxxMD 61c7f26007 fix: Fix where retries are checked 2024-01-23 11:37:51 -05:00
FoxxMD 54b9ece5b6 fix: Fix when to check connection
Since all sources can now control if they need to check connection or not do not restrict to just those that can poll
2024-01-23 11:27:54 -05:00
FoxxMD 335136561a fix(chromecast): Better handling of client offline events
* Actually remove device if it fails after X retries
* Properly close controllers/platform/client to prevent uncaught errors from castv2
* Move manually configured devices to device discovery function so they can be reconnected on heartbeat (if previously removed)
2024-01-23 10:45:12 -05:00
FoxxMD 56f1cacd07 fix: Fix return values for source init startup 2024-01-23 09:41:40 -05:00
FoxxMD 558e358d0b fix: Fix default import usage 2024-01-22 13:25:31 -05:00
FoxxMD 290e6eecc4 fix: Fix import missing extension 2024-01-22 13:25:17 -05:00
FoxxMD f512f55782 feat(deezer): Fix credentials check during build data stage 2024-01-22 13:21:00 -05:00
FoxxMD ad6eba58d2 feat: improve data init stage log wording 2024-01-22 13:13:29 -05:00
FoxxMD b675aee8ff fix: Fix status text based on initialization OK values 2024-01-22 13:10:12 -05:00
FoxxMD c29ea9c89c fix: Fix status indicator color based on status text 2024-01-22 13:09:43 -05:00
FoxxMD 50016a31a8 fix: Fix isReady logic 2024-01-22 13:09:32 -05:00
FoxxMD 1cecffc30f feat(subsonic): Improve error/response parsing and report errors correcetly on startup #129
* Implement interfaces for response shape and getters for data
* Parse version data from response and use ping endpoint to return info on connection test
* Fix throwing auth/connection errors to include actual cause along with more human-friendly parsed error info
2024-01-22 12:00:53 -05:00
FoxxMD 833273c19d feat: Improve discovery for network exceptions in error causes 2024-01-22 12:00:50 -05:00
FoxxMD 35603d0caf chore: update ChromecastSource to comply with new initialization flow 2024-01-22 12:00:47 -05:00
FoxxMD de1377afdd fix: actually throw auth failure so initialization fails correctly 2024-01-22 12:00:43 -05:00
FoxxMD a22ddd10cc Merge branch 'develop' into sourceInitializeRefactor
# Conflicts:
#	src/backend/scrobblers/LastfmScrobbler.ts
#	src/backend/sources/DeezerSource.ts
#	src/backend/sources/MopidySource.ts
#	src/backend/sources/SpotifySource.ts
#	src/backend/sources/SubsonicSource.ts
#	src/backend/tasks/heartbeatSources.ts
2024-01-22 11:59:59 -05:00
FoxxMD 280add7107 Prepare for reverse proxy usage #125
Not working correctly yet but no BC for regular, non suburl, usage
2024-01-19 11:40:15 -05:00
FoxxMD b34abcab56 Update index.html 2024-01-19 11:05:34 -05:00
FoxxMD 788b26949e Fix uncaught Vite startup errors 2024-01-19 11:00:09 -05:00
FoxxMD c11da3df3d Working dockerfile 2024-01-18 16:59:45 -05:00
FoxxMD 0d68fea0eb Working app version indicator 2024-01-18 16:41:35 -05:00
FoxxMD c98672bcef Simplify port usage 2024-01-18 15:23:56 -05:00
FoxxMD bb0c079757 Cleanup package.json 2024-01-18 15:23:47 -05:00
FoxxMD 891144546e Simplify (and fix) test tooling for new esm stuff 2024-01-18 15:06:25 -05:00
FoxxMD 86611d764d Cleanup 2024-01-18 14:36:28 -05:00
FoxxMD e604268004 It works! 2024-01-18 14:18:08 -05:00
FoxxMD 396c3da277 Convert all relative backend endings to js 2024-01-18 13:58:31 -05:00
FoxxMD 0591b90089 somewhat working esm conversion 2024-01-18 13:15:06 -05:00
FoxxMD 6763be53ac Use vite-express to migrate from CRA
TODO convert backend to esmodule?
2024-01-18 11:52:13 -05:00
FoxxMD aee23f7550 fix(chrome): Remove device if it fails to refresh after 6 retries 2024-01-17 10:11:53 -05:00
FoxxMD 57b5528d43 docs(chrome): Update device connection instructions 2024-01-16 11:40:19 -05:00
FoxxMD bd8cf18a1a feat(chrome): refactor mdns discovery to run on source heartbeat as well as startup 2024-01-16 11:29:43 -05:00
FoxxMD 84acdd901a refactor: Move mdns discovery into own functions
May be useful for other sources...
2024-01-16 11:10:49 -05:00
FoxxMD e213a95996 fix(chrome): Refactor error handling for client and mdns libraries to catch errors properly 2024-01-16 09:52:41 -05:00
FoxxMD 78cd2eecf9 feat(chrome): Allow manual device connections 2024-01-15 16:11:33 -05:00
FoxxMD ebfeea1087 feat(chrome): Implement user filtering media by type 2024-01-15 16:02:10 -05:00
FoxxMD ddca623792 Fix error causing schema to generate 2024-01-15 16:01:36 -05:00
FoxxMD f0da35d115 docs: Add local source reference to flatpak manifest 2024-01-15 14:18:29 -05:00
FoxxMD 10eb355f92 chore(chromecast): Switch to scoped chromecast-client to fix flatpak source generation limitation 2024-01-15 14:18:14 -05:00
FoxxMD 85702c534a feat(maloja): Improve error handling
* Build error causes based on known error response types and expected maloja error body
* Only retry requests if error is network-related
* Set upstream error as NOT showstopping if we find expected error structure and status is less than 500
* Better maloja error parsing and logging
2024-01-15 12:45:01 -05:00
FoxxMD e2dfe317a9 feat(chrome): Improve mdns discovery for docker
* Switch to lower-level mdns library for more control during debugging
* Use avahi-browse with host avahi-daemon to circumvent musl (alpline) issues with mdns resolution https://johnsiu.com/blog/alpine-linux/
2024-01-12 17:00:05 -05:00
FoxxMD 7008ae8672 feat(chrome): Handle applications with transportId context
* Add transportId shortname to logger label for better differentiation of "per session" apps
* Fix handling of MS player deletion when pruning apps by checking for other valid apps before deletion
2024-01-12 13:26:53 -05:00
FoxxMD 10d9fa3581 feat(chrome): Improved app logging
Use child winston logger for better readability
2024-01-12 12:58:14 -05:00
FoxxMD 2b85927739 feat(chrome): Don't use bad media data as a play
* Check media data for missing artist or track and skip play if either is missing
* Remove player if bad data for 60 seconds
2024-01-12 12:31:35 -05:00
FoxxMD 8c402238ed feat(chrome): Add user debugging 2024-01-12 11:38:52 -05:00
FoxxMD 758770d278 refactor(chrome): Use forked chromecast-client lib
* Instead of patching using forked lib with validation fixes and improvements
* Print raw data if validation error is found
2024-01-12 11:12:21 -05:00
FoxxMD ee7ebb159b fix(chrome): Fix ENV names 2024-01-12 10:14:31 -05:00
FoxxMD f47a66060f refactor(chrome): Proper package patch for optional/nullish validation fixes
https://github.com/dantaylor3/chromecast-client/pull/14
2024-01-12 10:14:17 -05:00
FoxxMD 8929a630ed docs: chromecast docs and schema 2024-01-11 15:25:06 -05:00
FoxxMD 077f5d7547 feat: Improve chromecast handling 2024-01-11 14:51:52 -05:00
FoxxMD 369ad42946 feat: Add initial Chromecast source 2024-01-10 16:43:34 -05:00
FoxxMD 4bd996eb46 Merge branch 'develop' 2024-01-10 08:45:47 -05:00
FoxxMD a6ddc2b9f5 chore: Bump version for release 2024-01-10 08:45:10 -05:00
FoxxMD afb2e76b2d feat: Improve track credits parsing to recognize post-feat suffixes
* Refactor credit parsing into two stages: joiner separation and credits parsing
  * Break credit parsing into wrapped vs. non-wrapped for simpler regexes
  * Implement suffix matching after credits
* Add tests for wrapped vs. non-wrapped credits and with suffixes
2024-01-09 11:50:42 -05:00
FoxxMD 47812546e3 Merge branch 'playCompletedContext' into develop 2024-01-08 14:07:02 -05:00
FoxxMD 5ab6b85b30 refactor(source): Implement more granular initialization
* Break up initialize into build data => check connection => check auth
* Implement stub methods in AbstractSource with default return for steps
* Refactor existing initialize overrides into steps for each source
2024-01-05 13:19:34 -05:00
FoxxMD 9709d6348a refactor(source): Start consolidating isReady behavior 2024-01-05 11:15:30 -05:00
FoxxMD 10fea9b23d feat(source): Improve (re)start behavior and ui
* Move (re)start to same location on status card as client and change verbage to be the same
* Fix api behavior to use correct poll function so that backlog is triggered
2024-01-05 11:06:09 -05:00
FoxxMD 051dd5d128 feat(player): Improved player deletion behavior
* Consolidate playerDelete event emitter to deletePlayer function
* Consolidate logging for player deletion to deletePlayer function
* Add removal reason when transferring player state for single-platform sources
2024-01-04 11:10:39 -05:00
FoxxMD 369e18b1c2 feat(player): Handle state transfer for single-platform sources
The assumption is that single platform sources (like spotify) can only play from one platform at a time. If they are smart (like Spotify) the "new" platform picks up where the old platform left off WRT playing music. This commit implements the same behavior for MS player state by transferring existing state from the existing (previous) platform to the newly discovered platform.
2024-01-04 10:57:03 -05:00
FoxxMD fd49d17de4 Remove unused import 2024-01-04 08:49:14 -05:00
FoxxMD e0f034fa99 feat: Implement play completion context #121
* Track datetime player "completes" play at
* Add scrobble datetime SOC metadata
  * Log SOC used when printing play datetime
* Use hinted SOC scrobble datetime when comparing existing scrobbles
* Use hinted SOC scrobble datetime when building scrobbler client scrobble payload
* Hint Spotify backlog plays use END play date for scrobble SOC
2024-01-03 12:42:27 -05:00
Matt Foxx 80bc629ac2 Merge pull request #123 from Bujiraso/issue-122
Correct json-schema.app links
2024-01-02 20:54:39 -05:00
Bujiraso 1a90c23e76 fix: correct json-schema.app links
* Add missing 'backend' folder
  * Change 'develop' branch to 'master'
2024-01-02 18:41:44 -04:00
FoxxMD 48ddd4272f feat(scrobbler): Improve log readability for duplicate checking 2024-01-02 15:38:13 -05:00
FoxxMD ea408ac7a4 feat(scrobbler): Use play data to extract non-joined track name when comparing for duplicate check 2024-01-02 15:25:38 -05:00
FoxxMD 79c48af3e9 chore(tests): Fix TS compile warning on test for bad data 2024-01-02 15:24:24 -05:00
FoxxMD 12c2c0a132 feat(scrobbler): Change fuzzy diff threshold to be inclusive 2024-01-02 14:46:33 -05:00
FoxxMD 78abfc2168 refactor: Temporal comparison result data improvements #121
* Refactor using 'close' boolean to 'match' granularity
  * Makes using granularity for future logic easier
  * Easier logging for granularity in summary
* Remove intermediate temporal functions in classes for DRY and so we can use comparison results
* Add Time Detail to match breakdown for more visibility during logging
2024-01-02 13:55:53 -05:00
FoxxMD 0f721b94c2 refactor: Move Play time comparison code into own file 2024-01-02 13:02:44 -05:00
FoxxMD f126bf0ec0 refactor: Improve string comparison robustness
* Fix undefined token inclusion caused by token lists with differing lengths
* Always compare longer to shorter string so sameness parameter order is invariant
* Add comments to make logic easier to understand
* Add tests to test new functionality
2024-01-02 12:38:24 -05:00
FoxxMD 064a435c79 Simplify debug mode usage for testing 2024-01-02 12:34:19 -05:00
FoxxMD c0e5bb872a fix(lastfm): Fix handling of non-string mbid string values #114 2024-01-02 11:09:51 -05:00
FoxxMD e9561b5783 fix(jellyfin): Split musicbrainz mbid artist identifier semicolon #114
Jellyfin can apparently return multiple artist mbids but does so in one string deliminated by a semicolon
2023-12-28 10:07:11 -05:00
FoxxMD 77c3a2f0c7 fix(listenbrainz): Treat 400 responses as non-showstopping #114 2023-12-28 09:56:25 -05:00
FoxxMD db4e64dc2a Merge remote-tracking branch 'origin/develop' into develop 2023-12-27 11:55:45 -05:00
FoxxMD fcc05a8b19 feat(scrobblers): Implement user-initiated (re)start mechanism #114
* Add api endpoint for restarting client
* Implement (re)start link on client card
2023-12-27 11:50:57 -05:00
FoxxMD c37d94e71b refactor(scrobblers): Consolidate scrobbler client startup surface area
* Move logging for init/auth into client instead of builder class
* Move startup from builder class to main backend and use initScrobbleMonitoring() instead of calling init/auth individually

Paves the way for manual restarting #114
2023-12-27 11:02:15 -05:00
FoxxMD aa59bc4607 Fix service documentation link #119 2023-12-20 13:31:40 -05:00
FoxxMD a993df4f0b docs: Add contributing guidelines and PR template 2023-12-12 10:01:58 -05:00
Matt Foxx 7e83e0f085 Merge pull request #118 from brunohpaiva/develop
fix(spotify): undefined player state data when in private session
2023-12-12 09:40:17 -05:00
Bruno Henrique Paiva 5b2449adb0 fix(spotify): undefined player state data when in private session 2023-12-12 11:37:47 -03:00
FoxxMD 5811912745 feat(webscrobbler): Log the best-guess URL for requests using configured slug 2023-12-04 11:13:20 -05:00
FoxxMD b7d6e9e497 docs: Update schema for logging enums 2023-12-04 11:06:14 -05:00
FoxxMD 5fd0cc3fe4 docs(webscrobbler): Move slug into data #111 2023-12-04 11:06:02 -05:00
FoxxMD 6e2e6f4492 fix(webscrobbler): Correct ENV names to match those documented #111 2023-12-04 11:02:19 -05:00
FoxxMD ed6f7cfe5f docs(webscrobbler): Add where to find connector list #112 2023-12-04 10:47:27 -05:00
FoxxMD 6b17babd7a fix(mopidy): Fix album artist parsing #110
Should be using mapped artist string name rather than model but cannot test due to mopidy being difficult. Hopefully this works?
2023-11-30 11:15:49 -05:00
FoxxMD c77e4355fc fix(player): Fix listen range default value 2023-11-30 11:14:52 -05:00
FoxxMD 25de65f41f chore: bump package-lock version 2023-11-30 11:13:59 -05:00
FoxxMD af037ee215 Merge branch 'develop' 2023-11-29 11:41:17 -05:00
FoxxMD 39c4764b40 chore: Bump releases in flatpak 2023-11-29 11:40:59 -05:00
FoxxMD ec28d2aeb5 Merge branch 'develop' 2023-11-29 11:22:22 -05:00
FoxxMD baf8635587 chore: Bump version for release 2023-11-29 11:22:07 -05:00
FoxxMD a3839a28a2 feat(scrobbler): Use listenedFor when checking for existing scrobbles
* Parse listenedFor from maloja response data
* Compare listenedFor against playdate diff when comparing fuzzy times for existing scrobbles
2023-11-28 11:43:45 -05:00
FoxxMD 4247f545c5 feat(player): Pad scrobble listen duration when confidence is high #108
If player is calculated as playing and tracks have positional data then start and end positions can be fudged to completion if jitter is small enough

* At beginning of new track if position is within 3 seconds of start of track fudge to 1 second
* At end of old track if position is within 3 seconds of end track fudge to completion

This can be achieved with high confidence due to smart polling near end of tracks added in 1d84f07a40
2023-11-16 13:24:26 -05:00
FoxxMD c54c3ff5de docs(spotify): Add note about duration accuracy when using Spotify's Automix feature 2023-11-16 12:38:45 -05:00
FoxxMD 1d84f07a40 feat(source): Smart interval calculation based on track position and end time #108
If player has an absolute position and play has duration we can determine if polling interval will occur after play would end. In this scenario we can shorten interval to before play ends to get more accurate data about play state close to end of track and report more accurate play time.
2023-11-16 12:29:55 -05:00
FoxxMD b47c80dc11 fix(source): Fix polling interval drift
* Wake up timestamp was based on ts AFTER polling finished + interval. This means real-time interval between polling drifted by the amount of time it takes to execute polling loop. Fixed by getting ts at beginning of loop instead of at end.
* Implement interval function that can be overriding by child classes
* Format interval to make floats less ugly
2023-11-16 12:27:03 -05:00
FoxxMD 75e2b2b337 fix(player): Correctly output value of play timestamps for player based on undefined or not #107
Fixes #107
2023-11-15 10:35:00 -05:00
FoxxMD 08ca7fff35 feat: Add app version output
Output to logs and display in front end next to header
2023-11-13 12:25:07 -05:00
FoxxMD 3140df28b7 non working version display 2023-11-13 11:50:05 -05:00
FoxxMD 14546b8d74 fix(lastfm): Fix api error when listening history is private by sending session key #105
Thanks @FozzieHi for doing the legwork on finding the solution for this

Closes #105
2023-11-13 10:40:06 -05:00
FoxxMD 341e896eca feat: Implement album artist for all sources #104 2023-11-07 11:44:36 -05:00
FoxxMD b3b4636e7f feat: Implement album artist and scrobble client payload function
* Implement abstract playToClientPayload function to make future testing easier
* Refactor play object to include album artist in data
   * Refactor scrobble client payloads to include album artist, if applicable
2023-11-07 10:54:14 -05:00
FoxxMD 43f5aeb5dc fix(plex): Use album artist if present and remove "Various" artist #104 2023-11-06 12:41:05 -05:00
FoxxMD d38aabf7b8 chore(flatpak): Update metainfo and sources 2023-11-03 15:58:37 -04:00
FoxxMD c64b8d16e6 fix(spotify): Improve api error handling #103
* Discriminate between proper errors (permissions) and network errors
* Fix return value and playbackState flag set during post-auth hook
* Improve error logging using cause and upstream error
* Fix undefined destructure when device is not present in playingNow payload
2023-11-02 11:20:42 -04:00
FoxxMD 1938fc3a27 fix: Catch errors during source backlog processing #103 2023-11-02 11:16:54 -04:00
FoxxMD 36181396c7 refactor: don't stringify error when using upstream options 2023-11-02 11:16:26 -04:00
FoxxMD bb76a3b214 fix: test auth correctly on auth flow completion 2023-11-02 11:16:02 -04:00
FoxxMD 339bb7fcd2 refactor: Simplify network error detection during auth test 2023-11-02 10:07:02 -04:00
FoxxMD 3a25057d05 fix(maloja): Fix missing return value for auth test 2023-11-02 09:49:03 -04:00
FoxxMD 14ab6032f5 feat: Refactor auth testing to differentiate between network issues and actual auth errors #102
* Implement type/guard for node network error
* Refactor auth in sources & clients to flag if error is network or api based
* Retry auth on heartbeat if auth failure was caused by network
* Implement initial tests for testing client auth
* Fix tests hanging due to running async functions
2023-11-01 15:15:09 -04:00
FoxxMD 3e1f785437 Add pony-cause find by function helper 2023-11-01 15:09:06 -04:00
FoxxMD 91bb9d6089 feat: Allow disabling console and file logging through ENV
Used internally for controlling logging when testing
2023-11-01 15:08:26 -04:00
FoxxMD 9cf373e9f4 chore: Update string-sameness version 2023-11-01 09:39:37 -04:00
FoxxMD 9ec22e9000 Merge branch 'develop' 2023-10-24 09:20:25 -04:00
FoxxMD 9616860e23 chore: Bump version for release 2023-10-24 09:20:04 -04:00
FoxxMD 0f6638dfdf docs: Add queued scrobble feature highlight 2023-10-24 08:56:14 -04:00
FoxxMD fe9f49779c feat: Implement queued scrobbles display
* Show number of queued scrobbles for clients in ui
* Implement live updates to queue count
2023-10-19 13:40:43 -04:00
FoxxMD 3aab6a207b feat: Implement live client/source status updates 2023-10-19 11:53:48 -04:00
FoxxMD 49e9fd4b3b feat: Implement live updates for dead letter and scrobble counts 2023-10-19 11:41:06 -04:00
FoxxMD 0143ae62aa feat: Implement retry/remove all dead letter scrobbles
* Implement API endpoints
* Implement front-end logic and actions on dead letter page
2023-10-19 11:03:04 -04:00
FoxxMD 2882f65b69 refactor: Reduce noisy logging at INFO level 2023-10-19 10:36:57 -04:00
FoxxMD 62e9f7f620 feat: Log storage improvements and limit controls
* Store logs by levels, fixes issue where rarer warn/info are pushed out of buffer by frequent debug/verbose
  * Use FixedSizeList instead of homegrown buffer
* Implement API and ui controls for setting log limit
2023-10-13 14:28:24 -04:00
FoxxMD 57f2ebf2b5 feat: Add dead letter error to data 2023-10-13 12:12:46 -04:00
FoxxMD 884134cab9 Simplify dead letter data clear
Just reset state on GET pending instead of using effect cleanup in react component
2023-10-13 11:38:38 -04:00
FoxxMD 0c90de78ab Implement full-fat deadletter reducer
couldn't figure out a better way to handle this with just the api endpoint code
2023-10-13 11:34:22 -04:00
FoxxMD 1c5530ee2a fix: deadletter endpoint responses
Send empty response so RTK Query correctly parses response
2023-10-13 11:33:47 -04:00
FoxxMD fbb67a8fe6 feat: Implement dead letter endpoints
* Implement backend api endpoints for dead letter CRUD
* Include dead letter queue count in client status data
* Implement dead letter client view
* Partially working retry/remove actions for client (request is OK, RTK not working for update yet)
2023-10-11 12:38:43 -04:00
FoxxMD c8c5f5dcb7 docs(webscrobbler): Add disclaimer about hostname for firefox 2023-10-06 09:44:29 -04:00
FoxxMD eb449eeca6 feat: Implement WebScrobbler source 2023-10-05 13:58:20 -04:00
FoxxMD 7cfdb10813 chore: Update flatpak commit 2023-10-05 10:10:58 -04:00
FoxxMD be599b5562 Merge branch 'develop'
# Conflicts:
#	flatpak/io.github.foxxmd.multiscrobbler.metainfo.xml
2023-10-04 10:05:50 -04:00
FoxxMD 07d97a4937 chore: Bump version numbers 2023-10-04 10:05:08 -04:00
FoxxMD 7037d6f833 Merge branch 'develop' 2023-10-04 09:13:21 -04:00
FoxxMD 2deca322e7 fix(ui): For non-polling sources use last activity date for status
So that status changes if player is updated instead of waiting for track to be discovered
2023-10-03 14:16:07 -04:00
FoxxMD ab84cb396d docs(jellyfin): Add request header workaround for jellyfin #101 2023-10-03 14:08:07 -04:00
FoxxMD bb63c61ca8 docs: Clarify subsonic api compatibility 2023-10-03 14:00:57 -04:00
FoxxMD f4256d1084 docs: Update dashboard screenshot 2023-10-03 13:56:27 -04:00
FoxxMD fc87b86f52 test(player): Add player tests 2023-10-03 12:36:00 -04:00
FoxxMD 5dff617ed6 fix(player): Fix some bugs and add param for easier testing
* Fix repeat end position percent test (should be 85% not 15%)
* Fix current play/range not being reset correctly on repeat
* Add reportedTS param to facilitate testing
2023-10-03 12:35:48 -04:00
FoxxMD f2a171c596 refactor(player): Move listen progress/range functionality into own class (SRP) 2023-10-03 11:06:44 -04:00
FoxxMD 336970c1f7 feat(ui): Improve status display
* Use running/idle for scrobble client to indicate processing status
* Change polling sources status to polling/idle to better reflect behavior
* Add status indicator color to statuses
2023-10-03 10:15:18 -04:00
FoxxMD d04bc038d4 feat(ui): Implement status indicator 2023-10-03 10:13:45 -04:00
FoxxMD 1ab12355f3 feat(player): Implement seek and repeat detection
* Start new listening ranges if player has seeked forward/backwards
* Treat track repeats as new plays
2023-10-02 17:22:59 -04:00
FoxxMD fb5d9fb7b0 feat(player): Log when play fails discovery on player change 2023-10-02 13:54:55 -04:00
FoxxMD 9aae57f966 test(scrobble): Add tests for scrobble processing queue and existing comparisons 2023-10-02 13:39:33 -04:00
FoxxMD fefc82c521 feat(scrobble): Improve existing scrobble comparison logic
* Use existing time comparison function with "close" matching
* Fix wording and return value
2023-10-02 13:39:14 -04:00
FoxxMD 43ebdcb10c refactor(scrobble): Refactor scrobble delay and scrobble processing sleep behavior
* Delay scrobble based on last scrobble attempt TS
  * rename abstract scrobble func to doScrobble
  * Implement scrobble() in abstract client and track last scrobble attempt time, delay based on arg or client property
    * This way first scrobble isn't delayed and we only delay if subsequent scrobble attempts are close in time
* Make scrobble processing sleep time on empty queue configurable
2023-10-02 13:38:08 -04:00
FoxxMD adfb166320 feat(scrobble): Implement heartbeat task for scrobblers
Task does scrobble processing restart and dead letter queue processing
2023-09-29 15:07:44 -04:00
FoxxMD 1c54ae6465 feat(scrobble): Implement dead letter queue processing
* Implemented logic for processing and removing scrobbles from dead letter queue
  * Only retry scrobbles under X retries based on config or argument
  * Add removal-only function (for later use with UI)
2023-09-29 15:07:08 -04:00
FoxxMD 9309f7d8cf feat(scrobble): Initial scrobble queue implementation
* Move scrobble checking into client (out of client manager) for single responsibility
* Implement scrobble queue/deadletter
  * Less chance of duplicate processing since sources can't scrobble concurrently
  * Better throttling since multiple sources can't scrobble concurrently
  * When scrobbling fails we can retry on delay just like polling and *keep* the failed scrobbles for later retry rather than dropping them
  * Deadletter (to be fully implemented) for scrobbles that fail multiple times or are signalled as not upstream api issues by client
2023-09-29 14:15:32 -04:00
FoxxMD 8717fad08a feat(source): Control backlogging via config #100 2023-09-29 11:28:32 -04:00
FoxxMD 983b4c7417 feat(maloja): Add listened duration to scrobble payload 2023-09-28 15:23:14 -04:00
FoxxMD a663e9af14 feat(player): Defer play discovery so listenedFor is accurate
Wait until player gets a new play or becomes stale before trying to discover the (previous) play so that we store a more accurate amount of time the user actually listened to that play
2023-09-28 15:22:22 -04:00
FoxxMD 249a606bf5 feat(scrobble): Implement artist dup bonus scoring heuristic
When looking for dups and finding a fuzzy timestamp match, close title, and mismatch (but some matched) multiple artist -- we give a bonus to artist score since its likely a dup.
2023-09-28 12:19:11 -04:00
FoxxMD 0ec8d8814d fix(scrobble): Fix not using reference duration for fuzzy comparison 2023-09-28 11:25:33 -04:00
FoxxMD f0cee72f43 test(scrobble): Improve test durability by using mixed duration sources 2023-09-28 11:01:07 -04:00
FoxxMD a82aafa6b8 test(scrobble): Add test for duplicate when exact title/time 2023-09-28 10:47:07 -04:00
FoxxMD 3b624205ad fix: Fix missing destructure default value 2023-09-28 10:35:38 -04:00
FoxxMD c6d38e40d0 refactor(scrobble): Adjust dup score weights 2023-09-28 10:31:45 -04:00
FoxxMD 1ca7834feb test(scrobble): Add more tests for similar-but-unique scrobble detection 2023-09-28 10:31:06 -04:00
FoxxMD 77c9977d27 Use string highScore instead of weighted since cosine isn't messing things up anymore 2023-09-28 10:29:34 -04:00
FoxxMD 521130562d feat(scrobble): Use new string approach for artists 2023-09-28 09:55:09 -04:00
FoxxMD 9512ca7e84 feat(scrobble): Use new string approach for track
And move weights to constants
2023-09-27 16:01:09 -04:00
FoxxMD 159901f79e refactor(tests): Move jellyfin/listenbrainz test into own folders 2023-09-27 15:44:22 -04:00
FoxxMD 9758568ded feat: Implement new string comparison logic 2023-09-27 15:43:44 -04:00
FoxxMD a953f83d07 test(scrobble): Add more tests 2023-09-27 14:14:49 -04:00
FoxxMD 2bbe2e6ba0 refactor(scrobble): Print score breakdown in different statement
Is more readable
2023-09-27 14:14:34 -04:00
FoxxMD ec4ab8c078 test(scrobble): Implement basic scrobble tests 2023-09-27 13:20:03 -04:00
FoxxMD 7885c4ca9c fix(scrobble): Fix wrong artists used for comparison 2023-09-27 13:19:25 -04:00
FoxxMD 8371ec3a01 fix(scrobble): Fix config destructing default value 2023-09-27 13:18:58 -04:00
FoxxMD 43cbadcd2d refactor(scrobble): Clean up base design a little
* Make alreadyScrobbled abstract function
* Make sorting recentScrobbles automatic by refactoring into a getter/setter
2023-09-27 13:18:20 -04:00
FoxxMD 6e1e2c3534 Add verify example for body parser 2023-09-26 15:38:04 -04:00
FoxxMD a46389e745 Remove unused import 2023-09-26 14:08:02 -04:00
FoxxMD ce5f8d5fca fix(jellyfin): Enable parsing requests with more content-types and add logging on invalid types
* Add text/* wildcard so any text-prefixed content-type is valid (Jellyfin should always be sending json anyway)
* Add logging WARN when body is empty

Fixes #101
2023-09-26 14:03:03 -04:00
FoxxMD bdbbce4cb0 feat(jellyfin): Initial testing suite 2023-09-26 11:39:34 -04:00
FoxxMD 034cb321ef fix(flatpak): Add missing release node 2023-09-21 09:12:07 -04:00
FoxxMD ac2d453aa2 feat(config): Implement property to control whether a source/client is used
* QoL improvement -- Instead of needing to rename a config file so it is not read by MS a user can now use the 'enable' property in all source/client configs to enable or disable loading the config
* Added property to all config examples
2023-09-20 11:31:15 -04:00
FoxxMD 860d2d953f fix(youtube): Use updated yt music library #98 2023-09-19 09:22:22 -04:00
FoxxMD a486102747 feat(lastfm): Add player support
* Use lastfm 'nowPlaying' property in user listens for MS player implementation
* Do not use MS player as source of truth -- continue to use lfm listen history
2023-09-18 15:21:25 -04:00
FoxxMD 735264cb2b feat(listenbrainz): Add player support #74
* Use listenbrainz 'playing-now' endpoint for MS player implementation
* Do not use MS player as source of truth -- continue to use LZ listen history
2023-09-18 14:58:18 -04:00
FoxxMD 7dece447df feat(player): Add sourceOfTruth variable to control logging and future use
Will not be used by listenbrainz source for actual scrobbling so make logs less noisy by suppressing what would normally be logging on newely added tracks from player
2023-09-18 14:56:23 -04:00
FoxxMD 3fb5d6aae9 fix(ui): Remove duration percent display when duration is zero 2023-09-18 14:40:22 -04:00
FoxxMD d5dfc33fce fix(deezer): Fix deezer passport generation 2023-09-14 12:51:16 -04:00
FoxxMD 6dc40e50ad feat: Implement Base URL
* Allows users to define a base URL to be used for default redirect URI generation
  * Convenience mostly for docker users so they do not have to explicitly define redirect uri for each source/client
* Use in UI start up logging hints
2023-09-14 12:38:20 -04:00
FoxxMD b8ebd8ca7e chore: Ensure production mode for webpack without relying on env 2023-09-14 10:34:07 -04:00
FoxxMD e9eb7d357f fix*(scrobblers): Test auth when client completes auth callback 2023-09-14 10:07:44 -04:00
FoxxMD 3f2cb8ac0c fix: Pass localUrl to scrobble clients for use by lastfm 2023-09-14 10:07:12 -04:00
FoxxMD 8e36c27ec9 fix(backend): Fix how port is determined and used for localUrl 2023-09-14 10:06:42 -04:00
FoxxMD fa90a3b821 fix: Load variables from .env for backend
Was being used for frontend (cra) but not applied to backend
2023-09-14 10:05:24 -04:00
FoxxMD 2d2319fa27 fix(ui): Fix wrong route for client auth #97 2023-09-14 09:08:01 -04:00
FoxxMD ba161b7b92 chore: Remove unused dashboard backend endpoint 2023-09-14 09:07:36 -04:00
FoxxMD d438c8b256 fix(docker): Normalize ownership of node_modules files
files in node_module can have wierd ownership based on how a package owner published the package. If this occurs a rootless container build system could potentially have issues with subui/gid mapping for non-standard (high number) ids.

During image build recursively chown all node_modules files to normalize ownership and avoid this problem.

Fixes #96
2023-09-13 14:55:04 -04:00
FoxxMD bbb4555c0e chore(docker)!: Bump lsio base image version to 3.18
* Updates alpine 3.17 -> 3.18
  * Main reason is to potentially fix DNS issues related to musl #88
    * https://github.com/nodejs/node/issues/15780#issuecomment-334496907 and https://bell-sw.com/blog/how-to-deal-with-alpine-dns-issues/
    * Resolved in musl 1.2.4
      * https://gitlab.alpinelinux.org/alpine/tsc/-/issues/43
      * https://www.openwall.com/lists/musl/2023/05/02/1
  * Bumps npm 9.1.2 -> 9.6.6

New/changed ENVs used by LSIO may require removing "stored" ENVs if using portainer
2023-09-13 13:21:44 -04:00
FoxxMD 6e8ea96f32 fix: Fix js endings 2023-09-13 12:19:58 -04:00
FoxxMD b06de77f42 Merge branch 'player' into develop 2023-09-13 12:13:17 -04:00
FoxxMD 3aa8279914 Merge branch 'develop' 2023-09-13 12:02:51 -04:00
FoxxMD a64724e114 chore: Bump version for release 2023-09-13 12:02:38 -04:00
FoxxMD 052234a3d9 Merge branch 'develop' 2023-09-13 12:02:04 -04:00
FoxxMD aa2c565afd fix(ui): Log line parsing missing line terminator capture 2023-09-13 11:59:27 -04:00
FoxxMD d0fd225d99 chore: Use constant for default retry multiplier 2023-09-13 11:40:29 -04:00
FoxxMD c40acdd7c7 feat!: Decrease default polling interval
* Due to increased scrobbled thresholds in 590610022d we need to pol more frequently to make sure more of the track is accurately monitored
* Decreased default from 30 to 10 seconds
* Decreased max interval from 60 to 30 seconds
2023-09-13 11:36:22 -04:00
FoxxMD 590610022d feat!: Set default scrobble thresholds to standard values #73
https://github.com/krateng/maloja/blob/master/API.md#scrobbling-guideline
https://www.last.fm/api/scrobbling

The defaults are 50% of the track or 4 minutes.
2023-09-13 10:49:54 -04:00
FoxxMD f7dae32f50 feat(maloja): Generate web links for scrobbled tracks 2023-09-12 16:01:32 -04:00
FoxxMD 3cbbc0837f feat(ui): Expand displayed plays
* Implement API for fetching client recently scrobbled
* Reduce text noise by combining track stats and link for recent
2023-09-12 15:46:49 -04:00
FoxxMD 0197e70a1a feat(ui): Remove unnecessary wording for track stats 2023-09-12 14:58:20 -04:00
FoxxMD 40a7d9e398 feat(ui): Make player dates human friendly 2023-09-12 14:57:41 -04:00
FoxxMD c7b2dcbbbe fix(ui): Use normal match parameters
groups are undefined probably due to minification?
2023-09-12 12:11:39 -04:00
FoxxMD a524e18820 feat(ui): More ui compaction
* Better use of header
  * Show name on separate line
  * Only show client/source if ambiguous
  * Move status into right side of header
* Reduce padding/margin on card grid
2023-09-12 12:03:49 -04:00
FoxxMD 81894d691b feat(ui): Compact ui a bit
* Reduce padding on card headers and bodies
* Improve log experience
  * Reduce log font size and convert to mono
  * Use proper log message parsing to remove dangerous html set
2023-09-12 11:07:39 -04:00
FoxxMD 0fb192dd41 refactor(ui): Prevent nodemon from restarting on tailwind config change 2023-09-12 11:05:56 -04:00
FoxxMD 3f608c6c8e fix(listenbrainz): Artist parsing joiner phrases must begin with non-word character
Prevents artists with joiner phrases in their name (EX 'Daft Punk' contains 'ft') from erroneously being split

Fixes #94
2023-09-12 09:29:04 -04:00
FoxxMD dcff9ee73d feat(player state): Implement player status and listened duration in UI 2023-09-11 13:21:24 -04:00
FoxxMD f1cd66d3e8 feat(player state): Preserve position when player is not in stopped status
Makes for a nicer UI experience to see where the player was paused
2023-09-11 13:20:58 -04:00
FoxxMD d95fef09a3 refactor(player state): Remove old play logic
Recent plays are dead. Long live recent plays.
2023-09-11 12:38:10 -04:00
FoxxMD d1060e724e feat(ui): Remove player when dead 2023-09-11 12:36:10 -04:00
FoxxMD a7faaaac86 refactor(player state): Refactor handling of non-updated players
* Check for stale/orphaned/dead in independent loop so they are not dependent on getting source updates to update
* Only emit events if player state has updated
2023-09-11 12:35:41 -04:00
FoxxMD 3762f95bae fix(player state): Fix stale/dead logic checks
* Stale should not reset to stale when status is orphaned
* Dead check should be in seconds, not minutes
2023-09-11 12:34:09 -04:00
FoxxMD b9f04deb28 Always emit player state 2023-09-08 16:35:20 -04:00
FoxxMD 9cc820c76e feat: More player polish and additional info pane 2023-09-08 16:33:23 -04:00
FoxxMD f7195fb40c feat: Rework source api to include player states
* Create and implement api interface for player state data
* Serialize memory source players to object indexed by platform id
* Refactor event emitters to separate event data better from event metadata
* Update source slice when player update event is broadcast
2023-09-08 14:47:53 -04:00
FoxxMD 9c0f029992 feat: POC player
Based on https://codepen.io/keithpickering/pen/wjYJKM
2023-09-08 13:40:43 -04:00
FoxxMD f370b8ad1e fix: Update heartbeat job to 20 minutes
1 minute was for debugging...
2023-09-05 11:44:22 -04:00
FoxxMD 7c962ec43a fix: Fix more import endings 2023-09-05 11:19:13 -04:00
FoxxMD 2013a6692e fix: Fix import ending 2023-09-05 11:18:15 -04:00
FoxxMD 4a5eb91f16 feat: Schedule heartbeat task to restart sources that should be polling
Addresses #5 and #65 -- in default config use-cases where:

 * request/polling retry attempts are low
 * source was initially OK (auth OK, initial communication OK)
 * and there is a non-auth related *upstream* communication issue

 there is no fallback path for restarting source polling after upstream has normalized.

 This feat implements a scheduled heartbeat task that attempts to restart polling every 20 minutes if this scenario is valid so that a good source config will recover without user intervention.
2023-09-05 11:11:10 -04:00
FoxxMD a606801bb0 Merge branch 'develop' 2023-08-31 10:58:48 -04:00
FoxxMD db395e40e8 chore: Bump for release 2023-08-31 10:58:30 -04:00
FoxxMD 1c498a2c71 Merge branch 'develop' 2023-08-31 10:55:10 -04:00
FoxxMD e4a647d425 fix(jellyfin): Attempt to fix route redirect #92
Should not be appending to route but replacing...see if leading slash fixes things
2023-08-31 09:29:11 -04:00
FoxxMD c1a91ab610 fix: Fix route redirect 2023-08-31 09:28:19 -04:00
FoxxMD 093007a72b Merge branch 'develop' 2023-08-29 09:46:25 -04:00
FoxxMD cb5cdb09f7 chore: Bump versions for release 2023-08-29 09:45:54 -04:00
FoxxMD 8f2e1ad7e7 refactor: Update flatpak build
* Update flatpak build instructions to use build
* Add run command to readme
* Update default config to keep web port backwards compatible
* Fix flatpak application name in run command in installation docs
2023-08-29 09:35:49 -04:00
FoxxMD ed6bcd60b3 refactor: Remove unused react packages and fix CRA babel plugin warning 2023-08-28 10:39:03 -04:00
FoxxMD edbe131f1b fix: Fix tsconfig and out locations for generated schema 2023-08-24 12:10:49 -04:00
FoxxMD 881e537a89 fix: ts libcheck prevents mocha from causing errors while generating schema
https://stackoverflow.com/a/65568463/1469797
2023-08-24 12:10:31 -04:00
FoxxMD abb286f4a8 fix: Fix js extensions 2023-08-24 11:53:42 -04:00
FoxxMD 04ffc0a589 refactor: Restructure server directory
* Rename to backend to better reflect what it is
* Move server functionality out of entry file and into own server subdirectory
* Rename 'apis' folder to 'vendor' to reflect usage and move into common
* Rename 'clients' folder to 'scrobblers' reflect usage and prevent ambiguity with client CRA folder
2023-08-24 11:12:27 -04:00
FoxxMD 4cccdeeff0 chore: Clean up site metadata and update logo
* Changed logo color to be easy to view in both dark/light modes (better color in general, imo)
* Changed and cleaned up head/meta/manifest info and logos for docs and frontend
2023-08-24 10:26:45 -04:00
FoxxMD 2295cf4756 docs: Update example configs 2023-08-24 09:40:59 -04:00
FoxxMD dcd63ff093 feat: Add more IP info and docker hint at server startup 2023-08-23 12:33:32 -04:00
FoxxMD c4bf076d0a Use string-sameness package 2023-08-23 10:31:39 -04:00
FoxxMD 5b32602aca Update yt music package and add missing leven package 2023-08-22 16:29:52 -04:00
FoxxMD 82167ab5ec fix(spotify): Correctly catch 401 errors on refresh token failure 2023-08-22 15:29:19 -04:00
FoxxMD aac963a21c feat: Improve LZ play parsing to pass test suite 2023-08-22 14:36:25 -04:00
FoxxMD fc84d846b2 feat: Implement test skeleton and basic testing for LZ listen parsing 2023-08-22 12:52:49 -04:00
FoxxMD 7674593bfe fix: Use correct data for clients slice 2023-08-22 09:32:41 -04:00
FoxxMD 500dc809bf Even more js extension removals... 2023-08-21 16:48:02 -04:00
FoxxMD b4e9c52301 More js extension removals 2023-08-21 16:43:17 -04:00
FoxxMD 6efac246ff Remove erroneous js extensions 2023-08-21 16:31:37 -04:00
FoxxMD bec841e833 Implement live events infra with POC for tracks discovered updates 2023-08-21 16:21:30 -04:00
FoxxMD 25ebfbcccf refactor: Migrate from react-query to RTK 2023-08-21 14:56:03 -04:00
FoxxMD 74c2bed572 Increase number of log lines in buffer 2023-08-21 11:28:59 -04:00
FoxxMD a1672dfa45 Implement log level filtering 2023-08-21 11:24:16 -04:00
FoxxMD abe494e2b0 Remove socket.io 2023-08-18 14:47:20 -04:00
FoxxMD fabcfc1377 Put dev packages in correct dependency location 2023-08-18 14:39:57 -04:00
FoxxMD 9f89507ce4 Switch back to NPM because of yarn prod dependency issue 2023-08-18 14:32:56 -04:00
FoxxMD f277557e83 Codeshift relative imports on server to not use extension
Makes debugging work in webstorm
2023-08-18 12:54:21 -04:00
FoxxMD 59dfdf1400 Implement codeshift for transforming import extensions 2023-08-18 12:53:45 -04:00
FoxxMD 322b25db00 Add 404 route 2023-08-18 09:26:34 -04:00
FoxxMD a2b424de93 Add empty render for recently played tracks 2023-08-18 09:24:05 -04:00
FoxxMD 09e6e5e534 Add reauth/initialze links for client 2023-08-18 09:22:25 -04:00
FoxxMD 5450c8ddf1 Add nodemon config to prevent auth json file writes from causing server to restart 2023-08-17 16:06:10 -04:00
FoxxMD 99e772e9f6 Add source auth flow link to source card 2023-08-17 14:32:42 -04:00
FoxxMD 69fb3f1dbd Need to use full source maps and remove '.js' extensions to get webstorm to fully use debugger 2023-08-17 13:57:35 -04:00
FoxxMD e9c4f9f9bb fix(web): Implement log prefetch for dashboard 2023-08-17 13:40:30 -04:00
FoxxMD fa6e46f453 fix(sources): Fix polling bug that caused infinite loop
* If user tried to start polling while polling was already occuring it caused an infinite while loop
* Add more signals for stopping polling for a restart use case
2023-08-17 13:19:02 -04:00
FoxxMD 2d6bafecaf Don't refetch status query on window focus 2023-08-17 13:17:58 -04:00
FoxxMD 7dffc3def7 Don't recompile tsx (front end only) 2023-08-17 13:17:38 -04:00
FoxxMD 922f4be14f refactor(web): Move server routes into own files and setup with one function (reorg) 2023-08-17 12:11:07 -04:00
FoxxMD e7bab87e6e refactor(web): Implement recently played 2023-08-16 16:08:57 -04:00
FoxxMD dd67501337 Add proxy setup to git 2023-08-16 15:02:45 -04:00
FoxxMD 4f60f2fa9a Prevent browser from auto opening on client start 2023-08-16 15:01:37 -04:00
FoxxMD 7cb215be52 refactor: Re-organize server/client structure so everything exists on same port
Used https://spin.atomicobject.com/2020/08/17/cra-express-share-code as a base for refactoring
2023-08-16 14:47:21 -04:00
FoxxMD e2b472cf44 Some progress towards recent page 2023-08-15 14:28:56 -04:00
FoxxMD 64c46ac87f Update yarn lock and nvm 2023-08-15 12:47:51 -04:00
FoxxMD 334b782ca3 Merge branch 'webClient' into next
# Conflicts:
#	package-lock.json
#	src/common/infrastructure/Atomic.ts
2023-08-15 12:44:25 -04:00
FoxxMD 9e214243c9 fix(jellyfin): Disregard time difference direction and always check absolute diff #87
May allow more UserDataSaved scrobbles to occur but gives up any form of disallowing future scrobbles from bad timestamps
2023-08-15 12:16:11 -04:00
FoxxMD 77bf84fef4 feat(jellyfin): Simplify tz correct and detect/correct ~future~ listens #87 2023-08-11 12:28:43 -04:00
FoxxMD cde6046ffb feat(jellyfin): Timezone trickery to sus out weird timezone reporting from jellyfin #87 2023-08-10 13:30:27 -04:00
FoxxMD af4ac98357 feat(logging): Expand temporal logging comparison functionality so we can analyze results
* Result and criteria are returned from methods
* Logging method to summarize results
* Log results for jellyfin if using debug mode or explicit config option
2023-08-10 11:44:27 -04:00
FoxxMD 6f66813db0 feat(logging): Add debug mode shortcut property
Allows enabling all debugging for properties that aren't explicitly set
2023-08-10 10:45:27 -04:00
FoxxMD f42fef85b7 feat(config): Add a common 'options' property for all sources and implement logPlayerState
* Will eventually migrate all non-source specific properties to this new property to simplify data config
* Add logPlayerState to allow configurable player state output to DEBUG logging
2023-08-10 10:14:45 -04:00
FoxxMD 400f448108 fix(jellyfin): Use new player state for checking for existing track 2023-08-10 09:58:52 -04:00
FoxxMD 4659735c12 feat: Use listen ranges for play comparisons 2023-08-10 09:58:31 -04:00
FoxxMD c08fe0e54b fix: State Player range fixes and logging
* include all listen ranges when played object output
* include incoming play details in logging when starting new play
2023-08-10 09:58:16 -04:00
FoxxMD 37d4a1c81a feat: POC player state tracking #74
* Implements generic Player state tracking when using MemorySource
  * State tracking will replace processRecentPlays and all its data structures
* Tracks reported player position OR real-time listened when position not available
* Records listened sessions for more exhaustive "listen to" duration and eventually comparing incoming data playedAt for dups
* Uses own tracked data to calculate most likely player state (stopped/started/paused) as well as health (stale/orphaned from no data)
* Tracks player's reported state, if provided
2023-08-09 14:35:29 -04:00
FoxxMD 857971517f Implement basic player state 2023-08-08 17:26:28 -04:00
FoxxMD 51d16c9026 Fix timezone offset detection #87
Check forward and backward offsets and use absolute difference
2023-08-08 15:47:05 -04:00
FoxxMD 6b4fdf53eb Improve Jellyfin UserDataSaved event handling and prevent double reporting due to incorrect timestamp #87
* Move UserDataSaved heuristics into scrobble method implementation with more descriptive logging
* Add more aggressive UserDataSaved filtering by discarding play if its found in memory tracking, regardless of play date
* Attempt to correct bad UTC offset for second UserDataSaved event
2023-08-07 12:33:07 -04:00
FoxxMD 09dd5b7c5f Implement heuristics to detect offline jellyfin plays #87
* Refactor candidate/discovered Play maps to use tuple id instead of string so user can be compared later
* Add option to check all discovered platform plays (since jellyfin UserDataSaved doesn't include device id in payload)
* Check for UserDataSaved notification with correct reason as well as sanity check last played date (since jellyfin will save as played regardless of duration played)
* Bypass play tracking
2023-08-04 15:56:13 -04:00
FoxxMD c182767e5e Implement most read-only functionality for dashboard in CRA
* Layout and skeleton for dashboard
* Client/source cards with initial data
* Logs using SSE
2023-08-03 14:50:03 -04:00
FoxxMD efa93a1529 Redirect non-view server routes to path with /api prefix 2023-08-02 15:18:20 -04:00
FoxxMD 6efd1e54a8 POC CRA with existing express server 2023-08-02 14:18:02 -04:00
FoxxMD 231bd03d9a chore: Add docsite folders to dockerignore 2023-08-02 11:07:21 -04:00
FoxxMD 38c096b4d6 fix(youtube): Use youtube-music-ts-api fork while waiting for upstream PR merge #86 2023-08-02 10:08:47 -04:00
FoxxMD 68ca275673 fix(listenbrainz): Check for delimiters in user-submitted values before splitting #90 2023-08-02 09:49:28 -04:00
FoxxMD de05612873 fix(listenbrainz): Normalize and parse artist names from user-submitted artist/track info #90
Try to parse common patterns (ft/feat/featuring etc) from both artist and track information
2023-08-01 17:51:57 -04:00
FoxxMD bcb0c7471b fix(listenbrainz): Normalize artist names before mapping to scrobble payload #90 2023-07-27 10:13:18 -04:00
FoxxMD f856f79e51 Merge branch 'develop' 2023-07-17 15:41:00 -04:00
FoxxMD fbea08dded chore: Bump version 2023-07-17 15:40:44 -04:00
FoxxMD dc52376395 docs: Use external links for config examples
Docusaurus treats the files as assets and forces them to download when relative links. Instead, use external links to github so the user can stay in browser
2023-07-17 15:39:24 -04:00
FoxxMD 1e2bc0ca85 Merge branch 'docsite' into develop 2023-07-14 11:27:56 -04:00
FoxxMD 7849d6098a docs: Fix docker heading 2023-07-13 16:29:55 -04:00
FoxxMD 0b101b758e feat(tautulli): Support empty user to enable source with all users #84 2023-07-13 16:13:16 -04:00
FoxxMD 756ff0e0c1 chore(github): Update trigger branch for docs deploy to master 2023-07-13 16:12:31 -04:00
FoxxMD 2ddeca7d29 Use working directory without cd command 2023-07-13 15:58:12 -04:00
FoxxMD 1de934e30a Use working directory 2023-07-13 15:57:26 -04:00
FoxxMD 0409c526ee Assume we are still in docsite dir? 2023-07-13 15:54:25 -04:00
FoxxMD a7c340188f Fix run step typo 2023-07-13 15:52:42 -04:00
FoxxMD 5dc54849cc chore(github): Add action for build/deploy docusaurus docsite 2023-07-13 15:51:20 -04:00
FoxxMD 058fbee0c8 Migrate all docs to docsite 2023-07-13 15:40:48 -04:00
FoxxMD daea76eef3 feat: Implement somewhat-working docusaurus site 2023-07-13 15:08:00 -04:00
FoxxMD b81b1d94a3 docs: Update docker registry options 2023-07-13 11:21:27 -04:00
FoxxMD 226b7c741c feat: Implement filter logging controls
For jellyfin/plex/tautulli -- `logFilterFailure` option in config allows controlling if and what log LEVEL user/server filter failures are logged. #85

* Updated schema
* Updated examples
* Also added expected/found detailed logging for filters to jellyfin #84
2023-07-13 11:07:12 -04:00
FoxxMD e452bd761c chore(github): Publish to GHCR
Also pin actions at versions instead of commits
2023-07-13 09:58:07 -04:00
FoxxMD 19c3d97a46 chore(docker): Remove armv7 buildx platform
It's legacy and not supported in most places! Don't need to build for this anymore.
2023-07-13 09:46:22 -04:00
FoxxMD 64164b28de fix(plex): More config data normalization
* Split user value on comma to align with what config describes as acceptable (comma-delim)
* Trim found values to fix any whitespace typos
* On disallowed event logging add expected/found values to help with debugging

Fixes #84
2023-07-13 09:42:21 -04:00
FoxxMD b03cbe7a0a Merge branch 'develop' 2023-06-12 14:20:09 -04:00
FoxxMD 91bd702deb docs: Add multiple artist scrobbling limitation to FAQ 2023-06-12 12:23:49 -04:00
Matt Foxx ede22e10e9 Merge pull request #82 from Whhoesj/lastfm-fix 2023-06-12 12:10:05 -04:00
Wouter Habets c648ad4ae3 fix(lastfm): only scrobble primary artist to last.fm
When a track contains multiple artist, only the first artist should be send to Last.fm since it doesn't support multiple artists per track.
Previous behaviour was to combine all artists in a single string.

Issue: #81
2023-06-12 17:56:37 +02:00
FoxxMD a0fbd1129a chore: Add git-cliff template 2023-06-07 10:01:30 -04:00
FoxxMD b90ea03924 Merge branch 'develop' 2023-06-07 09:23:51 -04:00
FoxxMD 88efe2ad87 Bump versions for release 2023-06-07 09:23:40 -04:00
FoxxMD 39f933418e fix(ntfy): Include authorization object correctly in publish request data #80 2023-05-30 10:32:20 -04:00
FoxxMD cc8c7d84e2 fix(kodi): Fix hardcoded string #79
Probably leftover from debugging, oops
2023-05-25 12:18:29 -04:00
FoxxMD 6d9783c5f5 fix(tautulli): More response formatting safeguards #77
* Log warning if payload is empty
* Destructure request body with default empty object
2023-05-25 11:46:00 -04:00
FoxxMD c10d46b774 fix: Passing wrong argument to tautulli payload formatter #77 2023-05-24 11:47:29 -04:00
FoxxMD a5417277a8 feat: Use pre/post auth hooks for polling start to simplify logic
* Removes need to override poll() in child class and better defines when logic should be performed
* Fixes app crash when spotify credentials are present but invalid #76
2023-05-24 11:04:51 -04:00
FoxxMD 5a1c931513 fix: Re-initialize auth state after receiving auth callback
So we don't have to restart after authenticating in order to start polling

#76
2023-05-24 10:45:07 -04:00
FoxxMD 09f4503593 fix: Handle null/non-string values when truncating
#76
2023-05-19 12:25:22 -04:00
FoxxMD 045cae66f5 fix: Respect maloja response status and throw on error
Don't assume track scrobble correctly. Check body status prop and throw on error or log warnings if they are present

Maybe fixes #57
2023-04-18 10:51:25 -04:00
FoxxMD e67ef5fb0f fix: try to limit number of plays returned by deezer 2023-04-03 16:10:58 -04:00
FoxxMD 1677c0021f Update maloja track parsing in preparation for Maloja 3.2
Album structure changes
2023-04-03 15:59:29 -04:00
FoxxMD 3400e747a8 Fix listenbrainz example
Thanks @captainnapalm
2023-04-03 15:46:43 -04:00
FoxxMD c08ff4843c Bump flatpak build source 2023-03-27 10:02:49 -04:00
FoxxMD 5e905eae17 Merge branch 'develop' 2023-03-27 09:40:42 -04:00
FoxxMD 7ebe73ccb3 Update flatpak install instructions (official!) 2023-03-27 09:40:20 -04:00
FoxxMD 574911cfa0 Update flatpak instructions 2023-03-24 09:34:15 -04:00
FoxxMD 91a84dc4f4 Use files from repo rather than including separately to keep more things upstreams
https://github.com/flathub/flathub/pull/4002#pullrequestreview-1355412003
2023-03-23 16:09:18 -04:00
FoxxMD 9a578e54a8 Fix app id and other issues from review in flathub PR
https://github.com/flathub/flathub/pull/4002
2023-03-23 16:03:43 -04:00
FoxxMD 1a8d5eded3 Update metadata 2023-03-23 15:21:48 -04:00
FoxxMD 94765057db oops typo on icon name 2023-03-23 12:36:54 -04:00
FoxxMD 86c639ad2c Add actual MS icon
Commissioned from @mkalunivsky https://www.fiverr.com/mkalunivsky
2023-03-23 12:35:00 -04:00
FoxxMD f3045debb3 Scope dbus access to mpris 2023-03-23 12:16:28 -04:00
FoxxMD ffc826f04d Log message if running as foreground app with flatpak 2023-03-23 12:00:48 -04:00
FoxxMD e70a4bbf1c Update flatpak build with proper desktop integration
* Fix appstream (metainfo) missing properties and correct component types
* Add .desktop file that specifies terminal use
* Add placeholder icon
2023-03-23 11:56:53 -04:00
FoxxMD a9eca51bfa Implement user-defined scrobble thresholds #73
Add a scrobbleThresholds object with 'duration' and 'percent' values to all sources and sourceDefaults. If both are defined then first passing condition trigger scrobble.

* duration, in seconds, tests how long MS has been tracking the play (since discovery)
* percent, only used if source provides track duration, tests time MS has been tracking play (since discovery) is some % of track duration
2023-03-22 14:55:38 -04:00
FoxxMD de4a0aecd5 Add .flatpak-builder ignore in flatpak folder 2023-03-22 12:00:52 -04:00
FoxxMD f097fac8ea docs: Add systemd usage and add preview flatpak install instructions 2023-03-22 11:39:51 -04:00
FoxxMD a20b35f7cd More build improvements and provide config defaults to flatpak config dir
* Remove npm dev dependencies after app install/build
* Delete typescript files after build
* Provide a default config with file logging disabled and copy examples to config folder if empty
2023-03-22 10:32:29 -04:00
FoxxMD 2d51a0e159 Don't create rotating log file on init logger
We don't know if use wants files or there may not be permissions necessary...wait until we try to parse logging from config in appLogger
2023-03-22 10:31:04 -04:00
FoxxMD e524077242 Update flatpak build instructions to use npm 2023-03-21 16:54:23 -04:00
FoxxMD a5b5fe8d2a Use npm package-lock and update winston dependencies
* Update winston-duplex to remove hard winston dependency
* Use scoped @foxxmd/winston instead of git commit
  * Replace 'winston' with '@foxxmd/winston' in application
2023-03-21 16:53:42 -04:00
FoxxMD ddb3e0e02d Move into own folder and cleanup/instructions
Only blocking issue is needing to use yarn instead of npm
2023-03-21 11:09:25 -04:00
FoxxMD ec97ec428a Update permissions 2023-03-21 09:15:13 -04:00
FoxxMD 49b834af5d working config folder 2023-03-21 09:15:13 -04:00
FoxxMD 6329aaf6a9 Revert to git source for winston to fix typings problem 2023-03-21 09:15:12 -04:00
FoxxMD fd6e5ab084 Working example 2023-03-21 09:15:12 -04:00
FoxxMD 1dbb1b9ba3 Add network permission 2023-03-21 09:15:12 -04:00
FoxxMD fc65a63d43 Use yarn instead of npm 2023-03-21 09:15:12 -04:00
FoxxMD 4d1ddde57b NON working flatpak manifest
See comments in manifest

use npm published @foxxmd winston package
2023-03-21 09:15:12 -04:00
FoxxMD 7d14fb5341 Modify ioc and index so we can get port from config 2023-03-21 09:05:16 -04:00
FoxxMD e310b3aec8 Add PORT to aio config 2023-03-21 09:05:16 -04:00
FoxxMD 056457d028 Fix readJson logging usage 2023-03-21 09:05:16 -04:00
FoxxMD 73183e760e feat: Improve robustness of MPRIS data parsing
* Log warnings when any specific mpris info for a player isn't able to be parsed rather than failing entire poll attempt
* Fallback to undefined tracking progress if position cannot be parsed
2023-03-21 09:04:41 -04:00
FoxxMD 3fff729d32 Scrobble to Listenbrainz with MBID metadata if present
* Add play object data "meta" with a brainz property and common mbid tags
* Parse MBID data from jellyfin webhook
* Add MBID data from play object to 'additional_info' when scrobbling to listenbrainz

Closes #72
2023-03-20 22:37:54 -04:00
FoxxMD 19e199bd58 Pre-emptive adding flatpak folders to gitignore 2023-03-20 22:31:27 -04:00
FoxxMD 7b0f5801a1 Merge branch 'develop' 2023-03-20 11:06:38 -04:00
FoxxMD 4dbea91a5e docs: some words 2023-03-15 12:28:44 -04:00
FoxxMD 548356aaa4 feat: Implement Kodi as a Source 2023-03-15 12:26:35 -04:00
FoxxMD 8b3f4c636c Merge branch 'develop' 2023-03-15 09:13:20 -04:00
FoxxMD 1b0212f3bb feat: Improve polling retry strategy
* Increase default to 5 for all sources
* Use true exponential backoff for determining delay between polling
2023-03-15 09:06:26 -04:00
FoxxMD 14e9ec0b45 refactor: Remove superfluous 'Client' from scrobbler label
Already know what it is since it is prefixed by [Scrobblers] label
2023-03-15 08:30:18 -04:00
FoxxMD dab113ab76 docs: Missing jriver TOC entry 2023-03-13 14:01:40 -04:00
FoxxMD ad729769ec Implement JRiver Source #26 2023-03-13 13:59:24 -04:00
FoxxMD bb4ae549a3 Remove superfluous spotify label 2023-03-13 10:13:47 -04:00
FoxxMD 0786642a38 Increase default poll retries for Spotify
This seems to be a pain point so increasing ought to help.
2023-03-13 10:09:22 -04:00
FoxxMD 77d5f5884e Use error with cause for spotify api errors 2023-03-13 10:00:23 -04:00
FoxxMD 7a0a23b7fd Merge branch 'develop' 2023-03-10 15:12:53 -05:00
FoxxMD 1207a5eec1 docs: Update wording 2023-03-10 14:58:27 -05:00
FoxxMD 48a0ecad41 feat: Implement Listenbrainz as a client #16 2023-03-10 14:38:07 -05:00
FoxxMD f792feeb6c fix: re-add accidentally delete lastfm docs 2023-03-10 14:28:50 -05:00
FoxxMD d6b682a23a fix: Do not prematurely return no existing scrobble when no recent scrobbles exist
don't want to assume we won't find an existing just because there are no scrobbles from the client itself -- we may have found it from recent recorded scrobbles sent
2023-03-10 14:26:42 -05:00
FoxxMD d77d547c15 fix: fix always adding plays to newly discovered 2023-03-10 14:25:05 -05:00
FoxxMD 0dc828fa85 fix: time frame should be valid if there were no recent scrobbles retrieved 2023-03-10 14:08:48 -05:00
FoxxMD bd00a5f414 feat: Improve error and json logging
* Properly handle ErrorWithCause stacks
* Output json when log meta exists
2023-03-10 12:41:07 -05:00
FoxxMD f587ad4bbc feat: Use fixed length array to store scrobbled plays
May reduce memory usage as we don't store all scrobbles ever
2023-03-10 11:58:57 -05:00
FoxxMD b8f2b1e3f8 feat: Increase number of recent scrobbles to fetch 2023-03-10 11:38:24 -05:00
FoxxMD bf3b81f1cd feat(listenbrainz): Add timeout for listens response 2023-03-10 11:34:41 -05:00
FoxxMD 9375a6fce4 feat: Improve listenbrainz scrobbler source of truth behavior
Use updated listenbrainz api methods and scrobbled play method
2023-03-10 11:24:15 -05:00
FoxxMD ca148f490e refactor: Make scrobbled data transform responsibility of concrete scrobbler class 2023-03-10 11:23:15 -05:00
FoxxMD 9ab0468913 feat: Improve listenbrainz api
* The response track data is different than submission data so make sure we are accurately parsing that data to PlayObjects
* Add separate method for getting listens as recently played (play objects) and refactor getUserListens to return raw payload
2023-03-10 11:22:21 -05:00
FoxxMD 99a73a80db feat: Implement Listenbrainz source 2023-03-10 09:54:20 -05:00
FoxxMD 80ba6e6c79 remove commented out code 2023-03-09 14:33:16 -05:00
FoxxMD 7a4f0f0d3f fix: Fix mopidy response handling when no track is being played 2023-03-09 13:12:31 -05:00
FoxxMD a9a6c6ae15 feat: Improve scrobble client debug logging during scrobble loop
Use client logger for cleaner labels
2023-03-09 13:09:26 -05:00
FoxxMD c5190fc2a6 feat: Improve logging
* use child loggers instead of creating new loggers for each part of the app
  * cleans up and automates nested labels in log output
  * fixes "event emitters may be leaky" node warning
* add ability to specify log levels per output type (console, ui, file) using aioconfig
* improve UI logging experience
  * use more color-blind friendly colors (borrowed from context-mod)
  * add verbose log level output
  * increase log buffer and store all logs, then filter at render time, instead of discarding based on min log level so that we always have logs to display
  * fix level selection in ui to work for all levels
2023-03-09 13:02:46 -05:00
FoxxMD 93b543e1a4 Fix mopidy json schema url 2023-03-08 15:21:10 -05:00
FoxxMD 4d77ca9c11 Add missing mopidy example 2023-03-08 15:18:24 -05:00
FoxxMD 03cf3d1e3c feat: Implement Mopidy source #24 2023-03-08 15:16:37 -05:00
FoxxMD d2134f3467 fix: fix reversing referenced array 2023-03-08 09:57:34 -05:00
FoxxMD 97fe411ac0 feat: improved last activity date tracking 2023-03-07 14:22:27 -05:00
FoxxMD ff953b4535 fix: oops left in debug statement 2023-03-07 12:31:27 -05:00
FoxxMD b68fb9a9f9 fix(youtube): Fix bad index when checking for repeat tracks 2023-03-07 12:20:02 -05:00
FoxxMD ad1895d6e1 fix: Add fallback comparison when sorting plays by playDate 2023-03-07 12:19:29 -05:00
FoxxMD 51b4ea3b16 Refactor: Consolidate formatPlayObj options 2023-03-07 11:51:36 -05:00
FoxxMD 3d86a38a2e Refactor: Create a Source of Truth for track discover by sources and consolidate/simplify scrobble behavior
* Add a 'recentDiscoveredPlays' class property for abstract source
  * recentDiscoveredPlays is platform aware and configured based on source 'multiPlatform' class property
* Consolidate scrobble behavior into abstract class
  * Check for existing discovered track before scrobbling
  * Add to discovered if not existing and increment discovered tracks counter
* Decouple sources from clients by using event emitter when a newly discovered track should be scrobbled
* Use discovered tracks for UI display instead of api data
2023-03-07 11:27:25 -05:00
FoxxMD 605aad1df3 Refactor: Consolidate scrobble client existing play check into abstract client
* Move main logic into class methods that can be overridden
* Move title, artist, and date checks into class methods that can be overridden
2023-03-07 10:36:05 -05:00
FoxxMD 3d2e3534ff Refactor: Move play date comparison into util 2023-03-07 10:34:20 -05:00
FoxxMD 2a65ffe6c4 Merge branch 'statefulSOT' into experimental 2023-03-06 13:20:37 -05:00
FoxxMD e3e3e03249 Merge branch 'spotifyConnect' into experimental
# Conflicts:
#	src/sources/AbstractSource.ts
#	src/sources/SpotifySource.ts
#	src/sources/SubsonicSource.ts
2023-03-06 13:20:30 -05:00
FoxxMD 6bd8642c91 Initial pass at IOC 2023-03-06 13:13:25 -05:00
FoxxMD 40ae842f82 Add fixed-size-list
To be used for storing a fixed length array of scrobbled plays for clients and a discovered tracks for sources
2023-03-06 11:12:11 -05:00
FoxxMD a2ab1aa49e feat: Add polling options to config schema and make polling more aggressive
Since spotify now uses currently playing with memory we need to poll more aggressively to make sure we catch tracks as they are played. Additionally need to decrease max polling interval so as not to miss activity.
2023-03-03 12:32:16 -05:00
FoxxMD 6d3cbf32df feat(spotify): Refactor spotify Source as Memory Source #51
* Use recently-played to scrobble backlogged tracks
* Use currently-playing or current-state to implement spotify as a memory source in order to track plays made from spotify connect devices
2023-03-02 15:29:31 -05:00
FoxxMD 7a8138773c feat(spotify): Implement currently playing retrieval 2023-03-02 13:38:56 -05:00
FoxxMD 5ae79deca4 feat: Implement MPRIS Source 2023-03-02 12:57:29 -05:00
FoxxMD 285f593a0f Add dbus-next dependency 2023-03-02 10:29:46 -05:00
FoxxMD e0ae621b62 feat: Implement more context awareness for memory sources to handle multi-platform/device plays
* Track candidate plays separately based on device/platform/user metadata
* Perform additional check for playback progress if metadata exists

Fixes #67
2023-02-28 12:34:09 -05:00
FoxxMD 259d4dabc8 feat: Parse device id metadata from sources to be used for multi-player awareness #67 2023-02-28 11:12:15 -05:00
FoxxMD f7b03d9622 refactor: Rename sourceId to trackId to be clearer in what it actually is 2023-02-28 10:14:06 -05:00
FoxxMD bd188a8ca7 refactor: Remove unused trackLength property 2023-02-28 09:58:56 -05:00
FoxxMD 28910a9332 Merge branch 'develop' 2023-02-27 11:13:28 -05:00
FoxxMD ee38252e1a docs: Add webhook and healthcheck endpoint documentation 2023-02-27 10:58:39 -05:00
FoxxMD 6f3595db31 feat: Add initialization and auth tests for notifiers 2023-02-27 10:58:31 -05:00
FoxxMD c5c0cd9cd2 refactor: Improve title and messages for webhook notifications 2023-02-27 10:09:25 -05:00
FoxxMD f5da32d233 fix: YT Music error throwing 2023-02-27 09:35:44 -05:00
FoxxMD 315869dafe fix: Update auth check when starting polling
Use general requiresAuth instead of only requiresAuthInteraction
2023-02-27 09:35:29 -05:00
FoxxMD 330802e29b fix: Default import for gotify library 2023-02-27 09:34:52 -05:00
FoxxMD 3d5e988ef0 Suppress error 2023-02-24 15:31:43 -05:00
FoxxMD 9a2b4b3d1e fix: Maybe fix for missing lastfm credentials configdir base 2023-02-24 15:27:16 -05:00
FoxxMD 4a1ebcdbc6 fix: Fix webhook missing initializer value 2023-02-24 13:57:08 -05:00
FoxxMD bc66fdb2b6 fix: Stop api calls if authentication failed 2023-02-24 13:56:52 -05:00
FoxxMD 6c2b0557c6 feat: Implement health endpoint #66
* HTTP Status as primary indicator with messages in json response
* No parameters in request aggregates all client/source statuses. Otherwise can use 'type' or 'name' parameters to restrict client/sources to aggregate
2023-02-24 13:51:59 -05:00
FoxxMD c24dc41811 feat: Implement webhook-based status notifications #66
* Implemented notifications for Gotify and Ntfy
* Notifications pushed on: polling started, polling retry, polling stopped on error, scrobble client scrobble failure
2023-02-24 13:19:03 -05:00
FoxxMD 0a41d32084 fix: Fix config deconstruct for polling data 2023-02-24 11:13:58 -05:00
FoxxMD b7aa087b74 feat: Add debug logging of jellyfin webhook payload with docs 2023-02-24 11:04:47 -05:00
FoxxMD 1f511d964d feat: Improve jellyfin logging
* Refactor initial connection info to be more verbose and only log once
* Refactor valid event check logging order of operations and make logging more verbose
2023-02-24 10:48:40 -05:00
FoxxMD 14eabecb7d Update google headers image 2023-02-23 12:57:11 -05:00
FoxxMD 8e31aa431d docs: Add youtube music docs and example 2023-02-23 12:54:48 -05:00
FoxxMD e47a636c2d feat: Add friendly auth error and hints for YT authentication test 2023-02-23 12:27:37 -05:00
FoxxMD bdd14b16de feat: Implement YT recent played UI 2023-02-23 12:16:44 -05:00
FoxxMD 8cc5aacb9a feat: Implement working YT Music source #34 2023-02-23 12:07:06 -05:00
FoxxMD d8e861dd54 feat: Add YTMusic api implementation and basic source 2023-02-22 16:35:09 -05:00
FoxxMD e369f9eb21 Merge branch 'develop' 2023-02-22 12:55:23 -05:00
FoxxMD ce6246e479 docs: Add FAQ to bug report template 2023-02-22 12:55:14 -05:00
FoxxMD 0c58cc6687 Merge branch 'develop' 2023-02-22 12:09:40 -05:00
FoxxMD 19687b725d fix: Fix logging empty ingress notifications 2023-02-22 12:00:13 -05:00
FoxxMD 02dee5fb6c docs: Add bug report template 2023-02-22 11:54:13 -05:00
FoxxMD e08a1ad514 fix: fix typo 2023-02-22 11:46:01 -05:00
FoxxMD 865f1064f5 docs: Add FAQ 2023-02-22 11:43:57 -05:00
FoxxMD 2b798e5a69 feat: Implement initial request logging for all sources that use ingress instead of polling
In order to make basic troubleshooting easier all ingress-based sources (plex, tautulli, jellyfin) now log initial connections and basic request validation checks:

* Request logging includes remote address and user agent
* Log initial requests before any middleware/body parsing
* Include method for checking if request is valid at a low-level (IE checking request verb)
* Include source-specific methods for checking if request payload is valid
2023-02-22 11:04:50 -05:00
FoxxMD da4d2ecade docs: Add additional properties to tautulli payload 2023-02-22 11:01:24 -05:00
FoxxMD 89540d8b76 docs: Add jellyfin example to kitchen sink and fix inconsistencies #55 2023-02-21 12:54:40 -05:00
FoxxMD 931709d277 fix: typo 2023-02-21 12:50:11 -05:00
FoxxMD 1b199750bd docs: Simplify and correct example configs
* Keep examples as valid json by removing comments and fixing trailing commas #55
* Add README pointing to configuration docs
2023-02-21 12:49:20 -05:00
FoxxMD 4b1fa710f7 docs: Rename json-based to file-based for consistent terminology 2023-02-21 12:47:56 -05:00
FoxxMD 360b6f4cf0 docs: Add links to schema explorer for json configs 2023-02-21 12:27:55 -05:00
FoxxMD e960438968 feat: Revert specific config requirement to be a top-level array
Wasn't really necessary and would cause breaking change for existing configs.
2023-02-21 12:27:27 -05:00
FoxxMD 93230c45c7 docs: Add examples and more descriptions to json schema 2023-02-21 11:52:27 -05:00
FoxxMD a3fd30c427 fix: Update project TS and node configuration in order to correctly generate json schema
* Output to commonjs using TS (remove type:module from package.json)
* Update tsconfig config
* Downgrade formidable to fix ES module import error
* Remove js assertions and use separate json schema for aio client/source validation to keep MS log output cleaner
2023-02-21 11:05:33 -05:00
FoxxMD c9bac6f7d2 fix: Use separate interface for maloja config data with inherited properties 2023-02-21 11:02:30 -05:00
FoxxMD 06c0b64ee5 fix: Correct plex data types 2023-02-21 11:01:28 -05:00
FoxxMD 70a1181ea1 chore: Add missing passport-deezer typings 2023-02-21 11:00:56 -05:00
FoxxMD be9938fe95 missed some wording 2023-02-20 11:32:47 -05:00
FoxxMD 27acfa9489 docs: Simplify oauth config by removing unused access/refresh token
Although it was nice to include the option no one is providing their own tokens from a separate flow completed elsewhere. It simplifies readability and usage in MS to remove these and always generate out own.
2023-02-20 11:31:24 -05:00
FoxxMD fc26125f80 fix anchor 2023-02-20 11:19:33 -05:00
FoxxMD 193a5c1e4d docs: Rewrite docs to include more examples and clearer usage 2023-02-20 11:18:01 -05:00
FoxxMD 700722627a feat: Allow parsing json files as json5
So users can use example json files with comments as-is
2023-02-20 09:42:16 -05:00
FoxxMD 1f6996d881 chore: Update superagent version 2023-02-14 11:35:44 -05:00
FoxxMD 1cbad9ccfd refactor: Use node from alpine packages instead of building from source
Using LTS either way but building from source makes image much larger and takes forever on build runners (github)
2023-02-14 11:23:05 -05:00
FoxxMD 04c9de648d refactor: Update s6-overlay usage for ms config and start up
Refactor s6 usage based on most recent lsio images
2023-02-14 10:44:31 -05:00
FoxxMD 2b60a0f76f fix: Fix ENV usage for jellyfin #61
Correct property names when parsing from ENV
2023-02-10 13:53:09 -05:00
FoxxMD 176ff80172 Fix docker example folder mapping 2023-02-10 12:46:20 -05:00
FoxxMD e2d150ce15 feat: add docker-compose with instructions #55 2023-02-10 12:44:22 -05:00
FoxxMD 636004a1bb Refactor: Update dockerfile and simplify configuration #55
* Update to alpine 3.17 and node 18 to match the project
* Use LSIO base to enable ui/guid usage through env
* Use build stages to reduce image size and speed up build
* Refactor build to use typescript
* Simplify config directory by using root-level /config as default
* Add bash script on startup to copy example configs if a new config folder is detected
2023-02-10 12:12:57 -05:00
FoxxMD 2258bb36b9 Refactor: Simplify logs location by using config directory #55 2023-02-10 12:09:20 -05:00
FoxxMD 0583a59177 Regenerate json schema files 2023-02-10 12:08:37 -05:00
FoxxMD fbbcfc5a57 Downgrade socket.io to avoid ESM module issue 2023-02-10 11:09:43 -05:00
FoxxMD 317da71ec3 Ignore .bak files 2023-02-10 10:18:15 -05:00
FoxxMD 22bbcfe3e5 Remove unused subsonic library 2023-02-10 10:18:05 -05:00
FoxxMD de3832f3ce More TS fixes 2023-02-09 16:09:52 -05:00
FoxxMD 04b07d6bfb Update abstract source config usage 2023-02-09 15:18:31 -05:00
FoxxMD fdf00d8acc Update deezer config usage 2023-02-09 15:18:09 -05:00
FoxxMD 3d2296858f Update subsonic source config usage 2023-02-09 15:15:24 -05:00
FoxxMD b1390cd74a Update spotify client typings 2023-02-09 15:05:14 -05:00
FoxxMD 216d92cad7 Add recentlyPlayed option interface 2023-02-09 15:04:59 -05:00
FoxxMD dcde41f89a Fix spotify source credential handling 2023-02-09 14:48:27 -05:00
FoxxMD 1e9518ac54 Fix spotify source credential handling 2023-02-09 14:48:20 -05:00
FoxxMD ac2aa8297c Remove ts-migrate 2023-02-09 14:48:12 -05:00
FoxxMD 8611798051 it works! 2023-02-09 13:51:06 -05:00
FoxxMD a659897d37 refactor: Update project to ESM
* Update relative import extension
* Remove (bad?) TSconfig extends presets
* Fix default non-relative imports
2023-02-09 12:50:37 -05:00
FoxxMD b4303fa81c refactor: add typings everywhere they are needed 2023-02-09 12:22:55 -05:00
FoxxMDandts-migrate 8f0a75a81b [ts-migrate][.] Run TS Migrate
Co-authored-by: ts-migrate <>
2023-02-08 12:56:09 -05:00
FoxxMDandts-migrate e006476b52 [ts-migrate][.] Rename files from JS/JSX to TS/TSX
Co-authored-by: ts-migrate <>
2023-02-08 12:56:04 -05:00
FoxxMDandts-migrate bb7bfc4a6e [ts-migrate][.] Init tsconfig.json file
Co-authored-by: ts-migrate <>
2023-02-08 12:56:04 -05:00
FoxxMDandts-migrate 03c3f6b6a2 [ts-migrate][.] Init tsconfig.json file
Co-authored-by: ts-migrate <>
2023-02-08 12:55:08 -05:00
FoxxMDandts-migrate 900f3c2471 [ts-migrate][.] Init tsconfig.json file
Co-authored-by: ts-migrate <>
2023-02-08 12:53:42 -05:00
FoxxMDandts-migrate cd86acdcf0 [ts-migrate][.] Init tsconfig.json file
Co-authored-by: ts-migrate <>
2023-02-08 12:53:13 -05:00
FoxxMD abf338f71a refactor: Move application files into src folder
* In preparation for documentation site and building from TS isolate app files to own folder
* Fix log/config/views folder default location
2023-02-08 12:46:46 -05:00
FoxxMD 91bb0cba45 Update to note 18 and add tsconfig 2023-02-08 12:30:39 -05:00
FoxxMD 8499e3197d feat: Add unknown route logging 2023-02-08 11:53:37 -05:00
FoxxMD bfc3568b00 feat: Add more debug logging for memory source play object tracking 2023-02-08 11:13:35 -05:00
FoxxMD 26613f8b68 docs: Update jellyfin webhook install instructions 2023-02-08 11:13:03 -05:00
FoxxMD 0b2d4b5f89 feat: Add more information to jellyfin play object and logging
* Standardize runtime stamp parsing
* Add playback position for future use
* Add source version and log first-seen server logging to jellyfin source
2023-02-08 11:12:44 -05:00
FoxxMD 6561ec9404 fix: Fix retrieving incorrect source for spotify callback when sources are unnamed
MS incorrectly chooses the first unnamed source (based on built source order) when handling spotify callback. Fixed by specify source type when retrieving source. Fixes #63
2023-02-03 11:04:49 -05:00
FoxxMD 517df8a3ad chore: remove quotes from nvmrc 2023-02-03 11:02:33 -05:00
Matt Foxx 4a0cdc0e87 Merge pull request #62 from samdoshi/lastfm-docs 2023-01-21 17:48:27 -05:00
Sam Doshi a54bc99925 add note about privacy setting for Last.fm sources 2023-01-21 15:28:21 +00:00
Matt Foxx eeae98360f Merge pull request #56 from CPU-Blanc/bugfixing
Fix: Jellyfin source
2022-12-29 08:58:08 -05:00
CPU_Blanc c5a938d4eb Fix Jellyfin source 2022-11-29 02:01:20 +00:00
FoxxMD c5b7dbd50f fix: Improve scrobble client init flow and retries
* Refactor init state to have an interim value and use getter/setter to control it
* Replace usage of 'ready' class field on maloja client with serverIsHealthy (make it client specific)
* Add some missing functions in abstract client class
* If client is not already initialized (or trying to initialize) then try to initialize client on scrobble attempt, maybe fixes #44
2022-06-16 11:06:06 -04:00
FoxxMD 26cd478c6f fix(plex): Properly handle requests without data 2022-06-15 12:01:38 -04:00
FoxxMD 670805f18a feat(plex): Improve request handling and debug info
* Replace multer with formidable as it is more up-to-date and has more granular options for tracking progress and parsing data
* Add debug logging for the lifecycle of a plex request
* throws errors if any part of plex request lifecycle is unexpected
2022-06-15 11:51:58 -04:00
Matt Foxx 8b851d2220 Merge pull request #49 from northys/tautulli_readme_duration
docs: tautulli payload duration => duration_sec [fixes #48]
2022-04-28 16:24:36 -04:00
Jiri Travnicek 7a1279b02c docs: tautulli payload duration => duration_sec [fixes #48] 2022-04-28 22:11:42 +02:00
FoxxMD 3f2a49dedf Merge remote-tracking branch 'origin/develop' into develop 2022-04-28 10:25:27 -04:00
FoxxMD d0f792c107 feat: Add scrobble client request data to logging
* Output request payload as part of log message on error
* Always output request payload (raw) to debug
* Should help with debugging #48
2022-04-28 10:25:19 -04:00
Matt Foxx ff9172001f Merge pull request #46 from northys/build_arm
build arm [closes #45]
2022-04-26 23:14:10 -04:00
Jiri Travnicek 0b1772b30d build arm [closes #45] 2022-04-26 20:50:28 +02:00
FoxxMD 64b77cab6b fix(maloja): Use length as track length property for all maloja versions
#42
2022-04-15 12:19:15 -04:00
FoxxMD 8aecd9dbce fix(maloja): Use a different data shape for new scrobbles on maloja >= 3.0.3
Addresses #42
2022-04-15 09:44:43 -04:00
FoxxMD 0bdd0fd801 fix(ui): Provide defaults to express-session to prevent deprecation message
Closes #41
2022-04-14 09:59:49 -04:00
FoxxMD 0ede2cd96b feat(maloja): Support maloja >= 3.0.0
* Use server version to determine shape of scrobble data when formatting play object
* Refactor adding new scrobble to recent list to use correct shape based on server version
2022-04-14 09:45:44 -04:00
FoxxMD 8219dcecd7 refactor(maloja): Handle maloja server version use cases for auth/readiness 2021-12-17 10:51:42 -05:00
FoxxMD 42a5fdf9a1 Don't invoke scrobble function if source has no plays
If source does not have new OR existing plays to check against clients there is no reason to call scrobble function
2021-12-14 11:06:21 -05:00
FoxxMD 9bc6f44af4 Implement readiness check for maloja client
* Use rebuild status and health reported from maloja api endpoint to determine if server is ready to scrobble/query
* Use readiness as prerequisite for auth test (can be removed if krateng/maloja#92 is merged)
2021-12-14 11:05:22 -05:00
FoxxMD ccb31c5866 Implement ready state check for scrobble clients
Default state is calculated from initialized and auth status. Allows an additional check for client readiness independent of communication (initialized) status and auth status.
2021-12-14 11:03:03 -05:00
FoxxMD 30f9ebf900 Add note about authentication flow on deezer config 2021-10-20 14:30:56 -04:00
FoxxMD 9b4eb2d383 Implement Deezer source
Closes #35
2021-10-20 14:23:41 -04:00
FoxxMD 6e2f765e69 fix(lastfm): Fix mishandled properties in scrobble request causing ignored track
Removing undefined properties from request data for track scrobble fixes an issue where last.fm ignores the track and returns error code 1

Fixes #33
2021-10-05 12:06:50 -04:00
FoxxMD 57fd88556d Better detection when last.fm ignores a scrobble
* Use ignored count to determine if warning is logged
* Let user know if no error message included
* Include link to error code reference in last.fm api docs
2021-09-29 12:42:22 -04:00
FoxxMD 034f5b217e Fix missing auth variable for status render 2021-08-26 10:23:07 -04:00
FoxxMD a5112e0bbb Add github action to push docker image 2021-08-26 10:22:54 -04:00
FoxxMD f6db5fa14e Add streaming indicator 2021-07-14 10:35:43 -04:00
FoxxMD d7de41d642 Improve log streaming
* Emit individual log statements in winston formatter
* Emit different event when logs should be cleared
* Use window global to keep track of sort order when appending on emit
2021-07-14 10:16:51 -04:00
Matt Foxx ca0d1a4f90 Merge pull request #30 from christophernewton/master
feat: removed fs and made the options rely more on sockets
2021-07-14 09:42:08 -04:00
Chris Newton 46fc9143b4 feat: removed fs and made the options rely more on sockets 2021-07-14 13:58:45 +10:00
Matt Foxx 0800cff6fc Merge pull request #29 from christophernewton/master
Added Websockets
2021-07-13 10:00:13 -04:00
Chris Newton 3106b56dee feat: fixed issue with trailing commas 2021-07-13 14:43:35 +10:00
Chris Newton 08b1ac04bb feat: added websocket for streaming logs 2021-07-13 14:20:20 +10:00
Chris Newton 1a55cc482c feat: cleaned up formatting 2021-07-13 11:53:18 +10:00
Matt Foxx 73aa7ad890 Merge pull request #28 from christophernewton/master
Small QOL updates
2021-07-12 13:47:38 -04:00
Chris Newton 95db3da58f Merge branch 'FoxxMD:master' into master 2021-07-12 14:14:46 +10:00
Chris Newton 31926e6ad9 feat: added better check for log colors, added favicon, fixed mobile viewport issue 2021-07-12 14:13:51 +10:00
FoxxMD 71a8ad2418 Fix typo on users specification for jellyfin source setup 2021-07-05 10:43:44 -04:00
FoxxMD 49bd1c8836 Refactor initialization and authentication stages for client/source building
* Break up into different functions and add properties to concrete classes signalling its capabilities
* More detailed logging based on which fails/succeeds
* Simplify client/source init/auth step (no more need for case switch specifics other than creating object)
2021-04-14 16:22:05 -04:00
FoxxMD fc3dd62858 Refactor source building and initialization to log failures but continue
* Instead of stopping the whole application for one misconfigured config just log the issue and continue with any valid/working configs.
* Add better logging for type.json issues (add index)
* Add deprecation warnings for single-user mode structures in type.json (to be removed in 0.4)
2021-04-14 11:28:55 -04:00
FoxxMD ff114a0ae5 Refactor client building and initialization to log failures but continue
* Instead of stopping the whole application for one misconfigured config just log the issue and continue with any valid/working configs.
* Add better logging for type.json issues (add index)
* Add deprecation warnings for single-user mode structures in type.json (to be removed in 0.4)
2021-04-14 11:14:57 -04:00
FoxxMD 8bfcb6cd9a Showcase last.fm as a source in readme 2021-04-14 10:19:23 -04:00
FoxxMD 8154e30939 Add last.fm as source to readme 2021-04-13 17:05:46 -04:00
FoxxMD 506440825f Implement Last.fm as a source
For #23
2021-04-13 17:04:40 -04:00
FoxxMD 646723fcf8 Add ui screenshot to readme 2021-03-17 10:34:51 -04:00
FoxxMD a560a49aac Fix typo 2021-03-16 15:44:38 -04:00
FoxxMD de763d68f3 Add Jellyfin source #15
* Implement Jellyfin source similar to Plex, but using MemorySource
* Add configuration docs and json example
2021-03-16 15:36:51 -04:00
FoxxMD f731c25332 Return new plays when memory source adds them to stateful
Will make it easier for non-polling sources to see what is new from state
2021-03-16 15:33:41 -04:00
FoxxMD f738eb92e8 Merge branch 'develop' into feat-jellyfin 2021-03-16 14:39:26 -04:00
FoxxMD b9f4f43d30 Refactor subsonic source to use MemorySource
* Should stabilize subsonic as a source and prevents duplicate scrobbles
* Reduce polling interval to 10 seconds for more accurate recently played data and restrict max backoff time to 30 seconds
2021-03-16 14:38:59 -04:00
FoxxMD e42f00604e Refactor polling source backoff approach
Use time-based approach rather than check count approach so active period is not sensitive to interval value
2021-03-16 14:37:15 -04:00
FoxxMD 74eed98b9b Fix filter method 2021-03-16 14:25:55 -04:00
FoxxMD cef6f5864a Implement "in memory" recently played tracking for sources that don't support a source of truth
If sources don't support a scrobble action or don't return a sane "recently played" data source we need to keep track of these plays ourselves. Do it in memory based on currently playing and return that as source "source of truth" for recently played.
2021-03-16 14:01:17 -04:00
FoxxMD d5e816b1d3 Use a persistent datetime based on source instantiation or last track play as client refresh check threshold
We don't need to refresh clients on every source poll, only when first creating a source or when a new track play is discovered.
2021-03-16 13:59:43 -04:00
FoxxMD dcc4201019 Improve parsing of last.fm track responses and handling recent tracks
* Include some metadata (mbid, url, nowplaying)
* Check for more artist properties since responses seem inconsistent now
* Check for timestamp to handle nowplaying use case
* Handle invalid scrobbles from last.fm on recents refresh WRT nowplaying and missing timestamps #22
2021-03-16 10:57:44 -04:00
FoxxMD 9c093a8455 POC jellyfin implementation #15
* Hopefully can get artist from webhook payload in the future so we can actually use this
* Add basic instructions for using Webhook plugin in configuration docs
2021-03-12 11:24:33 -05:00
FoxxMD 9a56a3ee4d Merge branch 'develop' into feat-jellyfin
# Conflicts:
#	index.js
2021-03-12 10:24:54 -05:00
Matt Foxx f2b0714dea Merge pull request #21 from christophernewton/master
Feature: Fix padding on client box
2021-03-02 09:19:19 -05:00
Chris Newton 0a1357acc7 feat: fix padding on client boxes 2021-03-02 08:54:47 +11:00
Matt Foxx 403af711eb Merge pull request #20 from christophernewton/master
Frontend update
2021-03-01 16:36:07 -05:00
Chris Newton 31cd72cd15 feat: fixed typo in debug output, added active state for links 2021-03-02 08:24:32 +11:00
Chris Newton 55afe876e0 feat: fixed typo in readme for docker 2021-02-27 14:31:41 +11:00
Chris Newton 49fadd8c9a feat: added styles, dark mode to frontend 2021-02-27 14:30:39 +11:00
Chris Newton 58157ddfee wip: updating styles for front end 2021-02-26 16:54:08 +11:00
FoxxMD 2a9cd87848 Update package properties 2021-02-25 09:20:37 -05:00
FoxxMD 7b31285f89 Fix missing throw error from polling try-catch
Without it the polling continues forever on loop! oops
2021-02-19 09:14:10 -05:00
FoxxMD 89db858289 Fix import error for Errors from spotify-web-api-node 2021-02-19 09:13:16 -05:00
FoxxMD 2fec6aff6e Implement request and polling retries
* Hierarchical retries and delay options for sources and clients (override general config => individual config)
* Logging for retry attempts
* Respect Retry-After header on responses if present
2021-02-18 15:01:21 -05:00
FoxxMD 9beadfaf0f Bump dependencies to fix spotify-web-api-node crash
spotify-web-api-node#340
2021-02-18 10:04:12 -05:00
FoxxMD dd7e971e71 Fix how scrobbled tracks are returned to source
Returns tracks array should only show that was a track was scrobbled or not (by existing in the array) -- and only be in the array once.
2021-01-12 09:47:47 -05:00
FoxxMD 855d3f6144 Jellyfin initial code 2021-01-04 12:06:45 -05:00
406 changed files with 78338 additions and 4165 deletions
+38
View File
@@ -0,0 +1,38 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node
{
"name": "Node.js",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/javascript-node:1-20-bookworm",
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [9078,3000],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "npm install",
// Configure tool-specific properties.
"customizations": {
"vscode": {
"extensions": [
"hbenl.vscode-mocha-test-adapter",
"dbaeumer.vscode-eslint",
"unifiedjs.vscode-mdx"
]
}
},
"portsAttributes": {
"3000": {
"label": "Docusaurus"
},
"9078": {
"label": "App"
}
}
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
+21 -4
View File
@@ -2,12 +2,29 @@
node_modules
npm-debug.log
Dockerfile
*.Dockerfile
.dockerignore
.gitignore
.github
.git
config/currentCreds.json
*.log
config/maloja.json
config/spotify.json
config/config.json
.idea
config/*.json
config/*.bak
config/*.p8
/docs
/logs
.flatpak-builder
**/generated-sources.*
flatpak/.flatpak-builder
docsite/build
docsite/node_modules
docsite/.docusaurus
docsite/.cache-loader
build
*.secrets
.devcontainer
.vscode
**/act/*
tmp-*
.env
+67
View File
@@ -0,0 +1,67 @@
name: Bug Report
description: Report a reproducible problem
title: "bug: "
labels: ["bug"]
body:
- type: checkboxes
attributes:
label: Please check existing knowledge before opening an issue
options:
- label: I have [checked the FAQ](https://foxxmd.github.io/multi-scrobbler/docs/FAQ/) and [documentation](https://foxxmd.github.io/multi-scrobbler/docs/configuration/)
required: true
- label: I have [searched through existing issues](https://github.com/FoxxMD/multi-scrobbler/issues?q=sort%3Aupdated-desc) and [discussions.](https://github.com/FoxxMD/multi-scrobbler/discussions)
required: true
- type: markdown
attributes:
value: |
If this a how-do-I question, troublehooting, or generally **not** a bug/error with multi-scrobbler please consider [**starting a discussion**](https://github.com/FoxxMD/multi-scrobbler/discussions) instead of opening an issue.
- type: textarea
id: what-happened
attributes:
label: Describe the Bug
description: Describe what you expected to happen and what happened instead. Please provide as much detail as possible.
placeholder: The app should have scrobbled from Spotify but instead it crashed with an error about undefined properties, etc...
validations:
required: true
- type: dropdown
id: platform
attributes:
label: Platform
description: What platform are you running multi-scrobbler on?
options:
- Docker
- Local (NodeJS)
- Flatpak
default: 0
validations:
required: true
- type: textarea
attributes:
label: Versions
description: What version of multi-scrobbler and issue-related sources/clients are running?
placeholder: |
- multi-scrobbler: 0.8.3 on docker
- maloja 3.1.4
- jellyfin 10.8.9
validations:
required: true
- type: textarea
id: logs
attributes:
label: Logs
description: |
Please copy and paste any/all relevant from the DEBUG level log output.
Even if your issue does not seem to be shown in the logs you should still add as much detail as possible, a _lack_ of issues in logs may be a good hint during debugging.
Your logs will be automatically formatted into code, do not need use backticks.
If possible reproduce the issue with [**Debug Mode** enabled](https://foxxmd.github.io/multi-scrobbler/docs/configuration#debug-mode).
render: shell
placeholder: |
[2025-02-14 12:47:57.170 -0500] VERBOSE: [Init] Config Dir ENV: undefined -> Resolved: CWD/config
[2025-02-14 12:47:57.236 -0500] INFO : [Init] Debug Mode: NO
[2025-02-14 12:47:57.258 -0500] INFO : [Init] Version: master
[2025-02-14 12:47:57.259 -0500] INFO : [Init] Generating schema definitions...
[2025-02-14 12:47:59.148 -0500] DEBUG : [App] [Scrobblers] [Maloja - default] Found 100 recent scrobbles
- type: textarea
attributes:
label: Additional Context
description: Add any other context about the problem here.
@@ -0,0 +1,31 @@
name: Feature Request
description: Suggest a new feature or functionality
title: "feat: "
labels: ["enhancement"]
body:
- type: checkboxes
attributes:
label: Please check existing knowledge before opening an issue
options:
- label: I have checked the [documentation](https://foxxmd.github.io/multi-scrobbler/docs/configuration/)
required: true
- label: I have [searched through existing issues](https://github.com/FoxxMD/multi-scrobbler/issues?q=sort%3Aupdated-desc) and [discussions.](https://github.com/FoxxMD/multi-scrobbler/discussions)
required: true
- type: textarea
attributes:
label: Describe the new functionality
description: Please describe the feature or functionality you want multi-scrobbler to have. Use as much detail as possible about the exact behavior you wish to see.
validations:
required: true
- type: textarea
attributes:
label: Configuration Example
description: |
If the functionality involves changes to configuration files please provide an example here
render: json5
- type: checkboxes
attributes:
label: Will you help implement it?
options:
- label: I am willing to contribute code towards completing this feature
- label: I am willing to help test preview builds for this feature
@@ -0,0 +1,75 @@
name: Request a new Source or Client
description: Request a new Source or Client to be implemented
title: "feat: "
labels: ["enhancement"]
body:
- type: checkboxes
attributes:
label: Please check existing knowledge before opening an issue
options:
- label: I have checked the [documentation](https://foxxmd.github.io/multi-scrobbler/docs/configuration/)
required: true
- label: I have [searched through existing issues](https://github.com/FoxxMD/multi-scrobbler/issues?q=sort%3Aupdated-desc) and [discussions.](https://github.com/FoxxMD/multi-scrobbler/discussions)
required: true
- type: dropdown
attributes:
label: What Type?
description: Is this a Source or a Client?
options:
- Source
- Client
default: 1
validations:
required: true
- type: input
attributes:
label: Name
description: "What is the name of the new service/software?"
placeholder: "My Cool Player"
validations:
required: true
- type: input
attributes:
label: Website
description: "Website for the service/software. Preferably a github repo or other source-code homepage"
validations:
required: true
- type: dropdown
attributes:
label: API Available?
description: Does the service have an API or other protocol that multi-scrobbler can use to communicate? **Without this the likelyhood of implementation is very low.**
options:
- 'Yes'
- 'No'
default: 1
validations:
required: true
- type: dropdown
attributes:
label: Free To Use?
description: Is this service/software freely available or does it require purchasing/subscription?
options:
- 'Yes'
- 'No'
default: 1
validations:
required: true
- type: dropdown
attributes:
label: Willing to finance development?
description: If the software is **non-free** are you will to provide a paid account/subscription to multi-scrobbler developer's in order to facilitate development/testing?
options:
- 'Yes'
- 'No'
validations:
required: false
- type: checkboxes
attributes:
label: Will you help implement it?
options:
- label: I am willing to contribute code towards completing this feature
- label: I am willing to help test preview builds for this feature
- type: textarea
attributes:
label: Additional Context
description: Add any other context about the new Source/Client here
+1
View File
@@ -0,0 +1 @@
blank_issues_enabled: false
+5
View File
@@ -0,0 +1,5 @@
--directory ../../
--env-file .github/act/.env
--secret-file .github/act/.secrets
--artifact-server-path out
#--action-offline-mode
+5
View File
@@ -0,0 +1,5 @@
NO_DOCKER_BUILD=true
#https://github.com/nektos/act/issues/329#issuecomment-1905955589
ACTIONS_RUNTIME_URL=http://YOUR_HOST_IP:8082/
ACTIONS_RUNTIME_TOKEN=foo
ACTIONS_CACHE_URL=http://YOUR_HOST_IP:8082/
+3
View File
@@ -0,0 +1,3 @@
DOCKER_PASSWORD=
DOCKER_USERNAME=
GITHUB_TOKEN=
+53
View File
@@ -0,0 +1,53 @@
Testing GH Actions with ACT
Need to have credentials in a [`.secrets` file](https://nektosact.com/usage/index.html#secrets), copy and rename [`.secrets.example`](./secrets.example) to `.secrets`, then fill out blank fields. Required for docker/metadata-action to read...something. Fails with `Parameter token or opts.auth is required` if they are not supplied.
An ENV file can also be made by copy and renaming [`.env.example`](./env.example). Set `NO_DOCKER_BUILD=true` if you only want to test APP_VERSION and docker tags output.
If running a full docker build for multi-runner workflows you will need to create an [artifact server](https://github.com/nektos/act/issues/329#issuecomment-1905955589) for ACT to work:
```shell
docker pull ghcr.io/jefuller/artifact-server:latest
docker run -d --name artifact-server -p 8082:8080 --add-host host.docker.internal:host-gateway -e AUTH_KEY=foo ghcr.io/jefuller/artifact-server:latest
```
Run the following **from this directory** to make use of `.actrc` and proper working directory.
### Test Branch Test Suite
```shell
act -W '.github/act/testSuite.yml' -e '.github/act/actBranchEvent.json'
```
### Test Branch Push
```shell
act -W '.github/act/actTest.yml' -e '.github/act/actBranchEvent.json'
```
### Test Tag (Release) Push
```shell
act -W '.github/act/actTest.yml' -e '.github/act/actTagEvent.json'
```
### Test Tag (Pre-Release) Push
```shell
act -W '.github/act/actTest.yml' -e '.github/act/actTagPreEvent.json'
```
### Test Multi-Runner Push (WIP)
```shell
act -W '.github/act/multiRunnerTest.yml' -e '.github/act/actBranchEvent.json'
```
### Test Multi-Runner Bake Push (WIP)
```shell
act -W '.github/act/multiRunnerBakeTest.yml' -e '.github/act/actBranchEvent.json'
```
+4
View File
@@ -0,0 +1,4 @@
{
"ref": "refs/heads/master",
"sha": "14d5eb3313110f2cfd1aa8fe9c229c21901c1eeb"
}
+164
View File
@@ -0,0 +1,164 @@
{
"action": "synchronize",
"number": 210,
"pull_request": {
"url": "https://api.github.com/repos/FoxxMD/multi-scrobbler/pulls/210",
"id": 2123337348,
"node_id": "PR_kwDOEpP-x85-j46E",
"number": 210,
"state": "open",
"locked": false,
"merge_commit_sha": "349bea1f4580b9e61aa22446eae73f8b1225bbb7",
"labels": [
{
"id": 2494982061,
"node_id": "MDU6TGFiZWwyNDk0OTgyMDYx",
"url": "https://api.github.com/repos/FoxxMD/multi-scrobbler/labels/bug",
"name": "bug",
"color": "d73a4a",
"default": true,
"description": "Something isn't working"
},
{
"id": 7131135993,
"node_id": "LA_kwDOEpP-x88AAAABqQx_-Q",
"url": "https://api.github.com/repos/FoxxMD/multi-scrobbler/labels/safe%20to%20test",
"name": "safe to test",
"color": "D4C5F9",
"default": false,
"description": "trusted to build image"
}
],
"milestone": null,
"draft": false,
"head": {
"label": "FoxxMD:GH-209/jellyfinCleanup",
"ref": "GH-209/jellyfinCleanup",
"sha": "e251da9b8eecfcb3aba8756393e5075d0092794b",
"user": {
"login": "FoxxMD",
"id": 4663766,
"node_id": "MDQ6VXNlcjQ2NjM3NjY=",
"avatar_url": "https://avatars.githubusercontent.com/u/4663766?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/FoxxMD",
"html_url": "https://github.com/FoxxMD",
"followers_url": "https://api.github.com/users/FoxxMD/followers",
"following_url": "https://api.github.com/users/FoxxMD/following{/other_user}",
"gists_url": "https://api.github.com/users/FoxxMD/gists{/gist_id}",
"starred_url": "https://api.github.com/users/FoxxMD/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/FoxxMD/subscriptions",
"organizations_url": "https://api.github.com/users/FoxxMD/orgs",
"repos_url": "https://api.github.com/users/FoxxMD/repos",
"events_url": "https://api.github.com/users/FoxxMD/events{/privacy}",
"received_events_url": "https://api.github.com/users/FoxxMD/received_events",
"type": "User",
"site_admin": false
},
"repo": {
"id": 311688903,
"node_id": "MDEwOlJlcG9zaXRvcnkzMTE2ODg5MDM=",
"name": "multi-scrobbler",
"full_name": "FoxxMD/multi-scrobbler",
"private": false,
"owner": {
"login": "FoxxMD",
"id": 4663766,
"node_id": "MDQ6VXNlcjQ2NjM3NjY=",
"avatar_url": "https://avatars.githubusercontent.com/u/4663766?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/FoxxMD",
"html_url": "https://github.com/FoxxMD",
"followers_url": "https://api.github.com/users/FoxxMD/followers",
"following_url": "https://api.github.com/users/FoxxMD/following{/other_user}",
"gists_url": "https://api.github.com/users/FoxxMD/gists{/gist_id}",
"starred_url": "https://api.github.com/users/FoxxMD/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/FoxxMD/subscriptions",
"organizations_url": "https://api.github.com/users/FoxxMD/orgs",
"repos_url": "https://api.github.com/users/FoxxMD/repos",
"events_url": "https://api.github.com/users/FoxxMD/events{/privacy}",
"received_events_url": "https://api.github.com/users/FoxxMD/received_events",
"type": "User",
"site_admin": false
},
"html_url": "https://github.com/FoxxMD/multi-scrobbler",
"description": "Scrobble plays from multiple sources to multiple clients",
"fork": false,
"created_at": "2020-11-10T14:45:38Z",
"updated_at": "2024-10-14T09:21:37Z",
"pushed_at": "2024-10-14T15:43:00Z",
"allow_forking": true,
"is_template": false,
"web_commit_signoff_required": false,
"visibility": "public",
"forks": 16,
"open_issues": 19,
"watchers": 369,
"default_branch": "master",
"allow_squash_merge": true,
"allow_merge_commit": true,
"allow_rebase_merge": true,
"allow_auto_merge": false,
"delete_branch_on_merge": false,
"allow_update_branch": false,
"use_squash_pr_title_as_default": false,
"squash_merge_commit_message": "COMMIT_MESSAGES",
"squash_merge_commit_title": "COMMIT_OR_PR_TITLE",
"merge_commit_message": "PR_TITLE",
"merge_commit_title": "MERGE_MESSAGE"
}
},
"base": {
"label": "FoxxMD:master",
"ref": "master",
"sha": "9c21db8172b6beff9d321e831e9353bf17e92a37",
"repo": {
"id": 311688903,
"node_id": "MDEwOlJlcG9zaXRvcnkzMTE2ODg5MDM=",
"name": "multi-scrobbler",
"full_name": "FoxxMD/multi-scrobbler",
"private": false,
"fork": false,
"created_at": "2020-11-10T14:45:38Z",
"updated_at": "2024-10-14T09:21:37Z",
"pushed_at": "2024-10-14T15:43:00Z",
"allow_forking": true,
"is_template": false,
"web_commit_signoff_required": false,
"visibility": "public",
"forks": 16,
"open_issues": 19,
"watchers": 369,
"default_branch": "master",
"allow_squash_merge": true,
"allow_merge_commit": true,
"allow_rebase_merge": true,
"allow_auto_merge": false,
"delete_branch_on_merge": false,
"allow_update_branch": false,
"use_squash_pr_title_as_default": false,
"squash_merge_commit_message": "COMMIT_MESSAGES",
"squash_merge_commit_title": "COMMIT_OR_PR_TITLE",
"merge_commit_message": "PR_TITLE",
"merge_commit_title": "MERGE_MESSAGE"
}
},
"author_association": "OWNER",
"auto_merge": null,
"active_lock_reason": null,
"merged": false,
"mergeable": null,
"rebaseable": null,
"mergeable_state": "unknown",
"merged_by": null,
"comments": 0,
"review_comments": 0,
"maintainer_can_modify": false,
"commits": 5,
"additions": 84,
"deletions": 28,
"changed_files": 5
},
"before": "335ee0cd601f5a82e36643c812507b2b53e7daf0",
"after": "e251da9b8eecfcb3aba8756393e5075d0092794b"
}
+8
View File
@@ -0,0 +1,8 @@
{
"ref": "refs/tags/0.8.4",
"ref_name": "0.8.4",
"before": "0000000000000000000000000000000000000000",
"after": "005dae76ab51799d3d55112738e301cb1af0dafd",
"sha": "005dae76ab51799d3d55112738e301cb1af0dafd",
"base_ref": "refs/heads/master"
}
+8
View File
@@ -0,0 +1,8 @@
{
"ref": "refs/tags/0.8.5-gh-runner",
"ref_name": "0.8.5-gh-runner",
"before": "0000000000000000000000000000000000000000",
"after": "005dae76ab51799d3d55112738e301cb1af0dafd",
"sha": "005dae76ab51799d3d55112738e301cb1af0dafd",
"base_ref": "refs/heads/master"
}
+105
View File
@@ -0,0 +1,105 @@
name: Publish Docker image to Dockerhub
on:
workflow_dispatch:
push:
branches:
- 'master'
tags:
- '*.*.*'
# don't trigger if just updating docs
paths-ignore:
- 'README.md'
- '.github/**'
- 'flatpak/**'
# use release instead of tags once version is correctly parsed
# https://github.com/docker/metadata-action/issues/422
# https://github.com/docker/metadata-action/issues/240
# release:
# types: [ published ]
jobs:
push_to_registry:
name: Build and push container images
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v4
- name: Debug
run: echo $JSON
env:
JSON: ${{ toJSON(github) }}
- name: Set git state to ENV
id: vars
# https://dev.to/hectorleiva/github-actions-and-creating-a-short-sha-hash-8b7
# short sha available under env.COMMIT_SHORT_SHA
run: |
calculatedSha=$(git rev-parse --short HEAD)
branchName=$(git rev-parse --abbrev-ref HEAD)
echo "COMMIT_SHORT_SHA=$calculatedSha" >> $GITHUB_ENV
echo "COMMIT_BRANCH=$branchName" >> $GITHUB_ENV
- name: Check App Version
env:
# use release instead of tags once version is correctly parsed
#APP_VERSION: ${{ github.event.release.tag_name }}
# https://github.com/actions/runner/issues/409#issuecomment-752775072
# https://stackoverflow.com/a/69919067/1469797
APP_VERSION: ${{ contains(github.ref, 'refs/tags/') && github.ref_name || format('{0}-{1}', env.COMMIT_BRANCH, env.COMMIT_SHORT_SHA ) }}
run: |
echo $APP_VERSION
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
# generate Docker tags based on the following events/attributes
# https://github.com/docker/metadata-action/issues/247#issuecomment-1511259674 for NOT is default branch, eventually
tags: |
type=edge
# maybe re-enable branch-named tags in the futures
#type=ref,event=branch,enable=${{ !endsWith(github.ref, 'master') }}
# tag non-prelease as latest -- has a higher priority than regular tag so it shows first in registries
type=match,pattern=\d.\d.\d$,priority=901
# tag all semver (include pre-release)
type=semver,pattern={{version}}
# flavor: |
# latest=false
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
id: qemu
if: ${{ !env.NO_DOCKER_BUILD }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
if: ${{ steps.qemu.outcome == 'success' }}
continue-on-error: true
- name: Build and push Docker image
if: ${{ steps.qemu.outcome == 'success' }}
env:
# use release instead of tags once version is correctly parsed
#APP_VERSION: ${{ github.event.release.tag_name }}
# https://github.com/actions/runner/issues/409#issuecomment-752775072
# https://stackoverflow.com/a/69919067/1469797
APP_VERSION: ${{ contains(github.ref, 'refs/tags/') && github.ref_name || format('{0}-{1}', env.COMMIT_BRANCH, env.COMMIT_SHORT_SHA ) }}
uses: docker/build-push-action@v5
with:
context: .
# https://github.com/docker/build-push-action/issues/1026#issue-2041857786
build-args: |
APP_BUILD_VERSION=${{env.APP_VERSION}}
push: false
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64
+30
View File
@@ -0,0 +1,30 @@
variable "DEFAULT_TAG" {
default = "multi-scrobbler:local"
}
// Special target: https://github.com/docker/metadata-action#bake-definition
target "docker-metadata-action" {
tags = ["${DEFAULT_TAG}"]
}
// Default target if none specified
group "default" {
targets = ["image-local"]
}
target "image" {
inherits = ["docker-metadata-action"]
}
target "image-local" {
inherits = ["image"]
output = ["type=docker"]
}
target "image-all" {
inherits = ["image"]
platforms = [
"linux/amd64",
"linux/arm64"
]
}
+233
View File
@@ -0,0 +1,233 @@
# Based on https://docs.docker.com/build/ci/github-actions/multi-platform/#with-bake (https://github.com/docker/build-push-action/issues/671#issuecomment-1609106171)
# with example from here https://github.com/crazy-max/docker-linguist/blob/master/.github/workflows/build.yml that uses caching
#
# may have to use this to enable pushing to multiple registries? https://github.com/docker/build-push-action/discussions/1067#discussioncomment-8677682
name: build
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches:
- 'master'
tags:
- '*.*.*'
paths-ignore:
- 'README.md'
- '.github/**'
- 'flatpak/**'
env:
DOCKERHUB_SLUG: foxxmd/multi-scrobbler
GHCR_SLUG: ghcr.io/foxxmd/multi-scrobbler
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.platforms.outputs.matrix }}
steps:
-
name: Checkout
uses: actions/checkout@v4
- name: Set short git commit SHA
id: appvars
# https://dev.to/hectorleiva/github-actions-and-creating-a-short-sha-hash-8b7
# short sha available under env.COMMIT_SHORT_SHA
run: |
calculatedSha=$(git rev-parse --short HEAD)
branchName=$(git rev-parse --abbrev-ref HEAD)
echo "COMMIT_SHORT_SHA=$calculatedSha" >> $GITHUB_ENV
echo "COMMIT_BRANCH=$branchName" >> $GITHUB_ENV
- name: Get App Version
id: appversion
env:
# use release instead of tags once version is correctly parsed
#APP_VERSION: ${{ github.event.release.tag_name }}
# https://github.com/actions/runner/issues/409#issuecomment-752775072
# https://stackoverflow.com/a/69919067/1469797
APP_VERSION: ${{ contains(github.ref, 'refs/tags/') && github.ref_name || format('{0}-{1}', env.COMMIT_BRANCH, env.COMMIT_SHORT_SHA ) }}
run: |
echo appversion=$APP_VERSION >>${GITHUB_OUTPUT}
-
name: Create matrix
id: platforms
run: |
echo "matrix=$(docker buildx bake --file ./docker-bake.hcl image-all --print | jq -cr '.target."image-all".platforms')" >>${GITHUB_OUTPUT}
-
name: Show matrix
run: |
echo ${{ steps.platforms.outputs.matrix }}
-
name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.DOCKERHUB_SLUG }}
${{ env.GHCR_SLUG }}
tags: |
type=edge
# maybe re-enable branch-named tags in the futures
#type=ref,event=branch,enable=${{ !endsWith(github.ref, 'master') }}
# tag non-prelease as latest -- has a higher priority than regular tag so it shows first in registries
type=match,pattern=\d.\d.\d$,priority=901
# tag all semver (include pre-release)
type=semver,pattern={{version}}
labels: |
org.opencontainers.image.title=Multi-Scrobbler
org.opencontainers.image.description=Scrobble from many sources to many clients
org.opencontainers.image.vendor=FoxxMD
-
name: Rename meta bake definition file
run: |
mv "${{ steps.meta.outputs.bake-file }}" "/tmp/bake-meta.json"
-
name: Upload meta bake definition
uses: actions/upload-artifact@v3
with:
name: bake-meta
path: /tmp/bake-meta.json
if-no-files-found: error
retention-days: 1
build:
runs-on: ubuntu-latest
needs:
- prepare
strategy:
fail-fast: false
matrix:
platform: ${{ fromJson(needs.prepare.outputs.matrix) }}
steps:
-
name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
-
name: Checkout
uses: actions/checkout@v4
-
name: Test App Version Transfer
run: |
echo "App Version in Build ${{ needs.prepare.appversion.outputs.appversion }}"
-
name: Download meta bake definition
uses: actions/download-artifact@v3
with:
name: bake-meta
path: /tmp
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
buildkitd-flags: "--debug"
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
-
name: Login to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
-
name: Build
id: bake
uses: docker/bake-action@v5
with:
files: |
./.github/act/docker-bake.hcl
/tmp/bake-meta.json
targets: image
set: |
*.args.APP_BUILD_VERSION=${{ needs.prepare.appversion.outputs.appversion }}
*.tags=
*.platform=${{ matrix.platform }}
*.cache-from=type=gha,scope=build-${{ env.PLATFORM_PAIR }}
*.cache-to=type=gha,scope=build-${{ env.PLATFORM_PAIR }}
*.output=type=image,"name=${{ env.DOCKERHUB_SLUG }},${{ env.GHCR_SLUG }}",push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }}
-
name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ fromJSON(steps.bake.outputs.metadata).image['containerimage.digest'] }}"
touch "/tmp/digests/${digest#sha256:}"
-
name: Upload digest
uses: actions/upload-artifact@v3
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
merge:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
needs:
- build
steps:
-
name: Download meta bake definition
uses: actions/download-artifact@v3
with:
name: bake-meta
path: /tmp
-
name: Download digests
uses: actions/download-artifact@v3
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
-
name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
-
name: Create manifest list and push
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.target."docker-metadata-action".tags | map(select(startswith("${{ env.DOCKERHUB_SLUG }}")) | "-t " + .) | join(" ")' /tmp/bake-meta.json) \
$(printf '${{ env.DOCKERHUB_SLUG }}@sha256:%s ' *)
docker buildx imagetools create $(jq -cr '.target."docker-metadata-action".tags | map(select(startswith("${{ env.GHCR_SLUG }}")) | "-t " + .) | join(" ")' /tmp/bake-meta.json) \
$(printf '${{ env.GHCR_SLUG }}@sha256:%s ' *)
-
name: Inspect image
run: |
tag=$(jq -r '.target."docker-metadata-action".args.DOCKER_META_VERSION' /tmp/bake-meta.json)
docker buildx imagetools inspect ${{ env.DOCKERHUB_SLUG }}:${tag}
docker buildx imagetools inspect ${{ env.GHCR_SLUG }}:${tag}
+198
View File
@@ -0,0 +1,198 @@
# Based on https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners
name: build
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches:
- 'master'
tags:
- '*.*.*'
paths-ignore:
- 'README.md'
- '.github/**'
- 'flatpak/**'
env:
DOCKERHUB_SLUG: foxxmd/multi-scrobbler
GHCR_SLUG: ghcr.io/foxxmd/multi-scrobbler
jobs:
build:
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
strategy:
fail-fast: false
matrix:
platform:
- linux/amd64
- linux/arm64
steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Check out the repo
uses: actions/checkout@v5
- name: Set short git commit SHA
id: appvars
# https://dev.to/hectorleiva/github-actions-and-creating-a-short-sha-hash-8b7
# short sha available under env.COMMIT_SHORT_SHA
run: |
calculatedSha=$(git rev-parse --short HEAD)
branchName=$(git rev-parse --abbrev-ref HEAD)
echo "COMMIT_SHORT_SHA=$calculatedSha" >> $GITHUB_ENV
echo "COMMIT_BRANCH=$branchName" >> $GITHUB_ENV
- name: Get App Version
id: appversion
env:
# use release instead of tags once version is correctly parsed
#APP_VERSION: ${{ github.event.release.tag_name }}
# https://github.com/actions/runner/issues/409#issuecomment-752775072
# https://stackoverflow.com/a/69919067/1469797
APP_VERSION: ${{ contains(github.ref, 'refs/tags/') && github.ref_name || format('{0}-{1}', env.COMMIT_BRANCH, env.COMMIT_SHORT_SHA ) }}
run: |
echo appversion=$APP_VERSION >>${GITHUB_OUTPUT}
# - name: Extract metadata (tags, labels) for Docker
# id: meta
# uses: docker/metadata-action@v5
# with:
# images: |
# ${{ env.DOCKERHUB_SLUG }}
# ${{ env.GHCR_SLUG }}
# labels: |
# org.opencontainers.image.title=Multi-Scrobbler
# org.opencontainers.image.description=Scrobble from many sources to many clients
# org.opencontainers.image.vendor=FoxxMD
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
# https://github.com/docker/build-push-action/issues/671#issuecomment-1619353328
# for caching
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
build-args: |
APP_BUILD_VERSION=${{steps.appversion.outputs.appversion}}
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
#tags: ${{ steps.meta.outputs.tags }}
outputs: type=image,"name=${{ env.DOCKERHUB_SLUG }},${{ env.GHCR_SLUG }}",push-by-digest=true,name-canonical=true,push=true
#cache-from: type=gha,scope=build-${{ env.PLATFORM_PAIR }}
#cache-to: type=gha,scope=build-${{ env.PLATFORM_PAIR }}
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
merge:
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
needs:
- build
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.DOCKERHUB_SLUG }}
${{ env.GHCR_SLUG }}
# generate Docker tags based on the following events/attributes
# https://github.com/docker/metadata-action/issues/247#issuecomment-1511259674 for NOT is default branch, eventually
tags: |
type=edge
# maybe re-enable branch-named tags in the futures
type=ref,event=branch,enable=${{ !endsWith(github.ref, 'master') }}
# tag non-prelease as latest -- has a higher priority than regular tag so it shows first in registries
type=match,pattern=\d.\d.\d$,priority=901
# tag all semver (include pre-release)
type=semver,pattern={{version}}
# flavor: |
# latest=false
labels: |
org.opencontainers.image.title=Multi-Scrobbler
org.opencontainers.image.description=Scrobble from many sources to many clients
org.opencontainers.image.vendor=FoxxMD
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.DOCKERHUB_SLUG }}@sha256:%s ' *)
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.GHCR_SLUG }}@sha256:%s ' *)
- name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.DOCKERHUB_SLUG }}:${{ steps.meta.outputs.version }}
docker buildx imagetools inspect ${{ env.GHCR_SLUG }}:${{ steps.meta.outputs.version }}
+10
View File
@@ -0,0 +1,10 @@
name: PR Workflow
on:
push:
branches:
- 'master'
jobs:
test:
uses: ./.github/workflows/testAndSanity.yml
+19
View File
@@ -0,0 +1,19 @@
## Checklist before requesting a review
- [ ] I have read the [contributing guidelines.](../CONTRIBUTING.md)
## Type of change
Please delete options that are not relevant.
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
## Describe your changes
## Issue number and link, if applicable
+16
View File
@@ -0,0 +1,16 @@
name: Test Dependabot PR
on:
pull_request_target:
types:
- synchronize
- reopened
- opened
branches:
- 'master'
jobs:
test:
name: Tests / Build / Sanity Run
if: github.actor == 'dependabot[bot]'
uses: ./.github/workflows/testAndSanity.yml
+52
View File
@@ -0,0 +1,52 @@
name: Deploy to GitHub Pages
on:
workflow_dispatch:
release:
types: [released]
# Review gh actions docs if you want to further define triggers, paths, etc
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on
jobs:
deploy:
name: Deploy to GitHub Pages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Install app dev dependencies
run: npm ci
- name: Generate schema files
run: npm run schema
- name: Install docusaurus dependencies
run: npm ci
working-directory: ./docsite
- name: Build website
env:
ANALYTICS: ${{ vars.ANALYTICS }}
ANALYTICS_DOMAIN: ${{ vars.ANALYTICS_DOMAIN }}
GSITEVERIFICATION: ${{ vars.GSITEVERIFICATION }}
DOCS_BASE: '/multi-scrobbler'
run: npm run build
working-directory: ./docsite
# 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@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
# Build output to publish to the `gh-pages` branch:
publish_dir: ./docsite/build
# The following lines assign commit authorship to the official
# GH-Actions bot for deploys to `gh-pages` branch:
# https://github.com/actions/checkout/issues/13#issuecomment-724415212
# The GH actions bot is used by default if you didn't specify the two fields.
# 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
@@ -0,0 +1,21 @@
name: Delete Untagged Packages
on:
schedule:
- cron: '30 1 * * 0'
workflow_run:
workflows: ["Publish Docker image to Dockerhub"]
types:
- completed
workflow_dispatch:
jobs:
delete-untagged:
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- uses: dataaxiom/ghcr-cleanup-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
# maybe set up for PR close later
# https://github.com/dataaxiom/ghcr-cleanup-action?tab=readme-ov-file#delete-image-when-pull-request-is-closed
+222
View File
@@ -0,0 +1,222 @@
name: PR Workflow
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.head.sha }}
cancel-in-progress: true
on:
pull_request_target:
types:
- labeled
- synchronize
- reopened
- opened
branches:
- 'master'
jobs:
test:
if: contains(github.event.pull_request.labels.*.name, 'safe to test')
uses: ./.github/workflows/testAndSanity.yml
with:
ref: ${{ github.event.pull_request.head.sha }}
build-snapshot:
name: Build OCI snapshot
needs: test
if: contains(github.event.pull_request.labels.*.name, 'safe to test')
runs-on: ${{ matrix.os }}
permissions:
packages: write
contents: read
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
arch: amd64
platform: linux/amd64
- os: ubuntu-24.04-arm
arch: arm64
platform: linux/arm64
steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
# list all registries to push to and join all non-empty with comma
# https://unix.stackexchange.com/a/693165/116849
# https://stackoverflow.com/a/9429887/1469797
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
# do not use here!
# instead we can specify repo#sha in build-push-action so we only deal with foreign SHA for actually building image
# - uses: actions/checkout@v4
# with:
# ref: ${{ github.event.pull_request.head.sha }}
- name: Set short git commit SHA
run: |
short=$(echo "${{ github.event.pull_request.head.sha }}" | head -c7)
echo "COMMIT_SHORT_SHA=$short" >> $GITHUB_ENV
- name: Get App Version
id: appversion
env:
APP_VERSION: ${{ format('pr{0}-{1}', github.event.number, env.COMMIT_SHORT_SHA ) }}
run: |
echo appversion=$APP_VERSION >>${GITHUB_OUTPUT}
- name: Login to Docker Hub
if: ${{ vars.DOCKERHUB_SLUG != '' }}
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
if: ${{ vars.GHCR_SLUG != '' }}
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
# metadata extract for docker labels/image names is done in merge job
- name: Set up Buildx
uses: docker/setup-buildx-action@v3
# https://github.com/docker/build-push-action/issues/671#issuecomment-1619353328
# for caching
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
# specify foreign repo#sha from PR that is used to build image
context: ${{github.event.pull_request.head.repo.clone_url}}#${{github.event.pull_request.head.sha}}
build-args: |
APP_BUILD_VERSION=${{steps.appversion.outputs.appversion}}
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,"name=${{ env.REGISTRIES_JOINED }}",push-by-digest=true,name-canonical=true,push=true
#cache-from: type=gha,scope=build-${{ env.PLATFORM_PAIR }}
#cache-to: type=gha,scope=build-${{ env.PLATFORM_PAIR }}
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
release-snapshot:
name: Merge OCI Images and Push
if: ${{ vars.DOCKERHUB_SLUG != '' || vars.GHCR_SLUG != '' }}
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
needs:
- build-snapshot
- test
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Login to Docker Hub
if: ${{ vars.DOCKERHUB_SLUG != '' }}
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
if: ${{ vars.GHCR_SLUG != '' }}
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ vars.DOCKERHUB_SLUG }}
${{ vars.GHCR_SLUG }}
# generate Docker tags based on the following events/attributes
# https://github.com/docker/metadata-action/issues/247#issuecomment-1511259674 for NOT is default branch, eventually
tags: |
type=ref,event=pr
flavor: |
latest=false
labels: |
org.opencontainers.image.title=Multi-Scrobbler
org.opencontainers.image.description=Scrobble from many sources to many clients
org.opencontainers.image.vendor=FoxxMD
- name: Create manifest list and push dockerhub
if: ${{ vars.DOCKERHUB_SLUG != '' }}
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ vars.DOCKERHUB_SLUG }}@sha256:%s ' *)
- name: Create manifest list and push gchr
if: ${{ vars.GHCR_SLUG != '' }}
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ vars.GHCR_SLUG }}@sha256:%s ' *)
- name: Inspect image dockerhub
if: ${{ vars.DOCKERHUB_SLUG != '' }}
run: |
docker buildx imagetools inspect ${{ vars.DOCKERHUB_SLUG }}:${{ steps.meta.outputs.version }}
- name: Inspect image ghcr
if: ${{ vars.GHCR_SLUG != '' }}
run: |
docker buildx imagetools inspect ${{ vars.GHCR_SLUG }}:${{ steps.meta.outputs.version }}
combine-and-comment:
name: Leave comment
runs-on: ubuntu-latest
needs: release-snapshot
if: contains(github.event.pull_request.labels.*.name, 'safe to test')
steps:
- name: Create comment
uses: marocchino/sticky-pull-request-comment@v2
with:
recreate: true
header: "pr-release"
message: |
#### :package: A new release has been made for this pull request.
To play around with this PR, pull an image:
* `foxxmd/multi-scrobbler:pr-${{ github.event.number }}`
Images are available for x86_64 and ARM64.
> Latest commit: ${{ github.event.pull_request.head.sha }}
+228
View File
@@ -0,0 +1,228 @@
# Based on https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners
name: Publish Docker image to Dockerhub
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
workflow_dispatch:
push:
branches:
- 'master'
tags:
- '*.*.*'
# don't trigger if just updating docs
paths-ignore:
- 'README.md'
- '.github/**'
- 'flatpak/**'
# use release instead of tags once version is correctly parsed
# https://github.com/docker/metadata-action/issues/422
# https://github.com/docker/metadata-action/issues/240
# release:
# types: [ published ]
# define in GH Repository -> Actions -> Variables (or act .variables) to enable pushing to registries
# -- will only push to registries that are defined
# EX
# DOCKERHUB_SLUG=foxxmd/multi-scrobbler
# GHCR_SLUG=ghcr.io/foxxmd/multi-scrobbler
jobs:
test:
if: github.event_name != 'pull_request'
uses: ./.github/workflows/testAndSanity.yml
build:
name: Build OCI Images
if: ${{ github.event_name != 'pull_request' && (vars.DOCKERHUB_SLUG != '' || vars.GHCR_SLUG != '') }}
needs: test
runs-on: ${{ matrix.os }}
permissions:
packages: write
contents: read
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
arch: amd64
platform: linux/amd64
- os: ubuntu-24.04-arm
arch: arm64
platform: linux/arm64
steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
# list all registries to push to and join all non-empty with comma
# https://unix.stackexchange.com/a/693165/116849
# https://stackoverflow.com/a/9429887/1469797
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
- name: Check out the repo
uses: actions/checkout@v4
- name: Set short git commit SHA
id: appvars
# https://dev.to/hectorleiva/github-actions-and-creating-a-short-sha-hash-8b7
# short sha available under env.COMMIT_SHORT_SHA
run: |
calculatedSha=$(git rev-parse --short HEAD)
branchName=$(git rev-parse --abbrev-ref HEAD)
echo "COMMIT_SHORT_SHA=$calculatedSha" >> $GITHUB_ENV
echo "COMMIT_BRANCH=$branchName" >> $GITHUB_ENV
- name: Get App Version
id: appversion
env:
# use release instead of tags once version is correctly parsed
#APP_VERSION: ${{ github.event.release.tag_name }}
# https://github.com/actions/runner/issues/409#issuecomment-752775072
# https://stackoverflow.com/a/69919067/1469797
APP_VERSION: ${{ contains(github.ref, 'refs/tags/') && github.ref_name || format('{0}-{1}', env.COMMIT_BRANCH, env.COMMIT_SHORT_SHA ) }}
run: |
echo appversion=$APP_VERSION >>${GITHUB_OUTPUT}
- name: Login to Docker Hub
if: ${{ github.event_name != 'pull_request' && vars.DOCKERHUB_SLUG != '' }}
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
if: ${{ github.event_name != 'pull_request' && vars.GHCR_SLUG != '' }}
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
# metadata extract for docker labels/image names is done in merge job
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# https://github.com/docker/build-push-action/issues/671#issuecomment-1619353328
# for caching
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
build-args: |
APP_BUILD_VERSION=${{steps.appversion.outputs.appversion}}
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,"name=${{ env.REGISTRIES_JOINED }}",push-by-digest=true,name-canonical=true,push=true
#cache-from: type=gha,scope=build-${{ env.PLATFORM_PAIR }}
#cache-to: type=gha,scope=build-${{ env.PLATFORM_PAIR }}
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
merge:
name: Merge OCI Images and Push
if: ${{ github.event_name != 'pull_request' && (vars.DOCKERHUB_SLUG != '' || vars.GHCR_SLUG != '') }}
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
needs:
- build
- test
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Login to Docker Hub
if: ${{ github.event_name != 'pull_request' && vars.DOCKERHUB_SLUG != '' }}
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
if: ${{ github.event_name != 'pull_request' && vars.GHCR_SLUG != '' }}
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ vars.DOCKERHUB_SLUG }}
${{ vars.GHCR_SLUG }}
# generate Docker tags based on the following events/attributes
# https://github.com/docker/metadata-action/issues/247#issuecomment-1511259674 for NOT is default branch, eventually
tags: |
type=edge
# push with branch name as tag if not master/main
type=ref,event=branch,enable=${{ !endsWith(github.ref, 'master') }}
# tag non-prelease as latest -- has a higher priority than regular tag so it shows first in registries
type=match,pattern=\d.\d.\d$,priority=901
# tag all semver (include pre-release)
type=semver,pattern={{version}}
# flavor: |
# latest=false
labels: |
org.opencontainers.image.title=Multi-Scrobbler
org.opencontainers.image.description=Scrobble from many sources to many clients
org.opencontainers.image.vendor=FoxxMD
- name: Create manifest list and push dockerhub
if: ${{ vars.DOCKERHUB_SLUG != '' }}
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ vars.DOCKERHUB_SLUG }}@sha256:%s ' *)
- name: Create manifest list and push gchr
if: ${{ vars.GHCR_SLUG != '' }}
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ vars.GHCR_SLUG }}@sha256:%s ' *)
- name: Inspect image dockerhub
if: ${{ vars.DOCKERHUB_SLUG != '' }}
run: |
docker buildx imagetools inspect ${{ vars.DOCKERHUB_SLUG }}:${{ steps.meta.outputs.version }}
- name: Inspect image ghcr
if: ${{ vars.GHCR_SLUG != '' }}
run: |
docker buildx imagetools inspect ${{ vars.GHCR_SLUG }}:${{ steps.meta.outputs.version }}
+65
View File
@@ -0,0 +1,65 @@
name: Tests and Sanity Run
on:
workflow_call:
inputs:
node-version:
description: "Node version"
required: false
default: '20.x'
type: string
ref:
description: "Ref to checkout"
required: false
type: string
jobs:
test:
name: Tests / Build / Sanity Run
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: 'npm'
- name: Install concurrently
run: npm install -g concurrently
- name: Install dev dependencies
run: npm run install:parallel
- name: Test Backend
run: npm run test
- name: Build
run: npm run build:parallel
# remove modules that might include dev stuff
# so that in the next step we are sure that prod-only runs work correctly
- name: Install Prod Deps
run: |
rm -rf node_modules && \
rm -rf docsite/node_modules && \
NODE_ENV=production npm ci --omit=dev
# run app for 10 seconds as sanity check to see if it errors for any reason
# easy testcase for missing packages and init errors
- name: Sanity Run
run: |
set +e
export NODE_ENV=production
timeout --preserve-status 10s node node_modules/.bin/tsx src/backend/index.ts
exitcode="$?"
if [[ "$exitcode" -eq 143 ]] || [[ "$exitcode" -eq 137 ]]; then
echo "App stayed up long enough and exited with expected status"
exit 0
else
echo "App exited with unexpected code $exitcode"
exit "$exitcode"
fi
+18 -2
View File
@@ -1,7 +1,7 @@
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
logs
/logs
*.log
npm-debug.log*
yarn-debug.log*
@@ -117,6 +117,22 @@ dist
.yarn/install-state.gz
.pnp.*
*.json
config/*.json
config/yti-*
*.txt
.idea/
src/**/**.js
src/**/**.js.map
*.bak
*.p8
.flatpak-builder
flatpak/generated-sources.json
*.secrets
build
!setupProxy.js
**/generated-sources.*
.github/act/out
tmp-*
+28
View File
@@ -0,0 +1,28 @@
{
"$schema": "https://lnav.org/schemas/format-v1.schema.json",
"error_log": {
"title": "Multi-Scrobbler Log",
"description": "Logs from Multi Scrobbler",
"regex": {
"std": {
"pattern": "^\\[(?<timestamp>\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}(?:\\.\\d{3})?)(?:.*?)\\] (?<level>[A-Z]*) : (?<body>.*)"
}
},
"level-field": "level",
"level": {
"info": "^(?i)(?:info)$",
"debug": "^(?i)(?:debug)$",
"error": "^(?i)(error)$",
"warning": "^(?i)(warn)$",
"trace": "^(?i)verbose$",
"critical": "^(?i)(?:crit|Cr)$",
"fatal": "^(?i)(?:alert|fatal|panic|Al|Em)$"
},
"sample": [
{
"line": "[2024-12-26 01:58:48.261 -0500] DEBUG : [App] [Sources] [Jellyfin - unnamed] Last activity was at 01:58:48-05:00 | Next check in 10.00s | No new tracks discovered",
"level": "debug"
}
]
}
}
+7
View File
@@ -0,0 +1,7 @@
{
"reporter": "dot",
"extension": "ts",
"import": "tsx/esm",
"spec": "./src/backend/tests/**/*.test.ts",
"exit": true
}
+1 -1
View File
@@ -1 +1 @@
"lts/fermium"
v18.19.1
+24
View File
@@ -0,0 +1,24 @@
{
"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"
}
+104
View File
@@ -0,0 +1,104 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "dev",
"type": "node",
"request": "launch",
// Debug app in VSCode
"program": "${workspaceFolder}/src/backend/index.ts",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/tsx",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"skipFiles": [
"<node_internals>/**",
// Ignore all dependencies (optional)
"${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",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"skipFiles": [
"<node_internals>/**",
// Ignore all dependencies (optional)
//"${workspaceFolder}/node_modules/**",
],
"resolveSourceMapLocations": [
"${workspaceFolder}/src/**/*",
"${workspaceFolder}/node_modules/**/*"
]
},
{
"args": [
"-u",
"bdd",
"--timeout",
"999999",
"--reporter",
"spec",
"--colors",
"--config",
"${workspaceRoot}/.mocharc.json",
"--recursive",
"${workspaceFolder}/src/backend/tests/**/*.test.ts"
],
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/tsx",
"internalConsoleOptions": "openOnSessionStart",
"name": "Mocha Tests",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"request": "launch",
"skipFiles": [
"<node_internals>/**",
// Ignore all dependencies (optional)
"${workspaceFolder}/node_modules/**",
],
"type": "node"
},
{
"args": [
"-u",
"bdd",
"--timeout",
"999999",
"--reporter",
"spec",
"--colors",
"--config", "${workspaceRoot}/.mocharc.json",
"${file}"
],
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/tsx",
"internalConsoleOptions": "openOnSessionStart",
"name": "Mocha Test on File",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"request": "launch",
"skipFiles": [
"<node_internals>/**",
// Ignore all dependencies (optional)
"${workspaceFolder}/node_modules/**",
],
"type": "node"
}
]
}
+5
View File
@@ -0,0 +1,5 @@
{
"mochaExplorer.require": "tsx/esm",
"mochaExplorer.timeout": 1200000,
"mochaExplorer.exit": true
}
+37
View File
@@ -0,0 +1,37 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "typescript",
"tsconfig": "src/backend/tsconfig.json",
"problemMatcher": [
"$tsc"
],
"group": {
"kind": "build",
"isDefault": true
},
"label": "tsc: build - src/backend/tsconfig.json"
},
{
"type": "shell",
"label": "compose-up-dev",
"command": "docker-compose -f docker-compose.dev.yml up",
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
},
{
"type": "shell",
"label": "compose-build-dev",
"command": "docker-compose -f docker-compose.dev.yml build",
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
}
]
}
+7
View File
@@ -0,0 +1,7 @@
# Creating a Pull Request
Please follow these guidelines when contributing code to this repository:
* Use [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/#summary) format when creating commits.
* Preferably, please use a [feature branch](https://stackoverflow.com/a/39586780/1469797) instead of committing directly to `master`.
* Ensure that if your code is covered by [an existing test](./src/backend/tests) that you have updated the test accordingly
+99 -18
View File
@@ -1,35 +1,116 @@
FROM node:fermium-alpine3.10
FROM ghcr.io/linuxserver/baseimage-debian:bookworm AS base
ENV TZ=Etc/GMT
ENV NODE_VERSION=20.18.1
# borrowing openssl header removal trick from offical docker-node
# https://github.com/nodejs/docker-node/blob/main/18/bookworm-slim/Dockerfile#L8
RUN \
ARCH= OPENSSL_ARCH= && dpkgArch="$(dpkg --print-architecture)" \
&& case "${dpkgArch##*-}" in \
amd64) ARCH='x64' OPENSSL_ARCH='linux-x86_64';; \
ppc64el) ARCH='ppc64le' OPENSSL_ARCH='linux-ppc64le';; \
s390x) ARCH='s390x' OPENSSL_ARCH='linux*-s390x';; \
arm64) ARCH='arm64' OPENSSL_ARCH='linux-aarch64';; \
armhf) ARCH='armv7l' OPENSSL_ARCH='linux-armv4';; \
i386) ARCH='x86' OPENSSL_ARCH='linux-elf';; \
*) echo "unsupported architecture"; exit 1 ;; \
esac && \
set -ex && \
echo "**** install build packages ****" && \
apt-get update && \
apt-get install --no-install-recommends -y \
#ca-certificates \
xz-utils \
avahi-utils \
curl && \
echo "**** Fetch and install node****" && \
# get node/npm directly from nodejs dist \
# https://github.com/nodejs/docker-node/blob/main/18/bookworm-slim/Dockerfile#L41
curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-$ARCH.tar.xz" && \
tar -xJf "node-v$NODE_VERSION-linux-$ARCH.tar.xz" -C /usr --strip-components=1 --no-same-owner && \
rm "node-v$NODE_VERSION-linux-$ARCH.tar.xz" && \
ln -s /usr/bin/node /usr/bin/nodejs && \
npm update -g npm && \
echo "**** cleanup ****" && \
# https://github.com/nodejs/docker-node/blob/main/18/bookworm-slim/Dockerfile#L49
# Remove unused OpenSSL headers to save ~34MB
# (does not affect arm64 issue below)
find /usr/include/node/openssl/archs -mindepth 1 -maxdepth 1 ! -name "$OPENSSL_ARCH" -exec rm -rf {} \; && \
apt-get purge --auto-remove -y perl xz-utils && \
apt-get autoclean && \
apt-get autoremove && \
rm -rf \
/config/.cache \
/root/cache \
/var/lib/apt/lists/* \
/var/tmp/* \
/tmp/*
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node
RUN npm install -g concurrently
WORKDIR /home/node/app
ARG data_dir=/config
VOLUME $data_dir
ENV CONFIG_DIR=$data_dir
COPY package*.json ./
COPY docker/root /
USER node
WORKDIR /app
RUN npm install --production
FROM base AS build
COPY --chown=node:node . .
COPY --chown=abc:abc package*.json tsconfig.json ./
COPY --chown=abc:abc patches ./patches
COPY --chown=abc:abc docsite/package*.json tsconfig.json ./docsite/
ENV NPM_CONFIG_LOGLEVEL debug
# for debugging, so the build fails faster when timing out (arm64)
#RUN npm config set fetch-retries 1 && \
# npm config set fetch-retry-mintimeout 5000 && \
# npm config set fetch-retry-maxtimeout 5000
ARG config_dir=/home/node/config
RUN mkdir -p $config_dir
VOLUME $config_dir
ENV CONFIG_DIR=$config_dir
# https://www.npmjs.com/package/tls-test
# used to test that the OS supports downloading packages over HTTPS with TLS 1.2 enforced
# -- this always succeeds but a good sanity check
#RUN npm install -g https://tls-test.npmjs.com/tls-test-1.0.0.tgz
# This FAILED for node < 20 when building arm64 but not amd64 (and alpine-based Dockerfile has no issues building arm64)
# see https://github.com/FoxxMD/multi-scrobbler/issues/126
RUN npm run install:parallel \
&& chown -R root:root node_modules \
&& chown -R root:root docsite/node_modules
COPY --chown=abc:abc . /app
# need to set before build so server/client build is optimized and has constants (if needed)
ENV NODE_ENV=production
RUN npm run build:parallel && rm -rf node_modules && rm -rf docsite/node_modules
FROM base AS app
COPY --chown=abc:abc *.json *.js *.ts index.html ./
COPY --chown=abc:abc patches ./patches
COPY --from=build --chown=abc:abc /app/dist /app/dist
COPY --from=build --chown=abc:abc /app/src /app/src
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
# https://stackoverflow.com/a/63640896/1469797
ARG APP_BUILD_VERSION
ENV APP_VERSION=$APP_BUILD_VERSION
RUN npm ci --omit=dev --no-audit \
&& npm cache clean --force \
&& chown -R abc:abc node_modules \
&& rm -rf /root/.cache
ARG log_dir=/home/node/logs
RUN mkdir -p $log_dir
VOLUME $log_dir
ENV LOG_DIR=$log_dir
ARG webPort=9078
ENV PORT=$webPort
EXPOSE $PORT
CMD [ "node", "index.js" ]
+88 -53
View File
@@ -3,85 +3,120 @@
[![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/)
A javascript app to scrobble plays from multiple sources to [Maloja](https://github.com/krateng/maloja), [Last.fm](https://www.last.fm), and other clients (eventually!)
* Supports scrobbling for many sources
* [Spotify](/docs/configuration.md#spotify)
* [Plex](/docs/configuration.md#plex) or [Tautulli](/docs/configuration.md#tautulli)
* [Subsonic-compatible APIs](/docs/configuration.md#subsonic) (like [Airsonic](https://airsonic.github.io/))
* Supports scrobbling to many clients
* [Maloja](/docs/configuration.md#maloja)
* [Last.fm](/docs/configuration.md#lastfm)
<img src="/assets/icon.png" align="right"
alt="multi-scrobbler logo" width="180" height="180">
A javascript app to scrobble music you listened to, to [Maloja](https://github.com/krateng/maloja), [Last.fm](https://www.last.fm), and [ListenBrainz](https://listenbrainz.org)
* Supports scrobbling from many **Sources**
* [Spotify](https://foxxmd.github.io/multi-scrobbler/docs/configuration#spotify)
* [Plex](https://foxxmd.github.io/multi-scrobbler/docs/configuration#plex) or [~~Tautulli~~](https://foxxmd.github.io/multi-scrobbler/docs/configuration#tautulli)
* [Subsonic-compatible APIs](https://foxxmd.github.io/multi-scrobbler/docs/configuration#subsonic) (like [Airsonic](https://airsonic.github.io/) and [Navidrome](https://www.navidrome.org/))
* [Jellyfin](https://foxxmd.github.io/multi-scrobbler/docs/configuration#jellyfin)
* [WebScrobbler](https://foxxmd.github.io/multi-scrobbler/docs/configuration#webscrobbler)
* [Youtube Music](https://foxxmd.github.io/multi-scrobbler/docs/configuration#youtube-music)
* [Last.fm](https://foxxmd.github.io/multi-scrobbler/docs/configuration#lastfm-source)
* [Last.fm (Endpoint)](https://foxxmd.github.io/multi-scrobbler/docs/configuration#lastfm-endpoint)
* [ListenBrainz](https://foxxmd.github.io/multi-scrobbler/docs/configuration#listenbrainz-source)
* [Koito](https://foxxmd.github.io/multi-scrobbler/docs/configuration#listenbrainz-source) using Listenbrainz Source
* [ListenBrainz (Endpoint)](https://foxxmd.github.io/multi-scrobbler/docs/configuration#listenbrainz-endpoint)
* [Deezer](https://foxxmd.github.io/multi-scrobbler/docs/configuration#deezer)
* [MPRIS (Linux Desktop)](https://foxxmd.github.io/multi-scrobbler/docs/configuration#mpris)
* [Mopidy](https://foxxmd.github.io/multi-scrobbler/docs/configuration#mopidy)
* [JRiver](https://foxxmd.github.io/multi-scrobbler/docs/configuration#jriver)
* [Kodi](https://foxxmd.github.io/multi-scrobbler/docs/configuration#kodi)
* [Google Cast (Chromecast)](https://foxxmd.github.io/multi-scrobbler/docs/configuration#google-cast-chromecast)
* [Musikcube](https://foxxmd.github.io/multi-scrobbler/docs/configuration#muikcube)
* [MPD (Music Player Daemon)](https://foxxmd.github.io/multi-scrobbler/docs/configuration#mpd-music-player-daemon)
* [VLC](https://foxxmd.github.io/multi-scrobbler/docs/configuration#vlc)
* [Azuracast](https://foxxmd.github.io/multi-scrobbler/docs/configuration#azuracast)
* [Yamaha MusicCast](https://foxxmd.github.io/multi-scrobbler/docs/configuration#yamaha-musiccast)
* [Icecast](https://foxxmd.github.io/multi-scrobbler/docs/configuration#icecast)
* Supports scrobbling to many **Clients**
* [Maloja](https://foxxmd.github.io/multi-scrobbler/docs/configuration#maloja)
* [Last.fm](https://foxxmd.github.io/multi-scrobbler/docs/configuration#lastfm)
* [ListenBrainz](https://foxxmd.github.io/multi-scrobbler/docs/configuration#listenbrainz)
* [Koito](https://foxxmd.github.io/multi-scrobbler/docs/configuration#listenbrainz) using Listenbrainz Client
* Monitor status of Sources and Clients using [webhooks (Gotify, Ntfy, Apprise)](https://foxxmd.github.io/multi-scrobbler/docs/configuration#webhook-configurations) or [healthcheck endpoint](https://foxxmd.github.io/multi-scrobbler/docs/configuration#health-endpoint)
* Supports configuring for single or multiple users (scrobbling for your friends and family!)
* Web server interface for stats, basic control, and detailed logs
* Graceful network and client failure handling (queued scrobbles that auto-retry)
* Smart handling of credentials (persistent, authorization through app)
* Easy configuration through ENVs or JSON
* Built for Docker and unattended use!
* Modify data before scrobbling with [regular expression or search patterns](https://foxxmd.github.io/multi-scrobbler/docs/configuration/transforms)
* Install using [Docker images for x86/ARM](https://foxxmd.github.io/multi-scrobbler/docs/installation#docker) or [locally with NodeJS](https://foxxmd.github.io/multi-scrobbler/docs/installation#nodejs)
[**Quick Start Guide**](https://foxxmd.github.io/multi-scrobbler/docs/quickstart)
<img src="/assets/status-ui.png" width="800">
**Why should I use this over a browser extension and/or mobile app scrobbler?**
* **Platform independent** -- Because multi-scrobbler communicates directly with service APIs it will scrobble everything you play regardless of where you play it. No more need for apps on every platform you use!
* **Open-source** -- Get peace of mind knowing exactly how your personal data is being handled.
* **Consolidate play sources** -- Scrobble from many sources to one client with ease and without duplicating tracks.
* **Track your activity regardless of where you listen** -- Scrobble from many Sources to one Client with ease and without duplicating tracks.
* **Manage scrobbling for others** -- Scrobble for your friends and family without any setup on their part. Easily silo sources to specific clients to keep plays separate.
**But I already scrobble my music to Last.fm/ListenBrainz, is multi-scrobbler for me?**
Yes! You can use [Last.fm as a **Source**](https://foxxmd.github.io/multi-scrobbler/docs/configuration#lastfm-source) or [Listenbrainz as a **Source**](https://foxxmd.github.io/multi-scrobbler/docs/configuration#listenbrainz-source) to forward scrobbles from your profile to any other Client! That way you can keep your current scrobble setup as-is but still get the benefit of capturing your data to a self-hosted location.
## How Does multi-scrobbler (MS) Work?
You set up configurations for one or more **Sources** and one or more **Clients**. MS monitors all of your configured **Sources**. When new tracks are played by a Source it grabs that information and then sends it (scrobbles it) to all **Clients** that Source is configured to scrobble to.
### Source
A **Source** is a data source that contains information about tracks you are playing like a music player or platform. Examples are **Spotify, Jellyfin, Plex, Youtube Music, Airsonic**, etc...
Source configurations consist of:
* A friendly name.
* Any data needed to communicate or authenticate with the Source.
* An optional list of Client names that the Source should scrobble to. If omitted the Source also scrobbles to all configured Clients.
### Client
A **Client** is an application that stores the historical information about what songs you have played (scrobbles). Examples are **Maloja, Last.fm, Listenbrainz**...
Client configurations consist of:
* A friendly name.
* Any data needed to communicate or authenticate with the Client.
## Quick Start
[See the **Quick Start Guide**](https://foxxmd.github.io/multi-scrobbler/docs/quickstart)
## Installation
[See the **Installation** documentation](https://foxxmd.github.io/multi-scrobbler/docs/installation)
### Locally
## Configuration
Clone this repository somewhere and then install from the working directory
```bash
git clone https://github.com/FoxxMD/multi-scrobbler.git .
cd multi-scrobbler
npm install
```
### [Docker](https://hub.docker.com/r/foxxmd/multi-scrobbler)
```
foxxmd/spotify-scrobbler:latest
```
## Setup
Some setup is required! See the [configuration](docs/configuration.md) docs for a full reference.
### TLDR, Minimal Example
You want to use multi-scrobbler to scrobble your plays from Spotify to Maloja:
#### Local
```bash
SPOTIFY_CLIENT_ID=yourId SPOTIFY_CLIENT_SECRET=yourSecret MALOJA_URL=http://domain.tld MALOJA_API_KEY=1234 node index.js
```
#### Docker
```bash
docker run -e "SPOTIFY_CLIENT_ID=yourId" -e "SPOTIFY_CLIENT_SECRET=yourSecret" -e "MALOJA_URL=http://domain.tld" -e "MALOJA_API_KEY=1234" -v /path/on/host/config:/home/node/app/config foxxmd/spotify-scrobbler
```
**But I want to use json for configuration?**
Then use [config.json.example](/config/config.json.example) and drop it in your `CONFIG_DIR` directory
**Is there an example configuration using everything?**
Yes, check out the [kitchen sink example](/docs/kitchensink.md)
[See the **Configuration** documentation](https://foxxmd.github.io/multi-scrobbler/docs/configuration)
## Usage
A status page with statistics, recent logs, and some runtime configuration options can be found at
```
https://localhost:9078
http://localhost:9078
```
Output is also provided to stdout/stderr as well as file if specified in configuration.
On first startup you may need to authorize Spotify by visiting the callback URL (which can also be accessed from the status page). Visit the status page above to find the applicable link to trigger this.
On first startup you may need to authorize Spotify and/or Last.fm by visiting the callback URL (which can also be accessed from the status page). Visit the status page above to find the applicable link to trigger this.
## Help/FAQ
Having issues with connections or configuration? Check the [FAQ](https://foxxmd.github.io/multi-scrobbler/docs/FAQ) before creating an issue!
## Development
[Detailed architecture and development guides for Sources/Clients](https://foxxmd.github.io/multi-scrobbler/docs/development/dev-common)
## License
+71
View File
@@ -0,0 +1,71 @@
FROM lsiobase/alpine:3.18 as base
ENV TZ=Etc/GMT
RUN \
echo "**** install build packages ****" && \
apk add --no-cache \
avahi \
avahi-tools \
nodejs \
npm \
&& \
echo "**** cleanup ****" && \
rm -rf \
/root/.cache \
/tmp/*
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ARG data_dir=/config
VOLUME $data_dir
ENV CONFIG_DIR=$data_dir
COPY docker/root/ /
WORKDIR /app
FROM base as build
# copy dep/TS config and install dev dependencies
COPY --chown=abc:abc package*.json tsconfig.json ./
COPY --chown=abc:abc patches ./patches
RUN npm ci \
&& chown -R root:root node_modules
#RUN yarn install
COPY --chown=abc:abc . /app
# need to set before build so server/client build is optimized and has constants (if needed)
ENV NODE_ENV=production
RUN npm run docs:install && npm run build && rm -rf node_modules && rm -rf docsite/node_modules
FROM base as app
#COPY --chown=abc:abc package.json yarn.lock ./
COPY --chown=abc:abc package*.json ./
COPY --chown=abc:abc patches ./patches
COPY --from=build --chown=abc:abc /app/dist /app/dist
COPY --from=build --chown=abc:abc /app/src /app/src
COPY --from=build --chown=abc:abc /app/docsite /app/docsite
COPY --from=base /usr/local/bin /usr/local/bin
COPY --from=base /usr/local/lib /usr/local/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 \
&& npm cache clean --force \
&& chown -R abc:abc node_modules \
&& rm -rf node_modules/@types
ARG webPort=9078
ENV PORT=$webPort
EXPOSE $PORT
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<defs>
<style>
.cls-1 {
fill: #c34a36;
}
</style>
</defs>
<path class="cls-1" d="M76.94,65.49c.87-7.85-5.27-14.51-12.94-14.51-4.56,0-8.57,2.36-10.9,5.91h-4.55c2.36-5.11,7.17-8.87,12.95-9.71V20c0-11.05-8.95-20-20-20H5C2.24,0,0,2.24,0,5V123c0,2.76,2.24,5,5,5H41.5c11.05,0,20-8.95,20-20v-27.17c-5.78-.84-10.59-4.6-12.95-9.72h4.55c2.59,3.96,7.27,6.43,12.46,5.82,6.08-.71,10.71-5.37,11.39-11.45ZM15.3,94.39c0,1.1-.9,2-2,2s-2-.9-2-2V33.61c0-1.1,.9-2,2-2s2,.9,2,2v60.78Zm11.12,14.55c0,1.1-.9,2-2,2s-2-.9-2-2V19.06c0-1.1,.9-2,2-2s2,.9,2,2V108.94Zm37.58-39.53c-2.28,0-4.23-1.41-5.02-3.41h-21.43v28.39c0,1.1-.9,2-2,2s-2-.9-2-2V33.61c0-1.1,.9-2,2-2s2,.9,2,2v28.39h21.43c.93-2.33,3.42-3.86,6.18-3.29,2.05,.42,3.7,2.08,4.13,4.12,.73,3.5-1.92,6.58-5.29,6.58Z"/>
<path class="cls-1" d="M123,0h-36.5c-11.05,0-20,8.95-20,20v27.18c7.55,1.12,13.5,7.2,14.4,14.82h9.55v-28.28c0-1,.68-1.92,1.66-2.08,1.25-.2,2.34,.76,2.34,1.97v60.67c0,1-.68,1.92-1.66,2.08-1.25,.2-2.34-.76-2.34-1.97v-28.39h-9.55c-.9,7.62-6.84,13.7-14.4,14.82v27.18c0,11.05,8.95,20,20,20h36.5c2.76,0,5-2.24,5-5V5c0-2.76-2.24-5-5-5Zm-17.42,108.94c0,1.1-.9,2-2,2s-2-.9-2-2V19.06c0-1.1,.9-2,2-2s2,.9,2,2V108.94Zm11.12-14.55c0,1.1-.9,2-2,2s-2-.9-2-2V33.61c0-1.1,.9-2,2-2s2,.9,2,2v60.78Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 KiB

-137
View File
@@ -1,137 +0,0 @@
import dayjs from "dayjs";
import {buildTrackString, capitalize, createLabelledLogger, playObjDataMatch} from "../utils.js";
export default class AbstractScrobbleClient {
name;
type;
initialized = false;
recentScrobbles = [];
scrobbledPlayObjs = [];
newestScrobbleTime;
oldestScrobbleTime = dayjs();
tracksScrobbled = 0;
lastScrobbleCheck = dayjs();
refreshEnabled;
checkExistingScrobbles;
verboseOptions;
config;
logger;
constructor(type, name, config = {}) {
this.type = type;
this.name = name;
const identifier = `${capitalize(this.type)} - ${name}`;
this.logger = createLabelledLogger(identifier, identifier);
const {
options: {
refreshEnabled = true,
checkExistingScrobbles = true,
verbose = {},
} = {},
...rest
} = config;
this.config = rest;
this.refreshEnabled = refreshEnabled;
this.checkExistingScrobbles = checkExistingScrobbles;
const {
match: {
onNoMatch = false,
onMatch = false,
confidenceBreakdown = false,
} = {},
...vRest
} = verbose
if (onMatch || onNoMatch) {
this.logger.warn('Setting verbose matching may produce noisy logs! Use with care.');
}
this.verboseOptions = {
...vRest,
match: {
onNoMatch,
onMatch,
confidenceBreakdown
}
};
}
scrobblesLastCheckedAt = () => {
return this.lastScrobbleCheck;
}
formatPlayObj = obj => {
this.logger.warn('formatPlayObj should be defined by concrete class!');
return obj;
}
// time frame is valid as long as the play date for the source track is newer than the oldest play time from the scrobble client
// ...this is assuming the scrobble client is returning "most recent" scrobbles
timeFrameIsValid = (playObj, log = false) => {
const {
data: {
playDate,
} = {},
} = playObj;
const validTime = playDate.isAfter(this.oldestScrobbleTime);
if (log && !validTime) {
this.logger.debug(`${buildTrackString(playObj)} was in an invalid time frame (played before the oldest scrobble found)`);
}
return validTime;
}
addScrobbledTrack = (playObj, scrobbleResp) => {
this.scrobbledPlayObjs.push({play: playObj, scrobble: this.formatPlayObj(scrobbleResp)});
}
cleanSourceSearchTitle = (playObj) => {
const {
data: {
track,
} = {},
} = playObj;
return track;
};
findExistingSubmittedPlayObj = (playObj) => {
const {
data: {
playDate
} = {},
meta: {
source,
} = {}
} = playObj;
const dtInvariantMatches = this.scrobbledPlayObjs.filter(x => playObjDataMatch(playObj, x.play));
if (dtInvariantMatches.length === 0) {
return [undefined, undefined];
}
const matchPlayDate = dtInvariantMatches.find((x) => {
const {
play: {
data: {
playDate: sPlayDate
} = {},
meta: {
source: playSource
} = {},
} = {},
} = x;
// need to account for inaccurate DT from subsonic
if(source === 'Subsonic' && playSource === 'Subsonic') {
return playDate.isSame(sPlayDate) || playDate.diff(sPlayDate, 'minute') <= 1;
}
return playDate.isSame(sPlayDate);
});
return [matchPlayDate, dtInvariantMatches];
}
}
-393
View File
@@ -1,393 +0,0 @@
import AbstractScrobbleClient from "./AbstractScrobbleClient.js";
import dayjs from 'dayjs';
import LastFm from 'lastfm-node-client';
import {
buildTrackString,
playObjDataMatch,
readJson,
setIntersection, sleep,
sortByPlayDate,
truncateStringToLength, writeFile
} from "../utils.js";
const badErrors = [
'api key suspended',
'invalid session key',
'invalid api key',
'authentication failed'
];
const retryErrors = [
'operation failed',
'service offline',
'temporarily unavailable',
'rate limit'
]
export default class LastfmScrobbler extends AbstractScrobbleClient {
client;
redirectUri;
workingCredsPath;
initialized = false;
user;
constructor(name, config = {}, options = {}) {
super('lastfm', name, config, options);
const {redirectUri, apiKey, secret, session, configDir} = config;
this.redirectUri = `${redirectUri}?state=${name}`;
if (apiKey === undefined) {
this.logger.warn("'apiKey' not found in config! Client will most likely fail when trying to scrobble");
}
this.workingCredsPath = `${configDir}/currentCreds-lastfm-${name}.json`;
this.client = new LastFm(apiKey, secret, session);
}
static formatPlayObj(obj) {
const {
artist: {
'#text': artists
},
name: title,
album: {
'#text': album,
},
duration,
date: {
uts: time,
},
} = obj;
let artistStrings = artists.split(',');
return {
data: {
artists: [...new Set(artistStrings)],
track: title,
album,
duration,
playDate: dayjs.unix(time),
},
meta: {
source: 'Lastfm',
}
}
}
formatPlayObj = obj => LastfmScrobbler.formatPlayObj(obj);
callApi = async (func, tries = 1) => {
try {
return await func(this.client);
} catch (e) {
const {
message,
} = e;
// for now check for exceptional errors by matching error code text
const retryError = retryErrors.find(x => message.toLocaleLowerCase().includes(x));
if(undefined !== retryError) {
if(tries <= 2) {
const delay = tries * 3;
this.logger.warn(`API call was not good but recoverable (${retryError}), retrying in ${delay} seconds...`);
await sleep(delay * 1000);
return this.callApi(func, tries + 1);
} else {
this.logger.warn('Could not recover!');
throw e;
}
}
throw e;
}
}
getAuthUrl = () => {
const redir = `${this.config.redirectUri}?state=${this.name}`;
return `http://www.last.fm/api/auth/?api_key=${this.config.apiKey}&cb=${encodeURIComponent(redir)}`
}
authenticate = async (token) => {
const sessionRes = await this.client.authGetSession({token});
const {
session: {
key: sessionKey,
name, // username
} = {}
} = sessionRes;
this.client.sessionKey = sessionKey;
await writeFile(this.workingCredsPath, JSON.stringify({
sessionKey,
}));
}
initialize = async () => {
try {
const creds = await readJson(this.workingCredsPath, {throwOnNotFound: false});
const {sessionKey} = creds || {};
if (this.client.sessionKey === undefined && sessionKey !== undefined) {
this.client.sessionKey = sessionKey;
}
} catch (e) {
this.logger.warn('Current lastfm credentials file exists but could not be parsed', {path: this.workingCredsPath});
}
if (this.client.sessionKey === undefined) {
this.logger.info('No session key found. User interaction for authentication required.');
return;
}
try {
const infoResp = await this.callApi(client => client.userGetInfo());
const {
user: {
name,
} = {}
} = infoResp;
this.user = name;
this.initialized = true;
this.logger.info(`Client authorized for user ${name}`)
return true;
} catch (e) {
this.logger.error('Testing connection failed');
return false;
}
}
refreshScrobbles = async () => {
if (this.refreshEnabled) {
this.logger.debug('Refreshing recent scrobbles');
const resp = await this.callApi(client => client.userGetRecentTracks({user: this.user, limit: 20}));
const {
recenttracks: {
track: list = [],
}
} = resp;
this.recentScrobbles = list.map(x => LastfmScrobbler.formatPlayObj(x)).sort(sortByPlayDate);
if (this.recentScrobbles.length > 0) {
const [{data: {playDate: newestScrobbleTime = dayjs()} = {}} = {}] = this.recentScrobbles.slice(-1);
const [{data: {playDate: oldestScrobbleTime = dayjs()} = {}} = {}] = this.recentScrobbles.slice(0, 1);
this.newestScrobbleTime = newestScrobbleTime;
this.oldestScrobbleTime = oldestScrobbleTime;
this.scrobbledPlayObjs = this.scrobbledPlayObjs.filter(x => this.timeFrameIsValid(x.play));
}
}
this.lastScrobbleCheck = dayjs();
}
cleanSourceSearchTitle = (playObj) => {
const {
data: {
track,
} = {},
} = playObj;
return track.toLocaleLowerCase().trim();
}
alreadyScrobbled = (playObj, log = false) => {
return this.existingScrobble(playObj, (log || this.verboseOptions.match.onMatch)) !== undefined;
}
existingScrobble = (playObj, logMatch = false) => {
const tr = truncateStringToLength(27);
const scoreTrackOpts = {include: ['track', 'time'], transformers: {track: t => tr(t).padEnd(30)}};
// return early if we don't care about checking existing
if (false === this.checkExistingScrobbles) {
if (this.verboseOptions.match.onNoMatch) {
this.logger.debug(`(Existing Check) Source: ${buildTrackString(playObj, scoreTrackOpts)} => No Match because existing scrobble check is FALSE`);
}
return undefined;
}
let existingScrobble;
let closestMatch = {score: 0, breakdowns: ['None']};
// then check if we have already recorded this
const [existingExactSubmitted, existingDataSubmitted = []] = this.findExistingSubmittedPlayObj(playObj);
// if we have an submitted play with matching data and play date then we can just return the response from the original scrobble
if (existingExactSubmitted !== undefined) {
existingScrobble = existingExactSubmitted.scrobble;
closestMatch = {
score: 1,
breakdowns: ['Exact Match found in previously successfully scrobbled']
}
}
// if not though then we need to check recent scrobbles from scrobble api.
// this will be less accurate than checking existing submitted (obv) but will happen if backlogging or on a fresh server start
// if no recent scrobbles found then assume we haven't submitted it
// (either user doesnt want to check history or there is no history to check!)
if (this.recentScrobbles.length === 0) {
if (this.verboseOptions.match.onNoMatch) {
this.logger.debug(`(Existing Check) ${buildTrackString(playObj, scoreTrackOpts)} => No Match because no recent scrobbles returned from API`);
}
return undefined;
}
if (existingScrobble === undefined) {
// we have have found an existing submission but without an exact date
// in which case we can check the scrobble api response against recent scrobbles (also from api) for a more accurate comparison
const referenceApiScrobbleResponse = existingDataSubmitted.length > 0 ? existingDataSubmitted[0].scrobble : undefined;
const {
data: {
artists: sourceArtists = [],
playDate
} = {},
meta: {
trackLength,
source,
} = {},
} = playObj;
// clean source title so it matches title from the scrobble api response as closely as we can get it
let cleanSourceTitle = this.cleanSourceSearchTitle(playObj);
existingScrobble = this.recentScrobbles.find((x) => {
const referenceMatch = referenceApiScrobbleResponse !== undefined && playObjDataMatch(x, referenceApiScrobbleResponse);
const {data: {playDate: scrobbleTime, track: scrobbleTitle, artists = []} = {}} = x;
const playDiffThreshold = source === 'Subsonic' ? 60 : 10;
let closeTime = false;
// check if scrobble time is same as play date (when the track finished playing AKA entered recent tracks)
let scrobblePlayDiff = Math.abs(playDate.unix() - scrobbleTime.unix());
let scrobblePlayStartDiff;
if (scrobblePlayDiff <= playDiffThreshold) {
//this.logger.debug(`Scrobble with same name (${scrobbleTitle}) found and the play (finish time) vs. scrobble time diff was smaller than 10 seconds`);
closeTime = true;
}
// also need to check that scrobble time isn't the BEGINNING of the track -- if the source supports durations
if (closeTime === false && trackLength !== undefined) {
scrobblePlayStartDiff = Math.abs(playDate.unix() - (scrobbleTime.unix() - trackLength));
if (scrobblePlayStartDiff <= playDiffThreshold) {
//this.logger.debug(`Scrobble with same name (${scrobbleTitle}) found and the play (start time) vs. scrobble time diff was smaller than 10 seconds`);
closeTime = true;
}
}
let titleMatch;
const lowerScrobbleTitle = scrobbleTitle.toLocaleLowerCase().trim();
// because of all this replacing we need a more position-agnostic way of comparing titles so use intersection on title split by spaces
// and compare against length of scrobble title
const sourceTitleTerms = new Set(cleanSourceTitle.split(' ').filter(x => x !== ''));
const commonTerms = setIntersection(new Set(lowerScrobbleTitle.split(' ')), sourceTitleTerms);
titleMatch = commonTerms.size / sourceTitleTerms.size;
let artistMatch;
const lowerSourceArtists = sourceArtists.map(x => x.toLocaleLowerCase());
const lowerScrobbleArtists = artists.map(x => x.toLocaleLowerCase());
artistMatch = setIntersection(new Set(lowerScrobbleArtists), new Set(lowerSourceArtists)).size / artists.length;
const artistScore = .2 * artistMatch;
const titleScore = .3 * titleMatch;
const timeScore = .5 * (closeTime ? 1 : 0);
const referenceScore = .5 * (referenceMatch ? 1 : 0);
const score = artistScore + titleScore + timeScore;
let scoreBreakdowns = [
`Reference: ${(referenceMatch ? 1 : 0)} * .5 = ${referenceScore.toFixed(2)}`,
`Artist ${artistMatch.toFixed(2)} * .2 = ${artistScore.toFixed(2)}`,
`Title: ${titleMatch.toFixed(2)} * .3 = ${titleScore.toFixed(2)}`,
`Time: ${closeTime ? 1 : 0} * .5 = ${timeScore.toFixed(2)}`,
`Score ${score.toFixed(2)} => ${score >= .7 ? 'Matched!' : 'No Match'}`
];
const confidence = `Score ${score.toFixed(2)} => ${score >= .7 ? 'Matched!' : 'No Match'}`
const scoreInfo = {
score,
scrobble: x,
breakdowns: this.verboseOptions.match.confidenceBreakdown ? scoreBreakdowns : [confidence]
}
if (closestMatch.score <= score && score > 0) {
closestMatch = scoreInfo
}
return score >= .7;
});
}
if ((existingScrobble !== undefined && this.verboseOptions.match.onMatch) || (existingScrobble === undefined && this.verboseOptions.match.onNoMatch)) {
const closestScrobble = closestMatch.scrobble === undefined ? closestMatch.breakdowns.join(' | ') : `Closest Scrobble: ${buildTrackString(closestMatch.scrobble, scoreTrackOpts)} => ${closestMatch.breakdowns.join(' | ')}`;
this.logger.debug(`(Existing Check) Source: ${buildTrackString(playObj, scoreTrackOpts)} => ${closestScrobble}`);
}
return existingScrobble;
}
scrobble = async (playObj) => {
const {
data: {
artists,
album,
track,
duration,
playDate
} = {},
data = {},
meta: {
source,
newFromSource = false,
} = {}
} = playObj;
const sType = newFromSource ? 'New' : 'Backlog';
try {
const response = await this.callApi(client => client.trackScrobble(
{
artist: artists.join(', '),
duration,
track,
album,
timestamp: playDate.unix(),
}));
const {
scrobbles: {
'@attr': {
accepted = 0,
ignored = 0,
code,
},
scrobble: {
track: {
'#text': trackName,
} = {},
timestamp,
ignoredMessage: {
code: ignoreCode,
'#text': ignoreMsg,
} = {},
...rest
} = {}
} = {},
} = response;
if(code === 5) {
this.initialized = false;
throw new Error('Service reported daily scrobble limit exceeded! 😬 Disabling client');
}
this.addScrobbledTrack(playObj, {...rest, date: { uts: timestamp}, name: trackName});
if (newFromSource) {
this.logger.info(`Scrobbled (New) => (${source}) ${buildTrackString(playObj)}`);
} else {
this.logger.info(`Scrobbled (Backlog) => (${source}) ${buildTrackString(playObj)}`);
}
if(ignoreMsg !== '') {
this.logger.warn(`Service ignored this scrobble 😬 => (Code ${ignoreCode}) ${ignoreMsg}`)
}
// last fm has rate limits but i can't find a specific example of what that limit is. going to default to 1 scrobble/sec to be safe
await sleep(1000);
} catch (e) {
this.logger.error(`Scrobble Error (${sType})`, {playInfo: buildTrackString(playObj)});
throw e;
}
return true;
}
}
-361
View File
@@ -1,361 +0,0 @@
import AbstractScrobbleClient from "./AbstractScrobbleClient.js";
import request from 'superagent';
import dayjs from 'dayjs';
import {buildTrackString, playObjDataMatch, setIntersection, sortByPlayDate, truncateStringToLength} from "../utils.js";
const feat = ["ft.", "ft", "feat.", "feat", "featuring", "Ft.", "Ft", "Feat.", "Feat", "Featuring"];
export default class MalojaScrobbler extends AbstractScrobbleClient {
constructor(name, config = {}, options = {}) {
super('maloja', name, config, options);
const {url, apiKey} = config;
if (apiKey === undefined) {
this.logger.warn("'apiKey' not found in config! Client will most likely fail when trying to scrobble");
}
if (url === undefined) {
throw new Error("Missing 'url' for Maloja config");
}
}
static formatPlayObj(obj) {
const {
artists,
title,
album,
duration,
time,
} = obj;
let artistStrings = artists.reduce((acc, curr) => {
let aString;
if (typeof curr === 'string') {
aString = curr;
} else if (typeof curr === 'object') {
aString = curr.name;
}
const aStrings = aString.split(',');
return [...acc, ...aStrings];
}, []);
return {
data: {
artists: [...new Set(artistStrings)],
track: title,
album,
duration,
playDate: dayjs.unix(time),
},
meta: {
source: 'Maloja',
}
}
}
formatPlayObj = obj => MalojaScrobbler.formatPlayObj(obj);
callApi = async (req) => {
try {
return await req;
} catch (e) {
const {
message,
response: {
status,
body,
text,
} = {},
response,
} = e;
let msg = response !== undefined ? `API Call failed: Server Response => ${message}` : `API Call failed: ${message}`;
const responseMeta = body ?? text;
this.logger.error(msg, {status, response: responseMeta});
throw e;
}
}
testConnection = async () => {
const {url, apiKey} = this.config;
try {
const serverInfoResp = await this.callApi(request.get(`${url}/apis/mlj_1/serverinfo`));
const {
body: {
version = [],
versionstring = '',
} = {},
} = serverInfoResp;
if (version.length === 0) {
this.logger.error('Server did not respond with a version. Either the base URL is incorrect or this Maloja server is too old :(');
return false;
}
this.logger.info(`Maloja Server Version: ${versionstring}`);
if (version[0] < 2 || version[1] < 7) {
this.logger.warn('Maloja Server Version is less than 2.7, please upgrade to ensure compatibility');
}
const resp = await this.callApi(request
.get(`${url}/apis/mlj_1/test`)
.query({key: apiKey}));
const {
status,
body: {
status: bodyStatus,
} = {},
body = {},
text = '',
} = resp;
if (bodyStatus.toLocaleLowerCase() === 'ok') {
this.logger.info('Test connection succeeded!');
this.initialized = true;
return true;
}
this.logger.error('Testing connection failed => Server Response body was malformed -- should have returned "status: ok"...is the URL correct?', {
status,
body,
text: text.slice(0, 50)
});
return false;
} catch (e) {
this.logger.error('Testing connection failed');
return false;
}
}
refreshScrobbles = async () => {
if (this.refreshEnabled) {
this.logger.debug('Refreshing recent scrobbles');
const {url} = this.config;
const resp = await this.callApi(request.get(`${url}/apis/mlj_1/scrobbles?max=20`));
const {
body: {
list = [],
} = {},
} = resp;
this.recentScrobbles = list.map(x => MalojaScrobbler.formatPlayObj(x)).sort(sortByPlayDate);
if (this.recentScrobbles.length > 0) {
const [{data: {playDate: newestScrobbleTime = dayjs()} = {}} = {}] = this.recentScrobbles.slice(-1);
const [{data: {playDate: oldestScrobbleTime = dayjs()} = {}} = {}] = this.recentScrobbles.slice(0, 1);
this.newestScrobbleTime = newestScrobbleTime;
this.oldestScrobbleTime = oldestScrobbleTime;
this.scrobbledPlayObjs = this.scrobbledPlayObjs.filter(x => this.timeFrameIsValid(x.play));
}
}
this.lastScrobbleCheck = dayjs();
}
cleanSourceSearchTitle = (playObj) => {
const {
data: {
track,
artists: sourceArtists = [],
} = {},
} = playObj;
let lowerTitle = track.toLocaleLowerCase();
lowerTitle = feat.reduce((acc, curr) => acc.replace(curr, ''), lowerTitle);
// also remove [artist] from the track if found since that gets removed as well
const lowerArtists = sourceArtists.map(x => x.toLocaleLowerCase());
lowerTitle = lowerArtists.reduce((acc, curr) => acc.replace(curr, ''), lowerTitle);
// remove any whitespace in parenthesis
lowerTitle = lowerTitle.replace("\\s+(?=[^()]*\\))", '')
// replace parenthesis
.replace('()', '')
.replace('( )', '')
.trim();
return lowerTitle;
}
alreadyScrobbled = (playObj, log = false) => {
return this.existingScrobble(playObj, (log || this.verboseOptions.match.onMatch)) !== undefined;
}
existingScrobble = (playObj, logMatch = false) => {
const tr = truncateStringToLength(27);
const scoreTrackOpts = {include: ['track', 'time'], transformers: {track: t => tr(t).padEnd(30)}};
// return early if we don't care about checking existing
if (false === this.checkExistingScrobbles) {
if (this.verboseOptions.match.onNoMatch) {
this.logger.debug(`(Existing Check) Source: ${buildTrackString(playObj, scoreTrackOpts)} => No Match because existing scrobble check is FALSE`);
}
return undefined;
}
let existingScrobble;
let closestMatch = {score: 0, breakdowns: ['None']};
// then check if we have already recorded this
const [existingExactSubmitted, existingDataSubmitted = []] = this.findExistingSubmittedPlayObj(playObj);
// if we have an submitted play with matching data and play date then we can just return the response from the original scrobble
if (existingExactSubmitted !== undefined) {
existingScrobble = existingExactSubmitted.scrobble;
closestMatch = {
score: 1,
breakdowns: ['Exact Match found in previously successfully scrobbled']
}
}
// if not though then we need to check recent scrobbles from scrobble api.
// this will be less accurate than checking existing submitted (obv) but will happen if backlogging or on a fresh server start
// if no recent scrobbles found then assume we haven't submitted it
// (either user doesnt want to check history or there is no history to check!)
if (this.recentScrobbles.length === 0) {
if (this.verboseOptions.match.onNoMatch) {
this.logger.debug(`(Existing Check) ${buildTrackString(playObj, scoreTrackOpts)} => No Match because no recent scrobbles returned from API`);
}
return undefined;
}
if (existingScrobble === undefined) {
// we have have found an existing submission but without an exact date
// in which case we can check the scrobble api response against recent scrobbles (also from api) for a more accurate comparison
const referenceApiScrobbleResponse = existingDataSubmitted.length > 0 ? existingDataSubmitted[0].scrobble : undefined;
const {
data: {
artists: sourceArtists = [],
playDate
} = {},
meta: {
trackLength,
source,
} = {},
} = playObj;
// clean source title so it matches title from the scrobble api response as closely as we can get it
let cleanSourceTitle = this.cleanSourceSearchTitle(playObj);
existingScrobble = this.recentScrobbles.find((x) => {
const referenceMatch = referenceApiScrobbleResponse !== undefined && playObjDataMatch(x, referenceApiScrobbleResponse);
const {data: {playDate: scrobbleTime, track: scrobbleTitle, artists = []} = {}} = x;
const playDiffThreshold = source === 'Subsonic' ? 60 : 10;
let closeTime = false;
// check if scrobble time is same as play date (when the track finished playing AKA entered recent tracks)
let scrobblePlayDiff = Math.abs(playDate.unix() - scrobbleTime.unix());
let scrobblePlayStartDiff;
if (scrobblePlayDiff <= playDiffThreshold) {
//this.logger.debug(`Scrobble with same name (${scrobbleTitle}) found and the play (finish time) vs. scrobble time diff was smaller than 10 seconds`);
closeTime = true;
}
// also need to check that scrobble time isn't the BEGINNING of the track -- if the source supports durations
if (closeTime === false && trackLength !== undefined) {
scrobblePlayStartDiff = Math.abs(playDate.unix() - (scrobbleTime.unix() - trackLength));
if (scrobblePlayStartDiff <= playDiffThreshold) {
//this.logger.debug(`Scrobble with same name (${scrobbleTitle}) found and the play (start time) vs. scrobble time diff was smaller than 10 seconds`);
closeTime = true;
}
}
let titleMatch;
const lowerScrobbleTitle = scrobbleTitle.toLocaleLowerCase().trim();
// because of all this replacing we need a more position-agnostic way of comparing titles so use intersection on title split by spaces
// and compare against length of scrobble title
const sourceTitleTerms = new Set(cleanSourceTitle.split(' ').filter(x => x !== ''));
const commonTerms = setIntersection(new Set(lowerScrobbleTitle.split(' ')), sourceTitleTerms);
titleMatch = commonTerms.size / sourceTitleTerms.size;
let artistMatch;
const lowerSourceArtists = sourceArtists.map(x => x.toLocaleLowerCase());
const lowerScrobbleArtists = artists.map(x => x.toLocaleLowerCase());
artistMatch = setIntersection(new Set(lowerScrobbleArtists), new Set(lowerSourceArtists)).size / artists.length;
const artistScore = .2 * artistMatch;
const titleScore = .3 * titleMatch;
const timeScore = .5 * (closeTime ? 1 : 0);
const referenceScore = .5 * (referenceMatch ? 1 : 0);
const score = artistScore + titleScore + timeScore;
let scoreBreakdowns = [
`Reference: ${(referenceMatch ? 1 : 0)} * .5 = ${referenceScore.toFixed(2)}`,
`Artist ${artistMatch.toFixed(2)} * .2 = ${artistScore.toFixed(2)}`,
`Title: ${titleMatch.toFixed(2)} * .3 = ${titleScore.toFixed(2)}`,
`Time: ${closeTime ? 1 : 0} * .5 = ${timeScore.toFixed(2)}`,
`Score ${score.toFixed(2)} => ${score >= .7 ? 'Matched!' : 'No Match'}`
];
const confidence = `Score ${score.toFixed(2)} => ${score >= .7 ? 'Matched!' : 'No Match'}`
const scoreInfo = {
score,
scrobble: x,
breakdowns: this.verboseOptions.match.confidenceBreakdown ? scoreBreakdowns : [confidence]
}
if (closestMatch.score <= score && score > 0) {
closestMatch = scoreInfo
}
return score >= .7;
});
}
if ((existingScrobble !== undefined && this.verboseOptions.match.onMatch) || (existingScrobble === undefined && this.verboseOptions.match.onNoMatch)) {
const closestScrobble = closestMatch.scrobble === undefined ? closestMatch.breakdowns.join(' | ') : `Closest Scrobble: ${buildTrackString(closestMatch.scrobble, scoreTrackOpts)} => ${closestMatch.breakdowns.join(' | ')}`;
this.logger.debug(`(Existing Check) Source: ${buildTrackString(playObj, scoreTrackOpts)} => ${closestScrobble}`);
}
return existingScrobble;
}
scrobble = async (playObj) => {
const {url, apiKey} = this.config;
const {
data: {
artists,
album,
track,
duration,
playDate
} = {},
meta: {
source,
newFromSource = false,
} = {}
} = playObj;
const sType = newFromSource ? 'New' : 'Backlog';
try {
const response = await this.callApi(request.post(`${url}/apis/mlj_1/newscrobble`)
.type('json')
.send({
// maloja seems to detect this deliminator much better than commas
// also less likely artist has a forward slash in their name than a comma
artist: artists.join(' / '),
seconds: duration,
title: track,
album,
key: apiKey,
time: playDate.unix(),
}));
const {body: {
track: {
time: mTime = playDate.unix(),
duration: mDuration = duration,
album: mAlbum = album,
...rest
}
} = {}} = response;
this.addScrobbledTrack(playObj, {...rest, album: mAlbum, time: mTime, duration: mDuration});
if (newFromSource) {
this.logger.info(`Scrobbled (New) => (${source}) ${buildTrackString(playObj)}`);
} else {
this.logger.info(`Scrobbled (Backlog) => (${source}) ${buildTrackString(playObj)}`);
}
} catch (e) {
this.logger.error(`Scrobble Error (${sType})`, {playInfo: buildTrackString(playObj)});
throw e;
}
return true;
}
}
-259
View File
@@ -1,259 +0,0 @@
import dayjs from "dayjs";
import {createLabelledLogger, isValidConfigStructure, readJson, returnDuplicateStrings} from "../utils.js";
import MalojaScrobbler from "./MalojaScrobbler.js";
import LastfmScrobbler from "./LastfmScrobbler.js";
export default class ScrobbleClients {
clients = [];
logger;
configDir;
clientTypes = ['maloja','lastfm'];
constructor(configDir) {
this.configDir = configDir;
this.logger = createLabelledLogger('scrobblers', 'Scrobblers');
}
getByName = (name) => {
return this.clients.find(x => x.name === name);
}
buildClientsFromConfig = async () => {
let configs = [];
let configFile;
try {
configFile = await readJson(`${this.configDir}/config.json`, {throwOnNotFound: false});
} catch (e) {
throw new Error('config.json could not be parsed');
}
if (configFile !== undefined) {
const {clients: mainConfigClientConfigs = []} = configFile;
if (!mainConfigClientConfigs.every(x => x !== null && typeof x === 'object')) {
throw new Error('All clients from config.json must be objects');
}
for (const c of mainConfigClientConfigs) {
const {name = 'unnamed'} = c;
configs.push({...c, name, source: 'config.json'});
}
}
for (const clientType of this.clientTypes) {
switch (clientType) {
case 'maloja':
// env builder for single user mode
const url = process.env.MALOJA_URL;
const apiKey = process.env.MALOJA_API_KEY;
if (url !== undefined || apiKey !== undefined) {
configs.push({
type: 'maloja',
name: 'unnamed',
source: 'ENV',
mode: 'single',
data: {
url,
apiKey
}
})
}
break;
case 'lastfm':
const lfm = {
apiKey: process.env.LASTFM_API_KEY,
secret: process.env.LASTFM_SECRET,
redirectUri: process.env.LASTFM_REDIRECT_URI,
session: process.env.LASTFM_SESSION,
};
if (!Object.values(lfm).every(x => x === undefined)) {
configs.push({
type: 'lastfm',
name: 'unnamed',
source: 'ENV',
mode: 'single',
data: lfm
})
}
break;
default:
break;
}
let rawClientConfigs;
try {
rawClientConfigs = await readJson(`${this.configDir}/${clientType}.json`, {throwOnNotFound: false});
} catch (e) {
throw new Error(`${clientType}.json config file could not be parsed`);
}
if (rawClientConfigs !== undefined) {
let clientConfigs = [];
if (Array.isArray(rawClientConfigs)) {
clientConfigs = rawClientConfigs;
} else if (rawClientConfigs === null || typeof rawClientConfigs === 'object') {
// backwards compatibility, assuming its single-user mode
if (rawClientConfigs.data === undefined) {
clientConfigs = [{data: rawClientConfigs, mode: 'single', name: 'unnamed'}];
} else {
clientConfigs = [rawClientConfigs];
}
} else {
throw new Error(`All top level data from ${clientType}.json must be an object or array of objects`);
}
for (const m of clientConfigs) {
if (m === null || typeof m !== 'object') {
throw new Error(`All top-level data from ${clientType}.json must be an object or array of objects`);
}
m.source = `${clientType}.json`;
m.type = clientType;
configs.push(m);
}
}
}
// we have all possible client configurations so we'll check they are minimally valid
const configErrors = configs.reduce((acc, c) => {
const isValid = isValidConfigStructure(c, {type: true, data: true});
if (isValid !== true) {
const msg = `Client config from ${c.source} with name [${c.name || 'unnamed'}] of type [${c.type || 'unknown'}] has errors: ${isValid.join(' | ')}`;
return acc.concat(msg);
}
return acc;
}, []);
if (configErrors.length > 0) {
for (const m of configErrors) {
this.logger.error(m);
}
throw new Error('Could not build clients due to above errors');
}
// all client configs are minimally valid
// now check that names are unique
const nameGroupedConfigs = configs.reduce((acc, curr) => {
const {name = 'unnamed'} = curr;
const {[name]: n = []} = acc;
return {...acc, [name]: [...n, curr]};
}, {});
let nameErrors = false;
for (const [name, configs] of Object.entries(nameGroupedConfigs)) {
if (configs.length > 1) {
const sources = configs.map(c => `Config object from ${c.source} of type [${c.type}]`);
this.logger.error(`Client config naming conflicts -- the following configs have the same name "${name}":
${sources.join('\n')}`);
nameErrors = true;
if (name === 'unnamed') {
this.logger.info('HINT: "unnamed" configs occur when using ENVs, if a multi-user mode config does not have a "name" property, or if a config is built in single-user mode');
}
}
}
if (nameErrors) {
throw new Error('Could not build clients due to naming conflicts');
}
// finally! all configs are valid, structurally, and can now be passed to addClient
// just need to re-map unnnamed to default
const finalConfigs = configs.map(({name = 'unnamed', ...x}) => ({
...x,
name
}));
for (const c of finalConfigs) {
await this.addClient(c);
}
}
addClient = async (clientConfig) => {
const isValidConfig = isValidConfigStructure(clientConfig, {name: true, data: true, type: true});
if (isValidConfig !== true) {
throw new Error(`Config object from ${clientConfig.source || 'unknown'} with name [${clientConfig.name || 'unnamed'}] of type [${clientConfig.type || 'unknown'}] has errors: ${isValidConfig.join(' | ')}`)
}
const {type, name, data = {}} = clientConfig;
switch (type) {
case 'maloja':
this.logger.debug(`(${name}) Attempting Maloja initialization...`);
const mj = new MalojaScrobbler(name, data);
const testSuccess = await mj.testConnection();
if (testSuccess === false) {
throw new Error(`(${name}) Maloja client not initialized due to failure during connection testing`);
} else {
this.logger.info(`(${name}) Maloja client initialized`);
this.clients.push(mj)
}
break;
case 'lastfm':
this.logger.debug(`(${name}) Attempting Lastfm initialization...`);
const lfm = new LastfmScrobbler(name, {...data, configDir: this.configDir});
try {
await lfm.initialize()
this.logger.info(`(${name}) Lastfm client initialized`);
this.clients.push(lfm)
} catch(e) {
this.logger.info(`(${name}) Could not initialize Lastfm client`)
}
break;
default:
break;
}
}
/**
* @param {*} data
* @param {{scrobbleFrom, scrobbleTo, forceRefresh: boolean}|{scrobbleFrom, scrobbleTo}} options
* @returns {Array}
*/
scrobble = async (data, options = {}) => {
const playObjs = Array.isArray(data) ? data : [data];
const {
forceRefresh = false,
checkTime = dayjs(),
scrobbleTo = [],
scrobbleFrom = 'source',
} = options;
const tracksScrobbled = [];
if (this.clients.length === 0) {
this.logger.warn('Cannot scrobble! No clients are configured.');
}
for (const client of this.clients) {
if (scrobbleTo.length > 0 && !scrobbleTo.includes(client.name)) {
this.logger.debug(`Client '${client.name}' was filtered out by '${scrobbleFrom}'`);
continue;
}
if(client.initialized === false) {
this.logger.debug(`Client '${client.name}' is not yet initialized (check authorization?)`);
continue;
}
if (forceRefresh || client.scrobblesLastCheckedAt().unix() < checkTime.unix()) {
try {
await client.refreshScrobbles();
} catch(e) {
this.logger.error(`Encountered error while refreshing scrobbles for ${client.name}`);
this.logger.error(e);
}
}
for (const playObj of playObjs) {
try {
const {
meta: {
newFromSource = false,
} = {}
} = playObj;
if (client.timeFrameIsValid(playObj, newFromSource) && !client.alreadyScrobbled(playObj, newFromSource)) {
await client.scrobble(playObj)
client.tracksScrobbled++;
tracksScrobbled.push(playObj);
}
} catch(e) {
this.logger.error(`Encountered error while in scrobble loop for ${client.name}`);
this.logger.error(e);
// for now just stop scrobbling plays for this client and move on. the client should deal with logging the issue
if(e.continueScrobbling !== true) {
break;
}
}
}
}
return tracksScrobbled;
}
}
+67
View File
@@ -0,0 +1,67 @@
# configuration file for git-cliff (0.1.0)
[changelog]
# changelog header
header = """
# Changelog
All notable changes to this project will be documented in this file.\n
"""
# template for the changelog body
# https://tera.netlify.app/docs/#introduction
body = """
{% if version %}\
## [{{ version | replace(from="v", to="") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits
| filter(attribute="scope")
| sort(attribute="scope") %}
- *({{commit.scope}})* {{ commit.message | upper_first }}
{%- if commit.breaking %}
{% raw %} {% endraw %}- **BREAKING**: {{commit.breaking_description}}
{%- endif -%}
{%- endfor -%}
{%- for commit in commits %}
{%- if commit.scope -%}
{% else -%}
- *(No Category)* {{ commit.message | upper_first }}
{% if commit.breaking -%}
{% raw %} {% endraw %}- **BREAKING**: {{commit.breaking_description}}
{% endif -%}
{% endif -%}
{% endfor -%}
{% endfor %}
"""
# remove the leading and trailing whitespaces from the template
trim = true
# changelog footer
footer = """
<!-- generated by git-cliff -->
"""
[git]
# allow only conventional commits
# https://www.conventionalcommits.org
conventional_commits = true
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "Features"},
{ message = "^fix", group = "Bug Fixes"},
{ message = "^doc", group = "Documentation"},
{ message = "^perf", group = "Performance"},
{ message = "^refactor", group = "Refactor"},
{ message = "^style", group = "Styling"},
{ message = "^test", group = "Testing"},
{ message = "^chore\\(release\\): prepare for", skip = true},
{ message = "^chore", group = "Miscellaneous Tasks"},
{ body = ".*security", group = "Security"},
]
# filter out the commits that are not matched by commit parsers
filter_commits = false
# glob pattern for matching git tags
tag_pattern = "[0-9]*"
# regex for skipping tags
skip_tags = "v0.1.0-beta.1"
+12
View File
@@ -0,0 +1,12 @@
These are **example configurations** for all Source/Client types and AIO config.
These can be used as-is by renaming them to `.json` and filling or replacing sample data.
For docker installations these examples are copied to your configuration directory on first-time use.
These are **NOT** exhaustive examples. You should consult the **configuration** documentation and the **schema explorer links** for each source/config type to see a complete list of options and descriptions for all properties.
Documentation at
* [internal docs](../docsite/docs/configuration/configuration.mdx)
* External Link: https://foxxmd.github.io/multi-scrobbler/docs/configuration
+13
View File
@@ -0,0 +1,13 @@
[
{
"type": "azuracast",
"enable": true,
"name": "azura",
"data": {
"url": "ws://192.168.0.101",
"station": "my-station-name",
"monitorWhenLive": true,
"monitorWhenListeners": 1
}
}
]
+11
View File
@@ -0,0 +1,11 @@
[
{
"name": "MyCast",
"data": {
"blacklistedDevices": [],
"whitelistedDevices": [],
"blacklistedApps": [],
"whitelistedApps": []
}
}
]
+58 -11
View File
@@ -1,23 +1,70 @@
{
"debugMode": false,
"disableWeb": false,
"sourceDefaults": {
"logPayload": false,
"logFilterFailure": "warn",
"logPlayerState": false,
"scrobbleThresholds": {
"duration": 30,
"percent": 50
},
"maxPollRetries": 1,
"maxRequestRetries": 1,
"retryMultiplier": 1.5
},
"clientDefaults": {
"maxRequestRetries": 1,
"retryMultiplier": 1.5
},
"sources": [
{
"type": "spotify", // required, source type
"clients": ["myConfig"], // optional, a list of Client config names this Source should scrobble to. Using an empty list or not including this property will make this Source scrobble to all Clients.
"name": "mySpotifySource", // optional, friendly name for the log
"data": { // required, the data for your config
"clientId": "example",
//...
"type": "spotify",
"enable": true,
"clients": ["myConfig"],
"name": "mySpotifySource",
"data": {
"clientId": "a89cba1569901a0671d5a9875fed4be1",
"clientSecret": "ec42e09d5ae0ee0f0816ca151008412a",
"redirectUri": "http://localhost:9078/callback"
}
}
],
"clients": [
{
"type": "maloja", // required, Client type
"name": "myConfig", // required, a name to identifier your Client
"data": { // required, the data for your config
"url": "http://example.com",
//...
"type": "maloja",
"enable": true,
"name": "myConfig",
"data": {
"url": "http://localhost:42010",
"apiKey": "myMalojaKey"
}
}
],
"webhooks": [
{
"name": "FirstGotifyServer",
"type": "gotify",
"url": "http://localhost:8070",
"token": "MyGotifyToken",
"priorities": {
"info": 5,
"warn": 7,
"error": 10
}
},
{
"type": "ntfy",
"name": "MyNtfyFriendlyNameForLogs",
"url": "http://localhost:9991",
"topic": "MyMultiScrobblerTopic",
"username": "Optional",
"password": "Optional",
"priorities": {
"info": 3,
"warn": 4,
"error": 5
}
}
]
}
+13
View File
@@ -0,0 +1,13 @@
[
{
"name": "DeezerOfficalDeprecated",
"enable": true,
"clients": [],
"data": {
"clientId": "a89cba1569901a0671d5a9875fed4be1",
"clientSecret": "ec42e09d5ae0ee0f0816ca151008412a",
"redirectUri": "http://localhost:9078/deezer/callback",
"interval": 60
}
}
]
+10
View File
@@ -0,0 +1,10 @@
[
{
"name": "DeezerARL",
"enable": true,
"clients": [],
"data": {
"arl": "UOsRPjT3U5Dhaaup3xQ30DWFoSgqugdSYzL3QE743waaVy2eur2ckyJcZ4RV123Q3PsbKXkLwA6ahc9XmEfev6BtfSP8LujE3JswWRQogZCDUOwVzWO5sAig",
}
}
]
+9
View File
@@ -0,0 +1,9 @@
[
{
"name": "myLfm",
"enable": true,
"data": {
"slug": "mySlug"
}
}
]
+9
View File
@@ -0,0 +1,9 @@
[
{
"name": "myLz",
"enable": true,
"data": {
"token": "myToken"
}
}
]
+10
View File
@@ -0,0 +1,10 @@
[
{
"name": "myIcy",
"enable": true,
"data": {
"url": "http://myIcecast.com/radio"
}
}
]
+26
View File
@@ -0,0 +1,26 @@
[
{
"name": "MyJellyfin",
"enable": true,
"clients": [],
"data": {
"url": "http://localhost:8096",
"user": "FoxxMD",
"apiKey": "c9fae8756fbf481ebd9c5bb56bd6540c",
// everything below is optional
"usersAllow": ["FoxxMD","SomeOtherUser"],
"usersBlock": ["AnotherUser"],
"devicesAllow": ["firefox"],
"devicesBlock": ["google-home"],
"librariesAllow": ["GoodMusic"],
"librariesBlock": ["BadMusic"],
"additionalAllowedLibraryTypes": ["musicvideos"],
"allowUnknown": false,
},
"options": {
"logPayload": true,
"logFilterFailure": "debug"
}
}
]
+11
View File
@@ -0,0 +1,11 @@
[
{
"name": "MyJriver",
"enable": true,
"data": {
"url": "0.0.0.0",
"username": "auser",
"password": "apassword"
}
}
]
+11
View File
@@ -0,0 +1,11 @@
[
{
"name": "MyKodi",
"enable": true,
"data": {
"url": "http://localhost:8080/jsonrpc",
"username": "myUser",
"password": "myPassword"
}
}
]
+16 -7
View File
@@ -1,13 +1,22 @@
[
{
"name": "myLastFm", // required, a name to identify your Client
"name": "myLastFmClient",
"enable": true,
"configureAs": "client",
"data": {
"apiKey": "string", // required, Lastfm api key
"secret": "string", // required, Lastfm shared secret
"session": "string", // optional, session id returned from a complete auth flow.
// if not specified will be generated during authentication
"redirectUri": "http://localhost:9078/lastfm/callback" // optional, if not different than this default
// callback for auth. Must have "lastfm/callback" in the url somewhere
"apiKey": "a89cba1569901a0671d5a9875fed4be1",
"secret": "ec42e09d5ae0ee0f0816ca151008412a",
"redirectUri": "http://localhost:9078/lastfm/callback"
}
},
{
"name": "myLastFmSource",
"enable": true,
"configureAs": "source",
"data": {
"apiKey": "a89cba1569901a0671d5a9875fed4be1",
"secret": "ec42e09d5ae0ee0f0816ca151008412a",
"redirectUri": "http://localhost:9078/lastfm/callback"
}
}
]
+30
View File
@@ -0,0 +1,30 @@
[
{
"name": "brainzClient",
"enable": true,
"configureAs": "client",
"data": {
"token": "029b081ba-9156-4pe7-88e5-3be671f5ea2b",
"username": "FoxxMD"
}
},
{
"name": "brainzSource",
"enable": true,
"configureAs": "source",
"data": {
"token": "029b081ba-9156-4pe7-88e5-3be671f5ea2b",
"username": "FoxxMD"
}
},
{
"name": "koitoClient",
"enable": true,
"configureAs": "client",
"data": {
"token": "029b081ba-9156-4pe7-88e5-3be671f5ea2b",
"username": "FoxxMD",
"url": "http://192.168.0.100:4110/apis/listenbrainz/1"
}
},
]
+4 -3
View File
@@ -1,9 +1,10 @@
[
{
"name": "myMaloja", // required, a name to identify your Client
"name": "myMaloja",
"enable": true,
"data": {
"url": "https://domain.tld", // required, the base url of your maloja installation
"apiKey": "string" // required, your maloja api key
"url": "http://localhost:42010",
"apiKey": "myMalojaKey"
}
}
]
+12
View File
@@ -0,0 +1,12 @@
[
{
"name": "MyMopidy",
"enable": true,
"data": {
"url": "localhost",
"uriBlacklist": [],
"uriWhitelist": [],
"albumBlacklist": []
}
}
]
+13
View File
@@ -0,0 +1,13 @@
[
{
"enable": true,
"name": "MyMPD",
"data": {
"url": "192.168.0.100:6600",
"password": "MY_PASSWORD"
},
"options": {
"disableDiscovery": false
}
}
]
+10
View File
@@ -0,0 +1,10 @@
[
{
"name": "ubuntu",
"enable": true,
"data": {
"whitelist": ["vlc", "mpd"],
"blacklist": ["spotify"]
}
}
]
+9
View File
@@ -0,0 +1,9 @@
[
{
"name": "myYamaha",
"enable": true,
"data": {
"url": "192.168.0.101"
}
}
]
+11
View File
@@ -0,0 +1,11 @@
[
{
"type": "musikcube",
"enable": true,
"name": "musikcube",
"data": {
"url": "ws://localhost:7905",
"password": "MY_PASSWORD"
}
}
]
+16 -6
View File
@@ -1,11 +1,21 @@
[
{
"name": "default", // optional, friendly name for logs
"clients": [], // optional, list of scrobble clients (by config name) that this source should scrobble to. Using an empty list or not including this property will make this source scrobble to all clients.
"name": "MyPlexApi",
"enable": true,
"clients": [],
"data": {
"user": ["username@gmail.com","anotherUser@gmail.com"], // optional, list of users to scrobble tracks for
"libraries": ["music","my podcasts"], // optional, list of libraries to scrobble tracks from
"servers": ["myServer","anotherServer"] // optional, list of servers to scrobble tracks from
}
"token": "1234",
"url": "http://192.168.0.120:32400",
"usersAllow": ["FoxxMD","SomeOtherUser"],
"usersBlock": ["AnotherUser"],
"devicesAllow": ["firefox"],
"devicesBlock": ["google-home"],
"librariesAllow": ["GoodMusic"],
"librariesBlock": ["BadMusic"]
},
"options": {
"logPayload": true,
"logFilterFailure": "debug"
}
}
]
+17
View File
@@ -0,0 +1,17 @@
[
// DEPRECATED, use API Source instead
// rename files to plex.json to use
{
"name": "MyPlex",
"enable": true,
"clients": [],
"data": {
"user": ["username@gmail.com","anotherUser@gmail.com"],
"libraries": ["music","my podcasts"],
"servers": ["myServer","anotherServer"],
"options": {
"logFilterFailure": "warn"
}
}
}
]
+7 -8
View File
@@ -1,14 +1,13 @@
[
{
"name": "default", // optional, friendly name for logs
"clients": [], // optional, list of scrobble clients (by config name) that this source should scrobble to. Using an empty list or not including this property will make this source scrobble to all clients.
"name": "MySpotify",
"enable": true,
"clients": [],
"data": {
"clientId": "string", // spotify client id -- required if not providing access token
"clientSecret": "string", // spotify client secret -- required if not providing access token
"accessToken": "string", // spotify access token -- required if not providing client id/secret
"refreshToken": "string", // spotify refresh token -- recommended to provide if not providing client id/secret
"redirectUri": "http://localhost:9078/callback",// spotify redirect URI -- required only if not the default shown here. URI must end in "callback"
"interval": 60 // optional, how long to wait before calling spotify for new tracks
"clientId": "a89cba1569901a0671d5a9875fed4be1",
"clientSecret": "ec42e09d5ae0ee0f0816ca151008412a",
"redirectUri": "http://localhost:9078/callback",
"interval": 60
}
}
]
+5 -4
View File
@@ -1,10 +1,11 @@
[
{
"name": "default", // optional, friendly name for logs
"name": "MySubsonic",
"enable": true,
"data": {
"url": "http://localhost:4040/airsonic",// required, the url you would visit to listen to music on the web
"user": "yourUser", // required, username to login with
"password": "yourPassword" // required, password to login with
"url": "http://localhost:4040/airsonic",
"user": "yourUser",
"password": "yourPassword"
}
}
]
+9 -5
View File
@@ -1,11 +1,15 @@
[
{
"name": "default", // optional, friendly name for logs
"clients": [], // optional, list of scrobble clients (by config name) that this source should scrobble to. Using an empty list or not including this property will make this source scrobble to all clients.
"name": "MyTautuilli",
"enable": true,
"clients": [],
"data": {
"user": ["username@gmail.com","anotherUser@gmail.com"], // optional, list of users to scrobble tracks for
"libraries": ["music","my podcasts"], // optional, list of libraries to scrobble tracks from
"servers": ["myServer","anotherServer"] // optional, list of servers to scrobble tracks from
"user": ["username@gmail.com","anotherUser@gmail.com"],
"libraries": ["music","my podcasts"],
"servers": ["myServer","anotherServer"],
"options": {
"logFilterFailure": "warn"
}
}
}
]
+10
View File
@@ -0,0 +1,10 @@
[
{
"enable": true,
"name": "MyVlc",
"data": {
"url": "localhost:8080",
"password": "changeme"
}
}
]
+10
View File
@@ -0,0 +1,10 @@
[
{
"name": "MyWebScrobbler",
"data": {
"slug": "MyOptionalSlug",
"whitelist": [],
"blacklist": []
}
}
]
+19
View File
@@ -0,0 +1,19 @@
[
{
"name": "MyYTMusic",
"enable": true,
"clients": [],
"data": {
"cookie": "__Secure-1PSIDTS=sidts-CjEB3EgAEvCd-......",
// either cookie or id/secret needs to be provided
"clientId": "891098404....apps.googleusercontent.com",
"clientSecret": "GOCS..."
// optional
//"redirectUri": "http://my.custom.tld/api/ytmusic/callback?name=MyYTMusic"
},
"options": {
"logDiff": true
}
}
]
+29
View File
@@ -0,0 +1,29 @@
version: '3.7'
services:
multi-scrobbler:
build:
context: .
env_file:
- path: ./.env
required: false
#environment:
#- TZ=Etc/GMT # Specify timezone from TZ Database name found here https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
#- SPOTIFY_CLIENT_ID=
#- SPOTIFY_CLIENT_SECRET=
#- SPOTIFY_REDIRECT_URI=http://multi-scrobbler-host-IP:9078/callback ## Need to be whitelisted in Spotify dashboard and is used for creating the connection first time
#- MALOJA_URL=http://maloja:42010
#- MALOJA_API_KEY=
#- PUID=1000 # required if running docker on linux host, see main README Docker setup instructions
#- PGID=1000 # required if running docker on linux host, see main README Docker setup instructions
# set if using a source/client with redirect URI that you have not explicitly set and MS is NOT running on the same machine that you will view the dashboard from
# EX: You will view MS dashboard at 'http://192.168.0.101:9078' -> set BASE_URL=http://192.168.0.101:9078
#- BASE_URL=http://MyHostIP:9078
volumes:
- "./config:/config"
#networks:
# - (optional to add container to the same bridge network that maloja is inside to be able to use docker internal networking & dns to resolve and connect to maloja URL via http://maloja:port)
ports:
- 9078:9078 # first port is the HOST port multi-scrobbler will serve UI and API on
restart: unless-stopped
+24
View File
@@ -0,0 +1,24 @@
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_CLIENT_ID=
#- SPOTIFY_CLIENT_SECRET=
#- SPOTIFY_REDIRECT_URI=http://multi-scrobbler-host-IP:9078/callback ## Need to be whitelisted in Spotify dashboard and is used for creating the connection first time
#- MALOJA_URL=http://maloja:42010
#- MALOJA_API_KEY=
#- PUID=1000 # required if running docker on linux host, see main README Docker setup instructions
#- PGID=1000 # required if running docker on linux host, see main README Docker setup instructions
# set if using a source/client with redirect URI that you have not explicitly set and MS is NOT running on the same machine that you will view the dashboard from
# EX: You will view MS dashboard at 'http://192.168.0.101:9078' -> set BASE_URL=http://192.168.0.101:9078
#- BASE_URL=http://MyHostIP:9078
volumes:
- "./config:/config"
#networks:
# - (optional to add container to the same bridge network that maloja is inside to be able to use docker internal networking & dns to resolve and connect to maloja URL via http://maloja:port)
ports:
- 9078:9078 # first port is the HOST port multi-scrobbler will serve UI and API on
restart: unless-stopped
+33
View File
@@ -0,0 +1,33 @@
#!/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"
# 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
fi
# add example configs
if [ "$POPULATE_EXAMPLES" = true ]; then
echo "Directory is empty, adding examples..."
cp -r /app/config/. "${CONFIG_DIR}"/
fi
# permissions
echo "chown'ing directory to ensure correct permissions."
chown -R abc:abc "${CONFIG_DIR}"
echo "Done!"
echo -e "-------------------------------------\n"
@@ -0,0 +1 @@
oneshot
@@ -0,0 +1 @@
/etc/s6-overlay/s6-rc.d/init-ms-config/run
+10
View File
@@ -0,0 +1,10 @@
#!/usr/bin/with-contenv bash
# used https://github.com/linuxserver/docker-wikijs/blob/master/root/etc/s6-overlay/s6-rc.d/svc-wikijs/run as a template
# NODE_ARGS can be passed by ENV in docker command like "docker run foxxmd/multi-scrobbler -e NODE_ARGS=--optimize_for_size"
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
@@ -0,0 +1 @@
longrun
-182
View File
@@ -1,182 +0,0 @@
# General
General configuration options. These must be set through environmental variables because they affect initial startup of
the app. **These variables are also available to Docker containers.**
| Environmental Variable | Required? | Default | Description |
|----------------------------|-----------|--------------|-------------------------------------------------------------------------------------------|
| `CONFIG_DIR` | No | `CWD/config` | Directory to look for all other configuration files |
| `LOG_PATH` | No | `CWD/logs` | If `false` no logs will be written. If `string` will be the directory logs are written to |
| `PORT` | No | 9078 | Port to run web server on |
**The app must have permission to write to `CONFIG_DIR` in order to store the current spotify access token.**
# Sources and (Scrobble) Clients
The app has two types of configurations:
* **Sources** -- Where plays are parsed from
* **Clients** -- Scrobble clients that plays are scrobbled to
All configurations can be configured through:
* environmental variables
* individual **json** files for each source/client type found in the `CONFIG_DIR` directory IE `config/spotify.json`
* or through the main `config.json` (also found in `CONFIG_DIR` directory) using the `clients` or `sources` property under `data`:
```json5
// in config.json
{
//...
"sources": [
{
"name": "myConfig",
"type": "spotify",
"clients": [
"myMalojaClient"
],
"data": {
"clientId": "anExample"
//...
}
}
]
}
```
See [config.json.example](../config/config.json.example) for a short example of this or check out [the kitchen sink example](kitchensink.md).
### ENV-Based or JSON-Based?
multi-scrobbler can be configured differently depending on how you will use it. See which use-case fits you the best and then use that approach when setting up each configuration:
#### ENV-Based (Single User)
* You are the only person for whom the application is scrobbling
* You may have many sources (Plex, Spotify, Tautulli...) but you only have one of each type of source
* You have only one scrobble client
* **Easier for small setups. Difficult for larger, multi-sourced setups (may want to switch to json)**
* **Will not work for multi-user setups**
#### JSON-Based (Multi User)
* You are a single user but want to set up many sources
* You want to use multi-scrobbler to scrobble for yourself and others IE family, friends, etc.
* You may 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, one for each person
* You want only some **Sources** to scrobble to some **Clients** IE Fred's Spotify account scrobbles to Fred's Maloja
server, but not Mary's Maloja server
Note: While you may mix and match configuration approaches it is recommended to **only use ENV-based configs if you are
doing everything in ENV-based configurations.**
# Sources
## [Spotify](https://www.spotify.com)
To access your Spotify history you must [register an application](https://developer.spotify.com/dashboard) to get a
Client ID/Secret. Make sure to also whitelist your redirect URI in the application settings.
### ENV-Based
| Environmental Variable | Required? | Default | Description |
|----------------------------|-----------|----------------------------------|----------------------------------------------------|
| `SPOTIFY_CLIENT_ID` | Yes | | |
| `SPOTIFY_CLIENT_SECRET` | Yes | | |
| `SPOTIFY_ACCESS_TOKEN` | No | | Must include either this token or client id/secret |
| `SPOTIFY_REFRESH_TOKEN` | No | | If using access token this is also recommended |
| `SPOTIFY_REDIRECT_URI` | No | `http://localhost:{PORT}/callback` | URI must end in `callback` |
### JSON-Based
See [`spotify.json.example`](../config/spotify.json.example)
## [Plex](https://plex.tv)
Check the [instructions](plex.md) on how to setup a [webhooks](https://support.plex.tv/articles/115002267687-webhooks) to scrobble your plays.
### ENV-Based
| Environmental Variable | Required | Default | Description |
|------------------------|----------|---------|-------------------------------------------------|
| `PLEX_USER` | No | | The a comma-delimited list of usernames to scrobble tracks for. No usernames specified means all tracks by all users will be scrobbled. |
### JSON-Based
See [`plex.json.example`](../config/plex.json.example)
## [Tautulli](https://tautulli.com)
Check the [instructions](plex.md) on how to setup a notification agent.
### ENV-Based
| Environmental Variable | Required | Default | Description |
|------------------------|----------|---------|-------------------------------------------------|
| `TAUTULLI_USER` | No | | The a comma-delimited list of usernames to scrobble tracks for. No usernames specified means all tracks by all users will be scrobbled. |
### JSON-Based
See [`tautulli.json.example`](../config/tautulli.json.example)
## [Subsonic](http://www.subsonic.org/)
Can use this source for any application that implements the [Subsonic API](http://www.subsonic.org/pages/api.jsp) (such as [Airsonic](https://airsonic.github.io/))
**Known Issues:**
* "Time played at" is somewhat inaccurate since the api only reports "played X minutes ago" so...
* All scrobble times are therefore "on the minute" and you may experience occasional duplicate scrobbles
* "played X minutes ago" sometimes is also not reported correctly
* Multiple artists are reported as one value and cannot be separated
* If using [Airsonic Advanced](https://github.com/airsonic-advanced/airsonic-advanced) the password used (under **Credentials**) must be **Decodable**
### ENV-Based
| Environmental Variable | Required? | Default | Description |
|----------------------------|-----------|----------------------------------|----------------------------------------------------|
| `SUBSONIC_USER` | Yes | | |
| `SUBSONIC_PASSWORD` | Yes | | |
| `SUBSONIC_URL` | Yes | | Base url of your subsonic-api server |
### JSON-Based
See [`subsonic.json.example`](../config/subsonic.json.example)
# Clients
## [Maloja](https://github.com/krateng/maloja)
### ENV-Based
| Environmental Variable | Required? | Default | Description |
|----------------------------|-----------|---------|-------------------------------|
| `MALOJA_URL` | Yes | | Base URL of your installation |
| `MALOJA_API_KEY` | Yes | | Api Key |
### JSON-Based
See [`maloja.json.example`](../config/maloja.json.example)
## [Last.fm](https://www.last.fm)
[Register for an API account here.](https://www.last.fm/api/account/create)
The Callback URL is actually specified by multi-scrobbler but to keep things consistent you should use
```
http://localhost:9078/lastfm/callback
```
or replace `localhost:9078` with your own base URL
### ENV-Based
| 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:{PORT}/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. |
### JSON-Based
See [`lastfm.json.example`](../config/lastfm.json.example)
+20
View File
@@ -0,0 +1,20 @@
# Dependencies
/node_modules
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
+41
View File
@@ -0,0 +1,41 @@
# Website
This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator.
### Installation
```
$ yarn
```
### Local Development
```
$ yarn start
```
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
### Build
```
$ yarn build
```
This command generates static content into the `build` directory and can be served using any static contents hosting service.
### Deployment
Using SSH:
```
$ USE_SSH=true yarn deploy
```
Not using SSH:
```
$ GIT_USER=<Your GitHub username> yarn deploy
```
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
+273
View File
@@ -0,0 +1,273 @@
---
toc_min_heading_level: 2
toc_max_heading_level: 5
---
## Connection Issues
### Plex (Webhook)/Tautulli/Jellyfin(Webhook)/Webscrobbler don't connect
These sources are **ingress-based** which means that multi-scrobbler waits for the Plex/Tautulli/Jellyfin server (or Webscrobbler extension) to contact multi-scrobbler, as opposed to multi-scrobbler contacting them.
multi-scrobbler will log information about any server that connects to it for these services. In the logs it looks something like this:
```
2023-02-22T10:55:56-05:00 info : [Ingress - Plex ] Received request from a new remote address: ::ffff:192.168.0.140 (UA: PlexMediaServer/1.24.5.5173-8dcc73a59)
2023-02-22T10:55:56-05:00 info : [Ingress - Plex ] ::ffff:192.168.0.140 (UA: PlexMediaServer/1.24.5.5173-8dcc73a59) Received valid data from server examplePlex for the first time.
2023-02-22T10:55:56-05:00 warn : [Plex Request ] Received valid Plex webhook payload but no Plex sources are configured
```
It also logs if a server tries to connect to a URL that it does not recognize:
```
2023-02-22T11:16:12-05:00 debug : [App ] Server received POST request from ::ffff:192.168.0.140 (UA: PlexMediaServer/1.24.5.5173-8dcc73a59) to unknown route: /plkex
```
**So, if you do not see either of these in your logs then Plex/Tautulli/Jellyfin/Webscrobbler is not able to connect to your multi-scrobbler instance at all.**
This is not something multi-scrobbler can fix and means you have an issue in your network.
#### Troubleshooting
Check or try all these steps before submitting an issue:
##### Turn on Debug Logging
First, turn on **debug** logging for multi-scrobbler by setting the environmental variable `DEBUG_MODE=true`. Then, check the output for any additional information.
##### Check Host name and URL
The URLs examples in the [configuration](configuration/configuration.mdx) documentation assume you are running Plex/Tautulli/Jellyfin/Webscrobbler on the same server as multi-scrobbler. If these are not the same machine then you need to determine the IP address or domain name that multi-scrobbler is reachable at and use that instead of `localhost` when configuring these sources. **This is likely the same host name that you would use to access the web interface for multi-scrobbler.**
EX `http://localhost:9078/plex` -> `http://192.168.0.140:9078/plex`
##### Check Firewall and Port Forwarding
If the machine multi-scrobbler is running on has a firewall ensure that port **9078** is open. Or if it is in another network entirely make sure your router is forwarding this port and it is open to the correct machine.
##### Check Source Service Logs
Plex/Tautulli/Jellyfin all have logs that will log if they cannot connect to multi-scrobbler. Check these for further information.
###### Plex
Settings -> Manage -> Console
###### Tautulli
Check the command-line output of the application or docker logs.
###### Jellyfin
Administration -> Dashboard -> Advanced -> Logs
###### Webscrobbler
See [Debugging the extension](https://github.com/web-scrobbler/web-scrobbler/wiki/Debug-the-extension) to get logs which should have information about failed requests.
### Jellyfin (Webhook) has warnings about undefined or missing data
Make sure you have
* [Configured the webhook plugin correctly](configuration/configuration.mdx#jellyfin)
* Checked the **Send All Properties(ignores template)** option in the webhook settings and **Saved**
multi-scrobbler is known to work on Jellyfin `10.8.9` with Webhook version `11.0.0.0`.
You can verify the payload sent from the webhook by enabling Debug Mode with the env `DEBUG_MODE=true` which will output the raw payload to DEBUG level logging.
If your issue persists and you open an Issue for it please include the raw payload logs in your report.
### Jellyfin (Webhook) has warnings about missing headers
If you experience issues trying to scrobble with Jellyfin and find this in your MS logs
```
[API] Jellyfin is not sending a request with valid headers...
```
A workaround that may fix this:
* In Webhook settings:
* [In the webhook you have already configured...](configuration/configuration.mdx#jellyfin)
* Add Request Header...
* **Key:** `Content-Type`
* **Value:** `application/json`
* Then Save
### Spotify/Deezer/LastFM won't authenticate
Ensure any **client id** or **secrets** are correct in your configuration.
The callback/redirect URL for these services must be:
* the same address you would use to access the multi-scrobbler web interface
* the web-interface must be accessible from the browser you are completing authentication from.
If multi-scrobbler is not running on the same machine your browser is on then the default/example addresses (`http://localhost...`) **will not work.** You must determine the address you can reach the web interface at (such as `http://192.168.0.140:9078`) then use that in place of `localhost` in the callback URLs.
EX `http://localhost:9078/lastfm/callback` -> `http://192.168.0.220:9078/lastfm/callback`
### Deezer is not working
Deezer has discontinued support for their API and the Deezer Source is now [**deprecated.**](configuration/configuration.mdx#deezer) See [this issue for more discussion.](https://github.com/FoxxMD/multi-scrobbler/issues/175#issuecomment-2296776625)
### Youtube Music fails after some time
If you see errors in multi-scrobbler for YTM that contain **401** or **403** like
```
Error: Could not send the specified request to browse. Status code: 401
```
then YTM has invalidated your authentication.
First, ensure you are NOT using [YoutubeTV authentication.](configuration/configuration.mdx?ytmAuth=ytt#youtube-music) If you completed authentication by entering a "User Code" you are using YoutubeTV which has stopped working. You should reauthenticate using **Cookies** or **Custom OAuth.**
#### When using OAuth Client Authentication
Refresh your authentication by using the **(Re)authenticate** link from MS's web dashboard.
#### When using Cookies Authentication
The library MS uses relies on scraping the YTM site by using cookies from your actual browser to pretend it is a browser. It does its best to keep these up to date but since this is not an official way to access the service YTM may invalidate your access _to the authenticated session_ at any time. How this is triggered is unknown and not something multi-scrobbler can control. You can help limit the chance of your session being invalidated by [getting the cookie from an Incognito/Private Session](https://github.com/LuanRT/YouTube.js/issues/803#issuecomment-2504032666) and then immediately closing the browser afterwards.
To re-authenticate MS [follow the YTM instructions to retrieve a new set of cookies for multi-scrobbler](configuration/configuration.mdx?ytmAuth=cookie#youtube-music) and then restart MS to potentially resolve the problem.
## Configuration Issues
### Config could not be parsed
If you see something like this in your logs:
```
2023-02-19T10:05:42-06:00 warn : [App] App config file exists but could not be parsed!
2023-02-19T10:05:42-06:00 error : [App] Exited with uncaught error
2023-02-19T10:05:42-06:00 error : [App] Error: config.json could not be parsed
```
It means the JSON in your configuration file is not valid. Copy and paste your configuration into a site like [JSONLint](https://jsonlint.com/) to find out where errors you have and fix them.
## Scrobbling Issues
### Last.fm does not scrobble tracks with multiple artists correctly
This is a limitation of the [Last.fm API](https://www.last.fm/api/show/track.scrobble) where the **artist** field is only one string and Last.fm does not recognize (play well) with "combined" artists.
Multi-scrobbler works the same was the official Spotify-Last.fm integration works -- it only scrobbles the **first** artist on a multi-artist track.
### Jellyfin (Webhook) does not scrobble tracks with multiple artists correctly
This is a limitation caused by the [Jellyfin webhook plugin](https://github.com/FoxxMD/multi-scrobbler/issues/70#issuecomment-1443804712) only sending the first artist to multi-scrobbler. This issues needs to be [fixed upstream on the Jellyfin webhook repository.](https://github.com/jellyfin/jellyfin-plugin-webhook/issues/166)
You can avoid this entirely by using the newer [Jellyfin API Source](configuration/configuration.mdx#jellyfin) which correctly handles multiple artists.
### Google Cast track information is missing/incorrect or MS player has weird times
The Google Cast integration relies on a few common fields in the data it receives from your casting device. Every platform that can cast (Spotify, Pandora, etc...) *should* use these fields the same but there are slight differences between their implementations that may confuse multi-scrobbler. Specific platforms may also return more information in non-common fields that are undocumented.
To diagnose these issues you [**must enable payload logging**](configuration/configuration.mdx#cast-troubleshooting) for your google cast Source, run MS, and then include logs with this output from that run. Without the raw data logged from your cast device it will be nearly impossible to resolve your issue.
### Google Cast device does not track media
It is likely the app playing on the cast device is incorrectly reporting the media type as **not music**.
MS logs will tell you what type the media is reported as with lines like:
```
My Artist - Example Track has 'unknown' media type and allowUnknownMedia=false, will not track
```
Refer to [Allow Unknown Media Type](configuration/configuration.mdx#allow-unknown-media-type) section to fix this
```
My Artist - Example Track has 'movie' media type so will not track
```
Refer to [Force Media Tracking](configuration/configuration.mdx#forcing-media-tracking) section to fix this
### VLC is not scrobbling fields correctly
Before reporting an issue turn on metadata logging in the MS VLC configuration, [see the VLC documentation.](configuration/configuration.mdx#vlc-information-reporting)
### Youtube Music misses or duplicates scrobbles
<details>
<summary>How MS detects YTM history</summary>
In order for multi-scrobbler to accurately determine if a song has been scrobbled it needs **a source of truth.** For YTM this is a "history" list scraped from the YTM website. Unfortunately, the data in this list can be (often) inconsistent which makes it hard for multi-scrobbler to "trust" that it is correct and determine when/if new scrobbles occur. This inconsistency is not something multi-scrobbler can control -- it is a side-effect of having to use an unofficial method to access YTM (scraping).
To compensate for this multi-scrobbler resets when it considers this list the "source of truth" based on if the list changes in an inconsistent way between consecutive checks. New scrobbles can only be detected when this list is "OK" as a source of truth for N+1 checks. Therefore, any new tracks that appear when the list is inconsistent will be ignored.
Duplicate scrobbles can also occur if the change between two checks is technically consistent. For instance, if you listen to a track twice in some period, separated by other music, YTM will sometimes "remove" the track from the earlier time (further down in your history) and "re-add" it at the top of the history.
</details>
#### Reporting YTM scrobble issues
If you experience scrobble detection issues you can help improve MS's YTM heureistic by providing thorough feedback as [an issue.](https://github.com/FoxxMD/multi-scrobbler/issues/new?assignees=&labels=bug&projects=&template=01-bug-report.yml&title=bug%3A+) **Please do the following to provide the most useful report:**
##### Turn on Change Detection
In your YTM configuration (`ytmusic.json`) add `logDiff` under `options` like this:
```json
{
"type": "ytmusic",
"name": "MyYTM",
"data": { ... },
"options": {
"logDiff": true
}
}
```
or set either ENVs:
* `YTM_LOG_DIFF=true`
* [`DEBUG_MODE=true`](configuration/configuration.mdx?#debug-mode)
This will cause MS to log YTM history changes similar to this:
```
[Ytmusic - MyYTM] Changes from last seen list:
1. (tuhe1CpHRxY) KNOWER - Im The President --- undefined => Moved - Originally at 6
2. (Mtg8V6Xa2nc) Vulfpeck - Romanian Drinking Song --- Schvitz => Moved - Originally at 1
3. (rxbCaiyYSXM) Nightmares On Wax - You Wish --- In A Space Outta Sound => Moved - Originally at 2
4. (tMt_YXr90AM) Gorillaz - O Green World --- undefined => Moved - Originally at 3
...
```
Which are essential to troubleshooting this behavior.
##### Turn on File Logging
Optionally, if your logs are busy due to many other sources, you can log **only** YTM logs to a file. Turn this on with the `logToFile` config option:
```json
{
"type": "ytmusic",
"name": "MyYTM",
"data": { ... },
"options": {
"logDiff": true,
"logToFile": true
}
}
```
This will cause MS to write YTM logs to a file in your `logs` folder named `ytmusic-MyYTM.log` (based on your source name).
##### Provide Detail and Context
Provide a detailed account of how you were using YTM when the issue occurred, including things like:
* the platform listening on (desktop, mobile, 3rd party client, etc...)
* any changes in platform
* > I switched from desktop to listening on my phone...
* how you were listening to music
* > I was playing an album start to finish
* > I listened to two songs in a row, then browsed for a new song in library by artist, then went back to a song in the queue...
Explain the expected behavior (it should have scrobbled songs x, y, then z) and what actually happened (it scrobbled songs x, then y, then x again, then z)
Provide ALL logs from the time when the issue occurred including logs from BEFORE (ideally 2-3 minutes of logs) and AFTER the issue.
@@ -0,0 +1,8 @@
{
"label": "Configuration",
"position": 3,
"link": {
"type": "generated-index",
"description": "Configuring Multi-Scrobbler and examples"
}
}
File diff suppressed because it is too large Load Diff
@@ -1,3 +1,8 @@
---
sidebar_position: 3
title: Kitchen Sink
---
# Example Config using all Possible Features
Scenario:
@@ -6,18 +11,27 @@ Scenario:
* Each person has their own Maloja server
* Each person has their own Spotify account
* You have your own Airsonic (subsonic) server you to scrobble from
* You have your own Youtube Music account you want to scrobble from
* Mary has her own Last.fm account she also wants to scrobble to
* Fred has his own Spotify application and provides you with just his access and refresh token because he doesn't trust you (wtf Fred)
* Fred has a Plex server and wants to scrobble everything he plays
* Mary uses Fred's Plex server but only wants to scrobble her plays from the `podcast` library
* The three of you have a shared library on Plex called `party` that you only play when you are hanging out. You want plays from that library to be scrobbled to everyone's servers.
* Fred also has his own Jellyfin server and wants to scrobble everything he plays
### All-in-one Config
Using just one config file located at `CONFIG_DIR/config.json`:
```json5
```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",
@@ -26,6 +40,9 @@ Using just one config file located at `CONFIG_DIR/config.json`:
"data": {
"clientId": "foxxSpotifyAppId",
"clientSecret": "foxxSpotifyAppSecret",
},
"options": {
"maxRequestRetries": 2, // override default max retries because spotify can...spotty
}
},
{
@@ -52,7 +69,9 @@ Using just one config file located at `CONFIG_DIR/config.json`:
"name": "fredPlex",
"clients": ["fredMaloja"],
"data": {
"user": ["fred@email.com"]
"token": "JtRnwQWD__XMJF8sT3jc",
"url": "192.168.0.233:32400",
"usersallow": ["fredUser"]
}
},
{
@@ -60,8 +79,10 @@ Using just one config file located at `CONFIG_DIR/config.json`:
"name": "maryPlex",
"clients": ["maryMaloja"],
"data": {
"user": ["mary@email.com"], // still need to specify mary as user so not all users who play from 'podcasts' get scrobbled
"libraries": ["podcasts"]
"token": "JtRnwQWD__XMJF8sT3jc",
"url": "192.168.0.233:32400",
"usersallow": ["maryUser"], // still need to specify mary as user so not all users who play from 'podcasts' get scrobbled
"librariesAllow": ["podcasts"]
}
},
{
@@ -69,7 +90,20 @@ Using just one config file located at `CONFIG_DIR/config.json`:
"name": "partyPlex",
// omitting clients (or making it empty) will make this Source scrobble to all Clients
"data": {
"libraries": ["party"],
"token": "JtRnwQWD__XMJF8sT3jc",
"url": "192.168.0.233:32400",
"librariesAllow": ["party"]
}
},
{
"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"]
}
},
{
@@ -82,6 +116,14 @@ Using just one config file located at `CONFIG_DIR/config.json`:
"url": "https://airsonic.foxx.example"
}
},
{
"type": "ytmusic",
"name": "foxxYoutube",
"clients": ["foxxMaloja"],
"data": {
"cookie": "__Secure-3PAPISID=3AxsXpy0MKGu75Qb/AkISXGqOnSDn1jEKn; DEVICE_INFO=ChxOekU0Tmpjek5EWTBPRGd3TlRBMk16QXpNdz09EJbS8Z0GGJbS8Z0G; ...",
}
},
],
"clients": [
{
@@ -114,6 +156,7 @@ Using just one config file located at `CONFIG_DIR/config.json`:
"data": {
"apiKey": "maryApiKey",
"secret": "marySecret",
"redirectUri": "http://localhost:9078/lastfm/callback"
}
}
]
@@ -123,9 +166,7 @@ Using just one config file located at `CONFIG_DIR/config.json`:
### Separate JSON files
In `CONFIG_DIR/spotify.json`:
```json5
```json5 title="CONFIG_DIR/spotify.json"
[
{
// may omit 'type' property since app knows this is file is for spotify configs
@@ -133,7 +174,7 @@ In `CONFIG_DIR/spotify.json`:
"clients": ["foxxMaloja"],
"data": {
"clientId": "foxxSpotifyAppId",
"clientSecret": "foxxSpotifyAppSecret",
"clientSecret": "foxxSpotifyAppSecret"
}
},
{
@@ -141,7 +182,7 @@ In `CONFIG_DIR/spotify.json`:
"clients": ["maryMaloja"],
"data": {
"clientId": "foxxSpotifyAppId",
"clientSecret": "foxxSpotifyAppSecret",
"clientSecret": "foxxSpotifyAppSecret"
}
},
{
@@ -150,43 +191,72 @@ In `CONFIG_DIR/spotify.json`:
"data": {
"accessToken": "fredsToken",
"refreshToken": "fredsRefreshToken",
"interval": 120,
"interval": 120
}
},
]
```
In `CONFIG_DIR/plex.json`
```json5
```json5 title="CONFIG_DIR/plex.json"
[
{
"name": "fredPlex",
"clients": ["fredMaloja"],
"data": {
"user": ["fred@email.com"]
"token": "JtRnwQWD__XMJF8sT3jc",
"url": "192.168.0.233:32400",
"usersallow": ["fredUser"]
}
},
{
"name": "maryPlex",
"clients": ["maryMaloja"],
"data": {
"user": ["mary@email.com"],
"libraries": ["podcasts"]
"token": "JtRnwQWD__XMJF8sT3jc",
"url": "192.168.0.233:32400",
"usersallow": ["maryUser"],
"librariesAllow": ["podcasts"]
}
},
{
"name": "partyPlex",
"data": {
"libraries": ["party"],
"token": "JtRnwQWD__XMJF8sT3jc",
"url": "192.168.0.233:32400",
"librariesAllow": ["party"]
}
}
]
```
In `CONFIG_DIR/maloja.json`:
```json5 title="CONFIG_DIR/jellyfin.json"
[
{
"name": "FredJelly",
"data": {
"url": "http://localhost:8096",
"user": "FoxxMD",
"apiKey": "c9fae8756fbf481ebd9c5bb56bd6540c",
"usersAllow": ["fredUser"]
}
}
]
```
```json5
```json5 title="CONFIG_DIR/ytmusic.json"
[
{
"type": "ytmusic",
"name": "foxxYoutube",
"clients": ["foxxMaloja"],
"data": {
"cookie": "__Secure-3PAPISID=3AxsXpy0MKGu75Qb/AkISXGqOnSDn1jEKn; DEVICE_INFO=ChxOekU0Tmpjek5EWTBPRGd3TlRBMk16QXpNdz09EJbS8Z0GGJbS8Z0G; ...",
}
}
]
```
```json5 title="CONFIG_DIR/maloja.json"
[
{
"name": "foxxMaloja",
@@ -212,15 +282,14 @@ In `CONFIG_DIR/maloja.json`:
]
```
In `CONFIG_DIR/lastfm.json`:
```json5
```json5 title="CONFIG_DIR/lastfm.json"
[
{
"name": "maryLFM",
"data": {
"apiKey": "maryApiKey",
"secret": "marySecret",
"redirectUri": "http://localhost:9078/lastfm/callback"
}
}
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

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