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