乐于分享
好东西不私藏

Obsidian 老友记!把微信搬进插件里

Obsidian 老友记!把微信搬进插件里

《喜相逢》
蓝天白云青草地,
萱花摇曳笑盈盈。
月寒日暖添人寿,
野汀新绿鹿呦呦。

一眨眼,椂宝满月了。我也顺理成章地以家庭为重,把公众号断更了一个多月。

福宝在客厅嬉闹,椂宝在卧室啼哭。两股声音交叠在一起,有时竟生出移形换位的错觉——恍惚间,仿佛有一个娃娃从卧室里爬出来,撑起手臂,朝我跑来。

我不知道他们长大后会是什么样子。他们有自由的意志、个性的喜好,还有尚不可知的拐点,足以在某一天扭转人生。

我便想起自己的公众号来。过去半年它停停续续,灵感与激情渐渐淡去,却仍是这两年半一路走来的见证。趁此契机,我也在重想:插件、Obsidian,乃至整个知识管理系统,应当走向何方。

我盯着 Obsidian 的名字出神。它陪了我太久,像一位沉默的老友——见证过笔记、脚本、插件的一路生长,却从不回话。那一刻,我希望它开口说话。

于是有了 OldBuddy

NoteChain 1.4.1 把它带进插件:在 Obsidian 里用微信风格界面聊天,文字、语音、图片、文件都能发;历史可写进日记,回复逻辑由 Templater 脚本定制,外部自动化也能通过 HTTP 推送消息。它不是要取代 Obsidian,而是给这位「老朋友」一张嘴、一扇窗。


为什么需要这样一个聊天窗口?

椂宝满月这阵子,我写字少了、更新慢了,却更清楚自己缺什么:不是更复杂的面板,而是一个会回应的入口。

Obsidian 擅长写笔记、链知识、跑脚本,但不擅长「像聊天一样随手说一句话」。你可能也熟悉这些时刻:

  • 走路时想记一句灵感,却不想打开整库找笔记、切面板、找模板
  • 脚本或 AI 跑完了,结果只出现在控制台或某张笔记里,没有「有人回你一句」的反馈
  • WebView LLM、MCP、定时任务各自为政,缺少一个统一的入口:发一条、收一条、历史可查
  • 想把对话写进当天日记,又不想手动复制粘贴

OldBuddy 要补的就是这块:一个轻量、熟悉、可编程的聊天面——像微信那样顺手,像日记那样留痕,像 Templater 那样听你的话。

它和「在 Obsidian 里嵌一个 ChatGPT 网页」不一样:

  • 界面像微信
    :手机浏览器打开就能用,语音、图片、位置都顺手
  • 逻辑在你 vault 里
    :回复、存盘、@ 列表、头像全是 Templater 脚本,改脚本即改行为
  • 和 Note-Chain 一体
    :HTTP 服务、ea.web.post、日记 infield、Dataview 查询,和现有工作流接得上
  • 对外可接入:自动化脚本 push_message 推通知,你在聊天里立刻看到

换句话说:OldBuddy 不是又一个聊天机器人,而是 Obsidian 的「对话层」——
你说话,脚本和外部系统回应,重要内容落进日记;平时当快捷输入,需要时再深度写笔记。


快速开始

1. 开启 HTTP 服务

设置 → Note Chain → 启用 HTTP Server(默认 127.0.0.1:3000)。

2. 打开聊天页

浏览器访问:

http://127.0.0.1:3000/oldbuddy

端口以设置里的 httpServerPort 为准。手机同一局域网可访问电脑 IP,例如 http://192.168.1.10:3000/oldbuddy

3. 准备 Templater 脚本(可选但推荐)

将 notes/OldBuddy/ 下七个脚本复制到 vault 的 Templater 脚本目录basename 必须一致(如 nochain_oldbuddy_reply.md)。脚本不存在时,OldBuddy 仍可用内置默认行为。

下文逐一说明;更完整的示例见 [[OldBuddy]] 系列笔记。


Templater 脚本详解

OldBuddy 的可配置能力几乎都在这七个脚本里。统一约定:
  • 脚本块用 ```js tpl 或 <%* … -%>
  • 列表类脚本返回 [{ label, text }, …] 或 JSON 字符串
  • 插件通过 extra.oldbuddy(Templater 里也可用 tp.config.extra.oldbuddy)传入上下文

nochain_oldbuddy_targets — 聊天对象

作用: 配置顶部「聊天对象」下拉框——和谁聊、切换后过滤历史与快捷命令。

返回格式:

return [    { label: '本地', text: 'local' },    { label: 'Obsidian', text: 'obsidian' },];
  • label
    :界面显示名
  • text:内部 id(写入消息的 target 字段)

默认: 脚本不存在时仅 [{ label: 'local', text: 'local' }]


nochain_oldbuddy_quick_commands — 快捷命令 ⚡

作用: 状态栏 ⚡ 按钮下的快捷短语,一点即发送(带 quick_cmd_id 传给 reply)。

返回格式(按 target 分组):

return {local: [        { label: '你是谁', text: '你是谁' },    ],obsidian: [        { label: 'Who u are', text: 'Who u are' },    ],'*': [  // 通用兜底        { label: '通用命令', text: '通用命令' },    ],};

默认: 脚本不存在时,local 下仅「你是谁」。


nochain_oldbuddy_reply — 自动回复

作用: 决定「老友」如何回应——纯文本、Markdown 卡片,或通过 push_message 自行推送多条消息。

传入 extra.oldbuddy

字段
说明
message
刚收到的用户消息
history
最近约 30 条上下文
target
当前聊天对象 id
quick_cmd_id
快捷命令 id(若有)
三种返回方式:
返回值
行为
字符串
作为老友文本回复(card: true 时可渲染 Markdown 卡片)
true不再
自动推内置回复——脚本内已用 push_message 推过
空 / 失败
默认「嗯,我听到了:…」
示例(纯文本):
const ob = tp.config?.extra?.oldbuddy || extra?.oldbuddy;return`收到(${ob?.target}):${ob?.message?.content}`;

示例(push_message + return true):

//javascriptconst ob = tp.config?.extra?.oldbuddy || extra?.oldbuddy;const s = app.plugins.plugins['note-chain'].settings.notechain;const base = `http://${s.httpServerHost}:${s.httpServerPort}`;await ea.web.post(`${base}/oldbuddy/push_message`, {content'## 分析结果\n\n……',sender'buddy',target: ob?.target || 'local',cardtrue,});returntrue;

nochain_oldbuddy_save — 消息写入日记

作用: 把聊天记录写入 vault——默认方案是当天日记YYYY-MM-DD.md 的 callout 下,用 Dataview infield 一行一条(与瞬时笔记同类写法)。

传入 extra.oldbuddy

字段
说明
action'save'
message
当前这条消息
messages
内存中全部消息
data_dir
 / messages_file
本地 JSON 路径

单行示例:

//markdown- (id:: xxx) (sender:: user) (target:: local) (type:: text) (timestamp:: …) (content:: 你好) (s::ob)
关键约定:return true

日记写入成功后 return true → 该条不再写入messages.json,长期历史交给 nochain_oldbuddy_query 从日记读。未返回 true 则 JSON 与日记双份保存。


nochain_oldbuddy_query — 从日记读历史

作用: 扫描 vault 里 YYYY-MM-DD 日记中带 (s::ob) 的列表行,解析为消息数组,与本地 messages.json按 id 合并后分页返回。

传入 extra.oldbuddy

字段
说明
action'query'
limit
 / before / target
分页与过滤
messages
本地 JSON 缓存(供脚本扩展)
返回:OldBuddyMessage[] 或 JSON.stringify([...])。与 save 成对使用:save 写日志、query 读日志,即可让 Obsidian 日记成为唯一真相来源

nochain_oldbuddy_reference — 输入 @ 引用

作用: 微信式 @ 浮层——选人/选笔记/选角色,选中后插入 @显示名 

返回格式:

return [    { label: '老友', text: 'buddy' },    { label: 'Obsidian', text: 'obsidian' },];
  • label
    :浮层显示、插入到输入框的文字
  • text:可选副标题或实际 id(reply 里可解析 message.content

默认: 脚本不存在或返回空数组 → 不显示浮层。


nochain_oldbuddy_avatar — 头像与昵称

作用: 为每条消息配置发送者昵称与头像——用户气泡在右、老友在左,类似微信。

返回格式:

//javascriptreturnJSON.stringify({user: ['我''OldBuddy/avatars/user.png'],user_script: ['脚本''OldBuddy/avatars/script.png'],buddy: ['老友''OldBuddy/avatars/buddy.png'],});
  • key:与消息的 sender 对应;user_* 可单独配置,否则回退到 user
  • 值:[昵称, 头像路径]
  • 头像路径:vault 相对路径(经 /oldbuddy/api/vault_asset?path= 读取)、http(s) URL,或 /oldbuddy/uploads/...

默认: 脚本不存在 → 无头像,仅显示昵称首字占位。


脚本协作关系(一图流)

//texttargets ──→ 选和谁聊    ├── quick_commands / reference / avatar(按 target 变化)    ├── 用户发消息    │       ├── save ──→ 日记 (s::ob)     returntrue → 跳过 JSON    │       └── reply ──→ 文本 或 push_message + returntrue    └── query ──→ 读日记 + 合并 JSON ──→ 聊天历史

聊天界面

  • 文字
    :输入框发送;桌面 Enter 发送,Shift+Enter 换行
  • 语音 / 图片 / 文件
    :底栏 📁 菜单
  • 位置
    :可读取 GPS 写入输入框
  • @ 引用
    :由 nochain_oldbuddy_reference 提供浮层
  • 实时
    :WebSocket /oldbuddy/ws 推送新消息
  • 历史
    :上滑加载;nochain_oldbuddy_query 与本地 JSON 合并
  • 筛选:状态栏可「仅当前对象」「隐藏旧记录」

消息布局:用户user / user_*)绿色气泡在右,老友在左;头像由 nochain_oldbuddy_avatar 配置。


消息存哪里?

位置
由谁控制
会话缓存
oldbuddy-data/messages.json
插件默认;save 返回 true 时跳过
长期存档
日记 infield (s::ob)
nochain_oldbuddy_save
 / nochain_oldbuddy_query

两层可叠加,也可只用其一。推荐:save 写日记并 return true,query 读日记


第三方推送:/oldbuddy/push_message

POST JSON 或表单,向聊天窗口注入消息(含 WebSocket 实时显示)。

//javascriptconst s = app.plugins.plugins['note-chain'].settings.notechain;const res = await ea.web.post(`http://${s.httpServerHost}:${s.httpServerPort}/oldbuddy/push_message`,    {content'来自脚本的通知',sender'buddy',      // user / user_* / buddy 等target'local',card: true,skip_reply: true,     // sender 为 user 时可禁止自动 reply    });
字段
说明
content
必填,文本或媒体 URL
sender
默认 buddyuser / user_* 视为用户侧(右侧气泡)
target
聊天对象,默认 local
typetext
 / image / audio / file
card
Markdown 卡片样式
skip_replytrue
 时不触发 reply

sender 约定

sender
气泡位置
自动 reply
user
user_*
(如 user_script
buddy
 等
#NoteChain #Obsidian