Aller au contenu
twitch.apiv2.0

Événements

Recevoir des événements

Comment les événements EventSub arrivent jusqu'à votre programme : les trois transports, comment en choisir un, et l'interface on() qu'ils partagent.

EventSub est le moyen par lequel Twitch signale à votre programme qu’il s’est passé quelque chose : un message dans le chat, un follow, un stream qui démarre. Vous vous abonnez à un type d’événement pour une chaîne ou un utilisateur donné, et Twitch vous envoie une notification chaque fois qu’il se produit. La bibliothèque reçoit les événements par WebSocket, par webhooks ou par conduits, et les trois écouteurs partagent une même interface.

Cet écouteur affiche le chat d’une chaîne. Il utilise un WebSocket : il n’y a donc rien à héberger.

import { Twitch } from "@ix-xs/twitch.api";
const twitch = new Twitch({
clientId: process.env.TWITCH_CLIENT_ID!,
clientSecret: process.env.TWITCH_CLIENT_SECRET!,
});
const bot = await twitch.auth.addUser({ accessToken, refreshToken });
const events = twitch.eventsub.websocket();
events.on("channel.chat.message", { broadcaster_user_id: "141981764", user_id: bot.userId }, (event) => {
console.log(`${event.chatter_user_name}: ${event.message.text}`);
});
events.onError((error) => console.error(error.message));

Le jeton enregistré avec addUser a besoin ici du scope user:read:chat. Agir au nom d’un utilisateur explique comment les jetons sont enregistrés.

Un transport est le chemin que suivent les notifications de Twitch jusqu’à vous. Les gestionnaires que vous écrivez sont les mêmes pour les trois.

WebSocketWebhooksConduits
Créé avectwitch.eventsub.websocket()twitch.eventsub.webhook({ callback, secret })twitch.eventsub.conduit()
Jeton qui s’abonneUn jeton utilisateurLe jeton d’applicationLe jeton d’application
Ce que vous hébergezRienUn endpoint HTTPS public sur le port 443Rien
Après un redémarrageLes abonnements sont recréésLes abonnements sont toujours làLes abonnements sont toujours là
Capacité3 connexions de 300 abonnementsLimitée par le coût de vos abonnementsLimitée par le coût de vos abonnements, répartie sur les shards
Convient pourUn bot ou un outil qui agit pour un seul utilisateur, le développement localLes serveurs web et les plateformes serverlessLes backends qui suivent beaucoup de chaînes sans URL publique

Chaque transport a sa page : WebSocket, Webhooks et Conduits. Le jeton d’application nécessite un secret client : les webhooks et les conduits sont donc réservés au code qui tourne sur un serveur.

Tous les écouteurs ont la même méthode on(type, condition, handler).

events.on("channel.follow", { broadcaster_user_id: channelId, moderator_user_id: botId }, async (event) => {
await twitch.chat.send({ broadcaster_id: event.broadcaster_user_id, message: `Welcome ${event.user_name}` });
});
  • type est le nom du type d’abonnement, par exemple channel.follow. Votre éditeur le propose en autocomplétion.
  • condition indique ce qu’il faut écouter : quelle chaîne, quel utilisateur. Ses champs dépendent du type et sont vérifiés par TypeScript.
  • handler reçoit l’événement, typé d’après le nom. Il peut être synchrone ou async.

L’écouteur n’attend pas qu’un gestionnaire async ait terminé pour livrer l’événement suivant : deux gestionnaires peuvent donc s’exécuter en même temps. Événements typés explique le fonctionnement des types, et la référence des types d’événements liste chaque type avec sa condition.

Le gestionnaire reçoit un second argument, meta, qui décrit la notification elle-même.

events.on("stream.online", { broadcaster_user_id: "141981764" }, (event, meta) => {
console.log(meta.messageId, meta.timestamp, meta.subscription.id);
console.log(`${event.broadcaster_user_name} went live at ${event.started_at}`);
});
ChampTypeContenu
messageIdstringIdentifiant unique de la notification.
timestampstringMoment où Twitch l’a envoyée, sous forme d’horodatage RFC 3339.
typestringType d’abonnement, par exemple stream.online.
versionstringVersion du type d’abonnement.
subscriptionEventSubSubscriptionL’abonnement auquel appartient l’événement, avec ses champs id, condition, transport et cost.

Twitch peut livrer la même notification plusieurs fois. Les écouteurs mémorisent les identifiants de message et exécutent vos gestionnaires une seule fois par notification : vous n’avez pas à comparer messageId vous-même.

on() renvoie un handle immédiatement. L’abonnement est créé en arrière-plan.

const handle = events.on("stream.online", { broadcaster_user_id: channelId }, (event) => {
console.log(`${event.broadcaster_user_name} is live`);
});
await handle.ready; // Twitch a accepté l'abonnement
await handle.stop(); // retire ce gestionnaire

ready est résolue dès que Twitch a accepté l’abonnement, et rejetée si Twitch le refuse. Vous n’êtes pas obligé de l’attendre : un échec est aussi signalé à onError, et une promesse ready que personne n’observe ne fait jamais planter le processus. Le handle expose aussi le type, la version et la condition avec lesquels il a été créé. Gérer les abonnements décrit ce que fait stop() sur chaque transport.

onError reçoit tout ce qui se passe mal dans un écouteur :

  • un abonnement refusé par Twitch, sous la forme d’une TwitchEventSubError dont le message commence par Could not subscribe to <type> et dont cause est l’erreur d’origine ;
  • une erreur levée par l’un de vos gestionnaires, ou le rejet d’un gestionnaire async, transmis tels quels ;
  • une connexion qui n’a pas pu être ouverte, ou un callback de webhook que Twitch n’a jamais vérifié.
import { TwitchEventSubError } from "@ix-xs/twitch.api";
const off = events.onError((error) => {
if (error instanceof TwitchEventSubError) console.error("EventSub:", error.message, error.cause);
else console.error("A handler failed:", error);
});
off(); // arrête d'écouter

Un gestionnaire qui lève une erreur n’interrompt pas la livraison : les autres gestionnaires et les événements suivants sont tout de même traités.

Twitch révoque un abonnement quand il ne peut plus le livrer : l’utilisateur a retiré votre application, l’utilisateur a été banni ou supprimé, ou le type d’abonnement a été retiré. L’écouteur abandonne l’abonnement, ses gestionnaires ne sont plus exécutés, et onRevocation est appelé avec l’abonnement tel que Twitch l’a décrit en dernier.

events.onRevocation((subscription) => {
console.warn(`${subscription.type} was revoked: ${subscription.status}`);
});

subscription.status donne la raison, par exemple authorization_revoked ou user_removed. Rien ne se réabonne tout seul. Comme onError, onRevocation renvoie une fonction qui retire l’écouteur.

Partager un abonnement entre plusieurs gestionnaires

Section intitulée « Partager un abonnement entre plusieurs gestionnaires »

Appeler on() deux fois avec le même type et la même condition crée un seul abonnement du côté de Twitch et y rattache les deux gestionnaires. L’ordre des champs dans la condition n’a pas d’importance.

const log = events.on("stream.online", { broadcaster_user_id: channelId }, (event) => {
console.log("online", event.started_at);
});
const announce = events.on("stream.online", { broadcaster_user_id: channelId }, async (event) => {
await twitch.chat.send({ broadcaster_id: event.broadcaster_user_id, message: "We are live" });
});
console.log(events.size); // 1
await log.stop(); // l'abonnement reste, announce s'exécute toujours
await announce.stop(); // plus aucun gestionnaire : l'abonnement est supprimé

size est le nombre d’abonnements actifs d’un écouteur, pas le nombre de gestionnaires. Le partage se fait à l’intérieur d’un même objet écouteur : deux écouteurs qui s’abonnent à la même chose créent deux abonnements.

La plupart des types d’événements exigent un scope, accordé par un utilisateur nommé dans la condition. channel.follow exige moderator:read:followers de la part de l’utilisateur indiqué dans moderator_user_id, channel.subscribe exige channel:read:subscriptions de la part du diffuseur, et stream.online n’exige rien. La référence des types d’événements donne le scope de chaque type.

Celui qui doit détenir le scope dépend du transport :

  • Par WebSocket, l’abonnement est créé avec un jeton utilisateur, et ce jeton doit porter le scope.
  • Par webhooks et par conduits, l’abonnement est créé avec le jeton d’application. L’utilisateur doit avoir accordé le scope à votre application à un moment donné, par l’un des flux présentés dans la vue d’ensemble de l’authentification. Vous n’avez pas besoin de conserver son jeton.

Contrairement aux appels d’API, la bibliothèque ne vérifie pas les scopes des événements avant de s’abonner. Quand un scope manque, Twitch répond 403 et l’échec parvient à handle.ready et à onError :

const handle = events.on("channel.follow", { broadcaster_user_id: channelId, moderator_user_id: botId }, () => {});
try {
await handle.ready;
} catch (error) {
// Could not subscribe to channel.follow (version 2): subscription missing proper authorization (403 on POST /helix/eventsub/subscriptions)
console.error((error as Error).message);
}
  • WebSocket : gestion de la connexion, reconnexion et limites.
  • Webhooks : brancher l’écouteur sur votre serveur HTTP.
  • Conduits : des abonnements par jeton d’application sur des shards WebSocket.
  • Événements typés : affiner les événements qui prennent plusieurs formes.
  • Types d’événements : chaque type, sa condition et son scope.