# ข้อมูลอ้างอิงตัวสร้างวิดเจ็ต (โค้ด)

> ข้อมูลอ้างอิงโค้ดของตัวสร้างวิดเจ็ต KickBot: ชื่อและ payload ของอีเวนต์ Kick ทุกตัว globalValues, userVariables ตัวช่วยแชท และวิธีที่ตัวอย่างรันโค้ด

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

หน้านี้รวมทุกอย่างที่โค้ดของคุณใช้ได้ในวิดเจ็ตจาก[ตัวสร้างวิดเจ็ต](https://kickbot.com/th/docs/widgets/widget-builder)ของ KickBot: อีเวนต์ `widgetEvent` อ็อบเจกต์ `globalValues` อ็อบเจกต์ `userVariables` และตัวช่วยใน `window.kickbotWidget` วิดเจ็ตทุกตัวเป็น HTML, CSS และ JavaScript ธรรมดาที่รันเป็น browser source ใน OBS KickBot เชื่อมต่อกับ Kick ให้ และส่งอีเวนต์เข้าโค้ดของคุณ

## วิดเจ็ตทำงานยังไง

เมื่อ OBS เปิด URL ของวิดเจ็ต KickBot จะ:

1. โหลดหน้าวิดเจ็ตด้วยพื้นหลังโปร่งใส
2. ใส่ CSS ของคุณในอิลิเมนต์ `<style>` และใส่ HTML ของคุณลงในหน้า
3. โหลดข้อมูลช่องของคุณจาก Kick (จำนวนผู้ติดตาม จำนวนผู้ชม สถานะสตรีม และแชทล่าสุด) ลงใน `globalValues`
4. รัน JavaScript ของคุณ โค้ดรันครั้งเดียวหลังขั้นตอนที่ 3 ดังนั้น `globalValues` จะพร้อมตั้งแต่โค้ดเริ่มทำงาน
5. ส่งอีเวนต์สดเข้าโค้ดของคุณเป็นอีเวนต์ `widgetEvent` บน `document`

JavaScript ของคุณอ่านอ็อบเจกต์ 2 ตัวได้โดยไม่ต้อง import อะไร: `globalValues` และ `userVariables` ส่วนตัวช่วยอยู่ใน `window.kickbotWidget`

ขีดจำกัด:

| รายการ | ขีดจำกัด |
| --- | --- |
| ความกว้างและความสูง | ตั้งใน **การตั้งค่าพื้นฐาน** วิดเจ็ตหน้าว่างใหม่มีขนาด 800 × 600 ป๊อปอัปวิดเจ็ตใหม่รับได้สูงสุด 7680 × 4320 |
| HTML, CSS และ JavaScript | สูงสุด 2,000,000 ตัวอักษรต่อไฟล์ |
| ชื่อวิดเจ็ต | สูงสุด 500 ตัวอักษร |
| ไฟล์สำหรับตัวแปรผู้ใช้ | สูงสุด 25 MB ต่อไฟล์ |
| วิดเจ็ตจากตัวสร้างวิดเจ็ตในแพ็กเกจฟรี | 10 (Premium ไม่จำกัด) |

## รับอีเวนต์

เพิ่ม listener 1 ตัวสำหรับ `widgetEvent` บน `document` แต่ละอีเวนต์มี `event.detail` ที่มี 3 ฟิลด์:

| ฟิลด์ | ชนิด | ค่า |
| --- | --- | --- |
| `event_name` | string | ชื่ออีเวนต์ เช่น `followEvent` |
| `source` | string | เป็น `kick` เสมอ |
| `data` | object | payload ของอีเวนต์ (ดูด้านล่าง) |

```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');
    }
});
```

### รายการอีเวนต์

| ชื่ออีเวนต์ | เกิดขึ้นเมื่อ | ปุ่มทดสอบ |
| --- | --- | --- |
| `subEvent` | ผู้ชมสมัครซับ | ซับ |
| `subGiftEvent` | ผู้ชมส่งกิฟต์ซับ | กิฟต์ซับ |
| `kicksEvent` | ผู้ชมส่ง KICKs | KICKs |
| `followEvent` | ผู้ชมกดติดตาม | ติดตาม |
| `unfollowEvent` | ผู้ชมเลิกติดตาม | เลิกติดตาม |
| `chatMessageEvent` | มีคนส่งข้อความแชท | ข้อความ |
| `chatMessageDeletedEvent` | ม็อดลบข้อความ | ลบข้อความ |
| `chatMessagePinnedEvent` | มีการปักหมุดข้อความ | ปักหมุดข้อความ |
| `chatMessageUnpinnedEvent` | ข้อความที่ปักหมุดถูกเอาออก | เลิกปักหมุดข้อความ |
| `chatClearedEvent` | ม็อดล้างแชท | ล้างแชท |
| `followerCountUpdatedEvent` | Kick ส่งจำนวนผู้ติดตามใหม่ | อัปเดตยอดผู้ติดตาม |
| `subCountUpdatedEvent` | จำนวนซับบน Kick เปลี่ยน | อัปเดตยอดซับ |
| `viewerCountUpdatedEvent` | Kick ส่งจำนวนผู้ชมใหม่ | อัปเดตยอดผู้ชม |
| `raidEvent` | ช่องอื่นเรด (โฮสต์) มาที่คุณ | เรด |
| `streamOnline` | สตรีมของคุณเริ่ม | สตรีมออนไลน์ |
| `streamOffline` | สตรีมของคุณจบ | สตรีมออฟไลน์ |

ไม่มีอีเวนต์โดเนท สำหรับแจ้งเตือนโดเนท ให้ใช้[วิดเจ็ตแจ้งเตือนโดเนท](https://kickbot.com/th/docs/tipping/tip-alerts)

### Payload ของอีเวนต์

นี่คือ payload ใน `event.detail.data` ซึ่งตรงกับอีเวนต์ทดสอบในตัวแก้ไข อีเวนต์จริงอาจมีฟิลด์มากกว่านี้ ชื่อ ตัวเลข และ ID ในที่นี้เป็นแค่ตัวอย่าง

`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` และ `unfollowEvent` (รูปแบบเดียวกัน `followers_count` คือจำนวนใหม่)

```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 }]
        }
    }
}
```

อีโมตของ Kick อยู่ใน `content` ในรูปแบบ `[emote:ID:NAME]` ใช้ `createChatMessageFragment` (ดู[แสดงข้อความแชทอย่างปลอดภัย](#show-chat-text-safely)) เพื่อแปลงเป็นรูปภาพ

`chatMessageDeletedEvent`

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

ใช้ `data.message.id` เพื่อหาและเอาข้อความที่คุณแสดงไว้ออก

`chatMessagePinnedEvent` (`data.message` มีรูปแบบเดียวกับ payload ของ `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` เป็นอาร์เรย์ว่าง (`[]`)

`chatClearedEvent`

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

`followerCountUpdatedEvent` (สังเกตว่า `followersCount` เป็นแบบ 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 }
    }
}
```

## ค่า global

`globalValues` เก็บสถานะปัจจุบันของช่องคุณ KickBot เติมค่าให้ก่อนโค้ดของคุณรัน และอัปเดตให้ตลอดเมื่อมีอีเวนต์เข้ามา ใช้ค่านี้วาดวิดเจ็ตตอนเริ่ม ก่อนที่จะมีอีเวนต์ใด ๆ เข้ามา

```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'
    }
}
```

| ค่า | อัปเดตยังไง |
| --- | --- |
| `subCount` | โหลดจาก Kick เพิ่มขึ้น 1 ทุกครั้งที่มี `subEvent` และเพิ่มขึ้น 1 ต่อกิฟต์ซับแต่ละซับ และถูกตั้งค่าใหม่โดย `subCountUpdatedEvent` |
| `followerCount` | โหลดจาก Kick ถูกตั้งค่าโดย `followEvent`, `unfollowEvent` และ `followerCountUpdatedEvent` |
| `viewerCount` | โหลดจาก Kick (เป็น 0 ตอนออฟไลน์) ถูกตั้งค่าโดย `viewerCountUpdatedEvent` |
| `lastFollower`, `lastSub`, `lastGiftedSub` | รายการล่าสุดที่ KickBot เห็นในช่องของคุณ หรือ `null` อัปเดตโดยอีเวนต์ที่ตรงกัน |
| `chatMessages` | แชทล่าสุดจาก Kick ตอนที่วิดเจ็ตโหลด แล้วตามด้วยข้อความใหม่ทุกข้อความ เก็บไว้ 100 ข้อความล่าสุด `chatClearedEvent` จะล้างให้ว่าง ถ้าจะซ่อนข้อความที่ถูกลบ ให้จัดการ `chatMessageDeletedEvent` ในโค้ดของคุณเอง |
| `streamerInfo.kick` | โปรไฟล์ Kick ของคุณ ไม่เปลี่ยนระหว่างที่วิดเจ็ตทำงาน |
| `currentStream` | โหลดจาก Kick ตอนที่วิดเจ็ตโหลด ไม่ถูกอัปเดตโดย `streamOnline` หรือ `streamOffline` ถ้าต้องการสถานะสด ให้รับอีเวนต์เหล่านั้นเอง |

## ตัวแปรผู้ใช้

ตัวแปรผู้ใช้คือการตั้งค่าที่สตรีมเมอร์เห็นเมื่อเปิดวิดเจ็ตของคุณในหน้าวิดเจ็ตที่บันทึกไว้ คุณเพิ่มได้ที่ **ตัวแปรผู้ใช้** ในตัวแก้ไข ในโค้ด ให้อ่านค่าด้วย `userVariables.<variable name>.value`

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

ฟิลด์ของตัวแปร:

| ฟิลด์ | คืออะไร |
| --- | --- |
| ชื่อที่แสดง | ชื่อที่สตรีมเมอร์เห็น |
| ชื่อตัวแปร | ชื่อที่ใช้ในโค้ด ใช้ได้เฉพาะตัวอักษร ตัวเลข และ `_` เปลี่ยนไม่ได้หลังสร้างตัวแปรแล้ว |
| คำอธิบายเพิ่มเติม | ข้อความช่วยเหลือใต้ชื่อที่แสดง |
| ประเภทตัวแปร | ดูตารางด้านล่าง |
| ค่าเริ่มต้น | ค่าก่อนที่สตรีมเมอร์จะเปลี่ยน เว้นว่างได้ |
| จำเป็น | สตรีมเมอร์ต้องใส่ค่าก่อนจึงจะบันทึกได้ |

| ประเภท | สิ่งที่สตรีมเมอร์เห็น | `value` ในโค้ด |
| --- | --- | --- |
| ข้อความ | ช่องข้อความ | string |
| ตัวเลข | ช่องตัวเลข | ตัวเลขหรือ string ที่เป็นตัวเลข ใช้ `Number()` เพื่อความชัวร์ |
| สี | ตัวเลือกสี | string สีแบบ hex เช่น `#ffffff` |
| ดรอปดาวน์ | เมนูที่มีตัวเลือกของคุณ | ตัวเลือกที่เลือกเป็น string ค่าเริ่มต้นคือตัวเลือกแรก |
| สวิตช์เปิด/ปิด | สวิตช์เปิดปิด | `true` หรือ `false` |
| รูปภาพ/GIF | ช่อง URL และปุ่มอัปโหลด | URL ของไฟล์ |
| วิดีโอ | ช่อง URL และปุ่มอัปโหลด | URL ของไฟล์ |
| เสียง | ช่อง URL และปุ่มอัปโหลด | URL ของไฟล์ |

ไฟล์ที่อัปโหลดจะเก็บไว้ที่ KickBot และ `value` คือ URL ของไฟล์ ใช้เมนูข้างตัวแปรเพื่อย้ายขึ้นหรือลง แก้ไข หรือลบ ลำดับในตัวแก้ไขคือลำดับที่สตรีมเมอร์เห็น

## แสดงข้อความแชทอย่างปลอดภัย

ให้ถือว่าทุกฟิลด์ของอีเวนต์และทุกตัวแปรผู้ใช้เป็นข้อความจากคนแปลกหน้า KickBot ทำความสะอาดข้อความแชทและข้อความของอีเวนต์ก่อนส่งให้โค้ดของคุณ จึงปลอดภัยเมื่อใช้เป็นข้อความ HTML แต่ไม่ปลอดภัยเมื่อใช้เป็น URL, CSS หรือสคริปต์ อย่าใส่ฟิลด์ของอีเวนต์ลงใน `innerHTML` ตรง ๆ ให้ใช้ตัวช่วยเหล่านี้:

| ตัวช่วย | ใช้ทำอะไร |
| --- | --- |
| `window.kickbotWidget.createChatMessageFragment(content, font_size)` | คืนค่า `DocumentFragment` ที่มี text node และรูปอีโมตของ Kick อีโมตมีขนาด `font_size + 3` พิกเซล ค่าเริ่มต้นของ `font_size` คือ 20 |
| `window.kickbotWidget.createTextNode(value)` | คืนค่า text node สำหรับค่าใดก็ได้ |
| `window.kickbotWidget.escapeHtmlText(value)` | escape ข้อความเพื่อใช้ระหว่างแท็ก HTML ในกรณีที่จำเป็นต้องสร้าง string HTML เอง อย่าใช้กับ URL, CSS หรือสคริปต์ |

```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);
});
```

สำหรับข้อความอื่น ให้ใช้ `textContent` หรือ `createTextNode`

## ตัวอย่าง (preview)

ตัวอย่างในตัวแก้ไขทำงานเหมือน OBS แต่มีความต่างเล็กน้อยเพื่อใช้ทดสอบ:

- เมื่อคุณเปลี่ยนโค้ดหรือตัวแปรผู้ใช้ ตัวอย่างจะรัน JavaScript ของคุณใหม่ KickBot จะเอา listener ของ `widgetEvent` จากรอบก่อนออกก่อน แต่ตัวจับเวลาจาก `setInterval` และ `setTimeout` จะไม่ถูกเอาออก ถ้ามันซ้อนกันเยอะ ให้คลิกปุ่มรีโหลด
- เมื่อคุณเปลี่ยนความกว้างหรือความสูง ตัวอย่างจะรีโหลด
- อีเวนต์ทดสอบส่งไปที่ตัวอย่างเท่านั้น **ติดตาม** ที่เป็นอีเวนต์ทดสอบจะบวก 1 ให้ `globalValues.followerCount` และ **เลิกติดตาม** จะลบ 1 **อัปเดตยอดผู้ชม** ส่งตัวเลขตัวอย่าง **ลบข้อความ** จะเอาข้อความแบบสุ่มออกจากแชทในตัวอย่าง
- ถ้าช่องของคุณยังไม่มีผู้ติดตาม ซับ หรือกิฟต์ซับล่าสุด ตัวอย่างจะใช้ข้อมูลตัวอย่างสำหรับ `lastFollower`, `lastSub` และ `lastGiftedSub`
- ถ้าจะดูผลลัพธ์ของ `console.log` และข้อผิดพลาด ให้คลิกปุ่มแยกหน้าต่าง แล้วเปิด developer tools ในหน้าต่างนั้น

## ตัวอย่างโค้ด: เป้าหมายผู้ติดตาม

นี่คือเป้าหมายผู้ติดตามแบบสั้น ๆ ใช้ event listener, `globalValues` และตัวแปรผู้ใช้ 2 ตัว: `follower_goal` (ตัวเลข) และ `bar_color` (สี)

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);
```

ถ้าอยากได้เวอร์ชันเต็มที่มีการตั้งค่ามากกว่านี้ ให้สร้างวิดเจ็ตใหม่จากเทมเพลต **Simple Follower Goal**

## เคล็ดลับ

- วาดสถานะแรกจาก `globalValues` แล้วค่อยเปลี่ยนเมื่อมีอีเวนต์ วิดเจ็ตที่รออีเวนต์จะดูว่างเปล่าจนกว่าจะมีอะไรเกิดขึ้น
- หน้าวิดเจ็ตที่บันทึกไว้จะแสดงปุ่มทดสอบเฉพาะชื่ออีเวนต์ที่อยู่ใน JavaScript ของคุณ ให้เขียนชื่ออีเวนต์เต็ม ๆ (เช่น `'followEvent'`) ในโค้ด เพื่อให้สตรีมเมอร์ทดสอบวิดเจ็ตของคุณได้
- ให้พื้นหลังโปร่งใสไว้ เว้นแต่พื้นหลังเป็นส่วนหนึ่งของดีไซน์ วิดเจ็ตจะได้วางทับซีนได้พอดี
- จัดการวิดเจ็ตจากตัวสร้างวิดเจ็ตผ่าน KickBot API ได้ด้วย ดู[ข้อมูลอ้างอิง API ของวิดเจ็ต](https://kickbot.com/docs/api/reference/widgets)
