Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Refactor Jellyfin source to use Jellyfin API #172

Merged
merged 4 commits into from
Aug 30, 2024
Merged

Conversation

FoxxMD
Copy link
Owner

@FoxxMD FoxxMD commented Jul 22, 2024

Checklist before requesting a review

Type of change

  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

Describe your changes

#171

  • Implement new jellyfin source using JF typescript api client library for communication
    • Uses either API Key or username/password
  • Implement real-time scrobble monitoring
  • Implement feature parity filters from old jellyfin source (users) and add devices filters
  • Refactor documentation for new configuration with api and add migration steps for webhook users
  • Add deprecation warning to jellyfin webhook source
  • ❌ backlog implementation
    • History items only indicate the track was played but not for how long (or that it was long enough to scrobble)
  • ❌ investigate "offline" scrobbling behavior Jellyfin songs listened to via Symfonium while offline are not scrobbled. #87
  • test multiple artists behavior Multiple Artists from Jellyfin are not scrobbled correctly #70

@FoxxMD FoxxMD added enhancement New feature or request safe to test trusted to build image labels Jul 22, 2024
@FoxxMD FoxxMD self-assigned this Jul 22, 2024
@FoxxMD FoxxMD changed the title feat(jellyfin)!: Refactor Jellyfin source to use Jellyfin API feat: Refactor Jellyfin source to use Jellyfin API Jul 22, 2024
* Implement new jellyfin source using JF typescript api client library for communication
  * Use either API Key or username/password
* Implement real-time scrobble monitoring
* Implement feature parity filters from old jellyfin source (users) and add devices filters
* Refactor documentation for new configuration with api and add migration steps for webhook users
* Add deprecation warning to jellyfin webhook source
@FoxxMD
Copy link
Owner Author

FoxxMD commented Aug 29, 2024

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

  • ActivityLogEntry from the ActivityLog endpoint, but these only include timestamp the activity was received and the library item does not not get updated UserData so no LastPlayedDate is available
  • UserDataChanged events for a session which do include more information (UserItemDataDto ) including LastPlayedDate which is what we need

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
Copy link
Owner Author

FoxxMD commented Aug 29, 2024

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

  • Create an API Key for multi-scrobbler
    • In the Jellyfin desktop web UI Navigate to -> Administration -> Dashboard -> API Keys (http://YOUR_JELLYIN_URL/web/index.html#!/apikeys.html)
    • Click + button and create a new key with App name multi-scrobbler
    • Copy the created API Key value for use in configuration below

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

Copy link
Contributor

📦 A new release has been made for this pull request.

To play around with this PR, pull an image:

  • foxxmd/multi-scrobbler:pr-172
  • foxxmd/multi-scrobbler:pr-172-alpine.

Images are available for x86_64 and ARM64.

Latest commit: be30cdd

@Karsten-Yan
Copy link

Karsten-Yan commented Aug 30, 2024

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?

@FoxxMD FoxxMD merged commit 8431e7b into develop Aug 30, 2024
4 checks passed
@FoxxMD FoxxMD deleted the jellyfinApi branch August 30, 2024 13:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request safe to test trusted to build image
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants