SiPush:一个零构建的 Obsidian → 思源笔记同步插件

无缝衔接双笔记生态,纯 JS 编写,即放即用

缘起

用 Obsidian 写笔记,用思源笔记做知识库——这是很多人的双笔记 workflow。但两套系统之间缺一座桥:写 Obsidian 里,想同步到思源怎么办?

手动复制粘贴?太慢。
用第三方同步工具?太重。
直接放弃一个?不舍得。

所以我做了一个轻量级的 Obsidian 插件—— SiPush,一键把 Obsidian 笔记推送到思源,而且重复推送不会产生重复文档

功能一览

功能说明
📄 推送当前笔记整篇推送,自动识别首次/更新
🔖 推送选中内容只推送选中的文字片段
📝 追加到已有文档搜思源文档 → 内容追加到末尾
🔌 连接测试一键测试连通性
🔄 原地更新再次推送同一笔记,直接更新内容,不新建文档

安装:30 秒

插件纯 JS 编写,零构建,只需 3 个文件:

mkdir -p 你的Vault/.obsidian/plugins/obsidian-si-push/
# 复制 manifest.json、main.js、styles.css 到该目录

然后 Obsidian 设置 → 第三方插件 → 启用 SiPush - 思源推送

配置

打开设置页,填 3 个参数:

参数说明我的值
思源服务器地址Kernel API 地址http://192.168.2.250:6806
API Token思源设置→关于token内容
默认笔记本点刷新按钮下拉选XXX

技术实现

关键挑战:SiYuan 没有"更新文档"的 API

开发过程中遇到的最大的坑是:思源笔记 v3.5 的 API 文档里找不到一个「更新文档内容」的接口

试过的方案:

方案结果
removeDoc​ + createDocWithMd❌ removeDoc 是软删除,同路径产生多个文档
deleteBlock​ + appendBlock❌ 删除后数据库记录还在,追加内容错乱
updateBlock​ + content 参数❌ 返回成功但内容没变
改名归档旧文档 + 创建新文档⚠️ 可行但有残留
自定义属性标记 + updateBlock(dataType)完美解决

最终方案:

思源的 /api/block/updateBlock 接口接受三个参数:

{
  "id": "文档ID",
  "dataType": "markdown",
  "data": "# 新内容"
}

注意参数名!data​ 和 dataType​,不是 content。这个细微的差别让我折腾了一整个晚上。

文档定位:三重保险

为了确保每次推送都能找到正确的思源文档,实现了三层定位:

  1. Obsidian frontmatter — 第一次推送时在笔记头部写入 custom-si-push-id​(如 si1y7z8a3b2c1d),即使文件改名/移动也不丢失
  2. 思源 attributes 表 — 在思源文档上通过 /api/attr/setBlockAttrs​ 写入相同的 custom-si-push-id,可通过 JOIN 查询快速定位
  3. 插件 data.json — 本地缓存映射表作为辅助

第一层保证了跨重命名的稳定性,第二层保证了跨机器/跨插件重装的可靠性。

ID 生成

格式: si + 36进制时间戳 + 6位随机数
示例: si1y7z8a3b2c1d
  • 36进制时间戳(0-9a-z):比十进制短,比 base64 可读
  • 6位随机数:防并发冲突
  • si 前缀:一眼可识别来源

使用体验

第一次推送某篇笔记时,Obsidian 会自动在 frontmatter 写入一个 custom-si-push-id

---
custom-si-push-id: si1y7z8a3b2c1d
tags:
  - obsidian
---

# 我的笔记

之后再次推送,插件流程是:

  1. 从 frontmatter 读取 custom-si-push-id
  2. 在思源的 attributes 表中搜索这个 ID
  3. 找到 → updateBlock 原地更新内容
  4. 没找到 → createDocWithMd 新建 + 写入 ID

效果:思源里始终只有一篇文档,内容永远是最新版。

遇到的坑与心得

1. SiYuan API 的"假成功"

很多接口返回 code=0​ 但实际上什么都没做。比如 renameDoc​、updateBlock(content)​ 都接受了请求却没生效。直到查看一个开源项目 siyuan-skill 的源码,才发现 updateBlock​ 的正确参数叫 data​ 不是 content

教训:不要相信文档,要相信源码。

2. SiYuan 的软删除

removeDoc 返回成功,但文档只是被"移入回收站",数据库记录和自定义属性都还在。如果按路径搜文档,会把回收站里的也搜出来,导致混乱。

教训:对于不能真正删除的系统,不如原地更新。

3. Obsidian 的 frontmatter API

Obsidian 提供了 app.fileManager.processFrontMatter(file, callback)​ 方法,可以安全地读写 frontmatter。这是操作文档元数据的推荐方式,比手动解析 --- 分隔符靠谱得多。

总结

SiPush 是一个小而美的插件,解决了一个具体的痛点:在 Obsidian 和思源之间建立单向的、可重复的同步通道

它不追求实时同步(那是 Syncthing 的事),也不做双向同步(那是 Remotely Save 的事),只专注做好一件事:把 Obsidian 的内容推送到思源,并且推多少次都不会重复,注意图片需要上传图床要不在思源笔记里无法显示图片

如果你也在用双笔记工作流,可以试试看。

obsidian-si-push-v1.0.0.zip