Add Emby webhook integration and remove unused username field

Add Emby as a third media server integration alongside Plex and Jellyfin.
Emby webhooks use a similar payload format (JSON with nested Item object
and ProviderIds). Also removes the mediaServerUsername field from all
webhook connections since it was never used for authentication — the
token-in-URL is the sole auth mechanism. This simplifies the connection
UX to a single "Connect" button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 00:50:32 -05:00
co-authored by Claude Opus 4.6
parent 0409a65a56
commit bafa45ec79
11 changed files with 2292 additions and 178 deletions
+10 -12
View File
@@ -29,18 +29,14 @@ async function getAdminSession() {
// --- Webhook actions ---
export async function saveWebhookConnection(
provider: "plex" | "jellyfin",
mediaServerUsername: string,
provider: "plex" | "jellyfin" | "emby",
enabled?: boolean,
) {
const session = await getSession();
if (!["plex", "jellyfin"].includes(provider)) {
if (!["plex", "jellyfin", "emby"].includes(provider)) {
throw new Error("Invalid provider");
}
if (!mediaServerUsername?.trim()) {
throw new Error("Media server username is required");
}
const existing = db
.select()
@@ -57,7 +53,6 @@ export async function saveWebhookConnection(
const connection = db
.update(webhookConnections)
.set({
mediaServerUsername: mediaServerUsername.trim(),
enabled: typeof enabled === "boolean" ? enabled : existing.enabled,
})
.where(eq(webhookConnections.id, existing.id))
@@ -81,7 +76,6 @@ export async function saveWebhookConnection(
userId: session.user.id,
provider,
token,
mediaServerUsername: mediaServerUsername.trim(),
enabled: true,
createdAt: now,
})
@@ -95,10 +89,12 @@ export async function saveWebhookConnection(
};
}
export async function deleteWebhookConnection(provider: "plex" | "jellyfin") {
export async function deleteWebhookConnection(
provider: "plex" | "jellyfin" | "emby",
) {
const session = await getSession();
if (!["plex", "jellyfin"].includes(provider)) {
if (!["plex", "jellyfin", "emby"].includes(provider)) {
throw new Error("Invalid provider");
}
@@ -112,10 +108,12 @@ export async function deleteWebhookConnection(provider: "plex" | "jellyfin") {
.run();
}
export async function regenerateWebhookToken(provider: "plex" | "jellyfin") {
export async function regenerateWebhookToken(
provider: "plex" | "jellyfin" | "emby",
) {
const session = await getSession();
if (!["plex", "jellyfin"].includes(provider)) {
if (!["plex", "jellyfin", "emby"].includes(provider)) {
throw new Error("Invalid provider");
}