Skip to content

feat: add rdt post β€” create post drafts and (captcha-gated) publish - #18

Open
nikicat wants to merge 1 commit into
public-clis:mainfrom
nikicat:feat/post-creation
Open

feat: add rdt post β€” create post drafts and (captcha-gated) publish#18
nikicat wants to merge 1 commit into
public-clis:mainfrom
nikicat:feat/post-creation

Conversation

@nikicat

@nikicat nikicat commented Jul 22, 2026

Copy link
Copy Markdown

Summary

Adds rdt post β€” create Reddit posts from the CLI via the web GraphQL API (svc/shreddit/graphql), reusing the existing cookie session. No new runtime dependencies.

  • Drafts (text/link) are created fully headlessly: rdt post <sub> <title> --text "..." --draft.
  • Publishing (subreddit CreatePost / profile CreateProfilePost, including images) is gated behind reCAPTCHA Enterprise, so it requires a browser-supplied --recaptcha-token (single-use, ~2 min). The tool never solves the captcha itself.
  • Image drafts are rejected with a clear message β€” Reddit's draft input type has no media field, so drafts can't store images.

Why the TLS change

httpx/httpcore ship a restricted cipher list whose TLS ClientHello is rejected by Reddit's WAF on POST /svc/shreddit/graphql (403 "blocked by network security") β€” reads and the legacy /api/* endpoints are unaffected. Resetting to ssl.create_default_context() + set_ciphers("DEFAULT") (in transports.build_ssl_context(), applied via verify=) produces a fingerprint the WAF accepts (stock curl passes the same way). Pure-Python, not browser impersonation, no new deps.

Command

rdt post <sub|u_name> <title> (--text | --url | --image) \
         [--draft] [--recaptcha-token TOK] [--nsfw] [--spoiler] [--json | --yaml]

--draft saves headlessly; without it, --recaptcha-token is required; --image --draft is rejected.

Implementation

  • transports.py β€” TLS SSL-context fix + application/json writes.
  • session.py β€” ensure_csrf_token() (double-submit CSRF; cookie == body).
  • client.py β€” _graphql, resolve_subreddit_id, create_media_lease, upload_image (S3 multipart), create_post, create_draft.
  • commands/submit.py β€” the post command.
  • constants.py, cli.py, tests, README/SKILL.

Testing

  • 141 unit tests pass (pytest, mocked β€” no network), ruff clean, mypy adds no new errors.
  • Text-draft path verified live end-to-end against Reddit.

πŸ€– Generated with Claude Code

Add a `rdt post` command that creates Reddit posts through Reddit's web
GraphQL endpoint (svc/shreddit/graphql), reusing the existing cookie session.

Drafts (text/link) are created fully headlessly. Publishing (subreddit
`CreatePost` / profile `CreateProfilePost`, incl. images) is gated behind
reCAPTCHA Enterprise, so it requires a browser-supplied `--recaptcha-token`;
the tool never solves the captcha itself. Image drafts are rejected because
Reddit's draft input type has no media field.

Key pieces:
- transports: TLS fix β€” httpx's default cipher list produces a ClientHello
  that Reddit's WAF blocks on the GraphQL endpoint (403 "blocked by network
  security"); reset to ssl.create_default_context() + set_ciphers("DEFAULT").
  Pure-Python, no new deps, not impersonation. Also allow application/json
  writes on the write transport.
- session: ensure_csrf_token() β€” double-submit CSRF (same value in cookie
  and JSON body; generated if absent).
- client: _graphql, resolve_subreddit_id, create_media_lease, upload_image
  (S3 multipart), create_post, create_draft.
- commands/submit.py: the `post` command (--text/--url/--image, --draft,
  --recaptcha-token, --nsfw/--spoiler, --json/--yaml; profile via u_<name>).
- tests + README/SKILL docs.

No new runtime dependencies.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant