No description
Find a file
2026-05-25 11:59:39 -04:00
cmd/love-machine refactor: rename research dir to data 2026-05-25 11:59:39 -04:00
internal chore: prepare repo for publication 2026-05-24 21:06:22 -04:00
pkg/remilia chore: prepare repo for publication 2026-05-24 21:06:22 -04:00
.gitignore refactor: rename research dir to data 2026-05-25 11:59:39 -04:00
go.mod chore: prepare repo for publication 2026-05-24 21:06:22 -04:00
go.sum chore: prepare repo for publication 2026-05-24 21:06:22 -04:00
LICENSE chore: prepare repo for publication 2026-05-24 21:06:22 -04:00
README.md refactor: rename research dir to data 2026-05-25 11:59:39 -04:00

love machine

a bot that lives inside your browser, borrowing your soul to reach out and poke strangers on the internet — making friends while you sleep, harvesting names from a shoutbox, walking the graph of who knows who.

it doesn't have credentials. it just is you, at scale.

all HTTP runs through your authenticated browser tab via Chrome DevTools Protocol. the session is yours. the bot just uses it.

status: research project. this codebase is unfinished and no longer maintained. it was built as an experiment — use at your own risk, expect rough edges, and don't rely on it for anything important.


structure

├── cmd/love-machine/              # main binary + loops + TUI
│   ├── main.go                    # entry point, orchestrates all goroutines
│   ├── chromium.go                # headless Chromium lifecycle
│   ├── args.go                    # CLI flags
│   ├── auth_flow.go                # startup auth classification
│   ├── loops/                     # concurrent loops
│   │   ├── poker.go               # burst-pokes ~80% of pool per session
│   │   ├── pokeback.go            # polls poke notifications, pokes back
│   │   ├── accept.go              # accepts incoming friend requests
│   │   ├── friendadd.go           # reciprocal friend + poke after accept
│   │   ├── miladychan.go          # shoutbox WebSocket harvester
│   │   ├── beetle.go              # beetle game automation
│   │   ├── friendenum.go          # BFS friend graph walker
│   │   ├── profilefetch.go        # background profile cache
│   │   ├── interaction_guard.go     # protected-user filtering
│   │   └── paths.go               # external runtime data resolution
│   └── tui/                       # Bubble Tea terminal UI
│       ├── model.go               # TUI state machine
│       └── styles.go              # lipgloss styles
├── internal/
│   ├── cache/                     # file-based response cache
│   ├── config/                    # timing constants, URLs, junk items, protected users, quiet hours
│   ├── pool/                      # user pool (thread-safe set)
│   ├── profile/                   # profile aggregation, leaderboard, stale detection
│   └── state/                     # persisted state (cooldowns, accepted, beetle, poked-back, notifications)
├── pkg/remilia/                   # Remilia CDP client library
├── go.mod
├── go.sum
└── README.md

what it does

nine concurrent loops over a single CDP connection:

loop what
poker_loop bursts ~80% of the pool per session, ~7s between pokes, long gaussian gap between sessions
poke_back_loop polls unread poke notifications every 3 min, pokes back
accept_loop polls incoming friend requests every 5 min, accepts all
add_back_loop drains the add-back queue, sends friend request + poke after 530 min delay
friend_add_loop fetches top friends' friend lists, sends friend requests to mutuals not in pool/cooldown/protected, 310 per session at 3090 min intervals
miladychan_ws_loop connects to the miladychan shoutbox WebSocket, harvests usernames
beetle_loop monitors the beetle game, runs safe cooldown actions and crafting
friend_enum_loop BFS walks the friend graph in the background, grows the pool
profile_fetch_loop fetches and caches user profiles in the background

the pool starts from seed files under data/ (miladychan_users.json + friends_pool.json) at launch and grows live from the activity feed and shoutbox. the pool itself is in-memory — runtime membership is not written back to those files.

cooldowns are ~20h server-enforced per user. the remaining time is parsed from the error message and stored in data/milady_state.json. state persists across restarts.


build

go build -o love-machine ./cmd/love-machine

seed data dir

seed/state files live under data/ at the project root:

mkdir -p data
file purpose
miladychan_users.json shoutbox-harvested usernames to seed the pool
friends_pool.json friend-graph walker output to seed the pool
milady_state.json persisted cooldowns, accepted friends, beetle state
cache/ API response caches (friend list, beetle)
profile_data/ fetched user profiles
leaderboard.json / aggregates.json rebuilt by profile fetch loop

the bot reads pool seeds at startup and writes state, cache, and profile data back. the in-memory pool itself does not persist.


running locally (against Brave)

launch Brave with remote debugging:

brave --remote-debugging-port=9223

verify:

curl -s http://localhost:9223/json/version

then:

./love-machine
./love-machine --dry-run --mean-minutes 60 --collect-seconds 60

login mode

if you need to authenticate first (fresh profile):

./love-machine --login --chromium-profile ~/.config/chromium-remilia

this opens a visible browser window. log in to remilia.net, then close the window — the session is saved.


running on a server (headless Chromium)

1. install Chromium

# Arch
sudo pacman -S chromium

# Debian/Ubuntu
sudo apt install -y chromium

2. copy your session to the server

on your local machine:

tar czf /tmp/session.tar.gz \
  -C ~/.config/BraveSoftware/Brave-Browser/Default \
  Cookies "Local Storage" "Session Storage" IndexedDB

scp /tmp/session.tar.gz user@yourserver:~/

on the server:

mkdir -p ~/.config/chromium-remilia/Default
cd ~/.config/chromium-remilia/Default
tar xzf ~/session.tar.gz

3. run

./love-machine --chromium-profile ~/.config/chromium-remilia

the binary auto-launches headless Chromium and connects to it via CDP. on shutdown it kills the Chromium instance it spawned.

in tmux:

tmux new -d -s love './love-machine --chromium-profile ~/.config/chromium-remilia'

if you manage Chromium separately, use --no-chromium:

./love-machine --no-chromium --cdp-host http://localhost:9223

session expiry

the OIDC refresh token keeps the session alive while Chromium is running. if auth lapses, re-sync from your local machine:

pkill chromium

rsync -av \
  --include="Cookies" \
  --include="Local Storage/***" \
  --include="Session Storage/***" \
  --include="IndexedDB/***" \
  --exclude="*" \
  ~/.config/BraveSoftware/Brave-Browser/Default/ \
  user@yourserver:~/.config/chromium-remilia/Default/

remote inspection

SSH tunnel the CDP port:

ssh -L 9223:localhost:9223 user@yourserver

then open chrome://inspect locally — you'll see the headless instance.


CLI flags

--dry-run               don't send any requests
--mean-minutes float    mean gap between poke sessions (default 60)
--collect-seconds int   activity feed collection window per session (default 60)
--no-beetle             disable beetle loop
--no-friend-add         disable friend add loop
--no-profile-fetch      disable background profile fetch loop
--beetle-daily-min float   min delay before daily cheese claim (seconds, default 30)
--beetle-daily-max float   max delay before daily cheese claim (seconds, default 180)
--profile-rate float    profile fetch rate limit (requests/second, default 1)
--refresh-days float    days before a cached profile is stale (default 30)
--backfill              fetch missing profiles for all pool members on start
--cdp-host string       CDP HTTP endpoint (default http://localhost:9223)
--chromium-bin string   path to chromium binary (auto-detected if empty)
--chromium-profile string   chromium user-data-dir (default ~/.config/chromium-remilia)
--no-chromium           skip automatic chromium launch
--login                 open visible browser to authenticate, then exit
--chat-reactions int    reactions to add to each sent Miladychan chat message (0 disables)
--chat-reaction-emoji int   reaction emoji index (-1 random, 0 😹, 1 🤍, 2 🫵)
--chat-reaction-min-delay-ms int   minimum delay between queued reactions
--chat-reaction-max-delay-ms int   maximum delay between queued reactions

chat reactions only affect messages you send through the love-machine Miladychan composer. they reuse the authenticated browser websocket, so keep them opt-in.

example:

./love-machine --chat-reactions 3 --chat-reaction-emoji 1

REMILIA_LIVE_MUTATE=1 is only for TestChatReactionsLiveMutation; it posts a probe message.


API reference

key endpoints used by the client (see pkg/remilia/api.go for the full list):

POST /api/pokeUser                    {username}
POST /api/friendship/request          {username}
POST /api/friendship/accept           {username}
GET  /api/profile/friends/incoming    ?page=&limit=
GET  /api/notifications               ?type=poke&states=unread&limit=
GET  /api/activityFeed                SSE stream (used inline in poker.go)
GET  /api/beetle/user
POST /api/beetle/action/{action}      catchBeetle | junkFaucet | claimUBC | craft
POST /api/whoami
GET  /api/health
GET  /api/profile/{username}
GET  /api/profile/friends/{username}  ?page=&limit=
POST /api/beetle/action/craft         CraftRequest body
POST /api/beetle/action/beetleHunt
POST /api/beetle/action/unlockCraftingSlot3