Skip to content

Commit

Permalink
feat(jellyfin)!: Refactor Jellyfin source to use Jellyfin API
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
FoxxMD committed Jul 22, 2024
1 parent 9735b98 commit 6e0c636
Show file tree
Hide file tree
Showing 12 changed files with 508 additions and 61 deletions.
12 changes: 8 additions & 4 deletions config/jellyfin.json.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
"enable": true,
"clients": [],
"data": {
"users": ["FoxxMD"],
"servers": ["myServer","anotherServer"]
"url": "http://localhost:8096",
"user": "FoxxMD",
"apiKey": "c9fae8756fbf481ebd9c5bb56bd6540c",
"usersAllowed": ["FoxxMD","SomeOtherUser"],
"usersBlocked": ["AnotherUser"],
"devicesAllowed": ["firefox"],
"devicesBlocked": ["google-home"]
},
"options": {
"logPayload": false,
"logFilterFailure": "warn"
"logFilterFailure": "debug"
}
}
]
57 changes: 36 additions & 21 deletions docsite/docs/configuration/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ If you do not need the dashboard and/or ingress sources, or have security concer

:::warning

Any **ingress-based sources will be unusable** (Plex, Jellyfin, Tautulli, etc...) if this is disabled.
Any **ingress-based sources will be unusable** (Plex, Tautulli, etc...) if this is disabled.

:::

Expand Down Expand Up @@ -358,37 +358,52 @@ Can use this source for any application that implements the [Subsonic API](http:

### [Jellyfin](https://jellyfin.org/)

#### Webhook Setup
<details>

<summary>Migrating from Webhook (multi-scrobbler below `v0.9.0`) to API</summary>

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.

</details>

Must be using Jellyfin 10.7 or greater

* In the Jellyfin desktop web UI Navigate to -> Administration -> Dashboard -> Plugins -> Catalog
* Under Notifications -> **Webhook** -> Install, then restart your server
* Navigate back to -> Administration -> Dashboard -> Plugins -> My Plugins -> Webhook
* Click "..." -> Settings
* In Webhook settings:
* `Add Generic Destination`
* In the new `Generic` dropdown:
* Webhook Url: `http://localhost:9078/jellyfin`
* Notification Type: `Playback Progress`
* Item Type: `Songs`
* Check `Send All Properties`
* Save
* 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.**

#### Configuration

:::tip

If you see errors in the MS logs regarding `missing headers` when using Jellyfin [see this workaround.](../FAQ.md#jellyfin-has-warnings-about-missing-headers)
By default, multi-scrobbler will only scrobble for the user authenticated with the API. Allowed Users are only necessary if you want to scrobble for additional users.

:::

#### Configuration

<Tabs groupId="configType" queryString>
<TabItem value="env" label="ENV">
| Environmental Variable | Required? | Default | Description |
|------------------------|-----------|---------|-------------------------------------------------------------------|
| `JELLYFIN_USER` | | | Comma-separated list of usernames (from Jellyfin) to scrobble for |
| `JELLYFIN_SERVER` | | | Comma-separated list of Jellyfin server names to scrobble from |
| 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 |
</TabItem>
<TabItem value="file" label="File">
<details>
Expand Down
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"@foxxmd/get-version": "^0.0.3",
"@foxxmd/logging": "^0.1.14",
"@foxxmd/string-sameness": "^0.4.0",
"@jellyfin/sdk": "^0.10.0",
"@kenyip/backoff-strategies": "^1.0.4",
"@react-nano/use-event-source": "^0.13.0",
"@reduxjs/toolkit": "^1.9.5",
Expand Down
1 change: 1 addition & 0 deletions src/backend/common/infrastructure/Atomic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export const readyStates: ReadyState[] = [NOT_READY, GETTING_READY, READY];
export interface InternalConfig {
localUrl: URL
configDir: string
version: string

logger: Logger
}
Expand Down
65 changes: 64 additions & 1 deletion src/backend/common/infrastructure/config/source/jellyfin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CommonSourceConfig, CommonSourceData } from "./index.js";
import { CommonSourceConfig, CommonSourceData, CommonSourceOptions } from "./index.js";

export interface JellyData extends CommonSourceData {
/**
Expand All @@ -19,10 +19,73 @@ export interface JellyData extends CommonSourceData {
servers?: string | string[]
}

export interface JellyApiData extends CommonSourceData {
/**
* HOST:PORT of the Jellyfin server to connect to
* */
url: string
/**
* The username of the user to authenticate for or track scrobbles for
* */
user: string
/**
* Password of the username to authenticate for
*
* Required if `apiKey` is not provided.
* */
password?: string
/**
* API Key to authenticate with.
*
* Required if `password` is not provided.
* */
apiKey?: string

/**
* Only scrobble for specific users (case-insensitive)
*
* If `true` MS will scrobble activity from all users
* */
usersAllow?: string | true | string[]
/**
* Do not scrobble for these users (case-insensitive)
* */
usersBlock?: string | string[]

/**
* Only scrobble if device or application name contains strings from this list (case-insensitive)
*
* Note: This only applies to real-time scrobbling as JF does not track device info in user activity history
* */
devicesAllow?: string | string[]
/**
* Do not scrobble if device or application name contains strings from this list (case-insensitive)
*
* Note: This only applies to real-time scrobbling as JF does not track device info in user activity history
* */
devicesBlock?: string | string[]
}

export interface JellyApiOptions extends CommonSourceOptions {
/* /!**
* Set a persistent device id suffix
* *!/
deviceId?: string*/
}

export interface JellySourceConfig extends CommonSourceConfig {
data: JellyData
}

export interface JellySourceAIOConfig extends JellySourceConfig {
type: 'jellyfin'
}

export interface JellyApiSourceConfig extends CommonSourceConfig {
data: JellyApiData
options: JellyApiOptions
}

export interface JellyApiSourceAIOConfig extends JellyApiSourceConfig {
type: 'jellyfin'
}
4 changes: 3 additions & 1 deletion src/backend/common/infrastructure/config/source/sources.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ChromecastSourceAIOConfig, ChromecastSourceConfig } from "./chromecast.js";
import { DeezerSourceAIOConfig, DeezerSourceConfig } from "./deezer.js";
import { JellySourceAIOConfig, JellySourceConfig } from "./jellyfin.js";
import { JellyApiSourceAIOConfig, JellyApiSourceConfig, JellySourceAIOConfig, JellySourceConfig } from "./jellyfin.js";
import { JRiverSourceAIOConfig, JRiverSourceConfig } from "./jriver.js";
import { KodiSourceAIOConfig, KodiSourceConfig } from "./kodi.js";
import { LastFmSouceAIOConfig, LastfmSourceConfig } from "./lastfm.js";
Expand All @@ -24,6 +24,7 @@ export type SourceConfig =
| DeezerSourceConfig
| SubSonicSourceConfig
| JellySourceConfig
| JellyApiSourceConfig
| LastfmSourceConfig
| YTMusicSourceConfig
| MPRISSourceConfig
Expand All @@ -43,6 +44,7 @@ export type SourceAIOConfig =
| DeezerSourceAIOConfig
| SubsonicSourceAIOConfig
| JellySourceAIOConfig
| JellyApiSourceAIOConfig
| LastFmSouceAIOConfig
| YTMusicSourceAIOConfig
| MPRISSourceAIOConfig
Expand Down
2 changes: 1 addition & 1 deletion src/backend/ioc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const createRoot = (options?: RootOptions) => {
const localUrl = generateBaseURL(baseUrl, items.port)
return {
clients: () => new ScrobbleClients(items.clientEmitter, items.sourceEmitter, localUrl, items.configDir, options.logger),
sources: () => new ScrobbleSources(items.sourceEmitter, localUrl, items.configDir, options.logger),
sources: () => new ScrobbleSources(items.sourceEmitter, { localUrl, configDir: items.configDir, version }, options.logger),
notifiers: () => new Notifiers(items.notifierEmitter, items.clientEmitter, items.sourceEmitter, options.logger),
localUrl,
hasDefinedBaseUrl: baseUrl !== undefined,
Expand Down
Loading

0 comments on commit 6e0c636

Please sign in to comment.