Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Initial commit.

  • Loading branch information...
commit aee9883876c9ac49d1ed6779fdd12f8f1e024b3e 0 parents
Seth Mason authored

Showing 4 changed files with 774 additions and 0 deletions. Show diff stats Hide diff stats

  1. +1 0  .gitignore
  2. +14 0 README
  3. +682 0 lisp/rtm.el
  4. +77 0 lisp/slack-rtm.el
1  .gitignore
... ... @@ -0,0 +1 @@
  1 +*~
14 README
... ... @@ -0,0 +1,14 @@
  1 +slack-rtm.el - Light emacs integration with Remember The Milk
  2 +
  3 +(c) 2010 Seth Mason (seth@sethmason.com)
  4 +(C) 2009 Friedrich Delgado Friedrichs (for rtm.el)
  5 +
  6 +This is my first emacs lisp program so I'm taking the path of releasing early
  7 +and releasing often.
  8 +
  9 +Right now, it just dumps whatever query you send to Remember The Milk and puts
  10 +the results in a buffer. It only shows task name.
  11 +
  12 +For more infomation see http://www.rememberthemilk.com
  13 +
  14 +
682 lisp/rtm.el
... ... @@ -0,0 +1,682 @@
  1 +;;; rtm.el --- An elisp implementation of the Remember The Milk API
  2 +
  3 +;; Copyright (C) 2009 Friedrich Delgado Friedrichs
  4 +;; uses parts of org-rtm.el Copyright (C) 2008 Avdi Grimm
  5 +
  6 +;; Author: Friedrich Delgado Friedrichs <frie...@nomaden.org>
  7 +;; Created: Oct 18 2009
  8 +;; Version: 0.0
  9 +;; Keywords: remember the milk productivity todo
  10 +
  11 +;; This product uses the Remember The Milk API but is not endorsed or
  12 +;; certified by Remember The Milk
  13 +
  14 +;; This file is NOT part of GNU Emacs.
  15 +
  16 +;; This file is free software; you can redistribute it and/or modify
  17 +;; it under the terms of the GNU General Public License as published by
  18 +;; the Free Software Foundation; either version 3, or (at your option)
  19 +;; any later version.
  20 +
  21 +;; This file is distributed in the hope that it will be useful,
  22 +;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  23 +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  24 +;; GNU General Public License for more details.
  25 +
  26 +;; You should have received a copy of the GNU General Public License
  27 +;; along with GNU Emacs; see the file COPYING. If not, write to
  28 +;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  29 +;; Boston, MA 02110-1301, USA.
  30 +
  31 +;;; Commentary:
  32 +
  33 +;; TODO Write commentary
  34 +
  35 +;;; Code:
  36 +
  37 +(eval-when-compile (require 'cl))
  38 +(require 'url-http)
  39 +(require 'url-util)
  40 +(require 'xml)
  41 +(require 'custom)
  42 +
  43 +;;;; Customisation
  44 +
  45 +(defgroup rtm nil
  46 + "Options for emacs lisp integration of Remember The Milk"
  47 + :tag "elisp RTM"
  48 + :group 'applications)
  49 +
  50 +(defcustom rtm-api-key "d40eb4df08dd52c1930afa9d79dceda0"
  51 + "Your own API key for Remember The Milk."
  52 + :type 'string :group 'rtm)
  53 +(defcustom rtm-api-shared-secret "39d8e367fdce977c"
  54 + "Your shared secret for your Remember The Milk API Key.
  55 +
  56 +Note that in an open source application it is not easily possible to
  57 +hide the secret. That's why it's probably the best solution for every
  58 +user to register their own API key.
  59 +
  60 +See also
  61 +http://groups.google.com/group/rememberthemilk-api/browse_thread/thread/dcb035f162d4dcc8%3Fpli%3D1
  62 +
  63 +You can register your own API key and secret under
  64 +http://www.rememberthemilk.com/services/api/requestkey.rtm
  65 +
  66 +In the description just tell them you're going to use the emacs lisp
  67 +API Kit"
  68 + :type 'string :group 'rtm)
  69 +
  70 +;;;; constants and variables
  71 +
  72 +(defconst rtm-rest-uri "http://api.rememberthemilk.com/services/rest/"
  73 + "Endpoint URL for REST requests. See
  74 + http://www.rememberthemilk.com/services/api/request.rest.rtm")
  75 +
  76 +(defconst rtm-auth-uri "http://www.rememberthemilk.com/services/auth/"
  77 + "Authentication service URL, see
  78 + http://www.rememberthemilk.com/services/api/authentication.rtm")
  79 +
  80 +(defvar rtm-auth-token ""
  81 + "Auth token received from RTM Website, after the user authenticated
  82 + your app")
  83 +
  84 +(defconst rtm-ui-buffer-name "*rtm*"
  85 + "Name for the rtm user interface buffer")
  86 +
  87 +(defconst rtm-auth-token-file ".rtm-auth-token"
  88 + "Name for storing the auth token for the current session")
  89 +
  90 +(defvar rtm-current-timeline nil
  91 + "The current timeline")
  92 +
  93 +(defvar rtm-debug nil
  94 + "debug level")
  95 +
  96 +;;;; API wrappers
  97 +(defmacro def-rtm-method (methodname rtm-method-name call-func result-func
  98 + result-path &rest parms)
  99 + (declare (indent 1))
  100 + `(defun ,methodname ,parms
  101 + (,result-func ,result-path
  102 + (,call-func ',rtm-method-name
  103 + ,@(mapcar (lambda (sym)
  104 + (list 'backquote
  105 + (cons (symbol-name sym)
  106 + (list ', sym))))
  107 + ;; remove lambda keywords
  108 + (remove-if (lambda (sym)
  109 + (or (eq sym '&optional)
  110 + (eq sym '&rest)))
  111 + parms))))))
  112 +
  113 +(defmacro def-rtm-macro (macro-name call-func result-func)
  114 + (declare (indent 0))
  115 + `(defmacro ,macro-name (methodname rtm-method-name result-path &rest parms)
  116 + (declare (indent 1))
  117 + `(def-rtm-method ,methodname ,rtm-method-name ,',call-func
  118 + ,',result-func
  119 + ',result-path ,@parms)))
  120 +
  121 +(def-rtm-macro def-rtm-signed-scalar-method
  122 + rtm-call-signed rtm-get-scalar-from-response)
  123 +
  124 +(def-rtm-macro def-rtm-authenticated-scalar-method
  125 + rtm-call-authenticated rtm-get-scalar-from-response)
  126 +
  127 +(def-rtm-macro def-rtm-timeline-scalar-method
  128 + rtm-call-timeline rtm-get-scalar-from-response)
  129 +
  130 +(def-rtm-macro def-rtm-signed-list-method
  131 + rtm-call-signed rtm-get-list-from-response)
  132 +
  133 +(def-rtm-macro def-rtm-authenticated-list-method
  134 + rtm-call-authenticated rtm-get-list-from-response)
  135 +
  136 +(def-rtm-macro def-rtm-timeline-list-method
  137 + rtm-call-timeline rtm-get-list-from-response)
  138 +
  139 +;; awfully brief aliases, but those long names mess up indentation
  140 +;; recomendation: use only the authenticated aliases, and the long
  141 +;; names for those (rarely used) methods that are only signed
  142 +(defalias 'def-rtm-si-sca 'def-rtm-signed-scalar-method)
  143 +(defalias 'def-rtm-scalar 'def-rtm-authenticated-scalar-method)
  144 +(defalias 'def-rtm-scalar! 'def-rtm-timeline-scalar-method)
  145 +(defalias 'def-rtm-si-lis 'def-rtm-signed-list-method)
  146 +(defalias 'def-rtm-list 'def-rtm-authenticated-list-method)
  147 +(defalias 'def-rtm-list! 'def-rtm-timeline-list-method)
  148 +
  149 +(put 'def-rtm-si-sca 'lisp-indent-function 1)
  150 +(put 'def-rtm-scalar 'lisp-indent-function 1)
  151 +(put 'def-rtm-scalar! 'lisp-indent-function 1)
  152 +(put 'def-rtm-si-lis 'lisp-indent--function 1)
  153 +(put 'def-rtm-list 'lisp-indent-function 1)
  154 +(put 'def-rtm-list! 'lisp-indent-function 1)
  155 +
  156 +;; note that, for modifying functions, it's mostly better to define
  157 +;; them via define-rtm-list!, since you will receive the transaction
  158 +;; *and* the result, while a function defined via define-rtm-scalar!
  159 +;; will only return the transaction
  160 +
  161 +(defun rtm-call-unsigned (method &rest params)
  162 + (let ((request (rtm-construct-request-url rtm-rest-uri
  163 + (rtm-prepare-params method
  164 + params))))
  165 + (rtm-do-request request)))
  166 +
  167 +(defun rtm-call-signed (method &rest params)
  168 + (let* ((unsigned-params (rtm-prepare-params method params))
  169 + (all-params (append-api-sig unsigned-params))
  170 + (request (rtm-construct-request-url rtm-rest-uri
  171 + all-params)))
  172 + (rtm-do-request request)))
  173 +
  174 +(defun rtm-call-authenticated (method &rest params)
  175 + (apply #'rtm-call-signed
  176 + method
  177 + `("auth_token" . ,(rtm-authenticate))
  178 + params))
  179 +
  180 +(defun rtm-call-timeline (method &rest params)
  181 + (apply #'rtm-call-authenticated
  182 + method
  183 + `("timeline" . ,(rtm-timeline))
  184 + params))
  185 +
  186 +(defun rtm-get-nodes-from-node-list (node-name node-list)
  187 + (remove-if-not (lambda (el) (eq node-name
  188 + (xml-node-name el)))
  189 + node-list))
  190 +
  191 +(defun rtm-get-node-content-from-response (node-name response)
  192 + (xml-node-children (car (rtm-get-nodes-from-node-list node-name
  193 + response))))
  194 +
  195 +(defun rtm-get-list-from-response (path response)
  196 + (let ((rst path)
  197 + (content response))
  198 + (while rst
  199 + (setq content (rtm-get-node-content-from-response (car rst) content))
  200 + (setq rst (cdr rst)))
  201 + content))
  202 +
  203 +(defun rtm-get-scalar-from-response (path response)
  204 + (car (rtm-get-list-from-response path response)))
  205 +
  206 +;;;;; Actual api wrappers from
  207 +;; http://www.rememberthemilk.com/services/api/methods/
  208 +;;;;;; auth
  209 +(def-rtm-signed-scalar-method rtm-auth-check-token rtm.auth.checkToken
  210 + (auth token) auth_token)
  211 +;; api call response (without post-processing):
  212 +;; ((auth nil
  213 +;; (token nil "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
  214 +;; (perms nil "delete")
  215 +;; (user
  216 +;; ((id . "xxxxxxx")
  217 +;; (username . "johndoe")
  218 +;; (fullname . "John Doe")))))
  219 +(def-rtm-signed-scalar-method rtm-auth-get-frob rtm.auth.getFrob (frob))
  220 +(def-rtm-signed-scalar-method rtm-auth-get-token rtm.auth.getToken
  221 + (auth token) frob)
  222 +;; api call response (without post-processing):
  223 +;; ((auth nil (token nil "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
  224 +;; (perms nil "delete") (user (... ... ...))))
  225 +
  226 +;;;;;; contacts
  227 +(def-rtm-list! rtm-contacts-add rtm.contacts.add (contact) contact)
  228 +(def-rtm-list! rtm-contacts-delete rtm.contacts.delete () contact_id)
  229 +(def-rtm-list rtm-contacts-get-list rtm.contacts.getList (contacts))
  230 +
  231 +;;;;;; groups
  232 +(def-rtm-list! rtm-groups-add rtm.groups.add () group)
  233 +(def-rtm-list! rtm-groups-add-contact rtm.groups.addContact ()
  234 + group_id contact_id)
  235 +(def-rtm-list! rtm-groups-delete rtm.groups.delete () group_id)
  236 +(def-rtm-list rtm-groups-get-list rtm.groups.getList ())
  237 +(def-rtm-list! rtm-groups-remove-contact rtm.groups.removeContact ()
  238 + group_id contact_id)
  239 +
  240 +;;;;;; lists
  241 +(def-rtm-list! rtm-lists-add rtm.lists.add ()
  242 + name &optional filter)
  243 +(def-rtm-list! rtm-lists-archive rtm.lists.archive ()
  244 + list_id)
  245 +(def-rtm-list! rtm-lists-delete rtm.lists.delete ()
  246 + list_id)
  247 +(def-rtm-list rtm-lists-get-list rtm.lists.getList (lists))
  248 +;; example response (after result function):
  249 +;; ((list
  250 +;; ((id . "7781815")
  251 +;; (name . "Inbox")
  252 +;; (deleted . "0")
  253 +;; (locked . "1")
  254 +;; (archived . "0")
  255 +;; (position . "-1")
  256 +;; (smart . "0")
  257 +;; (sort_order . "0")))
  258 +;; (list
  259 +;; ((id . "7781820")
  260 +;; (name . "All Tasks")
  261 +;; (deleted . "0")
  262 +;; (locked . "0")
  263 +;; (archived . "0")
  264 +;; (position . "0")
  265 +;; (smart . "1")
  266 +;; (sort_order . "0"))
  267 +;; (filter nil))
  268 +;; (list
  269 +;; ((id . "7781818")
  270 +;; (name . "Work")
  271 +;; (deleted . "0")
  272 +;; (locked . "0")
  273 +;; (archived . "0")
  274 +;; (position . "0")
  275 +;; (smart . "0")
  276 +;; (sort_order . "0")))
  277 +;; (list
  278 +;; ((id . "7781816")
  279 +;; (name . "Private")
  280 +;; (deleted . "0")
  281 +;; (locked . "0")
  282 +;; (archived . "0")
  283 +;; (position . "0")
  284 +;; (smart . "0")
  285 +;; (sort_order . "0")))
  286 +;; (list
  287 +;; ((id . "7781819")
  288 +;; (name . "Sent")
  289 +;; (deleted . "0")
  290 +;; (locked . "1")
  291 +;; (archived . "0")
  292 +;; (position . "1")
  293 +;; (smart . "0")
  294 +;; (sort_order . "0"))))
  295 +(def-rtm-list! rtm-lists-set-default-list rtm.lists.setDefaultList ()
  296 + list_id)
  297 +(def-rtm-list! rtm-lists-set-name rtm.lists.setName ()
  298 + list_id name)
  299 +(def-rtm-list! rtm-lists-unarchive rtm.lists.unarchive ()
  300 + list_id)
  301 +
  302 +;;;;;; locations
  303 +(def-rtm-list rtm-locations-get-list rtm.locations.getList (locations))
  304 +
  305 +;;;;;; reflection
  306 +(def-rtm-signed-list-method rtm-reflection-get-methods rtm.reflection.getMethods
  307 + (methods))
  308 +(def-rtm-signed-scalar-method rtm-reflection-get-method-info
  309 + rtm.reflection.getMethodInfo () method_name)
  310 +
  311 +;;;;;; settings
  312 +(def-rtm-list rtm-settings-get-list rtm.settings.getList (settings))
  313 +
  314 +;;;;;; tasks
  315 +(def-rtm-list! rtm-tasks-add rtm.tasks.add ()
  316 + name &optional parse list_id)
  317 +
  318 +(def-rtm-list! rtm-tasks-add-tags rtm.tasks.addTags ()
  319 + list_id taskseries_id task_id tags)
  320 +
  321 +(def-rtm-list! rtm-tasks-complete rtm.tasks.complete ()
  322 + list_id taskseries_id task_id)
  323 +
  324 +(def-rtm-list! rtm-tasks-delete rtm.tasks.delete ()
  325 + list_id taskseries_id task_id)
  326 +
  327 +(def-rtm-list rtm-tasks-get-list rtm.tasks.getList (tasks)
  328 + &optional list_id filter last_sync)
  329 +;; example response (after result function):
  330 +;; ((list
  331 +;; ((id . "7781819")))
  332 +;; (list
  333 +;; ((id . "7781817")))
  334 +;; (list
  335 +;; ((id . "7781816"))
  336 +;; (taskseries
  337 +;; ((id . "35272531")
  338 +;; (created . "2009-03-08T20:57:45Z")
  339 +;; (modified . "2009-03-08T21:52:18Z")
  340 +;; (name . "Try Remember The Milk")
  341 +;; (source . "js")
  342 +;; (url . "")
  343 +;; (location_id . ""))
  344 +;; (tags nil)
  345 +;; (participants nil)
  346 +;; (notes nil)
  347 +;; (task
  348 +;; ((id . "49791364")
  349 +;; (due . "2009-03-08T20:57:00Z")
  350 +;; (has_due_time . "1")
  351 +;; (added . "2009-03-08T20:57:45Z")
  352 +;; (completed . "2009-03-08T21:52:16Z")
  353 +;; (deleted . "")
  354 +;; (priority . "1")
  355 +;; (postponed . "0")
  356 +;; (estimate . "")))))
  357 +;; (list
  358 +;; ((id . "7781818")))
  359 +;; (list
  360 +;; ((id . "7781820"))))
  361 +
  362 +(def-rtm-list! rtm-tasks-move-priority rtm.tasks.movePriority ()
  363 + list_id taskseries_id task_id direction)
  364 +
  365 +(def-rtm-list! rtm-tasks-move-to rtm.tasks.moveTo ()
  366 + from_list_id to_list_id taskseries_id task_id)
  367 +
  368 +(def-rtm-list! rtm-tasks-postpone rtm.tasks.postpone ()
  369 + list_id taskseries_id task_id)
  370 +
  371 +(def-rtm-list! rtm-tasks-remove-tags rtm.tasks.removeTags ()
  372 + list_id taskseries_id task_id tags)
  373 +
  374 +(def-rtm-list! rtm-tasks-set-due-date rtm.tasks.setDueDate ()
  375 + list_id taskseries_id task_id &optional due has_due_time parse)
  376 +
  377 +(def-rtm-list! rtm-tasks-set-estimate rtm.tasks.setEstimate ()
  378 + list_id taskseries_id task_id &optional estimate)
  379 +
  380 +(def-rtm-list! rtm-tasks-set-location rtm.tasks.setLocation ()
  381 + list_id taskseries_id task_id &optional location_id)
  382 +
  383 +(def-rtm-list! rtm-tasks-set-name rtm.tasks.setName ()
  384 + list_id taskseries_id task_id name)
  385 +
  386 +(def-rtm-list! rtm-tasks-set-priority rtm.tasks.setPriority ()
  387 + list_id taskseries_id task_id &optional priority)
  388 +
  389 +(def-rtm-list! rtm-tasks-set-recurrence rtm.tasks.setRecurrence ()
  390 + list_id taskseries_id task_id &optional repeat)
  391 +
  392 +(def-rtm-list! rtm-tasks-set-tags rtm.tasks.setTags ()
  393 + list_id taskseries_id task_id &optional tags)
  394 +
  395 +(def-rtm-list! rtm-tasks-set-url rtm.tasks.setURL ()
  396 + list_id taskseries_id task_id &optional url)
  397 +
  398 +(def-rtm-list! rtm-tasks-uncomplete rtm.tasks.uncomplete ()
  399 + list_id taskseries_id task_id)
  400 +
  401 +;;;;;; tasks.notes
  402 +(def-rtm-list! rtm-tasks-notes-add rtm.tasks.notes.add ()
  403 + list_id taskseries_id task_id note_title note_text)
  404 +
  405 +(def-rtm-list! rtm-tasks-notes-delete rtm.tasks.notes.delete ()
  406 + note_id)
  407 +
  408 +(def-rtm-list! rtm-tasks-notes-edit rtm.tasks.notes.edit ()
  409 + note_id note_title note_text)
  410 +
  411 +;;;;;; test
  412 +(defun rtm-test-echo ()
  413 + (rtm-call-unsigned 'rtm.test.echo))
  414 +
  415 +(def-rtm-list rtm-test-login rtm.test.login ())
  416 +
  417 +;;;;;; time
  418 +(def-rtm-signed-list-method rtm-time-convert rtm.time.convert ()
  419 + to_timezone &optional from_timezone time)
  420 +
  421 +;;;;;; timelines
  422 +(def-rtm-scalar rtm-timelines-create rtm.timelines.create (timeline))
  423 +(defun rtm-timeline ()
  424 + (unless rtm-current-timeline
  425 + (progn
  426 + (setq rtm-current-timeline (rtm-timelines-create))))
  427 + rtm-current-timeline)
  428 +
  429 +;;;;;; timezones
  430 +(def-rtm-signed-list-method rtm-timezones-get-list rtm.timezones.getList ())
  431 +
  432 +;;;;;; transactions
  433 +(def-rtm-list! rtm-transactions-undo rtm.transactions.undo () transaction_id)
  434 +
  435 +;;;; User authentication
  436 +
  437 +(defun rtm-authenticate ()
  438 + "Always use this function to call an authenticated method, it's the only one
  439 +that will update rtm-auth-token"
  440 + (setq rtm-auth-token
  441 + (let ((auth-token (or (rtm-get-stored-auth-token)
  442 + rtm-auth-token)))
  443 + (if (and auth-token
  444 + (rtm-auth-token-valid auth-token))
  445 + auth-token
  446 + (rtm-get-new-auth-token))))
  447 + rtm-auth-token)
  448 +
  449 +(defun rtm-auth-token-valid (auth-token)
  450 + (let ((token (ignore-errors (rtm-auth-check-token auth-token))))
  451 + (if (and token
  452 + (string-equal auth-token token))
  453 + t
  454 + nil)))
  455 +
  456 +(defun rtm-get-new-auth-token ()
  457 + (let* ((frob (rtm-auth-get-frob))
  458 + (auth-url (rtm-authentication-url 'delete frob))
  459 + (auth-token nil))
  460 + (while (not auth-token)
  461 + (rtm-authentication-dialog auth-url)
  462 + (setq auth-token
  463 + (rtm-auth-get-token frob))
  464 + (if (rtm-auth-token-valid auth-token)
  465 + (rtm-store-auth-token auth-token)
  466 + (setq auth-token nil)))
  467 + auth-token))
  468 +
  469 +(defun rtm-store-auth-token (auth-token)
  470 + (let ((token-file (locate-user-emacs-file rtm-auth-token-file)))
  471 + (unless (file-exists-p token-file)
  472 + (with-temp-file token-file))
  473 + (set-file-modes token-file #o600)
  474 + (with-temp-file token-file
  475 + (insert auth-token)))
  476 + auth-token)
  477 +
  478 +(defun rtm-get-stored-auth-token ()
  479 + (let ((token-file (locate-user-emacs-file rtm-auth-token-file)))
  480 + (if (file-exists-p token-file)
  481 + (if (file-readable-p token-file)
  482 + (with-temp-buffer
  483 + (insert-file-contents token-file)
  484 + (buffer-string))
  485 + (error "Auth token store %s exists, but is not readable."
  486 + token-file))
  487 + nil)))
  488 +
  489 +(defun rtm-authentication-dialog (auth-url)
  490 + (let ((rtm-buffer (generate-new-buffer rtm-ui-buffer-name)))
  491 + (with-current-buffer rtm-buffer
  492 + (insert "Please visit the following url to authenticate this
  493 +application:\n\n")
  494 + (insert-text-button auth-url 'type 'rtm-url)
  495 + (display-buffer rtm-buffer)
  496 + ;; (redisplay)
  497 + (read-from-minibuffer
  498 + "Press RETURN if after authentication was granted")
  499 + (kill-buffer rtm-buffer))))
  500 +
  501 +(define-button-type 'rtm-url
  502 + 'action (lambda (x)
  503 + (let ((button (button-at (point))))
  504 + (browse-url
  505 + (button-label button))))
  506 + 'follow-link t)
  507 +
  508 +(define-button-type 'rtm-button
  509 + 'follow-link t)
  510 +
  511 +(defun rtm-authentication-url (perms frob)
  512 + (let* ((unsigned-params `(("api_key" . ,rtm-api-key)
  513 + ("perms" . ,(maybe-string perms))
  514 + ("frob" . ,frob)))
  515 + (all-params (append-api-sig unsigned-params)))
  516 + (rtm-construct-request-url rtm-auth-uri
  517 + all-params)))
  518 +
  519 +;;;; WebAPI handling
  520 +
  521 +(defun rtm-do-request (request)
  522 + (if rtm-debug
  523 + (message "request: %s" request))
  524 + (rtm-parse-response (url-retrieve-synchronously request)))
  525 +
  526 +;; adapted from avdi's code:
  527 +(defun rtm-api-sig (params)
  528 + (let* ((param-copy (copy-list params))
  529 + (sorted-params (sort param-copy
  530 + (lambda (lhs rhs) (string< (car lhs) (car rhs)))))
  531 + (joined-params (mapcar (lambda (param)
  532 + (concat (car param) (cdr param)))
  533 + sorted-params))
  534 + (params-str (reduce 'concat joined-params))
  535 + (with-secret (concat rtm-api-shared-secret params-str)))
  536 + (md5 with-secret)))
  537 +
  538 +(defun rtm-prepare-params (method params)
  539 + (rtm-add-method+api method
  540 + (rtm-stringify-params (rtm-weed-empty-params params))))
  541 +
  542 +(defun rtm-stringify-params (params)
  543 + (mapcar #'rtm-stringify-param params))
  544 +
  545 +(defun rtm-stringify-param (param)
  546 + (let* ((name (car param))
  547 + (value (cdr param)))
  548 + (cons (rtm-stringify-param-name name)
  549 + (rtm-stringify-value value))))
  550 +
  551 +(defun rtm-stringify-param-name (name)
  552 + (cond ((stringp name)
  553 + name)
  554 + ((symbolp name)
  555 + (symbol-name name))))
  556 +
  557 +;; note: because we can't really tell between parameter wasn't given
  558 +;; and explicitly set as nil (see rtm-weed-empty-params below), you
  559 +;; should give 'false rather than nil if you mean false
  560 +(defun rtm-stringify-value (value)
  561 + (cond ((stringp value)
  562 + value)
  563 + ((eq t value)
  564 + "true")
  565 + ((null value)
  566 + "false")
  567 + ((listp value)
  568 + (rtm-comma-separated-list value))
  569 + ((symbolp value)
  570 + (symbol-name value))
  571 + ((numberp value)
  572 + (number-to-string value))))
  573 +
  574 +(defun rtm-comma-separated-list (lis)
  575 + "turn a list into a comma separated string (and flatten it)"
  576 + (flet ((comsep (lis first)
  577 + (if (null lis)
  578 + ""
  579 + (concat (if first "" ",")
  580 + (rtm-stringify-value (car lis))
  581 + (comsep (cdr lis) nil)))))
  582 + (comsep lis t)))
  583 +
  584 +
  585 +(defun rtm-weed-empty-params (params)
  586 + (remove-if (lambda (param)
  587 + (and (listp param)
  588 + (not (null param))
  589 + (null (cdr param))))
  590 + params))
  591 +
  592 +(defun rtm-add-method+api (method params)
  593 + (append `(("method" . ,(maybe-string method))
  594 + ("api_key" . ,rtm-api-key))
  595 + params))
  596 +
  597 +;; adapted from avdi's code:
  598 +(defun rtm-construct-request-url (base-uri params)
  599 + "Construct a URL for calling a method from params"
  600 + (let* ((param-pairs (mapcar 'rtm-format-param params))
  601 + (query (rtm-join-params param-pairs)))
  602 + (concat base-uri "?" query)))
  603 +
  604 +;; adapted from avdi's code:
  605 +(defun rtm-format-param (param)
  606 + (let ((key (car param))
  607 + (value (cdr param)))
  608 + ;; it's important that we sign the unencoded parameters, but of
  609 + ;; course the request must be url-encoded
  610 + (concat key "=" (url-hexify-string value))))
  611 +
  612 +;; from avdi's code:
  613 +(defun rtm-join-params (params)
  614 + (reduce (lambda (left right) (concat left "&" right)) params))
  615 +
  616 +;; adapted from avdi's code:
  617 +(defun rtm-construct-url (method)
  618 + (concat rtm-rest-uri
  619 + "?"
  620 + "method=" method
  621 + "&"
  622 + "api_key=" rtm-api-key))
  623 +
  624 +;; from avdi's code:
  625 +;; TODO Interpret the stat attribute and throw an error if it's not ok
  626 +(defun rtm-parse-response (response)
  627 + (with-current-buffer response
  628 + (let* ((node-list (xml-parse-region (point-min) (point-max)))
  629 + (rsps (rtm-get-nodes-from-node-list 'rsp node-list)))
  630 + (when (> (length rsps) 1)
  631 + (warn
  632 + "Got more than one <rsp> node in response, please examine!
  633 +Response:%s" (pp node-list)))
  634 + (let* ((rsp (car rsps))
  635 + (children (xml-node-children rsp))
  636 + (stat (rtm-stat rsp)))
  637 + (unless stat
  638 + (warn "Weird, got no stat attribute in <rsp> node.
  639 +%s" (pp node-list)))
  640 + (if (eq stat 'ok)
  641 + children
  642 + (let* ((err (car (rtm-get-nodes-from-node-list 'err children)))
  643 + (code (xml-get-attribute err 'code))
  644 + (msg (xml-get-attribute err 'msg)))
  645 + (error "Error in server response: Code: %s\n
  646 +Message: \"%s\"" code msg)))))))
  647 +
  648 +(defun rtm-stat (rsp)
  649 + (let ((stat (xml-get-attribute-or-nil rsp 'stat)))
  650 + (if stat
  651 + (intern (downcase stat))
  652 + stat)))
  653 +
  654 +;;; example responses
  655 +;; failure:
  656 +;; ((rsp
  657 +;; ((stat . "fail"))
  658 +;; (err
  659 +;; ((code . "97")
  660 +;; (msg . "Missing signature")))))
  661 +;; success:
  662 +;; rtm.auth.getFrob:
  663 +;; ((rsp
  664 +;; ((stat . "ok"))
  665 +;; (frob nil "cce8d04e182212cddd5cdc815e09648fecd18e0e")))
  666 +;; rtm.test.echo:
  667 +;; ((rsp ((stat . "ok"))
  668 +;; (api_key nil "00000000000000000000000000000000")
  669 +;; (method nil "rtm.test.echo")))
  670 +(defun append-api-sig (unsigned-params)
  671 + (let ((api-sig (rtm-api-sig unsigned-params)))
  672 + (append unsigned-params
  673 + `(("api_sig" . ,api-sig)))))
  674 +
  675 +;;;; Misc/Helper functions
  676 +(defun maybe-string (symbol-or-string)
  677 + (if (stringp symbol-or-string) symbol-or-string
  678 + (symbol-name symbol-or-string)))
  679 +
  680 +(provide 'rtm)
  681 +
  682 +;;; rtm.el ends here
77 lisp/slack-rtm.el
... ... @@ -0,0 +1,77 @@
  1 +;;; slack-rtm.el --- An elisp implementation of the Remember The Milk API
  2 +
  3 +;; Copyright (C) 2010 Seth Mason
  4 +
  5 +;; Author: Seth Mason
  6 +;; Created: 18 August 2010
  7 +;; Version: 0.0
  8 +;; Keywords: remember the milk productivity todo
  9 +
  10 +;; This product uses the Remember The Milk API but is not endorsed or
  11 +;; certified by Remember The Milk
  12 +
  13 +;; This file is NOT part of GNU Emacs.
  14 +
  15 +;; This file is free software; you can redistribute it and/or modify
  16 +;; it under the terms of the GNU General Public License as published by
  17 +;; the Free Software Foundation; either version 3, or (at your option)
  18 +;; any later version.
  19 +
  20 +;; This file is distributed in the hope that it will be useful,
  21 +;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  22 +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  23 +;; GNU General Public License for more details.
  24 +
  25 +;; You should have received a copy of the GNU General Public License
  26 +;; along with GNU Emacs; see the file COPYING. If not, write to
  27 +;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  28 +;; Boston, MA 02110-1301, USA.
  29 +
  30 +;;; Commentary:
  31 +
  32 +;; Pull your tasks from RTM and then shove them into a buffer.
  33 +
  34 +;;; Code:
  35 +
  36 +(require 'rtm)
  37 +(require 'xml)
  38 +
  39 +(defcustom slack-rtm-query "list:Inbox_ AND status:incomplete"
  40 + "Query to use when getting lists from RTM."
  41 + :group 'slack-rtm
  42 + :type 'string)
  43 +
  44 +(defcustom slack-rtm-buffer-name "*todo*"
  45 + "Name of buffer that is created"
  46 + :group 'slack-rtm
  47 + :type 'string)
  48 +
  49 +(defconst slack-rtm-buffer-name "*todo*"
  50 + "Name for the buffer where tasks are written")
  51 +
  52 +(defun slack-rtm-get-tasks ()
  53 + "Get the tasks from rtm."
  54 + (rtm-tasks-get-list nil slack-rtm-query))
  55 +
  56 +(defun slack-rtm ()
  57 + "Create the `slack-rtm-buffer-name' and fill it with takss from RTM"
  58 + (interactive)
  59 + (let ((slack-buffer (generate-new-buffer slack-rtm-buffer-name))
  60 + (task-list (slack-rtm-get-tasks)))
  61 + (with-current-buffer slack-buffer
  62 + (insert (mapconcat 'slack-rtm-tasks-to-string task-list "\n"))
  63 + (display-buffer slack-buffer))))
  64 +
  65 +(defun slack-rtm-tasks-to-string (task)
  66 + "Return a formatted version of task. This walks the datastructure returned
  67 +by the rtm package. `rtm-tasks-get-list'"
  68 + (let ((taskseries (xml-get-children task 'taskseries)))
  69 + (mapconcat 'slack-rtm-taskseries-to-string taskseries "\n")))
  70 +
  71 +(defun slack-rtm-taskseries-to-string (taskseries)
  72 + "Return a formatted of taskseries"
  73 + (concat "* " (xml-get-attribute taskseries 'name)))
  74 +
  75 +(provide 'slack-rtm)
  76 +
  77 +;;; slack-rtm.el ends here

0 comments on commit aee9883

Please sign in to comment.
Something went wrong with that request. Please try again.