Apps Home
|
Create an App
test147
Author:
brenlyn_trinidad
Description
Source Code
Launch App
Current Users
Created by:
Brenlyn_Trinidad
/** Name: Dorothy's UltraApp Author: chelsea2950 Current version 1.9 Created 01/27/2019 Last Updated 4/27/2019 (see bot description page for change log) This app is intended to provide cammers with many of the most common features from Apps in one place, specifically goals and ticket shows. It does not, however, include things that I would expect to be managed by my Fembot, such as chat control and tracking tip leaders, etc. The expected overall setup would include this app in the App slot, Dorothy's Ultra Fembot and Dorothy's Ultra Gamebot in two of the bot slots, leaving room for a third bot of your choice. I've borrowed some existing features from many places/apps including CrazyTicket and CrazyGoal by acrazyguy and Tip Multi-Goal by mx2k6. However, this app is also intended to be transparent and not automatically add people to your modlist and ticket show lists and give other special rights without permission. The source for this app will always remain open and unobfuscated. Features of this app: - Single & Progressive Goals - Hidden Ticket Show - Ascending / Descending Tip Sequence Goals - Goal Counter show - Tip Jar - Goal Race **/ // prototype functions { String.prototype.capitalize = function() { return this.charAt(0).toUpperCase() + this.slice(1); } String.prototype.repeat = function (number) { return new Array(number + 1).join(this); } String.prototype.equals = function (str) { var m = new RegExp(str); return this.match(m) != null; } String.prototype.equalsIgnoreCase = function (str) { var m = new RegExp(str, "i"); return this.match(m) != null; } } { var dummycounter = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23' ]; var backgroundArray = { menu: ['Cube Wave', 'Hearts', 'Snow', 'Aquarium', 'Snakeskin', 'Pastels', 'Clouds', 'Lava Lamp', 'Light Blue', 'Moon', 'personalized - CumCreamery', 'personalized - Liv n Drew', 'Blue Bars', 'Valentines Pink Bars', 'Valentines 2 Hearts', 'Green and Blue Bars', 'Pink and Blue Bars', 'Green and Green Bars', 'Green and Yellow Bars', 'Purple and Pink Bars', 'Aqua and Pink Bars', 'Pink and Pink Bars', 'Yellow and Yellow Bars', 'personalized - SexyAutumn' ], command: ['cubewave', 'hearts', 'snow', 'aquarium', 'snakeskin', 'pastels', 'clouds', 'lavalamp', 'lightblue', 'moon', 'cumcreamery', 'livanddrew', 'bluegradient', 'valentines1', 'valentines2', 'greenblue', 'pinkblue', 'greengreen', 'greenyellow', 'purplepink', 'aquapink', 'pinkpink', 'yellowyellow', 'sexy_autumn' ], devfile: ['17606e52-2215-403d-b052-46a936ca9f92','add', 'add', 'add', 'add', 'add', 'add', 'add', 'add', 'add', '3f73f647-504a-4f9a-8e64-86cc89890955', 'add', 'add', 'add', 'add', 'add', 'add', 'ca7b4f10-9623-4db8-8f84-70153dc60f61', 'add', 'add', 'add', 'add', 'add', 'add' ], livefile: ['9478eff1-b042-4d33-ad91-f8427b98cad8','18994a59-8a72-44c4-ba3b-ab9e835460b8', '18a37566-956a-4e84-a668-2dc32bcf6963', 'e2632788-9b57-48b3-8b63-1880c39efa0d', '80a94656-e4b5-4217-adf3-5a7386dc4023', '8c0b2b81-cb3e-4fb7-9ae3-4f2656c90e4d', 'b3dcdf85-a351-4f50-a686-5a553657981f', '8da93470-9c6a-47c4-83e6-858907d4f84d', '2311527c-85cb-44a0-87d9-adc3097b53d8', '1b53854d-dd02-4e7e-a9cb-fd0e38255cf1', '87983935-461f-418b-a140-36b790fb1bcf', '51864982-fa61-466f-9de6-367461031eb9', 'a0ad9443-e8df-4286-9f95-ea5f51931b9f', 'f228e2e6-8d58-49f4-b9a4-4662de7290c0', '2a63550e-d6f8-4182-bf35-3a61b24067e5', '1a3e852f-8c39-4c0c-8365-ae7ad8c8ac29', '30182874-b8c9-4004-a145-7ed60f6bfedd', '8e5604f6-5552-4447-a43b-54b97b15f05d', '9817f86f-e3c1-4a1e-b4d7-c06da8fc5639', 'a4e5a8e1-4756-419b-8459-fa971558ebab', 'b12c143a-2ea8-488c-9345-854a36cdd8a3', 'cf4dd491-0f33-4c07-8d22-adec552bc1c9', 'f82bd0a1-5923-401a-bece-6becf546053e', '76bc7743-3c8b-4a81-a9d2-f28608650780' ] }; var fontSize = 12; var customPanel = false; } {cb.settings_choices = [ {name: 'intro', label: '******************* INTRODUCTION ********************* Latest Updt: 04/27/2019 (version 1.9) See Change Log ********************************************************* Welcome to Dorothy\'s Ultra App, if this is your first time using the App, the main thing you are choosing is which feature to start with, and then setting up the defaults for that section. Since Apps control the draw panel below the video window, only one app feature can be active at a time. Other features can be set up in future shows or turned on during the show by you or your moderator. Moderators are given significant privileges by this App, so please make sure you assign moderators you can trust. I have defined default settings using prices that I find to be common, but please update these per your preference, the defaults are only suggestions! Also, you can see the full list of commands for the App by typing "/uahelp" in the chat (no quotes), and then also see more detailed help by section. It is expected that you would be using an Ultrabot such as Dorothy\'s Ultra Fembot alongside this for features such as Chat Control and Tipper Count/Leader features. Please enjoy using the Ultra App and feel free to say hello if you see me around, or DM me on twitter @thechelsea2950 if you have questions. Thank you! - chelsea', type: 'choice',required: false}, // *** General Settings {name: 'general', label: '*********************************************************** ******************* GENERAL SETUP ********************** *********************************************************** Please make a choice below for which App feature you will start the show with. For each feature, there is a Config section below where you will define the App startup defaults, and some settings can be changed on the fly during the show. Also, certain features do well in certain scenarios based on the number of people in the room, quick or slow nights, etc - there is more info in my bio on show strategy based on what I\'ve seen work well (chaturbate.com/chelsea2950). Note that if planning a ticket show, it is very common and advisable to start the show with goals, and switch to the ticket show once the room has enough people to make ticket sales successful (usually at least 1000-1500 viewers)', type: 'choice',required: false}, {name: 'whichApp', label: 'Which App Feature would you like to begin the show with? . Show Types --- "Single or Progressive Goals" lets you setup a single goal or multiple goals that will automatically progress as each successive goal is met --- "Goal Counter" lets you set smaller incremental goals with intermediate prizes every X number of goals met --- "Ascending/Descending Tip Sequence Goal" lets the room tip through a sequence up to a final goal with smaller goals along the way --- "Tip Jar" drives continuous tipping to keep a show going after a goal is met --- "Ultra App Ticket Show" Sell tickets and then go into a private show for ticket holders, very similar to CrazyTicket... Alternatively you can start the app with no features active and start one later', type: 'choice', choice1: 'Single/Progressive Goals', choice2: 'Goal Counter', choice3: 'Asc/Desc Tip Sequence Goals', choice4: 'Tip Jar', choice5: 'UltraApp Ticket Show', choice6: 'Goal Race', choice7: 'None', defaultValue: 'Single/Progressive Goals'}, {name: 'enableEntryMessage', label: 'Display a notification to users when they enter?', type: 'choice', choice1: 'Yes', choice2: 'No', defaultValue: 'Yes'}, {name: 'entryMessage', label: 'Enter the message to display. A note regarding the current App Feature running will be appended to this message so you do not need to restate that', type: 'str',required: false, minLength: 1, maxLength: 1000, defaultValue: 'Welcome! Dorothys Ultra App is currently running for for the broadcaster to manage goal shows and ticket shows.'}, {name: 'allowModsAuthority', label: 'Allow moderators to have authority to broadcaster commands across all App Features? Note there are separate controls later for ticket show additions and price changes. It is usually ok to give moderators general authority with this setting, however they will have authority to perform actions such as manually adding tips to goals, start and ending ticket shows, and changing goal thresholds', type: 'choice', choice1: 'Yes', choice2: 'No', defaultValue: 'Yes'}, {name: 'allowModsStats', label: 'Allow moderators to use the "/stats" command to see current show time online and tip totals', type: 'choice', choice1: 'Yes', choice2: 'No', defaultValue: 'No'}, {name: 'showTotals', label: 'Show the total tips so far for the current app in the draw panel?', type: 'choice', choice1: 'Yes', choice2: 'No', defaultValue: 'Yes'}, {name: 'fembotRunning', label: 'Are you using Dorothy\'s Fembot alongside the Ultra App? If not, you should be! :) This setting is used to suppress error messages from the UltraApp for invalid commands as it is expected the Fembot will handle errors (so you don\'t get the same error twice)', type: 'choice', choice1: 'Yes', choice2: 'No', defaultValue: 'No'}, {name: 'panelBackground', label: 'Which background panel type would you like to display? Note you cannot use the personalized background unless they are for your room, it will use the default if they are selected', type: 'choice', choice1: 'default - no image', choice2: backgroundArray.menu[0], choice3: backgroundArray.menu[1], choice4: backgroundArray.menu[2], choice5: backgroundArray.menu[3], choice6: backgroundArray.menu[4], choice7: backgroundArray.menu[5], choice8: backgroundArray.menu[6], choice9: backgroundArray.menu[7], choice10: backgroundArray.menu[8], choice11: backgroundArray.menu[9], choice12: backgroundArray.menu[10], choice13: backgroundArray.menu[11], choice14: backgroundArray.menu[12], choice15: backgroundArray.menu[13], choice16: backgroundArray.menu[14], choice17: backgroundArray.menu[15], choice18: backgroundArray.menu[16], choice19: backgroundArray.menu[17], choice20: backgroundArray.menu[18], choice21: backgroundArray.menu[19], choice22: backgroundArray.menu[20], choice23: backgroundArray.menu[21], choice24: backgroundArray.menu[22], choice25: backgroundArray.menu[23], defaultValue: backgroundArray.menu[0]}, {name: 'panelTextColor',label: 'Text color for the panel text',type: 'choice',choice1: 'White/No Color',choice2: 'Black',choice3: 'Dark Grey',choice4: 'Dark Red',choice5: 'Dark Orange',choice6: 'Dark Green',choice7: 'Dark Aqua',choice8: 'Dark Blue',choice9: 'Dark Purple',choice10: 'Dark Pink',choice11: 'Dark Gold',choice12: 'Dark Teal',choice13: 'Dark Brown',choice14: 'Dark Bronze',choice15: 'Dark Periwinkle',choice16: 'Dark Fuschia',choice17: 'Dark Lime',choice18: 'Dark Plum',choice19: 'Custom',defaultValue: 'Dark Blue'}, {name: 'panelCustomTextColor', label: 'If you picked a custom text color in the previous setting, enter the hex color (6 character hex color codes including the # prefix, such as #FFFFFF):', type: 'str', minLength: 1, maxLength: 7, required: false}, // *** Single or Progressive Goals {name: 'progressivegoals', label: '*********************************************************** *********** SINGLE OR PROGRESSIVE GOALS ************* *********************************************************** You can set up a Single goal or multiple goals. Each goal amount is specific to that goal, it is not the cumulative total. Please clear out the descriptions and amounts for the goals that are not being used. If doing a single goal, you can recycle the goal multiple times using the "/restartgoal" command', type: 'choice',required: false}, {name: 'progressiveRoomSubjectSfx', label: 'Title? -- Enter any show description text or hashtags you would like appended to the individual goal descriptions shown in the room title', type: 'str',required: false, minLength: 1, maxLength: 100, defaultValue: 'Sex Show at Final Goal #couple #goals'}, {name: 'progressiveAutoNext', label: 'Once the current goal is reached, automatically start next goal, or require manual start of a goal with the "/next" command? Note that with manual start, tips that exceed the current goal are not counted toward the next goal. With auto-start, the excess amount is rolled over to the next goal.', type: 'choice', choice1: 'Auto-start next', choice2: 'Manually start next', defaultValue: 'Auto-start next'}, {name: 'progressiveGoalDescription1', label: 'Goal #1 Description', type: 'str',required: false, minLength: 1, maxLength: 100, defaultValue: 'Goal #1 Name'}, {name: 'progressiveGoalAmount1', label: 'Goal #1 amount', type: 'int',required: false, minValue: 1, maxValue: 100000, defaultValue: 500}, {name: 'progressiveGoalDescription2', label: 'Goal #2 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'progressiveGoalAmount2', label: 'Goal #2 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'progressiveGoalDescription3', label: 'Goal #3 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'progressiveGoalAmount3', label: 'Goal #3 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'progressiveGoalDescription4', label: 'Goal #4 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'progressiveGoalAmount4', label: 'Goal #4 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'progressiveGoalDescription5', label: 'Goal #5 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'progressiveGoalAmount5', label: 'Goal #5 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'progressiveGoalDescription6', label: 'Goal #6 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'progressiveGoalAmount6', label: 'Goal #6 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'progressiveGoalDescription7', label: 'Goal #7 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'progressiveGoalAmount7', label: 'Goal #7 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'progressiveGoalDescription8', label: 'Goal #8 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'progressiveGoalAmount8', label: 'Goal #8 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'progressiveGoalDescription9', label: 'Goal #9 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'progressiveGoalAmount9', label: 'Goal #9 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'progressiveGoalDescription10', label: 'Goal #10 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'progressiveGoalAmount10', label: 'Goal #10 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'progressiveGoalDescription11', label: 'Goal #11 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'progressiveGoalAmount11', label: 'Goal #11 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'progressiveGoalDescription12', label: 'Goal #12 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'progressiveGoalAmount12', label: 'Goal #12 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'progressiveGoalDescription13', label: 'Goal #13 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'progressiveGoalAmount13', label: 'Goal #13 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'progressiveGoalDescription14', label: 'Goal #14 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'progressiveGoalAmount14', label: 'Goal #14 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'progressiveGoalDescription15', label: 'Goal #15 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'progressiveGoalAmount15', label: 'Goal #15 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'progressiveGoalDescription16', label: 'Goal #16 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'progressiveGoalAmount16', label: 'Goal #16 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'progressiveGoalDescription17', label: 'Goal #17 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'progressiveGoalAmount17', label: 'Goal #17 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'progressiveGoalDescription18', label: 'Goal #18 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'progressiveGoalAmount18', label: 'Goal #18 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'progressiveGoalDescription19', label: 'Goal #19 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'progressiveGoalAmount19', label: 'Goal #19 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'progressiveGoalDescription20', label: 'Goal #20 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'progressiveGoalAmount20', label: 'Goal #20 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'progressiveGoalTextColor',label: 'Text color for chat notices related to the Progressive Goal App',type: 'choice',choice1: 'White/No Color',choice2: 'Black',choice3: 'Dark Grey',choice4: 'Dark Red',choice5: 'Dark Orange',choice6: 'Dark Green',choice7: 'Dark Aqua',choice8: 'Dark Blue',choice9: 'Dark Purple',choice10: 'Dark Pink',choice11: 'Dark Gold',choice12: 'Dark Teal',choice13: 'Dark Brown',choice14: 'Dark Bronze',choice15: 'Dark Periwinkle',choice16: 'Dark Fuschia',choice17: 'Dark Lime',choice18: 'Dark Plum',choice19: 'Custom',defaultValue: 'Dark Green'}, {name: 'progressiveGoalCustomTextColor', label: 'If you picked a custom text color in the previous setting, enter the hex color (6 character hex color codes including the # prefix, such as #FFFFFF):', type: 'str', minLength: 1, maxLength: 7, required: false}, {name: 'progressiveGoalBgColor',label: 'Background/Highlight color for chat notices related to the Progressive Goal App', type: 'choice', choice1: 'White/No Color',choice2: 'Light Yellow',choice3: 'Light Blue',choice4: 'Light Pink',choice5: 'Light Red',choice6: 'Light Green',choice7: 'Light Purple',choice8: 'Light Orange',choice9: 'Light Grey',choice10: 'Light Aqua',choice11: 'Light Teal',choice12: 'Cream',choice13: 'Light Bronze',choice14: 'Light Periwinkle',choice15: 'Light Fuschia',choice16: 'Light Lime',choice17: 'Light Plum',choice18: 'Custom',defaultValue: 'Light Green'}, {name: 'progressiveGoalCustomBgColor', label: 'If you picked a custom text color in the previous setting, enter the hex color (6 character hex color codes including the # prefix, such as #FFFFFF):', type: 'str', minLength: 1, maxLength: 7, required: false}, // *** Goal Counter {name: 'goalcounter', label: '*********************************************************** ******************* GOAL COUNTER *********************** *********************************************************** With this Feature, smaller goals are set, and prizes are performed at each number of goals met, such as top off at 5 goals, bottoms off at 10 goals, etc. Only fill in the label and goal threshold for the prizes you would like to use and leave the rest blank', type: 'choice',required: false}, {name: 'goalcounterRoomSubjectSfx', label: 'Title? -- Enter any show description text or hashtags you would like appended to the list of individual goal thresholds that shown by default in the room title', type: 'str',required: false, minLength: 1, maxLength: 100, defaultValue: 'Sex Show at Final Goal #couple #goals'}, {name: 'goalcounterPerGoalAmount', label: 'Individual goal amount', type: 'int',required: false, minValue: 1, maxValue: 10000, defaultValue: 99}, {name: 'goalcounterGoalDescription1', label: 'Prize Description #1', type: 'str',required: false, minLength: 1, maxLength: 100, defaultValue: 'Prize #1 Name'}, {name: 'goalcounterGoalAmount1', label: 'Goals Threshold #1 - Number of Goals', type: 'int',required: false, minValue: 1, maxValue: 100, defaultValue: 5}, {name: 'goalcounterGoalDescription2', label: 'Prize Description #2', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'goalcounterGoalAmount2', label: 'Goals Threshold #2 - Number of Goals', type: 'int',required: false, minValue: 1, maxValue: 100}, {name: 'goalcounterGoalDescription3', label: 'Prize Description #3', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'goalcounterGoalAmount3', label: 'Goals Threshold #3 - Number of Goals', type: 'int',required: false, minValue: 1, maxValue: 100}, {name: 'goalcounterGoalDescription4', label: 'Prize Description #4', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'goalcounterGoalAmount4', label: 'Goals Threshold #4 - Number of Goals', type: 'int',required: false, minValue: 1, maxValue: 100}, {name: 'goalcounterGoalDescription5', label: 'Prize Description #5', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'goalcounterGoalAmount5', label: 'Goals Threshold #5 - Number of Goals', type: 'int',required: false, minValue: 1, maxValue: 100}, {name: 'goalcounterGoalDescription6', label: 'Prize Description #6', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'goalcounterGoalAmount6', label: 'Goals Threshold #6 - Number of Goals', type: 'int',required: false, minValue: 1, maxValue: 100}, {name: 'goalcounterGoalDescription7', label: 'Prize Description #7', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'goalcounterGoalAmount7', label: 'Goals Threshold #7 - Number of Goals', type: 'int',required: false, minValue: 1, maxValue: 100}, {name: 'goalcounterGoalDescription8', label: 'Prize Description #8', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'goalcounterGoalAmount8', label: 'Goals Threshold #8 - Number of Goals', type: 'int',required: false, minValue: 1, maxValue: 100}, {name: 'goalcounterGoalDescription9', label: 'Prize Description #9', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'goalcounterGoalAmount9', label: 'Goals Threshold #9 - Number of Goals', type: 'int',required: false, minValue: 1, maxValue: 100}, {name: 'goalcounterGoalDescription10', label: 'Prize Description #10', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'goalcounterGoalAmount10', label: 'Goals Threshold #10 - Number of Goals', type: 'int',required: false, minValue: 1, maxValue: 100}, {name: 'goalcounterTextColor',label: 'Text color for chat notices related to the Goal Counter App',type: 'choice',choice1: 'White/No Color',choice2: 'Black',choice3: 'Dark Grey',choice4: 'Dark Red',choice5: 'Dark Orange',choice6: 'Dark Green',choice7: 'Dark Aqua',choice8: 'Dark Blue',choice9: 'Dark Purple',choice10: 'Dark Pink',choice11: 'Dark Gold',choice12: 'Dark Teal',choice13: 'Dark Brown',choice14: 'Dark Bronze',choice15: 'Dark Periwinkle',choice16: 'Dark Fuschia',choice17: 'Dark Lime',choice18: 'Dark Plum',choice19: 'Custom',defaultValue: 'Dark Blue'}, {name: 'goalcounterCustomTextColor', label: 'If you picked a custom text color in the previous setting, enter the hex color (6 character hex color codes including the # prefix, such as #FFFFFF):', type: 'str', minLength: 1, maxLength: 7, required: false}, {name: 'goalcounterBgColor',label: 'Background/Highlight color for chat notices related to the Goal Counter App', type: 'choice', choice1: 'White/No Color',choice2: 'Light Yellow',choice3: 'Light Blue',choice4: 'Light Pink',choice5: 'Light Red',choice6: 'Light Green',choice7: 'Light Purple',choice8: 'Light Orange',choice9: 'Light Grey',choice10: 'Light Aqua',choice11: 'Light Teal',choice12: 'Cream',choice13: 'Light Bronze',choice14: 'Light Periwinkle',choice15: 'Light Fuschia',choice16: 'Light Lime',choice17: 'Light Plum',choice18: 'Custom',defaultValue: 'Light Blue'}, {name: 'goalcounterCustomBgColor', label: 'If you picked a custom text color in the previous setting, enter the hex color (6 character hex color codes including the # prefix, such as #FFFFFF):', type: 'str', minLength: 1, maxLength: 7, required: false}, // *** Ascending/Descending Tip Sequence Goals {name: 'tipsequence', label: '*********************************************************** ****************** TIP SEQUENCE GOALS ****************** *********************************************************** With this Feature, users tip the next amount in a sequence that counts up or down through your configured range, toward a Final Prize at the end of the range. Intermediate prizes can be performed at intervals, such as (if counting up) top off at 20, bottoms off at 30, etc., or you can just have a single goal at the end. The room title will automatically show the thresholds you configure for prizes. For reference, here are the total number of tips for several common sequence ranges: from 1-10=55, 1-20=210, 1-30=465, 1-40=820, 1-50=1275, 1-60=1830, 1-70=2485, 1-80=3240, 1-90=4095, 1-100=5050', type: 'choice',required: false}, {name: 'tipsequenceRoomSubjectSfx', label: 'Title? -- Description of the show or end goal to appear as part of the room title (optional). Enter any text or hashtags you would like appended to the list of individual sequence thresholds that will be shown in the room title. The end goal can either be stated here or in a sequence goal entry below for the last sequence to be used', type: 'str',required: false, minLength: 1, maxLength: 100, defaultValue: 'Help us hit the goal! #couple #goals'}, {name: 'tipsequenceDirection', label: 'Use Ascending sequence (count up) or Descending sequence (count down). Typically counting down makes the tipping go faster near the end', type: 'choice', choice1: 'Ascending', choice2: 'Descending', defaultValue: 'Ascending'}, {name: 'tipsequenceLowNumber', label: 'Set the value used for the low end of your sequence range, which will be the start of the count when counting up, or end when counting down (this is typically 1 for ascending sequence)', type: 'int',required: false, minValue: 1, maxValue: 200, defaultValue: 1}, {name: 'tipsequenceHighNumber', label: 'Set the value used for the high end of your sequence range, which will be the end of the count when counting up, or start when counting down (this is typically a value that makes the total equate to what you want to make by the final goal - see reference table above)', type: 'int',required: false, minValue: 1, maxValue: 200, defaultValue: 50}, {name: 'tipsequenceGroupTips', label: 'Allow Group Tipping of the next number, where any tip counts toward the next value (faster), or only count tips of at least the next sequence number amount toward the goal (slower)', type: 'choice', choice1: 'Group Tipping', choice2: 'Exact Amount or Greater', defaultValue: 'Group Tipping'}, {name: 'tipsequenceChatMsg', label: 'Show a message in the chat each time a sequence number is tipped or surpassed?', type: 'choice', choice1: 'Yes', choice2: 'No', defaultValue: 'Yes'}, {name: 'tipsequenceShowTotal', label: 'Show the total goal for the sequence range in the draw panel?', type: 'choice', choice1: 'Yes', choice2: 'No', defaultValue: 'Yes'}, {name: 'tipsequenceGoalDescription1', label: 'Prize #1 - Description', type: 'str',required: false, minLength: 1, maxLength: 100, defaultValue: 'Prize #1 Name'}, {name: 'tipsequenceGoalAmount1', label: 'Prize #1 at Tip Sequence of', type: 'int',required: false, minValue: 1, maxValue: 200, defaultValue: 20}, {name: 'tipsequenceGoalDescription2', label: 'Prize #2 - Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'tipsequenceGoalAmount2', label: 'Prize #2 at Tip Sequence of', type: 'int',required: false, minValue: 1, maxValue: 200}, {name: 'tipsequenceGoalDescription3', label: 'Prize #3 - Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'tipsequenceGoalAmount3', label: 'Prize #3 at Tip Sequence of', type: 'int',required: false, minValue: 1, maxValue: 200}, {name: 'tipsequenceGoalDescription4', label: 'Prize #4 - Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'tipsequenceGoalAmount4', label: 'Prize #4 at Tip Sequence of', type: 'int',required: false, minValue: 1, maxValue: 200}, {name: 'tipsequenceGoalDescription5', label: 'Prize #5 - Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'tipsequenceGoalAmount5', label: 'Prize #5 at Tip Sequence of', type: 'int',required: false, minValue: 1, maxValue: 200}, {name: 'tipsequenceGoalDescription6', label: 'Prize #6 - Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'tipsequenceGoalAmount6', label: 'Prize #6 at Tip Sequence of', type: 'int',required: false, minValue: 1, maxValue: 200}, {name: 'tipsequenceGoalDescription7', label: 'Prize #7 - Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'tipsequenceGoalAmount7', label: 'Prize #7 at Tip Sequence of', type: 'int',required: false, minValue: 1, maxValue: 200}, {name: 'tipsequenceGoalDescription8', label: 'Prize #8 - Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'tipsequenceGoalAmount8', label: 'Prize #8 at Tip Sequence of', type: 'int',required: false, minValue: 1, maxValue: 200}, {name: 'tipsequenceGoalDescription9', label: 'Prize #9 - Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'tipsequenceGoalAmount9', label: 'Prize #9 at Tip Sequence of', type: 'int',required: false, minValue: 1, maxValue: 200}, {name: 'tipsequenceGoalDescription10', label: 'Prize #10 - Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'tipsequenceGoalAmount10', label: 'Prize #10 at Tip Sequence of', type: 'int',required: false, minValue: 1, maxValue: 200}, {name: 'tipsequenceTextColor',label: 'Text color for chat notices related to the Tip Sequence App',type: 'choice',choice1: 'White/No Color',choice2: 'Black',choice3: 'Dark Grey',choice4: 'Dark Red',choice5: 'Dark Orange',choice6: 'Dark Green',choice7: 'Dark Aqua',choice8: 'Dark Blue',choice9: 'Dark Purple',choice10: 'Dark Pink',choice11: 'Dark Gold',choice12: 'Dark Teal',choice13: 'Dark Brown',choice14: 'Dark Bronze',choice15: 'Dark Periwinkle',choice16: 'Dark Fuschia',choice17: 'Dark Lime',choice18: 'Dark Plum',choice19: 'Custom',defaultValue: 'Dark Pink'}, {name: 'tipsequenceCustomTextColor', label: 'If you picked a custom text color in the previous setting, enter the hex color (6 character hex color codes including the # prefix, such as #FFFFFF):', type: 'str', minLength: 1, maxLength: 7, required: false}, {name: 'tipsequenceBgColor',label: 'Background/Highlight color for chat notices related to the Tip Sequence App', type: 'choice', choice1: 'White/No Color',choice2: 'Light Yellow',choice3: 'Light Blue',choice4: 'Light Pink',choice5: 'Light Red',choice6: 'Light Green',choice7: 'Light Purple',choice8: 'Light Orange',choice9: 'Light Grey',choice10: 'Light Aqua',choice11: 'Light Teal',choice12: 'Cream',choice13: 'Light Bronze',choice14: 'Light Periwinkle',choice15: 'Light Fuschia',choice16: 'Light Lime',choice17: 'Light Plum',choice18: 'Custom',defaultValue: 'Light Pink'}, {name: 'tipsequenceCustomBgColor', label: 'If you picked a custom text color in the previous setting, enter the hex color (6 character hex color codes including the # prefix, such as #FFFFFF):', type: 'str', minLength: 1, maxLength: 7, required: false}, // *** Tip Jar {name: 'tipjar', label: '*********************************************************** ************************ TIP JAR ************************** *********************************************************** The Tip Jar is used to perform a particular action once goal is reached, and keep doing it until the tip jar empties, so be sure to use it for prizes that you are willing to do for that long, and make sense to use as an ongoing goal (stay naked, oral, sex, etc). Set the goal(s) and drain rate, tokens do not start to drain until the goal is hit. Can be operated with a single goal or multiple goals similar to Progressive Goal show. Set the Recycle Count to "0" to only use each goal once, or set it to the number of times you will continue to do that prize if the jar is refilled again. Only the goals that are filled in will be used, blank out the goals and amounts you do not wish to use.', type: 'choice',required: false}, {name: 'tipjarRoomSubjectSfx', label: 'Title? -- Description of the show to appear as part of the room title (optional). You can include hashtags for keywords you want to be searchable. The actual goal prize descriptions are defined below at each level', type: 'str',required: false, minLength: 1, maxLength: 100, defaultValue: 'Welcome to our tip jar show! #tipjar #couples #sex.'}, {name: 'tipjarGoalRecycle', label: 'Once the tip jar is empty, allow users to continue to fill it to keep going? If allowed to continue tipping, the app will not recycle (and require meeting goal again), or advance to the next goal', type: 'choice', choice1: 'Tip to continue current goal', choice2: 'Recycle Goal or Advance to Next Goal', defaultValue: 'Tip to continue current goal'}, {name: 'tipjarAutoNext', label: 'Once the tip jar is empty, automatically recycle/advance? Or require manual start of the recycled/new goal with the "/next" command?', type: 'choice', choice1: 'Automatically Recycle / Start next goal', choice2: 'Manually advance using command', defaultValue: 'Automatically Recycle / Start next goal'}, {name: 'tipjarStartDrainRate', label: 'Drain Rate for how fast tokens are removed from the jar. Default is 1 token/sec but initial setting can be changed here, and can also be changed during the show using the "/faster" and "/slower" commands', type: 'choice', choice1: '5 tokens per second (fastest)', choice2: '4 tokens per second', choice3: '3 tokens per second', choice4: '2 tokens per second', choice5: '1 token per second', choice6: '1 token every 2 seconds', choice7: '1 token every 3 seconds', choice8: '1 token every 4 seconds', choice9: '1 token every 5 seconds', choice10: '1 token every 10 seconds (slowest)', defaultValue: '1 token per second'}, {name: 'tipjarDescription1', label: 'Goal #1 Description', type: 'str',required: false, minLength: 1, maxLength: 100, defaultValue: 'Goal #1 Name.'}, {name: 'tipjarAmount1', label: 'Goal #1 amount', type: 'int',required: false, minValue: 1, maxValue: 100000, defaultValue: 500}, {name: 'tipjarRecycle1', label: 'Goal #1 Recycle Count', type: 'int',required: false, minValue: 0, maxValue: 10, defaultValue: 0}, {name: 'tipjarDescription2', label: 'Goal #2 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'tipjarAmount2', label: 'Goal #2 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'tipjarRecycle2', label: 'Goal #2 Recycle Count', type: 'int',required: false, minValue: 0, maxValue: 10, defaultValue: 0}, {name: 'tipjarDescription3', label: 'Goal #3 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'tipjarAmount3', label: 'Goal #3 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'tipjarRecycle3', label: 'Goal #3 Recycle Count', type: 'int',required: false, minValue: 0, maxValue: 10, defaultValue: 0}, {name: 'tipjarDescription4', label: 'Goal #4 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'tipjarAmount4', label: 'Goal #4 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'tipjarRecycle4', label: 'Goal #4 Recycle Count', type: 'int',required: false, minValue: 0, maxValue: 10, defaultValue: 0}, {name: 'tipjarDescription5', label: 'Goal #5 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'tipjarAmount5', label: 'Goal #5 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'tipjarRecycle5', label: 'Goal #5 Recycle Count', type: 'int',required: false, minValue: 0, maxValue: 10, defaultValue: 0}, {name: 'tipjarDescription6', label: 'Goal #6 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'tipjarAmount6', label: 'Goal #6 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'tipjarRecycle6', label: 'Goal #6 Recycle Count', type: 'int',required: false, minValue: 0, maxValue: 10, defaultValue: 0}, {name: 'tipjarDescription7', label: 'Goal #7 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'tipjarAmount7', label: 'Goal #7 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'tipjarRecycle7', label: 'Goal #7 Recycle Count', type: 'int',required: false, minValue: 0, maxValue: 10, defaultValue: 0}, {name: 'tipjarDescription8', label: 'Goal #8 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'tipjarAmount8', label: 'Goal #8 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'tipjarRecycle8', label: 'Goal #8 Recycle Count', type: 'int',required: false, minValue: 0, maxValue: 10, defaultValue: 0}, {name: 'tipjarDescription9', label: 'Goal #9 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'tipjarAmount9', label: 'Goal #9 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'tipjarRecycle9', label: 'Goal #9 Recycle Count', type: 'int',required: false, minValue: 0, maxValue: 10, defaultValue: 0}, {name: 'tipjarDescription10', label: 'Goal #10 Description', type: 'str',required: false, minLength: 1, maxLength: 100}, {name: 'tipjarAmount10', label: 'Goal #10 amount', type: 'int',required: false, minValue: 1, maxValue: 100000}, {name: 'tipjarRecycle10', label: 'Goal #10 Recycle Count', type: 'int',required: false, minValue: 0, maxValue: 10, defaultValue: 0}, {name: 'tipjarTextColor',label: 'Text color for chat notices related to the Tip Jar App',type: 'choice',choice1: 'White/No Color',choice2: 'Black',choice3: 'Dark Grey',choice4: 'Dark Red',choice5: 'Dark Orange',choice6: 'Dark Green',choice7: 'Dark Aqua',choice8: 'Dark Blue',choice9: 'Dark Purple',choice10: 'Dark Pink',choice11: 'Dark Gold',choice12: 'Dark Teal',choice13: 'Dark Brown',choice14: 'Dark Bronze',choice15: 'Dark Periwinkle',choice16: 'Dark Fuschia',choice17: 'Dark Lime',choice18: 'Dark Plum',choice19: 'Custom',defaultValue: 'Dark Purple'}, {name: 'tipjarCustomTextColor', label: 'If you picked a custom text color in the previous setting, enter the hex color (6 character hex color codes including the # prefix, such as #FFFFFF):', type: 'str', minLength: 1, maxLength: 7, required: false}, {name: 'tipjarBgColor',label: 'Background/Highlight color for chat notices related to the Tip Jar App', type: 'choice', choice1: 'White/No Color',choice2: 'Light Yellow',choice3: 'Light Blue',choice4: 'Light Pink',choice5: 'Light Red',choice6: 'Light Green',choice7: 'Light Purple',choice8: 'Light Orange',choice9: 'Light Grey',choice10: 'Light Aqua',choice11: 'Light Teal',choice12: 'Cream',choice13: 'Light Bronze',choice14: 'Light Periwinkle',choice15: 'Light Fuschia',choice16: 'Light Lime',choice17: 'Light Plum',choice18: 'Custom',defaultValue: 'Light Purple'}, {name: 'tipjarCustomBgColor', label: 'If you picked a custom text color in the previous setting, enter the hex color (6 character hex color codes including the # prefix, such as #FFFFFF):', type: 'str', minLength: 1, maxLength: 7, required: false}, // *** Hidden Ticket Show {name: 'ticketshow', label: '*********************************************************** ********************** TICKET SHOWS ********************* *********************************************************** When using this Ultra App in combination with Dorothy\'s Ultra Fembot, be sure to set the ticket show type there to "Dorothy\'s UltraApp". Ticket show Pre-sales and backup ticket list as well as all of the automated features around menus, polls, adding users from leaderboard, etc. are also maintined in the Ultra Fembot.', type: 'choice',required: false}, {name: 'ticketRoomSubjectSfx', label: 'Title? -- Description of the show to appear as part of the room title (optional). You can include hashtags for keywords you want to be searchable', type:'str', minLength: 1,maxLength: 100, required: false, defaultValue: 'Hidden Sex Show! Token Poll for cumshot!'}, {name: 'ticketShowPrice', label: 'Ticket Show Price? -- The Hidden Show feature cannot be enabled without a price defined. Discounted price for Fan Club is defined later below', type: 'int',minValue: 1,maxValue: 300,defaultValue: 69,required: false}, {name: 'ticketShowCumulative', label: 'Accumulate tips? -- Once ticket sales have started (including Pre-sales if used), accumulate tips toward ticket price? -- Recommend set to "Yes" to avoid need for many manual adds. If set to "No", user can only tip the ticket amount or higher to be added', type: 'choice', choice1: 'Yes', choice2: 'No', defaultValue: 'Yes'}, {name: 'ticketShowStartMode', label: 'Start Mode? -- Which mode is used for determining when to start the show? This setting works in combination with the next setting below to define what will trigger the start of show, and whether it starts automatically', type: 'choice', choice1: 'Start Show Anytime', choice2: 'Start Show after Timer', choice3: 'Start Show after Ticket Goal', choice4: 'Start Show after Token Goal', defaultValue: 'Start Show Anytime'}, {name: 'ticketShowStartAuto', label: 'AutoStart? -- If using a timer or goal to determine start of show, does the Broadcaster (or moderator) start the show manually with the /startshow command, or does the show start automatically when timer runs out or goal is reached?', type: 'choice', choice1: 'Start from Command', choice2: 'Automated Start', defaultValue: 'Start from Command'}, {name: 'ticketShowGoal', label: 'Goal Amount? -- If using a goal for the total sales before starting the show, set goal amount here (in terms of tickets or tokens based on type of show start goal defined above). When using a timed start, this setting is not used', type: 'int',minValue: 1,maxValue: 5000,defaultValue: 1000,required: false}, {name: 'ticketShowStartTimer', label: 'Default Timer -- If the start mode is set for using an automatic timer, define the number of minutes for the countdown to show start here. Timer will start immediately upon starting the broadcast, or when the Hidden Ticket Show feature is turned on later. For manual timer control, start the show in "Start Show Anytime" mode, turn off "Autostart", and use "/ticketstarttimer" to do a countdown', type: 'int',minValue: 1,maxValue: 120,defaultValue: 15,required: false}, {name: 'ticketShowFanAppreciation', label: 'Fan Appreciation Mode? When turned on, a special ticket show is used where only the members defined below for free shows are able to access. There will be no ticket sales available.', type: 'choice', choice1: 'Yes', choice2: 'No', defaultValue: 'No'}, {name: 'ticketShowFreeFC', label: 'Give a free ticket to CB Fanclub members?', type: 'choice', choice1: 'Yes', choice2: 'No', defaultValue: 'Yes'}, {name: 'ticketShowFreeMods', label: 'Give a free ticket to moderators?', type: 'choice', choice1: 'Yes', choice2: 'No', defaultValue: 'Yes'}, {name: 'ticketShowFreeEFC', label: 'Give a free ticket to External Fanclub members? Even if the External FanClub list has been setup in the Fembot, it must also be duplicated here (below) to provide ticket show privileges', type: 'choice', choice1: 'Yes', choice2: 'No', defaultValue: 'Yes'}, {name: 'ticketShowFreeVIP', label: 'Give a free ticket to VIP List members? Even if the VIP list has been setup in the Fembot, it must also be duplicated here (below) to provide ticket show privileges', type: 'choice', choice1: 'Yes', choice2: 'No', defaultValue: 'Yes'}, {name: 'ticketShowPriceFC', label: 'Discounted Price for a ticket to the show for CB Fan Club members if not set for a free ticket above. If no price is set, the Fanc Club ticket price is the same as regular viewers. This value is not used if the above setting grants them a free ticket.', type: 'int',minValue: 1,maxValue: 300,defaultValue: 35,required: false}, {name: 'ticketShowPriceEFC', label: 'Discounted Price for a ticket to the show for External Fan Club members. If no price is set, the External Fan Club ticket price is the same as regular viewers. This value is not used if the above setting grants them a free ticket.', type: 'int',minValue: 0,maxValue: 300,required: false}, {name: 'ticketShowPriceVIP', label: 'Discounted Price for a ticket to the show for VIP List members. If no price is set, the VIP List ticket price is the same as regular viewers. This value is not used if the above setting grants them a free ticket.', type: 'int',minValue: 0,maxValue: 300,required: false}, {name: 'extFanList', label: 'Enter the names of any External Fan Club members you would like to grant special privileges, such as free or reduced prices to ticket shows. User names should be separated by a comma with no spaces', type: 'str', minLength: 1, maxLength: 1000, defaultValue: '', required: false}, {name: 'VIPList', label: 'Enter the names of any VIP List users you would like to grant special privileges, such as free or reduced prices to ticket shows. User names should be separated by a comma with no spaces', type: 'str', minLength: 1, maxLength: 1000, defaultValue: '', required: false}, {name: 'ticketShowModsAdd', label: 'Allow moderators to use the "add" and "remove" commands? Note that enabling this will allow them to add themselves to a show even if the above setting for giving them a free ticket is set to "No". This setting overrides the general setting for mod authority specifically for the ticket show "/add" functions.', type: 'choice', choice1: 'Yes', choice2: 'No', defaultValue: 'Yes'}, {name: 'ticketShowModsChgPrice', label: 'Allow moderators to change the price of a ticket? This setting overrides the general setting for mod authority specifically for the ticket show price functions', type: 'choice', choice1: 'Yes', choice2: 'No', defaultValue: 'Yes'}, {name: 'ticketShowAfterNotice', label: 'Message to display in the Room Subject and Notice after the show has ended', type:'str',minLength: 1,maxLength: 100,required: false,defaultValue: 'After show hangout, please follow us on twitter @yourusername.'}, {name: 'ticketShowReducePriceWarn', label: 'Late addition Ticket Show Sale? -- Reduce Ticket Price by this amount when the /showwarn command is used to indicate the show is nearly over. Set to 0 to not use this feature. Note this is the price reduction, not the actual new price', type: 'int',minValue: 0,maxValue: 300,defaultValue: 0,required: false}, {name: 'ticketShowEnableOT', label: 'Use OT List? -- Enable Outstanding Ticket feature so users can save their ticket for use in a later show. Note that the broadcaster must still record the user names and enter them in the OTS list below. Can also be used for granting free tickets to viewers for a future show in place of a refund if there was a problem with a show, or user bought at the last second and missed the show.', type: 'choice', choice1: 'Yes', choice2: 'No', defaultValue: 'Yes'}, {name: 'ticketShowOTList', label: 'OT List -- If the Outstanding Ticket feature is enabled above, enter the names of any viewers you would like to grant a ticket to a future show. The user will be notified when they enter that they have a free ticket, and if they choose to use it, the broadcaster is notified so they can remove them from the list before next show. The format should be a comma separated list (user1,user2,user3)', type: 'str', minLength: 1, maxLength: 1000, defaultValue: '', required: false}, {name: 'ticketShowAllowGift', label: 'Gifting Allowed? -- Allow users to buy additional tickets they can use as gifts to other users', type: 'choice', choice1: 'Yes', choice2: 'No', defaultValue: 'No'}, {name: 'ticketShowTextColor',label: 'Text color for chat notices related to the Ticket Show App',type: 'choice',choice1: 'White/No Color',choice2: 'Black',choice3: 'Dark Grey',choice4: 'Dark Red',choice5: 'Dark Orange',choice6: 'Dark Green',choice7: 'Dark Aqua',choice8: 'Dark Blue',choice9: 'Dark Purple',choice10: 'Dark Pink',choice11: 'Custom',defaultValue: 'Dark Blue'}, {name: 'ticketShowCustomTextColor', label: 'If you picked a custom text color in the previous setting, enter the hex color (6 character hex color codes including the # prefix, such as #FFFFFF):', type: 'str', minLength: 1, maxLength: 7, required: false}, {name: 'ticketShowBgColor',label: 'Background/Highlight color for chat notices related to the Ticket Show App', type: 'choice', choice1: 'White/No Color',choice2: 'Light Yellow',choice3: 'Light Blue',choice4: 'Light Pink',choice5: 'Light Red',choice6: 'Light Green',choice7: 'Light Purple',choice8: 'Light Orange',choice9: 'Light Grey',choice10: 'Light Aqua',choice11: 'Custom',defaultValue: 'Light Aqua'}, {name: 'ticketShowCustomBgColor', label: 'If you picked a custom text color in the previous setting, enter the hex color (6 character hex color codes including the # prefix, such as #FFFFFF):', type: 'str', minLength: 1, maxLength: 7, required: false}, {name: 'ticketShowPresalesMode', label: 'Ticket show pre-sales mode to use at the start of the broadcast? Note you can also enable and disable presales during the broadcast using the "/usepresale on" and "/usepresale off" commands. You can also change the mode using the command "/chgpresalemode [mode]". Using pre-sales will automatically turn on the backup ticket list and use the pre-sale price as the backup price. There are three modes available, with further settings below for the automated modes. If the Hidden Show feature (above) is enabled, pre-sales will automatically be disabled regardless of this setting', type: 'choice', choice1: 'No Pre-sales', choice2: 'Yes, Mode 1: Increment Manually', choice3: 'Yes, Mode 2: Increment on X Tickets Sold',choice4: 'Yes, Mode 3: Increment on Automatic Timer',defaultValue: 'No Pre-sales'}, {name: 'ticketShowPresaleCountIncrement',type: 'int',minValue: 1,maxValue: 50,required: false,defaultValue: 10,label: 'For Presales Mode 2: If setting an automatic increment per number of tickets sold, indicate the number of tickets to be sold in each increment here. After this number are sold, price will increase by the amount specified in the later setting.'}, {name: 'ticketShowPresaleTimedIncrement',type: 'int',minValue: 1,maxValue: 50,required: false,defaultValue: 15,label: 'For Presales Mode 3: If setting an automatic timer increment, this is the number of minutes for the timer. Timer will recycle and continue incrementing until limit is reached, or next increment would exceed the planned ticket show price. After each cycle of the timer expires, the price will automatically increase by the amount specified in the below setting.'}, {name: 'ticketShowPresaleIncreasePerIncrement',type: 'int',minValue: 1,maxValue: 100,required: false,defaultValue: 10,label: 'If setting an automatic increment by timer or number of tickets sold, indicate the increment in ticket price for each occurrence. This means every [X] minutes (defined in previous setting), the price will go up by the number of tokens defined here, or if selling by count, once all tickets have been sold at the current price, the price will increment by this amount. Note that the price will never increment above the defined ticket price above.'}, {name: 'ticketShowPresaleMaxIncrements',type: 'int',minValue: 1,maxValue: 10,required: false,defaultValue: 3,label: 'When using mode 2 or mode 3 for an automatic increment, this is the maximum number of increments before the presale stops increments. Once it reaches this point (or next increment would exceed the planned ticket price), the price stops incrementing and pre-sales will continue at the same price until they are ended.'}, {name: 'ticketShowPresalePrice',type: 'int',minValue: 1,maxValue: 1000,required: false,defaultValue: 45,label: 'If the ticket show pre-sales are enabled at the start of the show, use this price as the initial pre-sale price. Otherwise, price can be updated during show with /presaleprice [p]. Note that as soon as pre-sales are enabled, this ticket price will be used if defined, so leave it blank here if it will be defined during the show.'}, {name: 'ticketShowPresaleNoticeInterval',type: 'int',minValue: 1,maxValue: 15,required: false,defaultValue: 2,label: 'Display Interval for generic Ticket Show Pre-sales notice (in minutes). This notice just alerts the room that a pre-sale is active, regardless of mode'}, {name: 'prepTicketStartTimer', label: 'Start a Countdown timer for the ticketshow when /prepticket is used? Note this will only be allowed if the Ticket Show start mode near the top of this section is set to manual start ("Start Show Anytime"). If set to "Yes", and no timer is specified on the /prepticket command, the "Default Timer" setting above is used', type: 'choice', choice1: 'Yes', choice2: 'No', defaultValue: 'Yes'}, // *** Goal Race {name: 'goalrace', label: '*********************************************************** ******************* GOAL RACE *********************** *********************************************************** With this Feature, the broadcaster defines two goals that the viewers can vote for when tipping. The App keeps track of the progress toward the two goals until one of the goals wins. There are commands to change the room subject, panel subject, goal amounts, and goal descriptions during the show.', type: 'choice',required: false}, {name: 'goalraceSubjectText', label: 'Room Title? -- Enter any show description text or hashtags you would like to appear in the configurable part of the room title.', type: 'str',required: false, minLength: 1, maxLength: 100, defaultValue: 'Sex Show at Final Goal #couple #goals'}, {name: 'goalracePanelText', label: 'Panel Description? -- Shorter description that will appear in the panel, must be less than 30 characters.', type: 'str',required: false, minLength: 1, maxLength: 30, defaultValue: 'Panel Description'}, {name: 'goalraceDesc1', label: 'Goal Description #1', type: 'str',required: false, minLength: 1, maxLength: 20, defaultValue: 'Oral for him'}, {name: 'goalraceAmount1', label: 'Goal Amount #1', type: 'int',required: false, minValue: 1, maxValue: 10000, defaultValue: 500}, {name: 'goalraceDesc2', label: 'Goal Description #2', type: 'str',required: false, minLength: 1, maxLength: 20, defaultValue: 'Oral for her'}, {name: 'goalraceAmount2', label: 'Goal Amount #2', type: 'int',required: false, minValue: 1, maxValue: 10000, defaultValue: 500}, {name: 'goalraceAllowClaim', label: 'Allow users to "claim" their unspecified votes later in the goal race.', type: 'choice', choice1: 'Yes', choice2: 'No', defaultValue: 'Yes'}, {name: 'goalraceBonusFC', label: 'Percent bonus for Fan Club tips when voting. Leave as 1 for all votes to count the same. Use 20 for 20% bonus, 50 for 50% bonus, etc (100% doubles their tip amount). Allowable values from 0 to 100%', type: 'int',minValue: 0,maxValue: 100, defaultValue: 0,required: false}, {name: 'goalraceBonusEFC', label: 'Percent bonus for External Fan Club tips when voting. Leave as 1 for all votes to count the same. Use 20 for 20% bonus, 50 for 50% bonus, etc (100% doubles their tip amount). Allowable values from 0 to 100%', type: 'int',minValue: 0,maxValue: 100,defaultValue: 0,required: false}, {name: 'goalraceBonusVIP', label: 'Percent bonus for VIP tips when voting. Leave as 1 for all votes to count the same. Use 20 for 20% bonus, 50 for 50% bonus, etc (100% doubles their tip amount). Allowable values from 0 to 100%', type: 'int',minValue: 0,maxValue: 100,defaultValue: 0,required: false}, {name: 'goalraceTextColor',label: 'Text color for chat notices related to the Goal Race App',type: 'choice',choice1: 'White/No Color',choice2: 'Black',choice3: 'Dark Grey',choice4: 'Dark Red',choice5: 'Dark Orange',choice6: 'Dark Green',choice7: 'Dark Aqua',choice8: 'Dark Blue',choice9: 'Dark Purple',choice10: 'Dark Pink',choice11: 'Dark Gold',choice12: 'Dark Teal',choice13: 'Dark Brown',choice14: 'Dark Bronze',choice15: 'Dark Periwinkle',choice16: 'Dark Fuschia',choice17: 'Dark Lime',choice18: 'Dark Plum',choice19: 'Custom',defaultValue: 'Dark Blue'}, {name: 'goalraceCustomTextColor', label: 'If you picked a custom text color in the previous setting, enter the hex color (6 character hex color codes including the # prefix, such as #FFFFFF):', type: 'str', minLength: 1, maxLength: 7, required: false}, {name: 'goalraceBgColor',label: 'Background/Highlight color for chat notices related to the Goal Race App', type: 'choice', choice1: 'White/No Color',choice2: 'Light Yellow',choice3: 'Light Blue',choice4: 'Light Pink',choice5: 'Light Red',choice6: 'Light Green',choice7: 'Light Purple',choice8: 'Light Orange',choice9: 'Light Grey',choice10: 'Light Aqua',choice11: 'Light Teal',choice12: 'Cream',choice13: 'Light Bronze',choice14: 'Light Periwinkle',choice15: 'Light Fuschia',choice16: 'Light Lime',choice17: 'Light Plum',choice18: 'Custom',defaultValue: 'Light Blue'}, {name: 'goalraceCustomBgColor', label: 'If you picked a custom text color in the previous setting, enter the hex color (6 character hex color codes including the # prefix, such as #FFFFFF):', type: 'str', minLength: 1, maxLength: 7, required: false} ]; } { // *********************************** Variables and Arrays ************************************** var initialize = 0; var numberOfModerators = 1; var timerStart = 0; var chatMsgToggle = 0; var groupTipToggle = 0; var BC = cb.room_slug; var whichApp = ''; var currentGoal = 1; var currentGoalTips = 0; var currentGoalTotal = 0; var currentSessionTotal = 0; var currentGoalRecycleCnt = 0; var totalGoalLevels = 0; var currentGoalLevel = 1; var currentGoalCountAmt = 0; var sequenceTotalTipsGoal = 0; var currentAppTotalGoal = 0; var currentAppTotalCounter = 0; var currentAppTotalSequence = 0; var currentAppTotalTipJar = 0; var currentAppTotalTicket = 0; var currentAppTotalGoalRace = 0; var currentAppTotalNone = 0; var tipJarRunning = false; var tipJarWaiting = false; var tipOptions = false; var goalSubjectText = cb.settings.progressiveRoomSubjectSfx; var counterSubjectText = cb.settings.goalcounterRoomSubjectSfx; var sequenceSubjectText = cb.settings.tipsequenceRoomSubjectSfx; var tipjarSubjectText = cb.settings.tipjarRoomSubjectSfx; var ticketSubjectText = cb.settings.ticketRoomSubjectSfx; var goalraceSubjectText = cb.settings.goalraceSubjectText; var goalracePanelText = cb.settings.goalracePanelText; var ticketStartMode = ""; var ticketModeAuto = ""; var ticketShowTotalTips = 0; var ticketShowTotalTickets = 0; var ticketStartTime = 0; var ticketStopTime = 0; var ticketTimeAdded = 0; var ticketMinsRemain = 0; var ticketSecsRemain = 0; var ticketModAdd = (cb.settings.ticketModAdd === "Yes"); var ticketShowGoal = parseInt(cb.settings.ticketShowGoal); var ticketShowOtToggle = 0; var ticketPrice = cb.settings.ticketShowPrice; var ticketShowEnded = false; var countTicketsSold = 0; var presalePrice = cb.settings.ticketShowPresalePrice; var nextPresalePrice = 0; var countPresaleSold = 0; var presaleMode = ""; var presaleModeText = ""; var presalesToggle = 0; var presaleStartTime = 0; var presaleStopTime = 0; var presaleTimeAdded = 0; var presaleMinsRemain = 0; var presaleSecsRemain = 0; var presaleSkipMin = false; var presaleSkipSec = false; var presaleSkipNotice = false; var lowSequence = cb.settings.tipsequenceLowNumber; var highSequence = cb.settings.tipsequenceHighNumber; var progressiveGoalDescription1 = cb.settings.progressiveGoalDescription1; var progressiveGoalDescription2 = cb.settings.progressiveGoalDescription2; var progressiveGoalDescription3 = cb.settings.progressiveGoalDescription3; var progressiveGoalDescription4 = cb.settings.progressiveGoalDescription4; var progressiveGoalDescription5 = cb.settings.progressiveGoalDescription5; var progressiveGoalDescription6 = cb.settings.progressiveGoalDescription6; var progressiveGoalDescription7 = cb.settings.progressiveGoalDescription7; var progressiveGoalDescription8 = cb.settings.progressiveGoalDescription8; var progressiveGoalDescription9 = cb.settings.progressiveGoalDescription9; var progressiveGoalDescription10 = cb.settings.progressiveGoalDescription10; var progressiveGoalDescription11 = cb.settings.progressiveGoalDescription11; var progressiveGoalDescription12 = cb.settings.progressiveGoalDescription12; var progressiveGoalDescription13 = cb.settings.progressiveGoalDescription13; var progressiveGoalDescription14 = cb.settings.progressiveGoalDescription14; var progressiveGoalDescription15 = cb.settings.progressiveGoalDescription15; var progressiveGoalDescription16 = cb.settings.progressiveGoalDescription16; var progressiveGoalDescription17 = cb.settings.progressiveGoalDescription17; var progressiveGoalDescription18 = cb.settings.progressiveGoalDescription18; var progressiveGoalDescription19 = cb.settings.progressiveGoalDescription19; var progressiveGoalDescription20 = cb.settings.progressiveGoalDescription20; var progressiveGoalAmount1 = cb.settings.progressiveGoalAmount1; var progressiveGoalAmount2 = cb.settings.progressiveGoalAmount2; var progressiveGoalAmount3 = cb.settings.progressiveGoalAmount3; var progressiveGoalAmount4 = cb.settings.progressiveGoalAmount4; var progressiveGoalAmount5 = cb.settings.progressiveGoalAmount5; var progressiveGoalAmount6 = cb.settings.progressiveGoalAmount6; var progressiveGoalAmount7 = cb.settings.progressiveGoalAmount7; var progressiveGoalAmount8 = cb.settings.progressiveGoalAmount8; var progressiveGoalAmount9 = cb.settings.progressiveGoalAmount9; var progressiveGoalAmount10 = cb.settings.progressiveGoalAmount10; var progressiveGoalAmount11 = cb.settings.progressiveGoalAmount11; var progressiveGoalAmount12 = cb.settings.progressiveGoalAmount12; var progressiveGoalAmount13 = cb.settings.progressiveGoalAmount13; var progressiveGoalAmount14 = cb.settings.progressiveGoalAmount14; var progressiveGoalAmount15 = cb.settings.progressiveGoalAmount15; var progressiveGoalAmount16 = cb.settings.progressiveGoalAmount16; var progressiveGoalAmount17 = cb.settings.progressiveGoalAmount17; var progressiveGoalAmount18 = cb.settings.progressiveGoalAmount18; var progressiveGoalAmount19 = cb.settings.progressiveGoalAmount19; var progressiveGoalAmount20 = cb.settings.progressiveGoalAmount20; var goalCounterDescription1 = cb.settings.goalcounterGoalDescription1; var goalCounterDescription2 = cb.settings.goalcounterGoalDescription2; var goalCounterDescription3 = cb.settings.goalcounterGoalDescription3; var goalCounterDescription4 = cb.settings.goalcounterGoalDescription4; var goalCounterDescription5 = cb.settings.goalcounterGoalDescription5; var goalCounterDescription6 = cb.settings.goalcounterGoalDescription6; var goalCounterDescription7 = cb.settings.goalcounterGoalDescription7; var goalCounterDescription8 = cb.settings.goalcounterGoalDescription8; var goalCounterDescription9 = cb.settings.goalcounterGoalDescription9; var goalCounterDescription10 = cb.settings.goalcounterGoalDescription10; var goalCounterAmount1 = cb.settings.goalcounterGoalAmount1; var goalCounterAmount2 = cb.settings.goalcounterGoalAmount2; var goalCounterAmount3 = cb.settings.goalcounterGoalAmount3; var goalCounterAmount4 = cb.settings.goalcounterGoalAmount4; var goalCounterAmount5 = cb.settings.goalcounterGoalAmount5; var goalCounterAmount6 = cb.settings.goalcounterGoalAmount6; var goalCounterAmount7 = cb.settings.goalcounterGoalAmount7; var goalCounterAmount8 = cb.settings.goalcounterGoalAmount8; var goalCounterAmount9 = cb.settings.goalcounterGoalAmount9; var goalCounterAmount10 = cb.settings.goalcounterGoalAmount10; var sequenceDescription1 = cb.settings.tipsequenceGoalDescription1; var sequenceDescription2 = cb.settings.tipsequenceGoalDescription2; var sequenceDescription3 = cb.settings.tipsequenceGoalDescription3; var sequenceDescription4 = cb.settings.tipsequenceGoalDescription4; var sequenceDescription5 = cb.settings.tipsequenceGoalDescription5; var sequenceDescription6 = cb.settings.tipsequenceGoalDescription6; var sequenceDescription7 = cb.settings.tipsequenceGoalDescription7; var sequenceDescription8 = cb.settings.tipsequenceGoalDescription8; var sequenceDescription9 = cb.settings.tipsequenceGoalDescription9; var sequenceDescription10 = cb.settings.tipsequenceGoalDescription10; var sequenceAmount1 = cb.settings.tipsequenceGoalAmount1; var sequenceAmount2 = cb.settings.tipsequenceGoalAmount2; var sequenceAmount3 = cb.settings.tipsequenceGoalAmount3; var sequenceAmount4 = cb.settings.tipsequenceGoalAmount4; var sequenceAmount5 = cb.settings.tipsequenceGoalAmount5; var sequenceAmount6 = cb.settings.tipsequenceGoalAmount6; var sequenceAmount7 = cb.settings.tipsequenceGoalAmount7; var sequenceAmount8 = cb.settings.tipsequenceGoalAmount8; var sequenceAmount9 = cb.settings.tipsequenceGoalAmount9; var sequenceAmount10 = cb.settings.tipsequenceGoalAmount10; var tipjarDescription1 = cb.settings.tipjarDescription1; var tipjarDescription2 = cb.settings.tipjarDescription2; var tipjarDescription3 = cb.settings.tipjarDescription3; var tipjarDescription4 = cb.settings.tipjarDescription4; var tipjarDescription5 = cb.settings.tipjarDescription5; var tipjarDescription6 = cb.settings.tipjarDescription6; var tipjarDescription7 = cb.settings.tipjarDescription7; var tipjarDescription8 = cb.settings.tipjarDescription8; var tipjarDescription9 = cb.settings.tipjarDescription9; var tipjarDescription10 = cb.settings.tipjarDescription10; var tipjarAmount1 = cb.settings.tipjarAmount1; var tipjarAmount2 = cb.settings.tipjarAmount2; var tipjarAmount3 = cb.settings.tipjarAmount3; var tipjarAmount4 = cb.settings.tipjarAmount4; var tipjarAmount5 = cb.settings.tipjarAmount5; var tipjarAmount6 = cb.settings.tipjarAmount6; var tipjarAmount7 = cb.settings.tipjarAmount7; var tipjarAmount8 = cb.settings.tipjarAmount8; var tipjarAmount9 = cb.settings.tipjarAmount9; var tipjarAmount10 = cb.settings.tipjarAmount10; var tipjarRecycle1 = cb.settings.tipjarRecycle1; var tipjarRecycle2 = cb.settings.tipjarRecycle2; var tipjarRecycle3 = cb.settings.tipjarRecycle3; var tipjarRecycle4 = cb.settings.tipjarRecycle4; var tipjarRecycle5 = cb.settings.tipjarRecycle5; var tipjarRecycle6 = cb.settings.tipjarRecycle6; var tipjarRecycle7 = cb.settings.tipjarRecycle7; var tipjarRecycle8 = cb.settings.tipjarRecycle8; var tipjarRecycle9 = cb.settings.tipjarRecycle9; var tipjarRecycle10 = cb.settings.tipjarRecycle10; var goalraceDesc1 = cb.settings.goalraceDesc1; var goalraceDesc2 = cb.settings.goalraceDesc2; var goalraceAmount1 = cb.settings.goalraceAmount1; var goalraceAmount2 = cb.settings.goalraceAmount2; var currentGoalRace1Tips = 0; var currentGoalRace2Tips = 0; var yellow = "#f4d599"; // Used for clock countdown var red = "#f4c1bc"; // Used for last 2 minutes of clock countdown var ticketHolderBgColor = '#e8fbe8' // Light green highlighting for ticket show buyers var dashLine80 = new Array(80).join("-"); var dashLine60 = new Array(60).join("-"); var dashLine70 = new Array(70).join("-"); var dashLine90 = new Array(90).join("-"); var backgroundImage = ''; var textColor = 'black'; var leftjust1 = 20; var leftjust2 = 20; var leftjust3 = 20; var topjust1 = 4; var topjust2 = 26; var topjust3 = 50; // Arrays */ var fanClubList = []; var VIPListArray = []; var extFanListArray = []; var ticketShowViewerList = []; var raceTipNotesOn = []; var moderatorList = {name: [], type: []}; var tipCountArray = {name: [], amount: []}; var ticketCumulative = {name: [], amount: []}; var outstandingTicketArray = []; var otChangesArray = {name: [], type: []}; var ticketShowExtraTickets = {name: [], count: []}; var progGoalArray = {desc: [], amt: [], recyc: []}; var sequenceArray = {desc: [], amt: []}; var goalCounterArray = {desc: [], amt: []}; var tipjarGoalArray = {desc: [], amt: [], recyc: []}; var drawpanel = {panel: {}}; var raceUnclaimedVotes = {name: [], amount: []}; } { // *********************************** Functions ************************************** { // Generic functions to set the color or separator characters function checkTextColor(color) { switch (color) { case "White/No color": return "#FFFFFF"; case "Black": return "#000000"; case "Dark Blue": return "#0629AC"; case "Dark Pink": return "#FF6680"; case "Dark Green": return "#006600"; case "Dark Red": return "#cc0000"; case "Dark Purple": return "#3d003d"; case "Dark Grey": return "#737373"; case "Dark Orange": return "#e77400"; case "Dark Aqua": return "#006767"; case "Dark Gold": return "#998100"; case "Dark Teal": return "#003f1f"; case "Dark Brown": return "#582c00"; case "Dark Bronze": return "#a56728"; case "Dark Periwinkle": return "#155bd7"; case "Dark Fuschia": return "#d6155c"; case "Dark Lime": return "#6b790c"; case "Dark Plum": return "#7f13bf"; default: if (/^#[0-9A-F]{6}$/i.test(color)) { return color; } else if (/^[0-9A-F]{6}$/i.test(color)) { return ('#' + color); } else { return ("default"); } } } function checkBgColor(color) { switch (color) { case "White/No color": return "#FFFFFF"; case "Light Aqua": return "#adeaea"; case "Light Pink": return "#FFE6EA"; case "Light Green": return "#94e594"; case "Light Red": return "#ff9a9a"; case "Light Purple": return "#f2cdff"; case "Light Orange": return "#ffd9b3"; case "Light Grey": return "#e6e6e6"; case "Light Blue": return "#d1eaee"; case "Light Yellow": return "#ffff94" case "Cream": return "#f9f6ed" case "Light Bronze": return "#ebccad"; case "Light Periwinkle": return "#d7e4fb"; case "Light Teal": return "#d7fbee"; case "Light Fuschia": return "#fbd7e4"; case "Light Lime": return "#ecf6a7"; case "Light Plum": return "#e3c0f9"; default: if (/^#[0-9A-F]{6}$/i.test(color)) { return color; } else if (/^[0-9A-F]{6}$/i.test(color)) { return ('#' + color); } else { return ("default"); } } } //********** Build Moderator Array ************** function populateModeratorArray(user,type) { if (!cbjs.arrayContains(moderatorList,user,type)) { moderatorList.name.push(user); moderatorList.type.push(type); } else { return; } } function populateFanClubArray(user) { if (!cbjs.arrayContains(fanClubList,user)) { fanClubList.push(user); } else { return; } } function addRmvVIP(user, mod, mode) { if (mode == 'a') { if(cbjs.arrayContains(VIPListArray,user)) { cb.sendNotice(user + ' has already been added to the VIP list.', mod, yellow); } else { VIPListArray.push(user); cb.sendNotice('You have added ' + user + ' to the VIP list. Note this is only applied during this session, permanent VIP list changes must be made to the list defined when starting the Fembot. This list should also be saved to a separate document.', mod, yellow); cb.sendNotice('Congratulations! You have been added to the VIP list!', user, yellow); } } else if (mode == 'r') { if(cbjs.arrayContains(VIPListArray,user)) { cbjs.arrayRemove(VIPListArray,user); cb.sendNotice('You have removed ' + user + ' from the VIP list. Note this is only applied during this session, permanent VIP list changes must be made to the list defined when starting the Fembot. This list should also be saved to a separate document.', mod, yellow); } else { cb.sendNotice(user + ' is not on the VIP list.', mod, yellow); } } } function addRmvExtFan(user, mod, mode) { if(mode == 'a') { if(cbjs.arrayContains(extFanListArray,user)) { cb.sendNotice(user + ' has already been added to the External Fan Club list.', mod, yellow); } else { extFanListArray.push(user); cb.sendNotice('You have added ' + user + ' to the External Fan Club list. Note this is only applied during this session, permanent External Fan Club list changes must be made to the list defined when starting the Fembot. This list should also be saved to a separate document.', mod, yellow); cb.sendNotice('Congratulations! You have been added to the External Fan Club list!', user, yellow); } } else if(mode == 'r') { if(cbjs.arrayContains(extFanListArray,user)) { cbjs.arrayRemove(extFanListArray,user); cb.sendNotice('You have removed ' + user + ' from the External Fan Club list. Note this is only applied during this session, permanent Fan Club list changes must be made to the list defined when starting the Fembot. This list should also be saved to a separate document.', mod, yellow); } else { cb.sendNotice(user + ' is not on the External Fan Club list.', mod, yellow); } } } //********** Tipper List ************** function findTipper(user) { for (var i = 0; i < tipCountArray.name.length; i++) { if(tipCountArray.name[i] == user) { break; } } return i; } //********** Set which Feature is Running ************** function setAppFeature(startapp, setby) { if (startapp == whichApp && startapp != 'none') { cb.sendNotice('The requested App feature is already running.', "", yellow, "", "bold"); } else { switch (whichApp) { case 'goals': { setProgressiveGoalsToggle('off', setby); break; } case 'goalcount': { setGoalCounterToggle('off', setby); break; } case 'ticket': { setTicketShowToggle('off', setby); break; } case 'sequence': { setSequenceToggle('off', setby); break; } case 'tipjar': { setTipJarToggle('off', setby); break; } case 'goalrace': { setGoalRaceToggle('off', setby); break; } } switch (startapp) { case 'goals': { setProgressiveGoalsToggle('on', setby); break; } case 'goalcount': { setGoalCounterToggle('on', setby); break; } case 'ticket': { setTicketShowToggle('on', setby); break; } case 'sequence': { setSequenceToggle('on', setby); break; } case 'tipjar': { setTipJarToggle('on', setby); break; } case 'goalrace': { setGoalRaceToggle('on', setby); break; } case 'none': { whichApp = 'none'; changeRoomSubject(); break; } } cb.drawPanel(); } } function resetApp() { switch (whichApp) { case 'goals': { initProgGoal(); break; } case 'goalcount': { initGoalCounter(); break; } case 'sequence': { initSequence(); break; } case 'tipjar': { initTipJar(); break; } case 'goalrace': { initTipJar(); break; } } cb.drawPanel(); } // *********************************** Length of show ************************************** function clockTimeCal() { timeNow = new Date(); return timeNow - ultraAppStartTime.getTime(); } function timeOnline() { var timeElapsed = clockTimeCal(); var clockMS = timeElapsed % 1000; var clockSeconds = ((timeElapsed - clockMS) % 60000); var clockMinutes = ((timeElapsed - clockSeconds - clockMS) % 3600000); var clockHours = (timeElapsed - clockMinutes - clockSeconds - clockMS); clockSeconds = clockSeconds / 1000; clockMinutes = clockMinutes / 60000; clockHours = clockHours / 3600000; if (clockHours > 0) { return clockHours + " hour" + (clockHours > 1 ? "s" : "") + " and " + clockMinutes + " minute" + (clockMinutes > 1 ? "s" : ""); } else if (clockMinutes > 0 && clockSeconds === 0) { return clockMinutes + " minute" + (clockMinutes > 1 ? "s" : ""); } else if (clockMinutes > 0) { return clockMinutes + " minute" + (clockMinutes > 1 ? "s" : "") + " and " + clockSeconds + " second" + (clockSeconds > 1 ? "s" : ""); } else { return clockSeconds + " second" + (clockSeconds > 1 ? "s" : ""); } } // *********************************** Progressive Goal Functions ************************************** function setProgressiveGoalsToggle(option, setby) { if (option == 'on') { progGoalColors(); whichApp = 'goals'; initProgGoal(); if (progGoalArray.desc.length <= 1) { cb.sendNotice(dashLine70 + '\n* ' + setby + ' has started the Progressive Goal Feature.\n* There is a single goal for the show.\n' + dashLine70, '', progGoalBgColor, progGoalTxtColor, 'bold'); } else { cb.sendNotice(dashLine70 + '\n* ' + setby + ' has started the Progressive Goal Feature.\n* There are ' + totalProgGoals + ' goals in the current show.\n' + dashLine70, '', progGoalBgColor, progGoalTxtColor, 'bold'); } } else if (option == 'off') { cb.sendNotice(dashLine70 + '\n* ' + setby + ' has ended the Progressive Goal Feature.\n' + dashLine70, '', progGoalBgColor, progGoalTxtColor, 'bold'); } } function initProgGoal() { finalGoalMet = false; totalProgGoals = progGoalArray.desc.length; currentGoalTips = 0; currentGoal = 1; currentGoalDesc = progGoalArray.desc[currentGoal-1]; currentGoalTotal = progGoalArray.amt[currentGoal-1]; changeRoomSubject(); } function progGoalColors() { progGoalNoticesTxtColor = checkTextColor("Dark Red"); progGoalNoticesBgColor = checkBgColor("Cream"); if (cb.settings.progressiveGoalTextColor == 'Custom') { progGoalTxtColor = checkTextColor(cb.settings.progressiveGoalCustomTextColor); if (progGoalTxtColor == 'default') { cb.sendNotice('Progressive Goals - Error while setting the text color. It has to be in a HEX format. Using default value.', cb.room_slug, yellow, '', 'bold'); progGoalTxtColor = '#FFFFFF'; } } else { progGoalTxtColor = checkTextColor(cb.settings.progressiveGoalTextColor); } if (cb.settings.progressiveGoalBgColor == 'Custom') { progGoalBgColor = checkBgColor(cb.settings.progressiveGoalCustomBgColor); if (progGoalBgColor == 'default') { cb.sendNotice('Leaderboard - Error while setting the background color. It has to be in a HEX format. Using default value.', cb.room_slug, yellow, '', 'bold'); progGoalBgColor = '#FFFFFF'; } } else { progGoalBgColor = checkBgColor(cb.settings.progressiveGoalBgColor); } } function restartGoal() { currentGoal--; currentGoalTips = 0; finalGoalMet = false; nextGoal(); cb.drawPanel(); } function updateGoal(goalnum) { currentGoalDesc = progGoalArray.desc[currentGoal-1]; currentGoalTotal = progGoalArray.amt[currentGoal-1]; changeRoomSubject(); cb.drawPanel(); } // *********************************** Tip Goal Counter Functions ************************************** function setGoalCounterToggle(option, setby) { if (option == 'on') { goalCounterColors(); whichApp = 'goalcount'; initGoalCounter(); cb.sendNotice(dashLine90 + '\n* ' + setby + ' has started the Tip Goal Counter Feature.\n* There are prizes at specifc goal count levels (' + (cbjs.arrayJoin(goalCounterArray.amt, 'g, ')) + 'g).\n* You can tip toward the goal, the app will keep track of progress.\n' + dashLine90, '', goalCountBgColor, goalCountTxtColor, 'bold'); } else if (option == 'off') { cb.sendNotice(dashLine70 + '\n* ' + setby + ' has ended the Tip Goal Counter Feature.\n' + dashLine70, '', goalCountBgColor, goalCountTxtColor, 'bold'); } } function initGoalCounter() { finalGoalMet = false; totalGoalLevels = goalCounterArray.desc.length; currentGoalTips = 0; currentGoalCountAmt = 0; currentGoalLevel = 1; currentGoalDesc = goalCounterArray.desc[currentGoalLevel-1]; currentGoalCountLevelAmt = goalCounterArray.amt[currentGoalLevel-1]; currentGoalTotal = cb.settings.goalcounterPerGoalAmount; changeRoomSubject(); } function goalCounterColors() { goalCountNoticesTxtColor = checkTextColor("Dark Red"); goalCountNoticesBgColor = checkBgColor("Cream"); if (cb.settings.goalcounterTextColor == 'Custom') { goalCountTxtColor = checkTextColor(cb.settings.goalcounterCustomTextColor); if (goalCountTxtColor == 'default') { cb.sendNotice('Goal Counter - Error while setting the text color. It has to be in a HEX format. Using default value.', cb.room_slug, yellow, '', 'bold'); goalCountTxtColor = '#FFFFFF'; } } else { goalCountTxtColor = checkTextColor(cb.settings.goalcounterTextColor); } if (cb.settings.goalcounterBgColor == 'Custom') { goalCountBgColor = checkBgColor(cb.settings.goalcounterCustomBgColor); if (goalCountBgColor == 'default') { cb.sendNotice('Goal Counter - Error while setting the background color. It has to be in a HEX format. Using default value.', cb.room_slug, yellow, '', 'bold'); goalCountBgColor = '#FFFFFF'; } } else { goalCountBgColor = checkBgColor(cb.settings.goalcounterBgColor); } } function updateGoalCount(goalnum) { currentGoalDesc = goalCounterArray.desc[currentGoalLevel-1]; currentGoalCountLevelAmt = goalCounterArray.amt[currentGoalLevel-1]; changeRoomSubject(); cb.drawPanel(); } function changeCountGoal(newgoal) { currentGoalTotal = newgoal; cb.drawPanel(); } // *********************************** Tip Jar Functions ************************************** function setTipJarToggle(option, setby) { if (option == 'on') { tipJarColors(); whichApp = 'tipjar'; initTipJar(); cb.sendNotice(dashLine90 + '\n* ' + setby + ' has started the Tip Jar Feature.\n* Once goal is met, the prize will be performed until the tip jar is empty.\n' + dashLine90, '', tipjarBgColor, tipjarTxtColor, 'bold'); } else if (option == 'off') { cb.sendNotice(dashLine60 + '\n* ' + setby + ' has ended the Tip Jar Feature.\n' + dashLine60, '', tipjarBgColor, tipjarTxtColor, 'bold'); } } function initTipJar() { finalGoalMet = false; tipJarRunning = false; tipJarWaiting = false; currentGoalLevel = 1; totalTipJarLevels = tipjarGoalArray.desc.length; currentGoalTotal = tipjarGoalArray.amt[currentGoalLevel-1]; currentGoalTips = 0; currentGoalDesc = tipjarGoalArray.desc[currentGoalLevel-1]; currentGoalTotal = tipjarGoalArray.amt[currentGoalLevel-1]; currentGoalRecycleTotal = tipjarGoalArray.recyc[currentGoalLevel-1]; currentGoalRecycleCount = 0; changeRoomSubject(); } function tipJarColors() { tipjarNoticesTxtColor = checkTextColor("Dark Red"); tipjarNoticesBgColor = checkBgColor("Cream"); if (cb.settings.tipjarTextColor == 'Custom') { tipjarTxtColor = checkTextColor(cb.settings.tipjarCustomTextColor); if (tipjarTxtColor == 'default') { cb.sendNotice('Tip Jar - Error while setting the text color. It has to be in a HEX format. Using default value.', cb.room_slug, yellow, '', 'bold'); tipjarTxtColor = '#FFFFFF'; } } else { tipjarTxtColor = checkTextColor(cb.settings.tipjarTextColor); } if (cb.settings.tipjarBgColor == 'Custom') { tipjarBgColor = checkBgColor(cb.settings.tipjarCustomBgColor); if (tipjarBgColor == 'default') { cb.sendNotice('Tip Jar - Error while setting the background color. It has to be in a HEX format. Using default value.', cb.room_slug, yellow, '', 'bold'); tipjarBgColor = '#FFFFFF'; } } else { tipjarBgColor = checkBgColor(cb.settings.tipjarBgColor); } } function startTipJar() { tipJarRunning = true; changeRoomSubject(); cb.setTimeout(checkJar, jarEmptyRate); } function checkJar() { if (tipJarRunning) { if (tipJarCurrentTokens > 0) { tipJarCurrentTokens -= jarEmptyTokens; if (tipJarCurrentTokens <= 0) { tipJarCurrentTokens = 0; tipjarIsEmpty(); changeRoomSubject(); } } cb.setTimeout(checkJar, jarEmptyRate); } cb.drawPanel(); } function setDrainRate(level) { switch (level) { case 1: { jarEmptyRate = 1000; jarEmptyTokens = 5; drainLevel = 1; break; } case 2: { jarEmptyRate = 1000; jarEmptyTokens = 4; drainLevel = 2; break; } case 3: { jarEmptyRate = 1000; jarEmptyTokens = 3; drainLevel = 3; break; } case 4: { jarEmptyRate = 1000; jarEmptyTokens = 2; drainLevel = 4; break; } case 5: { jarEmptyRate = 1000; jarEmptyTokens = 1; drainLevel = 5; break; } case 6: { jarEmptyRate = 2000; jarEmptyTokens = 1; drainLevel = 6; break; } case 7: { jarEmptyRate = 3000; jarEmptyTokens = 1; drainLevel = 7; break; } case 8: { jarEmptyRate = 4000; jarEmptyTokens = 1; drainLevel = 8; break; } case 9: { jarEmptyRate = 5000; jarEmptyTokens = 1; drainLevel = 9; break; } case 10: { jarEmptyRate = 10000; jarEmptyTokens = 1; drainLevel = 10; break; } } } function drainRateText() { switch (drainLevel) { case 1: { return '5 tokens per second'; } case 2: { return '4 tokens per second'; } case 3: { return '3 tokens per second'; } case 4: { return '2 tokens per second'; } case 5: { return '1 token per second'; } case 6: { return '1 token every 2 seconds'; } case 7: { return '1 token every 3 seconds'; } case 8: { return '1 token every 4 seconds'; } case 9: { return '1 token every 5 seconds'; } case 10: { return '1 token every 10 seconds'; } default: { return 'Invalid'; } } } function tipjarIsEmpty() { if (currentGoalRecycleCount >= tipjarGoalArray.recyc[currentGoalLevel-1]) { if (currentGoalLevel < totalTipJarLevels) { if (cb.settings.tipjarGoalRecycle == 'Recycle Goal or Advance to Next Goal') { if (cb.settings.tipjarAutoNext == 'Automatically Recycle / Start next goal') { tipJarRunning = false; cb.sendNotice('The Tip Jar is empty and the current goal is completed, the next goal is starting now, tip to reach goal!', '', tipjarBgColor, tipjarTxtColor, 'bold'); nextGoal(); } else { tipJarRunning = false; tipJarWaiting = true; cb.sendNotice('The Tip Jar is empty and the current goal is completed, the broadcaster will start the next goal when ready', '', tipjarNoticesBgColor, tipjarNoticesTxtColor, 'bold'); cb.sendNotice('Broadcaster: The Tip Jar is configured for manual advance, please use the "/next" command to start the next goal. This will advance through the specified number of Recycles (if any) and then to next goal.', cb.room_slug, yellow); cb.sendNotice('Moderators: The Tip Jar is configured for manual advance, please use the "/next" command to start the next goal. This will advance through the specified number of Recycles (if any) and then to next goal.', '', yellow, '', '', 'red'); } } else { cb.sendNotice('The Tip Jar is empty, you can tip to add to the jar again and re-start the prize', '', tipjarNoticesBgColor, tipjarNoticesTxtColor, 'bold'); cb.sendNotice('Broadcaster: The Tip Jar is configured to allow tipping to continue on the current goal, therefore it will not advance to the next goal or cycle unless you use the command "/next" to advance. This will advance through the specified number of Recycles (if any) and then to next goal.', cb.room_slug, yellow); cb.sendNotice('Moderators: The Tip Jar is configured to allow tipping to continue on the current goal, therefore it will not advance to the next goal or cycle unless you use the command "/next" to advance. This will advance through the specified number of Recycles (if any) and then to next goal.', '', yellow, '', '', 'red'); } } else { if (cb.settings.tipjarGoalRecycle == 'Tip to continue current goal') { cb.sendNotice('All goals are complete! The Tip Jar is empty, you can tip to fill up the jar again and re-start the prize.', '', tipjarNoticesBgColor, tipjarNoticesTxtColor, 'bold'); } else { currentGoalTips = 0; tipJarRunning = false; finalGoalMet = true; cb.sendNotice('The Tip Jar is empty and the last goal is completed!!', '', tipjarNoticesBgColor, tipjarNoticesTxtColor, 'bold'); } } } else { if (cb.settings.tipjarGoalRecycle == 'Recycle Goal or Advance to Next Goal') { if (cb.settings.tipjarAutoNext == 'Automatically Recycle / Start next goal') { tipJarRunning = false; currentGoalRecycleCount++; currentGoalTips = 0; cb.sendNotice('The Tip Jar has emptied and the current goal recycled, tip to goal to start the prize again!', '', tipjarNoticesBgColor, tipjarNoticesTxtColor, 'bold'); } else { tipJarRunning = false; tipJarWaiting = true; cb.sendNotice('The Tip Jar is empty and the current goal is completed and awaiting recycle, the broadcaster will restart the goal when ready', '', tipjarNoticesBgColor, tipjarNoticesTxtColor, 'bold'); cb.sendNotice('Broadcaster: The Tip Jar is configured for manual advance, and this goal is configured for Recycle. Use the command "/next" to start the next cycle of the current goal. This will advance through the specified number of Recycles, and if continued, to next goal (if any).', cb.room_slug, yellow); cb.sendNotice('Moderators: The Tip Jar is configured for manual advance, and this goal is configured for Recycle. Use the command "/next" to start the next cycle of the current goal. This will advance through the specified number of Recycles, and if continued, to next goal (if any).', '', yellow, '', '', 'red'); } } else { cb.sendNotice('The Tip Jar is empty, you can tip to add to the jar again and re-start the prize', '', tipjarNoticesBgColor, tipjarNoticesTxtColor, 'bold'); cb.sendNotice('Broadcaster: The Tip Jar is configured to allow tipping to continue on the current cycle, therefore it will not Recycle unless you use the command "/next" to advance. This will continue through the specified number of Recycles, and if continued, to next goal (if any).', cb.room_slug, yellow); cb.sendNotice('Moderators: The Tip Jar is configured to allow tipping to continue on the current cycle, therefore it will not Recycle unless you use the command "/next" to advance. This will continue through the specified number of Recycles, and if continued, to next goal (if any).', '', yellow,'', '', 'red'); } } } function updateTipjarGoal(goalnum,amt,desc) { currentGoalDesc = tipjarGoalArray.desc[currentGoalLevel-1]; currentGoalTotal = tipjarGoalArray.amt[currentGoalLevel-1]; changeRoomSubject(); cb.drawPanel(); } // *********************************** Sequence Goal Functions ************************************** function setSequenceToggle(option, setby) { if (option == 'on') { sequenceColors(); whichApp = 'sequence'; initSequence(); cb.sendNotice(dashLine80 + '\n* ' + setby + ' has started the Tip Sequence Feature.\n* Tip in ' + (tipSequenceDirection == 'up' ? 'Ascending' : 'Descending') + ' Sequence to the final goal.\n* There are prizes at specifc count sequence levels (' + (cbjs.arrayJoin(sequenceArray.amt, ', ')) + ').\n' + dashLine80, '', sequenceBgColor, sequenceTxtColor, 'bold'); } else if (option == 'off') { cb.sendNotice(dashLine60 + '\n* ' + setby + ' has ended the Tip Sequence Feature.\n' + dashLine60, '', sequenceBgColor, sequenceTxtColor, 'bold'); } } function initSequence() { currentGroupTipAmt = 0; finalGoalMet = false; currentGoalLevel = 1; currentGoalSequence = sequenceArray.amt[currentGoalLevel-1]; currentGoalDesc = sequenceArray.desc[currentGoalLevel-1]; if (tipSequenceDirection == 'up') { nextSequence = lowSequence; endSequence = highSequence; } else if (tipSequenceDirection == 'down') { endSequence = lowSequence; nextSequence = highSequence; } if (cb.settings.tipsequenceChatMsg == 'Yes') { setChatMsgToggle('on',cb.room_slug, 'init'); } if (cb.settings.tipsequenceGroupTips == 'Group Tipping') { setGroupTipToggle('on', cb.room_slug, 'init'); } sequenceTotalTipsGoal = sequenceTotal(lowSequence,highSequence); changeRoomSubject(); } function sequenceTotal(low,high) { sum = 0; for (var i = low; i <= high ; i++) { sum += i; } return sum } function sequenceColors() { sequenceNoticesTxtColor = checkTextColor("Dark Red"); sequenceNoticesBgColor = checkBgColor("Cream"); if (cb.settings.tipsequenceTextColor == 'Custom') { sequenceTxtColor = checkTextColor(cb.settings.tipsequenceCustomTextColor); if (sequenceTxtColor == 'default') { cb.sendNotice('Tip Sequence - Error while setting the text color. It has to be in a HEX format. Using default value.', cb.room_slug, yellow, '', 'bold'); sequenceTxtColor = '#FFFFFF'; } } else { sequenceTxtColor = checkTextColor(cb.settings.tipsequenceTextColor); } if (cb.settings.tipsequenceBgColor == 'Custom') { sequenceBgColor = checkBgColor(cb.settings.tipsequenceCustomBgColor); if (sequenceBgColor == 'default') { cb.sendNotice('Tip Sequence - Error while setting the background color. It has to be in a HEX format. Using default value.', cb.room_slug, yellow, '', 'bold'); sequenceBgColor = '#FFFFFF'; } } else { sequenceBgColor = checkBgColor(cb.settings.tipsequenceBgColor); } } function updateSequenceGoal(goalnum) { currentGoalDesc = sequenceArray.desc[currentGoalLevel-1]; currentGoalSequence = sequenceArray.amt[currentGoalLevel-1]; changeRoomSubject(); cb.drawPanel(); } function setChatMsgToggle(option, mod, mode) { if(option == 'on') { if(chatMsgToggle == 1) { if(mode != 'init') { cb.sendNotice('The chat notice toggle is already turned on.', mod, yellow); } } else { chatMsgToggle = 1; if(mode != 'init') { cb.sendNotice('You have enabled the display of chat notices for tip sequences completed.', mod, yellow); } } } else if(option == 'off') { if(chatMsgToggle == 0) { if(mode != 'init') { cb.sendNotice('The chat notice toggle is already turned off.', mod, yellow); } } else { chatMsgToggle = 0; if(mode != 'init') { cb.sendNotice('You have disabled the display of chat notices for tip sequences completed.', mod, yellow); } } } } function setGroupTipToggle(option, mod, mode) { if(option == 'on') { if(groupTipToggle == 1) { if(mode != 'init') { cb.sendNotice('The group tipping feature is already turned on.', mod, yellow); } } else { groupTipToggle = 1; if(mode != 'init') { cb.sendNotice('You have enabled the group tipping feature.', mod, yellow); } cb.drawPanel(); } } else if(option == 'off') { if(groupTipToggle == 0) { if(mode != 'init') { cb.sendNotice('The group tipping feature is already turned off.', mod, yellow); } } else { groupTipToggle = 0; if(mode != 'init') { cb.sendNotice('You have disabled the group tipping feature.', mod, yellow); } cb.drawPanel(); } } } function changeEndSequence(newseq) { endSequence = newseq; if (tipSequenceDirection == 'up') { highSequence = newseq; } else { lowSequence = newseq; } sequenceTotalTipsGoal = sequenceTotal(lowSequence,highSequence); cb.drawPanel(); } //********** Record Tip and Track Goal Progress ************** function recordTip(tipAmount,tipBy,tipNote,isFan,isExtFan,isVIP) { switch (whichApp) { case 'goals': { if (tipBy != 'bc') { currentAppTotalGoal += tipAmount; currentSessionTotal += tipAmount; } if (!finalGoalMet) { savedCurrentGoalTips = currentGoalTips; currentGoalTips += tipAmount; if (currentGoalTips >= currentGoalTotal) { if (cb.settings.progressiveAutoNext == 'Auto-start next') { exceedGoal(tipAmount,tipBy); } else { goalComplete(); } } } cb.drawPanel(); break; } case 'goalcount': { if (tipBy != 'bc') { currentAppTotalCounter += tipAmount; currentSessionTotal += tipAmount; } if (!finalGoalMet) { savedCurrentGoalTips = currentGoalTips; currentGoalTips += tipAmount; if (currentGoalTips >= currentGoalTotal) { exceedGoal(tipAmount,tipBy); } } cb.drawPanel(); break; } case 'ticket': { if (tipBy != 'bc') { currentAppTotalTicket += tipAmount; currentSessionTotal += tipAmount; } if (cb.settings.ticketShowFanAppreciation != 'Yes') { if (!ticketShowEnded && !ticketSalesEnded && !cb.limitCam_userHasAccess(tipBy)) { if (isFan) { if (tipAmount >= cb.settings.ticketShowPriceFC) { addRmvTicket('addtip', tipBy, 'fan', tipAmount); } else { checkCumulative(tipBy,tipAmount,cb.settings.ticketShowPriceFC,'fan','ticket'); } } else if (isExtFan) { if (tipAmount >= cb.settings.ticketShowPriceEFC) { addRmvTicket('addtip', tipBy, 'fan', tipAmount); } else { checkCumulative(tipBy,tipAmount,cb.settings.ticketShowPriceEFC,'fan','ticket'); } } else if (isVIP) { if (tipAmount >= cb.settings.ticketShowPriceVIP) { addRmvTicket('addtip', tipBy, 'vip', tipAmount); } else { checkCumulative(tipBy,tipAmount,cb.settings.ticketShowPriceVIP,'vip','ticket'); } } else { if (tipAmount >= ticketPrice) { addRmvTicket('addtip', tipBy, 'common', tipAmount); } else { checkCumulative(tipBy,tipAmount,ticketPrice,'common','ticket'); } } } else if (!ticketShowEnded && !ticketSalesEnded && cb.limitCam_userHasAccess(tipBy) && cb.settings.ticketShowAllowGift === 'Yes') { if (tipAmount >= ticketPrice) { addRmvTicket('addextra', tipBy, 'common', tipAmount); } } ticketShowGoalProgress(tipAmount); } cb.drawPanel(); break; } case 'sequence': { if (tipBy != 'bc') { currentAppTotalSequence += tipAmount; currentSessionTotal += tipAmount; } if (!finalGoalMet) { if (tipAmount < nextSequence) { if (groupTipToggle == 1) { currentGroupTipAmt += tipAmount; if (currentGroupTipAmt >= nextSequence) { exceedGoal((currentGroupTipAmt - nextSequence),tipBy); } } } else { exceedGoal((tipAmount - nextSequence),tipBy); } } cb.drawPanel(); break; } case 'tipjar': { if (tipBy != 'bc') { currentAppTotalTipJar += tipAmount; currentSessionTotal += tipAmount; } if (tipJarRunning) { tipJarCurrentTokens += tipAmount; } else if (tipJarWaiting) { // Do not make goal updates } else { if (!finalGoalMet) { savedCurrentGoalTips = currentGoalTips; currentGoalTips += tipAmount; if (currentGoalTips >= currentGoalTotal) { exceedGoal(tipAmount,tipBy); } } } cb.drawPanel(); break; } case 'goalrace': { if (tipBy != 'bc') { currentAppTotalGoalRace += tipAmount; currentSessionTotal += tipAmount; } if (!finalGoalMet) { originalTipAmount = tipAmount; if (isFan && cb.settings.goalraceBonusFC > 0) { tipAmount = parseInt(tipAmount * (1 + cb.settings.goalraceBonusFC/100)); } else if (isExtFan && cb.settings.goalraceBonusEFC > 0) { tipAmount = parseInt(tipAmount * (1 + cb.settings.goalraceBonusEFC/100)); } else if (isVIP && cb.settings.goalraceBonusVIP > 0) { tipAmount = parseInt(tipAmount * (1 + cb.settings.goalraceBonusVIP/100)); } if (tipNote == goalraceDesc1) { currentGoalRace1Tips += tipAmount; if (currentGoalRace1Tips >= goalraceAmount1) { goalRaceWinner = goalraceDesc1; goalComplete(); } } else if (tipNote == goalraceDesc2) { currentGoalRace2Tips += tipAmount; if (currentGoalRace2Tips >= goalraceAmount2) { goalRaceWinner = goalraceDesc2; goalComplete(); } } else { raceAddUnclaimed(tipBy,originalTipAmount); } } cb.drawPanel(); break; } default: { if (tipBy != 'bc') { currentAppTotalNone += tipAmount; currentSessionTotal += tipAmount; cb.drawPanel(); } break; } } } function raceAddUnclaimed(tipBy,originalTipAmount) { if (cb.settings.goalraceAllowClaim == 'Yes') { if (!cbjs.arrayContains(raceUnclaimedVotes.name, tipBy)) { raceUnclaimedVotes.name.push(tipBy); raceUnclaimedVotes.amount.push(originalTipAmount); } else { index = raceUnclaimedVotes.name.indexOf(tipBy); raceUnclaimedVotes.amount[index] += originalTipAmount; } cb.sendNotice(tipBy + ', your tip was not recorded against a Goal Race choice, you can add your unclaimed tips to one of the goals with the command "/addrace1" for goal #1 or "/addrace2" for goal #2', '', goalraceBgColor, goalraceTxtColor, 'bold'); } } function checkCumulative(tipBy,tipAmount,cumTktPrice,usertype,saletype) { if (cb.settings.ticketShowCumulative == 'Yes') { if (!cbjs.arrayContains(ticketCumulative.name, tipBy)) { ticketCumulative.name.push(tipBy); ticketCumulative.amount.push(tipAmount); } else { index = ticketCumulative.name.indexOf(tipBy); if (ticketCumulative.amount[index] + tipAmount >= cumTktPrice) { if (saletype == 'ticket') { addRmvTicket('addtip', tipBy, usertype, tipAmount); } else if (saletype == 'presale') { addRmvPresale('addtip', tipBy); } ticketCumulative.amount[index] = 0; } else { ticketCumulative.amount[index] += tipAmount; } } } } function exceedGoal(tipAmount,tipBy) { switch (whichApp) { case 'goals': { excessTip = tipAmount - (currentGoalTotal - savedCurrentGoalTips); goalComplete(); if (!finalGoalMet) { nextGoal(); currentGoalTips = excessTip; if (currentGoalTips >= currentGoalTotal) { savedCurrentGoalTips = 0; exceedGoal(currentGoalTips); } } break; } case 'goalcount': { cb.sendNotice('Goal #' + (currentGoalCountAmt + 1) + ' has been Completed!', '', goalCountBgColor, goalCountTxtColor, 'bold'); excessTip = tipAmount - (currentGoalTotal - savedCurrentGoalTips); if ((currentGoalCountAmt + 1) >= goalCounterArray.amt[currentGoalLevel-1]) { goalComplete(); } if (!finalGoalMet) { nextGoal(); currentGoalTips = excessTip; if (currentGoalTips >= currentGoalTotal) { savedCurrentGoalTips = 0; exceedGoal(currentGoalTips); } } break; } case 'ticket': { break; } case 'sequence': { if (tipSequenceDirection == 'up') { nextSequence++; if (chatMsgToggle == 1 && nextSequence <= endSequence) { var nextgif = ':next' + nextSequence; cb.sendNotice('Tip Sequence ' + (nextSequence-1) + ' has been Completed --- ' + nextgif, '', sequenceBgColor, sequenceTxtColor, 'bold'); } if (nextSequence > sequenceArray.amt[currentGoalLevel-1]) { goalComplete(); if (nextSequence > endSequence) { finalGoalMet = true; changeRoomSubject(); cb.sendNotice(dashLine80 + '\n :CGGoal15 The Tip Sequence has been Completed!! :CGGoal15 \n \u2724 \u2749 \u2724 \u2749 \u2724 ' + getTopTipper() + ' was the top tipper!!! \u2724 \u2749 \u2724 \u2749 \u2724 \n' + dashLine80, '', sequenceBgColor, sequenceTxtColor, 'bold'); } else { nextGoal(); } } else { if (nextSequence > endSequence) { finalGoalMet = true; changeRoomSubject(); cb.sendNotice(dashLine80 + '\n :CGGoal15 The Tip Sequence has been Completed!! :CGGoal15 \n \u2724 \u2749 \u2724 \u2749 \u2724 ' + getTopTipper() + ' was the top tipper!!! \u2724 \u2749 \u2724 \u2749 \u2724 \n' + dashLine80, '', sequenceBgColor, sequenceTxtColor, 'bold'); } } } else if (tipSequenceDirection == 'down') { nextSequence--; if (chatMsgToggle == 1 && nextSequence >= endSequence) { var nextgif = ':next' + nextSequence; cb.sendNotice('Tip Sequence ' + (nextSequence+1) + ' has been Completed --- ' + nextgif, '', sequenceBgColor, sequenceTxtColor, 'bold'); } if (nextSequence < sequenceArray.amt[currentGoalLevel-1]) { goalComplete(); if (nextSequence < endSequence) { finalGoalMet = true; changeRoomSubject(); cb.sendNotice(dashLine80 + '\n :CGGoal15 The Tip Sequence has been Completed!! :CGGoal15 \n \u2724 \u2749 \u2724 \u2749 \u2724 ' + getTopTipper() + ' was the top tipper!!! \u2724 \u2749 \u2724 \u2749 \u2724 \n' + dashLine80, '', sequenceBgColor, sequenceTxtColor, 'bold'); } else { nextGoal(); } } else { if (nextSequence < endSequence) { finalGoalMet = true; changeRoomSubject(); cb.sendNotice(dashLine80 + '\n :CGGoal15 The Tip Sequence has been Completed!! :CGGoal15 \n \u2724 \u2749 \u2724 \u2749 \u2724 ' + getTopTipper() + ' was the top tipper!!! \u2724 \u2749 \u2724 \u2749 \u2724 \n' + dashLine80, '', sequenceBgColor, sequenceTxtColor, 'bold'); } } } currentGroupTipAmt = 0; excessTip = tipAmount; if (excessTip > 0) { if (!finalGoalMet) { if (excessTip < nextSequence) { if (groupTipToggle == 1) { currentGroupTipAmt += excessTip; } } else { exceedGoal((excessTip - nextSequence),tipBy); } } } break; } case 'tipjar': { goalComplete(); tipJarCurrentTokens = currentGoalTips; startTipJar(); break; } } } function goalComplete() { switch (whichApp) { case 'goals': { if (progGoalArray.desc.length == 1) { cb.sendNotice(dashLine60 + '\n :CGGoal15 The Goal has been met!! :CGGoal15 \n' + dashLine60, '', progGoalBgColor, progGoalTxtColor, 'bold'); finalGoalMet = true; changeRoomSubject(); } else if ((currentGoal) < progGoalArray.desc.length) { cb.sendNotice(dashLine60 + '\n :CGGoal15 Goal #' + currentGoal + ' (' + progGoalArray.desc[currentGoal-1] + ') has been met!! :CGGoal15 \n' + dashLine60, '', progGoalBgColor, progGoalTxtColor, 'bold'); if (cb.settings.progressiveAutoNext != 'Auto-start next') { cb.sendNotice('The goal has been completed and per configuration the app is awaiting manual advance using the "/next" command', '', yellow, '', '', 'red') cb.sendNotice('The goal has been completed and per configuration the app is awaiting manual advance using the "/next" command', cb.room_slug, yellow, '', '') } } else if ((currentGoal) == progGoalArray.desc.length) { finalGoalMet = true; changeRoomSubject(); cb.sendNotice(dashLine60 + '\n :CGGoal15 Final Goal (' + progGoalArray.desc[currentGoal-1] + ') has been met!! :CGGoal15 \n *****' + getTopTipper() + ' was the top tipper!!! *****\n' + dashLine60, '', progGoalBgColor, progGoalTxtColor, 'bold'); } break; } case 'goalcount': { if (goalCounterArray.desc.length == 1) { cb.sendNotice(dashLine80 + '\n :CGGoal15 The Goal Count has been met!! :CGGoal15 \n' + dashLine80, '', goalCountBgColor, goalCountTxtColor, 'bold'); } else if ((currentGoalLevel) < goalCounterArray.desc.length) { cb.sendNotice(dashLine80 + '\n :CGGoal15 Goal Count #' + currentGoalLevel + ' (' + goalCounterArray.desc[currentGoalLevel-1] + ') has been met!! :CGGoal15 \n' + dashLine80, '', goalCountBgColor, goalCountTxtColor, 'bold'); } else if ((currentGoalLevel) == goalCounterArray.desc.length) { finalGoalMet = true; changeRoomSubject(); cb.sendNotice(dashLine60 + '\n :CGGoal15 Final Goal (' + goalCounterArray.desc[currentGoalLevel-1] + ') has been met!! :CGGoal15 \n *****' + getTopTipper() + ' was the top tipper!!! *****\n' + dashLine60, '', goalCountBgColor, goalCountTxtColor, 'bold'); } break; } case 'ticket': { break; } case 'sequence': { cb.sendNotice(dashLine80 + '\n :CGGoal15 Tip Sequence Goal #' + currentGoalLevel + ' (' + sequenceArray.desc[currentGoalLevel-1] + ') has been met!! :CGGoal15 \n' + dashLine80, '', sequenceBgColor, sequenceTxtColor, 'bold'); break; } case 'tipjar': { cb.sendNotice(dashLine80 + '\n :CGGoal15 The Tip Jar goal has been met!! :CGGoal15 \n ***** The Tip Jar will start to empty, tip to keep it full! *****\n' + dashLine80, '', tipjarBgColor, tipjarTxtColor, 'bold'); changeRoomSubject(); break; } case 'goalrace': { cb.sendNotice(dashLine80 + '\n :CGGoal15 Goal Race Winner : ' + goalRaceWinner + ' !! :CGGoal15 \n' + dashLine80, '', goalraceBgColor, goalraceTxtColor, 'bold'); cb.sendNotice('The Goal Race has finished, you can use the command /restartrace to play the same race again (you can change the goal descriptions with the command /setgoal1 or /setgoal2), or change to a different App Feature.', '', yellow, '', 'bold'); finalGoalMet = true; changeRoomSubject(); break; } } } function advanceGoal() { switch (whichApp) { case 'goalcount': case 'goals': { currentGoalTips = 0; goalComplete(); if (!finalGoalMet) { nextGoal(); } break; } case 'sequence': { exceedGoal(); break; } case 'tipjar': { tipJarRunning = false; tipJarWaiting = false; if (currentGoalRecycleCount >= tipjarGoalArray.recyc[currentGoalLevel-1]) { if (currentGoalLevel < totalTipJarLevels) { nextGoal(); } else { currentGoalTips = 0; finalGoalMet = true; cb.sendNotice('The Tip Jar is empty and the last goal is completed!!', '', tipjarNoticesBgColor, tipjarNoticesTxtColor, 'bold'); } } else { currentGoalRecycleCount++; currentGoalTips = 0; } break; } } cb.drawPanel(); } function advanceGoalLevel() { switch (whichApp) { case 'goalcount': { currentGoalCountAmt = goalCounterArray.amt[currentGoalLevel-1]; goalComplete(); currentGoalLevel++; currentGoalDesc = goalCounterArray.desc[currentGoalLevel-1]; currentGoalCountLevelAmt = goalCounterArray.amt[currentGoalLevel-1]; if ((currentGoalLevel) <= goalCounterArray.desc.length) { changeRoomSubject(); } currentGoalTips = 0; break; } case 'sequence': { if (tipSequenceDirection == 'up') { nextSequence = sequenceArray.amt[currentGoalLevel-1] + 1; if (chatMsgToggle == 1 && nextSequence <= endSequence) { var nextgif = ':next' + nextSequence; cb.sendNotice('Tip Sequence ' + (nextSequence-1) + ' has been Completed --- ' + nextgif, '', sequenceBgColor, sequenceTxtColor, 'bold'); } goalComplete(); if (nextSequence > endSequence) { finalGoalMet = true; changeRoomSubject(); cb.sendNotice(dashLine80 + '\n :CGGoal15 The Tip Sequence has been Completed!! :CGGoal15 \n ***** ' + getTopTipper() + ' was the top tipper!!! *****\n' + dashLine80, '', sequenceBgColor, sequenceTxtColor, 'bold'); } else { nextGoal(); } } else if (tipSequenceDirection == 'down') { nextSequence = sequenceArray.amt[currentGoalLevel-1] - 1; if (chatMsgToggle == 1 && nextSequence >= endSequence) { var nextgif = ':next' + nextSequence; cb.sendNotice('Tip Sequence ' + (nextSequence+1) + ' has been Completed --- ' + nextgif, '', sequenceBgColor, sequenceTxtColor, 'bold'); } goalComplete(); if (nextSequence < endSequence) { finalGoalMet = true; changeRoomSubject(); cb.sendNotice(dashLine80 + '\n :CGGoal15 The Tip Sequence has been Completed!! :CGGoal15 \n ***** ' + getTopTipper() + ' was the top tipper!!! *****\n' + dashLine80, '', sequenceBgColor, sequenceTxtColor, 'bold'); } else { nextGoal(); } } break; } } cb.drawPanel(); } function getTopTipper() { var swapped, temp1, temp2; do { swapped = false; for (var i = 0; i < tipCountArray.amount.length ; i++) { if (tipCountArray.amount[i] < tipCountArray.amount[i + 1]) { temp1 = tipCountArray.amount[i]; temp2 = tipCountArray.name[i]; tipCountArray.amount[i] = tipCountArray.amount[i + 1]; tipCountArray.amount[i + 1] = temp1; tipCountArray.name[i] = tipCountArray.name[i + 1]; tipCountArray.name[i + 1] = temp2; swapped = true; } } } while (swapped); return tipCountArray.name[0]; } function nextGoal() { switch (whichApp) { case 'goals': { currentGoal++; currentGoalDesc = progGoalArray.desc[currentGoal-1]; currentGoalTotal = progGoalArray.amt[currentGoal-1]; changeRoomSubject(); break; } case 'goalcount': { currentGoalCountAmt++; if (currentGoalCountAmt >= goalCounterArray.amt[currentGoalLevel-1]) { currentGoalLevel++; currentGoalDesc = goalCounterArray.desc[currentGoalLevel-1]; currentGoalCountLevelAmt = goalCounterArray.amt[currentGoalLevel-1]; changeRoomSubject(); } break; } case 'ticket': { break; } case 'sequence': { currentGoalLevel++; currentGoalSequence = sequenceArray.amt[currentGoalLevel-1]; currentGoalDesc = sequenceArray.desc[currentGoalLevel-1]; changeRoomSubject(); break; } case 'tipjar': { currentGoalLevel++; currentGoalRecycleCount = 0; currentGoalTips = 0; currentGoalRecycleTotal = tipjarGoalArray.recyc[currentGoalLevel-1]; currentGoalTotal = tipjarGoalArray.amt[currentGoalLevel-1]; currentGoalDesc = tipjarGoalArray.desc[currentGoalLevel-1]; changeRoomSubject(); break; } } } function changeRoomSubject() { switch (whichApp) { case 'goals': { if (!finalGoalMet) { newsubject = 'Current Goal: ' + currentGoalDesc + ' at ' + currentGoalTotal + ' tokens. '; newsubject += goalSubjectText; if (progGoalArray.desc.length >= (currentGoal + 1)) { newsubject += ' --- Next Goal: ' + progGoalArray.desc[currentGoal]; } else if (progGoalArray.desc.length > 1) { newsubject += ' --- This is the Last Goal!'; } } else { newsubject = 'All Goals Have Been Completed!!! '; newsubject += goalSubjectText; } cb.changeRoomSubject(newsubject); break; } case 'goalcount': { if (!finalGoalMet) { newsubject = 'Remaining Goal Levels: '; startlevel = currentGoalLevel - 1; for (let i = startlevel; i < goalCounterArray.desc.length; i++) { if (goalCounterArray.desc[i] != '' && goalCounterArray.desc[i] != null) { newsubject += (i > startlevel ? ', ' : '') + goalCounterArray.amt[i] + ' goals (' + goalCounterArray.desc[i] + ')'; } else { break; } } newsubject += '. ' + counterSubjectText; } else { newsubject = 'All Goals Have Been Completed!!! '; newsubject += counterSubjectText; } cb.changeRoomSubject(newsubject); break; } case 'ticket': { if (cb.settings.ticketShowFanAppreciation == 'Yes') { if (showStage == 'ticketsales') { newsubject = 'FANCLUB APPRECIATION SHOW STARTING SOON! JOIN FANCLUB TO SEE! ' + ticketSubjectText; } else if (showStage == 'ticketshow' || showStage == 'showwarn' || showStage == 'showfinale') { newsubject = 'FANCLUB APPRECIATION SHOW IN PROGRESS! JOIN FANCLUB TO SEE! ' + ticketSubjectText; } else if (showStage == 'aftershow') { newsubject = 'FANCLUB APPRECIATION SHOW HAS ENDED. THANK YOU FANS! ' + ticketSubjectText; } else { newsubject = 'FANCLUB APPRECIATION SHOW'; } } else { if (showStage == 'ticketsales') { newsubject = 'TICKET SHOW SALES [' + ticketPrice + ' tokens]: ' + ticketSubjectText; } else if (showStage == 'ticketshow') { newsubject = 'TICKET SHOW IN PROGRESS [' + ticketPrice + ' tokens]: ' + ticketSubjectText; } else if (showStage == 'showwarn') { newsubject = 'TICKET SHOW ENDING SOON [' + ticketPrice + ' tokens]: ' + ticketSubjectText; } else if (showStage == 'showfinale') { newsubject = 'TICKET SHOW SALES ENDED -- DO NOT BUY A TICKET!: ' + ticketSubjectText; } else if (showStage == 'aftershow') { newsubject = 'TICKET SHOW HAS ENDED: ' + ticketSubjectText; } else { newsubject = 'TICKET SHOW'; } } cb.changeRoomSubject(newsubject); break; } case 'sequence': { if (!finalGoalMet) { newsubject = 'Remaining Sequence Goals: '; startlevel = currentGoalLevel - 1; if (tipSequenceDirection == 'up' && nextSequence <= sequenceArray.amt[sequenceArray.amt.length-1] || tipSequenceDirection == 'down' && nextSequence >= sequenceArray.amt[sequenceArray.amt.length-1]) { for (let i = startlevel; i < sequenceArray.desc.length; i++) { if (sequenceArray.desc[i] != '' && sequenceArray.desc[i] != null) { newsubject += (i > startlevel ? ', at ' : 'at ') + sequenceArray.amt[i] + ' (' + sequenceArray.desc[i] + ')'; } } } else { newsubject += 'Final Tip Sequence'; currentGoalSequence = endSequence; currentGoalDesc = 'Final Tip Sequence'; } newsubject += '. ' + sequenceSubjectText; } else { newsubject = 'All Goals Have Been Completed!!! '; newsubject += sequenceSubjectText; } cb.changeRoomSubject(newsubject); break; } case 'tipjar': { if (!finalGoalMet) { if (tipJarRunning) { newsubject = 'Tip Jar Prize has started! Tip to keep it full! The prize for this goal (' + tipjarGoalArray.desc[currentGoalLevel-1] + ') will end if the jar empties. '; newsubject += tipjarSubjectText; } else { if (tipJarWaiting) { newsubject = 'Tip Jar has emptied and is awaiting advance to next goal!!! '; newsubject += tipjarSubjectText; } else { newsubject = 'Tip Jar Goal: [' + tipjarGoalArray.amt[currentGoalLevel-1] + ' tokens]. At goal, the prize (' + tipjarGoalArray.desc[currentGoalLevel-1] + ') will be performed until the jar empties. '; newsubject += tipjarSubjectText; } } } else { newsubject = 'All Goals Have Been Completed!!! '; newsubject += tipjarSubjectText; } cb.changeRoomSubject(newsubject); break; } case 'goalrace': { if (!finalGoalMet) { newsubject = 'Goal Race! Tip to vote for [' + goalraceDesc1 + '] vs [' + goalraceDesc2 + '] ' + goalraceSubjectText; } else { newsubject = 'The Goal Race has been Completed! ' + goalRaceWinner + ' has won the race!!! '; } cb.changeRoomSubject(newsubject); break; } case 'none': { newsubject = 'Welcome! No app currently running. '; cb.changeRoomSubject(newsubject); break; } } } function listGoals(group,reqby) { switch (whichApp) { case 'goals': { if (group == 'all') { outString = 'Goal List (sent to All) :'; sendto = ''; } else { outString = 'Goal List (sent to You) :'; sendto = reqby; } let i = 0; while (progGoalArray.amt[i] > 0 && progGoalArray.desc[i] != null && progGoalArray.desc[i] != '') { outString += '\nGoal #' + (i+1) + ' : ' + progGoalArray.desc[i] + ' (' + progGoalArray.amt[i] + ' tokens)'; i++; } if (i > 0) { cb.sendNotice(dashLine60 + '\n' + outString + '\n' + dashLine60, sendto, progGoalBgColor, progGoalTxtColor, 'bold'); } else { cb.sendNotice('There are no goals configured', reqby, yellow); } break; } case 'goalcount': { if (group == 'all') { outString = 'Goal Level List (sent to All) :'; sendto = ''; } else { outString = 'Goal Level List (sent to You) :'; sendto = reqby; } let i = 0; while (goalCounterArray.amt[i] > 0 && goalCounterArray.desc[i] != null && goalCounterArray.desc[i] != '') { outString += '\nGoal Level #' + (i+1) + ' : ' + goalCounterArray.desc[i] + ' (' + goalCounterArray.amt[i] + ' goals)'; i++; } if (i > 0) { cb.sendNotice(dashLine60 + '\n' + outString + '\n' + dashLine60, sendto, goalCountBgColor, goalCountTxtColor, 'bold'); } else { cb.sendNotice('There are no goal levels configured', reqby, yellow); } break; } case 'sequence': { if (group == 'all') { outString = 'Sequence Level List (sent to All) :'; sendto = ''; } else { outString = 'Sequence Level List (sent to You) :'; sendto = reqby; } let i = 0; while (sequenceArray.amt[i] > 0 && sequenceArray.desc[i] != null && sequenceArray.desc[i] != '') { outString += '\nGoal Level #' + (i+1) + ' : ' + sequenceArray.desc[i] + ' (at sequence ' + sequenceArray.amt[i] + ')'; i++; } if (i > 0) { cb.sendNotice(dashLine60 + '\n' + outString + '\n' + dashLine60, sendto, sequenceBgColor, sequenceTxtColor, 'bold'); } else { cb.sendNotice('There are no sequence levels configured', reqby, yellow); } break; } case 'tipjar': { if (group == 'all') { outString = 'Tip Jar Goal List (sent to All) :'; sendto = ''; } else { outString = 'Tip Jar Goal List (sent to You) :'; sendto = reqby; } let i = 0; while (tipjarGoalArray.amt[i] > 0 && tipjarGoalArray.desc[i] != null && tipjarGoalArray.desc[i] != '') { outString += '\nGoal Level #' + (i+1) + ' : ' + tipjarGoalArray.desc[i] + ' (' + tipjarGoalArray.amt[i] + ' tokens) [recycle count: ' + tipjarGoalArray.recyc[i] + ']'; i++; } if (i > 0) { cb.sendNotice(dashLine60 + '\n' + outString + '\n' + dashLine60, sendto, tipjarBgColor, tipjarTxtColor, 'bold'); } else { cb.sendNotice('There are no goals configured', reqby, yellow); } break; } break; } } //********** Goal Bar ************** function goalBar(current,total) { if (current > 0 && total > 0) { percent = Math.round(100*(current/total)); } else { percent = 0; } bar = ''; full = '\u25C6'; half = '\u25C8'; empty = '\u25C7'; a = (percent - (percent % 10)) / 10; b = (percent % 10) > 0 ? 1 : 0; c = 10 - (a + b); bar += charRepeat(full, a); (b === 1 ? bar += half : bar += ""); bar += charRepeat(empty, c) return bar; } function charRepeat(d, e) { var string = ""; for (var index = 1; index <= e; index++) { string += d } return string; } // *********************************** Ultra App Ticket Show Functions ************************************** function setTicketShowToggle(option, sendto) { if(option == 'on') { initTicketShow(sendto); if (cb.settings.ticketShowFanAppreciation == 'Yes') { cb.sendNotice(dashLine80 + '\nToday\'s Ticket Show is a Fan Club Appreciation Show!\nOnly Fan Club Members and VIPs will be admitted to the show!\nPlease consider joining the Fan Club!\n' + dashLine80, sendto, ticketBgColor, ticketTxtColor, "bold"); } else { cb.sendNotice(dashLine90 + '\n* The Broadcaster has started selling tickets for the show.\n* Tickets are ' + ticketPrice + ' tokens.\n' + ticketModeMessage + '\n' + dashLine90, '', ticketBgColor, ticketTxtColor, 'bold'); cb.sendNotice('***Warning Regarding the Ticket Show***: Do not deactivate or restart the Ultra App once ticket sales have started without saving the ticket show list or you will lose the list of ticket holders.\n* If it is necessary to restart, please save the ticket list first (use /tickets to view and then copy) and then add those users back to the show once restarted (/addtickets).\n* If you are using Dorothy\'s Ultra Fembot alongside this app, it can save a backup ticket list for you if configured to do so.',cb.room_slug,yellow); if (cb.settings.ticketShowAllowGift === 'Yes') { cb.sendNotice(dashLine90 + '\n* The Broadcaster has enabled the the gifting of tickets. \n* You can buy extra tickets and gift them to others using the command "/giftticket user" \n* You can also give away your own ticket using the command "/givemyticketto user".\n' + dashLine90, '', ticketBgColor, ticketTxtColor, 'bold'); } } } else if(option == 'off') { if (cb.limitCam_isRunning()) { cb.sendNotice('The show is still hidden, please end the hidden show and return to a public broadcast using the command "/stopshow" before disabling the Ticket Show or changing the current app.',sendto,yellow); } else { if (ticketMinsRemain > 0 || ticketSecsRemain > 0) { stopTicketTimer(); } cb.sendNotice('You have disabled the Ultra App Ticket Show feature. The ticket list and outstanding ticket list are still intact.', sendto, yellow); cb.sendNotice(dashLine70 + '\n* ' + sendto + ' has ended the UltraApp Ticket Show Feature.\n' + dashLine70, '', ticketBgColor, ticketTxtColor, 'bold'); } } } function initTicketShow(sendto) { whichApp = 'ticket'; if (cb.limitCam_allUsersWithAccess().length > 0) { countTicketsSold = cb.limitCam_allUsersWithAccess().length; } else { countTicketsSold = 0; } if (presalesToggle == 1) { setPresalesToggle('off',sendto); } ticketShowColors(); showStage = 'ticketsales'; changeRoomSubject(); ticketSkipMin = false; ticketSkipSec = false; setTicketMode('init',sendto); ticketShowEnded = false; ticketSalesEnded = false; loadFreeTickets(); if (cb.settings.ticketShowEnableOT == 'Yes' && cb.settings.ticketShowFanAppreciation != 'Yes') { setTicketShowOtToggle('on',sendto) } cb.drawPanel(); } function ticketShowColors() { ticketNoticesTxtColor = checkTextColor("Dark Red"); ticketNoticesBgColor = checkBgColor("Cream"); ticketTxtColorFan = checkTextColor("Black"); ticketBgColorFan = checkBgColor("Light Green"); ticketTxtColorVIP = checkTextColor("Black"); ticketBgColorVIP = checkBgColor("Light Purple"); ticketTxtColorMod = checkTextColor("Black"); ticketBgColorMod = checkBgColor("Light Red"); if (cb.settings.ticketShowTextColor == 'Custom') { ticketTxtColor = checkTextColor(cb.settings.ticketShowCustomTextColor); if (ticketTxtColor == 'default') { cb.sendNotice('Ticket Show - Error while setting the text color. It has to be in a HEX format. Using default value.', cb.room_slug, yellow, '', 'bold'); ticketTxtColor = '#FFFFFF'; } } else { ticketTxtColor = checkTextColor(cb.settings.ticketShowTextColor); } if (cb.settings.ticketShowBgColor == 'Custom') { ticketBgColor = checkBgColor(cb.settings.ticketShowCustomBgColor); if (ticketBgColor == 'default') { cb.sendNotice('Ticket Show - Error while setting the background color. It has to be in a HEX format. Using default value.', cb.room_slug, yellow, '', 'bold'); ticketBgColor = '#FFFFFF'; } } else { ticketBgColor = checkBgColor(cb.settings.ticketShowBgColor); } } function setTicketShowOtToggle(option, sendto) { if(option == 'on') { if(ticketShowOtToggle == 1) { cb.sendNotice('The Outstanding Ticket feature is already enabled.',sendto,yellow); } else { ticketShowOtToggle = 1; cb.sendNotice(dashLine90 + '\n* ' + sendto + ' has started the Outstanding Ticket (OT) feature.\n* You can save your ticket if you have to leave before the show starts.\n* Use the command "/saveticket" to save it, but note that by\n* saving your ticket you will be REMOVED from the current show\'s list.\n* You can view the OT List with the command "/otlist".\n* You can redeem an outstanding ticket with the command "/useticket".\n' + dashLine90, '', ticketBgColor, ticketTxtColor, 'bold'); cb.sendNotice('***Notice Regarding Outstanding Tickets***: The Outstanding tickets added and removed during the show must be made permanent by updating the list on the App Launch page.\n* The list can be viewed using the command "/otlist".\n* The changes for the current session can be seen using "/otchanges".\n* It is recommended to save the list externally and update the OT list before the next show.\n* Changes made during the show are not saved beyond the current session.',cb.room_slug,yellow); } } else if(option == 'off') { if(ticketShowOtToggle == 0) { cb.sendNotice('The Outstanding Ticket feature is already disabled.',sendto,yellow); } else { ticketShowOtToggle = 0; cb.sendNotice('You have disabled the Outstanding Ticket (OT) feature.\n* The outstanding ticket list is still intact, but no more tickets will be sold.\n* The list can be viewed using the command "/otlist".\n* The changes for the current session can be seen using "/otchanges" \n* Be sure to update these entries into the OT List on the App Launch Page, they cannot be saved automatically.',sendto,yellow); } } } function setTicketPrice(amount,sendto,announce) { ticketPrice = parseInt(amount); if (announce === 'yes') { ticketPriceChangeNotice(ticketPrice); } } function ticketPriceChangeNotice(price) { cb.sendNotice(':ticket_red_small \u2749 \u2749 \u2749 :ticket_red_small \u2749 \u2749 \u2749 :ticket_red_small \n' + '\u21D2 . . . . Ticket Show Price Updated!!!. . . . . \u21D0 \n' + '\u21D2 . . . . Tickets are now ' + ticketPrice + ' tokens!!! . . . . . \u21D0 \n' + ':ticket_red_small \u2749 \u2749 \u2749 :ticket_red_small \u2749 \u2749 \u2749 :ticket_red_small' , "", ticketNoticesBgColor, ticketNoticesTxtColor, "bold"); changeRoomSubject(); cb.drawPanel(); } function loadFreeTickets() { if (cb.settings.ticketShowFreeMods == 'Yes') { for (let i = 0; i < moderatorList.name.length; i++) { if (!cb.limitCam_userHasAccess(moderatorList.name[i]) && moderatorList.type[i] != 'bc') { addRmvTicket('add',moderatorList.name[i],'mod'); } } } if (cb.settings.ticketShowFreeFC == 'Yes') { for (let i = 0; i < fanClubList.length; i++) { if (!cb.limitCam_userHasAccess(fanClubList[i])) { addRmvTicket('add',fanClubList[i],'fan'); } } } if (cb.settings.hiddenShowFreeEFC == 'Yes') { for (let i = 0; i < extFanListArray.length; i++) { if (!cb.limitCam_userHasAccess(extFanListArray[i])) { addRmvTicket('add',extFanListArray[i],'fan'); } } } if (cb.settings.hiddenShowFreeVIP == 'Yes') { for (let i = 0; i < VIPListArray.length; i++) { if (!cb.limitCam_userHasAccess(VIPListArray[i])) { addRmvTicket('add',VIPListArray[i],'vip'); } } } } function setTicketMode(startmode,sendto) { if (startmode === 'init') { if (cb.settings.ticketShowStartMode === 'Start Show Anytime') { newTicketMode = 'manual'; } else if (cb.settings.ticketShowStartMode === 'Start Show after Timer') { newTicketMode = 'timer'; } else if (cb.settings.ticketShowStartMode === 'Start Show after Ticket Goal') { newTicketMode = 'ticketgoal'; } else if (cb.settings.ticketShowStartMode === 'Start Show after Token Goal') { newTicketMode = 'tokengoal'; } if (cb.settings.ticketShowStartAuto === 'Start from Command') { newTicketAuto = 'bc'; } else if (cb.settings.ticketShowStartAuto === 'Automated Start') { newTicketAuto = 'auto'; } } else { if(startmode != '' && startmode != null) { newTicketMode = startmode; } else { newTicketMode = ticketStartMode; } } if (ticketStartMode === 'timer' && newTicketMode != 'timer') { if (ticketMinsRemain > 0 || ticketSecsRemain > 0) { stopTicketTimer(sendto); } } if (newTicketMode == 'manual') { ticketStartMode = 'manual'; ticketModeAuto = 'bc'; updtTicketModeMessage(); } else if (newTicketMode == 'timer') { if (cb.settings.ticketShowStartTimer >= 1) { ticketStartMode = 'timer'; ticketTimeAmt = cb.settings.ticketShowStartTimer; if (newTicketAuto == 'auto') { ticketAutoTimer(ticketTimeAmt); ticketModeAuto = 'auto'; updtTicketModeMessage(); } else { ticketModeAuto = 'bc'; updtTicketModeMessage(); } } else { ticketStartMode = 'manual'; ticketModeAuto = 'bc'; ticketIncAmt = cb.settings.ticketIncreasePerIncrement; updtTicketModeMessage(); cb.sendNotice('Ticket Show start is set to manual mode. Mode was requested for automatic timer, but the timer was not set on the start page. You can restart the bot and update the missing time, or continue and start the show manually at the end of a timer or at a time of your choosing.', sendto, yellow); } } else if (newTicketMode == 'tokengoal') { if (cb.settings.ticketShowGoal >= 1) { ticketStartMode = 'tokengoal'; ticketShowGoalTokens = cb.settings.ticketShowGoal; if (newTicketAuto == 'auto') { ticketModeAuto = 'auto'; updtTicketModeMessage(); } else { ticketModeAuto = 'bc'; updtTicketModeMessage(); } } else { ticketStartMode = 'manual'; ticketModeAuto = 'bc'; ticketIncAmt = cb.settings.ticketIncreasePerIncrement; updtTicketModeMessage(); cb.sendNotice('Ticket Show start is set to manual mode. Mode was requested for automatic goal start, but the goal was not set on the start page. You can restart the bot and update the missing goal, update the goal using the command "/chgticketgoal [amt]" and then change the mode using "/chgticketmode goal", or continue and start the show manually at the end of a timer or at a time of your choosing.', sendto, yellow); } } else if (newTicketMode === 'ticketgoal') { if (cb.settings.ticketShowGoal >= 1) { ticketStartMode = 'ticketgoal'; ticketShowGoalTickets = cb.settings.ticketShowGoal; if (newTicketAuto === 'auto') { ticketModeAuto = 'auto'; updtTicketModeMessage(); } else { ticketModeAuto = 'bc'; updtTicketModeMessage(); } } else { ticketStartMode = 'manual'; ticketModeAuto = 'bc'; ticketIncAmt = cb.settings.ticketIncreasePerIncrement; updtTicketModeMessage(); cb.sendNotice('Ticket Show start is set to manual mode. Mode was requested for automatic goal start, but the goal was not set on the start page. You can restart the bot and update the missing goal, update the goal using the command "/chgticketgoal [amt]" and then change the mode using "/chgticketmode goal", or continue and start the show manually at the end of a timer or at a time of your choosing.', sendto, yellow); } } } function setTicketAuto(automode) { if (automode === 'bc') { ticketModeAuto = 'bc'; updtTicketModeMessage(); } else if (automode === 'auto') { ticketModeAuto = 'auto'; updtTicketModeMessage(); } } function updtTicketModeMessage() { if (ticketStartMode == 'manual') { ticketModeMessage = '* Ticket Show start mode is set to "Manual"\n* The show will be started by the broadcaster or moderator.'; } else if (ticketStartMode == 'timer') { if (ticketModeAuto == 'auto') { ticketModeMessage = '* Ticket Show start mode is set to "Automatic timer" with a duration of ' + ticketTimeAmt + ' minutes.\n* The Ticket Show will start automatically when the timer runs out.'; } else if (ticketModeAuto == 'bc') { ticketModeMessage = '* Ticket Show start mode is set to "Broadcaster managed timer". \n* The broadcaster will set a timer and start the show when the timer runs out.'; } } else if (ticketStartMode == 'tokengoal') { if (ticketModeAuto == 'auto') { ticketModeMessage = '* Ticket Show start mode is set to "Automatic at token goal." \n* There will be a short 2 minute countdown started once the goal is met for total tip amount (' + ticketShowGoalTokens + ' tokens), and then the show will start automatically.'; } else if (ticketModeAuto == 'bc') { ticketModeMessage = '* Ticket Show start mode is set to "Broadcaster managed token goal". \n* The broadcaster will start the show once the goal is met for the total tips (' + ticketShowGoalTokens + ' tokens).'; } } else if (ticketStartMode == 'ticketgoal') { if (ticketModeAuto == 'auto') { ticketModeMessage = '* Ticket Show start mode is set to "Automatic at ticket goal." \n* There will be a short 2 minute countdown started once the goal is met for tickets sold (' + ticketShowGoalTickets + ' tickets), and then the show will start automatically.'; } else if (ticketModeAuto == 'bc') { ticketModeMessage = '* Ticket Show start mode is set to "Broadcaster managed ticket goal". \n* The broadcaster will start the show once the goal is met for tickets sold (' + ticketShowGoalTickets + ' tickets).'; } } } function startTicketShowTimer(numtimer) { ticketMinsRemain = numtimer; ticketStartMode = 'timer'; ticketAutoTimer(ticketMinsRemain); cb.drawPanel(); } function ticketAutoTimer(addtime) { if (ticketModeAuto === 'auto') { if (cb.settings.ticketShowFanAppreciation == 'Yes') { cb.sendNotice('An automatic timer was started for the countdown to the Fan Appreciation show.', "", ticketBgColor, ticketTxtColor, "bold"); } else { cb.sendNotice('An automatic timer was started, ticket show price will be ' + ticketPrice + ' tokens.', "", ticketBgColor, ticketTxtColor, "bold"); } } else if (ticketModeAuto === 'bc') { cb.sendNotice('A timer was started to provide an countdown to approximate showtime. \n The broadcaster will start the show after the timer runs out.', "", ticketBgColor, ticketTxtColor, "bold"); } ticketMinsRemain = addtime; ticketSecsRemain = 0; ticketStartTime = new Date(); ticketStopTime = new Date(ticketStartTime.getTime() + ticketMinsRemain * 60000); ticketTimerMin(); } function ticketTimerMin() { ticketSeconds = ticketCheckMin(); if (ticketSkipMin === false && ticketStartMode == 'timer' && whichApp == 'ticket' && (ticketSeconds >= 55 || ticketSeconds == 0)) { switch (ticketMinsRemain) { case 120: case 90: case 60: case 45: case 30: case 20: case 15: case 10: case 9: case 8: case 7: case 6: case 5: case 4: case 3: case 2: cb.drawPanel(); if (cb.settings.ticketShowFanAppreciation == 'Yes') { cb.sendNotice('\u23f1 \u23f1 \u23f1 There are ' + ticketMinsRemain + ' minutes left until the Fan Appreciation Show! \u23f1 \u23f1 \u23f1', "", yellow, "", "bold"); } else { cb.sendNotice('\u23f1 \u23f1 \u23f1 There are ' + ticketMinsRemain + ' minutes left to buy a ticket for ' + ticketPrice + ' tokens before the show starts! \u23f1 \u23f1 \u23f1', "", yellow, "", "bold"); } } ticketMinsRemain--; if (ticketMinsRemain > 0) { cb.setTimeout(ticketTimerMin, 60000); } else { ticketSecsRemain = 60; cb.drawPanel(); if (cb.settings.ticketShowFanAppreciation == 'Yes') { cb.sendNotice('\u23f1 \u23f1 \u23f1 There is 1 minute left to until the Fan Appreciation Show!! \u23f1 \u23f1 \u23f1', "", red, "", "bold"); } else { cb.sendNotice('\u23f1 \u23f1 \u23f1 There is 1 minute left to buy a ticket for ' + ticketPrice + ' tokens before the show starts!! \u23f1 \u23f1 \u23f1', "", red, "", "bold"); } ticketTimerSec(); } } else { if (ticketSkipMin === true) { ticketSkipMin = false; } } } function ticketCheckMin() { var ticketTimeLeft = ticketTimeCal(); var ticketMS = ticketTimeLeft % 1000; var ticketSeconds = ((ticketTimeLeft - ticketMS) % 60000); ticketSeconds = ticketSeconds / 1000; return ticketSeconds; } function ticketTimerSec() { if (!ticketSkipSec && ticketStartMode === 'timer' && whichApp === 'ticket') { if (ticketMinsRemain > 0) { cb.setTimeout(ticketTimerMin, ticketSecsRemain*1000); } else { ticketSecsRemain--; if (ticketSecsRemain < 1) { cb.drawPanel(); if (ticketStartMode = 'timer' && ticketModeAuto === 'auto') { cb.sendNotice("\u23f0 \u23f0 \u23f0 Time is up! Ticket Show is starting! \u23f0 \u23f0 \u23f0", "", ticketNoticesBgColor, ticketNoticesTxtColor, "bold"); startTicketShow(cb.room_slug); } else { cb.sendNotice("\u23f0 \u23f0 \u23f0 Time is up! Broadcaster will be starting the show! \u23f0 \u23f0 \u23f0", "", ticketNoticesBgColor, ticketNoticesTxtColor, "bold"); } } else { switch (ticketSecsRemain) { case 30: case 10: case 5: case 4: case 3: case 2: case 1: cb.drawPanel(); cb.sendNotice('\u23f1 \u23f1 \u23f1 There are ' + ticketSecsRemain + ' second' + (ticketSecsRemain > 1 ? "s" : "") + ' left! \u23f1 \u23f1 \u23f1', "", red, "", "bold"); } } if (ticketSecsRemain > 0) { cb.setTimeout(ticketTimerSec, 1000); } } } else { ticketSkipSec = false; } } function ticketAddTime(tickettimetoadd, u) { ticketStopTime = new Date(ticketStopTime.getTime() + tickettimetoadd * 60000); cb.sendNotice(u + " has added " + tickettimetoadd + " minute" + (tickettimetoadd === 1 || tickettimetoadd === -1 ? "" : "s") + " to the timer.",cb.room_slug,"","", "bold"); cb.sendNotice(tickettimetoadd + " minute" + (tickettimetoadd === 1 || tickettimetoadd === -1 ? "" : "s") + " have been added to the timer. Now " + ticketTimeLeft(),"",yellow,"", "bold"); ticketMinsRemain = ticketMinsRemain + tickettimetoadd; if (ticketMinsRemain < 1) { ticketSkipMin = true; ticketTimeLeft = ticketTimeCal(); ticketMS = ticketTimeLeft % 1000; ticketSeconds = ((ticketTimeLeft - ticketMS) % 60000) / 1000; ticketSecsRemain = ticketSeconds; ticketTimerSec(); } cb.drawPanel(); return; } function ticketTimeCal() { ticketStartTime = new Date(); return ticketStopTime - ticketStartTime.getTime(); } function stopTicketTimer(mod) { ticketStopTime = new Date(); if (ticketMinsRemain > 0 || ticketSecsRemain > 0) { ticketSkipMin = true; ticketSkipSec = true; } ticketMinsRemain = 0; ticketSecsRemain = 0; if(mod != null && mod != '') { cb.sendNotice(mod + ' has stopped the ticket show timer.',"",yellow,"", "bold"); } cb.drawPanel(); } function ticketTimeLeft(user) { var ticketTimeLeft = ticketTimeCal(); var ticketMS = ticketTimeLeft % 1000; var ticketSeconds = ((ticketTimeLeft - ticketMS) % 60000); var ticketMinutes = ((ticketTimeLeft - ticketSeconds - ticketMS) % 3600000); var ticketHours = (ticketTimeLeft - ticketMinutes - ticketSeconds - ticketMS); ticketSeconds = ticketSeconds / 1000; ticketMinutes = ticketMinutes / 60000; ticketHours = ticketHours / 3600000; if (ticketHours > 0) { return ticketHours + " hour" + (ticketHours > 1 ? "s" : "") + " and " + ticketMinutes + " minute" + (ticketMinutes > 1 ? "s" : "") + " remaining on the ticket show timer."; } else if (ticketMinutes > 0 && ticketSeconds === 0) { return ticketMinutes + " minute" + (ticketMinutes > 1 ? "s" : "") + " remaining on the ticket show timer."; } else if (ticketMinutes > 0) { return ticketMinutes + " minute" + (ticketMinutes > 1 ? "s" : "") + " and " + ticketSeconds + " second" + (ticketSeconds > 1 ? "s" : "") + " remaining on the ticket show timer."; } else { return ticketSeconds + " second" + (ticketSeconds > 1 ? "s" : "") + " remaining on the ticket show timer."; } } function ticketTimeLeftPanel() { var ticketTimeLeft = ticketTimeCal(); var ticketMS = ticketTimeLeft % 1000; var ticketSeconds = ((ticketTimeLeft - ticketMS) % 60000); var ticketMinutes = ((ticketTimeLeft - ticketSeconds - ticketMS) % 3600000); var ticketHours = (ticketTimeLeft - ticketMinutes - ticketSeconds - ticketMS); ticketSeconds = ticketSeconds / 1000; ticketMinutes = ticketMinutes / 60000; if (ticketMinutes > 0) { return 'Less than ' + (ticketMinutes+1) + ' min rem'; } else { return 'Less than ' + (ticketSeconds) + ' sec rem';; } } function addRmvTicket(mode,user,usertype,tipamt) { switch(mode) { case 'add': { cb.limitCam_addUsers([user]); if (!cbjs.arrayContains(ticketShowViewerList,user)) { ticketShowViewerList.push(user); } if (usertype == 'fan') { cb.sendNotice('Fan Club member ' + user + ' has been added to the ticket show list.', '', ticketBgColorFan, ticketTxtColorFan, 'bold'); } else if (usertype == 'vip') { cb.sendNotice('VIP List member ' + user + ' has been added to the ticket show list.', '', ticketBgColorVIP, ticketTxtColorVIP, 'bold'); } else if (usertype == 'mod') { cb.sendNotice('Moderator ' + user + ' has been added to the ticket show list.', '', ticketBgColorMod, ticketTxtColorMod, 'bold'); } else { cb.sendNotice(user + ' has been added to the ticket show list.', '', ticketBgColor, ticketTxtColor, 'bold'); } countTicketsSold ++; cb.drawPanel(); break; } case 'rmv': { cb.limitCam_removeUsers([user]); if (cbjs.arrayContains(ticketShowViewerList,user)) { ticketShowViewerList.pop(user); } cb.sendNotice(user + ' has been removed from the ticket show list.', '', ticketBgColor, ticketTxtColor, 'bold'); countTicketsSold --; cb.drawPanel(); break; } case 'addtip': { cb.limitCam_addUsers([user]); if (!cbjs.arrayContains(ticketShowViewerList,user)) { ticketShowViewerList.push(user); } if (usertype == 'fan') { cb.sendNotice('Welcome Fan Club member ' + user + ', you have been added to the ticket show list.', "", ticketBgColorFan, ticketTxtColorFan, "bold"); } else if (usertype == 'vip') { cb.sendNotice('Welcome VIP List member ' + user + ', you have been added to the ticket show list.', "", ticketBgColorVIP, ticketTxtColorVIP, "bold"); } else if (usertype == 'mod') { cb.sendNotice('Welcome ' + user + ', you have been added to the ticket show list.', "", ticketBgColorMod, ticketTxtColorMod, "bold"); } else { cb.sendNotice('Welcome ' + user + ', you have been added to the ticket show list.', "", ticketBgColor, ticketTxtColor, "bold"); } countTicketsSold ++; if (tipamt >= (2*ticketPrice)) { addRmvTicket('addextra', user, 'common', (tipamt - ticketPrice)); } break; } case 'addextra': { var numtickets = Math.floor(parseInt(tipamt / ticketPrice)); if (numtickets > 0) { if (!cbjs.arrayContains(ticketShowExtraTickets.name,user)) { ticketShowExtraTickets.name.push(user); ticketShowExtraTickets.count.push(numtickets); } else { index = ticketShowExtraTickets.name.indexOf(user); ticketShowExtraTickets.count[index] = ticketShowExtraTickets.count[index] + numtickets; } cb.sendNotice(user + ', you have purchased ' + numtickets + ' extra tickets to the show. You can gift these to other users with the command "/giftticket user". Each time you use the command uses up one of your extra tickets. Please make sure to spell the user name right, gifted tickets cannot be recovered if they are gifted incorrectly.', user, ticketBgColor, ticketTxtColor, "bold"); } cb.drawPanel(); break; } } } function giftTicket(fromuser,touser) { cb.limitCam_addUsers([touser]); if (!cbjs.arrayContains(ticketShowViewerList,touser)) { ticketShowViewerList.push(touser); } cb.sendNotice('Welcome ' + touser + ', ' + fromuser + ' has gifted you a ticket to the show!', '', ticketBgColor, ticketTxtColor, 'bold'); countTicketsSold ++; cb.drawPanel(); } function giveAwayTicket(fromuser,touser) { cb.limitCam_addUsers([touser]); cb.limitCam_removeUsers([fromuser]); if (!cbjs.arrayContains(ticketShowViewerList,touser)) { ticketShowViewerList.push(touser); } if (cbjs.arrayContains(ticketShowViewerList,fromuser)) { ticketShowViewerList.pop(fromuser); } cb.sendNotice('Welcome ' + touser + ', ' + fromuser + ' has gifted you a ticket to the show!', '', ticketBgColor, ticketTxtColor, 'bold'); cb.drawPanel(); } function addRmvOutstandingTicket(mode,user) { switch(mode) { case "add": { outstandingTicketArray.push(user); populateOtChangesArray(user,'add'); break; } case "rmv": { cbjs.arrayRemove(outstandingTicketArray,user); populateOtChangesArray(user,'rmv'); break; } } } function populateOtChangesArray(user,type) { if (cbjs.arrayContains(otChangesArray.name,user)) { index = otChangesArray.name.indexOf(user); if (otChangesArray.type[index] != type) { otChangesArray.name.push(user); otChangesArray.type.push(type); } else { return; } } else { otChangesArray.name.push(user); otChangesArray.type.push(type); } } function ticketShowGoalProgress(tipAmount) { ticketShowTotalTips = ticketShowTotalTips + tipAmount; ticketShowTotalTickets++; if (ticketStartMode === 'tokengoal' && ticketShowTotalTips >= ticketShowGoalTokens) { cb.sendNotice(dashLine80 + '\n :siren1 \u25C8 \u25C8 \u25C8 The ticket show Tip goal has been met!! \u25C8 \u25C8 \u25C8 :siren1 \n \u25C8 \u25C8 \u25C8 Starting a 2 minute timer for automatic start of show! \u25C8 \u25C8 \u25C8 \n' + dashLine80, '', ticketNoticesBgColor, ticketNoticesTxtColor, 'bold'); ticketStartMode = 'timer'; ticketAutoTimer(2); } else if (ticketStartMode === 'ticketgoal' && ticketShowTotalTickets >= ticketShowGoalTickets) { cb.sendNotice(dashLine80 + '\n :siren1 \u25C8 \u25C8 The ticket show Ticket goal has been met!! \u25C8 \u25C8 :siren1 \n \u25C8 \u25C8 \u25C8 Starting a 2 minute timer for automatic start of show! \u25C8 \u25C8 \u25C8 \n' + dashLine80, '', ticketNoticesBgColor, ticketNoticesTxtColor, 'bold'); ticketStartMode = 'timer'; ticketAutoTimer(2); } } function startTicketShow(startedby) { var nowTime = new Date(); cb.sendNotice(dashLine60 + '\n :siren1 :siren1 :siren1 ' + startedby + ' has started the show! :siren1 :siren1 :siren1 \n' + dashLine60,'',ticketBgColor, ticketNoticesTxtColor, 'bold'); cb.sendNotice('*** REMINDER: Please do not deactivate the Hidden Ticket Show feature until after you end your show with the "/stopshow" command.', cb.room_slug, ticketBgColor, ticketTxtColor, 'bold'); if (cb.settings.ticketShowFanAppreciation == 'Yes') { cb.limitCam_start('Ultra App Ticket Show\n\nFan Club Appreciation Show in progress. Join the Fanclub to unlock the screen!'); } else { cb.limitCam_start('Ultra App Ticket Show\n\nHidden Cam show in progress \nCheck room notices for ticket price'); } showStage = 'ticketshow'; changeRoomSubject(); if (ticketMinsRemain > 0 || ticketSecsRemain > 0) { stopTicketTimer(); } hiddenTime = Date.now(); cb.drawPanel(); } function restartTicketShow(startedby) { var nowTime = new Date(); cb.sendNotice(dashLine80 + '\n :siren1 \u25C8 \u25C8 \u25C8 ' + startedby + ' has restarted the ticket show! \u25C8 \u25C8 \u25C8 :siren1 \n \u25C8 \u25C8 \u25C8 All current ticket holders still have access. \u25C8 \u25C8 \u25C8 \n \u25C8 \u25C8 \u25C8 Ticket sales are open, price is ' + ticketPrice + ' tokens. \u25C8 \u25C8 \u25C8 \n' + dashLine80,'',ticketBgColor, ticketNoticesTxtColor, 'bold'); if (cb.settings.ticketShowFanAppreciation == 'Yes') { cb.limitCam_start('Ultra App Ticket Show\n\nFan Club Appreciation Show in progress. Join the Fanclub to unlock the screen!'); } else { cb.limitCam_start('Ultra App Ticket Show\n\nHidden Cam show in progress \nCheck room notices for ticket price'); } if (showStage == 'aftershow') { ticketSubjectText = savedTicketSubjectText; } showStage = 'ticketshow'; changeRoomSubject(); ticketShowEnded = false; ticketSalesEnded = false; if (ticketMinsRemain > 0 || ticketSecsRemain > 0) { stopTicketTimer(); } hiddenTime = Date.now(); cb.drawPanel(); } function restartTicketSales(startedby) { if (showStage == 'aftershow') { cb.sendNotice(dashLine80 + '\n :siren1 ' + startedby + ' has restarted ticket sales! :siren1 \n * All current ticket holders still have access. \n * Ticket price is ' + ticketPrice + ' tokens. \n' + dashLine80,'',ticketBgColor, ticketNoticesTxtColor, 'bold'); showStage = 'ticketsales'; ticketSubjectText = savedTicketSubjectText; changeRoomSubject(); ticketShowEnded = false; ticketSalesEnded = false; } else if (showStage == 'showfinale') { cb.sendNotice(dashLine80 + '\n :siren1 ' + startedby + ' has restarted ticket sales! :siren1 \n * Ticket price is ' + ticketPrice + ' tokens. \n' + dashLine80,'',ticketBgColor, ticketNoticesTxtColor, 'bold'); showStage = 'ticketshow'; changeRoomSubject(); ticketSalesEnded = false; } cb.drawPanel(); } function warnShowEnding(by) { showStage = 'showwarn'; if (cb.settings.ticketShowFanAppreciation == 'Yes') { cb.sendNotice(dashLine90 + '\n :siren1 ' + by + ' has indicated the Fan Appreciation Show is nearly finished :siren1 \n' + dashLine90,'',ticketBgColor,ticketNoticesTxtColor,'bold'); } else { if (cb.settings.ticketShowReducePriceWarn > 0) { ticketPrice = ticketPrice - cb.settings.ticketShowReducePriceWarn; cb.sendNotice(dashLine90 + '\n :siren1 \u25C8 \u25C8 \u25C8 ' + by + ' has indicated the show is nearly finished. \u25C8 \u25C8 \u25C8 :siren1 \n \u25C8 \u25C8 It is not recommended to buy a ticket, but sales are still open. \u25C8 \u25C8 \n \u25B7 \u25B7 \u25B7 \u25B7 \u25B7 The Ticket Price has been reduced to ' + ticketPrice + ' tokens! \u25C1 \u25C1 \u25C1 \u25C1 \u25C1 \n' + dashLine90,'',ticketBgColor,ticketNoticesTxtColor,'bold'); } else { cb.sendNotice(dashLine90 + '\n :siren1 \u25C8 \u25C8 \u25C8 ' + by + ' has indicated the show is nearly finished. \u25C8 \u25C8 \u25C8 :siren1 \n \u25C8 \u25C8 It is not recommended to buy a ticket, but sales are still open. \u25C8 \u25C8 \n' + dashLine90,'',ticketBgColor,ticketNoticesTxtColor,'bold'); } } changeRoomSubject(); cb.drawPanel(); } function stopTicketSales(stoppedby) { if (cb.settings.ticketShowFanAppreciation == 'Yes') { cb.sendNotice(dashLine90 + '\n :siren1 ' + stoppedby + ' has indicated the Fan Appreciation Show is nearly finished :siren1 \n' + dashLine90,'',ticketBgColor,ticketNoticesTxtColor,'bold'); } else { cb.sendNotice(dashLine70 + '\n :siren1 :siren1 :siren1 \u25B7 \u25B7 ' + stoppedby + ' has ended ticket sales. \u25C1 \u25C1 :siren1 :siren1 :siren1 \n' + dashLine70,'',ticketBgColor,ticketNoticesTxtColor,'bold'); } showStage = 'showfinale'; changeRoomSubject(); ticketSalesEnded = true; cb.drawPanel(); afterNoticeInt = 90000; setAfterNoticeTimeout(); } function stopTicketShow(stoppedby) { cb.sendNotice(dashLine60 + '\n :siren1 \u25C8 \u25C8 \u25C8 ' + stoppedby + ' has ended the show \u25C8 \u25C8 \u25C8 :siren1 \n \u25C8 \u25C8 \u25C8 \u25C8 Ticket Show length ' + ((Date.now() - hiddenTime)/60000).toFixed(2) + ' minutes. \u25C8 \u25C8 \u25C8 \u25C8 \n' + dashLine60,'',ticketBgColor,ticketNoticesTxtColor,'bold'); cb.limitCam_stop(); cb.sendNotice(dashLine70 + '\n :siren1 :siren1 :siren1 The broadcast is returning to Public Chat. :siren1 :siren1 :siren1 \n' + dashLine70, '',ticketBgColor,ticketNoticesTxtColor,'bold'); hiddenTime = 0; ticketShowEnded = true; ticketSalesEnded = true; showStage = 'aftershow'; savedTicketSubjectText = ticketSubjectText; ticketSubjectText = cb.settings.ticketShowAfterNotice; changeRoomSubject(); cb.drawPanel(); if (ticketShowOtToggle == 1) { if (otChangesArray.name.length > 0) { outString = ''; for (var i = 0; i < otChangesArray.name.length; i++) { if (otChangesArray.name[i] == null) { break } else { outString += (i > 0 ? ',' : '') + otChangesArray.name[i] + '(' + otChangesArray.type[i] + ')'; } } cb.sendNotice('Listing of Changes to the Outstanding Ticket List from this show : \n' + outString + '\nEnd of List', cb.room_slug, yellow); cb.sendNotice('Listing of Changes to the Outstanding Ticket List from this show : \n' + outString + '\nEnd of List', '', yellow, '', '', 'red'); } else { cb.sendNotice('No entries have been added to the Outstanding Ticket Changes list from this show.', cb.room_slug, yellow); } } } function setAfterNoticeTimeout() { cb.setTimeout(afterNoticeTimer, afterNoticeInt); } function afterNoticeTimer() { if (showStage == 'showfinale') { cb.sendNotice(' :siren1 Ticket Sales Ended! Do not tip to buy a ticket! :siren1 ','',ticketBgColor,ticketNoticesTxtColor,'bold'); setAfterNoticeTimeout(); } } function useTicket(user) { addRmvOutstandingTicket('rmv',user); cb.limitCam_addUsers([user]); if (!cbjs.arrayContains(ticketShowViewerList,user)) { ticketShowViewerList.push(user); } cb.sendNotice('Welcome ' + user + ', your Outstanding Ticket has been redeemed for a ticket to this show.', '', ticketBgColor, ticketTxtColor, 'bold'); cb.drawPanel(); } function saveTicket(user) { addRmvOutstandingTicket('add',user); cb.limitCam_removeUsers([user]); if (cbjs.arrayContains(ticketShowViewerList,user)) { ticketShowViewerList.pop(user); } cb.drawPanel(); } function getShowTime(u) { cb.sendNotice(' \u25B7 \u25B7 \u25B7 Hidden Cam show in progress for ' + ((Date.now() - hiddenTime)/60000).toFixed(2) + ' minutes. \u25C1 \u25C1 \u25C1 ', u, ticketBgColor,ticketTxtColor,'bold'); } function sendPublicNotice (message, user, type) { if (message != null) { if (message != '' || message != ' ' || message != '\u00a0') { switch (type) { case 'div': cb.sendNotice(dashLine80 + '\n\u25ba ' + message.capitalize() + '\n' + dashLine80, '', '', ticketNoticesTxtColor, 'bold'); break; case 'divh': cb.sendNotice(dashLine80 + '\n\u25ba ' + message.capitalize() + '\n' + dashLine80, '', ticketBgColor,ticketNoticesTxtColor, 'bold'); break; case 'h': cb.sendNotice("\u25ba " + message.capitalize(), '', ticketBgColor,ticketNoticesTxtColor, 'bold'); break; case '': cb.sendNotice('\u25ba ' + message.capitalize(), '', '', ticketNoticesTxtColor, 'bold'); break; } } else { cb.sendNotice('You can\'t send a blank message. Type a message and try again.', user, yellow); } } else { cb.sendNotice('You can\'t send a blank message. Type a message and try again.', user, yellow); } } function customizePanelText(newcolor,sendto) { if (!newcolor && cb.settings.panelTextColor == 'Custom') { textcolorchk = checkTextColor(cb.settings.panelCustomTextColor); if (textcolorchk == 'default') { cb.sendNotice('Draw Panel Text Color - Error while setting the text color. It has to be in a HEX format.', sendto, yellow, ''); } else { textColor = textcolorchk; cb.drawPanel(); } } else if (newcolor) { textcolorchk = checkTextColor(newcolor); if (textcolorchk == 'default') { cb.sendNotice('Draw Panel Text Color - Error while setting the text color. It has to be in a HEX format.', sendto, yellow, ''); } else { textColor = textcolorchk; cb.drawPanel(); } } else { textColor = checkTextColor(cb.settings.panelTextColor); cb.drawPanel(); } } function customizePanelBackground(newbackground,sendto) { customPanel = false; if (cbjs.arrayContains(backgroundArray.command,newbackground)) { index = backgroundArray.command.indexOf(newbackground); if (cb.room_slug == 'dorothy') { backgroundImage = backgroundArray.devfile[index]; customPanel = true; currentPanel = newbackground; cb.drawPanel(); } else { menuname = backgroundArray.menu[index]; if (menuname.substring(0,12) == 'personalized') { if (newbackground == cb.room_slug) { backgroundImage = backgroundArray.livefile[index]; customPanel = true; currentPanel = newbackground; cb.drawPanel(); } else { cb.sendNotice('You have requested a personalized background, but you are not the room owner for that background, please choose again.', sendto, yellow, ''); } } else { backgroundImage = backgroundArray.livefile[index]; customPanel = true; currentPanel = newbackground; cb.drawPanel(); } } } else { cb.sendNotice('Invalid background name. The valid names are: \n' + cbjs.arrayJoin(backgroundArray.command, ', '), sendto, yellow, ''); } } function prepTicketShow(startedby,numtimer) { if (ticketPrice <= 0) { cb.sendNotice('Unable to start the Ultra App Ticket Show feature, a ticket price is not defined. The command "/ticketprice [price]" can be used to set a price.', startedby, yellow); } else { setAppFeature('ticket', startedby); if (presalesToggle == 1) { setPresalesToggle('off',startedby); } } if (cb.settings.prepTicketStartTimer == 'Yes' && ticketStartMode == 'manual') { if (numtimer > 0) { startTicketShowTimer(numtimer); } else if (cb.settings.ticketShowStartTimer > 0) { startTicketShowTimer(cb.settings.ticketShowStartTimer); } else { cb.sendNotice('Unable to start the ticket show countdown timer, a value was not provided on the /prepticket command and no "Default Timer" duration is defined on the launch page. You can manually start a timer with the "/ticketstarttimer xx" command (no quotes).', startedby, yellow); } } } // *********************************** Ticket Show Pre-sales ************************************** function setPresalesToggle(option, sendto, price) { if(option == 'on') { if(presalesToggle == 1) { cb.sendNotice('The Pre-Sales Ticket feature is already enabled.',sendto,yellow); } else { presalePrice = price; presalesToggle = 1; showStage = 'presales'; if (whichApp != 'ticket') { ticketShowColors(); } cb.sendNotice('***Warning Regarding Presales***: Once tickets have been sold, do not deactivate or restart the UltraApp without saving the pre-sale ticket list. If it is necessary to restart, please save the pre-sale list first (use /tickets to view and copy) and then add those users back to the presale (/addpresale) or the actual ticket show (/add).',cb.room_slug,yellow); presaleSkipMin = false; presaleSkipSec = false; presaleSkipNotice = false; setPresaleMode('init',sendto); countPresaleSold = 0; presaleIncrements = 0; stopIncrement = false; cb.setTimeout(presaleNoticeTimer, cb.settings.presaleNoticeInterval * 60000); } } else if(option == 'off') { if(presalesToggle == 0) { cb.sendNotice('The Pre-Sales Ticket feature is already disabled.',sendto,yellow); } else { presalesToggle = 0; stopPresaleTimer(sendto); presaleSkipNotice = true; cb.sendNotice('You have disabled the Pre-Sales Ticket feature. The pre-sales list is still intact, but no more tickets will be sold.', sendto, yellow); cb.sendNotice(sendto + ' has disabled the Ticket Show Pre-sales Feature. You can no longer buy Pre-sale tickets.', '', ticketBgColor, ticketTxtColor, 'bold'); } } else if(option != null) { cb.sendNotice(option + ' is not a valid option for /usepresale.\nType "/fbhelp presale" to see how to use pre-sales related commands.', sendto, yellow); } else if(option == null) { cb.sendNotice('You did not enter a valid option for /usepresale.\nType "/fbhelp presale" to see how to use pre-sales related commands.', sendto, yellow); } } function setPresaleMode(mode,sendto) { if (mode === 'init') { if (cb.settings.ticketShowPresalesMode === 'Yes, Mode 1: Increment Manually') { newPresaleMode = 'manual'; } else if (cb.settings.ticketShowPresalesMode === 'Yes, Mode 2: Increment on X Tickets Sold') { newPresaleMode = 'count'; } else if (cb.settings.ticketShowPresalesMode === 'Yes, Mode 3: Increment on Automatic Timer') { newPresaleMode = 'timer'; } else { newPresaleMode = 'initmanual'; } } else { countPresaleSold = 0; presaleIncrements = 0; stopIncrement = false; newPresaleMode = mode; } if (presaleMode === 'timer' && newPresaleMode != 'timer') { stopPresaleTimer(sendto); } if (newPresaleMode === 'manual') { presaleMode = 'manual'; presaleIncAmt = cb.settings.ticketShowPresaleIncreasePerIncrement; cb.sendNotice('Pre-sales is set to manual mode. The broadcaster or moderator can change the presale price on demand using the command "/presaleprice [newprice]".', sendto, yellow); } else if (newPresaleMode === 'timer') { if (cb.settings.ticketShowPresaleIncreasePerIncrement >= 1 && cb.settings.ticketShowPresaleTimedIncrement >= 1) { presaleMode = 'timer'; presaleTimeAmt = cb.settings.ticketShowPresaleTimedIncrement; presaleIncAmt = cb.settings.ticketShowPresaleIncreasePerIncrement; presaleAutoTimer(presaleTimeAmt); cb.sendNotice('Pre-sales is set to Mode 3: Automatic timer mode. Timer cycle is set to ' + presaleTimeAmt + ' minutes, and price will increase ' + presaleIncAmt + ' tokens each time, with a maximum of ' + cb.settings.ticketShowPresaleMaxIncrements + ' increases.', sendto, yellow); } else { presaleMode = 'manual'; presaleIncAmt = cb.settings.ticketShowPresaleIncreasePerIncrement; cb.sendNotice('Pre-sales is set to manual mode. Mode was requested for automatic timer, but either the price increment or number of minutes per increment is not set. You can restart the bot and update the missing values, update the pre-sale price manually without a timer, or manually start a single increment timer.', sendto, yellow); } } else if (newPresaleMode === 'count') { if (cb.settings.ticketShowPresaleIncreasePerIncrement >= 1 && cb.settings.ticketShowPresaleCountIncrement >= 1) { presaleMode = 'count'; presaleCountAmt = cb.settings.ticketShowPresaleCountIncrement; presaleIncAmt = cb.settings.ticketShowPresaleIncreasePerIncrement; ticketsLeft = presaleCountAmt; cb.sendNotice('Pre-sales is set to Mode 2: Increment by tickets sold. Ticket count cycle is set to ' + presaleCountAmt + ' tickets sold, and price will increase ' + presaleIncAmt + ' tokens each time, with a maximum of ' + cb.settings.ticketShowPresaleMaxIncrements + ' increases.', sendto, yellow); cb.sendNotice('Pre-sale tickets are limited. The pre-sale price will increase from ' + presalePrice + ' to ' + (presalePrice + presaleIncAmt) + ' tokens once ' + presaleCountAmt + ' tickets are sold. Buy now before the price goes up!', '', ticketBgColor, ticketTxtColor, 'bold'); } else { presaleMode = 'manual'; presaleIncAmt = cb.settings.ticketShowPresaleIncreasePerIncrement; cb.sendNotice('Pre-sales is set to manual mode. Mode was requested for increment by tickets sold, but either the price increment or number of tickets sold per increment is not set. You can restart the bot and update the missing values, update the pre-sale price manually with a count, or manually start a single increment timer.', sendto, yellow); } } else if (newPresaleMode === 'initmanual') { presaleMode = 'manual'; presaleIncAmt = cb.settings.ticketShowPresaleIncreasePerIncrement; cb.sendNotice('Pre-sales is set to manual mode since pre-sale was turned off on the start page. You can change the mode using the command "/chgpresalemode [mode]", where [mode] is "manual", "count", or "timer".', sendto, yellow); } } function presalePriceChange(price,mod) { cb.sendNotice('\u21D2 . . . . Ticket Show Pre-Sales Price Updated!!!. . . . . \u21D0 \n \u21D2 . . . . . Pre-sale Tickets are now ' + price + ' tokens!!! . . . . . \u21D0 \n' , '', ticketBgColor, ticketTxtColor, 'bold'); presalePrice = price; } function presaleNoticeTimer() { if(presalesToggle == 1) { if (presaleMode === 'manual') { cb.sendNotice('Ticket Show Pre-sales are active. You can now buy advance tickets to the show at a price of ' + presalePrice + ' tokens. Planned ticket price for the show is ' + ticketPrice + ' tokens.', '', ticketBgColor, ticketTxtColor, 'bold'); } else if (presaleMode === 'timer') { if (stopIncrement === true || (presalePrice + presaleIncAmt) > ticketPrice || presaleIncrements > cb.settings.ticketShowPresaleMaxIncrements) { cb.sendNotice('Ticket Show Pre-sales are active. You can now buy advance tickets to the show at a price of ' + presalePrice + ' tokens. Planned ticket price for the show is ' + ticketPrice + ' tokens.', '', ticketBgColor, ticketTxtColor, 'bold'); } else { cb.sendNotice('Ticket Show Pre-sales are active. You can now buy advance tickets to the show at a price of ' + presalePrice + ' tokens. Pre-sale tickets are being sold at this price for a limited period of time. The pre-sale price will increase from ' + presalePrice + ' to ' + (presalePrice + presaleIncAmt) + ' tokens once the timer runs out. Buy now before the price goes up!', '', ticketBgColor, ticketTxtColor, 'bold'); } } else if (presaleMode === 'count') { if (stopIncrement === true || (presalePrice + presaleIncAmt) > ticketPrice || (presaleIncrements + 1) > cb.settings.ticketShowPresaleMaxIncrements) { cb.sendNotice('Ticket Show Pre-sales are active. You can now buy advance tickets to the show at a price of ' + presalePrice + ' tokens. Planned ticket price for the show is ' + ticketPrice + ' tokens.', '', ticketBgColor, ticketTxtColor, 'bold'); } else { cb.sendNotice('Ticket Show Pre-sales are active. You can now buy advance tickets to the show at a price of ' + presalePrice + ' tokens. Pre-sale tickets are limited. The pre-sale price will increase from ' + presalePrice + ' to ' + (presalePrice + presaleIncAmt) + ' tokens once ' + ticketsLeft + ' more ticket' + (ticketsLeft > 1 ? "s are" : " is") + ' sold. Buy now before the price goes up!', '', ticketBgColor, ticketTxtColor, 'bold'); } } if (!presaleSkipNotice) { cb.setTimeout(presaleNoticeTimer, cb.settings.ticketShowPresaleNoticeInterval * 60000); } else { presaleSkipNotice = false; } } } function presaleAutoTimer(addtime) { cb.sendNotice('An automatic timer was started, pre-sale ticket prices will increase from ' + presalePrice + ' to ' + (presalePrice + presaleIncAmt) + ' tokens in ' + presaleTimeAmt + ' minutes. Buy now before the price goes up! Ticket show price will be ' + ticketPrice + ' tokens.', '', ticketBgColor, ticketTxtColor, 'bold'); presaleMinsRemain = addtime; presaleSecsRemain = 0; presaleStartTime = new Date(); presaleStopTime = new Date(presaleStartTime.getTime() + presaleMinsRemain * 60000); presaleTimerMin(); } function presaleTimerMin() { presaleSeconds = presaleCheckMin(); if (presaleSkipMin === false && presaleMode === 'timer' && (presaleSeconds >= 55 || presaleSeconds == 0)) { switch (presaleMinsRemain) { case 60: case 45: case 30: case 20: case 15: case 10: case 9: case 8: case 7: case 6: case 5: case 4: case 3: case 2: cb.sendNotice('\u23f1 \u23f1 \u23f1 There are ' + presaleMinsRemain + ' minutes left on the timer to buy a pre-sale ticket for ' + presalePrice + ' tokens! \u23f1 \u23f1 \u23f1', "", yellow, "", "bold"); } presaleMinsRemain--; if (presaleMinsRemain > 0) { cb.setTimeout(presaleTimerMin, 60000); } else { presaleSecsRemain = 60; cb.sendNotice('\u23f1 \u23f1 \u23f1 There is 1 minute left on the timer to buy a pre-sale ticket for ' + presalePrice + ' tokens!! \u23f1 \u23f1 \u23f1', "", red, "", "bold"); presaleTimerSec(); } } else { if (presaleSkipMin === true) { presaleSkipMin = false; } } } function presaleCheckMin() { var presaleTimeLeft = presaleTimeCal(); var presaleMS = presaleTimeLeft % 1000; var presaleSeconds = ((presaleTimeLeft - presaleMS) % 60000); presaleSeconds = presaleSeconds / 1000; return presaleSeconds; } function presaleTimerSec() { if (!presaleSkipSec && presaleMode === 'timer') { if (presaleMinsRemain > 0) { cb.setTimeout(presaleTimerMin, presaleSecsRemain*1000); } else { presaleSecsRemain--; if (presaleSecsRemain < 1) { cb.sendNotice("\u23f0 \u23f0 \u23f0 Time is up! Pre-sale Price is changing! \u23f0 \u23f0 \u23f0", "", yellow, "", "bold"); nextPresalePrice = presalePrice + presaleIncAmt; presalePriceChange(nextPresalePrice,cb.room_slug); presalePrice = nextPresalePrice; presaleIncrements ++; if (presalePrice + presaleIncAmt > ticketPrice) { stopIncrement = true; cb.sendNotice('The automated timer has ended. Pre-sale tickets will remain at ' + presalePrice + ' tokens until start of show unless manually updated by the broadcaster.','', ticketBgColor, ticketTxtColor, 'bold'); cb.sendNotice('No additional timer started, next increment would have exceeded planned ticket show price of ' + ticketPrice + '.',cb.room_slug, yellow); } else if ((presaleIncrements + 1) > cb.settings.ticketShowPresaleMaxIncrements) { stopIncrement = true; cb.sendNotice('The automated timer has ended. Pre-sale tickets will remain at ' + presalePrice + ' tokens until start of show unless manually updated by the broadcaster.','', ticketBgColor, ticketTxtColor, 'bold'); cb.sendNotice('No additional timer started, next increment would have exceeded max increments of ' + cb.settings.ticketShowPresaleMaxIncrements + '.',cb.room_slug, yellow); } else { presaleAutoTimer(presaleTimeAmt); } } else { switch (presaleSecsRemain) { case 30: case 10: case 5: case 4: case 3: case 2: case 1: cb.sendNotice('\u23f1 \u23f1 \u23f1 There are ' + presaleSecsRemain + ' second' + (presaleSecsRemain > 1 ? "s" : "") + ' left on the timer to buy a pre-sale ticket for ' + presalePrice + ' tokens! \u23f1 \u23f1 \u23f1', "", red, "", "bold"); } } if (presaleSecsRemain > 0) { cb.setTimeout(presaleTimerSec, 1000); } } } else { presaleSkipSec = false; } } function presaleAddTime(presaletimetoadd, u) { presaleStopTime = new Date(presaleStopTime.getTime() + presaletimetoadd * 60000); cb.sendNotice(u + " has added " + presaletimetoadd + " minute" + (presaletimetoadd === 1 || presaletimetoadd === -1 ? "" : "s") + " to the timer.",cb.room_slug,"","", "bold"); cb.sendNotice(presaletimetoadd + " minute" + (presaletimetoadd === 1 || presaletimetoadd === -1 ? "" : "s") + " have been added to the timer. Now " + presaleTimeLeft(),"",yellow,"", "bold"); presaleMinsRemain = presaleMinsRemain + presaletimetoadd; if (presaleMinsRemain < 1) { presaleSkipMin = true; presaleTimeLeft = presaleTimeCal(); presaleMS = presaleTimeLeft % 1000; presaleSeconds = ((presaleTimeLeft - presaleMS) % 60000) / 1000; presaleSecsRemain = presaleSeconds; presaleTimerSec(); } return; } function presaleTimeCal() { presaleStartTime = new Date(); return presaleStopTime - presaleStartTime.getTime(); } function stopPresaleTimer(mod) { presaleStopTime = new Date(); if (presaleMinsRemain > 0 || presaleSecsRemain > 0) { presaleMinsRemain = 0; presaleSecsRemain = 0; presaleSkipMin = true; presaleSkipSec = true; if(mod != null) { cb.sendNotice(mod + ' has stopped the ticket show pre-sales timer.',"",yellow,"", "bold"); } } } function presaleTimeLeft(user) { var presaleTimeLeft = presaleTimeCal(); var presaleMS = presaleTimeLeft % 1000; var presaleSeconds = ((presaleTimeLeft - presaleMS) % 60000); var presaleMinutes = ((presaleTimeLeft - presaleSeconds - presaleMS) % 3600000); var presaleHours = (presaleTimeLeft - presaleMinutes - presaleSeconds - presaleMS); presaleSeconds = presaleSeconds / 1000; presaleMinutes = presaleMinutes / 60000; presaleHours = presaleHours / 3600000; if (presaleHours > 0) { return presaleHours + " hour" + (presaleHours > 1 ? "s" : "") + " and " + presaleMinutes + " minute" + (presaleMinutes > 1 ? "s" : "") + " remaining to vote."; } else if (presaleMinutes > 0 && presaleSeconds === 0) { return presaleMinutes + " minute" + (presaleMinutes > 1 ? "s" : "") + " remaining on the timer."; } else if (presaleMinutes > 0) { return presaleMinutes + " minute" + (presaleMinutes > 1 ? "s" : "") + " and " + presaleSeconds + " second" + (presaleSeconds > 1 ? "s" : "") + " remaining on the timer."; } else { return presaleSeconds + " second" + (presaleSeconds > 1 ? "s" : "") + " remaining on the timer."; } } function addRmvPresale(mode,user) { switch(mode) { case 'add': { cb.limitCam_addUsers([user]); if (!cbjs.arrayContains(ticketShowViewerList,user)) { ticketShowViewerList.push(user); } cb.sendNotice(user + ' has been added to the pre-sale ticket list.', '', ticketBgColor, ticketTxtColor, 'bold'); countPresaleSold ++; if (presaleMode == 'count' && stopIncrement === false) { checkPresaleCount(); } break; } case 'rmv': { cb.limitCam_removeUsers([user]); if (cbjs.arrayContains(ticketShowViewerList,user)) { ticketShowViewerList.pop(user); } break; } case 'addtip': { cb.limitCam_addUsers([user]); if (!cbjs.arrayContains(ticketShowViewerList,user)) { ticketShowViewerList.push(user); } cb.sendNotice('Welcome ' + user + ', you have been added to the pre-sale ticket list.', '', ticketBgColor, ticketTxtColor, 'bold'); countPresaleSold ++; if (presaleMode == 'count' && stopIncrement === false) { checkPresaleCount(); } break; } } } function checkPresaleCount() { ticketsLeft = (cb.settings.ticketShowPresaleCountIncrement - countPresaleSold) if (ticketsLeft > 0) { cb.sendNotice('There ' + (ticketsLeft > 1 ? "are " : "is ") + (ticketsLeft) + ' pre-sale ticket' + (ticketsLeft > 1 ? "s" : "") + ' remaining at a price of ' + presalePrice + ' tokens!',"", ticketBgColor, ticketTxtColor, 'bold'); } else { if (presalePrice + presaleIncAmt > ticketPrice) { stopIncrement = true; cb.sendNotice('Pre-sale tickets will remain at ' + presalePrice + ' tokens until start of show','', ticketBgColor, ticketTxtColor, 'bold'); cb.sendNotice('No additional price changes made, next increment would have exceeded planned ticket show price of ' + ticketPrice + '.',cb.room_slug, yellow); } else if (presaleIncrements + 1 > cb.settings.ticketShowPresaleMaxIncrements) { stopIncrement = true; cb.sendNotice('Pre-sale tickets will remain at ' + presalePrice + ' tokens until start of show','', ticketBgColor, ticketTxtColor, 'bold'); cb.sendNotice('No additional price changes made, next increment would have exceeded max increments of ' + cb.settings.ticketShowPresaleMaxIncrements + '.',cb.room_slug, yellow); } else { cb.sendNotice('All pre-sale tickets at a price of ' + presalePrice + ' tokens have been sold!','', ticketBgColor, ticketTxtColor, 'bold'); presaleIncrements ++; nextPresalePrice = presalePrice + presaleIncAmt; presalePriceChange(nextPresalePrice,cb.room_slug); countPresaleSold = 0; ticketsLeft = cb.settings.ticketShowPresaleCountIncrement; } } } // *********************************** Goal Race ************************************** function setGoalRaceToggle(option, setby) { if (option == 'on') { goalraceColors(); whichApp = 'goalrace'; initGoalRace(); cb.sendNotice(dashLine80 + '\n* ' + setby + ' has started the Goal Race feature.\n* Select which goal to vote for when you tip!\n' + dashLine80, '', goalraceBgColor, goalraceTxtColor, 'bold'); } else if (option == 'off') { cb.sendNotice(dashLine60 + '\n* ' + setby + ' has ended the Goal Race feature.\n' + dashLine60, '', goalraceBgColor, goalraceTxtColor, 'bold'); } } function initGoalRace() { tipOptions = true; currentGroupTipAmt = 0; finalGoalMet = false; changeRoomSubject(); fontSize = 11; } function goalraceColors() { if (cb.settings.goalraceTextColor == 'Custom') { goalraceTxtColor = checkTextColor(cb.settings.goalraceCustomTextColor); if (goalraceTxtColor == 'default') { cb.sendNotice('Goal Race - Error while setting the text color. It has to be in a HEX format. Using default value.', cb.room_slug, yellow, '', 'bold'); goalraceTxtColor = '#FFFFFF'; } } else { goalraceTxtColor = checkTextColor(cb.settings.goalraceTextColor); } if (cb.settings.goalraceBgColor == 'Custom') { goalraceBgColor = checkBgColor(cb.settings.goalraceCustomBgColor); if (goalraceBgColor == 'default') { cb.sendNotice('Goal Race - Error while setting the background color. It has to be in a HEX format. Using default value.', cb.room_slug, yellow, '', 'bold'); goalraceBgColor = '#FFFFFF'; } } else { goalraceBgColor = checkBgColor(cb.settings.goalraceBgColor); } } function restartRace() { currentGoalRace1Tips = 0; currentGoalRace2Tips = 0; finalGoalMet = false; cb.drawPanel(); } // *********************************** Help Function ************************************** function helpModBC(option,from) { var valid = 0; if(option == null){option = '';} switch(option) { case '': { valid = 1; cb.sendNotice('Dorothys Ultra App Help Menu for Broadcasters and Moderators', from, yellow); cb.sendNotice('Type /uahelp [menu], where [menu] is one of the following choices, for more detailed information.\nEx: "/uahelp goals" to see the submenu for the Progressive Goal Show related commands.', from); cb.sendNotice('',from,yellow); cb.sendNotice( 'all : (/chgapp, /stats, /addtips, /listgoals (/lg))' + '\ngoals : (/restartgoal, /setgoal1, /setgoal2, /setgoal3...(thru /setgoal20), /rmvgoal, /setgoaltext, /next, /skip)' + '\ngoalcount : (/setcount1, /setcount2, /setcount3...(thru /setcount10), /rmvcount, /setcounttext, /chgcountgoal, /skip, /skiplevel)' + '\nsequence : (/setseq1, /setseq2, /setseq3...(thru /setseq10), /rmvseq, /setsequencetext, /usechatmsg, /usegrouptips, /skip, /skiplevel, /chgendseq )' + '\ntipjar : (/setjar1, /setjar2, /setjar3...(thru /setjar10), /rmvjar, /setjartext, /faster, slower, /next, /skip)' + '\ngoalrace : (/setrace1, /setrace2, /restartrace, /addrace1, /addrace2, /tipnoteon, tipnoteoff, /setracetext, /setracepaneltext)' + '\nticketshow : (see full command list in submenu)' ,from); cb.sendNotice('',from,yellow); break; } case 'all': { valid = 1; cb.sendNotice('Ultra App Command List',from,yellow); cb.sendNotice('',from,yellow); cb.sendNotice( 'Ultra App General Commands - these are used across all app features. The primary command noted below is the /chgapp command, which lets you switch between each of the 5 apps that are built into the UltraApp so far. You can switch at any time, but you if you change in the middle of a goal, you cancel any open goals in that app when you do. Total tipping and time online stats are also kept and can be accessed anytime. ' + '\n/chgapp [newapp] : switch from one app to another, or turn off the current app. The values used for [newapp] are "goals", "goalcount", "sequence", "tipjar", and "ticket" to go along with the 5 types of shows, or the value of "none" to turn off the current app feature and not start a new one immediately.' + '\n/stats : Display a listing of your time online (with the app running), total tips, and tips broken down by app.' + '\n/addtips [tokens] : Within the goal apps (Progressive Goal, Goal Count, and Tip Jar), this can be used to simulate users having tipped and advance the token count within a goal. Indicate the number of tokens you are adding as the [tokens] parameter, and note the value can be negative. You can add more than the current goal, but you cannot subtract less than has been tipped in the current goal.' + '\n/listgoals (also /lg) : List the current setup of the goals for the App feature that is active. ' + '\n/chgpanelbg [imagename]: Change the background of the drawpanel to one of the valid images. Images are updated regularly, so you can see the current list by entering this command with no parameter and the error message will show the current valid choices. Note that you can also see the images by going to the "Source Code" tab for the app and clicking the link for "App Images".' + '\n/chgpaneltext [newcolor]: Change the color of the text in the drawpanel to either a hex code (#0000ff) or the exact text of one of the color choices from the menu ("Dark Green", "Dark Red", etc). You can lookup hex codes for any color on a site such as Color-hexa: https://www.colorhexa.com/' ,from); cb.sendNotice('',from,yellow); break; } case 'goals': { valid = 1; cb.sendNotice('Ultra App Help - Progressive Goals',from,yellow); cb.sendNotice('For the progressive goal show, you can setup a single goal, or multiple goals that will be progressed through in sequence as users tip. There is a configuration flag that defines if the app automatically moves from one goal to the next, or requires the use of the /next command to advance after the goal is met. If set to automatic advance, tips that exceed the goal will carry over to the next goal. If set to advance by command, the tips do not roll over.' + '\nBy default, the room subject will show the current goal amount and description, the next goal description and a configurable block of text that you can use to describe what happens at the last goal or after the goals, or even just put searchable hashtags to draw more people into the show. This room subject text can be edited using the "/setgoaltext" command as noted below.' + '\nAny changes you make are not stored permanently, they are only stored within the current session. They will be kept if you switch between app features, but not if you deactivate the app. ' + '\nAlso, you must keep the goals in sequence, and cannot add an entry that would leave an empty level. For example, if goal levels 1-3 are filled in, you can\'t add a level 5, you must add a level 4 first. ' ,from); cb.sendNotice('',from,yellow); cb.sendNotice('Ultra App Goal Show Commands ' + '\n/restartgoal : If you\'d like to repeat a goal (common if you have a single goal you\'re recycling), you can use this command at any time to reset the tip count on the current goal to 0 (even if already complete)' + '\n/setgoal1, /setgoal2, /setgoal3...(thru /setgoal20) [goal] [description] : These are the commands that let you edit the goals for your show. Both the [goal] and [description] parameters must be entered every time and will update both values. Note this is also only a temporary change made within the session, it does not permanently update the launch page config.' + '\n--The 1-20 designation as part of the command identifies which entry you are modifying.' + '\n--The [goal] parameter is the new value you are setting for the goal amount. Even if you are not changing the goal (only changing the description, you must still enter the existing value for the goal.' + '\n--The [description] Parameter is the new value you are setting for the goal description. Even if you are not changing the description (only changing the goal amount), you must still enter the existing value for the description.' + '\n--An example of the syntax for this command would be "/setgoal4 400 Blow job", which would set goal 4 to be a Blow Job once you reach 400 tokens. ' + '\n--Note that you can\'t make updates to the current goal or past goal, only future goals.' + '\n/rmvgoal [level] : Remove the goal entry for goal level of [level] (amount and description are both removed). Note this is also only a temporary change made within the session, it does not permanently update the launch page config. ' + '\n/setgoaltext [newsubject] : Update the text that is shown in the Room Subject between the current goal and the next goal.' + '\n/next : Used to advance to the next goal once the current goal is complete, when auto advance is turned off.' + '\n/skip : Advance to the next goal regardless of the status of the current goal.' ,from); cb.sendNotice('',from,yellow); break; } case 'goalcount': { valid = 1; cb.sendNotice('Ultra App Help - Goal Counter',from,yellow); cb.sendNotice('For the Goal Counter show, you define the smaller goal amount that will be tipped repeatedly, and then define prizes that will occur at a certain number of goals reached. For example, you may define that each goal will be 99 tokens, and then at 5 goals you take off your shirt, 10 goals take off your pants, etc. If there is a final/end goal, set it up as the highest of the goal level thresholds in the configuration list, the app will stop counting once the highest goal count is met. ' + '\nBy default, the room subject will show the remaining goals levels and their descriptions/prizes, plus a configurable block of text that you can use to describe what happens at the last goal or after the goals, or even just put searchable hashtags to draw more people into the show. This room subject text can be edited using the "/setcounttext" command as noted below.' ,from); cb.sendNotice('',from,yellow); cb.sendNotice( 'Ultra App Goal Show Commands - these are used for the Goal Counter feature' + '\n/setcount1, /setcount2, /setcount3...(thru /setcount10) [goal] [description] : These are the commands that let you edit the goal levels for your show. Both the [goal] and [description] parameters must be entered every time and will update both values. Note that you can use these commands to add or edit an entry, however they cannot be used to delete an entry (use the /rmvcount).' + '\n--The 1-10 designation as part of the command identifies which Goal Level entry you are adding or modifying.' + '\n--The [goal] parameter is the new value you are setting for the goal level (such as "10" for doing a prize at the 10th goal). Even if you are not changing the goal level (only changing the description, you must still enter the existing value for the goal level.' + '\n--The [description] Parameter is the new value you are setting for the goal level description. Even if you are not changing the description (only changing the goal level), you must still enter the existing value for the description.' + '\n--An example of the syntax for this command would be "/setcount5 20 Blow job", which would set goal level 5 to be a Blow Job, to be done after you\'ve hit the individual goals 20 times. ' + '\n--Note that you can\'t make updates to the current goal level or past goal levels, only future goal levels.' + '\n/rmvcount [level] : Remove the goal from goal counter level of [level] (amount and description are both removed). Note this is also only a temporary change made within the session, it does not permanently update the launch page config. ' + '\n/setcounttext [newsubject] : Update the text that is shown in the Room Subject after the listing of the remaining goal levels.' + '\n/chgcountgoal [amt] : Change the amount that is being used for the individual goal levels (99 in the above example) to a new value of [amt]. If changing the value after the tipping has already started, it cannot be set to a value that is less than the tips received against the current goal.' + '\n/skip : Advance to the next individual goal regardless of the status of the current goal. Note this will only do one individual goal at a time, you can use the command /skiplevel described below to skip past the current goal level threshold' + '\n/skiplevel : Advance to the point the next goal level is complete, regardless of the status of the current goal. If there are no more goal level prizes, this will skip to the end of the show, so check the goal list (/lg) to be sure you know how what levels remaining have goals. As an example, if you are on goal 7, and there are prizes every 5 goals, it will skip ahead to having completed goal 10.' ,from); cb.sendNotice('',from,yellow); break; } case 'sequence': { valid = 1; cb.sendNotice('Ultra App Help - Sequence Goals',from,yellow); cb.sendNotice('For the Tip Sequence goal show, you can setup the starting and ending points in a sequence that users will tip through, and there can be intermediate goals defined at certain sequence thresholds. The tip sequence can be ascending or descending. For example, you could define the sequence as ascending from 1 to 50, with a goal threshold at 15, 20, 30, and 40... you could also define a sequence as descending from 60 to 10, with goal thresholds at 50, 40, 30, 20, 10, the direction, the range, and the goal levels are all configurable. ' + '\nBy default, the room subject will show the remaining goals sequence levels and their descriptions/prizes, plus a configurable block of text that you can use to describe what happens at the last goal or after the goals, or even just put searchable hashtags to draw more people into the show. This room subject text can be edited using the "/setsequencetext" command as noted below.' + '\nGroup tipping is available, which allows smaller tips to be accumulated against the current tip sequence number, and advancing when the accumulation meets or exceeds the next number. ' ,from); cb.sendNotice('',from,yellow); cb.sendNotice( 'Ultra App Goal Show Commands - these are used for the Sequence Goal feature' + '\n/setseq1, /setseq2, /setseq3...(thru /setseq10) [goal] [description] : These are the commands that let you edit the goal levels for your show. Both the [goal] and [description] parameters must be entered every time and will update both values. Note that you can use these commands to add or edit an entry, however they cannot be used to delete an entry (use the /rmvseq).' + '\n--The 1-10 designation as part of the command identifies which Sequence Goal Level entry you are adding or modifying.' + '\n--The [goal] parameter is the new value you are setting for the goal level (such as "10" for doing a prize once the count sequence reaches and exceeds the number 10). Even if you are not changing the sequence goal level (only changing the description), you must still enter the existing value for the sequence goal level.' + '\n--The [description] Parameter is the new value you are setting for the goal level description. Even if you are not changing the description (only changing the goal level), you must still enter the existing value for the description.' + '\n--An example of the syntax for this command would be "/setcount5 20 Blow job", which would set goal level 5 to be a Blow Job, to be done after you\'ve hit the individual goals 20 times.' + '\n--Note that you can\'t make updates to the current sequence goal level or past sequence goal levels, only future sequence goal levels.' + '\n/rmvseq [level] : Remove the goal from sequence goal level of [level] (amount and description are both removed). Note this is also only a temporary change made within the session, it does not permanently update the launch page config. ' + '\n/setsequencetext [newsubject] : Update the text that is shown in the Room Subject after the listing of the remaining goal sequence levels.' + '\n/usechatmsg [on/off] : Toggle the flag on and off for whether a message is posted in the chat for each sequence as it is achieved.' + '\n/usegrouptips [on/off] : Toggle the flag on and off for whether group tipping is allowed.' + '\n/skip : Advance to the next sequence regardless of the status of the current sequence.' + '\n/chgendseq [newseq] : Update the value for the end of the sequence. When ascending this is the higher number of the range, and when descending it is the low number of the range. The value cannot be updated to a sequence that has already been passed, or affect the current goal level target.' ,from); cb.sendNotice('',from,yellow); break; } case 'tipjar': { valid = 1; cb.sendNotice('Ultra App Help - Tip Jar Goals',from,yellow); cb.sendNotice('For the Tip Jar show, you can setup a single goal, or multiple goals that will be progressed through in sequence as users tip. As each goal is reach, a countdown begins using the number of tokens in the jar (total tips when goal was hit or passed). Tokens begin to "drain" out of the jar at the specified rate of tokens per second. During this time, the user is performing the prize for that goal, and they would stop performing once the tip jar runs out/empties. While it is draining, users can continue to tip to keep it full and keep the performance going. ' + '\nOne the jar is empty, there is a configuration flag that defines if the app automatically moves from the completed goal to the next, or requires the use of the /next command to advance after the goal is met. If set to automatic advance, user will now be able to tip towards the next goal. ' + '\nThe goals also have a recyle count setting, which lets the broadcaster identify that some goals will be repeated over and over, to the defined threshold. Note that a recycle of "1", means that the goal will be performed twice (two cycles). ' + '\nBy default, the room subject will show the current goal amount and description, and a configurable block of text that you cn use to describe what happens when the tip jar is running, or even just put searchable hashtags to draw more people into the show. This room subject text can be edited using the "/setjartext" command as noted below.' ,from); cb.sendNotice('',from,yellow); cb.sendNotice( 'Ultra App Goal Show Commands - these are used for the Tip Jar Goal feature' + '\n/setjar1, /setjar2, /setjar3...(thru /setjar10) [goal] [recyc] [description] : These are the commands that let you edit the goals for your show. All three of the [goal], [recyc], and [description] parameters must be entered every time and it will update all three values. Note that you can use these commands to add or edit an entry, however they cannot be used to delete an entry (use the /rmvjar for that).' + '\n--The 1-10 designation as part of the command identifies which entry you are modifying ' + '\n--The [goal] parameter is the value you are setting for the goal amount. Even if you are not changing the goal (only changing the description), you must still re-enter the existing value for the goal.' + '\n--The [recyc] parameter is the value you are setting for the recycle count. Even if you are not changing the recycle count (only changing the goal amount or description), you must still re-enter the existing value for the recycle count. Note that the default for recycle is "0", which means the goal is done once. With a recycle of "1", the goal is done twice, and if the recycle is "2", the goal is done three times, etc. ' + '\n--The [description] Parameter is the value you are setting for the goal description. Even if you are not changing the description (only changing the goal amount), you must still re-enter the existing value for the description.' + '\n--And example of the syntax for this command would be "/setjar2 400 Blow job", which would set goal 2 to begin a Blow Job once you reach 400 tokens and continue that prize as long as the tip jar has tokens in it. ' + '\n--Note that you can\'t make updates to the current goal or past goals, only future goals.' + '\n/rmvjar [level] : Remove the goal for the tip jar goal level of [level] (amount and description are both removed). Note this is also only a temporary change made within the session, it does not permanently update the launch page config. ' + '\n/setjartext [newsubject] : Update the text that is shown in the Room Subject after the current goal.' + '\n/faster [levels] : Speeds up the drain rate by the specified number of levels. If no level is specified it is assumed to be by one level. If a level is specified beyond the maximum, the maximum is used.' + '\n/slower [levels] : Slows down up the drain rate by the specified number of levels. If no level is specified it is assumed to be by one level. If a level is specified below the minimum, the minimum is used.' + '\n/next : Used to advance to the next goal once the current goal is complete, when auto advance is turned off.' + '\n/skip : Advance to the next goal regardless of the status of the current goal. This will also bypass the prize/draining of the jar for the current goal. Note that for the Tip Jar goals, the /skip and /next work in a similar fashion.' ,from); cb.sendNotice('',from,yellow); break; } case 'ticketshow': { valid = 1; cb.sendNotice('Ultra App Command List',from,yellow); cb.sendNotice('',from,yellow); cb.sendNotice( 'Ultra App Ticket Show Commands - they are used specifically for the Ultra App Ticket show' + '\n/useshow (or /useticketshow) [on/off]: (mods/bc only) Toggle the setting for whether the Ultra App Ticket Show feature is "on" or "off". Overrides the initial setting, and allows you to turn the Ticket Show feature on or off during the show. Note that turning the show off will suspend the display of the notice, and tips will no longer buy a ticket, however, the existing ticket purchases are kept until the Ultra App is restarted.' + '\n/tickets : (mods/bc only) Display the list of users that have bought a ticket. If the parameter of "alpha" is added, the list is displayed alphabetically. Note that viewers can be added back to the show using the /add or /addticket commands and pasting the list that is shown from the /tickets command. ' + '\n/useot [on/off]: (mods/bc only) Toggle the setting for whether the Outstanding Ticket feature of the Ticket Show is "on" or "off". Overrides the initial setting, and allows you to turn the Outstanding Ticket usage feature on or off during the show. .' + '\n/otlist : (all users) Display the list of outstanding ticket holders, can be used by anyone if the Outstanding Ticket feature is enabled.' + '\n/otchanges : (mods/bc only) ** IMPORTANT when using the OT feature ** Displays a list of tickets that have been saved or used during the current session so the permanent list can be updated.' + '\n/saveticket: (all ticket buyers) If the broadcaster has enabled Outstanding Tickets (and is tracking them) - If you have bought a ticket and will not be able to stay for the show, you can save it for a future show. You will no longer be able to see the current show. IMPORTANT: If in the same session, the ticket will be available automatically. However for future shows or if the broadcaster restarts the bot, the broadcaster must add the saved tickets to the outstanding ticket list to be able to use them with /useticket. ' + '\n/useticket: (all users with an outstanding ticket) If the broadcaster has enabled Outstanding Tickets (and is tracking them) - Redeem an outstanding ticket and use it for access to this show. You can use the command /otlist to view the list of outstanding ticket holders if the broadcaster has enabled this feature.' + '\n/addot : (mods, bc if granted privileges) If the outstanding ticket feature is in use, the broadcaster can manually give a user an outstanding ticket. Moderators can also add if they have authority. The addition still must be made permanent by updating the launch page outstanding ticket list.' + '\n/rmvot : (mods/bc only) Remove a user from the outstanding ticket list within the current show. The removal still must be made permanent by updating the launch page outstanding ticket list.' + '\n/addticket or (/add) [user]: (bc only, moderator when granted privileges) Manually add a user to the ticket show list. Can be a specific user or a list of users separated by a comma. Note that /add is the CrazyTicket command but also works with the Ultra App Ticket show.' + '\n/rmvticket (or /del or /delticket) [user]: (mods/bc only) Manually remove a specific user from the ticket show list, only used for one user at a time. ' + '\n/startshow: (mods/bc only) Start the ticket show when not set to automatic start. Once started, the show will only be visible to ticket holders. Hint: start the show when you are in a good position for the preview pic to be frozen that will attract more ticket buyers. Note that /startshow is the CrazyTicket command but also works with the Ultra App Ticket show.' + '\n/showwarn (or /showover): (mods/bc only) Display a warning that the show will be ending soon and ticket purchases are allowed but not recommended. If configured, this can also end the positions menu, and reduce the ticket price. Note that /showover is the CrazyTicket command but also works with the Ultra App Ticket show.' + '\n/showend (or /stopsales): (mods/bc only) Suspend ticket sales, no more automatic ticket purchases can be made. Recommended to always do this once you are less than a few minutes from the end of the show so people do not buy at the last second and are disappointed by getting a short show. If configured, this can also end the positions menu, and reduce the ticket price. Note that /showend is the CrazyTicket command but also works with the Ultra App Ticket show.' + '\n/stopshow: (mods/bc only) End the hidden show and return to a public broadcast.' + '\n/newticketshow: (mods/bc only) Completely refresh the ticket show to start a brand new show. This will remove all the ticket holders from the list, and re-initialize all settings using the configuration from the launch page.' + '\n/restartshow: (mods/bc only) Go back into hidden cam mode if the show was accidentally ended too soon, or the broadcaster wanted to go back to public to sell more tickets. The ticket holder list and all settings are kept intact.' + '\n/restartsales: (mods/bc only) Restart ticket sales either during the show (after /showend was used), or after you\'ve ended the show to go back to the ticket sales stage. The ticket holder list, ticket price and show description are kept intact.' + '\n/ticketprice (or /ctprice, or /chgticketprice) [newprice]: (mods/bc only) Update the ticket price to the [newprice].' + '\n/starttimer (or /ticketstarttimer, or /starttickettimer) [time]: (mods/bc only) Start a [time] minute timer for the raffle drawing when in "timer" mode with the drawing to be triggered by the /raffledrawing command. The timer will count down but not automatically perform the drawing (unless set to automatic mode but the auto-timer was ended, and this is a restart of that timer)' + '\n/addtime (or /ticketaddtime, or /addtickettime) [time]: (mods/bc only) Add [time] minutes to the timer for either automatic or manual drawing mode. The [time] value can be a negative number to subtract time, but cannot be greater than the remaining time.' + '\n/stoptimer (or /ticketstoptimer, or /stoptickettimer): (mods/bc only) Stop the raffle timer for either automatic or manual drawing mode.' + '\n/tickettimeleft : (mods/bc only) Display the time left on the ticket show countdown for either automatic or manual starting mode.' + '\n/showtime : (all users) Display a message showing how long the current show has been hidden.' + '\n/chgticketmode (or /chgtktmode) [manual/timer/ticketgoal/tokengoal]: (mods/bc only) Switch between the modes being used to determine when to start the ticket show. If switching from a timer show to a non-timer show, the timer will be ended. Ticket count and Tip Count are being tracked regardless of mode, so switching to a "goal" mode should not require starting progress at 0.' + '\n/chgticketauto (or /chgtktauto) [auto/bc]: (mods/bc only) Switch between the modes being used to define if the show starts automatically when a goal is reached or timer expires, or if the broadcaster or mods still control the start of the show. ' + '\n/giftticket [user]: (all users, once you have extra tickets) If the "gifting" feature is enabled, when you tip enough to buy extra tickets, you can gift those tickets to other users using this command. Each time you gift, it removes one of your "extra" tickets. You can only gift extra tickets with this command, to give away your own ticket, you can use /givemyticketto as noted below. Be sure to type the user name correctly for the person you are gifting to, extra tickets cannot be recovered once they are gifted. This can be done before and during the show.' + '\n/givemyticketto [user]: (all ticket buyers) If you cannot stay for a show, and outstanding ticket feature is no used to allow saving your ticket, you can give your ticket to another user. This can only be done before the show starts, and you will be removed from the ticket show list!' + '\n/ticketsubject (or /ctsubject) [newsubject]: (mods/bc only) Change the room description/subject/title to a new value. This keeps the standard ticket show formatting and only changes the configurable part of the subject.' + '\n/addlbtop [X]: (bc/mods) Add the top [X] number of tippers for the current session to the ticket show. Moderators may only use this if allowed per configuration. This can be used with the Ultra App Ticket show or in support of CrazyTicket. The /add command is executed, which will add them to the show if the user executing them has authority to the /add command' + '\n/addlbamt [X]: (bc/mods) Add tippers who have tipped at least [X] number of tokens for the current session to the ticket show. Moderators may only use this if allowed per configuration. This can be used with the Ultra App Ticket show or in support of CrazyTicket. The /add command is executed, which will add them to the show if the user executing them has authority to the /add command' + '\n/ctn: (bc/mods) Post a one-time notice in the chat. The syntax for using this type of notice is "/cn [X]", where [X] is the message you want to send. This is the plain notification without any separators or highlighting.' + '\n/ctnd: (bc/mods) Post a one-time notice in the chat. The syntax for using this type of notice is "/cnd [X]", where [X] is the message you want to send. This notification includes a dash separator before and after the message, but no highlighting.' + '\n/ctnh: (bc/mods) Post a one-time notice in the chat. The syntax for using this type of notice is "/cnh [X]", where [X] is the message you want to send. This notification includes highlighting, but no separators.' + '\n/ctndh: (bc/mods) Post a one-time notice in the chat. The syntax for using this type of notice is "/cndh [X]", where [X] is the message you want to send. This notification includes both separators and highlighting.' ,from); cb.sendNotice('',from,yellow); break; } case 'goalrace': { valid = 1; cb.sendNotice('Ultra App Help - Goal Race',from,yellow); cb.sendNotice('For the Goal Race, you define the two goal amounts and descriptions, and viewers tip toward each goal.' + '\nWhen this app is active and a user tips, the tip panel will show a choice of the two goals to tip toward, they can choose one of the two goals, or not make a selection.' + '\nThe user can also toggle on and off a personal setting that allows them to either send votes or regular tip notes.' + '\nThe goals, goal descriptions, room subject text, and panel subject text are configurable on the launch page and also through the commands below, so you don\'t have to restart the app to make changes.' ,from); cb.sendNotice('',from,yellow); cb.sendNotice( 'Ultra App Goal Race Commands - these are used for the Goal Race feature' + '\n/restartrace: Lets you restart the goal race (reset tip amounts to 0) with the same settings.' + '\n/setrace1, /setrace2 [amount] [description] : These are the commands that let you edit the goal amounts and goal descriptions for your show. Both the [amount] and [description] parameters must be entered every time and will update both values.' + '\n--The "1" or "2" designation as part of the command identifies which Goal Choice entry you are modifying.' + '\n--The [amount] parameter is the new value you are setting for the goal amount (such as "500" for a goal amount). Even if you are not changing the goal amount (only changing the description, you must still enter the existing value for the goal amount as both will be updated.' + '\n--The [description] parameter is the new value you are setting for the goal description (such as "Bra Off"). Even if you are not changing the description (only changing the goal amount), you must still enter the existing value for the description.' + '\n--An example of the syntax for this command would be "/setrace2 500 Bra Off", which would set goal #2 to be the choice of "Bra Off" (which may compete against "Panties Off, for example). ' + '\n--Note that you can\'t make updates to the goal amount that would be less than what is already tipped for that goal.' + '\n/addrace1, /addrace2: If you have tipped during the Race and did not specify a goal at the time, these tips are accumulated and can be "claimed" for either goal 1 or goal 2 using one of these two commands. The accrued amount cannot be split, the whole amount will go toward the goal selected.' + '\n/tipnoteon, /tipnoteoff: This command lets you toggle whether you are sending tip notes to the broadcaster or casting votes for the goal.' + '\n/setracetext [newsubject] : Update the text that is shown in the Room Subject after the listing of the race goals.' + '\n/setracepaneltext [newsubject] : Update the text that is shown in the First Line of the Draw Panel.' ,from); cb.sendNotice('',from,yellow); break; } } if(valid == 0) { cb.sendNotice(option + ' is not a valid subsection of the help menu. Type "/uahelp" to access the main help menu.',from,yellow); } } } } // ******************************* Upon user entry of a Message ************************************** { cb.onMessage(function (msg) { var message = msg['m'].split(' '); var cmd = 0; var symbolString = '~`!@#$%^&*()_-+={[}]|\\:;"\'<,>.?/'; var listRegExp = /[,\s]+/; var listRegExpSpc = /[,]+/; var m = msg.m; var u = msg.user; var isMod = msg.is_mod; var isFan = msg.in_fanclub; var isExtFan = cbjs.arrayContains(extFanListArray,u); var isVIP = cbjs.arrayContains(VIPListArray,u); var isBC = (u === cb.room_slug); var BC = cb.room_slug; var command = message[0] var commandVar1 = parseInt(message[1]); var commandVar2 = parseInt(message[2]); var commandVar3 = parseInt(message[3]); if(message[0].charAt(0) == '/') { msg['X-Spam'] = true; msg['X-Spam'] = true; var ntc = null; for (var i = 1; i < message.length; i++) { if (i == 1) ntc = message[i]; else ntc += " " + message[i]; } var ntc2 = null; for (var i = 2; i < message.length; i++) { if (i == 2) ntc2 = message[i]; else ntc2 += " " + message[i]; } var cmdval = null; for (var i = 1; i < message.length; i++) { if (i == 1) cmdval = message[i]; else cmdval += " " + message[i]; } if(isMod) { populateModeratorArray(u, "mod"); } if(isFan) { populateFanClubArray(u); } switch(command) { //********* General Ultra App Commands case '/chgapp': { cmd = 1; if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { newApp = message[1].toLowerCase(); if (newApp != 'ticket' && newApp != 'goals' && newApp != 'goalcount' && newApp != 'sequence' && newApp != 'tipjar' && newApp != 'goalrace' && newApp != 'none') { cb.sendNotice('The value entered for the app to use is invalid, please try again using a value of "ticket", "goals", "goalcount", "sequence", "tipjar", "goalrace", or "none".', u, yellow); } else if (newApp == 'ticket' && ticketPrice <= 0) { cb.sendNotice('Unable to start the Ultra App Ticket Show feature, a ticket price is not defined. The command "/ticketprice [price]" can be used to set a price.', u, yellow); } else if (newApp == 'goals' && progGoalArray.desc.length <= 0) { cb.sendNotice('Unable to start the Ultra App Progressive Goals feature, no Goals have been configured on the launch page.', u, yellow); } else if (newApp == 'goalcount' && goalCounterArray.desc.length <= 0) { cb.sendNotice('Unable to start the Ultra App Goal Count feature, no Goal Levels have been configured on the launch page.', u, yellow); } else if (newApp == 'sequence' && sequenceArray.desc.length <= 0) { cb.sendNotice('Unable to start the Ultra App Sequence Goals feature, no goal Seqeunce Level Goals have been configured on the launch page.', u, yellow); } else if (newApp == 'tipjar' && tipjarGoalArray.desc.length <= 0) { cb.sendNotice('Unable to start the Ultra App Tip Jar feature, no Tip Jar goals have been have been configured on the launch page.', u, yellow); } else if (newApp == 'goalrace' && (goalraceDesc1 == '' || goalraceDesc1 == null || goalraceDesc2 == '' || goalraceDesc2 == null)) { cb.sendNotice('Unable to start the Ultra App Goal Race feature, either the Goal 1 or Goal 2 Description is missing.', u, yellow); } else if (newApp == 'goalrace' && (goalraceAmount1 == 0 || goalraceAmount2 == 0)) { cb.sendNotice('Unable to start the Ultra App Goal Race feature, either the Goal 1 or Goal 2 Amount is zero.', u, yellow); } else { if (newApp === whichApp) { cb.sendNotice('The value entered for the new mode is the same as the existing mode, command ignored.', u, yellow); } else { setAppFeature(newApp, u); } } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } break; } case '/chgpanelbg': { cmd = 1; if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { if (message[1]) { newBg = message[1].toLowerCase(); customizePanelBackground(newBg,u); } else { cb.sendNotice('The /chgpanelbg command requires the entry of a parameter following the command, such as "/chgpanelbg lavalamp". The valid formats are: \n' + cbjs.arrayJoin(backgroundArray.command, ', '), u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } break; } case '/chgpaneltext': { cmd = 1; if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { newcolor = msg['m'].substring(14).trim(); if (newcolor) { customizePanelText(newcolor,u); } else { cb.sendNotice('The /chgpaneltext command requires the entry of a parameter following the command, which represents either the color name or the color hex code, such as "/chgpaneltext blue" or "/chgpaneltext #0000ff".' , u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } break; } case '/next': { cmd = 1; if (whichApp == 'tipjar' || whichApp == 'goals') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { if (finalGoalMet) { cb.sendNotice('Command cannot be used once all goals, cycles or sequences have been completed.', u, yellow); } else if (tipJarRunning) { cb.sendNotice('Command cannot be used while tip jar is running, tokens can be removed from the tip jar using "/addtips" with a negative number.', u, yellow); } else if (currentGoalTips < currentGoalTotal) { cb.sendNotice('Command cannot be used during goal, it should be used only to manually advance a goal once complete if the auto-advance is off. The /skip command can be used to bypass the rest of a goal.', u, yellow); } else { advanceGoal(); cb.sendNotice('You have manually advanced to the next goal.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/next" command is for use with the Tip Jar and Progressive Goals, and neither app is currently running (current app is ' + whichApp + ').', u, yellow); } break; } case '/skip': { cmd = 1; if (whichApp == 'tipjar' || whichApp == 'goals' || whichApp == 'goalcount' || whichApp == 'sequence') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { if (finalGoalMet) { cb.sendNotice('Command cannot be used once all goals, cycles or sequences have been completed.', u, yellow); } else if (tipJarRunning) { cb.sendNotice('Command cannot be used while tip jar is running, tokens can be removed from the tip jar using "/addtips" with a negative number.', u, yellow); } else { advanceGoal(); cb.sendNotice('You have skipped the remainder of the current goal, cycle, or sequence and advanced to the next.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/skip" command is for use with the Tip Jar, Progressive Goals, Goal Counter, or Tip Sequence apps (current app is ' + whichApp + ').', u, yellow); } break; } case '/skiplevel': { cmd = 1; if (whichApp == 'goalcount' || whichApp == 'sequence') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { if (finalGoalMet) { cb.sendNotice('Command cannot be used once all goals, cycles or sequences have been completed.', u, yellow); } else if (whichApp == 'goalcount' && (currentGoalLevel) > goalCounterArray.amt.length) { cb.sendNotice('No further Goal Counter Levels to skip.', u, yellow); } else if (whichApp == 'sequence' && (currentGoalLevel) > sequenceArray.amt.length) { cb.sendNotice('No further Sequence Goal Levels to skip.', u, yellow); } else { advanceGoalLevel(); cb.sendNotice('You have skipped the remainder of the current Goal Level and advanced to the next Goal Level.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/skiplevel" command is for use with the Goal Counter or Tip Sequence apps (current app is ' + whichApp + ').', u, yellow); } break; } case '/addtips': { cmd = 1; if (whichApp == 'tipjar' || whichApp == 'goals' || whichApp == 'goalcount' || whichApp == 'goalrace') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { if (commandVar1 == 0 || isNaN(commandVar1)) { if (whichApp == 'goalrace') { cb.sendNotice('The first parameter is the tip amount to be added to the current tip count, and must be a positive or negative number other than 0. For example, use "/addtips 100 1" to simulate a user having tipped 100 tokens for goal choice 1.',u,yellow); } else { cb.sendNotice('The parameter is the tip amount to be added to the current tip count, and must be a positive or negative number other than 0. For example, use "/addtips 100" to simulate a user having tipped 100 tokens.',u,yellow); } } else if (finalGoalMet) { cb.sendNotice('Command cannot be used once all goals, cycles or sequences have been completed.', u, yellow); } else if (!tipJarRunning && commandVar1 < 0 && Math.abs(commandVar1) > currentGoalTips) { cb.sendNotice('You cannot subtract more tokens than have been tipped for the current goal.', u, yellow); } else if (tipJarRunning && commandVar1 < 0 && Math.abs(commandVar1) > tipJarCurrentTokens) { cb.sendNotice('You cannot subtract more tokens than are in the tip jar.', u, yellow); } else if (tipJarWaiting) { cb.sendNotice('You cannot add tokens while the tip jar is awaiting manual advance to next goal. The "/next" command can be used to advance.', u, yellow); } else if (whichApp == 'goalrace' && commandVar2 != 1 && commandVar2 != 2) { cb.sendNotice('For Goal Race, the second parameter must be a "1" or a "2" to specify which goal the amout is added. Example syntax to add 25 tokens to Goal Choice 2 is "/addtips 25 2".', u, yellow); } else { if (whichApp == 'goalrace' && commandVar2 == 1) { tipNote = goalraceDesc1; } else if (whichApp == 'goalrace' && commandVar2 == 2) { tipNote = goalraceDesc2; } else { tipNote = ''; } recordTip(commandVar1,'bc',tipNote,isFan,isExtFan,isVIP); if (commandVar1 > 0) { cb.sendNotice('You have added ' + commandVar1 + ' tokens.', u, yellow); } else { cb.sendNotice('You have subtracted ' + Math.abs(commandVar1) + ' tokens.', u, yellow); } } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/addtips" command is only for use with the Tip Jar, Progressive Goals, Goal Race, or Goal Counter (current app is ' + whichApp + ').', u, yellow); } break; } case '/stats': { cmd = 1; if (isBC || (isMod && cb.settings.allowModsStats == 'Yes')) { cb.sendNotice('Current Session Stats: \n \u21D2 UltraApp Time Online............... ' + timeOnline() + '\n \u21D2 Total Tips................................. ' + currentSessionTotal + ' tokens ($' + Number(currentSessionTotal*.05).toFixed(2) + ' @ 5 cents per token)\n \u21D2 \u21D2 \u21D2 Progressive Goal Total..... ' + currentAppTotalGoal + ' tokens\n \u21D2 \u21D2 \u21D2 Goal Counter Total........... ' + currentAppTotalCounter + ' tokens\n \u21D2 \u21D2 \u21D2 Tip Sequence Goal Total... ' + currentAppTotalSequence + ' tokens\n \u21D2 \u21D2 \u21D2 Tip Jar Total..................... ' + currentAppTotalTipJar + ' tokens\n \u21D2 \u21D2 \u21D2 Ticket Show Total............ ' + currentAppTotalTicket + ' tokens\n \u21D2 \u21D2 \u21D2 Goal Race Total................ ' + currentAppTotalGoalRace + ' tokens\n \u21D2 \u21D2 \u21D2 No App Running Total...... ' + currentAppTotalNone + ' tokens', u, yellow, '', 'bold'); } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } break; } case '/lg': case '/listgoals': { cmd = 1; if (whichApp == 'tipjar' || whichApp == 'goals' || whichApp == 'goalcount' || whichApp == 'sequence') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { if (!message[1] || message[1] == 'all') { listGoals(message[1],u) } else { cb.sendNotice('Invalid parameter provided, the parameter should be left blank to send the list to only the requester, or can be set to "all" to send to the entire room.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/listgoals" command is for use with the Progressive Goals, Goal Count, Tip Sequence and Tip Jar apps.', u, yellow); } break; } case '/resetapp': { cmd = 1; if (whichApp == 'tipjar' || whichApp == 'goals' || whichApp == 'goalcount' || whichApp == 'sequence') { if (isBC) { resetApp(); cb.sendNotice('You have reset the App and started the goal show again at the first goal.', u, yellow); } else { cb.sendNotice('Only broadcasters are able to use the "/resetapp" command.', u, yellow); } } else { cb.sendNotice('The "/resetapp" command is for use with the Progressive Goals, Goal Count, Tip Sequence and Tip Jar apps.', u, yellow); } break; } //********* Progressive Goal Commands case '/restartgoal': { cmd = 1; if (whichApp == 'goals') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { restartGoal(); cb.sendNotice('You have restarted the current goal.', u, yellow); } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/restartgoal" command is for use with the Progressive Goals app, which is not running.', u, yellow); } break; } case '/setgoal1': case '/setgoal2': case '/setgoal3': case '/setgoal4': case '/setgoal5': case '/setgoal6': case '/setgoal7': case '/setgoal8': case '/setgoal9': case '/setgoal10': case '/setgoal11': case '/setgoal12': case '/setgoal13': case '/setgoal14': case '/setgoal15': case '/setgoal16': case '/setgoal17': case '/setgoal18': case '/setgoal19': case '/setgoal20': { cmd = 1; if (whichApp == 'goals') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { goalnum = parseInt(message[0].substring(8)); if (commandVar1 <= 0 || isNaN(commandVar1)) { cb.sendNotice('The first parameter is the new amount for goal ' + goalnum + ' and has be be a number greater than 0. For example, "/setgoal' + goalnum + ' 300 Shirt Off" to set goal ' + goalnum + ' to Shirt off at 300 tokens.', u, yellow); } else if (goalnum == currentGoal && commandVar1 < currentGoalTips) { cb.sendNotice('The current goal cannot be updated to an amount less than what has already been tipped for this goal. The "/restartgoal" command can be used to clear the current goal tip totals, but this is not recommended.', u, yellow); } else if (!message[2]) { cb.sendNotice('The second parameter is the description of goal ' + goalnum + ' (can be multiple words). For example, "/setgoal' + goalnum + ' 300 Shirt Off" to set goal ' + goalnum + ' to Shirt off at 300 tokens.', u, yellow); } else if (goalnum > 1 && !progGoalArray.amt[goalnum-2] >= 1) { cb.sendNotice('You cannot skip goal levels. If setting a goal for level ' + goalnum + ', then level ' + (goalnum-1) + ' must already have a goal defined.', u, yellow); } else { for (let i = 2; i < message.length; i++) { if (i === 2) { label = message[i]; } else { label += " " + message[i]; } } progGoalArray.amt[goalnum-1] = commandVar1; progGoalArray.desc[goalnum-1] = label; updateGoal(goalnum); cb.sendNotice('Goal ' + goalnum + ' was added/updated to the amount of ' + commandVar1 + ' and a description of "' + label + '".', u, yellow); cb.sendNotice(u + ' added/updated Progressive Goal #' + goalnum + ' to the goal amount of ' + commandVar1 + ' and a description of "' + label + '".', '', yellow, '', '', 'red'); cb.sendNotice(u + ' added/updated Progressive Goal #' + goalnum + ' to the goal amount of ' + commandVar1 + ' and a description of "' + label + '".', cb.room_slug, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/setgoalX" command is for use with the Progressive Goals app, and that feature is not running.', u, yellow); } break; } case '/rmvgoal': { cmd = 1; if (whichApp == 'goals') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { goalnum = message[1]; if (isNaN(goalnum) || goalnum < 1 || goalnum > 20) { cb.sendNotice('The first parameter is the goal level being removed, and musy be a number from 1 to 20 to indicate the goal level number that should be removed. For example, "/rmvgoal 3" will remove the goal and description info for level 3.', u, yellow); } else if (goalnum > progGoalArray.amt.length) { cb.sendNotice('There is no goal entry at level ' + goalnum + ', there are currently only entries up to goal level ' + progGoalArray.amt.length + '.', u, yellow); } else { progGoalArray.amt.splice((goalnum-1),1); progGoalArray.desc.splice((goalnum-1),1); updateGoal(goalnum); cb.sendNotice('Progressive Goal #' + goalnum + ' was removed from the goal list', u, yellow); cb.sendNotice(u + ' removed Progressive Goal #' + goalnum + ' from the goal list', '', yellow, '', '', 'red'); cb.sendNotice(u + ' removed Progressive Goal #' + goalnum + ' from the goal list', cb.room_slug, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/rmvgoal" command is for use with the Progressive Goals app, and that feature is not running.', u, yellow); } break; } case '/setgoaltext': { cmd = 1; if (whichApp == 'goals') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { goalSubjectText = msg['m'].substring(13).trim() if (goalSubjectText != '' && goalSubjectText != null) { changeRoomSubject(); } else { cb.sendNotice('No value was specified for the new room subject suffix.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/setgoaltxt" command is for use with the Progressive Goals app, and that feature is not running.', u, yellow); } break; } //********* Goal Counter Commands case '/setcount1': case '/setcount2': case '/setcount3': case '/setcount4': case '/setcount5': case '/setcount6': case '/setcount7': case '/setcount8': case '/setcount9': case '/setcount10': { cmd = 1; if (whichApp == 'goalcount') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { goalnum = parseInt(message[0].substring(9)); if (commandVar1 <= 0 || isNaN(commandVar1)) { cb.sendNotice('The first parameter is the new goal count for goal level ' + goalnum + ' and has be be a number greater than 0. For example, "/setcount' + goalnum + ' 20 Shirt Off" to set goal level ' + goalnum + ' as Shirt off at 20 goals.', u, yellow); } else if (goalnum <= currentGoal) { cb.sendNotice('You cannot update the attributes of the current goal level or a goal level that has already been passed in the goal sequence.', u, yellow); } else if (!message[2]) { cb.sendNotice('The second parameter is the description of prize at goal level ' + goalnum + ' (can be multiple words). For example, "/setcount' + goalnum + ' 20 Shirt Off" to set goal level ' + goalnum + ' as Shirt off at 20 goals.', u, yellow); } else if (goalnum > 1 && !goalCounterArray.amt[goalnum-2] >= 1) { cb.sendNotice('You cannot skip goal levels. If setting a value for level ' + goalnum + ', then level ' + (goalnum-1) + ' must already have a goal value defined.', u, yellow); } else if (goalnum > 1 && commandVar1 <= goalCounterArray.amt[goalnum-2]) { cb.sendNotice('You cannot update the value for a goal level to be less than or equal to the value for the previous level (level ' + (goalnum-1) + ' has a value of ' + goalCounterArray.amt[goalnum-2] + ', so you must use a value greater than ' + goalCounterArray.amt[goalnum-2] + ' for level ' + goalnum + ').', u, yellow); } else if (goalnum < 10 && commandVar1 >= goalCounterArray.amt[goalnum] && goalCounterArray.amt[goalnum] > 0) { cb.sendNotice('You cannot update the value for a goal level to be greater than or equal to the value for the next level (level ' + (goalnum+1) + ' has a value of ' + goalCounterArray.amt[goalnum] + ', so you must use a value less than ' + goalCounterArray.amt[goalnum] + ' for level ' + goalnum + ').', u, yellow); } else { for (let i = 2; i < message.length; i++) { if (i === 2) { label = message[i]; } else { label += " " + message[i]; } } goalCounterArray.amt[goalnum-1] = commandVar1; goalCounterArray.desc[goalnum-1] = label; updateGoalCount(goalnum); cb.sendNotice('Goal Counter Level ' + goalnum + ' was updated to the goal count of ' + commandVar1 + ' and a description of "' + label + '".', u, yellow); cb.sendNotice(u + ' added/updated Goal Counter Level #' + goalnum + ' to the goal count of ' + commandVar1 + ' and a description of "' + label + '".', '', yellow, '', '', 'red'); cb.sendNotice(u + ' added/updated Goal Counter Level #' + goalnum + ' to the goal count of ' + commandVar1 + ' and a description of "' + label + '".', cb.room_slug, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/setcountX" command is for use with the Tip Goal Counter App, and that feature is not running.', u, yellow); } break; } case '/rmvcount': { cmd = 1; if (whichApp == 'goalcount') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { goalnum = message[1]; if (isNaN(goalnum) || goalnum < 1 || goalnum > 10) { cb.sendNotice('The first parameter is the goal level being removed, and musy be a number from 1 to 10 to indicate the goal level number that should be removed. For example, "/rmvcount 3" will remove the goal and description info for level 3.', u, yellow); } else if (goalnum > goalCounterArray.amt.length) { cb.sendNotice('There is no goal entry at level ' + goalnum + ', there are currently only entries up to goal level ' + goalCounterArray.amt.length + '.', u, yellow); } else { goalCounterArray.amt.splice((goalnum-1),1); goalCounterArray.desc.splice((goalnum-1),1); updateGoalCount(goalnum); cb.sendNotice('Goal Counter Level #' + goalnum + ' was removed from the goal list', u, yellow); cb.sendNotice(u + ' removed Goal Counter Level #' + goalnum + ' from the goal list', '', yellow, '', '', 'red'); cb.sendNotice(u + ' removed Goal Counter Level #' + goalnum + ' from the goal list', cb.room_slug, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/rmvcount" command is for use with the Tip Goal Counter App, and that feature is not running.', u, yellow); } break; } case '/chgcountgoal': { cmd = 1; if (whichApp == 'goalcount') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { if (commandVar1 <= 0 || isNaN(commandVar1)) { cb.sendNotice('The parameter is the new Tip Goal Counter individual goal amount and has be be a number greater than 0. For example, use "/chgcountgoal 99" to set each goal to 99 tokens.', u, yellow); } else if (commandVar1 <= currentGoalTips){ cb.sendNotice('Cannot change the goal amount to be less then or equal to the tip count for the current goal (' + currentGoalTips + ').', u, yellow); } else { changeCountGoal(commandVar1); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/chgcountgoal" command is for use with the Tip Goal Counter App, and that feature is not running.', u, yellow); } break; } case '/setcounttext': { cmd = 1; if (whichApp == 'goalcount') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { counterSubjectText = msg['m'].substring(14).trim() if (counterSubjectText != '' && counterSubjectText != null) { changeRoomSubject(); } else { cb.sendNotice('No value was specified for the new room subject suffix.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/setcounttxt" command is for use with the Tip Goal Counter App, and that feature is not running.', u, yellow); } break; } //********* Tip Sequence Commands case '/setseq1': case '/setseq2': case '/setseq3': case '/setseq4': case '/setseq5': case '/setseq6': case '/setseq7': case '/setseq8': case '/setseq9': case '/setseq10': { cmd = 1; if (whichApp == 'sequence') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { goalnum = parseInt(message[0].substring(7)); if (commandVar1 <= 0 || isNaN(commandVar1)) { cb.sendNotice('The first parameter is the new tip sequence for goal ' + goalnum + ' and has be be a number greater than 0. For example, "/setseq' + goalnum + ' 20 Shirt Off" to set goal ' + goalnum + ' as Shirt off at tip sequence 20.', u, yellow); } else if (goalnum <= currentGoal) { cb.sendNotice('You cannot update the attributes of the current goal or a goal that has already been passed in the tip sequence.', u, yellow); } else if (!message[2]) { cb.sendNotice('The second parameter is the description of tip sequence goal ' + goalnum + ' (can be multiple words). For example, "/setseq' + goalnum + ' 20 Shirt Off" to set goal ' + goalnum + ' as Shirt off at tip sequence 20.', u, yellow); } else if (tipSequenceDirection == 'up' && commandVar1 > endSequence || tipSequenceDirection == 'down' && commandVar1 < endSequence ) { cb.sendNotice('You cannot set a goal level sequence target outside of the configured tip sequence range (ending sequence is at ' + endSequence + ').', u, yellow); } else if (tipSequenceDirection == 'up' && commandVar1 <= nextSequence || tipSequenceDirection == 'down' && commandVar1 >= nextSequence) { cb.sendNotice('You cannot set a goal level sequence target to a sequence value that has already been passed.', u, yellow); } else if (goalnum > 1 && !sequenceArray.amt[goalnum-2] >= 1) { cb.sendNotice('You cannot skip goal levels. If setting a value for level ' + goalnum + ', then level ' + (goalnum-1) + ' must already have a sequence value defined (and be greater than or less than based on sequence direction).', u, yellow); } else if (goalnum > 1 && tipSequenceDirection == 'up' && commandVar1 <= sequenceArray.amt[goalnum-2]) { cb.sendNotice('For ascending sequence, you cannot update the sequence for a goal level to be less than or equal to the sequence for the previous level (level ' + (goalnum-1) + ' has a sequence of ' + sequenceArray.amt[goalnum-2] + ', so you must use a sequence greater than ' + sequenceArray.amt[goalnum-2] + ' for level ' + goalnum + ').', u, yellow); } else if (goalnum < 10 && tipSequenceDirection == 'up' && commandVar1 >= sequenceArray.amt[goalnum] && sequenceArray.amt[goalnum] > 0) { cb.sendNotice('For ascending sequence, you cannot update the sequence for a goal level to be greater than or equal to the sequence for the next level (level ' + (goalnum+1) + ' has a sequence of ' + sequenceArray.amt[goalnum] + ', so you must use a sequence less than ' + sequenceArray.amt[goalnum] + ' for level ' + goalnum + ').', u, yellow); } else if (goalnum > 1 && tipSequenceDirection == 'down' && commandVar1 >= sequenceArray.amt[goalnum-2]) { cb.sendNotice('For descending sequence, you cannot update the sequence for a goal level to be greater than or equal to the sequence for the previous level (level ' + (goalnum-1) + ' has a sequence of ' + sequenceArray.amt[goalnum-2] + ', so you must use a sequence less than ' + sequenceArray.amt[goalnum-2] + ' for level ' + goalnum + ').', u, yellow); } else if (goalnum < 10 && tipSequenceDirection == 'down' && commandVar1 <= sequenceArray.amt[goalnum]) { cb.sendNotice('For descending sequence, you cannot update the sequence for a goal level to be less than or equal to the sequence for the next level (level ' + (goalnum+1) + ' has a sequence of ' + sequenceArray.amt[goalnum] + ', so you must use a sequence greater than ' + sequenceArray.amt[goalnum] + ' for level ' + goalnum + ').', u, yellow); } else { for (let i = 2; i < message.length; i++) { if (i === 2) { label = message[i]; } else { label += " " + message[i]; } } sequenceArray.amt[goalnum-1] = commandVar1; sequenceArray.desc[goalnum-1] = label; updateSequenceGoal(goalnum); cb.sendNotice('Sequence Goal #' + goalnum + ' was added/updated to occur at sequence ' + commandVar1 + ' and with a description of "' + label + '".', u, yellow); cb.sendNotice(u + ' added/updated Sequence Goal #' + goalnum + ' to occur at sequence ' + commandVar1 + ' and with a description of "' + label + '".', '', yellow, '', '', 'red'); cb.sendNotice(u + ' added/updated Sequence Goal #' + goalnum + ' to occur at sequence ' + commandVar1 + ' and with a description of "' + label + '".', cb.room_slug, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/setseqX" command is for use with the Tip Sequence app, and that feature is not running.', u, yellow); } break; } case '/rmvseq': { cmd = 1; if (whichApp == 'sequence') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { goalnum = message[1]; if (isNaN(goalnum) || goalnum < 1 || goalnum > 10) { cb.sendNotice('The first parameter is the sequence goal level being removed, and musy be a number from 1 to 10 to indicate the goal level number that should be removed. For example, "/rmvseq 3" will remove the goal and description info for sequence goal level 3.', u, yellow); } else if (goalnum > sequenceArray.amt.length) { cb.sendNotice('There is no goal entry at level ' + goalnum + ', there are currently only entries up to goal level ' + sequenceArray.amt.length + '.', u, yellow); } else { sequenceArray.amt.splice((goalnum-1),1); sequenceArray.desc.splice((goalnum-1),1); updateSequenceGoal(goalnum); cb.sendNotice('Sequence Goal #' + goalnum + ' was removed from the goal list', u, yellow); cb.sendNotice(u + ' removed Sequence Goal #' + goalnum + ' from the goal list', '', yellow, '', '', 'red'); cb.sendNotice(u + ' removed Sequence Goal #' + goalnum + ' from the goal list', cb.room_slug, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/rmvseq" command is for use with the Tip Sequence app, and that feature is not running.', u, yellow); } break; } case '/usechatmsg': { cmd = 1; if (whichApp == 'sequence') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { if (!message[1] || (message[1] != 'on' && message[1] != 'off')) { cb.sendNotice('The "/usechatmsg" command requires a parameter of either "on" or "off".', u, yellow); } else { setChatMsgToggle(message[1], u, 'updt') } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.\nType "/uahelp commands" to see a full list of the available commands.', u, yellow); } } else { cb.sendNotice('The "/usechatmsg" command is for use with the Tip Sequence app, and that feature is not running.', u, yellow); } break; } case '/chgendseq': { cmd = 1; if (whichApp == 'sequence') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { lastgoalindex = sequenceArray.amt.length - 1; if (commandVar1 <= 0 || isNaN(commandVar1)) { cb.sendNotice('The parameter is the new Tip Sequence end goal and has be be a number greater than 0. For example, use "/chgendseq 50" to set the new sequence end goal to 50.', u, yellow); } else if (tipSequenceDirection == 'up' && commandVar1 <= sequenceArray.amt[lastgoalindex]){ cb.sendNotice('For ascending sequence, cannot change the end sequence to be less then or equal to the sequence for the last tip sequence goal (' + sequenceArray.amt[lastgoalindex] + ').', u, yellow); } else if (tipSequenceDirection == 'down' && commandVar1 >= sequenceArray.amt[lastgoalindex]){ cb.sendNotice('For descending sequence, cannot change the end sequence to be greater then or equal to the sequence for the last tip sequence goal (' + sequenceArray.amt[lastgoalindex] + ').', u, yellow); } else { changeEndSequence(commandVar1); cb.sendNotice('The ending sequence number has been updated to ' + commandVar1 + '.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.\nType "/uahelp commands" to see a full list of the available commands.',u,yellow); } } else { cb.sendNotice('The "/chgendseq" command is for use with the Tip Sequence App, and that feature is not running.', u, yellow); } break; } case '/setseqtext': { cmd = 1; if (whichApp == 'sequence') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { sequenceSubjectText = msg['m'].substring(12).trim() if (sequenceSubjectText != '' && sequenceSubjectText != null) { changeRoomSubject(); } else { cb.sendNotice('No value was specified for the new room subject suffix.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/setseqtext" command is for use with the Tip Sequence App, and that feature is not running.', u, yellow); } break; } case '/usegrouptips': { cmd = 1; if (whichApp == 'sequence') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { if (!message[1] || (message[1] != 'on' && message[1] != 'off')) { cb.sendNotice('The "/usegrouptips" command requires a parameter of either "on" or "off".', u, yellow); } else { setGroupTipToggle(message[1], u, 'updt') } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.\nType "/uahelp commands" to see a full list of the available commands.', u, yellow); } } else { cb.sendNotice('The "/usegrouptips" command is for use with the Tip Sequence app, and that feature is not running.', u, yellow); } break; } //********* Tip Jar Commands case '/slower': { cmd = 1; if (whichApp == 'tipjar') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { ratechange = parseInt(message[1]); if(isNaN(ratechange)) { if (drainLevel >= 10) { cb.sendNotice('The Tip Jar Drain rate is already at the slowest setting.', u, yellow); } else { setDrainRate(drainLevel+1); cb.sendNotice('You have slowed the Drain Rate by 1 level.', u, yellow); } } else { if ((drainLevel+ratechange) > 10) { cb.sendNotice('Slowing the Drain Rate by ' + ratechange + ' levels would exceed the slowest setting, defaulting to the slowest setting of 1 token every 10 seconds', u, yellow); setDrainRate(10); } else { setDrainRate(drainLevel+ratechange); cb.sendNotice('You have slowed the Drain Rate by ' + ratechange + ' levels.', u, yellow); } } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/slower" command is for use with the Tip Jar, and the Tip Jar feature is not running.', u, yellow); } break; } case '/faster': { cmd = 1; if (whichApp === 'tipjar') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { ratechange = parseInt(message[1]); if(isNaN(ratechange)) { if (drainLevel <= 1) { cb.sendNotice('The Tip Jar Drain rate is already at the fastest setting.', u, yellow); } else { setDrainRate(drainLevel-1); cb.sendNotice('You have increased the Drain Rate by 1 level.', u, yellow); } } else { if ((drainLevel-ratechange) < 1) { cb.sendNotice('Speeding up the Drain Rate by ' + ratechange + ' levels would exceed the fastest setting, defaulting to the fastest setting of 5 tokens per second.', u, yellow); setDrainRate(1); } else { setDrainRate(drainLevel-ratechange); cb.sendNotice('You have increased the Drain Rate by ' + ratechange + ' levels.', u, yellow); } } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/faster" command is for use with the Tip Jar, and the Tip Jar feature is not running.', u, yellow); } break; } case '/setjar1': case '/setjar2': case '/setjar3': case '/setjar4': case '/setjar5': case '/setjar6': case '/setjar7': case '/setjar8': case '/setjar9': case '/setjar10': { cmd = 1; if (whichApp == 'tipjar') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { goalnum = parseInt(message[0].substring(7)); if (isNaN(commandVar1) || commandVar1 < 1 || commandVar1 > 100000) { cb.sendNotice('The first parameter is the amount for tip jar goal #' + goalnum + ' and has be be a number from 1 to 100,000. For example, "/setjar' + goalnum + ' 300 0 Shirt Off" to set goal ' + goalnum + ' to be for "Shirt off" at 300 tokens, and only do the goal once with no recycle.', u, yellow); } else if (isNaN(commandVar2) || commandVar2 < 0 || commandVar2 > 99) { cb.sendNotice('The second parameter is the recycle count for tip jar goal #' + goalnum + ' and has be be a number from 0 to 99. Note since it is a REcycle count, the value should be "0" to use a goal once (default), "1" to use a goal twice, etc. For example, "/setjar' + goalnum + ' 300 0 Shirt Off" to set goal #' + goalnum + ' to be for "Shirt off" at 300 tokens, and only do the goal once with no recycle.', u, yellow); } else if (goalnum == currentGoal && commandVar1 < currentGoalTips) { cb.sendNotice('The current tip jar goal cannot be updated to an amount less than what has already been tipped for this goal. The "/restartgoal" command can be used to clear the current goal tip jar goal totals, but this is not recommended.', u, yellow); } else if (!message[3]) { cb.sendNotice('The third parameter is the description of tip jar goal ' + goalnum + ' (can be multiple words). For example, "/setjar' + goalnum + ' 300 0 give a massage" to set goal ' + goalnum + ' to give a massage at 300 tokens, and only do the goal once with no recycle.', u, yellow); } else if (goalnum > 1 && !tipjarGoalArray.amt[goalnum-2] >= 1) { cb.sendNotice('You cannot skip goal levels. If setting a value for level ' + goalnum + ', then level ' + (goalnum-1) + ' must already have a goal value defined.', u, yellow); } else { for (let i = 3; i < message.length; i++) { if (i === 3) { label = message[i]; } else { label += " " + message[i]; } } tipjarGoalArray.amt[goalnum-1] = commandVar1; tipjarGoalArray.recyc[goalnum-1] = commandVar2; tipjarGoalArray.desc[goalnum-1] = label; updateTipjarGoal(goalnum); cb.sendNotice('Tip Jar Goal #' + goalnum + ' was updated to the amount of ' + commandVar1 + ', a description of "' + label + '", and a recycle count of ' + commandVar2 + '.', u, yellow); cb.sendNotice(u + ' added/updated Tip Jar Goal #' + goalnum + ' to the goal count of ' + commandVar1 + ', a description of "' + label + '", and a recycle count of ' + commandVar2 + '.', '', yellow, '', '', 'red'); cb.sendNotice(u + ' added/updated Tip Jar Goal #' + goalnum + ' to the goal count of ' + commandVar1 + ', a description of "' + label + '", and a recycle count of ' + commandVar2 + '.', cb.room_slug, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/setjarX" command is for use with the Tip Jar app, and that feature is not running.', u, yellow); } break; } case '/rmvjar': { cmd = 1; if (whichApp == 'tipjar') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { goalnum = message[1]; if (isNaN(goalnum) || goalnum < 1 || goalnum > 10) { cb.sendNotice('The first parameter is the Tip Jar goal level being removed, and musy be a number from 1 to 10 to indicate the goal level number that should be removed. For example, "/rmvjar 3" will remove the goal and description info for sequence goal level 3.', u, yellow); } else if (goalnum > tipjarGoalArray.amt.length) { cb.sendNotice('There is no goal entry at level ' + goalnum + ', there are currently only entries up to goal level ' + tipjarGoalArray.amt.length + '.', u, yellow); } else { tipjarGoalArray.amt.splice((goalnum-1),1); tipjarGoalArray.desc.splice((goalnum-1),1); tipjarGoalArray.recyc.splice((goalnum-1),1); updateTipjarGoal(goalnum); cb.sendNotice('Tip Jar Goal Level #' + goalnum + ' was removed from the goal list', u, yellow); cb.sendNotice(u + ' removed Tip Jar Goal Level #' + goalnum + ' from the goal list', '', yellow, '', '', 'red'); cb.sendNotice(u + ' removed Tip Jar Goal Level #' + goalnum + ' from the goal list', cb.room_slug, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/rmvjar" command is for use with the Tip Jar app, and that feature is not running.', u, yellow); } break; } case '/setjartext': { cmd = 1; if (whichApp == 'tipjar') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { tipjarSubjectText = msg['m'].substring(12).trim() if (tipjarSubjectText != '' && tipjarSubjectText != null) { changeRoomSubject(); } else { cb.sendNotice('No value was specified for the new room subject suffix.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/setjartxt" command is for use with the Tip Jar app, and that feature is not running.', u, yellow); } break; } //********* Goal Race Commands case '/restartrace': { cmd = 1; if (whichApp == 'goalrace') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { restartRace(); cb.sendNotice('You have restarted the goal race with the same goal choices. You can change the goal choices with the command "/setrace1" or "/setrace2".', u, yellow); } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/restartrace" command is for use with the Goal Race app, which is not running.', u, yellow); } break; } case '/setrace1': case '/setrace2': { cmd = 1; if (whichApp == 'goalrace') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { racenum = parseInt(message[0].substring(8)); if (commandVar1 <= 0 || isNaN(commandVar1)) { cb.sendNotice('The first parameter is the new amount for goal ' + racenum + ' and has be be a number greater than 0. For example, "/setrace' + racenum + ' 300 Shirt Off" to set goal ' + racenum + ' to Shirt off at 300 tokens.', u, yellow); } else if (racenum == 1 && commandVar1 < currentGoalRace1Tips) { cb.sendNotice('The goal 1 amount cannot be updated to a value less than what has already been tipped for this goal. The "/restartrace" command can be used to clear the current race totals, but this is not recommended.', u, yellow); } else if (racenum == 2 && commandVar1 < currentGoalRace2Tips) { cb.sendNotice('The goal 2 amount cannot be updated to a value less than what has already been tipped for this goal. The "/restartrace" command can be used to clear the current race totals, but this is not recommended.', u, yellow); } else if (!message[2]) { cb.sendNotice('The second parameter is the description of goal ' + racenum + ' (can be multiple words). For example, "/setrace' + racenum + ' 300 Shirt Off" to set goal ' + racenum + ' to Shirt off at 300 tokens.', u, yellow); } else { for (let i = 2; i < message.length; i++) { if (i === 2) { label = message[i]; } else { label += " " + message[i]; } } if (racenum == 1) { goalraceAmount1 = commandVar1; goalraceDesc1 = label; } else if (racenum == 2) { goalraceAmount2 = commandVar1; goalraceDesc2 = label; } changeRoomSubject(); cb.drawPanel(); cb.sendNotice('Goal ' + racenum + ' was added/updated to the amount of ' + commandVar1 + ' and a description of "' + label + '".', u, yellow); cb.sendNotice(u + ' added/updated Goal #' + racenum + ' to the goal amount of ' + commandVar1 + ' and a description of "' + label + '".', '', yellow, '', '', 'red'); cb.sendNotice(u + ' added/updated Goal #' + racenum + ' to the goal amount of ' + commandVar1 + ' and a description of "' + label + '".', cb.room_slug, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/setraceX" command is for use with the Goal Race app, and that feature is not running.', u, yellow); } break; } case '/addrace1': case '/addrace2': { cmd = 1; if (whichApp == 'goalrace') { racenum = parseInt(message[0].substring(8)); if (!cbjs.arrayContains(raceUnclaimedVotes.name, u)) { cb.sendNotice('Sorry, you do not have any unclaimed tips.', u, yellow); } else { index = raceUnclaimedVotes.name.indexOf(u); unclaimedTip = raceUnclaimedVotes.amount[index]; if (unclaimedTip > 0) { if (racenum == 1) { tipNote = goalraceDesc1; } else if (racenum == 2) { tipNote = goalraceDesc2; } else { tipNote = ''; } recordTip(unclaimedTip,'bc',tipNote,isFan,isExtFan,isVIP); raceUnclaimedVotes.amount[index] = 0; cb.sendNotice(u + ' has claimed previous tips and voted ' + unclaimedTip + ' tokens to goal #' + racenum + '.', '', goalraceBgColor, goalraceTxtColor, 'bold'); cb.drawPanel(); } else { cb.sendNotice('Sorry, you do not have any unclaimed tips.', u, yellow); } } } else { cb.sendNotice('The "/addrace" commands are for use with the Goal Race app, and that feature is not running.', u, yellow); } break; } case '/tipnoteon': { cmd = 1; if (whichApp == 'goalrace') { if (cbjs.arrayContains(raceTipNotesOn, u)) { cb.sendNotice('Sorry, you have already enabled Tip Notes for yourself during the Goal Race.', u, yellow); } else { raceTipNotesOn.push(u); cb.sendNotice('You have enabled Tip Notes for yourself during the Goal Race, you will now be able to enter tip notes rather than vote for the goals. You can switch back to voting for goals using the command /tipnoteoff.', u, yellow); } } else { cb.sendNotice('The "/tipnote" commands are for use with the Goal Race app, and that feature is not running.', u, yellow); } break; } case '/tipnoteoff': { cmd = 1; if (whichApp == 'goalrace') { if (cbjs.arrayContains(raceTipNotesOn, u)) { raceTipNotesOn.pop(u); cb.sendNotice('You have disabled Tip Notes for yourself during the Goal Race, you will now be able to vote for the goals. You can switch back to entering tip notes using the command /tipnoteon.', u, yellow); } else { cb.sendNotice('Sorry, you have already disabled (or never enabled) Tip Notes for yourself during the Goal Race.', u, yellow); } } else { cb.sendNotice('The "/tipnote" commands are for use with the Goal Race app, and that feature is not running.', u, yellow); } break; } case '/setracetext': { cmd = 1; if (whichApp == 'goalrace') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { goalraceSubjectText = msg['m'].substring(13).trim() if (goalraceSubjectText != '' && goalraceSubjectText != null) { changeRoomSubject(); } else { cb.sendNotice('No value was specified for the new room subject suffix.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/setracetxt" command is for use with the Goal Race app, and that feature is not running.', u, yellow); } break; } case '/setracepaneltext': { cmd = 1; if (whichApp == 'goalrace') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { goalracePanelText = msg['m'].substring(18).trim() if (goalracePanelText != '' && goalracePanelText != null) { cb.drawPanel(); } else { cb.sendNotice('No value was specified for the new panel text.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The "/setracepaneltext" command is for use with the Goal Race app, and that feature is not running.', u, yellow); } break; } //********* Ultra App Hidden Ticket Show Commands case '/tickets': { cmd = 1; if (whichApp == 'ticket' || presalesToggle == 1) { var ticketlist = cb.limitCam_allUsersWithAccess(); if (ticketlist.length > 0) { if (message[1] === 'a' || message[1] === 'A' || message[1] === 'alpha') { var sortedticketlist = cb.limitCam_allUsersWithAccess(); sortedticketlist.sort(); cb.sendNotice('Alphabetic listing of users currently with access to the Hidden Ticket Show : \n' + cbjs.arrayJoin(sortedticketlist, ', ') + '\nEnd of List', u, yellow); } else { cb.sendNotice('Users currently with access to the Hidden Ticket Show, in order of when added (use the "alpha" qualifier for a sorted list): \n' + cbjs.arrayJoin(ticketlist, ', ') + '\nEnd of List', u, yellow); } } else { cb.sendNotice('No ticket buyers yet.', u, yellow); } } break; } case '/backup': { cmd = 1; if (whichApp == 'ticket' || presalesToggle == 1) { if (cb.limitCam_allUsersWithAccess().length > 0) { cb.sendNotice('Copy and paste the below command into the chat to backup the current presale or ticket show list to the Fembot:\n/backupfb ' + cbjs.arrayJoin(cb.limitCam_allUsersWithAccess(), ', ')); } else { cb.sendNotice('No ticket buyers yet.', u, yellow); } } else { cb.sendNotice('The UltraApp is not selling tickets for a show.', u, yellow); } break; } case '/prepticket': { cmd = 1; if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { if (message[1]) { numtimer = parseInt(message[1]) if (isNaN(numtimer)) { cb.sendNotice('The value entered for the show countdown timer is not numeric, please try again.', u, yellow); break; } else if (numtimer < 1 || numtimer > 120) { cb.sendNotice('The value entered for the show countdown timer is outside allowable values from 1 to 120 min, please try again.', u, yellow); } else { prepTicketShow(u,numtimer); cb.sendNotice('UltraApp Ticket show prep has been completed.', u, yellow); } } else { prepTicketShow(u,0); cb.sendNotice('UltraApp Ticket show prep has been completed.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } break; } case '/useot': { cmd = 1; if (whichApp == 'ticket') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { if (cb.settings.ticketShowFanAppreciation != 'Yes') { if(message[1] != null && message[1] != 'on' && message[1] != 'off') { cb.sendNotice('The value ' + message[1] + ' is not a valid option for /useot, please try again.', u, yellow); } else if(message[1] == null) { cb.sendNotice('You did not enter a valid option for /useot, please try again.', u, yellow); } else { setTicketShowOtToggle(message[1], u); } } else { cb.sendNotice('Outstanding Tickets are not enabled for a Fan Appreciation show.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case "/otlist": { cmd = 1; cb.sendNotice('Users currently on the Outstanding Ticket List: ' + outstandingTicketArray.length, u, yellow); cb.sendNotice((outstandingTicketArray.length > 0 == true ? cbjs.arrayJoin(outstandingTicketArray, ',') : 'No outstanding ticket holders.'), u); cb.sendNotice('End of List', u, yellow); break; } case "/otchanges": { cmd = 1; if (isMod || isBC) { if (otChangesArray.name.length > 0) { outString = ''; for (var i = 0; i < otChangesArray.name.length; i++) { if (otChangesArray.name[i] == null) { break } else { outString += (i > 0 ? ',' : '') + otChangesArray.name[i] + '(' + otChangesArray.type[i] + ')'; } } cb.sendNotice('Listing of Changes to the Outstanding Ticket List : \n' + outString + '\nEnd of List', u, yellow); } else { cb.sendNotice('No entries have been added to the Outstanding Ticket Change list.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } break; } case '/ctprice': case '/chgticketprice': case '/ticketprice': { cmd = 1; if (isBC || (isMod && cb.settings.ticketShowModsChgPrice === 'Yes')) { numprice = parseInt(message[1]) if (isNaN(numprice)) { cb.sendNotice('The value entered for the ticket price is not numeric, please try again.', u, yellow); break; } else if (numprice < 1 || numprice > 1000) { cb.sendNotice('The value entered for the ticket price is outside allowable values from 1 to 1000, please try again.', u, yellow); } else { ticketannounce = 'no'; if (whichApp == 'ticket') { ticketannounce = 'yes'; } setTicketPrice(numprice,u,ticketannounce); cb.sendNotice('The UltraApp Ticket Show price has been updated to ' + numprice + ' tokens. You can view the ticket list with the command "/tickets".', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } break; } case '/starttickettimer': case '/ticketstarttimer': case '/starttimer': { cmd = 1; if (whichApp == 'ticket') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { numtimer = parseInt(message[1]) if(isNaN(numtimer)) { cb.sendNotice('The value entered for the minutes to use for the timer is not numeric, please try again.', u, yellow); break; } else if(numtimer < 1 || numtimer > 60) { cb.sendNotice('The value entered for the minutes to use for the ticket show timer is outside allowable values from 1 to 60 minutes, please try again.', u, yellow); } else { startTicketShowTimer(numtimer); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case '/addtickettime': case '/ticketaddtime': case '/addtime': { cmd = 1; if (whichApp == 'ticket') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { numtimer = parseInt(message[1]); if(isNaN(numtimer)) { cb.sendNotice('The value entered for the minutes to add to the ticket show start timer is not numeric, please try again.', u, yellow); } else if(numtimer < -60 || numtimer > 60) { cb.sendNotice('The value entered for the minutes to add to the ticket show start timer is outside allowable values from -60 to 60, please try again. Negative numbers can be used to subtract time.', u, yellow); } else if(numtimer == 0) { cb.sendNotice('Cannot add zero time.', u, yellow); } else if(numtimer < 0 && Math.abs(numtimer) > ticketMinsRemain) { cb.sendNotice('The value entered for the minutes to subtract is greater than the remaining time on the timer, please try again. Negative numbers can be used to subtract time.', u, yellow); } else if(numtimer > 0 && (Math.abs(numtimer) + ticketMinsRemain) > 60) { cb.sendNotice('The value entered for the minutes to add would exceed the maximum countdown time of 60 minutes when added to the current time left.', u, yellow); } else { ticketAddTime(numtimer, u); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case '/ticketstoptime': case '/ticketstoptimer': case '/stoptimer': { cmd = 1; if (whichApp == 'ticket') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { if (ticketMinsRemain > 0 || ticketSecsRemain > 0) { stopTicketTimer(u); } else { cb.sendNotice('A ticket timer is not currently running.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case '/add': case '/addticket': { cmd = 1; if (whichApp == 'ticket') { if (isBC || (isMod && cb.settings.ticketShowModsAdd === 'Yes')) { if (cmdval != null) { var cmdvalsplit = cmdval.split(listRegExp); if (cmdvalsplit.length > 1) { cb.sendNotice("Adding multiple users to the ticket show list.", u, yellow); for (var i = 0; i < cmdvalsplit.length; i++) { if (cmdvalsplit[i] != "") { if (!cb.limitCam_userHasAccess(cmdvalsplit[i])) { addRmvTicket("add", cmdvalsplit[i]); cb.sendNotice("Added " + cmdvalsplit[i] + " to the ticket show list.", u); cb.sendNotice(u + " has added you to the ticket show list.", cmdvalsplit[i], yellow); } else { cb.sendNotice(cmdvalsplit[i] + " is already on the ticket show list. Skipping.", u); } } } cb.sendNotice("All users were added and notified.", u, yellow) cb.sendNotice(u + " has added multiple users to the ticket show list.\n" + "Users added: " + cbjs.arrayJoin(cmdvalsplit, ", "), "", yellow, "", "normal", "red"); } else { if (!cb.limitCam_userHasAccess(message[1])) { addRmvTicket("add", message[1]); } else { cb.sendNotice('Note: User ' + message[1] + ' is already in the Ticket Show List.', u, yellow); } } } else { if (!cb.limitCam_userHasAccess(u)) { addRmvTicket("add", u); } else { cb.sendNotice('Note: User ' + u + ' is already in the Ticket Show List.', u, yellow); } } } else { cb.sendNotice('You do not have authority to use the /addticket command.', u, yellow); } } else { cb.sendNotice('The ticket show feature is not yet active in the UltraApp.', u, yellow); } break; } case '/del': case '/delticket': case '/rmvticket': { cmd = 1; if (whichApp == 'ticket') { if (isBC || (isMod && cb.settings.ticketShowModsAdd === 'Yes')) { if(message[1] != '' && message[1] != null) { if (cb.limitCam_userHasAccess(message[1])) { addRmvTicket("rmv", message[1]); } else { cb.sendNotice('Note: User is not in the Ticket Show List.', u, yellow); } } else { cb.sendNotice('No user was specified for the /rmvticket command.', u, yellow); } } else { cb.sendNotice('You do not have authority to use the /rmvticket command.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case '/giftticket': { cmd = 1; if (whichApp == 'ticket') { if (cb.settings.ticketShowFanAppreciation != 'Yes') { if (cb.settings.ticketShowAllowGift == 'Yes') { if (cbjs.arrayContains(ticketShowExtraTickets.name,u)) { index = ticketShowExtraTickets.name.indexOf(u); if (ticketShowExtraTickets.count[index] > 0) { giftTicket(u,message[1]); ticketShowExtraTickets.count[index]--; cb.sendNotice('You have gifted a ticket to ' + message[1] + '. Thank you for your generosity.', u, yellow, '', 'bold'); cb.sendNotice('Moderators: ' + u + ' has gifted a ticket to ' + message[1] + '.', '', yellow, '', '', 'red'); cb.sendNotice('Broadcaster: ' + u + ' has gifted a ticket to ' + message[1] + '.', BC, yellow); } else { cb.sendNotice('Sorry, you do not have any extra tickets remaining.', u, yellow); } } else { cb.sendNotice('Sorry, you have not purchased any extra tickets.', u, yellow); } } else { cb.sendNotice('Sorry, the broadcaster has not enabled the use of gifting tickets for this show.', u, yellow); } } else { cb.sendNotice('Tickets cannot be gifted for a Fan Appreciation show.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case '/givemyticketto': { cmd = 1; if (whichApp == 'ticket') { if (cb.settings.ticketShowFanAppreciation != 'Yes') { if (cb.settings.ticketShowAllowGift == 'Yes') { if (cb.limitCam_userHasAccess(u) && showStage === 'ticketsales') { giveAwayTicket(u,message[1]); cb.sendNotice('You have gifted your ticket to ' + message[1] + '. You will now be removed from the show. Thank you for your generosity.',u,yellow,'','bold'); cb.sendNotice('Mods: ' + u + ' has gifted their own ticket to ' + message[1] + '.', '', yellow, '', '', 'red'); cb.sendNotice('Broadcaster: ' + u + ' has gifted their own ticket to ' + message[1] + '.', BC, yellow); } else { cb.sendNotice('Sorry, you do not have a ticket purchased or the show has already started.', u, yellow); } } else { cb.sendNotice('Sorry, the broadcaster has not enabled the gifting of tickets for this show.', u, yellow); } } else { cb.sendNotice('Tickets cannot be gifted for a Fan Appreciation show.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case '/chgtktmode': case '/chgticketmode': { cmd = 1; if (whichApp == 'ticket') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { newTicketMode = message[1].toLowerCase(); if (newTicketMode != 'manual' && newTicketMode != 'timer' && newTicketMode != 'ticketgoal' && newTicketMode != 'tokengoal') { cb.sendNotice('The value entered for the new mode is not valid, please try again using a value of "manual", "timer", "ticketgoal", or "tokengoal".', u, yellow); } else { if (newTicketMode == ticketStartMode) { cb.sendNotice('The value entered for the new mode is the same as the existing mode, command ignored.', u, yellow); } else { setTicketMode(newTicketMode,u); cb.sendNotice(ticketModeMessage, '', ticketBgColor, ticketTxtColor, 'bold'); cb.drawPanel(); } } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case '/chgtktauto': case '/chgticketauto': { cmd = 1; if (whichApp == 'ticket') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { newTicketAuto = message[1].toLowerCase(); if (newTicketAuto != 'bc' && newTicketAuto != 'auto') { cb.sendNotice('The value entered for the new mode is not valid, please try again using a value of "bc" or "auto".', u, yellow); } else if (newTicketAuto === 'auto' && ticketStartMode === 'manual') { cb.sendNotice('The mode cannot be changed to "auto" unless there is a goal or timer being used to define when the show will start. Show is currently to be started at broadcaster discretion.', u, yellow); } else if (newTicketAuto === ticketModeAuto) { cb.sendNotice('The value entered for the new mode is the same as the existing mode, command ignored.', u, yellow); } else { setTicketAuto(newTicketAuto); cb.sendNotice(ticketModeMessage, '', ticketBgColor, ticketTxtColor, 'bold'); cb.drawPanel(); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case '/tickettimeleft': { cmd = 1; if (whichApp == 'ticket') { if (ticketMinsRemain >= 1 || ticketSecsRemain >= 1) { cb.sendNotice(ticketTimeLeft(), "", yellow, "", "bold"); } else { cb.sendNotice('A Hidden Ticket Show timer is not running.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case '/showtime': { cmd = 1; if (whichApp == 'ticket') { if (cb.limitCam_isRunning()) { getShowTime(u); } else { cb.sendNotice('The ticket show is not running, it has not yet started or already finished.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case '/useticket': { cmd = 1; if (whichApp == 'ticket') { if (cb.settings.ticketShowFanAppreciation != 'Yes') { if (cb.settings.ticketShowEnableOT == 'Yes') { if (!cb.limitCam_userHasAccess(u) && cbjs.arrayContains(outstandingTicketArray,u)) { useTicket(u); cb.sendNotice('Your outstanding ticket has been redeemed and you have been added to the ticket holders list for this show.', u, yellow, '', 'bold'); cb.sendNotice('Broadcaster: ' + u + ' has used their Outstanding Ticket to join this show. They should be removed from the permanent OT list on the bot start page.', BC, yellow); cb.sendNotice('Mods: ' + u + ' has used their Outstanding Ticket. The broadcaster has been notified to remove them from the permanent OT list on the bot start page.', '', yellow, '', '', 'red'); } else { cb.sendNotice('Sorry, you have no outstanding ticket available.', u, yellow); } } else { cb.sendNotice('Sorry, the broadcaster has not enabled the use of outstanding tickets for this show.', u, yellow); } } else { cb.sendNotice('Outstanding Ticket features are not enabled for a Fan Appreciation Show.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case '/saveticket': { cmd = 1; if (whichApp == 'ticket') { if (cb.settings.ticketShowFanAppreciation != 'Yes') { if (cb.settings.ticketShowEnableOT == 'Yes') { if (cb.limitCam_userHasAccess(u) && showStage === 'ticketsales') { saveTicket(u); cb.sendNotice('Your ticket from this show has been saved and you have been removed from the ticket list for this show.', u, yellow, '', 'bold'); cb.sendNotice('Broadcaster: ' + u + ' has saved their ticket from this show to the outstanding ticket list. They should be added to the permanent OT list on the bot start page upon next restart.', BC, yellow); cb.sendNotice('Mods: ' + u + ' has saved their ticket from this show to the outstanding ticket list. The broadcaster has been notified to add them to the permanent OT list on the bot start page.', '', yellow, '', '', 'red'); } else { cb.sendNotice('Sorry, you have no ticket purchase to save, or the show has already started.', u, yellow); } } else { cb.sendNotice('Sorry, the broadcaster has not enabled the ability to save an outstanding tickets for this show.', u, yellow); } } else { cb.sendNotice('Outstanding Ticket features are not enabled for a Fan Appreciation Show.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case '/addot': { cmd = 1; if (whichApp == 'ticket') { if (cb.settings.ticketShowEnableOT == 'Yes') { if (isBC || (isMod && cb.settings.ticketShowModsAdd === 'Yes')) { if(message[1] != '' && message[1] != null) { if (!cbjs.arrayContains(outstandingTicketArray,message[1])) { addRmvOutstandingTicket("add", message[1]); cb.sendNotice('User ' + message[1] + ' has been added to the Outstanding Ticket List.', u, yellow); } else { cb.sendNotice('Cannot add, user is already in the Outstanding Ticket List.', u, yellow); } } } } else { cb.sendNotice('Sorry, the broadcaster has not enabled the outstanding ticket show feature.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case '/rmvot': { cmd = 1; if (whichApp == 'ticket') { if (isBC || (isMod && cb.settings.ticketShowModsAdd === 'Yes')) { if(message[1] != '' && message[1] != null) { if (cbjs.arrayContains(outstandingTicketArray,message[1])) { addRmvOutstandingTicket("rmv", message[1]); cb.sendNotice('User ' + message[1] + ' has been removed from the Outstanding Ticket List.', u, yellow); } else { cb.sendNotice('Cannot remove, user is not in the Outstanding Ticket List.', u, yellow); } } } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case '/startshow': { cmd = 1; if (whichApp == 'ticket') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { if (!cb.limitCam_isRunning() && ticketShowEnded === false) { startTicketShow(u); if (presalesToggle == 1) { setPresalesToggle('off',u); } } else if (!cb.limitCam_isRunning() && ticketShowEnded === true) { cb.sendNotice('The Hidden Cam show was already started and stopped, please use the command "/restartshow" to resume the hidden show.', u, yellow); } else { cb.sendNotice('The Hidden Cam show is already underway.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case '/showover': case '/showwarn': { cmd = 1; if (whichApp == 'ticket') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { if (cb.limitCam_isRunning()) { if (showStage === 'ticketshow') { warnShowEnding(u); } else { cb.sendNotice('The /showwarn or /showover command is used for a first warning. The show has already progressed past the point this command should be used. To end the show and return to a public broadcast, use the /stopshow command.', u, yellow); } } else { cb.sendNotice('The Hidden Cam show has not been started or has already ended.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case '/showend': case '/stopsales': { cmd = 1; if (whichApp == 'ticket') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { if (cb.limitCam_isRunning()) { if (cb.settings.endPosMenuWithShow == 'Yes' && posTipMenuToggle == 1) { setPosTipMenuToggle('off', u); } stopTicketSales(u); } else { cb.sendNotice('The Hidden Cam show has not been started or has already ended.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case '/stopshow': { cmd = 1; if (whichApp == 'ticket') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { if (cb.limitCam_isRunning()) { if (cb.settings.endPosMenuWithShow == 'Yes' && posTipMenuToggle == 1) { setPosTipMenuToggle('off', u); } stopTicketShow(u); } else { cb.sendNotice('The Hidden Cam show has not been started or has already ended.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case '/ticketsubject': { cmd = 1; if (whichApp == 'ticket') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { ticketSubjectText = msg['m'].substring(15).trim(); if (ticketSubjectText != '' && ticketSubjectText != null) { changeRoomSubject(); } else { cb.sendNotice('No value was specified for the new room subject suffix.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case '/ctsubject': { cmd = 1; if (whichApp == 'ticket') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { ticketSubjectText = msg['m'].substring(11).trim(); if (ticketSubjectText != '' && ticketSubjectText != null) { changeRoomSubject(); } else { cb.sendNotice('No value was specified for the new room subject suffix.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case '/newticketshow': { cmd = 1; if (whichApp == 'ticket') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { if (!cb.limitCam_isRunning()) { ticketlist = cb.limitCam_allUsersWithAccess(); if (ticketlist.length > 0) { cb.sendNotice('In case the ticket show was reset by mistake, the previous ticket list is displayed below so they can be added back to the show using the /addticket command : ', cb.room_slug, yellow); cb.sendNotice(cbjs.arrayJoin(ticketlist, ', '), cb.room_slug); cb.sendNotice('End of List', cb.room_slug, yellow); if (isMod) { cb.sendNotice('In case the ticket show was reset by mistake, the previous ticket list is displayed below so they can be added back to the show using the /addticket command : ', u, yellow); cb.sendNotice(cbjs.arrayJoin(ticketlist, ', '), u); cb.sendNotice('End of List', u, yellow); } } else { cb.sendNotice('No ticket buyers in the previous ticket list.', u, yellow); } initTicketShow(u,ticketPrice); cb.limitCam_removeAllUsers(); while (ticketShowViewerList.length > 0) viewerList.pop(); } else { cb.sendNotice('A Hidden Cam show is still running. The /newticketshow command can be used to start a brand new ticket show. The ticket list is cleared but the outstanding ticket list and changes list remains intact.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case '/restartshow': { cmd = 1; if (whichApp == 'ticket') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { if (!cb.limitCam_isRunning()) { restartTicketShow(u); } else { cb.sendNotice('A Hidden Cam show is already running. The /restartshow command can be used to resume a hidden show that was ended prematurely or accidentally. All settings remain the same and the ticket list stays intact.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case '/restartsales': { cmd = 1; if (whichApp == 'ticket') { if (isBC || (isMod && cb.settings.allowModsAuthority == 'Yes')) { if (ticketSalesEnded == true) { restartTicketSales(u); } else { cb.sendNotice('Ticket Sales are already enabled.', u, yellow); } } else { cb.sendNotice('Only broadcasters and moderators with sufficient authority are able to use that command.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case '/ctn': { cmd = 1; if (whichApp == 'ticket') { if (isMod || isBC) { if (ntc != null && (ntc != "" || ntc != " " || ntc != "\u00a0")) { sendPublicNotice(ntc, u, ""); } else { cb.sendNotice('You cannot send a blank message. The correct syntax for this command is "/ctn [message]".', u, yellow); } } else { cb.sendNotice('Only moderators and broadcasters are able to use that command.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case "/ctnh": { cmd = 1; if (whichApp == 'ticket') { if (isMod || isBC) { if (ntc != null && (ntc != "" || ntc != " " || ntc != "\u00a0")) { sendPublicNotice(ntc, u, "h"); } else { cb.sendNotice('You cannot send a blank message. The correct syntax for this command is "/ctnh [message]".', u, yellow); } } else { cb.sendNotice('Only moderators and broadcasters are able to use that command.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case "/ctnd": { cmd = 1; if (whichApp == 'ticket') { if (isMod || isBC) { if (ntc != null && (ntc != "" || ntc != " " || ntc != "\u00a0")) { sendPublicNotice(ntc, u, "div"); } else { cb.sendNotice('You cannot send a blank message. The correct syntax for this command is "/ctnd [message]".', u, yellow); } } else { cb.sendNotice('Only moderators and broadcasters are able to use that command.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } case "/ctndh": { cmd = 1; if (whichApp == 'ticket') { if (isMod || isBC) { if (ntc != null && (ntc != "" || ntc != " " || ntc != "\u00a0")) { sendPublicNotice(ntc, u, "divh"); } else { cb.sendNotice('You cannot send a blank message. The correct syntax for this command is "/ctndh [message]".', u, yellow); } } else { cb.sendNotice('Only moderators and broadcasters are able to use that command.', u, yellow); } } else { cb.sendNotice('The UltraApp Ticket Show feature is not running.', u, yellow); } break; } //********* Pre-sales Commands case '/useps': case '/usepresales': case '/usepresale': { cmd = 1; if (isMod || isBC) { if (whichApp == 'ticket') { cb.sendNotice('Unable to start pre-sales, the actual ticket show feature is already started.', u, yellow); } else if (message[1] == 'on' && cb.settings.ticketShowFanAppreciation == 'Yes') { cb.sendNotice('Unable to start pre-sales since the Ticket Show feature is configured for a Fan Appreciation Show.', u, yellow); } else { if (presalePrice <= 0) { presalePrice = cb.settings.ticketShowPresalePrice; } if (presalePrice > 0) { if (ticketPrice <= 0) { cb.sendNotice('Unable to start pre-sales, the actual ticket show price must be set first using the command "/ticketprice xx" where xx will be the the Ticket App show price.', u, yellow); } else if (presalePrice > ticketPrice) { cb.sendNotice('Unable to start pre-sales, the Pre-Sales Price is greater than the ticket show price.', u, yellow); } else { setPresalesToggle(message[1], u, presalePrice); } } else { cb.sendNotice('Unable to start pre-sales, a pre-sale ticket price must be set first using the command "/presaleprice xx" where xx is in the initial price to start selling pre-sales tickets at.', u, yellow); } } } else { cb.sendNotice('Only moderators and broadcasters are able to use that command.', u, yellow); } break; } case '/psprice': case '/presaleprice': { cmd = 1; if (isMod || isBC) { numprice = parseInt(message[1]) if (isNaN(numprice)) { cb.sendNotice('The value entered for the pre-sale ticket price is not numeric, please try again.', u, yellow); break; } else if (numprice < 1 || numprice > 1000) { cb.sendNotice('The value entered for the pre-sale ticket price is outside allowable values from 1 to 1000, please try again.', u, yellow); } else if (numprice > ticketPrice) { cb.sendNotice('The value entered for the pre-sale ticket price is higher than the actual ticket price of ' + ticketPrice + '. Please update the ticket price first using the command "/ticketprice [newprice]".', u, yellow); } else { if (numprice < presalePrice) { cb.sendNotice('Warning: Value is updated but this price is lower than the previous pre-sale price of ' + presalePrice + '. It is not advisiable to decrease the presale price once tickets have been sold.', u, yellow); } if (presalesToggle == 1) { presalePriceChange(numprice,u); cb.sendNotice('The Pre-sale Ticket Price has been updated to ' + numprice + ' tokens, all tips of at least this amount will add a user to the Pre-sale Ticket List. You can view the pre-sales list with the command /presalelist and if not added from /prepticket, the presales list can be added to the show with the command /exppresale after the ticket show app is started.', u, yellow); } else { presalePrice = numprice; cb.sendNotice('The Pre-sale Ticket Price has been set to ' + numprice + ' tokens, and the pre-sale can now be started with the command "/usepresale on". ', u, yellow); } } } else { cb.sendNotice('Only moderators and broadcasters are able to use that command.', u, yellow); } break; } case '/pspt': case '/presalepricetimer': { cmd = 1; if (isMod || isBC) { if (presalesToggle == 1) { validtimer = 0; numtimer = parseInt(message[1]) if(isNaN(numtimer)) { cb.sendNotice('The timer value entered for the pre-sale price change is not numeric, please try again. The syntax for the command is "/presalepricetimer [t] [p]", where [t] is the number of minutes to use for the timer, and [p] is the new ticket price that will be used when the timer expires.', u, yellow); } else if (numtimer < 1 || numtimer > 60) { cb.sendNotice('The timer value entered for the pre-sale price change is outside allowable values from 1 to 60 minutes, please try again. The syntax for the command is "/presalepricetimer [t] [p]", where [t] is the number of minutes to use for the timer, and [p] is the new ticket price that will be used when the timer expires.', u, yellow); } else if (presaleMinsRemain > 0 || presaleSecsRemain > 0) { cb.sendNotice('A timer is already running for the next price change. You can add time to the timer with the command "/presaleaddtime [t]" where [t] is the number of minutes to add, and can be positive to add time or negative to subtract time.', u, yellow); } else if (presalesToggle == 0) { cb.sendNotice('The Pre-sales function has not been turned on, you can use the command "/usepresale on" to enable this function if an initial price has been set.', u, yellow); } else { validtimer = 1; } numprice = parseInt(message[2]) if(isNaN(numprice)) { cb.sendNotice('The new pre-sales price value entered for the price change timer is not numeric, please try again.', u, yellow); validtimer = 0; } else if(numprice < 1 || numprice > 1000) { cb.sendNotice('The new pre-sales price value entered for the price change timer is outside allowable values from 1 to 1000, please try again.', u, yellow); validtimer = 0; } else if(numprice > cb.settings.ticketShowPrice) { cb.sendNotice('The new pre-sales price value entered of ' + numprice + ' should not be greater than the ticket show price of ' + cb.settings.ticketShowPrice + ', please try again. If the ticket show price is incorrect or not set, it can be set using the command "/ticketprice pp", where pp is the planned ticket show price.', u, yellow); validtimer = 0; } else { if (validtimer = 1) { presaleMinsRemain = numtimer; nextPresalePrice = numprice; presaleAutoTimer(presaleMinsRemain); cb.sendNotice('The timer has been started to update the pre-sale price to ' + numprice + ' tokens in ' + numtimer + ' minutes.', u, yellow); } } } else { cb.sendNotice('The Ticket Show Pre-sales function has not been turned on, you can use the command "/usepresale on" to enable this function if an initial price has been set.', u, yellow); } } else { cb.sendNotice('Only moderators and broadcasters are able to use that command.', u, yellow); } break; } case '/psstarttimer': case '/presalestarttimer': { cmd = 1; if (presalesToggle == 1) { if (isMod || isBC) { numtimer = parseInt(message[1]) if(isNaN(numtimer)) { cb.sendNotice('The value entered for the minutes to use for the pre-sale timer is not numeric, please try again.', u, yellow); break; } else if(numtimer < 1 || numtimer > 60) { cb.sendNotice('The value entered for the minutes to use for the pre-sale timer is outside allowable values from 1 to 60, please try again.', u, yellow); } else { presaleMinsRemain = numtimer; presaleAutoTimer(presaleMinsRemain); } } else { cb.sendNotice('Only moderators and broadcasters are able to use that command.', u, yellow); } } else { cb.sendNotice('The Ticket Pre-sales feature is disabled.\nType "/fbhelp tickets" to see a full list of the available commands related to ticket shows.', u, yellow); } break; } case '/psat': case '/presaleaddtime': { cmd = 1; if (presalesToggle == 1) { if (isMod || isBC) { numtimer = parseInt(message[1]) if(isNaN(numtimer)) { cb.sendNotice('The value entered for the minutes to add to the pre-sale timer is not numeric, please try again.', u, yellow); break; } else if(numtimer < 1 || numtimer > 60) { cb.sendNotice('The value entered for the minutes to add to the pre-sale timer is outside allowable values from 1 to 60, please try again.', u, yellow); } else { presaleAddTime(numtimer, u); } } else { cb.sendNotice('Only moderators and broadcasters are able to use that command.', u, yellow); } } else { cb.sendNotice('The Ticket Pre-sales feature is disabled.\nType "/fbhelp tickets" to see a full list of the available commands related to ticket shows.', u, yellow); } break; } case '/psstoptime': case '/presalestoptime': case '/presalestoptimer': { cmd = 1; if (presalesToggle == 1) { if (isMod || isBC) { stopPresaleTimer(u); } else { cb.sendNotice('Only moderators and broadcasters are able to use that command.', u, yellow); } } else { cb.sendNotice('The Ticket Pre-sales feature is disabled.\nType "/fbhelp tickets" to see a full list of the available commands related to ticket shows.', u, yellow); } break; } case '/addps': case '/addpresale': { cmd = 1; if (isMod || isBC) { if (presalesToggle == 1) { if (cmdval != null) { var cmdvalsplit = cmdval.split(listRegExp); if (cmdvalsplit.length > 1) { cb.sendNotice('Adding multiple users to the Pre-sale list.', u, yellow); for (var i = 0; i < cmdvalsplit.length; i++) { if (cmdvalsplit[i] != "") { if (!cb.limitCam_userHasAccess(cmdvalsplit[i])) { addRmvPresale('add', cmdvalsplit[i]); cb.sendNotice('Added ' + cmdvalsplit[i] + ' to the pre-sale list.', u); cb.sendNotice(u + ' has added you to the Pre-sale Ticket Show list.', cmdvalsplit[i], yellow); } else { cb.sendNotice(cmdvalsplit[i] + ' is already on the ticket list. Skipping.', u); } } } cb.sendNotice('All users were added and notified.', u, yellow) cb.sendNotice(u + ' has added multiple users to the pre-sale list.\n' + 'Users added: ' + cbjs.arrayJoin(cmdvalsplit, ', '), '', yellow, '', 'normal', 'red'); } else { if (!cb.limitCam_userHasAccess(message[1])) { addRmvPresale('add',message[1]); } else { cb.sendNotice(message[1] + ' is already on the ticket list.', u); } } } else { cb.sendNotice('You didn\'t specify what user(s) you want to add to the Pre-sale list.', u, yellow); } } else { cb.sendNotice('Note: User not added to pre-sale ticket list, the Ticket Pre-sales feature is disabled.', u, yellow); } } break; } case '/rmvps': case '/rmvpresale': { cmd = 1; if (isMod || isBC) { if (presalesToggle == 1) { if(message[1] != '' && message[1] != null) { if (cb.limitCam_userHasAccess(message[1])) { addRmvPresale('rmv', message[1]); cb.sendNotice('User ' + message[1] + ' has been removed from the Pre-Sale Ticket List.', u, yellow); } else { cb.sendNotice('Note: User is not in the Pre-Sale Ticket List.',u,yellow); } } } else { cb.sendNotice('The Ticket Pre-sales feature is disabled.',u,yellow); } } break; } case '/psmode': case '/chgpresalemode': { cmd = 1; if (isMod || isBC) { if (presalesToggle == 1) { newPresaleMode = message[1].toLowerCase(); if (newPresaleMode != 'manual' && newPresaleMode != 'timer' && newPresaleMode != 'count') { cb.sendNotice('The value entered for the new mode is not valid, please try again using a value of "manual", "timer", or "count".', u, yellow); } else { if (newPresaleMode === presaleMode) { cb.sendNotice('The value entered for the new mode is the same as the existing mode, command ignored.', u, yellow); } else { setPresaleMode(newPresaleMode,u); } } } else { cb.sendNotice('The Ticket Pre-sales feature is disabled.',u,yellow); } } else { cb.sendNotice('Only moderators and broadcasters are able to use that command.', u, yellow); } break; } case '/pstimeleft': case '/presaletimeleft': { cmd = 1; if (isMod || isBC) { if (presaleMinsRemain >= 1 || presaleSecsRemain >= 1) { cb.sendNotice(presaleTimeLeft(), "", yellow, "", "bold"); } else { cb.sendNotice('A pre-sale timer is not running.', u, yellow); } } else { cb.sendNotice('Only moderators and broadcasters are able to use that command.', u, yellow); } break; } //******** VIP Commands *********** case '/addvipuser': { cmd = 1; if (isBC) { if (message[1]) { addRmvVIP(message[1], u, 'a'); } else { cb.sendNotice("You didn't specify what user(s) you want to add to the VIP list.", u, yellow); } } else { cb.sendNotice('Only broadcasters are able to use that command.', u, yellow); } break; } case '/rmvvipuser': { cmd = 1; if(isBC) { addRmvVIP(message[1], u, 'r'); } else { cb.sendNotice('Only broadcasters are able to use that command.', u, yellow); } break; } case '/listvip': { cmd = 1; if (isMod || isBC) { cb.sendNotice('Users currently on the Ticket Show App VIP List: ' + VIPListArray.length + '\n' + (VIPListArray.length > 0 == true ? cbjs.arrayJoin(VIPListArray, ', ') : 'No users.') + '\nEnd of List', u, yellow); } else { cb.sendNotice('Only moderators and broadcasters are able to use that command.', u, yellow); } break; } //******** External Fan Club Commands *********** case '/addefc': { cmd = 1; if (isBC) { if (message[1]) { addRmvExtFan(message[1], u, 'a'); } else { cb.sendNotice("You didn't specify what user(s) you want to add to the External Fan Club list.", u, yellow); } } else { cb.sendNotice('Only broadcasters are able to use that command.', u, yellow); } break; } case '/rmvefc': { cmd = 1; if(isBC) { addRmvExtFan(message[1], u, 'r'); } else { cb.sendNotice('Only broadcasters are able to use that command.\nType "/fbhelp commands" to see a full list of the available commands.', u, yellow); } break; } case '/listefc': { cmd = 1; if (isMod || isBC) { cb.sendNotice('Users currently in the Ticket Show App External Fan Club List: ' + extFanListArray.length + '\n' + (extFanListArray.length > 0 == true ? cbjs.arrayJoin(extFanListArray, ', ') : 'No users.') + '\nEnd of List', u, yellow); } else { cb.sendNotice('Only moderators and broadcasters are able to use that command.\nType "/fbhelp commands" to see a full list of the available commands.', u, yellow); } break; } //********* Help Menu case '/uahelp': { cmd = 1; if (isMod || isBC) { helpModBC(message[1],u); } else { cb.sendNotice('Only broadcasters and moderators are able to use the help command, there are no commands available to general users.', u, yellow); } break; } //********* End of Expected commands } if (message[0] == '/ki' || message[0] == '/gbhelp' || message[0] == '/whs' || message[0] == '/rri' || message[0] == '/whi' || message[0] == '/di' || message[0] == '/setusercolor' || message[0] == '/setusernn' || message[0] == '/setusericon' || message[0] == '/pt' || message[0] == '/gbstats' || message[0] == '/wi' || message[0] == '/wf' || message[0] == '/warfree' || message[0] == '/pi' || message[0] == '/pf' || message[0] == '/pressfree' || message[0] == '/pw' || message[0] == '/presswinners' || message[0] == '/ww' || message[0] == '/warwinners' || message[0] == '/cpd' || message[0] == '/chgprizedesc' || message[0] == '/addprize' || message[0] == '/cpp' || message[0] == '/changeprizeprice' || message[0] == '/mpl' || message[0] == '/masterprizelist' || message[0] == '/ap' || message[0] == '/pl' || message[0] == '/presslist' || message[0] == '/stoppress' || message[0] == '/press' || message[0] == '/pp' || message[0] == '/pressprizes' || message[0] == '/war' || message[0] == '/waragain' || message[0] == '/stopwar' || message[0] == '/warprizes' || message[0] == '/wl' || message[0] == '/warlist' || message[0] == '/wd' || message[0] == '/wardraw' || message[0] == '/wp' || message[0] == '/kb' || message[0] == '/freekeno' || message[0] == '/wargame' || message[0] == '/keno' || message[0] == '/kp' || message[0] == '/kpbc' || message[0] == '/rrprice' || message[0] == '/chambers' || message[0] == '/rrp' || message[0] == '/rr' || message[0] == '/rrs' || message[0] == '/shooters' || message[0] == '/wheelspins' || message[0] == '/wheelprice' || message[0] == '/freespin' || message[0] == '/freeroll' || message[0] == '/wheel' || message[0] == '/wheelprizes' || message[0] == '/dice' || message[0] == '/diceprizes' || message[0] == '/diceprice' || message[0] == '/usegraylock' || message[0] == '/chggraytime' || message[0] == '/fbhelp' || message[0] == '/expps' || message[0] == '/pslist' || message[0] == '/subject' || message[0] == '/check' || message[0] == '/pass' || message[0] == '/plist' || message[0] == '/plistw' || message[0] == '/email' || message[0] == '/newshow' || message[0] == '/useraffle' || message[0] == '/uselushmenu' || message[0] == '/usemedia' || message[0] == '/pm' || message[0] == '/reply' || message[0] == '/bc' || message[0] == '/tm' || message[0] == '/tbm' || message[0] == '/silencelevel' || message[0] == '/graphiclevel' || message[0] == '/ninja' || message[0] == '/unninja' || message[0] == '/ninjalist' || message[0] == '/silence' || message[0] == '/unsilence' || message[0] == '/silencelist' || message[0] == '/tipmenu' || message[0] == '/tipmenurequests' || message[0] == '/tipmenuadd' || message[0] == '/tipmenurmv' || message[0] == '/usemenu' || message[0] == '/posmenu' || message[0] == '/posmenurequests' || message[0] == '/posmenuadd' || message[0] == '/posmenurmv' || message[0] == '/useposmenu' || message[0] == '/startclock' || message[0] == '/addtoclock' || message[0] == '/timeleft' || message[0] == '/stopclock' || message[0] == '/cn' || message[0] == '/cnh' || message[0] == '/cnd' || message[0] == '/cndh' || message[0] == '/usenotifier' || message[0] == '/chgmsg1' || message[0] == '/chgmsg2' || message[0] == '/chgmsg3' || message[0] == '/chgmsg4' || message[0] == '/chgmsg5' || message[0] == '/dspmsg' || message[0] == '/leaders' || message[0] == '/useleaderboard' || message[0] == '/usetipcount' || message[0] == '/tippers' || message[0] == '/poll' || message[0] == '/usepoll' || message[0] == '/endpoll' || message[0] == '/restartpoll' || message[0] == '/addvote' || message[0] == '/polloptadd' || message[0] == '/polloptrmv' || message[0] == '/pollstarttimer' || message[0] == '/polladdtime' || message[0] == '/pollstoptimer' || message[0] == '/pollleader' || message[0] == '/addnice' || message[0] == '/rmvnice' || message[0] == '/nicelist' || message[0] == '/addvip' || message[0] == '/rmvvip' || message[0] == '/viplist' || message[0] == '/exportvip' || message[0] == '/addfan' || message[0] == '/rmvfan' || message[0] == '/fanlist' || message[0] == '/exportfans' || message[0] == '/addword' || message[0] == '/rmvword' || message[0] == '/wordlist' || message[0] == '/newsubject' || message[0] == '/dumpsettings' || message[0] == '/checkcolor' || message[0] == '/prepticket' || message[0] == '/dsptlist' || message[0] == '/usetlist' || message[0] == '/exptlist' || message[0] == '/addlbtop' || message[0] == '/addlbamt' || message[0] == '/useticketshow' || message[0] == '/usepresale' || message[0] == '/presalelist' || message[0] == '/presaleprice' || message[0] == '/presalepricetimer' || message[0] == '/presalestarttimer' || message[0] == '/presalestoptimer' || message[0] == '/presaleaddtime' || message[0] == '/exppresale' || message[0] == '/addpresale' || message[0] == '/rmvpresale' || message[0] == '/chgpresalemode' || message[0] == '/presaletimeleft)' || message[0] == '/lushmenu' || message[0] == '/uselushmenu' || message[0] == '/chgtoy' || message[0] == '/uselush' || message[0] == '/usedomi' || message[0] == '/usenora' || message[0] == '/media' || message[0] == '/usemedia' || message[0] == '/prizes' || message[0] == '/usedice' || message[0] == '/chgdiceprice' || message[0] == '/dicerolls' || message[0] == '/usealltime' || message[0] == 'top10' || message[0] == '/alltime' || message[0] == '/startprivate' || message[0] == '/stopprivate' || message[0] == '/useraffle' || message[0] == '/entries' || message[0] == '/raffletickets' || message[0] == '/raffleentries' || message[0] == '/previousentries' || message[0] == '/resetraffle' || message[0] == '/clearraffle' || message[0] == '/addraffletkt' || message[0] == '/rmvraffletkt' || message[0] == '/addraffleprize' || message[0] == '/rmvraffleprize' ||message[0] == '/raffleprizes' || message[0] == '/setraffleprice' || message[0] == '/raffledrawing' || message[0] == '/rafflestarttimer' || message[0] == '/startraffletimer' || message[0] == '/raffleaddtime' || message[0] == '/addraffletime' || message[0] == '/rafflestoptimer' || message[0] == '/stopraffletimer' || message[0] == '/raffletimeleft' || message[0] == '/chgrafflemode') { cmd = 1; } if (cb.settings.fembotRunning != 'Yes') { if (cmd == 0) { cb.sendNotice(message[0] + ' is not a recognized UltraApp, Fembot, or Gamebot command.\nType "/uahelp" to see a full list of the available UltraApp commands.', u, yellow); } } } // Highlight background for Ticket show users if (cb.limitCam_userHasAccess(u)) { msg['background'] = ticketHolderBgColor; } return msg; }); } // *********************************** Tip Options ********************************** { cb.tipOptions(function(user) { if (whichApp == 'goalrace') { if (cbjs.arrayContains(raceTipNotesOn, user)) { return; } else { return {options:[{label: goalraceDesc1}, {label: goalraceDesc2}], label: 'Select a Goal to Vote for :'}; } } else { return; } }); } // **************************** Actions on user entering ***************************** { cb.onEnter(function(user) { // Variables var u = user.user; var isMod = user.is_mod; var isBC = (u === cb.room_slug); var isFan = user.in_fanclub; var isExtFan = cbjs.arrayContains(extFanListArray,u); var isVIP = cbjs.arrayContains(VIPListArray,u); var isNotGray = user.has_tokens; // for testing gray/fanclub user functions on testbed //if (u == 'dorothyuser2'){ // isNotGray = false; // isFan = true; //} // if(isMod) { populateModeratorArray(u, "mod"); } if(isFan) { populateFanClubArray(u); } if(cb.limitCam_userHasAccess(u)) { if (!cbjs.arrayContains(ticketShowViewerList,u)) { ticketShowViewerList.push(u); } } // **** General Entry Message if(cb.settings.enableEntryMessage == 'Yes') { var entrymessage = cb.settings.entryMessage; switch (whichApp) { case 'goals': { entrymessage += '\n* The Progressive Goal Feature is currently active.'; if (progGoalArray.desc.length <= 1) { entrymessage += '\n* There is a single goal for the show.'; } else { entrymessage += '\n* There are ' + totalProgGoals + ' goals set up for the show.'; } cb.sendNotice(entrymessage, u, progGoalBgColor, progGoalTxtColor, 'bold'); break; } case 'goalcount': { entrymessage += '\n* The Goal Counter Feature is currently active.'; entrymessage += '\n* There are prizes at specifc goal levels (' + (cbjs.arrayJoin(goalCounterArray.amt, 'g, ')) + 'g).'; cb.sendNotice(entrymessage, u, goalCountBgColor, goalCountTxtColor, 'bold'); break; } case 'sequence': { entrymessage += '\n* The Tip Sequence Feature is currently active.'; entrymessage += '\n* You can tip in ' + (tipSequenceDirection == 'up' ? 'Ascending' : 'Descending') + ' Sequence to the final goal.'; entrymessage += '\n* There are prizes at specifc count sequence levels (' + (cbjs.arrayJoin(sequenceArray.amt, ', ')) + ').'; cb.sendNotice(entrymessage, u, sequenceBgColor, sequenceTxtColor, 'bold'); break; } case 'tipjar': { if (tipJarRunning) { entrymessage += '\n* The Tip Jar Feature is currently active and the jar has started draining.'; entrymessage += '\n* You can tip to keep the jar full and the prize will be performed until the tip jar is empty.'; } else { entrymessage += '\n* The Tip Jar Feature is currently active.'; entrymessage += '\n* Once the goal is met, the prize will be performed until the tip jar is empty.'; } cb.sendNotice(entrymessage, u, tipjarBgColor, tipjarTxtColor, 'bold'); break; } case 'goalrace': { entrymessage += '\n* The Goal Race Feature is currently active. \n* You can tip to vote for one of the goals.'; entrymessage += '\n* When tipping, a selection list will show in the tip pop-up, select the goal the vote for.'; entrymessage += '\n* If you would like to send the broadcaster tip notes rather than voting,'; entrymessage += '\n* you can use the command /tipnoteon to change your personal setting to allow tipnotes.'; entrymessage += '\n* You can then use /tipnoteoff to switch back to voting with your tips.'; if (cb.settings.goalraceAllowClaim == 'Yes') { entrymessage += '\n* If you do not choose a goal to vote for at the time of tipping, you can use'; entrymessage += '\n* the commands "/addrace1" or "/addrace2" to assign your tips to goal 1 or 2 later.'; } cb.sendNotice(entrymessage, u, goalraceBgColor, goalraceTxtColor, 'bold'); break; } case 'none': { cb.sendNotice(entrymessage, u, '', '', 'bold'); break; } } if (presalesToggle == 1) { cb.sendNotice('\u21E8 Ticket Show Pre-sales are active. \nYou can now buy advance tickets to the show at a price of ' + presalePrice + ' tokens. \nYou will automatically be added to the ticket show when the Ticket Show app is started later. \nBuy now before the price goes up! Ticket show price will be ' + ticketPrice + ' tokens.', u, ticketBgColor, ticketTxtColor, 'bold'); } } // **** Ticket Show functions and message if (whichApp == 'ticket' || presalesToggle == 1) { if (!cb.limitCam_userHasAccess(u)) { if (isMod) { if (cb.settings.ticketShowFreeMods == 'Yes') { addRmvTicket('add',u,'mod'); cb.sendNotice(dashLine80 + '\n* You\'ve been added to the show because you\'re a moderator! \n' + dashLine80, u, ticketBgColor, ticketTxtColor, 'bold'); } cb.sendNotice(dashLine80 + '\n* Moderators: The Ticket Show feature is enabled. \nType "/uahelp ticketshow" to see details on available commands for this feature. \n' + dashLine80, u, ticketBgColor, ticketTxtColor, 'bold'); } else if (isFan) { if (cb.settings.ticketShowFreeFC == 'Yes') { addRmvTicket('add',u,'fan'); cb.sendNotice(dashLine80 + '\n* You\'ve automatically been added to the show because you\'re a fan! \n' + dashLine80, u, ticketBgColor, ticketTxtColor, 'bold'); } else if (cb.settings.ticketShowPriceFC > 1) { cb.sendNotice(dashLine80 + '\n* As a fan club member, you can buy a ticket to the show for ' + cb.settings.ticketShowPriceFC + ' tokens. \n' + dashLine80, u, ticketBgColor, ticketTxtColor, 'bold'); } } else if (isExtFan) { if (cb.settings.ticketShowFreeEFC == 'Yes') { addRmvTicket('add',u,'fan'); cb.sendNotice(dashLine80 + '\n* You\'ve automatically been added to the show because you\'re a fan! \n' + dashLine80, u, ticketBgColor, ticketTxtColor, 'bold'); } else if (cb.settings.ticketShowPriceEFC > 1) { cb.sendNotice(dashLine80 + '\n* As a fan club member, you can buy a ticket to the show for ' + cb.settings.ticketShowPriceEFC + ' tokens. \n' + dashLine80, u, ticketBgColor, ticketTxtColor, 'bold'); } } else if (isVIP) { if (cb.settings.ticketShowFreeVIP == 'Yes') { addRmvTicket('add',u,'vip'); cb.sendNotice(dashLine80 + '\n* You\'ve automatically been added to the show because you\'re a VIP! \n' + dashLine80, u, ticketBgColor, ticketTxtColor, 'bold'); } else if (cb.settings.ticketShowPriceVIP > 1) { cb.sendNotice(dashLine80 + '\n* As a VIP List member, you can buy a ticket to the show for ' + cb.settings.ticketShowPriceVIP + ' tokens. \n' + dashLine80, u, ticketBgColor, ticketTxtColor, 'bold'); } } } if (cb.limitCam_isRunning()) { getShowTime(u); if (!cb.limitCam_userHasAccess(u)) { if (cb.settings.ticketShowFanAppreciation == 'Yes') { cb.sendNotice(dashLine80 + '\nToday\'s Ticket Show is a Fan Club Appreciation Show!\nOnly Fan Club Members and VIPs will be admitted to the show!\nPlease consider joining the Fan Club!\n' + dashLine80, u, ticketBgColor, ticketTxtColor, "bold"); } else { if (showStage == 'ticketshow') { cb.sendNotice(dashLine80 + '\nTicket Show is in progress, broadcaster has not indicated Show Finale.\nThe ticket price is ' + ticketPrice + ' tokens.\n' + dashLine80, u, ticketBgColor,ticketTxtColor,'bold'); if (ticketShowOtToggle == 1) { cb.sendNotice(dashLine80 + '\n* The Broadcaster has enabled the Outstanding Ticket Feature. \n* You can use the command "/otlist" to see if you have an outstanding ticket. \n* You can use a saved outstanding ticket with the command "/useticket". \n* You can also save your ticket for a future show using the command "/saveticket" if the show has not staretd yet.\n' + dashLine80, u, ticketBgColor, ticketTxtColor, 'bold'); } } else if (showStage == 'showwarn') { cb.sendNotice(dashLine90 + '\nTicket Show in progress but broadcaster has indicated show is nearly over. \nBuying a ticket is not recommended, but you may still buy one for ' + ticketPrice + ' tokens.\n' + dashLine90, u, ticketBgColor,ticketTxtColor,'bold'); } else if (showStage == 'showfinale') { cb.sendNotice(dashLine90 + '\nTicket Show in progress but broadcaster has indicated show is nearly over. \nTicket Sales have been suspended, you can no longer buy a ticket. \nBroadcaster may be returning to public chat shortly.\n' + dashLine90, u, ticketBgColor,ticketTxtColor,'bold'); } } } } else { if (showStage == 'ticketsales') { if (cb.settings.ticketShowFanAppreciation == 'Yes') { if (cb.limitCam_userHasAccess(u)) { cb.sendNotice(dashLine80 + '\nWelcome! You have a ticket to the Fan Appreciation show and the show has not yet started.\n' + dashLine80, u, ticketBgColor, ticketTxtColor, "bold"); } else { cb.sendNotice(dashLine80 + '\nToday\'s Ticket Show is a Fan Club Appreciation Show!\nOnly Fan Club Members and VIPs will be admitted to the show!\nPlease consider joining the Fan Club!\n' + dashLine80, u, ticketBgColor, ticketTxtColor, "bold"); } } else { if (cb.limitCam_userHasAccess(u)) { cb.sendNotice(dashLine80 + '\nWelcome! You have a ticket to the show and the show has not yet started.\n' + dashLine80, u, ticketBgColor, ticketTxtColor, "bold"); } else { cb.sendNotice(dashLine80 + '\nTicket Show sales are active and show has not yet started. \nThe ticket price is ' + ticketPrice + ' tokens.\n' + dashLine80, u, ticketBgColor, ticketTxtColor, "bold"); } if (cb.settings.hiddenShowAllowGift == 'Yes') { cb.sendNotice(dashLine80 + '\n* The Broadcaster has enabled the the gifting of tickets. \n* If you purchase extra tickets, you can gift them to others using the command "/giftticket user" \n* You can also choose to give away your own ticket using the command "/givemyticketto user".\n' + dashLine80, u, ticketBgColor, ticketTxtColor, 'bold'); } if (ticketShowOtToggle == 1) { cb.sendNotice(dashLine80 + '\n* The Broadcaster has enabled the Outstanding Ticket Feature. \n* You can use the command "/otlist" to see if you have an outstanding ticket. \n* You can use a saved outstanding ticket with the command "/useticket". \n* You can also save your ticket for a future show using the command "/saveticket" if the show has not started yet.\n' + dashLine80, u, ticketBgColor, ticketTxtColor, 'bold'); } } } else if (showStage == 'aftershow' || ticketShowEnded === true) { cb.sendNotice(dashLine60 + '\nThe Ticket Show is over.\n' + dashLine60, u, ticketBgColor, ticketTxtColor, 'bold'); } } } }); } // *********************************** Actions upon leaving ************************************** { cb.onLeave(function(user) { var u = user.user; if (whichApp == 'ticket' || presalesToggle == 1) { if (cbjs.arrayContains(ticketShowViewerList,u)) { cbjs.arrayRemove(ticketShowViewerList,u); } } }); } // *********************************** Actions upon Draw Panel ************************************** cb.onDrawPanel(function (user) { var panel = {}; if (customPanel) { panel.template = 'image_template'; panel.row1_label = "row1_label"; panel.row1_value = "row1_value"; panel.row2_label = "row2_label"; panel.row2_value = "row2_value"; panel.row3_label = "row3_label"; panel.row3_value = "row3_value"; } else { panel.template = '3_rows_11_21_31'; } if (whichApp == 'ticket') { if (cb.settings.ticketShowFanAppreciation == 'Yes') { panel.row1_value = 'Fan Appreciation Ticket Show'; leftjust1 = 49; panel.row2_value = 'Only Fan Club and VIPs Admitted'; leftjust2 = 40; panel.row3_value = 'Join the Fanclub Today to See the Show!'; leftjust3 = 20; } else if (showStage == 'ticketsales') { if (cb.settings.showTotals == 'Yes') { leftjust3 = 20; } else { leftjust3 = 65; } if (ticketStartMode == 'ticketgoal') { panel.row1_value = 'Start Mode: Ticket Goal \u25FE Progress: ' + ticketShowTotalTickets + ' / ' + ticketShowGoalTickets + ' tickets'; leftjust1 = 20; panel.row2_value = 'Ticket Holders: ' + countTicketsSold + ' \u25FE Viewers: ' + ticketShowViewerList.length; leftjust2 = 45; panel.row3_value = 'Ticket Price: ' + ticketPrice + ' tokens ' + (cb.settings.showTotals == 'Yes' ? (' \u25FE Total Tips: ' + currentAppTotalTicket) : ''); } else if (ticketStartMode == 'tokengoal') { panel.row1_value = 'Start Mode: Token Goal \u25FE Progress: ' + ticketShowTotalTips + ' / ' + ticketShowGoalTokens + ' tokens'; leftjust1 = 20; panel.row2_value = 'Ticket Holders: ' + countTicketsSold + ' \u25FE Viewers: ' + ticketShowViewerList.length; leftjust2 = 45; panel.row3_value = 'Ticket Price: ' + ticketPrice + ' tokens ' + (cb.settings.showTotals == 'Yes' ? (' \u25FE Total Tips: ' + currentAppTotalTicket) : ''); } else if (ticketStartMode == 'timer') { panel.row1_value = 'Start Mode: Timer (' + ((ticketSecsRemain > 0 || ticketMinsRemain > 0) ? ticketTimeLeftPanel() : 'Timer not yet started') + ')'; leftjust1 = 20; panel.row2_value = 'Ticket Holders: ' + countTicketsSold + ' \u25FE Viewers: ' + ticketShowViewerList.length; leftjust2 = 45; panel.row3_value = 'Ticket Price: ' + ticketPrice + ' tokens ' + (cb.settings.showTotals == 'Yes' ? (' \u25FE Total Tips: ' + currentAppTotalTicket) : ''); } else { panel.row1_value = 'Start Mode: Manual'; leftjust1 = 75; panel.row2_value = 'Ticket Holders: ' + countTicketsSold; leftjust2 = 78; panel.row3_value = 'Ticket Price: ' + ticketPrice + ' tokens ' + (cb.settings.showTotals == 'Yes' ? (' \u25FE Total Tips: ' + currentAppTotalTicket) : ''); } } else if (showStage == 'showfinale') { panel.row1_value = 'Ticket Holders: ' + countTicketsSold; leftjust1 = 75; panel.row2_value = 'Viewers: ' + ticketShowViewerList.length; leftjust2 = 92; panel.row3_value = 'Show ending soon, no more ticket sales!'; leftjust3 = 20; } else if (showStage == 'aftershow') { panel.row1_value = 'Ticket Show is Over!'; leftjust1 = 72; panel.row2_value = 'Thank you so much everyone for joining!'; leftjust2 = 20; panel.row3_value = ' '; leftjust3 = 70; } else { panel.row1_value = 'Ticket Holders: ' + countTicketsSold; leftjust1 = 75; panel.row2_value = 'Viewers: ' + ticketShowViewerList.length; leftjust2 = 92; panel.row3_value = 'Ticket Price: ' + ticketPrice + ' tokens ' + (cb.settings.showTotals == 'Yes' ? (' \u25FE Total Tips: ' + currentAppTotalTicket) : ''); if (cb.settings.showTotals == 'Yes') { leftjust3 = 20; } else { leftjust3 = 65; } } } else if (whichApp == 'goals') { if (!finalGoalMet) { panel.row1_value = 'Current Goal (#' + (currentGoal) + ') : ' + (currentGoalTips > currentGoalTotal ? currentGoalTotal : currentGoalTips) + ' / ' + currentGoalTotal + ' \u25FE (' + (currentGoalTotal-(currentGoalTips > currentGoalTotal ? currentGoalTotal : currentGoalTips)) + ' left)'; leftjust1 = 20; panel.row2_value = 'Progress: ' + goalBar((currentGoalTips > currentGoalTotal ? currentGoalTotal : currentGoalTips),currentGoalTotal) + ' (' + Math.floor((currentGoalTips > currentGoalTotal ? currentGoalTotal : currentGoalTips)/currentGoalTotal*100) + ' %)'; leftjust2 = 40; if (cb.settings.showTotals == 'Yes') { panel.row3_value = 'Total Goal Show Tips: ' + currentAppTotalGoal; leftjust3 = 57; } else { panel.row3_value = 'At Goal: ' + currentGoalDesc; leftjust3 = 40; } } else { panel.row1_value = 'All Goals Completed!'; leftjust1 = 70; panel.row2_value = 'Progress: ' + goalBar(100,100) + ' (100 %)'; leftjust2 = 40; if (cb.settings.showTotals == 'Yes') { panel.row3_value = 'Total Goal Show Tips: ' + currentAppTotalGoal; leftjust3 = 55; } else { panel.row3_value = ''; } } } else if (whichApp == 'goalcount') { if (!finalGoalMet) { panel.row1_value = 'Current Goal (#' + (currentGoalCountAmt + 1) + '): ' + (currentGoalTips > currentGoalTotal ? currentGoalTotal : currentGoalTips) + ' / ' + currentGoalTotal + ' \u25FE (' + (currentGoalTotal-(currentGoalTips > currentGoalTotal ? currentGoalTotal : currentGoalTips)) + ' left)'; leftjust1 = 10; panel.row2_value = 'Progress: ' + goalBar((currentGoalTips > currentGoalTotal ? currentGoalTotal : currentGoalTips),currentGoalTotal) + ' (' + Math.floor((currentGoalTips > currentGoalTotal ? currentGoalTotal : currentGoalTips)/currentGoalTotal*100) + ' %)'; leftjust2 = 40; panel.row3_value = 'Goals Completed: ' + currentGoalCountAmt + (cb.settings.showTotals == 'Yes' ? (' \u25FE Total Tips: ' + currentAppTotalCounter) : ''); if (cb.settings.showTotals == 'Yes') { leftjust3 = 20; } else { leftjust3 = 65; } } else { panel.row1_value = 'All Goals Completed!'; leftjust1 = 70; panel.row2_value = 'Progress: ' + goalBar(100,100) + ' (100 %)'; leftjust2 = 40; if (cb.settings.showTotals == 'Yes') { panel.row3_value = 'Total Goal Count Tips: ' + currentAppTotalCounter; leftjust3 = 65; } else { panel.row3_value = ' '; } } } else if (whichApp == 'sequence') { if (!finalGoalMet) { panel.row1_value = 'Next Tip Needed: ' + nextSequence + ' \u25FE Counting ' + tipSequenceDirection + ' to ' + endSequence; leftjust1 = 12; panel.row2_value = 'Next Prize at Sequence: ' + currentGoalSequence + ' (' + currentGoalDesc + ')'; leftjust2 = 15; if (cb.settings.showTotals == 'Yes') { panel.row3_value = 'Group Tipping: ' + (groupTipToggle == 1 ? ('ON (' + currentGroupTipAmt + '/' + nextSequence + ')') : 'OFF') + ' \u25FE Seq Tips: ' + currentAppTotalSequence + (cb.settings.tipsequenceShowTotal == 'Yes' ? (' / ' + sequenceTotalTipsGoal) : ''); if (cb.settings.tipsequenceShowTotal == 'Yes') { leftjust3 = 5; } else { leftjust3 = 20; } } else { panel.row3_value = 'Group Tipping: ' + (groupTipToggle == 1 ? ('ON (' + currentGroupTipAmt + '/' + nextSequence + ')') : 'OFF'); leftjust3 = 65; } } else { panel.row1_value = 'Tip Sequence Completed!'; leftjust1 = 65; if (cb.settings.showTotals == 'Yes') { panel.row2_value = 'Total Sequence Goal Tips: ' + currentAppTotalSequence; leftjust2 = 65; } else { panel.row2_value = ' '; } panel.row3_value = ' '; } } else if (whichApp == 'tipjar') { if (!finalGoalMet) { if (!tipJarRunning) { if (!tipJarWaiting) { panel.row1_value = 'Tip Jar Start Goal: ' + (currentGoalTips > currentGoalTotal ? currentGoalTotal : currentGoalTips) + ' / ' + currentGoalTotal + ' \u25FE (' + (currentGoalTotal-(currentGoalTips > currentGoalTotal ? currentGoalTotal : currentGoalTips)) + ' remaining)'; leftjust1 = 10; panel.row2_value = 'Progress: ' + goalBar((currentGoalTips > currentGoalTotal ? currentGoalTotal : currentGoalTips),currentGoalTotal) + ' (' + Math.floor((currentGoalTips > currentGoalTotal ? currentGoalTotal : currentGoalTips)/currentGoalTotal*100) + ' %)'; leftjust2 = 40; panel.row3_value = 'Cycle: ' + (currentGoalRecycleCount+1) + ' / ' + (currentGoalRecycleTotal+1) + ' \u25FE At Goal: ' + currentGoalDesc; leftjust3 = 25; } else { panel.row2_value = 'Tip Jar: Awaiting Manual Advance to Next Goal'; } } else { panel.row1_value = 'Tokens in Jar: ' + tipJarCurrentTokens; leftjust1 = 75; panel.row2_value = 'Drain Rate: ' + drainRateText(); leftjust2 = 35; panel.row3_value = 'When Jar Empties: ' + (cb.settings.tipjarGoalRecycle == 'Tip to continue current goal' ? 'Keep tipping to keep going' : 'Must reach goal again'); leftjust3 = 25; } } else { panel.row1_value = 'All Goals Completed!'; leftjust1 = 65; panel.row2_value = 'Progress: ' + goalBar(100,100) + ' (100 %)'; leftjust2 = 40; if (cb.settings.showTotals == 'Yes') { panel.row3_value = 'Total Tip Jar Tips: ' + currentAppTotalTipJar; leftjust3 = 65; } else { panel.row3_value = ' '; } } } else if (whichApp == 'goalrace') { if (!finalGoalMet) { panel.row1_value = 'Goal Race! ' + goalracePanelText; leftjust1 = 5; panel.row2_value = goalraceDesc1.substring(0,12) + ': ' + goalBar((currentGoalRace1Tips > goalraceAmount1 ? goalraceAmount1 : currentGoalRace1Tips),goalraceAmount1) + ' (' + Math.floor((currentGoalRace1Tips > goalraceAmount1 ? goalraceAmount1 : currentGoalRace1Tips)/goalraceAmount1*100) + '%) \u25FE ' + (currentGoalRace1Tips > goalraceAmount1 ? goalraceAmount1 : currentGoalRace1Tips) + '/' + goalraceAmount1; leftjust2 = 5; panel.row3_value = goalraceDesc2.substring(0,12) + ': ' + goalBar((currentGoalRace2Tips > goalraceAmount2 ? goalraceAmount2 : currentGoalRace2Tips),goalraceAmount2) + ' (' + Math.floor((currentGoalRace2Tips > goalraceAmount2 ? goalraceAmount2 : currentGoalRace2Tips)/goalraceAmount2*100) + '%) \u25FE ' + (currentGoalRace2Tips > goalraceAmount2 ? goalraceAmount2 : currentGoalRace2Tips) + '/' + goalraceAmount2; leftjust3 = 5; } else { panel.row1_value = 'Goal Race Completed!'; leftjust1 = 20; panel.row2_value = 'Winning Goal: ' + goalRaceWinner; leftjust2 = 20; if (cb.settings.showTotals == 'Yes') { panel.row3_value = 'Total Goal Race Tips: ' + currentAppTotalGoalRace; leftjust3 = 20; } else { panel.row3_value = ' '; } } } else if (whichApp == 'none') { panel.row1_value = 'No App Running'; leftjust1 = 80; panel.row2_value = 'Use /chgapp to start an UltraApp feature'; leftjust2 = 15; if (cb.settings.showTotals == 'Yes') { panel.row3_value = 'Total Tips: ' + currentSessionTotal; leftjust3 = 80; } else { panel.row3_value = ' '; } } panel.layers = [ {'type': 'image', 'fileID': backgroundImage}, { 'type': 'text', 'text': panel.row1_value, 'top': topjust1, 'left': leftjust1, 'font-size': fontSize, 'font-weight': 'bold', 'color': textColor, }, { 'type': 'text', 'text': panel.row2_value, 'top': topjust2, 'left': leftjust2, 'font-size': fontSize, 'color': textColor, }, { 'type': 'text', 'text': panel.row3_value, 'top': topjust3, 'left': leftjust3, 'font-size': fontSize, 'color': textColor, }, ] return panel; }); // *********************************** Actions upon tipping ************************************** { cb.onTip (function (tip) { var tipAmount = Number.parseInt(tip.amount, 10); var tipNote = tip.message; var u = tip.from_user var isFan = tip.from_user_in_fanclub; var isExtFan = cbjs.arrayContains(extFanListArray,u); var isVIP = cbjs.arrayContains(VIPListArray,u); // ***** Tip Count Array if (!cbjs.arrayContains(tipCountArray.name, u)) { tipCountArray.name.push(u); tipCountArray.amount.push(tipAmount); } else { tipCountArray.amount[findTipper(u)] += tipAmount; } // ***** Record tip for goal progress and track total stats recordTip(tipAmount,u,tipNote,isFan,isExtFan,isVIP); // ***** Add to Pre-Sale Ticket List if (presalesToggle == 1 && !cb.limitCam_userHasAccess(u) && cb.settings.ticketShowFanAppreciation != 'Yes') { if (tipAmount >= presalePrice) { addRmvPresale('addtip', u); } else { checkCumulative(u,tipAmount,presalePrice,'common','presale'); } } }); } // *********************************** Initialize ************************************** { if (initialize == 0) { var BC = cb.room_slug; // *** Load Array for Progressive Goals for (let i = 1; i <= 20; i++) { goalDesc = this["progressiveGoalDescription"+i]; goalAmt = this["progressiveGoalAmount"+i]; goalRecyc = this["progressiveGoalRecycle"+i]; if(goalDesc != '' && goalDesc != null && goalAmt != '' && goalAmt != null) { progGoalArray.desc.push(goalDesc); progGoalArray.amt.push(goalAmt); progGoalArray.recyc.push(goalRecyc); } } // *** Load Array for Goal Counter for (let i = 0; i < 10; i++) { goalDesc = this["goalCounterDescription"+i]; goalAmt = this["goalCounterAmount"+i]; if(goalDesc != '' && goalDesc != null && goalAmt != '' && goalAmt != null) { goalCounterArray.desc.push(goalDesc); goalCounterArray.amt.push(goalAmt); } } // *** Load Array for Tip Seqeunce for (let i = 0; i < 10; i++) { goalDesc = this["sequenceDescription"+i]; goalAmt = this["sequenceAmount"+i]; if(goalDesc != '' && goalDesc != null && goalAmt != '' && goalAmt != null) { sequenceArray.desc.push(goalDesc); sequenceArray.amt.push(goalAmt); } } // *** Load Array for Tip Jar for (let i = 0; i < 10; i++) { goalDesc = this["tipjarDescription"+i]; goalAmt = this["tipjarAmount"+i]; goalRecyc = this["tipjarRecycle"+i]; if(goalDesc != '' && goalDesc != null && goalAmt != '' && goalAmt != null) { tipjarGoalArray.desc.push(goalDesc); tipjarGoalArray.amt.push(goalAmt); tipjarGoalArray.recyc.push(goalRecyc); } } // *** Init Sequence switch (cb.settings.tipsequenceDirection) { case 'Ascending': { tipSequenceDirection = 'up'; break; } case 'Descending': { tipSequenceDirection = 'down'; break; } } // *** Init Drain Rate switch (cb.settings.tipjarStartDrainRate) { case '5 tokens per second (fastest)': { setDrainRate(1); break; } case '4 tokens per second': { setDrainRate(2); break; } case '3 tokens per second': { setDrainRate(3); break; } case '2 tokens per second': { setDrainRate(4); break; } case '1 token per second': { setDrainRate(5); break; } case '1 token every 2 seconds': { setDrainRate(6); break; } case '1 token every 3 seconds': { setDrainRate(7); break; } case '1 token every 4 seconds': { setDrainRate(8); break; } case '1 token every 5 seconds': { setDrainRate(9); break; } case '1 token every 10 seconds (slowest)': { setDrainRate(10); break; } } // *** Init Feature switch (cb.settings.whichApp) { case 'Single/Progressive Goals': { if (progGoalArray.desc.length <= 0) { cb.sendNotice('The App Feature of "Single/Progressive Goals" was chosen, but no goals have been defined. Please restart the UltraApp and define the goals you would like to use or change the app using the "/chgapp" command', BC, yellow); setAppFeature('none', BC); } else { setAppFeature('goals', BC); } break; } case 'Goal Counter': { if (goalCounterArray.desc.length <= 0) { cb.sendNotice('The App Feature of "Goal Counter" was chosen, but no goals have been defined. Please restart the UltraApp and define the goals you would like to use or change the app using the "/chgapp" command', BC, yellow); setAppFeature('none', BC); } else { allowGoals = true; let i = 1; while (goalCounterArray.amt[i] > 0) { if (goalCounterArray.amt[i] <= goalCounterArray.amt[i-1]) { allowGoals = false; } i++; } if (allowGoals) { setAppFeature('goalcount', BC); } else { cb.sendNotice('The App Feature of "Goal Counter" was chosen, but the "Number of goals" amounts are not listed in increasing value - each goal number setting amount be greater than the previous. Please restart the UltraApp and correct the goal amounts.', BC, yellow); setAppFeature('none', BC); } } break; } case 'UltraApp Ticket Show': { if (cb.settings.ticketShowPrice <= 0) { cb.sendNotice('Unable to start the Ultra App Ticket Show feature, a ticket price was not set on the start page. You can either set the price now using the command "/ticketprice [amt]" where [amt] is in the price in tokens, or restart the bot and set the price. The Ticket Show feature can be enabled after setting a price using the command "/chgapp ticketshow". ', BC, yellow); setAppFeature('none', BC); } else { setAppFeature('ticket', BC); } break; } case 'Asc/Desc Tip Sequence Goals': { if (sequenceArray.desc.length <= 0) { cb.sendNotice('The App Feature of "Asc/Desc Tip Sequence Goals" was chosen, but no goals have been defined. Please restart the UltraApp and define the goals you would like to use or change the app using the "/chgapp" command', BC, yellow); setAppFeature('none', BC); } else if (cb.settings.tipsequenceLowNumber >= cb.settings.tipsequenceHighNumber) { cb.sendNotice('The App Feature of "Asc/Desc Tip Sequence Goals" was chosen, but the lower end of the sequence is configured as higher than the high end of the sequence. Please restart the UltraApp and define the high and low end sequence appropriately.', BC, yellow); setAppFeature('none', BC); } else { allowSeq = true; if (tipSequenceDirection == 'up') { let i = 0; while (sequenceArray.amt[i+1] > 0) { if (sequenceArray.amt[i] > sequenceArray.amt[i+1]) { allowSeq = false; } i++; } } else if (tipSequenceDirection == 'down') { let i = 0; while (sequenceArray.amt[i+1] > 0) { if (sequenceArray.amt[i] < sequenceArray.amt[i+1]) { allowSeq = false; } i++; } } if (allowSeq) { setAppFeature('sequence', BC); } else { cb.sendNotice('The App Feature of "Asc/Desc Tip Sequence Goals" was chosen, but the goals are not sequenced correctly per the ascending or descending direction. Please restart the UltraApp and make sure the goal sequence matches the ascending/descending setting (for example, use 5,10,15 for ascending, or 15,10,5 for descending).', BC, yellow); setAppFeature('none', BC); } } break; } case 'Tip Jar': { if (tipjarGoalArray.desc.length <= 0) { cb.sendNotice('The App Feature of "Tip Jar" was chosen, but no goals have been defined. Please restart the UltraApp and define the goals you would like to use or change the app using the "/chgapp" command', BC, yellow); setAppFeature('none', BC); } else { setAppFeature('tipjar', BC); } break; } case 'Goal Race': { setAppFeature('goalrace', BC); break; } case 'None': { setAppFeature('none', BC); break; } } //*** Init Presales for Ticket Show if (cb.settings.ticketShowPresalesMode != 'No Pre-sales') { if (whichApp == 'ticket') { cb.sendNotice('Unable to start pre-sales, the actual ticket show feature is already started.', BC, yellow); } else if (cb.settings.ticketShowFanAppreciation == 'Yes') { cb.sendNotice('Unable to start pre-sales since the Ticket Show feature is configured for a Fan Appreciation Show.', BC, yellow); } else { if (cb.settings.ticketShowPresalePrice > 0) { if (cb.settings.ticketShowPresalePrice > ticketPrice && ticketPrice > 0) { cb.sendNotice('Unable to start pre-sales, the pre-sales price (' + cb.settings.ticketShowPresalePrice + ' tokens) is greater than the planned ticket show price (' + cb.settings.ticketPrice + ' tokens).\n Use the command "/presaleprice xx" or "/ctprice xx", where xx is in the initial price, to update and then start pre-sales with the command "/usepresale on".', BC, yellow); } else if (ticketPrice <= 0) { cb.sendNotice('Unable to start pre-sales, the Ticket Show Ticket Price is not yet defined. You can use the command "/ticketprice [amt]" to update the price, where [amt] is the planned price. The price can be changed later but is required now for pre-sales.', BC, yellow); } else { setPresalesToggle('on', cb.room_slug,cb.settings.ticketShowPresalePrice); } } else { cb.sendNotice('Unable to start pre-sales, a pre-sale ticket price must be set first using the command "/presaleprice [p]" where [p] is in the initial price. Pre-sales can then be started using the command "/usepresale on". ', BC, yellow); } } } //*** Init OT List for Ticket Show if(cb.settings.ticketShowOTList != '' && cb.settings.ticketShowOTList != null) { var n = cb.settings.ticketShowOTList; outstandingTicketArray = n.split(','); } //*** Init External Fan Club List if(cb.settings.extFanList != '' && cb.settings.extFanList != null) { var n = cb.settings.extFanList; extFanListArray = n.split(','); } //*** Init VIP List for Ticket Show if(cb.settings.VIPList != '' && cb.settings.VIPList != null) { var n = cb.settings.VIPList; VIPListArray = n.split(','); } //*** Initialize background if (cb.settings.panelBackground != 'default - no image') { if (cbjs.arrayContains(backgroundArray.menu,cb.settings.panelBackground)) { newbackground = backgroundArray.command[backgroundArray.menu.indexOf(cb.settings.panelBackground)]; customizePanelBackground(newbackground,BC); customizePanelText('',BC); } else { customPanel = false; cb.sendNotice('A custom panel background was chosen but it does not exist, using the default panel background.', BC, yellow); } } else { customPanel = false; } ultraAppStartTime = new Date(); populateModeratorArray(BC, "bc"); initialize = 1; } }
© Copyright Chaturbate 2011- 2026. All Rights Reserved.