chore: updated doom config 654624cb
Steve · 2026-10-06 07:26 2 file(s) · +142 −0
doom/config.el +21 −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))
doom/scripts/posts.el (added) +121 −0
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