Technical handoff · IMVU

Radio in the room: a music bot for IMVU, from zero to on air

The full road we traveled — the architecture, every build phase and, above all, the why behind each piece. Made so you can retrace the steps and have your own DJ in your own room.

★ read before building

There are two roads here — and it's worth picking one before you get your hands dirty. Chapter 03 (The build phases) builds the browser version: an invisible Chromium that reads the room's page. It's the most educational way to understand each piece, and it's how we started.

But today we don't use a browser anymore. The version that actually runs is a WebSocket client ~10x lighter, and it reuses almost everything from here — only the way it talks to the chat changes. If you follow the phases in order, you'll build the Chromium first and only later find out it became unnecessary.

Want to go straight to the current method? Jump to The turning point — the golden path: it takes you from zero to on air on its own, reusing the phases that didn't change.

// 01

The idea in one sentence

Before the steps, understand the design. Knowing what each piece solves, you adapt instead of copying blindly.

A separate IMVU account becomes the "bot". It runs 24/7 on a free virtual machine that does two things at once: it broadcasts a music radio (an audio stream) and keeps an invisible browser logged into your room, reading the chat and obeying commands. IMVU points at the radio's address; the people in the room hear the music and talk to the bot.

Why split it like this

Each piece solves a real problem we only discovered the hard way. The sections below explain every choice — read the "whys", they're worth more than the code. Note: we started with an invisible browser (Chromium); later it evolved into a browserless client, much lighter — the chapter The turning point tells how and why (and why the "bot = Chromium" in the diagram is the original version).

// 02

Before you start

What to have at hand. Nothing here costs money.

Install the PC tools in one go. Pick your system, click copy and paste it into your terminal (the page doesn't run anything for you — you see the command before running it).

# Windows 10/11 - winget comes built in (Terminal or PowerShell)
winget install OpenJS.NodeJS.LTS
winget install yt-dlp.yt-dlp
winget install Gyan.FFmpeg
winget install Git.Git
# macOS - via Homebrew (install brew first, at brew.sh)
brew install node yt-dlp ffmpeg git
# Debian/Ubuntu - apt's Node may be old; for v18+ see nodejs.org
sudo apt update
sudo apt install -y nodejs npm ffmpeg git python3-pip
python3 -m pip install -U yt-dlp
Careful

Keep secrets (the bot's login session, the Gemini key, the DuckDNS token) out of version control. A .gitignore with auth.json, *.key, *.token and cache/ keeps you from leaking everything on the first git push.

// 03

The build phases

Order matters — each phase builds on the previous one. Build and test on your PC first; only then move to the cloud.

0Prototype
On your PC · Node + Playwright

Join the room and read the chat

At heart, the bot is an automated browser. Use Playwright to open a Chromium, log into the bot account once and save the session (a login.js that writes an auth.json). After that you just reopen the room reusing that session.

Inside the room, IMVU talks over a WebSocket (the "IMQ"). The bot listens to the messages arriving through it — that's how it "reads" the chat and detects commands. Start simple: log in, join the room, print every message that shows up to the terminal.

Why Playwright

IMVU has no public chat API. The reliable way to take part in a room is to be a real client — a logged-in browser. Headless (invisible) so it can run on a server with no screen.

1Radio
On your PC · server.js

The audio stream (the jukebox)

A simple HTTP server that broadcasts a continuous MP3 on a port (e.g. :8000). It has a folder of "filler" songs (the ones that play on their own), shuffles the order, and exposes internal routes to queue and skip tracks (/add, /skip, /status). The bot talks to those routes over localhost.

Why a process separate from the bot

Later you'll want to restart the bot (it builds up memory) without cutting the music. Keeping the radio as its own service keeps the sound going even while the bot restarts.

2Commands
Connecting the two halves

Chat commands become songs

Now connect what the bot reads from the chat to the jukebox. A message like !play song name makes the bot call the stream over localhost and queue it. !skip, !queue etc. follow the same idea.

3Bridge
On your PC · resolver + tunnel

Resolving songs from YouTube (the bridge)

To play "any song", the bot needs to download from YouTube — and that's where the biggest trap lives: YouTube blocks datacenter IPs, so the VM can't download on its own. The fix is a small HTTP agent on your home PC (a residential IP, which YouTube accepts) running yt-dlp. The VM reaches that agent through a reverse SSH tunnel.

Careful

This means !play/!add only work while your PC is on and the bridge is running. Everything else (radio, chat, AI) works without it. If you don't want that dependency, you can play only the local music folder and skip YouTube.

4Cloud
Oracle Always Free · systemd

Moving everything to the virtual machine

Create an Always Free instance (the E2.1.Micro, 1 GB of RAM, is the easiest to get; the ARM A1 gives more room if a slot opens up). Ubuntu. Install Node, Playwright's Chromium and ffmpeg, send the files over scp, and register both services with systemd so they start at boot and restart on their own.

# skeleton of a systemd service (/etc/systemd/system/bot.service)
[Service]
ExecStart=/usr/bin/node /home/ubuntu/bot/bot.js
Restart=always
WorkingDirectory=/home/ubuntu/bot
Careful · 1 GB is tight

Chromium alone eats ~500 MB. Turn on swap (e.g. 2 GB in /swapfile) or the VM freezes. And forget 3D/WebGL on this machine — we tried, it ran out of memory. Run the browser without 3D.

5HTTPS
DuckDNS + Caddy + firewall

Making the radio public and secure

IMVU Next is an HTTPS page and refuses plain-HTTP stream addresses — TLS is mandatory. But Let's Encrypt won't issue a certificate for a bare IP, and that's why DuckDNS is here: it gives you a free domain pointing at the VM's IP. In front of everything sits Caddy, which gets the HTTPS certificate automatically and passes traffic to the stream.

# Caddyfile — forwards 443 to the local stream
your-name.duckdns.org {
    reverse_proxy localhost:8000 {
        flush_interval -1   # ESSENTIAL: without it Caddy holds the buffer and the sound stalls
    }
}
Careful · two firewall layers

On Oracle, a port only opens if both allow it: the system firewall (iptables) and the OCI "Security List". Open 22, 80 and 443; keep 8000 closed to the public (only Caddy talks to it from inside). And remember: Oracle's IP is ephemeral — a cron updating DuckDNS every 5 min keeps the domain right if the IP changes.

6Connect
In the room settings

Pointing IMVU at the radio

In your IMVU room's edit panel, paste the stream URL (https://your-name.duckdns.org/stream) into the music field. Done: whoever joins the room already hears the radio. The bot, logged into the room owner's account, shows up in the chat.

7Resilience
What keeps it standing on its own

Surviving 24/7 without a babysitter

A bot that drops at 3 a.m. is no use. These defenses were born, one by one, from real problems:

  • Anti-idle heartbeat: IMVU kicks idle avatars. The bot sends a little note (♫) every so often when it's been quiet, to look active.
  • Anti-throttle flags on Chromium: an invisible tab gets its timers throttled by the browser, which kills the WebSocket keepalive and drops the bot every ~8 min. Turn on --disable-background-timer-throttling (and its sister flags).
  • Self-heal: if the WebSocket drops, it reconnects on its own.
  • Watchdog: a timer independent of the page that restarts the process if it freezes (on the 1 GB machine the page sometimes locks up).
  • "Breather": a scheduled restart (e.g. twice a day) to clear memory before the slow leak blows up into an OOM. Since the stream is separate, the music doesn't stop.
  • Kick detection: by checking the room's participant list, the bot notices if it was removed and rejoins.
General lesson

Almost this whole list came from "why did she vanish again?". Write down your own symptoms — what looks like a bug is almost always IMVU or Chromium reacting to something specific.

8Soul
Optional · Gemini + memory

Personality and AI

With the base standing, the rest is flavor. Hook up a model (we use Gemini, it has a free tier) to give replies with a persona; let the bot decide on its own when a message is meant for it (answering "skip" when it isn't, so it doesn't butt in). You can add greetings, memory of who has visited, knowledge of the room itself/its furniture and a continuous conversation window.

Careful · the "-latest" alias

If you use a model with a name like -latest, know that it "rolls forward" on its own and may one day break silently (it happened here). Pin a specific version and test after any switch.

// 04 · update

The turning point: when the browser became unnecessary

★ current method · no browser

The phases above are how we started — and they taught everything. This chapter is what we run today, and it stands on its own: further down there's a step-by-step from zero that reuses the phases that didn't change and calmly teaches the one part that actually did.

The Phase 0 bot was an invisible Chromium driven by Playwright. It worked, but it was a very expensive messenger — a whole browser just to carry a few notes. Then it clicked: IMVU's "presence" in the room is just a protocol — a WebSocket (the IMQ) plus a few REST calls. You can speak that protocol directly, with no browser at all.

That's what we did: reverse engineering of the protocol — watching the frames a real browser exchanges with IMVU, starting from an old build found on GitHub and polishing it for our use — and we wrote a browserless client that logs in, joins the room, reads the chat, sits, talks and checks the participants by speaking raw IMQ. Same account, same room, same commands — zero Chromium.

Why it changes everything — goodbye, RAM pain

Chromium alone ate ~500 MB (and leaked until it ran out of memory at around ~19 h of uptime). The pure Node client sits at ~50 MB. On top of that, ALL the hacks that only existed because of the browser disappear: the mandatory swap, the "forget 3D/WebGL", the "load-bearing" anti-throttle flags, the frozen-page watchdog and the "breather" that restarted just to clear memory. Without a browser, none of that needs to exist.

The part that matters to YOU: machine size stopped being the boss

With the bot this light, that "1 GB is too little" agony evaporates — and it's worth saying out loud: you may not even need a VM. A decent home PC (a Windows 11 with 16 GB of RAM, say) runs the bot, the stream and everything else locally, with room to spare. The free cloud was really about having an always-on machine and a public address; if your home already covers that, the whole skeleton above — Oracle, swap, systemd — becomes optional. Use the cloud if it solves a problem for you, not out of obligation. The phases above are the cheapest road, not the only one.

And the best part: the browserless client reused almost everything. The radio/jukebox (server.js), the YouTube bridge, the AI layer and the room defenses never depended on Chromium — only the "browser" half of the bot became "direct protocol". The other phases still hold word for word. And it was that lightness that made room to grow later (playing in several rooms at the same time, for example, without the machine breaking a sweat).

The current road, from zero

You don't need to build the Chromium to get here. If you're starting now and just want what runs today, follow this trail: the steps that didn't change point back to the phases up above (it's literally the same), and the middle step — the new heart — is explained calmly.

  1. Pick the machine's home. An always-on VM (Oracle Always Free) or a home PC that stays on — it doesn't matter now, the bot is light. It only needs Node.js 18+.
    Same as Phase 4 if you go with the cloud — and, since the bot slimmed down, here you can already skip the swap.
  2. Bring up the radio and the YouTube bridge. The jukebox (server.js) that serves the MP3, plus the resolver on your PC that downloads the songs.
    Same as Phases 1 and 3 — word for word. The stream never depended on the browser, so nothing changes here.
  3. Make it public and secure. A free DuckDNS domain and Caddy handling HTTPS in front of the stream.
    Same as Phase 5. (On a home PC, this is all you need to solve: a way to have a fixed address and TLS — a tunnel or a dynamic domain will do.)
  4. The new heart: the IMQ client, no browser. Here is the only piece that really changes. Instead of Chromium reading the page, a Node client speaks IMVU's protocol directly. It comes down to four moves:
    • Log in (REST). You send the bot account's credentials to IMVU's login endpoint and keep the session that comes back (the cookies/the "sauce"). It's exactly the login the site does under the hood — just by hand. The password lives in the machine's environment, never in the code.
    • Open the IMQ (WebSocket). With the session in hand, you open a WebSocket to IMVU's message queue and subscribe to your room's queue. That's the pipe that delivers the events: chat messages, who joined/left, the seats.
    • Listen and react. Each incoming event is a simple JSON. You look at its type ("chat message", "participant joined"...) and fire the same command logic the bot already had — !play, !skip and friends don't change at all.
    • Talk and sit. To reply in the chat or sit in a seat, you send an event back through the same queue — the reverse of what you listen to.
    In practice this turns into a single file — imq-client.js — that wraps this layer; the rest of bot.js doesn't even notice the browser is gone.
    Where to find the frames

    The IMQ is not documented. You figure out the event format by watching a real login (the browser's network tools, WebSocket tab) or by starting from a reference build — that's what we did, finding an old one on GitHub and polishing it. Set aside time for this part; it's the only "detective" stretch of the project.

  5. Register it in systemd — now much leaner. Same scheme as Phase 4 (two services that start at boot and restart on their own), but you delete everything that was a browser crutch in Phases 4 and 7: no mandatory swap, no "forget 3D/WebGL", no anti-throttle flags, no frozen-page watchdog, no "breather" restart to clear memory. All gone — it no longer needs to exist.
    The other Phase 7 defenses — heartbeat, self-heal, kick detection — still apply.
  6. Point the room at the radio. Paste the stream URL into the room's music field and that's it: the bot is live.
    Same as Phase 6. And if you want to give the bot a soul, Phase 8 (AI) plugs in without any changes.
Careful · the price of reverse engineering

Speaking the protocol directly is light and fast, but it's an unofficial protocol: if IMVU changes the IMQ frames, your client breaks and you fix it by hand (a real browser would adapt on its own). It's a conscious trade — much lighter, a tad more fragile. Here it was well worth it.

// 05

Traps, all in one place

If you read only one section, read this one. Every line cost hours of debugging.

// 06

File map

Roughly, who does what. The names are the ones we used — yours may differ.

bot.jsThe bot: joins the room, reads the chat over the WebSocket, fires commands, greetings, AI and the defenses (heartbeat, self-heal, watchdog).
server.jsThe stream/jukebox: broadcasts the MP3, manages the queue and the music folder, exposes the internal routes.
ai.jsThe AI layer: builds the prompt (persona + conversation context) and calls the model.
resolver.js / bridge.jsThe YouTube bridge: yt-dlp resolves/downloads on the PC; the VM reaches it through a tunnel.
config.jsSettings in one place: room URL, the word that "calls" the AI, timings, ports.
login.jsRuns once to create the bot account's saved session (auth.json).
messages/ · scene/Editable texts (greetings, phrases) and the room "knowledge" (theme, furniture) the AI uses.
Playlist de filler da Sofia carregando…