Rich Presence
Uma atividade é o que alguém está fazendo agora: Jogando Tibia, com Caçando em Roshamuul, Cavaleiro de Elite nível 412, uma imagem e um cronômetro embaixo. O GameVox usa o objeto de atividade do Discord e as mesmas formas de defini-lo que o Discord, então código escrito para o Discord Rich Presence funciona quase sempre sem alteração. O GameVox decide qual nome aparece e confere todo o resto.
Visão geral
Há quatro caminhos de entrada e um de saída:
- O status do seu próprio bot: a
presencedo Identify e o op 3 do gateway, exatamente como no Discord. - A atividade de um usuário, definida pelo seu servidor: uma chamada REST com o token do seu bot, para usuários que autorizaram o seu app (o escopo
activities.write). O seu app precisa da aprovação do GameVox para isso. - A atividade de um usuário, definida por um programa no computador dele: o RPC local do app de desktop, que fala o protocolo IPC do Discord RPC.
- A detecção de jogos do próprio GameVox, que você não controla, mas com a qual a sua atividade pode se mesclar.
- Saída: bots com o intent
GUILD_PRESENCESrecebemPRESENCE_UPDATEquando o status ou a atividade de um membro muda.
Onde aparece: abaixo do nome na lista de membros de um servidor, no cartão de perfil (imagem, detalhes, estado, grupo, cronômetro, botões), no cabeçalho da DM e na lista de amigos, no desktop, na web, no iOS e no Android.
O que é mostrado
Uma pessoa (ou bot) mostra no máximo um status personalizado (tipo 4) e uma outra atividade. Quando várias fontes dizem algo ao mesmo tempo, o RPC local vence um app, um app vence a atividade do próprio bot, e qualquer um deles vence o jogo detectado. Uma atividade do mesmo jogo que o GameVox detectou é mesclada com ele: os seus detalhes, estado e imagens, com o nome do jogo e o horário de início do GameVox quando você não enviou nenhum. Uma atividade de outra coisa substitui o jogo detectado. Pessoas ainda não podem definir um status personalizado; bots podem.
O objeto de atividade
Os campos são os do Discord. O que você pode enviar depende de quem envia (veja cada seção). Tudo passa por limpeza: caracteres de controle e de inversão de direção são removidos, quebras de linha viram espaços, sequências de espaços são reduzidas a um e o texto é cortado nos limites abaixo.
| Campo | Observações |
|---|---|
name | Até 128 caracteres. Bots escolhem o próprio nome. Para a atividade de um usuário, o GameVox escolhe o nome e ignora o seu (veja “Nomes”). |
type | Veja a tabela de tipos. Quais tipos são permitidos depende da fonte. |
url | Só Streaming, e só um link https de twitch.tv ou youtube.com. |
timestamps | Horário Unix em milissegundos ou segundos (valores abaixo de 1011 são lidos como segundos). start conta para cima, end conta para baixo. Recusado se for anterior a 2000, se start estiver mais de um dia no futuro ou se end for anterior a start. |
details, state | Até 128 caracteres cada. A primeira e a segunda linha do cartão. |
status_display_type | 1 mostra o estado em vez do nome na forma de uma linha (“Jogando Cavaleiro de Elite nível 412”), 2 mostra os detalhes. 0 ou ausente mostra o nome. |
emoji | Só status personalizado: um emoji Unicode (o nome, até 32 caracteres). Ids de emoji personalizado são descartados. |
party | id até 128 caracteres, size [current, max] com 0 ≤ current ≤ max ≤ 10000. Aparece como “2 de 5”. Um tamanho inválido descarta só o tamanho. |
assets | Chaves de imagens que você enviou para a sua aplicação na aba Rich Presence (1 a 32 caracteres entre a-z 0-9 _ -), e textos de hover de até 128 caracteres. URLs e valores mp: são descartados. Veja “Imagens”. |
buttons | Até dois, cada um no formato {label, url}: rótulo até 32 caracteres, URL https até 512. Só para apps aprovados e apps do próprio GameVox. Os membros os abrem pelo aviso de link externo do GameVox; ninguém vê botões no próprio cartão. |
A atividade inteira, depois da limpeza, precisa caber em 4096 bytes de JSON. Links (url, URLs dos botões, details_url, state_url) precisam ser https, em um nome de host público, sem nome de usuário, endereço IP ou porta. application_id, flags, instance, secrets, sync_id e qualquer outra coisa que você enviar são substituídos ou descartados.
Tipos de atividade
| Tipo | Aparece como |
|---|---|
0 | Jogando {name} |
1 | Transmitindo {details or name} |
2 | Ouvindo {name} |
3 | Assistindo {name} |
4 | {emoji} {state} |
5 | Competindo em {name} |
A forma de uma linha é traduzida pelo GameVox para o idioma de quem está vendo; o seu texto, não.
O status do seu bot
Defina do jeito do Discord: o campo presence do Identify (op 2), e o op 3 do gateway (Presence Update) sempre que ele mudar. client.user.setPresence() do discord.js, change_presence() do discord.py e similares funcionam como estão.
{
"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) O que o GameVox usa
status:online,idle,dnd,invisibleouoffline. Ausente e Não perturbe mudam a bolinha do bot. Invisível e offline mostram o bot como offline e sem atividade, enquanto o bot continua conectado e funcionando. Se o status não for enviado, vale online; com qualquer outro valor, a atualização inteira é ignorada.activities: de cada atividade, sóname,type,state,url(Streaming) e o nome do emoji (status personalizado). Todo o resto é descartado. A lista é reduzida a um status personalizado mais uma outra atividade.- Um status personalizado (tipo 4) precisa de um
stateou de um emoji; o nome dele pode ser qualquer coisa (o Discord envia “Custom Status”). sinceeafksão aceitos e ignorados.- Um Identify sem
presencevolta o bot para online simples. Um Resume mantém o status que o bot tinha.
Limites
- O op 3 é limitado a 5 a cada 20 segundos por conexão. Acima do limite, o GameVox guarda a atualização mais recente e a aplica quando a janela permitir. A conexão nunca é fechada por isso.
- Membros com o cliente aberto veem uma mudança em até 30 segundos: o GameVox envia no máximo uma atualização por bot a cada 30 segundos. Uma lista de membros aberta depois mostra o status atual.
- Enviar a mesma presença de novo não muda nada.
O status aparece abaixo do nome do bot na lista de membros e no perfil dele, para os membros de todos os servidores em que o bot está, incluindo os auto-hospedados. Outros bots não o recebem.
Definindo a atividade de um usuário pelo seu app
O seu servidor pode mostrar o que um usuário está fazendo no seu jogo, por exemplo a partir dos dados do seu próprio servidor de jogo, sem nada rodando no computador do usuário. Esta é uma extensão do GameVox: o Discord não tem um endpoint assim.
O que é preciso
- Aprovação. O GameVox aprova apps para isso manualmente. Até o seu ser aprovado, todo PUT responde 403 com o código 50001. Fale com o suporte do GameVox. Os apps do próprio GameVox já estão aprovados.
- O consentimento do usuário. O usuário autoriza o seu app com o escopo
activities.writepela tela de consentimento OAuth2 normal (Entrar com o GameVox). Os usuários podem revogá-lo a qualquer momento nas configurações; a atividade deles vinda do seu app some na hora. - A opção do usuário. Para um usuário que desativou “Permitir que os apps definam minha atividade”, nada é mostrado. O seu PUT ainda responde 204, então você não tem como saber.
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 Definir e limpar
Autentique com o token do seu bot. {user_id} é a snowflake do usuário, como o seu bot a vê em todos os outros lugares.
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 Como o GameVox interpreta
ttl_seconds: quanto tempo a atividade dura sem ser renovada, de 60 a 900 segundos (padrão 300; valores fora disso são ajustados ao intervalo). Faça um PUT do estado atual de novo antes que ele acabe. Se o seu serviço parar, a atividade some sozinha.nameé ignorado. O GameVox mostra o nome do jogo ao qual o seu app está vinculado, ou então o nome do seu app, e sempre acrescenta “via” antes do nome do seu app.- Tipos 0, 1, 2, 3 e 5. Status personalizado (4) é recusado.
- Um PUT substitui a sua atividade anterior para aquele usuário. Uma renovação que não muda nada só estende o TTL.
created_atcontinua o mesmo enquanto o tipo e o nome não mudam, então o cartão não parece novo. - As chaves de imagem precisam ser recursos da sua aplicação. Chaves desconhecidas são descartadas e o texto continua aparecendo.
- DELETE remove só a atividade do seu app para aquele usuário e não precisa de concessão. Responde 204 havendo uma atividade ou não.
Erros
| HTTP | Código | Observações |
|---|---|---|
204 | Sucesso. Também é a resposta quando o usuário desativou “Permitir que os apps definam minha atividade” (nada é guardado). | |
400 | 50035 | O corpo não é JSON, activity está ausente ou não é um objeto, ttl_seconds não é um número inteiro, ou a atividade foi recusada. errors.activity.<field>._errors[0].code diz o motivo: ACTIVITY_TYPE_INVALID, ACTIVITY_NAME_REQUIRED, ACTIVITY_TIMESTAMPS_INVALID, ACTIVITY_TOO_LARGE, ACTIVITY_INVALID, BASE_TYPE_REQUIRED, NUMBER_TYPE_COERCE. |
403 | 50001 | O seu app não está aprovado para atividade de usuário, ou o usuário não concedeu activities.write a ele (ou revogou). |
404 | 10002 | Aplicação desconhecida. |
404 | 10013 | Usuário desconhecido (a snowflake não é de um usuário do GameVox). |
413 | O corpo tem mais de 16 KB. | |
429 | Limite de taxa atingido. O corpo é o do Discord, com 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." }
]
}
}
}
} Limites
- 5 requisições a cada 20 segundos por usuário, com PUT e DELETE contados separadamente.
- 50 requisições por segundo por aplicação, somando todos os usuários.
Imagens
Envie imagens na aba Rich Presence da sua aplicação, cada uma com uma chave (1 a 32 caracteres: letras, dígitos, _ e -, única por app, sem diferenciar maiúsculas de minúsculas). PNG, JPEG ou WebP de até 1 MB; todo envio é verificado antes de ser usado. Em uma atividade, coloque a chave em assets.large_image ou assets.small_image.
- O GameVox procura a chave entre os recursos da aplicação à qual a atividade pertence: o seu app, no caso do endpoint REST e de um bot; no RPC, o app que o client id local representa. Nada mais pode ser referenciado.
- Sem imagem grande, o GameVox mostra a arte que ele mesmo tem para o jogo e, na falta dela, o ícone do seu app. A imagem pequena só aparece por cima de uma grande.
- Um recurso excluído some dos cartões em poucos minutos.
- URLs de imagem, valores do proxy de mídia
mp:do Discord e chaves de outros apps são descartados.
Listando os recursos de um app
A lista de recursos é pública, como a do Discord, para que ferramentas possam conferir quais chaves existem. Sem autenticação.
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 }
] - Só recursos de imagem, do mais antigo para o mais novo.
idé o id do recurso,namea chave dele,typesempre1. - Uma aplicação desconhecida responde 404 com o código 10002.
- As respostas ficam em cache por 5 minutos, então um envio novo pode levar esse tempo para aparecer. 30 requisições por minuto por IP.
RPC local do desktop
Um programa no computador do usuário (um jogo, um launcher, um player de mídia) pode definir a atividade do usuário pelo app de desktop do GameVox (Windows, macOS, Linux). Ele fala o protocolo IPC do Discord RPC, então o enquadramento do Game SDK do Discord e das bibliotecas de Rich Presence da comunidade funciona como está. Os apps web, iOS e Android não têm RPC local.
Onde se conectar
- Windows: o named pipe
\\.\pipe\gamevox-ipc-0(tente de-0a-9). - macOS e Linux: o socket Unix
gamevox-ipc-0a-9em$XDG_RUNTIME_DIR, ou então em$TMPDIR/gamevox-<uid>/. - Só programas rodando com o mesmo usuário do sistema operacional podem se conectar. Nada escuta na rede, e não há transporte por WebSocket nem HTTP.
- Os usuários podem desligar o RPC local nas configurações do GameVox (ele vem ligado por padrão).
Modo de compatibilidade para programas feitos para o Discord RPC
Um programa que só conhece os nomes de pipe do Discord encontra o GameVox se o usuário ligar o modo de compatibilidade nas configurações do GameVox (desligado por padrão). O GameVox então também responde em discord-ipc-0 a -9, mas só enquanto o Discord não está rodando: ele sai do caminho assim que o Discord inicia e confere de novo a cada 10 segundos. Se puder, conecte-se você mesmo a gamevox-ipc-N; a maioria das bibliotecas permite definir o nome do pipe, e aí o modo de compatibilidade não é necessário.
Enquadramento
Cada frame é um cabeçalho de 8 bytes, o opcode e o tamanho do payload como inteiros de 32 bits sem sinal em little-endian, seguido dessa quantidade de bytes de JSON em UTF-8. Frames maiores que 64 KB fecham a conexão.
| Opcode | Observações |
|---|---|
0 HANDSHAKE | O primeiro frame, e só o primeiro. |
1 FRAME | Um comando e a resposta dele. |
2 CLOSE | Qualquer um dos lados fecha. O GameVox envia {code, message} quando recusa uma conexão. |
3 PING | O GameVox responde com PONG e o mesmo payload. |
4 PONG | Resposta ao PING. |
Handshake e READY
Envie {"v": 1, "client_id": "…"} em até 10 segundos após conectar. client_id é o id da sua aplicação (15 a 21 dígitos). O GameVox responde com um dispatch READY trazendo o usuário conectado (a snowflake dele no GameVox). Um handshake inválido recebe CLOSE com o 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
A resposta traz a atividade como o GameVox a aceitou. "activity": null a limpa, assim como fechar a conexão. O último programa a definir uma atividade é dono da única atividade 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 | Observações |
|---|---|
SET_ACTIVITY | Define ou limpa a atividade (veja acima). Só os tipos 0, 2, 3 e 5; tudo o que o GameVox recusa recebe evt: "ERROR" com o código 4000. |
SUBSCRIBE | Confirmado para que SDKs que se inscrevem em eventos de join, spectate ou ready continuem funcionando. O GameVox nunca envia esses eventos. |
UNSUBSCRIBE | Igual a SUBSCRIBE. |
* | Qualquer outra coisa (AUTHORIZE, AUTHENTICATE, GET_GUILDS, comandos de voz e de lobby…) recebe evt: "ERROR", código 4006, “Not supported by GameVox”. |
Consentimento e identidade
- Nada sai do computador até o usuário permitir o seu programa. Na primeira vez, o GameVox pergunta: Permitir, Agora não ou Nunca. A resposta é lembrada por executável e client id. Enquanto isso, SET_ACTIVITY responde normalmente e a atividade fica aguardando.
- O GameVox permite sem perguntar quando o seu programa é o jogo que ele detectou e o seu client id está vinculado a esse jogo no GameVox.
- O GameVox descobre quem você é pelo sistema operacional (o processo do outro lado do pipe e o executável dele), nunca por
args.pidnem por qualquer outra coisa no payload. - O nome é escolha do GameVox, não sua: o jogo ou app ao qual o seu client id está vinculado no GameVox; senão, o jogo que o GameVox detectou, se o seu executável for esse jogo; senão, o jogo que o catálogo do GameVox associa ao seu executável; senão, “um app”. O
nameda sua atividade é ignorado. - Os botões só aparecem quando o seu client id pertence a um app aprovado ou do próprio GameVox. As imagens são resolvidas entre os recursos desse app.
- O texto é conferido de novo pelos servidores do GameVox, e detalhes ou estado inaceitáveis são removidos.
Limites: 5 SET_ACTIVITY a cada 20 segundos por conexão, com pelo menos 500 ms entre eles; atualizações mais rápidas são mescladas e a mais recente vence. No máximo 16 programas conectados ao mesmo tempo.
Recebendo a presença dos membros
Com o intent GUILD_PRESENCES, o seu bot recebe PRESENCE_UPDATE quando um membro de um servidor em que ele está muda de status ou de atividade, no formato do 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" }
}
} statuséonline,idle,dndouoffline. Um membro invisível éofflinesem atividades.client_statussempre indicadesktop.- Você vê o que os membros do servidor veriam: nenhum jogo que um membro não compartilha com esse servidor, nenhum jogo adulto que ele não escolheu mostrar, e só nome e tipo de um membro que desativou os detalhes.
- As atividades são o objeto do Discord com um
idecreated_at. Osbuttonssão só rótulos; você nunca recebe a URL de um botão, nem nenhum dos campos próprios do GameVox. - Só mudanças: não há presenças no GUILD_CREATE nem nos chunks de membros. As mudanças são agrupadas por membro ao longo de cerca de 2 segundos.
- Os status dos próprios bots não são enviados a outros bots.
Servidores auto-hospedados
Em um servidor auto-hospedado:
- O status do seu bot funciona do mesmo jeito e aparece para os membros do servidor.
- O RPC local do desktop funciona do mesmo jeito, mas o servidor não consegue consultar client ids: ele mostra o nome do jogo detectado quando a sua atividade é desse jogo, ou então “um app”. Sem botões, e imagens só do host de arquivos do próprio GameVox.
- Atividades definidas pelo seu app com o endpoint REST aparecem na nuvem do GameVox (perfis, amigos, servidores na nuvem), não em servidores auto-hospedados.
PRESENCE_UPDATEfunciona do mesmo jeito, enviado pelo próprio servidor.
Sem suporte
- Entrar, assistir e pedir para entrar (
secrets,party.privacy, lobbies), e os eventos de RPC que os acompanham. - URLs de imagem e imagens do proxy de mídia do Discord (
mp:): envie recursos em vez disso. - O RPC por WebSocket ou HTTP, e a autorização de RPC (AUTHORIZE / AUTHENTICATE).
- Status personalizado para pessoas, e sessões headless.