Discord: add reaction notification allowlist
This commit is contained in:
committed by
Peter Steinberger
parent
cdfbd6e7eb
commit
451174ca10
@@ -13,6 +13,7 @@
|
|||||||
- Onboarding: shared wizard engine powering CLI + macOS via gateway wizard RPC.
|
- Onboarding: shared wizard engine powering CLI + macOS via gateway wizard RPC.
|
||||||
- Config: expose schema + UI hints for generic config forms (Web UI + future clients).
|
- Config: expose schema + UI hints for generic config forms (Web UI + future clients).
|
||||||
- Skills: add blogwatcher skill for RSS/Atom monitoring — thanks @Hyaxia.
|
- Skills: add blogwatcher skill for RSS/Atom monitoring — thanks @Hyaxia.
|
||||||
|
- Discord: emit system events for reaction add/remove with per-guild reaction notifications (off|own|all|allowlist) — thanks @thewilloftheshadow.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- Auto-reply: drop final payloads when block streaming to avoid duplicate Discord sends.
|
- Auto-reply: drop final payloads when block streaming to avoid duplicate Discord sends.
|
||||||
|
|||||||
@@ -515,6 +515,17 @@ struct ConnectionsSettings: View {
|
|||||||
.labelsHidden()
|
.labelsHidden()
|
||||||
.toggleStyle(.checkbox)
|
.toggleStyle(.checkbox)
|
||||||
}
|
}
|
||||||
|
GridRow {
|
||||||
|
self.gridLabel("Reaction notifications")
|
||||||
|
Picker("", selection: $guild.reactionNotifications) {
|
||||||
|
Text("Off").tag("off")
|
||||||
|
Text("Own").tag("own")
|
||||||
|
Text("All").tag("all")
|
||||||
|
Text("Allowlist").tag("allowlist")
|
||||||
|
}
|
||||||
|
.labelsHidden()
|
||||||
|
.pickerStyle(.segmented)
|
||||||
|
}
|
||||||
GridRow {
|
GridRow {
|
||||||
self.gridLabel("Users allowlist")
|
self.gridLabel("Users allowlist")
|
||||||
TextField("123456789, username#1234", text: $guild.users)
|
TextField("123456789, username#1234", text: $guild.users)
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ struct DiscordGuildForm: Identifiable {
|
|||||||
var key: String
|
var key: String
|
||||||
var slug: String
|
var slug: String
|
||||||
var requireMention: Bool
|
var requireMention: Bool
|
||||||
|
var reactionNotifications: String
|
||||||
var users: String
|
var users: String
|
||||||
var channels: [DiscordGuildChannelForm]
|
var channels: [DiscordGuildChannelForm]
|
||||||
|
|
||||||
@@ -169,12 +170,14 @@ struct DiscordGuildForm: Identifiable {
|
|||||||
key: String = "",
|
key: String = "",
|
||||||
slug: String = "",
|
slug: String = "",
|
||||||
requireMention: Bool = false,
|
requireMention: Bool = false,
|
||||||
|
reactionNotifications: String = "allowlist",
|
||||||
users: String = "",
|
users: String = "",
|
||||||
channels: [DiscordGuildChannelForm] = []
|
channels: [DiscordGuildChannelForm] = []
|
||||||
) {
|
) {
|
||||||
self.key = key
|
self.key = key
|
||||||
self.slug = slug
|
self.slug = slug
|
||||||
self.requireMention = requireMention
|
self.requireMention = requireMention
|
||||||
|
self.reactionNotifications = reactionNotifications
|
||||||
self.users = users
|
self.users = users
|
||||||
self.channels = channels
|
self.channels = channels
|
||||||
}
|
}
|
||||||
@@ -491,6 +494,10 @@ final class ConnectionsStore {
|
|||||||
let entry = value.dictionaryValue ?? [:]
|
let entry = value.dictionaryValue ?? [:]
|
||||||
let slug = entry["slug"]?.stringValue ?? ""
|
let slug = entry["slug"]?.stringValue ?? ""
|
||||||
let requireMention = entry["requireMention"]?.boolValue ?? false
|
let requireMention = entry["requireMention"]?.boolValue ?? false
|
||||||
|
let reactionModeRaw = entry["reactionNotifications"]?.stringValue ?? ""
|
||||||
|
let reactionNotifications = ["off", "own", "all", "allowlist"].contains(reactionModeRaw)
|
||||||
|
? reactionModeRaw
|
||||||
|
: "allowlist"
|
||||||
let users = entry["users"]?.arrayValue?
|
let users = entry["users"]?.arrayValue?
|
||||||
.compactMap { item -> String? in
|
.compactMap { item -> String? in
|
||||||
if let str = item.stringValue { return str }
|
if let str = item.stringValue { return str }
|
||||||
@@ -518,6 +525,7 @@ final class ConnectionsStore {
|
|||||||
key: key,
|
key: key,
|
||||||
slug: slug,
|
slug: slug,
|
||||||
requireMention: requireMention,
|
requireMention: requireMention,
|
||||||
|
reactionNotifications: reactionNotifications,
|
||||||
users: users,
|
users: users,
|
||||||
channels: channels)
|
channels: channels)
|
||||||
}
|
}
|
||||||
@@ -794,6 +802,9 @@ final class ConnectionsStore {
|
|||||||
let slug = entry.slug.trimmingCharacters(in: .whitespacesAndNewlines)
|
let slug = entry.slug.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
if !slug.isEmpty { payload["slug"] = slug }
|
if !slug.isEmpty { payload["slug"] = slug }
|
||||||
if entry.requireMention { payload["requireMention"] = true }
|
if entry.requireMention { payload["requireMention"] = true }
|
||||||
|
if ["off", "own", "all", "allowlist"].contains(entry.reactionNotifications) {
|
||||||
|
payload["reactionNotifications"] = entry.reactionNotifications
|
||||||
|
}
|
||||||
let users = entry.users
|
let users = entry.users
|
||||||
.split(separator: ",")
|
.split(separator: ",")
|
||||||
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||||||
|
|||||||
@@ -266,6 +266,7 @@ Configure the Discord bot by setting the bot token and optional gating:
|
|||||||
"123456789012345678": { // guild id (preferred) or slug
|
"123456789012345678": { // guild id (preferred) or slug
|
||||||
slug: "friends-of-clawd",
|
slug: "friends-of-clawd",
|
||||||
requireMention: false, // per-guild default
|
requireMention: false, // per-guild default
|
||||||
|
reactionNotifications: "allowlist", // off | own | all | allowlist
|
||||||
users: ["987654321098765432"], // optional per-guild user allowlist
|
users: ["987654321098765432"], // optional per-guild user allowlist
|
||||||
channels: {
|
channels: {
|
||||||
general: { allow: true },
|
general: { allow: true },
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ Note: Guild context `[from:]` lines include `author.tag` + `id` to make ping-rea
|
|||||||
"123456789012345678": {
|
"123456789012345678": {
|
||||||
slug: "friends-of-clawd",
|
slug: "friends-of-clawd",
|
||||||
requireMention: false,
|
requireMention: false,
|
||||||
|
reactionNotifications: "allowlist",
|
||||||
users: ["987654321098765432", "steipete"],
|
users: ["987654321098765432", "steipete"],
|
||||||
channels: {
|
channels: {
|
||||||
general: { allow: true },
|
general: { allow: true },
|
||||||
@@ -107,6 +108,7 @@ Note: Guild context `[from:]` lines include `author.tag` + `id` to make ping-rea
|
|||||||
- `guilds.<id>.users`: optional per-guild user allowlist (ids or names).
|
- `guilds.<id>.users`: optional per-guild user allowlist (ids or names).
|
||||||
- `guilds.<id>.channels`: channel rules (keys are channel slugs or ids).
|
- `guilds.<id>.channels`: channel rules (keys are channel slugs or ids).
|
||||||
- `guilds.<id>.requireMention`: per-guild mention requirement (overridable per channel).
|
- `guilds.<id>.requireMention`: per-guild mention requirement (overridable per channel).
|
||||||
|
- `guilds.<id>.reactionNotifications`: reaction system event mode (`off`, `own`, `all`, `allowlist`).
|
||||||
- `slashCommand`: optional config for user-installed slash commands (ephemeral responses).
|
- `slashCommand`: optional config for user-installed slash commands (ephemeral responses).
|
||||||
- `mediaMaxMb`: clamp inbound media saved to disk.
|
- `mediaMaxMb`: clamp inbound media saved to disk.
|
||||||
- `historyLimit`: number of recent guild messages to include as context when replying to a mention (default 20, `0` disables).
|
- `historyLimit`: number of recent guild messages to include as context when replying to a mention (default 20, `0` disables).
|
||||||
@@ -117,6 +119,8 @@ Note: Guild context `[from:]` lines include `author.tag` + `id` to make ping-rea
|
|||||||
- `roles` (role add/remove, default `false`)
|
- `roles` (role add/remove, default `false`)
|
||||||
- `moderation` (timeout/kick/ban, default `false`)
|
- `moderation` (timeout/kick/ban, default `false`)
|
||||||
|
|
||||||
|
Reaction notifications use `guilds.<id>.reactionNotifications`; `allowlist` checks `guilds.<id>.users`, while `all` ignores that allowlist.
|
||||||
|
|
||||||
### Tool action defaults
|
### Tool action defaults
|
||||||
|
|
||||||
| Action group | Default | Notes |
|
| Action group | Default | Notes |
|
||||||
|
|||||||
@@ -203,9 +203,17 @@ export type DiscordGuildChannelConfig = {
|
|||||||
requireMention?: boolean;
|
requireMention?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DiscordReactionNotificationMode =
|
||||||
|
| "off"
|
||||||
|
| "own"
|
||||||
|
| "all"
|
||||||
|
| "allowlist";
|
||||||
|
|
||||||
export type DiscordGuildEntry = {
|
export type DiscordGuildEntry = {
|
||||||
slug?: string;
|
slug?: string;
|
||||||
requireMention?: boolean;
|
requireMention?: boolean;
|
||||||
|
/** Reaction notification mode (off|own|all|allowlist). Default: own. */
|
||||||
|
reactionNotifications?: DiscordReactionNotificationMode;
|
||||||
users?: Array<string | number>;
|
users?: Array<string | number>;
|
||||||
channels?: Record<string, DiscordGuildChannelConfig>;
|
channels?: Record<string, DiscordGuildChannelConfig>;
|
||||||
};
|
};
|
||||||
@@ -1153,6 +1161,9 @@ export const ClawdisSchema = z.object({
|
|||||||
.object({
|
.object({
|
||||||
slug: z.string().optional(),
|
slug: z.string().optional(),
|
||||||
requireMention: z.boolean().optional(),
|
requireMention: z.boolean().optional(),
|
||||||
|
reactionNotifications: z
|
||||||
|
.enum(["off", "own", "all", "allowlist"])
|
||||||
|
.optional(),
|
||||||
users: z.array(z.union([z.string(), z.number()])).optional(),
|
users: z.array(z.union([z.string(), z.number()])).optional(),
|
||||||
channels: z
|
channels: z
|
||||||
.record(
|
.record(
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
import {
|
import {
|
||||||
ApplicationCommandOptionType,
|
ApplicationCommandOptionType,
|
||||||
|
type Attachment,
|
||||||
ChannelType,
|
ChannelType,
|
||||||
|
type ChatInputCommandInteraction,
|
||||||
Client,
|
Client,
|
||||||
type CommandInteractionOption,
|
type CommandInteractionOption,
|
||||||
Events,
|
Events,
|
||||||
GatewayIntentBits,
|
GatewayIntentBits,
|
||||||
|
type Guild,
|
||||||
type Message,
|
type Message,
|
||||||
|
type MessageReaction,
|
||||||
|
type PartialMessage,
|
||||||
|
type PartialMessageReaction,
|
||||||
Partials,
|
Partials,
|
||||||
|
type PartialUser,
|
||||||
|
type User,
|
||||||
} from "discord.js";
|
} from "discord.js";
|
||||||
|
|
||||||
import { chunkText, resolveTextChunkLimit } from "../auto-reply/chunk.js";
|
import { chunkText, resolveTextChunkLimit } from "../auto-reply/chunk.js";
|
||||||
@@ -21,6 +29,7 @@ import type {
|
|||||||
import { loadConfig } from "../config/config.js";
|
import { loadConfig } from "../config/config.js";
|
||||||
import { resolveStorePath, updateLastRoute } from "../config/sessions.js";
|
import { resolveStorePath, updateLastRoute } from "../config/sessions.js";
|
||||||
import { danger, logVerbose, shouldLogVerbose, warn } from "../globals.js";
|
import { danger, logVerbose, shouldLogVerbose, warn } from "../globals.js";
|
||||||
|
import { enqueueSystemEvent } from "../infra/system-events.js";
|
||||||
import { getChildLogger } from "../logging.js";
|
import { getChildLogger } from "../logging.js";
|
||||||
import { detectMime } from "../media/mime.js";
|
import { detectMime } from "../media/mime.js";
|
||||||
import { saveMediaBuffer } from "../media/store.js";
|
import { saveMediaBuffer } from "../media/store.js";
|
||||||
@@ -61,6 +70,7 @@ export type DiscordGuildEntryResolved = {
|
|||||||
id?: string;
|
id?: string;
|
||||||
slug?: string;
|
slug?: string;
|
||||||
requireMention?: boolean;
|
requireMention?: boolean;
|
||||||
|
reactionNotifications?: "off" | "own" | "all" | "allowlist";
|
||||||
users?: Array<string | number>;
|
users?: Array<string | number>;
|
||||||
channels?: Record<string, { allow?: boolean; requireMention?: boolean }>;
|
channels?: Record<string, { allow?: boolean; requireMention?: boolean }>;
|
||||||
};
|
};
|
||||||
@@ -149,10 +159,17 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
|||||||
intents: [
|
intents: [
|
||||||
GatewayIntentBits.Guilds,
|
GatewayIntentBits.Guilds,
|
||||||
GatewayIntentBits.GuildMessages,
|
GatewayIntentBits.GuildMessages,
|
||||||
|
GatewayIntentBits.GuildMessageReactions,
|
||||||
GatewayIntentBits.MessageContent,
|
GatewayIntentBits.MessageContent,
|
||||||
GatewayIntentBits.DirectMessages,
|
GatewayIntentBits.DirectMessages,
|
||||||
|
GatewayIntentBits.DirectMessageReactions,
|
||||||
|
],
|
||||||
|
partials: [
|
||||||
|
Partials.Channel,
|
||||||
|
Partials.Message,
|
||||||
|
Partials.Reaction,
|
||||||
|
Partials.User,
|
||||||
],
|
],
|
||||||
partials: [Partials.Channel],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const logger = getChildLogger({ module: "discord-auto-reply" });
|
const logger = getChildLogger({ module: "discord-auto-reply" });
|
||||||
@@ -503,6 +520,99 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleReactionEvent = async (
|
||||||
|
reaction: MessageReaction | PartialMessageReaction,
|
||||||
|
user: User | PartialUser,
|
||||||
|
action: "added" | "removed",
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
if (!user || user.bot) return;
|
||||||
|
const resolvedReaction = reaction.partial
|
||||||
|
? await reaction.fetch()
|
||||||
|
: reaction;
|
||||||
|
const message = (resolvedReaction.message as Message | PartialMessage)
|
||||||
|
.partial
|
||||||
|
? await resolvedReaction.message.fetch()
|
||||||
|
: resolvedReaction.message;
|
||||||
|
const guild = message.guild;
|
||||||
|
if (!guild) return;
|
||||||
|
const guildInfo = resolveDiscordGuildEntry({
|
||||||
|
guild,
|
||||||
|
guildEntries,
|
||||||
|
});
|
||||||
|
if (guildEntries && Object.keys(guildEntries).length > 0 && !guildInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const channelName =
|
||||||
|
"name" in message.channel
|
||||||
|
? (message.channel.name ?? undefined)
|
||||||
|
: undefined;
|
||||||
|
const channelSlug = channelName ? normalizeDiscordSlug(channelName) : "";
|
||||||
|
const channelConfig = resolveDiscordChannelConfig({
|
||||||
|
guildInfo,
|
||||||
|
channelId: message.channelId,
|
||||||
|
channelName,
|
||||||
|
channelSlug,
|
||||||
|
});
|
||||||
|
if (channelConfig?.allowed === false) return;
|
||||||
|
|
||||||
|
const botId = client.user?.id;
|
||||||
|
if (botId && user.id === botId) return;
|
||||||
|
|
||||||
|
const reactionMode = guildInfo?.reactionNotifications ?? "allowlist";
|
||||||
|
if (reactionMode === "off") return;
|
||||||
|
if (reactionMode === "own") {
|
||||||
|
const authorId = message.author?.id;
|
||||||
|
if (!botId || authorId !== botId) return;
|
||||||
|
}
|
||||||
|
if (reactionMode === "allowlist") {
|
||||||
|
const userAllow = guildInfo?.users;
|
||||||
|
if (!Array.isArray(userAllow) || userAllow.length === 0) return;
|
||||||
|
const users = normalizeDiscordAllowList(userAllow, [
|
||||||
|
"discord:",
|
||||||
|
"user:",
|
||||||
|
]);
|
||||||
|
const userOk =
|
||||||
|
!!users &&
|
||||||
|
allowListMatches(users, {
|
||||||
|
id: user.id,
|
||||||
|
name: user.username,
|
||||||
|
tag: user.tag,
|
||||||
|
});
|
||||||
|
if (!userOk) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emojiLabel = formatDiscordReactionEmoji(resolvedReaction);
|
||||||
|
const actorLabel = user.tag ?? user.username ?? user.id;
|
||||||
|
const guildSlug =
|
||||||
|
guildInfo?.slug ||
|
||||||
|
(guild.name ? normalizeDiscordSlug(guild.name) : guild.id);
|
||||||
|
const channelLabel = channelSlug
|
||||||
|
? `#${channelSlug}`
|
||||||
|
: channelName
|
||||||
|
? `#${normalizeDiscordSlug(channelName)}`
|
||||||
|
: `#${message.channelId}`;
|
||||||
|
const authorLabel = message.author?.tag ?? message.author?.username;
|
||||||
|
const baseText = `Discord reaction ${action}: ${emojiLabel} by ${actorLabel} on ${guildSlug} ${channelLabel} msg ${message.id}`;
|
||||||
|
const text = authorLabel ? `${baseText} from ${authorLabel}` : baseText;
|
||||||
|
enqueueSystemEvent(text, {
|
||||||
|
contextKey: `discord:reaction:${action}:${message.id}:${user.id}:${emojiLabel}`,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
runtime.error?.(
|
||||||
|
danger(`discord reaction handler failed: ${String(err)}`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
client.on(Events.MessageReactionAdd, async (reaction, user) => {
|
||||||
|
await handleReactionEvent(reaction, user, "added");
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on(Events.MessageReactionRemove, async (reaction, user) => {
|
||||||
|
await handleReactionEvent(reaction, user, "removed");
|
||||||
|
});
|
||||||
|
|
||||||
client.on(Events.InteractionCreate, async (interaction) => {
|
client.on(Events.InteractionCreate, async (interaction) => {
|
||||||
try {
|
try {
|
||||||
if (!slashCommand.enabled) return;
|
if (!slashCommand.enabled) return;
|
||||||
@@ -698,7 +808,7 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function resolveMedia(
|
async function resolveMedia(
|
||||||
message: import("discord.js").Message,
|
message: Message,
|
||||||
maxBytes: number,
|
maxBytes: number,
|
||||||
): Promise<DiscordMediaInfo | null> {
|
): Promise<DiscordMediaInfo | null> {
|
||||||
const attachment = message.attachments.first();
|
const attachment = message.attachments.first();
|
||||||
@@ -723,7 +833,7 @@ async function resolveMedia(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function inferPlaceholder(attachment: import("discord.js").Attachment): string {
|
function inferPlaceholder(attachment: Attachment): string {
|
||||||
const mime = attachment.contentType ?? "";
|
const mime = attachment.contentType ?? "";
|
||||||
if (mime.startsWith("image/")) return "<media:image>";
|
if (mime.startsWith("image/")) return "<media:image>";
|
||||||
if (mime.startsWith("video/")) return "<media:video>";
|
if (mime.startsWith("video/")) return "<media:video>";
|
||||||
@@ -768,17 +878,30 @@ async function resolveReplyContext(message: Message): Promise<string | null> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildDirectLabel(message: import("discord.js").Message) {
|
function buildDirectLabel(message: Message) {
|
||||||
const username = message.author.tag;
|
const username = message.author.tag;
|
||||||
return `${username} id:${message.author.id}`;
|
return `${username} id:${message.author.id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildGuildLabel(message: import("discord.js").Message) {
|
function buildGuildLabel(message: Message) {
|
||||||
const channelName =
|
const channelName =
|
||||||
"name" in message.channel ? message.channel.name : message.channelId;
|
"name" in message.channel ? message.channel.name : message.channelId;
|
||||||
return `${message.guild?.name ?? "Guild"} #${channelName} id:${message.channelId}`;
|
return `${message.guild?.name ?? "Guild"} #${channelName} id:${message.channelId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatDiscordReactionEmoji(
|
||||||
|
reaction: MessageReaction | PartialMessageReaction,
|
||||||
|
) {
|
||||||
|
if (typeof reaction.emoji.toString === "function") {
|
||||||
|
const rendered = reaction.emoji.toString();
|
||||||
|
if (rendered && rendered !== "[object Object]") return rendered;
|
||||||
|
}
|
||||||
|
if (reaction.emoji.id && reaction.emoji.name) {
|
||||||
|
return `${reaction.emoji.name}:${reaction.emoji.id}`;
|
||||||
|
}
|
||||||
|
return reaction.emoji.name ?? "emoji";
|
||||||
|
}
|
||||||
|
|
||||||
export function normalizeDiscordAllowList(
|
export function normalizeDiscordAllowList(
|
||||||
raw: Array<string | number> | undefined,
|
raw: Array<string | number> | undefined,
|
||||||
prefixes: string[],
|
prefixes: string[],
|
||||||
@@ -863,7 +986,7 @@ export function allowListMatches(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function resolveDiscordGuildEntry(params: {
|
export function resolveDiscordGuildEntry(params: {
|
||||||
guild: import("discord.js").Guild | null;
|
guild: Guild | null;
|
||||||
guildEntries: Record<string, DiscordGuildEntryResolved> | undefined;
|
guildEntries: Record<string, DiscordGuildEntryResolved> | undefined;
|
||||||
}): DiscordGuildEntryResolved | null {
|
}): DiscordGuildEntryResolved | null {
|
||||||
const { guild, guildEntries } = params;
|
const { guild, guildEntries } = params;
|
||||||
@@ -878,6 +1001,7 @@ export function resolveDiscordGuildEntry(params: {
|
|||||||
id: guildId,
|
id: guildId,
|
||||||
slug: direct.slug ?? guildSlug,
|
slug: direct.slug ?? guildSlug,
|
||||||
requireMention: direct.requireMention,
|
requireMention: direct.requireMention,
|
||||||
|
reactionNotifications: direct.reactionNotifications,
|
||||||
users: direct.users,
|
users: direct.users,
|
||||||
channels: direct.channels,
|
channels: direct.channels,
|
||||||
};
|
};
|
||||||
@@ -888,6 +1012,7 @@ export function resolveDiscordGuildEntry(params: {
|
|||||||
id: guildId,
|
id: guildId,
|
||||||
slug: entry.slug ?? guildSlug,
|
slug: entry.slug ?? guildSlug,
|
||||||
requireMention: entry.requireMention,
|
requireMention: entry.requireMention,
|
||||||
|
reactionNotifications: entry.reactionNotifications,
|
||||||
users: entry.users,
|
users: entry.users,
|
||||||
channels: entry.channels,
|
channels: entry.channels,
|
||||||
};
|
};
|
||||||
@@ -902,6 +1027,7 @@ export function resolveDiscordGuildEntry(params: {
|
|||||||
id: guildId,
|
id: guildId,
|
||||||
slug: entry.slug ?? guildSlug,
|
slug: entry.slug ?? guildSlug,
|
||||||
requireMention: entry.requireMention,
|
requireMention: entry.requireMention,
|
||||||
|
reactionNotifications: entry.reactionNotifications,
|
||||||
users: entry.users,
|
users: entry.users,
|
||||||
channels: entry.channels,
|
channels: entry.channels,
|
||||||
};
|
};
|
||||||
@@ -912,6 +1038,7 @@ export function resolveDiscordGuildEntry(params: {
|
|||||||
id: guildId,
|
id: guildId,
|
||||||
slug: wildcard.slug ?? guildSlug,
|
slug: wildcard.slug ?? guildSlug,
|
||||||
requireMention: wildcard.requireMention,
|
requireMention: wildcard.requireMention,
|
||||||
|
reactionNotifications: wildcard.reactionNotifications,
|
||||||
users: wildcard.users,
|
users: wildcard.users,
|
||||||
channels: wildcard.channels,
|
channels: wildcard.channels,
|
||||||
};
|
};
|
||||||
@@ -1121,7 +1248,7 @@ async function deliverSlashReplies({
|
|||||||
textLimit,
|
textLimit,
|
||||||
}: {
|
}: {
|
||||||
replies: ReplyPayload[];
|
replies: ReplyPayload[];
|
||||||
interaction: import("discord.js").ChatInputCommandInteraction;
|
interaction: ChatInputCommandInteraction;
|
||||||
ephemeral: boolean;
|
ephemeral: boolean;
|
||||||
textLimit: number;
|
textLimit: number;
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@@ -197,6 +197,13 @@ export function applyConfigSnapshot(state: ConfigState, snapshot: ConfigSnapshot
|
|||||||
typeof entry.requireMention === "boolean"
|
typeof entry.requireMention === "boolean"
|
||||||
? entry.requireMention
|
? entry.requireMention
|
||||||
: false,
|
: false,
|
||||||
|
reactionNotifications:
|
||||||
|
entry.reactionNotifications === "off" ||
|
||||||
|
entry.reactionNotifications === "all" ||
|
||||||
|
entry.reactionNotifications === "own" ||
|
||||||
|
entry.reactionNotifications === "allowlist"
|
||||||
|
? entry.reactionNotifications
|
||||||
|
: "allowlist",
|
||||||
users: toList(entry.users),
|
users: toList(entry.users),
|
||||||
channels,
|
channels,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -292,6 +292,14 @@ export async function saveDiscordConfig(state: ConnectionsState) {
|
|||||||
const slug = String(guild.slug ?? "").trim();
|
const slug = String(guild.slug ?? "").trim();
|
||||||
if (slug) entry.slug = slug;
|
if (slug) entry.slug = slug;
|
||||||
if (guild.requireMention) entry.requireMention = true;
|
if (guild.requireMention) entry.requireMention = true;
|
||||||
|
if (
|
||||||
|
guild.reactionNotifications === "off" ||
|
||||||
|
guild.reactionNotifications === "all" ||
|
||||||
|
guild.reactionNotifications === "own" ||
|
||||||
|
guild.reactionNotifications === "allowlist"
|
||||||
|
) {
|
||||||
|
entry.reactionNotifications = guild.reactionNotifications;
|
||||||
|
}
|
||||||
const users = parseList(guild.users);
|
const users = parseList(guild.users);
|
||||||
if (users.length > 0) entry.users = users;
|
if (users.length > 0) entry.users = users;
|
||||||
const channels: Record<string, unknown> = {};
|
const channels: Record<string, unknown> = {};
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export type DiscordGuildForm = {
|
|||||||
key: string;
|
key: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
requireMention: boolean;
|
requireMention: boolean;
|
||||||
|
reactionNotifications: "off" | "own" | "all" | "allowlist";
|
||||||
users: string;
|
users: string;
|
||||||
channels: DiscordGuildChannelForm[];
|
channels: DiscordGuildChannelForm[];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -645,6 +645,26 @@ function renderProvider(
|
|||||||
<option value="no">No</option>
|
<option value="no">No</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Reaction notifications</span>
|
||||||
|
<select
|
||||||
|
.value=${guild.reactionNotifications}
|
||||||
|
@change=${(e: Event) => {
|
||||||
|
const next = [...props.discordForm.guilds];
|
||||||
|
next[guildIndex] = {
|
||||||
|
...next[guildIndex],
|
||||||
|
reactionNotifications: (e.target as HTMLSelectElement)
|
||||||
|
.value as "off" | "own" | "all",
|
||||||
|
};
|
||||||
|
props.onDiscordChange({ guilds: next });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="off">Off</option>
|
||||||
|
<option value="own">Own</option>
|
||||||
|
<option value="all">All</option>
|
||||||
|
<option value="allowlist">Allowlist</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span>Users allowlist</span>
|
<span>Users allowlist</span>
|
||||||
<input
|
<input
|
||||||
@@ -812,6 +832,7 @@ function renderProvider(
|
|||||||
key: "",
|
key: "",
|
||||||
slug: "",
|
slug: "",
|
||||||
requireMention: false,
|
requireMention: false,
|
||||||
|
reactionNotifications: "allowlist",
|
||||||
users: "",
|
users: "",
|
||||||
channels: [],
|
channels: [],
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user