-
Notifications
You must be signed in to change notification settings - Fork 40
Expand file tree
/
Copy pathcheck_api.js
More file actions
190 lines (161 loc) · 6.18 KB
/
Copy pathcheck_api.js
File metadata and controls
190 lines (161 loc) · 6.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// check_sources_queue_retry.js
const fs = require("fs");
const path = require("path");
const axios = require("axios");
// === 配置 ===
const CONFIG_PATH = path.join(__dirname, "LunaTV-config.json");
const REPORT_PATH = path.join(__dirname, "report.md");
const MAX_DAYS = 30;
const WARN_STREAK = 3;
const ENABLE_SEARCH_TEST = true;
const SEARCH_KEYWORD = process.argv[2] || "斗罗大陆";
const TIMEOUT_MS = 10000;
const CONCURRENT_LIMIT = 10; // 并发限制
const MAX_RETRY = 3; // 请求最大重试次数
const RETRY_DELAY_MS = 500; // 重试间隔(ms)
// === 加载配置 ===
if (!fs.existsSync(CONFIG_PATH)) {
console.error("❌ 配置文件不存在:", CONFIG_PATH);
process.exit(1);
}
const config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
const apiEntries = Object.values(config.api_site).map((s) => ({
name: s.name,
api: s.api,
detail: s.detail || "-",
disabled: !!s.disabled,
}));
// === 读取历史记录 ===
let history = [];
if (fs.existsSync(REPORT_PATH)) {
const old = fs.readFileSync(REPORT_PATH, "utf-8");
const match = old.match(/```json\n([\s\S]+?)\n```/);
if (match) {
try {
history = JSON.parse(match[1]);
} catch {}
}
}
// === 当前 CST 时间 ===
const now = new Date(Date.now() + 8 * 60 * 60 * 1000)
.toISOString()
.replace("T", " ")
.slice(0, 16) + " CST";
// === 工具函数(带重试) ===
const delay = ms => new Promise(r => setTimeout(r, ms));
const safeGet = async (url) => {
for (let attempt = 1; attempt <= MAX_RETRY; attempt++) {
try {
const res = await axios.get(url, { timeout: TIMEOUT_MS });
return res.status === 200;
} catch {
if (attempt < MAX_RETRY) await delay(RETRY_DELAY_MS);
else return false;
}
}
};
const testSearch = async (api, keyword) => {
for (let attempt = 1; attempt <= MAX_RETRY; attempt++) {
try {
const url = `${api}?wd=${encodeURIComponent(keyword)}`;
const res = await axios.get(url, { timeout: TIMEOUT_MS });
if (res.status !== 200 || !res.data || typeof res.data !== "object") return "❌";
const list = res.data.list || [];
if (!list.length) return "无结果";
return list.some(item => JSON.stringify(item).includes(keyword)) ? "✅" : "不匹配";
} catch {
if (attempt < MAX_RETRY) await delay(RETRY_DELAY_MS);
else return "❌";
}
}
};
// === 队列并发执行函数 ===
const queueRun = (tasks, limit) => {
let index = 0;
let active = 0;
const results = [];
return new Promise(resolve => {
const next = () => {
while (active < limit && index < tasks.length) {
const i = index++;
active++;
tasks[i]().then(res => results[i] = res)
.catch(err => results[i] = { error: err })
.finally(() => {
active--;
next();
});
}
if (index >= tasks.length && active === 0) resolve(results);
};
next();
});
};
// === 主逻辑 ===
(async () => {
console.log("⏳ 正在检测 API 与搜索功能可用性(队列并发 + 重试机制)...");
const tasks = apiEntries.map(({ name, api, disabled }) => async () => {
if (disabled) return { name, api, disabled, success: false, searchStatus: "无法搜索" };
const ok = await safeGet(api);
const searchStatus = ENABLE_SEARCH_TEST ? await testSearch(api, SEARCH_KEYWORD) : "-";
return { name, api, disabled, success: ok, searchStatus };
});
const todayResults = await queueRun(tasks, CONCURRENT_LIMIT);
const todayRecord = {
date: new Date().toISOString().slice(0, 10),
keyword: SEARCH_KEYWORD,
results: todayResults,
};
history.push(todayRecord);
if (history.length > MAX_DAYS) history = history.slice(-MAX_DAYS);
// === 统计和生成报告 ===
const stats = {};
for (const { name, api, detail, disabled } of apiEntries) {
stats[api] = { name, api, detail, disabled, ok: 0, fail: 0, fail_streak: 0, trend: "", searchStatus: "-", status: "❌" };
for (const day of history) {
const rec = day.results.find((x) => x.api === api);
if (!rec) continue;
if (rec.success) stats[api].ok++;
else stats[api].fail++;
}
let streak = 0;
for (let i = history.length - 1; i >= 0; i--) {
const rec = history[i].results.find((x) => x.api === api);
if (!rec) continue;
if (rec.success) break;
streak++;
}
const total = stats[api].ok + stats[api].fail;
stats[api].successRate = total > 0 ? ((stats[api].ok / total) * 100).toFixed(1) + "%" : "-";
const recent = history.slice(-7);
stats[api].trend = recent.map(day => {
const r = day.results.find(x => x.api === api);
return r ? (r.success ? "✅" : "❌") : "-";
}).join("");
const latest = todayResults.find(x => x.api === api);
if (latest) stats[api].searchStatus = latest.searchStatus;
if (disabled) stats[api].status = "🚫";
else if (streak >= WARN_STREAK) stats[api].status = "🚨";
else if (latest?.success) stats[api].status = "✅";
}
// === 生成 Markdown 报告 ===
let md = `# 源接口健康检测报告\n\n`;
md += `最近更新时间:${now}\n\n`;
md += `**总源数:** ${apiEntries.length} | **检测关键词:** ${SEARCH_KEYWORD}\n\n`;
md += "| 状态 | 资源名称 | 地址 | API | 搜索功能 | 成功次数 | 失败次数 | 成功率 | 最近7天趋势 |\n";
md += "|------|---------|-----|-----|---------|---------:|--------:|-------:|--------------|\n";
const sorted = Object.values(stats).sort((a, b) => {
const order = { "🚨": 1, "❌": 2, "✅": 3, "🚫": 4 };
return order[a.status] - order[b.status];
});
for (const s of sorted) {
const detailLink = s.detail.startsWith("http") ? `[Link](${s.detail})` : s.detail;
const apiLink = `[Link](${s.api})`;
md += `| ${s.status} | ${s.name} | ${detailLink} | ${apiLink} | ${s.searchStatus} | ${s.ok} | ${s.fail} | ${s.successRate} | ${s.trend} |\n`;
}
md += `\n<details>\n<summary>📜 点击展开查看历史检测数据 (JSON)</summary>\n\n`;
md += "```json\n" + JSON.stringify(history, null, 2) + "\n```\n";
md += `</details>\n`;
fs.writeFileSync(REPORT_PATH, md, "utf-8");
console.log("📄 报告已生成:", REPORT_PATH);
})();