Skip to content
 
 

Repository files navigation

Reddit Bot

Selenium automation with a queue-first control plane and saved Chrome debug profiles

Python Versions CI License

How this repo is actually operated: live Reddit mutations go through a saved Chrome debug profile + SQLite queue/worker (agentctl / reddit-tool). Password/cookie batch runs via main.py and Docker headless are a legacy / owner escape hatch, not the agent or day-to-day path.

Agent runbook: AGENTS.md. Skill fast path: .claude/skills/reddit-bot/ (mirrored to .codex/skills/reddit-bot/).


Table of Contents


Features

Control plane (primary path)

  • Queue + worker — submit actions, lease Chrome profile/debug port, run one pass
  • reddit-tool do — one-shot: build action → queue submit → worker → result
  • Saved Chrome debug profiles — manually authenticated sessions; no scripted login
  • Healer extension — control discovery (confidence, bbox, state) before clicks
  • doctor — read-only diagnostics (DB, identity, DevTools, queue, executor)
  • resolve-url — convert /r/.../s/... share shortlinks to canonical /comments/ URLs
  • Schedules + executor — project-owned RRULE schedules; macOS LaunchAgent wakeup
  • Quotas & leases — daily action quotas and profile leases in SQLite (WAL mode)
  • Identity defaults — sole DB association or REDDIT_BOT_DEFAULT_USER
  • Local web UImake ui on 127.0.0.1:8765; optional REDDIT_BOT_UI_TOKEN for writes

Core Actions

  • Upvote / Downvote posts
  • Comment under posts
  • Join / Leave communities
  • Save / Hide posts
  • Direct Message users
  • Post Submission — text, link, image posts
  • Crosspost content to other subreddits
  • Follow / Unfollow users
  • Update Profile bio
  • Human search / search-upvote compound flows

Reliability & Error Handling

  • Retry logic with exponential backoff — failed actions automatically retry up to 3 times with increasing delays (2s, 4s, 8s)
  • Action verification — checks DOM state after actions to confirm success (e.g., aria-pressed on vote buttons)
  • Screenshot on failure — captures a browser screenshot when an action fails, saved to screenshots/ directory
  • Graceful degradation — if one action or account fails, the bot continues with the remaining work
  • Queue retry — requeue failed jobs via agentctl queue retry

Anti-Detection

  • Proxy support — load a list of proxies and rotate them per account session (legacy batch)
  • User-Agent rotation — randomize the browser fingerprint with realistic Chrome UA strings
  • Headless mode — run without a visible browser window (--headless; legacy batch / Docker)
  • Rate limiting — configurable random delays between actions and between accounts
  • Randomized action ordering — shuffle the action list per account so each executes a unique sequence
  • Human-like mouse movement — move the cursor along Bezier curves before clicking elements
  • Anti-automation flags — disables navigator.webdriver and Chrome automation indicators

Input & Configuration

  • YAML config file — store attach-mode and batch settings in config.yaml
  • Action files — pipe-delimited (|), CSV, and JSON for queue submit / schedules
  • URL validation — rejects non-Reddit and share shortlinks on queue/schedule submit
  • Accounts-file credentials — password/cookie batch path remains available as legacy (see below)

Orchestration

  • Project schedules — register RRULE work with agentctl schedules register --links
  • Daily action quotas — atomic reservations so parallel agents share one budget
  • Parallel agents — coordinated through SQLite queue leases (not ad hoc lock files)
  • Session persistence — cookie restore for legacy main.py batch runs

Reporting & Observability

  • Structured logging — JSON log output option for machine parsing, with colored terminal output
  • Execution summary — ASCII table printed at the end showing success/failure per action
  • Progress bar — visual progress indicator via tqdm for long runs
  • Webhook notifications — send results to Discord, Slack, or any generic JSON webhook on completion or failure
  • reddit-tool errors / overview — recent failures and control-plane snapshot

Database Tracking

  • SQLite (WAL) — action log, queue, leases, quotas, schedules, profile associations
  • Duplicate prevention — skips actions already successfully performed by the same account
  • Daily stats — tracks action counts per account per day for quota enforcement

Developer Experience

  • Unit tests — control plane, actions, database, UI API, and parsing coverage
  • CI pipeline — GitHub Actions workflow for automated testing on supported Python versions
  • Plugin architecture — actions are modular classes; add new actions without modifying core bot logic
  • Installable packagepyproject.toml for pip install . support
  • Dockerfile — legacy headless batch only (not for Chrome profile attach)

Installation

Standard Installation

Requires Python 3.10 or newer.

git clone https://github.com/markmelnic/reddit-bot
cd reddit-bot
pip install -e .

Development Installation

pip install -e ".[dev]"

or with uv:

uv sync --extra dev

After install, CLIs are available as reddit-tool, reddit-agentctl, and reddit-ui. From a checkout, the same entry points are:

.venv/bin/python scripts/reddit_tool.py --help
.venv/bin/python scripts/agentctl.py --help
.venv/bin/python scripts/reddit_ui.py --help

Note: Chrome and chromedriver are managed by webdriver-manager when the bot launches a browser. The primary path attaches to an already-running saved Chrome debug profile instead.

Local Web UI

make ui

The dashboard binds to 127.0.0.1:8765 by default and uses the same agentctl/reddit-tool control plane as the terminal workflow. See docs/local-ui.md. Optional write protection:

export REDDIT_BOT_UI_TOKEN='your-long-random-token'
make ui

When set, every UI POST must send X-Reddit-Bot-Token.


Quick Start

Primary path: saved Chrome profilequeue / reddit-tool do. Agents should follow AGENTS.md. Do not invent commands; use --help.

0. Inspect shared state

.venv/bin/python scripts/agentctl.py status
# or, when something looks wrong:
.venv/bin/python scripts/reddit_tool.py doctor --json

1. Open a manually authenticated Chrome profile

.venv/bin/python scripts/reddit_healer_debug.py open-profile
# Log in manually once inside that window. Do not script Reddit login.

Default profile: Chrome Reddit Bot Debug Profile on 127.0.0.1:9222.

2. Associate profile ↔ Reddit user (once)

.venv/bin/python scripts/agentctl.py profiles associate \
  --profile-name "Chrome Reddit Bot Debug Profile" \
  --reddit-user "u/Particular-Arm2102"

With a sole association (or REDDIT_BOT_DEFAULT_USER set), identity flags can be omitted on later commands.

3a. One action end to end (reddit-tool do)

.venv/bin/python scripts/reddit_tool.py do \
  --action upvote \
  --link "https://www.reddit.com/r/example/comments/abc123/title/" \
  --reddit-user "u/Particular-Arm2102"

do builds the action file, submits to the queue, runs one worker pass, and prints the outcome. Share shortlinks (/r/.../s/...) are rejected — resolve first:

.venv/bin/python scripts/reddit_tool.py resolve-url \
  --link "https://www.reddit.com/r/example/s/SHAREID" --json

3b. Queue a links file, then one worker pass

.venv/bin/python scripts/agentctl.py queue submit \
  --reddit-user "u/Particular-Arm2102" \
  --links links.txt

.venv/bin/python scripts/agentctl.py queue worker --once

Interactive menu

.venv/bin/python scripts/reddit_tool.py menu

Legacy batch path (main.py)

Password/cookie multi-account runs and dry-run previews still use main.py. This is an owner escape hatch, not the agent default:

python main.py --accounts accounts.txt --links links.txt --verbose
python main.py -a accounts.txt -l links.txt --dry-run --verbose
python main.py --config config.yaml

For attach mode without the queue (also owner-only):

.venv/bin/python main.py -a accounts.txt -l links.txt --verbose \
  --use-existing-chrome \
  --chrome-debugging-address 127.0.0.1:9222 \
  --chrome-extension-healer

Saved Chrome Debug Profiles

The preferred local workflow is to use manually authenticated Chrome profiles and attach through Chrome DevTools. This avoids scripted Reddit login and lets the Reddit Bot Healer extension identify controls before actions are clicked.

Default saved profile:

Name Path DevTools
Chrome Reddit Bot Debug Profile /Users/raulvecchione/Library/Application Support/Chrome Reddit Bot Debug Profile 127.0.0.1:9222

Open the default profile:

.venv/bin/python scripts/reddit_healer_debug.py open-profile

Open a saved profile for another Reddit account on a different port:

.venv/bin/python scripts/reddit_healer_debug.py open-profile \
  --profile-name "Chrome Reddit Bot Debug Profile - account2" \
  --port 9223 \
  --url "https://www.reddit.com/login/"

Log in manually in that Chrome window. Do not automate Reddit login for this workflow. The helper passes --load-extension=chrome_extension/reddit_healer; if Chrome does not show Reddit Bot Healer under chrome://extensions, enable Developer mode and load that unpacked extension manually.

Check the extension bridge:

.venv/bin/python scripts/reddit_healer_debug.py ping-bridge \
  --debug-address 127.0.0.1:9223

Find a control candidate before clicking:

.venv/bin/python scripts/reddit_healer_debug.py find-control \
  --debug-address 127.0.0.1:9223 \
  --intent downvote \
  --url "https://www.reddit.com/r/example/comments/abc/title/"

The expected action report includes candidate confidence, bounding box, state, and evidence. Click only when the command or user request explicitly calls for a real action.

Run live work through the control plane (preferred):

.venv/bin/python scripts/agentctl.py queue submit \
  --profile-name "Chrome Reddit Bot Debug Profile - account2" \
  --links links.txt
.venv/bin/python scripts/agentctl.py queue worker --once

Owner-only direct attach (escape hatch, not for agents):

.venv/bin/python main.py -a accounts.txt -l links.txt --verbose \
  --use-existing-chrome \
  --chrome-debugging-address 127.0.0.1:9223 \
  --chrome-extension-healer

For attach mode, accounts.txt is only an account label. The active Reddit account is whatever is manually logged in inside the Chrome profile on that port. Use one saved profile/port per Reddit account.

Print reusable setup details for any profile:

.venv/bin/python scripts/reddit_healer_debug.py profile-info \
  --profile-name "Chrome Reddit Bot Debug Profile - account2" \
  --port 9223

Configuration

Settings can be provided via YAML config file, CLI flags, or environment variables. Priority order (highest to lowest):

  1. CLI arguments — override everything
  2. Environment variables — override config file
  3. YAML config file — base configuration

Example config.yaml

accounts_path: "accounts.txt"
links_path: "links.txt"

verbose: true
headless: false
dry_run: false

rotate_user_agent: true
randomize_actions: true
human_mouse: false

proxy:
  enabled: true
  proxy_list_path: "proxies.txt"
  rotate_per_account: true

rate_limit:
  min_action_delay: 2.0
  max_action_delay: 8.0
  min_account_delay: 5.0
  max_account_delay: 15.0
  daily_action_quota: 50

parallel_accounts: 1
session_persistence: true

screenshot_on_failure: true
db_path: "reddit_bot.db"

webhook:
  enabled: true
  url: "https://discord.com/api/webhooks/..."
  on_completion: true
  on_failure: true

See config.example.yaml for the full template with comments.

Environment Variables

Variable Description
REDDIT_ACCOUNT_1 Account credentials as username|password
REDDIT_ACCOUNT_2 Second account, and so on
REDDIT_BOT_KEY Passphrase for encrypted credential files
REDDIT_BOT_ACCOUNTS Path to accounts file
REDDIT_BOT_LINKS Path to links file
REDDIT_BOT_HEADLESS Enable headless mode (true/false)
REDDIT_BOT_DRY_RUN Enable dry run (true/false)
REDDIT_BOT_DB_PATH Path to SQLite database
REDDIT_BOT_LOG_DIR Directory for durable JSONL logs
REDDIT_BOT_LOG_FILE File name for durable bot logs
REDDIT_BOT_WEBHOOK_URL Webhook URL for notifications
REDDIT_BOT_USE_EXISTING_CHROME Use already-authenticated Chrome profile/instance (true/false)
REDDIT_BOT_CHROME_USER_DATA_DIR Path to Chrome user data directory
REDDIT_BOT_CHROME_PROFILE_NAME Chrome profile folder name (e.g. Default)
REDDIT_BOT_CHROME_DEBUGGING_ADDRESS Existing Chrome debugger address (e.g. 127.0.0.1:9222)
REDDIT_BOT_SELECTOR_CACHE_PATH Path for healed Reddit selector cache
REDDIT_BOT_SELECTOR_DIAGNOSTICS_DIR Directory for selector diagnostics when healing fails
REDDIT_BOT_SELECTOR_FALLBACK_WAIT Short wait for legacy selector fallbacks
REDDIT_BOT_SELENIUM_IMPLICIT_WAIT Default Selenium implicit wait
REDDIT_BOT_CHROME_EXTENSION_HEALER_ENABLED Enable the Reddit healer Chrome extension bridge
REDDIT_BOT_CHROME_EXTENSION_PATH Path to the unpacked healer extension
REDDIT_BOT_CHROME_EXTENSION_BRIDGE_TIMEOUT_MS Timeout for extension bridge requests
REDDIT_BOT_CHROME_EXTENSION_MIN_CONFIDENCE Minimum control confidence required before clicking
REDDIT_BOT_DEFAULT_USER Default Reddit identity when no --reddit-user / --profile-name is passed (must match a chrome_profile_accounts row; sole association is preferred)
REDDIT_BOT_UI_TOKEN Optional shared secret for local UI write (POST) routes

Input Formats

Action/links files are used by queue submit, schedules, and legacy batch. Accounts files are for the legacy main.py password path only (control plane uses Chrome profile associations).

Accounts (legacy batch)

Pipe-delimited (default):

username1|password1
username2|password2

CSV:

username,password
username1,password1
username2,password2

JSON:

[
  {"username": "username1", "password": "password1"},
  {"username": "username2", "password": "password2"}
]

Actions / Links

Pipe-delimited (default):

https://www.reddit.com/r/ProgrammerHumor/comments/abc123/title|upvote
https://www.reddit.com/r/ProgrammerHumor/comments/xyz789/title|comment|Great post!
https://www.reddit.com/r/ProgrammerHumor/|join
https://www.reddit.com/r/ProgrammerHumor/comments/abc123/title|save
https://www.reddit.com/user/someone|follow

CSV:

link,action,comment,title,subreddit,body,recipient,message
https://reddit.com/r/test/comments/abc,upvote,,,,,,
https://reddit.com/r/test/comments/abc,comment,Hello!,,,,,
https://reddit.com/r/test,join,,,,,,
,dm,,,,,targetuser,Hello from the bot!
,post_text,,,My Post Title,ProgrammerHumor,Post body text,,

JSON:

[
  {"link": "https://reddit.com/r/test/comments/abc", "action": "upvote"},
  {"link": "https://reddit.com/r/test/comments/abc", "action": "comment", "comment": "Hello!"},
  {"action": "dm", "recipient": "targetuser", "title": "Subject", "message": "Hello!"},
  {"action": "post_text", "subreddit": "ProgrammerHumor", "title": "My Post", "body": "Content here"}
]

Supported Actions

Action Description Required Fields
upvote Upvote a post link
downvote Downvote a post link
comment Post a comment link, comment
join Join a subreddit link
leave Leave a subreddit link
save Save a post link
hide Hide a post link
dm Send a direct message recipient, message (optional: title)
post_text Create a text post subreddit, title (optional: body, flair)
post_link Create a link post subreddit, title, body (the URL)
post_image Create an image post subreddit, title, body (image file path)
crosspost Crosspost to another sub link, subreddit (optional: title)
follow Follow a user link (user profile URL)
unfollow Unfollow a user link (user profile URL)
update_bio Update profile bio body (bio text)

Anti-Detection

The flags below apply mainly to legacy main.py batch sessions. The control-plane path relies on a real saved Chrome profile (manual login, Healer extension) rather than scripted UA/proxy login.

Proxy Rotation

Create a proxies.txt file:

host1:port1
host2:port2
host3:port3:username:password
python main.py -a accounts.txt -l links.txt --proxy-list proxies.txt

Proxies rotate round-robin for each new account session.

User-Agent Rotation

python main.py -a accounts.txt -l links.txt --rotate-ua

Each browser session starts with a random Chrome user agent from a pool of realistic UA strings.

Human-Like Mouse Movement

python main.py -a accounts.txt -l links.txt --human-mouse

Uses Bezier curves to generate natural cursor paths before clicking elements. Install the optional dependency group before enabling it:

pip install -e ".[mouse]"

Without the optional extra, human mouse mode falls back to the standard Selenium pointer click path.

Headless Mode

python main.py -a accounts.txt -l links.txt --headless

Runs Chrome without a visible window. Useful for servers and Docker.

Action Randomization

python main.py -a accounts.txt -l links.txt --randomize-actions

Shuffles the action list for each account so they don't all perform the same sequence.


Orchestration

Agentic Operation (control plane)

LLM agents should start with the agent runbook and skill, not main.py:

# Read AGENTS.md for policy; use the reddit-bot skill for the fast path.
.venv/bin/python scripts/agentctl.py status
.venv/bin/python scripts/reddit_tool.py doctor --json
.venv/bin/python scripts/reddit_tool.py capabilities

Live mutations: reddit-tool do or queue submit + one worker pass:

.venv/bin/python scripts/agentctl.py profiles associate \
  --profile-name "Chrome Reddit Bot Debug Profile" \
  --reddit-user "u/Particular-Arm2102"

.venv/bin/python scripts/reddit_tool.py do \
  --action upvote \
  --link "https://www.reddit.com/r/example/comments/abc/title/" \
  --reddit-user "u/Particular-Arm2102"

# Equivalent multi-action flow:
.venv/bin/python scripts/agentctl.py queue submit \
  --reddit-user "u/Particular-Arm2102" \
  --links links.txt
.venv/bin/python scripts/agentctl.py --config config.yaml queue worker --once

Agents can also queue by Chrome profile with --profile-name. The queue uses SQLite WAL, leases, and atomic daily quota reservations. See docs/agentic-operations.md and docs/scheduler-and-rate-limits.md.

Human-Friendly Operations CLI

Use scripts/reddit_tool.py (reddit-tool after install) for day-to-day work. Live actions still go through the project queue, schedules, executor, leases, and quotas.

.venv/bin/python scripts/reddit_tool.py menu
.venv/bin/python scripts/reddit_tool.py overview
.venv/bin/python scripts/reddit_tool.py doctor --json
.venv/bin/python scripts/reddit_tool.py resolve-url --link "<share_or_post_url>" --json
.venv/bin/python scripts/reddit_tool.py schedules
.venv/bin/python scripts/reddit_tool.py queue --status failed
.venv/bin/python scripts/reddit_tool.py executor
.venv/bin/python scripts/reddit_tool.py errors

Add a one-time scheduled action (writes the action file automatically):

.venv/bin/python scripts/reddit_tool.py schedule add \
  --name "Upvote example post" \
  --link "https://www.reddit.com/r/example/comments/abc/title/" \
  --action upvote \
  --at "2026-07-06T09:00:00"

Register a recurring schedule from an existing links/action file:

.venv/bin/python scripts/reddit_tool.py schedule add \
  --name "Weekday Reddit actions" \
  --links links.txt \
  --weekly MO,WE,FR \
  --time 09:30

Submit immediate queue work without running it yet:

.venv/bin/python scripts/reddit_tool.py queue add --links links.txt

Run due project schedules and exactly one worker pass:

.venv/bin/python scripts/reddit_tool.py schedule run-due --run-worker

Daily Quotas (control plane)

.venv/bin/python scripts/agentctl.py limits set \
  --account "Particular-Arm2102" \
  --daily-action-quota 25
.venv/bin/python scripts/agentctl.py limits list

Legacy config.yaml rate_limit.daily_action_quota still applies to direct main.py batch runs; agents should use agentctl limits.

Legacy batch orchestration (main.py)

These flags remain for owner-controlled multi-account batch runs. Prefer the queue for anything an agent or schedule should own.

# Parallel browsers (legacy batch)
python main.py -a accounts.txt -l links.txt --parallel 3

# Simple interval schedule inside main.py (prefer agentctl schedules)
python main.py -a accounts.txt -l links.txt --schedule "0 */6 * * *"

# Cookie session files under .sessions/
python main.py -a accounts.txt -l links.txt --session-persistence

Direct attach without the queue (owner escape hatch):

.venv/bin/python scripts/reddit_healer_debug.py open-profile \
  --profile-name "Chrome Reddit Bot Debug Profile" \
  --port 9222

.venv/bin/python main.py -a accounts.txt -l links.txt \
  --use-existing-chrome \
  --chrome-debugging-address 127.0.0.1:9222 \
  --chrome-extension-healer

You can also pass --chrome-user-data-dir when not attaching to a running debugger. Attach mode is safer: login stays manual, DevTools is inspectable, and the Healer extension can already be loaded in that profile.

Saved profile per account

Use one Chrome user-data-dir and one port per Reddit account. Example:

.venv/bin/python scripts/reddit_healer_debug.py open-profile \
  --profile-name "Chrome Reddit Bot Debug Profile - account3" \
  --port 9224 \
  --url "https://www.reddit.com/login/"

After manual login, associate and queue with that profile/port — do not reuse one debugger address across accounts.


Credentials & Security

Primary operation uses manually authenticated Chrome profiles — no password file required. The sections below apply only to the legacy main.py accounts-file path.

Encrypt Credentials (legacy batch)

First, encrypt your accounts file:

from bot.utils.credentials import encrypt_file
encrypt_file("accounts.txt", "accounts.bin", "your-secret-passphrase")

Then run with the encrypted file:

export REDDIT_BOT_KEY="your-secret-passphrase"
python main.py -a accounts.bin -l links.txt --encrypt-credentials

Environment Variable Accounts (legacy batch)

export REDDIT_ACCOUNT_1="username1|password1"
export REDDIT_ACCOUNT_2="username2|password2"
python main.py -l links.txt

Reporting & Notifications

Durable Logs

Every bot run creates durable JSON-line logs at logs/reddit-bot.log by default, even when --verbose is not enabled. The Chrome debug helper writes command failures to logs/reddit-healer-debug.log.

Weekly troubleshooting instructions live in docs/weekly-log-maintenance.md.

Override the bot log location from config, environment variables, or CLI:

log_dir: "logs"
log_file: "reddit-bot.log"
python main.py -a accounts.txt -l links.txt --log-dir logs --log-file reddit-bot.log

Execution Summary

With --verbose, a summary table is printed after completion:

================================================================================
EXECUTION SUMMARY
================================================================================
Duration: 142.3s | Total: 12 | Success: 10 | Failed: 2
--------------------------------------------------------------------------------
Status   Action          Link                                Message
--------------------------------------------------------------------------------
OK       upvote          https://reddit.com/r/test/comme..   Vote registered
OK       comment         https://reddit.com/r/test/comme..   Comment posted
FAIL     join            https://reddit.com/r/private        Message: NoSuchElem..
OK       follow          https://reddit.com/user/someone     User followed
================================================================================

Webhook Notifications

Discord

webhook:
  enabled: true
  url: "https://discord.com/api/webhooks/YOUR_WEBHOOK_ID/YOUR_WEBHOOK_TOKEN"

Sends a rich embed with color-coded success/failure status.

Slack

webhook:
  enabled: true
  url: "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"

Generic JSON

Any other URL receives the full execution summary as JSON.

Screenshots on Failure

python main.py -a accounts.txt -l links.txt --screenshot-on-failure

Screenshots are saved to screenshots/ with descriptive filenames including the action name and timestamp.

Self-Healing Selectors

Reddit changes UI markup frequently. The bot keeps a local selector cache in .selector-healing/reddit_selectors.json and writes compact diagnostics to .selector-healing/diagnostics/ when an action cannot find a control.

When a normal selector misses, actions can run a browser-side probe that logs a reddit-bot:self-healing response in the page console, reads the structured result back through Selenium, and stores the discovered selector for future runs.

Chrome Extension Healer

The repo includes an unpacked Chrome extension at chrome_extension/reddit_healer. When enabled, vote actions ask the extension for a control candidate before falling back to Selenium selectors. The extension observes Reddit from inside Chrome, including dynamic DOM state, open shadow DOM, button attribute changes, clicks, page console logs, and fetch/XHR responses.

Enable it in config:

chrome_extension_healer_enabled: true
chrome_extension_path: "chrome_extension/reddit_healer"
chrome_extension_min_confidence: 0.8

Or from the legacy CLI:

python main.py -a accounts.txt -l links.txt --chrome-extension-healer

Queue workers honor the same config when attaching to a saved profile. For Chrome launched by the bot, the extension is loaded from chrome_extension_path. For attach mode, load the unpacked extension in that Chrome profile first (open-profile already passes --load-extension).

When operating through saved debug profiles, use the helper first:

.venv/bin/python scripts/reddit_healer_debug.py ping-bridge --debug-address 127.0.0.1:9222
.venv/bin/python scripts/reddit_healer_debug.py find-control --debug-address 127.0.0.1:9222 --intent upvote --url "<POST_URL>"

Report the returned best candidate's confidence, bounding box, state, and evidence before executing the click in manual testing tasks.


Database Tracking

All coordination and action history live in SQLite (reddit_bot.db by default). Connections open with WAL (PRAGMA journal_mode=WAL, synchronous=NORMAL) so readers (UI, agents, status) are less blocked by writers. Backup with the SQLite backup API, or stop writers first — do not copy only the main file while the process is running.

-- View all actions
SELECT * FROM action_log ORDER BY timestamp DESC;

-- View failures
SELECT * FROM action_log WHERE success = 0;

-- View daily stats per account
SELECT * FROM account_stats WHERE action_date = date('now');

The action log prevents duplicate successful actions for the same account. The same database also stores the agent queue, leases, quotas, schedules, and Chrome profile ↔ Reddit user associations. Details: docs/scheduler-and-rate-limits.md.


Docker (legacy batch)

Legacy only. The image runs main.py --headless for password/cookie batch jobs. It is not the Chrome debug-profile attach path and is not suitable for the queue-first agent workflow on a local Mac with saved profiles.

docker build -t reddit-bot .

docker run -v $(pwd)/accounts.txt:/app/accounts.txt \
           -v $(pwd)/links.txt:/app/links.txt \
           reddit-bot -a accounts.txt -l links.txt --verbose

With a config file:

docker run -v $(pwd)/config.yaml:/app/config.yaml \
           -v $(pwd)/accounts.txt:/app/accounts.txt \
           -v $(pwd)/links.txt:/app/links.txt \
           reddit-bot --config config.yaml

For day-to-day operation, use Quick Start above (profile + reddit-tool / agentctl) instead of Docker.


CLI Reference

Control plane (primary)

Verify flags with --help — do not invent subcommands.

.venv/bin/python scripts/agentctl.py --help
.venv/bin/python scripts/agentctl.py status
.venv/bin/python scripts/agentctl.py profiles --help
.venv/bin/python scripts/agentctl.py queue --help
.venv/bin/python scripts/agentctl.py schedules --help
.venv/bin/python scripts/agentctl.py limits --help
.venv/bin/python scripts/agentctl.py executor --help

.venv/bin/python scripts/reddit_tool.py --help
.venv/bin/python scripts/reddit_tool.py doctor --json
.venv/bin/python scripts/reddit_tool.py resolve-url --link URL
.venv/bin/python scripts/reddit_tool.py do --action upvote --link URL
.venv/bin/python scripts/reddit_tool.py capabilities

After install: reddit-agentctl, reddit-tool, reddit-ui.

main.py (legacy / owner escape hatch)

usage: reddit-bot [-h] [-a ACCOUNTS] [-l LINKS] [-c CONFIG] [-v]
                  [--headless] [--dry-run] [--proxy-list PROXY_LIST]
                  [--rotate-ua] [--randomize-actions] [--human-mouse]
                  [--manual-login] [--use-existing-chrome]
                  [--chrome-user-data-dir CHROME_USER_DATA_DIR]
                  [--chrome-profile-name CHROME_PROFILE_NAME]
                  [--chrome-debugging-address CHROME_DEBUGGING_ADDRESS]
                  [--parallel PARALLEL] [--schedule SCHEDULE]
                  [--session-persistence] [--encrypt-credentials]
                  [--screenshot-on-failure] [--webhook-url WEBHOOK_URL]

Legacy multi-account Selenium batch entry (owner escape hatch).

options:
  -h, --help            Show this help message and exit
  -a, --accounts        Path to accounts file (pipe, CSV, or JSON)
  -l, --links           Path to actions file (pipe, CSV, or JSON)
  -c, --config          Path to YAML configuration file
  -v, --verbose         Enable verbose logging to stdout
  --headless            Run browser in headless mode
  --dry-run             Log actions without executing them
  --proxy-list          Path to proxy list file (host:port per line)
  --rotate-ua           Randomize User-Agent per session
  --randomize-actions   Shuffle action order per account
  --human-mouse         Use Bezier curve mouse movements
  --manual-login        Pause for manual browser login when automatic login fails
  --use-existing-chrome Use an already logged-in Chrome instance instead of automated login
  --chrome-user-data-dir CHROME_USER_DATA_DIR
                        Chrome user-data-dir to reuse profile/session
  --chrome-profile-name CHROME_PROFILE_NAME
                        Chrome profile directory under user-data-dir (default: Default)
  --chrome-debugging-address CHROME_DEBUGGING_ADDRESS
                        Existing Chrome debugger address (e.g. 127.0.0.1:9222)
  --parallel N          Number of parallel browser instances
  --schedule CRON       Cron expression for scheduled runs
  --session-persistence Save/restore browser sessions
  --encrypt-credentials Accounts file is encrypted
  --screenshot-on-failure Capture screenshots on action failure
  --webhook-url URL     Webhook URL for notifications
  --log-dir DIR         Directory for durable bot logs
  --log-file FILE       File name for durable bot logs

Testing

Run the test suite:

# Install dev dependencies
pip install -e ".[dev]"

# Run all tests
make test

# Run all tests through uv
uv run pytest -q

# Run tests with the CI coverage report
pytest tests/ -q --cov=bot --cov=main --cov=args --cov-report=term-missing

# Run specific test module
pytest tests/test_config.py -q
pytest tests/test_database.py -q

Current coverage baseline: 63% total statement coverage from the CI coverage command.

TOTAL 5365 statements, 1971 missed, 63% covered

Tests cover:

  • Configuration loading, merging, and env var parsing
  • Input file parsing (pipe-delimited, CSV, JSON)
  • URL validation
  • Credential encryption/decryption
  • Proxy loading and rotation
  • Database action logging and queries
  • Execution summary reporting

Architecture

Two operational paths share the same Selenium action plugins and SQLite DB:

Path Entry When to use
Control plane (primary) scripts/agentctl.py, scripts/reddit_tool.py, make ui Agents, schedules, day-to-day live work via queue + Chrome debug profile
Legacy batch (escape hatch) main.py, Docker ENTRYPOINT Owner multi-account password/cookie/headless batch; not for agents
reddit-bot/
├── main.py                    # Legacy batch entry (owner escape hatch)
├── args.py                    # main.py CLI argument parser
├── config.example.yaml        # Example configuration file
├── pyproject.toml             # Package configuration
├── Dockerfile                 # Legacy headless batch image only
├── Makefile                   # test, check-skill, sync-skill, ui
├── AGENTS.md                  # Agent runbook and live-action policy
├── AGENT.md                   # Short Chrome-profile playbook
├── bot/
│   ├── __init__.py
│   ├── bot.py                 # Core RedditBot class (Selenium lifecycle)
│   ├── config.py              # BotConfig dataclass with YAML/env support
│   ├── database.py            # SQLite WAL: action log, queue, leases, schedules, quotas
│   ├── action_schema.py       # Machine-readable reddit-tool action schema
│   ├── agentctl.py            # Agent-safe control plane CLI
│   ├── tool_cli.py            # Human-friendly reddit-tool CLI and JSON envelope
│   ├── skills_sync.py         # .claude -> .codex skill mirror utility
│   ├── reporting.py           # Summary, durable structured logging, webhooks
│   ├── control/               # Shared control-plane helpers
│   │   ├── doctor.py          # Read-only diagnostics
│   │   ├── resolve.py         # Share-URL resolution helpers
│   │   ├── profiles.py        # Profile discovery + identity defaults
│   │   ├── queue.py           # Queue submit/worker helpers
│   │   ├── limits.py          # Quota helpers
│   │   ├── executor.py        # LaunchAgent / executor helpers
│   │   ├── status.py          # status JSON assembly
│   │   ├── errors.py          # CLI exception types
│   │   └── schedules.py       # RRULE, cadence, and schedule ID helpers
│   ├── actions/               # Plugin-based action system
│   │   ├── base.py            # BaseAction ABC and ActionResult
│   │   ├── registry.py        # Action name -> class mapping
│   │   ├── search.py          # Human search and search_upvote flows
│   │   ├── vote.py            # Upvote/downvote
│   │   ├── comment.py         # Comment on post
│   │   ├── community.py       # Join/leave subreddit
│   │   ├── save_hide.py       # Save/hide post
│   │   ├── post.py            # Text/link/image post, crosspost
│   │   ├── dm.py              # Direct messages
│   │   ├── follow.py          # Follow/unfollow users
│   │   └── profile.py         # Update bio
│   ├── utils/                 # Shared utilities
│   │   ├── chrome_extension_bridge.py # Reddit healer extension bridge
│   │   ├── chromedriver.py    # ChromeDriver resolution helper
│   │   ├── reddit_urls.py     # Canonical / share URL helpers
│   │   ├── clock.py           # UTC timestamp helpers
│   │   ├── timeouts.py        # Randomized delays
│   │   ├── retry.py           # Exponential backoff decorator
│   │   ├── mouse.py           # Bezier curve mouse movement
│   │   ├── self_healing.py    # Runtime selector healing
│   │   ├── visible_vote.py    # Visible vote control diagnostics/clicking
│   │   ├── user_agents.py     # UA string rotation
│   │   ├── credentials.py     # Account parsing and encryption (legacy batch)
│   │   ├── input_parser.py    # Action file parsing
│   │   ├── validators.py      # URL validation
│   │   └── proxy.py           # Proxy loading and rotation
│   └── web/                   # Localhost-only dashboard server
│       └── server.py          # API and static file server
├── chrome_extension/          # Reddit healer Chrome extension
├── docs/                      # Agent operations, scheduling, UI, and maintenance docs
├── scripts/                   # Thin CLI launchers (agentctl, reddit_tool, ui, healer)
├── tests/                     # Unit and API test suite
├── web/                       # Zero-build dashboard frontend
└── .github/
    └── workflows/
        └── ci.yml             # GitHub Actions CI pipeline

Adding Custom Actions

Create a new action by extending BaseAction:

# bot/actions/my_action.py
from bot.actions.base import BaseAction, ActionResult

class MyCustomAction(BaseAction):
    name = "my_action"

    def execute(self, link="", **kwargs):
        self.logger.info(f"Running my action on {link}")
        if self.config.dry_run:
            return ActionResult(success=True, action="my_action", link=link, message="Dry run")

        # Your Selenium logic here
        self._navigate(link)
        element = self._find_with_fallbacks(
            (By.CSS_SELECTOR, "button.my-button"),
            (By.XPATH, "//button[text()='Click Me']"),
        )
        self._click(element)

        return ActionResult(success=True, action="my_action", link=link, message="Done")

Register it in bot/actions/registry.py:

from .my_action import MyCustomAction

# In ActionRegistry._action_map:
"my_action": MyCustomAction,

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/new-action)
  3. Write tests for any new functionality
  4. Run the test suite (make test)
  5. Commit your changes
  6. Push to your branch
  7. Open a Pull Request

License

MIT License - see LICENSE for details.

About

Simple bot to interact with Reddit via Selenium using Chrome webdriver.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages