mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-07 22:44:16 +00:00
fix(discord): use fetch for voice upload slots
This commit is contained in:
@@ -15,6 +15,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Docs/tool-loop detection config keys: align `docs/tools/loop-detection.md` examples and field names with the current `tools.loopDetection` schema to prevent copy-paste validation failures from outdated keys. (#33182) Thanks @Mylszd.
|
||||
- Discord/presence defaults: send an online presence update on ready when no custom presence is configured so bots no longer appear offline by default. Thanks @thewilloftheshadow.
|
||||
- Discord/typing cleanup: stop typing indicators after silent/NO_REPLY runs by marking the run complete before dispatch idle cleanup. Thanks @thewilloftheshadow.
|
||||
- Discord/voice messages: request upload slots with JSON fetch calls so voice message uploads no longer fail with content-type errors. Thanks @thewilloftheshadow.
|
||||
- Telegram/DM draft finalization reliability: require verified final-text draft emission before treating preview finalization as delivered, and fall back to normal payload send when final draft delivery is not confirmed (preventing missing final responses and preserving media/button delivery). (#32118) Thanks @OpenCils.
|
||||
- Discord/audit wildcard warnings: ignore "\*" wildcard keys when counting unresolved guild channels so doctor/status no longer warns on allow-all configs. (#33125) Thanks @thewilloftheshadow.
|
||||
- Discord/channel resolution: default bare numeric recipients to channels, harden allowlist numeric ID handling with safe fallbacks, and avoid inbound WS heartbeat stalls. (#33142) Thanks @thewilloftheshadow.
|
||||
|
||||
@@ -56,7 +56,8 @@ const allowedRawFetchCallsites = new Set([
|
||||
"extensions/voice-call/src/providers/twilio/api.ts:23",
|
||||
"src/channels/telegram/api.ts:8",
|
||||
"src/discord/send.outbound.ts:347",
|
||||
"src/discord/voice-message.ts:267",
|
||||
"src/discord/voice-message.ts:264",
|
||||
"src/discord/voice-message.ts:308",
|
||||
"src/slack/monitor/media.ts:64",
|
||||
"src/slack/monitor/media.ts:68",
|
||||
"src/slack/monitor/media.ts:82",
|
||||
|
||||
@@ -524,6 +524,7 @@ export async function sendVoiceMessageDiscord(
|
||||
opts.replyTo,
|
||||
request,
|
||||
opts.silent,
|
||||
token,
|
||||
);
|
||||
|
||||
recordChannelActivity({
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import type { RequestClient } from "@buape/carbon";
|
||||
import { RateLimitError, type RequestClient } from "@buape/carbon";
|
||||
import type { RetryRunner } from "../infra/retry-policy.js";
|
||||
import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js";
|
||||
import { parseFfprobeCodecAndSampleRate, runFfmpeg, runFfprobe } from "../media/ffmpeg-exec.js";
|
||||
@@ -245,26 +245,57 @@ export async function sendDiscordVoiceMessage(
|
||||
replyTo: string | undefined,
|
||||
request: RetryRunner,
|
||||
silent?: boolean,
|
||||
token?: string,
|
||||
): Promise<{ id: string; channel_id: string }> {
|
||||
const filename = "voice-message.ogg";
|
||||
const fileSize = audioBuffer.byteLength;
|
||||
|
||||
// Step 1: Request upload URL from Discord
|
||||
const uploadUrlResponse = await request(
|
||||
() =>
|
||||
rest.post(`/channels/${channelId}/attachments`, {
|
||||
body: {
|
||||
files: [
|
||||
{
|
||||
filename,
|
||||
file_size: fileSize,
|
||||
id: "0",
|
||||
},
|
||||
],
|
||||
},
|
||||
}) as Promise<UploadUrlResponse>,
|
||||
"voice-upload-url",
|
||||
);
|
||||
// Must use fetch() directly instead of rest.post() because @buape/carbon's
|
||||
// RequestClient auto-converts requests to multipart/form-data when the body
|
||||
// contains a "files" key. Discord's /attachments endpoint expects JSON, so
|
||||
// the auto-conversion causes HTTP 400 "Expected Content-Type application/json".
|
||||
const botToken = token;
|
||||
if (!botToken) {
|
||||
throw new Error("Discord bot token is required for voice message upload");
|
||||
}
|
||||
const uploadUrlResponse = await request(async () => {
|
||||
const url = `${rest.options?.baseUrl ?? "https://discord.com/api"}/channels/${channelId}/attachments`;
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bot ${botToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
files: [{ filename, file_size: fileSize, id: "0" }],
|
||||
}),
|
||||
});
|
||||
if (!res.ok) {
|
||||
if (res.status === 429) {
|
||||
const retryData = (await res.json().catch(() => ({}))) as {
|
||||
message?: string;
|
||||
retry_after?: number;
|
||||
global?: boolean;
|
||||
};
|
||||
throw new RateLimitError(res, {
|
||||
message: retryData.message ?? "You are being rate limited.",
|
||||
retry_after: retryData.retry_after ?? 1,
|
||||
global: retryData.global ?? false,
|
||||
});
|
||||
}
|
||||
const errorBody = (await res.json().catch(() => null)) as {
|
||||
code?: number;
|
||||
message?: string;
|
||||
} | null;
|
||||
const err = new Error(`Upload URL request failed: ${res.status} ${errorBody?.message ?? ""}`);
|
||||
if (errorBody?.code !== undefined) {
|
||||
(err as Error & { code: number }).code = errorBody.code;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
return (await res.json()) as UploadUrlResponse;
|
||||
}, "voice-upload-url");
|
||||
|
||||
if (!uploadUrlResponse.attachments?.[0]) {
|
||||
throw new Error("Failed to get upload URL for voice message");
|
||||
|
||||
Reference in New Issue
Block a user