chore: Enable "curly" rule to avoid single-statement if confusion/errors.

This commit is contained in:
cpojer
2026-01-31 16:19:20 +09:00
parent 009b16fab8
commit 5ceff756e1
1266 changed files with 27871 additions and 9393 deletions

View File

@@ -53,13 +53,17 @@ export function normalizeDiscordAllowList(
raw: Array<string | number> | undefined,
prefixes: string[],
) {
if (!raw || raw.length === 0) return null;
if (!raw || raw.length === 0) {
return null;
}
const ids = new Set<string>();
const names = new Set<string>();
const allowAll = raw.some((entry) => String(entry).trim() === "*");
for (const entry of raw) {
const text = String(entry).trim();
if (!text || text === "*") continue;
if (!text || text === "*") {
continue;
}
const normalized = normalizeDiscordSlug(text);
const maybeId = text.replace(/^<@!?/, "").replace(/>$/, "");
if (/^\d+$/.test(maybeId)) {
@@ -69,7 +73,9 @@ export function normalizeDiscordAllowList(
const prefix = prefixes.find((entry) => text.startsWith(entry));
if (prefix) {
const candidate = text.slice(prefix.length);
if (candidate) ids.add(candidate);
if (candidate) {
ids.add(candidate);
}
continue;
}
if (normalized) {
@@ -92,11 +98,19 @@ export function allowListMatches(
list: DiscordAllowList,
candidate: { id?: string; name?: string; tag?: string },
) {
if (list.allowAll) return true;
if (candidate.id && list.ids.has(candidate.id)) return true;
if (list.allowAll) {
return true;
}
if (candidate.id && list.ids.has(candidate.id)) {
return true;
}
const slug = candidate.name ? normalizeDiscordSlug(candidate.name) : "";
if (slug && list.names.has(slug)) return true;
if (candidate.tag && list.names.has(normalizeDiscordSlug(candidate.tag))) return true;
if (slug && list.names.has(slug)) {
return true;
}
if (candidate.tag && list.names.has(normalizeDiscordSlug(candidate.tag))) {
return true;
}
return false;
}
@@ -129,7 +143,9 @@ export function resolveDiscordUserAllowed(params: {
userTag?: string;
}) {
const allowList = normalizeDiscordAllowList(params.allowList, ["discord:", "user:"]);
if (!allowList) return true;
if (!allowList) {
return true;
}
return allowListMatches(allowList, {
id: params.userId,
name: params.userName,
@@ -143,9 +159,13 @@ export function resolveDiscordCommandAuthorized(params: {
guildInfo?: DiscordGuildEntryResolved | null;
author: User;
}) {
if (!params.isDirectMessage) return true;
if (!params.isDirectMessage) {
return true;
}
const allowList = normalizeDiscordAllowList(params.allowFrom, ["discord:", "user:"]);
if (!allowList) return true;
if (!allowList) {
return true;
}
return allowListMatches(allowList, {
id: params.author.id,
name: params.author.username,
@@ -159,14 +179,22 @@ export function resolveDiscordGuildEntry(params: {
}): DiscordGuildEntryResolved | null {
const guild = params.guild;
const entries = params.guildEntries;
if (!guild || !entries) return null;
if (!guild || !entries) {
return null;
}
const byId = entries[guild.id];
if (byId) return { ...byId, id: guild.id };
if (byId) {
return { ...byId, id: guild.id };
}
const slug = normalizeDiscordSlug(guild.name ?? "");
const bySlug = entries[slug];
if (bySlug) return { ...bySlug, id: guild.id, slug: slug || bySlug.slug };
if (bySlug) {
return { ...bySlug, id: guild.id, slug: slug || bySlug.slug };
}
const wildcard = entries["*"];
if (wildcard) return { ...wildcard, id: guild.id, slug: slug || wildcard.slug };
if (wildcard) {
return { ...wildcard, id: guild.id, slug: slug || wildcard.slug };
}
return null;
}
@@ -227,7 +255,9 @@ export function resolveDiscordChannelConfig(params: {
}): DiscordChannelConfigResolved | null {
const { guildInfo, channelId, channelName, channelSlug } = params;
const channels = guildInfo?.channels;
if (!channels) return null;
if (!channels) {
return null;
}
const match = resolveDiscordChannelEntryMatch(channels, {
id: channelId,
name: channelName,
@@ -258,7 +288,9 @@ export function resolveDiscordChannelConfigWithFallback(params: {
scope,
} = params;
const channels = guildInfo?.channels;
if (!channels) return null;
if (!channels) {
return null;
}
const resolvedParentSlug = parentSlug ?? (parentName ? normalizeDiscordSlug(parentName) : "");
const match = resolveDiscordChannelEntryMatch(
channels,
@@ -289,10 +321,14 @@ export function resolveDiscordShouldRequireMention(params: {
/** Pass pre-computed value to avoid redundant checks. */
isAutoThreadOwnedByBot?: boolean;
}): boolean {
if (!params.isGuildMessage) return false;
if (!params.isGuildMessage) {
return false;
}
// Only skip mention requirement in threads created by the bot (when autoThread is enabled).
const isBotThread = params.isAutoThreadOwnedByBot ?? isDiscordAutoThreadOwnedByBot(params);
if (isBotThread) return false;
if (isBotThread) {
return false;
}
return params.channelConfig?.requireMention ?? params.guildInfo?.requireMention ?? true;
}
@@ -302,8 +338,12 @@ export function isDiscordAutoThreadOwnedByBot(params: {
botId?: string | null;
threadOwnerId?: string | null;
}): boolean {
if (!params.isThread) return false;
if (!params.channelConfig?.autoThread) return false;
if (!params.isThread) {
return false;
}
if (!params.channelConfig?.autoThread) {
return false;
}
const botId = params.botId?.trim();
const threadOwnerId = params.threadOwnerId?.trim();
return Boolean(botId && threadOwnerId && botId === threadOwnerId);
@@ -316,10 +356,18 @@ export function isDiscordGroupAllowedByPolicy(params: {
channelAllowed: boolean;
}): boolean {
const { groupPolicy, guildAllowlisted, channelAllowlistConfigured, channelAllowed } = params;
if (groupPolicy === "disabled") return false;
if (groupPolicy === "open") return true;
if (!guildAllowlisted) return false;
if (!channelAllowlistConfigured) return true;
if (groupPolicy === "disabled") {
return false;
}
if (groupPolicy === "open") {
return true;
}
if (!guildAllowlisted) {
return false;
}
if (!channelAllowlistConfigured) {
return true;
}
return channelAllowed;
}
@@ -330,7 +378,9 @@ export function resolveGroupDmAllow(params: {
channelSlug: string;
}) {
const { channels, channelId, channelName, channelSlug } = params;
if (!channels || channels.length === 0) return true;
if (!channels || channels.length === 0) {
return true;
}
const allowList = new Set(channels.map((entry) => normalizeDiscordSlug(String(entry))));
const candidates = [
normalizeDiscordSlug(channelId),
@@ -350,14 +400,20 @@ export function shouldEmitDiscordReactionNotification(params: {
allowlist?: Array<string | number>;
}) {
const mode = params.mode ?? "own";
if (mode === "off") return false;
if (mode === "all") return true;
if (mode === "off") {
return false;
}
if (mode === "all") {
return true;
}
if (mode === "own") {
return Boolean(params.botId && params.messageAuthorId === params.botId);
}
if (mode === "allowlist") {
const list = normalizeDiscordAllowList(params.allowlist, ["discord:", "user:"]);
if (!list) return false;
if (!list) {
return false;
}
return allowListMatches(list, {
id: params.userId,
name: params.userName,

View File

@@ -65,12 +65,16 @@ export function buildExecApprovalCustomId(
export function parseExecApprovalData(
data: ComponentData,
): { approvalId: string; action: ExecApprovalDecision } | null {
if (!data || typeof data !== "object") return null;
if (!data || typeof data !== "object") {
return null;
}
const coerce = (value: unknown) =>
typeof value === "string" || typeof value === "number" ? String(value) : "";
const rawId = coerce(data.id);
const rawAction = coerce(data.action);
if (!rawId || !rawAction) return null;
if (!rawId || !rawAction) {
return null;
}
const action = rawAction as ExecApprovalDecision;
if (action !== "allow-once" && action !== "allow-always" && action !== "deny") {
return null;
@@ -205,19 +209,29 @@ export class DiscordExecApprovalHandler {
shouldHandle(request: ExecApprovalRequest): boolean {
const config = this.opts.config;
if (!config.enabled) return false;
if (!config.approvers || config.approvers.length === 0) return false;
if (!config.enabled) {
return false;
}
if (!config.approvers || config.approvers.length === 0) {
return false;
}
// Check agent filter
if (config.agentFilter?.length) {
if (!request.request.agentId) return false;
if (!config.agentFilter.includes(request.request.agentId)) return false;
if (!request.request.agentId) {
return false;
}
if (!config.agentFilter.includes(request.request.agentId)) {
return false;
}
}
// Check session filter (substring match)
if (config.sessionFilter?.length) {
const session = request.request.sessionKey;
if (!session) return false;
if (!session) {
return false;
}
const matches = config.sessionFilter.some((p) => {
try {
return session.includes(p) || new RegExp(p).test(session);
@@ -225,14 +239,18 @@ export class DiscordExecApprovalHandler {
return session.includes(p);
}
});
if (!matches) return false;
if (!matches) {
return false;
}
}
return true;
}
async start(): Promise<void> {
if (this.started) return;
if (this.started) {
return;
}
this.started = true;
const config = this.opts.config;
@@ -270,7 +288,9 @@ export class DiscordExecApprovalHandler {
}
async stop(): Promise<void> {
if (!this.started) return;
if (!this.started) {
return;
}
this.started = false;
// Clear all pending timeouts
@@ -297,7 +317,9 @@ export class DiscordExecApprovalHandler {
}
private async handleApprovalRequested(request: ExecApprovalRequest): Promise<void> {
if (!this.shouldHandle(request)) return;
if (!this.shouldHandle(request)) {
return;
}
logDebug(`discord exec approvals: received request ${request.id}`);
@@ -394,7 +416,9 @@ export class DiscordExecApprovalHandler {
private async handleApprovalResolved(resolved: ExecApprovalResolved): Promise<void> {
const pending = this.pending.get(resolved.id);
if (!pending) return;
if (!pending) {
return;
}
clearTimeout(pending.timeoutId);
this.pending.delete(resolved.id);
@@ -402,7 +426,9 @@ export class DiscordExecApprovalHandler {
const request = this.requestCache.get(resolved.id);
this.requestCache.delete(resolved.id);
if (!request) return;
if (!request) {
return;
}
logDebug(`discord exec approvals: resolved ${resolved.id} with ${resolved.decision}`);
@@ -415,14 +441,18 @@ export class DiscordExecApprovalHandler {
private async handleApprovalTimeout(approvalId: string): Promise<void> {
const pending = this.pending.get(approvalId);
if (!pending) return;
if (!pending) {
return;
}
this.pending.delete(approvalId);
const request = this.requestCache.get(approvalId);
this.requestCache.delete(approvalId);
if (!request) return;
if (!request) {
return;
}
logDebug(`discord exec approvals: timeout for ${approvalId}`);

View File

@@ -7,8 +7,12 @@ export function resolveDiscordSystemLocation(params: {
channelName: string;
}) {
const { isDirectMessage, isGroupDm, guild, channelName } = params;
if (isDirectMessage) return "DM";
if (isGroupDm) return `Group DM #${channelName}`;
if (isDirectMessage) {
return "DM";
}
if (isGroupDm) {
return `Group DM #${channelName}`;
}
return guild?.name ? `${guild.name} #${channelName}` : `#${channelName}`;
}
@@ -28,7 +32,9 @@ export function formatDiscordUserTag(user: User) {
}
export function resolveTimestampMs(timestamp?: string | null) {
if (!timestamp) return undefined;
if (!timestamp) {
return undefined;
}
const parsed = Date.parse(timestamp);
return Number.isNaN(parsed) ? undefined : parsed;
}

View File

@@ -41,7 +41,9 @@ function logSlowDiscordListener(params: {
event: string;
durationMs: number;
}) {
if (params.durationMs < DISCORD_SLOW_LISTENER_THRESHOLD_MS) return;
if (params.durationMs < DISCORD_SLOW_LISTENER_THRESHOLD_MS) {
return;
}
const duration = formatDurationSeconds(params.durationMs, {
decimals: 1,
unit: "seconds",
@@ -180,10 +182,16 @@ async function handleDiscordReactionEvent(params: {
}) {
try {
const { data, client, action, botUserId, guildEntries } = params;
if (!("user" in data)) return;
if (!("user" in data)) {
return;
}
const user = data.user;
if (!user || user.bot) return;
if (!data.guild_id) return;
if (!user || user.bot) {
return;
}
if (!data.guild_id) {
return;
}
const guildInfo = resolveDiscordGuildEntry({
guild: data.guild ?? undefined,
@@ -194,7 +202,9 @@ async function handleDiscordReactionEvent(params: {
}
const channel = await client.fetchChannel(data.channel_id);
if (!channel) return;
if (!channel) {
return;
}
const channelName = "name" in channel ? (channel.name ?? undefined) : undefined;
const channelSlug = channelName ? normalizeDiscordSlug(channelName) : "";
const channelType = "type" in channel ? channel.type : undefined;
@@ -226,9 +236,13 @@ async function handleDiscordReactionEvent(params: {
parentSlug,
scope: isThreadChannel ? "thread" : "channel",
});
if (channelConfig?.allowed === false) return;
if (channelConfig?.allowed === false) {
return;
}
if (botUserId && user.id === botUserId) return;
if (botUserId && user.id === botUserId) {
return;
}
const reactionMode = guildInfo?.reactionNotifications ?? "own";
const message = await data.message.fetch().catch(() => null);
@@ -242,7 +256,9 @@ async function handleDiscordReactionEvent(params: {
userTag: formatDiscordUserTag(user),
allowlist: guildInfo?.users,
});
if (!shouldNotify) return;
if (!shouldNotify) {
return;
}
const emojiLabel = formatDiscordReactionEmoji(data.emoji);
const actorLabel = formatDiscordUserTag(user);
@@ -290,7 +306,9 @@ export class DiscordPresenceListener extends PresenceUpdateListener {
"user" in data && data.user && typeof data.user === "object" && "id" in data.user
? String(data.user.id)
: undefined;
if (!userId) return;
if (!userId) {
return;
}
setPresence(
this.accountId,
userId,

View File

@@ -61,12 +61,16 @@ export async function preflightDiscordMessage(
const logger = getChildLogger({ module: "discord-auto-reply" });
const message = params.data.message;
const author = params.data.author;
if (!author) return null;
if (!author) {
return null;
}
const allowBots = params.discordConfig?.allowBots ?? false;
if (author.bot) {
// Always ignore own messages to prevent self-reply loops
if (params.botUserId && author.id === params.botUserId) return null;
if (params.botUserId && author.id === params.botUserId) {
return null;
}
if (!allowBots) {
logVerbose("discord: drop bot message (allowBots=false)");
return null;
@@ -300,7 +304,9 @@ export async function preflightDiscordMessage(
channelName: displayChannelName,
channelSlug: displayChannelSlug,
});
if (isGroupDm && !groupDmAllowed) return null;
if (isGroupDm && !groupDmAllowed) {
return null;
}
const channelAllowlistConfigured =
Boolean(guildInfo?.channels) && Object.keys(guildInfo?.channels ?? {}).length > 0;

View File

@@ -47,22 +47,34 @@ export function createDiscordMessageHandler(params: {
buildKey: (entry) => {
const message = entry.data.message;
const authorId = entry.data.author?.id;
if (!message || !authorId) return null;
if (!message || !authorId) {
return null;
}
const channelId = message.channelId;
if (!channelId) return null;
if (!channelId) {
return null;
}
return `discord:${params.accountId}:${channelId}:${authorId}`;
},
shouldDebounce: (entry) => {
const message = entry.data.message;
if (!message) return false;
if (message.attachments && message.attachments.length > 0) return false;
if (!message) {
return false;
}
if (message.attachments && message.attachments.length > 0) {
return false;
}
const baseText = resolveDiscordMessageText(message, { includeForwarded: false });
if (!baseText.trim()) return false;
if (!baseText.trim()) {
return false;
}
return !hasControlCommand(baseText, params.cfg);
},
onFlush: async (entries) => {
const last = entries.at(-1);
if (!last) return;
if (!last) {
return;
}
if (entries.length === 1) {
const ctx = await preflightDiscordMessage({
...params,
@@ -71,7 +83,9 @@ export function createDiscordMessageHandler(params: {
data: last.data,
client: last.client,
});
if (!ctx) return;
if (!ctx) {
return;
}
await processDiscordMessage(ctx);
return;
}
@@ -100,7 +114,9 @@ export function createDiscordMessageHandler(params: {
data: syntheticData,
client: last.client,
});
if (!ctx) return;
if (!ctx) {
return;
}
if (entries.length > 1) {
const ids = entries.map((entry) => entry.data.message?.id).filter(Boolean) as string[];
if (ids.length > 0) {

View File

@@ -55,7 +55,9 @@ export async function resolveDiscordChannelInfo(
): Promise<DiscordChannelInfo | null> {
const cached = DISCORD_CHANNEL_INFO_CACHE.get(channelId);
if (cached) {
if (cached.expiresAt > Date.now()) return cached.value;
if (cached.expiresAt > Date.now()) {
return cached.value;
}
DISCORD_CHANNEL_INFO_CACHE.delete(channelId);
}
try {
@@ -98,7 +100,9 @@ export async function resolveMediaList(
maxBytes: number,
): Promise<DiscordMediaInfo[]> {
const attachments = message.attachments ?? [];
if (attachments.length === 0) return [];
if (attachments.length === 0) {
return [];
}
const out: DiscordMediaInfo[] = [];
for (const attachment of attachments) {
try {
@@ -127,22 +131,34 @@ export async function resolveMediaList(
function inferPlaceholder(attachment: APIAttachment): string {
const mime = attachment.content_type ?? "";
if (mime.startsWith("image/")) return "<media:image>";
if (mime.startsWith("video/")) return "<media:video>";
if (mime.startsWith("audio/")) return "<media:audio>";
if (mime.startsWith("image/")) {
return "<media:image>";
}
if (mime.startsWith("video/")) {
return "<media:video>";
}
if (mime.startsWith("audio/")) {
return "<media:audio>";
}
return "<media:document>";
}
function isImageAttachment(attachment: APIAttachment): boolean {
const mime = attachment.content_type ?? "";
if (mime.startsWith("image/")) return true;
if (mime.startsWith("image/")) {
return true;
}
const name = attachment.filename?.toLowerCase() ?? "";
if (!name) return false;
if (!name) {
return false;
}
return /\.(avif|bmp|gif|heic|heif|jpe?g|png|tiff?|webp)$/.test(name);
}
function buildDiscordAttachmentPlaceholder(attachments?: APIAttachment[]): string {
if (!attachments || attachments.length === 0) return "";
if (!attachments || attachments.length === 0) {
return "";
}
const count = attachments.length;
const allImages = attachments.every(isImageAttachment);
const label = allImages ? "image" : "file";
@@ -161,22 +177,34 @@ export function resolveDiscordMessageText(
message.embeds?.[0]?.description ||
options?.fallbackText?.trim() ||
"";
if (!options?.includeForwarded) return baseText;
if (!options?.includeForwarded) {
return baseText;
}
const forwardedText = resolveDiscordForwardedMessagesText(message);
if (!forwardedText) return baseText;
if (!baseText) return forwardedText;
if (!forwardedText) {
return baseText;
}
if (!baseText) {
return forwardedText;
}
return `${baseText}\n${forwardedText}`;
}
function resolveDiscordForwardedMessagesText(message: Message): string {
const snapshots = resolveDiscordMessageSnapshots(message);
if (snapshots.length === 0) return "";
if (snapshots.length === 0) {
return "";
}
const forwardedBlocks = snapshots
.map((snapshot) => {
const snapshotMessage = snapshot.message;
if (!snapshotMessage) return null;
if (!snapshotMessage) {
return null;
}
const text = resolveDiscordSnapshotMessageText(snapshotMessage);
if (!text) return null;
if (!text) {
return null;
}
const authorLabel = formatDiscordSnapshotAuthor(snapshotMessage.author);
const heading = authorLabel
? `[Forwarded message from ${authorLabel}]`
@@ -184,7 +212,9 @@ function resolveDiscordForwardedMessagesText(message: Message): string {
return `${heading}\n${text}`;
})
.filter((entry): entry is string => Boolean(entry));
if (forwardedBlocks.length === 0) return "";
if (forwardedBlocks.length === 0) {
return "";
}
return forwardedBlocks.join("\n\n");
}
@@ -194,7 +224,9 @@ function resolveDiscordMessageSnapshots(message: Message): DiscordMessageSnapsho
rawData?.message_snapshots ??
(message as { message_snapshots?: unknown }).message_snapshots ??
(message as { messageSnapshots?: unknown }).messageSnapshots;
if (!Array.isArray(snapshots)) return [];
if (!Array.isArray(snapshots)) {
return [];
}
return snapshots.filter(
(entry): entry is DiscordMessageSnapshot => Boolean(entry) && typeof entry === "object",
);
@@ -211,7 +243,9 @@ function resolveDiscordSnapshotMessageText(snapshot: DiscordSnapshotMessage): st
function formatDiscordSnapshotAuthor(
author: DiscordSnapshotAuthor | null | undefined,
): string | undefined {
if (!author) return undefined;
if (!author) {
return undefined;
}
const globalName = author.global_name ?? undefined;
const username = author.username ?? undefined;
const name = author.name ?? undefined;
@@ -220,8 +254,12 @@ function formatDiscordSnapshotAuthor(
if (username && discriminator && discriminator !== "0") {
return `@${username}#${discriminator}`;
}
if (base) return `@${base}`;
if (author.id) return `@${author.id}`;
if (base) {
return `@${base}`;
}
if (author.id) {
return `@${author.id}`;
}
return undefined;
}

View File

@@ -63,7 +63,9 @@ function buildDiscordCommandOptions(params: {
}): CommandOptions | undefined {
const { command, cfg } = params;
const args = command.args;
if (!args || args.length === 0) return undefined;
if (!args || args.length === 0) {
return undefined;
}
return args.map((arg) => {
const required = arg.required ?? false;
if (arg.type === "number") {
@@ -121,7 +123,9 @@ function readDiscordCommandArgs(
interaction: CommandInteraction,
definitions?: CommandArgDefinition[],
): CommandArgs | undefined {
if (!definitions || definitions.length === 0) return undefined;
if (!definitions || definitions.length === 0) {
return undefined;
}
const values: CommandArgValues = {};
for (const definition of definitions) {
let value: string | number | boolean | null | undefined;
@@ -140,7 +144,9 @@ function readDiscordCommandArgs(
}
function chunkItems<T>(items: T[], size: number): T[][] {
if (size <= 0) return [items];
if (size <= 0) {
return [items];
}
const rows: T[][] = [];
for (let i = 0; i < items.length; i += size) {
rows.push(items.slice(i, i + size));
@@ -168,16 +174,24 @@ function decodeDiscordCommandArgValue(value: string): string {
}
function isDiscordUnknownInteraction(error: unknown): boolean {
if (!error || typeof error !== "object") return false;
if (!error || typeof error !== "object") {
return false;
}
const err = error as {
discordCode?: number;
status?: number;
message?: string;
rawBody?: { code?: number; message?: string };
};
if (err.discordCode === 10062 || err.rawBody?.code === 10062) return true;
if (err.status === 404 && /Unknown interaction/i.test(err.message ?? "")) return true;
if (/Unknown interaction/i.test(err.rawBody?.message ?? "")) return true;
if (err.discordCode === 10062 || err.rawBody?.code === 10062) {
return true;
}
if (err.status === 404 && /Unknown interaction/i.test(err.message ?? "")) {
return true;
}
if (/Unknown interaction/i.test(err.rawBody?.message ?? "")) {
return true;
}
return false;
}
@@ -213,14 +227,18 @@ function buildDiscordCommandArgCustomId(params: {
function parseDiscordCommandArgData(
data: ComponentData,
): { command: string; arg: string; value: string; userId: string } | null {
if (!data || typeof data !== "object") return null;
if (!data || typeof data !== "object") {
return null;
}
const coerce = (value: unknown) =>
typeof value === "string" || typeof value === "number" ? String(value) : "";
const rawCommand = coerce(data.command);
const rawArg = coerce(data.arg);
const rawValue = coerce(data.value);
const rawUser = coerce(data.user);
if (!rawCommand || !rawArg || !rawValue || !rawUser) return null;
if (!rawCommand || !rawArg || !rawValue || !rawUser) {
return null;
}
return {
command: decodeDiscordCommandArgValue(rawCommand),
arg: decodeDiscordCommandArgValue(rawArg),
@@ -273,7 +291,9 @@ async function handleDiscordCommandArgInteraction(
components: [],
}),
);
if (!updated) return;
if (!updated) {
return;
}
const commandArgs = createCommandArgsWithValue({
argName: parsed.arg,
value: parsed.value,
@@ -502,7 +522,9 @@ async function dispatchDiscordCommandInteraction(params: {
const useAccessGroups = cfg.commands?.useAccessGroups !== false;
const user = interaction.user;
if (!user) return;
if (!user) {
return;
}
const channel = interaction.channel;
const channelType = channel?.type;
const isDirectMessage = channelType === ChannelType.DM;
@@ -851,25 +873,35 @@ async function deliverDiscordInteractionReply(params: {
maxLines: maxLinesPerMessage,
chunkMode,
});
if (!chunks.length && text) chunks.push(text);
if (!chunks.length && text) {
chunks.push(text);
}
const caption = chunks[0] ?? "";
await sendMessage(caption, media);
for (const chunk of chunks.slice(1)) {
if (!chunk.trim()) continue;
if (!chunk.trim()) {
continue;
}
await interaction.followUp({ content: chunk });
}
return;
}
if (!text.trim()) return;
if (!text.trim()) {
return;
}
const chunks = chunkDiscordTextWithMode(text, {
maxChars: textLimit,
maxLines: maxLinesPerMessage,
chunkMode,
});
if (!chunks.length && text) chunks.push(text);
if (!chunks.length && text) {
chunks.push(text);
}
for (const chunk of chunks) {
if (!chunk.trim()) continue;
if (!chunk.trim()) {
continue;
}
await sendMessage(chunk);
}
}

View File

@@ -52,14 +52,18 @@ export type MonitorDiscordOpts = {
};
function summarizeAllowList(list?: Array<string | number>) {
if (!list || list.length === 0) return "any";
if (!list || list.length === 0) {
return "any";
}
const sample = list.slice(0, 4).map((entry) => String(entry));
const suffix = list.length > sample.length ? ` (+${list.length - sample.length})` : "";
return `${sample.join(", ")}${suffix}`;
}
function summarizeGuilds(entries?: Record<string, unknown>) {
if (!entries || Object.keys(entries).length === 0) return "any";
if (!entries || Object.keys(entries).length === 0) {
return "any";
}
const keys = Object.keys(entries);
const sample = keys.slice(0, 4);
const suffix = keys.length > sample.length ? ` (+${keys.length - sample.length})` : "";
@@ -71,7 +75,9 @@ async function deployDiscordCommands(params: {
runtime: RuntimeEnv;
enabled: boolean;
}) {
if (!params.enabled) return;
if (!params.enabled) {
return;
}
const runWithRetry = createDiscordRetryRunner({ verbose: shouldLogVerbose() });
try {
await runWithRetry(() => params.client.handleDeployRequest(), "command deploy");
@@ -84,12 +90,16 @@ async function deployDiscordCommands(params: {
}
function formatDiscordDeployErrorDetails(err: unknown): string {
if (!err || typeof err !== "object") return "";
if (!err || typeof err !== "object") {
return "";
}
const status = (err as { status?: unknown }).status;
const discordCode = (err as { discordCode?: unknown }).discordCode;
const rawBody = (err as { rawBody?: unknown }).rawBody;
const details: string[] = [];
if (typeof status === "number") details.push(`status=${status}`);
if (typeof status === "number") {
details.push(`status=${status}`);
}
if (typeof discordCode === "number" || typeof discordCode === "string") {
details.push(`code=${discordCode}`);
}
@@ -204,7 +214,9 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
try {
const entries: Array<{ input: string; guildKey: string; channelKey?: string }> = [];
for (const [guildKey, guildCfg] of Object.entries(guildEntries)) {
if (guildKey === "*") continue;
if (guildKey === "*") {
continue;
}
const channels = guildCfg?.channels ?? {};
const channelKeys = Object.keys(channels).filter((key) => key !== "*");
if (channelKeys.length === 0) {
@@ -229,7 +241,9 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
const unresolved: string[] = [];
for (const entry of resolved) {
const source = entries.find((item) => item.input === entry.input);
if (!source) continue;
if (!source) {
continue;
}
const sourceGuild = guildEntries?.[source.guildKey] ?? {};
if (!entry.resolved || !entry.guildId) {
unresolved.push(entry.input);
@@ -301,22 +315,32 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
if (guildEntries && Object.keys(guildEntries).length > 0) {
const userEntries = new Set<string>();
for (const guild of Object.values(guildEntries)) {
if (!guild || typeof guild !== "object") continue;
if (!guild || typeof guild !== "object") {
continue;
}
const users = (guild as { users?: Array<string | number> }).users;
if (Array.isArray(users)) {
for (const entry of users) {
const trimmed = String(entry).trim();
if (trimmed && trimmed !== "*") userEntries.add(trimmed);
if (trimmed && trimmed !== "*") {
userEntries.add(trimmed);
}
}
}
const channels = (guild as { channels?: Record<string, unknown> }).channels ?? {};
for (const channel of Object.values(channels)) {
if (!channel || typeof channel !== "object") continue;
if (!channel || typeof channel !== "object") {
continue;
}
const channelUsers = (channel as { users?: Array<string | number> }).users;
if (!Array.isArray(channelUsers)) continue;
if (!Array.isArray(channelUsers)) {
continue;
}
for (const entry of channelUsers) {
const trimmed = String(entry).trim();
if (trimmed && trimmed !== "*") userEntries.add(trimmed);
if (trimmed && trimmed !== "*") {
userEntries.add(trimmed);
}
}
}
}
@@ -337,7 +361,9 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
const nextGuilds = { ...guildEntries };
for (const [guildKey, guildConfig] of Object.entries(guildEntries ?? {})) {
if (!guildConfig || typeof guildConfig !== "object") continue;
if (!guildConfig || typeof guildConfig !== "object") {
continue;
}
const nextGuild = { ...guildConfig } as Record<string, unknown>;
const users = (guildConfig as { users?: Array<string | number> }).users;
if (Array.isArray(users) && users.length > 0) {
@@ -345,7 +371,9 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
for (const entry of users) {
const trimmed = String(entry).trim();
const resolved = resolvedMap.get(trimmed);
if (resolved?.resolved && resolved.id) additions.push(resolved.id);
if (resolved?.resolved && resolved.id) {
additions.push(resolved.id);
}
}
nextGuild.users = mergeAllowlist({ existing: users, additions });
}
@@ -353,14 +381,20 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
if (channels && typeof channels === "object") {
const nextChannels: Record<string, unknown> = { ...channels };
for (const [channelKey, channelConfig] of Object.entries(channels)) {
if (!channelConfig || typeof channelConfig !== "object") continue;
if (!channelConfig || typeof channelConfig !== "object") {
continue;
}
const channelUsers = (channelConfig as { users?: Array<string | number> }).users;
if (!Array.isArray(channelUsers) || channelUsers.length === 0) continue;
if (!Array.isArray(channelUsers) || channelUsers.length === 0) {
continue;
}
const additions: string[] = [];
for (const entry of channelUsers) {
const trimmed = String(entry).trim();
const resolved = resolvedMap.get(trimmed);
if (resolved?.resolved && resolved.id) additions.push(resolved.id);
if (resolved?.resolved && resolved.id) {
additions.push(resolved.id);
}
}
nextChannels[channelKey] = {
...channelConfig,
@@ -564,7 +598,9 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
});
const abortSignal = opts.abortSignal;
const onAbort = () => {
if (!gateway) return;
if (!gateway) {
return;
}
// Carbon emits an error when maxAttempts is 0; keep a one-shot listener to avoid
// an unhandled error after we tear down listeners during abort.
gatewayEmitter?.once("error", () => {});
@@ -581,8 +617,12 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
let helloTimeoutId: ReturnType<typeof setTimeout> | undefined;
const onGatewayDebug = (msg: unknown) => {
const message = String(msg);
if (!message.includes("WebSocket connection opened")) return;
if (helloTimeoutId) clearTimeout(helloTimeoutId);
if (!message.includes("WebSocket connection opened")) {
return;
}
if (helloTimeoutId) {
clearTimeout(helloTimeoutId);
}
helloTimeoutId = setTimeout(() => {
if (!gateway?.isConnected) {
runtime.log?.(
@@ -618,7 +658,9 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
});
} finally {
stopGatewayLogging();
if (helloTimeoutId) clearTimeout(helloTimeoutId);
if (helloTimeoutId) {
clearTimeout(helloTimeoutId);
}
gatewayEmitter?.removeListener("debug", onGatewayDebug);
abortSignal?.removeEventListener("abort", onAbort);
if (execApprovalsHandler) {

View File

@@ -9,11 +9,15 @@ export function resolveReplyContext(
options?: { envelope?: EnvelopeFormatOptions },
): string | null {
const referenced = message.referencedMessage;
if (!referenced?.author) return null;
if (!referenced?.author) {
return null;
}
const referencedText = resolveDiscordMessageText(referenced, {
includeForwarded: true,
});
if (!referencedText) return null;
if (!referencedText) {
return null;
}
const fromLabel = referenced.author ? buildDirectLabel(referenced.author) : "Unknown";
const body = `${referencedText}\n[discord message id: ${referenced.id} channel: ${referenced.channelId} from: ${formatDiscordUserTag(referenced.author)} user id:${referenced.author?.id ?? "unknown"}]`;
return formatAgentEnvelope({

View File

@@ -27,7 +27,9 @@ export async function deliverDiscordReply(params: {
const rawText = payload.text ?? "";
const tableMode = params.tableMode ?? "code";
const text = convertMarkdownTables(rawText, tableMode);
if (!text && mediaList.length === 0) continue;
if (!text && mediaList.length === 0) {
continue;
}
const replyTo = params.replyToId?.trim() || undefined;
if (mediaList.length === 0) {
@@ -38,10 +40,14 @@ export async function deliverDiscordReply(params: {
maxLines: params.maxLinesPerMessage,
chunkMode: mode,
});
if (!chunks.length && text) chunks.push(text);
if (!chunks.length && text) {
chunks.push(text);
}
for (const chunk of chunks) {
const trimmed = chunk.trim();
if (!trimmed) continue;
if (!trimmed) {
continue;
}
await sendMessageDiscord(params.target, trimmed, {
token: params.token,
rest: params.rest,
@@ -54,7 +60,9 @@ export async function deliverDiscordReply(params: {
}
const firstMedia = mediaList[0];
if (!firstMedia) continue;
if (!firstMedia) {
continue;
}
await sendMessageDiscord(params.target, text, {
token: params.token,
rest: params.rest,

View File

@@ -48,7 +48,9 @@ export function resolveDiscordThreadChannel(params: {
message: DiscordMessageEvent["message"];
channelInfo: import("./message-utils.js").DiscordChannelInfo | null;
}): DiscordThreadChannel | null {
if (!params.isGuildMessage) return null;
if (!params.isGuildMessage) {
return null;
}
const { message, channelInfo } = params;
const channel = "channel" in message ? (message as { channel?: unknown }).channel : undefined;
const isThreadChannel =
@@ -57,8 +59,12 @@ export function resolveDiscordThreadChannel(params: {
"isThread" in channel &&
typeof (channel as { isThread?: unknown }).isThread === "function" &&
(channel as { isThread: () => boolean }).isThread();
if (isThreadChannel) return channel as unknown as DiscordThreadChannel;
if (!isDiscordThreadType(channelInfo?.type)) return null;
if (isThreadChannel) {
return channel as unknown as DiscordThreadChannel;
}
if (!isDiscordThreadType(channelInfo?.type)) {
return null;
}
return {
id: message.channelId,
name: channelInfo?.name ?? undefined,
@@ -76,7 +82,9 @@ export async function resolveDiscordThreadParentInfo(params: {
const { threadChannel, channelInfo, client } = params;
const parentId =
threadChannel.parentId ?? threadChannel.parent?.id ?? channelInfo?.parentId ?? undefined;
if (!parentId) return {};
if (!parentId) {
return {};
}
let parentName = threadChannel.parent?.name;
const parentInfo = await resolveDiscordChannelInfo(client, parentId);
parentName = parentName ?? parentInfo?.name;
@@ -93,13 +101,17 @@ export async function resolveDiscordThreadStarter(params: {
}): Promise<DiscordThreadStarter | null> {
const cacheKey = params.channel.id;
const cached = DISCORD_THREAD_STARTER_CACHE.get(cacheKey);
if (cached) return cached;
if (cached) {
return cached;
}
try {
const parentType = params.parentType;
const isForumParent =
parentType === ChannelType.GuildForum || parentType === ChannelType.GuildMedia;
const messageChannelId = isForumParent ? params.channel.id : params.parentId;
if (!messageChannelId) return null;
if (!messageChannelId) {
return null;
}
const starter = (await params.client.rest.get(
Routes.channelMessage(messageChannelId, params.channel.id),
)) as {
@@ -113,9 +125,13 @@ export async function resolveDiscordThreadStarter(params: {
};
timestamp?: string | null;
};
if (!starter) return null;
if (!starter) {
return null;
}
const text = starter.content?.trim() ?? starter.embeds?.[0]?.description?.trim() ?? "";
if (!text) return null;
if (!text) {
return null;
}
const author =
starter.member?.nick ??
starter.member?.displayName ??
@@ -142,10 +158,16 @@ export function resolveDiscordReplyTarget(opts: {
replyToId?: string;
hasReplied: boolean;
}): string | undefined {
if (opts.replyToMode === "off") return undefined;
if (opts.replyToMode === "off") {
return undefined;
}
const replyToId = opts.replyToId?.trim();
if (!replyToId) return undefined;
if (opts.replyToMode === "all") return replyToId;
if (!replyToId) {
return undefined;
}
if (opts.replyToMode === "all") {
return replyToId;
}
return opts.hasReplied ? undefined : replyToId;
}
@@ -183,9 +205,13 @@ export function resolveDiscordAutoThreadContext(params: {
createdThreadId?: string | null;
}): DiscordAutoThreadContext | null {
const createdThreadId = String(params.createdThreadId ?? "").trim();
if (!createdThreadId) return null;
if (!createdThreadId) {
return null;
}
const messageChannelId = params.messageChannelId.trim();
if (!messageChannelId) return null;
if (!messageChannelId) {
return null;
}
const threadSessionKey = buildAgentSessionKey({
agentId: params.agentId,
@@ -262,9 +288,15 @@ export async function maybeCreateDiscordAutoThread(params: {
baseText: string;
combinedBody: string;
}): Promise<string | undefined> {
if (!params.isGuildMessage) return undefined;
if (!params.channelConfig?.autoThread) return undefined;
if (params.threadChannel) return undefined;
if (!params.isGuildMessage) {
return undefined;
}
if (!params.channelConfig?.autoThread) {
return undefined;
}
if (params.threadChannel) {
return undefined;
}
try {
const threadName = sanitizeDiscordThreadName(
params.baseText || params.combinedBody || "Thread",

View File

@@ -2,7 +2,9 @@ import type { Client } from "@buape/carbon";
export async function sendTyping(params: { client: Client; channelId: string }) {
const channel = await params.client.fetchChannel(params.channelId);
if (!channel) return;
if (!channel) {
return;
}
if ("triggerTyping" in channel && typeof channel.triggerTyping === "function") {
await channel.triggerTyping();
}