Appearance
kapp.chat
Lire et écrire dans le chat Twitch du streamer, et réagir aux messages / commandes des viewers.
Service partagé appli + widget : l'API est identique. Seul le wrapper d'exécution diffère (côté widget :
window.executeWidgetFunction; côté appli :script.jsaprès l'eventAppliLoaded).
Quand l'utiliser (vs alternative)
kapp.chat.say(): faire parler le bot dans le chat.kapp.chat.onMessage / onCommand: réagir en continu au chat (ex. compteur de commande). Côté widget déclenché PAR une commande configurée, lis plutôt les arguments typéskapp.arguments.<key>.kapp.chat.onActivityChange/kapp.chat.activity: réagir au niveau d'activité du chat (calme/animé), pas à chaque message individuel.- Pour du TTS, c'est
kapp.textToSpeech; pour une notif écran,kapp.overlay.
Méthodes
twitch.getSettings(): Promise<ChatSettingsData>/twitch.setSettings(settings: ChatSettingsUpdate): Promise<void>— lit ou modifie les réglages viakapp.chat.twitch. Une appli déclarechat-settings:read/chat-settings:write.say(message: string): Promise<void>— envoie un message dans le chat (le bot parle). async.announce(message: string, color = 'primary'): Promise<void>— annonce Twitch colorée (primary | blue | green | orange | purple). async.shoutout(channel: string): Promise<void>—/shoutoutTwitch vers une chaîne. async. Appli : scopechat:shoutout.getChatEvents(count = 100): Promise<ChatEvent[]>— derniers messages. async.getActiveUsers(options = {}): User[]— viewers actifs récemment. Options :{ time, excludes, count, randomize, property }. sync.onMessage(callback): void—callback(user: User, ev: ChatEvent)à chaque message (hors commandes).onCommand(keywords, callback, options = {}): void—keywords:string | string[] | null(sans le symbole!).callback(user, ev).options:{ once?: boolean, grantedRole?: 'superadmin'|'admin'|'animator'|null }.null= toutes les commandes.onSharedMessage(callback): void— messages en direct provenant des autres chaînes du chat partagé, hors commandes. Même callback(user, ev).onSharedCommand(keywords, callback, options = {}): void— commandes en direct provenant des autres chaînes. Même signature et options queonCommand.onActivityChange(callback): void—callback(activity: ChatActivity)à chaque changement de niveau d'activité du chat.activity: ChatActivity | null— état d'activité courant, lisible en synchrone à tout moment (nulltant que rien n'a encore été reçu, au tout début de l'exécution). sync, propriété (pas une méthode).getActivityState(): ChatActivityState— raccourci équivalent àactivity?.state ?? 'calm'(jamaisnull). sync.
ChatEvent (objet ev) : ev.getMessage(), ev.getCommand(), ev.getArg(n) (1-based), ev.hasArg(n), ev.getArgs() (tous les ChatEventArg), ev.getArgsRaw() (chaînes brutes), ev.getArgsCount(), ev.isMessage(), ev.getMessageId(), ev.getDatetime() (réception locale, en millisecondes), ev.getEmotes() (emotes IRC ou null), ev.isCommand(), ev.isCheer(), ev.getCheer(), await ev.respond('texte'), ev.getTwitchTags() (tags IRC bruts : badges, color, bits…). ev.getArg(n) renvoie un ChatEventArg : .string(), .number() (entier ≥ 0 sinon null), await .user().
Badges / abonnement de l'auteur (sync) : ev.getBadges() (liste {set_id, version}), ev.isSubscriber(), ev.getSubscriberMonths() (mois d'abo exacts), ev.getSubscriberTier() (1|2|3|null). Pour les afficher en images : await kapp.emotes.getBadges(ev) (voir emotes.md).
Réglages du chat Twitch
getSettings() renvoie les champs emote_mode, follower_mode, follower_mode_duration (minutes), slow_mode, slow_mode_wait_time (secondes), subscriber_mode, unique_chat_mode et broadcaster_id. Les durées sont null lorsque le mode est désactivé. moderator_id, non_moderator_chat_delay et sa durée peuvent être absents selon les autorisations Twitch.
setSettings() reçoit uniquement les réglages à changer, au moins un. Les identifiants Twitch sont en lecture seule. Pour modifier une durée, envoyer aussi le mode associé à true :
js
await kapp.chat.twitch.setSettings({slow_mode: true, slow_mode_wait_time: 30});Le mode lent accepte 3 à 120 secondes, le mode followers 0 à 129600 minutes, et le délai hors modérateurs 2, 4 ou 6 secondes. Pour désactiver un mode, envoyer false et omettre sa durée. Les refus serveur rejettent la Promise. Voir la référence Twitch.
Chat partagé : participation explicite de chaque app
onMessage et onCommand ne reçoivent que les messages écrits dans la chaîne de l'app, y compris pendant une session Twitch Shared Chat. Le Core sépare les copies provenant des autres chaînes avant tout traitement local. Les abonnements onSharedMessage et onSharedCommand écoutent ce flux distinct ; ils n'activent aucun mode dans le Core et ne changent pas le comportement des autres apps.
Le flux partagé est en direct uniquement. Il n'alimente ni getChatEvents(), ni getActiveUsers(), ni l'activité locale, ni les commandes/triggers, votes, monnaie ou mémoires IA de la plateforme. Une app qui s'y abonne décide elle-même des actions à effectuer. Les messages/commandes locaux ne sont pas répétés dans les callbacks partagés.
Dans ev.getTwitchTags(), source-room-id identifie la chaîne d'origine et source-id le message original. room-id et id restent ceux de la copie reçue dans la chaîne de l'app. source-badges et source-badge-info sont des chaînes IRC brutes (ou null), pas des objets de badges parsés. Les rôles et badges habituels restent ceux de la chaîne destinataire ; les badges source ne confèrent aucun droit local. first-stream-message vaut false sur ce flux, sans suivi de participation partagé. ev.respond() répond toujours dans la chaîne de l'app, avec l'identifiant de la copie reçue.
Exemple de widget affichant messages et commandes des deux chats (dans sa fonction d'exécution habituelle) :
js
const messages = document.createElement('div');
document.body.append(messages);
function afficher(user, ev) {
const tags = ev.getTwitchTags();
const row = document.createElement('div');
row.textContent = `[${tags['source-room-id'] || tags['room-id']}] ${user.display_name}: ${ev.message}`;
messages.append(row);
while (messages.children.length > 100) messages.firstElementChild.remove();
}
kapp.chat.onMessage(afficher);
kapp.chat.onCommand(null, afficher);
kapp.chat.onSharedMessage(afficher);
kapp.chat.onSharedCommand(null, afficher);Exemple de jeu acceptant volontairement !join depuis les autres chaînes :
js
const participants = new Map();
function rejoindre(user, ev) {
participants.set(user.id, {
user,
channelId: ev.getTwitchTags()['source-room-id'] || ev.getTwitchTags()['room-id'],
});
}
kapp.chat.onCommand('join', rejoindre);
kapp.chat.onSharedCommand('join', rejoindre);Le symbole des commandes et grantedRole utilisent la configuration de la chaîne de l'app. Une commande refusée par grantedRole ne consomme pas un abonnement once. La liste participants appartient à ce jeu ; elle ne modifie pas les viewers actifs locaux de la plateforme.
Niveau d'activité du chat (ChatActivity)
Le niveau d'activité est calculé par la plateforme à partir du débit récent de messages relatif à la baseline propre du channel (pas un seuil absolu global) : un channel habituellement très bavard et un channel calme n'atteignent pas hot au même débit brut.
state/previous:'silent' | 'calm' | 'active' | 'hot'(croissant, quiétude → animation), état courant et état juste avant ce changement.rate: débit de messages courant (messages / unité de temps).baseline: débit habituel du channel.ratio:rate / baseline.ts: timestamp (ms) du calcul.activity.isSilent()/.isActive()/.isHot(): raccourcis surstate.activity.hasRisen()/.hasFallen(): le niveau vient-il de monter/descendre par rapport àprevious(ex.calm→active=hasRisen() === true).
js
kapp.chat.onActivityChange((activity) => {
if (activity.hasRisen() && activity.isHot()) {
kapp.overlay.notification('Le chat est en feu ! 🔥');
}
});Identité du bot (kapp.channel.bot)
Le compte Twitch qui poste réellement les messages de say() (le bot global Kappapps, ou le compte du streamer si un bot perso est configuré) est exposé en synchrone :
kapp.channel.bot:{ login, display_name, profile_image_url } | null.
Sert surtout à exclure le bot d'un tirage ou à filtrer ses propres messages :
js
// tirer 1 viewer actif, hors streamer et hors bot
const [gagnant] = kapp.chat.getActiveUsers({
count: 1,
randomize: true,
excludes: [
{ login: kapp.channel.twitchUser.login },
{ login: kapp.channel.bot?.login },
],
});
botpeut êtrenull(identité indisponible) → toujours l'accéder enkapp.channel.bot?.login.
Exemple (V2, exécutable)
js
// Réagit à !so <pseudo> pendant 30 s (corps d'exécution widget ou appli)
kapp.chat.onCommand('so', async (user, ev) => {
const target = ev.getArg(1).string(); // 1er argument (1-based)
if (target) {
await kapp.chat.say(`📣 Allez voir ${target} !`);
}
});Pièges
onMessage/onCommandne sont PAS async : ils enregistrent un écouteur. Le travail dans le callback, lui, est async →await.keywordss'écrit sans le symbole de commande ('so', pas'!so').getArg(n)est 1-based..number()rejette les décimaux/négatifs → pour un montant :Number(ev.getArg(1).string()).- V1 mort :
kapp.onChatMessage/kapp.onChatCommandn'existent plus. kapp.chat.activitypeut êtrenulljuste au démarrage (avant le premier calcul reçu de la plateforme) →activity?.isHot(), ou utilisegetActivityState()qui renvoie toujours une valeur ('calm'par défaut).- Les seuils
silent/calm/active/hotsont relatifs à la baseline du channel, pas un débit brut universel : ne compare pasrateentre deux channels différents, compare plutôtstate/ratio.