- TypeScript 97.4%
- Shell 1.2%
- JavaScript 0.7%
- Nix 0.4%
- Dockerfile 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| deploy | ||
| migrations | ||
| src | ||
| tests | ||
| .dockerignore | ||
| .env.docker.example | ||
| .env.example | ||
| .gitignore | ||
| arion-compose.nix | ||
| bot.config.example.json | ||
| docker-compose.yml | ||
| Dockerfile | ||
| drizzle.config.ts | ||
| live-verify-llm.mjs | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
IRC Bot (TypeScript + Postgres)
A modular IRC bot with these commands:
;tell <user|@account> <message>;in <duration> <text>;countdown <duration> [label];addquote <user> <text>;rmquote <quote id>;quote <user> [n|text];seen <nick>;calc <expression>;rand [max|min max];ping;uptime;version;units <number><unit> to <unit>;help;admin <...>(authenticated session);llm <ask|model|models>
In private query with the bot, command prefix is optional.
Additional behavior:
- multi-channel connect + runtime join/part
- accepts invites (configurable)
- dynamically loaded modules from
src/modules/* - per-channel module enable/disable state persisted in Postgres
- URL previewing with rich adapters:
- Wikipedia summary
- Reddit post metadata
- Hacker News item metadata
- generic title/description fallback
- chat history logging for future LLM/OpenRouter integration
- OpenRouter-backed LLM module with token-efficient context windowing
- channel-scoped memory extraction (
channel.*/channel.<user>.*) with no cross-channel leakage
Stack
- TypeScript
irc-framework- PostgreSQL + Drizzle ORM
- SQL migrations (
migrations/*.sql) - Docker + Arion
Admin Auth
Admin control is password-based and works through private query with the bot.
Bootstrap once (before any admin password exists):
register admin <password>
Login/logout:
login admin <password>logout admin
Admin sessions expire after 5 hours, then require another login.
After login, admin command works in query:
;admin diag(query/PM only);admin setting list;admin setting get <key>;admin setting set <key> <value>;admin setting set channels #a #b #c;admin setting set accept_invites true|false;admin do join #channel;admin do part #channel;admin module list #channel;admin module enable #channel <module>;admin module disable #channel <module>;admin config list llm;admin config get llm <api_key|model|extractor_model|system_prompt>;admin config set llm <api_key|model|extractor_model|system_prompt> <value>;admin config unset llm api_key;admin llm ...;admin memory show #channel [user];admin memory summary #channel;admin user ignore <nick>;admin user ignore <nick>#<channel>(channel-scoped);admin user unignore <nick|nick#channel>;admin user list [#channel]
Config keys currently supported:
channelsaccept_invitesurl_previewcommand_prefix
LLM module
The llm module is dynamically loaded and disabled by default per-channel.
Enable it in a private admin query:
;admin module enable #channel llm
User-facing commands:
;llm ask <question>;llm model;llm models [filter]
Token efficiency strategy:
- always include the latest 50 messages from channel/private history
- include channel-scoped memory facts (
channel.*,channel.<user>.*) - include a rolling 24h channel summary (resets automatically)
- constrained output token budget per request
Default model is deepseek/deepseek-v4-pro (Deepseek V4 Pro), configurable at runtime.
Default extractor model is openai/gpt-4.1-nano (cheap), also configurable.
Channel memory keys
channel.<key>for channel-wide factschannel.<user>.<key>for user-specific facts within that channel
The retriever only reads memory from the current channel to avoid leakage across channels.
Memory extraction is refreshed periodically from chat_history in the background (every ~5 minutes), so new facts are learned continually without rescanning the full log each request.
Quick start
- Copy config template:
cp bot.config.example.json bot.config.json
-
Edit
bot.config.json(onlydatabaseUrl). -
Start Postgres locally (example with Docker):
docker run --name ircbot-postgres -e POSTGRES_USER=ircbot -e POSTGRES_PASSWORD=ircbot -e POSTGRES_DB=ircbot -p 5432:5432 -d postgres:16-alpine
- Install deps and run migrations:
npm install
npm run db:migrate -- --config ./bot.config.json
- Configure at least one IRC server identity in DB, then run bot:
npm run dev -- --config ./bot.config.json --control server create main irc.example.net relaybot 6697
npm run dev -- --config ./bot.config.json --control setting set main channels "#example"
npm run dev -- --config ./bot.config.json
Control CLI (connected or disconnected)
Use control mode without connecting to IRC:
npm run dev -- --config ./bot.config.json --control observe
npm run dev -- --config ./bot.config.json --control server list
npm run dev -- --config ./bot.config.json --control server set main nick relaybot2
npm run dev -- --config ./bot.config.json --control setting list main
npm run dev -- --config ./bot.config.json --control setting set main channels "#chan1 #chan2"
npm run dev -- --config ./bot.config.json --control user ignore main alice#chan1
npm run dev -- --config ./bot.config.json --control user list main
If the bot is already running, it polls settings from DB and applies changes live (channels/prefix/invites/url preview) within ~15s.
Start/stop/restart (local or docker)
Use one wrapper with a mode flag:
./deploy/botctl.sh --mode local start
./deploy/botctl.sh --mode local restart
./deploy/botctl.sh --mode local status
./deploy/botctl.sh --mode local logs
./deploy/botctl.sh --mode docker start
./deploy/botctl.sh --mode docker restart
./deploy/botctl.sh --mode docker status
./deploy/botctl.sh --mode docker logs bot
Docker compose (minimal VPS)
- Ensure
bot.config.jsonexists and has Docker DB host (postgres) indatabaseUrl. - Start stack:
./deploy/botctl.sh --mode docker start
- Follow logs:
./deploy/botctl.sh --mode docker logs bot
Optional systemd wrapper for compose stack:
sudo cp deploy/irc-bot-compose.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now irc-bot-compose
Migrations
- Generate migration templates from schema:
npm run db:generate - Apply committed migrations:
npm run db:migrate
URL adapters
The URL feature follows a modular adapter chain, inspired by Sopel-like URL handlers:
- Wikipedia-specific summary API
- Reddit JSON metadata
- Hacker News item API
- Derpibooru image API metadata
- Danbooru post API metadata
- Gelbooru post API metadata
- Forgejo/Gitea repo/issue/PR/commit API metadata
- generic HTML title/metadata
- optional Playwright fallback (
URL_PREVIEW_PLAYWRIGHT=1)
Derpibooru requests can use bot_config.derpibooru_api_key when present.
Reddit previews can use OAuth when REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET are configured, which allows richer metadata (score, upvote ratio, comments, flair, media URL, created time) even when anonymous Reddit JSON endpoints are blocked.
Playwright fallback is optional and only used when enabled.
Chat history
Every inbound and outbound message is persisted to chat_history with metadata (target, nick, account, tags, timestamps, command marker). This is intended to provide a durable context layer for future OpenRouter/RAG integration.
Arion
With Arion installed:
cp bot.config.example.json bot.config.json
arion up
This starts:
postgres(persistent volumepgdata)bot(builds from local Dockerfile, runs migrations on startup)