拿Emacs对接我的cuckoo

49次阅读

共计 2907 个字符,预计需要花费 8 分钟才能阅读完成。

cuckoo 是一个我自己开发的类似待办事项的工具,运行在我本地的电脑上。它有如下两个接口:

传入一个 UNIX Epoch 时间戳创建提醒
传入一个标题以及提醒的 ID 来创建任务

这样一来,便能在设定的时刻调用 alerter 在屏幕右上角弹出提醒。
我喜欢用 Emacs 的 org-mode 来安排任务,但可惜的是,org-mode 没有定点提醒的功能(如果有的话希望来个人打我的脸 XD)。开发了 cuckoo 后,忽然灵机一动——何不给 Emacs 添砖加瓦,让它可以把 org-mode 中的条目内容(所谓的 heading)当做任务丢给 cuckoo,以此来实现定点提醒呢。感觉是个好主意,马上着手写这么些 Elisp 函数。
PS:读者朋友们就不用执着于我的 cuckoo 究竟是怎样的接口定义了。
为了实现所需要的功能,让我从结果反过来推导一番。首先,需要提炼一个 TODO 条目的标题和时间戳(用来创建提醒获取 ID),才能调用 cuckoo 的接口。标题就是 org-mode 中一个 TODO 条目的 heading text,在 Emacs 中用下面的代码获取
(nth 4 (org-heading-components))
org-headline-components 在光标位于 TODO 条目上的时候,会返回许多信息(参见下图)

其中下标为 4 的 component 就是我所需要的内容。
接着便是要获取一个提醒的 ID。ID 当然是从 cuckoo 的接口中返回的,这就需要能够解析 JSON 格式的文本。在 Emacs 中解析 JSON 序列化后的文本可以用 json 这个库,示例代码如下
(let ((s “{\”remind\”:{\”create_at\”:\”2019-01-11T14:53:59.000Z\”,\”duration\”:null,\”id\”:41,\”restricted_hours\”:null,\”timestamp\”:1547216100,\”update_at\”:\”2019-01-11T14:53:59.000Z\”}}”))
(cdr (assoc ‘id (cdr (car (json-read-from-string s))))))
既然知道如何解析(同时还知道如何提取解析后的内容),那么接下来便是要能够获取上述示例代码中的 s。s 来自于 HTTP 响应的 body,为了发出 HTTP 请求,可以用 Emacs 的 request 库,示例代码如下
(let* ((this-request (request
“http://localhost:7001/remind”
:data “{\”timestamp\”:1547216100}”
:headers ‘((“Content-Type” . “application/json”))
:parser ‘buffer-string
:type “POST”
:success (cl-function
(lambda (&key data &allow-other-keys)
(message “data: %S” data)))
:sync t))
(data (request-response-data this-request)))
data)
此处的:sync 参数花了我好长的时间才捣鼓出来——看了一下 request 函数的 docstring 后才发现,原来需要传递:sync 为 t 才可以让 request 函数阻塞地调用,否则一调用 request 就立马返回了 nil。
现在需要的就是构造:data 的值了,其中的关键是生成秒级的 UNIX Epoch 时间戳,这个时间戳可以通过 TODO 条目的 SCHEDULED 属性转换而来。比如,一个条目的 SCHEDULED 属性的值可能是 <2019-01-11 Fri 22:15>,将这个字符串传递给 date-to-time 函数可以解析成代表着秒数的几个数字
(date-to-time “<2019-01-11 Fri 22:15>”)
时间戳字符串要怎么拿到?答案是使用 org-mode 的 org-entry-get 函数
(org-entry-get nil “SCHEDULED”)
PS:需要先将光标定位在一个 TODO 条目上。
至此,所有的原件都准备齐全了,最终我的 Elisp 代码如下
(defun scheduled-to-time (scheduled)
“ 将 TODO 条目的 SCHEDULED 属性转换为 UNIX 时间戳 ”
(let ((lst (date-to-time scheduled)))
(+ (* (car lst) (expt 2 16))
(cadr lst))))

(defun create-remind-in-cuckoo (timestamp)
“ 往 cuckoo 中创建一个定时提醒并返回这个刚创建的提醒的 ID”
(let (remind-id)
(request
“http://localhost:7001/remind”
:data (json-encode-alist
(list (cons “timestamp” timestamp)))
:headers ‘((“Content-Type” . “application/json”))
:parser ‘buffer-string
:type “POST”
:success (cl-function
(lambda (&key data &allow-other-keys)
(message “ 返回内容为:%S” data)
(let ((remind (json-read-from-string data)))
(setq remind-id (cdr (assoc ‘id (cdr (car remind))))))))
:sync t)
remind-id))

(defun create-task-in-cuckoo ()
(interactive)
(let ((brief)
(remind-id))

(setq brief (nth 4 (org-heading-components)))

(let* ((scheduled (org-entry-get nil “SCHEDULED”))
(timestamp (scheduled-to-time scheduled)))
(setq remind-id (create-remind-in-cuckoo timestamp)))

(request
“http://localhost:7001/task”
:data (concat “brief=” (url-encode-url brief) “&detail=&remind_id=” (format “%S” remind-id))
:type “POST”
:success (cl-function
(lambda (&key data &allow-other-keys)
(message “ 任务创建完毕 ”))))))
在 create-task-in-cuckoo 中,之所以没有再传递 application/json 形式的数据给 cuckoo,是因为不管我怎么测试,始终无法避免中文字符在传递到接口的时候变成了 \u 编码的形式,不得已而为之,只好把中文先做一遍 url encoding,然后再通过表单的形式(form/x-www-urlencode)发送给接口了。
全文完。

正文完
 0