stremio/apps/master-api/src/settings/opensubtitles.ts
Jos Vooges | STH d89b207acf Store OpenSubtitles credentials in Admin settings and attach NL subs to Stremio streams.
No env vars needed; subtitle text is served from Master after download import.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-30 18:40:36 +02:00

92 lines
3 KiB
TypeScript

import { prisma } from "../database/client";
import { decryptSecret, encryptSecret } from "../security/crypto";
import { AppError } from "../security/errors";
import { OpenSubtitlesClient } from "../opensubtitles/client";
const OS_ID = "opensubtitles";
export type OpenSubtitlesSettingsPublic = {
configured: boolean;
enabled: boolean;
username: string;
hasApiKey: boolean;
hasPassword: boolean;
};
export async function getOpenSubtitlesSettingsPublic(): Promise<OpenSubtitlesSettingsPublic> {
const row = await prisma.integrationSetting.findUnique({ where: { id: OS_ID } });
return {
configured: !!(row?.apiKeyEnc && row.enabled),
enabled: row?.enabled ?? true,
username: row?.username ?? "",
hasApiKey: !!row?.apiKeyEnc,
hasPassword: !!row?.passwordEnc,
};
}
export async function upsertOpenSubtitlesSettings(
sessionSecret: string,
input: {
apiKey?: string;
username?: string;
password?: string;
enabled?: boolean;
}
): Promise<OpenSubtitlesSettingsPublic> {
const existing = await prisma.integrationSetting.findUnique({ where: { id: OS_ID } });
const apiKeyTrim = input.apiKey?.trim();
if (!apiKeyTrim && !existing?.apiKeyEnc) {
throw new AppError("INVALID_REQUEST", "API-key is verplicht", 400);
}
const apiKeyEnc = apiKeyTrim
? encryptSecret(apiKeyTrim, sessionSecret)
: existing!.apiKeyEnc;
const passwordEnc =
input.password !== undefined
? input.password.trim()
? encryptSecret(input.password.trim(), sessionSecret)
: null
: existing?.passwordEnc ?? null;
const username =
input.username !== undefined ? input.username.trim() || null : existing?.username ?? null;
await prisma.integrationSetting.upsert({
where: { id: OS_ID },
create: {
id: OS_ID,
apiKeyEnc,
username,
passwordEnc,
enabled: input.enabled ?? true,
},
update: {
apiKeyEnc,
username,
passwordEnc,
enabled: input.enabled ?? true,
},
});
// Validate by searching a known movie (optional light check: login if creds)
const client = await createOpenSubtitlesClientFromDb(sessionSecret);
if (!client) throw new AppError("OPENSUBTITLES", "Kon client niet maken", 400);
try {
await client.search({ imdbId: "tt1375666", languages: "nl" });
} catch (err) {
const message = err instanceof Error ? err.message : "OpenSubtitles test mislukt";
throw new AppError("OPENSUBTITLES", message, 400);
}
return getOpenSubtitlesSettingsPublic();
}
export async function createOpenSubtitlesClientFromDb(
sessionSecret: string
): Promise<OpenSubtitlesClient | null> {
const row = await prisma.integrationSetting.findUnique({ where: { id: OS_ID } });
if (!row?.enabled || !row.apiKeyEnc) return null;
const apiKey = decryptSecret(row.apiKeyEnc, sessionSecret);
const password = row.passwordEnc ? decryptSecret(row.passwordEnc, sessionSecret) : undefined;
return new OpenSubtitlesClient(apiKey, row.username ?? undefined, password);
}