Apps Home
|
Create an App
EatAssBot
Author:
eatass69
Description
Source Code
Launch App
Current Users
Created by:
Eatass69
// Establish the constants that control the app. const menuSize = 20 const leaderboardSize = 10 const recentSize = 20 const menuFrequency = 300 // seconds between each automatic menu print // Establish the colors that are used by the app. const stdGrey = "#CCCCCC" // system messages const stdPink = "#EF42F5" // menu const stdGreen = "#39FC2B" // tip received const stdBlue = "#36A1FF" // recent menu purchases const stdGold = "#E6BC05" // leaderboard const stdBrick = "#946C82" // help message const stdAqua = "#0CF2A6" // remind message // Create the stateful objects. There's a menu which tracks everything that // users can buy from the model with tokens. There's a list of recent purchases // that were purchased from the menu. And there's an array that contains all // historic tips, so that a leaderboard can be constructed. The historic tips // are kept as an array so that future functionality can be added without too // much trouble later. const menu = [] const recentPurchases = [] const allTips = [] // This is here to silence debuggers that might complain that 'cb' is not // defined. var cb = cb || {} // Establish the settings for the app. The menu line items are generated // programatically. cb.settings_choices = [ { name: "let_mods_update_menu", type: "choice", choice1: "no", choice2: "yes", defaultValue: "no", required: "true" }, { name: "start_with_default_menu", type: "choice", choice1: "yes", choice2: "no", defaultValue: "yes", required: "true" }, { name: "menu_item_1", type: "str", minLength: 1, maxLength: 255, defaultValue: "tkns--description of menu item", required: "false" }, { name: "menu_item_2", type: "str", minLength: 1, maxLength: 255, defaultValue: "100-!-example of a disabled item", required: "false" } ] // Add the remaining menu items programatically, since they are all essentially // the same. for (let i = 3; i <= menuSize; i++) { cb.settings_choices.push({ name: "menu_item_"+i.toString(), type: "str", minLength: 1, maxLength: 255, defaultValue: "-!-", required: "false" }) } // Parse the menu settings and create the initial menu. if (cb.settings.start_with_default_menu === "yes") { for (let i = 1; i <= menuSize; i++) { let index = "menu_item_"+i.toString() let priceStr = cb.settings[index].replace(/(^\d+)(.+$)/i,'$1') let price = Number(priceStr) let correctPrefix = cb.settings[index].startsWith(priceStr+("--")) let valid = Number.isInteger(price) && correctPrefix && price > 0 if (valid) { menu.push({ price, description: cb.settings[index].substring(priceStr.length+2) }) } } } // Define a sortMenu function so that we can get everything in order by price // every time there's an update. function sortMenu() { menu.sort((a, b) => { if (a.price < b.price) { return -1 } if (a.price > b.price) { return 1 } return 0 }) } // Call sort on the menu immediately. sortMenu() // isModMessage also considers the room broadcaster to be a mod. function isModMessage(message) { return message.is_mod || message.user === cb.room_slug } // processHelpMessage will display the output for the 'help' command. function processHelpMessage(message, targetAll) { // If targetAll was set, verify that the caller has the ability to blast // out the message. if (targetAll) { if (isModMessage(message) === false) { cb.sendNotice("command may only be used by moderators", message.user, stdGrey) return message } } // Construct the help message. let helpMsg = `This is the TurtleBot, created for Nomie Turtles. (https://chaturbate.com/nomieturtles69) TurtleBot features commands to view a menu, a leaderboard, and recent menu purchases. The menu can be updated mid-broadcast. See the full list of commands below: For all users: /help - display this help message /menu - display the menu /leaderboard - display the tip leaderboard /recent - display all recent purchases /remind - display purchases you've made (this is shown to everyone) For moderators / broadcasters only: /helpall - display this help message to everyone /menuall - display the menu to everyone /leaderboardall - display the tip leaderboard to everyone /recentall - display all recent purchases to everyone /add {tokenPrice}--{itemDescription} - add an item to the menu /remove {tokenPrice} - remove an item from the menu ` // Display the help message to the appropriate audience. if (targetAll) { cb.sendNotice(helpMsg, "", stdBrick) } else { cb.sendNotice(helpMsg, message.user, stdBrick) } return message } // printMenu will print the menu. function printMenu(targetAll) { // Write out the menu items, and display an empty message if there are // no menu items. let menuMsg = "The Menu" if (menu.length === 0) { menuMsg = "Menu is currently empty or inactive" } else { for (let i = 0; i < menu.length; i++) { menuMsg += "\n"+menu[i].price.toString()+" - "+menu[i].description } } // Send the final message to the chatroom. if (targetAll) { cb.sendNotice(menuMsg, "", stdPink) } else { cb.sendNotice(menuMsg, message.user, stdPink) } } // handlePrintMenu handles a /menu command. function handlePrintMenu(message, targetAll) { // If targetAll was set, verify that the caller has the ability to blast // out the message. if (targetAll) { if (isModMessage(message) === false) { cb.sendNotice("command may only be used by moderators", message.user, stdGrey) return message } } printMenu(targetAll) return message } // printLeaderboard will print out the leaderboard for everyone. function printLeaderboard(message, targetAll) { // If targetAll was set, verify that the caller has the ability to blast // out the message. if (targetAll) { if (isModMessage(message) === false) { cb.sendNotice("command may only be used by moderators", message.user, stdGrey) return message } } // If there are no tips yet, don't display the leaderboard. if (allTips.length === 0) { let leaderMsg = "Leaderboard: no tips have been made this session" if (targetAll) { cb.sendNotice(leaderMsg, "", stdGold) } else { cb.sendNotice(leaderMsg, message.user, stdGold) } return message } // Iterate over all of the tips to build a map from username to total // amount tipped. let allUsers = {} let totalTips = 0 for (let i = 0; i < allTips.length; i++) { // Convenience variables let user = allTips[i].user let amount = allTips[i].amount totalTips += amount // Handle anon tips first. if (allTips[i].isAnon) { user = "anonymous" } if (allUsers[user] === undefined) { allUsers[user] = 0 } allUsers[user] += amount } // Construct the leaderboard from the map. Though it's a double 'for' loop, // it's technically linear complexity because we iterate over the full set // of users at most leaderboardSize times. There are technically more // efficient ways to do this, but the following implementation is simple // and the total number of users who have provided a tip is expected to be // at most in the thousands. let users = Object.entries(allUsers) let userCount = users.length let leaders = [] for (let i = 0; i < leaderboardSize && users.length > 0; i++) { let largestAmt = 0 let largestJ = 0 for (let j = 0; j < users.length; j++) { if (users[j][1] > largestAmt) { largestAmt = users[j][1] largestJ = j } } leaders.push({ amount: users[largestJ][1], user: users[largestJ][0] }) users.splice(largestJ, 1) } // Construct the notice from the leaderboard. let tipsStr = totalTips.toString() let userNumStr = userCount.toString() let leaderMsg = "Leaderboard: "+tipsStr+" tokens total from "+userNumStr+" users" for (let i = 0; i < leaders.length; i++) { leaderMsg += "\n"+leaders[i].user+": "+leaders[i].amount } if (targetAll) { cb.sendNotice(leaderMsg, "", stdGold) } else { cb.sendNotice(leaderMsg, message.user, stdGold) } return message } function printRecent(message, targetAll) { // If targetAll was set, verify that the caller has the ability to blast // out the message. if (targetAll) { if (isModMessage(message) === false) { cb.sendNotice("command may only be used by moderators", message.user, stdGrey) return message } } // If there are no tips yet, there's nothing to display. if (recentPurchases.length === 0) { let recentMsg = "Recent: no purchases have been made this session" if (targetAll) { cb.sendNotice(recentMsg, "", stdBlue) } else { cb.sendNotice(recentMsg, message.user, stdBlue) } return message } // Build the list for recent purchases. let recentMsg = "Recent Menu Purchases" for (let i = recentPurchases.length-1; i >= 0 && recentPurchases.length-i < recentSize; i--) { recentMsg += "\n" if (recentPurchases[i].isAnon) { recentMsg += "anonymous" } else { recentMsg += recentPurchases[i].user } recentMsg += ": "+recentPurchases[i].amount+" - "+recentPurchases[i].item // Add some indication of how long ago the purchase was made. timeSince = Math.floor((new Date()-recentPurchases[i].date) / 1000) if (timeSince < 60) { recentMsg += " - "+timeSince+" seconds ago" } else if (timeSince < 60 * 60) { recentMsg += " - "+Math.floor(timeSince/60)+" minutes ago" } else { recentMsg += " - "+Math.floor(timeSince/3600)+" hours ago" } } if (targetAll) { cb.sendNotice(recentMsg, "", stdBlue) } else { cb.sendNotice(recentMsg, message.user, stdBlue) } return message } // printRemind will display the 10 most recent purchases made by the user. function printRemind(message) { // Build the list of recent purchases. let recent = [] for (let i = recentPurchases.length-1; i >= 0 && recent.length < recentSize; i--) { if (recentPurchases[i].user === message.user && recentPurchases[i].isAnon === false) { recent.push({ amount: recentPurchases[i].amount, item: recentPurchases[i].item }) } } // Check for an empty list of purchases. if (recent.length === 0) { cb.sendNotice("no recent menu purchases", message.user, stdGrey) return message } // Build the string that displays the user's recent purcahses. let recentStr = "Recent messages from "+message.user for (let i = 0; i < recent.length; i++) { recentStr += "\n" + recent[i].amount + " - " + recent[i].item } cb.sendNotice(recentStr, "", stdAqua) return message } function processAdd(message) { // Restrict the add query to the mods. if(isModMessage(message) === false) { cb.sendNotice("only moderators can add items to the menu", message.user, stdGrey) return message } // If the app is set to only let the model manipulate the menu, check if // this message is from the model. if (message.is_mod && message.user !== cb.room_slug && cb.settings.let_mods_update_menu === "no") { cb.sendNotice("only the broadcaster can update the menu", message.user, stdGrey) return message } // Parse the add query. let price = message.m.substring(5).replace(/(^\d+)(.+$)/i,'$1') let valid = Number.isInteger(Number(price)) && message.m.startsWith("/add "+price+"--") && Number(price) > 0 if (valid) { menu.push({ price: Number(price), description: message.m.substring(price.length+7) }) sortMenu() cb.sendNotice("menu updated successfully", message.user, stdGrey) } else { cb.sendNotice("command not valid, please check the formatting", message.user, stdGrey) return message } } function processRemove(message) { // Restrict the remove query to the mods. if(isModMessage(message) === false) { cb.sendNotice("[only moderators can remove items from the menu]", message.user, stdGrey) return message } // If the app is set to only let the model manipulate the menu, check if // this message is from the model. if (message.is_mod && message.user !== cb.room_slug && cb.settings.let_mods_update_menu === "no") { cb.sendNotice("[only the broadcaster can update the menu]", message.user, stdGrey) return message } // Parse the remove query. let price = Number(message.m.substring(8)) if (Number.isInteger(price)) { // Loop over the menu and remove every item that has the provided // price. found = true while (found === true) { found = false for (let i = 0; i < menu.length; i++) { if (menu[i].price === price) { found = true menu.splice(i, 1) break } } } cb.sendNotice("[menu updated successfully]", message.user, stdGrey) } else { cb.sendNotice("[command not valid, please check the formatting]", message.user, stdGrey) return message } } // processMessage will look at a message from users. This looks for commands as // well as attempts to filter spam. function processMessage(message) { // Perform a basic attempt to detect spam. We allow most of the common // english characters, and most of the emojis. const isSafe = /^[ -~\x7f-\xff\u{1f300}-\u{1f5ff}\u{1f900}-\u{1f9ff}\u{1f600}-\u{1f64f}\u{1f680}-\u{1f6ff}\u{2600}-\u{26ff}\u{2700}-\u{27bf}\u{1f1e6}-\u{1f1ff}\u{1f191}-\u{1f251}\u{1f004}\u{1f0cf}\u{1f170}-\u{1f171}\u{1f17e}-\u{1f17f}\u{1f18e}\u{3030}\u{2b50}\u{2b55}\u{2934}-\u{2935}\u{2b05}-\u{2b07}\u{2b1b}-\u{2b1c}\u{3297}\u{3299}\u{303d}\u{00a9}\u{00ae}\u{2122}\u{23f3}\u{24c2}\u{23e9}-\u{23ef}\u{25b6}\u{23f8}-\u{23fa}\u{20a0}-\u{20cf}\u{2000}-\u{206f}\u{fe0f}]+$/gu.test(message.m) if (isSafe === false) { message.m = "[spam detected - please only use english characters]" message["X-Spam"] = true return message } // Check if this is a bot query. If so, set the spam indicator to true so // that other users don't see commands being sent around. if (message.m.startsWith("/")) { message["X-Spam"] = true } // Check for commands. if (message.m === "/help") { return processHelpMessage(message, false) } if (message.m === "/helpall") { return processHelpMessage(message, true) } if (message.m === "/menu") { return printMenu(message, false) } if (message.m === "/menuall") { return printMenu(message, true) } if (message.m === "/leaderboard") { return printLeaderboard(message, false) } if (message.m === "/leaderboardall") { return printLeaderboard(message, true) } if (message.m === "/recent") { return printRecent(message, false) } if (message.m === "/recentall") { return printRecent(message, true) } if (message.m === "/remind") { return printRemind(message) } if (message.m.startsWith("/add ")) { return processAdd(message) } if (message.m.startsWith("/remove ")) { return processRemove(message) } if (message.m.startsWith("/")) { cb.sendNotice("[command not recognized, please check the spelling or look at /help]", message.user, stdGrey) return message } return message } // processTip is the handler for an onTip event. function processTip(tip) { // Ignore any tips that aren't going to the broadcaster. if (tip.to_user !== cb.room_slug) { return } // Add this tip to the set of all tips, so that we can produce a // leaderboard. allTips.push({ amount: tip.amount, isAnon: tip.is_anon_tip, user: tip.from_user }) // Search through the menu to see if this is a menu purchase. for (let i = 0; i < menu.length; i++) { if (menu[i].price === tip.amount) { recentPurchases.push({ amount: tip.amount, item: menu[i].description, isAnon: tip.is_anon_tip, user: tip.from_user, date: new Date() }) let tipMsg = "" if (tip.is_anon_tip) { tipMsg = "anonymous purchased "+menu[i].description+" for "+tip.amount+" tokens" } else { tipMsg = tip.from_user+" purchased "+menu[i].description+" for "+tip.amount+" tokens" } let msgLen = tipMsg.length let fullMsg = "" for (let i = 0; i < msgLen*1.15; i++) { fullMsg += "*" } fullMsg += "\n" fullMsg += tipMsg fullMsg += "\n" for (let i = 0; i < msgLen*1.15; i++) { fullMsg += "*" } cb.sendNotice(fullMsg, "", stdGreen) } } } // Add handlers for messages and tips. cb.onMessage(processMessage) cb.onTip(processTip) // Set up a recursive function to print the menu every 'menuFrequency' seconds. function printMenuRepeat() { printMenu(true) cb.setTimeout(printMenuRepeat, menuFrequency*1000) } cb.setTimeout(printMenuRepeat, menuFrequency*1000)
© Copyright Chaturbate 2011- 2024. All Rights Reserved.