feat: 飞书→Notion全自动闭环 — Pipeline B/C人格体唤醒+飞书推送 + write-notion-syslog.js + push-broadcast-feishu.yml
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
parent
fa60ea92a3
commit
78b000ca71
|
|
@ -0,0 +1,87 @@
|
||||||
|
name: 铸渊 · 广播推送飞书(聊天消息)
|
||||||
|
|
||||||
|
# 独立 workflow:接收广播内容,推送飞书消息卡片到原聊天窗口
|
||||||
|
# 用于自动链路推送失败时的手动兜底,或独立触发推送
|
||||||
|
#
|
||||||
|
# 依赖 Secrets:
|
||||||
|
# PUSH_BROADCAST_TOKEN 广播推送接口鉴权 token
|
||||||
|
#
|
||||||
|
# dispatch payload:
|
||||||
|
# chat_id 飞书聊天窗口 ID(优先)
|
||||||
|
# sender_open_id 发送者飞书 Open ID(备选)
|
||||||
|
# broadcast_title 广播标题
|
||||||
|
# broadcast_content 广播摘要内容
|
||||||
|
# broadcast_url Notion 广播页面链接
|
||||||
|
|
||||||
|
on:
|
||||||
|
repository_dispatch:
|
||||||
|
types: [push-broadcast-feishu]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
chat_id:
|
||||||
|
description: '飞书聊天窗口 ID'
|
||||||
|
required: false
|
||||||
|
sender_open_id:
|
||||||
|
description: '发送者飞书 Open ID'
|
||||||
|
required: false
|
||||||
|
broadcast_title:
|
||||||
|
description: '广播标题'
|
||||||
|
required: true
|
||||||
|
broadcast_content:
|
||||||
|
description: '广播摘要'
|
||||||
|
required: true
|
||||||
|
broadcast_url:
|
||||||
|
description: 'Notion 广播页面链接'
|
||||||
|
required: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
push-to-feishu:
|
||||||
|
name: 📢 推送广播到飞书聊天
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 📢 推送广播消息卡片
|
||||||
|
env:
|
||||||
|
PUSH_URL: https://guanghulab.com/webhook/push-broadcast
|
||||||
|
PUSH_TOKEN: ${{ secrets.PUSH_BROADCAST_TOKEN }}
|
||||||
|
CHAT_ID: ${{ github.event.client_payload.chat_id || github.event.inputs.chat_id || '' }}
|
||||||
|
SENDER_OPEN_ID: ${{ github.event.client_payload.sender_open_id || github.event.inputs.sender_open_id || '' }}
|
||||||
|
BROADCAST_TITLE: ${{ github.event.client_payload.broadcast_title || github.event.inputs.broadcast_title || '' }}
|
||||||
|
BROADCAST_CONTENT: ${{ github.event.client_payload.broadcast_content || github.event.inputs.broadcast_content || '' }}
|
||||||
|
BROADCAST_URL: ${{ github.event.client_payload.broadcast_url || github.event.inputs.broadcast_url || '' }}
|
||||||
|
run: |
|
||||||
|
if [ -z "$PUSH_TOKEN" ]; then
|
||||||
|
echo "⚠️ PUSH_BROADCAST_TOKEN 未配置,跳过推送"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
if [ -z "$CHAT_ID" ] && [ -z "$SENDER_OPEN_ID" ]; then
|
||||||
|
echo "⚠️ 缺少 chat_id 和 sender_open_id,无法推送"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "📢 推送广播到飞书..."
|
||||||
|
echo " 标题: $BROADCAST_TITLE"
|
||||||
|
echo " 目标: ${CHAT_ID:-$SENDER_OPEN_ID}"
|
||||||
|
|
||||||
|
HTTP_CODE=$(curl -s -o /tmp/push-response.txt -w "%{http_code}" \
|
||||||
|
-X POST "$PUSH_URL" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "x-push-token: $PUSH_TOKEN" \
|
||||||
|
-d "$(jq -n \
|
||||||
|
--arg chat_id "$CHAT_ID" \
|
||||||
|
--arg sender_open_id "$SENDER_OPEN_ID" \
|
||||||
|
--arg broadcast_title "$BROADCAST_TITLE" \
|
||||||
|
--arg broadcast_content "$BROADCAST_CONTENT" \
|
||||||
|
--arg broadcast_url "$BROADCAST_URL" \
|
||||||
|
'{chat_id: $chat_id, sender_open_id: $sender_open_id, broadcast_title: $broadcast_title, broadcast_content: $broadcast_content, broadcast_url: $broadcast_url}')")
|
||||||
|
|
||||||
|
echo " HTTP 响应: $HTTP_CODE"
|
||||||
|
cat /tmp/push-response.txt
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
||||||
|
echo "✅ 广播已推送到飞书"
|
||||||
|
else
|
||||||
|
echo "❌ 推送失败 (HTTP $HTTP_CODE)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
@ -23,6 +23,11 @@ jobs:
|
||||||
issues: read
|
issues: read
|
||||||
outputs:
|
outputs:
|
||||||
processed_count: ${{ steps.process.outputs.processed_count }}
|
processed_count: ${{ steps.process.outputs.processed_count }}
|
||||||
|
has_syslog: ${{ steps.parse.outputs.has_syslog }}
|
||||||
|
sender_open_id: ${{ steps.parse.outputs.sender_open_id }}
|
||||||
|
chat_id: ${{ steps.parse.outputs.chat_id }}
|
||||||
|
sender_name: ${{ steps.parse.outputs.sender_name }}
|
||||||
|
syslog_file: ${{ steps.parse.outputs.syslog_file }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
@ -39,6 +44,41 @@ jobs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
|
# Step 0: 解析 sender 信息(在 process-syslog.js 归档之前)
|
||||||
|
- name: 📋 解析 SYSLOG sender 信息
|
||||||
|
id: parse
|
||||||
|
run: |
|
||||||
|
LATEST=$(ls -t syslog-inbox/*.json 2>/dev/null | grep -v '.gitkeep' | head -1 || true)
|
||||||
|
if [ -z "$LATEST" ]; then
|
||||||
|
echo "📭 syslog-inbox/ 无待处理文件"
|
||||||
|
echo "has_syslog=false" >> "$GITHUB_OUTPUT"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "has_syslog=true" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "syslog_file=$LATEST" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
SENDER_OPEN_ID=$(jq -r '.sender_open_id // empty' "$LATEST" 2>/dev/null || true)
|
||||||
|
CHAT_ID=$(jq -r '.chat_id // empty' "$LATEST" 2>/dev/null || true)
|
||||||
|
SENDER_NAME=$(jq -r '.sender_name // empty' "$LATEST" 2>/dev/null || true)
|
||||||
|
|
||||||
|
echo "sender_open_id=$SENDER_OPEN_ID" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "chat_id=$CHAT_ID" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "sender_name=$SENDER_NAME" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
echo "📋 SYSLOG 文件: $LATEST"
|
||||||
|
echo " sender_open_id: ${SENDER_OPEN_ID:-无}"
|
||||||
|
echo " chat_id: ${CHAT_ID:-无}"
|
||||||
|
echo " sender_name: ${SENDER_NAME:-无}"
|
||||||
|
|
||||||
|
# Step 1: 写入 Notion SYSLOG 收件箱
|
||||||
|
- name: 📝 写入 Notion SYSLOG 收件箱
|
||||||
|
if: steps.parse.outputs.has_syslog == 'true'
|
||||||
|
env:
|
||||||
|
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
|
||||||
|
NOTION_DB_SYSLOG: ${{ secrets.NOTION_DB_SYSLOG }}
|
||||||
|
run: node scripts/write-notion-syslog.js "${{ steps.parse.outputs.syslog_file }}"
|
||||||
|
|
||||||
|
# Step 2: 处理 syslog-inbox 条目(归档到 syslog-processed/)
|
||||||
- name: 📥 处理 syslog-inbox 条目
|
- name: 📥 处理 syslog-inbox 条目
|
||||||
id: process
|
id: process
|
||||||
run: |
|
run: |
|
||||||
|
|
@ -54,6 +94,115 @@ jobs:
|
||||||
git diff --staged --quiet || git commit -m "📥 syslog: 处理 inbox 条目,更新大脑记忆 [skip ci]"
|
git diff --staged --quiet || git commit -m "📥 syslog: 处理 inbox 条目,更新大脑记忆 [skip ci]"
|
||||||
git push
|
git push
|
||||||
|
|
||||||
|
# ── Pipeline B:Claude API 唤醒人格体 ────────────────────
|
||||||
|
pipeline-b-persona:
|
||||||
|
name: 🧠 Pipeline B · 人格体唤醒
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: pipeline-a-syslog
|
||||||
|
if: needs.pipeline-a-syslog.outputs.has_syslog == 'true'
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
outputs:
|
||||||
|
broadcast_title: ${{ steps.persona.outputs.broadcast_title }}
|
||||||
|
broadcast_url: ${{ steps.persona.outputs.broadcast_url }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
ref: main
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: 🧠 唤醒人格体处理 SYSLOG
|
||||||
|
id: persona
|
||||||
|
env:
|
||||||
|
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
|
||||||
|
LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
|
||||||
|
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
|
||||||
|
CORE_BRAIN_PAGE_ID: ${{ secrets.CORE_BRAIN_PAGE_ID }}
|
||||||
|
PORTRAIT_DB_ID: ${{ secrets.PORTRAIT_DB_ID }}
|
||||||
|
FINGERPRINT_DB_ID: ${{ secrets.FINGERPRINT_DB_ID }}
|
||||||
|
SUBMIT_TYPE: syslog
|
||||||
|
SUBMIT_CONTENT: ${{ needs.pipeline-a-syslog.outputs.syslog_file }}
|
||||||
|
AUTHOR: ${{ needs.pipeline-a-syslog.outputs.sender_name }}
|
||||||
|
run: |
|
||||||
|
if [ -z "$LLM_API_KEY" ] || [ -z "$LLM_BASE_URL" ]; then
|
||||||
|
echo "⚠️ LLM_API_KEY 或 LLM_BASE_URL 未配置,跳过人格体唤醒"
|
||||||
|
echo "broadcast_title=" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "broadcast_url=" >> "$GITHUB_OUTPUT"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
node scripts/wake-persona.js || true
|
||||||
|
|
||||||
|
# 读取人格体输出的广播信息(如果有)
|
||||||
|
if [ -f /tmp/broadcast-result.json ]; then
|
||||||
|
BROADCAST_TITLE=$(jq -r '.title // empty' /tmp/broadcast-result.json)
|
||||||
|
BROADCAST_URL=$(jq -r '.notion_url // empty' /tmp/broadcast-result.json)
|
||||||
|
echo "broadcast_title=$BROADCAST_TITLE" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "broadcast_url=$BROADCAST_URL" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "✅ 人格体处理完成,广播标题: $BROADCAST_TITLE"
|
||||||
|
else
|
||||||
|
echo "⚠️ /tmp/broadcast-result.json 未生成(人格体可能未产出广播)"
|
||||||
|
echo "broadcast_title=" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "broadcast_url=" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Pipeline C:推送广播到飞书 ───────────────────────────
|
||||||
|
pipeline-c-push:
|
||||||
|
name: 📢 Pipeline C · 飞书广播推送
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [pipeline-a-syslog, pipeline-b-persona]
|
||||||
|
if: >-
|
||||||
|
needs.pipeline-b-persona.outputs.broadcast_title != '' &&
|
||||||
|
(needs.pipeline-a-syslog.outputs.chat_id != '' || needs.pipeline-a-syslog.outputs.sender_open_id != '')
|
||||||
|
steps:
|
||||||
|
- name: 📢 推送广播到飞书
|
||||||
|
env:
|
||||||
|
PUSH_URL: https://guanghulab.com/webhook/push-broadcast
|
||||||
|
PUSH_TOKEN: ${{ secrets.PUSH_BROADCAST_TOKEN }}
|
||||||
|
CHAT_ID: ${{ needs.pipeline-a-syslog.outputs.chat_id }}
|
||||||
|
SENDER_OPEN_ID: ${{ needs.pipeline-a-syslog.outputs.sender_open_id }}
|
||||||
|
BROADCAST_TITLE: ${{ needs.pipeline-b-persona.outputs.broadcast_title }}
|
||||||
|
BROADCAST_URL: ${{ needs.pipeline-b-persona.outputs.broadcast_url }}
|
||||||
|
run: |
|
||||||
|
if [ -z "$PUSH_TOKEN" ]; then
|
||||||
|
echo "⚠️ PUSH_BROADCAST_TOKEN 未配置,跳过飞书推送"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "📢 推送广播到飞书..."
|
||||||
|
echo " 标题: $BROADCAST_TITLE"
|
||||||
|
echo " 目标: ${CHAT_ID:-$SENDER_OPEN_ID}"
|
||||||
|
|
||||||
|
HTTP_CODE=$(curl -s -o /tmp/push-response.txt -w "%{http_code}" \
|
||||||
|
-X POST "$PUSH_URL" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "x-push-token: $PUSH_TOKEN" \
|
||||||
|
-d "$(jq -n \
|
||||||
|
--arg chat_id "$CHAT_ID" \
|
||||||
|
--arg sender_open_id "$SENDER_OPEN_ID" \
|
||||||
|
--arg broadcast_title "$BROADCAST_TITLE" \
|
||||||
|
--arg broadcast_content "人格体已验收,请查看完整广播。" \
|
||||||
|
--arg broadcast_url "$BROADCAST_URL" \
|
||||||
|
'{chat_id: $chat_id, sender_open_id: $sender_open_id, broadcast_title: $broadcast_title, broadcast_content: $broadcast_content, broadcast_url: $broadcast_url}')")
|
||||||
|
|
||||||
|
echo " HTTP 响应: $HTTP_CODE"
|
||||||
|
cat /tmp/push-response.txt 2>/dev/null || true
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
||||||
|
echo "✅ 广播已推送到飞书"
|
||||||
|
else
|
||||||
|
echo "❌ 推送失败 (HTTP $HTTP_CODE),可通过 push-broadcast-feishu workflow 手动补推"
|
||||||
|
fi
|
||||||
|
|
||||||
# ── Pipeline D:Issues 巡检 ─────────────────────────────
|
# ── Pipeline D:Issues 巡检 ─────────────────────────────
|
||||||
pipeline-d-issues:
|
pipeline-d-issues:
|
||||||
name: 🔍 Pipeline D · Issues 巡检
|
name: 🔍 Pipeline D · Issues 巡检
|
||||||
|
|
|
||||||
|
|
@ -885,6 +885,35 @@ async function main() {
|
||||||
fs.appendFileSync(outputFile, 'result<<' + delimiter + '\n' + result + '\n' + delimiter + '\n');
|
fs.appendFileSync(outputFile, 'result<<' + delimiter + '\n' + result + '\n' + delimiter + '\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 输出广播结果到 /tmp/broadcast-result.json(供飞书推送步骤使用)
|
||||||
|
try {
|
||||||
|
const broadcastResult = {
|
||||||
|
title: '',
|
||||||
|
summary: '',
|
||||||
|
notion_url: '',
|
||||||
|
dev_id: AUTHOR,
|
||||||
|
raw_length: result.length,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 尝试从人格体输出中提取广播标题(BC-xxx 格式)
|
||||||
|
const bcMatch = result.match(/BC-[\w]+-\d+-[\w]+/);
|
||||||
|
if (bcMatch) {
|
||||||
|
broadcastResult.title = bcMatch[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试提取摘要(取前100字符)
|
||||||
|
const summaryLines = result.split('\n').filter(function (l) { return l.trim().length > 0; });
|
||||||
|
if (summaryLines.length > 0) {
|
||||||
|
broadcastResult.summary = summaryLines[0].slice(0, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync('/tmp/broadcast-result.json', JSON.stringify(broadcastResult, null, 2));
|
||||||
|
console.log(' 📄 broadcast-result.json 已写入 /tmp/');
|
||||||
|
} catch (e) {
|
||||||
|
console.log(' ⚠️ broadcast-result.json 写入失败: ' + e.message);
|
||||||
|
}
|
||||||
|
|
||||||
// 同时输出到 stdout 供调试
|
// 同时输出到 stdout 供调试
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('═══════════════════════════════════════════');
|
console.log('═══════════════════════════════════════════');
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,195 @@
|
||||||
|
// scripts/write-notion-syslog.js
|
||||||
|
// 铸渊 · 飞书 SYSLOG → Notion 收件箱写入
|
||||||
|
//
|
||||||
|
// 读取 syslog-inbox/ 下的 JSON 文件,写入 Notion SYSLOG 收件箱数据库。
|
||||||
|
// 用法: node scripts/write-notion-syslog.js <syslog-file-path>
|
||||||
|
//
|
||||||
|
// 环境变量:
|
||||||
|
// NOTION_TOKEN Notion API token(必须)
|
||||||
|
// NOTION_DB_SYSLOG SYSLOG 收件箱数据库 ID(必须)
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const https = require('https');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════
|
||||||
|
// 常量
|
||||||
|
// ══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
const NOTION_VERSION = '2022-06-28';
|
||||||
|
const NOTION_API_HOSTNAME = 'api.notion.com';
|
||||||
|
const NOTION_RICH_TEXT_MAX = 2000;
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════
|
||||||
|
// HTTP 请求工具(复用 receive-syslog.js 模式)
|
||||||
|
// ══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
function notionPost(endpoint, body, token) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const payload = JSON.stringify(body);
|
||||||
|
const opts = {
|
||||||
|
hostname: NOTION_API_HOSTNAME,
|
||||||
|
port: 443,
|
||||||
|
path: endpoint,
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + token,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Notion-Version': NOTION_VERSION,
|
||||||
|
'Content-Length': Buffer.byteLength(payload),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const req = https.request(opts, res => {
|
||||||
|
let data = '';
|
||||||
|
res.on('data', chunk => data += chunk);
|
||||||
|
res.on('end', () => {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(data);
|
||||||
|
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||||
|
resolve(parsed);
|
||||||
|
} else {
|
||||||
|
reject(new Error('Notion API ' + res.statusCode + ': ' + (parsed.message || data)));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
reject(new Error('Notion API parse error: ' + data));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
req.on('error', reject);
|
||||||
|
req.write(payload);
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════
|
||||||
|
// Notion 属性构建辅助
|
||||||
|
// ══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
function richText(content) {
|
||||||
|
const str = String(content || '');
|
||||||
|
const chunks = [];
|
||||||
|
for (let i = 0; i < str.length; i += NOTION_RICH_TEXT_MAX) {
|
||||||
|
chunks.push({ type: 'text', text: { content: str.slice(i, i + NOTION_RICH_TEXT_MAX) } });
|
||||||
|
}
|
||||||
|
return chunks.length ? chunks : [{ type: 'text', text: { content: '' } }];
|
||||||
|
}
|
||||||
|
|
||||||
|
function titleProp(text) {
|
||||||
|
return { title: [{ type: 'text', text: { content: String(text || '').slice(0, 120) } }] };
|
||||||
|
}
|
||||||
|
|
||||||
|
function richTextProp(text) {
|
||||||
|
return { rich_text: richText(text) };
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectProp(name) {
|
||||||
|
return { select: { name: String(name) } };
|
||||||
|
}
|
||||||
|
|
||||||
|
function dateProp(dateStr) {
|
||||||
|
return { date: { start: dateStr } };
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkboxProp(val) {
|
||||||
|
return { checkbox: !!val };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════
|
||||||
|
// 主流程
|
||||||
|
// ══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const filePath = process.argv[2];
|
||||||
|
const notionToken = process.env.NOTION_TOKEN;
|
||||||
|
const syslogDbId = process.env.NOTION_DB_SYSLOG;
|
||||||
|
|
||||||
|
if (!filePath) {
|
||||||
|
console.error('❌ 用法: node write-notion-syslog.js <syslog-file-path>');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!notionToken || !syslogDbId) {
|
||||||
|
console.log('⚠️ 缺少 NOTION_TOKEN 或 NOTION_DB_SYSLOG,跳过 Notion 写入');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取 SYSLOG 文件
|
||||||
|
let entry;
|
||||||
|
try {
|
||||||
|
entry = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('❌ 读取/解析文件失败: ' + e.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const filename = path.basename(filePath);
|
||||||
|
const syslogText = entry.syslog || entry.content || '';
|
||||||
|
const source = entry.source || 'unknown';
|
||||||
|
const senderName = entry.sender_name || '';
|
||||||
|
const senderOpenId = entry.sender_open_id || '';
|
||||||
|
const chatId = entry.chat_id || '';
|
||||||
|
const timestamp = entry.timestamp || new Date().toISOString();
|
||||||
|
const dateStr = timestamp.split('T')[0] || new Date().toISOString().split('T')[0];
|
||||||
|
|
||||||
|
console.log('📋 写入 Notion SYSLOG 收件箱...');
|
||||||
|
console.log(' 文件: ' + filename);
|
||||||
|
console.log(' 来源: ' + source);
|
||||||
|
console.log(' 发送者: ' + (senderName || senderOpenId || '未知'));
|
||||||
|
|
||||||
|
// 构建 Notion 页面属性
|
||||||
|
const properties = {
|
||||||
|
'标题': titleProp('飞书SYSLOG · ' + (senderName || source) + ' · ' + dateStr),
|
||||||
|
'提交日期': dateProp(dateStr),
|
||||||
|
'霜砚已读': checkboxProp(false),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 可选字段(如果 Notion 数据库有这些列)
|
||||||
|
if (source) properties['来源'] = richTextProp(source);
|
||||||
|
if (senderName) properties['发送者'] = richTextProp(senderName);
|
||||||
|
if (senderOpenId) properties['发送者ID'] = richTextProp(senderOpenId);
|
||||||
|
|
||||||
|
// 构建页面内容
|
||||||
|
const contentBlocks = [];
|
||||||
|
|
||||||
|
// SYSLOG 正文
|
||||||
|
if (syslogText) {
|
||||||
|
contentBlocks.push({
|
||||||
|
object: 'block',
|
||||||
|
type: 'paragraph',
|
||||||
|
paragraph: {
|
||||||
|
rich_text: richText(syslogText),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原始 JSON(调试用)
|
||||||
|
contentBlocks.push({
|
||||||
|
object: 'block',
|
||||||
|
type: 'code',
|
||||||
|
code: {
|
||||||
|
rich_text: richText(JSON.stringify(entry, null, 2)),
|
||||||
|
language: 'json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
parent: { database_id: syslogDbId },
|
||||||
|
properties,
|
||||||
|
children: contentBlocks,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await notionPost('/v1/pages', body, notionToken);
|
||||||
|
console.log(' ✅ Notion SYSLOG 收件箱条目已创建: ' + result.id);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(' ❌ Notion 写入失败: ' + err.message);
|
||||||
|
// 不退出,允许后续步骤继续
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(err => {
|
||||||
|
console.error('❌ write-notion-syslog 失败: ' + err.message);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue