moltbot-dingtalk-stream 1.0.7 → 1.0.9
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/CHANGELOG.md +20 -0
- package/README.md +114 -41
- package/clawdbot.plugin.json +1 -1
- package/dist/index.d.ts +162 -21
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +347 -249
- package/dist/index.js.map +1 -1
- package/dist/runtime.d.ts +142 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +106 -0
- package/dist/runtime.js.map +1 -0
- package/dist/schema.d.ts +137 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +235 -0
- package/dist/schema.js.map +1 -0
- package/package.json +4 -3
- package/src/index.ts +574 -305
- package/src/runtime.ts +227 -0
- package/src/schema.ts +316 -0
- package/dist/clawdbot.plugin.json +0 -59
- package/dist/package.json +0 -11
package/src/runtime.ts
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { DWClient } from "dingtalk-stream";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
|
|
4
|
+
// Types for Clawdbot core runtime (obtained from pluginRuntime)
|
|
5
|
+
export interface ClawdbotCoreRuntime {
|
|
6
|
+
channel: {
|
|
7
|
+
routing: {
|
|
8
|
+
resolveAgentRoute: (opts: {
|
|
9
|
+
cfg: unknown;
|
|
10
|
+
channel: string;
|
|
11
|
+
accountId: string;
|
|
12
|
+
peer: { kind: "direct" | "group"; id: string };
|
|
13
|
+
}) => { agentId: string; sessionKey: string; accountId: string };
|
|
14
|
+
};
|
|
15
|
+
reply: {
|
|
16
|
+
formatAgentEnvelope: (opts: {
|
|
17
|
+
channel: string;
|
|
18
|
+
from: string;
|
|
19
|
+
timestamp?: number;
|
|
20
|
+
previousTimestamp?: number | null;
|
|
21
|
+
envelope: unknown;
|
|
22
|
+
body: string;
|
|
23
|
+
}) => string;
|
|
24
|
+
resolveEnvelopeFormatOptions: (cfg: unknown) => unknown;
|
|
25
|
+
resolveHumanDelayConfig: (cfg: unknown, agentId?: string) => unknown;
|
|
26
|
+
finalizeInboundContext: (ctx: unknown) => unknown;
|
|
27
|
+
createReplyDispatcherWithTyping: (opts: {
|
|
28
|
+
responsePrefix?: string;
|
|
29
|
+
responsePrefixContextProvider?: () => Promise<string>;
|
|
30
|
+
humanDelay?: unknown;
|
|
31
|
+
deliver: (payload: { text?: string; content?: string; mediaUrls?: string[] }) => Promise<void>;
|
|
32
|
+
onError?: (err: unknown, info: { kind: string }) => void;
|
|
33
|
+
onReplyStart?: () => void;
|
|
34
|
+
onIdle?: () => void;
|
|
35
|
+
}) => {
|
|
36
|
+
dispatcher: unknown;
|
|
37
|
+
replyOptions: Record<string, unknown>;
|
|
38
|
+
markDispatchIdle: () => void;
|
|
39
|
+
};
|
|
40
|
+
dispatchReplyFromConfig: (opts: {
|
|
41
|
+
ctx: unknown;
|
|
42
|
+
cfg: unknown;
|
|
43
|
+
dispatcher: unknown;
|
|
44
|
+
replyOptions?: Record<string, unknown>;
|
|
45
|
+
}) => Promise<{ queuedFinal: boolean; counts: { final: number } }>;
|
|
46
|
+
dispatchReplyWithBufferedBlockDispatcher: (opts: {
|
|
47
|
+
ctx: unknown;
|
|
48
|
+
cfg: unknown;
|
|
49
|
+
dispatcherOptions: {
|
|
50
|
+
deliver: (payload: { text?: string; content?: string }) => Promise<void>;
|
|
51
|
+
onError?: (err: unknown, info: { kind: string }) => void;
|
|
52
|
+
};
|
|
53
|
+
}) => Promise<void>;
|
|
54
|
+
};
|
|
55
|
+
session: {
|
|
56
|
+
resolveStorePath: (
|
|
57
|
+
storeConfig: unknown,
|
|
58
|
+
opts: { agentId: string }
|
|
59
|
+
) => string;
|
|
60
|
+
readSessionUpdatedAt?: (opts: { storePath: string; sessionKey: string }) => number | null;
|
|
61
|
+
recordInboundSession: (opts: {
|
|
62
|
+
storePath: string;
|
|
63
|
+
sessionKey: string;
|
|
64
|
+
ctx: unknown;
|
|
65
|
+
onRecordError: (err: unknown) => void;
|
|
66
|
+
}) => Promise<void>;
|
|
67
|
+
};
|
|
68
|
+
text: {
|
|
69
|
+
resolveMarkdownTableMode: (opts: {
|
|
70
|
+
cfg: unknown;
|
|
71
|
+
channel: string;
|
|
72
|
+
accountId: string;
|
|
73
|
+
}) => "off" | "plain" | "markdown" | "bullets" | "code";
|
|
74
|
+
chunkMarkdownTextWithMode: (text: string, limit: number, mode: string) => string[];
|
|
75
|
+
resolveChunkMode: (cfg: unknown, channel: string, accountId?: string) => string;
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
logging: {
|
|
79
|
+
shouldLogVerbose: () => boolean;
|
|
80
|
+
getChildLogger: (opts: { module: string }) => {
|
|
81
|
+
info: (msg: string) => void;
|
|
82
|
+
warn: (msg: string) => void;
|
|
83
|
+
error: (msg: string) => void;
|
|
84
|
+
debug?: (msg: string) => void;
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Types
|
|
90
|
+
export interface DingTalkRuntime {
|
|
91
|
+
channel: {
|
|
92
|
+
dingtalk: {
|
|
93
|
+
sendMessage: (
|
|
94
|
+
target: string,
|
|
95
|
+
text: string,
|
|
96
|
+
opts?: { accountId?: string; mediaUrl?: string }
|
|
97
|
+
) => Promise<{ ok: boolean; error?: string }>;
|
|
98
|
+
probe: (
|
|
99
|
+
clientId: string,
|
|
100
|
+
clientSecret: string,
|
|
101
|
+
timeoutMs?: number
|
|
102
|
+
) => Promise<{ ok: boolean; error?: string; bot?: { name?: string } }>;
|
|
103
|
+
getClient: (accountId: string) => DWClient | undefined;
|
|
104
|
+
setClient: (accountId: string, client: DWClient) => void;
|
|
105
|
+
removeClient: (accountId: string) => void;
|
|
106
|
+
setSessionWebhook: (conversationId: string, webhook: string) => void;
|
|
107
|
+
getSessionWebhook: (conversationId: string) => string | undefined;
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
logging: {
|
|
111
|
+
shouldLogVerbose: () => boolean;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Storage
|
|
116
|
+
const activeClients = new Map<string, DWClient>();
|
|
117
|
+
const sessionWebhooks = new Map<string, string>();
|
|
118
|
+
|
|
119
|
+
// Runtime implementation
|
|
120
|
+
const dingtalkRuntime: DingTalkRuntime = {
|
|
121
|
+
channel: {
|
|
122
|
+
dingtalk: {
|
|
123
|
+
sendMessage: async (target, text, opts = {}) => {
|
|
124
|
+
// Try multiple formats to find webhook
|
|
125
|
+
// target can be: "dingtalk:user:xxx", "dingtalk:channel:xxx", or plain conversationId
|
|
126
|
+
const normalizedTarget = target
|
|
127
|
+
.replace(/^dingtalk:(user|channel|group):/, "")
|
|
128
|
+
.replace(/^dingtalk:/, "");
|
|
129
|
+
|
|
130
|
+
let webhook = sessionWebhooks.get(target)
|
|
131
|
+
|| sessionWebhooks.get(normalizedTarget);
|
|
132
|
+
|
|
133
|
+
// Fallback: iterate all keys for partial match
|
|
134
|
+
if (!webhook) {
|
|
135
|
+
for (const [key, value] of sessionWebhooks.entries()) {
|
|
136
|
+
if (key.includes(normalizedTarget) || normalizedTarget.includes(key)) {
|
|
137
|
+
webhook = value;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!webhook) {
|
|
144
|
+
console.error(`[DingTalk] No webhook for target: ${target}, normalized: ${normalizedTarget}, available keys: ${Array.from(sessionWebhooks.keys()).join(", ")}`);
|
|
145
|
+
return { ok: false, error: `No webhook available for target: ${target}` };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const payload: Record<string, unknown> = {
|
|
150
|
+
msgtype: "text",
|
|
151
|
+
text: { content: text },
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// Use markdown format for media (DingTalk text messages don't support embedded images)
|
|
155
|
+
if (opts.mediaUrl) {
|
|
156
|
+
payload.msgtype = "markdown";
|
|
157
|
+
payload.markdown = {
|
|
158
|
+
title: "Message",
|
|
159
|
+
text: `${text}\n\n`,
|
|
160
|
+
};
|
|
161
|
+
delete payload.text;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log(`[DingTalk] Sending to webhook: ${webhook.substring(0, 50)}...`);
|
|
165
|
+
await axios.post(webhook, payload, {
|
|
166
|
+
headers: { "Content-Type": "application/json" },
|
|
167
|
+
timeout: 10000,
|
|
168
|
+
});
|
|
169
|
+
return { ok: true };
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.error(`[DingTalk] Send failed:`, error);
|
|
172
|
+
return {
|
|
173
|
+
ok: false,
|
|
174
|
+
error: error instanceof Error ? error.message : String(error),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
probe: async (clientId, clientSecret, timeoutMs = 5000) => {
|
|
180
|
+
try {
|
|
181
|
+
// Verify credentials by fetching access_token from DingTalk API
|
|
182
|
+
const response = await axios.post(
|
|
183
|
+
"https://api.dingtalk.com/v1.0/oauth2/accessToken",
|
|
184
|
+
{ appKey: clientId, appSecret: clientSecret },
|
|
185
|
+
{
|
|
186
|
+
headers: { "Content-Type": "application/json" },
|
|
187
|
+
timeout: timeoutMs,
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
if (response.data?.accessToken) {
|
|
192
|
+
return { ok: true, bot: { name: "DingTalk Bot" } };
|
|
193
|
+
}
|
|
194
|
+
return { ok: false, error: "Invalid credentials" };
|
|
195
|
+
} catch (error) {
|
|
196
|
+
return {
|
|
197
|
+
ok: false,
|
|
198
|
+
error: error instanceof Error ? error.message : String(error),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
getClient: (accountId) => activeClients.get(accountId),
|
|
204
|
+
setClient: (accountId, client) => activeClients.set(accountId, client),
|
|
205
|
+
removeClient: (accountId) => activeClients.delete(accountId),
|
|
206
|
+
setSessionWebhook: (conversationId, webhook) =>
|
|
207
|
+
sessionWebhooks.set(conversationId, webhook),
|
|
208
|
+
getSessionWebhook: (conversationId) => sessionWebhooks.get(conversationId),
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
logging: {
|
|
212
|
+
shouldLogVerbose: () => process.env.DEBUG === "true",
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
let runtimeInstance: DingTalkRuntime | null = null;
|
|
217
|
+
|
|
218
|
+
export function getDingTalkRuntime(): DingTalkRuntime {
|
|
219
|
+
if (!runtimeInstance) {
|
|
220
|
+
runtimeInstance = dingtalkRuntime;
|
|
221
|
+
}
|
|
222
|
+
return runtimeInstance;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function setDingTalkRuntime(runtime: DingTalkRuntime): void {
|
|
226
|
+
runtimeInstance = runtime;
|
|
227
|
+
}
|
package/src/schema.ts
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
|
|
3
|
+
// Channel ID
|
|
4
|
+
export const CHANNEL_ID = "moltbot-dingtalk-stream";
|
|
5
|
+
export const DEFAULT_ACCOUNT_ID = "default";
|
|
6
|
+
|
|
7
|
+
// DingTalk account configuration interface
|
|
8
|
+
export interface DingTalkAccountConfig {
|
|
9
|
+
enabled?: boolean;
|
|
10
|
+
clientId?: string;
|
|
11
|
+
clientSecret?: string;
|
|
12
|
+
webhookUrl?: string;
|
|
13
|
+
name?: string;
|
|
14
|
+
// Group settings
|
|
15
|
+
groupPolicy?: "open" | "allowlist";
|
|
16
|
+
requireMention?: boolean;
|
|
17
|
+
// DM settings
|
|
18
|
+
dm?: {
|
|
19
|
+
policy?: "open" | "pairing" | "allowlist";
|
|
20
|
+
allowFrom?: string[];
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Resolved account with computed fields
|
|
25
|
+
export interface ResolvedDingTalkAccount {
|
|
26
|
+
accountId: string;
|
|
27
|
+
name?: string;
|
|
28
|
+
enabled: boolean;
|
|
29
|
+
configured: boolean;
|
|
30
|
+
clientId: string;
|
|
31
|
+
clientSecret: string;
|
|
32
|
+
tokenSource: "config" | "env" | "none";
|
|
33
|
+
config: DingTalkAccountConfig;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Channel configuration interface
|
|
37
|
+
export interface DingTalkChannelConfig {
|
|
38
|
+
enabled?: boolean;
|
|
39
|
+
clientId?: string;
|
|
40
|
+
clientSecret?: string;
|
|
41
|
+
webhookUrl?: string;
|
|
42
|
+
name?: string;
|
|
43
|
+
groupPolicy?: "open" | "allowlist";
|
|
44
|
+
requireMention?: boolean;
|
|
45
|
+
dm?: {
|
|
46
|
+
policy?: "open" | "pairing" | "allowlist";
|
|
47
|
+
allowFrom?: string[];
|
|
48
|
+
};
|
|
49
|
+
accounts?: Record<string, DingTalkAccountConfig>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Full config interface
|
|
53
|
+
export interface ClawdbotConfig {
|
|
54
|
+
channels?: {
|
|
55
|
+
[CHANNEL_ID]?: DingTalkChannelConfig;
|
|
56
|
+
defaults?: {
|
|
57
|
+
groupPolicy?: "open" | "allowlist";
|
|
58
|
+
};
|
|
59
|
+
[key: string]: unknown;
|
|
60
|
+
};
|
|
61
|
+
[key: string]: unknown;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Config schema for validation
|
|
65
|
+
export const DingTalkConfigSchema = {
|
|
66
|
+
type: "object" as const,
|
|
67
|
+
properties: {
|
|
68
|
+
enabled: { type: "boolean" as const },
|
|
69
|
+
clientId: { type: "string" as const },
|
|
70
|
+
clientSecret: { type: "string" as const },
|
|
71
|
+
webhookUrl: { type: "string" as const },
|
|
72
|
+
name: { type: "string" as const },
|
|
73
|
+
groupPolicy: { type: "string" as const, enum: ["open", "allowlist"] },
|
|
74
|
+
requireMention: { type: "boolean" as const },
|
|
75
|
+
dm: {
|
|
76
|
+
type: "object" as const,
|
|
77
|
+
properties: {
|
|
78
|
+
policy: { type: "string" as const, enum: ["open", "pairing", "allowlist"] },
|
|
79
|
+
allowFrom: { type: "array" as const, items: { type: "string" as const } },
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
accounts: {
|
|
83
|
+
type: "object" as const,
|
|
84
|
+
additionalProperties: {
|
|
85
|
+
type: "object" as const,
|
|
86
|
+
properties: {
|
|
87
|
+
enabled: { type: "boolean" as const },
|
|
88
|
+
clientId: { type: "string" as const },
|
|
89
|
+
clientSecret: { type: "string" as const },
|
|
90
|
+
webhookUrl: { type: "string" as const },
|
|
91
|
+
name: { type: "string" as const },
|
|
92
|
+
},
|
|
93
|
+
required: ["clientId", "clientSecret"],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Helper functions
|
|
100
|
+
export function listDingTalkAccountIds(cfg: ClawdbotConfig): string[] {
|
|
101
|
+
const channelConfig = cfg.channels?.[CHANNEL_ID];
|
|
102
|
+
if (!channelConfig) return [];
|
|
103
|
+
|
|
104
|
+
const accountIds: string[] = [];
|
|
105
|
+
|
|
106
|
+
// Check for top-level (default) account
|
|
107
|
+
if (channelConfig.clientId && channelConfig.clientSecret) {
|
|
108
|
+
accountIds.push(DEFAULT_ACCOUNT_ID);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check for named accounts
|
|
112
|
+
const accounts = channelConfig.accounts;
|
|
113
|
+
if (accounts) {
|
|
114
|
+
for (const id of Object.keys(accounts)) {
|
|
115
|
+
if (id !== DEFAULT_ACCOUNT_ID || !accountIds.includes(DEFAULT_ACCOUNT_ID)) {
|
|
116
|
+
accountIds.push(id);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return accountIds;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function resolveDingTalkAccount(opts: {
|
|
125
|
+
cfg: ClawdbotConfig;
|
|
126
|
+
accountId?: string;
|
|
127
|
+
}): ResolvedDingTalkAccount {
|
|
128
|
+
const { cfg, accountId = DEFAULT_ACCOUNT_ID } = opts;
|
|
129
|
+
const channelConfig = cfg.channels?.[CHANNEL_ID];
|
|
130
|
+
|
|
131
|
+
// Try to get account config
|
|
132
|
+
let accountConfig: DingTalkAccountConfig | undefined;
|
|
133
|
+
let tokenSource: "config" | "env" | "none" = "none";
|
|
134
|
+
|
|
135
|
+
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
136
|
+
// For default account, check top-level first, then accounts.default
|
|
137
|
+
if (channelConfig?.clientId && channelConfig?.clientSecret) {
|
|
138
|
+
accountConfig = {
|
|
139
|
+
enabled: channelConfig.enabled,
|
|
140
|
+
clientId: channelConfig.clientId,
|
|
141
|
+
clientSecret: channelConfig.clientSecret,
|
|
142
|
+
webhookUrl: channelConfig.webhookUrl,
|
|
143
|
+
name: channelConfig.name,
|
|
144
|
+
groupPolicy: channelConfig.groupPolicy,
|
|
145
|
+
requireMention: channelConfig.requireMention,
|
|
146
|
+
dm: channelConfig.dm,
|
|
147
|
+
};
|
|
148
|
+
tokenSource = "config";
|
|
149
|
+
} else if (channelConfig?.accounts?.[DEFAULT_ACCOUNT_ID]) {
|
|
150
|
+
accountConfig = channelConfig.accounts[DEFAULT_ACCOUNT_ID];
|
|
151
|
+
tokenSource = "config";
|
|
152
|
+
} else if (process.env.DINGTALK_CLIENT_ID && process.env.DINGTALK_CLIENT_SECRET) {
|
|
153
|
+
accountConfig = {
|
|
154
|
+
enabled: true,
|
|
155
|
+
clientId: process.env.DINGTALK_CLIENT_ID,
|
|
156
|
+
clientSecret: process.env.DINGTALK_CLIENT_SECRET,
|
|
157
|
+
webhookUrl: process.env.DINGTALK_WEBHOOK_URL,
|
|
158
|
+
};
|
|
159
|
+
tokenSource = "env";
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
// Named account
|
|
163
|
+
accountConfig = channelConfig?.accounts?.[accountId];
|
|
164
|
+
if (accountConfig) {
|
|
165
|
+
tokenSource = "config";
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const config = accountConfig || { clientId: "", clientSecret: "" };
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
accountId,
|
|
173
|
+
name: config.name,
|
|
174
|
+
enabled: config.enabled ?? true,
|
|
175
|
+
configured: Boolean(config.clientId?.trim() && config.clientSecret?.trim()),
|
|
176
|
+
clientId: config.clientId || "",
|
|
177
|
+
clientSecret: config.clientSecret || "",
|
|
178
|
+
tokenSource,
|
|
179
|
+
config,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function resolveDefaultDingTalkAccountId(cfg: ClawdbotConfig): string {
|
|
184
|
+
const accountIds = listDingTalkAccountIds(cfg);
|
|
185
|
+
return accountIds.includes(DEFAULT_ACCOUNT_ID)
|
|
186
|
+
? DEFAULT_ACCOUNT_ID
|
|
187
|
+
: accountIds[0] || DEFAULT_ACCOUNT_ID;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function normalizeAccountId(accountId?: string): string {
|
|
191
|
+
if (!accountId || accountId === "default" || accountId === "") {
|
|
192
|
+
return DEFAULT_ACCOUNT_ID;
|
|
193
|
+
}
|
|
194
|
+
return accountId.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function setAccountEnabledInConfig(opts: {
|
|
198
|
+
cfg: ClawdbotConfig;
|
|
199
|
+
accountId: string;
|
|
200
|
+
enabled: boolean;
|
|
201
|
+
}): ClawdbotConfig {
|
|
202
|
+
const { cfg, accountId, enabled } = opts;
|
|
203
|
+
const channelConfig = cfg.channels?.[CHANNEL_ID] || {};
|
|
204
|
+
|
|
205
|
+
if (accountId === DEFAULT_ACCOUNT_ID && channelConfig.clientId) {
|
|
206
|
+
// Top-level default account
|
|
207
|
+
return {
|
|
208
|
+
...cfg,
|
|
209
|
+
channels: {
|
|
210
|
+
...cfg.channels,
|
|
211
|
+
[CHANNEL_ID]: {
|
|
212
|
+
...channelConfig,
|
|
213
|
+
enabled,
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Named account
|
|
220
|
+
return {
|
|
221
|
+
...cfg,
|
|
222
|
+
channels: {
|
|
223
|
+
...cfg.channels,
|
|
224
|
+
[CHANNEL_ID]: {
|
|
225
|
+
...channelConfig,
|
|
226
|
+
accounts: {
|
|
227
|
+
...channelConfig.accounts,
|
|
228
|
+
[accountId]: {
|
|
229
|
+
...channelConfig.accounts?.[accountId],
|
|
230
|
+
enabled,
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function deleteAccountFromConfig(opts: {
|
|
239
|
+
cfg: ClawdbotConfig;
|
|
240
|
+
accountId: string;
|
|
241
|
+
}): ClawdbotConfig {
|
|
242
|
+
const { cfg, accountId } = opts;
|
|
243
|
+
const channelConfig = cfg.channels?.[CHANNEL_ID];
|
|
244
|
+
|
|
245
|
+
if (!channelConfig) return cfg;
|
|
246
|
+
|
|
247
|
+
if (accountId === DEFAULT_ACCOUNT_ID && channelConfig.clientId) {
|
|
248
|
+
// Remove top-level credentials
|
|
249
|
+
const { clientId, clientSecret, webhookUrl, name, ...rest } = channelConfig;
|
|
250
|
+
return {
|
|
251
|
+
...cfg,
|
|
252
|
+
channels: {
|
|
253
|
+
...cfg.channels,
|
|
254
|
+
[CHANNEL_ID]: rest,
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Remove named account
|
|
260
|
+
if (channelConfig.accounts?.[accountId]) {
|
|
261
|
+
const { [accountId]: removed, ...remainingAccounts } = channelConfig.accounts;
|
|
262
|
+
return {
|
|
263
|
+
...cfg,
|
|
264
|
+
channels: {
|
|
265
|
+
...cfg.channels,
|
|
266
|
+
[CHANNEL_ID]: {
|
|
267
|
+
...channelConfig,
|
|
268
|
+
accounts: remainingAccounts,
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return cfg;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function applyAccountNameToConfig(opts: {
|
|
278
|
+
cfg: ClawdbotConfig;
|
|
279
|
+
accountId: string;
|
|
280
|
+
name?: string;
|
|
281
|
+
}): ClawdbotConfig {
|
|
282
|
+
const { cfg, accountId, name } = opts;
|
|
283
|
+
if (!name) return cfg;
|
|
284
|
+
|
|
285
|
+
const channelConfig = cfg.channels?.[CHANNEL_ID] || {};
|
|
286
|
+
|
|
287
|
+
if (accountId === DEFAULT_ACCOUNT_ID && channelConfig.clientId) {
|
|
288
|
+
return {
|
|
289
|
+
...cfg,
|
|
290
|
+
channels: {
|
|
291
|
+
...cfg.channels,
|
|
292
|
+
[CHANNEL_ID]: {
|
|
293
|
+
...channelConfig,
|
|
294
|
+
name,
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
...cfg,
|
|
302
|
+
channels: {
|
|
303
|
+
...cfg.channels,
|
|
304
|
+
[CHANNEL_ID]: {
|
|
305
|
+
...channelConfig,
|
|
306
|
+
accounts: {
|
|
307
|
+
...channelConfig.accounts,
|
|
308
|
+
[accountId]: {
|
|
309
|
+
...channelConfig.accounts?.[accountId],
|
|
310
|
+
name,
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "moltbot-dingtalk-stream",
|
|
3
|
-
"name": "DingTalk Stream Channel",
|
|
4
|
-
"version": "1.0.7",
|
|
5
|
-
"description": "Custom channel for integrating Moltbot with DingTalk (Stream Mode)",
|
|
6
|
-
"author": "Jack",
|
|
7
|
-
"license": "MIT",
|
|
8
|
-
"extensions": {
|
|
9
|
-
"channels": [
|
|
10
|
-
"./index.js"
|
|
11
|
-
]
|
|
12
|
-
},
|
|
13
|
-
"channels": [
|
|
14
|
-
"moltbot-dingtalk-stream"
|
|
15
|
-
],
|
|
16
|
-
"configSchema": {
|
|
17
|
-
"type": "object",
|
|
18
|
-
"properties": {
|
|
19
|
-
"channels": {
|
|
20
|
-
"type": "object",
|
|
21
|
-
"properties": {
|
|
22
|
-
"moltbot-dingtalk-stream": {
|
|
23
|
-
"type": "object",
|
|
24
|
-
"properties": {
|
|
25
|
-
"accounts": {
|
|
26
|
-
"type": "object",
|
|
27
|
-
"additionalProperties": {
|
|
28
|
-
"type": "object",
|
|
29
|
-
"properties": {
|
|
30
|
-
"enabled": {
|
|
31
|
-
"type": "boolean",
|
|
32
|
-
"description": "Whether this account is enabled"
|
|
33
|
-
},
|
|
34
|
-
"clientId": {
|
|
35
|
-
"type": "string",
|
|
36
|
-
"description": "AppKey from DingTalk developer console"
|
|
37
|
-
},
|
|
38
|
-
"clientSecret": {
|
|
39
|
-
"type": "string",
|
|
40
|
-
"description": "AppSecret from DingTalk developer console"
|
|
41
|
-
},
|
|
42
|
-
"webhookUrl": {
|
|
43
|
-
"type": "string",
|
|
44
|
-
"description": "Optional webhook URL for proactive messages"
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
"required": [
|
|
48
|
-
"clientId",
|
|
49
|
-
"clientSecret"
|
|
50
|
-
]
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|