spectrum-ts 4.2.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -67
- package/dist/authoring.d.ts +1 -6
- package/dist/authoring.js +2 -36
- package/dist/elysia.d.ts +1 -94
- package/dist/elysia.js +2 -15
- package/dist/express.d.ts +1 -62
- package/dist/express.js +2 -19
- package/dist/hono.d.ts +1 -64
- package/dist/hono.js +2 -11
- package/dist/index.d.ts +1 -2851
- package/dist/index.js +2 -3763
- package/dist/manifest.json +5 -5
- package/dist/providers/imessage/index.d.ts +1 -222
- package/dist/providers/imessage/index.js +2 -25
- package/dist/providers/index.d.ts +6 -19
- package/dist/providers/index.js +6 -34
- package/dist/providers/slack/index.d.ts +1 -46
- package/dist/providers/slack/index.js +2 -11
- package/dist/providers/telegram/index.d.ts +1 -45
- package/dist/providers/telegram/index.js +2 -13
- package/dist/providers/terminal/index.d.ts +1 -119
- package/dist/providers/terminal/index.js +2 -13
- package/dist/providers/whatsapp-business/index.d.ts +1 -27
- package/dist/providers/whatsapp-business/index.js +2 -14
- package/package.json +11 -38
- package/dist/attachment-CnivEhr6.d.ts +0 -29
- package/dist/authoring-b9AhXgPI.d.ts +0 -304
- package/dist/chunk-2D27WW5B.js +0 -63
- package/dist/chunk-34FQGGD7.js +0 -34
- package/dist/chunk-3GEJYGZK.js +0 -84
- package/dist/chunk-5XEFJBN2.js +0 -197
- package/dist/chunk-6UZFVXQF.js +0 -374
- package/dist/chunk-A37PM5N2.js +0 -91
- package/dist/chunk-ARL2NOBO.js +0 -887
- package/dist/chunk-B52VPQO3.js +0 -1379
- package/dist/chunk-DMPDLSFU.js +0 -864
- package/dist/chunk-FAIFTUV2.js +0 -139
- package/dist/chunk-LZXPLXZF.js +0 -35
- package/dist/chunk-N6THJDZV.js +0 -929
- package/dist/chunk-NLMQ75LH.js +0 -2980
- package/dist/chunk-UXAKIXVM.js +0 -409
- package/dist/chunk-WXLQNANA.js +0 -539
- package/dist/chunk-ZR3TKZMT.js +0 -129
- package/dist/read-C4uvozGX.d.ts +0 -53
- package/dist/types-CyfLJXgu.d.ts +0 -1530
- package/dist/types-ZgFTj5hJ.d.ts +0 -87
package/dist/chunk-WXLQNANA.js
DELETED
|
@@ -1,539 +0,0 @@
|
|
|
1
|
-
import { createRequire as __spectrumCreateRequire } from "node:module"; const require = __spectrumCreateRequire(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
cloud
|
|
4
|
-
} from "./chunk-3GEJYGZK.js";
|
|
5
|
-
import {
|
|
6
|
-
mergeStreams,
|
|
7
|
-
stream
|
|
8
|
-
} from "./chunk-5XEFJBN2.js";
|
|
9
|
-
import {
|
|
10
|
-
UnsupportedError,
|
|
11
|
-
definePlatform
|
|
12
|
-
} from "./chunk-B52VPQO3.js";
|
|
13
|
-
import {
|
|
14
|
-
asAttachment,
|
|
15
|
-
asCustom,
|
|
16
|
-
asReaction,
|
|
17
|
-
asText
|
|
18
|
-
} from "./chunk-UXAKIXVM.js";
|
|
19
|
-
|
|
20
|
-
// src/providers/slack/index.ts
|
|
21
|
-
import { createClient as createClient2, staticTokens } from "@photon-ai/slack";
|
|
22
|
-
|
|
23
|
-
// src/providers/slack/auth.ts
|
|
24
|
-
import {
|
|
25
|
-
createClient
|
|
26
|
-
} from "@photon-ai/slack";
|
|
27
|
-
var RENEWAL_RATIO = 0.8;
|
|
28
|
-
var EXPIRY_BUFFER_MS = 3e4;
|
|
29
|
-
var RETRY_DELAY_MS = 3e4;
|
|
30
|
-
var cloudAuthState = /* @__PURE__ */ new WeakMap();
|
|
31
|
-
var toTeamMetadata = (meta) => ({
|
|
32
|
-
appId: meta.appId,
|
|
33
|
-
botUserId: meta.botUserId,
|
|
34
|
-
grantedScopes: meta.grantedScopes,
|
|
35
|
-
teamName: meta.teamName
|
|
36
|
-
});
|
|
37
|
-
async function createCloudClients(projectId, projectSecret, endpoint) {
|
|
38
|
-
let tokenData = await cloud.issueSlackTokens(projectId, projectSecret);
|
|
39
|
-
let tokenExpiresAt = Date.now() + tokenData.expiresIn * 1e3;
|
|
40
|
-
let disposed = false;
|
|
41
|
-
let renewalTimer;
|
|
42
|
-
const clearRenewalTimer = () => {
|
|
43
|
-
if (renewalTimer !== void 0) {
|
|
44
|
-
clearTimeout(renewalTimer);
|
|
45
|
-
renewalTimer = void 0;
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
const refreshTokens = async () => {
|
|
49
|
-
tokenData = await cloud.issueSlackTokens(projectId, projectSecret);
|
|
50
|
-
tokenExpiresAt = Date.now() + tokenData.expiresIn * 1e3;
|
|
51
|
-
};
|
|
52
|
-
const scheduleRetry = () => {
|
|
53
|
-
if (disposed) {
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
clearRenewalTimer();
|
|
57
|
-
renewalTimer = setTimeout(async () => {
|
|
58
|
-
if (disposed) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
try {
|
|
62
|
-
await refreshTokens();
|
|
63
|
-
scheduleRenewal();
|
|
64
|
-
} catch (retryErr) {
|
|
65
|
-
console.warn(
|
|
66
|
-
`[spectrum-ts] Slack token refresh failed; retrying in ${RETRY_DELAY_MS}ms.`,
|
|
67
|
-
retryErr
|
|
68
|
-
);
|
|
69
|
-
scheduleRetry();
|
|
70
|
-
}
|
|
71
|
-
}, RETRY_DELAY_MS);
|
|
72
|
-
renewalTimer?.unref?.();
|
|
73
|
-
};
|
|
74
|
-
const scheduleRenewal = () => {
|
|
75
|
-
if (disposed) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
clearRenewalTimer();
|
|
79
|
-
const ttlMs = tokenData.expiresIn * 1e3;
|
|
80
|
-
const renewInMs = Math.max(ttlMs * RENEWAL_RATIO, 5e3);
|
|
81
|
-
renewalTimer = setTimeout(async () => {
|
|
82
|
-
try {
|
|
83
|
-
await refreshTokens();
|
|
84
|
-
scheduleRenewal();
|
|
85
|
-
} catch (err) {
|
|
86
|
-
console.warn(
|
|
87
|
-
`[spectrum-ts] Slack token refresh failed; retrying in ${RETRY_DELAY_MS}ms.`,
|
|
88
|
-
err
|
|
89
|
-
);
|
|
90
|
-
scheduleRetry();
|
|
91
|
-
}
|
|
92
|
-
}, renewInMs);
|
|
93
|
-
renewalTimer?.unref?.();
|
|
94
|
-
};
|
|
95
|
-
const refreshIfNeeded = async () => {
|
|
96
|
-
if (Date.now() < tokenExpiresAt - EXPIRY_BUFFER_MS) {
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
await refreshTokens();
|
|
100
|
-
scheduleRenewal();
|
|
101
|
-
};
|
|
102
|
-
scheduleRenewal();
|
|
103
|
-
const tokenProvider = {
|
|
104
|
-
async getAccessToken(teamId) {
|
|
105
|
-
await refreshIfNeeded();
|
|
106
|
-
const token = tokenData.auth[teamId];
|
|
107
|
-
if (!token) {
|
|
108
|
-
throw new Error(
|
|
109
|
-
`Slack team ${teamId} has no active installation in this project`
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
return token;
|
|
113
|
-
},
|
|
114
|
-
invalidate(_teamId) {
|
|
115
|
-
tokenExpiresAt = 0;
|
|
116
|
-
},
|
|
117
|
-
async listTeams() {
|
|
118
|
-
await refreshIfNeeded();
|
|
119
|
-
const entries = Object.entries(
|
|
120
|
-
tokenData.teams
|
|
121
|
-
).map(([teamId, meta]) => [teamId, toTeamMetadata(meta)]);
|
|
122
|
-
return new Map(entries);
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
const client = createClient({
|
|
126
|
-
spectrumSlackEndpoint: endpoint,
|
|
127
|
-
tokenProvider
|
|
128
|
-
});
|
|
129
|
-
cloudAuthState.set(client, {
|
|
130
|
-
dispose: async () => {
|
|
131
|
-
disposed = true;
|
|
132
|
-
clearRenewalTimer();
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
return client;
|
|
136
|
-
}
|
|
137
|
-
async function disposeCloudAuth(client) {
|
|
138
|
-
const auth = cloudAuthState.get(client);
|
|
139
|
-
if (!auth) {
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
await auth.dispose();
|
|
143
|
-
cloudAuthState.delete(client);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// src/providers/slack/messages.ts
|
|
147
|
-
var toRecord = (result, space, content) => ({
|
|
148
|
-
id: result.ts,
|
|
149
|
-
content,
|
|
150
|
-
space: { id: result.channel, teamId: space.teamId },
|
|
151
|
-
timestamp: tsToDate(result.ts),
|
|
152
|
-
ts: result.ts,
|
|
153
|
-
isFromMe: true
|
|
154
|
-
});
|
|
155
|
-
var toUploadRecord = (result, space, content) => {
|
|
156
|
-
const shareTs = result.shares.find((s) => s.channel === space.id)?.ts;
|
|
157
|
-
return {
|
|
158
|
-
id: shareTs ?? result.file.id,
|
|
159
|
-
content,
|
|
160
|
-
space: { id: space.id, teamId: space.teamId },
|
|
161
|
-
timestamp: shareTs ? tsToDate(shareTs) : /* @__PURE__ */ new Date(),
|
|
162
|
-
ts: shareTs,
|
|
163
|
-
isFromMe: true
|
|
164
|
-
};
|
|
165
|
-
};
|
|
166
|
-
var tsToDate = (ts) => {
|
|
167
|
-
if (!ts) {
|
|
168
|
-
return /* @__PURE__ */ new Date();
|
|
169
|
-
}
|
|
170
|
-
const seconds = Number.parseFloat(ts);
|
|
171
|
-
if (!Number.isFinite(seconds)) {
|
|
172
|
-
return /* @__PURE__ */ new Date();
|
|
173
|
-
}
|
|
174
|
-
return new Date(seconds * 1e3);
|
|
175
|
-
};
|
|
176
|
-
var lazySlackFile = (client, teamId, file) => asAttachment({
|
|
177
|
-
id: file.id,
|
|
178
|
-
name: file.name,
|
|
179
|
-
mimeType: file.mimeType,
|
|
180
|
-
size: file.size,
|
|
181
|
-
read: async () => {
|
|
182
|
-
const { bytes } = await client.team(teamId).files.getContentBuffer(file.id);
|
|
183
|
-
return Buffer.from(bytes);
|
|
184
|
-
},
|
|
185
|
-
stream: async () => {
|
|
186
|
-
const { content } = await client.team(teamId).files.getContent(file.id);
|
|
187
|
-
return new ReadableStream({
|
|
188
|
-
async start(controller) {
|
|
189
|
-
try {
|
|
190
|
-
for await (const chunk of content) {
|
|
191
|
-
controller.enqueue(chunk);
|
|
192
|
-
}
|
|
193
|
-
controller.close();
|
|
194
|
-
} catch (err) {
|
|
195
|
-
controller.error(err);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
var toMessages = (client, event) => {
|
|
202
|
-
if (event.type === "message") {
|
|
203
|
-
return messageToMessages(client, event.teamId, event.message);
|
|
204
|
-
}
|
|
205
|
-
if (event.type === "reaction") {
|
|
206
|
-
return [reactionToMessage(event.teamId, event.reaction)];
|
|
207
|
-
}
|
|
208
|
-
if (event.type === "mention") {
|
|
209
|
-
return [
|
|
210
|
-
{
|
|
211
|
-
id: event.mention.ts,
|
|
212
|
-
content: asText(event.mention.text),
|
|
213
|
-
sender: { id: event.mention.user },
|
|
214
|
-
space: { id: event.mention.channel, teamId: event.teamId },
|
|
215
|
-
timestamp: tsToDate(event.mention.ts),
|
|
216
|
-
ts: event.mention.ts,
|
|
217
|
-
isFromMe: event.mention.isFromMe
|
|
218
|
-
}
|
|
219
|
-
];
|
|
220
|
-
}
|
|
221
|
-
return [];
|
|
222
|
-
};
|
|
223
|
-
var messageToMessages = (client, teamId, msg) => {
|
|
224
|
-
const base = {
|
|
225
|
-
sender: { id: msg.user },
|
|
226
|
-
space: { id: msg.channel, teamId },
|
|
227
|
-
timestamp: tsToDate(msg.ts),
|
|
228
|
-
ts: msg.ts,
|
|
229
|
-
threadTs: msg.threadTs,
|
|
230
|
-
subtype: msg.subtype,
|
|
231
|
-
isFromMe: msg.isFromMe
|
|
232
|
-
};
|
|
233
|
-
const results = [];
|
|
234
|
-
if (msg.text) {
|
|
235
|
-
results.push({
|
|
236
|
-
...base,
|
|
237
|
-
id: msg.files.length > 0 ? `${msg.ts}:text` : msg.ts,
|
|
238
|
-
content: asText(msg.text)
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
for (const [index, file] of msg.files.entries()) {
|
|
242
|
-
const singleFile = msg.files.length === 1 && !msg.text;
|
|
243
|
-
results.push({
|
|
244
|
-
...base,
|
|
245
|
-
id: singleFile ? msg.ts : `${msg.ts}:file:${index}`,
|
|
246
|
-
content: lazySlackFile(client, teamId, file)
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
if (results.length === 0) {
|
|
250
|
-
results.push({
|
|
251
|
-
...base,
|
|
252
|
-
id: msg.ts,
|
|
253
|
-
content: asCustom({ slack_type: "empty" })
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
return results;
|
|
257
|
-
};
|
|
258
|
-
var reactionToMessage = (teamId, reaction) => {
|
|
259
|
-
const stubTarget = {
|
|
260
|
-
id: reaction.itemTs,
|
|
261
|
-
content: asCustom({ slack_type: "reaction-target", stub: true }),
|
|
262
|
-
sender: { id: "" },
|
|
263
|
-
space: { id: reaction.itemChannel, teamId }
|
|
264
|
-
};
|
|
265
|
-
return {
|
|
266
|
-
id: `${reaction.itemTs}:reaction:${reaction.user}:${reaction.name}`,
|
|
267
|
-
content: asReaction({
|
|
268
|
-
emoji: reaction.name,
|
|
269
|
-
// Cast through unknown: stub is a partial Message; core's
|
|
270
|
-
// wrapProviderMessage inflates the missing react/reply/edit methods.
|
|
271
|
-
target: stubTarget
|
|
272
|
-
}),
|
|
273
|
-
sender: { id: reaction.user },
|
|
274
|
-
space: { id: reaction.itemChannel, teamId },
|
|
275
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
276
|
-
ts: reaction.itemTs,
|
|
277
|
-
subtype: reaction.removed ? "reaction_removed" : "reaction_added",
|
|
278
|
-
isFromMe: reaction.isFromMe
|
|
279
|
-
};
|
|
280
|
-
};
|
|
281
|
-
var teamStream = (client, teamId) => {
|
|
282
|
-
const eventStream = client.team(teamId).events.subscribe();
|
|
283
|
-
return stream((emit, end) => {
|
|
284
|
-
const pump = (async () => {
|
|
285
|
-
try {
|
|
286
|
-
for await (const event of eventStream) {
|
|
287
|
-
for (const m of toMessages(client, event)) {
|
|
288
|
-
await emit(m);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
end();
|
|
292
|
-
} catch (e) {
|
|
293
|
-
end(e);
|
|
294
|
-
}
|
|
295
|
-
})();
|
|
296
|
-
return async () => {
|
|
297
|
-
await eventStream.close();
|
|
298
|
-
await pump;
|
|
299
|
-
};
|
|
300
|
-
});
|
|
301
|
-
};
|
|
302
|
-
var messages = (client, resolveTeamIds) => stream(async (emit, end) => {
|
|
303
|
-
let teamIds;
|
|
304
|
-
try {
|
|
305
|
-
teamIds = await resolveTeamIds();
|
|
306
|
-
} catch (err) {
|
|
307
|
-
end(err);
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
const merged = mergeStreams(teamIds.map((id) => teamStream(client, id)));
|
|
311
|
-
const pump = (async () => {
|
|
312
|
-
try {
|
|
313
|
-
for await (const value of merged) {
|
|
314
|
-
await emit(value);
|
|
315
|
-
}
|
|
316
|
-
end();
|
|
317
|
-
} catch (e) {
|
|
318
|
-
end(e);
|
|
319
|
-
}
|
|
320
|
-
})();
|
|
321
|
-
return async () => {
|
|
322
|
-
await merged.close();
|
|
323
|
-
await pump;
|
|
324
|
-
};
|
|
325
|
-
});
|
|
326
|
-
var mimeToMediaName = (mimeType, fallback) => {
|
|
327
|
-
const slash = mimeType.indexOf("/");
|
|
328
|
-
if (slash < 0) {
|
|
329
|
-
return fallback;
|
|
330
|
-
}
|
|
331
|
-
return `${fallback}.${mimeType.slice(slash + 1)}`;
|
|
332
|
-
};
|
|
333
|
-
var send = async (client, space, content) => {
|
|
334
|
-
if (content.type === "reply") {
|
|
335
|
-
return await replyToMessage(
|
|
336
|
-
client,
|
|
337
|
-
space,
|
|
338
|
-
content.target.ts ?? content.target.id,
|
|
339
|
-
content.content
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
|
-
if (content.type === "reaction") {
|
|
343
|
-
return await reactToMessage(
|
|
344
|
-
client,
|
|
345
|
-
space,
|
|
346
|
-
content.target.ts ?? content.target.id,
|
|
347
|
-
content
|
|
348
|
-
);
|
|
349
|
-
}
|
|
350
|
-
if (content.type === "typing") {
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
if (content.type === "read") {
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
return await sendContent(client, space, content);
|
|
357
|
-
};
|
|
358
|
-
var sendContent = async (client, space, content, threadTs) => {
|
|
359
|
-
const team = client.team(space.teamId);
|
|
360
|
-
switch (content.type) {
|
|
361
|
-
case "text": {
|
|
362
|
-
const result = await team.messages.send({
|
|
363
|
-
channel: space.id,
|
|
364
|
-
text: content.text,
|
|
365
|
-
threadTs
|
|
366
|
-
});
|
|
367
|
-
return toRecord(result, space, content);
|
|
368
|
-
}
|
|
369
|
-
case "attachment": {
|
|
370
|
-
const result = await team.files.upload({
|
|
371
|
-
channel: space.id,
|
|
372
|
-
content: await content.read(),
|
|
373
|
-
filename: content.name,
|
|
374
|
-
mimeType: content.mimeType,
|
|
375
|
-
threadTs
|
|
376
|
-
});
|
|
377
|
-
return toUploadRecord(result, space, content);
|
|
378
|
-
}
|
|
379
|
-
case "voice": {
|
|
380
|
-
const result = await team.files.upload({
|
|
381
|
-
channel: space.id,
|
|
382
|
-
content: await content.read(),
|
|
383
|
-
filename: content.name ?? mimeToMediaName(content.mimeType, "voice"),
|
|
384
|
-
mimeType: content.mimeType,
|
|
385
|
-
threadTs
|
|
386
|
-
});
|
|
387
|
-
return toUploadRecord(result, space, content);
|
|
388
|
-
}
|
|
389
|
-
default:
|
|
390
|
-
throw UnsupportedError.content(content.type);
|
|
391
|
-
}
|
|
392
|
-
};
|
|
393
|
-
var reactToMessage = async (client, space, targetTs, content) => {
|
|
394
|
-
await client.team(space.teamId).messages.send({
|
|
395
|
-
channel: space.id,
|
|
396
|
-
reaction: {
|
|
397
|
-
emoji: content.emoji,
|
|
398
|
-
itemChannel: space.id,
|
|
399
|
-
itemTs: targetTs
|
|
400
|
-
}
|
|
401
|
-
});
|
|
402
|
-
return {
|
|
403
|
-
id: `${targetTs}:reaction:self:${content.emoji}`,
|
|
404
|
-
content,
|
|
405
|
-
space: { id: space.id, teamId: space.teamId },
|
|
406
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
407
|
-
ts: targetTs,
|
|
408
|
-
isFromMe: true
|
|
409
|
-
};
|
|
410
|
-
};
|
|
411
|
-
var replyToMessage = async (client, space, targetTs, content) => await sendContent(client, space, content, targetTs);
|
|
412
|
-
|
|
413
|
-
// src/providers/slack/types.ts
|
|
414
|
-
import z from "zod";
|
|
415
|
-
var teamMetadataSchema = z.object({
|
|
416
|
-
appId: z.string(),
|
|
417
|
-
botUserId: z.string(),
|
|
418
|
-
grantedScopes: z.array(z.string()),
|
|
419
|
-
teamName: z.string()
|
|
420
|
-
});
|
|
421
|
-
var directConfig = z.object({
|
|
422
|
-
endpoint: z.string().optional(),
|
|
423
|
-
teams: z.record(z.string(), teamMetadataSchema).optional(),
|
|
424
|
-
tokens: z.record(z.string(), z.string().min(1)).refine((t) => Object.keys(t).length > 0, {
|
|
425
|
-
message: "at least one token entry is required"
|
|
426
|
-
})
|
|
427
|
-
});
|
|
428
|
-
var cloudConfig = z.object({}).strict();
|
|
429
|
-
var configSchema = z.union([directConfig, cloudConfig]);
|
|
430
|
-
var isCloudConfig = (config) => !("tokens" in config);
|
|
431
|
-
var userSchema = z.object({});
|
|
432
|
-
var spaceSchema = z.object({
|
|
433
|
-
id: z.string(),
|
|
434
|
-
teamId: z.string()
|
|
435
|
-
});
|
|
436
|
-
var spaceParamsSchema = z.object({
|
|
437
|
-
teamId: z.string()
|
|
438
|
-
});
|
|
439
|
-
var messageSchema = z.object({
|
|
440
|
-
isFromMe: z.boolean(),
|
|
441
|
-
subtype: z.string().optional(),
|
|
442
|
-
threadTs: z.string().optional(),
|
|
443
|
-
ts: z.string().optional()
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
// src/providers/slack/index.ts
|
|
447
|
-
var slack = definePlatform("Slack", {
|
|
448
|
-
config: configSchema,
|
|
449
|
-
lifecycle: {
|
|
450
|
-
createClient: async ({
|
|
451
|
-
config,
|
|
452
|
-
projectId,
|
|
453
|
-
projectSecret
|
|
454
|
-
}) => {
|
|
455
|
-
if (!isCloudConfig(config)) {
|
|
456
|
-
return createClient2({
|
|
457
|
-
spectrumSlackEndpoint: config.endpoint,
|
|
458
|
-
tokenProvider: staticTokens({
|
|
459
|
-
tokens: config.tokens,
|
|
460
|
-
teams: config.teams
|
|
461
|
-
})
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
if (!(projectId && projectSecret)) {
|
|
465
|
-
throw new Error(
|
|
466
|
-
"Slack cloud mode requires projectId and projectSecret. Either pass credentials to Spectrum(), or provide direct credentials: slack.config({ tokens: { T012ABCDE: 'jwt...' } })"
|
|
467
|
-
);
|
|
468
|
-
}
|
|
469
|
-
return await createCloudClients(
|
|
470
|
-
projectId,
|
|
471
|
-
projectSecret,
|
|
472
|
-
process.env.SPECTRUM_SLACK_ENDPOINT
|
|
473
|
-
);
|
|
474
|
-
},
|
|
475
|
-
destroyClient: async ({ client }) => {
|
|
476
|
-
await disposeCloudAuth(client);
|
|
477
|
-
await client.close();
|
|
478
|
-
}
|
|
479
|
-
},
|
|
480
|
-
user: {
|
|
481
|
-
schema: userSchema,
|
|
482
|
-
resolve: async ({ input }) => ({ id: input.userID })
|
|
483
|
-
},
|
|
484
|
-
space: {
|
|
485
|
-
schema: spaceSchema,
|
|
486
|
-
params: spaceParamsSchema,
|
|
487
|
-
create: async ({ input }) => {
|
|
488
|
-
const teamId = input.params?.teamId;
|
|
489
|
-
if (!teamId) {
|
|
490
|
-
throw new Error(
|
|
491
|
-
"Slack space creation requires a teamId param. Pass it via space.create(user, { teamId })."
|
|
492
|
-
);
|
|
493
|
-
}
|
|
494
|
-
if (input.users.length > 1) {
|
|
495
|
-
throw UnsupportedError.action(
|
|
496
|
-
"space.create",
|
|
497
|
-
"Slack",
|
|
498
|
-
"group DMs require an explicit channel id (Slack's conversations.open is not exposed); use space.get(channelId, { teamId })"
|
|
499
|
-
);
|
|
500
|
-
}
|
|
501
|
-
const user = input.users[0];
|
|
502
|
-
if (!user) {
|
|
503
|
-
throw new Error("Slack space creation requires a user");
|
|
504
|
-
}
|
|
505
|
-
return { id: user.id, teamId };
|
|
506
|
-
},
|
|
507
|
-
get: async ({ input }) => {
|
|
508
|
-
const teamId = input.params?.teamId;
|
|
509
|
-
if (!teamId) {
|
|
510
|
-
throw new Error(
|
|
511
|
-
"Slack spaces require a teamId param. Pass it via space.get(channelId, { teamId })."
|
|
512
|
-
);
|
|
513
|
-
}
|
|
514
|
-
return { id: input.id, teamId };
|
|
515
|
-
}
|
|
516
|
-
},
|
|
517
|
-
message: {
|
|
518
|
-
schema: messageSchema
|
|
519
|
-
},
|
|
520
|
-
// Discover the team list at subscribe time from the live `TokenProvider`.
|
|
521
|
-
// Direct mode: `staticTokens.listTeams` returns whatever `teams` metadata
|
|
522
|
-
// the caller passed (we fall back to the `tokens` keys when absent).
|
|
523
|
-
// Cloud mode: our renewing provider returns the live `auth` snapshot.
|
|
524
|
-
messages: ({ client, config }) => messages(client, async () => {
|
|
525
|
-
const teams = await client.teams();
|
|
526
|
-
if (teams.size > 0) {
|
|
527
|
-
return Array.from(teams.keys());
|
|
528
|
-
}
|
|
529
|
-
if (isCloudConfig(config)) {
|
|
530
|
-
return [];
|
|
531
|
-
}
|
|
532
|
-
return Object.keys(config.tokens);
|
|
533
|
-
}),
|
|
534
|
-
send: async ({ space, content, client }) => await send(client, { id: space.id, teamId: space.teamId }, content)
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
export {
|
|
538
|
-
slack
|
|
539
|
-
};
|
package/dist/chunk-ZR3TKZMT.js
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { createRequire as __spectrumCreateRequire } from "node:module"; const require = __spectrumCreateRequire(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
bufferToStream,
|
|
4
|
-
fetchUrlBytes,
|
|
5
|
-
readSchema,
|
|
6
|
-
streamSchema
|
|
7
|
-
} from "./chunk-UXAKIXVM.js";
|
|
8
|
-
|
|
9
|
-
// src/content/richlink.ts
|
|
10
|
-
import z from "zod";
|
|
11
|
-
|
|
12
|
-
// src/utils/link-metadata.ts
|
|
13
|
-
import ogs from "open-graph-scraper";
|
|
14
|
-
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
15
|
-
var USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15 spectrum-ts/richlink";
|
|
16
|
-
var normaliseImageUrl = (raw, base) => {
|
|
17
|
-
try {
|
|
18
|
-
return new URL(raw, base).toString();
|
|
19
|
-
} catch {
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
var cleanString = (v) => {
|
|
24
|
-
if (typeof v !== "string") {
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
const trimmed = v.trim();
|
|
28
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
29
|
-
};
|
|
30
|
-
var fetchLinkMetadata = async (url) => {
|
|
31
|
-
try {
|
|
32
|
-
const result = await ogs({
|
|
33
|
-
url,
|
|
34
|
-
timeout: DEFAULT_TIMEOUT_MS,
|
|
35
|
-
fetchOptions: { headers: { "User-Agent": USER_AGENT } }
|
|
36
|
-
});
|
|
37
|
-
if (result.error) {
|
|
38
|
-
return {};
|
|
39
|
-
}
|
|
40
|
-
const {
|
|
41
|
-
ogTitle,
|
|
42
|
-
ogDescription,
|
|
43
|
-
ogImage,
|
|
44
|
-
twitterTitle,
|
|
45
|
-
twitterDescription,
|
|
46
|
-
twitterImage
|
|
47
|
-
} = result.result;
|
|
48
|
-
const title = cleanString(ogTitle) ?? cleanString(twitterTitle);
|
|
49
|
-
const summary = cleanString(ogDescription) ?? cleanString(twitterDescription);
|
|
50
|
-
const imageCandidate = ogImage?.[0] ?? twitterImage?.[0];
|
|
51
|
-
const resolved = imageCandidate ? normaliseImageUrl(imageCandidate.url, url) : void 0;
|
|
52
|
-
const image = imageCandidate && resolved ? {
|
|
53
|
-
url: resolved,
|
|
54
|
-
mimeType: "type" in imageCandidate && typeof imageCandidate.type === "string" ? imageCandidate.type : void 0
|
|
55
|
-
} : void 0;
|
|
56
|
-
return { title, summary, image };
|
|
57
|
-
} catch {
|
|
58
|
-
return {};
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
var fetchImage = (url) => fetchUrlBytes(new URL(url), {
|
|
62
|
-
timeoutMs: DEFAULT_TIMEOUT_MS,
|
|
63
|
-
headers: { "User-Agent": USER_AGENT }
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
// src/content/richlink.ts
|
|
67
|
-
var richlinkCoverSchema = z.object({
|
|
68
|
-
mimeType: z.string().min(1).optional(),
|
|
69
|
-
read: readSchema,
|
|
70
|
-
stream: streamSchema
|
|
71
|
-
});
|
|
72
|
-
var optionalStringAccessor = z.function({
|
|
73
|
-
input: [],
|
|
74
|
-
output: z.promise(z.string().min(1).optional())
|
|
75
|
-
});
|
|
76
|
-
var coverAccessor = z.function({
|
|
77
|
-
input: [],
|
|
78
|
-
output: z.promise(richlinkCoverSchema.optional())
|
|
79
|
-
});
|
|
80
|
-
var richlinkSchema = z.object({
|
|
81
|
-
type: z.literal("richlink"),
|
|
82
|
-
url: z.url(),
|
|
83
|
-
title: optionalStringAccessor,
|
|
84
|
-
summary: optionalStringAccessor,
|
|
85
|
-
cover: coverAccessor
|
|
86
|
-
});
|
|
87
|
-
var memoize = (factory) => {
|
|
88
|
-
let cached;
|
|
89
|
-
return () => {
|
|
90
|
-
cached ??= factory();
|
|
91
|
-
return cached;
|
|
92
|
-
};
|
|
93
|
-
};
|
|
94
|
-
var buildCover = (image) => {
|
|
95
|
-
const read = memoize(
|
|
96
|
-
() => fetchImage(image.url).then((r) => r.data).catch(() => Buffer.alloc(0))
|
|
97
|
-
);
|
|
98
|
-
return {
|
|
99
|
-
mimeType: image.mimeType,
|
|
100
|
-
read,
|
|
101
|
-
stream: async () => bufferToStream(await read())
|
|
102
|
-
};
|
|
103
|
-
};
|
|
104
|
-
var asRichlink = (input) => {
|
|
105
|
-
const getMetadata = memoize(() => fetchLinkMetadata(input.url));
|
|
106
|
-
const getCover = memoize(async () => {
|
|
107
|
-
const { image } = await getMetadata();
|
|
108
|
-
return image ? buildCover(image) : void 0;
|
|
109
|
-
});
|
|
110
|
-
const title = async () => (await getMetadata()).title;
|
|
111
|
-
const summary = async () => (await getMetadata()).summary;
|
|
112
|
-
return richlinkSchema.parse({
|
|
113
|
-
type: "richlink",
|
|
114
|
-
url: input.url,
|
|
115
|
-
title,
|
|
116
|
-
summary,
|
|
117
|
-
cover: getCover
|
|
118
|
-
});
|
|
119
|
-
};
|
|
120
|
-
function richlink(url) {
|
|
121
|
-
return {
|
|
122
|
-
build: async () => asRichlink({ url })
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export {
|
|
127
|
-
asRichlink,
|
|
128
|
-
richlink
|
|
129
|
-
};
|
package/dist/read-C4uvozGX.d.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import z__default from 'zod';
|
|
2
|
-
import { M as Message, U as User, S as Space, C as ContentBuilder } from './types-CyfLJXgu.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Shared building blocks for photo-style content (chat background, group
|
|
6
|
-
* avatar/icon, …) whose builders all share the same `set | clear` shape and
|
|
7
|
-
* the same `"clear"` reserved-string sentinel.
|
|
8
|
-
*
|
|
9
|
-
* Keeping the action schema and `buildPhotoAction` factory here means both
|
|
10
|
-
* `background()` and `avatar()` parse against the same structural definition
|
|
11
|
-
* and any DX fix (mime inference, read caching, sentinel docs) lands once.
|
|
12
|
-
*/
|
|
13
|
-
declare const CLEAR_SENTINEL: "clear";
|
|
14
|
-
type PhotoInput = typeof CLEAR_SENTINEL | string | Buffer | URL;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* A `read` marks the conversation as read **up to** `target`, surfacing a
|
|
18
|
-
* read receipt to the sender where the platform supports one.
|
|
19
|
-
*
|
|
20
|
-
* `space.send(read(message))` is the canonical outbound API;
|
|
21
|
-
* `message.read()` and `space.read(message)` are sugar that delegate here.
|
|
22
|
-
* Reads are fire-and-forget — providers handle them inside their `send`
|
|
23
|
-
* action and the resolved value is `undefined`.
|
|
24
|
-
*
|
|
25
|
-
* Granularity is per-platform:
|
|
26
|
-
*
|
|
27
|
-
* - WhatsApp Business: per-message receipt via `markRead(target.id)`, which
|
|
28
|
-
* also marks every earlier message in the conversation as read.
|
|
29
|
-
* - iMessage (remote): chat-level `chats.markRead(chatGuid)` — `target` only
|
|
30
|
-
* identifies the chat, and **every** unread message in it is marked read.
|
|
31
|
-
* Local mode rejects with `UnsupportedError` (warned and skipped).
|
|
32
|
-
* - Telegram / Slack: silently no-op. Neither surfaces read state for bot
|
|
33
|
-
* conversations (Telegram bot chats are effectively auto-read), so the
|
|
34
|
-
* signal is vacuously satisfied — same best-effort contract as `typing`.
|
|
35
|
-
*/
|
|
36
|
-
declare const readSchema: z__default.ZodObject<{
|
|
37
|
-
type: z__default.ZodLiteral<"read">;
|
|
38
|
-
target: z__default.ZodCustom<Message<string, User, Space<unknown>>, Message<string, User, Space<unknown>>>;
|
|
39
|
-
}, z__default.core.$strip>;
|
|
40
|
-
type Read = z__default.infer<typeof readSchema>;
|
|
41
|
-
/**
|
|
42
|
-
* Construct a `read` content value marking the conversation read up to
|
|
43
|
-
* `target`.
|
|
44
|
-
*
|
|
45
|
-
* Only inbound messages (those received from a user) can be marked read;
|
|
46
|
-
* calling this with an outbound target throws at build time so the misuse
|
|
47
|
-
* surfaces before the send pipeline runs. The target is required (not
|
|
48
|
-
* `Message | undefined` like `unsend`): read targets come from the inbound
|
|
49
|
-
* stream, never from a chainable `send()` result.
|
|
50
|
-
*/
|
|
51
|
-
declare function read(target: Message): ContentBuilder;
|
|
52
|
-
|
|
53
|
-
export { type PhotoInput as P, type Read as R, read as r };
|