koishi-plugin-twitter-fetcher 0.0.2 → 0.0.4
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.js +149 -0
- package/package.json +9 -1
package/lib/index.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name2 in all)
|
|
8
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
Config: () => Config,
|
|
24
|
+
apply: () => apply,
|
|
25
|
+
inject: () => inject,
|
|
26
|
+
name: () => name
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(src_exports);
|
|
29
|
+
var import_koishi = require("koishi");
|
|
30
|
+
var name = "twitter-fetcher";
|
|
31
|
+
var inject = ["puppeteer"];
|
|
32
|
+
var logger = new import_koishi.Logger(name);
|
|
33
|
+
var Config = import_koishi.Schema.object({
|
|
34
|
+
showScreenshot: import_koishi.Schema.boolean().description("是否发送推文截图。").default(true),
|
|
35
|
+
sendText: import_koishi.Schema.boolean().description("是否发送提取的推文文本。").default(true),
|
|
36
|
+
sendMedia: import_koishi.Schema.boolean().description("是否发送推文中的图片和视频。").default(true),
|
|
37
|
+
cookie: import_koishi.Schema.string().role("textarea").description("可选的 Twitter/X 登录 Cookie"),
|
|
38
|
+
useForward: import_koishi.Schema.boolean().description("是否使用合并转发的形式发送(仅 QQ 平台效果最佳)。").default(true),
|
|
39
|
+
logDetails: import_koishi.Schema.boolean().description("是否在控制台输出详细的调试日志。").default(false)
|
|
40
|
+
});
|
|
41
|
+
var TWEET_URL_REGEX = /https?:\/\/(twitter\.com|x\.com)\/(\w+)\/status\/(\d+)/g;
|
|
42
|
+
function apply(ctx, config) {
|
|
43
|
+
logger.info("Twitter Fetcher 插件已启动。");
|
|
44
|
+
ctx.middleware(async (session, next) => {
|
|
45
|
+
TWEET_URL_REGEX.lastIndex = 0;
|
|
46
|
+
const match = TWEET_URL_REGEX.exec(session.content);
|
|
47
|
+
if (!match) return next();
|
|
48
|
+
const originalUrl = match[0];
|
|
49
|
+
if (config.logDetails) logger.info(`[1/5] 检测到推文链接: ${originalUrl}`);
|
|
50
|
+
const quote = (0, import_koishi.h)("quote", { id: session.messageId });
|
|
51
|
+
const statusMessage = await session.send(`${quote}正在解析推文链接,请稍候...`);
|
|
52
|
+
try {
|
|
53
|
+
const textParts = [];
|
|
54
|
+
const mediaParts = [];
|
|
55
|
+
let screenshotElement = null;
|
|
56
|
+
if (config.showScreenshot) {
|
|
57
|
+
try {
|
|
58
|
+
const screenshotBuffer = await getTweetScreenshot(ctx.puppeteer, originalUrl, config.cookie);
|
|
59
|
+
if (!screenshotBuffer) throw new Error("截图结果为空");
|
|
60
|
+
const dataUri = `data:image/png;base64,${screenshotBuffer.toString("base64")}`;
|
|
61
|
+
screenshotElement = import_koishi.h.image(dataUri);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (config.logDetails) logger.warn(`[!] 截图失败: ${error.message}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const apiUrl = originalUrl.replace(/(twitter\.com|x\.com)/, "api.vxtwitter.com");
|
|
68
|
+
const apiResponse = await ctx.http.get(apiUrl);
|
|
69
|
+
if (config.logDetails) logger.info(`[2/5] 收到 vxtwitter API 响应...`);
|
|
70
|
+
if (apiResponse.user_screen_name) textParts.push(`用户ID:${apiResponse.user_screen_name}`);
|
|
71
|
+
if (apiResponse.text && config.sendText) textParts.push(`推文内容:${apiResponse.text}`);
|
|
72
|
+
if (config.logDetails) logger.info(`[3/5] 文本部分构造完毕。`);
|
|
73
|
+
if (screenshotElement) mediaParts.push(screenshotElement);
|
|
74
|
+
if (config.sendMedia && apiResponse.media_extended) {
|
|
75
|
+
for (const media of apiResponse.media_extended) {
|
|
76
|
+
try {
|
|
77
|
+
const file = await ctx.http.file(media.url);
|
|
78
|
+
const buffer = Buffer.from(file.data);
|
|
79
|
+
const mimeType = file.mime || (media.type === "video" ? "video/mp4" : "image/jpeg");
|
|
80
|
+
const dataUri = `data:${mimeType};base64,${buffer.toString("base64")}`;
|
|
81
|
+
if (media.type === "image") mediaParts.push(import_koishi.h.image(dataUri));
|
|
82
|
+
else if (media.type === "video") mediaParts.push(import_koishi.h.video(dataUri));
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (config.logDetails) logger.warn(`下载媒体文件失败: ${media.url}`, error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
logger.warn(`[!] 通过 API 获取内容失败:`, error);
|
|
90
|
+
}
|
|
91
|
+
const hasText = textParts.length > 0;
|
|
92
|
+
const hasMedia = mediaParts.length > 0;
|
|
93
|
+
if (!hasText && !hasMedia) {
|
|
94
|
+
await session.send("未能获取到该推文的任何内容。");
|
|
95
|
+
} else {
|
|
96
|
+
if (config.useForward && session.platform === "onebot") {
|
|
97
|
+
const forwardElements = [];
|
|
98
|
+
if (hasText) forwardElements.push(textParts.join("\n"));
|
|
99
|
+
if (hasMedia) forwardElements.push(...mediaParts);
|
|
100
|
+
if (config.logDetails) logger.info(`[4/5] 正在构造合并转发消息...`);
|
|
101
|
+
await session.send((0, import_koishi.h)("figure", {}, forwardElements));
|
|
102
|
+
if (config.logDetails) logger.info(`[5/5] 合并转发消息已发送。`);
|
|
103
|
+
} else {
|
|
104
|
+
const finalParts = [];
|
|
105
|
+
if (hasText) finalParts.push(textParts.join("\n"));
|
|
106
|
+
if (hasMedia) finalParts.push(...mediaParts);
|
|
107
|
+
const messageToSend = finalParts.join("\n\n");
|
|
108
|
+
if (config.logDetails) logger.info(`[4/5] 最终拼接的待发消息字符串:
|
|
109
|
+
---
|
|
110
|
+
${messageToSend}
|
|
111
|
+
---`);
|
|
112
|
+
await session.send(messageToSend);
|
|
113
|
+
if (config.logDetails) logger.info(`[5/5] 消息已发送。`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} catch (error) {
|
|
117
|
+
logger.error("处理推文链接时发生未知错误:", error);
|
|
118
|
+
} finally {
|
|
119
|
+
await session.bot.deleteMessage(session.channelId, statusMessage[0]);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
__name(apply, "apply");
|
|
124
|
+
async function getTweetScreenshot(puppeteer, url, cookie) {
|
|
125
|
+
const page = await puppeteer.page();
|
|
126
|
+
try {
|
|
127
|
+
if (cookie) {
|
|
128
|
+
const cookieObj = { name: "auth_token", value: cookie, domain: ".twitter.com", path: "/", httpOnly: true, secure: true };
|
|
129
|
+
const xCookie = { ...cookieObj, domain: ".x.com" };
|
|
130
|
+
await page.setCookie(cookieObj, xCookie);
|
|
131
|
+
}
|
|
132
|
+
await page.goto(url, { waitUntil: "networkidle2", timeout: 2e4 });
|
|
133
|
+
const tweetSelector = 'article[data-testid="tweet"]';
|
|
134
|
+
await page.waitForSelector(tweetSelector, { timeout: 15e3 });
|
|
135
|
+
const tweetElement = await page.$(tweetSelector);
|
|
136
|
+
if (!tweetElement) throw new Error("无法在页面上定位到推文元素。");
|
|
137
|
+
return await tweetElement.screenshot();
|
|
138
|
+
} finally {
|
|
139
|
+
await page.close();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
__name(getTweetScreenshot, "getTweetScreenshot");
|
|
143
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
144
|
+
0 && (module.exports = {
|
|
145
|
+
Config,
|
|
146
|
+
apply,
|
|
147
|
+
inject,
|
|
148
|
+
name
|
|
149
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-twitter-fetcher",
|
|
3
3
|
"description": "自动解析 推特Twitter/X 链接,聚合推文截图、文本、图片和视频。",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.4",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -10,6 +10,14 @@
|
|
|
10
10
|
],
|
|
11
11
|
"author": "whitebr1ck",
|
|
12
12
|
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/WhiteBr1ck/koishi-plugin-twitter-fetcher.git"
|
|
16
|
+
},
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/WhiteBr1ck/koishi-plugin-twitter-fetcher/issues"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/WhiteBr1ck/koishi-plugin-twitter-fetcher#readme",
|
|
13
21
|
"keywords": [
|
|
14
22
|
"koishi",
|
|
15
23
|
"plugin",
|