@aigne/afs-mattermost 1.11.0-beta.12
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.md +26 -0
- package/dist/index.cjs +487 -0
- package/dist/index.d.cts +159 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +159 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +488 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +56 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Proprietary License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2025 ArcBlock, Inc. All Rights Reserved.
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") are proprietary
|
|
6
|
+
and confidential. Unauthorized copying, modification, distribution, or use of
|
|
7
|
+
this Software, via any medium, is strictly prohibited.
|
|
8
|
+
|
|
9
|
+
The Software is provided for internal use only within ArcBlock, Inc. and its
|
|
10
|
+
authorized affiliates.
|
|
11
|
+
|
|
12
|
+
## No License Granted
|
|
13
|
+
|
|
14
|
+
No license, express or implied, is granted to any party for any purpose.
|
|
15
|
+
All rights are reserved by ArcBlock, Inc.
|
|
16
|
+
|
|
17
|
+
## Public Artifact Distribution
|
|
18
|
+
|
|
19
|
+
Portions of this Software may be released publicly under separate open-source
|
|
20
|
+
licenses (such as MIT License) through designated public repositories. Such
|
|
21
|
+
public releases are governed by their respective licenses and do not affect
|
|
22
|
+
the proprietary nature of this repository.
|
|
23
|
+
|
|
24
|
+
## Contact
|
|
25
|
+
|
|
26
|
+
For licensing inquiries, contact: legal@arcblock.io
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
let _aigne_afs_provider = require("@aigne/afs/provider");
|
|
2
|
+
let _aigne_afs_messaging = require("@aigne/afs-messaging");
|
|
3
|
+
|
|
4
|
+
//#region src/client.ts
|
|
5
|
+
/**
|
|
6
|
+
* MattermostClient — pure Mattermost REST API wrapper.
|
|
7
|
+
*
|
|
8
|
+
* Zero external dependencies (uses native fetch).
|
|
9
|
+
* No AFS or application-specific concepts.
|
|
10
|
+
* WebSocket is handled by the provider, not the client.
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_API_BASE = "https://mattermost.example.com/api/v4";
|
|
13
|
+
const DEFAULT_TIMEOUT = 3e4;
|
|
14
|
+
var MattermostClient = class {
|
|
15
|
+
_token;
|
|
16
|
+
_apiBase;
|
|
17
|
+
_timeout;
|
|
18
|
+
constructor(options) {
|
|
19
|
+
if (!options.token) throw new Error("MattermostClient requires a token");
|
|
20
|
+
this._token = options.token;
|
|
21
|
+
this._apiBase = options.apiBase ?? DEFAULT_API_BASE;
|
|
22
|
+
this._timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
23
|
+
}
|
|
24
|
+
async sendMessage(channelId, text) {
|
|
25
|
+
return this._call("POST", "/posts", {
|
|
26
|
+
channel_id: channelId,
|
|
27
|
+
message: text
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async sendTyping(channelId) {
|
|
31
|
+
await this._call("POST", "/users/me/typing", { channel_id: channelId });
|
|
32
|
+
}
|
|
33
|
+
async getCurrentUser() {
|
|
34
|
+
return this._call("GET", "/users/me");
|
|
35
|
+
}
|
|
36
|
+
async _call(method, path, body) {
|
|
37
|
+
const headers = { Authorization: `Bearer ${this._token}` };
|
|
38
|
+
const init = {
|
|
39
|
+
method,
|
|
40
|
+
headers,
|
|
41
|
+
signal: AbortSignal.timeout(this._timeout)
|
|
42
|
+
};
|
|
43
|
+
if (body) {
|
|
44
|
+
headers["Content-Type"] = "application/json";
|
|
45
|
+
init.body = JSON.stringify(body);
|
|
46
|
+
}
|
|
47
|
+
const response = await fetch(`${this._apiBase}${path}`, init);
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
const desc = (await response.text().catch(() => "")).replace(this._token, "***");
|
|
50
|
+
throw new Error(`Mattermost API error ${response.status}: ${desc}`);
|
|
51
|
+
}
|
|
52
|
+
return response.json();
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region \0@oxc-project+runtime@0.108.0/helpers/decorate.js
|
|
58
|
+
function __decorate(decorators, target, key, desc) {
|
|
59
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
60
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
61
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
62
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/index.ts
|
|
67
|
+
var AFSMattermost = class extends _aigne_afs_messaging.BaseMessageProvider {
|
|
68
|
+
static manifest() {
|
|
69
|
+
return {
|
|
70
|
+
name: "mattermost",
|
|
71
|
+
description: "Mattermost messaging — bidirectional via WebSocket events.\n- Self-hosted team messaging with REST + WebSocket API\n- Multi-bot support with per-bot channel monitoring\n- Path: /:bot/conversations/:channelId/messages/:msgId",
|
|
72
|
+
uriTemplate: "mattermost://{serverUrl}",
|
|
73
|
+
category: "messaging",
|
|
74
|
+
schema: {
|
|
75
|
+
type: "object",
|
|
76
|
+
properties: {
|
|
77
|
+
serverUrl: {
|
|
78
|
+
type: "string",
|
|
79
|
+
description: "Mattermost server URL"
|
|
80
|
+
},
|
|
81
|
+
token: {
|
|
82
|
+
type: "string",
|
|
83
|
+
description: "Personal access token or bot token",
|
|
84
|
+
sensitive: true
|
|
85
|
+
},
|
|
86
|
+
channels: {
|
|
87
|
+
type: "array",
|
|
88
|
+
items: { type: "string" },
|
|
89
|
+
description: "Channel IDs to monitor"
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
required: ["serverUrl", "token"]
|
|
93
|
+
},
|
|
94
|
+
tags: [
|
|
95
|
+
"mattermost",
|
|
96
|
+
"messaging",
|
|
97
|
+
"chat",
|
|
98
|
+
"self-hosted"
|
|
99
|
+
],
|
|
100
|
+
capabilityTags: [
|
|
101
|
+
"read-write",
|
|
102
|
+
"crud",
|
|
103
|
+
"search",
|
|
104
|
+
"auth:token",
|
|
105
|
+
"remote",
|
|
106
|
+
"http",
|
|
107
|
+
"on-premise",
|
|
108
|
+
"real-time"
|
|
109
|
+
],
|
|
110
|
+
security: {
|
|
111
|
+
riskLevel: "external",
|
|
112
|
+
resourceAccess: ["internet"],
|
|
113
|
+
notes: ["Connects to self-hosted Mattermost instance — requires access token"]
|
|
114
|
+
},
|
|
115
|
+
capabilities: { network: { egress: true } }
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
static treeSchema() {
|
|
119
|
+
return {
|
|
120
|
+
operations: [
|
|
121
|
+
"list",
|
|
122
|
+
"read",
|
|
123
|
+
"exec",
|
|
124
|
+
"stat",
|
|
125
|
+
"explain"
|
|
126
|
+
],
|
|
127
|
+
tree: {
|
|
128
|
+
"/": {
|
|
129
|
+
kind: "messaging:root",
|
|
130
|
+
operations: ["list", "exec"],
|
|
131
|
+
actions: [
|
|
132
|
+
"add-bot",
|
|
133
|
+
"remove-bot",
|
|
134
|
+
"start",
|
|
135
|
+
"stop",
|
|
136
|
+
"process-event"
|
|
137
|
+
]
|
|
138
|
+
},
|
|
139
|
+
"/{bot}": {
|
|
140
|
+
kind: "messaging:bot",
|
|
141
|
+
operations: ["list", "read"]
|
|
142
|
+
},
|
|
143
|
+
"/{bot}/ctl": {
|
|
144
|
+
kind: "messaging:status",
|
|
145
|
+
operations: ["read"]
|
|
146
|
+
},
|
|
147
|
+
"/{bot}/conversations": {
|
|
148
|
+
kind: "messaging:conversations",
|
|
149
|
+
operations: ["list"]
|
|
150
|
+
},
|
|
151
|
+
"/{bot}/conversations/{convId}": {
|
|
152
|
+
kind: "messaging:conversation",
|
|
153
|
+
operations: ["list"]
|
|
154
|
+
},
|
|
155
|
+
"/{bot}/conversations/{convId}/messages": {
|
|
156
|
+
kind: "messaging:messages",
|
|
157
|
+
operations: ["list", "exec"],
|
|
158
|
+
actions: ["send"]
|
|
159
|
+
},
|
|
160
|
+
"/{bot}/conversations/{convId}/messages/{msgId}": {
|
|
161
|
+
kind: "messaging:message",
|
|
162
|
+
operations: ["read"]
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
auth: {
|
|
166
|
+
type: "token",
|
|
167
|
+
env: ["MATTERMOST_TOKEN"]
|
|
168
|
+
},
|
|
169
|
+
bestFor: [
|
|
170
|
+
"self-hosted chat",
|
|
171
|
+
"team messaging",
|
|
172
|
+
"devops alerts"
|
|
173
|
+
],
|
|
174
|
+
notFor: ["file storage", "database queries"]
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
providerName = "mattermost";
|
|
178
|
+
eventPrefix = "mattermost";
|
|
179
|
+
_wsStates = /* @__PURE__ */ new Map();
|
|
180
|
+
constructor(options) {
|
|
181
|
+
let bots;
|
|
182
|
+
if (options.bots) {
|
|
183
|
+
bots = options.bots;
|
|
184
|
+
for (const bot of bots) if (!bot.token) throw new Error(`AFSMattermost bot "${bot.name}" requires a token`);
|
|
185
|
+
} else {
|
|
186
|
+
if (!options.token) throw new Error("AFSMattermost requires a token");
|
|
187
|
+
bots = [{
|
|
188
|
+
name: "default",
|
|
189
|
+
token: options.token,
|
|
190
|
+
conversations: options.channels ?? [],
|
|
191
|
+
apiBase: options.apiBase
|
|
192
|
+
}];
|
|
193
|
+
}
|
|
194
|
+
super({
|
|
195
|
+
bots,
|
|
196
|
+
bufferSize: options.bufferSize
|
|
197
|
+
});
|
|
198
|
+
for (const bot of bots) this._wsStates.set(bot.name, {
|
|
199
|
+
ws: null,
|
|
200
|
+
connected: false,
|
|
201
|
+
disposed: false,
|
|
202
|
+
seq: 1,
|
|
203
|
+
heartbeatTimer: null,
|
|
204
|
+
backoff: 1e3
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
getMessageCapabilities() {
|
|
208
|
+
return {
|
|
209
|
+
formats: {
|
|
210
|
+
send: ["text", "markdown"],
|
|
211
|
+
receive: ["text"]
|
|
212
|
+
},
|
|
213
|
+
maxMessageLength: 16383,
|
|
214
|
+
features: {
|
|
215
|
+
edit: true,
|
|
216
|
+
delete: true,
|
|
217
|
+
reply: true,
|
|
218
|
+
thread: true,
|
|
219
|
+
reaction: true,
|
|
220
|
+
inlineKeyboard: false
|
|
221
|
+
},
|
|
222
|
+
limits: { messagesPerSecond: 10 }
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
createBotClient(config) {
|
|
226
|
+
return new MattermostClient({
|
|
227
|
+
token: config.token,
|
|
228
|
+
apiBase: config.apiBase
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
async sendMessage(client, convId, text, _opts) {
|
|
232
|
+
return { messageId: (await client.sendMessage(convId, text)).id };
|
|
233
|
+
}
|
|
234
|
+
async sendTypingIndicator(client, convId) {
|
|
235
|
+
await client.sendTyping(convId);
|
|
236
|
+
}
|
|
237
|
+
normalizeMessage(raw) {
|
|
238
|
+
const msg = raw;
|
|
239
|
+
return {
|
|
240
|
+
id: msg.id ?? "0",
|
|
241
|
+
text: String(msg.message ?? ""),
|
|
242
|
+
from: this.normalizeSender({ id: msg.user_id }),
|
|
243
|
+
timestamp: msg.create_at ? Math.floor(msg.create_at / 1e3) : Math.floor(Date.now() / 1e3),
|
|
244
|
+
conversationId: String(msg.channel_id ?? ""),
|
|
245
|
+
platform: { root_id: msg.root_id }
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
normalizeSender(raw) {
|
|
249
|
+
return {
|
|
250
|
+
id: String(raw.id ?? "0"),
|
|
251
|
+
name: String(raw.username ?? raw.name ?? ""),
|
|
252
|
+
isBot: raw.is_bot
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
start(botName) {
|
|
256
|
+
const names = botName ? [botName] : [...this._wsStates.keys()];
|
|
257
|
+
for (const name of names) {
|
|
258
|
+
const state = this._wsStates.get(name);
|
|
259
|
+
if (!state || state.connected || state.disposed) continue;
|
|
260
|
+
this._connectWebSocket(name);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
stop(botName) {
|
|
264
|
+
const names = botName ? [botName] : [...this._wsStates.keys()];
|
|
265
|
+
for (const name of names) this._disconnectWebSocket(name);
|
|
266
|
+
}
|
|
267
|
+
dispose() {
|
|
268
|
+
for (const name of this._wsStates.keys()) {
|
|
269
|
+
const state = this._wsStates.get(name);
|
|
270
|
+
state.disposed = true;
|
|
271
|
+
this._disconnectWebSocket(name);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
async listRootActions(_ctx) {
|
|
275
|
+
return { data: [
|
|
276
|
+
this.buildEntry("/.actions/add-bot", { meta: { description: "Add a bot instance at runtime" } }),
|
|
277
|
+
this.buildEntry("/.actions/remove-bot", { meta: { description: "Remove a bot instance" } }),
|
|
278
|
+
this.buildEntry("/.actions/start", { meta: { description: "Connect to Mattermost WebSocket" } }),
|
|
279
|
+
this.buildEntry("/.actions/stop", { meta: { description: "Disconnect from WebSocket" } }),
|
|
280
|
+
this.buildEntry("/.actions/process-event", { meta: { description: "Inject a Mattermost event externally" } })
|
|
281
|
+
] };
|
|
282
|
+
}
|
|
283
|
+
async execStart(_ctx) {
|
|
284
|
+
this.start();
|
|
285
|
+
return {
|
|
286
|
+
success: true,
|
|
287
|
+
data: {
|
|
288
|
+
ok: true,
|
|
289
|
+
websocket: true
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
async execStop(_ctx) {
|
|
294
|
+
this.stop();
|
|
295
|
+
return {
|
|
296
|
+
success: true,
|
|
297
|
+
data: {
|
|
298
|
+
ok: true,
|
|
299
|
+
websocket: false
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
async execProcessEvent(_ctx, args) {
|
|
304
|
+
this._processEvent(args);
|
|
305
|
+
return {
|
|
306
|
+
success: true,
|
|
307
|
+
data: { ok: true }
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
async _connectWebSocket(botName) {
|
|
311
|
+
const state = this._wsStates.get(botName);
|
|
312
|
+
if (!state || state.disposed) return;
|
|
313
|
+
const botConfig = this._pendingBots?.find((b) => b.name === botName);
|
|
314
|
+
const token = botConfig?.token;
|
|
315
|
+
const apiBase = botConfig?.apiBase ?? "https://mattermost.example.com/api/v4";
|
|
316
|
+
if (!token) return;
|
|
317
|
+
try {
|
|
318
|
+
const wsUrl = `${apiBase.replace(/^http/, "ws").replace(/\/api\/v4$/, "")}/api/v4/websocket`;
|
|
319
|
+
const ws = new WebSocket(wsUrl);
|
|
320
|
+
state.ws = ws;
|
|
321
|
+
ws.onopen = () => {
|
|
322
|
+
state.ws?.send(JSON.stringify({
|
|
323
|
+
seq: state.seq++,
|
|
324
|
+
action: "authentication_challenge",
|
|
325
|
+
data: { token }
|
|
326
|
+
}));
|
|
327
|
+
state.connected = true;
|
|
328
|
+
state.backoff = 1e3;
|
|
329
|
+
};
|
|
330
|
+
ws.onmessage = (event) => {
|
|
331
|
+
try {
|
|
332
|
+
const payload = JSON.parse(String(event.data));
|
|
333
|
+
this._handleWSEvent(botName, payload);
|
|
334
|
+
} catch {}
|
|
335
|
+
};
|
|
336
|
+
ws.onclose = () => {
|
|
337
|
+
this._cleanupWebSocket(botName);
|
|
338
|
+
if (!state.disposed) {
|
|
339
|
+
setTimeout(() => this._connectWebSocket(botName), state.backoff);
|
|
340
|
+
state.backoff = Math.min(state.backoff * 2, 6e4);
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
ws.onerror = () => {};
|
|
344
|
+
} catch {
|
|
345
|
+
if (!state.disposed) {
|
|
346
|
+
setTimeout(() => this._connectWebSocket(botName), state.backoff);
|
|
347
|
+
state.backoff = Math.min(state.backoff * 2, 6e4);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
_handleWSEvent(botName, payload) {
|
|
352
|
+
if (!this._wsStates.get(botName)) return;
|
|
353
|
+
if (payload.event === "posted") {
|
|
354
|
+
const postStr = payload.data.post;
|
|
355
|
+
if (!postStr) return;
|
|
356
|
+
let post;
|
|
357
|
+
try {
|
|
358
|
+
post = JSON.parse(postStr);
|
|
359
|
+
} catch {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const text = String(post.message ?? "").trim();
|
|
363
|
+
if (!text) return;
|
|
364
|
+
if (post.type && post.type !== "") return;
|
|
365
|
+
const channelId = String(post.channel_id ?? payload.broadcast?.channel_id ?? "");
|
|
366
|
+
this.emitMessageReceived(botName, {
|
|
367
|
+
id: String(post.id),
|
|
368
|
+
text,
|
|
369
|
+
from: this.normalizeSender({ id: post.user_id }),
|
|
370
|
+
timestamp: post.create_at ? Math.floor(post.create_at / 1e3) : Math.floor(Date.now() / 1e3),
|
|
371
|
+
conversationId: channelId,
|
|
372
|
+
platform: { root_id: post.root_id }
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
_disconnectWebSocket(botName) {
|
|
377
|
+
const state = this._wsStates.get(botName);
|
|
378
|
+
if (!state) return;
|
|
379
|
+
if (state.heartbeatTimer) {
|
|
380
|
+
clearInterval(state.heartbeatTimer);
|
|
381
|
+
state.heartbeatTimer = null;
|
|
382
|
+
}
|
|
383
|
+
if (state.ws) {
|
|
384
|
+
state.ws.onclose = null;
|
|
385
|
+
state.ws.onmessage = null;
|
|
386
|
+
state.ws.onerror = null;
|
|
387
|
+
state.ws.close();
|
|
388
|
+
state.ws = null;
|
|
389
|
+
}
|
|
390
|
+
state.connected = false;
|
|
391
|
+
}
|
|
392
|
+
_cleanupWebSocket(botName) {
|
|
393
|
+
const state = this._wsStates.get(botName);
|
|
394
|
+
if (!state) return;
|
|
395
|
+
if (state.heartbeatTimer) {
|
|
396
|
+
clearInterval(state.heartbeatTimer);
|
|
397
|
+
state.heartbeatTimer = null;
|
|
398
|
+
}
|
|
399
|
+
state.ws = null;
|
|
400
|
+
state.connected = false;
|
|
401
|
+
}
|
|
402
|
+
/** Process an externally-provided Mattermost event. Public for testing. */
|
|
403
|
+
_processEvent(payload) {
|
|
404
|
+
const botName = payload.botName ?? this._botOrder[0] ?? "default";
|
|
405
|
+
const event = payload.event;
|
|
406
|
+
const data = payload.data ?? payload;
|
|
407
|
+
if (event === "posted" || data.message !== void 0) {
|
|
408
|
+
let post;
|
|
409
|
+
if (typeof data.post === "string") try {
|
|
410
|
+
post = JSON.parse(data.post);
|
|
411
|
+
} catch {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
else if (data.post && typeof data.post === "object") post = data.post;
|
|
415
|
+
else post = data;
|
|
416
|
+
const text = String(post.message ?? "").trim();
|
|
417
|
+
if (!text) return;
|
|
418
|
+
if (post.type && post.type !== "") return;
|
|
419
|
+
const channelId = String(post.channel_id ?? payload.broadcast?.channel_id ?? "");
|
|
420
|
+
this.emitMessageReceived(botName, {
|
|
421
|
+
id: String(post.id ?? Date.now()),
|
|
422
|
+
text,
|
|
423
|
+
from: this.normalizeSender({
|
|
424
|
+
id: post.user_id ?? "0",
|
|
425
|
+
username: post.username ?? ""
|
|
426
|
+
}),
|
|
427
|
+
timestamp: post.create_at ? Math.floor(post.create_at / 1e3) : Math.floor(Date.now() / 1e3),
|
|
428
|
+
conversationId: channelId,
|
|
429
|
+
platform: { root_id: post.root_id }
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/** Add a channel to a bot. Defaults to first bot. */
|
|
434
|
+
addChannel(channelId, botName) {
|
|
435
|
+
const name = botName ?? this._botOrder[0];
|
|
436
|
+
if (!name) return;
|
|
437
|
+
const convs = this._botConversations.get(name);
|
|
438
|
+
if (!convs || convs.has(channelId)) return;
|
|
439
|
+
convs.add(channelId);
|
|
440
|
+
const buffers = this._botBuffers.get(name);
|
|
441
|
+
if (buffers && !buffers.has(channelId)) buffers.set(channelId, []);
|
|
442
|
+
}
|
|
443
|
+
/** Remove a channel from a bot. */
|
|
444
|
+
removeChannel(channelId, botName) {
|
|
445
|
+
const name = botName ?? this._botOrder[0];
|
|
446
|
+
if (!name) return;
|
|
447
|
+
const convs = this._botConversations.get(name);
|
|
448
|
+
if (convs) convs.delete(channelId);
|
|
449
|
+
const buffers = this._botBuffers.get(name);
|
|
450
|
+
if (buffers) buffers.delete(channelId);
|
|
451
|
+
}
|
|
452
|
+
/** Add a message to the ring buffer directly. Public for testing/conformance. */
|
|
453
|
+
_addToBuffer(channelId, msg) {
|
|
454
|
+
const botName = this._botOrder[0] ?? "default";
|
|
455
|
+
const convs = this._botConversations.get(botName);
|
|
456
|
+
if (convs && !convs.has(channelId)) convs.add(channelId);
|
|
457
|
+
let botBuffers = this._botBuffers.get(botName);
|
|
458
|
+
if (!botBuffers) {
|
|
459
|
+
botBuffers = /* @__PURE__ */ new Map();
|
|
460
|
+
this._botBuffers.set(botName, botBuffers);
|
|
461
|
+
}
|
|
462
|
+
let buffer = botBuffers.get(channelId);
|
|
463
|
+
if (!buffer) {
|
|
464
|
+
buffer = [];
|
|
465
|
+
botBuffers.set(channelId, buffer);
|
|
466
|
+
}
|
|
467
|
+
buffer.push({
|
|
468
|
+
id: msg.id,
|
|
469
|
+
text: msg.message,
|
|
470
|
+
from: {
|
|
471
|
+
id: msg.user_id,
|
|
472
|
+
name: msg.username ?? ""
|
|
473
|
+
},
|
|
474
|
+
timestamp: msg.create_at ? Math.floor(msg.create_at / 1e3) : Math.floor(Date.now() / 1e3),
|
|
475
|
+
conversationId: channelId,
|
|
476
|
+
platform: {}
|
|
477
|
+
});
|
|
478
|
+
while (buffer.length > this._bufferSize) buffer.shift();
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
__decorate([(0, _aigne_afs_provider.Actions)("/")], AFSMattermost.prototype, "listRootActions", null);
|
|
482
|
+
__decorate([_aigne_afs_provider.Actions.Exec("/", "start")], AFSMattermost.prototype, "execStart", null);
|
|
483
|
+
__decorate([_aigne_afs_provider.Actions.Exec("/", "stop")], AFSMattermost.prototype, "execStop", null);
|
|
484
|
+
__decorate([_aigne_afs_provider.Actions.Exec("/", "process-event")], AFSMattermost.prototype, "execProcessEvent", null);
|
|
485
|
+
|
|
486
|
+
//#endregion
|
|
487
|
+
exports.AFSMattermost = AFSMattermost;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { AFSExecResult, AFSListResult, ProviderTreeSchema } from "@aigne/afs";
|
|
2
|
+
import { RouteContext } from "@aigne/afs/provider";
|
|
3
|
+
import { BaseMessageProvider, BotConfig, BufferedMessage, MessageCapabilities, MessageSender, SendOptions } from "@aigne/afs-messaging";
|
|
4
|
+
|
|
5
|
+
//#region src/client.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* MattermostClient — pure Mattermost REST API wrapper.
|
|
8
|
+
*
|
|
9
|
+
* Zero external dependencies (uses native fetch).
|
|
10
|
+
* No AFS or application-specific concepts.
|
|
11
|
+
* WebSocket is handled by the provider, not the client.
|
|
12
|
+
*/
|
|
13
|
+
interface MattermostClientOptions {
|
|
14
|
+
token: string;
|
|
15
|
+
apiBase?: string;
|
|
16
|
+
timeout?: number;
|
|
17
|
+
}
|
|
18
|
+
/** Mattermost post object (subset). */
|
|
19
|
+
interface MattermostPost {
|
|
20
|
+
id: string;
|
|
21
|
+
channel_id: string;
|
|
22
|
+
user_id: string;
|
|
23
|
+
message: string;
|
|
24
|
+
create_at: number;
|
|
25
|
+
update_at?: number;
|
|
26
|
+
root_id?: string;
|
|
27
|
+
type?: string;
|
|
28
|
+
}
|
|
29
|
+
/** Mattermost user object (subset). */
|
|
30
|
+
interface MattermostUser {
|
|
31
|
+
id: string;
|
|
32
|
+
username: string;
|
|
33
|
+
email?: string;
|
|
34
|
+
first_name?: string;
|
|
35
|
+
last_name?: string;
|
|
36
|
+
nickname?: string;
|
|
37
|
+
roles?: string;
|
|
38
|
+
}
|
|
39
|
+
/** Mattermost WebSocket event envelope. */
|
|
40
|
+
interface MattermostWSEvent {
|
|
41
|
+
event: string;
|
|
42
|
+
data: Record<string, unknown>;
|
|
43
|
+
broadcast?: {
|
|
44
|
+
channel_id?: string;
|
|
45
|
+
team_id?: string;
|
|
46
|
+
user_id?: string;
|
|
47
|
+
};
|
|
48
|
+
seq: number;
|
|
49
|
+
}
|
|
50
|
+
declare class MattermostClient {
|
|
51
|
+
private readonly _token;
|
|
52
|
+
private readonly _apiBase;
|
|
53
|
+
private readonly _timeout;
|
|
54
|
+
constructor(options: MattermostClientOptions);
|
|
55
|
+
sendMessage(channelId: string, text: string): Promise<MattermostPost>;
|
|
56
|
+
sendTyping(channelId: string): Promise<void>;
|
|
57
|
+
getCurrentUser(): Promise<MattermostUser>;
|
|
58
|
+
private _call;
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/index.d.ts
|
|
62
|
+
interface AFSMattermostOptions {
|
|
63
|
+
/** Multi-bot config */
|
|
64
|
+
bots?: Array<{
|
|
65
|
+
name: string;
|
|
66
|
+
token: string;
|
|
67
|
+
conversations?: string[];
|
|
68
|
+
apiBase?: string;
|
|
69
|
+
}>;
|
|
70
|
+
/** Single-bot shorthand: token */
|
|
71
|
+
token?: string;
|
|
72
|
+
/** Single-bot shorthand: channel IDs to monitor */
|
|
73
|
+
channels?: string[];
|
|
74
|
+
apiBase?: string;
|
|
75
|
+
bufferSize?: number;
|
|
76
|
+
}
|
|
77
|
+
declare class AFSMattermost extends BaseMessageProvider {
|
|
78
|
+
static manifest(): {
|
|
79
|
+
name: string;
|
|
80
|
+
description: string;
|
|
81
|
+
uriTemplate: string;
|
|
82
|
+
category: string;
|
|
83
|
+
schema: {
|
|
84
|
+
type: string;
|
|
85
|
+
properties: {
|
|
86
|
+
serverUrl: {
|
|
87
|
+
type: string;
|
|
88
|
+
description: string;
|
|
89
|
+
};
|
|
90
|
+
token: {
|
|
91
|
+
type: string;
|
|
92
|
+
description: string;
|
|
93
|
+
sensitive: boolean;
|
|
94
|
+
};
|
|
95
|
+
channels: {
|
|
96
|
+
type: string;
|
|
97
|
+
items: {
|
|
98
|
+
type: string;
|
|
99
|
+
};
|
|
100
|
+
description: string;
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
required: string[];
|
|
104
|
+
};
|
|
105
|
+
tags: string[];
|
|
106
|
+
capabilityTags: string[];
|
|
107
|
+
security: {
|
|
108
|
+
riskLevel: string;
|
|
109
|
+
resourceAccess: string[];
|
|
110
|
+
notes: string[];
|
|
111
|
+
};
|
|
112
|
+
capabilities: {
|
|
113
|
+
network: {
|
|
114
|
+
egress: boolean;
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
static treeSchema(): ProviderTreeSchema;
|
|
119
|
+
readonly providerName = "mattermost";
|
|
120
|
+
readonly eventPrefix = "mattermost";
|
|
121
|
+
private readonly _wsStates;
|
|
122
|
+
constructor(options: AFSMattermostOptions);
|
|
123
|
+
getMessageCapabilities(): MessageCapabilities;
|
|
124
|
+
createBotClient(config: BotConfig): MattermostClient;
|
|
125
|
+
sendMessage(client: unknown, convId: string, text: string, _opts?: SendOptions): Promise<{
|
|
126
|
+
messageId: string;
|
|
127
|
+
}>;
|
|
128
|
+
sendTypingIndicator(client: unknown, convId: string): Promise<void>;
|
|
129
|
+
normalizeMessage(raw: Record<string, unknown>): BufferedMessage;
|
|
130
|
+
normalizeSender(raw: Record<string, unknown>): MessageSender;
|
|
131
|
+
start(botName?: string): void;
|
|
132
|
+
stop(botName?: string): void;
|
|
133
|
+
dispose(): void;
|
|
134
|
+
listRootActions(_ctx: RouteContext): Promise<AFSListResult>;
|
|
135
|
+
execStart(_ctx: RouteContext): Promise<AFSExecResult>;
|
|
136
|
+
execStop(_ctx: RouteContext): Promise<AFSExecResult>;
|
|
137
|
+
execProcessEvent(_ctx: RouteContext, args: Record<string, unknown>): Promise<AFSExecResult>;
|
|
138
|
+
private _connectWebSocket;
|
|
139
|
+
private _handleWSEvent;
|
|
140
|
+
private _disconnectWebSocket;
|
|
141
|
+
private _cleanupWebSocket;
|
|
142
|
+
/** Process an externally-provided Mattermost event. Public for testing. */
|
|
143
|
+
_processEvent(payload: Record<string, unknown>): void;
|
|
144
|
+
/** Add a channel to a bot. Defaults to first bot. */
|
|
145
|
+
addChannel(channelId: string, botName?: string): void;
|
|
146
|
+
/** Remove a channel from a bot. */
|
|
147
|
+
removeChannel(channelId: string, botName?: string): void;
|
|
148
|
+
/** Add a message to the ring buffer directly. Public for testing/conformance. */
|
|
149
|
+
_addToBuffer(channelId: string, msg: {
|
|
150
|
+
id: string;
|
|
151
|
+
message: string;
|
|
152
|
+
user_id: string;
|
|
153
|
+
username?: string;
|
|
154
|
+
create_at?: number;
|
|
155
|
+
}): void;
|
|
156
|
+
}
|
|
157
|
+
//#endregion
|
|
158
|
+
export { AFSMattermost, AFSMattermostOptions, type MattermostClient, type MattermostPost, type MattermostUser, type MattermostWSEvent };
|
|
159
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/client.ts","../src/index.ts"],"mappings":";;;;;;;;;;AAWA;;UAAiB,uBAAA;EACf,KAAA;EACA,OAAA;EACA,OAAA;AAAA;;UAIe,cAAA;EACf,EAAA;EACA,UAAA;EACA,OAAA;EACA,OAAA;EACA,SAAA;EACA,SAAA;EACA,OAAA;EACA,IAAA;AAAA;;UAIe,cAAA;EACf,EAAA;EACA,QAAA;EACA,KAAA;EACA,UAAA;EACA,SAAA;EACA,QAAA;EACA,KAAA;AAAA;;UAIe,iBAAA;EACf,KAAA;EACA,IAAA,EAAM,MAAA;EACN,SAAA;IACE,UAAA;IACA,OAAA;IACA,OAAA;EAAA;EAEF,GAAA;AAAA;AAAA,cAGW,gBAAA;EAAA,iBACM,MAAA;EAAA,iBACA,QAAA;EAAA,iBACA,QAAA;cAEL,OAAA,EAAS,uBAAA;EASf,WAAA,CAAY,SAAA,UAAmB,IAAA,WAAe,OAAA,CAAQ,cAAA;EAOtD,UAAA,CAAW,SAAA,WAAoB,OAAA;EAI/B,cAAA,CAAA,GAAkB,OAAA,CAAQ,cAAA;EAAA,QAMlB,KAAA;AAAA;;;UCvCC,oBAAA;EDdc;ECgB7B,IAAA,GAAO,KAAA;IACL,IAAA;IACA,KAAA;IACA,aAAA;IACA,OAAA;EAAA;EDbF;ECgBA,KAAA;EDhBK;ECkBL,QAAA;EACA,OAAA;EACA,UAAA;AAAA;AAAA,cAYW,aAAA,SAAsB,mBAAA;EAAA,OAC1B,QAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SA8CA,UAAA,CAAA,GAAc,kBAAA;EAAA,SA6BZ,YAAA;EAAA,SACA,WAAA;EAAA,iBAEQ,SAAA;cAEL,OAAA,EAAS,oBAAA;EAmCrB,sBAAA,CAAA,GAA0B,mBAAA;EAqB1B,eAAA,CAAgB,MAAA,EAAQ,SAAA,GAAY,gBAAA;EAO9B,WAAA,CACJ,MAAA,WACA,MAAA,UACA,IAAA,UACA,KAAA,GAAQ,WAAA,GACP,OAAA;IAAU,SAAA;EAAA;EAMP,mBAAA,CAAoB,MAAA,WAAiB,MAAA,WAAiB,OAAA;EAK5D,gBAAA,CAAiB,GAAA,EAAK,MAAA,oBAA0B,eAAA;EAYhD,eAAA,CAAgB,GAAA,EAAK,MAAA,oBAA0B,aAAA;EAU/C,KAAA,CAAM,OAAA;EASN,IAAA,CAAK,OAAA;EAOL,OAAA,CAAA;EAWM,eAAA,CAAgB,IAAA,EAAM,YAAA,GAAe,OAAA,CAAQ,aAAA;EAuB7C,SAAA,CAAU,IAAA,EAAM,YAAA,GAAe,OAAA,CAAQ,aAAA;EAMvC,QAAA,CAAS,IAAA,EAAM,YAAA,GAAe,OAAA,CAAQ,aAAA;EAMtC,gBAAA,CACJ,IAAA,EAAM,YAAA,EACN,IAAA,EAAM,MAAA,oBACL,OAAA,CAAQ,aAAA;EAAA,QAOG,iBAAA;EAAA,QAyDN,cAAA;EAAA,QAsCA,oBAAA;EAAA,QAiBA,iBAAA;EA1PkB;EAwQ1B,aAAA,CAAc,OAAA,EAAS,MAAA;EAnPa;EAmSpC,UAAA,CAAW,SAAA,UAAmB,OAAA;EAvR3B;EAoSH,aAAA,CAAc,SAAA,UAAmB,OAAA;EAzRX;EAmStB,YAAA,CACE,SAAA,UACA,GAAA;IACE,EAAA;IACA,OAAA;IACA,OAAA;IACA,QAAA;IACA,SAAA;EAAA;AAAA"}
|