@tencent-connect/openclaw-qqbot 1.6.2-alpha.1 → 1.6.2-alpha.3
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/README.md +2 -2
- package/README.zh.md +2 -2
- package/dist/src/gateway.js +15 -33
- package/dist/src/outbound.js +17 -18
- package/dist/src/ref-index-store.d.ts +1 -1
- package/dist/src/ref-index-store.js +2 -3
- package/dist/src/user-messages.d.ts +5 -40
- package/dist/src/user-messages.js +5 -62
- package/package.json +1 -1
- package/src/gateway.ts +16 -33
- package/src/outbound.ts +17 -18
- package/src/ref-index-store.ts +3 -4
- package/src/user-messages.ts +4 -70
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
**Connect your AI assistant to QQ — private chat, group chat, and rich media, all in one plugin.**
|
|
12
12
|
|
|
13
|
-
### 🚀 Current Version: `v1.6.
|
|
13
|
+
### 🚀 Current Version: `v1.6.2`
|
|
14
14
|
|
|
15
15
|
[](./LICENSE)
|
|
16
16
|
[](https://bot.q.qq.com/wiki/)
|
|
@@ -163,7 +163,7 @@ Measures end-to-end latency from QQ server push to plugin response, broken down
|
|
|
163
163
|
|
|
164
164
|
> **You**: `/qqbot-version`
|
|
165
165
|
>
|
|
166
|
-
> **QQBot**: 🦞 Framework: OpenClaw 2026.3.13 (61d171a) / 🤖 Plugin: v1.6.
|
|
166
|
+
> **QQBot**: 🦞 Framework: OpenClaw 2026.3.13 (61d171a) / 🤖 Plugin: v1.6.2 / 🌟 GitHub repo
|
|
167
167
|
|
|
168
168
|
Shows framework version, plugin version, and a direct link to the official repository.
|
|
169
169
|
|
package/README.zh.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
**让你的 AI 助手接入 QQ — 私聊、群聊、富媒体,一个插件全搞定。**
|
|
11
11
|
|
|
12
|
-
### 🚀 当前版本: `v1.6.
|
|
12
|
+
### 🚀 当前版本: `v1.6.2`
|
|
13
13
|
|
|
14
14
|
[](./LICENSE)
|
|
15
15
|
[](https://bot.q.qq.com/wiki/)
|
|
@@ -158,7 +158,7 @@ AI 可直接发送视频,支持本地文件和公网 URL。
|
|
|
158
158
|
|
|
159
159
|
> **你**:`/qqbot-version`
|
|
160
160
|
>
|
|
161
|
-
> **QQBot**:🦞框架版本:OpenClaw 2026.3.13 (61d171a) / 🤖QQBot 插件版本:v1.6.
|
|
161
|
+
> **QQBot**:🦞框架版本:OpenClaw 2026.3.13 (61d171a) / 🤖QQBot 插件版本:v1.6.2 / 🌟官方 GitHub 仓库
|
|
162
162
|
|
|
163
163
|
一目了然查看框架版本、插件版本,并可直接跳转官方仓库。
|
|
164
164
|
|
package/dist/src/gateway.js
CHANGED
|
@@ -15,7 +15,6 @@ import { convertSilkToWav, isVoiceAttachment, formatDuration, resolveTTSConfig,
|
|
|
15
15
|
import { normalizeMediaTags } from "./utils/media-tags.js";
|
|
16
16
|
import { checkFileSize, readFileAsync, fileExistsAsync, formatFileSize } from "./utils/file-utils.js";
|
|
17
17
|
import { getQQBotDataDir, isLocalPath as isLocalFilePath, normalizePath, sanitizeFileName, runDiagnostics } from "./utils/platform.js";
|
|
18
|
-
import { MSG, formatMediaErrorMessage } from "./user-messages.js";
|
|
19
18
|
import { sendPhoto, sendVoice, sendVideoMsg, sendDocument, sendMedia as sendMediaAuto } from "./outbound.js";
|
|
20
19
|
import { chunkText, TEXT_CHUNK_LIMIT } from "./channel.js";
|
|
21
20
|
function resolveSTTConfig(cfg) {
|
|
@@ -347,7 +346,7 @@ export async function startGateway(ctx) {
|
|
|
347
346
|
attachments.push(attachment);
|
|
348
347
|
}
|
|
349
348
|
setRefIndex(refIdx, {
|
|
350
|
-
content:
|
|
349
|
+
content: meta.text ?? "",
|
|
351
350
|
senderId: account.accountId,
|
|
352
351
|
senderName: account.accountId,
|
|
353
352
|
timestamp: Date.now(),
|
|
@@ -1507,7 +1506,6 @@ export async function startGateway(ctx) {
|
|
|
1507
1506
|
const result = await sendPhoto(mediaTarget, item.content);
|
|
1508
1507
|
if (result.error) {
|
|
1509
1508
|
log?.error(`[qqbot:${account.accountId}] sendPhoto error: ${result.error}`);
|
|
1510
|
-
await sendErrorMessage(formatMediaErrorMessage("图片", new Error(result.error)));
|
|
1511
1509
|
}
|
|
1512
1510
|
}
|
|
1513
1511
|
else if (item.type === "voice") {
|
|
@@ -1522,26 +1520,22 @@ export async function startGateway(ctx) {
|
|
|
1522
1520
|
]);
|
|
1523
1521
|
if (result.error) {
|
|
1524
1522
|
log?.error(`[qqbot:${account.accountId}] sendVoice error: ${result.error}`);
|
|
1525
|
-
await sendErrorMessage(formatMediaErrorMessage("语音", new Error(result.error)));
|
|
1526
1523
|
}
|
|
1527
1524
|
}
|
|
1528
1525
|
catch (err) {
|
|
1529
1526
|
log?.error(`[qqbot:${account.accountId}] sendVoice unexpected error: ${err}`);
|
|
1530
|
-
await sendErrorMessage(formatMediaErrorMessage("语音", err));
|
|
1531
1527
|
}
|
|
1532
1528
|
}
|
|
1533
1529
|
else if (item.type === "video") {
|
|
1534
1530
|
const result = await sendVideoMsg(mediaTarget, item.content);
|
|
1535
1531
|
if (result.error) {
|
|
1536
1532
|
log?.error(`[qqbot:${account.accountId}] sendVideoMsg error: ${result.error}`);
|
|
1537
|
-
await sendErrorMessage(formatMediaErrorMessage("视频", new Error(result.error)));
|
|
1538
1533
|
}
|
|
1539
1534
|
}
|
|
1540
1535
|
else if (item.type === "file") {
|
|
1541
1536
|
const result = await sendDocument(mediaTarget, item.content);
|
|
1542
1537
|
if (result.error) {
|
|
1543
1538
|
log?.error(`[qqbot:${account.accountId}] sendDocument error: ${result.error}`);
|
|
1544
|
-
await sendErrorMessage(formatMediaErrorMessage("文件", new Error(result.error)));
|
|
1545
1539
|
}
|
|
1546
1540
|
}
|
|
1547
1541
|
else if (item.type === "media") {
|
|
@@ -1556,7 +1550,6 @@ export async function startGateway(ctx) {
|
|
|
1556
1550
|
});
|
|
1557
1551
|
if (result.error) {
|
|
1558
1552
|
log?.error(`[qqbot:${account.accountId}] sendMedia(auto) error: ${result.error}`);
|
|
1559
|
-
await sendErrorMessage(formatMediaErrorMessage("媒体", new Error(result.error)));
|
|
1560
1553
|
}
|
|
1561
1554
|
}
|
|
1562
1555
|
}
|
|
@@ -1573,9 +1566,7 @@ export async function startGateway(ctx) {
|
|
|
1573
1566
|
const payloadResult = parseQQBotPayload(replyText);
|
|
1574
1567
|
if (payloadResult.isPayload) {
|
|
1575
1568
|
if (payloadResult.error) {
|
|
1576
|
-
// 载荷解析失败,发送错误提示
|
|
1577
1569
|
log?.error(`[qqbot:${account.accountId}] Payload parse error: ${payloadResult.error}`);
|
|
1578
|
-
await sendErrorMessage(MSG.PAYLOAD_PARSE_ERROR);
|
|
1579
1570
|
return;
|
|
1580
1571
|
}
|
|
1581
1572
|
if (payloadResult.payload) {
|
|
@@ -1625,12 +1616,12 @@ export async function startGateway(ctx) {
|
|
|
1625
1616
|
if (parsedPayload.source === "file") {
|
|
1626
1617
|
try {
|
|
1627
1618
|
if (!(await fileExistsAsync(imageUrl))) {
|
|
1628
|
-
|
|
1619
|
+
log?.error(`[qqbot:${account.accountId}] Image not found: ${imageUrl}`);
|
|
1629
1620
|
return;
|
|
1630
1621
|
}
|
|
1631
1622
|
const imgSzCheck = checkFileSize(imageUrl);
|
|
1632
1623
|
if (!imgSzCheck.ok) {
|
|
1633
|
-
|
|
1624
|
+
log?.error(`[qqbot:${account.accountId}] Image size check failed: ${imgSzCheck.error}`);
|
|
1634
1625
|
return;
|
|
1635
1626
|
}
|
|
1636
1627
|
const fileBuffer = await readFileAsync(imageUrl);
|
|
@@ -1646,7 +1637,7 @@ export async function startGateway(ctx) {
|
|
|
1646
1637
|
};
|
|
1647
1638
|
const mimeType = mimeTypes[ext];
|
|
1648
1639
|
if (!mimeType) {
|
|
1649
|
-
|
|
1640
|
+
log?.error(`[qqbot:${account.accountId}] Unsupported image format: ${ext}`);
|
|
1650
1641
|
return;
|
|
1651
1642
|
}
|
|
1652
1643
|
imageUrl = `data:${mimeType};base64,${base64Data}`;
|
|
@@ -1654,7 +1645,6 @@ export async function startGateway(ctx) {
|
|
|
1654
1645
|
}
|
|
1655
1646
|
catch (readErr) {
|
|
1656
1647
|
log?.error(`[qqbot:${account.accountId}] Failed to read local image: ${readErr}`);
|
|
1657
|
-
await sendErrorMessage(MSG.IMAGE_SEND_FAILED);
|
|
1658
1648
|
return;
|
|
1659
1649
|
}
|
|
1660
1650
|
}
|
|
@@ -1690,7 +1680,6 @@ export async function startGateway(ctx) {
|
|
|
1690
1680
|
}
|
|
1691
1681
|
catch (err) {
|
|
1692
1682
|
log?.error(`[qqbot:${account.accountId}] Failed to send image: ${err}`);
|
|
1693
|
-
await sendErrorMessage(formatMediaErrorMessage("图片", err));
|
|
1694
1683
|
}
|
|
1695
1684
|
}
|
|
1696
1685
|
else if (parsedPayload.mediaType === "audio") {
|
|
@@ -1698,13 +1687,12 @@ export async function startGateway(ctx) {
|
|
|
1698
1687
|
try {
|
|
1699
1688
|
const ttsText = parsedPayload.caption || parsedPayload.path;
|
|
1700
1689
|
if (!ttsText?.trim()) {
|
|
1701
|
-
|
|
1690
|
+
log?.error(`[qqbot:${account.accountId}] Voice missing text`);
|
|
1702
1691
|
}
|
|
1703
1692
|
else {
|
|
1704
1693
|
const ttsCfg = resolveTTSConfig(cfg);
|
|
1705
1694
|
if (!ttsCfg) {
|
|
1706
1695
|
log?.error(`[qqbot:${account.accountId}] TTS not configured (channels.qqbot.tts in openclaw.json)`);
|
|
1707
|
-
await sendErrorMessage(MSG.VOICE_NOT_AVAILABLE);
|
|
1708
1696
|
}
|
|
1709
1697
|
else {
|
|
1710
1698
|
log?.info(`[qqbot:${account.accountId}] TTS: "${ttsText.slice(0, 50)}..." via ${ttsCfg.model}`);
|
|
@@ -1719,7 +1707,8 @@ export async function startGateway(ctx) {
|
|
|
1719
1707
|
await sendGroupVoiceMessage(token, event.groupOpenid, silkBase64, event.messageId);
|
|
1720
1708
|
}
|
|
1721
1709
|
else if (event.channelId) {
|
|
1722
|
-
|
|
1710
|
+
log?.error(`[qqbot:${account.accountId}] Voice not supported in channel, sending text fallback`);
|
|
1711
|
+
await sendChannelMessage(token, event.channelId, ttsText, event.messageId);
|
|
1723
1712
|
}
|
|
1724
1713
|
});
|
|
1725
1714
|
log?.info(`[qqbot:${account.accountId}] Voice message sent`);
|
|
@@ -1728,7 +1717,6 @@ export async function startGateway(ctx) {
|
|
|
1728
1717
|
}
|
|
1729
1718
|
catch (err) {
|
|
1730
1719
|
log?.error(`[qqbot:${account.accountId}] TTS/voice send failed: ${err}`);
|
|
1731
|
-
await sendErrorMessage(formatMediaErrorMessage("语音", err));
|
|
1732
1720
|
}
|
|
1733
1721
|
}
|
|
1734
1722
|
else if (parsedPayload.mediaType === "video") {
|
|
@@ -1736,7 +1724,7 @@ export async function startGateway(ctx) {
|
|
|
1736
1724
|
try {
|
|
1737
1725
|
const videoPath = normalizePath(parsedPayload.path ?? "");
|
|
1738
1726
|
if (!videoPath?.trim()) {
|
|
1739
|
-
|
|
1727
|
+
log?.error(`[qqbot:${account.accountId}] Video missing path`);
|
|
1740
1728
|
}
|
|
1741
1729
|
else {
|
|
1742
1730
|
const isHttpUrl = videoPath.startsWith("http://") || videoPath.startsWith("https://");
|
|
@@ -1751,7 +1739,7 @@ export async function startGateway(ctx) {
|
|
|
1751
1739
|
await sendGroupVideoMessage(token, event.groupOpenid, videoPath, undefined, event.messageId);
|
|
1752
1740
|
}
|
|
1753
1741
|
else if (event.channelId) {
|
|
1754
|
-
|
|
1742
|
+
log?.error(`[qqbot:${account.accountId}] Video not supported in channel`);
|
|
1755
1743
|
}
|
|
1756
1744
|
}
|
|
1757
1745
|
else {
|
|
@@ -1773,7 +1761,7 @@ export async function startGateway(ctx) {
|
|
|
1773
1761
|
await sendGroupVideoMessage(token, event.groupOpenid, undefined, videoBase64, event.messageId);
|
|
1774
1762
|
}
|
|
1775
1763
|
else if (event.channelId) {
|
|
1776
|
-
|
|
1764
|
+
log?.error(`[qqbot:${account.accountId}] Video not supported in channel`);
|
|
1777
1765
|
}
|
|
1778
1766
|
}
|
|
1779
1767
|
});
|
|
@@ -1796,7 +1784,6 @@ export async function startGateway(ctx) {
|
|
|
1796
1784
|
}
|
|
1797
1785
|
catch (err) {
|
|
1798
1786
|
log?.error(`[qqbot:${account.accountId}] Video send failed: ${err}`);
|
|
1799
|
-
await sendErrorMessage(formatMediaErrorMessage("视频", err));
|
|
1800
1787
|
}
|
|
1801
1788
|
}
|
|
1802
1789
|
else if (parsedPayload.mediaType === "file") {
|
|
@@ -1804,7 +1791,7 @@ export async function startGateway(ctx) {
|
|
|
1804
1791
|
try {
|
|
1805
1792
|
const filePath = normalizePath(parsedPayload.path ?? "");
|
|
1806
1793
|
if (!filePath?.trim()) {
|
|
1807
|
-
|
|
1794
|
+
log?.error(`[qqbot:${account.accountId}] File missing path`);
|
|
1808
1795
|
}
|
|
1809
1796
|
else {
|
|
1810
1797
|
const isHttpUrl = filePath.startsWith("http://") || filePath.startsWith("https://");
|
|
@@ -1819,7 +1806,7 @@ export async function startGateway(ctx) {
|
|
|
1819
1806
|
await sendGroupFileMessage(token, event.groupOpenid, undefined, filePath, event.messageId, fileName);
|
|
1820
1807
|
}
|
|
1821
1808
|
else if (event.channelId) {
|
|
1822
|
-
|
|
1809
|
+
log?.error(`[qqbot:${account.accountId}] File not supported in channel`);
|
|
1823
1810
|
}
|
|
1824
1811
|
}
|
|
1825
1812
|
else {
|
|
@@ -1839,7 +1826,7 @@ export async function startGateway(ctx) {
|
|
|
1839
1826
|
await sendGroupFileMessage(token, event.groupOpenid, fileBase64, undefined, event.messageId, fileName);
|
|
1840
1827
|
}
|
|
1841
1828
|
else if (event.channelId) {
|
|
1842
|
-
|
|
1829
|
+
log?.error(`[qqbot:${account.accountId}] File not supported in channel`);
|
|
1843
1830
|
}
|
|
1844
1831
|
}
|
|
1845
1832
|
});
|
|
@@ -1848,12 +1835,10 @@ export async function startGateway(ctx) {
|
|
|
1848
1835
|
}
|
|
1849
1836
|
catch (err) {
|
|
1850
1837
|
log?.error(`[qqbot:${account.accountId}] File send failed: ${err}`);
|
|
1851
|
-
await sendErrorMessage(formatMediaErrorMessage("文件", err));
|
|
1852
1838
|
}
|
|
1853
1839
|
}
|
|
1854
1840
|
else {
|
|
1855
1841
|
log?.error(`[qqbot:${account.accountId}] Unknown media type: ${parsedPayload.mediaType}`);
|
|
1856
|
-
await sendErrorMessage(MSG.UNSUPPORTED_MEDIA_TYPE);
|
|
1857
1842
|
}
|
|
1858
1843
|
// 记录活动并返回
|
|
1859
1844
|
pluginRuntime.channel.activity.record({
|
|
@@ -1866,7 +1851,6 @@ export async function startGateway(ctx) {
|
|
|
1866
1851
|
else {
|
|
1867
1852
|
// 未知的载荷类型
|
|
1868
1853
|
log?.error(`[qqbot:${account.accountId}] Unknown payload type: ${parsedPayload.type}`);
|
|
1869
|
-
await sendErrorMessage(MSG.UNSUPPORTED_PAYLOAD_TYPE);
|
|
1870
1854
|
return;
|
|
1871
1855
|
}
|
|
1872
1856
|
}
|
|
@@ -2207,10 +2191,10 @@ export async function startGateway(ctx) {
|
|
|
2207
2191
|
// 发送错误提示给用户,显示完整错误信息
|
|
2208
2192
|
const errMsg = String(err);
|
|
2209
2193
|
if (errMsg.includes("401") || errMsg.includes("key") || errMsg.includes("auth")) {
|
|
2210
|
-
|
|
2194
|
+
log?.error(`[qqbot:${account.accountId}] AI auth error: ${errMsg}`);
|
|
2211
2195
|
}
|
|
2212
2196
|
else {
|
|
2213
|
-
|
|
2197
|
+
log?.error(`[qqbot:${account.accountId}] AI process error: ${errMsg}`);
|
|
2214
2198
|
}
|
|
2215
2199
|
},
|
|
2216
2200
|
},
|
|
@@ -2228,7 +2212,6 @@ export async function startGateway(ctx) {
|
|
|
2228
2212
|
}
|
|
2229
2213
|
if (!hasResponse) {
|
|
2230
2214
|
log?.error(`[qqbot:${account.accountId}] No response within timeout`);
|
|
2231
|
-
await sendErrorMessage(MSG.TIMEOUT_HINT);
|
|
2232
2215
|
}
|
|
2233
2216
|
}
|
|
2234
2217
|
finally {
|
|
@@ -2247,7 +2230,6 @@ export async function startGateway(ctx) {
|
|
|
2247
2230
|
}
|
|
2248
2231
|
catch (err) {
|
|
2249
2232
|
log?.error(`[qqbot:${account.accountId}] Message processing failed: ${err}`);
|
|
2250
|
-
await sendErrorMessage(MSG.GENERIC_ERROR);
|
|
2251
2233
|
}
|
|
2252
2234
|
};
|
|
2253
2235
|
ws.on("open", () => {
|
package/dist/src/outbound.js
CHANGED
|
@@ -9,7 +9,6 @@ import { normalizeMediaTags } from "./utils/media-tags.js";
|
|
|
9
9
|
import { checkFileSize, readFileAsync, fileExistsAsync, formatFileSize } from "./utils/file-utils.js";
|
|
10
10
|
import { isLocalPath as isLocalFilePath, normalizePath, sanitizeFileName, getQQBotDataDir } from "./utils/platform.js";
|
|
11
11
|
import { downloadFile } from "./image-server.js";
|
|
12
|
-
import { MSG } from "./user-messages.js";
|
|
13
12
|
// ============ 消息回复限流器 ============
|
|
14
13
|
// 同一 message_id 1小时内最多回复 4 次,超过 1 小时无法被动回复(需改为主动消息)
|
|
15
14
|
const MESSAGE_REPLY_LIMIT = 4;
|
|
@@ -208,7 +207,7 @@ export async function sendPhoto(ctx, imagePath) {
|
|
|
208
207
|
let imageUrl = mediaPath;
|
|
209
208
|
if (isLocal) {
|
|
210
209
|
if (!(await fileExistsAsync(mediaPath))) {
|
|
211
|
-
return { channel: "qqbot", error:
|
|
210
|
+
return { channel: "qqbot", error: "Image not found" };
|
|
212
211
|
}
|
|
213
212
|
const sizeCheck = checkFileSize(mediaPath);
|
|
214
213
|
if (!sizeCheck.ok) {
|
|
@@ -222,7 +221,7 @@ export async function sendPhoto(ctx, imagePath) {
|
|
|
222
221
|
};
|
|
223
222
|
const mimeType = mimeTypes[ext];
|
|
224
223
|
if (!mimeType) {
|
|
225
|
-
return { channel: "qqbot", error:
|
|
224
|
+
return { channel: "qqbot", error: `Unsupported image format: ${ext}` };
|
|
226
225
|
}
|
|
227
226
|
imageUrl = `data:${mimeType};base64,${fileBuffer.toString("base64")}`;
|
|
228
227
|
console.log(`${prefix} sendPhoto: local → Base64 (${formatFileSize(fileBuffer.length)})`);
|
|
@@ -318,8 +317,8 @@ transcodeEnabled = true) {
|
|
|
318
317
|
return { channel: "qqbot", messageId: r.id, timestamp: r.timestamp };
|
|
319
318
|
}
|
|
320
319
|
else {
|
|
321
|
-
|
|
322
|
-
return { channel: "qqbot",
|
|
320
|
+
console.log(`${prefix} sendVoice: voice not supported in channel`);
|
|
321
|
+
return { channel: "qqbot", error: "Voice not supported in channel" };
|
|
323
322
|
}
|
|
324
323
|
}
|
|
325
324
|
catch (err) {
|
|
@@ -345,7 +344,7 @@ async function sendVoiceFromLocal(ctx, mediaPath, directUploadFormats, transcode
|
|
|
345
344
|
// 等待文件就绪(TTS 异步生成,文件可能还没写完)
|
|
346
345
|
const fileSize = await waitForFile(mediaPath);
|
|
347
346
|
if (fileSize === 0) {
|
|
348
|
-
return { channel: "qqbot", error:
|
|
347
|
+
return { channel: "qqbot", error: "Voice generate failed" };
|
|
349
348
|
}
|
|
350
349
|
// 精细检测:是否需要转码
|
|
351
350
|
const needsTranscode = shouldTranscodeVoice(mediaPath);
|
|
@@ -376,8 +375,8 @@ async function sendVoiceFromLocal(ctx, mediaPath, directUploadFormats, transcode
|
|
|
376
375
|
return { channel: "qqbot", messageId: r.id, timestamp: r.timestamp };
|
|
377
376
|
}
|
|
378
377
|
else {
|
|
379
|
-
|
|
380
|
-
return { channel: "qqbot",
|
|
378
|
+
console.log(`${prefix} sendVoice: voice not supported in channel`);
|
|
379
|
+
return { channel: "qqbot", error: "Voice not supported in channel" };
|
|
381
380
|
}
|
|
382
381
|
}
|
|
383
382
|
catch (err) {
|
|
@@ -417,8 +416,8 @@ export async function sendVideoMsg(ctx, videoPath) {
|
|
|
417
416
|
return { channel: "qqbot", messageId: r.id, timestamp: r.timestamp };
|
|
418
417
|
}
|
|
419
418
|
else {
|
|
420
|
-
|
|
421
|
-
return { channel: "qqbot",
|
|
419
|
+
console.log(`${prefix} sendVideoMsg: video not supported in channel`);
|
|
420
|
+
return { channel: "qqbot", error: "Video not supported in channel" };
|
|
422
421
|
}
|
|
423
422
|
}
|
|
424
423
|
// 本地文件
|
|
@@ -441,7 +440,7 @@ export async function sendVideoMsg(ctx, videoPath) {
|
|
|
441
440
|
/** 从本地文件发送视频(sendVideoMsg 的内部辅助) */
|
|
442
441
|
async function sendVideoFromLocal(ctx, mediaPath, prefix) {
|
|
443
442
|
if (!(await fileExistsAsync(mediaPath))) {
|
|
444
|
-
return { channel: "qqbot", error:
|
|
443
|
+
return { channel: "qqbot", error: "Video not found" };
|
|
445
444
|
}
|
|
446
445
|
const sizeCheck = checkFileSize(mediaPath);
|
|
447
446
|
if (!sizeCheck.ok) {
|
|
@@ -461,8 +460,8 @@ async function sendVideoFromLocal(ctx, mediaPath, prefix) {
|
|
|
461
460
|
return { channel: "qqbot", messageId: r.id, timestamp: r.timestamp };
|
|
462
461
|
}
|
|
463
462
|
else {
|
|
464
|
-
|
|
465
|
-
return { channel: "qqbot",
|
|
463
|
+
console.log(`${prefix} sendVideoMsg: video not supported in channel`);
|
|
464
|
+
return { channel: "qqbot", error: "Video not supported in channel" };
|
|
466
465
|
}
|
|
467
466
|
}
|
|
468
467
|
catch (err) {
|
|
@@ -503,8 +502,8 @@ export async function sendDocument(ctx, filePath) {
|
|
|
503
502
|
return { channel: "qqbot", messageId: r.id, timestamp: r.timestamp };
|
|
504
503
|
}
|
|
505
504
|
else {
|
|
506
|
-
|
|
507
|
-
return { channel: "qqbot",
|
|
505
|
+
console.log(`${prefix} sendDocument: file not supported in channel`);
|
|
506
|
+
return { channel: "qqbot", error: "File not supported in channel" };
|
|
508
507
|
}
|
|
509
508
|
}
|
|
510
509
|
// 本地文件
|
|
@@ -528,7 +527,7 @@ export async function sendDocument(ctx, filePath) {
|
|
|
528
527
|
async function sendDocumentFromLocal(ctx, mediaPath, prefix) {
|
|
529
528
|
const fileName = sanitizeFileName(path.basename(mediaPath));
|
|
530
529
|
if (!(await fileExistsAsync(mediaPath))) {
|
|
531
|
-
return { channel: "qqbot", error:
|
|
530
|
+
return { channel: "qqbot", error: "File not found" };
|
|
532
531
|
}
|
|
533
532
|
const sizeCheck = checkFileSize(mediaPath);
|
|
534
533
|
if (!sizeCheck.ok) {
|
|
@@ -551,8 +550,8 @@ async function sendDocumentFromLocal(ctx, mediaPath, prefix) {
|
|
|
551
550
|
return { channel: "qqbot", messageId: r.id, timestamp: r.timestamp };
|
|
552
551
|
}
|
|
553
552
|
else {
|
|
554
|
-
|
|
555
|
-
return { channel: "qqbot",
|
|
553
|
+
console.log(`${prefix} sendDocument: file not supported in channel`);
|
|
554
|
+
return { channel: "qqbot", error: "File not supported in channel" };
|
|
556
555
|
}
|
|
557
556
|
}
|
|
558
557
|
catch (err) {
|
|
@@ -18,7 +18,6 @@ import { getQQBotDataDir } from "./utils/platform.js";
|
|
|
18
18
|
// ============ 配置 ============
|
|
19
19
|
const STORAGE_DIR = getQQBotDataDir("data");
|
|
20
20
|
const REF_INDEX_FILE = path.join(STORAGE_DIR, "ref-index.jsonl");
|
|
21
|
-
const MAX_CONTENT_LENGTH = 500; // 存储的消息内容最大字符数
|
|
22
21
|
const MAX_ENTRIES = 50000; // 内存中最大缓存条目数
|
|
23
22
|
const TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 天
|
|
24
23
|
const COMPACT_THRESHOLD_RATIO = 2; // 文件行数超过有效条目 N 倍时 compact
|
|
@@ -164,7 +163,7 @@ export function setRefIndex(refIdx, entry) {
|
|
|
164
163
|
evictIfNeeded();
|
|
165
164
|
const now = Date.now();
|
|
166
165
|
store.set(refIdx, {
|
|
167
|
-
content: entry.content
|
|
166
|
+
content: entry.content,
|
|
168
167
|
senderId: entry.senderId,
|
|
169
168
|
senderName: entry.senderName,
|
|
170
169
|
timestamp: entry.timestamp,
|
|
@@ -176,7 +175,7 @@ export function setRefIndex(refIdx, entry) {
|
|
|
176
175
|
appendLine({
|
|
177
176
|
k: refIdx,
|
|
178
177
|
v: {
|
|
179
|
-
content: entry.content
|
|
178
|
+
content: entry.content,
|
|
180
179
|
senderId: entry.senderId,
|
|
181
180
|
senderName: entry.senderName,
|
|
182
181
|
timestamp: entry.timestamp,
|
|
@@ -1,43 +1,8 @@
|
|
|
1
|
+
export {};
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
+
* 用户面向的提示文案 — 已清空
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 3. 统一风格:去掉 [QQBot] 前缀和 [方括号] 格式
|
|
8
|
-
* 4. 所有面向用户的文案集中在此文件,便于维护和国际化
|
|
5
|
+
* 设计原则(对齐飞书插件):
|
|
6
|
+
* QQBot 插件层不生成额外的用户提示信息。
|
|
7
|
+
* 所有运行时错误仅写日志,不面向用户展示。
|
|
9
8
|
*/
|
|
10
|
-
export declare const MSG: {
|
|
11
|
-
readonly GENERIC_ERROR: "抱歉,处理消息时遇到了问题,请稍后再试~";
|
|
12
|
-
readonly AI_AUTH_ERROR: "抱歉,AI 服务暂时不可用,请联系管理员检查配置~";
|
|
13
|
-
readonly AI_PROCESS_ERROR: "抱歉,AI 处理遇到了问题,请稍后再试~";
|
|
14
|
-
readonly TIMEOUT_HINT: "已收到消息,正在处理中…";
|
|
15
|
-
readonly IMAGE_NOT_FOUND: "抱歉,图片不存在或已失效,无法发送~";
|
|
16
|
-
readonly IMAGE_FORMAT_UNSUPPORTED: (ext: string) => string;
|
|
17
|
-
readonly IMAGE_SEND_FAILED: "抱歉,图片发送失败了,请稍后再试~";
|
|
18
|
-
readonly IMAGE_UPLOADING: (size: string) => string;
|
|
19
|
-
readonly VOICE_GENERATE_FAILED: "抱歉,语音生成失败,请稍后重试~";
|
|
20
|
-
readonly VOICE_CONVERT_FAILED: "抱歉,语音格式转换失败,请稍后重试~";
|
|
21
|
-
readonly VOICE_SEND_FAILED: "抱歉,语音发送失败了,请稍后再试~";
|
|
22
|
-
readonly VOICE_NOT_AVAILABLE: "抱歉,语音功能暂未开启~";
|
|
23
|
-
readonly VOICE_MISSING_TEXT: "抱歉,语音消息缺少内容~";
|
|
24
|
-
readonly VOICE_CHANNEL_UNSUPPORTED: "抱歉,语音消息暂不支持在频道中发送~";
|
|
25
|
-
readonly VIDEO_NOT_FOUND: "抱歉,视频文件不存在或已失效,无法发送~";
|
|
26
|
-
readonly VIDEO_SEND_FAILED: "抱歉,视频发送失败了,请稍后再试~";
|
|
27
|
-
readonly VIDEO_MISSING_PATH: "抱歉,视频消息缺少内容~";
|
|
28
|
-
readonly VIDEO_CHANNEL_UNSUPPORTED: "抱歉,视频消息暂不支持在频道中发送~";
|
|
29
|
-
readonly VIDEO_UPLOADING: (size: string) => string;
|
|
30
|
-
readonly FILE_NOT_FOUND: "抱歉,文件不存在或已失效,无法发送~";
|
|
31
|
-
readonly FILE_SEND_FAILED: "抱歉,文件发送失败了,请稍后再试~";
|
|
32
|
-
readonly FILE_MISSING_PATH: "抱歉,文件消息缺少内容~";
|
|
33
|
-
readonly FILE_CHANNEL_UNSUPPORTED: "抱歉,文件消息暂不支持在频道中发送~";
|
|
34
|
-
readonly FILE_UPLOADING: (name: string, size: string) => string;
|
|
35
|
-
readonly PAYLOAD_PARSE_ERROR: "抱歉,消息格式异常,无法处理~";
|
|
36
|
-
readonly UNSUPPORTED_MEDIA_TYPE: "抱歉,暂不支持该媒体类型~";
|
|
37
|
-
readonly UNSUPPORTED_PAYLOAD_TYPE: "抱歉,暂不支持该消息类型~";
|
|
38
|
-
};
|
|
39
|
-
/**
|
|
40
|
-
* 将媒体上传/发送错误转为对用户友好的提示文案
|
|
41
|
-
* 技术细节不暴露给用户,仅记录到日志
|
|
42
|
-
*/
|
|
43
|
-
export declare function formatMediaErrorMessage(mediaType: string, err: unknown): string;
|
|
@@ -1,65 +1,8 @@
|
|
|
1
|
+
export {};
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
+
* 用户面向的提示文案 — 已清空
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 3. 统一风格:去掉 [QQBot] 前缀和 [方括号] 格式
|
|
8
|
-
* 4. 所有面向用户的文案集中在此文件,便于维护和国际化
|
|
5
|
+
* 设计原则(对齐飞书插件):
|
|
6
|
+
* QQBot 插件层不生成额外的用户提示信息。
|
|
7
|
+
* 所有运行时错误仅写日志,不面向用户展示。
|
|
9
8
|
*/
|
|
10
|
-
// ============ 媒体发送错误 ============
|
|
11
|
-
export const MSG = {
|
|
12
|
-
// 通用错误
|
|
13
|
-
GENERIC_ERROR: "抱歉,处理消息时遇到了问题,请稍后再试~",
|
|
14
|
-
AI_AUTH_ERROR: "抱歉,AI 服务暂时不可用,请联系管理员检查配置~",
|
|
15
|
-
AI_PROCESS_ERROR: "抱歉,AI 处理遇到了问题,请稍后再试~",
|
|
16
|
-
TIMEOUT_HINT: "已收到消息,正在处理中…",
|
|
17
|
-
// 图片
|
|
18
|
-
IMAGE_NOT_FOUND: "抱歉,图片不存在或已失效,无法发送~",
|
|
19
|
-
IMAGE_FORMAT_UNSUPPORTED: (ext) => `抱歉,暂不支持 ${ext} 格式的图片~`,
|
|
20
|
-
IMAGE_SEND_FAILED: "抱歉,图片发送失败了,请稍后再试~",
|
|
21
|
-
IMAGE_UPLOADING: (size) => `正在上传图片 (${size})...`,
|
|
22
|
-
// 语音
|
|
23
|
-
VOICE_GENERATE_FAILED: "抱歉,语音生成失败,请稍后重试~",
|
|
24
|
-
VOICE_CONVERT_FAILED: "抱歉,语音格式转换失败,请稍后重试~",
|
|
25
|
-
VOICE_SEND_FAILED: "抱歉,语音发送失败了,请稍后再试~",
|
|
26
|
-
VOICE_NOT_AVAILABLE: "抱歉,语音功能暂未开启~",
|
|
27
|
-
VOICE_MISSING_TEXT: "抱歉,语音消息缺少内容~",
|
|
28
|
-
VOICE_CHANNEL_UNSUPPORTED: "抱歉,语音消息暂不支持在频道中发送~",
|
|
29
|
-
// 视频
|
|
30
|
-
VIDEO_NOT_FOUND: "抱歉,视频文件不存在或已失效,无法发送~",
|
|
31
|
-
VIDEO_SEND_FAILED: "抱歉,视频发送失败了,请稍后再试~",
|
|
32
|
-
VIDEO_MISSING_PATH: "抱歉,视频消息缺少内容~",
|
|
33
|
-
VIDEO_CHANNEL_UNSUPPORTED: "抱歉,视频消息暂不支持在频道中发送~",
|
|
34
|
-
VIDEO_UPLOADING: (size) => `正在上传视频 (${size})...`,
|
|
35
|
-
// 文件
|
|
36
|
-
FILE_NOT_FOUND: "抱歉,文件不存在或已失效,无法发送~",
|
|
37
|
-
FILE_SEND_FAILED: "抱歉,文件发送失败了,请稍后再试~",
|
|
38
|
-
FILE_MISSING_PATH: "抱歉,文件消息缺少内容~",
|
|
39
|
-
FILE_CHANNEL_UNSUPPORTED: "抱歉,文件消息暂不支持在频道中发送~",
|
|
40
|
-
FILE_UPLOADING: (name, size) => `正在上传文件 ${name} (${size})...`,
|
|
41
|
-
// 载荷解析
|
|
42
|
-
PAYLOAD_PARSE_ERROR: "抱歉,消息格式异常,无法处理~",
|
|
43
|
-
UNSUPPORTED_MEDIA_TYPE: "抱歉,暂不支持该媒体类型~",
|
|
44
|
-
UNSUPPORTED_PAYLOAD_TYPE: "抱歉,暂不支持该消息类型~",
|
|
45
|
-
};
|
|
46
|
-
/**
|
|
47
|
-
* 将媒体上传/发送错误转为对用户友好的提示文案
|
|
48
|
-
* 技术细节不暴露给用户,仅记录到日志
|
|
49
|
-
*/
|
|
50
|
-
export function formatMediaErrorMessage(mediaType, err) {
|
|
51
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
52
|
-
if (msg.includes("上传超时") || msg.includes("timeout") || msg.includes("Timeout")) {
|
|
53
|
-
return `抱歉,${mediaType}资源加载超时,可能是网络原因或文件太大,请稍后再试~`;
|
|
54
|
-
}
|
|
55
|
-
if (msg.includes("文件不存在") || msg.includes("not found") || msg.includes("Not Found")) {
|
|
56
|
-
return `抱歉,${mediaType}文件不存在或已失效,无法发送~`;
|
|
57
|
-
}
|
|
58
|
-
if (msg.includes("文件大小") || msg.includes("too large") || msg.includes("exceed")) {
|
|
59
|
-
return `抱歉,${mediaType}文件太大了,超出了发送限制~`;
|
|
60
|
-
}
|
|
61
|
-
if (msg.includes("Network error") || msg.includes("ECONNREFUSED") || msg.includes("ENOTFOUND")) {
|
|
62
|
-
return `抱歉,网络连接异常,${mediaType}发送失败,请稍后再试~`;
|
|
63
|
-
}
|
|
64
|
-
return `抱歉,${mediaType}发送失败了,请稍后再试~`;
|
|
65
|
-
}
|
package/package.json
CHANGED
package/src/gateway.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { convertSilkToWav, isVoiceAttachment, formatDuration, resolveTTSConfig,
|
|
|
16
16
|
import { normalizeMediaTags } from "./utils/media-tags.js";
|
|
17
17
|
import { checkFileSize, readFileAsync, fileExistsAsync, isLargeFile, formatFileSize } from "./utils/file-utils.js";
|
|
18
18
|
import { getQQBotDataDir, isLocalPath as isLocalFilePath, normalizePath, sanitizeFileName, runDiagnostics } from "./utils/platform.js";
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
import { sendPhoto, sendVoice, sendVideoMsg, sendDocument, sendMedia as sendMediaAuto, type MediaTargetContext } from "./outbound.js";
|
|
21
21
|
import { chunkText, TEXT_CHUNK_LIMIT } from "./channel.js";
|
|
22
22
|
|
|
@@ -444,7 +444,7 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|
|
444
444
|
attachments.push(attachment);
|
|
445
445
|
}
|
|
446
446
|
setRefIndex(refIdx, {
|
|
447
|
-
content:
|
|
447
|
+
content: meta.text ?? "",
|
|
448
448
|
senderId: account.accountId,
|
|
449
449
|
senderName: account.accountId,
|
|
450
450
|
timestamp: Date.now(),
|
|
@@ -1683,7 +1683,6 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|
|
1683
1683
|
const result = await sendPhoto(mediaTarget, item.content);
|
|
1684
1684
|
if (result.error) {
|
|
1685
1685
|
log?.error(`[qqbot:${account.accountId}] sendPhoto error: ${result.error}`);
|
|
1686
|
-
await sendErrorMessage(formatMediaErrorMessage("图片", new Error(result.error)));
|
|
1687
1686
|
}
|
|
1688
1687
|
} else if (item.type === "voice") {
|
|
1689
1688
|
const uploadFormats = account.config?.audioFormatPolicy?.uploadDirectFormats ?? account.config?.voiceDirectUploadFormats;
|
|
@@ -1699,23 +1698,19 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|
|
1699
1698
|
]);
|
|
1700
1699
|
if (result.error) {
|
|
1701
1700
|
log?.error(`[qqbot:${account.accountId}] sendVoice error: ${result.error}`);
|
|
1702
|
-
await sendErrorMessage(formatMediaErrorMessage("语音", new Error(result.error)));
|
|
1703
1701
|
}
|
|
1704
1702
|
} catch (err) {
|
|
1705
1703
|
log?.error(`[qqbot:${account.accountId}] sendVoice unexpected error: ${err}`);
|
|
1706
|
-
await sendErrorMessage(formatMediaErrorMessage("语音", err));
|
|
1707
1704
|
}
|
|
1708
1705
|
} else if (item.type === "video") {
|
|
1709
1706
|
const result = await sendVideoMsg(mediaTarget, item.content);
|
|
1710
1707
|
if (result.error) {
|
|
1711
1708
|
log?.error(`[qqbot:${account.accountId}] sendVideoMsg error: ${result.error}`);
|
|
1712
|
-
await sendErrorMessage(formatMediaErrorMessage("视频", new Error(result.error)));
|
|
1713
1709
|
}
|
|
1714
1710
|
} else if (item.type === "file") {
|
|
1715
1711
|
const result = await sendDocument(mediaTarget, item.content);
|
|
1716
1712
|
if (result.error) {
|
|
1717
1713
|
log?.error(`[qqbot:${account.accountId}] sendDocument error: ${result.error}`);
|
|
1718
|
-
await sendErrorMessage(formatMediaErrorMessage("文件", new Error(result.error)));
|
|
1719
1714
|
}
|
|
1720
1715
|
} else if (item.type === "media") {
|
|
1721
1716
|
// qqmedia: 自动根据扩展名路由到 sendPhoto/sendVoice/sendVideoMsg/sendDocument
|
|
@@ -1729,7 +1724,6 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|
|
1729
1724
|
});
|
|
1730
1725
|
if (result.error) {
|
|
1731
1726
|
log?.error(`[qqbot:${account.accountId}] sendMedia(auto) error: ${result.error}`);
|
|
1732
|
-
await sendErrorMessage(formatMediaErrorMessage("媒体", new Error(result.error)));
|
|
1733
1727
|
}
|
|
1734
1728
|
}
|
|
1735
1729
|
}
|
|
@@ -1749,9 +1743,7 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|
|
1749
1743
|
|
|
1750
1744
|
if (payloadResult.isPayload) {
|
|
1751
1745
|
if (payloadResult.error) {
|
|
1752
|
-
// 载荷解析失败,发送错误提示
|
|
1753
1746
|
log?.error(`[qqbot:${account.accountId}] Payload parse error: ${payloadResult.error}`);
|
|
1754
|
-
await sendErrorMessage(MSG.PAYLOAD_PARSE_ERROR);
|
|
1755
1747
|
return;
|
|
1756
1748
|
}
|
|
1757
1749
|
|
|
@@ -1804,12 +1796,12 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|
|
1804
1796
|
if (parsedPayload.source === "file") {
|
|
1805
1797
|
try {
|
|
1806
1798
|
if (!(await fileExistsAsync(imageUrl))) {
|
|
1807
|
-
|
|
1799
|
+
log?.error(`[qqbot:${account.accountId}] Image not found: ${imageUrl}`);
|
|
1808
1800
|
return;
|
|
1809
1801
|
}
|
|
1810
1802
|
const imgSzCheck = checkFileSize(imageUrl);
|
|
1811
1803
|
if (!imgSzCheck.ok) {
|
|
1812
|
-
|
|
1804
|
+
log?.error(`[qqbot:${account.accountId}] Image size check failed: ${imgSzCheck.error}`);
|
|
1813
1805
|
return;
|
|
1814
1806
|
}
|
|
1815
1807
|
const fileBuffer = await readFileAsync(imageUrl);
|
|
@@ -1825,14 +1817,13 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|
|
1825
1817
|
};
|
|
1826
1818
|
const mimeType = mimeTypes[ext];
|
|
1827
1819
|
if (!mimeType) {
|
|
1828
|
-
|
|
1820
|
+
log?.error(`[qqbot:${account.accountId}] Unsupported image format: ${ext}`);
|
|
1829
1821
|
return;
|
|
1830
1822
|
}
|
|
1831
1823
|
imageUrl = `data:${mimeType};base64,${base64Data}`;
|
|
1832
1824
|
log?.info(`[qqbot:${account.accountId}] Converted local image to Base64 (size: ${formatFileSize(fileBuffer.length)})`);
|
|
1833
1825
|
} catch (readErr) {
|
|
1834
1826
|
log?.error(`[qqbot:${account.accountId}] Failed to read local image: ${readErr}`);
|
|
1835
|
-
await sendErrorMessage(MSG.IMAGE_SEND_FAILED);
|
|
1836
1827
|
return;
|
|
1837
1828
|
}
|
|
1838
1829
|
}
|
|
@@ -1865,19 +1856,17 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|
|
1865
1856
|
}
|
|
1866
1857
|
} catch (err) {
|
|
1867
1858
|
log?.error(`[qqbot:${account.accountId}] Failed to send image: ${err}`);
|
|
1868
|
-
await sendErrorMessage(formatMediaErrorMessage("图片", err));
|
|
1869
1859
|
}
|
|
1870
1860
|
} else if (parsedPayload.mediaType === "audio") {
|
|
1871
1861
|
// TTS 语音发送:文字 → PCM → SILK → QQ 语音
|
|
1872
1862
|
try {
|
|
1873
1863
|
const ttsText = parsedPayload.caption || parsedPayload.path;
|
|
1874
1864
|
if (!ttsText?.trim()) {
|
|
1875
|
-
|
|
1865
|
+
log?.error(`[qqbot:${account.accountId}] Voice missing text`);
|
|
1876
1866
|
} else {
|
|
1877
1867
|
const ttsCfg = resolveTTSConfig(cfg as Record<string, unknown>);
|
|
1878
1868
|
if (!ttsCfg) {
|
|
1879
1869
|
log?.error(`[qqbot:${account.accountId}] TTS not configured (channels.qqbot.tts in openclaw.json)`);
|
|
1880
|
-
await sendErrorMessage(MSG.VOICE_NOT_AVAILABLE);
|
|
1881
1870
|
} else {
|
|
1882
1871
|
log?.info(`[qqbot:${account.accountId}] TTS: "${ttsText.slice(0, 50)}..." via ${ttsCfg.model}`);
|
|
1883
1872
|
const ttsDir = getQQBotDataDir("tts");
|
|
@@ -1890,7 +1879,8 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|
|
1890
1879
|
} else if (event.type === "group" && event.groupOpenid) {
|
|
1891
1880
|
await sendGroupVoiceMessage(token, event.groupOpenid, silkBase64, event.messageId);
|
|
1892
1881
|
} else if (event.channelId) {
|
|
1893
|
-
|
|
1882
|
+
log?.error(`[qqbot:${account.accountId}] Voice not supported in channel, sending text fallback`);
|
|
1883
|
+
await sendChannelMessage(token, event.channelId, ttsText, event.messageId);
|
|
1894
1884
|
}
|
|
1895
1885
|
});
|
|
1896
1886
|
log?.info(`[qqbot:${account.accountId}] Voice message sent`);
|
|
@@ -1898,14 +1888,13 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|
|
1898
1888
|
}
|
|
1899
1889
|
} catch (err) {
|
|
1900
1890
|
log?.error(`[qqbot:${account.accountId}] TTS/voice send failed: ${err}`);
|
|
1901
|
-
await sendErrorMessage(formatMediaErrorMessage("语音", err));
|
|
1902
1891
|
}
|
|
1903
1892
|
} else if (parsedPayload.mediaType === "video") {
|
|
1904
1893
|
// 视频发送:支持公网 URL 和本地文件
|
|
1905
1894
|
try {
|
|
1906
1895
|
const videoPath = normalizePath(parsedPayload.path ?? "");
|
|
1907
1896
|
if (!videoPath?.trim()) {
|
|
1908
|
-
|
|
1897
|
+
log?.error(`[qqbot:${account.accountId}] Video missing path`);
|
|
1909
1898
|
} else {
|
|
1910
1899
|
const isHttpUrl = videoPath.startsWith("http://") || videoPath.startsWith("https://");
|
|
1911
1900
|
log?.info(`[qqbot:${account.accountId}] Video send: "${videoPath.slice(0, 60)}..."`);
|
|
@@ -1918,7 +1907,7 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|
|
1918
1907
|
} else if (event.type === "group" && event.groupOpenid) {
|
|
1919
1908
|
await sendGroupVideoMessage(token, event.groupOpenid, videoPath, undefined, event.messageId);
|
|
1920
1909
|
} else if (event.channelId) {
|
|
1921
|
-
|
|
1910
|
+
log?.error(`[qqbot:${account.accountId}] Video not supported in channel`);
|
|
1922
1911
|
}
|
|
1923
1912
|
} else {
|
|
1924
1913
|
// 本地文件:读取为 Base64
|
|
@@ -1938,7 +1927,7 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|
|
1938
1927
|
} else if (event.type === "group" && event.groupOpenid) {
|
|
1939
1928
|
await sendGroupVideoMessage(token, event.groupOpenid, undefined, videoBase64, event.messageId);
|
|
1940
1929
|
} else if (event.channelId) {
|
|
1941
|
-
|
|
1930
|
+
log?.error(`[qqbot:${account.accountId}] Video not supported in channel`);
|
|
1942
1931
|
}
|
|
1943
1932
|
}
|
|
1944
1933
|
});
|
|
@@ -1959,14 +1948,13 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|
|
1959
1948
|
}
|
|
1960
1949
|
} catch (err) {
|
|
1961
1950
|
log?.error(`[qqbot:${account.accountId}] Video send failed: ${err}`);
|
|
1962
|
-
await sendErrorMessage(formatMediaErrorMessage("视频", err));
|
|
1963
1951
|
}
|
|
1964
1952
|
} else if (parsedPayload.mediaType === "file") {
|
|
1965
1953
|
// 文件发送
|
|
1966
1954
|
try {
|
|
1967
1955
|
const filePath = normalizePath(parsedPayload.path ?? "");
|
|
1968
1956
|
if (!filePath?.trim()) {
|
|
1969
|
-
|
|
1957
|
+
log?.error(`[qqbot:${account.accountId}] File missing path`);
|
|
1970
1958
|
} else {
|
|
1971
1959
|
const isHttpUrl = filePath.startsWith("http://") || filePath.startsWith("https://");
|
|
1972
1960
|
const fileName = sanitizeFileName(path.basename(filePath));
|
|
@@ -1979,7 +1967,7 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|
|
1979
1967
|
} else if (event.type === "group" && event.groupOpenid) {
|
|
1980
1968
|
await sendGroupFileMessage(token, event.groupOpenid, undefined, filePath, event.messageId, fileName);
|
|
1981
1969
|
} else if (event.channelId) {
|
|
1982
|
-
|
|
1970
|
+
log?.error(`[qqbot:${account.accountId}] File not supported in channel`);
|
|
1983
1971
|
}
|
|
1984
1972
|
} else {
|
|
1985
1973
|
if (!(await fileExistsAsync(filePath))) {
|
|
@@ -1996,7 +1984,7 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|
|
1996
1984
|
} else if (event.type === "group" && event.groupOpenid) {
|
|
1997
1985
|
await sendGroupFileMessage(token, event.groupOpenid, fileBase64, undefined, event.messageId, fileName);
|
|
1998
1986
|
} else if (event.channelId) {
|
|
1999
|
-
|
|
1987
|
+
log?.error(`[qqbot:${account.accountId}] File not supported in channel`);
|
|
2000
1988
|
}
|
|
2001
1989
|
}
|
|
2002
1990
|
});
|
|
@@ -2004,11 +1992,9 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|
|
2004
1992
|
}
|
|
2005
1993
|
} catch (err) {
|
|
2006
1994
|
log?.error(`[qqbot:${account.accountId}] File send failed: ${err}`);
|
|
2007
|
-
await sendErrorMessage(formatMediaErrorMessage("文件", err));
|
|
2008
1995
|
}
|
|
2009
1996
|
} else {
|
|
2010
1997
|
log?.error(`[qqbot:${account.accountId}] Unknown media type: ${(parsedPayload as MediaPayload).mediaType}`);
|
|
2011
|
-
await sendErrorMessage(MSG.UNSUPPORTED_MEDIA_TYPE);
|
|
2012
1998
|
}
|
|
2013
1999
|
|
|
2014
2000
|
// 记录活动并返回
|
|
@@ -2021,7 +2007,6 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|
|
2021
2007
|
} else {
|
|
2022
2008
|
// 未知的载荷类型
|
|
2023
2009
|
log?.error(`[qqbot:${account.accountId}] Unknown payload type: ${(parsedPayload as any).type}`);
|
|
2024
|
-
await sendErrorMessage(MSG.UNSUPPORTED_PAYLOAD_TYPE);
|
|
2025
2010
|
return;
|
|
2026
2011
|
}
|
|
2027
2012
|
}
|
|
@@ -2370,9 +2355,9 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|
|
2370
2355
|
// 发送错误提示给用户,显示完整错误信息
|
|
2371
2356
|
const errMsg = String(err);
|
|
2372
2357
|
if (errMsg.includes("401") || errMsg.includes("key") || errMsg.includes("auth")) {
|
|
2373
|
-
|
|
2358
|
+
log?.error(`[qqbot:${account.accountId}] AI auth error: ${errMsg}`);
|
|
2374
2359
|
} else {
|
|
2375
|
-
|
|
2360
|
+
log?.error(`[qqbot:${account.accountId}] AI process error: ${errMsg}`);
|
|
2376
2361
|
}
|
|
2377
2362
|
},
|
|
2378
2363
|
},
|
|
@@ -2390,7 +2375,6 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|
|
2390
2375
|
}
|
|
2391
2376
|
if (!hasResponse) {
|
|
2392
2377
|
log?.error(`[qqbot:${account.accountId}] No response within timeout`);
|
|
2393
|
-
await sendErrorMessage(MSG.TIMEOUT_HINT);
|
|
2394
2378
|
}
|
|
2395
2379
|
} finally {
|
|
2396
2380
|
// 清理 tool-only 兜底定时器
|
|
@@ -2407,7 +2391,6 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|
|
2407
2391
|
}
|
|
2408
2392
|
} catch (err) {
|
|
2409
2393
|
log?.error(`[qqbot:${account.accountId}] Message processing failed: ${err}`);
|
|
2410
|
-
await sendErrorMessage(MSG.GENERIC_ERROR);
|
|
2411
2394
|
}
|
|
2412
2395
|
};
|
|
2413
2396
|
|
package/src/outbound.ts
CHANGED
|
@@ -26,7 +26,6 @@ import { normalizeMediaTags } from "./utils/media-tags.js";
|
|
|
26
26
|
import { checkFileSize, readFileAsync, fileExistsAsync, isLargeFile, formatFileSize } from "./utils/file-utils.js";
|
|
27
27
|
import { isLocalPath as isLocalFilePath, normalizePath, sanitizeFileName, getQQBotDataDir } from "./utils/platform.js";
|
|
28
28
|
import { downloadFile } from "./image-server.js";
|
|
29
|
-
import { MSG } from "./user-messages.js";
|
|
30
29
|
|
|
31
30
|
// ============ 消息回复限流器 ============
|
|
32
31
|
// 同一 message_id 1小时内最多回复 4 次,超过 1 小时无法被动回复(需改为主动消息)
|
|
@@ -313,7 +312,7 @@ export async function sendPhoto(
|
|
|
313
312
|
|
|
314
313
|
if (isLocal) {
|
|
315
314
|
if (!(await fileExistsAsync(mediaPath))) {
|
|
316
|
-
return { channel: "qqbot", error:
|
|
315
|
+
return { channel: "qqbot", error: "Image not found" };
|
|
317
316
|
}
|
|
318
317
|
const sizeCheck = checkFileSize(mediaPath);
|
|
319
318
|
if (!sizeCheck.ok) {
|
|
@@ -327,7 +326,7 @@ export async function sendPhoto(
|
|
|
327
326
|
};
|
|
328
327
|
const mimeType = mimeTypes[ext];
|
|
329
328
|
if (!mimeType) {
|
|
330
|
-
return { channel: "qqbot", error:
|
|
329
|
+
return { channel: "qqbot", error: `Unsupported image format: ${ext}` };
|
|
331
330
|
}
|
|
332
331
|
imageUrl = `data:${mimeType};base64,${fileBuffer.toString("base64")}`;
|
|
333
332
|
console.log(`${prefix} sendPhoto: local → Base64 (${formatFileSize(fileBuffer.length)})`);
|
|
@@ -430,8 +429,8 @@ export async function sendVoice(
|
|
|
430
429
|
const r = await sendGroupVoiceMessage(token, ctx.targetId, undefined, mediaPath, ctx.replyToId);
|
|
431
430
|
return { channel: "qqbot", messageId: r.id, timestamp: r.timestamp };
|
|
432
431
|
} else {
|
|
433
|
-
|
|
434
|
-
return { channel: "qqbot",
|
|
432
|
+
console.log(`${prefix} sendVoice: voice not supported in channel`);
|
|
433
|
+
return { channel: "qqbot", error: "Voice not supported in channel" };
|
|
435
434
|
}
|
|
436
435
|
} catch (err) {
|
|
437
436
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -464,7 +463,7 @@ async function sendVoiceFromLocal(
|
|
|
464
463
|
// 等待文件就绪(TTS 异步生成,文件可能还没写完)
|
|
465
464
|
const fileSize = await waitForFile(mediaPath);
|
|
466
465
|
if (fileSize === 0) {
|
|
467
|
-
return { channel: "qqbot", error:
|
|
466
|
+
return { channel: "qqbot", error: "Voice generate failed" };
|
|
468
467
|
}
|
|
469
468
|
|
|
470
469
|
// 精细检测:是否需要转码
|
|
@@ -498,8 +497,8 @@ async function sendVoiceFromLocal(
|
|
|
498
497
|
const r = await sendGroupVoiceMessage(token, ctx.targetId, uploadBase64, undefined, ctx.replyToId);
|
|
499
498
|
return { channel: "qqbot", messageId: r.id, timestamp: r.timestamp };
|
|
500
499
|
} else {
|
|
501
|
-
|
|
502
|
-
return { channel: "qqbot",
|
|
500
|
+
console.log(`${prefix} sendVoice: voice not supported in channel`);
|
|
501
|
+
return { channel: "qqbot", error: "Voice not supported in channel" };
|
|
503
502
|
}
|
|
504
503
|
} catch (err) {
|
|
505
504
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -543,8 +542,8 @@ export async function sendVideoMsg(
|
|
|
543
542
|
const r = await sendGroupVideoMessage(token, ctx.targetId, mediaPath, undefined, ctx.replyToId);
|
|
544
543
|
return { channel: "qqbot", messageId: r.id, timestamp: r.timestamp };
|
|
545
544
|
} else {
|
|
546
|
-
|
|
547
|
-
return { channel: "qqbot",
|
|
545
|
+
console.log(`${prefix} sendVideoMsg: video not supported in channel`);
|
|
546
|
+
return { channel: "qqbot", error: "Video not supported in channel" };
|
|
548
547
|
}
|
|
549
548
|
}
|
|
550
549
|
|
|
@@ -570,7 +569,7 @@ export async function sendVideoMsg(
|
|
|
570
569
|
/** 从本地文件发送视频(sendVideoMsg 的内部辅助) */
|
|
571
570
|
async function sendVideoFromLocal(ctx: MediaTargetContext, mediaPath: string, prefix: string): Promise<OutboundResult> {
|
|
572
571
|
if (!(await fileExistsAsync(mediaPath))) {
|
|
573
|
-
return { channel: "qqbot", error:
|
|
572
|
+
return { channel: "qqbot", error: "Video not found" };
|
|
574
573
|
}
|
|
575
574
|
const sizeCheck = checkFileSize(mediaPath);
|
|
576
575
|
if (!sizeCheck.ok) {
|
|
@@ -590,8 +589,8 @@ async function sendVideoFromLocal(ctx: MediaTargetContext, mediaPath: string, pr
|
|
|
590
589
|
const r = await sendGroupVideoMessage(token, ctx.targetId, undefined, videoBase64, ctx.replyToId);
|
|
591
590
|
return { channel: "qqbot", messageId: r.id, timestamp: r.timestamp };
|
|
592
591
|
} else {
|
|
593
|
-
|
|
594
|
-
return { channel: "qqbot",
|
|
592
|
+
console.log(`${prefix} sendVideoMsg: video not supported in channel`);
|
|
593
|
+
return { channel: "qqbot", error: "Video not supported in channel" };
|
|
595
594
|
}
|
|
596
595
|
} catch (err) {
|
|
597
596
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -636,8 +635,8 @@ export async function sendDocument(
|
|
|
636
635
|
const r = await sendGroupFileMessage(token, ctx.targetId, undefined, mediaPath, ctx.replyToId, fileName);
|
|
637
636
|
return { channel: "qqbot", messageId: r.id, timestamp: r.timestamp };
|
|
638
637
|
} else {
|
|
639
|
-
|
|
640
|
-
return { channel: "qqbot",
|
|
638
|
+
console.log(`${prefix} sendDocument: file not supported in channel`);
|
|
639
|
+
return { channel: "qqbot", error: "File not supported in channel" };
|
|
641
640
|
}
|
|
642
641
|
}
|
|
643
642
|
|
|
@@ -665,7 +664,7 @@ async function sendDocumentFromLocal(ctx: MediaTargetContext, mediaPath: string,
|
|
|
665
664
|
const fileName = sanitizeFileName(path.basename(mediaPath));
|
|
666
665
|
|
|
667
666
|
if (!(await fileExistsAsync(mediaPath))) {
|
|
668
|
-
return { channel: "qqbot", error:
|
|
667
|
+
return { channel: "qqbot", error: "File not found" };
|
|
669
668
|
}
|
|
670
669
|
const sizeCheck = checkFileSize(mediaPath);
|
|
671
670
|
if (!sizeCheck.ok) {
|
|
@@ -687,8 +686,8 @@ async function sendDocumentFromLocal(ctx: MediaTargetContext, mediaPath: string,
|
|
|
687
686
|
const r = await sendGroupFileMessage(token, ctx.targetId, fileBase64, undefined, ctx.replyToId, fileName);
|
|
688
687
|
return { channel: "qqbot", messageId: r.id, timestamp: r.timestamp };
|
|
689
688
|
} else {
|
|
690
|
-
|
|
691
|
-
return { channel: "qqbot",
|
|
689
|
+
console.log(`${prefix} sendDocument: file not supported in channel`);
|
|
690
|
+
return { channel: "qqbot", error: "File not supported in channel" };
|
|
692
691
|
}
|
|
693
692
|
} catch (err) {
|
|
694
693
|
const msg = err instanceof Error ? err.message : String(err);
|
package/src/ref-index-store.ts
CHANGED
|
@@ -20,7 +20,7 @@ import { getQQBotDataDir } from "./utils/platform.js";
|
|
|
20
20
|
// ============ 存储的消息摘要 ============
|
|
21
21
|
|
|
22
22
|
export interface RefIndexEntry {
|
|
23
|
-
/**
|
|
23
|
+
/** 消息文本内容(完整保存) */
|
|
24
24
|
content: string;
|
|
25
25
|
/** 发送者 ID */
|
|
26
26
|
senderId: string;
|
|
@@ -56,7 +56,6 @@ export interface RefAttachmentSummary {
|
|
|
56
56
|
|
|
57
57
|
const STORAGE_DIR = getQQBotDataDir("data");
|
|
58
58
|
const REF_INDEX_FILE = path.join(STORAGE_DIR, "ref-index.jsonl");
|
|
59
|
-
const MAX_CONTENT_LENGTH = 500; // 存储的消息内容最大字符数
|
|
60
59
|
const MAX_ENTRIES = 50000; // 内存中最大缓存条目数
|
|
61
60
|
const TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 天
|
|
62
61
|
const COMPACT_THRESHOLD_RATIO = 2; // 文件行数超过有效条目 N 倍时 compact
|
|
@@ -234,7 +233,7 @@ export function setRefIndex(refIdx: string, entry: RefIndexEntry): void {
|
|
|
234
233
|
|
|
235
234
|
const now = Date.now();
|
|
236
235
|
store.set(refIdx, {
|
|
237
|
-
content: entry.content
|
|
236
|
+
content: entry.content,
|
|
238
237
|
senderId: entry.senderId,
|
|
239
238
|
senderName: entry.senderName,
|
|
240
239
|
timestamp: entry.timestamp,
|
|
@@ -247,7 +246,7 @@ export function setRefIndex(refIdx: string, entry: RefIndexEntry): void {
|
|
|
247
246
|
appendLine({
|
|
248
247
|
k: refIdx,
|
|
249
248
|
v: {
|
|
250
|
-
content: entry.content
|
|
249
|
+
content: entry.content,
|
|
251
250
|
senderId: entry.senderId,
|
|
252
251
|
senderName: entry.senderName,
|
|
253
252
|
timestamp: entry.timestamp,
|
package/src/user-messages.ts
CHANGED
|
@@ -1,73 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 用户面向的提示文案 — 已清空
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 3. 统一风格:去掉 [QQBot] 前缀和 [方括号] 格式
|
|
8
|
-
* 4. 所有面向用户的文案集中在此文件,便于维护和国际化
|
|
4
|
+
* 设计原则(对齐飞书插件):
|
|
5
|
+
* QQBot 插件层不生成额外的用户提示信息。
|
|
6
|
+
* 所有运行时错误仅写日志,不面向用户展示。
|
|
9
7
|
*/
|
|
10
|
-
|
|
11
|
-
// ============ 媒体发送错误 ============
|
|
12
|
-
|
|
13
|
-
export const MSG = {
|
|
14
|
-
// 通用错误
|
|
15
|
-
GENERIC_ERROR: "抱歉,处理消息时遇到了问题,请稍后再试~",
|
|
16
|
-
AI_AUTH_ERROR: "抱歉,AI 服务暂时不可用,请联系管理员检查配置~",
|
|
17
|
-
AI_PROCESS_ERROR: "抱歉,AI 处理遇到了问题,请稍后再试~",
|
|
18
|
-
TIMEOUT_HINT: "已收到消息,正在处理中…",
|
|
19
|
-
|
|
20
|
-
// 图片
|
|
21
|
-
IMAGE_NOT_FOUND: "抱歉,图片不存在或已失效,无法发送~",
|
|
22
|
-
IMAGE_FORMAT_UNSUPPORTED: (ext: string) => `抱歉,暂不支持 ${ext} 格式的图片~`,
|
|
23
|
-
IMAGE_SEND_FAILED: "抱歉,图片发送失败了,请稍后再试~",
|
|
24
|
-
IMAGE_UPLOADING: (size: string) => `正在上传图片 (${size})...`,
|
|
25
|
-
|
|
26
|
-
// 语音
|
|
27
|
-
VOICE_GENERATE_FAILED: "抱歉,语音生成失败,请稍后重试~",
|
|
28
|
-
VOICE_CONVERT_FAILED: "抱歉,语音格式转换失败,请稍后重试~",
|
|
29
|
-
VOICE_SEND_FAILED: "抱歉,语音发送失败了,请稍后再试~",
|
|
30
|
-
VOICE_NOT_AVAILABLE: "抱歉,语音功能暂未开启~",
|
|
31
|
-
VOICE_MISSING_TEXT: "抱歉,语音消息缺少内容~",
|
|
32
|
-
VOICE_CHANNEL_UNSUPPORTED: "抱歉,语音消息暂不支持在频道中发送~",
|
|
33
|
-
|
|
34
|
-
// 视频
|
|
35
|
-
VIDEO_NOT_FOUND: "抱歉,视频文件不存在或已失效,无法发送~",
|
|
36
|
-
VIDEO_SEND_FAILED: "抱歉,视频发送失败了,请稍后再试~",
|
|
37
|
-
VIDEO_MISSING_PATH: "抱歉,视频消息缺少内容~",
|
|
38
|
-
VIDEO_CHANNEL_UNSUPPORTED: "抱歉,视频消息暂不支持在频道中发送~",
|
|
39
|
-
VIDEO_UPLOADING: (size: string) => `正在上传视频 (${size})...`,
|
|
40
|
-
|
|
41
|
-
// 文件
|
|
42
|
-
FILE_NOT_FOUND: "抱歉,文件不存在或已失效,无法发送~",
|
|
43
|
-
FILE_SEND_FAILED: "抱歉,文件发送失败了,请稍后再试~",
|
|
44
|
-
FILE_MISSING_PATH: "抱歉,文件消息缺少内容~",
|
|
45
|
-
FILE_CHANNEL_UNSUPPORTED: "抱歉,文件消息暂不支持在频道中发送~",
|
|
46
|
-
FILE_UPLOADING: (name: string, size: string) => `正在上传文件 ${name} (${size})...`,
|
|
47
|
-
|
|
48
|
-
// 载荷解析
|
|
49
|
-
PAYLOAD_PARSE_ERROR: "抱歉,消息格式异常,无法处理~",
|
|
50
|
-
UNSUPPORTED_MEDIA_TYPE: "抱歉,暂不支持该媒体类型~",
|
|
51
|
-
UNSUPPORTED_PAYLOAD_TYPE: "抱歉,暂不支持该消息类型~",
|
|
52
|
-
} as const;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* 将媒体上传/发送错误转为对用户友好的提示文案
|
|
56
|
-
* 技术细节不暴露给用户,仅记录到日志
|
|
57
|
-
*/
|
|
58
|
-
export function formatMediaErrorMessage(mediaType: string, err: unknown): string {
|
|
59
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
60
|
-
if (msg.includes("上传超时") || msg.includes("timeout") || msg.includes("Timeout")) {
|
|
61
|
-
return `抱歉,${mediaType}资源加载超时,可能是网络原因或文件太大,请稍后再试~`;
|
|
62
|
-
}
|
|
63
|
-
if (msg.includes("文件不存在") || msg.includes("not found") || msg.includes("Not Found")) {
|
|
64
|
-
return `抱歉,${mediaType}文件不存在或已失效,无法发送~`;
|
|
65
|
-
}
|
|
66
|
-
if (msg.includes("文件大小") || msg.includes("too large") || msg.includes("exceed")) {
|
|
67
|
-
return `抱歉,${mediaType}文件太大了,超出了发送限制~`;
|
|
68
|
-
}
|
|
69
|
-
if (msg.includes("Network error") || msg.includes("ECONNREFUSED") || msg.includes("ENOTFOUND")) {
|
|
70
|
-
return `抱歉,网络连接异常,${mediaType}发送失败,请稍后再试~`;
|
|
71
|
-
}
|
|
72
|
-
return `抱歉,${mediaType}发送失败了,请稍后再试~`;
|
|
73
|
-
}
|