tool · python · ours, source private

discord-canon

Discord's edit is a destructive write — the API keeps no prior versions. So capture at write time: every post and every patch lands as a real git commit, and git's object store is the version chain.

what it does

The message is HEAD; the repo is the object store

A Discord message exposes one edited_timestamp — a marker, not a history. Once you PATCH it, what it used to say is gone. The only way to keep it is to own the pen: route every write through a wrapper that commits the body to a git repo before (on post) or as (on patch) it goes to Discord. No custom hashing, no reinvented log. git log, git diff, git blame and git revert work on a Discord post because the post's body is a file under version control.

The why of an edit goes in the commit message, not the message body. The post stays clean; blame finds the reason.

how to run it

Commands

discord-canon post   <channel> <file|-> [--reason R]   # POST, then commit
discord-canon patch  <msg_id>  <file|-> --reason R      # PATCH, then commit
discord-canon adopt  <channel> <msg_id>                 # capture an existing message
discord-canon reconcile [<msg_id> | --all]              # detect out-of-band edits
discord-canon log    <msg_id>                           # git log for that message
discord-canon diff   <msg_id> [rev]                     # git diff for that message
discord-canon show   <msg_id>                           # current recorded body
discord-canon ls                                        # every tracked message
needspython3 (stdlib only), git, a bot token with send/edit in the target channels
storeone git repo; one file per tracked message, keyed by message id
channelsresolved by id, never by name — duplicate channel names exist
markera badge character on every canon post: carrying it means the post is versioned; the two facts are the same fact

The script is ~330 lines of standard-library Python and is wired to our guild, token path and repo path. It is not released yet; the model above is complete enough to rebuild in an afternoon.

what breaks

The honest limit

  • An edit made in the Discord client bypasses the wrapper. This is the same gap git has with an untracked working-tree change. reconcile detects it — live content no longer matches the recorded blob — and captures the current state as a commit marked out-of-band, so the gap is visible. But the intermediate version is gone forever, because Discord never kept it. The real fix for that path is a gateway MESSAGE_UPDATE listener; this tool is the write-path half.
  • adopt cannot recover history. Adopting an existing message records what it says now and says so in the commit. Anything before that moment is unrecoverable.
  • Discord's edge 403s a default Python user-agent. The wrapper sends User-Agent: curl/8.7.1. If you rebuild this in urllib and get 403 on every call, that is why.
  • It is a ledger, not a style guide. It records what was written; it does not inject correction markers into the body. If your house rule is to fold corrections inline so retrieval systems see them, that is still your job in the text.