gewe-openclaw 2026.1.29
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/LICENSE +21 -0
- package/README.md +56 -0
- package/assets/gewe-rs_logo.jpeg +0 -0
- package/index.ts +18 -0
- package/openclaw.plugin.json +11 -0
- package/package.json +39 -0
- package/src/accounts.ts +164 -0
- package/src/api.ts +53 -0
- package/src/channel.ts +465 -0
- package/src/config-schema.ts +105 -0
- package/src/delivery.ts +837 -0
- package/src/download-queue.ts +74 -0
- package/src/download.ts +84 -0
- package/src/inbound.ts +660 -0
- package/src/media-server.ts +154 -0
- package/src/monitor.ts +351 -0
- package/src/normalize.ts +19 -0
- package/src/policy.ts +185 -0
- package/src/runtime.ts +14 -0
- package/src/send.ts +171 -0
- package/src/types.ts +137 -0
- package/src/xml.ts +59 -0
package/src/runtime.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
|
2
|
+
|
|
3
|
+
let runtime: PluginRuntime | null = null;
|
|
4
|
+
|
|
5
|
+
export function setGeweRuntime(next: PluginRuntime) {
|
|
6
|
+
runtime = next;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function getGeweRuntime(): PluginRuntime {
|
|
10
|
+
if (!runtime) {
|
|
11
|
+
throw new Error("GeWe runtime not initialized");
|
|
12
|
+
}
|
|
13
|
+
return runtime;
|
|
14
|
+
}
|
package/src/send.ts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { assertGeweOk, postGeweJson } from "./api.js";
|
|
2
|
+
import type { GeweSendResult, ResolvedGeweAccount } from "./types.js";
|
|
3
|
+
|
|
4
|
+
type GeweSendContext = {
|
|
5
|
+
baseUrl: string;
|
|
6
|
+
token: string;
|
|
7
|
+
appId: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function buildContext(account: ResolvedGeweAccount): GeweSendContext {
|
|
11
|
+
const baseUrl = account.config.apiBaseUrl?.trim() || "http://api.geweapi.com";
|
|
12
|
+
return { baseUrl, token: account.token, appId: account.appId };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function resolveSendResult(params: {
|
|
16
|
+
toWxid: string;
|
|
17
|
+
data?: {
|
|
18
|
+
msgId?: number | string;
|
|
19
|
+
newMsgId?: number | string;
|
|
20
|
+
createTime?: number | null;
|
|
21
|
+
};
|
|
22
|
+
}): GeweSendResult {
|
|
23
|
+
const msgId = params.data?.newMsgId ?? params.data?.msgId ?? "ok";
|
|
24
|
+
const createTime = params.data?.createTime;
|
|
25
|
+
return {
|
|
26
|
+
toWxid: params.toWxid,
|
|
27
|
+
messageId: String(msgId),
|
|
28
|
+
newMessageId: params.data?.newMsgId ? String(params.data.newMsgId) : undefined,
|
|
29
|
+
timestamp: typeof createTime === "number" ? createTime * 1000 : undefined,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function sendTextGewe(params: {
|
|
34
|
+
account: ResolvedGeweAccount;
|
|
35
|
+
toWxid: string;
|
|
36
|
+
content: string;
|
|
37
|
+
ats?: string;
|
|
38
|
+
}): Promise<GeweSendResult> {
|
|
39
|
+
const ctx = buildContext(params.account);
|
|
40
|
+
const resp = await postGeweJson<{
|
|
41
|
+
msgId?: number | string;
|
|
42
|
+
newMsgId?: number | string;
|
|
43
|
+
createTime?: number;
|
|
44
|
+
}>({
|
|
45
|
+
baseUrl: ctx.baseUrl,
|
|
46
|
+
token: ctx.token,
|
|
47
|
+
path: "/gewe/v2/api/message/postText",
|
|
48
|
+
body: {
|
|
49
|
+
appId: ctx.appId,
|
|
50
|
+
toWxid: params.toWxid,
|
|
51
|
+
content: params.content,
|
|
52
|
+
...(params.ats ? { ats: params.ats } : {}),
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
const data = assertGeweOk(resp, "postText");
|
|
56
|
+
return resolveSendResult({ toWxid: params.toWxid, data });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function sendImageGewe(params: {
|
|
60
|
+
account: ResolvedGeweAccount;
|
|
61
|
+
toWxid: string;
|
|
62
|
+
imgUrl: string;
|
|
63
|
+
}): Promise<GeweSendResult> {
|
|
64
|
+
const ctx = buildContext(params.account);
|
|
65
|
+
const resp = await postGeweJson({
|
|
66
|
+
baseUrl: ctx.baseUrl,
|
|
67
|
+
token: ctx.token,
|
|
68
|
+
path: "/gewe/v2/api/message/postImage",
|
|
69
|
+
body: {
|
|
70
|
+
appId: ctx.appId,
|
|
71
|
+
toWxid: params.toWxid,
|
|
72
|
+
imgUrl: params.imgUrl,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
const data = assertGeweOk(resp, "postImage");
|
|
76
|
+
return resolveSendResult({ toWxid: params.toWxid, data });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function sendVoiceGewe(params: {
|
|
80
|
+
account: ResolvedGeweAccount;
|
|
81
|
+
toWxid: string;
|
|
82
|
+
voiceUrl: string;
|
|
83
|
+
voiceDuration: number;
|
|
84
|
+
}): Promise<GeweSendResult> {
|
|
85
|
+
const ctx = buildContext(params.account);
|
|
86
|
+
const resp = await postGeweJson({
|
|
87
|
+
baseUrl: ctx.baseUrl,
|
|
88
|
+
token: ctx.token,
|
|
89
|
+
path: "/gewe/v2/api/message/postVoice",
|
|
90
|
+
body: {
|
|
91
|
+
appId: ctx.appId,
|
|
92
|
+
toWxid: params.toWxid,
|
|
93
|
+
voiceUrl: params.voiceUrl,
|
|
94
|
+
voiceDuration: params.voiceDuration,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
const data = assertGeweOk(resp, "postVoice");
|
|
98
|
+
return resolveSendResult({ toWxid: params.toWxid, data });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function sendVideoGewe(params: {
|
|
102
|
+
account: ResolvedGeweAccount;
|
|
103
|
+
toWxid: string;
|
|
104
|
+
videoUrl: string;
|
|
105
|
+
thumbUrl: string;
|
|
106
|
+
videoDuration: number;
|
|
107
|
+
}): Promise<GeweSendResult> {
|
|
108
|
+
const ctx = buildContext(params.account);
|
|
109
|
+
const resp = await postGeweJson({
|
|
110
|
+
baseUrl: ctx.baseUrl,
|
|
111
|
+
token: ctx.token,
|
|
112
|
+
path: "/gewe/v2/api/message/postVideo",
|
|
113
|
+
body: {
|
|
114
|
+
appId: ctx.appId,
|
|
115
|
+
toWxid: params.toWxid,
|
|
116
|
+
videoUrl: params.videoUrl,
|
|
117
|
+
thumbUrl: params.thumbUrl,
|
|
118
|
+
videoDuration: params.videoDuration,
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
const data = assertGeweOk(resp, "postVideo");
|
|
122
|
+
return resolveSendResult({ toWxid: params.toWxid, data });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function sendFileGewe(params: {
|
|
126
|
+
account: ResolvedGeweAccount;
|
|
127
|
+
toWxid: string;
|
|
128
|
+
fileUrl: string;
|
|
129
|
+
fileName: string;
|
|
130
|
+
}): Promise<GeweSendResult> {
|
|
131
|
+
const ctx = buildContext(params.account);
|
|
132
|
+
const resp = await postGeweJson({
|
|
133
|
+
baseUrl: ctx.baseUrl,
|
|
134
|
+
token: ctx.token,
|
|
135
|
+
path: "/gewe/v2/api/message/postFile",
|
|
136
|
+
body: {
|
|
137
|
+
appId: ctx.appId,
|
|
138
|
+
toWxid: params.toWxid,
|
|
139
|
+
fileUrl: params.fileUrl,
|
|
140
|
+
fileName: params.fileName,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
const data = assertGeweOk(resp, "postFile");
|
|
144
|
+
return resolveSendResult({ toWxid: params.toWxid, data });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function sendLinkGewe(params: {
|
|
148
|
+
account: ResolvedGeweAccount;
|
|
149
|
+
toWxid: string;
|
|
150
|
+
title: string;
|
|
151
|
+
desc: string;
|
|
152
|
+
linkUrl: string;
|
|
153
|
+
thumbUrl: string;
|
|
154
|
+
}): Promise<GeweSendResult> {
|
|
155
|
+
const ctx = buildContext(params.account);
|
|
156
|
+
const resp = await postGeweJson({
|
|
157
|
+
baseUrl: ctx.baseUrl,
|
|
158
|
+
token: ctx.token,
|
|
159
|
+
path: "/gewe/v2/api/message/postLink",
|
|
160
|
+
body: {
|
|
161
|
+
appId: ctx.appId,
|
|
162
|
+
toWxid: params.toWxid,
|
|
163
|
+
title: params.title,
|
|
164
|
+
desc: params.desc,
|
|
165
|
+
linkUrl: params.linkUrl,
|
|
166
|
+
thumbUrl: params.thumbUrl,
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
const data = assertGeweOk(resp, "postLink");
|
|
170
|
+
return resolveSendResult({ toWxid: params.toWxid, data });
|
|
171
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BlockStreamingCoalesceConfig,
|
|
3
|
+
DmConfig,
|
|
4
|
+
DmPolicy,
|
|
5
|
+
GroupPolicy,
|
|
6
|
+
GroupToolPolicyConfig,
|
|
7
|
+
} from "openclaw/plugin-sdk";
|
|
8
|
+
|
|
9
|
+
export type GeweGroupConfig = {
|
|
10
|
+
requireMention?: boolean;
|
|
11
|
+
tools?: GroupToolPolicyConfig;
|
|
12
|
+
skills?: string[];
|
|
13
|
+
enabled?: boolean;
|
|
14
|
+
allowFrom?: string[];
|
|
15
|
+
systemPrompt?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type GeweAccountConfig = {
|
|
19
|
+
name?: string;
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
apiBaseUrl?: string;
|
|
22
|
+
token?: string;
|
|
23
|
+
tokenFile?: string;
|
|
24
|
+
appId?: string;
|
|
25
|
+
appIdFile?: string;
|
|
26
|
+
webhookPort?: number;
|
|
27
|
+
webhookHost?: string;
|
|
28
|
+
webhookPath?: string;
|
|
29
|
+
webhookSecret?: string;
|
|
30
|
+
webhookPublicUrl?: string;
|
|
31
|
+
mediaHost?: string;
|
|
32
|
+
mediaPort?: number;
|
|
33
|
+
mediaPath?: string;
|
|
34
|
+
mediaPublicUrl?: string;
|
|
35
|
+
mediaMaxMb?: number;
|
|
36
|
+
voiceAutoConvert?: boolean;
|
|
37
|
+
voiceFfmpegPath?: string;
|
|
38
|
+
voiceSilkPath?: string;
|
|
39
|
+
voiceSilkArgs?: string[];
|
|
40
|
+
voiceSampleRate?: number;
|
|
41
|
+
voiceDecodePath?: string;
|
|
42
|
+
voiceDecodeArgs?: string[];
|
|
43
|
+
voiceDecodeSampleRate?: number;
|
|
44
|
+
voiceDecodeOutput?: "pcm" | "wav";
|
|
45
|
+
videoFfmpegPath?: string;
|
|
46
|
+
videoFfprobePath?: string;
|
|
47
|
+
videoThumbUrl?: string;
|
|
48
|
+
downloadMinDelayMs?: number;
|
|
49
|
+
downloadMaxDelayMs?: number;
|
|
50
|
+
dmPolicy?: DmPolicy;
|
|
51
|
+
allowFrom?: Array<string | number>;
|
|
52
|
+
groupAllowFrom?: Array<string | number>;
|
|
53
|
+
groupPolicy?: GroupPolicy;
|
|
54
|
+
groups?: Record<string, GeweGroupConfig>;
|
|
55
|
+
historyLimit?: number;
|
|
56
|
+
dmHistoryLimit?: number;
|
|
57
|
+
dms?: Record<string, DmConfig>;
|
|
58
|
+
textChunkLimit?: number;
|
|
59
|
+
chunkMode?: "length" | "newline";
|
|
60
|
+
blockStreaming?: boolean;
|
|
61
|
+
blockStreamingCoalesce?: BlockStreamingCoalesceConfig;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type GeweConfig = {
|
|
65
|
+
accounts?: Record<string, GeweAccountConfig>;
|
|
66
|
+
} & GeweAccountConfig;
|
|
67
|
+
|
|
68
|
+
export type CoreConfig = {
|
|
69
|
+
channels?: {
|
|
70
|
+
gewe?: GeweConfig;
|
|
71
|
+
};
|
|
72
|
+
[key: string]: unknown;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export type GeweTokenSource = "env" | "config" | "configFile" | "none";
|
|
76
|
+
|
|
77
|
+
export type GeweAppIdSource = "env" | "config" | "configFile" | "none";
|
|
78
|
+
|
|
79
|
+
export type ResolvedGeweAccount = {
|
|
80
|
+
accountId: string;
|
|
81
|
+
name?: string;
|
|
82
|
+
enabled: boolean;
|
|
83
|
+
token: string;
|
|
84
|
+
tokenSource: GeweTokenSource;
|
|
85
|
+
appId: string;
|
|
86
|
+
appIdSource: GeweAppIdSource;
|
|
87
|
+
config: GeweAccountConfig;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export type GeweCallbackPayload = {
|
|
91
|
+
TypeName?: string;
|
|
92
|
+
Appid?: string;
|
|
93
|
+
Wxid?: string;
|
|
94
|
+
Data?: {
|
|
95
|
+
MsgId?: number;
|
|
96
|
+
NewMsgId?: number;
|
|
97
|
+
FromUserName?: { string?: string };
|
|
98
|
+
ToUserName?: { string?: string };
|
|
99
|
+
MsgType?: number;
|
|
100
|
+
Content?: { string?: string };
|
|
101
|
+
CreateTime?: number;
|
|
102
|
+
PushContent?: string;
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export type GeweInboundMessage = {
|
|
107
|
+
messageId: string;
|
|
108
|
+
newMessageId: string;
|
|
109
|
+
appId: string;
|
|
110
|
+
botWxid: string;
|
|
111
|
+
fromId: string;
|
|
112
|
+
toId: string;
|
|
113
|
+
senderId: string;
|
|
114
|
+
senderName?: string;
|
|
115
|
+
text: string;
|
|
116
|
+
msgType: number;
|
|
117
|
+
xml?: string;
|
|
118
|
+
timestamp: number;
|
|
119
|
+
isGroupChat: boolean;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export type GeweWebhookServerOptions = {
|
|
123
|
+
port: number;
|
|
124
|
+
host: string;
|
|
125
|
+
path: string;
|
|
126
|
+
secret?: string;
|
|
127
|
+
onMessage: (message: GeweInboundMessage) => void | Promise<void>;
|
|
128
|
+
onError?: (error: Error) => void;
|
|
129
|
+
abortSignal?: AbortSignal;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export type GeweSendResult = {
|
|
133
|
+
messageId: string;
|
|
134
|
+
newMessageId?: string;
|
|
135
|
+
toWxid: string;
|
|
136
|
+
timestamp?: number;
|
|
137
|
+
};
|
package/src/xml.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
function decodeEntities(value: string): string {
|
|
2
|
+
return value
|
|
3
|
+
.replace(/&/g, "&")
|
|
4
|
+
.replace(/</g, "<")
|
|
5
|
+
.replace(/>/g, ">")
|
|
6
|
+
.replace(/"/g, '"')
|
|
7
|
+
.replace(/'/g, "'")
|
|
8
|
+
.replace(/&#x([0-9a-fA-F]+);/g, (_match, hex) => {
|
|
9
|
+
const code = Number.parseInt(hex, 16);
|
|
10
|
+
return Number.isFinite(code) ? String.fromCodePoint(code) : _match;
|
|
11
|
+
})
|
|
12
|
+
.replace(/&#(\d+);/g, (_match, dec) => {
|
|
13
|
+
const code = Number.parseInt(dec, 10);
|
|
14
|
+
return Number.isFinite(code) ? String.fromCodePoint(code) : _match;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function stripCdata(value: string): string {
|
|
19
|
+
const trimmed = value.trim();
|
|
20
|
+
if (trimmed.startsWith("<![CDATA[") && trimmed.endsWith("]]>")) {
|
|
21
|
+
return trimmed.slice(9, -3);
|
|
22
|
+
}
|
|
23
|
+
return trimmed;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function extractXmlTag(xml: string, tag: string): string | undefined {
|
|
27
|
+
const re = new RegExp(`<${tag}>([\\s\\S]*?)<\\/${tag}>`, "i");
|
|
28
|
+
const match = re.exec(xml);
|
|
29
|
+
if (!match) return undefined;
|
|
30
|
+
const raw = stripCdata(match[1]);
|
|
31
|
+
return decodeEntities(raw);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function extractAppMsgType(xml: string): number | undefined {
|
|
35
|
+
const match = /<appmsg[\s\S]*?<type>(\d+)<\/type>/i.exec(xml);
|
|
36
|
+
if (!match?.[1]) return undefined;
|
|
37
|
+
const value = Number.parseInt(match[1], 10);
|
|
38
|
+
return Number.isFinite(value) ? value : undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function extractLinkDetails(xml: string): {
|
|
42
|
+
title?: string;
|
|
43
|
+
desc?: string;
|
|
44
|
+
linkUrl?: string;
|
|
45
|
+
thumbUrl?: string;
|
|
46
|
+
} {
|
|
47
|
+
return {
|
|
48
|
+
title: extractXmlTag(xml, "title"),
|
|
49
|
+
desc: extractXmlTag(xml, "des"),
|
|
50
|
+
linkUrl: extractXmlTag(xml, "url"),
|
|
51
|
+
thumbUrl: extractXmlTag(xml, "thumburl"),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function extractFileName(xml: string): string | undefined {
|
|
56
|
+
const title = extractXmlTag(xml, "title");
|
|
57
|
+
if (title) return title.trim();
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|