Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .dev.vars.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ OVERSEERR_API_KEY=""
# Only if the Overseerr hostname is behind Cloudflare Access (Zero Trust) — service token.
CF_ACCESS_CLIENT_ID=""
CF_ACCESS_CLIENT_SECRET=""
# Shared PIN required to request in Overseerr (the app is public). Unset = requests disabled.
REQUEST_PIN=""
19 changes: 17 additions & 2 deletions public/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -628,16 +628,31 @@ function showMatch(card, reason) {

async function addToOverseerr() {
if (!currentMatchCard) return;
let pin = localStorage.getItem("cinemate_request_pin");
if (!pin) {
pin = (window.prompt("Request PIN") || "").trim();
if (!pin) return;
localStorage.setItem("cinemate_request_pin", pin);
}
const btn = $("add-overseerr-btn");
btn.disabled = true;
try {
await api(`/api/rooms/${state.room.id}/request`, {
method: "POST",
body: JSON.stringify({ user_id: state.userId, tmdb_id: currentMatchCard.tmdb_id }),
body: JSON.stringify({ user_id: state.userId, tmdb_id: currentMatchCard.tmdb_id, pin }),
});
toast("✅ Requested in Overseerr");
} catch (e) {
toast(e.message === "not_configured" ? "Overseerr not set up" : "Overseerr request failed");
if (e.message === "invalid_pin") {
localStorage.removeItem("cinemate_request_pin");
toast("Wrong PIN — try again");
} else if (e.message === "requests_disabled") {
toast("Requests are disabled");
} else if (e.message === "not_configured") {
toast("Overseerr not set up");
} else {
toast("Overseerr request failed");
}
} finally {
btn.disabled = false;
}
Expand Down
6 changes: 6 additions & 0 deletions src/routes/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,12 @@ rooms.post("/:id/request", async (c) => {
return c.json({ error: "forbidden" }, 403);
}

// Overseerr requests are gated by a shared PIN (the app itself is public).
// If no PIN is configured, requests are disabled (closed by default).
if (!c.env.REQUEST_PIN) return c.json({ error: "requests_disabled" }, 403);
const pin = typeof body?.pin === "string" ? body.pin : "";
if (pin !== c.env.REQUEST_PIN) return c.json({ error: "invalid_pin" }, 403);

const result = await createRequest(c.env, room.media_type, tmdbId);
if (!result.ok) return c.json({ error: result.error ?? "request_failed" }, 502);
return c.json({ ok: true, tmdb_id: tmdbId }, 200);
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export interface Env {
/** Secret — Cloudflare Access service token (optional; if the Overseerr hostname is behind Access). */
CF_ACCESS_CLIENT_ID: string;
CF_ACCESS_CLIENT_SECRET: string;
/** Secret — shared PIN required to request in Overseerr. If unset, requests are disabled. */
REQUEST_PIN: string;
}

export type MediaType = "movie" | "tv";
Expand Down
Loading