feat: add jellyfin integration (#672)

* feat: #655 implement jellyfin media server

* fix: table overflow

* feat: pr feedback

* refactor: format

* refactor: merge existing code

* fix: code smells

* refactor: format commit
This commit is contained in:
Manuel
2024-07-03 20:06:57 +02:00
committed by GitHub
parent 1cf119c768
commit bb8640b162
25 changed files with 435 additions and 17 deletions

View File

@@ -1,6 +1,7 @@
import type { IntegrationKind } from "@homarr/definitions";
import { HomeAssistantIntegration } from "../homeassistant/homeassistant-integration";
import { JellyfinIntegration } from "../jellyfin/jellyfin-integration";
import { SonarrIntegration } from "../media-organizer/sonarr/sonarr-integration";
import { PiHoleIntegration } from "../pi-hole/pi-hole-integration";
import type { IntegrationInput } from "./integration";
@@ -11,6 +12,8 @@ export const integrationCreatorByKind = (kind: IntegrationKind, integration: Int
return new PiHoleIntegration(integration);
case "homeAssistant":
return new HomeAssistantIntegration(integration);
case "jellyfin":
return new JellyfinIntegration(integration);
case "sonarr":
return new SonarrIntegration(integration);
default:

View File

@@ -1,8 +1,12 @@
// General integrations
export { PiHoleIntegration } from "./pi-hole/pi-hole-integration";
export { HomeAssistantIntegration } from "./homeassistant/homeassistant-integration";
export { JellyfinIntegration } from "./jellyfin/jellyfin-integration";
export { SonarrIntegration } from "./media-organizer/sonarr/sonarr-integration";
// Types
export type { StreamSession } from "./interfaces/media-server/session";
// Helpers
export { IntegrationTestConnectionError } from "./base/test-connection-error";
export { integrationCreatorByKind } from "./base/creator";

View File

@@ -0,0 +1,17 @@
export interface StreamSession {
sessionId: string;
sessionName: string;
user: {
userId: string;
username: string;
profilePictureUrl: string | null;
};
currentlyPlaying: {
type: "audio" | "video" | "tv" | "movie";
name: string;
seasonName: string | undefined;
episodeName?: string | null;
albumName?: string | null;
episodeCount?: number | null;
} | null;
}

View File

@@ -0,0 +1,68 @@
import { Jellyfin } from "@jellyfin/sdk";
import { getSessionApi } from "@jellyfin/sdk/lib/utils/api/session-api";
import { getSystemApi } from "@jellyfin/sdk/lib/utils/api/system-api";
import { Integration } from "../base/integration";
import type { StreamSession } from "../interfaces/media-server/session";
export class JellyfinIntegration extends Integration {
private readonly jellyfin: Jellyfin = new Jellyfin({
clientInfo: {
name: "Homarr",
version: "0.0.1",
},
deviceInfo: {
name: "Homarr",
id: "homarr",
},
});
public async testConnectionAsync(): Promise<void> {
const api = this.getApi();
const systemApi = getSystemApi(api);
await systemApi.getPingSystem();
}
public async getCurrentSessionsAsync(): Promise<StreamSession[]> {
const api = this.getApi();
const sessionApi = getSessionApi(api);
const sessions = await sessionApi.getSessions();
if (sessions.status !== 200) {
throw new Error(
`Jellyfin server ${this.integration.url} returned a non successful status code: ${sessions.status}`,
);
}
return sessions.data.map((sessionInfo): StreamSession => {
let nowPlaying: StreamSession["currentlyPlaying"] | null = null;
if (sessionInfo.NowPlayingItem) {
nowPlaying = {
type: "tv",
name: sessionInfo.NowPlayingItem.Name ?? "",
seasonName: sessionInfo.NowPlayingItem.SeasonName ?? "",
episodeName: sessionInfo.NowPlayingItem.EpisodeTitle,
albumName: sessionInfo.NowPlayingItem.Album ?? "",
episodeCount: sessionInfo.NowPlayingItem.EpisodeCount,
};
}
return {
sessionId: `${sessionInfo.Id}`,
sessionName: `${sessionInfo.Client} (${sessionInfo.DeviceName})`,
user: {
profilePictureUrl: `${this.integration.url}/Users/${sessionInfo.UserId}/Images/Primary`,
userId: sessionInfo.UserId ?? "",
username: sessionInfo.UserName ?? "",
},
currentlyPlaying: nowPlaying,
};
});
}
private getApi() {
const apiKey = this.getSecretValue("apiKey");
return this.jellyfin.createApi(this.integration.url, apiKey);
}
}