2017-02-02 23:52:24 +01:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
var lbry;
|
|
|
|
var mongo;
|
2017-02-13 21:44:51 +01:00
|
|
|
var slackbot;
|
|
|
|
var channel;
|
2017-02-02 23:52:24 +01:00
|
|
|
var moment = require('moment');
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
init: init,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2017-02-13 21:44:51 +01:00
|
|
|
function init(slackbot_, channel_, rpcuser, rpcpassword, mongodburl) {
|
2017-02-02 23:52:24 +01:00
|
|
|
if (lbry)
|
|
|
|
{
|
|
|
|
throw new Error('init was already called once');
|
|
|
|
}
|
|
|
|
|
2017-02-13 21:44:51 +01:00
|
|
|
slackbot = slackbot_;
|
|
|
|
|
|
|
|
channel = channel_;
|
|
|
|
if (!channel_)
|
2017-02-02 23:52:24 +01:00
|
|
|
{
|
|
|
|
console.log('No claims channel, disabling claimbot');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const MongoClient = require('mongodb').MongoClient;
|
|
|
|
MongoClient.connect(mongodburl, function (err, db) {
|
|
|
|
if (err)
|
|
|
|
{
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
mongo = db;
|
|
|
|
|
|
|
|
const bitcoin = require('bitcoin');
|
|
|
|
lbry = new bitcoin.Client({
|
|
|
|
host: 'localhost',
|
|
|
|
'port': 9245,
|
|
|
|
'user': rpcuser,
|
|
|
|
'pass': rpcpassword
|
|
|
|
});
|
|
|
|
|
2017-02-13 22:26:01 +01:00
|
|
|
console.log('Activating claimbot');
|
|
|
|
|
2017-02-02 23:52:24 +01:00
|
|
|
setInterval(function () {
|
2017-02-13 21:44:51 +01:00
|
|
|
announceNewClaims();
|
2017-02-02 23:52:24 +01:00
|
|
|
}, 60 * 1000);
|
2017-02-13 21:44:51 +01:00
|
|
|
announceNewClaims();
|
2017-02-02 23:52:24 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-02-13 21:44:51 +01:00
|
|
|
function announceNewClaims() {
|
2017-02-02 23:52:24 +01:00
|
|
|
|
|
|
|
if (!mongo)
|
|
|
|
{
|
2017-02-13 21:44:51 +01:00
|
|
|
slackPost('Failed to connect to mongo', {icon_emoji: ':exclamation:'});
|
2017-02-02 23:52:24 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!lbry)
|
|
|
|
{
|
2017-02-13 21:44:51 +01:00
|
|
|
slackPost('Failed to connect to lbrycrd', {icon_emoji: ':exclamation:'});
|
2017-02-02 23:52:24 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Promise.all([getLastBlock(), lbryCall('getinfo')])
|
|
|
|
.then(function ([lastProcessedBlock, currentBlockInfo]) {
|
|
|
|
const currentHeight = currentBlockInfo['blocks'];
|
|
|
|
|
|
|
|
// console.log('Checking for new blocks');
|
|
|
|
|
|
|
|
if (lastProcessedBlock === null)
|
|
|
|
{
|
|
|
|
console.log('First run. Setting last processed block to ' + currentHeight + ' and exiting.');
|
|
|
|
return setLastBlock(currentHeight);
|
|
|
|
}
|
|
|
|
|
2017-02-14 18:50:46 +01:00
|
|
|
const testBlock = false;
|
|
|
|
|
|
|
|
if (testBlock || lastProcessedBlock < currentHeight)
|
2017-02-02 23:52:24 +01:00
|
|
|
{
|
2017-02-14 18:50:46 +01:00
|
|
|
const firstBlockToProcess = testBlock || lastProcessedBlock + 1,
|
|
|
|
lastBlockToProcess = testBlock || currentHeight;
|
2017-02-02 23:52:24 +01:00
|
|
|
|
|
|
|
// console.log('Doing blocks ' + firstBlockToProcess + ' to ' + lastBlockToProcess);
|
2017-02-13 21:44:51 +01:00
|
|
|
return announceClaimsLoop(firstBlockToProcess, lastBlockToProcess, currentHeight);
|
2017-02-02 23:52:24 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch(function (err) {
|
2017-02-13 21:44:51 +01:00
|
|
|
slackPost(err.stack, {icon_emoji: ':exclamation:'});
|
2017-02-02 23:52:24 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-02-13 21:44:51 +01:00
|
|
|
function announceClaimsLoop(block, lastBlock, currentHeight) {
|
2017-02-02 23:52:24 +01:00
|
|
|
// console.log('Doing block ' + block)
|
|
|
|
return lbryCall('getblockhash', block)
|
|
|
|
.then(function (blockHash) {
|
|
|
|
return lbryCall('getblock', blockHash);
|
|
|
|
})
|
|
|
|
.then(function (blockData) {
|
|
|
|
return Promise.all(blockData['tx'].map(getClaimsForTxid))
|
|
|
|
})
|
|
|
|
.then(function (arrayOfClaimArrays) {
|
|
|
|
const claims = Array.prototype.concat(...arrayOfClaimArrays).filter(function (c) {
|
|
|
|
return !!c;
|
|
|
|
});
|
|
|
|
console.log('Found ' + claims.length + ' claims in ' + block);
|
|
|
|
return Promise.all(claims.map(function (claim) {
|
2017-02-13 21:44:51 +01:00
|
|
|
return announceClaim(claim, block, currentHeight);
|
2017-02-02 23:52:24 +01:00
|
|
|
}));
|
|
|
|
})
|
|
|
|
.then(function () {
|
|
|
|
return setLastBlock(block);
|
|
|
|
})
|
|
|
|
.then(function () {
|
|
|
|
const nextBlock = block + 1;
|
|
|
|
if (nextBlock <= lastBlock)
|
|
|
|
{
|
2017-02-13 21:44:51 +01:00
|
|
|
return announceClaimsLoop(nextBlock, lastBlock, currentHeight);
|
2017-02-02 23:52:24 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-02-13 21:44:51 +01:00
|
|
|
function announceClaim(claim, claimBlockHeight, currentHeight) {
|
2017-02-02 23:52:24 +01:00
|
|
|
console.log('' + claimBlockHeight + ': New claim for ' + claim['name']);
|
|
|
|
return Promise.all([
|
|
|
|
lbryCall('getvalueforname', claim['name']),
|
|
|
|
lbryCall('getclaimsforname', claim['name']),
|
|
|
|
])
|
|
|
|
.then(function ([currentWinningClaim, claimsForName]) {
|
|
|
|
let value;
|
|
|
|
try
|
|
|
|
{
|
|
|
|
value = JSON.parse(claim['value']);
|
|
|
|
}
|
|
|
|
catch (e)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
const text = [];
|
|
|
|
|
2017-02-14 18:50:46 +01:00
|
|
|
if (value)
|
2017-02-02 23:52:24 +01:00
|
|
|
{
|
2017-02-14 18:50:46 +01:00
|
|
|
if (value['author'])
|
2017-02-02 23:52:24 +01:00
|
|
|
{
|
2017-02-14 18:50:46 +01:00
|
|
|
text.push(value['author']);
|
|
|
|
}
|
|
|
|
if (value['description'])
|
|
|
|
{
|
|
|
|
text.push(value['description']);
|
|
|
|
}
|
|
|
|
// if (value['content_type'])
|
|
|
|
// {
|
|
|
|
// text.push("*Content Type:* " + value['content_type']);
|
|
|
|
// }
|
|
|
|
if (value['nsfw'])
|
|
|
|
{
|
|
|
|
text.push("*Warning: Adult Content*");
|
|
|
|
}
|
|
|
|
if (value['fee'])
|
|
|
|
{
|
|
|
|
const fees = [];
|
|
|
|
for (var key in value['fee'])
|
|
|
|
{
|
|
|
|
fees.push(value['fee'][key]['amount'] + ' ' + key);
|
|
|
|
}
|
|
|
|
text.push(fees.join(', '));
|
2017-02-02 23:52:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-14 18:50:46 +01:00
|
|
|
if (!claim['is controlling'])
|
2017-02-02 23:52:24 +01:00
|
|
|
{
|
2017-02-14 18:50:46 +01:00
|
|
|
// the following is based on https://lbry.io/faq/claimtrie-implementation
|
|
|
|
const lastTakeoverHeight = claimsForName['nLastTakeoverHeight'],
|
|
|
|
maxDelay = 4032, // 7 days of blocks at 2.5min per block
|
|
|
|
activationDelay = Math.min(maxDelay, Math.floor((claimBlockHeight - lastTakeoverHeight) / 32)),
|
|
|
|
takeoverHeight = claimBlockHeight + activationDelay,
|
|
|
|
secondsPerBlock = 161, // in theory this should be 150, but in practice its closer to 161
|
|
|
|
takeoverTime = Date.now() + ((takeoverHeight - currentHeight) * secondsPerBlock * 1000);
|
2017-02-02 23:52:24 +01:00
|
|
|
|
2017-02-14 18:50:46 +01:00
|
|
|
text.push('Takes effect on approx. *' + moment(takeoverTime, 'x').format('MMMM Do [at] HH:mm [UTC]') + '* (block ' + takeoverHeight + ')');
|
|
|
|
}
|
2017-02-02 23:52:24 +01:00
|
|
|
|
2017-02-14 18:50:46 +01:00
|
|
|
const attachment = {
|
|
|
|
"fallback": "New claim for lbry://" + claim['name'],
|
2017-02-02 23:52:24 +01:00
|
|
|
"color": "#155b4a",
|
|
|
|
// "pretext": "New claim in block " + claimBlockHeight,
|
2017-02-14 18:50:46 +01:00
|
|
|
// "author_name": 'lbry://' + claim['name'],
|
|
|
|
// "author_link": 'lbry://' + claim['name'],
|
2017-02-02 23:52:24 +01:00
|
|
|
// "author_icon": "http://flickr.com/icons/bobby.jpg",
|
2017-02-14 18:50:46 +01:00
|
|
|
"title": "lbry://" + claim['name'], //escapeSlackHtml(value['title']),
|
2017-02-02 23:52:24 +01:00
|
|
|
"title_link": "lbry://" + claim['name'],
|
|
|
|
"text": escapeSlackHtml(text.join("\n")),
|
2017-02-14 18:37:23 +01:00
|
|
|
// "fields": [],
|
2017-02-02 23:52:24 +01:00
|
|
|
// "image_url": value['nsfw'] ? null : value['thumbnail'],
|
2017-02-14 18:50:46 +01:00
|
|
|
// "thumb_url": (!value || value['nsfw']) ? null : value['thumbnail'],
|
2017-02-02 23:52:24 +01:00
|
|
|
"unfurl_links": false,
|
|
|
|
"unfurl_media": false,
|
|
|
|
"link_names": false,
|
|
|
|
"parse": "none",
|
|
|
|
"footer": "Block " + claimBlockHeight + " • Claim ID " + claim['claimId'],
|
|
|
|
"mrkdwn_in": ['text'],
|
|
|
|
};
|
|
|
|
|
2017-02-14 18:50:46 +01:00
|
|
|
if (value)
|
|
|
|
{
|
|
|
|
attachment['fallback'] += (': "' + value['title'] + '" by ' + value['author']);
|
|
|
|
attachment['author_name'] = 'lbry://' + claim['name'];
|
|
|
|
attachment['author_link'] = 'lbry://' + claim['name'];
|
|
|
|
attachment['title'] = escapeSlackHtml(value['title']);
|
|
|
|
if (!value['nsfw'])
|
|
|
|
{
|
|
|
|
attachment['thumb_url'] = value['thumbnail'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-13 21:44:51 +01:00
|
|
|
slackPost('', {icon_emoji: ':bellhop_bell:', attachments: [attachment]});
|
2017-02-14 18:37:23 +01:00
|
|
|
});
|
2017-02-02 23:52:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function escapeSlackHtml(txt) {
|
|
|
|
return txt.replace('&', '&').replace('<', '<').replace('>', '>');
|
|
|
|
}
|
|
|
|
|
|
|
|
function getClaimsForTxid(txid) {
|
|
|
|
return lbryCall('getclaimsfortx', txid)
|
|
|
|
.catch(function (err) {
|
|
|
|
// an error here most likely means the transaction is spent,
|
|
|
|
// which also means there are no claims worth looking at
|
|
|
|
return [];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function getLastBlock() {
|
|
|
|
return new Promise(function (resolve, reject) {
|
|
|
|
mongo.collection('claimbot').findOne({}, function (err, obj) {
|
|
|
|
if (err)
|
|
|
|
{
|
|
|
|
reject(err);
|
|
|
|
}
|
|
|
|
else if (!obj)
|
|
|
|
{
|
|
|
|
mongo.collection('claimbot').createIndex({'last_block': 1}, {unique: true});
|
|
|
|
resolve(null);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
resolve(obj.last_block);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function setLastBlock(block) {
|
|
|
|
return new Promise(function (resolve, reject) {
|
|
|
|
mongo.collection('claimbot').findOneAndUpdate(
|
|
|
|
{'last_block': {'$exists': true}},
|
|
|
|
{'last_block': block},
|
|
|
|
{'upsert': true, 'returnOriginal': false},
|
|
|
|
function (err, obj) {
|
|
|
|
if (!err && obj && obj.value.last_block != block)
|
|
|
|
{
|
|
|
|
reject('Last value should be ' + block + ', but it is ' + obj.value.last_block);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
resolve();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-02-13 21:44:51 +01:00
|
|
|
function slackPost(text, params) {
|
|
|
|
slackbot.postMessage(channel, text, params).fail(function (value) {
|
2017-02-13 22:26:01 +01:00
|
|
|
console.log('FAILED TO SLACK to ' + channel + '. Text: "' + text + '". Params: ' + JSON.stringify(params) + "\nResponse: " + JSON.stringify(value));
|
2017-02-13 21:44:51 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-02-02 23:52:24 +01:00
|
|
|
function lbryCall(...args) {
|
|
|
|
return new Promise(function (resolve, reject) {
|
|
|
|
lbry.cmd(...args, function (err, ...response) {
|
|
|
|
if (err)
|
|
|
|
{
|
|
|
|
reject(new Error('JSONRPC call failed. Args: [' + args.join(', ') + ']'));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
resolve(...response);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|