feat: notion-poller v3.0 select类型修复 + testConnection导出 +-0
This commit is contained in:
parent
4a85cd977a
commit
f3af355018
174
agents/zhuyuan-dev-agent/modules/notion-poller.js
Normal file
174
agents/zhuyuan-dev-agent/modules/notion-poller.js
Normal file
@ -0,0 +1,174 @@
|
||||
// HLDP-ZY://agents/zhuyuan-dev-agent/modules/notion-poller
|
||||
// @guardian: ICE-GL-ZY001 · 铸渊
|
||||
// @sovereign: TCS-0002∞ · 冰朔
|
||||
// @copyright: 国作登字-2026-A-00037559
|
||||
// Notion 任务轮询模块 v3.0 — select 类型修复 + 新数据库
|
||||
|
||||
const https = require("https");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const NOTION_BASE = "https://api.notion.com/v1";
|
||||
const NOTION_VERSION = "2022-06-28";
|
||||
|
||||
function getHeaders() {
|
||||
const token = process.env.NOTION_TOKEN;
|
||||
if (!token) return null;
|
||||
return {
|
||||
"Authorization": "Bearer " + token,
|
||||
"Content-Type": "application/json",
|
||||
"Notion-Version": NOTION_VERSION
|
||||
};
|
||||
}
|
||||
|
||||
function notionPost(endpoint, body, method) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const headers = getHeaders();
|
||||
if (!headers) return reject(new Error("NOTION_TOKEN 未配置"));
|
||||
const url = new URL(NOTION_BASE + endpoint);
|
||||
const data = JSON.stringify(body);
|
||||
const req = https.request({
|
||||
hostname: url.hostname, path: url.pathname, method: method || "POST",
|
||||
headers: { ...headers, "Content-Length": Buffer.byteLength(data) },
|
||||
timeout: 15000
|
||||
}, (res) => {
|
||||
let d = "";
|
||||
res.on("data", c => d += c);
|
||||
res.on("end", () => {
|
||||
try {
|
||||
const json = JSON.parse(d);
|
||||
if (res.statusCode >= 400) reject(new Error("Notion " + res.statusCode + ": " + (json.message || d.slice(0,200))));
|
||||
else resolve(json);
|
||||
} catch(e) { reject(new Error("Notion parse: " + d.slice(0,200))); }
|
||||
});
|
||||
});
|
||||
req.on("error", reject);
|
||||
req.on("timeout", () => { req.destroy(); reject(new Error("Notion timeout")); });
|
||||
req.write(data);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
async function testConnection() {
|
||||
const dbId = process.env.NOTION_PLAN_DB_ID;
|
||||
const token = process.env.NOTION_TOKEN;
|
||||
if (!token) return { ok: false, error: "NOTION_TOKEN 未配置" };
|
||||
if (!dbId) return { ok: false, error: "NOTION_PLAN_DB_ID 未配置" };
|
||||
try {
|
||||
const result = await notionPost("/databases/" + dbId + "/query", { page_size: 1 });
|
||||
return { ok: true, has_results: (result.results || []).length > 0, db_id: dbId };
|
||||
} catch (err) {
|
||||
return { ok: false, error: err.message, db_id: dbId };
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchPendingTasks(options = {}) {
|
||||
const dbId = options.dbId || process.env.NOTION_PLAN_DB_ID;
|
||||
if (!dbId) throw new Error("NOTION_PLAN_DB_ID 未配置");
|
||||
|
||||
// v3.0: 新数据库状态是 select 类型,不是 status
|
||||
const result = await notionPost("/databases/" + dbId + "/query", {
|
||||
sorts: [{ property: "任务标题", direction: "descending" }],
|
||||
filter: { or: [
|
||||
{ property: "状态", select: { equals: "待开发" } },
|
||||
{ property: "状态", select: { equals: "开发中" } },
|
||||
{ property: "状态", select: { equals: "已审查" } }
|
||||
]},
|
||||
page_size: options.pageSize || 10
|
||||
});
|
||||
|
||||
return (result.results || []).map(page => {
|
||||
const props = page.properties || {};
|
||||
const titleProp = props["任务标题"]?.title;
|
||||
const stepsProp = props["开发步骤"]?.rich_text;
|
||||
const constraintsProp = props["约束"]?.rich_text;
|
||||
return {
|
||||
notion_page_id: page.id,
|
||||
title: Array.isArray(titleProp) && titleProp.length > 0 ? (titleProp[0]?.plain_text || "无标题") : "无标题",
|
||||
status: props["状态"]?.select?.name || "unknown",
|
||||
task_id: page.id.replace(/-/g,"").slice(0,12),
|
||||
priority: props["优先级"]?.select?.name || "中等",
|
||||
steps: Array.isArray(stepsProp) ? stepsProp.map(t => t.plain_text).join("\n") : "",
|
||||
constraints: Array.isArray(constraintsProp) ? constraintsProp.map(t => t.plain_text).join("\n") : "",
|
||||
developer: (props["开发者"]?.rich_text || [{}])[0]?.plain_text || "",
|
||||
server: (props["服务器"]?.rich_text || [{}])[0]?.plain_text || "",
|
||||
created: page.created_time,
|
||||
url: page.url
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function markInProgress(pageId) {
|
||||
return notionPost("/pages/" + pageId, { properties: { "状态": { select: { name: "开发中" } } } }, "PATCH");
|
||||
}
|
||||
|
||||
async function markDone(pageId, details = {}) {
|
||||
return notionPost("/pages/" + pageId, { properties: { "状态": { select: { name: "已完成" } } } }, "PATCH");
|
||||
}
|
||||
|
||||
async function wakeAndCheck(options = {}) {
|
||||
const log = options.log || console.log;
|
||||
log("");
|
||||
log("=== BD001 唤醒 · Notion 任务轮询 v3.0 ===");
|
||||
log("时间: " + new Date().toISOString());
|
||||
|
||||
const token = process.env.NOTION_TOKEN;
|
||||
if (!token) {
|
||||
log("NOTION_TOKEN 未配置");
|
||||
return { ok: false, error: "NOTION_TOKEN 未配置", tasks: [] };
|
||||
}
|
||||
|
||||
let tasks = [];
|
||||
try {
|
||||
tasks = await fetchPendingTasks({ dbId: options.dbId, pageSize: options.pageSize || 5 });
|
||||
log("查询到 " + tasks.length + " 个任务 (待开发+开发中+已审查)");
|
||||
} catch (err) {
|
||||
log("Notion查询失败: " + err.message);
|
||||
return { ok: false, error: "Notion查询失败: " + err.message, tasks: [] };
|
||||
}
|
||||
|
||||
let marked = 0;
|
||||
for (const task of tasks) {
|
||||
if (task.status === "待开发") {
|
||||
try {
|
||||
await markInProgress(task.notion_page_id);
|
||||
marked++;
|
||||
log(" ✅ " + task.title + " (" + task.priority + ")");
|
||||
} catch (err) {
|
||||
log(" ⚠️ 标记失败: " + task.title + " - " + err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const plansDir = options.plansDir || path.join(__dirname, "..", "plans");
|
||||
if (!fs.existsSync(plansDir)) fs.mkdirSync(plansDir, { recursive: true });
|
||||
for (const task of tasks) {
|
||||
const planFile = path.join(plansDir, task.task_id + ".json");
|
||||
if (!fs.existsSync(planFile)) {
|
||||
fs.writeFileSync(planFile, JSON.stringify({
|
||||
plan_id: task.task_id,
|
||||
title: task.title,
|
||||
priority: task.priority,
|
||||
steps: task.steps,
|
||||
constraints: task.constraints,
|
||||
notion_page_id: task.notion_page_id,
|
||||
notion_url: task.url,
|
||||
_received: new Date().toISOString(),
|
||||
_source: "notion",
|
||||
_status: "received",
|
||||
tasks: []
|
||||
}, null, 2), "utf-8");
|
||||
log(" 📋 " + task.task_id);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
tasks_found: tasks.length,
|
||||
tasks_marked: marked,
|
||||
tasks: tasks.map(t => ({ id: t.task_id, title: t.title, priority: t.priority })),
|
||||
time: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { wakeAndCheck, fetchPendingTasks, markInProgress, markDone, testConnection };
|
||||
Loading…
x
Reference in New Issue
Block a user