Memulai
Tab HTML & CSS menggambar widget kamu. Kotaknya transparan dan melayang di atas video — jangan pernah mewarnai latar penuh yang tidak transparan.
Di tab JS, subscribe dengan TE.on('gift', fn). window.TE sudah dimuat — tanpa import, tanpa setup.
Pakai Simulate atau tombol Fire untuk pratinjau offline, lalu Add to overlay — atau biarkan Claude membuatnya lewat MCP.
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');
});Hubungkan Claude (MCP)
Bikin dan edit widget ini langsung dari Claude Desktop atau Claude Code. Buat token, tambahkan connector, dan Claude bisa memanggil tool berikut di akun kamu:
get_docsPanduan pembuatan lengkap + setiap trigger dan aksi (Claude membaca ini lebih dulu).list_widgetsDaftar widget tersimpan beserta id-nya.get_widgetAmbil html / css / js dari satu widget.create_widgetBuat widget HTML baru dari nama + html/css/js.update_widgetEdit widget yang sudah ada.delete_widgetHapus widget berdasarkan id.list_templatesDaftar template bawaan untuk disalin.get_templateBaca html/css/js dari sebuah template.create_from_templateSalin template menjadi widget baru.list_community_templatesJelajahi widget yang dipublikasikan komunitas.install_community_templateInstal fork privat yang bisa diedit dari template komunitas.SDK TE
Semua yang dilakukan widget lewat objek global window.TE.
TE.on(type, fn)Subscribe ke event live. `type` adalah trigger mana pun di bawah; `fn(ev)` dijalankan setiap kali event terjadi.
TE.on('*', fn)Tangkap semua event. `fn(ev, type)` menerima payload dan nama event.
TE.off(type, fn)Hapus handler yang sebelumnya didaftarkan.
TE.onGift(name, fn)Hanya jalan untuk gift yang namanya cocok (substring, tidak peka huruf besar/kecil). Kosongkan `name` untuk menangkap semua gift.
TE.onSticker(idOrUrl, fn)Hanya jalan untuk satu emote / stiker subscriber tertentu — dicocokkan lewat id TikTok, URL gambar, atau sumbernya.
TE.rules.allowUser / allowGiftFilter kelayakan yang bisa dipakai ulang untuk role, daftar izin/tolak, nama gift, dan nilai koin minimum.
TE.metricsObjek snapshot stream terbaru (viewers, likes, coins, followers, topGifters…). Juga dikirim lewat TE.on('metrics', fn).
TE.demoTrue di tempat widget dipamerkan (galeri, tile pratinjau, tombol Demo di builder). False di overlay live dan saat kamu tes di builder atau editor overlay, di mana hanya event yang kamu kirim atau yang masuk secara live yang muncul. Setiap widget menampilkan dirinya di sana: taruh contoh yang terlihat seperti event asli (gambar gift asli, nama, avatar) di dalam blok if (TE.demo), lewat jalur kode asli widget. Widget dengan blok seperti itu tidak mendapat event pratinjau dari host, jadi tidak ada yang jalan dua kali.
TE.defineSettings([...])Deklarasikan kontrol yang bisa diatur streamer; mengembalikan nilai saat ini (default digabung dengan pilihan streamer).
TE.settingsObjek nilai pengaturan saat ini (bentuknya sama dengan yang dikembalikan defineSettings).
TE.on('settings', fn)Jalan saat streamer mengubah pengaturan secara live — render ulang dengan nilai baru.
TE.state.get/set/incrementNilai atomik berbasis Promise untuk total dan state game bersama.
TE.collection.*Tabel peserta join-sekali dan skor yang dikelola server: join, increment, list, count, remove, dan clear.
TE.queue.*Antrean FIFO terbatas yang dikelola server untuk media, request, dan aksi yang dipicu penonton.
TE.shared.*Masukkan beberapa widget ke satu namespace state channel bernama; widget yang tidak terkait tetap terisolasi.
TE.random.draw(name, opts)Undi satu atau lebih pemenang dari sebuah koleksi secara aman dan catat hasilnya.
TE.cooldown.claim(scope, user, ms)Klaim cooldown global atau per pengguna secara atomik; mengembalikan claimed dan retryAfterMs.
TE.timer.*Mulai, jeda, reset, dan baca timer jam dinding tersimpan yang tetap bertahan saat reload.
TE.points.trySpend(user, amount, reason)Cek dan potong poin dalam satu transaksi; selalu periksa result.ok sebelum menjalankan interaksi berbayar.
Trigger
Event live yang bisa kamu dengarkan. Setiap user berisi { id, name, username, avatar, roles }.
TE.on('gift')Penonton mengirim gift. coins = total untuk combo; streakEnd menandai combo selesai.
{ user, gift: { name, image|null, coins, repeat, combo, streakEnd } }TE.on('follow')Penonton mem-follow akun.
{ user }TE.on('subscribe')Penonton berlangganan. months = berapa bulan berturut-turut.
{ user, months }TE.on('share')Penonton membagikan live.
{ user }TE.on('chat')Pesan chat. emotes = URL gambar emote subscriber di dalam pesan ini.
{ user, comment, emotes:[url,…] }TE.on('like')Penonton mengirim like. count = burst ini; total = total berjalan.
{ user, count, total }TE.on('join')Penonton masuk ke room. isTop = seorang top gifter bergabung.
{ user, isTop }TE.on('sticker')Emote subscriber atau stiker di layar yang dikirim secara live. url = gambar emote; id = id TikTok-nya.
{ user|null, id|null, url, source }TE.on('metrics')Snapshot stream terbaru. TE.metrics menyimpannya; event ini jalan setiap kali diperbarui. hostNick dan hostAvatar adalah nama dan foto profil streamer sendiri.
{ live, viewers, likes, followers, coins, hostNick, hostAvatar, topGifters:[…], … }TE.on('milestone')Milestone angka bulat terlewati (likes, coins, followers, subs).
{ metric, value, label }TE.on('poll')Polling live bawaan TikTok — vote asli, dari awal sampai akhir.
{ state:'start'|'update'|'end', title, options:[{text,votes}], endsAt }TE.on('battle')Update battle LinkMic: kartu battle, fan ticket, ukuran army.
{ card|null, tickets|null, armies|null, battleId|null }TE.on('rank')Posisi ranking per jam dan momen naik peringkat.
{ rank|null, from|null, to|null, countdown|null }TE.on('envelope')Amplop merah / treasure box dijatuhkan di live.
{ }TE.on('pinned')Host menyematkan komentar.
{ text }TE.on('deleted')Moderator menghapus pesan chat (id cocok dengan event chat sebelumnya).
{ id }TE.on('streamState')Stream mulai live atau offline.
{ live }TE.on('apiEvent')Event custom yang dikirim lewat Event API. Dengarkan apiEvent atau langsung nama custom-nya.
{ name, data }TE.on('nowPlaying')Lagu Spotify yang sedang diputar streamer PLUS antrean berikutnya. Jalan saat terhubung dan setiap kali lagu, status putar, progres, atau antrean berubah. playback bernilai null saat tidak ada yang diputar; queue adalah lagu-lagu berikutnya.
{ 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) { /* … */ });Emote channel kamu ada di widget builder: buka Simulate events, pilih Sticker, lalu pilih salah satu. Event tes membawa id dan URL gambar aslinya, jadi handler yang memakai salah satunya dites dengan data asli.
Aksi
Apa yang bisa dilakukan widget kamu sebagai respons — API browser biasa, siap ditempel.
Munculkan gambar di layar — artwork gift/emote, aset yang diupload, atau URL apa pun. Hilang otomatis setelah beberapa detik.
// 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
});Putar audio saat ada event. Sediakan pengaturan suara supaya streamer memilih file sendiri — tanpa edit kode.
// 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(); }
});Tulis teks dinamis dan picu ulang animasi CSS dengan mengganti class.
// 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');
});Hubungkan angka di layar ke metrik live — viewers, likes, coins, followers — diperbarui otomatis.
// Keep a number in sync with the live stream
TE.on('metrics', function (m) {
document.getElementById('count').textContent = m.viewers.toLocaleString();
});Ucapkan event dengan text-to-speech browser. Cocok untuk shout-out gift atau follow.
// Text-to-speech shout-out
TE.on('gift', function (ev) {
var u = new SpeechSynthesisUtterance(ev.user.name + ' sent ' + ev.gift.name);
speechSynthesis.speak(u);
});Setiap widget menampilkan dirinya di galeri dan builder: contoh yang terlihat seperti event asli, dengan artwork gift, nama, dan avatar asli. Saat live, TE.demo bernilai false dan hanya event asli yang muncul.
// 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);
}Penyimpanan sinkron sederhana untuk state visual. Pakai runtime transaksional untuk peserta, pembelian, dan skor bersama.
// 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();
});Perintah chat yang cocok persis plus koleksi atomik memungkinkan entri, vote, dan spin yang aman.
// 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);
});
});Minta alert overlay, suara, TTS, penghitung, waktu subathon, atau ganti scene OBS langsung dari widget.
// One call, one stream action
TE.act({ id: 'tts', message: 'New high score!' });
TE.act({ id: 'points', user: ev.user, amount: 50 });Cek, pakai, atau beri poin loyalitas secara transaksional dan terima saldo hasilnya.
// 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);
});
});State atomik, koleksi, undian acak yang aman, cooldown, dan timer persisten — aman meski ada browser source ganda.
// 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);
});Sediakan teks, warna, angka, toggle — streamer mengaturnya secara live di editor overlay, tanpa kode.
// 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 */ });Pengaturan streamer
Deklarasikan kontrol dengan TE.defineSettings([...]) dan siapa pun yang memakai widget bisa mengaturnya secara live di editor overlay — tanpa kode. Setiap field:
Widget yang terpasang dengan satu atau lebih field 'button' menampilkan aksi tersebut di pengaturannya di dalam Overlay Editor. Widget Builder menampilkan tombol yang sama untuk tes pratinjau yang aman saat coding.
'text'Input teks satu barisstring'number'Input angkanumber'color'Pemilih warnastring hex, mis. "#FF2E4D"'select'Dropdown (butuh options: [...])salah satu opsi'toggle'Saklar on/offboolean'range'Slider (min / max / step)number'button'Tombol aksi deklaratifincrement, set, atau toggle — JSON tervalidasi, tidak pernah kode dashboard'sound'Pemilih dari suara yang diupload streamer + UploadURL file yang dipilih ("" = tidak ada)'image'Pemilih dari gambar yang diupload streamer + UploadURL file yang dipilih ("" = tidak ada)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();
});Definisi kontrol adalah data yang divalidasi. Tombol action hanya menerima increment, set, atau toggle. HTML, kode callback, dan URL sembarang ditolak atau diabaikan oleh renderer dashboard.
Sandbox & batasan
Widget berjalan di iframe terisolasi dengan content-security policy yang ketat, jadi widget yang rusak atau berbahaya tidak akan pernah bisa menyentuh akun kamu atau bagian lain halaman.
- Gambar & GIF remote (artwork gift TikTok, avatar, URL apa pun)
- Google Fonts + @font-face milikmu sendiri
- Audio lewat new Audio(url)
- Request ke API milik TokElements (mis. /api/files/…)
- Animasi CSS, SVG, canvas, Web Audio
- Tag <script> eksternal / CDN
- fetch / XHR / WebSocket ke host lain
- Cookie, localStorage, akses ke halaman induk
- Memuat paket npm
Contoh
Widget lengkap, tinggal salin-tempel. Setiap pratinjau live — event demo dijalankan supaya kamu bisa melihatnya bereaksi.
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;
});