koishi-plugin-nitter 0.0.9 → 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,59 +160,58 @@ 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
 
157
167
  // src/index.tsx
158
168
  var import_rettiwt_api = require("rettiwt-api");
169
+ var import_node_cron = require("node-cron");
159
170
 
160
171
  // src/download.ts
161
172
  var import_fluent_ffmpeg = __toESM(require("fluent-ffmpeg"));
162
173
  var import_path = __toESM(require("path"));
163
174
  var import_promises = __toESM(require("fs/promises"));
164
175
  var import_axios2 = __toESM(require("axios"));
165
- var tempDir = import_path.default.join(process.cwd(), "tmp");
166
- async function downloadVideosToBase64(videoUrls, maxSize = 20) {
176
+ async function downloadVideosToTempFiles(videoUrls, tempDir, maxSize = 20) {
167
177
  await import_promises.default.mkdir(tempDir, { recursive: true });
168
178
  const results = [];
169
179
  for (const url of videoUrls) {
170
180
  let outputPath = null;
171
181
  try {
172
- 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
+ );
173
186
  await new Promise((resolve, reject) => {
174
- (0, import_fluent_ffmpeg.default)(url).inputOptions([
175
- "-protocol_whitelist",
176
- "file,http,https,tcp,tls,crypto"
177
- ]).outputOptions([
178
- "-c",
179
- "copy"
180
- ]).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();
181
188
  });
182
189
  const stats = await import_promises.default.stat(outputPath);
183
190
  const fileSizeInMB = stats.size / (1024 * 1024);
184
191
  if (fileSizeInMB > maxSize) {
185
- 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);
186
196
  } else {
187
- const fileBuffer = await import_promises.default.readFile(outputPath);
188
- const base64String = fileBuffer.toString("base64");
189
- const dataUrl = `data:video/mp4;base64,${base64String}`;
190
- results.push(dataUrl);
197
+ results.push(outputPath);
191
198
  }
192
199
  } catch (error) {
193
- console.error(`处理失败 (${url}):`, error.message);
200
+ console.error(`下载失败 (${url}):`, error?.message || error);
201
+ if (outputPath) await import_promises.default.unlink(outputPath).catch(() => {
202
+ });
194
203
  results.push(null);
195
- } finally {
196
- if (outputPath) {
197
- try {
198
- await import_promises.default.unlink(outputPath);
199
- } catch (deleteError) {
200
- }
201
- }
202
204
  }
203
205
  }
204
206
  return results;
205
207
  }
206
- __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");
207
215
 
208
216
  // src/taskqueue.ts
209
217
  var taskQueue = class {
@@ -236,7 +244,7 @@ var taskQueue = class {
236
244
  // src/index.tsx
237
245
  var import_jsx_runtime = require("@satorijs/element/jsx-runtime");
238
246
  var name = "nitter";
239
- var inject = ["puppeteer", "subscription", "database"];
247
+ var inject = ["puppeteer", "subscription"];
240
248
  var Config = import_koishi.Schema.intersect([
241
249
  import_koishi.Schema.object({
242
250
  apiKey: import_koishi.Schema.string().required().description("Twitter API Key"),
@@ -270,6 +278,7 @@ var Config = import_koishi.Schema.intersect([
270
278
  app: import_koishi.Schema.string().description("subscription配置中应用名")
271
279
  }).description("订阅配置"),
272
280
  import_koishi.Schema.object({
281
+ enablePollingOnStart: import_koishi.Schema.boolean().default(true).description("启动后是否立刻开始轮询"),
273
282
  enableReTweet: import_koishi.Schema.boolean().default(false).description("是否发送转推"),
274
283
  sendPic: import_koishi.Schema.boolean().default(false).description("是否单独发送推文中的图片"),
275
284
  cronString: import_koishi.Schema.string().default("15 */5 * * * *").description("使用cron表达式描述检查更新的时间,默认为每隔5分钟检查一次")
@@ -277,18 +286,12 @@ var Config = import_koishi.Schema.intersect([
277
286
  import_koishi.Schema.union([
278
287
  import_koishi.Schema.object({
279
288
  sendPic: import_koishi.Schema.const(true).required(),
280
- 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和对接的应用都能访问")
281
291
  })
282
292
  ])
283
293
  ]);
284
294
  function apply(ctx, config) {
285
- ctx.model.extend("nitter_records", {
286
- id: "string",
287
- createdAt: "timestamp"
288
- }, {
289
- primary: "id",
290
- autoInc: false
291
- });
292
295
  config.nitterUrl = config.nitterUrl.replace(/\/+$/, "");
293
296
  const twitterClient = new import_rettiwt_api.Rettiwt({
294
297
  apiKey: config.apiKey,
@@ -303,14 +306,30 @@ function apply(ctx, config) {
303
306
  } else if (config.enableTranslate == "openai") {
304
307
  setOpenAiTranslate(config.baseurl, config.openaiApiKey, config.model, config.prompt, config.temperature, config.timeout);
305
308
  }
306
- const tweetList = await getFollowedFeed();
307
- await ctx.database.upsert("nitter_records", tweetList.map((data) => {
308
- return {
309
- id: data.id,
310
- createdAt: new Date(data.createdAt)
311
- };
312
- }));
313
- queue.onProcess(broadcast);
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
+ }
314
333
  })();
315
334
  ctx.command("nitter.follow", "按照subscription中的订阅配置,使用登录的推特账号关注所有需要订阅的账号", { authority: 3 }).action(async () => {
316
335
  const whiteList = ctx.subscription.getAvailableAccounts(config.app);
@@ -321,12 +340,13 @@ function apply(ctx, config) {
321
340
  let user;
322
341
  await retry(3, async () => {
323
342
  user = await twitterClient.user.details(id);
324
- });
343
+ }, (err) => ctx.logger("nitter").error(`获取用户${id}信息失败: ${err}`));
344
+ if (!user) continue;
325
345
  await new Promise((resolve) => setTimeout(resolve, 3 * 1e3));
326
346
  await retry(3, async () => {
327
347
  await twitterClient.user.follow(user.id);
328
- }, 30 * 1e3);
329
- console.log(`关注${id}成功`);
348
+ ctx.logger("nitter").info(`关注${id}成功`);
349
+ }, (err) => ctx.logger("nitter").error(`关注${id}失败: ${err}`));
330
350
  await new Promise((resolve) => setTimeout(resolve, 30 * 1e3));
331
351
  }
332
352
  }
@@ -337,17 +357,11 @@ function apply(ctx, config) {
337
357
  return "请输入推文ID";
338
358
  }
339
359
  try {
340
- const [screenshot, imageUrls, hlsUrls] = await renderTweetScreenshot(tweetId);
341
- let msg = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: "data:image/png;base64," + screenshot.toString("base64") });
342
- let forwardMsg = [];
343
- const videoUrls = await downloadVideosToBase64(hlsUrls, config.maxSize);
344
- if (imageUrls.length > 0) {
345
- forwardMsg.push(...imageUrls.map((url) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: url }) })));
346
- }
347
- if (videoUrls.length > 0) {
348
- forwardMsg.push(...videoUrls.map((url) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("video", { src: url }) })));
349
- }
350
- 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
+ );
351
365
  } catch (error) {
352
366
  ctx.logger("nitter").error("获取推文失败:", error);
353
367
  return "获取推文失败";
@@ -359,53 +373,79 @@ function apply(ctx, config) {
359
373
  }
360
374
  queue.push({ account, tweetId });
361
375
  });
362
- async function broadcast(task) {
363
- const { account, tweetId } = task;
376
+ async function work(tweetId, sendFunc, sendForwardFunc) {
377
+ let videoFiles = [];
364
378
  try {
365
- const [screenshot, imageUrls, hlsUrls] = await renderTweetScreenshot(tweetId);
379
+ const { screenshot, pieces } = await renderTweetScreenshot(tweetId);
366
380
  const screenshotMsg = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: "data:image/png;base64," + screenshot.toString("base64") });
367
- await ctx.subscription.broadcast(config.app, account, screenshotMsg);
368
- let forwardMsg = [`${ctx.subscription.getName(config.app, account)} ${ctx.subscription.getAccount(config.app, account)}`];
369
- const videoUrls = await downloadVideosToBase64(hlsUrls, config.maxSize);
370
- if (imageUrls.length > 0) {
371
- 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);
372
387
  }
373
- if (videoUrls.length > 0) {
374
- 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
+ }
375
406
  }
376
407
  if (forwardMsg.length > 0) {
377
- await ctx.subscription.broadcastForward(config.app, account, forwardMsg);
408
+ await sendForwardFunc(forwardMsg);
378
409
  }
379
410
  } catch (error) {
380
411
  ctx.logger("nitter").error("获取推文失败:", error);
412
+ } finally {
413
+ await cleanupTempFiles(videoFiles);
381
414
  }
382
415
  }
383
- __name(broadcast, "broadcast");
416
+ __name(work, "work");
384
417
  async function getFollowedFeed() {
385
418
  let tweetList;
386
- await retry(3, async () => {
419
+ await retry(5, async () => {
387
420
  ({ list: tweetList } = await twitterClient.user.followed());
388
- });
421
+ }, (err) => ctx.logger("nitter").error(`获取推文列表失败: ${err}`));
422
+ if (!tweetList) return [];
389
423
  return tweetList.reverse();
390
424
  }
391
425
  __name(getFollowedFeed, "getFollowedFeed");
426
+ let checking = false;
392
427
  async function checkForUpdates() {
393
- const tweetList = await getFollowedFeed();
394
- for (const data of tweetList) {
395
- if (!config.enableReTweet && data.retweetedTweet)
396
- continue;
397
- if ((/* @__PURE__ */ new Date()).getTime() - new Date(data.createdAt).getTime() > 2 * 60 * 60 * 1e3)
398
- continue;
399
- if ((await ctx.database.get("nitter_records", { id: data.id })).length > 0)
400
- continue;
401
- await ctx.database.upsert("nitter_records", [{
402
- id: data.id,
403
- createdAt: new Date(data.createdAt)
404
- }]);
405
- if (!ctx.subscription.getAvailableAccounts(config.app).includes(data.tweetBy.userName))
406
- continue;
407
- ctx.logger("nitter").info(`检测到推文id:${data.id},开始推送`);
408
- 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;
409
449
  }
410
450
  }
411
451
  __name(checkForUpdates, "checkForUpdates");
@@ -419,13 +459,48 @@ function apply(ctx, config) {
419
459
  await page.setCacheEnabled(false);
420
460
  const tweetUrl = `${config.nitterUrl}/i/status/${tweetId}`;
421
461
  await retry(3, async () => {
422
- await page.goto(tweetUrl);
462
+ await page.goto(tweetUrl, { timeout: 12e4 });
423
463
  const element2 = await page.$(".main-thread");
424
464
  if (!element2) throw new Error("Rate Limited");
465
+ }, (err) => {
466
+ throw new Error(`获取推文${tweetId}失败: ${err}`);
425
467
  }, 2e3);
426
- 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") {
427
501
  try {
428
- 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;
429
504
  } catch (e) {
430
505
  ctx.logger("nitter").info("翻译失败", e);
431
506
  }
@@ -469,18 +544,19 @@ function apply(ctx, config) {
469
544
  omitBackground: false,
470
545
  clip: boundingBox
471
546
  });
472
- if (config.sendPic) {
473
- const originalImages = await page.$$eval(
474
- ".main-tweet a.still-image",
475
- (links, baseUrl) => links.map((link) => link.getAttribute("href")).filter((href) => href).map((href) => `${baseUrl}${href}`),
476
- config.nitterUrl
477
- );
478
- const hlsUrls = await page.$$eval(".main-tweet video", (videos, baseUrl) => {
479
- return videos.map((video) => video.getAttribute("data-url")).filter((dataUrl) => dataUrl).map((dataUrl) => `${baseUrl}${dataUrl}`);
480
- }, config.nitterUrl);
481
- 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
+ }
482
558
  }
483
- return [buffer, [], []];
559
+ return { screenshot: buffer, pieces };
484
560
  } catch (e) {
485
561
  throw e;
486
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.9",
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",