# Kreator widgetów: dokumentacja techniczna

> Dokumentacja kodu widgetów z kreatora KickBot: każde zdarzenie z Kick i jego dane, globalValues, userVariables, funkcje pomocnicze czatu i działanie podglądu.

Source: https://kickbot.com/pl/docs/widgets/widget-builder-reference

Ta strona opisuje, czego Twój kod może używać w widgecie z [kreatora widgetów](https://kickbot.com/pl/docs/widgets/widget-builder) KickBot: zdarzeń `widgetEvent`, obiektu `globalValues`, obiektu `userVariables` i funkcji pomocniczych `window.kickbotWidget`. Każdy widget to zwykły HTML, CSS i JavaScript, który działa w OBS jako browser source. KickBot łączy się z Kick za Ciebie i przekazuje zdarzenia do Twojego kodu.

## Jak działa widget

Gdy OBS otwiera URL widgetu, KickBot:

1. Wczytuje stronę widgetu z przezroczystym tłem.
2. Dodaje Twój CSS w elemencie `<style>` i wstawia Twój HTML na stronę.
3. Wczytuje dane Twojego kanału z Kick (liczbę obserwujących, liczbę widzów, stan streama i ostatnie wiadomości z czatu) do `globalValues`.
4. Uruchamia Twój JavaScript. Działa on raz, po kroku 3, więc `globalValues` jest gotowy, gdy Twój kod startuje.
5. Wysyła do Twojego kodu zdarzenia na żywo jako zdarzenia `widgetEvent` na `document`.

Twój JavaScript może czytać dwa obiekty bez żadnego importu: `globalValues` i `userVariables`. Funkcje pomocnicze są w `window.kickbotWidget`.

Limity:

| Element | Limit |
| --- | --- |
| Szerokość i wysokość | Ustawiasz w sekcji **Ustawienia podstawowe**. Nowy pusty widget ma 800 × 600. Okienko Nowy widget przyjmuje do 7680 × 4320. |
| HTML, CSS i JavaScript | Do 2 000 000 znaków na plik |
| Nazwa widgetu | Do 500 znaków |
| Pliki dla zmiennych użytkownika | Do 25 MB na plik |
| Widgety z kreatora w planie Darmowym | 10 (bez limitu w Premium) |

## Nasłuchuj zdarzeń

Dodaj jeden listener `widgetEvent` na `document`. Każde zdarzenie ma `event.detail` z trzema polami:

| Pole | Typ | Wartość |
| --- | --- | --- |
| `event_name` | string | Zdarzenie, np. `followEvent` |
| `source` | string | Zawsze `kick` |
| `data` | object | Dane zdarzenia (zobacz niżej) |

```javascript
document.addEventListener('widgetEvent', (event) => {
    const { event_name, data } = event.detail;

    if (event_name === 'followEvent') {
        showAlert(data.user.username + ' followed!');
    }
    if (event_name === 'subEvent') {
        showAlert(data.username + ' subscribed for ' + data.months + ' months');
    }
});
```

### Lista zdarzeń

| Nazwa zdarzenia | Kiedy się pojawia | Przycisk testowy |
| --- | --- | --- |
| `subEvent` | Widz subuje kanał | Sub |
| `subGiftEvent` | Widz podarowuje suby | Sub w prezencie |
| `kicksEvent` | Widz wysyła KICKi | KICKs |
| `followEvent` | Widz zaczyna obserwować | Follow |
| `unfollowEvent` | Widz przestaje obserwować | Unfollow |
| `chatMessageEvent` | Ktoś wysyła wiadomość na czacie | Wiadomość |
| `chatMessageDeletedEvent` | Moderator usuwa wiadomość | Wiadomość usunięta |
| `chatMessagePinnedEvent` | Wiadomość zostaje przypięta | Wiadomość przypięta |
| `chatMessageUnpinnedEvent` | Przypięta wiadomość zostaje usunięta | Wiadomość odpięta |
| `chatClearedEvent` | Moderator czyści czat | Czat wyczyszczony |
| `followerCountUpdatedEvent` | Kick wysyła nową liczbę obserwujących | Aktualizacja liczby followerów |
| `subCountUpdatedEvent` | Liczba subów na Kick się zmieniła | Aktualizacja liczby subów |
| `viewerCountUpdatedEvent` | Kick wysyła nową liczbę widzów | Aktualizacja liczby widzów |
| `raidEvent` | Inny kanał robi na Ciebie raid (hostuje Cię) | Raid |
| `streamOnline` | Twój stream się zaczyna | Stream online |
| `streamOffline` | Twój stream się kończy | Stream offline |

Nie ma zdarzenia napiwku. Do alertów o napiwkach użyj [widgetu alertów napiwków](https://kickbot.com/pl/docs/tipping/tip-alerts).

### Dane zdarzeń

To są dane w `event.detail.data`. Odpowiadają zdarzeniom testowym w edytorze. Prawdziwe zdarzenia mogą mieć więcej pól. Nazwy, liczby i ID poniżej to przykłady.

`subEvent`

```javascript
{
    months: 1,
    username: 'viewer_one',
    chatroom_id: 3847194
}
```

`subGiftEvent`

```javascript
{
    chatroom_id: 3847194,
    gifted_usernames: ['viewer_one', 'viewer_two', 'viewer_three'],
    gifter_username: 'big_gifter'
}
```

`kicksEvent`

```javascript
{
    message: 'Great stream!',
    sender: { id: 12345678, username: 'viewer_one', username_color: '#93EBE0' },
    gift: {
        gift_id: 'full_send',
        name: 'Full Send',
        amount: 100,
        type: 'BASIC',
        tier: 'BASIC',
        character_limit: 0,
        pinned_time: 0
    },
    channel: 'channel_3984507'
}
```

`followEvent` i `unfollowEvent` (ten sam format; `followers_count` to nowa liczba)

```javascript
{
    id: 'd4e5f6a7-89b0-12c3-d456-78901e2f3a4b',
    channel: { id: 3984507, slug: 'kickbotdev' },
    user: { id: 14873539, slug: 'viewer_one', username: 'viewer_one' },
    followers_count: 36,
    created_at: '2024-05-13T22:19:07+00:00'
}
```

`chatMessageEvent`

```javascript
{
    id: '85c884b3-7377-478c-b6aa-22b7db752b93',
    chatroom_id: 3974112,
    content: 'hello [emote:37226:KEKW]',
    type: 'message',
    created_at: '2024-05-13T22:29:52+00:00',
    sender: {
        id: 4063326,
        username: 'viewer_one',
        slug: 'viewer_one',
        identity: {
            color: '#72ACED',
            badges: [{ type: 'subscriber', text: 'Subscriber', count: 3 }]
        }
    }
}
```

Emotki z Kick są w `content` jako `[emote:ID:NAME]`. Użyj `createChatMessageFragment` (zobacz [Bezpieczne wyświetlanie tekstu z czatu](#show-chat-text-safely)), żeby zamienić je na obrazki.

`chatMessageDeletedEvent`

```javascript
{
    id: '9cd3788a-ecd2-4051-a162-a7ea9be8ce7a',
    message: { id: '89d6be86-63db-4581-922e-14a6c4a9c2e6' }
}
```

Użyj `data.message.id`, żeby znaleźć i usunąć wyświetloną wiadomość.

`chatMessagePinnedEvent` (`data.message` ma ten sam format co dane `chatMessageEvent`)

```javascript
{
    message: {
        id: '5d798a38-ad17-45e4-8169-bda2faa85b79',
        chatroom_id: 3974112,
        content: 'This is a pinned message!',
        type: 'message',
        created_at: '2024-05-13T22:30:41+00:00',
        sender: { id: 4063326, username: 'kickbotdev', slug: 'kickbotdev', identity: { color: '#72ACED', badges: [] } }
    }
}
```

`chatMessageUnpinnedEvent`: `data` to pusta tablica (`[]`).

`chatClearedEvent`

```javascript
{ id: '47e3596f-0274-46e0-85b1-029fa3bfc5ea' }
```

`followerCountUpdatedEvent` (uwaga: `followersCount` jest w camelCase)

```javascript
{ followersCount: 1532, channel_id: 4599, created_at: 1715639984 }
```

`subCountUpdatedEvent`

```javascript
{ subscriber_count: 184, channel_id: 4599 }
```

`viewerCountUpdatedEvent`

```javascript
{ viewer_count: 320, channel_id: 5937485 }
```

`raidEvent`

```javascript
{
    chatroom_id: 839485,
    optional_message: 'gg stream',
    number_viewers: 150,
    host_username: 'raiding_channel'
}
```

`streamOnline`

```javascript
{
    livestream: {
        id: 3984507,
        channel_id: 3984507,
        session_title: 'Test stream!',
        created_at: '2024-05-13T22:30:41+00:00'
    }
}
```

`streamOffline`

```javascript
{
    livestream: {
        id: 3984507,
        channel: { id: 3984507, is_banned: false }
    }
}
```

## Wartości globalne

`globalValues` przechowuje bieżący stan Twojego kanału. KickBot wypełnia go, zanim uruchomi się Twój kod, i aktualizuje go, gdy przychodzą zdarzenia. Użyj go, żeby narysować widget od razu na starcie, zanim przyjdzie jakiekolwiek zdarzenie.

```javascript
globalValues = {
    subCount: 184,
    followerCount: 1532,
    viewerCount: 320,
    lastFollower: { /* the last follow */ },
    lastSub: { /* the last sub, for example { username, months } */ },
    lastGiftedSub: { /* the last gift, for example { gifter_username, gifted_usernames } */ },
    chatMessages: [ /* chatMessageEvent payloads */ ],
    streamerInfo: {
        kick: {
            username: 'KickBotDev',
            slug: 'kickbotdev',
            profile_picture: 'https://files.kick.com/images/user/.../fullsize.webp',
            user_id: '4063326',
            channel_id: '3984507',
            chatroom_id: '3974112',
            bio: 'Channel bio',
            subscriber_badges: [ /* your channel's sub badges from Kick */ ]
        }
    },
    currentStream: {
        is_live: true,
        title: 'Stream title',
        started_at: '2026-10-24 18:00:00'
    }
}
```

| Wartość | Jak się aktualizuje |
| --- | --- |
| `subCount` | Wczytana z Kick. Rośnie o 1 przy każdym `subEvent` i o 1 przy każdym podarowanym subie, a `subCountUpdatedEvent` ustawia ją na nowo. |
| `followerCount` | Wczytana z Kick. Ustawiają ją `followEvent`, `unfollowEvent` i `followerCountUpdatedEvent`. |
| `viewerCount` | Wczytana z Kick (0, gdy jesteś offline). Ustawia ją `viewerCountUpdatedEvent`. |
| `lastFollower`, `lastSub`, `lastGiftedSub` | Ostatni taki przypadek, który KickBot widział na Twoim kanale, albo `null`. Aktualizuje je odpowiednie zdarzenie. |
| `chatMessages` | Ostatnie wiadomości z czatu Kick przy wczytaniu widgetu, potem każda nowa wiadomość. Trzyma 100 najnowszych. `chatClearedEvent` ją opróżnia. Żeby ukrywać usunięte wiadomości, obsłuż `chatMessageDeletedEvent` we własnym kodzie. |
| `streamerInfo.kick` | Twój profil Kick. Nie zmienia się, gdy widget działa. |
| `currentStream` | Wczytana z Kick przy wczytaniu widgetu. `streamOnline` ani `streamOffline` jej nie aktualizują. Jeśli potrzebujesz aktualnego stanu, nasłuchuj tych zdarzeń. |

## Zmienne użytkownika

Zmienne użytkownika to ustawienia, które streamerzy widzą, gdy otwierają Twój widget w Zapisanych widgetach. Dodajesz je w sekcji **Zmienne użytkownika** w edytorze. W kodzie odczytujesz wartość przez `userVariables.<variable name>.value`.

```javascript
const goal = Number(userVariables.follower_goal.value);
document.body.style.color = userVariables.text_color.value;
```

Pola zmiennej:

| Pole | Co to jest |
| --- | --- |
| Tytuł | Etykieta, którą widzą streamerzy. |
| Nazwa zmiennej | Nazwa w Twoim kodzie. Tylko litery, cyfry i `_`. Po utworzeniu zmiennej nie można jej zmienić. |
| Tooltip | Dodatkowy tekst pomocy pod tytułem. |
| Typ zmiennej | Zobacz tabelę poniżej. |
| Wartość domyślna | Wartość, zanim streamer ją zmieni. Może być pusta. |
| Wymagana | Streamer musi wpisać wartość, zanim zapisze. |

| Typ | Co widzi streamer | `value` w Twoim kodzie |
| --- | --- | --- |
| Tekst | Pole tekstowe | String |
| Liczba | Pole liczbowe | Liczba albo string z liczbą. Dla pewności użyj `Number()`. |
| Kolor | Wybór koloru | String z kolorem hex, np. `#ffffff` |
| Lista rozwijana | Menu z Twoimi opcjami | Wybrana opcja jako string. Domyślnie pierwsza opcja. |
| Przełącznik | Przełącznik wł./wył. | `true` albo `false` |
| Obraz/GIF | Pole na URL i przycisk wgrywania | URL do pliku |
| Wideo | Pole na URL i przycisk wgrywania | URL do pliku |
| Audio | Pole na URL i przycisk wgrywania | URL do pliku |

Wgrane pliki przechowuje KickBot, a `value` to URL pliku. Menu obok zmiennej pozwala przenieść ją w górę lub w dół, edytować ją albo usunąć. Kolejność w edytorze to kolejność, którą widzą streamerzy.

## Bezpieczne wyświetlanie tekstu z czatu

Traktuj każde pole zdarzenia i każdą zmienną użytkownika jak tekst od obcej osoby. KickBot czyści tekst z czatu i ze zdarzeń, zanim dostanie go Twój kod, więc jest bezpieczny jako tekst w HTML, ale nie jako URL, CSS czy skrypt. Nie wstawiaj surowych pól zdarzeń do `innerHTML`. Użyj tych funkcji pomocniczych:

| Funkcja | Co robi |
| --- | --- |
| `window.kickbotWidget.createChatMessageFragment(content, font_size)` | Zwraca `DocumentFragment` z węzłami tekstowymi i obrazkami emotek z Kick. Emotki mają `font_size + 3` pikseli. Domyślnie `font_size` to 20. |
| `window.kickbotWidget.createTextNode(value)` | Zwraca węzeł tekstowy dla dowolnej wartości. |
| `window.kickbotWidget.escapeHtmlText(value)` | Escapuje tekst do użycia między tagami HTML, gdy musisz zbudować string z HTML. Nie używaj go do URL-i, CSS ani skryptów. |

```javascript
document.addEventListener('widgetEvent', (event) => {
    if (event.detail.event_name !== 'chatMessageEvent') return;
    const data = event.detail.data;

    const line = document.createElement('div');
    line.id = 'msg-' + data.id;
    const name = document.createElement('strong');
    name.style.color = data.sender.identity.color;
    name.append(window.kickbotWidget.createTextNode(data.sender.username + ': '));
    line.append(name);
    line.append(window.kickbotWidget.createChatMessageFragment(data.content, 20));
    document.getElementById('chat').append(line);
});
```

Do innego tekstu używaj `textContent` albo `createTextNode`.

## Podgląd

Podgląd w edytorze działa jak OBS, z kilkoma różnicami ułatwiającymi testy:

- Gdy zmienisz kod albo zmienną użytkownika, podgląd uruchamia Twój JavaScript ponownie. KickBot najpierw usuwa listenery `widgetEvent` z poprzedniego uruchomienia. Timery z `setInterval` i `setTimeout` nie są usuwane, więc kliknij przycisk odświeżania, jeśli zaczną się nawarstwiać.
- Gdy zmienisz szerokość albo wysokość, podgląd się przeładowuje.
- Zdarzenia testowe trafiają tylko do podglądu. Testowy **Follow** dodaje 1 do `globalValues.followerCount`, a **Unfollow** odejmuje 1. **Aktualizacja liczby widzów** wysyła przykładową liczbę. **Wiadomość usunięta** usuwa losową wiadomość z czatu w podglądzie.
- Jeśli Twój kanał nie ma jeszcze ostatniego obserwującego, suba ani podarowanego suba, podgląd używa przykładowych danych dla `lastFollower`, `lastSub` i `lastGiftedSub`.
- Żeby zobaczyć wynik `console.log` i błędy, kliknij przycisk otwierania w nowym oknie i otwórz w tym oknie narzędzia deweloperskie.

## Przykład: cel obserwujących

To krótki cel obserwujących. Używa listenera zdarzeń, `globalValues` i dwóch zmiennych użytkownika: `follower_goal` (Liczba) i `bar_color` (Kolor).

HTML:

```html
<div class="goal">
    <span id="count"></span> / <span id="target"></span>
    <div class="bar"><div class="fill"></div></div>
</div>
```

CSS:

```css
body { margin: 0; font-family: Arial, sans-serif; color: #fff; }
.goal { padding: 12px; background: #100c28; border-radius: 16px; font-size: 32px; font-weight: bold; }
.bar { height: 24px; margin-top: 8px; background: #3d3957; border-radius: 12px; overflow: hidden; }
.fill { height: 100%; width: 0; transition: width 0.3s ease; }
```

JavaScript:

```javascript
const target = Number(userVariables.follower_goal.value) || 100;
document.getElementById('target').textContent = target;
document.querySelector('.fill').style.background = userVariables.bar_color.value;

function update(count) {
    document.getElementById('count').textContent = count;
    document.querySelector('.fill').style.width = Math.min((count / target) * 100, 100) + '%';
}

document.addEventListener('widgetEvent', (event) => {
    const { event_name, data } = event.detail;
    if (event_name === 'followEvent' || event_name === 'unfollowEvent') update(data.followers_count);
    if (event_name === 'followerCountUpdatedEvent') update(data.followersCount);
});

update(globalValues.followerCount);
```

Pełną wersję z większą liczbą ustawień dostaniesz, gdy zaczniesz nowy widget od szablonu **Simple Follower Goal**.

## Wskazówki

- Narysuj stan początkowy z `globalValues`, a potem zmieniaj go przy zdarzeniach. Widget, który czeka na zdarzenie, jest pusty, dopóki coś się nie wydarzy.
- Zapisane widgety pokazują przyciski testowe tylko dla nazw zdarzeń, które występują w Twoim JavaScripcie. Wpisz w kodzie pełną nazwę zdarzenia (np. `'followEvent'`), żeby streamerzy mogli przetestować Twój widget.
- Zostaw przezroczyste tło, chyba że tło jest częścią projektu. Dzięki przezroczystemu tłu widget leży na wierzchu sceny.
- Widgetami z kreatora możesz też zarządzać przez API KickBot. Zobacz [dokumentację API widgetów](https://kickbot.com/docs/api/reference/widgets).
