Emacs library to control Basecamp
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

196 lines
7.4KB

  1. ;;; libbcel-client.el --- Handles connection to the Basecamp 3 API -*- lexical-binding: t; -*-
  2. ;; Copyright (C) 2019 Damien Cassou
  3. ;; Author: Damien Cassou <damien@cassou.me>
  4. ;; Url: https://gitlab.petton.fr/bcel/libbcel
  5. ;; Package-requires: ((emacs "26.1"))
  6. ;; Version: 0.4.0
  7. ;; This program is free software; you can redistribute it and/or modify
  8. ;; it under the terms of the GNU General Public License as published by
  9. ;; the Free Software Foundation, either version 3 of the License, or
  10. ;; (at your option) any later version.
  11. ;; This program is distributed in the hope that it will be useful,
  12. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;; GNU General Public License for more details.
  15. ;; You should have received a copy of the GNU General Public License
  16. ;; along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. ;;; Commentary:
  18. ;; This file takes care of communicating with Basecamp 3 API. The
  19. ;; authentication part is handled by `libbcel-oauth.el'.
  20. ;;; Code:
  21. (require 'request)
  22. (require 'json)
  23. (require 'libbcel-oauth)
  24. (defcustom libbcel-client-account-id nil
  25. "The account id to connect to.
  26. This is the first number appearing after basecamp.com in the URL
  27. when you are on the basecamp website."
  28. :type 'string
  29. :group 'libbcel)
  30. (defvar libbcel-client--oauth-store nil
  31. "Remembers the OAuth authentication data.")
  32. (defun libbcel-client--oauth-store ()
  33. "Return the OAuth authentication data."
  34. (or libbcel-client--oauth-store
  35. (setq libbcel-client--oauth-store (libbcel-oauth-get-store))))
  36. (defvar libbcel-client--log-directory (expand-file-name "libbcel-client-logs" temporary-file-directory)
  37. "Temporary directory where to put communication logs.")
  38. (defun libbcel-client--make-log-subdir (url)
  39. "Create a subdirectory in `libbcel-client--log-directory' to store logs for a request on URL."
  40. (let* ((subdir-name (format "%s_%s"
  41. (format-time-string "%F_%H:%M:%S")
  42. (replace-regexp-in-string "/" "_" url)))
  43. (subdir (expand-file-name subdir-name libbcel-client--log-directory)))
  44. (make-directory subdir t)
  45. subdir))
  46. (defun libbcel-client--write-log (content filename url)
  47. "Write CONTENT to FILENAME in a temporary directory based on URL.
  48. CONTENT can be anything that can be printed with `princ'."
  49. (when content
  50. (with-temp-file (expand-file-name filename (libbcel-client--make-log-subdir url))
  51. ;; use #'format because CONTENT is not necessarily a string.
  52. (insert (format "%s" content))
  53. (ignore-errors (pp-buffer)))))
  54. (cl-defun libbcel-client-request (access-token url &rest rest &allow-other-keys)
  55. "Call `request'.
  56. URL, SUCCESS, ERROR are passed to `request'.
  57. ACCESS-TOKEN is found in the result of the OAUTH2 authentication.
  58. See `libbcel-oauth-get-access-token'."
  59. (let* ((request-log-level 'trace)
  60. (log-buffer-name (generate-new-buffer-name (format " *libbcel-client-%s*" url)))
  61. (request-log-buffer-name log-buffer-name)
  62. (log-filename (expand-file-name "request-logs" (libbcel-client--make-log-subdir url))))
  63. (apply
  64. #'request
  65. url
  66. :timeout 5
  67. :headers `(("User-Agent" . "bcel (damien@cassou.me)")
  68. ("Authorization" . ,(format "Bearer %s" access-token)))
  69. :parser #'json-read
  70. :complete (cl-function
  71. (lambda (&key data error-thrown symbol-status response &allow-other-keys)
  72. (let ((save-silently t))
  73. (with-current-buffer log-buffer-name
  74. (write-file log-filename))
  75. (libbcel-client--write-log data "data" url)
  76. (libbcel-client--write-log (cdr error-thrown) (format "error:%s" (car error-thrown)) url)
  77. (libbcel-client--write-log symbol-status (format "status:%s" symbol-status) url)
  78. (libbcel-client--write-log response "response" url))))
  79. rest)))
  80. (defun libbcel-client--get-url-from-token (access-token url &optional callback params)
  81. "Do a GET query to Basecamp 3 API at URL.
  82. If PARAMS is non-nil it should be an alist that is passed to the GET request.
  83. ACCESS-TOKEN is found in the result of the OAUTH2 authentication.
  84. See `libbcel-oauth-get-access-token'.
  85. When CALLBACK is non-nil, evaluate it with the response."
  86. (libbcel-client-request
  87. access-token
  88. url
  89. :type "GET"
  90. :params params
  91. :parser #'json-read
  92. :success (cl-function (lambda (&key data &allow-other-keys)
  93. (when callback
  94. (funcall callback data))))))
  95. (defun libbcel-client--delete-url-from-token (access-token url &optional callback)
  96. "Do a DELETE query to Basecamp 3 API at URL.
  97. ACCESS-TOKEN is found in the result of the OAUTH2 authentication.
  98. See `libbcel-oauth-get-access-token'.
  99. When CALLBACK is non-nil, evaluate it with the response."
  100. (libbcel-client-request
  101. access-token
  102. url
  103. :type "DELETE"
  104. :parser #'json-read
  105. :success (cl-function (lambda (&key data &allow-other-keys)
  106. (when callback
  107. (funcall callback data))))))
  108. (defun libbcel-client--post-url-from-token (access-token url &optional callback)
  109. "Do a POST query to Basecamp 3 API at URL.
  110. ACCESS-TOKEN is found in the result of the OAUTH2 authentication.
  111. See `libbcel-oauth-get-access-token'.
  112. When CALLBACK is non-nil, evaluate it with the response."
  113. (libbcel-client-request
  114. access-token
  115. url
  116. :type "POST"
  117. :parser #'json-read
  118. :success (cl-function (lambda (&key data &allow-other-keys)
  119. (when callback
  120. (funcall callback data))))))
  121. (defun libbcel-client--get-path-from-token (access-token account-id path &optional callback)
  122. "Execute CALLBACK with the result of the GET call to PATH.
  123. ACCESS-TOKEN can be retrieved with `libbcel-oauth-get-access-token'.
  124. ACCOUNT-ID is the first number appearing after basecamp.com in
  125. the URL when you are on the basecamp website."
  126. (libbcel-client--get-url-from-token
  127. access-token
  128. (format "https://3.basecampapi.com/%s/%s"
  129. account-id
  130. path)
  131. callback))
  132. (defun libbcel-client-get-path (path &optional callback)
  133. "Execute CALLBACK with the result of a GET call to PATH."
  134. (libbcel-oauth-get-access-token
  135. (libbcel-client--oauth-store)
  136. (lambda (access-token)
  137. (libbcel-client--get-path-from-token access-token libbcel-client-account-id path callback))))
  138. (defun libbcel-client-get-url (url &optional callback params)
  139. "Do a GET request on URL and evaluate CALLBACK with the result.
  140. If PARAMS is non-nil it should be an alist that is passed to the GET request."
  141. (libbcel-oauth-get-access-token
  142. (libbcel-client--oauth-store)
  143. (lambda (access-token)
  144. (libbcel-client--get-url-from-token access-token url callback params))))
  145. (defun libbcel-client-delete-url (url &optional callback)
  146. "Do a DELETE request on URL and evaluate CALLBACK with the result."
  147. (libbcel-oauth-get-access-token
  148. (libbcel-client--oauth-store)
  149. (lambda (access-token)
  150. (libbcel-client--delete-url-from-token access-token url callback))))
  151. (defun libbcel-client-post-url (url &optional callback)
  152. "Do a POST request on URL and evaluate CALLBACK with the result."
  153. (libbcel-oauth-get-access-token
  154. (libbcel-client--oauth-store)
  155. (lambda (access-token)
  156. (libbcel-client--post-url-from-token access-token url callback))))
  157. (provide 'libbcel-client)
  158. ;;; libbcel-client.el ends here