mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:10:44 +00:00
fix(discord): thread runtime config through guild actions
This commit is contained in:
committed by
Peter Steinberger
parent
e9d16cbd8c
commit
43366cd541
@@ -56,33 +56,32 @@ export const discordGuildActionRuntime = {
|
||||
uploadStickerDiscord,
|
||||
};
|
||||
|
||||
type DiscordRoleMutation = (params: {
|
||||
guildId: string;
|
||||
userId: string;
|
||||
roleId: string;
|
||||
}) => Promise<unknown>;
|
||||
type DiscordRoleMutationWithAccount = (
|
||||
type DiscordRoleMutationOpts = { cfg: OpenClawConfig; accountId?: string };
|
||||
type DiscordRoleMutation = (
|
||||
params: {
|
||||
guildId: string;
|
||||
userId: string;
|
||||
roleId: string;
|
||||
},
|
||||
options: { accountId: string },
|
||||
options: DiscordRoleMutationOpts,
|
||||
) => Promise<unknown>;
|
||||
|
||||
async function runRoleMutation(params: {
|
||||
cfgOptions: { cfg: OpenClawConfig };
|
||||
accountId?: string;
|
||||
values: Record<string, unknown>;
|
||||
mutate: DiscordRoleMutation & DiscordRoleMutationWithAccount;
|
||||
mutate: DiscordRoleMutation;
|
||||
}) {
|
||||
const guildId = readStringParam(params.values, "guildId", { required: true });
|
||||
const userId = readStringParam(params.values, "userId", { required: true });
|
||||
const roleId = readStringParam(params.values, "roleId", { required: true });
|
||||
if (params.accountId) {
|
||||
await params.mutate({ guildId, userId, roleId }, { accountId: params.accountId });
|
||||
return;
|
||||
}
|
||||
await params.mutate({ guildId, userId, roleId });
|
||||
await params.mutate(
|
||||
{ guildId, userId, roleId },
|
||||
{
|
||||
...params.cfgOptions,
|
||||
...(params.accountId ? { accountId: params.accountId } : {}),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function readChannelPermissionTarget(params: Record<string, unknown>) {
|
||||
@@ -100,6 +99,15 @@ export async function handleDiscordGuildAction(
|
||||
options?: { mediaLocalRoots?: readonly string[] },
|
||||
): Promise<AgentToolResult<unknown>> {
|
||||
const accountId = readStringParam(params, "accountId");
|
||||
if (!cfg) {
|
||||
throw new Error("Discord guild actions require a resolved runtime config.");
|
||||
}
|
||||
const cfgOptions = { cfg };
|
||||
const withOpts = (extra?: Record<string, unknown>) => ({
|
||||
...cfgOptions,
|
||||
...(accountId ? { accountId } : {}),
|
||||
...extra,
|
||||
});
|
||||
switch (action) {
|
||||
case "memberInfo": {
|
||||
if (!isActionEnabled("memberInfo")) {
|
||||
@@ -111,13 +119,13 @@ export async function handleDiscordGuildAction(
|
||||
const userId = readStringParam(params, "userId", {
|
||||
required: true,
|
||||
});
|
||||
const effectiveAccountId =
|
||||
accountId ?? (cfg ? resolveDefaultDiscordAccountId(cfg) : undefined);
|
||||
const effectiveAccountId = accountId ?? resolveDefaultDiscordAccountId(cfg);
|
||||
const member = effectiveAccountId
|
||||
? await discordGuildActionRuntime.fetchMemberInfoDiscord(guildId, userId, {
|
||||
...cfgOptions,
|
||||
accountId: effectiveAccountId,
|
||||
})
|
||||
: await discordGuildActionRuntime.fetchMemberInfoDiscord(guildId, userId);
|
||||
: await discordGuildActionRuntime.fetchMemberInfoDiscord(guildId, userId, cfgOptions);
|
||||
const presence = getPresence(effectiveAccountId, userId);
|
||||
const activities = presence?.activities ?? undefined;
|
||||
const status = presence?.status ?? undefined;
|
||||
@@ -130,9 +138,7 @@ export async function handleDiscordGuildAction(
|
||||
const guildId = readStringParam(params, "guildId", {
|
||||
required: true,
|
||||
});
|
||||
const roles = accountId
|
||||
? await discordGuildActionRuntime.fetchRoleInfoDiscord(guildId, { accountId })
|
||||
: await discordGuildActionRuntime.fetchRoleInfoDiscord(guildId);
|
||||
const roles = await discordGuildActionRuntime.fetchRoleInfoDiscord(guildId, withOpts());
|
||||
return jsonResult({ ok: true, roles });
|
||||
}
|
||||
case "emojiList": {
|
||||
@@ -142,9 +148,7 @@ export async function handleDiscordGuildAction(
|
||||
const guildId = readStringParam(params, "guildId", {
|
||||
required: true,
|
||||
});
|
||||
const emojis = accountId
|
||||
? await discordGuildActionRuntime.listGuildEmojisDiscord(guildId, { accountId })
|
||||
: await discordGuildActionRuntime.listGuildEmojisDiscord(guildId);
|
||||
const emojis = await discordGuildActionRuntime.listGuildEmojisDiscord(guildId, withOpts());
|
||||
return jsonResult({ ok: true, emojis });
|
||||
}
|
||||
case "emojiUpload": {
|
||||
@@ -159,22 +163,15 @@ export async function handleDiscordGuildAction(
|
||||
required: true,
|
||||
});
|
||||
const roleIds = readStringArrayParam(params, "roleIds");
|
||||
const emoji = accountId
|
||||
? await discordGuildActionRuntime.uploadEmojiDiscord(
|
||||
{
|
||||
guildId,
|
||||
name,
|
||||
mediaUrl,
|
||||
roleIds: roleIds?.length ? roleIds : undefined,
|
||||
},
|
||||
{ accountId },
|
||||
)
|
||||
: await discordGuildActionRuntime.uploadEmojiDiscord({
|
||||
guildId,
|
||||
name,
|
||||
mediaUrl,
|
||||
roleIds: roleIds?.length ? roleIds : undefined,
|
||||
});
|
||||
const emoji = await discordGuildActionRuntime.uploadEmojiDiscord(
|
||||
{
|
||||
guildId,
|
||||
name,
|
||||
mediaUrl,
|
||||
roleIds: roleIds?.length ? roleIds : undefined,
|
||||
},
|
||||
withOpts(),
|
||||
);
|
||||
return jsonResult({ ok: true, emoji });
|
||||
}
|
||||
case "stickerUpload": {
|
||||
@@ -192,24 +189,16 @@ export async function handleDiscordGuildAction(
|
||||
const mediaUrl = readStringParam(params, "mediaUrl", {
|
||||
required: true,
|
||||
});
|
||||
const sticker = accountId
|
||||
? await discordGuildActionRuntime.uploadStickerDiscord(
|
||||
{
|
||||
guildId,
|
||||
name,
|
||||
description,
|
||||
tags,
|
||||
mediaUrl,
|
||||
},
|
||||
{ accountId },
|
||||
)
|
||||
: await discordGuildActionRuntime.uploadStickerDiscord({
|
||||
guildId,
|
||||
name,
|
||||
description,
|
||||
tags,
|
||||
mediaUrl,
|
||||
});
|
||||
const sticker = await discordGuildActionRuntime.uploadStickerDiscord(
|
||||
{
|
||||
guildId,
|
||||
name,
|
||||
description,
|
||||
tags,
|
||||
mediaUrl,
|
||||
},
|
||||
withOpts(),
|
||||
);
|
||||
return jsonResult({ ok: true, sticker });
|
||||
}
|
||||
case "roleAdd": {
|
||||
@@ -217,6 +206,7 @@ export async function handleDiscordGuildAction(
|
||||
throw new Error("Discord role changes are disabled.");
|
||||
}
|
||||
await runRoleMutation({
|
||||
cfgOptions,
|
||||
accountId,
|
||||
values: params,
|
||||
mutate: discordGuildActionRuntime.addRoleDiscord,
|
||||
@@ -228,6 +218,7 @@ export async function handleDiscordGuildAction(
|
||||
throw new Error("Discord role changes are disabled.");
|
||||
}
|
||||
await runRoleMutation({
|
||||
cfgOptions,
|
||||
accountId,
|
||||
values: params,
|
||||
mutate: discordGuildActionRuntime.removeRoleDiscord,
|
||||
@@ -241,9 +232,10 @@ export async function handleDiscordGuildAction(
|
||||
const channelId = readStringParam(params, "channelId", {
|
||||
required: true,
|
||||
});
|
||||
const channel = accountId
|
||||
? await discordGuildActionRuntime.fetchChannelInfoDiscord(channelId, { accountId })
|
||||
: await discordGuildActionRuntime.fetchChannelInfoDiscord(channelId);
|
||||
const channel = await discordGuildActionRuntime.fetchChannelInfoDiscord(
|
||||
channelId,
|
||||
withOpts(),
|
||||
);
|
||||
return jsonResult({ ok: true, channel });
|
||||
}
|
||||
case "channelList": {
|
||||
@@ -253,9 +245,10 @@ export async function handleDiscordGuildAction(
|
||||
const guildId = readStringParam(params, "guildId", {
|
||||
required: true,
|
||||
});
|
||||
const channels = accountId
|
||||
? await discordGuildActionRuntime.listGuildChannelsDiscord(guildId, { accountId })
|
||||
: await discordGuildActionRuntime.listGuildChannelsDiscord(guildId);
|
||||
const channels = await discordGuildActionRuntime.listGuildChannelsDiscord(
|
||||
guildId,
|
||||
withOpts(),
|
||||
);
|
||||
return jsonResult({ ok: true, channels });
|
||||
}
|
||||
case "voiceStatus": {
|
||||
@@ -268,11 +261,11 @@ export async function handleDiscordGuildAction(
|
||||
const userId = readStringParam(params, "userId", {
|
||||
required: true,
|
||||
});
|
||||
const voice = accountId
|
||||
? await discordGuildActionRuntime.fetchVoiceStatusDiscord(guildId, userId, {
|
||||
accountId,
|
||||
})
|
||||
: await discordGuildActionRuntime.fetchVoiceStatusDiscord(guildId, userId);
|
||||
const voice = await discordGuildActionRuntime.fetchVoiceStatusDiscord(
|
||||
guildId,
|
||||
userId,
|
||||
withOpts(),
|
||||
);
|
||||
return jsonResult({ ok: true, voice });
|
||||
}
|
||||
case "eventList": {
|
||||
@@ -282,9 +275,10 @@ export async function handleDiscordGuildAction(
|
||||
const guildId = readStringParam(params, "guildId", {
|
||||
required: true,
|
||||
});
|
||||
const events = accountId
|
||||
? await discordGuildActionRuntime.listScheduledEventsDiscord(guildId, { accountId })
|
||||
: await discordGuildActionRuntime.listScheduledEventsDiscord(guildId);
|
||||
const events = await discordGuildActionRuntime.listScheduledEventsDiscord(
|
||||
guildId,
|
||||
withOpts(),
|
||||
);
|
||||
return jsonResult({ ok: true, events });
|
||||
}
|
||||
case "eventCreate": {
|
||||
@@ -321,11 +315,11 @@ export async function handleDiscordGuildAction(
|
||||
image,
|
||||
privacy_level: 2,
|
||||
};
|
||||
const event = accountId
|
||||
? await discordGuildActionRuntime.createScheduledEventDiscord(guildId, payload, {
|
||||
accountId,
|
||||
})
|
||||
: await discordGuildActionRuntime.createScheduledEventDiscord(guildId, payload);
|
||||
const event = await discordGuildActionRuntime.createScheduledEventDiscord(
|
||||
guildId,
|
||||
payload,
|
||||
withOpts(),
|
||||
);
|
||||
return jsonResult({ ok: true, event });
|
||||
}
|
||||
case "channelCreate": {
|
||||
@@ -339,28 +333,18 @@ export async function handleDiscordGuildAction(
|
||||
const topic = readStringParam(params, "topic");
|
||||
const position = readNumberParam(params, "position", { integer: true });
|
||||
const nsfw = params.nsfw as boolean | undefined;
|
||||
const channel = accountId
|
||||
? await discordGuildActionRuntime.createChannelDiscord(
|
||||
{
|
||||
guildId,
|
||||
name,
|
||||
type: type ?? undefined,
|
||||
parentId: parentId ?? undefined,
|
||||
topic: topic ?? undefined,
|
||||
position: position ?? undefined,
|
||||
nsfw,
|
||||
},
|
||||
{ accountId },
|
||||
)
|
||||
: await discordGuildActionRuntime.createChannelDiscord({
|
||||
guildId,
|
||||
name,
|
||||
type: type ?? undefined,
|
||||
parentId: parentId ?? undefined,
|
||||
topic: topic ?? undefined,
|
||||
position: position ?? undefined,
|
||||
nsfw,
|
||||
});
|
||||
const channel = await discordGuildActionRuntime.createChannelDiscord(
|
||||
{
|
||||
guildId,
|
||||
name,
|
||||
type: type ?? undefined,
|
||||
parentId: parentId ?? undefined,
|
||||
topic: topic ?? undefined,
|
||||
position: position ?? undefined,
|
||||
nsfw,
|
||||
},
|
||||
withOpts(),
|
||||
);
|
||||
return jsonResult({ ok: true, channel });
|
||||
}
|
||||
case "channelEdit": {
|
||||
@@ -397,9 +381,7 @@ export async function handleDiscordGuildAction(
|
||||
autoArchiveDuration: autoArchiveDuration ?? undefined,
|
||||
availableTags,
|
||||
};
|
||||
const channel = accountId
|
||||
? await discordGuildActionRuntime.editChannelDiscord(editPayload, { accountId })
|
||||
: await discordGuildActionRuntime.editChannelDiscord(editPayload);
|
||||
const channel = await discordGuildActionRuntime.editChannelDiscord(editPayload, withOpts());
|
||||
return jsonResult({ ok: true, channel });
|
||||
}
|
||||
case "channelDelete": {
|
||||
@@ -409,9 +391,7 @@ export async function handleDiscordGuildAction(
|
||||
const channelId = readStringParam(params, "channelId", {
|
||||
required: true,
|
||||
});
|
||||
const result = accountId
|
||||
? await discordGuildActionRuntime.deleteChannelDiscord(channelId, { accountId })
|
||||
: await discordGuildActionRuntime.deleteChannelDiscord(channelId);
|
||||
const result = await discordGuildActionRuntime.deleteChannelDiscord(channelId, withOpts());
|
||||
return jsonResult(result);
|
||||
}
|
||||
case "channelMove": {
|
||||
@@ -424,24 +404,15 @@ export async function handleDiscordGuildAction(
|
||||
});
|
||||
const parentId = readDiscordParentIdParam(params);
|
||||
const position = readNumberParam(params, "position", { integer: true });
|
||||
if (accountId) {
|
||||
await discordGuildActionRuntime.moveChannelDiscord(
|
||||
{
|
||||
guildId,
|
||||
channelId,
|
||||
parentId,
|
||||
position: position ?? undefined,
|
||||
},
|
||||
{ accountId },
|
||||
);
|
||||
} else {
|
||||
await discordGuildActionRuntime.moveChannelDiscord({
|
||||
await discordGuildActionRuntime.moveChannelDiscord(
|
||||
{
|
||||
guildId,
|
||||
channelId,
|
||||
parentId,
|
||||
position: position ?? undefined,
|
||||
});
|
||||
}
|
||||
},
|
||||
withOpts(),
|
||||
);
|
||||
return jsonResult({ ok: true });
|
||||
}
|
||||
case "categoryCreate": {
|
||||
@@ -451,22 +422,15 @@ export async function handleDiscordGuildAction(
|
||||
const guildId = readStringParam(params, "guildId", { required: true });
|
||||
const name = readStringParam(params, "name", { required: true });
|
||||
const position = readNumberParam(params, "position", { integer: true });
|
||||
const channel = accountId
|
||||
? await discordGuildActionRuntime.createChannelDiscord(
|
||||
{
|
||||
guildId,
|
||||
name,
|
||||
type: 4,
|
||||
position: position ?? undefined,
|
||||
},
|
||||
{ accountId },
|
||||
)
|
||||
: await discordGuildActionRuntime.createChannelDiscord({
|
||||
guildId,
|
||||
name,
|
||||
type: 4,
|
||||
position: position ?? undefined,
|
||||
});
|
||||
const channel = await discordGuildActionRuntime.createChannelDiscord(
|
||||
{
|
||||
guildId,
|
||||
name,
|
||||
type: 4,
|
||||
position: position ?? undefined,
|
||||
},
|
||||
withOpts(),
|
||||
);
|
||||
return jsonResult({ ok: true, category: channel });
|
||||
}
|
||||
case "categoryEdit": {
|
||||
@@ -478,20 +442,14 @@ export async function handleDiscordGuildAction(
|
||||
});
|
||||
const name = readStringParam(params, "name");
|
||||
const position = readNumberParam(params, "position", { integer: true });
|
||||
const channel = accountId
|
||||
? await discordGuildActionRuntime.editChannelDiscord(
|
||||
{
|
||||
channelId: categoryId,
|
||||
name: name ?? undefined,
|
||||
position: position ?? undefined,
|
||||
},
|
||||
{ accountId },
|
||||
)
|
||||
: await discordGuildActionRuntime.editChannelDiscord({
|
||||
channelId: categoryId,
|
||||
name: name ?? undefined,
|
||||
position: position ?? undefined,
|
||||
});
|
||||
const channel = await discordGuildActionRuntime.editChannelDiscord(
|
||||
{
|
||||
channelId: categoryId,
|
||||
name: name ?? undefined,
|
||||
position: position ?? undefined,
|
||||
},
|
||||
withOpts(),
|
||||
);
|
||||
return jsonResult({ ok: true, category: channel });
|
||||
}
|
||||
case "categoryDelete": {
|
||||
@@ -501,9 +459,7 @@ export async function handleDiscordGuildAction(
|
||||
const categoryId = readStringParam(params, "categoryId", {
|
||||
required: true,
|
||||
});
|
||||
const result = accountId
|
||||
? await discordGuildActionRuntime.deleteChannelDiscord(categoryId, { accountId })
|
||||
: await discordGuildActionRuntime.deleteChannelDiscord(categoryId);
|
||||
const result = await discordGuildActionRuntime.deleteChannelDiscord(categoryId, withOpts());
|
||||
return jsonResult(result);
|
||||
}
|
||||
case "channelPermissionSet": {
|
||||
@@ -517,26 +473,16 @@ export async function handleDiscordGuildAction(
|
||||
const targetType = targetTypeRaw === "member" ? 1 : 0;
|
||||
const allow = readStringParam(params, "allow");
|
||||
const deny = readStringParam(params, "deny");
|
||||
if (accountId) {
|
||||
await discordGuildActionRuntime.setChannelPermissionDiscord(
|
||||
{
|
||||
channelId,
|
||||
targetId,
|
||||
targetType,
|
||||
allow: allow ?? undefined,
|
||||
deny: deny ?? undefined,
|
||||
},
|
||||
{ accountId },
|
||||
);
|
||||
} else {
|
||||
await discordGuildActionRuntime.setChannelPermissionDiscord({
|
||||
await discordGuildActionRuntime.setChannelPermissionDiscord(
|
||||
{
|
||||
channelId,
|
||||
targetId,
|
||||
targetType,
|
||||
allow: allow ?? undefined,
|
||||
deny: deny ?? undefined,
|
||||
});
|
||||
}
|
||||
},
|
||||
withOpts(),
|
||||
);
|
||||
return jsonResult({ ok: true });
|
||||
}
|
||||
case "channelPermissionRemove": {
|
||||
@@ -544,13 +490,11 @@ export async function handleDiscordGuildAction(
|
||||
throw new Error("Discord channel management is disabled.");
|
||||
}
|
||||
const { channelId, targetId } = readChannelPermissionTarget(params);
|
||||
if (accountId) {
|
||||
await discordGuildActionRuntime.removeChannelPermissionDiscord(channelId, targetId, {
|
||||
accountId,
|
||||
});
|
||||
} else {
|
||||
await discordGuildActionRuntime.removeChannelPermissionDiscord(channelId, targetId);
|
||||
}
|
||||
await discordGuildActionRuntime.removeChannelPermissionDiscord(
|
||||
channelId,
|
||||
targetId,
|
||||
withOpts(),
|
||||
);
|
||||
return jsonResult({ ok: true });
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PermissionFlagsBits } from "discord-api-types/v10";
|
||||
import type { DiscordActionConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { DiscordActionConfig, OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
discordModerationActionRuntime,
|
||||
@@ -13,6 +13,11 @@ const timeoutMemberDiscord = vi.fn(async () => ({ id: "user-1" }));
|
||||
const hasAnyGuildPermissionDiscord = vi.fn(async () => false);
|
||||
|
||||
const enableAllActions = (_key: keyof DiscordActionConfig, _defaultValue = true) => true;
|
||||
const DISCORD_TEST_CFG = {} as OpenClawConfig;
|
||||
|
||||
function handleModerationAction(action: string, params: Record<string, unknown>) {
|
||||
return handleDiscordModerationAction(action, params, enableAllActions, DISCORD_TEST_CFG);
|
||||
}
|
||||
|
||||
describe("discord moderation sender authorization", () => {
|
||||
beforeEach(() => {
|
||||
@@ -29,22 +34,18 @@ describe("discord moderation sender authorization", () => {
|
||||
hasAnyGuildPermissionDiscord.mockResolvedValueOnce(false);
|
||||
|
||||
await expect(
|
||||
handleDiscordModerationAction(
|
||||
"ban",
|
||||
{
|
||||
guildId: "guild-1",
|
||||
userId: "user-1",
|
||||
senderUserId: "sender-1",
|
||||
},
|
||||
enableAllActions,
|
||||
),
|
||||
handleModerationAction("ban", {
|
||||
guildId: "guild-1",
|
||||
userId: "user-1",
|
||||
senderUserId: "sender-1",
|
||||
}),
|
||||
).rejects.toThrow("required permissions");
|
||||
|
||||
expect(hasAnyGuildPermissionDiscord).toHaveBeenCalledWith(
|
||||
"guild-1",
|
||||
"sender-1",
|
||||
[PermissionFlagsBits.BanMembers],
|
||||
undefined,
|
||||
{ cfg: DISCORD_TEST_CFG },
|
||||
);
|
||||
expect(banMemberDiscord).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -53,22 +54,18 @@ describe("discord moderation sender authorization", () => {
|
||||
hasAnyGuildPermissionDiscord.mockResolvedValueOnce(false);
|
||||
|
||||
await expect(
|
||||
handleDiscordModerationAction(
|
||||
"kick",
|
||||
{
|
||||
guildId: "guild-1",
|
||||
userId: "user-1",
|
||||
senderUserId: "sender-1",
|
||||
},
|
||||
enableAllActions,
|
||||
),
|
||||
handleModerationAction("kick", {
|
||||
guildId: "guild-1",
|
||||
userId: "user-1",
|
||||
senderUserId: "sender-1",
|
||||
}),
|
||||
).rejects.toThrow("required permissions");
|
||||
|
||||
expect(hasAnyGuildPermissionDiscord).toHaveBeenCalledWith(
|
||||
"guild-1",
|
||||
"sender-1",
|
||||
[PermissionFlagsBits.KickMembers],
|
||||
undefined,
|
||||
{ cfg: DISCORD_TEST_CFG },
|
||||
);
|
||||
expect(kickMemberDiscord).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -77,23 +74,19 @@ describe("discord moderation sender authorization", () => {
|
||||
hasAnyGuildPermissionDiscord.mockResolvedValueOnce(false);
|
||||
|
||||
await expect(
|
||||
handleDiscordModerationAction(
|
||||
"timeout",
|
||||
{
|
||||
guildId: "guild-1",
|
||||
userId: "user-1",
|
||||
senderUserId: "sender-1",
|
||||
durationMinutes: 60,
|
||||
},
|
||||
enableAllActions,
|
||||
),
|
||||
handleModerationAction("timeout", {
|
||||
guildId: "guild-1",
|
||||
userId: "user-1",
|
||||
senderUserId: "sender-1",
|
||||
durationMinutes: 60,
|
||||
}),
|
||||
).rejects.toThrow("required permissions");
|
||||
|
||||
expect(hasAnyGuildPermissionDiscord).toHaveBeenCalledWith(
|
||||
"guild-1",
|
||||
"sender-1",
|
||||
[PermissionFlagsBits.ModerateMembers],
|
||||
undefined,
|
||||
{ cfg: DISCORD_TEST_CFG },
|
||||
);
|
||||
expect(timeoutMemberDiscord).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -102,51 +95,46 @@ describe("discord moderation sender authorization", () => {
|
||||
hasAnyGuildPermissionDiscord.mockResolvedValueOnce(true);
|
||||
kickMemberDiscord.mockResolvedValueOnce({ ok: true });
|
||||
|
||||
await handleDiscordModerationAction(
|
||||
"kick",
|
||||
{
|
||||
guildId: "guild-1",
|
||||
userId: "user-1",
|
||||
senderUserId: "sender-1",
|
||||
reason: "rule violation",
|
||||
},
|
||||
enableAllActions,
|
||||
);
|
||||
await handleModerationAction("kick", {
|
||||
guildId: "guild-1",
|
||||
userId: "user-1",
|
||||
senderUserId: "sender-1",
|
||||
reason: "rule violation",
|
||||
});
|
||||
|
||||
expect(hasAnyGuildPermissionDiscord).toHaveBeenCalledWith(
|
||||
"guild-1",
|
||||
"sender-1",
|
||||
[PermissionFlagsBits.KickMembers],
|
||||
undefined,
|
||||
{ cfg: DISCORD_TEST_CFG },
|
||||
);
|
||||
expect(kickMemberDiscord).toHaveBeenCalledWith(
|
||||
{
|
||||
guildId: "guild-1",
|
||||
userId: "user-1",
|
||||
reason: "rule violation",
|
||||
},
|
||||
{ cfg: DISCORD_TEST_CFG },
|
||||
);
|
||||
expect(kickMemberDiscord).toHaveBeenCalledWith({
|
||||
guildId: "guild-1",
|
||||
userId: "user-1",
|
||||
reason: "rule violation",
|
||||
});
|
||||
});
|
||||
|
||||
it("forwards accountId into permission check and moderation execution", async () => {
|
||||
hasAnyGuildPermissionDiscord.mockResolvedValueOnce(true);
|
||||
timeoutMemberDiscord.mockResolvedValueOnce({ id: "user-1" });
|
||||
|
||||
await handleDiscordModerationAction(
|
||||
"timeout",
|
||||
{
|
||||
guildId: "guild-1",
|
||||
userId: "user-1",
|
||||
senderUserId: "sender-1",
|
||||
accountId: "ops",
|
||||
durationMinutes: 5,
|
||||
},
|
||||
enableAllActions,
|
||||
);
|
||||
await handleModerationAction("timeout", {
|
||||
guildId: "guild-1",
|
||||
userId: "user-1",
|
||||
senderUserId: "sender-1",
|
||||
accountId: "ops",
|
||||
durationMinutes: 5,
|
||||
});
|
||||
|
||||
expect(hasAnyGuildPermissionDiscord).toHaveBeenCalledWith(
|
||||
"guild-1",
|
||||
"sender-1",
|
||||
[PermissionFlagsBits.ModerateMembers],
|
||||
{ accountId: "ops" },
|
||||
{ cfg: DISCORD_TEST_CFG, accountId: "ops" },
|
||||
);
|
||||
expect(timeoutMemberDiscord).toHaveBeenCalledWith(
|
||||
{
|
||||
@@ -156,7 +144,7 @@ describe("discord moderation sender authorization", () => {
|
||||
until: undefined,
|
||||
reason: undefined,
|
||||
},
|
||||
{ accountId: "ops" },
|
||||
{ cfg: DISCORD_TEST_CFG, accountId: "ops" },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
jsonResult,
|
||||
readStringParam,
|
||||
type DiscordActionConfig,
|
||||
type OpenClawConfig,
|
||||
} from "../runtime-api.js";
|
||||
import {
|
||||
banMemberDiscord,
|
||||
@@ -29,6 +30,7 @@ async function verifySenderModerationPermission(params: {
|
||||
senderUserId?: string;
|
||||
requiredPermission: bigint;
|
||||
accountId?: string;
|
||||
cfgOptions: { cfg: OpenClawConfig };
|
||||
}) {
|
||||
// CLI/manual flows may not have sender context; enforce only when present.
|
||||
if (!params.senderUserId) {
|
||||
@@ -38,7 +40,10 @@ async function verifySenderModerationPermission(params: {
|
||||
params.guildId,
|
||||
params.senderUserId,
|
||||
[params.requiredPermission],
|
||||
params.accountId ? { accountId: params.accountId } : undefined,
|
||||
{
|
||||
...params.cfgOptions,
|
||||
...(params.accountId ? { accountId: params.accountId } : {}),
|
||||
},
|
||||
);
|
||||
if (!hasPermission) {
|
||||
throw new Error("Sender does not have required permissions for this moderation action.");
|
||||
@@ -49,6 +54,7 @@ export async function handleDiscordModerationAction(
|
||||
action: string,
|
||||
params: Record<string, unknown>,
|
||||
isActionEnabled: ActionGate<DiscordActionConfig>,
|
||||
cfg?: OpenClawConfig,
|
||||
): Promise<AgentToolResult<unknown>> {
|
||||
if (!isDiscordModerationAction(action)) {
|
||||
throw new Error(`Unknown action: ${action}`);
|
||||
@@ -56,75 +62,59 @@ export async function handleDiscordModerationAction(
|
||||
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 cfgOptions = { cfg };
|
||||
const command = readDiscordModerationCommand(action, params);
|
||||
const accountId = readStringParam(params, "accountId");
|
||||
const senderUserId = readStringParam(params, "senderUserId");
|
||||
const withOpts = () => ({
|
||||
...cfgOptions,
|
||||
...(accountId ? { accountId } : {}),
|
||||
});
|
||||
await verifySenderModerationPermission({
|
||||
guildId: command.guildId,
|
||||
senderUserId,
|
||||
requiredPermission: requiredGuildPermissionForModerationAction(command.action),
|
||||
accountId,
|
||||
cfgOptions,
|
||||
});
|
||||
switch (command.action) {
|
||||
case "timeout": {
|
||||
const member = accountId
|
||||
? await discordModerationActionRuntime.timeoutMemberDiscord(
|
||||
{
|
||||
guildId: command.guildId,
|
||||
userId: command.userId,
|
||||
durationMinutes: command.durationMinutes,
|
||||
until: command.until,
|
||||
reason: command.reason,
|
||||
},
|
||||
{ accountId },
|
||||
)
|
||||
: await discordModerationActionRuntime.timeoutMemberDiscord({
|
||||
guildId: command.guildId,
|
||||
userId: command.userId,
|
||||
durationMinutes: command.durationMinutes,
|
||||
until: command.until,
|
||||
reason: command.reason,
|
||||
});
|
||||
const member = await discordModerationActionRuntime.timeoutMemberDiscord(
|
||||
{
|
||||
guildId: command.guildId,
|
||||
userId: command.userId,
|
||||
durationMinutes: command.durationMinutes,
|
||||
until: command.until,
|
||||
reason: command.reason,
|
||||
},
|
||||
withOpts(),
|
||||
);
|
||||
return jsonResult({ ok: true, member });
|
||||
}
|
||||
case "kick": {
|
||||
if (accountId) {
|
||||
await discordModerationActionRuntime.kickMemberDiscord(
|
||||
{
|
||||
guildId: command.guildId,
|
||||
userId: command.userId,
|
||||
reason: command.reason,
|
||||
},
|
||||
{ accountId },
|
||||
);
|
||||
} else {
|
||||
await discordModerationActionRuntime.kickMemberDiscord({
|
||||
await discordModerationActionRuntime.kickMemberDiscord(
|
||||
{
|
||||
guildId: command.guildId,
|
||||
userId: command.userId,
|
||||
reason: command.reason,
|
||||
});
|
||||
}
|
||||
},
|
||||
withOpts(),
|
||||
);
|
||||
return jsonResult({ ok: true });
|
||||
}
|
||||
case "ban": {
|
||||
if (accountId) {
|
||||
await discordModerationActionRuntime.banMemberDiscord(
|
||||
{
|
||||
guildId: command.guildId,
|
||||
userId: command.userId,
|
||||
reason: command.reason,
|
||||
deleteMessageDays: command.deleteMessageDays,
|
||||
},
|
||||
{ accountId },
|
||||
);
|
||||
} else {
|
||||
await discordModerationActionRuntime.banMemberDiscord({
|
||||
await discordModerationActionRuntime.banMemberDiscord(
|
||||
{
|
||||
guildId: command.guildId,
|
||||
userId: command.userId,
|
||||
reason: command.reason,
|
||||
deleteMessageDays: command.deleteMessageDays,
|
||||
});
|
||||
}
|
||||
},
|
||||
withOpts(),
|
||||
);
|
||||
return jsonResult({ ok: true });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,25 @@ function handleMessagingAction(
|
||||
return handleDiscordMessagingAction(action, params, isActionEnabled, options, cfg);
|
||||
}
|
||||
|
||||
function handleGuildAction(
|
||||
action: string,
|
||||
params: Record<string, unknown>,
|
||||
isActionEnabled: (key: keyof DiscordActionConfig) => boolean,
|
||||
cfg: OpenClawConfig = DISCORD_TEST_CFG,
|
||||
options?: { mediaLocalRoots?: readonly string[] },
|
||||
) {
|
||||
return handleDiscordGuildAction(action, params, isActionEnabled, cfg, options);
|
||||
}
|
||||
|
||||
function handleModerationAction(
|
||||
action: string,
|
||||
params: Record<string, unknown>,
|
||||
isActionEnabled: (key: keyof DiscordActionConfig, defaultValue?: boolean) => boolean,
|
||||
cfg: OpenClawConfig = DISCORD_TEST_CFG,
|
||||
) {
|
||||
return handleDiscordModerationAction(action, params, isActionEnabled, cfg);
|
||||
}
|
||||
|
||||
const disabledActions = (key: keyof DiscordActionConfig) => key !== "reactions";
|
||||
const channelInfoEnabled = (key: keyof DiscordActionConfig) => key === "channelInfo";
|
||||
const moderationEnabled = (key: keyof DiscordActionConfig) => key === "moderation";
|
||||
@@ -516,26 +535,28 @@ describe("handleDiscordGuildAction", () => {
|
||||
user: { id: "U1" },
|
||||
})) as never;
|
||||
|
||||
const result = await handleDiscordGuildAction(
|
||||
const cfg = {
|
||||
channels: {
|
||||
discord: {
|
||||
defaultAccount: "work",
|
||||
accounts: {
|
||||
work: { token: "token-work" },
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
const result = await handleGuildAction(
|
||||
"memberInfo",
|
||||
{
|
||||
guildId: "G1",
|
||||
userId: "U1",
|
||||
},
|
||||
enableAllActions,
|
||||
{
|
||||
channels: {
|
||||
discord: {
|
||||
defaultAccount: "work",
|
||||
accounts: {
|
||||
work: { token: "token-work" },
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
cfg,
|
||||
);
|
||||
|
||||
expect(discordGuildActionRuntime.fetchMemberInfoDiscord).toHaveBeenCalledWith("G1", "U1", {
|
||||
cfg,
|
||||
accountId: "work",
|
||||
});
|
||||
expect(result.details).toEqual(
|
||||
@@ -557,7 +578,7 @@ describe("handleDiscordGuildAction - channel management", () => {
|
||||
});
|
||||
|
||||
it("creates a channel", async () => {
|
||||
const result = await handleDiscordGuildAction(
|
||||
const result = await handleGuildAction(
|
||||
"channelCreate",
|
||||
{
|
||||
guildId: "G1",
|
||||
@@ -567,35 +588,37 @@ describe("handleDiscordGuildAction - channel management", () => {
|
||||
},
|
||||
channelsEnabled,
|
||||
);
|
||||
expect(createChannelDiscord).toHaveBeenCalledWith({
|
||||
guildId: "G1",
|
||||
name: "test-channel",
|
||||
type: 0,
|
||||
parentId: undefined,
|
||||
topic: "Test topic",
|
||||
position: undefined,
|
||||
nsfw: undefined,
|
||||
});
|
||||
expect(createChannelDiscord).toHaveBeenCalledWith(
|
||||
{
|
||||
guildId: "G1",
|
||||
name: "test-channel",
|
||||
type: 0,
|
||||
parentId: undefined,
|
||||
topic: "Test topic",
|
||||
position: undefined,
|
||||
nsfw: undefined,
|
||||
},
|
||||
{ cfg: DISCORD_TEST_CFG },
|
||||
);
|
||||
expect(result.details).toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
it("respects channel gating for channelCreate", async () => {
|
||||
await expect(
|
||||
handleDiscordGuildAction("channelCreate", { guildId: "G1", name: "test" }, channelsDisabled),
|
||||
handleGuildAction("channelCreate", { guildId: "G1", name: "test" }, channelsDisabled),
|
||||
).rejects.toThrow(/Discord channel management is disabled/);
|
||||
});
|
||||
|
||||
it("forwards accountId for channelList", async () => {
|
||||
await handleDiscordGuildAction(
|
||||
"channelList",
|
||||
{ guildId: "G1", accountId: "ops" },
|
||||
channelInfoEnabled,
|
||||
);
|
||||
expect(listGuildChannelsDiscord).toHaveBeenCalledWith("G1", { accountId: "ops" });
|
||||
await handleGuildAction("channelList", { guildId: "G1", accountId: "ops" }, channelInfoEnabled);
|
||||
expect(listGuildChannelsDiscord).toHaveBeenCalledWith("G1", {
|
||||
cfg: DISCORD_TEST_CFG,
|
||||
accountId: "ops",
|
||||
});
|
||||
});
|
||||
|
||||
it("edits a channel", async () => {
|
||||
await handleDiscordGuildAction(
|
||||
await handleGuildAction(
|
||||
"channelEdit",
|
||||
{
|
||||
channelId: "C1",
|
||||
@@ -604,22 +627,25 @@ describe("handleDiscordGuildAction - channel management", () => {
|
||||
},
|
||||
channelsEnabled,
|
||||
);
|
||||
expect(editChannelDiscord).toHaveBeenCalledWith({
|
||||
channelId: "C1",
|
||||
name: "new-name",
|
||||
topic: "new topic",
|
||||
position: undefined,
|
||||
parentId: undefined,
|
||||
nsfw: undefined,
|
||||
rateLimitPerUser: undefined,
|
||||
archived: undefined,
|
||||
locked: undefined,
|
||||
autoArchiveDuration: undefined,
|
||||
});
|
||||
expect(editChannelDiscord).toHaveBeenCalledWith(
|
||||
{
|
||||
channelId: "C1",
|
||||
name: "new-name",
|
||||
topic: "new topic",
|
||||
position: undefined,
|
||||
parentId: undefined,
|
||||
nsfw: undefined,
|
||||
rateLimitPerUser: undefined,
|
||||
archived: undefined,
|
||||
locked: undefined,
|
||||
autoArchiveDuration: undefined,
|
||||
},
|
||||
{ cfg: DISCORD_TEST_CFG },
|
||||
);
|
||||
});
|
||||
|
||||
it("forwards thread edit fields", async () => {
|
||||
await handleDiscordGuildAction(
|
||||
await handleGuildAction(
|
||||
"channelEdit",
|
||||
{
|
||||
channelId: "C1",
|
||||
@@ -629,25 +655,28 @@ describe("handleDiscordGuildAction - channel management", () => {
|
||||
},
|
||||
channelsEnabled,
|
||||
);
|
||||
expect(editChannelDiscord).toHaveBeenCalledWith({
|
||||
channelId: "C1",
|
||||
name: undefined,
|
||||
topic: undefined,
|
||||
position: undefined,
|
||||
parentId: undefined,
|
||||
nsfw: undefined,
|
||||
rateLimitPerUser: undefined,
|
||||
archived: true,
|
||||
locked: false,
|
||||
autoArchiveDuration: 1440,
|
||||
});
|
||||
expect(editChannelDiscord).toHaveBeenCalledWith(
|
||||
{
|
||||
channelId: "C1",
|
||||
name: undefined,
|
||||
topic: undefined,
|
||||
position: undefined,
|
||||
parentId: undefined,
|
||||
nsfw: undefined,
|
||||
rateLimitPerUser: undefined,
|
||||
archived: true,
|
||||
locked: false,
|
||||
autoArchiveDuration: 1440,
|
||||
},
|
||||
{ cfg: DISCORD_TEST_CFG },
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["parentId is null", { parentId: null }],
|
||||
["clearParent is true", { clearParent: true }],
|
||||
])("clears the channel parent when %s", async (_label, payload) => {
|
||||
await handleDiscordGuildAction(
|
||||
await handleGuildAction(
|
||||
"channelEdit",
|
||||
{
|
||||
channelId: "C1",
|
||||
@@ -655,27 +684,30 @@ describe("handleDiscordGuildAction - channel management", () => {
|
||||
},
|
||||
channelsEnabled,
|
||||
);
|
||||
expect(editChannelDiscord).toHaveBeenCalledWith({
|
||||
channelId: "C1",
|
||||
name: undefined,
|
||||
topic: undefined,
|
||||
position: undefined,
|
||||
parentId: null,
|
||||
nsfw: undefined,
|
||||
rateLimitPerUser: undefined,
|
||||
archived: undefined,
|
||||
locked: undefined,
|
||||
autoArchiveDuration: undefined,
|
||||
});
|
||||
expect(editChannelDiscord).toHaveBeenCalledWith(
|
||||
{
|
||||
channelId: "C1",
|
||||
name: undefined,
|
||||
topic: undefined,
|
||||
position: undefined,
|
||||
parentId: null,
|
||||
nsfw: undefined,
|
||||
rateLimitPerUser: undefined,
|
||||
archived: undefined,
|
||||
locked: undefined,
|
||||
autoArchiveDuration: undefined,
|
||||
},
|
||||
{ cfg: DISCORD_TEST_CFG },
|
||||
);
|
||||
});
|
||||
|
||||
it("deletes a channel", async () => {
|
||||
await handleDiscordGuildAction("channelDelete", { channelId: "C1" }, channelsEnabled);
|
||||
expect(deleteChannelDiscord).toHaveBeenCalledWith("C1");
|
||||
await handleGuildAction("channelDelete", { channelId: "C1" }, channelsEnabled);
|
||||
expect(deleteChannelDiscord).toHaveBeenCalledWith("C1", { cfg: DISCORD_TEST_CFG });
|
||||
});
|
||||
|
||||
it("moves a channel", async () => {
|
||||
await handleDiscordGuildAction(
|
||||
await handleGuildAction(
|
||||
"channelMove",
|
||||
{
|
||||
guildId: "G1",
|
||||
@@ -685,19 +717,22 @@ describe("handleDiscordGuildAction - channel management", () => {
|
||||
},
|
||||
channelsEnabled,
|
||||
);
|
||||
expect(moveChannelDiscord).toHaveBeenCalledWith({
|
||||
guildId: "G1",
|
||||
channelId: "C1",
|
||||
parentId: "P1",
|
||||
position: 5,
|
||||
});
|
||||
expect(moveChannelDiscord).toHaveBeenCalledWith(
|
||||
{
|
||||
guildId: "G1",
|
||||
channelId: "C1",
|
||||
parentId: "P1",
|
||||
position: 5,
|
||||
},
|
||||
{ cfg: DISCORD_TEST_CFG },
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["parentId is null", { parentId: null }],
|
||||
["clearParent is true", { clearParent: true }],
|
||||
])("clears the channel parent on move when %s", async (_label, payload) => {
|
||||
await handleDiscordGuildAction(
|
||||
await handleGuildAction(
|
||||
"channelMove",
|
||||
{
|
||||
guildId: "G1",
|
||||
@@ -706,44 +741,53 @@ describe("handleDiscordGuildAction - channel management", () => {
|
||||
},
|
||||
channelsEnabled,
|
||||
);
|
||||
expect(moveChannelDiscord).toHaveBeenCalledWith({
|
||||
guildId: "G1",
|
||||
channelId: "C1",
|
||||
parentId: null,
|
||||
position: undefined,
|
||||
});
|
||||
expect(moveChannelDiscord).toHaveBeenCalledWith(
|
||||
{
|
||||
guildId: "G1",
|
||||
channelId: "C1",
|
||||
parentId: null,
|
||||
position: undefined,
|
||||
},
|
||||
{ cfg: DISCORD_TEST_CFG },
|
||||
);
|
||||
});
|
||||
|
||||
it("creates a category with type=4", async () => {
|
||||
await handleDiscordGuildAction(
|
||||
await handleGuildAction(
|
||||
"categoryCreate",
|
||||
{ guildId: "G1", name: "My Category" },
|
||||
channelsEnabled,
|
||||
);
|
||||
expect(createChannelDiscord).toHaveBeenCalledWith({
|
||||
guildId: "G1",
|
||||
name: "My Category",
|
||||
type: 4,
|
||||
position: undefined,
|
||||
});
|
||||
expect(createChannelDiscord).toHaveBeenCalledWith(
|
||||
{
|
||||
guildId: "G1",
|
||||
name: "My Category",
|
||||
type: 4,
|
||||
position: undefined,
|
||||
},
|
||||
{ cfg: DISCORD_TEST_CFG },
|
||||
);
|
||||
});
|
||||
|
||||
it("edits a category", async () => {
|
||||
await handleDiscordGuildAction(
|
||||
await handleGuildAction(
|
||||
"categoryEdit",
|
||||
{ categoryId: "CAT1", name: "Renamed Category" },
|
||||
channelsEnabled,
|
||||
);
|
||||
expect(editChannelDiscord).toHaveBeenCalledWith({
|
||||
channelId: "CAT1",
|
||||
name: "Renamed Category",
|
||||
position: undefined,
|
||||
});
|
||||
expect(editChannelDiscord).toHaveBeenCalledWith(
|
||||
{
|
||||
channelId: "CAT1",
|
||||
name: "Renamed Category",
|
||||
position: undefined,
|
||||
},
|
||||
{ cfg: DISCORD_TEST_CFG },
|
||||
);
|
||||
});
|
||||
|
||||
it("deletes a category", async () => {
|
||||
await handleDiscordGuildAction("categoryDelete", { categoryId: "CAT1" }, channelsEnabled);
|
||||
expect(deleteChannelDiscord).toHaveBeenCalledWith("CAT1");
|
||||
await handleGuildAction("categoryDelete", { categoryId: "CAT1" }, channelsEnabled);
|
||||
expect(deleteChannelDiscord).toHaveBeenCalledWith("CAT1", { cfg: DISCORD_TEST_CFG });
|
||||
});
|
||||
|
||||
it.each([
|
||||
@@ -781,23 +825,27 @@ describe("handleDiscordGuildAction - channel management", () => {
|
||||
},
|
||||
},
|
||||
])("sets channel permissions for $name", async ({ params, expected }) => {
|
||||
await handleDiscordGuildAction("channelPermissionSet", params, channelsEnabled);
|
||||
expect(setChannelPermissionDiscord).toHaveBeenCalledWith(expected);
|
||||
await handleGuildAction("channelPermissionSet", params, channelsEnabled);
|
||||
expect(setChannelPermissionDiscord).toHaveBeenCalledWith(expected, {
|
||||
cfg: DISCORD_TEST_CFG,
|
||||
});
|
||||
});
|
||||
|
||||
it("removes channel permissions", async () => {
|
||||
await handleDiscordGuildAction(
|
||||
await handleGuildAction(
|
||||
"channelPermissionRemove",
|
||||
{ channelId: "C1", targetId: "R1" },
|
||||
channelsEnabled,
|
||||
);
|
||||
expect(removeChannelPermissionDiscord).toHaveBeenCalledWith("C1", "R1");
|
||||
expect(removeChannelPermissionDiscord).toHaveBeenCalledWith("C1", "R1", {
|
||||
cfg: DISCORD_TEST_CFG,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("handleDiscordModerationAction", () => {
|
||||
it("forwards accountId for timeout", async () => {
|
||||
await handleDiscordModerationAction(
|
||||
await handleModerationAction(
|
||||
"timeout",
|
||||
{
|
||||
guildId: "G1",
|
||||
@@ -813,7 +861,7 @@ describe("handleDiscordModerationAction", () => {
|
||||
userId: "U1",
|
||||
durationMinutes: 5,
|
||||
}),
|
||||
{ accountId: "ops" },
|
||||
{ cfg: DISCORD_TEST_CFG, accountId: "ops" },
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -836,7 +884,7 @@ describe("handleDiscordAction per-account gating", () => {
|
||||
);
|
||||
expect(timeoutMemberDiscord).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ guildId: "G1", userId: "U1" }),
|
||||
{ accountId: "ops" },
|
||||
{ cfg, accountId: "ops" },
|
||||
);
|
||||
});
|
||||
|
||||
@@ -921,7 +969,7 @@ describe("handleDiscordAction per-account gating", () => {
|
||||
|
||||
expect(createChannelDiscord).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ guildId: "G1", name: "alerts" }),
|
||||
{ accountId: "ops" },
|
||||
{ cfg, accountId: "ops" },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -72,7 +72,7 @@ export async function handleDiscordAction(
|
||||
return await handleDiscordGuildAction(action, params, isActionEnabled, cfg, options);
|
||||
}
|
||||
if (moderationActions.has(action)) {
|
||||
return await handleDiscordModerationAction(action, params, isActionEnabled);
|
||||
return await handleDiscordModerationAction(action, params, isActionEnabled, cfg);
|
||||
}
|
||||
if (presenceActions.has(action)) {
|
||||
return await handleDiscordPresenceAction(action, params, isActionEnabled);
|
||||
|
||||
Reference in New Issue
Block a user