Skip to content

xtea/auto-x

Repository files navigation

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.

About

Open-source X (Twitter) publisher CLI. Patchright + imported cookies. Sibling of auto-instagram, auto-tiktok, auto-linkedin.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages