branch: elpa/fedi
commit 460ec9874b8344fb215bc44041ffb3beb1fd20f8
Merge: 8f0afbb5cd2 4a13306ce23
Author: marty hiatt <[email protected]>
Commit: marty hiatt <[email protected]>

    Merge branch 'dev'
---
 fedi-auth.el |  68 ++++++++++++++
 fedi-http.el | 284 ++++++++++++++++++++++++++++++++---------------------------
 fedi-post.el |  23 ++++-
 fedi.el      |   2 +-
 4 files changed, 243 insertions(+), 134 deletions(-)

diff --git a/fedi-auth.el b/fedi-auth.el
new file mode 100644
index 00000000000..9f9ce41e502
--- /dev/null
+++ b/fedi-auth.el
@@ -0,0 +1,68 @@
+;;; fedi.el --- Auth utilities -*- lexical-binding: t -*-
+
+;; Copyright (C) 2020-2023 Marty Hiatt
+;; Author: Marty Hiatt <[email protected]>
+;; Homepage: https://codeberg.org/martianh/fedi.el
+
+;; This file is not part of GNU Emacs.
+
+;; fedi-http.el is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; fedi.el is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with fedi.el.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; client/user authentication utilities
+
+;;; Code:
+
+(require 'auth-source)
+
+(defmacro fedi-auth-authorized-request (method token body
+                                               &optional unauthenticated-p)
+  "Make a METHOD request, with auth TOKEN.
+Call BODY. If UNAUTHENTICATED-P is non-nil, don't set token in the auth
+header."
+  (declare (debug 'body)
+           (indent 2))
+  `(let ((url-request-method ,method)
+         (url-request-extra-headers
+          (unless ,unauthenticated-p
+            (list (cons "Authorization"
+                        (concat "token " ,token))))))
+     ,body))
+
+(defun fedi-auth-source-get (user host &optional create)
+  "Fetch an auth source token, searching with USER and HOST.
+If CREATE, prompt for a token and save it if there is no such entry.
+Return a list of user, password/secret, and the item's save-function."
+  (let* ((auth-source-creation-prompts
+          '((secret . "%u access token: ")))
+         (source
+          (car
+           (auth-source-search :host host :user user
+                               :require '(:user :secret)
+                               ;; "create" doesn't work here!:
+                               :create (if create t nil)))))
+    (when source
+      (let ((creds
+             `(,(plist-get source :user)
+               ,(auth-info-password source)
+               ,(plist-get source :save-function))))
+        ;; FIXME: is this ok to be here?
+        (when create ;; call save function:
+          (when (functionp (nth 2 creds))
+            (funcall (nth 2 creds))))
+        creds))))
+
+(provide 'fedi-auth)
+;;; fedi-auth.el ends here
diff --git a/fedi-http.el b/fedi-http.el
index 10e3aa0eb5f..5e0d6ef1c0d 100644
--- a/fedi-http.el
+++ b/fedi-http.el
@@ -71,28 +71,29 @@
   "User-Agents to use for reverso.el requests.
 A random one is picked at package initialization.")
 
+;;; UTILS
+
 (defun fedi-http--api (endpoint &optional url ver-str)
   "Return Fedi API URL for ENDPOINT."
   (concat (or url fedi-instance-url) "/api/"
           (or ver-str fedi-http--api-version)
           "/" endpoint))
 
-(defun fedi-http--response ()
-  "Capture response buffer content as string."
-  (with-current-buffer (current-buffer)
-    (buffer-substring-no-properties (point-min) (point-max))))
-
-(defun fedi-http--response-body (pattern)
-  "Return substring matching PATTERN from `fedi-http--response'."
-  (let ((resp (fedi-http--response)))
-    (string-match pattern resp)
-    (match-string 0 resp)))
+(defun fedi-http--render-html-err (string)
+  "Render STRING as HTML in a temp buffer.
+STRING should be HTML for a 404 errror."
+  (with-temp-buffer
+    (insert string)
+    (shr-render-buffer (current-buffer))
+    (view-mode))) ; for 'q' to kill buffer and window
+;; FIXME: this is awful, it pops up also:
+;; (error ""))) ; stop subsequent processing
 
-(defun fedi-http--status ()
-  "Return HTTP Response Status Code from `fedi-http--response'."
-  (let* ((status-line (fedi-http--response-body "^HTTP/1.*$")))
-    (string-match "[0-9][0-9][0-9]" status-line)
-    (match-string 0 status-line)))
+(defun fedi-http--read-file-as-string (filename)
+  "Read a file FILENAME as a string. Used to generate image preview."
+  (with-temp-buffer
+    (insert-file-contents filename)
+    (string-to-unibyte (buffer-string))))
 
 (defun fedi-http--url-retrieve-synchronously (url &optional silent)
   "Retrieve URL asynchronously.
@@ -104,31 +105,7 @@ SILENT means don't message."
       (url-retrieve-synchronously url)
     (url-retrieve-synchronously url (or silent nil) nil fedi-http--timeout)))
 
-(defun fedi-http--triage (response success)
-  "Determine if RESPONSE was successful.
-If successful, call SUCCESS with single arg RESPONSE.
-If unsuccessful, message status and JSON error from RESPONSE."
-  (let ((status (condition-case err
-                    (with-current-buffer response
-                      (fedi-http--status))
-                  (wrong-type-argument
-                   "Looks like we got no response from the server."))))
-    (cond ((string-prefix-p "2" status)
-           (funcall success response))
-          ((string-prefix-p "404" status)
-           (message "Error %s: page not found" status))
-          (t
-           (let ((json-response (with-current-buffer response
-                                  (fedi-http--process-json))))
-             (user-error "Error %s: %s" status
-                         (or (alist-get 'error json-response)
-                             (alist-get 'message json-response))))))))
-
-(defun fedi-http--read-file-as-string (filename)
-  "Read a file FILENAME as a string. Used to generate image preview."
-  (with-temp-buffer
-    (insert-file-contents filename)
-    (string-to-unibyte (buffer-string))))
+;;; PARAMS
 
 (defun fedi-http--build-params-string (params)
   "Build a request parameters string from parameters alist PARAMS."
@@ -146,6 +123,32 @@ Used for API form data parameters that take an array."
   (cl-loop for x in array
            collect (cons param-str x)))
 
+(defun fedi-http--concat-params-to-url (url params)
+  "Build a query string with PARAMS and concat to URL."
+  (if params
+      (concat url "?"
+              (fedi-http--build-params-string params))
+    url))
+
+;;; BASIC REQUESTS
+
+(defun fedi-http--get (url &optional params silent)
+  "Make synchronous GET request to URL.
+PARAMS is an alist of any extra parameters to send with the request.
+SILENT means don't message."
+  (let ((url (fedi-http--concat-params-to-url url params)))
+    (condition-case err
+        (fedi-http--url-retrieve-synchronously url silent)
+      (t (error "I am Error. Request borked. %s"
+                (error-message-string err))))))
+
+(defun fedi-http--get-json (url &optional params silent vector)
+  "Return only JSON data from URL request.
+PARAMS is an alist of any extra parameters to send with the request.
+SILENT means don't message.
+VECTOR means return json arrays as vectors."
+  (car (fedi-http--get-response url params :no-headers silent vector)))
+
 (defun fedi-http--post (url &optional params headers json)
   "POST synchronously to URL, optionally with PARAMS and HEADERS.
 JSON means we are posting a JSON payload, so we add headers and
@@ -169,41 +172,107 @@ json-string PARAMS."
     (with-temp-buffer
       (fedi-http--url-retrieve-synchronously url))))
 
-(defun fedi-http--concat-params-to-url (url params)
-  "Build a query string with PARAMS and concat to URL."
-  (if params
-      (concat url "?"
-              (fedi-http--build-params-string params))
-    url))
+(defun fedi-http--delete (url &optional params)
+  "Make DELETE request to URL.
+PARAMS is an alist of any extra parameters to send with the request."
+  ;; url-request-data only works with POST requests?
+  (let ((url-request-method "DELETE")
+        (url (fedi-http--concat-params-to-url url params)))
+    (with-temp-buffer
+      (fedi-http--url-retrieve-synchronously url))))
 
-(defun fedi-http--get (url &optional params silent)
-  "Make synchronous GET request to URL.
+(defun fedi-http--put (url &optional params headers json)
+  "Make PUT request to URL.
 PARAMS is an alist of any extra parameters to send with the request.
-SILENT means don't message."
-  (let ((url (fedi-http--concat-params-to-url url params)))
-    (condition-case err
-        (fedi-http--url-retrieve-synchronously url silent)
-      (t (error "I am Error. Request borked. %s"
-                (error-message-string err))))))
+HEADERS is an alist of any extra headers to send with the request.
+If JSON, encode params as JSON."
+  (let* ((url-request-method "PUT")
+         (url-request-data
+          (when params
+            (if json
+                (encode-coding-string
+                 (json-encode params) 'utf-8)
+              (fedi-http--build-params-string params))))
+         (headers (when json
+                    (append headers
+                            '(("Content-Type" . "application/json")
+                              ("Accept" . "application/json")))))
+         (url-request-extra-headers
+          (append url-request-extra-headers ; auth set in macro
+                  headers)))
+    (with-temp-buffer
+      (fedi-http--url-retrieve-synchronously url))))
+
+(defun fedi-http--patch-json (url &optional params json)
+  "Make synchronous PATCH request to URL. Return JSON response.
+Optionally specify the PARAMS to send."
+  (with-current-buffer (fedi-http--patch url params json)
+    (fedi-http--process-json)))
+
+(defun fedi-http--patch (url &optional params json)
+  "Make synchronous PATCH request to URL.
+Optionally specify the PARAMS to send.
+If JSON, encode request data as JSON."
+  ;; NB: unless JSON arg, we use query params and do not set
+  ;; `url-request-data'.
+  (let* ((url-request-method "PATCH")
+         (url-request-data
+          (when (and params json)
+            (encode-coding-string
+             (json-encode params) 'utf-8)))
+         (url (if (not json)
+                  (fedi-http--concat-params-to-url url params)
+                url))
+         (headers (when json
+                    '(("Content-Type" . "application/json")
+                      ("Accept" . "application/json"))))
+         (url-request-extra-headers
+          (append url-request-extra-headers headers)))
+    (fedi-http--url-retrieve-synchronously url)))
+
+;;; RESPONSES
+
+(defun fedi-http--triage (response success &optional error-fun)
+  "Determine if RESPONSE was successful.
+If successful, call SUCCESS with single arg RESPONSE.
+If unsuccessful, message status and JSON error from RESPONSE.
+Optionally, provide an ERROR-FUN, called on the process JSON response,
+to returnany error message needed."
+  (let ((status (condition-case _err
+                    (with-current-buffer response
+                      (url-http-parse-response))
+                  (wrong-type-argument
+                   "Looks like we got no response from the server."))))
+    (cond ((and (>= status 200)
+                (<= status 299))
+           (funcall success response))
+          ((= 404 status)
+           (message "Error %s: page not found" status))
+          (t
+           (let ((json-response (with-current-buffer response
+                                  (fedi-http--process-json))))
+             (user-error "Error %s: %s" status
+                         (or (when error-fun
+                               (funcall error-fun json-response))
+                             (alist-get 'error json-response)
+                             (alist-get 'message json-response))))))))
 
 (defun fedi-http--get-response (url &optional params no-headers silent vector)
-  "Make synchronous GET request to URL. Return JSON and response headers.
+  "Make synchronous GET request to URL.
+Return JSON and response headers.
 PARAMS is an alist of any extra parameters to send with the request.
 SILENT means don't message.
 NO-HEADERS means don't collect http response headers.
 VECTOR means return json arrays as vectors."
   ;; some APIs return nil if no data, so we can't just error
+  ;; Really? but then the `with-current-buffer' call would error!
   ;; (condition-case err
-  (with-current-buffer (fedi-http--get url params silent)
-    (fedi-http--process-response no-headers vector)))
-  ;; (t (error "I am Error. Looks like server borked."))))
-
-(defun fedi-http--get-json (url &optional params silent vector)
-  "Return only JSON data from URL request.
-PARAMS is an alist of any extra parameters to send with the request.
-SILENT means don't message.
-VECTOR means return json arrays as vectors."
-  (car (fedi-http--get-response url params :no-headers silent vector)))
+  (let ((buf (fedi-http--get url params silent)))
+    (if (not buf)
+        (user-error "No response. Server borked?"))
+    (with-current-buffer buf
+      (fedi-http--process-response no-headers vector))))
+;; (t (error "I am Error. Looks like server borked"))))
 
 (defun fedi-http--process-json ()
   "Return only JSON data from async URL request.
@@ -211,16 +280,6 @@ Callback to `fedi-http--get-json-async', usually
 `fedi-tl--init*', is run on the result."
   (car (fedi-http--process-response :no-headers)))
 
-(defun fedi-http--render-html-err (string)
-  "Render STRING as HTML in a temp buffer.
-STRING should be HTML for a 404 errror."
-  (with-temp-buffer
-    (insert string)
-    (shr-render-buffer (current-buffer))
-    (view-mode))) ; for 'q' to kill buffer and window
-;; FIXME: this is awful, it pops up also:
-;; (error ""))) ; stop subsequent processing
-
 (defun fedi-http--process-response (&optional no-headers vector)
   "Process http response.
 Return a cons of JSON list and http response headers.
@@ -269,62 +328,8 @@ Callback to `fedi-http--get-response-async'."
                 (cons (car list) (cadr list))))
             head-list)))
 
-(defun fedi-http--delete (url &optional params)
-  "Make DELETE request to URL.
-PARAMS is an alist of any extra parameters to send with the request."
-  ;; url-request-data only works with POST requests?
-  (let ((url-request-method "DELETE")
-        (url (fedi-http--concat-params-to-url url params)))
-    (with-temp-buffer
-      (fedi-http--url-retrieve-synchronously url))))
-
-(defun fedi-http--put (url &optional params headers json)
-  "Make PUT request to URL.
-PARAMS is an alist of any extra parameters to send with the request.
-HEADERS is an alist of any extra headers to send with the request.
-If JSON, encode params as JSON."
-  (let* ((url-request-method "PUT")
-         (url-request-data
-          (when params
-            (if json
-                (encode-coding-string
-                 (json-encode params) 'utf-8)
-              (fedi-http--build-params-string params))))
-         (headers (when json
-                    (append headers
-                            '(("Content-Type" . "application/json")
-                              ("Accept" . "application/json")))))
-         (url-request-extra-headers
-          (append url-request-extra-headers ; auth set in macro
-                  headers)))
-    (with-temp-buffer
-      (fedi-http--url-retrieve-synchronously url))))
-
-(defun fedi-http--patch-json (url &optional params json)
-  "Make synchronous PATCH request to URL. Return JSON response.
-Optionally specify the PARAMS to send."
-  (with-current-buffer (fedi-http--patch url params json)
-    (fedi-http--process-json)))
-
-(defun fedi-http--patch (url &optional params json)
-  "Make synchronous PATCH request to BASE-URL.
-Optionally specify the PARAMS to send."
-  (let* ((url-request-method "PATCH")
-         (url-request-data
-          (when params
-            (if json
-                (encode-coding-string
-                 (json-encode params) 'utf-8)
-              (fedi-http--build-params-string params))))
-         ;; (url (fedi-http--concat-params-to-url base-url params)))))
-         (headers (when json
-                    '(("Content-Type" . "application/json")
-                      ("Accept" . "application/json"))))
-         (url-request-extra-headers
-          (append url-request-extra-headers headers)))
-    (fedi-http--url-retrieve-synchronously url)))
-
- ;; Asynchronous functions
+
+;;; ASYNCHRONOUS FUNCTIONS
 
 (defun fedi-http--get-async (url &optional params callback &rest cbargs)
   "Make GET request to URL.
@@ -364,6 +369,23 @@ Then run function CALLBACK with arguements CBARGS."
     (with-temp-buffer
       (url-retrieve url callback cbargs))))
 
+;;; BASIC AUTH REQUEST
+
+(defun fedi-http--basic-auth-request (req-fun url user
+                                              &optional pwd &rest args)
+  "Do a BasicAuth request.
+Call REQ-FUN, a request function, on URL, providing USER and password
+PWD.
+ARGS is any addition arguments for REQ-FUN, after the URL.
+REQ-FUN can be a fedi.el request function such as `fedi-http--post'."
+  (let* ((pwd (or pwd (read-passwd (format "Password: "))))
+         (auth (base64-encode-string
+                (format "%s:%s" user pwd)))
+         (url-request-extra-headers
+          (list (cons "Authorization"
+                      (format "Basic %s" auth)))))
+    (apply req-fun url args)))
+
 ;; ;; TODO: test for curl first?
 ;; (defun fedi-http--post-media-attachment (url filename caption)
 ;;   "Make POST request to upload FILENAME with CAPTION to the server's media 
URL.
diff --git a/fedi-post.el b/fedi-post.el
index a6a5bd50d8a..1a55724c6d9 100644
--- a/fedi-post.el
+++ b/fedi-post.el
@@ -586,12 +586,27 @@ Added to `after-change-functions'."
           (fill-region (prop-match-beginning prop)
                        (point)))))))
 
+(defun fedi-post--render-reply-region-str (str)
+  "Refill STR and prefix all lines with >, as reply-quote text."
+  (with-temp-buffer
+    (insert str)
+    ;; unfill first:
+    (let ((fill-column (point-max)))
+      (fill-region (point-min) (point-max)))
+    ;; then fill:
+    (fill-region (point-min) (point-max))
+    ;; add our own prefix, pauschal:
+    (goto-char (point-min))
+    (save-match-data
+      (while (re-search-forward "^" nil t)
+        (replace-match " > ")))
+    (buffer-substring-no-properties (point-min) (point-max))))
 
 
 ;;; COMPOSE BUFFER FUNCTION
 
 (defun fedi-post--compose-buffer
-    (&optional edit major minor prefix type capf-funs fields init-text)
+    (&optional edit major minor prefix type capf-funs fields init-text 
reply-text)
   "Create a new buffer to capture text for a new post.
 EDIT means we are editing an existing post, not composing a new one.
 MAJOR is the major mode to enable.
@@ -666,7 +681,11 @@ string, the other elements should be symbols."
       (cl-pushnew #'fedi-post-fontify-body-region after-change-functions))
     (when init-text
       (insert init-text)
-      (delete-trailing-whitespace))))
+      (delete-trailing-whitespace))
+    (when reply-text
+      (insert "\n"
+              (fedi-post--render-reply-region-str reply-text)
+              "\n"))))
 
 (defun fedi-post-fontify-body-region (&rest _args)
   "Call `font-lock-fontify-region' on post body.
diff --git a/fedi.el b/fedi.el
index 3cf369e60ac..ee24ce0cf89 100644
--- a/fedi.el
+++ b/fedi.el
@@ -234,7 +234,7 @@ PROP is the text property to search for."
         (prog1 nil ;; return nil if nothing (so we can use in or clause)
           (message "Nothing else here."))
       (goto-char (car next-range))
-      (if-let ((hecho (fedi--property 'help-echo)))
+      (if-let* ((hecho (fedi--property 'help-echo)))
           (message "%s" hecho)))))
 
 (defun fedi-previous-tab-item ()

Reply via email to