文件预览

notion-sync.js

查看 Parcel Station Route Qr 技能包中的文件内容。

文件内容

references/notion-sync.js

// notion-sync.js — 坦坦 Notion 同步工具
// 用法: node notion-sync.js write <标题> <内容> [类型] [标签] [状态]
//        node notion-sync.js list [数量]
//        node notion-sync.js recent [天数]

const fs = require('fs');
const path = require('path');
const https = require('https');

// === 配置 ===
const configPath = path.join(process.env.USERPROFILE || '', '.config', 'notion');
const API_KEY = fs.readFileSync(path.join(configPath, 'api_key'), 'utf8').trim();
const DB_ID = fs.readFileSync(path.join(configPath, 'db_id'), 'utf8').trim();
const DS_ID = fs.readFileSync(path.join(configPath, 'ds_id'), 'utf8').trim();

// Notion API 封装
function notion(method, urlPath, body) {
  return new Promise((resolve, reject) => {
    const opts = {
      hostname: 'api.notion.com',
      path: urlPath,
      method: method,
      headers: {
        'Authorization': 'Bearer ' + API_KEY,
        'Notion-Version': '2025-09-03',
        'Content-Type': 'application/json'
      }
    };
    const req = https.request(opts, (res) => {
      let data = '';
      res.on('data', c => data += c);
      res.on('end', () => {
        try { resolve(JSON.parse(data)); }
        catch (e) { reject(new Error('Parse error: ' + data.substring(0, 200))); }
      });
    });
    req.on('error', reject);
    if (body) req.write(JSON.stringify(body));
    req.end();
  });
}

// 获取今天的日期字符串
function today() {
  const d = new Date();
  const tz = d.getTime() + (d.getTimezoneOffset() * 60000) + (8 * 3600000); // Asia/Shanghai
  const cn = new Date(tz);
  return cn.toISOString().split('T')[0];
}

// === 命令: 写入记录 ===
async function writeRecord(title, content, type, tags, status) {
  const result = await notion('POST', '/v1/pages', {
    parent: { database_id: DB_ID },
    properties: {
      'Name': { title: [{ text: { content: title || today() + ' 记录' } }] },
      '日期': { date: { start: today() } },
      '类型': { select: { name: type || '每日记录' } },
      '内容': { rich_text: [{ text: { content: content || '' } }] },
      '标签': { multi_select: (tags || '').split(',').filter(Boolean).map(t => ({ name: t.trim() })) },
      '状态': { select: { name: status || '待同步' } }
    }
  });
  if (result.object === 'error') {
    console.error('❌ 写入失败:', result.message);
    process.exit(1);
  }
  console.log('✅ 记录已写入: ' + title);
  console.log('   类型: ' + (type || '每日记录'));
  console.log('   标签: ' + (tags || '(无)'));
  console.log('   状态: ' + (status || '待同步'));
}

// === 命令: 列出记录 ===
async function listRecords(count) {
  const result = await notion('POST', '/v1/data_sources/' + DS_ID + '/query', {
    sorts: [{ property: '日期', direction: 'descending' }],
    page_size: count || 10
  });

  if (!result.results || result.results.length === 0) {
    console.log('📭 暂无记录');
    return;
  }

  console.log('\n📋 坦坦·工作日志 — 最近的 ' + result.results.length + ' 条记录\n');
  result.results.forEach((r, i) => {
    const p = r.properties;
    const title = (p.Name && p.Name.title && p.Name.title[0]) ? p.Name.title[0].text.content : '(无标题)';
    const date = (p['日期'] && p['日期'].date) ? p['日期'].date.start : '--';
    const type = (p['类型'] && p['类型'].select) ? p['类型'].select.name : '--';
    const tags = (p['标签'] && p['标签'].multi_select) ? p['标签'].multi_select.map(t => t.name).join(', ') : '';
    const status = (p['状态'] && p['状态'].select) ? p['状态'].select.name : '--';
    const content = (p['内容'] && p['内容'].rich_text && p['内容'].rich_text[0]) ? p['内容'].rich_text[0].text.content.substring(0, 80) + '...' : '';

    console.log('  #' + (i + 1) + '  ' + date + '  [' + type + ']');
    console.log('      ' + title);
    if (content) console.log('      ' + content);
    if (tags) console.log('      🏷️ ' + tags);
    console.log('      📎 ' + status);
    console.log('');
  });
}

// === 命令: 最近N天的记录(文件写入) ===
async function recentDays(days) {
  const startDate = new Date();
  startDate.setDate(startDate.getDate() - (days || 7));
  const startStr = startDate.toISOString().split('T')[0];

  const result = await notion('POST', '/v1/data_sources/' + DS_ID + '/query', {
    filter: { property: '日期', date: { on_or_after: startStr } },
    sorts: [{ property: '日期', direction: 'descending' }]
  });

  if (!result.results || result.results.length === 0) {
    console.log('📭 最近 ' + (days || 7) + ' 天没有记录');
    return;
  }

  let output = '# 坦坦同步 - 最近' + (days || 7) + '天\n\n';
  result.results.forEach(r => {
    const p = r.properties;
    const title = (p.Name && p.Name.title && p.Name.title[0]) ? p.Name.title[0].text.content : '(无标题)';
    const date = (p['日期'] && p['日期'].date) ? p['日期'].date.start : '--';
    const type = (p['类型'] && p['类型'].select) ? p['类型'].select.name : '--';
    const content = (p['内容'] && p['内容'].rich_text) ? p['内容'].rich_text.map(t => t.text.content).join('') : '';
    const tags = (p['标签'] && p['标签'].multi_select) ? p['标签'].multi_select.map(t => t.name).join(', ') : '';

    output += '## ' + date + ' | ' + type + ' | ' + title + '\n\n';
    if (content) output += content + '\n\n';
    if (tags) output += '标签: ' + tags + '\n\n';
    output += '---\n\n';
  });

  console.log(output);
}

// === MAIN ===
async function main() {
  const cmd = process.argv[2];

  if (!cmd || cmd === 'help') {
    console.log('\n📝 坦坦 Notion 同步工具\n');
    console.log('用法:');
    console.log('  写记录:   node notion-sync.js write \"标题\" \"内容\" [类型] [标签] [状态]');
    console.log('  列记录:   node notion-sync.js list [数量]');
    console.log('  近况:     node notion-sync.js recent [天数]');
    console.log('  帮助:     node notion-sync.js help\n');
    console.log('示例:');
    console.log('  write \"2026-05-27 记录\" \"今天改了UI和扫\" \"每日记录\" \"驿站系统,UI改版\" \"待同步\"');
    return;
  }

  if (cmd === 'write') {
    await writeRecord(process.argv[3], process.argv[4], process.argv[5], process.argv[6], process.argv[7]);
  } else if (cmd === 'list') {
    await listRecords(parseInt(process.argv[3]) || 10);
  } else if (cmd === 'recent') {
    await recentDays(parseInt(process.argv[3]) || 7);
  } else {
    console.log('未知命令: ' + cmd);
    process.exit(1);
  }
}

main().catch(err => { console.error('❌', err.message); process.exit(1); });