chore: updated doom config
654624cb
2 file(s) · +142 −0
| 48 | 48 | :nick "stevedylandev_" |
|
| 49 | 49 | :pass ,(lambda (&rest _) (my/znc-pass "emacs")))) |
|
| 50 | 50 | ||
| 51 | + | ;; ERC: same ZNC bouncer, separate client id so sessions don't collide with circe |
|
| 52 | + | (after! erc |
|
| 53 | + | (setq erc-server "138.197.115.89" |
|
| 54 | + | erc-port 6697 |
|
| 55 | + | erc-nick "stevedylandev_" |
|
| 56 | + | erc-user-full-name "Steve Simkins" |
|
| 57 | + | erc-prompt-for-password nil |
|
| 58 | + | erc-track-shorten-start 8 |
|
| 59 | + | erc-kill-buffer-on-part t |
|
| 60 | + | erc-kill-queries-on-quit t |
|
| 61 | + | erc-hide-list '("JOIN" "PART" "QUIT"))) |
|
| 62 | + | ||
| 63 | + | (defun my/erc-znc () |
|
| 64 | + | "Connect ERC to ZNC over TLS." |
|
| 65 | + | (interactive) |
|
| 66 | + | (erc-tls :server "138.197.115.89" |
|
| 67 | + | :port 6697 |
|
| 68 | + | :nick "stevedylandev_" |
|
| 69 | + | :password (my/znc-pass "erc"))) |
|
| 70 | + | ||
| 51 | 71 | (after! elfeed-org |
|
| 52 | 72 | (setq rmh-elfeed-org-files (list "~/org/elfeed.org"))) |
|
| 53 | 73 | ||
| 73 | 93 | +dashboard-widget-loaded)) |
|
| 74 | 94 | ||
| 75 | 95 | (add-to-list 'load-path "/opt/homebrew/share/emacs/site-lisp/mu/mu4e") |
|
| 96 | + | (load! "scripts/posts") |
|
| 76 | 97 | ||
| 77 | 98 | ;; Make GUI Emacs see Homebrew binaries (mbsync, msmtp, pass, gpg) |
|
| 78 | 99 | (when (memq window-system '(mac ns)) |
|
| 1 | + | ;;; posts.el --- Emacs extension for Andromeda/Posts -*- lexical-binding: t; -*- |
|
| 2 | + | ;; |
|
| 3 | + | ;; Copyright (C) 2026 Steve Simkins |
|
| 4 | + | ;; |
|
| 5 | + | ;; Author: Steve Simkins <contact@stevedylan.dev> |
|
| 6 | + | ;; Version: 0.0.1 |
|
| 7 | + | ;; Keywords: tools blogging extension |
|
| 8 | + | ;; Homepage: https://github.com/stevedylandev/andromeda |
|
| 9 | + | ;; Package-Requires: ((emacs "27.1")) |
|
| 10 | + | ;; |
|
| 11 | + | ;; This file is not part of GNU Emacs. |
|
| 12 | + | ;; |
|
| 13 | + | ;;; Commentary: |
|
| 14 | + | ;; Sends micro posts to an Andromeda/posts instance. |
|
| 15 | + | ;; Run M-x posts-compose, write the title on the first line and the |
|
| 16 | + | ;; content below it, then press C-c C-c to send. |
|
| 17 | + | ;; |
|
| 18 | + | ;;; Code: |
|
| 19 | + | ||
| 20 | + | (require 'url) |
|
| 21 | + | (require 'json) |
|
| 22 | + | (require 'subr-x) |
|
| 23 | + | (require 'auth-source-pass) |
|
| 24 | + | ||
| 25 | + | (defgroup posts nil |
|
| 26 | + | "Andromeda/posts." |
|
| 27 | + | :group 'tools) |
|
| 28 | + | ||
| 29 | + | (defcustom posts-pass-entry "andromeda/posts-api-key" |
|
| 30 | + | "Name of the `pass' entry holding the API key." |
|
| 31 | + | :type 'string |
|
| 32 | + | :group 'posts) |
|
| 33 | + | ||
| 34 | + | (defun posts--api-key () |
|
| 35 | + | "Return the API key from password-store." |
|
| 36 | + | (or (auth-source-pass-get 'secret posts-pass-entry) |
|
| 37 | + | (user-error "No pass entry `%s'" posts-pass-entry))) |
|
| 38 | + | ||
| 39 | + | (defcustom posts-endpoint "https://posts.stevedylan.dev/api/posts" |
|
| 40 | + | "URL that receives the JSON post." |
|
| 41 | + | :type 'string |
|
| 42 | + | :group 'posts) |
|
| 43 | + | ||
| 44 | + | ;;;; Sending |
|
| 45 | + | ||
| 46 | + | (defun posts (title content) |
|
| 47 | + | "Send a post with TITLE and CONTENT to `posts-endpoint' as JSON." |
|
| 48 | + | (interactive |
|
| 49 | + | (list (read-string "Title: ") |
|
| 50 | + | (read-string "Content: "))) |
|
| 51 | + | (let ((url-request-method "POST") |
|
| 52 | + | (url-request-extra-headers |
|
| 53 | + | `(("Content-Type" . "application/json; charset=utf-8") |
|
| 54 | + | ("X-API-Key" . ,(posts--api-key)))) |
|
| 55 | + | (url-request-data |
|
| 56 | + | (encode-coding-string |
|
| 57 | + | (json-serialize `(:title ,title :content ,content :status "draft")) |
|
| 58 | + | 'utf-8))) |
|
| 59 | + | (url-retrieve |
|
| 60 | + | posts-endpoint |
|
| 61 | + | (lambda (status) |
|
| 62 | + | (unwind-protect |
|
| 63 | + | (if (plist-get status :error) |
|
| 64 | + | (message "Posts error: %S" (plist-get status :error)) |
|
| 65 | + | (goto-char (point-min)) |
|
| 66 | + | (re-search-forward "\r?\n\r?\n" nil t) |
|
| 67 | + | (condition-case nil |
|
| 68 | + | (let ((resp (json-parse-buffer :object-type 'alist))) |
|
| 69 | + | (message "Posted: https://stevedylan.dev/now/%S" (alist-get 'slug resp))) |
|
| 70 | + | (error (message "Posted (non-JSON response)")))) |
|
| 71 | + | (kill-buffer (current-buffer)))) |
|
| 72 | + | nil t))) |
|
| 73 | + | ||
| 74 | + | ;;;; Compose buffer |
|
| 75 | + | ||
| 76 | + | (defvar posts-compose-mode-map |
|
| 77 | + | (let ((m (make-sparse-keymap))) |
|
| 78 | + | (define-key m (kbd "C-c C-c") #'posts-compose-submit) |
|
| 79 | + | (define-key m (kbd "C-c C-k") #'posts-compose-cancel) |
|
| 80 | + | m) |
|
| 81 | + | "Keymap for `posts-compose-mode'.") |
|
| 82 | + | ||
| 83 | + | (define-derived-mode posts-compose-mode text-mode "Posts" |
|
| 84 | + | "Compose a post. First line is the title, the rest is content. |
|
| 85 | + | \\{posts-compose-mode-map}" |
|
| 86 | + | (setq header-line-format |
|
| 87 | + | "Title on first line. C-c C-c to send, C-c C-k to cancel")) |
|
| 88 | + | ||
| 89 | + | ;;;###autoload |
|
| 90 | + | (defun posts-compose () |
|
| 91 | + | "Open a buffer to compose a post." |
|
| 92 | + | (interactive) |
|
| 93 | + | (pop-to-buffer (get-buffer-create "*posts-compose*")) |
|
| 94 | + | (erase-buffer) |
|
| 95 | + | (posts-compose-mode)) |
|
| 96 | + | ||
| 97 | + | (defun posts-compose-submit () |
|
| 98 | + | "Parse the compose buffer and send it." |
|
| 99 | + | (interactive) |
|
| 100 | + | (let* ((title (save-excursion |
|
| 101 | + | (goto-char (point-min)) |
|
| 102 | + | (string-trim |
|
| 103 | + | (buffer-substring-no-properties |
|
| 104 | + | (line-beginning-position) (line-end-position))))) |
|
| 105 | + | (content (save-excursion |
|
| 106 | + | (goto-char (point-min)) |
|
| 107 | + | (forward-line 1) |
|
| 108 | + | (string-trim |
|
| 109 | + | (buffer-substring-no-properties (point) (point-max)))))) |
|
| 110 | + | (when (string-empty-p title) |
|
| 111 | + | (user-error "Title is empty")) |
|
| 112 | + | (posts title content) |
|
| 113 | + | (quit-window t))) |
|
| 114 | + | ||
| 115 | + | (defun posts-compose-cancel () |
|
| 116 | + | "Discard the compose buffer." |
|
| 117 | + | (interactive) |
|
| 118 | + | (quit-window t)) |
|
| 119 | + | ||
| 120 | + | (provide 'posts) |
|
| 121 | + | ;;; posts.el ends here |