koishi-plugin-nitter 0.0.5 → 0.0.7

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/index.d.ts CHANGED
@@ -14,8 +14,10 @@ export interface Config {
14
14
  temperature: number;
15
15
  timeout?: number;
16
16
  app: string;
17
- sendPic: boolean;
18
17
  enableReTweet: boolean;
18
+ sendPic: boolean;
19
+ maxSize: number;
19
20
  }
20
21
  export declare const Config: Schema<Config, Dict>;
21
22
  export declare function apply(ctx: Context, config: Config): void;
23
+ export declare function downloadVideosToBase64(videoUrls: any, maxSize?: number): Promise<any[]>;
package/lib/index.js CHANGED
@@ -32,6 +32,7 @@ var src_exports = {};
32
32
  __export(src_exports, {
33
33
  Config: () => Config,
34
34
  apply: () => apply,
35
+ downloadVideosToBase64: () => downloadVideosToBase64,
35
36
  inject: () => inject,
36
37
  name: () => name
37
38
  });
@@ -154,13 +155,11 @@ __name(addTranslate, "addTranslate");
154
155
  var import_rettiwt_api = require("rettiwt-api");
155
156
  var import_node_cron = require("node-cron");
156
157
  var import_fluent_ffmpeg = __toESM(require("fluent-ffmpeg"));
157
- var import_ffmpeg_static = __toESM(require("ffmpeg-static"));
158
158
  var import_path = __toESM(require("path"));
159
159
  var import_promises = __toESM(require("fs/promises"));
160
- var import_url = require("url");
161
160
  var import_jsx_runtime = require("@satorijs/element/jsx-runtime");
162
- import_fluent_ffmpeg.default.setFfmpegPath(import_ffmpeg_static.default);
163
161
  var name = "nitter";
162
+ var logger = new import_koishi.Logger(name);
164
163
  var inject = ["puppeteer", "subscription"];
165
164
  var Config = import_koishi.Schema.intersect([
166
165
  import_koishi.Schema.object({
@@ -195,9 +194,15 @@ var Config = import_koishi.Schema.intersect([
195
194
  app: import_koishi.Schema.string().description("subscription配置中应用名")
196
195
  }).description("订阅配置"),
197
196
  import_koishi.Schema.object({
198
- sendPic: import_koishi.Schema.boolean().default(false).description("是否单独发送推文中的图片"),
199
- enableReTweet: import_koishi.Schema.boolean().default(false).description("是否发送转推")
200
- }).description("推送设置")
197
+ enableReTweet: import_koishi.Schema.boolean().default(false).description("是否发送转推"),
198
+ sendPic: import_koishi.Schema.boolean().default(false).description("是否单独发送推文中的图片")
199
+ }).description("推送设置"),
200
+ import_koishi.Schema.union([
201
+ import_koishi.Schema.object({
202
+ sendPic: import_koishi.Schema.const(true).required(),
203
+ maxSize: import_koishi.Schema.number().default(10).description("发送视频的最大大小,单位为mb")
204
+ })
205
+ ])
201
206
  ]);
202
207
  function apply(ctx, config) {
203
208
  config.nitterUrl = config.nitterUrl.replace(/\/+$/, "");
@@ -248,15 +253,14 @@ function apply(ctx, config) {
248
253
  const [screenshot, imageUrls, hlsUrls] = await renderTweetScreenshot(ctx, tweetId, config);
249
254
  let msg = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: "data:image/png;base64," + screenshot.toString("base64") });
250
255
  let forwardMsg = [];
251
- const videoUrls = await downloadAndConvertVideos(hlsUrls);
256
+ const videoUrls = await downloadVideosToBase64(hlsUrls, config.maxSize);
252
257
  if (imageUrls.length > 0) {
253
258
  forwardMsg.push(...imageUrls.map((url) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: url }) })));
254
259
  }
255
260
  if (videoUrls.length > 0) {
256
- forwardMsg.push(...videoUrls.map((url) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("video", { src: (0, import_url.pathToFileURL)(url).href }) })));
261
+ forwardMsg.push(...videoUrls.map((url) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("video", { src: url }) })));
257
262
  }
258
263
  await session.send(msg + /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { forward: true, children: forwardMsg }));
259
- await deleteFiles(videoUrls);
260
264
  } catch (error) {
261
265
  ctx.logger("nitter").error("获取推文失败:", error);
262
266
  return "获取推文失败";
@@ -273,18 +277,17 @@ function apply(ctx, config) {
273
277
  const [screenshot, imageUrls, hlsUrls] = await renderTweetScreenshot(ctx, tweetId, config);
274
278
  const screenshotMsg = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: "data:image/png;base64," + screenshot.toString("base64") });
275
279
  ctx.subscription.broadcast(config.app, account, screenshotMsg);
276
- let forwardMsg = ``;
277
- const videoUrls = await downloadAndConvertVideos(hlsUrls);
280
+ let forwardMsg = [];
281
+ const videoUrls = await downloadVideosToBase64(hlsUrls, config.maxSize);
278
282
  if (imageUrls.length > 0) {
279
- forwardMsg += imageUrls.map((url) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: url }) }));
283
+ forwardMsg.push(...imageUrls.map((url) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: url }) })));
280
284
  }
281
285
  if (videoUrls.length > 0) {
282
- forwardMsg += videoUrls.map((url) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("video", { src: (0, import_url.pathToFileURL)(url).href }) }));
286
+ forwardMsg.push(...videoUrls.map((url) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("video", { src: url }) })));
283
287
  }
284
288
  if (forwardMsg.length > 0) {
285
289
  await ctx.subscription.broadcastForward(config.app, account, forwardMsg);
286
290
  }
287
- await deleteFiles(videoUrls);
288
291
  } catch (error) {
289
292
  ctx.logger("nitter").error("获取推文失败:", error);
290
293
  }
@@ -316,33 +319,47 @@ function apply(ctx, config) {
316
319
  }
317
320
  __name(apply, "apply");
318
321
  var tempDir = import_path.default.join(process.cwd(), "tmp");
319
- async function downloadAndConvertVideos(videoUrls) {
322
+ async function downloadVideosToBase64(videoUrls, maxSize = 20) {
320
323
  await import_promises.default.mkdir(tempDir, { recursive: true });
321
- const promises = videoUrls.map(async (url, index) => {
324
+ const results = [];
325
+ for (const url of videoUrls) {
326
+ let outputPath = null;
322
327
  try {
323
- const outputPath = import_path.default.join(tempDir, `video_${index}_${Date.now()}.mp4`);
328
+ outputPath = import_path.default.join(tempDir, `video_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.mp4`);
324
329
  await new Promise((resolve, reject) => {
325
- (0, import_fluent_ffmpeg.default)(url).output(outputPath).on("end", () => {
326
- resolve(outputPath);
327
- }).on("error", reject).run();
330
+ (0, import_fluent_ffmpeg.default)(url).inputOptions([
331
+ "-protocol_whitelist",
332
+ "file,http,https,tcp,tls,crypto"
333
+ ]).outputOptions([
334
+ "-c",
335
+ "copy"
336
+ ]).output(outputPath).on("end", () => resolve(outputPath)).on("error", reject).run();
328
337
  });
329
- return outputPath;
338
+ const stats = await import_promises.default.stat(outputPath);
339
+ const fileSizeInMB = stats.size / (1024 * 1024);
340
+ if (fileSizeInMB > maxSize) {
341
+ logger.info(`视频大小 ${fileSizeInMB.toFixed(2)}MB 超过${maxSize}MB限制,跳过转换`);
342
+ } else {
343
+ const fileBuffer = await import_promises.default.readFile(outputPath);
344
+ const base64String = fileBuffer.toString("base64");
345
+ const dataUrl = `data:video/mp4;base64,${base64String}`;
346
+ results.push(dataUrl);
347
+ }
330
348
  } catch (error) {
331
349
  console.error(`处理失败 (${url}):`, error.message);
332
- return null;
350
+ results.push(null);
351
+ } finally {
352
+ if (outputPath) {
353
+ try {
354
+ await import_promises.default.unlink(outputPath);
355
+ } catch (deleteError) {
356
+ }
357
+ }
333
358
  }
334
- });
335
- const results = await Promise.all(promises);
336
- return results.filter((path2) => path2 !== null);
337
- }
338
- __name(downloadAndConvertVideos, "downloadAndConvertVideos");
339
- async function deleteFiles(filePaths) {
340
- await Promise.allSettled(
341
- filePaths.map((filePath) => import_promises.default.unlink(filePath).catch(() => {
342
- }))
343
- );
359
+ }
360
+ return results;
344
361
  }
345
- __name(deleteFiles, "deleteFiles");
362
+ __name(downloadVideosToBase64, "downloadVideosToBase64");
346
363
  async function renderTweetScreenshot(ctx, tweetId, config) {
347
364
  const puppeteer = ctx.puppeteer;
348
365
  if (!puppeteer) {
@@ -399,6 +416,7 @@ __name(renderTweetScreenshot, "renderTweetScreenshot");
399
416
  0 && (module.exports = {
400
417
  Config,
401
418
  apply,
419
+ downloadVideosToBase64,
402
420
  inject,
403
421
  name
404
422
  });
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.5",
4
+ "version": "0.0.7",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -24,7 +24,6 @@
24
24
  "dependencies": {
25
25
  "node-cron": "^4.2.1",
26
26
  "rettiwt-api": "^6.0.6",
27
- "ffmpeg-static": "^5.2.0",
28
27
  "fluent-ffmpeg": "^2.1.3"
29
28
  }
30
29
  }