koishi-plugin-nitter 0.0.10 → 0.0.12

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/lib/download.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export declare function downloadImagesToBase64(imageUrls: any): Promise<any[]>;
2
- export declare function downloadVideosToBase64(videoUrls: any, maxSize?: number): Promise<any[]>;
2
+ export declare function downloadVideosToTempFiles(videoUrls: string[], tempDir: string, maxSize?: number): Promise<string[]>;
3
+ export declare function cleanupTempFiles(files: Array<string | null>): Promise<void>;
package/lib/index.d.ts CHANGED
@@ -14,18 +14,12 @@ export interface Config {
14
14
  temperature: number;
15
15
  timeout?: number;
16
16
  app: string;
17
+ enablePollingOnStart: boolean;
17
18
  enableReTweet: boolean;
18
19
  sendPic: boolean;
19
20
  cronString: string;
20
- maxSize: number;
21
- }
22
- declare module 'koishi' {
23
- interface Tables {
24
- nitter_records: {
25
- id: string;
26
- createdAt: Date;
27
- };
28
- }
21
+ maxSize?: number;
22
+ tmpDir?: string;
29
23
  }
30
24
  export declare const Config: Schema<Config, Dict>;
31
25
  export declare function apply(ctx: Context, config: Config): void;
package/lib/index.js CHANGED
@@ -43,14 +43,19 @@ var import_axios = __toESM(require("axios"));
43
43
  var import_https_proxy_agent = require("https-proxy-agent");
44
44
 
45
45
  // src/retry.ts
46
- async function retry(retries, fn, delay = 500) {
46
+ async function retry(retries, fn, error_fn = (err) => {
47
+ throw err;
48
+ }, delay = 500) {
47
49
  try {
48
50
  return await fn();
49
51
  } catch (err) {
50
52
  console.log(`剩余${retries}次尝试`, err);
51
- if (retries <= 1) throw err;
53
+ if (retries <= 1) {
54
+ error_fn(err);
55
+ return;
56
+ }
52
57
  await new Promise((r) => setTimeout(r, delay));
53
- return retry(retries - 1, fn, delay * 2);
58
+ return retry(retries - 1, fn, error_fn, delay * 2);
54
59
  }
55
60
  }
56
61
  __name(retry, "retry");
@@ -137,6 +142,13 @@ async function addTranslate(page, className) {
137
142
  }, className);
138
143
  if (!elementsHTML || !elementsHTML.length) return;
139
144
  const translatedHTMLs = await translate(elementsHTML);
145
+ const translatedTexts = await page.evaluate((htmlArr) => {
146
+ return htmlArr.map((html) => {
147
+ const div = document.createElement("div");
148
+ div.innerHTML = html;
149
+ return (div.innerText || "").replace(/\n{3,}/g, "\n\n").trim();
150
+ });
151
+ }, translatedHTMLs);
140
152
  await page.evaluate((className2, originalHTMLs, translatedHTMLs2) => {
141
153
  const elements = document.querySelectorAll(className2);
142
154
  elements.forEach((element, index) => {
@@ -151,6 +163,7 @@ async function addTranslate(page, className) {
151
163
  }
152
164
  });
153
165
  }, className, elementsHTML, translatedHTMLs);
166
+ return translatedTexts;
154
167
  }
155
168
  __name(addTranslate, "addTranslate");
156
169
 
@@ -163,48 +176,45 @@ var import_fluent_ffmpeg = __toESM(require("fluent-ffmpeg"));
163
176
  var import_path = __toESM(require("path"));
164
177
  var import_promises = __toESM(require("fs/promises"));
165
178
  var import_axios2 = __toESM(require("axios"));
166
- var tempDir = import_path.default.join(process.cwd(), "tmp");
167
- async function downloadVideosToBase64(videoUrls, maxSize = 20) {
179
+ async function downloadVideosToTempFiles(videoUrls, tempDir, maxSize = 20) {
168
180
  await import_promises.default.mkdir(tempDir, { recursive: true });
169
181
  const results = [];
170
182
  for (const url of videoUrls) {
171
183
  let outputPath = null;
172
184
  try {
173
- outputPath = import_path.default.join(tempDir, `video_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.mp4`);
185
+ outputPath = import_path.default.join(
186
+ tempDir,
187
+ `video_${Date.now()}_${crypto.randomUUID()}.mp4`
188
+ );
174
189
  await new Promise((resolve, reject) => {
175
- (0, import_fluent_ffmpeg.default)(url).inputOptions([
176
- "-protocol_whitelist",
177
- "file,http,https,tcp,tls,crypto"
178
- ]).outputOptions([
179
- "-c",
180
- "copy"
181
- ]).output(outputPath).on("end", () => resolve(outputPath)).on("error", reject).run();
190
+ (0, import_fluent_ffmpeg.default)(url).inputOptions(["-protocol_whitelist", "file,http,https,tcp,tls,crypto"]).outputOptions(["-c", "copy"]).output(outputPath).on("end", () => resolve(outputPath)).on("error", reject).run();
182
191
  });
183
192
  const stats = await import_promises.default.stat(outputPath);
184
193
  const fileSizeInMB = stats.size / (1024 * 1024);
185
194
  if (fileSizeInMB > maxSize) {
186
- console.log(`视频大小 ${fileSizeInMB.toFixed(2)}MB 超过${maxSize}MB限制,跳过转换`);
195
+ console.log(`视频大小 ${fileSizeInMB.toFixed(2)}MB 超过 ${maxSize}MB,跳过发送`);
196
+ await import_promises.default.unlink(outputPath).catch(() => {
197
+ });
198
+ results.push(null);
187
199
  } else {
188
- const fileBuffer = await import_promises.default.readFile(outputPath);
189
- const base64String = fileBuffer.toString("base64");
190
- const dataUrl = `data:video/mp4;base64,${base64String}`;
191
- results.push(dataUrl);
200
+ results.push(outputPath);
192
201
  }
193
202
  } catch (error) {
194
- console.error(`处理失败 (${url}):`, error.message);
203
+ console.error(`下载失败 (${url}):`, error?.message || error);
204
+ if (outputPath) await import_promises.default.unlink(outputPath).catch(() => {
205
+ });
195
206
  results.push(null);
196
- } finally {
197
- if (outputPath) {
198
- try {
199
- await import_promises.default.unlink(outputPath);
200
- } catch (deleteError) {
201
- }
202
- }
203
207
  }
204
208
  }
205
209
  return results;
206
210
  }
207
- __name(downloadVideosToBase64, "downloadVideosToBase64");
211
+ __name(downloadVideosToTempFiles, "downloadVideosToTempFiles");
212
+ async function cleanupTempFiles(files) {
213
+ await Promise.allSettled(
214
+ files.filter((f) => !!f).map((f) => import_promises.default.unlink(f))
215
+ );
216
+ }
217
+ __name(cleanupTempFiles, "cleanupTempFiles");
208
218
 
209
219
  // src/taskqueue.ts
210
220
  var taskQueue = class {
@@ -237,7 +247,7 @@ var taskQueue = class {
237
247
  // src/index.tsx
238
248
  var import_jsx_runtime = require("@satorijs/element/jsx-runtime");
239
249
  var name = "nitter";
240
- var inject = ["puppeteer", "subscription", "database"];
250
+ var inject = ["puppeteer", "subscription"];
241
251
  var Config = import_koishi.Schema.intersect([
242
252
  import_koishi.Schema.object({
243
253
  apiKey: import_koishi.Schema.string().required().description("Twitter API Key"),
@@ -271,6 +281,7 @@ var Config = import_koishi.Schema.intersect([
271
281
  app: import_koishi.Schema.string().description("subscription配置中应用名")
272
282
  }).description("订阅配置"),
273
283
  import_koishi.Schema.object({
284
+ enablePollingOnStart: import_koishi.Schema.boolean().default(true).description("启动后是否立刻开始轮询"),
274
285
  enableReTweet: import_koishi.Schema.boolean().default(false).description("是否发送转推"),
275
286
  sendPic: import_koishi.Schema.boolean().default(false).description("是否单独发送推文中的图片"),
276
287
  cronString: import_koishi.Schema.string().default("15 */5 * * * *").description("使用cron表达式描述检查更新的时间,默认为每隔5分钟检查一次")
@@ -278,18 +289,12 @@ var Config = import_koishi.Schema.intersect([
278
289
  import_koishi.Schema.union([
279
290
  import_koishi.Schema.object({
280
291
  sendPic: import_koishi.Schema.const(true).required(),
281
- maxSize: import_koishi.Schema.number().default(20).description("发送视频的最大大小,单位为mb")
292
+ maxSize: import_koishi.Schema.number().default(20).description("发送视频的最大大小,单位为mb"),
293
+ tmpDir: import_koishi.Schema.string().default("/shared/tmp").description("临时存放视频的目录,需要koishi和对接的应用都能访问")
282
294
  })
283
295
  ])
284
296
  ]);
285
297
  function apply(ctx, config) {
286
- ctx.model.extend("nitter_records", {
287
- id: "string",
288
- createdAt: "timestamp"
289
- }, {
290
- primary: "id",
291
- autoInc: false
292
- });
293
298
  config.nitterUrl = config.nitterUrl.replace(/\/+$/, "");
294
299
  const twitterClient = new import_rettiwt_api.Rettiwt({
295
300
  apiKey: config.apiKey,
@@ -304,19 +309,30 @@ function apply(ctx, config) {
304
309
  } else if (config.enableTranslate == "openai") {
305
310
  setOpenAiTranslate(config.baseurl, config.openaiApiKey, config.model, config.prompt, config.temperature, config.timeout);
306
311
  }
307
- const tweetList = await getFollowedFeed();
308
- await ctx.database.upsert("nitter_records", tweetList.map((data) => {
309
- return {
310
- id: data.id,
311
- createdAt: new Date(data.createdAt)
312
- };
313
- }));
314
- queue.onProcess(broadcast);
315
- cronJob = (0, import_node_cron.schedule)(config.cronString, checkForUpdates);
316
- ctx.on("dispose", () => {
317
- cronJob.stop();
318
- });
319
- ctx.logger("nitter").info("开始监听推特动态");
312
+ queue.onProcess(
313
+ (task) => {
314
+ return work(
315
+ task.tweetId,
316
+ async (msg) => await ctx.subscription.broadcast(config.app, task.account, msg),
317
+ async (forwardMsg) => await ctx.subscription.broadcastForward(config.app, task.account, forwardMsg)
318
+ );
319
+ }
320
+ );
321
+ if (config.enablePollingOnStart) {
322
+ const tweetList = await getFollowedFeed();
323
+ if (tweetList.length === 0) {
324
+ ctx.logger("nitter").info("初始化失败,请重试");
325
+ return;
326
+ }
327
+ await Promise.all(tweetList.map((data) => {
328
+ return ctx.subscription.checkExist(config.app, data.id);
329
+ }));
330
+ cronJob = (0, import_node_cron.schedule)(config.cronString, checkForUpdates);
331
+ ctx.on("dispose", () => {
332
+ cronJob.stop();
333
+ });
334
+ ctx.logger("nitter").info("开始监听推特动态");
335
+ }
320
336
  })();
321
337
  ctx.command("nitter.follow", "按照subscription中的订阅配置,使用登录的推特账号关注所有需要订阅的账号", { authority: 3 }).action(async () => {
322
338
  const whiteList = ctx.subscription.getAvailableAccounts(config.app);
@@ -327,12 +343,13 @@ function apply(ctx, config) {
327
343
  let user;
328
344
  await retry(3, async () => {
329
345
  user = await twitterClient.user.details(id);
330
- });
346
+ }, (err) => ctx.logger("nitter").error(`获取用户${id}信息失败: ${err}`));
347
+ if (!user) continue;
331
348
  await new Promise((resolve) => setTimeout(resolve, 3 * 1e3));
332
349
  await retry(3, async () => {
333
350
  await twitterClient.user.follow(user.id);
334
- }, 30 * 1e3);
335
- console.log(`关注${id}成功`);
351
+ ctx.logger("nitter").info(`关注${id}成功`);
352
+ }, (err) => ctx.logger("nitter").error(`关注${id}失败: ${err}`));
336
353
  await new Promise((resolve) => setTimeout(resolve, 30 * 1e3));
337
354
  }
338
355
  }
@@ -343,17 +360,11 @@ function apply(ctx, config) {
343
360
  return "请输入推文ID";
344
361
  }
345
362
  try {
346
- const [screenshot, imageUrls, hlsUrls] = await renderTweetScreenshot(tweetId);
347
- let msg = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: "data:image/png;base64," + screenshot.toString("base64") });
348
- let forwardMsg = [];
349
- const videoUrls = await downloadVideosToBase64(hlsUrls, config.maxSize);
350
- if (imageUrls.length > 0) {
351
- forwardMsg.push(...imageUrls.map((url) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: url }) })));
352
- }
353
- if (videoUrls.length > 0) {
354
- forwardMsg.push(...videoUrls.map((url) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("video", { src: url }) })));
355
- }
356
- await session.send(msg + /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { forward: true, children: forwardMsg }));
363
+ await work(
364
+ tweetId,
365
+ async (msg) => await session.send(msg),
366
+ async (forwardMsg) => await session.send(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { forward: true, children: forwardMsg }))
367
+ );
357
368
  } catch (error) {
358
369
  ctx.logger("nitter").error("获取推文失败:", error);
359
370
  return "获取推文失败";
@@ -365,57 +376,82 @@ function apply(ctx, config) {
365
376
  }
366
377
  queue.push({ account, tweetId });
367
378
  });
368
- async function broadcast(task) {
369
- const { account, tweetId } = task;
379
+ async function work(tweetId, sendFunc, sendForwardFunc) {
380
+ let videoFiles = [];
370
381
  try {
371
- const [screenshot, imageUrls, hlsUrls] = await renderTweetScreenshot(tweetId);
382
+ const { screenshot, pieces } = await renderTweetScreenshot(tweetId);
372
383
  const screenshotMsg = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: "data:image/png;base64," + screenshot.toString("base64") });
373
- await ctx.subscription.broadcast(config.app, account, screenshotMsg);
374
- let forwardMsg = [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("message", { children: [
375
- ctx.subscription.getName(config.app, account),
376
- " ",
377
- ctx.subscription.getAccount(config.app, account)
378
- ] })];
379
- const videoUrls = await downloadVideosToBase64(hlsUrls, config.maxSize);
380
- if (imageUrls.length > 0) {
381
- forwardMsg.push(...imageUrls.map((url) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: url }) })));
384
+ await sendFunc(screenshotMsg);
385
+ const forwardMsg = [];
386
+ const videoUrls = pieces.filter((p) => p.type === "video").map((p) => p.url);
387
+ const tmpDir = config.tmpDir ?? "/shared/tmp";
388
+ if (videoUrls.length) {
389
+ videoFiles = await downloadVideosToTempFiles(videoUrls, tmpDir, config.maxSize ?? 20);
382
390
  }
383
- if (videoUrls.length > 0) {
384
- forwardMsg.push(...videoUrls.map((url) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("video", { src: url }) })));
391
+ let vIdx = 0;
392
+ for (const p of pieces) {
393
+ if (p.type === "account") {
394
+ forwardMsg.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: p.text }));
395
+ } else if (p.type === "text") {
396
+ forwardMsg.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: p.text }));
397
+ } else if (p.type === "image") {
398
+ forwardMsg.push(
399
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: p.url }) })
400
+ );
401
+ } else if (p.type === "video") {
402
+ const file = videoFiles[vIdx++];
403
+ if (file) {
404
+ forwardMsg.push(
405
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("video", { src: "file://" + file }) })
406
+ );
407
+ }
408
+ }
385
409
  }
386
410
  if (forwardMsg.length > 0) {
387
- await ctx.subscription.broadcastForward(config.app, account, forwardMsg);
411
+ await sendForwardFunc(forwardMsg);
388
412
  }
389
413
  } catch (error) {
390
414
  ctx.logger("nitter").error("获取推文失败:", error);
415
+ } finally {
416
+ await cleanupTempFiles(videoFiles);
391
417
  }
392
418
  }
393
- __name(broadcast, "broadcast");
419
+ __name(work, "work");
394
420
  async function getFollowedFeed() {
395
421
  let tweetList;
396
- await retry(3, async () => {
422
+ await retry(5, async () => {
397
423
  ({ list: tweetList } = await twitterClient.user.followed());
398
- });
424
+ }, (err) => ctx.logger("nitter").error(`获取推文列表失败: ${err}`));
425
+ if (!tweetList) return [];
399
426
  return tweetList.reverse();
400
427
  }
401
428
  __name(getFollowedFeed, "getFollowedFeed");
429
+ let checking;
402
430
  async function checkForUpdates() {
403
- const tweetList = await getFollowedFeed();
404
- for (const data of tweetList) {
405
- if (!config.enableReTweet && data.retweetedTweet)
406
- continue;
407
- if ((/* @__PURE__ */ new Date()).getTime() - new Date(data.createdAt).getTime() > 2 * 60 * 60 * 1e3)
408
- continue;
409
- if ((await ctx.database.get("nitter_records", { id: data.id })).length > 0)
410
- continue;
411
- await ctx.database.upsert("nitter_records", [{
412
- id: data.id,
413
- createdAt: new Date(data.createdAt)
414
- }]);
415
- if (!ctx.subscription.getAvailableAccounts(config.app).includes(data.tweetBy.userName))
416
- continue;
417
- ctx.logger("nitter").info(`检测到推文id:${data.id},开始推送`);
418
- queue.push({ account: data.tweetBy.userName, tweetId: data.id });
431
+ if (checking) {
432
+ ctx.logger("nitter").info(`${new Date(checking).toLocaleTimeString()}开始的检查尚未完成`);
433
+ return;
434
+ }
435
+ checking = Date.now();
436
+ try {
437
+ const tweetList = await getFollowedFeed();
438
+ if (tweetList.length === 0) {
439
+ return;
440
+ }
441
+ for (const data of tweetList) {
442
+ if (!config.enableReTweet && data.retweetedTweet)
443
+ continue;
444
+ if ((/* @__PURE__ */ new Date()).getTime() - new Date(data.createdAt).getTime() > 2 * 60 * 60 * 1e3)
445
+ continue;
446
+ if (await ctx.subscription.checkExist(config.app, data.id))
447
+ continue;
448
+ if (!ctx.subscription.getAvailableAccounts(config.app).includes(data.tweetBy.userName))
449
+ continue;
450
+ ctx.logger("nitter").info(`检测到推文id:${data.id},开始推送`);
451
+ queue.push({ account: data.tweetBy.userName, tweetId: data.id });
452
+ }
453
+ } finally {
454
+ checking = void 0;
419
455
  }
420
456
  }
421
457
  __name(checkForUpdates, "checkForUpdates");
@@ -429,13 +465,48 @@ function apply(ctx, config) {
429
465
  await page.setCacheEnabled(false);
430
466
  const tweetUrl = `${config.nitterUrl}/i/status/${tweetId}`;
431
467
  await retry(3, async () => {
432
- await page.goto(tweetUrl);
468
+ await page.goto(tweetUrl, { timeout: 12e4 });
433
469
  const element2 = await page.$(".main-thread");
434
470
  if (!element2) throw new Error("Rate Limited");
471
+ }, (err) => {
472
+ throw new Error(`获取推文${tweetId}失败: ${err}`);
435
473
  }, 2e3);
436
- if (config.enableTranslate) {
474
+ const captured = await page.evaluate((baseUrl) => {
475
+ const root = document.querySelector(".main-thread");
476
+ if (!root) return { accountText: "unknown", flow: [] };
477
+ const fullName = (root.querySelector(".fullname")?.textContent || "").trim();
478
+ const username = (root.querySelector(".username")?.textContent || "").trim();
479
+ const accountText = fullName && username ? `${fullName} ${username}` : username || fullName || "unknown";
480
+ const selector = ".tweet-content, .quote-text, a.still-image[href], video[data-url]";
481
+ const nodes = Array.from(root.querySelectorAll(selector));
482
+ const flow = [];
483
+ let textIndex = 0;
484
+ for (const n of nodes) {
485
+ const el = n;
486
+ if (el.classList.contains("tweet-content") || el.classList.contains("quote-text")) {
487
+ const text = (el.innerText || "").replace(/\n{3,}/g, "\n\n").trim();
488
+ flow.push({ type: "text", textIndex, fallbackText: text });
489
+ textIndex++;
490
+ continue;
491
+ }
492
+ if (el.tagName.toLowerCase() === "a" && el.classList.contains("still-image")) {
493
+ const href = el.getAttribute("href");
494
+ if (href) flow.push({ type: "image", url: `${baseUrl}${href}` });
495
+ continue;
496
+ }
497
+ if (el.tagName.toLowerCase() === "video") {
498
+ const dataUrl = el.getAttribute("data-url");
499
+ if (dataUrl) flow.push({ type: "video", url: `${baseUrl}${dataUrl}` });
500
+ continue;
501
+ }
502
+ }
503
+ return { accountText, flow };
504
+ }, config.nitterUrl);
505
+ let translatedTexts = [];
506
+ if (config.enableTranslate != "disable") {
437
507
  try {
438
- await addTranslate(page, ".main-thread .tweet-content, .main-thread .quote-text");
508
+ const result = await addTranslate(page, ".main-thread .tweet-content, .main-thread .quote-text");
509
+ if (result?.length) translatedTexts = result;
439
510
  } catch (e) {
440
511
  ctx.logger("nitter").info("翻译失败", e);
441
512
  }
@@ -479,18 +550,19 @@ function apply(ctx, config) {
479
550
  omitBackground: false,
480
551
  clip: boundingBox
481
552
  });
482
- if (config.sendPic) {
483
- const originalImages = await page.$$eval(
484
- ".main-tweet a.still-image",
485
- (links, baseUrl) => links.map((link) => link.getAttribute("href")).filter((href) => href).map((href) => `${baseUrl}${href}`),
486
- config.nitterUrl
487
- );
488
- const hlsUrls = await page.$$eval(".main-tweet video", (videos, baseUrl) => {
489
- return videos.map((video) => video.getAttribute("data-url")).filter((dataUrl) => dataUrl).map((dataUrl) => `${baseUrl}${dataUrl}`);
490
- }, config.nitterUrl);
491
- return [buffer, originalImages, hlsUrls];
553
+ const pieces = [{ type: "account", text: `${captured.accountText}
554
+ https://x.com/i/status/${tweetId}` }];
555
+ for (const item of captured.flow) {
556
+ if (item.type === "text") {
557
+ const t = translatedTexts[item.textIndex] || item.fallbackText || "";
558
+ if (t.trim()) pieces.push({ type: "text", text: t });
559
+ } else if (item.type === "image" && config.sendPic) {
560
+ pieces.push({ type: "image", url: item.url });
561
+ } else if (item.type === "video" && config.sendPic) {
562
+ pieces.push({ type: "video", url: item.url });
563
+ }
492
564
  }
493
- return [buffer, [], []];
565
+ return { screenshot: buffer, pieces };
494
566
  } catch (e) {
495
567
  throw e;
496
568
  } finally {
package/lib/retry.d.ts CHANGED
@@ -1 +1,2 @@
1
- export declare function retry<T>(retries: number, fn: () => Promise<T> | T, delay?: number): Promise<T>;
1
+ export declare function retry<T>(retries: number, fn: () => Promise<T> | T, error_fn?: (err: any) => void, delay?: number): Promise<T>;
2
+ export declare function withTimeout(promise: any, ms: any, fallbackValue?: any): Promise<any>;
@@ -1,4 +1,4 @@
1
1
  import { Page } from 'puppeteer-core';
2
2
  export declare function setGoogleTranslate(key: string, proxy: string): void;
3
3
  export declare function setOpenAiTranslate(url: string, key: string, model: string, prompt: string, temperature: number, timeout?: number): void;
4
- export declare function addTranslate(page: Page, className: string): Promise<void>;
4
+ export declare function addTranslate(page: Page, className: string): Promise<any>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-nitter",
3
3
  "description": "使用Rettiwt-API订阅推文,并使用nitter渲染",
4
- "version": "0.0.10",
4
+ "version": "0.0.12",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -19,11 +19,11 @@
19
19
  "axios": "^1.12.2",
20
20
  "https-proxy-agent": "^7.0.6",
21
21
  "koishi": "^4.18.9",
22
- "koishi-plugin-subscription": "^0.0.6"
22
+ "koishi-plugin-subscription": "^0.0.7"
23
23
  },
24
24
  "dependencies": {
25
25
  "fluent-ffmpeg": "^2.1.3",
26
26
  "node-cron": "^4.2.1",
27
- "rettiwt-api": "^6.1.7"
27
+ "rettiwt-api": "^6.2.1"
28
28
  }
29
29
  }