Apps Home
|
My Uploads
|
Create an App
ardo_colors
Author:
ardo_account
Description
Source Code
Launch App
Current Users
Created by:
Ardo_Account
App Images
// chat.js // imported into another file, compiled with rollup, uploaded to cb const SESSION_SEED = Math.floor(Math.random() * Math.pow(2,20)); const emoji = { EXCLAMATION: unescape('%u26A0%uFE0F'), WHITE_EXCLAMATION: unescape('%u2755'), STOP_SIGN: unescape('%uD83D%uDED1'), RIGHT_ARROW: unescape('%u27F6'), RIGHT_DOUBLE_ARROW: unescape('%u27F9'), // 💧 RAINDROP: unescape('%uD83D%uDCA7'), // 🔥 FLAME: unescape('%uD83D%uDD25'), // 🍆 EGGPLANT: unescape('%uD83C%uDF46'), }; // https://gist.github.com/iperelivskiy/4110988 const utils = { ObjectEntries: (obj) => { // Object.entries polyfill const ownProps = Object.keys( obj ); let i = ownProps.length; const resArray = new Array(i); // preallocate the Array while (i--) resArray[i] = [ownProps[i], obj[ownProps[i]]]; return resArray; }, hashStringToInt: function (s) { /* Simple hash function. */ var a = 1, c = 0, h, o; if (s) { a = 0; /*jshint plusplus:false bitwise:false*/ for (h = s.length - 1; h >= 0; h--) { o = s.charCodeAt(h); a = (a<<6&268435455) + o + (o<<14); c = a & 266338304; a = c!==0?a^c>>21:a; } } return String(a); }, isStringOfInt: function (_s) { return `${parseInt(_s)}` === _s; }, noop: function () {}, }; const renameOffensiveAbbr = function (obj) { if (obj.gender && obj.gender === 's') { // 't' for Transgender // using appropriate terms , when possible, makes it look // less exploitative obj.gender = 't'; } // peeve. but, i would have called this property "username", // rather than "user" if (obj.user) { obj.username = obj.user; // obj.user = undefined; } return obj; }; const chat = { notice: (message, opts) => { if (typeof opts !== 'object') { opts = {to: opts}; } chat.noticePrefixes.forEach(function(prefix){ if (typeof prefix === 'function') { message = prefix(opts) + message; } else { message = prefix + message; } }); let {to, color, background, fontWeight, group} = opts; cb.sendNotice(message, to, background, color, fontWeight, group); }, room_slug: cb.room_slug, noticePrefixes: [], sessionSeed () { return SESSION_SEED; }, utils, constants: { emoji, }, }; { chat.users = {}; chat.tippers = {}; chat.users[cb.room_slug] = true; chat.queryUser = (userMatch) => { if (!userMatch) { throw new Error(`bad user query`); } else if (userMatch.length < 3) { throw new Error(`query is too short: '${userMatch}'`); } const userRe = new RegExp(`^${userMatch}`, 'i'); const matches = []; Object.keys(chat.users).forEach((username) => { if (username.match(userRe)) { matches.push(username); } }); if (matches.length === 0) { throw new Error(`No matching user found: '${userMatch}'`); } else if (matches.length > 1) { throw new Error(`Multiple users found matching query: ${matches.join(' ')}`); } return matches[0] }; } { const onEnterCallbacks = []; const onLeaveCallbacks = []; const cbOnEnter = cb.onEnter; const cbOnLeave = cb.onLeave; chat.onEnter = function (callback) { onEnterCallbacks.push({ fn: callback, }); }; chat.onLeave = function (callback) { onLeaveCallbacks.push({ fn: callback, }); }; cb.onEnter(function (_acct) { const acct = renameOffensiveAbbr(_acct); const { username } = acct; chat.users[username] = true; onEnterCallbacks.forEach(function ({ fn }) { fn(acct); }); }); cb.onLeave(function (_acct) { const acct = renameOffensiveAbbr(_acct); const { username } = acct; chat.users[username] = true; onLeaveCallbacks.forEach(function ({ fn }) { fn(acct); }); }); cb.onEnter = function (fn) { chat.onEnter(function(u){ cb.log(`use of cb.onEnter is discouraged with this library use chat.onEnter(...) which guarantees that the code will run onEnter`); fn(u); }); }; cb.onLeave = function (fn) { chat.onLeave(function(u){ cb.log(`use of cb.onLeave is discouraged with this library use chat.onLeave(...) which guarantees that the code will run onEnter`); fn(u); }); }; } // establishing onMessage callbacks { class MsgContext { constructor (msg) { this._msg = msg; this.sender = msg.user; this.origin = this.sender; this.prependers = []; this.text = msg.m || ''; } snuff () { return this.preventDefault(); } unsnuff () { this._msg['X-Spam'] = false; return this; } preventDefault () { this._msg['X-Spam'] = true; return this; } isSnuffed () { return this._msg['X-Spam'] === false; } words () { return this.text.split(/\s+/g); } reject (rejectMsg) { this.preventDefault(); if (rejectMsg) { this.setPrefix(emoji.STOP_SIGN + ' '); this.setFont('Arial Narrow'); this.setColor('rgb(199, 86, 0)', 'rgb(255, 218, 217)'); chat.notice(`${emoji.STOP_SIGN} ${rejectMsg}`, this.origin); } return this; } setPrefix (prefix) { this.prependers.push(prefix); // this._msg.m = `${prefix}${this._msg.m}`; } isOwner () { return this.origin === cb.room_slug; } setFont (fontName) { this._msg.f = fontName; } setColor (foreground, background) { if (foreground) { this._msg['c'] = foreground; } if (background) { this._msg['background'] = background; } return this; } isMod () { return this.isOwner() || this._msg.is_mod; } finish () { let prefix = ''; if (this.prependers.length > 0) { prefix = this.prependers.join(''); this._msg.m = `${prefix}${this.delim || ''}${this.text}`; } else { this._msg.m = this.text; } } } const onMessageCallbacks = []; cb.onMessage((msg) => { if (!chat.users[msg.user]) { chat.users[msg.user] = true; } try { var context = new MsgContext(msg); for (var i=0; i < onMessageCallbacks.length; i++) { onMessageCallbacks[i](context); } return context.finish(); } catch (e) { cb.log(`Error: ${e.message}`); } }); chat.onMessage = (callback) => { onMessageCallbacks.push(callback); }; } // Command { const commands = new Map(); function pushHelpString (strOrArray, prefix='') { if (Array.isArray(strOrArray)) { if (prefix) { return strOrArray.map((line)=>{ return `${prefix}${line}`; }).join('\n'); } } return prefix + strOrArray; } class Command { constructor (whichCmd, opts) { this.id = whichCmd; this.opts = opts; this.callback = opts.callback; this.calledWithNoArgs = opts.calledWithNoArgs; this.onlyMods = opts.onlyMods; this.onlyOwner = opts.onlyOwner; this._detailedHelp = [ pushHelpString(opts.detailedHelp, `${emoji.RIGHT_ARROW} `), ].join('\n'); if (opts.help && Array.isArray(opts.help)) { opts.help = opts.help.join('\n'); } this._help = opts.help || ''; this.helpStyle = [ opts.onlyMods ? '#5416c3' : (opts.onlyOwner ? '#c31616' : '#1d8a04'), opts.important ? 'bold' : 'normal', ]; } callFn (arg, ctx) { ctx.hasArgs = !(arg === undefined || arg === ''); if (ctx.hasArgs) { ctx.arg = arg; } if (typeof this.callback === 'function') { this.callback(ctx); } else { throw new Error(`Could not call command: "${this.id}"`); } } } const onCommand = (whichCmd, opts) => { if (typeof opts === "function") { opts = {callback: opts}; } if (opts.callback && "function" === typeof opts.callback) { if (commands.get(whichCmd)) { throw new Error(`Command "${whichCmd}" is already defined`); } commands.set(whichCmd, [new Command(whichCmd, opts)]); } }; const callCommand = (whichCmd, arg, ctx) => { let clist = commands.get(whichCmd); if (!clist) { throw new Error(`Command not found: "${whichCmd}"`); } for (let i = 0, cl = clist.length; i < cl; i++) { clist[i].callFn(arg, ctx); } }; onCommand("help", { help: ['/help - displays available commands', '/help [command] - displays more info. example: [/help link]'], callback: function(ctx){ ctx.snuff(); let showCommands = commands; let showDetailedHelp = false; if (ctx.arg && !commands.get(ctx.arg)) { return ctx.reject(`Command not found: ${ctx.arg}`); } else if (ctx.arg) { showCommands = [commands.get(ctx.arg)]; showDetailedHelp = true; } const mod = ctx.isMod(); const owner = ctx.isOwner(); let messages = []; showCommands.forEach(([command]) => { if (command._help === '') { return; } let _help = [command._help]; if (showDetailedHelp) { _help.push(command._detailedHelp || 'No more details available for this command'); } const helpings = [_help.join('\n'), command.helpStyle[0], command.helpStyle[1]]; if (owner) { messages.push(helpings); } else if (mod) { if (!command.onlyOwner) { messages.push(helpings); } } else { if (!command.onlyMods && !command.onlyOwner) { messages.push(helpings); } } }); messages.forEach(([multilineHelp, color, fontWeight])=>{ chat.notice(multilineHelp, { to: ctx.sender, color, fontWeight, }); }); } }); chat.onMessage(function(context){ var cmsg = context.text.match(/^\s*\/(\w+)(\s+.*)?$/); if (cmsg) { const [_, command, _arg] = cmsg; let arg; if (_arg && _arg.trim) { arg = _arg.trim(); } try { chat.callCommand(command, arg, context); } catch (e) { context.reject(`Command failed: "${e.message}"`); } } }); Object.assign(chat, { onCommand, callCommand, }); } { const onTipCallbacks = []; chat.onTip = (fn) => { onTipCallbacks.push(fn); }; cb.onTip(({ amount, from_user, message, })=>{ if (!chat.users[from_user]) { chat.users[from_user] = true; } if (!chat.tippers[from_user]) { chat.tippers[from_user] = 0; } chat.tippers[from_user] += amount; onTipCallbacks.forEach((otcb) => { otcb({ sender: from_user, amount, message, totalTipped: chat.tippers[from_user], }); }); }); } // chat.getTopic and chat.setTopic { // default topic is "user"'s room. (as in vanilla room) // this app cannot know about changes made directly through the website let topic = `${cb.room_slug}'s room.`; const getTopic = () => { return topic; }; const setTopic = (nextTopic) => { if (nextTopic) { topic = nextTopic; cb.changeRoomSubject(topic); } else { return false; } }; Object.assign(chat, { getTopic, setTopic, }); } let oldTopic = false; chat.onCommand('away', { help: [ `/away [optional message] (sets an away message)`, `/back (resets the room subject after being 'away')`, ], onlyMods: true, callback: (ctx) => { const str = ctx.arg; const awayMessage = !!str ? `away: ${str}` : 'away'; if (ctx.isMod()) { if (!oldTopic) { oldTopic = chat.getTopic(); } chat.notice(awayMessage); chat.setTopic(awayMessage); } else { chat.notice(`${ctx.origin} is ${awayMessage}`); } ctx.preventDefault(); }, }); chat.onCommand('back', (ctx) => { const str = ctx.arg; if (ctx.isMod()) { chat.notice('back'); if (oldTopic) { chat.setTopic(oldTopic); oldTopic = false; } } else { chat.notice(`${ctx.origin} is back`); } ctx.preventDefault(); }); const userCards = {}; const SUITS = ['Diamonds', 'Clubs', 'Hearts', 'Spades']; const RANKS = [2, 3, 4, 5, 6, 7, 8, 9, 10, 'Jack', 'Queen', 'King', 'Ace']; const RANKS_ABBR = [2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K', 'A']; const ICONS = {}; const expandSuitChar = (n) => unescape(`%u${(n + 2660)}`); [['Spades', 4, 0], ['Hearts', 1, 5], ['Diamonds', 2, 6], ['Clubs', 7, 3]].forEach(([key, outline, solid]) => { ICONS[key] = { outline: expandSuitChar(outline), solid: expandSuitChar(solid), }; }); const UNICODE_CARDS = (() => { const cards = []; // corresponding to offset in utf-8 mapping of cards [3, 2, 0, 1].forEach((suitIndex, utfOffset) => { const utfIndex = 3530 + utfOffset; [-1, 12, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, 10, 11].forEach((cardRank, utfSecondaryIndex)=> { if (cardRank > -1) { let index = suitIndex + cardRank * 4; cards[index] = { suitIndex, index, cardRank, card: unescape(`%ud83c%u${(16 * utfIndex + utfSecondaryIndex).toString(16)}`), suit: SUITS[suitIndex], suitIcon: ICONS[SUITS[suitIndex]], rank: RANKS[cardRank], rankAbbr: RANKS_ABBR[cardRank], isRed: suitIndex % 2 === 0, }; } }); }); return cards; })(); const calcCard = (username) => { if (!userCards[username]) { userCards[username] = (chat.sessionSeed() + chat.utils.hashStringToInt(username)) % 52; } return userCards[username]; }; const viewCard = (username) => { return UNICODE_CARDS[calcCard(username)]; }; let cardsActive = false; chat.onCommand('cards', { usage: '/cards', description: `deal a card to each member in the room. they can access their cards by tipping or typing '/card'`, onlyMods: true, callback: (ctx) => { if (!ctx.isMod()) { return ctx.reject('Only mods can start this app'); } if (!cardsActive) { cardsActive = true; chat.notice(`"Highest card" game has been started. This is a simple game, where everyone draws a card to see who gets the highest. Try it with: /card`); } else { cardsActive = false; ctx.snuff(); chat.notice(`"Highest card" game hidden.`); } } }); let rankingNum = 0; let sortedCards = []; let topCards = []; const SHOW_TOP = 3; function getRanks () { sortedCards = chat.utils.ObjectEntries(userCards).sort((a, b) => a[1] < b[1]); topCards = sortedCards.slice(0, SHOW_TOP); rankingNum = topCards[topCards.length-1][1]; } function listCardRanks () { let topCardInfo = topCards.map(([username, cardNumber], place)=>{ const card = UNICODE_CARDS[cardNumber]; return `${chat.constants.emoji.RIGHT_ARROW} #${place+1} with a [ ${card.rankAbbr} ${card.suitIcon.solid} ], ${username}`; }).join('\n'); chat.notice(`Cards drawn- ${topCardInfo} ${chat.constants.emoji.RIGHT_DOUBLE_ARROW} Check your card by typing '/card'`); } const showingCard = {}; chat.onCommand('card', { usage: '/card', description: `view your card`, onlyMods: false, callback: (ctx) => { const { sender, arg } = ctx; if (!cardsActive) { return ctx.reject(`the 'cards' app is not active. ask a mod or the owner to start it with the command '/cards'.`); } let player = sender; let selfPlayed = true; if (arg) { let user = chat.queryUser(arg); player = user; if (player !== sender) { selfPlayed = false; } } if (!userCards[player]) { const card = viewCard(player); showingCard[player] = true; ctx.text = ''; if (selfPlayed) { chat.notice(`${player} pulled a ${card.rank} of ${card.suit.toLowerCase()} from the deck`); } else { chat.notice(`${sender} pulled a ${card.rank} of ${card.suit.toLowerCase()} from the deck for ${player}`); } getRanks(); let curCardIndex = calcCard(player); if (curCardIndex >= rankingNum) { listCardRanks(); } } else if (showingCard[player]) { ctx.snuff(); const card = viewCard(ctx.origin); chat.notice(`Hiding your card (${card.rank} of ${card.suit})`, { to: player, }); showingCard[player] = false; } else { const card = viewCard(ctx.origin); chat.notice(`Showing your card (${card.rank} of ${card.suit})`, { to: player, }); showingCard[player] = true; } } }); chat.onMessage((ctx) => { const { sender, prependers } = ctx; if (cardsActive && userCards[sender] !== undefined && showingCard[sender]) { const card = viewCard(sender); ctx.delim = ' - '; prependers.push(`${card.rankAbbr} ${card.suitIcon.solid} `); } }); // http://paletton.com/#uid=13L0u0kllllaFw0g0qFqFg0w0aF const colors = [ ['#78020b', '#ffc1c5'], ['#af0814', '#e7c8ca'], ['#551a00', '#ffdac9'], ['#7d3717', '#e8c9bc'], ['#230339', '#e5c1fe'], ['#1f793c', '#e2cdf0'], ['#0f073b', '#c8bffb'], ['#221858', '#dfd9fb'], ['#0034b1', '#afc1ec'], ['#053196', '#d0d7e9'], ]; const colorLength = colors.length; let userColorsActive = false; const userColors = {}; chat.onCommand('usercolors', { help: '/usercolors (sets every users color)', onlyMods: true, detailedHelp: [ 'These colors are determined by the username ', 'and will persist from one session to the next.' ], callback: (ctx) => { if (!ctx.isMod()) { return ctx.reject('you cant do that'); } userColorsActive = !userColorsActive; chat.notice(userColorsActive ? `"Usercolors" activated. Each member has the same color from one day to the next. There are ${colorLength} different sets of colors.` : '"Usercolors" OFF'); } }); chat.onMessage((ctx) => { if (userColorsActive) { if (!userColors[ctx.sender]) { userColors[ctx.sender] = colors[chat.utils.hashStringToInt(ctx.sender) % colorLength]; } ctx.setColor(userColors[ctx.sender][0], userColors[ctx.sender][1]); } }); // an 'eval' command for debugging { chat.onCommand('eval', function(ctx){ let resp; let str = ctx.arg || ''; const evalCopy = eval; if (!ctx.isMod()) { return ctx.reject('You must be a moderator to run this command'); } ctx.snuff(); if(str.match(/^(\'|\")(.*)(\'|\")$/)) { str = str.match(/^(\'|\")(.*)(\'|\")$/)[2]; } try { resp = evalCopy(str); } catch (e) { resp = `Error: ${e.message}`; } chat.notice(`Evaluated response: ${resp}`, ctx.origin); }); } chat.onCommand("link", { help: '/link [url]', detailedHelp: 'A way to post URLs that might be filtered from the general chat', callback ({ sender, arg }) { chat.notice(`${sender}'s /link (copy-paste into new tab) ${arg}`); } }); // "/me [message]" // users must talk in the third person. or this looks dumb chat.onCommand('me', { help: `/me [message to broadcast] (displays a message to the room)`, detailedHelp: `Note: must be written in the third person or it won't make sense!!`, callback: function (ctx) { const status = ctx.arg; if (status !== undefined) { chat.notice(`* ${ctx.origin} ${status}`); } ctx.preventDefault(); } }); var latestMessageSenders = {}; chat.onCommand('msg', { help: '/msg [username] [message]', detailedHelp: 'A new way to direct message', callback: (ctx) => { const userAndMsg = ctx.arg || ''; const { sender } = ctx; const [_, usernameGuess, message] = userAndMsg.match(/^(\w+)\s+(.*)$/); if (!message) { return ctx.reject(`Sending message failed. Message not recognized`); } // var username = blocks[0], message = blocks.splice(1).join(" "); let username; try { username = chat.queryUser(usernameGuess); } catch (e) { username = usernameGuess; } if (username && message) { chat.notice(`Direct message from ${sender} to ${username} [ ${message} ]`, username); chat.notice(`Direct message from ${sender} to ${username} [ ${message} ]`, sender); latestMessageSenders[username] = sender; } ctx.text = `(${ctx.text})`; ctx.snuff(); } }); chat.onCommand('r', { callback: (ctx) => { const message = ctx.arg || ''; var username = latestMessageSenders[ctx.origin]; if (username) { chat.notice(`Direct message from ${ctx.origin} to ${username} [ ${username} ]`, username); chat.notice(`Direct message from ${ctx.origin} to ${username} [ ${message} ]`, ctx.origin); latestMessageSenders[username] = ctx.origin; } ctx.text = `(${ctx.text})`; ctx.snuff(); } }); let options = {}; let optionPrices = []; let optionStringRep = false; const tipMenuOptions = { color: '#ff0000', minutes: 4, messages: 10, // minMessages: 5, // minMinutes: 2, actionColor: '#ff00ff', prizeWonColor: '#42e006', }; // const tipMenuOptions = { // color: '#ff0000', // minutes: 10, // messages: 12, // minMessages: 5, // actionColor: '#ff00ff', // }; let intervalFreq = Math.floor(tipMenuOptions.minutes * 1000 * 60 / tipMenuOptions.messages); let messageCount = 0; let continueMessages = true; let blastNext = false; let _interval; // intervalFreq = Math.floor(tipMenuOptions.minutes * 1000 * 60 / tipMenuOptions.messages) const setMenuBlaster = (recalc)=> { if (recalc) { intervalFreq = Math.floor(tipMenuOptions.minutes * 60000 / tipMenuOptions.messages); } if (_interval !== undefined) { cb.cancelTimeout(_interval); } if (continueMessages) { _interval = cb.setTimeout(function () { blastIt(); if (continueMessages) { setMenuBlaster(); } }, intervalFreq); } }; const optionsString = (delim=',', spacer='') => { return optionPrices.map((price) => `${options[price]}${spacer}(${price})` ).join(delim) }; chat.onMessage(function(ctx){ if (!ctx.isSnuffed()) { messageCount++; } if (continueMessages && optionStringRep) { setMenuBlaster(false); } }); let _latest; const blastIt = (forceDisplay) => { if (optionStringRep) { if (blastNext) { forceDisplay = true; blastNext = false; } let showDisplay = false; /* if numMessages < minMessages sleep! if timeDiff < minTimeDiff sleep! if numMessages > tipMenuOptions.messages *blastIt targetTimeDiff = minMinutes * 60k if timeDiff is > than targetTimeDiff; speak! resetVals */ let curTime = new Date().getTime(); let timeDiff = (curTime - _latest); let _minMessages = tipMenuOptions.minMessages || (tipMenuOptions.messages / 2); let minTimeDiff = 60000 * tipMenuOptions.minMinutes; if (!forceDisplay) { if (messageCount < _minMessages) { cb.log(`havent sent enough messages ${messageCount}/${_minMessages}`); return; } else if (timeDiff < minTimeDiff) { cb.log(`hasnt been long enough ${Math.floor(timeDiff/6000)/10}/${Math.floor(minTimeDiff/6000)/10}`); return; } if (tipMenuOptions.messages < messageCount) { cb.log(`messageCount surpassed options.messages ${messageCount}/${tipMenuOptions.messages}`); showDisplay = true; } if ((tipMenuOptions.minutes * 60000) < timeDiff) { showDisplay = true; } } if (forceDisplay || showDisplay) { let _n = optionPrices.length; chat.notice(`${_n} item${_n === 1 ? '' : 's'} on the TipMenu: ${optionStringRep}`, { color: tipMenuOptions.color, }); messageCount = 0; _latest = new Date().getTime(); } } }; const parseOptions = (opts) => { let _optionPrices = []; let _options = {}; let lines = opts.split(/[,\|]/g); // if (lines.length === 1) { // throw new Error('cannot bulk add a single tip menu item. Try /tipmenu add [price] [item]'); // } lines.map((opt) => { const optMatch = opt.match(/\s*([^\(]+)\((\s*\d+\s*)\)\s*$/); if (!optMatch) { throw new Error(`Option '${opt}' does not match the required format: "name of option (###)"`); } const [_, optionName, price] = optMatch; const numeric = forceInt(price, `Price is not a recognized number: ${opt}`); if (_optionPrices.includes(numeric)) { throw new Error(`Prices must not repeat: ${numeric} appears twice`); } _optionPrices.push(numeric); _options[`${numeric}`] = optionName.trim(); }); setOptionPrices(_optionPrices); options = _options; optionStringRep = optionsString(' | ', ' '); }; const setOptionPrices = (_optionPrices) => { optionPrices = _optionPrices.sort(( a, b ) => a - b ); }; const addOptionPrice = (optionPrice, optionText) => { if (optionPrices.includes(optionPrice)) { throw new Error(`There is already a menu item with this price: ${options[optionPrice]} (${optionPrice}) You can either delete it: /tipmenu -${optionPrice} or change the price, for example: /tipmenu ${optionPrice}->${optionPrice+1} `); } options[optionPrice] = optionText; setOptionPrices([optionPrice].concat(optionPrices)); optionStringRep = optionsString(' | ', ' '); }; const removeOptionPrice = (optionPrice) => { let _i = optionPrices.indexOf(optionPrice); if (_i === -1) { throw new Error(`No option found at price: ${optionPrice}`); } optionPrices = optionPrices.filter((p)=> p !== optionPrice); optionStringRep = optionsString(' | ', ' '); }; const forceInt = (_maybeInt, failMessage) => { let int = parseInt(_maybeInt); if (isNaN(int) || int <= 0) { throw new Error(failMessage); } return int; }; // https://github.com/dysfunc/ascii-emoji const faceFns = [ // (nn) => { // let s = '_'.repeat(nn); // let rN = randNumInCenter(nn); // cb.log(nn); // cb.log(rN); // let _1 = s.slice(0, rN); // let _2 = s.slice(rN, nn); // // ^•ﻌ•^; // let face = unescape('%5E%u2022%uFECC%u2022%5E'); // // ฅ; // let paw = '%u0E05'; // return `${paw}${_1}${face}${_2}${paw}`; // }, (nn) => { let parenW = Math.round(nn / 6); isBig = nn > 25; return [ '('.repeat(parenW), ' ', unescape('%u2768'), ' t(', isBig ? '--__--' : '-_-', 't) ', unescape('%u2769'), ' ', ')'.repeat(parenW), ].join(''); }, ]; function facesOfLength (nn) { let index = Math.floor(Math.random()*faceFns.length); return faceFns[index](nn) } chat.onTip(function({ amount, sender, message, totalTipped, }){ // ヾ(´〇`)ノ♪♪♪ // (ᵔᴥᵔ) // [¬º-°]¬ // {•̃_•̃} // (⊃。•́‿•̀。)⊃ // (งツ)ว // ♥‿♥ // ◔_◔ // ⁽⁽ଘ( ˊᵕˋ )ଓ⁾⁾ // 。゚( ゚இ‸இ゚)゚。 messageCount += 2; if (optionPrices.includes(amount)) { let prize = options[amount]; let paddedPrize = prize.toLowerCase().replace(/\W/g, ''); let _line = '* * * * * * * * * * * * * * * * * *'; let celebrationLength = 2 + Math.round(12 * Math.log(amount) / Math.log(10)); cb.log(`celebrationLength ${celebrationLength}`); if (paddedPrize.length > 0) { while (paddedPrize.length < celebrationLength) { paddedPrize = paddedPrize + paddedPrize; } paddedPrize = paddedPrize.slice(0, celebrationLength); } chat.notice(` ${_line} * ${sender} tipped ${amount} for ${prize} ${_line}`, { color: tipMenuOptions.prizeWonColor, fontFamily: 'Verdana', fontWeight: 'bold', backgroundColor: '#eeeeee', }); chat.notice(` ${paddedPrize} ${facesOfLength(celebrationLength)} ${paddedPrize.slice(Math.floor(Math.random()*10), 2)}(^${paddedPrize.replace(/./g, '=').slice(2)}^) ${paddedPrize} `, { backgroundColor: '#3fff00', color: '#416d30', }); } }); chat.onCommand('tipmenu', { help: `/tipmenu smile @20`, detailedHelp: `There are several ways to use this command. Easiest: /tipmenu smile @20 /tipmenu 10 jumping jacks @50 -- creates a new tip menu and adds one item to it /tipmenu -20 -- removes the item priced 20 tokens from the tip menu /tipmenu 50->1000 * You can add items individually: /tipmenu add 10 something for 10tk * You can remove items individually /tipmenu remove 10 * you can list existing items /tipmenu * you can bulk load items: (separated by a "," or a "|") /tipmenu item1(10),item2(20),item3(30) * you can change settings /tipmenu setting color #ff0000 --- change color of the notice to red (or any color represented by its hex code) /tipmenu setting [settingname] [number] ((for each of these settings:)) --> *minutes* - number of minutes in between posted notices (default: 10) --> *messages* is a number. when that number of messages has been written . | it will automatically insert a new notice into the chat. (default 12) --> *minMinutes* is the minimum time between notices. if the chat is very . | active then this will prevent the notice from showing too often . | (default: half of *minutes*) --> *minMessages* is the minimum number of user messages in between . | notices. This prevents the notices from consuming the whole chat . | when users aren't talking much. (default: half of *messages*) `, onlyMods: true, callback: (ctx) => { if (!ctx.isMod()) { return ctx.reject('Only administrators can access the tip menu'); } if (!ctx.arg) { return blastIt(true); } ctx.snuff(); let info = {}; if (ctx.arg.search(/(add|remove|change|setting)/) === 0) { let [maincmd, cmd, _price, ...rest] = ctx.words(); cb.log('cmd '+cmd); if (cmd === 'setting') { let [setting, val] = rest; if (setting === 'list') { return chat.notice(`${JSON.stringify({ tipMenuOptions })}`, { to: ctx.sender, }); } else if (setting === 'color') { tipMenuOptions[setting] = val; return chat.notice(`TipMenu setting: ${setting}="${tipMenuOptions[setting]}"`); } else if (['minutes', 'messages', 'minMessages', 'minMinutes'].includes(setting)) { tipMenuOptions[setting] = forceInt(val); setMenuBlaster(true); return chat.notice(`TipMenu setting: ${setting}=${tipMenuOptions[setting]}`); } } let price; if (cmd === 'add' || cmd === 'remove' || cmd === 'change') { price = forceInt(_price, `Second argument (price) must be a number greater than 0. [${_price}]`); } // info.action = `${cmd}ed item`; if (cmd === 'add') { let _item = rest.join(' '); blastNext = true; addOptionPrice(price, _item); info.message = `${preMessage}added "${_item}" @ ${price}tks`; } else if (cmd === 'change') { if (options[price] === undefined) { throw new Error('cannot change unset price option'); } let fromItem = options[price]; removeOptionPrice(price); addOptionPrice(price, rest.join(' ')); blastNext = true; let toItem = options[price]; info.message = `changed from "${fromItem}" to "${toItem}" @ ${price}tks`; } else if (cmd === 'remove') { // info.action = `removed item`; info.message = `removed item "${options[price]}" @ ${price}tks`; delete options[price]; removeOptionPrice(price); } } else if (ctx.arg && ctx.arg.search(/[,\|]/) !== -1) { try { parseOptions(ctx.arg); info.message = `tipmenu synced`; blastNext = true; } catch (e) { return ctx.reject(`Error parsing options: ${e.message}`); } } else { let arg = ctx.arg || ''; let removal = arg.match(/^-(\d+)/); let addition = arg.match(/@\s?(\d+)$/); let priceChange = arg.match(/(\d+)->(\d+)/); if (removal) { let tokes = forceInt(removal[1]); removeOptionPrice(tokes); info.message = `removed item "${options[tokes]}" @ ${tokes}tks`; } else if (addition) { let preMessage = ''; if (optionPrices.length === 0) { preMessage = `${chat.room_slug} has started a tip menu! `; } let [_s, _tokes] = addition; let tokes = forceInt(_tokes); arg = arg.replace(/@\s?(\d+)/, '').trim(); addOptionPrice(tokes, arg); info.message = `${preMessage}added "${arg}" @ ${tokes}tks`; } else if (priceChange) { let [_t, _fromPrice, _toPrice] = priceChange; let fromPrice = forceInt(_fromPrice); let toPrice = forceInt(_toPrice); let fromItem = options[`${fromPrice}`]; removeOptionPrice(fromPrice); addOptionPrice(toPrice, fromItem); info.message = `changed "${fromItem}" price from ${fromPrice}tks to ${toPrice}tks`; } } chat.notice(`The tip menu has been updated: /tipmenu ${optionsString(' | ', ' ')}`, { // to: ctx.sender, color: '#120dab', }); if (info.message) { chat.notice(`${info.message}`, { color: tipMenuOptions.actionColor, }); } // blastIt(true); setMenuBlaster(true); } }); chat.onEnter(function({ user, in_fanclub, is_mod, has_tokens, gender, tipped_recently, room, tipped_alot_recently, tipped_tons_recently, }){ if (optionStringRep) { chat.notice(` 'tipmenu' mini-app is running. Options: ${optionStringRep}`, { to: user, color: tipMenuOptions.actionColor, }); } }); /* Chat area: i wrote a command that works similar to "cb.sendNotice" The main difference is that the order of the arguments doesn't matter. This is the syntax: chat.notice("message", { color: 'black', //optional. hex only background: 'white', //optional. hex only to: 'username', //optional }); */ // to get the 'escaped' value, open up a chrome inspector and run this command: // escape('💧') --shows--> "%uD83D%uDCA7" Object.assign(chat.constants.emoji, { // escape('💧') RAINDROP: unescape('%uD83D%uDCA7'), // escape('🏖') BEACH: unescape('%uD83C%uDFD6'), // escape('🛡') SHIELD: unescape('%uD83D%uDEE1'), // escape('🌊') WAVE: unescape('%uD83C%uDF0A'), // escape('🌙') CRESCENT_MOON: unescape('%uD83C%uDF19'), // escape('🍉') WATERMELON: unescape('%uD83C%uDF49'), // escape('🕺') DANCING_MAN: unescape('%uD83D%uDD7A'), // escape('💃') DANCING_LADY: unescape('%uD83D%uDC83'), // escape('💩') POOP: unescape('%uD83D%uDCA9'), // escape('🍈') MELON: unescape('%uD83C%uDF48'), // escape('☙') FLORAL: unescape('%u2619'), }); // on enter, show the user a welcome message var welcomeMessage = `Psst! Wanna pick your color? type /theme`; cb.onEnter(() => { chat.notice(`:helpicon ${welcomeMessage}`, { color: '#fff', background: '#0084ff', to: 'username', weight: 'bold', }); }); const PRICE_OF_SIMPLE = 20; const PRICE_OF_GRADIENT = 40; //check out the `color-picker` atom package. //i have it and pigment //nice //advanced color themes [text color, background color] const customThemes = { midnight: { plainVersion:["rgba(255,196,37,1)","rgba(55,56,84,1)"], gradientVersion:["rgba(255,196,37,1)","linear-gradient(to right, rgba(55,56,84,1), #a908d2)"], textFlair: chat.constants.emoji.CRESCENT_MOON, }, retro: { plainVersion:["rgba(251,46,1,1)","rgba(111,203,159,1)"], // i tried it out with a hex color and it worked fine: // plainVersion:["rgba(251,46,1,1)","#990000"], gradientVersion:["rgba(251,46,1,1)","linear-gradient(to right, rgba(111,203,159,1), rgba(255,226,138,1))"], textFlair: chat.constants.emoji.DANCING_MAN, }, // melon: { // plainVersion: ["rgba(250,125,128,1)", "rgba(16,105,93,1)"], // gradientVersion: ["rgba(250,125,128,1)", "linear-gradient(to right, rgba(16,105,93,1), rgba(255,226,138,1))"], // textFlair: chat.constants.emoji.MELON, // }, fire: { plainVersion: ["rgba(242,125,12,1)", "rgba(128,9,9,1)"], gradientVersion: ["rgba(242,125,12,1)", "linear-gradient(to right, rgba(128,9,9,1), rgba(0,0,0,1))"], textFlair: chat.constants.emoji.FLAME, }, gryffindor: { plainVersion: ["rgba(211,166,37,1)", "rgba(116,0,1,1)"], gradientVersion: ["rgba(211,166,37,1)", "linear-gradient(to right, rgba(116,0,1,1), rgba(174,0,1,1))"], textFlair: chat.constants.emoji.SHIELD, }, beach: { plainVersion: ["rgba(255,204,92,1)", "rgba(255,111,105,1)"], gradientVersion: ["rgba(255,204,92,1)", "linear-gradient(to right, rgba(255,111,105,1), rgba(150,206,180,1))"], textFlair: chat.constants.emoji.BEACH, }, seafoam: { plainVersion: ["rgba(49,120,115,1)", "rgba(160,214,180,1)"], gradientVersion: ["rgba(49,120,115,1)", "linear-gradient(to right, rgba(160,214,180,1), rgba(95,158,160,1))"], textFlair: chat.constants.emoji.WAVE, }, rain: { plainVersion: ["rgba(100,151,177,1)", "rgba(1,31,75,1)"], gradientVersion: ["rgba(100,151,177,1)", "linear-gradient(to right, rgba(1,31,75,1), rgba(0,91,150,1))"], textFlair: chat.constants.emoji.RAINDROP }, plum: { plainVersion: ["rgba(211,196,207,1)", "rgba(110,60,96,1)"], gradientVersion: ["rgba(211,196,207,1)", "linear-gradient(to right, rgba(110,60,96,1), rgba(178,48,92,1))"], textFlair: chat.constants.emoji.EGGPLANT }, }; /* /theme (list themes) /theme snow (activate snow theme) */ const savedFlair = {}; const savedColors = {}; const userSelectedTheme = {}; const themeCommand = (ctx, gradient)=> { // temporarily showing ctx.snuff(); const { arg, sender } = ctx; // const username = ctx.origin; let tipAmount = chat.tippers[sender] || 0; let canSetTheme = ctx.isMod() || tipAmount >= PRICE_OF_SIMPLE; let canSetGradient = ctx.isMod() || tipAmount >= PRICE_OF_GRADIENT; if (!arg) { // let themes = '| ' + Object.keys(customThemes).map((themeId, n)=> { // let space = n % 3 === 2 ? ' |\n | ' : ' | '; // return `${customThemes[themeId].textFlair}${themeId}${space}`; // }).join(''); chat.notice(` Tip ${PRICE_OF_SIMPLE}+ tokens so you can set a theme. ${PRICE_OF_GRADIENT}+ to set a theme with a gradient! /theme midnight (set your theme to midnight)`, { to: sender, fontWeight: 'bold', }); setTimeout((()=>{ Object.keys(customThemes).forEach((themeId) => { chat.notice(`[ ${customThemes[themeId].textFlair} /theme ${themeId} ${customThemes[themeId].textFlair} ]`, { to: sender, color: customThemes[themeId].plainVersion[0], background: customThemes[themeId].plainVersion[1], }); }); }), 1000); return; } if (canSetGradient) { gradient = true; } let [themeId, ...rest] = arg.split(' '); if (themeId[0] === '-') { themeId = themeId.substr(1); gradient = false; } if (themeId === 'reset') { savedFlair[sender] = false; savedColors[sender] = []; } else if (!canSetTheme) { chat.notice(`You must tip at least 20 tokens to set a theme`, { to: sender, color: '#9900000', weight: 'bold', }); } else if (customThemes[themeId]) { const { plainVersion, gradientVersion, textFlair, } = customThemes[themeId]; const flairValue = unescape(textFlair); savedFlair[sender] = flairValue; if (rest.length > 0) { ctx.text = `${themeId} - ${rest.join(' ')}`; ctx.unsnuff(); } else { ctx.text = `${themeId}`; } userSelectedTheme[sender] = themeId; savedColors[sender] = gradient ? gradientVersion : plainVersion; } else { ctx.reject(`Theme not found: '${themeId}'`); } }; chat.onCommand('theme', (ctx)=>{ themeCommand(ctx, false); }); chat.onMessage((context) => { const username = context.origin; if (savedFlair[username]) { context.setPrefix(savedFlair[username]); } if (savedColors[context.origin]) { context.setColor(savedColors[context.origin][0], savedColors[context.origin][1]); } }); const notifiedOfThemes = {}; const notifiedOfGradientThemes = {}; chat.onTip(({ sender, amount, message, totalTipped, })=>{ if (totalTipped >= PRICE_OF_GRADIENT && !notifiedOfGradientThemes[sender]) { chat.notice(`You have tipped more than ${PRICE_OF_GRADIENT} tokens. You can now set a theme with a gradient! You can see the available themes with the command '/theme'`, { to: sender, fontWeight: 'bold', }); if (userSelectedTheme[sender]) { let themeId = userSelectedTheme[sender]; chat.notice(`Upgrading your theme to ${userSelectedTheme[sender]} with a gradient.`, { to: sender, }); savedColors[sender] = customThemes[themeId].gradientVersion; } notifiedOfThemes[sender] = true; notifiedOfGradientThemes[sender] = true; } if (totalTipped >= PRICE_OF_SIMPLE && !notifiedOfThemes[sender]) { chat.notice(`You have tipped more than ${PRICE_OF_SIMPLE} tokens. You can set a theme. You can see the available themes with the command '/theme'`, { to: sender, }); notifiedOfThemes[sender] = true; } }); let activeFont; // list of fonts pulled from here: // http://www.lalit.org/lab/javascript-css-font-detect/ const FONTS = [ 'Arial Black', 'Arial Narrow', 'Arial Rounded MT Bold', 'Arial', 'Comic Sans MS', 'Courier', 'Courier New', 'cursive', 'fantasy', 'Georgia', 'Impact', 'Papyrus', 'Trebuchet MS', 'Verdana', ]; chat.onCommand('roomfont', { help: [ '/roomfont (lists the fonts available)', '/roomfont [font-name] (sets the font for all messages in the room)', ], onlyMods: true, callback: (ctx) => { const { origin, reject, arg } = ctx; if (!ctx.isMod()) { reject('You are not allowed to use this function'); } ctx.snuff(); if (arg === 'reset') { activeFont = false; return reject('Reset room font'); } if (['list', '', undefined].includes(arg)) { let fontStrings = FONTS.map((fontName)=>{ return '\n' + (fontName === activeFont ? '* ' : '- ') + fontName; }).join(''); chat.notice(`Available fonts: ${fontStrings}`, origin); return; } let font; if (FONTS.includes(arg)) { font = arg; } else if (chat.utils.isargingOfInt(arg)) { font = FONTS[parseInt(arg)]; } if (font) { activeFont = font; chat.notice(`Setting font to '${font}'`, origin); } else { reject(`Font not found: ${arg}`); } } }); chat.onMessage((ctx)=>{ if (activeFont) { ctx.setFont(activeFont); } }); const savedColors$1 = {}; // i'm doing this so we can test out raw colors. we can remove this before we publish the app chat.onCommand('rawcolor', { help: [ `/rawcolor [textcolor] [backgroundcolor] (sets color options for your own messages)`, `/rawcolor reset (resets color)` ], onlyMods: true, callback: (ctx) => { // ctx.snuff() // don't show the "/rawcolor" message to the whole chat room if (!ctx.isMod()) { ctx.reject('Only administrators can change this value'); } const [cmd, ...params] = ctx.words(); const sender = ctx.origin; if (params.length === 0) { chat.notice(`These are the commands to set your colors: /rawcolor (display this help message) /rawcolor blue pink (will set your textColor to blue and backgroundColor to pink) /rawcolor reset (will reset your colors to the default)`, sender); } else if (params.length === 1 && params[0] === 'reset') { savedColors$1[sender] = []; } else { const [textColor, ...backgroundColorArr] = params; const backgroundColor = backgroundColorArr.join(' ') || chat.utils.noop(ctx) || 'inherit'; chat.notice(`${sender} is changing their text color with the command /rawcolor`); chat.notice(`Changing textColor to ${textColor}`, sender); chat.notice(`Changing backgroundColor to ${backgroundColor}`, sender); savedColors$1[sender] = [textColor, backgroundColor]; } } }); chat.onMessage((context) => { const { sender } = context; if (savedColors$1[sender]) { context.setColor(savedColors$1[sender][0], savedColors$1[sender][1]); } }); chat.onCommand('topic', { help: `/topic [new room topic] (sets the room subject)`, onlyMods: true, callback: (ctx) => { const nextTopic = ctx.arg; if (ctx.isMod()) { chat.setTopic(nextTopic); ctx.reject(); } } }); // list users and tippers chat.onCommand('users', { help: '/users (list all users who have messaged or tipped)', callback (ctx) { ctx.snuff(); let out = `Talked or tipped:\n ${chat.constants.emoji.RIGHT_ARROW} `; out += Object.keys(chat.users).join(' '); chat.notice(out, ctx.sender); } }); chat.onCommand('checkuser', { callback (ctx) { ctx.snuff(); try { let username = chat.queryUser(ctx.arg); chat.notice(`user found: ${username}`, { to: ctx.sender, }); } catch (e) { ctx.reject(e.message); } } }); chat.onCommand('tippers', { help: '/tippers (list all users who have tipped)', callback (ctx) { ctx.snuff(); let tippersList = chat.utils.ObjectEntries(chat.tippers); if (tippersList.length === 0) { chat.notice('No tippers'); } else { let out = [`Tippers:`]; let userList = tippersList.sort((a,b)=>a[1] < b[1]).forEach(([user, tiptotal])=>{ out.push(` - ${user} (${tiptotal})`); }); chat.notice(out.join('\n'), ctx.sender); } } }); let timestamps = false; let baseTime = new Date().getTime(); chat.onCommand('timestamps', { help: '/timestamps', detailedHelp: 'Prepends timestamps to all messages', onlyMods: true, callback (ctx) { if (!ctx.isMod()) { return ctx.reject('Only mods can enable or disable timestamps'); } if (!ctx.arg) { timestamps = !timestamps; if (timestamps === undefined) { baseTime = new Date().getTime(); } chat.notice(`${ctx.sender} has enabled timestamps for the room`); // } else { // let [_, offset] = ctx.words(); } } }); // chat.noticePrefixes.push(function(){ // return calcTime()+' '; // }); // chat.onMessage(function(ctx) { // if (timestamps) { // ctx.setPrefix(`${calcTime()} `); // } // }); /* summary: a list of the different commands which are implemented We compile this script using the javascript library: "rollup" rollup -f es script.js */
© Copyright Chaturbate 2011- 2025. All Rights Reserved.