Découverte des APIs Twitch #3 : Afficher des alertes pour les nouveaux followers !

On poursuit notre découverte des APIs Twitch avec ce troisième article qui traitera de l'API REST fournie par le célèbre service de streaming vidéo. Aujourd'hui on va développer une alerte qui affichera (presque) en temps réel les nouveaux followers de notre chaîne Twitch, toujours à l'aide des langages JavaScript, HTML et CSS.

Pour le code de cet article, on repart de celui écrit dans l'article précédent (que vous pouvez retrouver sur mon Github), que l'on va compléter au fur et à mesure.

Note

Cet article fait partie d'une série consacrée à la découverte des APIs Twitch :

  1. Création d'applications et intégration à OBS
  2. Authentification
  3. Afficher des alertes pour les nouveaux followers ! (API REST)

Des promesses, toujours des promesses

Pour développer nos alertes, on va intensivement utiliser un concept présent depuis la norme ES2015 de JavaScript : les promises (ou promesses en français).

Pour faire court, les promesses vont nous permettre de chaîner facilement des appels à des fonctions asynchrones (lorsque l'on fait une requête HTTP par exemple) et de gérer une file d'attente pour nos notifications (pour ne pas risquer d'en afficher plusieurs simultanément).

Lorsqu'une fonction retourne une promesse, elle retourne un objet dont le contenu sera rempli une fois la tâche asynchrone terminée. Cet objet permet, via sa méthode .then(), de renseigner une fonction a exécuter ensuite, lorsque la tâche se sera terminée avec succès. La fonction ainsi renseignée recevra en paramètre la valeur de retours de la fonction précédente.

On peut également renseigner une fonction à appeler lorsque tout ne se passe pas comme prévu à l'aide de la méthode .catch(). La fonction passée à .catch() recevra alors l'erreur rencontrée.

Voici un exemple de chaîne de promise pour expliciter un peu la description ci-dessus :

twitch.getUserId("flozz_")
    .then(twitch.getNewFollowers)
    .then(afficherNouveauxFollowers)
    .catch(function(error) {
        console.log("Quelque chose s'est mal passé :", error);
    });
  • Ici, on appelle la fonction asynchrone twitch.getUserId() qui va récupérer mon id Twitch depuis mon pseudo,
  • puis une fois l'information récupérée, on enchaîne avec la fonction twitch.getNewFollowers(), qui recevra en paramètre l'id retourné par twitch.getUserId(),
  • enfin on chaîne avec la fonction afficherNouveauxFollowers() qui recevra en paramètre les nouveaux followers retournés par la fonction précédente.
  • On a également fourni une fonction à appeler au cas où tout ne se passerait pas comme prévu afin de loger l'erreur.

Je ne vous en dis pas plus sur les Promises, ce n'est pas le sujet principal de cet article, mais je vous invite à aller lire un peu de documentation à leur sujet :

Faire des requêtes HTTP

Comme vous le savez certainement, une API REST s'exploite en faisant des requêtes HTTP (souvent on parle aussi de requêtes AJAX). Je vais utiliser l'API Fetch de JavaScript pour réaliser ces requêtes, mais comme pour les helpers de l'article précédent, je ne vais pas vous expliquer tout cela en détails : ça dépasse le scope de cet article.

Je vais donc créer une fonction qui effectue tout le travail, et vous expliquer comment elle s'utilise, sans en détailler le contenu :

const request = {

    // [Promise] Download (GET) a JSON from the given URL
    getJson: function(url, params=null, headers={}) {
        // ...
    },

};

Cette fonction prend de 1 à 3 paramètres :

  • url : L'adresse du endpoint de l'API REST de Twitch que l'on souhaite interroger (par exemple https://api.twitch.tv/helix/users).
  • (optionnel) params : un objet contenant des paramètres à rajouter à l'URL (par exemple définir ce paramètre à {login: "trucmuche"} rajoutera ?login=trucmuche à l'URL passée précédemment).
  • (optionnel) headers : des entêtes supplémentaires à ajouter à notre requête HTTP (on en aura besoin pour passer le token d'authentification à l'API).

Pour finir, cette fonction retourne une Promise qui, si elle est résolue, fournira la réponse de l'API sous la forme d'un objet.

Exemple d'utilisation :

request.getJson("http://example.org/data.json")
    .then(function(data) {
        console.log("Réponse de l'API :", data);
    })
    .catch(function(error) {
        console.error("Une erreur est survenue :", error);
    });

Premier appel API : récupérer son ID Twitch

Pour notre premier appel à l'API de Twich, on va commencer par récupérer notre ID. En effet, nous aurons besoin de fournir cet identifiant numérique lors de la plupart de nos appels à l'API.

La première étape est de trouver quel endpoint de l'API peut nous fournir cette information. Après quelques recherches dans la documentation, on trouve ce qu'il nous faut :

  • Le endpoint "/helix/users" permet de récupérer tout un tas d'informations à propos d'un utilisateur.
  • Il accepte en paramètre soit l'identifiant numérique de l'utilisateur ("id") soit son pseudo ("login").
  • Il nécessite cependant l'autorisation "user:read:email" pour pouvoir être appelé.

Maintenant que l'on sait tout ça on peut se remettre les mains dans le code, en commençant par ajouter l'autorisation manquante à notre variable SCOPES :

const SCOPES = ["user:read:email"];

Avis

ATTENTION : Étant donné qu'on demande de nouvelles autorisations, il nous faut demander un nouveau token, et donc repasser par la phase d'authentification et d'autorisation de l'application, comme expliqué dans l'article précédent.

On peut à présent écrire une fonction twitch.getUserId() qui nous permettra de récupérer notre ID Twitch depuis notre nom d'utilisateur :

const twitch = {

    // ...

    getUserId: function(userName) {
        const params = helpers.getUrlParams();

        // On fait appel à la fonction que je vous ai présentée plus tôt
        // pour faire des requêtes HTTP
        return request.getJson("https://api.twitch.tv/helix/users", {
            // Paramètres à passer dans l'URL (?login=trucuche)
            "login": userName,
        }, {
            // Entêtes supplémentaires pour la requête
            // ID de notre application
            "client-id": CLIENT_ID,
            // Le token obtenu après la phase d'authentification / authorisation de l'application.
            // ATTENTION : Ce token s'écrit sous la forme "Bearer XXXXXXX".
            "Authorization": `Bearer ${params["access_token"]}`,
        })
        .then(function(result) {
            // On retourne la partie qui nous intéresse de la réponse.
            return result.data[0].id;
        });
    },

};

On peut à présent tester tout ça rapidement dans notre navigateur. Pour cela, appuyez sur F12 afin d'ouvrir la console et tapez le code suivant :

twitch.getUserId("flozz_").then(console.log)

Si tout s'est bien passé, Twitch devrait vous retourner l'ID de l'utilisateur demandé, soit 92454370 pour moi :

Capturé d'écran de l'exécution de twitch.getUserId() dans la console du navigateur

Récupérer les derniers followers

Maintenant qu'on sait faire un appel API et qu'on sait comment récupérer son ID Twitch, on peut aller récupérer la liste de ses derniers followers.

Encore une fois on trouve notre bonheur en fouillant dans la doc :

  • Il existe un endpoint "/helix/users/follows" permettant de récupérer au choix :
    • la liste des utilisateurs suivis par une personne (paramètre "from_id"),
    • ou la liste des utilisateurs qui suivent une personne (paramètre "to_id").
  • Ce endpoint ne demande pas d'autorisation particulière.

On peut donc écrire une fonction twitch.getLastFollowers() qui récupère nos derniers followers :

const twitch = {

    // ...

    getLastFollowers(userId) {
        const params = helpers.getUrlParams();
        return request.getJson("https://api.twitch.tv/helix/users/follows", {
            to_id: userId,
        }, {
            "client-id": CLIENT_ID,
            "Authorization": `Bearer ${params["access_token"]}`,
        }).then(function(data) {
            return data.data;
        });
    },

}

Cette fonction nous retournera la liste des 20 derniers followers, classés du plus récent au plus ancien.

Pour tester tout ça, on peut encore une fois reprendre notre navigateur et y écrire le code suivant :

twitch.getLastFollowers(92454370).then(console.log)

On peut également chaîner cette fonction avec la précédente pour ne pas avoir à écrire l'ID à la main :

twitch.getUserId("flozz_")
    .then(twitch.getLastFollowers)
    .then(console.log)

Voici un aperçu du résultat :

Capturé d'écran de l'exécution de twitch.getLastFollowers() dans la console du navigateur

Récupérer les NOUVEAUX followers

Bon, on arrive à récupérer nos derniers followers, c'est cool, mais nous ce dont on a besoin pour afficher nos alertes, ce sont les NOUVEAUX followers.

Pour faire cela, rien de bien compliqué : on va écrire une fonction qui va retenir l'ID des derniers followers, ce qui nous permettra de déterminer si une nouvelle personne a suivi la chaîne depuis le dernier appel à cette fonction.

Si on traduit ça sous forme de code, ça donne ceci :

const twitch = {

    // ...

    _lastFollowersIds: null,

    getNewFollowers: function(userId) {
        // On appelle notre fonction qui récupère les derniers followers
        return twitch.getLastFollowers(userId)
            .then(function(followers) {
                // On gère le cas où la chaîne n'aurait pas encore de followers du tout
                if (followers.length == 0) {
                    twitch._lastFollowersIds = [];
                    return [];
                }

                // Premier appel à cette fonction : on remplit la liste des
                // derniers followers
                if (twitch._lastFollowersIds === null) {
                    twitch._lastFollowersIds = [];
                    for (const i in followers) {
                        twitch._lastFollowersIds.push(followers[i].from_id);
                    }
                    return [];
                }

                const result = [];

                // On parcours la liste des derniers followers et on met de côté
                // les petits nouveaux... :)
                for (const i in followers) {
                    // On arrête de parcourir la liste lorsque l'on
                    // rencontre un follower déjà connu
                    if (twitch._lastFollowersIds.includes(followers[i].from_id)) {
                        break;
                    }

                    // On met de côté le nouveau follower
                    result.push(followers[i]);

                    // ... et on oublie pas d'enregistrer son ID dans la liste
                    // des followers connus
                    twitch._lastFollowersIds.push(followers[i].from_id);
                }

                return result;
            });
    },
}

Note

NOTE : Dans une première version de cette fonction, écrite en live sur Twitch, je conservais uniquement l'ID du dernier follower. Ça fonctionne bien la plupart du temps, mais si le follower arrête de suivre la chaîne une fois l'alerte le concernant passée, le script perd tout ses repères et va considérer les 20 derniers followers comme étant nouveaux... ce qui est quelque peu embêtant puisqu'on part pour 3 minutes et 40 secondes de notifications... 😅️

Cette fois-ci ça va être plus compliqué de tester : on va devoir exécuter en boucle le code suivant dans la console de notre navigateur jusqu'à ce que quelqu'un suive la chaîne :

twitch.getUserId("flozz_")
    .then(twitch.getNewFollowers)
    .then(console.log)

Et voici un exemple de résultat si quelqu'un suit la chaîne entre deux appels à la fonction :

Capturé d'écran de l'exécution de twitch.getNewFollowers() dans la console du navigateur
  • Ici on peut voir mon premier appel, qui forcément, ne me retournera aucun nouveau follower.
  • Puis j'ai utilisé un compte de test pour suivre ma chaîne et appelé à nouveau la fonction : cette fois-ci elle nous retourne bien le petit nouveau !
  • Et j'ai ensuite appelé une dernière fois la fonction pour m'assurer qu'elle ne me retournerait rien de plus (étant donné que personne de nouveau n'a suivi la chaîne depuis l'appel précédent).

Développer une alerte graphique et sonore

Maintenant que nos appels API sont au point, on va passer à l'implémentation de l'alerte en elle-même. On va commencer par rajouter un peu de HTML et de CSS à notre projet.

Dans index.html :

<div class="alert" id="alert-follower">
    <img src="./follower.gif" alt="" />
    <div class="alert-text">
        <span id="alert-follower-name">Trucmuche</span>
        just followed the channel!
    </div>
</div>

<audio src="./alert.ogg" preload="auto" id="alert-sound"></audio>

Dans style.css :

.alert {
    opacity: 0;
}

.alert.visible {
    opacity: 1;
}

Je vous ai mis ici le minimum de code nécessaire à la compréhension pour pas que ça soit trop long. Vous retrouverez bien sûr le code complet de cet exemple sur Github.

Il ne nous reste plus qu'à écrire un peu de JavaScript pour afficher / masquer cette alerte... Mais juste avant, ça, on va ajouter un nouvel helper dont on aura besoin :

const helpers = {

    // ...

    wait: function(seconds) {
        return new Promise(function(resolve, reject) {
            setTimeout(resolve, seconds * 1000);
        });
    },

}

Ce helper est tellement simple que je vous ai laissé le code cette fois-ci. Il s'agit simplement d'une fonction qui retourne une promise qui sera résolue au bout du nombre de secondes demandées. Le but est simplement de « mettre en pause » notre programme pour laisser aux gens le temps de voir nos alertes avant de la masquer.

Passons à présent à la fonction affichant l'alerte :

const alerts = {

    // Ici on a une Promise résolue qui nous servira de file d'attente pour
    // nos alertes.
    _queue: Promise.resolve(),

    // Rajoute une alerte de nouveau follower à la file d'attente.
    newFollower(name) {
        const divAlertFollower = document.getElementById("alert-follower");
        const spanAlertFollowerName = document.getElementById("alert-follower-name");
        const audioAlertSound = document.getElementById("alert-sound");

        // Affiche l'alerte
        function _show() {
            // On met à jour le nom du nouveau follower
            spanAlertFollowerName.innerText = name;
            // On rend l'alerte visible en lui ajoutant la classe "visible"
            divAlertFollower.classList.add("visible");
            // On joue le son de l'alerte
            audioAlertSound.play();
        }

        // Masque l'alerte
        function _hide() {
            // On cache l'alerte en supprimant la classe "visible"
            divAlertFollower.classList.remove("visible");
        }

        // On rajoute une chaîne de Promise à la file d'attente.
        alerts._queue = alerts._queue
            .then(_show)                        // On affiche l'alerte
            .then(helpers.wait.bind(null, 10))  // On attend 10 secondes
            .then(_hide)                        // On masque l'alerte
            .then(helpers.wait.bind(null, 1));  // On attend 1 seconde
                                                // | (pour laisser le temps à l'alerte
                                                // | de se masquer avant d'enchaîner
                                                // | avec la suivante)
    },

};

Note

NOTE sur la méthode .bind() :

La méthode .bind() retourne une fonction avec le contexte (this) et des paramètres préremplis. Ici on s'en sert pour préremplir la durée de l'attente. On aurait également pu se passer de l'appel à cette méthode en « wrappant » l'appel à helpers.wait() dans une fonction anonyme :

.then(function() { helpers.wait(10); })

Pour plus d'informations sur la méthode .bind(), vous pouvez consulter sa documentation sur MDN.

Si on veut tester notre alerte, la méthode est toujours la même, on ouvre la console JavaScript du navigateur, et on appelle notre fonction. On peut même essayer de l'appeler plusieurs fois de suite afin de s'assurer qu'une seule notification est bien affichée à la fois :

alerts.newFollower("Trucmuche");
alerts.newFollower("JeanKevin");

Voici ce que ça donne chez moi :

Polling

On est presque au bout, il ne nous reste plus qu'une seule chose à implémenter : le polling. Rien de bien compliqué, il s'agit juste d'écrire une fonction qui va aller interroger en boucle l'API de Twitch pour récupérer les nouveaux followers :

function newFollowerPolling(userId) {

    function _displayNewFollowers(newFollowers) {
        for (let i in newFollowers) {
            alerts.newFollower(newFollowers[i].from_name);
        }
    }

    // On récupère les nouveaux followers
    twitch.getNewFollowers(userId)
        .then(_displayNewFollowers);

    // On rajoute un petit timer qui rappelera cette fonction dans 15 secondes
    // /!\ On prend bien soin de lui fournir le paramètre userId à l'aide de bind() !
    setTimeout(newFollowerPolling.bind(null, userId), 15 * 1000);
}

Et pour terminer, on met à jour notre fonction main() comme ceci :

const TWITCH_CHANNEL = "flozz_";

function main() {
    if (!twitch.isAuthenticated()) {
        twitch.authentication();
    } else {
        twitch.getUserId(TWITCH_CHANNEL)
            .then(newFollowerPolling);
    }
}

window.onload = main;

Intégration dans OBS

Le code est terminé, il ne reste plus qu'à intégrer cette page web dans OBS comme on l'a vu dans le premier article... À un détail près cependant.

Lorsque cette page sera intégrée dans le browser d'OBS, on va se retrouver bloqués sur un page d'authentification de Twitch.

Demande d'authentification de Twitch dans le Browser d'OBS

Il y a alors 2 possibilités  :

La première est d'ouvrir l'application dans un navigateur, de vous identifier et de recopier dans OBS l'URL de la page avec le token obtenu après authentification. Je n'ai par contre aucune idée de la durée de validité dudit token.

L'autre solution est de passer le navigateur d'OBS en mode « interactif » et de s'identifier directement dedans. Pour ce faire,

  • Faites un clic droit sur la source Browser pour afficher son menu contextuel,
  • Puis cliquez sur « Interagir » :
Menu contextuel de la source OBS Linux Browser

Il ne vous reste plus qu'à vous identifier dans la fenêtre qui apparait :

Fenêtre interactive de la source OBS Linux Browser

C'est tout pour aujourd'hui !

Et voilà, on a implémenté nos propres alertes pour afficher les nouveaux followers de la chaîne !

Comme d'habitude, vous retrouvez les sources complètes de l'exemple de cet article sur Github :

J'avais à l'origine développé ces alertes en live sur ma chaîne Twitch. Vous pourrez retrouver la VOD issue de ces lives sur ma chaîne YouTube :

À bientôt pour le prochain article de la série qui traitera de l'API PubSub de Twitch, qui nous permettra entre autres de récupérer en temps réel les nouveaux subscribers (abonnés payants) et d'interagir avec les points de chaînes !