koishi-plugin-nitter 0.0.4 → 0.0.6
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 +5 -2
- package/lib/index.js +90 -31
- package/lib/translate.d.ts +1 -0
- package/package.json +6 -3
package/lib/index.d.ts
CHANGED
|
@@ -5,11 +5,13 @@ export interface Config {
|
|
|
5
5
|
apiKey: string;
|
|
6
6
|
nitterUrl: string;
|
|
7
7
|
proxy: string;
|
|
8
|
-
enableTranslate: "google" | "
|
|
8
|
+
enableTranslate: "google" | "openai" | "disable";
|
|
9
9
|
googleApiKey?: string;
|
|
10
|
-
|
|
10
|
+
baseurl?: string;
|
|
11
|
+
openaiApiKey?: string;
|
|
11
12
|
model?: string;
|
|
12
13
|
prompt?: string;
|
|
14
|
+
temperature: number;
|
|
13
15
|
timeout?: number;
|
|
14
16
|
app: string;
|
|
15
17
|
sendPic: boolean;
|
|
@@ -17,3 +19,4 @@ export interface Config {
|
|
|
17
19
|
}
|
|
18
20
|
export declare const Config: Schema<Config, Dict>;
|
|
19
21
|
export declare function apply(ctx: Context, config: Config): void;
|
|
22
|
+
export declare function downloadVideosToBase64(videoUrls: any): 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
|
});
|
|
@@ -73,14 +74,13 @@ function setGoogleTranslate(key, proxy) {
|
|
|
73
74
|
}, "translate");
|
|
74
75
|
}
|
|
75
76
|
__name(setGoogleTranslate, "setGoogleTranslate");
|
|
76
|
-
function
|
|
77
|
+
function setOpenAiTranslate(url, key, model, prompt, temperature, timeout = 6e4) {
|
|
77
78
|
translate = /* @__PURE__ */ __name(async (texts) => {
|
|
78
|
-
const url = "https://api.siliconflow.cn/v1/chat/completions";
|
|
79
79
|
let data;
|
|
80
80
|
await retry(3, async () => {
|
|
81
81
|
const response = await (0, import_axios.default)({
|
|
82
82
|
method: "POST",
|
|
83
|
-
url
|
|
83
|
+
url: `${url}/chat/completions`,
|
|
84
84
|
headers: {
|
|
85
85
|
"Authorization": `Bearer ${key}`,
|
|
86
86
|
"Content-Type": "application/json"
|
|
@@ -94,14 +94,15 @@ function setSiliconTranslate(key, model, prompt, timeout = 6e4) {
|
|
|
94
94
|
},
|
|
95
95
|
{
|
|
96
96
|
role: "user",
|
|
97
|
-
content:
|
|
97
|
+
content: `请以JSON数组格式翻译以下${texts.length}条HTML内容:
|
|
98
98
|
|
|
99
|
-
${JSON.stringify(texts)}
|
|
99
|
+
${JSON.stringify(texts)}
|
|
100
|
+
|
|
101
|
+
json输出格式:["翻译结果1", "翻译结果2", ...]`
|
|
100
102
|
}
|
|
101
103
|
],
|
|
102
104
|
stream: false,
|
|
103
|
-
temperature
|
|
104
|
-
// 较低的温度值使翻译更稳定
|
|
105
|
+
temperature,
|
|
105
106
|
response_format: { type: "json_object" }
|
|
106
107
|
},
|
|
107
108
|
timeout
|
|
@@ -124,7 +125,7 @@ ${JSON.stringify(texts)}`
|
|
|
124
125
|
return data;
|
|
125
126
|
}, "translate");
|
|
126
127
|
}
|
|
127
|
-
__name(
|
|
128
|
+
__name(setOpenAiTranslate, "setOpenAiTranslate");
|
|
128
129
|
async function addTranslate(page, className) {
|
|
129
130
|
const elementsHTML = await page.evaluate((className2) => {
|
|
130
131
|
const elements = document.querySelectorAll(className2);
|
|
@@ -153,8 +154,12 @@ __name(addTranslate, "addTranslate");
|
|
|
153
154
|
// src/index.tsx
|
|
154
155
|
var import_rettiwt_api = require("rettiwt-api");
|
|
155
156
|
var import_node_cron = require("node-cron");
|
|
157
|
+
var import_fluent_ffmpeg = __toESM(require("fluent-ffmpeg"));
|
|
158
|
+
var import_path = __toESM(require("path"));
|
|
159
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
156
160
|
var import_jsx_runtime = require("@satorijs/element/jsx-runtime");
|
|
157
161
|
var name = "nitter";
|
|
162
|
+
var logger = new import_koishi.Logger(name);
|
|
158
163
|
var inject = ["puppeteer", "subscription"];
|
|
159
164
|
var Config = import_koishi.Schema.intersect([
|
|
160
165
|
import_koishi.Schema.object({
|
|
@@ -165,7 +170,7 @@ var Config = import_koishi.Schema.intersect([
|
|
|
165
170
|
import_koishi.Schema.object({
|
|
166
171
|
enableTranslate: import_koishi.Schema.union([
|
|
167
172
|
import_koishi.Schema.const("google").description("google cloud translation"),
|
|
168
|
-
import_koishi.Schema.const("
|
|
173
|
+
import_koishi.Schema.const("openai").description("openai"),
|
|
169
174
|
import_koishi.Schema.const("disable").description("关闭")
|
|
170
175
|
]).default("disable").role("radio")
|
|
171
176
|
}).description("翻译设置"),
|
|
@@ -175,10 +180,12 @@ var Config = import_koishi.Schema.intersect([
|
|
|
175
180
|
googleApiKey: import_koishi.Schema.string().required().description("访问https://cloud.google.com/获取,使用v2")
|
|
176
181
|
}),
|
|
177
182
|
import_koishi.Schema.object({
|
|
178
|
-
enableTranslate: import_koishi.Schema.const("
|
|
179
|
-
|
|
183
|
+
enableTranslate: import_koishi.Schema.const("openai").required(),
|
|
184
|
+
baseurl: import_koishi.Schema.string().required().description('不要在后面添加"/"'),
|
|
185
|
+
openaiApiKey: import_koishi.Schema.string().required().role("secret"),
|
|
180
186
|
model: import_koishi.Schema.string().required().description("模型名称"),
|
|
181
187
|
prompt: import_koishi.Schema.string().required().default("你是一个专业的HTML翻译助手。请将一个HTML数组翻译为中文,严格遵循以下规则:\n1. 翻译文本内容,包括链接标签(如<a>)内的显示文本\n2.保持所有HTML标签、属性、class、id,以及结构和格式,URL链接完全不变\n3. 返回一个严格的由html文本组成的JSON数组,包含与输入数量完全相同的翻译结果,不要添加任何额外说明").role("textarea").description("提示词"),
|
|
188
|
+
temperature: import_koishi.Schema.number().default(1.3).required(),
|
|
182
189
|
timeout: import_koishi.Schema.number().default(6e4).description("等待翻译时长")
|
|
183
190
|
}),
|
|
184
191
|
import_koishi.Schema.object({})
|
|
@@ -202,8 +209,8 @@ function apply(ctx, config) {
|
|
|
202
209
|
(async () => {
|
|
203
210
|
if (config.enableTranslate == "google") {
|
|
204
211
|
setGoogleTranslate(config.googleApiKey, config.proxy);
|
|
205
|
-
} else if (config.enableTranslate == "
|
|
206
|
-
|
|
212
|
+
} else if (config.enableTranslate == "openai") {
|
|
213
|
+
setOpenAiTranslate(config.baseurl, config.openaiApiKey, config.model, config.prompt, config.temperature, config.timeout);
|
|
207
214
|
}
|
|
208
215
|
const tweetList = await getFollowedFeed();
|
|
209
216
|
for (const data of tweetList) {
|
|
@@ -237,12 +244,17 @@ function apply(ctx, config) {
|
|
|
237
244
|
return "请输入推文ID";
|
|
238
245
|
}
|
|
239
246
|
try {
|
|
240
|
-
const [screenshot, imageUrls] = await renderTweetScreenshot(ctx, tweetId, config);
|
|
247
|
+
const [screenshot, imageUrls, hlsUrls] = await renderTweetScreenshot(ctx, tweetId, config);
|
|
241
248
|
let msg = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: "data:image/png;base64," + screenshot.toString("base64") });
|
|
249
|
+
let forwardMsg = [];
|
|
250
|
+
const videoUrls = await downloadVideosToBase64(hlsUrls);
|
|
242
251
|
if (imageUrls.length > 0) {
|
|
243
|
-
|
|
252
|
+
forwardMsg.push(...imageUrls.map((url) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: url }) })));
|
|
253
|
+
}
|
|
254
|
+
if (videoUrls.length > 0) {
|
|
255
|
+
forwardMsg.push(...videoUrls.map((url) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("video", { src: url }) })));
|
|
244
256
|
}
|
|
245
|
-
|
|
257
|
+
await session.send(msg + /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { forward: true, children: forwardMsg }));
|
|
246
258
|
} catch (error) {
|
|
247
259
|
ctx.logger("nitter").error("获取推文失败:", error);
|
|
248
260
|
return "获取推文失败";
|
|
@@ -256,12 +268,19 @@ function apply(ctx, config) {
|
|
|
256
268
|
});
|
|
257
269
|
async function broadcast(account, tweetId) {
|
|
258
270
|
try {
|
|
259
|
-
const [screenshot, imageUrls] = await renderTweetScreenshot(ctx, tweetId, config);
|
|
271
|
+
const [screenshot, imageUrls, hlsUrls] = await renderTweetScreenshot(ctx, tweetId, config);
|
|
260
272
|
const screenshotMsg = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: "data:image/png;base64," + screenshot.toString("base64") });
|
|
261
273
|
ctx.subscription.broadcast(config.app, account, screenshotMsg);
|
|
274
|
+
let forwardMsg = [];
|
|
275
|
+
const videoUrls = await downloadVideosToBase64(hlsUrls);
|
|
262
276
|
if (imageUrls.length > 0) {
|
|
263
|
-
|
|
264
|
-
|
|
277
|
+
forwardMsg.push(...imageUrls.map((url) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: url }) })));
|
|
278
|
+
}
|
|
279
|
+
if (videoUrls.length > 0) {
|
|
280
|
+
forwardMsg.push(...videoUrls.map((url) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("video", { src: url }) })));
|
|
281
|
+
}
|
|
282
|
+
if (forwardMsg.length > 0) {
|
|
283
|
+
await ctx.subscription.broadcastForward(config.app, account, forwardMsg);
|
|
265
284
|
}
|
|
266
285
|
} catch (error) {
|
|
267
286
|
ctx.logger("nitter").error("获取推文失败:", error);
|
|
@@ -293,6 +312,48 @@ function apply(ctx, config) {
|
|
|
293
312
|
__name(checkForUpdates, "checkForUpdates");
|
|
294
313
|
}
|
|
295
314
|
__name(apply, "apply");
|
|
315
|
+
var tempDir = import_path.default.join(process.cwd(), "tmp");
|
|
316
|
+
async function downloadVideosToBase64(videoUrls) {
|
|
317
|
+
await import_promises.default.mkdir(tempDir, { recursive: true });
|
|
318
|
+
const results = [];
|
|
319
|
+
for (const url of videoUrls) {
|
|
320
|
+
let outputPath = null;
|
|
321
|
+
try {
|
|
322
|
+
outputPath = import_path.default.join(tempDir, `video_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.mp4`);
|
|
323
|
+
await new Promise((resolve, reject) => {
|
|
324
|
+
(0, import_fluent_ffmpeg.default)(url).inputOptions([
|
|
325
|
+
"-protocol_whitelist",
|
|
326
|
+
"file,http,https,tcp,tls,crypto"
|
|
327
|
+
]).outputOptions([
|
|
328
|
+
"-c",
|
|
329
|
+
"copy"
|
|
330
|
+
]).output(outputPath).on("end", () => resolve(outputPath)).on("error", reject).run();
|
|
331
|
+
});
|
|
332
|
+
const stats = await import_promises.default.stat(outputPath);
|
|
333
|
+
const fileSizeInMB = stats.size / (1024 * 1024);
|
|
334
|
+
if (fileSizeInMB > 10) {
|
|
335
|
+
logger.info(`视频大小 ${fileSizeInMB.toFixed(2)}MB 超过10MB限制,跳过转换`);
|
|
336
|
+
} else {
|
|
337
|
+
const fileBuffer = await import_promises.default.readFile(outputPath);
|
|
338
|
+
const base64String = fileBuffer.toString("base64");
|
|
339
|
+
const dataUrl = `data:video/mp4;base64,${base64String}`;
|
|
340
|
+
results.push(dataUrl);
|
|
341
|
+
}
|
|
342
|
+
} catch (error) {
|
|
343
|
+
console.error(`处理失败 (${url}):`, error.message);
|
|
344
|
+
results.push(null);
|
|
345
|
+
} finally {
|
|
346
|
+
if (outputPath) {
|
|
347
|
+
try {
|
|
348
|
+
await import_promises.default.unlink(outputPath);
|
|
349
|
+
} catch (deleteError) {
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return results;
|
|
355
|
+
}
|
|
356
|
+
__name(downloadVideosToBase64, "downloadVideosToBase64");
|
|
296
357
|
async function renderTweetScreenshot(ctx, tweetId, config) {
|
|
297
358
|
const puppeteer = ctx.puppeteer;
|
|
298
359
|
if (!puppeteer) {
|
|
@@ -327,20 +388,17 @@ async function renderTweetScreenshot(ctx, tweetId, config) {
|
|
|
327
388
|
omitBackground: false
|
|
328
389
|
});
|
|
329
390
|
if (config.sendPic) {
|
|
330
|
-
const originalImages = await page
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
});
|
|
339
|
-
return imageUrls;
|
|
391
|
+
const originalImages = await page.$$eval(
|
|
392
|
+
".main-tweet a.still-image",
|
|
393
|
+
(links, baseUrl) => links.map((link) => link.getAttribute("href")).filter((href) => href).map((href) => `${baseUrl}${href}`),
|
|
394
|
+
config.nitterUrl
|
|
395
|
+
);
|
|
396
|
+
const hlsUrls = await page.$$eval(".main-tweet video", (videos, baseUrl) => {
|
|
397
|
+
return videos.map((video) => video.getAttribute("data-url")).filter((dataUrl) => dataUrl).map((dataUrl) => `${baseUrl}${dataUrl}`);
|
|
340
398
|
}, config.nitterUrl);
|
|
341
|
-
return [buffer, originalImages];
|
|
399
|
+
return [buffer, originalImages, hlsUrls];
|
|
342
400
|
}
|
|
343
|
-
return [buffer, []];
|
|
401
|
+
return [buffer, [], []];
|
|
344
402
|
} catch (e) {
|
|
345
403
|
throw e;
|
|
346
404
|
} finally {
|
|
@@ -352,6 +410,7 @@ __name(renderTweetScreenshot, "renderTweetScreenshot");
|
|
|
352
410
|
0 && (module.exports = {
|
|
353
411
|
Config,
|
|
354
412
|
apply,
|
|
413
|
+
downloadVideosToBase64,
|
|
355
414
|
inject,
|
|
356
415
|
name
|
|
357
416
|
});
|
package/lib/translate.d.ts
CHANGED
|
@@ -2,4 +2,5 @@ import { Page } from 'puppeteer-core';
|
|
|
2
2
|
export declare function retry(retries: number, fn: () => any, delay?: number): any;
|
|
3
3
|
export declare function setGoogleTranslate(key: string, proxy: string): void;
|
|
4
4
|
export declare function setSiliconTranslate(key: string, model: string, prompt: string, timeout?: number): void;
|
|
5
|
+
export declare function setOpenAiTranslate(url: string, key: string, model: string, prompt: string, temperature: number, timeout?: number): void;
|
|
5
6
|
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.6",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -19,8 +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.5"
|
|
22
|
+
"koishi-plugin-subscription": "^0.0.5"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
23
25
|
"node-cron": "^4.2.1",
|
|
24
|
-
"rettiwt-api": "^6.0.6"
|
|
26
|
+
"rettiwt-api": "^6.0.6",
|
|
27
|
+
"fluent-ffmpeg": "^2.1.3"
|
|
25
28
|
}
|
|
26
29
|
}
|