mirror of
https://github.com/LBRYFoundation/lbry-wunderbot.git
synced 2025-08-23 17:47:27 +00:00
285 lines
8.9 KiB
JavaScript
285 lines
8.9 KiB
JavaScript
'use strict';
|
|
|
|
let lbry;
|
|
let mongo;
|
|
let discordBot;
|
|
let moment = require('moment');
|
|
let request = require('request');
|
|
let sleep = require('sleep');
|
|
let config = require('config');
|
|
let channels = config.get('claimbot').channels;
|
|
const Discord = require('discord.js');
|
|
|
|
module.exports = {
|
|
init: init
|
|
};
|
|
|
|
function init(discordBot_) {
|
|
if (lbry) {
|
|
throw new Error('init was already called once');
|
|
}
|
|
|
|
discordBot = discordBot_;
|
|
|
|
const MongoClient = require('mongodb').MongoClient;
|
|
MongoClient.connect(config.get('mongodb').url, function(err, db) {
|
|
if (err) {
|
|
throw err;
|
|
}
|
|
mongo = db;
|
|
|
|
const bitcoin = require('bitcoin');
|
|
lbry = new bitcoin.Client(config.get('lbrycrd'));
|
|
|
|
console.log('Activating claimbot ');
|
|
discordBot.channels.get(channels[0]).send('activating claimbot');
|
|
|
|
setInterval(function() {
|
|
announceNewClaims();
|
|
}, 60 * 1000);
|
|
announceNewClaims();
|
|
});
|
|
}
|
|
|
|
function announceNewClaims() {
|
|
if (!mongo) {
|
|
discordPost('Failed to connect to mongo', {});
|
|
return;
|
|
}
|
|
|
|
if (!lbry) {
|
|
discordPost('Failed to connect to lbrycrd', {});
|
|
return;
|
|
}
|
|
|
|
Promise.all([getLastBlock(), lbryCall('getinfo')])
|
|
.then(function([lastProcessedBlock, currentBlockInfo]) {
|
|
const currentHeight = currentBlockInfo['blocks'];
|
|
console.log(currentHeight);
|
|
if (lastProcessedBlock === null) {
|
|
console.log('First run. Setting last processed block to ' + currentHeight + ' and exiting.');
|
|
return setLastBlock(currentHeight);
|
|
}
|
|
|
|
const testBlock = false;
|
|
|
|
if (testBlock || lastProcessedBlock < currentHeight) {
|
|
const firstBlockToProcess = testBlock || lastProcessedBlock + 1,
|
|
lastBlockToProcess = testBlock || currentHeight;
|
|
|
|
console.log('Doing blocks ' + firstBlockToProcess + ' to ' + lastBlockToProcess);
|
|
return announceClaimsLoop(firstBlockToProcess, lastBlockToProcess, currentHeight);
|
|
}
|
|
})
|
|
.catch(function(err) {
|
|
discordPost(err.stack, {});
|
|
});
|
|
}
|
|
|
|
function announceClaimsLoop(block, lastBlock, currentHeight) {
|
|
let claimsFound = 0;
|
|
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);
|
|
claimsFound = claims.length;
|
|
return Promise.all(
|
|
claims.map(function(claim) {
|
|
//the API has a rate limit. to avoid hitting it we must have a small delay between each message
|
|
//if claims were found in this block, then we wait, otherwise we don't
|
|
if (claimsFound > 0 && claim.hasOwnProperty('claimId')) sleep.msleep(300);
|
|
return announceClaim(claim, block, currentHeight);
|
|
})
|
|
);
|
|
})
|
|
.then(function() {
|
|
return setLastBlock(block);
|
|
})
|
|
.then(function() {
|
|
const nextBlock = block + 1;
|
|
if (nextBlock <= lastBlock) {
|
|
return announceClaimsLoop(nextBlock, lastBlock, currentHeight);
|
|
}
|
|
});
|
|
}
|
|
|
|
function announceClaim(claim, claimBlockHeight, currentHeight) {
|
|
console.log('' + claimBlockHeight + ': New claim for ' + claim['name']);
|
|
console.log(claim);
|
|
|
|
//ignore supports for now
|
|
//the issue with supports is that they should be treated completely differently
|
|
//they are not new claims...
|
|
if (claim.hasOwnProperty('supported claimId')) return;
|
|
|
|
let options = {
|
|
method: 'GET',
|
|
url: 'http://127.0.0.1:5000/claim_decode/' + claim['name']
|
|
};
|
|
|
|
request(options, function(error, response, body) {
|
|
if (error) throw new Error(error);
|
|
try {
|
|
console.log(JSON.stringify(JSON.parse(body), null, 2));
|
|
let claimData = null;
|
|
let channelName = null;
|
|
try {
|
|
body = JSON.parse(body);
|
|
if (body.hasOwnProperty('stream') && body.stream.hasOwnProperty('metadata')) {
|
|
claimData = body.stream.metadata;
|
|
channelName = body.hasOwnProperty('channel_name') ? body['channel_name'] : null;
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
return;
|
|
}
|
|
|
|
return Promise.all([lbryCall('getvalueforname', claim['name']), lbryCall('getclaimsforname', claim['name'])]).then(function([currentWinningClaim, claimsForName]) {
|
|
//console.log(JSON.stringify(claimData));
|
|
let value = null;
|
|
if (claimData !== null) value = claimData;
|
|
else {
|
|
try {
|
|
value = JSON.parse(claim['value']);
|
|
} catch (e) {}
|
|
}
|
|
|
|
const text = [];
|
|
|
|
if (value) {
|
|
/*
|
|
if (channelName) {
|
|
text.push("Channel: lbry://" + channelName);
|
|
}
|
|
else
|
|
*/
|
|
console.log(value);
|
|
if (value['author']) {
|
|
text.push('author: ' + 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*');
|
|
}
|
|
|
|
//"fee":{"currency":"LBC","amount":186,"version":"_0_0_1","address":"bTGoFCakvQXvBrJg1b7FJzombFUu6iRJsk"}
|
|
if (value['fee']) {
|
|
const fees = [];
|
|
text.push('Price: ' + value['fee'].amount + ' *' + value['fee'].currency + '*');
|
|
}
|
|
|
|
if (!claim['is controlling']) {
|
|
// 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;
|
|
|
|
text.push('Takes effect on approx. **' + moment(takeoverTime, 'x').format('MMMM Do [at] HH:mm [UTC]') + '** (block ' + takeoverHeight + ')');
|
|
}
|
|
|
|
const richEmbeded = {
|
|
author: {
|
|
name: value['author'] || 'Anonymous',
|
|
url: `http://open.lbry.io/${claim['name']}#${claim['claimId']}`,
|
|
icon_url: 'http://barkpost-assets.s3.amazonaws.com/wp-content/uploads/2013/11/3dDoge.gif'
|
|
},
|
|
title: 'lbry://' + (channelName ? channelName + '/' : '') + claim['name'],
|
|
color: 1399626,
|
|
description: escapeSlackHtml(text.join('\n')),
|
|
footer: {
|
|
text: 'Block ' + claimBlockHeight + ' • Claim ID ' + claim['claimId']
|
|
},
|
|
image: { url: !value['nsfw'] ? value['thumbnail'] || '' : '' },
|
|
url: `http://open.lbry.io/${claim['name']}#${claim['claimId']}`
|
|
};
|
|
|
|
discordPost(text, richEmbeded);
|
|
}
|
|
});
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
});
|
|
}
|
|
|
|
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();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function discordPost(text, params) {
|
|
let richEmbeded = new Discord.RichEmbed(params);
|
|
|
|
channels.forEach(channel => {
|
|
discordBot.channels
|
|
.get(channel)
|
|
.send('', richEmbeded)
|
|
.catch(console.error);
|
|
});
|
|
}
|
|
|
|
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);
|
|
}
|
|
});
|
|
});
|
|
}
|