jeopi-cli 16.6.17 → 16.6.21

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.
@@ -40,7 +40,7 @@ function requiredToken(value: string | undefined, prefix: string): string {
40
40
  }
41
41
 
42
42
  async function slackRequest(
43
- method: "auth.test" | "apps.connections.open" | "chat.postMessage",
43
+ method: "auth.test" | "apps.connections.open" | "chat.postMessage" | "chat.update" | "chat.delete",
44
44
  token: string,
45
45
  body: Record<string, unknown>,
46
46
  signal?: AbortSignal,
@@ -247,6 +247,7 @@ export function createSlackAdapter(
247
247
  ): MessengerAdapter {
248
248
  let teamId: string | undefined;
249
249
  let running = false;
250
+ const activityMessages = new Map<string, string>();
250
251
  return {
251
252
  platform: "slack",
252
253
  async start(onMessage, signal) {
@@ -329,5 +330,65 @@ export function createSlackAdapter(
329
330
  );
330
331
  }
331
332
  },
333
+ releaseActivity(message) {
334
+ activityMessages.delete(
335
+ JSON.stringify([message.accountId, message.channelId, message.threadId ?? "", message.messageId]),
336
+ );
337
+ },
338
+ async activity(message, text, signal) {
339
+ if (message.platform !== "slack" || (teamId !== undefined && message.accountId !== teamId)) {
340
+ throw new SlackConfigurationError("Slack reply account does not match");
341
+ }
342
+ const token = requiredToken(env[config.tokenEnv], "xoxb-");
343
+ const key = JSON.stringify([message.accountId, message.channelId, message.threadId ?? "", message.messageId]);
344
+ const statusId = activityMessages.get(key);
345
+ if (text === undefined) {
346
+ if (!statusId) return;
347
+ await slackRequest("chat.delete", token, { channel: message.channelId, ts: statusId }, signal, deps.fetch);
348
+ activityMessages.delete(key);
349
+ return;
350
+ }
351
+ const body = {
352
+ channel: message.channelId,
353
+ text: `⏳ ${text}`,
354
+ mrkdwn: false,
355
+ parse: "none",
356
+ link_names: false,
357
+ unfurl_links: false,
358
+ unfurl_media: false,
359
+ };
360
+ if (statusId) {
361
+ await slackRequest("chat.update", token, { ...body, ts: statusId }, signal, deps.fetch);
362
+ return;
363
+ }
364
+ if (signal?.aborted) return;
365
+ // Keep creation alive after cancellation so a late ID can be deleted; HTTP has its own timeout.
366
+ const created = await slackRequest(
367
+ "chat.postMessage",
368
+ token,
369
+ {
370
+ ...body,
371
+ thread_ts: message.threadId ?? (message.direct ? undefined : message.messageId),
372
+ },
373
+ undefined,
374
+ deps.fetch,
375
+ );
376
+ if (typeof created.ts !== "string" || !created.ts) throw new Error("Slack activity message returned no ID");
377
+ if (signal?.aborted) {
378
+ try {
379
+ await slackRequest(
380
+ "chat.delete",
381
+ token,
382
+ { channel: message.channelId, ts: created.ts },
383
+ AbortSignal.timeout(3_000),
384
+ deps.fetch,
385
+ );
386
+ } catch {
387
+ logger.warn("Slack late activity removal failed");
388
+ }
389
+ return;
390
+ }
391
+ activityMessages.set(key, created.ts);
392
+ },
332
393
  };
333
394
  }
@@ -144,6 +144,7 @@ export function createTelegramAdapter(
144
144
  const baseUrl = `https://api.telegram.org/bot${encodeURIComponent(token).replaceAll("%3A", ":")}/`;
145
145
  let running = false;
146
146
  let offset = 0;
147
+ const activityMessages = new Map<string, number>();
147
148
 
148
149
  async function request(method: string, parameters: Record<string, unknown>, signal?: AbortSignal): Promise<unknown> {
149
150
  const response = await messengerFetchJson(
@@ -156,7 +157,9 @@ export function createTelegramAdapter(
156
157
  if (!response.ok) {
157
158
  const status = isInteger(response.error_code) ? response.error_code : 400;
158
159
  const retryAfter = isRecord(response.parameters) ? response.parameters.retry_after : undefined;
159
- throw new TelegramApiError(status, isPositiveId(retryAfter) ? Math.min(retryAfter, 300) * 1_000 : 0);
160
+ const retryAfterMs = isPositiveId(retryAfter) ? Math.min(retryAfter, 300) * 1_000 : 0;
161
+ if (status === 429) throw new MessengerHttpError(status, retryAfterMs || undefined);
162
+ throw new TelegramApiError(status, retryAfterMs);
160
163
  }
161
164
  if (!("result" in response)) throw new TelegramApiError(400);
162
165
  return response.result;
@@ -240,5 +243,55 @@ export function createTelegramAdapter(
240
243
  if (!isRecord(result) || !isPositiveId(result.message_id)) throw new TelegramApiError(400);
241
244
  }
242
245
  },
246
+ releaseActivity(message) {
247
+ activityMessages.delete(
248
+ JSON.stringify([message.accountId, message.channelId, message.threadId ?? "", message.messageId]),
249
+ );
250
+ },
251
+ async activity(message, text, signal) {
252
+ const key = JSON.stringify([message.accountId, message.channelId, message.threadId ?? "", message.messageId]);
253
+ const statusId = activityMessages.get(key);
254
+ if (text === undefined) {
255
+ if (!statusId) return;
256
+ await request("deleteMessage", { chat_id: message.channelId, message_id: statusId }, signal);
257
+ activityMessages.delete(key);
258
+ return;
259
+ }
260
+ if (statusId) {
261
+ await request(
262
+ "editMessageText",
263
+ { chat_id: message.channelId, message_id: statusId, text: `⏳ ${text}` },
264
+ signal,
265
+ );
266
+ return;
267
+ }
268
+ const threadId = message.threadId === undefined ? undefined : Number(message.threadId);
269
+ if (threadId !== undefined && !isPositiveId(threadId)) throw new Error("Telegram topic identifier is invalid");
270
+ if (signal?.aborted) return;
271
+ // Keep creation alive after cancellation so a late ID can be deleted; HTTP has its own timeout.
272
+ const created = await request(
273
+ "sendMessage",
274
+ {
275
+ chat_id: message.channelId,
276
+ text: `⏳ ${text}`,
277
+ ...(threadId === undefined ? {} : { message_thread_id: threadId }),
278
+ },
279
+ undefined,
280
+ );
281
+ if (!isRecord(created) || !isPositiveId(created.message_id)) throw new TelegramApiError(400);
282
+ if (signal?.aborted) {
283
+ try {
284
+ await request(
285
+ "deleteMessage",
286
+ { chat_id: message.channelId, message_id: created.message_id },
287
+ AbortSignal.timeout(3_000),
288
+ );
289
+ } catch {
290
+ logger.warn("Telegram late activity removal failed");
291
+ }
292
+ return;
293
+ }
294
+ activityMessages.set(key, created.message_id);
295
+ },
243
296
  };
244
297
  }