拿Emacs對接我的cuckoo

cuckoo是一個我自己開發的類似待辦事項的工具,運行在我本地的電腦上。它有如下兩個接口:

  1. 傳入一個UNIX Epoch時間戳創建提醒
  2. 傳入一個標題以及提醒的ID來創建任務

這樣一來,便能在設定的時刻調用alerter在屏幕右上角彈出提醒。

我喜歡用Emacsorg-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))))))

既然知道如何解析(同時還知道如何提取解析後的內容),那麼接下來便是要能夠獲取上述示例代碼中的ss來自於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後才發現,原來需要傳遞:synct纔可以讓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)發送給接口了。

全文完。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章