From bf0fae69d65ef0aa2f8bda0b2211db50f5fe8e48 Mon Sep 17 00:00:00 2001 From: Vaica <94612053+zqlit@users.noreply.github.com> Date: Wed, 24 Jun 2026 14:20:43 +0800 Subject: [PATCH] 1420 --- write-server/src/lib/bot/notify.ts | 88 ++++++++++++++++++++++++++++-- write-server/src/lib/config.ts | 2 + 2 files changed, 84 insertions(+), 6 deletions(-) diff --git a/write-server/src/lib/bot/notify.ts b/write-server/src/lib/bot/notify.ts index 863ba642..cee06443 100644 --- a/write-server/src/lib/bot/notify.ts +++ b/write-server/src/lib/bot/notify.ts @@ -1,4 +1,4 @@ -import { ARTALK_SERVER, ARTALK_SITE } from "@/lib/config"; +import { ARTALK_SERVER, ARTALK_SITE, ARTALK_ADMIN_EMAIL, ARTALK_ADMIN_PASSWORD } from "@/lib/config"; import { sendMessage } from "./tg"; import { ALLOWED_IDS } from "./config"; @@ -13,22 +13,98 @@ interface ArtalkComment { let lastCheckedId = 0; let notifierStarted = false; +let authToken: string | null = null; +let tokenExpiry = 0; + +// 登录 Artalk 获取管理员 token +async function loginToArtalk(): Promise { + if (!ARTALK_ADMIN_EMAIL || !ARTALK_ADMIN_PASSWORD) { + console.error("[comment-notify] ARTALK_ADMIN_EMAIL or ARTALK_ADMIN_PASSWORD not configured"); + return null; + } -// 获取所有评论(使用 type=all 参数) -async function fetchLatestComments(): Promise { try { - // 尝试使用 type=all 参数获取所有评论 - const url = `${ARTALK_SERVER}/api/v2/comments?site_name=${encodeURIComponent(ARTALK_SITE)}&type=all&limit=5&sort_by=created_at&sort_order=desc`; - console.log("[comment-notify] fetching comments with type=all..."); + const url = `${ARTALK_SERVER}/api/v2/auth/email/login`; + console.log("[comment-notify] logging in to Artalk..."); const res = await fetch(url, { + method: "POST", headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + email: ARTALK_ADMIN_EMAIL, + password: ARTALK_ADMIN_PASSWORD, + }), + }); + + if (!res.ok) { + console.error("[comment-notify] login failed:", res.status); + return null; + } + + const data = await res.json() as { token?: string; user?: { name: string } }; + if (!data.token) { + console.error("[comment-notify] no token in response"); + return null; + } + + console.log("[comment-notify] logged in as:", data.user?.name || "admin"); + return data.token; + } catch (e) { + console.error("[comment-notify] login error:", e instanceof Error ? e.message : String(e)); + return null; + } +} + +// 获取或刷新 token +async function getAuthToken(): Promise { + const now = Date.now(); + + // Token 有效(提前 5 分钟刷新) + if (authToken && now < tokenExpiry - 5 * 60 * 1000) { + return authToken; + } + + // 登录获取新 token + authToken = await loginToArtalk(); + if (authToken) { + // Token 有效期设为 24 小时(Artalk 默认较长) + tokenExpiry = now + 24 * 60 * 60 * 1000; + } + + return authToken; +} + +// 获取所有评论(使用管理员 API) +async function fetchLatestComments(): Promise { + try { + const token = await getAuthToken(); + if (!token) { + console.error("[comment-notify] no auth token available"); + return []; + } + + // 使用管理员 API 获取所有评论 + const url = `${ARTALK_SERVER}/api/v2/admin/comments?site_name=${encodeURIComponent(ARTALK_SITE)}&limit=5&sort_by=date&sort_order=desc`; + console.log("[comment-notify] fetching comments with admin API..."); + + const res = await fetch(url, { + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${token}`, + }, }); if (!res.ok) { const errorText = await res.text().catch(() => "Unable to read response"); console.error("[comment-notify] fetch failed:", res.status, res.statusText); console.error("[comment-notify] response:", errorText); + + // 如果是认证失败,清除 token 以便下次重试 + if (res.status === 401 || res.status === 403) { + authToken = null; + tokenExpiry = 0; + } + return []; } diff --git a/write-server/src/lib/config.ts b/write-server/src/lib/config.ts index 74883fa6..a0be9e34 100644 --- a/write-server/src/lib/config.ts +++ b/write-server/src/lib/config.ts @@ -13,6 +13,8 @@ export const RECYCLE_POSTS_DIR = path.join(RECYCLE_DIR, "posts"); export const RECYCLE_META_DIR = path.join(RECYCLE_DIR, "meta"); export const ARTALK_SERVER = process.env.ARTALK_SERVER || "https://artalk.usj.cc"; export const ARTALK_SITE = process.env.ARTALK_SITE || "优世界"; +export const ARTALK_ADMIN_EMAIL = process.env.ARTALK_ADMIN_EMAIL || ""; +export const ARTALK_ADMIN_PASSWORD = process.env.ARTALK_ADMIN_PASSWORD || ""; export const ITEMS_PER_PAGE = parseInt(process.env.ITEMS_PER_PAGE || "20", 10); export const RECYCLE_DAYS = parseInt(process.env.RECYCLE_DAYS || "30", 10); export const HUGO_PREVIEW = process.env.NEXT_PUBLIC_HUGO_PREVIEW || process.env.HUGO_PREVIEW || "http://localhost:1313";