1527
This commit is contained in:
1 parent
eabdb1a507
commit
0d698d40d8
1 file changed
+61
-28
@@ -1,7 +1,7 @@
|
|||||||
import { sendMessage } from "./tg";
|
import { sendMessage } from "./tg";
|
||||||
import { ALLOWED_IDS } from "./config";
|
import { ALLOWED_IDS } from "./config";
|
||||||
import { getFeedsHealth } from "@/lib/rssapi";
|
import { getLinks, checkLinkViaProxy } from "@/lib/rssapi";
|
||||||
import { ARTALK_SERVER, ARTALK_SITE, ARTALK_ADMIN_EMAIL, ARTALK_ADMIN_PASSWORD } from "@/lib/config";
|
import { ARTALK_SERVER, ARTALK_SITE, ARTALK_ADMIN_EMAIL, ARTALK_ADMIN_PASSWORD, CONTENT_DIR } from "@/lib/config";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
@@ -51,28 +51,51 @@ async function notifyAll(message: string): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 功能 1: 友链状态监控
|
// 功能 1: 友链状态监控(使用 SCF 代理)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
export async function checkLinksStatus(): Promise<void> {
|
export async function checkLinksStatus(): Promise<void> {
|
||||||
console.log("[blog-monitor] 🔗 checking links status...");
|
console.log("[blog-monitor] 🔗 checking links status...");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 直接调用 API 的健康检查端点
|
// 获取可见友链(不传 all 参数,API 自动过滤隐藏友链)
|
||||||
const healthResult = await getFeedsHealth();
|
const links = await getLinks();
|
||||||
|
|
||||||
if (!healthResult || !healthResult.results) {
|
if (links.length === 0) {
|
||||||
console.log("[blog-monitor] ❌ failed to fetch health status");
|
console.log("[blog-monitor] ℹ️ no visible links found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { total, alive, dead, results } = healthResult;
|
console.log(`[blog-monitor] 📊 checking ${links.length} visible links...`);
|
||||||
|
|
||||||
|
// 使用 SCF 代理并行检查所有链接(限制并发数为 10)
|
||||||
|
const batchSize = 10;
|
||||||
|
const results: Array<{ name: string; url: string; ok: boolean; status?: number; latency?: number; error?: string }> = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < links.length; i += batchSize) {
|
||||||
|
const batch = links.slice(i, i + batchSize);
|
||||||
|
const batchResults = await Promise.all(
|
||||||
|
batch.map(async (link) => {
|
||||||
|
const health = await checkLinkViaProxy(link.url);
|
||||||
|
return {
|
||||||
|
name: link.name,
|
||||||
|
url: link.url,
|
||||||
|
ok: health.ok,
|
||||||
|
status: health.status,
|
||||||
|
latency: health.latency,
|
||||||
|
error: health.error,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
results.push(...batchResults);
|
||||||
|
}
|
||||||
|
|
||||||
// 分类链接状态
|
// 分类链接状态
|
||||||
const invalidLinks = results.filter((r) => !r.ok);
|
const invalidLinks = results.filter((r) => !r.ok);
|
||||||
const slowLinks = results.filter((r) => r.ok && r.latency && r.latency > 3000);
|
const slowLinks = results.filter((r) => r.ok && r.latency && r.latency > 3000);
|
||||||
|
const validLinks = results.filter((r) => r.ok);
|
||||||
|
|
||||||
console.log(`[blog-monitor] ✅ links check completed: ${alive} valid, ${dead} invalid, ${slowLinks.length} slow`);
|
console.log(`[blog-monitor] ✅ links check completed: ${validLinks.length} valid, ${invalidLinks.length} invalid, ${slowLinks.length} slow`);
|
||||||
|
|
||||||
// 只在有失效或缓慢链接时发送通知
|
// 只在有失效或缓慢链接时发送通知
|
||||||
if (invalidLinks.length > 0 || slowLinks.length > 0) {
|
if (invalidLinks.length > 0 || slowLinks.length > 0) {
|
||||||
@@ -80,39 +103,39 @@ export async function checkLinksStatus(): Promise<void> {
|
|||||||
`🔗 <b>友链状态监控报告</b>`,
|
`🔗 <b>友链状态监控报告</b>`,
|
||||||
``,
|
``,
|
||||||
`📊 <b>总体情况:</b>`,
|
`📊 <b>总体情况:</b>`,
|
||||||
`├─ 📁 总数:${total} 个`,
|
`├─ 📁 总数:${links.length} 个`,
|
||||||
`├─ ✅ 有效:${alive} 个`,
|
`├─ ✅ 有效:${validLinks.length} 个`,
|
||||||
`├─ ❌ 失效:${dead} 个`,
|
`├─ ❌ 失效:${invalidLinks.length} 个`,
|
||||||
`└─ ⚠️ 缓慢:${slowLinks.length} 个(> 3 秒)`,
|
`└─ ⚠️ 缓慢:${slowLinks.length} 个(> 3 秒)`,
|
||||||
``,
|
``,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (invalidLinks.length > 0) {
|
if (invalidLinks.length > 0) {
|
||||||
msg.push(`🚨 <b>失效链接:</b>`);
|
msg.push(`🚨 <b>失效链接:</b>`);
|
||||||
invalidLinks.slice(0, 5).forEach((link, index) => {
|
invalidLinks.forEach((link, index) => {
|
||||||
const error = link.error || `HTTP ${link.status}`;
|
const error = link.error || `HTTP ${link.status}`;
|
||||||
const prefix = index === invalidLinks.length - 1 || index === 4 ? "└─" : "├─";
|
const prefix = index === invalidLinks.length - 1 ? "└─" : "├─";
|
||||||
msg.push(`${prefix} 🔴 ${link.url}`);
|
msg.push(`${prefix} 🔴 ${link.name}`);
|
||||||
|
msg.push(` └─ ${link.url}`);
|
||||||
msg.push(` └─ ${error}`);
|
msg.push(` └─ ${error}`);
|
||||||
});
|
});
|
||||||
if (invalidLinks.length > 5) {
|
|
||||||
msg.push(` ⋯ 还有 ${invalidLinks.length - 5} 个`);
|
|
||||||
}
|
|
||||||
msg.push(``);
|
msg.push(``);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (slowLinks.length > 0) {
|
if (slowLinks.length > 0) {
|
||||||
msg.push(`🐢 <b>响应缓慢:</b>`);
|
msg.push(`🐢 <b>响应缓慢:</b>`);
|
||||||
slowLinks.slice(0, 3).forEach((link, index) => {
|
slowLinks.forEach((link, index) => {
|
||||||
const latency = ((link.latency || 0) / 1000).toFixed(1);
|
const latency = ((link.latency || 0) / 1000).toFixed(1);
|
||||||
const prefix = index === slowLinks.length - 1 || index === 2 ? "└─" : "├─";
|
const prefix = index === slowLinks.length - 1 ? "└─" : "├─";
|
||||||
msg.push(`${prefix} 🟡 ${link.url}`);
|
msg.push(`${prefix} 🟡 ${link.name}`);
|
||||||
|
msg.push(` └─ ${link.url}`);
|
||||||
msg.push(` └─ ${latency}秒`);
|
msg.push(` └─ ${latency}秒`);
|
||||||
});
|
});
|
||||||
msg.push(``);
|
msg.push(``);
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.push(`💡 <b>建议:</b>尽快检查并修复失效链接,保持友链健康`);
|
msg.push(`💡 <b>建议:</b>尽快检查并修复失效链接,保持友链健康`);
|
||||||
|
msg.push(`📡 <b>检测方式:</b>SCF 代理`);
|
||||||
|
|
||||||
await notifyAll(msg.join("\n"));
|
await notifyAll(msg.join("\n"));
|
||||||
console.log("[blog-monitor] 📤 links notification sent");
|
console.log("[blog-monitor] 📤 links notification sent");
|
||||||
@@ -132,14 +155,16 @@ export async function checkBlogMilestones(): Promise<void> {
|
|||||||
console.log("[blog-monitor] 🎯 checking blog milestones...");
|
console.log("[blog-monitor] 🎯 checking blog milestones...");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 获取文章数量(通过读取 content/posts 目录)
|
// 使用配置中的 CONTENT_DIR
|
||||||
const postsDir = path.join(process.cwd(), "..", "content", "posts");
|
console.log(`[blog-monitor] 📂 posts directory: ${CONTENT_DIR}`);
|
||||||
let postsCount = 0;
|
let postsCount = 0;
|
||||||
|
|
||||||
if (fs.existsSync(postsDir)) {
|
if (fs.existsSync(CONTENT_DIR)) {
|
||||||
const years = fs.readdirSync(postsDir).filter((f) => /^\d{4}$/.test(f));
|
const years = fs.readdirSync(CONTENT_DIR).filter((f) => /^\d{4}$/.test(f));
|
||||||
|
console.log(`[blog-monitor] 📅 found years: ${years.join(", ")}`);
|
||||||
|
|
||||||
for (const year of years) {
|
for (const year of years) {
|
||||||
const yearDir = path.join(postsDir, year);
|
const yearDir = path.join(CONTENT_DIR, year);
|
||||||
if (fs.statSync(yearDir).isDirectory()) {
|
if (fs.statSync(yearDir).isDirectory()) {
|
||||||
const posts = fs.readdirSync(yearDir).filter((f) => {
|
const posts = fs.readdirSync(yearDir).filter((f) => {
|
||||||
const fullPath = path.join(yearDir, f);
|
const fullPath = path.join(yearDir, f);
|
||||||
@@ -148,11 +173,14 @@ export async function checkBlogMilestones(): Promise<void> {
|
|||||||
postsCount += posts.length;
|
postsCount += posts.length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
console.error(`[blog-monitor] ❌ posts directory not found: ${CONTENT_DIR}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取评论数量
|
// 获取评论数量
|
||||||
let commentsCount = 0;
|
let commentsCount = 0;
|
||||||
const token = await getArtalkToken();
|
const token = await getArtalkToken();
|
||||||
|
console.log(`[blog-monitor] 🔑 artalk token: ${token ? "obtained" : "failed"}`);
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
try {
|
try {
|
||||||
@@ -164,10 +192,15 @@ export async function checkBlogMilestones(): Promise<void> {
|
|||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json() as { count?: number };
|
const data = await res.json() as { count?: number };
|
||||||
commentsCount = data.count || 0;
|
commentsCount = data.count || 0;
|
||||||
|
console.log(`[blog-monitor] 💬 comments API response: ${JSON.stringify(data)}`);
|
||||||
|
} else {
|
||||||
|
console.error(`[blog-monitor] ❌ comments API failed: ${res.status}`);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (e) {
|
||||||
console.error("[blog-monitor] ❌ failed to fetch comments count");
|
console.error("[blog-monitor] ❌ failed to fetch comments:", e instanceof Error ? e.message : String(e));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
console.error("[blog-monitor] ❌ no artalk token, cannot fetch comments");
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[blog-monitor] 📊 stats: ${postsCount} posts, ${commentsCount} comments`);
|
console.log(`[blog-monitor] 📊 stats: ${postsCount} posts, ${commentsCount} comments`);
|
||||||
|
|||||||
Reference in new issue
Block a user