Bots Home
|
Create an App
Freda2bot1
Author:
fredda2
Description
Source Code
Launch Bot
Current Users
Created by:
Fredda2
// Version: 0.2.2 of 2018-12-07 inspired to inspire /* Commands: #pass <line> - print <line> without processing commands #help <cmd> - show hot to use #<cmd> #help - show an overview of main commands #stat - show statistics #roll - roll the dice #show - ticket show #task - change a task for menu or dice #show $ ... #show run #show end #show add <user> Announce a ticket show that starts in <delay> minutes. The show lasts <duration> minutes and in the show you do <task>. A ticket costs <amount> tokens and clients may buy it any time. */ cb.settings_choices = [ { label: 'How much do you charge per roll?', name: 'tokens', type: 'int', minValue: 1, defaultValue: 15 }, { label: 'Wait how many minutes before touting again? 0 for never', name: 'notice_wait_time', type: 'int', minValue: 0, defaultValue: 7 }, { label: 'Minimum lines of normal chat before touting again?', name: 'notice_wait_lines', type: 'int', minValue: 0, defaultValue: 20 }, { label: 'Room name if you want to use the goal bot. The bot automatically ' +'adds a goal which you can define while the but is running.', name: 'room_title', type: 'str', required: false }, { label: 'Lush? try: 1+ 3s low, 15+ 10s medium, 99+ 1m high', name: 'lush_levels', type: 'str', required: false }, { label: 'Change command prefix if other bots use the same prefix.', name: 'cmd_prefix', type: 'str', minLength: 1, defaultValue: '#' }, ]; Object.prototype.forAll = function(fun) { for (let key of Object.keys(this)) fun(key,this[key]); }; Object.prototype.isEmpty = function() { return ! Object.keys(this).length; }; const uni = { heart: { blue: "\u{1f499}", green: "\u{1f49a}", yellow: "\u{1f49b}", purple: "\u{1f49c}", ribbon: "\u{1f49d}", black: "\u{1f5a4}", }, dice: ["\u{1f3b2}","\u2680","\u2681","\u2682","\u2683","\u2684","\u2685"], stern: "\u2b50", coins: "\u26c1", arrov: "\u2192", arruw: "\u2b62", arrow: "\u279c", sac: "\u{1fb40}", eye: "\u{1f441}", uhr: "\u29d7", hourglass: "\u231b", ticket: "\u{1f3ab}", arts: "\u{1f3ad}", postal: "\u{1f4ef}", speaker: "\u{1f508}", bell: "\u{1f514}", geviert: "\u2001", carrot: "\u{1f955}", cucumber: "\u{1f952}", sparks: "\u2728", one: "\u278a", flag: "\u{1f3c1}", } const colour = { tout: "#C060F0", // fg colour for touted notes only: "#EDFEFF", // bg colour for only for you tick: "#FFFFCC", // bg colour for ticket owner pale: "#CCCCCC", // fg colour for other people } String.prototype.tidy = function(uname) { return this.trim().replace(/\s+/g,' '); } String.prototype.tout = function(uname) { if (!this) return; cb.sendNotice(this,uname,"",colour.tout); } String.prototype.note = function(uname) { if (!this) return; if (uname) cb.sendNotice (this, uname, colour.only); else cb.sendNotice (this, "", "", "", "bold"); } Array.prototype.note = function(uname) { var a; while (a=this.shift(),a) a.note(uname); } String.prototype.slug = function(uname) { if (!uname) uname = cb.room_slug; this.note(uname); if (uname==cb.room_slug) return; (" ... to "+uname+": "+line).slug(); } function Memo() { this.text = ""; } Memo.prototype.word = function(s) { this.text += s; } Memo.prototype.line = function(s) { if(s) this.word("\n".substring(!this.text)+s); } reply = (function () { let text = ""; return function(line) { if (line) text += "\n".substring(!text)+line; if (line===undefined && text) text.note(), text=""; } }) (); milliPerMinute = 60000; function Timer(fun,arg) { this.tid = undefined; this.cb = function(t,f,a) { // not in prototype - callbacks are per timer return function() { // ensure t remains visible for the inner function t.tid = undefined; fun(arg); } } (this,fun,arg); } Timer.prototype.on = function() { return this.tid !== undefined; } Timer.prototype.end = function() { if (this.on()) cb.cancelTimeout(this.tid), this.tid = undefined; } Timer.prototype.set = function(s) { this.end(); this.tid = cb.setTimeout(this.cb,s*milliPerMinute); } function doHelp(topic,uname) { const tops = { show: [ "A Ticket Show is visible only to clients whoy buy a ticket. Try it:", "#show 17 8m Sexy Dance", "The bot announces a ticket show. 17 tokens buy a ticket.", "After a few minuntes, the bot asks you to type:", "#show run", "After you typed it, only ticket owners see you, and you perform the show.", "After 8 minuntes the bot asks you to type:", "#show end", "... and then everything is back to normal, everybody can see you.", ], goal: [ "After a goal of tokens is reached you perform the announced action.", "The bot can handle several goals and updates the room name to show the current goal.", "In the bot settings you can add text to the room name, for example, the tags you use.", "#goal 200 Take off Bra @ you promise to remove the bra after a total of 200 tokens.", "#goal +200 Strip Dance @ you strip after highest goal plus 200 tokens.", "#goal 400 @ you delete the goal at 400 tokens", ], sale: [ "Delete tasks from tip menu or dice menu or offer new tasks once or for several times.", "#sale menu 9 = Blow Kiss @ The Tip Menu offers a new task 'Blow Kiss' for 9 tokens.", "#sale menu 9 - Remove Bra @ Only once, you remove the bra for 9 tokens.", "#sale menu 9 = @ Restore the original menu entry for 9 tokens.", "#sale menu 9 - @ Hide the original menu entry for 9 tokens.", "#sale dice 9 = Sing a Song @ Become a singer if the dice roll a 9.", "#sale dice = @ Restore all entries of the dice menu to show the original tasks.", "#sale = @ Restore all entries of both the dice menu and the tip menu.", ] }; memo = new Memo; for (line of (tops[topic]|| [ "This app supports several commands:", "#stat @ show statistics about how you earned tokens.", "#roll @ roll the dice yourself. No tokens for you, but clients have fun.", "#tipp 5 @ tip yourself. No tokens for you, but you can test the bot.", "#help show @ explains the details of a ticket show.", "#help goal @ explains how to set goals for which clients can tip.", "#help sale @ explains how to change Tip+Dice menu dynamically.", ] )) memo.line(" "+uni.geviert+" "+line); memo.text.replace(/@/g," "+uni.geviert+" "+uni.arrow).replace(/#/g,cmdPrefix).note(uname); } const numOfDice = 3; const cmdPrefix = cb.settings.cmd_prefix; function Job(properties) { // decor - lush, goal, tick, roll Object.assign(this,properties); this.zero(); } Job.prototype.handles = function(key) { return this.sale[key] ? true : (key in this.sale) ? false : this.bids[key] ? true : false; } Job.prototype.actWhy = function(uname) { // Why will the slug act? return uname+" tipped "+this.key; } Job.prototype.actHow = function(uname) { // How will the slug act? const key = this.key; const list = (key in this.sale) ? this.sale : this.bids; const task = list[key]; if (this.once[key]) { delete this.once[key]; delete this.sale[key]; } return task; } Job.prototype.act = function(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) reply(act1+" "+uni.stern+" "+act2); // by default, tip notice is the only action } Job.prototype.bidWhen = function(key,separ) { return separ+" "+key; } Job.prototype.bidDeco = function(key) { return this.sale[key] ? uni.sparks+uni.one.substring(!this.once[key]) : this.decor||""; } Job.prototype.bidWhat = function(key) { return this.sale[key] || this.bids[key]; } Job.prototype.bid = function(key,separ) { const deco = this.bidDeco(key); return this.bidWhen(key,separ)+" "+uni.arrow+" "+deco+" ".substring(!deco)+this.bidWhat(key); } Job.prototype.init = function() { } Job.prototype.zero = function() { this.bids = {}; this.sale = {}; this.once = {}; this.init(); } function Dir(properties) { // mini, maxi, cname, preset, hint, scan - menu, dice Job.call(this,properties); } Dir.prototype = Object.create(Job.prototype); Dir.prototype.constructor = Dir; Dir.prototype.init = function() { let j = 0; for (let i=this.mini; i<=this.maxi; i++, j++) { const name = this.cname + i; // 1st run: prepare settings_choices for the slug cb.settings_choices.push({ name: name, type: 'str', label: this.hint(i), defaultValue: j<this.preset.length ? this.preset[j] : "", required: false }); // 2nd run: evaluate the settings filled by the slug if(!(name in cb.settings)) continue; const task = this.scan(cb.settings[name]); if (task) this.bids[task.key] = task.val; } }; function Chain(properties) // title, separ, [jobs] { Object.assign(this,properties); }; Chain.prototype.merge = function() { }; Chain.prototype.bid = function() { 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; } ); } let line = ""; map.forAll( (key,job) => line += " "+job.bid(key,this.separ) ) return line ? this.title+":"+line : null; }; Chain.prototype.act = function(uname,key) { let jobs = this.jobs; let job = null; find: { let k=jobs.length; while (k) { job = jobs[--k]; if (job.sale[key]) break find; if (key in job.sale) continue; if (job.bids[key]) break find; } return false; } job.act(uname,key); }; bot = { total: 0, count: {}, stat: function(uname) { let line = "statistics:"; this.count.forAll ( (key,val) => line += " "+val+"*"+key ); (line+" total: "+this.total).note(uname); }, }; bot.lush = new Job ({ bidWhen : function(key,separ) { return separ+" "+key+"+"; }, init: function() { // e.g. 1+ 3s low, 15+ 10s medium, 99+ 1m high let line = cb.settings.lush; if(!("lush_levels" in cb.settings)) /*continue;*/ line="1+ 3s low, 15+ 10s medium, 99+ 1m high"; line="1+ 3s low, 15+ 10s medium, 99+ 1m high"; 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; this.bids[split[1]] = split[2]+" "+(split[3].toLowerCase()=="s"?"sec":"min")+" "+split[4]; } }, }); bot.goal = new Job ({ nameTail: cb.settings.room_title, nameUse: !!cb.settings.room_title, nameNow: "tohu wa bohu", bidWhen : function(key,separ) { return separ+" "+"+"+key; }, roomUpdate: function() { if (!this.nameUse) return; let keys = Object.keys(this.bids); let nameNew = keys.length ? "Goal: "+this.bids[keys[0]]+"!" : ""; if (this.nameTail.trim()) { if (nameNew) nameNew += " "+uni.geviert+" "; nameNew += this.nameTail; } if (this.nameNow == nameNew) return; cb.changeRoomSubject(nameNew); this.nameNow = nameNew; }, tip: function(amnt) { let keys = Object.keys(this.bids); for (let key of keys) { if (key<=amnt) reply("Goal achieved "+uni.stern+" "+this.bids[key]); else this.bids[key-amnt] = this.bids[key]; delete this.bids[key]; } this.roomUpdate(); }, scanCmd: function(line,uname) { let keys = Object.keys(this.bids); let m = /(\+?) ?(\d+) ?(\S.*)?$/.exec(line); if (!m) return false; let key = parseInt(m[2]); if (m[1]&&keys.length) key += keys[keys.length-1]; // +200$ means highest goal plus 200 tokens if (m[3]) { ("Goal for "+key+" tokens "+uni.flag+" "+m[3]).tout(); this.bids[key] = m[3]; this.nameUse = true; } else do if (this.bids[key]) { delete this.bids[key]; break; } while(key--); this.roomUpdate(); return true; }, init: function() { this.roomUpdate(); } }); bot.tick = new Job ({ decor: uni.arts, par: {}, actHow: function(uname) { this.addUser(uname); return "Ticket for "+this.bids[this.key]; }, countUsers: function() { return cb.limitCam_allUsersWithAccess().length; }, showRun: function(par) { if (!par.cost) "First announce the show, like:\n#show $ 25 wait 5 time 10 Pillow Dance".slug(uname); else if (!cb.limitCam_isRunning()) { this.timBefore.end(); let d = new Date(); d = new Date(d.getTime() + par.full*milliPerMinute); let h = ("0"+d.getUTCHours()).slice(-2); let m = ("0"+d.getUTCMinutes()).slice(-2); let s = ("0"+d.getUTCSeconds()).slice(-2); cb.limitCam_start ("... until "+h+":"+m+":"+s+" UTC\nTip "+par.cost+" to view\n"+par.task); ("Ticket Show starting now! You can still tip "+par.cost+" to see "+par.task).note(); this.timDuring.set(par.full); } else "Ticket Show is already running".slug(uname); return true; }, showEnd: function(par) { this.timBefore.end(); this.timDuring.end(); this.bids = {}; if (par.cost) { ((cb.limitCam_isRunning()?"Thank you for watching ":"Cancelling ticket show ")+par.task).note(); cb.limitCam_stop(); cb.limitCam_removeAllUsers(); par.cost = 0; } else "Nothing to end, there is no ticket show announced".slug(uname); return true; }, showNew: function(par,line,uname) { let m = /^(\d+) (\d+) ?[mM] (\S.*)$/.exec(line); if (!m) { ("illegal ticket show parameters: "+line).slug(uname); return false; }; par.cost = parseInt(m[1]); par.full = parseInt(m[2]); par.task = m[3]; par.wait = 4; this.bids[par.cost] = par.task; this.buyBefore(); return true; }, buyBefore: function() { let par = bot.tick.par; ("Ticket Show starts in "+par.wait+" minutes. Tip "+par.cost+" to see "+par.full+" minutes of "+uni.arts+" "+par.task).tout(); if (par.wait) { bot.tick.timBefore.set(1); par.wait--; } else if (!bot.tick.countUsers()) bot.tick.showEnd(par); else ("run your ticket show now! Type: "+cmdPrefix+"show run").slug(); }, buyDuring: function() { "The show will end soon.".note(); ("end your ticket show now! Type: "+cmdPrefix+"show end").slug(); }, addUser: function(invitee) { cb.limitCam_addUsers([invitee]); return true; }, scanCmd: function(args,line,uname) { let par = this.par; switch (args[0]) { case 'add': return this.addUser(args[1]); case 'run': return this.showRun(par); case 'end': return this.showEnd(par); default: return this.showNew(par,line,uname); } doHelp("show",uname); }, init: function() { }, }); bot.tick.timBefore = new Timer(bot.tick.buyBefore); bot.tick.timDuring = new Timer(bot.tick.buyDuring); bot.menu = new Dir ({ cname: "tipme1_", mini: 1, maxi: 20, preset: [ "5 Private Message", ], hint: function(key) { return "Fee and Task for Tip Menu"; }, scan: function(line) { // split "10 P M" or " 10: P M" or " 10--P M " into 10 and "P M" const split = /^\s*(\d+)\W+(\w.*\S)\s*$/.exec(line); // ignore blank in some places^ if (split == null) return null; const amt=parseInt(split[1]); if (!amt) return null; return {key:amt,val:split[2]}; }, }); bot.dice = new Dir ({ // title: "Roll Dice and win", // separ: uni.dice[0], cname: "prize1_", mini: numOfDice, maxi: numOfDice*6, roll1: parseInt(cb.settings.tokens), // 1x dice roll roll5: 0, // discount price for 5 dice rolls preset: [ "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", ], actWhy: function() { const memory = 5; // we use our limited memory of dice results to manipulate luck ... var record = Array(memory).fill(0); // ... just in case we get too many repetitions. return function(uname) { // What triggers a task? We have to roll the dice! let rep = 99; do { var line = ""; // describes the process and result of rolling dice. var sum = 0; // The key, which here is the result of rolling the dice. for (var i = 0; i < numOfDice; i++) { var iacta = Math.floor(Math.random()*6) + 1; line += "[" + iacta + "] "; sum += iacta; } if (!bot.dice.handles(sum)) continue; var bad1 = 0, bad2 = 0; for (var i=0; i<memory; i++) { for (var j=i; ++j<memory;) if (record[i]==record[j]) bad1 += i+j; if (record[i]==sum) bad2 += i+memory; } // multiple repetitions are a bit less likely to happen now. if (Math.random()*(memory+bad1+bad2+bad1*bad2)>bad1*bad2) break; } while (rep--); record.shift(), record.push(sum); this.key = sum; return line+uname+" rolled "+sum; }; } (), hint: function() { const dice = numOfDice; const vals= 6; 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); return function (index) { const rate = hits[index]; var prop = Math.round(dezi*rate/full); var line = "%"; for (var i=digs; i; i--) { line = prop%10+line, prop=Math.floor(prop/10); if (i==3) line = "."+line; } return "Task for sum "+index+", chance is "+rate+"/"+full+" = "+line; }; } (), scan: function(line) { // get the task, no key, and return key and task if (!this.points) this.points = numOfDice; const p=this.points++; // The key is simply the next available dice result. return {key:p,val:(line.length?line:"Thank You")}; // task is empty? Be friendly! }, }); bot.roll = new Job ({ roll1: parseInt(cb.settings.tokens), // 1x dice roll roll5: 0, // discount price for 5 dice rolls many: function(key) { return key==this.roll5 ? 5 : 1 }, bidDeco: function(key) { return uni.dice[0].repeat(this.many(key)); }, act: function(uname,key) { for ( let count = this.many(key); count; count-- ) bot.dice.act(uname,0); }, fill: function(key,task) { if (!key) return; this.bids[key] = task; }, init: function() { this.roll1 = parseInt(cb.settings.tokens); // 1x dice roll f5: for (let i=0; i*2<this.roll1; i++) for (let j=-1; j<2; j+=2) { const k = 4*this.roll1+i*j; if (k in bot.menu.bids) continue; this.roll5 = k; // discount price for 5 dice rolls break f5; } if (this.roll1) this.bids[this.roll1] = " Roll Dice"; if (this.roll5) this.bids[this.roll5] = " Roll 5 times"; }, }); bot.init = function() { bot.goal.zero(); let x = bot.goal; let y = bot["goal"]; x.zero(); y.zero(); //for (job in [this.menu,this.roll,this.tick,this.dice,this.lush,this.goal]) job.zero(); }; const chains = { bid: [ new Chain ( { separ: uni.heart.purple, title: "Tip for Tasks", jobs: [bot.menu,bot.roll,bot.tick] } ), new Chain ( { separ: uni.dice[0], title: "Roll the Dice", jobs: [bot.dice] } ), new Chain ( { separ: uni.carrot, title: "Kindle my Cunt", jobs: [bot.lush] } ), new Chain ( { separ: uni.flag, title: "Aim for Goal", jobs: [bot.goal] } ), ], brag: function(uname) { memo = new Memo(); for ( let d of this.bid ) memo.line(d.bid()); memo.text.tout(uname); } } var brag = { once: function(uname) { chains.brag(uname); }, show: function() { this.once(); this.lines.have = 0; this.set(); }, check: function() { if (brag.lines.wait() || brag.tim.on()) return; brag.show(); }, delay: parseInt(cb.settings.notice_wait_time), lines: { // number of normal lines seen after last touting need: parseInt(cb.settings.notice_wait_lines), have: 0, wait: function() {return this.have < this.need}, }, set: function() { if (this.delay) this.tim.set(this.delay); }, init: function() { this.run(true); }, }; brag.tim = new Timer(brag.check); // #sale - // #sale = // #sale <duty> - // #sale <duty> = // #sale <duty> <amnt> - // #sale <duty> <amnt> = // #sale <duty> <amnt> - <task> // #sale <duty> <amnt> = <task> bot.sale = function(line) { let all = ["menu","dice"]; var cmd = null; let duty = null; let amnt = null; let task = null; let m = /^(\S+) ?(\S+)? ?(\S+)? ?(\S.*)?$/.exec(line); if (!m) return false; let size = m.length; ("sale vector size "+size)-slug(); switch(size) { case 5: task = m[4]; size = 4; case 4: amnt = parseInt(m[2]); if (amnt<=0||amnt!=m[2]) return false; case 3: duty = m[1]; if (all.includes(duty)) all = [duty]; else return false; case 2: cmd = m[size-1]; break; default: return false; } if (cmd!='-'&&cmd!='=') return false; if (task) { bot[duty].sale[amnt] = task; bot[duty].once[amnt] = cmd=='-'; } else for (let d of all) { let keys = amnt ? [amnt] : Object.keys(bot[d].sale); for ( let key of keys ) { if (cmd=="-") bot[d].sale[key] = null; else { delete bot[d].sale[key]; delete bot[d].once[key]; } } } return true; }; function doTip (uname,amount) { bot.count[amount] = (bot.count[amount]||0)+1, bot.total += amount; // if (!cb.limitCam_isRunning()) chains.bid[0].act(uname,amount) // if (bot.tick.par && bot.tick.par.cost==amount) bot.tick.addUser(uname); chains.bid[0].act(uname,amount); bot.goal.tip(amount); reply(); } cb.onEnter (function(usr) { brag.once(usr.user); }); cb.onTip (function(tip) { doTip(tip.from_user,parseInt(tip.amount)); }); cb.onMessage (function(msg) { const uname = msg.user; ( function() { if (!msg.is_mod && uname != cb.room_slug) return; let line = msg.m; const head = line.indexOf(cmdPrefix); // 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. line = line.substring(head+cmdPrefix.length).tidy().trim(); let args = line.toLowerCase().split(' '); let cmd = args[0]; if (!cmd) return; line = line.substring(line.toLowerCase().indexOf(cmd)+cmd.length).trim(); args.shift(); switch (cmd) { case 'pass': msg.m = line; return; case 'fast': milliPerMinute = 9999; break; case 'help': doHelp(args[0],uname); break; case 'tipp': doTip(uname,parseInt(args[0])); break; case 'stat': bot.stat(uname); break; case 'roll': bot.dice.act(uname,0); break; case 'show': bot.tick.scanCmd(args,line,uname); break; case 'goal': if (!bot.goal.scanCmd(line,uname)) doHelp("goal",uname); break; case 'sale': if (!bot.sale(line)) doHelp("sale",uname); break; default: return; } msg.background = colour.only; msg["X-Spam"] = true; // Don't print command messages to chat. } ) (); if (!bot.tick.par||uname==cb.room_slug); else if (cb.limitCam_userHasAccess(uname)) msg.background = colour.tick; else if (cb.limitCam_isRunning()) msg.c = colour.pale; if(!msg["X-Spam"]) { brag.lines.have++; brag.check(); } reply(); return msg; }); ("Lines in this color are visible only for you. For help type: "+cmdPrefix+"help").slug(); bot.init(); brag.show();
© Copyright Chaturbate 2011- 2024. All Rights Reserved.