diff --git a/bot/modules/tipbot.js b/bot/modules/tipbot.js index 32319cf..53357fb 100644 --- a/bot/modules/tipbot.js +++ b/bot/modules/tipbot.js @@ -2,18 +2,15 @@ const bitcoin = require('bitcoin'); let config = require('config'); +let spamchannel = config.get('sandboxchannel'); +let regex = require('regex'); let lbrycrdConfig = config.get('lbrycrd'); -let sandboxChannel = config.get('sandboxchannel'); const lbry = new bitcoin.Client(lbrycrdConfig); -exports.commands = ['tip']; +exports.commands = ['tip', 'multitip', 'roletip', 'tipcommands']; exports.tip = { usage: '', - description: `balance: get your balance - deposit: get address for your deposits - withdraw ADDRESS AMOUNT: withdraw AMOUNT credits to ADDRESS - [private] : mention a user with @ and then the amount to tip them, or put private before the user to tip them privately. - Key: [] : Optionally include contained keyword, <> : Replace with appropriate value.`, + description: 'Tip a given user with an amount of LBC or perform wallet specific operations.', process: async function(bot, msg, suffix) { let tipper = msg.author.id.replace('!', ''), words = msg.content @@ -22,30 +19,151 @@ exports.tip = { .filter(function(n) { return n !== ''; }), - subcommand = words.length >= 2 ? words[1] : 'help'; + subcommand = words.length >= 2 ? words[1] : 'help', + helpmsgparts = [ + ['[help]', 'Get this message.'], + ['balance', 'Get your balance.'], + ['deposit', 'Get address for your deposits.'], + ['withdraw ADDRESS AMOUNT', 'Withdraw AMOUNT credits to ADDRESS'], + ['[private] ', 'Mention a user with @ and then the amount to tip them, or put private before the user to tip them privately.'] + ], + helpmsg = { + embed: { + description: formatDescriptions(helpmsgparts) + '\nKey: [] : Optionally include contained keyword, <> : Replace with appropriate value.', + color: 1109218, + author: { name: '!tip' } + } + }, + channelwarning = 'Please use <' + spamchannel + '> or DMs to talk to bots.'; switch (subcommand) { case 'help': - doHelp(msg); + privateOrSandboxOnly(msg, channelwarning, doHelp, [helpmsg]); break; case 'balance': doBalance(msg, tipper); break; case 'deposit': - doDeposit(msg, tipper); + privateOrSandboxOnly(msg, channelwarning, doDeposit, [tipper]); break; case 'withdraw': - doWithdraw(msg, tipper, words); + privateOrSandboxOnly(msg, channelwarning, doWithdraw, [tipper, words, helpmsg]); break; default: - doTip(msg, tipper, words); + doTip(msg, tipper, words, helpmsg); } } }; +exports.multitip = { + usage: '', + description: 'Tip multiple users simultaneously for the same amount of LBC each.', + process: async function(bot, msg, suffix) { + let tipper = msg.author.id.replace('!', ''), + words = msg.content + .trim() + .split(' ') + .filter(function(n) { + return n !== ''; + }), + subcommand = words.length >= 2 ? words[1] : 'help', + helpmsgparts = [ + ['[help]', 'Get this message.'], + ['+ ', 'Mention one or more users in a row, seperated by spaces, then an amount that each mentioned user will receive.'], + ['private + ', 'Put private before the user list to have each user tipped privately, without revealing other users tipped.'] + ], + helpmsg = { + embed: { + description: formatDescriptions(helpmsgparts) + '\nKey: [] : Optionally include contained keyword, <> : Replace with appropriate value, + : Value can be repeated for multiple entries.', + color: 1109218, + author: { name: '!multitip' } + } + }, + channelwarning = 'Please use <' + spamchannel + '> or DMs to talk to bots.'; + switch (subcommand) { + case 'help': + privateOrSandboxOnly(msg, channelwarning, doHelp, [helpmsg]); + break; + default: + doMultiTip(msg, tipper, words, helpmsg); + break; + } + } +}; + +exports.roletip = { + usage: '', + description: 'Tip every user in a given role the same amount of LBC.', + process: async function(bot, msg, suffix) { + let tipper = msg.author.id.replace('!', ''), + words = msg.content + .trim() + .split(' ') + .filter(function(n) { + return n !== ''; + }), + subcommand = words.length >= 2 ? words[1] : 'help', + helpmsgparts = [ + ['[help]', 'Get this message'], + [' ', 'Mention a single role, then an amount that each user in that role will receive.'], + ['private ', 'Put private before the role to have each user tipped privately, without revealing other users tipped.'] + ], + helpmsg = { + embed: { + description: formatDescriptions(helpmsgparts) + '\nKey: [] : Optionally include contained keyword, <> : Replace with appropriate value.', + color: 1109218, + author: { name: '!roletip' } + } + }, + channelwarning = `Please use <${spamchannel}> or DMs to talk to bots.`; + switch (subcommand) { + case 'help': + privateOrSandboxOnly(msg, channelwarning, doHelp, [helpmsg]); + break; + default: + doRoleTip(msg, tipper, words, helpmsg); + break; + } + } +}; + +exports.tipcommands = { + usage: '', + description: 'Lists all available tipbot commands with brief descriptions for each one.', + process: async function(bot, msg, suffix) { + let helpmsgparts = [ + ['!tip', 'Tip a given user with an amount of LBC or perform wallet specific operations.'], + ['!multitip', 'Tip multiple users simultaneously for the same amount of LBC each.'], + ['!roletip', 'Tip every user in a given role the same amount of LBC.'], + ['!tipcommands', 'Lists all available tipbot commands with brief descriptions for each one.'] + ], + helpmsg = { + embed: { + description: `These are all the commands that TipBot currently supports. Use \`! help\` for usage instructions. +${formatDescriptions(helpmsgparts)}`, + color: 1109218, + author: { name: 'Tipbot Commands' } + } + }; + msg.reply(helpmsg); + } +}; + +function privateOrSandboxOnly(message, wrongchannelmsg, fn, args) { + if (!inPrivateOrBotSandbox(message)) { + message.reply(wrongchannelmsg); + return; + } + fn.apply(null, [message, ...args]); +} + +function doHelp(message, helpmsg) { + message.author.send(helpmsg); +} + function doBalance(message, tipper) { lbry.getBalance(tipper, 1, function(err, balance) { if (err) { - message.reply('Error getting balance'); + message.reply('Error getting balance.').then(message => message.delete(5000)); } else { message.reply(`You have *${balance}* LBC`); } @@ -53,58 +171,53 @@ function doBalance(message, tipper) { } function doDeposit(message, tipper) { - if (!inPrivateOrBotSandbox(message)) { - return message.reply(`Please use <#${sandboxChannel}> or DMs to talk to bots.`); - } getAddress(tipper, function(err, address) { if (err) { - message.reply('Error getting deposit address'); + message.reply('Error getting your deposit address.').then(message => message.delete(5000)); } else { message.reply(`Your address is ${address}`); } }); } -function doWithdraw(message, tipper, words) { - if (!inPrivateOrBotSandbox(message)) { - return message.reply(`Please use <#${sandboxChannel}> or DMs to talk to bots.`); - } +function doWithdraw(message, tipper, words, helpmsg) { if (words.length < 4) { - return doHelp(message); + return doHelp(message, helpmsg); } let address = words[2], amount = getValidatedAmount(words[3]); if (amount === null) { - message.reply("I don't know how to withdraw that many credits"); + message.reply("I don't know how to withdraw that many credits...").then(message => message.delete(5000)); return; } lbry.sendFrom(tipper, address, amount, function(err, txId) { if (err) { - return message.reply(err.message); + return message.reply(err.message).then(message => message.delete(5000)); } - message.reply(`You withdrew ${amount} to ${address} (${txLink(txId)})`); + message.reply(`You withdrew ${amount} LBC to ${address}. +${txLink(txId)}`); }); } -function doTip(message, tipper, words) { +function doTip(message, tipper, words, helpmsg) { if (words.length < 3 || !words) { - return doHelp(message); + return doHelp(message, helpmsg); } - let prv = 0; + let prv = false; let amountOffset = 2; if (words.length >= 4 && words[1] === 'private') { - prv = 1; + prv = true; amountOffset = 3; } let amount = getValidatedAmount(words[amountOffset]); if (amount === null) { - return message.reply("I don't know how to tip that many credits"); + return message.reply("I don't know how to tip that many credits...").then(message => message.delete(5000)); } if (message.mentions.users.first().id) { @@ -113,35 +226,108 @@ function doTip(message, tipper, words) { message.reply('Sorry, I could not find a user in your tip...'); } -function doHelp(message) { - if (!inPrivateOrBotSandbox(message)) { - message.reply(`Sent you help via DM! Please use <#${sandboxChannel}> or DMs to talk to bots.`); +function doMultiTip(message, tipper, words, helpmsg) { + if (!words) { + doHelp(message, helpmsg); + return; + } + if (words.length < 4) { + doTip(message, tipper, words, helpmsg); + return; + } + let prv = false; + if (words.length >= 5 && words[1] === 'private') { + prv = true; + } + let [userIDs, amount] = findUserIDsAndAmount(message, words, prv); + if (amount == null) { + message.reply("I don't know how to tip that many credits...").then(message => message.delete(5000)); + return; + } + if (!userIDs) { + message.reply('Sorry, I could not find a user in your tip...').then(message => message.delete(5000)); + return; + } + for (let i = 0; i < userIDs.length; i++) { + sendLBC(message, tipper, userIDs[i].toString(), amount, prv); } - message.author.send(`**!tip** - balance: get your balance - deposit: get address for your deposits - withdraw ADDRESS AMOUNT: withdraw AMOUNT credits to ADDRESS - [private] : mention a user with @ and then the amount to tip them, or put private before the user to tip them privately. - Key: [] : Optionally include contained keyword, <> : Replace with appropriate value.`); } -function sendLbc(message, tipper, recipient, amount, privacyFlag) { - getAddress(recipient, function(err, address) { - if (err) { - message.reply(err.message); +function doRoleTip(message, tipper, words, helpmsg) { + if (!words || words.length < 3) { + doHelp(message, helpmsg); + return; + } + let prv = false; + let amountOffset = 2; + if (words.length >= 4 && words[1] === 'private') { + prv = true; + amountOffset = 3; + } + let amount = getValidatedAmount(words[amountOffset]); + if (amount == null) { + message.reply("I don't know how to tip that many credits...").then(message => message.delete(5000)); + return; + } + if (message.mentions.roles.first().id) { + if (message.mentions.roles.first().members.first().id) { + let userIDs = message.mentions.roles.first().members.map(member => member.user.id.replace('!', '')); + for (let i = 0; i < userIDs; i++) { + sendLBC(message, tipper, userIDs[i], amount, prv); + } } else { - lbry.sendFrom(tipper, address, amount, 1, null, null, function(err, txId) { + message.reply('Sorry, I could not find any users to tip in that role...').then(message => message.delete(5000)); + } + } else { + message.reply('Sorry, I could not find any roles in your tip...').then(message => message.delete(5000)); + } +} + +function findUserIDsAndAmount(message, words, prv) { + let idList = []; + let amount = null; + let count = 0; + let startOffset = 1; + if (prv) startOffset = 2; + let regex = new RegExp(/<@!?[0-9]+>/); + for (let i = startOffset; i < words.length; i++) { + if (regex.test(words[i])) { + count++; + idList.push(words[i].match(/[0-9]+/)); + } else { + amount = getValidatedAmount(words[Number(count) + 1]); + break; + } + } + return [idList, amount]; +} + +function sendLBC(message, tipper, recipient, amount, privacyFlag) { + getAddress(recipient.toString(), function(err, address) { + if (err) { + message.reply(err.message).then(message => message.delete(5000)); + } else { + lbry.sendFrom(tipper, address, Number(amount), 1, null, null, function(err, txId) { if (err) { - message.reply(err.message); + message.reply(err.message).then(message => message.delete(5000)); } else { - let imessage = `Wubba lubba dub dub! <@${tipper}> tipped <@${recipient}> ${amount} LBC (${txLink(txId)}). DM me \`!tip\` for tipbot instructions.`; + let tx = txLink(txId); + let msgtail = ` +DM me with \`${message.content.split(' ', 1)[0]}\` for command specific instructions or with \`!tipcommands\` for all available commands`; if (privacyFlag) { - message.author.send(imessage); + let authmsg = `You have just privately tipped <@${recipient}> ${amount} LBC. +${tx}${msgtail}`; + message.author.send(authmsg); if (message.author.id !== message.mentions.users.first().id) { - message.mentions.users.first().send(imessage); + let usr = message.guild.members.find('id', recipient).user; + let recipientmsg = `You have just been privately tipped ${amount} LBC by <@${tipper}>. +${tx}${msgtail}`; + usr.send(recipientmsg); } } else { - message.reply(imessage); + let generalmsg = `Wubba lubba dub dub! <@${tipper}> tipped <@${recipient}> ${amount} LBC. +${tx}${msgtail}`; + message.reply(generalmsg); } } }); @@ -168,7 +354,7 @@ function getAddress(userId, cb) { } function inPrivateOrBotSandbox(msg) { - return msg.channel.type === 'dm' || msg.channel.id === sandboxChannel; + return msg.channel.type === 'dm' || msg.channel.id === spamchannel; } function getValidatedAmount(amount) { @@ -182,3 +368,13 @@ function getValidatedAmount(amount) { function txLink(txId) { return ''; } + +function formatDescriptions(msgparts) { + return msgparts + .map( + elem => `\t**${elem[0]}** +\t\t${elem[1]} +` + ) + .join(''); +} diff --git a/config/default.json.example b/config/default.json.example index c5d4fb5..2aab9d5 100644 --- a/config/default.json.example +++ b/config/default.json.example @@ -1,20 +1,13 @@ { - // Bot configuration "bot": { - "token":"discordbottoken", - "prefix": "!", - "debug": false + "token": "discordbottoken", + "prefix": "!", + "debug": false }, "lbrycrd": { - "port": 9245, - "user": "lbry", - "pass": "lbry" + "port": 9245, + "user": "lbry", + "pass": "lbry" }, - "mongodb": { - "url":"mongodb://localhost:27017/wunderbot" - }, - "moderation":{ - "perms": ["LBRY MODS","LBRY TEAM"], // Roles that have access to all commands. - "logchannel": "371620338263523328" // Channel to log the bots moderation.. - } -} \ No newline at end of file + "sandboxchannel": "#369896313082478594" +} diff --git a/package-lock.json b/package-lock.json index 8faff0e..0d08b0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -95,6 +95,11 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, + "augment": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/augment/-/augment-3.2.1.tgz", + "integrity": "sha1-7F2elFYUDvqxEQ/ds1LL5U5/sxw=" + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -1991,6 +1996,11 @@ "is-buffer": "1.1.6" } }, + "lex": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/lex/-/lex-1.7.4.tgz", + "integrity": "sha1-M91muYCjjXemxCTXFz5vQNYCwMU=" + }, "lex-parser": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/lex-parser/-/lex-parser-0.1.4.tgz", @@ -2402,6 +2412,38 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==" }, + "regex": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/regex/-/regex-0.1.1.tgz", + "integrity": "sha1-BYCZXRhGxTdqdiudNYuDBo0geu8=", + "requires": { + "augment": "3.2.1", + "jison": "0.4.4", + "lex": "1.7.4", + "statemachines": "0.1.0" + }, + "dependencies": { + "esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=" + }, + "jison": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/jison/-/jison-0.4.4.tgz", + "integrity": "sha1-hrA7Ze/sAHqR9JPs6r5W+QqmtDk=", + "requires": { + "JSONSelect": "0.4.0", + "ebnf-parser": "0.1.10", + "escodegen": "0.0.21", + "esprima": "1.0.4", + "jison-lex": "0.2.1", + "lex-parser": "0.1.4", + "nomnom": "1.5.2" + } + } + } + }, "regex-cache": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", @@ -2522,6 +2564,11 @@ "hoek": "4.2.0" } }, + "sorted-array": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/sorted-array/-/sorted-array-1.1.0.tgz", + "integrity": "sha1-YamDJeQSu5CZm0usGN/tXzVkEC0=" + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -2558,6 +2605,15 @@ } } }, + "statemachines": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/statemachines/-/statemachines-0.1.0.tgz", + "integrity": "sha1-tUX9PYpVKA8wK4aW8ZwKEcTjOt4=", + "requires": { + "augment": "3.2.1", + "sorted-array": "1.1.0" + } + }, "static-eval": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-0.2.3.tgz", diff --git a/package.json b/package.json index 57cece3..c66b836 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "mongoose": "^4.13.11", "node-config": "^0.0.2", "numeral": "^2.0.6", + "regex": "^0.1.1", "request": "^2.83.0" }, "scripts": {