305 lines
10 KiB
JavaScript
305 lines
10 KiB
JavaScript
|
|
/**
|
|||
|
|
* 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);
|
|||
|
|
});
|