Apps Home
|
Create an App
BellasTipDice
Author:
owllovesbella
Description
Source Code
Launch App
Current Users
Created by:
Owllovesbella
G = { dice: 3, name: 'BellasTipDice', ab: 'app', od: 'schruti'} // 2020-03-27T05:06:11+00:00 BellasTipDice specific values G.pic = [ "a95fef7b-489d-4335-b4f5-91c9fe3f18f8", "6f9ea307-8e02-4b76-ae92-991a4dc70ebc", "9d650763-c280-4e65-9edd-09daa623ad4c", "72c60029-88d9-48c0-b334-5c714c426881", "4b53bb0f-6f52-449d-ab81-7d5a7837d501", "dcb3c530-52d4-4d59-b44f-610396311c9c", "406b62ef-bbd4-46c7-a39b-d3724be725a9", "a0b4d977-6d30-4b50-b314-e0d4f274045d", "281e7106-a66c-4e8f-bc07-0c21b212a083", "40945f3b-ad2d-40fc-b843-1247a559370b", "4f687b5a-f9e7-4fc8-ab03-65a26bb8b24b", "808bb382-59d5-4573-9de4-b331873b288d", ] C = {} // custom configurations for selected slugs C._ = { dice: [ "Strip Dance", "Strip to topless", "Flash Ass", "Flash Back", "Flash Belly", "Flash Shoulders", "Flash Neck", "Lick Lips", "Blow Kiss", "Turn around", "Show Legs", "Dance", "Remove 1 Garment 3 min", "Hand bra 3 min", "Topless 3 min", "Naked 3 min", ], }; C.naked = { dice: [ "Strip Dance", "Strip to topless", "Flash Pussy", "Flash Tits", "Flash Belly", "Flash Back", "Flash Neck", "lick Lips", "Lick Finger", "Show Legs", "Dance", "Remove 1 Garment 3 min", "Hand bra 3 min", "Hair bra 3 min", "Topless 3 min", "Naked 3 min", ], }; C.shy = { dice: [ "Naked Ass", "Naked Back", "Show Panties", "Show Legs", "Show Shoulder", "Lick Lips", "Show Tongue", "Meow", "Say Thank you", "Your name in Air", "Stand up", "Show Neck", "Show Feet", "Show Bra", "Ass in Panties", "Hand Bra", ], }; C._sweety_kitty_ = { tokens: "9", style: "@tulip", menu: [ "", "3 Blow Kiss", "5 PM", "", "7 Song Request", "10 Stand up", "11 Turn around", "", "13 Show Tongue", "15 Lick Finger", "", "20 Show Feet", "25 If u like me", "", "30 Dance", "35 Massage Boobs in clothes", "", "45 Shake Ass", "50 Doggy Style", "", "99 Allow recording in pvt", "100 If u love me", "", "150 Access on Snapchat", "500 Make my Day", "700 Give me a Day off", ], dice: [ "Strip Dance", "Show Panties", "Show Boobs in Bra", "Show Belly", "Show Feet", "Show Neck", "Show Tongue", "Blow Kisses", "Lick Lips", "Lick Finger", "Whisper your Name", "Turn around", "Dance", "Doggy style", "Remove 1 Garment 3 min", "Hand Bra 3 min", ], }; C.fridaminatty = { style: "tail.f=9370DB @sakura", lush_levels: "", tokens: "19", blab: 3, lump: 5, papi: 8, grey: 1, lord: 0, lush: 0, room_title: "#beauty #natural #cute #tease #sexy #dance #smile #nude #c2c #young #skinny #party", menu: [ "7 pm", "", "10 blow kiss and hug you", "", "20 show feet", "", "26 request for song", "30 slap my ass", "35 stay in underwear for 4 min", "38 for squats", "", "49 c2c", "51 for squats with panty", "52 dance for you", "", "66 dance in underwear", "", "77 just thanks to me", "80 bra off + t - shirt on", "", "88 flash ass", "99 flash boobs", "", "", "", "250 tk instagram", "333 take your heart", "", "555 nude body with water", "666 nude body with oil", "777 take your soul"], dice: [ "show panties", "show bra", "slap my ass", "show tongue", "dance for you", "tease", "hand bra", "stay in underwear for 2 min", "10 squats", "show outfit", "stay in bra for 2 min", "stay in panties for 2 min", "10 squats with panty", "", "", "", ], }; C.beverlysun = { tokens: 18, style: "@cherry", lush: 3, lush_levels: "1+ 3s (Low vibrations), 15+ 6s (High vibrations), 17+ 10s (Medium vibrations), 500+ 1M (High vibrations), 1000+ 3M (High vibrations)", room_title: "#naked #new #pvt #feet #natural #young #pussy #tattoos #anal #teen #dildo #girl #lovense #talk #shy #ass #squirt #pvt #strip #feet #smoke #c2c #russian #18 #blonde", menu: [ "3 blow kiss", "5 PM", "", "10 smoke", "15 if you like me", "", "20 show feet", "", "30 c2c", "", "50 show armpits", "", "80 write your name on body", "90 show ass in clothes", "100 take off bra under clothes", "110 show boobs", "120 show underwear", "130 show ass", "", "150 dildo game", "170 1 hour in an erotic costume", "", "300 lovens control", "", "400 fulfill your wish", "", "1000 show with a password-10min", "", "1999 anal", "", ".r. your tips make me horny |.r. :milafunclub1 Get FREE access to PM, Videos and Pictures |.r. :takeherprivate44 |.r. :makemecum01 |.r. :moretipsfun", ".r. let's have some fun |.r. :bossfollow |.r. :hhelphercum |.r. :aqdontbeshy |.r. :_tip03 |.r. :appreciatel4", ".r. buy my photos |.r. :enjoyT |.r. :bbwait :hourglass |.r. :aqsendvib |.r. :Lovense Lush Give me pleasure with your tips!", ".r. i love my tippers |.r. :lovetip |.r. :bbsohot |.r. :tiprequestb |.r. :midsat", ], dice: [ "hair bra", "show ass", "show back", "show belly", "show feet", "show shoulders", "show neck", "blow kiss", "lick lips", "shake hair", "turn around", "5 squats", "show bra", "show panties", "hand bra", "hand panties", ], }; C.asianangel01 = { style: "@water", tokens: "66:250", room_title: "#new #asian #shaved #18 #teen #naked #smalltits #cum #slim #dress #longhair #blackhair #pvt #lovense #cute #young", dice: [ "dance, you choose song", "dance", "flash, you choose:", "flash pussy", "flash boobs", "flash butt", "camel toe", "spank butt", "spank butt both hands", "pussy close up", "spank pussy", ], menu: [ "", "15 PM", "", "", "33 Show Feet", "", "35 Spank Ass", "", "", "", "88 Flash Ass", "", "99 Flash Tits", "", "111 Flash Pussy", "", "123 C2C", "", "", "", "555 Get Naked", "", "", "999 Pussy Play", "", "1111 APP", "", "", "2222 Toy Pussy Play", "", "3333 CUM SHOW", ], }; C.mia_sweety18 = { style: "@cherry", room_title: "My first stream (^_^) #new #young #18 #barelylegal #teen #roleplay #smalltits #bigass #tease #daddy #petite #cum #joi #mistress #bdsm #cum #nylons", dice: [ "Strip Dance", "Strip to topless", "Topless 3 min", "Flash Tits", "Flash Belly", "Flash Shoulder", "Flash Back", "Flash Neck", "Blow Kiss", "Lick Lips", "Show Legs", "Dance", "Remove 1 Garment 3 min", "Hand bra 3 min", "Hand panties 3 min", "Naked 3 min", ], menu: [ "", "5 PM", "7 Blow Kiss", "", "14 Turn around", "16 Flash Legs", // "18 Show Armpits", "20 Comb Hair", "22 3 Squats", "25 Belly Dance", "30 Dance with Hand Bra", "35 10 Topless Squats", "40 Spanks", "45 Topless Dance", "50 Hand Bra 5 min", "60 Pillow Dance", "80 Strip Dance", "", "100 If you love me", "", "200 Unlock Private", "", "... Welcome to private after F352; 200 ėc; Unlock Private! Details are best discussed in F352; 5 ėc; PM", "", ".g. 60 Remove Stockings | .g. 60 Teasing Dance | .g. 60 Remove Shorts | .g. 60 Sexy Dance | .g. 60 Remove Bra | .g. 60 Shake Blanket | .g. 60 Sexy Dance | .g. 60 Remove Panties", ".g. 90 Pillow Dance | .g. 60 Shake Blanket | .g. 120 Strip Dance | .g. 120 Dance naked on Floor | .g. 120 Dance naked on Sofa", ".g. 150 Lotion Back | .g. 150 Lotion Feet | .g. 200 Lotion Tits", "", ], }; C.quartzrose = { style: "@rose", lush: 0, room_title: "#shhh #redhead #petite #curvy #natural #bush #tits #bigboobs #bigtits #pussy #pvt #hairypussy #office #feet #footjob #titsjob #blowjob #toys #ass #daddysgirl #schoolgirl #c2c #roleplay #teen", menu: [ "", "", "15 Blow kiss", "", "23 c2c", "25 Show Feet", "", "28 Take off stockings", "29 Take on stockings", "30 Bare soles", "", "33 Ponytail", "35 Show legs", "", "50 Show ass", "55 Spank ass", "", "60 My sweet smile", "66 Suck finger", "", "70 Show body", "75 Play with tits", "", "90 To say your name", "", "101 If you love me", "123 If you love me a lot", "", "", "100000 Perfect dream", "", "... All actions are in clothes, except 28-30 and private shows.", ], dice: [ "Naked Back", "Show Panties", "Show Legs", "Show Belly", "Show Feet", "Lick Lips", "Show Tongue", "Meow", "Blow Kiss", "Your name in Air", "Stand up", "Show Neck", "Show Feet", "Show Bra", "Show Panties", "Hand Bra", ], }; C.april_with_bun = { style: "@cherry", lush_levels: "", tokens: "9", blab: 3, lump: 5, papi: 3, grey: 1, lush: 0, room_title: "#blonde #new #teen #cute #tits #c2c #longhair #stockings #young #smalltits #strip #dance #petite #natural #boobs #private #pvt #naked #cum #toys #cumshow #dildo #ass #joi #slim #smallboobs #skinny", menu: [ "", "5 PM", "7 Blow Kiss", "", "14 Stand up for You", "16 Flash Legs", "18 Show Armpits", "// 20 Comb Hair", "20 Say your Name", "22 3 Squats", "23 Send me Flowers", "25 Belly Dance", "30 Read a Poem", "35 Dance with Hand Bra", "40 Spanks", "45 Topless Dance", "50 Sing a Song", "55 Dance of the Ass", "60 Hand Bra 5 min", "", "100 If you love me", "", "200 Unlock Private", "", "... Welcome to private after unlocked! Details are best discussed in F352; 5 PM", "", ".g. 60 Remove Stockings | .g. 60 Teasing Dance | .g. 60 Remove Shorts | .g. 60 Sexy Dance | .g. 60 Remove Bra | .g. 60 Shake Blanket | .g. 60 Sexy Dance | .g. 60 Remove Panties", ".g. 90 Pillow Dance | .g. 60 Shake Blanket | .g. 120 Strip Dance | .g. 120 Dance naked on Floor | .g. 120 Dance naked on Sofa", ".g. 150 Lotion Back | .g. 150 Lotion Feet | .g. 200 Lotion Tits", "", ], dice: [ "Caress naked Tits", "Hand Panties 3 min", "Hand Bra 3 min", "Flash Belly", "Flash Neck", "Your Name in Air", "Blow Kiss", "Show Tongue", "Turn around", "Flash Feet", "Flash Legs", "Shake Blanket", "Shake Hips", "Shake naked Ass", "Strip to topless", "Strip Dance", ], }; C.beckypickle = { tokens: "9", style: "@tulip", room_title: "#18 #feet #new #teen #smalltits #young #smoke #daddy #schoolgirl #c2c #petite #naked #strip #dance #topless #slim #brunette", menu: [ "3 PM", "5 Blow Kiss", "7 Song Request", "11 Turn around", "13 Flash Feet", "20 Dance", "25 Shake Ass", "30 Shake Tits", "50 Striptease", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ], dice: [ "Naked 5 min", "Flash Pussy", "Flash Tits", "Flash Ass", "Flash Neck", "Flash Ears", "Flash Tongue", "Blow Kiss", "Whisper your Name", "Your Name in Air", "Turn around", "Dance", "Remove 1 Garment 3 min", "Hand Bra 3 min", "Topless 3 min", "Strip Dance", ], }; C.leonamoor = { pmtk: 2, lush: 3, lush_levels: "1+ 2s Low, 24+ 4s Medium, 100+ 8s Medium, 500+ 12s High, 1000+ 20s Ultra-High", room_title: "ૌ #lovense #c2c #pantyhose #cum #pvt #18 #feet #new #young #daddy #smoke #smalltits #skinny #cute #naked #strip #tease #dance #schoolgirl #natural #stocking #ohmibod", tokens: "9", style: "menu.s=F430; note.f=000 tout.f=000 @rose", menu: [ "3 You choose music", "5 PM", "7 Blow kiss", "8 Smile", "10 Brew tea", "13 Suck fingers", "15 Lipstick", "", "20 If you like me", "25 Turn around", "30 Flash feet", "", "40 Show ass", "44 Spank ass", "49 Hand bra", "55 Remove 1 garment", "59 Show boobs", "", "", "77 Play with nipples", "85 Topless", "90 Smoke", "100 Strip dance", "133 Suck dildo", "155 Show pussy", "189 Finger in pussy", "202 Oil boobs", "", "333 Naked", "450 Tear tights on yourself", "666 Your name on my boobs", "1111 Masturbation dildo", "", ".r. The sounds of a tip turn me on ;)) :lovense8", ".r. And a pink toy inside me turns me on even more :lovense10", ".r. You know what I mean..Make me feel good! :baberwatch", ], dice: [ "Topless", "Hair bra", "Shake ass", "Flash bra", "Spank ass", "Suck fingers", "Flash tongue", "Blow kiss", "Smile", "Turn around", "Remove 1 garment 1 min", "Flash ass", "Squat 5 times", "Flash feet", "Hand bra", "Strip dance", ], }; C.sakuramey = { tokens: 28, pmtk: 2, lush: 3, lush_levels: "1+1s Medium, 15+5s High, 100+20s Ultra-High, 500+1m Ultra-High, 1000+2m Ultra-High", room_title: "#asian #18 #teen #naked #strip #tease #dance #feet #young #skinny #cum #lush #ass #lovense #slim #new #squirt #pussy #anal #pvt #orgasm #boobs #dildo #natural #c2c #smalltits", menu: [ "11 Get up", "15 Like Me", "", "20 Smile", "22 Show Tongue", "25 Hot Kiss", "", "42 Sexy Dance", "55 Show Feet", "66 Order Music", "77 Ahegao Face", "", "89 Show Tits", "111 Show Pussy", "121 Flash Ass Doggy", "123 27 seconds Ultra High Vibrations", "", "333 Finger Pussy", "377 Suck Dildo", "", "666 Dildo Pussy", "999 Whats App", "1001 If You Really Love Me", "1111 Lovens to Asshole", "1234 Dildo Anal", "", "5555 My Dream", "... :milafunclub1 Get FREE access to PM, Videos and Pictures", ".r. :lovense4 Give me pleasure with the sound of your tips!!!", ".r. :lovense8 Toy that vibrates with your Tips, Give me pleasure and make me wet", ".r. :lovense10 Make my WET with the SOUND of your TIPS!!! :lovenselush", ".s. :appreciatel4 | .s. :_tip03 | .s. :moretipsfun", ".s. :bbsexy | .s. :keepgoingtip | .s. :support4", ".s. :bbrequest | .s. :playboysexy | .s. :makehercum1", ".s. :amazingmodel_16 | .s. :roger-follow", ], dice: [ "Strip Dance", "Spank Pussy", "Spank Ass 3x", "Flash Tits", "Flash Shoulder", "Flash Neck", "Flash Tongue ", "Lick Lips", "Suck Finger", "Blow Kiss", "Ride Pillow", "Slow Dance", "Dance with Hand Bra", "Dance with Hair Bra", "Naked Doggy", "Play Pussy", ], }; C.xx_berna_stein_ = { }; C.emma_alone = { tokens: 9, blab: 3, lump: 5, papi: 0, grey: 1, lord: 0, lush: 3, style: "@rose", lush_levels: "1+1sLow, 13+10sMedium, 50+30sHigh, 150+1mUltra", room_title: "#browneyes #slim #dance #strip #lovense #cum #spank #pussy #ass #cute #sexy #tits #naked #natural #toys #brunette #dildo #goal #joi #flex #muscles #topless #trimmed #gym #athletic #smalltits #finger #ticket #tipmenu #dice", menu: [ "5 Blow Kiss", "10 PM", "16 Flash Muscles", "23 Dance", "25 Suck Finger", "28 Spank Ass", "30 Next Task topless", "32 10 Squats", "34 Doggy Pose", "35 Spread Legs", "", "40 Nibble Nipples", "45 Spit on Tits", "50 Knead Tits", "", "60 Next Task naked", "75 Spank Tits", "90 If you love me", "100 Spread Pussy", "110 Spread Ass", "150 Unlock Private Show", "250 Finger in Pussy", "300 Masturbate", "", "... I'm topless/naked in any task if you tip 30/60 first. Unlock Private for 150 tokens. Add 30/60/90 for topless/naked/play. For details use F339; 10 ėc; PM", "", ".g. 80 Strip Dance to Panties and open Bra", ".g. 70 Dump the Top | .g. 70 10 topless Squats", // ".g. 80 Hand Bra 5 Minutes", ".g. 70 Dump the Shorts | .g. 70 10 Push Ups without Panties", ".g. 80 Spank Ass", // ".g. 80 Topless 5 Minutes", ".g. 120 Knead Tits", ".g. 120 Dance naked", ".g. 250 Masturbate", ], dice: [ "Strip Dance", "Knead Tits", "Caress Tits", "Hand Bra 5 Mintues", "Flash Abs", "Flash Neck", "Flash Feet", "Flash Muscles", "Blow Kiss", "Lick Lips", "Lick Finger", "Shake Hips", "Flash Ass", "Spank Ass", "Doggy Pose", "Spread Pussy", ], }; C.berry_lou = { tokens: "21", style: "@cherry", room_title: "help me buy lush ૌૌૌ #boobs #topless #shy #private #18 #teen #feet #new #bigboobs #blond #c2c#cute #natural #feet #teen #18 #young", menu: [ "5 Pm", "7 Blow Kiss", "", "15 feet", "", "18 song request", "21 roll dice", "", "30 show clothes today", "", "35 if you like me", "", "45 open your cam/room", "", "55 stare into your eyes", "", "65 smoke", "80 slap myself", "", "", "100 undress to underwear (5min)", "", "", "333 make my day", "", "400 my social media", ], dice: [ "Undress to underwear", "Show panties", "Take off t-shirt", "Show bra", "Show clothes today", "Show legs", "Wink", - "Lick lips", - "Blow kiss", "Show tongue", "Show feet", "Show neck", "Show shoulder", "Slap my ass", "Take off skirt", "Show ass in panties", ], }; C.ayajanae = { dice: [ "strip dance", "strip to topless", "flash ass", "flash back", "flash belly", "flash neck", "blow kiss", "turn around", "show feet", "show legs", "dance", "show bra", "show panties", "hand bra 3 min", "topless 5 min", "naked 5 min", ], }; C.alice_akemi = { tokens: 15, lump: 2, style: "@fire", room_title: "#18 #teen #new #smalltits #daddy #young #skinny #deepthroat #pvt #petite #cute #daddysgirl #slave #pantyhose #private #c2c #dildo #sissy #cum #cute #pussy #roleplay #tattoo #shy #slim #horny #blowjob", menu: [ "", "12 Spank tits", "13 Spank ass 10x", "14 Spit on tits", "16 Spank pussy", "", "", "23 Show feet", "25 Play with naked nipples", "", "30 Flash ass", "31 Flash pussy", "33 Continue what's happening", "", "43 2 fingers in pussy", "53 Ball gag 5 mins", "63 Suck dildo", "", "93 Fuck dildo 5 mins", "", "", "123 Naked Doggie", "133 Ride a dildo", "", "153 Anal tail ( MeoW! )", "", "193 Sloppy deepthroat blowjob", "", "233 Snapchat with nude ♥", "253 Ball gag + fuck yourself", "", "333 Little gift for a little princess ♥♥♥", "", "2000 Go to bed little girl ♥", ], dice: [ "flash pussy", "flash boobs", "flash ass", "suck dildo", "ball gag 2 mins", "shake tits", "blow air kiss", "show neck", "show tongue", "show feet", "blow job", "spank ass", "spank pussy", "flash (you choose what)", "fuck dildo 5 mins", "finger pussy 2 mins", ], }; C.tina_joness = { tokens: 25, style: "@rose", // "menu.s=F49A; menu.f=E02818 dice.f=009088 goal.f=404040", menu: [ "5 PM", "15 Show clothes today", "20 Say something with my voice", "30 Rate your cock", "45 Feet teasing", "70 Armpit tease", "80 Heart-to-heart conversation", "100 Become my slave", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ], dice: [ "Flash Tits", "Take off top", "Lick lips", "Show Heels", "Show legs", "Show neck", "Show clothes today", "Wink", "Blow kiss", "Show tongue", "Show feet", "Dance", "Lick finger", "Show nails", "Show shoulder", "Show ass", ], }; C.shyadalin = { tokens: 0, style: "lush.s=\\nF34C; @fire", room_title: "play pussy with dildo and cum #lovense #ohmibod #interactivetoy #squirt #anal #latina #teen #new #bigass #young #c2c #cum #smalltits #natural #dildo #stockings #ass #cute #pussy #horny #hairy #naked #asian", lush_levels: "1+1s Low, 15+4s Medium, 100+6s Medium, 500+8s High, 1000+10s Ultra-High", menu: [ "20 pm", "44 play tits", "99 play pussy", "150 naked", "333 dildo in pussy 5 min", "222 suck dildo", ], }; C.cherry_may = { tokens: "12", style: "@cherry", // or try @sakura, @hibiscus, @cherry, @rose, @tulip, @fire, @clover room_title: "Hi! I have free day and we can have fun together in pvt) #boobs #ass #pvt #c2c #new #teen #young #18 #smoke #petite #feet #blonde #greeneyes #tits #longhair #natural #cute #sexy #sweet #girl", menu: [ "3 PM", "7 Blow Kiss", "", "10 Stand up", "16 Show clothes today", "", "20 Say something with my voice", "25 Smoke", "", "30 Rate your cock", "35 Twerk", "40 Open your cam/room", "", "45 Show feet", "50 Show ass", "55 I tell u about my dirty most fantasy", "", "60 Slap my self", "65 Show ass in doggy style", "", "70 Heart-to heart conversation", "", "80 Become my slave", "", "99 Take off top", "", "200 Undress me down to underwear", "", "444 Make me happy", "1000 Phone number"], dice: [ "Hand bra", "Show bra", "Show panties", "Shake ass", "Show clothes today", "Lick fingers", "Show legs", "Wink", "Show tongue", "Blos kiss", "Show shoulder", "Show feet", "Dance", "Gently slap ass", "Slap ass until red", "Show naked back", ], }; C.li_liliana = { tokens: 11, room_title: "tip to help my cat please, because he worth it, nya!=^-^= #brunette #student #pantyhose #dance #skirt #dress #heels #teen #young #shy #c2c #home #pvt #feet #fetish #legs #sweet #fun #tease #longhair #nails #conversation", style: "menu.s=F335; menu.f=0000FF dice.f=cc3399 @hibiscus", menu: [ "4 PM", "5 Stand Up", "6 Blow Kiss", "7 Smile", "8 Drink Water", "9 Meow", "10 Lick Lips", "12 Show Tongue", "13 'The Ring'", "14 Snap Fingers", "15 If You Like Me", "", "18 Play Eyebrows", "19 Play Eyes", "20 Show Middle Finger", "22 Stare at Cam", "", "30 Hold My Breath (30 sec)", "35 Flash Feet", "40 Eat Chili", "55 Squats (5 times)", "80 Running Around The Room", "50 Change Song", "66 TITTS", "70 Wear Heels", "90 Feet Dance", "", "250 Feet Dick", "300 Scream" ], dice: [ "Flash Feet", "Flash Ears", "Heart", "Play Eyebrows", "Show Tongue", "Blow a Kiss", "Grimace", "Wink", "Smile", "TITTS", "Shake Head", "Lick Lips", "Meow", "Drink Water", "Flash Belly", "Naked Shoulder!!o0o" ], }; C.sexy_foxy4u = { tokens: "0", room_title: "#new #submissive #atm #spank #play #pussy #show #smoking #fuckass #bdsm #torture #nolimits", style: "menu.s=F56F; menu.f=b21 tout.w=bold", menu: [ "5 PM", "", "15 Blow Kiss", "22 Flash Pussy", "25 Lick Finger", "", "37 Smoke", "40 Oil Tits", "", "44 Nipple Clamps", "46 4 Fingers in Pussy", "49 Hot Wax on Tits", "50 2 Finger in Ass", "52 Flash Feet", "53 6 Fingers in Pussy", "55 Slap Legs", "57 Lick Feet", "60 Spank Face", "65 Deep Throat", "66 Slobber 3 min", "69 Spank Ass", "70 Spank Tits", "", "90 ATM", "99 8 Fingers in Pussy", "100 Hot Wax on Tits and Pussy", "", "150 Slap Pussy", "200 5 Finger in Ass", "", "500 Get an orgasm" ], dice: [ "Naked 5 min", "Flash Pussy", "Flash Tits", "Flash Ass", "Flash Legs", "Shake Ass", "Dance", "Blow Kiss", "Lick Lips", "Turn around", "Your Name in Air", "Remove 1 Garment 3 min", "Hand Bra 3 min", "Topless 3 min", "You chose Dice Result", "Strip Dance", ], }; C.miss_dark = { tokens: "10", room_title: "#teen #young #ass #tits #tipmenu #fingerpussy #analplay #daddy #pvt", menu: [ "2 if u like me", "", "5 blow Kiss", "6 stand up and turn around", "", "15 Flash feet", "", "", "25 slap ass/tits", "30 flash ass", "", "33 flash tits", "35 Pinch Nipples", "", "43 doggy naked", "45pussy close up", "", "50 naked dance in public", "", "", "125 telegram snap whatsapp or insta", "150 naked fingering 10 min(public)", "", "", "297 anal pvt", "360 anal(public)"], dice: [ "Striptease", "Pinch Nipples", "Doggy in panties", "Shake Tits", "Shake Ass", "Flash feet", "Blow Kiss", "Smile", "Dance", "Turn around", "Flash Panties", "Flash Belly", "Flash Tits", "suck fingers", "Topless 5 min", "Naked dancing 5 min", ], }; C.malena_mi = { tokens: "15", room_title: "#beauty #c2c #bigboobs #blonde #pvt #tits #curvy #boobs", style: "@hibiscus menu.s=F49A; menu.f=E02818 dice.f=009088 goal.f=404040", menu: [ "", "7 if you love me", "", "11 send a PM", "", "21 say something with my voice", "23 feet teasing", "", "31 rate your cock", "33 slap myself", "41 i tell u about my most dirty fantasy", "", "81 show ass in panties", "91 5 min C2C", "", "474 make my day", "", "4747 my number" ], dice: [ "Flash Tits", "Show ass", "Show panties", "Show tongue", "Show bra", "Blow Kiss", "Wink", "Play with hair", "Smile", "Show clothes today", "Show Legs", "Show nails", "Lick finger", "Show feet", "Slap on ass", "Hand bra", ], }; C.funnymagic = { // she wants glitter, the fat rabbit and snow flakes of Lovense-Me tokens: "13", style: "lush.s=\\nF48B;~Level~\\i: menu.s=f5a4; menu.f=ff2058 dice.f=00b080 @cherry", // style: "lush.s=\\nF48B;: menu.s=f5a4; menu.f=ff2058 dice.f=00b080 @cherry", lush_levels: "1+ 1s Low, 15+ 4s Medium, 100+ 6s Medium, 500+ 8s High, 1000+ 10s Ultra-High", room_title: "#lovense #skirt #lush #18 #teen #schoolgirl #c2c #daddy #feet #natural #daddysgirl #bigboobs #shy #young #ohmibod", menu: [ "5 PM", "", "9 kisses for u", "10 outfit ^-^", "", "14 JUMPING ON THE BED", "15 feet time", "", "20 Flash bra", "", "25 thumbs up <3", "", "30 Show Ass in Panties", "", "", "50 underwear for 5 mins", "", "", "", "111 LUSH CONTROL", "", "", "666 devilish seduction" ], dice: [ "Hand Bra 3 min", "Show Panties", "Show Tongue", "Say you name", "Dance", "Smile", "Flash Tongue", "Blow Kiss", "Flash Bra", "Show panties", "Turn around", "Show Feet", "Show Legs", "Show Belly", "Blow kiss", "Naked behind the chair", ], }; C.leonehoney = { tokens: "9", room_title: "#18 #new #lovense #pantyhose #daddy #smalltits #young #c2c #smoke #cum #dildo #schoolgirl #longhair", style: "menu.s=F34C; @rose", menu: [ "2 You choose Music", "3 Private Message", "5 Blow a kiss", "6 Open your camera", "8 Suck fingers", "9 Turn around", "11 Spank ass in jeans", "14 Remove 1 garment 2 min", "17 Flash feet", "19 Smoke", "20 Flash bra", "22 Flash ass", "25 Spank naked ass", "30 Flash boobs", "40 Hair boobs 2 min", "45 Topless 2 min", "50 Oil boobs", "60 Naked 2 min", "70 Your name on my boobs",], dice: [ "", "Topless 2 min", "Spank naked ass", "Flash boobs", "Smoke", "Flash shoulders", "Flash neck", "You choose Music", "Blow Kiss", "Suck fingers", "Turn around", "Spank ass in jeans", "Flash bra", "Remove 1 garment 1 min", "Flash ass", "Hair boobs 2 min", "Naked 2 min"], }; C.emmi_rosee = { tokens: 25, lord: 4, style: "menu.s=F49A; menu.f=E02818 dice.f=009088 goal.f=404040", room_title: "I'm back guys :) 20 tk pm and 25 tk roll the dice :)", dice: [ "show tits", "show ass in panties", "take off shorts", "slap on ass", "show panties", "show tongue))", "show legs", "wink", "kiss you))", "show my today's outfit", "lick finger", "show feet", "show bra", "take off t-shirt", "show tattoo", "show tits", ] , menu: [ "", "", "20 pm", "22 say something in my voice", "24 smoke", "", "30 rate your cock", "", "39 show my today's outfit", "40 show legs", "45 feet teasing", "50 show ass in panties", "", "60 tales of the dirtiest fantasy", "", "80 sincere conversation", "90 take off shorts", "", "", "130 take off t-shirt", "150 if you like me)))", "200 undress me in public", "", "300 flash tits", "... Welcome in pvt: strip (full nude 500),suck dildo, tease, topless, c2c(100), oil show(200), recording(800).", ], }; C.cocosha7 = { room_title: "#brunette #lovense #young #c2c #natural #pussy #cute #lush #strip #pvt #teen #tits #longhair #sexy #nipples #naked #ass #topless #sweet #girl #lips", tokens: 12, style: "menu.s=F49A; menu.f=E02818 dice.f=009088 goal.f=404040", menu: [ "5 pm", "7 blow kiss", "16 song request", "23 smoke", "30 rate your cock", "33 present clothes", "38 show bra", "42 play with nipples", "44 take off t-shirt", "46 take off short", "50 show doggy in panties", "56 slap my ass 5 times", "62 play with panties", "72 heart-to-heart talk", "85 my most dirty fantasi", "99 for my eyes", "215 topless dance", "300 control lovense 5 min", "555 strip dance", "... Welkome in pvt: strip (full nude 200), tease,topless,c2c(50), oil show(125), recording(399), Unlimited Lush Control(200).", "", "", "", "", "", ], dice: [ "Flash tits", "Slap my ass", "Play with nipples", "Play with panties", "Show bra", "Show belly", "Play with hair", "Kiss you", "For my dream", "Show Neck", "Lick my finger", "Show my clothes", "Doggy position", "Hand Bra 2 min", "Topless Dance", "Strip Dance", ], }; C.elizainlove = { base: "shy", tokens: 0, room_title: "New day! Who wants to chat? #new #young #girl #nonude #18 #teen #longhair #blonde #slim #chat #feet #longnails", menu: [ "5 PM", "6 Blow Kiss", "7 Poke Tongue & Wink", "20 Stand and Sexy 360&#BA; Spin", "30 5 Squats", "... Welcome to private. There I can c2c, get up, turn around, walk nicely, all in clothes. For request please use tip notes." ], }; C.miss_malena = { base: "shy", tokens: 0, room_title: "#redhair #nonnude #curly #glasses #dance #tease #sexy #cute #funny #stockings", menu: ["... I like to cook. I dance and tease in private chat."] }; C.marmaladelady = { style: "@cherry", tokens: 7, lush: 3, lump: 2, lush_levels: "1+ 3s (Low vibrations), 15+ 6s (Medium vibrations), 100+ 10s (Medium vibrations), 500+ 1M (High vibrations), 1000+ 3M (High vibrations)", room_title: "#new #brunette #young #strip #hairy #pvt #teen #tits #bush #feet #longhair #natural #cute #pussy #naked #c2c #ass #dildo #oilshow #deepthroat #sexy #naughty #nipples", menu: [ "3 PM", "5 Blow Kiss", "12 Show Legs", "15 Show Tits", "18 Oil Feet", "22 Spank Ass", "25 Oil Tits", "30 Deep Throat", "40 Oil Pussy", "50 Slow Strip Dance", "70 Spank Tits", "90 Masturbation", ], dice: [ "Naked 5 min", "No Panties 5 min", "Topless 5 min", "Show Ass", "Show Back", "Show Neck", "Show Ears", "Blow Kiss", "Your Name in Air", "Turn around", "Dance", "Show Legs", "Hand Bra 5 min", "Hair Bra 5 min", "Pillow Dance", "Striptease", ], }; C.chiara177 = { lush_levels: "1+1s High, 15+3s Ultra, 15+3s Ultra, 50+11s Ultra, 100+22s Ultra, 500+66s Ultra", room_title: "#pussy #pvt #german #cum #lovense #lush #ohmibod #interactivetoy #nude #cute #slim #natural #brunette #fun #strip #tease #feet #panties #smalltits #spank #dildo", menu: [ "15 Flash feet", "20 spanks ass 2x", "22 flash ass", "24 10 pm", "30 flash ass", "35 flash tits", "40 flash pussy", "50 watch cam for 5minutes", "60 fingering my pussy", "222 dildo play 5minutes ", "555 lovense control 5 mins", "2222 get my panties ", "3333 my whatsapp", ".r. Welcome lets have fun", ".r. Lush is inside make me cum ;)", ".r. I speak english feel free to chat ;)", ".r. Please read Bio for special requests", ".r. Single tips: put request in tipnote please", // goal: finger pussy // Random Level Tip 111 Tokens will randomly choose a level between 1 - 5 Wave Pattern Tip 22 Tokens Send 10 second wave pattern Pulse Pattern Tip 33 Tokens Send 11 second pulse pattern Earthquake Pattern Tip 44 Tokens Send 12 second earthquake pattern Fireworks Pattern Tip 55 Tokens Send 13 second fireworks pattern // drop straps? ], dice: [ "Hand Bra", "Flash Belly", "Flash Neck", "Dance", "Shake Hair", "Your Name in Air", "Say your Name", "Blow Kiss", "Smile", "Meow", "Turn around", "Lick Lips", "Lick Finger", "Flash Feet", "Flash Legs", "Ass in Panties", ], }; // Tip*Dice commom code // // The slug is the center of a room, and next important are the clients and their words. // Tip*Dice presents options to the clients; it shows them what they can get for tokens. // It also shows when a task was triggered, e.g. after a client has tipped for the task. // It presents all that information in a consistent style easy to understand by clients. // The bot avoids writing more than neccessary. It will never push away words of humans. // // A task is a single action the slug performs, e.g. blow a kiss // A bid is a pair key -> task, e.g. 7 -> blow a kiss // A job maintains a set of bids, e.g. bot.dice or bot.tick // A row maintains jobs to be presented in one line. The row "menu" contains the jobs "menu", "roll", "tick". // Thus a single line presents TipMenu bids, TicketShow bids and bids to throw dice once or five times. // Each of the other rows just contain one single job: Dice tasks, goals or lush levels. // // For the slug, "job" means what we here call "task". #jobs presents the most recent tasks. // // 🍒🍑🍓🍏🍎🍇🍄🌱🌿🍀💮🌸🌷🌹🌺🌻🌼🥀🍁🍂🌵🍇 // 🍸🥂💋🍭🔥💎🐰 🐇🐸🐷🦔🦇🦉🐙🦑🦎🦋🦇🐌🐞🕷🕸💞👣🧦👋🌊💦💧 // 💎💍🗡🧷⛓🔗🗝📌📍🧹🧻 👙👠🧡💛💚💙💜🖤 // 👑👢👒🍾💘👁💃🍀 // 🍆🥕🌽🍌🥒🥖🥑 // 🌸🌺🍒🌹🔥🌷🍀🍑🍓🍏🍎🍇🍄🌱🌿🍀💮🌻🌼🥀🍁🍂🌵🍇🍸💋🍭💎🐰 🐇🐸🐷🦔🐌🐞👒🍆🥕🌽🍌🥒🥖👠🧡💛💚💙💜🖤 // #note @name // --------The model doesn't have a toy connected but your tip has been added to the queue // see users come and go without being a mod // font color during #hide ?? // edit panel of top tip amounts or just clear the panel without restarting app // tip amounts beside tipper // PM gif image - check always? Just remove colons? // rotate search tags // #hide +1 should not defer goals, and maybe even present the menu. // any way we can get #tout to not reset notice rotations? And just show only the menu's? // more precice number of ticket owners. // delete Thanks for Support, center and increase tippers // filter: Mädchen // set many goals in one command // when PM and dice have same price, PM filter is irritating // check first for naughty, then for PM req? // illegale tip menu option gibt keine warnung: 8 x|9 x|7x // notice on #show add // head, note // update grey filter: map unicode tables // repeat missed actions! // after 25 tokens, vote up // filter words selected by broadcaster // #note - to everybody // #. #, // line count // #lush on/off // different colors for Lovense ------- and user // discount for greys // for fan discount, have a green line in the panel, but standard price in notices and general menu // VIP list // #show needs ticket price // Lili likes fish eating each other and wants dinosaurs for that // A girl with long red hair wants a premium dice menu // A girl with freckles prefers not to show tip amounts in the panel, but the medals G.prefix = '#' // was configurable, but models could not handle that. G.mps = 1000 // milli per second - here we can change speet of time. G.tipped = []; // list of everybody who ever tipped G.spambots = 0 G.cyriots = 0 G.pm = { // statistics about automatic replies to PM requests begged: 0, // how often did anybody beg and we answered? morons: 0, // how many different people begged for PM? tipped: 0, // how many of them tipped enough later? } G.tl = { // trust level: grey: 0, // well, at least he registered tokens: 1, // owns token, but maybe never spent tokens recent: 2, // spent tokens recently alot: 3, // spent a lot of tokens tons: 4, // spent tons of tokens tipped: 5, // tipped the slug fan: 6, // is in fan club lord: 7, // has been made a lord mod: 8, // is a moderator mofa: 9, // is mod and fan author: 10, // wrote this app slug: 11, // shows her skin } G.num = { // how many people people ... sit: 0, // ... are here in the room own: 0, // ... own a hidden cam ticket see: 0, // ... are both sitting and owning cam: 0, // ... are added with limitCam_addUsers tip: 0, // ... have tipped at all } // once set, g.does.cam stays true even when g.does.sit becomes false Object.prototype.forAll = function(fun) { for (let key of Object.keys(this)) fun(key,this[key]); }; Object.prototype.doString = function() { let line = "class:"+this.constructor.name; this.forAll((k,v)=>line+=" "+k+":"+v); return line; }; Object.prototype.isEmpty = function() { return ! Object.keys(this).length; }; String.prototype.h4ck= function() { return this // adapt to disguised characters in case spam bots do the same .replace(/o/g,"0") .replace(/i/g,"1") .replace(/l/g,"1") .replace(/z/g,"2") .replace(/e/g,"3") .replace(/a/g,"4") .replace(/s/g,"5") } String.prototype.whiteSpace = function() { return this.replace(/\\n/g,"\n").replace(/~/g," "); } String.prototype.chat = function(n,b,f,w) { // if ( !this || this=="" ) return; // if ( !n ) // brag.wrote(); if (this&&this!="") cb.sendNotice(this.replace(/<t[+]d>/g,G.name),n,b,f,w); } String.prototype.damn = function() { if (this&&this!="") ("<t+d>: "+this).chat(_.names.user||cb.room_slug,undefined,"#FF0000","bolder"); } G.barn = { // style collections: @sakura, @hibiscus, @cherry, @rose, @fire, @tulip, @clover - 🌸 🌺 🍒 🌹 💧 🔥 🌷 🍀 sakura: { style: "menu.s=F338; fans.s=F341; goal.f=404040 menu.f=c00090 fans.f=407000 dice.f=6eb3f7 @lt_xx" }, // 🌸 🍁 hibiscus: { style: "menu.s=F33A; fans.s=F341; goal.f=404040 menu.f=d02087 fans.f=407000 dice.f=008098 @lt_xx" }, // 🌺 🍁 cherry: { style: "menu.s=F352; fans.s=F33F; goal.f=404040 menu.f=cc0038 fans.f=009055 dice.f=40c0ff @lt_xx" }, // 🍒 🌿 rose: { style: "menu.s=F339; fans.s=F5DD; goal.f=383838 menu.f=bb0033 fans.f=606060 dice.f=407000 @lt_xx" }, // 🌹 🗝 fire: { style: "menu.s=F525; fans.s=F48e; goal.f=404040 menu.f=ff4400 fans.f=30a0ff dice.f=009055 @lt_lf" }, // 🔥 💎 water: { style: "menu.s=F4A7; fans.s=F525; goal.f=404040 menu.f=cc0038 fans.f=30a0ff dice.f=009055 @lt_lf" }, // 💧 🔥 tulip: { style: "menu.s=F337; fans.s=F48d; goal.f=404040 menu.f=E02818 fans.f=40c0ff dice.f=009055 @lt_xx" }, // 🌷 💍 clover: { style: "menu.s=F340; fans.s=F341; goal.f=383838 menu.f=bb0033 fans.f=606060 dice.f=009D66 @lt_xx" }, // 🍀 🍁 lt_xx: { style: "lush.f=ff4400 @tails" }, lt_lf: { style: "lush.f=aa0033 @tails" }, tails: { style: "tail.f=9c9c9c spam.f=660000" }, //tails: { style: "tail.f=9c9c9c rota.f=000 spam.f=000"}, "_": { tokens: 9, style: "@tulip", menu: [ "", "3 PM", "5 Blow Kiss", "", "11 Turn around", "13 Flash Feet", "", "15 Flash Legs", "", "", "20 Dance", "", "", "30 Shake naked Ass", "", "40 Shake naked Tits", "", "50 Ass in Doggy Position", "", "60 10 minutes topless", "", "70 Striptease", "", "80 10 minutes naked", ], dice: [], }, shy: { tokens: 12, style: "@hibiscus", menu: [ "", "3 PM", "5 Blow Kiss", "7 Song Request", "", "11 Turn around", "13 Flash Feet", "", "", "", "20 Dance", "", "", "25 Shake Ass in clothes", "30 Show Belly", "", "40 Flash Bra", "", "", "60 Flash Panties", ], }, naked: { tokens: 5, style: "@rose", menu: [ "", "3 PM", "5 Blow Kiss", "", "11 Turn around", "13 Flash Feet", "", "15 Flash Legs", "", "17 Shake naked Ass", "", "20 Flash Tits", "", "25 Shake Tits", "", "30 Dance topless", "", "35 Play with Nipples", "", "40 Ass in Doggy Position", "", "", "50 Striptease", "", "60 10 minutes naked", ], }, schruti: { lush_levels: "", tokens: 0, lump: 5, papi: 1, room_title: "#this #that", style: "menu.s=F989; @rose", menu: [ "3 PM | 4 Blow Kiss | 5 Raise left Eyebrow | 6 Raise right Eyebrow | 9 Raise both Eyebrows", "", "", "13 Spread Nostrils", "17 Wiggle Ears", "99 Shave", "", "", "", "", "", "... Weather forecasts are useless.", "... You want to know if it rains? Look out of the window!", "... You want to know if it rains tomorrow? Look out of the window tomorrow!", "", "", ".g. 33 Drink Coffee", ".g. 33 Drink more Coffee", ".g. 33 Drink even more Coffee", ".g. 30 Drink Water | .g. 30 Drink Milk", ".g. 30 Eat Mozzarella | .g. 30 Eat Fruits", "", ".r. Hexen stehen immer zwischen Birken.", ".r. Wissen ist Nacht!", ], }, nakedpi: { tokens: 0, lush: 2, lush_levels: "1+1s low, 3+2s low, 4+3s true moose pleasure", style: "lush.s=F34C; menu.s=𝄞 @hibiscus", // 𝄞 room_title: "Hi, My Name is Nastya! Playlist in Bio. Lovense is in the Moose #boobs #feet #fingers #lovense #music #naked #new #piano #russian", menu: [ "100 Pick one of the works in the playlist in Bio", "101 Take Off The Dress", "102 Put On The Dress", "103 Take On Another Dress", "... I play only works from the playlist in Bio", "... Check me out on instagram @pianagirl", "... No pm, pvt or group shows!", ], }, ayajanae: { tokens: 13, lump: 5, lush: 2, style: "@cherry", lush_levels: "1+ 1s Low, 15+ 5s Medium, 100+ 10s Medium, 500+ 15s High, 1000+ 30s Ultra-High", room_title: "#asian #teen #naked #smalltits #cum #slim #dress #pantyhose #longhair #mistress #panties #fetish #beauty #pvt #braces #lovense #dirtytalk #daddy #schoolgirl #brunette #cute #18 #young #joi #cei #naughty #anime", menu: [ "", "5 PM", "7 blow kiss", "", "", "15 if u like me", "", "20 show feet", "", "25 show tonge", "", "30 show ass", "", "40 remove bra 5 minutes", "", "45 hair bra 5 minutes", "", "50 flash tits", "", "60 slap ass 5 times", "", "70 dance topless", "", "", "100 flash pussy", "", "120 sexy strip dance", "", "", "", "", ".r. :lovense10 Make my WET with the SOUND of your TIPS!!! :lovenselush", ".r. :lovense4 Give me pleasure with the sound of your tips!!!", ".r. :lovense8 Toy that vibrates with your Tips, Give me pleasure and make me wet", ], }, perra: { tokens: 0, style: "menu.s=F56F; menu.s=F4CC; @tulip", lush_levels: "1+2s Low, 11+8s Medium, 50+15s Medium, 100+25s High, 500+50s Ultra", room_title: "#whip #clamps #atm #nolimits #anal #squirt #deepthroat #cum #dance #smoking #doggystyle #latinas #colombiana #torture #masturbation #dildo #latina #feetfetish #heels #new #dirty #slave #slut #bondage #twerk #analshow #squirtshow #cumshow #slavegirl #twerking #cumslut #dancing", menu: [ "3 PM", "7 Suck Finger", "12 Suck Toy", "15 Bind Tits", "20 Spank Ass", "25 Spank Tits", "30 Spank Pussy", "33 Finger in Pussy", "35 Toy in Pussy", "38 Nipple Clamps", "40 Pussy Clamps", "45 Whip Ass", "50 Whip Tits", "55 Whip Pussy", "60 Deep Throat", "65 Finger in Ass", "70 Toy in Ass", "75 Spank Ass red", "80 Spank Tits red", "95 Squirt", "110 Whip Ass red", "120 Whip Tits red", "130 Hot Wax", "140 Spank Tits with Toy", "... Welcome to private! I'll gratefully accept humiliation.", ], }, dayanitasexyxxx: { base: "perra", }, yazminsweet: { base: "perra", }, }; C.forAll ( (name,specific) => { if (!G.barn[name]) G.barn[name]=specific; else Object.assign(G.barn[name],specific); }); delete C; O = { lush: "1+2sLow, 15+20sMedium, 100+90sHigh, 300+3mUltra", preModel(key,name=cb.room_slug) { while (true) { const set = G.barn[name||"_"] || {}; if (key in set) return set[key]; if (!name) break; name = set.base; } }, rev: {}, // key of a choice box => revers choice map box: {}, // key of a choice box => index in that box dump() { let oBox="", oStr="", oTop=""; this.keys.forEach ( set => { const key = set.name; const box = set.type=='choice'; // dump boxed choices by their indices const val = key + ": " + ( box ? this.box[key] : '"'+cb.settings[key]+'"' ) + ", "; if (box) oBox += val; else if (key=="room_title") oTop = val; else oStr += val; }); return oStr + "\n" + oBox + "\n" + oTop; }, cast: (function(){ class Option { // create an option to be used for cb.settings_choices[] constructor(key,lab) { this.key = key; this.lab = lab; this.val = O.preModel(key); this.set = { name: key, label: lab, required: false }; } def(val) { if (this.val == undefined) this.val = val; return this; } any(typ) { let set = this.set; set.type = this.typ = typ; let val = this.val; if (val == undefined) delete this.val; else set.defaultValue = val; return set; } box(...choice) { O.rev[this.key] = {}; // reverse map from string to index for (let i=0; i<choice.length;) { const text = choice[i]; O.rev[this.key][text] = i++; this.set["choice"+i] = text; } const val = this.val; this.val = val && val<choice.length ? choice[val] : undefined; return this.any('choice'); } str() { return this.any('str'); } int() { return this.any('int'); } } return function(key,lab) { return new Option(key,lab) } })(), }; { O.keys = [ O.cast('blab', 'Print room guide how often').def(3).box ("Rarely." ,"Seldom." ,"Sometimes." ,"Sufficiently." ,"A bit often." ,"More often." ,"Very often." ,"Flood the chat." ,"Spam as spam can." ), O.cast('lump','Split room guide or print one big lump').def(5).box ("Print guide in one big lump, as in the old days." // 0 ,"Split any rotating notifiers from main guide." // 1 ,"Split notifiers and lush levels from main guide." // 2 ,"Split notifiers, lush, and tails from main guide." // 3 ,"Split all that and even goals from main guide." // 4 ,"Split all sections but keep tails together." // 5 ,"Split even tailing lines from each other." // 6 ), O.cast('style', "Personal Style - try @rose, @tulip or any of " +"@sakura,@hibiscus,@cherry,@fire,@clover").str(), O.cast('room_title', 'Room Subject and search #tags').str(), ...(G.pic?[ O.cast('papi','Picture for the Panel').box ("Use random image or pick any of this list." ,"Bridge in Fog" ,"Stone Stack" ,"Blob of Water" ,"Sahara Dune" ,"Wave on Sand" ,"Shallow Water" ,"Blue Sky" ,"Red Sky" ,"Bug on Blue" ,"Bug on Green" )]:[]), O.cast('pmtk','Automatically reply to requests for ...').def(2).box ("... nothing. I want to answer to all requests myself." ,"... PM: If available, tell them what to tip for a PM." ,"... PM + private show: Tell them to discuss pvt in PM." ,"... PM + private show: Tell them there is no pvt today." ,"... PM + private show: I offer neither pvt nor PM." ), O.cast('grey','Policy for grey visitors').def(1).box ("Greys are great! They may gab whatever they wish." ,"Greys may chat unless they advertise other models." ,"Only people who own tokens are allowed to chat." ,"Only those who tipped since "+G.name+" runs can chat." ), O.cast('trud','If anybody writes in cyrillic ...').def(2).box ("... never mind, I welcome messages in any language." ,"... mute the line, warn him, next time mute him." ,"... mute him and tell me the command to unmute him." ,"... mute him and tell me that and why he was muted." ,"... mute him and don't even tell me about him." ), O.cast('lord','Who can use '+G.ab+' commands').box ("I and the author and my moderators." ,"I and the author of "+G.name+"." ,"I and my trusted moderators." ,"I and nobody else." ,"Nobody." ), // Fans see hidden cam - and more? O.cast('lush','How to present Lush Levels').box ("Do not present any levels for a vibrator." ,"Altogether in one line, similar to tip menu." ,"Each level in its own line, similar to lovense "+G.ab+"s." ,"Same plus Level numbers, as in lovense "+G.ab+"s." ), // try: 1+ 3s low, 15+ 10s medium, 99+ 1m hig O.cast('lush_levels', 'Lush Levels, for example '+O.lush).str(), O.cast('tokens', 'Tokens to roll dice, 0 to disable').str(), //@ goals abusing tip menu ]; cb.settings_choices = O.keys.slice(); } O.dir = { // prepare for both presenting and processing options menu: { head: O.cast('head_menu','Tip Menu and some other lines').box ("This explanation is not part of your Menu." ,"Here you can offer actions for tokens." ,"By this line you offer to dance for 17 tokens:" ,"17 Dance" ,"A | separates actions on the same line:" ,"17 Dance | 18 Show Legs | 19 Comb Hair" ,"// Two leading slashes disable a line." ,"You can do much more in this section." ,"For details, see the upcoming description." ), cname: G.pic ? "menu_" : "tipme1_", mini: 1, maxi: 36, }, dice: { head: O.cast('dice_menu','Tasks for Rolling '+G.dice+' Dice').box ("This explanation is not part of your Menu." ,""+G.dice+" dice roll results between "+G.dice+" and "+G.dice*6+"." ,"Results in the middle of numbers happen most often." ,"Very low and very high results happen seldom." ,"Just write your tasks into the text entry fields." ,"// Two leading slashes disable a task." ), cname: G.pic ? "dice_" : "prize1_", mini: G.dice, maxi: G.dice*6, }, }; [ // after all the sporadic options, add the option areas for dice and menu { key: "dice", fun: function() { const dice = G.dice; const vals= 6; const wide = dice*(vals-1)+1; const full = Math.pow(vals,dice); var digs = 2; var dezi = 100; while (2*dezi<full) digs++,dezi*=10; var hits = [1]; for (let i=0; i<dice; i++) { // This approach scales well even for many dice var sums = Array(hits.length+vals-1).fill(0); for (let i=0; i<hits.length; i++) for (let j=0; j<vals; j++) sums[i+j]+=hits[i]; hits = sums; } hits = Array(dice).fill(0).concat(hits); function prop(index) { const rate = hits[index]; const prob = rate*wide/full; return prob / (prob+1); // due to our "managed random" policy } const high = prop(Math.floor(dice*(vals+1)/2)); return function (index) { const stars = Math.floor(25*prop(index)/high+.2); return "*".repeat(stars)+" roll "+("_"+index).slice(-2); }; }() }, { key: "menu", fun: function() { return "Fee and Task for Tip Menu"; } }, ].forEach ( k => { const boundaries = O.dir[k.key]; cb.settings_choices.push(boundaries.head); const preset = O.preModel(k.key); const prelen = preset.length; for (let j=0, i=boundaries.mini; i<=boundaries.maxi; i++, j++) { const iname = boundaries.cname + i; cb.settings_choices.push({ name: iname, type: "str", required: false, label: k.fun(i), defaultValue: j<prelen ? preset[j] : "" }) } } ); // now cb.settings_choices is initialized, and we can use cb.settings function unicode(value) { if (!value) return; value = (""+value).trim(); if (!value) return; while (true) { const u = /\\u\{[0-9a-f]+\}/i.exec(value) || /&#[0-9a-f]+;/i.exec(value); if (!u) return value; value = value.replace(u[0],String.fromCodePoint("0x"+/[0-9a-f]+/i.exec(u[0])[0])); } } cbSetting = (function(){ // return a setting as a string const style = cb.settings.style; const match = /model=(\S+)/i.exec(style); const model = match && match[1] || undefined; O.rev.forAll ( (key,map) => { // for a choice box store the 0-based index const value = model && O.preModel(key,model); O.box[key] = value===undefined ? map[cb.settings[key]] : value; }); return function(name,raw) { let value = model && O.preModel(name,model); if (value === undefined) value = cb.settings[name]; if (Array.isArray(value)) return value; // exception: Not a string if (value===null || value===undefined) return ""; value = ""+value; // This converts 0 to "0", for example if (raw) return value; value = value.replace(/\/\/.*/,""); if (name.indexOf("_")<0) while (true) { const m = /\s@(\S+)\s/.exec(" "+value+" "); const s = m && m[1]; if (!s) break; const b = G.barn[s] && G.barn[s][name] || ""; if (Array.isArray(b)) return b; // e.g. dice: "@shy"; value = value.replace("@"+s,b); } if (!value) return ""; // ""+undefined is undefined, ""+null is null return unicode(value); }; })(); const uni = { heart: { blue: "\u{1f499}", green: "\u{1f49a}", yellow: "\u{1f49b}", purple: "\u{1f49c}", ribbon: "\u{1f49d}", black: "\u{1f5a4}", }, medal: [ "\u{1F947}", "\u{1F948}", "\u{1F949}", ], bunnyface: "\u{1F430}", wineglass: "\u{1f377}", dice: "\u{1f3b2}", flag: "\u{1f3c1}", carrot: "\u{1f955}", arts: "\u{1f3ad}", party: "\u{1f389}", trophy: "\u{1f3c6}", trumpet: "\u{1f3ba}", postal: "\u{1f4ef}", speaker: "\u{1f508}", lightbulb: "\u{1f4a1}", arrow: "\u279c", star: "\u2b50", sparks: "\u2728", geviert: "\u2001", ensp: "\u2002", emsp: "\u2003", emsp13: "\u2004", emsp14: "\u2005", figure: "\u2007", ellipsis: "\u2026", one: "\u278a", } const colour = { // ensures a consistent style tout: "#C060F0", // fg colour for touted notes only: "#EDFEFF", // bg colour for only for you tick: "#FFFDD0", // bg colour for ticket owner pale: "#B8B8B8", // fg colour for other people tipp: "#FFFF5F", // normal tip notes are khaki } String.prototype.only = function(uname) { if (!this||this=="") return; const step = !uname||uname==cb.room_slug ? "slug" : uname==_.names.user ? "user" : "alii"; if (step!="alii") _[step].only.drop(this); else _.alii.only.chat(this,uname); } String.prototype.ccme = function(comment="") { (comment+this).only(G.od); } _ = { t: {}, s: {}, _gL:[], // all gates in a list _rL:[], _rM:{}, names: {slug:cb.room_slug, user:cb.room_slug}, send: function() { // apparently cb sends notices to specific users first. We want them last. No soultion yet. for (let gate of _._gL) gate.send(); // maybe we could create a timer and handle personal notes just after. _.names = {slug:cb.room_slug}; } }; R = (function(){ let allWords = []; // all words found as components of Gate names let billBook = []; // book of all bills - a bill is a vector of rules class Gate { applyBill(bill, trait) { for (const rule of bill) rule: { if (rule.trait != trait) continue; for (let word of rule.words) if(!this.words.includes(word)) break rule; if (rule.value === null) return; // skip this whole bill of rules this.style[trait] = rule.value; // we found a match and style return true; } } applyBook(book,trait) { for (let bill of book) if(this.applyBill(bill,trait)) return true; } allTraits(book) { for (let trait of ['b','f','w','s','t']) this.applyBook(book,trait); } constructor(name) { this.name = name; // debug only this.note = ""; this.wrap = ""; let n = name.split("."); this.words = n.slice(); this.whom = this.words[0]; this.style = {}; n.forEach( word => {if (!allWords.includes.word) allWords.push(word)} ) let node = _; while(n.length>1) { let step = n.shift(); if (!node[step]) node[step]={}; node = node[step]; } node[n[0]] = this; _._gL.push(this); } drop(...line) { line = line.join("\n"); if (line=="") return; if (!_.names[this.whom] && this.wrap=="") brag.wrote(); this.note += this.wrap+line; this.wrap = "\n"; } chat(line,uname) { line.chat ( uname || _.names[this.whom] ,this.style.b ,this.style.f ,this.style.w ); } send() { this.chat(this.note); this.note = ""; this.wrap = ""; } } [ "tout.welcome", // the welcome message printed for each individual user "tout.goal.one", // A goal has been defined and we note that in public "tout.menu.one", // A menu entry has changed dynamically "tout.fans.one", // A Fan menu entry changed dynamically "tout.dice.one", // A dice action changed dynamically "tout.hide.one", // ? we announce a ticket show ? "tout.goal.all", // As part of the guide, we show all goals "tout.lush.all", // As part of the guide, we show all lush levels "tout.menu.all", // As part of the guide, we show all menu actions "tout.fans.all", // As part of the guide, we show all fans actions "tout.dice.all", // As part of the guide, we show all dice actions "tout.tail.all", // As part of the guide, we show all tailing lines "tout.rota.all", // finally we show one rotating notifier, that's all "tout.spam.all", // one of these between any consecutive separate blobs "note.menu.act", // Somebody tipped for the menu and the slug must act now "note.fans.act", // A fan tipped for the menu - thus the slug must act now "note.dice.act", // Somebody rolled dice, thus the slug must act now "note.goal.act", // A goal was reached, thus the slug must act now "note.hide", "user.only", "slug.only", "alii.only", "hidden.ticket", "hidden.blind", "hidden.slug", "tipp", ].forEach(name => new Gate(name)); function checkWord(thisWord) { return allWords.includes(thisWord); } function makeRule(text) { const m = /^((?:[a-z]+\.)*)([bfwst])(?:(=)(.*))?$/.exec(text); if (!m) return "Illegal Style rule"; let words = m[1].split(".").filter(word => word); let trait = m[2]; let value = m[4]; for (const word of words) if (!checkWord(word)) return "Unknown property name '"+word+"'"; if (!m[3]) value=null; // "dice.s" - ignore dice.s in that rule set. "dice.s=" - separator is "" if(value) { //else e.g. ... a parameter for cb.sendNotice() const hex = value.indexOf("l")<0; let topic = "text"; switch(trait) { case 's': topic = "separator"; case 't': { const first = words.shift(); const t = _[trait]; if (!first) return "You must name the line for which you define a "+topic; if (t[first]) return; if (words.shift()) return "The "+topic+" must apply to exactly one line."; while(true) { const u = /\\u\{([0-9a-f]+)\}/i.exec(value); if (!u) break; // const h = /[0-9a-f]+/i.exec(u[0]); value = value.replace(u[0],String.fromCodePoint("0x"+u[1])); } if (/^%[0-9a-f]+$/i.test(value)) value = String.fromCodePoint(value.replace("%","0x")); t[first]=value; return; } case 'w': if (!["normal","bold","bolder"].includes(value)) return "weight can be 'normal', 'bold' or 'bolder'"; break; default: if (!hex) return "property '"+trait+"' needs hexadecimal RGB value"; value = value.replace("#",""); if (!/^[0-9a-f]+$/i.test(value)) return "RGB value must be hexadecimal"; if (value.length!=3 && value.length!=6) return "RGB value must have 3 or 6 digits"; value = '#'+value; } } return { trait: trait, value: value, words: words } } function chkRule(text) { if (!text) return; if (/model=(\S+)/i.exec(text)) return; const rule = makeRule(text); if (!rule) return; if (rule.trait) return rule; _.user.only.drop("Bad Style Rule '"+text+"': '"+text+"' - "+rule); } [ (cbSetting("style")||"").split(/\s+/), [ "note.w=bold", "tout.f=" +colour.tout, "only.b=" +colour.only, "ticket.b=" +colour.tick, "blind.f=" +colour.pale, "tipp.b=" +colour.tipp, "tipp.s=" +"1", "goal.s=" +uni.flag, "lush.s=" +uni.carrot, "menu.s=" +uni.heart.purple, "fans.s=" +uni.wineglass, "dice.s=" +uni.dice, "welcome.t=Welcome %u!", ] ].forEach( list => { let bill = []; list.forEach( item => { const rule = chkRule(item); if (rule) bill.push(rule); }) billBook.push(bill); }); function allGates(law) { _._gL.forEach(gate => gate.allTraits(law)); } allGates(billBook); return { newRule: function(text) { const rule = makerule(text); if (!rule) return; if (rule.value) return rule; ("Bad Style Rule: '"+text+"' - "+rule).damn(); }, } })(); /* Rule contains: Set of words, property, value Style contains: Set of words, map Property -> value Apply Rules to single Gab Apply Rules to single Gate Apply Rules to all Gates */ const iaSep = uni.emsp13; String.prototype.both = function() { // write to _.names.user, cc slug if (_.names.user!=cb.room_slug) _.slug.only.drop(this); _.user.only.drop(this); } class Timer { constructor(name,fun,arg) { this.name = name; this.tid = undefined; this.cb = function(t) { // not in prototype - callbacks are per timer return function() { // ensure t remains visible for the inner function t.tid = undefined; fun(arg); brag.after(); } } (this); } end() { if (this.tid === undefined) return; cb.cancelTimeout(this.tid); this.tid = undefined; } set(s) { this.end(); s = 0+s; // ("Timer("+this.name+").set("+s+")").damn(); this.tid = cb.setTimeout(this.cb,s); } } timeNow = ( function() { let was = 1; // keep 0 reserved return function() { const is = Date.now(); if (was<is) was=is; return was; } } ) (); function twoPrices (line) { // 5 PM - everybody gets a PM for 5 tokens. // 5/ PM - everybody gets a PM for 5 tokens. // 5/3 PM - Fans pay 3 and all others pays 5. // /3 PM - Only Fans can get a PM: 3 tokens. // Similar for dice price but without action. const split = /^(\d*)\/?(\d*)(?:[\s➜:=|-]+(.*))?$/.exec(line); return [ split && split[1] ? parseInt(split[1]) : 0, // price for most clients split && split[2] ? parseInt(split[2]) : 0, // price for club members split && split[3] || "" ]; }; const [rollToken,fallToken] = twoPrices(cbSetting("tokens")); offerHelp = function(){ const remind = { quartzrose: "To start a hidden camera show and sell tickets, try a command like ..." + "\n#hide 57 spy on me" + "\n#hide 12 Break 15 Minutes" + "\n#hide 12 Prepare to go home" + "\n.... and to go public again:" + "\n#open" + "\n- - - - -" + "\nAvoid this trap on which nearly every model falls once:" + "\n#hide 15 Minutes Break" + "\nThat starts a ticket show called 'Minutes Break' and sells tickets for 15 tokens." + "\nA number that follows immediately after '#hide' is the ticket price." + "\nIf you really need some privacy, omit the ticket price in the command:" + "\n#hide Break 15 Minutes" + "\nThat starts a show for which nobody can buy a ticket. You can donate a ticket:" + "\n#show add schruti" + "\nThat will grant me a free ticket. Models use that to do a pre-paid private show." + "\nFor example, assume we agreed in PM that for 400 tokens I get a special show." + "\nI send 400 tokens, then you use these commands:" + "\n#hide Darkest Desires" + "\n#show add schruti" + "\n... and only I can see you, nobody else can see you." + "\nTalk to me in PM - the main chat is still visible in public." + "\nThat works like a password show, but without kicking everybody out of your room." + "\n- - - - -" + "\nIf you like to try goals, use a command like this:" + "\n#goal 99 Drink Coffee" + "\nThat means after you got tips of 99 or more in total, you drink coffee." + "\nOften models prefer goals that are somehow related to nudity." + "\nYou don't get naked in public, though, but you'll find another goal to motivate clients." + "\nIt's important to understand that the goal motivates the clients, not the model." + "\nYou want the clients to tip. A goal like 'Fly to Sidney' does not motivate them." + "\nA goal like 'Shake Ass' may very well motivate several clients." + "\n- - - - -" + "\nHave Fun! And sorry for flooding your screen. I take that away in a few days.", evangelinedecker: "Si quieres definir un goal, prueba este comando:\n#goal 300 Squirt", fredda1: "hi from\nbarney", kellyamazing: "Please deactivate all bots except <t+d>. After that, set a goal." + "\nYou set a goal by typing this line into the main chat window, where you talk to clients:" + "\n#goal 333 Strip Dance", sensualemma: "A few useful commands:" + "\n#hide 35 spy on me" + "\n#goal +99 Remove Top" + "\n#goal +99 Remove Skirt" + "\n#goal +99 Remove Bra", april_sweety: "ceterum censeo mamillare esse exuendum.", petitejuana: "Hi Brenda, to set all your goals, type this into the main chat, starting with #\n#goal once", lakotadream: "--------------------------------------------------------------------------------------------------------" + "\nHi Ellen, you'll see this message whenever you start Tip3Dice. To replace the carrots," + "\njust replace the 4th option 'Personal Style ...' by this: lush.s=b50; @tulip" + "\n--------------------------------------------------------------------------------------------------------" + "\n#goal 99 Drink Coffee" + "\n#hide 9 Break 15 Minutes" + "\n#open", li_liliana: "F996; I am missing you.", cryptic_ava: "Hi Ava! You can set a goal at 93 using this command:" + "\n#goal 93 take off dress" + "\nYou can set a 2nd goal using any of these commands then:" + "\n#goal 193 take off bra" + "\n#goal +100 take off bra" + "\nThe 2nd form (+100) works well even when people tip while you type it.", } [cb.room_slug]; let known = {}; return function offerHelp(uname) { if (!uname) uname = cb.room_slug; if (known[uname]) return; known[uname] = true; if (uname == cb.room_slug) ("schruti wishes you a pleasant time, happy clients and many tokens.").only(uname); ("When you type a line, <t+d> considers it a command if it contains: "+G.prefix).only(uname); ("Your commands and all other lines in this color are visible only for you.").only(uname); ("For help ask schruti, write to schruti@yahoo.com or type: "+G.prefix+"help").only(uname); if (uname == cb.room_slug) _.slug.only.drop(unicode(remind)); } }(); function doHelp(topic,error) { const tops = { goal: [ "After a goal is reached you perform the associated action.", "The bot handles many goals. The room name shows the next goal.", "Use bot settings to add tags or other text to the room name.", "#goal 99 Drop Bra % You drop the bra after a total of 99 tokens.", "#goal +200 Strip % You strip after highest goal plus 200 tokens.", "#goal 400 % You delete the goal at 400 tokens", ], lush: [ "Use bot settings to announce your vibrator: restart <t+d>.", "In the pop-up window with the settings, one line describes the Lush:", "1+3sLow, 15+5sMedium, 99+10sHigh, 200+20sUltra", "That means for 1 token the Lush vibrates 3 seconds in low intensity,", "for 15 tokens the Lush vibrates 5 seconds in medium intensity, ...", ], menu: [ "Delete tasks from tip menu or offer new tasks once or often.", "#menu 9 = Blow Kiss % Offer a new task 'Blow Kiss' for 9 tokens.", "#menu 9 - Drink % Only once, drink if anybody tips 9 tokens.", "#menu 9 % Delete the task for 9 tokens.", "#menu % Restore the complete tip menu.", ], dice: [ "Delete tasks from dice menu or offer new tasks once or often.", "#dice 3 = Strip Dance % Striptease if the dice show 1+1+1.", "#dice 18 = Blow Kiss % ... when the dice show 6+6+6.", "#dice 13 - Strip % Only once, strip when the dice sum up to 13.", "#dice 11 % Delete the original task for a sum of 11.", "#dice % Restore all dice tasks.", ], hide: [ "Hide your camera. Only paying viewers can see you. Try it:", "#hide % Hide your camera immediately. Nobody can see you.", "#hide 17 % Hide your camera, 17 tokens buy a Show Ticket.", "#hide +17 % Hide except for viewers who tipped 17 or more.", "#hide 17 8m % Hide your camera for at least 8 minutes.", "#hide Break 5 minutes % Hide camera during a short break.", "#hide 5 Break % Have a break; for 5 tokens clients may watch.", "#hide 17 8m Strip Dance % Hide, 17 Tokens buy a Ticket", "and during the hidden show you strip dance for at least 8 minutes.", "To learn more about hidden cam shows, type: #help show", "To learn how to end a hidden cam show, type: #help open", ], show: [ "Same as #hide, but <t+d> announces the show for 15 Minutes.", "After 15 Minutes, <t+d> will kindly ask you to type: #hide", "Everything else works exactly like #hide, so try: #help hide", "#show +33 Strip Dance % Wait 15 Minutes, announcing a show", "during which you dance for viewers who have tipped 33 or more.", "Also, #show can give away or invalidate a Show Ticket:", "#show add kunibert % Send a free Show Ticket to kunibert.", "#show del kunibert % Kick kuinibert out of your Ticket Show.", ], open: [ "End any Ticket Show or Hidden Camera modus.", "Everybody will see you after you type: #open.", "Thus if you just performed a Strip Dance in a Ticket Show,", "You might want to arrange your clothes before you type: #open", ], tail: [ "#tail Ceterum censeo ... % Append a line to the advertisement.", "#tail another line % Append another line to the advertisement.", "#tail % Delete all tailing lines.", ], stat: [ "#stat % Show statistics about how you earned tokens.", "#jobs % List the last 15 actions you had to perform.", "#jobs +50 % List the last 50 actions you had to perform.", "#tips % List the last 15 tip notes.", "#tips +30 99 % List the last 30 notes for 99 tokens.", "#tips +80 buy % List the last 80 tip notes containing 'buy'.", "#tips 5 hi % List 15 tip notes for 5 tokens containing 'hi'.", "#tips +40 9 dear % Altogether now:", "List the last 40 tip notes for 9 tokens containing 'dear'.", "To make somebody else revise tipnotes, see #help lord", "To test commands by using fake tokens, see #help whip", ], lord: [ "#lord kunibert % Make kunibert the lord of the bot.", "That means kunibert can use all bot commands listed here.", "He can add goals and new temporary tip menu actions.", "He can start hidden cam shows for you and invite guests.", "He can read all tip notes. So be careful using #lord", "#lord % No more lords, only you can use the bot commands.", "To train a lord and explain commands, use #pass:", "#pass #whip 9 % Do not interpret #whip 9 as a command.", "Thus instead of executing the command, the bot shows it.", ], whip: [ "#whip 5 % Tip yourself. No real tokens, just for tests.", "#whip 5 me@here % Tip yourself with tip note 'me@here'.", "None of these commands sends real tokens. They can't.", "They instruct <t+d> to pretend it received tips.", "That can be very useful if you want to test commands.", "The bot can never take away real tokens from you.", "No bot can take away tokens.", ], }; if (error) _.user.only.drop("Error in "+topic+": "+error+"\n"+G.prefix+"help "+topic); for ( line of [ null, [ "<t+d> is announcing that your will perform a Ticket Show.", "Do not yet perform the show! Still, everybody can see you.", P.view.hidden.viewers("Clients who tip ")+" you during the Ticket Show.", "You can give a free ticket to Hugo by typing: #show add Hugo", "When it's time to actually perform the Ticket Show,", "<t+d> will tell you which command you should use next.", ], [ "<t+d> has announced that your Ticket Show starts immediately.", "The Ticket Show is not running yet. Still, everybody can see you.", "The ticket owners expect you to perform your show soon.", "To hide the camera and start the show, type: #hide", "After that command, only ticket owners can see you.", ], [ "Only ticket owners can see you. Perform your Ticket Show now!", "Once the time is over, <t+d> will tell you how to end the show.", "<t+d> won't end the show until you type the command to end it.", "Until you type the command to end the show, only ticket owners see you.", ], [ "The regular length of the Ticket Show has expired.", "Still, you are invisible except for ticket owners.", "Take your time, prepare yourself, then type: #open", "After that, everybody can see you again.", ], [ "Only ticket owners can see you until you type: #open", "After that, everybody can see you again.", ], ] [P.view.hidden.stat] || tops[topic] || [ "You can use special commands. Only you can see the commands.", "Light bulbs mark Explanations: % This is not a part of a command.", "You type the commands into the public chat window, like: #help", "For each command you can get more help. Type this: #help hide\n", "#help goal_ % Set as many "+_.s.goal+" Goals as you like.", "#help menu % Change the "+_.s.menu+" Tip Menu just for now.", "#help dice_ % Change the "+_.s.dice+" Dice Tasks just for now.", "#help hide_ % Hide Camera "+uni.arts+" except for viewers who pay.", "#help lush_ % Love a "+_.s.lush+" Vibrator.", "#help tail__ % Append lines to the end of each announcement.", "#help stat_ % Show tips recived, tip notes and actions.", ] ) _.user.only.drop ( (" "+uni.geviert+" "+line) .replace(/%/g,uni.emsp+uni.lightbulb) .replace(/#/g,G.prefix) .replace(/_/g,uni.ensp) ); } function timeUnit (time,round) { const t = time/G.mps, step = ( t<60 ? 2 : t<300 ? 5 : t<600 ? 10 : 60 ) * G.mps; let t0 = Math.floor(time/step)*step; const r0 = t0, r1 = t0+step; if (round && 2*time > r0+r1) t0 = r1; for (let t1 = Math.floor(t0/G.mps), u = ["seconds","minutes","hours"]; ; u.shift()) { t0 = t1, t1 = Math.floor(t1/60); if (t0<100 || !u[1]) return { time: r0, next: r1, text: ""+t0+" "+u[0] }; if (t0<600) return { time: r0, next: r1, text: ""+t1+":"+("0"+t0%60).slice(-2)+" "+u[1] }; } } P = (function(){ const hide = [ "fridaminatty","april_pretty","sweetdreamemma","beautifulgirle","amazing_april","passion_emma" ].includes(cb.room_slug); const left = hide ? 80 : 108; const span = ( function() { let text = ""; let down = false; let zero = 0; // absolute time of start or end update = function() { const now = timeNow(); const diff = down ? zero-now : now-zero; const here = timeUnit(diff); text = here.text; // cb.log("now="+now+" down="+down+" zero="+zero+" diff="+diff+" here=("+here.time+","+text+")"); P.must.redraw(); // ("down="+down+" diff="+diff+" here.time="+here.time+" next.time="+here.next.time).damn(); if (down?here.time:here.next) timer.set((down ? diff-here.time : here.next-diff )+20); }; var timer = new Timer("update",update); return function(arg) { if (arg === undefined) return text; if (arg === null) return timer.end(); const now = timeNow(); zero = arg || now; down = now < arg; // cb.log("++++++++ now="+now+" arg="+arg+" down="+down); update(); } })(); const pic = G.pic ? { out: G.pic.shift(), // currently unused - an image for model being out of the room hic: G.pic.shift(), // the dark picture indicating that the camera is hidden std: G.pic[O.box.papi?O.box.papi-1:Math.floor(Math.random()*G.pic.length)] } : {}; class Stack { constructor() { this.layers = [{'type': 'image', 'fileID': pic.std}]; this.fontcolor = "black"; } image(key) { this.layers[0].fileID = pic[key]; } white() { this.fontcolor = "white" } line(text,x,y,size,weight="normal") { if (y<0) y+=69; this.layers.push({ 'type': 'text', 'text': text, 'left': x, 'top': y, 'font-size': size, 'font-weight': weight, 'color': this.fontcolor }) } block(x,y,d,size,...text) { text.forEach(line => { this.line(line,x,y,size); y += d; }) } head(header) { this.line(header,2,0,19); } tail(...lines) { this.block(2,-42,20,17,...lines); } done() { return { "template": "image_template", "layers": this.layers } } } class ViewAny { // any or out or hic constructor(name) { this.name = name; } charge() { // virt // prepare any data structure needed, including timers // after this call, onDraw() is able to return a panel at any time. // charge will be called on any change of stat, as opposed to ... } assign() { // ... will only be called when this view becomes the active view // ViewNormal uses that to make the model perform any goals achieved while not public // } set(val) { if (this.stat == val) return; this.stat = val; const v = P.view; const actor = v.hidden.stat ? v.hidden : v.absent.stat ? v.absent : v.normal ; const change = P.actor != actor; if (change) { P.actor = actor; span(null); actor.assign(); } L.updown(); // actually we only need that when P.view.hidden.stat changes between <2 and >1 if (this==P.view.hidden) bot.goal.roomUpdate(); if (change || this==actor) { actor.charge(); P.must.update(); P.must.redraw(); } } onDraw(s,u) { // virt // return the complete layer stack } } class ViewNormal extends ViewAny { assign() { bot.goal.aim(); } onDraw(s,g) { const toGoal = bot.goal.lowest; if (toGoal) { const decimal = ""+toGoal; const length = decimal.length; const height = length<5 ? 29 : Math.floor(117/length); s.line(decimal,2,0,height,"bold"); s.line(toGoal==1?"Token":"Tokens",3,28,20); s.line("to Goal",2,48,20); } const lead = P.leaders.slice(0,3); if(lead.length) s.line("Thanks for support!",left,2,13); for (let i=0, y=20; i<lead.length; i++, y+=16) { const ttip = lead[i]; // top tipper const gave = ""+ttip.given; s.line(uni.medal[i]+ttip.uname,left,y,13); if(!hide) s.line(gave,left-gave.length*7,y,13); } } } class ViewAbsent extends ViewAny { charge() { if (this.stat) span(0); } onDraw(s,g) { s.image("out"); s.head(cb.room_slug); s.white(); s.tail("left "+(g.does.sit?span()+" ago":"the room"), bot.goal.lowest?"Goal is postponed":""); } // don't tell clients in private how much they already spent. Anybody not being there might be in private. } class ViewHidden extends ViewAny { constructor(name) { super(name); this.par = {}; } charge() { // 1: announcing, 2: ready to run, 3: fixed length show, 4: ready to end, 5: open end show span(this.par.zero) } onDraw(s,g) { if (this.stat>2) { s.white(); s.image("hic"); } // else we are still announcing it - we might use a special image for that, too let text = g.pslug ? [] : ["Hidden Camera Show"]; { const when = g.pslug ? ["" ,"Hide in %" ,"It's time to #hide" ,"Open in %" ,"It's time to #open" ,"Hidden % ago" ] : ["" ,"Starts in %" ,"Starts immediately" ,"Will end in %" ,"Will end very soon" ,"Started % ago" ] ; text.push(when[this.stat].replace("%",span())); } if (g.pslug) { [ [ G.num.cam, "% viewer received a ticket", "% viewers received tickets"], // [ G.num.see, "% viewer is watching you", "% viewers are watching you"], [ G.num.see, "% ticket owner is watching", "% ticket owners are watching"], ].forEach(l=>{ const n = l[0]; const t = l[1+(n>1)]; text.push(t.replace("%",""+n)); }) } else { const much = g.ticket(); text.push( much==0 ? "You own a Ticket" : much ? ""+much+" Tokens buy your Ticket" : "No Tickets on Sale" ); } s.head(text[0]), s.tail(text[1],text[2]); } viewers(topic) { const par = this.par; return !par.cost ? "Invitees see" : par.plus ? "Guests who tipped "+par.cost+"+ see" : (topic||"Tip ")+par.cost+" Tokens to see"; } } return { leaders: [], view: { normal: new ViewNormal("ViewNormal"), absent: new ViewAbsent("ViewAbsent"), hidden: new ViewHidden("ViewHidden"), }, normal() { return this.actor == this.view.normal }, todo: { }, after() { let t = this.todo; if (t.update) this.actor.charge(); delete t.update; if (t.redraw) cb.drawPanel(); delete t.redraw; }, must: { update() { P.todo.update = true }, // update the actor redraw() { P.todo.redraw = true }, // cb.drawPanel() normal() { if (P.normal()) this.redraw() }, // after tipping or goal changes }, onDraw(g) { let s = new Stack(); this.actor.onDraw(s,g); return s.done(); }, lead(g) { // update leader list, if in normal view redraw ll and goal let i = this.leaders.indexOf(g); if (i<0) i = this.leaders.length; this.leaders[i] = g; while (i>0) { const j = i-1; const h = this.leaders[j]; if (g.given <= h.given) break; this.leaders[i] = h; this.leaders[j] = g; i=j; } if (i<3) this.must.normal(); this.leaders = this.leaders.slice(0,12); }, } })(); P.actor = P.view.normal; class Lump { constructor(name) { this.name = name; this.kids = []; this.lots = 0; this.next = 0; } toString() { return "Lump("+this.name+") kids="+this.kids.length+" lots="+this.lots+" level="+this.level+" split="+this.split+" next="+this.next; } trace() { _.slug.only.drop(this.toString()); } drop(line) { if (this.name) _.tout[this.name].all.drop(line); } count() { this.lots = 0; this.kids.forEach ( c => this.lots += c.lots ); } group() { if (!this.split&&this.lots) this.lots = 1; } hide() { // while slug is not visible in public, we hide all lumps except the lush if (P.view.hidden.stat>1) this.lots = 0; } update() { this.count(); this.group(); } updown() { // _.slug.only.drop("updown run "+this.toString()); this.kids.forEach ( c => c.updown() ); this.update(); // _.slug.only.drop("updown end "+this.toString()); } downup() { let b = this; while (b) { const lots = b.lots; b.update(); if (lots == b.lots) return; b = b.parent; } } adopt(kid,level) { if (!level) level = (this.level||0)+1; kid.level = level; kid.split = level<=O.box.lump; this.kids.push(kid); kid.dad = this; // kid.trace(); } reset() { this.next = 0; this.kids.forEach ( c => c.reset() ); }; doit(total) { // we won't return unless we printed something const kids = this.kids; if (total) { // print all, don't account kids.forEach (kid => kid.brag(total)); return; } let got = false, all = false; while (!this.split&&!all||!got) { { const [any,all] = kids[this.next].brag(); if (any) got = true; if (!all) continue; } this.next++; if ( this.next < kids.length) continue; this.next = 0; all = true; } return [true,all]; } brag(total) { // this.trace(); return this.lots ? this.doit(total) : [false,true]; // we run doit)= only of there is really work to do. } } class LuTerm extends Lump { update() { super.update(); this.hide(); // only for near terminal nodes - the lush anchestors should not be suppressed. } doit() { this.dump(); return [true,true]; }; } class LuMap extends LuTerm { constructor(name,title,...jobs) { super(name); this.separ = _.s[name]; // this might start with a \n for those models who want to see every entry on a separate line. this.sebar = this.separ.trim().replace("\\n",""); // use the bare separator without leading \n for messages that talk about one action. this.title = title; this.jobs = jobs; _._rL.push(this); _._rM[name]=this; for (let job of jobs) job.myRow = this; } map() { let map = {}; for (let job of this.jobs) { job.bids.forAll ( key => { if ( ! (key in job.sale ) ) map[key]=job; } ); job.sale.forAll ( key => { if ( job.sale[key] ) map[key]=job; } ); } return map; }; count() { this.lots = 0; for (let job of this.jobs) { job.bids.forAll ( key => { if ( ! (key in job.sale ) ) this.lots=1; } ); job.sale.forAll ( key => { if ( job.sale[key] ) this.lots=1; } ); } } dump() { let line = this.title+":"; this.map().forAll ( (key,job) => line += iaSep+" "+job.bid(key,this.separ) ); this.drop(line); }; } class LuVib extends LuMap { hide() { // that's the point: When hidden, show just the lush, nothing else } dump() { const lush = O.box.lush; if (lush>1) { let index = 0; this.map().forAll ( (key,job) => { let sep = this.separ; if (lush>2) sep = "Lush Level "+(++index)+": "+sep; this.drop(iaSep+" "+job.bid(key,sep)); } ); } else if (lush) super.dump(); }; } class LuLine extends LuTerm { constructor(name,line) { super(name); // because this name defines where we drop the line this.line = line.whiteSpace(); this.lots = 1; } count() { this.lots = 1; } dump() { this.drop(this.line); }; } class LuTail extends Lump { clean() { this.kids = []; this.next = 0; this.downup(); } add(line) { this.kids.push(new LuLine(this.name,line)); this.downup(); } } class LuRota extends LuTail { group() { if(this.lots) this.lots = 1; } doit(total) { if (total) return; // no rotating notifiers onEnter() const kids = this.kids; kids[this.next++%kids.length].dump(); if (this.next >= kids.length) this.next = 0; return [true,true]; } } L = new Lump ("lump"); L.tail = new LuTail("tail"); // tailing messages at the end of each briefing L.rota = new LuRota("rota"); // one of these after each briefing L.spam = new LuRota("spam"); // one of these after each lump L.split = true; L.update = function() { Lump.prototype.update.call(this); const fac = this.spam.lots + 1; this.lots = this.head.lots * fac; } L.doit = function(total) { // we won't return unless we printed something const kids = this.kids; if (total) return L.head.brag(total); while (true) { const [any,all] = kids[this.next++].brag(); if ( this.next >= kids.length) this.next = 0; if (any) break; } } class Job { constructor(...properties) { // for lush, goal, tick, roll+rall // .name is the name displayed turing tout: lush, goal, menu, dice, tail // .decor is an optional string to decorate each bid, currently used for Ticket Show only Object.assign(this,...properties); this.zero(); } getTask(key) { return key in this.sale ? this.sale[key] : this.bids[key]; } actWhy(uname) { // Why will the slug act? return uname+" tipped "+this.key; } actHow(uname) { // How will the slug act? const key = this.key; const how = this.getTask(key); if (this.once[key]) { delete this.once[key]; delete this.sale[key]; } return how; } win(why,what) { // present the trophy and prize _.note[this.myRow.name].act.drop(why+" "+uni.trophy+" "+what); D.jobs.enter(what); } act(uname,key) { // act on a tip and return a tip notice this.key = key; const act1 = this.actWhy(uname); // dice.actWhy() changes the key ... const act2 = this.actHow(uname); // ... thus actHow() uses the new key if (act2) this.win(act1,act2); // by default, tip notice is the only action } bidWhen(key,separ) { return separ+" "+key; } bidDeco(key) { return !this.sale[key] ? this.decor||"" : this.once[key] ? uni.star : uni.sparks; } bid(key,separ) { let deco = this.bidDeco(key); if (deco) deco += " "; return this.bidWhen(key,separ).whiteSpace()+" "+uni.arrow+" "+deco+this.getTask(key); } updateRow() { if (this.myRow) this.myRow.downup(); } init() { } zero() { this.bids = {}; this.sale = {}; this.once = {}; this.init(); } } Job.prototype.offer = (function(){ let known = {}; return function(task) { if (!task) return; const [key,val] = [task.key,task.val]; if (!key) return; if (!this.unique); else if (known[key]) { ("For "+key+" tokens you define conflicting actions: '"+known[key]+"' and '"+val+"'\n" +"Restart <t+d> to change one of the prices, then scroll down and press 'Start "+G.ab+"'.").damn(); if (key==O.pm) return; // Else the automatic PM reply is pointless } else known[key] = val; this.bids[key] = val; // anyway; precedence is implemented somewhere else } })(); class Dir extends Job { alter(line) { // return undefined or error string const row = this.myRow; // title and separator are properties of the complete row, not of the job if (line==="") { // no arguments: restore all this.sale = {}; this.once = {}; brag.hurry(); // present the restored menu } else { const m = /^([1-9]\d*) ?(\S.*)?$/.exec(line); // first argument must be amount or sum if (!m) return "First argument must be a number"; const amnt = parseInt(m[1]); let diff = "added "; if (m[2]) { const n = /^([-=]) ?(\S.*)$/.exec(m[2]); if (!n) return "after the number, we need a '-' or '=', followed by a task"; this.sale[amnt] = n[2]; this.once[amnt] = n[1]=='-'; diff += n[1]=='-' ? "one time" : "special"; } else this.sale[amnt] = null, diff = "removed"; let report = row.title+": "+cb.room_slug+" "+diff+" action "+(m[2]?uni.emsp13+" "+this.bid(amnt,row.sebar,0):(row.sebar+" "+amnt)); if (this==bot.dice&&!m[2]) report += "\nIf the dice sum up to "+amnt+", <t+d> rolls again automatically."; _.tout[row.name].one.drop(report); } this.updateRow(); } } // <amnt> = <task> ==> create a new task in TipMenu or RollDice // <amnt> - <task> ==> same, but the task will happen only once // <amnt> ==> restore the original task for the amount // ==> restore all tasks of TipMenu or RollDice bot = { total: 0, count: {}, stat() { { let line = "statistics:"; this.count.forAll ( (key,val) => line += " "+val+"*"+key ); _.user.only.drop(line+" total: "+this.total); } { let line = "top tippers:"; for (const g of P.leaders) line += " "+g.uname+"="+g.given; _.user.only.drop(line); } { let td = G.name, line = ""; if (G.spambots) line += td + " suppressed "+G.spambots+" spambots", td = " and"; if (G.cyriots) line += td + " muted "+G.cyriots+" cyrilliots", td = " and"; if (G.pm.begged) line += td + " replied to "+G.pm.begged+" PM requests sent by "+G.pm.morons+" visitors"; if (G.pm.tipped) line += " of which "+G.pm.tipped+" tipped later"; if (line) _.user.only.drop(line+"."); } }, }; bot.lush = new Job ({ bidWhen(key,separ) { return separ+" "+key+"+"; }, init() { // e.g. 1+ 3s low, 15+ 10s medium, 99+ 1m high const line = cbSetting("lush_levels"); if (line) for (let step of line.split(",")) { const split = /^(\d+)\s*\+\s*(\d+)\s*([sSmM])\s*(\S.*)$/.exec(step.trim()); if (!split||split.length<5) break; const count = split[2]; let unit = split[3].toLowerCase()=="s"?"second":"minute"; if (count>1) unit += "s"; if (O.box.lush<2) unit = unit.substring(0,3); this.bids[split[1]] = count+" "+unit+" "+split[4]; } }, }); bot.goal = new Job ({ // search tags are important, but we can pass only 200 characters to the room subject. // We save some characters by deleting blanks before '#' - CB inserts ' ' automatically nameTail: cbSetting("room_title").replace(/\s*#/g,"#"), nameUse: !!cbSetting("room_title"), nameNow: "tohu wa bohu", goaList: [], doLater: [], lowest: 0, addList(line) { const split = /^([1-9]\d*)\s*(\S.*)/.exec(line); if (split&&split[2]) this.goaList.push([parseInt(split[1]),split[2]]); else ("Illegal goal definition: "+line).damn(); }, bidWhen(key,separ) { return separ+" "+"+"+key; }, roomUpdate() { if (!this.nameUse) return; const keys = Object.keys(this.bids); const goal = keys.length && keys[0]; // const defer = goal&&!P.view.hidden.par.plus ? ", Goal deferred" : ""; const defer = goal ? ", Goal deferred" : ""; cb.log(this.bids); cb.log(goal); if (this.lowest!=goal) { this.lowest = goal; P.must.normal(); } let nameNew = P.actor == P.view.hidden ? P.actor.par.task+" @TicketShow"+defer : goal ? this.bids[goal]+" @Goal" : ""; const subject = this.nameSet || this.nameTail; if (subject.trim()) { if (nameNew) nameNew += ", "; nameNew += subject; } nameNew = /^(.{0,201})[#\s]/.exec(" "+nameNew+" ")[1].trim(); // max 200 chars, complete words only if (this.nameNow == nameNew) return; cb.changeRoomSubject(nameNew); this.nameNow = nameNew; this.updateRow(); }, //@@@ app does not see tips made in private. Thus app should not brag public while in ptivate. achieve(action) { this.win("Goal achieved",action) }, aim(amnt) { // apply to goals const keys = Object.keys(this.bids); if (amnt) for (let key of keys) { const action = this.bids[key]; delete this.bids[key]; if (amnt<key) { brag.token(amnt/key); this.bids[key-amnt] = action; } else if(!P.normal()) { _.note.goal.act.drop("Goal achieved - after the hidden cam show, "+cb.room_slug+" performs "+this.myRow.sebar+" "+action); this.doLater.push(action); } else this.achieve(action); } else while (this.doLater.length) this.achieve(this.doLater.shift()); this.roomUpdate(); }, scanCmd(line) { let plus = false; // append to top goal let show = false; // drop a notice for each goal let add = []; // the goals to add let m = /(\+?) ?(\d+) ?(\S.*)?$/.exec(line); if (m) { show = true; let key = parseInt(m[2]); if (!key) return "Number of Tokens missing"; if (m[1]) plus = true; // +200$ means highest goal plus 200 tokens if (m[3]) add = [[key,m[3]]]; else do if (this.bids[key]) { ("Deleting goal for "+key+" tokens "+uni.flag+" "+this.bids[key]).both(); delete this.bids[key]; break; } while(key--); } else if (line=="once") { plus = true; add = this.goaList; brag.hurry(); // present the list of goals } else return "Bad command line"; add.forEach ( g => { let key = g[0]; if (plus) { const keys = Object.keys(this.bids); //@ this? key += keys.length&&(keys[keys.length-1]-0); } this.bids[key] = g[1]; //@ this? this.nameUse = true; //@ this? if (show) _.tout.goal.one.drop("Goal for "+key+" tokens "+uni.flag+" "+g[1]); }); this.roomUpdate(); }, init() { this.roomUpdate(); } }); bot.tick = new Job ({ decor: uni.arts, getTask() { return P.view.hidden.par.task; }, actHow() { return "Ticket for "+this.decor+" "+this.getTask(); }, audienceList() { return cb.limitCam_allUsersWithAccess(); }, audienceLoop(fun) { this.audienceList().forEach(fun); }, audienceLine(sep) { let line = ""; this.audienceLoop(user=>{line+=sep+user;sep=", "}); return line; }, doHide(line,delay) { const hid = P.view.hidden; const par = hid.par; if (2<hid.stat) return "Ticket Show is already running"; if (!hid.stat) { const m = /^(?:(\+)?(\d+)(?: ([1-9]\d*)(?: ?[mM])?)?( )?)?(.*)$/.exec(line); if (!m) return "illegal Ticket Show parameters: "+line; if (m[5]) { if (!/^[a-z]/i.test(m[5])) return "The title must begin with a-z, not: '"+m[5]+"'"; if (m[2]&&!m[4]) return "For clarity, separate the title of your show by inserting a blank space before it."; par.task = m[5]; } else par.task = "Hidden Cam"; par.plus = m[1]; par.cost = m[2]?parseInt(m[2]):0; par.full = m[3]?parseInt(m[3]):0; par.wait = delay?15:0; // if (!par.cost); // else if (!par.plus) this.bids[par.cost] = par.task; // else guest(); // adds all users who spent enough tokens if (par.plus) G.tipped.forEach(g=>g.ticket()) if (par.wait) { par.zero = timeNow() + par.wait*60*G.mps; hid.set(1); ("<t+d> is announcing that your will perform a Ticket Show.\n" +"Do not yet perform the show! Still, everybody can see you.\n" +"In "+par.wait+" minutes <t+d> will explain the next step you do.\n" +"At any time, <t+d> explains the next step if you type: "+G.prefix+"help").both(); this.buyBefore(); return; } } else if (line) return "You already defined the show parameters.\nBefore you define the next show, end this one."; // brag.pause(); this.timBefore.end(); const msec = par.full*60*G.mps; par.zero = timeNow() + msec; if (par.full) { ("In "+par.full+" minutes <t+d> will tell you how to end the Ticket Show.").both(); this.timDuring.set(msec); hid.set(3); } else hid.set(5); let get = hid.viewers(); _.note.hide.drop("Ticket Show starting now! "+get+" "+uni.arts+" "+par.task); // cb.limitCam_start (title+get+"\n"+par.task); // cb.limitCam_start (get+" "+par.task); cb.limitCam_start(); this.audienceLine("Ticket owners see you: ").both(); }, doOpen() { let thanks = // this.countUsers() ? "Thank you for watching" : "Finished"; this.timBefore.end(); this.timDuring.end(); this.bids = {}; const hid = P.view.hidden; const par = hid.par; delete par.plus; delete par.cost; switch(hid.stat) { case 0: return "Nothing to end, there is no Ticket Show running or planned"; case 1: case 2: thanks = "Cancelling"; break; default: this.audienceLine("Everybody can see you again. Ticket owners were ").both(); break; } // bot.panel.dirty(); _.note.hide.drop(thanks+" "+uni.arts+" "+par.task); hid.set(); guest(); brag.hurry(); }, doShow(line) { const m = /^(?:(run|end|add|del)(?: (\S+))?)$/i.exec(line.toLowerCase()); const cmd = m ? m[1] : null; if (cmd=="add"||cmd=="del") { const usr = m[2]; if (!m[2]) return "Please tell <t+d> which user you wish to "+cmd; var g = guest(usr); if (!g) return "Could not find anybody called "+usr; } const usr = cmd=="add"||cmd=="del" ? m[2] : "---"; switch (cmd) { case 'add': g.addUser(); return; case 'del': g.delUser(); return; case 'end': return this.doOpen(""); case 'run': return this.doHide(""); } if (!P.view.hidden.stat) return this.doHide(line,true); return "Ticket Show is already defined."; }, buyBefore() { const hid = P.view.hidden; const par = hid.par; if (!par.wait && !G.num.cam) { bot.tick.doOpen(); return; } const when = !par.wait ? "immediately" : par.wait==1 ? "next minute" : "in "+par.wait+" minutes"; let buy = "Ticket Show starts "+when+". "+P.view.hidden.viewers(); if (par.full) buy += " "+par.full+" minutes of"; _.tout.hide.one.drop(buy+" "+uni.arts+" "+par.task); // if (par.wait==3 && brag.waito()<222222) brag.hurry(); // if (par.wait<3) brag.pause(); if (par.wait) { bot.tick.timBefore.set(60*G.mps); par.wait--; } else { _.slug.only.drop("To run your Ticket Show now, type: "+G.prefix+"hide"); hid.set(2); } }, buyDuring() { _.note.hide.drop("Ticket Show will end soon."); _.slug.only.drop("To end your Ticket Show, type: "+G.prefix+"open"); P.view.hidden.set(4); }, init() { }, }); bot.tick.timBefore = new Timer("Before",bot.tick.buyBefore); bot.tick.timDuring = new Timer("During",bot.tick.buyDuring); bot.menu = new Dir ( {unique: true} ); bot.fans = new Dir ( {unique: true} ); bot.dice = new Dir ( { bid(key,separ) { let deco = this.bidDeco(key); if (deco) deco += " "; return separ+" "+deco+this.getTask(key); }, actWhy: function() { var memory = Array(2*G.dice).fill(0); // use limited memory do reduce visible repetitions { // We don't want to frustraste clients be showing the most easy actions first ... let d=G.dice; do memory.push((7*G.dice+(d=-d))/2); while (d<0||(d-=2)>=0); } // ... thus we pretend that the middle actions have just been executed. var bored = 0; // are we already bored by previous repetitions? return function(uname) { // What triggers a task? We throw dice! let rep = 0; do { var line = ""; // describes the process and result of throwing dice. var sum = 0; // The key, which here is the result of throwing the dice. let i = G.dice; while(true) { const iacta = Math.floor(Math.random()*6) + 1; sum += iacta; //line += "[" + iacta + "] "; //line += String.fromCharCode(0x2473+iacta)+" "; line += iacta; if (!--i) break; line += "+"; } if (!bot.dice.getTask(sum)) continue; var known = 0; for (let v of memory) known = .80*known + (v==sum); // .82 var malus = (1+known)*(1+known)*(1+known*bored); if (Math.random()*malus<1) break; } while (++rep<99); // let deb = " skip="+rep+" bored="+bored+" known="+known+" malus="+malus; bored = .75*bored + known; // .8 memory.shift(), memory.push(sum); this.key = sum; const gap = sum>9 ? "" : bot.align; return line+" "+uname+" rolled "+gap+sum; // + deb; }; } (), }); { let points = G.dice; const scan = { menu(line) { if(line) line.split("|").forEach ( item => { item = item.trim(); const other = /^\.([.rsg])\.\s*(\S.*\S)/.exec(item); if (other&&other[2]) { const typ = other[1]; // ("found other: "+item+" ; type="+typ+" item="+other[2]).damn(); item = other[2]; if (typ == 'g') bot.goal.addList(item); else L[typ=='r'?"rota":typ=='s'?"spam":"tail"].add(item); } else { const [all,fan,val] = twoPrices(item); if (!val) return; const sd = val.toLowerCase(); bot.menu.offer({key:all,val:val}); bot.fans.offer({key:fan,val:val}); if (!O.pm&&(sd.startsWith("pm")||sd.endsWith("pm"))&&all) O.pm=all; // after offer()! } }) }, dice(line) { const p=points++; // The key is simply the next available dice result. if (rollToken && line) bot.dice.offer({key:p,val:line}); }, }; O.dir.forAll((name,o)=>{ const set = cbSetting(name); for (let i=o.mini; i<=o.maxi; i++) { const line = set ? unicode(set[i-o.mini]) : cbSetting(o.cname+i); scan[name](line); } }); } bot.roll = new Job ({ unique: true, roll1: rollToken, // 1x dice roll roll5: 0, // discount price for 5 dice rolls many(key) { return key==this.roll5 ? 5 : 1 }, bidDeco(key) { return _.s.tipp=="0" ? "" : _.s.dice.repeat(this.many(key)); }, run(uname,count) { bot.align = count==1 ? "" : uni.figure; while ( count-- ) bot.dice.act(uname,0); }, act(uname,key) { this.run(uname,this.many(key)); }, init() { const split = /:(\d+)/.exec(cbSetting("tokens")); this.roll5 = split && split[1] && parseInt(split[1]) || function() { for (let r = rollToken*4, l = r*5/6, s=1, d=0; l<=(r+=d); d=(s*=-1)-d) { if (r in bot.menu.bids) continue; if (r in bot.fans.bids) continue; return r; // discount price for 5 dice rolls } }(); this.offer({key:this.roll1,val:"Roll Dice"}); this.offer({key:this.roll5,val:"Roll 5 times"}); }, }); bot.fall = new Job ({ unique: true, roll1: fallToken, // 1x dice roll for Fan Club Members bidDeco(key) { return _.s.dice; }, act(uname,key) { bot.align = ""; bot.dice.act(uname,0); }, init() { this.offer({key:this.roll1,val:"Roll Dice"}); }, }); { let head = new Lump("head"); // bids and rotating notifiers let bids = new Lump("bids"); // lush and main let main = new Lump("main"); // core and tails let core = new Lump("core"); // goals and just let just = new Lump("just"); // just tip menu, fan menu, dice L .adopt( head ); head.adopt( bids ); bids.adopt(new LuVib( "lush" ,"Levels of Lush",bot.lush) ); bids.adopt( main ); main.adopt( core ); core.adopt(new LuMap( "goal" ,"Go for the Goal",bot.goal) ); core.adopt( just ); just.adopt(new LuMap( "menu" ,"Tip for Tasks",bot.menu,bot.roll,bot.tick) ); just.adopt(new LuMap( "fans" ,"Fan Club Only",bot.fans,bot.fall) ); just.adopt(new LuMap( "dice" ,"Tip "+rollToken+" to roll Dice",bot.dice) ); main.adopt( L. tail , 6 ); head.adopt( L. rota ); L .adopt( L. spam ); L .head = head; } // 1 head // 2 bids // 3 lush // 3 main // 4 core // 5 goal // 5 just // 6 menu // 6 fans // 6 dice // 4 tail (6) // 2 rota // 1 spam brag = ( function() { const often = [ [30,20], [22,12], [17, 9], [13, 7], [10, 5], [ 7, 4], [ 5, 3], [ 3, 2], [ 2, 1], ] [O.box.blab||3]; let block = 0; let yello = false; let goals = { need: .45 }; let lines = { need: often[0] }; let delay = { need: often[1]*60*G.mps }; function reset(now=0) { goals.have = 0; lines.have = 0; delay.have = now; }; reset(); L.reset(); return { print: function(total=false) { L.brag(total); }, waito: function() { // milliseconds until we brag next time const now = timeNow(); const have = now-delay.have; let need = 4; need /= 1 + L.lots*lines.have/lines.need; if (!G.pic) need /= 1 + goals.have/goals.need; need -= 1; need *= delay.need; need /= (L.lots||1); // const need = (4/(G.pic||quota(goals))/quota(lines)-1)*delay.need; return need>have ? Math.ceil(need-have) : now>=block ? 0 : 3*need>have ? block-now : 0; // return need<have ? 0 : Math.ceil(need-have); }, after: function() { // after everything else, possibly brag, then flash output if (!brag.waito()) { const total = !delay.have; brag.print(total); if (total) L.reset(); reset(timeNow()); } const wt = brag.waito(); // ("now wait again "+wt).damn(); brag.tim.set(wt<99?99:wt); if (G.pic) P.after(); _.send(); }, slush: function( ) { block = timeNow()+3000 }, hurry: function( ) { delay.have = 0 }, token: function(n) { goals.have += (n) }, wrote: function(n) { const tip = n<0; // we address the problem that a yellow wall is interrupted by the room guide after too many lines written. lines.have += tip ? !yello : n||1, yello = tip; // thus only the first line in a series of tip reports counts for next lump. }, }; } ) (); brag.tim = new Timer("brag",brag.after); guest = ( function() { const par = P.view.hidden.par; let tick = []; // list of all people who watched parts of the current hidden cam show const suckers = [ // a few users known for begging and trolling "davidkerver15", "tallhung69", "jse73", "flibertyjib", "mardart88","fallatonce", "gohardquick", "whitecatblackears", "rohnbtw", "azzzza", "giannluca197", "lovepussy1980lp", "maddy_70", "bleueyedguy", "mandykingtoman", "lovelydickers99", "kronos54", "stevie_grey", "mihai30cta", "gtr_gtr", "iamhotboyohhhh", "master1xxx", "remi11111", "miloussou", "hairloverctt", "hornyinid77", "judaeaa", "raven36116", "heroguyhero", "djsoko2000", "cuminpvt", "sega4769", "lovetooseepuss9", "abrahamnajim7", "breeder66", "gico_s9", "js_ihavecookie", "giorgi_12", "redsad", "lowinsugar", "poloosdj", "yes_drpsy_6875", // 2020-01: // chibrits: you can always spot hotrod1867 entering right after devil_mark "hotrod1867", "devil_mark", "bboommpp", "texwille", "zoemax990", "chubby_boy96", "jaubob", "jonni1000", "martin_taslesha", "davidromeo777", "hhibcgujvxgr", "hawashoho", "raybekkk", "waji123zia", "satanartist", "eastcoastmoonman", "nakedman3131", "barradossus", "waazzzzg", "_ikex_", "debjans21", "spermakolben53", "patrick228526", "niceboyssexy", "jacquessssssssss", "stroke1", "crazy_eate", "usexychick", "kickstartx", "mert__000000000", "didoo6", "klm_jong_un", "generoushotbot", "yuppoyuppo", "johnfromwales2", "rico1911", "baron_s9", "uridium69", "deutscher1980", "aridragon2003", "johnniehcole12", "lingerlovesfeetandass", "victorvulkanon1", "magnosommo", "markiz9898", "atinpanman", "dickofsperm", // 2020-02: "pantyhosefetish1998", "dadojee", "ettoreettoreettoreettore", "ladyandthechamp", "khola_hawa", "speedfire24", "sugar33daddy", "mroldnavy4", "genuindelisious", // "sanyacherepovets", - floods the channel, but he is keira's friend, thus it's ok "rotzloeffel22", "larysalovexxx94", "mickyin", "mikelalalala", "juicyrebecca_35", "jakesummers131", "rrr406", "harisah86", "davens23", "crazyandyfucker", "gopguy33", "jonesman24new", "keanau0101", "boborik", "8ul", "asscleaner92", "dfik777", "julianaalmeida", "falconpop1", "boombang25yeah", "mikowera", "marcderoc4", "castelinio", "_maya7m__", "guideelk", "sugardaddy_uk", // "lovefitgirls55", // not yet sure "tomce69", "xxx_gg7", "gradle88", "roseraces", "braham271", "hwayzwaml", "smkfd", "05437753944", "babelvr", "macgyverangus1983", "straightdane", "icr90385", "jollychat89", "mikeet247", "nicu2019", "mrmojo003", "dirtyhorneylove", "morenitocaliente22", // 2020-03: "germany3232", "ladylover6900", "mrmojo003", "sexitto", "allyouveneverwanted", "stockingslover699", "monica_slavic", "smithjer0520", "symes81", "romika26", "mariano_2", "mcpandas55", "feel_it_", "yumumcum", "trippa833", "devilsmagicbutterflyi", "cairo22", "luxeon1972", "hitallo2001", "nusmkami", "ruhigbrauner", "imraqeeb19", "dicker_schwanz1", "elwapo0000", "ematrax2020", "dadddy4", "samirsams", "dronkie123", "foreskinfordays", "bobbyboi999", "qwer696969", "sakindu2002", "squireshybrid22", "akela_23", "fhkmknk", "simar0077", "lionlion9999", "mustafa_420", "yahiandf", "tap_hand2", "ncg2400mix", "gaisimthang", "rosaach66", "vivid_cummer", "lukas18cmschwanz", "chump3", "dingo570", "creampiefartlove1", "zenaella", "mattcoat", "deadazz", "prontolove", "6400iso", "aya234", "ichbinhola", "mandatoemanuele", "prince12_", "noko_s9", "jacksparrowsparrow", "frechezunge40", "mmnnsskk33", "motia12", "evropa400", "djjplease", "andromeda300", "mrmdg", "manutoutdoux", "nawalelh123", "dan5555555", "chris6373784936", "scar2144", "t09rul", "thetunisianboy", "buenrabo25", "fourplay888", "eddiemaclane", "danyel_197", "lovewifes69", "germany93ok", "harman1985", "lifehouses300", "ok161974", "nazimoo1", "mrdchoc", "im_drpsy6875", "xxxhotboyxx4u", "al87643", "criszu1985", "cuteandbig87", "anonyme1122", "qwertyzxcv1234", "hottbiguy101", "bronsonclevesv", "johnbonne", "touhemizeboutwil", "zolollo12", "bigshow_76", "virtwish_com", "barragemans", "taylor25cm", "tulaa12", "miezuxxx", "rivassstu", "sexyhotbigass1234", "daddyjaytogood", "romiyo22", "imagineuwithme", "golfholex", "slxughter", "cdawgs6569", "lucifer_imthedevil", "luke1260", "moneycash22", "biof1978", "frakie55", "zuke1778", "jboy2007a9", "padrino28", "fackboyy99", "yfrankyy", "cuckoldmefor1sttime", "zampa00", "babe_fuckbbtyyyxxx", "andreasmeme21", "tululu29?", "horny70foru", "kuko303", "fuckyourcuteface", "andy44440", "saif696", "feetslav38", "locoxpussy", "sidmay396", "bronsoncleves", "pushtender7", "falah1977", "cuckoldlover2", "kabarinap", "yazan468", "traktortom263", "anacondalive", "adamnorris36", "monaas2", "cheftodddunn", "pendejiyo", "tom28_life_isnot_more_once", "sweety_heart11", "mashusik", "samuel20060", "stecherhb", "chokeuhbitch", "katie_hot_wife", "cris9588", "yummyboi90", "joker31087", "masallen1220", "cowboyssssssssss", "youssef75j", "mraldersonn", "ciortik", "rainsaltycum", "coronaviruscovid19", "ronniemcclure", "carne2good", "neno_show", "pringles1980", "stronceka", "jj_tyy", "listerandkochanski", "venkatharddick", // eternal: "frank3142", "gavonterry", "pele12345678", "mostlymostly123", "bigbrother_sweetcum", "hhsjsjh", "nigggernigggerchickendinner777", "theatakan", "metalmatt88", "alicemistresse", "moryak", "daddyscockinyou", "excape1245", "danyhelper", "yourdaddy2099", // eternal because offering tokens for advance action: "iageta01", "lehmann18", ]; const book = { ad: [ // Notice: Banned words and phrases: cam2cam,C2C,c2c,private,pvt,cum,mmm,shaved,naked,anal,pm,pussy,bb,baby // Notice: babe,flash,spread,hole,asshole,ass,rub,feet,cock,dick,boobs,hack,b00bs // Notice: bob,bobs,slut,cunt,whore,best sex site,sex in your city // Notice: delete space,look at my cam,look my cam,watch me,cam 2 cam // Notice: how much,show boobs,show feet,show pussy,spread legs [14,/_p011c3_/,/_1nv35t1g/,/_j411_/,/_ch11d/,/_4bu5/,/_r4p3/,/_r4p15t/,/_m0135t/], [13,/_([bg][0u]+[1y]|du+d3|jung|p33)_?[52]/], [10,/_fr33/,/_14?m1\d_/,/jung3n/,/d4ddy/,/pr?1?v[43]?t/], [9,/(c4m|p14y|5h0w|pr?1?v[43]?t?3?|g1r1.?.?.?.?|j3rk0ff)_(m3|11v3|0n11n3|5h0w)/], [8,/_b10/,/_(0n|j01n|w1th|533|w4tch|v151t|v13w|100k.*)_(m3|my)_/,/p4g3_|p14c3|_c1ub|_53?1t3|53?1t3_|0n11n3/,/_(d313t3|r3m0v3|g0_?t0)/], [7,/_tw1tt3r_/,/_f4c3b00k_/,/cum 11k3 n3v3r b3f0r3/], [7,/c(4m)?_?2_?c(4m)?/,/c4m_/,/_m33t_/,/_d4t1n/,/b4b[y3]_/,/_b4b[y3]/,/(51t3|51t1?0)_/], [6,/_13t5_/,/_cb_g1f_1m4g3/,/_pr?1?v4?t3?/,/5h0w_/,/_bb_/,/_(m3j0r|m1g110r)_/,/_(b10,4cc0unt)_/], [5,/xxx|b1tch|cunt|wh0r3|51ut|g1r1|14d[1y]|m11f|5w33t|4ng31|c4ndy|t33n|m4dch3n|r4g422|f1113|ch1c4/,/n4ughty|d1rty|m4m45/,/_cum|5qu1rt|cum_|_m45turb/,/_(c0ck|d1ck)/], [5,/_t3nn_/,/h31[bs]/], [4,/_(f1t|b4d|w3t)/,/(f1t|b4d|w3t)_/,/c4ndy|h0rny|k1nky|ju1cy|5p3rm|g3113|c4tt1v3|3xc1t|ch4ud3|4d0135c|c41d|c0qu1n/,/_(f4p|j3rk|5uck|11ck|f[u1]ck|0b3y|m4?5tu?r?b|g01d3n)/], [3,/_(t1t5|b0+b5|455)/,/k1tty|pu55y|n4k3d/,/11tt13|cut3|bd5m/,/r41n_/,/_h0t|h0t_/,/_53x|53x+_|h0tt13|53x+y/], [3,/_1(_w4n(t|n?a)|_?4?m).*_+(n4ughty|h0rny|k1nky|d1rty|cum|53x+y|h0t)/,/(t33n|g1r1|14d[1y]|w0m.n).*_(f0r_(yo?)?u|w4n(t|n?a))/], [2,/_w41t1ng_/,/_f45t_/,/_b35t/], ], pm: [ "" ,"In the menu you find" ,"Best is you try tipping" ,"I'll happily write you if you tip" ,"Don't miss your chance to get" ,"Today we have a special offer for you:" ,"You always dreamed of models writing you?" ,"Appreciating your patience, I finally offer you" ,"Did you know?\nThe tiny black images on the screen are characters.\nTogether they form words and phrases!\nYou get even more words for" ,"Still, the price is" ], }; const lat1n = function(){ const latex = function(){ const liga = { B:"bi", O:"oe", A:"ae", I:"io", E:"ie", Y:"oy", R:"re", U:"un", D:"dz", L:"lj", N:"nj", T:"ts" }; let res = []; for ( const c of ( "------------------------------------------------0123456789------"+ "-abcdefghijklmnopqrstuvwxyz----_-abcdefghijklmnopqrstuvwxyz-l---"+ "e--f--t---s-O-z-----------s-O-zy-icloyl--ca---r------up---o-----"+ "aaaaaaAceeeeiiiidnoooooxouuuuybbaaaaaaAceeeeiiiidnooooo-ouuuuyby"+ "aaaaaaccccccccddddeeeeeeeeeegggggggghhhhiiiiiiiiiijjjjkkklllllll"+ "lllnnnnnnnnnooooooOOrrrrrrssssssssttttftuuuuuuuuuuuuwwyyyzzzzzzr"+ "bbbbbbcccddddgeeeffg-hiikki-wnnoooaapprss-lttftuuouyyzzzzzzz--bp"+ "il--DDDLLLNNNaaiioouuuuuuuuuueaaaaAAggggkkqqqqzzjDDDgghdnnaaAAoo"+ "aaaaeeeeiiiioooorrrruuuusstteehhnd--zzaaeeooooooooyylntj--acclts"+ "z--ba-eejjqqrryyaaabcdddeeeeeeefggg--hhhiiiiii-wwmnnnoo-orr-rrrr"+ "rrsssssttaouvwyyzzzz---c-b-ghjkl---DDDTTTFSZ----hhjrrlrwy-------"+ "--------------------------------ylsxcllttll---------------------"+ "----------------------------------------------------------------"+ "-----------------------------------aeioucdhmrtvx--tt--nn---ccc-j"+ "------a-ehi-o-yoiab--ezhoik-mn-onp--tyoxyoiyaeniuabyde-noik-uv-o"+ "np-otuyxywiuouwkbdyyyownoo--ff--eeww--b-ss--aattn-cjoeebbcmmpccc"+ "ee--esiij---knyuabb-aexennknmhonpctyoxuuwwb-be-rabb-aexennknmhon"+ "pctyoxuuwwb-be-reehresiij--nknyuwwbb--aa------zzyyoovvvv--oowwww"+ "--------oonnbbpp--ffbbxxeekkkkkkkkhhhh--aaccttyyyyxxuuyyyyhheeee"+ "ixx--nnaahhyymmiaaaa--eeeeeexxeezznnnnooooooeeyyyyyyyy----ffxxxx"+ "dddd----nnhhggtteennxx--RRqqwwkk----nnhh---------uf--t-tl--rl--y"+ "----ueltno--nuysr-to-o-----------wfqntqtnpdhl-oyhdnbujuznzw-nuy-"+ "ngl-po-u--------------------------------------------------------"+ "----------------n---nirnu-----ni-oy-gyzp-wn---------------------"+ "" ).h4ck() ) res.push(liga[c]||c); return res; }(); const matex = [ // U+[2100,2150) - 80 letterlike symbols ,,"c","oc",,,,"e","e","of","g","h","h","h","h","h", "j","j","l","l","lb","n","no",,"p","p","q","r","r","r","px",, "sm","tel","tm",,"z","z","o","u","z","i","k","a","b","c","e","e", "e","f","f","m","o","x",,,,"i","o","fax","n","y",,"n", "e","g",,,,"d","d","e","i","j",,,"p",,"f",, ]; const reMarks = /[\u0300-\u036F\u0483-\u0489\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7]|\uDB40[\uDD00-\uDDEF]/g; // mths.be/bwm const xother = { 0x0266d:"b", 0x0266d:"b", 0x02715:"x", 0x02716:"x", 0x02717:"x", 0x02718:"x", 0x0275a:"1", 0x027d9:"t", 0x0292b:"x", 0x0292c:"x", 0x02a09:"x", 0x02a2f:"x", 0x02a30:"x", 0x02a31:"x", 0x02b2f:"0", 0x1d6a4:"1", 0x1d6a5:"j", 0x1d6a6:"-", 0x1d6a7:"-", 0x1d7ca:"f", 0x1d7cb:"f", 0x1d7cc:"-", 0x1d7cd:"-", }; const mathAlphaNum = [ [ 0x1d400, "abcd3fgh1jk1mn0pqr5tuvwxy2".h4ck() ], [ 0x1d6a8, "abc-ezhoikamneonpoetyoxyo-abyde-noik-uveonp-otvyxywdedxogw".h4ck() ], [ 0x1d7ce, "0123456789" ], ]; return function(src) { let lat = ""; let bad = 0; Array.from(src.replace(reMarks,"")).forEach ( c => { if (c.length > 2) { bad++; return; } let p = c.charCodeAt(0); if (c.length>1) p = (p-0xD7F7)*0x400 + c.charCodeAt(1); let l = latex[p] || 0x2100<=p && matex[p-0x2100]; if (l); // happy else if (xother[p]) l = xother[p]; else if (p<0x02600) bad++; else if (p<0x02c00) ; else if (p<0x1d400) bad++; else if (p<0x1d800) for (let j=3; j;) { const m = mathAlphaNum[--j]; if (p<m[0]) continue; l = m[1].charAt((p-m[0])%m[1].length) break; } lat += l||"-"; } ); return [lat,bad]; } }(); const naughti = function(ll, trust=0) { if (trust>G.tl.recent) return 0; let [line,score] = lat1n(ll.replace(/\s+:\S+/g,"_cb_g1f_1m4g3").replace(/[._\s]+/g,"_")); line = line.replace(/\W/g,"") score = trust ? 0 : score / (score/13+.5); book.ad.forEach(l=>{ let bad = 0; l.forEach(rex=>{ if (!bad) bad = rex; else if(rex.test(line)) score+=bad; }) }); return score; } class Guest { constructor(f) { this.isguy = f("gender")=="m"; this.birth = timeNow(); this.given = 0; // tokens given this.lines = 0; // lines written this.bitch = 0; // this is a guest sucking bitch this.pmreq = 0; // how often did the user ask for PM without having paid at least the price for PM? this.does = {}; } // see == own && sit // cam: truw on first see, false if no longer oww will(what) { if(this.does[what]) return false; this.does[what] = true; G.num[what]++; if (what=="tip") G.tipped.push(this), this.raise(G.tl.tipped); if (what=="see") { // happens whenver this re-enters the room if (this.will("cam")) { // happens only once per ticket show for this tick.push(this); cb.limitCam_addUsers([this.uname]); "Now you own a Ticket - you can see the show.".only(this.uname); } // we tell the good news, but every time the user refreshes the browser P.must.redraw(); // We refresh the panel whenever this re-enters the room } if (this.does.own && this.does.sit) this.will("see"); // It's that simple. return true; } wont(what) { if(!this.does[what]) return false; delete this.does[what]; G.num[what]--; if (what=="cam") cb.limitCam_removeUsers([this.uname]); if (what=="own") this.wont("see"), this.wont("cam"); if (what=="sit") this.wont("see"); if (what=="see") P.must.redraw(); return true; } grant(f) { const uname = this.uname; const slug = uname==cb.room_slug; const author = [G.od,"hagob","gianni"+"wani","halmack","enreuther"].includes(uname); const perm = O.box.lord; const mod = f("is_mod"); this.isfan = f("in_fanclub"); // every time - becoming a fan activates privileges immediately this.cmd_w = slug && perm<4 // cmd_w allows "this" to do perform any app commands || author && perm<2 || mod && !(perm&~2) || this.noble; this.cmd_r = this.cmd_w || author; // cmd_r allows "this" to read informations that are public anyway this.trust = slug ? G.tl.slug : author ? G.tl.author : mod ? this.isfan ? G.tl.mofa : G.tl.mod : this.noble ? G.tl.lord : this.isfan ? G.tl.fan : this.given ? G.tl.tipped : f("tipped_tons_recently") ? G.tl.tons : f("tipped_alot_recently") ? G.tl.alot : f("tipped_recently") ? G.tl.recent : f("has_tokens") ? G.tl.tokens : G.tl.grey ; if (this.muted == undefined) this.muted = this.trust<G.tl.alot && perm<2 && suckers.includes(uname) ? G.od : false ; } raise(level) { if (this.trust<level) this.trust=level; } addUser() { // return: Did we change anything? return this.will("own"); } delUser() { // return: Did we change anything? return this.wont("own"); } ticket() { // undefined: no tickets on sale, 0: has a ticket, >0: price for ticket if (this.does.own) return 0; let cost = par.cost; // apply any Fan Club discount here if (!cost) return undefined; if (par.plus) cost -= this.given; if (cost>0) return cost; this.addUser(); return 0; } gave(amount) { this.will("tip"); this.given += amount; if (this.pmreq&&this.given>=O.pm) this.pmreq=0, G.pm.tipped++; if (par.plus&&this.ticket()) { P.must.redraw(); // because for this the amount of tokens needed must be updated _.alii.only.chat("Since <t+d> started, you tipped "+this.given+" Tokens - Thanks a lot!\n" + "The Ticket Show is open for visitors who have tipped at least "+par.cost+" Tokens.", this.uname); } } naughty(ll) { if (!this.trust) ll = " "+this.uname+ll; // some bots spam in their name return naughti(ll, this.trust); } spam(msg) { const n = 666, limit = 48, age = (timeNow()-this.birth)/1000; this.bitch *= n/(this.bitch+n); // rudeness wears off: if y = n/x, then n/(x+1) = n*y/(n+y) this.lines++; if (this.muted) { _.alii.only.chat("muted '"+this.uname+"' blabs: "+msg.m, this.muted); // ("muted '"+this.uname+"' blabs: "+msg.m).only(this.muted); return true; } if (this.cmd_r) return; // *after* checking for muted - even users with command privilege may be muted. const ml = msg.m; // message line const ll = " "+ml.toLowerCase()+" "; // lower case line const sd = ll.replace(/[ _\W]+/g," "); const none = O.box.pmtk==4; let sin = undefined; if ( O.box.pmtk && this.trust<G.tl.fan && this.given<(none||O.pm||1) && !sd.includes(" "+O.pm+" ") && !/ to?ke?n?s? /.test(sd) ) { let price = false; let reply = undefined; if ( / p ?m|p ?m | p(r?i?v[ae]?te?|e?rso?na?l) ?me?s+a?ge?s? /.test(sd) // client asks for PM || this.pmgit && /\s:pm/.test(ll) // he already got a reply, now tries by GIF image ) { if ( none || O.pm ) { G.pm.begged ++, G.pm.morons += !this.pmgit, this.pmgit = true; if (none) reply = cb.room_slug + " does not offer private messages."; else price = true, reply = book.pm[this.pmreq+=this.pmreq<9]; } } else if ( / p ?r? ?i? ?v ?[ae]? ?t ?e? /.test(sd) ) { // client asks for private show if (O.box.pmtk==2) price = true, reply = "Please discuss private shows in"; if (O.box.pmtk>2) reply = cb.room_slug + " does not offer private shows today."; } if (price) reply += O.pm ? " "+bot.menu.myRow.sebar+" "+O.pm+" "+uni.arrow+" "+bot.menu.getTask(O.pm) : " PM."; if (reply) sin="begged", msg.m = reply, msg.background = colour.only; } poor: { let advice = 0; switch(O.box.grey) { case 0: break poor; // not considered spam case 2: if (!this.trust) advice = "get"; break; case 3: if (!this.given) advice = "tip"; break; } if (!advice) break poor; msg.m = "Please "+advice+" a token before writing here"; msg.background = colour.only; return true; } let trust = (2+this.trust+this.isguy)/2; const naughty = this.naughty(ll); const praecox = 20/(2*trust+age); const bitch = (naughty+praecox)/trust; const before = this.bitch; this.bitch += bitch; if (O.box.trud && this.trust<G.tl.tipped) { let tot = 0, rus = 0; Array.from(ll).forEach ( c => { let p = c.charCodeAt(0); if (c!=" ") tot++, rus+=(0x400<=p&&p<0x530); } ); let malus = 4*rus/(tot+15); this.bitch += limit*malus; if (malus>1) { switch(O.box.trud) { case 1: if (!this.alien) { this.alien = true; msg.m = "Please write English here"; msg.background = colour.only; break; } case 2: _.slug.only.drop("you can unmute "+this.uname+" by this command: #loud "+this.uname); case 3: _.slug.only.drop(this.uname+" has been muted for using cyrillic: "+msg.m); case 4: this.muted=G.od; G.cyriots++; } sin = "cyrill"; } } const acute = bitch * (this.lines+4)/this.lines; if (sin); else if (this.bitch+acute<limit) return; else G.spambots++, sin="farted"; //@ if (this.trust) ml.ccme("[" // I have to watch out for false positives: +Math.floor(before)+"+" +Math.floor(bitch)+"+" +Math.floor(acute)+"] '" +this.uname+"'["+this.trust+"+"+1*this.isguy+","+Math.floor(age)+"] " +sin+": "); return true; } test(w) { w.forEach(v => { v.map(l => { return {line:l, score:naughti(" "+l.toLowerCase()+" ")} }).sort((x,y) => x.score-y.score).slice(0,22).forEach(i => { _.user.only.drop("score["+i.score+"] "+i.line); }) }) } } list = {}; return function(usr) { if (!usr) { cb.limitCam_stop(); cb.limitCam_removeAllUsers(); G.num.cam = 0; while(true) { const g = tick.shift(); if (!g) return; delete g.does.cam; g.wont("own"); } } let g = list[usr]; let uname = usr.from_user; let prefix = "from_user_"; if (!uname) uname = usr.user, prefix = ""; if (uname) { _.names.user = uname; function get(key) {return usr[prefix+key]} g = list[uname]; if(!g) { g = new Guest(get); g.uname = uname; g.pslug = uname == cb.room_slug; // see the slug's panel list[uname] = g; } g.grant(get); if ( usr.m && g.spam(usr) ) usr["X-Spam"]=true; g.will("sit"); // all cb callbacks with a full user structure imply the user is in the room, except cb.onLeave. } // Apparently CB sometimes drops the event cb.onEnter. Thus we establish presence as a side effect of most events. if(g) g.ticket(); // everytime we check for a plus ticket now. It's a lot of effort for a feature used very rarely. return g; }; } ) (); class Diary { constructor(show,size) { this.show = show || function(x){return x}; this.size = size; this.list = []; } enter(x) { this.list.push({when:timeNow(),item:x}); while (this.size<this.list.length) this.list.shift(); } report(pick,upto,...args) { let just = timeNow(); if (!pick) pick = function(){return true}; // if (!upto) upto=15; if (upto>99) upto=99; for (let item of this.list.slice(-upto)) { if (!pick(item.item,item.when,...args)) continue; if (!upto--) break; let age = just-item.when; let all = [G.mps,"s",60,"m",60,"h",24,"d",7,"w"]; while ((age/=all.shift())>99&&all[1]) all.shift(); const unit = all.shift(); const fill = unit=="m"||unit=="w"?"":uni.emsp14; _.user.only.drop(uni.emsp+(uni.figure+Math.round(age)).slice(-2)+unit+fill+uni.emsp+" "+this.show(item.item)); } } } D = { jobs: new Diary(null,99), tips: new Diary(function(tip){return ""+tip.amount+" "+tip.from_user+" "+tip.message}), } function doRow (amount,row) { let all = _._rM[row].jobs; let k=all.length; while (k) { let job = all[--k]; if (job.sale[amount]) return job; if (amount in job.sale) continue; if (job.bids[amount]) return job; } } function doTip (g,amount,message) { // >=par.cost) cb.limitCam_addUsers(name)} ); if (message) D.tips.enter({amount:amount,from_user:g.uname,message:message}); const m = message && " - "+message; g.gave(amount); // personal statistic of that guest, cumulative ticket P.lead(g); // update leader list, if in normal view redraw ll and goal bot.goal.aim(amount); // apply to goals bot.count[amount] = (bot.count[amount]||0)+1, bot.total += amount; const job = !P.view.hidden.par.plus && g.ticket()==amount && g.addUser() ? bot.tick : g.isfan && doRow(amount,"fans") || doRow(amount,"menu"); if (job) { job.act(g.uname,amount); // the job notice tells the slugh who had tipped if (!m) return; // ... and that's enought except if there is a tip note } // pass it to the slug even if absent, e.g. in private: if ( P.view.absent.stat ) _.slug.only.drop("while you are absent, "+g.uname+" just tipped "+amount+m); } function doUsage (topic,error) { if (error) doHelp(topic,error); } if (G.pic) cb.onDrawPanel ( function(usr) { // don't waste time with anonymous return usr.user ? P.onDraw(guest(usr)) : {}; } ) cb.onEnter (function(usr) { let g = guest(usr); if (!g.trust) g.birth = timeNow(); // Greys are borm again on enter, thus they can't easily spam. const uname = usr.user; _.names.tout = uname; const iDice = _._rM.dice.lots; const iMenu = _._rM.menu.lots; const iLush = _._rM.lush.lots; const iGoal = bot.goal.nameUse; const hid = P.view.hidden; const par = hid.par; const stat = hid.stat; let welcome = _.t.welcome.replace("%u",uname).whiteSpace(), ago = ""; if (uname==cb.room_slug) { // P.view.absent.set(); offerHelp(); } else if (!welcome); // keep quiet. else if (2<stat) ticketShow: { welcome += " Ticket Show "; const now = timeNow(); let duration = now - par.zero; if (par.full) { duration = par.zero - now;; if (par.zero<30*G.mps) { welcome += "is finishing."; break ticketShow; } welcome += "will continue for "; } else welcome += "started ", ago = " ago"; const who = g.does.cam ? "You own a ticket for" : P.view.hidden.viewers(); welcome += timeUnit(duration,true).text+ago+". "+who+" "+uni.arts+" "+par.task; } else if (g.trust) { // greys can't if (O.box.lump||brag.waito()>9999) brag.print(true); if (iMenu) { welcome += " "+cb.room_slug+" offers a Tip Menu."; if (iDice) welcome += " Tipping "+bot.roll.roll1+" rolls the Dice."; if (iGoal||iLush) { welcome += " Each tip also counts for"; if (iGoal) welcome += " goals"; if (iGoal&&iLush) welcome += " and"; if (iLush) welcome += " the lush"; welcome += "." } } } _.tout.welcome.drop(welcome); brag.after(); }); cb.onLeave (function(usr) { guest(usr).wont("sit"); brag.after(); // because the slug's viewer counter might need to be decreased }); cb.onTip (function(tip) { const g = guest(tip); const amount = parseInt(tip.amount); doTip(g,amount,tip.message); brag.wrote(-1); brag.after(); }); function doCommand(msg,g) { const slug = cb.room_slug; const uname = msg.user; const head = msg.m.indexOf(G.prefix); // We can't just use the begin of the line because ... if (head < 0) return; // ... some bots decorate lines by prefixes before we see them. const foot = msg.m.substring(head+G.prefix.length).trim().replace(/\s+/g,' '); const subl = foot.substring(0,1)=="." ? 1 : 4; const cmd = foot.substring(0,subl).toLowerCase(); // refine that in case we get commands ... let line = foot.substring(subl).trim(); // .... which are not exactly 4 characters in length. const args = line.toLowerCase().split(' '); let err = null, hint = null, whipped = "whipped "; let u = 0; if (["lord","mute","loud","club"].includes(cmd) && !(u=guest(args[0]))) hint = "There is no user named '"+args[0]+"' in your room."; if (!g.cmd_w && !["dump","slug","pass","mute","loud","stat","jobs","tout","help"].includes(cmd)) hint = "cmd power limited to read status."; if (!hint) switch (cmd) { case '.': _.slug.only.drop(uname+": "+line); break; case 'pass': // author teaches slug msg.m = line; return; case 'dump': _.user.only.drop(O.dump()); O.dir.forAll((name,o)=>{ let line = name + ": [ "; for (let i=o.mini; i<=o.maxi; i++) line += '"'+cb.settings[o.cname+i]+'"'+", "; _.user.only.drop(line+"],"); }); break; case 'test': if (G.test) G.test(g); break; case 'tipp': if (g.trust>G.tl.fan) whipped = "tipped "; case 'whip': // for paranoid slug { const tipl = /^([1-9]\d*) ?(.*)/.exec(line); if (!tipl) { hint = "Don't forget the tipp amount!"; break; } const amnt = parseInt(args[0]); let tipn = whipped+amnt+" token"; if (amnt>1) tipn+="s"; if (tipl[2]) tipn += " -- "+tipl[2]; msg.m = tipn; doTip(g,amnt,tipl[2]); } return _.tipp; // stil case 'roll': // slug rolls dice herself bot.roll.run(uname,parseInt(args[0])||1); break; case 'club': // promote to fan, or demote u.isfan = !args[1]; _.alii.only.chat(slug+" "+(args[1]?"terminated your":"gave you")+" fan club privileges.",u.uname); break; case 'slug': // get the slug's panel g.pslug = !args[0]; P.must.redraw(); break; case 'lord': // rule the room u.noble = !args[1]; u.raise(G.tl.lord); _.alii.only.chat ( (u.noble=!args[1]) (u.cmd_r=u.cmd_w=!args[1]) ? g.uname+" made you Lord of <t+d> which is ready to serve your commands." : "Welcome in the world of mortal users. You have no power to command <t+d>." , u.uname ); break; case 'mute': // calm down a user if (u.muted) err = u.muted + " already muted " + u.uname; else if (u.trust<g.trust) u.muted = uname; else err = "You can only mute users with a lower trust level than yours."; break; case 'loud': // user can talk again u.muted = false; u.bitch = 0; break; case 'fast': // author is impatient G.mps = parseInt(args[0]) || Math.round(G.mps/2); msg.m = "G.mps = "+G.mps; break; case 'stat': // slug wants to know who has tipped and what bot.stat(); break; case 'jobs': // show up to 99 recent actions reuested case 'tips': // show up to 99 recent tips with messages { let n = 15; // number of lines to print { const m = /^(\+[1-9]\d*)? ?(.*)$/.exec(line); if (m[1]) { n = parseInt(m[1]); line = m[2]; } else msg.m = G.prefix + cmd + " +" + n + " " + line; } if (cmd=='tips') { const m = /^([1-9]\d*)? ?(.*)$/.exec(line); const amnt = parseInt(m[1]||"0"); if (uname==slug) D.tips.report( function(tip) { return (!amnt||tip.amount==amnt) && tip.message.indexOf(m[2])>=0 }, n ) } else D.jobs.report(null,n); } break; case 'show': // slug prepares a Ticket Show err = bot.tick.doShow(line); break; case 'hide': // slug runs a Ticket Show now err = bot.tick.doHide(line); break; case 'open': // slug ends a Ticket Show now err = bot.tick.doOpen(line); break; case 'goal': // slug sets a goal err = bot.goal.scanCmd(line); break; case 'menu': // slug changes a Tip Menu task case 'fans': // slug changes a Fan Club task case 'dice': // slug changes a Dice Sum taks err = bot[cmd].alter(line); break; case 'tail': case 'spam': case 'rota': if (!line) L[cmd].clean(); else L[cmd].add(line); break; case 'room': bot.goal.nameSet = line; bot.goal.roomUpdate(); break; case 'tout': brag.hurry(); break; case 'help': // slug learns about bot default: // slug needs to learn a lot doHelp(args[0]); break; } _.user.only.drop(hint); doUsage(cmd,err); msg.background = colour.only; msg["X-Spam"] = true; // Don't print command messages to chat. // if (uname!=slug&&!msg.is_mod) _.slug.only.drop(uname+" commands: "+msg.m); } cb.onMessage (function(msg) { const g = guest(msg); // detects spam if (!msg["X-Spam"]) { // no colour for spammers let stil = null; const uname = msg.user; if (uname==cb.room_slug && msg.m.startsWith("--------")) brag.slush(); setColor: { const m = /(#RGB=[0-9,a-z]{6}\s+)/i.exec(msg.m); if (!m) break setColor; msg.m = msg.m.replace(m[1],""); msg.c = "#"+m[1].substring(5).trim(); } if (g.cmd_r) stil = doCommand(msg,g); // g need at least status read permission to try a command if (msg["X-Spam"]) stil = _.user.only; else if (P.view.hidden.stat) { const step = uname==cb.room_slug ? "slug" : g.does.cam ? "ticket" : P.view.hidden.par.cost ? "blind" : "nixda"; if (P.view.hidden.stat>2||step=="ticket") stil = _.hidden[step]; // blind viewers go grey if tickets on sale } if (stil) msg.background = stil.style.b, msg.c = stil.style.f; const req = g.pmreq; if (req) didpm: { g.pmreq = 0; if (g.given>=O.pm || g.trust>G.tl.tipped) break didpm; msg.m = String.fromCodePoint(10121+req)+" "+msg.m; if (G.pm.hinted) break didpm; G.pm.hinted = true; _.slug.only.drop( "Earlier, " + g.uname + " already asked for PM " + req + " time" + (req>1?"s":"") + "." ); _.slug.only.drop( G.name + " privately replied that a PM cost " + O.pm + " token" + (O.pm>1?"s":"") + "." ); } if(!msg["X-Spam"]) brag.wrote(); } brag.after(); return msg; }); guest(); // opens the camera bot.goal.roomUpdate(); offerHelp(); // brag.hurry(); L.spam.updown(); L.updown(); brag.after();
© Copyright Chaturbate 2011- 2026. All Rights Reserved.