@max1874/openclaw-wecom 0.1.0
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 +110 -0
- package/docs/CONFIG.md +362 -0
- package/docs/SETUP.md +162 -0
- package/index.ts +43 -0
- package/openclaw.plugin.json +17 -0
- package/package.json +73 -0
- package/src/accounts.ts +42 -0
- package/src/bot.ts +377 -0
- package/src/channel.ts +208 -0
- package/src/config-schema.ts +77 -0
- package/src/media.ts +149 -0
- package/src/monitor.ts +75 -0
- package/src/outbound.ts +75 -0
- package/src/policy.ts +111 -0
- package/src/probe.ts +32 -0
- package/src/reply-dispatcher.ts +91 -0
- package/src/runtime.ts +14 -0
- package/src/send.ts +250 -0
- package/src/targets.ts +66 -0
- package/src/types.ts +257 -0
- package/src/webhook.ts +171 -0
package/src/webhook.ts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { createServer, type Server, type IncomingMessage, type ServerResponse } from "http";
|
|
2
|
+
import type { ClawdbotConfig, RuntimeEnv, HistoryEntry } from "openclaw/plugin-sdk";
|
|
3
|
+
import type { WecomConfig, WecomWebhookEvent } from "./types.js";
|
|
4
|
+
import { handleWecomMessage } from "./bot.js";
|
|
5
|
+
|
|
6
|
+
export type WebhookServerOptions = {
|
|
7
|
+
cfg: ClawdbotConfig;
|
|
8
|
+
runtime?: RuntimeEnv;
|
|
9
|
+
abortSignal?: AbortSignal;
|
|
10
|
+
chatHistories: Map<string, HistoryEntry[]>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Parse request body as JSON.
|
|
15
|
+
*/
|
|
16
|
+
async function parseBody(req: IncomingMessage): Promise<unknown> {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const chunks: Buffer[] = [];
|
|
19
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
20
|
+
req.on("end", () => {
|
|
21
|
+
try {
|
|
22
|
+
const body = Buffer.concat(chunks).toString("utf-8");
|
|
23
|
+
resolve(body ? JSON.parse(body) : {});
|
|
24
|
+
} catch (err) {
|
|
25
|
+
reject(err);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
req.on("error", reject);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Send JSON response.
|
|
34
|
+
*/
|
|
35
|
+
function sendJson(res: ServerResponse, status: number, data: unknown): void {
|
|
36
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
37
|
+
res.end(JSON.stringify(data));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create and start the webhook HTTP server.
|
|
42
|
+
*/
|
|
43
|
+
export async function startWebhookServer(options: WebhookServerOptions): Promise<Server> {
|
|
44
|
+
const { cfg, runtime, abortSignal, chatHistories } = options;
|
|
45
|
+
const wecomCfg = cfg.channels?.wecom as WecomConfig | undefined;
|
|
46
|
+
const log = runtime?.log ?? console.log;
|
|
47
|
+
const error = runtime?.error ?? console.error;
|
|
48
|
+
|
|
49
|
+
const webhookPath = wecomCfg?.webhookPath ?? "/webhooks/wechat";
|
|
50
|
+
const port = wecomCfg?.webhookPort ?? 3000;
|
|
51
|
+
|
|
52
|
+
const server = createServer(async (req, res) => {
|
|
53
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
|
54
|
+
const path = url.pathname;
|
|
55
|
+
|
|
56
|
+
log(`wecom webhook: ${req.method} ${path}`);
|
|
57
|
+
|
|
58
|
+
// Health check endpoint
|
|
59
|
+
if (path === "/health" || path === `${webhookPath}/health`) {
|
|
60
|
+
sendJson(res, 200, { status: "ok", channel: "wecom" });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Only accept POST requests to webhook paths
|
|
65
|
+
if (req.method !== "POST") {
|
|
66
|
+
sendJson(res, 405, { error: "Method not allowed" });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Check if this is a webhook event path
|
|
71
|
+
if (!path.startsWith(webhookPath)) {
|
|
72
|
+
sendJson(res, 404, { error: "Not found" });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const body = await parseBody(req);
|
|
78
|
+
const eventPath = path.slice(webhookPath.length);
|
|
79
|
+
|
|
80
|
+
log(`wecom webhook: event path=${eventPath}, body=${JSON.stringify(body).slice(0, 200)}`);
|
|
81
|
+
|
|
82
|
+
// Route by event path
|
|
83
|
+
if (eventPath === "/events/message" || eventPath === "") {
|
|
84
|
+
// Main message event
|
|
85
|
+
const event = body as WecomWebhookEvent;
|
|
86
|
+
|
|
87
|
+
// Validate required fields
|
|
88
|
+
if (!event.chatId || !event.contactId) {
|
|
89
|
+
sendJson(res, 400, { error: "Missing required fields: chatId, contactId" });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Respond immediately to avoid timeout
|
|
94
|
+
sendJson(res, 200, { status: "received" });
|
|
95
|
+
|
|
96
|
+
// Process message asynchronously
|
|
97
|
+
handleWecomMessage({
|
|
98
|
+
cfg,
|
|
99
|
+
event,
|
|
100
|
+
runtime,
|
|
101
|
+
chatHistories,
|
|
102
|
+
}).catch((err) => {
|
|
103
|
+
error(`wecom webhook: failed to handle message: ${String(err)}`);
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (eventPath === "/events/room/joined") {
|
|
109
|
+
// Room join event - log and acknowledge
|
|
110
|
+
log(`wecom webhook: room join event: ${JSON.stringify(body)}`);
|
|
111
|
+
sendJson(res, 200, { status: "received" });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (eventPath === "/events/chatroom/loaded") {
|
|
116
|
+
// Chatroom loaded event - log and acknowledge
|
|
117
|
+
log(`wecom webhook: chatroom loaded event: ${JSON.stringify(body)}`);
|
|
118
|
+
sendJson(res, 200, { status: "received" });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (eventPath === "/events/chatroom/joined") {
|
|
123
|
+
// Chatroom joined event - log and acknowledge
|
|
124
|
+
log(`wecom webhook: chatroom joined event: ${JSON.stringify(body)}`);
|
|
125
|
+
sendJson(res, 200, { status: "received" });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Unknown event path - still acknowledge
|
|
130
|
+
log(`wecom webhook: unknown event path: ${eventPath}`);
|
|
131
|
+
sendJson(res, 200, { status: "received" });
|
|
132
|
+
} catch (err) {
|
|
133
|
+
error(`wecom webhook: error processing request: ${String(err)}`);
|
|
134
|
+
sendJson(res, 500, { error: "Internal server error" });
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Handle abort signal
|
|
139
|
+
if (abortSignal) {
|
|
140
|
+
abortSignal.addEventListener("abort", () => {
|
|
141
|
+
log(`wecom webhook: abort signal received, closing server`);
|
|
142
|
+
server.close();
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Start listening
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
148
|
+
server.on("error", (err) => {
|
|
149
|
+
error(`wecom webhook: server error: ${String(err)}`);
|
|
150
|
+
reject(err);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
server.listen(port, () => {
|
|
154
|
+
log(`wecom webhook: server listening on port ${port}`);
|
|
155
|
+
log(`wecom webhook: message endpoint: http://localhost:${port}${webhookPath}/events/message`);
|
|
156
|
+
resolve(server);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Stop the webhook server.
|
|
163
|
+
*/
|
|
164
|
+
export function stopWebhookServer(server: Server): Promise<void> {
|
|
165
|
+
return new Promise((resolve, reject) => {
|
|
166
|
+
server.close((err) => {
|
|
167
|
+
if (err) reject(err);
|
|
168
|
+
else resolve();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
}
|