x-browser 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- x_browser-0.1.2/.github/workflows/publish.yml +24 -0
- x_browser-0.1.2/.gitignore +24 -0
- x_browser-0.1.2/LICENSE +21 -0
- x_browser-0.1.2/LLMs.md +172 -0
- x_browser-0.1.2/PKG-INFO +364 -0
- x_browser-0.1.2/README.md +336 -0
- x_browser-0.1.2/SKILL.md +165 -0
- x_browser-0.1.2/pyproject.toml +50 -0
- x_browser-0.1.2/scripts/auto-follow-back.py +98 -0
- x_browser-0.1.2/scripts/auto-reply-mentions.py +135 -0
- x_browser-0.1.2/scripts/engagement-bot.py +195 -0
- x_browser-0.1.2/scripts/scheduled-poster.py +122 -0
- x_browser-0.1.2/scripts/search-and-like.py +183 -0
- x_browser-0.1.2/scripts/search-and-retweet.py +145 -0
- x_browser-0.1.2/src/x_browser/__init__.py +1 -0
- x_browser-0.1.2/src/x_browser/actions.py +389 -0
- x_browser-0.1.2/src/x_browser/browser.py +174 -0
- x_browser-0.1.2/src/x_browser/cli.py +449 -0
- x_browser-0.1.2/src/x_browser/config.py +89 -0
- x_browser-0.1.2/src/x_browser/formatters.py +285 -0
- x_browser-0.1.2/src/x_browser/scrapers.py +458 -0
- x_browser-0.1.2/uv.lock +326 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
id-token: write
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
publish:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
environment: pypi
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Install uv
|
|
18
|
+
uses: astral-sh/setup-uv@v4
|
|
19
|
+
|
|
20
|
+
- name: Build package
|
|
21
|
+
run: uv build
|
|
22
|
+
|
|
23
|
+
- name: Publish to PyPI
|
|
24
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
*.pyo
|
|
5
|
+
*.egg-info/
|
|
6
|
+
*.egg
|
|
7
|
+
.eggs/
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
.venv/
|
|
11
|
+
|
|
12
|
+
# IDE
|
|
13
|
+
.vscode/
|
|
14
|
+
.idea/
|
|
15
|
+
*.swp
|
|
16
|
+
*.swo
|
|
17
|
+
.DS_Store
|
|
18
|
+
|
|
19
|
+
# x-browser session data (contains login cookies!)
|
|
20
|
+
chrome-data/
|
|
21
|
+
|
|
22
|
+
# Environment / secrets
|
|
23
|
+
.env
|
|
24
|
+
.env.*
|
x_browser-0.1.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shivam Tiwari
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
x_browser-0.1.2/LLMs.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# x-browser — LLM Codebase Guide
|
|
2
|
+
|
|
3
|
+
You are working on **x-browser**, a browser-based X/Twitter automation tool. It uses Playwright to control real Chrome via CDP, avoiding all API restrictions.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/x_browser/
|
|
11
|
+
├── __init__.py # Package marker
|
|
12
|
+
├── cli.py # Click CLI — all commands, entry point
|
|
13
|
+
├── browser.py # Chrome launch + CDP connection
|
|
14
|
+
├── config.py # Politeness delays, config load/save
|
|
15
|
+
├── actions.py # Write operations (post, like, retweet, reply, quote, delete, bookmark)
|
|
16
|
+
├── scrapers.py # Read operations (get tweet, search, user profile, timeline, followers, mentions, bookmarks)
|
|
17
|
+
└── formatters.py # Output modes: human (Rich), JSON, TSV, markdown
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
scripts/
|
|
22
|
+
└── search-and-like.py # Example long-running automation script
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Module details
|
|
28
|
+
|
|
29
|
+
### `browser.py` — Browser session management
|
|
30
|
+
|
|
31
|
+
**Key insight:** Chrome is launched as a normal subprocess with `--remote-debugging-port`, then Playwright connects over CDP. This means zero automation flags — X cannot detect it.
|
|
32
|
+
|
|
33
|
+
- `XBrowser` class: async context manager, holds Chrome process + Playwright connection
|
|
34
|
+
- `XBrowser(headless=True/False)` — headless uses `--headless=new` flag
|
|
35
|
+
- Session data persists in `~/.config/x-browser/chrome-data/`
|
|
36
|
+
- `login_interactive()` — opens headed Chrome, waits for user to log in, saves session
|
|
37
|
+
- `ensure_logged_in(page)` — checks if session is still valid
|
|
38
|
+
|
|
39
|
+
### `config.py` — Configuration
|
|
40
|
+
|
|
41
|
+
- `Config` dataclass with `min_politeness_delay` and `max_politeness_delay`
|
|
42
|
+
- Defaults: 3s min, 30s max
|
|
43
|
+
- Loads from `~/.config/x-browser/config.json`, falls back to defaults
|
|
44
|
+
- `Config.save()` persists to disk
|
|
45
|
+
- `config.politeness_wait()` — async sleep for random(min, max) seconds, logs to stderr
|
|
46
|
+
|
|
47
|
+
### `actions.py` — Write operations
|
|
48
|
+
|
|
49
|
+
Every function signature follows this pattern:
|
|
50
|
+
```python
|
|
51
|
+
async def action_name(
|
|
52
|
+
page: Page,
|
|
53
|
+
..., # action-specific args
|
|
54
|
+
*,
|
|
55
|
+
config: Config | None = None, # uses Config.load() if None
|
|
56
|
+
min_delay: float | None = None,
|
|
57
|
+
max_delay: float | None = None,
|
|
58
|
+
) -> dict:
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Every function calls `politeness_wait()` as its first line, then performs browser interactions.
|
|
62
|
+
|
|
63
|
+
Functions and what they do:
|
|
64
|
+
- `tweet_post(page, text, poll_options=None)` — navigates to `/compose/post`, types, clicks Post
|
|
65
|
+
- `tweet_delete(page, id_or_url)` — navigates to tweet, clicks ⋯ → Delete → Confirm
|
|
66
|
+
- `tweet_reply(page, id_or_url, text)` — navigates to tweet, clicks reply, types, posts
|
|
67
|
+
- `tweet_quote(page, id_or_url, text)` — navigates to tweet, clicks repost → Quote, types, posts
|
|
68
|
+
- `like(page, id_or_url)` — navigates to tweet, clicks heart (`data-testid="like"`)
|
|
69
|
+
- `retweet(page, id_or_url)` — navigates to tweet, clicks repost → Repost
|
|
70
|
+
- `follow(page, username)` — navigates to user profile, clicks Follow button
|
|
71
|
+
- `bookmark(page, id_or_url)` — navigates to tweet, clicks bookmark icon
|
|
72
|
+
- `unbookmark(page, id_or_url)` — navigates to tweet, clicks removeBookmark icon
|
|
73
|
+
|
|
74
|
+
All return `{"ok": True, ...}` dicts.
|
|
75
|
+
|
|
76
|
+
### `scrapers.py` — Read operations
|
|
77
|
+
|
|
78
|
+
Functions return Python dicts or lists of dicts. No politeness delay on reads.
|
|
79
|
+
|
|
80
|
+
- `tweet_get(page, id_or_url)` → dict with: text, author_username, author_name, created_at, id, url, reply_count, retweet_count, like_count, view_count
|
|
81
|
+
- `tweet_metrics(page, id_or_url)` → subset of tweet_get focused on counts
|
|
82
|
+
- `tweet_search(page, query, max_results=10)` → list of tweet dicts
|
|
83
|
+
- `user_get(page, username)` → dict with: username, name, description, followers_count, following_count, joined, location, url
|
|
84
|
+
- `user_timeline(page, username, max_results=10)` → list of tweet dicts
|
|
85
|
+
- `user_followers(page, username, max_results=100)` → list of user dicts
|
|
86
|
+
- `user_following(page, username, max_results=100)` → list of user dicts
|
|
87
|
+
- `me_mentions(page, max_results=10)` → list of tweet dicts
|
|
88
|
+
- `me_bookmarks(page, max_results=10)` → list of tweet dicts
|
|
89
|
+
|
|
90
|
+
Key helper: `_extract_tweet_from_article(article, page)` — extracts data from a single `<article data-testid="tweet">` element.
|
|
91
|
+
|
|
92
|
+
### `formatters.py` — Output formatting
|
|
93
|
+
|
|
94
|
+
- `output(data, mode="human", verbose=False)` — router to the correct formatter
|
|
95
|
+
- Modes: `human` (Rich panels/tables), `json`, `plain` (TSV), `markdown`
|
|
96
|
+
- Auto-detects data type (tweet, user, action result, list) and formats accordingly
|
|
97
|
+
|
|
98
|
+
### `cli.py` — Click CLI
|
|
99
|
+
|
|
100
|
+
- Root group `cli()` with global options: `-j`, `-p`, `-md`, `-v`, `--min-delay`, `--max-delay`, `--headed`
|
|
101
|
+
- Sub-groups: `tweet`, `user`, `me`
|
|
102
|
+
- Top-level commands: `like`, `retweet`, `login`, `config`
|
|
103
|
+
- `State` dataclass holds mode, verbose, config, and lazy-loaded browser
|
|
104
|
+
- `async_command` decorator bridges async functions into Click's sync world via `asyncio.run()`
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## X DOM selectors used
|
|
109
|
+
|
|
110
|
+
These are the key `data-testid` selectors x-browser relies on:
|
|
111
|
+
|
|
112
|
+
| Selector | Used for |
|
|
113
|
+
|----------|----------|
|
|
114
|
+
| `article[data-testid="tweet"]` | Tweet containers |
|
|
115
|
+
| `div[data-testid="tweetTextarea_0"]` | Compose text box |
|
|
116
|
+
| `button[data-testid="tweetButton"]` | Post/Reply button |
|
|
117
|
+
| `button[data-testid="reply"]` | Reply button on a tweet |
|
|
118
|
+
| `button[data-testid="retweet"]` | Repost button |
|
|
119
|
+
| `button[data-testid="like"]` | Like button (unliked state) |
|
|
120
|
+
| `button[data-testid="unlike"]` | Like button (liked state) |
|
|
121
|
+
| `button[data-testid="bookmark"]` | Bookmark button |
|
|
122
|
+
| `button[data-testid="removeBookmark"]` | Unbookmark button |
|
|
123
|
+
| `button[data-testid="caret"]` | ⋯ more menu on tweet |
|
|
124
|
+
| `div[data-testid="tweetText"]` | Tweet text content |
|
|
125
|
+
| `div[data-testid="User-Name"]` | User display name |
|
|
126
|
+
| `div[data-testid="UserDescription"]` | User bio |
|
|
127
|
+
| `span[data-testid="UserJoinDate"]` | User join date |
|
|
128
|
+
| `a[data-testid="UserUrl"]` | User website link |
|
|
129
|
+
|
|
130
|
+
If X changes these selectors, update the corresponding functions in `actions.py` and `scrapers.py`.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Adding a new action
|
|
135
|
+
|
|
136
|
+
1. Add the async function in `actions.py` following the existing pattern
|
|
137
|
+
2. Call `politeness_wait()` as the first line
|
|
138
|
+
3. Add a Click command in `cli.py` under the appropriate group
|
|
139
|
+
4. Return a dict — formatters handle it automatically
|
|
140
|
+
|
|
141
|
+
## Adding a new scraper
|
|
142
|
+
|
|
143
|
+
1. Add the async function in `scrapers.py`
|
|
144
|
+
2. Use `_extract_tweet_from_article()` for tweet lists, or write custom extraction
|
|
145
|
+
3. Add a Click command in `cli.py`
|
|
146
|
+
4. Return a dict or list of dicts — formatters handle it automatically
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Automation scripts
|
|
151
|
+
|
|
152
|
+
Located in `scripts/`. Each uses argparse and imports from `x_browser.*`:
|
|
153
|
+
|
|
154
|
+
| Script | Purpose |
|
|
155
|
+
|--------|---------|
|
|
156
|
+
| `search-and-like.py` | Search & like tweets, scrolling continuously |
|
|
157
|
+
| `search-and-retweet.py` | Search & retweet tweets |
|
|
158
|
+
| `engagement-bot.py` | Search → like + retweet + follow author (each toggleable via `--skip-*`) |
|
|
159
|
+
| `auto-follow-back.py` | Compare followers vs following, follow back the difference |
|
|
160
|
+
| `auto-reply-mentions.py` | Monitor mentions, reply with templates (fixed, file, or defaults) |
|
|
161
|
+
| `scheduled-poster.py` | Post tweets from a text file at intervals |
|
|
162
|
+
|
|
163
|
+
All scripts support `--headed`, `--min-delay`, `--max-delay`, `--dry-run` (where applicable).
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## File paths
|
|
168
|
+
|
|
169
|
+
- Config: `~/.config/x-browser/config.json`
|
|
170
|
+
- Browser session: `~/.config/x-browser/chrome-data/`
|
|
171
|
+
- Source: `src/x_browser/`
|
|
172
|
+
- Scripts: `scripts/`
|
x_browser-0.1.2/PKG-INFO
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: x-browser
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Browser-based CLI for X/Twitter using Playwright + CDP. No API keys needed.
|
|
5
|
+
Project-URL: Homepage, https://github.com/shivamtiwari93/x-browser
|
|
6
|
+
Project-URL: Repository, https://github.com/shivamtiwari93/x-browser
|
|
7
|
+
Project-URL: Issues, https://github.com/shivamtiwari93/x-browser/issues
|
|
8
|
+
Author-email: Shivam Tiwari <shivamtiwari93@users.noreply.github.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: automation,browser,cli,playwright,twitter,x
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Internet
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Requires-Dist: click>=8.1
|
|
24
|
+
Requires-Dist: httpx>=0.27
|
|
25
|
+
Requires-Dist: playwright>=1.40
|
|
26
|
+
Requires-Dist: rich>=13.0
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# x-browser
|
|
30
|
+
|
|
31
|
+
A CLI and Python module for X/Twitter that works through browser automation — no API keys, no tier restrictions, no rate limits.
|
|
32
|
+
|
|
33
|
+
Uses [Playwright](https://playwright.dev/) to control your real Chrome installation via CDP. X cannot distinguish this from a human using Chrome.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# 1. Install
|
|
41
|
+
git clone https://github.com/shivamtiwari93/x-browser.git
|
|
42
|
+
cd x-browser
|
|
43
|
+
uv tool install .
|
|
44
|
+
|
|
45
|
+
# 2. Login (one-time — opens Chrome, you log in normally)
|
|
46
|
+
x-browser login
|
|
47
|
+
|
|
48
|
+
# 3. Use it
|
|
49
|
+
x-browser tweet post 'Hello from x-browser!'
|
|
50
|
+
x-browser tweet search 'AI agents' --max 10
|
|
51
|
+
x-browser like https://x.com/user/status/123
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Install from PyPI
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install x-browser
|
|
58
|
+
# or
|
|
59
|
+
uv tool install x-browser
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Install from Homebrew (macOS)
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
brew tap shivamtiwari93/tap
|
|
66
|
+
brew install x-browser
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Why not the API?
|
|
72
|
+
|
|
73
|
+
| | X API (x-cli) | Browser (x-browser) |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| **Setup** | 5 API keys from Developer Portal | Log into X once |
|
|
76
|
+
| **Likes** | Removed from Free tier | Works |
|
|
77
|
+
| **Replies** | Restricted on self-serve tiers | Works |
|
|
78
|
+
| **Bookmarks** | Requires Basic+ ($200/mo) | Works |
|
|
79
|
+
| **Post limits** | 500/mo on Free | Normal user limits |
|
|
80
|
+
| **Rate limits** | API rate limiting (429) | None |
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## CLI Reference
|
|
85
|
+
|
|
86
|
+
### Tweets
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
x-browser tweet post 'Hello world'
|
|
90
|
+
x-browser tweet post --poll 'Yes,No' 'Do you like polls?'
|
|
91
|
+
x-browser tweet get <id-or-url>
|
|
92
|
+
x-browser tweet delete <id-or-url>
|
|
93
|
+
x-browser tweet reply <id-or-url> 'nice post'
|
|
94
|
+
x-browser tweet quote <id-or-url> 'this is important'
|
|
95
|
+
x-browser tweet search 'machine learning' --max 20
|
|
96
|
+
x-browser tweet metrics <id-or-url>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Users
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
x-browser user get elonmusk
|
|
103
|
+
x-browser user timeline elonmusk --max 10
|
|
104
|
+
x-browser user followers elonmusk --max 50
|
|
105
|
+
x-browser user following elonmusk
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Self (authenticated user)
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
x-browser me mentions --max 20
|
|
112
|
+
x-browser me bookmarks
|
|
113
|
+
x-browser me bookmark <id-or-url>
|
|
114
|
+
x-browser me unbookmark <id-or-url>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Quick actions
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
x-browser like <id-or-url>
|
|
121
|
+
x-browser retweet <id-or-url>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Output formats
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
x-browser -j tweet get <id> # JSON output
|
|
128
|
+
x-browser -p user get elonmusk # TSV (pipe to awk/cut)
|
|
129
|
+
x-browser -md tweet get <id> # Markdown
|
|
130
|
+
x-browser -v tweet get <id> # Verbose (timestamps, full metrics)
|
|
131
|
+
x-browser --headed tweet post 'hi' # Show browser window
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Automation Scripts
|
|
137
|
+
|
|
138
|
+
Ready-to-run scripts in `scripts/` for long-running tasks:
|
|
139
|
+
|
|
140
|
+
### Search & Like
|
|
141
|
+
|
|
142
|
+
Search for tweets and like them continuously:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
uv run python scripts/search-and-like.py 'YC W26' --hours 5 --headed
|
|
146
|
+
uv run python scripts/search-and-like.py 'AI agents' --hours 8 --min-delay 10 --max-delay 60
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Search & Retweet
|
|
150
|
+
|
|
151
|
+
Search and retweet matching posts:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
uv run python scripts/search-and-retweet.py 'AI startups' --hours 3 --headed
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Engagement Bot
|
|
158
|
+
|
|
159
|
+
Full engagement: search, then like + retweet + follow the author:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
uv run python scripts/engagement-bot.py 'YC W26' --hours 5 --headed
|
|
163
|
+
uv run python scripts/engagement-bot.py 'AI agents' --skip-follow --hours 3 # like + retweet only
|
|
164
|
+
uv run python scripts/engagement-bot.py 'web3' --skip-retweet --hours 2 # like + follow only
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Auto Follow-Back
|
|
168
|
+
|
|
169
|
+
Check your followers and follow back anyone you're not already following:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
uv run python scripts/auto-follow-back.py --headed
|
|
173
|
+
uv run python scripts/auto-follow-back.py --max-check 200 --dry-run # preview without acting
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Auto Reply Mentions
|
|
177
|
+
|
|
178
|
+
Monitor your mentions and auto-reply with templates:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
uv run python scripts/auto-reply-mentions.py --hours 8 --headed
|
|
182
|
+
uv run python scripts/auto-reply-mentions.py --reply 'Thanks for the mention!' --hours 2
|
|
183
|
+
uv run python scripts/auto-reply-mentions.py --replies-file my-replies.txt --hours 12
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Scheduled Poster
|
|
187
|
+
|
|
188
|
+
Post tweets from a text file on a schedule:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# Create a tweets file (one per line, # comments ignored)
|
|
192
|
+
echo 'Hello world!
|
|
193
|
+
Check out x-browser
|
|
194
|
+
# this line is skipped
|
|
195
|
+
Another scheduled tweet' > tweets.txt
|
|
196
|
+
|
|
197
|
+
uv run python scripts/scheduled-poster.py tweets.txt --interval 3600 --headed # every hour
|
|
198
|
+
uv run python scripts/scheduled-poster.py tweets.txt --interval 1800 --random-order # every 30min, shuffled
|
|
199
|
+
uv run python scripts/scheduled-poster.py tweets.txt --interval 600 --loop # every 10min, loop forever
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Use as a Python Module
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
import asyncio
|
|
208
|
+
from x_browser.browser import XBrowser
|
|
209
|
+
from x_browser.actions import tweet_post, like, retweet, follow
|
|
210
|
+
from x_browser.scrapers import tweet_search, user_get, user_timeline
|
|
211
|
+
from x_browser.config import Config
|
|
212
|
+
|
|
213
|
+
async def main():
|
|
214
|
+
config = Config.load()
|
|
215
|
+
async with XBrowser(headless=True) as xb:
|
|
216
|
+
page = xb.page
|
|
217
|
+
|
|
218
|
+
# Post a tweet
|
|
219
|
+
await tweet_post(page, "Hello from Python!", config=config)
|
|
220
|
+
|
|
221
|
+
# Search and like
|
|
222
|
+
tweets = await tweet_search(page, "YC W26", max_results=10)
|
|
223
|
+
for tweet in tweets:
|
|
224
|
+
await like(page, tweet["url"], config=config)
|
|
225
|
+
|
|
226
|
+
# Follow a user
|
|
227
|
+
await follow(page, "elonmusk", config=config)
|
|
228
|
+
|
|
229
|
+
# Get user profile
|
|
230
|
+
profile = await user_get(page, "elonmusk")
|
|
231
|
+
print(profile)
|
|
232
|
+
|
|
233
|
+
asyncio.run(main())
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Custom delays per call
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
await like(page, url, min_delay=5, max_delay=15)
|
|
240
|
+
await tweet_post(page, text, min_delay=0, max_delay=0) # no delay
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Install as a dependency
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
# In your project
|
|
247
|
+
pip install x-browser
|
|
248
|
+
# or add to pyproject.toml
|
|
249
|
+
# dependencies = ["x-browser"]
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
# Then import directly
|
|
254
|
+
from x_browser.browser import XBrowser
|
|
255
|
+
from x_browser.actions import tweet_post
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## AI Agent Integration
|
|
261
|
+
|
|
262
|
+
### Claude Code
|
|
263
|
+
|
|
264
|
+
Add x-browser as a slash command so Claude can use it:
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
# Copy SKILL.md to your Claude commands
|
|
268
|
+
mkdir -p ~/.claude/commands
|
|
269
|
+
cp SKILL.md ~/.claude/commands/x-browser.md
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Now Claude Code can use `/x-browser` to see all available commands.
|
|
273
|
+
|
|
274
|
+
Or add it to a specific project:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
mkdir -p .claude/commands
|
|
278
|
+
cp /path/to/x-browser/SKILL.md .claude/commands/x-browser.md
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### OpenClaw / Other AI Agents
|
|
282
|
+
|
|
283
|
+
Add the contents of `SKILL.md` to your agent's system prompt or tool configuration. The file is structured so any LLM can parse and use the commands.
|
|
284
|
+
|
|
285
|
+
### Building with x-browser
|
|
286
|
+
|
|
287
|
+
If you're building an AI agent that needs X/Twitter capabilities:
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
# In your agent's tool implementation
|
|
291
|
+
from x_browser.browser import XBrowser
|
|
292
|
+
from x_browser.actions import tweet_post, like
|
|
293
|
+
from x_browser.scrapers import tweet_search
|
|
294
|
+
|
|
295
|
+
async def x_post_tool(text: str):
|
|
296
|
+
async with XBrowser() as xb:
|
|
297
|
+
return await tweet_post(xb.page, text)
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Politeness Delay
|
|
303
|
+
|
|
304
|
+
Every write action (post, like, retweet, reply, quote, delete, bookmark, follow) waits a random delay before executing. This makes behavior look human.
|
|
305
|
+
|
|
306
|
+
**Defaults:** 3 – 30 seconds
|
|
307
|
+
|
|
308
|
+
### Configure
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
# Per-run override
|
|
312
|
+
x-browser --min-delay 5 --max-delay 30 like <url>
|
|
313
|
+
|
|
314
|
+
# Persistent config
|
|
315
|
+
x-browser config --min-delay 5 --max-delay 30
|
|
316
|
+
|
|
317
|
+
# View current config
|
|
318
|
+
x-browser config --show
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Config is stored in `~/.config/x-browser/config.json`.
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## How It Works
|
|
326
|
+
|
|
327
|
+
1. **Chrome launches as a normal process** — no automation flags, no bundled Chromium
|
|
328
|
+
2. **Playwright connects via CDP** (Chrome DevTools Protocol) — passive observer
|
|
329
|
+
3. **Session persists** in `~/.config/x-browser/chrome-data/` — login once, use forever
|
|
330
|
+
4. X sees a real Chrome browser with real cookies — indistinguishable from a human
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Troubleshooting
|
|
335
|
+
|
|
336
|
+
### "Google Chrome not found"
|
|
337
|
+
Install Chrome or set the path. x-browser looks in `/Applications/Google Chrome.app/`.
|
|
338
|
+
|
|
339
|
+
### Login fails / "Could not log you in"
|
|
340
|
+
Clear the session and try again:
|
|
341
|
+
```bash
|
|
342
|
+
rm -rf ~/.config/x-browser/chrome-data
|
|
343
|
+
x-browser login
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Shell interprets `!` in tweet text
|
|
347
|
+
Use single quotes: `x-browser tweet post 'Hello, World!'`
|
|
348
|
+
|
|
349
|
+
### `command not found: x-browser`
|
|
350
|
+
Add `~/.local/bin` to your PATH:
|
|
351
|
+
```bash
|
|
352
|
+
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
|
|
353
|
+
source ~/.zshrc
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## Acknowledgements
|
|
359
|
+
|
|
360
|
+
Inspired by [x-cli](https://github.com/Infatoshi/x-cli) by Infatoshi — the API-based counterpart to this project.
|
|
361
|
+
|
|
362
|
+
## License
|
|
363
|
+
|
|
364
|
+
MIT
|