Self-hosted Discord message archive, recovery, and inspection bot. You run it, you own the data: SQLite + a local media folder — no SaaS, no shared database, no central server. Its headline feature is sniping: recovering deleted messages and browsing edit history, right inside Discord, through a native Components V2 browser UI.
/snipe— opens the latest deleted message as a rich detail card (author avatar, channel, timestamps, content, attachment gallery, reactions/embeds/polls summary). Older/Newer walk through other deletions, List opens the paged browser, and a jump select picks any result./snipe index:3jumps straight to #3;/snipe bulk:truebrowses bulk-deletion purges as groups./edits— paged browser of edited messages plus a revision walker (Older/Newer through every revision, never just before/after)./search— full-text archive search (text:,author:,after: YYYY-MM-DD,has: attachment|poll,deleted:) with paged V2 results and per-hit detail views.- Message context menus — right-click any message: View Edit History or Search User Messages.
- Archive engine — every observed message snapshotted to SQLite on sight (deletes/edits can't be fetched after the fact); attachments copied locally before CDN links expire; reactions, polls, voice durations, threads, and best-effort deletion attribution all archived.
/backfill— import a channel's recent history (paged, resumable)./settings+/stats— per-server role gates, archiving opt-out, retention windows, and archive statistics.- Privacy by design — channel visibility is re-checked at read time
on every command and every button press, so
/snipecan never leak channels the caller can't see. DMs are not archived unless explicitly enabled. Logs never contain message content.
- Node.js ≥ 24.17.0 and npm (or Docker — see below)
- A Discord application with a bot token
- The privileged Message Content intent enabled (Bot page)
-
Go to https://discord.com/developers/applications → New Application.
-
Bot page → Reset Token → copy it (this is
DISCORD_TOKEN). -
Bot page → enable Message Content Intent (privileged).
-
Copy the Application ID (this is
DISCORD_CLIENT_ID). -
Invite the bot with minimal permissions (
ViewChannel+ReadMessageHistory+SendMessages= bitfield68608):https://discord.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&permissions=68608&scope=bot%20applications.commands
cp .env.example .env # then fill in DISCORD_TOKEN + DISCORD_CLIENT_ID
chmod 600 .env| Variable | Required | Default | Notes |
|---|---|---|---|
DISCORD_TOKEN |
yes | — | Bot token. Never commit it. |
DISCORD_CLIENT_ID |
for deploys | — | Application ID for deploy:commands. |
DISCORD_GUILD_ID |
no | — | Set: instant guild command deploy. Empty: global (takes up to 1h). |
DATABASE_PATH |
no | ./data/snipebot.db |
SQLite file (use /app/data/… in containers). |
MEDIA_STORAGE_PATH |
no | ./data/media |
Local attachment archive. |
MEDIA_MAX_BYTES |
no | 100000000 |
Largest single attachment archived. |
ARCHIVE_DMS |
no | false |
Must be exactly 'true' to archive DMs. |
NODE_ENV |
no | development |
Containers set production. |
LOG_LEVEL |
no | info |
debug for troubleshooting. |
npm ci
npm run db:migrate # create/upgrade the SQLite schema
npm run deploy:commands # register slash + context-menu commands
npm run dev # watch mode, or:
npm run build && node dist/index.jsFor production without Docker: NODE_ENV=production node dist/index.js
(e.g. behind systemd with Restart=always); re-run db:migrate after
every upgrade.
docker compose up -d --build
docker compose logs -f snipebot # expect "Logged in as …"The container runs migrations automatically on every boot, runs as a
non-root user, persists ./data (database + media) via a volume, and
exposes no ports (outbound gateway traffic only). Upgrades:
docker compose up -d --build again (migrations re-apply harmlessly).
Full details, backup/restore, and troubleshooting:
docs/deployment.md.
All recovery commands are channel-scoped and auth-checked — you only ever see messages from channels you can currently view.
| Command | What it does |
|---|---|
/snipe |
Latest deleted message as a detail card. Older/Newer walk history, List opens the browser, Close ends it. |
/snipe index:3 |
The 3rd-latest recoverable deletion (1–10). |
/snipe bulk:true |
Bulk-deletion purges as browsable groups. |
/edits |
Edited-message browser; open any message to walk every revision. |
/search text:docker author:@mark has:attachment |
Full-text search with filters; results are browsable. |
| Right-click message → View Edit History | Revision walker for that message. |
| Right-click message → Search User Messages | That author's messages in this channel. |
/backfill limit:100 |
Import this channel's recent history into the archive. |
/settings … |
snipe-roles (restrict commands to roles), archiving (server opt-out), retention-* (expiry windows). Requires Manage Server. |
/stats |
Archive counts and uptime. |
/ping |
Liveness check. |
How sniping works: the bot must be online and able to see the
channel before deletion — Discord offers no fetch-deleted-messages
API, so recovery depends entirely on pre-deletion capture. Uncached
deletes become id-only markers (*no text content*).
- Intents:
Guilds,GuildMessages,MessageContent(privileged — toggle in the Portal),GuildMessageReactions,GuildMessagePolls. Rationale: docs/discord.md. - Bot permissions:
ViewChannel+ReadMessageHistory(observe),SendMessages(reply). Optional:ViewAuditLog(best-effort "possibly deleted by X" hints). NeverAdministratororManageMessages— sniping needs neither.
src/
discord/ gateway adapters (discord.js stops here)
events/ thin per-event handlers
commands/ slash + context-menu modules
interactions/ button/select router tables
ui/ discord.js-free view models, browser state, search
sessions → renderers/ (Components V2 builders)
domain/ MessageSnapshot (the archive contract)
services/ archive/media/retention orchestration
repositories/ query ports (drizzle-backed)
database/ connection + schema storage/ local media
scripts/ deploy-commands, migrate, healthcheck, load-test
tests/ unit + integration suites (+ helpers/fakes)
docs/ architecture, discord truth, decisions, roadmap, …
specs/ Spec Kit phases 000–023 (spec → plan → tasks)
Architecture, boundaries, and the Components V2 system: docs/architecture.md · docs/components-v2.md · AGENTS.md (agent operating manual).
| Script | Purpose |
|---|---|
npm run dev |
Watch mode (tsx) |
npm run build / npm start |
Compile / run compiled app |
npm run typecheck / npm run lint / npm run format |
Strict TS, eslint, prettier |
npm test / npm run test:integration |
Unit (vitest) / DB integration suites |
npm run deploy:commands |
Register Discord commands |
npm run db:generate / db:migrate / db:push |
Drizzle schema workflow |
npm run load:test |
Local throughput probe |
This bot stores other people's messages — treat the archive as
sensitive. Read docs/security.md before inviting
it anywhere: token hygiene (chmod 600 .env, umask-restricted files),
least privilege, read-time visibility enforcement, retention semantics,
and encrypted off-host backups. To report a vulnerability privately,
see SECURITY.md.
Contributions welcome — start with CONTRIBUTING.md (and AGENTS.md for the operating rules). Bug reports and feature ideas: open an issue. Please follow the Code of Conduct.
MIT — you run it, you own the data.
Planned and shipped work lives in docs/roadmap.md
with per-phase specs under specs/; decisions in
docs/decisions.md. The previous discord.js-13
prototype is preserved on the legacy branch — this codebase is a
complete remake sharing only the product idea.