slack-term 1.22.0 → 1.23.1

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,
@@ -205,6 +218,7 @@ export async function searchPage(
205
218
  query: string,
206
219
  count: number,
207
220
  page: number,
221
+ cookie?: string,
208
222
  ): Promise<Json> {
209
223
  return get(token, "search.messages", {
210
224
  query,
@@ -212,20 +226,20 @@ export async function searchPage(
212
226
  sort_dir: "desc",
213
227
  count: String(Math.min(Math.max(count, 1), 100)),
214
228
  page: String(Math.max(page, 1)),
215
- });
229
+ }, cookie);
216
230
  }
217
231
 
218
- export async function search(token: string, query: string): Promise<Json> {
219
- return searchPage(token, query, 100, 1);
232
+ export async function search(token: string, query: string, cookie?: string): Promise<Json> {
233
+ return searchPage(token, query, 100, 1, cookie);
220
234
  }
221
235
 
222
- export async function searchAll(token: string, query: string, max: number): Promise<Json> {
236
+ export async function searchAll(token: string, query: string, max: number, cookie?: string): Promise<Json> {
223
237
  const perPage = 100;
224
238
  let page = 1;
225
239
  const all: Json[] = [];
226
240
  let last: Json = { ok: true, messages: {} };
227
241
  while (true) {
228
- const resp = await searchPage(token, query, perPage, page);
242
+ const resp = await searchPage(token, query, perPage, page, cookie);
229
243
  const matches = getPath(resp, ["messages", "matches"]);
230
244
  const arr = Array.isArray(matches) ? matches : [];
231
245
  all.push(...arr);
@@ -246,6 +260,7 @@ export async function send(
246
260
  text: string,
247
261
  threadTs?: string,
248
262
  replyBroadcast?: boolean,
263
+ cookie?: string,
249
264
  ): Promise<string> {
250
265
  const body: Record<string, Json> = {
251
266
  channel,
@@ -256,7 +271,7 @@ export async function send(
256
271
  // "Also send to channel": broadcast a threaded reply back to the channel.
257
272
  // Only meaningful alongside thread_ts; Slack ignores it on top-level sends.
258
273
  if (replyBroadcast && threadTs !== undefined) body.reply_broadcast = true;
259
- const resp = (await post(token, "chat.postMessage", body)) as { ts?: string };
274
+ const resp = (await post(token, "chat.postMessage", body, cookie)) as { ts?: string };
260
275
  return resp.ts ?? "";
261
276
  }
262
277
 
@@ -266,6 +281,7 @@ export async function scheduleMessage(
266
281
  text: string,
267
282
  postAt: number,
268
283
  threadTs?: string,
284
+ cookie?: string,
269
285
  ): Promise<string> {
270
286
  const body: Record<string, Json> = {
271
287
  channel,
@@ -274,39 +290,42 @@ export async function scheduleMessage(
274
290
  blocks: [{ type: "markdown", text }],
275
291
  };
276
292
  if (threadTs !== undefined) body.thread_ts = threadTs;
277
- const resp = (await post(token, "chat.scheduleMessage", body)) as { scheduled_message_id?: string };
293
+ const resp = (await post(token, "chat.scheduleMessage", body, cookie)) as { scheduled_message_id?: string };
278
294
  return resp.scheduled_message_id ?? "";
279
295
  }
280
296
 
281
297
  export async function listScheduledMessages(
282
298
  token: string,
283
299
  channel?: string,
300
+ cookie?: string,
284
301
  ): Promise<Json> {
285
302
  const params: Record<string, string> = {};
286
303
  if (channel) params.channel = channel;
287
- return get(token, "chat.scheduledMessages.list", params);
304
+ return get(token, "chat.scheduledMessages.list", params, cookie);
288
305
  }
289
306
 
290
307
  export async function deleteScheduledMessage(
291
308
  token: string,
292
309
  channel: string,
293
310
  scheduledMessageId: string,
311
+ cookie?: string,
294
312
  ): Promise<void> {
295
313
  await post(token, "chat.deleteScheduledMessage", {
296
314
  channel,
297
315
  scheduled_message_id: scheduledMessageId,
298
- });
316
+ }, cookie);
299
317
  }
300
318
 
301
319
  export async function getPermalink(
302
320
  token: string,
303
321
  channel: string,
304
322
  messageTs: string,
323
+ cookie?: string,
305
324
  ): Promise<string> {
306
325
  const resp = (await get(token, "chat.getPermalink", {
307
326
  channel,
308
327
  message_ts: messageTs,
309
- })) as { permalink?: string };
328
+ }, cookie)) as { permalink?: string };
310
329
  return resp.permalink ?? "";
311
330
  }
312
331
 
@@ -315,13 +334,14 @@ export async function editMessage(
315
334
  channel: string,
316
335
  ts: string,
317
336
  text: string,
337
+ cookie?: string,
318
338
  ): Promise<string> {
319
339
  const resp = (await post(token, "chat.update", {
320
340
  channel,
321
341
  ts,
322
342
  text,
323
343
  blocks: [{ type: "markdown", text }],
324
- })) as { ts?: string };
344
+ }, cookie)) as { ts?: string };
325
345
  return resp.ts ?? ts;
326
346
  }
327
347
 
@@ -329,8 +349,9 @@ export async function deleteMessage(
329
349
  token: string,
330
350
  channel: string,
331
351
  ts: string,
352
+ cookie?: string,
332
353
  ): Promise<void> {
333
- await post(token, "chat.delete", { channel, ts });
354
+ await post(token, "chat.delete", { channel, ts }, cookie);
334
355
  }
335
356
 
336
357
  // Add or remove an emoji reaction on a message. `name` is the emoji shortcode
@@ -342,8 +363,9 @@ export async function reactionAdd(
342
363
  channel: string,
343
364
  ts: string,
344
365
  name: string,
366
+ cookie?: string,
345
367
  ): Promise<void> {
346
- await post(token, "reactions.add", { channel, timestamp: ts, name });
368
+ await post(token, "reactions.add", { channel, timestamp: ts, name }, cookie);
347
369
  }
348
370
 
349
371
  export async function reactionRemove(
@@ -351,8 +373,9 @@ export async function reactionRemove(
351
373
  channel: string,
352
374
  ts: string,
353
375
  name: string,
376
+ cookie?: string,
354
377
  ): Promise<void> {
355
- await post(token, "reactions.remove", { channel, timestamp: ts, name });
378
+ await post(token, "reactions.remove", { channel, timestamp: ts, name }, cookie);
356
379
  }
357
380
 
358
381
  // Create a public (or private) channel via conversations.create. Slack
@@ -363,11 +386,12 @@ export async function createChannel(
363
386
  token: string,
364
387
  name: string,
365
388
  isPrivate = false,
389
+ cookie?: string,
366
390
  ): Promise<{ id: string; name: string }> {
367
391
  const resp = (await post(token, "conversations.create", {
368
392
  name,
369
393
  is_private: isPrivate,
370
- })) as { channel?: { id?: string; name?: string } };
394
+ }, cookie)) as { channel?: { id?: string; name?: string } };
371
395
  return { id: resp.channel?.id ?? "", name: resp.channel?.name ?? name };
372
396
  }
373
397
 
@@ -376,17 +400,18 @@ export async function inviteToChannel(
376
400
  token: string,
377
401
  channel: string,
378
402
  userIds: string[],
403
+ cookie?: string,
379
404
  ): Promise<void> {
380
- await post(token, "conversations.invite", { channel, users: userIds.join(",") });
405
+ await post(token, "conversations.invite", { channel, users: userIds.join(",") }, cookie);
381
406
  }
382
407
 
383
- export async function listConversations(token: string): Promise<Json> {
408
+ export async function listConversations(token: string, cookie?: string): Promise<Json> {
384
409
  const allChannels: Json[] = [];
385
410
  let cursor = "";
386
411
  while (true) {
387
412
  const params: Record<string, string> = { limit: "200", types: "public_channel,private_channel,im,mpim" };
388
413
  if (cursor) params.cursor = cursor;
389
- const resp = (await get(token, "conversations.list", params)) as {
414
+ const resp = (await get(token, "conversations.list", params, cookie)) as {
390
415
  channels?: Json[];
391
416
  response_metadata?: { next_cursor?: string };
392
417
  };
@@ -397,8 +422,8 @@ export async function listConversations(token: string): Promise<Json> {
397
422
  return { channels: allChannels };
398
423
  }
399
424
 
400
- export async function openDm(token: string, userId: string): Promise<string> {
401
- const resp = (await post(token, "conversations.open", { users: userId })) as {
425
+ export async function openDm(token: string, userId: string, cookie?: string): Promise<string> {
426
+ const resp = (await post(token, "conversations.open", { users: userId }, cookie)) as {
402
427
  channel?: { id?: string };
403
428
  };
404
429
  const id = resp.channel?.id;
@@ -501,7 +526,7 @@ export async function resolveChannel(token: string, ref: string, cookie?: string
501
526
  };
502
527
  const dm = (dmResp.channels ?? []).find((ch) => ch.user === userId);
503
528
  if (dm?.id) return String(dm.id);
504
- return openDm(token, userId);
529
+ return openDm(token, userId, cookie);
505
530
  }
506
531
 
507
532
  // Find user ID first via users.list (batch), then locate existing DM.
@@ -577,9 +602,10 @@ export async function resolveChannel(token: string, ref: string, cookie?: string
577
602
  export async function userInfoPair(
578
603
  token: string,
579
604
  userId: string,
605
+ cookie?: string,
580
606
  ): Promise<[string, string]> {
581
607
  try {
582
- const resp = (await get(token, "users.info", { user: userId })) as {
608
+ const resp = (await get(token, "users.info", { user: userId }, cookie)) as {
583
609
  user?: { profile?: { display_name?: string }; real_name?: string; name?: string };
584
610
  };
585
611
  const display = resp.user?.profile?.display_name;
@@ -594,9 +620,9 @@ export async function userInfoPair(
594
620
  }
595
621
  }
596
622
 
597
- export async function userName(token: string, userId: string): Promise<string> {
623
+ export async function userName(token: string, userId: string, cookie?: string): Promise<string> {
598
624
  try {
599
- const resp = (await get(token, "users.info", { user: userId })) as {
625
+ const resp = (await get(token, "users.info", { user: userId }, cookie)) as {
600
626
  user?: { profile?: { display_name?: string }; real_name?: string; name?: string };
601
627
  };
602
628
  const display = resp.user?.profile?.display_name;
@@ -700,8 +726,8 @@ export async function userInfo(token: string, userId: string, cookie?: string):
700
726
  return get(token, "users.info", { user: userId }, cookie);
701
727
  }
702
728
 
703
- export async function conversationInfo(token: string, channelId: string): Promise<Json> {
704
- return get(token, "conversations.info", { channel: channelId });
729
+ export async function conversationInfo(token: string, channelId: string, cookie?: string): Promise<Json> {
730
+ return get(token, "conversations.info", { channel: channelId }, cookie);
705
731
  }
706
732
 
707
733
  export async function deleteDraft(token: string, draftId: string, cookie?: string): Promise<Json> {
@@ -724,6 +750,7 @@ export async function uploadFile(
724
750
  channel: string,
725
751
  filePath: string,
726
752
  opts: { title?: string; threadTs?: string; initialComment?: string } = {},
753
+ cookie?: string,
727
754
  ): Promise<{ fileId: string; permalink?: string }> {
728
755
  const { statSync, readFileSync } = await import("node:fs");
729
756
  const { basename } = await import("node:path");
@@ -735,7 +762,7 @@ export async function uploadFile(
735
762
  const urlResp = (await get(token, "files.getUploadURLExternal", {
736
763
  filename,
737
764
  length: String(stat.size),
738
- })) as { upload_url?: string; file_id?: string };
765
+ }, cookie)) as { upload_url?: string; file_id?: string };
739
766
 
740
767
  const uploadUrl = urlResp.upload_url;
741
768
  const fileId = urlResp.file_id;
@@ -758,7 +785,7 @@ export async function uploadFile(
758
785
  if (opts.threadTs) completeBody.thread_ts = opts.threadTs;
759
786
  if (opts.initialComment) completeBody.initial_comment = opts.initialComment;
760
787
 
761
- const completeResp = (await post(token, "files.completeUploadExternal", completeBody)) as {
788
+ const completeResp = (await post(token, "files.completeUploadExternal", completeBody, cookie)) as {
762
789
  files?: Array<{ permalink?: string }>;
763
790
  };
764
791