@nextclaw/channel-runtime 0.1.25 → 0.1.26
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/index.d.ts +6 -0
- package/dist/index.js +73 -11
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -175,6 +175,7 @@ declare class QQChannel extends BaseChannel<Config["channels"]["qq"]> {
|
|
|
175
175
|
private bot;
|
|
176
176
|
private processedIds;
|
|
177
177
|
private processedSet;
|
|
178
|
+
private senderNameCache;
|
|
178
179
|
private reconnectTimer;
|
|
179
180
|
private connectTask;
|
|
180
181
|
private reconnectAttempt;
|
|
@@ -184,13 +185,18 @@ declare class QQChannel extends BaseChannel<Config["channels"]["qq"]> {
|
|
|
184
185
|
start(): Promise<void>;
|
|
185
186
|
stop(): Promise<void>;
|
|
186
187
|
send(msg: OutboundMessage): Promise<void>;
|
|
188
|
+
private sendByMessageType;
|
|
187
189
|
private handleIncoming;
|
|
188
190
|
private resolveSenderName;
|
|
189
191
|
private decorateSpeakerPrefix;
|
|
190
192
|
private sanitizeSpeakerToken;
|
|
193
|
+
private extractDeclaredName;
|
|
191
194
|
private isDuplicate;
|
|
192
195
|
private sendWithTokenRetry;
|
|
193
196
|
private isTokenExpiredError;
|
|
197
|
+
private isDisallowedUrlParamError;
|
|
198
|
+
private toQqSafeText;
|
|
199
|
+
private extractBlockedUrlToken;
|
|
194
200
|
private tryConnect;
|
|
195
201
|
private connect;
|
|
196
202
|
private createBot;
|
package/dist/index.js
CHANGED
|
@@ -893,10 +893,10 @@ function chunkDiscordText(text, opts = {}) {
|
|
|
893
893
|
preserveWhitespace: wasInsideFence
|
|
894
894
|
});
|
|
895
895
|
for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex += 1) {
|
|
896
|
-
const
|
|
896
|
+
const segment = segments[segmentIndex];
|
|
897
897
|
const isContinuation = segmentIndex > 0;
|
|
898
898
|
const delimiter = isContinuation ? "" : current.length > 0 ? "\n" : "";
|
|
899
|
-
const addition = `${delimiter}${
|
|
899
|
+
const addition = `${delimiter}${segment}`;
|
|
900
900
|
const nextLength = current.length + addition.length;
|
|
901
901
|
const nextLineCount = currentLines + (isContinuation ? 0 : 1);
|
|
902
902
|
const exceedsChars = nextLength > charLimit;
|
|
@@ -910,7 +910,7 @@ function chunkDiscordText(text, opts = {}) {
|
|
|
910
910
|
currentLines += 1;
|
|
911
911
|
}
|
|
912
912
|
} else {
|
|
913
|
-
current =
|
|
913
|
+
current = segment;
|
|
914
914
|
currentLines = 1;
|
|
915
915
|
}
|
|
916
916
|
}
|
|
@@ -2414,14 +2414,14 @@ function sleep3(ms) {
|
|
|
2414
2414
|
import {
|
|
2415
2415
|
Bot,
|
|
2416
2416
|
ReceiverMode,
|
|
2417
|
-
SessionEvents
|
|
2418
|
-
segment
|
|
2417
|
+
SessionEvents
|
|
2419
2418
|
} from "qq-official-bot";
|
|
2420
2419
|
var QQChannel = class extends BaseChannel {
|
|
2421
2420
|
name = "qq";
|
|
2422
2421
|
bot = null;
|
|
2423
2422
|
processedIds = [];
|
|
2424
2423
|
processedSet = /* @__PURE__ */ new Set();
|
|
2424
|
+
senderNameCache = /* @__PURE__ */ new Map();
|
|
2425
2425
|
reconnectTimer = null;
|
|
2426
2426
|
connectTask = null;
|
|
2427
2427
|
reconnectAttempt = 0;
|
|
@@ -2458,7 +2458,20 @@ var QQChannel = class extends BaseChannel {
|
|
|
2458
2458
|
const metadataMessageId = msg.metadata?.message_id ?? null;
|
|
2459
2459
|
const sourceId = msg.replyTo ?? metadataMessageId ?? void 0;
|
|
2460
2460
|
const source = sourceId ? { id: sourceId } : void 0;
|
|
2461
|
-
const
|
|
2461
|
+
const rawContent = msg.content ?? "";
|
|
2462
|
+
const payload = rawContent;
|
|
2463
|
+
try {
|
|
2464
|
+
await this.sendByMessageType({ messageType, qqMeta, msg, payload, source });
|
|
2465
|
+
} catch (error) {
|
|
2466
|
+
if (!this.isDisallowedUrlParamError(error)) {
|
|
2467
|
+
throw error;
|
|
2468
|
+
}
|
|
2469
|
+
const safeText = this.toQqSafeText(rawContent, error);
|
|
2470
|
+
await this.sendByMessageType({ messageType, qqMeta, msg, payload: safeText, source });
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
async sendByMessageType(params) {
|
|
2474
|
+
const { messageType, qqMeta, msg, payload, source } = params;
|
|
2462
2475
|
if (messageType === "group") {
|
|
2463
2476
|
const groupId = qqMeta.groupId ?? msg.chatId;
|
|
2464
2477
|
await this.sendWithTokenRetry(() => this.bot?.sendGroupMessage(groupId, payload, source));
|
|
@@ -2492,7 +2505,15 @@ var QQChannel = class extends BaseChannel {
|
|
|
2492
2505
|
}
|
|
2493
2506
|
const content = event.raw_message?.trim() ?? "";
|
|
2494
2507
|
const normalizedContent = content || "[empty message]";
|
|
2495
|
-
const
|
|
2508
|
+
const eventSenderName = this.resolveSenderName(rawEvent);
|
|
2509
|
+
if (eventSenderName) {
|
|
2510
|
+
this.senderNameCache.set(senderId, eventSenderName);
|
|
2511
|
+
}
|
|
2512
|
+
const declaredName = this.extractDeclaredName(normalizedContent);
|
|
2513
|
+
if (declaredName) {
|
|
2514
|
+
this.senderNameCache.set(senderId, declaredName);
|
|
2515
|
+
}
|
|
2516
|
+
const senderName = declaredName ?? eventSenderName ?? this.senderNameCache.get(senderId) ?? null;
|
|
2496
2517
|
let chatId = senderId;
|
|
2497
2518
|
let messageType = "private";
|
|
2498
2519
|
const qqMeta = {};
|
|
@@ -2524,6 +2545,9 @@ var QQChannel = class extends BaseChannel {
|
|
|
2524
2545
|
}
|
|
2525
2546
|
} else {
|
|
2526
2547
|
qqMeta.userId = senderId;
|
|
2548
|
+
if (senderName) {
|
|
2549
|
+
qqMeta.userName = senderName;
|
|
2550
|
+
}
|
|
2527
2551
|
}
|
|
2528
2552
|
qqMeta.messageType = messageType;
|
|
2529
2553
|
const safeContent = this.decorateSpeakerPrefix({
|
|
@@ -2554,7 +2578,8 @@ var QQChannel = class extends BaseChannel {
|
|
|
2554
2578
|
rawEvent.sender?.card,
|
|
2555
2579
|
rawEvent.sender?.nickname,
|
|
2556
2580
|
rawEvent.sender?.nick,
|
|
2557
|
-
rawEvent.sender?.username
|
|
2581
|
+
rawEvent.sender?.username,
|
|
2582
|
+
rawEvent.sender?.user_name
|
|
2558
2583
|
];
|
|
2559
2584
|
for (const value of candidates) {
|
|
2560
2585
|
if (typeof value !== "string") {
|
|
@@ -2568,9 +2593,6 @@ var QQChannel = class extends BaseChannel {
|
|
|
2568
2593
|
return null;
|
|
2569
2594
|
}
|
|
2570
2595
|
decorateSpeakerPrefix(params) {
|
|
2571
|
-
if (params.messageType !== "group" && params.messageType !== "guild") {
|
|
2572
|
-
return params.content;
|
|
2573
|
-
}
|
|
2574
2596
|
const userId = this.sanitizeSpeakerToken(params.senderId);
|
|
2575
2597
|
if (!userId) {
|
|
2576
2598
|
return params.content;
|
|
@@ -2585,6 +2607,25 @@ var QQChannel = class extends BaseChannel {
|
|
|
2585
2607
|
sanitizeSpeakerToken(value) {
|
|
2586
2608
|
return value.replace(/[\r\n;\]]/g, " ").trim();
|
|
2587
2609
|
}
|
|
2610
|
+
extractDeclaredName(content) {
|
|
2611
|
+
const trimmed = content.trim();
|
|
2612
|
+
const patterns = [
|
|
2613
|
+
/^我的昵称是\s*([^\s,。!?!?,]{1,24})$/u,
|
|
2614
|
+
/^我叫\s*([^\s,。!?!?,]{1,24})$/u,
|
|
2615
|
+
/^叫我\s*([^\s,。!?!?,]{1,24})$/u
|
|
2616
|
+
];
|
|
2617
|
+
for (const pattern of patterns) {
|
|
2618
|
+
const match = trimmed.match(pattern);
|
|
2619
|
+
if (!match) {
|
|
2620
|
+
continue;
|
|
2621
|
+
}
|
|
2622
|
+
const candidate = this.sanitizeSpeakerToken(match[1] ?? "");
|
|
2623
|
+
if (candidate) {
|
|
2624
|
+
return candidate;
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
return null;
|
|
2628
|
+
}
|
|
2588
2629
|
isDuplicate(messageId) {
|
|
2589
2630
|
if (this.processedSet.has(messageId)) {
|
|
2590
2631
|
return true;
|
|
@@ -2614,6 +2655,27 @@ var QQChannel = class extends BaseChannel {
|
|
|
2614
2655
|
const message = error instanceof Error ? error.message : String(error);
|
|
2615
2656
|
return message.includes("code(11244)") || message.toLowerCase().includes("token not exist or expire");
|
|
2616
2657
|
}
|
|
2658
|
+
isDisallowedUrlParamError(error) {
|
|
2659
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2660
|
+
return message.includes("code(40034028)") || message.includes("\u8BF7\u6C42\u53C2\u6570\u4E0D\u5141\u8BB8\u5305\u542Burl");
|
|
2661
|
+
}
|
|
2662
|
+
toQqSafeText(content, error) {
|
|
2663
|
+
let safe = content.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1").replace(/https?:\/\/\S+/gi, "[link]").replace(/www\.\S+/gi, "[link]").replace(/\b[a-z0-9._/-]+\.md\b/gi, "[file]");
|
|
2664
|
+
const blocked = this.extractBlockedUrlToken(error);
|
|
2665
|
+
if (blocked) {
|
|
2666
|
+
safe = safe.replaceAll(blocked, "[link]");
|
|
2667
|
+
}
|
|
2668
|
+
return safe;
|
|
2669
|
+
}
|
|
2670
|
+
extractBlockedUrlToken(error) {
|
|
2671
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2672
|
+
const match = message.match(/包含url\s+([^\s]+)/);
|
|
2673
|
+
if (!match) {
|
|
2674
|
+
return null;
|
|
2675
|
+
}
|
|
2676
|
+
const token = match[1].trim();
|
|
2677
|
+
return token.length > 0 ? token : null;
|
|
2678
|
+
}
|
|
2617
2679
|
tryConnect(trigger) {
|
|
2618
2680
|
if (!this.running || this.bot || this.connectTask) {
|
|
2619
2681
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/channel-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.26",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Runtime implementations for NextClaw builtin channel plugins.",
|
|
6
6
|
"type": "module",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
],
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@larksuiteoapi/node-sdk": "^1.58.0",
|
|
18
|
-
"@nextclaw/core": "^0.6.
|
|
18
|
+
"@nextclaw/core": "^0.6.43",
|
|
19
19
|
"@slack/socket-mode": "^1.3.3",
|
|
20
20
|
"@slack/web-api": "^7.6.0",
|
|
21
21
|
"dingtalk-stream": "^2.1.4",
|