TokElementsDOCS
WIDGET SDK / V1
01

เริ่มต้นใช้งาน

1
เขียนมาร์กอัปและสไตล์

แท็บ HTML และ CSS ใช้วาดวิดเจ็ตของคุณ กล่องวิดเจ็ตโปร่งใสและลอยอยู่เหนือวิดีโอ อย่าลงพื้นหลังทึบเต็มกล่อง

2
ตอบสนองต่ออีเวนต์

ในแท็บ JS ให้ subscribe ด้วย TE.on('gift', fn) window.TE โหลดไว้ให้แล้ว ไม่ต้อง import ไม่ต้องตั้งค่า

3
ทดสอบและเพิ่ม

ใช้ Simulate หรือปุ่ม Fire เพื่อพรีวิวแบบออฟไลน์ แล้วกด Add to overlay หรือให้ Claude สร้างให้ผ่าน MCP

วิดเจ็ตแรกของคุณ: ชาวต์เอาต์ของขวัญ· แก้ JS แล้วพรีวิวจะตอบสนองสดๆ
var box = document.getElementById('box');
TE.on('gift', function (ev) {
  box.textContent = ev.user.name + ' sent ' + ev.gift.name + '!';
  box.classList.remove('pop'); void box.offsetWidth; box.classList.add('pop');
});
พรีวิวสด
02

เชื่อม Claude (MCP)

สร้างและแก้ไขวิดเจ็ตเหล่านี้ได้โดยตรงจาก Claude Desktop หรือ Claude Code สร้างโทเคน เพิ่มคอนเนกเตอร์ แล้ว Claude จะเรียกใช้เครื่องมือเหล่านี้กับบัญชีของคุณได้:

get_docsคู่มือการเขียนฉบับเต็ม + ทริกเกอร์และแอ็กชันทั้งหมด (Claude อ่านอันนี้ก่อน)
list_widgetsแสดงรายการวิดเจ็ตที่บันทึกไว้พร้อม id
get_widgetดึง html / css / js ของวิดเจ็ตหนึ่งตัว
create_widgetสร้างวิดเจ็ต HTML ใหม่จากชื่อ + html/css/js
update_widgetแก้ไขวิดเจ็ตที่มีอยู่
delete_widgetลบวิดเจ็ตด้วย id
list_templatesแสดงรายการเทมเพลตในตัวสำหรับคัดลอก
get_templateอ่าน html/css/js ของเทมเพลต
create_from_templateคัดลอกเทมเพลตเป็นวิดเจ็ตใหม่
list_community_templatesดูวิดเจ็ตที่คอมมูนิตี้เผยแพร่
install_community_templateติดตั้งสำเนาส่วนตัวที่แก้ไขได้ของเทมเพลตคอมมูนิตี้
กำลังโหลด…
03

TE SDK

ทุกอย่างที่วิดเจ็ตทำจะผ่านอ็อบเจกต์ส่วนกลาง window.TE

TE.on(type, fn)

Subscribe อีเวนต์สด `type` คือทริกเกอร์ใดก็ได้ด้านล่าง ส่วน `fn(ev)` จะทำงานทุกครั้งที่เกิดอีเวนต์

TE.on('*', fn)

รับทุกอีเวนต์ `fn(ev, type)` จะได้รับ payload และชื่ออีเวนต์

TE.off(type, fn)

ลบ handler ที่ลงทะเบียนไว้ก่อนหน้า

TE.onGift(name, fn)

ทำงานเฉพาะของขวัญที่ชื่อตรงกัน (ค้นหาบางส่วนของข้อความ ไม่สนตัวพิมพ์เล็กใหญ่) ไม่ใส่ `name` เพื่อรับทุกของขวัญ

TE.onSticker(idOrUrl, fn)

ทำงานเฉพาะอีโมตหรือสติกเกอร์ของผู้สมัครสมาชิกตัวใดตัวหนึ่ง จับคู่ด้วย TikTok id, URL รูปภาพ หรือแหล่งที่มา

TE.rules.allowUser / allowGift

ตัวกรองสิทธิ์ที่ใช้ซ้ำได้ สำหรับบทบาท รายการอนุญาต/ปฏิเสธ ชื่อของขวัญ และมูลค่าเหรียญขั้นต่ำ

TE.metrics

อ็อบเจกต์สแนปช็อตล่าสุดของสตรีม (viewers, likes, coins, followers, topGifters…) ส่งผ่าน TE.on('metrics', fn) ด้วย

TE.demo

เป็น true เมื่อวิดเจ็ตถูกนำไปโชว์ (แกลเลอรี ไทล์พรีวิว สวิตช์ Demo ในตัวสร้าง) เป็น false บนโอเวอร์เลย์สด และขณะคุณทดสอบในตัวสร้างหรือตัวแก้ไขโอเวอร์เลย์ ซึ่งจะแสดงเฉพาะสิ่งที่คุณยิงเองหรือสิ่งที่เข้ามาสดเท่านั้น ทุกวิดเจ็ตต้องโชว์ตัวเองได้ที่นั่น: ใส่ตัวอย่างที่ดูเหมือนอีเวนต์จริง (ภาพของขวัญจริง ชื่อ รูปโปรไฟล์) ไว้ในบล็อก if (TE.demo) โดยผ่านโค้ดจริงของวิดเจ็ต วิดเจ็ตที่มีบล็อกแบบนี้จะไม่ได้รับอีเวนต์พรีวิวจากโฮสต์ จึงไม่มีอะไรทำงานซ้ำสองครั้ง

TE.defineSettings([...])

ประกาศตัวควบคุมที่สตรีมเมอร์ปรับได้ คืนค่าปัจจุบัน (ค่าเริ่มต้นรวมกับสิ่งที่สตรีมเมอร์เลือก)

TE.settings

อ็อบเจกต์ค่าการตั้งค่าปัจจุบัน (รูปแบบเดียวกับที่ defineSettings คืนมา)

TE.on('settings', fn)

ทำงานเมื่อสตรีมเมอร์เปลี่ยนการตั้งค่าสดๆ ให้เรนเดอร์ใหม่ด้วยค่าใหม่

TE.state.get/set/increment

ค่าแบบ atomic ที่ใช้ Promise สำหรับยอดรวมและสถานะเกมที่ใช้ร่วมกัน

TE.collection.*

ตารางผู้เข้าร่วมแบบเข้าร่วมได้ครั้งเดียวและตารางคะแนนที่เซิร์ฟเวอร์ดูแล: join, increment, list, count, remove และ clear

TE.queue.*

คิว FIFO แบบจำกัดขนาดที่เซิร์ฟเวอร์ดูแล สำหรับมีเดีย คำขอ และแอ็กชันที่ผู้ชมเรียกใช้

TE.shared.*

ให้วิดเจ็ตหลายตัวใช้ namespace สถานะช่องที่ตั้งชื่อร่วมกัน วิดเจ็ตที่ไม่เกี่ยวข้องยังแยกจากกัน

TE.random.draw(name, opts)

สุ่มผู้ชนะหนึ่งคนหรือมากกว่าจากคอลเลกชันอย่างปลอดภัย และบันทึกผลไว้

TE.cooldown.claim(scope, user, ms)

จอง cooldown แบบ global หรือรายผู้ใช้แบบ atomic คืนค่า claimed และ retryAfterMs

TE.timer.*

เริ่ม หยุดชั่วคราว รีเซ็ต และอ่านตัวจับเวลาตามเวลาจริงที่บันทึกไว้และอยู่รอดแม้รีโหลด

TE.points.trySpend(user, amount, reason)

ตรวจและหักแต้มในธุรกรรมเดียว ตรวจ result.ok ทุกครั้งก่อนรันการโต้ตอบที่ต้องใช้แต้ม

04

ทริกเกอร์

อีเวนต์สดที่คุณรับฟังได้ ทุก user มี { id, name, username, avatar, roles }

TE.on('gift')

ผู้ชมส่งของขวัญ coins = ยอดรวมของคอมโบ ส่วน streakEnd บอกว่าคอมโบจบแล้ว

{ user, gift: { name, image|null, coins, repeat, combo, streakEnd } }
TE.on('follow')

ผู้ชมกดติดตามบัญชี

{ user }
TE.on('subscribe')

ผู้ชมสมัครสมาชิก months = จำนวนเดือนติดต่อกัน

{ user, months }
TE.on('share')

ผู้ชมแชร์ไลฟ์

{ user }
TE.on('chat')

ข้อความแชต emotes = URL รูปอีโมตผู้สมัครสมาชิกในข้อความนี้

{ user, comment, emotes:[url,…] }
TE.on('like')

ผู้ชมส่งไลก์ count = ไลก์ชุดนี้ ส่วน total = ยอดรวมสะสม

{ user, count, total }
TE.on('join')

ผู้ชมเข้าห้อง isTop = ผู้ส่งของขวัญสูงสุดเข้ามา

{ user, isTop }
TE.on('sticker')

อีโมตผู้สมัครสมาชิกหรือสติกเกอร์บนจอที่ส่งระหว่างไลฟ์ url = รูปอีโมต ส่วน id = TikTok id ของมัน

{ user|null, id|null, url, source }
TE.on('metrics')

สแนปช็อตล่าสุดของสตรีม เก็บไว้ใน TE.metrics และอีเวนต์จะทำงานทุกครั้งที่อัปเดต hostNick และ hostAvatar คือชื่อและรูปโปรไฟล์ของสตรีมเมอร์เอง

{ live, viewers, likes, followers, coins, hostNick, hostAvatar, topGifters:[…], … }
TE.on('milestone')

ข้ามหลักตัวเลขกลมๆ (ไลก์ เหรียญ ผู้ติดตาม สมาชิก)

{ metric, value, label }
TE.on('poll')

โพลไลฟ์ของ TikTok เอง โหวตจริง ตั้งแต่ต้นจนจบ

{ state:'start'|'update'|'end', title, options:[{text,votes}], endsAt }
TE.on('battle')

อัปเดตการแบตเทิล LinkMic: การ์ดแบตเทิล ตั๋วแฟน ขนาดกองทัพ

{ card|null, tickets|null, armies|null, battleId|null }
TE.on('rank')

อันดับในการจัดอันดับรายชั่วโมงและช่วงเวลาที่อันดับขึ้น

{ rank|null, from|null, to|null, countdown|null }
TE.on('envelope')

ซองแดงหรือกล่องสมบัติถูกปล่อยในไลฟ์

{ }
TE.on('pinned')

โฮสต์ปักหมุดคอมเมนต์

{ text }
TE.on('deleted')

ผู้ดูแลลบข้อความแชต (id ตรงกับอีเวนต์แชตก่อนหน้า)

{ id }
TE.on('streamState')

สตรีมเริ่มไลฟ์หรือออฟไลน์

{ live }
TE.on('apiEvent')

อีเวนต์แบบกำหนดเองที่ส่งผ่าน Event API รับฟังด้วย apiEvent หรือด้วยชื่อที่กำหนดเองโดยตรง

{ name, data }
TE.on('nowPlaying')

เพลง Spotify ที่สตรีมเมอร์กำลังเล่น พร้อมคิวเพลงถัดไป ทำงานเมื่อเชื่อมต่อ และทุกครั้งที่เพลง สถานะการเล่น ความคืบหน้า หรือคิวเปลี่ยน playback เป็น null เมื่อไม่มีเพลงเล่น ส่วน queue คือเพลงที่กำลังจะเล่น

{ connected, playback: { isPlaying, progressMs, durationMs, title, artists:[…], album, artwork|null } | null, queue:[{ title, artist, artwork|null, durationMs }] }
เฉพาะของขวัญหรืออีโมตตัวใดตัวหนึ่ง
TE.onGift('Rose', function (ev) { /* … */ });
TE.onSticker('<sticker id>', function (ev) { /* … */ });

อีโมตของช่องคุณอยู่ในตัวสร้างวิดเจ็ต: เปิด Simulate events เลือก Sticker แล้วเลือกหนึ่งตัว อีเวนต์ทดสอบจะมี id และ URL รูปภาพจริง handler ที่จับคู่ด้วยค่าใดค่าหนึ่งจึงได้ทดสอบกับของจริง

05

แอ็กชัน

สิ่งที่วิดเจ็ตของคุณทำตอบสนองได้ ใช้ API ของเบราว์เซอร์ธรรมดา พร้อมวางใช้

แสดงรูปภาพ

เด้งรูปขึ้นจอ ไม่ว่าจะเป็นภาพของขวัญ/อีโมต ไฟล์ที่อัปโหลด หรือ URL ใดก็ได้ และหายไปเองหลังไม่กี่วินาที

// Show an image, then fade it out
TE.on('gift', function (ev) {
  var img = document.createElement('img');
  img.src = ev.gift.image;                 // ← any image URL works
  img.style.cssText = 'position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);max-width:60%';
  document.body.appendChild(img);
  setTimeout(function () { img.remove(); }, 4000);   // ← how long it stays
});
เล่นเสียง

เล่นเสียงเมื่อเกิดอีเวนต์ เปิดการตั้งค่าเสียงไว้ให้สตรีมเมอร์เลือกไฟล์เองได้ โดยไม่ต้องแก้โค้ด

// Play a sound the streamer chose in settings
var s = TE.defineSettings([{ key: 'sfx', label: 'Alert sound', type: 'sound', default: '' }]);
TE.on('settings', function (ns) { s = ns; });
TE.on('gift', function (ev) {
  if (s.sfx) { var a = new Audio(s.sfx); a.volume = 0.8; a.play(); }
});
แสดง / ทำแอนิเมชันข้อความ

เขียนข้อความแบบไดนามิกและเล่นแอนิเมชัน CSS ซ้ำด้วยการสลับคลาส

// Announce the event with a CSS pop animation
var box = document.getElementById('box');   // your element in the HTML tab
TE.on('follow', function (ev) {
  box.textContent = ev.user.name + ' followed!';
  box.classList.remove('pop'); void box.offsetWidth; box.classList.add('pop');
});
แสดงสถิติสด

ผูกตัวเลขบนจอกับค่าสด เช่น ผู้ชม ไลก์ เหรียญ ผู้ติดตาม อัปเดตอัตโนมัติ

// Keep a number in sync with the live stream
TE.on('metrics', function (m) {
  document.getElementById('count').textContent = m.viewers.toLocaleString();
});
อ่านออกเสียง (TTS)

อ่านอีเวนต์ออกเสียงด้วยระบบแปลงข้อความเป็นเสียงของเบราว์เซอร์ เหมาะสำหรับชาวต์เอาต์ของขวัญหรือผู้ติดตาม

// Text-to-speech shout-out
TE.on('gift', function (ev) {
  var u = new SpeechSynthesisUtterance(ev.user.name + ' sent ' + ev.gift.name);
  speechSynthesis.speak(u);
});
พรีวิว (TE.demo)

ทุกวิดเจ็ตโชว์ตัวเองในแกลเลอรีและตัวสร้าง: ตัวอย่างที่ดูเหมือนอีเวนต์จริง พร้อมภาพของขวัญ ชื่อ และรูปโปรไฟล์จริง ตอนไลฟ์ TE.demo เป็น false และจะแสดงเฉพาะอีเวนต์จริง

// Preview samples through the real code path. Never on a live overlay.
if (TE.demo) {
  var GIFTS = [
    { name: 'Rose', coins: 1, image: 'https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/eba3a9bb85c33e017f3648eaf88d7189~tplv-obj.png' },
    { name: 'Perfume', coins: 20, image: 'https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/20b8f61246c7b6032777bb81bf4ee055~tplv-obj.png' }
  ];
  var NAMES = ['lunaa', 'nightowl_gaming_official'], n = 0;
  function sample() {
    n++;
    onGift({ user: { name: NAMES[n % 2], avatar: window.__TE_AVATAR }, gift: GIFTS[n % 2] });
  }
  sample();
  setInterval(sample, 6000);
}
จดจำข้อมูล (TE.store)

การเก็บข้อมูลแบบ synchronous อย่างง่ายสำหรับสถานะที่แสดงผล ใช้รันไทม์แบบธุรกรรมสำหรับผู้เข้าร่วม การซื้อ และคะแนนที่ใช้ร่วมกัน

// Persistent state: survives OBS reloads
var total = TE.store.get('total', 0);          // read (with default)
TE.on('gift', function (ev) {
  total += ev.gift.coins;
  TE.store.set('total', total);                 // write (auto-saved)
  render();
});
ตอบสนองต่อคำสั่งในแชต

คำสั่งแชตแบบตรงทุกตัวอักษรร่วมกับคอลเลกชันแบบ atomic ทำให้การลงชื่อ โหวต และหมุนวงล้อปลอดภัย

// One server-authoritative entry per viewer.
// A function keyword reads s.command on every message, so the
// streamer can change the command in settings without a reload.
TE.onCommand(function () { return s.command || '!join'; }, function (c) {
  TE.collection.join('entrants', c.user.id, c.user).then(function (result) {
    if (result.joined) renderCount(result.count);
  });
});
สั่งแอ็กชันของสตรีม (TE.act)

ขอแจ้งเตือนโอเวอร์เลย์ เสียง TTS ตัวนับ เวลา Subathon หรือสลับฉาก OBS ได้โดยตรงจากวิดเจ็ต

// One call, one stream action
TE.act({ id: 'tts', message: 'New high score!' });
TE.act({ id: 'points', user: ev.user, amount: 50 });
ให้แต้มผู้ชม

ตรวจ ใช้ หรือให้แต้มสะสมแบบธุรกรรม และรับยอดคงเหลือที่ได้

// Only spin after the points were really debited
TE.onCommand(function () { return s.command || '!spin'; }, function (ev) {
  TE.points.trySpend(ev.user, 50, 'Wheel spin').then(function (result) {
    if (result.ok) spin();
    else showMissing(result.missing);
  });
});
สร้างเกมที่ยุติธรรม

สถานะแบบ atomic คอลเลกชัน การสุ่มที่ปลอดภัย cooldown และตัวจับเวลาที่คงอยู่ ปลอดภัยแม้มี browser source ซ้ำกัน

// Fair draw from a server-owned entrant collection
TE.random.draw('entrants', { count: 1 }).then(function (result) {
  if (result.ok) reveal(result.winners[0].value);
});
เพิ่มตัวควบคุมสำหรับสตรีมเมอร์

เปิดให้ปรับข้อความ สี ตัวเลข สวิตช์ สตรีมเมอร์ปรับได้สดในตัวแก้ไขโอเวอร์เลย์ ไม่ต้องใช้โค้ด

// No-code controls for whoever uses the widget
var s = TE.defineSettings([
  { key: 'title',  label: 'Title', type: 'text',  default: 'Goal' },
  { key: 'accent', label: 'Color', type: 'color', default: '#FF2E4D' },
]);
document.body.style.setProperty('--accent', s.accent);
TE.on('settings', function (ns) { s = ns; /* re-render with new values */ });
06

การตั้งค่าสตรีมเมอร์

ประกาศตัวควบคุมด้วย TE.defineSettings([...]) แล้วใครก็ตามที่ใช้วิดเจ็ตจะปรับได้สดในตัวแก้ไขโอเวอร์เลย์ ไม่ต้องใช้โค้ด แต่ละฟิลด์:

วิดเจ็ตที่วางแล้วซึ่งมีฟิลด์ 'button' หนึ่งตัวขึ้นไป จะแสดงแอ็กชันเหล่านั้นในการตั้งค่าภายใน Overlay Editor ส่วน Widget Builder จะแสดงปุ่มเดียวกันไว้ทดสอบพรีวิวอย่างปลอดภัยระหว่างเขียนโค้ด

ประเภทแสดงเป็นค่า
'text'ช่องกรอกข้อความบรรทัดเดียวstring
'number'ช่องกรอกตัวเลขnumber
'color'ตัวเลือกสีสตริง hex เช่น "#FF2E4D"
'select'ดรอปดาวน์ (ต้องมี options: [...])หนึ่งในตัวเลือก
'toggle'สวิตช์เปิด/ปิดboolean
'range'แถบเลื่อน (min / max / step)number
'button'ปุ่มแอ็กชันแบบประกาศincrement, set หรือ toggle — JSON ที่ผ่านการตรวจสอบ ไม่ใช่โค้ดของแดชบอร์ด
'sound'ตัวเลือกจากเสียงที่สตรีมเมอร์อัปโหลด + อัปโหลดURL ของไฟล์ที่เลือก ("" = ไม่มี)
'image'ตัวเลือกจากรูปที่สตรีมเมอร์อัปโหลด + อัปโหลดURL ของไฟล์ที่เลือก ("" = ไม่มี)
var s = TE.defineSettings([
  { key: 'title',  label: 'Title', type: 'text',   default: 'Follower goal' },
  { key: 'target', label: 'Goal',  type: 'number', default: 100 },
  { key: 'accent', label: 'Color', type: 'color',  default: '#FF2E4D' },
  { key: 'reset',  label: 'Reset', type: 'button', default: 0,
    action: { type: 'increment', step: 1 }, confirm: true, tone: 'danger' },
]);
// re-render when the streamer changes something live
var lastReset = s.reset;
TE.on('settings', function (ns) {
  s = ns;
  if (ns.reset !== lastReset) { lastReset = ns.reset; resetCounter(); }
  render();
});

คำนิยามตัวควบคุมเป็นข้อมูลที่ผ่านการตรวจสอบ action ของปุ่มรับเฉพาะ increment, set หรือ toggle เท่านั้น HTML โค้ด callback และ URL ใดๆ จะถูกปฏิเสธหรือเพิกเฉยโดยตัวเรนเดอร์ของแดชบอร์ด

07

แซนด์บ็อกซ์และข้อจำกัด

วิดเจ็ตรันใน iframe ที่แยกออกมาพร้อม content-security policy ที่เข้มงวด วิดเจ็ตที่พังหรือเป็นอันตรายจึงแตะต้องบัญชีของคุณหรือส่วนอื่นของหน้าไม่ได้เลย

อนุญาต
  • รูปภาพและ GIF จากภายนอก (ภาพของขวัญ TikTok รูปโปรไฟล์ URL ใดก็ได้)
  • Google Fonts + @font-face ของคุณเอง
  • เสียงผ่าน new Audio(url)
  • คำขอไปยัง API ของ TokElements เอง (เช่น /api/files/…)
  • แอนิเมชัน CSS, SVG, canvas, Web Audio
บล็อก
  • แท็ก <script> ภายนอก / CDN
  • fetch / XHR / WebSocket ไปยังโฮสต์อื่น
  • คุกกี้, localStorage, การเข้าถึงหน้าแม่
  • การโหลดแพ็กเกจ npm
08

ตัวอย่าง

วิดเจ็ตครบชุด คัดลอกไปวางได้เลย ทุกพรีวิวทำงานสด อีเวนต์เดโมจะยิงให้คุณดูการตอบสนอง

ตอบสนองต่ออีโมต· เด้งอีโมตขึ้นเมื่อถูกส่งในแชต
var img = document.getElementById('emote');
// fires for every emote — use TE.onSticker('<id>', …) for one specific one
TE.on('sticker', function (ev) {
  img.src = ev.url;
  img.classList.remove('show'); void img.offsetWidth; img.classList.add('show');
});
พรีวิวสด
แถบเป้าหมายผู้ติดตาม· ผูกแถบกับค่าสด
var target = 200;                       // ← your goal
TE.on('metrics', function (m) {
  var pct = Math.min(100, m.follows / target * 100);
  document.getElementById('fill').style.width = pct + '%';
  document.getElementById('txt').textContent = m.follows + ' / ' + target;
});
พรีวิวสด