SiPush 插件:Obsidian 到思源笔记的无缝推送
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。这个细微的差别让我折腾了一整个晚上。
文档定位:三重保险
为了确保每次推送都能找到正确的思源文档,实现了三层定位:
- Obsidian frontmatter — 第一次推送时在笔记头部写入
custom-si-push-id(如si1y7z8a3b2c1d),即使文件改名/移动也不丢失 - 思源 attributes 表 — 在思源文档上通过
/api/attr/setBlockAttrs 写入相同的custom-si-push-id,可通过 JOIN 查询快速定位 - 插件 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
---
# 我的笔记之后再次推送,插件流程是:
- 从 frontmatter 读取
custom-si-push-id - 在思源的
attributes表中搜索这个 ID - 找到 →
updateBlock原地更新内容 - 没找到 →
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 的内容推送到思源,并且推多少次都不会重复,注意图片需要上传图床要不在思源笔记里无法显示图片。
如果你也在用双笔记工作流,可以试试看。