Automatically Downloading Gemini Files with Emacs and Elpher (publ. 2025-02-17)
My go-to tool for automatically downloading files over Gemini protocol has been the gmni command line client. I've very glad to gmni exists, but it bothered me that I didn't have a way to do using just Elisp code. Naturally, I wondered if Elpher made available an API for this. The short answer to that is that there is no convenient Elpher function dedicated just to that purpose, but enough API tools are available to put together your own function. Working on it over a few lunch breaks, I came up with this function:
(defun get-gemini-with-elpher (url &optional file)
"Uses some Elpher API to fetch the file at URL through the gemini \
protocol. If a FILE is specified, then save the fetched file to that \
FILE. If FILE is not specified, the data is saved in the buffer \
*get-gemini*, erasing any contents already in that buffer. In either \
case, the three part response object is returned.
Currently, the implementation only handles gemini responses 20-29, \
i.e., a simple response returning data. All other responses will cause \
an unhandled-code exception to be thrown."
(let ((reply-components nil))
(save-excursion
(elpher-process-cleanup)
(get-buffer-create "*elpher*")
(let* ((cleaned-url (string-trim url))
(address (elpher-address-from-url cleaned-url)))
(setq get-gemini-ready nil)
(with-current-buffer (get-buffer-create "*get-gemini*")
(erase-buffer))
(elpher-get-host-response address 1965
(concat
cleaned-url
"\r\n")
(lambda (s)
(let ((components (elpher-parse-gemini-response s)))
(setq reply-components components))
(setq get-gemini-ready t))
'gemini))
(while (not get-gemini-ready)
(sleep-for 0.1))
(pcase (first reply-components)
((or "20" "21" "22" "23" "24" "25" "26" "27" "28" "29")
(if file
(let ((coding-system-for-write 'binary))
(write-region (third reply-components) nil file))
(with-current-buffer
(get-buffer-create "*get-gemini*")
(erase-buffer)
(insert (third reply-components)))))
(_ (throw 'unhandled-code (first reply-components))))
reply-components)))
It is a bit limited because it doesn't deal with certificates or redirects. I would also need to modify this further if I wanted to be able to send input along with the request, which is possible in Gemini. But otherwise it should be able to download any file that is available through the Gemini protocol.
Note, however, that the function (indirectly) modifies the *elpher* buffer, so you do not want to run this function while you are browsing with Elpher, at least not from within the same Emacs instance.
Hat tip to user "joneworlds" on the Elpher mailing list, who sent me a similar function for the gopher protocol, to look at. Elpher handles both gopher and gemini protocols.
Copyright
The elisp code above is made available under the same license as GNU Emacs:
GNU Emacs 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.
GNU Emacs 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 GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
This article is © 2025 by Christopher Howard is licensed under Attribution-ShareAlike 4.0 International.
CC BY-SA 4.0 Deed