mirror of
https://github.com/FoxxMD/multi-scrobbler.git
synced 2026-09-03 05:10:00 +03:00
Compare commits
382
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
38d7a9cfce | ||
|
|
83a425a3d4 | ||
|
|
7ae727a8fe | ||
|
|
fe4b947d26 | ||
|
|
c5cdc465f3 | ||
|
|
c86819b0b3 | ||
|
|
5228d4894c | ||
|
|
e6709c442f | ||
|
|
0a594d5f83 | ||
|
|
49be95a4d1 | ||
|
|
fbf3af8474 | ||
|
|
146916bc6d | ||
|
|
a410721acf | ||
|
|
af5aec2ed3 | ||
|
|
51b7b3fd40 | ||
|
|
c22867a9b1 | ||
|
|
f93c1fa4db | ||
|
|
1f199e28ef | ||
|
|
811615dd06 | ||
|
|
fc6ec840da | ||
|
|
eb2159de27 | ||
|
|
d0a490b200 | ||
|
|
53870ac1f2 | ||
|
|
d2f8717555 | ||
|
|
756d9b84e1 | ||
|
|
a1ad250dcc | ||
|
|
f36fd16517 | ||
|
|
28ade6ac28 | ||
|
|
fa1051b29a | ||
|
|
3cdbfc337e | ||
|
|
80474a96fe | ||
|
|
957db35c30 | ||
|
|
90f0bdb5ef | ||
|
|
640d944b4c | ||
|
|
c7fce06480 | ||
|
|
8cffd0d068 | ||
|
|
b8a9623def | ||
|
|
d922f37359 | ||
|
|
dcc53d1656 | ||
|
|
0cb20b83b4 | ||
|
|
e814cf58fd | ||
|
|
f211a4a4c4 | ||
|
|
4d7162a1a4 | ||
|
|
a5926d71c1 | ||
|
|
d95f323dc0 | ||
|
|
1b53d22e00 | ||
|
|
75c0230dae | ||
|
|
630edd7b3f | ||
|
|
092cf907a2 | ||
|
|
562b388e3e | ||
|
|
f788894494 | ||
|
|
d0379e1bd6 | ||
|
|
5eb967f61c | ||
|
|
0bf9d5e786 | ||
|
|
102f9795f8 | ||
|
|
7a7d7bb975 | ||
|
|
246e9ad7c5 | ||
|
|
d772c8c8cd | ||
|
|
48b6b555c1 | ||
|
|
8be56457e2 | ||
|
|
d9a5aa1ce0 | ||
|
|
f6799955a0 | ||
|
|
077c3d33b5 | ||
|
|
524afb60a9 | ||
|
|
12ba99437c | ||
|
|
8882e7ab87 | ||
|
|
8de382f581 | ||
|
|
691db9215c | ||
|
|
22c0916544 | ||
|
|
cbe996db95 | ||
|
|
8fd870b9ef | ||
|
|
272c3cc8be | ||
|
|
54da8b0add | ||
|
|
cfb4131d5d | ||
|
|
b8494986d5 | ||
|
|
f9f923041f | ||
|
|
5774be1d22 | ||
|
|
4bc460f598 | ||
|
|
0f6aa56fe5 | ||
|
|
439f0e05aa | ||
|
|
952ddbdcc3 | ||
|
|
765b13f910 | ||
|
|
47caf8de8c | ||
|
|
3c3faf22fc | ||
|
|
f94538ea1f | ||
|
|
e9b2990e15 | ||
|
|
3ef48547a7 | ||
|
|
047de49ee6 | ||
|
|
2a52d1e050 | ||
|
|
8f049a089b | ||
|
|
adf6b9ed58 | ||
|
|
59d1050b2b | ||
|
|
561c62b426 | ||
|
|
f38f10d2a0 | ||
|
|
7aa5595cc9 | ||
|
|
46389fd81d | ||
|
|
c0384317b1 | ||
|
|
6e66cb06a9 | ||
|
|
cb1ff7d030 | ||
|
|
3b5468499a | ||
|
|
c7361a8976 | ||
|
|
60a6ddbc41 | ||
|
|
92e027bfc4 | ||
|
|
b388ca6106 | ||
|
|
84b43d7c39 | ||
|
|
99ebb53fc6 | ||
|
|
cda33ba04f | ||
|
|
dda73cd96a | ||
|
|
5771fbf496 | ||
|
|
e9d1c81380 | ||
|
|
d59cb2668d | ||
|
|
3c51149d9a | ||
|
|
40234e64b3 | ||
|
|
2df8bfe6aa | ||
|
|
fec39b2e2e | ||
|
|
83f501df0d | ||
|
|
c40add7d70 | ||
|
|
569c5a8690 | ||
|
|
11f7299c0e | ||
|
|
8a6ae698a1 | ||
|
|
d6ed03028d | ||
|
|
2818d1d275 | ||
|
|
8127e4b6ce | ||
|
|
8e829a53b5 | ||
|
|
1562f8bffc | ||
|
|
e04514f6a1 | ||
|
|
00e9755fb1 | ||
|
|
5ba4729f83 | ||
|
|
e5df4f2752 | ||
|
|
d11db4cb8d | ||
|
|
14f5f60c11 | ||
|
|
4d7eadbd38 | ||
|
|
747b62289a | ||
|
|
1eeb5e6b08 | ||
|
|
ac01348266 | ||
|
|
3028f83d47 | ||
|
|
a02d43acea | ||
|
|
c7679a0a62 | ||
|
|
d11ac3d6ed | ||
|
|
8a09c91713 | ||
|
|
81d24f1753 | ||
|
|
5be12aab15 | ||
|
|
47c600ed8d | ||
|
|
5f84fefd5d | ||
|
|
a143aa4193 | ||
|
|
23a3dfa5d1 | ||
|
|
3005d36db0 | ||
|
|
ada9d70be9 | ||
|
|
9d839db2e1 | ||
|
|
ca20b23fbc | ||
|
|
577cff3abf | ||
|
|
c4786bdfe2 | ||
|
|
df0da7054c | ||
|
|
272a336a22 | ||
|
|
ec4a51074e | ||
|
|
5a4bcb2949 | ||
|
|
77387c292b | ||
|
|
4caf1c8cdb | ||
|
|
981805aa96 | ||
|
|
60e3242c44 | ||
|
|
e7e1417b0b | ||
|
|
bf765be174 | ||
|
|
e27c301932 | ||
|
|
fc4606585d | ||
|
|
fd80101a7a | ||
|
|
2cbc5542d1 | ||
|
|
6f15f15722 | ||
|
|
ff1d9248c6 | ||
|
|
218ece8f5c | ||
|
|
3adac7c6e0 | ||
|
|
1c78da7985 | ||
|
|
457279465c | ||
|
|
d551c8563f | ||
|
|
75bd4a04d6 | ||
|
|
339232c368 | ||
|
|
8741ff25ea | ||
|
|
8a952cb576 | ||
|
|
eeb55aa02e | ||
|
|
6a81fc1be2 | ||
|
|
a47e2a6242 | ||
|
|
dadaa6df35 | ||
|
|
fb19191209 | ||
|
|
2f53a60da1 | ||
|
|
ab2f397fab | ||
|
|
db2dd8fb8b | ||
|
|
135439f509 | ||
|
|
d743b849fe | ||
|
|
9885768cad | ||
|
|
c6e6bbb87b | ||
|
|
bbc2351ebc | ||
|
|
b547db9045 | ||
|
|
28c2e9873a | ||
|
|
6bcd3f9516 | ||
|
|
11b2632d02 | ||
|
|
fb6dbe307d | ||
|
|
2474b792c3 | ||
|
|
3007c949a0 | ||
|
|
63533534df | ||
|
|
5c03062976 | ||
|
|
dbe41fa5ab | ||
|
|
5454f817e5 | ||
|
|
8d8f8260b5 | ||
|
|
62bb00571f | ||
|
|
c10537d658 | ||
|
|
e9ccc319e9 | ||
|
|
54803c6ef9 | ||
|
|
a3d37a864a | ||
|
|
394570e5a3 | ||
|
|
2bbfdf424f | ||
|
|
9785ad6ba7 | ||
|
|
fe7d77c03d | ||
|
|
0e7aa92f5b | ||
|
|
2ff80283b9 | ||
|
|
4f34b1a055 | ||
|
|
4effdd6df5 | ||
|
|
af3ce9dea7 | ||
|
|
f0c5cb524f | ||
|
|
46883384b6 | ||
|
|
7bc5ab3d0f | ||
|
|
fd15d15124 | ||
|
|
14da0795f1 | ||
|
|
3faf0ee7f9 | ||
|
|
78ddb15dcf | ||
|
|
760373c227 | ||
|
|
6bb7b17e97 | ||
|
|
231156d452 | ||
|
|
d85628b4c9 | ||
|
|
db0e74aec3 | ||
|
|
a918a96d34 | ||
|
|
bb51915085 | ||
|
|
7c177dfe03 | ||
|
|
cf5e7ca1fe | ||
|
|
fd43ff86c8 | ||
|
|
194a0e78ff | ||
|
|
04027e87e7 | ||
|
|
a998060bca | ||
|
|
5b16e8857e | ||
|
|
c53ce7c517 | ||
|
|
7897dc9503 | ||
|
|
160ddac3e4 | ||
|
|
59b4f27294 | ||
|
|
21f42bf45b | ||
|
|
3e8f170f9a | ||
|
|
15fbed09a5 | ||
|
|
52b301e729 | ||
|
|
0bef197521 | ||
|
|
f16750609a | ||
|
|
c39ef29597 | ||
|
|
72639616b7 | ||
|
|
effe870104 | ||
|
|
ee753389f7 | ||
|
|
5f4a5cb815 | ||
|
|
0607aedf04 | ||
|
|
1f165096b3 | ||
|
|
fb6f6a18c8 | ||
|
|
03f8b20704 |
@@ -30,7 +30,9 @@
|
||||
"dbaeumer.vscode-eslint",
|
||||
"unifiedjs.vscode-mdx",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"TakumiI.markdowntable"
|
||||
"TakumiI.markdowntable",
|
||||
"YoavBls.pretty-ts-errors",
|
||||
"bowlerr.sqlite-intelliview-vscode"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
+3
-1
@@ -13,6 +13,7 @@ config/*.json
|
||||
config/*.bak
|
||||
config/*.p8
|
||||
/docs
|
||||
dist
|
||||
/logs
|
||||
.flatpak-builder
|
||||
**/generated-sources.*
|
||||
@@ -30,4 +31,5 @@ tmp-*
|
||||
.env
|
||||
*.env
|
||||
.storybook
|
||||
.stories
|
||||
.stories
|
||||
**/lexicons/**/*.json
|
||||
@@ -6,6 +6,11 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm" # See documentation for possible values
|
||||
groups:
|
||||
sec-updates:
|
||||
applies-to: "security-updates"
|
||||
patterns:
|
||||
- "*"
|
||||
directories:
|
||||
- "/"
|
||||
- "/docsite"
|
||||
@@ -17,3 +22,16 @@ updates:
|
||||
# Check for updates once a week
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
groups:
|
||||
sec-updates:
|
||||
applies-to: "security-updates"
|
||||
patterns:
|
||||
- "*"
|
||||
version-updates:
|
||||
applies-to: "version-updates"
|
||||
patterns:
|
||||
- "*"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -1,6 +1,6 @@
|
||||
## Checklist before requesting a review
|
||||
|
||||
- [ ] I have read the [contributing guidelines.](../CONTRIBUTING.md)
|
||||
- [ ] I have read the [contributing guidelines.](../blob/master/CONTRIBUTING.md)
|
||||
|
||||
## Type of change
|
||||
|
||||
|
||||
@@ -12,12 +12,12 @@ jobs:
|
||||
name: Deploy to GitHub Pages
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 #v4.3.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 #4.4.0
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f #6.3.0
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
cache: npm
|
||||
|
||||
- name: Install app dev dependencies
|
||||
@@ -28,22 +28,24 @@ jobs:
|
||||
- name: Install docusaurus dependencies
|
||||
run: npm ci
|
||||
working-directory: ./docsite
|
||||
|
||||
- name: Build website
|
||||
env:
|
||||
ANALYTICS: ${{ vars.ANALYTICS }}
|
||||
ANALYTICS_DOMAIN: ${{ vars.ANALYTICS_DOMAIN }}
|
||||
RY_ANALYTICS: ${{ vars.RY_ANALYTICS }}
|
||||
RY_ANALYTICS_SITEID: ${{ vars.RY_ANALYTICS_SITEID }}
|
||||
RY_ANALYTICS_REPLAY: ${{ vars.RY_ANALYTICS_REPLAY }}
|
||||
GSITEVERIFICATION: ${{ vars.GSITEVERIFICATION }}
|
||||
DOCS_BASE: '/multi-scrobbler'
|
||||
run: npm run build
|
||||
working-directory: ./docsite
|
||||
|
||||
- name: Replace canonical
|
||||
working-directory: ./docsite
|
||||
run: node --experimental-strip-types canonical-replace.ts ./build
|
||||
|
||||
# Popular action to deploy to GitHub Pages:
|
||||
# Docs: https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-docusaurus
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: peaceiris/actions-gh-pages@373f7f263a76c20808c831209c920827a82a2847 #3.9.3
|
||||
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e #4.0.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Build output to publish to the `gh-pages` branch:
|
||||
@@ -55,3 +57,24 @@ jobs:
|
||||
# You can swap them out with your own user credentials.
|
||||
user_name: github-actions[bot]
|
||||
user_email: 41898282+github-actions[bot]@users.noreply.github.com
|
||||
|
||||
- name: Trigger dependent build
|
||||
if: ${{ vars.DOCS_REPO != ''}}
|
||||
run: |
|
||||
# Trigger builds in dependent repositories
|
||||
curl -X POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${{ secrets.DOCS_DISPATCH_TOKEN }}" \
|
||||
"https://api.github.com/repos/${{ vars.DOCS_REPO }}/dispatches" \
|
||||
-d "{
|
||||
\"event_type\": \"release\",
|
||||
\"client_payload\": {
|
||||
\"commit\": \"${{ github.sha }}\"
|
||||
}
|
||||
}"
|
||||
- name: Trigger CF Pages build
|
||||
if: ${{ vars.CF_PAGES == 'true' }}
|
||||
run: |
|
||||
# trigger CF pages hook deployment
|
||||
curl -d "" \
|
||||
"${{ secrets.CF_PAGES_HOOK }}"
|
||||
@@ -51,7 +51,9 @@ jobs:
|
||||
strings=("${VARS_DOCKERHUB_SLUG}" "${VARS_GHCR_SLUG}")
|
||||
for i in ${!strings[@]}; do [[ -z ${strings[i]} ]] && unset strings[i]; done
|
||||
joined_string=$(IFS=, ; echo "${strings[*]}")
|
||||
echo "REGISTRIES_JOINED=$joined_string" >> $GITHUB_ENV
|
||||
echo "REGISTRIES_JOINED=$joined_string" >> $GITHUB_ENV
|
||||
date=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
echo "BUILD_DATE=$date" >> $GITHUB_ENV
|
||||
env:
|
||||
VARS_DOCKERHUB_SLUG: ${{ vars.DOCKERHUB_SLUG }}
|
||||
VARS_GHCR_SLUG: ${{vars.GHCR_SLUG}}
|
||||
@@ -104,6 +106,7 @@ jobs:
|
||||
context: ${{github.event.pull_request.head.repo.clone_url}}#${{github.event.pull_request.head.sha}}
|
||||
build-args: |
|
||||
APP_BUILD_VERSION=${{steps.appversion.outputs.appversion}}
|
||||
BUILD_DATE=${{ env.BUILD_DATE }}
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=image,"name=${{ env.REGISTRIES_JOINED }}",push-by-digest=true,name-canonical=true,push=true
|
||||
@@ -117,7 +120,7 @@ jobs:
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 #4.6.2
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f #7.0.0
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: /tmp/digests/*
|
||||
@@ -136,7 +139,7 @@ jobs:
|
||||
- test
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 #4.3.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #8.0.1
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
@@ -162,7 +165,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata (tags, labels)
|
||||
id: meta
|
||||
uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 #5.9.0
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf #6.0.0
|
||||
with:
|
||||
images: |
|
||||
${{ vars.DOCKERHUB_SLUG }}
|
||||
@@ -220,7 +223,7 @@ jobs:
|
||||
if: contains(github.event.pull_request.labels.*.name, 'safe to test')
|
||||
steps:
|
||||
- name: Create comment
|
||||
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 #2.9.4
|
||||
uses: marocchino/sticky-pull-request-comment@d4d6b0936434b21bc8345ad45a440c5f7d2c40ff #3.0.3
|
||||
with:
|
||||
recreate: true
|
||||
header: "pr-release"
|
||||
|
||||
@@ -66,13 +66,15 @@ jobs:
|
||||
for i in ${!strings[@]}; do [[ -z ${strings[i]} ]] && unset strings[i]; done
|
||||
joined_string=$(IFS=, ; echo "${strings[*]}")
|
||||
echo "REGISTRIES_JOINED=$joined_string" >> $GITHUB_ENV
|
||||
date=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
echo "BUILD_DATE=$date" >> $GITHUB_ENV
|
||||
env:
|
||||
VARS_DOCKERHUB_SLUG: ${{ vars.DOCKERHUB_SLUG }}
|
||||
VARS_GHCR_SLUG: ${{vars.GHCR_SLUG}}
|
||||
|
||||
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 #v4.3.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -126,6 +128,7 @@ jobs:
|
||||
with:
|
||||
build-args: |
|
||||
APP_BUILD_VERSION=${{steps.appversion.outputs.appversion}}
|
||||
BUILD_DATE=${{ env.BUILD_DATE }}
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=image,"name=${{ env.REGISTRIES_JOINED }}",push-by-digest=true,name-canonical=true,push=true
|
||||
@@ -140,7 +143,7 @@ jobs:
|
||||
env:
|
||||
VARS_DIGEST: ${{ steps.build.outputs.digest }}
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 #4.6.2
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f #7.0.0
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: /tmp/digests/*
|
||||
@@ -159,7 +162,7 @@ jobs:
|
||||
- test
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 #4.3.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #8.0.1
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
@@ -185,7 +188,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata (tags, labels)
|
||||
id: meta
|
||||
uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 #5.9.0
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf #6.0.0
|
||||
with:
|
||||
images: |
|
||||
${{ vars.DOCKERHUB_SLUG }}
|
||||
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
node-version:
|
||||
description: "Node version"
|
||||
required: false
|
||||
default: '20.x'
|
||||
default: '24.x'
|
||||
type: string
|
||||
ref:
|
||||
description: "Ref to checkout"
|
||||
@@ -18,13 +18,18 @@ jobs:
|
||||
name: Tests / Build / Sanity Run
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 #v4.3.0
|
||||
- name: Check out the repo (gha)
|
||||
if: ${{ !env.ACT }}
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Check out the repo (act)
|
||||
if: ${{ env.ACT }}
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 #4.4.0
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f #6.3.0
|
||||
with:
|
||||
node-version: ${{ inputs.node-version }}
|
||||
cache: 'npm'
|
||||
|
||||
@@ -151,3 +151,8 @@ tmp-*
|
||||
|
||||
*storybook.log
|
||||
storybook-static
|
||||
|
||||
*.db
|
||||
*.db*
|
||||
|
||||
**/lexicons/**/*.json
|
||||
@@ -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, {
|
||||
|
||||
@@ -3,8 +3,12 @@ import addonA11y from "@storybook/addon-a11y";
|
||||
import { definePreview } from "@storybook/react-vite";
|
||||
|
||||
import { themes } from 'storybook/theming';
|
||||
import { initialize, mswLoader } from 'msw-storybook-addon';
|
||||
|
||||
initialize();
|
||||
|
||||
export default definePreview({
|
||||
loaders: [mswLoader],
|
||||
parameters: {
|
||||
controls: {
|
||||
matchers: {
|
||||
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"sanjulaganepola.github-local-actions",
|
||||
"github.vscode-github-actions"
|
||||
]
|
||||
}
|
||||
Vendored
+13
@@ -1,6 +1,19 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Run Canonical Replace",
|
||||
"program": "${workspaceFolder}/docsite/canonical-replace.ts",
|
||||
"runtimeArgs": ["--experimental-strip-types"],
|
||||
"args": [
|
||||
"${workspaceFolder}/docsite/build"
|
||||
],
|
||||
"request": "launch",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"type": "node"
|
||||
},
|
||||
{
|
||||
"name": "dev",
|
||||
"type": "node",
|
||||
|
||||
+22
-4
@@ -65,7 +65,8 @@ FROM base AS build
|
||||
|
||||
COPY --chown=abc:abc package*.json tsconfig.json ./
|
||||
COPY --chown=abc:abc patches ./patches
|
||||
COPY --chown=abc:abc docsite/package*.json tsconfig.json ./docsite/
|
||||
COPY --chown=abc:abc docsite/patches ./docsite/patches
|
||||
COPY --chown=abc:abc docsite/package*.json docsite/tsconfig* ./docsite/
|
||||
|
||||
# for debugging, so the build fails faster when timing out (arm64)
|
||||
#RUN npm config set fetch-retries 1 && \
|
||||
@@ -92,18 +93,21 @@ RUN npm run build:parallel && rm -rf node_modules && rm -rf docsite/node_modules
|
||||
|
||||
FROM base AS app
|
||||
|
||||
LABEL org.opencontainers.image.source="https://github.com/FoxxMD/multi-scrobbler"
|
||||
|
||||
COPY --chown=abc:abc *.json *.js *.ts index.html ./
|
||||
COPY --chown=abc:abc patches ./patches
|
||||
# frontend build from vite/esbuild
|
||||
COPY --from=build --chown=abc:abc /app/dist /app/dist
|
||||
COPY --from=build --chown=abc:abc /app/src /app/src
|
||||
# backend
|
||||
COPY --from=build --chown=abc:abc /app/src/backend /app/src/backend
|
||||
COPY --from=build --chown=abc:abc /app/src/core /app/src/core
|
||||
# docusaurus docs
|
||||
COPY --from=build --chown=abc:abc /app/docsite /app/docsite
|
||||
COPY --from=base /usr/bin /usr/bin
|
||||
COPY --from=base /usr/lib /usr/lib
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV IS_DOCKER=true
|
||||
ENV COLORED_STD=true
|
||||
|
||||
# https://stackoverflow.com/a/63640896/1469797
|
||||
ARG APP_BUILD_VERSION
|
||||
@@ -114,8 +118,22 @@ RUN npm ci --omit=dev --no-audit \
|
||||
&& npm uninstall ts-json-schema-generator \
|
||||
&& npm cache clean --force \
|
||||
&& chown -R abc:abc node_modules \
|
||||
&& npx @usex/prune-mod -w \
|
||||
&& rm -rf /root/.cache
|
||||
|
||||
ARG BUILD_DATE=0
|
||||
|
||||
LABEL org.opencontainers.image.version="$APP_BUILD_VERSION" \
|
||||
org.opencontainers.image.source="https://github.com/FoxxMD/multi-scrobbler" \
|
||||
org.opencontainers.image.documentation="https://docs.multi-scrobbler.app" \
|
||||
org.opencontainers.image.description="Scrobble from multiple sources to multiple clients" \
|
||||
org.opencontainers.image.title="Multi-Scrobbler" \
|
||||
org.opencontainers.image.licenses="MIT" \
|
||||
org.opencontainers.image.authors="FoxxMD" \
|
||||
org.opencontainers.image.created="$BUILD_DATE" \
|
||||
org.opencontainers.image.url="https://docs.multi-scrobbler.app" \
|
||||
maintainer="FoxxMD" \
|
||||
build_version="$APP_BUILD_VERSION"
|
||||
|
||||
ARG webPort=9078
|
||||
ENV PORT=$webPort
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
[](https://github.com/FoxxMD/multi-scrobbler/releases)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://hub.docker.com/r/foxxmd/multi-scrobbler)
|
||||
[](https://foxxmd.github.io/multi-scrobbler/)
|
||||
[](https://docs.multi-scrobbler.app/)
|
||||
|
||||
|
||||
<img src="/assets/icon.png" align="right"
|
||||
@@ -11,56 +11,56 @@ alt="multi-scrobbler logo" width="180" height="180">
|
||||
|
||||
A dockerized app that monitors your music listening activity from *everywhere* and scrobbles it *anywhere*.
|
||||
|
||||
* Supports scrobbling from many [**Sources**](https://foxxmd.github.io/multi-scrobbler/configuration/sources)
|
||||
* [Azuracast](https://foxxmd.github.io/multi-scrobbler/configuration/sources/azuracast)
|
||||
* [Deezer](https://foxxmd.github.io/multi-scrobbler/configuration/sources/deezer)
|
||||
* [Google Cast (Chromecast)](https://foxxmd.github.io/multi-scrobbler/configuration/sources/google-cast)
|
||||
* [Icecast](https://foxxmd.github.io/multi-scrobbler/configuration/sources/icecast)
|
||||
* [Jellyfin](https://foxxmd.github.io/multi-scrobbler/configuration/sources/jellyfin)
|
||||
* [JRiver](https://foxxmd.github.io/multi-scrobbler/configuration/sources/jriver)
|
||||
* [Kodi](https://foxxmd.github.io/multi-scrobbler/configuration/sources/kodi)
|
||||
* [Koito](https://foxxmd.github.io/multi-scrobbler/configuration/sources/koito-source)
|
||||
* [Last.fm](https://foxxmd.github.io/multi-scrobbler/configuration/sources/lastfm-source)
|
||||
* [Last.fm (Endpoint)](https://foxxmd.github.io/multi-scrobbler/configuration/sources/lastfm-endpoint)
|
||||
* [Libre.fm](https://foxxmd.github.io/multi-scrobbler/configuration/sources/librefm-source)
|
||||
* [ListenBrainz](https://foxxmd.github.io/multi-scrobbler/configuration/sources/listenbrainz-source)
|
||||
* [ListenBrainz (Endpoint)](https://foxxmd.github.io/multi-scrobbler/configuration/sources/listenbrainz-endpoint)
|
||||
* [Maloja](https://foxxmd.github.io/multi-scrobbler/configuration/sources/maloja)
|
||||
* [Mopidy](https://foxxmd.github.io/multi-scrobbler/configuration/sources/mopidy)
|
||||
* [MPD (Music Player Daemon)](https://foxxmd.github.io/multi-scrobbler/configuration/sources/mpd)
|
||||
* [MPRIS (Linux Desktop)](https://foxxmd.github.io/multi-scrobbler/configuration/sources/mpris)
|
||||
* [Musikcube](https://foxxmd.github.io/multi-scrobbler/configuration/sources/musikcube)
|
||||
* [Music Assistant](https://foxxmd.github.io/multi-scrobbler/configuration/sources/listenbrainz-endpoint#music-assistant)
|
||||
* [Plex](https://foxxmd.github.io/multi-scrobbler/configuration/sources/plex)
|
||||
* [Rocksky](https://foxxmd.github.io/multi-scrobbler/configuration/sources/rocksky-source)
|
||||
* [Sonos](https://foxxmd.github.io/multi-scrobbler/configuration/sources/sonos)
|
||||
* [Spotify](https://foxxmd.github.io/multi-scrobbler/configuration/sources/spotify)
|
||||
* [Subsonic-compatible APIs](https://foxxmd.github.io/multi-scrobbler/configuration/sources/subsonic) (like [Airsonic](https://airsonic.github.io/) and [Navidrome](https://www.navidrome.org/))
|
||||
* [teal.fm](https://foxxmd.github.io/multi-scrobbler/configuration/sources/tealfm-source)
|
||||
* [WebScrobbler](https://foxxmd.github.io/multi-scrobbler/configuration/sources/webscrobbler)
|
||||
* [VLC](https://foxxmd.github.io/multi-scrobbler/configuration/sources/vlc)
|
||||
* [Yamaha MusicCast](https://foxxmd.github.io/multi-scrobbler/configuration/sources/yamaha-musiccast)
|
||||
* [Youtube Music](https://foxxmd.github.io/multi-scrobbler/configuration/sources/youtube-music)
|
||||
* Supports scrobbling to many [**Clients**](https://foxxmd.github.io/multi-scrobbler/configuration/clients)
|
||||
* [Discord](https://foxxmd.github.io/multi-scrobbler/configuration/clients/discord) (Now Playing)
|
||||
* [Koito](https://foxxmd.github.io/multi-scrobbler/configuration/clients/koito)
|
||||
* [Last.fm](https://foxxmd.github.io/multi-scrobbler/configuration/clients/lastfm)
|
||||
* [Libre.fm](https://foxxmd.github.io/multi-scrobbler/configuration/clients/librefm)
|
||||
* [ListenBrainz](https://foxxmd.github.io/multi-scrobbler/configuration/clients/listenbrainz)
|
||||
* [Maloja](https://foxxmd.github.io/multi-scrobbler/configuration/clients/maloja)
|
||||
* [Rocksky](https://foxxmd.github.io/multi-scrobbler/configuration/clients/rocksky)
|
||||
* [teal.fm](https://foxxmd.github.io/multi-scrobbler/configuration/clients/tealfm)
|
||||
* Monitor status of Sources and Clients using [webhooks (Gotify, Ntfy, Apprise)](https://foxxmd.github.io/multi-scrobbler/configuration#webhook-configurations), [healthcheck endpoints](https://foxxmd.github.io/multi-scrobbler/configuration#health-endpoint), or [prometheus metrics](https://foxxmd.github.io/multi-scrobbler/configuration/#prometheus).
|
||||
* Supports [Now Playing](https://foxxmd.github.io/multi-scrobbler/configuration/clients#now-playing) for scrobble Clients
|
||||
* Supports scrobbling from many [**Sources**](https://docs.multi-scrobbler.app/configuration/sources)
|
||||
* [Azuracast](https://docs.multi-scrobbler.app/configuration/sources/azuracast)
|
||||
* [Deezer](https://docs.multi-scrobbler.app/configuration/sources/deezer)
|
||||
* [Google Cast (Chromecast)](https://docs.multi-scrobbler.app/configuration/sources/google-cast)
|
||||
* [Icecast](https://docs.multi-scrobbler.app/configuration/sources/icecast)
|
||||
* [Jellyfin](https://docs.multi-scrobbler.app/configuration/sources/jellyfin)
|
||||
* [JRiver](https://docs.multi-scrobbler.app/configuration/sources/jriver)
|
||||
* [Kodi](https://docs.multi-scrobbler.app/configuration/sources/kodi)
|
||||
* [Koito](https://docs.multi-scrobbler.app/configuration/sources/koito-source)
|
||||
* [Last.fm](https://docs.multi-scrobbler.app/configuration/sources/lastfm-source)
|
||||
* [Last.fm (Endpoint)](https://docs.multi-scrobbler.app/configuration/sources/lastfm-endpoint)
|
||||
* [Libre.fm](https://docs.multi-scrobbler.app/configuration/sources/librefm-source)
|
||||
* [ListenBrainz](https://docs.multi-scrobbler.app/configuration/sources/listenbrainz-source)
|
||||
* [ListenBrainz (Endpoint)](https://docs.multi-scrobbler.app/configuration/sources/listenbrainz-endpoint)
|
||||
* [Maloja](https://docs.multi-scrobbler.app/configuration/sources/maloja)
|
||||
* [Mopidy](https://docs.multi-scrobbler.app/configuration/sources/mopidy)
|
||||
* [MPD (Music Player Daemon)](https://docs.multi-scrobbler.app/configuration/sources/mpd)
|
||||
* [MPRIS (Linux Desktop)](https://docs.multi-scrobbler.app/configuration/sources/mpris)
|
||||
* [Musikcube](https://docs.multi-scrobbler.app/configuration/sources/musikcube)
|
||||
* [Music Assistant](https://docs.multi-scrobbler.app/configuration/sources/listenbrainz-endpoint#music-assistant)
|
||||
* [Plex](https://docs.multi-scrobbler.app/configuration/sources/plex)
|
||||
* [Rocksky](https://docs.multi-scrobbler.app/configuration/sources/rocksky-source)
|
||||
* [Sonos](https://docs.multi-scrobbler.app/configuration/sources/sonos)
|
||||
* [Spotify](https://docs.multi-scrobbler.app/configuration/sources/spotify)
|
||||
* [Subsonic-compatible APIs](https://docs.multi-scrobbler.app/configuration/sources/subsonic) (like [Airsonic](https://airsonic.github.io/) and [Navidrome](https://www.navidrome.org/))
|
||||
* [teal.fm](https://docs.multi-scrobbler.app/configuration/sources/tealfm-source)
|
||||
* [WebScrobbler](https://docs.multi-scrobbler.app/configuration/sources/webscrobbler)
|
||||
* [VLC](https://docs.multi-scrobbler.app/configuration/sources/vlc)
|
||||
* [Yamaha MusicCast](https://docs.multi-scrobbler.app/configuration/sources/yamaha-musiccast)
|
||||
* [Youtube Music](https://docs.multi-scrobbler.app/configuration/sources/youtube-music)
|
||||
* Supports scrobbling to many [**Clients**](https://docs.multi-scrobbler.app/configuration/clients)
|
||||
* [Discord](https://docs.multi-scrobbler.app/configuration/clients/discord) (Now Playing)
|
||||
* [Koito](https://docs.multi-scrobbler.app/configuration/clients/koito)
|
||||
* [Last.fm](https://docs.multi-scrobbler.app/configuration/clients/lastfm)
|
||||
* [Libre.fm](https://docs.multi-scrobbler.app/configuration/clients/librefm)
|
||||
* [ListenBrainz](https://docs.multi-scrobbler.app/configuration/clients/listenbrainz)
|
||||
* [Maloja](https://docs.multi-scrobbler.app/configuration/clients/maloja)
|
||||
* [Rocksky](https://docs.multi-scrobbler.app/configuration/clients/rocksky)
|
||||
* [teal.fm](https://docs.multi-scrobbler.app/configuration/clients/tealfm)
|
||||
* Monitor status of Sources and Clients using [webhooks (Gotify, Ntfy, Apprise)](https://docs.multi-scrobbler.app/configuration#webhook-configurations), [healthcheck endpoints](https://docs.multi-scrobbler.app/configuration#health-endpoint), or [prometheus metrics](https://docs.multi-scrobbler.app/configuration/#prometheus).
|
||||
* Supports [Now Playing](https://docs.multi-scrobbler.app/configuration/clients#now-playing) for scrobble Clients
|
||||
* Supports configuring for single or multiple users (scrobbling for your friends and family!)
|
||||
* Web server interface for stats, basic control, and detailed logs
|
||||
* Graceful network and client failure handling (queued scrobbles that auto-retry)
|
||||
* Smart handling of credentials (persistent, authorization through app)
|
||||
* Easy configuration through [ENVs or JSON](ttps://foxxmd.github.io/multi-scrobbler/configuration#configuration-types)
|
||||
* Modify data before scrobbling with [regular expression or search patterns](https://foxxmd.github.io/multi-scrobbler/configuration/transforms)
|
||||
* Install using [Docker images for x86/ARM](https://foxxmd.github.io/multi-scrobbler/installation#docker) or [locally with NodeJS](https://foxxmd.github.io/multi-scrobbler/installation#nodejs)
|
||||
* Modify data before scrobbling with [regular expression or search patterns](https://docs.multi-scrobbler.app/configuration/transforms)
|
||||
* Install using [Docker images for x86/ARM](https://docs.multi-scrobbler.app/installation#docker) or [locally with NodeJS](https://docs.multi-scrobbler.app/installation#nodejs)
|
||||
|
||||
[**Quick Start Guide**](https://foxxmd.github.io/multi-scrobbler//quickstart)
|
||||
[**Quick Start Guide**](https://docs.multi-scrobbler.app//quickstart)
|
||||
|
||||
<img src="/docsite/static/img/status-ui.png" width="800">
|
||||
|
||||
@@ -73,15 +73,15 @@ A dockerized app that monitors your music listening activity from *everywhere* a
|
||||
|
||||
**But I already scrobble my music to Last.fm/ListenBrainz, is multi-scrobbler for me?**
|
||||
|
||||
Yes! You can use [Last.fm as a **Source**](https://foxxmd.github.io/multi-scrobbler/configuration/sources/lastfm-source) or [Listenbrainz as a **Source**](https://foxxmd.github.io/multi-scrobbler/configuration/sources/listenbrainz-source) to forward scrobbles from your profile to any other Client! That way you can keep your current scrobble setup as-is but still get the benefit of capturing your data to a self-hosted location.
|
||||
Yes! You can use [Last.fm as a **Source**](https://docs.multi-scrobbler.app/configuration/sources/lastfm-source) or [Listenbrainz as a **Source**](https://docs.multi-scrobbler.app/configuration/sources/listenbrainz-source) to forward scrobbles from your profile to any other Client! That way you can keep your current scrobble setup as-is but still get the benefit of capturing your data to a self-hosted location.
|
||||
|
||||
## How Does multi-scrobbler (MS) Work?
|
||||
|
||||
You set up [configurations](https://foxxmd.github.io/multi-scrobbler/configuration) for one or more [**Sources**](https://foxxmd.github.io/multi-scrobbler/configuration/sources) and one or more [**Clients**](https://foxxmd.github.io/multi-scrobbler/configuration/clients). MS monitors all of your configured **Sources**. When new tracks are played by a Source MS grabs that information and then sends it (scrobbles it) to all **Clients** that Source is configured to forward to.
|
||||
You set up [configurations](https://docs.multi-scrobbler.app/configuration) for one or more [**Sources**](https://docs.multi-scrobbler.app/configuration/sources) and one or more [**Clients**](https://docs.multi-scrobbler.app/configuration/clients). MS monitors all of your configured **Sources**. When new tracks are played by a Source MS grabs that information and then sends it (scrobbles it) to all **Clients** that Source is configured to forward to.
|
||||
|
||||
### Source
|
||||
|
||||
A [**Source**](https://foxxmd.github.io/multi-scrobbler/configuration/sources) is a data source that contains information about music you are playing or have listened to, such as: a desktop player, web music player, or cloud music service. Examples are **Spotify, Jellyfin, Plex, Youtube Music, Navidrome**, etc...
|
||||
A [**Source**](https://docs.multi-scrobbler.app/configuration/sources) is a data source that contains information about music you are playing or have listened to, such as: a desktop player, web music player, or cloud music service. Examples are **Spotify, Jellyfin, Plex, Youtube Music, Navidrome**, etc...
|
||||
|
||||
Source configurations consist of:
|
||||
|
||||
@@ -91,7 +91,7 @@ Source configurations consist of:
|
||||
|
||||
### Client
|
||||
|
||||
A [**Client**](https://foxxmd.github.io/multi-scrobbler/configuration/clients) is an application that stores the historical information about what music you have played (scrobbles). Examples are **Koito, Last.fm, Listenbrainz**...
|
||||
A [**Client**](https://docs.multi-scrobbler.app/configuration/clients) is an application that stores the historical information about what music you have played (scrobbles). Examples are **Koito, Last.fm, Listenbrainz**...
|
||||
|
||||
Client configurations consist of:
|
||||
|
||||
@@ -100,15 +100,15 @@ Client configurations consist of:
|
||||
|
||||
## Quick Start
|
||||
|
||||
[See the **Quick Start Guide**](https://foxxmd.github.io/multi-scrobbler/quickstart)
|
||||
[See the **Quick Start Guide**](https://docs.multi-scrobbler.app/quickstart)
|
||||
|
||||
## Installation
|
||||
|
||||
[See the **Installation** documentation](https://foxxmd.github.io/multi-scrobbler/installation)
|
||||
[See the **Installation** documentation](https://docs.multi-scrobbler.app/installation)
|
||||
|
||||
## Configuration
|
||||
|
||||
[See the **Configuration** documentation](https://foxxmd.github.io/multi-scrobbler/configuration)
|
||||
[See the **Configuration** documentation](https://docs.multi-scrobbler.app/configuration)
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -123,11 +123,11 @@ On first startup you may need to authorize Spotify and/or Last.fm by visiting th
|
||||
|
||||
## Help/FAQ
|
||||
|
||||
Having issues with connections or configuration? Check the [FAQ](https://foxxmd.github.io/multi-scrobbler/FAQ) before creating an issue!
|
||||
Having issues with connections or configuration? Check the [FAQ](https://docs.multi-scrobbler.app/FAQ) before creating an issue!
|
||||
|
||||
## Development
|
||||
|
||||
[Detailed architecture and development guides for Sources/Clients](https://foxxmd.github.io/multi-scrobbler/development/dev-common)
|
||||
[Detailed architecture and development guides for Sources/Clients](https://docs.multi-scrobbler.app/development/dev-common)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -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": [],
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"id": "myYandex",
|
||||
"name": "Yandex Music",
|
||||
"type": "ymbridge",
|
||||
"data": {
|
||||
"url": "http://192.168.1.161:9980",
|
||||
"apiKey": "change-me",
|
||||
"interval": 3
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "MyYTMusic",
|
||||
"id": "myYtMusic",
|
||||
"name": "Foxx YT Music",
|
||||
"enable": true,
|
||||
"clients": [],
|
||||
"data": {
|
||||
|
||||
@@ -4,6 +4,8 @@ services:
|
||||
multi-scrobbler:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
BUILD_DATE: "${BUILD_DATE}"
|
||||
env_file:
|
||||
- path: ./.env
|
||||
required: false
|
||||
|
||||
@@ -18,3 +18,4 @@
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lib
|
||||
@@ -0,0 +1,42 @@
|
||||
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
var args = process.argv.slice(2);
|
||||
|
||||
const linkRegex = new RegExp(/<link data-rh="true" rel="\w+"\shref="(https:\/\/foxxmd\.github\.io\/multi-scrobbler)/g,);
|
||||
const replacement = process.env.CANONICAL_HREF ?? 'https://docs.multi-scrobbler.app';
|
||||
|
||||
/**
|
||||
* Replace canonical and alternative <link> nodes with the "real" domain of the site
|
||||
* so that SEO (google) chooses the correct domain when showing search results
|
||||
*
|
||||
*/
|
||||
|
||||
(async function () {
|
||||
|
||||
if(replacement === undefined || replacement === '') {
|
||||
console.warn('No replacement value found in process.env.CANONICAL_HREF');
|
||||
return;
|
||||
}
|
||||
|
||||
const buildDir = path.resolve(args[0]);
|
||||
console.log(`Reading dir recursively ${buildDir}`);
|
||||
const files = (await fs.readdir(buildDir, { recursive: true })).filter(x => x.includes('.html'));
|
||||
console.log(`Found ${files.length} files with .html extensions`);
|
||||
let modifications = 0;
|
||||
|
||||
await Promise.all(files.map(async (x) => {
|
||||
const filePath = path.resolve(path.join(buildDir, x));
|
||||
//console.log(`Replacing at ${filePath}`);
|
||||
await fs.writeFile(filePath, (await fs.readFile(filePath)).toString().replace(linkRegex, (match, capture, offset, string, groups) => {
|
||||
// this may not be fully accurate since we're mutating concurrently/async
|
||||
// but its a good enough signal for logging to tell if replacements happened
|
||||
modifications++;
|
||||
return match.replace(capture, replacement);
|
||||
}));
|
||||
}));
|
||||
|
||||
console.log(`Done with ${modifications} replacements`);
|
||||
|
||||
}());
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
toc_min_heading_level: 2
|
||||
toc_max_heading_level: 5
|
||||
title: FAQ
|
||||
---
|
||||
|
||||
## Connection Issues
|
||||
@@ -113,6 +114,12 @@ It means the JSON in your configuration file is not valid. Copy and paste your c
|
||||
|
||||
## Scrobbling Issues
|
||||
|
||||
### How do I prevent duplicate scrobbles?
|
||||
|
||||
Multi-scrobbler has a [robust duplicate scrobble detection system](/configuration/duplicates#how-duplicates-are-detected) that should handle this for you in the majority of usecases. If you have a [simple setup without any circular dependencies](/configuration/duplicates#valid-configs) then you are already protected against duplicates.
|
||||
|
||||
If your scenario is more complex or you have a service(s) acting as **both** a **Client** and **Source** you should refer to the [**Duplicate Scrobble Guidance**](/configuration/duplicates#valid-configs) page to determine if you can avoid a configuration that may result in duplicate scrobbles.
|
||||
|
||||
### Last.fm does not scrobble tracks with multiple artists correctly
|
||||
|
||||
This is a limitation of the [Last.fm API](https://www.last.fm/api/show/track.scrobble) where the **artist** field is only one string and Last.fm does not recognize (play well) with "combined" artists.
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
<Tabs groupId="configType" queryString>
|
||||
<TabItem value="file" label="File">
|
||||
|
||||
Using [File Config](/configuration/?configType=file#configuration-types)
|
||||
|
||||
```json5 title="CONFIG_DIR/jellyin.json"
|
||||
[
|
||||
{
|
||||
"id": "fredJf",
|
||||
"name": "Freds Jellyfin",
|
||||
"clients": ["fredLFM"],
|
||||
"data": {
|
||||
"url": "https://jellyfin.fred.com",
|
||||
"user": "Fred",
|
||||
"apiKey": "c9fae8756fbf481ebd9c5bb56bd6540c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "maryJf",
|
||||
"name": "Marys Jellyfin",
|
||||
"clients": ["maryLFM"],
|
||||
"data": {
|
||||
"url": "https://jellyfin.mary.com",
|
||||
"user": "Mary",
|
||||
"apiKey": "26fe9c5dc6374542adc4f4d5e8352838"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
```json5 title="CONFIG_DIR/plex.json"
|
||||
[
|
||||
{
|
||||
"id": "foxxPlex",
|
||||
"name": "Foxx Plex",
|
||||
"clients": ["foxxKoi"],
|
||||
"data": {
|
||||
"token": "JtRnwQWD__XMJF8sT3jc",
|
||||
"url": "192.168.0.233:32400"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
```json5 title="CONFIG_DIR/lastfm.json"
|
||||
[
|
||||
{
|
||||
"id": "maryLFM",
|
||||
"name": "Marys Lastfm",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"apiKey": "maryApiKey",
|
||||
"secret": "marySecret",
|
||||
"redirectUri": "http://localhost:9078/lastfm/callback"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "fredLFM",
|
||||
"name": "Freds Lastfm",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"apiKey": "fredApiKey",
|
||||
"secret": "fredSecret",
|
||||
"redirectUri": "http://localhost:9078/lastfm/callback"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
```json5 title="CONFIG_DIR/koito.json"
|
||||
[
|
||||
{
|
||||
"id": "foxxKoi",
|
||||
"name": "Foxx Koito",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"token": "029b081ba-9156-4pe7-88e5-3be671f5ea2b",
|
||||
"username": "admin",
|
||||
"url": "http://192.168.0.100:4110"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="aio" label="AIO">
|
||||
|
||||
Using [AIO Config](/configuration/?configType=aio#configuration-types)
|
||||
|
||||
```json5 title="CONFIG_DIR/config.json"
|
||||
{
|
||||
"sources": [
|
||||
{
|
||||
"type": "jellyfin",
|
||||
"id": "fredJf",
|
||||
"name": "Freds Jellyfin",
|
||||
"clients": [
|
||||
"fredLFM"
|
||||
],
|
||||
"data": {
|
||||
"url": "https://jellyfin.fred.com",
|
||||
"user": "Fred",
|
||||
"apiKey": "c9fae8756fbf481ebd9c5bb56bd6540c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "jellyfin",
|
||||
"id": "maryJf",
|
||||
"name": "Marys Jellyfin",
|
||||
"clients": [
|
||||
"maryLFM"
|
||||
],
|
||||
"data": {
|
||||
"url": "https://jellyfin.mary.com",
|
||||
"user": "Mary",
|
||||
"apiKey": "26fe9c5dc6374542adc4f4d5e8352838"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "plex",
|
||||
"id": "foxxPlex",
|
||||
"name": "Foxx Plex",
|
||||
"clients": [
|
||||
"foxxKoi"
|
||||
],
|
||||
"data": {
|
||||
"token": "JtRnwQWD__XMJF8sT3jc",
|
||||
"url": "192.168.0.233:32400",
|
||||
"usersallow": [
|
||||
"foxx"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"clients": [
|
||||
{
|
||||
"type": "lastfm",
|
||||
"id": "maryLFM",
|
||||
"name": "Marys Lastfm",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"apiKey": "maryApiKey",
|
||||
"secret": "marySecret",
|
||||
"redirectUri": "http://localhost:9078/lastfm/callback"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "lastfm",
|
||||
"id": "fredLFM",
|
||||
"name": "Freds Lastfm",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"apiKey": "fredApiKey",
|
||||
"secret": "fredSecret",
|
||||
"redirectUri": "http://localhost:9078/lastfm/callback"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "koito",
|
||||
"id": "foxxKoi",
|
||||
"name": "Foxx Koito",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"token": "029b081ba-9156-4pe7-88e5-3be671f5ea2b",
|
||||
"username": "admin",
|
||||
"url": "http://192.168.0.100:4110"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
@@ -0,0 +1,196 @@
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
<Tabs groupId="configType" queryString>
|
||||
<TabItem value="env" label="ENV">
|
||||
|
||||
Using [ENV Config](/configuration/?configType=env#configuration-types)
|
||||
|
||||
```yaml title="compose.yaml"
|
||||
services:
|
||||
multi-scrobbler:
|
||||
image: foxxmd/multi-scrobbler
|
||||
container_name: multi-scrobbler
|
||||
environment:
|
||||
- TZ=Etc/GMT # Specify timezone from TZ Database name found here https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
# spotify
|
||||
- SPOTIFY_ID=foxxSpotify
|
||||
- SPOTIFY_CLIENT_ID=H2UvGv8okrFlv2OUoc1lAtK7Xe5EGDgs
|
||||
- SPOTIFY_CLIENT_SECRET=HvoFE5Ce2hdblNE6vaFBEx6dRVcDdo43
|
||||
- SPOTIFY_REDIRECT_URI=https://multiscrobbler.example.com/callback
|
||||
|
||||
# navidrome listenbrainz endpoint
|
||||
- LZE_ID=myLzNavidrome
|
||||
- LZE_TOKEN=myToken
|
||||
|
||||
# plex
|
||||
- PLEX_ID=foxxPlex
|
||||
- PLEX_URL=192.168.0.233:32400
|
||||
- PLEX_TOKEN=JtRnwQWD__XMJF8sT3jc
|
||||
- PLEX_USERS_ALLOW=foxx
|
||||
- PLEX_LIBRARIES_ALLOW=oldies
|
||||
|
||||
# lastfm
|
||||
- LASTFM_ID=myLFM
|
||||
- LASTFM_API_KEY=myApiKey
|
||||
- LASTFM_SECRET=mySecret
|
||||
- LASTFM_REDIRECT_URI=https://multiscrobbler.example.com/lastfm/callback
|
||||
|
||||
# koito
|
||||
- KOITO_ID=foxxKoi
|
||||
- KOITO_TOKEN=029b081ba-9156-4pe7-88e5-3be671f5ea2b
|
||||
- KOITO_USER=admin
|
||||
- KOITO_URL=http://192.168.0.100:4110
|
||||
|
||||
volumes:
|
||||
- "./config:/config"
|
||||
ports:
|
||||
- "9078:9078"
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="file" label="File">
|
||||
|
||||
Using [File Config](/configuration/?configType=file#configuration-types)
|
||||
|
||||
```json5 title="CONFIG_DIR/spotify.json"
|
||||
[
|
||||
{
|
||||
"id": "foxxSpotify",
|
||||
"name": "Foxx Spotify",
|
||||
"data": {
|
||||
"clientId": "a89cba1569901a0671d5a9875fed4be1",
|
||||
"clientSecret": "ec42e09d5ae0ee0f0816ca151008412a",
|
||||
"redirectUri": "http://localhost:9078/callback",
|
||||
"interval": 60
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
```json5 title="CONFIG_DIR/endpointlz.json"
|
||||
[
|
||||
{
|
||||
"id": "myLzNavidrome",
|
||||
"name": "LZ for Navidrome",
|
||||
"data": {
|
||||
"token": "myToken"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
```json5 title="CONFIG_DIR/plex.json"
|
||||
[
|
||||
{
|
||||
"id": "foxxPlex",
|
||||
"name": "Foxx Plex",
|
||||
"data": {
|
||||
"token": "JtRnwQWD__XMJF8sT3jc",
|
||||
"url": "192.168.0.233:32400",
|
||||
"usersallow": ["foxx"],
|
||||
"librariesAllow": ["oldies"],
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
```json5 title="CONFIG_DIR/lastfm.json"
|
||||
[
|
||||
{
|
||||
"id": "myLFM",
|
||||
"name": "Old Lastfm",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"apiKey": "myApiKey",
|
||||
"secret": "mySecret",
|
||||
"redirectUri": "http://localhost:9078/lastfm/callback"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
```json5 title="CONFIG_DIR/koito.json"
|
||||
[
|
||||
{
|
||||
"id": "foxxKoi",
|
||||
"name": "Foxx Koito",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"token": "029b081ba-9156-4pe7-88e5-3be671f5ea2b",
|
||||
"username": "admin",
|
||||
"url": "http://192.168.0.100:4110"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="aio" label="AIO">
|
||||
|
||||
Using [AIO Config](/configuration/?configType=aio#configuration-types)
|
||||
|
||||
```json5 title="CONFIG_DIR/config.json"
|
||||
{
|
||||
"sources": [
|
||||
{
|
||||
"type": "spotify",
|
||||
"id": "foxxSpotify",
|
||||
"name": "Foxx Spotify",
|
||||
"data": {
|
||||
"clientId": "a89cba1569901a0671d5a9875fed4be1",
|
||||
"clientSecret": "ec42e09d5ae0ee0f0816ca151008412a",
|
||||
"redirectUri": "http://localhost:9078/callback",
|
||||
"interval": 60
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "endpointlz",
|
||||
"name": "LZ for Navidrome",
|
||||
"id": "myLzNavidrome",
|
||||
"data": {
|
||||
"token": "myToken"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "plex",
|
||||
"id": "foxxPlex",
|
||||
"name": "Foxx Plex",
|
||||
"data": {
|
||||
"token": "JtRnwQWD__XMJF8sT3jc",
|
||||
"url": "192.168.0.233:32400",
|
||||
"usersallow": ["foxx"],
|
||||
"librariesAllow": ["oldies"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"clients": [
|
||||
{
|
||||
"type": "lastfm",
|
||||
"id": "myLFM",
|
||||
"name": "Old Lastfm",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"apiKey": "myApiKey",
|
||||
"secret": "mySecret",
|
||||
"redirectUri": "http://localhost:9078/lastfm/callback"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "koito",
|
||||
"id": "foxxKoi",
|
||||
"name": "Foxx Koito",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"token": "029b081ba-9156-4pe7-88e5-3be671f5ea2b",
|
||||
"username": "admin",
|
||||
"url": "http://192.168.0.100:4110"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
@@ -0,0 +1,226 @@
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
<Tabs groupId="configType" queryString>
|
||||
<TabItem value="env" label="ENV">
|
||||
|
||||
Using [ENV Config](/configuration/?configType=env#configuration-types)
|
||||
|
||||
```yaml title="compose.yaml"
|
||||
services:
|
||||
multi-scrobbler:
|
||||
image: foxxmd/multi-scrobbler
|
||||
container_name: multi-scrobbler
|
||||
environment:
|
||||
- TZ=Etc/GMT # Specify timezone from TZ Database name found here https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
|
||||
# required setup for musicbrainz
|
||||
- MB_CONTACT=contact@mydomain.com # a real email
|
||||
# searches with sensible release sorting with the searchOrder
|
||||
# isrc => basic => artist (native mode)
|
||||
- MB_PRESETS=default,sensible,native
|
||||
|
||||
# spotify
|
||||
- SPOTIFY_ID=foxxSpotify
|
||||
- SPOTIFY_CLIENT_ID=H2UvGv8okrFlv2OUoc1lAtK7Xe5EGDgs
|
||||
- SPOTIFY_CLIENT_SECRET=HvoFE5Ce2hdblNE6vaFBEx6dRVcDdo43
|
||||
- SPOTIFY_REDIRECT_URI=https://multiscrobbler.example.com/callback
|
||||
|
||||
# navidrome listenbrainz endpoint
|
||||
- LZE_ID=myLzNavidrome
|
||||
- LZE_TOKEN=myToken
|
||||
# applies musicbrainz Stage to preTransform of navidrome source
|
||||
- LZE_TRANSFORMS=musicbrainz
|
||||
|
||||
# lastfm
|
||||
- LASTFM_ID=myLFM
|
||||
- LASTFM_API_KEY=myApiKey
|
||||
- LASTFM_SECRET=mySecret
|
||||
- LASTFM_REDIRECT_URI=https://multiscrobbler.example.com/lastfm/callback
|
||||
|
||||
volumes:
|
||||
- "./config:/config"
|
||||
ports:
|
||||
- "9078:9078"
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="file" label="File">
|
||||
|
||||
Using [File Config](/configuration/?configType=file#configuration-types)
|
||||
|
||||
For non-ENV config, [Musicbrainz Transformer](/configuration/transforms/musicbrainz/#api-setup) must be setup using the [AIO Config](/configuration/?configType=aio#configuration-types) `config.json` file. This configuration uses the [sensible defaults](/configuration/transforms/musicbrainz/#sensible-default) in the `defaults` property. This is a suggestion and can be removed if not desired.
|
||||
|
||||
```json5 title="CONFIG_DIR/config.json"
|
||||
{
|
||||
"transformers": [
|
||||
{
|
||||
"type": "musicbrainz",
|
||||
"name": "MyMB",
|
||||
"data": {
|
||||
"apis": [
|
||||
{
|
||||
"contact": "contact@mydomain.com"
|
||||
}
|
||||
]
|
||||
},
|
||||
"defaults": {
|
||||
"releaseStatusPriority": ["official"],
|
||||
"releaseGroupPrimaryTypePriority": [
|
||||
"album",
|
||||
"single",
|
||||
"ep"
|
||||
],
|
||||
"releaseCountryPriority": ["XW"],
|
||||
"searchArtistMethod": "native",
|
||||
"searchOrder": [
|
||||
"isrc",
|
||||
"basic",
|
||||
"artist"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```json5 title="CONFIG_DIR/spotify.json"
|
||||
[
|
||||
{
|
||||
"id": "foxxSpotify",
|
||||
"name": "Foxx Spotify",
|
||||
"data": {
|
||||
"clientId": "a89cba1569901a0671d5a9875fed4be1",
|
||||
"clientSecret": "ec42e09d5ae0ee0f0816ca151008412a",
|
||||
"redirectUri": "http://localhost:9078/callback",
|
||||
"interval": 60
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
```json5 title="CONFIG_DIR/endpointlz.json"
|
||||
[
|
||||
{
|
||||
"id": "myLzNavidrome",
|
||||
"name": "LZ for Navidrome",
|
||||
"data": {
|
||||
"token": "myToken"
|
||||
},
|
||||
"options": {
|
||||
"playTransform": {
|
||||
"preCompare": [
|
||||
{
|
||||
"type": "musicbrainz",
|
||||
"name": "MyMB"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
```json5 title="CONFIG_DIR/lastfm.json"
|
||||
[
|
||||
{
|
||||
"id": "myLFM",
|
||||
"name": "Old Lastfm",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"apiKey": "myApiKey",
|
||||
"secret": "mySecret",
|
||||
"redirectUri": "http://localhost:9078/lastfm/callback"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="aio" label="AIO">
|
||||
|
||||
Using [AIO Config](/configuration/?configType=aio#configuration-types)
|
||||
|
||||
This configuration uses the [sensible defaults](/configuration/transforms/musicbrainz/#sensible-default) in the `defaults` property. This is a suggestion and can be removed if not desired.
|
||||
|
||||
```json5 title="CONFIG_DIR/config.json"
|
||||
{
|
||||
"transformers": [
|
||||
{
|
||||
"type": "musicbrainz",
|
||||
"name": "MyMB",
|
||||
"data": {
|
||||
"apis": [
|
||||
{
|
||||
"contact": "contact@mydomain.com"
|
||||
}
|
||||
]
|
||||
},
|
||||
"defaults": {
|
||||
"releaseStatusPriority": ["official"],
|
||||
"releaseGroupPrimaryTypePriority": [
|
||||
"album",
|
||||
"single",
|
||||
"ep"
|
||||
],
|
||||
"releaseCountryPriority": ["XW"],
|
||||
"searchArtistMethod": "native",
|
||||
"searchOrder": [
|
||||
"isrc",
|
||||
"basic",
|
||||
"artist"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "spotify",
|
||||
"id": "foxxSpotify",
|
||||
"name": "Foxx Spotify",
|
||||
"data": {
|
||||
"clientId": "a89cba1569901a0671d5a9875fed4be1",
|
||||
"clientSecret": "ec42e09d5ae0ee0f0816ca151008412a",
|
||||
"redirectUri": "http://localhost:9078/callback",
|
||||
"interval": 60
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "endpointlz",
|
||||
"name": "LZ for Navidrome",
|
||||
"id": "myLzNavidrome",
|
||||
"data": {
|
||||
"token": "myToken"
|
||||
},
|
||||
"options": {
|
||||
"playTransform": {
|
||||
"preCompare": [
|
||||
{
|
||||
"type": "musicbrainz",
|
||||
"name": "MyMB"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"clients": [
|
||||
{
|
||||
"type": "lastfm",
|
||||
"id": "myLFM",
|
||||
"name": "Old Lastfm",
|
||||
"configureAs": "client",
|
||||
"data": {
|
||||
"apiKey": "myApiKey",
|
||||
"secret": "mySecret",
|
||||
"redirectUri": "http://localhost:9078/lastfm/callback"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
title: Overview
|
||||
description: Scrobble Clients
|
||||
toc_max_heading_level: 4
|
||||
---
|
||||
|
||||
@@ -33,60 +34,9 @@ For each Play, MS fetches (cached) scrobbles from the Client in a time range inc
|
||||
* Temporal closeness of the Play's timestamp to the existing scrobble's timestamp
|
||||
* Whether MS detected the Play as a repeat (applicable to Sources that report realtime Player data)
|
||||
|
||||
Detailed scoring breakdowns against each existing scrobble are logged at the `TRACE` level.
|
||||
Detailed scoring breakdowns against each existing scrobble are logged at the `TRACE` logging level or in the [debug data](/help#copy-play-debug-data) for a scrobble.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Detailed Explanation</summary>
|
||||
|
||||
A **candidate** (to be scrobbled) Play is first transformed using the configured [`compare.candidate` Hook](/configuration/transforms/#hook), if any exists.
|
||||
|
||||
Next, MS checks (up to) the last 100 scrobbles *in-memory* scrobbles that it has made. These are *not* from the Client but the actual scrobbles MS made while it has been running. The data from these scrobbles is much richer than what is usually parsed from the Client which makes it easier to detect duplicates from.
|
||||
|
||||
If no in-memory scrobble matches then MS starts comparing the candidate against historical scrobbles fetched from an inclusive time range of the candidate's timestamp.
|
||||
|
||||
<h3>Matching Title/Artist/Album</h3>
|
||||
|
||||
For these string-based values MS uses an [token-order-invariant](https://github.com/FoxxMD/multi-scrobbler/blob/0dfa4c7aad6df98e13aee6d395827665c2414adb/src/backend/utils/StringUtils.ts#L299) method that scores string similarity based on a (token count) weighted average of two [similarity](https://foxxmd.github.io/string-sameness/#md:strategies) algorithms, [Levenshtien Distance](https://en.wikipedia.org/wiki/Levenshtein_distance) and [Dice's Coefficient](https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient). The weighted average ensures that very long strings (Track titles) are scored with a confidence proportional to their length.
|
||||
|
||||
<h3>Matching Timestamp</h3>
|
||||
|
||||
Timestamps are scored on how temporally close they are. There are four possible scores with decreasing value:
|
||||
|
||||
* **Exact** - Timestamps are within 1 second of each other
|
||||
* **Close** - Timestamps are within `threshold` seconds of each other
|
||||
* This is determined by the smallest update interval of Source
|
||||
* Some Sources (subsonic) only update every 60 seconds so this is the smallest "close" value possible. Most are 10 seconds. Signified by `(Needed <10s)` in the breakdown example below.
|
||||
* **Fuzzy** - One timestamp is within `threshold` seconds of the *end* of the other timestamp
|
||||
* Sources can set the scrobble timestamp at different times. Some do it when the track is started listening to, some when it ends, some when the *player* stops.
|
||||
* Where possible, MS knows and keeps track of when this timestamp *should* be, for each Source. If it's not possible then Fuzzy may be allowed.
|
||||
* **None** - There is no correlation between timestamps
|
||||
|
||||
<h3>Scoring and Breakdows</h3>
|
||||
|
||||
A candidate Play must score >= 1 to be detected as a duplicate of an existing scrobble.
|
||||
|
||||
Each score and a breakdown of the scores for its individual components can be see at the `TRACE` logging level or in the [debug data](/help#copy-play-debug-data) for a scrobble. An example:
|
||||
|
||||
```
|
||||
* Artist: 0.06 * 0.3 = 0.02
|
||||
* Title: 0.04 * 0.4 = 0.02
|
||||
* Time: (Exact) 1 * 0.5 = 0.50
|
||||
* Existing: 19:13:33-04:00 - Candidate: 19:13:33-04:00
|
||||
* Temporal Sameness: Exact
|
||||
* Play Diff: 0s (Needed <10s)
|
||||
* Range Comparison N/A
|
||||
Score 0.54 => No Match
|
||||
```
|
||||
|
||||
In each component equation, the first number is the similarity (or temporal closeness).
|
||||
|
||||
* 0 = no correlation
|
||||
* 1 = exactly the same
|
||||
|
||||
The second number is the *weight* of that component in the final score.
|
||||
|
||||
</details>
|
||||
See the [**Duplicate Scrobble Guidance**](/configuration/duplicates#how-duplicates-are-detected) page to learn how duplicates are detected and how you can ensure your configuration avoids possible duplicates.
|
||||
|
||||
### Dead Scrobbles
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
title: Discord
|
||||
description: Discord Client Overview
|
||||
toc_max_heading_level: 4
|
||||
---
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ Using the URL path `/apis/listenbrainz` [Koito docs describe](https://koito.io/g
|
||||
|
||||
:::tip
|
||||
|
||||
Ensure that Koito is configured to allow requests from multi-scrobbler! In Koito config set [`KOITO_ALLOWED_HOSTS`](https://koito.io/reference/configuration/#koito_allowed_hosts) to the IP the multi-scrobbler dashboard is accessible from.
|
||||
For Koito versions `<=0.1.7`, you must have the environment variable `KOITO_ALLOWED_HOSTS` set correctly in your Koito environment/container. Ensure it is set to the IP address or domain name that you use to access Koito with e.g. `koito.mydomain.com`.
|
||||
|
||||
:::
|
||||
|
||||
@@ -54,7 +54,9 @@ Ensure that Koito is configured to allow requests from multi-scrobbler! In Koito
|
||||
<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 |
|
||||
</Config>
|
||||
| `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>
|
||||
@@ -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/` |
|
||||
| `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>
|
||||
@@ -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,16 +41,10 @@ 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 a [Bluesky](https://bsky.app/), or other [ATmosphere](https://atproto.brussels/about-the-atmosphere), account to use Rocksky.
|
||||
|
||||
:::note
|
||||
|
||||
Usage is adapted from [Rocksky docs](https://docs.rocksky.app/migrating-to-rocksky-scrobble-api-957839m0) on [docs.rocksky.app](https://docs.rocksky.app)
|
||||
|
||||
:::
|
||||
|
||||
* Navigate to [rocksky.app](https://rocksky.app/) and sign up/logn using your Bluesky account
|
||||
* Then, navigate to the [RockSky developer dashboard](https://rocksky.app/apikeys) and obtain an API Key
|
||||
* 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
|
||||
|
||||
@@ -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) 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>
|
||||
@@ -73,6 +73,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>
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
title: Overview
|
||||
description: Configuration
|
||||
toc_max_heading_level: 3
|
||||
---
|
||||
import Tabs from '@theme/Tabs';
|
||||
@@ -244,11 +245,8 @@ WARN : [App] [Sources] [spotify Secrets] Matched: None | Unmatched: SPOTIFY_SECR
|
||||
|
||||
</DetailsAdmo>
|
||||
|
||||
## Application Options
|
||||
|
||||
These options affect multi-scrobbler's behavior and are not specific to any source/client.
|
||||
|
||||
### Base URL
|
||||
## Base URL
|
||||
|
||||
Defines the URL that is used to generate default redirect URLs for authentication on [spotify](/configuration/sources/spotify) and [lastfm](/configuration/clients/lastfm) -- as well as some logging hints.
|
||||
|
||||
@@ -279,79 +277,51 @@ Useful when running with [docker](../installation/installation.mdx#docker) so th
|
||||
|
||||
</details>
|
||||
|
||||
### Caching
|
||||
## Caching
|
||||
|
||||
Multi-scrobbler caches some activities to persist important data across restarts, reduce external API calls, and make some actions faster.
|
||||
Multi-scrobbler implements caching to persist important data across restarts, reduce external API calls, and make some actions faster.
|
||||
|
||||
All of the activities below are **always** cached **in-memory** with an optional, configurable [**secondary** store](#secondary-caching-configuration) for persistence.
|
||||
A default **in-memory** cache store is used so that you always benefit from some caching. An optional, [**secondary** store](#secondary-caching) can be configured for greater caching capabilities.
|
||||
|
||||
<Tabs groupId="cachedThings" queryString>
|
||||
<TabItem value="scrobbles" label="Scrobbles">
|
||||
|
||||
**Queued** and **Failed** Scrobbles are cached so that any un-scrobbled data you have is persisted across restarts of multi-scrobbler.
|
||||
|
||||
:::tip
|
||||
|
||||
By default, this data use a [Secondary](#secondary-caching-configuration) [File](./?cacheType=file#secondary-caching-configuration) store, configured for you automatically.
|
||||
|
||||
If you have configured a [persisted volume/bind mount](/installation?dockerSetting=storage#recommended-settings) for configuration (`/config` is mounted in [docker compose](/quickstart#create-docker-compose-file)) then you are already done. If you are not persisting this directory then you should consider setting up [Valkey Cache](./?cacheType=valkey#secondary-caching-configuration) for this.
|
||||
|
||||
:::
|
||||
|
||||
##### Configuration
|
||||
|
||||
Use any [Secondary Cache](#secondary-caching-configuration), the config examples below show the default values:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="env" label="ENV">
|
||||
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| :--------------------- | --------- | --------- | --------------------- |
|
||||
| `CACHE_SCROBBLE` | No | `file` | The cache type to use |
|
||||
| `CACHE_SCROBBLE_CONN` | No | `/config` | |
|
||||
<DetailsAdmo type="note" summary="What is Cached?">
|
||||
|
||||
<Tabs groupId="cachedThings">
|
||||
<TabItem value="auth" label="Auth Data">
|
||||
Authentication sessions/tokens/etc... are cached for quicker requests and for persistence across restarts.
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="aio" label="AIO">
|
||||
|
||||
```json5 title="config.json"
|
||||
{
|
||||
"cache": {
|
||||
"scrobble": {
|
||||
"provider": "file",
|
||||
"connection": "/config"
|
||||
}
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
<TabItem value="transform" label="Scrobble Transforms">
|
||||
The results of [transform rules](/configuration/transforms) are cached so that if a scrobble with identical data (track/artists/album) is identified and it has the same set of transform rules then the cached transform results can be applied.
|
||||
</TabItem>
|
||||
<TabItem value="metadata" label="Transform API Calls">
|
||||
API Calls to external (metadata) services used to [Enhance Scrobbles](/configuration/transforms), like calls to [Musicbrainz](/configuration/transforms/musicbrainz), can be cached to avoid duplicate calls and speed up scrobble transformations.
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="auth" label="Auth Data">
|
||||
</DetailsAdmo>
|
||||
|
||||
Authentication sessions/tokens/etc... are cached for quicker requests and for persistence across restarts.
|
||||
<DetailsAdmo type="important" summary="Auth Cache Configuration">
|
||||
|
||||
:::tip
|
||||
Auth caching defaults to a **file** that is stored in the `CONFIG_DIR` directory using the pre-defined file name `ms-auth.cache`.
|
||||
|
||||
By default, this data use a [Secondary](#secondary-caching-configuration) [File](./?cacheType=file#secondary-caching-configuration) store, configured for you automatically.
|
||||
This provides automatic persistence across restarts for long-lived auth data/credentials if you have configured a [persisted volume/bind mount](/installation?dockerSetting=storage#recommended-settings) for configuration (`/config` is mounted in [docker compose](/quickstart#create-docker-compose-file)).
|
||||
|
||||
If you have configured a [persisted volume/bind mount](/installation?dockerSetting=storage#recommended-settings) for configuration (`/config` is mounted in [docker compose](/quickstart#create-docker-compose-file)) then you are already done. If you are not persisting this directory then you should consider setting up [Valkey Cache](./?cacheType=valkey#secondary-caching-configuration) for this.
|
||||
If you wish to use the [secondary store](#secondary-caching) for caching Auth you must explicitly configure it. This is because valkey can potentially be *ephemeral* if you do not provide a volume for its data directory.
|
||||
|
||||
:::
|
||||
To explicitly configure auth to use the secondary store:
|
||||
|
||||
##### Configuration
|
||||
|
||||
Use any [Secondary Cache](#secondary-caching-configuration), the config examples below show the default values:
|
||||
|
||||
<Tabs>
|
||||
<Tabs groupId="configType" queryString>
|
||||
<TabItem value="env" label="ENV">
|
||||
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| :--------------------- | --------- | --------- | --------------------- |
|
||||
| `CACHE_AUTH` | No | `file` | The cache type to use |
|
||||
| `CACHE_AUTH_CONN` | No | `/config` | |
|
||||
```yaml title="compose.yaml"
|
||||
services:
|
||||
multi-scrobbler:
|
||||
# ...
|
||||
environment:
|
||||
// highlight-start
|
||||
- CACHE_AUTH=valkey
|
||||
// highlight-end
|
||||
# ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
@@ -361,8 +331,7 @@ Use any [Secondary Cache](#secondary-caching-configuration), the config examples
|
||||
{
|
||||
"cache": {
|
||||
"auth": {
|
||||
"provider": "file",
|
||||
"connection": "/config"
|
||||
"provider": "valkey"
|
||||
}
|
||||
},
|
||||
// ...
|
||||
@@ -371,93 +340,19 @@ Use any [Secondary Cache](#secondary-caching-configuration), the config examples
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="metadata" label="Transform API Calls">
|
||||
</DetailsAdmo>
|
||||
|
||||
API Calls to external (metadata) services used to [Enhance Scrobbles](/configuration/transforms), like calls to [Musicbrainz](/configuration/transforms/musicbrainz), can be cached to avoid duplicate calls and speed up scrobble transformations.
|
||||
### Secondary Caching Store {#secondary-caching}
|
||||
|
||||
By default, these calls are only cached in memory. If you wish for cached calls to be persisted across restarts then setup [Valkey Cache](./?cacheType=valkey#secondary-caching-configuration).
|
||||
Using a secondary store enables:
|
||||
|
||||
##### Configuration
|
||||
* persistence of cached data across restarts
|
||||
* a larger store (more data is saved)
|
||||
* a longer time-to-live in the store (cached data is fetchable for a longer period)
|
||||
|
||||
Use any [Secondary Cache](#secondary-caching-configuration), the config examples below show the default values:
|
||||
These benefits are particularly beneficial when using [transforms](/configuration/transforms) like Musicbrainz and it is **strongly recommended** for these scenarios.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="env" label="ENV">
|
||||
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| :--------------------- | --------- | ------- | --------------------- |
|
||||
| `CACHE_METADATA` | No | | The cache type to use |
|
||||
| `CACHE_METADATA_CONN` | No | | |
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="aio" label="AIO">
|
||||
|
||||
```json5 title="config.json"
|
||||
{
|
||||
"cache": {
|
||||
"metadata": {
|
||||
"provider": "valkey",
|
||||
"connection": "yourConnectionStringHere"
|
||||
}
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### Secondary Caching Configuration
|
||||
|
||||
The type of cache used, and its connection properties, can be configured through **ENV** or **AIO** config.
|
||||
|
||||
<Tabs groupId="cacheType" queryString>
|
||||
|
||||
<TabItem value="file" label="File">
|
||||
|
||||
**File** cache is stored in the `CONFIG_DIR` directory using the pre-defined file name `ms-[cacheName].cache`.
|
||||
|
||||
<Tabs groupId="configType" queryString>
|
||||
|
||||
<TabItem value="env" label="ENV">
|
||||
|
||||
Example
|
||||
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| :--------------------- | --------- | --------- | ------------------------------------------------------------ |
|
||||
| `CACHE_SCROBBLE` | No | `file` | The cache type to use |
|
||||
| `CACHE_SCROBBLE_CONN` | No | `/config` | The directory, within the container, to store the cache file |
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="aio" label="AIO">
|
||||
|
||||
Example
|
||||
|
||||
```json5 title="config.json"
|
||||
{
|
||||
"cache": {
|
||||
"scrobble": {
|
||||
"provider": "file",
|
||||
"connection": "/config"
|
||||
}
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="valkey" label="Valkey">
|
||||
|
||||
[**Valkey**](https://valkey.io/) is an open-source fork of Redis.
|
||||
Currently, Multi-scrobbler only supports [**Valkey**](https://valkey.io/), an open-source fork of Redis, as a secondary store.
|
||||
|
||||
<details>
|
||||
|
||||
@@ -465,11 +360,13 @@ Example
|
||||
|
||||
A valkey container can be added to the [multi-scrobbler docker compose stack](/installation?runType=docker-compose#docker):
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="named" label="Named Volume">
|
||||
|
||||
```yaml title="docker-compose.yml"
|
||||
services:
|
||||
multi-scrobbler:
|
||||
# ...
|
||||
# adding everything below
|
||||
// highlight-start
|
||||
valkey:
|
||||
image: valkey/valkey
|
||||
@@ -482,6 +379,25 @@ volumes:
|
||||
// highlight-end
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="bind" label="Bind Mount">
|
||||
|
||||
```yaml title="docker-compose.yml"
|
||||
services:
|
||||
multi-scrobbler:
|
||||
# ...
|
||||
// highlight-start
|
||||
valkey:
|
||||
image: valkey/valkey
|
||||
volumes:
|
||||
- ./valkeyData:/data
|
||||
// highlight-end
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
Use `redis://valkey:6379` as the connection string in the configurations below.
|
||||
|
||||
</details>
|
||||
@@ -496,12 +412,16 @@ redis://HOST_IP:HOST_PORT
|
||||
|
||||
<TabItem value="env" label="ENV">
|
||||
|
||||
Example
|
||||
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| :--------------------- | --------- | -------- | ------------------------------------------------------------------- |
|
||||
| `CACHE_METADATA` | Yes | `valkey` | The cache type to use |
|
||||
| `CACHE_METADATA_CONN` | Yes | | The host/IP and port to connect to EX: `redis://192.168.0.120:6379` |
|
||||
```yaml title="compose.yaml"
|
||||
services:
|
||||
multi-scrobbler:
|
||||
# ...
|
||||
environment:
|
||||
// highlight-start
|
||||
- CACHE_VALKEY=redis://192.168.0.120:6379
|
||||
// highlight-end
|
||||
# ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
@@ -512,10 +432,7 @@ Example
|
||||
```json5 title="config.json"
|
||||
{
|
||||
"cache": {
|
||||
"metadata": {
|
||||
"provider": "valkey",
|
||||
"connection": "redis://192.168.0.120:6379"
|
||||
}
|
||||
"valkey": "redis://192.168.0.120:6379"
|
||||
},
|
||||
// ...
|
||||
}
|
||||
@@ -524,11 +441,275 @@ Example
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Database
|
||||
|
||||
Multi-scrobbler depends on a SQLite database (`ms.db`) that is created on first run and stored in the [`CONFIG_DIR`](/installation/?dockerSetting=storage#recommended-settings). When upgrading Multi-scrobbler version, if there are any required database changes than this database is [automatically backed up and migrated.](/updating#database)
|
||||
|
||||
The database stores *all* Plays for your Sources/Clients as well as metadata and debugging information to help troubleshoot issues. Each Play is associated with a Source/Config in the database based on your configuration.
|
||||
|
||||
You **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>
|
||||
|
||||
### Debug Mode
|
||||
</DetailsAdmo>
|
||||
|
||||
A retention policy can be configured to delete Plays, or unused debug data, from the database after a certain amount of time. If no configuration is provided then a default policy is used that should be reasonable for most users.
|
||||
|
||||
<Tabs groupId="policy" queryString>
|
||||
|
||||
<TabItem label="Compaction" value="compaction">
|
||||
|
||||
The **Compaction** Retention Policy is used to delete different types of debug data from your stored Plays.
|
||||
|
||||
This is a useful way to reduce used storage space when you are not having problems with your Plays, or iterating on a configuration, that requires referencing all this extra data.
|
||||
|
||||
There are two types of data that can be compacted (deleted from the Play):
|
||||
|
||||
* `input` - this is the untouched data retrieved by Multi-scrobbler, from a Source, and used to generate a Play/scrobble. This can be used to reconstruct and replay a Play, when used from troubleshooting or reporting an issue
|
||||
* `transform` - this is all of the steps generated by [transforms](/configuration/transforms), the diff of the Play resulting from the step, and any request/responses used to complete the step
|
||||
|
||||
:::note[Defaults]
|
||||
|
||||
When no Compact configuration is provided, Multi-scrobbler uses this policy:
|
||||
|
||||
* Compact (delete) `input` and `transform` data on all Plays after 3 days
|
||||
|
||||
:::
|
||||
|
||||
#### Configuring Compaction Policy
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Details</summary>
|
||||
|
||||
Each value in the configuration properties below can be either
|
||||
|
||||
* a number of seconds EX `3600` = 10 minutes
|
||||
* a unit of a common duration with the pattern `X unit` EX
|
||||
* `30 minutes`
|
||||
* `5 hours`
|
||||
* `2 days`
|
||||
|
||||
<Tabs groupId="configType" queryString>
|
||||
|
||||
<TabItem value="env" label="ENV">
|
||||
|
||||
* `COMPACT_PROPERTIES` - which properties to compact
|
||||
* `RETENTION_COMPACT_AFTER` - Default to use for all Plays
|
||||
* `RETENTION_COMPACT_COMPLETED_AFTER` - Compact only completed Plays after...
|
||||
* `RETENTION_COMPACT_FAILED_AFTER` - Compact only failed Plays after...
|
||||
* `RETENTION_COMPACT_DUPED_AFTER` - Compact only duped/discard Plays after...
|
||||
|
||||
Example
|
||||
|
||||
```ini
|
||||
# only delete input when compacting
|
||||
COMPACT_PROPERTIES=input
|
||||
# compact all plays after 3 days
|
||||
RETENTION_COMPACT_AFTER=3 days
|
||||
# specifically compact completed plays after 30 minutes
|
||||
RETENTION_COMPACT_COMPLETED_AFTER=30 minutes
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="aio" label="AIO">
|
||||
|
||||
Compacting all Play types and deleting both input and transform:
|
||||
|
||||
```json title="config.json"
|
||||
{
|
||||
"database": {
|
||||
"retention": {
|
||||
"compactAfter": "3 days",
|
||||
"compact": [
|
||||
"input",
|
||||
"transform"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* Delete only input during compacting
|
||||
* Compact all after 3 days except completed which compacts after 30 minutes
|
||||
|
||||
```json title="config.json"
|
||||
{
|
||||
"database": {
|
||||
"retention": {
|
||||
"compactAfter": {
|
||||
"completed": "30 minutes",
|
||||
"duped": "3 days",
|
||||
"failed": "3 days"
|
||||
},
|
||||
"compact": [
|
||||
"input"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
</details>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Deletion" value="deletion">
|
||||
|
||||
The **Deletion** Retention Policy is used to delete different types of stored Plays from Multi-scrobbler database.
|
||||
|
||||
**This does not delete Plays from your Clients.** It's only deleting the "in-flight" data MS used to create the scrobble that was eventually sent to your clients.
|
||||
|
||||
:::warning[Plays Should be Ephemeral]
|
||||
|
||||
**Multi-Scrobbler is not designed to store Plays/Scrobbles indefinitely.**
|
||||
|
||||
It should scale fine for thousands of scrobbles but it not meant to store 10's of thousands of scrobbles forever. It is not a scrobbler server.
|
||||
|
||||
You **should** set a reasonable deletion policy so that MS stores less than 1000 scrobbles at a time, ideally less.
|
||||
|
||||
:::
|
||||
|
||||
:::note[Defaults]
|
||||
|
||||
When no Deletion policy configuration is provided, Multi-scrobbler uses this policy:
|
||||
|
||||
* Delete all Plays after 7 days
|
||||
|
||||
:::
|
||||
|
||||
#### Configuring Deletion Policy
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Details</summary>
|
||||
|
||||
Each value in the configuration properties below can be either
|
||||
|
||||
* a number of seconds EX `3600` = 10 minutes
|
||||
* a unit of a common duration with the pattern `X unit` EX
|
||||
* `30 minutes`
|
||||
* `5 hours`
|
||||
* `2 days`
|
||||
|
||||
<Tabs groupId="configType" queryString>
|
||||
|
||||
<TabItem value="env" label="ENV">
|
||||
|
||||
* `RETENTION_DELETE_AFTER` - Default to use for all Plays
|
||||
* `RETENTION_DELETE_COMPLETED_AFTER` - Delete only completed Plays after...
|
||||
* `RETENTION_DELETE_FAILED_AFTER` - Delete only failed Plays after...
|
||||
* `RETENTION_DELETE_DUPED_AFTER` - Delete only duped/discard Plays after...
|
||||
|
||||
Example
|
||||
|
||||
```ini
|
||||
# delete all plays after 3 days
|
||||
RETENTION_DELETE_AFTER=3 days
|
||||
# specifically, delete completed plays after 30 minutes
|
||||
RETENTIOND_DELETE_COMPLETED_AFTER=30 minutes
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="aio" label="AIO">
|
||||
|
||||
Deleting all Play types after 3 days:
|
||||
|
||||
```json title="config.json"
|
||||
{
|
||||
"database": {
|
||||
"retention": {
|
||||
"deleteAfter": "3 days"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* Delete all after 3 days except completed which are deleted after 30 minutes
|
||||
|
||||
```json title="config.json"
|
||||
{
|
||||
"database": {
|
||||
"retention": {
|
||||
"deleteAfter": {
|
||||
"completed": "30 minutes",
|
||||
"duped": "3 days",
|
||||
"failed": "3 days"
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
</details>
|
||||
|
||||
</TabItem>
|
||||
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Debug Mode
|
||||
|
||||
Turning on Debug Mode will
|
||||
|
||||
@@ -549,7 +730,7 @@ To set debug mode either add it to [AIO `config.json`](./?configType=aio#configu
|
||||
|
||||
or set the [ENV](./?configType=env#configuration-types) `DEBUG_MODE=true`
|
||||
|
||||
### Disable Web
|
||||
## Disable Web
|
||||
|
||||
If you do not need the dashboard and/or ingress sources, or have security concerns about ingress and cannot control your hosting environment, the web server and API can be disabled.
|
||||
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
title: Duplicate Scrobble Guidance
|
||||
description: Common Scenarios and Solutions for Duplicate Scrobbles
|
||||
---
|
||||
|
||||
Multi-scrobbler's flexibility in configuration allows you to build some complex pipelines for scrobbling but it can also be a footgun for duplicating scrobbles.
|
||||
|
||||
## How Duplicates Are Detected
|
||||
|
||||
Multi-scrobbler uses multiple techniques for evaluating the **similarity** of text in scrobbles against existing data.
|
||||
|
||||
These techniques are used on all text from a scrobble (title, artist, album...):
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Character and string normalization</summary>
|
||||
|
||||
Symbols and multiple whitespaces are removed. All characters are converted to lowercase. Non ascii characters are converted to their ascii counterparts.
|
||||
|
||||
```
|
||||
this string! is the. same => this string is the same
|
||||
this string is the same => this string is the same
|
||||
ThIs STRING iS THe SAMe => this string is the same
|
||||
|
||||
Dina Ögon => Dina Ogon
|
||||
Nanã => Nana
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Word-order invariant text similarity</summary>
|
||||
|
||||
Strings with multiple words (tokens) are re-arranged so that tokens are ordered to be as close to other, similar tokens are possible:
|
||||
|
||||
Example 1
|
||||
|
||||
```
|
||||
String A: there change is => there change is
|
||||
String B: there is change => there change is
|
||||
```
|
||||
|
||||
Example 2
|
||||
|
||||
```
|
||||
String A: you, doin hwat are => you doin hwat are
|
||||
String B: what are you doing => you doing what are
|
||||
```
|
||||
|
||||
Then, the strings are independently compared using [Levenshtein Distance](https://en.wikipedia.org/wiki/Levenshtein_distance) and [Dice's Coefficient](https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient). The algorithm that gives the highest score for similarity is used so that matching is aggressive.
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
For **Artists**, Multi-scrobbler attempts to extract unique Artists from common patterns in **single-string** artist values and track **titles**.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Matching Common Artist Patterns</summary>
|
||||
|
||||
Common joiners and patterns are detected in artist/track strings and the artists are extracted. Example of common joiners are: `ft.` `feat` `vs.` etc...
|
||||
|
||||
Example 1
|
||||
|
||||
```
|
||||
Melendi, Ryan Lewis, The Righteous Brothers (featuring Joan Jett & The Blackhearts, Robin Schulz)
|
||||
|
||||
becomes
|
||||
|
||||
Melendi, Ryan Lewis, The Righteous Brothers, Joan Jett, The Blackhearts, Robin Schulz
|
||||
```
|
||||
|
||||
Example 2
|
||||
|
||||
```
|
||||
Childish Gambino - 12.38 (feat. 21 Savage, Ink & Kadhja Bonet)
|
||||
|
||||
becomes
|
||||
|
||||
Childish Gambino, 21 Savage, Ink, Kadhja Bonet - 12.38
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
* MS uses "smart" heuristics to prevent artists with actual punctuation (`Tyler, The Creator`) from accidentally being separated
|
||||
* The extraction examples above are done **only for matching** other scrobble data (which is also parsed like the above), the resulting scrobble data is never transformed unless the user explicitly configures it via a [native transform](/configuration/transforms/native)
|
||||
|
||||
:::
|
||||
|
||||
</details>
|
||||
|
||||
The **timestamps** for Scrobbles are compared at different levels of granularity based on where they originate from.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Temporally Comparing Scrobbles</summary>
|
||||
|
||||
Timestamp closeness for two scrobbles can be classified in one of these states:
|
||||
|
||||
**Exact**
|
||||
|
||||
Timestamps differ by 1 seconds or less.
|
||||
|
||||
**Close**
|
||||
|
||||
Timestamps differ by less than the known granularity of the Source they are from. For most Sources (and all Clients) this is 10 seconds or less. For some sources (subsonic) the granularity is 60 seconds because they only report "now playing" changes every 60 seconds.
|
||||
|
||||
**During**
|
||||
|
||||
If the Play was monitoring in real-time by Multi-scrobbler then it knows the time range during which the Play was listened to. If an existing scrobble with similar artist/track/album has a timestamp that occurred *during* the known listening time then it is classified as during.
|
||||
|
||||
**Fuzzy**
|
||||
|
||||
If the timestamp occurs within a few seconds +/- of (existing scrobble + track duration) then, potentially, the Source timestamped the existing scrobble at the *end* of when the user listened to it, rather than when the user *started* listening to it. This is common for Spotify plays.
|
||||
|
||||
___
|
||||
|
||||
Each of the above classification results in a different score that is *roughly* descending from the order given above. Exact score is based on the Souce of the scrobble and if it was monitored in real-time by Multi-Scrobbler.
|
||||
|
||||
</details>
|
||||
|
||||
Finally, all of the above comparison/normalization techniques are combined when comparing a "new" Play/Scrobble against existing data. Each comparison is given a weighted score. If the score is above the match threshold then it is marked as a duplicated.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Match Scoring Example</summary>
|
||||
|
||||
```
|
||||
Frédéric Chopin - Ballade No. 4 in F Minor, Op. 52 @ 2026-05-04T13:01:51-04:00 <-- New scrobble
|
||||
Frédéric Chopin / Krystian Zimerman - Ballade No. 4 in F Minor, Op. 52 @ 2026-05-04T13:01:48-04:00 <-- Existing scrobble from client
|
||||
```
|
||||
|
||||
After comparing against all existing scrobbles from the client, in a timerange around the new scrobble, MS finds a match and logs the match along with the score breakdown of the consistuent parts:
|
||||
|
||||
```
|
||||
Source:
|
||||
Frédéric Chopin - Ballade No. 4 in F Minor, Op. 52 @ 2026-05-04T13:01:51-04:00 (S)
|
||||
|
||||
Closest Scrobble:
|
||||
Frédéric Chopin / Krystian Zimerman - Ballade No. 4 in F Minor, Op. 52 @ 2026-05-04T13:01:48-04:00 (S)
|
||||
|
||||
Artist: (0.30 + Whole Match Bonus 0.52) * (0.3 + Whole Match Bonus 0.05) = 0.29
|
||||
Title: 1.00 * 0.4 = 0.40
|
||||
Time: (Close) 1 * 0.5 = 0.50
|
||||
Time Detail => Existing: 13:01:48-04:00 - Candidate: 13:01:51-04:00 | Temporal Sameness: Close | Play Diff: 3s (Needed <10s) | Range Comparison N/A
|
||||
Score 1.19 => Matched!
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
All of the above techniques and scoring gives Multi-scrobbler a *very good chance* of detecting duplicate scrobbles, regardless of where they were originally from. Of Multi-Scrobbler's [400+ unit tests](https://github.com/FoxxMD/multi-scrobbler/tree/master/src/backend/tests), 70+ are related to testing scoring, artist extraction, and [fuzzing](https://en.wikipedia.org/wiki/Fuzzing) string normalization/simiarlity.
|
||||
|
||||
The dupe detection is so good that it is virtually gauranteed for [most configurations](#valid-configs) and is used for *backlogging* missed scrobbles for Sources that support fetching history.
|
||||
|
||||
**However**, it's not possible to gaurantee this for *all* possible Multi-scrobbler configurations. See the below sections to learn more.
|
||||
|
||||
## Configurations Where Duplicates DO NOT Occur {#valid-configs}
|
||||
|
||||
* Any Source/Client configuration where there is only one Source and one Client and they are not the same service/account
|
||||
* Any Source/Client configuration where scrobbles only flow in **one direction**
|
||||
* IE you *do not* have a Last.fm **Client** Account A that is *also* a Last.fm **Source** Account A
|
||||
|
||||
:::tip
|
||||
|
||||
**The majority of simple Multi-scrobbler configurations fall into these categories. If your workflow is straightforward you do not need to worry about duplicates at all.**
|
||||
|
||||
:::
|
||||
|
||||
Multi-scrobbler can handle recieving the same data from a Source multiple times. This is fine because the data is *always the same* and *always unique* to each Source. Since MS scrobbled the data to your clients you can be pretty confident that the *same* cleanup/matching process for that data will result in an indentical/similar match to the existing scrobble.
|
||||
|
||||
IE Same inputs in -> same inputs out.
|
||||
|
||||
**Even this scenario with multiple, mixed Sources is OK:**
|
||||
|
||||
<img className='image-center' src={require('/img/diagrams/unidirectional-flow.png').default}/>
|
||||
|
||||
Because the scrobbles coming from each **Source** are **independent** of each other. And they end up in Clients that are not *also* used as Sources.
|
||||
|
||||
:::tip
|
||||
|
||||
This is a common scenario when scrobbles are *backlogged* from a Source IE on startup MS fetches the last 50 Plays from Spotify/Lastfm/Listenbrainz.
|
||||
|
||||
:::
|
||||
|
||||
## Configurations Where Duplicates CAN Occur {#invalid-configs}
|
||||
|
||||
### Upstream Scrobbles Sent to Multiple Sources
|
||||
|
||||
In this scenario your device using Spotify is configured in MS as a Spotify **Source** but it **also** is scrobbling to Last.fm (via Pano Scrobbler, as an example).
|
||||
|
||||
<img className='image-center' src={require('/img/diagrams/dupe sources.png').default}/>
|
||||
|
||||
|
||||
❌ **Time Race Condition**: Potentially, the scrobbles arrive in Multi-scrobbler at exactly the same time. There are no existing scrobbles in Koito to check against, yet. So Multi-scrobbler scrobbles both to Koito, resulting in duplicate scrobbles.
|
||||
|
||||
❌ **Different Scrobble Data**: Pano Scrobbler, or Last.fm, [may modify the scrobble data.](#lastfm) When MS recieves the scrobble from Last.fm it may be *just different enough* that it does not get detected as a duplicate.
|
||||
|
||||
### Sources and Clients are Dependent {#dependent-source-client}
|
||||
|
||||
In this scenario, you have a **Client** that is also a **Source** and that Source is feeding scrobbles to a different Client that receives scrobbles from everywhere (or has some overlap with the original Client).
|
||||
|
||||
<img className='image-center' src={require('/img/diagrams/duped client source.png').default}/>
|
||||
|
||||
❌ **Different Scrobble Data**: [Last.fm may modify the scrobble data.](#lastfm) When MS recieves the scrobble from Last.fm it may be *just different enough* that it does not get detected as a duplicate.
|
||||
|
||||
❌ **Consistency Issues**: If the Koito Client has a backed up queue or is not currently scrobbling (imagine it was Last.fm or Listenbrainz, and they had upstream availability issues), then there is no existing scrobble yet and nothing to match against.
|
||||
|
||||
## Configuration Solutions to Avoid Duplicates
|
||||
|
||||
### Independent Sources
|
||||
|
||||
Use a configuration mentioned in [Configurations Where Duplicates DO NOT Occur](#valid-configs): make sure all of your Sources are **independent**. The activity/scrobbles from each Source do not have any upstream duplicates of activity.
|
||||
|
||||
<img className='image-center' src={require('/img/diagrams/independent source.png').default}/>
|
||||
|
||||
### Alternative Sources
|
||||
|
||||
Imagine you have a scenario where a device/player can **only** scrobble to Last.fm. But you want to use MS for all other devices/sources and still have all your scrobbles end up in your "main" Last.fm account for social reasons/stats.
|
||||
|
||||
Instead of using the same Last.fm account for all activity and potentially having [dependent client/sources](#dependent-source-client), create an *additional* last.fm account that is used *only for the device that needs it*. This ensures the scrobbles from that device are isolated from the main account and do not prevent duplicates, so you can continue to use the main account as an MS Client:
|
||||
|
||||
<img className='image-center' src={require('/img/diagrams/alt source.png').default}/>
|
||||
|
||||
### Mirror-Only Clients or Limiting Scrobble Destination {#isolated-sources}
|
||||
|
||||
In Multi-scrobbler, by default, all Sources scrobble to all Clients. Using [Limit Scrobble Destinations](/configuration/sources/#limiting-scrobble-destination) configuration can ensure that your dependent Source/Clients only scrobble to destinations where the scrobbles won't be duplicated. And/or limit other Sources to *not* scrobble to dependent Sources/Clients.
|
||||
|
||||
Here is an example where all Sources only scrobble to a *dependent* Last.fm Client, and then the *dependent* Last.fm Source only scrobbles to a "mirror-only" (backup) Koito Client:
|
||||
|
||||
<img className='image-center' src={require('/img/diagrams/mirror source.png').default}/>
|
||||
|
||||
### Normalize All Scrobbles
|
||||
|
||||
If using one of the above solutions is not possible you can also try to *aggressively normalize all scrobble data* using [the Musicbrainz transform stage](/configuration/transforms/musicbrainz).
|
||||
|
||||
In this scenario you still have [dependent Source/Clients](#dependent-source-client) that are [not isolated](#isolated-sources) but you can *potentially* improve duplicate detection by first using a transform stage to match scrobbles against the Musibrainz database and normalize their track/artist/album data.
|
||||
|
||||
:::warning
|
||||
|
||||
**There is no gaurantee this will work** but it is better than nothing. You should attempt to re-configure MS to use any of the other solutions before using this one.
|
||||
|
||||
:::
|
||||
|
||||
<img className='image-center' src={require('/img/diagrams/normalized dupes.png').default}/>
|
||||
|
||||
## The Last.fm Problem {#lastfm}
|
||||
|
||||
Last.fm will happily accept almost any scrobble data you throw at it.
|
||||
|
||||
It can then decide, opaquely, to match or modify that scrobble to some more "official" data it has server-side. This could be modifying the artists, title, or album sent in the scrobble. It can also mean modifying the [`mbid` (unique identifier)](https://musicbrainz.org/doc/MusicBrainz_Identifier) or adding what it _thinks_ is the correct one.
|
||||
|
||||
There is nothing in the scrobble history API response from Last.fm that identifies it has done any of this. And [it's not super great at being right about these modifications](https://github.com/FoxxMD/multi-scrobbler/issues/333#issuecomment-3145161339).
|
||||
|
||||
What this means for you, the user, is that this is very plausible scenario:
|
||||
|
||||
* You scrobble `Artist Foo, Artist Bar - My Cool Track` to Last.fm via Pano Scrobbler (outside of Multi-scrobbler)
|
||||
* Last.fm modifies the data to `The Artist Foo - My Cool Track ft. Artist Bar (Album Version)`
|
||||
* You _also_ scrobble `Artist Foo, Artist Bar - My Cool Track` to Multi-scrobbler via Pano LFM endpoint **Source**
|
||||
* MS forwards this to Koito Client as-is
|
||||
* Soon after, MS polls your Last.fm **Source** for new scrobbles and finds `The Artist Foo - My Cool Track ft. Artist Bar (Album Version)`
|
||||
* It queries Koito to see if this was scrobbled
|
||||
* It sees that a scrobble did happen at almost the same time BUT the track titles and artist strings are _just different enough_ that it decides it's not a duplicate
|
||||
* MS forwards the scrobble to Koito and you now have a duplicate
|
||||
|
||||
This behavior is unique to Last.fm. It's truly the **worst** out of all the scrobbler services in terms of preserving your data as-is, or providing a unqiue identifier for your data.
|
||||
@@ -1,316 +1,93 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
sidebar_position: 5
|
||||
title: Kitchen Sink
|
||||
description: Kitchen Sink Examples
|
||||
---
|
||||
|
||||
# Example Complex Scenario
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import MultiUsecase from "@site/src/components/snippets/_scenario-config-multi.mdx"
|
||||
import MultiPersonsServices from "@site/docs/configuration/_kitchensink_configs/_multi-persons-services.mdx"
|
||||
import MultiSourcesClients from "@site/docs/configuration/_kitchensink_configs/_multi-sources-clients.mdx"
|
||||
import Musicbrainz from "@site/docs/configuration/_kitchensink_configs/_musicbrainz.mdx"
|
||||
import SingleUsecase from "@site/src/components/snippets/_scenario-config-single.mdx"
|
||||
|
||||
Scenario:
|
||||
## What's This?
|
||||
|
||||
* You want to scrobble plays for yourself (Foxx), Fred, and Mary
|
||||
* Each person has their own [Maloja](/configuration/clients/maloja) server
|
||||
* Each person has their own Spotify account
|
||||
* You have your own Airsonic ([subsonic](/configuration/sources/subsonic)) server you to scrobble from
|
||||
* You have your own [Youtube Music](/configuration/sources/youtube-music) account you want to scrobble from
|
||||
* Mary has her own [Last.fm](/configuration/clients/lastfm) account she also wants to scrobble to
|
||||
* Fred has his own [Spotify](/configuration/sources/spotify) application and provides you with just his access and refresh token because he doesn't trust you (wtf Fred)
|
||||
* Fred has a Plex server and wants to scrobble everything he plays
|
||||
* Mary uses Fred's Plex server but only wants to scrobble her plays from the `podcast` library
|
||||
* The three of you have a shared library on [Plex](/configuration/sources/plex) called `party` that you only play when you are hanging out. You want plays from that library to be scrobbled to everyone's servers.
|
||||
* Fred also has his own [Jellyfin server](/configuration/sources/jellyfin) and wants to scrobble everything he plays
|
||||
* You have an android music app that can scrobble to a [custom listenbrainz server](/configuration/sources/listenbrainz-endpoint)
|
||||
On this page you find contrived, complex usecases for multi-scrobbler that provide examples of equivalent [configuration types](/configuration#configuration-types) to fulfill that usecase.
|
||||
|
||||
### All-in-one Config
|
||||
These example are useful for:
|
||||
|
||||
```json5 title="CONFIG_DIR/config.json"
|
||||
{
|
||||
"sourceDefaults": {
|
||||
"maxPollRetries": 0, // optional, default # of automatic polling restarts on error. can be overridden by property in individual config
|
||||
"maxRequestRetries": 1, // optional, default # of http request retries a source can make before error is thrown. can be overridden by property in individual config
|
||||
"retryMultiplier": 1.5, // optional, default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying). can be overridden by property in individual config
|
||||
},
|
||||
"clientDefaults": {
|
||||
"maxRequestRetries": 1, // optional, default # of http request retries a client can make before error is thrown. can be overridden by property in individual config
|
||||
"retryMultiplier": 1.5, // optional, default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying). can be overridden by property in individual config
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"type": "spotify",
|
||||
"name": "foxxSpot",
|
||||
"clients": ["foxxMaloja"],
|
||||
"data": {
|
||||
"clientId": "foxxSpotifyAppId",
|
||||
"clientSecret": "foxxSpotifyAppSecret",
|
||||
},
|
||||
"options": {
|
||||
"maxRequestRetries": 2, // override default max retries because spotify can...spotty
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "spotify",
|
||||
"name": "marySpot",
|
||||
"clients": ["maryMaloja"],
|
||||
"data": {
|
||||
"clientId": "foxxSpotifyAppId", // only need one application, it can be used by all users of this multi-scrobbler instance
|
||||
"clientSecret": "foxxSpotifyAppSecret",
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "spotify",
|
||||
"name": "fredSpot",
|
||||
"clients": ["fredMaloja"],
|
||||
"data": {
|
||||
"accessToken": "fredsToken",
|
||||
"refreshToken": "fredsRefreshToken",
|
||||
"interval": 120, // he also wants a slower check interval because his application already has heavy api usage
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "plex",
|
||||
"name": "fredPlex",
|
||||
"clients": ["fredMaloja"],
|
||||
"data": {
|
||||
"token": "JtRnwQWD__XMJF8sT3jc",
|
||||
"url": "192.168.0.233:32400",
|
||||
"usersallow": ["fredUser"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "plex",
|
||||
"name": "maryPlex",
|
||||
"clients": ["maryMaloja"],
|
||||
"data": {
|
||||
"token": "JtRnwQWD__XMJF8sT3jc",
|
||||
"url": "192.168.0.233:32400",
|
||||
"usersallow": ["maryUser"], // still need to specify mary as user so not all users who play from 'podcasts' get scrobbled
|
||||
"librariesAllow": ["podcasts"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "plex",
|
||||
"name": "partyPlex",
|
||||
// omitting clients (or making it empty) will make this Source scrobble to all Clients
|
||||
"data": {
|
||||
"token": "JtRnwQWD__XMJF8sT3jc",
|
||||
"url": "192.168.0.233:32400",
|
||||
"librariesAllow": ["party"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "jellyfin",
|
||||
"name": "FredJelly",
|
||||
// omitting clients (or making it empty) will make this Source scrobble to all Clients
|
||||
"data": {
|
||||
"url": "http://localhost:8096",
|
||||
"user": "FoxxMD",
|
||||
"apiKey": "c9fae8756fbf481ebd9c5bb56bd6540c",
|
||||
"usersAllow": ["fredUser"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "subsonic",
|
||||
"name": "foxxAirsonic",
|
||||
"clients": ["foxxMaloja"],
|
||||
"data": {
|
||||
"user": "foxx",
|
||||
"password": "foxxPassword",
|
||||
"url": "https://airsonic.foxx.example"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "ytmusic",
|
||||
"name": "foxxYoutube",
|
||||
"clients": ["foxxMaloja"],
|
||||
"data": {
|
||||
"cookie": "__Secure-3PAPISID=3AxsXpy0MKGu75Qb/AkISXGqOnSDn1jEKn; DEVICE_INFO=ChxOekU0Tmpjek5EWTBPRGd3TlRBMk16QXpNdz09EJbS8Z0GGJbS8Z0G; ...",
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "endpointlz",
|
||||
"name": "listenbrainzfoxx",
|
||||
"clients": ["foxxMaloja"],
|
||||
"data": {
|
||||
"token": "myToken"
|
||||
}
|
||||
}
|
||||
],
|
||||
"clients": [
|
||||
{
|
||||
"type": "maloja",
|
||||
"name": "foxxMaloja",
|
||||
"data": {
|
||||
"url": "https://maloja.foxx.example",
|
||||
"apiKey": "foxxApiKey"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "maloja",
|
||||
"name": "fredMaloja",
|
||||
"data": {
|
||||
"url": "https://maloja.fred.example",
|
||||
"apiKey": "fredApiKey"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "maloja",
|
||||
"name": "maryMaloja",
|
||||
"data": {
|
||||
"url": "https://maloja.mary.example",
|
||||
"apiKey": "maryApiKey"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "lastfm",
|
||||
"name": "maryLFM",
|
||||
"data": {
|
||||
"apiKey": "maryApiKey",
|
||||
"secret": "marySecret",
|
||||
"redirectUri": "http://localhost:9078/lastfm/callback"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
* getting an idea of what you can do with Multi-scrobbler
|
||||
* using these examples as a starting point for your own configuration
|
||||
* providing an example of the same configuration across different [configuration types](/configuration#configuration-types)
|
||||
|
||||
```
|
||||
:::tip
|
||||
|
||||
### Separate JSON files
|
||||
More common, straightforward scenarios can be found in the [**quickstart**](/quickstart) guide.
|
||||
|
||||
```json5 title="CONFIG_DIR/spotify.json"
|
||||
[
|
||||
{
|
||||
// may omit 'type' property since app knows this is file is for spotify configs
|
||||
"name": "foxxSpot",
|
||||
"clients": ["foxxMaloja"],
|
||||
"data": {
|
||||
"clientId": "foxxSpotifyAppId",
|
||||
"clientSecret": "foxxSpotifyAppSecret"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "marySpot",
|
||||
"clients": ["maryMaloja"],
|
||||
"data": {
|
||||
"clientId": "foxxSpotifyAppId",
|
||||
"clientSecret": "foxxSpotifyAppSecret"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fredSpot",
|
||||
"clients": ["fredMaloja"],
|
||||
"data": {
|
||||
"accessToken": "fredsToken",
|
||||
"refreshToken": "fredsRefreshToken",
|
||||
"interval": 120
|
||||
}
|
||||
},
|
||||
]
|
||||
```
|
||||
:::
|
||||
|
||||
```json5 title="CONFIG_DIR/plex.json"
|
||||
[
|
||||
{
|
||||
"name": "fredPlex",
|
||||
"clients": ["fredMaloja"],
|
||||
"data": {
|
||||
"token": "JtRnwQWD__XMJF8sT3jc",
|
||||
"url": "192.168.0.233:32400",
|
||||
"usersallow": ["fredUser"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "maryPlex",
|
||||
"clients": ["maryMaloja"],
|
||||
"data": {
|
||||
"token": "JtRnwQWD__XMJF8sT3jc",
|
||||
"url": "192.168.0.233:32400",
|
||||
"usersallow": ["maryUser"],
|
||||
"librariesAllow": ["podcasts"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "partyPlex",
|
||||
"data": {
|
||||
"token": "JtRnwQWD__XMJF8sT3jc",
|
||||
"url": "192.168.0.233:32400",
|
||||
"librariesAllow": ["party"]
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
## Examples
|
||||
|
||||
```json5 title="CONFIG_DIR/jellyfin.json"
|
||||
[
|
||||
{
|
||||
"name": "FredJelly",
|
||||
"data": {
|
||||
"url": "http://localhost:8096",
|
||||
"user": "FoxxMD",
|
||||
"apiKey": "c9fae8756fbf481ebd9c5bb56bd6540c",
|
||||
"usersAllow": ["fredUser"]
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
### Multiple Sources to Multiple Clients {#multi-sources-clients}
|
||||
|
||||
```json5 title="CONFIG_DIR/ytmusic.json"
|
||||
[
|
||||
{
|
||||
"name": "foxxYoutube",
|
||||
"clients": ["foxxMaloja"],
|
||||
"data": {
|
||||
"cookie": "__Secure-3PAPISID=3AxsXpy0MKGu75Qb/AkISXGqOnSDn1jEKn; DEVICE_INFO=ChxOekU0Tmpjek5EWTBPRGd3TlRBMk16QXpNdz09EJbS8Z0GGJbS8Z0G; ...",
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
```json5 title="CONFIG_DIR/endpointlz.json"
|
||||
[
|
||||
{
|
||||
"name": "listenbrainzfoxx",
|
||||
"clients": ["foxxMaloja"],
|
||||
"data": {
|
||||
"token": "myToken",
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
You are the only user of Multi-scrobbler. You have many Sources you listen to and want to scrobble them to multiple services.
|
||||
|
||||
```json5 title="CONFIG_DIR/maloja.json"
|
||||
[
|
||||
{
|
||||
"name": "foxxMaloja",
|
||||
"data": {
|
||||
"url": "https://maloja.foxx.example",
|
||||
"apiKey": "foxxApiKey"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fredMaloja",
|
||||
"data": {
|
||||
"url": "https://maloja.fred.example",
|
||||
"apiKey": "fredApiKey"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "maryMaloja",
|
||||
"data": {
|
||||
"url": "https://maloja.mary.example",
|
||||
"apiKey": "maryApiKey"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
* You want to **scrobble from...**
|
||||
* A [Spotify](/configuration/sources/spotify) account
|
||||
* A Navidrome instance that you want to use with the built-in [listenbrainz](/configuration/sources/listenbrainz-endpoint) scrobble function
|
||||
* A shared [Plex](/configuration/sources/plex) instance that you want to scrobble only from a specific library named `oldies`
|
||||
* You want to **scrobble to...**
|
||||
* Your decades-old [Last.fm](/configuration/clients/lastfm) account
|
||||
* A [Koito](/configuration/clients/koito) server for backup
|
||||
|
||||
```json5 title="CONFIG_DIR/lastfm.json"
|
||||
[
|
||||
{
|
||||
"name": "maryLFM",
|
||||
"data": {
|
||||
"apiKey": "maryApiKey",
|
||||
"secret": "marySecret",
|
||||
"redirectUri": "http://localhost:9078/lastfm/callback"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
<details>
|
||||
|
||||
<summary>Configuration</summary>
|
||||
|
||||
<MultiSourcesClients/>
|
||||
|
||||
</details>
|
||||
|
||||
### Scrobble for Multiple People to Multiple Clients {#multi-persons-services}
|
||||
|
||||
<MultiUsecase/>
|
||||
|
||||
You want to scrobble plays for yourself (Foxx), Fred, and Mary:
|
||||
|
||||
* The three of you want to **scrobble from...**
|
||||
* Fred and Mary each have their own [Jellyfin servers](/configuration/sources/jellyfin)
|
||||
* You have a [Plex](/configuration/sources/plex) server
|
||||
* The three of you want to **scrobble to...**
|
||||
* Fred and Mary each have their own [Last.fm](/configuration/clients/lastfm) accounts
|
||||
* You have a [Koito](/configuration/clients/koito) server
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Configuration</summary>
|
||||
|
||||
<MultiPersonsServices/>
|
||||
|
||||
</details>
|
||||
|
||||
### Use Musicbrainz to Fix Scrobbles from Navidrome
|
||||
|
||||
You have multiple Sources of music but your personal, on-filesystem music collection is not well organized.
|
||||
|
||||
You want to use the [Musicbrainz Transformer](/configuration/transforms/musicbrainz) to fix any scrobbles that use Navidrome, where your personal collection is located, so that the scrobble artists/album/title are all standardized.
|
||||
|
||||
* You want to **scrobble from...**
|
||||
* A [Spotify](/configuration/sources/spotify) account
|
||||
* A Navidrome instance that you want to use with the built-in [listenbrainz](/configuration/sources/listenbrainz-endpoint) scrobble function
|
||||
* Plays from this Source should be fixed by Musicbrainz before going to your scrobble Clients
|
||||
* You want to **scrobble to...**
|
||||
* A [Last.fm](/configuration/clients/lastfm) account
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Configuration</summary>
|
||||
|
||||
<Musicbrainz/>
|
||||
|
||||
</details>
|
||||
@@ -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>
|
||||
@@ -2,6 +2,7 @@
|
||||
title: Icecast
|
||||
toc_min_heading_level: 2
|
||||
toc_max_heading_level: 5
|
||||
description: Icecast Source Configuration
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
@@ -63,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>
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Koito
|
||||
description: Koito (Source)
|
||||
toc_min_heading_level: 2
|
||||
toc_max_heading_level: 5
|
||||
---
|
||||
@@ -26,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 |
|
||||
| :--------------------- | :-------- | ------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| `LFMENDPOINT_ENABLE` | No | | Use LFM Endpoint as a Source without any other configuration. Only required if slug/token are not provided as ENVs |
|
||||
| `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>
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Last.fm
|
||||
description: Last.fm (Source)
|
||||
toc_min_heading_level: 2
|
||||
toc_max_heading_level: 5
|
||||
---
|
||||
@@ -25,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>
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Libre.fm
|
||||
description: Libre.fm (Source)
|
||||
toc_min_heading_level: 2
|
||||
toc_max_heading_level: 5
|
||||
---
|
||||
@@ -24,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>
|
||||
@@ -109,9 +109,12 @@ To troubleshoot any errors, and assuming you are using Home Assistant, view the
|
||||
## Configuration
|
||||
|
||||
<Config config="ListenbrainzEndpointSourceConfig" fileContent={ListenbrainzEndpointConfig} name="endpointlz">
|
||||
| Environmental Variable | Required? | Default | Description |
|
||||
| :--------------------- | :-------- | ------- | ----------------------------------------------------------------------------------------------------------------- |
|
||||
| `LZENDPOINT_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 |
|
||||
| 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>
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Listenbrainz
|
||||
description: Listenbrainz (Source)
|
||||
toc_min_heading_level: 2
|
||||
toc_max_heading_level: 5
|
||||
---
|
||||
@@ -27,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>
|
||||
@@ -18,11 +18,15 @@ bind_to_address "any" # or a specific ipv4/v6 address
|
||||
port "6600"
|
||||
```
|
||||
|
||||
Multi-scrobbler requires only the [`read`](https://mpd.readthedocs.io/en/stable/user.html#permissions-and-passwords) permission in order to monitor your activity.
|
||||
|
||||
## 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>
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Rocksky
|
||||
description: Rocksky (Source)
|
||||
toc_min_heading_level: 2
|
||||
toc_max_heading_level: 5
|
||||
---
|
||||
@@ -34,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>
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
title: Overview
|
||||
description: Scrobble Sources
|
||||
toc_max_heading_level: 4
|
||||
---
|
||||
|
||||
@@ -40,6 +41,7 @@ A **Source** is a data source that contains information about tracks you are pla
|
||||
| [WebScrobbler](/configuration/sources/webscrobbler) | [Ingress](./?sourceComm=active#by-communication-method) | [History](./?sot=history#by-data-source-of-truth) | ❌ | ✅ | ❌ | ❌ |
|
||||
| [VLC](/configuration/sources/vlc) | [Active](./?sourceComm=active#by-communication-method) | [Activity](./?sot=activity#by-data-source-of-truth) | ❌ | ✅ | ✅ | ❌ |
|
||||
| [Yamaha MusicCast](/configuration/sources/yamaha-musiccast) | [Active](./?sourceComm=active#by-communication-method) | [Activity](./?sot=activity#by-data-source-of-truth) | ❌ | ✅ | ✅ | ❌ |
|
||||
| [Yandex Music](/configuration/sources/yandex-music) | [Active](./?sourceComm=active#by-communication-method) | [Activity](./?sot=activity#by-data-source-of-truth) | | ✅ | ✅ | ❌ |
|
||||
| [Youtube Music](/configuration/sources/youtube-music) | [Active](./?sourceComm=active#by-communication-method) | [History](./?sot=history#by-data-source-of-truth) | ❌ | ✅ | ❌ | ❌ |
|
||||
|
||||
## Features
|
||||
|
||||
@@ -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>
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Teal.fm
|
||||
description: Teal.fm (Source)
|
||||
toc_min_heading_level: 2
|
||||
toc_max_heading_level: 5
|
||||
---
|
||||
@@ -105,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>
|
||||
@@ -0,0 +1,81 @@
|
||||
---
|
||||
title: Yandex 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 JsonConfig from '!!raw-loader!@site/../config/ymbridge.json.example';
|
||||
|
||||
Monitor your [**Yandex Music**](https://music.yandex.com) listening activity in real-time.
|
||||
|
||||
This Source requires a third party docker container to communicate with Yandex Music, [`yandex-music-bridge`](https://github.com/Druidblack/yandex-music-bridge).
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Example Docker Compose with `yandex-music-bridge`</summary>
|
||||
|
||||
```yaml title="~/msData/docker-compose.yml"
|
||||
services:
|
||||
multi-scrobbler:
|
||||
image: foxxmd/multi-scrobbler
|
||||
container_name: multi-scrobbler
|
||||
environment:
|
||||
- TZ=Etc/GMT
|
||||
# ...your other sources/clients here
|
||||
|
||||
# add for yandex music
|
||||
// highlight-start
|
||||
- YMBRIDGE_URL=http://yandex-music-bridge:9980
|
||||
- YMBRIDGE_API_KEY=change-me
|
||||
// highlight-end
|
||||
|
||||
volumes:
|
||||
- "./config:/config"
|
||||
ports:
|
||||
- "9078:9078"
|
||||
restart: unless-stopped
|
||||
|
||||
// highlight-start
|
||||
yandex-music-bridge:
|
||||
image: ghcr.io/druidblack/yandex-music-bridge:latest
|
||||
environment:
|
||||
- TZ=Europe/Moscow
|
||||
- YM_TOKEN=AgAAAAACO3_345345
|
||||
- YM_API_KEY=change-me
|
||||
- YM_PORT=9980
|
||||
- YM_LANGUAGE=ru
|
||||
- YM_ENABLE_YNISON=true
|
||||
- YM_PUSH_TTL=45
|
||||
- YM_QUEUE_CACHE_TTL=15
|
||||
- YM_LOG_LEVEL=INFO
|
||||
ports:
|
||||
- "9980:9980"
|
||||
restart: unless-stopped
|
||||
// highlight-end
|
||||
```
|
||||
|
||||
You will need to acquire your own token for `YM_TOKEN` used in [`yandex-music-bridge`](https://github.com/Druidblack/yandex-music-bridge) using the instructions provided there.
|
||||
|
||||
</details>
|
||||
|
||||
<DetailsAdmo type="tip" summary="Need Help?">
|
||||
|
||||
If your issue is specifically related to [`yandex-music-bridge`](https://github.com/Druidblack/yandex-music-bridge) (errors in the container, usage instructions, general Yandex setup like token etc...) please open an issue on **that repository** instead of Multi-Scrobbler's.
|
||||
|
||||
If you have issues specifically with the MS Source please mention [**Druidblack**](https://github.com/Druidblack) (`@Druidblack`) when opening an [issue](https://github.com/FoxxMD/multi-scrobbler/issues/new/choose) or [discussion](https://github.com/FoxxMD/multi-scrobbler/discussions/new/choose). Yandex Music is [not available](https://en.wikipedia.org/wiki/Yandex_Music) in the country of the main Multi-Scrobbler developer (FoxxMD) so they can only help troubleshooting general MS issues related to Yandex Music.
|
||||
|
||||
</DetailsAdmo>
|
||||
|
||||
## Configuration
|
||||
|
||||
<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>
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Musicbrainz Stage
|
||||
description: Enhancing Scrobbles with Musicbrainz
|
||||
toc_min_heading_level: 2
|
||||
toc_max_heading_level: 5
|
||||
---
|
||||
@@ -139,7 +140,7 @@ Example:
|
||||
|
||||
### Rules and Hooks
|
||||
|
||||
[Add your Stage](https://foxxmd.github.io/multi-scrobbler/configuration/transforms/#stage) to a Source or Client by specifying it in a [Hook](/configuration/transforms/#hook):
|
||||
[Add your Stage](/configuration/transforms/#stage) to a Source or Client by specifying it in a [Hook](/configuration/transforms/#hook):
|
||||
|
||||
```json5 title="subsonic.json"
|
||||
[
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Native Stage
|
||||
description: Enhancing Scrobbles with Built-in Parsing
|
||||
toc_min_heading_level: 2
|
||||
toc_max_heading_level: 5
|
||||
---
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
sidebar_position: 3
|
||||
title: Enhancing Scrobbles
|
||||
toc_max_heading_level: 5
|
||||
---
|
||||
@@ -591,40 +591,7 @@ The output shows the diff between the previous stage (or original Play) and the
|
||||
|
||||
MS uses [caching](/configuration/#caching) to reduce the number of API calls needed for stages like [Musicbrainz](/configuration/transforms/musicbrainz) and to speed up all transforms by caching steps and results. However, the default caching strategy uses a small cache size and very short [TTLs](https://en.wikipedia.org/wiki/Time_to_live) because it is *in-memory*.
|
||||
|
||||
**If you are using any Transform stages you should configure [secondary caching with Valkey for Metadata](/configuration/?cacheType=valkey&cachedThings=metadata#secondary-caching-configuration)** 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>
|
||||
**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.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: User Stage
|
||||
description: Enhancing Scrobbles with Regex Parsing
|
||||
toc_min_heading_level: 2
|
||||
toc_max_heading_level: 5
|
||||
---
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
toc_min_heading_level: 2
|
||||
toc_max_heading_level: 5
|
||||
sidebar_position: 3
|
||||
title: Client Development/Tutorial
|
||||
---
|
||||
|
||||
To do...
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user