-
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: plex integration * feat: plex integration * fix: DeepSource error * fix: lint error * fix: pnpm-lock * fix: lint error * fix: errors * fix: pnpm-lock * fix: reviewed changes * fix: reviewed changes * fix: reviewed changes * fix: pnpm-lock
- Loading branch information
Showing
7 changed files
with
190 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
interface MediaContainer { | ||
Video?: Session[]; | ||
Track?: Session[]; | ||
} | ||
|
||
interface Session { | ||
User?: { | ||
$: { | ||
id: string; | ||
title: string; | ||
thumb?: string; | ||
}; | ||
}[]; | ||
Player?: { | ||
$: { | ||
product: string; | ||
title: string; | ||
}; | ||
}[]; | ||
Session?: { | ||
$: { | ||
id: string; | ||
}; | ||
}[]; | ||
$: { | ||
grandparentTitle?: string; | ||
parentTitle?: string; | ||
title?: string; | ||
index?: number; | ||
type: string; | ||
live?: string; | ||
}; | ||
} | ||
|
||
export interface PlexResponse { | ||
MediaContainer: MediaContainer; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import { parseStringPromise } from "xml2js"; | ||
|
||
import { logger } from "@homarr/log"; | ||
|
||
import { Integration } from "../base/integration"; | ||
import { IntegrationTestConnectionError } from "../base/test-connection-error"; | ||
import type { StreamSession } from "../interfaces/media-server/session"; | ||
import type { PlexResponse } from "./interface"; | ||
|
||
export class PlexIntegration extends Integration { | ||
public async getCurrentSessionsAsync(): Promise<StreamSession[]> { | ||
const token = super.getSecretValue("apiKey"); | ||
|
||
const response = await fetch(`${this.integration.url}/status/sessions`, { | ||
headers: { | ||
"X-Plex-Token": token, | ||
}, | ||
}); | ||
const body = await response.text(); | ||
// convert xml response to objects, as there is no JSON api | ||
const data = await PlexIntegration.parseXml<PlexResponse>(body); | ||
const mediaContainer = data.MediaContainer; | ||
const mediaElements = [mediaContainer.Video ?? [], mediaContainer.Track ?? []].flat(); | ||
|
||
// no sessions are open or available | ||
if (mediaElements.length === 0) { | ||
logger.info("No active video sessions found in MediaContainer"); | ||
return []; | ||
} | ||
|
||
const medias = mediaElements | ||
.map((mediaElement): StreamSession | undefined => { | ||
const userElement = mediaElement.User ? mediaElement.User[0] : undefined; | ||
const playerElement = mediaElement.Player ? mediaElement.Player[0] : undefined; | ||
const sessionElement = mediaElement.Session ? mediaElement.Session[0] : undefined; | ||
|
||
if (!playerElement) { | ||
return undefined; | ||
} | ||
|
||
return { | ||
sessionId: sessionElement?.$.id ?? "unknown", | ||
sessionName: `${playerElement.$.product} (${playerElement.$.title})`, | ||
user: { | ||
userId: userElement?.$.id ?? "Anonymous", | ||
username: userElement?.$.title ?? "Anonymous", | ||
profilePictureUrl: userElement?.$.thumb ?? null, | ||
}, | ||
currentlyPlaying: { | ||
type: mediaElement.$.live === "1" ? "tv" : PlexIntegration.getCurrentlyPlayingType(mediaElement.$.type), | ||
name: mediaElement.$.grandparentTitle ?? mediaElement.$.title ?? "Unknown", | ||
seasonName: mediaElement.$.parentTitle, | ||
episodeName: mediaElement.$.title ?? null, | ||
albumName: mediaElement.$.type === "track" ? (mediaElement.$.parentTitle ?? null) : null, | ||
episodeCount: mediaElement.$.index ?? null, | ||
}, | ||
}; | ||
}) | ||
.filter((session): session is StreamSession => session !== undefined); | ||
|
||
return medias; | ||
} | ||
|
||
public async testConnectionAsync(): Promise<void> { | ||
const token = super.getSecretValue("apiKey"); | ||
|
||
await super.handleTestConnectionResponseAsync({ | ||
queryFunctionAsync: async () => { | ||
return await fetch(this.integration.url, { | ||
headers: { | ||
"X-Plex-Token": token, | ||
}, | ||
}); | ||
}, | ||
handleResponseAsync: async (response) => { | ||
try { | ||
const result = await response.text(); | ||
await PlexIntegration.parseXml<PlexResponse>(result); | ||
return; | ||
} catch { | ||
throw new IntegrationTestConnectionError("invalidCredentials"); | ||
} | ||
}, | ||
}); | ||
} | ||
|
||
static parseXml<T>(xml: string): Promise<T> { | ||
return parseStringPromise(xml) as Promise<T>; | ||
} | ||
|
||
static getCurrentlyPlayingType(type: string): NonNullable<StreamSession["currentlyPlaying"]>["type"] { | ||
switch (type) { | ||
case "movie": | ||
return "movie"; | ||
case "episode": | ||
return "video"; | ||
case "track": | ||
return "audio"; | ||
default: | ||
return "video"; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.