Bots Home
|
Create an App
Getting busy - Pay-per-view
Author:
mozart_between_your_legs
Description
Source Code
Launch Bot
Current Users
Created by:
Mozart_Between_Your_Legs
/** * @license MIT * * Copyright (c) 2017 Mozart_Between_Your_Legs <mozart_between_your_legs@sags-per-mail.de> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.gettingBusy = factory()); }(this, (function () { 'use strict'; class EventEmitter { constructor() { this._listeners = new Map(); } on(event, listener) { this.addListener(event, listener); return () => this.removeListener(event, listener); } once(event, listener) { if ("function" !== typeof listener) throw new TypeError("Listener needs to be a function"); const wrapped = (...args) => { this.removeListener(event, wrapped); listener.apply(null, args); }; return this.on(event, wrapped); } getListeners(event) { return this._listeners.get(event) || new Set(); } addListeners(map) { for (const event in map) this.addListener(event, map[event]); return this; } addListener(event, listener) { if ("function" !== typeof listener) throw new TypeError("Listener needs to be a function"); let listeners = this._listeners.get(event); if (!listeners) this._listeners.set(event, listeners = new Set()); listeners.add(listener); return this; } removeListeners(map) { for (const event in map) this.removeListener(event, map[event]); return this; } removeListener(event, listener) { const listeners = this._listeners.get(event); if (listeners) { listeners.delete(listener); if (!listeners.size) this._listeners.delete(event); } return this; } removelisteners(event = null) { if (event) { this._listeners.delete(event); } else { this._listeners.clear(); } return this; } emit(event, ...args) { let value = undefined; for (const listener of this.getListeners(event)) if (value = listener.apply(this, args)) return value; return value; } } class Message { constructor(data) { this._data = { "m": data["m"], "c": data["c"], "f": data["f"], "background": data["background"], "X-Spam": data["X-Spam"], "X-Denied": data["X-Denied"], }; } get text() { return this._data["m"]; } set text(value) { this._data["m"] = String(value); } get color() { return this._data["c"]; } set color(value) { this._data["c"] = String(value); } get font() { return this._data["f"]; } set font(value) { this._data["f"] = String(value); } get background() { return this._data["background"]; } set background(value) { this._data["background"] = String(value); } isSpam() { return !!this._data["X-Spam"]; } setSpam(flag = true) { this._data["X-Spam"] = flag ? true : undefined; return this; } isDenied() { return this._data["X-Denied"]; } setDenied(message = "Your message was denied.") { this._data["X-Denied"] = message ? String(message) : undefined; return this; } toJSON() { return this._data; } } class Commands extends Map { parse(message, ...args) { if (!(message instanceof Message)) throw new TypeError("Message needs to be an instance of Message."); if (message.isSpam() || message.isDenied()) { return null; } else { const callback = this.match(message.text); if (callback) { try { callback(message, ...args); } catch (error) { if (!(error instanceof Error)) error = new Error(error); message.setDenied(`${error.name} with "${message.text}": ${error.message}`); } return message.isDenied() ? message : message.setSpam(true); } return null; } } match(text) { for (const [ pattern, callback ] of this) { const match = text.match(pattern); if (match) { const [ , ...params ] = match; return function(...args) { return callback(...args, ...params); }; } } return null; } set(pattern, callback) { if (!(pattern instanceof RegExp) && ("string" !== typeof pattern)) { throw new TypeError("Pattern needs to be string or instance of RegExp"); } else if ("function" !== typeof callback) { throw new TypeError("Callback needs to be a function"); } return super.set(pattern, callback); } } var UserStatus = Object.freeze({ NONE: 0b0000000, HAS_TOKENS: 0b0000001, TIPPED_RECENTLY: 0b0000010, TIPPED_ALOT_RECENTLY: 0b0000110, TIPPED_TONS_RECENTLY: 0b0001110, IS_MODERATOR: 0b0010000, IS_FAN: 0b0100000, IS_BROADCASTER: 0b1000000, }); const users = new Map(); class User extends EventEmitter { static get(username) { let user = users.get(username); if (!user) users.set(username, user = new User({ "user": username })); return user; } constructor(data) { super(); this._data = { "user": data["user"], "gender": data["gender"], "is_mod": data["is_mod"], "in_fanclub": data["in_fanclub"], "has_tokens": data["has_tokens"], "tipped_recently": data["tipped_recently"], "tipped_alot_recently": data["tipped_alot_recently"], "tipped_tons_recently": data["tipped_tons_recently"], }; } updateFromData(data) { if (data["user"] !== this.username) throw new Error(`Data does not match this user.`); Object.assign(this._data, { "gender": data["gender"], "is_mod": data["is_mod"], "in_fanclub": data["in_fanclub"], "has_tokens": data["has_tokens"], "tipped_recently": data["tipped_recently"], "tipped_alot_recently": data["tipped_alot_recently"], "tipped_tons_recently": data["tipped_tons_recently"], }); return this; } updateFromTipData(data) { if (data["from_user"] !== this.username) throw new Error(`Tip data does not match this user.`); Object.assign(this._data, { "gender": data["from_user_gender"], "is_mod": data["from_user_is_mod"], "in_fanclub": data["from_user_in_fanclub"], "has_tokens": data["from_user_has_tokens"], "tipped_recently": data["from_user_tipped_recently"], "tipped_alot_recently": data["from_user_tipped_alot_recently"], "tipped_tons_recently": data["from_user_tipped_tons_recently"], }); return this; } get username() { return this._data["user"]; } get gender() { return this._data["gender"]; } get status() { let status = 0; if (this.hasTokens()) status |= UserStatus.HAS_TOKENS; if (this.isModerator()) status |= UserStatus.IS_MODERATOR; if (this.isFan()) status |= UserStatus.IS_FAN; if (this.isBroadcaster()) status |= UserStatus.IS_BROADCASTER; switch (true) { case this.tippedTonsRecently(): status |= UserStatus.TIPPED_TONS_RECENTLY; break; case this.tippedAlotRecently(): status |= UserStatus.TIPPED_ALOT_RECENTLY; break; case this.tippedRecently(): status |= UserStatus.TIPPED_RECENTLY; break; } return status; } isModerator() { return this._data["is_mod"]; } isFan() { return this._data["in_fanclub"]; } isBroadcaster() { return this._data["user"] == cb.room_slug; } hasTokens() { return this._data["has_tokens"]; } tippedRecently() { return this._data["tipped_recently"]; } tippedAlotRecently() { return this._data["tipped_alot_recently"]; } tippedTonsRecently() { return this._data["tipped_tons_recently"]; } toString(pretty = false) { return pretty ? this.username.replace(/(?:^|_)./g, m => m.toUpperCase()) : this.username; } toJSON() { return this._data; } } class Viewers extends Set { constructor(room) { if (!(room instanceof Room)) throw new TypeError("Room needs to be an instance of Room"); super(); this._room = room; this._userListeners = { enter: function(r) { (r === room) && room.emit("enter", this); }, leave: function(r) { (r === room) && room.emit("leave", this); }, message: function(message) { room.emit("message", this, message); }, tip: function(tip) { room.emit("tip", this, tip); }, tipOptions: function() { room.emit("tipOptions", this); }, drawPanel: function() { room.emit("drawPanel", this); }, }; } get room() { return this._room; } add(user) { if (!(user instanceof User)) throw new TypeError("User needs to be an instance of User."); if (!this.has(user)) { super.add(user); user.addListeners(this._userListeners); user.emit("enter", this.room); } return this; } delete(user) { if (this.has(user)) { super.delete(user); user.emit("leave", this.room); user.removeListeners(this._userListeners); } return this; } clear() { for (const user of this) this.delete(user); return super.clear(); } } class Room extends EventEmitter { constructor() { super(); this._commands = new Commands(); this._viewers = new Viewers(this); this.addListener("message", (user, message) => this.commands.parse(message, user)); } get viewers() { return this._viewers; } get commands() { return this._commands; } sendNotice(message, color, background, weight) { for (const user of this.viewers) chaturbate.sendNoticeToUser(user, message, color, background, weight); return this; } } class Tip { constructor(data) { this._data = { "amount": data["amount"], "message": data["message"], }; } get amount() { return this._data["amount"]; } get note() { return this._data["message"]; } toJSON() { return this._data; } } const MIN_TIMEOUT = 10; const MIN_INTERVAL = 25; class Timer { constructor(callback) { if ("function" !== typeof callback) throw new TypeError("Callback needs to be a function"); this.callback = callback; this.scheduled = Date.now(); } schedule() {} call() { this.scheduled && this.callback(Date.now() - this.scheduled); } clear() { this.scheduled = false; } } class Immediate extends Timer { constructor(callback) { super(callback); this.schedule(); } schedule() { Promise.resolve().then(this.call.bind(this)); } } class Timeout extends Timer { constructor(callback, timeout = MIN_TIMEOUT) { if (!Number.isFinite(timeout)) { throw new TypeError("Timeout needs to be a finite number"); } else if (timeout < 0) { throw new RangeError("Timeout needs to be a non-negative number"); } super(callback); this.timeout = timeout; this.schedule(); } schedule(timeout = this.timeout) { cb.setTimeout(this.call.bind(this), Math.max(MIN_TIMEOUT, timeout)); } } class Interval extends Timer { constructor(callback, interval = MIN_INTERVAL) { if (!Number.isFinite(interval)) { throw new TypeError("Interval needs to be a finite number"); } else if (interval <= 0) { throw new RangeError("Interval needs to be a positive number"); } super(callback); this.interval = interval; this.schedule(); } schedule(timeout = this.interval) { cb.setTimeout(this.call.bind(this), Math.max(MIN_INTERVAL, timeout)); } call() { const now = Date.now(); super.call(); if (this.scheduled) { this.scheduled += this.interval; this.schedule(this.scheduled - now + this.interval); } } } var Timer$1 = { setImmediate(callback) { return new Immediate(callback); }, setTimeout(callback, timeout) { return new Timeout(callback, timeout); }, setInterval(callback, interval) { return new Interval(callback, interval); }, clearImmediate(timer) { if (timer instanceof Immediate) timer.clear(); }, clearTimeout(timer) { if (timer instanceof Timeout) timer.clear(); }, clearInterval(timer) { if (timer instanceof Interval) timer.clear(); }, }; const commands = new Commands(); const broadcaster$1 = User.get(cb["room_slug"]).addListener("message", (message) => { return commands.parse(message); }); class Broadcaster extends User { get commands() { return commands; } } var broadcaster$1$1 = Object.setPrototypeOf(broadcaster$1, Broadcaster.prototype); var settings = new class Settings { constructor() { Object.assign(this, cb.settings || {}); cb.settings_choices = []; } init(config) { cb.settings_choices.push(config); return (config.name in this) ? this[config.name] : config.defaultValue; } integer(name, params = {}) { const config = Object.assign({ label: name, minValue: Number.MIN_SAFE_INTEGER, maxValue: Number.MAX_SAFE_INTEGER, defaultValue: 0, }, params, { name, type: "int" }); return Number(this.init(config)); } string(name, params = {}) { const config = Object.assign({ label: name, minLength: 0, maxLength: Number.MAX_SAFE_INTEGER, defaultValue: 0, }, params, { name, type: "str" }); return String(this.init(config)); } choice(name, choices = [], params = {}) { const config = Object.assign({ label: name, defaultValue: null, }, params, { name, type: "choice" }); let num = 1; for (const value of choices) config[`choice${num++}`] = String(value); return String(this.init(config)); } select(name, options = {}, params = {}) { const defaultValue = params.defaultValue; const labels = Object.keys(options).map(value => { if (value == defaultValue) params.defaultValue = options[value]; return options[value]; }); const chosen = this.choice(name, labels, params); return Object.keys(options).find(value => { return chosen == options[value]; }) || defaultValue; } }; var limitCam$1 = new class LimitCam extends Room { constructor() { cb.limitCam_stop(); cb.log(`limitCam_stop()`); super(); this.addListeners({ enter: (user) => { if (this.isStarted()) { cb.limitCam_addUsers([ user.username ]); cb.log(`limitCam_addUsers(["${user.username}"])`); } }, leave: (user) => { if (this.isStarted()) { cb.limitCam_removeUsers([ user.username ]); cb.log(`limitCam_removeUsers(["${user.username}"])`); } }, }); } isStarted() { return cb.limitCam_isRunning(); } start(title = null) { if (!this.isStarted()) { cb.limitCam_removeAllUsers(); cb.log(`limitCam_removeAllUsers()`); const users = Array.from(this.viewers).map(String); cb.limitCam_start(title, users); cb.log(`limitCam_start(${JSON.stringify(title)}, ${JSON.stringify(users)})`); this.emit("start"); } else { cb.limitCam_start(title); cb.log(`limitCam_start(${JSON.stringify(title)})`); } return this; } stop() { if (this.isStarted()) { cb.limitCam_stop(); cb.log(`limitCam_stop()`); this.emit("stop"); } else { cb.limitCam_stop(); cb.log(`limitCam_stop()`); } return this; } }; class Panel { constructor(template, values = [], labels = []) { this._template = template; this._values = Array.prototype.slice.call(values, 0, 3); this._labels = Array.prototype.slice.call(labels, 0, 3); } get template() { return this._template; } set template(value) { if (!["3_rows_11_21_31", "3_rows_12_21_31", "3_rows_12_22_31", "3_rows_of_labels"].includes(value)) throw new ReferenceError(`Unknown template "${value}".`); this._template = value; } get values() { return this._values; } get labels() { return this._labels; } toJSON() { const properties = { template: this._template }; for (let i = 0; i < 3; i++) { properties[`row${i+1}_value`] = this._values[i]; properties[`row${i+1}_label`] = this._labels[i]; } return properties; } } var defaultPanel = new Panel("3_rows_of_labels"); function sendNotice(message, user = null, bg = null, color = null, weight = null, group = null) { if (user instanceof User) user = user.username; if (Array.isArray(message)) message = message.join("\n"); if (Number.isInteger(bg)) bg = `#${(0xFFFFFF & bg | 0x1000000).toString(16).substr(-6, 6)}`; if (Number.isInteger(color)) color = `#${(0xFFFFFF & color | 0x1000000).toString(16).substr(-6, 6)}`; Promise.resolve().then(() => { return cb.sendNotice(message, user, bg, color, weight, group); }); } var chaturbate$2 = new class Chaturbate extends Room { constructor() { super(); cb.onEnter(data => this.viewers.add(User.get(data["user"]).updateFromData(data))); cb.onLeave(data => this.viewers.delete(User.get(data["user"]).updateFromData(data))); cb.onTip(data => { const tip = new Tip(data); const user = User.get(data["from_user"]).updateFromTipData(data); return user.emit("tip", tip) || tip; }); cb.onMessage(data => { const message = new Message(data); const user = User.get(data["user"]).updateFromData(data); return user.emit("message", message) || message; }); cb.onDrawPanel(data => { if (data["user"]) { const user = User.get(data["user"]).updateFromData(data); return user.emit("drawPanel") || this.defaultPanel; } else return this.defaultPanel; }); cb.tipOptions(username => { const user = User.get(username); return user.emit("tipOptions") || null; }); // make broadcaster enter the room in next tick Promise.resolve().then(() => this.viewers.add(this.broadcaster)); // tell viewers to reload, so they officially enter the room this.sendNoticeToLightBlueUsers("All viewers have to reload the chat room for this app/bot to work properly! If you can see this notice, press [F5] on Windows, [cmd]+[R] on Mac, or just click the \"reload\" button in your browser.", 0xFFFFFF, 0xFF0000); } get appId() { return cb.app_id; } get slot() { return cb.slot; } get roomname() { return cb.room_slug; } get broadcaster() { return broadcaster$1$1; } get settings() { return settings; } get limitCam() { return limitCam$1; } get defaultPanel() { return defaultPanel; } log(message) { cb.log(String(message)); return this; } sendNoticeToPublic(m, c, b, w) { sendNotice(m, null, b, c, w, null); return this; } sendNoticeToUser(user, m, c, b, w) { sendNotice(m, user, b, c, w, null); return this; } sendNoticeToGroup(group, m, c, b, w) { sendNotice(m, null, b, c, w, group); return this; } sendNoticeToRedUsers(m, c, b, w) { sendNotice(m, null, b, c, w, "red"); return this; } sendNoticeToGreenUsers(m, c, b, w) { sendNotice(m, null, b, c, w, "green"); return this; } sendNoticeToLightBlueUsers(m, c, b, w) { sendNotice(m, null, b, c, w, "lightblue"); return this; } sendNoticeToDarkBlueUsers(m, c, b, w) { sendNotice(m, null, b, c, w, "darkblue"); return this; } sendNoticeToLightPurpleUsers(m, c, b, w) { sendNotice(m, null, b, c, w, "lightpurple"); return this; } sendNoticeToDarkPurpleUsers(m, c, b, w) { sendNotice(m, null, b, c, w, "darkpurple"); return this; } drawPanel(user = null) { cb.drawPanel(user && String(user)); return this; } changeRoomSubject(subject) { cb.changeRoomSubject(String(subject)); return this; } setImmediate(callback) { return Timer$1.setImmediate(callback); } setTimeout(callback, timeout) { return Timer$1.setTimeout(callback, timeout); } setInterval(callback, interval) { return Timer$1.setInterval(callback, interval); } clearImmediate(timer) { Timer$1.clearImmediate(timer); } clearTimeout(timer) { Timer$1.clearTimeout(timer); } clearInterval(timer) { Timer$1.clearInterval(timer); } }; const createStyle = (() => { function hue2rgb(p, q, t) { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1/6) return p + (q - p) * 6 * t; if (t < 1/2) return q; if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; return p; } function hsl2hex(h, s, l) { let r, g, b; if (s == 0) { r = g = b = l; // achromatic } else { const q = l < 0.5 ? l * (1 + s) : l + s - l * s; const p = 2 * l - q; r = hue2rgb(p, q, h + 1/3); g = hue2rgb(p, q, h); b = hue2rgb(p, q, h - 1/3); } return Math.round(r * 0xFF) << 16 | Math.round(g * 0xFF) << 8 | Math.round(b * 0xFF); } return function createStyle(hue, luminance = 0.64) { return { "default": [ 0x202020, hsl2hex(hue, 1, luminance), "bold" ], "light": [ 0x000000, hsl2hex(hue, 1, Math.min(1, luminance * 1.3)) ], }; }; })(); var styles = { "orange": createStyle(23/360), "lime": createStyle(83/360), "spring": createStyle(143/360, 0.58), "sky": createStyle(203/360), "purple": createStyle(263/360, 0.72), "orchid": createStyle(323/360, 0.72), "black": { "default": [ 0xFFFFFF, 0x202020, "bold" ], "light": [ 0xFFFFFF, 0x606060 ] }, }; const userGroups = { [ UserStatus.NONE ]: "nobody", [ UserStatus.IS_MODERATOR ]: "mods only", [ UserStatus.IS_FAN ]: "fans only", [ UserStatus.IS_MODERATOR | UserStatus.IS_FAN ]: "mods and fans", }; function parse(value) { let userGroup = UserStatus.NONE; if (!/no/i.test(value)) { if (/mod/i.test(value)) userGroup |= UserStatus.IS_MODERATOR; if (/fan/i.test(value)) userGroup |= UserStatus.IS_FAN; if (userGroup == UserStatus.NONE) throw new Error(`Unable to parse "${value}" as user group. Valid values are: ` + Object.keys(userGroups).map(status => `"${userGroups[status]}"`).join(", ")); } return userGroup; } var settings$1 = { pricePerMinute: Number(chaturbate$2.settings.integer("pricePerMinute", { label: "Price per minute [tks/min]", minValue: 1, defaultValue: 12, })), freeMinutes: Number(chaturbate$2.settings.integer("freeMinutes", { label: "Free preview time (0 = disabled) [minutes]", minValue: 0, defaultValue: 2, })), unlimitedAccess: Number(chaturbate$2.settings.select("unlimitedAccess", userGroups, { label: "Grant unlimited and free access to", defaultValue: parse("fans only"), })), colorScheme: String(chaturbate$2.settings.choice("colorScheme", Object.keys(styles), { label: "Color scheme", defaultValue: Object.keys(styles).shift(), })), }; var commands$1 = [ { pattern: /^\/help$/i, syntax: "/help", description: `Display this help message.`, callback: function(message) { return this.help(); }, }, { pattern: /^\/busy(?: (.+))?$/i, syntax: "/busy [title]", description: `Start the show, go hidden. Set an optional title, for example "/busy dancing and teasing".`, callback: function(message, title = null) { return this.start(title); }, }, { pattern: /^\/back$/i, syntax: "/back", description: `End the show, go public.`, callback: function(message) { return this.stop(); }, }, { pattern: /^\/pause$/i, syntax: "/pause", description: `Pause the show. Token timer is stopped, but cam stays hidden.`, callback: function(message) { return this.pause(); }, }, { pattern: /^\/resume$/i, syntax: "/resume", description: `Resume a paused show.`, callback: function(message) { return this.resume(); }, }, { pattern: /^\/price(?: (-?[\d\.]+))?$/i, syntax: "/price [pricePerMinute]", description: `Get or set the price per minute.`, callback: function(message, pricePerMinute = null) { return this.price(pricePerMinute); }, }, { pattern: /^\/free(?: (-?[\d\.]+))?$/i, syntax: "/free [freeMinutes]", description: `Get or set the number of free minutes new users will receive.`, callback: function(message, freeMinutes = null) { return this.free(freeMinutes); }, }, { pattern: /^\/unlimited(?: (.+))?$/i, syntax: "/unlimited [group]", description: `Get or set the group with unlimited and free access. Valid groups are: ` + Object.keys(userGroups).map(status => `"${userGroups[status]}"`).join(", "), callback: function(message, userGroup = null) { return this.unlimited(userGroup); }, }, ]; const broadcaster = chaturbate$2.broadcaster; const limitCam = chaturbate$2.limitCam; const duration = function duration(secs) { if (secs < 1) { return "0 sec"; } else if (secs === Number.POSITIVE_INFINITY) { return "unlimited time"; } else { const parts = { d: 86400, h: 3600, min: 60, sec: 1 }; for (const label of Object.keys(parts)) { const devider = parts[label]; if (secs >= devider) { parts[label] = Math.floor(secs / devider); secs %= devider; } else { delete parts[label]; } } return Object.keys(parts).map(label => `${parts[label]} ${label}`).join(" "); } }; var gettingBusy$1 = new class GettingBusy { constructor() { this._tickrate = 1.0; // seconds this._sendTickNoticeEvery = 150; // seconds this._sendRemainingNoticeAt = [ 3600, 1800, 900, 600, 300, 180, 120, 60, 30 ]; // seconds this.pricePerSecond = settings$1.pricePerMinute / 60; this.freeSeconds = settings$1.freeMinutes * 60; this.unlimitedAccess = settings$1.unlimitedAccess; this.colorScheme = settings$1.colorScheme; this._reset(); this._init(); } get pricePerSecond() { return this._pricePerSecond; } set pricePerSecond(price) { if (!Number.isFinite(price)) { throw new TypeError("Price must be a finite number."); } else if (price <= 0) { throw new RangeError("Price must be a positive number."); } this._pricePerSecond = price; } get freeSeconds() { return this._freeSeconds; } set freeSeconds(free) { if (!Number.isFinite(free)) { throw new TypeError("Free must be a finite number."); } else if (free < 0) { throw new RangeError("Free must be a non-negative number."); } this._freeSeconds = free; } get unlimitedAccess() { return this._unlimitedAccess; } set unlimitedAccess(group) { if (!(String(group) in userGroups)) throw new ReferenceError("Invalid group."); if (this._unlimitedAccess !== group) { this._unlimitedAccess = group; this._refresh(); } } get colorScheme() { return this._colorScheme; } set colorScheme(colorScheme) { if (!(colorScheme in styles)) throw new ReferenceError("Invalid color scheme."); this._colorScheme = colorScheme; } _sendNoticeToPublic(message, style = "default") { chaturbate$2.sendNoticeToPublic(message, ...styles[this._colorScheme][style]); return this; } _sendNoticeToUser(user, message, style = "light") { chaturbate$2.sendNoticeToUser(user, message, ...styles[this._colorScheme][style]); return this; } _matchesInterval(time, interval) { return (time % interval) < this._tickrate; } _matchesTimes(time, times) { return times.some(t => (time >= t) && ((time - t) < this._tickrate)); } _isStarted() { return limitCam.isStarted(); } _start(title = null) { limitCam.start(title); } _stop() { limitCam.stop(); } _isPaused() { return this._isStarted() && !this._timer; } _pause() { chaturbate$2.clearInterval(this._timer); this._timer = null; } _resume() { this._timer = chaturbate$2.setInterval(elapsed => this._tick(elapsed/1000), this._tickrate * 1000); } _init() { for (const command of commands$1) broadcaster.commands.set(command.pattern, command.callback.bind(this)); chaturbate$2.addListeners({ enter: this._onEnter.bind(this), leave: this._onLeave.bind(this), tip: this._onTip.bind(this), }); } _reset() { limitCam.viewers.clear(); this._timer = null; this._elapsed = -1; // seconds this._credits = new WeakMap(); // { user: seconds } this._tokens = 0; this._title = null; return this._sendNoticeToUser(broadcaster, `Type "/busy" if you want to get busy, "/busy dancing and teasing" if you are busy dancing and teasing, or "/help" for a list of available commands.`); } _refresh() { for (const user of chaturbate$2.viewers) if (user !== broadcaster) this._ping(user); } _tick(elapsed = this._tickrate) { this._elapsed += elapsed; for (const user of limitCam.viewers) this._ping(user, -elapsed); if (this._matchesInterval(this._elapsed, this._sendTickNoticeEvery)) { this._sendNoticeToUser(broadcaster, `${limitCam.viewers.size} users are peeking, ` + `${duration(this.freeSeconds)} free preview time, ` + `${userGroups[this.unlimitedAccess]} gets unlimited and free access.` ); this._sendNoticeToPublic( `${this._title} ` + `Tip ${Math.ceil(this.pricePerSecond * this._tickrate)} tks or more to peek into my show ` + `(${this.pricePerSecond * 60} tks/min).` ); } } _ping(user, delta = 0) { const notices = []; // Load, is undefined for new users let credits = this._credits.get(user); // Test for unlimited if ((Number.POSITIVE_INFINITY !== credits) && (this.unlimitedAccess & user.status)) { credits = Number.POSITIVE_INFINITY; notices.push(`You were granted unlimited and free access to the show. Enjoy!`); } else if ((Number.POSITIVE_INFINITY == credits) && !(this.unlimitedAccess & user.status)) { credits = undefined; notices.push(`Your unlimited and free access to the show was revoked.`); } // Give free credits if (('number' !== typeof credits) && (credits = this.freeSeconds)) notices.push(`You received ${duration(this.freeSeconds)} free preview time: ${duration(credits)} remaining.`); // Save this._credits.set(user, credits = Math.max(0, credits + delta)); // Update limitCam viewer if ((credits >= this._tickrate) && !limitCam.viewers.has(user)) { // Add user to show limitCam.viewers.add(user); } else if ((credits < this._tickrate) && limitCam.viewers.has(user)) { // Remove user from show limitCam.viewers.delete(user); notices.push( `${duration(0)} remaining. ` + `Tip ${Math.ceil(this.pricePerSecond * this._tickrate)} tks or more to peek into my show ` + `(${this.pricePerSecond * 60} tks/min).` ); } else if (limitCam.viewers.has(user)) { // User stays in show if (!notices.length && (!delta || this._matchesTimes(credits, this._sendRemainingNoticeAt))) { if (Number.POSITIVE_INFINITY == credits) { notices.push(`You have unlimited and free access to the show. Enjoy!`); } else { notices.push(`${duration(credits)} remaining.`); } } } // Send notices in line if (notices.length) this._sendNoticeToUser(user, notices); return credits; } _onEnter(user) { if (!this._isStarted()) return; this._sendNoticeToUser(user, `${this._title} ` + `Tip ${Math.ceil(this.pricePerSecond * this._tickrate)} tks or more to peek into my show ` + `(${this.pricePerSecond * 60} tks/min).` , "default"); if (this._isPaused()) this._sendNoticeToUser(user, `The broadcaster paused the show. The timer is stopped.`, "default"); if (user === broadcaster) { if (this._isPaused()) this._sendNoticeToUser(broadcaster, `Type "/resume" to resume the show or "/back" to stop it.`); } else { this._ping(user); } } _onLeave(user) { if (!this._isStarted()) return; if (user === broadcaster) if (!this._isPaused()) this.pause(); } _onTip(user, tip) { if (!this._isStarted()) return; const addCredits = tip.amount / this.pricePerSecond; const credits = this._ping(user, addCredits); if (Number.POSITIVE_INFINITY === credits) { this._sendNoticeToUser(user, `Thank you!`); } else if (credits >= this._tickrate) { this._sendNoticeToUser(user, `Thank you! ` + `${duration(addCredits)} were added to your time: ${duration(credits)} remaining.` ); } this._tokens += tip.amount; } start(title = null) { if (this._isStarted()) throw new Error("Unable to start the show, it is already started."); this._title = title ? `I am busy ${title} right now!` : `I am getting busy right now!`; this._refresh(); this._start(this._title); this._resume(); Promise.resolve().then(() => this._tick()); return this; } stop() { if (!this._isStarted()) throw new Error("Unable to stop the show, it was not started yet."); this._sendNoticeToUser(broadcaster, `You made ${this._tokens} tks with this ${duration(this._elapsed)} show. ` + `If you are happy, please recommend this bot to your friends. Thank you!` ); this._pause(); this._stop(); this._reset(); return this._sendNoticeToPublic(`I am done getting busy. Thank you for staying with me!`); } pause() { if (!this._isStarted()) { throw new Error("Unable to pause the show, it was not started yet."); } else if (this._isPaused()) { throw new Error("Unable to pause the show, it is already paused."); } this._pause(); this._sendNoticeToPublic(`The broadcaster paused the show. The timer is stopped.`); this._sendNoticeToUser(broadcaster, `Type "/resume" to resume the show or "/back" to stop it.`); return this; } resume() { if (!this._isStarted()) { throw new Error("Unable to resume the show, it was not started yet."); } else if (!this._isPaused()) { throw new Error("Unable to resume the show, it was not paused yet."); } this._resume(); this._sendNoticeToPublic(`The broadcaster resumed the show. The timer is ticking again.`); this._sendNoticeToUser(broadcaster, `Type "/pause" to pause the show or "/back" to stop it.`); Promise.resolve().then(() => this._tick(0)); // 0 increment return this; } price(value = null) { if (value !== null) { this.pricePerSecond = Number(value) / 60; return this._sendNoticeToPublic( `The broadcaster set a new price to peek: ${this.pricePerSecond * 60} tks/min.` ); } else { return this._sendNoticeToUser(broadcaster, `The price to peek is ${this.pricePerSecond * 60} tks/min. ` + `Type "/price 7" to change it to 7 tks/min.` ); } } free(value = null) { if (value !== null) { this.freeSeconds = Number(value) * 60; return this._sendNoticeToPublic( `The broadcaster set a new free preview time: ${duration(this.freeSeconds)}.` ); } else { return this._sendNoticeToUser(broadcaster, `Users get ${duration(this.freeSeconds)} free preview time. ` + `Type "/free 4" to change it to 4 min or "/free 0" to disable it.` ); } } unlimited(value = null) { if (value !== null) { this.unlimitedAccess = parse(value); return this._sendNoticeToPublic( `The broadcaster now grants unlimited and free access to ${userGroups[this.unlimitedAccess]}.` ); } else { return this._sendNoticeToUser(broadcaster, `Unlimited and free access is granted to ${userGroups[this.unlimitedAccess]}. ` + `Type "/unlimited mods and fans" to change it to mods and fans.` ); } } help() { const notice = commands$1.map(command => `${command.syntax} -- ${command.description}`); return this._sendNoticeToUser(broadcaster, [ `Getting busy - Pay-per-view`, `Available commands are:`, ...notice, ]); } }; return gettingBusy$1; })));
© Copyright Chaturbate 2011- 2026. All Rights Reserved.