koishi-plugin-nitter 0.0.7 → 0.0.9
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 +2 -0
- package/lib/fixedSet.d.ts +7 -0
- package/lib/index.d.ts +9 -1
- package/lib/index.js +203 -126
- package/lib/retry.d.ts +1 -0
- package/lib/taskqueue.d.ts +8 -0
- package/lib/translate.d.ts +0 -2
- package/package.json +4 -4
package/lib/index.d.ts
CHANGED
|
@@ -16,8 +16,16 @@ export interface Config {
|
|
|
16
16
|
app: string;
|
|
17
17
|
enableReTweet: boolean;
|
|
18
18
|
sendPic: boolean;
|
|
19
|
+
cronString: string;
|
|
19
20
|
maxSize: number;
|
|
20
21
|
}
|
|
22
|
+
declare module 'koishi' {
|
|
23
|
+
interface Tables {
|
|
24
|
+
nitter_records: {
|
|
25
|
+
id: string;
|
|
26
|
+
createdAt: Date;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
21
30
|
export declare const Config: Schema<Config, Dict>;
|
|
22
31
|
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,7 +32,6 @@ var src_exports = {};
|
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
Config: () => Config,
|
|
34
34
|
apply: () => apply,
|
|
35
|
-
downloadVideosToBase64: () => downloadVideosToBase64,
|
|
36
35
|
inject: () => inject,
|
|
37
36
|
name: () => name
|
|
38
37
|
});
|
|
@@ -42,18 +41,21 @@ var import_koishi = require("koishi");
|
|
|
42
41
|
// src/translate.ts
|
|
43
42
|
var import_axios = __toESM(require("axios"));
|
|
44
43
|
var import_https_proxy_agent = require("https-proxy-agent");
|
|
44
|
+
|
|
45
|
+
// src/retry.ts
|
|
45
46
|
async function retry(retries, fn, delay = 500) {
|
|
46
47
|
try {
|
|
47
|
-
await fn();
|
|
48
|
-
return Promise.resolve();
|
|
48
|
+
return await fn();
|
|
49
49
|
} catch (err) {
|
|
50
50
|
console.log(`剩余${retries}次尝试`, err);
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
if (retries <= 1) throw err;
|
|
52
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
53
|
+
return retry(retries - 1, fn, delay * 2);
|
|
53
54
|
}
|
|
54
|
-
;
|
|
55
55
|
}
|
|
56
56
|
__name(retry, "retry");
|
|
57
|
+
|
|
58
|
+
// src/translate.ts
|
|
57
59
|
var translate;
|
|
58
60
|
function setGoogleTranslate(key, proxy) {
|
|
59
61
|
translate = /* @__PURE__ */ __name((texts) => {
|
|
@@ -109,6 +111,7 @@ json输出格式:["翻译结果1", "翻译结果2", ...]`
|
|
|
109
111
|
});
|
|
110
112
|
if (response.data && response.data.choices && response.data.choices[0]) {
|
|
111
113
|
const content = response.data.choices[0].message.content;
|
|
114
|
+
console.log(content);
|
|
112
115
|
data = JSON.parse(content);
|
|
113
116
|
if (!Array.isArray(data)) {
|
|
114
117
|
if (typeof data === "object" && Object.keys(data).length === 1) {
|
|
@@ -122,7 +125,7 @@ json输出格式:["翻译结果1", "翻译结果2", ...]`
|
|
|
122
125
|
throw new Error("API返回数据格式异常");
|
|
123
126
|
}
|
|
124
127
|
});
|
|
125
|
-
return data;
|
|
128
|
+
return data.map((content) => content.replace(/\\n/g, "\n"));
|
|
126
129
|
}, "translate");
|
|
127
130
|
}
|
|
128
131
|
__name(setOpenAiTranslate, "setOpenAiTranslate");
|
|
@@ -142,8 +145,8 @@ async function addTranslate(page, className) {
|
|
|
142
145
|
tempDiv.innerHTML = translatedHTMLs2[index];
|
|
143
146
|
const newElement = tempDiv.firstElementChild;
|
|
144
147
|
if (newElement) {
|
|
145
|
-
element.insertAdjacentElement("
|
|
146
|
-
element.insertAdjacentHTML("
|
|
148
|
+
element.insertAdjacentElement("beforebegin", newElement);
|
|
149
|
+
element.insertAdjacentHTML("beforebegin", '<div style="height: 1px;background-color: #ccc; margin-top: 10px; margin-bottom: 10px;"></div>');
|
|
147
150
|
}
|
|
148
151
|
}
|
|
149
152
|
});
|
|
@@ -153,14 +156,87 @@ __name(addTranslate, "addTranslate");
|
|
|
153
156
|
|
|
154
157
|
// src/index.tsx
|
|
155
158
|
var import_rettiwt_api = require("rettiwt-api");
|
|
156
|
-
|
|
159
|
+
|
|
160
|
+
// src/download.ts
|
|
157
161
|
var import_fluent_ffmpeg = __toESM(require("fluent-ffmpeg"));
|
|
158
162
|
var import_path = __toESM(require("path"));
|
|
159
163
|
var import_promises = __toESM(require("fs/promises"));
|
|
164
|
+
var import_axios2 = __toESM(require("axios"));
|
|
165
|
+
var tempDir = import_path.default.join(process.cwd(), "tmp");
|
|
166
|
+
async function downloadVideosToBase64(videoUrls, maxSize = 20) {
|
|
167
|
+
await import_promises.default.mkdir(tempDir, { recursive: true });
|
|
168
|
+
const results = [];
|
|
169
|
+
for (const url of videoUrls) {
|
|
170
|
+
let outputPath = null;
|
|
171
|
+
try {
|
|
172
|
+
outputPath = import_path.default.join(tempDir, `video_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.mp4`);
|
|
173
|
+
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();
|
|
181
|
+
});
|
|
182
|
+
const stats = await import_promises.default.stat(outputPath);
|
|
183
|
+
const fileSizeInMB = stats.size / (1024 * 1024);
|
|
184
|
+
if (fileSizeInMB > maxSize) {
|
|
185
|
+
console.log(`视频大小 ${fileSizeInMB.toFixed(2)}MB 超过${maxSize}MB限制,跳过转换`);
|
|
186
|
+
} 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);
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.error(`处理失败 (${url}):`, error.message);
|
|
194
|
+
results.push(null);
|
|
195
|
+
} finally {
|
|
196
|
+
if (outputPath) {
|
|
197
|
+
try {
|
|
198
|
+
await import_promises.default.unlink(outputPath);
|
|
199
|
+
} catch (deleteError) {
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return results;
|
|
205
|
+
}
|
|
206
|
+
__name(downloadVideosToBase64, "downloadVideosToBase64");
|
|
207
|
+
|
|
208
|
+
// src/taskqueue.ts
|
|
209
|
+
var taskQueue = class {
|
|
210
|
+
static {
|
|
211
|
+
__name(this, "taskQueue");
|
|
212
|
+
}
|
|
213
|
+
queue = [];
|
|
214
|
+
consumer;
|
|
215
|
+
processing = false;
|
|
216
|
+
push(task) {
|
|
217
|
+
this.queue.push(task);
|
|
218
|
+
if (!this.processing) {
|
|
219
|
+
this.process();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
onProcess(handler) {
|
|
223
|
+
this.consumer = handler;
|
|
224
|
+
}
|
|
225
|
+
async process() {
|
|
226
|
+
if (this.processing || !this.consumer) return;
|
|
227
|
+
this.processing = true;
|
|
228
|
+
while (this.queue.length > 0) {
|
|
229
|
+
const task = this.queue.shift();
|
|
230
|
+
await this.consumer(task);
|
|
231
|
+
}
|
|
232
|
+
this.processing = false;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// src/index.tsx
|
|
160
237
|
var import_jsx_runtime = require("@satorijs/element/jsx-runtime");
|
|
161
238
|
var name = "nitter";
|
|
162
|
-
var
|
|
163
|
-
var inject = ["puppeteer", "subscription"];
|
|
239
|
+
var inject = ["puppeteer", "subscription", "database"];
|
|
164
240
|
var Config = import_koishi.Schema.intersect([
|
|
165
241
|
import_koishi.Schema.object({
|
|
166
242
|
apiKey: import_koishi.Schema.string().required().description("Twitter API Key"),
|
|
@@ -195,23 +271,32 @@ var Config = import_koishi.Schema.intersect([
|
|
|
195
271
|
}).description("订阅配置"),
|
|
196
272
|
import_koishi.Schema.object({
|
|
197
273
|
enableReTweet: import_koishi.Schema.boolean().default(false).description("是否发送转推"),
|
|
198
|
-
sendPic: import_koishi.Schema.boolean().default(false).description("是否单独发送推文中的图片")
|
|
274
|
+
sendPic: import_koishi.Schema.boolean().default(false).description("是否单独发送推文中的图片"),
|
|
275
|
+
cronString: import_koishi.Schema.string().default("15 */5 * * * *").description("使用cron表达式描述检查更新的时间,默认为每隔5分钟检查一次")
|
|
199
276
|
}).description("推送设置"),
|
|
200
277
|
import_koishi.Schema.union([
|
|
201
278
|
import_koishi.Schema.object({
|
|
202
279
|
sendPic: import_koishi.Schema.const(true).required(),
|
|
203
|
-
maxSize: import_koishi.Schema.number().default(
|
|
280
|
+
maxSize: import_koishi.Schema.number().default(20).description("发送视频的最大大小,单位为mb")
|
|
204
281
|
})
|
|
205
282
|
])
|
|
206
283
|
]);
|
|
207
284
|
function apply(ctx, config) {
|
|
285
|
+
ctx.model.extend("nitter_records", {
|
|
286
|
+
id: "string",
|
|
287
|
+
createdAt: "timestamp"
|
|
288
|
+
}, {
|
|
289
|
+
primary: "id",
|
|
290
|
+
autoInc: false
|
|
291
|
+
});
|
|
208
292
|
config.nitterUrl = config.nitterUrl.replace(/\/+$/, "");
|
|
209
293
|
const twitterClient = new import_rettiwt_api.Rettiwt({
|
|
210
294
|
apiKey: config.apiKey,
|
|
211
|
-
proxyUrl: config.proxy ? new URL(config.proxy) : void 0
|
|
295
|
+
proxyUrl: config.proxy ? new URL(config.proxy) : void 0,
|
|
296
|
+
logging: true
|
|
212
297
|
});
|
|
213
|
-
let latestTweetId;
|
|
214
298
|
let cronJob;
|
|
299
|
+
const queue = new taskQueue();
|
|
215
300
|
(async () => {
|
|
216
301
|
if (config.enableTranslate == "google") {
|
|
217
302
|
setGoogleTranslate(config.googleApiKey, config.proxy);
|
|
@@ -219,16 +304,13 @@ function apply(ctx, config) {
|
|
|
219
304
|
setOpenAiTranslate(config.baseurl, config.openaiApiKey, config.model, config.prompt, config.temperature, config.timeout);
|
|
220
305
|
}
|
|
221
306
|
const tweetList = await getFollowedFeed();
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
cronJob.stop();
|
|
230
|
-
});
|
|
231
|
-
ctx.logger("nitter").info("开始监听推特动态");
|
|
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);
|
|
232
314
|
})();
|
|
233
315
|
ctx.command("nitter.follow", "按照subscription中的订阅配置,使用登录的推特账号关注所有需要订阅的账号", { authority: 3 }).action(async () => {
|
|
234
316
|
const whiteList = ctx.subscription.getAvailableAccounts(config.app);
|
|
@@ -236,9 +318,14 @@ function apply(ctx, config) {
|
|
|
236
318
|
const followingIdList = followingList.map((user) => user.userName);
|
|
237
319
|
for (const id of whiteList) {
|
|
238
320
|
if (!followingIdList.includes(id)) {
|
|
239
|
-
|
|
321
|
+
let user;
|
|
322
|
+
await retry(3, async () => {
|
|
323
|
+
user = await twitterClient.user.details(id);
|
|
324
|
+
});
|
|
240
325
|
await new Promise((resolve) => setTimeout(resolve, 3 * 1e3));
|
|
241
|
-
await
|
|
326
|
+
await retry(3, async () => {
|
|
327
|
+
await twitterClient.user.follow(user.id);
|
|
328
|
+
}, 30 * 1e3);
|
|
242
329
|
console.log(`关注${id}成功`);
|
|
243
330
|
await new Promise((resolve) => setTimeout(resolve, 30 * 1e3));
|
|
244
331
|
}
|
|
@@ -250,7 +337,7 @@ function apply(ctx, config) {
|
|
|
250
337
|
return "请输入推文ID";
|
|
251
338
|
}
|
|
252
339
|
try {
|
|
253
|
-
const [screenshot, imageUrls, hlsUrls] = await renderTweetScreenshot(
|
|
340
|
+
const [screenshot, imageUrls, hlsUrls] = await renderTweetScreenshot(tweetId);
|
|
254
341
|
let msg = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: "data:image/png;base64," + screenshot.toString("base64") });
|
|
255
342
|
let forwardMsg = [];
|
|
256
343
|
const videoUrls = await downloadVideosToBase64(hlsUrls, config.maxSize);
|
|
@@ -270,14 +357,15 @@ function apply(ctx, config) {
|
|
|
270
357
|
if (!tweetId) {
|
|
271
358
|
return "请输入推文ID";
|
|
272
359
|
}
|
|
273
|
-
|
|
360
|
+
queue.push({ account, tweetId });
|
|
274
361
|
});
|
|
275
|
-
async function broadcast(
|
|
362
|
+
async function broadcast(task) {
|
|
363
|
+
const { account, tweetId } = task;
|
|
276
364
|
try {
|
|
277
|
-
const [screenshot, imageUrls, hlsUrls] = await renderTweetScreenshot(
|
|
365
|
+
const [screenshot, imageUrls, hlsUrls] = await renderTweetScreenshot(tweetId);
|
|
278
366
|
const screenshotMsg = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: "data:image/png;base64," + screenshot.toString("base64") });
|
|
279
|
-
ctx.subscription.broadcast(config.app, account, screenshotMsg);
|
|
280
|
-
let forwardMsg = [];
|
|
367
|
+
await ctx.subscription.broadcast(config.app, account, screenshotMsg);
|
|
368
|
+
let forwardMsg = [`${ctx.subscription.getName(config.app, account)} ${ctx.subscription.getAccount(config.app, account)}`];
|
|
281
369
|
const videoUrls = await downloadVideosToBase64(hlsUrls, config.maxSize);
|
|
282
370
|
if (imageUrls.length > 0) {
|
|
283
371
|
forwardMsg.push(...imageUrls.map((url) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: url }) })));
|
|
@@ -306,117 +394,106 @@ function apply(ctx, config) {
|
|
|
306
394
|
for (const data of tweetList) {
|
|
307
395
|
if (!config.enableReTweet && data.retweetedTweet)
|
|
308
396
|
continue;
|
|
309
|
-
if (data.
|
|
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)
|
|
310
400
|
continue;
|
|
401
|
+
await ctx.database.upsert("nitter_records", [{
|
|
402
|
+
id: data.id,
|
|
403
|
+
createdAt: new Date(data.createdAt)
|
|
404
|
+
}]);
|
|
311
405
|
if (!ctx.subscription.getAvailableAccounts(config.app).includes(data.tweetBy.userName))
|
|
312
406
|
continue;
|
|
313
|
-
latestTweetId = data.id;
|
|
314
407
|
ctx.logger("nitter").info(`检测到推文id:${data.id},开始推送`);
|
|
315
|
-
|
|
408
|
+
queue.push({ account: data.tweetBy.userName, tweetId: data.id });
|
|
316
409
|
}
|
|
317
410
|
}
|
|
318
411
|
__name(checkForUpdates, "checkForUpdates");
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
for (const url of videoUrls) {
|
|
326
|
-
let outputPath = null;
|
|
412
|
+
async function renderTweetScreenshot(tweetId) {
|
|
413
|
+
const puppeteer = ctx.puppeteer;
|
|
414
|
+
if (!puppeteer) {
|
|
415
|
+
throw new Error("Puppeteer 服务未找到,请安装 koishi-plugin-puppeteer");
|
|
416
|
+
}
|
|
417
|
+
const page = await puppeteer.page();
|
|
327
418
|
try {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
]).output(outputPath).on("end", () => resolve(outputPath)).on("error", reject).run();
|
|
337
|
-
});
|
|
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
|
-
}
|
|
348
|
-
} catch (error) {
|
|
349
|
-
console.error(`处理失败 (${url}):`, error.message);
|
|
350
|
-
results.push(null);
|
|
351
|
-
} finally {
|
|
352
|
-
if (outputPath) {
|
|
419
|
+
await page.setCacheEnabled(false);
|
|
420
|
+
const tweetUrl = `${config.nitterUrl}/i/status/${tweetId}`;
|
|
421
|
+
await retry(3, async () => {
|
|
422
|
+
await page.goto(tweetUrl);
|
|
423
|
+
const element2 = await page.$(".main-thread");
|
|
424
|
+
if (!element2) throw new Error("Rate Limited");
|
|
425
|
+
}, 2e3);
|
|
426
|
+
if (config.enableTranslate) {
|
|
353
427
|
try {
|
|
354
|
-
await
|
|
355
|
-
} catch (
|
|
428
|
+
await addTranslate(page, ".main-thread .tweet-content, .main-thread .quote-text");
|
|
429
|
+
} catch (e) {
|
|
430
|
+
ctx.logger("nitter").info("翻译失败", e);
|
|
356
431
|
}
|
|
357
432
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
433
|
+
await page.evaluate(() => {
|
|
434
|
+
const nav = document.querySelector("nav");
|
|
435
|
+
if (nav) {
|
|
436
|
+
nav.style.visibility = "hidden";
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
440
|
+
await page.evaluate(() => {
|
|
441
|
+
const element2 = document.querySelector(".main-thread");
|
|
442
|
+
if (!element2) return;
|
|
443
|
+
Object.assign(element2.style, {
|
|
444
|
+
border: "1px solid #1DA1F2",
|
|
445
|
+
borderRadius: "8px",
|
|
446
|
+
boxShadow: "0px 1px 9px 12px rgba(29, 161, 242, 0.2)",
|
|
447
|
+
margin: "20px",
|
|
448
|
+
boxSizing: "border-box",
|
|
449
|
+
overflow: "hidden",
|
|
450
|
+
width: "100%",
|
|
451
|
+
padding: "10px 10px 0 10px",
|
|
452
|
+
backgroundColor: "#fff"
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
456
|
+
const element = await page.$(".main-thread");
|
|
457
|
+
const boundingBox = await page.evaluate((el) => {
|
|
458
|
+
const rect = el.getBoundingClientRect();
|
|
459
|
+
const margin = 20;
|
|
460
|
+
return {
|
|
461
|
+
x: rect.left - margin,
|
|
462
|
+
y: rect.top - margin,
|
|
463
|
+
width: rect.width + margin * 2,
|
|
464
|
+
height: rect.height + margin * 2
|
|
465
|
+
};
|
|
466
|
+
}, element);
|
|
467
|
+
const buffer = await page.screenshot({
|
|
468
|
+
type: "png",
|
|
469
|
+
omitBackground: false,
|
|
470
|
+
clip: boundingBox
|
|
471
|
+
});
|
|
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];
|
|
388
482
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
omitBackground: false
|
|
395
|
-
});
|
|
396
|
-
if (config.sendPic) {
|
|
397
|
-
const originalImages = await page.$$eval(
|
|
398
|
-
".main-tweet a.still-image",
|
|
399
|
-
(links, baseUrl) => links.map((link) => link.getAttribute("href")).filter((href) => href).map((href) => `${baseUrl}${href}`),
|
|
400
|
-
config.nitterUrl
|
|
401
|
-
);
|
|
402
|
-
const hlsUrls = await page.$$eval(".main-tweet video", (videos, baseUrl) => {
|
|
403
|
-
return videos.map((video) => video.getAttribute("data-url")).filter((dataUrl) => dataUrl).map((dataUrl) => `${baseUrl}${dataUrl}`);
|
|
404
|
-
}, config.nitterUrl);
|
|
405
|
-
return [buffer, originalImages, hlsUrls];
|
|
483
|
+
return [buffer, [], []];
|
|
484
|
+
} catch (e) {
|
|
485
|
+
throw e;
|
|
486
|
+
} finally {
|
|
487
|
+
await page.close();
|
|
406
488
|
}
|
|
407
|
-
return [buffer, [], []];
|
|
408
|
-
} catch (e) {
|
|
409
|
-
throw e;
|
|
410
|
-
} finally {
|
|
411
|
-
await page.close();
|
|
412
489
|
}
|
|
490
|
+
__name(renderTweetScreenshot, "renderTweetScreenshot");
|
|
413
491
|
}
|
|
414
|
-
__name(
|
|
492
|
+
__name(apply, "apply");
|
|
415
493
|
// Annotate the CommonJS export names for ESM import in node:
|
|
416
494
|
0 && (module.exports = {
|
|
417
495
|
Config,
|
|
418
496
|
apply,
|
|
419
|
-
downloadVideosToBase64,
|
|
420
497
|
inject,
|
|
421
498
|
name
|
|
422
499
|
});
|
package/lib/retry.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function retry<T>(retries: number, fn: () => Promise<T> | T, delay?: number): Promise<T>;
|
package/lib/translate.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { Page } from 'puppeteer-core';
|
|
2
|
-
export declare function retry(retries: number, fn: () => any, delay?: number): any;
|
|
3
2
|
export declare function setGoogleTranslate(key: string, proxy: string): void;
|
|
4
|
-
export declare function setSiliconTranslate(key: string, model: string, prompt: string, timeout?: number): void;
|
|
5
3
|
export declare function setOpenAiTranslate(url: string, key: string, model: string, prompt: string, temperature: number, timeout?: number): void;
|
|
6
4
|
export declare function addTranslate(page: Page, className: string): Promise<void>;
|
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.
|
|
4
|
+
"version": "0.0.9",
|
|
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.
|
|
22
|
+
"koishi-plugin-subscription": "^0.0.6"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
+
"fluent-ffmpeg": "^2.1.3",
|
|
25
26
|
"node-cron": "^4.2.1",
|
|
26
|
-
"rettiwt-api": "^6.
|
|
27
|
-
"fluent-ffmpeg": "^2.1.3"
|
|
27
|
+
"rettiwt-api": "^6.1.7"
|
|
28
28
|
}
|
|
29
29
|
}
|