import { getPost, listPosts } from "@/lib/posts"; import { getLinks, addLink, updateLink, deleteLink, getFeeds, addFeed, updateFeed, deleteFeed, getFeedsHealth, getArticles } from "@/lib/rssapi"; import { moveToRecycle } from "@/lib/recycle"; import { deployQueue } from "@/lib/git"; import { hugo } from "@/lib/hugo"; import { getLanIP, DEFAULT_AUTHOR, BLOG_ROOT } from "@/lib/config"; import { sendMessage, tg } from "./tg"; import { getSession, hasSession, clearSession, saveSession, undoLastChunk, initSessionDir } from "./sessions"; import { makeSlug, sessionSummary, truncateText } from "./helpers"; import { getCached, setCache, invalidateByPrefix } from "./cache"; type Handler = (chatId: number, arg?: string) => Promise; const handlers: Record = {}; // ---- Cached API wrappers ---- const CACHE_TTL_LINKS = 5 * 60 * 1000; const CACHE_TTL_FEEDS = 5 * 60 * 1000; const CACHE_TTL_ARTICLES = 3 * 60 * 1000; const CACHE_TTL_HEALTH = 10 * 60 * 1000; async function cachedLinks(all = true) { const key = "links_" + (all ? "all" : "visible"); const hit = getCached extends Promise ? R : never>(key); if (hit) return hit; const data = await getLinks(all); setCache(key, data, CACHE_TTL_LINKS); return data; } async function cachedFeeds() { const key = "feeds"; const hit = getCached extends Promise ? R : never>(key); if (hit) return hit; const data = await getFeeds(); setCache(key, data, CACHE_TTL_FEEDS); return data; } async function cachedArticles(limit: number) { const key = "articles_" + limit; const hit = getCached extends Promise ? R : never>(key); if (hit) return hit; const data = await getArticles(limit); setCache(key, data, CACHE_TTL_ARTICLES); return data; } async function cachedHealth() { const key = "health"; const hit = getCached extends Promise ? R : never>(key); if (hit) return hit; const data = await getFeedsHealth(); setCache(key, data, CACHE_TTL_HEALTH); return data; } // ---- 键盘 ---- function mainKeyboard() { return { keyboard: [ [{ text: "/new" }, { text: "/list" }], [{ text: "/links" }, { text: "/feeds" }], [{ text: "/stats" }, { text: "/help" }], ], resize_keyboard: true, one_time_keyboard: false, }; } function writingKeyboard() { return { keyboard: [ [{ text: "/preview" }, { text: "/undo" }], [{ text: "/publish" }, { text: "/deploy" }], [{ text: "/draft" }, { text: "/cancel" }], [{ text: "/title" }, { text: "/categories" }], ], resize_keyboard: true, one_time_keyboard: false, }; } async function sendMain(chatId: number, text: string) { await sendMessage(chatId, text, { reply_markup: mainKeyboard() }); } async function sendWrite(chatId: number, text: string) { await sendMessage(chatId, text, { reply_markup: writingKeyboard() }); } // ---- System ---- handlers.start = async (chatId) => { await sendMain(chatId, [ `👋 欢迎使用写作后台!`, ``, `📝 快速开始:`, `├─ /new 标题 - 新建文章`, `├─ /edit slug - 编辑文章`, `└─ /list - 查看文章列表`, ``, `🔗 友链 & 订阅:`, `├─ /links - 友链管理`, `└─ /feeds - 订阅管理`, ``, `🌐 访问地址:`, `├─ 📊 后台管理:https://post.usj.cc`, `└─ 🌍 博客首页:https://usj.cc`, ``, `💡 帮助:输入 /help 查看所有命令`, ``, `🎯 开始写作吧!`, ].join("\n")); await tg("sendMessage", { chat_id: chatId, text: "点下方按钮打开编辑器:", reply_markup: { inline_keyboard: [[{ text: "🌐 打开编辑器", web_app: { url: "https://post.usj.cc" } }]] }, }); }; handlers.ip = async (chatId) => { await sendMain(chatId, [ `🌐 访问地址`, ``, `├─ 📊 后台管理:https://post.usj.cc`, `└─ 🌍 博客首页:https://usj.cc`, ].join("\n")); }; handlers.help = async (chatId) => { if (hasSession(chatId)) { await sendWrite(chatId, [ `✍ 写作模式`, ``, `📝 编辑命令:`, `├─ /publish - 保存并发布`, `├─ /draft - 保存草稿`, `├─ /deploy - 部署到服务器`, `├─ /preview - 预览文章`, `└─ /undo - 撤销最后一段`, ``, `⚙️ 设置命令:`, `├─ /title 标题 - 设置标题`, `├─ /categories 分类 - 设置分类`, `├─ /tags 标签 - 设置标签`, `└─ /author 作者 - 设置作者`, ``, `💡 提示:`, `• 直接发送文字追加到正文`, `• 发送图片添加到文章`, `• 发送语音自动转文字`, `• 输入 /cancel 取消写作`, ].join("\n")); } else { await sendMain(chatId, [ `🤖 WriteBot 命令指南`, ``, `📝 文章管理:`, `├─ /new 标题 - 新建文章`, `├─ /edit slug - 编辑文章`, `├─ /delete slug - 删除文章`, `└─ /list - 文章列表`, ``, `🔗 友链管理:`, `├─ /links - 查看友链列表`, `├─ /link_add url 名称 - 添加友链`, `├─ /link_edit url - 编辑友链`, `├─ /link_del url - 删除友链`, `└─ /link_toggle url - 显示/隐藏`, ``, `📡 订阅管理:`, `├─ /feeds - 查看订阅列表`, `├─ /feed_add url - 添加订阅`, `├─ /feed_edit url - 编辑订阅`, `├─ /feed_del url - 删除订阅`, `└─ /feed_health - 健康检查`, ``, `📰 其他功能:`, `├─ /read - 友链最新文章`, `├─ /stats - 网站统计数据`, `└─ /ip - 访问地址`, ``, `🌐 快速访问:`, `├─ 📊 后台:https://post.usj.cc`, `└─ 🌍 博客:https://usj.cc`, ].join("\n")); } }; // ---- Articles: New ---- handlers.new = async (chatId, title) => { if (title) { const s = getSession(chatId); s.title = title; s.slug = makeSlug(title); s.content = ""; s.categories = []; s.tags = []; s.draft = true; s.author = DEFAULT_AUTHOR; s.chunks = []; initSessionDir(chatId, title); await tg("sendMessage", { chat_id: chatId, text: "📝 开始写:" + title + "\n\n📂 设置分类(可选)", parse_mode: "HTML", reply_markup: { inline_keyboard: [[ { text: "跳过", callback_data: "skip_cat" }, { text: "设置分类", callback_data: "skip_cat" }, ]], }, }); await tg("sendMessage", { chat_id: chatId, text: "请输入分类(逗号分隔,或发「跳过」):", reply_markup: { force_reply: true, input_field_placeholder: "技术、生活" }, }); } else { await tg("sendMessage", { chat_id: chatId, text: "📝 请输入文章标题:", reply_markup: { force_reply: true, input_field_placeholder: "在这里输入标题..." } }); } }; handlers.skip_categories = async (chatId) => { const s = getSession(chatId); if (!s.title) return; s.categories = []; await tg("sendMessage", { chat_id: chatId, text: "🏷 设置标签(可选)", reply_markup: { inline_keyboard: [[ { text: "跳过", callback_data: "skip_tag" }, { text: "设置标签", callback_data: "skip_tag" }, ]], }, }); await tg("sendMessage", { chat_id: chatId, text: "请输入标签(逗号分隔,或发「跳过」):", reply_markup: { force_reply: true, input_field_placeholder: "Hugo、博客" }, }); }; handlers.skip_tags = async (chatId) => { const s = getSession(chatId); if (!s.title) return; s.tags = []; await sendWrite(chatId, "✅ 准备就绪,开始写正文吧!\n\n📝 " + s.title + "\n📂 " + (s.categories.join("、") || "无分类") + "\n🏷 " + (s.tags.join("、") || "无标签")); }; handlers.categories = async (chatId, cats) => { const s = getSession(chatId); s.categories = cats ? cats.split(/[,;",、]+/).map((c) => c.trim()).filter(Boolean) : []; await tg("sendMessage", { chat_id: chatId, text: "📂 分类:" + (s.categories.join("、") || "无") + "\n\n🏷 设置标签(可选)", parse_mode: "HTML", reply_markup: { inline_keyboard: [[ { text: "跳过", callback_data: "skip_tag" }, { text: "设置标签", callback_data: "skip_tag" }, ]], }, }); await tg("sendMessage", { chat_id: chatId, text: "请输入标签(逗号分隔,或发「跳过」):", reply_markup: { force_reply: true, input_field_placeholder: "Hugo、博客" }, }); }; handlers.tags = async (chatId, tags) => { const s = getSession(chatId); s.tags = tags ? tags.split(/[,;",、]+/).map((t) => t.trim()).filter(Boolean) : []; await sendWrite(chatId, "✅ 准备就绪,开始写正文吧!\n\n📝 " + s.title + "\n📂 " + (s.categories.join("、") || "无分类") + "\n🏷 " + (s.tags.join("、") || "无标签")); }; // ---- Articles: Edit (方案 B) ---- handlers.edit = async (chatId, slug) => { if (!slug) { await sendMain(chatId, "格式:/edit slug\n先用 /list 查看文章。"); return; } const post = getPost(slug); if (!post) { await sendMain(chatId, "❌ 未找到该文章"); return; } const originalContent = post.content || ""; if (originalContent) { const maxLen = 3800; for (let i = 0; i < originalContent.length; i += maxLen) { const chunk = originalContent.slice(i, i + maxLen); const prefix = originalContent.length > maxLen ? "(" + (Math.floor(i / maxLen) + 1) + "/" + Math.ceil(originalContent.length / maxLen) + ")\n" : ""; await sendMessage(chatId, prefix + chunk); } } const s = getSession(chatId); s.title = post.frontMatter.title; s.slug = post.frontMatter.slug; s.originalSlug = post.frontMatter.slug; s.content = ""; s.chunks = []; s.dirPath = post.dirPath; s.categories = (post.frontMatter.categories as string[]) || []; s.tags = (post.frontMatter.tags as string[]) || []; s.author = post.frontMatter.author || DEFAULT_AUTHOR; s.draft = post.frontMatter.draft ?? true; await sendWrite(chatId, "✏ 编辑:" + s.title + "\n\n" + "原文已显示,现在重新写正文\n" + "📂 " + (s.categories.join("、") || "无分类") + "\n" + "🏷 " + (s.tags.join("、") || "无标签") + "\n\n" + "直接发消息写正文,完成后点「发布」" ); }; // ---- Articles: Delete (二次确认) ---- handlers.delete = async (chatId, slug) => { if (!slug) { await sendMain(chatId, "格式:/delete slug\n先用 /list 查看文章。"); return; } const post = getPost(slug); if (!post) { await sendMain(chatId, "❌ 未找到该文章"); return; } await tg("sendMessage", { chat_id: chatId, text: "⚠️ 确认删除《" + post.frontMatter.title + "》?\n\n删除后移入回收站,30天内可恢复。", parse_mode: "HTML", reply_markup: { inline_keyboard: [[ { text: "✅ 确认删除", callback_data: "confirm_del_" + slug }, { text: "❌ 取消", callback_data: "cancel_del_" + slug }, ]], }, }); }; handlers.confirm_del = async (chatId, slug) => { if (!slug) return; const post = getPost(slug); if (!post) { await sendMain(chatId, "❌ 未找到该文章"); return; } moveToRecycle(slug, post.dirPath, post.frontMatter.title); await sendMain(chatId, "✂ 已移入回收站:" + post.frontMatter.title + "\n30天内可恢复。"); }; handlers.cancel_del = async (chatId) => { await sendMain(chatId, "已取消删除。"); }; // ---- Articles: List ---- handlers.list = async (chatId, pageStr) => { const page = parseInt(pageStr || "1") || 1; const perPage = 10; try { const { posts, total, totalPages } = listPosts({ page, perPage }); if (total === 0) { await sendMain(chatId, "📖 还没有文章,发 /new 开始写。"); return; } const lines = ["📖 文章列表 共 " + total + " 篇 · 第 " + page + "/" + totalPages + " 页\n"]; for (const p of posts) { const draft = p.draft ? " 📝" : ""; lines.push(p.title + draft + "\n " + p.slug + ""); } const navRow: { text: string; callback_data: string }[] = []; if (page > 1) navRow.push({ text: "◀ 上一页", callback_data: "list_" + (page - 1) }); if (page < totalPages) navRow.push({ text: "下一页 ▶", callback_data: "list_" + (page + 1) }); await tg("sendMessage", { chat_id: chatId, text: lines.join("\n"), parse_mode: "HTML", disable_web_page_preview: true, reply_markup: navRow.length ? { inline_keyboard: [navRow] } : undefined, }); } catch (e: unknown) { await sendMain(chatId, "❌ " + (e instanceof Error ? e.message : String(e))); } }; // ---- Articles: View / Title / Author / Preview ---- handlers.view = async (chatId, slug) => { if (!slug) { await sendMain(chatId, "格式:/view slug"); return; } const ip = getLanIP(); await tg("sendMessage", { chat_id: chatId, text: "🔖 " + slug + "\n\n📱 http://" + ip + ":1313/" + slug + ".html\n💻 http://localhost:1313/" + slug + ".html", parse_mode: "HTML", disable_web_page_preview: true, reply_markup: { inline_keyboard: [[{ text: "🌐 浏览器打开", url: "http://localhost:1313/" + slug + ".html" }]] }, }); }; handlers.title = async (chatId, newTitle) => { if (!newTitle) { await tg("sendMessage", { chat_id: chatId, text: "请输入新标题:", reply_markup: { force_reply: true, input_field_placeholder: "新标题..." } }); return; } const s = getSession(chatId); s.title = newTitle; await sendWrite(chatId, "✏ 标题已更新:" + newTitle + ""); }; handlers.author = async (chatId, author) => { getSession(chatId).author = author || DEFAULT_AUTHOR; await sendWrite(chatId, "✍ 作者:" + getSession(chatId).author); }; handlers.preview = async (chatId) => { const s = getSession(chatId); if (!s || !s.title) { await sendMain(chatId, "还没开始写。发 /new 开始。"); return; } if (!s.content) { await sendWrite(chatId, "正文为空。"); return; } await sendWrite(chatId, "📝 " + s.title + "\n\n" + truncateText(s.content, 3800)); }; // ---- Writing: Publish / Draft / Deploy / Sync / Undo / Cancel ---- handlers.publish = async (chatId) => { const s = getSession(chatId); if (!s || !s.title) { await sendMain(chatId, "还没开始写。发 /new 开始。"); return; } if (!s.content) { await sendWrite(chatId, "正文为空,先写点内容再发布。"); return; } try { const title = s.title; await saveSession(s, false); clearSession(chatId); await sendMain(chatId, "✅ 已保存:" + title + "\n\n发 /deploy 推送到线上"); } catch (e: unknown) { await sendWrite(chatId, "❌ 保存失败:" + (e instanceof Error ? e.message : String(e))); } }; handlers.draft = async (chatId) => { const s = getSession(chatId); if (!s || !s.title) { await sendMain(chatId, "还没开始写。发 /new 开始。"); return; } if (!s.content) { await sendWrite(chatId, "正文为空,先写点内容再保存。"); return; } try { const title = s.title; await saveSession(s, true); clearSession(chatId); await sendMain(chatId, "📄 草稿已保存:" + title + ""); } catch (e: unknown) { await sendWrite(chatId, "❌ 保存失败:" + (e instanceof Error ? e.message : String(e))); } }; handlers.deploy = async (chatId) => { await sendMessage(chatId, "⏳ 正在部署...", { reply_markup: { remove_keyboard: true } }); const result = await deployQueue.enqueue("发布文章"); if (result.success) await sendMain(chatId, "🚀 已部署!\n\n🌐 https://usj.cc"); else if (result.conflict) await sendMain(chatId, "⚠️ " + (result.error || "远程冲突")); else await sendMain(chatId, "❌ 部署失败:" + (result.error || "未知错误")); }; handlers.sync = async (chatId) => { await sendMessage(chatId, "⏳ 正在同步远程..."); const result = await deployQueue.sync(); if (result.success) await sendMain(chatId, "✅ 同步完成\n\n" + (result.output || "").slice(0, 500)); else await sendMain(chatId, "❌ " + (result.error || "同步失败")); }; handlers.undo = async (chatId) => { if (!hasSession(chatId)) { await sendMain(chatId, "没有活动的草稿。"); return; } const ok = undoLastChunk(chatId); if (!ok) { await sendWrite(chatId, "没有可撤销的内容。"); return; } const s = getSession(chatId); await sendWrite(chatId, "↩ 已撤销\n\n📝 " + s.title + "\n\n" + (s.content ? truncateText(s.content, 3800) : "(正文为空)")); }; handlers.cancel = async (chatId) => { if (!hasSession(chatId)) { await sendMain(chatId, "没有活动的草稿。"); return; } clearSession(chatId); await sendMain(chatId, "✂ 草稿已丢弃。"); }; // ---- Links ---- handlers.links = async (chatId, pageStr) => { const page = parseInt(pageStr || "1") || 1; const perPage = 10; try { const allLinks = await cachedLinks(true); if (allLinks.length === 0) { await sendMain(chatId, "还没有友链。\n\n添加:/link_add url 名称"); return; } const totalPages = Math.ceil(allLinks.length / perPage); const p = Math.min(page, totalPages); const start = (p - 1) * perPage; const pageLinks = allLinks.slice(start, start + perPage); const lines = ["🔗 友链 共 " + allLinks.length + " 个 · 第 " + p + "/" + totalPages + " 页\n"]; for (const l of pageLinks) { const icon = l.hidden ? "🔒" : "🌐"; lines.push(icon + " " + (l.name || l.url) + "\n " + l.url); } lines.push("\n/link_add /link_edit /link_del /link_toggle"); const navRow: { text: string; callback_data: string }[] = []; if (p > 1) navRow.push({ text: "◀ 上一页", callback_data: "links_" + (p - 1) }); if (p < totalPages) navRow.push({ text: "下一页 ▶", callback_data: "links_" + (p + 1) }); await tg("sendMessage", { chat_id: chatId, text: lines.join("\n"), parse_mode: "HTML", disable_web_page_preview: true, reply_markup: navRow.length ? { inline_keyboard: [navRow] } : undefined, }); } catch (e) { await sendMain(chatId, "⚠️ API 暂时不可用,请稍后再试"); } }; handlers.link_add = async (chatId, arg) => { if (!arg) { await tg("sendMessage", { chat_id: chatId, text: "请输入友链信息:\n格式:url 名称", reply_markup: { force_reply: true, input_field_placeholder: "url 名称" } }); return; } const parts = arg.split(/\s+/); let url = parts[0]; const name = parts.slice(1).join(" ") || url; if (!url.startsWith("http")) url = "https://" + url; try { await addLink({ name, url }); invalidateByPrefix("links"); await sendMain(chatId, "✅ 友链已添加:" + name); } catch (e) { await sendMain(chatId, "⚠️ API 暂时不可用,请稍后再试"); } }; handlers.link_edit = async (chatId, arg) => { if (!arg) { await sendMain(chatId, "格式:/link_edit url\n用 /links 查看友链。"); return; } const s = getSession(chatId); s.originalSlug = arg; await tg("sendMessage", { chat_id: chatId, text: "当前 URL:" + arg + "\n请输入新名称(空则保持不变):", reply_markup: { force_reply: true, input_field_placeholder: "新名称" }, }); }; // 处理友链编辑的回复 handlers.handleLinkEditReply = async (chatId, newName) => { const s = getSession(chatId); const oldUrl = s.originalSlug; if (!oldUrl) { await sendMain(chatId, "⚠️ 会话已过期,请重新执行 /link_edit"); return; } try { // 如果用户输入了新名称,更新友链 if (newName && newName.trim()) { await updateLink(oldUrl, { name: newName.trim() }); await sendMain(chatId, "✅ 友链已更新:" + newName.trim()); } else { await sendMain(chatId, "ℹ️ 未做修改"); } // 清除保存的 URL s.originalSlug = undefined; invalidateByPrefix("links"); } catch (e) { await sendMain(chatId, "⚠️ 更新失败,请稍后再试"); } }; handlers.link_del = async (chatId, url) => { if (!url) { await sendMain(chatId, "格式:/link_del url"); return; } try { await deleteLink(url); invalidateByPrefix("links"); await sendMain(chatId, "✅ 已删除:" + url); } catch (e) { await sendMain(chatId, "⚠️ API 暂时不可用,请稍后再试"); } }; handlers.link_toggle = async (chatId, url) => { if (!url) { await sendMain(chatId, "格式:/link_toggle url"); return; } try { const allLinks = await cachedLinks(true); const link = allLinks.find((l) => l.url === url); if (!link) { await sendMain(chatId, "❌ 未找到:" + url); return; } await updateLink(url, { hidden: !link.hidden }); invalidateByPrefix("links"); await sendMain(chatId, (!link.hidden ? "🔒 已隐藏" : "🌐 已显示") + ":" + (link.name || url)); } catch (e) { await sendMain(chatId, "⚠️ API 暂时不可用,请稍后再试"); } }; // ---- Feeds ---- handlers.feeds = async (chatId, pageStr) => { const page = parseInt(pageStr || "1") || 1; const perPage = 10; try { const allFeeds = await cachedFeeds(); if (allFeeds.length === 0) { await sendMain(chatId, "还没有订阅源。\n\n添加:/feed_add url"); return; } const totalPages = Math.ceil(allFeeds.length / perPage); const p = Math.min(page, totalPages); const start = (p - 1) * perPage; const pageFeeds = allFeeds.slice(start, start + perPage); const lines = ["📡 订阅源 共 " + allFeeds.length + " 个 · 第 " + p + "/" + totalPages + " 页\n"]; for (const f of pageFeeds) { lines.push("📰 " + (f.feedTitle || f.url) + "\n " + f.url); } lines.push("\n/feed_add /feed_edit /feed_del /feed_health"); const navRow: { text: string; callback_data: string }[] = []; if (p > 1) navRow.push({ text: "◀ 上一页", callback_data: "feeds_" + (p - 1) }); if (p < totalPages) navRow.push({ text: "下一页 ▶", callback_data: "feeds_" + (p + 1) }); await tg("sendMessage", { chat_id: chatId, text: lines.join("\n"), parse_mode: "HTML", disable_web_page_preview: true, reply_markup: navRow.length ? { inline_keyboard: [navRow] } : undefined, }); } catch (e) { await sendMain(chatId, "⚠️ API 暂时不可用,请稍后再试"); } }; handlers.feed_add = async (chatId, arg) => { if (!arg) { await tg("sendMessage", { chat_id: chatId, text: "请输入订阅源 URL:", reply_markup: { force_reply: true, input_field_placeholder: "https://example.com/rss.xml" } }); return; } const parts = arg.split(/\s+/); const url = parts[0]; const feedTitle = parts.slice(1).join(" ") || ""; try { await addFeed(url, feedTitle); invalidateByPrefix("feeds"); await sendMain(chatId, "✅ 订阅源已添加:" + url); } catch (e) { await sendMain(chatId, "⚠️ API 暂时不可用,请稍后再试"); } }; handlers.feed_edit = async (chatId, arg) => { if (!arg) { await sendMain(chatId, "格式:/feed_edit url\n用 /feeds 查看。"); return; } const s = getSession(chatId); s.originalSlug = arg; await tg("sendMessage", { chat_id: chatId, text: "当前 URL:" + arg + "\n请输入新的订阅信息:\n格式:新URL 新名称", reply_markup: { force_reply: true, input_field_placeholder: "新URL 新名称" }, }); }; // 处理订阅编辑的回复 handlers.handleFeedEditReply = async (chatId, newText) => { const s = getSession(chatId); const oldUrl = s.originalSlug; if (!oldUrl) { await sendMain(chatId, "⚠️ 会话已过期,请重新执行 /feed_edit"); return; } try { if (newText && newText.trim()) { const parts = newText.trim().split(/\s+/); const newUrl = parts[0] || oldUrl; const feedTitle = parts.slice(1).join(" ") || ""; await updateFeed(oldUrl, newUrl, feedTitle); await sendMain(chatId, "✅ 订阅源已更新:" + (feedTitle || newUrl)); } else { await sendMain(chatId, "ℹ️ 未做修改"); } s.originalSlug = undefined; invalidateByPrefix("feeds"); } catch (e) { await sendMain(chatId, "⚠️ 更新失败,请稍后再试"); } }; handlers.feed_del = async (chatId, url) => { if (!url) { await sendMain(chatId, "格式:/feed_del url"); return; } try { await deleteFeed(url); invalidateByPrefix("feeds"); await sendMain(chatId, "✅ 已删除:" + url); } catch (e) { await sendMain(chatId, "⚠️ API 暂时不可用,请稍后再试"); } }; handlers.feed_health = async (chatId) => { try { await sendMessage(chatId, "⏳ 正在检测订阅源..."); const result = await cachedHealth(); const lines = ["📊 订阅源健康报告\n", "✅ 存活:" + result.alive + " ❌ 异常:" + result.dead]; const dead = (result.results || []).filter((r) => !r.ok); if (dead.length > 0) { lines.push("\n❌ 异常站点:"); for (const d of dead) { lines.push("🔴 " + d.url + " — " + (d.error || String(d.status))); } } await sendMain(chatId, lines.join("\n")); } catch (e) { await sendMain(chatId, "⚠️ API 暂时不可用,请稍后再试"); } }; // ---- Read ---- handlers.read = async (chatId) => { try { await sendMessage(chatId, "📰 正在获取友链最新文章..."); const articles = await getArticles(10); if (articles.length === 0) { await sendMain(chatId, "暂时没有新文章。"); return; } const lines = ["📰 友链最新文章\n"]; for (const a of articles) { const date = new Date(a.pubDate).toLocaleDateString("zh-CN", { timeZone: "Asia/Shanghai" }); lines.push("• " + a.title + ""); lines.push(" " + (a.feedName || a.siteUrl) + " · " + date); } await tg("sendMessage", { chat_id: chatId, text: lines.join("\n"), parse_mode: "HTML", disable_web_page_preview: true, }); } catch (e) { await sendMain(chatId, "⚠️ 获取失败,请稍后再试"); } }; // ---- Stats ---- handlers.stats = async (chatId) => { try { const posts = await listPosts({ page: 1, perPage: 10000 }); const lines = ["📊 网站数据\n"]; lines.push("📝 文章总数:" + posts.total + " 篇"); lines.push("🌐 访问地址:https://post.usj.cc"); await sendMain(chatId, lines.join("\n")); } catch (e) { await sendMain(chatId, "⚠️ 获取失败,请稍后再试"); } }; export default handlers;