Skip to content

Spécification des flux d'alerte ​

Un AlertFlow est la structure de données derrière chaque alerte construite dans le Créateur d'alertes. C'est un arbre d'étapes, pas un graphe nœuds/arêtes : chaque flux a un seul déclencheur et une liste ordonnée d'étapes évaluées de haut en bas.

Les types canoniques vivent dans app/src/shared/types/alert-flow.ts ; l'évaluation dans app/src/main/alerts/flowEvaluator.ts.

AlertFlow ​

typescript
interface AlertFlow {
  id: string;
  name: string;
  enabled: boolean;
  trigger: AlertTrigger;
  root: Step[];
  createdAt: string;
  updatedAt: string;
}

Seuls les flux avec enabled: true sont chargés et liés au runtime. Le tableau root est le point d'entrée de l'évaluation.

AlertTrigger ​

typescript
interface AlertTrigger {
  type: AlertTriggerType;
  params?: Record<string, unknown>;
}
typeChemin sourceparams
followEventSub-
subscribeEventSub-
cheerEventSub-
raidEventSub-
gift_subEventSub-
welcomeEventSub-
channel_pointFlowEngine (redemption){ rewardId } (absent = toute récompense)
cronFlowEngine (scheduler){ schedule, onlyWhileLive }

Pour cron, onlyWhileLive vaut true par défaut (le job ne se déclenche que pendant le live). Pour channel_point, omettre rewardId fait se déclencher le flux à chaque échange d'une récompense personnalisée.

Step ​

Un Step prend l'une des trois formes discriminées :

typescript
type Step =
  | { kind: "display"; id: string; display: AlertDisplay }
  | {
      kind: "switch";
      id: string;
      variable: string;
      cases: { match: string; steps: Step[] }[];
      default: Step[];
    }
  | {
      kind: "if";
      id: string;
      variable: string;
      op: ConditionOp;
      value: string;
      then: Step[];
      else: Step[];
    };
  • display : pousse son AlertDisplay dans la liste de sortie.
  • switch : résout variable dans le contexte d'event, la convertit en chaîne, puis exécute le premier case dont match est égal à cette chaîne (comparaison de chaînes exacte). Si aucun ne correspond, exécute default.
  • if : compare variable à value via op, puis exécute then ou else.

Les étapes s'imbriquent librement : un case de switch ou une branche de if contient son propre Step[].

ConditionOp ​

typescript
type ConditionOp = "eq" | "neq" | "gt" | "lt" | "gte" | "lte" | "contains";

Sémantique de comparaison (compare() dans flowEvaluator.ts) : les opérandes sont d'abord convertis en chaînes. eq / neq / contains opèrent toujours sur des chaînes. Pour gt / lt / gte / lte, si les deux opérandes se parsent en nombres finis, la comparaison est numérique ; sinon elle retombe sur l'ordre lexicographique (donc "9" > "10" vaut true). Une variable absente résout vers la chaîne vide.

AlertDisplay ​

typescript
interface AlertDisplay {
  phrase: string;
  htmlTemplate?: string;
  assetId?: string;
  imageUrl?: string; // résolu au runtime, jamais persisté
  durationMs: number;
  volume: number; // 0..1
  animaleseEnabled?: boolean;
  animalesePitch?: number;
}

assetId référence un asset de la Médiathèque. Au déclenchement, resolveDisplayImage() retrouve l'asset et fixe imageUrl au nom de fichier que l'overlay peut charger. Voir le Protocole d'assets. imageUrl n'est jamais stocké dans SQLite.

Évaluation ​

evaluateFlow(flow, ctx) parcourt flow.root et retourne un AlertDisplay[] ordonné. La fonction est pure et ne throw jamais : une variable non résolue, un switch sans match et au default vide, ou une branche malformée produisent simplement moins d'affichages. Les étapes s'exécutent dans l'ordre du tableau ; le runtime n'émet actuellement que le premier affichage produit.

Le contexte d'évaluation est indexé sous event, par exemple event.tier, event.userName, event.bits. TRIGGER_VARIABLE_HINTS liste les variables mises en avant par déclencheur dans le picker de l'éditeur (toutes restent accessibles).

Chemins d'exécution ​

Deux chemins alimentent evaluateFlow, tous deux dans app/src/main/handlers/twitch.ts :

  1. Chemin EventSub (processAlert) : follow / subscribe / cheer / raid / gift_sub / welcome. Un event Twitch entrant est confronté aux flux activés du même trigger.type ; un flux correspondant décore l'alerte avec son affichage. Les types réservés aux flux (channel_point, cron) sont rejetés ici.
  2. Chemin flow trigger (handleFlowTrigger, action alerts:flow_trigger) : les flux channel_point et cron sont liés par le FlowEngine (app/src/main/flows/engine.ts), qui dispatche twitch / alerts:flow_trigger au déclenchement. Le bouton de test de l'éditeur utilise le même chemin avec un event synthétique TestUser. Ici le flux est la source : s'il ne produit aucun affichage, rien n'est émis.

L'alerte résultante porte TwitchAlertData.flowId (l'id du flux qui a produit l'affichage). Un overlay restreint à un flux peut filtrer dessus.

Filtre de flux côté overlay ​

L'overlay intégré twitch-alerts lit un paramètre de requête ?flow=<id>. Quand il est présent, l'overlay n'enfile que les alertes dont lastAlert.flowId est égal à cet id ; sans lui, l'overlay affiche toutes les alertes (file partagée). Cela permet à un seul flux de piloter une source navigateur OBS dédiée.

Exemple de flux ​

Un flux d'abonnement qui switche sur le tier :

json
{
  "id": "flow_sub_tiers",
  "name": "Sub tiers",
  "enabled": true,
  "trigger": { "type": "subscribe" },
  "root": [
    {
      "kind": "switch",
      "id": "s1",
      "variable": "event.tier",
      "cases": [
        {
          "match": "3000",
          "steps": [
            {
              "kind": "display",
              "id": "d3",
              "display": {
                "phrase": "{{userName}} passe en Tier 3 !",
                "assetId": "tier3gifAB",
                "durationMs": 6000,
                "volume": 0.9
              }
            }
          ]
        },
        {
          "match": "2000",
          "steps": [
            {
              "kind": "display",
              "id": "d2",
              "display": {
                "phrase": "{{userName}} s'abonne en Tier 2 !",
                "durationMs": 5000,
                "volume": 0.8
              }
            }
          ]
        }
      ],
      "default": [
        {
          "kind": "display",
          "id": "d1",
          "display": {
            "phrase": "Merci pour l'abo, {{userName}} !",
            "durationMs": 4000,
            "volume": 0.7
          }
        }
      ]
    }
  ],
  "createdAt": "2026-06-07T10:00:00.000Z",
  "updatedAt": "2026-06-07T10:00:00.000Z"
}

EventSub fixe event.tier à "1000", "2000" ou "3000". Le Tier 1 retombe sur default.

Released under the MIT License.