La semana pasada quería tener control total sobre el rich presence de mi perfil de Discord. El default solo muestra lo que Discord detecta como tu “juego actual” — yo quería estado, detalles, botones, timestamps y rotación automática de perfiles.
Así que construí discord-rpc-tui: un gestor de Discord Rich Presence usando Ink (React para terminal) que corre como servicio systemd.
Arquitectura
Ink TUI (React terminal) ← o modo headless via systemd
│
RPCManager ← @xhayper/discord-rpc con reconexión automática
│
Socket IPC de Discord ← /run/user/1000/discord-ipc-0
La app tiene dos modos:
- Modo TUI — interfaz Ink interactiva con barra de estado, lista de perfiles y log de eventos. Para ver qué está pasando.
- Modo headless — corre como daemon via systemd, logs a journalctl. Arranca automáticamente al iniciar sesión.
[5:19:58 PM] ○ Connecting...
[5:19:58 PM] Connected to Discord
[5:19:58 PM] Activity set: Coding
Profile: Coding
State: Building something cool
Details: TypeScript | Ink TUI
[●] q:quit Space:pause n:next r:reload
Características Clave
Rotación de Actividades
Múltiples perfiles que rotan automáticamente. Cada perfil tiene su propio estado, detalles, tipo de actividad, botones e imágenes:
{
"profiles": [
{
"name": "Coding",
"activity": {
"state": "Building something cool",
"details": "TypeScript | Ink TUI",
"startTimestamp": true,
"type": 0
}
},
{
"name": "Gaming",
"activity": {
"state": "Exploring Hyrule",
"details": "Zelda: Tears of the Kingdom",
"largeImageKey": "zelda",
"buttons": [{ "label": "Ver Stream", "url": "https://twitch.tv/..." }]
}
}
],
"rotationInterval": 600
}
Reconexión Automática con Backoff Exponencial
Si Discord se reinicia o el socket IPC se desconecta, RPCManager reintenta automáticamente:
1s → 2s → 4s → 8s → 16s → máx 30s
Detección de Discord
Un DiscordDetector monitorea el socket IPC en /run/user/1000/discord-ipc-0 cada 5 segundos. Cuando Discord se cierra, pausa el RPC. Cuando Discord se abre, reconecta automáticamente.
Título de Actividad
Cada perfil ahora soporta un campo name que aparece como título de la actividad en Discord — ej., “Playing easy-rag” o “Listening to My Playlist”. Control total sobre cómo se etiqueta tu presencia:
{
"name": "Coding",
"activity": {
"name": "easy-rag",
"state": "Building something cool",
"details": "TypeScript • Ink TUI"
}
}
El Bug Que Me Enseñó Algo
El bug más escurridizo fue descubrir que el método connect() de @xhayper/discord-rpc no emite el evento ready. Hay que llamar a login() — que internamente llama a connect() y luego emite ready cuando no se necesitan scopes OAuth:
❌ this.client.on('ready', handler);
await this.client.connect(); // ready NUNCA se dispara
✅ this.client.on('ready', handler);
await this.client.login(); // ready se dispara!
El método login() verifica si se requieren scopes OAuth. Para SET_ACTIVITY, no se necesita autenticación — solo un Client ID. Aunque el nombre login() suene a autenticación, es la forma correcta de establecer una sesión RPC completa incluso sin auth.
Stack Tecnológico
| Componente | Elección | Por qué |
|---|---|---|
| TUI Framework | Ink 7 | React para terminal, Flexbox, input de teclado first-class |
| RPC Library | @xhayper/discord-rpc | Mantenimiento activo, TypeScript, transporte IPC + WebSocket |
| Config | Zod + JSON | Dependencia compartida con AI SDK, inferencia de tipos |
| Runtime | Node.js 22 | ESM nativo, integración systemd |
| Tests | Vitest — 37 tests en 4 módulos core | 3 nuevos archivos (activity-rotator, discord-detector, rpc-manager) |
| Auto-start | systemd —user | Servicio de usuario Linux estándar, restart on failure |
Probarlo
git clone https://github.com/fxckcode/discord-rpc-tui.git
cd discord-rpc-tui
pnpm install
bash install.sh
# Editar ~/.config/discord-rpc-tui/config.json con tu Client ID de Discord
El script de instalación hace todo: compila el binario, crea la configuración, instala el servicio systemd, y opcionalmente habilita el auto-start.
El proyecto tiene licencia MIT y está abierto a contribuciones en GitHub.