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:
@@ -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:
|
||||
|
||||
@@ -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";
|
||||
|
||||
17
packages/integrations/src/interfaces/media-server/session.ts
Normal file
17
packages/integrations/src/interfaces/media-server/session.ts
Normal 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;
|
||||
}
|
||||
68
packages/integrations/src/jellyfin/jellyfin-integration.ts
Normal file
68
packages/integrations/src/jellyfin/jellyfin-integration.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user