Directory listing for: qdb
DIR Parent directory (..)
TEXT README.txt (13.1KB, 2026-05-21):
qdb -- a Gopher <-> IRC quote database
=============================================================
qdb bridges IRC and gopherspace. In #main on irc.someodd.zip you
type
.qdb 10
and the last 10 lines of channel buffer are saved forever as a
plain-text file; oddbot replies in-channel with the gopher
permalink. In gopherspace those snapshots are browsable as text or
rendered as a Microsoft Comic Chat strip.
The directory of .txt files IS the database -- no schema, no SQLite.
Each file is self-contained: a setext header block then the raw
transcript.
------------------------------------------------------------
Directory contents
------------------------------------------------------------
qdbviewer.lhs the gopher applet (read side). Literate
Haskell, run by Venusia's .lhs script-
extension.
qdb-watch.sh capture side: tails ii's channel `out` file,
writes quotes/<id>.txt on `.qdb`, announces
the permalink back
qdb-logprune.sh weekly trim of ii's append-only `out` logs
qdb-comic render shim: a snapshot -> a Comic Chat strip
PNG via Spittoon. Called by qdbviewer.lhs.
comic-config.yaml Spittoon config used by qdb-comic
spittoon-compat.rb Ruby compatibility shim that lets Spittoon
(2005-era) run on modern RMagick + Ruby 3.x
qdb.env deployment config for the systemd units
systemd/ qdb-stunnel / qdb-ii / qdb-watch / qdb-logprune
.service and .timer files
stunnel/qdb.conf TLS tunnel config (only used if the IRC server
is remote --- see setup section)
setup.sh installs the systemd user units
README.txt this file
quotes/ data dir: <id>.txt snapshots, cached
<id>.comic.png renders, and a .counter file
One level up, ../qdb.bcard is the directory sidecar shown when you
browse the /applets menu. (Subdirectory applets get exactly one
.bcard, at the parent level --- not another one inside.)
------------------------------------------------------------
The .qdb command (IRC side)
------------------------------------------------------------
.qdb save the last 10 lines of channel buffer
.qdb N save the last N lines (1..50)
oddbot replies with the gopher permalink. The buffer holds the last
50 chat lines the watcher has seen since it started -- joins, parts,
and other `-!-` server notices are excluded, so a snapshot is
conversation, not channel noise.
------------------------------------------------------------
Endpoints (gopher side)
------------------------------------------------------------
`$SCRIPT` is wherever routes.toml mounts qdbviewer.lhs, e.g.
/applets/qdb/qdbviewer.lhs
$SCRIPT
landing -- latest quotes, browse, random
$SCRIPT/browse
full index, newest first, plus a link to the raw
quotes/ directory
$SCRIPT/random
a random quote's menu
$SCRIPT/q/<id>
per-quote menu: read-as-text / Comic Chat strip /
raw .txt, plus a bookmarkable permalink
$SCRIPT/q/<id>/txt
the quote as plain gopher text
$SCRIPT/q/<id>/comic
the quote as a Microsoft Comic Chat strip (PNG). Rendered
lazily on first request and cached beside the .txt as
<id>.comic.png; later requests are served straight from
the cached file by Venusia's [[files]] block.
`<id>` is always all-digits; anything else is rejected.
------------------------------------------------------------
From the command line
------------------------------------------------------------
curl gopher://gopher.someodd.zip/1/applets/qdb/qdbviewer.lhs
curl gopher://gopher.someodd.zip/1/applets/qdb/qdbviewer.lhs/q/0001
curl gopher://gopher.someodd.zip/0/applets/qdb/qdbviewer.lhs/q/0001/txt
curl gopher://gopher.someodd.zip/I/applets/qdb/qdbviewer.lhs/q/0001/comic > 0001.png
------------------------------------------------------------
Snapshot file format (quotes/<id>.txt)
------------------------------------------------------------
qdb #0042
=========
saved-by: alice
channel: #main
network: irc.someodd.zip
date: 2026-05-14T17:30:00Z
lines: 10
2026-05-14 17:29 <alice> it compiled on my machine i swear
2026-05-14 17:29 <bob> your machine is a liar
Title line + setext underline, a block of `key: value` headers, a
blank line, then the verbatim transcript. The parser is tolerant:
missing headers render as "?", a malformed file still shows its
body.
------------------------------------------------------------
Setting it up
------------------------------------------------------------
qdb is two halves: a gopher applet (no setup beyond dropping the
files in place) and an always-on IRC capture daemon (three small
systemd user services).
1. FILES
Put this directory at applets/qdb/ under Venusia's gopher root,
with ../qdb.bcard beside it. The applet is live immediately --
Venusia's existing /applets [[files]] block already runs .lhs.
2. routes.toml (optional)
The applet links cached comics as image items itself, so nothing
is strictly required. To also have raw quotes/*.png show as
images when the directory is browsed, add to the /applets
[[files]] block:
[[files.file_type]]
extension = "png"
item_type = "I"
3. PERMISSIONS
quotes/ must be writable by BOTH the watcher (snapshots) and the
Venusia daemon (cached PNGs). Make it group-venusia, group-
writable, setgid, and run the watcher as a user in group venusia:
chgrp venusia applets/qdb applets/qdb/quotes
chmod 2775 applets/qdb applets/qdb/quotes
4. IRC CAPTURE DAEMON
Edit qdb.env (nick, channel, network display name), then run --
as the user the bot should run as (a member of group venusia):
./setup.sh
systemctl --user enable --now qdb-ii qdb-watch
setup.sh installs the user units; the two enabled here are:
qdb-ii ii: connects to the IRC server, joins the channel
qdb-watch tails ii's `out`, writes snapshots, announces
(BindsTo qdb-ii)
Follow them:
journalctl --user -u qdb-ii -u qdb-watch -f
For the bot to survive logout: loginctl enable-linger "$USER"
ON SIMULACRA the bot runs as `venusia` -- the same user Venusia
runs applets as. That makes `venusia` the OWNER of ii's `out` log,
so any applet can read it directly with no cross-user group hop;
the group-venusia/setgid quotes/ setup in step 3 still applies and
is satisfied trivially. (Running as venusia also means applet code
could write ii's `in` FIFO -- acceptable here, where only trusted
venusia-owned applets run.)
SHARED LOG: ii's `out` is a single append-only transcript with two
independent readers -- qdb-watch (this: curated `.qdb` quotes) and
girc.lhs's `/log` view (full scrollback). One logger, one log, two
gopher front-ends; see ../girc.lhs.
ii connects to 127.0.0.1:6667. On simulacra the IRC server
(ngircd) runs on the same host, so that is a loopback connection
-- no TLS needed, the traffic never leaves the machine. Because
ii dials 127.0.0.1 its on-disk dir is ~/irc/127.0.0.1/ --
qdb.env's QDB_IRC_NETDIR reflects that, while QDB_IRC_NET is the
pretty name written into each snapshot.
IF YOUR IRC SERVER IS REMOTE:
ii is plaintext-only, so put TLS in front of it. setup.sh also
installs qdb-stunnel.service (left disabled). Point the `connect`
line in stunnel/qdb.conf at your server, then also:
systemctl --user enable --now qdb-stunnel
stunnel listens on 127.0.0.1:6667 and tunnels to the remote
server; ii's connection is unchanged.
Caveats:
- ii does not auto-rejoin after an IRC-side reconnect. Run
`systemctl --user restart qdb-ii` to rejoin.
- NickServ identification, if the nick needs it, can be added as
another ExecStartPost in qdb-ii.service.
5. LOG PRUNING (optional)
ii has no built-in rotation -- it appends every channel line to
one `out` file per channel, forever. qdb-logprune.sh trims those
to a recent window (QDB_LOG_KEEP_DAYS in qdb.env, default 14);
setup.sh installs it as a weekly timer, left disabled. Turn it on
with:
systemctl --user enable --now qdb-logprune.timer
Because ii holds `out` open, the prune briefly stops qdb-ii (and
thus qdb-watch) while it rewrites the files, ~10s once a week.
qdb snapshots in quotes/ are NEVER pruned -- they are permanent,
which is the whole point.
------------------------------------------------------------
Comic Chat (the /comic render mode)
------------------------------------------------------------
/comic shells out to qdb-comic, which drives Spittoon (statico/
spittoon) -- an implementation of the Microsoft Comic Chat
SIGGRAPH '96 layout algorithm.
Dependencies (root-level, one-time):
- Ruby + ImageMagick + RMagick
sudo apt install imagemagick libmagickwand-dev ruby-dev
sudo gem install rmagick # must be a SYSTEM gem ---
# /comic runs as `venusia`
- Spittoon
sudo git clone https://github.com/statico/spittoon /opt/spittoon
qdb works without these; /comic just returns a graceful "is Spittoon
installed?" error until they are in place.
Spittoon is 2005-era Ruby and predates several modern API changes
(RMagick configuration blocks no longer instance_eval; File.exists?
removed in Ruby 3.2). qdb-comic loads spittoon-compat.rb via `ruby
-r` ahead of make_comic.rb to bridge those --- without touching the
root-owned /opt/spittoon tree. If you upgrade RMagick / Ruby and
/comic starts failing, the shim is the first place to look.
qdb-comic is zero-config in the standard layout:
- Spittoon lib/bin /opt/spittoon/lib, /opt/spittoon/bin/make_comic.rb
- Spittoon config comic-config.yaml (beside qdb-comic)
- assets dir the dir cd'd into so the config's relative
artwork/background paths resolve: ./comic-assets/
if it has an artwork/ subdir, else
/opt/spittoon/examples/
All four are overridable: QDB_SPITTOON_LIB, QDB_SPITTOON_BIN,
QDB_SPITTOON_CONFIG, QDB_COMIC_ASSETS. Also QDB_COMIC_MAXPANELS
(default 24) and QDB_COMIC_RETRIES (default 5).
AVATARS -- the authentic look:
Out of the box qdb-comic uses Spittoon's bundled example artwork
(bucket/easy/melon/nibbles/taff). The real Microsoft Comic Chat
cast (TIKI, MAYNARD, ANNA, ...) shipped as .avb files and is
NOT a drop-in retrofit; here is the state of the world as of
the build of this kit:
- AVBuster (the only known .avb extractor) does NOT work on
the official MS-shipped characters, only on user-made custom
ones. <https://archive.org/details/AVBuster>
- files.defcon.no/mscc/ has all 22 official characters as PNGs,
but only ONE pose each (single portraits, ~50x128 px).
- Spittoon expects 17 sprites per character (10 face names
composited as the head + 7 pose names composited as the body),
so the defcon portraits do not slot in directly.
- The .avb v2.1/v2.5 format is documented at
<http://justsolve.archiveteam.org/wiki/Microsoft_Comic_Chat>,
but the official files appear to have additional encoding
beyond what is published. Writing a working extractor is real
preservation work, not a config swap.
Drop-in path if you DO end up with a full per-expression sprite
set: put it under a comic-assets/ dir beside qdb-comic and it is
picked up automatically.
comic-assets/artwork/faces/<character>-<face>.png
comic-assets/artwork/poses/<character>-<pose>.png
comic-assets/backgrounds/*.jpg
Every character needs a PNG for each face name in comic-config.yaml
(angry, annoyed, bored, grinning, grossed, happy, listening,
shocked, speaking, surprised) and each pose name (exclaiming,
explaining, pointing, question, standing1, standing2, surprised).
List the character names under `characters:` in comic-config.yaml.
Spittoon picks layout variations at random and occasionally bails
when one doesn't fit; qdb-comic retries to absorb that. The first
/comic hit for a quote renders and caches <id>.comic.png beside the
.txt; later hits are served straight from the cache.
------------------------------------------------------------
Running the doctests
------------------------------------------------------------
qdbviewer.lhs's pure helpers carry doctest examples:
stack exec --resolver lts-22.6 \
--package doctest \
--package text --package bytestring --package directory \
--package filepath --package time --package process \
-- doctest -XOverloadedStrings qdbviewer.lhs
------------------------------------------------------------
Limits
------------------------------------------------------------
`.qdb N` is clamped to 1..50. The ring buffer is 50 lines; right
after a watcher restart it is short until the channel talks again.
The comic render is capped at 24 panels (QDB_COMIC_MAXPANELS) --
longer quotes show their first 24 lines as a comic; /txt always
shows the whole thing. Unrecognised path-info, bad ids, and missing
quotes come back as a type-3 row plus a "back to qdb" link.
DIR quotes/ 0B 2026-06-10
DIR stunnel/ 0B 2026-05-15
DIR systemd/ 0B 2026-05-15
BIN comic-config.yaml 2.2KB 2026-05-15
BIN qdb-comic 8.4KB 2026-06-09
BIN qdb-comic.bak.20260609 7.2KB 2026-05-15
TEXT qdb-logprune.sh 1.8KB 2026-05-15
TEXT qdb-watch.sh 5.2KB 2026-05-21
BIN qdb.env 979B 2026-05-15
TEXT qdbviewer.lhs 25.6KB 2026-05-15
TEXT setup.sh 1.7KB 2026-05-15
BIN spittoon-compat.rb 3.9KB 2026-06-09
BIN spittoon-compat.rb.bak.20260609 2.7KB 2026-05-15