Skip to content

Instantly share code, notes, and snippets.

@TuringMachinegun
Created September 7, 2020 14:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TuringMachinegun/dde781e5f6667e373db5423f2517ae93 to your computer and use it in GitHub Desktop.
Save TuringMachinegun/dde781e5f6667e373db5423f2517ae93 to your computer and use it in GitHub Desktop.
Post markdown with images to micro.blog in emacs
(require 'request)
(setq mb-emacs-app-token "123456789") ;; create this in account -> app tokens -> edit apps
(setq mb-micropub-endpoint "https://micro.blog/micropub")
(setq mb-destination-address "https://your.micro.blog")
(setq mb-image-upload-timeout 20) ;; seconds
(defun mb-get-media-endpoint ()
(cdr (assoc "media-endpoint"
(let (result)
(request
mb-micropub-endpoint
:params '(("q" . "config"))
:type "GET"
:sync t
:timeout: 10
:parser 'json-read
:headers `(("Content-Type" . "application/json")
("Authorization".,(format "Bearer %s" mb-emacs-app-token)))
:complete (cl-function
(lambda (&key data &allow-other-keys)
(setq result data))))
(if result
result
(error "Can't get media endpoint")))
#'string=)))
(defun mb-upload-image (img-path media-endpoint)
"Post inline images."
(cdr (assoc "url"
(let ((result))
(request
(concat media-endpoint
(if (boundp 'mb-destination-address) (concat "?mp-destination=" mb-destination-address)))
:type "POST"
:files `(("file" . ,img-path))
:headers `(("Content-Type" . "multipart/form-data")
("Authorization".,(format "Bearer %s" mb-emacs-app-token)))
:sync t
:timeout mb-image-upload-timeout
:parser 'json-read
:success (cl-function
(lambda (&key data &allow-other-keys)
(setq result data))))
(if result
result
(error "Error in uploading.")))
#'string=)))
(defun mb-markdown-upload-images-substitute-links ()
"Upload images to micro.blog and substitute their local links with the upload locations."
(interactive)
(save-excursion
(save-restriction
(let ((media-endpoint (mb-get-media-endpoint)))
(widen)
(goto-char (point-min))
(while (re-search-forward markdown-regex-link-inline nil t)
(let ((start (match-beginning 0))
(imagep (match-beginning 1))
(end (match-end 0))
(file (match-string-no-properties 6)))
(when (and imagep
(not (zerop (length file))))
(when (file-exists-p file)
(let* ((abspath (if (file-name-absolute-p file)
file
(concat default-directory file)))
(img-upload-url (save-match-data (mb-upload-image abspath media-endpoint))))
(replace-match img-upload-url t t nil 6))))))))))
(defun mb-post-buffer ()
"Post current buffer to micro.blog (possibly as draft)."
(interactive)
(if (yes-or-no-p "Are you sure you want to post this?")
(save-restriction
(widen)
(let ((buffer-contents (buffer-substring-no-properties (point-min) (point-max)))
(mb-post-name (read-string "Enter post name (leave empty if none):"))
(mb-post-status `(post-status . [,(if (yes-or-no-p "Post as draft?") "draft" "published")])))
;; copy content of current buffer to new buffer,
;; then upload eventual linked images, substitute links with the upload locations, send buffer to microblog
(with-current-buffer (generate-new-buffer "post2mb")
(goto-char (point-min))
(insert buffer-contents)
(goto-char (point-min))
(mb-markdown-upload-images-substitute-links)
(request
(concat mb-micropub-endpoint
(if (boundp 'mb-destination-address) (concat "?mp-destination=" mb-destination-address)))
:type "POST"
:data (json-encode `((type . ["h-entry"])
(properties
(content . [,(buffer-substring-no-properties (point-min) (point-max))])
(name . [,mb-post-name]) ,mb-post-status)))
:headers `(("Content-Type" . "application/json")
("Authorization".,(format "Bearer %s" mb-emacs-app-token)))
:success (cl-function
(lambda (&key data &allow-other-keys)
(message "Success.")))))))))
@djwaix
Copy link

djwaix commented Jun 11, 2021

I am absolutely loving this function.
I've used Emacs off-and-on for awhile, but I'm no Lisp programmer. And this posting to micro.blog is something that's keeping me active in Emacs.

I was hoping I might be able to get your help in making this function a little more "secure" by not setting our app-token in plain text.

By using .authinfo.gpg I am able to set my token in an encrypted file.
This function will allow me to set my token as a variable (as you define it: mb-emacs-app-token):

(setq mb-emacs-app-token (let* ((creds (auth-source-search :host "micro.blog"))
                                (token (plist-get (car creds) :secret))
                                )
                           (funcall token)
                           ))

However, that sets my token as a plain text variable that can be seen in a buffer throughout all of Emacs.

I'm thinking that using this inside of your function should work:

(let* ((creds (auth-source-search :host "micro.blog"))
       (token (plist-get (car-creds) :secret))
       )
  (funcall token)
  )

If so, this would allow me (and anyone else) to more safely call the token. I've just not been able to figure out how or where this could fit into your code.

What are you thoughts on this?

@TuringMachinegun
Copy link
Author

Hi Thadeej, I'm also no Lisp programmer, so let's see if I'm of any help!

What about if instead of setting the token as a variable, you create a function which, when called, returns the token?
Something like this (didn't test it):

(defun get-mb-emacs-app-token ()
   "Return mb token from .authinfo.gpg"
  (plist-get (car (auth-source-search :host "micro.blog")) :secret)
)

Then the headers section in the requests just becomes

                  :headers  `(("Content-Type" . "application/json")
                              ("Authorization".,(format "Bearer %s" (get-mb-emacs-app-token))))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment