Emacs Lisp: Making a multi-part form PUT or POST using url-retrieve-synchronously and mm-url-encode-multipart-form-data
| emacs
I spent some time figuring out how to submit a multipart/form-data form with url-retrieve-synchronously with Emacs Lisp. It was surprisingly hard to find an example of working with multi-part forms. I had totally forgotten that I had figured something out last year: Using Emacs Lisp to export TXT/EPUB/PDF from Org Mode to the Supernote via Browse and Access. Well, I still had to spend some extra time dealing with the quirks of the PeerTube REST API. For toobnix.org, having = in the boundary didn't seem to work. Also, since I had newlines (\n) in my data, I needed to replace all of them with \r\n, which I could do with encode-coding-string and the utf-8-dos coding system. So here's an example I can use for the future:
(let* ((boundary (format "%s%d" (make-string 20 ?-) (time-to-seconds)))
(url-request-method "PUT") ; or POST
(url-request-extra-headers
(append
(list
(cons "Content-Type"
(concat "multipart/form-data; boundary=" boundary))
;; put any authentication things you need here too, like
;; (cons "Authorization" "Bearer ...")
)
url-request-extra-headers
nil))
(url-request-data
(mm-url-encode-multipart-form-data
`(("field1" .
,(encode-coding-string "Whatever\nyour value is" 'utf-8-dos)))
boundary))
(url "http://127.0.0.1")) ; or whatever the URL is
(with-current-buffer (url-retrieve-synchronously url)
(prog1 (buffer-string)
(kill-buffer (current-buffer)))))
I've also added it to my local elisp-demos notes file (see the elisp-demos-user-files variable) so that helpful can display it when I use C-h f to describe mm-url-encode-multipart-form-data.
Here I'm using it to update the video description in emacsconf-toobnix.el:
emacsconf-toobnix-update-video-description: Update the description for TALK.
(defun emacsconf-toobnix-update-video-description (talk &optional type) "Update the description for TALK. TYPE is 'talk or 'answers." (interactive (let ((talk (emacsconf-complete-talk-info))) (list talk (if (plist-get talk :qa-toobnix-url) (intern (completing-read "Type: " '("talk" "answers"))) 'talk)))) (setq type (or type 'talk)) (let* ((properties (pcase type ('answers (emacsconf-publish-answers-video-properties talk 'toobnix)) (_ (emacsconf-publish-talk-video-properties talk 'toobnix)))) (id (emacsconf-toobnix-id-from-url (plist-get talk (pcase type ('answers :qa-toobnix-url) (_ :toobnix-url))))) (boundary (format "%s%d" (make-string 20 ?-) (time-to-seconds))) (url-request-method "PUT") (url-request-extra-headers (cons (cons "Content-Type" (concat "multipart/form-data; boundary=" boundary)) (emacsconf-toobnix-api-header))) (url-request-data (mm-url-encode-multipart-form-data `(("description" . ,(encode-coding-string (plist-get properties :description) 'utf-8-dos))) boundary)) (url (concat "https://toobnix.org/api/v1/videos/" id))) (with-current-buffer (url-retrieve-synchronously url) (prog1 (buffer-string) (kill-buffer (current-buffer))))))