feat: unified group modes (open/listen/mention-only) (#267)
Consolidates listeningGroups and groups.requireMention into a single groups config with explicit mode per group. Backward compatible -- legacy formats auto-normalize with deprecation warnings. - Add shared group-mode.ts with isGroupAllowed/resolveGroupMode helpers - Update all 5 channel adapters to use mode-based gating - Default to mention-only for configured entries (safe), open when no config - Listening mode now set at adapter level, bot.ts has legacy fallback - Fix YAML large-ID parsing for groups map keys (Discord snowflakes) - Add migration in normalizeAgents for listeningGroups + requireMention - Add unit tests for group-mode helpers + update all gating tests - Update docs, README, and example config Closes #266 Written by Cameron and Letta Code "Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away." -- Antoine de Saint-Exupery
This commit is contained in:
@@ -11,6 +11,7 @@ import type { DmPolicy } from '../pairing/types.js';
|
||||
import { isUserAllowed, upsertPairingRequest } from '../pairing/store.js';
|
||||
import { buildAttachmentPath, downloadToFile } from './attachments.js';
|
||||
import { HELP_TEXT } from '../core/commands.js';
|
||||
import { isGroupAllowed, resolveGroupMode, type GroupModeConfig } from './group-mode.js';
|
||||
|
||||
// Dynamic import to avoid requiring Discord deps if not used
|
||||
let Client: typeof import('discord.js').Client;
|
||||
@@ -23,7 +24,7 @@ export interface DiscordConfig {
|
||||
allowedUsers?: string[]; // Discord user IDs
|
||||
attachmentsDir?: string;
|
||||
attachmentsMaxBytes?: number;
|
||||
groups?: Record<string, { requireMention?: boolean }>; // Per-guild/channel settings
|
||||
groups?: Record<string, GroupModeConfig>; // Per-guild/channel settings
|
||||
}
|
||||
|
||||
export class DiscordAdapter implements ChannelAdapter {
|
||||
@@ -242,32 +243,24 @@ Ask the bot owner to approve with:
|
||||
const groupName = isGroup && 'name' in message.channel ? message.channel.name : undefined;
|
||||
const displayName = message.member?.displayName || message.author.globalName || message.author.username;
|
||||
const wasMentioned = isGroup && !!this.client?.user && message.mentions.has(this.client.user);
|
||||
let isListeningMode = false;
|
||||
|
||||
// Group gating: config-based allowlist + mention requirement
|
||||
// Group gating: config-based allowlist + mode
|
||||
if (isGroup && this.config.groups) {
|
||||
const groups = this.config.groups;
|
||||
const chatId = message.channel.id;
|
||||
const serverId = message.guildId;
|
||||
const allowlistEnabled = Object.keys(groups).length > 0;
|
||||
|
||||
if (allowlistEnabled) {
|
||||
const hasWildcard = Object.hasOwn(groups, '*');
|
||||
const hasSpecific = Object.hasOwn(groups, chatId)
|
||||
|| (serverId && Object.hasOwn(groups, serverId));
|
||||
if (!hasWildcard && !hasSpecific) {
|
||||
console.log(`[Discord] Group ${chatId} not in allowlist, ignoring`);
|
||||
return;
|
||||
}
|
||||
const keys = [chatId];
|
||||
if (serverId) keys.push(serverId);
|
||||
if (!isGroupAllowed(this.config.groups, keys)) {
|
||||
console.log(`[Discord] Group ${chatId} not in allowlist, ignoring`);
|
||||
return;
|
||||
}
|
||||
|
||||
const groupConfig = groups[chatId]
|
||||
?? (serverId ? groups[serverId] : undefined)
|
||||
?? groups['*'];
|
||||
const requireMention = groupConfig?.requireMention ?? true;
|
||||
|
||||
if (requireMention && !wasMentioned) {
|
||||
const mode = resolveGroupMode(this.config.groups, keys, 'open');
|
||||
if (mode === 'mention-only' && !wasMentioned) {
|
||||
return; // Mention required but not mentioned -- silent drop
|
||||
}
|
||||
isListeningMode = mode === 'listen' && !wasMentioned;
|
||||
}
|
||||
|
||||
await this.onMessage({
|
||||
@@ -283,6 +276,7 @@ Ask the bot owner to approve with:
|
||||
groupName,
|
||||
serverId: message.guildId || undefined,
|
||||
wasMentioned,
|
||||
isListeningMode,
|
||||
attachments,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user