Skip to main content

Open-source X (Twitter) publisher that drives x.com with Patchright and imported cookies.

Project description

auto-x

Open-source X (Twitter) publisher that drives x.com with Patchright and your own imported cookies. Prepare content in a folder, configure your account once, publish.

Status: Alpha. Personal-account use only. Browser-driven automation of X is restricted by X's ToS — use a scratch account, conservative pacing, and a residential IP that matches where you got the cookies.

Why this project

X's official developer API gates writes behind a paid tier and an app-review process. For one operator who already has a real logged-in browser tab, the most durable workflow is to drive that same browser. auto-x does exactly that.

Supported post types in v1:

  • tweet — single post, up to 4 images or 1 video, with optional per-image alt text
  • thread — a chain of 2+ tweets, each segment can carry its own media

X Premium accounts unlock 25,000-character posts; toggle premium: true in the account YAML to lift the 280-char cap.

Not supported in v1: replies, quote-tweets, polls, Spaces, long-form Articles. They will follow once the v1 surface is stable.

Requirements

  • Python 3.11+
  • macOS or Linux (tested on macOS)
  • A real Chrome/Chromium you can log in from on the same network you'll run the bot on
  • (Recommended) a residential proxy if you plan to run this on a different machine from where you logged in

Install

One command with pipx (recommended):

pipx install auto-tt
auto-tt init --account demo

Or with uv:

uv tool install auto-tt
auto-tt init --account demo

auto-tt init installs the patched Chrome channel Patchright needs and scaffolds a working directory in .:

./config/demo.yaml            # account config from the shipped template
./content/example-post/       # sample post descriptor
./sessions/                   # session files (gitignored)
./.gitignore                  # appended with auto-x entries

It is safe to re-run — existing files are preserved.

From source (contributors)

git clone https://github.com/xtea/auto-x
cd auto-x
uv sync
uv run auto-tt init --account demo

Configure an account

Edit config/<account>.yaml. The important fields:

  • handle — your X username without @ (display only)
  • premium — true if your account has X Premium (raises the per-tweet cap to 25,000 chars)
  • user_agent, viewport, locale, timezonematch the browser you'll log in from. Drift between these and the cookie's origin is the #1 cause of re-challenges on x.com.
  • pacing.max_posts_per_day — start at 25 or lower. X's rolling-window soft caps fire well before the documented daily limit.

Authenticate

Two paths; pick whichever you prefer.

Option A — Headed manual login (simplest)

auto-tt login --account demo

A Chrome window opens at https://x.com/i/flow/login regardless of your headless setting. Log in by hand (handle Arkose / 2FA yourself). When the home timeline appears, the session is saved to sessions/demo.json.

Option B — Import cookies from your real browser

X's most important cookies (auth_token, ct0, kdt) are HttpOnly — they will not appear in document.cookie. Use a tool that reads the browser's cookie store directly:

  1. Install Cookie-Editor (browser extension).

  2. Open x.com in your real browser, click the extension, Export → Export as JSON, save to x-cookies.json.

  3. Run:

    auto-tt import-cookies ./x-cookies.json --account demo
    

The tool rejects the import if auth_token or ct0 is missing. Cookies on the legacy .twitter.com domain are auto-mirrored to .x.com, so a session migrated mid-rebrand still works.

Verify

auto-tt doctor --account demo

Should print OK: <handle> session is valid.

Publish content

Layout

content/
└── my-post/
    ├── post.yaml
    └── media/
        ├── 1.jpg
        └── 2.jpg

post.yaml schema (single tweet)

type: tweet
caption: |
  Your caption. 280 chars on free, 25,000 on Premium.
  #opensource
media:
  - ./media/1.jpg          # 1–4 images, OR exactly one video
alt_texts:                 # optional, aligned by index, ≤1000 chars each
  - "Description of 1.jpg"
schedule: 2026-04-25T14:00:00Z   # optional, UTC or with offset

post.yaml schema (thread)

type: thread
segments:
  - caption: "First tweet in the chain."
    media: [./media/1.jpg]
  - caption: "Second tweet  every segment can have its own media."
    media: [./media/2.mp4]

Validation runs before any browser work:

Surface Rule
Caption (free) ≤ 280 chars
Caption (Premium) ≤ 25,000 chars
Images per segment 1–4, .jpg/.jpeg/.png/.webp, ≤5 MB (15 MB for .gif)
Video per segment 1 only, .mp4/.mov, ≤512 MB and ≤140 s on free tier
Mixing media images xor video — never both
Alt text ≤ 1000 chars per image
Thread length 2–25 segments

One-shot publish

auto-tt publish content/my-post --account demo --dry-run   # safe first run
auto-tt publish content/my-post --account demo             # actually posts

--dry-run walks the full upload flow and stops before clicking Post — useful when patching selectors.

Scheduled / queued publish

Drop posts with schedule: set in the future, then run auto-tt queue from cron / launchd / systemd-timer:

*/5 * * * * cd /path/to/auto-x && auto-tt queue --account demo >> sessions/queue.log 2>&1

The queue stores state in sessions/queue.db (SQLite) with statuses: queued | running | succeeded | failed | paused. Use auto-tt list to inspect.

How the Playwright flow works

  1. Launch Chrome via Patchright (Chromium with CDP/webdriver leaks patched at the binary level). Vanilla playwright is detectable by X's fingerprinting stack — don't use it.
  2. Load sessions/<account>.json as the Playwright storage_state.
  3. Navigate to https://x.com/home, confirm the Post side-nav button is visible (signal of authenticated state).
  4. Click PostsetInputFiles into the hidden <input data-testid="fileInput">.
  5. Type the caption into [data-testid="tweetTextarea_0"] (contenteditable, must use keyboard.type not fill).
  6. For threads, click the addButton and repeat for tweetTextarea_1..N.
  7. Click tweetButtonInline. Watch for the toast [data-testid="toast"] and read its View link to capture the new tweet's numeric ID.

All selectors are in src/auto_x/publisher/selectors.py — when X changes the UI, that is the file to patch.

Pacing & safety

Built-in guardrails, tunable in config/<account>.yaml:

  • max_posts_per_day daily cap (enforced by the queue)
  • min/max_step_delay_seconds randomized delays between UI actions (30–180 s recommended)
  • pre_run_idle_seconds_* scroll/dwell on the home timeline before the first click

On any redirect to /i/flow/login, /account/access, /i/flow/consent_flow, /i/flow/login/check, or /account/suspended, the runner pauses the job and records the reason. Re-authenticate with auto-tt login and retry — there is no auto-retry.

headless: true is the shipped default. The auto-tt login command always forces a headed window regardless, so cookie capture and 2FA still work.

Known limitations

  • v1 surface only. Reply, quote, poll, Spaces, and long-form Article composition are not supported yet.
  • Selectors rot. X ships UI changes frequently. Expect periodic patches to selectors.py.
  • Arkose / Cloudflare-style challenges. If X drops a FunCaptcha mid-run, the tool pauses; manual re-login is required.
  • Shared IP. Using cookies captured from residence A while running the bot on residence B's IP is the single most reliable way to get challenged.
  • ToS risk. Browser-driven automation of personal X accounts is against X's terms. Ban risk is real. Use a scratch account.

Non-goals (for now)

  • Web UI / dashboard (CLI + YAML only)
  • Long-running daemon (cron-friendly invocation instead)
  • Engagement automation (likes, follows, DMs, retweets-without-quote)
  • Account registration

License

MIT.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

auto_tt-0.1.0.tar.gz (31.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

auto_tt-0.1.0-py3-none-any.whl (36.3 kB view details)

Uploaded Python 3

File details

Details for the file auto_tt-0.1.0.tar.gz.

File metadata

  • Download URL: auto_tt-0.1.0.tar.gz
  • Upload date:
  • Size: 31.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for auto_tt-0.1.0.tar.gz
Algorithm Hash digest
SHA256 92f6b7d6decd92b6ac8d6b0655dd638232523abd2cf7dff800686ea79be75af0
MD5 0d4ea96fd7c8a3f7e687117790d5d65f
BLAKE2b-256 e10231080f363b96221449bb7b395177806bbc85feb2f0f6b724c4ad2e6c4819

See more details on using hashes here.

File details

Details for the file auto_tt-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: auto_tt-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 36.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for auto_tt-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e337e0f18d8e49c03413c9748bbfa16f267c6eae5f6c4f4866763d57a788a383
MD5 1a50e816bb15ce9932ea53cbae1a2b72
BLAKE2b-256 9adc2c7882f814654a8ec8b35bf1e82bfd27f622b7b765f0c8153b85b586a6bf

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page