Skip to content

feat(scripts): discord-dump.py — channel/thread→jsonl exporter - #226

Closed
alex-nsheaps[bot] wants to merge 5 commits into
mainfrom
alex/discord-dump-script
Closed

alex-nsheaps[bot] wants to merge 5 commits into
mainfrom
alex/discord-dump-script

Conversation

@alex-nsheaps

@alex-nsheaps alex-nsheaps Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Summary

New scripts/discord-dump.py: stdlib-only Python script for dumping a Discord channel/thread (or whole guild) to JSON Lines with full pagination, rate-limit handling, retry on transient failures, and (for threads) explicit fetch of the starter message that Discord doesn't return from GET /channels/{id}/messages.

Per Nate's Discord ask.

What it does

  • --guild / --channel / --thread (also env: DISCORD_GUILD_ID / DISCORD_CHANNEL_ID / DISCORD_THREAD_ID)
  • --all for every textual channel in the guild
  • --with-threads / --forum to also dump active + archived-public threads
  • --start / --end UTC date filter (YYYY-MM-DD or ISO8601)
  • --user / --exclude-user author-id allow/deny (repeatable)
  • Bot token from $DISCORD_BOT_TOKEN (override with --token-env)

Resilience

  • Pagination via before= cursor (limit 100)
  • Honors 429 from both body retry_after and Retry-After header
  • Proactive sleep when X-RateLimit-Remaining: 0 (uses X-RateLimit-Reset-After)
  • Exponential backoff + jitter on 5xx / network / timeout (cap 60s, --max-retries 8)
  • Thread starter quirk: GET /channels/{id}/messages does NOT include the post-body message whose id equals the thread id. When target is a thread (type 10/11/12), script fetches /channels/{id}/messages/{id} explicitly and appends.

Output

One raw Message JSON per line — embeds/attachments/reactions/components preserved verbatim.

Test

Dumped two channels end-to-end (see alex PR #34):

  • #behavior (1490890535878131792, type 0): 152 messages.
  • Thread "GitHub Issue Migration" (1497431286661517353, type 11): 5752 messages, including the starter.

Commits

  • 8f9ce7f initial script + README
  • 050e255 fix: fetch thread-starter message explicitly

Test plan

  • --help renders
  • Single-channel dump works (#behavior)
  • Single-thread dump includes starter (forum/announcement/public thread)
  • --with-threads discovers active + archived threads
  • --forum errors helpfully when used on non-forum channel
  • --start / --end filtering correctness
  • Token from $DISCORD_BOT_TOKEN and from custom --token-env

…orter

Stdlib-only Python script that dumps Discord messages to JSON Lines.

CLI / env-var inputs:
- --guild/-g (or DISCORD_GUILD_ID) — required
- --channel/-c (or DISCORD_CHANNEL_ID) — required unless --all
- --thread/-t (or DISCORD_THREAD_ID) — required when --channel is forum and
  --forum / --with-threads / --thread is not specified
- --all — dump every textual channel in the guild
- --with-threads / --forum — also dump every active + archived-public thread
  (one jsonl per thread)
- --start / --end — UTC date or ISO8601 filter
- --user / --exclude-user — author-id allow/deny (repeatable)
- --token-env (default DISCORD_BOT_TOKEN)

Resilience:
- Full pagination via the \`before=\` cursor (limit 100)
- Honors 429 with retry_after from body or Retry-After header
- Proactive sleep when X-RateLimit-Remaining: 0
- Exponential backoff + jitter for 5xx / network / timeout (cap 60s,
  --max-retries 8)

Output is one raw Message JSON per line — embeds, attachments, reactions,
and components preserved verbatim for downstream tooling.

Tested by dumping #behavior channel: 5748 messages in one run, 0 lost
messages despite dozens of 429 hits.
@alex-nsheaps alex-nsheaps Bot added the request-review Force an AI code review on a draft PR (open non-draft PRs review automatically) label Jun 7, 2026
alex-nsheaps Bot added 4 commits June 7, 2026 18:22
Discord's GET /channels/{id}/messages does NOT return a thread's starter
message (the post body with msg-id == thread-id). When the target channel
is a thread (type 10/11/12), fetch it explicitly via
GET /channels/{id}/messages/{id} and append after the pagination walk.

Verified against thread 1497431286661517353 (forum thread, type 11):
5752 messages now include the starter (was 5751 from /messages walk only).
After each dump, query GET /channels/{id}/messages?before=<min_id>&limit=100.
If the API returns 0 messages older than the dump's min id, the dump is
provably complete from the start. For threads, also verify the starter id
(== thread id) is present. Exits non-zero on FAIL.

Tested against #behavior (152 msgs): PASS — oldest msg
1490890554513555559 @ 2026-04-07T01:46:47Z, no older messages, starter
present.
Discord's GET /channels/{id}/messages returns newest-first 100 at a time;
the previous implementation wrote per-page chunks reversed to oldest→newest
WITHIN each page, so the file's first line was a recent message and the
true oldest message was buried mid-file. That's not display order.

Accumulate all fetched messages (+ optional thread starter) in memory and
sort by snowflake id before writing. Output is now oldest-at-top end-to-end,
matching how Discord displays the channel.

For typical channels this is fine — Discord caps channels at single-digit
millions of messages and this script is for ad-hoc dumps; streaming-sort
isn't needed yet.
@alex-nsheaps

alex-nsheaps Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Closing — moved into ai-mktpl/plugins/discord per Nate Discord 17:45Z. New PR: nsheaps/ai-mktpl#588.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

request-review Force an AI code review on a draft PR (open non-draft PRs review automatically)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants