tekuti-el/tekuti.el

168 lines
6.2 KiB
EmacsLisp
Raw Normal View History

2021-06-04 08:26:47 +02:00
;;; tekuti.el --- Tekuti interaction library -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Tom Willemse
;; Author: Tom Willemse <chelys@data>
;; Keywords: comm
;; Package-Requires: (org)
2022-03-24 02:58:00 +01:00
;; Version: 0.1.0
2021-06-04 08:26:47 +02:00
;; This program 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.
;; This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; A simple module to help post things to your tekuti blog. Youll want to
;; customize the tekuti-host variable to point to your own blog. You should be
;; asked for your credentials, which should then be stored in ~/.authinfo.gpg.
;; Currently you should add these headers to your org document that you want to
;; send up:
;; #+TITLE: SOME TITLE
;; #+DATE: <2021-06-03 Thu 22:45>
;; #+TAGS: comma,separated
2023-12-08 08:27:21 +01:00
;; #+COMMENTS: on
2021-06-04 08:26:47 +02:00
;; #+COMMENTS_DATE:
;; #+STATUS: publish
;; The main entry point is tekuti-send. Call this from an org-mode buffer to
;; export the buffer to HTML and send it up to your blog.
;;; Code:
(require 'org)
(defvar tekuti-host "http://127.0.0.1:8080"
"The host where tekuti is running.
This should be the base URL of your blog. Where adding /admin
will get you into the admin area of your blog. This shouldn't end
with a /.")
(defun tekuti--format-date (&optional date)
2021-06-25 06:09:22 +02:00
"Format DATE into a date that tekuti understands."
(format-time-string "%a, %d %b %Y %H:%M:%S %Z"
(unless (null date)
(date-to-time date))
2021-06-25 06:09:22 +02:00
t))
(defun tekuti--parse-data-keyword (pair)
"Ensure that both the key and value in PAIR match what tekuti needs."
(pcase pair
(`("DATE" ,date)
(list "DATE" (tekuti--format-date date)))
(`("COMMENTS_DATE" ,date)
(list "COMMENTS-CLOSED-DATE"
(and (not (string-empty-p date))
(tekuti--format-date date))))
(`("STATUS" ,status)
(let ((accepted-values '("publish" "draft" "private")))
(if (member status accepted-values)
pair
(error "Unknown post status: %s; Accepted values are: %s"
status accepted-values))))
2023-12-08 08:27:21 +01:00
(`("COMMENTS" ,status)
(if (string= status "on")
pair
(error "Unknown value for comments: %s; Can only be on or not present."
status)))
(_ pair)))
2021-06-04 08:26:47 +02:00
(defun tekuti-build-data-from-org ()
"Parse the current org buffer to collect the necessary metadata."
(let ((defaults `(("TITLE")
("DATE" ,(tekuti--format-date))
("TAGS")
("STATUS")
2023-12-08 08:27:21 +01:00
("COMMENTS")
("COMMENTS-CLOSED-DATE")
("BODY")))
(data (append
(mapcar
#'tekuti--parse-data-keyword
2023-12-08 08:27:21 +01:00
(org-collect-keywords '("TITLE" "TAGS" "STATUS" "COMMENTS" "DATE" "COMMENTS_DATE")))
(list
(list "BODY"
(with-current-buffer (org-html-export-as-html nil nil nil t)
(buffer-substring-no-properties (point-min) (point-max))))))))
2023-12-08 08:27:21 +01:00
(seq-remove
(lambda (pair) (and (string= "COMMENTS" (car pair))
(null (cdr pair))))
(mapcar (lambda (item)
(let ((key (car item)))
`(,key ,@(or (alist-get key data nil nil #'string=)
(cdr item)))))
defaults))))
2021-06-04 08:26:47 +02:00
(defun tekuti-form-encode-pair (key value)
(concat (url-hexify-string key) "=" (url-hexify-string value)))
(defun tekuti-form-encode-data (data)
(mapconcat
(lambda (pair)
(tekuti-form-encode-pair (downcase (car pair)) (cadr pair)))
data
"&"))
(defun tekuti--send-finished (status buffer args updatep)
(pcase-exhaustive status
(`(:redirect ,url . ,_)
(save-excursion
(with-current-buffer buffer
(goto-char (point-min))
(search-forward "\n\n")
(backward-char)
2023-12-08 08:27:21 +01:00
(let ((data (org-collect-keywords '("TITLE" "TAGS" "STATUS" "COMMENTS" "DATE" "COMMENTS_DATE"))))
(mapc (lambda (item)
(let ((key (car item))
(value (cadr item)))
(when (and (not (string= key "BODY"))
(null (alist-get key data nil nil #'string=))
(not (null value)))
(insert "#+" key ": " value "\n"))))
args))
(unless (tekuti--get-update-url)
(insert "#+UPDATE_URL: "
(string-replace "/posts/"
"/modify-post/"
(car (url-path-and-query
(url-generic-parse-url url))))
"\n"))))
(message "%s post: %s" (if updatep "Updated" "Created new") url))
(`(:error (error http ,code) . ,_)
(message "Failed to %s post: %d" (if updatep "update" "create") code))))
(defun tekuti--get-update-url ()
(save-excursion
(goto-char (point-min))
(car (map-elt (org-collect-keywords '("UPDATE_URL")) "UPDATE_URL"))))
2021-06-04 08:26:47 +02:00
;;;###autoload
(defun tekuti-send ()
(interactive)
(let* ((data (tekuti-build-data-from-org))
(url-request-method "POST")
(url-request-extra-headers
`(("Content-Type" . "application/x-www-form-urlencoded")))
(url-request-data (tekuti-form-encode-data data))
(previous-update-url (tekuti--get-update-url))
(updatep (not (null previous-update-url))))
(url-retrieve (format "%s%s" (string-trim-right tekuti-host "/")
(or previous-update-url
2023-04-12 10:00:48 +02:00
"/admin/new-post"))
#'tekuti--send-finished
(list (current-buffer) data updatep))))
2021-06-04 08:26:47 +02:00
(provide 'tekuti)
;;; tekuti.el ends here