Add Discord webhook refresh notifications
This commit is contained in:
+125
-29
@@ -2,9 +2,19 @@
|
||||
|
||||
"""Config page template for ani-cli-web."""
|
||||
|
||||
from app_support import DISCORD_WEBHOOK_EVENT_CHOICES, DISCORD_WEBHOOK_EVENT_LABELS
|
||||
from template_helpers import BROWSER_API_HELPER, render_page_links, render_sidebar_brand
|
||||
|
||||
|
||||
WEBHOOK_EVENT_OPTIONS = "\n".join(
|
||||
(
|
||||
f' <label class="check-item"><input class="webhook-event" type="checkbox" value="{event_name}"> '
|
||||
f"<span>{DISCORD_WEBHOOK_EVENT_LABELS[event_name]}</span></label>"
|
||||
)
|
||||
for event_name in DISCORD_WEBHOOK_EVENT_CHOICES
|
||||
)
|
||||
|
||||
|
||||
CONFIG_HTML = r"""<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -49,7 +59,7 @@ CONFIG_HTML = r"""<!doctype html>
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.03), transparent 24%);
|
||||
opacity: 0.5;
|
||||
}
|
||||
button, input, select {
|
||||
button, input, select, textarea {
|
||||
font: inherit;
|
||||
}
|
||||
button {
|
||||
@@ -74,7 +84,7 @@ CONFIG_HTML = r"""<!doctype html>
|
||||
font-weight: 700;
|
||||
box-shadow: 0 16px 32px rgba(123, 92, 255, 0.26);
|
||||
}
|
||||
input, select {
|
||||
input, select, textarea {
|
||||
width: 100%;
|
||||
min-height: 42px;
|
||||
border-radius: 14px;
|
||||
@@ -85,7 +95,12 @@ CONFIG_HTML = r"""<!doctype html>
|
||||
outline: none;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
input:focus, select:focus {
|
||||
textarea {
|
||||
min-height: 108px;
|
||||
padding: 12px 13px;
|
||||
resize: vertical;
|
||||
}
|
||||
input:focus, select:focus, textarea:focus {
|
||||
border-color: var(--focus);
|
||||
box-shadow: 0 0 0 3px rgba(178, 150, 255, 0.12);
|
||||
}
|
||||
@@ -233,6 +248,38 @@ CONFIG_HTML = r"""<!doctype html>
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.check-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
.check-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-height: 48px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 16px;
|
||||
padding: 10px 12px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
color: var(--text);
|
||||
text-transform: none;
|
||||
letter-spacing: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.check-item input {
|
||||
width: 18px;
|
||||
min-height: 18px;
|
||||
margin: 0;
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
.inline-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
.settings-main, .deps {
|
||||
width: min(760px, 100%);
|
||||
}
|
||||
@@ -242,7 +289,7 @@ CONFIG_HTML = r"""<!doctype html>
|
||||
.toolbar, .row { align-items: stretch; flex-direction: column; }
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.form-grid {
|
||||
.form-grid, .check-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -330,6 +377,28 @@ CONFIG_HTML = r"""<!doctype html>
|
||||
<div class="field-hint">When enabled, the server periodically starts the existing background “Refresh All” job using this interval. Bulk refreshes now wait this many seconds between shows to avoid flooding upstream sources.</div>
|
||||
</section>
|
||||
|
||||
<section class="settings settings-main">
|
||||
<div class="toolbar">
|
||||
<div>
|
||||
<h2>Discord notifications</h2>
|
||||
<p class="muted">Send webhook notifications for refresh discoveries, refresh problems, and unexpected runtime errors.</p>
|
||||
</div>
|
||||
<button id="testWebhookBtn" type="button">Test webhook</button>
|
||||
</div>
|
||||
<div class="stack">
|
||||
<label>Discord webhook URL
|
||||
<textarea id="discordWebhookUrl" placeholder="https://discord.com/api/webhooks/..."></textarea>
|
||||
</label>
|
||||
<div class="field-hint">The new-episodes notification includes one card per anime, with local cached thumbnails attached to the webhook when available.</div>
|
||||
</div>
|
||||
<div class="stack">
|
||||
<label>Notify for</label>
|
||||
<div class="check-grid">
|
||||
""" + WEBHOOK_EVENT_OPTIONS + r"""
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="deps" id="deps"></section>
|
||||
</main>
|
||||
</div>
|
||||
@@ -342,7 +411,9 @@ CONFIG_HTML = r"""<!doctype html>
|
||||
download_dir: "",
|
||||
watchlist_auto_refresh_enabled: false,
|
||||
watchlist_auto_refresh_minutes: 60,
|
||||
watchlist_refresh_delay_seconds: 5
|
||||
watchlist_refresh_delay_seconds: 5,
|
||||
discord_webhook_url: "",
|
||||
discord_webhook_events: []
|
||||
}
|
||||
};
|
||||
|
||||
@@ -353,42 +424,66 @@ CONFIG_HTML = r"""<!doctype html>
|
||||
$("notice").textContent = text || "";
|
||||
}
|
||||
|
||||
function selectedWebhookEvents() {
|
||||
return [...document.querySelectorAll(".webhook-event:checked")].map((input) => input.value);
|
||||
}
|
||||
|
||||
function formConfigPayload() {
|
||||
return {
|
||||
download_dir: $("downloadDir").value,
|
||||
mode: $("configMode").value,
|
||||
quality: $("configQuality").value,
|
||||
watchlist_auto_refresh_enabled: $("watchlistAutoRefreshEnabled").value === "true",
|
||||
watchlist_auto_refresh_minutes: $("watchlistAutoRefreshMinutes").value,
|
||||
watchlist_refresh_delay_seconds: $("watchlistRefreshDelaySeconds").value,
|
||||
discord_webhook_url: $("discordWebhookUrl").value,
|
||||
discord_webhook_events: selectedWebhookEvents()
|
||||
};
|
||||
}
|
||||
|
||||
function applyConfig(data) {
|
||||
state.config = data;
|
||||
$("downloadDir").value = data.download_dir;
|
||||
$("configMode").value = data.mode;
|
||||
$("configQuality").value = data.quality;
|
||||
$("watchlistAutoRefreshEnabled").value = String(Boolean(data.watchlist_auto_refresh_enabled));
|
||||
$("watchlistAutoRefreshMinutes").value = data.watchlist_auto_refresh_minutes;
|
||||
$("watchlistRefreshDelaySeconds").value = data.watchlist_refresh_delay_seconds;
|
||||
$("discordWebhookUrl").value = data.discord_webhook_url || "";
|
||||
const selected = new Set(data.discord_webhook_events || []);
|
||||
for (const input of document.querySelectorAll(".webhook-event")) {
|
||||
input.checked = selected.has(input.value);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadConfig() {
|
||||
state.config = await api("/api/config");
|
||||
$("downloadDir").value = state.config.download_dir;
|
||||
$("configMode").value = state.config.mode;
|
||||
$("configQuality").value = state.config.quality;
|
||||
$("watchlistAutoRefreshEnabled").value = String(Boolean(state.config.watchlist_auto_refresh_enabled));
|
||||
$("watchlistAutoRefreshMinutes").value = state.config.watchlist_auto_refresh_minutes;
|
||||
$("watchlistRefreshDelaySeconds").value = state.config.watchlist_refresh_delay_seconds;
|
||||
applyConfig(await api("/api/config"));
|
||||
}
|
||||
|
||||
async function saveConfig() {
|
||||
try {
|
||||
const data = await api("/api/config", {
|
||||
applyConfig(await api("/api/config", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
download_dir: $("downloadDir").value,
|
||||
mode: $("configMode").value,
|
||||
quality: $("configQuality").value,
|
||||
watchlist_auto_refresh_enabled: $("watchlistAutoRefreshEnabled").value === "true",
|
||||
watchlist_auto_refresh_minutes: $("watchlistAutoRefreshMinutes").value,
|
||||
watchlist_refresh_delay_seconds: $("watchlistRefreshDelaySeconds").value
|
||||
})
|
||||
});
|
||||
state.config = data;
|
||||
$("downloadDir").value = data.download_dir;
|
||||
$("configMode").value = data.mode;
|
||||
$("configQuality").value = data.quality;
|
||||
$("watchlistAutoRefreshEnabled").value = String(Boolean(data.watchlist_auto_refresh_enabled));
|
||||
$("watchlistAutoRefreshMinutes").value = data.watchlist_auto_refresh_minutes;
|
||||
$("watchlistRefreshDelaySeconds").value = data.watchlist_refresh_delay_seconds;
|
||||
body: JSON.stringify(formConfigPayload())
|
||||
}));
|
||||
setNotice("Defaults saved.");
|
||||
} catch (error) {
|
||||
setNotice(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function testWebhook() {
|
||||
try {
|
||||
const data = await api("/api/config/webhook/test", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(formConfigPayload())
|
||||
});
|
||||
setNotice(data.message || "Discord webhook test notification sent.");
|
||||
} catch (error) {
|
||||
setNotice(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDeps() {
|
||||
const data = await api("/api/dependencies");
|
||||
const el = $("deps");
|
||||
@@ -410,6 +505,7 @@ CONFIG_HTML = r"""<!doctype html>
|
||||
}
|
||||
|
||||
$("saveConfigBtn").addEventListener("click", saveConfig);
|
||||
$("testWebhookBtn").addEventListener("click", testWebhook);
|
||||
|
||||
(async function init() {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user