From b879e534301952f9e50bce7a5cf53abbfcbf3936 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 24 Jul 2026 15:10:32 +0000 Subject: [PATCH] Only deliver EQNotify alerts to active Raiders Gate batphone dispatch on the subscriber currently holding the Raider role, checked live against Discord at push time. A subscriber who loses the role (goes inactive, leaves the guild) stops receiving alerts without needing to unregister; unresolvable members are treated as non-Raiders. The manual /eqnotify test path is intentionally unaffected. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01BWXRmPbtEz35wquwFd8Cu7 --- README.md | 2 +- .../eqnotify/commands/register-subcommand.ts | 2 +- src/features/eqnotify/eqnotify.service.ts | 31 +++++++++++++++++-- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a21f426a..5110ac20 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ Some features required secrets, such as to connect to CastleDKP.com or the Castl #### 📣 EQNotify -`/eqnotify` lets raiders subscribe to phone notifications for the raid targets they care about. It watches the batphone channel and, for each subscriber whose keyword tags match the batphone (buff / last-hour RTE calls are filtered out), pushes an alert to their phone. +`/eqnotify` lets raiders subscribe to phone notifications for the raid targets they care about. It watches the batphone channel and, for each subscriber whose keyword tags match the batphone (buff / last-hour RTE calls are filtered out), pushes an alert to their phone. Alerts are only delivered to subscribers who **currently hold the Raider role**, so anyone who goes inactive or leaves stops receiving them automatically. - **Delivery channels**: **WirePusher** (free, Android-only) or **Telegram** (iOS/Android/desktop). Telegram delivery requires `TELEGRAM_BOT_TOKEN`; if unset, only WirePusher is offered. - **Getting your ID**: WirePusher users use their device ID from the app. Telegram users start a chat with the guild's EQNotify bot and get their numeric chat ID (e.g. from [@userinfobot](https://t.me/userinfobot)). diff --git a/src/features/eqnotify/commands/register-subcommand.ts b/src/features/eqnotify/commands/register-subcommand.ts index 0aabb05a..a4de6e55 100644 --- a/src/features/eqnotify/commands/register-subcommand.ts +++ b/src/features/eqnotify/commands/register-subcommand.ts @@ -46,7 +46,7 @@ class RegisterSubcommand extends Subcommand { ? `Updated your EQNotify delivery to **${channel}**. Your notification tags are unchanged.` : `You're enrolled in EQNotify via **${channel}**! You'll be notified for: ${DEFAULT_TAGS.join( ", " - )}.\nUse \`/eqnotify add-tag\` / \`/eqnotify remove-tag\` to customize, and \`/eqnotify test\` to verify delivery.` + )}.\nUse \`/eqnotify add-tag\` / \`/eqnotify remove-tag\` to customize, and \`/eqnotify test\` to verify delivery.\n_Note: alerts are only sent while you actively hold the Raider role._` ); } diff --git a/src/features/eqnotify/eqnotify.service.ts b/src/features/eqnotify/eqnotify.service.ts index 9f853f79..7b162c06 100644 --- a/src/features/eqnotify/eqnotify.service.ts +++ b/src/features/eqnotify/eqnotify.service.ts @@ -1,5 +1,6 @@ import { eqnotify_subscriber, eqnotify_type } from "@prisma/client"; -import { prismaClient } from "../.."; +import { getMember, prismaClient } from "../.."; +import { raiderRoleId } from "../../config"; import { log } from "../../shared/logger"; import { notify } from "./notifiers"; import { @@ -18,6 +19,24 @@ interface EnrollInput { type: eqnotify_type; } +/** + * Whether a member currently holds the Raider role. Batphone alerts are only + * delivered to active Raiders, so a subscriber who loses the role (goes + * inactive, leaves the guild, etc.) stops receiving alerts without having to + * unregister. Failures to resolve the member are treated as "not a Raider". + */ +const hasRaiderRole = async (discordId: string): Promise => { + try { + const member = await getMember(discordId); + return member.roles.cache.has(raiderRoleId); + } catch (error) { + log( + `EQNotify could not resolve member ${discordId} for role check: ${error}` + ); + return false; + } +}; + const requireSubscriber = async (discordId: string) => { const subscriber = await prismaClient.eqnotify_subscriber.findUnique({ where: { discordId }, @@ -95,7 +114,8 @@ export const eqnotifyService = { /** * Matches a batphone against every subscriber's tags and delivers alerts. - * Delivery failures are logged but never interrupt other subscribers. + * Only subscribers who currently hold the Raider role are notified. Delivery + * failures are logged but never interrupt other subscribers. */ dispatch: async (content: string) => { if (!content.trim() || isFiltered(content)) { @@ -105,7 +125,12 @@ export const eqnotifyService = { await Promise.all( subscribers .filter((sub) => matchesTags(content, sub.tags)) - .map((sub) => eqnotifyService.notifySubscriber(sub, content)) + .map(async (sub) => { + if (!(await hasRaiderRole(sub.discordId))) { + return; + } + await eqnotifyService.notifySubscriber(sub, content); + }) ); },