@shadowob/openclaw-shadowob 1.1.0 → 1.1.3
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/dist/chunk-NBNZ7NVR.js +175 -0
- package/dist/chunk-PEV3R2R7.js +2018 -0
- package/dist/index.js +1011 -4
- package/dist/monitor-AE3LRQYD.js +16 -0
- package/dist/setup-entry.js +3 -3
- package/openclaw.plugin.json +118 -0
- package/package.json +4 -10
- package/skills/shadowob/SKILL.md +37 -57
- package/dist/chunk-QFUUQPJZ.js +0 -1062
- package/dist/chunk-XPNVTXKL.js +0 -712
- package/dist/monitor-G2J667KY.js +0 -6
package/dist/chunk-XPNVTXKL.js
DELETED
|
@@ -1,712 +0,0 @@
|
|
|
1
|
-
// src/channel.ts
|
|
2
|
-
import { ShadowClient as ShadowClient2 } from "@shadowob/sdk";
|
|
3
|
-
import {
|
|
4
|
-
createChannelPluginBase,
|
|
5
|
-
createChatChannelPlugin
|
|
6
|
-
} from "openclaw/plugin-sdk/core";
|
|
7
|
-
|
|
8
|
-
// src/config.ts
|
|
9
|
-
var DEFAULT_ACCOUNT_ID = "default";
|
|
10
|
-
function getShadowBlock(cfg) {
|
|
11
|
-
const channels = cfg.channels;
|
|
12
|
-
return channels?.shadowob ?? channels?.["openclaw-shadowob"] ?? channels?.shadow;
|
|
13
|
-
}
|
|
14
|
-
function getAccountConfig(cfg, accountId) {
|
|
15
|
-
const shadow = getShadowBlock(cfg);
|
|
16
|
-
if (!shadow) return null;
|
|
17
|
-
const accounts = shadow.accounts;
|
|
18
|
-
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
19
|
-
const fromAccounts = accounts?.[DEFAULT_ACCOUNT_ID];
|
|
20
|
-
const baseLevel = {
|
|
21
|
-
token: typeof shadow.token === "string" ? shadow.token : void 0,
|
|
22
|
-
serverUrl: typeof shadow.serverUrl === "string" ? shadow.serverUrl : void 0,
|
|
23
|
-
enabled: typeof shadow.enabled === "boolean" ? shadow.enabled : void 0
|
|
24
|
-
};
|
|
25
|
-
const merged = {
|
|
26
|
-
...fromAccounts,
|
|
27
|
-
// Base-level fields take precedence (convenience shorthand)
|
|
28
|
-
...Object.fromEntries(Object.entries(baseLevel).filter(([, v]) => v !== void 0))
|
|
29
|
-
};
|
|
30
|
-
if (merged.token) {
|
|
31
|
-
return {
|
|
32
|
-
token: merged.token,
|
|
33
|
-
serverUrl: merged.serverUrl ?? "https://shadowob.com",
|
|
34
|
-
enabled: merged.enabled
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
return fromAccounts ?? null;
|
|
38
|
-
}
|
|
39
|
-
return accounts?.[accountId] ?? null;
|
|
40
|
-
}
|
|
41
|
-
function listAccountIds(cfg) {
|
|
42
|
-
const shadow = getShadowBlock(cfg);
|
|
43
|
-
if (!shadow) return [];
|
|
44
|
-
const accounts = shadow.accounts;
|
|
45
|
-
const ids = [];
|
|
46
|
-
if (accounts) {
|
|
47
|
-
ids.push(...Object.keys(accounts));
|
|
48
|
-
}
|
|
49
|
-
const hasBaseLevelConfig = typeof shadow.token === "string" || typeof shadow.serverUrl === "string";
|
|
50
|
-
if (hasBaseLevelConfig && !ids.includes(DEFAULT_ACCOUNT_ID)) {
|
|
51
|
-
ids.push(DEFAULT_ACCOUNT_ID);
|
|
52
|
-
}
|
|
53
|
-
return ids;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// src/outbound.ts
|
|
57
|
-
import { ShadowClient } from "@shadowob/sdk";
|
|
58
|
-
function parseTarget(to) {
|
|
59
|
-
const parts = to.split(":");
|
|
60
|
-
const prefix = parts[0];
|
|
61
|
-
if ((prefix === "shadowob" || prefix === "openclaw-shadowob" || prefix === "shadow") && parts[1] === "channel" && parts[2]) {
|
|
62
|
-
return { channelId: parts[2] };
|
|
63
|
-
}
|
|
64
|
-
if ((prefix === "shadowob" || prefix === "openclaw-shadowob" || prefix === "shadow") && parts[1] === "thread" && parts[2]) {
|
|
65
|
-
return { threadId: parts[2] };
|
|
66
|
-
}
|
|
67
|
-
return { channelId: to };
|
|
68
|
-
}
|
|
69
|
-
function resolveClient(cfg, accountId) {
|
|
70
|
-
const account = getAccountConfig(cfg, accountId ?? DEFAULT_ACCOUNT_ID);
|
|
71
|
-
if (!account) return null;
|
|
72
|
-
return { client: new ShadowClient(account.serverUrl, account.token), account };
|
|
73
|
-
}
|
|
74
|
-
var shadowOutbound = {
|
|
75
|
-
deliveryMode: "direct",
|
|
76
|
-
chunker: null,
|
|
77
|
-
textChunkLimit: 4e3,
|
|
78
|
-
attachedResults: {
|
|
79
|
-
sendText: async (params) => {
|
|
80
|
-
const resolved = resolveClient(params.cfg, params.accountId);
|
|
81
|
-
if (!resolved) throw new Error("Shadow account not configured");
|
|
82
|
-
const { client } = resolved;
|
|
83
|
-
const { channelId, threadId: parsedThreadId } = parseTarget(params.to);
|
|
84
|
-
const threadId = params.threadId ?? parsedThreadId;
|
|
85
|
-
let message;
|
|
86
|
-
if (threadId) {
|
|
87
|
-
message = await client.sendToThread(threadId, params.text);
|
|
88
|
-
} else if (channelId) {
|
|
89
|
-
message = await client.sendMessage(channelId, params.text, {
|
|
90
|
-
replyToId: params.replyToMessageId
|
|
91
|
-
});
|
|
92
|
-
} else {
|
|
93
|
-
throw new Error("Could not resolve target channel or thread");
|
|
94
|
-
}
|
|
95
|
-
return { messageId: message.id };
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
base: {
|
|
99
|
-
sendMedia: async (params) => {
|
|
100
|
-
const resolved = resolveClient(params.cfg, params.accountId);
|
|
101
|
-
if (!resolved) throw new Error("Shadow account not configured");
|
|
102
|
-
const { client } = resolved;
|
|
103
|
-
const { channelId, threadId: parsedThreadId } = parseTarget(params.to);
|
|
104
|
-
const threadId = params.threadId ?? parsedThreadId;
|
|
105
|
-
const mediaUrls = [params.mediaUrl ?? params.filePath, ...params.mediaUrls ?? []].filter(
|
|
106
|
-
Boolean
|
|
107
|
-
);
|
|
108
|
-
const content = params.text || "\u200B";
|
|
109
|
-
let message;
|
|
110
|
-
if (threadId) {
|
|
111
|
-
message = await client.sendToThread(threadId, content);
|
|
112
|
-
} else if (channelId) {
|
|
113
|
-
message = await client.sendMessage(channelId, content, {
|
|
114
|
-
replyToId: params.replyToMessageId
|
|
115
|
-
});
|
|
116
|
-
} else {
|
|
117
|
-
throw new Error("Could not resolve target channel or thread");
|
|
118
|
-
}
|
|
119
|
-
for (const mediaUrl of mediaUrls) {
|
|
120
|
-
try {
|
|
121
|
-
await client.uploadMediaFromUrl(mediaUrl, message.id);
|
|
122
|
-
} catch {
|
|
123
|
-
if (threadId) {
|
|
124
|
-
await client.sendToThread(threadId, mediaUrl);
|
|
125
|
-
} else if (channelId) {
|
|
126
|
-
await client.sendMessage(channelId, mediaUrl);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
// src/channel.ts
|
|
135
|
-
function resolveAccount(cfg, accountId) {
|
|
136
|
-
const account = getAccountConfig(cfg, accountId ?? DEFAULT_ACCOUNT_ID);
|
|
137
|
-
if (!account) {
|
|
138
|
-
return { token: "", serverUrl: "https://shadowob.com", enabled: false };
|
|
139
|
-
}
|
|
140
|
-
return account;
|
|
141
|
-
}
|
|
142
|
-
function inspectAccount(cfg, accountId) {
|
|
143
|
-
const account = getAccountConfig(cfg, accountId ?? DEFAULT_ACCOUNT_ID);
|
|
144
|
-
return {
|
|
145
|
-
enabled: account?.enabled !== false,
|
|
146
|
-
configured: !!account?.token?.trim(),
|
|
147
|
-
tokenStatus: account?.token?.trim() ? "available" : "missing"
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
var shadowPlugin = createChatChannelPlugin({
|
|
151
|
-
base: createChannelPluginBase({
|
|
152
|
-
id: "shadowob",
|
|
153
|
-
config: {
|
|
154
|
-
listAccountIds: (cfg) => listAccountIds(cfg),
|
|
155
|
-
resolveAccount: (cfg, accountId) => {
|
|
156
|
-
return resolveAccount(cfg, accountId);
|
|
157
|
-
},
|
|
158
|
-
defaultAccountId: () => DEFAULT_ACCOUNT_ID,
|
|
159
|
-
isConfigured: (account) => {
|
|
160
|
-
return !!account?.token?.trim();
|
|
161
|
-
},
|
|
162
|
-
isEnabled: (account) => {
|
|
163
|
-
return account?.enabled !== false;
|
|
164
|
-
},
|
|
165
|
-
describeAccount: (account) => ({
|
|
166
|
-
accountId: DEFAULT_ACCOUNT_ID,
|
|
167
|
-
enabled: account?.enabled !== false,
|
|
168
|
-
configured: !!account?.token?.trim()
|
|
169
|
-
})
|
|
170
|
-
},
|
|
171
|
-
setup: {
|
|
172
|
-
resolveAccount,
|
|
173
|
-
inspectAccount
|
|
174
|
-
}
|
|
175
|
-
}),
|
|
176
|
-
// DM security: define allowlist-based DM policy
|
|
177
|
-
security: {
|
|
178
|
-
dm: {
|
|
179
|
-
channelKey: "shadowob",
|
|
180
|
-
resolvePolicy: (account) => {
|
|
181
|
-
return void 0;
|
|
182
|
-
},
|
|
183
|
-
resolveAllowFrom: (_account) => [],
|
|
184
|
-
defaultPolicy: "allowlist"
|
|
185
|
-
}
|
|
186
|
-
},
|
|
187
|
-
// Threading: how replies are delivered (config-driven with fallback)
|
|
188
|
-
threading: {
|
|
189
|
-
topLevelReplyToMode: "reply",
|
|
190
|
-
resolveReplyToMode: ({ cfg }) => {
|
|
191
|
-
const shadow = cfg.channels?.shadowob ?? cfg.channels?.["openclaw-shadowob"];
|
|
192
|
-
const mode = shadow?.replyToMode;
|
|
193
|
-
if (mode === "first" || mode === "all" || mode === "off" || mode === "reply") return mode;
|
|
194
|
-
return "first";
|
|
195
|
-
}
|
|
196
|
-
},
|
|
197
|
-
// Outbound: send messages to the platform
|
|
198
|
-
outbound: shadowOutbound
|
|
199
|
-
// ── Additional adapters (set directly on the plugin object) ──────────────
|
|
200
|
-
// The createChatChannelPlugin helper builds the standard ChannelPlugin.
|
|
201
|
-
// We extend it below with adapters that the helper doesn't cover.
|
|
202
|
-
});
|
|
203
|
-
shadowPlugin.meta = {
|
|
204
|
-
id: "shadowob",
|
|
205
|
-
label: "ShadowOwnBuddy",
|
|
206
|
-
selectionLabel: "ShadowOwnBuddy (Server)",
|
|
207
|
-
docsPath: "/channels/shadowob",
|
|
208
|
-
blurb: "Shadow server channel integration \u2014 chat with AI agents in Shadow channels",
|
|
209
|
-
aliases: ["shadow-server", "openclaw-shadowob"]
|
|
210
|
-
};
|
|
211
|
-
shadowPlugin.capabilities = {
|
|
212
|
-
chatTypes: ["channel", "thread"],
|
|
213
|
-
reactions: true,
|
|
214
|
-
threads: true,
|
|
215
|
-
media: true,
|
|
216
|
-
reply: true,
|
|
217
|
-
edit: true,
|
|
218
|
-
unsend: true
|
|
219
|
-
};
|
|
220
|
-
shadowPlugin.reload = {
|
|
221
|
-
configPrefixes: ["channels.shadowob"]
|
|
222
|
-
};
|
|
223
|
-
shadowPlugin.defaults = {
|
|
224
|
-
queue: { debounceMs: 500 }
|
|
225
|
-
};
|
|
226
|
-
shadowPlugin.configSchema = {
|
|
227
|
-
schema: {
|
|
228
|
-
type: "object",
|
|
229
|
-
properties: {
|
|
230
|
-
token: { type: "string", description: "Agent JWT token" },
|
|
231
|
-
serverUrl: { type: "string", description: "Shadow server URL" },
|
|
232
|
-
enabled: { type: "boolean" },
|
|
233
|
-
accounts: {
|
|
234
|
-
type: "object",
|
|
235
|
-
additionalProperties: {
|
|
236
|
-
type: "object",
|
|
237
|
-
properties: {
|
|
238
|
-
token: { type: "string" },
|
|
239
|
-
serverUrl: { type: "string" },
|
|
240
|
-
enabled: { type: "boolean" }
|
|
241
|
-
},
|
|
242
|
-
required: ["token", "serverUrl"]
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
},
|
|
247
|
-
uiHints: {
|
|
248
|
-
token: {
|
|
249
|
-
label: "Agent Token",
|
|
250
|
-
sensitive: true,
|
|
251
|
-
placeholder: "Paste the JWT token generated in Shadow \u2192 Agents"
|
|
252
|
-
},
|
|
253
|
-
serverUrl: {
|
|
254
|
-
label: "Server URL",
|
|
255
|
-
placeholder: "https://shadowob.com"
|
|
256
|
-
},
|
|
257
|
-
enabled: {
|
|
258
|
-
label: "Enabled"
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
shadowPlugin.agentPrompt = {
|
|
263
|
-
messageToolHints: () => [
|
|
264
|
-
'- Shadow server management: use `action: "get-server"` with `serverId` (slug or UUID) to fetch server info including homepage HTML.',
|
|
265
|
-
'- Shadow homepage decoration: use `action: "update-homepage"` with `serverId` (slug or UUID) and `html` (full HTML string) to update the server\'s homepage. Set `html` to null to reset to default.',
|
|
266
|
-
"- The server slug or ID is provided in the message context as ServerSlug/ServerId when the message originates from a Shadow channel.",
|
|
267
|
-
"- When a user asks to customize/decorate the server homepage, first use `get-server` to see current state, then generate beautiful HTML and use `update-homepage` to apply it.",
|
|
268
|
-
'- Connection diagnostics: use `action: "get-connection-status"` (no params) to probe all configured Shadow accounts and report connection health.'
|
|
269
|
-
]
|
|
270
|
-
};
|
|
271
|
-
shadowPlugin.mentions = {
|
|
272
|
-
stripPatterns: () => ["@[\\w-]+"]
|
|
273
|
-
};
|
|
274
|
-
shadowPlugin.streaming = {
|
|
275
|
-
blockStreamingCoalesceDefaults: { minChars: 1500, idleMs: 1e3 }
|
|
276
|
-
};
|
|
277
|
-
shadowPlugin.messaging = {
|
|
278
|
-
normalizeTarget: (raw) => {
|
|
279
|
-
if (/^(shadowob|openclaw-shadowob):(channel|thread):.+$/i.test(raw)) return raw;
|
|
280
|
-
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(raw)) {
|
|
281
|
-
return `shadowob:channel:${raw}`;
|
|
282
|
-
}
|
|
283
|
-
return void 0;
|
|
284
|
-
},
|
|
285
|
-
targetResolver: {
|
|
286
|
-
looksLikeId: (raw) => /^(shadowob|openclaw-shadowob):(channel|thread):.+$/i.test(raw) || /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(raw),
|
|
287
|
-
hint: "Provide a Shadow channel UUID or shadowob:channel:<uuid>"
|
|
288
|
-
}
|
|
289
|
-
};
|
|
290
|
-
shadowPlugin.status = {
|
|
291
|
-
defaultRuntime: {
|
|
292
|
-
accountId: DEFAULT_ACCOUNT_ID,
|
|
293
|
-
running: false,
|
|
294
|
-
lastStartAt: null,
|
|
295
|
-
lastStopAt: null,
|
|
296
|
-
lastError: null
|
|
297
|
-
},
|
|
298
|
-
probeAccount: async ({
|
|
299
|
-
account,
|
|
300
|
-
timeoutMs
|
|
301
|
-
}) => {
|
|
302
|
-
const controller = new AbortController();
|
|
303
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
304
|
-
try {
|
|
305
|
-
const client = new ShadowClient2(account.serverUrl, account.token);
|
|
306
|
-
const me = await client.getMe();
|
|
307
|
-
return { ok: true, user: me };
|
|
308
|
-
} catch (err) {
|
|
309
|
-
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
310
|
-
} finally {
|
|
311
|
-
clearTimeout(timeout);
|
|
312
|
-
}
|
|
313
|
-
},
|
|
314
|
-
buildAccountSnapshot: ({
|
|
315
|
-
account,
|
|
316
|
-
runtime,
|
|
317
|
-
probe
|
|
318
|
-
}) => ({
|
|
319
|
-
accountId: DEFAULT_ACCOUNT_ID,
|
|
320
|
-
enabled: account?.enabled !== false,
|
|
321
|
-
configured: !!account?.token?.trim(),
|
|
322
|
-
running: runtime?.running ?? false,
|
|
323
|
-
lastStartAt: runtime?.lastStartAt ?? null,
|
|
324
|
-
lastStopAt: runtime?.lastStopAt ?? null,
|
|
325
|
-
lastError: runtime?.lastError ?? null,
|
|
326
|
-
probe
|
|
327
|
-
}),
|
|
328
|
-
buildChannelSummary: ({
|
|
329
|
-
snapshot
|
|
330
|
-
}) => ({
|
|
331
|
-
configured: snapshot.configured ?? false,
|
|
332
|
-
running: snapshot.running ?? false,
|
|
333
|
-
lastStartAt: snapshot.lastStartAt ?? null,
|
|
334
|
-
lastStopAt: snapshot.lastStopAt ?? null,
|
|
335
|
-
lastError: snapshot.lastError ?? null,
|
|
336
|
-
probe: snapshot.probe
|
|
337
|
-
})
|
|
338
|
-
};
|
|
339
|
-
shadowPlugin.gateway = {
|
|
340
|
-
startAccount: async (ctx) => {
|
|
341
|
-
const account = ctx.account;
|
|
342
|
-
const accountId = ctx.accountId;
|
|
343
|
-
ctx.setStatus({
|
|
344
|
-
accountId,
|
|
345
|
-
running: true,
|
|
346
|
-
lastStartAt: Date.now(),
|
|
347
|
-
lastError: null
|
|
348
|
-
});
|
|
349
|
-
ctx.log?.info(`Starting Shadow connection for account ${accountId}`);
|
|
350
|
-
const { monitorShadowProvider } = await import("./monitor-G2J667KY.js");
|
|
351
|
-
await monitorShadowProvider({
|
|
352
|
-
account,
|
|
353
|
-
accountId,
|
|
354
|
-
config: ctx.cfg,
|
|
355
|
-
runtime: {
|
|
356
|
-
log: (msg) => ctx.log?.info(msg),
|
|
357
|
-
error: (msg) => ctx.log?.error(msg)
|
|
358
|
-
},
|
|
359
|
-
abortSignal: ctx.abortSignal
|
|
360
|
-
});
|
|
361
|
-
},
|
|
362
|
-
stopAccount: async (ctx) => {
|
|
363
|
-
ctx.setStatus({
|
|
364
|
-
accountId: ctx.accountId,
|
|
365
|
-
running: false,
|
|
366
|
-
lastStopAt: Date.now()
|
|
367
|
-
});
|
|
368
|
-
ctx.log?.info(`Stopped Shadow connection for account ${ctx.accountId}`);
|
|
369
|
-
}
|
|
370
|
-
};
|
|
371
|
-
var SHADOW_ACTIONS = [
|
|
372
|
-
"send",
|
|
373
|
-
"sendAttachment",
|
|
374
|
-
"react",
|
|
375
|
-
"edit",
|
|
376
|
-
"delete",
|
|
377
|
-
"reply",
|
|
378
|
-
"thread-create",
|
|
379
|
-
"thread-reply",
|
|
380
|
-
"pin",
|
|
381
|
-
"unpin",
|
|
382
|
-
"update-homepage",
|
|
383
|
-
"get-server",
|
|
384
|
-
"get-connection-status"
|
|
385
|
-
];
|
|
386
|
-
shadowPlugin.actions = {
|
|
387
|
-
listActions: () => [...SHADOW_ACTIONS],
|
|
388
|
-
supportsAction: ({ action }) => SHADOW_ACTIONS.includes(action),
|
|
389
|
-
handleAction: async (ctx) => {
|
|
390
|
-
const account = getAccountConfig(ctx.cfg, ctx.accountId ?? DEFAULT_ACCOUNT_ID);
|
|
391
|
-
if (!account) {
|
|
392
|
-
return {
|
|
393
|
-
content: [
|
|
394
|
-
{
|
|
395
|
-
type: "text",
|
|
396
|
-
text: JSON.stringify({ ok: false, error: "Shadow account not configured" })
|
|
397
|
-
}
|
|
398
|
-
]
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
const { action, params } = ctx;
|
|
402
|
-
if (action === "sendAttachment") {
|
|
403
|
-
try {
|
|
404
|
-
const client = new ShadowClient2(account.serverUrl, account.token);
|
|
405
|
-
const to = params.to ?? "";
|
|
406
|
-
const text = params.message ?? params.caption ?? "";
|
|
407
|
-
const filename = params.filename || "file";
|
|
408
|
-
const contentType = params.contentType || params.mimeType || "application/octet-stream";
|
|
409
|
-
const base64Buffer = params.buffer;
|
|
410
|
-
const mediaUrl = params.media ?? params.path ?? params.filePath ?? "";
|
|
411
|
-
const { channelId, threadId: parsedThreadId } = parseTarget(to);
|
|
412
|
-
const threadId = params.threadId ?? parsedThreadId;
|
|
413
|
-
const content = text || "\u200B";
|
|
414
|
-
let message;
|
|
415
|
-
if (threadId) {
|
|
416
|
-
message = await client.sendToThread(threadId, content);
|
|
417
|
-
} else if (channelId) {
|
|
418
|
-
message = await client.sendMessage(channelId, content, {
|
|
419
|
-
replyToId: params.replyTo
|
|
420
|
-
});
|
|
421
|
-
} else {
|
|
422
|
-
return {
|
|
423
|
-
content: [
|
|
424
|
-
{
|
|
425
|
-
type: "text",
|
|
426
|
-
text: JSON.stringify({
|
|
427
|
-
ok: false,
|
|
428
|
-
error: "Could not resolve target channel or thread"
|
|
429
|
-
})
|
|
430
|
-
}
|
|
431
|
-
]
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
if (base64Buffer) {
|
|
435
|
-
const raw = base64Buffer.includes(",") ? base64Buffer.split(",")[1] ?? "" : base64Buffer;
|
|
436
|
-
if (!raw) throw new Error("Invalid base64 attachment payload");
|
|
437
|
-
const bytes = Buffer.from(raw, "base64");
|
|
438
|
-
const blob = new Blob([Uint8Array.from(bytes)], { type: contentType });
|
|
439
|
-
await client.uploadMedia(blob, filename, contentType, message.id);
|
|
440
|
-
} else if (mediaUrl) {
|
|
441
|
-
await client.uploadMediaFromUrl(mediaUrl, message.id);
|
|
442
|
-
} else {
|
|
443
|
-
return {
|
|
444
|
-
content: [
|
|
445
|
-
{
|
|
446
|
-
type: "text",
|
|
447
|
-
text: JSON.stringify({
|
|
448
|
-
ok: false,
|
|
449
|
-
error: "No buffer or media URL provided for attachment"
|
|
450
|
-
})
|
|
451
|
-
}
|
|
452
|
-
]
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
return {
|
|
456
|
-
content: [
|
|
457
|
-
{
|
|
458
|
-
type: "text",
|
|
459
|
-
text: JSON.stringify({
|
|
460
|
-
ok: true,
|
|
461
|
-
action: "sendAttachment",
|
|
462
|
-
messageId: message.id,
|
|
463
|
-
filename
|
|
464
|
-
})
|
|
465
|
-
}
|
|
466
|
-
]
|
|
467
|
-
};
|
|
468
|
-
} catch (err) {
|
|
469
|
-
return {
|
|
470
|
-
content: [
|
|
471
|
-
{
|
|
472
|
-
type: "text",
|
|
473
|
-
text: JSON.stringify({
|
|
474
|
-
ok: false,
|
|
475
|
-
error: err instanceof Error ? err.message : String(err)
|
|
476
|
-
})
|
|
477
|
-
}
|
|
478
|
-
]
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
if (action === "react") {
|
|
483
|
-
const client = new ShadowClient2(account.serverUrl, account.token);
|
|
484
|
-
const messageId = params.messageId ?? params.message_id ?? "";
|
|
485
|
-
const emoji = params.emoji ?? params.reaction ?? "";
|
|
486
|
-
if (!messageId || !emoji) {
|
|
487
|
-
return {
|
|
488
|
-
content: [
|
|
489
|
-
{
|
|
490
|
-
type: "text",
|
|
491
|
-
text: JSON.stringify({ ok: false, error: "messageId and emoji are required" })
|
|
492
|
-
}
|
|
493
|
-
]
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
try {
|
|
497
|
-
await client.addReaction(messageId, emoji);
|
|
498
|
-
return {
|
|
499
|
-
content: [
|
|
500
|
-
{
|
|
501
|
-
type: "text",
|
|
502
|
-
text: JSON.stringify({ ok: true, action: "react", messageId, emoji })
|
|
503
|
-
}
|
|
504
|
-
]
|
|
505
|
-
};
|
|
506
|
-
} catch (err) {
|
|
507
|
-
return {
|
|
508
|
-
content: [
|
|
509
|
-
{ type: "text", text: JSON.stringify({ ok: false, error: String(err) }) }
|
|
510
|
-
]
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
if (action === "edit") {
|
|
515
|
-
const client = new ShadowClient2(account.serverUrl, account.token);
|
|
516
|
-
const messageId = params.messageId ?? params.message_id ?? "";
|
|
517
|
-
const content = params.message ?? params.content ?? "";
|
|
518
|
-
if (!messageId || !content) {
|
|
519
|
-
return {
|
|
520
|
-
content: [
|
|
521
|
-
{
|
|
522
|
-
type: "text",
|
|
523
|
-
text: JSON.stringify({ ok: false, error: "messageId and content are required" })
|
|
524
|
-
}
|
|
525
|
-
]
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
try {
|
|
529
|
-
await client.editMessage(messageId, content);
|
|
530
|
-
return {
|
|
531
|
-
content: [
|
|
532
|
-
{
|
|
533
|
-
type: "text",
|
|
534
|
-
text: JSON.stringify({ ok: true, action: "edit", messageId })
|
|
535
|
-
}
|
|
536
|
-
]
|
|
537
|
-
};
|
|
538
|
-
} catch (err) {
|
|
539
|
-
return {
|
|
540
|
-
content: [
|
|
541
|
-
{ type: "text", text: JSON.stringify({ ok: false, error: String(err) }) }
|
|
542
|
-
]
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
if (action === "delete") {
|
|
547
|
-
const client = new ShadowClient2(account.serverUrl, account.token);
|
|
548
|
-
const messageId = params.messageId ?? params.message_id ?? "";
|
|
549
|
-
if (!messageId) {
|
|
550
|
-
return {
|
|
551
|
-
content: [
|
|
552
|
-
{
|
|
553
|
-
type: "text",
|
|
554
|
-
text: JSON.stringify({ ok: false, error: "messageId is required" })
|
|
555
|
-
}
|
|
556
|
-
]
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
|
-
try {
|
|
560
|
-
await client.deleteMessage(messageId);
|
|
561
|
-
return {
|
|
562
|
-
content: [
|
|
563
|
-
{
|
|
564
|
-
type: "text",
|
|
565
|
-
text: JSON.stringify({ ok: true, action: "delete", messageId })
|
|
566
|
-
}
|
|
567
|
-
]
|
|
568
|
-
};
|
|
569
|
-
} catch (err) {
|
|
570
|
-
return {
|
|
571
|
-
content: [
|
|
572
|
-
{ type: "text", text: JSON.stringify({ ok: false, error: String(err) }) }
|
|
573
|
-
]
|
|
574
|
-
};
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
if (action === "pin" || action === "unpin") {
|
|
578
|
-
return {
|
|
579
|
-
content: [
|
|
580
|
-
{
|
|
581
|
-
type: "text",
|
|
582
|
-
text: JSON.stringify({
|
|
583
|
-
ok: false,
|
|
584
|
-
error: `${action} is not yet supported for Shadow channels`
|
|
585
|
-
})
|
|
586
|
-
}
|
|
587
|
-
]
|
|
588
|
-
};
|
|
589
|
-
}
|
|
590
|
-
if (action === "get-server") {
|
|
591
|
-
const serverId = params.serverId ?? params.server_id ?? params.server ?? "";
|
|
592
|
-
if (!serverId) {
|
|
593
|
-
return {
|
|
594
|
-
content: [
|
|
595
|
-
{
|
|
596
|
-
type: "text",
|
|
597
|
-
text: JSON.stringify({ ok: false, error: "serverId is required" })
|
|
598
|
-
}
|
|
599
|
-
]
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
try {
|
|
603
|
-
const client = new ShadowClient2(account.serverUrl, account.token);
|
|
604
|
-
const server = await client.getServer(serverId);
|
|
605
|
-
return {
|
|
606
|
-
content: [
|
|
607
|
-
{
|
|
608
|
-
type: "text",
|
|
609
|
-
text: JSON.stringify({ ok: true, action: "get-server", server })
|
|
610
|
-
}
|
|
611
|
-
]
|
|
612
|
-
};
|
|
613
|
-
} catch (err) {
|
|
614
|
-
return {
|
|
615
|
-
content: [
|
|
616
|
-
{ type: "text", text: JSON.stringify({ ok: false, error: String(err) }) }
|
|
617
|
-
]
|
|
618
|
-
};
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
if (action === "update-homepage") {
|
|
622
|
-
const serverId = params.serverId ?? params.server_id ?? params.server ?? "";
|
|
623
|
-
const html = params.html ?? params.homepageHtml ?? params.homepage_html ?? null;
|
|
624
|
-
if (!serverId) {
|
|
625
|
-
return {
|
|
626
|
-
content: [
|
|
627
|
-
{
|
|
628
|
-
type: "text",
|
|
629
|
-
text: JSON.stringify({ ok: false, error: "serverId is required" })
|
|
630
|
-
}
|
|
631
|
-
]
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
try {
|
|
635
|
-
const client = new ShadowClient2(account.serverUrl, account.token);
|
|
636
|
-
const result = await client.updateServerHomepage(serverId, html);
|
|
637
|
-
return {
|
|
638
|
-
content: [
|
|
639
|
-
{
|
|
640
|
-
type: "text",
|
|
641
|
-
text: JSON.stringify({
|
|
642
|
-
ok: true,
|
|
643
|
-
action: "update-homepage",
|
|
644
|
-
serverId: result.id,
|
|
645
|
-
slug: result.slug,
|
|
646
|
-
homepageHtml: result.homepageHtml ? `(${result.homepageHtml.length} chars)` : null
|
|
647
|
-
})
|
|
648
|
-
}
|
|
649
|
-
]
|
|
650
|
-
};
|
|
651
|
-
} catch (err) {
|
|
652
|
-
return {
|
|
653
|
-
content: [
|
|
654
|
-
{ type: "text", text: JSON.stringify({ ok: false, error: String(err) }) }
|
|
655
|
-
]
|
|
656
|
-
};
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
if (action === "get-connection-status") {
|
|
660
|
-
const accountIds = listAccountIds(ctx.cfg);
|
|
661
|
-
const results = await Promise.all(
|
|
662
|
-
accountIds.map(async (id) => {
|
|
663
|
-
const acc = getAccountConfig(ctx.cfg, id);
|
|
664
|
-
if (!acc) return { accountId: id, configured: false, ok: false, error: "not configured" };
|
|
665
|
-
if (!acc.token?.trim())
|
|
666
|
-
return { accountId: id, configured: false, ok: false, error: "no token" };
|
|
667
|
-
try {
|
|
668
|
-
const client = new ShadowClient2(acc.serverUrl, acc.token);
|
|
669
|
-
const me = await client.getMe();
|
|
670
|
-
return {
|
|
671
|
-
accountId: id,
|
|
672
|
-
configured: true,
|
|
673
|
-
enabled: acc.enabled !== false,
|
|
674
|
-
ok: true,
|
|
675
|
-
serverUrl: acc.serverUrl,
|
|
676
|
-
user: me
|
|
677
|
-
};
|
|
678
|
-
} catch (err) {
|
|
679
|
-
return {
|
|
680
|
-
accountId: id,
|
|
681
|
-
configured: true,
|
|
682
|
-
enabled: acc.enabled !== false,
|
|
683
|
-
ok: false,
|
|
684
|
-
serverUrl: acc.serverUrl,
|
|
685
|
-
error: err instanceof Error ? err.message : String(err)
|
|
686
|
-
};
|
|
687
|
-
}
|
|
688
|
-
})
|
|
689
|
-
);
|
|
690
|
-
return {
|
|
691
|
-
content: [
|
|
692
|
-
{
|
|
693
|
-
type: "text",
|
|
694
|
-
text: JSON.stringify({ ok: true, action: "get-connection-status", accounts: results })
|
|
695
|
-
}
|
|
696
|
-
]
|
|
697
|
-
};
|
|
698
|
-
}
|
|
699
|
-
return {
|
|
700
|
-
content: [
|
|
701
|
-
{
|
|
702
|
-
type: "text",
|
|
703
|
-
text: JSON.stringify({ ok: false, error: `Action ${action} not yet implemented` })
|
|
704
|
-
}
|
|
705
|
-
]
|
|
706
|
-
};
|
|
707
|
-
}
|
|
708
|
-
};
|
|
709
|
-
|
|
710
|
-
export {
|
|
711
|
-
shadowPlugin
|
|
712
|
-
};
|