mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 05:37:41 +00:00
chore: Enable "curly" rule to avoid single-statement if confusion/errors.
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user