Primeros pasos
Las pestañas HTML y CSS dibujan tu widget. La caja es transparente y flota sobre el vídeo: nunca pintes un fondo opaco completo.
En la pestaña JS, suscríbete con TE.on('gift', fn). window.TE ya está cargado: sin imports, sin configuración.
Usa Simulate o los botones Fire para previsualizar sin conexión, y luego Add to overlay, o deja que Claude lo cree mediante 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');
});Conectar Claude (MCP)
Crea y edita estos widgets directamente desde Claude Desktop o Claude Code. Genera un token, añade el conector y Claude podrá usar estas herramientas en tu cuenta:
get_docsLa guía completa + todos los triggers y acciones (Claude la lee primero).list_widgetsLista tus widgets guardados con sus ids.get_widgetObtiene el html / css / js de un widget.create_widgetCrea un widget HTML nuevo a partir de nombre + html/css/js.update_widgetEdita un widget existente.delete_widgetBorra un widget por id.list_templatesLista las plantillas integradas para copiar.get_templateLee el html/css/js de una plantilla.create_from_templateCopia una plantilla en un widget nuevo.list_community_templatesExplora los widgets publicados por la comunidad.install_community_templateInstala una copia privada y editable de una plantilla de la comunidad.El SDK TE
Todo lo que hace un widget pasa por el objeto global window.TE.
TE.on(type, fn)Suscríbete a un evento en vivo. `type` es cualquiera de los triggers de abajo; `fn(ev)` se ejecuta cada vez que ocurre.
TE.on('*', fn)Captura todos los eventos. `fn(ev, type)` recibe el payload y el nombre del evento.
TE.off(type, fn)Elimina un handler registrado previamente.
TE.onGift(name, fn)Se activa solo con regalos cuyo nombre coincide (subcadena, sin distinguir mayúsculas). Omite `name` para capturar todos los regalos.
TE.onSticker(idOrUrl, fn)Se activa solo con un emote / sticker de suscriptor concreto, identificado por su id de TikTok, su URL de imagen o su origen.
TE.rules.allowUser / allowGiftFiltros de elegibilidad reutilizables por roles, listas de permitidos/bloqueados, nombre del regalo y valor mínimo en monedas.
TE.metricsEl último objeto de estado del stream (viewers, likes, coins, followers, topGifters…). También llega mediante TE.on('metrics', fn).
TE.demoTrue donde el widget se muestra como escaparate (galería, miniaturas de vista previa, el interruptor Demo del builder). False en un overlay en vivo y mientras pruebas en el builder o en el editor de overlay, donde solo aparece lo que lanzas o lo que llega en vivo. Todo widget se muestra ahí: pon ejemplos que parezcan eventos reales (arte real de regalos, nombres, avatares) en un bloque if (TE.demo), pasando por el código real del widget. Un widget con ese bloque no recibe eventos de vista previa del host, así que nada se dispara dos veces.
TE.defineSettings([...])Declara controles que el streamer puede ajustar; devuelve los valores actuales (los predeterminados combinados con lo que eligió el streamer).
TE.settingsEl objeto con los valores actuales de los ajustes (misma forma que devolvió defineSettings).
TE.on('settings', fn)Se ejecuta cuando el streamer cambia un ajuste en vivo: vuelve a renderizar con los nuevos valores.
TE.state.get/set/incrementValores atómicos basados en promesas para totales y estado de juego compartido.
TE.collection.*Tablas de participantes (una sola inscripción) y puntuaciones gestionadas por el servidor: join, increment, list, count, remove y clear.
TE.queue.*Colas FIFO limitadas gestionadas por el servidor para medios, peticiones y acciones activadas por espectadores.
TE.shared.*Une varios widgets a un espacio de estado de canal con nombre; los widgets no relacionados siguen aislados.
TE.random.draw(name, opts)Sortea de forma segura uno o más ganadores de una colección y registra el resultado.
TE.cooldown.claim(scope, user, ms)Reclama de forma atómica un cooldown global o por usuario; devuelve claimed y retryAfterMs.
TE.timer.*Inicia, pausa, reinicia y lee un temporizador de tiempo real persistente que sobrevive a las recargas.
TE.points.trySpend(user, amount, reason)Comprueba y descuenta puntos en una sola transacción; revisa siempre result.ok antes de ejecutar una interacción de pago.
Triggers
Eventos en vivo que puedes escuchar. Cada user contiene { id, name, username, avatar, roles }.
TE.on('gift')Un espectador envió un regalo. coins = total del combo; streakEnd indica que el combo terminó.
{ user, gift: { name, image|null, coins, repeat, combo, streakEnd } }TE.on('follow')Un espectador siguió la cuenta.
{ user }TE.on('subscribe')Un espectador se suscribió. months = cuántos meses seguidos.
{ user, months }TE.on('share')Un espectador compartió el LIVE.
{ user }TE.on('chat')Un mensaje de chat. emotes = URLs de imagen de los emotes de suscriptor incluidos en este mensaje.
{ user, comment, emotes:[url,…] }TE.on('like')Un espectador envió likes. count = esta ráfaga; total = total acumulado.
{ user, count, total }TE.on('join')Un espectador entró en la sala. isTop = entró un top donador.
{ user, isTop }TE.on('sticker')Un emote de suscriptor o un sticker en pantalla enviado en vivo. url = la imagen del emote; id = su id de TikTok.
{ user|null, id|null, url, source }TE.on('metrics')El último estado del stream. TE.metrics lo contiene; el evento se dispara cada vez que se actualiza. hostNick y hostAvatar son el nombre y la foto de perfil del propio streamer.
{ live, viewers, likes, followers, coins, hostNick, hostAvatar, topGifters:[…], … }TE.on('milestone')Se superó un hito de número redondo (likes, monedas, seguidores, suscriptores).
{ metric, value, label }TE.on('poll')La encuesta nativa de TikTok LIVE: votos reales, de principio a fin.
{ state:'start'|'update'|'end', title, options:[{text,votes}], endsAt }TE.on('battle')Actualizaciones de batallas LinkMic: tarjetas de batalla, fan tickets, tamaño de los ejércitos.
{ card|null, tickets|null, armies|null, battleId|null }TE.on('rank')Posición en el ranking por horas y momentos de subida de puesto.
{ rank|null, from|null, to|null, countdown|null }TE.on('envelope')Cayó un sobre rojo / cofre del tesoro en el LIVE.
{ }TE.on('pinned')El host fijó un comentario.
{ text }TE.on('deleted')Un moderador eliminó un mensaje del chat (el id coincide con el evento de chat anterior).
{ id }TE.on('streamState')El stream empezó o terminó.
{ live }TE.on('apiEvent')Un evento personalizado enviado a través de la Event API. Escucha apiEvent o directamente su nombre personalizado.
{ name, data }TE.on('nowPlaying')La canción actual del streamer en Spotify MÁS la cola de lo que viene. Se dispara al conectar y cada vez que cambian la canción, el estado de reproducción, el progreso o la cola. playback es null cuando no suena nada; queue son las próximas canciones.
{ 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) { /* … */ });Los emotes de tu canal están en el creador de widgets: abre Simulate events, elige Sticker y selecciona uno. El evento de prueba lleva su id y su URL de imagen reales, así que un handler basado en cualquiera de los dos se prueba con el emote real.
Acciones
Lo que tu widget puede hacer como respuesta: APIs normales del navegador, listas para pegar.
Muestra una imagen en pantalla: el arte del regalo/emote, un archivo subido o cualquier URL. Se quita sola a los pocos segundos.
// 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
});Reproduce audio con un evento. Expón un ajuste de sonido para que el streamer elija su propio archivo, sin editar código.
// 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(); }
});Escribe texto dinámico y vuelve a lanzar una animación CSS alternando una clase.
// 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');
});Vincula un número en pantalla a una métrica en vivo (espectadores, likes, monedas, seguidores) que se actualiza sola.
// Keep a number in sync with the live stream
TE.on('metrics', function (m) {
document.getElementById('count').textContent = m.viewers.toLocaleString();
});Lee un evento con el texto a voz del navegador. Ideal para agradecer regalos o follows.
// Text-to-speech shout-out
TE.on('gift', function (ev) {
var u = new SpeechSynthesisUtterance(ev.user.name + ' sent ' + ev.gift.name);
speechSynthesis.speak(u);
});Todo widget se muestra en la galería y en el builder: ejemplos que parecen eventos reales, con arte real de regalos, nombres y avatares. En vivo, TE.demo es false y solo aparecen eventos reales.
// 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);
}Persistencia síncrona sencilla para el estado visual. Usa el runtime transaccional para participantes, compras y puntuaciones compartidas.
// 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();
});Los comandos de chat con coincidencia exacta más las colecciones atómicas permiten inscripciones, votaciones y giros seguros.
// 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);
});
});Pide alertas del overlay, sonidos, TTS, contadores, tiempo de subathon o cambios de escena de OBS directamente desde un widget.
// One call, one stream action
TE.act({ id: 'tts', message: 'New high score!' });
TE.act({ id: 'points', user: ev.user, amount: 50 });Comprueba, gasta o da puntos de fidelidad de forma transaccional y recibe el saldo resultante.
// 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);
});
});Estado atómico, colecciones, sorteos aleatorios seguros, cooldowns y temporizadores persistentes, seguros aunque haya fuentes de navegador duplicadas.
// 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);
});Expón textos, colores, números e interruptores: el streamer los ajusta en vivo en el editor de overlay, sin código.
// 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 */ });Ajustes del streamer
Declara controles con TE.defineSettings([...]) y quien use el widget los ajusta en vivo en el editor de overlay, sin código. Cada campo:
Un widget colocado con uno o más campos 'button' muestra esas acciones en sus ajustes dentro del editor de overlay. El Widget Builder muestra los mismos botones para probarlos de forma segura mientras programas.
'text'Campo de texto de una líneastring'number'Campo numériconumber'color'Selector de colorcadena hex, p. ej. "#FF2E4D"'select'Desplegable (necesita options: [...])una de las opciones'toggle'Interruptor on/offboolean'range'Deslizador (min / max / step)number'button'Botón de acción declarativoincrement, set o toggle: JSON validado, nunca código del panel'sound'Selector de los sonidos subidos por el streamer + Subirla URL del archivo elegido ("" = ninguno)'image'Selector de las imágenes subidas por el streamer + Subirla URL del archivo elegido ("" = ninguno)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();
});Las definiciones de controles son datos validados. El action de un botón solo acepta increment, set o toggle. El renderizador del panel rechaza o ignora HTML, código de callback y URLs arbitrarias.
Sandbox y límites
Los widgets se ejecutan en un iframe aislado con una política de seguridad de contenido estricta, así que un widget roto o malicioso nunca puede tocar tu cuenta ni el resto de la página.
- Imágenes y GIFs remotos (arte de regalos de TikTok, avatares, cualquier URL)
- Google Fonts + tu propio @font-face
- Audio mediante new Audio(url)
- Peticiones a las APIs propias de TokElements (p. ej. /api/files/…)
- Animaciones CSS, SVG, canvas, Web Audio
- Etiquetas <script> externas / CDNs
- fetch / XHR / WebSocket a otros hosts
- Cookies, localStorage, acceso a la página padre
- Cargar paquetes npm
Ejemplos
Widgets completos para copiar y pegar. Cada vista previa está en vivo: se lanzan eventos de demo para que veas cómo reacciona.
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;
});