Apps Home
|
Create an App
damnyanqui's all-in-one
Author:
damnyanqui
Description
Source Code
Launch App
Current Users
Created by:
Damnyanqui
// All-In-One Suite v3: 14 Modules in One App Slot // KOTH + Tip Menu + Mystery Box + Auction House + Ticket Show + Tip Goals // + Welcome Messages + Top Tipper Leaderboard + Tip Thank-You + Moderation // + Follower Notifications + Whale Detection + Lovense Integration + Hashtag Rotation // // FREEFORM TEXT FORMATS: // Tip Menu: "price=Item, price=Item, ..." (e.g. "15=Shoutout, 25=Song Request") // Goals: "amount=Goal, amount=Goal, ..." (e.g. "500=Dance, 1000=Song, 2000=Outfit") // Thank-You: "threshold=Message, ..." (e.g. "1=Thanks {user}!, 100=WOW {user}!") // Lovense: "threshold=Level, ..." (e.g. "1=Low (3s), 15=Medium (6s)") // Hashtags: space-separated (e.g. "#fun #live #tips #interactive") // Word Filter: comma-separated (e.g. "spam,kik,snapchat") // // TIP ROUTING: Ticket > Menu > Mystery Box > Auction (exclusive). KOTH + Goals // + Leaderboard + Thank-You + Lovense always fire on every qualifying tip. // // PANEL PRIORITY: Ticket > Auction > Goals > KOTH > Leaderboard > Mystery > Lovense > Menu // // COMMANDS: !menu !mb !collection !bid !auction !endauction !king !mystats !hill // !goal !top !levels !ticket !startshow !endshow !ticketlist !grantticket !revoketicket // !filter !help // =================================================================== // SETTINGS // =================================================================== cb.settings_choices = [ // --- Module Toggles --- { name: 'enable_menu', type: 'choice', choice1: 'on', choice2: 'off', defaultValue: 'on', label: "[MODULES] Tip Menu" }, { name: 'enable_mystery', type: 'choice', choice1: 'on', choice2: 'off', defaultValue: 'on', label: "[MODULES] Mystery Box" }, { name: 'enable_auction', type: 'choice', choice1: 'on', choice2: 'off', defaultValue: 'on', label: "[MODULES] Auction House" }, { name: 'enable_koth', type: 'choice', choice1: 'on', choice2: 'off', defaultValue: 'on', label: "[MODULES] King of the Hill" }, { name: 'enable_goals', type: 'choice', choice1: 'on', choice2: 'off', defaultValue: 'on', label: "[MODULES] Tip Goals" }, { name: 'enable_leaderboard', type: 'choice', choice1: 'on', choice2: 'off', defaultValue: 'on', label: "[MODULES] Top Tipper Leaderboard" }, { name: 'enable_thankyou', type: 'choice', choice1: 'on', choice2: 'off', defaultValue: 'on', label: "[MODULES] Tip Thank-You" }, { name: 'enable_lovense', type: 'choice', choice1: 'on', choice2: 'off', defaultValue: 'off', label: "[MODULES] Lovense Integration" }, { name: 'enable_welcome', type: 'choice', choice1: 'on', choice2: 'off', defaultValue: 'on', label: "[MODULES] Welcome Messages" }, { name: 'enable_whale', type: 'choice', choice1: 'on', choice2: 'off', defaultValue: 'on', label: "[MODULES] Whale Detection" }, { name: 'enable_followers', type: 'choice', choice1: 'on', choice2: 'off', defaultValue: 'on', label: "[MODULES] Follower Notifications" }, { name: 'enable_moderation', type: 'choice', choice1: 'on', choice2: 'off', defaultValue: 'on', label: "[MODULES] Moderation (silence/filter)" }, { name: 'enable_hashtags', type: 'choice', choice1: 'on', choice2: 'off', defaultValue: 'on', label: "[MODULES] Hashtag Rotation" }, { name: 'enable_ticket', type: 'choice', choice1: 'on', choice2: 'off', defaultValue: 'off', label: "[MODULES] Ticket Show" }, // --- Tip Menu --- { name: 'menu_items', type: 'str', defaultValue: '15=Shoutout, 25=Song Request, 50=Dance, 100=Special Request', maxLength: 1024, label: "[MENU] Items (format: 15=Shoutout, 25=Song, ...)" }, { name: 'menu_items_2', type: 'str', defaultValue: '', maxLength: 1024, label: "[MENU] More items (overflow, same format, blank=none)" }, { name: 'menu_sort', type: 'choice', choice1: 'by price', choice2: 'as entered', defaultValue: 'by price', label: "[MENU] Sort order" }, { name: 'menu_repeat', type: 'int', minValue: 0, maxValue: 30, defaultValue: 10, label: "[MENU] Post menu in chat every X min (0=off)" }, // --- Mystery Box --- { name: 'mystery_cost', type: 'int', minValue: 1, maxValue: 9999, defaultValue: 25, label: "[MYSTERY] Token cost per box" }, { name: 'mystery_reward_1', type: 'str', defaultValue: 'Shoutout', maxLength: 100, label: "[MYSTERY] Common Reward 1" }, { name: 'mystery_reward_2', type: 'str', defaultValue: 'Song Request', maxLength: 100, label: "[MYSTERY] Common Reward 2" }, { name: 'mystery_reward_3', type: 'str', defaultValue: 'Dance', maxLength: 100, label: "[MYSTERY] Uncommon Reward" }, { name: 'mystery_reward_4', type: 'str', defaultValue: 'Special Request', maxLength: 100, label: "[MYSTERY] Rare Reward" }, { name: 'mystery_reward_5', type: 'str', defaultValue: 'Grand Prize!', maxLength: 100, label: "[MYSTERY] Legendary Reward" }, { name: 'mystery_pity', type: 'int', minValue: 0, maxValue: 50, defaultValue: 10, label: "[MYSTERY] Guaranteed rare after X boxes (0=off)" }, // --- Auction House --- { name: 'auction_item_1', type: 'str', defaultValue: '', maxLength: 100, label: "[AUCTION] Auto-start Item 1 (blank=off)" }, { name: 'auction_item_2', type: 'str', defaultValue: '', maxLength: 100, label: "[AUCTION] Auto-start Item 2 (blank=off)" }, { name: 'auction_start_bid', type: 'int', minValue: 1, maxValue: 9999, defaultValue: 10, label: "[AUCTION] Starting bid" }, { name: 'auction_increment', type: 'int', minValue: 1, maxValue: 100, defaultValue: 5, label: "[AUCTION] Minimum bid increment" }, { name: 'auction_duration', type: 'int', minValue: 1, maxValue: 60, defaultValue: 3, label: "[AUCTION] Duration (minutes)" }, { name: 'auction_anti_snipe', type: 'int', minValue: 0, maxValue: 120, defaultValue: 30, label: "[AUCTION] Anti-snipe extension (seconds, 0=off)" }, // --- King of the Hill --- { name: 'koth_window', type: 'int', minValue: 1, maxValue: 60, defaultValue: 5, label: "[KOTH] Minutes between tips to keep streak" }, { name: 'koth_min_tip', type: 'int', minValue: 1, maxValue: 1000, defaultValue: 1, label: "[KOTH] Minimum tip for streak" }, { name: 'koth_min_streak', type: 'int', minValue: 2, maxValue: 10, defaultValue: 2, label: "[KOTH] Tips needed to claim crown" }, { name: 'koth_show_challengers', type: 'choice', choice1: 'yes', choice2: 'no', defaultValue: 'yes', label: "[KOTH] Show challengers publicly" }, // --- Tip Goals --- { name: 'goal_items', type: 'str', defaultValue: '500=Dance, 1000=Song, 2000=Outfit Change', maxLength: 1024, label: "[GOALS] Goals (format: 500=Dance, 1000=Song, ...)" }, { name: 'goal_subject_prefix', type: 'str', defaultValue: 'Tipping for:', maxLength: 100, label: "[GOALS] Subject prefix before goal name" }, // --- Top Tipper Leaderboard --- { name: 'leaderboard_size', type: 'int', minValue: 3, maxValue: 10, defaultValue: 5, label: "[LEADERBOARD] How many top tippers to show" }, // --- Tip Thank-You --- { name: 'thankyou_tiers', type: 'str', defaultValue: '1=Thanks {user}!, 50=Wow {user} generous!, 100={user} AMAZING!, 500=MEGA TIP {user}!!!', maxLength: 512, label: "[THANKYOU] Tiers (format: 1=Thanks {user}!, ... supports {user} {amount})" }, // --- Lovense --- { name: 'lovense_levels', type: 'str', defaultValue: '2=Low (3s), 15=Medium (6s), 50=High (10s), 100=Ultra (20s)', maxLength: 512, label: "[LOVENSE] Levels (format: 2=Low (3s), 15=Medium (6s), ...)" }, // --- Welcome Messages --- { name: 'welcome_msg', type: 'str', defaultValue: 'Welcome {user}! Type !menu for tip options, !help for commands.', maxLength: 256, label: "[WELCOME] Message ({user} and {room} placeholders)" }, { name: 'welcome_show_menu', type: 'choice', choice1: 'yes', choice2: 'no', defaultValue: 'yes', label: "[WELCOME] Also show tip menu on enter" }, // --- Whale Detection --- { name: 'whale_notify', type: 'choice', choice1: 'broadcaster only', choice2: 'public', defaultValue: 'broadcaster only', label: "[WHALE] Who sees whale alerts" }, // --- Follower Notifications --- { name: 'follower_msg', type: 'str', defaultValue: 'Thanks for following, {user}!', maxLength: 256, label: "[FOLLOWER] Message ({user} placeholder)" }, // --- Moderation --- { name: 'mod_silence_gray', type: 'choice', choice1: 'yes', choice2: 'no', defaultValue: 'no', label: "[MODERATION] Silence gray (tokenless) users" }, { name: 'mod_word_filter', type: 'str', defaultValue: '', maxLength: 512, label: "[MODERATION] Banned words (comma-separated)" }, { name: 'mod_gray_msg', type: 'str', defaultValue: 'You must have tokens to chat. Purchase tokens to join!', maxLength: 256, label: "[MODERATION] Message shown to silenced gray users" }, // --- Ticket Show --- { name: 'ticket_price', type: 'int', minValue: 1, maxValue: 9999, defaultValue: 50, label: "[TICKET] Ticket price (tokens)" }, { name: 'ticket_min_sold', type: 'int', minValue: 0, maxValue: 999, defaultValue: 5, label: "[TICKET] Min tickets before show can start (0=no minimum)" }, { name: 'ticket_mods_free', type: 'choice', choice1: 'yes', choice2: 'no', defaultValue: 'yes', label: "[TICKET] Free tickets for moderators" }, { name: 'ticket_fanclub', type: 'choice', choice1: 'yes', choice2: 'no', defaultValue: 'yes', label: "[TICKET] Free tickets for fan club members" }, { name: 'ticket_hidden_msg', type: 'str', defaultValue: 'Hidden show in progress! Tip {price} tokens for a ticket. Type !ticket for info.', maxLength: 256, label: "[TICKET] Message shown to non-ticket holders ({price} placeholder)" }, // --- Hashtag Rotation --- { name: 'hashtag_tags', type: 'str', defaultValue: '#fun #live #tips #interactive', maxLength: 256, label: "[HASHTAGS] Tags (space-separated)" }, { name: 'hashtag_interval', type: 'int', minValue: 1, maxValue: 60, defaultValue: 15, label: "[HASHTAGS] Rotate every X minutes" }, // --- General --- { name: 'chat_interval', type: 'int', minValue: 0, maxValue: 30, defaultValue: 8, label: "[GENERAL] Rotate ads every X minutes (0=off)" } ]; // =================================================================== // SHARED HELPERS // =================================================================== function now() { return new Date().getTime(); } function parseItemString(str) { var items = []; if (!str || str.trim() === '') return items; var parts = str.split(','); for (var i = 0; i < parts.length; i++) { var part = parts[i].trim(); if (part === '') continue; var eq = part.indexOf('='); if (eq <= 0) continue; var price = parseInt(part.substring(0, eq).trim(), 10); var name = part.substring(eq + 1).trim(); if (isNaN(price) || price <= 0 || name === '') continue; items.push({ name: name, price: price }); } return items; } // =================================================================== // MODULE: TIP MENU // =================================================================== var menuItems = []; var menuByPrice = {}; var menuFulfilled = 0; function initMenu() { menuItems = []; menuByPrice = {}; var items1 = parseItemString(cb.settings.menu_items); var items2 = parseItemString(cb.settings.menu_items_2); menuItems = items1.concat(items2); if (cb.settings.menu_sort === 'by price') { menuItems.sort(function(a, b) { return a.price - b.price; }); } for (var i = 0; i < menuItems.length; i++) { var p = menuItems[i].price; if (!menuByPrice[p]) menuByPrice[p] = []; menuByPrice[p].push(menuItems[i]); } } function findMenuItem(amount, tipMessage) { var matches = menuByPrice[amount]; if (!matches || matches.length === 0) return null; if (matches.length === 1) return matches[0]; if (tipMessage) { var msgLower = tipMessage.toLowerCase(); for (var i = 0; i < matches.length; i++) { if (msgLower.indexOf(matches[i].name.toLowerCase()) !== -1) return matches[i]; } } return matches[0]; } function getMenuText() { if (menuItems.length === 0) return 'No menu items configured.'; var lines = ['--- TIP MENU (' + menuItems.length + ' items) ---']; for (var i = 0; i < menuItems.length; i++) { lines.push(' ' + menuItems[i].price + ' - ' + menuItems[i].name); } return lines.join('\n'); } function handleMenuTip(user, amount, tipMessage) { var item = findMenuItem(amount, tipMessage); if (!item) return false; menuFulfilled++; cb.sendNotice( '>> TIP MENU >> ' + user + ' tipped for: ' + item.name + ' (' + item.price + ' tokens)', '', '', '#2d8633', 'bold' ); cb.sendNotice( 'Menu request from ' + user + ': "' + item.name + '" (' + item.price + ' tokens) - please fulfill!', cb.room_slug, '#ffffff', '#333333', 'bold' ); return true; } // =================================================================== // MODULE: MYSTERY BOX // =================================================================== var RARITY = { COMMON: { label: 'Common', color: '#aaaaaa', weight: 50 }, UNCOMMON: { label: 'Uncommon', color: '#1eff00', weight: 30 }, RARE: { label: 'Rare', color: '#0070dd', weight: 15 }, LEGENDARY: { label: 'Legendary', color: '#ff8000', weight: 5 } }; var mysteryRewards = []; var mysteryAttempts = {}; var mysteryCollections = {}; var totalBoxes = 0; function initMystery() { mysteryRewards = [ { name: cb.settings.mystery_reward_1, rarity: RARITY.COMMON }, { name: cb.settings.mystery_reward_2, rarity: RARITY.COMMON }, { name: cb.settings.mystery_reward_3, rarity: RARITY.UNCOMMON }, { name: cb.settings.mystery_reward_4, rarity: RARITY.RARE }, { name: cb.settings.mystery_reward_5, rarity: RARITY.LEGENDARY } ]; } function pickReward(user) { var pity = cb.settings.mystery_pity; var userAttempts = mysteryAttempts[user] || 0; if (pity > 0 && userAttempts >= pity) { mysteryAttempts[user] = 0; var rareRewards = []; for (var i = 0; i < mysteryRewards.length; i++) { if (mysteryRewards[i].rarity.weight <= RARITY.RARE.weight) rareRewards.push(mysteryRewards[i]); } return rareRewards[Math.floor(Math.random() * rareRewards.length)]; } var totalWeight = 0; for (var i = 0; i < mysteryRewards.length; i++) totalWeight += mysteryRewards[i].rarity.weight; var roll = Math.random() * totalWeight; var cumulative = 0; for (var i = 0; i < mysteryRewards.length; i++) { cumulative += mysteryRewards[i].rarity.weight; if (roll <= cumulative) { if (mysteryRewards[i].rarity.weight <= RARITY.RARE.weight) { mysteryAttempts[user] = 0; } else { mysteryAttempts[user] = (mysteryAttempts[user] || 0) + 1; } return mysteryRewards[i]; } } return mysteryRewards[0]; } function trackCollection(user, reward) { if (!mysteryCollections[user]) mysteryCollections[user] = {}; mysteryCollections[user][reward.name] = (mysteryCollections[user][reward.name] || 0) + 1; } function getCollectionCount(user) { if (!mysteryCollections[user]) return 0; var count = 0; for (var key in mysteryCollections[user]) { if (mysteryCollections[user].hasOwnProperty(key)) count++; } return count; } function handleMysteryTip(user, amount) { if (amount !== cb.settings.mystery_cost) return false; var reward = pickReward(user); trackCollection(user, reward); totalBoxes++; cb.sendNotice( '>> MYSTERY BOX >> ' + user + ' won: ' + reward.name + ' [' + reward.rarity.label + ']', '', '', reward.rarity.color, 'bold' ); cb.sendNotice( user + ' won "' + reward.name + '" (' + reward.rarity.label + ') - please fulfill!', cb.room_slug, '#ffffff', '#333333', 'bold' ); if (getCollectionCount(user) === mysteryRewards.length) { cb.sendNotice('*** ' + user + ' collected ALL mystery rewards! ***', '', '', '#ff8000', 'bold'); } return true; } // =================================================================== // MODULE: AUCTION HOUSE // =================================================================== var auctions = {}; var auctionHistory = []; function startAuction(itemName) { if (auctions[itemName] && auctions[itemName].active) return false; var duration = cb.settings.auction_duration * 60 * 1000; auctions[itemName] = { highBid: 0, highBidder: null, endTime: now() + duration, active: true, timer: null }; scheduleAuctionEnd(itemName); cb.sendNotice( '>>> AUCTION STARTED: "' + itemName + '" <<<\n' + 'Starting bid: ' + cb.settings.auction_start_bid + ' | Min increment: ' + cb.settings.auction_increment + ' | Duration: ' + cb.settings.auction_duration + 'min | Tip to bid!', '', '#ffffff', '#2d8633', 'bold' ); cb.drawPanel(); return true; } function scheduleAuctionEnd(itemName) { var auction = auctions[itemName]; if (auction.timer) cb.cancelTimeout(auction.timer); var remaining = auction.endTime - now(); if (remaining <= 0) remaining = 1000; auction.timer = cb.setTimeout(function() { endAuction(itemName); }, remaining); } function endAuction(itemName) { var auction = auctions[itemName]; if (!auction || !auction.active) return; auction.active = false; if (auction.highBidder) { cb.sendNotice( '>>> AUCTION WON: "' + itemName + '" <<<\nWinner: ' + auction.highBidder + ' with ' + auction.highBid + ' tokens!', '', '#ffffff', '#ffd700', 'bold' ); cb.sendNotice( 'Auction won by ' + auction.highBidder + ' for "' + itemName + '" (' + auction.highBid + ' tokens) - please fulfill!', cb.room_slug, '#ffffff', '#333333', 'bold' ); auctionHistory.push({ item: itemName, winner: auction.highBidder, amount: auction.highBid }); } else { cb.sendNotice('>>> AUCTION ENDED: "' + itemName + '" - No bids! <<<', '', '', '#ff4444', ''); } cb.drawPanel(); } function getActiveAuctions() { var active = []; for (var item in auctions) { if (auctions.hasOwnProperty(item) && auctions[item].active) active.push(item); } return active; } function getMinBid(itemName) { var auction = auctions[itemName]; if (!auction || auction.highBid === 0) return cb.settings.auction_start_bid; return auction.highBid + cb.settings.auction_increment; } function handleAuctionTip(user, amount) { var active = getActiveAuctions(); if (active.length === 0) return false; for (var i = 0; i < active.length; i++) { var itemName = active[i]; var auction = auctions[itemName]; var minBid = getMinBid(itemName); if (amount >= minBid) { var prevBidder = auction.highBidder; auction.highBid = amount; auction.highBidder = user; var remaining = auction.endTime - now(); var snipeWindow = cb.settings.auction_anti_snipe * 1000; if (snipeWindow > 0 && remaining < snipeWindow) { auction.endTime = now() + snipeWindow; scheduleAuctionEnd(itemName); cb.sendNotice('Anti-snipe! Timer extended by ' + cb.settings.auction_anti_snipe + 's', '', '', '#ff8800', ''); } var timeLeft = Math.ceil((auction.endTime - now()) / 1000); cb.sendNotice( '>> BID: ' + user + ' bids ' + amount + ' on "' + itemName + '"' + (prevBidder ? ' (outbidding ' + prevBidder + ')' : '') + ' | Next min: ' + (amount + cb.settings.auction_increment) + ' | ' + timeLeft + 's left', '', '', '#2d8633', 'bold' ); cb.drawPanel(); return true; } } return false; } // =================================================================== // MODULE: KING OF THE HILL // =================================================================== var kothStreaks = {}; var kothTimers = {}; var king = null; var reignCount = 0; function kothCheckStreak(user) { if (!kothStreaks[user]) return; var elapsed = now() - kothStreaks[user].lastTip; var window = cb.settings.koth_window * 60 * 1000; if (elapsed >= window) { var s = kothStreaks[user]; var wasKing = king && king.user === user; if (s.count >= cb.settings.koth_min_streak) { if (wasKing) { cb.sendNotice( '>> The King\'s streak has ended! ' + user + ' held the crown with ' + s.streakTotal + ' tokens over ' + s.count + ' tips. The throne is OPEN!', '', '', '#ff4444', 'bold' ); } else { cb.sendNotice(user + '\'s streak of ' + s.count + ' tips (' + s.streakTotal + ' tokens) has ended.', '', '', '#ff4444', ''); } } s.count = 0; s.streakTotal = 0; if (wasKing) { king = null; reignCount = 0; kothFindNewKing(); } } } function kothFindNewKing() { var bestUser = null; var bestTotal = 0; var minCount = cb.settings.koth_min_streak; for (var user in kothStreaks) { if (kothStreaks.hasOwnProperty(user) && kothStreaks[user].count >= minCount && kothStreaks[user].streakTotal > bestTotal) { bestTotal = kothStreaks[user].streakTotal; bestUser = user; } } if (bestUser) { king = { user: bestUser, streakTotal: bestTotal }; reignCount = 0; cb.sendNotice('>> ' + bestUser + ' inherits the crown! Streak: ' + kothStreaks[bestUser].count + ' tips (' + bestTotal + ' tokens)', '', '', '#ffd700', 'bold'); cb.drawPanel(); } } function kothScheduleCheck(user) { if (kothTimers[user]) cb.cancelTimeout(kothTimers[user]); var window = cb.settings.koth_window * 60 * 1000; kothTimers[user] = cb.setTimeout(function() { kothCheckStreak(user); cb.drawPanel(); }, window + 1000); } function handleKothTip(user, amount) { if (amount < cb.settings.koth_min_tip) return; if (!kothStreaks[user]) kothStreaks[user] = { count: 0, lastTip: 0, streakTotal: 0, bestTotal: 0 }; var elapsed = now() - kothStreaks[user].lastTip; var window = cb.settings.koth_window * 60 * 1000; if (kothStreaks[user].count === 0 || elapsed < window) { kothStreaks[user].count++; kothStreaks[user].streakTotal += amount; } else { kothStreaks[user].count = 1; kothStreaks[user].streakTotal = amount; } kothStreaks[user].lastTip = now(); if (kothStreaks[user].streakTotal > kothStreaks[user].bestTotal) kothStreaks[user].bestTotal = kothStreaks[user].streakTotal; var s = kothStreaks[user]; if (s.count >= cb.settings.koth_min_streak) { if (!king) { king = { user: user, streakTotal: s.streakTotal }; reignCount = 0; cb.sendNotice('>> ' + user + ' claims the crown! ' + s.count + ' tips (' + s.streakTotal + ' tokens). Dethrone them!', '', '', '#ffd700', 'bold'); } else if (king.user === user) { king.streakTotal = s.streakTotal; } else if (s.streakTotal > king.streakTotal) { var oldKing = king.user; var oldTotal = king.streakTotal; king = { user: user, streakTotal: s.streakTotal }; reignCount = 0; cb.sendNotice('>> DETHRONED! ' + user + ' (' + s.streakTotal + ' tokens) overtakes ' + oldKing + ' (' + oldTotal + ')!', '', '', '#ff4444', 'bold'); cb.sendNotice('>> ' + user + ' is the new King of the Hill!', '', '', '#ffd700', 'bold'); } else { var rem = king.streakTotal - s.streakTotal; var cMsg = user + ' challenging! ' + s.streakTotal + '/' + king.streakTotal + ' (' + rem + ' more to dethrone ' + king.user + ')'; if (cb.settings.koth_show_challengers === 'yes') { cb.sendNotice(cMsg, '', '', '#ffaa00', ''); } else { cb.sendNotice(cMsg, user, '', '#ffaa00', ''); } } } else if (s.count > 0) { var needed = cb.settings.koth_min_streak - s.count; cb.sendNotice('Streak started! ' + needed + ' more tip' + (needed === 1 ? '' : 's') + ' to qualify. Tip within ' + cb.settings.koth_window + 'min!', user, '', '#888888', ''); } kothScheduleCheck(user); } function kothGetLeaderboard() { var sorted = []; for (var user in kothStreaks) { if (kothStreaks.hasOwnProperty(user) && kothStreaks[user].bestTotal > 0) sorted.push({ user: user, data: kothStreaks[user] }); } sorted.sort(function(a, b) { return b.data.bestTotal - a.data.bestTotal; }); if (sorted.length === 0) return 'No one has claimed the hill yet!'; var lines = ['--- KING OF THE HILL ---']; if (king) { lines.push('Current King: ' + king.user + ' (' + king.streakTotal + ' tokens)'); lines.push('---'); } var max = Math.min(sorted.length, 5); for (var i = 0; i < max; i++) { var s = sorted[i]; var isKing = king && king.user === s.user; var status = s.data.count >= cb.settings.koth_min_streak ? 'ACTIVE (' + s.data.count + ' tips, ' + s.data.streakTotal + ' tokens)' : 'idle'; lines.push('#' + (i + 1) + ' ' + (isKing ? '[K] ' : '') + s.user + ' - Best: ' + s.data.bestTotal + ' | ' + status); } return lines.join('\n'); } // =================================================================== // MODULE: TIP GOALS // =================================================================== var goals = []; var goalTotal = 0; var currentGoalIndex = 0; function initGoals() { goals = parseItemString(cb.settings.goal_items); goals.sort(function(a, b) { return a.price - b.price; }); goalTotal = 0; currentGoalIndex = 0; } function handleGoalTip(amount) { goalTotal += amount; while (currentGoalIndex < goals.length && goalTotal >= goals[currentGoalIndex].price) { var reached = goals[currentGoalIndex]; currentGoalIndex++; cb.sendNotice('>>> GOAL REACHED: ' + reached.name + '! (' + reached.price + ' tokens) <<<', '', '#ffffff', '#2d8633', 'bold'); if (currentGoalIndex < goals.length) { var next = goals[currentGoalIndex]; cb.sendNotice('Next goal: ' + next.name + ' (' + goalTotal + '/' + next.price + ')', '', '', '#2d8633', ''); } else { cb.sendNotice('>>> ALL GOALS REACHED! Thank you everyone! <<<', '', '#ffffff', '#ffd700', 'bold'); } } updateSubject(); } function getGoalProgressText() { if (goals.length === 0) return 'No goals configured.'; var lines = ['--- TIP GOALS --- Session total: ' + goalTotal + ' tokens']; for (var i = 0; i < goals.length; i++) { var g = goals[i]; var done = goalTotal >= g.price; var marker = done ? '[X]' : (i === currentGoalIndex ? '[>]' : '[ ]'); lines.push(marker + ' ' + g.name + ' (' + (done ? g.price + '/' + g.price : goalTotal + '/' + g.price) + ')'); } if (currentGoalIndex < goals.length) { lines.push('--- ' + (goals[currentGoalIndex].price - goalTotal) + ' tokens to next goal! ---'); } else { lines.push('--- All goals reached! ---'); } return lines.join('\n'); } // =================================================================== // MODULE: TOP TIPPER LEADERBOARD // =================================================================== var tipTotals = {}; function handleLeaderboardTip(user, amount) { tipTotals[user] = (tipTotals[user] || 0) + amount; } function getTopTippers(n) { var sorted = []; for (var user in tipTotals) { if (tipTotals.hasOwnProperty(user)) sorted.push({ user: user, total: tipTotals[user] }); } sorted.sort(function(a, b) { return b.total - a.total; }); return sorted.slice(0, n); } function getLeaderboardText() { var top = getTopTippers(cb.settings.leaderboard_size); if (top.length === 0) return 'No tips yet! Be the first tipper!'; var lines = ['--- TOP TIPPERS ---']; for (var i = 0; i < top.length; i++) { lines.push('#' + (i + 1) + ' ' + top[i].user + ' - ' + top[i].total + ' tokens'); } return lines.join('\n'); } // =================================================================== // MODULE: TIP THANK-YOU // =================================================================== var thankyouTiers = []; function initThankYou() { thankyouTiers = parseItemString(cb.settings.thankyou_tiers); thankyouTiers.sort(function(a, b) { return b.price - a.price; }); } function handleThankYou(user, amount) { if (thankyouTiers.length === 0) return; var match = null; var matchIdx = -1; for (var i = 0; i < thankyouTiers.length; i++) { if (amount >= thankyouTiers[i].price) { match = thankyouTiers[i]; matchIdx = i; break; } } if (!match) return; var msg = match.name.split('{user}').join(user).split('{amount}').join('' + amount); var colors = ['#ff8000', '#0070dd', '#1eff00', '#aaaaaa']; var quartile = Math.min(3, Math.floor((matchIdx / thankyouTiers.length) * 4)); cb.sendNotice(msg, '', '', colors[quartile], 'bold'); } // =================================================================== // MODULE: LOVENSE INTEGRATION // =================================================================== var lovenseLevels = []; function initLovense() { lovenseLevels = parseItemString(cb.settings.lovense_levels); lovenseLevels.sort(function(a, b) { return a.price - b.price; }); } function findLovenseLevel(amount) { var match = null; for (var i = 0; i < lovenseLevels.length; i++) { if (amount >= lovenseLevels[i].price) match = lovenseLevels[i]; else break; } return match; } function handleLovenseTip(user, amount) { var level = findLovenseLevel(amount); if (!level) return; cb.sendNotice('>> LOVENSE >> ' + user + ' tipped ' + amount + ' -- ' + level.name + '!', '', '', '#ff69b4', 'bold'); cb.sendNotice('Lovense: ' + user + ' tipped ' + amount + ' -- ' + level.name, cb.room_slug, '#ffffff', '#333333', 'bold'); } function getLovenseLevelsText() { if (lovenseLevels.length === 0) return 'No Lovense levels configured.'; var lines = ['--- LOVENSE LEVELS ---']; for (var i = 0; i < lovenseLevels.length; i++) { lines.push(' ' + lovenseLevels[i].price + '+ tokens - ' + lovenseLevels[i].name); } lines.push('Tip to activate!'); return lines.join('\n'); } // =================================================================== // MODULE: TICKET SHOW // =================================================================== var ticketHolders = {}; // { username: true } var ticketsSold = 0; var ticketShowActive = false; var ticketShowStartTime = 0; function getTicketPrice() { return cb.settings.ticket_price; } function hasTicket(user) { return !!ticketHolders[user]; } function grantTicket(user) { if (ticketHolders[user]) return false; ticketHolders[user] = true; ticketsSold++; if (cb.limitCam_isRunning()) { cb.limitCam_addUsers([user]); } return true; } function revokeTicket(user) { if (!ticketHolders[user]) return false; delete ticketHolders[user]; if (cb.limitCam_isRunning()) { cb.limitCam_removeUsers([user]); } return true; } function getTicketHolderList() { var list = []; for (var user in ticketHolders) { if (ticketHolders.hasOwnProperty(user)) list.push(user); } return list; } function startTicketShow() { if (ticketShowActive) return false; var minSold = cb.settings.ticket_min_sold; if (minSold > 0 && ticketsSold < minSold) return false; ticketShowActive = true; ticketShowStartTime = now(); var hiddenMsg = cb.settings.ticket_hidden_msg.split('{price}').join('' + getTicketPrice()); var holders = getTicketHolderList(); // Always include broadcaster if (holders.indexOf(cb.room_slug) === -1) holders.push(cb.room_slug); cb.limitCam_start(hiddenMsg, holders); cb.sendNotice( '>>> HIDDEN SHOW STARTED! <<<\n' + ticketsSold + ' ticket holders have access. Tip ' + getTicketPrice() + ' for a ticket!', '', '#ffffff', '#6441a5', 'bold' ); cb.sendNotice( 'Hidden show started with ' + ticketsSold + ' tickets sold.', cb.room_slug, '#ffffff', '#333333', 'bold' ); cb.drawPanel(); return true; } function stopTicketShow() { if (!ticketShowActive) return false; ticketShowActive = false; cb.limitCam_stop(); var duration = Math.floor((now() - ticketShowStartTime) / 60000); var revenue = ticketsSold * getTicketPrice(); cb.sendNotice( '>>> HIDDEN SHOW ENDED! <<<\n' + 'Duration: ' + duration + ' min | Tickets sold: ' + ticketsSold + ' | Revenue: ' + revenue + ' tokens', '', '#ffffff', '#6441a5', 'bold' ); cb.sendNotice( 'Show ended. ' + ticketsSold + ' tickets, ' + revenue + ' tokens revenue, ' + duration + ' min.', cb.room_slug, '#ffffff', '#333333', 'bold' ); cb.drawPanel(); return true; } function handleTicketTip(user, amount) { if (amount !== getTicketPrice()) return false; if (hasTicket(user)) { cb.sendNotice('You already have a ticket!', user, '', '#6441a5', ''); return true; } grantTicket(user); cb.sendNotice( '>> TICKET >> ' + user + ' bought a show ticket! (' + ticketsSold + ' sold)', '', '', '#6441a5', 'bold' ); cb.sendNotice( 'Ticket sold to ' + user + '. Total: ' + ticketsSold, cb.room_slug, '#ffffff', '#333333', '' ); // Auto-notify if minimum reached and show not started var minSold = cb.settings.ticket_min_sold; if (!ticketShowActive && minSold > 0 && ticketsSold === minSold) { cb.sendNotice( 'Minimum tickets reached! (' + minSold + ') Type !startshow to begin the hidden show.', cb.room_slug, '', '#ffd700', 'bold' ); } return true; } function getTicketInfoText() { var price = getTicketPrice(); var lines = ['--- TICKET SHOW ---']; lines.push('Ticket price: ' + price + ' tokens'); lines.push('Tickets sold: ' + ticketsSold); if (ticketShowActive) { var elapsed = Math.floor((now() - ticketShowStartTime) / 60000); lines.push('Status: LIVE (' + elapsed + ' min)'); lines.push('Tip ' + price + ' to join the show!'); } else { var minSold = cb.settings.ticket_min_sold; if (minSold > 0 && ticketsSold < minSold) { lines.push('Status: Selling tickets (' + ticketsSold + '/' + minSold + ' needed)'); } else { lines.push('Status: Ready to start'); } lines.push('Tip ' + price + ' to buy a ticket!'); } return lines.join('\n'); } function getTicketPanel() { if (cb.settings.enable_ticket !== 'on') return null; if (!ticketShowActive && ticketsSold === 0) return null; var price = getTicketPrice(); if (ticketShowActive) { var elapsed = Math.floor((now() - ticketShowStartTime) / 60000); return { 'template': '3_rows_of_labels', 'row1_label': 'HIDDEN SHOW', 'row1_value': 'LIVE (' + elapsed + ' min)', 'row2_label': 'Tickets', 'row2_value': ticketsSold + ' sold (' + (ticketsSold * price) + ' tokens)', 'row3_label': 'Tip ' + price, 'row3_value': 'for a ticket | !ticket' }; } var minSold = cb.settings.ticket_min_sold; var progress = minSold > 0 ? ticketsSold + '/' + minSold : ticketsSold + ' sold'; return { 'template': '3_rows_of_labels', 'row1_label': 'Ticket Show', 'row1_value': price + ' tokens', 'row2_label': 'Tickets', 'row2_value': progress, 'row3_label': 'Tip ' + price, 'row3_value': 'to buy a ticket | !ticket' }; } function getTicketAd() { if (cb.settings.enable_ticket !== 'on') return null; var price = getTicketPrice(); var text; if (ticketShowActive) { text = '--- HIDDEN SHOW LIVE --- Tip ' + price + ' for a ticket! ' + ticketsSold + ' viewers watching. !ticket ---'; } else if (ticketsSold > 0 || cb.settings.ticket_min_sold > 0) { var minSold = cb.settings.ticket_min_sold; text = '--- TICKET SHOW --- Tip ' + price + ' to reserve your ticket! ' + ticketsSold + ' sold' + (minSold > 0 ? ' (' + minSold + ' needed to start)' : '') + '. !ticket ---'; } else { return null; } return { msg: text, bg: '#6441a5' }; } // =================================================================== // MODULE: MODERATION (Gray Silence + Word Filter) // =================================================================== var bannedWords = []; function initWordFilter() { bannedWords = []; var raw = cb.settings.mod_word_filter; if (!raw || raw.trim() === '') return; var parts = raw.split(','); for (var i = 0; i < parts.length; i++) { var word = parts[i].trim().toLowerCase(); if (word !== '') bannedWords.push(word); } } function checkWordFilter(text) { if (bannedWords.length === 0) return null; var lower = text.toLowerCase(); for (var i = 0; i < bannedWords.length; i++) { if (lower.indexOf(bannedWords[i]) !== -1) return bannedWords[i]; } return null; } // =================================================================== // MODULE: ROOM SUBJECT + HASHTAG ROTATION // =================================================================== var hashtagList = []; var hashtagOffset = 0; var hashtagTimer = null; function initHashtags() { hashtagList = []; var raw = cb.settings.hashtag_tags; if (!raw || raw.trim() === '') return; var parts = raw.trim().split(/\s+/); for (var i = 0; i < parts.length; i++) { if (parts[i] !== '') hashtagList.push(parts[i]); } hashtagOffset = 0; } function getHashtags() { if (hashtagList.length === 0) return ''; if (hashtagList.length <= 5) return hashtagList.join(' '); var subset = []; for (var i = 0; i < 5; i++) { subset.push(hashtagList[(hashtagOffset + i) % hashtagList.length]); } return subset.join(' '); } function updateSubject() { var parts = []; if (cb.settings.enable_goals === 'on' && goals.length > 0) { var prefix = cb.settings.goal_subject_prefix || 'Tipping for:'; if (currentGoalIndex < goals.length) { var g = goals[currentGoalIndex]; parts.push(prefix + ' ' + g.name + ' [' + goalTotal + '/' + g.price + ']'); } else { parts.push(prefix + ' All goals reached!'); } } if (cb.settings.enable_hashtags === 'on') { var tags = getHashtags(); if (tags !== '') parts.push(tags); } if (parts.length > 0) cb.changeRoomSubject(parts.join(' | ')); } function rotateHashtags() { if (cb.settings.enable_hashtags !== 'on' || hashtagList.length <= 5) return; hashtagOffset = (hashtagOffset + 1) % hashtagList.length; updateSubject(); hashtagTimer = cb.setTimeout(rotateHashtags, cb.settings.hashtag_interval * 60 * 1000); } function startHashtagTimer() { if (hashtagTimer) cb.cancelTimeout(hashtagTimer); hashtagTimer = null; if (cb.settings.enable_hashtags !== 'on' || hashtagList.length <= 5) return; hashtagTimer = cb.setTimeout(rotateHashtags, cb.settings.hashtag_interval * 60 * 1000); } // =================================================================== // TIP HANDLER — Central Router // =================================================================== cb.onTip(function(tip) { var user = tip.from_user; var amount = parseInt(tip.amount); // --- Parallel modules (always fire) --- if (cb.settings.enable_koth === 'on') handleKothTip(user, amount); if (cb.settings.enable_goals === 'on') handleGoalTip(amount); if (cb.settings.enable_leaderboard === 'on') handleLeaderboardTip(user, amount); if (cb.settings.enable_thankyou === 'on') handleThankYou(user, amount); if (cb.settings.enable_lovense === 'on') handleLovenseTip(user, amount); // --- Exclusive modules (first match wins) --- if (cb.settings.enable_ticket === 'on') { if (handleTicketTip(user, amount)) { cb.drawPanel(); return; } } if (cb.settings.enable_menu === 'on') { if (handleMenuTip(user, amount, tip.message)) { cb.drawPanel(); return; } } if (cb.settings.enable_mystery === 'on') { if (handleMysteryTip(user, amount)) { cb.drawPanel(); return; } } if (cb.settings.enable_auction === 'on') { handleAuctionTip(user, amount); } cb.drawPanel(); }); // =================================================================== // MESSAGE HANDLER — Moderation + Command Router // =================================================================== cb.onMessage(function(msg) { var user = msg.user; var isBroadcaster = (user === cb.room_slug); var isMod = msg.is_mod; // --- Moderation (runs first, can suppress message) --- if (cb.settings.enable_moderation === 'on') { // Gray user silencing if (cb.settings.mod_silence_gray === 'yes' && !msg.has_tokens && !isBroadcaster && !isMod) { cb.sendNotice(cb.settings.mod_gray_msg, user, '', '#cc0000', 'bold'); msg['X-Spam'] = true; return msg; } // Word filter if (!isBroadcaster && !isMod) { var matched = checkWordFilter(msg.m); if (matched) { msg['X-Spam'] = true; cb.sendNotice('Your message was blocked (contains filtered word).', user, '', '#cc0000', ''); cb.sendNotice('[FILTER] ' + user + '\'s message blocked: \'' + matched + '\'', cb.room_slug, '', '#cc0000', 'bold'); return msg; } } // Broadcaster filter commands if (isBroadcaster) { var cmdRaw = msg.m.trim(); var cmdLow = cmdRaw.toLowerCase(); if (cmdLow === '!filter') { var display = bannedWords.length === 0 ? '[FILTER] No banned words.' : '[FILTER] (' + bannedWords.length + '): ' + bannedWords.join(', '); cb.sendNotice(display, cb.room_slug, '', '#333333', ''); msg['X-Spam'] = true; } else if (cmdLow.indexOf('!filter add ') === 0) { var w = cmdRaw.substring(12).trim().toLowerCase(); if (w !== '') { var exists = false; for (var i = 0; i < bannedWords.length; i++) { if (bannedWords[i] === w) { exists = true; break; } } if (exists) { cb.sendNotice('[FILTER] "' + w + '" already in filter.', cb.room_slug, '', '#cc0000', ''); } else { bannedWords.push(w); cb.sendNotice('[FILTER] Added "' + w + '". (' + bannedWords.length + ' total)', cb.room_slug, '', '#2d8633', 'bold'); } } msg['X-Spam'] = true; } else if (cmdLow.indexOf('!filter remove ') === 0) { var w = cmdRaw.substring(15).trim().toLowerCase(); var newList = []; var found = false; for (var i = 0; i < bannedWords.length; i++) { if (bannedWords[i] === w) { found = true; } else { newList.push(bannedWords[i]); } } if (found) { bannedWords = newList; cb.sendNotice('[FILTER] Removed "' + w + '". (' + bannedWords.length + ' total)', cb.room_slug, '', '#2d8633', 'bold'); } else { cb.sendNotice('[FILTER] "' + w + '" not found.', cb.room_slug, '', '#cc0000', ''); } msg['X-Spam'] = true; } } } var cmd = msg.m.toLowerCase().trim(); // --- Tip Menu --- if (cb.settings.enable_menu === 'on' && (cmd === '!menu' || cmd === '!tipmenu')) { cb.sendNotice(getMenuText(), user); msg['X-Spam'] = true; } // --- Mystery Box --- if (cb.settings.enable_mystery === 'on') { if (cmd === '!mb' || cmd === '!mysterybox') { cb.sendNotice('Mystery Box: Tip ' + cb.settings.mystery_cost + ' tokens for a random reward!\nRarities: Common | Uncommon | Rare | Legendary', user, '#ffffff', '#333333', ''); msg['X-Spam'] = true; } if (cmd === '!collection') { if (!mysteryCollections[user]) { cb.sendNotice('No mystery boxes opened yet! Tip ' + cb.settings.mystery_cost + ' to try.', user); } else { var lines = ['Your Collection:']; for (var i = 0; i < mysteryRewards.length; i++) { var r = mysteryRewards[i]; var ct = mysteryCollections[user][r.name] || 0; lines.push(' ' + r.rarity.label + ' - ' + r.name + ': ' + (ct > 0 ? 'x' + ct : '???')); } cb.sendNotice(lines.join('\n'), user); } msg['X-Spam'] = true; } } // --- Ticket Show --- if (cb.settings.enable_ticket === 'on') { if (cmd === '!ticket' || cmd === '!tickets' || cmd === '!show') { cb.sendNotice(getTicketInfoText(), user); msg['X-Spam'] = true; } if (isBroadcaster) { if (cmd === '!startshow') { var minSold = cb.settings.ticket_min_sold; if (ticketShowActive) { cb.sendNotice('Show is already running!', cb.room_slug, '', '#cc0000', ''); } else if (minSold > 0 && ticketsSold < minSold) { cb.sendNotice('Need ' + minSold + ' tickets sold first (' + ticketsSold + ' so far).', cb.room_slug, '', '#cc0000', ''); } else { startTicketShow(); } msg['X-Spam'] = true; } if (cmd === '!endshow' || cmd === '!stopshow') { if (ticketShowActive) { stopTicketShow(); } else { cb.sendNotice('No show is running.', cb.room_slug, '', '#cc0000', ''); } msg['X-Spam'] = true; } if (cmd === '!ticketlist') { var holders = getTicketHolderList(); if (holders.length === 0) { cb.sendNotice('[TICKETS] No tickets sold yet.', cb.room_slug, '', '#333333', ''); } else { cb.sendNotice('[TICKETS] ' + holders.length + ' holders: ' + holders.join(', '), cb.room_slug, '', '#333333', ''); } msg['X-Spam'] = true; } if (cmd.indexOf('!grantticket ') === 0) { var targetUser = msg.m.substring(13).trim(); if (targetUser !== '') { if (grantTicket(targetUser)) { cb.sendNotice('[TICKETS] Granted free ticket to ' + targetUser + '.', cb.room_slug, '', '#2d8633', 'bold'); } else { cb.sendNotice('[TICKETS] ' + targetUser + ' already has a ticket.', cb.room_slug, '', '#cc0000', ''); } } msg['X-Spam'] = true; } if (cmd.indexOf('!revoketicket ') === 0) { var targetUser = msg.m.substring(14).trim(); if (targetUser !== '') { if (revokeTicket(targetUser)) { cb.sendNotice('[TICKETS] Revoked ticket from ' + targetUser + '.', cb.room_slug, '', '#cc0000', 'bold'); } else { cb.sendNotice('[TICKETS] ' + targetUser + ' does not have a ticket.', cb.room_slug, '', '#cc0000', ''); } } msg['X-Spam'] = true; } } } // --- Auction --- if (cb.settings.enable_auction === 'on') { if (isBroadcaster) { if (cmd.indexOf('!auction ') === 0) { var itemName = msg.m.substring(9).trim(); if (itemName) startAuction(itemName); msg['X-Spam'] = true; } if (cmd === '!endauction') { var active = getActiveAuctions(); for (var i = 0; i < active.length; i++) endAuction(active[i]); msg['X-Spam'] = true; } } if (cmd === '!bid' || cmd === '!bids') { var active = getActiveAuctions(); if (active.length === 0) { cb.sendNotice('No active auctions. Broadcaster: !auction [item name]', user); } else { var lines = ['--- ACTIVE AUCTIONS ---']; for (var i = 0; i < active.length; i++) { var item = active[i]; var a = auctions[item]; var tl = Math.ceil((a.endTime - now()) / 1000); lines.push('"' + item + '" - ' + (a.highBidder ? a.highBid + ' by ' + a.highBidder : 'No bids') + ' | Min: ' + getMinBid(item) + ' | ' + tl + 's'); } cb.sendNotice(lines.join('\n'), user); } msg['X-Spam'] = true; } } // --- KOTH --- if (cb.settings.enable_koth === 'on') { if (cmd === '!king') { if (!king) { cb.sendNotice('No King yet! Tip ' + cb.settings.koth_min_streak + '+ times within ' + cb.settings.koth_window + 'min to claim the crown.', user); } else { var ks = kothStreaks[king.user]; cb.sendNotice('Current King: ' + king.user + ' | ' + ks.count + ' tips (' + king.streakTotal + ' tokens) | Need ' + (king.streakTotal + 1) + '+ to dethrone!', user); } msg['X-Spam'] = true; } if (cmd === '!mystats') { var s = kothStreaks[user]; if (s && s.count > 0) { kothCheckStreak(user); var info = 'Your streak: ' + s.count + ' tips (' + s.streakTotal + ' tokens) | Best: ' + s.bestTotal; if (king) info += king.user === user ? ' | You are the King!' : ' | ' + (king.streakTotal - s.streakTotal) + ' behind crown'; cb.sendNotice(info, user); } else { cb.sendNotice('No active streak. Tip ' + cb.settings.koth_min_tip + '+ to start!', user); } msg['X-Spam'] = true; } if (cmd === '!hill' || cmd === '!leaderboard') { cb.sendNotice(kothGetLeaderboard(), user); msg['X-Spam'] = true; } } // --- Goals --- if (cb.settings.enable_goals === 'on' && (cmd === '!goal' || cmd === '!goals')) { cb.sendNotice(getGoalProgressText(), user); msg['X-Spam'] = true; } // --- Leaderboard --- if (cb.settings.enable_leaderboard === 'on' && (cmd === '!top' || cmd === '!tippers')) { cb.sendNotice(getLeaderboardText(), user); msg['X-Spam'] = true; } // --- Lovense --- if (cb.settings.enable_lovense === 'on' && (cmd === '!levels' || cmd === '!lovense')) { cb.sendNotice(getLovenseLevelsText(), user); msg['X-Spam'] = true; } // --- Help --- if (cmd === '!help' || cmd === '!commands') { var h = ['--- ALL-IN-ONE SUITE ---']; if (cb.settings.enable_menu === 'on') h.push('!menu - Tip menu'); if (cb.settings.enable_mystery === 'on') h.push('!mb - Mystery box | !collection - Your rewards'); if (cb.settings.enable_auction === 'on') h.push('!bid - Active auctions'); if (cb.settings.enable_koth === 'on') h.push('!king - Current king | !mystats - Your streak | !hill - Leaderboard'); if (cb.settings.enable_goals === 'on') h.push('!goal - Tip goal progress'); if (cb.settings.enable_leaderboard === 'on') h.push('!top - Top tippers'); if (cb.settings.enable_lovense === 'on') h.push('!levels - Lovense levels'); if (cb.settings.enable_ticket === 'on') h.push('!ticket - Show ticket info'); cb.sendNotice(h.join('\n'), user); msg['X-Spam'] = true; } // --- Chat decoration: KOTH badges --- if (cb.settings.enable_koth === 'on') { if (king && king.user === user) { msg.m = '[K] ' + msg.m; } else if (kothStreaks[user] && kothStreaks[user].count >= cb.settings.koth_min_streak) { msg.m = '[~] ' + msg.m; } } return msg; }); // =================================================================== // ENTER HANDLER — Welcome Messages + Whale Detection // =================================================================== cb.onEnter(function(user) { // Welcome message if (cb.settings.enable_welcome === 'on') { var msg = cb.settings.welcome_msg.split('{user}').join(user.user).split('{room}').join(cb.room_slug); cb.sendNotice(msg, user.user, '', '#6441a5', ''); if (cb.settings.welcome_show_menu === 'yes' && cb.settings.enable_menu === 'on' && menuItems.length > 0) { cb.sendNotice(getMenuText(), user.user, '', '#2d8633', ''); } } // Ticket show: auto-grant to mods/fanclub on enter if (cb.settings.enable_ticket === 'on') { if (cb.settings.ticket_mods_free === 'yes' && user.is_mod && !hasTicket(user.user)) { grantTicket(user.user); cb.sendNotice('You have a free ticket as a moderator!', user.user, '', '#6441a5', ''); } if (cb.settings.ticket_fanclub === 'yes' && user.in_fanclub && !hasTicket(user.user)) { grantTicket(user.user); cb.sendNotice('You have a free ticket as a fan club member!', user.user, '', '#6441a5', ''); } } // Whale detection if (cb.settings.enable_whale === 'on') { var tier = null; if (user.tipped_tons_recently) { tier = 'Whale'; } else if (user.tipped_alot_recently) { tier = 'Big Spender'; } if (tier) { var alert = '>> [WHALE ALERT] ' + user.user + ' (' + tier + ') just entered!'; if (cb.settings.whale_notify === 'broadcaster only') { cb.sendNotice(alert, cb.room_slug, '', '#ffd700', 'bold'); } else { cb.sendNotice(alert, '', '', '#ffd700', 'bold'); } } } }); // =================================================================== // FOLLOW HANDLER — Follower Notifications // =================================================================== cb.onFollow(function(user) { if (cb.settings.enable_followers === 'on') { var msg = cb.settings.follower_msg.split('{user}').join(user.user); cb.sendNotice(msg, '', '', '#1eff00', ''); } }); // =================================================================== // PANEL — Priority: Ticket > Auction > Goals > KOTH > Leaderboard > Mystery > Lovense > Menu // =================================================================== cb.onDrawPanel(function() { // 0. Ticket show (highest priority when active or selling) var ticketPanel = getTicketPanel(); if (ticketPanel) return ticketPanel; // 1. Active auction if (cb.settings.enable_auction === 'on') { var active = getActiveAuctions(); if (active.length > 0) { var item = active[0]; var a = auctions[item]; var tl = Math.max(0, Math.ceil((a.endTime - now()) / 1000)); return { 'template': '3_rows_of_labels', 'row1_label': 'AUCTION', 'row1_value': item, 'row2_label': 'High Bid', 'row2_value': a.highBidder ? a.highBid + ' (' + a.highBidder + ')' : 'None yet', 'row3_label': 'Time Left', 'row3_value': tl + 's | !bid' }; } } // 2. Goals progress if (cb.settings.enable_goals === 'on' && goals.length > 0) { var goalName, progressText, pct; if (currentGoalIndex < goals.length) { var g = goals[currentGoalIndex]; goalName = g.name; progressText = goalTotal + '/' + g.price; pct = Math.min(100, Math.floor((goalTotal / g.price) * 100)) + '%'; } else { goalName = 'All goals reached!'; progressText = goalTotal + ' total'; pct = '100%'; } return { 'template': '3_rows_of_labels', 'row1_label': 'Tip Goal', 'row1_value': goalName, 'row2_label': 'Progress', 'row2_value': progressText + ' (' + pct + ')', 'row3_label': 'Type !goal', 'row3_value': 'for full progress' }; } // 3. KOTH king if (cb.settings.enable_koth === 'on' && king) { var topCh = ''; var topChTotal = 0; for (var u in kothStreaks) { if (kothStreaks.hasOwnProperty(u) && king.user !== u && kothStreaks[u].count >= cb.settings.koth_min_streak && kothStreaks[u].streakTotal > topChTotal) { topChTotal = kothStreaks[u].streakTotal; topCh = u; } } return { 'template': '3_rows_of_labels', 'row1_label': 'King of the Hill', 'row1_value': king.user + ' (' + king.streakTotal + ')', 'row2_label': 'Challenger', 'row2_value': topCh ? topCh + ' (' + topChTotal + ')' : 'None', 'row3_label': 'Commands', 'row3_value': '!king | !mystats | !help' }; } // 4. Leaderboard if (cb.settings.enable_leaderboard === 'on') { var top3 = getTopTippers(3); if (top3.length > 0) { var r1 = '#1 ' + top3[0].user + ' (' + top3[0].total + ')'; var r2parts = []; if (top3.length > 1) r2parts.push('#2 ' + top3[1].user + ' (' + top3[1].total + ')'); if (top3.length > 2) r2parts.push('#3 ' + top3[2].user + ' (' + top3[2].total + ')'); return { 'template': '3_rows_of_labels', 'row1_label': 'Top Tipper', 'row1_value': r1, 'row2_label': '', 'row2_value': r2parts.join(' | '), 'row3_label': '', 'row3_value': '!top for full list' }; } } // 5. Mystery box if (cb.settings.enable_mystery === 'on' && totalBoxes > 0) { return { 'template': '3_rows_of_labels', 'row1_label': 'Mystery Box', 'row1_value': cb.settings.mystery_cost + ' tokens', 'row2_label': 'Boxes Opened', 'row2_value': '' + totalBoxes, 'row3_label': 'Type !mb', 'row3_value': '!help for all commands' }; } // 6. Lovense levels if (cb.settings.enable_lovense === 'on' && lovenseLevels.length > 0) { var lParts = []; for (var i = 0; i < lovenseLevels.length; i++) { lParts.push(lovenseLevels[i].price + '=' + lovenseLevels[i].name); } var lSummary = lParts.join(' | '); if (lSummary.length > 60) lSummary = lSummary.substring(0, 57) + '...'; return { 'template': '3_rows_of_labels', 'row1_label': 'Lovense', 'row1_value': 'Tip to activate!', 'row2_label': 'Levels', 'row2_value': lSummary, 'row3_label': '', 'row3_value': '!levels for details' }; } // 7. Tip menu if (cb.settings.enable_menu === 'on' && menuItems.length > 0) { var summary = ''; var shown = 0; for (var i = 0; i < menuItems.length && summary.length < 60; i++) { if (i > 0) summary += ' | '; summary += menuItems[i].price + '=' + menuItems[i].name; shown++; } if (shown < menuItems.length) summary += ' +' + (menuItems.length - shown) + ' more'; return { 'template': '3_rows_of_labels', 'row1_label': 'Tip Menu', 'row1_value': summary, 'row2_label': 'Fulfilled', 'row2_value': menuFulfilled + ' requests', 'row3_label': 'Type !menu', 'row3_value': '!help for all commands' }; } // Fallback return { 'template': '3_rows_of_labels', 'row1_label': 'All-In-One Suite', 'row1_value': 'Active', 'row2_label': '', 'row2_value': '', 'row3_label': 'Type !help', 'row3_value': 'for available commands' }; }); // =================================================================== // ROTATING ADVERTISEMENTS // =================================================================== var adRotation = 0; function advertise() { if (cb.settings.chat_interval <= 0) return; var ads = []; // Menu ad if (cb.settings.enable_menu === 'on' && menuItems.length > 0) { var menuAd = '--- TIP MENU (' + menuItems.length + ' items) --- '; var adLen = menuAd.length; for (var i = 0; i < menuItems.length; i++) { var entry = (i > 0 ? ' | ' : '') + menuItems[i].price + '=' + menuItems[i].name; if (adLen + entry.length > 250) { menuAd += ' +more'; break; } menuAd += entry; adLen += entry.length; } menuAd += ' | !menu ---'; ads.push({ msg: menuAd, bg: '#2d8633' }); } // Mystery ad if (cb.settings.enable_mystery === 'on') { ads.push({ msg: '--- MYSTERY BOX --- Tip ' + cb.settings.mystery_cost + ' for a random reward! Common > Uncommon > Rare > Legendary. !mb ---', bg: '#6441a5' }); } // Auction ad if (cb.settings.enable_auction === 'on') { var active = getActiveAuctions(); if (active.length > 0) { var item = active[0]; var a = auctions[item]; var tl = Math.ceil((a.endTime - now()) / 1000); ads.push({ msg: '--- AUCTION --- "' + item + '" | ' + (a.highBidder ? a.highBid + ' by ' + a.highBidder : 'No bids') + ' | ' + tl + 's left! !bid ---', bg: '#cc7700' }); } } // KOTH ad if (cb.settings.enable_koth === 'on') { var kAd = '--- KING OF THE HILL --- '; if (king) { kAd += 'Crown: ' + king.user + ' (' + king.streakTotal + '). Build ' + (king.streakTotal + 1) + '+ to dethrone!'; } else { kAd += 'No King yet! Tip ' + cb.settings.koth_min_streak + '+ times to claim!'; } kAd += ' !king ---'; ads.push({ msg: kAd, bg: '#b8860b' }); } // Goals ad if (cb.settings.enable_goals === 'on' && goals.length > 0) { var gAd; if (currentGoalIndex < goals.length) { var g = goals[currentGoalIndex]; var pct = Math.min(100, Math.floor((goalTotal / g.price) * 100)); gAd = '--- TIP GOAL --- ' + g.name + ' ' + goalTotal + '/' + g.price + ' (' + pct + '%) Keep tipping! !goal ---'; } else { gAd = '--- TIP GOAL --- All goals reached! (' + goalTotal + ' total) ---'; } ads.push({ msg: gAd, bg: '#2d8633' }); } // Leaderboard ad if (cb.settings.enable_leaderboard === 'on') { var top = getTopTippers(3); if (top.length > 0) { var lAd = '--- TOP TIPPERS --- '; for (var i = 0; i < top.length; i++) { if (i > 0) lAd += ' | '; lAd += '#' + (i + 1) + ': ' + top[i].user + ' (' + top[i].total + ')'; } lAd += ' | !top ---'; ads.push({ msg: lAd, bg: '#2d8633' }); } } // Ticket show ad var ticketAd = getTicketAd(); if (ticketAd) ads.push(ticketAd); // Lovense ad if (cb.settings.enable_lovense === 'on' && lovenseLevels.length > 0) { var lvAd = '--- LOVENSE --- Tip to activate! '; for (var i = 0; i < lovenseLevels.length; i++) { if (i > 0) lvAd += ' | '; lvAd += lovenseLevels[i].price + '=' + lovenseLevels[i].name; } lvAd += ' | !levels ---'; ads.push({ msg: lvAd, bg: '#ff69b4' }); } if (ads.length > 0) { var ad = ads[adRotation % ads.length]; cb.sendNotice(ad.msg, '', '#ffffff', ad.bg, 'bold'); adRotation++; } cb.setTimeout(advertise, cb.settings.chat_interval * 60 * 1000); } // Menu auto-repeat (independent of ad rotation) function menuRepeat() { if (cb.settings.enable_menu === 'on' && cb.settings.menu_repeat > 0 && menuItems.length > 0) { cb.sendNotice(getMenuText(), '', '#ffffff', '#2d8633', ''); cb.setTimeout(menuRepeat, cb.settings.menu_repeat * 60 * 1000); } } // =================================================================== // STARTUP // =================================================================== cb.onStart(function() { initMenu(); initMystery(); if (cb.settings.enable_goals === 'on') initGoals(); initThankYou(); if (cb.settings.enable_lovense === 'on') initLovense(); initWordFilter(); initHashtags(); // Auto-start auctions if (cb.settings.enable_auction === 'on') { var items = [cb.settings.auction_item_1, cb.settings.auction_item_2]; for (var i = 0; i < items.length; i++) { if (items[i] && items[i].trim() !== '') startAuction(items[i].trim()); } } // Set initial subject updateSubject(); startHashtagTimer(); // Start ad/menu timers advertise(); menuRepeat(); });
© Copyright Chaturbate 2011- 2026. All Rights Reserved.