koishi-plugin-nitter 0.0.10 → 0.0.11

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