guanghulab/scripts/skyeye/dev-portal-notion-sync.js
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

194 lines
6.2 KiB
JavaScript
Raw Permalink 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.

#!/usr/bin/env node
// scripts/skyeye/dev-portal-notion-sync.js
// 开发者门户 · Notion 动态同步
//
// ═══════════════════════════════════════════════
// 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
// 📜 Copyright: 国作登字-2026-A-00037559
// ═══════════════════════════════════════════════
//
// 目的:将开发者频道部署动态同步到 Notion 画像数据库
// 铁律(与 write-receipt-to-notion.js 一致):
// 1. 写入失败不阻塞主流程
// 2. 同一事件幂等
// 3. 失败时写入本地 SYSLOG
//
// 环境变量:
// NOTION_API_KEY — Notion API 密钥(必须)
// DEV_PROFILE_DB_ID — 开发者画像数据库 ID必须
//
// 参数:
// --dev-ids "DEV-001,DEV-002" — 涉及的开发者编号
// --action "channel_deploy" — 操作类型
// --timestamp "ISO-8601" — 时间戳
'use strict';
const fs = require('fs');
const path = require('path');
const https = require('https');
const NOTION_API_KEY = process.env.NOTION_API_KEY || '';
const DEV_PROFILE_DB_ID = process.env.DEV_PROFILE_DB_ID || '';
const ROOT = path.resolve(__dirname, '../..');
const SYSLOG_DIR = path.join(ROOT, 'data/neural-reports/syslog');
// ━━━ 参数解析 ━━━
function parseArgs() {
const args = process.argv.slice(2);
const result = { devIds: [], action: 'unknown', timestamp: new Date().toISOString() };
for (let i = 0; i < args.length; i++) {
if (args[i] === '--dev-ids' && args[i + 1]) {
result.devIds = args[++i].split(',').filter(Boolean);
} else if (args[i] === '--action' && args[i + 1]) {
result.action = args[++i];
} else if (args[i] === '--timestamp' && args[i + 1]) {
result.timestamp = args[++i];
}
}
return result;
}
// ━━━ 加载门禁配置获取开发者信息 ━━━
function loadDevInfo(devId) {
try {
const configPath = path.join(ROOT, '.github/persona-brain/gate-guard-config.json');
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
const dev = config.developer_permissions && config.developer_permissions[devId];
return dev || null;
} catch (e) {
return null;
}
}
// ━━━ Notion API 请求 ━━━
function notionRequest(endpoint, body) {
return new Promise(function(resolve, reject) {
if (!NOTION_API_KEY) {
reject(new Error('NOTION_API_KEY 未配置'));
return;
}
const data = JSON.stringify(body);
const options = {
hostname: 'api.notion.com',
path: endpoint,
method: 'POST',
headers: {
'Authorization': 'Bearer ' + NOTION_API_KEY,
'Notion-Version': '2022-06-28',
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(data)
}
};
const req = https.request(options, function(res) {
let body = '';
res.on('data', function(chunk) { body += chunk; });
res.on('end', function() {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(JSON.parse(body));
} else {
reject(new Error('Notion API ' + res.statusCode + ': ' + body.slice(0, 200)));
}
});
});
req.on('error', reject);
req.setTimeout(15000, function() { req.destroy(new Error('Notion API timeout')); });
req.write(data);
req.end();
});
}
// ━━━ 写入 SYSLOG ━━━
function writeSyslog(entry) {
try {
if (!fs.existsSync(SYSLOG_DIR)) fs.mkdirSync(SYSLOG_DIR, { recursive: true });
const date = new Date().toISOString().slice(0, 10);
const logPath = path.join(SYSLOG_DIR, 'dev-portal-sync-' + date + '.jsonl');
fs.appendFileSync(logPath, JSON.stringify(entry) + '\n');
} catch (e) {
console.error('SYSLOG写入失败:', e.message);
}
}
// ━━━ 同步单个开发者动态到 Notion ━━━
async function syncDevActivity(devId, action, timestamp) {
const devInfo = loadDevInfo(devId);
const properties = {
'开发者编号': { title: [{ text: { content: devId } }] },
'开发者姓名': { rich_text: [{ text: { content: devInfo ? devInfo.name : devId } }] },
'人格体编号': { rich_text: [{ text: { content: devInfo ? devInfo.persona_id : '—' } }] },
'操作类型': { select: { name: action } },
'操作时间': { date: { start: timestamp } },
'门户频道': { rich_text: [{ text: { content: 'channels/' + devId + '/' } }] }
};
try {
await notionRequest('/v1/pages', {
parent: { database_id: DEV_PROFILE_DB_ID },
properties: properties
});
console.log(' ✅ ' + devId + ' 同步成功');
return true;
} catch (e) {
console.error(' ⚠️ ' + devId + ' 同步失败: ' + e.message);
writeSyslog({
time: new Date().toISOString(),
type: 'notion_sync_error',
dev_id: devId,
error: e.message
});
return false;
}
}
// ━━━ 主逻辑 ━━━
async function main() {
const args = parseArgs();
console.log('📡 开发者门户 · Notion 动态同步');
console.log(' 开发者: ' + (args.devIds.join(', ') || '无'));
console.log(' 操作: ' + args.action);
console.log(' 时间: ' + args.timestamp);
console.log('');
if (args.devIds.length === 0) {
console.log('⚠️ 无开发者编号,跳过同步');
process.exit(0);
}
if (!NOTION_API_KEY || !DEV_PROFILE_DB_ID) {
console.log('⚠️ Notion 配置缺失 (NOTION_API_KEY / DEV_PROFILE_DB_ID)');
console.log(' 动态将写入本地 SYSLOG');
writeSyslog({
time: args.timestamp,
type: 'channel_deploy',
dev_ids: args.devIds,
action: args.action,
note: 'Notion未配置写入本地'
});
process.exit(0);
}
let success = 0;
for (const devId of args.devIds) {
const ok = await syncDevActivity(devId, args.action, args.timestamp);
if (ok) success++;
}
console.log('');
console.log('同步结果: ' + success + '/' + args.devIds.length + ' 成功');
}
main().catch(function(e) {
console.error('❌ 同步异常: ' + e.message);
writeSyslog({ time: new Date().toISOString(), type: 'sync_crash', error: e.message });
// Non-fatal — don't block the deploy
process.exit(0);
});