Metadata-Version: 2.4
Name: auto-tt
Version: 0.1.0
Summary: Open-source X (Twitter) publisher that drives x.com with Patchright and imported cookies.
Project-URL: Homepage, https://github.com/xtea/auto-x
Project-URL: Issues, https://github.com/xtea/auto-x/issues
Project-URL: Repository, https://github.com/xtea/auto-x
Author: auto-tt contributors
License: MIT
License-File: LICENSE
Keywords: automation,patchright,playwright,publishing,scheduler,twitter,x
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet
Requires-Python: >=3.11
Requires-Dist: patchright>=1.55.0
Requires-Dist: pydantic-settings>=2.6.0
Requires-Dist: pydantic>=2.9.0
Requires-Dist: pyyaml>=6.0.2
Requires-Dist: rich>=13.9.0
Requires-Dist: typer>=0.15.0
Requires-Dist: tzdata>=2024.2
Provides-Extra: camoufox
Requires-Dist: camoufox>=0.4.0; extra == 'camoufox'
Provides-Extra: dev
Requires-Dist: mypy>=1.13.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
Requires-Dist: pytest-mock>=3.14.0; extra == 'dev'
Requires-Dist: pytest>=8.3.0; extra == 'dev'
Requires-Dist: ruff>=0.9.0; extra == 'dev'
Requires-Dist: types-pyyaml>=6.0.12; extra == 'dev'
Description-Content-Type: text/markdown

# auto-x

Open-source X (Twitter) publisher that drives `x.com` with [Patchright](https://github.com/Kaliiiiiiiiii-Vinyzu/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](https://pipx.pypa.io/) (recommended):

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

Or with [uv](https://docs.astral.sh/uv/):

```bash
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)

```bash
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`, `timezone` — **match 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)

```bash
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](https://cookie-editor.com/) (browser extension).
2. Open `x.com` in your real browser, click the extension, **Export → Export as JSON**, save to `x-cookies.json`.
3. Run:

   ```bash
   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

```bash
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)

```yaml
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)

```yaml
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

```bash
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:

```cron
*/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](https://github.com/Kaliiiiiiiiii-Vinyzu/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 **Post** → `setInputFiles` 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`](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.
