Allow re-picking OS.org subtitles on imported jobs; fix OpenSubtitles.com User-Agent format.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
afcc2e759f
commit
942da3a58c
6 changed files with 273 additions and 37 deletions
|
|
@ -54,10 +54,13 @@ interface JobRow {
|
||||||
movieTitle: string;
|
movieTitle: string;
|
||||||
movieYear: number | null;
|
movieYear: number | null;
|
||||||
posterUrl: string | null;
|
posterUrl: string | null;
|
||||||
|
tmdbId?: number | null;
|
||||||
|
imdbId?: string | null;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
hasSubtitle?: boolean;
|
hasSubtitle?: boolean;
|
||||||
subtitlePath?: string | null;
|
subtitlePath?: string | null;
|
||||||
subtitleError?: string | null;
|
subtitleError?: string | null;
|
||||||
|
subtitleSource?: string | null;
|
||||||
importedPath?: string | null;
|
importedPath?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,6 +132,8 @@ export default function DownloadsPage() {
|
||||||
const [selectedSubId, setSelectedSubId] = useState<number | null>(null);
|
const [selectedSubId, setSelectedSubId] = useState<number | null>(null);
|
||||||
const [selectedSubSource, setSelectedSubSource] = useState<"com" | "org" | null>(null);
|
const [selectedSubSource, setSelectedSubSource] = useState<"com" | "org" | null>(null);
|
||||||
const [subsMsg, setSubsMsg] = useState<string | null>(null);
|
const [subsMsg, setSubsMsg] = useState<string | null>(null);
|
||||||
|
const [subPickJob, setSubPickJob] = useState<JobRow | null>(null);
|
||||||
|
const [subPickBusy, setSubPickBusy] = useState(false);
|
||||||
|
|
||||||
const [jobs, setJobs] = useState<JobRow[]>([]);
|
const [jobs, setJobs] = useState<JobRow[]>([]);
|
||||||
|
|
||||||
|
|
@ -324,16 +329,83 @@ export default function DownloadsPage() {
|
||||||
loadJobs();
|
loadJobs();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function retrySubtitle(id: string) {
|
async function openSubtitlePicker(job: JobRow) {
|
||||||
const res = await fetch(`/api/v1/admin/downloads/jobs/${id}/retry-subtitle`, {
|
setSubPickJob(job);
|
||||||
method: "POST",
|
setSubs([]);
|
||||||
credentials: "include",
|
setSelectedSubId(null);
|
||||||
});
|
setSelectedSubSource(null);
|
||||||
const data = await res.json().catch(() => ({}));
|
setSubsMsg(null);
|
||||||
if (!res.ok) {
|
setSubsBusy(true);
|
||||||
alert(data.error?.message ?? "Ondertitel schrijven mislukt");
|
setSubsConfigured(true);
|
||||||
|
try {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
languages: "nl",
|
||||||
|
releaseHint: job.sourceTitle || job.movieTitle,
|
||||||
|
movieTitle: job.movieTitle,
|
||||||
|
});
|
||||||
|
if (job.movieYear) params.set("movieYear", String(job.movieYear));
|
||||||
|
if (job.imdbId) params.set("imdbId", job.imdbId);
|
||||||
|
else if (job.tmdbId) params.set("tmdbId", String(job.tmdbId));
|
||||||
|
else {
|
||||||
|
setSubsMsg("Geen IMDb/TMDB op deze job — kan niet zoeken");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const res = await fetch(`/api/v1/admin/downloads/subtitles?${params}`, {
|
||||||
|
credentials: "include",
|
||||||
|
});
|
||||||
|
const data = await res.json().catch(() => ({}));
|
||||||
|
if (!res.ok) {
|
||||||
|
setSubsMsg(data.error?.message ?? "Ondertitels zoeken mislukt");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSubsConfigured(data.configured !== false);
|
||||||
|
const results: SubtitleHit[] = data.results ?? [];
|
||||||
|
// Toon .org eerst — werkt zonder .com-login
|
||||||
|
results.sort((a, b) => {
|
||||||
|
if (a.source === "org" && b.source !== "org") return -1;
|
||||||
|
if (b.source === "org" && a.source !== "org") return 1;
|
||||||
|
return (b.downloadCount || 0) - (a.downloadCount || 0);
|
||||||
|
});
|
||||||
|
setSubs(results);
|
||||||
|
const preferred = results.find((r) => r.source === "org") || results[0];
|
||||||
|
if (preferred) {
|
||||||
|
setSelectedSubId(preferred.fileId);
|
||||||
|
setSelectedSubSource(preferred.source === "org" ? "org" : "com");
|
||||||
|
} else {
|
||||||
|
setSubsMsg("Geen NL-ondertitels gevonden");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setSubsBusy(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmSubtitlePick() {
|
||||||
|
if (!subPickJob || selectedSubId == null) {
|
||||||
|
alert("Kies een ondertitel (bij voorkeur OS.org)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSubPickBusy(true);
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/v1/admin/downloads/jobs/${subPickJob.id}/retry-subtitle`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
subtitleFileId: selectedSubId,
|
||||||
|
subtitleSource: selectedSubSource || "org",
|
||||||
|
subtitleLang: "nl",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const data = await res.json().catch(() => ({}));
|
||||||
|
if (!res.ok) {
|
||||||
|
alert(data.error?.message ?? "Ondertitel schrijven mislukt");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSubPickJob(null);
|
||||||
|
loadJobs();
|
||||||
|
} finally {
|
||||||
|
setSubPickBusy(false);
|
||||||
}
|
}
|
||||||
loadJobs();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -487,9 +559,9 @@ export default function DownloadsPage() {
|
||||||
Opnieuw importeren
|
Opnieuw importeren
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{j.status === "IMPORTED" && j.hasSubtitle && !j.subtitlePath && (
|
{j.status === "IMPORTED" && j.importedPath && !j.subtitlePath && (
|
||||||
<button type="button" onClick={() => void retrySubtitle(j.id)}>
|
<button type="button" onClick={() => void openSubtitlePicker(j)}>
|
||||||
Ondertitel opnieuw
|
Ondertitel kiezen
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -536,9 +608,9 @@ export default function DownloadsPage() {
|
||||||
Retry
|
Retry
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{j.status === "IMPORTED" && j.hasSubtitle && !j.subtitlePath && (
|
{j.status === "IMPORTED" && j.importedPath && !j.subtitlePath && (
|
||||||
<button type="button" onClick={() => void retrySubtitle(j.id)}>
|
<button type="button" onClick={() => void openSubtitlePicker(j)}>
|
||||||
Sub opnieuw
|
Sub kiezen
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -676,6 +748,73 @@ export default function DownloadsPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{subPickJob && (
|
||||||
|
<div className="modal-backdrop" onClick={() => setSubPickJob(null)}>
|
||||||
|
<div className="modal-panel downloads-modal" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<div className="page-kicker">Ondertitel opnieuw</div>
|
||||||
|
<h2 className="downloads-modal-title">
|
||||||
|
{subPickJob.movieTitle}
|
||||||
|
{subPickJob.movieYear ? ` (${subPickJob.movieYear})` : ""}
|
||||||
|
</h2>
|
||||||
|
<p className="muted" style={{ marginTop: 0 }}>
|
||||||
|
Kies bij voorkeur <strong>OS.org</strong> — OpenSubtitles.com login faalt vaak (403 /
|
||||||
|
User-Agent).
|
||||||
|
</p>
|
||||||
|
{subsBusy && <p className="muted">OpenSubtitles zoeken…</p>}
|
||||||
|
{subsMsg && <p className="danger-text">{subsMsg}</p>}
|
||||||
|
{!subsBusy && (
|
||||||
|
<div className="match-list">
|
||||||
|
{subs.map((s) => (
|
||||||
|
<button
|
||||||
|
key={`${s.source || "com"}-${s.fileId}`}
|
||||||
|
type="button"
|
||||||
|
className={`match-item${
|
||||||
|
selectedSubId === s.fileId &&
|
||||||
|
(selectedSubSource || "com") === (s.source || "com")
|
||||||
|
? " selected"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedSubId(s.fileId);
|
||||||
|
setSelectedSubSource(s.source === "org" ? "org" : "com");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<strong>
|
||||||
|
{(s.language || "nl").toUpperCase()} · {s.downloadCount} downloads
|
||||||
|
{s.hearingImpaired ? " · HI" : ""}
|
||||||
|
{" · "}
|
||||||
|
{s.source === "org" ? "OS.org" : "OS.com"}
|
||||||
|
</strong>
|
||||||
|
<small className="muted">
|
||||||
|
{s.release || s.fileName}
|
||||||
|
{s.uploader ? ` · ${s.uploader}` : ""}
|
||||||
|
</small>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="downloads-modal-actions">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={subPickBusy || selectedSubId == null}
|
||||||
|
onClick={() => void confirmSubtitlePick()}
|
||||||
|
>
|
||||||
|
{subPickBusy
|
||||||
|
? "Schrijven…"
|
||||||
|
: selectedSubSource === "org"
|
||||||
|
? "Schrijf OS.org-ondertitel"
|
||||||
|
: "Schrijf ondertitel (.com kan falen)"}
|
||||||
|
</button>
|
||||||
|
<button type="button" className="btn-logout" onClick={() => setSubPickJob(null)}>
|
||||||
|
Annuleren
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ export default function SettingsPage() {
|
||||||
const [osApiKey, setOsApiKey] = useState("");
|
const [osApiKey, setOsApiKey] = useState("");
|
||||||
const [osUsername, setOsUsername] = useState("");
|
const [osUsername, setOsUsername] = useState("");
|
||||||
const [osPassword, setOsPassword] = useState("");
|
const [osPassword, setOsPassword] = useState("");
|
||||||
const [osUserAgent, setOsUserAgent] = useState("MediaCluster v1.3");
|
const [osUserAgent, setOsUserAgent] = useState("MediaCluster v1.3.2");
|
||||||
const [osEnabled, setOsEnabled] = useState(true);
|
const [osEnabled, setOsEnabled] = useState(true);
|
||||||
const [osHasKey, setOsHasKey] = useState(false);
|
const [osHasKey, setOsHasKey] = useState(false);
|
||||||
const [osHasPassword, setOsHasPassword] = useState(false);
|
const [osHasPassword, setOsHasPassword] = useState(false);
|
||||||
|
|
@ -133,7 +133,7 @@ export default function SettingsPage() {
|
||||||
setOsConfigured(!!s.configured);
|
setOsConfigured(!!s.configured);
|
||||||
setOsEnabled(s.enabled !== false);
|
setOsEnabled(s.enabled !== false);
|
||||||
setOsUsername(s.username ?? "");
|
setOsUsername(s.username ?? "");
|
||||||
setOsUserAgent(s.userAgent || "MediaCluster v1.3");
|
setOsUserAgent(s.userAgent || "MediaCluster v1.3.2");
|
||||||
setOsHasKey(!!s.hasApiKey);
|
setOsHasKey(!!s.hasApiKey);
|
||||||
setOsHasPassword(!!s.hasPassword);
|
setOsHasPassword(!!s.hasPassword);
|
||||||
setOsWarn(s.warning || null);
|
setOsWarn(s.warning || null);
|
||||||
|
|
@ -231,7 +231,7 @@ export default function SettingsPage() {
|
||||||
apiKey: osApiKey || undefined,
|
apiKey: osApiKey || undefined,
|
||||||
username: osUsername,
|
username: osUsername,
|
||||||
password: osPassword || undefined,
|
password: osPassword || undefined,
|
||||||
userAgent: osUserAgent.trim() || "MediaCluster v1.3",
|
userAgent: osUserAgent.trim() || "MediaCluster v1.3.2",
|
||||||
enabled: osEnabled,
|
enabled: osEnabled,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
@ -526,7 +526,7 @@ export default function SettingsPage() {
|
||||||
<input
|
<input
|
||||||
value={osUserAgent}
|
value={osUserAgent}
|
||||||
onChange={(e) => setOsUserAgent(e.target.value)}
|
onChange={(e) => setOsUserAgent(e.target.value)}
|
||||||
placeholder="MediaCluster v1.3"
|
placeholder="MediaCluster v1.3.2"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,16 @@ export function registerDownloadRoutes(app: FastifyInstance, config: Config): Do
|
||||||
|
|
||||||
app.post("/api/v1/admin/downloads/jobs/:id/retry-subtitle", { preHandler: requireAdmin }, async (request) => {
|
app.post("/api/v1/admin/downloads/jobs/:id/retry-subtitle", { preHandler: requireAdmin }, async (request) => {
|
||||||
const { id } = request.params as { id: string };
|
const { id } = request.params as { id: string };
|
||||||
const job = await downloads.retrySubtitle(id);
|
const body = (request.body || {}) as {
|
||||||
|
subtitleFileId?: number | null;
|
||||||
|
subtitleSource?: "com" | "org" | null;
|
||||||
|
subtitleLang?: string | null;
|
||||||
|
};
|
||||||
|
const job = await downloads.retrySubtitle(id, {
|
||||||
|
subtitleFileId: body.subtitleFileId,
|
||||||
|
subtitleSource: body.subtitleSource,
|
||||||
|
subtitleLang: body.subtitleLang,
|
||||||
|
});
|
||||||
return { job };
|
return { job };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -758,33 +758,82 @@ export class DownloadService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Re-download (if needed) and write sidecar next to already-imported video. */
|
/** Re-download / re-pick and write sidecar next to already-imported video. */
|
||||||
async retrySubtitle(jobId: string) {
|
async retrySubtitle(
|
||||||
|
jobId: string,
|
||||||
|
opts?: {
|
||||||
|
subtitleFileId?: number | null;
|
||||||
|
subtitleSource?: SubtitleSource | null;
|
||||||
|
subtitleLang?: string | null;
|
||||||
|
}
|
||||||
|
) {
|
||||||
const job = await prisma.downloadJob.findUnique({ where: { id: jobId } });
|
const job = await prisma.downloadJob.findUnique({ where: { id: jobId } });
|
||||||
if (!job) throw new AppError("NOT_FOUND", "Job niet gevonden", 404);
|
if (!job) throw new AppError("NOT_FOUND", "Job niet gevonden", 404);
|
||||||
if (!job.importedPath) {
|
if (!job.importedPath) {
|
||||||
throw new AppError("INVALID_REQUEST", "Nog geen geïmporteerd pad — importeer eerst de film", 400);
|
throw new AppError("INVALID_REQUEST", "Nog geen geïmporteerd pad — importeer eerst de film", 400);
|
||||||
}
|
}
|
||||||
if (!job.subtitleFileId && !job.subtitleContent) {
|
|
||||||
throw new AppError("INVALID_REQUEST", "Deze job heeft geen ondertitel gekozen", 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
let content = job.subtitleContent;
|
const pickId =
|
||||||
|
opts?.subtitleFileId != null && Number.isFinite(opts.subtitleFileId)
|
||||||
|
? Math.floor(opts.subtitleFileId)
|
||||||
|
: null;
|
||||||
|
const pickSource: SubtitleSource | null = pickId
|
||||||
|
? opts?.subtitleSource === "org"
|
||||||
|
? "org"
|
||||||
|
: "com"
|
||||||
|
: null;
|
||||||
|
|
||||||
|
let content = pickId ? null : job.subtitleContent;
|
||||||
let fileName: string | null = null;
|
let fileName: string | null = null;
|
||||||
if (!content?.trim() && job.subtitleFileId) {
|
let source: SubtitleSource | null = pickSource || (job.subtitleSource === "org" ? "org" : job.subtitleFileId ? "com" : null);
|
||||||
const source = job.subtitleSource === "org" ? "org" : "com";
|
let fileId = pickId || job.subtitleFileId;
|
||||||
const file = await this.downloadSubtitleFile(job.subtitleFileId, source);
|
const lang = (opts?.subtitleLang || job.subtitleLang || "nl").trim().toLowerCase() || "nl";
|
||||||
|
|
||||||
|
// Nieuwe keuze of opnieuw downloaden
|
||||||
|
if (pickId && pickSource) {
|
||||||
|
const file = await this.downloadSubtitleFile(pickId, pickSource);
|
||||||
content = file.content.toString("utf8");
|
content = file.content.toString("utf8");
|
||||||
fileName = file.fileName;
|
fileName = file.fileName;
|
||||||
await prisma.downloadJob.update({
|
source = pickSource;
|
||||||
where: { id: jobId },
|
fileId = pickId;
|
||||||
data: { subtitleContent: content, subtitleError: null },
|
} else if (!content?.trim() && fileId && source) {
|
||||||
});
|
try {
|
||||||
|
const file = await this.downloadSubtitleFile(fileId, source);
|
||||||
|
content = file.content.toString("utf8");
|
||||||
|
fileName = file.fileName;
|
||||||
|
} catch (err) {
|
||||||
|
const message = err instanceof Error ? err.message : "Download mislukt";
|
||||||
|
// Als .com faalt: forceer opnieuw kiezen via UI (OS.org)
|
||||||
|
if (source === "com") {
|
||||||
|
throw new AppError(
|
||||||
|
"OPENSUBTITLES",
|
||||||
|
`${message} — kies opnieuw een OS.org-ondertitel bij deze job.`,
|
||||||
|
400
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw err instanceof AppError ? err : new AppError("OPENSUBTITLES", message, 400);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!content?.trim()) {
|
if (!content?.trim()) {
|
||||||
throw new AppError("OPENSUBTITLES", "Geen ondertitelinhoud beschikbaar", 400);
|
throw new AppError(
|
||||||
|
"OPENSUBTITLES",
|
||||||
|
"Geen ondertitelinhoud — kies een OS.org-hit bij deze job",
|
||||||
|
400
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await prisma.downloadJob.update({
|
||||||
|
where: { id: jobId },
|
||||||
|
data: {
|
||||||
|
subtitleFileId: fileId,
|
||||||
|
subtitleSource: source,
|
||||||
|
subtitleLang: lang,
|
||||||
|
subtitleContent: content,
|
||||||
|
subtitleError: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const ext = fileName?.toLowerCase().endsWith(".ass") ? ".ass" : ".srt";
|
const ext = fileName?.toLowerCase().endsWith(".ass") ? ".ass" : ".srt";
|
||||||
const ack = await nodeConnectionManager.writeSubtitleAcked(
|
const ack = await nodeConnectionManager.writeSubtitleAcked(
|
||||||
job.nodeId,
|
job.nodeId,
|
||||||
|
|
@ -792,7 +841,7 @@ export class DownloadService {
|
||||||
jobId: job.id,
|
jobId: job.id,
|
||||||
videoPath: job.importedPath,
|
videoPath: job.importedPath,
|
||||||
subtitle: {
|
subtitle: {
|
||||||
language: job.subtitleLang || "nl",
|
language: lang,
|
||||||
extension: ext,
|
extension: ext,
|
||||||
contentBase64: Buffer.from(content, "utf8").toString("base64"),
|
contentBase64: Buffer.from(content, "utf8").toString("base64"),
|
||||||
},
|
},
|
||||||
|
|
@ -818,6 +867,43 @@ export class DownloadService {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
let movieId: string | null = null;
|
||||||
|
if (job.tmdbId) {
|
||||||
|
const movie = await prisma.movie.findUnique({ where: { tmdbId: job.tmdbId } });
|
||||||
|
movieId = movie?.id ?? null;
|
||||||
|
}
|
||||||
|
const existing = await prisma.librarySubtitle.findFirst({
|
||||||
|
where: { downloadJobId: job.id },
|
||||||
|
});
|
||||||
|
if (existing) {
|
||||||
|
await prisma.librarySubtitle.update({
|
||||||
|
where: { id: existing.id },
|
||||||
|
data: {
|
||||||
|
content,
|
||||||
|
fileName,
|
||||||
|
language: lang,
|
||||||
|
format: ext.replace(/^\./, ""),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await prisma.librarySubtitle.create({
|
||||||
|
data: {
|
||||||
|
movieId,
|
||||||
|
tmdbId: job.tmdbId,
|
||||||
|
imdbId: job.imdbId,
|
||||||
|
language: lang,
|
||||||
|
format: ext.replace(/^\./, ""),
|
||||||
|
content,
|
||||||
|
fileName,
|
||||||
|
downloadJobId: job.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`[downloads] library subtitle retry job=${jobId}:`, err);
|
||||||
|
}
|
||||||
|
|
||||||
return prisma.downloadJob.findUniqueOrThrow({
|
return prisma.downloadJob.findUniqueOrThrow({
|
||||||
where: { id: jobId },
|
where: { id: jobId },
|
||||||
include: { node: { select: { id: true, name: true } } },
|
include: { node: { select: { id: true, name: true } } },
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export type SubtitleHit = {
|
||||||
|
|
||||||
type OsLogin = { token: string; baseUrl: string; expiresAt: number };
|
type OsLogin = { token: string; baseUrl: string; expiresAt: number };
|
||||||
|
|
||||||
const DEFAULT_UA = "MediaCluster v1.3";
|
const DEFAULT_UA = "MediaCluster v1.3.2";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpenSubtitles.com REST API (v1).
|
* OpenSubtitles.com REST API (v1).
|
||||||
|
|
@ -38,7 +38,9 @@ export class OpenSubtitlesClient {
|
||||||
private headers(extra?: Record<string, string>): Record<string, string> {
|
private headers(extra?: Record<string, string>): Record<string, string> {
|
||||||
return {
|
return {
|
||||||
"Api-Key": this.apiKey,
|
"Api-Key": this.apiKey,
|
||||||
|
// Docs: descriptive app name + parseable version, e.g. "MyApp v1.2.3"
|
||||||
"User-Agent": this.userAgent,
|
"User-Agent": this.userAgent,
|
||||||
|
"X-User-Agent": this.userAgent,
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
...extra,
|
...extra,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { AppError } from "../security/errors";
|
||||||
import { OpenSubtitlesClient } from "../opensubtitles/client";
|
import { OpenSubtitlesClient } from "../opensubtitles/client";
|
||||||
|
|
||||||
const OS_ID = "opensubtitles";
|
const OS_ID = "opensubtitles";
|
||||||
const DEFAULT_UA = "MediaCluster v1.3";
|
const DEFAULT_UA = "MediaCluster v1.3.2";
|
||||||
|
|
||||||
export async function isOpenSubtitlesEnabled(): Promise<boolean> {
|
export async function isOpenSubtitlesEnabled(): Promise<boolean> {
|
||||||
const row = await prisma.integrationSetting.findUnique({ where: { id: OS_ID } });
|
const row = await prisma.integrationSetting.findUnique({ where: { id: OS_ID } });
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue