mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 04:15:48 -06:00
fix(discord): preserve per-button reusable settings (#129068)
This commit is contained in:
committed by
GitHub
parent
27e3eac75d
commit
d99ed2ac5b
@@ -207,6 +207,7 @@ function parseButtonSpec(raw: unknown, label: string): DiscordComponentButtonSpe
|
||||
),
|
||||
emoji: readOptionalEmoji(obj.emoji, `${label}.emoji`),
|
||||
disabled: typeof obj.disabled === "boolean" ? obj.disabled : undefined,
|
||||
reusable: typeof obj.reusable === "boolean" ? obj.reusable : undefined,
|
||||
allowedUsers: readOptionalStringArray(obj.allowedUsers, `${label}.allowedUsers`),
|
||||
};
|
||||
}
|
||||
@@ -408,7 +409,6 @@ export function readDiscordComponentSpec(raw: unknown): DiscordComponentMessageS
|
||||
? blocksRaw.map((entry, idx) => parseComponentBlock(entry, `components.blocks[${idx}]`))
|
||||
: undefined;
|
||||
const modalRaw = obj.modal;
|
||||
const reusable = typeof obj.reusable === "boolean" ? obj.reusable : undefined;
|
||||
let modal: DiscordModalSpec | undefined;
|
||||
if (modalRaw !== undefined) {
|
||||
const modalObj = requireObject(modalRaw, "components.modal");
|
||||
@@ -433,7 +433,7 @@ export function readDiscordComponentSpec(raw: unknown): DiscordComponentMessageS
|
||||
}
|
||||
return {
|
||||
text: normalizeOptionalString(obj.text),
|
||||
reusable,
|
||||
reusable: typeof obj.reusable === "boolean" ? obj.reusable : undefined,
|
||||
container:
|
||||
typeof obj.container === "object" && obj.container && !Array.isArray(obj.container)
|
||||
? {
|
||||
|
||||
@@ -363,6 +363,113 @@ describe("discord component registry", () => {
|
||||
await expect(resolveDiscordComponentEntryWithPersistence({ id: "btn_1" })).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ placement: "row", buttonReusable: true, cardReusable: undefined, expectedReusable: true },
|
||||
{ placement: "row", buttonReusable: true, cardReusable: false, expectedReusable: true },
|
||||
{ placement: "row", buttonReusable: false, cardReusable: true, expectedReusable: false },
|
||||
{ placement: "row", buttonReusable: undefined, cardReusable: true, expectedReusable: true },
|
||||
{
|
||||
placement: "row",
|
||||
buttonReusable: undefined,
|
||||
cardReusable: undefined,
|
||||
expectedReusable: undefined,
|
||||
},
|
||||
{ placement: "section", buttonReusable: true, cardReusable: undefined, expectedReusable: true },
|
||||
{ placement: "section", buttonReusable: false, cardReusable: true, expectedReusable: false },
|
||||
{
|
||||
placement: "section",
|
||||
buttonReusable: undefined,
|
||||
cardReusable: true,
|
||||
expectedReusable: true,
|
||||
},
|
||||
] as const)(
|
||||
"preserves $placement button reuse=$buttonReusable over card reuse=$cardReusable through delivery",
|
||||
async ({ placement, buttonReusable, cardReusable, expectedReusable }) => {
|
||||
const { RequestClient } = await import("./internal/discord.js");
|
||||
const { sendDiscordComponentMessage } = await import("./send.components.js");
|
||||
const button = {
|
||||
label: "Refresh",
|
||||
callbackData: "refresh",
|
||||
...(buttonReusable === undefined ? {} : { reusable: buttonReusable }),
|
||||
};
|
||||
const spec = readDiscordComponentSpec({
|
||||
...(cardReusable === undefined ? {} : { reusable: cardReusable }),
|
||||
blocks: [
|
||||
placement === "row"
|
||||
? { type: "actions", buttons: [button] }
|
||||
: { type: "section", text: "Actions", accessory: { type: "button", button } },
|
||||
],
|
||||
});
|
||||
if (!spec) {
|
||||
throw new Error("Expected component spec to be parsed");
|
||||
}
|
||||
|
||||
const requests: Array<{ method: string | undefined; path: string; body: unknown }> = [];
|
||||
const rest = new RequestClient("fixture-token", {
|
||||
queueRequests: false,
|
||||
fetch: async (input, init) => {
|
||||
const url = new URL(input instanceof Request ? input.url : input);
|
||||
requests.push({
|
||||
method: init?.method,
|
||||
path: url.pathname,
|
||||
body: typeof init?.body === "string" ? JSON.parse(init.body) : undefined,
|
||||
});
|
||||
const message =
|
||||
init?.method === "GET"
|
||||
? { id: "789", type: 0 }
|
||||
: { id: "delivered-message", channel_id: "789" };
|
||||
return new Response(JSON.stringify(message), {
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const delivery = await sendDiscordComponentMessage("channel:789", spec, {
|
||||
cfg: { channels: { discord: { token: "fixture-token" } } },
|
||||
rest,
|
||||
token: "fixture-token",
|
||||
});
|
||||
expect(delivery.messageId).toBe("delivered-message");
|
||||
const sent = requests.find((request) => request.method === "POST");
|
||||
expect(sent?.path).toBe("/api/v10/channels/789/messages");
|
||||
expect(JSON.stringify(sent?.body)).not.toContain("reusable");
|
||||
|
||||
const sentBody = sent?.body as {
|
||||
components?: Array<{
|
||||
components?: Array<{
|
||||
components?: Array<{ custom_id?: string }>;
|
||||
accessory?: { custom_id?: string };
|
||||
}>;
|
||||
}>;
|
||||
};
|
||||
const block = sentBody.components?.[0]?.components?.[0];
|
||||
const customId = block?.components?.[0]?.custom_id ?? block?.accessory?.custom_id;
|
||||
if (!customId) {
|
||||
throw new Error("Expected a delivered Discord button custom id");
|
||||
}
|
||||
const componentId = parseDiscordComponentCustomId(customId)?.componentId;
|
||||
if (!componentId) {
|
||||
throw new Error("Expected a registered Discord component id");
|
||||
}
|
||||
|
||||
const registered = await resolveDiscordComponentEntryWithPersistence({
|
||||
id: componentId,
|
||||
consume: false,
|
||||
});
|
||||
expect(registered?.reusable).toBe(expectedReusable);
|
||||
const firstInteraction = await resolveDiscordComponentEntryWithPersistence({
|
||||
id: componentId,
|
||||
consume: !registered?.reusable,
|
||||
});
|
||||
expect(firstInteraction?.id).toBe(componentId);
|
||||
const secondInteraction = await resolveDiscordComponentEntryWithPersistence({
|
||||
id: componentId,
|
||||
consume: false,
|
||||
});
|
||||
expect(Boolean(secondInteraction)).toBe(expectedReusable === true);
|
||||
},
|
||||
);
|
||||
|
||||
it("consumes sibling entries from the same non-reusable component message", async () => {
|
||||
const result = buildDiscordComponentMessage({
|
||||
spec: {
|
||||
|
||||
Reference in New Issue
Block a user