Mettre son bot Discord en casque VR
L'icône casque à côté du nom d'un bot n'existe dans aucune documentation officielle,
et la documentation communautaire affirme même qu'elle est hors de portée.
C'est faux : trois lignes de properties suffisent. Voici lesquelles, et pourquoi ça marche.
Ce que Discord affiche vraiment
À côté du nom d'un utilisateur, Discord peut afficher une petite icône de plateforme : un téléphone, une manette, un casque VR. Beaucoup de développeurs pensent que Discord détecte cette plateforme tout seul. Ce n'est pas le cas.
Presque tout ce qu'affiche Discord à propos d'une session, c'est le client qui l'envoie.
Le serveur se contente de le relayer aux autres membres. Le statut vient du champ status
de la présence, le point violet du stream vient du type d'activité, et l'icône de plateforme vient
d'un endroit que la plupart des bibliothèques ne laissent jamais toucher :
le champ properties du paquet IDENTIFY, envoyé une seule fois,
à l'ouverture de la connexion gateway.
discord.py y écrit "discord.py" en dur. Résultat : un bot apparaît toujours
comme un client bureau, quoi qu'on fasse ensuite avec change_presence().
Pourquoi la doc dit que c'est impossible
La documentation reverse-engineered de l'API (celle que tout le monde consulte quand
la doc officielle est muette) classe le statut vr comme accessible uniquement
via des « headless sessions », créées avec un jeton OAuth portant le scope
activities.write — un scope privé, réservé à Meta. Conclusion logique :
inatteignable pour un bot.
Sauf que ce mécanisme a bougé. Vers février 2026, Discord a reclassé les clients Android tournant sur les casques Meta : ils ne remontent plus dans le slot mobile mais dans le slot vr. L'application Discord native sur Quest, elle, n'est arrivée qu'en juin 2026 — après. Le mécanisme est donc récent, absent de l'API publique, et la documentation communautaire décrit encore l'état d'avant.
En pratique, la classification se fait sur la chaîne envoyée dans properties.
Un client qui s'annonce comme un Discord tournant en VR sur Android est rangé dans le slot VR.
Un bot peut envoyer exactement la même chose.
Les valeurs qui marchent
Chaque ligne correspond au contenu du champ properties envoyé dans l'IDENTIFY.
Testées une par une en conditions réelles, en observant le rendu sur un vrai client Discord.
| Icône obtenue | os |
browser / device |
|---|---|---|
| Aucune (client bureau) | win32, linux… |
Discord Client |
| Téléphone vert | android |
Discord Android |
| Casque VR | android |
Discord VR |
| Aucune | — | Toute chaîne non reconnue (Discord Quest, etc.) |
Le code
discord.py n'expose pas les properties. On remplace donc la méthode
DiscordWebSocket.identify par la nôtre. Le corps est une copie conforme
de l'originale — seul le bloc properties change.
import discord import discord.gateway # Les properties envoyées dans l'IDENTIFY : c'est ce trio qui # fait ranger la session dans le slot "vr" côté Discord. PROPERTIES_VR = { "os": "android", "browser": "Discord VR", "device": "Discord VR", } def installer_patch_identify(): """Remplace identify() pour injecter nos properties. Copie conforme de l'originale de discord.py 2.7.1 : si tu es sur une autre version, recopie le corps de TA version et ne change que le bloc properties. À appeler avant bot.run(). """ async def identify(self): payload = { "op": self.IDENTIFY, "d": { "token": self.token, "properties": PROPERTIES_VR, # <-- la seule ligne qui change "compress": True, "large_threshold": 250, }, } if self.shard_id is not None and self.shard_count is not None: payload["d"]["shard"] = [self.shard_id, self.shard_count] state = self._connection if state._activity is not None or state._status is not None: payload["d"]["presence"] = { "status": state._status, "game": state._activity, "since": 0, "afk": False, } if state._intents is not None: payload["d"]["intents"] = state._intents.value await self.call_hooks("before_identify", self.shard_id, initial=self._initial_identify) await self.send_as_json(payload) discord.gateway.DiscordWebSocket.identify = identify
Côté lancement, il n'y a rien de plus à faire que d'appeler le patch avant de connecter le bot :
from presence import installer_patch_identify installer_patch_identify() # AVANT bot.run(), sinon l'IDENTIFY est déjà parti bot.run(TOKEN)
_connection, _intents).
C'est le prix à payer, et ça implique de revérifier le code à chaque mise à jour
de la bibliothèque. En contrepartie, le patch est sans effet de bord : il n'ajoute rien au payload,
il remplace juste une valeur codée en dur.
Les trois pièges
Redémarrage obligatoire
L'IDENTIFY ne part qu'à l'ouverture de connexion. Une simple reconnexion ne suffit pas : discord.py envoie alors un RESUME, pas un IDENTIFY. Changer de plateforme exige de relancer le process.
Le bot est aveugle
Discord ne renvoie jamais à un bot sa propre présence : zéro PRESENCE_UPDATE,
même avec l'intent presences, même depuis une seconde session du même token.
Aucun script ne peut vérifier son propre affichage — il faut regarder le client.
L'échec est silencieux
Une valeur non reconnue ne lève rien du tout. Même chose pour le point violet du stream :
une URL hors twitch.tv / youtube.com retombe sans bruit
en « joue à… » vert.
Et l'icône manette ?
La question tombe toujours juste après. L'icône manette correspond au slot
embedded, celui des activités lancées dans Discord même. Aucune valeur de
properties testée ne l'a déclenchée, et l'argument empirique est solide :
l'intégration console est bien plus ancienne que la VR, des milliers de personnes ont cherché,
personne n'a publié de méthode. Si le slot était atteignable côté client, ça se saurait.
La différence avec la VR tient précisément au reclassement de février 2026 : Discord y range
des sessions Android ordinaires, celles que n'importe quel client — donc n'importe quel bot —
peut imiter. Rien d'équivalent n'existe pour embedded.
Testez, ne croyez pas la doc sur parole
Cette icône était classée « impossible » par la seule documentation qui en parlait. Il aura suffi d'essayer les chaînes une par une, et de regarder le client, pour que le casque apparaisse. Si vous tombez sur d'autres slots atteignables, venez le partager : c'est exactement le genre de trouvaille qui circule mal ailleurs.
En parler sur DevHub