šŸ’¾ Archived View for tilde.pink ā€ŗ ~jsv ā€ŗ gemlog ā€ŗ fun_with_elpher.gmi captured on 2024-05-10 at 13:44:45. Gemini links have been rewritten to link to archived content

View Raw

More Information

ā¬…ļø Previous capture (2023-01-29)

-=-=-=-=-=-=-

Exploratory Elpher programming

As a newcomer to the gopher/gemini world, Iā€™m still exploring it, trying to find the corners I have never visited before, finding old articles and so onā€¦ And Iā€™m trying various clients as well. I really like Lagrange, and I use it on my android tablet, but on desktop I find myself appreciating Elpher more and more. One reason is that as a emacs user I like it when I can do something without leaving emacs. And the otherā€¦ well, itā€™s more fun. I always liked those interactive environments like lisp or forth systems, when I can tinker with things as I go. I havenā€™t had the opportunity to use them much in recent years, and my lisp-fu is a bit rusty, but I still enjoy it. Some idea comes to my mind and I can try it instantly. The resulting code might be not the most elegant that I ever written, and I do not test it against all the edge cases, the idea itself might be bad, nobody else in the world might need this functionalityā€¦ but who cares but myself?

Some examples.

Going up and around

Not that long ago, there was a discussion about ā€œUp Buttonā€ in gemini clients:

Up Buttons for exploring capsules

Elpher has ā€˜elpher-go-currentā€™ command that allows to edit current url easily enough, but itā€™s not quite the same thing.

Implementing the same thing is not hard, although gopher always wants special treatment:

(defun my-elpher-go-up ()
  "Tries to guess the address of the parent page and goes there."
  (interactive)
  (let ((address (copy-seq (elpher-page-address elpher-current-page))))
    (if (string-equal (url-type address) "gopher")
        (when (string-match "\\(/.\\)\\(.*\\)/.+" #1=(url-filename address))
          (setf #1# (format "/1%s" (match-string 2 #1#))))
      (when (string-match "\\(.*\\)/.+" #1=(url-filename address))
        (setf #1# (match-string 1 #1#))))
     (elpher-go (elpher-address-to-url address))))

Itā€™s more than a bit hacky, and not very smart, resulting sometimes in addresses that not open but it works most of the time, and other ā€œup buttonā€ implementations that Iā€™ve seen are not much more reliable. Itā€™s good enough for me so far.

Often enough, people who have a gemini capsule, have a gopher hole on the same host as well. So, as a variation of the same theme:

(defun my-elpher-switch-protocol ()
  "Tries switching between gemini and gopher versions of the same site"
  (interactive)
  (let* ((address (elpher-page-address elpher-current-page))
         (new-path (if (string-match "\\(/~.*?\\(?:/\\|$\\)\\)" #1=(url-filename address))
		       (match-string 1 #1#)
		     "/")))
    (elpher-go
      (format (if (string-equal (url-type address) "gemini")
		   "gopher://%s/1%s"
		 "gemini://%s%s")
	      (url-host address)
	      new-path))))

This works surprisingly well, and it led me to interesting places much more often than I expected.

Client certificates and the cache

Elpher supports client certificates, both ephemeral and permanent, but it doesnā€™t store long-term association between capsules and certs. It stores the current cert in a buffer-local variable and it lasts as long as the buffer exists, if you close it, youā€™ll have to select the certificate once again.

Iā€™m lazy, and the simplest solution that comes to my mind is to write an advice function that selects the certificate for me on the sites that I visit often:

(defvar my-elpher-certificate-alist
  '(("astrobotany" . "my-bonanist-cert")
    ("/station\\|spellbinding" . "jsv")))

(defun my-elpher-current-url ()
  "Returns an url for the current elpher page"
  (elpher-address-to-url
    (elpher-page-address elpher-current-page)))

(defun my-elpher-consult-certificate-alist ()
  (when-let (matching-cert
              (alist-get (my-elpher-current-url) my-elpher-certificate-alist
                          nil nil #'string-match-p))
    (elpher-get-existing-certificate matching-cert)))

(add-function
 :before-until
 (symbol-function 'elpher-choose-client-certificate)
 #'my-elpher-consult-certificate-alist)

ā€™elpher-choose-client-certificateā€™ is a complex function that can do a lot of things but in the end it sets the certificate, so when I know what cert I want, I can simply set in myself and do not call the rest.

So, now I can go spellbinding anytime and it will recognize me. But itā€™s not very playable in elpher, as elpher caches everything and spellbindingā€™s cgi links are not meant to be cached. Well, I can make an exception for them:

(defvar my-elpher-cache-exceptions
  '("game/cgi"))

(defun my-maybe-cache-content (cache-fn address content)
  (funcall
   cache-fn address
   (if (find (elpher-address-to-url address) my-elpher-cache-exceptions
             :test (lambda (s r) (string-match-p r s)))
       nil content)))

(add-function
 :around
 (symbol-function 'elpher-cache-content)
 #'my-maybe-cache-content)

And now I can play spellbinding in emacs without problems.

See? Itā€™s fun! For some definition of fun, at least šŸ˜‰