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.
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
| needs | python3 (stdlib only), git, a bot token with send/edit in the target channels |
| store | one git repo; one file per tracked message, keyed by message id |
| channels | resolved by id, never by name — duplicate channel names exist |
| marker | a 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.
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.
reconciledetects 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 gatewayMESSAGE_UPDATElistener; this tool is the write-path half. adoptcannot 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.