Skip to content

kapp.preferences (config streamer)

Les préférences sont la configuration de l'appli définie par le streamer dans son dashboard. Tu déclares leur structure dans preferences.config.js, et tu les lis à l'exécution via kapp.preferences (ou le global prefs).

Lire les préférences

  • kapp.preferences.get(path = null): Record<string, any> | undefined — lit une préférence par chemin ('global.theme.color'). Renvoie undefined si le chemin n'existe pas. Sans argument : renvoie tout l'arbre.
  • kapp.preferences.preferences — l'arbre complet (objet). Exposé aussi en global prefs.
js
const color = kapp.preferences.get('global.theme.color') ?? '#9146FF';
// équivalent pratique : const color = prefs.global?.theme?.color ?? '#9146FF';

Écrire les préférences (avancé)

  • await kapp.preferences.set(changes: { path: string, data: any }[]): Promise<{ changeset, errors }> — applique une liste de modifications, persistées côté serveur. La lecture locale reflète l’écriture après le await. L’événement EVENTS.UPDATED est livré lorsque les valeurs effectives changent.
js
const { errors } = await kapp.preferences.set([{ path: 'global.counter', data: 0 }]);
if (errors.length) console.warn('prefs non appliquées', errors);

Préférences principales et variantes

Le streamer peut créer des variantes ordonnées conditionnées par son statut Kappapps et/ou sa catégorie Twitch. La première variante active satisfaisant toutes ses conditions gagne ; aucune valeur des autres variantes ne contribue. Sans correspondance, les principales s’appliquent.

Le SDK lit toujours les valeurs effectives dans le format habituel. Une écriture applicative modifie la surcharge existante dans la variante gagnante, sinon les principales. Les groupes se traitent champ par champ ; les collections se traitent en bloc, identifiants internes inclus. Une surcharge reste explicite même si sa valeur est identique aux principales ou vaut null pour un champ qui l’accepte.

Les événements sont propagés à toutes les instances de l’appli, sur chaque overlay concerné. Une édition sans changement effectif ne déclenche aucun événement. Les rafraîchissements du SDK V2 sont ordonnés pour empêcher une ancienne réponse de remplacer les valeurs récentes.

Déclarer la structure : preferences.config.js

Arbre de groupes et d'items typés, exporté en module.exports. Compilé en config/preferences.config.json par le HUB (transpile / watch). Réutilise des structures avec modules/Model.js.

js
const { Model } = require('./modules/Model');

module.exports = {
    type: 'group',
    appearance: { wrap: 'tabs' },
    groupItems: {
        global: {
            type: 'group',
            label: { en: 'Global', fr: 'Global' },
            groupItems: {
                color: {
                    type: 'string',
                    label: { en: 'Theme color', fr: 'Couleur du thème' },
                },
            },
        },
    },
};

Libellés localisés des choix

Pour un item choice ou multipleChoice, chaque valeur de wuOptions.choices peut être associée à un libellé traduit :

js
difficulty: {
    type: 'choice',
    label: { en: 'Difficulty', fr: 'Difficulté' },
    default: 'normal',
    wuOptions: {
        choices: {
            easy: { en: 'Easy', fr: 'Facile' },
            normal: { en: 'Normal', fr: 'Normale' },
            hard: { en: 'Hard', fr: 'Difficile' },
        },
    },
},

Les clés (easy, normal, hard) restent les valeurs stockées et renvoyées par kapp.preferences.get() ; seuls les libellés affichés sont localisés.

Type spécial : runTarget (cible lançable)

Un item runTarget affiche au streamer un picker mixant les applis oneshot installées et les widgets de son channel. La valeur lue (get(path) / prefs) est directement une cible pour kapp.apps.run() : { type: 'app', slug } | { type: 'widget', id } | null (rien de choisi). set() accepte le même objet. wuOptions.targets: 'app' | 'widget' restreint le picker ; default interdit ; en collection → tableau de cibles. Détails et exemples : app-to-app.md.

Type spécial : mediaFile (média du streamer ou de la bibliothèque publique)

Un item mediaFile affiche au streamer un picker de média : ses propres fichiers ou un asset de la bibliothèque publique Kappapps (référencé directement, sans copie). wuOptions : allowedTypes: ['sound'|'video'|'image'], withPosition: bool.

La valeur lue (get(path) / prefs) est hydratée en { file, volume, position } — passe-la telle quelle à kapp.medias.playMedia(pref), quel que soit le média choisi (channel ou bibliothèque).

Default : uniquement un asset de la bibliothèque publique, référencé par son slug — jamais un média de channel (aucun sens cross-channel) :

js
alertSound: {
    type: 'mediaFile',
    label: { en: 'Alert sound', fr: 'Son d\'alerte' },
    wuOptions: { allowedTypes: ['sound'] },
    default: { library: 'kapp-ding', volume: 70 },
},

Le slug se trouve dans le catalogue (kapp.medias.library(), champ slug). Un slug inexistant = erreur CRIT à la validation de l'appli ; un asset dépublié = WARN (il reste jouable, mais il n'apparaît plus au catalogue).

Type spécial : status (statut de stream du channel)

Un item status affiche au streamer un sélecteur de ses statuts de stream configurés (les mêmes que k.statuses). La valeur lue (get(path) / prefs) est hydratée en { id, name, keyword } — même forme que StatusData de k.statuses — ou null si rien n'est sélectionné, ou si le statut choisi a été supprimé depuis. default/defaults interdits ; pas de wuOptions.

js
favoriteStatus: {
    type: 'status',
    label: { en: 'Favorite status', fr: 'Statut favori' },
},

Type spécial : trigger (callback persistant)

Une préférence trigger crée un trigger durable configuré par le streamer. L'app enregistre k.triggers.onTrigger(pref, callback) ; l'activation appelle uniquement une exécution déjà ouverte. Les variantes, collections, arguments propres à chaque préférence et liens API stables sont décrits dans persistent-triggers.md.

Type spécial : dynamicTrigger (trigger choisi par le streamer)

Un item dynamicTrigger affiche le formulaire d'un trigger à l'exécution et hydrate une valeur DynamicTriggerConfig | null. La valeur est prête à être transmise directement à kapp.dynamicTriggers.createFromConfig() : ne la remodèle pas dans l'appli. La même méthode V2 accepte aussi une DynamicTriggerConfig construite manuellement ; elle n'est pas réservée aux préférences.

Pour recevoir des arguments typés propres au callback, le code de l'app peut enrichir une copie de cette valeur avec arguments : config === null ? null : {...config, arguments: schema}. Le schéma n'appartient ni à la préférence, ni à ses default/wuOptions, ni à infos.json. Il est immuable après création. Voir dynamic-triggers.md.

js
// preferences.config.js
viewerAction: {
    type: 'dynamicTrigger',
    label: { en: 'Viewer action', fr: 'Action viewers' },
    required: false,
    wuOptions: {
        types: ['CHATCMD', 'PLDASHB'],
    },
    default: {
        type: 'CHATCMD',
        title: 'Action',
        keyword: 'action',
    },
},

wuOptions.types est optionnel : absent, les neuf types sont disponibles. S'il est présent, il doit être une liste non vide, sans doublon, composée de CHATCMD, KEYWORD, REGEX, BITSCMD, BITS, CHDASHB, PLDASHB, INTERVAL et TWREW. Avec un seul type, le sélecteur est masqué.

required: false permet au streamer de ne rien configurer : la valeur hydratée est alors null. default, defaults localisés, collections, conditions et migrations suivent les conventions habituelles des WorkUnits. Lors d'un changement de type, les champs devenus inapplicables sont retirés de la valeur stockée.

Matrice des champs

Tous les types portent type et title. Les champs supplémentaires sont strictement limités à cette matrice :

TypeChamps spécifiques
CHATCMD, KEYWORD, REGEXkeyword, cost, description, cooldown, user_cooldown, max_per_stream, max_per_player_per_stream, authorization_level
BITSCMDkeyword, cost, description
BITScost, description
CHDASHBaucun
PLDASHBcost, description, cooldown, user_cooldown, max_per_stream, max_per_player_per_stream
INTERVALinterval_duration
TWREWcost, prompt, color, needs_input, cooldown, max_per_stream, max_per_player_per_stream

La forme hydratée est complète et normalisée pour le type choisi :

  • cost: 0 quand le champ existe, sauf TWREW à 500 ;
  • description: '' et prompt: '' ;
  • interval_duration: 300 ;
  • color: '#546a7b' ;
  • needs_input: false pour TWREW ;
  • cooldowns, limites et authorization_level à null quand ils ne sont pas limités.

Contraintes produit : keyword est requis pour CHATCMD, KEYWORD, REGEX et BITSCMD ; l'intervalle minimal est 300 secondes ; le coût Twitch minimal est 10 ; le titre est limité à 25 caractères, ou 45 pour TWREW ; les valeurs négatives sont refusées.

Pour CHDASHB et PLDASHB, la présence d'un formulaire dépend uniquement des arguments déclarés par l'appli. L'ancien champ needs_input est accepté puis ignoré pour préserver les applis déjà compilées. Il reste pertinent pour TWREW, car sa valeur configure directement la collecte d'une saisie par Twitch.

Exemple de valeur CHATCMD réellement lue dans l'appli :

js
{
    type: 'CHATCMD',
    title: 'Action',
    keyword: 'action',
    cost: 0,
    description: '',
    cooldown: null,
    user_cooldown: null,
    max_per_stream: null,
    max_per_player_per_stream: null,
    authorization_level: null,
}

Utilisation directe :

js
const trigger = await kapp.dynamicTriggers.createFromConfig(
    kapp.preferences.get('viewerAction'),
    async (_trigger, request, resolve) => {
        await kapp.chat.say(`${request.setter.display_name} a lancé l'action`);
        await resolve();
        return 'ok';
    },
);

// `trigger` vaut null si la préférence optionnelle n'a pas été configurée.

Déclare le scope triggers:manage dans infos.json. Le type TWREW exige aussi l'accès à la feature Twitch Rewards du channel. Voir dynamic-triggers.md pour la séparation stricte entre cette voie V2 et create() historique.

Type spécial : announces (annonce multi-canal configurée par le streamer)

Un item announces affiche au streamer le formulaire d'annonce complet : messages, variations, et les tracks qu'il veut activer (chat, écran, voix, dashboard, Discord, services d'appli). wuOptions.tags déclare les tags #…# que ton appli fournira, pour qu'il sache quoi écrire.

Ne lis pas la valeur pour la rejouer toi-même — donne son chemin au service d'annonces :

js
// preferences.config.js
messages: {
    type: 'group',
    groupItems: {
        onWin: {
            type: 'announces',
            label: { en: 'On win', fr: 'À la victoire' },
            wuOptions: { tags: { user: 'Le gagnant', amount: 'Les points gagnés' } },
        },
    },
},
js
// script.js
await kapp.announces.play('messages.onWin', { user: user.getDisplayName(), amount: '250' });

Scope à déclarer dans infos.json : announces:play — et lui seul, quelles que soient les tracks que le streamer activera. La valeur hydratée porte une référence interne (_ref) et un discord.webhook réduit à un booléen : le webhook du streamer ne sort jamais du serveur.

appearance : options de présentation d'un item

Chaque item (groupe, collection ou champ) accepte un objet appearance qui pilote son rendu dans le dashboard streamer :

CléValeursEffet
wrap'tabs' | 'list' (défaut)Sur un group ou une collection : affiche les enfants en onglets plutôt qu'empilés.
tabsStyle'underline' | 'pills'Sur un group en wrap: 'tabs' : force le style des onglets. Par défaut le style est automatique (underline pour le groupe racine, pills pour les groupes imbriqués) — c'est la hiérarchie visuelle recommandée.
choicesStyle'cards' | 'select' (défaut)Sur un multipleChoice à choices statiques (objet) : affiche les options en cartes cochables au lieu d'un <select multiple>. Ignoré (WARN) si choices est un path dynamique.
colorDéprécié, ignoré : la colorisation par appli n'est plus rendue, tous les items utilisent l'accent neutre du design system. La clé reste tolérée dans les schémas existants ; ne plus la déclarer dans une nouvelle appli.
foldedtrue | falseL'item démarre replié (dépliable via sa poignée ou son titre).
hiddentrue | falseItem masqué dans le formulaire (la valeur reste lue/écrite normalement).
handletrue | falseAffiche la poignée de pli/tri à gauche de l'item.
lineBreaktrue | falseInsère un séparateur horizontal avant l'item ; lineBreakText: '...' y ajoute un texte.
formDéprécié, ignoré : le formulaire utilise un layout unique (label au-dessus du champ).
js
settings: {
    type: 'group',
    label: { en: 'Advanced', fr: 'Avancé' },
    appearance: { folded: true, handle: true },
    groupItems: { /* ... */ },
},

Pièges

  • prefs n'est pas réactif : après un set(), relis la valeur (ou écoute EVENTS.UPDATED).
  • N'édite pas config/preferences.config.json à la main : il est régénéré depuis le .js.
  • get(path) renvoie undefined pour un chemin inconnu → prévois des valeurs par défaut.