added gilding functionality
This commit is contained in:
parent
f7e3da2880
commit
49e043b0ce
6 changed files with 195 additions and 20 deletions
198
app.js
198
app.js
|
@ -11,14 +11,10 @@ if (config.debug) {
|
|||
}
|
||||
|
||||
// URLS
|
||||
const howToUseUrl = 'https://reddit.com/r/lbry';
|
||||
const baseUrl = 'https://oauth.reddit.com';
|
||||
const rateUrl = 'https://api.lbry.io/lbc/exchange_rate';
|
||||
const tokenUrlFormat = 'https://%s:%s@www.reddit.com/api/v1/access_token';
|
||||
const txBaseUrl = 'https://explorer.lbry.io/tx';
|
||||
|
||||
// Other globals
|
||||
const userAgent = 'lbryian/1.0.0 Node.js (by /u/lbryian)';
|
||||
const commentKind = 't1';
|
||||
const privateMessageKind = 't4';
|
||||
let globalAccessToken;
|
||||
|
@ -29,6 +25,8 @@ const messageTemplates = {};
|
|||
const templateNames = [
|
||||
'onbalance',
|
||||
'ondeposit',
|
||||
'ongild',
|
||||
'ongild.insufficientfunds',
|
||||
'onsendtip',
|
||||
'onsendtip.insufficientfunds',
|
||||
'onsendtip.invalidamount',
|
||||
|
@ -223,7 +221,8 @@ const sendTip = (sender, recipient, amount, tipdata, callback) => {
|
|||
(senderBalance, cb) => {
|
||||
// balance is less than amount to tip, or the difference after sending the tip is negative
|
||||
if (senderBalance < amount || (senderBalance - amount) < 0) {
|
||||
return sendPMUsingTemplate('onsendtip.insufficientfunds', { how_to_use_url: howToUseUrl }, message.data.author, () => {
|
||||
return sendPMUsingTemplate('onsendtip.insufficientfunds',
|
||||
{ how_to_use_url: config.howToUseUrl, recipient: `u/${recipient}`, amount: amount, balance: senderBalance }, message.data.author, () => {
|
||||
cb(new Error('Insufficient funds'), null);
|
||||
});
|
||||
}
|
||||
|
@ -269,7 +268,7 @@ const sendTip = (sender, recipient, amount, tipdata, callback) => {
|
|||
},
|
||||
(res, fields, cb) => {
|
||||
// reply to the source message with message template after successful commit
|
||||
replyMessageUsingTemplate('onsendtip', { recipient: `u/${recipient}`, tip: `${amount} LBC ($${tipdata.amountUsd})`, how_to_use_url: howToUseUrl},
|
||||
replyMessageUsingTemplate('onsendtip', { recipient: `u/${recipient}`, tip: `${amount} LBC ($${tipdata.amountUsd})`, how_to_use_url: config.howToUseUrl},
|
||||
tipdata.message.data.name, cb);
|
||||
},
|
||||
(success, cb) => {
|
||||
|
@ -343,9 +342,33 @@ const convertLbcToUsd = (amount, callback) => {
|
|||
});
|
||||
};
|
||||
|
||||
const gildThing = (thingFullId, callback) => {
|
||||
const url = `${baseUrl}/api/v1/gold/gild/${thingFullId}`;
|
||||
request.post({ url, headers: { 'User-Agent': config.userAgent, 'Authorization': 'Bearer ' + globalAccessToken } }, (err, res, body) => {
|
||||
if (err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = JSON.parse(body);
|
||||
} catch (e) {
|
||||
return callback(e, null);
|
||||
}
|
||||
|
||||
if (response.json.ratelimit > 0 ||
|
||||
response.json.errors.length > 0) {
|
||||
return callback(new Error('Rate limited.'), null);
|
||||
}
|
||||
|
||||
// success
|
||||
return callback(null, true);
|
||||
});
|
||||
};
|
||||
|
||||
const markMessageRead = (messageFullId, callback) => {
|
||||
const url = `${baseUrl}/api/read_message`;
|
||||
request.post({ url, form: { id: messageFullId }, headers: { 'User-Agent': userAgent, 'Authorization': 'Bearer ' + globalAccessToken } }, (err, res, body) => {
|
||||
request.post({ url, form: { id: messageFullId }, headers: { 'User-Agent': config.userAgent, 'Authorization': 'Bearer ' + globalAccessToken } }, (err, res, body) => {
|
||||
if (err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
@ -381,7 +404,7 @@ const sendPMUsingTemplate = (template, substitions, subject, recipient, callback
|
|||
request.post({
|
||||
url,
|
||||
form: { api_type: 'json', text: messageText, subject, to: recipient },
|
||||
headers: { 'User-Agent': userAgent, 'Authorization': 'Bearer ' + globalAccessToken }
|
||||
headers: { 'User-Agent': config.userAgent, 'Authorization': 'Bearer ' + globalAccessToken }
|
||||
}, (err, res, body) => {
|
||||
if (err) {
|
||||
return callback(err, null);
|
||||
|
@ -422,7 +445,7 @@ const replyMessageUsingTemplate = (template, substitutions, sourceMessageFullId,
|
|||
request.post({
|
||||
url,
|
||||
form: { api_type: 'json', text: messageText, thing_id: sourceMessageFullId },
|
||||
headers: { 'User-Agent': userAgent, 'Authorization': 'Bearer ' + globalAccessToken }
|
||||
headers: { 'User-Agent': config.userAgent, 'Authorization': 'Bearer ' + globalAccessToken }
|
||||
}, (err, res, body) => {
|
||||
if (err) {
|
||||
return callback(err, null);
|
||||
|
@ -447,7 +470,7 @@ const replyMessageUsingTemplate = (template, substitutions, sourceMessageFullId,
|
|||
|
||||
const getMessageAuthor = (thingId, accessToken, callback) => {
|
||||
const url = util.format('%s/api/info?id=%s', baseUrl, thingId);
|
||||
request.get({ url: url, headers: { 'User-Agent': userAgent, 'Authorization': 'Bearer ' + globalAccessToken } }, (err, res, body) => {
|
||||
request.get({ url: url, headers: { 'User-Agent': config.userAgent, 'Authorization': 'Bearer ' + globalAccessToken } }, (err, res, body) => {
|
||||
if (err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
@ -463,6 +486,140 @@ const getMessageAuthor = (thingId, accessToken, callback) => {
|
|||
});
|
||||
};
|
||||
|
||||
const sendGild = (sender, recipient, amount, gilddata, callback) => {
|
||||
console.log(`gilding ${recipient} with ${amount} LBC worth ${gilddata.amountUsd} from ${sender}`);
|
||||
|
||||
const data = {};
|
||||
async.waterfall([
|
||||
(cb) => {
|
||||
// Start DB transaction
|
||||
db.beginTransaction((err) => {
|
||||
if (err) {
|
||||
return cb(err, null);
|
||||
}
|
||||
return cb(null, true);
|
||||
});
|
||||
},
|
||||
(started, cb) => {
|
||||
// start a transaction
|
||||
// check the sender's balance
|
||||
createOrGetUserId(sender, cb);
|
||||
},
|
||||
(senderId, cb) => {
|
||||
data.senderId = senderId;
|
||||
getBalance(senderId, cb);
|
||||
},
|
||||
(senderBalance, cb) => {
|
||||
// balance is less than amount required for gilding, or the difference after sending the tip is negative
|
||||
if (senderBalance < amount || (senderBalance - amount) < 0) {
|
||||
return sendPMUsingTemplate('ongild.insufficientfunds',
|
||||
{ how_to_use_url: config.howToUseUrl, amount: amount, amount_usd: gilddata.amountUsd, balance: senderBalance }, message.data.author, () => {
|
||||
cb(new Error('Insufficient funds'), null);
|
||||
});
|
||||
}
|
||||
|
||||
return db.query('UPDATE Users SET Balance = Balance - ? WHERE Id = ?', [amount, data.senderId], cb);
|
||||
},
|
||||
(res, fields, cb) => {
|
||||
// save the message
|
||||
const msgdata = gilddata.message.data;
|
||||
db.query( ['INSERT INTO Messages (AuthorId, Type, FullId, RedditId, ParentRedditId, Subreddit, Body, Context, RedditCreated, Created) ',
|
||||
'VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, UTC_TIMESTAMP())'].join(''),
|
||||
[data.senderId,
|
||||
gilddata.message.kind === privateMessageKind ? 1 : 2,
|
||||
msgdata.name,
|
||||
msgdata.id,
|
||||
msgdata.parent_id,
|
||||
msgdata.subreddit,
|
||||
msgdata.body,
|
||||
msgdata.context,
|
||||
moment.utc(msgdata.created_utc * 1000).format('YYYY-MM-DD HH:mm:ss')
|
||||
], cb);
|
||||
},
|
||||
(res, fields, cb) => {
|
||||
// save the tip information
|
||||
db.query( ['INSERT INTO Tips (MessageId, SenderId, RecipientId, Amount, AmountUsd, ParsedAmount, IsGild, Created) ',
|
||||
'VALUES (?, ?, ?, ?, ?, ?, 1, UTC_TIMESTAMP())'].join(''),
|
||||
[res.insertId,
|
||||
data.senderId,
|
||||
data.recipientId,
|
||||
amount,
|
||||
gilddata.amountUsd,
|
||||
['$', config.gildPrice].join(''),
|
||||
], cb);
|
||||
},
|
||||
(res, fields, cb) => {
|
||||
// send the gild
|
||||
gildThing(gilddata.message.data.name, cb);
|
||||
},
|
||||
(success, cb) => {
|
||||
// reply to the source message with message template after successful commit
|
||||
replyMessageUsingTemplate('ongild', { sender: `u/${sender}`, recipient: `u/${recipient}`, gild_amount: `${amount} LBC ($${gilddata.amountUsd})`, how_to_use_url: config.howToUseUrl},
|
||||
gilddata.message.data.name, cb);
|
||||
},
|
||||
(success, cb) => {
|
||||
// Mark the message as read
|
||||
markMessageRead(gilddata.message.data.name, cb);
|
||||
},
|
||||
(success, cb) => {
|
||||
// commit the transaction
|
||||
db.commit((err) => {
|
||||
if (err) {
|
||||
return cb(err, null);
|
||||
}
|
||||
|
||||
return cb(null, true);
|
||||
});
|
||||
}
|
||||
], (err) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return db.rollback(() => {
|
||||
callback(err, null);
|
||||
});
|
||||
}
|
||||
|
||||
// success
|
||||
return callback(null, true);
|
||||
});
|
||||
};
|
||||
|
||||
const doGild = function(message, callback) {
|
||||
async.waterfall([
|
||||
(cb) => {
|
||||
getMessageAuthor(message.data.parent_id, globalAccessToken, cb);
|
||||
},
|
||||
(recipient, cb) => {
|
||||
const sender = message.data.author;
|
||||
if (sender !== recipient) {
|
||||
return cb(null, { message, recipient, sender, amountUsd: config.gildPrice });
|
||||
}
|
||||
|
||||
return cb(null, null);
|
||||
},
|
||||
(gilddata, cb) => {
|
||||
if (gilddata && gilddata.amountUsd > 0) {
|
||||
return convertUsdToLbc(gilddata.amountUsd, (err, convertedAmount) => {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
gilddata.amountLbc = convertedAmount;
|
||||
return cb(null, gilddata);
|
||||
});
|
||||
}
|
||||
return cb(null, null);
|
||||
},
|
||||
(data, cb) => {
|
||||
if (gilddata) {
|
||||
return sendGild(data.sender, data.recipient, data.amountLbc, data, cb);
|
||||
}
|
||||
|
||||
return cb(null, null);
|
||||
}
|
||||
], callback);
|
||||
};
|
||||
|
||||
const doSendTip = function(body, message, callback) {
|
||||
/**
|
||||
* accepted formats:
|
||||
|
@ -488,7 +645,7 @@ const doSendTip = function(body, message, callback) {
|
|||
// get the amount
|
||||
amountUsd = parseFloat(parts[nameFirst ? 1 : 0].substring(1));
|
||||
if (isNaN(amountUsd) || amountUsd <= 0) {
|
||||
return sendPMUsingTemplate('onsendtip.invalidamount', { how_to_use_url: howToUseUrl }, message.data.author, () => {
|
||||
return sendPMUsingTemplate('onsendtip.invalidamount', { how_to_use_url: config.howToUseUrl }, message.data.author, () => {
|
||||
callback(null, null);
|
||||
});
|
||||
}
|
||||
|
@ -568,7 +725,7 @@ const doSendBalance = (message, callback) => {
|
|||
},
|
||||
(balance, cb) => {
|
||||
// send message with balance
|
||||
replyMessageUsingTemplate('onbalance', { how_to_use_url: howToUseUrl, amount: balance }, message.data.name, cb);
|
||||
replyMessageUsingTemplate('onbalance', { how_to_use_url: config.howToUseUrl, amount: balance }, message.data.name, cb);
|
||||
},
|
||||
(success, cb) => {
|
||||
// mark messge as read
|
||||
|
@ -625,7 +782,7 @@ const doWithdrawal = (amount, address, message, callback) => {
|
|||
(balance, cb) => {
|
||||
// check sufficient balance
|
||||
if (balance < amount || balance - amount < 0) {
|
||||
return sendPMUsingTemplate('onwithdraw.insufficientfunds', { how_to_use_url: howToUseUrl }, message.data.author, () => {
|
||||
return sendPMUsingTemplate('onwithdraw.insufficientfunds', { how_to_use_url: config.howToUseUrl }, message.data.author, () => {
|
||||
cb(new Error('Insufficient funds'), null);
|
||||
});
|
||||
}
|
||||
|
@ -658,7 +815,7 @@ const doWithdrawal = (amount, address, message, callback) => {
|
|||
},
|
||||
(success, cb) => {
|
||||
// send a reply
|
||||
replyMessageUsingTemplate('onwithdraw', { how_to_use_url: howToUseUrl, address: address, amount: amount, txid: data.txhash }, message.data.name, cb);
|
||||
replyMessageUsingTemplate('onwithdraw', { how_to_use_url: config.howToUseUrl, address: address, amount: amount, txid: data.txhash }, message.data.name, cb);
|
||||
}
|
||||
], (err) => {
|
||||
if (err) {
|
||||
|
@ -683,7 +840,7 @@ const doSendDepositAddress = (message, callback) => {
|
|||
},
|
||||
(address, cb) => {
|
||||
// send message with balance
|
||||
replyMessageUsingTemplate('ondeposit', { how_to_use_url: howToUseUrl, address: address }, message.data.name, cb);
|
||||
replyMessageUsingTemplate('ondeposit', { how_to_use_url: config.howToUseUrl, address: address }, message.data.name, cb);
|
||||
},
|
||||
(success, cb) => {
|
||||
// mark messge as read
|
||||
|
@ -731,13 +888,13 @@ const processMessage = function(message, callback) {
|
|||
const amount = parseFloat(parts[1]);
|
||||
if (isNaN(amount) || amount < 0) {
|
||||
// TODO: send a message that the withdrawal amount is invalid
|
||||
return sendPMUsingTemplate('onwithdraw.invalidamount', { how_to_use_url: howToUseUrl }, message.data.author, () => {
|
||||
return sendPMUsingTemplate('onwithdraw.invalidamount', { how_to_use_url: config.howToUseUrl }, message.data.author, () => {
|
||||
callback(null, null);
|
||||
});
|
||||
}
|
||||
|
||||
if (amount <= config.lbrycrd.txfee) {
|
||||
return sendPMUsingTemplate('onwithdraw.amountltefee', { how_to_use_url: howToUseUrl, amount: amount, fee: config.lbrycrd.txfee }, message.data.author, () => {
|
||||
return sendPMUsingTemplate('onwithdraw.amountltefee', { how_to_use_url: config.howToUseUrl, amount: amount, fee: config.lbrycrd.txfee }, message.data.author, () => {
|
||||
callback(null, null);
|
||||
});
|
||||
}
|
||||
|
@ -747,7 +904,7 @@ const processMessage = function(message, callback) {
|
|||
try {
|
||||
base58.decode(address);
|
||||
} catch(e) {
|
||||
return sendPMUsingTemplate('onwithdraw.invalidaddress', { how_to_use_url: howToUseUrl }, message.data.author, () => {
|
||||
return sendPMUsingTemplate('onwithdraw.invalidaddress', { how_to_use_url: config.howToUseUrl }, message.data.author, () => {
|
||||
callback(null, null);
|
||||
});
|
||||
}
|
||||
|
@ -759,8 +916,13 @@ const processMessage = function(message, callback) {
|
|||
}
|
||||
|
||||
if (message.kind === commentKind) {
|
||||
const bodyParts = body.split(' ', 2);
|
||||
if (bodyParts.length === 2 && ('gild' === bodyParts[0].toLowerCase() || 'gild' === bodyParts[1].toLowerCase())) {
|
||||
doGild(message, callback);
|
||||
} else {
|
||||
doSendTip(body, message, callback);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Run the bot
|
||||
|
|
|
@ -6,8 +6,12 @@ module.exports = {
|
|||
username: '<USERNAME>',
|
||||
password: '<PASSWORD>',
|
||||
|
||||
userAgent: '<USER_AGENT_STRING>',
|
||||
howToUseUrl: 'https://np.reddit.com/r/lbry/wiki/tipbot',
|
||||
|
||||
// for handling tip comments
|
||||
redditName: 'u/lbryian',
|
||||
gildPrice: 2.5,
|
||||
|
||||
mariadb: {
|
||||
host: 'localhost',
|
||||
|
|
|
@ -37,6 +37,7 @@ CREATE TABLE Tips
|
|||
`ParsedAmount` VARCHAR(20) NOT NULL COMMENT 'user amount string, $0.x, 0.x usd or 0.x lbc',
|
||||
`AmountUsd` DECIMAL(18,2) UNSIGNED,
|
||||
`Amount` DECIMAL(18,8) UNSIGNED NOT NULL,
|
||||
`IsGild` TINYINT(1) DEFAULT 0 NOT NULL,
|
||||
`Created` DATETIME NOT NULL,
|
||||
PRIMARY KEY `PK_TipId` (`Id`),
|
||||
FOREIGN KEY `FK_TipSender` (`SenderId`) REFERENCES `Users` (`Id`),
|
||||
|
|
4
templates/ongild.insufficientfunds.txt
Normal file
4
templates/ongild.insufficientfunds.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
You do not have sufficient funds to gild {recipient}. Gilding costs **{amount} LBC** ({amount_usd}) but your balance is **{balance} LBC**.
|
||||
|
||||
----
|
||||
[^How ^to ^use]({how_to_use_url}) ^• [^What ^is ^LBRY?](https://lbry.io/faq/what-is-lbry) ^• ^r/lbry
|
4
templates/ongild.txt
Normal file
4
templates/ongild.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
{recipient}, {sender} gilded your post for {gild_amount}! Congratulations!
|
||||
|
||||
----
|
||||
[^How ^to ^use]({how_to_use_url}) ^• [^What ^is ^LBRY?](https://lbry.io/faq/what-is-lbry) ^• ^r/lbry
|
|
@ -1,4 +1,4 @@
|
|||
You do not have sufficient funds to send a tip to {recipient}. You tried to send **{tip} LBC** but your balance is **{amount} LBC**.
|
||||
You do not have sufficient funds to send a tip to {recipient}. You tried to send **{amount} LBC** but your balance is **{balance} LBC**.
|
||||
|
||||
----
|
||||
[^How ^to ^use]({how_to_use_url}) ^• [^What ^is ^LBRY?](https://lbry.io/faq/what-is-lbry) ^• ^r/lbry
|
Loading…
Reference in a new issue