import { useEffect, useState } from "react"; import { Button } from "~/shared/components/ui/button"; import { Input } from "~/shared/components/ui/input"; import { api } from "~/shared/lib/api"; interface MqttStatus { status: "connected" | "disconnected" | "error" | "not_configured"; error: string | null; } interface TestResult { connected: boolean; receivedMessage: boolean; error?: string; samplePayload?: string; } interface WebhookPluginInfo { ok: boolean; installed?: boolean; plugin?: { Name?: string; Version?: string } | null; error?: string; } const HANDLEBARS_TEMPLATE = `{ "event": "{{NotificationType}}", "itemId": "{{ItemId}}", "itemType": "{{ItemType}}" }`; function CopyableValue({ label, value, mono = true }: { label: string; value: string; mono?: boolean }) { const [copied, setCopied] = useState(false); const copy = async () => { await navigator.clipboard.writeText(value); setCopied(true); setTimeout(() => setCopied(false), 1500); }; return (
{label}
				{value}
			
); } export function MqttSection({ cfg, locked }: { cfg: Record; locked: Set }) { const [url, setUrl] = useState(cfg.mqtt_url ?? ""); const [topic, setTopic] = useState(cfg.mqtt_topic || "jellyfin/events"); const [username, setUsername] = useState(cfg.mqtt_username ?? ""); const [password, setPassword] = useState(""); const [saving, setSaving] = useState(false); const [savedMsg, setSavedMsg] = useState(""); const [testing, setTesting] = useState(false); const [testResult, setTestResult] = useState(null); const [status, setStatus] = useState({ status: "not_configured", error: null }); const [plugin, setPlugin] = useState(null); const allLocked = locked.has("mqtt_url") && locked.has("mqtt_topic") && locked.has("mqtt_username") && locked.has("mqtt_password"); useEffect(() => { let cancelled = false; const fetchStatus = async () => { try { const s = await api.get("/api/settings/mqtt/status"); if (!cancelled) setStatus(s); } catch { /* ignore */ } }; fetchStatus(); const interval = setInterval(fetchStatus, 5000); return () => { cancelled = true; clearInterval(interval); }; }, []); useEffect(() => { api .get("/api/settings/jellyfin/webhook-plugin") .then(setPlugin) .catch((err) => setPlugin({ ok: false, error: String(err) })); }, []); const save = async () => { setSaving(true); setSavedMsg(""); try { await api.post("/api/settings/mqtt", { url, topic, username, password }); setSavedMsg(password ? "Saved." : "Saved (password unchanged)."); setPassword(""); setTimeout(() => setSavedMsg(""), 2500); } catch (e) { setSavedMsg(`Failed: ${String(e)}`); } setSaving(false); }; const runTest = async () => { setTesting(true); setTestResult(null); try { const r = await api.post("/api/settings/mqtt/test", { url, topic, username, password }); setTestResult(r); } catch (e) { setTestResult({ connected: false, receivedMessage: false, error: String(e) }); } setTesting(false); }; const statusColor = status.status === "connected" ? "text-green-700 bg-green-50 border-green-300" : status.status === "error" ? "text-red-700 bg-red-50 border-red-300" : status.status === "disconnected" ? "text-amber-700 bg-amber-50 border-amber-300" : "text-gray-600 bg-gray-50 border-gray-300"; return (
Jellyfin webhook (MQTT)
MQTT: {status.status}

Close the loop: once Jellyfin finishes its post-ffmpeg rescan, it publishes an event to your MQTT broker. We re-analyze the item, confirming it as done or flipping it back to pending if something didn't stick.

{savedMsg && ✓ {savedMsg}}
{testResult && (
{testResult.connected && testResult.receivedMessage && (
✓ Connected and received a message. {testResult.samplePayload && (
{testResult.samplePayload}
)}
)} {testResult.connected && !testResult.receivedMessage && !testResult.error && (
⚠ Connected, but no traffic in the timeout window. Trigger a library scan in Jellyfin, then retry.
)} {testResult.error &&
✗ {testResult.error}
}
)} {/* Plugin + setup instructions */}
Jellyfin Webhook plugin
{plugin?.ok === false &&

Couldn't reach Jellyfin: {plugin.error}

} {plugin?.ok && !plugin.installed && (

⚠ The Webhook plugin is not installed on Jellyfin. Install it from{" "} Jellyfin → Dashboard → Plugins → Catalog → Webhook, restart Jellyfin, then configure an MQTT destination with the values below.

)} {plugin?.ok && plugin.installed && (

✓ Plugin detected{plugin.plugin?.Version ? ` (v${plugin.plugin.Version})` : ""}. Add an MQTT destination with:

)}
); }