mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
fix(discord): add voice listener compat shim
This commit is contained in:
125
extensions/discord/src/monitor/provider.startup.test.ts
Normal file
125
extensions/discord/src/monitor/provider.startup.test.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import type { Client, Plugin } from "@buape/carbon";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { registerVoiceClientSpy } = vi.hoisted(() => ({
|
||||
registerVoiceClientSpy: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@buape/carbon/voice", () => ({
|
||||
VoicePlugin: class VoicePlugin {
|
||||
id = "voice";
|
||||
|
||||
registerClient(client: {
|
||||
getPlugin: (id: string) => unknown;
|
||||
registerListener: (listener: object) => object;
|
||||
unregisterListener: (listener: object) => boolean;
|
||||
}) {
|
||||
registerVoiceClientSpy(client);
|
||||
if (!client.getPlugin("gateway")) {
|
||||
throw new Error("gateway plugin missing");
|
||||
}
|
||||
client.registerListener({ type: "legacy-voice-listener" });
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/config-runtime", () => ({
|
||||
isDangerousNameMatchingEnabled: () => false,
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/runtime-env", () => ({
|
||||
danger: (value: string) => value,
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/text-runtime", () => ({
|
||||
normalizeOptionalString: (value: string | null | undefined) => {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const normalized = value.trim();
|
||||
return normalized.length > 0 ? normalized : undefined;
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../proxy-request-client.js", () => ({
|
||||
createDiscordRequestClient: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./auto-presence.js", () => ({
|
||||
createDiscordAutoPresenceController: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./gateway-plugin.js", () => ({
|
||||
createDiscordGatewayPlugin: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./gateway-supervisor.js", () => ({
|
||||
createDiscordGatewaySupervisor: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./listeners.js", () => ({
|
||||
DiscordMessageListener: class DiscordMessageListener {},
|
||||
DiscordPresenceListener: class DiscordPresenceListener {},
|
||||
DiscordReactionListener: class DiscordReactionListener {},
|
||||
DiscordReactionRemoveListener: class DiscordReactionRemoveListener {},
|
||||
DiscordThreadUpdateListener: class DiscordThreadUpdateListener {},
|
||||
registerDiscordListener: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./presence.js", () => ({
|
||||
resolveDiscordPresenceUpdate: vi.fn(() => undefined),
|
||||
}));
|
||||
|
||||
import { createDiscordMonitorClient } from "./provider.startup.js";
|
||||
|
||||
describe("createDiscordMonitorClient", () => {
|
||||
it("adds listener compat for legacy voice plugins", () => {
|
||||
registerVoiceClientSpy.mockReset();
|
||||
|
||||
const gatewayPlugin = {
|
||||
id: "gateway",
|
||||
registerClient: vi.fn(),
|
||||
registerRoutes: vi.fn(),
|
||||
} as Plugin;
|
||||
|
||||
const result = createDiscordMonitorClient({
|
||||
accountId: "default",
|
||||
applicationId: "app-1",
|
||||
token: "token-1",
|
||||
commands: [],
|
||||
components: [],
|
||||
modals: [],
|
||||
voiceEnabled: true,
|
||||
discordConfig: {},
|
||||
runtime: {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
exit: vi.fn(),
|
||||
},
|
||||
createClient: (_options, handlers, plugins = []) => {
|
||||
const pluginRegistry = plugins.map((plugin) => ({ id: plugin.id, plugin }));
|
||||
return {
|
||||
listeners: [...(handlers.listeners ?? [])],
|
||||
plugins: pluginRegistry,
|
||||
getPlugin: (id: string) => pluginRegistry.find((entry) => entry.id === id)?.plugin,
|
||||
} as Client;
|
||||
},
|
||||
createGatewayPlugin: () => gatewayPlugin as never,
|
||||
createGatewaySupervisor: () => ({ shutdown: vi.fn(), handleError: vi.fn() }) as never,
|
||||
createAutoPresenceController: () =>
|
||||
({
|
||||
enabled: false,
|
||||
start: vi.fn(),
|
||||
stop: vi.fn(),
|
||||
refresh: vi.fn(),
|
||||
runNow: vi.fn(),
|
||||
}) as never,
|
||||
isDisallowedIntentsError: () => false,
|
||||
});
|
||||
|
||||
expect(registerVoiceClientSpy).toHaveBeenCalledTimes(1);
|
||||
expect(result.client.listeners).toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ type: "legacy-voice-listener" })]),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -41,6 +41,44 @@ type CreateClientFn = (
|
||||
plugins: ConstructorParameters<typeof Client>[2],
|
||||
) => Client;
|
||||
|
||||
type ListenerCompatClient = Client & {
|
||||
plugins?: Array<{ id: string; plugin: Plugin }>;
|
||||
registerListener?: (listener: object) => object;
|
||||
unregisterListener?: (listener: object) => boolean;
|
||||
};
|
||||
|
||||
function withLegacyListenerCompat(client: Client): ListenerCompatClient {
|
||||
const compatClient = client as ListenerCompatClient;
|
||||
if (!compatClient.registerListener) {
|
||||
compatClient.registerListener = (listener: object) => {
|
||||
if (!compatClient.listeners.includes(listener as never)) {
|
||||
compatClient.listeners.push(listener as never);
|
||||
}
|
||||
return listener;
|
||||
};
|
||||
}
|
||||
if (!compatClient.unregisterListener) {
|
||||
compatClient.unregisterListener = (listener: object) => {
|
||||
const index = compatClient.listeners.indexOf(listener as never);
|
||||
if (index < 0) {
|
||||
return false;
|
||||
}
|
||||
compatClient.listeners.splice(index, 1);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
return compatClient;
|
||||
}
|
||||
|
||||
function registerLatePlugin(client: Client, plugin: Plugin) {
|
||||
const compatClient = withLegacyListenerCompat(client);
|
||||
void plugin.registerClient?.(compatClient);
|
||||
void plugin.registerRoutes?.(compatClient);
|
||||
if (!compatClient.plugins?.some((entry) => entry.id === plugin.id)) {
|
||||
compatClient.plugins?.push({ id: plugin.id, plugin });
|
||||
}
|
||||
}
|
||||
|
||||
export function createDiscordStatusReadyListener(params: {
|
||||
discordConfig: Parameters<typeof resolveDiscordPresenceUpdate>[0];
|
||||
getAutoPresenceController: () => DiscordAutoPresenceController | null;
|
||||
@@ -97,6 +135,10 @@ export function createDiscordMonitorClient(params: {
|
||||
if (params.voiceEnabled) {
|
||||
clientPlugins.push(new VoicePlugin());
|
||||
}
|
||||
const voicePlugin = clientPlugins.find((plugin) => plugin.id === "voice");
|
||||
const constructorPlugins = voicePlugin
|
||||
? clientPlugins.filter((plugin) => plugin !== voicePlugin)
|
||||
: clientPlugins;
|
||||
|
||||
// Pass eventQueue config to Carbon so the gateway listener budget can be tuned.
|
||||
// Default listenerTimeout is 120s (Carbon defaults to 30s, which is too short for some
|
||||
@@ -125,8 +167,11 @@ export function createDiscordMonitorClient(params: {
|
||||
components: params.components,
|
||||
modals: params.modals,
|
||||
},
|
||||
clientPlugins,
|
||||
constructorPlugins,
|
||||
);
|
||||
if (voicePlugin) {
|
||||
registerLatePlugin(client, voicePlugin);
|
||||
}
|
||||
if (params.proxyFetch) {
|
||||
client.rest = createDiscordRequestClient(params.token, {
|
||||
fetch: params.proxyFetch,
|
||||
|
||||
@@ -194,15 +194,18 @@ describe("monitorDiscordProvider", () => {
|
||||
Parameters<typeof providerTesting.setLoadDiscordProviderSessionRuntime>[0]
|
||||
>,
|
||||
);
|
||||
providerTesting.setCreateClient((options, handlers) => {
|
||||
providerTesting.setCreateClient((options, handlers, plugins = []) => {
|
||||
clientConstructorOptionsMock(options);
|
||||
const pluginRegistry = plugins.map((plugin) => ({ id: plugin.id, plugin }));
|
||||
return {
|
||||
options,
|
||||
listeners: handlers.listeners ?? [],
|
||||
plugins: pluginRegistry,
|
||||
rest: { put: vi.fn(async () => undefined) },
|
||||
handleDeployRequest: async () => await clientHandleDeployRequestMock(),
|
||||
fetchUser: async (target: string) => await clientFetchUserMock(target),
|
||||
getPlugin: (name: string) => clientGetPluginMock(name),
|
||||
getPlugin: (name: string) =>
|
||||
clientGetPluginMock(name) ?? pluginRegistry.find((entry) => entry.id === name)?.plugin,
|
||||
} as never;
|
||||
});
|
||||
providerTesting.setGetPluginCommandSpecs((provider?: string) =>
|
||||
|
||||
Reference in New Issue
Block a user