Rich Presence
Una actividad es lo que alguien está haciendo en este momento: Jugando a Tibia, con Cazando en Roshamuul, Nivel 412, Caballero de élite, una imagen y un temporizador debajo. GameVox usa el objeto de actividad de Discord y las formas de Discord de establecerlo, así que el código escrito para Discord Rich Presence suele funcionar sin cambios. GameVox decide qué nombre se muestra y revisa todo lo demás.
Resumen
Hay cuatro vías de entrada y una de salida:
- El estado propio de tu bot: la
presencedel Identify y la op 3 del gateway, exactamente como en Discord. - La actividad de una persona, establecida desde tu servidor: una llamada REST con el token de tu bot, para las personas que han autorizado tu app (el scope
activities.write). Tu app necesita la aprobación de GameVox para esto. - La actividad de una persona, establecida por un programa en su ordenador: el RPC local de la app de escritorio, que habla el protocolo IPC de Discord RPC.
- La detección de juegos propia de GameVox, que no controlas pero con la que tu actividad puede fusionarse.
- Salida: los bots con el intent
GUILD_PRESENCESrecibenPRESENCE_UPDATEcuando cambia el estado o la actividad de un miembro.
Dónde se muestra: bajo el nombre en la lista de miembros de un servidor, en la tarjeta de perfil (imagen, detalles, estado, grupo, temporizador, botones), en la cabecera de los MD y en la lista de amigos, en escritorio, web, iOS y Android.
Qué se muestra
Una persona (o un bot) muestra como máximo un estado personalizado (tipo 4) y otra actividad. Cuando varias fuentes dicen algo a la vez, el RPC local gana a una app, una app a la actividad propia de un bot, y cualquiera de ellas al juego detectado. Una actividad del mismo juego que detectó GameVox se fusiona con él: tus detalles, tu estado y tus imágenes, con el nombre del juego de GameVox y su hora de inicio si no enviaste ninguna. Una actividad de otra cosa sustituye al juego detectado. Las personas todavía no pueden establecer un estado personalizado; los bots sí.
El objeto de actividad
Los campos son los de Discord. Lo que puedes enviar depende de quién lo envía (consulta cada sección). Todo se limpia: se eliminan los caracteres de control y los de cambio de dirección del texto, los saltos de línea pasan a ser espacios, los espacios seguidos se reducen a uno y el texto se recorta a los límites indicados abajo.
| Campo | Notas |
|---|---|
name | Hasta 128 caracteres. Los bots eligen su propio nombre. Para la actividad de una persona, GameVox elige el nombre e ignora el tuyo (consulta «Nombres»). |
type | Consulta la tabla de tipos. Qué tipos se permiten depende de la fuente. |
url | Solo para Streaming, y solo un enlace https de twitch.tv o youtube.com. |
timestamps | Hora Unix en milisegundos o segundos (los valores por debajo de 1011 se leen como segundos). start cuenta hacia arriba, end hacia atrás. Se rechaza si es anterior a 2000, si start está más de un día en el futuro o si end es anterior a start. |
details, state | Hasta 128 caracteres cada uno. La primera y la segunda línea de la tarjeta. |
status_display_type | 1 muestra el estado en lugar del nombre en la forma de una línea («Jugando a Nivel 412, Caballero de élite»), 2 los detalles. 0 o ausente muestra el nombre. |
emoji | Solo para el estado personalizado: un emoji Unicode (el nombre, hasta 32 caracteres). Los ids de emojis personalizados se descartan. |
party | id hasta 128 caracteres, size [current, max] con 0 ≤ current ≤ max ≤ 10000. Se muestra como «2 de 5». Un tamaño no válido descarta solo el tamaño. |
assets | Claves de imágenes que subiste a tu aplicación en la pestaña Rich Presence (de 1 a 32 caracteres de a-z 0-9 _ -), y textos al pasar el ratón de hasta 128 caracteres. Las URL y los valores mp: se descartan. Consulta «Imágenes». |
buttons | Hasta dos, cada uno {label, url}: etiqueta de hasta 32 caracteres, URL https de hasta 512. Solo para apps aprobadas y propias de GameVox. Los miembros los abren a través del aviso de enlace externo de GameVox; nadie ve botones en su propia tarjeta. |
La actividad completa, una vez limpia, debe caber en 4096 bytes de JSON. Los enlaces (url, URL de botones, details_url, state_url) deben ser https, en un nombre de host público, sin nombre de usuario, dirección IP ni puerto. application_id, flags, instance, secrets, sync_id y cualquier otra cosa que envíes se sustituyen o se descartan.
Tipos de actividad
| Tipo | Se muestra como |
|---|---|
0 | Jugando a {name} |
1 | Transmitiendo {details or name} |
2 | Escuchando {name} |
3 | Viendo {name} |
4 | {emoji} {state} |
5 | Compitiendo en {name} |
GameVox traduce la forma de una línea al idioma de quien la ve; tu texto no.
El estado de tu bot
Establécelo como en Discord: el campo presence del Identify (op 2) y la op 3 del gateway (Presence Update) cada vez que cambie. client.user.setPresence() de discord.js, change_presence() de discord.py y similares funcionan tal cual.
{
"op": 2,
"d": {
"token": "<bot token>",
"intents": 513,
"properties": { "os": "linux", "browser": "my-bot", "device": "my-bot" },
"presence": {
"since": null,
"activities": [ { "name": "12 Tibia worlds", "type": 3 } ],
"status": "online",
"afk": false
}
}
} {
"op": 3,
"d": {
"since": null,
"activities": [ { "name": "Custom Status", "type": 4, "state": "/help for commands" } ],
"status": "dnd",
"afk": false
}
}
// discord.js
client.user.setPresence({
activities: [{ name: 'raid signups', type: ActivityType.Watching }],
status: 'idle',
});
# discord.py
await client.change_presence(activity=discord.Game(name="VALORANT"), status=discord.Status.online) Qué usa GameVox
status:online,idle,dnd,invisibleuoffline. Ausente y No molestar cambian el punto del bot. Invisible y offline muestran el bot desconectado y sin actividad, mientras el bot sigue conectado y trabajando. Si falta el estado, significa online; con cualquier otro valor se ignora toda la actualización.activities: por cada actividad soloname,type,state,url(Streaming) y el nombre del emoji (personalizado). Todo lo demás se descarta. La lista se reduce a un estado personalizado más otra actividad.- Un estado personalizado (tipo 4) necesita un
stateo un emoji; su nombre puede ser cualquiera (Discord envía «Custom Status»). sinceyafkse aceptan y se ignoran.- Un Identify sin
presencerestablece el bot al estado online sin más. Un Resume mantiene el estado que tenía el bot.
Límites
- La op 3 está limitada a 5 cada 20 segundos por conexión. Por encima del límite, GameVox se queda con la actualización más reciente y la aplica cuando la ventana lo permite. La conexión nunca se cierra por ello.
- Los miembros con el cliente abierto ven un cambio en menos de 30 segundos: GameVox envía como máximo una actualización por bot cada 30 segundos. Una lista de miembros abierta más tarde muestra el estado actual.
- Enviar de nuevo la misma presencia no cambia nada.
El estado se muestra bajo el nombre del bot en la lista de miembros y en su perfil, a los miembros de cada servidor en el que está el bot, incluidos los autoalojados. Los demás bots no lo reciben.
Establecer la actividad de una persona desde tu app
Tu servidor puede mostrar lo que una persona está haciendo en tu juego, por ejemplo a partir de los datos de tu propio servidor de juego, sin que nada se ejecute en el ordenador de esa persona. Es una extensión de GameVox: Discord no tiene un endpoint así.
Qué hace falta
- Aprobación. GameVox aprueba las apps para esto a mano. Hasta que la tuya esté aprobada, cada PUT responde 403 con el código 50001. Pídelo al soporte de GameVox. Las apps propias de GameVox ya están aprobadas.
- El consentimiento de la persona. La persona autoriza tu app con el scope
activities.writemediante la pantalla de consentimiento OAuth2 normal (Iniciar sesión con GameVox). Puede revocarlo en cualquier momento en sus ajustes; su actividad desde tu app desaparece al instante. - El interruptor de la persona. A quien haya desactivado «Permitir que las aplicaciones establezcan mi actividad» no se le muestra nada. Tu PUT sigue respondiendo 204, así que no puedes saberlo.
https://gamevox.com/oauth2/authorize/?client_id=1491200000000000000
&response_type=code
&scope=identify%20activities.write
&redirect_uri=https%3A%2F%2Fexample.com%2Fgamevox%2Fcallback
&state=8f2c1a Establecer y borrar
Autentícate con el token de tu bot. {user_id} es el snowflake de la persona, tal como lo ve tu bot en todas partes.
PUT https://app-api.gamevox.com/api/v10/applications/@me/users/1491250000000000003/activity
Authorization: Bot <token>
Content-Type: application/json
{
"activity": {
"type": 0,
"details": "Hunting in Roshamuul",
"state": "Level 412 Elite Knight",
"timestamps": { "start": 1761868000000 },
"party": { "size": [2, 5] },
"assets": {
"large_image": "roshamuul", "large_text": "Roshamuul",
"small_image": "knight", "small_text": "Elite Knight"
},
"buttons": [ { "label": "View character", "url": "https://example.com/characters/412" } ]
},
"ttl_seconds": 300
}
HTTP/1.1 204 No Content
DELETE https://app-api.gamevox.com/api/v10/applications/@me/users/1491250000000000003/activity
Authorization: Bot <token>
HTTP/1.1 204 No Content Cómo lo lee GameVox
ttl_seconds: cuánto dura la actividad sin una actualización, de 60 a 900 segundos (por defecto 300; los valores fuera de rango se ajustan al límite). Vuelve a enviar el estado actual con PUT antes de que caduque. Si tu servicio se detiene, la actividad desaparece sola.namese ignora. GameVox muestra el nombre del juego al que está vinculada tu app, o si no el nombre de tu app, y siempre añade «a través de» tu app.- Tipos 0, 1, 2, 3 y 5. El estado personalizado (4) se rechaza.
- Un PUT sustituye tu actividad anterior para esa persona. Una actualización que no cambia nada solo amplía el TTL.
created_atse mantiene mientras el tipo y el nombre no cambien, para que la tarjeta no parezca nueva. - Las claves de imagen deben ser recursos de tu aplicación. Las claves desconocidas se descartan y el texto se sigue mostrando.
- DELETE elimina solo la actividad de tu app para esa persona y no necesita autorización. Responde 204 tanto si había una como si no.
Errores
| HTTP | Código | Notas |
|---|---|---|
204 | Éxito. También es la respuesta cuando la persona desactivó «Permitir que las aplicaciones establezcan mi actividad» (no se guarda nada). | |
400 | 50035 | El cuerpo no es JSON, falta activity o no es un objeto, ttl_seconds no es un número entero, o la actividad se rechazó. errors.activity.<field>._errors[0].code indica el motivo: ACTIVITY_TYPE_INVALID, ACTIVITY_NAME_REQUIRED, ACTIVITY_TIMESTAMPS_INVALID, ACTIVITY_TOO_LARGE, ACTIVITY_INVALID, BASE_TYPE_REQUIRED, NUMBER_TYPE_COERCE. |
403 | 50001 | Tu app no está aprobada para la actividad de personas, o la persona no le ha concedido activities.write (o lo ha revocado). |
404 | 10002 | Aplicación desconocida. |
404 | 10013 | Usuario desconocido (el snowflake no es de un usuario de GameVox). |
413 | El cuerpo supera los 16 KB. | |
429 | Límite de tasa alcanzado. El cuerpo es el de Discord, con retry_after. |
HTTP/1.1 400 Bad Request
{
"code": 50035,
"message": "Invalid Form Body",
"errors": {
"activity": {
"type": {
"_errors": [
{ "code": "ACTIVITY_TYPE_INVALID",
"message": "This activity type is not allowed for an application. Use 0, 1, 2, 3 or 5." }
]
}
}
}
} Límites
- 5 peticiones cada 20 segundos por persona, PUT y DELETE contados por separado.
- 50 peticiones por segundo por aplicación entre todas las personas.
Imágenes
Sube imágenes en la pestaña Rich Presence de tu aplicación, cada una con una clave (de 1 a 32 caracteres, letras, dígitos, _ y -, única por app, sin distinguir mayúsculas). PNG, JPEG o WebP de hasta 1 MB; cada subida se revisa antes de usarse. En una actividad, pon la clave en assets.large_image o assets.small_image.
- GameVox busca la clave entre los recursos de la aplicación a la que pertenece la actividad: tu app para el endpoint REST y para un bot, la app a la que corresponde el client id local en el RPC. No se puede hacer referencia a nada más.
- Sin imagen grande, GameVox muestra su propio arte del juego y, si no hay, el icono de tu app. La imagen pequeña solo se muestra encima de una grande.
- Un recurso eliminado desaparece de las tarjetas en pocos minutos.
- Las URL de imágenes, los valores
mp:del proxy de medios de Discord y las claves de otras apps se descartan.
Listar los recursos de una app
La lista de recursos es pública, como la de Discord, para que las herramientas puedan comprobar qué claves existen. Sin autenticación.
GET https://app-api.gamevox.com/api/v10/oauth2/applications/1491200000000000000/assets
HTTP/1.1 200 OK
Cache-Control: public, max-age=300
[
{ "id": "6f1c2e3a-…", "name": "roshamuul", "type": 1 },
{ "id": "9b0d4c7e-…", "name": "knight", "type": 1 }
] - Solo recursos de imagen, del más antiguo al más reciente.
ides el id del recurso,namesu clave,typesiempre1. - Una aplicación desconocida responde 404 con el código 10002.
- Las respuestas se guardan en caché 5 minutos, así que una subida nueva puede tardar eso en aparecer. 30 peticiones por minuto por IP.
RPC local de escritorio
Un programa en el ordenador de la persona (un juego, un launcher, un reproductor multimedia) puede establecer su actividad a través de la app de escritorio de GameVox (Windows, macOS, Linux). Habla el protocolo IPC de Discord RPC, así que el formato de tramas del Discord Game SDK y de las bibliotecas de Rich Presence de la comunidad funciona tal cual. Las apps web, iOS y Android no tienen RPC local.
Dónde conectarse
- Windows: la tubería con nombre
\\.\pipe\gamevox-ipc-0(prueba de-0a-9). - macOS y Linux: el socket Unix de
gamevox-ipc-0a-9en$XDG_RUNTIME_DIR, o si no en$TMPDIR/gamevox-<uid>/. - Solo pueden conectarse los programas que se ejecutan con el mismo usuario del sistema operativo. Nada escucha en la red, y no hay transporte WebSocket ni HTTP.
- Las personas pueden desactivar el RPC local en los ajustes de GameVox (está activado por defecto).
Modo de compatibilidad para programas hechos para Discord RPC
Un programa que solo conoce los nombres de tubería de Discord encuentra GameVox si la persona activa el modo de compatibilidad en los ajustes de GameVox (desactivado por defecto). GameVox entonces también responde en las tuberías de discord-ipc-0 a -9, pero solo mientras Discord no se esté ejecutando: se aparta en cuanto Discord arranca y vuelve a comprobarlo cada 10 segundos. Si puedes, conéctate tú mismo a gamevox-ipc-N; la mayoría de las bibliotecas permiten fijar el nombre de la tubería, y entonces el modo de compatibilidad no hace falta.
Formato de tramas
Cada trama es una cabecera de 8 bytes, el opcode y la longitud de la carga útil como enteros sin signo de 32 bits en little-endian, seguida de esa misma cantidad de bytes de JSON en UTF-8. Las tramas de más de 64 KB cierran la conexión.
| Opcode | Notas |
|---|---|
0 HANDSHAKE | La primera trama, y solo la primera. |
1 FRAME | Un comando y su respuesta. |
2 CLOSE | Cualquiera de los dos lados cierra. GameVox envía {code, message} cuando rechaza una conexión. |
3 PING | GameVox responde con PONG y la misma carga útil. |
4 PONG | Respuesta a PING. |
Handshake y READY
Envía {"v": 1, "client_id": "…"} en los 10 segundos siguientes a conectarte. client_id es el id de tu aplicación (de 15 a 21 dígitos). GameVox responde con un dispatch READY que lleva a la persona que ha iniciado sesión (su snowflake de GameVox). Un handshake no válido se responde con CLOSE y el código 4000.
→ op 0 HANDSHAKE
{ "v": 1, "client_id": "1491200000000000000" }
← op 1 FRAME
{
"cmd": "DISPATCH",
"evt": "READY",
"nonce": null,
"data": {
"v": 1,
"config": {
"cdn_host": "files.gamevox.com",
"api_endpoint": "//app-api.gamevox.com/api",
"environment": "production"
},
"user": {
"id": "1491250000000000003", "username": "ben", "discriminator": "0",
"global_name": "Ben", "avatar": null, "bot": false, "flags": 0, "premium_type": 0
}
}
} SET_ACTIVITY
La respuesta lleva la actividad tal como la aceptó GameVox. "activity": null la borra, y cerrar la conexión también. El último programa en establecer una actividad es el dueño de la única actividad local.
→ op 1 FRAME
{
"cmd": "SET_ACTIVITY",
"nonce": "7c1f0a52",
"args": {
"pid": 4242,
"activity": {
"type": 0,
"details": "Hunting in Roshamuul",
"state": "Level 412 Elite Knight",
"timestamps": { "start": 1761868000 },
"assets": { "large_image": "roshamuul", "large_text": "Roshamuul" },
"party": { "id": "p1", "size": [2, 5] }
}
}
}
← op 1 FRAME
{
"cmd": "SET_ACTIVITY",
"evt": null,
"nonce": "7c1f0a52",
"data": {
"id": "a41c09e2f3b8d715", "name": "Tibia", "type": 0, "url": null,
"created_at": 1761868800000,
"timestamps": { "start": 1761868000000 },
"application_id": "1491200000000000000",
"details": "Hunting in Roshamuul", "state": "Level 412 Elite Knight", "emoji": null,
"party": { "id": "p1", "size": [2, 5] },
"assets": { "large_image": "roshamuul", "large_text": "Roshamuul" },
"flags": 0
}
} | Comando | Notas |
|---|---|
SET_ACTIVITY | Establece o borra la actividad (ver arriba). Solo tipos 0, 2, 3 y 5; todo lo que GameVox rechaza se responde con evt: "ERROR" y el código 4000. |
SUBSCRIBE | Se confirma para que los SDK que se suscriben a eventos de unirse, ver como espectador o ready sigan funcionando. GameVox nunca envía esos eventos. |
UNSUBSCRIBE | Igual que SUBSCRIBE. |
* | Cualquier otra cosa (AUTHORIZE, AUTHENTICATE, GET_GUILDS, comandos de voz y de lobby…) se responde con evt: "ERROR", código 4006, «Not supported by GameVox». |
Consentimiento e identidad
- Nada sale del ordenador hasta que la persona dé permiso a tu programa. La primera vez, GameVox pregunta: Permitir, Ahora no o Nunca. La respuesta se recuerda por ejecutable y client id. Mientras tanto, SET_ACTIVITY responde como siempre y la actividad espera.
- GameVox permite sin preguntar cuando tu programa es el juego que detectó y tu client id está vinculado a ese juego en GameVox.
- GameVox sabe quién eres por el sistema operativo (el proceso al otro lado de la tubería y su ejecutable), nunca por
args.pidni por nada más de la carga útil. - El nombre lo elige GameVox, no tú: el juego o la app vinculados a tu client id en GameVox; si no, el juego que detectó GameVox si tu ejecutable es ese juego; si no, el juego que su catálogo asocia a tu ejecutable; y, si no, «una app».
nameen tu actividad se ignora. - Los botones solo se muestran cuando tu client id pertenece a una app aprobada o propia de GameVox. Las imágenes se resuelven con los recursos de esa app.
- Los servidores de GameVox vuelven a revisar el texto, y los detalles o el estado inaceptables se eliminan.
Límites: 5 SET_ACTIVITY cada 20 segundos por conexión, con al menos 500 ms entre ellos; las actualizaciones más rápidas se fusionan y gana la última. Como máximo 16 programas conectados a la vez.
Recibir la presencia de los miembros
Con el intent GUILD_PRESENCES, tu bot recibe PRESENCE_UPDATE cuando un miembro de un servidor en el que está cambia de estado o de actividad, con la forma de Discord.
{
"op": 0,
"t": "PRESENCE_UPDATE",
"d": {
"user": { "id": "1491250000000000003" },
"guild_id": "1491230000000000001",
"status": "online",
"activities": [ {
"id": "a41c09e2f3b8d715", "name": "Tibia", "type": 0, "url": null,
"created_at": 1761868800000,
"timestamps": { "start": 1761868000000 },
"application_id": "1491200000000000000",
"details": "Hunting in Roshamuul", "state": "Level 412 Elite Knight", "emoji": null,
"assets": { "large_image": "roshamuul", "large_text": "Roshamuul" },
"buttons": [ "View character" ],
"flags": 0
} ],
"client_status": { "desktop": "online" }
}
} statusesonline,idle,dnduoffline. Un miembro invisible aparece comoofflinesin actividades.client_statussiempre indicadesktop.- Ves lo que verían los miembros del servidor: ningún juego que un miembro no comparta con este servidor, ningún juego para adultos que no haya elegido mostrar, y solo el nombre y el tipo de un miembro que desactivó los detalles.
- Las actividades son el objeto de Discord con un
idycreated_at.buttonsson solo etiquetas; nunca recibes la URL de un botón, ni ninguno de los campos propios de GameVox. - Solo cambios: no hay presencias en GUILD_CREATE ni en los fragmentos de miembros. Los cambios se agrupan por miembro durante unos 2 segundos.
- Los estados propios de los bots no se envían a otros bots.
Servidores autoalojados
En un servidor autoalojado:
- El estado de tu bot funciona igual y se muestra a los miembros del servidor.
- El RPC local de escritorio funciona igual, pero el servidor no puede consultar los client id: muestra el nombre del juego detectado cuando tu actividad es de ese juego y, si no, «una app». Sin botones, y las imágenes solo desde el alojamiento de archivos propio de GameVox.
- Las actividades establecidas desde tu app con el endpoint REST se muestran en la nube de GameVox (perfiles, amigos, servidores en la nube), no en los servidores autoalojados.
PRESENCE_UPDATEfunciona igual, enviado por el propio servidor.
No compatible
- Unirse, ver como espectador y solicitar unirse (
secrets,party.privacy, lobbies), y los eventos RPC que los acompañan. - URL de imágenes e imágenes del proxy de medios de Discord (
mp:): sube recursos en su lugar. - El RPC sobre WebSocket o HTTP, y la autorización RPC (AUTHORIZE / AUTHENTICATE).
- El estado personalizado para personas, y las sesiones sin interfaz.