openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
1,426 lines • 59.2 kB
JavaScript
import { a as normalizeLowercaseStringOrEmpty } from "./string-coerce-mnp54Vah.js";
import { i as formatErrorMessage } from "./errors-BXgSefBE.js";
import { t as assertMediaNotDataUrl } from "./sandbox-paths-DAj1Euvz.js";
import { _ as readReactionParams, b as readStringParam, d as parseAvailableTags, g as readPositiveIntegerParam, l as jsonResult, m as readNonNegativeIntegerParam, v as readStringArrayParam } from "./common-C4yy9V-D.js";
import { i as resolveOpenProviderRuntimeGroupPolicy } from "./runtime-group-policy-CSD1Is8G.js";
import { r as resolvePollMaxSelections } from "./polls-C-v11_tu.js";
import { t as readBooleanParam } from "./boolean-param-K-k-cDXs.js";
import "./error-runtime-C8vbtAJt.js";
import "./string-coerce-runtime-CEGJWkQ_.js";
import { o as withNormalizedTimestamp } from "./date-time-CcWivhrg.js";
import { a as mergeDiscordAccountConfig, o as resolveDefaultDiscordAccountId, t as createDiscordActionGate } from "./accounts-BI_6uJlY.js";
import { n as resolveDiscordChannelId } from "./target-parsing-CxulEx_f.js";
import { r as sendDiscordComponentMessage } from "./send.components-BkX2GzM6.js";
import { Gt as PermissionFlagsBits, It as ChannelType } from "./discord-DdqXEGEm.js";
import { n as getGateway, o as getPresence } from "./gateway-registry-DPxmW0Db.js";
import { E as canManageGuildRoleDiscord, L as createDiscordRuntimeAccountContext, M as hasAnyGuildPermissionDiscord, N as isThreadChannelType$1, O as fetchChannelPermissionsDiscord, T as canManageGuildMemberRoleDiscord, d as resolveDiscordTargetChannelId, j as hasAnyChannelPermissionDiscord } from "./send.shared-SdWkx4sF.js";
import { a as normalizeDiscordSlug, c as resolveDiscordChannelConfigWithFallback, n as isDiscordGroupAllowedByPolicy } from "./allow-list-CBhA2l4a.js";
import { A as removeRoleDiscord, B as removeChannelPermissionDiscord, C as fetchGuildInfoDiscord, D as kickMemberDiscord, E as fetchVoiceStatusDiscord, F as uploadStickerDiscord, I as createChannelDiscord, L as deleteChannelDiscord, M as timeoutMemberDiscord, N as listGuildEmojisDiscord, O as listGuildChannelsDiscord, P as uploadEmojiDiscord, R as editChannelDiscord, S as fetchChannelInfoDiscord, T as fetchRoleInfoDiscord, V as setChannelPermissionDiscord, _ as searchMessagesDiscord, b as banMemberDiscord, c as DiscordThreadInitialMessageError, d as editMessageDiscord, f as fetchMessageDiscord, g as readMessagesDiscord, h as pinMessageDiscord, i as removeReactionDiscord, j as resolveEventCoverImage, k as listScheduledEventsDiscord, l as createThreadDiscord, m as listThreadsDiscord, n as reactMessageDiscord, o as sendVoiceMessageDiscord, p as listPinsDiscord, r as removeOwnReactionsDiscord, t as fetchReactionsDiscord, u as deleteMessageDiscord, v as unpinMessageDiscord, w as fetchMemberInfoDiscord, x as createScheduledEventDiscord, y as addRoleDiscord, z as moveChannelDiscord } from "./send-DU6EOsCH.js";
import { n as sendPollDiscord, r as sendStickerDiscord, t as sendMessageDiscord } from "./send.outbound-Df86oBHP.js";
import { s as readDiscordComponentSpec } from "./components-CDHzCfc9.js";
import "./targets-B5qkz8Y1.js";
//#region extensions/discord/src/actions/runtime.shared.ts
function readDiscordParentIdParam(params) {
if (params.clearParent === true) return null;
if (params.parentId === null) return null;
return readStringParam(params, "parentId");
}
function readDiscordBooleanParam(params, key) {
return typeof params[key] === "boolean" ? params[key] : void 0;
}
function createDiscordActionOptions(params) {
return {
cfg: params.cfg,
...params.accountId ? { accountId: params.accountId } : {},
...params.extra ?? {}
};
}
function readDiscordChannelCreateParams(params) {
const parentId = readDiscordParentIdParam(params);
return {
guildId: readStringParam(params, "guildId", { required: true }),
name: readStringParam(params, "name", { required: true }),
type: readNonNegativeIntegerParam(params, "channelType") ?? readNonNegativeIntegerParam(params, "type") ?? void 0,
parentId: parentId ?? void 0,
topic: readStringParam(params, "topic") ?? void 0,
position: readNonNegativeIntegerParam(params, "position") ?? void 0,
nsfw: readDiscordBooleanParam(params, "nsfw")
};
}
function readDiscordChannelEditParams(params) {
const parentId = readDiscordParentIdParam(params);
return {
channelId: readStringParam(params, "channelId", { required: true }),
name: readStringParam(params, "name") ?? void 0,
topic: readStringParam(params, "topic") ?? void 0,
position: readNonNegativeIntegerParam(params, "position") ?? void 0,
parentId: parentId === void 0 ? void 0 : parentId,
nsfw: readDiscordBooleanParam(params, "nsfw"),
rateLimitPerUser: readNonNegativeIntegerParam(params, "rateLimitPerUser") ?? void 0,
archived: readDiscordBooleanParam(params, "archived"),
locked: readDiscordBooleanParam(params, "locked"),
autoArchiveDuration: readPositiveIntegerParam(params, "autoArchiveDuration") ?? void 0,
availableTags: parseAvailableTags(params.availableTags)
};
}
function readDiscordChannelMoveParams(params) {
const parentId = readDiscordParentIdParam(params);
return {
guildId: readStringParam(params, "guildId", { required: true }),
channelId: readStringParam(params, "channelId", { required: true }),
parentId: parentId === void 0 ? void 0 : parentId,
position: readNonNegativeIntegerParam(params, "position") ?? void 0
};
}
//#endregion
//#region extensions/discord/src/actions/runtime.guild.ts
const discordGuildActionRuntime = {
addRoleDiscord,
canManageGuildRoleDiscord,
canManageGuildMemberRoleDiscord,
createChannelDiscord,
createScheduledEventDiscord,
resolveEventCoverImage,
deleteChannelDiscord,
editChannelDiscord,
fetchChannelInfoDiscord,
fetchMemberInfoDiscord,
hasAnyChannelPermissionDiscord,
hasAnyGuildPermissionDiscord,
fetchRoleInfoDiscord,
fetchVoiceStatusDiscord,
listGuildChannelsDiscord,
listGuildEmojisDiscord,
listScheduledEventsDiscord,
moveChannelDiscord,
removeChannelPermissionDiscord,
removeRoleDiscord,
setChannelPermissionDiscord,
uploadEmojiDiscord,
uploadStickerDiscord
};
const expressionPermissions = [
PermissionFlagsBits.ManageGuildExpressions,
PermissionFlagsBits.CreateGuildExpressions,
PermissionFlagsBits.ManageEmojisAndStickers
];
const channelGuard = {
gate: "channels",
disabledMessage: "Discord channel management is disabled.",
permissions: [PermissionFlagsBits.ManageChannels]
};
const existingChannelGuard = {
...channelGuard,
permissionScope: "channel"
};
const channelPermissionGuard = {
...channelGuard,
permissions: [PermissionFlagsBits.ManageRoles],
permissionScope: "channel"
};
const guildAdminActionGuards = {
emojiUpload: {
gate: "emojiUploads",
disabledMessage: "Discord emoji uploads are disabled.",
permissions: expressionPermissions
},
stickerUpload: {
gate: "stickerUploads",
disabledMessage: "Discord sticker uploads are disabled.",
permissions: expressionPermissions
},
roleAdd: {
gate: "roles",
defaultEnabled: false,
disabledMessage: "Discord role changes are disabled.",
permissions: [PermissionFlagsBits.ManageRoles]
},
roleRemove: {
gate: "roles",
defaultEnabled: false,
disabledMessage: "Discord role changes are disabled.",
permissions: [PermissionFlagsBits.ManageRoles]
},
eventCreate: {
gate: "events",
disabledMessage: "Discord events are disabled.",
permissions: [PermissionFlagsBits.ManageEvents, PermissionFlagsBits.CreateEvents]
},
channelCreate: channelGuard,
channelEdit: existingChannelGuard,
channelDelete: existingChannelGuard,
channelMove: existingChannelGuard,
categoryCreate: channelGuard,
categoryEdit: existingChannelGuard,
categoryDelete: existingChannelGuard,
channelPermissionSet: channelPermissionGuard,
channelPermissionRemove: channelPermissionGuard
};
function isThreadChannelType(channelType) {
return channelType === ChannelType.GuildNewsThread || channelType === ChannelType.GuildPublicThread || channelType === ChannelType.GuildPrivateThread;
}
function isLockedThreadChannel(channel) {
if (!channel || typeof channel !== "object") return false;
return channel.thread_metadata?.locked === true;
}
function assertGuildAdminActionEnabled(action, isActionEnabled) {
const guard = guildAdminActionGuards[action];
if (guard && !isActionEnabled(guard.gate, guard.defaultEnabled)) throw new Error(guard.disabledMessage);
}
async function resolveGuildIdForGuildAdminAction(params) {
const guildId = readStringParam(params.values, "guildId");
if (guildId) return guildId;
const channelLikeId = readStringParam(params.values, "channelId") ?? readStringParam(params.values, "categoryId");
if (!channelLikeId) return;
const channel = await discordGuildActionRuntime.fetchChannelInfoDiscord(channelLikeId, createDiscordActionOptions({
cfg: params.cfg,
accountId: params.accountId
}));
return "guild_id" in channel ? channel.guild_id ?? void 0 : void 0;
}
function readChannelScopedPermissionTargetId(action, values) {
if (action === "eventCreate") return readStringParam(values, "channelId");
if (action === "categoryEdit" || action === "categoryDelete") return readStringParam(values, "categoryId");
return readStringParam(values, "channelId");
}
async function resolveGuildAdminActionPermissions(params) {
if (params.action !== "channelEdit") return params.guard.permissions;
const channelId = readStringParam(params.values, "channelId");
if (!channelId) return params.guard.permissions;
const channel = await discordGuildActionRuntime.fetchChannelInfoDiscord(channelId, createDiscordActionOptions({
cfg: params.cfg,
accountId: params.accountId
}));
if (!isThreadChannelType("type" in channel ? channel.type : void 0)) return params.guard.permissions;
return params.values.archived === false && !("name" in params.values) && !("topic" in params.values) && !("position" in params.values) && !("parentId" in params.values) && !("clearParent" in params.values) && !("nsfw" in params.values) && !("rateLimitPerUser" in params.values) && !("locked" in params.values) && !("autoArchiveDuration" in params.values) && !isLockedThreadChannel(channel) ? [PermissionFlagsBits.ManageThreads, PermissionFlagsBits.SendMessagesInThreads] : [PermissionFlagsBits.ManageThreads];
}
async function verifySenderGuildAdminPermission(params) {
const guard = guildAdminActionGuards[params.action];
const senderUserId = readStringParam(params.values, "senderUserId");
if (!guard?.permissions.length || !senderUserId) return;
const requiredPermissions = await resolveGuildAdminActionPermissions({
...params,
guard
});
const guildId = await resolveGuildIdForGuildAdminAction(params);
if (!guildId) throw new Error(`Guild id required to authorize Discord guild action: ${params.action}`);
const actionOptions = createDiscordActionOptions({
cfg: params.cfg,
accountId: params.accountId
});
const targetChannelId = guard?.permissionScope === "channel" || params.action === "eventCreate" ? readChannelScopedPermissionTargetId(params.action, params.values) : void 0;
if (!(targetChannelId ? await discordGuildActionRuntime.hasAnyChannelPermissionDiscord(guildId, targetChannelId, senderUserId, requiredPermissions, actionOptions) : await discordGuildActionRuntime.hasAnyGuildPermissionDiscord(guildId, senderUserId, requiredPermissions, actionOptions))) throw new Error("Sender does not have required permissions for this guild action.");
if (params.action === "roleAdd" || params.action === "roleRemove") {
const targetUserId = readStringParam(params.values, "userId", { required: true });
const roleId = readStringParam(params.values, "roleId", { required: true });
if (!await discordGuildActionRuntime.canManageGuildMemberRoleDiscord(guildId, senderUserId, targetUserId, roleId, actionOptions, { assignablePermissionCeiling: params.action === "roleAdd" })) throw new Error("Sender cannot manage the requested role or member.");
}
if (params.action === "channelPermissionSet" || params.action === "channelPermissionRemove") {
const targetType = readStringParam(params.values, "targetType");
if (targetType === "member") return;
const targetId = readStringParam(params.values, "targetId", { required: true });
const canManageRole = await discordGuildActionRuntime.canManageGuildRoleDiscord(guildId, senderUserId, targetId, actionOptions);
if (canManageRole === false || targetType === "role" && canManageRole === null) throw new Error("Sender cannot manage the requested role overwrite.");
}
}
async function runRoleMutation(params) {
const guildId = readStringParam(params.values, "guildId", { required: true });
const userId = readStringParam(params.values, "userId", { required: true });
const roleId = readStringParam(params.values, "roleId", { required: true });
await params.mutate({
guildId,
userId,
roleId
}, createDiscordActionOptions({
cfg: params.cfg,
accountId: params.accountId
}));
}
function readChannelPermissionTarget(params) {
return {
channelId: readStringParam(params, "channelId", { required: true }),
targetId: readStringParam(params, "targetId", { required: true })
};
}
async function handleDiscordGuildAction(action, params, isActionEnabled, cfg, options) {
const accountId = readStringParam(params, "accountId");
if (!cfg) throw new Error("Discord guild actions require a resolved runtime config.");
assertGuildAdminActionEnabled(action, isActionEnabled);
await verifySenderGuildAdminPermission({
action,
values: params,
accountId,
cfg
});
const withOpts = (extra) => createDiscordActionOptions({
cfg,
accountId,
extra
});
switch (action) {
case "memberInfo": {
if (!isActionEnabled("memberInfo")) throw new Error("Discord member info is disabled.");
const guildId = readStringParam(params, "guildId", { required: true });
const userId = readStringParam(params, "userId", { required: true });
const effectiveAccountId = accountId ?? resolveDefaultDiscordAccountId(cfg);
const member = await discordGuildActionRuntime.fetchMemberInfoDiscord(guildId, userId, createDiscordActionOptions({
cfg,
accountId: effectiveAccountId
}));
const presence = getPresence(effectiveAccountId, userId);
const activities = presence?.activities ?? void 0;
const status = presence?.status ?? void 0;
return jsonResult({
ok: true,
member,
...presence ? {
status,
activities
} : {}
});
}
case "roleInfo": {
if (!isActionEnabled("roleInfo")) throw new Error("Discord role info is disabled.");
const guildId = readStringParam(params, "guildId", { required: true });
return jsonResult({
ok: true,
roles: await discordGuildActionRuntime.fetchRoleInfoDiscord(guildId, withOpts())
});
}
case "emojiList": {
if (!isActionEnabled("reactions")) throw new Error("Discord reactions are disabled.");
const guildId = readStringParam(params, "guildId", { required: true });
return jsonResult({
ok: true,
emojis: await discordGuildActionRuntime.listGuildEmojisDiscord(guildId, withOpts())
});
}
case "emojiUpload": {
if (!isActionEnabled("emojiUploads")) throw new Error("Discord emoji uploads are disabled.");
const guildId = readStringParam(params, "guildId", { required: true });
const name = readStringParam(params, "name", { required: true });
const mediaUrl = readStringParam(params, "mediaUrl", { required: true });
const roleIds = readStringArrayParam(params, "roleIds");
return jsonResult({
ok: true,
emoji: await discordGuildActionRuntime.uploadEmojiDiscord({
guildId,
name,
mediaUrl,
roleIds: roleIds?.length ? roleIds : void 0
}, withOpts())
});
}
case "stickerUpload": {
if (!isActionEnabled("stickerUploads")) throw new Error("Discord sticker uploads are disabled.");
const guildId = readStringParam(params, "guildId", { required: true });
const name = readStringParam(params, "name", { required: true });
const description = readStringParam(params, "description", { required: true });
const tags = readStringParam(params, "tags", { required: true });
const mediaUrl = readStringParam(params, "mediaUrl", { required: true });
return jsonResult({
ok: true,
sticker: await discordGuildActionRuntime.uploadStickerDiscord({
guildId,
name,
description,
tags,
mediaUrl
}, withOpts())
});
}
case "roleAdd":
if (!isActionEnabled("roles", false)) throw new Error("Discord role changes are disabled.");
await runRoleMutation({
cfg,
accountId,
values: params,
mutate: discordGuildActionRuntime.addRoleDiscord
});
return jsonResult({ ok: true });
case "roleRemove":
if (!isActionEnabled("roles", false)) throw new Error("Discord role changes are disabled.");
await runRoleMutation({
cfg,
accountId,
values: params,
mutate: discordGuildActionRuntime.removeRoleDiscord
});
return jsonResult({ ok: true });
case "channelInfo": {
if (!isActionEnabled("channelInfo")) throw new Error("Discord channel info is disabled.");
const channelId = readStringParam(params, "channelId", { required: true });
return jsonResult({
ok: true,
channel: await discordGuildActionRuntime.fetchChannelInfoDiscord(channelId, withOpts())
});
}
case "channelList": {
if (!isActionEnabled("channelInfo")) throw new Error("Discord channel info is disabled.");
const guildId = readStringParam(params, "guildId", { required: true });
return jsonResult({
ok: true,
channels: await discordGuildActionRuntime.listGuildChannelsDiscord(guildId, withOpts())
});
}
case "voiceStatus": {
if (!isActionEnabled("voiceStatus")) throw new Error("Discord voice status is disabled.");
const guildId = readStringParam(params, "guildId", { required: true });
const userId = readStringParam(params, "userId", { required: true });
return jsonResult({
ok: true,
voice: await discordGuildActionRuntime.fetchVoiceStatusDiscord(guildId, userId, withOpts())
});
}
case "eventList": {
if (!isActionEnabled("events")) throw new Error("Discord events are disabled.");
const guildId = readStringParam(params, "guildId", { required: true });
return jsonResult({
ok: true,
events: await discordGuildActionRuntime.listScheduledEventsDiscord(guildId, withOpts())
});
}
case "eventCreate": {
if (!isActionEnabled("events")) throw new Error("Discord events are disabled.");
const guildId = readStringParam(params, "guildId", { required: true });
const name = readStringParam(params, "name", { required: true });
const startTime = readStringParam(params, "startTime", { required: true });
const endTime = readStringParam(params, "endTime");
const description = readStringParam(params, "description");
const channelId = readStringParam(params, "channelId");
const location = readStringParam(params, "location");
const imageUrl = readStringParam(params, "image", { trim: false });
const entityTypeRaw = readStringParam(params, "entityType");
const entityType = entityTypeRaw === "stage" ? 1 : entityTypeRaw === "external" ? 3 : 2;
const image = imageUrl ? await discordGuildActionRuntime.resolveEventCoverImage(imageUrl, { localRoots: options?.mediaLocalRoots }) : void 0;
const payload = {
name,
description,
scheduled_start_time: startTime,
scheduled_end_time: endTime,
entity_type: entityType,
channel_id: channelId,
entity_metadata: entityType === 3 && location ? { location } : void 0,
image,
privacy_level: 2
};
return jsonResult({
ok: true,
event: await discordGuildActionRuntime.createScheduledEventDiscord(guildId, payload, withOpts())
});
}
case "channelCreate":
if (!isActionEnabled("channels")) throw new Error("Discord channel management is disabled.");
return jsonResult({
ok: true,
channel: await discordGuildActionRuntime.createChannelDiscord(readDiscordChannelCreateParams(params), withOpts())
});
case "channelEdit":
if (!isActionEnabled("channels")) throw new Error("Discord channel management is disabled.");
return jsonResult({
ok: true,
channel: await discordGuildActionRuntime.editChannelDiscord(readDiscordChannelEditParams(params), withOpts())
});
case "channelDelete": {
if (!isActionEnabled("channels")) throw new Error("Discord channel management is disabled.");
const channelId = readStringParam(params, "channelId", { required: true });
return jsonResult(await discordGuildActionRuntime.deleteChannelDiscord(channelId, withOpts()));
}
case "channelMove":
if (!isActionEnabled("channels")) throw new Error("Discord channel management is disabled.");
await discordGuildActionRuntime.moveChannelDiscord(readDiscordChannelMoveParams(params), withOpts());
return jsonResult({ ok: true });
case "categoryCreate": {
if (!isActionEnabled("channels")) throw new Error("Discord channel management is disabled.");
const guildId = readStringParam(params, "guildId", { required: true });
const name = readStringParam(params, "name", { required: true });
const position = readNonNegativeIntegerParam(params, "position");
return jsonResult({
ok: true,
category: await discordGuildActionRuntime.createChannelDiscord({
guildId,
name,
type: 4,
position: position ?? void 0
}, withOpts())
});
}
case "categoryEdit": {
if (!isActionEnabled("channels")) throw new Error("Discord channel management is disabled.");
const categoryId = readStringParam(params, "categoryId", { required: true });
const name = readStringParam(params, "name");
const position = readNonNegativeIntegerParam(params, "position");
return jsonResult({
ok: true,
category: await discordGuildActionRuntime.editChannelDiscord({
channelId: categoryId,
name: name ?? void 0,
position: position ?? void 0
}, withOpts())
});
}
case "categoryDelete": {
if (!isActionEnabled("channels")) throw new Error("Discord channel management is disabled.");
const categoryId = readStringParam(params, "categoryId", { required: true });
return jsonResult(await discordGuildActionRuntime.deleteChannelDiscord(categoryId, withOpts()));
}
case "channelPermissionSet": {
if (!isActionEnabled("channels")) throw new Error("Discord channel management is disabled.");
const { channelId, targetId } = readChannelPermissionTarget(params);
const targetType = readStringParam(params, "targetType", { required: true }) === "member" ? 1 : 0;
const allow = readStringParam(params, "allow");
const deny = readStringParam(params, "deny");
await discordGuildActionRuntime.setChannelPermissionDiscord({
channelId,
targetId,
targetType,
allow: allow ?? void 0,
deny: deny ?? void 0
}, withOpts());
return jsonResult({ ok: true });
}
case "channelPermissionRemove": {
if (!isActionEnabled("channels")) throw new Error("Discord channel management is disabled.");
const { channelId, targetId } = readChannelPermissionTarget(params);
await discordGuildActionRuntime.removeChannelPermissionDiscord(channelId, targetId, withOpts());
return jsonResult({ ok: true });
}
default: throw new Error(`Unknown action: ${action}`);
}
}
//#endregion
//#region extensions/discord/src/actions/runtime.messaging.runtime.ts
const discordMessagingActionRuntime = {
createThreadDiscord,
deleteMessageDiscord,
editChannelDiscord,
editMessageDiscord,
fetchChannelInfoDiscord,
fetchGuildInfoDiscord,
fetchChannelPermissionsDiscord,
fetchMessageDiscord,
fetchReactionsDiscord,
listPinsDiscord,
listThreadsDiscord,
pinMessageDiscord,
reactMessageDiscord,
readDiscordComponentSpec,
readMessagesDiscord,
removeOwnReactionsDiscord,
removeReactionDiscord,
resolveDiscordReactionTargetChannelId,
resolveDiscordChannelId,
searchMessagesDiscord,
sendDiscordComponentMessage,
sendMessageDiscord,
sendPollDiscord,
sendStickerDiscord,
sendVoiceMessageDiscord,
unpinMessageDiscord
};
async function resolveDiscordReactionTargetChannelId(params) {
try {
return resolveDiscordChannelId(params.target);
} catch {
return (await resolveDiscordTargetChannelId(params.target, {
cfg: params.cfg,
accountId: params.accountId
})).channelId;
}
}
//#endregion
//#region extensions/discord/src/actions/runtime.messaging.messages.ts
function parseDiscordMessageLink(link) {
const match = link.trim().match(/^(?:https?:\/\/)?(?:ptb\.|canary\.)?discord(?:app)?\.com\/channels\/(\d+)\/(\d+)\/(\d+)(?:\/?|\?.*)$/i);
if (!match) throw new Error("Invalid Discord message link. Expected https://discord.com/channels/<guildId>/<channelId>/<messageId>.");
return {
guildId: match[1],
channelId: match[2],
messageId: match[3]
};
}
function describeDiscordMessageListResult(value) {
if (Array.isArray(value)) return "array";
if (value === null) return "null";
if (value && typeof value === "object") {
const keys = Object.keys(value).toSorted();
return keys.length ? `object with keys ${keys.join(", ")}` : "object";
}
return typeof value;
}
function assertDiscordMessageListResult(value) {
if (Array.isArray(value)) return value;
throw new Error(`Discord message read returned ${describeDiscordMessageListResult(value)} instead of an array.`);
}
async function handleDiscordMessageManagementAction(ctx) {
switch (ctx.action) {
case "permissions": {
if (!ctx.isActionEnabled("permissions")) throw new Error("Discord permissions are disabled.");
const channelId = ctx.resolveChannelId();
await ctx.assertReadTargetAllowed({ channelId });
return jsonResult({
ok: true,
permissions: await discordMessagingActionRuntime.fetchChannelPermissionsDiscord(channelId, ctx.withOpts())
});
}
case "fetchMessage": {
if (!ctx.isActionEnabled("messages")) throw new Error("Discord message reads are disabled.");
const messageLink = readStringParam(ctx.params, "messageLink");
let guildId = readStringParam(ctx.params, "guildId");
let channelId = readStringParam(ctx.params, "channelId");
let messageId = readStringParam(ctx.params, "messageId");
if (messageLink) {
const parsed = parseDiscordMessageLink(messageLink);
guildId = parsed.guildId;
channelId = parsed.channelId;
messageId = parsed.messageId;
}
if (!guildId || !channelId || !messageId) throw new Error("Discord message fetch requires guildId, channelId, and messageId (or a valid messageLink).");
await ctx.assertReadTargetAllowed({
guildId,
channelId
});
const message = await discordMessagingActionRuntime.fetchMessageDiscord(channelId, messageId, ctx.withOpts());
return jsonResult({
ok: true,
message: ctx.normalizeMessage(message),
guildId,
channelId,
messageId
});
}
case "readMessages": {
if (!ctx.isActionEnabled("messages")) throw new Error("Discord message reads are disabled.");
const channelId = ctx.resolveChannelId();
await ctx.assertReadTargetAllowed({ channelId });
const query = {
limit: readPositiveIntegerParam(ctx.params, "limit"),
before: readStringParam(ctx.params, "before"),
after: readStringParam(ctx.params, "after"),
around: readStringParam(ctx.params, "around")
};
return jsonResult({
ok: true,
messages: assertDiscordMessageListResult(await discordMessagingActionRuntime.readMessagesDiscord(channelId, query, ctx.withOpts())).map((message) => ctx.normalizeMessage(message))
});
}
case "editMessage": {
if (!ctx.isActionEnabled("messages")) throw new Error("Discord message edits are disabled.");
const channelId = ctx.resolveChannelId();
const messageId = readStringParam(ctx.params, "messageId", { required: true });
const content = readStringParam(ctx.params, "content", { required: true });
return jsonResult({
ok: true,
message: await discordMessagingActionRuntime.editMessageDiscord(channelId, messageId, { content }, ctx.withOpts())
});
}
case "deleteMessage": {
if (!ctx.isActionEnabled("messages")) throw new Error("Discord message deletes are disabled.");
const channelId = ctx.resolveChannelId();
const messageId = readStringParam(ctx.params, "messageId", { required: true });
await discordMessagingActionRuntime.deleteMessageDiscord(channelId, messageId, ctx.withOpts());
return jsonResult({ ok: true });
}
case "pinMessage": {
if (!ctx.isActionEnabled("pins")) throw new Error("Discord pins are disabled.");
const channelId = ctx.resolveChannelId();
const messageId = readStringParam(ctx.params, "messageId", { required: true });
await discordMessagingActionRuntime.pinMessageDiscord(channelId, messageId, ctx.withOpts());
return jsonResult({ ok: true });
}
case "unpinMessage": {
if (!ctx.isActionEnabled("pins")) throw new Error("Discord pins are disabled.");
const channelId = ctx.resolveChannelId();
const messageId = readStringParam(ctx.params, "messageId", { required: true });
await discordMessagingActionRuntime.unpinMessageDiscord(channelId, messageId, ctx.withOpts());
return jsonResult({ ok: true });
}
case "listPins": {
if (!ctx.isActionEnabled("pins")) throw new Error("Discord pins are disabled.");
const channelId = ctx.resolveChannelId();
await ctx.assertReadTargetAllowed({ channelId });
return jsonResult({
ok: true,
pins: (await discordMessagingActionRuntime.listPinsDiscord(channelId, ctx.withOpts())).map((pin) => ctx.normalizeMessage(pin))
});
}
case "searchMessages": {
if (!ctx.isActionEnabled("search")) throw new Error("Discord search is disabled.");
const guildId = readStringParam(ctx.params, "guildId", { required: true });
const content = readStringParam(ctx.params, "content", { required: true });
const channelId = readStringParam(ctx.params, "channelId");
const channelIds = readStringArrayParam(ctx.params, "channelIds");
const authorId = readStringParam(ctx.params, "authorId");
const authorIds = readStringArrayParam(ctx.params, "authorIds");
const limit = readPositiveIntegerParam(ctx.params, "limit");
const channelIdList = [...channelIds ?? [], ...channelId ? [channelId] : []];
if (channelIdList.length > 0) for (const targetChannelId of channelIdList) await ctx.assertReadTargetAllowed({
guildId,
channelId: targetChannelId
});
else await ctx.assertGuildReadTargetAllowed({ guildId });
const authorIdList = [...authorIds ?? [], ...authorId ? [authorId] : []];
const results = await discordMessagingActionRuntime.searchMessagesDiscord({
guildId,
content,
channelIds: channelIdList.length ? channelIdList : void 0,
authorIds: authorIdList.length ? authorIdList : void 0,
limit
}, ctx.withOpts());
if (!results || typeof results !== "object") return jsonResult({
ok: true,
results
});
const resultsRecord = results;
const messages = resultsRecord.messages;
const normalizedMessages = Array.isArray(messages) ? messages.map((group) => Array.isArray(group) ? group.map((msg) => ctx.normalizeMessage(msg)) : group) : messages;
return jsonResult({
ok: true,
results: {
...resultsRecord,
messages: normalizedMessages
}
});
}
default: return;
}
}
//#endregion
//#region extensions/discord/src/actions/runtime.messaging.reactions.ts
async function handleDiscordReactionMessagingAction(ctx) {
switch (ctx.action) {
case "react": {
if (!ctx.isActionEnabled("reactions")) throw new Error("Discord reactions are disabled.");
const channelId = await ctx.resolveReactionChannelId();
const messageId = readStringParam(ctx.params, "messageId", { required: true });
const { emoji, remove, isEmpty } = readReactionParams(ctx.params, { removeErrorMessage: "Emoji is required to remove a Discord reaction." });
if (remove) {
await discordMessagingActionRuntime.removeReactionDiscord(channelId, messageId, emoji, ctx.withReactionRuntimeOptions());
return jsonResult({
ok: true,
removed: emoji
});
}
if (isEmpty) return jsonResult({
ok: true,
removed: (await discordMessagingActionRuntime.removeOwnReactionsDiscord(channelId, messageId, ctx.withReactionRuntimeOptions())).removed
});
await discordMessagingActionRuntime.reactMessageDiscord(channelId, messageId, emoji, ctx.withReactionRuntimeOptions());
return jsonResult({
ok: true,
added: emoji
});
}
case "reactions": {
if (!ctx.isActionEnabled("reactions")) throw new Error("Discord reactions are disabled.");
const channelId = await ctx.resolveReactionChannelId();
const messageId = readStringParam(ctx.params, "messageId", { required: true });
const limit = readPositiveIntegerParam(ctx.params, "limit");
await ctx.assertReadTargetAllowed({ channelId });
return jsonResult({
ok: true,
reactions: await discordMessagingActionRuntime.fetchReactionsDiscord(channelId, messageId, ctx.withReactionRuntimeOptions({ limit }))
});
}
default: return;
}
}
//#endregion
//#region extensions/discord/src/actions/runtime.messaging.send.ts
function hasDiscordComponentObjectKeys(value) {
return Boolean(value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length > 0);
}
async function appendDiscordThreadRenameResult(ctx, params) {
const threadName = params.threadName?.trim();
if (!threadName) return params.payload;
if (!ctx.isActionEnabled("channels")) return {
...params.payload,
warning: "Discord threadName was ignored because Discord channel management is disabled."
};
let channelId;
try {
channelId = discordMessagingActionRuntime.resolveDiscordChannelId(params.target);
} catch {
return {
...params.payload,
warning: "Discord threadName was ignored because the send target is not a channel/thread."
};
}
try {
if (!isThreadChannelType$1((await discordMessagingActionRuntime.fetchChannelInfoDiscord(channelId, ctx.withOpts())).type)) return {
...params.payload,
warning: "Discord threadName was ignored because the send target is not a thread."
};
const renamed = await discordMessagingActionRuntime.editChannelDiscord({
channelId,
name: threadName
}, ctx.withOpts());
return {
...params.payload,
threadRename: {
ok: true,
channelId,
name: renamed.name ?? threadName
}
};
} catch (error) {
return {
...params.payload,
warning: `Discord message was sent, but thread rename failed: ${formatErrorMessage(error)}`
};
}
}
async function handleDiscordMessageSendAction(ctx) {
switch (ctx.action) {
case "sticker": {
if (!ctx.isActionEnabled("stickers")) throw new Error("Discord stickers are disabled.");
const to = readStringParam(ctx.params, "to", { required: true });
const content = readStringParam(ctx.params, "content");
const stickerIds = readStringArrayParam(ctx.params, "stickerIds", {
required: true,
label: "stickerIds"
});
await discordMessagingActionRuntime.sendStickerDiscord(to, stickerIds, ctx.withOpts({ content }));
return jsonResult({ ok: true });
}
case "poll": {
if (!ctx.isActionEnabled("polls")) throw new Error("Discord polls are disabled.");
const to = readStringParam(ctx.params, "to", { required: true });
const content = readStringParam(ctx.params, "content");
const question = readStringParam(ctx.params, "question", { required: true });
const answers = readStringArrayParam(ctx.params, "answers", {
required: true,
label: "answers"
});
const allowMultiselect = readBooleanParam(ctx.params, "allowMultiselect");
const durationHours = readPositiveIntegerParam(ctx.params, "durationHours");
const maxSelections = resolvePollMaxSelections(answers.length, allowMultiselect);
await discordMessagingActionRuntime.sendPollDiscord(to, {
question,
options: answers,
maxSelections,
durationHours
}, ctx.withOpts({ content }));
return jsonResult({ ok: true });
}
case "sendMessage": {
if (!ctx.isActionEnabled("messages")) throw new Error("Discord message sends are disabled.");
const to = readStringParam(ctx.params, "to", { required: true });
const asVoice = ctx.params.asVoice === true;
const silent = ctx.params.silent === true;
const suppressEmbeds = ctx.params.suppressEmbeds === void 0 ? void 0 : ctx.params.suppressEmbeds === true;
const rawComponents = ctx.params.components;
const componentSpec = hasDiscordComponentObjectKeys(rawComponents) ? discordMessagingActionRuntime.readDiscordComponentSpec(rawComponents) : null;
const components = Array.isArray(rawComponents) || typeof rawComponents === "function" ? rawComponents : void 0;
const mediaUrl = readStringParam(ctx.params, "mediaUrl", { trim: false }) ?? readStringParam(ctx.params, "path", { trim: false }) ?? readStringParam(ctx.params, "filePath", { trim: false });
const content = readStringParam(ctx.params, "content", {
required: !asVoice && !componentSpec && !components && !mediaUrl,
allowEmpty: true
});
const filename = readStringParam(ctx.params, "filename");
const replyTo = readStringParam(ctx.params, "replyTo");
const threadName = readStringParam(ctx.params, "threadName");
const rawEmbeds = ctx.params.embeds;
const embeds = Array.isArray(rawEmbeds) ? rawEmbeds : void 0;
const sessionKey = readStringParam(ctx.params, "__sessionKey");
const agentId = readStringParam(ctx.params, "__agentId");
if (componentSpec) {
if (asVoice) throw new Error("Discord components cannot be sent as voice messages.");
if (embeds?.length) throw new Error("Discord components cannot include embeds.");
const normalizedContent = content?.trim() ? content : void 0;
const payload = componentSpec.text ? componentSpec : {
...componentSpec,
text: normalizedContent
};
return jsonResult(await appendDiscordThreadRenameResult(ctx, {
payload: {
ok: true,
result: await discordMessagingActionRuntime.sendDiscordComponentMessage(to, payload, {
...ctx.withOpts(),
silent,
replyTo: replyTo ?? void 0,
sessionKey: sessionKey ?? void 0,
agentId: agentId ?? void 0,
mediaUrl: mediaUrl ?? void 0,
filename: filename ?? void 0,
mediaAccess: ctx.options?.mediaAccess,
mediaLocalRoots: ctx.options?.mediaLocalRoots,
mediaReadFile: ctx.options?.mediaReadFile,
...suppressEmbeds === void 0 ? {} : { suppressEmbeds }
}),
components: true
},
target: to,
threadName
}));
}
if (asVoice) {
if (!mediaUrl) throw new Error("Voice messages require a media file reference (mediaUrl, path, or filePath).");
if (content && content.trim()) throw new Error("Voice messages cannot include text content (Discord limitation). Remove the content parameter.");
assertMediaNotDataUrl(mediaUrl);
return jsonResult(await appendDiscordThreadRenameResult(ctx, {
payload: {
ok: true,
result: await discordMessagingActionRuntime.sendVoiceMessageDiscord(to, mediaUrl, {
...ctx.withOpts(),
replyTo,
silent
}),
voiceMessage: true
},
target: to,
threadName
}));
}
return jsonResult(await appendDiscordThreadRenameResult(ctx, {
payload: {
ok: true,
result: await discordMessagingActionRuntime.sendMessageDiscord(to, content ?? "", {
...ctx.withOpts(),
mediaAccess: ctx.options?.mediaAccess,
mediaUrl,
filename: filename ?? void 0,
mediaLocalRoots: ctx.options?.mediaLocalRoots,
mediaReadFile: ctx.options?.mediaReadFile,
replyTo,
components,
embeds,
silent,
...suppressEmbeds === void 0 ? {} : { suppressEmbeds }
})
},
target: to,
threadName
}));
}
case "threadCreate": {
if (!ctx.isActionEnabled("threads")) throw new Error("Discord threads are disabled.");
const channelId = ctx.resolveChannelId();
const name = readStringParam(ctx.params, "name", { required: true });
const messageId = readStringParam(ctx.params, "messageId");
const content = readStringParam(ctx.params, "content");
const payload = {
name,
messageId,
autoArchiveMinutes: readPositiveIntegerParam(ctx.params, "autoArchiveMinutes"),
content,
appliedTags: readStringArrayParam(ctx.params, "appliedTags") ?? void 0
};
try {
return jsonResult({
ok: true,
thread: await discordMessagingActionRuntime.createThreadDiscord(channelId, payload, ctx.withOpts())
});
} catch (error) {
if (error instanceof DiscordThreadInitialMessageError) return jsonResult({
ok: true,
partial: true,
thread: error.thread,
warning: "Discord thread was created, but sending the initial message failed.",
initialMessageError: error.initialMessageError
});
throw error;
}
}
case "threadList": {
if (!ctx.isActionEnabled("threads")) throw new Error("Discord threads are disabled.");
const guildId = readStringParam(ctx.params, "guildId", { required: true });
const channelId = readStringParam(ctx.params, "channelId");
const includeArchived = readBooleanParam(ctx.params, "includeArchived");
const before = readStringParam(ctx.params, "before");
const limit = readPositiveIntegerParam(ctx.params, "limit");
return jsonResult({
ok: true,
threads: await discordMessagingActionRuntime.listThreadsDiscord({
guildId,
channelId,
includeArchived,
before,
limit
}, ctx.withOpts())
});
}
case "threadReply": {
if (!ctx.isActionEnabled("threads")) throw new Error("Discord threads are disabled.");
const channelId = ctx.resolveChannelId();
const content = readStringParam(ctx.params, "content", { required: true });
const mediaUrl = readStringParam(ctx.params, "mediaUrl");
const replyTo = readStringParam(ctx.params, "replyTo");
return jsonResult({
ok: true,
result: await discordMessagingActionRuntime.sendMessageDiscord(`channel:${channelId}`, content, {
...ctx.withOpts(),
mediaUrl,
mediaLocalRoots: ctx.options?.mediaLocalRoots,
mediaReadFile: ctx.options?.mediaReadFile,
replyTo
})
});
}
default: return;
}
}
//#endregion
//#region extensions/discord/src/actions/runtime.messaging.shared.ts
function hasDiscordGuildEntries(guilds) {
return Boolean(guilds && Object.keys(guilds).length > 0);
}
function allowsAllDiscordGuildChannels(channels) {
const wildcard = channels?.["*"];
if (!wildcard || wildcard.enabled === false) return false;
return Object.values(channels ?? {}).every((entry) => entry?.enabled !== false);
}
function resolveDiscordActionGuildEntry(params) {
const guildId = params.guildId?.trim();
if (!params.guilds) return null;
if (guildId && params.guilds[guildId]) return {
...params.guilds[guildId],
id: guildId
};
if (guildId) {
const byConfiguredId = Object.values(params.guilds).find((guild) => guild?.id === guildId);
if (byConfiguredId) return {
...byConfiguredId,
id: guildId
};
}
const guildSlug = params.guildName ? normalizeDiscordSlug(params.guildName) : "";
if (guildSlug) {
const bySlug = params.guilds[guildSlug] ?? Object.values(params.guilds).find((guild) => guild?.slug === guildSlug);
if (bySlug) return {
...bySlug,
id: guildId,
slug: guildSlug || bySlug.slug
};
}
if (params.includeWildcard === false) return null;
const wildcard = params.guilds["*"];
return wildcard ? {
...wildcard,
id: guildId
} : null;
}
function readDiscordChannelStringField(value, ...keys) {
if (!value || typeof value !== "object") return;
const record = value;
for (const key of keys) {
const candidate = record[key];
if (typeof candidate === "string" && candidate.trim()) return candidate.trim();
}
}
function readDiscordChannelType(value) {
if (!value || typeof value !== "object") return;
const type = value.type;
return typeof type === "number" ? type : void 0;
}
function isDiscordThreadChannel(value) {
const type = readDiscordChannelType(value);
return type === 10 || type === 11 || type === 12;
}
function isDiscordReadTargetAllowedInGuild(params) {
if (resolveDiscordChannelConfigWithFallback({
guildInfo: params.guildInfo,
channelId: params.target.channelId,
channelName: params.target.channelName,
channelSlug: params.target.channelSlug,
parentId: params.target.parentId,
parentName: params.target.parentName,
parentSlug: params.target.parentSlug,
scope: params.target.scope
})?.allowed === false) return false;
return isDiscordGroupAllowedByPolicy({
groupPolicy: params.groupPolicy,
guildAllowlisted: Boolean(params.guildInfo),
channelAllowlistConfigured: hasDiscordGuildEntries(params.guildInfo?.channels),
channelAllowed: true
});
}
function isDiscordReadTargetExplicitlyAllowedById(params) {
const channelEntry = params.guildInfo?.channels?.[params.target.channelId];
if (!channelEntry || channelEntry.enabled === false) return false;
return isDiscordGroupAllowedByPolicy({
groupPolicy: params.groupPolicy,
guildAllowlisted: Boolean(params.guildInfo),
channelAllowlistConfigured: true,
channelAllowed: true
});
}
function createDiscordMessagingActionContext(params) {
const accountId = readStringParam(params.input, "accountId");
const cfgOptions = { cfg: params.cfg };
const accountConfig = mergeDiscordAccountConfig(params.cfg, accountId ?? resolveDefaultDiscordAccountId(params.cfg));
const guilds = accountConfig.guilds;
const hasGuildEntries = Object.keys(guilds ?? {}).length > 0;
const { groupPolicy } = resolveOpenProviderRuntimeGroupPolicy({
providerConfigPresent: params.cfg.channels?.discord !== void 0,
groupPolicy: accountConfig.groupPolicy,
defaultGroupPolicy: params.cfg.channels?.defaults?.groupPolicy
});
const withOpts = (extra) => createDiscordActionOptions({
cfg: params.cfg,
accountId,
extra
});
const resolvedReactionAccountId = accountId ?? resolveDefaultDiscordAccountId(params.cfg);
const reactionRuntimeOptions = resolvedReactionAccountId ? createDiscordRuntimeAccountContext({
cfg: params.cfg,
accountId: resolvedReactionAccountId
}) : cfgOptions;
const guildNameById = /* @__PURE__ */ new Map();
const resolveGuildName = async (guildId) => {
if (guildNameById.has(guildId)) return guildNameById.get(guildId) ?? null;
try {
const guildName = readDiscordChannelStringField(await discordMessagingActionRuntime.fetchGuildInfoDiscord(guildId, withOpts()), "name") ?? null;
guildNameById.set(guildId, guildName);
return guildName;
} catch {
guildNameById.set(guildId, null);
return null;
}
};
const resolveReadGuildEntry = async (guildId) => {
const direct = resolveDiscordActionGuildEntry({
guilds,
guildId,
includeWildcard: false
});
if (direct || !guildId) return direct;
const named = resolveDiscordActionGuildEntry({
guilds,
guildId,
guildName: await resolveGuildName(guildId) ?? void 0,
includeWildcard: false
});
if (named) return named;
return resolveDiscordActionGuildEntry({
guilds,
guildId
});
};
const resolveReadTargetContext = async (channelId) => {
const fallback = {
channelId,
channelSlug: normalizeDiscordSlug(channelId) || channelId
};
let channelInfo;
try {
channelInfo = await discordMessagingActionRuntime.fetchChannelInfoDiscord(channelId, withOpts());
} catch {
return fallback;
}
const channelName = readDiscordChannelStringField(channelInfo, "name");
const target = {
channelId,
channelSlug: channelName ? normalizeDiscordSlug(channelName) : fallback.channelSlug
};
const targetGuildId = readDiscordChannelStringField(channelInfo, "guild_id", "guildId");
if (targetGuildId) target.guildId = targetGuildId;
if (channelName) target.channelName = channelName;
if (!isDiscordThreadChannel(channelInfo)) return target;
target.scope = "thread";
target.parentId = readDiscordChannelStringField(channelInfo, "parent_id", "parentId");
if (!target.parentId) return target;
try {
const parentName = readDiscordChannelStringField(await discordMessagingActionRuntime.fetchChannelInfoDiscord(target.parentId, withOpts()), "name");
if (parentName) {
target.parentName = parentName;
target.parentSlug = normalizeDiscordSlug(parentName);
}
} catch {}
return target;
};
return {
action: params.action,
params: params.input,
isActionEnabled: params.isActionEnabled,
cfg: params.cfg,
options: params.options,
accountId,
resolveChannelId: () => discordMessagingActionRuntime.resolveDiscordChannelId(readStringParam(params.input, "channelId", { required: true })),
assertReadTargetAllowed: async ({ guildId, channelId }) => {
const targetChannelId = discordMessagingActionRuntime.resolveDiscordChannelId(channelId);
if (!hasGuildEntries && groupPolicy !== "disabled" && groupPolicy !== "allowlist") return;
const target = await resolveReadTargetContext(targetChannelId);
if (guildId) {
if (target.guildId && target.guildId !== guildId) throw new Error("Discord read target channel is not allowed.");
if (!isDiscordReadTargetAllowedInGuild({
groupPolicy,
guildInfo: await resolveReadGuildEntry(guildId),
target
})) throw new Error("Discord read target channel is not allowed.");
return;
}
if (target.guildId) {
if (!isDiscordReadTargetAllowedInGuild({
groupPolicy,
guildInfo: await resolveReadGuildEntry(target.guildId),
target
})) throw new Error("Discord read target channel is not allowed.");
return;
}
if (!Object.values(guilds ?? {}).some((guildInfo) => isDiscordReadTargetExplicitlyAllowedById({
groupPolicy,
guildInfo: guildInfo ?? null,
target
}))) throw new Error("Discord read target channel is not allowed.");
},
assertGuildReadTargetAllowed: async ({ guildId }) => {
const guildInfo = await resolveReadGuildEntry(guildId);
if (!isDiscordGroupAllowedByPolicy({
groupPolicy,
guildAllowlisted: Boolean(guildInfo),
channelAllowlistConfigured: false,
channelAllowed: true
})) throw new Error("Discord read target channel is not allowed.");
if (hasDiscordGuildEntries(guildInfo?.channels) && !allowsAllDiscordGuildChannels(guildInfo.channels)) throw new Error("Discord message search requires channelId or channelIds so each read target can be authorized.");
},
resolveReactionChannelId: async () => {
const target = readStringParam(params.input, "channelId") ?? readStringParam(params.input, "to", { required: true });
return await discordMessagingActionRuntime.resolveDiscordReactionTargetChannelId({
target,
cfg: params.cfg,
accountId: resolvedReactionAccountId
});
},
withOpts,
withReactionRuntimeOptions: (extra) => ({
...reactionRuntimeOptions ?? cfgOptions,
...extra
}),
normalizeMessage: (message) => {
if (!message || typeof message !== "object") return message;
return withNormalizedTimestamp(message, message.timestamp);
}
};
}
//#endregion
//#region extensions/discord/src/actions/runtime.messaging.ts
async function handleDiscordMessagingAction(action, params, isActionEnabled, cfg, options) {
if (!cfg) throw new Error("Discord messaging actions require a resolved runtime config.");
const ctx = createDiscordMessagingActionContext({
action,
input: params,
isActionEnabled,
cfg,
options
});
return await handleDiscordReactionMessagingAction(ctx) ?? await handleDiscordMessageSendAction(ctx) ?? await handleDiscordMessageManagementAction(ctx) ?? (() => {
throw new Error(`Unknown action: ${action}`);
})();
}
//#endregion
//#region extensions/discord/src/actions/runtime.moderation-shared.ts
const moderationPermissions = {
timeout: PermissionFlagsBits.ModerateMembers,
kick: PermissionFlagsBits.KickMembers,
ban: PermissionFlagsBits.BanMembers
};
function isDiscordModerationAction(action) {
return action === "timeout" || action === "kick" || action === "ban";
}
function requiredGuildPermissionForModerationAction(action) {
return moderationPermissions[action];
}
function readDiscordModerationCommand(action, params) {
if (!isDiscordModerationAction(action)) throw new Error(`Unsupported Discord moderation action: ${action}`);
return {
action,
guildId: readStringParam(params, "guildId", { required: true }),
userId: readStringParam(params, "userId", { required: true }),
durationMinutes: readNonNegativeIntegerParam(params, "durationMinutes"),
until: readStringParam(params, "until"),
reason: readStringParam(params, "reason"),
deleteMessageDays: readNonNegativeIntegerParam(params, "deleteMessageDays", {
max: 7,
message: "deleteMessageDays must be an integer from 0 to 7"
})
};
}
//#endregion
//#region extensions/discord/src/actions/runtime.moderation.ts
const discordModerationActionRuntime = {
banMemberDiscord,
hasAnyGuildPermissionDiscord,
kickMemberDiscord,
timeoutMemberDiscord
};
async function verifySenderModerationPermission(params) {
if (!params.senderUserId) return;
if (!await discordModerationActionRuntime.hasAnyGuildPermissionDiscord(params.guildId, params.senderUserId, [params.requiredPermission], createDiscordActionOptions({
cfg: params.cfg,
accountId: params.accountId
}))) throw new Error("Sender does not have required permissions for this moderation action.");
}
async function handleDiscordModerationAction(action, params, isActionEnabled, cfg) {
if (!isDiscordModerationAction(action)) throw new Error(`Unknown action: ${action}`);
if (!isActionEnabled("moderation", false)) throw new Error("Discord moderation is disabled.");
if (!cfg) throw new Error("Discord moderation actions require a resolved runtime config.");
const accountId = readStringParam(params, "accountId");
const command = readDiscordModerationCommand(action, params);
const senderUserId = readStringParam(params, "senderUserId");
const withOpts = () => createDiscordActionOptions({
cfg,
accountId
});
await verifySenderModerationPermission({
guildId: command.guildId,
senderUserId,
requiredPermission: requiredGuildPermissionForModerationAction(command.action),
accountId,
cfg
});
switch (command.action) {
case "timeout": return jsonResult({
ok: true,
member: await discordModerationActionRuntime.timeoutMemberDiscord({
guildId: command.guildId,
userId: command.userId,
durationMinutes: command.durationMinutes,
until: command.until,
reason: command.reason
}, withOpts())
});
case "kick":
await discordModerationActionRuntime.kickMemberDiscord({
guildId: command.guildId,
userId: command.userId,
reason: command.reason
}, withOpts());
return jsonResult({ ok: true });
case "ban":
await discordModerationActionRuntime.banMemberDiscord({
guildId: command.guildId,
userId: command.userId,
reason: command.reason,
deleteMessageDays: command.deleteMessageDays
}, withOpts());
return jsonResult({ ok: true });
}
throw new Error("Unsupported Discord moderation action");
}
//#endregion
//#region extensions/discord/src/actions/runtime.presence.ts
const ACTIVITY_TYPE_MAP = {
playing: 0,
streaming: 1,
listening: 2,
watching: 3,
custom: 4,
competing: 5
};
const VALID_STATUSES = new Set([
"online",
"dnd",
"idle",
"invisible"
]);
async function handleDiscordPresenceAction(action, params, isActionEnabled) {
if (action !== "setPresence") throw new Error(`Unknown presence action: ${action}`);
if (!isActionEnabled("presence", false)) throw new Error("Discord presence changes are disabled.");
const accountId = readStringParam(params, "accountId");
const gateway = getGateway(accountId);
if (!gateway) throw new Error(`Discord gateway not available${accountId ? ` for account "${accountId}"` : ""}. The bot may not be connected.`);
if (!gateway.isConnected) throw new Error(`Discord gateway is not connected${accountId ? ` for account "${accountId}"` : ""}.`);
const statusRaw = readStringParam(params, "status") ?? "online";
if (!VALID_STATUSES.has(statusRaw)) throw new Error(`Invalid status "${statusRaw}". Must be one of: ${[...VALID_STATUSES].join(", ")}`);
const status = statusRaw;
const activityTypeRaw = readStringParam(params, "activityType");
const activityName = readStringParam(params, "activityName");
const activities = [];
if (activityTypeRaw || activityName) {
if (!activityTypeRaw) throw new Error(`activityType is required when activityName is provided. Valid types: ${Object.keys(ACTIVITY_TYPE_MAP).join(", ")}`);
const typeNum = ACTIVITY_TYPE_MAP[normalizeLowercaseStringOrEmpty(activityTypeRaw)];
if (typeNum === void 0) throw new Error(`Invalid activityType "${activityTypeRaw}". Must be one of: ${Object.keys(ACTIVITY_TYPE_MAP).join(", ")}`);
const activity = {
name: activityName ?? "",
type: typeNum
};
if (typeNum === 1) {
const url = readStringParam(params, "activityUrl");
if (url) activity.url = url;
}
const state = readStringParam(params, "activityState");
if (state) activity.state = state;
activities.push(activity);
}
const presenceData = {
since: null,
activities,
status,
afk: false
};
gateway.updatePresence(presenceData);
return jsonResult({
ok: true,
status,
activities: activities.map((a) => Object.assign({
type: a.type,
name: a.name
}, a.url ? { url: a.url } : {}, a.state ? { state: a.state } : {}))
});
}
//#endregion
//#region extensions/discord/src/actions/runtime.ts
const messagingActions = new Set([
"react",
"reactions",
"sticker",
"poll",
"permissions",
"fetchMessage",
"readMessages",
"sendMessage",
"editMessage",
"deleteMessage",
"threadCreate",
"threadList",
"threadReply",
"pinMessage",
"unpinMessage",
"listPins",
"searchMessages"
]);
const guildActions = new Set([
"memberInfo",
"roleInfo",
"emojiList",
"emojiUpload",
"stickerUpload",
"roleAdd",
"roleRemove",
"channelInfo",
"channelList",
"voiceStatus",
"eventList",
"eventCreate",
"channelCreate",
"channelEdit",
"channelDelete",
"channelMove",
"categoryCreate",
"categoryEdit",
"categoryDelete",
"channelPermissionSet",
"channelPermissionRemove"
]);
const moderationActions = new Set([
"timeout",
"kick",
"ban"
]);
const presenceActions = new Set(["setPresence"]);
async function handleDiscordAction(params, cfg, options) {
const action = readStringParam(params, "action", { required: true });
const isActionEnabled = createDiscordActionGate({
cfg,
accountId: readStringParam(params, "accountId")
});
if (messagingActions.has(action)) return await handleDiscordMessagingAction(action, params, isActionEnabled, cfg, options);
if (guildActions.has(action)) return await handleDiscordGuildAction(action, params, isActionEnabled, cfg, options);
if (moderationActions.has(action)) return await handleDiscordModerationAction(action, params, isActionEnabled, cfg);
if (presenceActions.has(action)) return await handleDiscordPresenceAction(action, params, isActionEnabled);
throw new Error(`Unknown action: ${action}`);
}
//#endregion
export { readDiscordChannelCreateParams as a, readDiscordParentIdParam as c, requiredGuildPermissionForModerationAction as i, isDiscordModerationAction as n, readDiscordChannelEditParams as o, readDiscordModerationCommand as r, readDiscordChannelMoveParams as s, handleDiscordAction as t };