1 #+OPTIONS: H:3 num:nil toc:t \n:nil ::t |:t ^:t -:t f:t *:t tex:t d:(HIDE) tags:not-in-toc
2 #+STARTUP: align fold nodlcheck hidestars oddeven lognotestate
3 #+SEQ_TODO: TODO(t) INPROGRESS(i) WAITING(w@) | DONE(d) CANCELED(c@)
4 #+TAGS: Write(w) Update(u) Fix(f) Check(c)
5 #+TITLE: Import items from remember the milk
7 #+EMAIL: arun@nubati.net
13 This page will show you how to import items from [[http://www.rememberthemilk.com][remember the milk]](RTM) into org mode (one way sync).
14 The script will parse the atom feed from RTM adding more information such as scheduled date, location, tags and priority.
18 The script uses the built-in feed parser from org-mode. Since the feed parser is very unspecific, we add a function that parses each RTM entry
19 and scans it for some org-mode relevant information such as the scheduled date, location, tags, etc. You will need to add these scripts to your .emacs file
20 and modify them to fit your needs (you need to add your RTM-atom feed url at least).
22 #+begin_src emacs-lisp
23 (defun org-feed-parse-RTM-entry (entry)
24 "Parse the `:item-full-text' as a sexp and create new properties."
25 (let ((xml (car (read-from-string (plist-get entry :item-full-text)))))
26 ;; Get first <link href='foo'/>.
27 (setq entry (plist-put entry :link
29 (car (xml-get-children xml 'link))
31 ;; Add <title/> as :title.
32 (setq entry (plist-put entry :title
33 (xml-substitute-special
34 (car (xml-node-children
35 (car (xml-get-children xml 'title)))))))
36 ;; look for some other information that's in the content of the entry
37 ;; the structure looks something like:
38 ;; <content><div> <div item> <span itemname></span><span itemvalue></span></div>...
39 (let* ((content (car (xml-get-children xml 'content)))
40 (main (car (xml-get-children content 'div)))
41 (items (xml-get-children main 'div)))
43 ; iterate over all items and check for certain classes
45 (setq item (car items))
46 ; get the second span entry
47 (setq valuesub (car (cdr (xml-node-children item))))
49 ((string= (xml-get-attribute item 'class) "rtm_due")
50 (setq entry (plist-put entry :due (car (xml-node-children valuesub))))
51 (setq mydate (car (xml-node-children valuesub)))
52 ;; Any time will be stripped
53 ;; Entries will be only schedued to a date
54 (if (string= mydate "never")
56 ;; entries could be scheduled to a date "Tue 4 Aug 15"
57 ;; or to a date/time "Tue 4 Aug 15 at 10:00AM"
58 (if (string-match "^\\([a-zA-Z]*\\) \\([0-9]*\\) \\([a-zA-Z]*\\) \\([0-9]*\\) at \\([0-9:]*\\)" mydate)
59 (setq mydate (concat "20" (match-string 4 mydate) " " (match-string 3 mydate) " " (match-string 2 mydate) " " (match-string 5 mydate) ":01"))
61 (string-match "^\\([a-zA-Z]*\\) \\([0-9]*\\) \\([a-zA-Z]*\\) \\([0-9]*\\)$" mydate)
62 (setq mydate (concat "20" (match-string 4 mydate) " " (match-string 3 mydate) " " (match-string 2 mydate) " 00:00:01"))))
64 (setq mydate (parse-time-string mydate))
65 (setq mydate (apply #'encode-time mydate))
66 (setq mydate (format-time-string (car org-time-stamp-formats) mydate))
67 (setq entry (plist-put entry :dueorgformat mydate)))))
68 ((string= (xml-get-attribute item 'class) "rtm_tags")
69 (setq entry (plist-put entry :tags (car (xml-node-children valuesub)))))
70 ((string= (xml-get-attribute item 'class) "rtm_time_estimate")
71 (setq entry (plist-put entry :timeestimate (car (xml-node-children valuesub)))))
72 ((string= (xml-get-attribute item 'class) "rtm_priority")
73 (setq entry (plist-put entry :priority (car (xml-node-children valuesub)))))
74 ((string= (xml-get-attribute item 'class) "rtm_location")
75 (setq entry (plist-put entry :location (car (xml-node-children valuesub))))))
76 (setq items (cdr items))
81 It's also easy to set up special filters for the RTM entries. Here, we for example set up a filter that skips all entries that don't have a due-date set in RTM.
83 #+begin_src emacs-lisp
84 (defun org-feed-RTM-filter-non-scheduled (entry)
85 "filter out all entries that don't have a due date set"
86 (if (string= (plist-get entry :due) "never")
91 Next we need to tell org-mode where to find the feed, for this you need to copy the url location for your RTM atom-feed. The code below will
92 write the RTM information into a file called "~/org/RTM.org". If you want to change the file name, just edit the 4th line (you will also need to edit the file name in the scriptlet below). Furthermore, we define
93 here the functions we defined above to filter and parse the RTM entries. We also define a template that will be used in the org-file. Here, we use
94 the newly defined items for the due date and location, etc.
96 #+begin_src emacs-lisp
98 '(("Remember The Milk"
99 "https://www.rememberthemilk.com/atom/<add url for RTM atom feed here>"
102 :parse-feed org-feed-parse-atom-feed
103 :parse-entry org-feed-parse-RTM-entry
104 :template "* TODO %title\n SCHEDULED:%dueorgformat\n Due: %due\n Location: %location\n Priority:%priority\n Tags:%tags\n %a\n "
105 :filter org-feed-RTM-filter-non-scheduled
109 Finally, we need to tell emacs to run this automatically, for example, every hour. For this, we delete the old file and buffer and then recreate it. If you wanted to change the file name
110 for the RTM file you need to edit this too.
112 #+begin_src emacs-lisp
113 (defun myupdate-RTM ()
114 "deletes old RTM.org and generates a new one"
116 (if (not (eq nil (get-buffer "RTM.org")))
118 (kill-buffer "RTM.org")
119 (delete-file "~/org/RTM.org")))
120 (set-buffer (get-buffer-create "RTM.org"))
121 (write-file "~/org/RTM.org")
122 (org-feed-update-all)
126 (run-at-time 3600 3600 'myupdate-RTM)
130 [[file:index.org][{Back to Worg's index}]]