Hou.la Connect · Documentation développeur

Documentation développeur de Hou.la Connect

Hou.la Connect joue, sur le PC du streamer, les réactions aux événements d'un live. Cette documentation décrit comment y brancher un jeu, un programme ou un appareil, et comment écrire les packs qui les pilotent.

Format de pack : schéma 3 · Protocole du pont : versions 1 et 2 · Exemples vérifiés par le validateur de Hou.la

Bases

Vue d'ensemble

Connect reçoit les événements d'un live (cadeaux, likes, messages, commandes) depuis Hou.la et d'autres plateformes, puis joue des actions sur le PC du streamer. Trois objets décrivent ce comportement. Ce sont des fichiers JSON validés à la publication : aucun ne contient de code exécuté par Connect.

Pack

Associe des déclencheurs (quand) à des chaînes d'actions (alors). Un pack s'écrit dans le Lab de Connect, à la main ou avec une IA, et se partage dans le Store.

Plugin de jeu

Décrit un jeu ou un programme : ses actions, leurs réglages et son mode de liaison (mod, rcon, telnet, rcon-evrima, ws ou http). Un plugin par jeu, utilisable par tous les packs.

Connexion

Les paramètres d'accès propres à un streamer : adresse, port, mot de passe, jeton. Saisie dans « Mes plugins », elle reste sur son PC. Un pack ne contient aucun identifiant : il désigne une connexion par son rôle.

1 Un spectateur cadeau, like, commande 2 Hou.la Connect le pack choisit l'action 3 Le plugin du jeu mod · rcon · telnet · ws · http 4 Le jeu l'action est jouée événement action + réglages commande, message, requête 1 Un spectateur cadeau, like, commande événement 2 Hou.la Connect le pack choisit l'action action + réglages 3 Le plugin du jeu mod · rcon · telnet · ws · http commande, message, requête 4 Le jeu l'action est jouée

Tout s'exécute sur le PC du streamer. Les réglages sont validés à la publication, puis de nouveau avant chaque envoi.

Le trajet d'une action, de l'événement au jeu.

Ce que tu peux construire

  • Un plugin de jeu : rendre un jeu pilotable par les packs, au moyen d'un mod, de RCON, d'un WebSocket ou d'une API HTTP.
  • Une intégration avec un programme ou un appareil : un outil local, un logiciel de stream, un objet connecté, par HTTP, WebSocket, MQTT ou OSC.
  • Un pack : des interactions prêtes à installer, à partager dans le Store.

Tutoriel

Démarrage rapide : un plugin HTTP

Ce tutoriel relie un serveur HTTP local à Connect par un plugin de jeu en mode http, puis joue une de ses actions depuis un pack, sans être en live.

Prérequis : Hou.la Connect installé et connecté à ton compte, et Node.js pour le serveur d'exemple. Tout langage capable de servir du HTTP convient pour ton propre programme.

  1. Lance un serveur HTTP local

    Enregistre ce code dans server.js, puis lance node server.js. Le serveur écoute sur 127.0.0.1:7777, expose une route POST par action et répond 204 quand l'action a réussi. Si la variable TOKEN est définie, il exige l'en-tête Authorization: Bearer <TOKEN>.

    server.js JavaScript
    // server.js : node server.js
    const http = require('node:http')
    
    const TOKEN = process.env.TOKEN || ''
    const ROUTES = new Set(['/actions/soin', '/actions/ennemi'])
    
    http
        .createServer((req, res) => {
            if (TOKEN && req.headers.authorization !== `Bearer ${TOKEN}`) {
                return res.writeHead(401).end()
            }
            let body = ''
            req.on('data', (chunk) => (body += chunk))
            req.on('end', () => {
                if (req.method !== 'POST' || !ROUTES.has(req.url)) {
                    return res.writeHead(404).end()
                }
                console.log(req.method, req.url, body)
                res.writeHead(204).end()
            })
        })
        .listen(7777, '127.0.0.1', () => console.log('http://127.0.0.1:7777'))
  2. Vérifie-le

    Dans un autre terminal, envoie la requête que Connect enverra pour l'action soin :

    Shell
    curl -i -X POST http://127.0.0.1:7777/actions/soin

    Résultat attendu : HTTP/1.1 204 No Content, et le serveur affiche POST /actions/soin. Une route inconnue rend 404.

  3. Importe le plugin

    Le fichier ci-dessous déclare deux actions : soin (POST /actions/soin) et ennemi (POST /actions/ennemi, avec un corps JSON). Dans Connect : « Mes plugins », « + Créer un plugin », « Un jeu », onglet « JSON », « Importer ». Termine avec « Ajouter mon jeu » : il s'installe sur ce PC, sans relecture.

    plugin-mode-http.json
    plugin-mode-http.json JSON
    {
      "id": "mon-outil-compagnon",
      "nom": "Mon outil compagnon",
      "protocole": "jeu",
      "mode": "http",
      "description": "Exemple : un programme que le joueur lance à côté du jeu, et qui expose une API HTTP locale (son adresse se saisit dans « URL de base », par exemple http://127.0.0.1:7777).",
      "actions": [
        {
          "id": "soin",
          "libelle": {
            "fr": "Soins : trousse",
            "en": "Health: medipack"
          },
          "effet": "aide",
          "method": "POST",
          "path": "/actions/soin"
        },
        {
          "id": "ennemi",
          "libelle": {
            "fr": "Ennemi : au choix",
            "en": "Enemy: pick one"
          },
          "effet": "gene",
          "params": [
            {
              "id": "quoi",
              "type": "choix",
              "defaut": "loup",
              "libelle": {
                "fr": "Ennemi",
                "en": "Enemy"
              },
              "valeurs": [
                {
                  "id": "loup",
                  "libelle": {
                    "fr": "Loup",
                    "en": "Wolf"
                  }
                },
                {
                  "id": "ours",
                  "libelle": {
                    "fr": "Ours",
                    "en": "Bear"
                  }
                }
              ]
            }
          ],
          "method": "POST",
          "path": "/actions/ennemi",
          "json": {
            "type": "{param.quoi}",
            "de": "{sender.name}"
          }
        }
      ]
    }
    Télécharger ce fichier
  4. Crée la connexion

    Dans « Mes plugins », carte « HTTP / API », « Configurer » : ajoute une connexion dont l'« URL de base » est http://127.0.0.1:7777. Le jeton est facultatif ; s'il est rempli, Connect l'envoie dans l'en-tête Authorization: Bearer …, ou dans l'en-tête nommé par la connexion. Quand un pack utilise ton plugin, Connect lui lie une connexion HTTP existante, ou ouvre ce formulaire s'il n'en trouve aucune.

  5. Joue une action depuis un pack

    Dans le Lab, crée un pack et un écouteur (un cadeau, par exemple). Ajoute à sa chaîne un bloc « Dans le jeu », choisis « Mon outil compagnon », puis l'action « Soins : trousse ». Clique « Tester » : l'écouteur est joué tout de suite, sans live.

    Résultat attendu : le serveur affiche POST /actions/soin, et la notification du test indique l'action comme jouée. En cas d'échec, elle donne la cause : aucune réponse en 5 secondes, statut HTTP hors 2xx, ou aucune connexion liée au pack.

Étapes suivantes : ajoute des réglages à tes actions (Référence, « Format d'un plugin de jeu »), puis publie le plugin pour tous les streamers (Publication).

Concepts

Concepts

Les notions employées dans le reste de cette documentation.

Déclencheurs

Un déclencheur est l'événement qui lance une interaction : cadeau Hou.la (dont les cadeaux personnalisés), follow, message ou commande du chat, likes et partages, spectateurs, sondage, roue, raccourci du streamer, événements d'overlay ou de boutique, et événements de TikTok, YouTube et Kick ("type": "platform"). Une interaction accepte jusqu'à 8 déclencheurs : n'importe lequel lance la chaîne. Chaque déclencheur remplit ses propres variables, comme {sender.name} ou {gift.quantity}.

Actions et chaînes

Une action est ce que Connect joue : un son, une requête OBS, un message de chat, une action de jeu… Connect connaît quinze types d'actions (effect.type), listés dans la référence. Un pack choisit dans cette liste ; un type inconnu fait refuser le pack.

Une interaction (rules[i]) relie ses déclencheurs à une chaîne de blocs (do) jouée dans l'ordre : actions, attentes, conditions, tirages au sort, répétitions, compteurs, variables. La durée d'une chaîne est calculée avant de la jouer : 30 secondes au plus.

Connexions et rôles

Une action qui joint un service (RCON, OBS, HTTP, WebSocket, MQTT…) passe par une connexion du streamer. Le pack la désigne par un rôle : par défaut le type de l'action (rcon, http…), ou connector pour en nommer un autre ; un plugin de jeu utilise le rôle jeu:<id>. À l'installation d'un pack, Connect lie chaque rôle à une connexion existante du bon type, ou demande d'en créer une.

Modes de liaison d'un plugin de jeu

Le mode dit comment Connect joint le jeu. Il fixe les champs de la connexion que le streamer remplit, et la forme de chaque action.

Un mod dans le jeu

"mode": "mod"

Pour un jeu sans serveur ni API, que tu peux moder. Le mod se connecte au pont local de Connect. Hou.la héberge l'archive du mod ; Connect l'installe dans le dossier du jeu, trouvé par Steam.

Plugins officiels dans ce mode : 1

Le serveur du jeu, par RCON

"mode": "rcon"

Pour un jeu à serveur qui accepte des commandes RCON. Chaque action envoie 1 à 5 commandes, dans l'ordre. Connexion : hôte, port, mot de passe et pseudo en jeu.

Plugins officiels dans ce mode : 9

La console du serveur, par Telnet

"mode": "telnet"

Pour un serveur qui ouvre une console texte sur TCP, une commande par ligne (7 Days to Die). Chaque action envoie 1 à 5 lignes, dans l'ordre. Si le serveur demande un mot de passe à la connexion, Connect le donne. Une réponse « *** ERROR » est un échec. Connexion : hôte, port, mot de passe (facultatif) et pseudo en jeu. Telnet n'est pas chiffré.

Plugins officiels dans ce mode : 1

Le RCON d'Evrima (The Isle)

"mode": "rcon-evrima"

Le RCON binaire de The Isle (Evrima), pas celui de Source. Chaque action envoie 1 à 5 commandes nommées, prises dans une liste fermée : announce, entities:wipe:corpses, ai:toggle, ai:density, growth:multiplier:toggle, growth:multiplier:set, migrations:toggle, globalchat:toggle, humans:toggle, pause. Bannir, expulser et la liste blanche ne sont pas permis. Connexion : hôte, port (8888 par défaut) et mot de passe.

Plugins officiels dans ce mode : 1

Un WebSocket local

"mode": "ws"

Pour un jeu ou un programme qui écoute un WebSocket. Chaque action envoie un message texte de 2 000 caractères au plus. Connexion : l'adresse du WebSocket.

Plugins officiels dans ce mode : 0

Une API HTTP locale

"mode": "http"

Pour un jeu ou un programme qui expose une API HTTP. Chaque action est une requête GET, POST ou PUT sur un chemin, avec un corps JSON facultatif. Connexion : l'URL de base et, si besoin, un jeton. Si l'API demande le compte du joueur, le bloc oauth fait la connexion avec un compte (OAuth 2 avec PKCE) : Connect ouvre le navigateur sur la page de l'éditeur, garde le jeton de renouvellement chiffré et l'envoie en Authorization: Bearer.

Plugins officiels dans ce mode : 1

Cycle de vie : privé, en relecture, public

Un plugin et un pack passent par ces états.

  1. PrivéAjouté avec « Ajouter mon jeu » : installé sur ton PC, utilisable dans tes packs, invisible des autres.
  2. En relectureEnvoyé avec le premier pack publié dans le Store qui l'utilise : relu par Hou.la avant d'entrer dans le Store, en même temps que le pack. Le motif d'un refus de plugin s'affiche dans Connect.
  3. PublicVisible dans le Store et installable par tous les streamers. Toute modification repasse en relecture.

Guides

Guides

Des procédures pas à pas, classées par objectif.

Les trois façons de relier un jeu

Connect joint un jeu de l'une de trois façons, selon ce que le jeu offre déjà. Identifie la tienne avant d'écrire le plugin : elle fixe son mode, ce que tu écris, et ce que le streamer doit faire.

  1. Le jeu accepte des commandes

    Hou.la Connect RCON, WebSocket ou HTTPLe serveur ou l'API du jeu
    Tu écris
    un plugin
    Le streamer
    saisit au plus l'adresse de son serveur
  2. Le jeu accepte les mods

    Hou.la Connect Pont local (WebSocket)Ton mod, chargé par le jeu
    Tu écris
    un plugin et un mod
    Le streamer
    n'installe rien : Connect installe le mod
  3. Le jeu n'a ni l'un ni l'autre

    Hou.la Connect Pont local (WebSocket)Un mod que tu crées (recommandé)Ou : un programme externe, en HTTP ou WebSocket, qui agit sur le jeu
    Tu écris
    un plugin, et un mod ou un programme
    Le streamer
    rien avec un mod ; lance le programme sinon
Chaque voie se reconnaît à son numéro, à la forme de son cadre (trait épais, double trait, pointillés) et à son titre.
  1. Le jeu a un serveur ou une API qui accepte des commandes

    RCON, console Telnet, WebSocket ou HTTP, comme Minecraft, Counter-Strike 2, Rust ou 7 Days to Die. Un plugin suffit, en mode rcon, telnet, rcon-evrima, ws ou http : chaque action devient une commande, un message ou une requête. Le streamer n'installe rien d'autre que le pack ; il saisit au plus l'adresse de son serveur dans la connexion.

  2. Le jeu accepte les mods

    Écris le mod une fois, puis envoie son archive dans l'assistant, mode « Un mod dans le jeu ». Hou.la l'héberge. Chez chaque streamer qui installe un pack de ce jeu, Connect installe le mod automatiquement dans le dossier du jeu. Exemple : Pratfall.

  3. Le jeu n'a ni l'un ni l'autre

    Crée-lui une porte d'entrée. La voie recommandée est un mod chargé par le jeu : même circuit qu'à la voie 2, rien à installer pour le streamer. À défaut, un programme externe qui expose une API locale, en mode http ou ws : le streamer doit le télécharger et le lancer lui-même, ce qui est plus contraignant.

Quelle voie pour mon jeu ?

Réponds aux questions dans l'ordre : la première réponse « oui » donne la voie.

Question Oui Non
1. Le jeu, ou son serveur, accepte-t-il des commandes RCON ou par une console Telnet, ou expose-t-il une API WebSocket ou HTTP ? OuiVoie 1 : un plugin en mode rcon, telnet, rcon-evrima, ws ou http.NonQuestion 2.
2. Le jeu charge-t-il des mods ? OuiVoie 2 : un plugin en mode mod, et ton mod.NonQuestion 3.
3. Peux-tu ajouter au jeu du code qu'il charge, par exemple en tant que développeur du jeu ? OuiVoie 3, recommandée : crée ce mod, en mode mod, comme à la voie 2.NonVoie 3, à défaut : un programme externe avec une API locale, en mode http ou ws. Le streamer le lance lui-même.

Créer un plugin de jeu

Ce guide part du cas courant : écrire un pack pour un jeu que Connect ne connaît pas encore. Le plugin du jeu se trouve ou se crée depuis le pack, sans quitter le Lab. Un plugin de jeu est une fiche JSON : l'assistant de Connect l'écrit et la valide, et son onglet « JSON » importe et exporte le fichier.

  1. Ouvre la liste des jeux depuis ton pack

    Dans le Lab, onglet « Informations », ouvre le champ « Jeu » de la fiche du pack (le bloc « Dans le jeu » propose la même liste). Si ton jeu n'y figure pas, choisis « Mon jeu n'est pas dans la liste », puis « Ajouter mon jeu » : l'assistant s'ouvre sans quitter le pack.

  2. Installe un plugin existant, s'il y en a un

    Connect propose les plugins de la communauté pour les jeux qui ne sont pas encore installés sur ce PC. Un clic installe le plugin, mod compris, et le jeu est choisi dans le pack : passe à l'étape 8.

  3. Sinon, ouvre l'assistant

    « Ajouter mon jeu » ouvre l'assistant sans quitter le pack (il s'ouvre aussi depuis « Mes plugins », « + Créer un plugin », « Un jeu »). Ses étapes : « Le jeu », « La liaison », « Les actions », « Le mod » (mode mod seulement), « Terminer ». Si ton mod est déjà connecté au pont, le jeu est proposé « en développement » et l'assistant reprend les actions qu'il annonce.

  4. Le jeu

    Nom, identifiant (minuscules, chiffres et tirets, 2 à 40 caractères ; le même que dans le mod), numéro Steam et description. Le numéro Steam est facultatif, mais Connect en a besoin pour trouver le dossier où installer un mod. Dans la description, indique ce que le streamer doit installer ou lancer.

  5. La liaison

    Choisis le mode selon ce que le jeu offre. Un serveur qui accepte RCON : rcon. Une console texte sur TCP : telnet. Le serveur de The Isle : rcon-evrima. Une API HTTP ou un WebSocket, du jeu ou d'un programme compagnon : http ou ws. Aucun des deux, mais un jeu modable : mod. Les champs de la connexion découlent du mode : le plugin ne les déclare pas.

  6. Les actions

    Pour chacune : un nom (en français, et en anglais si tu le souhaites), une description, un effet (aide, gene ou neutre) et jusqu'à 8 réglages (un entier borné, ou un choix dans une liste). 60 actions au plus. En rcon, ws et http, écris le modèle de la commande, du message ou de la requête (Référence, « Format d'un plugin de jeu »).

  7. Le mod (mode mod)

    Moteur, dossier d'installation relatif au dossier du jeu (3 niveaux au plus, sans ..), version (1.0.0), prérequis, puis l'archive .zip (100 Mo au plus). Hou.la l'héberge et en calcule l'empreinte. Le code du mod suit le guide « Écrire un mod qui parle au pont local ».

  8. Ajoute ton jeu, puis teste

    Termine avec « Ajouter mon jeu » : le plugin s'installe sur ce PC, sans relecture. De retour dans le pack, le jeu est déjà choisi, et chaque bloc « Dans le jeu » ajouté le reprend : choisis une action et ses réglages, puis « Tester » joue l'écouteur tout de suite, sans live. Vérifie ainsi chaque action.

  9. Publie ton pack, le plugin suit

    Publie le pack « dans le Store (public) » : le plugin part en relecture avec lui, sans autre geste. Le pack entre dans le Store une fois le plugin validé ; d'ici là, Connect affiche « Attend son jeu » (voir Publication).

Règle de la plateforme : le streamer installe le pack, rien d'autre

Installer un pack installe automatiquement les plugins de jeu qu'il utilise et, en mode mod, dépose leur mod dans le dossier du jeu trouvé par Steam. Si une connexion manque (rcon, ws, http), Connect la demande à l'installation. Un jeu absent de ce PC n'empêche pas l'installation : Connect le signale et réessaie à la première action du pack. Seul le programme externe d'un plugin http ou ws s'installe à part, par le streamer.

Un exemple par mode

Chaque fichier passe le validateur de Hou.la. Importe-le dans l'assistant (onglet « JSON ») comme point de départ.

plugin-mode-mod.jsonUn mod dans le jeu

Les actions ne portent pas de modèle : le mod reçoit l'identifiant de l'action et ses réglages.

plugin-mode-mod.json JSON
{
  "id": "mon-jeu-godot",
  "nom": "Mon jeu Godot",
  "protocole": "jeu",
  "mode": "mod",
  "description": "Exemple : un jeu Godot 4 .NET piloté par un mod qui se relie à Hou.la Connect.",
  "actions": [
    {
      "id": "ralenti",
      "libelle": {
        "fr": "Ralenti",
        "en": "Slow motion"
      },
      "description": {
        "fr": "Le jeu tourne au ralenti quelques secondes.",
        "en": "The game runs in slow motion for a few seconds."
      },
      "effet": "gene",
      "params": [
        {
          "id": "secondes",
          "type": "entier",
          "min": 1,
          "max": 10,
          "defaut": 3,
          "libelle": {
            "fr": "Durée (secondes)",
            "en": "Duration (seconds)"
          }
        }
      ]
    },
    {
      "id": "soigner",
      "libelle": {
        "fr": "Soigner",
        "en": "Heal"
      },
      "effet": "aide",
      "params": [
        {
          "id": "cible",
          "type": "choix",
          "defaut": "moi",
          "libelle": {
            "fr": "Qui",
            "en": "Who"
          },
          "valeurs": [
            {
              "id": "moi",
              "libelle": {
                "fr": "Le streamer",
                "en": "The streamer"
              }
            },
            {
              "id": "tous",
              "libelle": {
                "fr": "Tout le monde",
                "en": "Everyone"
              }
            }
          ]
        }
      ]
    }
  ],
  "mod": {
    "moteur": "godot",
    "dossier": "mods/MonMod",
    "version": "1.0.0",
    "prerequis": {
      "fr": "Aucun : le jeu charge les mods de son dossier mods.",
      "en": "None: the game loads mods from its mods folder."
    }
  }
}

Voir le fichierTélécharger ce fichier

plugin-mode-rcon.jsonLe serveur du jeu, par RCON

{param.valeur} est remplacé par le réglage choisi dans le pack ; {sender.name} et {gift.name} par les données de l'événement.

plugin-mode-rcon.json JSON
{
  "id": "mon-serveur-rcon",
  "nom": "Mon serveur RCON",
  "protocole": "jeu",
  "mode": "rcon",
  "description": "Exemple : un jeu à serveur dédié qui accepte des commandes RCON.",
  "actions": [
    {
      "id": "gravite",
      "libelle": {
        "fr": "Changer la gravité",
        "en": "Change gravity"
      },
      "effet": "neutre",
      "params": [
        {
          "id": "valeur",
          "type": "entier",
          "min": 100,
          "max": 2000,
          "defaut": 200,
          "libelle": {
            "fr": "Gravité (800 = normale)",
            "en": "Gravity (800 = normal)"
          }
        }
      ],
      "commandes": [
        "sv_gravity {param.valeur}"
      ]
    },
    {
      "id": "annonce",
      "libelle": {
        "fr": "Annonce",
        "en": "Announcement"
      },
      "effet": "neutre",
      "commandes": [
        "say {sender.name} a offert {gift.name} !"
      ]
    }
  ]
}

Voir le fichierTélécharger ce fichier

plugin-mode-ws.jsonUn WebSocket local

Le message est du texte, ici du JSON. Pour un réglage à choix, la valeur (goblin) remplace l'identifiant du choix.

plugin-mode-ws.json JSON
{
  "id": "mon-jeu-websocket",
  "nom": "Mon jeu WebSocket",
  "protocole": "jeu",
  "mode": "ws",
  "description": "Exemple : un jeu qui écoute déjà un WebSocket sur ce PC (son adresse se saisit dans la connexion, par exemple ws://127.0.0.1:8123).",
  "actions": [
    {
      "id": "ennemis",
      "libelle": {
        "fr": "Faire apparaître des ennemis",
        "en": "Spawn enemies"
      },
      "effet": "gene",
      "params": [
        {
          "id": "nombre",
          "type": "entier",
          "min": 1,
          "max": 10,
          "defaut": 3,
          "libelle": {
            "fr": "Nombre",
            "en": "Count"
          }
        },
        {
          "id": "type",
          "type": "choix",
          "defaut": "petit",
          "libelle": {
            "fr": "Type",
            "en": "Type"
          },
          "valeurs": [
            {
              "id": "petit",
              "libelle": {
                "fr": "Petits",
                "en": "Small"
              },
              "valeur": "goblin"
            },
            {
              "id": "gros",
              "libelle": {
                "fr": "Gros",
                "en": "Big"
              },
              "valeur": "troll"
            }
          ]
        }
      ],
      "message": "{\"action\":\"spawn\",\"enemy\":\"{param.type}\",\"count\":{param.nombre},\"by\":\"{sender.name}\"}"
    }
  ]
}

Voir le fichierTélécharger ce fichier

plugin-mode-http.jsonUne API HTTP locale

Un chemin par action, un corps JSON facultatif. C'est le plugin du démarrage rapide.

plugin-mode-http.json JSON
{
  "id": "mon-outil-compagnon",
  "nom": "Mon outil compagnon",
  "protocole": "jeu",
  "mode": "http",
  "description": "Exemple : un programme que le joueur lance à côté du jeu, et qui expose une API HTTP locale (son adresse se saisit dans « URL de base », par exemple http://127.0.0.1:7777).",
  "actions": [
    {
      "id": "soin",
      "libelle": {
        "fr": "Soins : trousse",
        "en": "Health: medipack"
      },
      "effet": "aide",
      "method": "POST",
      "path": "/actions/soin"
    },
    {
      "id": "ennemi",
      "libelle": {
        "fr": "Ennemi : au choix",
        "en": "Enemy: pick one"
      },
      "effet": "gene",
      "params": [
        {
          "id": "quoi",
          "type": "choix",
          "defaut": "loup",
          "libelle": {
            "fr": "Ennemi",
            "en": "Enemy"
          },
          "valeurs": [
            {
              "id": "loup",
              "libelle": {
                "fr": "Loup",
                "en": "Wolf"
              }
            },
            {
              "id": "ours",
              "libelle": {
                "fr": "Ours",
                "en": "Bear"
              }
            }
          ]
        }
      ],
      "method": "POST",
      "path": "/actions/ennemi",
      "json": {
        "type": "{param.quoi}",
        "de": "{sender.name}"
      }
    }
  ]
}

Voir le fichierTélécharger ce fichier

Créer un mod

Un mod Hou.la est une archive .zip que Connect extrait dans le dossier du jeu. Connect n'exécute jamais son contenu : c'est le jeu qui le charge, par son propre système de mods ou par un chargeur comme BepInEx.

  1. Relie le mod à Connect

    Une fois chargé par le jeu, le mod se connecte au pont local de Connect, un WebSocket ouvert sur le PC du streamer. Il annonce le jeu et ses actions, reçoit chaque action et répond. Le protocole est décrit dans le guide « Écrire un mod qui parle au pont local » et dans la référence « Protocole du pont ».

  2. Prépare l'archive

    Place à la racine de l'archive ce qui doit se trouver dans le dossier d'installation, rangé comme le jeu l'attend : la racine de l'archive devient ce dossier. Un installateur ou un exécutable n'y sert à rien : aucun fichier de l'archive n'est lancé. Si le jeu a besoin d'un chargeur de mods, indique-le dans les prérequis du plugin : Connect les affiche à l'installation, sans les installer.

  3. Choisis le dossier d'installation

    Un chemin relatif au dossier du jeu, par exemple mods/MonJeu ou BepInEx/plugins/MonJeu : 3 niveaux et 120 caractères au plus, chaque niveau fait de lettres non accentuées, de chiffres, de _, . et -, sans .. ni chemin absolu. Connect trouve le dossier du jeu par Steam, grâce au numéro Steam du plugin. Pour un jeu hors de Steam, Connect n'installe pas le mod : le streamer l'installe à la main, d'après la description du plugin.

  4. Respecte les limites de l'archive

    Zip classique (ni zip64 ni chiffrement), fichiers stockés ou compressés en deflate. 100 Mo au plus, 250 Mo une fois décompressée, 500 entrées au plus. Chaque chemin est relatif, séparé par /, de 200 caractères au plus, sans .. ni antislash, et ne contient que des lettres non accentuées, des chiffres, des espaces et _ . ( ) + -. Aucun lien symbolique, aucun fichier en double. Une archive hors de ces règles est refusée dès l'envoi, avec la raison.

  5. Installation et mises à jour

    Connect télécharge l'archive, vérifie son empreinte, contrôle chaque chemin, puis écrit chaque fichier à côté avant de le renommer : le jeu ne charge jamais un fichier à moitié écrit. Sous Windows, un jeu lancé verrouille souvent ses fichiers : Connect demande alors de fermer le jeu. Change la version du mod à chaque nouvelle archive : Connect compare la version installée à celle du plugin et met le mod à jour quand elles diffèrent.

  6. La relecture

    « Ajouter mon jeu » installe le plugin sur ton PC seulement, pour tes tests. Publier un pack public qui l'utilise envoie le plugin et son mod en relecture : un mod est du code, il est toujours relu par une personne avant d'être proposé aux autres streamers. Chaque nouvelle version repasse en relecture.

Note

Des kits de mod existent pour Godot .NET, Unity (BepInEx 5) et Lua, mais ils ne sont pas en téléchargement public. Le protocole du pont s'implémente directement, dans tout langage qui dispose d'un client WebSocket.

À lire ensuite : Écrire un mod qui parle au pont local · Protocole du pont (mode mod)

Écrire un mod qui parle au pont local

En mode mod, le mod est le client et Connect le serveur : le jeu n'ouvre aucun port, et le mod se reconnecte quand Connect redémarre. Les messages sont du JSON texte sur un WebSocket local. Le détail de chaque message est dans la référence (« Protocole du pont »).

  1. Lis le fichier d'appairage

    Au démarrage, Connect écrit ~/.houla/pont-jeu.json : { "v": 1, "port": 30212, "jeton": "<48 caractères hexadécimaux>" }. Le jeton change à chaque démarrage de Connect et le fichier est effacé à sa fermeture : relis-le avant chaque tentative de connexion.

  2. Connecte-toi

    Ouvre ws://127.0.0.1:<port>/pont/v1 avec l'en-tête X-Houla-Pont: <jeton>, sans en-tête Origin.

  3. Envoie bonjour dans les 5 secondes

    Envoie bonjour avec l'identifiant du jeu, la version du mod et ses actions. En version 2, chaque action est décrite (identifiant, libellé, réglages) et l'assistant s'en sert pour préremplir le plugin. Connect répond bienvenue.

  4. Reçois les actions

    Chaque message action porte un id, l'identifiant de l'action, ses réglages et l'auteur (de : pseudo, quantité, nom du cadeau). Joue l'action dans le jeu.

  5. Réponds dans les 5 secondes

    Renvoie fait avec le même id : "ok": true, ou "ok": false avec une raison (codes de refus dans la référence) et un detail facultatif. Sans réponse en 5 secondes, l'action échoue.

  6. Teste avant d'avoir un plugin

    Un identifiant de jeu valide sans plugin est accepté et affiché « en développement » dans « Mes plugins » : crée ensuite le plugin depuis ce jeu. Une seconde connexion du même jeu remplace la première (code de fermeture 4004).

Note

Aucun kit de mod n'est proposé en téléchargement public, et il n'existe pas de kit pour Unreal Engine. Le protocole s'implémente dans tout langage qui dispose d'un client WebSocket.

Piloter un programme externe ou un objet connecté

Connect ne lance aucun programme. Un programme externe (outil compagnon d'un jeu, logiciel de ton choix) tourne à côté de Connect et expose une API locale ; un plugin de jeu en mode http ou ws lui envoie les actions.

Un programme local avec une API

Le PC du streamer 1 Hou.la Connect plugin en mode http connexion : URL de base 2 Ton programme lancé par le streamer http://127.0.0.1:PORT 3 Le jeu piloté par ton programme POST /… réponse, 5 s ton code Hou.la Connect et ton plugin Ton programme, distribué par toi Le jeu ou l'appareil Le PC du streamer 1 Hou.la Connect plugin en mode http Hou.la Connect et ton plugin POST /… réponse, 5 s 2 Ton programme http://127.0.0.1:PORT Ton programme, distribué par toi ton code 3 Le jeu piloté par ton programme Le jeu ou l'appareil
Connect envoie les actions à ton programme ; ton programme agit sur le jeu ou l'appareil.
  1. Expose une API locale

    Ton programme ouvre une API HTTP (ou un WebSocket) sur 127.0.0.1, à un port fixe. Une route par action : POST /actions/soin, POST /actions/ennemi… Réponds par un statut 2xx quand l'action a réussi. Délai maximal : 5 secondes en HTTP, 4 en WebSocket ; tout autre statut est affiché comme un échec dans le journal du live.

  2. Publie un plugin en mode http

    Une action par route, avec ses réglages. Dans la description du plugin, indique où télécharger ton programme et quelle adresse saisir dans la connexion.

  3. Le streamer installe ton programme

    Il le télécharge depuis ton site, le lance, puis saisit dans Connect l'« URL de base » (par exemple http://127.0.0.1:7777) et un jeton si ton programme en exige un.

  4. Les packs utilisent tes actions

    Dans le Lab, le bloc « Dans le jeu » propose ton plugin et ses actions. Une adresse locale passe obligatoirement par une connexion : un pack ne peut pas écrire http://127.0.0.1 lui-même.

Un logiciel ou un objet connecté courant

Les plugins officiels génériques couvrent les protocoles courants : HTTP / API, WebSocket, MQTT (Zigbee2MQTT, Tasmota…), OSC (VRChat, TouchDesigner, Resolume), RCON, OBS et Streamlabs. D'autres sont prêts à l'emploi : Home Assistant, Elgato Key Light, Discord, Telegram, webhook. Un pack les utilise directement avec les actions http, ws, mqtt ou osc, par une connexion du streamer.

Écrire un pack

Le Lab écrit le JSON d'un pack à ta place ; les recettes ci-dessous montrent les constructions courantes. Chaque fichier passe le validateur de Hou.la et s'importe dans le Lab : onglet « Écouteurs », « JSON », « Importer un fichier… », puis « Vérifier mon pack ».

pack-multi-sources.jsonPlusieurs plateformes, une seule action

Une interaction à cinq déclencheurs : un cadeau personnalisé Hou.la à 100 pièces, un cadeau TikTok d'au moins 99 diamants, un Super Chat YouTube, 100 KICKs ou 100 bits Twitch. Chacun joue la même action de jeu (plugin Minecraft). Les événements Twitch sont acceptés par le format ; leur réception n'est pas encore disponible dans Connect.

pack-multi-sources.json JSON
{
  "schema": 3,
  "rules": [
    {
      "id": "creeper",
      "label": "Creeper",
      "on": [
        {
          "type": "gift",
          "giftSlug": "ix_slot_01",
          "coinCost": 100,
          "group": "Pagaille"
        },
        {
          "type": "platform",
          "platform": "tiktok",
          "event": "gift",
          "min": 99
        },
        {
          "type": "platform",
          "platform": "youtube",
          "event": "superchat"
        },
        {
          "type": "platform",
          "platform": "kick",
          "event": "kicks",
          "min": 100
        },
        {
          "type": "platform",
          "platform": "twitch",
          "event": "bits",
          "min": 100
        }
      ],
      "do": [
        {
          "kind": "action",
          "id": "creeper",
          "effect": {
            "type": "jeu",
            "jeu": "minecraft",
            "action": "invoquer",
            "params": {
              "creature": "creeper",
              "position": "devant_3",
              "qui": "joueur"
            }
          }
        }
      ]
    }
  ]
}
Voir le fichierTélécharger ce fichier
pack-paliers-et-montants.jsonPaliers, switch et repeat

Les likes de Hou.la et de TikTok passent par la même chaîne ; un switch sur hearts.palier choisit la réaction (trois fanfares à 10 000, avec repeat). Les partages lisent leur total. Un cadeau quelconque est routé selon coins, et repeat joue cinq klaxons quand la quantité atteint 5.

pack-paliers-et-montants.json JSON
{
  "schema": 3,
  "rules": [
    {
      "id": "paliers-likes",
      "label": "Paliers de likes",
      "on": [
        {
          "type": "hearts",
          "milestones": [
            1000,
            5000,
            10000
          ]
        },
        {
          "type": "platform",
          "platform": "tiktok",
          "event": "like_total",
          "milestones": [
            1000,
            5000,
            10000
          ]
        }
      ],
      "do": [
        {
          "kind": "switch",
          "id": "selon-palier",
          "conditions": [
            {
              "all": [
                {
                  "left": "hearts.palier",
                  "op": ">=",
                  "right": 10000
                }
              ]
            },
            {
              "all": [
                {
                  "left": "hearts.palier",
                  "op": ">=",
                  "right": 5000
                }
              ]
            }
          ],
          "branches": [
            [
              {
                "kind": "repeat",
                "id": "trois-fanfares",
                "times": 3,
                "gapMs": 1500,
                "branches": [
                  [
                    {
                      "kind": "action",
                      "id": "fanfare",
                      "effect": {
                        "type": "sound",
                        "soundId": "34f7bfb4-b51f-11f1-a1b0-920007989f15",
                        "volume": 0.8
                      }
                    }
                  ]
                ]
              }
            ],
            [
              {
                "kind": "action",
                "id": "wow",
                "effect": {
                  "type": "sound",
                  "soundId": "2ebf44c5-b51a-11f1-a1b0-920007989f15"
                }
              }
            ]
          ],
          "else": [
            {
              "kind": "action",
              "id": "merci",
              "effect": {
                "type": "chat",
                "text": "{hearts.palier} likes, merci à tous !",
                "cooldownMs": 10000
              }
            }
          ]
        }
      ]
    },
    {
      "id": "paliers-partages",
      "label": "Paliers de partages",
      "on": {
        "type": "share_total",
        "milestones": [
          10,
          50,
          100
        ]
      },
      "do": [
        {
          "kind": "action",
          "id": "voix",
          "effect": {
            "type": "tts",
            "text": "{shares.total} partages, merci !"
          }
        }
      ]
    },
    {
      "id": "selon-montant",
      "label": "Selon le montant du cadeau",
      "on": {
        "type": "gift",
        "minCoins": 1
      },
      "do": [
        {
          "kind": "switch",
          "id": "selon-pieces",
          "conditions": [
            {
              "all": [
                {
                  "left": "coins",
                  "op": ">=",
                  "right": 500
                }
              ]
            },
            {
              "all": [
                {
                  "left": "coins",
                  "op": ">=",
                  "right": 100
                }
              ]
            }
          ],
          "branches": [
            [
              {
                "kind": "action",
                "id": "gros",
                "effect": {
                  "type": "sound",
                  "soundId": "34f7bfb4-b51f-11f1-a1b0-920007989f15"
                }
              }
            ],
            [
              {
                "kind": "repeat",
                "id": "klaxons",
                "times": 1,
                "gapMs": 600,
                "cases": [
                  {
                    "when": {
                      "all": [
                        {
                          "left": "quantity",
                          "op": ">=",
                          "right": 5
                        }
                      ]
                    },
                    "times": 5
                  }
                ],
                "branches": [
                  [
                    {
                      "kind": "action",
                      "id": "klaxon",
                      "effect": {
                        "type": "sound",
                        "soundId": "301b847c-b529-11f1-a1b0-920007989f15"
                      }
                    }
                  ]
                ]
              }
            ]
          ],
          "else": [
            {
              "kind": "action",
              "id": "petit",
              "effect": {
                "type": "chat",
                "text": "Merci {sender.name} pour {gift.name} !",
                "cooldownMs": 5000
              }
            }
          ]
        }
      ]
    }
  ]
}
Voir le fichierTélécharger ce fichier
pack-commandes-chat.jsonCommandes de chat

!klaxon est gratuite, limitée à une fois toutes les 30 secondes par spectateur, et tire un son au hasard (70 / 30). !dis coûte 10 pièces et lit le texte du spectateur à voix haute. « !gg » est écouté sur les chats de Hou.la, TikTok, YouTube et Kick.

pack-commandes-chat.json JSON
{
  "schema": 3,
  "rules": [
    {
      "id": "commande-klaxon",
      "label": "Klaxon",
      "on": {
        "type": "command",
        "name": "klaxon",
        "description": "Joue un klaxon au hasard",
        "perViewerMs": 30000
      },
      "enchainement": "ignorer",
      "do": [
        {
          "kind": "random",
          "id": "tirage",
          "weights": [
            70,
            30
          ],
          "branches": [
            [
              {
                "kind": "action",
                "id": "police",
                "effect": {
                  "type": "sound",
                  "soundId": "051db1d2-b523-11f1-a1b0-920007989f15",
                  "maxMs": 3000
                }
              }
            ],
            [
              {
                "kind": "action",
                "id": "dj",
                "effect": {
                  "type": "sound",
                  "soundId": "301b847c-b529-11f1-a1b0-920007989f15"
                }
              }
            ]
          ]
        }
      ]
    },
    {
      "id": "commande-dis",
      "label": "Dis-le à voix haute",
      "on": {
        "type": "command",
        "name": "dis",
        "description": "Le streamer lit ton message à voix haute",
        "coinCost": 10,
        "args": "texte",
        "who": "tous"
      },
      "do": [
        {
          "kind": "action",
          "id": "lire",
          "effect": {
            "type": "tts",
            "text": "{sender.name} dit : {commande.texte}"
          }
        }
      ]
    },
    {
      "id": "mot-gg",
      "label": "GG dans le chat",
      "on": {
        "type": "comment",
        "startsWith": "!gg",
        "platforms": [
          "houla",
          "tiktok",
          "youtube",
          "kick"
        ]
      },
      "enchainement": "ignorer",
      "do": [
        {
          "kind": "action",
          "id": "roulement",
          "effect": {
            "type": "sound",
            "soundId": "5703a3d3-b522-11f1-a1b0-920007989f15",
            "volume": 0.6
          }
        }
      ]
    }
  ]
}
Voir le fichierTélécharger ce fichier
pack-cadeaux-personnalises.jsonCadeaux personnalisés

Quatre cadeaux personnalisés (ix_slot_01 à 04), chacun avec son nom, son prix dans l'échelle fermée et son rayon, reliés aux actions d'un plugin de jeu (Pratfall). L'icône d'un cadeau se règle ensuite dans le Lab.

pack-cadeaux-personnalises.json JSON
{
  "schema": 3,
  "rules": [
    {
      "id": "bombes",
      "label": "Pluie de bombes",
      "on": {
        "type": "gift",
        "giftSlug": "ix_slot_01",
        "coinCost": 30,
        "group": "Pagaille"
      },
      "do": [
        {
          "kind": "action",
          "id": "bombes",
          "effect": {
            "type": "jeu",
            "jeu": "pratfall",
            "action": "bombes",
            "params": {
              "nombre": 3,
              "cible": "hasard"
            }
          }
        }
      ]
    },
    {
      "id": "soins",
      "label": "Soins",
      "on": {
        "type": "gift",
        "giftSlug": "ix_slot_02",
        "coinCost": 45,
        "group": "Coup de main"
      },
      "do": [
        {
          "kind": "action",
          "id": "soigner",
          "effect": {
            "type": "jeu",
            "jeu": "pratfall",
            "action": "soigner",
            "params": {}
          }
        }
      ]
    },
    {
      "id": "klaxon",
      "label": "Klaxon",
      "on": {
        "type": "gift",
        "giftSlug": "ix_slot_03",
        "coinCost": 5,
        "group": "Ambiance"
      },
      "do": [
        {
          "kind": "action",
          "id": "klaxon",
          "effect": {
            "type": "jeu",
            "jeu": "pratfall",
            "action": "klaxon",
            "params": {}
          }
        }
      ]
    },
    {
      "id": "resurrection",
      "label": "Résurrection",
      "on": {
        "type": "gift",
        "giftSlug": "ix_slot_04",
        "coinCost": 280,
        "group": "Coup de main"
      },
      "do": [
        {
          "kind": "action",
          "id": "ressusciter",
          "effect": {
            "type": "jeu",
            "jeu": "pratfall",
            "action": "ressusciter",
            "params": {
              "cible": "hasard"
            }
          }
        }
      ]
    }
  ]
}
Voir le fichierTélécharger ce fichier
pack-outil-compagnon-http.jsonUn programme local en HTTP

Deux cadeaux qui pilotent un plugin officiel en mode http (Tomb Raider I-III Remastered) ; l'action de soin répond aussi à un cadeau TikTok précis (giftId 5655, la Rose).

pack-outil-compagnon-http.json JSON
{
  "schema": 3,
  "rules": [
    {
      "id": "trousse",
      "label": "Trousse de soins",
      "on": [
        {
          "type": "gift",
          "giftSlug": "ix_slot_01",
          "coinCost": 20,
          "group": "Coup de main"
        },
        {
          "type": "platform",
          "platform": "tiktok",
          "event": "gift",
          "giftId": "5655"
        }
      ],
      "do": [
        {
          "kind": "action",
          "id": "trousse",
          "effect": {
            "type": "jeu",
            "jeu": "tomb-raider-remastered",
            "action": "small_medkit",
            "params": {}
          }
        }
      ]
    },
    {
      "id": "trex",
      "label": "T-Rex",
      "on": {
        "type": "gift",
        "giftSlug": "ix_slot_02",
        "coinCost": 490,
        "group": "Pagaille"
      },
      "do": [
        {
          "kind": "action",
          "id": "annonce",
          "effect": {
            "type": "chat",
            "text": "{sender.name} lâche un T-Rex sur Lara !",
            "cooldownMs": 5000
          }
        },
        {
          "kind": "action",
          "id": "trex",
          "effect": {
            "type": "jeu",
            "jeu": "tomb-raider-remastered",
            "action": "trex",
            "params": {}
          }
        }
      ]
    }
  ]
}
Voir le fichierTélécharger ce fichier

Générer un pack avec une IA

  1. Donne à l'IA le fichier guide-ia.md du kit et l'adresse de cette page, puis décris le pack : ce que les spectateurs déclenchent, et ce qui se passe.
  2. Si le pack joue des sons, ajoute la liste des sons : dans Connect, « Sons », « Exporter la liste… » (ou « Liste des sons… » dans le Lab).
  3. L'IA rend un fichier JSON. Dans le Lab : onglet « Écouteurs », « JSON », « Importer un fichier… », puis « Vérifier mon pack ».
  4. Règle ensuite dans le Lab ce qu'une IA ne peut pas connaître : l'icône d'un cadeau personnalisé, les overlays du streamer, les connexions.

Référence

Référence

Formats et protocoles, champ par champ. Les validateurs sont fermés : une clé inconnue fait refuser le fichier entier. La liste exhaustive des déclencheurs, de leurs variables et de l'échelle des prix est dans guide-ia.md, généré depuis Connect.

Format d'un plugin de jeu

Un libellé est une chaîne, ou un objet { "fr": "…", "en": "…" }.

Clé Règle
idIdentifiant du plugin : ^[a-z0-9][a-z0-9-]{1,39}$. Unique dans le Store, et identique à celui que le mod annonce.
nomNom du jeu, 60 caractères au plus.
protocoleToujours "jeu".
descriptionFacultative, 400 caractères au plus. Indique ce qu'il faut installer ou lancer.
jeu.steamAppIdFacultatif : le numéro de la page Steam du jeu (store.steampowered.com/app/4244510/… donne 4244510). Sans lui, Connect ne peut pas installer de mod.
modemod, rcon, telnet, rcon-evrima, ws ou http.
modMode mod seulement : moteur (godot, unity, lua, autre), dossier, version, prerequis. Le bloc fichier (adresse, empreinte, taille) est rempli par Hou.la à l'envoi de l'archive.
oauthMode http seulement, facultatif : autorisation (page de connexion, https), jeton (adresse d'échange et de renouvellement, https), scopes (1 à 20 droits), clientId (l'application que tu as enregistrée chez l'éditeur), port (facultatif, de 1024 à 65535 : l'adresse de rappel devient http://127.0.0.1:<port>/callback, à déclarer chez l'éditeur ; sans lui, un port libre). Aucun secret client : la connexion utilise PKCE.
actions[].idIdentifiant de l'action : a-z, 0-9, _ et -, 40 caractères au plus.
actions[].libelleNom de l'action, 80 caractères au plus.
actions[].descriptionFacultative, 300 caractères au plus.
actions[].effetaide, gene ou neutre (par défaut). Le Lab l'affiche à côté de l'action.
actions[].params8 au plus. entier : min, max, defaut. choix : valeurs (60 au plus) et defaut ; une valeur peut porter une valeur, substituée dans le modèle à la place de son identifiant (sauf en mode mod, où le mod reçoit l'identifiant).
actions[].commandesModes rcon, telnet et rcon-evrima : 1 à 5 commandes, une ligne chacune, 500 caractères au plus.
actions[].messageMode ws : le message, 2 000 caractères au plus.
actions[].method / path / jsonMode http : method (GET, POST, PUT), path ajouté à l'URL de base de la connexion (commence par /, sans ..), json facultatif (un objet, 2 000 caractères au plus).

Variables d'un modèle

{param.<réglage>} : la valeur d'un réglage de l'action. {player} et les autres champs non secrets de la connexion ; un mot de passe ou un jeton n'est jamais inséré dans un modèle. Les données de l'événement : {sender.name} (le pseudo du spectateur), {gift.name}, {gift.quantity}… Chaque valeur est échappée selon sa destination (adresse, JSON, texte).

Format d'un pack (schéma 3)

Un pack est un objet JSON. Squelette minimal :

JSON
{ "schema": 3, "rules": [ { "id": "…", "label": "…", "on": [ … ], "do": [ … ] } ] }

Clés du pack

Clé Règle
schemaToujours 3.
rulesLes interactions, 300 au plus.
chatbot / profiles / maxPlayersFacultatives : réponses automatiques du chat, configurations clavier ou manette, nombre de joueurs simultanés d'un pack manette.
slugFacultatif : le Lab y met l'identifiant du pack.

Clés d'une interaction (rules[i])

Clé Règle
idIdentifiant unique dans le pack, 60 caractères au plus. Garde-le stable d'une version à l'autre : les réglages des streamers y sont attachés.
labelNom de l'interaction, 120 caractères au plus. Pour un cadeau personnalisé, c'est aussi le nom affiché sur sa tuile.
onUn déclencheur, ou une liste de 8 au plus : n'importe lequel lance la chaîne.
doLa chaîne de blocs, jouée dans l'ordre.
followersOnly / moderatorsOnlytrue : réservée aux abonnés, ou aux modérateurs.
enchainementSi l'interaction se déclenche pendant qu'elle tourne : suite (par défaut, les demandes attendent leur tour), ensemble ou ignorer.
profileL'identifiant d'une configuration de profiles (clavier, manette). Absent : toutes.

Déclencheurs (on)

Chaque déclencheur a un type et ses propres réglages (seuil, palier, cadeau précis…). Les variables qu'il remplit et ses réglages exacts sont listés dans guide-ia.md.

Famille type Description
Cadeaux et commandes giftcommandpriorityUn cadeau Hou.la (du catalogue, ou personnalisé ix_slot_01, ix_slot_02… avec son prix et son rayon), une commande de chat déclarée par le pack (gratuite ou payante), un message prioritaire payé.
Audience followjoinviewerheartsshareshare_totalFollow, arrivée d'un spectateur, tous les N spectateurs, likes et partages (un palier, une liste de paliers, ou tous les N).
Chat commentUn message : début (startsWith), mot contenu (contains) ou tous les N messages, sur les chats choisis (Hou.la, TikTok, YouTube, Kick).
Live pollgoalwheelhotkeySondage, objectif de cadeaux, roue du streamer, raccourci clavier du streamer.
Overlays chronomatchobjectifFin d'un chrono, fin d'un match, objectif atteint, sur un overlay du streamer.
Boutique productauctionpurchasepromoProduit épinglé, fin d'enchère, achat payé, code promo ajouté au live.
Autres plateformes platformUn événement de TikTok, YouTube ou Kick : platform, event, et selon l'événement min, giftId ou des paliers. Les événements Twitch sont acceptés par le format, sans être reçus pour l'instant.

Blocs d'une chaîne (do)

Chaque bloc a un kind et un id unique dans son interaction (lettres, chiffres, -, _, 24 caractères au plus).

kind Champs principaux Description
actioneffect, holdMs, doJoue une action (effect). holdMs : temps d'attente après l'action, 10 s au plus.
delayms, doAttend ms millisecondes, 10 s au plus.
ifwhen, do, elseSi la condition when est vraie, joue do, sinon else.
switchconditions, branches, else1 à 20 conditions, une branche par condition : seule la première condition vraie part. else est obligatoire.
randombranches, weights2 à 10 branches, une seule tirée au sort. weights : un poids de 1 à 1000 par branche.
parallelbranches2 à 4 branches, jouées en même temps. Pas de parallel dans un parallel.
repeattimes, branches, gapMs, casesRépète une branche times fois (50 au plus), avec une pause gapMs. cases choisit le nombre de tours selon une condition. Pas de repeat dans un repeat.
countername, op, valueUn compteur de la session : inc, dec ou set. Se lit dans une condition : counter:<nom>. 8 compteurs par pack.
varname, valuePose {var.<nom>} pour la suite de la chaîne. Se lit dans une condition : var:<nom>. 8 par interaction.

Conditions

{ "all": [ … ] } (toutes vraies) ou { "any": [ … ] } (une suffit), 3 clauses au plus. Une clause compare left à une valeur écrite (right, jamais une variable) avec ==, !=, <, <=, >, >= ou contains. left : le montant (coins), la quantité, le message, le palier de likes (hearts.palier), le montant d'une autre plateforme (platform.amount), un tirage de 0 à 99 (random), un compteur, une variable…

JSON
{ "all": [ { "left": "coins", "op": ">=", "right": 100 } ] }

Variables dans les textes

Dans un texte (chat.text, tts.text, rcon.command…), {sender.name}, {gift.name}, {hearts.palier}, {commande.texte}… sont remplacées au moment de jouer. Seules les variables remplies par un déclencheur de l'interaction ont une valeur ; une variable inconnue fait refuser le pack. Une variable n'est jamais admise dans un identifiant (son, overlay, réglage de jeu, scène).

Bornes

48 blocs par interaction et 1200 par pack, 6 niveaux d'imbrication, 30 s de durée calculée par chaîne, 50 tours pour un repeat, 10 s par attente.

Protocole du pont (mode mod)

Un WebSocket local, des messages JSON texte. Versions acceptées : 1 et 2.

Appairage ~/.houla/pont-jeu.json : v, port (30212), jeton (48 caractères hexadécimaux, nouveau à chaque démarrage de Connect). Fichier effacé à la fermeture de Connect.
Adresse ws://127.0.0.1:<port>/pont/v1
En-têtes X-Houla-Pont: <jeton> obligatoire. Aucun en-tête Origin. Host : 127.0.0.1 ou localhost.
Délais bonjour dans les 5 s après la connexion ; fait dans les 5 s après une action. Connect envoie un ping WebSocket toutes les 15 s : un client qui ne répond pas est déconnecté.

Messages

"t": "bonjour"mod → Connect

Premier message du mod. jeu : l'identifiant du plugin (^[a-z0-9][a-z0-9-]{1,39}$). actions : en version 1, une liste d'identifiants ; en version 2, des objets (identifiant, libellé, réglages) que l'assistant reprend pour préremplir le plugin. Au-delà de 32 Ko, ces descriptions sont ignorées.

JSON
{ "t": "bonjour", "v": 2, "jeu": "mon-jeu", "versionMod": "1.0.0", "actions": [ … ] }

"t": "bienvenue"Connect → mod

Réponse de Connect, avec la version retenue.

JSON
{ "t": "bienvenue", "v": 2 }

"t": "action"Connect → mod

Une action à jouer. id : à renvoyer dans fait. params : les réglages validés. de : le pseudo du spectateur, la quantité, le nom du cadeau.

JSON
{ "t": "action", "id": "a7", "action": "ralenti", "params": { "secondes": 3 }, "de": { "pseudo": "Lina", "quantite": 1, "cadeau": "Rose" } }

"t": "fait"mod → Connect

Le résultat. "ok": false s'accompagne d'une raison (40 caractères au plus) et d'un detail facultatif (200 caractères au plus).

JSON
{ "t": "fait", "id": "a7", "ok": true }

"t": "evenement"mod → Connect

Facultatif. Un événement du jeu : nom et clés en a-z0-9_, 20 valeurs simples au plus. Accepté et filtré ; aucun déclencheur ne le lit pour l'instant.

JSON
{ "t": "evenement", "nom": "joueur_mort", "donnees": { "niveau": 3 } }

Codes de refus (raison)

raison Signification
pas_en_partie Aucune partie en cours (menu, chargement).
mode_interdit Le mode de jeu en cours refuse les actions des spectateurs.
pas_maintenant L'action n'a pas pu être jouée à ce moment.
sans_effet Rien à faire (vie déjà pleine, personne à ranimer).
action_inconnue Le mod installé ne connaît pas cette action (version trop ancienne).
params Réglages refusés par le mod.
erreur Erreur dans le jeu, précisée dans detail.
pas_hote Seul l'hôte de la partie peut recevoir les actions.
delai Posé par Connect : aucune réponse fait en 5 secondes.
deconnecte Posé par Connect : le mod s'est déconnecté avant de répondre.

Codes de fermeture

Code Signification
4001 Aucun bonjour reçu dans les 5 secondes.
4002 Version de protocole non prise en charge.
4003 Identifiant de jeu invalide.
4004 Remplacée par une nouvelle connexion du même jeu.
1001 Connect se ferme.

Garanties du serveur

  • Il n'écoute que sur 127.0.0.1.
  • Le jeton est exigé et comparé en temps constant ; absent ou faux, la connexion est refusée (401).
  • Tout en-tête Origin est refusé (403) : une page web ouverte sur le PC ne peut pas se faire passer pour un jeu.
  • L'en-tête Host doit nommer 127.0.0.1 ou localhost, ce qui bloque le DNS rebinding.
  • Un message fait 64 Ko au plus. Une action ne part que si un plugin la déclare, avec des réglages validés.

Types d'actions (effect)

Le champ type d'un effect choisit l'exécuteur. Les champs exacts et leurs valeurs admises sont dans guide-ia.md.

type Champs principaux Description
jeuAction de jeujeuactionparamsUne action d'un plugin de jeu, avec ses réglages écrits en clair (jamais une variable).
soundSonsoundIdlocalFilevolumeinterruptmaxMsUn son de la bibliothèque Hou.la (soundId), ou un fichier du PC désigné par un rôle que le streamer relie (localFile, jamais un chemin). Volume, coupure du son précédent, durée maximale.
chatChattextcooldownMsÉcrit dans le chat Hou.la du live, 300 caractères au plus, avec au moins 2 secondes entre deux messages (cooldownMs).
ttsSynthèse vocaletextlangvoiceratevolumevoiceModehoulaVoicevoiceSetpitchvoiceEffectLit un texte à voix haute, 300 caractères au plus : une voix du système (voice), ou une voix Hou.la (voiceMode : houla avec houlaVoice, random, ou viewer pour une voix fixe par spectateur, qu'il choisit avec !voix). voiceSet limite les voix permises, pitch (-8 à 8 demi-tons) et voiceEffect (robot, radio, echo) s'ajoutent. Les voix Hou.la (16 voix, 31 langues) sont calculées sur le PC du streamer ; sans elles, la voix du système lit.
overlayOverlayoverlayIdcommandevaleurtextePilote un overlay de la bibliothèque du streamer : afficher, masquer, ajouter, retirer, fixer, demarrer, arreter.
keyboardClavierkeysgapMsTouches, combinaisons (shift+c), suites (up,up,down), maintien (space:400). Le jeu doit être au premier plan. Désactivé par défaut.
gamepadManette virtuellebuttonbuttonssequencestepsanalogUne manette Xbox virtuelle : boutons, sticks, gâchettes, séquences. Windows seulement (pilote ViGEmBus). Désactivée par défaut.
obsOBS StudiorequestparamsUne requête OBS WebSocket v5 prise dans une liste fermée : scène, sources, filtres, micro, médias, transitions, enregistrement, replay, caméra virtuelle. Démarrer ou couper le stream est exclu.
streamlabsStreamlabs DesktopcommandscenesourceUne commande d'une liste fermée : scènes, sources, son, enregistrement, replay. Démarrer le stream est exclu.
voicemodVoicemodcommandvoicesounddurationMsChange de voix, joue un son de la soundboard. Voicemod doit être lancé (Windows). Aucune variable.
rconRCONcommandconnectorUne commande à un serveur de jeu (Minecraft, Rust, ARK…). Les verbes destructeurs (stop, ban, op, whitelist…) sont refusés.
httpHTTPmethodurlpathconnectorjsonUne requête GET, POST ou PUT, 5 secondes au plus. Par une connexion : son adresse, même locale, son jeton, et la réponse est lue ({reponse.…}). Par une adresse écrite dans le pack : jamais ce PC ni le réseau local, ni jeton, ni redirection, et la réponse n'est pas lue.
wsWebSocketmessageurlconnectorUn message texte, par une connexion ou par une adresse écrite dans le pack (mêmes règles que HTTP pour ce PC et le réseau local).
mqttMQTTtopicpayloadqosretainconnectorUn message sur un topic, vers le courtier de la connexion du streamer.
oscOSCaddressargshostportUn message OSC en UDP, par défaut vers 127.0.0.1:9000. UDP ne confirme pas la réception : réussi signifie envoyé.

Plugins de jeu officiels

Livrés avec Connect, au même format que les plugins de la communauté. Utilise-les dans tes packs, ou lis-les comme exemples.

Jeu id Mode Actions
Pratfallpratfallmod 16 actions
Minecraftminecraftrcon 8 actions
7 Days to Die7-days-to-dietelnet 8 actions
ARK: Survival Evolvedark-survival-evolvedrcon 6 actions
Counter-Strike 2counter-strike-2rcon 9 actions
Factoriofactoriorcon 4 actions
Left 4 Dead 2left-4-dead-2rcon 6 actions
Project Zomboidproject-zomboidrcon 10 actions
Rustrustrcon 5 actions
Squadsquadrcon 4 actions
Team Fortress 2team-fortress-2rcon 7 actions
The Islethe-islercon-evrima 9 actions
Tomb Raider I Remastered (TRR Director)tomb-raider-remasteredhttp 31 actions

Extraits

Un plugin officiel par mode représenté : l'en-tête et les deux premières actions, tels que Connect les charge.

pratfallPratfall · mod · 16 actions
pratfall.json JSON
{
  "id": "pratfall",
  "nom": "Pratfall",
  "protocole": "jeu",
  "mode": "mod",
  "jeu": {
    "steamAppId": 4244510
  },
  "actions": [
    {
      "id": "bombes",
      "libelle": {
        "fr": "Pluie de bombes",
        "en": "Bomb rain",
        "it": "Pioggia di bombe",
        "es": "Lluvia de bombas",
        "pt": "Chuva de bombas"
      },
      "effet": "gene",
      "params": [
        {
          "id": "nombre",
          "type": "entier",
          "libelle": {
            "fr": "Nombre de bombes",
            "en": "Number of bombs",
            "it": "Numero di bombe",
            "es": "Número de bombas",
            "pt": "Número de bombas"
          },
          "min": 1,
          "max": 5,
          "defaut": 3
        },
        {
          "id": "cible",
          "type": "choix",
          "libelle": {
            "fr": "Qui",
            "en": "Who",
            "it": "Chi",
            "es": "Quién",
            "pt": "Quem"
          },
          "defaut": "moi",
          "valeurs": [
            {
              "id": "moi",
              "libelle": {
                "fr": "Toi",
                "en": "You",
                "it": "Tu",
                "es": "Tú",
                "pt": "Você"
              }
            },
            {
              "id": "hasard",
              "libelle": {
                "fr": "Un joueur au hasard",
                "en": "A random player",
                "it": "Un giocatore a caso",
                "es": "Un jugador al azar",
                "pt": "Um jogador aleatório"
              }
            }
          ]
        }
      ]
    },
    {
      "id": "mine",
      "libelle": {
        "fr": "Mine foreuse",
        "en": "Digging mine",
        "it": "Mina scavatrice",
        "es": "Mina excavadora",
        "pt": "Mina escavadora"
      },
      "effet": "gene",
      "params": [
        {
          "id": "cible",
          "type": "choix",
          "libelle": {
            "fr": "Qui",
            "en": "Who",
            "it": "Chi",
            "es": "Quién",
            "pt": "Quem"
          },
          "defaut": "moi",
          "valeurs": [
            {
              "id": "moi",
              "libelle": {
                "fr": "Toi",
                "en": "You",
                "it": "Tu",
                "es": "Tú",
                "pt": "Você"
              }
            },
            {
              "id": "hasard",
              "libelle": {
                "fr": "Un joueur au hasard",
                "en": "A random player",
                "it": "Un giocatore a caso",
                "es": "Un jugador al azar",
                "pt": "Um jogador aleatório"
              }
            }
          ]
        }
      ]
    }
  ]
}
counter-strike-2Counter-Strike 2 · rcon · 9 actions
counter-strike-2.json JSON
{
  "id": "counter-strike-2",
  "nom": "Counter-Strike 2",
  "protocole": "jeu",
  "mode": "rcon",
  "jeu": {
    "steamAppId": 730,
    "alias": [
      "CS2",
      "Counter Strike 2",
      "CS"
    ],
    "moteur": "source"
  },
  "actions": [
    {
      "id": "gravite",
      "libelle": {
        "fr": "Changer la gravité (triche)",
        "en": "Change gravity (cheat)",
        "it": "Cambia la gravità (trucco)",
        "es": "Cambiar la gravedad (truco)",
        "pt": "Mudar a gravidade (trapaça)"
      },
      "effet": "neutre",
      "params": [
        {
          "id": "gravite",
          "type": "entier",
          "libelle": {
            "fr": "Gravité (800 = normale)",
            "en": "Gravity (800 = normal)",
            "it": "Gravità (800 = normale)",
            "es": "Gravedad (800 = normal)",
            "pt": "Gravidade (800 = normal)"
          },
          "min": 100,
          "max": 2000,
          "defaut": 200
        }
      ],
      "commandes": [
        "sv_gravity {param.gravite}"
      ]
    },
    {
      "id": "bunny_hop",
      "libelle": {
        "fr": "Bunny hop",
        "en": "Bunny hop",
        "it": "Bunny hop",
        "es": "Bunny hop",
        "pt": "Bunny hop"
      },
      "effet": "aide",
      "params": [
        {
          "id": "etat",
          "type": "choix",
          "libelle": {
            "fr": "État",
            "en": "State",
            "it": "Stato",
            "es": "Estado",
            "pt": "Estado"
          },
          "defaut": "oui",
          "valeurs": [
            {
              "id": "oui",
              "libelle": {
                "fr": "Activé",
                "en": "On",
                "it": "Attivo",
                "es": "Activado",
                "pt": "Ativado"
              },
              "valeur": "1"
            },
            {
              "id": "non",
              "libelle": {
                "fr": "Désactivé",
                "en": "Off",
                "it": "Disattivo",
                "es": "Desactivado",
                "pt": "Desativado"
              },
              "valeur": "0"
            }
          ]
        }
      ],
      "commandes": [
        "sv_enablebunnyhopping {param.etat}",
        "sv_autobunnyhopping {param.etat}"
      ]
    }
  ]
}
tomb-raider-remasteredTomb Raider I Remastered (TRR Director) · http · 31 actions
tomb-raider-remastered.json JSON
{
  "id": "tomb-raider-remastered",
  "nom": "Tomb Raider I Remastered (TRR Director)",
  "protocole": "jeu",
  "mode": "http",
  "jeu": {
    "steamAppId": 2478970,
    "alias": [
      "TRR",
      "Tomb Raider Remastered",
      "Tomb Raider 1",
      "Tomb Raider I",
      "Tomb Raider 1 Remastered",
      "TRR Director"
    ],
    "moteur": "autre"
  },
  "actions": [
    {
      "id": "small_medkit",
      "libelle": {
        "fr": "Soins : petite trousse de soins",
        "en": "Health: small medipack",
        "it": "Cure: medikit piccolo",
        "es": "Curación: botiquín pequeño",
        "pt": "Cura: kit médico pequeno"
      },
      "effet": "aide",
      "method": "POST",
      "path": "/small_medkit",
      "json": {
        "provider": "houla",
        "viewer": "{sender.name}"
      }
    },
    {
      "id": "large_medkit",
      "libelle": {
        "fr": "Soins : grande trousse de soins",
        "en": "Health: large medipack",
        "it": "Cure: medikit grande",
        "es": "Curación: botiquín grande",
        "pt": "Cura: kit médico grande"
      },
      "effet": "aide",
      "method": "POST",
      "path": "/large_medkit",
      "json": {
        "provider": "houla",
        "viewer": "{sender.name}"
      }
    }
  ]
}

Sécurité

Limites et sécurité

Règles appliquées à tout pack, plugin et mod. Hou.la les contrôle à la publication, puis Connect avant chaque action.

Interdit à un pack, un plugin ou un mod

  • Lancer un exécutable ou un script. Aucun pack, plugin ou mod ne peut lancer un programme : aucun champ ne désigne un fichier à exécuter. Pour piloter un programme externe, utilise le mode http ou ws vers une API locale exposée par ce programme.
  • Viser ce PC ou le réseau local par une adresse écrite dans le pack (127.0.0.1, 192.168.…, localhost, ou un nom qui y mène). Seule une connexion saisie par le streamer, ou un hôte qu'il a approuvé, y accède.
  • Envoyer un jeton ou un mot de passe ailleurs que vers la connexion du streamer, ou suivre une redirection depuis une adresse écrite dans le pack.
  • Insérer un champ secret d'une connexion (mot de passe, jeton, webhook) dans une commande ou une requête.
  • Jouer une commande RCON destructrice : stop, restart, op, ban, kick, whitelist, save-off, gamemode…
  • Utiliser le clavier, la manette ou le pilote bas niveau sans que le streamer les ait activés : ils sont désactivés par défaut.
  • Démarrer ou couper le stream depuis OBS ou Streamlabs.
  • Envoyer à un jeu une action ou un réglage que son plugin ne déclare pas, ou mettre une variable venue du chat dans un réglage.
  • Porter une clé inconnue : chaque objet est reconstruit depuis une liste fermée, et une clé en trop fait refuser le fichier.
  • Faire exécuter un mod par Connect. Connect vérifie l'empreinte de l'archive, contrôle son contenu (aucun chemin hors du dossier, aucun lien symbolique) et l'extrait dans le dossier du jeu ; c'est le jeu qui charge le mod.

Limites actuelles

Attention

  • Les événements Twitch sont acceptés par le format de pack, mais Connect ne les reçoit pas encore.
  • Un plugin de type « logiciel ou objet connecté » publié depuis l'assistant s'installe depuis le Store, mais ne se configure pas encore dans Connect. Pour piloter un programme, utilise un plugin de jeu en mode http ou ws.
  • Les chaînes autres que Hou.la ne sont écoutées que pendant un live Hou.la.
  • Aucun kit de mod n'est en téléchargement public, et il n'existe pas de kit pour Unreal Engine.
  • Une action OSC ne confirme pas la réception (UDP).

Publication

Publication

Tout compte Hou.la peut publier un plugin ou un pack. La publication pour tous passe par une relecture.

  • Plugin ajouté avec « Ajouter mon jeu » : utilisable tout de suite sur ton PC, dans tes packs, invisible des autres. Rendu public avec le premier pack public qui l'utilise : relu par Hou.la avant d'entrer dans le Store ; chaque modification repasse en relecture. Un plugin avec un mod est toujours relu par une personne.
  • Un pack public attend ses plugins : publier dans le Store un pack qui utilise un plugin de jeu privé envoie ce plugin en relecture. Le pack est relu de son côté, et n'est proposé dans le Store qu'une fois tous ses plugins approuvés. Un plugin refusé ou un mod sans archive bloque l'envoi (PLUGIN_JEU_A_CORRIGER), avec le motif.
  • Un pack public est relu : l'analyse automatique peut seulement refuser, une personne approuve. Un pack approuvé est signé, et Connect vérifie sa signature.
  • Installer un pack installe ses plugins : la liste se calcule depuis ses actions de jeu, sans rien à déclarer. Le mod d'un plugin s'installe avec lui.
  • Commission : le créateur d'un pack peut fixer la sienne, de 0 à 15 %.

Ressources

Kit et téléchargements

Les fichiers de cette documentation. Les exemples sont générés depuis Connect et vérifiés par le validateur de Hou.la.

Télécharger le kit (291 Ko)

  • guide-ia.md : la référence complète du format de pack : variables de chaque déclencheur, échelle des prix, bornes, et actions de chaque plugin de jeu officiel. Le même fichier que le bouton « Guide pour une IA… » du Lab.
  • packs/ : les cinq packs d'exemple des guides.
  • plugins/ : un plugin de jeu par mode.
  • visuels/gabarit-banniere-1600x900.jpg, visuels/prompt-banniere.md : le gabarit de la bannière d'un pack (1600 x 900) et le prompt associé, pour une IA d'images. Icônes de cadeaux : 512 x 512, PNG transparent.
Gabarit de bannière : à gauche le titre, le sous-titre et l'image du jeu ; à droite, un panneau avec le solde de pièces et une grille de cadeaux (icône, nom, prix) dont la dernière rangée est coupée.
Le gabarit de la bannière d'un pack.

FAQ

Questions fréquentes

Dans quel langage écrire un plugin ?

Un plugin est un fichier JSON, sans code. Le seul code est celui de ton mod (mode mod) ou de ton programme (modes http et ws), dans le langage de ton choix.

Comment tester sans être en live ?

Dans le Lab, « Tester » joue un écouteur tout de suite, comme en direct. La notification du test indique ce qu'a fait chaque action : jouée, sautée ou en échec, avec la cause.

Un pack peut-il appeler une adresse locale, comme 127.0.0.1 ?

Seulement par une connexion saisie par le streamer. Une adresse locale écrite dans le pack est refusée.

Comment mon programme authentifie-t-il les requêtes de Connect ?

Par le jeton de la connexion : Connect l'envoie dans Authorization: Bearer <jeton>, ou dans l'en-tête nommé par la connexion (par exemple X-API-Key). Le jeton reste sur le PC du streamer et n'apparaît jamais dans le pack.

Le numéro Steam est-il obligatoire ?

Seulement en mode mod : sans lui, Connect ne peut pas installer de mod. Il est facultatif dans les autres modes.

Que se passe-t-il si le jeu n'est pas lancé (mode mod) ?

L'action échoue et le journal le dit. Si le mod manque, Connect l'installe à la première action qui vise le jeu, puis demande de relancer le jeu.

Puis-je modifier un plugin ou un pack publié ?

Oui. La nouvelle version repasse en relecture avant d'être proposée dans le Store.

Quelle est la différence entre un plugin de jeu en mode http et une action http dans un pack ?

Le plugin déclare des actions nommées et bornées, réutilisables par tous les packs, avec une connexion par jeu. Une action http dans un pack est une requête libre, écrite dans le pack, soumise aux règles d'adresse décrites dans « Limites et sécurité ».

Support développeur

Pose tes questions techniques sur le Discord de Hou.la.