guanghulab/scripts/push-inspection-report.js
2026-05-10 13:12:44 +08:00

305 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* scripts/push-inspection-report.js
* 铸渊巡检报告推送脚本
*
* 将巡检报告推送到两个 Notion 工作空间的工单库:
* - 冰朔空间(零点原核)
* - 之之空间(明天见频道)
*
* 同时在仓库 Issue 区更新固定标题的巡检公告。
*/
'use strict';
const fs = require('fs');
const path = require('path');
const https = require('https');
const ROOT = path.resolve(__dirname, '..');
const BRAIN_DIR = path.join(ROOT, '.github/persona-brain');
const REPORT_PATH = path.join(BRAIN_DIR, 'inspection-report.json');
const NOTION_TOKEN_BINGSUO = process.env.NOTION_TOKEN_BINGSUO;
const NOTION_TOKEN_ZHIZHI = process.env.NOTION_TOKEN_ZHIZHI;
const WORKORDER_DB_BINGSUO = process.env.WORKORDER_DB_BINGSUO;
const WORKORDER_DB_ZHIZHI = process.env.WORKORDER_DB_ZHIZHI;
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
const REPO = process.env.GITHUB_REPOSITORY || 'qinfendebingshuo/guanghulab';
const today = new Date().toISOString().split('T')[0];
console.log(`📤 巡检报告推送开始 · ${today}`);
// ── Notion API helper ─────────────────────────────────────────────────────
function notionRequest(token, method, urlPath, body) {
return new Promise((resolve) => {
const postData = body ? JSON.stringify(body) : '';
const options = {
hostname: 'api.notion.com',
path: urlPath,
method: method,
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28',
},
};
if (body) {
options.headers['Content-Length'] = Buffer.byteLength(postData);
}
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
try {
resolve({ statusCode: res.statusCode, data: JSON.parse(data) });
} catch {
resolve({ statusCode: res.statusCode, data: null });
}
});
});
req.on('error', (err) => {
console.error(` ⚠️ Notion API 请求失败: ${err.message}`);
resolve({ statusCode: 0, data: null });
});
if (body) req.write(postData);
req.end();
});
}
// ── GitHub API helper ─────────────────────────────────────────────────────
function githubApi(urlPath, method = 'GET', body = null) {
return new Promise((resolve) => {
const postData = body ? JSON.stringify(body) : '';
const options = {
hostname: 'api.github.com',
path: urlPath,
method: method,
headers: {
'User-Agent': 'zhuyuan-inspection-report',
'Accept': 'application/vnd.github.v3+json',
},
};
if (GITHUB_TOKEN) {
options.headers['Authorization'] = `Bearer ${GITHUB_TOKEN}`;
}
if (body) {
options.headers['Content-Type'] = 'application/json';
options.headers['Content-Length'] = Buffer.byteLength(postData);
}
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
try {
resolve({ statusCode: res.statusCode, data: JSON.parse(data) });
} catch {
resolve({ statusCode: res.statusCode, data: null });
}
});
});
req.on('error', (err) => {
console.error(` ⚠️ GitHub API 请求失败: ${err.message}`);
resolve({ statusCode: 0, data: null });
});
if (body) req.write(postData);
req.end();
});
}
// ── 生成报告 Markdown ─────────────────────────────────────────────────────
function generateReportMarkdown(report) {
const lines = [];
lines.push(`# [铸渊公告栏] 每日人格体签到报告`);
lines.push(`📅 日期: ${report.date}`);
lines.push(`📊 总计: ${report.summary.total} | ✅ 已签到: ${report.summary.checked_in} | ❌ 缺席: ${report.summary.missing}`);
lines.push('');
// 已签到列表
lines.push('## ✅ 已签到小兵');
if (report.checked_in && report.checked_in.length > 0) {
lines.push('| Agent ID | 名称 |');
lines.push('|----------|------|');
for (const agent of report.checked_in) {
lines.push(`| ${agent.agent_id} | ${agent.agent_name} |`);
}
} else {
lines.push('无');
}
lines.push('');
// 缺席列表
lines.push('## ❌ 缺席小兵');
if (report.missing_agents && report.missing_agents.length > 0) {
lines.push('| Agent ID | 名称 |');
lines.push('|----------|------|');
for (const agent of report.missing_agents) {
lines.push(`| ${agent.agent_id} | ${agent.agent_name} |`);
}
} else {
lines.push('无');
}
lines.push('');
// 自修复情况
lines.push('## 🔧 自修复情况');
if (report.auto_repaired && report.auto_repaired.length > 0) {
lines.push('| Agent ID | 名称 | 操作 | 结果 |');
lines.push('|----------|------|------|------|');
for (const agent of report.auto_repaired) {
lines.push(`| ${agent.agent_id} | ${agent.agent_name} | ${agent.action} | ${agent.result} |`);
}
} else {
lines.push('无需自修复');
}
lines.push('');
// 需要干预
lines.push('## 📌 需要之之干预');
if (report.intervention_required && report.intervention_required.length > 0) {
lines.push('| Agent ID | 名称 | 原因 |');
lines.push('|----------|------|------|');
for (const agent of report.intervention_required) {
lines.push(`| ${agent.agent_id} | ${agent.agent_name} | ${agent.reason} |`);
}
} else {
lines.push('全部正常,无需干预 🎉');
}
lines.push('');
lines.push(`---`);
lines.push(`> 报告由铸渊自动生成 · ${new Date().toISOString()}`);
return lines.join('\n');
}
// ── 推送到 Notion ─────────────────────────────────────────────────────────
async function pushToNotion(token, dbId, report, target) {
if (!token || !dbId) {
console.log(` ⚠️ ${target}: 缺少 Notion Token 或 DB ID跳过推送`);
return;
}
const body = {
parent: { database_id: dbId },
properties: {
'标题': {
title: [{ text: { content: report.title } }],
},
'类型': {
select: { name: '巡检报告' },
},
'日期': {
date: { start: report.date },
},
},
children: [
{
object: 'block',
type: 'paragraph',
paragraph: {
rich_text: [{
text: {
content: `已签到: ${report.summary.checked_in} / 缺席: ${report.summary.missing} / 自修复: ${report.summary.auto_repaired} / 需干预: ${report.summary.needs_intervention}`,
},
}],
},
},
],
};
const result = await notionRequest(token, 'POST', '/v1/pages', body);
if (result.statusCode === 200 || result.statusCode === 201) {
console.log(`${target}: 工单推送成功`);
} else {
console.log(` ⚠️ ${target}: 工单推送失败 (HTTP ${result.statusCode})`);
}
}
// ── 更新 GitHub Issue ─────────────────────────────────────────────────────
async function updateIssue(report) {
if (!GITHUB_TOKEN) {
console.log(' ⚠️ 缺少 GITHUB_TOKEN跳过 Issue 更新');
return;
}
const issueTitle = '[铸渊公告栏] 每日人格体签到报告';
const body = generateReportMarkdown(report);
// 搜索已存在的 Issue
const searchResult = await githubApi(
`/repos/${REPO}/issues?state=open&labels=${encodeURIComponent('铸渊公告栏')}&per_page=10`
);
let existingIssue = null;
if (searchResult.data && Array.isArray(searchResult.data)) {
existingIssue = searchResult.data.find(i => i.title === issueTitle);
}
if (existingIssue) {
// 更新现有 Issue
const updateResult = await githubApi(
`/repos/${REPO}/issues/${existingIssue.number}`,
'PATCH',
{ body: body }
);
if (updateResult.statusCode === 200) {
console.log(` ✅ Issue #${existingIssue.number} 已更新`);
} else {
console.log(` ⚠️ Issue 更新失败 (HTTP ${updateResult.statusCode})`);
}
} else {
// 创建新 Issue
const createResult = await githubApi(
`/repos/${REPO}/issues`,
'POST',
{
title: issueTitle,
body: body,
labels: ['铸渊公告栏'],
}
);
if (createResult.statusCode === 201) {
console.log(` ✅ Issue #${createResult.data.number} 已创建`);
} else {
console.log(` ⚠️ Issue 创建失败 (HTTP ${createResult.statusCode})`);
}
}
}
// ── 主流程 ────────────────────────────────────────────────────────────────
async function main() {
// 读取巡检报告
if (!fs.existsSync(REPORT_PATH)) {
console.error('❌ inspection-report.json 不存在,请先运行巡检脚本');
process.exit(1);
}
const report = JSON.parse(fs.readFileSync(REPORT_PATH, 'utf8'));
// 推送到冰朔空间(零点原核)
console.log('\n📤 推送到冰朔空间(零点原核)...');
await pushToNotion(NOTION_TOKEN_BINGSUO, WORKORDER_DB_BINGSUO, report, '零点原核');
// 推送到之之空间(明天见频道)
console.log('\n📤 推送到之之空间(明天见频道)...');
await pushToNotion(NOTION_TOKEN_ZHIZHI, WORKORDER_DB_ZHIZHI, report, '明天见频道');
// 更新 GitHub Issue
console.log('\n📤 更新 GitHub Issue 公告栏...');
await updateIssue(report);
console.log(`\n✅ 巡检报告推送完成`);
}
main().catch((err) => {
console.error('❌ 推送脚本异常:', err.message);
process.exit(1);
});