moltbot-dingtalk-stream 1.0.1
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 +61 -0
- package/dist/clawdbot.plugin.json +19 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +293 -0
- package/dist/index.js.map +1 -0
- package/dist/package.json +11 -0
- package/local-test-config.json +21 -0
- package/moltbot.plugin.json +56 -0
- package/package.json +37 -0
- package/src/index.ts +373 -0
- package/test-config.json +20 -0
- package/test-local.js +90 -0
- package/test-plugin.js +51 -0
- package/tsconfig.json +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# DingTalk Channel for Moltbot
|
|
2
|
+
|
|
3
|
+
A Moltbot channel plugin for DingTalk (钉钉) using **Stream Mode** for seamless integration.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Stream Mode**: Uses WebSocket for receiving messages (no public IP required)
|
|
8
|
+
- **Zero Configuration**: No webhook setup, ngrok, or firewall configuration needed
|
|
9
|
+
- **Single/Group Chat**: Supports both direct messages and group mentions
|
|
10
|
+
- **Easy Setup**: Just configure your DingTalk app credentials
|
|
11
|
+
|
|
12
|
+
## Proactive Messaging (CLI)
|
|
13
|
+
|
|
14
|
+
You can send messages to DingTalk conversations using the Clawdbot CLI. You need the `conversationId` (which you can find in the logs when a message is received).
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
clawdbot send --channel dingtalk --to <conversationId> "Hello from CLI"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Troubleshooting
|
|
21
|
+
|
|
22
|
+
- **Logs**: Check `~/.clawdbot/logs/gateway.log` for debug information.
|
|
23
|
+
- **Connection**: Ensure your server has outbound internet access to DingTalk servers.
|
|
24
|
+
- **Permissions**: Verify your DingTalk app has the necessary robot permissions.
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
1. Install the plugin:
|
|
29
|
+
```bash
|
|
30
|
+
npm install moltbot-dingtalk-stream
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
2. Configure in your moltbot config:
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"channels": {
|
|
37
|
+
"dingtalk": {
|
|
38
|
+
"accounts": {
|
|
39
|
+
"default": {
|
|
40
|
+
"enabled": true,
|
|
41
|
+
"clientId": "YOUR_APP_KEY",
|
|
42
|
+
"clientSecret": "YOUR_APP_SECRET"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
3. Set up your DingTalk app:
|
|
51
|
+
- Create an enterprise internal app at [DingTalk Developer Console](https://open.dingtalk.com/)
|
|
52
|
+
- Add Robot capability with **Stream Mode** enabled
|
|
53
|
+
- Use the AppKey as `clientId` and AppSecret as `clientSecret`
|
|
54
|
+
|
|
55
|
+
## Documentation
|
|
56
|
+
|
|
57
|
+
See [DEVELOPMENT.md](./DEVELOPMENT.md) for detailed setup and configuration.
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
MIT
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "dingtalk-channel",
|
|
3
|
+
"name": "DingTalk Channel",
|
|
4
|
+
"version": "2.0.0",
|
|
5
|
+
"description": "Custom channel for integrating Clawdbot with DingTalk (Stream Mode)",
|
|
6
|
+
"author": "Your Name",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"extensions": [
|
|
9
|
+
"./index.js"
|
|
10
|
+
],
|
|
11
|
+
"channels": [
|
|
12
|
+
"dingtalk"
|
|
13
|
+
],
|
|
14
|
+
"configSchema": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"additionalProperties": false,
|
|
17
|
+
"properties": {}
|
|
18
|
+
}
|
|
19
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
interface ClawdbotPluginApi {
|
|
2
|
+
config: ClawdbotConfig;
|
|
3
|
+
logger: any;
|
|
4
|
+
runtime: any;
|
|
5
|
+
postMessage(params: any): Promise<void>;
|
|
6
|
+
registerChannel(opts: {
|
|
7
|
+
plugin: any;
|
|
8
|
+
}): void;
|
|
9
|
+
registerService(service: any): void;
|
|
10
|
+
}
|
|
11
|
+
interface ClawdbotConfig {
|
|
12
|
+
channels?: {
|
|
13
|
+
dingtalk?: {
|
|
14
|
+
accounts?: {
|
|
15
|
+
[key: string]: DingTalkAccountConfig;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
[key: string]: any;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
interface DingTalkAccountConfig {
|
|
22
|
+
enabled?: boolean;
|
|
23
|
+
clientId: string;
|
|
24
|
+
clientSecret: string;
|
|
25
|
+
webhookUrl?: string;
|
|
26
|
+
name?: string;
|
|
27
|
+
}
|
|
28
|
+
declare const plugin: {
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
description: string;
|
|
32
|
+
configSchema: {
|
|
33
|
+
type: "object";
|
|
34
|
+
properties: {};
|
|
35
|
+
};
|
|
36
|
+
register(api: ClawdbotPluginApi): void;
|
|
37
|
+
};
|
|
38
|
+
export default plugin;
|
|
39
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,UAAU,iBAAiB;IACzB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,GAAG,CAAC;IACZ,OAAO,EAAE,GAAG,CAAC;IACb,WAAW,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,eAAe,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,GAAG,CAAA;KAAE,GAAG,IAAI,CAAC;IAC7C,eAAe,CAAC,OAAO,EAAE,GAAG,GAAG,IAAI,CAAC;CACrC;AAED,UAAU,cAAc;IACtB,QAAQ,CAAC,EAAE;QACT,QAAQ,CAAC,EAAE;YACT,QAAQ,CAAC,EAAE;gBACT,CAAC,GAAG,EAAE,MAAM,GAAG,qBAAqB,CAAC;aACtC,CAAC;SACH,CAAC;QACF,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC;CACH;AAED,UAAU,qBAAqB;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAwUD,QAAA,MAAM,MAAM;;;;;;;;kBAQI,iBAAiB;CAIhC,CAAC;AAEF,eAAe,MAAM,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const dingtalk_stream_1 = require("dingtalk-stream");
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
// Store plugin runtime
|
|
9
|
+
let pluginRuntime = null;
|
|
10
|
+
// Store session webhooks for reply
|
|
11
|
+
const sessionWebhooks = new Map();
|
|
12
|
+
// Store active clients for each account
|
|
13
|
+
const activeClients = new Map();
|
|
14
|
+
// Helper functions
|
|
15
|
+
function listDingTalkAccountIds(cfg) {
|
|
16
|
+
const accounts = cfg.channels?.dingtalk?.accounts;
|
|
17
|
+
return accounts ? Object.keys(accounts) : [];
|
|
18
|
+
}
|
|
19
|
+
function resolveDingTalkAccount(opts) {
|
|
20
|
+
const { cfg, accountId = 'default' } = opts;
|
|
21
|
+
const account = cfg.channels?.dingtalk?.accounts?.[accountId];
|
|
22
|
+
return {
|
|
23
|
+
accountId,
|
|
24
|
+
name: account?.name,
|
|
25
|
+
enabled: account?.enabled ?? false,
|
|
26
|
+
configured: Boolean(account?.clientId && account?.clientSecret),
|
|
27
|
+
config: account || { clientId: '', clientSecret: '' }
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// DingTalk Channel Plugin
|
|
31
|
+
const dingTalkChannelPlugin = {
|
|
32
|
+
id: "dingtalk",
|
|
33
|
+
meta: {
|
|
34
|
+
id: "dingtalk",
|
|
35
|
+
label: "钉钉",
|
|
36
|
+
selectionLabel: "DingTalk Bot (Stream)",
|
|
37
|
+
docsPath: "/channels/dingtalk",
|
|
38
|
+
docsLabel: "dingtalk",
|
|
39
|
+
blurb: "钉钉机器人通道插件 (Stream模式)",
|
|
40
|
+
order: 100,
|
|
41
|
+
aliases: ["dt", "ding"],
|
|
42
|
+
},
|
|
43
|
+
capabilities: {
|
|
44
|
+
chatTypes: ["direct", "group"],
|
|
45
|
+
},
|
|
46
|
+
reload: { configPrefixes: ["channels.dingtalk"] },
|
|
47
|
+
configSchema: {
|
|
48
|
+
type: "object",
|
|
49
|
+
properties: {
|
|
50
|
+
channels: {
|
|
51
|
+
type: "object",
|
|
52
|
+
properties: {
|
|
53
|
+
dingtalk: {
|
|
54
|
+
type: "object",
|
|
55
|
+
properties: {
|
|
56
|
+
accounts: {
|
|
57
|
+
type: "object",
|
|
58
|
+
additionalProperties: {
|
|
59
|
+
type: "object",
|
|
60
|
+
properties: {
|
|
61
|
+
enabled: { type: "boolean" },
|
|
62
|
+
clientId: { type: "string" },
|
|
63
|
+
clientSecret: { type: "string" },
|
|
64
|
+
webhookUrl: { type: "string" },
|
|
65
|
+
name: { type: "string" },
|
|
66
|
+
},
|
|
67
|
+
required: ["clientId", "clientSecret"],
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
config: {
|
|
77
|
+
listAccountIds: (cfg) => listDingTalkAccountIds(cfg),
|
|
78
|
+
resolveAccount: (cfg, accountId) => resolveDingTalkAccount({ cfg, accountId }),
|
|
79
|
+
defaultAccountId: (_cfg) => 'default',
|
|
80
|
+
isConfigured: (account) => account.configured,
|
|
81
|
+
describeAccount: (account) => ({
|
|
82
|
+
accountId: account.accountId,
|
|
83
|
+
name: account.name,
|
|
84
|
+
enabled: account.enabled,
|
|
85
|
+
configured: account.configured,
|
|
86
|
+
}),
|
|
87
|
+
},
|
|
88
|
+
gateway: {
|
|
89
|
+
startAccount: async (ctx) => {
|
|
90
|
+
const account = ctx.account;
|
|
91
|
+
const config = account.config;
|
|
92
|
+
const accountId = account.accountId;
|
|
93
|
+
if (!config.clientId || !config.clientSecret) {
|
|
94
|
+
ctx.log?.warn?.(`[${accountId}] missing clientId or clientSecret`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
ctx.log?.info?.(`[${accountId}] starting DingTalk Stream client`);
|
|
98
|
+
try {
|
|
99
|
+
const client = new dingtalk_stream_1.DWClient({
|
|
100
|
+
clientId: config.clientId,
|
|
101
|
+
clientSecret: config.clientSecret,
|
|
102
|
+
});
|
|
103
|
+
// Helper to safely handle messages
|
|
104
|
+
const handleMessage = async (res) => {
|
|
105
|
+
try {
|
|
106
|
+
const message = JSON.parse(res.data);
|
|
107
|
+
const textContent = message.text?.content || "";
|
|
108
|
+
const senderId = message.senderId;
|
|
109
|
+
const convoId = message.conversationId;
|
|
110
|
+
const msgId = message.msgId;
|
|
111
|
+
// Store session webhook if provided (DingTalk Stream mode provides this for replies)
|
|
112
|
+
if (message.sessionWebhook) {
|
|
113
|
+
sessionWebhooks.set(convoId, message.sessionWebhook);
|
|
114
|
+
}
|
|
115
|
+
// Log reception
|
|
116
|
+
ctx.log?.info?.(`[${accountId}] received message from ${message.senderNick || senderId}: ${textContent}`);
|
|
117
|
+
// Filter out empty messages
|
|
118
|
+
if (!textContent)
|
|
119
|
+
return;
|
|
120
|
+
// Simple text cleaning (remove @bot mentions if possible, though DingTalk usually gives clean content or we might need to parse entities)
|
|
121
|
+
const cleanedText = textContent.replace(/@\w+\s*/g, '').trim();
|
|
122
|
+
// Forward the message to Clawdbot for processing
|
|
123
|
+
if (pluginRuntime?.runtime?.channel?.reply) {
|
|
124
|
+
const replyModule = pluginRuntime.runtime.channel.reply;
|
|
125
|
+
const chatType = String(message.conversationType) === '2' ? 'group' : 'direct';
|
|
126
|
+
const fromAddress = chatType === 'group' ? `dingtalk:group:${convoId}` : `dingtalk:${senderId}`;
|
|
127
|
+
const ctxPayload = {
|
|
128
|
+
Body: cleanedText,
|
|
129
|
+
RawBody: textContent,
|
|
130
|
+
CommandBody: cleanedText,
|
|
131
|
+
From: fromAddress,
|
|
132
|
+
To: 'bot',
|
|
133
|
+
SessionKey: `dingtalk:${convoId}`,
|
|
134
|
+
AccountId: accountId,
|
|
135
|
+
ChatType: chatType,
|
|
136
|
+
SenderName: message.senderNick,
|
|
137
|
+
SenderId: senderId,
|
|
138
|
+
Provider: 'dingtalk',
|
|
139
|
+
Surface: 'dingtalk',
|
|
140
|
+
MessageSid: message.msgId,
|
|
141
|
+
Timestamp: message.createAt,
|
|
142
|
+
// Required for some logic
|
|
143
|
+
GroupSubject: chatType === 'group' ? (message.conversationId) : undefined,
|
|
144
|
+
};
|
|
145
|
+
const finalizedCtx = replyModule.finalizeInboundContext(ctxPayload);
|
|
146
|
+
let replyBuffer = "";
|
|
147
|
+
let replySent = false;
|
|
148
|
+
const sendToDingTalk = async (text) => {
|
|
149
|
+
if (!text)
|
|
150
|
+
return;
|
|
151
|
+
if (replySent) {
|
|
152
|
+
ctx.log?.info?.(`[${accountId}] Reply already sent, skipping buffer flush.`);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const replyWebhook = sessionWebhooks.get(convoId) || config.webhookUrl;
|
|
156
|
+
if (!replyWebhook) {
|
|
157
|
+
ctx.log?.error?.(`[${accountId}] No webhook to reply to ${convoId}`);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
await axios_1.default.post(replyWebhook, {
|
|
162
|
+
msgtype: "text",
|
|
163
|
+
text: { content: text }
|
|
164
|
+
}, { headers: { 'Content-Type': 'application/json' } });
|
|
165
|
+
replySent = true;
|
|
166
|
+
ctx.log?.info?.(`[${accountId}] Reply sent successfully.`);
|
|
167
|
+
}
|
|
168
|
+
catch (e) {
|
|
169
|
+
ctx.log?.error?.(`[${accountId}] Failed to send reply: ${e}`);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
const dispatcher = {
|
|
173
|
+
sendFinalReply: (payload) => {
|
|
174
|
+
const text = payload.text || payload.content || '';
|
|
175
|
+
sendToDingTalk(text).catch(e => ctx.log?.error?.(`[${accountId}] sendToDingTalk failed: ${e}`));
|
|
176
|
+
return true;
|
|
177
|
+
},
|
|
178
|
+
typing: async () => { },
|
|
179
|
+
reaction: async () => { },
|
|
180
|
+
isSynchronous: () => false,
|
|
181
|
+
waitForIdle: async () => { },
|
|
182
|
+
sendBlockReply: async (block) => {
|
|
183
|
+
// Accumulate text from blocks
|
|
184
|
+
const text = block.text || block.delta || block.content || '';
|
|
185
|
+
if (text) {
|
|
186
|
+
replyBuffer += text;
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
getQueuedCounts: () => ({ active: 0, queued: 0, final: 0 })
|
|
190
|
+
};
|
|
191
|
+
// Internal dispatch
|
|
192
|
+
const dispatchPromise = replyModule.dispatchReplyFromConfig({
|
|
193
|
+
ctx: finalizedCtx,
|
|
194
|
+
cfg: pluginRuntime.config,
|
|
195
|
+
dispatcher: dispatcher,
|
|
196
|
+
replyOptions: {}
|
|
197
|
+
});
|
|
198
|
+
// ACK immediately to prevent retries
|
|
199
|
+
if (res.headers && res.headers.messageId) {
|
|
200
|
+
client.socketCallBackResponse(res.headers.messageId, { status: "SUCCEED" });
|
|
201
|
+
}
|
|
202
|
+
// Wait for run to finish
|
|
203
|
+
await dispatchPromise;
|
|
204
|
+
// If final reply wasn't called but we have buffer (streaming case where agent didn't return final payload?)
|
|
205
|
+
if (!replySent && replyBuffer) {
|
|
206
|
+
ctx.log?.info?.(`[${accountId}] Sending accumulated buffer from blocks (len=${replyBuffer.length}).`);
|
|
207
|
+
await sendToDingTalk(replyBuffer);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
ctx.log?.error?.(`[${accountId}] runtime.channel.reply not available`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
ctx.log?.error?.(`[${accountId}] error processing message: ${error instanceof Error ? error.message : String(error)}`);
|
|
216
|
+
console.error('DingTalk Handler Error:', error);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
// Register callback for robot messages
|
|
220
|
+
client.registerCallbackListener('/v1.0/im/bot/messages/get', handleMessage);
|
|
221
|
+
// Connect to DingTalk Stream
|
|
222
|
+
await client.connect();
|
|
223
|
+
activeClients.set(accountId, client);
|
|
224
|
+
ctx.log?.info?.(`[${accountId}] DingTalk Stream client connected`);
|
|
225
|
+
// Handle abort signal for cleanup
|
|
226
|
+
ctx.abortSignal?.addEventListener('abort', () => {
|
|
227
|
+
ctx.log?.info?.(`[${accountId}] stopping DingTalk Stream client`);
|
|
228
|
+
client.disconnect();
|
|
229
|
+
activeClients.delete(accountId);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
ctx.log?.error?.(`[${accountId}] failed to start: ${error instanceof Error ? error.message : String(error)}`);
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
outbound: {
|
|
239
|
+
deliveryMode: "direct",
|
|
240
|
+
sendText: async (opts) => {
|
|
241
|
+
const { text, account, target } = opts;
|
|
242
|
+
const config = account.config;
|
|
243
|
+
// Try session webhook first (for replies)
|
|
244
|
+
const sessionWebhook = sessionWebhooks.get(target);
|
|
245
|
+
if (sessionWebhook) {
|
|
246
|
+
try {
|
|
247
|
+
await axios_1.default.post(sessionWebhook, {
|
|
248
|
+
msgtype: "text",
|
|
249
|
+
text: { content: text }
|
|
250
|
+
}, {
|
|
251
|
+
headers: { 'Content-Type': 'application/json' }
|
|
252
|
+
});
|
|
253
|
+
return { ok: true };
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
// Fall through to webhookUrl
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// Fallback to webhookUrl for proactive messages
|
|
260
|
+
if (config?.webhookUrl) {
|
|
261
|
+
try {
|
|
262
|
+
await axios_1.default.post(config.webhookUrl, {
|
|
263
|
+
msgtype: "text",
|
|
264
|
+
text: { content: text }
|
|
265
|
+
}, {
|
|
266
|
+
headers: { 'Content-Type': 'application/json' }
|
|
267
|
+
});
|
|
268
|
+
return { ok: true };
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return { ok: false, error: "No webhook available for sending messages" };
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
// Plugin object format required by Clawdbot
|
|
279
|
+
const plugin = {
|
|
280
|
+
id: "dingtalk-channel",
|
|
281
|
+
name: "DingTalk Channel",
|
|
282
|
+
description: "DingTalk channel plugin using Stream mode",
|
|
283
|
+
configSchema: {
|
|
284
|
+
type: "object",
|
|
285
|
+
properties: {}
|
|
286
|
+
},
|
|
287
|
+
register(api) {
|
|
288
|
+
pluginRuntime = api;
|
|
289
|
+
api.registerChannel({ plugin: dingTalkChannelPlugin });
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
exports.default = plugin;
|
|
293
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"channels": {
|
|
3
|
+
"dingtalk": {
|
|
4
|
+
"accounts": {
|
|
5
|
+
"local_test": {
|
|
6
|
+
"enabled": true,
|
|
7
|
+
"clientId": "YOUR_APP_KEY_HERE",
|
|
8
|
+
"clientSecret": "YOUR_APP_SECRET_HERE",
|
|
9
|
+
"webhookUrl": ""
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"plugins": {
|
|
15
|
+
"entries": {
|
|
16
|
+
"dingtalk-channel": {
|
|
17
|
+
"path": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "dingtalk-channel",
|
|
3
|
+
"name": "DingTalk Channel",
|
|
4
|
+
"version": "2.0.0",
|
|
5
|
+
"description": "Custom channel for integrating Moltbot with DingTalk (Stream Mode)",
|
|
6
|
+
"author": "Your Name",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"extensions": {
|
|
9
|
+
"channels": [
|
|
10
|
+
"./dist/index.js"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"configSchema": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"properties": {
|
|
16
|
+
"channels": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"properties": {
|
|
19
|
+
"dingtalk": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"properties": {
|
|
22
|
+
"accounts": {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"additionalProperties": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"properties": {
|
|
27
|
+
"enabled": {
|
|
28
|
+
"type": "boolean",
|
|
29
|
+
"description": "Whether this account is enabled"
|
|
30
|
+
},
|
|
31
|
+
"clientId": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"description": "AppKey from DingTalk developer console"
|
|
34
|
+
},
|
|
35
|
+
"clientSecret": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"description": "AppSecret from DingTalk developer console"
|
|
38
|
+
},
|
|
39
|
+
"webhookUrl": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"description": "Optional webhook URL for proactive messages"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"required": [
|
|
45
|
+
"clientId",
|
|
46
|
+
"clientSecret"
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "moltbot-dingtalk-stream",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "DingTalk custom channel plugin for Moltbot/Clawdbot",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"dev": "tsc --watch",
|
|
10
|
+
"prepublishOnly": "npm run build"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"moltbot",
|
|
14
|
+
"clawdbot",
|
|
15
|
+
"plugin",
|
|
16
|
+
"dingtalk",
|
|
17
|
+
"channel",
|
|
18
|
+
"chatbot"
|
|
19
|
+
],
|
|
20
|
+
"author": "Your Name",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"axios": "^1.6.0",
|
|
24
|
+
"dingtalk-stream": "^2.1.4"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^20.0.0",
|
|
28
|
+
"typescript": "^5.0.0"
|
|
29
|
+
},
|
|
30
|
+
"clawdbot": {
|
|
31
|
+
"extensions": {
|
|
32
|
+
"channels": [
|
|
33
|
+
"./dist/index.js"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { DWClient, DWClientDownStream } from 'dingtalk-stream';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
|
|
4
|
+
// Define interfaces
|
|
5
|
+
interface ClawdbotPluginApi {
|
|
6
|
+
config: ClawdbotConfig;
|
|
7
|
+
logger: any;
|
|
8
|
+
runtime: any;
|
|
9
|
+
postMessage(params: any): Promise<void>;
|
|
10
|
+
registerChannel(opts: { plugin: any }): void;
|
|
11
|
+
registerService(service: any): void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface ClawdbotConfig {
|
|
15
|
+
channels?: {
|
|
16
|
+
dingtalk?: {
|
|
17
|
+
accounts?: {
|
|
18
|
+
[key: string]: DingTalkAccountConfig;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
[key: string]: any;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface DingTalkAccountConfig {
|
|
26
|
+
enabled?: boolean;
|
|
27
|
+
clientId: string;
|
|
28
|
+
clientSecret: string;
|
|
29
|
+
webhookUrl?: string;
|
|
30
|
+
name?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface ResolvedDingTalkAccount {
|
|
34
|
+
accountId: string;
|
|
35
|
+
name?: string;
|
|
36
|
+
enabled: boolean;
|
|
37
|
+
configured: boolean;
|
|
38
|
+
config: DingTalkAccountConfig;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface DingTalkRobotMessage {
|
|
42
|
+
conversationId: string;
|
|
43
|
+
chatbotCorpId: string;
|
|
44
|
+
chatbotUserId: string;
|
|
45
|
+
msgId: string;
|
|
46
|
+
senderNick: string;
|
|
47
|
+
isAdmin: boolean;
|
|
48
|
+
senderStaffId?: string;
|
|
49
|
+
sessionWebhook: string;
|
|
50
|
+
sessionWebhookExpiredTime: number;
|
|
51
|
+
createAt: number;
|
|
52
|
+
senderCorpId?: string;
|
|
53
|
+
conversationType: '1' | '2';
|
|
54
|
+
senderId: string;
|
|
55
|
+
text?: {
|
|
56
|
+
content: string;
|
|
57
|
+
};
|
|
58
|
+
msgtype: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Store plugin runtime
|
|
62
|
+
let pluginRuntime: ClawdbotPluginApi | null = null;
|
|
63
|
+
|
|
64
|
+
// Store session webhooks for reply
|
|
65
|
+
const sessionWebhooks: Map<string, string> = new Map();
|
|
66
|
+
// Store active clients for each account
|
|
67
|
+
const activeClients: Map<string, DWClient> = new Map();
|
|
68
|
+
|
|
69
|
+
// Helper functions
|
|
70
|
+
function listDingTalkAccountIds(cfg: ClawdbotConfig): string[] {
|
|
71
|
+
const accounts = cfg.channels?.dingtalk?.accounts;
|
|
72
|
+
return accounts ? Object.keys(accounts) : [];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function resolveDingTalkAccount(opts: { cfg: ClawdbotConfig; accountId?: string }): ResolvedDingTalkAccount {
|
|
76
|
+
const { cfg, accountId = 'default' } = opts;
|
|
77
|
+
const account = cfg.channels?.dingtalk?.accounts?.[accountId];
|
|
78
|
+
return {
|
|
79
|
+
accountId,
|
|
80
|
+
name: account?.name,
|
|
81
|
+
enabled: account?.enabled ?? false,
|
|
82
|
+
configured: Boolean(account?.clientId && account?.clientSecret),
|
|
83
|
+
config: account || { clientId: '', clientSecret: '' }
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// DingTalk Channel Plugin
|
|
88
|
+
const dingTalkChannelPlugin = {
|
|
89
|
+
id: "dingtalk",
|
|
90
|
+
meta: {
|
|
91
|
+
id: "dingtalk",
|
|
92
|
+
label: "钉钉",
|
|
93
|
+
selectionLabel: "DingTalk Bot (Stream)",
|
|
94
|
+
docsPath: "/channels/dingtalk",
|
|
95
|
+
docsLabel: "dingtalk",
|
|
96
|
+
blurb: "钉钉机器人通道插件 (Stream模式)",
|
|
97
|
+
order: 100,
|
|
98
|
+
aliases: ["dt", "ding"],
|
|
99
|
+
},
|
|
100
|
+
capabilities: {
|
|
101
|
+
chatTypes: ["direct", "group"] as const,
|
|
102
|
+
},
|
|
103
|
+
reload: { configPrefixes: ["channels.dingtalk"] },
|
|
104
|
+
configSchema: {
|
|
105
|
+
type: "object" as const,
|
|
106
|
+
properties: {
|
|
107
|
+
channels: {
|
|
108
|
+
type: "object" as const,
|
|
109
|
+
properties: {
|
|
110
|
+
dingtalk: {
|
|
111
|
+
type: "object" as const,
|
|
112
|
+
properties: {
|
|
113
|
+
accounts: {
|
|
114
|
+
type: "object" as const,
|
|
115
|
+
additionalProperties: {
|
|
116
|
+
type: "object" as const,
|
|
117
|
+
properties: {
|
|
118
|
+
enabled: { type: "boolean" as const },
|
|
119
|
+
clientId: { type: "string" as const },
|
|
120
|
+
clientSecret: { type: "string" as const },
|
|
121
|
+
webhookUrl: { type: "string" as const },
|
|
122
|
+
name: { type: "string" as const },
|
|
123
|
+
},
|
|
124
|
+
required: ["clientId", "clientSecret"],
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
config: {
|
|
134
|
+
listAccountIds: (cfg: ClawdbotConfig) => listDingTalkAccountIds(cfg),
|
|
135
|
+
resolveAccount: (cfg: ClawdbotConfig, accountId?: string) => resolveDingTalkAccount({ cfg, accountId }),
|
|
136
|
+
defaultAccountId: (_cfg: ClawdbotConfig) => 'default',
|
|
137
|
+
isConfigured: (account: ResolvedDingTalkAccount) => account.configured,
|
|
138
|
+
describeAccount: (account: ResolvedDingTalkAccount) => ({
|
|
139
|
+
accountId: account.accountId,
|
|
140
|
+
name: account.name,
|
|
141
|
+
enabled: account.enabled,
|
|
142
|
+
configured: account.configured,
|
|
143
|
+
}),
|
|
144
|
+
},
|
|
145
|
+
gateway: {
|
|
146
|
+
startAccount: async (ctx: any) => {
|
|
147
|
+
const account: ResolvedDingTalkAccount = ctx.account;
|
|
148
|
+
const config = account.config;
|
|
149
|
+
const accountId = account.accountId;
|
|
150
|
+
|
|
151
|
+
if (!config.clientId || !config.clientSecret) {
|
|
152
|
+
ctx.log?.warn?.(`[${accountId}] missing clientId or clientSecret`);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
ctx.log?.info?.(`[${accountId}] starting DingTalk Stream client`);
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
const client = new DWClient({
|
|
160
|
+
clientId: config.clientId,
|
|
161
|
+
clientSecret: config.clientSecret,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Helper to safely handle messages
|
|
165
|
+
const handleMessage = async (res: any) => {
|
|
166
|
+
try {
|
|
167
|
+
const message = JSON.parse(res.data);
|
|
168
|
+
const textContent = message.text?.content || "";
|
|
169
|
+
const senderId = message.senderId;
|
|
170
|
+
const convoId = message.conversationId;
|
|
171
|
+
const msgId = message.msgId;
|
|
172
|
+
// Store session webhook if provided (DingTalk Stream mode provides this for replies)
|
|
173
|
+
if (message.sessionWebhook) {
|
|
174
|
+
sessionWebhooks.set(convoId, message.sessionWebhook);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Log reception
|
|
178
|
+
ctx.log?.info?.(`[${accountId}] received message from ${message.senderNick || senderId}: ${textContent}`);
|
|
179
|
+
|
|
180
|
+
// Filter out empty messages
|
|
181
|
+
if (!textContent) return;
|
|
182
|
+
|
|
183
|
+
// Simple text cleaning (remove @bot mentions if possible, though DingTalk usually gives clean content or we might need to parse entities)
|
|
184
|
+
const cleanedText = textContent.replace(/@\w+\s*/g, '').trim();
|
|
185
|
+
|
|
186
|
+
// Forward the message to Clawdbot for processing
|
|
187
|
+
if (pluginRuntime?.runtime?.channel?.reply) {
|
|
188
|
+
const replyModule = pluginRuntime.runtime.channel.reply;
|
|
189
|
+
const chatType = String(message.conversationType) === '2' ? 'group' : 'direct';
|
|
190
|
+
const fromAddress = chatType === 'group' ? `dingtalk:group:${convoId}` : `dingtalk:${senderId}`;
|
|
191
|
+
|
|
192
|
+
const ctxPayload = {
|
|
193
|
+
Body: cleanedText,
|
|
194
|
+
RawBody: textContent,
|
|
195
|
+
CommandBody: cleanedText,
|
|
196
|
+
From: fromAddress,
|
|
197
|
+
To: 'bot',
|
|
198
|
+
SessionKey: `dingtalk:${convoId}`,
|
|
199
|
+
AccountId: accountId,
|
|
200
|
+
ChatType: chatType,
|
|
201
|
+
SenderName: message.senderNick,
|
|
202
|
+
SenderId: senderId,
|
|
203
|
+
Provider: 'dingtalk',
|
|
204
|
+
Surface: 'dingtalk',
|
|
205
|
+
MessageSid: message.msgId,
|
|
206
|
+
Timestamp: message.createAt,
|
|
207
|
+
// Required for some logic
|
|
208
|
+
GroupSubject: chatType === 'group' ? (message.conversationId) : undefined,
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const finalizedCtx = replyModule.finalizeInboundContext(ctxPayload);
|
|
212
|
+
|
|
213
|
+
let replyBuffer = "";
|
|
214
|
+
let replySent = false;
|
|
215
|
+
|
|
216
|
+
const sendToDingTalk = async (text: string) => {
|
|
217
|
+
if (!text) return;
|
|
218
|
+
if (replySent) {
|
|
219
|
+
ctx.log?.info?.(`[${accountId}] Reply already sent, skipping buffer flush.`);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const replyWebhook = sessionWebhooks.get(convoId) || config.webhookUrl;
|
|
224
|
+
if (!replyWebhook) {
|
|
225
|
+
ctx.log?.error?.(`[${accountId}] No webhook to reply to ${convoId}`);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
await axios.post(replyWebhook, {
|
|
231
|
+
msgtype: "text",
|
|
232
|
+
text: { content: text }
|
|
233
|
+
}, { headers: { 'Content-Type': 'application/json' } });
|
|
234
|
+
replySent = true;
|
|
235
|
+
ctx.log?.info?.(`[${accountId}] Reply sent successfully.`);
|
|
236
|
+
} catch (e) {
|
|
237
|
+
ctx.log?.error?.(`[${accountId}] Failed to send reply: ${e}`);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const dispatcher = {
|
|
242
|
+
sendFinalReply: (payload: any) => {
|
|
243
|
+
const text = payload.text || payload.content || '';
|
|
244
|
+
sendToDingTalk(text).catch(e => ctx.log?.error?.(`[${accountId}] sendToDingTalk failed: ${e}`));
|
|
245
|
+
return true;
|
|
246
|
+
},
|
|
247
|
+
typing: async () => { },
|
|
248
|
+
reaction: async () => { },
|
|
249
|
+
isSynchronous: () => false,
|
|
250
|
+
waitForIdle: async () => { },
|
|
251
|
+
sendBlockReply: async (block: any) => {
|
|
252
|
+
// Accumulate text from blocks
|
|
253
|
+
const text = block.text || block.delta || block.content || '';
|
|
254
|
+
if (text) {
|
|
255
|
+
replyBuffer += text;
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
getQueuedCounts: () => ({ active: 0, queued: 0, final: 0 })
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// Internal dispatch
|
|
262
|
+
const dispatchPromise = replyModule.dispatchReplyFromConfig({
|
|
263
|
+
ctx: finalizedCtx,
|
|
264
|
+
cfg: pluginRuntime.config,
|
|
265
|
+
dispatcher: dispatcher,
|
|
266
|
+
replyOptions: {}
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// ACK immediately to prevent retries
|
|
270
|
+
if (res.headers && res.headers.messageId) {
|
|
271
|
+
client.socketCallBackResponse(res.headers.messageId, { status: "SUCCEED" });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Wait for run to finish
|
|
275
|
+
await dispatchPromise;
|
|
276
|
+
|
|
277
|
+
// If final reply wasn't called but we have buffer (streaming case where agent didn't return final payload?)
|
|
278
|
+
if (!replySent && replyBuffer) {
|
|
279
|
+
ctx.log?.info?.(`[${accountId}] Sending accumulated buffer from blocks (len=${replyBuffer.length}).`);
|
|
280
|
+
await sendToDingTalk(replyBuffer);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
} else {
|
|
284
|
+
ctx.log?.error?.(`[${accountId}] runtime.channel.reply not available`);
|
|
285
|
+
}
|
|
286
|
+
} catch (error) {
|
|
287
|
+
ctx.log?.error?.(`[${accountId}] error processing message: ${error instanceof Error ? error.message : String(error)}`);
|
|
288
|
+
console.error('DingTalk Handler Error:', error);
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// Register callback for robot messages
|
|
293
|
+
client.registerCallbackListener('/v1.0/im/bot/messages/get', handleMessage);
|
|
294
|
+
|
|
295
|
+
// Connect to DingTalk Stream
|
|
296
|
+
await client.connect();
|
|
297
|
+
activeClients.set(accountId, client);
|
|
298
|
+
ctx.log?.info?.(`[${accountId}] DingTalk Stream client connected`);
|
|
299
|
+
|
|
300
|
+
// Handle abort signal for cleanup
|
|
301
|
+
ctx.abortSignal?.addEventListener('abort', () => {
|
|
302
|
+
ctx.log?.info?.(`[${accountId}] stopping DingTalk Stream client`);
|
|
303
|
+
client.disconnect();
|
|
304
|
+
activeClients.delete(accountId);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
} catch (error) {
|
|
308
|
+
ctx.log?.error?.(`[${accountId}] failed to start: ${error instanceof Error ? error.message : String(error)}`);
|
|
309
|
+
throw error;
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
outbound: {
|
|
314
|
+
deliveryMode: "direct" as const,
|
|
315
|
+
sendText: async (opts: { text: string; account: ResolvedDingTalkAccount; target: string; senderId?: string }) => {
|
|
316
|
+
const { text, account, target } = opts;
|
|
317
|
+
const config = account.config;
|
|
318
|
+
|
|
319
|
+
// Try session webhook first (for replies)
|
|
320
|
+
const sessionWebhook = sessionWebhooks.get(target);
|
|
321
|
+
|
|
322
|
+
if (sessionWebhook) {
|
|
323
|
+
try {
|
|
324
|
+
await axios.post(sessionWebhook, {
|
|
325
|
+
msgtype: "text",
|
|
326
|
+
text: { content: text }
|
|
327
|
+
}, {
|
|
328
|
+
headers: { 'Content-Type': 'application/json' }
|
|
329
|
+
});
|
|
330
|
+
return { ok: true as const };
|
|
331
|
+
} catch (error) {
|
|
332
|
+
// Fall through to webhookUrl
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Fallback to webhookUrl for proactive messages
|
|
337
|
+
if (config?.webhookUrl) {
|
|
338
|
+
try {
|
|
339
|
+
await axios.post(config.webhookUrl, {
|
|
340
|
+
msgtype: "text",
|
|
341
|
+
text: { content: text }
|
|
342
|
+
}, {
|
|
343
|
+
headers: { 'Content-Type': 'application/json' }
|
|
344
|
+
});
|
|
345
|
+
return { ok: true as const };
|
|
346
|
+
} catch (error) {
|
|
347
|
+
return { ok: false as const, error: error instanceof Error ? error.message : String(error) };
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return { ok: false as const, error: "No webhook available for sending messages" };
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
// Plugin object format required by Clawdbot
|
|
359
|
+
const plugin = {
|
|
360
|
+
id: "dingtalk-channel",
|
|
361
|
+
name: "DingTalk Channel",
|
|
362
|
+
description: "DingTalk channel plugin using Stream mode",
|
|
363
|
+
configSchema: {
|
|
364
|
+
type: "object" as const,
|
|
365
|
+
properties: {}
|
|
366
|
+
},
|
|
367
|
+
register(api: ClawdbotPluginApi) {
|
|
368
|
+
pluginRuntime = api;
|
|
369
|
+
api.registerChannel({ plugin: dingTalkChannelPlugin });
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
export default plugin;
|
package/test-config.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"channels": {
|
|
3
|
+
"dingtalk": {
|
|
4
|
+
"accounts": {
|
|
5
|
+
"test": {
|
|
6
|
+
"enabled": true,
|
|
7
|
+
"clientId": "YOUR_APP_KEY",
|
|
8
|
+
"clientSecret": "YOUR_APP_SECRET"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"plugins": {
|
|
14
|
+
"entries": {
|
|
15
|
+
"dingtalk-channel": {
|
|
16
|
+
"path": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
package/test-local.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Script to test the DingTalk plugin with local Clawdbot
|
|
3
|
+
* This script will:
|
|
4
|
+
* 1. Validate the plugin structure
|
|
5
|
+
* 2. Create a test configuration
|
|
6
|
+
* 3. Provide instructions for testing
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { execSync } = require('child_process');
|
|
12
|
+
|
|
13
|
+
console.log('🧪 Testing DingTalk Plugin (Stream Mode)\n');
|
|
14
|
+
|
|
15
|
+
// Check if the plugin built successfully
|
|
16
|
+
const distDir = path.join(__dirname, 'dist');
|
|
17
|
+
if (!fs.existsSync(distDir)) {
|
|
18
|
+
console.error('❌ Dist directory does not exist. Please run `npm run build` first.');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const indexJs = path.join(distDir, 'index.js');
|
|
23
|
+
if (!fs.existsSync(indexJs)) {
|
|
24
|
+
console.error('❌ dist/index.js does not exist. Please run `npm run build` first.');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log('✅ Plugin built successfully');
|
|
29
|
+
|
|
30
|
+
// Check if Clawdbot is available
|
|
31
|
+
try {
|
|
32
|
+
execSync('which clawdbot', { stdio: 'pipe' });
|
|
33
|
+
console.log('✅ Clawdbot/Moltbot is available');
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.log('⚠️ Clawdbot/Moltbot not found, but you can still install manually');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Create a sample configuration for testing
|
|
39
|
+
const testConfig = {
|
|
40
|
+
channels: {
|
|
41
|
+
dingtalk: {
|
|
42
|
+
accounts: {
|
|
43
|
+
local_test: {
|
|
44
|
+
enabled: true,
|
|
45
|
+
clientId: "YOUR_APP_KEY_HERE",
|
|
46
|
+
clientSecret: "YOUR_APP_SECRET_HERE"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
plugins: {
|
|
52
|
+
entries: {
|
|
53
|
+
"dingtalk-channel": {
|
|
54
|
+
path: path.resolve(__dirname, 'dist/index.js')
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const configPath = path.join(__dirname, 'local-test-config.json');
|
|
61
|
+
fs.writeFileSync(configPath, JSON.stringify(testConfig, null, 2));
|
|
62
|
+
console.log(`✅ Created test configuration at ${configPath}`);
|
|
63
|
+
|
|
64
|
+
// Provide instructions
|
|
65
|
+
console.log('\n📋 Testing Instructions:\n');
|
|
66
|
+
|
|
67
|
+
console.log('1. Set up your DingTalk app:');
|
|
68
|
+
console.log(' - Go to https://open.dingtalk.com/');
|
|
69
|
+
console.log(' - Create an enterprise internal app');
|
|
70
|
+
console.log(' - Add Robot capability with Stream Mode enabled');
|
|
71
|
+
console.log(' - Get your AppKey (clientId) and AppSecret (clientSecret)\n');
|
|
72
|
+
|
|
73
|
+
console.log('2. Update the test config:');
|
|
74
|
+
console.log(` - Edit ${configPath}`);
|
|
75
|
+
console.log(' - Replace YOUR_APP_KEY_HERE with your AppKey');
|
|
76
|
+
console.log(' - Replace YOUR_APP_SECRET_HERE with your AppSecret\n');
|
|
77
|
+
|
|
78
|
+
console.log('3. Build and test:');
|
|
79
|
+
console.log(' npm run build');
|
|
80
|
+
console.log(` clawdbot --config ${configPath}\n`);
|
|
81
|
+
|
|
82
|
+
console.log('4. Test by sending a message to your robot in DingTalk.');
|
|
83
|
+
console.log(' For group chats, @mention the robot.\n');
|
|
84
|
+
|
|
85
|
+
console.log('💡 Tips:');
|
|
86
|
+
console.log('- No ngrok or public IP needed! Stream mode connects outbound.');
|
|
87
|
+
console.log('- Check Clawdbot logs at ~/.clawdbot/logs/ for debugging');
|
|
88
|
+
console.log('- Session webhooks for replies expire after ~2 hours');
|
|
89
|
+
|
|
90
|
+
console.log('\n🚀 You\'re ready to test your DingTalk Stream plugin!');
|
package/test-plugin.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple test script to validate the plugin structure
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
// Check if necessary files exist
|
|
9
|
+
const requiredFiles = [
|
|
10
|
+
'package.json',
|
|
11
|
+
'moltbot.plugin.json',
|
|
12
|
+
'tsconfig.json',
|
|
13
|
+
'src/index.ts',
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
console.log('🔍 Validating plugin structure...\n');
|
|
17
|
+
|
|
18
|
+
let allGood = true;
|
|
19
|
+
|
|
20
|
+
for (const file of requiredFiles) {
|
|
21
|
+
const filePath = path.join(__dirname, file);
|
|
22
|
+
if (fs.existsSync(filePath)) {
|
|
23
|
+
console.log(`✅ ${file}`);
|
|
24
|
+
} else {
|
|
25
|
+
console.log(`❌ ${file} - MISSING`);
|
|
26
|
+
allGood = false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Check if src directory exists and has content
|
|
31
|
+
const srcDir = path.join(__dirname, 'src');
|
|
32
|
+
if (fs.existsSync(srcDir)) {
|
|
33
|
+
const srcFiles = fs.readdirSync(srcDir);
|
|
34
|
+
console.log(`\n📁 Source files: ${srcFiles.length} files`);
|
|
35
|
+
srcFiles.forEach(file => console.log(` - ${file}`));
|
|
36
|
+
} else {
|
|
37
|
+
console.log('\n❌ src directory missing');
|
|
38
|
+
allGood = false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log('\n' + '='.repeat(50));
|
|
42
|
+
|
|
43
|
+
if (allGood) {
|
|
44
|
+
console.log('🎉 Plugin structure looks good!');
|
|
45
|
+
console.log('\nNext steps:');
|
|
46
|
+
console.log('1. Run `npm install` to install dependencies');
|
|
47
|
+
console.log('2. Run `npm run build` to compile TypeScript');
|
|
48
|
+
console.log('3. Test the plugin with your local Clawdbot');
|
|
49
|
+
} else {
|
|
50
|
+
console.log('❌ Some files are missing. Please check the structure.');
|
|
51
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"declarationMap": true,
|
|
14
|
+
"sourceMap": true,
|
|
15
|
+
"removeComments": false,
|
|
16
|
+
"noImplicitAny": true,
|
|
17
|
+
"strictNullChecks": true,
|
|
18
|
+
"strictFunctionTypes": true,
|
|
19
|
+
"noImplicitThis": true,
|
|
20
|
+
"noImplicitReturns": true,
|
|
21
|
+
"noFallthroughCasesInSwitch": true,
|
|
22
|
+
"noUncheckedIndexedAccess": true,
|
|
23
|
+
"noImplicitOverride": true,
|
|
24
|
+
"allowUnreachableCode": false,
|
|
25
|
+
"allowUnusedLabels": false
|
|
26
|
+
},
|
|
27
|
+
"include": [
|
|
28
|
+
"src/**/*"
|
|
29
|
+
],
|
|
30
|
+
"exclude": [
|
|
31
|
+
"node_modules",
|
|
32
|
+
"dist"
|
|
33
|
+
]
|
|
34
|
+
}
|