Cómo hice un cliente de WhatsApp para Emacs sin API
Una de las cosas más divertidas que puedes hacer con Emacs es crearle una interfaz a otro software. Es el equivalente, en versión Lisp, a hacer correr Doom en cualquier aparato. Además, es bastante útil, ya que puedes conectarlo con otras herramientas del editor del ñu y crear flujos de trabajo más llevaderos.
La gran pregunta: ¿puedo chatear por WhatsApp desde Emacs? Te adelanto que lo logré, aunque con ciertas limitaciones.
No hay puerta
Contexto: trabajo en macOS. Es importante que lo sepas, porque la solución depende de ello.
Meta no ofrece una API para esto, y la app de escritorio para macOS no tiene ningún puente: ni puertos abiertos, ni gRPC, ni diccionario de AppleScript. Nada.
Existen clientes no oficiales, como whatsmeow, que hablan directamente con los servidores de WhatsApp haciéndose pasar por un dispositivo. Funcionan, pero tu cuenta pasa a depender de un software que Meta no autoriza y es un proceso más a mantener. Además, Meta puede detectarlos y bloquearte el número. No pienso jugarme mi cuenta por chatear desde Emacs.
¿Y cómo hablas con una app que no quiere hablar contigo? Pues leyendo lo mismo que lee ella.
Leer: la base de datos
La app de WhatsApp guarda todos los chats en un SQLite local:
~/Library/Group Containers/group.net.whatsapp.WhatsApp.shared/ChatStorage.sqlite
Emacs 29 trae SQLite de serie, sin librerías externas. Abrirlo en modo solo lectura es una línea:
(sqlite-open file t)
El esquema no está documentado, así que tocó explorar a mano. Los chats viven en ZWACHATSESSION, los mensajes en ZWAMESSAGE y los nombres de los miembros de grupo en ZWAGROUPMEMBER y ZWAPROFILEPUSHNAME. Esa Z al principio de cada nombre delata que es una base de datos de Core Data.
Y Core Data tiene una sorpresa. Las fechas no cuentan desde 1970, sino desde el 1 de enero de 2001. Mis primeros mensajes salían fechados en 1995. La solución: una constante.
(defconst whatsapp--epoch-offset 978307200
"Seconds between the Unix epoch and the Core Data epoch (2001-01-01).")
Hubo más trampas. Los chats fijados guardan una fecha en el futuro para salir arriba, y el texto del último mensaje de la sesión viene codificado. Mejor leer ambos del propio mensaje.
Un detalle más: macOS protege ese directorio. Debes darle a Emacs Acceso total al disco en los ajustes de privacidad. A Emacs le daría hasta las llaves de mi casa si me las pidiera.
Enviar: el truco de Atajos
Leer es fácil. ¿Y escribir? De nada sirve editar la base de datos: la app no se enteraría y nada saldría por la red.
Entonces recordé que la app de WhatsApp ofrece una acción en Atajos (Shortcuts) de macOS: "Enviar mensaje". Y Atajos se puede ejecutar desde la terminal:
shortcuts run "Send WhatsApp" --input-path mensaje.json
El problema es que la acción espera un contacto de tu agenda, no un número. El truco: construir un vCard al vuelo con el teléfono del chat, que el atajo convierte en contacto temporal. No se guarda nada en tu agenda.
:quality(85)/https://andros.dev/media/blog/2026/10/whatsapp-shortcut.png)
Emacs escribe un JSON con el vCard y el texto, lanza el proceso y espera:
(json-encode
`((vcard . ,(whatsapp--vcard name phone))
(text . ,text)))
El flujo completo queda así:
flowchart LR
E[Emacs] -- lee --> DB[(ChatStorage.sqlite)]
E -- JSON con vCard --> S[Atajo Send WhatsApp]
S -- Enviar mensaje --> W[App oficial de WhatsApp]
W -- guarda --> DB
Me gusta un detalle. Mientras el atajo trabaja, el mensaje aparece como (sending...). Cuando WhatsApp lo guarda en su base de datos, Emacs lo sustituye por el real. Lo que ves es lo que WhatsApp envió de verdad, no lo que Emacs cree que envió.
El resultado
Una interfaz al estilo de ERC o rcirc, con una lista de chats y un buffer por conversación:
--- Wednesday 1 October 2026 ---
[08:15] <Alice> Good morning!
[08:16] <Me> Morning! Are we still on for today?
[08:20] <Alice> Yes, 10:00 at the station
[08:21] <Alice> [image] This is the entrance
Alice> _
Es lo más trivial del proceso. Emacs superó este reto hace décadas.
Todo el cliente cabe en unas pocas líneas de Elisp, sin dependencias.
Tiene sus límites, claro.
- Solo funciona en macOS.
- No se puede escribir a grupos, porque la acción de Atajos solo acepta personas.
- Algunos chats no guardan el teléfono en local y quedan en solo lectura.
- Y no hay notificaciones: pulsas
C-c C-upara traer mensajes nuevos.
Pero ahora puedo hablar con mi familia sin salir de Emacs. ¿Qué más se puede pedir?
Fuentes
- whatsapp.el, el código y las instrucciones para crear el atajo.
- SQLite en Emacs, el soporte nativo desde Emacs 29.
- Shortcuts desde la terminal, la documentación de Apple del comando
shortcuts. - vCard 3.0 (RFC 2426), el formato de contacto que usa el truco.
This work is under a Attribution-NonCommercial-NoDerivatives 4.0 International license.
Help me keep writing
Every coffee gives me a push toward the next article.
Sure, it's on me!
Comments
There are no comments yet.