Apps Home
|
My Uploads
|
Create an App
bunniedark multigoal
Author:
bunniedark
Description
Source Code
Launch App
Current Users
Created by:
Bunniedark
App Images
/****************************************************************************** * This app is released open source to encourage transparency, trust, and * collaboration. Anyone may clone this app to make your own modifications, * personalizations, and enhancements, and if you do so I would likewise * encourage you to release your version open source and to make an attribution * to me as the original author. If you are cloning it just to fix a bug, * though, I would ask you to report it to me to fix here and let me give you * credit in the app description for your contribution. ******************************************************************************/ //////////////////////////////////////// // Constants ///////////////////////// //////////////////////////////////////// // Do not change this... const AppAuthor = 'smoker919'; // ...change this instead if you fork this app const AppMaintainer = 'smoker919'; // colours const AdminColor = 'Black'; const AdminHighlightColor = 'FireBrick'; const AdminBgColor = '#E6E6E6'; // 90% lighter Black, i.e., grey // fonts const Bold = 'bold'; // spacing const NBSP = '\u00A0'; const EmSpace = '\u2001'; const EnSpace = '\u2002'; const ThreePerEmSpace = '\u2004'; // K-V keys const BroadcasterKey = 'broadcaster'; // settings const ThemeKey = 'theme'; const GoalsKey = 'goals'; const SettingsKey = 'settings'; // runtime state const CurrentGoalIndexKey = 'current_goal_index'; const TokensReceivedForCurrentGoalKey = 'tokens_received_for_current_goal'; const LastTipKey = 'last_tip'; const IsReactingToTipKey = 'is_reacting_to_tip'; const CurrentAnimatedImageKey = 'current_animated_image'; const CurrentTipReplacementImageKey = 'current_tip_replacement_image'; const ShowGoalInTopRowKey = 'show_goal_in_top_row'; const ShowLastTipInBottomRowKey = 'show_last_tip_in_bottom_row'; const CustomThemesKey = 'custom_themes'; const ActiveCustomThemeKey = 'active_custom_theme'; const EnterNoticesQueueKey = 'enter_notices_queue'; const LastEnterNoticeTimesKey = 'last_enter_notice_times'; // callbacks const SetNextAnimatedImageLabel = 'set_next_animated_image'; const RotateTopRowViewLabel = 'rotate_top_row_view'; const RotateBottomRowViewLabel = 'rotate_bottom_row_view'; const StopReactingToTipLabel = 'stop_reacting_to_tip'; const MovingToNextGoalLabel = 'moving_to_next_goal'; const StartNextGoalLabl = 'start_next_goal'; const EnterNoticesLabel = 'enter_notices'; // misc const ProgressSquares = 12; const AnonymousUser = 'anonymous user'; const UsernameCharacters = 'abcdefghijklmnopqrstuvwxyz'; const KeycapNumbers = ['0️⃣', '1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣']; const EnterNoticesDelay = 4000; // 4 seconds const MinEnterNoticeInterval = 600000; // 10 minutes // transition timings const GoalViewDuration = 35; const LovenseViewDuration = 5; const LastTipViewDuration = 25; const PrivateViewDuration = 15; const MovingToNextGoalDelay = 15; const NextGoalDelay = 45; const AnimatedImageDuration = 10; const MaxDescriptionLengthInGoalView = 30; const NoticeBackgrounds = [ 'linear-gradient(to right, rgb(0,255,255), rgb(153,255,153))', // aqua to green 'linear-gradient(to right, rgb(86,204,242), rgb(255,204,255))', // blue to pink 'linear-gradient(to right, rgb(151,150,240), rgb(251,199,212))', // purple to pink 'linear-gradient(to right, rgb(168,192,255) 40.0%, rgb(86,204,242) 60.0%)', // grey/purple to aqua blue 'linear-gradient(to right, rgb(176,196,222), rgb(204,235,255), rgb(165,254,203))', // grey/blue to blue to mint green 'linear-gradient(to right, rgb(255,204,255), rgb(255, 153, 204))', // pink to pink 'linear-gradient(to right, rgb(255,255,0), rgb(199,250,195))', // yellow to mint green 'linear-gradient(to right, rgb(255,255,0), rgb(255,153,204))' // yellow to pink ]; const InvalidCustomThemeNames = ['setgoal', 'setcustomtheme', 'removecustomtheme', 'listcustomthemes', 'test', 'testanon']; const GoalRegex = /\s*(.+)\s*:\s*([1-9]\d*)\s*/; const SetGoalRegex = /^\s*\/setgoal\s*(.+)\s*:\s*([1-9]\d*)\s*$/i; const SetCustomThemeRegex = /^\s*\/setcustomtheme\s*(\w{3,30})\s*([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.webp)\s*([1-9]\d*)\s*$/i; const RemoveCustomThemeRegex = /^\s*\/removecustomtheme\s*(\w{3,30})\s*$/i; const ActiveCustomThemeRegex = /^\s*\/(\w{3,30})\s*(on|off)\s*$/i; const TestCommandRegex = /^\/(test|testanon) ([1-9]\d*)$/; const GoalCompletedMessage = '💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎' + '\n░>░>░>░G░O░A░L░ ░C░O░M░P░L░E░T░E░D░!░!░!░<░<░<░' + '\n:minions_hurra :goalcomplete-GR ' + '\nThe goal of: [description] was completed!' + '\n💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎'; const MovingToNextGoalMessage = '⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️' + '\n–––––––––––––– NEXT GOAL ––––––––––––––––––' + '\n' + NBSP.repeat(13) + '► [description]: [amount]' + '\n––––––––––––––––––––––––––––––––––––––––––' + '\n' + NBSP.repeat(13) + 'Moving to next goal in ' + (NextGoalDelay - MovingToNextGoalDelay) + ' seconds ...' + '\n⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️'; const NextGoalSetMessage = '⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️' + '\n–––––––––––––– NEXT GOAL ––––––––––––––––––' + '\n' + NBSP.repeat(13) + '► [description]: [amount]' + '\n––––––––––––––––––––––––––––––––––––––––––' + '\n' + NBSP.repeat(13) + 'The next goal has been set!' + '\n⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️'; const NewGoalSetMessage = '⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡' + '\n–––––––––––––– NEW GOAL ––––––––––––––––––' + '\n' + NBSP.repeat(13) + '► [description]: [amount]' + '\n––––––––––––––––––––––––––––––––––––––––––' + '\n' + NBSP.repeat(13) + 'The goal has been set!' + '\n⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡' // from https://venngage.com/blog/accessible-colors/ const PastelColors = [ '#FFE1AC', '#FFFFA9', '#D8FAAC', '#BDF5CD', '#A3F2D8', '#FFF0B7', '#FFF4CB', '#C2F1E9', '#F2E2FF', '#EBD6FF', '#8BBEE2', '#B2D4EF', '#FFDBDA', '#FEBAB7', '#FE9691', '#A2E0B0', '#C0EAC8', '#F6E0FE', '#E9C3F6', '#DDA5F0' ]; //////////////////////////////////////// // Classes /////////////////////////// //////////////////////////////////////// class Image { constructor(id, width, height) { this.id = id; this.width = width; this.height = height; } } class Theme { constructor( topRowTextColour, bottomRowTextColour, baseBackgroundImageId, overlayBackgroundImage, tipBackgroundImageId, tipAnimatedImage, tipMiddleRowImage, tipReplacementAnimatedImages, animatedImages ) { this.topRowTextColour = topRowTextColour, this.bottomRowTextColour = bottomRowTextColour, this.baseBackgroundImageId = baseBackgroundImageId; this.overlayBackgroundImageId = overlayBackgroundImage; this.tipBackgroundImageId = tipBackgroundImageId; this.tipAnimatedImage = tipAnimatedImage; this.tipMiddleRowImage = tipMiddleRowImage; this.tipReplacementAnimatedImages = tipReplacementAnimatedImages; this.animatedImages = animatedImages; } } class Goal { constructor(description, amount) { this.description = description; this.amount = amount; } } class Tip { constructor(username, isAnon, tokens) { this.username = username; this.isAnon = isAnon; this.tokens = tokens; } } class ReloadSettingsResults { constructor( themeChanged, resetGoals, currentGoalDescriptionChanged, subsequentGoalsRemoved, roomSettingsChanged) { this.themeChanged = themeChanged; this.resetGoals = resetGoals; this.currentGoalDescriptionChanged = currentGoalDescriptionChanged; this.subsequentGoalsRemoved = subsequentGoalsRemoved; this.roomSettingsChanged = roomSettingsChanged; } } //////////////////////////////////////// // Themes //////////////////////////// //////////////////////////////////////// // shared images const SparklesBackgroundImageId = 'ccc880e9-43c8-4d87-ba4e-c678354fad40.webp'; const SnowBackgroundImageId = '28bebcf1-ca4e-46a6-b9e3-e6639806a3f4.webp'; const LushImage = new Image('87737c19-12fb-40fc-9e5d-91f011c37b64.webp', 39, 38); const PinkLovenseLogoImage = new Image('16c524b8-b38c-4a63-b16e-f3d60a120c05.png', 160, 20); const WhiteLovenseLogoImage = new Image('fcf25240-d7f2-4178-a22f-df512f308643.png', 156, 20); const ThankYouImage = new Image('65732a4e-c4cb-4510-9f04-b608588a517e.webp', 150, 22); const TopOverlayImageId = 'a198d251-ac80-49a6-be2f-48bf669e40b7.png'; const Themes = { 'Baby Yoda': new Theme('', '', '83567d1f-cc33-441c-92e2-b12dfe821fd2.png', SparklesBackgroundImageId, 'bbae2371-4b1a-4fc3-9e86-94fd2b0bea06.png', LushImage, PinkLovenseLogoImage, [ new Image('c0a34311-c4f9-4c1f-91e3-d15e7579c26e.webp', 66, 69), new Image('5fa515a9-fe0b-4aac-b3ba-88709d1ee2e6.webp', 72, 69), new Image('29b5f4ac-5cd7-4d5e-8752-0628d8b79f63.webp', 69, 69) ], [ new Image('973a3121-7b09-491f-a874-b15e2f9afbdd.webp', 69, 69), new Image('2359f42c-8def-432c-a8b2-881a290851fe.webp', 72, 69), new Image('325dddf4-7b02-4f20-97bf-824f2cfb3a67.webp', 69, 69), new Image('eceedc74-fc87-44e5-bce7-3d5768cefa61.webp', 69, 69), new Image('ce795b87-3097-4e9d-b792-7965a8d8b007.webp', 69, 69), new Image('dac210a3-3caa-45f0-b24f-411eb95a2d57.webp', 58, 69), new Image('88be4735-bf1c-4f37-adef-c54e33bedf2a.webp', 69, 69), new Image('265ccde1-1866-4d7f-94ce-1e73b57a67ca.webp', 74, 69), new Image('d28cee35-e661-4c9d-bfb2-4679be74cdb0.webp', 76, 69) ] ), 'Daisy Flower': new Theme('', '', 'b4eaaf91-7ec2-424d-8ffc-be1c6742ece1.png', SparklesBackgroundImageId, '2aa9abd8-424f-4591-9939-e5e0953c5d3e.png', LushImage, PinkLovenseLogoImage, [ new Image('fbee1784-972a-44cf-9d78-96e8b3f89592.webp', 69, 69), new Image('0b8bc11d-8ae7-45c2-97d6-bbba9702e56f.webp', 69, 69) ], [ new Image('81f27db8-55d9-4034-997d-8076a383beaf.webp', 69, 69), new Image('ca96ae22-396c-4acd-a317-bde0da70392a.webp', 69, 69), new Image('000ee06b-e24b-49e0-9ecf-501411c46f21.webp', 69, 69), new Image('60f8a738-73df-4ce1-b760-fcc9a6d6c47b.webp', 69, 69), new Image('28f21f9d-13fe-4317-9a19-d612af8f6859.webp', 69, 69), new Image('a785e3c0-e579-4c6f-bb76-43e293a45003.webp', 69, 69), new Image('92934a1a-fac9-43e1-92c1-0a73a43ffe73.webp', 69, 69), new Image('de38149f-f6b3-465b-a30c-111301a49914.webp', 69, 69), new Image('42fb17f2-592a-4d4d-8396-9f0b418b2c82.webp', 69, 69), new Image('68a4334e-72b7-47a6-8217-a370fb589329.webp', 69, 69) ] ), 'DJ Hello Kitty': new Theme('', '', 'e056fbec-3e0d-4eb4-8265-ac7072a573d7.png', SparklesBackgroundImageId, '6598c60b-0dec-4df2-a7de-7794c7c1f5b7.png', LushImage, PinkLovenseLogoImage, [ new Image('3f3e1634-db79-4390-ad03-622c1009affb.webp', 69, 69) ], [ new Image('1a0861fb-7fde-4a1a-aa36-0f2c46bad805.webp', 69, 69), new Image('f0de9b9a-1cb3-4e18-bcbb-e1af13e0e427.webp', 69, 69), new Image('25088952-9434-4a8f-9791-b53ff1ce87dd.webp', 73, 69), new Image('0249afc0-a61a-4765-bd98-1cb245a1b500.webp', 69, 69), new Image('0799af7e-93cc-4cd9-a35f-6832c237062e.webp', 81, 69), new Image('b01fdd7a-b50e-4c9e-a56b-adb163cddf23.webp', 72, 69), new Image('e4159e06-fc10-44e7-8a83-a47c62d64cd0.webp', 71, 69), new Image('e618b97c-432d-4036-92d2-8987d5614470.webp', 69, 69), new Image('3f9e5f19-0b39-40d4-94d6-f7109eed0c56.webp', 69, 69), new Image('8b7e12d1-5701-4cb2-b6a3-db4f31e79d36.webp', 69, 69) ] ), 'Gloomy Bear': new Theme('', '', '6b88eccb-68c4-4107-ac9d-53f5e3e07da1.png', SnowBackgroundImageId, '6b88eccb-68c4-4107-ac9d-53f5e3e07da1.png', new Image('232570a8-20ed-4d67-855c-7c48d23bd74c.gif', 39, 39), ThankYouImage, [ new Image('0ff0e5e0-b115-4df9-be45-e2bd901c353d.gif', 79, 69) ], [ new Image('5a09daa9-40dc-47c4-892d-35ab37b76781.gif', 70, 69) ] ), 'Galactic Orb': new Theme('', '', 'e056fbec-3e0d-4eb4-8265-ac7072a573d7.png', SparklesBackgroundImageId, '6598c60b-0dec-4df2-a7de-7794c7c1f5b7.png', LushImage, PinkLovenseLogoImage, [ new Image('7c3d00c1-6c42-444e-b774-4498adbcbf1f.webp', 60, 69) ], [ new Image('7c3d00c1-6c42-444e-b774-4498adbcbf1f.webp', 60, 69) ] ), 'Harley Quinn': new Theme('', '', 'b4eaaf91-7ec2-424d-8ffc-be1c6742ece1.png', SparklesBackgroundImageId, '2aa9abd8-424f-4591-9939-e5e0953c5d3e.png', LushImage, PinkLovenseLogoImage, [ new Image('537d3c43-3319-48ed-80c0-fbe425c56fea.webp', 69, 69) ], [ new Image('dcdc15fc-192a-4b87-a000-a327ecdc3906.webp', 69, 69), new Image('81d81e55-0d72-418d-b6da-e20c5644ba20.webp', 69, 69), new Image('5e35546e-b845-44aa-903b-3d85ddf78564.webp', 69, 69), new Image('c43cbf5a-0f7e-4549-aaa5-e0d46d1df0b5.webp', 69, 69), new Image('000d5f0a-07f0-493f-87fc-52c9e3a814b9.webp', 69, 69), new Image('a7271cfd-1c88-44d1-8f35-1bba66744d33.webp', 69, 69), new Image('b1822122-58ee-45d8-b486-5e90d5600be2.webp', 69, 69), new Image('7f60eaf0-ea20-4f09-a4f0-e64fc5aa5b7b.webp', 69, 69), new Image('392d3de2-af9c-4148-b94c-5ab94ec5154d.webp', 69, 69), new Image('e8d7df54-7d9f-4145-881a-887c21940c90.webp', 69, 69) ] ), 'Milk, White Bear in Love': new Theme('', '', '6b88eccb-68c4-4107-ac9d-53f5e3e07da1.png', SparklesBackgroundImageId, '274b1d56-f70a-46cc-bf95-bc4c2d22da41.png', LushImage, PinkLovenseLogoImage, [ new Image('2bf60ef3-c8a7-4d59-a0a4-6bb6167e706f.webp', 70, 69) ], [ new Image('058cab86-047d-4a1d-8d10-7df164882b3f.webp', 62, 69), new Image('d8ee8585-7be3-42f4-83cb-3341c9b3d45c.webp', 72, 69), new Image('7bd82684-e9ab-47b6-b8eb-7ce648e5c3fb.webp', 66, 69), new Image('f252a0f1-4ef7-4bf1-adcb-09ac52465057.webp', 65, 69), new Image('3dd42bc8-e985-42d7-8cdc-c9a69d4f439d.webp', 75, 69), new Image('ed11b105-414f-4911-a87d-6c0e46693d43.webp', 77, 69), new Image('a9b1ee6a-945d-4e0e-8fc0-ad3bde6953fb.webp', 67, 69) ] ), 'Pikachu': new Theme('', '', 'b4eaaf91-7ec2-424d-8ffc-be1c6742ece1.png', SparklesBackgroundImageId, '2aa9abd8-424f-4591-9939-e5e0953c5d3e.png', LushImage, PinkLovenseLogoImage, [ new Image('74d0b8aa-ab18-4828-828d-8285cfb989dd.webp', 70, 69) ], [ new Image('7a4f620a-c403-4b02-82f1-7a739529cc28.webp', 69, 69), new Image('6d1e290e-f0d4-40d4-905a-74c8f41d2151.webp', 69, 69), new Image('b2d292e5-b141-401d-94ef-9392899493ba.webp', 69, 69), new Image('0df1c35a-0048-4702-a515-4b0f764c942a.webp', 69, 69), new Image('0565687a-7080-4b6e-a59f-4e54ed376e0b.webp', 69, 69), new Image('014a2793-6611-4684-acb2-ffbf32704dd3.webp', 69, 69), new Image('0e27511b-8ed6-42dc-965f-b887bf97e154.webp', 69, 69), new Image('96f15563-6999-4db0-98ec-2603a44bf2cd.webp', 69, 69) ] ), 'Pusheen the Cat': new Theme('', '', 'e52e2a10-5b96-412e-bf6b-a44b873bad0c.png', SparklesBackgroundImageId, 'c871ed69-e475-4c2b-8b30-8d5e4d2a637b.png', LushImage, PinkLovenseLogoImage, [ new Image('5e331616-ff8b-407c-a268-09787c597ed9.webp', 67, 69) ], [ new Image('fd30f813-9cc2-4097-85ef-d3742572023d.webp', 66, 69), new Image('07d48db5-8abb-401e-9d2d-8a79e13a4e3a.webp', 69, 69), new Image('cc9ae4d1-811a-4fd6-b432-c1c3303f8a26.webp', 71, 69), new Image('cd389dc6-016f-4aa1-83e7-4d1a5d8ac4eb.webp', 75, 69), new Image('1753407e-8540-4288-8a90-5d06b7f4312d.webp', 67, 69), new Image('a0714859-51ee-4568-b5ac-d2fa1608730a.webp', 68, 69), new Image('eeb50e9b-d3b3-4be8-ae5b-a01982b92163.webp', 75, 69) ] ), 'Qoobee the Dino': new Theme('', '', 'b4eaaf91-7ec2-424d-8ffc-be1c6742ece1.png', SparklesBackgroundImageId, '2aa9abd8-424f-4591-9939-e5e0953c5d3e.png', LushImage, PinkLovenseLogoImage, [ new Image('4d258b4c-7b88-4d75-be42-1aa3c4fc64dc.webp', 69, 69), new Image('3d893fbb-080f-4a41-96ba-0710ec9afcb2.webp', 69, 69), new Image('b3bf4a52-67b5-4466-a4d8-470e66f79a9c.webp', 69, 69) ], [ new Image('a301c5fa-3170-49ae-a3b2-810fcc37a8fd.webp', 70, 69), new Image('400a470a-065d-4d8d-8fa1-883ea064972a.webp', 69, 69), new Image('9558eac0-d9bd-4fe8-b8f3-7ebc643890da.webp', 69, 69), new Image('1b8e25ce-7c66-48a1-b088-72ba99625e75.webp', 67, 69), new Image('edf491f8-6cc0-4e8b-b4f7-95ed94cb83ca.webp', 69, 69), new Image('3feb26ef-10d8-4202-ada1-966f4d73c237.webp', 70, 69), new Image('3f5372ab-5d08-47ff-a3fd-a21d7c6da57a.webp', 70, 69), new Image('9b2c9221-462e-4e41-93e0-00605da1573d.webp', 66, 69), new Image('f9221c3c-9237-4cf6-ac18-a48e33e39bdd.webp', 67, 69), new Image('85766949-599f-403d-913d-9ce75085bdb0.webp', 69, 69) ] ), 'Rick (from Rick and Morty)': new Theme('', '', '59195b42-489a-49ff-b1d5-66d6359f9f21.png', SparklesBackgroundImageId, '17d043c0-49a8-414f-91f3-92de42f1036f.webp', LushImage, PinkLovenseLogoImage, [ new Image('616fa5f0-1234-4320-b10f-a59b9c0136a3.webp', 66, 69) ], [ new Image('6fabcc4b-6daf-42df-8831-5515348a08ba.webp', 51, 69), new Image('24e68c7d-383b-4785-8088-0f7e870d851c.webp', 67, 69), new Image('c5d48dbb-b971-49e9-adca-64c9424bf1f1.webp', 72, 69), new Image('6f44c67b-783c-4903-90ec-abf274458811.webp', 74, 69), new Image('2863e66f-789f-4ebd-87b1-90d53d174b4b.webp', 71, 69), new Image('20fd940b-613b-4a12-abff-ef8f68eb3743.webp', 69, 69), new Image('d200ebaf-a535-44a6-8db2-7360acbd6469.webp', 68, 69), new Image('e32dc1c4-55c2-4b90-8df1-926b229e3a4a.webp', 72, 69), new Image('406a1d55-ef74-49db-ab0b-e269caec1a01.webp', 70, 69) ] ), 'Santa Girl (Christmas!)': new Theme('', '', 'b0eae496-d2fb-4dc9-b8be-a911b86e10d5.png', SnowBackgroundImageId, '76399767-aff4-4b51-88e9-b19278fd1c93.png', LushImage, WhiteLovenseLogoImage, [ new Image('b1e47578-bd59-48cd-96a8-83f9f6f90996.webp', 69, 69) ], [ new Image('97930ecb-4dda-4ef9-8d98-42f288e041d3.webp', 69, 69), new Image('417b867c-d891-481f-9a86-c31a05546c7f.webp', 69, 69), new Image('da8aae4e-9f90-49cf-840a-dc824d27d02b.webp', 69, 69), new Image('40b181ff-268b-4c80-a240-f7486607220b.webp', 69, 69), new Image('3fbcf7f2-38ab-47ee-a31a-54a7afe68f60.webp', 69, 69), new Image('94a0ccf5-5400-4ed7-88a8-7fb1adb573ed.webp', 69, 69), new Image('9ea78e5b-ffb0-4313-9f77-2fe18684766c.webp', 69, 69), new Image('141937c0-2b4f-41bb-9e1c-30f9c2b91023.webp', 69, 69), new Image('a00f3c4f-73c8-45fa-ac4b-6d478fb52b92.webp', 69, 69) ] ), 'Spotty the Giraffe': new Theme('', '', '83567d1f-cc33-441c-92e2-b12dfe821fd2.png', SparklesBackgroundImageId, 'bbae2371-4b1a-4fc3-9e86-94fd2b0bea06.png', LushImage, PinkLovenseLogoImage, [ new Image('c2cb6541-aafc-4b86-a353-5a1d96cffdd8.webp', 69, 69), new Image('36a982d2-783b-4aa2-bc44-c17924de934b.webp', 69, 69), new Image('76f66adc-f50e-4699-8b0b-25b41396aeea.webp', 69, 69) ], [ new Image('ba42db44-0b14-4064-a8cf-db69d2024cab.webp', 69, 69), new Image('7c2a2550-5304-46d7-a1ad-a83a6288d30e.webp', 69, 69), new Image('d4796fed-45c4-4d04-ad78-39d2e0afb04b.webp', 69, 69), new Image('cb63faf4-290b-4d48-aadf-d430a79562f2.webp', 69, 69), new Image('8ade2569-eca8-4a4d-95d2-ebd0040f0fb7.webp', 69, 69), new Image('3bfb051f-326e-4012-9441-4198796ea1f0.webp', 69, 69) ] ), 'Spyro the Dragon': new Theme('', '', '59195b42-489a-49ff-b1d5-66d6359f9f21.png', SparklesBackgroundImageId, '17d043c0-49a8-414f-91f3-92de42f1036f.webp', LushImage, PinkLovenseLogoImage, [ new Image('54ed9bee-0d76-4740-bef4-ab052702d730.webp', 65, 69), new Image('756c25cf-c43d-462d-8b5e-b9e28d6030e8.webp', 62, 69) ], [ new Image('757fc76e-1b9e-4227-bc05-6a419077a3fd.webp', 65, 69) ] ), 'Unikitty': new Theme('White', 'White', '8ab34d67-8241-48d2-922d-e1f4cdc7886b.png', SparklesBackgroundImageId, null, new Image('2283034d-a9d3-4ab0-8385-be902c3bfd70.webp', 50, 46), PinkLovenseLogoImage, [ new Image('7096588e-60ee-467b-9171-96f2d0c6fd31.webp', 72, 69) ], [ new Image('fa3969ee-35ee-4f4e-9451-d338d178b0d1.webp', 69, 69), new Image('43abbaf9-b487-439d-9d13-060d532590d6.webp', 69, 69), new Image('0943a5f0-abfc-4bf3-92f0-a3cecc576d73.webp', 69, 69), new Image('332b00b1-d9c2-45ce-acc5-5510bf419fde.webp', 69, 69), new Image('81222967-8a1c-4056-a747-fc00b99656c5.webp', 69, 69) ] ), 'Vampire Girl': new Theme('White', 'rgb(249,40,128)', '4d8d7520-2697-4893-a3a7-c042396b75ca.webp', SparklesBackgroundImageId, null, LushImage, PinkLovenseLogoImage, [ new Image('c2ca61c7-2d87-4574-b828-a977f4f4991e.webp', 69, 69) ], [ new Image('d5b8dc8e-3625-4e35-a595-208ae889a20c.webp', 69, 69), new Image('5ac194ca-1dd1-4919-a576-d9ab4b7d6a7b.webp', 69, 69), new Image('108f8aa3-3eb6-451f-a879-1adc142ade0e.webp', 69, 69), new Image('f4211ff4-e0b5-4bc3-bb24-7d3056b8afdc.webp', 69, 69), new Image('08668d37-7dee-445f-9e54-f4551b29de2d.webp', 69, 69), new Image('59512822-1326-4dba-aff2-8b9aa1f96b64.webp', 69, 69), new Image('5fa20e8b-4bb6-426d-8727-fad341c052f3.webp', 69, 69), new Image('56012577-d0c3-4f19-8a18-16c1f0cbd854.webp', 69, 69), new Image('f7867ef4-07d2-4700-a669-9bf1c5ce684e.webp', 69, 69), new Image('65eb9231-9ad2-4cea-b7b5-17c23d5e8988.webp', 69, 69) ] ) }; //////////////////////////////////////// // Utility Functions ///////////////// //////////////////////////////////////// function commandMatchesOrStartsWith(str, command) { str = str.trim().toLowerCase(); return str === command || str.startsWith(command + ' '); } function matches(str, regex) { return !!str.match(regex); } function randomInt(min, max, inclusive) { return min + Math.floor(Math.random() * (max - min + (inclusive ? 1 : 0))); } function randomElement(array) { return array[randomInt(0, array.length, false)]; } // capitalize the leading character of each part of a username function capitalizeUsername(username) { return username .split('_') .map(part => { switch (part.length) { case 0: return part; case 1: return part.toUpperCase(); default: return part[0].toUpperCase() + part.substring(1); } }) .join('_'); } function log(message) { message = '[' + $app.name + '] ' + message; $room.sendNotice(message, {toUsername: $room.owner}); } //////////////////////////////////////// // Functions ///////////////////////// //////////////////////////////////////// function initKeyValueStore() { $kv.set(BroadcasterKey, $room.owner); $kv.set(LastTipKey, null); $kv.set(IsReactingToTipKey, false); if (!$kv.get(CustomThemesKey, null)) { $kv.set(CustomThemesKey, {}); } $kv.set(EnterNoticesQueueKey, []); $kv.set(LastEnterNoticeTimesKey, {}); } function loadSettings() { $kv.set(ThemeKey, Themes[String($settings.theme)]); if ($settings.reset_goals_on_app_restart) { $kv.set(GoalsKey, loadGoalsFromSettings()); resetGoals(); } else { let goalsFromKV = $kv.get(GoalsKey, null); let goalsFromSettings = loadGoalsFromSettings(); if (!goalsFromKV || areGoalsDifferent(goalsFromKV, goalsFromSettings)) { $kv.set(GoalsKey, goalsFromSettings); resetGoals(); } } storeSettings(); } function areGoalsDifferent(a, b) { if (a.length != b.length) { return true; } for (let i = 0; i < a.length; i++) { if (a[i].description !== b[i].description || a[i].amount !== b[i].amount) { return true; } } return false; } function reloadSettings() { let currentGoals = $kv.get(GoalsKey); let currentGoalIndex = $kv.get(CurrentGoalIndexKey); let currentSettings = $kv.get(SettingsKey); let tokensReceivedForCurrentGoal = $kv.get(TokensReceivedForCurrentGoalKey); let newGoals = loadGoalsFromSettings(); let resetGoals = false; let currentGoalDescriptionChanged = false; if (newGoals.length <= currentGoalIndex) { // less goals than where the progression is, so reset the progression resetGoals = true; if ($app.version === 'Testbed') { log('Less goals than where the current progression is, so reset the progression.'); } } else if (currentGoalIndex === currentGoals.length - 1 && tokensReceivedForCurrentGoal === currentGoals[currentGoalIndex].amount && newGoals.length > currentGoals.length ) { // new goals added after the last goal was completed, so reset the progression resetGoals = true; if ($app.version === 'Testbed') { log('New goals added after the last goal was completed, so reset the progression.'); } } else if (currentGoalIndex === currentGoals.length - 1 && tokensReceivedForCurrentGoal === currentGoals[currentGoalIndex].amount && newGoals[currentGoalIndex].description !== currentGoals[currentGoalIndex].description ) { // description of last goal changed after it was completed, so reset the progression resetGoals = true; if ($app.version === 'Testbed') { log('Description of the last goal changed after it was completed, so reset the progression.'); } } else { // check if any of the previous goals or the current goals changed amount and reset the progression for (let i = 0; i <= currentGoalIndex; i++) { if (newGoals[i].amount !== currentGoals[i].amount) { resetGoals = true; if ($app.version === 'Testbed') { log('A previous or the current goal changed amount, so reset the progression.'); } break; } } // check if the current goal description changed and update the room and load the panel if (newGoals[currentGoalIndex].description !== currentGoals[currentGoalIndex].description) { currentGoalDescriptionChanged = true; if ($app.version === 'Testbed') { log('Current goal description changed, so update the room subject.'); } } } let subsequentGoalsRemoved = currentGoals.length > newGoals.length && newGoals.length - 1 === currentGoalIndex; let roomSettingsChanged = false; if ($settings.tags !== currentSettings.tags || $settings.room_greeting !== currentSettings.room_greeting || $settings.change_subject_on_each_tip !== currentSettings.change_subject_on_each_tip ) { roomSettingsChanged = true; if ($app.version === 'Testbed') { log('Room settings changed, so update the room subject.'); } } // now we can mirror the logic from loadSettings() let themeChanged = $settings.theme !== currentSettings.theme; if (themeChanged) { $kv.set(ThemeKey, Themes[String($settings.theme)]); } $kv.set(GoalsKey, newGoals); storeSettings(); return new ReloadSettingsResults(themeChanged, resetGoals, currentGoalDescriptionChanged, subsequentGoalsRemoved, roomSettingsChanged); } function loadGoalsFromSettings() { let goals = []; for (let i = 1; i <= 10; i++) { let goal = parseGoal(String($settings['goal_' + i])); if (goal) { goals.push(goal); } } return goals; } function parseGoal(value) { let match = value.match(GoalRegex); return match ? new Goal(match[1].trim(), parseInt(match[2])) : null; } function getCurrentGoal() { return $kv.get(GoalsKey)[$kv.get(CurrentGoalIndexKey)]; } function getNextGoal() { return isMoreGoals() ? $kv.get(GoalsKey)[$kv.get(CurrentGoalIndexKey) + 1] : null; } function storeSettings() { $kv.set(SettingsKey, { theme: $settings.theme, goal_1: $settings.goal_1, goal_2: $settings.goal_2, goal_3: $settings.goal_3, goal_4: $settings.goal_4, goal_5: $settings.goal_5, goal_6: $settings.goal_6, goal_7: $settings.goal_7, goal_8: $settings.goal_8, goal_9: $settings.goal_9, goal_10: $settings.goal_10, tags: $settings.tags, room_greeting: $settings.room_greeting, change_subject_on_each_tip: $settings.change_subject_on_each_tip, show_private_is_open_message: $settings.show_private_is_open_message }); } function resetGoals() { $kv.set(CurrentGoalIndexKey, 0); $kv.set(TokensReceivedForCurrentGoalKey, 0); } function setGoal(newGoal) { // if a goal is running, replace it and start the new one if (isGoalRunning()) { startNewGoal(newGoal); // if no goal is running but there are more goals, replace the next goal } else if (isMoreGoals()) { startNewNextGoal(newGoal); // if all goals are completed, add the new goal and start it } else { startAdditionalNewGoal(newGoal); } } function isGoalRunning() { let currentGoal = getCurrentGoal(); let currentGoalTokens = $kv.get(TokensReceivedForCurrentGoalKey); return currentGoalTokens < currentGoal.amount; } function isMoreGoals() { let goals = $kv.get(GoalsKey); let currentGoalIndex = $kv.get(CurrentGoalIndexKey); return currentGoalIndex < goals.length - 1; } function getAttributions() { if (AppAuthor === AppMaintainer) { return capitalizeUsername(AppAuthor); } else { return capitalizeUsername(AppAuthor) + ' (author) and ' + capitalizeUsername(AppMaintainer) + ' (maintainer)'; } } function announceAppStarted() { $room.sendNotice($app.name + ' (v' + $app.version + ') has started.', { color: AdminColor }); $room.sendNotice($app.name + ' is an open-source app by ' + getAttributions() + '.' + '\nVisit https://chaturbate.com/smoker919/ to learn more about app safety on CB.', { color: AdminColor, bgColor: randomElement(PastelColors) } ); } function announceAppStopped() { $room.sendNotice($app.name + ' has stopped.', { color: AdminColor }); } function announceAppSettingsChanged() { $room.sendNotice($app.name + ' has changed settings.', { color: AdminColor }); } function startBroadcastPanelCallbacks() { $kv.set(ShowGoalInTopRowKey, true); $kv.set(ShowLastTipInBottomRowKey, true); let theme = $kv.get(ThemeKey); if (theme.animatedImages.length === 1) { $kv.set(CurrentAnimatedImageKey, theme.animatedImages[0]); } else { $kv.set(CurrentAnimatedImageKey, randomElement(theme.animatedImages)); $callback.create(SetNextAnimatedImageLabel, AnimatedImageDuration, true); } $room.reloadPanel() $callback.create(RotateTopRowViewLabel, GoalViewDuration); if ($settings.show_private_is_open_message) { $callback.create(RotateBottomRowViewLabel, LastTipViewDuration); } } function stopBroadcastPanelCallbacks() { $callback.cancel(SetNextAnimatedImageLabel); $callback.cancel(RotateTopRowViewLabel); $callback.cancel(RotateBottomRowViewLabel); } function startPersistentCallbacks() { $callback.create(EnterNoticesLabel, 1, true); } function stopPersistentCallbacks() { $callback.cancel(EnterNoticesLabel); } function sendEnterNotices() { let enterNoticesQueue = $kv.get(EnterNoticesQueueKey); let now = Date.now(); let lastIndex = enterNoticesQueue.findLastIndex(item => item[1] + EnterNoticesDelay <= now); if (lastIndex > -1) { let items = enterNoticesQueue.splice(0, lastIndex + 1); $kv.set(EnterNoticesQueueKey, enterNoticesQueue); let message = capitalizeUsername($room.owner) + ' is running ' + $app.name + ', an open-source app by ' + getAttributions() + '.' + '\nVisit https://chaturbate.com/smoker919/ to learn more about app safety on CB.'; let lastEnterNoticeTimes = $kv.get(LastEnterNoticeTimesKey); items.forEach(item => { let lastEnterNoticeTime = lastEnterNoticeTimes[item[0]]; if (!lastEnterNoticeTime || now - lastEnterNoticeTime >= MinEnterNoticeInterval) { $room.sendNotice(message, { toUsername: item[0], color: AdminColor, bgColor: randomElement(PastelColors) }); lastEnterNoticeTimes[item[0]] = now; } }); $kv.set(LastEnterNoticeTimesKey, lastEnterNoticeTimes); } } function getTopRowText() { return $kv.get(ShowGoalInTopRowKey) ? getGoalViewText() : '👸 Hi, my Lovense is active!'; } function getGoalViewText() { let currentGoal = getCurrentGoal(); return $kv.get(TokensReceivedForCurrentGoalKey) + ' / ' + currentGoal.amount + ': ' + maybeTruncateDescription(currentGoal.description); } function maybeTruncateDescription(description) { return description.length > MaxDescriptionLengthInGoalView ? description.substring(0, MaxDescriptionLengthInGoalView - 3) + '...' : description; } function getMiddleRowText() { let percent = 100.0 * $kv.get(TokensReceivedForCurrentGoalKey) / getCurrentGoal().amount; let greenSquaresCount = Math.floor(ProgressSquares * percent / 100.0); return greenSquaresCount < 1 ? '⬜️'.repeat(ProgressSquares) : $settings.progress_squares_color.repeat(greenSquaresCount) + '⬜️'.repeat(ProgressSquares - greenSquaresCount); } function getBottomRowText() { if (getCurrentGoal().amount - $kv.get(TokensReceivedForCurrentGoalKey) === 0) { // overrides the basic views return '🏅🏅 GOAL COMPLETED! 🏅🏅'; } else if ($kv.get(ShowLastTipInBottomRowKey)) { return getLastTipViewText(); } else { return 'Private is open!' + EnSpace + 'Click this link!' + EnSpace + '👉👉👉👉👉👉'; } } function getLastTipViewText() { let lastTip = $kv.get(LastTipKey); if (lastTip) { return 'Last Tip: ❤️ ' + (lastTip.isAnon ? AnonymousUser : lastTip.username) + ': ' + lastTip.tokens.toString().split('').map(i => KeycapNumbers[i]).join(''); } else { return 'Last Tip: ❌❌ (your name here)'; } } function updateRoomSubject(includeGoal = true) { let newSubject = ''; if (includeGoal) { let currentGoal = getCurrentGoal(); newSubject += 'GOAL: ' + currentGoal.description; if ($settings.change_subject_on_each_tip) { let tokensRemaining = currentGoal.amount - $kv.get(TokensReceivedForCurrentGoalKey); newSubject += ' [' + tokensRemaining + (tokensRemaining === 1 ? ' token' : ' tokens') + ' remaining]'; } } if ($settings.room_greeting) { if (newSubject) { newSubject += ' '; } newSubject += $settings.room_greeting; } if ($settings.tags) { if (newSubject) { newSubject += ' '; } newSubject += $settings.tags; } $room.setSubject(newSubject); } function announceGoalCompleted() { let message = GoalCompletedMessage.replace('[description]', getCurrentGoal().description); $room.sendNotice(message, { bgColor: randomElement(NoticeBackgrounds), fontWeight: Bold }); } function getTipReactionDuration(tokens) { // use the default Lovense levels if (tokens <= 9) { return 2; } else if (tokens <= 49) { return 5; } else if (tokens <= 99) { return 10; } else if (tokens <= 300) { return 15; } else { return 30; } } function handleTip(username, isAnon, tokens) { $callback.cancel(StopReactingToTipLabel); $kv.set(LastTipKey, new Tip(username, isAnon, tokens)); $kv.set(IsReactingToTipKey, true); $kv.set(CurrentTipReplacementImageKey, randomElement($kv.get(ThemeKey).tipReplacementAnimatedImages)); let currentGoal = getCurrentGoal(); let tokensReceivedForCurrentGoal = $kv.get(TokensReceivedForCurrentGoalKey); let tokensRemaining = currentGoal.amount - tokensReceivedForCurrentGoal; if (tokensRemaining > 0) { tokensReceivedForCurrentGoal = Math.min(tokensReceivedForCurrentGoal + tokens, currentGoal.amount); $kv.set(TokensReceivedForCurrentGoalKey, tokensReceivedForCurrentGoal); if ($settings.carry_over_tokens_and_start_subsequent_goals_right_away && tokensReceivedForCurrentGoal === currentGoal.amount) { tokens -= tokensRemaining; announceGoalCompleted(); while (isMoreGoals()) { startNextGoal(false); currentGoal = getCurrentGoal(); if (tokens >= currentGoal.amount) { $kv.set(TokensReceivedForCurrentGoalKey, currentGoal.amount); tokens -= currentGoal.amount; announceGoalCompleted(); } else { $kv.set(TokensReceivedForCurrentGoalKey, tokens); break; } } updateRoomSubject(); $room.reloadPanel(); } else { if ($settings.change_subject_on_each_tip) { updateRoomSubject(); } $room.reloadPanel(); if (tokensReceivedForCurrentGoal === currentGoal.amount) { announceGoalCompleted(); if (isMoreGoals()) { $callback.create(MovingToNextGoalLabel, MovingToNextGoalDelay); $callback.create(StartNextGoalLabl, NextGoalDelay); } } } } else { $room.reloadPanel(); } $callback.create(StopReactingToTipLabel, getTipReactionDuration(tokens)); } // callback functions function setNextAnimatedImage() { $kv.set(CurrentAnimatedImageKey, randomElement($kv.get(ThemeKey).animatedImages)); $room.reloadPanel(); } function rotateTopRowView() { let newValue = !$kv.get(ShowGoalInTopRowKey); $kv.set(ShowGoalInTopRowKey, newValue); $room.reloadPanel(); $callback.create(RotateTopRowViewLabel, newValue ? GoalViewDuration : LovenseViewDuration); } function rotateBottomRowView() { let newValue = !$kv.get(ShowLastTipInBottomRowKey); $kv.set(ShowLastTipInBottomRowKey, newValue); $room.reloadPanel(); $callback.create(RotateBottomRowViewLabel, newValue ? LastTipViewDuration : PrivateViewDuration); } function stopReactingToTip() { $kv.set(IsReactingToTipKey, false); $room.reloadPanel(); } function announceMovingToNextGoal() { let nextGoal = getNextGoal(); let message = MovingToNextGoalMessage .replace('[description]', nextGoal.description) .replace('[amount]', nextGoal.amount); $room.sendNotice(message, { toUsername: $room.owner, bgColor: randomElement(NoticeBackgrounds), fontWeight: Bold }); } function startNextGoal(update = true) { $kv.incr(CurrentGoalIndexKey); $kv.set(TokensReceivedForCurrentGoalKey, 0); if (update) { updateRoomSubject(); $room.reloadPanel(); let currentGoal = getCurrentGoal(); let message = NextGoalSetMessage .replace('[description]', currentGoal.description) .replace('[amount]', currentGoal.amount); $room.sendNotice(message, { bgColor: randomElement(NoticeBackgrounds), fontWeight: Bold }); } } function startNewGoal(newGoal) { replaceCurrentGoal(newGoal); $kv.set(TokensReceivedForCurrentGoalKey, 0); updateRoomSubject(); $room.reloadPanel(); let message = NewGoalSetMessage .replace('[description]', newGoal.description) .replace('[amount]', newGoal.amount); $room.sendNotice(message, { bgColor: randomElement(NoticeBackgrounds), fontWeight: Bold }); } function startNewNextGoal(newGoal) { $callback.cancel(MovingToNextGoalLabel); $callback.cancel(StartNextGoalLabl); replaceNextGoal(newGoal); $kv.incr(CurrentGoalIndexKey); $kv.set(TokensReceivedForCurrentGoalKey, 0); updateRoomSubject(); $room.reloadPanel(); let message = NewGoalSetMessage .replace('[description]', newGoal.description) .replace('[amount]', newGoal.amount); $room.sendNotice(message, { bgColor: randomElement(NoticeBackgrounds), fontWeight: Bold }); } function startAdditionalNewGoal(newGoal) { addGoal(newGoal); $kv.incr(CurrentGoalIndexKey); $kv.set(TokensReceivedForCurrentGoalKey, 0); updateRoomSubject(); $room.reloadPanel(); let message = NewGoalSetMessage .replace('[description]', newGoal.description) .replace('[amount]', newGoal.amount); $room.sendNotice(message, { bgColor: randomElement(NoticeBackgrounds), fontWeight: Bold }); } function replaceCurrentGoal(newGoal) { let goals = $kv.get(GoalsKey); goals[$kv.get(CurrentGoalIndexKey)] = newGoal; $kv.set(GoalsKey, goals); } function replaceNextGoal(newGoal) { let goals = $kv.get(GoalsKey); goals[$kv.get(CurrentGoalIndexKey) + 1] = newGoal; $kv.set(GoalsKey, goals); } function addGoal(newGoal) { let goals = $kv.get(GoalsKey); goals.push(newGoal); $kv.set(GoalsKey, goals); }
© Copyright Chaturbate 2011- 2025. All Rights Reserved.