- Python 59.4%
- JavaScript 23.4%
- HTML 11.8%
- CSS 5.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| instance/plugin_data/soundboard | ||
| plugins | ||
| tests | ||
| twitchiou | ||
| .env.example | ||
| .gitignore | ||
| external_api.md | ||
| README.md | ||
| requirements.txt | ||
| run.py | ||
Twitchiou
Twitchiou est un bot Twitch modulaire en Python/Flask doté d’un panneau de contrôle Bootstrap pleine largeur. Il utilise deux identités OAuth séparées :
- le bot lit et écrit dans le chat et exécute les actions de modération ;
- le broadcaster pilote les sondages, prédictions, récompenses et opérations réservées à la chaîne.
Le cœur maintient deux transports EventSub WebSocket, normalise les notifications dans une enveloppe JSON stable, les affiche en direct dans l’UI et les diffuse aux plugins actifs.
Installation
Prérequis : Python 3.11 ou plus récent et une application créée dans la console développeur Twitch.
Dans l’application Twitch, ajoutez exactement cette URL OAuth :
https://localhost:5000/oauth/callback
Puis lancez :
python -m venv .venv
.venv/bin/pip install -r requirements.txt
cp .env.example .env
Sous PowerShell :
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
Copy-Item .env.example .env
Renseignez les paires TWITCH_BROADCASTER_CLIENT_ID / TWITCH_BROADCASTER_CLIENT_SECRET et TWITCH_BOT_CLIENT_ID / TWITCH_BOT_CLIENT_SECRET, ainsi qu’une longue valeur aléatoire dans APP_SECRET. TOKEN_ENCRYPTION_KEY peut être une autre longue valeur dédiée ; sinon APP_SECRET sert à chiffrer les jetons OAuth.
.venv/bin/python run.py
Sous PowerShell :
.\.venv\Scripts\python.exe run.py
Ouvrez ensuite https://localhost:5000. Au premier démarrage, Twitchiou génère un certificat HTTPS autosigné dans instance/certificates/. Le navigateur affichera donc un avertissement tant que ce certificat n’aura pas été approuvé localement. Vérifiez son empreinte avant de poursuivre.
Les anciennes adresses http://localhost:5000/... sont redirigées sur le même port vers leur équivalent HTTPS ; aucune page ni WebSocket applicatif n’est servi en clair.
Les identifiants Twitch du Broadcaster et du Bot peuvent être renseignés et connectés séparément dans Paramètres → Comptes Twitch. L’URL https://localhost:5000/oauth/callback doit être autorisée exactement dans les deux applications de la console Twitch. Une ancienne valeur HTTP présente dans l’environnement est automatiquement forcée en HTTPS au chargement.
Une tâche d’arrière-plan surveille les deux jetons chaque minute et les renouvelle dix minutes avant leur expiration. Ces délais peuvent être ajustés avec TOKEN_REFRESH_INTERVAL et TOKEN_REFRESH_MARGIN (en secondes).
Le composant principal surveille également l’audience : le nombre de viewers est contrôlé toutes les 30 secondes, puis les abonnements et followers toutes les 5 minutes. Les délais sont configurables avec VIEWER_COUNT_INTERVAL, SUB_COUNT_INTERVAL et FOLLOWER_COUNT_INTERVAL. Le suivi détaillé des followers utilise le scope moderator:read:followers ; reconnectez le Broadcaster après une mise à jour si Twitch doit accorder ce scope.
Le compte bot doit être modérateur de la chaîne. Vous pouvez le faire avec /mod nom_du_bot dans le chat du broadcaster. Sans ce rôle, Twitch refusera les abonnements et actions de modération même si le jeton possède les scopes nécessaires.
Vidéos et clips
La page Contenus liste les vidéos et les clips de la chaîne dans deux onglets, par pages de 10. Chaque élément peut être ouvert sur Twitch ou téléchargé en arrière-plan avec yt-dlp. Le téléchargement continue lorsque l’onglet du navigateur est fermé ; sa progression est restaurée sur la miniature à la prochaine ouverture. Les fichiers sont enregistrés sur le bureau par défaut ; changez le dossier dans .env si nécessaire :
TWITCH_CONTENT_OUTPUT_DIR=~/Desktop
TWITCH_CONTENT_AUTO_DOWNLOAD=false
Le dossier est créé automatiquement lors du premier téléchargement. Un chemin relatif est résolu depuis la racine du projet. Ces deux valeurs se règlent aussi depuis Paramètres → Centre de contenu. Lorsque le téléchargement automatique est activé, Twitchiou repère la fin du live, attend que la rediffusion correspondante soit publiée par Twitch, puis démarre son téléchargement en arrière-plan.
Statistiques et chat
La page Statistiques conserve dans SQLite les mesures d’audience, d’abonnements et de followers du core, calcule leurs évolutions, le nombre de messages par heure et classe les participants les plus actifs. La barre de période permet de basculer entre 24 heures, 7, 30 ou 90 jours, ou d’isoler une session de stream précise. Les sessions et les changements de jeu sont détectés automatiquement ; les pochettes sont récupérées avec Helix puis superposées au graphique.
La page Chat permet d’envoyer un message avec le compte Bot et de consulter les messages, sondages et résultats archivés par session. Le bouton de son active une courte notification locale lors de chaque nouveau message. Les sondages en cours proposent Répondre sur Twitch, qui ouvre le chat officiel : l’API Twitch publique permet de créer, lire et terminer un sondage, mais ne fournit pas d’endpoint permettant de voter au nom d’un viewer.
Overlays OBS
La page Overlays contient un overlay système prêt à l’emploi, le preset supprimable Overlay principal glossy, et permet de créer d’autres sorties. Le preset affiche une barre d’informations, les statistiques de la chaîne, le dernier message, les sondages et les alertes Twitch en temps réel. Le preset Just Chatting · Flash info ouvre la scène sur une sphère quadrillée rouge calculée en HTML/Canvas, dont la grille tourne autour d’un axe incliné sous une horloge analogique accélérée. Après la transition glitch, le petit globe et son horloge superposée rejoignent le coin supérieur gauche, sans bandeau supérieur, et l’habillage affiche autour d’une zone centrale transparente le titre du live, la catégorie, l’heure et les viewers. Ajoutez &intro=0 à son URL OBS pour ignorer l’ouverture ou &headline=Votre%20titre pour remplacer ponctuellement le titre Twitch. Pour chaque sortie, copiez le lien fourni dans une source Navigateur d’OBS et reportez la largeur et la hauteur affichées. Le fond est transparent. Chaque URL contient une clé aléatoire renouvelable ; son renouvellement invalide immédiatement l’ancien canal.
Le gestionnaire permet de tester une animation HTML/CSS, un son distant, de vider l’overlay et de l’ouvrir dans un navigateur. Les commandes sont diffusées en temps réel par Socket.IO et journalisées. Le HTML/CSS de chaque animation est isolé dans un Shadow DOM.
Entrée vidéo RTMP
Twitchiou peut recevoir directement un flux RTMP envoyé par OBS. Installez FFmpeg, puis ouvrez Paramètres → Interfaçage → Entrée RTMP pour activer le flux interne et copier l’adresse du serveur et sa clé. Dans OBS, choisissez Service personnalisé, collez ces deux valeurs, puis démarrez la diffusion. La clé est créée une seule fois si aucune clé n’existe, puis conservée lors des arrêts, réactivations et redémarrages. Elle est masquée par défaut et peut être remplacée, uniquement à la demande, par une nouvelle clé aléatoire de 10 caractères après confirmation.
Le core convertit le signal en HLS local, affiche directement la vidéo dans l’overlay système et émet on_core_signal_in lorsque les premières images sont disponibles, puis on_core_signal_out quand elles disparaissent. Cet affichage ne dépend d’aucun plugin. Le plugin Diffusion assistant peut enrichir le rendu avec ses transitions et déclencher son écran glitché NO SIGNAL après la première perte de signal.
Le serveur écoute par défaut sur rtmp://0.0.0.0:1935. Les variables RTMP_HOST, RTMP_PUBLIC_HOST, RTMP_PORT, RTMP_STREAM_KEY, RTMP_SIGNAL_TIMEOUT, RTMP_HLS_DIR et FFMPEG_BINARY permettent d’adapter l’écoute, l’adresse présentée à OBS, la clé, le délai de détection et l’emplacement des segments. Désactivez complètement l’entrée avec RTMP_ENABLED=false.
Sources OBS brutes
Des pages publiques volontairement sans feuille de style sont disponibles pour composer directement une source Navigateur OBS :
/chat: messages du chat, avec mise à jour, suppression et vidage en temps réel (?limit=100, jusqu’à 500) ;/followers,/subset/viewers: compteurs d’audience ;/titleet/game: titre et jeu actuels ;/game_pic: pochette du jeu dans un élément<img>.
Chaque page utilise des identifiants et classes stables (#chat, .chat-message, .chat-user, .chat-text, #followers, #subs, #viewers, #title, #game, #game_pic) et ne charge aucun thème Twitchiou. Les changements sont appliqués par Socket.IO. L’état initial complet est également disponible en JSON via GET /api/public/obs-state.
Un plugin actif peut utiliser le service du core :
self.context.overlays.show(
'<div class="alert">Nouveau record !</div>',
css='.alert{animation:pop .5s;color:white}',
script='root.dataset.score = data.score',
data={"score": 42},
duration=5000,
position="center",
)
self.context.overlays.sound("https://example.test/alert.mp3", volume=0.8)
self.context.overlays.emit("score_changed", {"score": 42})
self.context.overlays.clear()
Pour servir un son, une image ou une police depuis le dossier du plugin, déclarez explicitement le fichier dans manifest.json, puis utilisez son URL :
{"overlay_assets": ["overlay/alert.mp3", "overlay/logo.webp"]}
self.context.overlays.sound(self.context.overlays.asset("overlay/alert.mp3"))
Un plugin Python actif est déjà du code de confiance. Les champs html, css et surtout script disposent donc volontairement de toute la puissance nécessaire aux animations ; n’installez que des plugins dont vous contrôlez la provenance.
Couverture Twitch
Le catalogue embarqué couvre les événements de chaîne EventSub actuels : chat, AutoMod, modération, abonnements, Bits, Power-ups, points de chaîne, sondages, prédictions, Hype Trains, objectifs, raids, publicités, charité, Guest Star, Shield Mode, shoutouts, utilisateurs suspects, avertissements, live/offline et whispers.
Les événements liés à une organisation, une extension, un Drop, un conduit ou à un identifiant externe ne peuvent pas être devinés depuis les deux comptes. Ajoutez-les avec le formulaire Temps réel Twitch → Ajouter un événement. Les conditions peuvent utiliser directement le compte Broadcaster ou Bot connecté, ou un identifiant Twitch saisi manuellement. Chaque abonnement peut ensuite être activé, désactivé, modifié ou supprimé depuis sa carte.
Trente-et-une actions conviviales sont fournies. helix.request offre en plus un accès générique à tous les endpoints Helix actuels ou futurs, sans exposer les jetons au navigateur :
{
"role": "broadcaster",
"method": "GET",
"path": "/channels",
"params": {"broadcaster_id": "1234"},
"body": {}
}
Événements JSON
Tous les événements Twitch partagent cette enveloppe :
{
"id": "identifiant EventSub",
"type": "on_twitch_poll_end",
"twitch_type": "channel.poll.end",
"version": "1",
"source": "twitch",
"occurred_at": "2026-08-12T18:42:00Z",
"received_at": "2026-08-12T18:42:00.123Z",
"subscription": {},
"data": {}
}
Les noms pratiques suivants sont notamment garantis : on_twitch_message, on_twitch_poll_start, on_twitch_poll_end, on_twitch_prediction_start et on_twitch_prediction_end. Tous les autres deviennent automatiquement on_twitch_ suivi du type Twitch avec les points remplacés par des underscores. Le type Twitch original est toujours conservé.
Deux événements sont calculés en arrière-plan par le composant principal après une première mesure de référence :
on_twitch_onviewercount_changecontientcount,previous_count,delta, l’étatliveet les informations du stream ;on_twitch_subcount_changecontienttotal,points, les compteurstiers(1000,2000,3000),gifts,plans,prime, ainsi que les valeurs précédentes et les deltas.
L’API Helix des abonnements ne fournit pas systématiquement un indicateur Prime. prime.count comptabilise donc uniquement les lignes explicitement identifiées comme Prime ; prime.unknown indique les Tier 1 non classables et prime.complete vaut false tant qu’il en existe.
Événements HTTP externes
Le contrat complet, les exemples pour chaque système et le catalogue d’erreurs sont détaillés dans external_api.md.
La page Paramètres → Interfaçage crée des clés propres à chaque fournisseur, avec une expiration facultative, une liste d’événements autorisés et un callback HTTP optionnel. La clé complète est affichée uniquement à sa création ; seule son empreinte SHA-256 est ensuite conservée.
Le fournisseur envoie un objet JSON vers POST /api/external/events. Par exemple, une clé nommée Maison connectée autorisant porte_ouverte publie on_external_maison_connectee_porte_ouverte sur le bus, auprès de l’interface temps réel et des plugins :
curl.exe -k -X POST "https://localhost:5000/api/external/events" -H "Authorization: Bearer twi_ext_REMPLACEZ_PAR_LA_CLE" -H "Content-Type: application/json" --data-raw "{\"event\":\"porte_ouverte\",\"data\":{\"porte\":\"garage\",\"ouverte\":true}}"
Cette forme est compatible avec l’invite de commandes Windows (cmd.exe). L’URL doit rester brute : ne copiez pas une représentation Markdown de la forme [URL](URL) et n’ajoutez pas d’antislash devant les underscores.
Le champ id peut être fourni pour rendre l’envoi idempotent et occurred_at accepte une date ISO 8601. Si un callback est activé, Twitchiou lui transmet en arrière-plan l’enveloppe normalisée complète. Une répétition du même id n’est ni redistribuée aux plugins, ni renvoyée au callback.
ACTION PROCESSOR
Le core fournit un moteur unique pour valider puis exécuter, dans l’ordre, une suite d’actions. L’éditeur de triggers s’appuie directement sur ce moteur : les messages, timeouts, bannissements, injections HTML dans l’overlay système, signaux personnalisés, actions Twitch/OBS autorisées et actions déclarées par les plugins utilisent donc le même contrat.
GET /api/core/action-processorretourne le catalogue complet et les variables disponibles.POST /api/core/action-processor/validatevalide une suite sans l’exécuter.POST /api/core/action-processor/executevalide puis exécute la suite avec son contexte.
Le composant d’interface réutilisable se trouve dans templates/components/action_processor_editor.html. Son contrôleur static/js/action_processor.js expose window.ActionProcessorEditor ; il reçoit les fonctions de lecture, rendu et mise à jour de l’éditeur hôte. Il peut ainsi être inclus dans un autre écran sans recopier le panneau d’actions des triggers.
Le bouton Mode conception avancée ouvre un plan de travail plein écran. Les blocs sont glissés depuis le volet droit, déplacés librement puis raccordés en tirant un câble depuis leur sortie vers l’entrée d’un autre bloc. Valider et utiliser remplace la suite linéaire du trigger par le workflow dessiné ; Annuler ferme la modale sans appliquer le graphe. La bibliothèque située dans la barre supérieure permet d’enregistrer, charger et supprimer jusqu’à 100 workflows réutilisables. Les routes correspondantes sont GET/POST /api/core/action-processor/workflows et DELETE /api/core/action-processor/workflows/<id>.
Le moteur avancé fournit les blocs suivants : début et fin, définition de variable, calcul arithmétique, condition à deux branches, boucle, temporisation, récupération d’un utilisateur Twitch, extraction d’une valeur JSON, création/manipulation de listes et action core/plugin. Les comparaisons affichent des champs adaptés à l’opérateur choisi, notamment un champ d’expression régulière dédié.
Une liste accepte jusqu’à 100 éléments et peut être lue par index, mélangée, dédupliquée, découpée, assemblée en texte ou enrichie ; un bloc permet aussi d’en choisir un élément au hasard. Le résultat complet de chaque action peut être conservé dans une variable puis lu avec un chemin, par exemple {resultat.data.0.id}. Le bloc d’action permet en plus d’associer plusieurs chemins de la réponse à des variables nommées. Cela fonctionne avec les objets JSON retournés par les actions des plugins, dont les paramètres sont présentés dans le même formulaire structuré que dans l’éditeur simplifié. Un workflow est refusé si un bloc est déconnecté, si une branche obligatoire manque ou si une même sortie possède plusieurs câbles. L’exécution est limitée à 500 étapes, les boucles à 100 passages et une temporisation à 300 secondes.
Les variables sont résolues une seule fois avant l’exécution de la série. {arguments} contient la chaîne complète, tandis que {arg_0}, {arg_1}, etc. correspondent aux arguments séparés (les guillemets permettent de conserver un argument contenant des espaces). Le core fournit notamment {followers}, {subs}, {viewers}, {title}, {game}, {current_scene}, {obs_connected}, {obs_streaming} et {obs_recording}. Une variable inconnue reste intacte dans le texte afin de rendre les erreurs de configuration visibles.
Exemple d’exécution manuelle :
{
"access": "moderator",
"context": {"user": "Alice", "login": "alice", "user_id": "123", "arguments": "bonjour"},
"actions": [
{"type": "message", "message": "Message de {user} : {arguments}", "sender_role": "bot"},
{"type": "custom_signal", "event": "suite_terminee", "data": {"auteur": "{login}"}}
]
}
Plugins
Un plugin est un dossier plugins/<slug> contenant manifest.json et plugin.py :
class Plugin:
def __init__(self, context):
self.context = context
def on_event(self, event):
pass
def on_twitch_message(self, event):
if event["data"]["message"]["text"] == "!hello":
self.context.actions.twitch_message("Bonjour !")
def on_unload(self):
pass
def create_plugin(context):
return Plugin(context)
La page Plugins → Tous les plugins affiche tous les modules détectés, actifs ou non. Chaque carte possède un interrupteur d’activation. Lorsqu’un plugin est actif, ses interfaces ordinaires apparaissent dans la même colonne de navigation ; ses pages de configuration restent disponibles dans Paramètres.
context.config contient la configuration enregistrée du plugin. context.actions expose directement les actions du composant principal sous forme de méthodes : twitch_message(), twitch_poll_start(), twitch_ban(), etc. Les paramètres peuvent être fournis comme arguments nommés ou dans un dictionnaire. context.action(name, payload) reste disponible pour les anciens plugins et context.emit(type, data) publie un événement interne. Les callbacks sont exécutés dans un pool de threads pour ne pas bloquer EventSub. Un plugin d’exemple est inclus mais désactivé par défaut.
Les secrets configurables peuvent être déclarés avec "secret_fields": ["api_key"] dans le manifeste. Leur valeur reste disponible côté serveur dans context.config, mais elle est retirée des réponses de l’API des plugins et des variables transmises aux templates. Le booléen api_key_configured permet à l’interface d’indiquer qu’une clé existe. Envoyer un champ secret vide conserve la valeur actuelle ; envoyer api_key_clear: true la supprime explicitement.
Un plugin actif peut également exposer une action dans l’éditeur Paramètres → Triggers. Les triggers peuvent être des commandes préfixées (!commande), des expressions régulières appliquées à l’ensemble du message ou des récompenses de points de chaîne identifiées par leur ID Twitch ou leur titre exact. Les trois utilisent les mêmes conditions et le même éditeur d’actions. Pour une récompense, son éventuelle saisie utilisateur alimente {arguments}. Seules les actions ainsi déclarées sont proposées ; le handler reçoit les paramètres JSON après remplacement des variables du trigger :
def on_load(self):
self.context.commands.register_action(
"changer_mode",
self.changer_mode,
name="Changer de mode",
description="Applique un mode au plugin.",
example={"mode": "{arguments}"},
schema={"mode": {
"type": "select",
"label": "Mode du plugin",
"options": ["normal", "furtif", "festif"],
}},
returns={
"active_mode": {
"path": "mode",
"label": "Mode réellement appliqué",
},
},
access="moderator",
)
def changer_mode(self, command):
mode = command["payload"]["mode"]
utilisateur = command["user_id"]
Un plugin peut aussi fournir ses propres variables. Il déclare leur documentation séparément de leur valeur ; le fournisseur reçoit le contexte déjà construit et est appelé au début de chaque série :
def on_load(self):
self.context.commands.register_variables(
self.action_variables,
variables={
"score": "Score actuel du viewer",
"rang": {"label": "Rang", "description": "Rang du viewer dans le plugin"},
},
)
def action_variables(self, context):
return {
"score": self.score(context["user_id"]),
"rang": self.rang(context["user_id"]),
}
Les types de champs disponibles sont text, textarea, number, boolean, select, string_list et json. Si le schéma est omis, Twitchiou génère le formulaire à partir de example. returns documente les chemins de la réponse JSON : l’éditeur avancé les présente sous forme de boutons et crée la variable choisie sans demander à l’utilisateur de connaître la structure brute. Chaque action conserve également une variable pour sa réponse complète. Les niveaux acceptés sont everyone, moderator et broadcaster. Toutes les variables déclarées fonctionnent dans les chaînes des paramètres, y compris dans les objets et tableaux imbriqués. Discord Webhook déclare ainsi l’action Envoyer un message sur Discord. L’intégration OBS fournit notamment le changement de scène, le volume d’une source, sa mise en sourdine, la diffusion et l’enregistrement.
E-LOIS
Le plugin E-LOIS répond avec le compte Bot lorsqu’un message mentionne son pseudo, ou lorsqu’un viewer répond directement à l’un de ses messages. Sa page de configuration accepte la clé API OpenAI, le modèle et le prompt système, ainsi qu’une taille maximale d’historique. Chaque réponse Twitch utilise la fonction « reply » et référence donc le message d’origine.
Le contexte transmis à OpenAI est configurable indépendamment : titre, jeu, liste des personnes présentes, dernières statistiques connues et capture ponctuelle du rendu OBS. Lorsqu’E-LOIS est interpellé, la capture utilise la scène Programme actuellement composée par OBS via WebSocket, avant toute publicité ajoutée par Twitch. Si OBS est indisponible, E-LOIS répond sans image et ne retombe pas sur le flux Twitch public. La clé OpenAI est un champ secret : elle n’est jamais réinjectée dans le HTML ni renvoyée par l’API Web.
Mario Kart 7 Toolkit
Le plugin Mario Kart 7 Toolkit consomme par défaut le même événement MetaSave que Pokémon GB Toolkit. Lorsque la catégorie Twitch courante est exactement Mario Kart 7, il injecte dans l’overlay système le circuit et le classement animé avec l’objet détenu par chaque joueur. La première détection d’une carapace bleue déclenche une alerte glitchée de deux secondes et une annonce TTS ; les collectes suivantes ne la répètent pas tant que la carapace n’a pas disparu.
Pages Web des plugins
Un plugin peut déclarer une ou plusieurs pages dans manifest.json. Une page ordinaire apparaît dans Plugins. Une page portant "kind": "settings" apparaît uniquement dans Paramètres → Configuration des plugins. Dans les deux cas, le plugin doit être actif :
{
"slug": "scores",
"name": "Scores viewers",
"version": "1.0.0",
"pages": [
{
"id": "dashboard",
"title": "Tableau des scores",
"icon": "bi-trophy",
"template": "pages/dashboard.html",
"style": "pages/dashboard.css",
"script": "pages/dashboard.js"
},
{
"id": "settings",
"title": "Configuration",
"icon": "bi-sliders",
"kind": "settings",
"template": "pages/settings.html",
"script": "pages/settings.js"
}
]
}
Widgets du Tableau de Stream
Un plugin actif peut aussi fournir des widgets déplaçables et redimensionnables pour le Tableau de Stream. Ils sont déclarés séparément des pages :
{
"widgets": [
{
"id": "scores",
"title": "Scores viewers",
"icon": "bi-trophy",
"description": "Classement compact de la session.",
"template": "widgets/scores.html",
"style": "widgets/scores.css",
"script": "widgets/scores.js",
"default_w": 4,
"default_h": 4,
"min_w": 3,
"min_h": 2
}
]
}
Le template Jinja reçoit plugin, widget, config et instance_id. style et script sont optionnels ; leurs fichiers doivent rester dans le dossier du plugin. Le script est chargé chaque fois que le widget devient visible et doit donc pouvoir initialiser plusieurs instances sans réutiliser d’identifiants HTML globaux. La désactivation du plugin retire son widget du catalogue, mais conserve sa place dans la disposition afin qu’il puisse réapparaître après réactivation.
Le widget principal Onglets de widgets fournit une grille interne par onglet. En mode édition, ses boutons permettent d’ajouter ou supprimer des onglets et d’ouvrir la bibliothèque directement sur la grille interne. Les widgets principaux et ceux des plugins peuvent y être déposés ; un widget Onglets ne peut toutefois pas en contenir un autre.
Tous les widgets peuvent être ajoutés plusieurs fois. Chaque exemplaire conserve sa propre position, sa taille et sa configuration ; les lecteurs vidéo et les chats utilisent également des instances DOM indépendantes.
Le widget principal Événements Twitch transforme les signaux techniques en phrases lisibles. Sa configuration permet de changer son titre, le nombre de lignes, l’affichage des descriptions et les catégories visibles : chat, direct, audience, sondages/prédictions, récompenses, modération ou autres événements.
Widgets de l’éditeur d’overlays
Un plugin actif peut enrichir la bibliothèque de l’éditeur visuel d’overlays avec overlay_widgets. Ces widgets utilisent des dimensions en pixels correspondant directement à la résolution OBS :
{
"overlay_widgets": [
{
"id": "score",
"title": "Score",
"icon": "bi-trophy-fill",
"description": "Score courant affiché dans OBS.",
"template": "overlay_widgets/score.html",
"style": "overlay_widgets/score.css",
"script": "overlay_widgets/score.js",
"default_w": 420,
"default_h": 160
}
]
}
Les trois fichiers sont copiés dans chaque instance lors de son ajout. L’utilisateur peut donc personnaliser indépendamment le HTML, le CSS et le JavaScript de plusieurs exemplaires du même widget. Le script peut lire window.TwitchiouWidget, qui contient instanceId, type et config, puis écouter les messages {source: "twitchiou-overlay", kind: "twitch-event", payload: événement} envoyés en temps réel par le core.
Les widgets restent transparents par défaut. Leur code s’exécute dans un document dédié afin d’isoler leurs styles du document permanent et des autres widgets. NMS Toolkit fournit trois exemples : Planète, Base et Position glyphes.
Le widget core Flux RTMP autonome réserve sa propre entrée vidéo dès l’enregistrement de la composition. Chaque exemplaire dispose d’un port et d’une clé indépendants, affiche uniquement son signal dans sa zone et peut être activé, désactivé ou renouvelé séparément. La configuration du widget fournit les valeurs à copier dans OBS ainsi qu’un lien RTMP complet. Supprimer le widget libère son entrée sans affecter les autres flux.
Les ports autonomes sont attribués dans la plage 1940-1999. Adaptez-la avec RTMP_WIDGET_PORT_START et RTMP_WIDGET_PORT_END, notamment si les émetteurs sont sur une autre machine et que le pare-feu doit autoriser cette plage. RTMP_WIDGET_HLS_DIR définit le dossier des segments temporaires.
Intégration OBS
La page Paramètres → Interfaçage → Intégration OBS configure une connexion persistante au protocole OBS WebSocket 5.x. Activez d’abord le serveur dans OBS → Outils → Paramètres du serveur WebSocket, puis renseignez l’hôte, le port (4455 par défaut) et le mot de passe dans Twitchiou.
La page permet de tester la connexion, consulter les scènes, changer la scène programme et exécuter toute requête OBS 5.x. Le mot de passe est chiffré dans SQLite et n’est jamais renvoyé à l’interface. La connexion se rétablit automatiquement après une coupure.
Les routes principales sont GET /api/core/obs/scenes, PUT /api/core/obs/scenes/current et POST /api/core/obs/request. Les plugins disposent des actions obs_scene_list(), obs_scene_current(), obs_scene_set({"scene_name": "Pause"}) et obs_request(...). Les événements bruts reçus depuis OBS sont publiés sur le bus sous la forme on_obs_<nom_evenement>. Twitchiou publie aussi les signaux simplifiés on_obs_scene_changed, on_obs_stream_started, on_obs_stream_stopped, on_obs_record_started et on_obs_record_stopped pour les automatisations et les plugins.
Le template est rendu avec Jinja et reçoit plugin, page et config. style et script sont optionnels et doivent rester dans le dossier du plugin. Une page de paramètres peut enregistrer sa configuration avec PUT /api/plugins/<slug>/config ; le plugin d’exemple fournit un formulaire complet servant de référence.
Deux contrôles HTML génériques sont fournis par le core :
<div data-core-control="game-picker" data-name="game_id"></div>
<div data-core-control="viewer-picker" data-name="viewer_id"></div>
Ils créent un champ caché portant le nom indiqué, puis publient un événement DOM core:select avec l’objet Twitch sélectionné dans event.detail.
Données utilisateurs
Chaque plugin dispose d’un stockage JSON, isolé par défaut dans son slug :
score = self.context.users.get(user_id, "score", 0)
self.context.users.set(user_id, "score", score + 1, user_login=login)
self.context.users.increment(user_id, "score", 1, user_login=login)
self.context.users.delete(user_id, "score")
rows = self.context.users.list(query="viewer")
Le namespace peut être précisé avec namespace="commun". Toutes les valeurs sont consultables et modifiables depuis Paramètres → Données utilisateurs, ou par l’API /api/core/user-data.
Chaque ajout, modification ou suppression produit :
{
"type": "on_core_changevalue",
"source": "core",
"data": {
"user_id": "123",
"namespace": "scores",
"key": "points",
"old_value": 10,
"value": 11,
"deleted": false,
"origin": "PLUGIN:scores",
"origin_type": "PLUGIN",
"origin_plugin": "scores"
}
}
origin vaut API, WEB ou PLUGIN:<slug>.
Envoi d’e-mails
Le service SMTP du core se configure dans Paramètres → Envoi d’e-mails. Il prend en charge SSL/TLS direct, STARTTLS et, pour les réseaux maîtrisés uniquement, SMTP sans chiffrement. Le mot de passe est chiffré dans SQLite et n’est jamais renvoyé à l’interface.
La même page permet de choisir une adresse destinataire et d’activer séparément les notifications de follows, abonnements et cadeaux, cheers, raids, récompenses ainsi que le rapport de fin de stream. Les alertes sont envoyées en arrière-plan. Le rapport est produit à la fermeture de la session Twitch et récapitule sa durée, son audience, les nouveaux followers, les abonnements, le chat et les jeux diffusés.
Un plugin peut envoyer un message texte ou HTML avec le service central :
self.context.mail.send(
"viewer@example.com",
"Votre score",
"Vous avez 42 points.",
to_name="Viewer",
)
Le raccourci self.context.send_mail(...) fournit la même fonction.
L’API équivalente est POST /api/core/mail/send, avec to, subject, body et, facultativement, html et to_name.
La validation TLS utilise d’abord les autorités de confiance du système et vérifie le nom du serveur. Si le certificat est invalide ou auto-signé, aucune authentification SMTP n’est envoyée : l’interface affiche le sujet, l’émetteur, les dates et l’empreinte SHA-256. Après confirmation explicite, le certificat est conservé et son empreinte exacte est vérifiée avant chaque authentification. Tout changement de certificat provoque une nouvelle demande.
Les plugins sont du code Python de confiance : l’interface n’installe aucun paquet distant et ne sandboxe pas leur exécution.
Tests
.venv/bin/python -m unittest discover -s tests -v
Notes de sécurité
- Les jetons OAuth sont chiffrés dans SQLite et ne sont jamais retournés par l’API Web.
- Ne changez pas
APP_SECRET/TOKEN_ENCRYPTION_KEYsans reconnecter ensuite les comptes. - L’UI est prévue pour une utilisation locale. Avant de l’exposer sur Internet, ajoutez TLS, une authentification inverse-proxy, une politique d’origine stricte et une protection CSRF.
- Twitch recommande de ne demander que les scopes réellement nécessaires. Cette configuration en demande volontairement beaucoup pour le centre de contrôle généraliste ; réduisez les listes dans
twitchiou/twitch/scopes.pypour une application distribuée ou soumise à revue.
Documentation de référence : scopes Twitch, types EventSub, API Helix.