@openclaw/qqbot 2026.7.1-beta.1 → 2026.7.1-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +6 -6
- package/dist/{channel-Bh0MrYvU.js → channel-D1UztsnG.js} +84 -34
- package/dist/{channel-entry-LMtNY-J6.js → channel-entry-C5YdhX3Y.js} +3 -3
- package/dist/channel-entry-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.setup-CldhUlTO.js → channel.setup-2ItDYKhz.js} +1 -1
- package/dist/{config-schema-gMW9zKvQ.js → config-schema-JZEf1dvB.js} +2 -5
- package/dist/{gateway-BgfQAYxB.js → gateway-pJQppxe4.js} +344 -138
- package/dist/{group-DQQsZ6Y0.js → group-o0GmovSf.js} +14 -11
- package/dist/{handler-runtime-BQ72YPsH.js → handler-runtime-zQvT6SrI.js} +3 -3
- package/dist/{log-SDfMMBWe.js → log-DEtcoDWe.js} +3 -4
- package/dist/{outbound-Dfa2uOEp.js → outbound-BIrfvvFJ.js} +310 -68
- package/dist/{runtime-BL0xZzWe.js → runtime-DodcT_fQ.js} +1 -1
- package/dist/runtime-api.js +1 -1
- package/dist/{sender-BHWpE_yH.js → sender-CjDuU-uz.js} +31 -25
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{tools-BvXe7bTq.js → tools-UJJ-tLHP.js} +19 -36
- package/dist/tools-api.js +1 -1
- package/npm-shrinkwrap.json +3 -3
- package/package.json +4 -4
- package/skills/qqbot-media/SKILL.md +14 -11
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import { A as getFileTypeName, D as downloadFile, E as checkFileSize, L as __exportAll, M as getMaxUploadSize, N as readFileAsync, O as fileExistsAsync, S as UploadDailyLimitExceededError, g as sendText$1, h as sendMedia$1, j as getImageMimeType, k as formatFileSize, t as accountToCreds, u as initApiConfig, w as UPLOAD_PREPARE_FALLBACK_CODE } from "./sender-
|
|
1
|
+
import { A as getFileTypeName, D as downloadFile, E as checkFileSize, L as __exportAll, M as getMaxUploadSize, N as readFileAsync, O as fileExistsAsync, S as UploadDailyLimitExceededError, g as sendText$1, h as sendMedia$1, j as getImageMimeType, k as formatFileSize, t as accountToCreds, u as initApiConfig, w as UPLOAD_PREPARE_FALLBACK_CODE } from "./sender-CjDuU-uz.js";
|
|
2
2
|
import { c as getPlatformAdapter, i as normalizeOptionalString, n as normalizeLowercaseStringOrEmpty, s as sanitizeFileName } from "./string-normalize-R_0cKO7Q.js";
|
|
3
|
-
import { a as formatErrorMessage, n as debugLog, r as debugWarn, t as debugError } from "./log-
|
|
3
|
+
import { a as formatErrorMessage, n as debugLog, r as debugWarn, t as debugError } from "./log-DEtcoDWe.js";
|
|
4
4
|
import { r as parseTarget$1 } from "./target-parser-DagYRQkP.js";
|
|
5
|
-
import
|
|
5
|
+
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
6
|
+
import * as fs$2 from "node:fs";
|
|
6
7
|
import * as os$1 from "node:os";
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
7
9
|
import { pathExistsSync, resolveLocalPathFromRootsSync } from "openclaw/plugin-sdk/security-runtime";
|
|
8
10
|
import * as path$1 from "node:path";
|
|
9
11
|
import path from "node:path";
|
|
10
|
-
import { getFileExtension } from "openclaw/plugin-sdk/media-mime";
|
|
12
|
+
import { extensionForMime, getFileExtension } from "openclaw/plugin-sdk/media-mime";
|
|
13
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
14
|
+
import { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
|
|
11
15
|
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/sandbox";
|
|
12
16
|
//#region extensions/qqbot/src/engine/utils/platform.ts
|
|
13
17
|
/**
|
|
@@ -32,10 +36,10 @@ import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/sandbox";
|
|
|
32
36
|
function getHomeDir() {
|
|
33
37
|
try {
|
|
34
38
|
const home = os$1.homedir();
|
|
35
|
-
if (home && fs$
|
|
39
|
+
if (home && fs$2.existsSync(home)) return home;
|
|
36
40
|
} catch {}
|
|
37
41
|
const envHome = process.env.HOME || process.env.USERPROFILE;
|
|
38
|
-
if (envHome && fs$
|
|
42
|
+
if (envHome && fs$2.existsSync(envHome)) return envHome;
|
|
39
43
|
return getPlatformAdapter().getTempDir();
|
|
40
44
|
}
|
|
41
45
|
/**
|
|
@@ -76,7 +80,7 @@ function getQQBotDataPath(...subPaths) {
|
|
|
76
80
|
/** Return a path under `~/.openclaw/qqbot`, creating it on demand. */
|
|
77
81
|
function getQQBotDataDir(...subPaths) {
|
|
78
82
|
const dir = getQQBotDataPath(...subPaths);
|
|
79
|
-
if (!fs$
|
|
83
|
+
if (!fs$2.existsSync(dir)) fs$2.mkdirSync(dir, { recursive: true });
|
|
80
84
|
return dir;
|
|
81
85
|
}
|
|
82
86
|
/**
|
|
@@ -94,7 +98,7 @@ function getQQBotMediaPath(...subPaths) {
|
|
|
94
98
|
/** Return a path under `<openclaw-home>/.openclaw/media/qqbot`, creating it on demand. */
|
|
95
99
|
function getQQBotMediaDir(...subPaths) {
|
|
96
100
|
const dir = getQQBotMediaPath(...subPaths);
|
|
97
|
-
if (!fs$
|
|
101
|
+
if (!fs$2.existsSync(dir)) fs$2.mkdirSync(dir, { recursive: true });
|
|
98
102
|
return dir;
|
|
99
103
|
}
|
|
100
104
|
/**
|
|
@@ -124,7 +128,7 @@ async function checkSilkWasmAvailable() {
|
|
|
124
128
|
if (silkWasmAvailable !== null) return silkWasmAvailable;
|
|
125
129
|
try {
|
|
126
130
|
const { isSilk } = await import("silk-wasm");
|
|
127
|
-
isSilk(new Uint8Array(0));
|
|
131
|
+
isSilk(/* @__PURE__ */ new Uint8Array(0));
|
|
128
132
|
silkWasmAvailable = true;
|
|
129
133
|
debugLog("[platform] silk-wasm: available");
|
|
130
134
|
} catch (err) {
|
|
@@ -163,20 +167,20 @@ function isLocalPath(p) {
|
|
|
163
167
|
if (p.startsWith(".\\") || p.startsWith("..\\")) return true;
|
|
164
168
|
return false;
|
|
165
169
|
}
|
|
166
|
-
function isPathWithinRoot(candidate, root) {
|
|
170
|
+
function isPathWithinRoot$1(candidate, root) {
|
|
167
171
|
const relative = path$1.relative(root, candidate);
|
|
168
172
|
return relative === "" || !relative.startsWith("..") && !path$1.isAbsolute(relative);
|
|
169
173
|
}
|
|
170
174
|
/** Remap legacy or hallucinated QQ Bot local media paths to real files when possible. */
|
|
171
175
|
function resolveQQBotLocalMediaPath(p) {
|
|
172
176
|
const normalized = normalizePath$1(p);
|
|
173
|
-
if (!isLocalPath(normalized) || fs$
|
|
177
|
+
if (!isLocalPath(normalized) || fs$2.existsSync(normalized)) return normalized;
|
|
174
178
|
const osHomeDir = getHomeDir();
|
|
175
179
|
const openclawHomeDir = resolveOpenClawHome();
|
|
176
180
|
const mediaRoot = getQQBotMediaPath();
|
|
177
181
|
const dataRoot = getQQBotDataPath();
|
|
178
182
|
const candidateRoots = [
|
|
179
|
-
...Array.from(new Set([path$1.join(osHomeDir, ".openclaw", "workspace", "qqbot"), path$1.join(openclawHomeDir, ".openclaw", "workspace", "qqbot")])).map((from) => ({
|
|
183
|
+
...Array.from(/* @__PURE__ */ new Set([path$1.join(osHomeDir, ".openclaw", "workspace", "qqbot"), path$1.join(openclawHomeDir, ".openclaw", "workspace", "qqbot")])).map((from) => ({
|
|
180
184
|
from,
|
|
181
185
|
to: mediaRoot
|
|
182
186
|
})),
|
|
@@ -190,10 +194,10 @@ function resolveQQBotLocalMediaPath(p) {
|
|
|
190
194
|
}
|
|
191
195
|
];
|
|
192
196
|
for (const { from, to } of candidateRoots) {
|
|
193
|
-
if (!isPathWithinRoot(normalized, from)) continue;
|
|
197
|
+
if (!isPathWithinRoot$1(normalized, from)) continue;
|
|
194
198
|
const relative = path$1.relative(from, normalized);
|
|
195
199
|
const candidate = path$1.join(to, relative);
|
|
196
|
-
if (fs$
|
|
200
|
+
if (fs$2.existsSync(candidate)) {
|
|
197
201
|
debugWarn(`[platform] Remapped missing QQBot media path ${normalized} -> ${candidate}`);
|
|
198
202
|
return candidate;
|
|
199
203
|
}
|
|
@@ -208,12 +212,12 @@ function resolveQQBotPayloadLocalFilePath(p) {
|
|
|
208
212
|
const candidate = resolveQQBotLocalMediaPath(p);
|
|
209
213
|
if (!candidate.trim()) return null;
|
|
210
214
|
const resolvedCandidate = path$1.resolve(candidate);
|
|
211
|
-
if (!fs$
|
|
212
|
-
const canonicalCandidate = fs$
|
|
215
|
+
if (!fs$2.existsSync(resolvedCandidate)) return null;
|
|
216
|
+
const canonicalCandidate = fs$2.realpathSync(resolvedCandidate);
|
|
213
217
|
const allowedRoots = [getOpenClawMediaDir(), getQQBotMediaPath()];
|
|
214
218
|
for (const root of allowedRoots) {
|
|
215
219
|
const resolvedRoot = path$1.resolve(root);
|
|
216
|
-
if (isPathWithinRoot(canonicalCandidate, fs$
|
|
220
|
+
if (isPathWithinRoot$1(canonicalCandidate, fs$2.existsSync(resolvedRoot) ? fs$2.realpathSync(resolvedRoot) : resolvedRoot)) return canonicalCandidate;
|
|
217
221
|
}
|
|
218
222
|
return null;
|
|
219
223
|
}
|
|
@@ -406,6 +410,46 @@ function buildFileTooLargeResult(fileType, fileSize) {
|
|
|
406
410
|
};
|
|
407
411
|
}
|
|
408
412
|
//#endregion
|
|
413
|
+
//#region extensions/qqbot/src/engine/messaging/outbound-media-path.ts
|
|
414
|
+
function mergeMediaLocalRoots(...groups) {
|
|
415
|
+
const roots = groups.flatMap((group) => group ?? []).map((root) => root.trim()).filter(Boolean);
|
|
416
|
+
return roots.length > 0 ? Array.from(new Set(roots)) : void 0;
|
|
417
|
+
}
|
|
418
|
+
function resolveOutboundMediaLocalRoots(ctx) {
|
|
419
|
+
return mergeMediaLocalRoots(ctx.mediaAccess?.localRoots, ctx.mediaLocalRoots);
|
|
420
|
+
}
|
|
421
|
+
function isPathWithinRoot(candidatePath, rootPath) {
|
|
422
|
+
const resolvedRoot = path.resolve(rootPath);
|
|
423
|
+
if (resolvedRoot === path.parse(resolvedRoot).root) return false;
|
|
424
|
+
const relative = path.relative(resolvedRoot, path.resolve(candidatePath));
|
|
425
|
+
return relative === "" || relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
426
|
+
}
|
|
427
|
+
function resolvePathInsideWorkspace(workspaceDir, pathWithinWorkspace) {
|
|
428
|
+
const mappedPath = path.resolve(workspaceDir, pathWithinWorkspace);
|
|
429
|
+
return isPathWithinRoot(mappedPath, workspaceDir) ? mappedPath : null;
|
|
430
|
+
}
|
|
431
|
+
function isVirtualWorkspacePath(normalizedPath) {
|
|
432
|
+
return normalizedPath === "/workspace" || normalizedPath.startsWith("/workspace/");
|
|
433
|
+
}
|
|
434
|
+
function resolveWorkspaceScopedLocalRoots(roots, workspaceDir) {
|
|
435
|
+
if (!roots?.length) return;
|
|
436
|
+
const scopedRoots = roots.map((root) => root.trim()).filter(Boolean).map((root) => workspaceDir && isVirtualWorkspacePath(root) ? resolveWorkspacePathCandidate(root, workspaceDir) : root).filter((root) => Boolean(root));
|
|
437
|
+
return scopedRoots.length > 0 ? Array.from(new Set(scopedRoots)) : void 0;
|
|
438
|
+
}
|
|
439
|
+
function resolveWorkspacePathCandidate(normalizedPath, workspaceDir) {
|
|
440
|
+
if (!workspaceDir) return isVirtualWorkspacePath(normalizedPath) ? null : normalizedPath;
|
|
441
|
+
if (normalizedPath === "/workspace") return workspaceDir;
|
|
442
|
+
if (normalizedPath.startsWith("/workspace/")) return resolvePathInsideWorkspace(workspaceDir, normalizedPath.slice(11));
|
|
443
|
+
if (path.isAbsolute(normalizedPath)) return normalizedPath;
|
|
444
|
+
return resolvePathInsideWorkspace(workspaceDir, normalizedPath);
|
|
445
|
+
}
|
|
446
|
+
function resolveWorkspacePathCandidates(normalizedPath, workspaceDir) {
|
|
447
|
+
const mappedPath = resolveWorkspacePathCandidate(normalizedPath, workspaceDir);
|
|
448
|
+
if (!mappedPath) return [];
|
|
449
|
+
if (mappedPath === normalizedPath) return [normalizedPath];
|
|
450
|
+
return path.isAbsolute(normalizedPath) && !isVirtualWorkspacePath(normalizedPath) ? [normalizedPath, mappedPath] : [mappedPath];
|
|
451
|
+
}
|
|
452
|
+
//#endregion
|
|
409
453
|
//#region extensions/qqbot/src/engine/messaging/trusted-media-path.ts
|
|
410
454
|
let cachedTrustedTmpRoot;
|
|
411
455
|
function trustedOpenClawTmpRoot() {
|
|
@@ -462,11 +506,15 @@ function parseTarget(to) {
|
|
|
462
506
|
/** Build a media target from a normal outbound context. */
|
|
463
507
|
function buildMediaTarget(ctx) {
|
|
464
508
|
const target = parseTarget(ctx.to);
|
|
509
|
+
const mediaLocalRoots = resolveOutboundMediaLocalRoots(ctx);
|
|
465
510
|
return {
|
|
466
511
|
targetType: target.type,
|
|
467
512
|
targetId: target.id,
|
|
468
513
|
account: ctx.account,
|
|
469
|
-
replyToId: ctx.replyToId ?? void 0
|
|
514
|
+
replyToId: ctx.replyToId ?? void 0,
|
|
515
|
+
...mediaLocalRoots ? { mediaLocalRoots } : {},
|
|
516
|
+
...ctx.mediaAccess ? { mediaAccess: ctx.mediaAccess } : {},
|
|
517
|
+
...ctx.mediaReadFile ? { mediaReadFile: ctx.mediaReadFile } : {}
|
|
470
518
|
};
|
|
471
519
|
}
|
|
472
520
|
/** Return true when public URLs should be passed through directly. */
|
|
@@ -480,19 +528,28 @@ const qqBotMediaKindLabel = {
|
|
|
480
528
|
file: "File",
|
|
481
529
|
media: "Media"
|
|
482
530
|
};
|
|
531
|
+
function isHttpUrl(pathValue) {
|
|
532
|
+
return pathValue.startsWith("http://") || pathValue.startsWith("https://");
|
|
533
|
+
}
|
|
534
|
+
function isDataUrl(pathValue) {
|
|
535
|
+
return pathValue.startsWith("data:");
|
|
536
|
+
}
|
|
483
537
|
function isHttpOrDataSource(pathValue) {
|
|
484
|
-
return pathValue
|
|
538
|
+
return isHttpUrl(pathValue) || isDataUrl(pathValue);
|
|
485
539
|
}
|
|
486
|
-
function
|
|
540
|
+
function resolveMissingPathWithinRoots(normalizedPath, allowedRoots) {
|
|
487
541
|
const resolvedCandidate = path.resolve(normalizedPath);
|
|
488
542
|
if (pathExistsSync(resolvedCandidate)) return null;
|
|
489
543
|
return resolveLocalPathFromRootsSync({
|
|
490
544
|
filePath: resolvedCandidate,
|
|
491
|
-
roots:
|
|
492
|
-
label: "QQ Bot
|
|
545
|
+
roots: allowedRoots,
|
|
546
|
+
label: "QQ Bot local roots",
|
|
493
547
|
allowMissing: true
|
|
494
548
|
})?.path ?? null;
|
|
495
549
|
}
|
|
550
|
+
function isPathWithinAnyRoot(candidatePath, allowedRoots) {
|
|
551
|
+
return allowedRoots?.some((root) => root.trim() && isPathWithinRoot(candidatePath, root)) ?? false;
|
|
552
|
+
}
|
|
496
553
|
function resolveExistingPathWithinRoots(normalizedPath, allowedRoots) {
|
|
497
554
|
return resolveLocalPathFromRootsSync({
|
|
498
555
|
filePath: normalizedPath,
|
|
@@ -500,30 +557,166 @@ function resolveExistingPathWithinRoots(normalizedPath, allowedRoots) {
|
|
|
500
557
|
label: "QQ Bot local roots"
|
|
501
558
|
})?.path ?? null;
|
|
502
559
|
}
|
|
560
|
+
function resolveOutboundMediaReadFile(ctx) {
|
|
561
|
+
return ctx.mediaAccess?.readFile ?? ctx.mediaReadFile;
|
|
562
|
+
}
|
|
563
|
+
function resolveHostReadMediaAccess(ctx) {
|
|
564
|
+
const mediaLocalRoots = resolveWorkspaceScopedLocalRoots(resolveOutboundMediaLocalRoots(ctx), ctx.mediaAccess?.workspaceDir);
|
|
565
|
+
if (!ctx.mediaAccess && !mediaLocalRoots) return;
|
|
566
|
+
const { localRoots: _localRoots, ...mediaAccessWithoutRoots } = ctx.mediaAccess ?? {};
|
|
567
|
+
return {
|
|
568
|
+
...mediaAccessWithoutRoots,
|
|
569
|
+
...mediaLocalRoots ? { localRoots: mediaLocalRoots } : {}
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
function mediaFileTypeForKind(mediaKind) {
|
|
573
|
+
switch (mediaKind) {
|
|
574
|
+
case "image": return 1;
|
|
575
|
+
case "voice": return 3;
|
|
576
|
+
case "video": return 2;
|
|
577
|
+
default: return 4;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
function senderKindForLoadedMedia(mediaKind, loadedKind) {
|
|
581
|
+
if (mediaKind === "image") return loadedKind === "image" ? "image" : null;
|
|
582
|
+
if (mediaKind === "video") return loadedKind === "video" ? "video" : null;
|
|
583
|
+
if (mediaKind === "file") return "file";
|
|
584
|
+
if (loadedKind === "image") return "image";
|
|
585
|
+
if (loadedKind === "video") return "video";
|
|
586
|
+
return "file";
|
|
587
|
+
}
|
|
588
|
+
function resolveHostReadMediaPath(ctx, mediaPath) {
|
|
589
|
+
const normalizedPath = normalizePath$1(mediaPath);
|
|
590
|
+
if (path.isAbsolute(normalizedPath)) {
|
|
591
|
+
if (normalizedPath === "/workspace" || normalizedPath.startsWith("/workspace/")) return ctx.mediaAccess?.workspaceDir ? resolveWorkspacePathCandidate(normalizedPath, ctx.mediaAccess.workspaceDir) : null;
|
|
592
|
+
if (isPathWithinAnyRoot(normalizedPath, resolveOutboundMediaLocalRoots(ctx))) return normalizedPath;
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
595
|
+
if (!ctx.mediaAccess?.workspaceDir) return null;
|
|
596
|
+
return resolveWorkspacePathCandidate(normalizedPath, ctx.mediaAccess.workspaceDir);
|
|
597
|
+
}
|
|
598
|
+
async function stageLoadedHostReadVoice(mediaPath, loaded) {
|
|
599
|
+
const stagedDir = getQQBotMediaDir("host-read", "voice");
|
|
600
|
+
await mkdir(stagedDir, { recursive: true });
|
|
601
|
+
const rawFileName = sanitizeFileName(loaded.fileName || path.basename(mediaPath) || "voice");
|
|
602
|
+
const ext = path.extname(rawFileName);
|
|
603
|
+
const inferredExt = extensionForMime(loaded.contentType);
|
|
604
|
+
const baseName = sanitizeFileName(path.basename(rawFileName, ext)) || "voice";
|
|
605
|
+
const stagedPath = path.join(stagedDir, `${baseName}-${randomUUID()}${ext || inferredExt || ".bin"}`);
|
|
606
|
+
await writeFile(stagedPath, loaded.buffer);
|
|
607
|
+
return stagedPath;
|
|
608
|
+
}
|
|
609
|
+
async function stageHostReadVoice(ctx, mediaPath) {
|
|
610
|
+
const mediaReadFile = resolveOutboundMediaReadFile(ctx);
|
|
611
|
+
if (!mediaReadFile || isHttpOrDataSource(mediaPath)) return null;
|
|
612
|
+
const hostReadMediaPath = resolveHostReadMediaPath(ctx, mediaPath);
|
|
613
|
+
if (!hostReadMediaPath) return null;
|
|
614
|
+
const mediaAccess = resolveHostReadMediaAccess(ctx);
|
|
615
|
+
const loaded = await loadOutboundMediaFromUrl(hostReadMediaPath, {
|
|
616
|
+
maxBytes: getMaxUploadSize(3),
|
|
617
|
+
mediaAccess,
|
|
618
|
+
mediaReadFile,
|
|
619
|
+
workspaceDir: mediaAccess?.workspaceDir
|
|
620
|
+
});
|
|
621
|
+
if (loaded.kind !== "audio") throw new Error(`Unsupported voice media type: ${loaded.kind ?? "unknown"}`);
|
|
622
|
+
return await stageLoadedHostReadVoice(mediaPath, loaded);
|
|
623
|
+
}
|
|
624
|
+
async function trySendViaHostRead(ctx, mediaPath, mediaKind) {
|
|
625
|
+
const mediaReadFile = resolveOutboundMediaReadFile(ctx);
|
|
626
|
+
if (!mediaReadFile || isHttpOrDataSource(mediaPath)) return null;
|
|
627
|
+
const hostReadMediaPath = resolveHostReadMediaPath(ctx, mediaPath);
|
|
628
|
+
if (!hostReadMediaPath) return null;
|
|
629
|
+
const mediaAccess = resolveHostReadMediaAccess(ctx);
|
|
630
|
+
try {
|
|
631
|
+
const loaded = await loadOutboundMediaFromUrl(hostReadMediaPath, {
|
|
632
|
+
maxBytes: getMaxUploadSize(mediaFileTypeForKind(mediaKind)),
|
|
633
|
+
mediaAccess,
|
|
634
|
+
mediaReadFile,
|
|
635
|
+
workspaceDir: mediaAccess?.workspaceDir
|
|
636
|
+
});
|
|
637
|
+
const kind = senderKindForLoadedMedia(mediaKind, loaded.kind);
|
|
638
|
+
if (!kind) return {
|
|
639
|
+
channel: "qqbot",
|
|
640
|
+
error: `Unsupported ${mediaKind} media type: ${loaded.kind ?? "unknown"}`
|
|
641
|
+
};
|
|
642
|
+
if (loaded.buffer.length === 0) return {
|
|
643
|
+
channel: "qqbot",
|
|
644
|
+
error: `File is empty: ${hostReadMediaPath}`
|
|
645
|
+
};
|
|
646
|
+
if (mediaKind === "media" && loaded.kind === "audio") {
|
|
647
|
+
const directUploadFormats = ctx.account.config?.audioFormatPolicy?.uploadDirectFormats ?? ctx.account.config?.voiceDirectUploadFormats;
|
|
648
|
+
const transcodeEnabled = ctx.account.config?.audioFormatPolicy?.transcodeEnabled !== false;
|
|
649
|
+
return await sendVoiceFromLocal(ctx, await stageLoadedHostReadVoice(mediaPath, loaded), directUploadFormats, transcodeEnabled);
|
|
650
|
+
}
|
|
651
|
+
const creds = accountToCreds(ctx.account);
|
|
652
|
+
const target = {
|
|
653
|
+
type: ctx.targetType,
|
|
654
|
+
id: ctx.targetId
|
|
655
|
+
};
|
|
656
|
+
if (target.type !== "c2c" && target.type !== "group") return {
|
|
657
|
+
channel: "qqbot",
|
|
658
|
+
error: `${qqBotMediaKindLabel[mediaKind]} not supported in channel`
|
|
659
|
+
};
|
|
660
|
+
const r = await sendMedia$1({
|
|
661
|
+
target,
|
|
662
|
+
creds,
|
|
663
|
+
kind,
|
|
664
|
+
source: {
|
|
665
|
+
buffer: loaded.buffer,
|
|
666
|
+
...loaded.fileName ? { fileName: sanitizeFileName(loaded.fileName) } : {},
|
|
667
|
+
...loaded.contentType ? { mime: loaded.contentType } : {}
|
|
668
|
+
},
|
|
669
|
+
msgId: ctx.replyToId,
|
|
670
|
+
...kind === "file" && loaded.fileName ? { fileName: sanitizeFileName(loaded.fileName) } : {}
|
|
671
|
+
});
|
|
672
|
+
return {
|
|
673
|
+
channel: "qqbot",
|
|
674
|
+
messageId: r.id,
|
|
675
|
+
timestamp: r.timestamp
|
|
676
|
+
};
|
|
677
|
+
} catch (err) {
|
|
678
|
+
if (err instanceof UploadDailyLimitExceededError) return buildDailyLimitExceededResult(err.filePath === "<buffer>" ? new UploadDailyLimitExceededError(hostReadMediaPath, err.fileSize, err.message) : err);
|
|
679
|
+
return {
|
|
680
|
+
channel: "qqbot",
|
|
681
|
+
error: formatErrorMessage(err)
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
async function sendAutoDetectedMedia(ctx, mediaPath) {
|
|
686
|
+
const hostReadResult = await trySendViaHostRead(ctx, mediaPath, "media");
|
|
687
|
+
if (hostReadResult) return hostReadResult;
|
|
688
|
+
return await sendDocument(ctx, mediaPath);
|
|
689
|
+
}
|
|
503
690
|
function resolveOutboundMediaPath(rawPath, mediaKind, options = {}) {
|
|
504
691
|
const normalizedPath = normalizePath$1(rawPath);
|
|
505
692
|
if (isHttpOrDataSource(normalizedPath)) return {
|
|
506
693
|
ok: true,
|
|
507
694
|
mediaPath: normalizedPath
|
|
508
695
|
};
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
};
|
|
514
|
-
if (options.extraLocalRoots && options.extraLocalRoots.length > 0) {
|
|
515
|
-
const extraAllowedPath = resolveExistingPathWithinRoots(normalizedPath, options.extraLocalRoots);
|
|
516
|
-
if (extraAllowedPath) return {
|
|
696
|
+
const candidatePaths = resolveWorkspacePathCandidates(normalizedPath, options.workspaceDir);
|
|
697
|
+
for (const candidatePath of candidatePaths) {
|
|
698
|
+
const allowedPath = resolveTrustedOutboundMediaPath(candidatePath, { allowMissing: options.allowMissingLocalPath });
|
|
699
|
+
if (allowedPath) return {
|
|
517
700
|
ok: true,
|
|
518
|
-
mediaPath:
|
|
701
|
+
mediaPath: allowedPath
|
|
519
702
|
};
|
|
703
|
+
if (options.extraLocalRoots && options.extraLocalRoots.length > 0) {
|
|
704
|
+
const extraAllowedPath = resolveExistingPathWithinRoots(candidatePath, options.extraLocalRoots);
|
|
705
|
+
if (extraAllowedPath) return {
|
|
706
|
+
ok: true,
|
|
707
|
+
mediaPath: extraAllowedPath
|
|
708
|
+
};
|
|
709
|
+
}
|
|
520
710
|
}
|
|
521
711
|
if (options.allowMissingLocalPath) {
|
|
522
|
-
const
|
|
523
|
-
if (
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
712
|
+
const missingRoots = mergeMediaLocalRoots([getQQBotMediaDir()], options.extraLocalRoots);
|
|
713
|
+
if (missingRoots) for (const candidatePath of candidatePaths) {
|
|
714
|
+
const allowedMissingPath = resolveMissingPathWithinRoots(candidatePath, missingRoots);
|
|
715
|
+
if (allowedMissingPath) return {
|
|
716
|
+
ok: true,
|
|
717
|
+
mediaPath: allowedMissingPath
|
|
718
|
+
};
|
|
719
|
+
}
|
|
527
720
|
}
|
|
528
721
|
debugWarn(`blocked local ${mediaKind} path outside QQ Bot media storage`);
|
|
529
722
|
return {
|
|
@@ -535,28 +728,33 @@ function resolveOutboundMediaPath(rawPath, mediaKind, options = {}) {
|
|
|
535
728
|
* Send a photo from a local file, public URL, or Base64 data URL.
|
|
536
729
|
*/
|
|
537
730
|
async function sendPhoto(ctx, imagePath) {
|
|
538
|
-
const
|
|
731
|
+
const hostReadResult = await trySendViaHostRead(ctx, imagePath, "image");
|
|
732
|
+
if (hostReadResult) return hostReadResult;
|
|
733
|
+
const resolvedMediaPath = resolveOutboundMediaPath(imagePath, "image", {
|
|
734
|
+
extraLocalRoots: resolveOutboundMediaLocalRoots(ctx),
|
|
735
|
+
workspaceDir: ctx.mediaAccess?.workspaceDir
|
|
736
|
+
});
|
|
539
737
|
if (!resolvedMediaPath.ok) return {
|
|
540
738
|
channel: "qqbot",
|
|
541
739
|
error: resolvedMediaPath.error
|
|
542
740
|
};
|
|
543
741
|
const mediaPath = resolvedMediaPath.mediaPath;
|
|
544
742
|
const isLocal = isLocalPath(mediaPath);
|
|
545
|
-
const isHttp =
|
|
546
|
-
const isData = mediaPath
|
|
743
|
+
const isHttp = isHttpUrl(mediaPath);
|
|
744
|
+
const isData = isDataUrl(mediaPath);
|
|
547
745
|
if (isHttp && !shouldDirectUploadUrl(ctx.account)) {
|
|
548
746
|
debugLog(`sendPhoto: urlDirectUpload=false, downloading URL first...`);
|
|
549
747
|
const localFile = await downloadToFallbackDir(mediaPath, "sendPhoto");
|
|
550
748
|
if (localFile) return await sendPhotoFromLocal(ctx, localFile);
|
|
551
749
|
return {
|
|
552
750
|
channel: "qqbot",
|
|
553
|
-
error: `Failed to download image: ${mediaPath
|
|
751
|
+
error: `Failed to download image: ${truncateUtf16Safe(mediaPath, 80)}`
|
|
554
752
|
};
|
|
555
753
|
}
|
|
556
754
|
if (isLocal) return await sendPhotoFromLocal(ctx, mediaPath);
|
|
557
755
|
if (!isHttp && !isData) return {
|
|
558
756
|
channel: "qqbot",
|
|
559
|
-
error: `Unsupported image source: ${mediaPath
|
|
757
|
+
error: `Unsupported image source: ${truncateUtf16Safe(mediaPath, 50)}`
|
|
560
758
|
};
|
|
561
759
|
try {
|
|
562
760
|
const creds = accountToCreds(ctx.account);
|
|
@@ -663,13 +861,29 @@ async function sendPhotoFromLocal(ctx, mediaPath) {
|
|
|
663
861
|
* URL handling respects `urlDirectUpload`, and local files are transcoded when needed.
|
|
664
862
|
*/
|
|
665
863
|
async function sendVoice(ctx, voicePath, directUploadFormats, transcodeEnabled = true) {
|
|
666
|
-
|
|
864
|
+
let stagedHostReadVoice;
|
|
865
|
+
try {
|
|
866
|
+
stagedHostReadVoice = await stageHostReadVoice(ctx, voicePath);
|
|
867
|
+
} catch (err) {
|
|
868
|
+
return {
|
|
869
|
+
channel: "qqbot",
|
|
870
|
+
error: formatErrorMessage(err)
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
const resolvedMediaPath = stagedHostReadVoice ? {
|
|
874
|
+
ok: true,
|
|
875
|
+
mediaPath: stagedHostReadVoice
|
|
876
|
+
} : resolveOutboundMediaPath(voicePath, "voice", {
|
|
877
|
+
allowMissingLocalPath: true,
|
|
878
|
+
extraLocalRoots: resolveOutboundMediaLocalRoots(ctx),
|
|
879
|
+
workspaceDir: ctx.mediaAccess?.workspaceDir
|
|
880
|
+
});
|
|
667
881
|
if (!resolvedMediaPath.ok) return {
|
|
668
882
|
channel: "qqbot",
|
|
669
883
|
error: resolvedMediaPath.error
|
|
670
884
|
};
|
|
671
885
|
const mediaPath = resolvedMediaPath.mediaPath;
|
|
672
|
-
if (
|
|
886
|
+
if (isHttpUrl(mediaPath)) {
|
|
673
887
|
if (shouldDirectUploadUrl(ctx.account)) try {
|
|
674
888
|
const creds = accountToCreds(ctx.account);
|
|
675
889
|
const target = {
|
|
@@ -703,7 +917,7 @@ async function sendVoice(ctx, voicePath, directUploadFormats, transcodeEnabled =
|
|
|
703
917
|
if (localFile) return await sendVoiceFromLocal(ctx, localFile, directUploadFormats, transcodeEnabled);
|
|
704
918
|
return {
|
|
705
919
|
channel: "qqbot",
|
|
706
|
-
error: `Failed to download audio: ${mediaPath
|
|
920
|
+
error: `Failed to download audio: ${truncateUtf16Safe(mediaPath, 80)}`
|
|
707
921
|
};
|
|
708
922
|
}
|
|
709
923
|
return await sendVoiceFromLocal(ctx, mediaPath, directUploadFormats, transcodeEnabled);
|
|
@@ -716,7 +930,8 @@ async function sendVoiceFromLocal(ctx, mediaPath, directUploadFormats, transcode
|
|
|
716
930
|
error: "Voice generate failed"
|
|
717
931
|
};
|
|
718
932
|
if (fileSize > getMaxUploadSize(3)) return buildFileTooLargeResult(3, fileSize);
|
|
719
|
-
const
|
|
933
|
+
const extraLocalRoots = resolveOutboundMediaLocalRoots(ctx);
|
|
934
|
+
const safeMediaPath = resolveTrustedOutboundMediaPath(mediaPath) ?? (extraLocalRoots ? resolveExistingPathWithinRoots(mediaPath, extraLocalRoots) : null);
|
|
720
935
|
if (!safeMediaPath) {
|
|
721
936
|
debugWarn(`sendVoice: blocked local voice path outside QQ Bot media storage`);
|
|
722
937
|
return {
|
|
@@ -779,20 +994,25 @@ async function sendVoiceFromLocal(ctx, mediaPath, directUploadFormats, transcode
|
|
|
779
994
|
}
|
|
780
995
|
/** Send video from either a public URL or a local file. */
|
|
781
996
|
async function sendVideoMsg(ctx, videoPath) {
|
|
782
|
-
const
|
|
997
|
+
const hostReadResult = await trySendViaHostRead(ctx, videoPath, "video");
|
|
998
|
+
if (hostReadResult) return hostReadResult;
|
|
999
|
+
const resolvedMediaPath = resolveOutboundMediaPath(videoPath, "video", {
|
|
1000
|
+
extraLocalRoots: resolveOutboundMediaLocalRoots(ctx),
|
|
1001
|
+
workspaceDir: ctx.mediaAccess?.workspaceDir
|
|
1002
|
+
});
|
|
783
1003
|
if (!resolvedMediaPath.ok) return {
|
|
784
1004
|
channel: "qqbot",
|
|
785
1005
|
error: resolvedMediaPath.error
|
|
786
1006
|
};
|
|
787
1007
|
const mediaPath = resolvedMediaPath.mediaPath;
|
|
788
|
-
const isHttp =
|
|
1008
|
+
const isHttp = isHttpUrl(mediaPath);
|
|
789
1009
|
if (isHttp && !shouldDirectUploadUrl(ctx.account)) {
|
|
790
1010
|
debugLog(`sendVideoMsg: urlDirectUpload=false, downloading URL first...`);
|
|
791
1011
|
const localFile = await downloadToFallbackDir(mediaPath, "sendVideoMsg");
|
|
792
1012
|
if (localFile) return await sendVideoFromLocal(ctx, localFile);
|
|
793
1013
|
return {
|
|
794
1014
|
channel: "qqbot",
|
|
795
|
-
error: `Failed to download video: ${mediaPath
|
|
1015
|
+
error: `Failed to download video: ${truncateUtf16Safe(mediaPath, 80)}`
|
|
796
1016
|
};
|
|
797
1017
|
}
|
|
798
1018
|
try {
|
|
@@ -887,13 +1107,18 @@ async function sendVideoFromLocal(ctx, mediaPath) {
|
|
|
887
1107
|
}
|
|
888
1108
|
/** Send a file from a local path or public URL. */
|
|
889
1109
|
async function sendDocument(ctx, filePath, options = {}) {
|
|
890
|
-
const
|
|
1110
|
+
const hostReadResult = await trySendViaHostRead(ctx, filePath, "file");
|
|
1111
|
+
if (hostReadResult) return hostReadResult;
|
|
1112
|
+
const resolvedMediaPath = resolveOutboundMediaPath(filePath, "file", {
|
|
1113
|
+
extraLocalRoots: mergeMediaLocalRoots(options.allowQQBotDataDownloads ? [getQQBotDataDir("downloads")] : void 0, resolveOutboundMediaLocalRoots(ctx)),
|
|
1114
|
+
workspaceDir: ctx.mediaAccess?.workspaceDir
|
|
1115
|
+
});
|
|
891
1116
|
if (!resolvedMediaPath.ok) return {
|
|
892
1117
|
channel: "qqbot",
|
|
893
1118
|
error: resolvedMediaPath.error
|
|
894
1119
|
};
|
|
895
1120
|
const mediaPath = resolvedMediaPath.mediaPath;
|
|
896
|
-
const isHttp =
|
|
1121
|
+
const isHttp = isHttpUrl(mediaPath);
|
|
897
1122
|
const fileName = sanitizeFileName(path.basename(mediaPath));
|
|
898
1123
|
if (isHttp && !shouldDirectUploadUrl(ctx.account)) {
|
|
899
1124
|
debugLog(`sendDocument: urlDirectUpload=false, downloading URL first...`);
|
|
@@ -901,7 +1126,7 @@ async function sendDocument(ctx, filePath, options = {}) {
|
|
|
901
1126
|
if (localFile) return await sendDocumentFromLocal(ctx, localFile);
|
|
902
1127
|
return {
|
|
903
1128
|
channel: "qqbot",
|
|
904
|
-
error: `Failed to download file: ${mediaPath
|
|
1129
|
+
error: `Failed to download file: ${truncateUtf16Safe(mediaPath, 80)}`
|
|
905
1130
|
};
|
|
906
1131
|
}
|
|
907
1132
|
try {
|
|
@@ -918,7 +1143,7 @@ async function sendDocument(ctx, filePath, options = {}) {
|
|
|
918
1143
|
kind: "file",
|
|
919
1144
|
source: { url: mediaPath },
|
|
920
1145
|
msgId: ctx.replyToId,
|
|
921
|
-
fileName
|
|
1146
|
+
...fileName ? { fileName } : {}
|
|
922
1147
|
});
|
|
923
1148
|
return {
|
|
924
1149
|
channel: "qqbot",
|
|
@@ -1006,7 +1231,7 @@ async function downloadToFallbackDir(httpUrl, caller) {
|
|
|
1006
1231
|
try {
|
|
1007
1232
|
const localFile = await downloadFile(httpUrl, getQQBotMediaDir("downloads", "url-fallback"));
|
|
1008
1233
|
if (!localFile) {
|
|
1009
|
-
debugError(`${caller} fallback: download also failed for ${httpUrl
|
|
1234
|
+
debugError(`${caller} fallback: download also failed for ${truncateUtf16Safe(httpUrl, 80)}`);
|
|
1010
1235
|
return null;
|
|
1011
1236
|
}
|
|
1012
1237
|
debugLog(`${caller} fallback: downloaded → ${localFile}`);
|
|
@@ -1171,7 +1396,8 @@ function parseQQBotPayload(text) {
|
|
|
1171
1396
|
/** Encode a cron reminder payload into the stored cron-message format. */
|
|
1172
1397
|
function encodePayloadForCron(payload) {
|
|
1173
1398
|
const jsonString = JSON.stringify(payload);
|
|
1174
|
-
|
|
1399
|
+
const base64 = Buffer.from(jsonString, "utf-8").toString("base64");
|
|
1400
|
+
return `${CRON_PREFIX}${base64}`;
|
|
1175
1401
|
}
|
|
1176
1402
|
/** Decode a stored cron payload. */
|
|
1177
1403
|
function decodeCronPayload(message) {
|
|
@@ -1288,7 +1514,7 @@ function decodeMediaPath(raw, log) {
|
|
|
1288
1514
|
* These replace the inline `isImageFile` and `isVideoFile` helpers scattered
|
|
1289
1515
|
* across `outbound.ts`. Centralizing them here keeps detection consistent.
|
|
1290
1516
|
*/
|
|
1291
|
-
const IMAGE_EXTENSIONS = new Set([
|
|
1517
|
+
const IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1292
1518
|
".jpg",
|
|
1293
1519
|
".jpeg",
|
|
1294
1520
|
".png",
|
|
@@ -1296,7 +1522,7 @@ const IMAGE_EXTENSIONS = new Set([
|
|
|
1296
1522
|
".webp",
|
|
1297
1523
|
".bmp"
|
|
1298
1524
|
]);
|
|
1299
|
-
const VIDEO_EXTENSIONS = new Set([
|
|
1525
|
+
const VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1300
1526
|
".mp4",
|
|
1301
1527
|
".mov",
|
|
1302
1528
|
".avi",
|
|
@@ -1358,7 +1584,7 @@ async function sendText(ctx) {
|
|
|
1358
1584
|
initApiConfig(account.appId, { markdownSupport: account.markdownSupport });
|
|
1359
1585
|
debugLog("[qqbot] sendText ctx:", JSON.stringify({
|
|
1360
1586
|
to,
|
|
1361
|
-
text: text
|
|
1587
|
+
text: truncateUtf16Safe(text, 50),
|
|
1362
1588
|
replyToId,
|
|
1363
1589
|
accountId: account.accountId
|
|
1364
1590
|
}, null, 2));
|
|
@@ -1435,7 +1661,10 @@ async function sendText(ctx) {
|
|
|
1435
1661
|
const mediaTarget = buildMediaTarget({
|
|
1436
1662
|
to,
|
|
1437
1663
|
account,
|
|
1438
|
-
replyToId
|
|
1664
|
+
replyToId,
|
|
1665
|
+
mediaAccess: ctx.mediaAccess,
|
|
1666
|
+
mediaLocalRoots: ctx.mediaLocalRoots,
|
|
1667
|
+
mediaReadFile: ctx.mediaReadFile
|
|
1439
1668
|
});
|
|
1440
1669
|
let lastResult = { channel: "qqbot" };
|
|
1441
1670
|
for (const item of sendQueue) try {
|
|
@@ -1453,7 +1682,7 @@ async function sendText(ctx) {
|
|
|
1453
1682
|
timestamp: result.timestamp,
|
|
1454
1683
|
refIdx: result.ext_info?.ref_idx
|
|
1455
1684
|
};
|
|
1456
|
-
debugLog(`[qqbot] sendText: Sent text part: ${item.content
|
|
1685
|
+
debugLog(`[qqbot] sendText: Sent text part: ${truncateUtf16Safe(item.content, 30)}...`);
|
|
1457
1686
|
} else if (item.type === "image") lastResult = await sendPhoto(mediaTarget, item.content);
|
|
1458
1687
|
else if (item.type === "voice") lastResult = await sendVoice(mediaTarget, item.content, void 0, account.config?.audioFormatPolicy?.transcodeEnabled !== false);
|
|
1459
1688
|
else if (item.type === "video") lastResult = await sendVideoMsg(mediaTarget, item.content);
|
|
@@ -1464,7 +1693,10 @@ async function sendText(ctx) {
|
|
|
1464
1693
|
mediaUrl: item.content,
|
|
1465
1694
|
accountId: account.accountId,
|
|
1466
1695
|
replyToId,
|
|
1467
|
-
account
|
|
1696
|
+
account,
|
|
1697
|
+
mediaAccess: ctx.mediaAccess,
|
|
1698
|
+
mediaLocalRoots: ctx.mediaLocalRoots,
|
|
1699
|
+
mediaReadFile: ctx.mediaReadFile
|
|
1468
1700
|
});
|
|
1469
1701
|
} catch (err) {
|
|
1470
1702
|
const errMsg = formatErrorMessage(err);
|
|
@@ -1526,17 +1758,27 @@ async function sendMedia(ctx) {
|
|
|
1526
1758
|
channel: "qqbot",
|
|
1527
1759
|
error: "mediaUrl is required for sendMedia"
|
|
1528
1760
|
};
|
|
1529
|
-
const
|
|
1761
|
+
const target = buildMediaTarget({
|
|
1762
|
+
to,
|
|
1763
|
+
account,
|
|
1764
|
+
replyToId,
|
|
1765
|
+
mediaAccess: ctx.mediaAccess,
|
|
1766
|
+
mediaLocalRoots: ctx.mediaLocalRoots,
|
|
1767
|
+
mediaReadFile: ctx.mediaReadFile
|
|
1768
|
+
});
|
|
1769
|
+
const resolvedMediaPath = !ctx.mediaAccess?.readFile && !ctx.mediaReadFile ? resolveOutboundMediaPath(ctx.mediaUrl, "media", {
|
|
1770
|
+
allowMissingLocalPath: true,
|
|
1771
|
+
extraLocalRoots: target.mediaLocalRoots ? [...target.mediaLocalRoots] : void 0,
|
|
1772
|
+
workspaceDir: target.mediaAccess?.workspaceDir
|
|
1773
|
+
}) : {
|
|
1774
|
+
ok: true,
|
|
1775
|
+
mediaPath: ctx.mediaUrl
|
|
1776
|
+
};
|
|
1530
1777
|
if (!resolvedMediaPath.ok) return {
|
|
1531
1778
|
channel: "qqbot",
|
|
1532
1779
|
error: resolvedMediaPath.error
|
|
1533
1780
|
};
|
|
1534
1781
|
const mediaUrl = resolvedMediaPath.mediaPath;
|
|
1535
|
-
const target = buildMediaTarget({
|
|
1536
|
-
to,
|
|
1537
|
-
account,
|
|
1538
|
-
replyToId
|
|
1539
|
-
});
|
|
1540
1782
|
if (isAudioFile(mediaUrl, mimeType)) {
|
|
1541
1783
|
const result = await sendVoice(target, mediaUrl, account.config?.audioFormatPolicy?.uploadDirectFormats ?? account.config?.voiceDirectUploadFormats, account.config?.audioFormatPolicy?.transcodeEnabled !== false);
|
|
1542
1784
|
if (!result.error) {
|
|
@@ -1561,7 +1803,7 @@ async function sendMedia(ctx) {
|
|
|
1561
1803
|
return result;
|
|
1562
1804
|
}
|
|
1563
1805
|
if (!isImageFile(mediaUrl, mimeType) && !isAudioFile(mediaUrl, mimeType) && !isVideoFile(mediaUrl, mimeType)) {
|
|
1564
|
-
const result = await
|
|
1806
|
+
const result = await sendAutoDetectedMedia(target, mediaUrl);
|
|
1565
1807
|
if (!result.error && text?.trim()) await sendTextAfterMedia(target, text);
|
|
1566
1808
|
return result;
|
|
1567
1809
|
}
|
|
@@ -1622,4 +1864,4 @@ async function sendCronMessage(account, to, message) {
|
|
|
1622
1864
|
});
|
|
1623
1865
|
}
|
|
1624
1866
|
//#endregion
|
|
1625
|
-
export {
|
|
1867
|
+
export { OUTBOUND_ERROR_CODES as A, normalizePath$1 as B, resolveUserFacingMediaError as C, getMessageReplyStats as D, getMessageReplyConfig as E, getQQBotMediaDir as F, getQQBotMediaPath as I, getTempDir as L, checkSilkWasmAvailable as M, getHomeDir as N, recordMessageReply as O, getQQBotDataDir as P, isLocalPath as R, resolveWorkspaceScopedLocalRoots as S, checkMessageReplyLimit as T, sendVideoMsg as _, sendText as a, resolveOutboundMediaLocalRoots as b, isCronReminderPayload as c, normalizeMediaTags as d, buildMediaTarget as f, sendPhoto as g, sendDocument as h, sendProactiveMessage as i, setOutboundAudioPort as j, DEFAULT_MEDIA_SEND_ERROR as k, isMediaPayload as l, resolveOutboundMediaPath as m, sendCronMessage as n, decodeMediaPath as o, parseTarget as p, sendMedia as r, encodePayloadForCron as s, outbound_exports as t, parseQQBotPayload as u, sendVoice as v, MESSAGE_REPLY_LIMIT as w, resolveWorkspacePathCandidates as x, resolveTrustedOutboundMediaPath as y, isWindows as z };
|