slack-term 1.23.0 → 1.24.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/SKILL.md +5 -0
- package/dist/cli.js +85 -81
- package/package.json +4 -2
- package/ts/cli.ts +230 -58
- package/ts/slack.ts +92 -44
package/ts/slack.ts
CHANGED
|
@@ -40,10 +40,13 @@ async function call(token: string, method: string, init: RequestInit, cookie?: s
|
|
|
40
40
|
if (body.ok !== true) {
|
|
41
41
|
const err = body.error ?? "unknown";
|
|
42
42
|
if (err === "ratelimited") throw new RateLimitError(60);
|
|
43
|
+
// A desktop session token (xoxc-) is only accepted by the public Web API when
|
|
44
|
+
// paired with its xoxd session cookie — without one, point at the xoxp fallback.
|
|
43
45
|
if (err === "invalid_auth" && token.startsWith("xoxc-") && !cookie) {
|
|
44
46
|
throw new Error(
|
|
45
|
-
`Desktop app token (xoxc-)
|
|
46
|
-
`
|
|
47
|
+
`Desktop app token (xoxc-) needs its session cookie to be accepted by the public Slack API.\n` +
|
|
48
|
+
`Attach it: slack auth chrome (macOS) or slack auth firefox\n` +
|
|
49
|
+
`Or replace the token with an xoxp- user token:\n` +
|
|
47
50
|
` slack auth token`,
|
|
48
51
|
);
|
|
49
52
|
}
|
|
@@ -119,16 +122,19 @@ function get(token: string, method: string, params: Record<string, string>, cook
|
|
|
119
122
|
return call(token, `${method}?${qs}`, { method: "GET" }, cookie);
|
|
120
123
|
}
|
|
121
124
|
|
|
122
|
-
function post(token: string, method: string, body: Record<string, Json
|
|
125
|
+
function post(token: string, method: string, body: Record<string, Json>, cookie?: string): Promise<Json> {
|
|
123
126
|
return call(token, method, {
|
|
124
127
|
method: "POST",
|
|
125
128
|
headers: { "Content-Type": "application/json" },
|
|
126
129
|
body: JSON.stringify(body),
|
|
127
|
-
});
|
|
130
|
+
}, cookie);
|
|
128
131
|
}
|
|
129
132
|
|
|
130
|
-
export async function authTest(
|
|
131
|
-
|
|
133
|
+
export async function authTest(
|
|
134
|
+
token: string,
|
|
135
|
+
cookie?: string,
|
|
136
|
+
): Promise<{ team: string; teamId: string; url: string; user: string; userId: string }> {
|
|
137
|
+
const resp = (await get(token, "auth.test", {}, cookie)) as {
|
|
132
138
|
team?: string; team_id?: string; url?: string; user?: string; user_id?: string;
|
|
133
139
|
};
|
|
134
140
|
return {
|
|
@@ -143,13 +149,12 @@ export async function authTest(token: string): Promise<{ team: string; teamId: s
|
|
|
143
149
|
// auth.test plus the token's granted scopes. Slack returns the granted bot/user
|
|
144
150
|
// scopes in the `X-OAuth-Scopes` response header on every Web API call, which is
|
|
145
151
|
// the only way to learn what a token can do without probing each endpoint.
|
|
146
|
-
export async function authScopes(token: string): Promise<{
|
|
152
|
+
export async function authScopes(token: string, cookie?: string): Promise<{
|
|
147
153
|
userId: string; botId: string; team: string; url: string; scopes: string[];
|
|
148
154
|
}> {
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
});
|
|
155
|
+
const headers: Record<string, string> = { Authorization: `Bearer ${token}` };
|
|
156
|
+
if (cookie) headers["Cookie"] = `d=${cookie}`;
|
|
157
|
+
const res = await fetch(`${base()}/auth.test`, { method: "GET", headers });
|
|
153
158
|
if (res.status === 429) {
|
|
154
159
|
const retryAfter = parseInt(res.headers.get("retry-after") ?? "60", 10);
|
|
155
160
|
throw new RateLimitError(isNaN(retryAfter) ? 60 : retryAfter);
|
|
@@ -170,18 +175,25 @@ export async function authScopes(token: string): Promise<{
|
|
|
170
175
|
|
|
171
176
|
// Resolve a bot's app_id (and bot user id) from its bot_id — needed to build the
|
|
172
177
|
// app-settings deep link in messaging diagnostics.
|
|
173
|
-
export async function botsInfo(token: string, botId: string): Promise<{ appId: string; userId: string }> {
|
|
174
|
-
const resp = (await get(token, "bots.info", { bot: botId })) as {
|
|
178
|
+
export async function botsInfo(token: string, botId: string, cookie?: string): Promise<{ appId: string; userId: string }> {
|
|
179
|
+
const resp = (await get(token, "bots.info", { bot: botId }, cookie)) as {
|
|
175
180
|
bot?: { app_id?: string; user_id?: string };
|
|
176
181
|
};
|
|
177
182
|
return { appId: resp.bot?.app_id ?? "", userId: resp.bot?.user_id ?? "" };
|
|
178
183
|
}
|
|
179
184
|
|
|
180
|
-
export async function history(
|
|
185
|
+
export async function history(
|
|
186
|
+
token: string,
|
|
187
|
+
channel: string,
|
|
188
|
+
limit = 20,
|
|
189
|
+
oldest?: string,
|
|
190
|
+
cursor?: string,
|
|
191
|
+
cookie?: string,
|
|
192
|
+
): Promise<Json> {
|
|
181
193
|
const params: Record<string, string> = { channel, limit: String(limit) };
|
|
182
194
|
if (oldest !== undefined) params.oldest = oldest;
|
|
183
195
|
if (cursor !== undefined) params.cursor = cursor;
|
|
184
|
-
return get(token, "conversations.history", params);
|
|
196
|
+
return get(token, "conversations.history", params, cookie);
|
|
185
197
|
}
|
|
186
198
|
|
|
187
199
|
export async function replies(
|
|
@@ -189,8 +201,9 @@ export async function replies(
|
|
|
189
201
|
channel: string,
|
|
190
202
|
ts: string,
|
|
191
203
|
limit = 50,
|
|
204
|
+
cookie?: string,
|
|
192
205
|
): Promise<Json> {
|
|
193
|
-
return get(token, "conversations.replies", { channel, ts, limit: String(limit) });
|
|
206
|
+
return get(token, "conversations.replies", { channel, ts, limit: String(limit) }, cookie);
|
|
194
207
|
}
|
|
195
208
|
|
|
196
209
|
// Metadata for a single uploaded file (files.info). Carries url_private_download,
|
|
@@ -200,11 +213,33 @@ export async function filesInfo(token: string, fileId: string, cookie?: string):
|
|
|
200
213
|
return get(token, "files.info", { file: fileId }, cookie);
|
|
201
214
|
}
|
|
202
215
|
|
|
216
|
+
// files.list — list files visible to the token. Paginated by page number (not a
|
|
217
|
+
// cursor); the response's `paging.pages` tells the caller how many pages exist.
|
|
218
|
+
// `types` is Slack's comma-separated filter (images,pdfs,gdocs,snippets,zips,
|
|
219
|
+
// spaces,all). Note: canvases and Slack Lists shown in the web "unified files"
|
|
220
|
+
// view are largely NOT returned here — those live behind internal APIs the CLI's
|
|
221
|
+
// token type can't reach — so this surfaces conventional file uploads.
|
|
222
|
+
export async function filesList(
|
|
223
|
+
token: string,
|
|
224
|
+
opts: { page?: number; count?: number; types?: string; channel?: string; user?: string } = {},
|
|
225
|
+
cookie?: string,
|
|
226
|
+
): Promise<Json> {
|
|
227
|
+
const params: Record<string, string> = {
|
|
228
|
+
count: String(Math.min(Math.max(opts.count ?? 100, 1), 200)),
|
|
229
|
+
page: String(Math.max(opts.page ?? 1, 1)),
|
|
230
|
+
};
|
|
231
|
+
if (opts.types) params.types = opts.types;
|
|
232
|
+
if (opts.channel) params.channel = opts.channel;
|
|
233
|
+
if (opts.user) params.user = opts.user;
|
|
234
|
+
return get(token, "files.list", params, cookie);
|
|
235
|
+
}
|
|
236
|
+
|
|
203
237
|
export async function searchPage(
|
|
204
238
|
token: string,
|
|
205
239
|
query: string,
|
|
206
240
|
count: number,
|
|
207
241
|
page: number,
|
|
242
|
+
cookie?: string,
|
|
208
243
|
): Promise<Json> {
|
|
209
244
|
return get(token, "search.messages", {
|
|
210
245
|
query,
|
|
@@ -212,20 +247,20 @@ export async function searchPage(
|
|
|
212
247
|
sort_dir: "desc",
|
|
213
248
|
count: String(Math.min(Math.max(count, 1), 100)),
|
|
214
249
|
page: String(Math.max(page, 1)),
|
|
215
|
-
});
|
|
250
|
+
}, cookie);
|
|
216
251
|
}
|
|
217
252
|
|
|
218
|
-
export async function search(token: string, query: string): Promise<Json> {
|
|
219
|
-
return searchPage(token, query, 100, 1);
|
|
253
|
+
export async function search(token: string, query: string, cookie?: string): Promise<Json> {
|
|
254
|
+
return searchPage(token, query, 100, 1, cookie);
|
|
220
255
|
}
|
|
221
256
|
|
|
222
|
-
export async function searchAll(token: string, query: string, max: number): Promise<Json> {
|
|
257
|
+
export async function searchAll(token: string, query: string, max: number, cookie?: string): Promise<Json> {
|
|
223
258
|
const perPage = 100;
|
|
224
259
|
let page = 1;
|
|
225
260
|
const all: Json[] = [];
|
|
226
261
|
let last: Json = { ok: true, messages: {} };
|
|
227
262
|
while (true) {
|
|
228
|
-
const resp = await searchPage(token, query, perPage, page);
|
|
263
|
+
const resp = await searchPage(token, query, perPage, page, cookie);
|
|
229
264
|
const matches = getPath(resp, ["messages", "matches"]);
|
|
230
265
|
const arr = Array.isArray(matches) ? matches : [];
|
|
231
266
|
all.push(...arr);
|
|
@@ -246,6 +281,7 @@ export async function send(
|
|
|
246
281
|
text: string,
|
|
247
282
|
threadTs?: string,
|
|
248
283
|
replyBroadcast?: boolean,
|
|
284
|
+
cookie?: string,
|
|
249
285
|
): Promise<string> {
|
|
250
286
|
const body: Record<string, Json> = {
|
|
251
287
|
channel,
|
|
@@ -256,7 +292,7 @@ export async function send(
|
|
|
256
292
|
// "Also send to channel": broadcast a threaded reply back to the channel.
|
|
257
293
|
// Only meaningful alongside thread_ts; Slack ignores it on top-level sends.
|
|
258
294
|
if (replyBroadcast && threadTs !== undefined) body.reply_broadcast = true;
|
|
259
|
-
const resp = (await post(token, "chat.postMessage", body)) as { ts?: string };
|
|
295
|
+
const resp = (await post(token, "chat.postMessage", body, cookie)) as { ts?: string };
|
|
260
296
|
return resp.ts ?? "";
|
|
261
297
|
}
|
|
262
298
|
|
|
@@ -266,6 +302,7 @@ export async function scheduleMessage(
|
|
|
266
302
|
text: string,
|
|
267
303
|
postAt: number,
|
|
268
304
|
threadTs?: string,
|
|
305
|
+
cookie?: string,
|
|
269
306
|
): Promise<string> {
|
|
270
307
|
const body: Record<string, Json> = {
|
|
271
308
|
channel,
|
|
@@ -274,39 +311,42 @@ export async function scheduleMessage(
|
|
|
274
311
|
blocks: [{ type: "markdown", text }],
|
|
275
312
|
};
|
|
276
313
|
if (threadTs !== undefined) body.thread_ts = threadTs;
|
|
277
|
-
const resp = (await post(token, "chat.scheduleMessage", body)) as { scheduled_message_id?: string };
|
|
314
|
+
const resp = (await post(token, "chat.scheduleMessage", body, cookie)) as { scheduled_message_id?: string };
|
|
278
315
|
return resp.scheduled_message_id ?? "";
|
|
279
316
|
}
|
|
280
317
|
|
|
281
318
|
export async function listScheduledMessages(
|
|
282
319
|
token: string,
|
|
283
320
|
channel?: string,
|
|
321
|
+
cookie?: string,
|
|
284
322
|
): Promise<Json> {
|
|
285
323
|
const params: Record<string, string> = {};
|
|
286
324
|
if (channel) params.channel = channel;
|
|
287
|
-
return get(token, "chat.scheduledMessages.list", params);
|
|
325
|
+
return get(token, "chat.scheduledMessages.list", params, cookie);
|
|
288
326
|
}
|
|
289
327
|
|
|
290
328
|
export async function deleteScheduledMessage(
|
|
291
329
|
token: string,
|
|
292
330
|
channel: string,
|
|
293
331
|
scheduledMessageId: string,
|
|
332
|
+
cookie?: string,
|
|
294
333
|
): Promise<void> {
|
|
295
334
|
await post(token, "chat.deleteScheduledMessage", {
|
|
296
335
|
channel,
|
|
297
336
|
scheduled_message_id: scheduledMessageId,
|
|
298
|
-
});
|
|
337
|
+
}, cookie);
|
|
299
338
|
}
|
|
300
339
|
|
|
301
340
|
export async function getPermalink(
|
|
302
341
|
token: string,
|
|
303
342
|
channel: string,
|
|
304
343
|
messageTs: string,
|
|
344
|
+
cookie?: string,
|
|
305
345
|
): Promise<string> {
|
|
306
346
|
const resp = (await get(token, "chat.getPermalink", {
|
|
307
347
|
channel,
|
|
308
348
|
message_ts: messageTs,
|
|
309
|
-
})) as { permalink?: string };
|
|
349
|
+
}, cookie)) as { permalink?: string };
|
|
310
350
|
return resp.permalink ?? "";
|
|
311
351
|
}
|
|
312
352
|
|
|
@@ -315,13 +355,14 @@ export async function editMessage(
|
|
|
315
355
|
channel: string,
|
|
316
356
|
ts: string,
|
|
317
357
|
text: string,
|
|
358
|
+
cookie?: string,
|
|
318
359
|
): Promise<string> {
|
|
319
360
|
const resp = (await post(token, "chat.update", {
|
|
320
361
|
channel,
|
|
321
362
|
ts,
|
|
322
363
|
text,
|
|
323
364
|
blocks: [{ type: "markdown", text }],
|
|
324
|
-
})) as { ts?: string };
|
|
365
|
+
}, cookie)) as { ts?: string };
|
|
325
366
|
return resp.ts ?? ts;
|
|
326
367
|
}
|
|
327
368
|
|
|
@@ -329,8 +370,9 @@ export async function deleteMessage(
|
|
|
329
370
|
token: string,
|
|
330
371
|
channel: string,
|
|
331
372
|
ts: string,
|
|
373
|
+
cookie?: string,
|
|
332
374
|
): Promise<void> {
|
|
333
|
-
await post(token, "chat.delete", { channel, ts });
|
|
375
|
+
await post(token, "chat.delete", { channel, ts }, cookie);
|
|
334
376
|
}
|
|
335
377
|
|
|
336
378
|
// Add or remove an emoji reaction on a message. `name` is the emoji shortcode
|
|
@@ -342,8 +384,9 @@ export async function reactionAdd(
|
|
|
342
384
|
channel: string,
|
|
343
385
|
ts: string,
|
|
344
386
|
name: string,
|
|
387
|
+
cookie?: string,
|
|
345
388
|
): Promise<void> {
|
|
346
|
-
await post(token, "reactions.add", { channel, timestamp: ts, name });
|
|
389
|
+
await post(token, "reactions.add", { channel, timestamp: ts, name }, cookie);
|
|
347
390
|
}
|
|
348
391
|
|
|
349
392
|
export async function reactionRemove(
|
|
@@ -351,8 +394,9 @@ export async function reactionRemove(
|
|
|
351
394
|
channel: string,
|
|
352
395
|
ts: string,
|
|
353
396
|
name: string,
|
|
397
|
+
cookie?: string,
|
|
354
398
|
): Promise<void> {
|
|
355
|
-
await post(token, "reactions.remove", { channel, timestamp: ts, name });
|
|
399
|
+
await post(token, "reactions.remove", { channel, timestamp: ts, name }, cookie);
|
|
356
400
|
}
|
|
357
401
|
|
|
358
402
|
// Create a public (or private) channel via conversations.create. Slack
|
|
@@ -363,11 +407,12 @@ export async function createChannel(
|
|
|
363
407
|
token: string,
|
|
364
408
|
name: string,
|
|
365
409
|
isPrivate = false,
|
|
410
|
+
cookie?: string,
|
|
366
411
|
): Promise<{ id: string; name: string }> {
|
|
367
412
|
const resp = (await post(token, "conversations.create", {
|
|
368
413
|
name,
|
|
369
414
|
is_private: isPrivate,
|
|
370
|
-
})) as { channel?: { id?: string; name?: string } };
|
|
415
|
+
}, cookie)) as { channel?: { id?: string; name?: string } };
|
|
371
416
|
return { id: resp.channel?.id ?? "", name: resp.channel?.name ?? name };
|
|
372
417
|
}
|
|
373
418
|
|
|
@@ -376,17 +421,18 @@ export async function inviteToChannel(
|
|
|
376
421
|
token: string,
|
|
377
422
|
channel: string,
|
|
378
423
|
userIds: string[],
|
|
424
|
+
cookie?: string,
|
|
379
425
|
): Promise<void> {
|
|
380
|
-
await post(token, "conversations.invite", { channel, users: userIds.join(",") });
|
|
426
|
+
await post(token, "conversations.invite", { channel, users: userIds.join(",") }, cookie);
|
|
381
427
|
}
|
|
382
428
|
|
|
383
|
-
export async function listConversations(token: string): Promise<Json> {
|
|
429
|
+
export async function listConversations(token: string, cookie?: string): Promise<Json> {
|
|
384
430
|
const allChannels: Json[] = [];
|
|
385
431
|
let cursor = "";
|
|
386
432
|
while (true) {
|
|
387
433
|
const params: Record<string, string> = { limit: "200", types: "public_channel,private_channel,im,mpim" };
|
|
388
434
|
if (cursor) params.cursor = cursor;
|
|
389
|
-
const resp = (await get(token, "conversations.list", params)) as {
|
|
435
|
+
const resp = (await get(token, "conversations.list", params, cookie)) as {
|
|
390
436
|
channels?: Json[];
|
|
391
437
|
response_metadata?: { next_cursor?: string };
|
|
392
438
|
};
|
|
@@ -397,8 +443,8 @@ export async function listConversations(token: string): Promise<Json> {
|
|
|
397
443
|
return { channels: allChannels };
|
|
398
444
|
}
|
|
399
445
|
|
|
400
|
-
export async function openDm(token: string, userId: string): Promise<string> {
|
|
401
|
-
const resp = (await post(token, "conversations.open", { users: userId })) as {
|
|
446
|
+
export async function openDm(token: string, userId: string, cookie?: string): Promise<string> {
|
|
447
|
+
const resp = (await post(token, "conversations.open", { users: userId }, cookie)) as {
|
|
402
448
|
channel?: { id?: string };
|
|
403
449
|
};
|
|
404
450
|
const id = resp.channel?.id;
|
|
@@ -501,7 +547,7 @@ export async function resolveChannel(token: string, ref: string, cookie?: string
|
|
|
501
547
|
};
|
|
502
548
|
const dm = (dmResp.channels ?? []).find((ch) => ch.user === userId);
|
|
503
549
|
if (dm?.id) return String(dm.id);
|
|
504
|
-
return openDm(token, userId);
|
|
550
|
+
return openDm(token, userId, cookie);
|
|
505
551
|
}
|
|
506
552
|
|
|
507
553
|
// Find user ID first via users.list (batch), then locate existing DM.
|
|
@@ -577,9 +623,10 @@ export async function resolveChannel(token: string, ref: string, cookie?: string
|
|
|
577
623
|
export async function userInfoPair(
|
|
578
624
|
token: string,
|
|
579
625
|
userId: string,
|
|
626
|
+
cookie?: string,
|
|
580
627
|
): Promise<[string, string]> {
|
|
581
628
|
try {
|
|
582
|
-
const resp = (await get(token, "users.info", { user: userId })) as {
|
|
629
|
+
const resp = (await get(token, "users.info", { user: userId }, cookie)) as {
|
|
583
630
|
user?: { profile?: { display_name?: string }; real_name?: string; name?: string };
|
|
584
631
|
};
|
|
585
632
|
const display = resp.user?.profile?.display_name;
|
|
@@ -594,9 +641,9 @@ export async function userInfoPair(
|
|
|
594
641
|
}
|
|
595
642
|
}
|
|
596
643
|
|
|
597
|
-
export async function userName(token: string, userId: string): Promise<string> {
|
|
644
|
+
export async function userName(token: string, userId: string, cookie?: string): Promise<string> {
|
|
598
645
|
try {
|
|
599
|
-
const resp = (await get(token, "users.info", { user: userId })) as {
|
|
646
|
+
const resp = (await get(token, "users.info", { user: userId }, cookie)) as {
|
|
600
647
|
user?: { profile?: { display_name?: string }; real_name?: string; name?: string };
|
|
601
648
|
};
|
|
602
649
|
const display = resp.user?.profile?.display_name;
|
|
@@ -700,8 +747,8 @@ export async function userInfo(token: string, userId: string, cookie?: string):
|
|
|
700
747
|
return get(token, "users.info", { user: userId }, cookie);
|
|
701
748
|
}
|
|
702
749
|
|
|
703
|
-
export async function conversationInfo(token: string, channelId: string): Promise<Json> {
|
|
704
|
-
return get(token, "conversations.info", { channel: channelId });
|
|
750
|
+
export async function conversationInfo(token: string, channelId: string, cookie?: string): Promise<Json> {
|
|
751
|
+
return get(token, "conversations.info", { channel: channelId }, cookie);
|
|
705
752
|
}
|
|
706
753
|
|
|
707
754
|
export async function deleteDraft(token: string, draftId: string, cookie?: string): Promise<Json> {
|
|
@@ -724,6 +771,7 @@ export async function uploadFile(
|
|
|
724
771
|
channel: string,
|
|
725
772
|
filePath: string,
|
|
726
773
|
opts: { title?: string; threadTs?: string; initialComment?: string } = {},
|
|
774
|
+
cookie?: string,
|
|
727
775
|
): Promise<{ fileId: string; permalink?: string }> {
|
|
728
776
|
const { statSync, readFileSync } = await import("node:fs");
|
|
729
777
|
const { basename } = await import("node:path");
|
|
@@ -735,7 +783,7 @@ export async function uploadFile(
|
|
|
735
783
|
const urlResp = (await get(token, "files.getUploadURLExternal", {
|
|
736
784
|
filename,
|
|
737
785
|
length: String(stat.size),
|
|
738
|
-
})) as { upload_url?: string; file_id?: string };
|
|
786
|
+
}, cookie)) as { upload_url?: string; file_id?: string };
|
|
739
787
|
|
|
740
788
|
const uploadUrl = urlResp.upload_url;
|
|
741
789
|
const fileId = urlResp.file_id;
|
|
@@ -758,7 +806,7 @@ export async function uploadFile(
|
|
|
758
806
|
if (opts.threadTs) completeBody.thread_ts = opts.threadTs;
|
|
759
807
|
if (opts.initialComment) completeBody.initial_comment = opts.initialComment;
|
|
760
808
|
|
|
761
|
-
const completeResp = (await post(token, "files.completeUploadExternal", completeBody)) as {
|
|
809
|
+
const completeResp = (await post(token, "files.completeUploadExternal", completeBody, cookie)) as {
|
|
762
810
|
files?: Array<{ permalink?: string }>;
|
|
763
811
|
};
|
|
764
812
|
|