f239d5f394
Implements MSFTblockchain's fixes from PR #4 without customisations or version regression. Now requires Node.js Regex module for testing for user mentions in text with regex, including a fallback if a unit test fails. Also adds !tipcommands command which gives an up to date list of all commands tipbot accepts with a brief description of each. The tip message tail about DMing tipbot is now contextualised to give the appropriate !<command> and also points users towards the new !tipcommands command. Private tip messages are now a bit nicer to read and prv is now a boolean flag to fix some odd errors that were occuring. Error Messages now delete themselves after a short time for cleanliness. Help messages are now in Discord Embeds to line up with Wunderbot, though this currently ignores tabs, so help messages don't quite match the requested format. Commands have been made bold to prevent confusion. Everything should now actually work, but will require testing to be sure.
367 lines
12 KiB
JavaScript
367 lines
12 KiB
JavaScript
'use strict';
|
|
|
|
const bitcoin = require('bitcoin');
|
|
let config = require('config'),
|
|
spamchannel = config.get('moderation').botspamchannel,
|
|
regex = require('regex');
|
|
config = config.get('lbrycrd');
|
|
const lbry = new bitcoin.Client(config);
|
|
|
|
exports.commands = [
|
|
"tip",
|
|
"multitip",
|
|
"roletip",
|
|
"tipcommands"
|
|
]
|
|
exports.tip = {
|
|
usage: "<subcommand>",
|
|
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] <user> <amount>', '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: "<subcommand>",
|
|
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.'],
|
|
['<user>+ <amount>', 'Mention one or more users in a row, seperated by spaces, then an amount that each mentioned user will receive.'],
|
|
['private <user>+ <amount>','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: "<subcommand>",
|
|
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'],
|
|
['<role> <amount>', 'Mention a single role, then an amount that each user in that role will receive.'],
|
|
['private <role> <amount>','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 `!<command> help` for usage instructions.\n" +
|
|
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) {
|
|
doHelp(message, helpmsg);
|
|
return;
|
|
}
|
|
|
|
var address = words[2],
|
|
amount = getValidatedAmount(words[3]);
|
|
|
|
if (amount === null) {
|
|
message.reply('I dont know how to withdraw that many coins...').then(message => message.delete(5000));
|
|
return;
|
|
}
|
|
|
|
lbry.sendFrom(tipper, address, amount, function (err, txId) {
|
|
if (err) {
|
|
message.reply(err.message).then(message => message.delete(5000));
|
|
}
|
|
else {
|
|
message.reply('You withdrew ' + amount + ' LBC to ' + address + '.\n' + txLink(txId));
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
function doTip(message, tipper, words, helpmsg) {
|
|
if (words.length < 3 || !words) {
|
|
doHelp(message, helpmsg);
|
|
return;
|
|
}
|
|
|
|
var prv = false;
|
|
var amountOffset = 2;
|
|
if (words.length >= 4 && words[1] === 'private') {
|
|
prv = true;
|
|
amountOffset = 3;
|
|
}
|
|
|
|
let amount = getValidatedAmount(words[amountOffset]);
|
|
|
|
if (amount === null) {
|
|
message.reply('I dont know how to tip that many coins...').then(message => message.delete(5000));
|
|
return;
|
|
}
|
|
|
|
if (message.mentions.users.first().id) {
|
|
sendLBC(message, tipper, message.mentions.users.first().id.replace('!', ''), amount, prv);
|
|
}
|
|
else {
|
|
message.reply('Sorry, I could not find a user in your tip...').then(message => message.delete(5000));
|
|
}
|
|
}
|
|
|
|
|
|
function doMultiTip(message, tipper, words, helpmsg) {
|
|
if (!words) {
|
|
doHelp(message, helpmsg);
|
|
return;
|
|
}
|
|
if (words.length < 4) {
|
|
doTip(message, tipper, words, helpmsg);
|
|
return;
|
|
}
|
|
var 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 coins...').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 (var i = 0; i < userIDs.length; i++) {
|
|
sendLBC(message, tipper, userIDs[i], amount, prv);
|
|
}
|
|
}
|
|
|
|
|
|
function doRoleTip(message, tipper, words, helpmsg) {
|
|
if (!words || words.length < 3) {
|
|
doHelp(message, helpmsg);
|
|
return;
|
|
}
|
|
var prv = false;
|
|
var 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 coins...').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 (var i = 0; i < userIDs; i++) {
|
|
sendLBC(message, tipper, userIDs[i], amount, prv);
|
|
}
|
|
return;
|
|
}
|
|
else {
|
|
message.reply('Sorry, I could not find any users to tip in that role...').then(message => message.delete(5000));
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
message.reply('Sorry, I could not find any roles in your tip...').then(message => message.delete(5000));
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
function findUserIDsAndAmount(message, words, prv) {
|
|
var idList = [];
|
|
var amount = null;
|
|
var count = 0;
|
|
var startOffset = 1;
|
|
if (prv) startOffset = 2;
|
|
var userPattern = message.mentions.USERS_PATTERN;
|
|
if (!userPattern || !userPattern.test('<@123456789>')) userPattern = new Regex(/<@!?[0-9]+>/);
|
|
|
|
for (var i = startOffset; i < words.length; i++) {
|
|
if (userPattern.test(words[i])) {
|
|
count++;
|
|
}
|
|
else {
|
|
amount = getValidatedAmount(words[i]);
|
|
if (amount == null) break;
|
|
}
|
|
}
|
|
if (count > 0) idList = message.mentions.users.first(count).forEach(function(user) { return user.id.replace('!', ''); });
|
|
return [idList, amount];
|
|
}
|
|
|
|
|
|
function sendLbc(message, tipper, recipient, amount, privacyFlag) {
|
|
getAddress(recipient, function (err, address) {
|
|
if (err) {
|
|
message.reply(err.message).then(message => message.delete(5000));
|
|
}
|
|
else {
|
|
lbry.sendFrom(tipper, address, amount, 1, null, null, function (err, txId) {
|
|
if (err) {
|
|
message.reply(err.message).then(message => message.delete(5000));
|
|
}
|
|
else {
|
|
var tx = txLink(txId);
|
|
var msgtail = '\nDM me with `' + message.content.split(" ", 1)[0] + '` for command specific instructions or with `!tipcommands` for all available commands';
|
|
if (privacyFlag) {
|
|
var authmsg = 'You have just privately tipped <@' + recipient + '> ' + amount + ' LBC.\n' + tx + msgtail;
|
|
message.author.send(authmsg);
|
|
if (message.author.id != message.mentions.users.first().id) {
|
|
var recipientmsg = 'You have just been privately tipped ' + amount + ' LBC by <@' + tipper + '>.\n' + tx + msgtail;
|
|
message.mentions.users.first().send(recipientmsg);
|
|
}
|
|
} else {
|
|
var generalmsg =
|
|
'Wubba lubba dub dub! <@' + tipper + '> tipped <@' + recipient + '> ' + amount + ' LBC.\n' + 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) {
|
|
if ((msg.channel.type == 'dm') || (msg.channel.id === spamchannel)) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
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 "<https://explorer.lbry.io/tx/" + txId + ">";
|
|
}
|
|
|
|
|
|
function formatDescriptions(msgparts) {
|
|
return msgparts.map(elem => '\t**' + elem[0] + '**\n\t\t' + elem[1] + '\n')
|
|
.join('');
|
|
}
|