FoxxMD / multi-scrobbler

Scrobble plays from multiple sources to multiple clients
https://foxxmd.github.io/multi-scrobbler
MIT License
424 stars 17 forks source link

feat: Refactor Jellyfin source to use Jellyfin API #172

Closed FoxxMD closed 2 months ago

FoxxMD commented 4 months ago

Checklist before requesting a review

Type of change

Describe your changes

171

FoxxMD commented 2 months ago

Offline played track events from Symfonium (#87) manifest in JF as:

Unfortunately, the openapi spec/sdk does not have a good way to get all change events for all sessions in a way that is helpful because the offline events are registered in an "instantaneous" session by the client (android symfonium) IE the client connects to send change data and then disconnects -- if we are only using API polling its possible (probable) we will entirely miss the session being active unless polling interval is extremely short.

There is another solution: the websockets system bubbles all session data a user has access to which includes these short lived sessions. The data recieved is what I need:

{
  "MessageId": "8e5e58bb1a104683b2bf1232d6955dbf",
  "Data": {
    "UserId": "1ae8a43ed293456da28274f1a89cd2a5",
    "UserDataList": [
      {
        "PlaybackPositionTicks": 0,
        "PlayCount": 1,
        "IsFavorite": false,
        "LastPlayedDate": "2024-08-29T17:00:19.9369451Z",
        "Played": true,
        "Key": "Ana Tijoux-1977-0001-0005Obst\u00E1culo",
        "ItemId": "097bd1f3c949da7e0b7d4a0f52bae481"
      },
      {
        "PlaybackPositionTicks": 0,
        "PlayCount": 0,
        "IsFavorite": false,
        "Played": false,
        "Key": "39f26414-6687-7c9b-adac-f506b347cb15",
        "ItemId": "39f2641466877c9badacf506b347cb15"
      }
    ]
  },
  "MessageType": "UserDataChanged"
}

More unfortunately, the JS sdk does not have a websocket api yet and the JF devs confirmed for me on matrix that the WS protocol is undocumented. I'll need to dig through the JF web client to figure out exactly what is going on.

Sidebar: The two UserDataList entries in the ws message may explain why the jf webhook plugin was sending multiple events for one playback stopped action...

EDIT: Nope doesn't work. Even though the data from offline client (symfonium) is pushed by WS the LastPlayedDate is still the LAST played date from being the offline play and not the actual timestamp when it was played offline. Checking the item after this data is pushed still shows the stale last played (not offline) so that's not useful either.

Additionally, and inconveniently, the WS data for sessions is only available if the authentication is done using a username/password so its not available to API authentication.

At this point I'm giving up on offline scrobbling because JF just doesn't surface the right info through its API and its impossible to coax it out. It may be stored/sent correctly (see all discussion in #87) but its not retrievable and might as well not exist.

FoxxMD commented 2 months ago

Use docker image foxxmd/multi-scrobbler:pr-172

Migrating from Webhook (multi-scrobbler below v0.9.0) to API In multi-scrobbler **below v0.9.0** communication with Jellyfin was done using Jellyfin's **Webhook** plugin. This has been deprecated in favor of directly using Jeyllfin's API for a better experience in multi-scrobbler. ### To Migrate * Remove the **Webhook** plugin (if not using it for anything else) * Follow the new instructions below (outside this section) to setup API usage * The `servers` (`JELLYFIN_SERVERS`) setting is no longer available as MS only scrobbles from the server using the API anyways. * If you need to scrobble for multiple servers set up each server as a separate Jellyfin source * The `users` (`JELLYFIN_USER`) setting has been renamed `usersAllowed` (`JELLYFIN_USERS_ALLOWED`) * If you were using this filter to ensure only scrobbles from yourself were registered then you no longer need this setting -- by default MS will only scrobble for the user authenticated with Jellyfin's API.

New Jellyfin (API) Source

Must be using Jellyfin 10.7 or greater

It is recommended to use API Key + username but if you are not an admin for your Jellyfin instance you can also authenticate with your Jellyfin username and password.

File-Config

[
  {
    "name": "MyJellyfin",
    "enable": true,
    "clients": [],
    "data": {
      "url": "http://localhost:8096",
      "user": "FoxxMD",

      //either this
      "password": "mypass"
      // or this
      "apiKey": "c9fa0875pfbf411ebb9c55b56bd6540c",

      // optional
      "usersAllowed": ["FoxxMD","SomeOtherUser"],
      "usersBlocked": ["AnotherUser"],
      "devicesAllowed": ["firefox"],
      "devicesBlocked": ["google-home"]
    },
    "options": {
      "logFilterFailure": "debug"
    }
  }
]

ENV-Config

Environmental Variable Required? Default Description
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)
JELLYFIN_PASSWORD No The password of the user to authenticate for. (Must provide either apikey or password)
JELLYFIN_USERS_ALLOWED No Comma-separated list of usernames (from Jellyfin) to scrobble for
JELLYFIN_DEVICES_ALLOWED No Comma-separated list of devices to scrobble from
github-actions[bot] commented 2 months ago

:package: A new release has been made for this pull request.

To play around with this PR, pull an image:

Images are available for x86_64 and ARM64.

Latest commit: be30cdd54079d4233644ff4dfc16878039366db0

Karsten-Yan commented 2 months ago

Edit: Nevermind my error in the original message. I had a typo in my jellyfin url. Now it works like a charm. Thanks for the work and fixing the old issue. Really appreciate it!

I will leave the original message in, in case someone else has a typo in their url :D.

Best, Karsten

Hi @FoxxMD

I am trying to test this new api connection. I get the following error:

multiscrobbler  | [2024-08-30 08:18:54.543 +0200] DEBUG  : [App] [Sources] (jellyfin_karsten) Constructing jellyfin source
multiscrobbler  | [2024-08-30 08:18:54.553 +0200] DEBUG  : [App] [Sources] [Jellyfin - jellyfin_karsten] Attempting to initialize...
multiscrobbler  | [2024-08-30 08:18:54.556 +0200] VERBOSE: [App] [Sources] [Jellyfin - jellyfin_karsten] Building required data init succeeded
multiscrobbler  | [2024-08-30 08:18:59.641 +0200] ERROR  : [App] [Sources] [Jellyfin - jellyfin_karsten] Initialization failed
multiscrobbler  | Error: Initialization failed
multiscrobbler  |     at JellyfinApiSource.initialize (CWD/src/backend/common/AbstractComponent.ts:50:31)
multiscrobbler  |     at runNextTicks (node:internal/process/task_queues:60:5)
multiscrobbler  |     at listOnTimeout (node:internal/timers:540:9)
multiscrobbler  |     at process.processTimers (node:internal/timers:514:7)
multiscrobbler  |     at ScrobbleSources.addSource (CWD/src/backend/sources/ScrobbleSources.ts:596:18)
multiscrobbler  |     at ScrobbleSources.buildSourcesFromConfig (CWD/src/backend/sources/ScrobbleSources.ts:497:25)
multiscrobbler  |     at <anonymous> (CWD/src/backend/index.ts:115:9)
multiscrobbler  | caused by: Error: Communicating with upstream service failed
multiscrobbler  |     at JellyfinApiSource.checkConnection (CWD/src/backend/common/AbstractComponent.ts:176:19)
multiscrobbler  |     at runNextTicks (node:internal/process/task_queues:60:5)
multiscrobbler  |     at listOnTimeout (node:internal/timers:540:9)
multiscrobbler  |     at process.processTimers (node:internal/timers:514:7)
multiscrobbler  |     at JellyfinApiSource.initialize (CWD/src/backend/common/AbstractComponent.ts:40:13)
multiscrobbler  |     at ScrobbleSources.addSource (CWD/src/backend/sources/ScrobbleSources.ts:596:18)
multiscrobbler  |     at ScrobbleSources.buildSourcesFromConfig (CWD/src/backend/sources/ScrobbleSources.ts:497:25)
multiscrobbler  |     at <anonymous> (CWD/src/backend/index.ts:115:9)
multiscrobbler  | caused by: TypeError: Cannot read properties of undefined (reading 'address')
multiscrobbler  |     at JellyfinApiSource.doCheckConnection (CWD/src/backend/sources/JellyfinApiSource.ts:163:51)
multiscrobbler  |     at runNextTicks (node:internal/process/task_queues:60:5)
multiscrobbler  |     at listOnTimeout (node:internal/timers:540:9)
multiscrobbler  |     at process.processTimers (node:internal/timers:514:7)
multiscrobbler  |     at JellyfinApiSource.checkConnection (CWD/src/backend/common/AbstractComponent.ts:163:25)
multiscrobbler  |     at JellyfinApiSource.initialize (CWD/src/backend/common/AbstractComponent.ts:40:13)
multiscrobbler  |     at ScrobbleSources.addSource (CWD/src/backend/sources/ScrobbleSources.ts:596:18)
multiscrobbler  |     at ScrobbleSources.buildSourcesFromConfig (CWD/src/backend/sources/ScrobbleSources.ts:497:25)
multiscrobbler  |     at <anonymous> (CWD/src/backend/index.ts:115:9)

My url is not with http but https (running it through a tailscale funnel). Could that be the problem?