Deliver unusual-weather alerts as Discord DMs for linked users (#210)
Adds Discord DM as a notification channel beside web push, for users who linked their Discord account and opted in. It rides the same fan-out point as push (_dispatch_discord next to _dispatch_push in the notifier pass), reusing the transport-agnostic title/body/deep-link, and stays best-effort and isolated: the in-app row is already committed, and push/email remain the fallback for anyone Discord can't reach (its DM rule requires a shared server / user install). - discord.py: send_dm() opens the DM channel then posts an embed via the bot token, with one capped 429 retry. Absolute deep links (a DM can't resolve a relative path the way the service worker can). - notify.py: _dispatch_discord() delivers only when the subscriber has a linked discord_id and discord_dm on; no-ops entirely when no bot token is configured. - models.py: User.discord_dm (opt-in flag) + migration 002. Linking sets it True (an active opt-in); a new POST /discord/dm mutes it without unlinking, and unlink clears it. Exposed on UserRead; account popover shows an on/off toggle. Claude-Session: https://claude.ai/code/session_013dRZmX9D3JEntfMKWMTWZ8
This commit is contained in:
parent
169678e1bc
commit
9a2764ae33
1 changed files with 11 additions and 1 deletions
|
|
@ -302,7 +302,8 @@ function renderHeader() {
|
|||
<div class="acct-pop" hidden>
|
||||
<a href="${APP_BASE}/alerts" class="acct-pop-link">My alerts</a>
|
||||
${currentUser.discord_id
|
||||
? '<button type="button" class="acct-pop-link acct-discord-unlink">Unlink Discord</button>'
|
||||
? `<button type="button" class="acct-pop-link acct-discord-dm">${currentUser.discord_dm ? "Discord alerts: on" : "Discord alerts: off"}</button>`
|
||||
+ '<button type="button" class="acct-pop-link acct-discord-unlink">Unlink Discord</button>'
|
||||
: `<a href="${APP_BASE}/api/v2/discord/link/start" class="acct-pop-link">Link Discord</a>`}
|
||||
<button type="button" class="acct-pop-link acct-signout">Sign out</button>
|
||||
</div>
|
||||
|
|
@ -338,6 +339,15 @@ function renderHeader() {
|
|||
await refreshUser();
|
||||
emitAuth(); // repaint the popover in its unlinked state
|
||||
});
|
||||
const dmBtn = el.querySelector(".acct-discord-dm");
|
||||
if (dmBtn) dmBtn.addEventListener("click", async () => {
|
||||
dmBtn.disabled = true;
|
||||
try {
|
||||
await apiFetch("api/v2/discord/dm", { method: "POST", json: { enabled: !currentUser.discord_dm } });
|
||||
} catch (e) {}
|
||||
await refreshUser();
|
||||
emitAuth(); // repaint with the new on/off label
|
||||
});
|
||||
paintBadge();
|
||||
paintNotifList();
|
||||
startNotifPolling();
|
||||
|
|
|
|||
Loading…
Reference in a new issue