From f93d6edfbdc554a1b162e679f4798b6b99440952 Mon Sep 17 00:00:00 2001 From: zen1zi Date: Tue, 21 Oct 2025 15:31:19 +0800 Subject: [PATCH 1/5] Update plugins.json --- plugins.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins.json b/plugins.json index 3926bee4..59d54c6d 100644 --- a/plugins.json +++ b/plugins.json @@ -267,6 +267,10 @@ "url": "https://github.com/TeleBoxDev/TeleBox_Plugins/blob/main/zpr/zpr.ts?raw=true", "desc": "二次元图片" }, + "parsehub": { + "url": "https://github.com/TeleBoxDev/TeleBox_Plugins/blob/main/parsehub/parsehub.ts?raw=true", + "desc": "社交媒体链接解析助手" + }, "pmcaptcha": { "url": "https://github.com/TeleBoxDev/TeleBox_Plugins/blob/main/pmcaptcha/pmcaptcha.ts?raw=true", "desc": "pmcaptcha私聊验证" From 13df5c319f72763ec3a8f3197a2baf560880b032 Mon Sep 17 00:00:00 2001 From: zen1zi Date: Tue, 21 Oct 2025 15:51:21 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9Eparsehub=E6=8F=92?= =?UTF-8?q?=E4=BB=B6,=20=E8=A7=A3=E6=9E=90=E7=A4=BE=E4=BA=A4=E5=AA=92?= =?UTF-8?q?=E4=BD=93=E9=93=BE=E6=8E=A5=E4=B8=AD=E7=9A=84=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E5=92=8C=E5=9B=BE=E7=89=87=E7=AD=89(=E4=BE=9D=E8=B5=96@ParseHu?= =?UTF-8?q?bot)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- parsehub/parsehub.ts | 314 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 314 insertions(+) create mode 100644 parsehub/parsehub.ts diff --git a/parsehub/parsehub.ts b/parsehub/parsehub.ts new file mode 100644 index 00000000..5fd38342 --- /dev/null +++ b/parsehub/parsehub.ts @@ -0,0 +1,314 @@ +import { Plugin } from "@utils/pluginBase"; +import { Api } from "telegram"; +import { getPrefixes } from "@utils/pluginManager"; +import { sleep } from "telegram/Helpers"; + +const BOT_USERNAME = "ParseHubot"; +const POLL_INTERVAL_MS = 2000; +const MAX_POLL_DURATION_MS = 3 * 60 * 1000; +const IDLE_TIMEOUT_MS = 3 * 60 * 1000; + +const prefixes = getPrefixes(); +const mainPrefix = prefixes[0]; +const pluginName = "parsehub"; +const commandName = `${mainPrefix}${pluginName}`; + +const helpText = ` +依赖 @ParseHubot + +${commandName} 链接 解析社交媒体链接(支持多条,空格或换行分隔) + +示例: +${commandName} https://twitter.com/user/status/123 +${commandName} https://www.instagram.com/p/xxxx/ +`.trim(); + +const htmlEscape = (text: string): string => + text.replace( + /[&<>"']/g, + (ch) => + ({ + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + })[ch] || ch, + ); + +function extractLinks(text: string): string[] { + if (!text) return []; + const matches = text.match(/(?:https?:\/\/|www\.)\S+/gi) || []; + const sanitized = matches.map((raw) => { + const cleaned = raw.replace( + /[)\]\}\u3002\uff1a\uff01\uff1f\u3001\uff0c>]+$/u, + "", + ); + return cleaned.startsWith("http") ? cleaned : `https://${cleaned}`; + }); + return Array.from(new Set(sanitized.map((link) => link.trim()))).filter( + Boolean, + ); +} + +async function ensureBotReady(msg: Api.Message) { + const client = msg.client; + if (!client) return; + try { + await client.invoke(new Api.contacts.Unblock({ id: BOT_USERNAME })); + } catch {} + + try { + const inputPeer = await client.getInputEntity(BOT_USERNAME); + await client.invoke( + new Api.account.UpdateNotifySettings({ + peer: new Api.InputNotifyPeer({ peer: inputPeer }), + settings: new Api.InputPeerNotifySettings({ + silent: true, + muteUntil: 2147483647, + }), + }), + ); + } catch {} + + try { + await client.invoke( + new Api.messages.StartBot({ + bot: BOT_USERNAME, + peer: BOT_USERNAME, + startParam: "", + }), + ); + } catch { + try { + await client.sendMessage(BOT_USERNAME, { message: "/start" }); + } catch {} + } +} + +function buildStatusText( + link: string, + botMessage: Api.Message | null, + state: "pending" | "done" | "timeout", +): string { + const prefix = + state === "done" + ? "✅ 解析完成" + : state === "timeout" + ? "⌛ 超时结束" + : "🔄 等待解析"; + const intro = `${prefix}\n${htmlEscape(link)}`; + + if (!botMessage) { + return `${intro}\n\n尚未收到 @${BOT_USERNAME} 的回复,请稍后重试。`; + } + + const text = botMessage.message?.trim(); + const snippet = text + ? htmlEscape(text.length > 3500 ? `${text.slice(0, 3500)}…` : text) + : botMessage.media + ? "🖼️ Bot 返回了多媒体内容,已尝试直接转发。" + : "ℹ️ Bot 返回了空消息。"; + + const updatedAt = + botMessage.editDate || botMessage.date || Math.floor(Date.now() / 1000); + const timestamp = new Date(updatedAt * 1000).toLocaleString(); + + return `${intro}\n\n${snippet}\n\n来源:@${BOT_USERNAME}\n更新时间:${htmlEscape(timestamp)}`; +} + +async function forwardBotMessage( + msg: Api.Message, + statusMessage: Api.Message, + botMessage: Api.Message, + forward: boolean, + botPeer: Api.TypeInputPeer | string, +) { + const client = msg.client; + if (!client) return; + + if (forward) { + try { + await client.forwardMessages(msg.peerId, { + messages: [botMessage.id], + fromPeer: botPeer, + }); + } catch (error: any) { + const fallback = botMessage.message?.trim(); + if (fallback) { + await client.sendMessage(msg.peerId, { + message: `📨 @${BOT_USERNAME} 最新内容(转发失败,转文本展示):\n\n${fallback}`, + replyTo: statusMessage.id, + }); + } else { + await client.sendMessage(msg.peerId, { + message: `⚠️ 未能转发 @${BOT_USERNAME} 的多媒体消息,请前往私聊查看。`, + replyTo: statusMessage.id, + }); + } + } + } +} + +async function processLink( + msg: Api.Message, + link: string, + baselineId: number, +): Promise { + const client = msg.client; + if (!client) return baselineId; + + const statusMessage = await client.sendMessage(msg.peerId, { + message: `⏳ 正在解析 ${htmlEscape(link)},请稍候…`, + parseMode: "html", + replyTo: msg.replyTo?.replyToTopId || msg.replyTo?.replyToMsgId || msg.id, + }); + + let lastBotMessageId = baselineId; + let latestBotMessage: Api.Message | null = null; + let lastActivity = Date.now(); + let botPeer: Api.TypeInputPeer | string = BOT_USERNAME; + + try { + botPeer = await client.getInputEntity(BOT_USERNAME); + } catch {} + + try { + const history = await client.getMessages(BOT_USERNAME, { limit: 1 }); + if (history.length > 0) { + lastBotMessageId = Math.max(lastBotMessageId, history[0].id); + } + } catch {} + + await client.sendMessage(BOT_USERNAME, { message: link }); + + const deadline = Date.now() + MAX_POLL_DURATION_MS; + + while (Date.now() < deadline) { + await sleep(POLL_INTERVAL_MS); + + let updateDetected = false; + let messages: Api.Message[] = []; + + try { + messages = await client.getMessages(BOT_USERNAME, { limit: 10 }); + } catch (error: any) { + await statusMessage.edit({ + text: `❌ 获取 @${BOT_USERNAME} 消息失败:${htmlEscape(error.message || String(error))}`, + parseMode: "html", + }); + break; + } + + const chronological = messages.slice().reverse(); + for (const botMsg of chronological) { + if (botMsg.out) continue; + if (botMsg.id <= lastBotMessageId) continue; + + lastBotMessageId = botMsg.id; + latestBotMessage = botMsg; + lastActivity = Date.now(); + updateDetected = true; + + await forwardBotMessage(msg, statusMessage, botMsg, true, botPeer); + await statusMessage.edit({ + text: buildStatusText(link, botMsg, "pending"), + parseMode: "html", + }); + } + + if (!updateDetected && latestBotMessage) { + const newest = messages.find( + (m) => + !m.out && + m.id === latestBotMessage?.id && + ((m.editDate || 0) > (latestBotMessage?.editDate || 0) || + (m.message || "") !== (latestBotMessage?.message || "")), + ); + + if (newest) { + latestBotMessage = newest; + lastActivity = Date.now(); + updateDetected = true; + await statusMessage.edit({ + text: buildStatusText(link, newest, "pending"), + parseMode: "html", + }); + } + } + + if (!updateDetected && Date.now() - lastActivity > IDLE_TIMEOUT_MS) { + break; + } + } + + const timedOut = Date.now() >= deadline; + const idleTimeout = Date.now() - lastActivity > IDLE_TIMEOUT_MS && !timedOut; + const finalState: "pending" | "done" | "timeout" = + latestBotMessage && !timedOut ? "done" : "timeout"; + + await statusMessage.edit({ + text: buildStatusText( + link, + latestBotMessage, + finalState === "timeout" && !latestBotMessage ? "timeout" : finalState, + ), + parseMode: "html", + }); + + if (!latestBotMessage) { + await client.sendMessage(msg.peerId, { + message: `⚠️ 在 3 分钟内未收到 @${BOT_USERNAME} 的任何回复,请稍后重试或直接私聊机器人处理。`, + replyTo: statusMessage.id, + }); + } + + if (idleTimeout && latestBotMessage) { + await client.sendMessage(msg.peerId, { + message: `ℹ️ @${BOT_USERNAME} 在 ${Math.round( + (Date.now() - lastActivity) / 1000, + )} 秒内未继续更新,已返回最新状态。`, + replyTo: statusMessage.id, + }); + } + + return lastBotMessageId; +} + +class ParseHubPlugin extends Plugin { + description: string = `\nparsehub\n\n${helpText}`; + cmdHandlers: Record Promise> = { + parsehub: async (msg: Api.Message) => { + const rawText = msg.message || ""; + const cleaned = rawText.replace( + new RegExp(`^${commandName}\\s*`, "i"), + "", + ); + const links = extractLinks(cleaned); + + if (!links.length) { + await msg.edit({ text: helpText, parseMode: "html" }); + return; + } + + await msg.edit({ + text: `🚀 将 ${links.length} 条链接发送至 @${BOT_USERNAME},请稍候...`, + parseMode: "html", + }); + + await ensureBotReady(msg); + + let baselineId = 0; + for (const link of links) { + baselineId = await processLink(msg, link, baselineId); + await sleep(500); + } + + try { + await msg.delete(); + } catch {} + }, + }; +} + +export default new ParseHubPlugin(); From b95ee231a77f096683db76d3a43e1cdd6fdebad9 Mon Sep 17 00:00:00 2001 From: zen1zi Date: Tue, 21 Oct 2025 16:18:16 +0800 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20parsehub=E6=8F=92=E4=BB=B6=E5=8F=AA?= =?UTF-8?q?=E5=9C=A8=E9=A6=96=E6=AC=A1=E4=B8=94=E4=BC=9A=E8=AF=9D=E5=8E=86?= =?UTF-8?q?=E5=8F=B2=E6=9C=AA=E7=A9=BA=E6=97=B6=E5=8F=91=E9=80=81/start?= =?UTF-8?q?=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- parsehub/parsehub.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/parsehub/parsehub.ts b/parsehub/parsehub.ts index 5fd38342..ed688b9d 100644 --- a/parsehub/parsehub.ts +++ b/parsehub/parsehub.ts @@ -36,6 +36,8 @@ const htmlEscape = (text: string): string => })[ch] || ch, ); +let hasStartedBot = false; + function extractLinks(text: string): string[] { if (!text) return []; const matches = text.match(/(?:https?:\/\/|www\.)\S+/gi) || []; @@ -71,6 +73,18 @@ async function ensureBotReady(msg: Api.Message) { ); } catch {} + if (hasStartedBot) { + return; + } + + try { + const history = await client.getMessages(BOT_USERNAME, { limit: 1 }); + if (history.length > 0) { + hasStartedBot = true; + return; + } + } catch {} + try { await client.invoke( new Api.messages.StartBot({ @@ -79,9 +93,11 @@ async function ensureBotReady(msg: Api.Message) { startParam: "", }), ); + hasStartedBot = true; } catch { try { await client.sendMessage(BOT_USERNAME, { message: "/start" }); + hasStartedBot = true; } catch {} } } From b83d610803263b72e13f2332b69ecde03ae74aec Mon Sep 17 00:00:00 2001 From: zen1zi Date: Tue, 21 Oct 2025 17:10:52 +0800 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20=E5=8F=AA=E4=BF=9D=E7=95=99=E6=9C=80?= =?UTF-8?q?=E5=90=8E=E7=9A=84=E8=A7=A3=E6=9E=90=E7=BB=93=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- parsehub/parsehub.ts | 324 ++++++++++++++++++++++--------------------- 1 file changed, 167 insertions(+), 157 deletions(-) diff --git a/parsehub/parsehub.ts b/parsehub/parsehub.ts index ed688b9d..ee52c9cc 100644 --- a/parsehub/parsehub.ts +++ b/parsehub/parsehub.ts @@ -5,8 +5,16 @@ import { sleep } from "telegram/Helpers"; const BOT_USERNAME = "ParseHubot"; const POLL_INTERVAL_MS = 2000; -const MAX_POLL_DURATION_MS = 3 * 60 * 1000; -const IDLE_TIMEOUT_MS = 3 * 60 * 1000; +const MAX_WAIT_MS = 3 * 60 * 1000; +const RESULT_IDLE_MS = 5000; +const FETCH_LIMIT = 50; + +const PROGRESS_PREFIXES = [ + "解 析 中", + "已有相同任务正在解析", + "下 载 中", + "上 传 中", +] as const; const prefixes = getPrefixes(); const mainPrefix = prefixes[0]; @@ -38,6 +46,12 @@ const htmlEscape = (text: string): string => let hasStartedBot = false; +const isProgressText = (text?: string | null): boolean => { + if (!text) return false; + const trimmed = text.trim(); + return PROGRESS_PREFIXES.some((prefix) => trimmed.startsWith(prefix)); +}; + function extractLinks(text: string): string[] { if (!text) return []; const matches = text.match(/(?:https?:\/\/|www\.)\S+/gi) || []; @@ -56,6 +70,7 @@ function extractLinks(text: string): string[] { async function ensureBotReady(msg: Api.Message) { const client = msg.client; if (!client) return; + try { await client.invoke(new Api.contacts.Unblock({ id: BOT_USERNAME })); } catch {} @@ -102,197 +117,169 @@ async function ensureBotReady(msg: Api.Message) { } } -function buildStatusText( - link: string, - botMessage: Api.Message | null, - state: "pending" | "done" | "timeout", -): string { - const prefix = - state === "done" - ? "✅ 解析完成" - : state === "timeout" - ? "⌛ 超时结束" - : "🔄 等待解析"; - const intro = `${prefix}\n${htmlEscape(link)}`; - - if (!botMessage) { - return `${intro}\n\n尚未收到 @${BOT_USERNAME} 的回复,请稍后重试。`; - } - - const text = botMessage.message?.trim(); - const snippet = text - ? htmlEscape(text.length > 3500 ? `${text.slice(0, 3500)}…` : text) - : botMessage.media - ? "🖼️ Bot 返回了多媒体内容,已尝试直接转发。" - : "ℹ️ Bot 返回了空消息。"; +async function getLatestBotMessageId(client: any): Promise { + if (!client) return 0; + try { + const history = await client.getMessages(BOT_USERNAME, { limit: 1 }); + if (history.length > 0) { + return history[0].id; + } + } catch {} + return 0; +} - const updatedAt = - botMessage.editDate || botMessage.date || Math.floor(Date.now() / 1000); - const timestamp = new Date(updatedAt * 1000).toLocaleString(); +type RelayReason = "timeout" | "fetch_failed" | "send_failed" | "no_client"; - return `${intro}\n\n${snippet}\n\n来源:@${BOT_USERNAME}\n更新时间:${htmlEscape(timestamp)}`; +interface RelayOutcome { + lastId: number; + forwarded: boolean; + reason?: RelayReason; + error?: string; } -async function forwardBotMessage( - msg: Api.Message, - statusMessage: Api.Message, - botMessage: Api.Message, - forward: boolean, - botPeer: Api.TypeInputPeer | string, -) { - const client = msg.client; - if (!client) return; - - if (forward) { - try { - await client.forwardMessages(msg.peerId, { - messages: [botMessage.id], - fromPeer: botPeer, - }); - } catch (error: any) { - const fallback = botMessage.message?.trim(); - if (fallback) { - await client.sendMessage(msg.peerId, { - message: `📨 @${BOT_USERNAME} 最新内容(转发失败,转文本展示):\n\n${fallback}`, - replyTo: statusMessage.id, - }); - } else { - await client.sendMessage(msg.peerId, { - message: `⚠️ 未能转发 @${BOT_USERNAME} 的多媒体消息,请前往私聊查看。`, - replyTo: statusMessage.id, - }); - } - } +const describeReason = (reason?: RelayReason): string => { + switch (reason) { + case "timeout": + return "等待超时"; + case "fetch_failed": + return "获取机器人消息失败"; + case "send_failed": + return "向机器人发送链接失败"; + case "no_client": + return "客户端未就绪"; + default: + return "原因未知"; } +}; + +async function forwardChunk(client: any, peer: any, ids: number[]) { + await client.forwardMessages(peer, { + fromPeer: BOT_USERNAME, + messages: ids, + }); } -async function processLink( - msg: Api.Message, +async function relayParseResult( + originMsg: Api.Message, link: string, baselineId: number, -): Promise { - const client = msg.client; - if (!client) return baselineId; - - const statusMessage = await client.sendMessage(msg.peerId, { - message: `⏳ 正在解析 ${htmlEscape(link)},请稍候…`, - parseMode: "html", - replyTo: msg.replyTo?.replyToTopId || msg.replyTo?.replyToMsgId || msg.id, - }); - - let lastBotMessageId = baselineId; - let latestBotMessage: Api.Message | null = null; - let lastActivity = Date.now(); - let botPeer: Api.TypeInputPeer | string = BOT_USERNAME; - - try { - botPeer = await client.getInputEntity(BOT_USERNAME); - } catch {} +): Promise { + const client = originMsg.client; + if (!client) { + return { lastId: baselineId, forwarded: false, reason: "no_client" }; + } try { - const history = await client.getMessages(BOT_USERNAME, { limit: 1 }); - if (history.length > 0) { - lastBotMessageId = Math.max(lastBotMessageId, history[0].id); - } - } catch {} + await client.sendMessage(BOT_USERNAME, { message: link }); + } catch (error: any) { + return { + lastId: baselineId, + forwarded: false, + reason: "send_failed", + error: error?.message || String(error), + }; + } - await client.sendMessage(BOT_USERNAME, { message: link }); + const processedIds = new Set(); + const finalMessages = new Map(); - const deadline = Date.now() + MAX_POLL_DURATION_MS; + const deadline = Date.now() + MAX_WAIT_MS; + let lastId = baselineId; + let lastFinalActivity = 0; while (Date.now() < deadline) { await sleep(POLL_INTERVAL_MS); - let updateDetected = false; let messages: Api.Message[] = []; - try { - messages = await client.getMessages(BOT_USERNAME, { limit: 10 }); + messages = await client.getMessages(BOT_USERNAME, { limit: FETCH_LIMIT }); } catch (error: any) { - await statusMessage.edit({ - text: `❌ 获取 @${BOT_USERNAME} 消息失败:${htmlEscape(error.message || String(error))}`, - parseMode: "html", - }); - break; + return { + lastId, + forwarded: false, + reason: "fetch_failed", + error: error?.message || String(error), + }; } - const chronological = messages.slice().reverse(); - for (const botMsg of chronological) { - if (botMsg.out) continue; - if (botMsg.id <= lastBotMessageId) continue; - - lastBotMessageId = botMsg.id; - latestBotMessage = botMsg; - lastActivity = Date.now(); - updateDetected = true; + messages.sort((a, b) => a.id - b.id); - await forwardBotMessage(msg, statusMessage, botMsg, true, botPeer); - await statusMessage.edit({ - text: buildStatusText(link, botMsg, "pending"), - parseMode: "html", - }); - } + for (const botMsg of messages) { + if (!botMsg || (botMsg as any).className === "MessageService") continue; + if (botMsg.out) continue; + if (botMsg.id <= lastId) continue; + if (processedIds.has(botMsg.id)) continue; - if (!updateDetected && latestBotMessage) { - const newest = messages.find( - (m) => - !m.out && - m.id === latestBotMessage?.id && - ((m.editDate || 0) > (latestBotMessage?.editDate || 0) || - (m.message || "") !== (latestBotMessage?.message || "")), - ); + processedIds.add(botMsg.id); + lastId = Math.max(lastId, botMsg.id); - if (newest) { - latestBotMessage = newest; - lastActivity = Date.now(); - updateDetected = true; - await statusMessage.edit({ - text: buildStatusText(link, newest, "pending"), - parseMode: "html", - }); + const text = botMsg.message?.trim(); + if (isProgressText(text)) { + continue; } + + finalMessages.set(botMsg.id, botMsg); + lastFinalActivity = Date.now(); } - if (!updateDetected && Date.now() - lastActivity > IDLE_TIMEOUT_MS) { + if ( + finalMessages.size > 0 && + Date.now() - lastFinalActivity >= RESULT_IDLE_MS + ) { break; } } - const timedOut = Date.now() >= deadline; - const idleTimeout = Date.now() - lastActivity > IDLE_TIMEOUT_MS && !timedOut; - const finalState: "pending" | "done" | "timeout" = - latestBotMessage && !timedOut ? "done" : "timeout"; - - await statusMessage.edit({ - text: buildStatusText( - link, - latestBotMessage, - finalState === "timeout" && !latestBotMessage ? "timeout" : finalState, - ), - parseMode: "html", - }); + if (finalMessages.size === 0) { + return { lastId, forwarded: false, reason: "timeout" }; + } + + const sortedMessages = Array.from(finalMessages.values()).sort( + (a, b) => a.id - b.id, + ); + + let forwarded = false; + const fallbackTexts: string[] = []; - if (!latestBotMessage) { - await client.sendMessage(msg.peerId, { - message: `⚠️ 在 3 分钟内未收到 @${BOT_USERNAME} 的任何回复,请稍后重试或直接私聊机器人处理。`, - replyTo: statusMessage.id, - }); + for (let i = 0; i < sortedMessages.length; i += 100) { + const chunk = sortedMessages.slice(i, i + 100); + const ids = chunk.map((m) => m.id); + + try { + await forwardChunk(client, originMsg.peerId, ids); + forwarded = true; + } catch { + const snippet = chunk + .map((m) => m.message?.trim()) + .filter(Boolean) + .join("\n\n"); + fallbackTexts.push( + snippet.length + ? snippet + : `⚠️ 未能转发 @${BOT_USERNAME} 的多媒体结果,请前往私聊机器人查看。`, + ); + } } - if (idleTimeout && latestBotMessage) { - await client.sendMessage(msg.peerId, { - message: `ℹ️ @${BOT_USERNAME} 在 ${Math.round( - (Date.now() - lastActivity) / 1000, - )} 秒内未继续更新,已返回最新状态。`, - replyTo: statusMessage.id, - }); + if (!forwarded && fallbackTexts.length) { + try { + await client.sendMessage(originMsg.peerId, { + message: `📨 @${BOT_USERNAME} 返回内容:\n\n${fallbackTexts.join("\n\n")}`, + replyTo: originMsg.id, + }); + forwarded = true; + } catch {} } - return lastBotMessageId; + return { + lastId, + forwarded, + reason: forwarded ? undefined : "timeout", + }; } class ParseHubPlugin extends Plugin { - description: string = `\nparsehub\n\n${helpText}`; + description: string = `\n${pluginName}\n\n${helpText}`; cmdHandlers: Record Promise> = { parsehub: async (msg: Api.Message) => { const rawText = msg.message || ""; @@ -308,16 +295,39 @@ class ParseHubPlugin extends Plugin { } await msg.edit({ - text: `🚀 将 ${links.length} 条链接发送至 @${BOT_USERNAME},请稍候...`, + text: `🚀 已提交 ${links.length} 条链接至 @${BOT_USERNAME},仅保留最终解析结果。`, parseMode: "html", }); await ensureBotReady(msg); + const client = msg.client; + if (!client) { + await msg.edit({ + text: `❌ 无法获取 Telegram 客户端实例,请稍后重试。`, + }); + return; + } + + let baselineId = await getLatestBotMessageId(client); - let baselineId = 0; for (const link of links) { - baselineId = await processLink(msg, link, baselineId); - await sleep(500); + const outcome = await relayParseResult(msg, link, baselineId); + baselineId = outcome.lastId; + + if (!outcome.forwarded) { + const reasonText = describeReason(outcome.reason); + const detail = + outcome.error && outcome.error !== "undefined" + ? `\n\n错误信息:${outcome.error}` + : ""; + await client.sendMessage(msg.peerId, { + message: `⚠️ 未能获取 ${htmlEscape(link)} 的最终结果(${reasonText})。请稍后重试或直接私聊 @${BOT_USERNAME}。${detail}`, + parseMode: "html", + replyTo: msg.id, + }); + } + + await sleep(600); } try { From 65163e9094a4e826db672e61e81fd685322b4223 Mon Sep 17 00:00:00 2001 From: zen1zi Date: Tue, 21 Oct 2025 17:34:32 +0800 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20parsehub=E6=8F=92=E4=BB=B6=E9=83=A8?= =?UTF-8?q?=E5=88=86=E6=96=87=E6=A1=88=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- parsehub/parsehub.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parsehub/parsehub.ts b/parsehub/parsehub.ts index ee52c9cc..8a209078 100644 --- a/parsehub/parsehub.ts +++ b/parsehub/parsehub.ts @@ -24,7 +24,7 @@ const commandName = `${mainPrefix}${pluginName}`; const helpText = ` 依赖 @ParseHubot -${commandName} 链接 解析社交媒体链接(支持多条,空格或换行分隔) +${commandName} 链接 解析社交媒体链接 示例: ${commandName} https://twitter.com/user/status/123 @@ -295,7 +295,7 @@ class ParseHubPlugin extends Plugin { } await msg.edit({ - text: `🚀 已提交 ${links.length} 条链接至 @${BOT_USERNAME},仅保留最终解析结果。`, + text: `✅ 已提交链接至 @${BOT_USERNAME},正在解析中,请等待。`, parseMode: "html", });