gewe-openclaw 2026.3.23 → 2026.3.25
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 +9 -3
- package/package.json +1 -1
- package/src/config-schema.ts +12 -1
- package/src/inbound.ts +100 -20
- package/src/monitor.ts +8 -1
- package/src/policy.ts +15 -6
- package/src/reply-options.ts +46 -6
- package/src/types.ts +9 -2
- package/src/xml.ts +18 -0
package/README.md
CHANGED
|
@@ -141,7 +141,12 @@ openclaw onboard
|
|
|
141
141
|
- `plain`:普通回复
|
|
142
142
|
- `quote_source`:首条回复自动引用当前入站消息
|
|
143
143
|
- `at_sender`:首条文本回复自动 `@` 发送者
|
|
144
|
-
|
|
144
|
+
|
|
145
|
+
兼容说明:
|
|
146
|
+
|
|
147
|
+
- `quote_and_at` 已下线,不再建议继续配置
|
|
148
|
+
- 历史配置里的 `quote_and_at` 仍会被兼容读取,但会自动降级为 `quote_source + 可见 @昵称前缀`
|
|
149
|
+
- 这个兼容降级不是微信原生 `@`,只是为了避免旧配置直接失效
|
|
145
150
|
|
|
146
151
|
群聊默认值会跟随 `autoQuoteReply`:
|
|
147
152
|
|
|
@@ -167,6 +172,7 @@ openclaw onboard
|
|
|
167
172
|
- `requireMention: true/false` 仍然可用,会分别映射到群聊 `trigger.mode = "at"` / `"any_message"`
|
|
168
173
|
- 新的 `trigger` / `reply` 配置优先级更高
|
|
169
174
|
- `autoQuoteReply` 现在主要用于“未显式配置 `reply.mode` 时”的默认值回退
|
|
175
|
+
- 历史 `reply.mode = "quote_and_at"` 会自动降级为 `quote_source + 可见 @昵称前缀`
|
|
170
176
|
|
|
171
177
|
示例:
|
|
172
178
|
|
|
@@ -182,7 +188,7 @@ openclaw onboard
|
|
|
182
188
|
},
|
|
183
189
|
"project-room@chatroom": {
|
|
184
190
|
"trigger": { "mode": "at_or_quote" },
|
|
185
|
-
"reply": { "mode": "
|
|
191
|
+
"reply": { "mode": "at_sender" },
|
|
186
192
|
"skills": ["project-skill"]
|
|
187
193
|
},
|
|
188
194
|
"ops-room@chatroom": {
|
|
@@ -303,7 +309,7 @@ GeWe 的状态页现在会额外显示:
|
|
|
303
309
|
"groups": {
|
|
304
310
|
"ops-room@chatroom": {
|
|
305
311
|
"trigger": { "mode": "at_or_quote" },
|
|
306
|
-
"reply": { "mode": "
|
|
312
|
+
"reply": { "mode": "quote_source" }
|
|
307
313
|
}
|
|
308
314
|
}
|
|
309
315
|
}
|
package/package.json
CHANGED
package/src/config-schema.ts
CHANGED
|
@@ -7,8 +7,11 @@ import {
|
|
|
7
7
|
ToolPolicySchema,
|
|
8
8
|
requireOpenAllowFrom,
|
|
9
9
|
} from "./openclaw-compat.js";
|
|
10
|
+
import type { GeweGroupReplyModeInput } from "./types.js";
|
|
10
11
|
import { z } from "zod";
|
|
11
12
|
|
|
13
|
+
const GEWE_GROUP_REPLY_MODES = ["plain", "quote_source", "at_sender", "quote_and_at"] as const;
|
|
14
|
+
|
|
12
15
|
const GeweGroupTriggerSchema = z
|
|
13
16
|
.object({
|
|
14
17
|
mode: z.enum(["at", "quote", "at_or_quote", "any_message"]).optional(),
|
|
@@ -23,7 +26,15 @@ const GeweDmTriggerSchema = z
|
|
|
23
26
|
|
|
24
27
|
const GeweGroupReplySchema = z
|
|
25
28
|
.object({
|
|
26
|
-
mode: z
|
|
29
|
+
mode: z
|
|
30
|
+
.custom<GeweGroupReplyModeInput>(
|
|
31
|
+
(value) => typeof value === "string" && GEWE_GROUP_REPLY_MODES.includes(value as GeweGroupReplyModeInput),
|
|
32
|
+
{
|
|
33
|
+
message:
|
|
34
|
+
"invalid GeWe group reply mode; supported values are plain, quote_source, at_sender (quote_and_at is accepted only for compatibility)",
|
|
35
|
+
},
|
|
36
|
+
)
|
|
37
|
+
.optional(),
|
|
27
38
|
})
|
|
28
39
|
.strict();
|
|
29
40
|
|
package/src/inbound.ts
CHANGED
|
@@ -37,9 +37,9 @@ import {
|
|
|
37
37
|
import type {
|
|
38
38
|
CoreConfig,
|
|
39
39
|
GeweDmReplyMode,
|
|
40
|
-
GeweGroupReplyMode,
|
|
41
40
|
GeweInboundMessage,
|
|
42
41
|
ResolvedGeweAccount,
|
|
42
|
+
ResolvedGeweGroupReplyMode,
|
|
43
43
|
} from "./types.js";
|
|
44
44
|
import {
|
|
45
45
|
extractAppMsgType,
|
|
@@ -68,7 +68,7 @@ type PreparedInbound = {
|
|
|
68
68
|
groupName?: string;
|
|
69
69
|
groupSystemPrompt?: string;
|
|
70
70
|
groupSkillFilter?: string[];
|
|
71
|
-
replyMode:
|
|
71
|
+
replyMode: ResolvedGeweGroupReplyMode | GeweDmReplyMode;
|
|
72
72
|
route: ReturnType<ReturnType<typeof getGeweRuntime>["channel"]["routing"]["resolveAgentRoute"]>;
|
|
73
73
|
storePath: string;
|
|
74
74
|
toWxid: string;
|
|
@@ -113,6 +113,41 @@ function resolveAppMsgPlaceholder(appType?: number): string {
|
|
|
113
113
|
return typeof appType === "number" ? `<appmsg:${appType}>` : "<appmsg>";
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
function isQuoteFromBot(params: {
|
|
117
|
+
quoteDetails?: GeweQuoteDetails;
|
|
118
|
+
isGroup: boolean;
|
|
119
|
+
botWxid: string;
|
|
120
|
+
}): boolean {
|
|
121
|
+
const quoteDetails = params.quoteDetails;
|
|
122
|
+
if (!quoteDetails) return false;
|
|
123
|
+
if (!params.isGroup) return true;
|
|
124
|
+
|
|
125
|
+
const botWxid = params.botWxid.trim();
|
|
126
|
+
if (!botWxid) return false;
|
|
127
|
+
return [quoteDetails.fromUsr, quoteDetails.chatUsr].some(
|
|
128
|
+
(value) => value?.trim() === botWxid,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function summarizeUnsupportedInboundMessage(message: GeweInboundMessage): string {
|
|
133
|
+
const preview = message.text.replace(/\s+/g, " ").trim().slice(0, 120);
|
|
134
|
+
const parts = [
|
|
135
|
+
`msgType=${message.msgType}`,
|
|
136
|
+
`from=${message.fromId}`,
|
|
137
|
+
`to=${message.toId}`,
|
|
138
|
+
`sender=${message.senderId}`,
|
|
139
|
+
`messageId=${message.messageId}`,
|
|
140
|
+
`newMessageId=${message.newMessageId}`,
|
|
141
|
+
preview ? `text=${JSON.stringify(preview)}` : undefined,
|
|
142
|
+
];
|
|
143
|
+
return parts.filter(Boolean).join(" ");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function summarizeTextPreview(text: string): string | undefined {
|
|
147
|
+
const preview = text.replace(/\s+/g, " ").trim().slice(0, 160);
|
|
148
|
+
return preview ? JSON.stringify(preview) : undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
116
151
|
function resolveGewePairCodeCandidate(rawBody: string): string | null {
|
|
117
152
|
const trimmed = rawBody.trim();
|
|
118
153
|
if (!trimmed) return null;
|
|
@@ -342,7 +377,7 @@ function normalizeInboundEntry(params: {
|
|
|
342
377
|
const { message, runtime } = params;
|
|
343
378
|
const msgType = message.msgType;
|
|
344
379
|
if (![1, 3, 34, 43, 49].includes(msgType)) {
|
|
345
|
-
runtime.log?.(`gewe: skip unsupported
|
|
380
|
+
runtime.log?.(`gewe: skip unsupported ${summarizeUnsupportedInboundMessage(message)}`);
|
|
346
381
|
return null;
|
|
347
382
|
}
|
|
348
383
|
|
|
@@ -611,6 +646,7 @@ async function dispatchGeweInbound(params: {
|
|
|
611
646
|
mode: prepared.replyMode,
|
|
612
647
|
isGroup: prepared.isGroup,
|
|
613
648
|
senderId: prepared.senderId,
|
|
649
|
+
senderName: prepared.senderName,
|
|
614
650
|
defaultReplyToId: prepared.messageSid,
|
|
615
651
|
repliedRef,
|
|
616
652
|
});
|
|
@@ -834,15 +870,39 @@ export async function handleGeweInboundBatch(params: {
|
|
|
834
870
|
return;
|
|
835
871
|
}
|
|
836
872
|
|
|
837
|
-
const
|
|
838
|
-
|
|
873
|
+
const route = core.channel.routing.resolveAgentRoute({
|
|
874
|
+
cfg: config as OpenClawConfig,
|
|
875
|
+
channel: CHANNEL_ID,
|
|
876
|
+
accountId: account.accountId,
|
|
877
|
+
peer: {
|
|
878
|
+
kind: isGroup ? "group" : "direct",
|
|
879
|
+
id: isGroup ? groupId ?? "" : senderId,
|
|
880
|
+
},
|
|
881
|
+
});
|
|
882
|
+
const mentionRegexes = core.channel.mentions.buildMentionRegexes(
|
|
883
|
+
config as OpenClawConfig,
|
|
884
|
+
route.agentId,
|
|
885
|
+
);
|
|
886
|
+
const nativeAtWxids = Array.from(
|
|
887
|
+
new Set(
|
|
888
|
+
entries
|
|
889
|
+
.flatMap((entry) => entry.message.atWxids ?? [])
|
|
890
|
+
.map((wxid) => wxid.trim())
|
|
891
|
+
.filter(Boolean),
|
|
892
|
+
),
|
|
893
|
+
);
|
|
894
|
+
const nativeAtAll = entries.some((entry) => entry.message.atAll === true);
|
|
895
|
+
const nativeAtTriggered = nativeAtWxids.includes(lastMessage.botWxid.trim());
|
|
896
|
+
const regexAtTriggered = mentionRegexes.length
|
|
839
897
|
? core.channel.mentions.matchesMentionPatterns(rawBodyCandidate, mentionRegexes)
|
|
840
898
|
: false;
|
|
899
|
+
const wasAtTriggered = nativeAtTriggered || regexAtTriggered;
|
|
841
900
|
const latestQuote = entries.at(-1)?.quoteDetails;
|
|
842
|
-
const wasQuoteTriggered =
|
|
843
|
-
latestQuote
|
|
844
|
-
|
|
845
|
-
|
|
901
|
+
const wasQuoteTriggered = isQuoteFromBot({
|
|
902
|
+
quoteDetails: latestQuote,
|
|
903
|
+
isGroup,
|
|
904
|
+
botWxid: lastMessage.botWxid,
|
|
905
|
+
});
|
|
846
906
|
const triggerMode = isGroup
|
|
847
907
|
? resolveGeweGroupTriggerMode({
|
|
848
908
|
groupConfig: groupMatch?.groupConfig,
|
|
@@ -862,23 +922,43 @@ export async function handleGeweInboundBatch(params: {
|
|
|
862
922
|
commandAuthorized,
|
|
863
923
|
});
|
|
864
924
|
if (triggerGate.shouldSkip) {
|
|
925
|
+
const detail =
|
|
926
|
+
triggerMode === "at"
|
|
927
|
+
? [
|
|
928
|
+
`agent=${route.agentId ?? "default"}`,
|
|
929
|
+
`wasAtTriggered=${String(wasAtTriggered)}`,
|
|
930
|
+
`nativeAtTriggered=${String(nativeAtTriggered)}`,
|
|
931
|
+
`nativeAtAll=${String(nativeAtAll)}`,
|
|
932
|
+
`regexAtTriggered=${String(regexAtTriggered)}`,
|
|
933
|
+
`wasQuoteTriggered=${String(wasQuoteTriggered)}`,
|
|
934
|
+
`mentionRegexes=${JSON.stringify(mentionRegexes.map((regex) => regex.source))}`,
|
|
935
|
+
`nativeAtWxids=${JSON.stringify(nativeAtWxids)}`,
|
|
936
|
+
summarizeTextPreview(rawBodyCandidate)
|
|
937
|
+
? `rawBody=${summarizeTextPreview(rawBodyCandidate)}`
|
|
938
|
+
: undefined,
|
|
939
|
+
]
|
|
940
|
+
.filter(Boolean)
|
|
941
|
+
.join(" ")
|
|
942
|
+
: triggerMode === "quote"
|
|
943
|
+
? [
|
|
944
|
+
`wasQuoteTriggered=${String(wasQuoteTriggered)}`,
|
|
945
|
+
latestQuote?.fromUsr ? `quoteFromUsr=${JSON.stringify(latestQuote.fromUsr)}` : undefined,
|
|
946
|
+
latestQuote?.chatUsr ? `quoteChatUsr=${JSON.stringify(latestQuote.chatUsr)}` : undefined,
|
|
947
|
+
summarizeTextPreview(rawBodyCandidate)
|
|
948
|
+
? `rawBody=${summarizeTextPreview(rawBodyCandidate)}`
|
|
949
|
+
: undefined,
|
|
950
|
+
]
|
|
951
|
+
.filter(Boolean)
|
|
952
|
+
.join(" ")
|
|
953
|
+
: undefined;
|
|
865
954
|
runtime.log?.(
|
|
866
955
|
isGroup
|
|
867
|
-
? `gewe: drop group ${groupId} (trigger=${triggerMode})`
|
|
868
|
-
: `gewe: drop DM sender ${senderId} (trigger=${triggerMode})`,
|
|
956
|
+
? `gewe: drop group ${groupId} (trigger=${triggerMode})${detail ? ` ${detail}` : ""}`
|
|
957
|
+
: `gewe: drop DM sender ${senderId} (trigger=${triggerMode})${detail ? ` ${detail}` : ""}`,
|
|
869
958
|
);
|
|
870
959
|
return;
|
|
871
960
|
}
|
|
872
961
|
|
|
873
|
-
const route = core.channel.routing.resolveAgentRoute({
|
|
874
|
-
cfg: config as OpenClawConfig,
|
|
875
|
-
channel: CHANNEL_ID,
|
|
876
|
-
accountId: account.accountId,
|
|
877
|
-
peer: {
|
|
878
|
-
kind: isGroup ? "group" : "direct",
|
|
879
|
-
id: isGroup ? groupId ?? "" : senderId,
|
|
880
|
-
},
|
|
881
|
-
});
|
|
882
962
|
const storePath = core.channel.session.resolveStorePath(config.session?.store, {
|
|
883
963
|
agentId: route.agentId,
|
|
884
964
|
});
|
package/src/monitor.ts
CHANGED
|
@@ -23,6 +23,7 @@ import type {
|
|
|
23
23
|
GeweWebhookServerOptions,
|
|
24
24
|
ResolvedGeweAccount,
|
|
25
25
|
} from "./types.js";
|
|
26
|
+
import { extractAtUserList } from "./xml.js";
|
|
26
27
|
|
|
27
28
|
const DEFAULT_WEBHOOK_PORT = 4399;
|
|
28
29
|
const DEFAULT_WEBHOOK_HOST = "0.0.0.0";
|
|
@@ -155,6 +156,8 @@ function payloadToInboundMessage(payload: GeweCallbackPayload): GeweInboundMessa
|
|
|
155
156
|
const groupParsed = isGroupChat ? splitGroupContent(content) : { body: content };
|
|
156
157
|
const senderId = (isGroupChat ? groupParsed.senderId : fromId) ?? fromId;
|
|
157
158
|
const text = groupParsed.body?.trim() ?? "";
|
|
159
|
+
const atWxids = extractAtUserList(data.MsgSource);
|
|
160
|
+
const atAll = atWxids.includes("notify@all");
|
|
158
161
|
|
|
159
162
|
return {
|
|
160
163
|
messageId: String(msgId),
|
|
@@ -166,6 +169,8 @@ function payloadToInboundMessage(payload: GeweCallbackPayload): GeweInboundMessa
|
|
|
166
169
|
senderId,
|
|
167
170
|
senderName: resolveSenderName(data.PushContent),
|
|
168
171
|
text,
|
|
172
|
+
atWxids: atWxids.length ? atWxids : undefined,
|
|
173
|
+
atAll,
|
|
169
174
|
msgType,
|
|
170
175
|
xml: text,
|
|
171
176
|
timestamp,
|
|
@@ -178,7 +183,7 @@ export function createGeweWebhookServer(opts: GeweWebhookServerOptions): {
|
|
|
178
183
|
start: () => Promise<void>;
|
|
179
184
|
stop: () => void;
|
|
180
185
|
} {
|
|
181
|
-
const { port, host, path, mediaPath, secret, onMessage, onError, abortSignal } = opts;
|
|
186
|
+
const { port, host, path, mediaPath, secret, onRawPayload, onMessage, onError, abortSignal } = opts;
|
|
182
187
|
|
|
183
188
|
const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
|
|
184
189
|
if (req.url === HEALTH_PATH) {
|
|
@@ -230,6 +235,7 @@ export function createGeweWebhookServer(opts: GeweWebhookServerOptions): {
|
|
|
230
235
|
return;
|
|
231
236
|
}
|
|
232
237
|
|
|
238
|
+
onRawPayload?.(bodyResult.raw);
|
|
233
239
|
const payload = parseWebhookPayload(bodyResult.raw);
|
|
234
240
|
if (!payload) {
|
|
235
241
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
@@ -357,6 +363,7 @@ export async function monitorGeweProvider(
|
|
|
357
363
|
path,
|
|
358
364
|
mediaPath: shouldStartMedia ? mediaPath : undefined,
|
|
359
365
|
secret,
|
|
366
|
+
onRawPayload: (raw) => runtime.log?.(`[${account.accountId}] GeWe webhook raw: ${raw}`),
|
|
360
367
|
onMessage: async (message) => {
|
|
361
368
|
const isSelf = message.fromId === message.botWxid || message.senderId === message.botWxid;
|
|
362
369
|
if (isSelf) return;
|
package/src/policy.ts
CHANGED
|
@@ -15,8 +15,9 @@ import type {
|
|
|
15
15
|
GeweDmReplyMode,
|
|
16
16
|
GeweDmTriggerMode,
|
|
17
17
|
GeweGroupConfig,
|
|
18
|
-
|
|
18
|
+
GeweGroupReplyModeInput,
|
|
19
19
|
GeweGroupTriggerMode,
|
|
20
|
+
ResolvedGeweGroupReplyMode,
|
|
20
21
|
} from "./types.js";
|
|
21
22
|
|
|
22
23
|
function normalizeAllowEntry(raw: string): string {
|
|
@@ -189,16 +190,24 @@ export function resolveGeweDmTriggerMode(params: {
|
|
|
189
190
|
return params.dmConfig?.trigger?.mode ?? params.wildcardConfig?.trigger?.mode ?? "any_message";
|
|
190
191
|
}
|
|
191
192
|
|
|
193
|
+
function resolveConfiguredGroupReplyMode(
|
|
194
|
+
configuredMode: GeweGroupReplyModeInput | undefined,
|
|
195
|
+
): ResolvedGeweGroupReplyMode | undefined {
|
|
196
|
+
if (configuredMode === "quote_and_at") {
|
|
197
|
+
return "quote_and_at_compat";
|
|
198
|
+
}
|
|
199
|
+
return configuredMode;
|
|
200
|
+
}
|
|
201
|
+
|
|
192
202
|
export function resolveGeweGroupReplyMode(params: {
|
|
193
203
|
groupConfig?: GeweGroupConfig;
|
|
194
204
|
wildcardConfig?: GeweGroupConfig;
|
|
195
205
|
autoQuoteReply?: boolean;
|
|
196
|
-
}):
|
|
197
|
-
|
|
198
|
-
params.groupConfig?.reply?.mode ??
|
|
199
|
-
params.wildcardConfig?.reply?.mode ??
|
|
200
|
-
(params.autoQuoteReply === false ? "plain" : "quote_source")
|
|
206
|
+
}): ResolvedGeweGroupReplyMode {
|
|
207
|
+
const configuredMode = resolveConfiguredGroupReplyMode(
|
|
208
|
+
params.groupConfig?.reply?.mode ?? params.wildcardConfig?.reply?.mode,
|
|
201
209
|
);
|
|
210
|
+
return configuredMode ?? (params.autoQuoteReply === false ? "plain" : "quote_source");
|
|
202
211
|
}
|
|
203
212
|
|
|
204
213
|
export function resolveGeweDmReplyMode(params: {
|
package/src/reply-options.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { ReplyPayload } from "./openclaw-compat.js";
|
|
2
2
|
import type {
|
|
3
3
|
GeweDmReplyMode,
|
|
4
|
-
GeweGroupReplyMode,
|
|
5
4
|
ResolvedGeweAccount,
|
|
5
|
+
ResolvedGeweGroupReplyMode,
|
|
6
6
|
} from "./types.js";
|
|
7
7
|
|
|
8
8
|
const CHANNEL_DATA_KEY = "gewe-openclaw";
|
|
9
9
|
|
|
10
|
-
type GeweReplyMode =
|
|
10
|
+
type GeweReplyMode = ResolvedGeweGroupReplyMode | GeweDmReplyMode;
|
|
11
11
|
type RepliedRef = { value: boolean };
|
|
12
12
|
|
|
13
13
|
function canAttachAt(payload: ReplyPayload): boolean {
|
|
@@ -64,6 +64,38 @@ function withAtSender(payload: ReplyPayload, senderId: string | undefined): Repl
|
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
function withAtSenderPrefix(payload: ReplyPayload, senderName: string | undefined): ReplyPayload {
|
|
68
|
+
const trimmedSenderName = senderName?.trim();
|
|
69
|
+
const trimmedText = payload.text?.trim();
|
|
70
|
+
if (!trimmedSenderName || !trimmedText) {
|
|
71
|
+
return payload;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const mentionPrefix = `@${trimmedSenderName}`;
|
|
75
|
+
if (trimmedText === mentionPrefix || trimmedText.startsWith(`${mentionPrefix} `) || trimmedText.startsWith(`${mentionPrefix}\u2005`)) {
|
|
76
|
+
return payload;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
...payload,
|
|
81
|
+
text: `${mentionPrefix}\u2005${trimmedText}`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function withoutDefaultReplyToId(
|
|
86
|
+
payload: ReplyPayload,
|
|
87
|
+
defaultReplyToId: string | undefined,
|
|
88
|
+
): ReplyPayload {
|
|
89
|
+
const explicitReplyToId = payload.replyToId?.trim();
|
|
90
|
+
const trimmedDefaultReplyToId = defaultReplyToId?.trim();
|
|
91
|
+
if (!explicitReplyToId || !trimmedDefaultReplyToId || explicitReplyToId !== trimmedDefaultReplyToId) {
|
|
92
|
+
return payload;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const { replyToId: _ignored, ...rest } = payload;
|
|
96
|
+
return rest;
|
|
97
|
+
}
|
|
98
|
+
|
|
67
99
|
export function resolveGeweReplyOptions(
|
|
68
100
|
account: Pick<ResolvedGeweAccount, "config">,
|
|
69
101
|
opts?: { skillFilter?: string[] },
|
|
@@ -83,25 +115,33 @@ export function applyGeweReplyModeToPayload(
|
|
|
83
115
|
mode: GeweReplyMode;
|
|
84
116
|
isGroup: boolean;
|
|
85
117
|
senderId?: string;
|
|
118
|
+
senderName?: string;
|
|
86
119
|
defaultReplyToId?: string;
|
|
87
120
|
repliedRef?: RepliedRef;
|
|
88
121
|
},
|
|
89
122
|
): ReplyPayload {
|
|
90
123
|
let nextPayload = payload;
|
|
91
124
|
const effectiveMode =
|
|
92
|
-
params.mode === "
|
|
125
|
+
params.mode === "quote_and_at_compat" && !canAttachAt(payload) ? "quote_source" : params.mode;
|
|
93
126
|
|
|
94
|
-
if (effectiveMode === "quote_source" || effectiveMode === "
|
|
127
|
+
if (effectiveMode === "quote_source" || effectiveMode === "quote_and_at_compat") {
|
|
95
128
|
nextPayload = withReplyToId(nextPayload, params.defaultReplyToId, params.repliedRef);
|
|
96
129
|
} else if (nextPayload.replyToId?.trim() && params.repliedRef) {
|
|
97
130
|
params.repliedRef.value = true;
|
|
98
131
|
}
|
|
99
132
|
|
|
133
|
+
if (effectiveMode === "plain" || effectiveMode === "at_sender") {
|
|
134
|
+
nextPayload = withoutDefaultReplyToId(nextPayload, params.defaultReplyToId);
|
|
135
|
+
}
|
|
136
|
+
|
|
100
137
|
if (
|
|
101
138
|
params.isGroup &&
|
|
102
|
-
(effectiveMode === "at_sender" || effectiveMode === "
|
|
139
|
+
(effectiveMode === "at_sender" || effectiveMode === "quote_and_at_compat")
|
|
103
140
|
) {
|
|
104
|
-
nextPayload =
|
|
141
|
+
nextPayload = withAtSenderPrefix(nextPayload, params.senderName);
|
|
142
|
+
if (effectiveMode === "at_sender") {
|
|
143
|
+
nextPayload = withAtSender(nextPayload, params.senderId);
|
|
144
|
+
}
|
|
105
145
|
}
|
|
106
146
|
|
|
107
147
|
return nextPayload;
|
package/src/types.ts
CHANGED
|
@@ -9,7 +9,10 @@ import type {
|
|
|
9
9
|
|
|
10
10
|
export type GeweGroupTriggerMode = "at" | "quote" | "at_or_quote" | "any_message";
|
|
11
11
|
export type GeweDmTriggerMode = "any_message" | "quote";
|
|
12
|
-
export type GeweGroupReplyMode = "plain" | "quote_source" | "at_sender"
|
|
12
|
+
export type GeweGroupReplyMode = "plain" | "quote_source" | "at_sender";
|
|
13
|
+
export type GeweDeprecatedGroupReplyMode = "quote_and_at";
|
|
14
|
+
export type GeweGroupReplyModeInput = GeweGroupReplyMode | GeweDeprecatedGroupReplyMode;
|
|
15
|
+
export type ResolvedGeweGroupReplyMode = GeweGroupReplyMode | "quote_and_at_compat";
|
|
13
16
|
export type GeweDmReplyMode = "plain" | "quote_source";
|
|
14
17
|
|
|
15
18
|
export type GeweGroupTriggerConfig = {
|
|
@@ -21,7 +24,7 @@ export type GeweDmTriggerConfig = {
|
|
|
21
24
|
};
|
|
22
25
|
|
|
23
26
|
export type GeweGroupReplyConfig = {
|
|
24
|
-
mode?:
|
|
27
|
+
mode?: GeweGroupReplyModeInput;
|
|
25
28
|
};
|
|
26
29
|
|
|
27
30
|
export type GeweDmReplyConfig = {
|
|
@@ -175,6 +178,7 @@ export type GeweCallbackPayload = {
|
|
|
175
178
|
ToUserName?: { string?: string };
|
|
176
179
|
MsgType?: number;
|
|
177
180
|
Content?: { string?: string };
|
|
181
|
+
MsgSource?: string;
|
|
178
182
|
CreateTime?: number;
|
|
179
183
|
PushContent?: string;
|
|
180
184
|
};
|
|
@@ -190,6 +194,8 @@ export type GeweInboundMessage = {
|
|
|
190
194
|
senderId: string;
|
|
191
195
|
senderName?: string;
|
|
192
196
|
text: string;
|
|
197
|
+
atWxids?: string[];
|
|
198
|
+
atAll?: boolean;
|
|
193
199
|
msgType: number;
|
|
194
200
|
xml?: string;
|
|
195
201
|
timestamp: number;
|
|
@@ -202,6 +208,7 @@ export type GeweWebhookServerOptions = {
|
|
|
202
208
|
path: string;
|
|
203
209
|
mediaPath?: string;
|
|
204
210
|
secret?: string;
|
|
211
|
+
onRawPayload?: (raw: string) => void;
|
|
205
212
|
onMessage: (message: GeweInboundMessage) => void | Promise<void>;
|
|
206
213
|
onError?: (error: Error) => void;
|
|
207
214
|
abortSignal?: AbortSignal;
|
package/src/xml.ts
CHANGED
|
@@ -120,6 +120,24 @@ export function extractXmlTag(xml: string, tag: string): string | undefined {
|
|
|
120
120
|
return decodeEntities(raw);
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
export function extractAtUserList(xml?: string): string[] {
|
|
124
|
+
const atUserList = xml?.trim() ? extractXmlTag(xml, "atuserlist") : undefined;
|
|
125
|
+
if (!atUserList) return [];
|
|
126
|
+
|
|
127
|
+
const seen = new Set<string>();
|
|
128
|
+
const values = atUserList
|
|
129
|
+
.split(/[,\uFF0C;\s]+/)
|
|
130
|
+
.map((value) => value.trim())
|
|
131
|
+
.filter(Boolean)
|
|
132
|
+
.filter((value) => {
|
|
133
|
+
if (seen.has(value)) return false;
|
|
134
|
+
seen.add(value);
|
|
135
|
+
return true;
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return values;
|
|
139
|
+
}
|
|
140
|
+
|
|
123
141
|
export function extractAppMsgType(xml: string): number | undefined {
|
|
124
142
|
const match = /<appmsg[\s\S]*?<type>(\d+)<\/type>/i.exec(xml);
|
|
125
143
|
if (!match?.[1]) return undefined;
|