Rich Presence
Une activité, c’est ce que fait quelqu’un en ce moment : Joue à Tibia, avec Chasse à Roshamuul, Chevalier d’élite niveau 412, une image et un minuteur en dessous. GameVox utilise l’objet activité de Discord et les mêmes moyens que Discord pour le définir, si bien que le code écrit pour Discord Rich Presence fonctionne le plus souvent sans modification. GameVox décide du nom affiché et filtre tout le reste.
Vue d’ensemble
Il y a quatre voies d’entrée et une voie de sortie :
- Le statut de votre bot : la
presenced’Identify et l’op 3 du gateway, exactement comme sur Discord. - L’activité d’un utilisateur, définie depuis votre serveur : un appel REST avec le token de votre bot, pour les utilisateurs qui ont autorisé votre app (le scope
activities.write). Votre app doit pour cela être approuvée par GameVox. - L’activité d’un utilisateur, définie par un programme sur son ordinateur : le RPC local de l’app de bureau, qui parle le protocole IPC de Discord RPC.
- La détection de jeux de GameVox, que vous ne contrôlez pas mais avec laquelle votre activité peut fusionner.
- Sortie : les bots qui ont l’intent
GUILD_PRESENCESreçoiventPRESENCE_UPDATEquand le statut ou l’activité d’un membre change.
Où elle s’affiche : sous le nom dans la liste des membres d’un serveur, sur la carte de profil (image, détails, état, groupe, minuteur, boutons), dans l’en-tête des messages privés et dans la liste d’amis, sur bureau, web, iOS et Android.
Ce qui est affiché
Une personne (ou un bot) affiche au plus un statut personnalisé (type 4) et une autre activité. Quand plusieurs sources indiquent quelque chose en même temps, le RPC local l’emporte sur une app, une app sur l’activité propre d’un bot, et chacun d’eux sur le jeu détecté. Une activité pour le jeu que GameVox a détecté est fusionnée avec lui : vos détails, votre état et vos images, avec le nom du jeu et l’heure de début de GameVox si vous n’en avez pas envoyé. Une activité pour autre chose remplace le jeu détecté. Les personnes ne peuvent pas encore définir de statut personnalisé ; les bots, si.
L’objet activité
Les champs sont ceux de Discord. Ce que vous pouvez envoyer dépend de l’expéditeur (voir chaque section). Tout est nettoyé : les caractères de contrôle et de forçage de direction sont retirés, les sauts de ligne deviennent des espaces, les suites d’espaces sont réduites, et le texte est coupé aux limites ci-dessous.
| Champ | Remarques |
|---|---|
name | Jusqu’à 128 caractères. Les bots choisissent leur propre nom. Pour l’activité d’un utilisateur, GameVox choisit le nom et ignore le vôtre (voir « Noms »). |
type | Voir le tableau des types. Les types autorisés dépendent de la source. |
url | Streaming uniquement, et seulement un lien https twitch.tv ou youtube.com. |
timestamps | Heure Unix en millisecondes ou en secondes (les valeurs inférieures à 1011 sont lues comme des secondes). start compte vers le haut, end compte à rebours. Refusé si antérieur à 2000, si start est à plus d’un jour dans le futur, ou si end précède start. |
details, state | Jusqu’à 128 caractères chacun. La première et la deuxième ligne de la carte. |
status_display_type | 1 affiche l’état au lieu du nom dans la forme sur une ligne (« Joue à Chevalier d’élite niveau 412 »), 2 les détails. 0 ou absent affiche le nom. |
emoji | Statut personnalisé uniquement : un emoji Unicode (le nom, jusqu’à 32 caractères). Les ids d’emoji personnalisés sont écartés. |
party | id jusqu’à 128 caractères, size [current, max] avec 0 ≤ current ≤ max ≤ 10000. Affiché sous la forme « 2 sur 5 ». Une taille invalide n’écarte que la taille. |
assets | Clés des images que vous avez téléversées dans votre application sur l’onglet Rich Presence (1 à 32 caractères parmi a-z 0-9 _ -), et textes de survol jusqu’à 128 caractères. Les URL et les valeurs mp: sont écartées. Voir « Images ». |
buttons | Jusqu’à deux, chacun de la forme {label, url} : libellé jusqu’à 32 caractères, URL https jusqu’à 512. Uniquement pour les apps approuvées et les apps propres à GameVox. Les membres les ouvrent via l’avertissement de GameVox pour les liens externes ; personne ne voit de boutons sur sa propre carte. |
L’activité entière, une fois nettoyée, doit tenir dans 4096 octets de JSON. Les liens (url, URL des boutons, details_url, state_url) doivent être en https, sur un nom d’hôte public, sans nom d’utilisateur, adresse IP ni port. application_id, flags, instance, secrets, sync_id et tout ce que vous envoyez d’autre sont remplacés ou écartés.
Types d’activité
| Type | Affichage |
|---|---|
0 | Joue à {name} |
1 | Diffuse {details or name} |
2 | Écoute {name} |
3 | Regarde {name} |
4 | {emoji} {state} |
5 | Participe à {name} |
GameVox traduit la forme sur une ligne dans la langue de la personne qui regarde ; votre texte, non.
Le statut de votre bot
Définissez-le à la manière de Discord : le champ presence d’Identify (op 2), et l’op 3 du gateway (Presence Update) à chaque changement. client.user.setPresence() de discord.js, change_presence() de discord.py et leurs équivalents fonctionnent tels quels.
{
"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) Ce que GameVox utilise
status:online,idle,dnd,invisibleouoffline. Inactif et Ne pas déranger changent la pastille du bot. Invisible et hors ligne affichent le bot comme hors ligne sans activité, alors que le bot reste connecté et continue de fonctionner. Un statut manquant signifie en ligne ; avec toute autre valeur, la mise à jour entière est ignorée.activities: pour chaque activité, seulsname,type,state,url(Streaming) et le nom de l’emoji (statut personnalisé) sont retenus. Tout le reste est écarté. La liste est réduite à un statut personnalisé plus une autre activité.- Un statut personnalisé (type 4) nécessite un
stateou un emoji ; son nom peut être n’importe quoi (Discord envoie « Custom Status »). sinceetafksont acceptés et ignorés.- Un Identify sans
presenceremet le bot simplement en ligne. Un Resume conserve le statut qu’avait le bot.
Limites
- L’op 3 est limité à 5 par tranche de 20 secondes et par connexion. Au-delà, GameVox garde la mise à jour la plus récente et l’applique dès que la fenêtre le permet. La connexion n’est jamais fermée pour cette raison.
- Les membres dont le client est ouvert voient un changement en moins de 30 secondes : GameVox envoie au plus une mise à jour par bot toutes les 30 secondes. Une liste des membres ouverte plus tard affiche le statut actuel.
- Renvoyer la même présence ne change rien.
Le statut s’affiche sous le nom du bot dans la liste des membres et sur son profil, pour les membres de chaque serveur où se trouve le bot, y compris les serveurs auto-hébergés. Les autres bots ne le reçoivent pas.
Définir l’activité d’un utilisateur depuis votre app
Votre serveur peut montrer ce que fait un utilisateur dans votre jeu, par exemple à partir des données de votre propre serveur de jeu, sans que rien ne tourne sur l’ordinateur de l’utilisateur. C’est une extension GameVox : Discord n’a pas d’endpoint équivalent.
Ce qu’il faut
- Une approbation. GameVox approuve manuellement les apps pour cette fonction. Tant que la vôtre n’est pas approuvée, chaque PUT répond 403 avec le code 50001. Adressez-vous au support GameVox. Les apps propres à GameVox sont déjà approuvées.
- Le consentement de l’utilisateur. L’utilisateur autorise votre app avec le scope
activities.writevia l’écran de consentement OAuth2 habituel (Se connecter avec GameVox). Les utilisateurs peuvent le révoquer à tout moment dans leurs réglages ; leur activité issue de votre app disparaît aussitôt. - Le réglage de l’utilisateur. Pour un utilisateur qui a désactivé « Laisser les applications définir mon activité », rien n’est affiché. Votre PUT répond quand même 204, vous ne pouvez donc pas le savoir.
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 Définir et effacer
Authentifiez-vous avec le token de votre bot. {user_id} est la snowflake de l’utilisateur, telle que votre bot la voit partout ailleurs.
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 Comment GameVox le lit
ttl_seconds: la durée de vie de l’activité sans actualisation, de 60 à 900 secondes (300 par défaut ; les valeurs hors plage sont ramenées dans la plage). Renvoyez l’état actuel par PUT avant l’expiration. Si votre service s’arrête, l’activité disparaît d’elle-même.nameest ignoré. GameVox affiche le nom du jeu auquel votre app est liée, ou à défaut le nom de votre app, et ajoute toujours « via » votre app.- Types 0, 1, 2, 3 et 5. Le statut personnalisé (4) est refusé.
- Un PUT remplace votre activité précédente pour cet utilisateur. Une actualisation qui ne change rien ne fait que prolonger le TTL.
created_atreste le même tant que le type et le nom ne changent pas, si bien que la carte ne paraît pas nouvelle. - Les clés d’image doivent être des ressources de votre application. Les clés inconnues sont écartées et le texte s’affiche quand même.
- DELETE ne retire que l’activité de votre app pour cet utilisateur et ne nécessite aucune autorisation. Il répond 204, qu’il y en ait eu une ou non.
Erreurs
| HTTP | Code | Remarques |
|---|---|---|
204 | Succès. C’est aussi la réponse quand l’utilisateur a désactivé « Laisser les applications définir mon activité » (rien n’est stocké). | |
400 | 50035 | Le corps n’est pas du JSON, activity est absent ou n’est pas un objet, ttl_seconds n’est pas un nombre entier, ou l’activité a été refusée. errors.activity.<field>._errors[0].code indique pourquoi : ACTIVITY_TYPE_INVALID, ACTIVITY_NAME_REQUIRED, ACTIVITY_TIMESTAMPS_INVALID, ACTIVITY_TOO_LARGE, ACTIVITY_INVALID, BASE_TYPE_REQUIRED, NUMBER_TYPE_COERCE. |
403 | 50001 | Votre app n’est pas approuvée pour l’activité utilisateur, ou l’utilisateur ne lui a pas accordé activities.write (ou l’a révoqué). |
404 | 10002 | Application inconnue. |
404 | 10013 | Utilisateur inconnu (la snowflake n’est pas un utilisateur GameVox). |
413 | Le corps dépasse 16 Ko. | |
429 | Limite de débit atteinte. Le corps est celui de Discord, avec 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 requêtes par tranche de 20 secondes et par utilisateur, PUT et DELETE comptés séparément.
- 50 requêtes par seconde et par application, tous utilisateurs confondus.
Images
Téléversez des images sur l’onglet Rich Presence de votre application, chacune avec une clé (1 à 32 caractères : lettres, chiffres, _ et -, unique par app, sans distinction de casse). PNG, JPEG ou WebP jusqu’à 1 Mo ; chaque téléversement est vérifié avant d’être utilisé. Dans une activité, mettez la clé dans assets.large_image ou assets.small_image.
- GameVox cherche la clé parmi les ressources de l’application à laquelle appartient l’activité : votre app pour l’endpoint REST et pour un bot, l’app que représente le client id local en RPC. Rien d’autre ne peut être référencé.
- Sans grande image, GameVox affiche ses propres visuels du jeu, puis l’icône de votre app. La petite image ne s’affiche que par-dessus une grande.
- Une ressource supprimée disparaît des cartes en quelques minutes.
- Les URL d’images, les valeurs du proxy média Discord
mp:et les clés d’autres apps sont écartées.
Lister les ressources d’une app
La liste des ressources est publique, comme chez Discord, pour que les outils puissent vérifier quelles clés existent. Aucune authentification.
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 }
] - Ressources d’image uniquement, des plus anciennes aux plus récentes.
idest l’id de la ressource,namesa clé,typetoujours1. - Une application inconnue répond 404 avec le code 10002.
- Les réponses sont mises en cache 5 minutes, un nouveau téléversement peut donc mettre ce temps à apparaître. 30 requêtes par minute et par IP.
RPC local de bureau
Un programme sur l’ordinateur de l’utilisateur (un jeu, un lanceur, un lecteur multimédia) peut définir l’activité de l’utilisateur via l’app de bureau GameVox (Windows, macOS, Linux). Elle parle le protocole IPC de Discord RPC, si bien que le tramage du Game SDK de Discord et des bibliothèques Rich Presence de la communauté fonctionne tel quel. Les apps web, iOS et Android n’ont pas de RPC local.
Où se connecter
- Windows : le tube nommé
\\.\pipe\gamevox-ipc-0(essayez de-0à-9). - macOS et Linux : le socket Unix
gamevox-ipc-0à-9dans$XDG_RUNTIME_DIR, ou sinon dans$TMPDIR/gamevox-<uid>/. - Seuls les programmes exécutés sous le même utilisateur du système d’exploitation peuvent se connecter. Rien n’écoute sur le réseau, et il n’y a pas de transport WebSocket ni HTTP.
- Les utilisateurs peuvent désactiver le RPC local dans les réglages de GameVox (il est activé par défaut).
Mode de compatibilité pour les programmes conçus pour Discord RPC
Un programme qui ne connaît que les noms de tube de Discord trouve GameVox si l’utilisateur active le mode de compatibilité dans les réglages de GameVox (désactivé par défaut). GameVox répond alors aussi sur discord-ipc-0 à -9, mais seulement tant que Discord n’est pas lancé : il se retire dès que Discord démarre et vérifie à nouveau toutes les 10 secondes. Si vous le pouvez, connectez-vous vous-même à gamevox-ipc-N ; la plupart des bibliothèques permettent de définir le nom du tube, et le mode de compatibilité devient alors inutile.
Tramage
Chaque trame se compose d’un en-tête de 8 octets, l’opcode et la longueur de la charge utile sous forme d’entiers non signés de 32 bits en little-endian, suivi d’autant d’octets de JSON UTF-8. Une trame de plus de 64 Ko ferme la connexion.
| Opcode | Remarques |
|---|---|
0 HANDSHAKE | La première trame, et seulement la première. |
1 FRAME | Une commande et sa réponse. |
2 CLOSE | L’un ou l’autre côté ferme la connexion. GameVox envoie {code, message} quand il refuse une connexion. |
3 PING | GameVox répond par PONG avec la même charge utile. |
4 PONG | Réponse à PING. |
Handshake et READY
Envoyez {"v": 1, "client_id": "…"} dans les 10 secondes suivant la connexion. client_id est l’id de votre application (15 à 21 chiffres). GameVox répond par un dispatch READY portant l’utilisateur connecté (son snowflake GameVox). Un handshake invalide reçoit CLOSE avec le code 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 réponse porte l’activité telle que GameVox l’a acceptée. "activity": null l’efface, tout comme la fermeture de la connexion. Le dernier programme à avoir défini une activité possède l’unique activité locale.
→ 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
}
} | Commande | Remarques |
|---|---|
SET_ACTIVITY | Définit ou efface l’activité (voir ci-dessus). Types 0, 2, 3 et 5 uniquement ; tout ce que GameVox refuse reçoit evt: "ERROR" avec le code 4000. |
SUBSCRIBE | Acquitté pour que les SDK qui s’abonnent aux événements join, spectate ou ready continuent de fonctionner. GameVox n’envoie jamais ces événements. |
UNSUBSCRIBE | Comme SUBSCRIBE. |
* | Tout le reste (AUTHORIZE, AUTHENTICATE, GET_GUILDS, commandes vocales et de lobby…) reçoit evt: "ERROR", code 4006, « Not supported by GameVox ». |
Consentement et identité
- Rien ne quitte l’ordinateur tant que l’utilisateur n’a pas autorisé votre programme. La première fois, GameVox demande : Autoriser, Pas maintenant ou Jamais. La réponse est mémorisée par exécutable et par client id. En attendant, SET_ACTIVITY répond comme d’habitude et l’activité reste en attente.
- GameVox autorise sans demander quand votre programme est le jeu qu’il a détecté et que votre client id est lié à ce jeu sur GameVox.
- GameVox apprend qui vous êtes par le système d’exploitation (le processus à l’autre bout du tube et son exécutable), jamais par
args.pidni par quoi que ce soit d’autre dans la charge utile. - Le nom est le choix de GameVox, pas le vôtre : le jeu ou l’app auquel votre client id est lié sur GameVox, sinon le jeu détecté par GameVox si votre exécutable est ce jeu, sinon le jeu sous lequel son catalogue connaît votre exécutable, sinon « une app ». Le
namede votre activité est ignoré. - Les boutons ne s’affichent que si votre client id appartient à une app GameVox approuvée ou propre à GameVox. Les images sont résolues parmi les ressources de cette app.
- Le texte est vérifié à nouveau par les serveurs de GameVox, et les détails ou l’état inacceptables sont retirés.
Limites : 5 SET_ACTIVITY par tranche de 20 secondes et par connexion, espacés d’au moins 500 ms ; les mises à jour plus rapides sont fusionnées et la plus récente l’emporte. Au plus 16 programmes connectés à la fois.
Recevoir la présence des membres
Avec l’intent GUILD_PRESENCES, votre bot reçoit PRESENCE_UPDATE quand un membre d’un serveur où il se trouve change de statut ou d’activité, au format 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" }
}
} statusvautonline,idle,dndouoffline. Un membre invisible estofflinesans activités.client_statusnomme toujoursdesktop.- Vous voyez ce que verraient les membres du serveur : aucun jeu qu’un membre ne partage pas avec ce serveur, aucun jeu pour adultes qu’il n’a pas choisi de montrer, et seulement le nom et le type pour un membre qui a désactivé les détails.
- Les activités sont l’objet de Discord avec un
idet uncreated_at. Lesbuttonsne sont que des libellés ; vous ne recevez jamais d’URL de bouton, ni aucun des champs propres à GameVox. - Changements uniquement : il n’y a pas de présences dans GUILD_CREATE ni dans les chunks de membres. Les changements sont regroupés par membre sur environ 2 secondes.
- Les statuts propres des bots ne sont pas envoyés aux autres bots.
Serveurs auto-hébergés
Sur un serveur auto-hébergé :
- Le statut de votre bot fonctionne de la même façon et s’affiche pour les membres du serveur.
- Le RPC local de bureau fonctionne de la même façon, mais le serveur ne peut pas résoudre les client ids : il affiche le nom du jeu détecté quand votre activité concerne ce jeu, sinon « une app ». Pas de boutons, et des images uniquement depuis l’hébergement de fichiers de GameVox.
- Les activités définies depuis votre app via l’endpoint REST s’affichent dans le cloud GameVox (profils, amis, serveurs cloud), pas sur les serveurs auto-hébergés.
PRESENCE_UPDATEfonctionne de la même façon, envoyé par le serveur lui-même.
Non pris en charge
- Rejoindre, regarder et demander à rejoindre (
secrets,party.privacy, lobbies), et les événements RPC associés. - Les URL d’images et les images du proxy média Discord (
mp:) : téléversez plutôt des ressources. - Le RPC via WebSocket ou HTTP, et l’autorisation RPC (AUTHORIZE / AUTHENTICATE).
- Le statut personnalisé pour les personnes, et les sessions sans interface.