////////////////////// // GLOBAL VARIABLES // ////////////////////// const sbActionPrintRoutine = '5c756513-a1d0-4285-9dbc-21ad34491310'; ///////////////////////// // STREAMER.BOT EVENTS // ///////////////////////// window.parent.sbClient.on('General.Custom', (response) => { console.debug(response.data); CustomEvent(response.data); }) ///////////////// // PRINTER BOT // ///////////////// async function CustomEvent(data) { if (data.actionName != 'Printer Bot | Events') return; // Get a reference to the template const template = document.getElementById('receipt-template'); // Create a new instance of the template const instance = template.content.cloneNode(true); // Get divs const headerEl = instance.querySelector('#receipt-header'); const contentEl = instance.querySelector('#receipt-content'); const footerEl = instance.querySelector('#receipt-footer'); const avatarEl = instance.querySelector('#receipt-avatar'); const titleEl = instance.querySelector('#receipt-title'); const subtitleEl = instance.querySelector('#receipt-subtitle'); const iconEl = instance.querySelector('#receipt-icon'); const dateEl = instance.querySelector('#receipt-date'); // Set the main contents switch (data.__source) { // Twitch events case ('TwitchCheer'): { avatarEl.src = await GetAvatar(data.userName, 'twitch'); titleEl.innerText = `${data.bits} BITS`; subtitleEl.innerText = `${data.user}`; const messageEl = document.createElement('div'); messageEl.innerHTML = SanitizeHTML(data.message); // Render emotes for (i in data.emotes) { const emoteElement = ``; const emoteName = EscapeRegExp(data.emotes[i].name); function EscapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string } let regexPattern = emoteName; // Check if the emote name consists only of word characters (alphanumeric and underscore) if (/^\w+$/.test(emoteName)) { regexPattern = `\\b${emoteName}\\b`; } else { // For non-word emotes, ensure they are surrounded by non-word characters or boundaries regexPattern = `(?<=^|[^\\w])${emoteName}(?=$|[^\\w])`; } const regex = new RegExp(regexPattern, 'g'); messageEl.innerHTML = messageEl.innerHTML.replace(regex, emoteElement); } // Render cheermotes for (i in data.cheerEmotes) { const bits = data.cheerEmotes[i].bits; const imageUrl = data.cheerEmotes[i].imageUrl; const name = data.cheerEmotes[i].name; const cheerEmoteElement = ``; const bitsElements = `${bits}` messageEl.innerHTML = messageEl.innerHTML.replace(new RegExp(`\\b${name}${bits}\\b`, 'i'), cheerEmoteElement + bitsElements); } contentEl.appendChild(messageEl); // Set the platform icon SetPlatformIcon(iconEl, 'twitch'); } break; case ('TwitchSub'): { avatarEl.src = await GetAvatar(data.userName, 'twitch'); titleEl.innerText = `${data.tier} subscriber`; subtitleEl.innerText = `${data.user}`; const messageEl = document.createElement('div'); messageEl.innerHTML = 'First time subscriber!'; contentEl.appendChild(messageEl); // Set the platform icon SetPlatformIcon(iconEl, 'twitch'); } break; case ('TwitchReSub'): { avatarEl.src = await GetAvatar(data.userName, 'twitch'); titleEl.innerText = `${data.tier} subscriber`; subtitleEl.innerText = `${data.user}`; const messageEl = document.createElement('div'); messageEl.innerHTML = `${data.cumulative} months${data.monthStreak > 1 ? ' (' + data.monthStreak + ' in a row!)' : ''}`; if (data.messageStripped) messageEl.innerHTML += `

${SanitizeHTML(data.messageStripped)}`; contentEl.appendChild(messageEl); // Set the platform icon SetPlatformIcon(iconEl, 'twitch'); } break; case ('TwitchGiftSub'): { // Don't put profile pictures for subs that come from Gift Bombs to avoid rate limits if (data.fromGiftBomb) //avatarEl.style.display = 'none'; return; else avatarEl.src = await GetAvatar(data.recipientUserName, 'twitch'); titleEl.innerText = `Gifted Sub`; const messageEl = document.createElement('div'); if (data.anonymous) messageEl.innerHTML += `✦・゚ A mysterious admirer ・゚✦
`; else messageEl.innerHTML += `${data.user}
`; messageEl.innerHTML += `gifted a ${data.tier} sub to
${data.recipientUser}`; contentEl.appendChild(messageEl); // Set the platform icon SetPlatformIcon(iconEl, 'twitch'); } break; case ('TwitchGiftBomb'): { avatarEl.src = await GetAvatar(data.userName, 'twitch'); titleEl.innerHTML = `${data.gifts} × Gifted Subs`; subtitleEl.innerHTML += `✧*・゚✧ ${data.tier.toUpperCase()} ✧・゚*✧`; if (data.anonymous) subtitleEl.innerHTML += `
From a mystery person...`; else subtitleEl.innerHTML += `
${data.user}`; const messageEl = document.createElement('div'); if (data.totalGifts > 1) { messageEl.innerHTML = `They've gifted ${data.totalGifts} subs in total!

`; } // Get a list of all recipient users Object.keys(data) .filter(key => /^gift\.recipientUser\d+$/.test(key)) .forEach((key, index) => { const username = data[key]; messageEl.innerHTML += `${username}
`; }); contentEl.appendChild(messageEl); // Set the platform icon SetPlatformIcon(iconEl, 'twitch'); } break; case ('TwitchRaid'): { avatarEl.src = await GetAvatar(data.userName, 'twitch'); const messageEl = document.createElement('div'); messageEl.innerHTML = `${data.user}
is raiding with a party of
${data.viewers} viewers!`; contentEl.appendChild(messageEl); // Set the platform icon SetPlatformIcon(iconEl, 'twitch'); } break; case ('TwitchCustomPowerUpRedemption'): { avatarEl.src = await GetAvatar(data.userName, 'twitch'); titleEl.innerText = `${data["customPowerUp.bitsCost"]} BITS`; subtitleEl.innerText = `${data.user}`; const messageEl = document.createElement('div'); messageEl.innerHTML = SanitizeHTML(data.rawInput); // // Render emotes // for (i in data.emotes) { // const emoteElement = ``; // const emoteName = EscapeRegExp(data.emotes[i].name); // function EscapeRegExp(string) { // return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string // } // let regexPattern = emoteName; // // Check if the emote name consists only of word characters (alphanumeric and underscore) // if (/^\w+$/.test(emoteName)) { // regexPattern = `\\b${emoteName}\\b`; // } // else { // // For non-word emotes, ensure they are surrounded by non-word characters or boundaries // regexPattern = `(?<=^|[^\\w])${emoteName}(?=$|[^\\w])`; // } // const regex = new RegExp(regexPattern, 'g'); // messageEl.innerHTML = messageEl.innerHTML.replace(regex, emoteElement); // } // // Render cheermotes // for (i in data.cheerEmotes) { // const bits = data.cheerEmotes[i].bits; // const imageUrl = data.cheerEmotes[i].imageUrl; // const name = data.cheerEmotes[i].name; // const cheerEmoteElement = ``; // const bitsElements = `${bits}` // messageEl.innerHTML = messageEl.innerHTML.replace(new RegExp(`\\b${name}${bits}\\b`, 'i'), cheerEmoteElement + bitsElements); // } contentEl.appendChild(messageEl); // Set the platform icon SetPlatformIcon(iconEl, 'twitch'); } break; // YouTube Events case ('YouTubeNewSponsor'): { if (data.userProfileUrl) avatarEl.src = data.userProfileUrl; else avatarEl.style.display = 'none'; titleEl.innerText = `${data.levelName}`; subtitleEl.innerText = `${data.user}`; contentEl.style.display = 'none'; // Set the platform icon SetPlatformIcon(iconEl, 'youtube'); } break; case ('YouTubeGiftMembershipReceived'): { if (data.gifterProfileUrl) avatarEl.src = data.gifterProfileUrl; else avatarEl.style.display = 'none'; titleEl.innerText = `Gifted Membership`; const messageEl = document.createElement('div'); messageEl.innerHTML = `${data.gifterUser}
gifted a membership to
${data.user}!`; contentEl.appendChild(messageEl); // Set the platform icon SetPlatformIcon(iconEl, 'youtube'); } break; case ('YouTubeSuperChat'): { if (data.userProfileUrl) avatarEl.src = data.userProfileUrl; else avatarEl.style.display = 'none'; titleEl.style.fontSize = '2em'; titleEl.innerText = `${data.amount}`; const messageEl = document.createElement('div'); messageEl.innerHTML = `${data.user}
sent a Super Chat!`; if (data.message) messageEl.innerHTML += `

${SanitizeHTML(data.message)}`; contentEl.appendChild(messageEl); // Set the platform icon SetPlatformIcon(iconEl, 'youtube'); } break; case ('YouTubeSuperSticker'): { if (data.stickerImageUrl) avatarEl.src = data.stickerImageUrl; else avatarEl.style.display = 'none'; titleEl.style.fontSize = '2em'; titleEl.innerText = `${data.amount}`; const messageEl = document.createElement('div'); messageEl.innerHTML = `${data.user}
sent a Super Sticker!`; contentEl.appendChild(messageEl); // Set the platform icon SetPlatformIcon(iconEl, 'youtube'); } break; break; // Kick Events case ('KickSubscription'): case ('KickResubscription'): { avatarEl.src = ConvertWEBPToPNG(await GetAvatar(data.user, 'kick')); titleEl.innerText = `Subscriber`; subtitleEl.innerText = `${data.user}`; const messageEl = document.createElement('div'); if (data.duration > 1) messageEl.innerHTML = `${data.duration} months`; else messageEl.innerHTML = 'First time subscriber!'; contentEl.appendChild(messageEl); // Set the platform icon SetPlatformIcon(iconEl, 'kick'); } break; case ('KickGiftSubscription'): { avatarEl.src = ConvertWEBPToPNG(await GetAvatar(data["recipient.userLogin"], 'kick')); titleEl.innerText = `Gifted Sub`; const messageEl = document.createElement('div'); messageEl.innerHTML = `${data.user}
gifted a sub to
${data["recipient.userName"]}!`; contentEl.appendChild(messageEl); // Set the platform icon SetPlatformIcon(iconEl, 'kick'); } break; case ('KickMassGiftSubscription'): { // There is only one sub, so use the same template for a single gifted sub if ('recipient.userName' in data) { avatarEl.src = ConvertWEBPToPNG(await GetAvatar(data["recipient.userLogin"], 'kick')); titleEl.innerText = `Gifted Sub`; const messageEl = document.createElement('div'); messageEl.innerHTML = `${data.user}
gifted a sub to
${data["recipient.userName"]}!`; contentEl.appendChild(messageEl); } else { avatarEl.src = ConvertWEBPToPNG(await GetAvatar(data.user, 'kick')); // Calculate how many subs were gived let maxIndex = -1; for (const key in data) { const match = key.match(/^recipient\.(\d+)\./); if (match) { const index = parseInt(match[1], 10); if (index > maxIndex) { maxIndex = index; } } } const totalGifts = maxIndex + 1; titleEl.innerHTML = `${totalGifts} × Gifted Subs`; subtitleEl.innerText = `${data.user}`; const messageEl = document.createElement('div'); // Loop through each recipient and include it in the receipt const recipients = {}; // Reconstruct recipient objects for (const key in data) { const match = key.match(/^recipient\.(\d+)\.(.+)$/); if (match) { const index = match[1]; const field = match[2]; if (!recipients[index]) { recipients[index] = {}; } recipients[index][field] = data[key]; } } // Loop through and print userName for (const index in recipients) { messageEl.innerHTML += `${recipients[index].userName}
`; } contentEl.appendChild(messageEl); } // Set the platform icon SetPlatformIcon(iconEl, 'kick'); } break; // StreamElements Events case ('StreamElementsTip'): { const avatarURL = await GetAvatar(data.tipUsername, 'twitch'); if (IsValidUrl(avatarURL)) avatarEl.src = avatarURL; else avatarEl.style.display = 'none' titleEl.style.fontSize = '2em'; titleEl.innerText = FormatCurrency(data.tipAmount, data.tipCurrency); subtitleEl.innerText = `${data.tipUsername}`; if (data.tipMessage) { const messageEl = document.createElement('div'); messageEl.innerHTML = `${SanitizeHTML(data.tipMessage)}`; contentEl.appendChild(messageEl); } else { contentEl.style.display = 'none'; } } break; // Streamlabs Events case ('StreamlabsDonation'): { const avatarURL = await GetAvatar(data.donationFrom, 'twitch'); if (IsValidUrl(avatarURL)) avatarEl.src = avatarURL; else avatarEl.style.display = 'none' titleEl.style.fontSize = '2em'; titleEl.innerText = data.donationFormattedAmount; subtitleEl.innerText = `${data.donationFrom}`; if (data.donationMessage) { const messageEl = document.createElement('div'); messageEl.innerHTML = `${SanitizeHTML(data.donationMessage)}`; contentEl.appendChild(messageEl); } else { contentEl.style.display = 'none'; } } break; // Fourthwall Events case ('FourthwallDonation'): { const avatarURL = await GetAvatar(data["fw.username"], 'twitch'); if (IsValidUrl(avatarURL)) avatarEl.src = avatarURL; else avatarEl.style.display = 'none' titleEl.style.fontSize = '2em'; titleEl.innerText = FormatCurrency(data["fw.amount"], data["fw.currency"]); if (data["fw.username"]) subtitleEl.innerText = `${data["fw.username"]}`; else if (data["fw.email"]) subtitleEl.innerText = `${data["fw.email"]}`; if (data["fw.message"]) { const messageEl = document.createElement('div'); messageEl.innerHTML = `${SanitizeHTML(data["fw.message"])}`; contentEl.appendChild(messageEl); } else { contentEl.style.display = 'none'; } } break; // case ('FourthwallGiftPurchase'): // break; case ('FourthwallOrderPlaced'): { // Only print non-free orders if (data["fw.total"] <= 0) return; const avatarURL = await GetAvatar(data["fw.username"], 'twitch'); if (IsValidUrl(avatarURL)) avatarEl.src = avatarURL; else avatarEl.style.display = 'none' titleEl.style.fontSize = '2em'; titleEl.innerText = FormatCurrency(data["fw.total"], data["fw.currency"]); if (data["fw.username"]) subtitleEl.innerText = `${data["fw.username"]}`; else if (data["fw.email"]) subtitleEl.innerText = `${data["fw.email"]}`; // Compile a list of all items bought const variants = []; // Iterate through all keys in the data object for (const key in data) { const match = key.match(/^fw\.variants\[(\d+)\]\.(\w+)$/); if (match) { const index = Number(match[1]); const field = match[2]; // Make sure the array slot exists if (!variants[index]) { variants[index] = {}; } // Assign the field to the appropriate variant object variants[index][field] = data[key]; } } // Print each item on the receipt const messageEl = document.createElement('div'); variants.forEach((variant, i) => { messageEl.innerHTML += `${variant.quantity} × ${variant.name}
`; }); messageEl.style.textAlign = 'left'; // Check if they left a custom message let customMessageEl = document.createElement('div'); const customMessage = data["fw.statmessageus"]; if (customMessage) { const txt = document.createElement("textarea"); txt.innerHTML = customMessage; customMessageEl.innerHTML += `
${SanitizeHTML(txt.value)}`; } // Add a cute thank you message because you're uwu like that const thankYouEl = document.createElement('div'); thankYouEl.innerHTML += `
Thank you for your purchase!`; contentEl.appendChild(messageEl); contentEl.appendChild(customMessageEl); contentEl.appendChild(thankYouEl); } break; case ('FourthwallSubscriptionPurchased'): { const avatarURL = await GetAvatar(data["fw.nickname"], 'twitch'); if (IsValidUrl(avatarURL)) avatarEl.src = avatarURL; else avatarEl.style.display = 'none' titleEl.innerText = `New Member`; subtitleEl.innerHTML = `${data["fw.nickname"]}`; const messageEl = document.createElement('div'); messageEl.innerHTML = `Thanks for joining at the ${FormatCurrency(data["fw.amount"], data["fw.currency"])} tier!`; contentEl.appendChild(messageEl); } break; // Custom Code Events case ('CustomCodeEvent'): { switch (data.triggerCustomCodeEventName) { case ('kickIncomingRaid'): { avatarEl.src = ConvertWEBPToPNG(await GetAvatar(data.user, 'kick')); const messageEl = document.createElement('div'); messageEl.innerHTML = `${data.user}
is hosting with a party of
${data.viewers} viewers!`; contentEl.appendChild(messageEl); // Set the platform icon SetPlatformIcon(iconEl, 'kick'); } break; case ('kickKicksGifted'): { if (data.giftType != 'LEVEL_UP') return; avatarEl.src = ConvertWEBPToPNG(`https://files.kick.com/kicks/gifts/${data.gift.toLowerCase().replace(/ /g, "-")}.webp`); avatarEl.style.borderRadius = '0px'; titleEl.innerText = `${data.amount} KICKS`; subtitleEl.innerText = `${data.sender}`; const messageEl = document.createElement('div'); messageEl.innerHTML = data.message; contentEl.appendChild(messageEl); // Set the platform icon SetPlatformIcon(iconEl, 'kick'); } break; } } break; // Don't print any event not excplicitly listed above default: return; } // Set the timestamp const { DateTime } = luxon; const now = DateTime.local(); const formatted = now.toFormat("cccc, LLLL d',' yyyy HH:mm:ss"); // Add ordinal suffix manually function addOrdinal(n) { if (n >= 11 && n <= 13) return 'th'; switch (n % 10) { case 1: return 'st'; case 2: return 'nd'; case 3: return 'rd'; default: return 'th'; } } const day = now.day; const ordinal = addOrdinal(day); const fullFormatted = now.toFormat(`cccc, LLLL '${day}${ordinal}' yyyy HH:mm:ss`); dateEl.textContent = fullFormatted; // Send it to the print routine! const receiptHTML = await GetRenderedHTML(instance); window.parent.sbClient.doAction({ id: sbActionPrintRoutine }, { receiptHTML: receiptHTML, isTest: data.isTest, printerName: document.getElementById('printer-name').value, paperWidth: document.getElementById('paper-width').value, ignoreTestTriggers: document.getElementById('ignore-test-triggers').checked, deleteTempFiles: document.getElementById('delete-temp-files').checked }); } ////////////////////// // HELPER FUNCTIONS // ////////////////////// async function GetRenderedHTML(fragment) { if (!(fragment instanceof DocumentFragment)) { throw new Error('Argument must be a DocumentFragment'); } // Filter out comment nodes from fragment content const nodes = Array.from(fragment.childNodes).filter( node => node.nodeType !== Node.COMMENT_NODE ); const bodyContent = nodes .map(node => node.outerHTML || node.textContent) .join(''); // Get inline ${bodyContent} `.trim(); return fullHTML; } function ConvertWEBPToPNG(URL) { return `https://images.weserv.nl/?url=${URL}&output=png`; } function FormatCurrency(amount, currency) { const isISOCode = /^[A-Z]{3}$/.test(currency); if (isISOCode) { try { return new Intl.NumberFormat(undefined, { style: 'currency', currency: currency, currencyDisplay: 'symbol', }).format(amount); } catch { return `${amount.toFixed(2)} ${currency}`; } } // Handle some common symbols that go before the number const symbolsBefore = ['$', '€', '£', '¥', '₹']; if (symbolsBefore.includes(currency)) { return `${currency}${amount.toFixed(2)}`; } // Otherwise default to appending after return `${amount.toFixed(2)} ${currency}`; } function IsValidUrl(string) { try { new URL(string); return true; } catch { return false; } } function SetPlatformIcon(el, platform) { // Set the platform icon let baseURL = window.location.href; baseURL = baseURL.replace(/index\.html$/i, ''); el.src = `${baseURL}/icons/platforms/${platform}.png`; } /////////////////////// // PAGE INTERACTIONS // /////////////////////// let data = { "__source": "TwitchSub", "tier": "prime", "isPrimeSub": true, "monthsSubscribed": 1, "isTest": false, "actionName": "Printer Bot | Events", "user": "nutty", "userName": "nutty", "userType": "twitch" } // let data = { // "__source": "CustomCodeEvent", // "actionName": "Printer Bot | Events", // "triggerCustomCodeEventName": "kickKicksGifted", // "amount": 100, // "message": "Eat my ass", // "gift": "Flex", // "giftType": "LEVEL_UP", // "giftTier": "LEVEL_UP", // "sender": "DJSUSAN00", // "senderId": "170168", // "senderColor": "#1475E1", // "eventSource": "kick", // "fromKick": true // } async function TestPrint() { CustomEvent(data); } /////////////////// // PAGE SETTINGS // /////////////////// // Get references const printerNameInput = document.getElementById('printer-name'); const paperWidthInput = document.getElementById('paper-width'); const ignoreTestTriggersInput = document.getElementById('ignore-test-triggers'); const deleteTempFilesInput = document.getElementById('delete-temp-files'); // Local storage key must be prefixed with the first URL segment const currentPath = window.location.pathname; const urlSegment = currentPath.split('/').filter(Boolean)[0]; const storageKey = (id) => `${urlSegment}::${id}`; function saveSetting(id) { const el = document.getElementById(id); const value = el.type === "checkbox" ? el.checked : el.value; localStorage.setItem(storageKey(id), value); } // Add event listeners [printerNameInput, paperWidthInput, ignoreTestTriggersInput, deleteTempFilesInput].forEach(input => { input.addEventListener("input", () => saveSetting(input.id)); input.addEventListener("change", () => saveSetting(input.id)); }); // Load settings if (localStorage.getItem(storageKey(printerNameInput.id))) printerNameInput.value = localStorage.getItem(storageKey(printerNameInput.id)); if (localStorage.getItem(storageKey(paperWidthInput.id))) paperWidthInput.value = localStorage.getItem(storageKey(paperWidthInput.id)); if (localStorage.getItem(storageKey(ignoreTestTriggersInput.id))) ignoreTestTriggersInput.checked = JSON.parse(localStorage.getItem(storageKey(ignoreTestTriggersInput.id))); if (localStorage.getItem(storageKey(deleteTempFilesInput.id))) deleteTempFilesInput.checked = JSON.parse(localStorage.getItem(storageKey(deleteTempFilesInput.id)));