efinger.el/README.md
Andros Fenollosa 324cef61d5 Add efinger: a Finger client and .plan feed reader
Efinger is a client for the Finger protocol (RFC 1288) with an
Elfeed-inspired reader.  Configure a list of accounts once and browse
their .plan files from a two-pane interface: an account list on one
side and the fingered content on the other.

Move with n/p to preview each account, RET to read one in full, l to go
back to the list and q to close both panes.  M-x efinger-finger fingers
a single account on demand.

Connections are asynchronous (:nowait) with a configurable timeout, so
Emacs never freezes when a host resolves but never answers on port 79.

The finger core is forked from GNU Emacs' net-utils.el (by Peter
Breton) and rewritten around an asynchronous connection.
2026-08-18 11:16:05 +02:00

206 lines
9.1 KiB
Markdown

# Efinger for Emacs
A client for the [Finger protocol](https://www.rfc-editor.org/rfc/rfc1288.html) (RFC 1288) with an [Elfeed](https://github.com/skeeto/elfeed)-inspired reader for Emacs.
Requires Emacs 28.1 or later.
Finger, born at Stanford in 1971, is arguably the first social network: your whole profile lives in a plain-text `.plan` file that anyone can read with `finger you@your-host`. John Carmack famously kept his DOOM/Quake development diary this way. Efinger lets you keep a list of accounts and browse their `.plan` files from a two-pane interface, the same way you follow feeds in Elfeed. For the story behind the protocol, see [Finger: la red social de 1971](https://andros.dev/blog/79c99190/finger-la-red-social-de-1971-que-nunca-llego-a-morir/).
## How it works
```
Emacs Finger servers (TCP 79)
+---------------------+
| *efinger* | n / p +----------------------+
| ------------------ |------ move -->| happynetbox.com |
| >random@happynetbox | | thebackupbox.net |
| Carmack diary | RET / l | your own fingerd |
| ring@thebackupbox |<-- enter/back | |
+----------+----------+ +----------+-----------+
| |
v preview | async, :nowait
+---------------------+ |
| *efinger .plan* |<-------------------------+
| the fingered text |
+---------------------+
```
1. You keep a list of finger accounts in `efinger-accounts`.
2. `M-x efinger` opens the account list next to a content pane.
3. Moving with `n` / `p` fingers the account under point and previews its `.plan` in the other pane, asynchronously so Emacs never blocks.
4. `RET` jumps into the content, `l` goes back to the list, `q` closes both panes.
## Buffers
### Account list + content (`M-x efinger`)
```
Efinger — n/p move · RET open · g refresh · q quit
┌────────────────────────┬───────────────────────────────────────┐
│ random@happynetbox.com │ johnc@idsoftware.com │
│ Carmack diary │ ───────────────────────────────────── │
│ ring@thebackupbox.net │ │
│ me@happynetbox.com │ -------------------------------------- │
│ │ John Carmack's .plan for Aug 18, 1997 │
│ │ -------------------------------------- │
│ │ │
│ │ I get asked about the DOOM source code │
│ │ every once in a while, so here is a │
│ │ full status update: │
│ │ ... │
└────────────────────────┴───────────────────────────────────────┘
```
### Single account (`M-x efinger-finger`)
```
.plan — l back · n/p next/prev · g refresh · q quit
random@happynetbox.com
──────────────────────
Emily Carter
Marine biologist. Coffee, kayaks and old maps.
--- 2026-08-14 ---
Back from sampling on the coast. Three jars of plankton to review.
```
## Features
- **Account list**: keep your finger accounts in `efinger-accounts` and browse them in a dedicated buffer, the way Elfeed lists feeds.
- **Live preview**: move with `n` / `p` and the `.plan` under point is fingered and shown in the content pane automatically.
- **Two-pane reader**: `RET` enters the content pane to read and scroll, `l` returns to the list, `q` closes both.
- **Asynchronous connections**: connections use `:nowait`, so Emacs never freezes even when a host resolves but never answers on port 79 (the common case today).
- **Connection timeout**: a stalled connection is abandoned after `efinger-connection-timeout` seconds with a clear message, instead of hanging.
- **Graceful errors**: refused or timed-out connections are reported in the content pane, never as a backtrace.
- **One-off finger**: `M-x efinger-finger` fingers a single `user@host` in its own buffer.
- **Configurable layout**: list on the left and content on the right (`horizontal`), or list on top and content below (`vertical`).
- **Ports and listing**: targets accept an optional `:port`; omitting the user (`host` or `@host`) asks the server to list every user.
- **UTF-8**: replies are decoded as UTF-8, so accents and box drawing render correctly.
## Keymap
### Account list (`efinger-list-mode`)
| Key | Description |
|-------|------------------------------------------|
| `n` | Move to next account and preview it |
| `p` | Move to previous account and preview it |
| `RET` | Enter the content pane for this account |
| `g` | Re-finger the account at point |
| `G` | Rebuild the list from `efinger-accounts`|
| `q` | Close both panes |
### Content pane (`efinger-plan-mode`)
| Key | Description |
|-------|------------------------------------------|
| `l` | Back to the account list |
| `n` | Preview next account (stay in content) |
| `p` | Preview previous account (stay in content)|
| `g` | Re-finger the current account |
| `q` | Close both panes |
| `SPC` / `DEL` | Scroll the content up / down |
## Installation
### MELPA
```
M-x package-install RET efinger RET
```
### use-package with :vc (Emacs 29+)
```elisp
(use-package efinger
:vc (:url "https://git.andros.dev/andros/efinger.el"
:rev :newest)
:config
(setq efinger-accounts
'("random@happynetbox.com"
("Backup Box ring" "ring@thebackupbox.net"))))
```
### use-package with :load-path
For manual installation or Emacs < 29:
```elisp
(use-package efinger
:load-path "/path/to/efinger.el"
:config
(setq efinger-accounts
'("random@happynetbox.com"
("Backup Box ring" "ring@thebackupbox.net"))))
```
### Manual
Clone the repository and place the file in a directory on your `load-path`:
```sh
git clone https://git.andros.dev/andros/efinger.el.git
```
Then add to your init file:
```elisp
(add-to-list 'load-path "/path/to/efinger.el")
(require 'efinger)
(setq efinger-accounts
'("random@happynetbox.com"
("Backup Box ring" "ring@thebackupbox.net")))
```
## Usage
1. Set `efinger-accounts` in your init file. Each entry is either a
`"[user@]host[:port]"` string or a `(LABEL "[user@]host[:port]")` list.
2. Run `M-x efinger` to open the reader.
3. Move with `n` / `p` to preview each account, `RET` to read one in full,
`l` to return to the list and `q` to quit.
4. Or run `M-x efinger-finger` to finger a single account on demand.
Public servers you can try today: `random@happynetbox.com`,
`benbrown@happynetbox.com` and `ring@thebackupbox.net`.
## Customization
Run `M-x customize-group RET efinger RET` to list all available options.
Key options:
- `efinger-accounts`: the accounts shown in the reader.
- `efinger-port` (default `79`): default finger TCP port.
- `efinger-connection-timeout` (default `10`): seconds before a stalled
connection is abandoned; `nil` waits indefinitely.
- `efinger-forward-host` (default `nil`): send `user@host` instead of just
`user` (finger forwarding, for old daemons).
- `efinger-layout` (default `horizontal`): `horizontal` (list left, content
right) or `vertical` (list top, content bottom).
- `efinger-list-width` (default `40`): list width in columns (horizontal).
- `efinger-list-height` (default `12`): list height in lines (vertical).
## The Finger protocol
Finger is minimal: the client opens a TCP connection to port 79 of the server,
sends a single line (the user name, or an empty line to list everyone)
terminated by CRLF, and the server replies with plain text and closes the
connection. No encryption, no headers, no sessions. You can speak it by hand:
```sh
echo "random" | nc happynetbox.com 79
```
## Contributing
Contributions are welcome! Please see the [contribution guidelines](https://git.andros.dev/andros/contribute) for instructions on how to submit issues or pull requests.
## License
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/](https://www.gnu.org/licenses/).