article_image

本文向你展示了如何利用 Drafts 的自动化能力,无缝地在多个平台间同步 Obsidian 笔记,确保你随时随地都可以访问到最新的信息。

Alt text
0-1 效果展示

按照沨沄极客的这两篇文章《Obsidian 的 PC 端同步方案,无代码搞定 Git 同步》《Obsidian 的手机端同步方案,iOS + Git + Shortcuts 实现自动同步》,完成了信息管理“基建”施工。

在通车之前,在 iOS 上还需要一个能够快速记录文本到 Obsidian 库的提速方案。Drafts 自然是首选。

Drafts 有着丰富的自动化支持,向 Obsidian 添加笔记不是什么难事。 有以下几种方案:

  1. 使用 Obsidian URL Schemes1
  2. 使用 Drafts Folder Bookmarks2
  3. 使用 Shortcuts 读取文本然后保存文件

但这 3 个方案都不够快也不够好。 URL 和 Shortcuts 需要跳转,Folder Bookmarks 虽快人一步不需要跳转,却和其他方案一样将文件保存在本地,在没上传(push)之前其他设备不能访问保存的笔记,在同步上存在割裂感。

但这难不倒 Drafts。 还有一种又快、又全的方案:在 Drafts 中使用 Github API,不离开 Drafts 又能全平台都有这份笔记的档案。

广受欢迎的 Drafts to Todoist/Twitter 也是取道 API,实现无缝发送文本。深入学习其中一种,其他的便可如法炮制,一通百通。

第一步:帐号凭证

首先,在 Github 设置中生成一个用于访问 Github API 的 token。 点击 Generate new token——Generate new token(classic),Note 和 Expiration 看心情填,勾选上 repo 权限,其他空着,翻到页面底部,点击 Generate token。

Alt text
1-1 生成 Github token

然后,下载这个 Drafts Action,第一次运行会提示填入 token 和 Obsidian 仓库地址。 如果你的仓库地址是 https://github.com/erpwibw/Obsidian-Library,那么要填入的 Repo 是 erpwibw/Obsidian-Library

完成这两个配置,即可将笔记发送到 Github。 如果发送成功,会把这条 draft 移动到 Archive 并创建一个新的 draft,然后提示“成功发送并归档”; 否则,提示发送失败。


接下来是从零开始的 How 部分。 打开 Drafts,点击右上角侧边栏按钮——点击 + 号——New Action——STEPS——点击 + 号——下滑点击 Script,就可以开始编写访问 Github API 的脚本。

要访问 API ,帐号凭证是必须项,先来看一段关于 Drafts 中帐号凭证的介绍

帐号凭证用于要求用户提供用户名、密码而无需将用户名和密码硬编码到代码中 第一次运行脚本时会提示用户输入其凭证,然后 Drafts 会存储这些凭证以供以后使用 通过标识符在脚本中使用凭证

下面是在 Drafts 创建帐号凭证的代码

// 创建一个新的凭据
// Github 是这个凭据的标识符,在以后的脚本中通过标识符来使用
let credential = Credential.create("Github", "输入 Github Token 和 Repo");

// 为 Github 凭据添加一个 token 字段
// token 是脚本中用于检索的名字
// Token 是提示用户输入凭据时显示的标签
credential.addTextField("token", "Token");
// 为 Github 凭据添加一个 repo 字段
credential.addTextField("repo", "Repo");

// 显示一个对话框,要求用户输入前面定义的字段
credential.authorize();
Alt text
1-2 管理 Credentials

创建过的凭据可以通过右下角设置齿轮——Credentials——Forget 来删除。

第二步:处理文本

下面是将标题行作为笔记文件名,余下部分作为笔记正文的代码

// 从当前 Drafts 文档中获取全部文本内容
// draft 是一个全局变量,代表当前的文档
// content 是 draft 的一个属性,代表文档的全部文本内容
let content = draft.content;
// 查找 content 中第一个换行符("\n")的位置
// .indexOf() 用于查找字符串中的文本,参数是要查找的文本
let firstNewline = content.indexOf("\n");
// 提取 content 中从开头到第一个换行符之前的文本
// .substring() 用于提取字符串中的一部分,参数是起始位置和结束位置
let titleLine = content.substring(0, firstNewline);
// 将 titleLine 作为文件名,并添加.md作为文件后缀
// 也就是,将标题行作为文件名
// `${titleLine}.md` 是一种字符串模板语法,可以将变量的值插入到字符串中
let filename = `${titleLine}.md`;

// 提取 content 中从第一个换行符后面的文本
// .substring() 如果省略结束位置,则提取到字符串的末尾
content = content.substring(firstNewline + 1);

draft.contentindexOf()substring()${},是 Drafts 文本处理中的基操,像 Shortcuts 一样搭积木就行。

第三步:请求 API

这部分代码行数有些多,但不难。都是些车轱辘套话:📞你好,我是王二,编号 1943,来一份鸡排饭。 其实这部分代码我是直接问 GPT 老师要的,现在谁还去翻文档啊。GPT 老师的代码一把成功,节省了巨多时间。 但授人以鱼不如授人以渔,真正解决问题的能力需要对原理有深入的理解。掌握基础和原理,在遇到问题时,有更多的方法和角度去思考和解决,也不会因为一个工具出现问题而束手无策。 当然,我并非认为利用 GPT 老师是错误的,拿着答案找答案,可以帮助我们更快的获取有效线索。

Github API 官方文档找到与 repo 即 Repositories 仓库相关的部分。我们想想,在仓库中干点啥肯定与 create 或 upload 有关,在页面中搜索一下,有一条 “Create or update file contents” 看起来像是可能有用的。

Alt text
3-1 Github API 文档

根据左边文本加粗情况来看,需要 3 个部分:Headers、Path parameters、Body parameters。 依次来看,Headers 有一个 accept 参数,但没有 Required 标记,也就是说是可选项。那不用管了,看看其他 Required 的是啥。 Path parameters 需要 owner、repo Body parameters 需要 message、content(需要用 Base64 编码) 知道了如何请求,继续往下看请求的响应部分。HTTP response status codes 部分表示如果成功创建会返回状态码 201,也就是说收到这个码就可以放心的关掉 Drafts 了。

再看一下右边的使用 cURL 发送请求的示例。cURL 是一个命令行工具,用于获取或发送数据,是 Client URL 的缩写。从这个示例中,可以了解应该如何使用参数。

  • -L 是自动重定向
  • -X 是 HTTP 请求的方法
  • -H 用来构建请求的头部,每个 -H 都是请求头的一部分
  • https://... 请求的 URL
  • -d 指定了要发送的数据

示例一般都是细而全的,我们不需要那么多细节,根据前面分析的 Required 信息,可以将这个 cURL 精简如下:

Alt text
3-2 精简 cURL 命令

官方文档中 Headers 部分没说要提供 Authorization,这是对新手不太有好的地方。文档通常列出的是针对特定 API 端点的参数。对于 Github API 的大多数端点,Authorization 头是必须的。根据实际情况我们也可以推测需要身份验证,不然可以随便往别人仓库里发文件了。

知道要发啥了,接下来是怎么发的问题。 JavaScript 不能直接发起网络请求,需要宿主环境提供网络请求方式。例如在 Drafts 和 Scriptable 脚本中发起网络请求的方式并不一样。当然这里面也有 app 开发者自已的考量,提供符合自己 app 风格的方法可以降低学习曲线,使得创建脚本更为简单。

Drafts 官方指南中,找到 Actions——Scripting

Alt text
3-3 Drafts Guide

进入Drafts Script Reference ,继续阅读文档之旅。其实这也是在调用 API,只不过以本地的方式。 浏览一下侧面,这个 HTTP 看起来可能是有用的。 我之前从没看过这个文档,在浏览的时候发现 Drafts Script 居然内置了许多知名服务的直接支持:Airtable、Dropbox、Gmail、MicrosoftToDo、Notion、OpenAI、Things、Todoist 等等。

Alt text
3-4 Drafts Script Reference HTTP

书归正传,这个 HTTP 文档也提供了示例。 统共分 3 步:.create() 创建 HTTP 对象、.request(...) 请求链接、.sucess 请求是否成功。 .request(...) 部分有些复杂,接着往下看文档,request 应该怎么写。

Alt text
3-5 Drafts Script Reference HTTP request

比对着 request 文档,根据之前精简后的 Github API cURL 请求命令,一起连连看。

  • -X 对应 method
  • -H 对应 headers
  • https://... 对应 url 用排除法,-d 应该对应 data

按照连连看的结果,写出脚本代码

// 构建请求头
// 从凭证中获取名为"token"的字段的值
token = credential.getValue("token");
let headers = {
  // 用于验证和授权的token
  Authorization: `Bearer ${token}`,
};

// 构建请求 URL
// 从凭证中获取名为"repo"的字段的值
repo = credential.getValue("repo");
// 使用 encodeURI 对文件名进行编码,以便在URL中使用
let path = encodeURI(filename);
// 使用前文提到的模板字符串构建完整 URL
// repo、path 的值会被插入到字符串中
let url = `https://api.github.com/repos/${repo}/contents/${path}`;

// 要发送给GitHub API的数据
let data = {
  // 描述这次提交的信息
  message: `Added ${filename} via Drafts app`,
  // 要上传的文件的内容,这个内容被转换为Base64编码
  content: Base64.encode(content),
};

// 使用 Drafts 的HTTP.create()方法创建并发起请求
let http = HTTP.create();
let response = http.request({
  // 要请求的 API 的URL
  url: url,
  // HTTP请求的方法,"PUT"方法是为了添加文件
  method: "PUT",
  // 前面定义的请求头
  headers: headers,
  // 前面定义的要发送的数据
  data: data,
});

搞定,进入最后一步

第四步:后续处理

发送内容后的思路是: 如果收到状态码 201,把当前 draft 移动到 Archive 并创建一个新的 draft,然后提示“成功发送并归档”; 否则,提示发送失败。

第一个要解决的问题是如何获取状态码。 继续在 Drafts Script Reference 里面翻,HTTP 下方有一个 HTTPResponse 可能是需要的,进去看看

Alt text
4-1 Drafts Script Reference HTTPResponse

有了,通过 response.statusCode 获得返回的 HTTP 状态码。

第二个要解决的问题是如何移动到 Archive、创建新的 draft 和提示消息。 实现这个可以继续在 Drafts Script Reference 文档中翻。 也可以取巧到 Drafts Actions Directory 搜索 archive 和 info 或 message,看看别人是怎么写的,直接拿来用或按图索骥到 Drafts Script Reference 文档中一睹细节。 我采用的是第二种:) 过程不再赘述,直接 show me code

// 如果响应码是 201
if (response.statusCode == 201) {
  // 将当前 draft 的 isArchived 属性设置为true
  draft.isArchived = true;
  // 调用当前 draft 的 update 方法以保存更改
  draft.update();
  // 创建新的空白 draft
  editor.new();
  // 显示一个信息提示,内容为 success ...
  app.displayInfoMessage("Success to GitHub and archived.");
} else {
  // 否则,显示一个错误提示,内容为 Failed ...
  app.displayErrorMessage("Failed to GitHub");
}

这里着重看一下 draft.isArchived、和 draft.update(),在文档中你会发现 isArchived 前面有个 P 符号,update 前面有个 M 符号。

Alt text
4-2 Drafts Script Reference Draft

P 符号表示 isArchived 是一个属性(Property),表示对象的状态或数据。例如,draft 对象有已归档、已标记、已删除等属性。 M 符号表示 update 是一个方法(Method),表示对象可以执行的行为或操作。例如,draft 对象有更新、插入、查找等方法。 了解这两点有助于更有效地写出符合自己需求的脚本。

小结

快速的记录、有效的管理、稳定的同步,Drafts、Obsidian 和 Github 这三者的强大功能和协同效应,为我们提供了一个既简单又高效的解决方案。 掌握了如何使用 API 进行信息的传输,能够轻松地与其他服务和工具进行整合,创造出更多的自定义工作流程和自动化方案。

补充

Action Log 运行操作时,Drafts 可以记录操作日志中发生的信息和错误。 点击右上角侧边栏按钮,点击日志按钮,可以打开日记记录。

Alt text
5-1 Drafts Action Log

可以看到运行 send to Github Obsidian 后,消息记录中有一行 HTTP.request Failed: 201, Unknown error。 看起来好像请求失败了,其实并不是。 这可能是 Drafts 的一个 bug,因为它根据返回码为 200 来判断请求结果,Github 返回的 201 被误判为请求失败。

在光标处插入时间戳 快乐卡片笔记男孩必备。 我制作的这个 Drafts Action 用来在光标处插入 202310311733 格式的时间戳。


  1. 详见这篇文章https://forums.getdrafts.com/t/using-obsidian-with-drafts/11221
  2. 详见这篇文章https://forums.getdrafts.com/t/using-obsidian-with-drafts/11221

author_avatar

https://twitter.com/fastLonggogo