stremio/apps/master-api/src/settings/opensubtitles.ts
Jos Vooges | STH 942da3a58c Allow re-picking OS.org subtitles on imported jobs; fix OpenSubtitles.com User-Agent format.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-30 21:45:48 +02:00

135 lines
4 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";
const DEFAULT_UA = "MediaCluster v1.3.2";
export async function isOpenSubtitlesEnabled(): Promise<boolean> {
const row = await prisma.integrationSetting.findUnique({ where: { id: OS_ID } });
if (!row) return true;
return row.enabled;
}
export type OpenSubtitlesSettingsPublic = {
configured: boolean;
enabled: boolean;
username: string;
userAgent: string;
hasApiKey: boolean;
hasPassword: boolean;
comOk: boolean;
orgOk: boolean;
warning: string | null;
};
export async function getOpenSubtitlesSettingsPublic(): Promise<OpenSubtitlesSettingsPublic> {
const row = await prisma.integrationSetting.findUnique({ where: { id: OS_ID } });
const enabled = row?.enabled ?? true;
return {
configured: enabled,
enabled,
username: row?.username ?? "",
userAgent: row?.userAgent?.trim() || DEFAULT_UA,
hasApiKey: !!row?.apiKeyEnc,
hasPassword: !!row?.passwordEnc,
comOk: false,
orgOk: false,
warning: null,
};
}
export async function upsertOpenSubtitlesSettings(
sessionSecret: string,
input: {
apiKey?: string;
username?: string;
password?: string;
userAgent?: string;
enabled?: boolean;
}
): Promise<OpenSubtitlesSettingsPublic> {
const existing = await prisma.integrationSetting.findUnique({ where: { id: OS_ID } });
const apiKeyTrim = input.apiKey?.trim();
const apiKeyEnc = apiKeyTrim
? encryptSecret(apiKeyTrim, sessionSecret)
: existing?.apiKeyEnc ?? null;
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;
const userAgent =
input.userAgent !== undefined
? input.userAgent.trim() || DEFAULT_UA
: existing?.userAgent?.trim() || DEFAULT_UA;
await prisma.integrationSetting.upsert({
where: { id: OS_ID },
create: {
id: OS_ID,
apiKeyEnc,
username,
passwordEnc,
userAgent,
enabled: input.enabled ?? true,
},
update: {
apiKeyEnc,
username,
passwordEnc,
userAgent,
enabled: input.enabled ?? true,
},
});
let comOk = false;
let orgOk = false;
let warning: string | null = null;
const client = await createOpenSubtitlesClientFromDb(sessionSecret);
if (client) {
try {
await client.search({ imdbId: "tt1375666", languages: "nl" });
comOk = true;
} catch (err) {
warning = err instanceof Error ? err.message : "OpenSubtitles.com test mislukt";
}
}
try {
const { OpenSubtitlesOrgClient } = await import("../opensubtitles/org-client");
await new OpenSubtitlesOrgClient().search({ imdbId: "tt1375666", languages: "nl" });
orgOk = true;
} catch (err) {
const msg = err instanceof Error ? err.message : "OpenSubtitles.org test mislukt";
warning = warning ? `${warning} · ${msg}` : msg;
}
if (!comOk && !orgOk) {
throw new AppError("OPENSUBTITLES", warning || "Geen OpenSubtitles-bron bereikbaar", 400);
}
// Opslaan mag als .org OK is, ook als .com login/API 403 geeft
const pub = await getOpenSubtitlesSettingsPublic();
return { ...pub, comOk, orgOk, warning };
}
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,
row.userAgent
);
}