mirror of
https://github.com/FoxxMD/multi-scrobbler.git
synced 2026-09-03 13:20:00 +03:00
Compare commits
381
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc85790df5 | ||
|
|
a30d1ae6a5 | ||
|
|
5c586fb635 | ||
|
|
de0d336325 | ||
|
|
65d0c7c16b | ||
|
|
fc6cc68d59 | ||
|
|
8c83969e95 | ||
|
|
7ac57ca757 | ||
|
|
1dc8c21716 | ||
|
|
6f8e7d3862 | ||
|
|
a6d33fc2ef | ||
|
|
26121ace10 | ||
|
|
27e5656a04 | ||
|
|
f3d7d59af6 | ||
|
|
2a4afefe64 | ||
|
|
1dba73ccb2 | ||
|
|
3e82e035ac | ||
|
|
b12153fba9 | ||
|
|
8c82412de4 | ||
|
|
e804ce33ef | ||
|
|
3907fa5474 | ||
|
|
a7b3593314 | ||
|
|
76ef994067 | ||
|
|
c3cb3cf3a1 | ||
|
|
8096219526 | ||
|
|
67c1823195 | ||
|
|
2014e78ade | ||
|
|
b3c332deeb | ||
|
|
93a1042e03 | ||
|
|
ba010240c0 | ||
|
|
7f491d6974 | ||
|
|
74e67e0e41 | ||
|
|
1853f9abba | ||
|
|
c13e7ebeec | ||
|
|
207354e799 | ||
|
|
d7f6b17308 | ||
|
|
c069020aac | ||
|
|
f84a0f190d | ||
|
|
d0f43fbd2d | ||
|
|
b38168975e | ||
|
|
a845ac10ce | ||
|
|
56fdb12683 | ||
|
|
1f40e1e414 | ||
|
|
c1a260dc56 | ||
|
|
5d34ea68af | ||
|
|
41ea9e0aa7 | ||
|
|
a9d91c2c8a | ||
|
|
a696436a75 | ||
|
|
4ae388676e | ||
|
|
89b698d48d | ||
|
|
b71a4cbbd6 | ||
|
|
a870cdc6f4 | ||
|
|
c404dd5396 | ||
|
|
cae845028d | ||
|
|
1698c5d71e | ||
|
|
765d86e02b | ||
|
|
f347b82d33 | ||
|
|
87aa9c461e | ||
|
|
417ce85bc7 | ||
|
|
c8d917cfe6 | ||
|
|
8c789123f9 | ||
|
|
06ed3f4388 | ||
|
|
fab2e4938d | ||
|
|
fbcd1e1b9a | ||
|
|
d48f2bd4ac | ||
|
|
e2dad463f6 | ||
|
|
642af13f3c | ||
|
|
4146b159bd | ||
|
|
3a3a97132d | ||
|
|
04a5b91694 | ||
|
|
945f494a2e | ||
|
|
4b09909b33 | ||
|
|
a5a1365b72 | ||
|
|
6bc618c481 | ||
|
|
85d1a0e648 | ||
|
|
1ae45c6060 | ||
|
|
7daa43a883 | ||
|
|
f8501b2a2e | ||
|
|
14c35edadf | ||
|
|
cbb9774a18 | ||
|
|
7b1eb7c2c1 | ||
|
|
8c8d1e1673 | ||
|
|
fbdd504453 | ||
|
|
612a90d7e7 | ||
|
|
de8ad60db8 | ||
|
|
43fd8ea807 | ||
|
|
ec3a7e2d20 | ||
|
|
981a211e56 | ||
|
|
c42b5cacaa | ||
|
|
919907f1cb | ||
|
|
9eac9fdfa1 | ||
|
|
86cea49ed4 | ||
|
|
8fc892a738 | ||
|
|
fd591be3d6 | ||
|
|
63b23689a4 | ||
|
|
3035d4c5e8 | ||
|
|
7891133002 | ||
|
|
66db4d017e | ||
|
|
15e55dc6c5 | ||
|
|
239fb5919e | ||
|
|
7e53553a8e | ||
|
|
4bd0345d1b | ||
|
|
b207109697 | ||
|
|
77de2a7526 | ||
|
|
69b19da6aa | ||
|
|
f2e2d67668 | ||
|
|
9c44ccb663 | ||
|
|
f431f4d5af | ||
|
|
be872d5ce1 | ||
|
|
0246f7dfa6 | ||
|
|
b57f325684 | ||
|
|
153f37e927 | ||
|
|
de4cb8a5c2 | ||
|
|
eb133e4f7e | ||
|
|
a3e056e7c5 | ||
|
|
7d8b00ab22 | ||
|
|
8b633dd901 | ||
|
|
f055cf683f | ||
|
|
baaa954f31 | ||
|
|
c727de9c40 | ||
|
|
e6f4a1dbd2 | ||
|
|
3d6e86f258 | ||
|
|
e6130eb5eb | ||
|
|
97b6042768 | ||
|
|
b3c1ce6d8a | ||
|
|
5daba47ac7 | ||
|
|
8d7760df6d | ||
|
|
75c95fdaa8 | ||
|
|
e874ccc2d3 | ||
|
|
17e97b3a6a | ||
|
|
c6389590fe | ||
|
|
7151d96b59 | ||
|
|
b2b2018764 | ||
|
|
655b97ce1c | ||
|
|
5c6edefc56 | ||
|
|
bf7fb4f558 | ||
|
|
32b64ff384 | ||
|
|
f6e0f4be4a | ||
|
|
fb66f0ca84 | ||
|
|
c4b1d72f3f | ||
|
|
fb452eb40b | ||
|
|
d3fd03a78c | ||
|
|
7b04e76c3b | ||
|
|
9f34c45192 | ||
|
|
7ab0f710ff | ||
|
|
ea1fb54de1 | ||
|
|
35fd84a1d0 | ||
|
|
e498db8ad3 | ||
|
|
145960ca04 | ||
|
|
91ba24e165 | ||
|
|
66fc7a2b09 | ||
|
|
97a7c7044c | ||
|
|
53d7de7e8d | ||
|
|
32da14c08e | ||
|
|
c7f370f121 | ||
|
|
9d105173da | ||
|
|
3e7bbab5b0 | ||
|
|
e04965eb35 | ||
|
|
f2c5e82665 | ||
|
|
4c41f0976d | ||
|
|
db333184cc | ||
|
|
a5963a2333 | ||
|
|
0fe81d6164 | ||
|
|
3ad9f5dcac | ||
|
|
d39abe40c6 | ||
|
|
6f8ca72ccf | ||
|
|
2ae32ee19f | ||
|
|
2ac89c5db3 | ||
|
|
b0db1dd8bc | ||
|
|
a603be6e22 | ||
|
|
7ad23a76de | ||
|
|
4481646153 | ||
|
|
5d2c9b557f | ||
|
|
eb69ae48f1 | ||
|
|
c7479b2f00 | ||
|
|
8ae7a8d338 | ||
|
|
7573c762c0 | ||
|
|
6d503a1a15 | ||
|
|
94d3ea5da8 | ||
|
|
e998006e6c | ||
|
|
234fc67ab2 | ||
|
|
37bb507359 | ||
|
|
1237056d59 | ||
|
|
7174702f7b | ||
|
|
3b721673c8 | ||
|
|
b64f0d2dd5 | ||
|
|
7738dc004a | ||
|
|
5c5563a917 | ||
|
|
7d08939a18 | ||
|
|
fabc50aa62 | ||
|
|
4c0615d427 | ||
|
|
b4dd729150 | ||
|
|
f336089755 | ||
|
|
4fc6285081 | ||
|
|
0cf3e33fe6 | ||
|
|
ab424aee0d | ||
|
|
9fb55d479d | ||
|
|
c8fb01bbab | ||
|
|
f008c4074d | ||
|
|
d66922c99a | ||
|
|
c7689199cc | ||
|
|
9a8b6bfa09 | ||
|
|
45c8141137 | ||
|
|
4e7b3e9ceb | ||
|
|
dfcc9c32a8 | ||
|
|
a3d891aa2f | ||
|
|
088ae4a26a | ||
|
|
f425b5a906 | ||
|
|
a4ddaeff61 | ||
|
|
8932224b57 | ||
|
|
ffe2203ba5 | ||
|
|
987819cc7c | ||
|
|
974a017e5a | ||
|
|
594a59347e | ||
|
|
b0970c6653 | ||
|
|
d8017dda62 | ||
|
|
a8c3e88ec9 | ||
|
|
7bf31a7fc7 | ||
|
|
deb3310e3f | ||
|
|
5450b48eb3 | ||
|
|
70a94cff9b | ||
|
|
ff31259092 | ||
|
|
8b4f1849f6 | ||
|
|
110ffad7d7 | ||
|
|
62a0670b4d | ||
|
|
7a1142fdd5 | ||
|
|
5def3cde99 | ||
|
|
075fa877ed | ||
|
|
6ffb46a65a | ||
|
|
ac5f34ea5f | ||
|
|
9975f377d0 | ||
|
|
76b4606d17 | ||
|
|
1244e0ee3e | ||
|
|
c56b16e2e6 | ||
|
|
831fd420ff | ||
|
|
2125f92bd8 | ||
|
|
42c9383dda | ||
|
|
f5eab006f5 | ||
|
|
5a2c664fe1 | ||
|
|
d7cbb7c9bd | ||
|
|
8afe30bfd4 | ||
|
|
24b67fc430 | ||
|
|
d01a00ad13 | ||
|
|
ac250eba39 | ||
|
|
778a2d6565 | ||
|
|
0b9a7b78df | ||
|
|
6b93e839fa | ||
|
|
7e6040ce7b | ||
|
|
d4b2057c41 | ||
|
|
bc04603fb4 | ||
|
|
ba2a23e039 | ||
|
|
6bfe203a34 | ||
|
|
8c6f34ebc7 | ||
|
|
18e20123c2 | ||
|
|
dc44f6c921 | ||
|
|
a19fd42a5b | ||
|
|
206fd62f70 | ||
|
|
6123503ee8 | ||
|
|
b751c6f5f9 | ||
|
|
d1ef09ba0d | ||
|
|
d073642203 | ||
|
|
9bb48fbf13 | ||
|
|
bf8f1175ce | ||
|
|
4bfcc40571 | ||
|
|
892d035d7a | ||
|
|
d4019e6dea | ||
|
|
3ce94f443a | ||
|
|
b863a9773a | ||
|
|
489d7d12cb | ||
|
|
63c7ea7809 | ||
|
|
a6a019e9fd | ||
|
|
dbd1c81c90 | ||
|
|
2b42b40c0a | ||
|
|
4230674c2c | ||
|
|
b0955270ba | ||
|
|
a194f752bb | ||
|
|
4180ae0c6b | ||
|
|
3227541f34 | ||
|
|
e949da3830 | ||
|
|
e13d86b1d0 | ||
|
|
f7577c43e0 | ||
|
|
f3531f41ec | ||
|
|
d9d37c68fc | ||
|
|
3bb887763f | ||
|
|
c65504ee9f | ||
|
|
373fdb0dc2 | ||
|
|
825d0da532 | ||
|
|
2efdada4c0 | ||
|
|
01afe9006a | ||
|
|
da9edb2116 | ||
|
|
d5c886d718 | ||
|
|
e99351407e | ||
|
|
d9184fa350 | ||
|
|
e4af1e3f18 | ||
|
|
9ca4f3fa62 | ||
|
|
20e3f22f3f | ||
|
|
bca9e195c9 | ||
|
|
8b1765cb03 | ||
|
|
0605cf1fd5 | ||
|
|
07d6487875 | ||
|
|
6222aa5899 | ||
|
|
47f61b491e | ||
|
|
42818c51c1 | ||
|
|
a0fee4184e | ||
|
|
470a70f951 | ||
|
|
2d2b3a6d48 | ||
|
|
6f40dd387e | ||
|
|
2445336ec0 | ||
|
|
41d181e02f | ||
|
|
ff158c99cd | ||
|
|
c1cdcd016a | ||
|
|
fcb9092f8a | ||
|
|
ba2bd0cc8a | ||
|
|
f224ce6b6a | ||
|
|
c74678e9ef | ||
|
|
1b1770ff29 | ||
|
|
41d3a63c42 | ||
|
|
52240aa13b | ||
|
|
95d7d276d7 | ||
|
|
18c520ce2f | ||
|
|
fb65cb222a | ||
|
|
c4f028fd2c | ||
|
|
b85452f5c6 | ||
|
|
6546ee3db2 | ||
|
|
f0e798c052 | ||
|
|
44424f2570 | ||
|
|
72df520300 | ||
|
|
25c783051d | ||
|
|
264bc31af6 | ||
|
|
e0bdb8fe3c | ||
|
|
4d7c93f7dc | ||
|
|
dd54075c52 | ||
|
|
ff08b6327d | ||
|
|
76392a9668 | ||
|
|
f47faabf7c | ||
|
|
120e280fc8 | ||
|
|
6ae80fd881 | ||
|
|
be70e7074a | ||
|
|
65841a8c6b | ||
|
|
82374fc806 | ||
|
|
004400d7ea | ||
|
|
d54e169a14 | ||
|
|
31c35647dd | ||
|
|
204ee159d6 | ||
|
|
4fe1f8ced9 | ||
|
|
c26e6f77f5 | ||
|
|
e2b59900e5 | ||
|
|
c9cde11871 | ||
|
|
ea0c75ccf3 | ||
|
|
13a64bd63a | ||
|
|
c3b23271d4 | ||
|
|
d047c600c2 | ||
|
|
b6cae4a538 | ||
|
|
1eed48456c | ||
|
|
20834e812e | ||
|
|
d1e5b11598 | ||
|
|
c7af346daf | ||
|
|
3975edeaf3 | ||
|
|
119c8d7fd7 | ||
|
|
3a96c28b2d | ||
|
|
6905853aa3 | ||
|
|
3f1a952e44 | ||
|
|
405ee4f6d7 | ||
|
|
34e0eaefd8 | ||
|
|
6d4b617409 | ||
|
|
4195bde081 | ||
|
|
925f3b916d | ||
|
|
770621ba41 | ||
|
|
a00e5d0dbc | ||
|
|
0d6d803df3 | ||
|
|
396e4b922e | ||
|
|
14ecb57210 | ||
|
|
14369836f6 | ||
|
|
70e5aaaad5 | ||
|
|
366e668c13 | ||
|
|
d0e842ede3 | ||
|
|
a0c1f71162 | ||
|
|
7ef159448e | ||
|
|
b4e6ce6e78 | ||
|
|
abe75869b1 | ||
|
|
246e9ad7c5 |
+2
-1
@@ -31,4 +31,5 @@ tmp-*
|
||||
.env
|
||||
*.env
|
||||
.storybook
|
||||
.stories
|
||||
.stories
|
||||
**/lexicons/**/*.json
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
run: |
|
||||
set +e
|
||||
export NODE_ENV=production
|
||||
timeout --preserve-status 10s node node_modules/.bin/tsx src/backend/index.ts
|
||||
timeout --preserve-status 10s node src/backend/index.ts
|
||||
exitcode="$?"
|
||||
if [[ "$exitcode" -eq 143 ]] || [[ "$exitcode" -eq 137 ]]; then
|
||||
echo "App stayed up long enough and exited with expected status"
|
||||
|
||||
+7
-5
@@ -153,8 +153,10 @@ tmp-*
|
||||
storybook-static
|
||||
|
||||
*.db
|
||||
*.db.*
|
||||
*.db-*lib
|
||||
*.db
|
||||
*.db.*
|
||||
*.db-*lib
|
||||
*.db*
|
||||
|
||||
**/lexicons/**/*.json
|
||||
|
||||
bun.lock
|
||||
.DS_Store
|
||||
**/.DS_Store
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"reporter": "dot",
|
||||
"extension": "ts",
|
||||
"import": "tsx/esm",
|
||||
"spec": "./src/backend/tests/**/*.test.ts",
|
||||
"file": [
|
||||
"./src/backend/tests/setup.ts"
|
||||
|
||||
@@ -12,12 +12,14 @@ export default defineMain({
|
||||
// "@storybook/addon-vitest",
|
||||
"@storybook/addon-a11y",
|
||||
"@storybook/addon-docs",
|
||||
'storybook-addon-remix-react-router'
|
||||
// "@storybook/addon-onboarding"
|
||||
],
|
||||
features: {
|
||||
// https://github.com/storybookjs/storybook/discussions/33279
|
||||
sidebarOnboardingChecklist: false,
|
||||
},
|
||||
staticDirs: ['../public'],
|
||||
"framework": "@storybook/react-vite",
|
||||
viteFinal: async (config) => {
|
||||
return mergeConfig(config, {
|
||||
|
||||
@@ -2,5 +2,5 @@ import { addons } from 'storybook/manager-api';
|
||||
import { themes } from 'storybook/theming';
|
||||
|
||||
addons.setConfig({
|
||||
theme: themes.dark,
|
||||
theme: themes.normal,
|
||||
});
|
||||
+10
-1
@@ -3,8 +3,17 @@ import addonA11y from "@storybook/addon-a11y";
|
||||
import { definePreview } from "@storybook/react-vite";
|
||||
|
||||
import { themes } from 'storybook/theming';
|
||||
import { initialize, mswLoader, getWorker } from 'msw-storybook-addon';
|
||||
|
||||
// https://github.com/mswjs/msw-storybook-addon/issues/82#issuecomment-3894302575
|
||||
try {
|
||||
getWorker();
|
||||
} catch (_) {
|
||||
initialize();
|
||||
}
|
||||
|
||||
export default definePreview({
|
||||
loaders: [mswLoader],
|
||||
parameters: {
|
||||
controls: {
|
||||
matchers: {
|
||||
@@ -20,7 +29,7 @@ export default definePreview({
|
||||
test: 'todo'
|
||||
},
|
||||
docs: {
|
||||
theme: themes.dark,
|
||||
theme: themes.normal,
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
Vendored
+5
-20
@@ -20,7 +20,7 @@
|
||||
"request": "launch",
|
||||
// Debug app in VSCode
|
||||
"program": "${workspaceFolder}/src/backend/index.ts",
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/tsx",
|
||||
"runtimeExecutable": "node",
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"skipFiles": [
|
||||
@@ -35,7 +35,7 @@
|
||||
"request": "launch",
|
||||
// Debug app in VSCode
|
||||
"program": "${workspaceFolder}/src/backend/index.ts",
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/tsx",
|
||||
"runtimeExecutable": "node",
|
||||
"console": "integratedTerminal",
|
||||
"envFile": "${workspaceFolder}/.env",
|
||||
"env": {
|
||||
@@ -48,28 +48,13 @@
|
||||
"${workspaceFolder}/node_modules/**",
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "tsx",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
// Debug current file in VSCode
|
||||
"program": "${file}",
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/tsx",
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**",
|
||||
// Ignore all dependencies (optional)
|
||||
"${workspaceFolder}/node_modules/**",
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "schema",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
// Debug app in VSCode
|
||||
"program": "${workspaceFolder}/src/backend/utils/SchemaStaticUtil.ts",
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/tsx",
|
||||
"runtimeExecutable": "node",
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"skipFiles": [
|
||||
@@ -96,7 +81,7 @@
|
||||
"--recursive",
|
||||
"${workspaceFolder}/src/backend/tests/**/*.test.ts"
|
||||
],
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/tsx",
|
||||
"runtimeExecutable": "node",
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"name": "Mocha Tests",
|
||||
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
|
||||
@@ -120,7 +105,7 @@
|
||||
"--config", "${workspaceRoot}/.mocharc.json",
|
||||
"${file}"
|
||||
],
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/tsx",
|
||||
"runtimeExecutable": "node",
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"name": "Mocha Test on File",
|
||||
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
|
||||
|
||||
Vendored
+4
-3
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"mochaExplorer.require": "tsx/esm",
|
||||
"mochaExplorer.timeout": 1200000,
|
||||
"mochaExplorer.exit": true,
|
||||
"search.exclude": {
|
||||
@@ -11,7 +10,9 @@
|
||||
"docsite/**"
|
||||
],
|
||||
"files.associations": {
|
||||
"*.css": "tailwindcss"
|
||||
"*.css": "tailwindcss",
|
||||
"**/config/*.json": "jsonc"
|
||||
},
|
||||
"tailwindCSS.experimental.configFile": "src/client/index.css"
|
||||
"tailwindCSS.experimental.configFile": "src/client/index.css",
|
||||
|
||||
}
|
||||
@@ -56,6 +56,7 @@ RUN npm install -g concurrently
|
||||
ARG data_dir=/config
|
||||
VOLUME $data_dir
|
||||
ENV CONFIG_DIR=$data_dir
|
||||
ENV DATA_DIR=$data_dir
|
||||
|
||||
COPY docker/root /
|
||||
|
||||
@@ -118,6 +119,8 @@ RUN npm ci --omit=dev --no-audit \
|
||||
&& npm uninstall ts-json-schema-generator \
|
||||
&& npm cache clean --force \
|
||||
&& chown -R abc:abc node_modules \
|
||||
## peerDependency of validbot and can(?) be removed for production
|
||||
&& rm -r node_modules/typescript \
|
||||
&& npx @usex/prune-mod -w \
|
||||
&& rm -rf /root/.cache
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ alt="multi-scrobbler logo" width="180" height="180">
|
||||
A dockerized app that monitors your music listening activity from *everywhere* and scrobbles it *anywhere*.
|
||||
|
||||
* Supports scrobbling from many [**Sources**](https://docs.multi-scrobbler.app/configuration/sources)
|
||||
* [Apple Music](https://docs.multi-scrobbler.app/configuration/sources/applemusic/?configType=file)
|
||||
* [Azuracast](https://docs.multi-scrobbler.app/configuration/sources/azuracast)
|
||||
* [Deezer](https://docs.multi-scrobbler.app/configuration/sources/deezer)
|
||||
* [Google Cast (Chromecast)](https://docs.multi-scrobbler.app/configuration/sources/google-cast)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
[
|
||||
{
|
||||
"id": "myAppleMusic",
|
||||
"name": "My Apple Music",
|
||||
"enable": true,
|
||||
"clients": [],
|
||||
"data": {
|
||||
// either key or token needs to be provided
|
||||
"key": {
|
||||
"id": "2HPSNJZ88N",
|
||||
"teamId": "SN6YASW8G4",
|
||||
"p8": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEG...-----END PRIVATE KEY-----"
|
||||
},
|
||||
"token": "your-jwt-token-here",
|
||||
"mediaUserToken": "your-media-user-token-here",
|
||||
|
||||
// optional: Origin header for API requests (required when using a browser token)
|
||||
"origin": "https://music.apple.com",
|
||||
|
||||
// optional: polling interval in seconds (default 60)
|
||||
"interval": 60
|
||||
},
|
||||
"options": {
|
||||
"logDiff": true,
|
||||
"recoverUnchangedTopHistory": true,
|
||||
"normalizeAlbum": true
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -2,7 +2,8 @@
|
||||
{
|
||||
"type": "azuracast",
|
||||
"enable": true,
|
||||
"name": "azura",
|
||||
"name": "Cool Station",
|
||||
"id": "myAzura",
|
||||
"data": {
|
||||
"url": "ws://192.168.0.101",
|
||||
"station": "my-station-name",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "MyCast",
|
||||
"name": "My Cast",
|
||||
"id": "myCast",
|
||||
"data": {
|
||||
"blacklistedDevices": [],
|
||||
"whitelistedDevices": [],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "DeezerARL",
|
||||
"name": "Family Account",
|
||||
"id": "myDeezerId",
|
||||
"enable": true,
|
||||
"clients": [],
|
||||
"data": {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
[
|
||||
{
|
||||
"name": "MS",
|
||||
"name": "Desktop",
|
||||
"enable": true,
|
||||
"id": "myDiscordLocal",
|
||||
"data": {
|
||||
"applicationId": "8190211179716453570",
|
||||
"ipcLocations": ["192.168.0.105:6655"]
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
[
|
||||
{
|
||||
"name": "MS",
|
||||
"enable": true,
|
||||
"data": {
|
||||
"token": "Ma7XBvzRERK3gQdkX4dkb3OQ.6aaGmJ.rvFiiVrzG4VXHHL3LyhZYhpWdG3Ph_if9aKz5E",
|
||||
"applicationId": "8190211179716453570"
|
||||
}
|
||||
{
|
||||
"name": "Discord",
|
||||
"enable": true,
|
||||
"id": "myDiscordRemote",
|
||||
"data": {
|
||||
"token": "Ma7XBvzRERK3gQdkX4dkb3OQ.6aaGmJ.rvFiiVrzG4VXHHL3LyhZYhpWdG3Ph_if9aKz5E",
|
||||
"applicationId": "8190211179716453570"
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
]
|
||||
@@ -1,7 +1,8 @@
|
||||
[
|
||||
{
|
||||
"name": "myLfm",
|
||||
"name": "LFM For App",
|
||||
"enable": true,
|
||||
"id": "myLfmApp",
|
||||
"data": {
|
||||
"slug": "mySlug"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "myLz",
|
||||
"name": "LZ for Navidrome",
|
||||
"id": "myLzNavidrome",
|
||||
"enable": true,
|
||||
"data": {
|
||||
"token": "myToken"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
[
|
||||
{
|
||||
"name": "myIcy",
|
||||
"name": "Station 1",
|
||||
"enable": true,
|
||||
"id": "myIceStation1",
|
||||
"data": {
|
||||
"url": "http://myIcecast.com/radio"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
[
|
||||
{
|
||||
"name": "MyJellyfin",
|
||||
"name": "Cool JF Server",
|
||||
"enable": true,
|
||||
"id": "myJellyfin",
|
||||
"clients": [],
|
||||
"data": {
|
||||
"url": "http://localhost:8096",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "MyJriver",
|
||||
"name": "My JRiver Server",
|
||||
"id": "myRiver",
|
||||
"enable": true,
|
||||
"data": {
|
||||
"url": "0.0.0.0",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "MyKodi",
|
||||
"name": "My Kodi Server",
|
||||
"id": "myKodi",
|
||||
"enable": true,
|
||||
"data": {
|
||||
"url": "http://localhost:8080/jsonrpc",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "koito-client",
|
||||
{
|
||||
"name": "Koito Client A",
|
||||
"id": "myKoitoClient",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"token": "029b081ba-9156-4pe7-88e5-3be671f5ea2b",
|
||||
@@ -9,7 +10,8 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "koito-source",
|
||||
"name": "Koito Source B",
|
||||
"id": "myKoitoSource",
|
||||
"configureAs": "source",
|
||||
"data": {
|
||||
"token": "029b081ba-9156-4pe7-88e5-3be671f5ea2b",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "myLastFmClient",
|
||||
"name": "Foxx LFM Client",
|
||||
"id": "myLastFmClient",
|
||||
"enable": true,
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
@@ -10,7 +11,8 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "myLastFmSource",
|
||||
"name": "Foxx LFM Source",
|
||||
"id": "myLastFmSource",
|
||||
"enable": true,
|
||||
"configureAs": "source",
|
||||
"data": {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "myLibreFmClient",
|
||||
"name": "Foxx Libre Client",
|
||||
"id": "myLibreFmClient",
|
||||
"enable": true,
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
@@ -10,7 +11,8 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "myLibreFmSource",
|
||||
"name": "Foxx Libre Source",
|
||||
"id": "myLibreFmSource",
|
||||
"enable": true,
|
||||
"configureAs": "source",
|
||||
"data": {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "brainzClient",
|
||||
"name": "Foxx LZ Client",
|
||||
"id": "brainzClient",
|
||||
"enable": true,
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
@@ -10,7 +11,8 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "brainzSource",
|
||||
"name": "Foxx LZ Source",
|
||||
"id": "brainzSource",
|
||||
"enable": true,
|
||||
"configureAs": "source",
|
||||
"data": {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "myMaloja-client",
|
||||
"name": "Maloja Client",
|
||||
"id": "myMalojaClient",
|
||||
"enable": true,
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
@@ -9,7 +10,8 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "myMaloja-source",
|
||||
"name": "Maloja Source",
|
||||
"id": "myMalojaSource",
|
||||
"enable": true,
|
||||
"configureAs": "source",
|
||||
"data": {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "MyMopidy",
|
||||
"name": "Mopidy Desktop",
|
||||
"id": "MyMopidy",
|
||||
"enable": true,
|
||||
"data": {
|
||||
"url": "localhost",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
[
|
||||
{
|
||||
"enable": true,
|
||||
"name": "MyMPD",
|
||||
"id": "myMPD",
|
||||
"name": "Desktop",
|
||||
"data": {
|
||||
"url": "192.168.0.100:6600",
|
||||
"password": "MY_PASSWORD"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "ubuntu",
|
||||
"name": "Desktop",
|
||||
"id": "myMprisUbuntu",
|
||||
"enable": true,
|
||||
"data": {
|
||||
"whitelist": ["vlc", "mpd"],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "myYamaha",
|
||||
"name": "Livingroom Reciever",
|
||||
"id": "myYamaha",
|
||||
"enable": true,
|
||||
"data": {
|
||||
"url": "192.168.0.101"
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
{
|
||||
"type": "musikcube",
|
||||
"enable": true,
|
||||
"name": "musikcube",
|
||||
"name": "Desktop",
|
||||
"id": "myMusikcube",
|
||||
"data": {
|
||||
"url": "ws://localhost:7905",
|
||||
"password": "MY_PASSWORD"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "MyPlexApi",
|
||||
"name": "Foxx Plex",
|
||||
"id": "MyPlexApi",
|
||||
"enable": true,
|
||||
"clients": [],
|
||||
"data": {
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
[
|
||||
{
|
||||
"name": "myRockskyClient",
|
||||
"id": "myRockskyClient",
|
||||
"name": "FoxxMD Rocksky Client",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"key": "7cdr86vis3mpq5b1pi01md0hkm7ykzzxjy81r",
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkaWQiOiJkaWQ6cGxjOng0NjVwZmtyeW5zaDZ...",
|
||||
"handle": "alice.bsky.social"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "myRockskySource",
|
||||
"id": "myRockskySource",
|
||||
"name": "FoxxMD Rocksky Source",
|
||||
"configureAs": "source",
|
||||
"data": {
|
||||
"key": "e4xwktm7jxm607x8el1fcda30eu14dzb64h3j",
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkaWQiOiJkaWQ6cGxjOng0NjVwZmtyeW5zaDZ...",
|
||||
"handle": "alice.bsky.social"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
[
|
||||
{
|
||||
"enable": true,
|
||||
"name": "MySonos",
|
||||
"id": "MySonos",
|
||||
"name": "Boom 2",
|
||||
"data": {
|
||||
"host": "192.168.0.150",
|
||||
"devicesAllow": ["move","beam"],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "MySpotify",
|
||||
"id": "MySpotify",
|
||||
"name": "Foxx Spotify",
|
||||
"enable": true,
|
||||
"clients": [],
|
||||
"data": {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "MySubsonic",
|
||||
"id": "mySubsonic",
|
||||
"name": "Airsonic Server",
|
||||
"enable": true,
|
||||
"data": {
|
||||
"url": "http://localhost:4040/airsonic",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "myTeal",
|
||||
"id": "myTealClient",
|
||||
"name": "Foxx Teal Client",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"identifier": "alice.bsky.social",
|
||||
@@ -8,7 +9,8 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "myTealSource",
|
||||
"id": "myTealSource",
|
||||
"name": "Foxx Teal Source",
|
||||
"configureAs": "source",
|
||||
"data": {
|
||||
"identifier": "alice.bsky.social",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
[
|
||||
{
|
||||
"enable": true,
|
||||
"name": "MyVlc",
|
||||
"id": "myVlc",
|
||||
"name": "Desktop",
|
||||
"data": {
|
||||
"url": "localhost:8080",
|
||||
"password": "changeme"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "MyWebScrobbler",
|
||||
"id": "MyWebScrobbler",
|
||||
"name": "Firefox Laptop",
|
||||
"data": {
|
||||
"slug": "MyOptionalSlug",
|
||||
"whitelist": [],
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[
|
||||
{
|
||||
"id": "myYandex",
|
||||
"name": "Yandex Music",
|
||||
"type": "ymbridge",
|
||||
"data": {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "MyYTMusic",
|
||||
"id": "myYtMusic",
|
||||
"name": "Foxx YT Music",
|
||||
"enable": true,
|
||||
"clients": [],
|
||||
"data": {
|
||||
|
||||
@@ -1,33 +1,19 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
|
||||
# used https://github.com/linuxserver/docker-plex as a template
|
||||
|
||||
POPULATE_EXAMPLES=false
|
||||
|
||||
echo "-------------------------------------"
|
||||
echo -e "Setting up multi-scrobbler config directory based on CONFIG_DIR env: ${CONFIG_DIR}\n"
|
||||
echo -e "Verifying multi-scrobbler directory permissions based on CONFIG_DIR env: ${CONFIG_DIR} and DATA_DIR env: ${DATA_DIR}\n"
|
||||
|
||||
# make config folder if it does not exist
|
||||
if [ ! -d "${CONFIG_DIR}" ]; then
|
||||
echo "Directory does not exist! Creating..."
|
||||
POPULATE_EXAMPLES=true
|
||||
mkdir -p "${CONFIG_DIR}"
|
||||
else
|
||||
if [ "$(ls -A ${CONFIG_DIR})" ]; then
|
||||
echo "Directory is not empty, not creating examples."
|
||||
else
|
||||
POPULATE_EXAMPLES=true
|
||||
fi
|
||||
if [ -d "${CONFIG_DIR}" ]; then
|
||||
echo "chown'ing config directory to ensure correct permissions."
|
||||
chown -R abc:abc "${CONFIG_DIR}"
|
||||
echo "Done!"
|
||||
fi
|
||||
|
||||
# add example configs
|
||||
if [ "$POPULATE_EXAMPLES" = true ]; then
|
||||
echo "Directory is empty, adding examples..."
|
||||
cp -r /app/config/. "${CONFIG_DIR}"/
|
||||
if [ -d "${DATA_DIR}" ]; then
|
||||
echo "chown'ing data directory to ensure correct permissions."
|
||||
chown -R abc:abc "${DATA_DIR}"
|
||||
echo "Done!"
|
||||
fi
|
||||
|
||||
# permissions
|
||||
echo "chown'ing directory to ensure correct permissions."
|
||||
chown -R abc:abc "${CONFIG_DIR}"
|
||||
echo "Done!"
|
||||
echo -e "-------------------------------------\n"
|
||||
|
||||
@@ -7,4 +7,4 @@ echo -e "\nmulti-scrobbler is starting!"
|
||||
export NODE_ENV=production
|
||||
cd /app || exit
|
||||
exec \
|
||||
s6-setuidgid abc /usr/bin/node $NODE_ARGS /app/node_modules/.bin/tsx /app/src/backend/index.ts
|
||||
s6-setuidgid abc /usr/bin/node $NODE_ARGS /app/src/backend/index.ts
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
<Tabs groupId="configType" queryString>
|
||||
<TabItem value="file" label="File">
|
||||
|
||||
Using [File Config](/configuration/?configType=file#configuration-types)
|
||||
|
||||
```json5 title="CONFIG_DIR/jellyin.json"
|
||||
[
|
||||
{
|
||||
"id": "fredJf",
|
||||
"name": "Freds Jellyfin",
|
||||
"clients": ["fredLFM"],
|
||||
"data": {
|
||||
"url": "https://jellyfin.fred.com",
|
||||
"user": "Fred",
|
||||
"apiKey": "c9fae8756fbf481ebd9c5bb56bd6540c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "maryJf",
|
||||
"name": "Marys Jellyfin",
|
||||
"clients": ["maryLFM"],
|
||||
"data": {
|
||||
"url": "https://jellyfin.mary.com",
|
||||
"user": "Mary",
|
||||
"apiKey": "26fe9c5dc6374542adc4f4d5e8352838"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
```json5 title="CONFIG_DIR/plex.json"
|
||||
[
|
||||
{
|
||||
"id": "foxxPlex",
|
||||
"name": "Foxx Plex",
|
||||
"clients": ["foxxKoi"],
|
||||
"data": {
|
||||
"token": "JtRnwQWD__XMJF8sT3jc",
|
||||
"url": "192.168.0.233:32400"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
```json5 title="CONFIG_DIR/lastfm.json"
|
||||
[
|
||||
{
|
||||
"id": "maryLFM",
|
||||
"name": "Marys Lastfm",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"apiKey": "maryApiKey",
|
||||
"secret": "marySecret",
|
||||
"redirectUri": "http://localhost:9078/lastfm/callback"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "fredLFM",
|
||||
"name": "Freds Lastfm",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"apiKey": "fredApiKey",
|
||||
"secret": "fredSecret",
|
||||
"redirectUri": "http://localhost:9078/lastfm/callback"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
```json5 title="CONFIG_DIR/koito.json"
|
||||
[
|
||||
{
|
||||
"id": "foxxKoi",
|
||||
"name": "Foxx Koito",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"token": "029b081ba-9156-4pe7-88e5-3be671f5ea2b",
|
||||
"username": "admin",
|
||||
"url": "http://192.168.0.100:4110"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="aio" label="AIO">
|
||||
|
||||
Using [AIO Config](/configuration/?configType=aio#configuration-types)
|
||||
|
||||
```json5 title="CONFIG_DIR/config.json"
|
||||
{
|
||||
"sources": [
|
||||
{
|
||||
"type": "jellyfin",
|
||||
"id": "fredJf",
|
||||
"name": "Freds Jellyfin",
|
||||
"clients": [
|
||||
"fredLFM"
|
||||
],
|
||||
"data": {
|
||||
"url": "https://jellyfin.fred.com",
|
||||
"user": "Fred",
|
||||
"apiKey": "c9fae8756fbf481ebd9c5bb56bd6540c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "jellyfin",
|
||||
"id": "maryJf",
|
||||
"name": "Marys Jellyfin",
|
||||
"clients": [
|
||||
"maryLFM"
|
||||
],
|
||||
"data": {
|
||||
"url": "https://jellyfin.mary.com",
|
||||
"user": "Mary",
|
||||
"apiKey": "26fe9c5dc6374542adc4f4d5e8352838"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "plex",
|
||||
"id": "foxxPlex",
|
||||
"name": "Foxx Plex",
|
||||
"clients": [
|
||||
"foxxKoi"
|
||||
],
|
||||
"data": {
|
||||
"token": "JtRnwQWD__XMJF8sT3jc",
|
||||
"url": "192.168.0.233:32400",
|
||||
"usersallow": [
|
||||
"foxx"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"clients": [
|
||||
{
|
||||
"type": "lastfm",
|
||||
"id": "maryLFM",
|
||||
"name": "Marys Lastfm",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"apiKey": "maryApiKey",
|
||||
"secret": "marySecret",
|
||||
"redirectUri": "http://localhost:9078/lastfm/callback"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "lastfm",
|
||||
"id": "fredLFM",
|
||||
"name": "Freds Lastfm",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"apiKey": "fredApiKey",
|
||||
"secret": "fredSecret",
|
||||
"redirectUri": "http://localhost:9078/lastfm/callback"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "koito",
|
||||
"id": "foxxKoi",
|
||||
"name": "Foxx Koito",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"token": "029b081ba-9156-4pe7-88e5-3be671f5ea2b",
|
||||
"username": "admin",
|
||||
"url": "http://192.168.0.100:4110"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
@@ -0,0 +1,196 @@
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
<Tabs groupId="configType" queryString>
|
||||
<TabItem value="env" label="ENV">
|
||||
|
||||
Using [ENV Config](/configuration/?configType=env#configuration-types)
|
||||
|
||||
```yaml title="compose.yaml"
|
||||
services:
|
||||
multi-scrobbler:
|
||||
image: foxxmd/multi-scrobbler
|
||||
container_name: multi-scrobbler
|
||||
environment:
|
||||
- TZ=Etc/GMT # Specify timezone from TZ Database name found here https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
# spotify
|
||||
- SPOTIFY_ID=foxxSpotify
|
||||
- SPOTIFY_CLIENT_ID=H2UvGv8okrFlv2OUoc1lAtK7Xe5EGDgs
|
||||
- SPOTIFY_CLIENT_SECRET=HvoFE5Ce2hdblNE6vaFBEx6dRVcDdo43
|
||||
- SPOTIFY_REDIRECT_URI=https://multiscrobbler.example.com/callback
|
||||
|
||||
# navidrome listenbrainz endpoint
|
||||
- LZE_ID=myLzNavidrome
|
||||
- LZE_TOKEN=myToken
|
||||
|
||||
# plex
|
||||
- PLEX_ID=foxxPlex
|
||||
- PLEX_URL=192.168.0.233:32400
|
||||
- PLEX_TOKEN=JtRnwQWD__XMJF8sT3jc
|
||||
- PLEX_USERS_ALLOW=foxx
|
||||
- PLEX_LIBRARIES_ALLOW=oldies
|
||||
|
||||
# lastfm
|
||||
- LASTFM_ID=myLFM
|
||||
- LASTFM_API_KEY=myApiKey
|
||||
- LASTFM_SECRET=mySecret
|
||||
- LASTFM_REDIRECT_URI=https://multiscrobbler.example.com/lastfm/callback
|
||||
|
||||
# koito
|
||||
- KOITO_ID=foxxKoi
|
||||
- KOITO_TOKEN=029b081ba-9156-4pe7-88e5-3be671f5ea2b
|
||||
- KOITO_USER=admin
|
||||
- KOITO_URL=http://192.168.0.100:4110
|
||||
|
||||
volumes:
|
||||
- "./config:/config"
|
||||
ports:
|
||||
- "9078:9078"
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="file" label="File">
|
||||
|
||||
Using [File Config](/configuration/?configType=file#configuration-types)
|
||||
|
||||
```json5 title="CONFIG_DIR/spotify.json"
|
||||
[
|
||||
{
|
||||
"id": "foxxSpotify",
|
||||
"name": "Foxx Spotify",
|
||||
"data": {
|
||||
"clientId": "a89cba1569901a0671d5a9875fed4be1",
|
||||
"clientSecret": "ec42e09d5ae0ee0f0816ca151008412a",
|
||||
"redirectUri": "http://localhost:9078/callback",
|
||||
"interval": 60
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
```json5 title="CONFIG_DIR/endpointlz.json"
|
||||
[
|
||||
{
|
||||
"id": "myLzNavidrome",
|
||||
"name": "LZ for Navidrome",
|
||||
"data": {
|
||||
"token": "myToken"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
```json5 title="CONFIG_DIR/plex.json"
|
||||
[
|
||||
{
|
||||
"id": "foxxPlex",
|
||||
"name": "Foxx Plex",
|
||||
"data": {
|
||||
"token": "JtRnwQWD__XMJF8sT3jc",
|
||||
"url": "192.168.0.233:32400",
|
||||
"usersallow": ["foxx"],
|
||||
"librariesAllow": ["oldies"],
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
```json5 title="CONFIG_DIR/lastfm.json"
|
||||
[
|
||||
{
|
||||
"id": "myLFM",
|
||||
"name": "Old Lastfm",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"apiKey": "myApiKey",
|
||||
"secret": "mySecret",
|
||||
"redirectUri": "http://localhost:9078/lastfm/callback"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
```json5 title="CONFIG_DIR/koito.json"
|
||||
[
|
||||
{
|
||||
"id": "foxxKoi",
|
||||
"name": "Foxx Koito",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"token": "029b081ba-9156-4pe7-88e5-3be671f5ea2b",
|
||||
"username": "admin",
|
||||
"url": "http://192.168.0.100:4110"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="aio" label="AIO">
|
||||
|
||||
Using [AIO Config](/configuration/?configType=aio#configuration-types)
|
||||
|
||||
```json5 title="CONFIG_DIR/config.json"
|
||||
{
|
||||
"sources": [
|
||||
{
|
||||
"type": "spotify",
|
||||
"id": "foxxSpotify",
|
||||
"name": "Foxx Spotify",
|
||||
"data": {
|
||||
"clientId": "a89cba1569901a0671d5a9875fed4be1",
|
||||
"clientSecret": "ec42e09d5ae0ee0f0816ca151008412a",
|
||||
"redirectUri": "http://localhost:9078/callback",
|
||||
"interval": 60
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "endpointlz",
|
||||
"name": "LZ for Navidrome",
|
||||
"id": "myLzNavidrome",
|
||||
"data": {
|
||||
"token": "myToken"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "plex",
|
||||
"id": "foxxPlex",
|
||||
"name": "Foxx Plex",
|
||||
"data": {
|
||||
"token": "JtRnwQWD__XMJF8sT3jc",
|
||||
"url": "192.168.0.233:32400",
|
||||
"usersallow": ["foxx"],
|
||||
"librariesAllow": ["oldies"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"clients": [
|
||||
{
|
||||
"type": "lastfm",
|
||||
"id": "myLFM",
|
||||
"name": "Old Lastfm",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"apiKey": "myApiKey",
|
||||
"secret": "mySecret",
|
||||
"redirectUri": "http://localhost:9078/lastfm/callback"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "koito",
|
||||
"id": "foxxKoi",
|
||||
"name": "Foxx Koito",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"token": "029b081ba-9156-4pe7-88e5-3be671f5ea2b",
|
||||
"username": "admin",
|
||||
"url": "http://192.168.0.100:4110"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
@@ -0,0 +1,226 @@
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
<Tabs groupId="configType" queryString>
|
||||
<TabItem value="env" label="ENV">
|
||||
|
||||
Using [ENV Config](/configuration/?configType=env#configuration-types)
|
||||
|
||||
```yaml title="compose.yaml"
|
||||
services:
|
||||
multi-scrobbler:
|
||||
image: foxxmd/multi-scrobbler
|
||||
container_name: multi-scrobbler
|
||||
environment:
|
||||
- TZ=Etc/GMT # Specify timezone from TZ Database name found here https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
|
||||
# required setup for musicbrainz
|
||||
- MB_CONTACT=contact@mydomain.com # a real email
|
||||
# searches with sensible release sorting with the searchOrder
|
||||
# isrc => basic => artist (native mode)
|
||||
- MB_PRESETS=default,sensible,native
|
||||
|
||||
# spotify
|
||||
- SPOTIFY_ID=foxxSpotify
|
||||
- SPOTIFY_CLIENT_ID=H2UvGv8okrFlv2OUoc1lAtK7Xe5EGDgs
|
||||
- SPOTIFY_CLIENT_SECRET=HvoFE5Ce2hdblNE6vaFBEx6dRVcDdo43
|
||||
- SPOTIFY_REDIRECT_URI=https://multiscrobbler.example.com/callback
|
||||
|
||||
# navidrome listenbrainz endpoint
|
||||
- LZE_ID=myLzNavidrome
|
||||
- LZE_TOKEN=myToken
|
||||
# applies musicbrainz Stage to preTransform of navidrome source
|
||||
- LZE_TRANSFORMS=musicbrainz
|
||||
|
||||
# lastfm
|
||||
- LASTFM_ID=myLFM
|
||||
- LASTFM_API_KEY=myApiKey
|
||||
- LASTFM_SECRET=mySecret
|
||||
- LASTFM_REDIRECT_URI=https://multiscrobbler.example.com/lastfm/callback
|
||||
|
||||
volumes:
|
||||
- "./config:/config"
|
||||
ports:
|
||||
- "9078:9078"
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="file" label="File">
|
||||
|
||||
Using [File Config](/configuration/?configType=file#configuration-types)
|
||||
|
||||
For non-ENV config, [Musicbrainz Transformer](/configuration/transforms/musicbrainz/#api-setup) must be setup using the [AIO Config](/configuration/?configType=aio#configuration-types) `config.json` file. This configuration uses the [sensible defaults](/configuration/transforms/musicbrainz/#sensible-default) in the `defaults` property. This is a suggestion and can be removed if not desired.
|
||||
|
||||
```json5 title="CONFIG_DIR/config.json"
|
||||
{
|
||||
"transformers": [
|
||||
{
|
||||
"type": "musicbrainz",
|
||||
"name": "MyMB",
|
||||
"data": {
|
||||
"apis": [
|
||||
{
|
||||
"contact": "contact@mydomain.com"
|
||||
}
|
||||
]
|
||||
},
|
||||
"defaults": {
|
||||
"releaseStatusPriority": ["official"],
|
||||
"releaseGroupPrimaryTypePriority": [
|
||||
"album",
|
||||
"single",
|
||||
"ep"
|
||||
],
|
||||
"releaseCountryPriority": ["XW"],
|
||||
"searchArtistMethod": "native",
|
||||
"searchOrder": [
|
||||
"isrc",
|
||||
"basic",
|
||||
"artist"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```json5 title="CONFIG_DIR/spotify.json"
|
||||
[
|
||||
{
|
||||
"id": "foxxSpotify",
|
||||
"name": "Foxx Spotify",
|
||||
"data": {
|
||||
"clientId": "a89cba1569901a0671d5a9875fed4be1",
|
||||
"clientSecret": "ec42e09d5ae0ee0f0816ca151008412a",
|
||||
"redirectUri": "http://localhost:9078/callback",
|
||||
"interval": 60
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
```json5 title="CONFIG_DIR/endpointlz.json"
|
||||
[
|
||||
{
|
||||
"id": "myLzNavidrome",
|
||||
"name": "LZ for Navidrome",
|
||||
"data": {
|
||||
"token": "myToken"
|
||||
},
|
||||
"options": {
|
||||
"playTransform": {
|
||||
"preCompare": [
|
||||
{
|
||||
"type": "musicbrainz",
|
||||
"name": "MyMB"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
```json5 title="CONFIG_DIR/lastfm.json"
|
||||
[
|
||||
{
|
||||
"id": "myLFM",
|
||||
"name": "Old Lastfm",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"apiKey": "myApiKey",
|
||||
"secret": "mySecret",
|
||||
"redirectUri": "http://localhost:9078/lastfm/callback"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="aio" label="AIO">
|
||||
|
||||
Using [AIO Config](/configuration/?configType=aio#configuration-types)
|
||||
|
||||
This configuration uses the [sensible defaults](/configuration/transforms/musicbrainz/#sensible-default) in the `defaults` property. This is a suggestion and can be removed if not desired.
|
||||
|
||||
```json5 title="CONFIG_DIR/config.json"
|
||||
{
|
||||
"transformers": [
|
||||
{
|
||||
"type": "musicbrainz",
|
||||
"name": "MyMB",
|
||||
"data": {
|
||||
"apis": [
|
||||
{
|
||||
"contact": "contact@mydomain.com"
|
||||
}
|
||||
]
|
||||
},
|
||||
"defaults": {
|
||||
"releaseStatusPriority": ["official"],
|
||||
"releaseGroupPrimaryTypePriority": [
|
||||
"album",
|
||||
"single",
|
||||
"ep"
|
||||
],
|
||||
"releaseCountryPriority": ["XW"],
|
||||
"searchArtistMethod": "native",
|
||||
"searchOrder": [
|
||||
"isrc",
|
||||
"basic",
|
||||
"artist"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "spotify",
|
||||
"id": "foxxSpotify",
|
||||
"name": "Foxx Spotify",
|
||||
"data": {
|
||||
"clientId": "a89cba1569901a0671d5a9875fed4be1",
|
||||
"clientSecret": "ec42e09d5ae0ee0f0816ca151008412a",
|
||||
"redirectUri": "http://localhost:9078/callback",
|
||||
"interval": 60
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "endpointlz",
|
||||
"name": "LZ for Navidrome",
|
||||
"id": "myLzNavidrome",
|
||||
"data": {
|
||||
"token": "myToken"
|
||||
},
|
||||
"options": {
|
||||
"playTransform": {
|
||||
"preCompare": [
|
||||
{
|
||||
"type": "musicbrainz",
|
||||
"name": "MyMB"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"clients": [
|
||||
{
|
||||
"type": "lastfm",
|
||||
"id": "myLFM",
|
||||
"name": "Old Lastfm",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"apiKey": "myApiKey",
|
||||
"secret": "mySecret",
|
||||
"redirectUri": "http://localhost:9078/lastfm/callback"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.9 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 501 KiB |
@@ -22,7 +22,7 @@ Check the [main Discord docs page](/configuration/clients/discord) for a compari
|
||||
|
||||
<DetailsAdmo type="note" summary="How MS Uses Discord API">
|
||||
|
||||
Multi-scrobbler does its upmost to implement, to-spec, [Gateway API communication](https://docs.discord.com/developers/events/gateway) and respect usage. It requires no [permissions](https://docs.discord.com/developers/events/gateway#gateway-intents) and reads no data other than monitoring what other user sessions are active.
|
||||
Multi-scrobbler does its utmost to implement, to-spec, [Gateway API communication](https://docs.discord.com/developers/events/gateway) and respect usage. It requires no [permissions](https://docs.discord.com/developers/events/gateway#gateway-intents) and reads no data other than monitoring what other user sessions are active.
|
||||
|
||||
Aside from the on-paper violation of using a user token programmatically (on your behalf), there is no misuse of the API to achieve rich presence.
|
||||
|
||||
|
||||
@@ -54,7 +54,9 @@ For Koito versions `<=0.1.7`, you must have the environment variable `KOITO_ALLO
|
||||
<Config config="KoitoClientConfig" fileContent={JsonConfig} client name="koito">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| :--------------------- | --------- | ------- | --------------------------------- |
|
||||
| `KOITO_ID` | Yes | | A unique id |
|
||||
| `KOITO_TOKEN` | Yes | | API Key from your Koito Account |
|
||||
| `KOITO_USER` | Yes | | Your Koito username |
|
||||
| `KOITO_URL` | No | | The base URL for the Koito server |
|
||||
| `KOITO_NAME` | No | | A vanity name, different than ID |
|
||||
</Config>
|
||||
|
||||
@@ -16,6 +16,20 @@ import JsonConfig from '!!raw-loader!@site/../config/lastfm.json.example';
|
||||
|
||||
:::
|
||||
|
||||
<DetailsAdmo type="warning" summary="Limits for Old Scrobbles">
|
||||
|
||||
Last.fm will not accept scrobbles that are timestamped as **older than 2 weeks** from the current time.
|
||||
|
||||
This is not officially documented but has been confirmed by multiple people/mods in the last.fm support forums: [[1]](https://support.last.fm/t/importing-listening-history-spotify-apple-music-and-itunes-wmp/1424/3) [[2]](https://support.last.fm/t/suggestion-allow-last-fm-pro-members-to-use-the-api-to-submit-scrobbles-older-than-2-weeks/69190/2) [[3]](https://support.last.fm/t/how-to-scrobble-complete-spotify-history/44558)
|
||||
|
||||
Multi-scrobbler can queue scrobbles for you if the Last.fm service is unavailable, or if there is an issue with a specific scrobble. However, you are responsible for making sure these scrobbles are eventually sent to Last.fm (by retrying in the UI or retarting Last.fm client in UI) before the two week window has passed or else they will not be scrobblable *at all* unless the timestamp is changed.
|
||||
|
||||
A future update to Multi-scrobbler will enable editing of unsent scrobble data and modifying the timestamp from the UI.
|
||||
|
||||
If you have issues with specific scrobbles not being sent and retries/restarts do not fix it, please make sure to [create a new issue](https://github.com/FoxxMD/multi-scrobbler/issues/new?template=01-bug-report.yml) in a timely manner so that it can be resolved before the window has passed.
|
||||
|
||||
</DetailsAdmo>
|
||||
|
||||
You will need a [Last.fm](https://www.last.fm) account to use the Last.fm API.
|
||||
|
||||
After creating your account, [register for an API account here.](https://www.last.fm/api/account/create)
|
||||
@@ -51,8 +65,10 @@ The Redirect URL can be explicitly specified in the configuration below, or by u
|
||||
<Config config="LastfmClientConfig" fileContent={JsonConfig} client name="lastfm">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| ---------------------- | --------- | --------------------------------------- | ----------------------------------------------------------------------------- |
|
||||
| `LASTFM_ID` | Yes | | A unique id |
|
||||
| `LASTFM_API_KEY` | Yes | | Api Key from your API Account |
|
||||
| `LASTFM_SECRET` | Yes | | Shared secret from your API Account |
|
||||
| `LASTFM_REDIRECT_URI` | No | `http://localhost:9078/lastfm/callback` | Url to use for authentication. Must include `lastfm/callback` somewhere in it |
|
||||
| `LASTFM_SESSION` | No | | Session id. Will be generated by authentication flow if not provided. |
|
||||
| `LASTFM_NAME` | No | | A vanity name, different than ID |
|
||||
</Config>
|
||||
@@ -15,9 +15,9 @@ import JsonConfig from '!!raw-loader!@site/../config/librefm.json.example';
|
||||
|
||||
:::
|
||||
|
||||
To use the official [Libre.fm](https://libre.fm/) instance you will need an account. Or, you can run your own Libre.fm instance [using the legacy source code](https://github.com/foocorp/gnu-fm).
|
||||
To use the official [Libre.fm](https://libre.fm/) website you will need an account. Or, you can run your own GNU FM instance [using the source code](https://github.com/foocorp/gnu-fm).
|
||||
|
||||
No API account, [API Key, or Secret](https://github.com/libre-fm/developer/wiki/Libre.fm-fundamentals#api-keys) is needed to use the Libre.fm API. Instead, use **any values** for API Key/Secret in the configuration. Authentication is done by allowing access from your account on redirect.
|
||||
No API account, [API Key, or Secret](https://github.com/libre-fm/developer/wiki/Libre.fm-fundamentals#api-keys) is currently needed to use the Libre.fm API. Instead, use **any values** for API Key/Secret in the configuration. Authentication is done by allowing access from your account on redirect.
|
||||
|
||||
### Callback/Redirect URL
|
||||
|
||||
@@ -56,10 +56,12 @@ https://my.cool.libre.com/2.0/
|
||||
<Config config="LibrefmClientConfig" fileContent={JsonConfig} client name="librefm">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| ---------------------- | --------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
||||
| `LIBREFM_ENABLE` | No | | Set to `true` to enable Libre.fm, if not using any of the below variables |
|
||||
| `LIBREFM_ID` | Yes | | A unique ID |
|
||||
| `LIBREFM_ENABLE` | No | | Set to `true` to enable Libre.fm, if not using any of the below variables |
|
||||
| `LIBREFM_API_KEY` | No | `anything` | Optional API Key |
|
||||
| `LIBREFM_SECRET` | No | `anything` | Optional shared secret |
|
||||
| `LIBREFM_REDIRECT_URI` | No | `http://localhost:9078/librefm/callback` | Url to use for authentication. Must include `librefm/callback` somewhere in it |
|
||||
| `LIBREFM_SESSION` | No | | Session id. Will be generated by authentication flow if not provided. |
|
||||
| `LIBREFM_URLBASE` | No | | Use for custom Libre.fm instance only. The host + path prefix EX `https://my.libre.example/2.0/` |
|
||||
</Config>
|
||||
| `LIBREFM_NAME` | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
|
||||
@@ -25,7 +25,9 @@ On your [profile page](https://listenbrainz.org/profile/) find your **User Token
|
||||
<Config config="ListenBrainzClientConfig" fileContent={JsonConfig} client name="listenbrainz">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| ---------------------- | --------- | ----------------------------- | ------------------------------- |
|
||||
| `LZE_ID` | Yes | | A unique ID |
|
||||
| `LZ_TOKEN` | Yes | | User token from your LZ profile |
|
||||
| `LZ_USER` | Yes | | Your LZ username |
|
||||
| `LZ_URL` | No | https://api.listenbrainz.org/ | The base URL for the LZ server |
|
||||
| `LZ_NAME` | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -38,8 +38,10 @@ Finally, add the Maloja server URL and API Key to the configuration type you cho
|
||||
## Configuration
|
||||
|
||||
<Config config="MalojaClientConfig" fileContent={JsonConfig} client name="maloja">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
|------------------------|-----------|---------|-------------------------------|
|
||||
| `MALOJA_URL` | Yes | | Base URL of your installation |
|
||||
| `MALOJA_API_KEY` | Yes | | Api Key |
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| ---------------------- | --------- | ------- | ------------------------------- |
|
||||
| `MALOJA_ID` | Yes | | A unique ID |
|
||||
| `MALOJA_URL` | Yes | | Base URL of your installation |
|
||||
| `MALOJA_API_KEY` | Yes | | Api Key |
|
||||
| `MALOJA_NAME` | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -13,7 +13,7 @@ To monitor and re-scrobble activity **from** Rocksky create a [Rocksky (Source)]
|
||||
|
||||
:::
|
||||
|
||||
[**Rocksky**](https://rocksky.app/) is music tracking and discovery platform built on the AT Protocol (Bluesky).
|
||||
[**Rocksky**](https://rocksky.app/) is music tracking and discovery platform built on the AT Protocol and your your [**Atmosphere Account**](https://atmosphereaccount.com/).
|
||||
|
||||
<details>
|
||||
|
||||
@@ -23,13 +23,13 @@ To monitor and re-scrobble activity **from** Rocksky create a [Rocksky (Source)]
|
||||
|
||||
</details>
|
||||
|
||||
<DetailsAdmo type="warning" summary="Your Data Is Public">
|
||||
<DetailsAdmo type="important" summary="Your Data Is Public">
|
||||
|
||||
The default Bluesky PDS is **public**, meaning your scrobbles are also public (read-only). This isn't any different than using Last.fm or Listenbrainz, in terms of privacy.
|
||||
|
||||
</DetailsAdmo>
|
||||
|
||||
<DetailsAdmo type="warning" summary="No Scrobbling Guarantee">
|
||||
<DetailsAdmo type="important" summary="No Scrobbling Guarantee">
|
||||
|
||||
Rocksky **does not guarantee** that every scrobble you send will be persisted. Rocksky's design goal is to store scrobbles with **metdata-complete, 3rd party verifiable track information** so that it can be a metadata-rich discovery platform, rather than an exhaustive scrobble data source-of-truth.
|
||||
|
||||
@@ -41,22 +41,16 @@ If you need your scrobbles to be 100% captured then you should scrobble to an *a
|
||||
|
||||
## Setup
|
||||
|
||||
You will need a [Bluesky](https://bsky.app/) account to use Rocksky.
|
||||
You will need an [**Atmosphere Account**](https://atmosphereaccount.com/) to use Rocksky.
|
||||
|
||||
:::note
|
||||
|
||||
Usage is adapted from [Rocksky docs](https://docs.rocksky.app/migrating-to-rocksky-scrobble-api-957839m0) on [docs.rocksky.app](https://docs.rocksky.app)
|
||||
|
||||
:::
|
||||
|
||||
* Navigate to [rocksky.app](https://rocksky.app/) and sign up/logn using your Bluesky account
|
||||
* Then, navigate to the [RockSky developer dashboard](https://rocksky.app/apikeys) and obtain an API Key
|
||||
* Navigate to [rocksky.app](https://rocksky.app/) and sign up/logn using your Atmosphere Account
|
||||
* Then, navigate to the [Access Tokens](https://rocksky.app/access-tokens) in the Rocksky settings dropdown, and create a new token
|
||||
|
||||
## Configuration
|
||||
|
||||
### Handle
|
||||
|
||||
The handle used with multi-scrobbler should be your **full** ATProto handle, including TLD.
|
||||
The handle used with multi-scrobbler should be your **full** [**Atmosphere Account**](https://atmosphereaccount.com/) handle, including TLD.
|
||||
|
||||
* For regular Bluesky account this will be like: `alice.bsky.social`
|
||||
* For Bluesky accounts that [use their website as their account](https://bsky.social/about/blog/4-28-2023-domain-handle-tutorial) this is your domain: `mydomain.com`
|
||||
@@ -64,9 +58,28 @@ The handle used with multi-scrobbler should be your **full** ATProto handle, inc
|
||||
|
||||
If you do not include a TLD then multi-scrobbler will automatically append `.bsky.social` to your handle value.
|
||||
|
||||
<DetailsAdmo type="warning" summary="API Applications Deprecated">
|
||||
|
||||
In < 0.14.1, Multi-Scrobber used Rocksky's API Appication "API Key" for communication with a Listenbrainz-compatible API from Rocksky.
|
||||
|
||||
Going forward, this should be replaced with the official guidance in the [Setup](#setup) section above: use an **Access Token**. This uses the native Rocksky API which has more functionality.
|
||||
|
||||
If your configuration is using either
|
||||
|
||||
* (File/AIO) `'key': ...`
|
||||
* (ENV) `ROCKSKY_KEY`
|
||||
|
||||
you should redo the [Setup](#setup) section and modify your configuration to use an Access Token instead.
|
||||
|
||||
Keys are now **deprecated** and functionality for them will be removed in a future version of Multi-Scrobbler.
|
||||
|
||||
</DetailsAdmo>
|
||||
|
||||
<Config config="RockSkyClientConfig" fileContent={JsonConfig} client name="rocksky">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| :--------------------- | --------- | ------- | ----------------------------------------------------------------------------------- |
|
||||
| `ROCKSKY_ID` | Yes | | A unique ID used in MS |
|
||||
| `ROCKSKY_HANDLE` | Yes | | Your **full** ATProto handle. For Bluesky account this is like `myUser.bsky.social` |
|
||||
| `ROCKSKY_KEY` | Yes | | API Key obtained from Rocksky developer dashboard |
|
||||
| `ROCKSKY_TOKEN` | Yes | | Access Token obtained from Rocksky |
|
||||
| `ROCKSKY_NAME` | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -16,7 +16,7 @@ To monitor and re-scrobble activity from a ATProto Profile create a [teal.fm (So
|
||||
|
||||
:::
|
||||
|
||||
[**teal.fm**](https://teal.fm/) is a social discovery and web viewing service for scrobbles stored/available on the ATProto network/your repository in a [PDS (Personal Data Server)](https://github.com/Bluesky-social/pds).
|
||||
[**teal.fm**](https://teal.fm/) is a social discovery and browsing app for scrobbles stored in your [**Atmosphere Account**](https://atmosphereaccount.com/).
|
||||
|
||||
<details>
|
||||
|
||||
@@ -46,6 +46,13 @@ To view your teal.fm scrobble data you can:
|
||||
|
||||
## Setup
|
||||
|
||||
|
||||
:::note
|
||||
|
||||
Multi-scrobbler will work with *any* Atmosphere Account, not just Bluesky. See [Configuration](#configuration) below for provider-agnostic config.
|
||||
|
||||
:::
|
||||
|
||||
#### Bluesky
|
||||
|
||||
* Create a [Bluesky](https://bsky.app) account, if you don't already have one
|
||||
@@ -57,10 +64,10 @@ To view your teal.fm scrobble data you can:
|
||||
|
||||
### Identifier
|
||||
|
||||
The ATPRoto identifier used with multi-scrobbler should be either:
|
||||
The ATProto identifier used with multi-scrobbler should be either:
|
||||
|
||||
* A valid [DID](https://atproto.com/specs/did#at-protocol-did-identifier-syntax), starting with `did:plc:...` or `did:web:...`
|
||||
* Your **full** ATProto handle, including TLD
|
||||
* Your **full** [Atmosphere Account](https://atmosphereaccount.com/) ATProto handle, including TLD
|
||||
|
||||
If using a handle:
|
||||
|
||||
@@ -73,6 +80,8 @@ If your identifier does not look like a DID and does not include a TLD then mult
|
||||
<Config config="TealClientConfig" fileContent={JsonConfig} client name="tealfm">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| :--------------------- | --------- | ------- | ------------------------------------------------------------------------------------------ |
|
||||
| `TEAMFM_ID` | Yes | | A unique ID |
|
||||
| `TEALFM_IDENTIFIER` | Yes | | Your **full** ATProto handle or DID. For Bluesky account this is like `myUser.bsky.social` |
|
||||
| `TEALFM_APP_PW` | Yes | | Bluesky/ATProto network App Password |
|
||||
| `TEALFM_NAME` | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -440,6 +440,273 @@ Example
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Database
|
||||
|
||||
Multi-scrobbler depends on a SQLite database (`ms.db`) that is created on first run and stored in the [`CONFIG_DIR`](/installation/?dockerSetting=storage#recommended-settings). When upgrading Multi-scrobbler version, if there are any required database changes than this database is [automatically backed up and migrated.](/updating#database)
|
||||
|
||||
The database stores *all* Plays for your Sources/Clients as well as metadata and debugging information to help troubleshoot issues. Each Play is associated with a Source/Config in the database based on your configuration.
|
||||
|
||||
You **should set IDs for each Source/Client** so that the database can identify these even when the configuration is changed.
|
||||
|
||||
### Retention
|
||||
|
||||
<DetailsAdmo status="note" summary="How much storage does the database use?">
|
||||
|
||||
The amount of data stored for each Play can widely vary based on a few factors:
|
||||
|
||||
* how much data the Source service exposes
|
||||
* how many clients each Source is scrobble to (Plays are duplicated for each Client a Source sends a scrobble to)
|
||||
* if you are using any [Transforms](/configuration/transforms) the diff of each step is stored, along with any external request/response data (like [Musicbrainz queries](/configuration/transforms/musicbrainz))
|
||||
|
||||
The [MS repository contains a benchmark](https://github.com/FoxxMD/multi-scrobbler/blob/master/src/backend/tests/database/drizzle.test.ts#L551) to measure an average database size in a few common scenarios.
|
||||
|
||||
<Tabs groupId="dbSize">
|
||||
|
||||
<TabItem label="Minimal" value="minimal">
|
||||
|
||||
Assuming your Sources send a minimal amount of data or you have compacted all plays:
|
||||
|
||||
| Play Count | DB Size |
|
||||
| ---------- | -------- |
|
||||
| 100 | `160kb` |
|
||||
| 1000 | `1MB` |
|
||||
| 10k | `10.2MB` |
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="With Original Input" value="input">
|
||||
|
||||
Assuming your Sources have a non-trivial amount of input data (like Spotify or Listenbrainz) that is not compacted:
|
||||
|
||||
| Play Count | DB Size |
|
||||
| ---------- | -------- |
|
||||
| 100 | `356kb` |
|
||||
| 1000 | `3MB` |
|
||||
| 10k | `29.6MB` |
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="All Debug Deta" value="all">
|
||||
|
||||
Assuming your Sources/Clients:
|
||||
|
||||
* have a non-trivial amount of input data (like Spotify or Listenbrainz)
|
||||
* and has many [Transforms](/configuration/transforms) steps that include requests
|
||||
* nothing is compacted
|
||||
|
||||
| Play Count | DB Size |
|
||||
| ---------- | -------- |
|
||||
| 100 | `684kb` |
|
||||
| 1000 | `6.28MB` |
|
||||
| 10k | `61.3MB` |
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
</DetailsAdmo>
|
||||
|
||||
A retention policy can be configured to delete Plays, or unused debug data, from the database after a certain amount of time. If no configuration is provided then a default policy is used that should be reasonable for most users.
|
||||
|
||||
<Tabs groupId="policy" queryString>
|
||||
|
||||
<TabItem label="Compaction" value="compaction">
|
||||
|
||||
The **Compaction** Retention Policy is used to delete different types of debug data from your stored Plays.
|
||||
|
||||
This is a useful way to reduce used storage space when you are not having problems with your Plays, or iterating on a configuration, that requires referencing all this extra data.
|
||||
|
||||
There are two types of data that can be compacted (deleted from the Play):
|
||||
|
||||
* `input` - this is the untouched data retrieved by Multi-scrobbler, from a Source, and used to generate a Play/scrobble. This can be used to reconstruct and replay a Play, when used from troubleshooting or reporting an issue
|
||||
* `transform` - this is all of the steps generated by [transforms](/configuration/transforms), the diff of the Play resulting from the step, and any request/responses used to complete the step
|
||||
|
||||
:::note[Defaults]
|
||||
|
||||
When no Compact configuration is provided, Multi-scrobbler uses this policy:
|
||||
|
||||
* Compact (delete) `input` and `transform` data on all Plays after 3 days
|
||||
|
||||
:::
|
||||
|
||||
#### Configuring Compaction Policy
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Details</summary>
|
||||
|
||||
Each value in the configuration properties below can be either
|
||||
|
||||
* a number of seconds EX `3600` = 10 minutes
|
||||
* a unit of a common duration with the pattern `X unit` EX
|
||||
* `30 minutes`
|
||||
* `5 hours`
|
||||
* `2 days`
|
||||
|
||||
<Tabs groupId="configType" queryString>
|
||||
|
||||
<TabItem value="env" label="ENV">
|
||||
|
||||
* `COMPACT_PROPERTIES` - which properties to compact
|
||||
* `RETENTION_COMPACT_AFTER` - Default to use for all Plays
|
||||
* `RETENTION_COMPACT_COMPLETED_AFTER` - Compact only completed Plays after...
|
||||
* `RETENTION_COMPACT_FAILED_AFTER` - Compact only failed Plays after...
|
||||
* `RETENTION_COMPACT_DUPED_AFTER` - Compact only duped/discard Plays after...
|
||||
|
||||
Example
|
||||
|
||||
```ini
|
||||
# only delete input when compacting
|
||||
COMPACT_PROPERTIES=input
|
||||
# compact all plays after 3 days
|
||||
RETENTION_COMPACT_AFTER=3 days
|
||||
# specifically compact completed plays after 30 minutes
|
||||
RETENTION_COMPACT_COMPLETED_AFTER=30 minutes
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="aio" label="AIO">
|
||||
|
||||
Compacting all Play types and deleting both input and transform:
|
||||
|
||||
```json title="config.json"
|
||||
{
|
||||
"database": {
|
||||
"retention": {
|
||||
"compactAfter": "3 days",
|
||||
"compact": [
|
||||
"input",
|
||||
"transform"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* Delete only input during compacting
|
||||
* Compact all after 3 days except completed which compacts after 30 minutes
|
||||
|
||||
```json title="config.json"
|
||||
{
|
||||
"database": {
|
||||
"retention": {
|
||||
"compactAfter": {
|
||||
"completed": "30 minutes",
|
||||
"duped": "3 days",
|
||||
"failed": "3 days"
|
||||
},
|
||||
"compact": [
|
||||
"input"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
</details>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Deletion" value="deletion">
|
||||
|
||||
The **Deletion** Retention Policy is used to delete different types of stored Plays from Multi-scrobbler database.
|
||||
|
||||
**This does not delete Plays from your Clients.** It's only deleting the "in-flight" data MS used to create the scrobble that was eventually sent to your clients.
|
||||
|
||||
:::warning[Plays Should be Ephemeral]
|
||||
|
||||
**Multi-Scrobbler is not designed to store Plays/Scrobbles indefinitely.**
|
||||
|
||||
It should scale fine for thousands of scrobbles but it not meant to store 10's of thousands of scrobbles forever. It is not a scrobbler server.
|
||||
|
||||
You **should** set a reasonable deletion policy so that MS stores less than 1000 scrobbles at a time, ideally less.
|
||||
|
||||
:::
|
||||
|
||||
:::note[Defaults]
|
||||
|
||||
When no Deletion policy configuration is provided, Multi-scrobbler uses this policy:
|
||||
|
||||
* Delete all Plays after 7 days
|
||||
|
||||
:::
|
||||
|
||||
#### Configuring Deletion Policy
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Details</summary>
|
||||
|
||||
Each value in the configuration properties below can be either
|
||||
|
||||
* a number of seconds EX `3600` = 10 minutes
|
||||
* a unit of a common duration with the pattern `X unit` EX
|
||||
* `30 minutes`
|
||||
* `5 hours`
|
||||
* `2 days`
|
||||
|
||||
<Tabs groupId="configType" queryString>
|
||||
|
||||
<TabItem value="env" label="ENV">
|
||||
|
||||
* `RETENTION_DELETE_AFTER` - Default to use for all Plays
|
||||
* `RETENTION_DELETE_COMPLETED_AFTER` - Delete only completed Plays after...
|
||||
* `RETENTION_DELETE_FAILED_AFTER` - Delete only failed Plays after...
|
||||
* `RETENTION_DELETE_DUPED_AFTER` - Delete only duped/discard Plays after...
|
||||
|
||||
Example
|
||||
|
||||
```ini
|
||||
# delete all plays after 3 days
|
||||
RETENTION_DELETE_AFTER=3 days
|
||||
# specifically, delete completed plays after 30 minutes
|
||||
RETENTIOND_DELETE_COMPLETED_AFTER=30 minutes
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="aio" label="AIO">
|
||||
|
||||
Deleting all Play types after 3 days:
|
||||
|
||||
```json title="config.json"
|
||||
{
|
||||
"database": {
|
||||
"retention": {
|
||||
"deleteAfter": "3 days"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* Delete all after 3 days except completed which are deleted after 30 minutes
|
||||
|
||||
```json title="config.json"
|
||||
{
|
||||
"database": {
|
||||
"retention": {
|
||||
"deleteAfter": {
|
||||
"completed": "30 minutes",
|
||||
"duped": "3 days",
|
||||
"failed": "3 days"
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
</details>
|
||||
|
||||
</TabItem>
|
||||
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Debug Mode
|
||||
|
||||
@@ -1,317 +1,93 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
title: Kitchen Sink
|
||||
description: Kitchen Sink Example
|
||||
description: Kitchen Sink Examples
|
||||
---
|
||||
|
||||
# Example Complex Scenario
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import MultiUsecase from "@site/src/components/snippets/_scenario-config-multi.mdx"
|
||||
import MultiPersonsServices from "@site/docs/configuration/_kitchensink_configs/_multi-persons-services.mdx"
|
||||
import MultiSourcesClients from "@site/docs/configuration/_kitchensink_configs/_multi-sources-clients.mdx"
|
||||
import Musicbrainz from "@site/docs/configuration/_kitchensink_configs/_musicbrainz.mdx"
|
||||
import SingleUsecase from "@site/src/components/snippets/_scenario-config-single.mdx"
|
||||
|
||||
Scenario:
|
||||
## What's This?
|
||||
|
||||
* You want to scrobble plays for yourself (Foxx), Fred, and Mary
|
||||
* Each person has their own [Maloja](/configuration/clients/maloja) server
|
||||
* Each person has their own Spotify account
|
||||
* You have your own Airsonic ([subsonic](/configuration/sources/subsonic)) server you to scrobble from
|
||||
* You have your own [Youtube Music](/configuration/sources/youtube-music) account you want to scrobble from
|
||||
* Mary has her own [Last.fm](/configuration/clients/lastfm) account she also wants to scrobble to
|
||||
* Fred has his own [Spotify](/configuration/sources/spotify) application and provides you with just his access and refresh token because he doesn't trust you (wtf Fred)
|
||||
* Fred has a Plex server and wants to scrobble everything he plays
|
||||
* Mary uses Fred's Plex server but only wants to scrobble her plays from the `podcast` library
|
||||
* The three of you have a shared library on [Plex](/configuration/sources/plex) called `party` that you only play when you are hanging out. You want plays from that library to be scrobbled to everyone's servers.
|
||||
* Fred also has his own [Jellyfin server](/configuration/sources/jellyfin) and wants to scrobble everything he plays
|
||||
* You have an android music app that can scrobble to a [custom listenbrainz server](/configuration/sources/listenbrainz-endpoint)
|
||||
On this page you find contrived, complex usecases for multi-scrobbler that provide examples of equivalent [configuration types](/configuration#configuration-types) to fulfill that usecase.
|
||||
|
||||
### All-in-one Config
|
||||
These example are useful for:
|
||||
|
||||
```json5 title="CONFIG_DIR/config.json"
|
||||
{
|
||||
"sourceDefaults": {
|
||||
"maxPollRetries": 0, // optional, default # of automatic polling restarts on error. can be overridden by property in individual config
|
||||
"maxRequestRetries": 1, // optional, default # of http request retries a source can make before error is thrown. can be overridden by property in individual config
|
||||
"retryMultiplier": 1.5, // optional, default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying). can be overridden by property in individual config
|
||||
},
|
||||
"clientDefaults": {
|
||||
"maxRequestRetries": 1, // optional, default # of http request retries a client can make before error is thrown. can be overridden by property in individual config
|
||||
"retryMultiplier": 1.5, // optional, default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying). can be overridden by property in individual config
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"type": "spotify",
|
||||
"name": "foxxSpot",
|
||||
"clients": ["foxxMaloja"],
|
||||
"data": {
|
||||
"clientId": "foxxSpotifyAppId",
|
||||
"clientSecret": "foxxSpotifyAppSecret",
|
||||
},
|
||||
"options": {
|
||||
"maxRequestRetries": 2, // override default max retries because spotify can...spotty
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "spotify",
|
||||
"name": "marySpot",
|
||||
"clients": ["maryMaloja"],
|
||||
"data": {
|
||||
"clientId": "foxxSpotifyAppId", // only need one application, it can be used by all users of this multi-scrobbler instance
|
||||
"clientSecret": "foxxSpotifyAppSecret",
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "spotify",
|
||||
"name": "fredSpot",
|
||||
"clients": ["fredMaloja"],
|
||||
"data": {
|
||||
"accessToken": "fredsToken",
|
||||
"refreshToken": "fredsRefreshToken",
|
||||
"interval": 120, // he also wants a slower check interval because his application already has heavy api usage
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "plex",
|
||||
"name": "fredPlex",
|
||||
"clients": ["fredMaloja"],
|
||||
"data": {
|
||||
"token": "JtRnwQWD__XMJF8sT3jc",
|
||||
"url": "192.168.0.233:32400",
|
||||
"usersallow": ["fredUser"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "plex",
|
||||
"name": "maryPlex",
|
||||
"clients": ["maryMaloja"],
|
||||
"data": {
|
||||
"token": "JtRnwQWD__XMJF8sT3jc",
|
||||
"url": "192.168.0.233:32400",
|
||||
"usersallow": ["maryUser"], // still need to specify mary as user so not all users who play from 'podcasts' get scrobbled
|
||||
"librariesAllow": ["podcasts"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "plex",
|
||||
"name": "partyPlex",
|
||||
// omitting clients (or making it empty) will make this Source scrobble to all Clients
|
||||
"data": {
|
||||
"token": "JtRnwQWD__XMJF8sT3jc",
|
||||
"url": "192.168.0.233:32400",
|
||||
"librariesAllow": ["party"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "jellyfin",
|
||||
"name": "FredJelly",
|
||||
// omitting clients (or making it empty) will make this Source scrobble to all Clients
|
||||
"data": {
|
||||
"url": "http://localhost:8096",
|
||||
"user": "FoxxMD",
|
||||
"apiKey": "c9fae8756fbf481ebd9c5bb56bd6540c",
|
||||
"usersAllow": ["fredUser"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "subsonic",
|
||||
"name": "foxxAirsonic",
|
||||
"clients": ["foxxMaloja"],
|
||||
"data": {
|
||||
"user": "foxx",
|
||||
"password": "foxxPassword",
|
||||
"url": "https://airsonic.foxx.example"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "ytmusic",
|
||||
"name": "foxxYoutube",
|
||||
"clients": ["foxxMaloja"],
|
||||
"data": {
|
||||
"cookie": "__Secure-3PAPISID=3AxsXpy0MKGu75Qb/AkISXGqOnSDn1jEKn; DEVICE_INFO=ChxOekU0Tmpjek5EWTBPRGd3TlRBMk16QXpNdz09EJbS8Z0GGJbS8Z0G; ...",
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "endpointlz",
|
||||
"name": "listenbrainzfoxx",
|
||||
"clients": ["foxxMaloja"],
|
||||
"data": {
|
||||
"token": "myToken"
|
||||
}
|
||||
}
|
||||
],
|
||||
"clients": [
|
||||
{
|
||||
"type": "maloja",
|
||||
"name": "foxxMaloja",
|
||||
"data": {
|
||||
"url": "https://maloja.foxx.example",
|
||||
"apiKey": "foxxApiKey"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "maloja",
|
||||
"name": "fredMaloja",
|
||||
"data": {
|
||||
"url": "https://maloja.fred.example",
|
||||
"apiKey": "fredApiKey"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "maloja",
|
||||
"name": "maryMaloja",
|
||||
"data": {
|
||||
"url": "https://maloja.mary.example",
|
||||
"apiKey": "maryApiKey"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "lastfm",
|
||||
"name": "maryLFM",
|
||||
"data": {
|
||||
"apiKey": "maryApiKey",
|
||||
"secret": "marySecret",
|
||||
"redirectUri": "http://localhost:9078/lastfm/callback"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
* getting an idea of what you can do with Multi-scrobbler
|
||||
* using these examples as a starting point for your own configuration
|
||||
* providing an example of the same configuration across different [configuration types](/configuration#configuration-types)
|
||||
|
||||
```
|
||||
:::tip
|
||||
|
||||
### Separate JSON files
|
||||
More common, straightforward scenarios can be found in the [**quickstart**](/quickstart) guide.
|
||||
|
||||
```json5 title="CONFIG_DIR/spotify.json"
|
||||
[
|
||||
{
|
||||
// may omit 'type' property since app knows this is file is for spotify configs
|
||||
"name": "foxxSpot",
|
||||
"clients": ["foxxMaloja"],
|
||||
"data": {
|
||||
"clientId": "foxxSpotifyAppId",
|
||||
"clientSecret": "foxxSpotifyAppSecret"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "marySpot",
|
||||
"clients": ["maryMaloja"],
|
||||
"data": {
|
||||
"clientId": "foxxSpotifyAppId",
|
||||
"clientSecret": "foxxSpotifyAppSecret"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fredSpot",
|
||||
"clients": ["fredMaloja"],
|
||||
"data": {
|
||||
"accessToken": "fredsToken",
|
||||
"refreshToken": "fredsRefreshToken",
|
||||
"interval": 120
|
||||
}
|
||||
},
|
||||
]
|
||||
```
|
||||
:::
|
||||
|
||||
```json5 title="CONFIG_DIR/plex.json"
|
||||
[
|
||||
{
|
||||
"name": "fredPlex",
|
||||
"clients": ["fredMaloja"],
|
||||
"data": {
|
||||
"token": "JtRnwQWD__XMJF8sT3jc",
|
||||
"url": "192.168.0.233:32400",
|
||||
"usersallow": ["fredUser"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "maryPlex",
|
||||
"clients": ["maryMaloja"],
|
||||
"data": {
|
||||
"token": "JtRnwQWD__XMJF8sT3jc",
|
||||
"url": "192.168.0.233:32400",
|
||||
"usersallow": ["maryUser"],
|
||||
"librariesAllow": ["podcasts"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "partyPlex",
|
||||
"data": {
|
||||
"token": "JtRnwQWD__XMJF8sT3jc",
|
||||
"url": "192.168.0.233:32400",
|
||||
"librariesAllow": ["party"]
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
## Examples
|
||||
|
||||
```json5 title="CONFIG_DIR/jellyfin.json"
|
||||
[
|
||||
{
|
||||
"name": "FredJelly",
|
||||
"data": {
|
||||
"url": "http://localhost:8096",
|
||||
"user": "FoxxMD",
|
||||
"apiKey": "c9fae8756fbf481ebd9c5bb56bd6540c",
|
||||
"usersAllow": ["fredUser"]
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
### Multiple Sources to Multiple Clients {#multi-sources-clients}
|
||||
|
||||
```json5 title="CONFIG_DIR/ytmusic.json"
|
||||
[
|
||||
{
|
||||
"name": "foxxYoutube",
|
||||
"clients": ["foxxMaloja"],
|
||||
"data": {
|
||||
"cookie": "__Secure-3PAPISID=3AxsXpy0MKGu75Qb/AkISXGqOnSDn1jEKn; DEVICE_INFO=ChxOekU0Tmpjek5EWTBPRGd3TlRBMk16QXpNdz09EJbS8Z0GGJbS8Z0G; ...",
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
```json5 title="CONFIG_DIR/endpointlz.json"
|
||||
[
|
||||
{
|
||||
"name": "listenbrainzfoxx",
|
||||
"clients": ["foxxMaloja"],
|
||||
"data": {
|
||||
"token": "myToken",
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
You are the only user of Multi-scrobbler. You have many Sources you listen to and want to scrobble them to multiple services.
|
||||
|
||||
```json5 title="CONFIG_DIR/maloja.json"
|
||||
[
|
||||
{
|
||||
"name": "foxxMaloja",
|
||||
"data": {
|
||||
"url": "https://maloja.foxx.example",
|
||||
"apiKey": "foxxApiKey"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fredMaloja",
|
||||
"data": {
|
||||
"url": "https://maloja.fred.example",
|
||||
"apiKey": "fredApiKey"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "maryMaloja",
|
||||
"data": {
|
||||
"url": "https://maloja.mary.example",
|
||||
"apiKey": "maryApiKey"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
* You want to **scrobble from...**
|
||||
* A [Spotify](/configuration/sources/spotify) account
|
||||
* A Navidrome instance that you want to use with the built-in [listenbrainz](/configuration/sources/listenbrainz-endpoint) scrobble function
|
||||
* A shared [Plex](/configuration/sources/plex) instance that you want to scrobble only from a specific library named `oldies`
|
||||
* You want to **scrobble to...**
|
||||
* Your decades-old [Last.fm](/configuration/clients/lastfm) account
|
||||
* A [Koito](/configuration/clients/koito) server for backup
|
||||
|
||||
```json5 title="CONFIG_DIR/lastfm.json"
|
||||
[
|
||||
{
|
||||
"name": "maryLFM",
|
||||
"data": {
|
||||
"apiKey": "maryApiKey",
|
||||
"secret": "marySecret",
|
||||
"redirectUri": "http://localhost:9078/lastfm/callback"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
<details>
|
||||
|
||||
<summary>Configuration</summary>
|
||||
|
||||
<MultiSourcesClients/>
|
||||
|
||||
</details>
|
||||
|
||||
### Scrobble for Multiple People to Multiple Clients {#multi-persons-services}
|
||||
|
||||
<MultiUsecase/>
|
||||
|
||||
You want to scrobble plays for yourself (Foxx), Fred, and Mary:
|
||||
|
||||
* The three of you want to **scrobble from...**
|
||||
* Fred and Mary each have their own [Jellyfin servers](/configuration/sources/jellyfin)
|
||||
* You have a [Plex](/configuration/sources/plex) server
|
||||
* The three of you want to **scrobble to...**
|
||||
* Fred and Mary each have their own [Last.fm](/configuration/clients/lastfm) accounts
|
||||
* You have a [Koito](/configuration/clients/koito) server
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Configuration</summary>
|
||||
|
||||
<MultiPersonsServices/>
|
||||
|
||||
</details>
|
||||
|
||||
### Use Musicbrainz to Fix Scrobbles from Navidrome
|
||||
|
||||
You have multiple Sources of music but your personal, on-filesystem music collection is not well organized.
|
||||
|
||||
You want to use the [Musicbrainz Transformer](/configuration/transforms/musicbrainz) to fix any scrobbles that use Navidrome, where your personal collection is located, so that the scrobble artists/album/title are all standardized.
|
||||
|
||||
* You want to **scrobble from...**
|
||||
* A [Spotify](/configuration/sources/spotify) account
|
||||
* A Navidrome instance that you want to use with the built-in [listenbrainz](/configuration/sources/listenbrainz-endpoint) scrobble function
|
||||
* Plays from this Source should be fixed by Musicbrainz before going to your scrobble Clients
|
||||
* You want to **scrobble to...**
|
||||
* A [Last.fm](/configuration/clients/lastfm) account
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Configuration</summary>
|
||||
|
||||
<Musicbrainz/>
|
||||
|
||||
</details>
|
||||
@@ -0,0 +1,262 @@
|
||||
---
|
||||
title: Apple Music
|
||||
toc_min_heading_level: 2
|
||||
toc_max_heading_level: 5
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import CodeBlock from '@theme/CodeBlock';
|
||||
import AppleMusicConfig from '!!raw-loader!@site/../config/applemusic.json.example';
|
||||
|
||||
This Source **monitors your Apple Music listening history** via the official Apple Music API and scrobbles new activity to your configured [Clients](/configuration/clients).
|
||||
|
||||
Because of how the Apple Music API works, Multi-Scrobbler (MS) requires two different tokens to function: a **Media User Token** (identifies your personal account) and an **Authentication Token** (authorizes API access).
|
||||
|
||||
### 1. Getting a Media User Token
|
||||
|
||||
The `mediaUserToken` is required to access your recently played history. You can extract it directly from the Apple Music web app:
|
||||
|
||||
1. Visit [music.apple.com](https://music.apple.com) in your browser and log in.
|
||||
2. Open your browser's Developer Tools (usually `F12` or <kbd>⌘</kbd> + <kbd>⌥</kbd> + <kbd>I</kbd>).
|
||||
3. Navigate to the **Application** tab (Chrome/Edge) or **Storage** tab (Safari/Firefox).
|
||||
4. Expand **Cookies** in the left sidebar and select `https://music.apple.com`.
|
||||
5. Find the cookie named `media-user-token` and copy its value (it typically starts with `0.`).
|
||||
|
||||
:::caution[Token Expiry]
|
||||
The `mediaUserToken` will eventually expire. If Multi-Scrobbler begins throwing authentication errors in the logs, simply repeat these steps to obtain and configure a fresh token.
|
||||
:::
|
||||
|
||||
### 2. Authentication
|
||||
|
||||
To authorize API access, Multi-Scrobbler requires an Authentication Token (JWT). Because the official Apple Music API is designed for developers, there are two ways to provide this token depending on your situation:
|
||||
|
||||
* **Browser Token (Free & Most Common):** Best for 99% of users. You can easily extract a temporary token from the Apple Music web player without needing an Apple Developer account.
|
||||
* **MusicKit Key (Requires Paid Apple Developer Account):** If you happen to be enrolled in the paid Apple Developer Program ($99/year), you can provide a private key to automatically generate permanent tokens.
|
||||
|
||||
Choose your preferred method below:
|
||||
|
||||
<Tabs groupId="appleMusicAuth" queryString>
|
||||
<TabItem value="token" label="Browser Token (Free)">
|
||||
|
||||
Since most users don't have a paid developer account, extracting the token from the web player is the standard approach.
|
||||
|
||||
Behind the scenes, the Apple Music website makes a lot of background requests, which can make finding the right token confusing. To filter out the noise and grab the exact token we need, follow the steps for your browser below:
|
||||
|
||||
<details>
|
||||
<summary>Chrome, Edge & Firefox</summary>
|
||||
|
||||
1. Visit [music.apple.com](https://music.apple.com) and log in.
|
||||
2. Open Developer Tools (`F12` or `Ctrl+Shift+I` / <kbd>⌘</kbd> + <kbd>⌥</kbd> + <kbd>I</kbd>) and navigate to the **Network** tab.
|
||||
3. In the filter box at the top left, paste exactly: `https://amp-api.music.apple.com/v1/me/account` (**Label 1**).
|
||||
4. Click the **Fetch/XHR** filter button to hide irrelevant requests (**Label 2**).
|
||||
5. Reload the page (**Label 3**).
|
||||
6. Under the "Name" column, click the request starting with `account?meta=` (**Label 4**).
|
||||
7. In the panel that opens, scroll down to the **Request Headers** section.
|
||||
8. Find the `authorization` header. Copy the long string of text **after** the `Bearer ` prefix (**Label 5**).
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Safari</summary>
|
||||
|
||||
Before you can extract the token in Safari, you must enable developer tools.
|
||||
|
||||
{/* This weird formatting is because ⌘ is usually incredibly tiny */}
|
||||
1. Open Safari Settings (<kbd>⌘</kbd> + <kbd>,</kbd>), navigate to the **Advanced** tab, and check **"Show features for web developers"** at the very bottom.
|
||||
|
||||

|
||||
|
||||
2. Visit [music.apple.com](https://music.apple.com) and log in.
|
||||
3. Open the Web Inspector (<kbd>⌘</kbd>+<kbd>⌥</kbd>+<kbd>I</kbd> or Develop > Show Web Inspector) and navigate to the **Network** tab.
|
||||
4. In the filter bar on the left side of the inspector, paste exactly: `https://amp-api.music.apple.com/v1/me/account` (**Label 1**).
|
||||
5. Reload the page (**Label 2**).
|
||||
6. Click the `account` request that appears in the Name list (**Label 3**).
|
||||
7. In the details sidebar, look under the **Request** section for the `Authorization` header. Copy the long string of text **after** the `Bearer ` prefix (**Label 4**).
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
<br />
|
||||
|
||||
Once you have your token, add it to your configuration file along with the origin header:
|
||||
|
||||
```json title="applemusic.json"
|
||||
{
|
||||
"data": {
|
||||
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsIm...",
|
||||
"mediaUserToken": "your-media-user-token-here",
|
||||
"origin": "https://music.apple.com"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::info[Origin Header Required]
|
||||
When using a JWT extracted from the browser, Apple requires the request to match the domain it was issued to. You **must** include the `origin` field in your config as shown above.
|
||||
:::
|
||||
|
||||
*Note: Browser-generated JWTs are valid for a maximum of 35 days and must be updated manually when they expire.*
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="key" label="MusicKit Key (Paid Account)">
|
||||
|
||||
:::warning[Apple Developer Account Required]
|
||||
To generate JWTs automatically, you must be enrolled in the paid [Apple Developer Program](https://developer.apple.com/programs/) ($99/year) and create a MusicKit key. If you don't have an account, use the **Browser Token (Free)** tab instead.
|
||||
:::
|
||||
|
||||
To generate JWTs automatically and avoid manual token refreshes, you need an Apple Music API key:
|
||||
|
||||
1. Go to the [Apple Developer portal](https://developer.apple.com/account/resources/authkeys/list) and create a **MusicKit** key.
|
||||
2. Download the `.p8` file — this is your private key.
|
||||
3. Note your **Key ID** and **Team ID** (found in your Apple Developer account Membership details).
|
||||
|
||||
Add these details to your config:
|
||||
|
||||
```json title="applemusic.json"
|
||||
{
|
||||
"data": {
|
||||
"key": {
|
||||
"id": "2HPSNJZ88N",
|
||||
"teamId": "SN6YASW8G4",
|
||||
"p8": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEG...-----END PRIVATE KEY-----"
|
||||
},
|
||||
"mediaUserToken": "your-media-user-token-here"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::tip
|
||||
When pasting the contents of your `.p8` file into JSON, make sure to replace physical line breaks with `\n` so it remains a valid, single-line JSON string.
|
||||
:::
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
### How Multi-Scrobbler handles Apple Music quirks
|
||||
|
||||
#### Timestamp Estimation
|
||||
|
||||
The Apple Music API **does not provide timestamps** for when tracks were played. Multi-Scrobbler estimates play times by taking the current time and subtracting track durations backwards:
|
||||
* The most recent track is assumed to have finished playing **now**.
|
||||
* Each older track is estimated to have played `duration` seconds before the previous one.
|
||||
|
||||
*For the most accurate scrobble timestamps, it is highly recommended to keep the polling interval (`APPLEMUSIC_INTERVAL`) low (the default is 60 seconds).*
|
||||
|
||||
#### Duplicate Play Recovery
|
||||
|
||||
The Apple Music history API strictly deduplicates tracks. Instead of showing the same song multiple times in a row, it simply bumps the re-played track back to the #1 spot and removes the older entry.
|
||||
|
||||
For example, if you listen to **Song A** → **Song B**, your history is `[Song B, Song A]`. If you then listen to **Song A** again, the API returns `[Song A, Song B]` (Song A was bumped to the top).
|
||||
|
||||
If Multi-Scrobbler polled when the top track was Song A, and polls again after it's bumped back to the top, the top track appears functionally unchanged. This usually confuses standard scrobblers into ignoring the update entirely. By default, MS correctly detects this "top-rebound" pattern, extracts the intermediate tracks that were squeezed in (Song B), and scrobbles Song A again as a re-listen.
|
||||
|
||||
*Note: Because of this strict deduplication, **"pure loops"** (listening to Song A continuously on repeat without playing anything else in between) cannot be tracked. The history list never changes shape, so MS has no way of knowing it was played again.*
|
||||
|
||||
If you ever experience false positives (tracks being scrobbled that you didn't actually re-listen to), you can disable this recovery behavior with `"recoverUnchangedTopHistory": false` in your config options or by setting the `APPLEMUSIC_RECOVER_UNCHANGED_TOP_HISTORY=false` env var.
|
||||
|
||||
#### Album name normalization
|
||||
|
||||
The Apple Music API appends ` - EP` or ` - Single` to the album name for EPs and singles. These suffixes are *not* part of the official names of the album and can cause issues when trying to scrobble or match metadata.
|
||||
|
||||
By default, Multi-Scrobbler strips these suffixes out so only the official name is used.
|
||||
|
||||
If you do not want this behavior it can be disabled with ENV `APPLEMUSIC_NORMALIZE_ALBUM=false` or File/AIO option `"normalizeAlbum": false`.
|
||||
|
||||
<DetailsAdmo type="tip" summary="Stripping Suffix with User Stage">
|
||||
|
||||
If you still want this stripping functionality but with more control over how it is applied you can use a [User Stage](/configuration/transforms/user).
|
||||
|
||||
Here is an example of creating a User Stage to only strip suffixes for a specific scrobble client:
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Example</summary>
|
||||
|
||||
```json5 title="config.json"
|
||||
{
|
||||
// ...
|
||||
"transformers": [
|
||||
{
|
||||
"type": "user",
|
||||
"name": "AppleAlbumStip",
|
||||
"defaults": {
|
||||
"album": [
|
||||
"/ - (EP|Single)$/i"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```json5 title="applemusic.json"
|
||||
{
|
||||
{
|
||||
"id": "myAppleMusic",
|
||||
"name": "My Apple Music",
|
||||
"data": {
|
||||
// ...
|
||||
},
|
||||
// highlight-start
|
||||
"options": {
|
||||
"normalizeAlbum": false
|
||||
}
|
||||
// highlight-end
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json5 title="lastfm.json"
|
||||
[
|
||||
{
|
||||
"name": "Foxx LFM Client",
|
||||
"id": "myLastFmClient",
|
||||
"enable": true,
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"apiKey": "a89cba1569901a0671d5a9875fed4be1",
|
||||
"secret": "ec42e09d5ae0ee0f0816ca151008412a",
|
||||
"redirectUri": "http://localhost:9078/lastfm/callback"
|
||||
},
|
||||
// highlight-start
|
||||
"options": {
|
||||
"playTransform": {
|
||||
"preCompare": [
|
||||
{
|
||||
"type": "user",
|
||||
"name": "AppleAlbumStip",
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
// highlight-end
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
</DetailsAdmo>
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
<Config config="AppleMusicSourceConfig" fileContent={AppleMusicConfig} name="applemusic">
|
||||
| Environment Variable | Required? | Default | Description |
|
||||
| ------------------------------------------ | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `APPLEMUSIC_ID` | Yes | | A unique ID for this source. |
|
||||
| `APPLEMUSIC_MEDIA_USER_TOKEN` | Yes | | The media-user-token extracted from the browser. |
|
||||
| `APPLEMUSIC_KEY_ID` | No | | Key ID from your MusicKit key. |
|
||||
| `APPLEMUSIC_TEAM_ID` | No | | Team ID from your Apple Developer account. |
|
||||
| `APPLEMUSIC_KEY_P8` | No | | The contents of your MusicKit `.p8` private key file. |
|
||||
| `APPLEMUSIC_TOKEN` | No | | The authentication JWT extracted from the browser. |
|
||||
| `APPLEMUSIC_INTERVAL` | No | `60` | Polling interval in seconds. |
|
||||
| `APPLEMUSIC_ORIGIN_HEADER` | No | | Origin header for API requests, e.g. `https://music.apple.com`. Required when using a browser token. |
|
||||
| `APPLEMUSIC_RECOVER_UNCHANGED_TOP_HISTORY` | No | `true` | Apple Music deduplicates its history by bumping re-played tracks to the top. If you listen to Song A → Song B → Song A, the history changes from `[A]` to `[A, B]` (bumping A). MS detects this "top-rebound" to recover the intermediate play (B) and the re-listen (A). Disable this only if you see incorrect duplicate scrobbles. |
|
||||
| `APPLEMUSIC_NORMALIZE_ALBUM` | No | `true` | Strips extraneous `- EP` and `- Single` suffixes from album names |
|
||||
| `APPLEMUSIC_NAME` | No | | A vanity name different than the ID. |
|
||||
</Config>
|
||||
@@ -16,9 +16,9 @@ Azuracast may not correctly report track length or position. If this is the case
|
||||
|
||||
:::
|
||||
|
||||
The Azuracast server should have **Use High-Performance Now Playing Updates** enabled in _Administration -> System Settings_
|
||||
The Azuracast server should have **Use [High-Performance Now Playing Updates](https://www.azuracast.com/docs/developers/now-playing-data/#high-performance-updates)** enabled in _Administration -> System Settings_
|
||||
|
||||
#### URL
|
||||
### URL
|
||||
|
||||
The URL used by MS to connect to Azuracast has the syntax:
|
||||
|
||||
@@ -47,13 +47,77 @@ If either is configured then MS will automatically scrobble based on when these
|
||||
|
||||
If neither is configured automatic scrobble defaults to **off** and you should either manually initiate it or explicitly set the default behavior.
|
||||
|
||||
### Station
|
||||
|
||||
#### Station Identifier
|
||||
|
||||
The station identifier multi-scrobbler uses (ENV `AZURA_STATION` or `station` in File/AIO config) needs to be the **URL Stub** or **shortcode** of the Azuracast station, **not** its display name. Use of the methods below to find an apprioriate station identifier.
|
||||
|
||||
<Tabs groupId="stationId" queryString>
|
||||
<TabItem value="stub" label="URL Stub">
|
||||
The **URL Stub** is the value used in the URL to access the station EX
|
||||
|
||||
```
|
||||
https://azura.mydomain.com/public/my-cool-station
|
||||
^^^^ url stub ^^
|
||||
```
|
||||
|
||||
Use `my-cool-station` as the station identifier.
|
||||
</TabItem>
|
||||
<TabItem value="shortcode" label="Shortcode">
|
||||
The **shortcode** can be found in the raw JSON response for your station. Navigate to this URL in your browser:
|
||||
|
||||
```
|
||||
https://azura.mydomain.com/api/stations
|
||||
```
|
||||
|
||||
Find your station in the returned data (by `name`) and then locate the value of the `shortcode` property in that object.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Example</summary>
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "My Cool Station",
|
||||
"shortcode": "my-cool-station",
|
||||
"description": "Re-broadcasts of my cool music",
|
||||
"frontend": "icecast",
|
||||
"backend": "liquidsoap",
|
||||
...
|
||||
```
|
||||
|
||||
Use `my-cool-station` as the station identifier.
|
||||
</details>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### Station Access
|
||||
|
||||
Multi-Scrobbler does not use authentication to communicate with Azuracast. Your station will likely need some form of **Public Pages** in order for you to monitor it.
|
||||
|
||||
In your station's **Edit Station Settings** -> **Public Pages** make sure that at least one of these is enabled:
|
||||
|
||||
* Enable Public Pages
|
||||
* Enable Public APIs
|
||||
|
||||
:::note
|
||||
|
||||
For users who aren't administrators of Azuracast: if you can access the station using `https://azura.mydomain.com/public/xxxxxx` then it is public.
|
||||
|
||||
:::
|
||||
|
||||
## Configuration
|
||||
|
||||
<Config config="AzuracastSourceConfig" fileContent={AzuracastConfig} name="azuracast">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| :--------------------- | :-------- | :------ | ---------------------------------------------------------------------------------------------- |
|
||||
| `AZ_URL` | Yes | | Azuracast URL *without station name* |
|
||||
| `AZ_STATION` | Yes | | The station name shown on the public page |
|
||||
| `AZURA_ID` | Yes | | A unique ID |
|
||||
| `AZURA_URL` | Yes | | Azuracast URL *without station name* |
|
||||
| `AZURA_STATION` | Yes | | The station URL Stub or shortcode |
|
||||
| `AZURA_LIVE` | No | Yes | Only scrobble when station status is ONLINE |
|
||||
| `AZURA_LISTENERS_NUM` | No | `true` | Only scrobble if station has any listeners (`true`) or listeners are equal-to/greater-than `X` |
|
||||
| `AZURA_NAME` | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -176,10 +176,12 @@ import DeezerDeprecatedConfig from '!!raw-loader!@site/../config/deezer-deprecat
|
||||
<Tabs groupId="configType" queryString>
|
||||
<TabItem value="env" label="ENV">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
|------------------------|-----------|-----------------------------------------|-----------------------------------|
|
||||
| ---------------------- | --------- | --------------------------------------- | --------------------------------- |
|
||||
| `DEEZER_ID` | Yes | | A unique ID |
|
||||
| `DEEZER_CLIENT_ID` | Yes | | Your **Application ID** |
|
||||
| `DEEZER_CLIENT_SECRET` | Yes | | Your **Secret Key** |
|
||||
| `DEEZER_REDIRECT_URI` | No | `http://localhost:9078/deezer/callback` | URI must end in `deezer/callback` |
|
||||
| `DEEZER_NAME` | No | | A vanity name different than ID |
|
||||
</TabItem>
|
||||
<TabItem value="file" label="File">
|
||||
|
||||
|
||||
@@ -149,11 +149,13 @@ To diagnose bad/incomplete track information or strange MS player behavior pleas
|
||||
[Manually configuring cast device connections](#connecting-devices) is only available through file-based config.
|
||||
:::
|
||||
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
|------------------------|-----------|---------|--------------------------------------------------------------------------------------|
|
||||
| CC_ENABLE | No | | Set to 'true' to enable Cast monitoring without needing to define other ENVs |
|
||||
| CC_WHITELIST_DEVICES | No | | Only scrobble from these Cast devices. Comma-delimited list. EX mini-home, family-tv |
|
||||
| CC_BLACKLIST_DEVICES | No | | Do not scrobble from these Cast devices. Comma-delimited list |
|
||||
| CC_WHITELIST_APPS | No | | Only scrobble from these casted Apps. Comma-delimited list. EX spotify, pandora |
|
||||
| CC_BLACKLIST_APPS | No | | Do not scrobble from these casted Apps. Comma-delimited list |
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| ---------------------- | --------- | ------- | ------------------------------------------------------------------------------------ |
|
||||
| `CC_ID` | Yes | | A unique ID |
|
||||
| `CC_ENABLE` | No | | Set to 'true' to enable Cast monitoring without needing to define other ENVs |
|
||||
| `CC_WHITELIST_DEVICES` | No | | Only scrobble from these Cast devices. Comma-delimited list. EX mini-home, family-tv |
|
||||
| `CC_BLACKLIST_DEVICES` | No | | Do not scrobble from these Cast devices. Comma-delimited list |
|
||||
| `CC_WHITELIST_APPS` | No | | Only scrobble from these casted Apps. Comma-delimited list. EX spotify, pandora |
|
||||
| `CC_BLACKLIST_APPS` | No | | Do not scrobble from these casted Apps. Comma-delimited list |
|
||||
| `CC_NAME` | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -64,7 +64,9 @@ If no `sources` are configured for multi-scrobbler it will automatically try to
|
||||
## Configuration
|
||||
|
||||
<Config config="IcecastSourceConfig" fileContent={IcecastConfig} name="icecast">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| :--------------------- | :-------- | :------ | ----------- |
|
||||
| `ICECAST_URL` | Yes | | Icecast URL |
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| :--------------------- | :-------- | :------ | ------------------------------- |
|
||||
| `ICECAST_ID` | Yes | | A unique ID |
|
||||
| `ICECAST_URL` | Yes | | Icecast URL |
|
||||
| `ICECAST_NAME` | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -54,6 +54,7 @@ Use `JELLYFIN_FRONTEND_URL_OVERRIDE=https://jellyfin.mydomain.com` in order for
|
||||
<Config config="JellyApiSourceConfig" fileContent={JellyfinConfig} name="jellyfin">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| -------------------------------- | --------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `JELLYFIN_ID` | **Yes** | | A unique ID |
|
||||
| `JELLYFIN_URL` | **Yes** | | The URL of the Jellyfin server IE `http://localhost:8096` |
|
||||
| `JELLYFIN_USER` | **Yes** | | The user to authenticate with the API |
|
||||
| `JELLYFIN_APIKEY` | No | | The API Key to use for authentication **(Must provide either apikey or password)** |
|
||||
@@ -66,4 +67,5 @@ Use `JELLYFIN_FRONTEND_URL_OVERRIDE=https://jellyfin.mydomain.com` in order for
|
||||
| `JELLYFIN_LIBRARIES_BLOCK` | No | | Comma-separated list of libraries to disallow scrobbles from |
|
||||
| `JELLYFIN_MEDIATYPES_ALLOW` | No | | Comma-separated list of [media types](https://github.com/jellyfin/jellyfin-sdk-typescript/blob/master/src/generated-client/models/media-type.ts#L22) to allow scrobbling. Defaults to `audio` |
|
||||
| `JELLYFIN_FRONTEND_URL_OVERRIDE` | No | | The external URL to use for album art assets instead of `JELLYFIN_URL` |
|
||||
| `JELLYFIN_NAME` | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -9,7 +9,7 @@ import TabItem from '@theme/TabItem';
|
||||
import CodeBlock from '@theme/CodeBlock';
|
||||
import JriverConfig from '!!raw-loader!@site/../config/jriver.json.example';
|
||||
|
||||
In order for multi-scrobbler to communicate with JRiver you must have [Web Server Interface](https://wiki.jriver.com/index.php/Web_Service_Interface#Documentation_of_Functions) enabled. This can can be in the JRiver GUI:
|
||||
In order for multi-scrobbler to communicate with JRiver you must have [Web Server Interface](https://wiki.jriver.com/index.php/Web_Service_Interface#Documentation_of_Functions) enabled. This can be found in the JRiver GUI:
|
||||
|
||||
* Tools -> Options -> Media Network
|
||||
* Check `Use Media Network to share this library...`
|
||||
@@ -50,9 +50,11 @@ If any part of this URL is missing multi-scrobbler will use a default value. Thi
|
||||
## Configuration
|
||||
|
||||
<Config config="JRiverSourceConfig" fileContent={JriverConfig} name="jriver">
|
||||
| Environmental Variable | Required | Default | Description |
|
||||
|------------------------|----------|---------------------------------|------------------------------------------------|
|
||||
| Environmental Variable | Required | Default | Description |
|
||||
| ---------------------- | -------- | ------------------------------- | ---------------------------------------------- |
|
||||
| JRIVER_ID | Yes | | A unique ID |
|
||||
| JRIVER_URL | Yes | http://localhost:52199/MCWS/v1/ | The URL of the JRiver server |
|
||||
| JRIVER_USERNAME | No | | If authentication is enabled, the username set |
|
||||
| JRIVER_PASSWORD | No | | If authenticated is enabled, the password set |
|
||||
| JRIVER_NAME | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -51,9 +51,11 @@ If any part of this URL is missing multi-scrobbler will use a default value. Thi
|
||||
## Configuration
|
||||
|
||||
<Config config="KodiSourceConfig" fileContent={KodiConfig} name="kodi">
|
||||
| Environmental Variable | Required | Default | Description |
|
||||
|------------------------|----------|-------------------------------|----------------------------|
|
||||
| KODI_URL | Yes | http://localhost:8080/jsonrpc | The URL of the Kodi server |
|
||||
| KODI_USERNAME | No | | The username set |
|
||||
| KODI_PASSWORD | No | | The password set |
|
||||
| Environmental Variable | Required | Default | Description |
|
||||
| ---------------------- | -------- | ----------------------------- | ------------------------------- |
|
||||
| KODI_ID | Yes | | A unique ID |
|
||||
| KODI_URL | Yes | http://localhost:8080/jsonrpc | The URL of the Kodi server |
|
||||
| KODI_USERNAME | No | | The username set |
|
||||
| KODI_PASSWORD | No | | The password set |
|
||||
| KODI_NAME | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -27,7 +27,9 @@ See the [Koito (Client)](/configuration/clients/koito) configuration for general
|
||||
<Config config="KoitoSourceConfig" fileContent={KoitoConfig} name="koito">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| :--------------------- | --------- | ------- | --------------------------------- |
|
||||
| `SOURCE_KOITO_ID` | Yes | | A unique ID |
|
||||
| `SOURCE_KOITO_TOKEN` | Yes | | API Key from your Koito Account |
|
||||
| `SOURCE_KOITO_USER` | Yes | | Your Koito username |
|
||||
| `SOURCE_KOITO_URL` | No | | The base URL for the Koito server |
|
||||
| `SOURCE_KOITO_NAME` | No | | A vanity name differen than ID |
|
||||
</Config>
|
||||
@@ -41,6 +41,8 @@ http://localhost:9078/api/lastfm/mySlug
|
||||
<Config config="LastFMEndpointSourceConfig" fileContent={LastfmEndpointConfig} name="endpointlfm">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| :--------------------- | :-------- | ------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| `LFM_ID` | Yes | | A unique ID |
|
||||
| `LFM_ENABLE` | No | | Use LFM Endpoint as a Source without any other configuration. Only required if slug/token are not provided as ENVs |
|
||||
| `LFM_SLUG` | No | | (Optional) The URL suffix to use for accepting LFM scrobbles |
|
||||
| `LFM_NAME` | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -26,8 +26,10 @@ To configure this Source use the same registration instructions and configuratio
|
||||
<Config config="LastfmSourceConfig" fileContent={LastfmConfig} name="lastfm">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| ---------------------------- | --------- | --------------------------------------- | ----------------------------------------------------------------------------- |
|
||||
| `SOURCE_LASTFM_ID` | Yes | | A unique ID |
|
||||
| `SOURCE_LASTFM_API_KEY` | Yes | | Api Key from your API Account |
|
||||
| `SOURCE_LASTFM_SECRET` | Yes | | Shared secret from your API Account |
|
||||
| `SOURCE_LASTFM_REDIRECT_URI` | No | `http://localhost:9078/lastfm/callback` | Url to use for authentication. Must include `lastfm/callback` somewhere in it |
|
||||
| `SOURCE_LASTFM_SESSION` | No | | Session id. Will be generated by authentication flow if not provided. |
|
||||
| `SOURCE_LASTFM_NAME` | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -25,10 +25,12 @@ To configure this Source use the same registration instructions and configuratio
|
||||
<Config config="LibrefmSourceConfig" fileContent={JsonConfig} name="librefm">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| ----------------------------- | --------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
||||
| `SOURCE_LIBREFM_ID` | Yes | | A unique ID |
|
||||
| `SOURCE_LIBREFM_ENABLE` | No | | Set to `true` to enable Libre.fm, if not using any of the below variables |
|
||||
| `SOURCE_LIBREFM_API_KEY` | No | `anything` | Optional API Key |
|
||||
| `SOURCE_LIBREFM_SECRET` | No | `anything` | Optional shared secret |
|
||||
| `SOURCE_LIBREFM_REDIRECT_URI` | No | `http://localhost:9078/librefm/callback` | Url to use for authentication. Must include `librefm/callback` somewhere in it |
|
||||
| `SOURCE_LIBREFM_SESSION` | No | | Session id. Will be generated by authentication flow if not provided. |
|
||||
| `SOURCE_LIBREFM_URLBASE` | No | | Use for custom Libre.fm instance only. The host + path prefix EX `https://my.libre.example/2.0/` |
|
||||
| `SOURCE_LIBREFM_NAME` | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -111,8 +111,10 @@ To troubleshoot any errors, and assuming you are using Home Assistant, view the
|
||||
<Config config="ListenbrainzEndpointSourceConfig" fileContent={ListenbrainzEndpointConfig} name="endpointlz">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| :--------------------- | :-------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `LZE_ID` | Yes | | A unique ID |
|
||||
| `LZE_ENABLE` | No | | Use LZ Endpoint as a Source without any other configuration. Only required if slug/token are not provided as ENVs |
|
||||
| `LZE_TOKEN` | No | | LZ "Authentication Token" you provided to the scrobbling application |
|
||||
| `LZE_SLUG` | No | | (Optional) The URL suffix to use for accepting LZ scrobbles |
|
||||
| `LZE_USERNAME` | No | | (Optional) A fake username that will be returned for token validation and on now-playing responses. If none is provided the Source's name is used instead. |
|
||||
| `LZE_NAME` | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -28,7 +28,9 @@ On your [profile page](https://listenbrainz.org/profile/) find your **User Token
|
||||
<Config config="ListenBrainzSourceConfig" fileContent={ListenbrainzConfig} name="listenbrainz">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| ---------------------- | --------- | ----------------------------- | ------------------------------- |
|
||||
| `SOURCE_LZ_ID` | Yes | | A unique ID |
|
||||
| `SOURCE_LZ_TOKEN` | Yes | | User token from your LZ profile |
|
||||
| `SOURCE_LZ_USER` | Yes | | Your LZ username |
|
||||
| `SOURCE_LZ_URL` | No | https://api.listenbrainz.org/ | The base URL for the LZ server |
|
||||
| `SOURCE_LZ_NAME` | No | | A vanity name different than ID |d
|
||||
</Config>
|
||||
@@ -22,8 +22,10 @@ See the [Maloja (Client)](/configuration/clients/maloja) configuration for gener
|
||||
## Configuration
|
||||
|
||||
<Config config="MalojaSourceConfig" fileContent={MalojaConfig} name="maloja">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| ----------------------- | --------- | ------- | ----------------------------- |
|
||||
| `SOURCE_MALOJA_URL` | Yes | | Base URL of your installation |
|
||||
| `SOURCE_MALOJA_API_KEY` | Yes | | Api Key |
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| ----------------------- | --------- | ------- | ------------------------------- |
|
||||
| `SOURCE_MALOJA_ID` | Yes | | A unique ID |
|
||||
| `SOURCE_MALOJA_URL` | Yes | | Base URL of your installation |
|
||||
| `SOURCE_MALOJA_API_KEY` | Yes | | Api Key |
|
||||
| `SOURCE_MALOJA_NAME` | Yes | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -23,8 +23,10 @@ Multi-scrobbler requires only the [`read`](https://mpd.readthedocs.io/en/stable/
|
||||
## Configuration
|
||||
|
||||
<Config config="MPDSourceConfig" fileContent={MPDConfig} name="mpd">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
|------------------------|-----------|------------------|-------------|
|
||||
| `MPD_URL` | No | `localhost:6600` | |
|
||||
| `MPD_PASSWORD` | No | | |
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| ---------------------- | --------- | ---------------- | ------------------------------- |
|
||||
| `MPD_ID` | Yes | | A unique ID |
|
||||
| `MPD_URL` | No | `localhost:6600` | |
|
||||
| `MPD_PASSWORD` | No | | |
|
||||
| `MPD_NAME` | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -72,8 +72,10 @@ MPRIS communication requires multi-scrobbler to have access to the host's dbus-d
|
||||
|
||||
<Config config="MPRISSourceConfig" fileContent={MPRISConfig} name="mpris">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
|------------------------|-----------|---------|----------------------------------------------------------------------------------|
|
||||
| ---------------------- | --------- | ------- | -------------------------------------------------------------------------------- |
|
||||
| MPRIS_ID | Yes | | A unique ID |
|
||||
| MPRIS_ENABLE | No | | Use MPRIS as a Source (useful when you don't need any other options) |
|
||||
| MPRIS_BLACKLIST | No | | Comma-delimited list of player names not to scrobble from |
|
||||
| MPRIS_WHITELIST | No | | Comma-delimited list of players names to ONLY scrobble from. Overrides blacklist |
|
||||
| MPRIS_NAME | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -32,7 +32,9 @@ If no URL is provided to MS it will try to use `ws://localhost:7905`
|
||||
|
||||
<Config config="MusikcubeSourceConfig" fileContent={MusikcubeConfig} name="musikcube">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
|------------------------|-----------|-----------------------|--------------------------------------|
|
||||
| ---------------------- | --------- | --------------------- | ------------------------------------ |
|
||||
| `MC_ID` | Yes | | A unique ID |
|
||||
| `MC_URL` | No | `ws://localhost:7905` | Use port set for **metadata server** |
|
||||
| `MC_PASSWORD` | Yes | | |
|
||||
| `MC_NAME` | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -196,6 +196,7 @@ To configure MS to use Musicbrainz with your Plex metadata use one of the method
|
||||
<Config config="PlexApiSourceConfig" fileContent={PlexConfig} name="plex">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| ---------------------- | --------- | ------- | ---------------------------------------------------------------------- |
|
||||
| `PLEX_ID` | **Yes** | | A unique ID |
|
||||
| `PLEX_URL` | **Yes** | | The URL of the Plex server IE `http://localhost:32400` |
|
||||
| `PLEX_TOKEN` | **Yes** | | The **Plex Token** to use with the API |
|
||||
| `PLEX_USERS_ALLOW` | No | | Comma-separated list of usernames (from Plex) to scrobble for |
|
||||
@@ -204,4 +205,5 @@ To configure MS to use Musicbrainz with your Plex metadata use one of the method
|
||||
| `PLEX_DEVICES_BLOCK` | No | | Comma-separated list of devices to disallow scrobbles from |
|
||||
| `PLEX_LIBRARIES_ALLOW` | No | | Comma-separated list of libraries to allow scrobbles from |
|
||||
| `PLEX_LIBRARIES_BLOCK` | No | | Comma-separated list of libraries to disallow scrobbles from |
|
||||
| `PLEX_NAME` | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -35,6 +35,8 @@ See the [Rocksky (Client)](/configuration/clients/rocksky#setup) configuration f
|
||||
<Config config="RockskySourceConfig" fileContent={JsonConfig} name="rocksky">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| :---------------------- | --------- | ------- | ----------------------------------------------------------------------------------- |
|
||||
| `SOURCE_ROCKSKY_ID` | Yes | | A unique ID |
|
||||
| `SOURCE_ROCKSKY_HANDLE` | Yes | | Your **full** ATProto handle. For Bluesky account this is like `myUser.bsky.social` |
|
||||
| `SOURCE_ROCKSKY_KEY` | Yes | | API Key obtained from Rocksky developer dashboard |
|
||||
| `SOURCE_ROCKSKY_NAME` | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -92,9 +92,11 @@ You **must** define the IP address of at least one Sonos/accessory in order for
|
||||
<Config config="SonosSourceConfig" fileContent={JsonConfig} name="sonos">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| ---------------------- | --------- | ------- | :--------------------------------------------------------------------- |
|
||||
| `SONOS_ID` | Yes | | A unique ID |
|
||||
| `SONOS_HOST` | Yes | | The IP address of any active Sonos device/accessory EX `192.168.0.150` |
|
||||
| `SONOS_DEVICES_ALLOW` | No | | Comma-separated list of Sonos device names to scrobble from |
|
||||
| `SONOS_DEVICES_BLOCK` | No | | Comma-separated list of Sonos device names to disallow scrobbles from |
|
||||
| `SONOS_GROUPS_ALLOW` | No | | Comma-separated list of device group names to scrobble from |
|
||||
| `SONOS_GROUPS_BLOCK` | No | | Comma-separated list of devices group names to disallow scrobbles from |
|
||||
| `SONOS_NAME` | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -15,6 +15,7 @@ A **Source** is a data source that contains information about tracks you are pla
|
||||
|
||||
| Name | Networking | Scrobble SOT | [Multi Device/User](#multi-deviceuser) | [Scrobble Destination](#limiting-scrobble-destination) | [Thresholds](#scrobble-thresholds) | [Should Scrobble](#should-scrobble-behavior) |
|
||||
| :---------------------------------------------------------------------- | :------------------------------------------------------- | :-------------------------------------------------- | :------------------------------------- | :----------------------------------------------------- | :--------------------------------- | :------------------------------------------- |
|
||||
| [Apple Music](/configuration/sources/applemusic) | [Active](./?sourceComm=active#by-communication-method) | [History](./?sot=history#by-data-source-of-truth) | ❌ | ✅ | ❌ | ❌ |
|
||||
| [Azuracast](/configuration/sources/azuracast) | [Active](./?sourceComm=active#by-communication-method) | [Activity](./?sot=activity#by-data-source-of-truth) | ❌ | ✅ | ✅ | ✅ |
|
||||
| [Deezer](/configuration/sources/deezer) | [Active](./?sourceComm=active#by-communication-method) | [History](./?sot=history#by-data-source-of-truth) | ✅ | ✅ | ❌ | ❌ |
|
||||
| [Google Cast (Chromecast)](/configuration/sources/google-cast) | [Active](./?sourceComm=active#by-communication-method) | [Activity](./?sot=activity#by-data-source-of-truth) | ✅ | ✅ | ✅ | ❌ |
|
||||
|
||||
@@ -111,9 +111,11 @@ Use **one** of the following methods to specify a valid Redirect URI and complet
|
||||
## Configuration
|
||||
|
||||
<Config config="SpotifySourceConfig" fileContent={SpotifyConfig} name="spotify">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| ----------------------- | --------- | -------------------------------- | -------------------------- |
|
||||
| `SPOTIFY_CLIENT_ID` | Yes | | |
|
||||
| `SPOTIFY_CLIENT_SECRET` | Yes | | |
|
||||
| `SPOTIFY_REDIRECT_URI` | No | `http://localhost:9078/callback` | URI must end in `callback` |
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| ----------------------- | --------- | -------------------------------- | ------------------------------- |
|
||||
| `SPOTIFY_ID` | Yes | | A unique ID |
|
||||
| `SPOTIFY_CLIENT_ID` | Yes | | |
|
||||
| `SPOTIFY_CLIENT_SECRET` | Yes | | |
|
||||
| `SPOTIFY_REDIRECT_URI` | No | `http://localhost:9078/callback` | URI must end in `callback` |
|
||||
| `SPOTIFY_NAME` | No | | A vantiy name different than ID |
|
||||
</Config>
|
||||
@@ -32,8 +32,10 @@ Use the optional `usersAllow` property with **File** or **AIO** configuration to
|
||||
|
||||
<Config config="SubSonicSourceConfig" fileContent={SubsonicConfig} name="subsonic">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
|------------------------|-----------|---------|--------------------------------------|
|
||||
| ---------------------- | --------- | ------- | ------------------------------------ |
|
||||
| `SUBSONIC_ID` | Yes | | A unique ID |
|
||||
| `SUBSONIC_USER` | Yes | | |
|
||||
| `SUBSONIC_PASSWORD` | Yes | | |
|
||||
| `SUBSONIC_URL` | Yes | | Base url of your subsonic-api server |
|
||||
| `SUBSONIC_NAME` | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -106,6 +106,8 @@ Allow scrobbles from plyr.fm and unknown services:
|
||||
<Config config="TealClientConfig" fileContent={JsonConfig} name="tealfm">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| :------------------------- | --------- | ------- | ------------------------------------------------------------------------------------------ |
|
||||
| `SOURCE_TEALFM_ID` | Yes | | A unique ID |
|
||||
| `SOURCE_TEALFM_IDENTIFIER` | Yes | | Your **full** ATProto handle or DID. For Bluesky account this is like `myUser.bsky.social` |
|
||||
| `SOURCE_TEALFM_APP_PW` | Yes | | Bluesky/ATProto network App Password |
|
||||
| `SORUCE_TEALFM_NAME` | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -99,8 +99,10 @@ If you find that VLC is incorrectly reporting track information (in its interfac
|
||||
## Configuration
|
||||
|
||||
<Config config="VLCSourceConfig" fileContent={JsonConfig} name="vlc">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
|------------------------|------------|------------------|-------------|
|
||||
| `VLC_URL` | No | `localhost:8080` | |
|
||||
| `VLC_PASSWORD` | Yes | | |
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| ---------------------- | --------- | ---------------- | ------------------------------- |
|
||||
| `VLC_ID` | Yes | | A unique ID |
|
||||
| `VLC_URL` | No | `localhost:8080` | |
|
||||
| `VLC_PASSWORD` | Yes | | |
|
||||
| `VLC_NAME` | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -88,8 +88,10 @@ This affects **only** MS's behavior and does not affect the general connector be
|
||||
|
||||
<Config config="WebScrobblerSourceConfig" fileContent={JsonConfig} name="webscrobbler">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
|------------------------|-----------|---------|--------------------------------------------------------------------------|
|
||||
| ---------------------- | --------- | ------- | ------------------------------------------------------------------------ |
|
||||
| WS_ID | Yes | | A unique ID |
|
||||
| WS_ENABLE | No | | Set to 'true' to enable WS without needing to define other ENVs |
|
||||
| WS_WHITELIST | No | | Only scrobble from these WebScrobbler Connectors. Comma-delimited list |
|
||||
| WS_BLACKLIST | No | | Do not scrobble from these WebScrobbler Connectors. Comma-delimited list |
|
||||
| WS_NAME | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -31,7 +31,9 @@ EX
|
||||
## Configuration
|
||||
|
||||
<Config config="MusicCastSourceConfig" fileContent={JsonConfig} name="musiccast">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| :--------------------- | :-------- | ------- | :----------------------- |
|
||||
| `MCAST_URL` | Yes | | The Musiccast device URL |
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| :--------------------- | :-------- | ------- | :------------------------------ |
|
||||
| `MCAST_ID` | Yes | | A unique ID |
|
||||
| `MCAST_URL` | Yes | | The Musiccast device URL |
|
||||
| `MCAST_NAME` | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -74,6 +74,8 @@ If you have issues specifically with the MS Source please mention [**Druidblack*
|
||||
<Config config="YandexMusicBridgeSourceConfig" fileContent={JsonConfig} name="ymbridge">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| :--------------------- | :-------- | ------- | :------------------------------------------------------------------------ |
|
||||
| `YMBRIDGE_ID` | Yes | | A unique ID |
|
||||
| `YMBRIDGE_URL` | Yes | | `http://URL:PORT` for the `yandex-music-bridge` container |
|
||||
| `YMBRIDGE_API_KEY` | No | | The same key used for `YM_API_KEY` on the `yandex-music-bridge` container |
|
||||
| `YMBRIDGE_NAME` | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -179,11 +179,13 @@ Only one of these methods needs to be used.
|
||||
## Configuration
|
||||
|
||||
<Config config="YTMusicSourceConfig" fileContent={JsonConfig} name="ytmusic">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
|------------------------|-----------|---------|-----------------------------------------------|
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| ---------------------- | --------- | ------- | --------------------------------------------- |
|
||||
| YTM_ID | Yes | | A unique ID |
|
||||
| YTM_COOKIE | No | | Value for Cookie Authentication |
|
||||
| YTM_CLIENT_ID | No | | Client ID for OAuth Athentication |
|
||||
| YTM_CLIENT_SECRET | No | | Client Secret for OAuth Athentication |
|
||||
| YTM_REDIRECT_URI | No | | A custom redirect URI for OAuth Athentication |
|
||||
| YTM_LOG_DIFF | No | false | Log YTM history changes |
|
||||
| YTM_NAME | No | | A vanity name different than ID |
|
||||
</Config>
|
||||
@@ -593,39 +593,6 @@ MS uses [caching](/configuration/#caching) to reduce the number of API calls nee
|
||||
|
||||
**If you are using any Transform stages you should configure [secondary caching](/configuration#secondary-caching)** to increase the cache size and lifetime of cached items. This will also reduce memory usage in MS.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Example</summary>
|
||||
|
||||
Add valkey service to your [multi-scrobbler docker compose stack](/installation?runType=docker-compose#docker) and configure MS to use it for metadata:
|
||||
|
||||
```yaml title="docker-compose.yml"
|
||||
services:
|
||||
multi-scrobbler:
|
||||
image: foxxmd/multi-scrobbler
|
||||
# ...
|
||||
environment:
|
||||
# ...
|
||||
// highlight-start
|
||||
- CACHE_METADATA=valkey
|
||||
- CACHE_METADATA_CONN=redis://valkey:6379
|
||||
// highlight-end
|
||||
# ...
|
||||
|
||||
// highlight-start
|
||||
valkey:
|
||||
image: valkey/valkey
|
||||
volumes:
|
||||
- valkeydata:/data
|
||||
|
||||
volumes:
|
||||
valkeydata:
|
||||
driver: local
|
||||
// highlight-end
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Examples
|
||||
|
||||
See **Examples** sections in specific Stage docs (also in the sidebar):
|
||||
|
||||
@@ -3,6 +3,8 @@ slug: /
|
||||
title: Overview
|
||||
sidebar_position: 1
|
||||
---
|
||||
import Carousel from '@site/src/components/Carousel/NextCarousel';
|
||||
import BrowserOnly from '@docusaurus/BrowserOnly';
|
||||
|
||||

|
||||

|
||||
@@ -13,6 +15,7 @@ sidebar_position: 1
|
||||
A dockerized app that monitors your music listening activity from *everywhere* and scrobbles it *anywhere*.
|
||||
|
||||
* Supports monitoring activity from many [**Sources**](/configuration/sources)
|
||||
* [Apple Music](/configuration/sources/applemusic)
|
||||
* [Azuracast](/configuration/sources/azuracast)
|
||||
* [Deezer](/configuration/sources/deezer)
|
||||
* [Google Cast (Chromecast)](/configuration/sources/google-cast)
|
||||
@@ -64,6 +67,18 @@ A dockerized app that monitors your music listening activity from *everywhere* a
|
||||
|
||||
[**Quick Start Guide**](/quickstart)
|
||||
|
||||
<DetailsAdmo type="tip" summary="New UI Preview">
|
||||
|
||||
A **full rewrite** of the UI is now available for preview. The new UI modernizes Multi-Scrobbler and gives you in depth insight into how your scrobbles are handled end-to-end.
|
||||
|
||||
<Carousel/>
|
||||
|
||||
To try out the stable-release preview navigate to `http://yourMSDomain:9078/next`
|
||||
|
||||
Or to try out the **latest preview snapshot** see the docker image pinned [in this comment.](https://github.com/FoxxMD/multi-scrobbler/issues/500#issuecomment-4917525794)
|
||||
|
||||
</DetailsAdmo>
|
||||
|
||||
<img src={require('/img/status-ui.png').default} width="800"/>
|
||||
|
||||
**Why should I use this over a browser extension and/or mobile app scrobbler?**
|
||||
|
||||
@@ -42,7 +42,7 @@ Cross-platform images are built for x86 (Intel/AMD) and ARM64 (IE Raspberry Pi)
|
||||
|
||||
<Tabs groupId="dockerSetting" queryString>
|
||||
<TabItem value="storage" label="Storage">
|
||||
You **should** bind a host directory into the container for storing configurations and credentials. Otherwise, these will be lost when the container is updated.
|
||||
You **should** bind a host directory into the container for storing configurations, credentials, database, and other files. Otherwise, these will be lost when the container is updated.
|
||||
|
||||
```yaml title="docker-compose.yml"
|
||||
services:
|
||||
@@ -54,6 +54,24 @@ services:
|
||||
// highlight-end
|
||||
```
|
||||
|
||||
Optionally, use a separate directory for data (database, cache, etc...) by mounting an additional directory and setting the `DATA_DIR` ENV to the directory that should be used inside the container:
|
||||
|
||||
```yaml title="docker-compose.yml"
|
||||
services:
|
||||
multi-scrobbler:
|
||||
# ...
|
||||
environment:
|
||||
# ...
|
||||
// highlight-start
|
||||
- DATA_DIR=/msData
|
||||
// highlight-end
|
||||
volumes:
|
||||
- "./config:/config" # used for config files
|
||||
// highlight-start
|
||||
- "./my/host/folder:/msData" # used for data files
|
||||
// highlight-end
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="networking" label="Networking">
|
||||
If you are using a [bridge network](https://www.appsdeveloperblog.com/docker-networking-bridging-host-and-overlay/) (default docker setup) you must map a port to the container in order to access the dashboard and use MS with some sources (Webscrobbler, LFM/LZ Endpoints). The default container port is `9078`.
|
||||
@@ -231,6 +249,33 @@ npm run docs:install && npm run build
|
||||
npm run start
|
||||
```
|
||||
|
||||
#### Config And Data Directories
|
||||
|
||||
The directories MS uses for **Configuration** (`config.json`, `jellyfin.json`, etc...) and **Data** (database, cache, etc...) can be specified by environmental variables passed directly to `npm`/`node`, or set in an `.env` file in the working directory.
|
||||
|
||||
When parsing these ENVs MS will resolve relative directories but *not* bash expansions like `~` for home. Examples:
|
||||
|
||||
* `CONFIG_DIR=/my/absolute/path` => sets directory to use for config files
|
||||
* `DATA_DIR=./relative/data` => sets directory relative to working directory for data files
|
||||
|
||||
If you do not set these environmental variables then MS will attempt to use OS-standardized directories:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="linux" label="Linux">
|
||||
* Config => `$XDG_CONFIG_HOME/multi-scrobbler` or `~/.config/multi-scrobbler`
|
||||
* Data => `$XDG_DATA_HOME/multi-scrobbler` or `~/.local/share/multi-scrobbler`
|
||||
</TabItem>
|
||||
<TabItem value="mac" label="MacOS">
|
||||
* Config => `~/Library/Preferences/multi-scrobbler`
|
||||
* Data => `~/Library/Application Support/multi-scrobbler`
|
||||
</TabItem>
|
||||
<TabItem value="win" label="Windows">
|
||||
* Config => `%APPDATA%\multi-scrobbler\Config`
|
||||
* Data => `%LOCALAPPDATA%\multi-scrobbler\Data`
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
||||
#### Rollup build error
|
||||
|
||||
During building if you encounter an error like: `Your current platform "XXX" and architecture "XXX" combination is not yet supported by the native Rollup build.`
|
||||
|
||||
@@ -54,6 +54,7 @@ services:
|
||||
- JELLYFIN_URL=192.168.0.110:8096 # URL to Jellyfin UI
|
||||
- JELLYFIN_APIKEY=c9fae8756fbf481ebd9c5bb56b # Api Key created in previous step
|
||||
- JELLYFIN_USER=MyUser # Your username in Jellyfin
|
||||
- JELLYFIN_ID=myJellyin
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="spotify" label="Spotify">
|
||||
@@ -68,6 +69,7 @@ services:
|
||||
- SPOTIFY_CLIENT_ID=yourClientId
|
||||
- SPOTIFY_CLIENT_SECRET=yourClientSecret
|
||||
- SPOTIFY_REDIRECT_URI=https://multi-scrobbler.mydomain.com/callback
|
||||
- SPOTIFY_ID=mySpotify
|
||||
```
|
||||
|
||||
Later, after [starting multi-scrobbler](#start-multi-scrobbler), visit the dashboard at `http://192.168.0.100:9078` and click **(Re)authenticate** on the Spotify card to authorize multi-scrobbler to use your account. Monitoring will begin automatically after authorization is complete.
|
||||
@@ -83,6 +85,7 @@ services:
|
||||
|
||||
```yaml title="~/msData/docker-compose.yml"
|
||||
- LZE_TOKEN=myCoolToken
|
||||
- LZE_ID=navidromeLZ
|
||||
```
|
||||
|
||||
The token can be anything. [(Re)start multi-scrobbler](#start-multi-scrobbler) after making this change.
|
||||
@@ -105,6 +108,7 @@ services:
|
||||
```yaml title="~/msData/docker-compose.yml"
|
||||
- PLEX_URL=http://192.168.0.101:32400
|
||||
- PLEX_TOKEN=6c0ihz8j7__f5r18oa0zm8
|
||||
- PLEX_ID=myPlex
|
||||
# optional, restrict scrobbling to a list of comma-delimited users
|
||||
#- PLEX_USERS_ALLOW=myPlexUsername
|
||||
```
|
||||
@@ -132,6 +136,7 @@ services:
|
||||
- SOURCE_LASTFM_API_KEY=MY_API_KEY_PLACEDHOLDER
|
||||
- SOURCE_LASTFM_SECRET=MY_SECRET_PLACEHOLDER
|
||||
- SOURCE_LASTFM_REDIRECT_URI=http://192.168.0.100:9078/lastfm/callback
|
||||
- SOURCE_LASTFM_ID=myLFMSource
|
||||
```
|
||||
|
||||
Later, after [starting multi-scrobbler](#start-multi-scrobbler), visit the dashboard at `http://192.168.0.100:9078` and click **(Re)authenticate** on the **Last.fm (Source)** card to authorize multi-scrobbler to use your account. Monitoring will begin automatically after authorization is complete.
|
||||
@@ -251,6 +256,7 @@ Modify the `lastfm.json` file you created in the [Setup Sources](#setup-sources)
|
||||
- KOITO_TOKEN=myToken
|
||||
- KOITO_USER=myUser
|
||||
- KOITO_URL=myURL
|
||||
- KOITO_ID=myKoito
|
||||
```
|
||||
|
||||
:::tip
|
||||
@@ -274,6 +280,7 @@ Modify the `lastfm.json` file you created in the [Setup Sources](#setup-sources)
|
||||
```yaml title="~/msData/docker-compose.yml"
|
||||
- LASTFM_API_KEY=myApiKey
|
||||
- LASTFM_SECRET=myApiSecret
|
||||
- LASTFM_ID=myLfmClient
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="maloja" label="Maloja">
|
||||
@@ -306,6 +313,7 @@ Modify the `lastfm.json` file you created in the [Setup Sources](#setup-sources)
|
||||
```yaml title="~/msData/docker-compose.yml"
|
||||
- MALOJA_URL=http://192.168.0.100:42010
|
||||
- MALOJA_API_KEY=myApiKey
|
||||
- MALOJA_ID=myMaloja
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
@@ -9,7 +9,7 @@ import TabItem from '@theme/TabItem';
|
||||
import CodeBlock from '@theme/CodeBlock';
|
||||
import RequiredUpgrade from "@site/src/components/snippets/_upgrade-required.mdx"
|
||||
|
||||
<RequiredUpgrade before='< 0.14.0' current='0.14.0' old='0.13.2' />
|
||||
{/* <RequiredUpgrade before='< 0.14.0' current='0.14.0' old='0.13.2' /> */}
|
||||
|
||||
## Source/Client IDs
|
||||
|
||||
@@ -75,32 +75,34 @@ Add an `id` to the top-level for each Source/Client configuration, next to `data
|
||||
|
||||
Add an `id` to the top-level for each Source/Client configuration, next to `data`:
|
||||
|
||||
```json title="koito.json"
|
||||
[
|
||||
{
|
||||
"name": "koito-source",
|
||||
"configureAs": "source",
|
||||
"type": "koito",
|
||||
// highlight-start
|
||||
"id": "myKoitoID",
|
||||
// highlight-end
|
||||
"data": {
|
||||
"token": "029b081ba-9156-4pe7-88e5-3be671f5ea2b",
|
||||
"username": "admin",
|
||||
"url": "http://192.168.0.100:4110"
|
||||
```json title="config.json"
|
||||
{
|
||||
"sources": [
|
||||
{
|
||||
"name": "koito-source",
|
||||
"configureAs": "source",
|
||||
"type": "koito",
|
||||
// highlight-start
|
||||
"id": "myKoitoID",
|
||||
// highlight-end
|
||||
"data": {
|
||||
"token": "029b081ba-9156-4pe7-88e5-3be671f5ea2b",
|
||||
"username": "admin",
|
||||
"url": "http://192.168.0.100:4110"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::note[Default ID]
|
||||
:::important[Default ID]
|
||||
|
||||
If you do not add an ID then Multi-Scrobbler will automatically use the **name** of the Source/Client as the ID. The name is shown in the Dashboard.
|
||||
**If you do not add an ID then Multi-Scrobbler will automatically use the name of the Source/Client as the ID.** The name is shown in the Dashboard.
|
||||
|
||||
If you decide to add an ID later you must use the name as the ID in order to keep Plays/Scobbles associated with the same config.
|
||||
If you decide to add an ID later, you must use the Source/Client name as the ID in order to keep Plays/Scobbles associated with the same config.
|
||||
|
||||
:::
|
||||
|
||||
@@ -108,13 +110,19 @@ If you decide to add an ID later you must use the name as the ID in order to kee
|
||||
|
||||
:::tip
|
||||
|
||||
Before upgrading, if your MS dashboard shows 0 queued/failed for all [Scrobble Clients](/configuration/clients) then you can **ignore this step.**
|
||||
Before upgrading, if your MS dashboard shows 0 queued/failed for all [Scrobble Clients](/configuration/clients) then you can **ignore this section.**
|
||||
|
||||
:::
|
||||
|
||||
:::important
|
||||
|
||||
The cached scrobbles for a Client/Source were based on its **name**. If you plan on changing the name of a Client/Source with this upgrade you should do it **after** this migration has run or else MS will not be able to find any associated cached scrobbles.
|
||||
|
||||
:::
|
||||
|
||||
MS `0.14.0` introduces a database for persisting Plays/Scrobbles, queues, and other data. Prior to `0.14.0`, queued/failed scrobbles were stored in a cache **file** inside your [`CONFIG_DIR`](/installation/?dockerSetting=storage#recommended-settings) named `ms-scrobble.cache`.
|
||||
|
||||
When a [Client](/configuration/clients) is created and **enabled** in `0.14.0` it will attempt to automatically migrate scrobbles from this cache into the database.
|
||||
When a [Client](/configuration/clients) is created and **enabled** in `0.14.0` it will attempt to **automatically migrate scrobbles** from this cache into the database.
|
||||
|
||||
* You can see progress/results for this process by filtering logs for `[Cached Scrobble Migration]`
|
||||
* If the process fails MS will attempt to migrate failed scrobbles, again, the next time it is started
|
||||
@@ -140,7 +148,7 @@ In `0.14.0` [Cache](/configuration#caching) has been simplified with much of the
|
||||
|
||||
This only applies to users who have `cache` in their [AIO Config](/configuration/?configType=aio#configuration-types) `config.json` or are using `CACHE_*` [ENV Config](/configuration/?configType=env#configuration-types)
|
||||
|
||||
If you do not have any of the above [Cache](/configuration#caching) configuration defined then you can **ignore this step.**
|
||||
If you do not have any of the above [Cache](/configuration#caching) configuration defined then you can **ignore this section.**
|
||||
|
||||
:::
|
||||
|
||||
@@ -210,9 +218,41 @@ Remove the `connection` property from the `auth` object. If using `file` the ent
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Renamed ENV Keys
|
||||
## Renamed ENV Keys
|
||||
|
||||
In an effort to standard ENV prefixes some ENV keys have been renamed:
|
||||
|
||||
* `LZENDPOINT_ENABLE` => `LZE_ENABLE`
|
||||
* `LFMENDPOINT_ENABLE` => `LFM_ENABLE`
|
||||
* `LFMENDPOINT_ENABLE` => `LFM_ENABLE`
|
||||
|
||||
## (Optional) ENV Client/Source Names
|
||||
|
||||
In the MS Dashboard, prior to 0.14.0, UI Client/Sources configured with [ENV Config](/configuration/?configType=env#configuration-types) always had generated names like `unnamed-koito`.
|
||||
|
||||
ENV Config Clients/Sources can now be configured with a name by using the `_NAME` suffix, example:
|
||||
|
||||
```yaml title="compose.yaml"
|
||||
services:
|
||||
multi-scrobbler:
|
||||
# ...
|
||||
environment:
|
||||
- KOTIO_TOKEN=...
|
||||
# ...
|
||||
- KOITO_ID=myKoitoID
|
||||
// highlight-start
|
||||
- KOITO_NAME=Koito A
|
||||
// highlight-end
|
||||
|
||||
- JELLYFIN_URL=...
|
||||
# ..
|
||||
- JELLYFIN_ID=myJellyfinID
|
||||
// highlight-start
|
||||
- JELLYFIN_NAME=Foxx Jelly
|
||||
// highlight-end
|
||||
```
|
||||
|
||||
:::important
|
||||
|
||||
If you have **cached scrobbles** make sure to upgrade and run [Cached Scrobble Migration](#cached-scrobble-migration) **before** adding names to Client/Sources.
|
||||
|
||||
:::
|
||||
|
||||
Generated
+30
@@ -22,6 +22,8 @@
|
||||
"clsx": "^2.0.0",
|
||||
"docusaurus-plugin-sass": "^0.2.6",
|
||||
"docusaurus-theme-github-codeblock": "^2.0.2",
|
||||
"embla-carousel": "^9.0.0-rc02",
|
||||
"embla-carousel-react": "^9.0.0-rc02",
|
||||
"json5": "^2.2.3",
|
||||
"micromark-extension-directive": "^3.0.1",
|
||||
"modern-react-json-editor": "^1.0.2",
|
||||
@@ -10863,6 +10865,34 @@
|
||||
"integrity": "sha512-/4t+AS1l4S3ZC0Ja7PHFIWeBIxGA3QGqV8/yKsP36v7NcyUCl+bIcmw6s5zVuMIECWwBrAK/6QLzTmbJChBboQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/embla-carousel": {
|
||||
"version": "9.0.0-rc02",
|
||||
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-9.0.0-rc02.tgz",
|
||||
"integrity": "sha512-GOQBX6v5cNk/ptfuHUoHhW9rvl5KeSGsBoppaUqtihu2kDmQAyzvnH/95r3TaSScFSJTj68V9AM6E5nq3jUVcQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/embla-carousel-react": {
|
||||
"version": "9.0.0-rc02",
|
||||
"resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-9.0.0-rc02.tgz",
|
||||
"integrity": "sha512-DpcD0kTv6L31KvBcrx5d57E6T2rP/YceTGAdq7llkpKFdfM1of8YotHVEQTNIU3I9o5mjE/QxLi1ndJmm1DikA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"embla-carousel": "9.0.0-rc02",
|
||||
"embla-carousel-reactive-utils": "9.0.0-rc02"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/embla-carousel-reactive-utils": {
|
||||
"version": "9.0.0-rc02",
|
||||
"resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-9.0.0-rc02.tgz",
|
||||
"integrity": "sha512-H1c+yUbcVST9eJoGU+05L1vSDyBs0hf/Gf1OadHYIJ3asBfyvn6oaGCs2wXq1D8/f09y0wwCxeRxT9UZU/TxJA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"embla-carousel": "9.0.0-rc02"
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
"clsx": "^2.0.0",
|
||||
"docusaurus-plugin-sass": "^0.2.6",
|
||||
"docusaurus-theme-github-codeblock": "^2.0.2",
|
||||
"embla-carousel": "^9.0.0-rc02",
|
||||
"embla-carousel-react": "^9.0.0-rc02",
|
||||
"json5": "^2.2.3",
|
||||
"micromark-extension-directive": "^3.0.1",
|
||||
"modern-react-json-editor": "^1.0.2",
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import React, {
|
||||
ComponentPropsWithRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState
|
||||
} from 'react'
|
||||
import { EmblaCarouselType } from 'embla-carousel'
|
||||
|
||||
type UsePrevNextButtonsType = {
|
||||
prevBtnDisabled: boolean
|
||||
nextBtnDisabled: boolean
|
||||
onPrevButtonClick: () => void
|
||||
onNextButtonClick: () => void
|
||||
}
|
||||
|
||||
export const usePrevNextButtons = (
|
||||
emblaApi: EmblaCarouselType | undefined
|
||||
): UsePrevNextButtonsType => {
|
||||
const [prevBtnDisabled, setPrevBtnDisabled] = useState(true)
|
||||
const [nextBtnDisabled, setNextBtnDisabled] = useState(true)
|
||||
|
||||
const onPrevButtonClick = useCallback(() => {
|
||||
if (!emblaApi) return
|
||||
emblaApi.goToPrev()
|
||||
}, [emblaApi])
|
||||
|
||||
const onNextButtonClick = useCallback(() => {
|
||||
if (!emblaApi) return
|
||||
emblaApi.goToNext()
|
||||
}, [emblaApi])
|
||||
|
||||
const onSelect = useCallback((emblaApi: EmblaCarouselType) => {
|
||||
setPrevBtnDisabled(!emblaApi.canGoToPrev())
|
||||
setNextBtnDisabled(!emblaApi.canGoToNext())
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!emblaApi) return
|
||||
|
||||
onSelect(emblaApi)
|
||||
emblaApi.on('reinit', onSelect).on('select', onSelect)
|
||||
}, [emblaApi, onSelect])
|
||||
|
||||
return {
|
||||
prevBtnDisabled,
|
||||
nextBtnDisabled,
|
||||
onPrevButtonClick,
|
||||
onNextButtonClick
|
||||
}
|
||||
}
|
||||
|
||||
type PropType = ComponentPropsWithRef<'button'>
|
||||
|
||||
export const PrevButton = (props: PropType) => {
|
||||
const { children, disabled, ...restProps } = props
|
||||
|
||||
return (
|
||||
<button
|
||||
className={'embla__button embla__button--prev'.concat(
|
||||
disabled ? ' embla__button--disabled' : ''
|
||||
)}
|
||||
type="button"
|
||||
{...restProps}
|
||||
>
|
||||
<svg className="embla__button__svg" viewBox="0 0 532 532">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M355.66 11.354c13.793-13.805 36.208-13.805 50.001 0 13.785 13.804 13.785 36.238 0 50.034L201.22 266l204.442 204.61c13.785 13.805 13.785 36.239 0 50.044-13.793 13.796-36.208 13.796-50.002 0a5994246.277 5994246.277 0 0 0-229.332-229.454 35.065 35.065 0 0 1-10.326-25.126c0-9.2 3.393-18.26 10.326-25.2C172.192 194.973 332.731 34.31 355.66 11.354Z"
|
||||
/>
|
||||
</svg>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export const NextButton = (props: PropType) => {
|
||||
const { children, disabled, ...restProps } = props
|
||||
|
||||
return (
|
||||
<button
|
||||
className={'embla__button embla__button--next'.concat(
|
||||
disabled ? ' embla__button--disabled' : ''
|
||||
)}
|
||||
type="button"
|
||||
{...restProps}
|
||||
>
|
||||
<svg className="embla__button__svg" viewBox="0 0 532 532">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M176.34 520.646c-13.793 13.805-36.208 13.805-50.001 0-13.785-13.804-13.785-36.238 0-50.034L330.78 266 126.34 61.391c-13.785-13.805-13.785-36.239 0-50.044 13.793-13.796 36.208-13.796 50.002 0 22.928 22.947 206.395 206.507 229.332 229.454a35.065 35.065 0 0 1 10.326 25.126c0 9.2-3.393 18.26-10.326 25.2-45.865 45.901-206.404 206.564-229.332 229.52Z"
|
||||
/>
|
||||
</svg>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
:root[data-theme="light"] {
|
||||
--text-body: rgb(54, 49, 61);
|
||||
--text-comment: rgb(99, 94, 105);
|
||||
--text-high-contrast: rgb(49, 49, 49);
|
||||
--text-medium-contrast: rgb(99, 94, 105);
|
||||
--text-low-contrast: rgb(116, 109, 118);
|
||||
--detail-high-contrast: rgb(192, 192, 192);
|
||||
--detail-medium-contrast: rgb(234, 234, 234);
|
||||
--detail-low-contrast: rgb(240, 240, 242);
|
||||
--text-body-rgb-value: 54, 49, 61;
|
||||
--text-comment-rgb-value: 99, 94, 105;
|
||||
--text-high-contrast-rgb-value: 49, 49, 49;
|
||||
--text-medium-contrast-rgb-value: 99, 94, 105;
|
||||
--text-low-contrast-rgb-value: 116, 109, 118;
|
||||
--detail-high-contrast-rgb-value: 192, 192, 192;
|
||||
--detail-medium-contrast-rgb-value: 234, 234, 234;
|
||||
--detail-low-contrast-rgb-value: 240, 240, 242;
|
||||
}
|
||||
:root[data-theme="dark"] {
|
||||
--text-body: rgb(222, 222, 222);
|
||||
--text-comment: rgb(170, 170, 170);
|
||||
--text-high-contrast: rgb(230, 230, 230);
|
||||
--text-medium-contrast: rgb(202, 202, 202);
|
||||
--text-low-contrast: rgb(170, 170, 170);
|
||||
--detail-high-contrast: rgb(101, 101, 101);
|
||||
--detail-medium-contrast: rgb(25, 25, 25);
|
||||
--detail-low-contrast: rgb(21, 21, 21);
|
||||
--text-body-rgb-value: 222, 222, 222;
|
||||
--text-comment-rgb-value: 170, 170, 170;
|
||||
--text-high-contrast-rgb-value: 230, 230, 230;
|
||||
--text-medium-contrast-rgb-value: 202, 202, 202;
|
||||
--text-low-contrast-rgb-value: 170, 170, 170;
|
||||
--detail-high-contrast-rgb-value: 101, 101, 101;
|
||||
--detail-medium-contrast-rgb-value: 25, 25, 25;
|
||||
--detail-low-contrast-rgb-value: 21, 21, 21;
|
||||
}
|
||||
|
||||
.embla {
|
||||
max-width: 48rem;
|
||||
margin: auto;
|
||||
|
||||
--slide-height: 33rem;
|
||||
--slide-spacing: 1rem;
|
||||
--slide-size: 100%;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.embla__viewport {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.embla__container {
|
||||
display: flex;
|
||||
touch-action: pan-y pinch-zoom;
|
||||
margin-left: calc(var(--slide-spacing) * -1);
|
||||
}
|
||||
|
||||
.embla__slide {
|
||||
transform: translate3d(0, 0, 0);
|
||||
flex: 0 0 var(--slide-size);
|
||||
min-width: 0;
|
||||
padding-left: var(--slide-spacing);
|
||||
|
||||
}
|
||||
|
||||
.embla__slide__container {
|
||||
border: 0.15rem solid transparent;
|
||||
border-radius: 1.2rem;
|
||||
display: flex;
|
||||
align-items: normal;
|
||||
justify-content: center;
|
||||
height: var(--slide-height);
|
||||
user-select: none;
|
||||
overflow:hidden;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.imageCaption {
|
||||
font-style: italic;
|
||||
font-size: 0.8em;
|
||||
text-wrap-style: balance;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.embla__controls {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
justify-content: space-between;
|
||||
gap: 1.2rem;
|
||||
// margin-top: 1.8rem;
|
||||
}
|
||||
|
||||
.embla__buttons {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 0.6rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.embla__button {
|
||||
-webkit-tap-highlight-color: rgba(var(--text-high-contrast-rgb-value), 0.5);
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background-color: transparent;
|
||||
touch-action: manipulation;
|
||||
display: inline-flex;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0.2rem solid var(--detail-medium-contrast);
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
|
||||
z-index: 1;
|
||||
border-radius: 50%;
|
||||
color: var(--text-body);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
.embla__button--disabled {
|
||||
color: var(--detail-high-contrast);
|
||||
}
|
||||
|
||||
.embla__button__svg {
|
||||
width: 35%;
|
||||
height: 35%;
|
||||
}
|
||||
|
||||
.embla__selected-snap-display {
|
||||
justify-self: flex-end;
|
||||
align-self: center;
|
||||
color: var(--text-low-contrast);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.embla__slide:nth-child(1) {
|
||||
flex: 0 0 90%;
|
||||
}
|
||||
.embla__slide:nth-child(2) {
|
||||
flex: 0 0 60%;
|
||||
}
|
||||
.embla__slide:nth-child(3) {
|
||||
flex: 0 0 75%;
|
||||
}
|
||||
.embla__slide:nth-child(4) {
|
||||
flex: 0 0 80%;
|
||||
}
|
||||
.embla__slide:nth-child(5) {
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
.embla__slide:nth-child(6) {
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user