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/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-) is not accepted by the public Slack API.\n` +
46
- `Replace it with an xoxp- user token:\n` +
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>): Promise<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(token: string): Promise<{ team: string; teamId: string; url: string; user: string; userId: string }> {
131
- const resp = (await get(token, "auth.test", {})) as {
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 res = await fetch(`${base()}/auth.test`, {
150
- method: "GET",
151
- headers: { Authorization: `Bearer ${token}` },
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(token: string, channel: string, limit = 20, oldest?: string, cursor?: string): Promise<Json> {
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