'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'); const rp = require('request-promise'); const jsonfile = require('jsonfile'); const path = require('path'); const fs = require('fs'); const appRoot = require('app-root-path'); const fileExists = require('file-exists'); 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; console.log('Activating claimbot '); discordBot.channels.get(channels[0]).send('activating claimbot'); // Check that our syncState file exist. fileExists(path.join(appRoot.path, 'syncState.json'), (err, exists) => { if (err) { throw err; } if (!exists) { fs.writeFileSync(path.join(appRoot.path, 'syncState.json'), '{}'); } }); setInterval(function() { announceClaims(); }, 60 * 1000); announceClaims(); }); } async function announceClaims() { // get last block form the explorer API. let lastBlockHeight = JSON.parse(await rp('https://explorer.lbry.io/api/v1/status')).status.height; // get the latest claims from chainquery since last sync let syncState = await getJSON(path.join(appRoot.path, 'syncState.json')); // get our persisted state if (!syncState.LastSyncTime) { syncState.LastSyncTime = new Date() .toISOString() .slice(0, 19) .replace('T', ' '); } let claimsSince = JSON.parse(await getClaimsSince(syncState.LastSyncTime)).data; // filter out the claims that we should add to discord let claims = []; for (let claim of claimsSince) { claim.value = JSON.parse(claim.value); if (claim.value.Claim && claim.value.Claim.stream) { claim.metadata = claim.value.Claim.stream.metadata; } else { claim.metadata = null; } if (claim.bid_state !== 'Spent' || claim.bid_state !== 'Expired') { claims.push(claim); } } for (let claim of claims) { console.log(claim); } // send each claim to discord. for (let claim of claims) { console.log(claim); if (claim.metadata) { // If its a claim, make a claimEmbed let claimEmbed = new Discord.RichEmbed() .setAuthor( claim.channel ? `New claim from ${claim.channel}` : 'New claim from Anonymous', 'http://barkpost-assets.s3.amazonaws.com/wp-content/uploads/2013/11/3dDoge.gif', `http://open.lbry.io/${claim.channel ? `${claim.channel}#${claim.channelId}/${claim['name']}` : `${claim['name']}#${claim['claimId']}`}` ) .setTitle('lbry://' + (claim.channel ? `${claim.channel}/` : '') + claim['name']) .setURL(`http://open.lbry.io/${claim.channel ? `${claim.channel}#${claim.channelId}/${claim['name']}` : `${claim['name']}#${claim['claimId']}`}`) .setColor(1399626) .setFooter(`Block ${claim.height} • Claim ID ${claim.claimId} • Data from Chainquery`); if (claim.metadata['title']) claimEmbed.addField('Title', claim.metadata['title']); if (claim.channel) claimEmbed.addField('Channel', claim.channel); if (claim.metadata['description']) { claimEmbed.addField('Description', claim.metadata['description'].substring(0, 1020)); } if (claim.metadata['fee']) claimEmbed.addField('Fee', claim.metadata['fee'].amount + ' ' + claim.metadata['fee'].currency); if (claim.metadata['license'] && claim.metadata['license'].length > 2) claimEmbed.addField('License', claim.metadata['license']); if (!claim.metadata['nsfw'] && claim.metadata['thumbnail']) claimEmbed.setImage(claim.metadata['thumbnail']); if (claim.bid_state !== 'Controlling' && claim.height < claim.valid_at_height) { // Claim have not taken over the old claim, send approx time to event. let takeoverTime = Date.now() + (claim.valid_at_height - lastBlockHeight) * 161 * 1000; // in theory this should be 150, but in practice its closer to 161 claimEmbed.addField('Takes effect on approx', moment(takeoverTime, 'x').format('MMMM Do [at] HH:mm [UTC]') + ` • at block height ${claim.valid_at_height}`); } claimEmbed.addField('Claimed for', `${Number.parseFloat(claim.outputValue)} LBC`); discordPost(claimEmbed); } else if (claim.name.charAt(0) === '@') { // This is a channel claim let channelEmbed = new Discord.RichEmbed() .setAuthor('New channel claim', 'http://barkpost-assets.s3.amazonaws.com/wp-content/uploads/2013/11/3dDoge.gif', `http://open.lbry.io/${claim['name']}#${claim['claimId']}`) .setTitle('lbry://' + (claim.channel ? claim.channel + '/' : '') + claim['name']) .setURL(`http://open.lbry.io/${claim['name']}#${claim['claimId']}`) .setColor(1399626) .setFooter(`Block ${claim.height} • Claim ID ${claim.claimId} • Data from Chainquery`) .addField('Channel Name', claim['name']); discordPost(channelEmbed); } } // set the last sync time to the db. syncState.LastSyncTime = new Date() .toISOString() .slice(0, 19) .replace('T', ' '); await saveJSON(path.join(appRoot.path, 'syncState.json'), syncState); } function getJSON(path) { return new Promise((resolve, reject) => { jsonfile.readFile(path, function(err, jsoncontent) { if (err) { reject(err); } else { resolve(jsoncontent); } }); }); } function saveJSON(path, obj) { return new Promise((resolve, reject) => { jsonfile.writeFile(path, obj, function(err, jsoncontent) { if (err) { reject(err); } else { resolve(); } }); }); } function discordPost(embed) { channels.forEach(channel => { discordBot.channels .get(channel) .send('', embed) .catch(console.error); }); } function getClaimsSince(time) { return new Promise((resolve, reject) => { let query = `` + `SELECT ` + `c.name,` + `c.valid_at_height,` + `c.height,` + `p.name as channel,` + `c.publisher_id as channelId,` + `c.bid_state,` + `c.effective_amount,` + `c.claim_id as claimId,` + `c.value_as_json as value, ` + `c.transaction_hash_id, ` + // txhash and vout needed to leverage old format for comparison. `c.vout, ` + `o.value as outputValue ` + `FROM claim c ` + `LEFT JOIN claim p on p.claim_id = c.publisher_id ` + `LEFT JOIN output o on (o.transaction_hash=c.transaction_hash_id and o.vout=c.vout) ` + `WHERE c.created_at >='` + time + `'`; // Outputs full query to console for copy/paste into chainquery (debugging) // console.log(query); rp(`https://chainquery.lbry.io/api/sql?query=` + query) .then(function(htmlString) { resolve(htmlString); }) .catch(function(err) { console.log('error', '[Importer] Error getting updated claims. ' + err); reject(err); }); }); }