'use strict'; const bitcoin = require('bitcoin'); let config = require('config'); let spamchannel = config.get('sandboxchannel'); let regex = require('regex'); let lbrycrdConfig = config.get('lbrycrd'); const lbry = new bitcoin.Client(lbrycrdConfig); exports.commands = ['tip', 'multitip', 'roletip', 'tips']; exports.tip = { usage: '', 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 .trim() .split(' ') .filter(function(n) { return n !== ''; }), 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': privateOrSandboxOnly(msg, channelwarning, doHelp, [helpmsg]); break; case 'balance': doBalance(msg, tipper); break; case 'deposit': privateOrSandboxOnly(msg, channelwarning, doDeposit, [tipper]); break; case 'withdraw': privateOrSandboxOnly(msg, channelwarning, doWithdraw, [tipper, words, helpmsg]); break; default: 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.tips = { 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.'], ['!tips', '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.').then(message => message.delete(5000)); } else { message.reply(`You have *${balance}* LBC`); } }); } function doDeposit(message, tipper) { getAddress(tipper, function(err, address) { if (err) { message.reply('Error getting your deposit address.').then(message => message.delete(5000)); } else { message.reply(`Your address is ${address}`); } }); } function doWithdraw(message, tipper, words, helpmsg) { if (words.length < 4) { 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...").then(message => message.delete(5000)); return; } lbry.sendFrom(tipper, address, amount, function(err, txId) { if (err) { return message.reply(err.message).then(message => message.delete(5000)); } message.reply(`You withdrew ${amount} LBC to ${address}. ${txLink(txId)}`); }); } function doTip(message, tipper, words, helpmsg) { if (words.length < 3 || !words) { return doHelp(message, helpmsg); } 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) { return message.reply("I don't know how to tip that many credits...").then(message => message.delete(5000)); } if (message.mentions.users.first() && message.mentions.users.first().id) { return sendLBC(message, tipper, message.mentions.users.first().id.replace('!', ''), amount, prv); } message.reply('Sorry, I could not find a user in your tip...'); } 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); } } 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].toString(), amount, prv); } } else { 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).then(message => message.delete(5000)); } else { let tx = txLink(txId); let msgtail = ` DM me with \`${message.content.split(' ', 1)[0]}\` for command specific instructions or with \`!tips\` for all available commands`; if (privacyFlag) { let usr = message.guild.members.find('id', recipient).user; let authmsg = `You have just privately tipped @${usr.tag} ${amount} LBC. ${tx}${msgtail}`; message.author.send(authmsg); if (message.author.id !== message.mentions.users.first().id) { let recipientmsg = `You have just been privately tipped ${amount} LBC by @${message.author.tag}. ${tx}${msgtail}`; usr.send(recipientmsg); } } else { let generalmsg = `Wubba lubba dub dub! <@${tipper}> tipped <@${recipient}> ${amount} LBC. ${tx}${msgtail}`; message.reply(generalmsg); } } }); } }); } function getAddress(userId, cb) { lbry.getAddressesByAccount(userId, function(err, addresses) { if (err) { cb(err); } else if (addresses.length > 0) { cb(null, addresses[0]); } else { lbry.getNewAddress(userId, function(err, address) { if (err) { cb(err); } else { cb(null, address); } }); } }); } function inPrivateOrBotSandbox(msg) { return msg.channel.type === 'dm' || msg.channel.id === spamchannel; } function getValidatedAmount(amount) { amount = amount.trim(); if (amount.toLowerCase().endsWith('lbc')) { amount = amount.substring(0, amount.length - 3); } return amount.match(/^[0-9]+(\.[0-9]+)?$/) ? amount : null; } function txLink(txId) { return ''; } function formatDescriptions(msgparts) { return msgparts .map( elem => `\t**${elem[0]}** \t\t${elem[1]} ` ) .join(''); }