koishi-plugin-nitter 0.0.16 → 0.0.18
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 +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +102 -10
- package/package.json +1 -1
- package/readme.md +2 -2
package/lib/download.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare function downloadImagesToBase64(imageUrls:
|
|
1
|
+
export declare function downloadImagesToBase64(imageUrls: string[]): Promise<Array<string | null>>;
|
|
2
2
|
export declare function downloadVideosToTempFiles(videoUrls: string[], tempDir: string, maxSize?: number): Promise<string[]>;
|
|
3
3
|
export declare function cleanupTempFiles(files: Array<string | null>): Promise<void>;
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -176,6 +176,19 @@ var import_fluent_ffmpeg = __toESM(require("fluent-ffmpeg"));
|
|
|
176
176
|
var import_path = __toESM(require("path"));
|
|
177
177
|
var import_promises = __toESM(require("fs/promises"));
|
|
178
178
|
var import_axios2 = __toESM(require("axios"));
|
|
179
|
+
async function downloadImagesToBase64(imageUrls) {
|
|
180
|
+
return Promise.all(imageUrls.map(async (url) => {
|
|
181
|
+
try {
|
|
182
|
+
const response = await import_axios2.default.get(url, { responseType: "arraybuffer" });
|
|
183
|
+
const base64 = Buffer.from(response.data).toString("base64");
|
|
184
|
+
const contentType = response.headers?.["content-type"] || "image/jpeg";
|
|
185
|
+
return `data:${contentType};base64,${base64}`;
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}));
|
|
190
|
+
}
|
|
191
|
+
__name(downloadImagesToBase64, "downloadImagesToBase64");
|
|
179
192
|
async function downloadVideosToTempFiles(videoUrls, tempDir, maxSize = 20) {
|
|
180
193
|
await import_promises.default.mkdir(tempDir, { recursive: true });
|
|
181
194
|
const results = [];
|
|
@@ -278,7 +291,8 @@ var Config = import_koishi.Schema.intersect([
|
|
|
278
291
|
import_koishi.Schema.object({})
|
|
279
292
|
]),
|
|
280
293
|
import_koishi.Schema.object({
|
|
281
|
-
app: import_koishi.Schema.string().description("subscription配置中应用名")
|
|
294
|
+
app: import_koishi.Schema.string().description("subscription配置中应用名"),
|
|
295
|
+
ownerAccount: import_koishi.Schema.string().description("主人账号。轮询连续失败3次后会停止轮询并向该账号发送告警。可填写 userId,或 platform:userId 指定平台。")
|
|
282
296
|
}).description("订阅配置"),
|
|
283
297
|
import_koishi.Schema.object({
|
|
284
298
|
enablePollingOnStart: import_koishi.Schema.boolean().default(true).description("启动后是否立刻开始轮询"),
|
|
@@ -302,6 +316,7 @@ function apply(ctx, config) {
|
|
|
302
316
|
logging: true
|
|
303
317
|
});
|
|
304
318
|
let cronJob;
|
|
319
|
+
let pollingFailures = 0;
|
|
305
320
|
const queue = new taskQueue();
|
|
306
321
|
(async () => {
|
|
307
322
|
if (config.enableTranslate == "google") {
|
|
@@ -336,7 +351,21 @@ function apply(ctx, config) {
|
|
|
336
351
|
})();
|
|
337
352
|
ctx.command("nitter.follow", "按照subscription中的订阅配置,使用登录的推特账号关注所有需要订阅的账号", { authority: 3 }).action(async () => {
|
|
338
353
|
const whiteList = ctx.subscription.getAvailableAccounts(config.app);
|
|
339
|
-
const
|
|
354
|
+
const followingList = [];
|
|
355
|
+
let cursor = void 0;
|
|
356
|
+
while (true) {
|
|
357
|
+
const res = await retry(
|
|
358
|
+
3,
|
|
359
|
+
async () => {
|
|
360
|
+
return await twitterClient.user.following(void 0, 100, cursor);
|
|
361
|
+
},
|
|
362
|
+
(err) => ctx.logger("nitter").error(`获取已关注账号列表失败: ${err}`)
|
|
363
|
+
);
|
|
364
|
+
followingList.push(...res.list);
|
|
365
|
+
if (!res.next || res.next.split("|")[0] == "0") break;
|
|
366
|
+
cursor = res.next;
|
|
367
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
368
|
+
}
|
|
340
369
|
const followingIdList = followingList.map((user) => user.userName);
|
|
341
370
|
for (const id of whiteList) {
|
|
342
371
|
if (!followingIdList.includes(id)) {
|
|
@@ -350,7 +379,7 @@ function apply(ctx, config) {
|
|
|
350
379
|
await twitterClient.user.follow(user.id);
|
|
351
380
|
ctx.logger("nitter").info(`关注${id}成功`);
|
|
352
381
|
}, (err) => ctx.logger("nitter").error(`关注${id}失败: ${err}`));
|
|
353
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
382
|
+
await new Promise((resolve) => setTimeout(resolve, 60 * 1e3));
|
|
354
383
|
}
|
|
355
384
|
}
|
|
356
385
|
return "关注完成";
|
|
@@ -384,10 +413,13 @@ function apply(ctx, config) {
|
|
|
384
413
|
await sendFunc(screenshotMsg);
|
|
385
414
|
const forwardMsg = [];
|
|
386
415
|
const videoUrls = pieces.filter((p) => p.type === "video").map((p) => p.url);
|
|
416
|
+
const imageUrls = pieces.filter((p) => p.type === "image").map((p) => p.url);
|
|
387
417
|
const tmpDir = config.tmpDir ?? "/shared/tmp";
|
|
418
|
+
const imageSources = imageUrls.length ? await downloadImagesToBase64(imageUrls) : [];
|
|
388
419
|
if (videoUrls.length) {
|
|
389
420
|
videoFiles = await downloadVideosToTempFiles(videoUrls, tmpDir, config.maxSize ?? 20);
|
|
390
421
|
}
|
|
422
|
+
let iIdx = 0;
|
|
391
423
|
let vIdx = 0;
|
|
392
424
|
for (const p of pieces) {
|
|
393
425
|
if (p.type === "account") {
|
|
@@ -395,9 +427,12 @@ function apply(ctx, config) {
|
|
|
395
427
|
} else if (p.type === "text") {
|
|
396
428
|
forwardMsg.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: p.text }));
|
|
397
429
|
} else if (p.type === "image") {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
430
|
+
const src = imageSources[iIdx++];
|
|
431
|
+
if (src) {
|
|
432
|
+
forwardMsg.push(
|
|
433
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src }) })
|
|
434
|
+
);
|
|
435
|
+
}
|
|
401
436
|
} else if (p.type === "video") {
|
|
402
437
|
const file = videoFiles[vIdx++];
|
|
403
438
|
if (file) {
|
|
@@ -436,6 +471,7 @@ function apply(ctx, config) {
|
|
|
436
471
|
checking = Date.now();
|
|
437
472
|
try {
|
|
438
473
|
const tweetList = await getFollowedFeed();
|
|
474
|
+
pollingFailures = 0;
|
|
439
475
|
if (tweetList.length === 0) {
|
|
440
476
|
return;
|
|
441
477
|
}
|
|
@@ -451,11 +487,67 @@ function apply(ctx, config) {
|
|
|
451
487
|
ctx.logger("nitter").info(`检测到推文id:${data.id},开始推送`);
|
|
452
488
|
queue.push({ account: data.tweetBy.userName, tweetId: data.id });
|
|
453
489
|
}
|
|
490
|
+
} catch (error) {
|
|
491
|
+
pollingFailures += 1;
|
|
492
|
+
ctx.logger("nitter").warn("检查推文更新失败:%o", error);
|
|
493
|
+
if (pollingFailures >= 3) {
|
|
494
|
+
cronJob?.stop();
|
|
495
|
+
const message = `Nitter 轮询连续失败 ${pollingFailures} 次,已停止轮询。
|
|
496
|
+
${formatError(error)}`;
|
|
497
|
+
ctx.logger("nitter").warn(message);
|
|
498
|
+
await notifyOwner(message);
|
|
499
|
+
}
|
|
454
500
|
} finally {
|
|
455
501
|
checking = void 0;
|
|
456
502
|
}
|
|
457
503
|
}
|
|
458
504
|
__name(checkForUpdates, "checkForUpdates");
|
|
505
|
+
async function notifyOwner(message) {
|
|
506
|
+
const owner = parseOwnerAccount(config.ownerAccount);
|
|
507
|
+
if (!owner) return;
|
|
508
|
+
try {
|
|
509
|
+
const bot = await findOwnerBot(owner.platform);
|
|
510
|
+
if (!bot) {
|
|
511
|
+
ctx.logger("nitter").warn("未找到可用于发送 Nitter 轮询告警的 bot");
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
await bot.sendPrivateMessage(owner.userId, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: message }));
|
|
515
|
+
} catch (error) {
|
|
516
|
+
ctx.logger("nitter").warn("发送 Nitter 轮询告警失败:%o", error);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
__name(notifyOwner, "notifyOwner");
|
|
520
|
+
async function findOwnerBot(platform) {
|
|
521
|
+
if (platform) return ctx.bots.find((bot) => bot.platform === platform);
|
|
522
|
+
const accounts = ctx.subscription.getAvailableAccounts(config.app);
|
|
523
|
+
for (const account of accounts) {
|
|
524
|
+
const groups = await ctx.subscription.getSubscribedGroups(config.app, account);
|
|
525
|
+
for (const group of groups) {
|
|
526
|
+
const groupPlatform = group.split(":")[0];
|
|
527
|
+
const bot = ctx.bots.find((bot2) => bot2.platform === groupPlatform);
|
|
528
|
+
if (bot) return bot;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return ctx.bots[0];
|
|
532
|
+
}
|
|
533
|
+
__name(findOwnerBot, "findOwnerBot");
|
|
534
|
+
function parseOwnerAccount(input) {
|
|
535
|
+
const value = input?.trim();
|
|
536
|
+
if (!value) return null;
|
|
537
|
+
const index = value.indexOf(":");
|
|
538
|
+
if (index > 0) {
|
|
539
|
+
return {
|
|
540
|
+
platform: value.slice(0, index),
|
|
541
|
+
userId: value.slice(index + 1)
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
return { userId: value };
|
|
545
|
+
}
|
|
546
|
+
__name(parseOwnerAccount, "parseOwnerAccount");
|
|
547
|
+
function formatError(error) {
|
|
548
|
+
return error?.message || String(error);
|
|
549
|
+
}
|
|
550
|
+
__name(formatError, "formatError");
|
|
459
551
|
async function renderTweetScreenshot(tweetId) {
|
|
460
552
|
const puppeteer = ctx.puppeteer;
|
|
461
553
|
if (!puppeteer) {
|
|
@@ -523,15 +615,15 @@ function apply(ctx, config) {
|
|
|
523
615
|
const element2 = document.querySelector(".main-thread");
|
|
524
616
|
if (!element2) return;
|
|
525
617
|
Object.assign(element2.style, {
|
|
526
|
-
border: "
|
|
618
|
+
border: "3px solid transparent",
|
|
527
619
|
borderRadius: "8px",
|
|
528
|
-
|
|
620
|
+
background: "linear-gradient(#fff, #fff) padding-box, linear-gradient(135deg, #1DA1F2 0%, #35C2FF 35%, #6EE7F9 68%, #A7F3D0 100%) border-box",
|
|
621
|
+
boxShadow: "0 8px 24px rgba(29, 161, 242, 0.18)",
|
|
529
622
|
margin: "20px",
|
|
530
623
|
boxSizing: "border-box",
|
|
531
624
|
overflow: "hidden",
|
|
532
625
|
width: "100%",
|
|
533
|
-
padding: "20px 20px 10px 20px"
|
|
534
|
-
backgroundColor: "#fff"
|
|
626
|
+
padding: "20px 20px 10px 20px"
|
|
535
627
|
});
|
|
536
628
|
});
|
|
537
629
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
# koishi-plugin-
|
|
1
|
+
# koishi-plugin-nitter
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/koishi-plugin-twitter)
|
|
4
4
|
|
|
5
5
|
推文订阅
|