diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..a5b82de --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + "extends": "standard" +}; \ No newline at end of file diff --git a/.gitignore b/.gitignore index 69026f6..7e4b167 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,8 @@ logs *.log npm-debug.log* -config.json state.json +config.json # Runtime data pids diff --git a/README.md b/README.md index 57c6bc7..b3fede7 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,5 @@ -# Community Bot - Voting bot for communities on Steem! +# Actifit Bot -This is a voting bot for communities on the Steem platform. Members of the community can all chip in to power up the bot which will then go through the list of members and upvote the latest post by each. +This bot was built to perform voting on actifit posts, but also store and calculate actifit tokens, transactions and relevant posts into own Mongogb -## Installation -``` -$ npm install -``` - -## Configuration -First rename config-example.json to config.json: -``` -$ mv config-example.json config.json -``` - -Then set the following options in config.json: -``` -{ - "disabled_mode": false, - "testing": true, - "detailed_logging": false, - "account": "actifit", - "main_tag": "actifit", - "memo_key": "your_private_memo_key", - "posting_key": "your_private_posting_key", - "active_key": "your_private_active_key", - "auto_claim_rewards" : true, - "vote_weight": 100000, - "min_hours": 24, - "comment_location": "comment.md", - "comment": false, - "resteem": false, - "no_paid_bots": true, - "beneficiaries": ["actifit", "actifit.pay"], - "flag_signal_accounts": ["spaminator", "cheetah", "steemcleaners", "mack-bot"], - "report_emails": "urucrypto@gmail.com", - "smtp_usr": "SMTP USERNAME", - "smtp_from": "'From Name' ", - "smtp_key": "key" -} -``` -## Run -``` -$ npm run all -``` - -This will run the process in the foreground which is not recommended. We recommend using a tool such as [PM2](http://pm2.keymetrics.io/) to run the process in the background as well as providing many other great features. - -### To run each service apart - -``` -$ npm run api -$ npm run import -$ npm run curate -``` +The work in this bot has been implemented so far by @cryptouru, based on a fork of yabapmatt's community bot https://github.com/MattyIce/communitybot diff --git a/app.js b/app.js index 093455d..7f153d2 100644 --- a/app.js +++ b/app.js @@ -1,71 +1,5584 @@ -var express = require('express'); -var exphbs = require('express-handlebars'); -const MongoClient = require('mongodb').MongoClient; -var utils = require('./utils'); - - -var app = express(); - -app.engine('handlebars', exphbs({defaultLayout: 'main'})); -app.set('view engine', 'handlebars'); - -var config = utils.getConfig(); - -// Connection URL -const url = config.mongo_uri; - -var db; -var collection; -// Database Name -const db_name = config.db_name; -const collection_name = 'user_tokens'; - -// Use connect method to connect to the server -MongoClient.connect(url, function(err, client) { - if(!err) { - console.log("Connected successfully to server"); - - db = client.db(db_name); - - // Get the documents collection - collection = db.collection(collection_name); - } else { - utils.log(err, 'api'); - mail.sendPlainMail('Database Error', err, 'cryptouru@gmail.com') - .then(function(res, err) { - if (!err) { - console.log(res); - } else { - utils.log(err, 'api'); - } - }); - process.exit(); - } - -}); - -app.get('/', function (req, res) { - var data = {}; - data.posts = [ - { - url: 'dsadsa', - net_votes: 44, - vote_weight: "0.03" - }]; - data.total_votes = 323; - data.total_money = "$0.63"; - res.render('home', data); -}); - -app.get('/user/:user', async function (req, res) { - let user = await collection.findOne({_id: req.params.user}); - console.log(user); - res.send(user); -}); - -app.get('/transactions', async function (req, res) { - let transactions = await db.collection('token_transactions').find().sort({date: -1}).limit(250).toArray(); - res.send(transactions); -}); - -app.listen(process.env.PORT || 3000); \ No newline at end of file +var express = require('express'); +var exphbs = require('express-handlebars'); +const MongoClient = require('mongodb').MongoClient; +var utils = require('./utils'); +const moment = require('moment') +var crypto = require('crypto'); + +var appPort = process.env.PORT || 3120; + +var app = express(); + +app.engine('handlebars', exphbs({defaultLayout: 'main'})); +app.set('view engine', 'handlebars'); + +var config = utils.getConfig(); + +let ObjectId = require('mongodb').ObjectId; + +const request = require("request"); + +// Connection URL +let url = config.mongo_uri; +if (config.testing){ + url = config.mongo_local; +} + +var db; +var collection; +// Database Name +const db_name = config.db_name; +const collection_name = 'user_tokens'; + + +// Use connect method to connect to the server +MongoClient.connect(url, function(err, client) { + if(!err) { + console.log("Connected successfully to server"); + + db = client.db(db_name); + + //print version + /* var adminDb = db.admin(); + adminDb.serverStatus(function(err, info) { + if (err){ + console.log(err); + }else{ + console.log(info.version); + } + })*/ + + // Get the documents collection + collection = db.collection(collection_name); + + //clearCorruptData(); + + //disableUserLogin(); + + } else { + utils.log(err, 'api'); + } + +}); + +async function clearCorruptData(){ + let res = await db.collection('token_transactions').remove({exchange: 'HE'}); + console.log(res); + console.log('annnd done'); +} + +let schedule = require('node-schedule') + +const SSC = require('sscjs'); +const ssc = new SSC(config.steem_engine_rpc); + +const hsc = new SSC(config.hive_engine_rpc); + +let rule = new schedule.RecurrenceRule(); + +let usersAFITXBal = []; +let usersAFITXBalHE = []; +let fullSortedAFITXList = []; +//initial fetch +fetchAFITXBal(0); + +//fetch new AFITX user account balance every 5 mins +let scJob = schedule.scheduleJob('*/5 * * * *', async function(){ + //reset array + //usersAFITXBal = []; + fetchAFITXBal(0); + + //only run cleanup on secondary thread to avoid duplication of effort and collision + if (process.env.BOT_THREAD == 'SECOND_API'){ + disableUserLogin(); + } +}); + +//allows setting acceptable origins to be included across all function calls +app.use(function(req, res, next) { + var allowedOrigins = ['*', 'https://actifit.io', 'http://localhost:3000']; + var origin = req.headers.origin; + if(allowedOrigins.indexOf(origin) > -1){ + res.setHeader('Access-Control-Allow-Origin', origin); + } + res.setHeader('Access-Control-Allow-Headers', 'Origin, Content-Type, x-acti-token'); + return next(); +}); + +app.get('/', function (req, res) { + var data = {}; + data.posts = [ + { + url: 'dsadsa', + net_votes: 44, + vote_weight: "0.03" + }]; + data.total_votes = 323; + data.total_money = "$0.63"; + // res.render('home', data); + res.send('Hello there!'); +}); + + + +//schedule restart intervals due to memory drain down +function restartApiNode() { + request.post( + { + url: 'https://api.heroku.com/apps/' + config.heroku_app_id + '/dynos/' + config.heroku_app_dyno + '/actions/stop', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/vnd.heroku+json; version=3', + 'Authorization': 'Bearer ' + config.heroku_app_token + } + }, + function(error, response, body) { + console.log(response); + console.log(body); + } + ); +} + +if (process.env.BOT_THREAD == 'MAIN'){ + let j = schedule.scheduleJob({hour: 0, minute: 20}, function(){ + restartApiNode(); + }); + let k = schedule.scheduleJob({hour: 6, minute: 20}, function(){ + restartApiNode(); + }); + let l = schedule.scheduleJob({hour: 12, minute: 20}, function(){ + restartApiNode(); + }); + let m = schedule.scheduleJob({hour: 18, minute: 20}, function(){ + restartApiNode(); + }); +} + +//initial load +let account = null; +let accountRefresh = false; +let accountQueries = 0; +loadAccountData(); + +async function disableUserLogin(){ + console.log('check outdated logins'); + let db_col = db.collection('user_login_token'); + let db_hist_col = db.collection('user_login_history'); + let dateTarget = new Date(); + //allow logins to remain valid for 12 hours + dateTarget.setHours(dateTarget.getHours()-12); + console.log(dateTarget); + //find existing login entry in DB to move to history and then remove .. only if user wants to get logged out + let items_to_move = await db_col.find({lastlogin: {$lt: dateTarget }, keeploggedin: {$ne: true}}).toArray(); + if (Array.isArray(items_to_move) && items_to_move.length > 0){ + console.log('moving and deleting '+ items_to_move.length + ' old logins'); + //cleanup data to prevent keeping keys + items_to_move.forEach(function(ent){ delete ent.ppkey; delete ent.token }); + + await db_hist_col.insert(items_to_move); + let result = await db_col.remove({lastlogin: {$lt: dateTarget }}); + }else{ + console.log('noting to clean'); + } + + //console.log(result); +} + +let exchangeAfitPrice = {}; +let priorExchangeAfitHivePrice = {}; + +loadExchAfitPrice(); + +//reload every 5 mins +setInterval(loadExchAfitPrice, 5*60000); + +async function loadExchAfitPrice(){ + try{ + console.log('loading AFIT exchange prices'); + let afitSEPrice = await ssc.find('market', 'metrics', {symbol : 'AFIT' }, 1000, 0, '', false); + + let afitHEPrice = await hsc.find('market', 'metrics', {symbol : 'AFIT' }, 1000, 0, '', false); + + //grab STEEM price + let steemPriceQuery = await fetch('https://api.coingecko.com/api/v3/simple/price?ids=steem&vs_currencies=usd'); + let steemPrice = await steemPriceQuery.json(); + console.log('steemPrice'); + console.log(steemPrice); + + //grab HIVE price + let hivePriceQuery = await fetch('https://api.coingecko.com/api/v3/simple/price?ids=hive&vs_currencies=usd'); + let hivePrice = await hivePriceQuery.json(); + console.log('hivePrice'); + console.log(hivePrice); + //json.hive.usd + + //set prior hive value for reference and reuse if needed + if (exchangeAfitPrice.afitHiveLastPrice){ + priorExchangeAfitHivePrice = exchangeAfitPrice.afitHiveLastPrice; + }else{ + exchangeAfitPrice.afitHiveLastPrice = parseFloat(afitHEPrice[0].lastPrice); + } + + + + exchangeAfitPrice.afitSEPrice = afitSEPrice; + exchangeAfitPrice.afitHEPrice = afitHEPrice; + + exchangeAfitPrice.afitSteemLastUsdPrice = parseFloat(afitSEPrice[0].lastPrice) * steemPrice.steem.usd; + exchangeAfitPrice.afitHiveLastUsdPrice = parseFloat(afitHEPrice[0].lastPrice) * hivePrice.hive.usd; + + exchangeAfitPrice.afitSteemLastPrice = parseFloat(afitSEPrice[0].lastPrice); + exchangeAfitPrice.afitHiveLastPrice = parseFloat(afitHEPrice[0].lastPrice); + + exchangeAfitPrice.lastMedianPrice = (exchangeAfitPrice.afitSteemLastUsdPrice + exchangeAfitPrice.afitHiveLastUsdPrice)/2; + exchangeAfitPrice.lastUpdated = new Date(); + + console.log(exchangeAfitPrice); + }catch(exc){ + console.log('problem fetching AFIT price'); + console.log(exc); + } +} + + +async function loadAccountData(bchain){ + //load main account data + + account = await utils.getAccountData(config.account, bchain); +} + +async function fetchAFITXBal(offset){ + try{ + console.log('--- Fetch new AFITX token balance ---'); + console.log(offset); + let tempArr = await ssc.find('tokens', 'balances', { symbol : 'AFITX' }, 1000, offset, '', false) //max amount, offset, + if (offset == 0 && tempArr.length > 0){ + console.log('>>Found new results, reset older ones'); + //reset existing data if we have fresh new data + usersAFITXBal = []; + } + usersAFITXBal = usersAFITXBal.concat(tempArr); + + if (tempArr.length > 999){ + //we possibly have more entries, let's call again + setTimeout(function(){ + fetchAFITXBal(usersAFITXBal.length); + }, 1000); + }else{ + //if we were not able to fetch entries, we need to try API again + if (offset == 0 && tempArr.length < 1){ + console.log('no AFITX data, fetch again in 30 secs'); + setTimeout(function(){ + fetchAFITXBal(0); + }, 30000); + }else{ + //done with AFITX SE, proceed with AFITX HE + fetchAFITXBalHE(0); + } + } + }catch(err){ + console.log(err); + if (offset == 0){ + console.log('no AFITX data, fetch again in 30 secs'); + setTimeout(function(){ + fetchAFITXBal(0); + }, 30000); + } + } + //console.log(usersAFITXBal); +} + +async function fetchAFITXBalHE(offset){ + try{ + console.log('--- Fetch new AFITX token balance ---'); + console.log(offset); + let tempArr = await hsc.find('tokens', 'balances', { symbol : 'AFITX' }, 1000, offset, '', false) //max amount, offset, + if (offset == 0 && tempArr.length > 0){ + console.log('>>Found new results, reset older ones'); + //reset existing data if we have fresh new data + usersAFITXBalHE = []; + } + usersAFITXBalHE = usersAFITXBalHE.concat(tempArr); + + if (tempArr.length > 999){ + //we possibly have more entries, let's call again + setTimeout(function(){ + fetchAFITXBalHE(usersAFITXBalHE.length); + }, 1000); + }else{ + //if we were not able to fetch entries, we need to try API again + if (offset == 0 && tempArr.length < 1){ + console.log('no AFITX data HE, fetch again in 30 secs'); + setTimeout(function(){ + fetchAFITXBalHE(0); + }, 30000); + }else{ + //done, let's merge both SE & HE lists + for (let i=0;i entry.account === usersAFITXBal[i].account); + if (match){ + usersAFITXBal[i].sebalance = usersAFITXBal[i].balance; + usersAFITXBal[i].hebalance = match.balance; + usersAFITXBal[i].balance = parseFloat(usersAFITXBal[i].balance) + parseFloat(match.balance); + usersAFITXBal[i].heholder = true; + } + } + //append HE holdings + for (let i=0;i entry.account === usersAFITXBalHE[i].account); + if (!match){ + usersAFITXBal.push(usersAFITXBalHE[i]); + //usersAFITXBal[i].hebalance = match.balance; + //usersAFITXBal[i].balance = parseFloat(usersAFITXBal[i].balance) + parseFloat(match.balance); + } + } + + /* + + let req = new Object(); + req.query = new Object(); + req.query.new_account= 'jumbo'; + req.query.usd_invest= '1'; + req.query.steem_invest= '1'; + req.query.afit_reward= '1'; + req.query.memo= 'jumdfsfddbo'; + req.query.referrer= 'mcfarhat'; + storeSignupTransaction(req); + */ + } + } + }catch(err){ + console.log(err); + if (offset == 0){ + console.log('no AFITX data HE, fetch again in 30 secs'); + setTimeout(function(){ + fetchAFITXBalHE(0); + }, 30000); + } + } + //console.log(usersAFITXBal); +} + +async function getAFITXUserData(user){ + let ind = fullSortedAFITXList.findIndex(v => v.account == user) + let entry = fullSortedAFITXList.find(v => v.account == user) + return {ind: ind, entry: entry} +} + +/* function handles calculating and returning user token count */ +grabUserTokensFunc = async function (username){ + let user = await db.collection('user_tokens').findOne({_id: username}); + console.log(user); + //fixing token amount display for 3 digits + if (typeof user!= "undefined" && user!=null){ + if (typeof user.tokens!= "undefined"){ + user.tokens = user.tokens.toFixed(3) + } + }else{ + user = new Object(); + user._id=username; + user.name=username; + user.tokens=0; + } + return user; +} + +/* function handles returning product specific data */ +grabProductInfo = async function(product_id){ + let o_id = new ObjectId(product_id); + let product = await db.collection('products').findOne({_id: o_id}); + return product; +} + +/* function handles generating a random password/access_token */ +generatePassword = function (multip) { + //generate random 11 characters password + let passString = ''; + for (let i=0;i 10){ + accountQueries = 0; + accountRefresh = true; + } + let bchain = (req.query&&req.query.bchain?req.query.bchain:''); + //fetch anew account data if account is empty or we need to refresh account data + if (!account || accountRefresh){ + console.log('refreshing account data'); + account = await utils.getAccountData(config.account, bchain); + accountRefresh = false; + } + let vp_res = await utils.getVotingPower(account); + + let reward_start = utils.toHrMn(utils.timeTilKickOffVoting(vp_res * 100)); + + res.send({'status': votingStatus, 'vp': vp_res, 'reward_start': reward_start}); +}); + + +let jwt = require('jsonwebtoken'); + +//function ensures user is properly logged in +let checkHdrs = (req, res, next) => { + let token = req.headers['x-acti-token'] || req.headers['authorization']; // Express headers are auto converted to lowercase + + if (token) { + if (token.startsWith('Bearer ')) { + // Remove Bearer from string + token = token.slice(7, token.length); + } + req.query.token = token; + jwt.verify(token, config.secret, async (err, decoded) => { + if (err) { + return res.json({ + success: false, + message: 'Token is not valid' + }); + } else { + let user; + + if (req.query && req.query.user){ + user = req.query.user; + }else{ + res.send({error: 'user not supplied'}); + } + + //console.log(operation[1].required_posting_auths); + //console.log(req.query.token); + + //check if user is validated with stored encrypted posting key + let db_col = db.collection('user_login_token'); + //find existing login entry in DB + let user_tkn = await db_col.findOne({user: user, token: req.query.token}); + //console.log(user_tkn); + if (!user_tkn || !user_tkn.ppkey){ + console.error('Authentication failed. Key not found'); + res.send({error: 'Authentication failed. Key not found'}); + return; + } + req.ppkey = user_tkn.ppkey; + req.decoded = decoded; + next(); + } + }); + } else { + return res.json({ + success: false, + message: 'Auth token is not provided' + }); + } +}; + + +app.post('/performTrxPost', checkHdrs, async function (req, res) { + console.log('>>performTrx'); + + + const receivedPlaintext = decrypt(req.ppkey); + + //set HIVE as default + let bchain = 'HIVE'; + + let userKey = receivedPlaintext; + + let operation; + console.log(req.body); + console.log(req.body.operation); + if (req.body && req.body.operation){ + operation = JSON.parse(req.body.operation); + //operation = req.query.operation; + }else{ + res.send({error: 'operation not supplied'}); + } + + if (req.query.bchain){ + bchain = req.query.bchain; + } + + let match_arr = Object.entries(operation); + /*console.log(user); + console.log(operation); + console.log((typeof operation)); + console.log(match_arr); + console.log(match_arr[0][1]);*/ + + //perform transaction + let performTrx = await utils.processSteemTrx(match_arr[0][1], userKey, bchain); + console.log(performTrx); + if (!performTrx.tx.block_num){ + res.send({error: true, trx: performTrx}); + }else{ + res.send({success: true, trx: performTrx}); + } +}); + +app.get('/performTrx', checkHdrs, async function (req, res) { + console.log('>>performTrx'); + + + const receivedPlaintext = decrypt(req.ppkey); + + //set HIVE as default + let bchain = 'HIVE'; + + let userKey = receivedPlaintext; + + let operation; + console.log(req.query.operation); + if (req.query && req.query.operation){ + operation = JSON.parse(req.query.operation); + //operation = req.query.operation; + }else{ + res.send({error: 'operation not supplied'}); + } + + if (req.query.bchain){ + bchain = req.query.bchain; + } + + let match_arr = Object.entries(operation); + /*console.log(user); + console.log(operation); + console.log((typeof operation)); + console.log(match_arr); + console.log(match_arr[0][1]);*/ + + //perform transaction + let performTrx = await utils.processSteemTrx(match_arr[0][1], userKey, bchain); + console.log(performTrx); + if (!performTrx.tx.block_num){ + res.send({error: true, trx: performTrx}); + }else{ + res.send({success: true, trx: performTrx}); + } +}); + +app.get('/fetchUserData', checkHdrs, async function (req, res) { + //validate proper data used + let username; + if (req.query && req.query.user){ + username = req.query.user; + } + let bchain = 'HIVE'; + if (req.query && req.query.bchain){ + bchain = req.query.bchain; + } + console.log('>>>>fetchuserdata'); + console.log(bchain); + //console.log(username); + //console.log(req.ppkey); + const receivedPlaintext = decrypt(req.ppkey); + let isValidUser = await utils.validateAccountLogin(username, receivedPlaintext, bchain); + + console.log('isValidUser'); + console.log(isValidUser); + //if (username === mockedUsername && ppkey === mockedPpkey) { + if (isValidUser.result){ + res.json({ + success: true, + userdata: isValidUser.account + }); + } else { + res.status(403).send({ + success: false, + message: 'Invalid user' + }); + } +}); + + +app.get('/resetLogin', checkHdrs, async function (req, res) { + let db_col = db.collection('user_login_token'); + let result = await db_col.remove({user: req.query.user, token: req.query.token}); + res.send({success: true}); +}); + +app.get('/updateSettings/', checkHdrs, async function (req, res) { + let newSettings; + if (req.query && req.query.user && req.query.settings){ + newSettings = JSON.parse(req.query.settings); + }else{ + res.send({error:'invalid request'}) + return; + } + console.log(newSettings); + try{ + let setgs = await db.collection('user_settings').replaceOne({user: req.query.user}, {user: req.query.user, settings: newSettings}, {upsert : true }); + //console.log(setgs); + res.send({success: true}); + }catch(err){ + res.send({error: true}); + } +}); + + +app.get('/userSettings/:user', async function (req, res) { + let setgs = await db.collection('user_settings').findOne({user: req.params.user}, {fields : { _id:0} }); + console.log(setgs); + if (!setgs){ + res.send({}); + }else{ + res.send(setgs); + } +}); + + +app.post('/loginAuth', async function (req, res) { + console.log('login'); + let username = null; + if (req.body && req.body.username){ + username = req.body.username; + } + let ppkey = null; + if (req.body && req.body.ppkey){ + ppkey = req.body.ppkey; + } + + + if (username && ppkey) { + let db_col = db.collection('user_login_token'); + //find existing login entry in DB to override + let user_tkn = await db_col.findOne({user: username}); + //console.log(user_tkn); + + //encode ppkey + const ciphertext = encrypt(ppkey); + + let bchain = 'HIVE'; + if (req.body && req.body.bchain){ + bchain = req.body.bchain; + } + + //validate proper data used + let isValidUser = await utils.validateAccountLogin(username, ppkey, bchain); + console.log('isValidUser'); + //console.log(isValidUser); + //if (username === mockedUsername && ppkey === mockedPpkey) { + if (isValidUser.result){ + let token = jwt.sign({username: username}, + config.secret, + { expiresIn: '24h' // expires in 24 hours + } + ); + //save to DB + //save encrypted version + token + if (!user_tkn){ + user_tkn = new Object(); + } + user_tkn.user = username; + user_tkn.token = token; + user_tkn.ppkey = ciphertext; + user_tkn.lastlogin = new Date(); + //keep record free from deletion on cleanup + if (req.body && req.body.keeploggedin){ + user_tkn.keeploggedin = req.body.keeploggedin; + } + let db_save = await db_col.save(user_tkn); + + // return the JWT token for the future API calls + res.json({ + success: true, + message: 'Authentication successful!', + token: token, + userdata: isValidUser.account + }); + } else { + res.status(403).send({ + success: false, + message: 'Incorrect username or ppkey' + }); + } + } else { + res.status(400).send({ + success: false, + message: 'Authentication failed! Please check the request' + }); + } +}); + +app.get('/getDailyDelegationPool/', async function(req, res){ + res.send({'hive_pool': config.weekly_rewards_limit, 'steem_pool': config.weekly_rewards_limit}); +}); + +/* end point for user total token count display */ +app.get('/user/:user', async function (req, res) { + let user = await grabUserTokensFunc(req.params.user); + res.send(user); +}); + +/* end point for user transactions display (per user or general actifit token transactions, limited by 1000) */ +app.get('/transactions/:user?', async function (req, res) { + let query = {}; + var transactions; + if(req.params.user){ + query = {user: req.params.user} + transactions = await db.collection('token_transactions').find(query, {fields : { _id:0} }).sort({date: -1}).limit(1000).toArray(); + }else{ + //only limit returned transactions in case this is a general query + transactions = await db.collection('token_transactions').find(query, {fields : { _id:0} }).sort({date: -1}).limit(1000).toArray(); + } + res.send(transactions); +}); + +app.get('/postsbytag/:tag', async function (req, res) { + let posts = {}; + if(req.params.tag){ + let query = {"json_metadata.tags": {$all: [req.params.tag]}}; + posts = await db.collection('verified_posts').find(query, {fields : { _id:0} }).sort({date: -1}).toArray(); + } + res.send(posts); +}); + +/* end point for transactions display by type (limited by 1000) */ +app.get('/transactionsByType/', async function (req, res) { + let query = {}; + let transactions = {}; + let proceed = false; + let dateSort = 1; + let sortQuery = {}; + if (req.query.type){ + proceed = true; + query = {reward_activity: req.query.type} + + } + if (req.query.chain){ + query['chain'] = req.query.chain; + } + if (req.query.datesort){ + dateSort = parseInt(req.query.datesort) + sortQuery = {date: dateSort}; + }else if (req.query.sortByToken && !isNaN(req.query.sortByToken)){ + sortQuery = {token_count: parseInt(req.query.sortByToken)}; + } + console.log(sortQuery); + //default end date as yesterday + let endDate = moment(moment().utc().subtract(1, 'days').toDate()).format('YYYY-MM-DD'); + //default start date as day before + let startDate = moment(moment(endDate).utc().subtract(1, 'days').toDate()).format('YYYY-MM-DD');; + console.log('startDate:'+startDate); + console.log('endDate:'+endDate); + if (req.query.startDate){ + startDate = moment(moment(req.query.startDate).utc().startOf('date').add(1, 'days').toDate()).format('YYYY-MM-DD'); + } + if (req.query.endDate){ + //let endDate = moment(moment(startDate).utc().add(1, 'days').toDate()).format('YYYY-MM-DD'); + endDate = moment(moment(req.query.endDate).utc().endOf('date').add(1, 'days').toDate()).format('YYYY-MM-DD'); + } + if (startDate && endDate){ + query['date'] = { + "$lt": new Date(endDate), + "$gte": new Date(startDate) + }; + }else if (startDate){ + query['date'] = { + "$gte": new Date(startDate) + }; + } + console.log(query); + if (proceed){ + transactions = await db.collection('token_transactions').find(query).sort(sortQuery).limit(1000).toArray(); + } + res.send(transactions); +}); + + +/* end point for user signup display (per user or general signups */ +app.get('/signups/:user?', async function (req, res) { + let query = {account_created: true}; + var referrals; + if(req.params.user){ + query['referrer'] = req.params.user; + referrals = await db.collection('signup_transactions').find(query, {fields : { _id:0} }).sort({date: -1}).toArray(); + }else{ + //only limit returned referrals in case this is a general query + referrals = await db.collection('signup_transactions').find(query, {fields : { _id:0} }).sort({date: -1}).limit(1000).toArray(); + } + res.send(referrals); +}); + +app.get('/referrals/:user?', async function (req, res) { + let query = {account_created: true, referrer:{$ne:null}}; + let referrals; + if(req.params.user){ + query['referrer'] = req.params.user; + referrals = await db.collection('signup_transactions').find(query, {fields : { _id:0} }).sort({date: -1}).toArray(); + }else{ + //only limit returned referrals in case this is a general query + referrals = await db.collection('signup_transactions').find(query, {fields : { _id:0} }).sort({date: -1}).limit(1000).toArray(); + } + res.send(referrals); +}); + +app.get('/signupInfo/:user', async function (req, res) { + let query = {account_name: req.params.user, account_created: true}; + let referrals = await db.collection('signup_transactions').findOne(query, {fields : { _id:0} }); + if (!referrals){ + referrals = {}; + } + res.send(referrals); +}); + + +/* end point for fetching all verified newbie accounts */ +app.get('/activeVerifiedNewbies/', async function (req, res) { + let maxRewardDate = moment(moment().utc().subtract(config.newbie_rewards_days, 'days').toDate()).toDate(); + console.log(maxRewardDate); + let query = { + verify_date: { + $gte: new Date(maxRewardDate), + } + }; + + console.log(query); + let data = await db.collection('verified_newbie').find(query).sort({date: -1}).toArray(); + if (!data){ + data = {}; + } + res.send(data); +}); + + +/* end point for fetching all verified newbie accounts */ +app.get('/verifiedNewbies/', async function (req, res) { + let data = await db.collection('verified_newbie').find().sort({date: -1}).toArray(); + if (!data){ + data = {}; + } + res.send(data); +}); + +app.get('/activeRefReward/:referred', async function (req, res) { + //referral rewards are active for up to 30 days + let maxSignupDate = moment(moment().utc().subtract(config.ref_rew_act_days, 'days').toDate()).toDate(); + console.log(maxSignupDate); + let query = {account_name: req.params.referred, account_created: true, date: {$gte: maxSignupDate}}; + let refReward = await db.collection('signup_transactions').findOne(query, {fields : { _id:0} }); + console.log(refReward); + if (refReward){ + refReward.ref_rew_act_days = config.ref_rew_act_days; + //calculate user reward percentage + refReward.ref_rew_pct = config.ref_rew_def_pct + (parseFloat(refReward.referrer_cur_rank)>=config.userRankMin?5:0) + (parseFloat(refReward.referrer_cur_afit)>=config.userTokensMin?5:0) + (parseFloat(refReward.referrer_cur_afitx)>=config.afitxMin?5:0); + }else{ + refReward = {}; + } + res.send(refReward); +}); + +/* end point for returning number of awarded users and tokens distributed */ +app.get('/user-tokens-info', async function(req, res) { + + await db.collection(collection_name).aggregate([ + { + $match: {} + }, + { + $group: + { + _id: null, + tokens_distributed: { $sum: "$tokens" }, + user_count: { $sum: 1 } + } + } + ]).toArray(function(err, results) { + if (results.length>0){ + try{ + var output = 'rewarded users:'+results[0].user_count+','; + output += 'tokens distributed:'+results[0].tokens_distributed; + res.send(results); + console.log(results); + }catch(err){ + console.log(err); + res.send(''); + } + }else{ + res.send(''); + } + }); + +}); + +app.get('/tokensBurnt', async function (req, res) { + let agg = await db.collection('token_transactions').aggregate([ + { + $match: { + reward_activity: 'Buy Product', + seller: 'actifit' + } + }, + { + $group: + { + _id: null, + tokens_burnt: { $sum: "$token_count" }, + burn_trx_count: { $sum: 1 } + + } + } + ]).toArray(function(err, results) { + if (results.length>0){ + results[0].tokens_burnt = Math.abs(results[0].tokens_burnt); + res.send(results); + console.log(results); + } + }); +}); + +/* end point for user total token count display */ +app.get('/modAction', async function (req, res) { + if (!req.query.moderator || !req.query.fundsPass || !req.query.targetAction){ + res.send({'error':'Missing Data'}); + }else{ + let moderator = req.query.moderator; + let fundsPass = req.query.fundsPass; + + //confirm matching funds password + let query = {user: moderator}; + + if (!isModerator(moderator)){ + res.send({'error': 'Account does not have proper privileges'}); + return; + } + + let entryFound = await db.collection('account_funds_pass').findOne(query, {fields : { _id:0} }); + + if (entryFound == null){ + res.send({'error': 'Account does not have a recorded funds password'}); + return; + }else if (!entryFound.passVerified){ + res.send({'error': 'Account\'s funds password not verified'}); + return; + }else{ + //create encrypted version of sent password + var cipher = crypto.createCipher(config.funds_encr_mode, config.funds_encr_key); + let encr_pass = cipher.update(fundsPass, 'utf8', 'hex'); + encr_pass += cipher.final('hex'); + if (entryFound.pass !== encr_pass){ + res.send({'error': 'Incorrect username and/or funds password'}); + return; + } + } + + //reached here, we're fine + console.log('reached here, we\'re fine'); + + let result = ''; + + //store every moderator transaction as log + let modTrans = { + "moderator": req.query.moderator, + "action": req.query.targetAction, + "date": new Date(), + }; + + console.log(req.query.targetAction); + + switch(req.query.targetAction){ + + case 'ban': + modTrans.user = req.query.banuser.trim().toLowerCase(); + collection = db.collection('banned_accounts') + //var dt = new Date().toJSON() + //dt.substring(0,dt.indexOf(".")); + + if (modTrans.user == ''){ + res.send({'error': 'Cannot ban empty user'}); + return; + } + result = await collection.insert({ + "user": modTrans.user, + "ban_date": new Date(), + "ban_length": req.query.ban_length, + "ban_status": 'active', + "ban_reason": req.query.ban_reason + }); + console.log(req.query.banuser+" banned "); + result.status='success'; + break; + case 'unban': + + modTrans.user = req.query.unbanuser.trim().toLowerCase(); + collection = db.collection('banned_accounts') + + if (modTrans.user == ''){ + res.send({'error': 'Cannot unban empty user'}); + return; + } + + result = await collection.update( + { "user": req.query.unbanuser.trim().toLowerCase() }, + { + $set: { + "ban_status": "inactive", + } + }, + { + multi: true + } + ); + console.log(req.query.unbanuser+" ban removed! "); + result.status='success'; + break; + + case 'resetpass': + modTrans.user = req.query.resetuser.trim().toLowerCase(); + + collection = db.collection('account_funds_pass') + + if (modTrans.user == ''){ + res.send({'error': 'Cannot resetpass empty user'}); + return; + } + + let user = req.query.resetuser.trim().toLowerCase(); + result = 'no change'; + if (user!=''){ + result = await collection.remove({ + "user": user, + }); + console.log(user+" password reset "); + } + console.log(user+" pass reset! "); + result.status='success'; + break; + + case 'reward': + modTrans.fullurl = req.query.fullurl.trim().toLowerCase(); + modTrans.vp = req.query.power; + if (modTrans.fullurl == ''){ + res.send({'error': 'Need to send URL to vote'}); + return; + } + + if (isNaN(modTrans.vp)){ + res.send({'error': 'VP needs to be numeric'}); + return; + } + let bchain = (req.query&&req.query.bchain?req.query.bchain:''); + result = await utils.rewardPost(modTrans.fullurl, modTrans.vp, bchain) + console.log(result); + result.status='success'; + break; + + case 'verifynewbie': + modTrans.user = req.query.account.trim().toLowerCase(); + collection = db.collection('verified_newbie') + //var dt = new Date().toJSON() + //dt.substring(0,dt.indexOf(".")); + + if (modTrans.user == ''){ + res.send({'error': 'Cannot verify empty user'}); + return; + } + result = await collection.insert({ + "user": modTrans.user, + "verify_date": new Date(), + "sm_verif_lnk": req.query.verif_link, + "verif_mod": moderator + }); + console.log(modTrans.user+" verified "); + result.status='success'; + break; + } + + collection = db.collection('team_transactions'); + let modTransRes = await collection.insert(modTrans); + console.log(modTransRes) + + res.send(result); + } +}); + + + + +/* end point for user total token count display */ +app.get('/topAFITHolders', async function (req, res) { + let tokenHolders = []; + if (isNaN(req.query.count)){ + tokenHolders = await db.collection('user_tokens').find().sort({tokens: -1}).toArray(); + }else{ + tokenHolders = await db.collection('user_tokens').find().sort({tokens: -1}).limit(parseInt(req.query.count)).toArray(); + } + res.send(tokenHolders); +}); + +/* end point for user total token count display */ +app.get('/topAFITXHolders', async function (req, res) { + let afitxSorted = utils.sortArrLodash(usersAFITXBal); + fullSortedAFITXList = afitxSorted; + let maxAmount = parseInt(req.query.count); + if (isNaN(maxAmount)){ + //set max as 100 + maxAmount = 100; + } + + //fetch banned accounts + let banned_users = await db.collection('banned_accounts').find({ban_status:"active"}, {fields : { user: 1, _id: 0 } }).toArray(); + //console.log(banned_users); + let banned_arr = banned_users.map(entr => entr.user); + banned_arr.push('afitx.s-e'); + banned_arr.push('afitx.h-e'); + banned_arr.push(''); + + afitxSorted = utils.removeArrMatchLodash(afitxSorted, banned_arr, 'account'); + + //always skip top holder as that would be actifit + afitxSorted = afitxSorted.slice(1, maxAmount + 1); + + let output = afitxSorted; + + if (req.query.pretty){ + output = '#|Token Holder | AFITX Tokens Held |
'; + output += '|---|---|---|
'; + for(var i = 0; i < afitxSorted.length; i++) { + let tokenHolder = afitxSorted[i]; + output += (i+1) + '|'; + output += '@'+tokenHolder.account + '|'; + output += gk_add_commas(parseFloat(tokenHolder.balance).toFixed(3)) + '|'; + output += '
'; + } + } + + res.send(output); +}); + +/* end point for fetching user AFITX data */ +app.get('/afitxData/:user', async function (req, res) { + let val = await getAFITXUserData(req.params.user); + res.send(val); +}); + + + +/* end point for returning total delegation payments (number of delegators and amount paid) on a specific date */ +app.get('/delegationPayments', async function(req, res) { + var todayDate = moment(moment().utc().startOf('date').toDate()).format('YYYY-MM-DD'); + var dateRegex = todayDate; // /^2018-08-05/ + if (req.query.targetDate){ + dateRegex = req.query.targetDate; + } + + await db.collection('token_transactions').aggregate([ + { + $match: + { + "reward_activity": "Delegation", + "date": { + '$eq' : new Date(dateRegex) + } + } + }, + { + $group: + { + _id: null, + tokens_distributed: { $sum: "$token_count" }, + user_count: { $sum: 1 } + } + } + ]).toArray(function(err, results) { + res.send(results); + console.log(results); + }); + +}); + + +/* end point for returning total payments (categorized by reward type as well as a full total) on a specific date */ +app.get('/totalTokensDistributed', async function(req, res) { + + var startDate = moment(moment().utc().startOf('date').toDate()).format('YYYY-MM-DD'); + if (req.query.targetDate){ + startDate = moment(moment(req.query.targetDate).utc().startOf('date').toDate()).format('YYYY-MM-DD'); + } + var endDate = moment(moment(startDate).utc().add(1, 'days').toDate()).format('YYYY-MM-DD'); + console.log("startDate:"+startDate+" endDate:"+endDate); + + await db.collection('token_transactions').aggregate([ + { + $match: + { + "date": { + "$lte": new Date(endDate), + "$gt": new Date(startDate) + } + } + }, + { + $group: + { + _id: {reward_activity:"$reward_activity"}, + tokens_distributed: { $sum: "$token_count" }, + } + } + ]).toArray(function(err, results) { + //also append total token count to the grouped display + let tot_tokens = 0; + for (let entry of results) { + tot_tokens += entry.tokens_distributed; + } + console.log(tot_tokens); + results.push([{"_id":null,"tokens_distributed":tot_tokens}]); + + res.send(results); + console.log(results); + }); + +}); + +/* end point for returning count of posts/activities rewarded */ +app.get('/rewarded-activity-count', async function(req, res) { + + await db.collection("posts").aggregate( [ + { $count: "reward_count" } + ]).toArray(function(err, results) { + console.log(results); + utils.log(results, 'rewarded-activity-count'); + res.send(results); + }); +}); + +/* end point for returning charity data supported by actifit */ +app.get('/charities', async function (req, res) { + var charities = await db.collection('available_charities').find({status:"enabled"}, {charity_name: 1}).sort({charity_name: 1}).toArray(); + res.send(charities); +}); + +/* end point for fetching user's current badges */ +app.get('/userBadges/:user', async function (req, res) { + let user = await db.collection('user_badges').find({user: req.params.user}).toArray(); + res.send(user); +}); + +async function getUserFriends(user){ + let friendsA = await db.collection('friends').find({userA: user}, {fields : {userB:1, _id:0}}).toArray(); + let friendsB = await db.collection('friends').find({userB: user}, {fields : {userA:1, _id:0}}).toArray(); + console.log(friendsA); + console.log(friendsB); + friendsA = JSON.parse(JSON.stringify(friendsA).replace(/userB/g,'friend')); + friendsB = JSON.parse(JSON.stringify(friendsB).replace(/userA/g,'friend')); + return friendsA.concat(friendsB); +} + +/* end point for fetching user's friends */ +app.get('/userFriends/:user', async function (req, res) { + let friendList = await getUserFriends(req.params.user); + res.send(friendList); +}); + +/* end point for marking a notification as read */ +app.get('/markRead/:notif_id', checkHdrs, async function (req, res) { + let notif_to_update = { + _id: new ObjectId(req.params.notif_id), + }; + try{ + let transaction = await db.collection('notifications').update(notif_to_update, { $set: {status: 'read'} } ); + console.log('success updating notification status'); + res.send({status: 'success'}); + }catch(err){ + console.log('error'); + res.send({status: 'error'}); + } +}); + +/* end point for marking a notification as Unread */ +app.get('/markUnread/:notif_id', checkHdrs, async function (req, res) { + let notif_to_update = { + _id: new ObjectId(req.params.notif_id), + }; + try{ + let transaction = await db.collection('notifications').update(notif_to_update, { $set: {status: 'unread'} } ); + console.log('success updating notification status'); + res.send({status: 'success'}); + }catch(err){ + console.log('error'); + res.send({status: 'error'}); + } +}); + +/* end point for marking all user's notifications as read */ +app.get('/markAllRead/', checkHdrs, async function (req, res) { + let notif_to_update = { + user: req.query.user, + }; + console.log('markAllRead'); + console.log(notif_to_update); + try{ + let transaction = await db.collection('notifications').update(notif_to_update, { $set: {status: 'read'} }, {multi: true} ); + console.log('success updating notification status'); + console.log(transaction); + res.send({status: 'success'}); + }catch(err){ + console.log('error'); + res.send({status: 'error'}); + } +}); + +/* end point for tracking gadget buy orders */ +app.get('/buyGadgetHive/:user/:gadget/:blockNo/:trxID/:bchain', async function (req, res) { + + let user = req.params.user; + let product_id = req.params.gadget; + + //fetch product info + let product = await grabProductInfo (product_id); + if (!product){ + res.send({'error': 'Product not found'}); + return; + } + + //check if query has already been verified + let matchingEntries = await db.collection('gadget_transactions_hive').find( + { + blockNo: req.params.blockNo, + trxID: req.params.trxID, + bchain: req.params.bchain + }).toArray(); + + if (Array.isArray(matchingEntries) && matchingEntries.length > 0){ + res.send({'error': 'Transaction already verified'}); + return; + } + + let price_options = product.price; + let price_options_count = price_options.length; + let item_price = 0; + let item_price_afit = 0; + let item_currency = req.params.bchain; + let actifit_percent_cut = 10; + let item_price_alt = 0; + for (let i=0; i < price_options_count; i++){ + let entry = price_options[i]; + //calculate HIVE price + item_price_afit = entry.price; + item_price = entry.price * exchangeAfitPrice.afitHiveLastPrice; + //alternate price to match if at a time where AFIT price changes + item_price_alt = entry.price * priorExchangeAfitHivePrice; + item_currency = entry.currency; + actifit_percent_cut = entry.actifit_percent_cut; + } + + //round down number + console.log('Before rounding'); + console.log(item_price); + item_price = (Math.floor(item_price * 1000) - 1) / 1000; + console.log('After rounding'); + console.log(item_price); + + //ensure proper transaction + let ver_trx = await utils.verifyGadgetPayTransaction(req.params.user, req.params.gadget, item_price, item_price_alt, 'buy-gadget', req.params.blockNo, req.params.trxID, req.params.bchain); + if (!ver_trx || !ver_trx.success){ + res.send({status: 'error'}); + return; + } + + + product.provider = 'actifit'; + + //perform transaction + let productBuyTrans = { + user: user, + reward_activity: 'Buy Product', + buyer: user, + seller: product.provider, + product_id: product_id, + product_type: product.type, + product_name: product.name, + product_level: product.level, + product_price_afit: item_price_afit, + product_price_hive: item_price, + hive_paid: ver_trx.amount_hive, + currency: req.params.bchain, + blockNo: req.params.blockNo, + trxID: req.params.trxID, + bchain: req.params.bchain, + note: 'Bought Product '+product.name+ ' Level '+product.level, + date: new Date(), + } + try{ + console.log(productBuyTrans); + let transaction = await db.collection('gadget_transactions_hive').insert(productBuyTrans); + console.log('success inserting post data'); + }catch(err){ + console.log(err); + res.send({'error': 'Error performing buy action. DB storing issue'}); + return; + } + + //add a ticket to the user to enter draw if user meets min requirements + let user_info = await grabUserTokensFunc (user); + console.log(user_info); + let cur_user_token_count = parseFloat(user_info.tokens); + + if (cur_user_token_count >= config.minUserTokensGadgetTicket){ + //perform transaction + let ticketEntry = { + user: user, + product_id: product_id, + product_name: product.name, + product_level: product.level, + product_price_afit: item_price_afit, + product_price_hive: item_price, + hive_paid: ver_trx.amount_hive, + currency: req.params.bchain, + count: 1, + date: new Date(), + } + let transaction = await db.collection('gadget_buy_tickets').insert(ticketEntry); + } + + //store into user_gadgets table as well + let userGadgetTrans = { + user: user, + gadget: new ObjectId(product_id), + product_type: product.type, + gadget_name: product.name, + gadget_level: product.level, + status: "bought", + span: parseInt(product.benefits.time_span), + span_unit: product.benefits.time_unit, + consumed: 0, + posts_consumed: [], + date_bought: new Date(), + last_updated: new Date(), + note: 'Bought Product '+product.name+ ' Level '+product.level, + } + try{ + console.log(userGadgetTrans); + let transaction = await db.collection('user_gadgets').insert(userGadgetTrans); + console.log('success inserting post data'); + }catch(err){ + console.log(err); + res.send({'error': 'Error performing buy action. DB storing issue'}); + return; + } + + //decrease product available count + product.count = parseInt(product.count) - 1; + //extreme case + if (product.count < 0) { + product.count = 0; + } + try{ + let trans = await db.collection('products').save(product); + console.log('success updating product count'); + }catch(err){ + console.log(err); + } + + + res.send({'status': 'Success'}); +}); + + +/* end point for tracking multi-gadget buy orders */ +app.get('/buyMultiGadgetHive/:user/:gadgets/:blockNo/:trxID/:bchain', async function (req, res) { + + let user = req.params.user; + let product_ids = req.params.gadgets.split('-'); + + let products_tot_price_afit = 0; + let products_tot_hive_price = 0; + let products_tot_hive_price_alt = 0; + for (let i=0;i 0){ + res.send({'error': 'Transaction already verified'}); + return; + } + + + //round down number + console.log('Before rounding'); + console.log(products_tot_hive_price); + products_tot_hive_price = (Math.floor(products_tot_hive_price * 1000) - 1) / 1000; + console.log('After rounding'); + console.log(products_tot_hive_price); + + //ensure proper transaction + let ver_trx = await utils.verifyGadgetPayTransaction(req.params.user, req.params.gadgets, products_tot_hive_price, products_tot_hive_price_alt, 'buy-gadget', req.params.blockNo, req.params.trxID, req.params.bchain); + if (!ver_trx || !ver_trx.success){ + res.send({status: 'error'}); + return; + } + + + let provider = 'actifit'; + + //perform transaction + let productBuyTrans = { + user: user, + reward_activity: 'Buy Product', + buyer: user, + seller: provider, + product_ids: product_ids, + product_price_afit: products_tot_price_afit, + product_price_hive_tot: products_tot_hive_price, + hive_paid: ver_trx.amount_hive, + currency: req.params.bchain, + blockNo: req.params.blockNo, + trxID: req.params.trxID, + bchain: req.params.bchain, + note: 'Bought Products '+req.params.gadgets, + date: new Date(), + } + try{ + console.log(productBuyTrans); + let transaction = await db.collection('gadget_transactions_hive').insert(productBuyTrans); + console.log('success inserting post data'); + }catch(err){ + console.log(err); + res.send({'error': 'Error performing buy action. DB storing issue'}); + return; + } + + //add a ticket to the user to enter draw if user meets min requirements + let user_info = await grabUserTokensFunc (user); + console.log(user_info); + let cur_user_token_count = parseFloat(user_info.tokens); + + if (cur_user_token_count >= config.minUserTokensGadgetTicket){ + for (let i=0;i x.user))]; + res.send({badges: badges, userCount: distinctUsers.length}); +}); + +/* end point for fetching if the user had a random doubled up win before */ +app.get('/luckyWinner/:user', async function (req, res) { + let user = await db.collection('token_transactions').find({user: req.params.user, reward_activity : "Post", lucky_winner: 1}).toArray(); + res.send(user); +}); + +/* end point for fetching if the user had contributed to charity before */ +app.get('/charityDonor/:user', async function (req, res) { + let user = await db.collection('token_transactions').find({reward_activity : "Charity Post", giver: req.params.user}).toArray(); + res.send(user); +}); + +/* end point for fetching all random doubled up winners */ +app.get('/luckyWinnerList/', async function (req, res) { + let user = await db.collection('token_transactions').find({reward_activity : "Post", lucky_winner: 1}).toArray(); + res.send(user); +}); + +/* claim this badge and store it for this user */ +app.get('/claimBadge/', async function (req, res) { + if (req.query.user && req.query.badge){ + const iso_badge = 'iso'; + const rew_activity_badge = 'rewarded_activity_lev_'; + const doubledup_badge = 'doubledup_badge'; + const charity_badge = 'charity_badge'; + let proceed = false; + //double check user eligibility in case of ISO + if (req.query.badge === iso_badge){ + let isoParticipant = await db.collection('iso_participants').find({user: req.query.user}).toArray(); + if (isoParticipant.length > 0){ + proceed = true; + } + }else if (req.query.badge.includes(rew_activity_badge)){ + //double check user eligibility in case of Rewarded Activity + req.params.user = req.query.user; + let activityCount = await userRewardedPostCountFunc(req, res); + //console.log('activityCount:'+activityCount); + let badgeLevel = req.query.badge.replace(rew_activity_badge,''); + //console.log('badgeLevel:'+badgeLevel); + let rewarded_posts_rules = [ + [9,0], + [29,1], + [59,2], + [89,3], + [119,4], + [179,5], + [359,6], + [539,7], + [719,8], + [1079,9], + [1080,10] + ]; + rewarded_posts_rules.some(function (item){ + //console.log(item); + //if we are attempting to claim the proper activity count passing level at proper matching level, proceed + if (parseInt(activityCount) > item[0] && parseInt(badgeLevel) == item[1] + 1){ + proceed = true; + } + }); + }else if (req.query.badge === doubledup_badge){ + let doubledupWinner = await db.collection('token_transactions').find({user: req.query.user, reward_activity : "Post", lucky_winner: 1}).toArray(); + if (doubledupWinner.length > 0){ + proceed = true; + } + }else if (req.query.badge === charity_badge){ + let charityDonor = await db.collection('token_transactions').find({reward_activity : "Charity Post", giver: req.query.user}).toArray(); + if (charityDonor.length > 0){ + proceed = true; + } + } + if (proceed){ + let user_badge = { + user: req.query.user, + badge: req.query.badge, + date_claimed: new Date(), + }; + try{ + let transaction = await db.collection('user_badges').insert(user_badge); + console.log('success inserting post data'); + res.send({status: 'success', user: req.query.user, badge: req.query.badge}); + }catch(err){ + console.log('error'); + res.send({status: 'error'}); + } + }else{ + res.send({status: 'error'}); + } + }else{ + res.send({status: 'error'}); + } +}); + +/* end point for checking if user took part of ISO event */ +app.get('/isoParticipant/:user', async function (req, res) { + let user = await db.collection('iso_participants').find({user: req.params.user}).toArray(); + res.send(user); +}); + +/* end point for checking if user took part of ISO event */ +app.get('/isoParticipantList/', async function (req, res) { + let userList = await db.collection('iso_participants').find().toArray(); + res.send(userList); +}); + +/* end point for returning current active delegator data by actifit */ +app.get('/topDelegators', async function (req, res) { + let delegatorList; + let hiveDelegatorList; + if (isNaN(req.query.count)){ + delegatorList = await db.collection('active_delegations').find().sort({steem_power: -1}).toArray(); + hiveDelegatorList = await db.collection('hive_active_delegations').find().sort({steem_power: -1}).toArray(); + }else{ + delegatorList = await db.collection('active_delegations').find().sort({steem_power: -1}).limit(parseInt(req.query.count)).toArray(); + hiveDelegatorList = await db.collection('hive_active_delegations').find().sort({steem_power: -1}).limit(parseInt(req.query.count)).toArray(); + } + res.send({steem: delegatorList, hive: hiveDelegatorList}); +}); + +activeDelegationFunc = async function (userName){ + let user = await db.collection('hive_active_delegations').findOne({_id: userName}, {fields : { _id:0} }); + console.log(user); + return user; +} + +/* end point for returning a single user last recorded active delegation amount */ +app.get('/delegation/:user', async function (req, res) { + var user = await activeDelegationFunc(req.params.user); + res.send(user); +}); + + +isModerator = async function(userName){ + let entryFound = false + let moderatorList = await db.collection('team').find({name: userName, title:'moderator', status:'active'}).toArray(); + if (Array.isArray(moderatorList) && moderatorList.length>0){ + entryFound = true; + } + return entryFound; +} +moderatorsListFunc = async function () { + let moderatorList = await db.collection('team').find({title:'moderator', status:'active'}).sort({name: 1}).toArray(); + return moderatorList; +} + +/* end point for returning current active moderators data by actifit */ +app.get('/moderators', async function (req, res) { + var moderatorList; + moderatorList = await moderatorsListFunc(); + res.send(moderatorList); +}); + +/* end point for returning current active ambassadors data by actifit */ +app.get('/ambassadors', async function (req, res) { + var ambassadorList; + ambassadorList = await db.collection('team').find({title:'ambassador', status:'active'}).sort({name: 1}).toArray(); + res.send(ambassadorList); +}); + +/* end point for returning current active professionals list */ +app.get('/professionals', async function (req, res) { + var professionalsList; + professionalsList = await db.collection('professionals').find({active:true}).sort({name: 1}).toArray(); + res.send(professionalsList); +}); + +/* end point for returning current active product list */ +app.get('/products', async function (req, res) { + var productsList; + productsList = await db.collection('products').find({active:true}).sort({name: -1}).toArray(); + res.send(productsList); +}); + +/* end point for returning current top AFIT token holders */ +app.get('/topTokenHolders', async function (req, res) { + var tokenHolders; + + //fetch banned accounts + let banned_users = await db.collection('banned_accounts').find({ban_status:"active"}, {fields : { user: 1, _id: 0 } }).toArray(); + //console.log(banned_users); + let banned_arr = banned_users.map(entr => entr.user); + banned_arr.push(''); + + if (isNaN(req.query.count)){ + tokenHolders = await db.collection('user_tokens').find({_id:{$nin: banned_arr}}).sort({tokens: -1}).toArray(); + }else{ + tokenHolders = await db.collection('user_tokens').find({_id:{$nin: banned_arr}}).sort({tokens: -1}).limit(parseInt(req.query.count)).toArray(); + } + let output = tokenHolders; + if (req.query.pretty){ + output = '#|Token Holder | AFIT Tokens Held |
'; + output += '|---|---|---|
'; + for(var i = 0; i < tokenHolders.length; i++) { + let tokenHolder = tokenHolders[i]; + output += (i+1) + '|'; + output += '@'+tokenHolder.user + '|'; + output += gk_add_commas(tokenHolder.tokens.toFixed(3)) + '|'; + output += '
'; + } + } + res.send(output); +}); + + +/* end point for returning accounts banned by actifit*/ +app.get('/banned_users', async function (req, res) { + var banned_users = await db.collection('banned_accounts').find({ban_status:"active"}).toArray(); + res.send(banned_users); +}); + +/* end point for returning if a user is banned by actifit*/ +app.get('/is_banned/:user', async function (req, res) { + let is_banned = await db.collection('banned_accounts').findOne({user: req.params.user, ban_status:"active"}); + console.log (is_banned!=null) + res.send(is_banned!=null); +}); + +/* end point for returning if a user is powering down AFIT*/ +app.get('/isPoweringDown/:user', async function (req, res) { + let poweringDown = await db.collection('powering_down_he').findOne({user: req.params.user}); + console.log (poweringDown) + if (!poweringDown){ + res.send({}); + }else{ + res.send(poweringDown); + } +}); + +/* end point for returning the list of users powering down AFIT*/ +app.get('/poweringDownList/', async function (req, res) { + let poweringDown = await db.collection('powering_down_he').find().toArray(); + console.log (poweringDown) + res.send(poweringDown); +}); + +app.get('/cancelAFITMoveSE', async function(req, res){ + if (!req.query.user || !req.query.fundsPass){ + res.send({'error':'generic error'}); + }else{ + let user = req.query.user; + let fundsPass = req.query.fundsPass; + + //confirm matching funds password + let query = {user: user}; + + let entryFound = await db.collection('account_funds_pass').findOne(query, {fields : { _id:0} }); + + if (entryFound == null){ + res.send({'error': 'Account does not have a recorded funds password'}); + return; + }else if (!entryFound.passVerified){ + res.send({'error': 'Account\'s funds password not verified'}); + return; + }else{ + //create encrypted version of sent password + var cipher = crypto.createCipher(config.funds_encr_mode, config.funds_encr_key); + let encr_pass = cipher.update(fundsPass, 'utf8', 'hex'); + encr_pass += cipher.final('hex'); + if (entryFound.pass !== encr_pass){ + res.send({'error': 'Incorrect username and/or funds password'}); + return; + } + } + + //reached here, we're fine + + try{ + let result = await db.collection('powering_down_he').remove({user: req.query.user}); + res.send({'status': 'Success'}); + }catch(err){ + console.log(err); + } + } +}); + +/* function handles the processing of AFIT power down and moving tokens to S-E */ +app.get('/initiateAFITMoveSE', async function(req, res){ + if (!req.query.user || !req.query.amount || !req.query.fundsPass) { + //make sure all params are sent + res.send({'error':'generic error'}); + }else{ + let user = req.query.user; + let amount = parseFloat(req.query.amount); + let fundsPass = req.query.fundsPass; + + + //check first if user is banned, as he wont be able to move funds + let is_banned = await db.collection('banned_accounts').findOne({user: user, ban_status:"active"}); + if (is_banned){ + res.send({'error': 'You cannot move AFIT as your account is banned'}); + return; + } + + //check if amount is numeric + if (isNaN(amount)){ + res.send({'error': 'Amount sent is non numeric'}); + return; + } + + if (amount > config.max_afit_to_se_day){ + res.send({'error': 'You cannot transfer more than ' + config.max_afit_to_se_day + ' AFIT / day'}); + return; + } + + //confirm matching funds password + let query = {user: user}; + + let entryFound = await db.collection('account_funds_pass').findOne(query, {fields : { _id:0} }); + + if (entryFound == null){ + res.send({'error': 'Account does not have a recorded funds password'}); + return; + }else if (!entryFound.passVerified){ + res.send({'error': 'Account\'s funds password not verified'}); + return; + }else{ + //create encrypted version of sent password + var cipher = crypto.createCipher(config.funds_encr_mode, config.funds_encr_key); + let encr_pass = cipher.update(fundsPass, 'utf8', 'hex'); + encr_pass += cipher.final('hex'); + if (entryFound.pass !== encr_pass){ + res.send({'error': 'Incorrect username and/or funds password'}); + return; + } + } + + //reached here, we're fine + + //confirm proper AFIT token balance. Test against target amount to be sent + let user_info = await grabUserTokensFunc (user); + console.log(user_info); + let cur_sender_token_count = parseFloat(user_info.tokens); + + if (cur_sender_token_count < amount){ + res.send({'error': 'Account does not have enough AFIT funds'}); + return; + } + let tot_afitx_bal = 0; + let afitx_se_balance = 0; + let afitx_he_balance = 0; + //confirm amount within AFITX conditions + let bal = await ssc.findOne('tokens', 'balances', { account: user, symbol: 'AFITX' }); + let bal_he = await hsc.findOne('tokens', 'balances', { account: user, symbol: 'AFITX' }); + + if (bal){ + afitx_se_balance = parseFloat(bal.balance); + } + if (bal_he){ + afitx_he_balance = parseFloat(bal_he.balance); + } + tot_afitx_bal = afitx_se_balance + afitx_he_balance; + /*if (bal || bal_he){ + + }else{ + res.send({'error': 'Unable to fetch AFITX Funds. Try again later.'}); + return; + }*/ + + //make sure user has at least 0.1 AFITX to move tokens + if (tot_afitx_bal < 0.1){ + res.send({'error': 'You do not have enough AFITX to move AFIT tokens over.'}); + return; + } + //console.log(amount_to_powerdown); + //console.log(this.afitx_se_balance); + //calculate amount that can be transferred daily + if (amount / 100 > tot_afitx_bal){ + res.send({'error': 'You do not have enough AFITX to move '+amount+ ' AFIT'}); + return; + } + + //register transaction for upcoming powering down cycle + + //query to see if user is already powering down + let tokenPowerDownQuery = { + user: user, + } + //store the transaction to the user's profile + let tokenPowerDownTrans = { + user: user, + daily_afit_transfer: amount, + min_afitx: amount / 100, + date: new Date(), + } + + try{ + console.log(tokenPowerDownTrans); + let transaction = await db.collection('powering_down_he').update(tokenPowerDownQuery, tokenPowerDownTrans, { upsert: true }); + console.log('success inserting power down data'); + }catch(err){ + console.log(err); + res.send({'error': 'Error performing power down. DB storing issue'}); + return; + } + + res.send({'status': 'Success', trx: tokenPowerDownTrans}); + } +}) + +/* function handles the processing of a buy order */ +app.get('/tipAccount', async function(req, res){ + if (!req.query.user || !req.query.targetUser || !req.query.amount || !req.query.fundsPass) { + //make sure all params are sent + res.send({'error':'generic error'}); + }else{ + let user = req.query.user; + let targetUser = req.query.targetUser; + let amount = parseFloat(req.query.amount); + let fundsPass = req.query.fundsPass; + + + //check first if user is banned, as he wont be able to tip + let is_banned = await db.collection('banned_accounts').findOne({user: user, ban_status:"active"}); + if (is_banned){ + res.send({'error': 'You cannot tip AFIT as your account is banned'}); + return; + } + + //check first if targetUuser is banned, as he wont be able to tip + is_banned = await db.collection('banned_accounts').findOne({user: targetUser, ban_status:"active"}); + if (is_banned){ + res.send({'error': 'You cannot tip AFIT to a banned account'}); + return; + } + + //confirm matching funds password + let query = {user: user}; + + let entryFound = await db.collection('account_funds_pass').findOne(query, {fields : { _id:0} }); + + if (entryFound == null){ + res.send({'error': 'Account does not have a recorded funds password'}); + return; + }else if (!entryFound.passVerified){ + res.send({'error': 'Account\'s funds password not verified'}); + return; + }else{ + //create encrypted version of sent password + var cipher = crypto.createCipher(config.funds_encr_mode, config.funds_encr_key); + let encr_pass = cipher.update(fundsPass, 'utf8', 'hex'); + encr_pass += cipher.final('hex'); + if (entryFound.pass !== encr_pass){ + res.send({'error': 'Incorrect username and/or funds password'}); + return; + } + } + + //reached here, we're fine + + //confirm proper AFIT token balance. Test against target amount to be sent + let user_info = await grabUserTokensFunc (user); + console.log(user_info); + let cur_sender_token_count = parseFloat(user_info.tokens); + + if (cur_sender_token_count < amount){ + res.send({'error': 'Account does not have enough AFIT funds'}); + return; + } + + //check how much the user has tipped today + let totalTipAmount = await tippedToday(req, res); + if (parseFloat(totalTipAmount) >= parseFloat(config.max_allowed_tips_per_day)){ + res.send({'error': 'User cannot tip more today. Max tips per day is set at '+config.max_allowed_tips_per_day + ' AFIT'}); + return; + } + + if (parseFloat(totalTipAmount) + amount > parseFloat(config.max_allowed_tips_per_day)){ + res.send({'error': 'Tip amount exceeds daily limit (' + config.max_allowed_tips_per_day + ') by '+ ((parseFloat(totalTipAmount) + amount) - parseFloat(config.max_allowed_tips_per_day) ) + ' AFIT. Try a smaller amount.'}); + return; + } + + //perform transaction, decrease sender amount + let tipTrans = { + user: user, + reward_activity: 'Send Tip', + recipient: targetUser, + token_count: -amount, + tip_amount: amount, + note: user + ' tipped ' + targetUser + ' ' + amount + ' AFIT', + date: new Date(), + } + try{ + console.log(tipTrans); + let transaction = await db.collection('token_transactions').insert(tipTrans); + console.log('success inserting tip data'); + }catch(err){ + console.log(err); + res.send({'error': 'Error performing tip action. DB storing issue'}); + return; + } + + //perform transaction, increase recipient amount + let tipReceiptTrans = { + user: targetUser, + reward_activity: 'Receive Tip', + sender: user, + token_count: amount, + tip_amount: amount, + note: user + ' tipped ' + targetUser + ' ' + amount + ' AFIT', + date: new Date(), + } + + try{ + console.log(tipReceiptTrans); + let transaction = await db.collection('token_transactions').insert(tipReceiptTrans); + console.log('success inserting post data'); + }catch(err){ + console.log(err); + res.send({'error': 'Error performing tip action. DB storing issue'}); + return; + } + + //also send notification to the recipient about tipped amount + utils.sendNotification(db, targetUser, user, 'tip_notification', 'User ' + user + ' has sent you a tip of '+ amount +' AFIT', 'https://actifit.io/'+user); + + //update sending user's token balance & store to db + let new_token_count = cur_sender_token_count - amount; + user_info.tokens = new_token_count; + console.log('new_token_count:'+new_token_count); + try{ + let trans = await db.collection('user_tokens').save(user_info); + console.log('success updating user token count'); + }catch(err){ + console.log(err); + } + + //confirm proper AFIT token balance. Test against target amount to be sent + let target_user_info = await grabUserTokensFunc (targetUser); + if (target_user_info == null){ + //first time actifit user, let's create a new entry + target_user_info = new Object(); + target_user_info._id = targetUser; + target_user_info.user = targetUser; + target_user_info.tokens = 0; + } + console.log(target_user_info); + let cur_target_user_token_count = parseFloat(target_user_info.tokens); + + //update receiving user's token balance & store to db + let upd_token_count = cur_target_user_token_count + amount; + target_user_info.tokens = upd_token_count; + console.log('upd_token_count:'+upd_token_count); + try{ + let trans = await db.collection('user_tokens').save(target_user_info); + console.log('success updating user token count'); + }catch(err){ + console.log(err); + } + + res.send({'status': 'Success', 'tipAmount': amount,'senderTokenCount': new_token_count, 'recipientTokenCount': upd_token_count}); + } +}) + + +tippedToday = async function (req, res){ + let startDate = moment(moment().utc().startOf('date').toDate()).format('YYYY-MM-DD'); + //console.log("startDate:"+startDate+" endDate:"+endDate); + + if (req.query.targetDate){ + startDate = moment(moment(req.query.targetDate).utc().startOf('date').toDate()).format('YYYY-MM-DD'); + } + + let endDate = moment(moment(startDate).utc().add(1, 'days').toDate()).format('YYYY-MM-DD'); + + query_json = { + "reward_activity": "Send Tip", + "date": { + "$lte": new Date(endDate), + "$gt": new Date(startDate) + } + }; + //adjust query to include user + //console.log(req.params.user) + if (req.params.user){ + query_json.user = req.params.user; + }else if (req.query.user){ + query_json.user = req.query.user; + } + + let result = await db.collection('token_transactions').find(query_json).toArray(); + let totalTipAmount = 0; + try{ + for (let i = 0; i< result.length; i++){ + //console.log(result[i]); + totalTipAmount += parseFloat(result[i].tip_amount); + } + console.log('totalTipAmount:'+totalTipAmount); + }catch(err){ + console.log(err.message); + } + return totalTipAmount; +} + +/* end point for counting amount of tips on a single day */ +app.get('/totalTipped', async function (req, res) { + let totalTipAmount = await tippedToday(req, res); + res.send(JSON.stringify({total_tip:totalTipAmount})); + +}); + +/* end point for counting amount of tips by user on a single day */ +app.get('/tippedToday/:user', async function (req, res) { + let totalTipAmount = await tippedToday(req, res); + res.send(JSON.stringify({total_tip:totalTipAmount})); + +}); + + + +/* end point for counting number of reblogs on a certain date param (default current date) */ +app.get('/reblogCount', async function (req, res) { + var todayDate = moment(moment().utc().startOf('date').toDate()).format('YYYY-MM-DD'); + //fileName = "steemrewards"+fileName+".json"; + var dateRegex = new RegExp ('^'+todayDate); // /^2018-08-05/ + if (req.query.targetDate){ + dateRegex = new RegExp ('^'+req.query.targetDate); + } + let query = await db.collection('token_transactions').find({ + "reward_activity": "Post Reblog", + "date": dateRegex + }) + try{ + console.log('counting'); + let reblog_count = await query.count(); + console.log(reblog_count); + res.send(JSON.stringify({reblog_count:reblog_count})); + }catch(err){ + console.log(err.message); + } +}); + +/* end point for counting number of upvotes on a certain date param (default current date) */ +app.get('/upvoteCount', async function (req, res) { + + var startDate = moment(moment().utc().startOf('date').toDate()).format('YYYY-MM-DD'); + if (req.query.targetDate){ + startDate = moment(moment(req.query.targetDate).utc().startOf('date').toDate()).format('YYYY-MM-DD'); + } + var endDate = moment(moment(startDate).utc().add(1, 'days').toDate()).format('YYYY-MM-DD'); + console.log("startDate:"+startDate+" endDate:"+endDate); + //adjust query to include dates + query_json = { + "reward_activity": "Post Vote", + "date": { + "$lte": new Date(endDate), + "$gt": new Date(startDate) + } + }; + + let query = await db.collection('token_transactions').find(query_json); + + try{ + console.log('counting'); + let upvote_count = await query.count(); + console.log(upvote_count); + res.send(JSON.stringify({upvote_count:upvote_count})); + }catch(err){ + console.log(err.message); + } +}); + + +/* end point for counting number of rewarded posts on a certain date param (default current date) */ +app.get('/rewardedPostCount', async function (req, res) { + + var startDate = moment(moment().utc().startOf('date').toDate()).format('YYYY-MM-DD'); + if (req.query.targetDate){ + startDate = moment(moment(req.query.targetDate).utc().startOf('date').toDate()).format('YYYY-MM-DD'); + } + var endDate = moment(moment(startDate).utc().add(1, 'days').toDate()).format('YYYY-MM-DD'); + console.log("startDate:"+startDate+" endDate:"+endDate); + //adjust query to include dates + query_json = { + "reward_activity": "Post", + "date": { + "$lte": new Date(endDate), + "$gt": new Date(startDate) + } + }; + + let query = await db.collection('token_transactions').find(query_json); + + try{ + console.log('counting'); + let rewarded_post_count = await query.count(); + console.log(rewarded_post_count); + res.send(JSON.stringify({rewarded_post_count:rewarded_post_count})); + }catch(err){ + console.log(err.message); + } +}); + +/* refactored function to grab rewarded post count per user for use across get calls */ +userRewardedPostCountFunc = async function(req, res){ + var user = req.params.user; + //default query + var query_json = { + "reward_activity": "Post", + "user": user + }; + //if this is a sum for specific period v/s a total sum + if (typeof req.query.period != "undefined" && !isNaN(req.query.period)){ + let days = req.query.period; + //console.log("days:"+days); + let startDate = moment(moment().utc().startOf('date').toDate()).format('YYYY-MM-DD'); + if (!isNaN(req.query.delay)){ + startDate = moment(moment().utc().startOf('date').subtract(req.query.delay, 'days').toDate()).format('YYYY-MM-DD'); + } + let endDate = moment(moment(startDate).utc().startOf('date').subtract(days, 'days').toDate()).format('YYYY-MM-DD'); + //console.log("startDate:"+startDate+" endDate:"+endDate); + //adjust query to include dates + query_json = { + "reward_activity": "Post", + "user": user, + "date": { + "$gte": new Date(endDate), + "$lt": new Date(startDate) + } + }; + } + + //build up query accordingly + let query = await db.collection('token_transactions').find(query_json); + try{ + //grab total number of matching records + let rewarded_post_count = await query.count(); + //console.log("rewarded_post_count:"+rewarded_post_count); + + return rewarded_post_count; + }catch(err){ + console.log(err.message); + return ""; + } +} + +/* end point for counting number of rewarded posts on a certain date param (default current date) */ +app.get('/userRewardedPostCount/:user', async function (req, res) { + + //grab user account + if (typeof req.params.user!= "undefined" && req.params.user!=null){ + + var rewarded_post_count = await userRewardedPostCountFunc(req, res); + res.send(JSON.stringify({rewarded_post_count:rewarded_post_count})); + }else{ + res.send(""); + } +}); + +calcRank = async function (req, res){ + //delegation calculation matrix + var delegation_rules = [ + [9,0], + [499,0.05], + [999,0.10], + [4999,0.20], + [9999,0.30], + [19999,0.40], + [49999,0.55], + [99999,0.65], + [499999,0.75], + [999999,0.90], + [1000000,1] + ] + + //AFIT token calculation matrix + var afit_token_rules = [ + [9,0], + [999,0.10], + [4999,0.20], + [9999,0.30], + [19999,0.40], + [49999,0.50], + [99999,0.60], + [499999,0.70], + [999999,0.80], + [4999999,0.90], + [5000000,1] + ] + + //Rewarded Posts calculation matrix + var rewarded_posts_rules = [ + [9,0], + [29,0.10], + [59,0.20], + [89,0.30], + [119,0.40], + [179,0.50], + [359,0.60], + [539,0.70], + [719,0.80], + [1079,0.90], + [1080,1] + ] + + //Rewarded Posts calculation matrix + var recent_reward_posts_rules = [ + [0,0], + [2,0.20], + [4,0.40], + [6,0.60], + [8,0.80], + [9,1] + ] + + var user_rank = 0; + + //grab delegation amount + var userDelegations = await activeDelegationFunc(req.params.user); + + let delegSP = 0; + //get current delegated SP if any + if (userDelegations != null){ + console.log('already delegated'); + delegSP = userDelegations.steem_power; + } + //console.log(userDelegations.steem_power); + + var delegation_score = 0; + + //check if the user has an alt account as beneficiary + let delegator_info = await getAltAccountStatusFunc(req.params.user); + //check if returned object is not empty + if (Object.keys(delegator_info).length > 0){ + if (parseInt(delegator_info.user_rank_benefit) == 1){ + //consider as no delegations + delegSP = 0; + } + }else{ + //also check the other case where the account is an alt-account + delegator_info = await getAltAccountByNameFunc(req.params.user); + //check if returned object is not empty + if (delegator_info.length > 0){ + for (let x=0, max_limit=delegator_info.length;x 0){ + delegation_score = utils.calcScore(delegation_rules, config.delegation_factor, parseFloat(delegSP)); + } + + user_rank += delegation_score; + + //grab user token count + var userTokens = await grabUserTokensFunc(req.params.user); + //console.log(userTokens.tokens); + + var afit_tokens_score = 0; + if (userTokens != null){ + afit_tokens_score = utils.calcScore(afit_token_rules, config.afit_token_factor, parseFloat(userTokens.tokens)); + } + + user_rank += afit_tokens_score; + + //grab total rewarded posts count + var tot_rewarded_post_count = await userRewardedPostCountFunc(req, res); + //console.log(tot_rewarded_post_count); + + var tot_posts_score = utils.calcScore(rewarded_posts_rules, config.rewarded_posts_factor, parseInt(tot_rewarded_post_count)); + + user_rank += tot_posts_score; + + //set the check period for config value of days days, and rerun the call to get last rewarded posting activity during this period + req.query.period = config.recent_posts_period; + + //add a 2 day delay to take into consideration late voting rounds + req.query.delay = 2; + + var recent_rewarded_post_count = await userRewardedPostCountFunc(req, res); + //console.log(recent_rewarded_post_count); + + var recent_posts_score = utils.calcScore(recent_reward_posts_rules, config.recent_posts_factor, parseInt(recent_rewarded_post_count)); + + user_rank += recent_posts_score; + + let rank_no_afitx = user_rank; + //also append AFITX based rank. for every 1 AFITX, increase 0.1 rank + let userHasAFITX = usersAFITXBal.find(entry => entry.account === req.params.user); + let user_rank_afitx = 0; + + if (userHasAFITX){ + user_rank_afitx = (parseFloat(userHasAFITX.balance) / 10).toFixed(2); + //max increase by holding AFITX is 100 + if (user_rank_afitx > 100){ + user_rank_afitx = 100; + } + user_rank += parseFloat(user_rank_afitx); + } + + var score_components = JSON.stringify({ + user_rank: user_rank.toFixed(2), + rank_no_afitx: rank_no_afitx, + afitx_rank: parseFloat(user_rank_afitx), + delegation_score: delegation_score, + afit_tokens_score: afit_tokens_score, + tot_posts_score: tot_posts_score, + recent_posts_score:recent_posts_score + }); + console.log(score_components) + return score_components; +} + +/* end point for getting current user's Actifit rank */ +app.get('/getRank/:user', async function (req, res) { + if (typeof req.params.user!= "undefined" && req.params.user!=null){ + let score_components = await calcRank(req, res); + res.send(score_components); + }else{ + res.send(""); + } +}); + +/* function handles the backbone for grabbing Alt Account Status */ +getAltAccountStatusFunc = async function (user){ + let delegator_info = null; + if (typeof user!= "undefined" && user!=null){ + //in this case, we check the status of a single user + var query_json = { + "delegator": user + }; + + delegator_info = await db.collection('delegation_alt_beneficiaries').findOne(query_json, {fields : { _id:0} }); + if (delegator_info==null){ + delegator_info = {}; + } + console.log(delegator_info); + }else{ + //alternatively grab all alt-account reward delegations + delegator_info = await db.collection('delegation_alt_beneficiaries').find().toArray(); + console.log(delegator_info); + } + return delegator_info; +} + +/* function handles checking if alt-account is linked to a delegator */ +getAltAccountByNameFunc = async function (targetUser){ + let delegator_info = null; + if (typeof targetUser!= "undefined" && targetUser!=null){ + //in this case, we check the status of a single user + var query_json = { + "alt_account": targetUser + }; + + delegator_info = await db.collection('delegation_alt_beneficiaries').find(query_json, {fields : { _id:0} }).toArray(); + console.log(delegator_info); + } + return delegator_info; +} + +/* end point for getting list of SP delegator accounts who wish to move their user rank and/or rewards to their alt-accounts*/ +app.get('/getAltAccountStatus/:user?', async function (req, res) { + let delegator_info = await getAltAccountStatusFunc(req.params.user); + res.send(delegator_info); +}); + +/* function handles processing requests for getting AFIT token pay depending on reward activity type */ +getPostRewardFunc = async function(user, url, reward_activity){ + var query_json = { + "reward_activity": reward_activity, + "user": user, + "url":url + }; + + let post_details = await db.collection('token_transactions').findOne(query_json, {fields : { _id:0} }); + console.log(post_details); + //fixing token amount display for 3 digits + if (typeof post_details!= "undefined" && post_details!=null){ + if (typeof post_details.token_count!= "undefined"){ + return parseFloat(post_details.token_count.toFixed(4)); + } + } + //otherwise return no tokens + return 0; +} + +/* end point for getting a post's reward */ +app.get('/getPostReward', async function (req, res) { + + if (typeof req.query.user!= "undefined" && req.query.user!=null + && typeof req.query.url!= "undefined" && req.query.url!=null){ + var user = req.query.user; + var url = req.query.url; + console.log('url:'+url); + //grab specific reward type for user and post + var token_count = await getPostRewardFunc(user, url, "Post"); + res.send({token_count: token_count}); + }else{ + res.send({token_count: 0}); + } +}); + +/* end point for retrieving a post's full AFIT Pay reward */ +app.get('/getPostFullAFITPayReward', async function (req, res) { + + if (typeof req.query.user!= "undefined" && req.query.user!=null + && typeof req.query.url!= "undefined" && req.query.url!=null){ + var user = req.query.user; + var url = req.query.url; + + //for the full AFIT rewards, grab only permalink portion without the community and author name + url = url.substring(url.lastIndexOf('/')+1); + console.log('url:'+url); + //grab specific reward type for user and post + var token_count = await getPostRewardFunc(user, url, "Full AFIT Payout"); + res.send({token_count: token_count}); + }else{ + res.send({token_count: 0}); + } +}); + + +/* end point for returning total number of rewarded tokens to charities based upon user activity, along with unique user count who donated */ +app.get('/getCharityRewards', async function(req, res) { + + await db.collection('token_transactions').aggregate([ + { + $match: {reward_activity:'Charity Post'} + }, + { + $group: + { + _id: null, + tokens_distributed: { $sum: "$token_count" }, + user_count: { $sum: 1 } + } + } + ]).toArray(function(err, results) { + var output = 'rewarded users:'+results[0].user_count+','; + output += 'tokens distributed:'+results[0].tokens_distributed; + res.send(results); + console.log(results); + }); + +}); + + + +/* end point for returning total number of AFIT tokens paid in return for full AFIT pay along with matching STEEM + SBD */ +app.get('/getFullAFITPayStats', async function(req, res) { + + await db.collection('token_transactions').aggregate([ + { + $match: {"reward_activity": "Full AFIT Payout"} + }, + { + $group: + { + _id: null, + afit_tokens: { $sum: "$token_count" }, + orig_sbd_amount: { $sum: "$orig_sbd_amount" }, + orig_steem_amount: { $sum: "$orig_steem_amount" }, + orig_sp_amount: { $sum: "$orig_sp_amount" }, + transaction_count: { $sum: 1 } + } + } + ]).toArray(function(err, results) { + res.send(results); + console.log(results); + }); + +}); + + +/* end point for capturing moderator activity on a specific date and for a specific period (defaults today and a single day activity) */ +app.get('/moderatorActivity', async function(req, res) { + let moderatorsList = await moderatorsListFunc(); + + //default today + var startDate = moment(moment().utc().startOf('date').toDate()).format('YYYY-MM-DD'); + if (req.query.targetDate){ + startDate = moment(moment(req.query.targetDate).utc().startOf('date').toDate()).format('YYYY-MM-DD'); + } + //default single day + let days = 1; + if (!isNaN(req.query.days)){ + days = req.query.days; + } + var endDate = moment(moment(startDate).utc().subtract(days, 'days').toDate()).format('YYYY-MM-DD'); + console.log("startDate:"+startDate+" endDate:"+endDate); + + await db.collection('team').aggregate([ + { + $match: + { + title:'moderator', + status:'active' + } + }, + { + $lookup: + { + from: "token_transactions", + localField: "name", + foreignField: "user", + as: "moderatorActivity" + } + }, + { + $project: + { + '_id':0, + items: + { + $filter: { + input: "$moderatorActivity", + as: "singleEntry", + cond: { $and: [ + { "$lte": ["$$singleEntry.date", new Date(startDate)] }, + { "$gt": ["$$singleEntry.date", new Date(endDate)] } + ] } + } + } + } + } + ]).toArray(function(err, results) { + res.send(results); + console.log(results); + }); + +}); + + +/* end point for capturing moderator activity stats during last week */ +app.get('/moderatorWeeklyStats', async function(req, res) { + let moderatorsList = await moderatorsListFunc(); + + //console.log(moment().utc().startOf('date').day()); + //return; + //default today + var startDate = moment(moment().utc().startOf('date').toDate()).format('YYYY-MM-DD'); + + //make sure stats cover up to 1 week + let days = moment().utc().startOf('date').day(); + + //need to fetch last week data if properly set + if (req.query.priorWeek){ + startDate = moment(moment(startDate).utc().subtract(days, 'days').toDate()).format('YYYY-MM-DD'); + days = 7; + } + + var endDate = moment(moment(startDate).utc().subtract(days, 'days').toDate()).format('YYYY-MM-DD'); + console.log("startDate:"+startDate+" endDate:"+endDate); + + await db.collection('team').aggregate([ + { + $match: + { + title:'moderator', + status:'active' + } + }, + { + $lookup: + { + from: "token_transactions", + localField: "name", + foreignField: "user", + as: "moderatorActivity" + } + }, + { + $project: + { + '_id':0, + items: + { + $filter: { + input: "$moderatorActivity", + as: "singleEntry", + cond: { $and: [ + { "$lte": ["$$singleEntry.date", new Date(startDate)] }, + { "$gt": ["$$singleEntry.date", new Date(endDate)] }, + { "$in": ["$$singleEntry.reward_activity", ["Moderator Comment", "Post Vote"]] } + ] } + } + } + } + }, + ]).toArray(function(err, results) { + res.send(results); + console.log(results); + }); + +}); + + +/* end point to grab current AFIT token price */ +app.get('/exchangeAFITPrice', async function(req, res) { + + console.log('exchangeAfitPrice:'+exchangeAfitPrice); + res.send(exchangeAfitPrice); +}); + +/* end point to grab current AFIT token price */ +app.get('/curAFITPrice', async function(req, res) { + let curAFITPrice = await db.collection('afit_price').find().sort({'date': -1}).limit(1).next(); + console.log('curAfitPrice:'+curAFITPrice.unit_price_usd); + res.send(curAFITPrice); +}); + +/* handles the process of creating accounts*/ +proceedAccountCreation = async function (req){ + //let's create the account now + let accountCreated = false; + let transStored = false; + accountCreated = await utils.createAccount(req.query.new_account, req.query.new_pass, req.query.cur_bchain); + if (accountCreated){ + transStored = await storeSignupTransaction(req); + //proceed only if a proper referrer was sent + if (typeof req.query.referrer != 'undefined' && req.query.referrer != 'undefined' && req.query.referrer != null){ + if (!req.query.promo_proceed || (req.query.promo_proceed && req.query.referrer_reward)){ + referralRewarded = await storeReferralReward(req); + } + } + } + console.log('account created:'+accountCreated); + return accountCreated; +} + +/* handles saving data related to signup to db */ +storeSignupTransaction = async function (req){ + console.log('reward new user'); + let result = false; + //setup new reward transaction for user + let new_transaction = { + account_name: req.query.new_account, + usd_invest: parseFloat(req.query.usd_invest), + steem_invest: parseFloat(req.query.steem_invest), + afit_reward: parseFloat(req.query.afit_reward), + memo: req.query.memo, + account_created: true, + payment_confirmed: true, + confirming_tx: req.query.confirming_tx, + promo_code: req.query.promo_code, + promo_used: req.query.promo_proceed, + signup_reward: req.query.signup_reward, + date: new Date(), + } + + + + + if (typeof req.query.referrer != 'undefined' && req.query.referrer != 'undefined' && req.query.referrer != null){ + + new_transaction['referrer'] = req.query.referrer; + new_transaction['referrer_afit_reward'] = parseFloat(req.query.afit_reward * config.referrerBonus); + + //calculate proper referral reward based on user data + + //user rank component + if (!req.params){ + req.params = new Object(); + } + req.params.user = req.query.referrer; + let ref_rank_obj = await calcRank(req, ''); + let ref_rank = JSON.parse(ref_rank_obj); + //let ref_rank = await ref_rank_obj.json(); + if (ref_rank){ + new_transaction['referrer_cur_rank'] = ref_rank.user_rank; + } + + //afit amount component + let user_info = await grabUserTokensFunc(req.query.referrer); + if (user_info){ + new_transaction['referrer_cur_afit'] = user_info.tokens; + } + + //afitx component + let userHasAFITX = usersAFITXBal.find(entry => entry.account === req.params.user); + + if (userHasAFITX){ + new_transaction['referrer_cur_afitx'] = userHasAFITX.balance; + } + + } + + if (typeof req.query.email != 'undefined' && req.query.email != 'undefined' && req.query.email != '' && req.query.email != null){ + new_transaction['email'] = req.query.email; + } + + //make sure we're not double storing referral + let query = { + account_name: req.query.new_account, + referrer: req.query.referrer, + }; + + try{ + let transaction = await db.collection('signup_transactions') + .replaceOne(query, new_transaction, { upsert: true }); + result = true; + }catch(e){ + console.log(e); + result = false; + } + + //also store this properly into user balance + if (!req.query.promo_proceed || (req.query.promo_proceed && req.query.signup_reward)){ + + new_transaction = { + user: req.query.new_account, + reward_activity: 'Signup Reward', + token_count: parseFloat(req.query.afit_reward), + date: new Date(), + steem_invest: parseFloat(req.query.steem_invest), + usd_invest: parseFloat(req.query.usd_invest), + note: 'Successful Signup', + } + + //make sure we're not double rewarding user + query = { + user: req.query.new_account, + reward_activity: 'Signup Reward', + }; + + try{ + let transaction = db.collection('token_transactions') + .replaceOne(query, new_transaction, { upsert: true }); + result = true; + }catch(e){ + console.log(e); + result = false; + } + console.log(result); + } + return result; +} + + +/* function handles saving referral info and reward if the signup came through a referral */ +storeReferralReward = async function (req){ + console.log('reward referrer'); + //setup new reward transaction for user + let refRewarded = false; + let new_transaction = { + user: req.query.referrer, + reward_activity: 'Signup Referral', + token_count: parseFloat(req.query.afit_reward * config.referrerBonus), + date: new Date(), + referred: req.query.new_account, + note: 'Referral reward for signup of user '+req.query.new_account, + } + + //make sure we're not double rewarding user + let query = { + user: req.query.referrer, + reward_activity: 'Signup Referral', + referred: req.query.new_account, + }; + + try{ + let transaction = await db.collection('token_transactions') + .replaceOne(query, new_transaction, { upsert: true }); + refRewarded = true; + }catch(e){ + console.log(e); + } + + console.log('success'); + return refRewarded; +}; + +app.get('/confirmAFITSEBulk', async function(req,res){ + //let's call the service by S-E + + let bchain = (req.query&&req.query.bchain?req.query.bchain:'HIVE'); + + let url = new URL(config.hive_engine_trans_acct_his); + if (bchain == 'STEEM'){ + url = new URL(config.steem_engine_trans_acct_his); + } + //console.log(config.steem_engine_trans_acct_his_lrg); + //connect with our service to confirm AFIT received to proper wallet + try{ + let se_connector = await fetch(url); + let trx_entries = await se_connector.json(); + + + //console.log(trx_entries); + trx_entries.forEach( async function(entry){ + console.log(entry); + let user = entry.from; + if (user != config.steem_engine_actifit_se && user != config.hive_engine_actifit_he){ + + let exchangeType = 'HE'; + + if (bchain == 'STEEM'){ + exchangeType = 'SE'; + } + + //query to see if entry already stored + let tokenExchangeTransQuery = { + user: user, + se_trx_ref: entry.transactionId + } + //store the transaction to the user's profile + let tokenExchangeTrans = { + user: user, + reward_activity: 'Move AFIT ' + exchangeType + ' to Actifit Wallet', + token_count: parseFloat(entry.quantity), + se_trx_ref: entry.transactionId, + exchange: exchangeType, + date: new Date(entry.timestamp * 1000) //timestamp linux convert to seconds first + } + try{ + console.log(tokenExchangeTrans); + //insert the query ensuring we do not write it twice + let transaction = await db.collection('token_transactions').update(tokenExchangeTransQuery, tokenExchangeTrans, { upsert: true }); + let trans_res = transaction.result; + console.log(trans_res); + + if (trans_res.upserted){ + //we have a new entry, increase user token count + + let user_info = await grabUserTokensFunc (user); + + let cur_user_token_count = 0; + if (user_info){ + cur_user_token_count = parseFloat(user_info.tokens); + //update current user's token balance & store to db + afit_amount = parseFloat(entry.quantity); + let new_token_count = cur_user_token_count + parseFloat(afit_amount); + user_info.tokens = new_token_count; + console.log('new_token_count:'+new_token_count); + try{ + let trans = await db.collection('user_tokens').save(user_info); + console.log('success adding AFIT tokens to user balance'); + }catch(err){ + console.log(err); + return; + } + } + } + + }catch(err){ + console.log(err); + res.write(JSON.stringify({'error': 'Error adding AFIT tokens to user balance'})); + res.end(); + return; + } + } + }); + + res.write(JSON.stringify({'status': 'done updating AFIT SE moves'})); + res.end(); + + }catch(err){ + console.log(err); + } +}) + +//function handles the process of confirming AFIT S-E receipt into proper account, and increases AFIT amount held in power mode +app.get('/confirmAFITSEReceipt', async function(req,res){ + if (!req.query.user){ + res.send('{}'); + }else{ + //keeping request alive to avoid timeouts + let intID = setInterval(function(){ + res.write(' '); + }, 6000); + let afit_amount = 0; + let found_entry = false; + try{ + let bchain = (req.query&&req.query.bchain?req.query.bchain:'HIVE'); + //attempt to find matching transaction + let targetUser = req.query.user; + let match_trx = await utils.confirmSEAFITReceived(targetUser, bchain); + console.log(match_trx); + let exchangeType = 'HE'; + if (bchain == 'STEEM'){ + exchangeType = 'SE'; + } + //we found a match + if (match_trx){ + found_entry = true; + //query to see if entry already stored + let tokenExchangeTransQuery = { + user: targetUser, + se_trx_ref: match_trx.transactionId + } + //store the transaction to the user's profile + let tokenExchangeTrans = { + user: targetUser, + reward_activity: 'Move AFIT '+ exchangeType + ' to Actifit Wallet', + token_count: parseFloat(match_trx.quantity), + se_trx_ref: match_trx.transactionId, + exchange: exchangeType, + date: new Date(match_trx.timestamp * 1000) //timestamp linux convert to seconds first + } + try{ + console.log(tokenExchangeTrans); + //insert the query ensuring we do not write it twice + let transaction = await db.collection('token_transactions').update(tokenExchangeTransQuery, tokenExchangeTrans, { upsert: true }); + let trans_res = transaction.result; + console.log(trans_res); + /*console.log('nMatched:'+trans_res.nMatched); + console.log('nUpserted:'+trans_res.upserted); + console.log('nModified:'+trans_res.nModified);*/ + if (trans_res.upserted){ + //we have a new entry, increase user token count + + let user_info = await grabUserTokensFunc (targetUser); + + let cur_user_token_count = 0; + if (user_info){ + cur_user_token_count = parseFloat(user_info.tokens); + //update current user's token balance & store to db + afit_amount = parseFloat(match_trx.quantity); + let new_token_count = cur_user_token_count + parseFloat(afit_amount); + user_info.tokens = new_token_count; + console.log('new_token_count:'+new_token_count); + try{ + let trans = await db.collection('user_tokens').save(user_info); + console.log('success adding AFIT tokens to user balance'); + }catch(err){ + console.log(err); + return; + } + } + }else{ + //do nothing + } + }catch(err){ + console.log(err); + res.write(JSON.stringify({'error': 'Error adding AFIT tokens to user balance'})); + res.end(); + return; + } + } + }catch(err){ + console.log(err); + } + //we're done, let's clear our running interval + clearInterval(intID); + //send response with confirming AFIT power up + let status = 'success'; + if (!found_entry){ + status = 'error'; + afit_amount = ''; + } + res.write(JSON.stringify({'afit_se_power': status, 'afit_amount': afit_amount})); + res.end(); + } +}); + + +//function handles the process of confirming AFITX S-E receipt into proper account, and then duplicating to new exchange +app.get('/proceedAfitxTransition', async function(req,res){ + if (!req.query.user || !req.query.amount || !req.query.txid){ + res.send('{}'); + }else{ + //keeping request alive to avoid timeouts + let intID = setInterval(function(){ + res.write(' '); + }, 6000); + let found_entry = false; + let afitx_amount = ''; + let status = ''; + try{ + let bchain = (req.query&&req.query.bchain?req.query.bchain:'HIVE'); + //attempt to find matching transaction + let targetUser = req.query.user; + let amount = req.query.amount; + let txid = req.query.txid; + let match_trx = await utils.confirmAFITXTransition(targetUser, txid, amount, bchain); + console.log(match_trx); + + //we found a match + if (match_trx){ + found_entry = true; + //query to see if entry already stored + let tokenExchangeTransQuery = { + user: targetUser, + token_count: amount, + trx: match_trx.transactionId, + block: match_trx.blockNumber, + chain: bchain, + } + //store the transaction to the user's profile + let tokenExchangeTrans = { + user: targetUser, + action: 'Move AFITX ', + token_count: amount, + net_amount: amount * (1-config.trx_burn_rate),//apply 0.5% burn rate + trx: match_trx.transactionId, + block: match_trx.blockNumber, + chain: bchain, + date: new Date(match_trx.timestamp * 1000) //timestamp linux convert to seconds first + } + console.log(tokenExchangeTrans); + try{ + //insert the query ensuring we do not write it twice + let transaction = await db.collection('afitx_transitions').find(tokenExchangeTransQuery).toArray(); + if (Array.isArray(transaction) && transaction.length > 0){ + //match found, duplicate request, ignore + console.log('Existing processed transaction. Ignore'); + status = 'error'; + }else{ + //apply 0.5% burn rate + amount = amount * (1-config.trx_burn_rate) + let res = await utils.proceedAfitxMove(targetUser, amount, (bchain=='STEEM'?'HIVE':'STEEM')); + let transaction = await db.collection('afitx_transitions').insert(tokenExchangeTrans); + console.log('success moving & inserting transaction data'); + afitx_amount = amount; + status = 'success'; + } + }catch(err){ + console.log(err); + res.write(JSON.stringify({'error': 'Error moving AFITX tokens to user balance'})); + res.end(); + return; + } + } + }catch(err){ + console.log(err); + } + //we're done, let's clear our running interval + clearInterval(intID); + //send response + res.write(JSON.stringify({'afitx_transition': status, 'afitx_amount': afitx_amount})); + res.end(); + } +}); + +//function handles the process of confirming AFITX S-E receipt into proper account, and then duplicating to new exchange +app.get('/proceedAfitTransition', async function(req,res){ + if (!req.query.user || !req.query.amount || !req.query.txid){ + res.send('{}'); + }else{ + //keeping request alive to avoid timeouts + let intID = setInterval(function(){ + res.write(' '); + }, 6000); + let found_entry = false; + let afitx_amount = ''; + let status = ''; + try{ + let bchain = (req.query&&req.query.bchain?req.query.bchain:'HIVE'); + //attempt to find matching transaction + let targetUser = req.query.user; + let amount = req.query.amount; + let txid = req.query.txid; + let standardAfit = 1; + let match_trx = await utils.confirmAFITXTransition(targetUser, txid, amount, bchain, standardAfit); + console.log(match_trx); + + //we found a match + if (match_trx){ + found_entry = true; + //query to see if entry already stored + let tokenExchangeTransQuery = { + user: targetUser, + token_count: amount, + trx: match_trx.transactionId, + block: match_trx.blockNumber, + chain: bchain, + } + //store the transaction to the user's profile + let tokenExchangeTrans = { + user: targetUser, + action: 'Move AFIT', + token_count: amount, + net_amount: amount * (1-config.trx_burn_rate),//apply 0.5% burn rate + trx: match_trx.transactionId, + block: match_trx.blockNumber, + chain: bchain, + date: new Date(match_trx.timestamp * 1000) //timestamp linux convert to seconds first + } + console.log(tokenExchangeTrans); + try{ + //insert the query ensuring we do not write it twice + let transaction = await db.collection('afit_transitions').find(tokenExchangeTransQuery).toArray(); + if (Array.isArray(transaction) && transaction.length > 0){ + //match found, duplicate request, ignore + console.log('Existing processed transaction. Ignore'); + status = 'error'; + }else{ + //apply 0.5% burn rate + amount = amount * (1-config.trx_burn_rate) + let res = await utils.proceedAfitxMove(targetUser, amount, (bchain=='STEEM'?'HIVE':'STEEM'), standardAfit); + let transaction = await db.collection('afit_transitions').insert(tokenExchangeTrans); + console.log('success moving & inserting transaction data'); + afitx_amount = amount; + status = 'success'; + } + }catch(err){ + console.log(err); + res.write(JSON.stringify({'error': 'Error moving AFIT tokens to user balance'})); + res.end(); + return; + } + } + }catch(err){ + console.log(err); + } + //we're done, let's clear our running interval + clearInterval(intID); + //send response + res.write(JSON.stringify({'afit_transition': status, 'afit_amount': afitx_amount})); + res.end(); + } +}); + + +/* function handles the processing of a buy order paid in HIVE */ +app.get('/processBuyOrderHive', async function(req, res){ + if (!req.query.user || !req.query.product_id) { + //make sure all params are sent + res.send({'error':'generic error'}); + }else{ + let user = req.query.user; + let product_id = req.query.product_id; + //confirm matching funds password + let query = {user: user}; + + let access_token; + + //fetch product info + let product = await grabProductInfo (product_id); + if (!product){ + res.send({'error': 'Product not found'}); + return; + } + + let price_options = product.price; + let price_options_count = price_options.length; + let item_price = 0; + let item_currency = 'HIVE'; + let actifit_percent_cut = 10; + for (let i=0; i < price_options_count; i++){ + let entry = price_options[i]; + item_price = entry.price; + item_currency = entry.currency; + actifit_percent_cut = entry.actifit_percent_cut; + } + + + //product.provider = 'actifit.test.provider'; + + //perform transaction + let productBuyTrans = { + user: user, + reward_activity: 'Buy Product', + buyer: user, + seller: product.provider, + product_id: product_id, + product_type: product.type, + product_price: item_price, + token_count: -item_price, + note: 'Bought Product '+product.name+ ' by '+product.provider, + date: new Date(), + } + try{ + console.log(productBuyTrans); + let transaction = await db.collection('token_transactions').insert(productBuyTrans); + console.log('success inserting post data'); + }catch(err){ + console.log(err); + res.send({'error': 'Error performing buy action. DB storing issue'}); + return; + } + + + + //store this in escrow + let productSellTrans = { + user: config.escrow_account,//targetAccount,//product.provider,//config.escrow_account, + reward_activity: 'Sell Product', + buyer: user, + seller: product.provider, + product_id: product_id, + product_type: product.type, + product_price: item_price, + token_count: item_price, + actifit_percent_cut: actifit_percent_cut, + note: 'Sold Product '+product.name+ ' to '+user, + date: new Date(), + } + + //alternatively, send to provider directly + if (product.type == 'ebook'){ + //close the transaction on the fly, no need to put in escrow. Rewards goes to seller + productSellTrans.user = product.provider; + productSellTrans.token_count = parseFloat(item_price) * (100 - parseFloat(actifit_percent_cut)) / 100; + } + + try{ + console.log(productSellTrans); + let transaction = await db.collection('gadget_transactions_hive').insert(productSellTrans); + console.log('success inserting post data'); + }catch(err){ + console.log(err); + res.send({'error': 'Error performing sell action. DB storing issue'}); + return; + } + + res.send({'status': 'Success', 'access_token': access_token}); + } +}) + + +/* function handles the processing of a buy order */ +app.get('/processBuyOrder', async function(req, res){ + if (!req.query.user || !req.query.product_id) { + //make sure all params are sent + res.send({'error':'generic error'}); + }else{ + let user = req.query.user; + let product_id = req.query.product_id; + //confirm matching funds password + let query = {user: user}; + + let access_token; + + /*let entryFound = await db.collection('account_funds_pass').findOne(query, {fields : { _id:0} }); + + if (entryFound == null){ + res.send({'error': 'Account does not have a recorded funds password'}); + return; + }else if (!entryFound.passVerified){ + res.send({'error': 'Account\'s funds password not verified'}); + return; + }else{ + //create encrypted version of sent password + var cipher = crypto.createCipher(config.funds_encr_mode, config.funds_encr_key); + let encr_pass = cipher.update(req.query.pass, 'utf8', 'hex'); + encr_pass += cipher.final('hex'); + if (entryFound.pass !== encr_pass){ + res.send({'error': 'Incorrect username and/or funds password'}); + return; + } + } + */ + + //fetch product info + let product = await grabProductInfo (product_id); + if (!product){ + res.send({'error': 'Product not found'}); + return; + } + + //confirm proper AFIT token balance. Test against product price + let user_info = await grabUserTokensFunc (user); + console.log(user_info); + let cur_user_token_count = parseFloat(user_info.tokens); + + let price_options = product.price; + let price_options_count = price_options.length; + let item_price = 0; + let item_currency = 'AFIT'; + let actifit_percent_cut = 10; + for (let i=0; i < price_options_count; i++){ + let entry = price_options[i]; + item_price = entry.price; + item_currency = entry.currency; + actifit_percent_cut = entry.actifit_percent_cut; + } + + if (cur_user_token_count < item_price){ + res.send({'error': 'Account does not have enough AFIT funds'}); + return; + } + + //product.provider = 'actifit.test.provider'; + + //perform transaction + let productBuyTrans = { + user: user, + reward_activity: 'Buy Product', + buyer: user, + seller: product.provider, + product_id: product_id, + product_type: product.type, + product_price: item_price, + token_count: -item_price, + note: 'Bought Product '+product.name+ ' by '+product.provider, + date: new Date(), + } + try{ + console.log(productBuyTrans); + let transaction = await db.collection('token_transactions').insert(productBuyTrans); + console.log('success inserting post data'); + }catch(err){ + console.log(err); + res.send({'error': 'Error performing buy action. DB storing issue'}); + return; + } + + + + //store this in escrow + let productSellTrans = { + user: config.escrow_account,//targetAccount,//product.provider,//config.escrow_account, + reward_activity: 'Sell Product', + buyer: user, + seller: product.provider, + product_id: product_id, + product_type: product.type, + product_price: item_price, + token_count: item_price, + actifit_percent_cut: actifit_percent_cut, + note: 'Sold Product '+product.name+ ' to '+user, + date: new Date(), + } + + //alternatively, send to provider directly + if (product.type == 'ebook'){ + //close the transaction on the fly, no need to put in escrow. Rewards goes to seller + productSellTrans.user = product.provider; + productSellTrans.token_count = parseFloat(item_price) * (100 - parseFloat(actifit_percent_cut)) / 100; + } + + try{ + console.log(productSellTrans); + let transaction = await db.collection('token_transactions').insert(productSellTrans); + console.log('success inserting post data'); + }catch(err){ + console.log(err); + res.send({'error': 'Error performing sell action. DB storing issue'}); + return; + } + + if (product.type == 'ebook'){ + //also put profit to actifit.profit account + + productSellTrans = { + user: config.sale_profit_account, + reward_activity: 'Sell Product Profit', + buyer: user, + seller: product.provider, + product_id: product_id, + product_type: product.type, + product_price: item_price, + token_count: parseFloat(item_price) * parseFloat(actifit_percent_cut) / 100, + actifit_percent_cut: actifit_percent_cut, + note: 'Sale Profit Product '+product.name+ ' by ' + product.provider + ' to '+user, + date: new Date(), + } + + try{ + console.log(productSellTrans); + let transaction = await db.collection('token_transactions').insert(productSellTrans); + console.log('success inserting post data'); + }catch(err){ + console.log(err); + res.send({'error': 'Error performing sell action. DB storing issue'}); + return; + } + + //we also need to store this transaction alongside an access token that enables this user only to access the download ebook + access_token = generatePassword(2); + + let productTokenTrans = { + user: user, + product_id: product_id, + access_token: access_token, + enabled: true, + date: new Date(), + } + + try{ + console.log(productTokenTrans); + let transaction = await db.collection('user_product_key').insert(productTokenTrans); + console.log('success inserting access_token'); + }catch(err){ + console.log(err); + res.send({'error': 'Error performing sell action. DB storing issue'}); + return; + } + + } + + + //update current user's token balance & store to db + let new_token_count = cur_user_token_count - parseFloat(item_price); + user_info.tokens = new_token_count; + console.log('new_token_count:'+new_token_count); + try{ + let trans = await db.collection('user_tokens').save(user_info); + console.log('success updating user token count'); + }catch(err){ + console.log(err); + } + + res.send({'status': 'Success', 'access_token': access_token}); + } +}) + +/* grab user token entry for user */ + +matchProductTrans = async function (user, product_id){ + let token_match = await db.collection('user_product_key').findOne( + { user: user, product_id: product_id }, + { user: 1, product_id: 1 } + ); + console.log(token_match); + return token_match; +} + +matchAccessToken = async function (user, product_id, access_token){ + let token_match = await db.collection('user_product_key').findOne({user: user, product_id: product_id, access_token: access_token}); + return token_match; +} + +app.get("/gadgetBoughtName", async function(req, res) { + console.log('gadgetBought'); + console.log(req.query); + //check if proper params sent + if (!req.query.user || !req.query.gadget_name || !req.query.gadget_level) { + //make sure all params are sent + res.send({'error':'generic error'}); + } + + let user = req.query.user; + let gadget_name = req.query.gadget_name; + let gadget_level = req.query.gadget_level; + + //check if the proper access token is valid for this user/product combination + let gadget_match = await db.collection('user_gadgets').find( + { user: user, gadget_name: gadget_name, gadget_level: parseInt(gadget_level)}, + + ).toArray(); + + console.log(gadget_match); + + //let token_match = await matchProductTrans(user, gadget_id); + + res.send(gadget_match); +}); + + +app.get("/gadgetsBought", async function(req, res){ + let gadgets = await db.collection('user_gadgets').find().toArray(); + res.send(gadgets); +}); + +app.get("/gadgetsBoughtByDate", async function(req, res){ + let startDate = moment(moment().utc().startOf('date').toDate()).format('YYYY-MM-DD'); + if (req.query.targetDate){ + startDate = moment(moment(req.query.targetDate).utc().startOf('date').toDate()).format('YYYY-MM-DD'); + } + let endDate = moment(moment(startDate).utc().subtract(1, 'days').toDate()).format('YYYY-MM-DD'); + let gadgets = await db.collection('user_gadgets').find( + { + date_bought:{ + $lte: new Date(startDate), + $gt: new Date(endDate) + } + }).toArray(); + let usersArray = []; + for (let i=0;i { + if (!err) { + console.log('match ebook'); + res.writeHead(200, { + "Content-Type": "application/pdf", + "Content-Disposition": "attachment; filename=" + fileName + }); + fs.createReadStream(filePath).pipe(res); + return; + } + console.log(err); + console.log('ebook does not exist'); + res.writeHead(400, {"Content-Type": "text/plain"}); + res.end("ERROR File does not exist"); + }); + +}) + + +//function handles the process of confirming payment receipt, and then proceeds with account creation, reward and delegation +app.get('/confirmPayment', async function(req,res){ + //if (req.query.confirm_payment_token != config.confirmPaymentToken){ + if (false){ + res.send('{}'); + }else{ + let paymentReceivedTx = ''; + let accountCreated = false; + let spToDelegate = 10; + //keeping request alive to avoid timeouts + let intID = setInterval(function(){ + res.write(' '); + }, 6000); + try{ + //check if promo code was sent, and confirm against available promo codes + if (req.query.promo_code){ + let promo_match = await db.collection('signup_promo_codes').findOne({code: req.query.promo_code}); + console.log(promo_match); + + if (promo_match && parseInt(promo_match.entries) > 0){ + //proceed creating account + req.query.promo_proceed = true; + req.query.signup_reward = promo_match.signup_reward; + req.query.referrer_reward = promo_match.referrer_reward; + try{ + accountCreated = await claimAndCreateAccount(req); + //only delegate if account created and delegation is enabled + if (accountCreated && promo_match.delegation){ + delegationSuccess = await utils.delegateToAccount(req.query.new_account, spToDelegate, req.query.cur_bchain); + } + + //decrease number of permitted entries + //update current user's token balance & store to db + promo_match.entries = parseInt(promo_match.entries) - 1; + console.log('promo_match.entries:'+promo_match.entries); + try{ + let trans = await db.collection('signup_promo_codes').save(promo_match); + console.log('success updating pending entries'); + }catch(err){ + console.log(err); + return; + } + }catch(e){ + console.log(e); + } + paymentReceivedTx = req.query.promo_code; + } + res.write(JSON.stringify({'paymentReceivedTx':paymentReceivedTx, 'accountCreated': accountCreated})); + res.end(); + }else{ + req.query.promo_proceed = false; + + //first step is to ensure memo has not been tampered with, nor has it been claimed before + //to do that, let's try to find if any signup has been done using this memo + let memo_used = await db.collection('signup_transactions').findOne({memo: req.query.memo}); + console.log('memo_used:'+memo_used); + if (typeof memo_used == "undefined" || memo_used == null){ + //check on which blockchain transaction was sent based on currency + let bchain = (req.query&&req.query.bchain?req.query.bchain:''); + if (req.query.sent_cur){ + if (req.query.sent_cur == 'STEEM' || req.query.sent_cur == 'SBD'){ + bchain = 'STEEM'; + }else if (req.query.sent_cur == 'HIVE' || req.query.sent_cur == 'HBD'){ + bchain = 'HIVE'; + } + } + paymentReceivedTx = await utils.confirmPaymentReceived(req, bchain); + console.log('>>>> got TX '+paymentReceivedTx); + if (paymentReceivedTx != ''){ + req.query.confirming_tx = paymentReceivedTx; + console.log(req.query); + try{ + accountCreated = await claimAndCreateAccount(req); + if (accountCreated){ + delegationSuccess = await utils.delegateToAccount(req.query.new_account, spToDelegate, req.query.bchain); + } + }catch(e){ + console.log(e); + } + } + } + //res.send({'paymentReceivedTx':paymentReceivedTx, 'accountCreated': accountCreated}); + res.write(JSON.stringify({'paymentReceivedTx':paymentReceivedTx, 'accountCreated': accountCreated})); + res.end(); + } + }catch(err){ + console.log(err); + } + //we're done, let's clear our running interval + clearInterval(intID); + + } +}); + +/* core function for discounted account claims and creation */ +claimAndCreateAccount = async function (req){ + let accountClaimed = false; + let accountCreated = false; + let results = ''; + try{ + results = await utils.getRC(config.account); + console.log('Current RC: ' + utils.format(results.estimated_pct) + '% '); + if (results.estimated_pct>50){ + //if we reached min threshold, claim more spots for discounted accounts + accountClaimed = await utils.claimDiscountedAccount(req.query.cur_bchain); + } + }catch(err){ + console.log('error grabbing RC'); + } + + console.log('discounted account claimed:'+accountClaimed); + //proceed creating account + try{ + accountCreated = await proceedAccountCreation(req); + }catch(err){ + console.log(err); + } + return accountCreated; + +}; + +//send notification +app.get('/sendNotification', async function(req,res){ + let passed_var = eval("req.query."+config.verifyNotifParam); + //console.log(passed_var); + //make sure needed security var is passed, and with proper value + if ((typeof passed_var == 'undefined') || passed_var != config.verifyNotifToken){ + res.send('{}'); + }else{ + if (req.query.notifType == 'new_post'){ + //first notify post owner + utils.sendNotification(db, req.query.user, req.query.actionTaker, req.query.notifType, 'You successfully created a new actifit report "' + req.query.title + '" ', 'https://actifit.io/'+req.query.user+'/'+req.query.permlink); + + //fetch user friends + let friends = await getUserFriends(req.query.user); + //send out a notification for each friend + for (let i=0;i>>> got TX '+paymentReceivedTx); + if (paymentReceivedTx != ''){ + try{ + //we found the transfer, now let's update the status properly + let query = {user: req.query.from}; + console.log(query); + let entryFound = await db.collection('account_funds_pass').findOne(query); + console.log(entryFound); + if (entryFound != null && + (!entryFound.passVerified)){ + try{ + //we need to set this transaction as processed via upvote + entryFound.passVerified = true; + let transaction = await db.collection('account_funds_pass').save(entryFound); + statusUpdated = true; + console.log('saved'); + }catch(e){ + console.log(e); + } + } + }catch(e){ + console.log(e); + } + } + }catch(err){ + console.log(err); + } + //we're done, let's clear our running interval + clearInterval(intID); + //res.send({'paymentReceivedTx':paymentReceivedTx, 'accountCreated': accountCreated}); + res.write(JSON.stringify({'paymentReceivedTx': paymentReceivedTx, 'statusUpdated': statusUpdated})); + res.end(); +}); + + +//function handles the process of confirming buy event for AFIT via STEEM +app.get('/confirmBuyAction', async function(req,res){ + let match_trx = ''; + let statusUpdated = false; + //keeping request alive to avoid timeouts + let intID = setInterval(function(){ + res.write(' '); + },8000); + try{ + let bchain = (req.query&&req.query.bchain?req.query.bchain:''); + match_trx = await utils.confirmPaymentReceivedBuy(req, bchain); + console.log('>>>> got TX '+match_trx); + let targetUser = req.query.from; + if (match_trx != ''){ + try{ + //we found the transfer, now let's book proper AFIT tokens for the user + //query to see if entry already stored + let tokenBuyTransQuery = { + user: targetUser, + buy_trx_ref: match_trx + } + //store the transaction to the user's profile + let tokenBuyTrans = { + user: targetUser, + reward_activity: 'Buy AFIT Actifit.io', + steem_spent: parseFloat(req.query.steem_amount), + token_count: parseFloat(req.query.afit_amount), + buy_trx_ref: match_trx, + date: new Date(), + } + try{ + console.log(tokenBuyTrans); + //insert the query ensuring we do not write it twice + let transaction = await db.collection('token_transactions').update(tokenBuyTransQuery, tokenBuyTrans, { upsert: true }); + let trans_res = transaction.result; + console.log(trans_res); + /*console.log('nMatched:'+trans_res.nMatched); + console.log('nUpserted:'+trans_res.upserted); + console.log('nModified:'+trans_res.nModified);*/ + if (trans_res.upserted){ + //we have a new entry, increase user token count + + let user_info = await grabUserTokensFunc (targetUser); + + let cur_user_token_count = 0; + if (user_info){ + cur_user_token_count = parseFloat(user_info.tokens); + //update current user's token balance & store to db + let afit_amount = parseFloat(req.query.afit_amount); + let new_token_count = cur_user_token_count + parseFloat(afit_amount); + user_info.tokens = new_token_count; + console.log('new_token_count:'+new_token_count); + try{ + let trans = await db.collection('user_tokens').save(user_info); + console.log('success adding AFIT tokens to user balance'); + }catch(err){ + console.log(err); + return; + } + } + }else{ + //do nothing + } + }catch(err){ + console.log(err); + res.write(JSON.stringify({'error': 'Error adding AFIT tokens to user balance'})); + res.end(); + return; + } + }catch(e){ + console.log(e); + } + } + }catch(err){ + console.log(err); + } + //we're done, let's clear our running interval + clearInterval(intID); + //res.send({'paymentReceivedTx':paymentReceivedTx, 'accountCreated': accountCreated}); + res.write(JSON.stringify({'paymentReceivedTx': match_trx})); + res.end(); +}); + + + +/* end point finding whether user has a pending AFIT token swap */ +app.get('/userHasPendingTokenSwap/:user', async function(req, res){ + let user_pending_swap = await db.collection('exchange_afit_steem').findOne({user: req.params.user,upvote_processed: {$in: [null, false, 'false']}},{fields : { _id:0} }); + res.send({user_pending_swap: user_pending_swap}); +}); + +/* end point finding user's historical AFIT token swap */ +app.get('/getUserTokenSwapHistory/:user', async function(req, res){ + let user_token_swap_hist = await db.collection('exchange_afit_steem').find({user: req.params.user},{fields : { _id:0} }).sort({'date': -1}).toArray(); + res.send({userTokenSwapHist: user_token_swap_hist}); +}); + +/* end point for getting number of AFIT -> STEEM upvotes pending exchanges */ +app.get('/getPendingTokenSwapTransCount/', async function(req, res){ + let tokenSwapTrans = await db.collection('exchange_afit_steem').find({upvote_processed: {$in: [null, false, 'false']}}).sort({'date': 1}).toArray(); + res.send({pendingSwap: tokenSwapTrans.length}); +}); + +/* end point for getting exchanges pending upvotes */ +app.get('/getPendingTokenSwapTrans/', async function(req, res){ + let tokenSwapTrans = await db.collection('exchange_afit_steem').find({upvote_processed: {$in: [null, false, 'false']}}).sort({'date': 1}).toArray(); + //generate total AFIT value as well + let afit_count = 0; + for (let i=0;i 1,000,000 AFIT", ""]} + ] + } + } + }, + { + $group: { + "_id" : "$range", + count: { + $sum: 1 + } + } + }, + { + $sort: { + "count": -1, + } + } + ]).toArray(function(err, results) { + if (req.query.pretty==1){ + let output = '|Category|Count|
'; + output += '|---|---|
'; + for (let entry of results) { + output += '|' + entry._id + '|' + entry.count + '
'; + } + res.send(output); + }else{ + res.send(results); + } + }); + +}); + + +/* end point handling additional reward to user votes via web */ +app.get('/rewardActifitWebVote/:user', async function(req,res){ + if (req.query.web_vote_token != config.actifitWebVoteToken){ + res.send('{}'); + }else{ + let reward_activity = 'Web Vote'; + let rewarded = await rewardActifitTokenWeb(req, reward_activity); + res.send({'rewarded':rewarded, amount: config.actifitWebVoteRewardAmount}); + } +}); + +/* end point handling rewarding web edits */ +app.get('/rewardActifitWebEdit/:user', async function(req,res){ + if (req.query.web_edit_token != config.actifitWebEditToken){ + res.send('{}'); + }else{ + let reward_activity = 'Web Edit'; + let rewarded = await rewardActifitTokenWeb(req, reward_activity); + res.send({'rewarded':rewarded, amount: config.actifitWebEditRewardAmount}); + } +}); + +/* end point handling additional reward to user comments via web */ +app.get('/rewardActifitWebComment/:user', async function(req,res){ + if (req.query.web_comment_token != config.actifitWebCommentToken){ + res.send('{}'); + }else{ + let reward_activity = 'Web Comment'; + let rewarded = await rewardActifitTokenWeb(req, reward_activity); + res.send({'rewarded':rewarded, amount: config.actifitWebCommentRewardAmount}); + } +}); + + +/* core function handling user rewards for various web related activities */ +rewardActifitTokenWeb = async function (req, reward_activity) { + //store outcome + let rewarded = false; + + //make sure we have user and url params set + if (req.params.user && typeof req.query.url!= "undefined" && req.query.url!=null) { + try{ + //let's reward this user for performing an edit using our web interface + let reward_date = new Date(); + + //only one reward per day, disregard time + reward_date.setHours(0,0,0,0); + + let new_transaction = { + user: req.params.user, + reward_activity: reward_activity, + token_count: parseFloat(config.actifitWebEditRewardAmount), + date: new Date(), + reward_date: reward_date, + url: req.query.url, + } + + //make sure we're not double rewarding user. New url edits override older ones on same date + let query = { + user: req.params.user, + reward_activity: reward_activity, + reward_date: reward_date, + }; + + //check if we have a match already to skip rewarding and/or notifying the user + let user_pre_rewarded = await db.collection('token_transactions').findOne(query); + if (typeof user_pre_rewarded == undefined || user_pre_rewarded == 'undefined' || user_pre_rewarded == null){ + console.log('first reward today'); + try{ + let transaction = db.collection('token_transactions') + .replaceOne(query, new_transaction, { upsert: true }); + rewarded = true; + }catch(e){ + console.log(e); + } + } + console.log(rewarded); + + }catch(err){ + console.log(err); + } + } + + return rewarded; +} + + +/* end point for returning total post count on a specific date */ +app.get('/recentVerifiedPosts', async function(req, res) { + + var startDate = moment(moment().utc().startOf('date').toDate()).format('YYYY-MM-DD'); + if (req.query.targetDate){ + startDate = moment(moment(req.query.targetDate).utc().startOf('date').toDate()).format('YYYY-MM-DD'); + } + var endDate = moment(moment(startDate).utc().add(2, 'days').toDate()).format('YYYY-MM-DD'); + console.log("startDate:"+startDate+" endDate:"+endDate); + + let maxCount = 100; + if (req.query.maxCount && !isNaN(req.query.maxCount)){ + maxCount = parseInt(req.query.maxCount); + } + + //fetch banned accounts + let banned_users = await db.collection('banned_accounts').find({ban_status:"active"}, {fields : { user: 1, _id: 0 } }).toArray(); + //console.log(banned_users); + let banned_arr = banned_users.map(entr => entr.user); + banned_arr.push(''); + //console.log(banned_arr); + + await db.collection('verified_posts').aggregate([ + {$match: + { + date: { + $lte: new Date(endDate), + $gt: new Date(startDate) + }, + author: { + $nin: banned_arr, + } + }, + }, + {$sort: + { + date:1 + }, + }, + {$group: + { + _id: '$author', + } + } + ]).limit(maxCount).toArray(function(err, results) { + //also append total token count to the grouped display + console.log(results.length); + res.send(results); + }); + +}); + +/* end point for returning total post count on a specific date */ +app.get('/recentAuthorsData', async function(req, res) { + + var startDate = moment(moment().utc().startOf('date').toDate()).format('YYYY-MM-DD'); + if (req.query.targetDate){ + startDate = moment(moment(req.query.targetDate).utc().startOf('date').toDate()).format('YYYY-MM-DD'); + } + var endDate = moment(moment(startDate).utc().add(1, 'days').toDate()).format('YYYY-MM-DD'); + let maxCount = 10; + if (req.query.maxCount && !isNaN(req.query.maxCount)){ + maxCount = parseInt(req.query.maxCount); + } + console.log("startDate:"+startDate+" endDate:"+endDate); + + await db.collection('verified_posts').aggregate([ + { + $match: + { + "date": { + "$lte": new Date(endDate), + "$gt": new Date(startDate) + } + } + } + ]).toArray(async function(err, results) { + //also append total token count to the grouped display + console.log(results.length); + results = results.reverse(); + let finalSet = []; + if (!req.params){ + req.params = new Object(); + } + for (let i=0;i < maxCount;i++){ + req.params.user = results[i].author; + let rank = await calcRank (req, res); + console.log(results[i].author); + console.log(rank); + finalSet.push({'author': results[i].author, 'rank': rank}); + } + res.send(finalSet); + + }); + +}); + +/* end point for returning total post count on a specific date */ +app.get('/totalPostsSubmitted', async function(req, res) { + + var startDate = moment(moment().utc().startOf('date').toDate()).format('YYYY-MM-DD'); + if (req.query.targetDate){ + startDate = moment(moment(req.query.targetDate).utc().startOf('date').toDate()).format('YYYY-MM-DD'); + } + var endDate = moment(moment(startDate).utc().add(1, 'days').toDate()).format('YYYY-MM-DD'); + console.log("startDate:"+startDate+" endDate:"+endDate); + + await db.collection('verified_posts').aggregate([ + { + $match: + { + "date": { + "$lte": new Date(endDate), + "$gt": new Date(startDate) + } + } + } + ]).toArray(function(err, results) { + //also append total token count to the grouped display + console.log(results.length); + res.send({count:results.length}); + }); + +}); + +/* end point for fetching user's recorded metrics */ +app.get('/trackedMeasurements/:user', async function(req, res) { + let query = {"author": req.params.user, + $or: [ + { "json_metadata.weight": {$exists: true} }, + { "json_metadata.height": {$exists: true} }, + { "json_metadata.chest": {$exists: true} }, + { "json_metadata.waist": {$exists: true} }, + { "json_metadata.thighs": {$exists: true} }, + { "json_metadata.bodyfat": {$exists: true} } + ] + } + posts = await db.collection('verified_posts').find(query, {fields : { _id:0} }).sort({date: -1}).toArray(); + res.send(posts); +}); + + + +/* end point for fetching user's recorded activity records */ +app.get('/trackedActivity/:user', async function(req, res) { + let query = {"author": req.params.user, + } + posts = await db.collection('verified_posts').find(query, {fields : { _id:0} }).sort({date: -1}).toArray(); + res.send(posts); +}); + + + +function gk_add_commas(nStr) { + if (isNaN(nStr)){ + return nStr; + } + nStr += ''; + var x = nStr.split('.'); + var x1 = x[0]; + var x2 = x.length > 1 ? '.' + x[1] : ''; + var rgx = /(\d+)(\d{3})/; + while (rgx.test(x1)) { + x1 = x1.replace(rgx, '$1' + ',' + '$2'); + } + return x1 + x2; +} + +app.listen(appPort); diff --git a/comment.md b/comment.md index f5625b4..f521445 100644 --- a/comment.md +++ b/comment.md @@ -1,3 +1,18 @@ -Congrats! You just reached **{milestone}** via using Actifit fitness tracker and provided **Proof of Activity**! -You accordingly gained {token_count} Actifit tokens for attaining {step_count} steps! -You also received an {weight}% upvote via @actifit account. \ No newline at end of file +Congrats on providing **Proof of Activity** via your Actifit report! +{lucky_reward} +You have accordingly been rewarded {token_count} AFIT tokens for your effort in reaching {step_count} activity, as well as your user rank and report quality! +You also received an {weight}% upvote via @actifit account. +Actifit rewards and upvotes are based on your: +- User rank: which depends on your delegated SP, accumulated AFIT tokens, rewarded post count, recent rewarded activity, and AFITX owned. +- Post score: which depends on your activity count, post content, post upvotes, quality comments, moderator review and user rank. + +To improve your user rank, delegate more, pile up more AFIT & AFITX tokens, and post more. +To improve your post score, get to the max activity count, work on improving your post content, improve your user rank, engage with the community to get more upvotes and quality comments. + +Actifit is now a Steem Witness. If you believe in our project, consider [voting for us](https://steemconnect.com/sign/account-witness-vote?witness=actifit&approve=true) + +![rulersig2.jpg](https://cdn.steemitimages.com/DQmXrZz658YfMQBXNTA12rmbzqWXASfaGcNSqatJJ2ba7NR/rulersig2.jpg) +Vote for [Actifit as a Witness](https://steemconnect.com/sign/account-witness-vote?witness=actifit&approve=1) +Chat with us on [discord](https://discord.gg/aHtcA6r) | Visit our [website](https://actifit.io/) +[Download on playstore](https://bit.ly/actifit-app) | [Download on app store](https://bit.ly/actifit-ios) +[FAQs](https://steemit.com/actifit/@katerinaramm/actifit-app-or-rewarding-fitness-activity-with-tokens-and-steemit-upvotes-faqs) | [Text Tutorial](https://steemit.com/utopian-io/@katerinaramm/tutorial-for-actifit-app-android) | [Video Tutorial](https://youtu.be/tqkaDoonyvI) \ No newline at end of file diff --git a/config-example.json b/config-example.json index c1d9e34..5b27cd6 100644 --- a/config-example.json +++ b/config-example.json @@ -4,6 +4,7 @@ "detailed_logging": false, "account": "actifit", "main_tag": "actifit", + "pay_account": "actifit.pay", "memo_key": "your_private_memo_key", "posting_key": "your_private_posting_key", "active_key": "your_private_active_key", @@ -14,6 +15,8 @@ "comment": false, "resteem": false, "no_paid_bots": true, + "exclude_rewards": ["mcfarhat"], + "weekly_rewards_limit": 700000, "beneficiaries": ["actifit", "actifit.pay"], "flag_signal_accounts": ["spaminator", "cheetah", "steemcleaners", "mack-bot"], "mongo_uri": "mongodb://localhost:27017", @@ -21,4 +24,4 @@ "smtp_usr": "SMTP USER NAME", "smtp_from": "'FROM NANE' ", "smtp_key": "smtp_key_or_password" -} +} \ No newline at end of file diff --git a/curation-bot.js b/curation-bot.js index 6892131..37c4903 100644 --- a/curation-bot.js +++ b/curation-bot.js @@ -1,493 +1,3555 @@ -var fs = require("fs"); -const steem = require('steem'); -var utils = require('./utils'); -var mail = require('./mail'); -var _ = require('lodash'); -var moment = require('moment'); - -var account = null; -var last_trans = 0; -var members = []; -var whitelist = []; -var config = null; -var first_load = true; -var is_voting = false; -var last_voted = 0; -var vote_time; -var last_votes = Array(); -var skip = false; -var version = '0.0.1'; -var error_sent = false; - -steem.api.setOptions({ url: 'https://api.steemit.com' }); - -utils.log("* START - Version: " + version + " *"); - -// Load the settings from the config file -loadConfig(); -var botNames; - -// Check if bot state has been saved to disk, in which case load it -if (fs.existsSync('state.json')) { - var state = JSON.parse(fs.readFileSync("state.json")); - - if (state.last_trans) - last_trans = state.last_trans; - - if (state.last_voted) - last_voted = state.last_voted; - - if (state.vote_time) - vote_time = state.vote_time; - - utils.log('Restored saved bot state: ' + JSON.stringify(state)); -} - -// Check if members list has been saved to disk, in which case load it -if (fs.existsSync('members.json')) { - var members_file = JSON.parse(fs.readFileSync("members.json")); - members = members_file.members; - utils.log('Loaded ' + members.length + ' members.'); -} - -startProcess(); -// Schedule to run every minute -setInterval(startProcess, 60 * 1000); - -async function startProcess() { - if(!botNames) - botNames = await utils.loadBots(); - if (config.detailed_logging) - console.log('Start process'); - // Load the settings from the config file each time so we can pick up any changes - loadConfig(); - - // Load the bot account info - steem.api.getAccounts([config.account], function (err, result) { - if (err || !result) - console.log(err, result); - else { - account = result[0]; - - // Check if there are any rewards to claim. - claimRewards(); - } - }); - - var oneMoreDay = new Date(new Date(vote_time).getTime() + (24 * 60 * 60 * 1000)); - var today = new Date(); - var passedOneDay = today >= oneMoreDay; - - if (account && !skip && !is_voting && passedOneDay) { - // Load the current voting power of the account - var vp = utils.getVotingPower(account); - - if (config.detailed_logging) - utils.log('Voting Power: ' + utils.format(vp / 100) + '% | Time until next vote: ' + utils.toTimer(utils.timeTilFullPower(vp))); - - console.log('Voting Power: ' + utils.format(vp / 100) + '% | Time until next vote: ' + utils.toTimer(utils.timeTilFullPower(vp))); - // We are at 100% voting power - time to vote! - if (vp >= 9800) { - skip = true; - processVotes(); - } - - } else if(skip) - skip = false; - else if (!account) - console.log('Loading account data...'); - else console.log('Voting... or waiting for a day to pass'); -} - -function processVotes() { - - var query = {tag: config.main_tag, limit: 100}; - - steem.api.getDiscussionsByCreated(query, function (err, result) { - if (result && !err) { - is_voting = true; - if(result.length == 0 || !result[0]) { - utils.log('No posts found for this tag: ' + config.main_tag); - last_voted++; - return; - } - var votePosts = Array(); - utils.log(result.length + ' posts to process...'); - - for(var i = 0; i < result.length; i++) { - var post = result[i]; - - // Make sure the post is less than 6.5 days - if((new Date() - new Date(post.created + 'Z')) >= (6.5 * 24 * 60 * 60 * 1000)) { - utils.log('This post is too old for a vote: ' + post.url); - continue; - } - - // Make sure the post is older than 24hs - if (new Date(post.created) >= new Date(new Date().getTime() - (config.min_hours * 60 * 60 * 1000))) { - utils.log('This post is too new for a vote: ' + post.url); - continue; - } - - // Check if the bot already voted on this post - if(post.active_votes.find(v => v.voter == 'actifit')) { - utils.log('Bot already voted on: ' + post.url); - continue; - } - - // Check if any tags on this post are blacklisted in the settings - if ((config.blacklisted_tags && config.blacklisted_tags.length > 0) || (config.whitelisted_tags && config.whitelisted_tags.length > 0) && post.json_metadata && post.json_metadata != '') { - var tags = JSON.parse(post.json_metadata).tags; - - if((config.blacklisted_tags && config.blacklisted_tags.length > 0) && tags && tags.length > 0 && tags.find(t => config.blacklisted_tags.indexOf(t) >= 0)) { - utils.log('Post contains one or more blacklisted tags. ' + post.url); - continue; - } - - if((config.whitelisted_tags && config.whitelisted_tags.length > 0) && tags && tags.length > 0 && !tags.find(t => config.whitelisted_tags.indexOf(t) >= 0)) { - utils.log('Post does not contain a whitelisted tag. ' + post.url); - continue; - } - } - - // Check if post category is main tag - if (post.category != config.main_tag) { - utils.log('Post does not match category tag. ' + post.url); - continue; - } - - // Check if this post has been flagged by any flag signal accounts - if(config.flag_signal_accounts) { - if(post.active_votes.find(function(v) { return v.percent < 0 && config.flag_signal_accounts.indexOf(v.voter) >= 0; })) { - utils.log('Post was downvoted by a flag signal account. ' + post.url); - continue; - } - } - - // Check if this post has been voted by any type of paid bot - if(botNames && config.no_paid_bots) { - if(post.active_votes.find(function(v) { return botNames.includes(v.voter); })) { - utils.log('Post was vote by a paid bot account. ' + post.url); - continue; - } - } - - // Check if account is beneficiary - var benefit = 0; - for (var x = 0; x < post.beneficiaries.length; x++) { - for (var n = 0; n < config.beneficiaries.length; n++) { - if (post.beneficiaries[x].account === config.beneficiaries[n]) - benefit ++; - } - if (benefit === config.beneficiaries.length) { - benefit = true; - break; - } - } - if (!benefit) { - utils.log('Post does not match account beneficiary. ' + post.url); - continue; - } - - try { - post.json = JSON.parse(post.json_metadata); - step_count = post.json.step_count; - if (step_count < 5000) - continue; - else if (step_count < 6000) - post.rate_multiplier = 0.2; - else if(step_count < 7000) - post.rate_multiplier = 0.35; - else if(step_count < 8000) - post.rate_multiplier = 0.5; - else if(step_count < 9000) - post.rate_multiplier = 0.65; - else if(step_count < 10000) - post.rate_multiplier = 0.8; - else - post.rate_multiplier = 1; - } catch (err) { - utils.log('Error parsing json metadata'); - console.log(err); - continue; - } - - let last_index = _.findLastIndex(votePosts, ['author', post.author]); - if (last_index != -1) { - console.log('---- User already has vote ------'); - let last_voted = votePosts[last_index]; - var last_date = moment(last_voted.created).format('D'); - var this_date = moment(post.created).format('D'); - if (last_date != this_date) { - console.log('Voting on: ' + post.url); - votePosts.push(post); - } else { - console.log('---- Last voted -----'); - console.log(new Date (last_voted.created)); - console.log('---- This voted -----'); - console.log(new Date (post.created)); - console.log('---- Moment-----'); - console.log(last_date); - console.log(this_date); - } - - } else { - console.log('Voting on: ' + post.url); - votePosts.push(post); - } - } - /*let testPost = {rate_multiplier: 0.8}; - votePosts.push(testPost);*/ - if (votePosts.length > 0) { - utils.log(votePosts.length + ' posts to vote...'); - vote_data = utils.calculateVotes(votePosts, config.vote_weight); - votePosts.sort(function(post1, post2) { - // Ascending: first age less than the previous - return post2.json.step_count - post1.json.step_count; - }); - //utils.log(vote_data.total_votes + ' total votes to divide.'); - utils.log(vote_data.power_per_vote + ' power per full vote.'); - utils.log(vote_data.power_per_vote * 0.8 + ' power per second vote.'); - utils.log(vote_data.power_per_vote * 0.65 + ' power per third vote.'); - utils.log(vote_data.power_per_vote * 0.5 + ' power per fourth vote.'); - utils.log(vote_data.power_per_vote * 0.35 + ' power per fith vote.'); - utils.log(vote_data.power_per_vote * 0.2 + ' power per lowest vote.'); - if(config.testing) - process.exit(); - else - votingProcess(votePosts, vote_data.power_per_vote); - - } else { - utils.log('No posts to vote...'); - if(!error_sent) { - errorEmail('No posts to vote...', config.report_emails); - error_sent = true; - } - } - last_voted++; - } else { - console.log(err, result); - errorEmail(err, config.report_emails); - } - }); -} - -function votingProcess(posts, power_per_vote) { - // Get the first bid in the list - sendVote(posts.pop(), 2, power_per_vote); - // If there are more bids, vote on the next one after 10 seconds - if (posts.length > 0) { - setTimeout(function () { votingProcess(posts, power_per_vote); }, 5000); - } else { - setTimeout(function () { - utils.log('======================================================='); - utils.log('Voting Complete!'); - utils.log('======================================================='); - is_voting = false; - error_sent = false; - saveState(); - //reportEmail(config.report_emails) - }, 5000); - } -} - -function sendVote(post, retries, power_per_vote) { - utils.log('Voting on: ' + post.url); - var vote_weight = Math.floor(post.rate_multiplier * power_per_vote); - if (vote_weight > 10000) - vote_weight = 10000; - post.vote_weight = vote_weight; - last_votes.push(post); - - steem.broadcast.vote(config.posting_key, account.name, post.author, post.permlink, vote_weight, function (err, result) { - if (!err && result) { - utils.log(utils.format(vote_weight / 100) + '% vote cast for: ' + post.url); - - if(config.comment_location && config.comment) - setTimeout(function () { sendComment(post.author, post.permlink, vote_weight, post.rate_multiplier, post.json.step_count); }, 10000); - } else { - utils.log(err, result); - - // Try again one time on error - if (retries < 1) - sendVote(post, retries + 1); - else { - var message = '============= Vote transaction failed two times for: ' + post.url + ' ===============' - utils.log(message); - //errorEmail(message, config.report_emails); - } - } - }); -} - -function sendComment(parentAuthor, parentPermlink, vote_weight, rate_multiplier, post_step_count) { - var content = null; - - content = fs.readFileSync(config.comment_location, "utf8"); - - // If promotion content is specified in the config then use it to comment on the upvoted post - if (content && content != '') { - - // Generate the comment permlink via steemit standard convention - var permlink = 're-' + parentAuthor.replace(/\./g, '') + '-' + parentPermlink + '-' + new Date().toISOString().replace(/-|:|\./g, '').toLowerCase(); - - var token_count = parseFloat(rate_multiplier)*100; - var milestone_txt = "level 1 milestone"; - if(token_count < 36) - milestone_txt = "level 2 milestone"; - else if(token_count < 51) - milestone_txt = "level 3 milestone"; - else if(token_count < 66) - milestone_txt = "level 4 milestone"; - else if(token_count < 81) - milestone_txt = "level 5 milestone"; - else - milestone_txt = "the top level milestone"; - - - // Replace variables in the promotion content - content = content.replace(/\{weight\}/g, utils.format(vote_weight / 100)).replace(/\{milestone\}/g, milestone_txt).replace(/\{token_count\}/g,token_count).replace(/\{step_count\}/g,post_step_count); - - // Broadcast the comment - steem.broadcast.comment(config.posting_key, parentAuthor, parentPermlink, account.name, permlink, permlink, content, '{"app":"communitybot/' + version + '"}', function (err, result) { - if (!err && result) { - utils.log('Posted comment: ' + permlink); - } else { - utils.log('Error posting comment: ' + permlink); - } - }); - } - - // Check if the bot should resteem this post - if (config.resteem) - resteem(parentAuthor, parentPermlink); -} - -function reportEmail(to) { - - var data = {}; - data.posts = last_votes; - data.total_votes = _.sumBy(last_votes, 'net_votes'); - data.total_money = _.sumBy(last_votes, 'vote_weight'); - - mail.sendWithTemplate('Report Mail', data, to, 'votes'); - last_votes = Array(); - -} - -function errorEmail(message, to) { - - mail.sendPlainMail('Info Mail', message, to) - .then(function(res, err) { - if (!err) { - console.log(res); - } else { - console.log(err); - } - }); -} - -function resteem(author, permlink) { - var json = JSON.stringify(['reblog', { - account: config.account, - author: author, - permlink: permlink - }]); - - steem.broadcast.customJson(config.posting_key, [], [config.account], 'follow', json, (err, result) => { - if (!err && result) { - utils.log('Resteemed Post: @' + author + '/' + permlink); - } else { - utils.log('Error resteeming post: @' + author + '/' + permlink); - } - }); -} - -function saveState() { - var state = { - last_trans: last_trans, - last_voted: last_voted, - vote_time: new Date() - }; - - // Save the state of the bot to disk - fs.writeFile('state.json', JSON.stringify(state), function (err) { - if (err) - utils.log(err); - }); -} - -function loadConfig() { - config = JSON.parse(fs.readFileSync("config.json")); -} - -function sendPayment(to, amount, currency, reason, retries, data) { - if(!retries) - retries = 0; - - // Make sure the recipient isn't on the no-refund list (for exchanges and things like that). - if (reason != 'forward_payment' && config.no_refund && config.no_refund.indexOf(to) >= 0) { - utils.log("Payment not sent to: @" + to + " for: " + reason + ' because they are on the no_refund list.'); - return; - } - - // Replace variables in the memo text - var memo = config.transfer_memos[reason]; - memo = memo.replace(/{amount}/g, utils.format(amount, 3) + ' ' + currency); - memo = memo.replace(/{currency}/g, currency); - memo = memo.replace(/{account}/g, config.account); - memo = memo.replace(/{to}/g, to); - memo = memo.replace(/{tag}/g, data); - - // Issue the payment. - steem.broadcast.transfer(config.active_key, config.account, to, utils.format(amount, 3) + ' ' + currency, memo, function (err, response) { - if (err) { - utils.log('Error sending payment to @' + to + ' for: ' + amount + ' ' + currency + ', Error: ' + err); - - // Try again on error - if(retries < 2) - setTimeout(function() { refund(to, amount, currency, reason, retries + 1, data) }, (Math.floor(Math.random() * 10) + 3) * 1000); - else - utils.log('============= Payment failed three times for: @' + to + ' ==============='); - } else { - utils.log('Payment of ' + amount + ' ' + currency + ' sent to @' + to + ' for reason: ' + reason); - } - }); -} - -function claimRewards() { - if (!config.auto_claim_rewards) - return; - - // Make api call only if you have actual reward - if (parseFloat(account.reward_steem_balance) > 0 || parseFloat(account.reward_sbd_balance) > 0 || parseFloat(account.reward_vesting_balance) > 0) { - steem.broadcast.claimRewardBalance(config.posting_key, config.account, account.reward_steem_balance, account.reward_sbd_balance, account.reward_vesting_balance, function (err, result) { - if (err) { - utils.log(err); - } - - if (result) { - - var rewards_message = "$$$ ==> Rewards Claim"; - if (parseFloat(account.reward_sbd_balance) > 0) { rewards_message = rewards_message + ' SBD: ' + parseFloat(account.reward_sbd_balance); } - if (parseFloat(account.reward_steem_balance) > 0) { rewards_message = rewards_message + ' STEEM: ' + parseFloat(account.reward_steem_balance); } - if (parseFloat(account.reward_vesting_balance) > 0) { rewards_message = rewards_message + ' VESTS: ' + parseFloat(account.reward_vesting_balance); } - - utils.log(rewards_message); - - // If there are liquid post rewards, withdraw them to the specified account - if (parseFloat(account.reward_sbd_balance) > 0 && config.post_rewards_withdrawal_account && config.post_rewards_withdrawal_account != '') { - - // Send liquid post rewards to the specified account - steem.broadcast.transfer(config.active_key, config.account, config.post_rewards_withdrawal_account, account.reward_sbd_balance, 'Liquid Post Rewards Withdrawal', function (err, response) { - if (err) - utils.log(err, response); - else { - utils.log('$$$ Auto withdrawal - liquid post rewards: ' + account.reward_sbd_balance + ' sent to @' + config.post_rewards_withdrawal_account); - } - }); - } - } - }); - } -} +var fs = require("fs"); +const steem = require('steem'); + +const hive = require('@hiveio/hive-js'); + + +var utils = require('./utils'); +var mail = require('./mail'); +var _ = require('lodash'); +var moment = require('moment'); +const MongoClient = require('mongodb').MongoClient; +let ObjectId = require('mongodb').ObjectId; + +const cheerio = require('cheerio') +const axios = require('axios'); +const request = require("request"); + +var account = null; +let actSteemAccount = null; +var last_trans = 0; +var members = []; +var whitelist = []; +var config = null; +var first_load = true; +var is_voting = false; +var last_voted = 0; +var vote_time; +var last_votes = Array(); +var skip = false; +var version = '0.3.4'; +var lucky_winner_id = -1; + +let topUsersAFITX = []; + +let gadgetsFetched = false; +let activeGadgets = []; + +//version of the reward system +var reward_sys_version = 'v0.2'; + +var error_sent = false; + +var steem_price = 1; // This will get overridden with actual prices if a price_feed_url is specified in settings +var sbd_price = 1; // This will get overridden with actual prices if a price_feed_url is specified in settings + +let hive_price = 1; +let hbd_price = 1; + +let finalEligNewbieList = []; //contains array of newbies eligible for extra vote in current round + +// Load the settings from the config file +loadConfig(); + +loadHivePrices(); +//kick off loading steem prices in 30 seconds +setTimeout(loadSteemPrices, 30*1000); + +//set proper nodes +steem.api.setOptions({ + url: config.active_node , + //useAppbaseApi: true +}); + +hive.api.setOptions({ + url: config.active_hive_node , + //useAppbaseApi: true +}); + +var STEEMIT_100_PERCENT = 10000; +var STEEMIT_VOTE_REGENERATION_SECONDS = (5 * 60 * 60 * 24); +var HOURS = 60 * 60; + +//keep alive +var http = require("http"); +setInterval(function() { + try{ + http.get("http://actifitvoter.herokuapp.com"); + + if (!is_voting){ + //let's also run our token exchange cleanup process + console.log('running cleanup'); + request('https://actifitbot.herokuapp.com/cancelOutdatedAfitSteemExchange', function (error, response, body) { + console.log('cleanup result'); + console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received + console.log('error: '+ error) + }); + + //let's also run the cleanup for any missed AFIT SE to Actifit wallet processes + request('https://actifitbot.herokuapp.com/confirmAFITSEBulk?bchain=STEEM', function (error, response, body) { + console.log('process any missed AFIT SE to Actifit Wallet'); + console.log(response); + //console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received + //console.log('error: '+ error) + }); + + //let's also run the cleanup for any missed AFIT SE to Actifit wallet processes + request('https://actifitbot.herokuapp.com/confirmAFITSEBulk?bchain=HIVE', function (error, response, body) { + console.log('process any missed AFIT HE to Actifit Wallet'); + console.log(response); + //console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received + //console.log('error: '+ error) + }); + + //also load prices & broadcast updates to witness nodes (STEEM & HIVE) + loadHivePrices(); + //in 30 seconds load steem prices + setTimeout(loadSteemPrices, 30*1000); + + } + + }catch(err){ + console.log('error:'+err); + } + +}, 600000); // every 10 minutes (600000) + + +let crypto = require('crypto'); + +const activity_rules = [ + [4999,0], + [5999,0.20], + [6999,0.35], + [7999,0.50], + [8999,0.65], + [9999,0.80], + [10000,1.00], + [149999,1.00], + [150000,0], +] + +const content_rules = [ + [99,0], + [399,0.20], + [799,0.35], + [1199,0.50], + [1599,0.65], + [1999,0.80], + [2000,1.00] +] + +const img_rules = [ + [0,0], + [1,0.2], + [2,0.4], + [3,0.6], + [4,0.8], + [5,1] +] + +const vid_rules = [ + [0,0], + [1,1] +] + +const upv_rules = [ + [0,0], + [10,0.2], + [20,0.4], + [30,0.6], + [50,0.8], + [100,1] +] + +const cmts_rules = [ + [0,0], + [2,0.2], + [4,0.4], + [6,0.6], + [8,0.8], + [10,1] +] + +//our pre-defined image pool +const actifit_img_urls = [ + "https://cdn.steemitimages.com/DQmXv9QWiAYiLCSr3sKxVzUJVrgin3ZZWM2CExEo3fd5GUS/sep3.png", + "https://cdn.steemitimages.com/DQmRgAoqi4vUVymaro8hXdRraNX6LHkXhMRBZxEo5vVWXDN/ACTIVITYCOUNT.png", + "https://cdn.steemitimages.com/DQmZ6ZT8VaEpaDzB16qZzK8omffbWUpEpe4BkJkMXmN3xrF/ACTIVITYTYPE.png", + "https://cdn.steemitimages.com/DQmdnh1nApZieHZ3s1fEhCALDjnzytFwo78zbAY5CLUMpoG/TRACKM.png", + "https://cdn.steemitimages.com/DQmfSsFiXem7AxWG1NCiYYPAjtT4Y7LR8FsXpfsZQe7XqPC/h1.png", + "https://cdn.steemitimages.com/DQmVqJVEWUwicFRtkEz2WYq2mDH61mQLDsrzN1yBrKLrpyZ/w1a.png", + "https://cdn.steemitimages.com/DQmPJ2Vvi3mBQXKHoy5CTG7fyLFWMG8JaAZ8y1XZFeDkRUC/bd1.png", + "https://cdn.steemitimages.com/DQmZ2Lfwg77FLaf3YpU1VPLsJvnBt1F8DG8y6t6xUAKnsYq/w1.png", + "https://cdn.steemitimages.com/DQmbbAAFy6hwwBWqtSmcSwosTyNZi9rcd6GNeugQRY9MF1h/t1.png", + "https://cdn.steemitimages.com/DQmbaoNBT5Unnjqh8JgP6TPj4mFKFnyKkLgP6eDYnnkiLkB/c1.png", + + "https://cdn.steemitimages.com/DQmQqfpSmcQtfrHAtzfBtVccXwUL9vKNgZJ2j93m8WNjizw/l5.png", + "https://cdn.steemitimages.com/DQmbWy8KzKT1UvCvznUTaFPw6wBUcyLtBT5XL9wdbB7Hfmn/l6.png", + + "https://cdn.steemitimages.com/DQmNp6YwAm2qwquALZw8PdcovDorwaBSFuxQ38TrYziGT6b/A-20.png", + "https://cdn.steemitimages.com/DQmY5UUP99u5ob3D8MA9JJW23zXLjHXHSRofSH3jLGEG1Yr/A-10.png", + "https://cdn.steemitimages.com/DQmRDW8jdYmE37tXvM6xPxuNnzNQnUJWSDnxVYyRJEHyc9H/A-14.png", + "https://cdn.steemitimages.com/DQmPscjCVBggXvJT2GaUp66vbtyxzdzyHuhnzc38WDp4Smg/A-3.png", "https://cdn.steemitimages.com/DQmVoLkmU47N4fM75HVY7se7JiMzdXhQKQUZ5fyCDwh1BrE/A-13.png", + "https://cdn.steemitimages.com/DQmcngR7AdBJio52C5stkD5C7vgsQ1yDH57Lb4J96Pys4a9/A-6.png","https://cdn.steemitimages.com/DQmdL69SXfqqKKoaEC55u3wsiMyAhcSErdK1fYjckAUyMCz/A-2.png", "https://cdn.steemitimages.com/DQmRgZTP4R6q9DfAWf9dNuqXWgvxkduxuH5QJfeyUVEqsk9/A-8.png", "https://cdn.steemitimages.com/DQmWzwdS5u4G1GheceM1bmBC3HL6zWubUGYbPkCmEcEDXrD/A-4.png", "https://cdn.steemitimages.com/DQmUVjgmJHvtbYB2APdxqNxxkZeJ2KvPeXEE7v3BpxGJkbR/A-18.png", "https://cdn.steemitimages.com/DQmdMW7LzuiKLi9vaEWsXnWGcU1oMHbF4983L16CE63dvwz/A-17.png", + "https://cdn.steemitimages.com/DQmVD3pXR4EHzYeCapMNSanTeK9wGJeJ24XYJhZSUmjJReR/A-11.png", "https://cdn.steemitimages.com/DQmY67NW9SgDEsLo2nsAw4nYcddrTjp4aHNLyogKvGuVMMH/A-9.png", "https://cdn.steemitimages.com/DQmcrdacUAEHoeiX9gNVAiiL5iydmJoPve2nXpzszNtJZPb/A-12.png", "https://cdn.steemitimages.com/DQmbP8GuFvcHUyh7bKDheDN5iz8ERPCYzMaSVoRT2R5ZYPE/A-15.png","https://cdn.steemitimages.com/DQmW1VsUNbEjTUKawau4KJQ6agf41p69teEvdGAj1TMXmuc/A-5.png", + "https://cdn.steemitimages.com/DQmeBn1PLf6a3QaXjM23EbQcaKtfDckgtGPHE4DApoUeBEJ/A-1.png", "https://cdn.steemitimages.com/DQmV7NRosGCmNLsyHGzmh4Vr1pQJuBPEy2rk3WvnEUDxDFA/A-21.png", "https://cdn.steemitimages.com/DQmdNAWWwv6MAJjiNUWRahmAqbFBPxrX8WLQvoKyVHHqih1/A-19.png","https://cdn.steemitimages.com/DQmVNqM8wQj2TnfwqSPYtfAuPHYjeBXSFekCHGZw9K3B9Gi/A-16.png", "https://cdn.steemitimages.com/DQma7nn1yV2w9iY6qXDBJUoTWkELTYxot7R9eoG1M3Tbtqn/A-7.png"]; + + + + + +var botNames; +//fetchGadgets(); +let tokensBurntLastRound = false; +const SSC = require('sscjs'); +const ssc = new SSC(config.steem_engine_rpc); + +const hsc = new SSC(config.hive_engine_rpc); + +// Initial Load of top AFITX token holders +// Top 25 will be stored in topUsersAFITX +fetchAFITXTopHolders(); + +//grab list of active gadgets +async function fetchGadgets(){ + try{ + console.log('>>>>>>>>>>>>>fetchGadgets'); + + //let fetch_gadgets_res = await axios.get(config.api_url + 'activeGadgets'); + let gadUrl = config.api_url + 'activeGadgets'; + if (config.testing){ + gadUrl = config.api_test_url + 'activeGadgets'; + } + console.log(gadUrl); + let fetch_gadgets_res = await axios.get(gadUrl); + + activeGadgets = fetch_gadgets_res.data; + gadgetsFetched = true; + //console.log(activeGadgets); + }catch(err){ + console.log(err); + } +} + + +setInterval(function(){ + try{ + if (!is_voting){ + // Load updated top AFITX token holders every 10 minutes + // Top 25 will be stored in topUsersAFITX + fetchAFITXTopHolders(); + } + }catch(err){ + console.log(err); + } +}, 1200000); // every 20 minutes (1200000) + + + +utils.log("* START - Version: " + version + " *"); + +// Connection URL +var url = config.mongo_uri; + +//check if this is a test scenario to use local DB url +if (config.testing){ + url = config.mongo_local; +} +//utils.log('db url:'+url); +var db; +var collection; + +var db_name = config.db_name; + +const collection_name = 'banned_accounts'; + +var banned_users; + +var moderator_list; + +var skippable_posts; + +var afit_steem_upvote_list; + +var cur_afit_price; + +var helping_accounts_votes = 0; + + +// Check if bot state has been saved to disk, in which case load it +if (fs.existsSync('state.json')) { + var state = JSON.parse(fs.readFileSync("state.json")); + + if (state.last_trans) + last_trans = state.last_trans; + + if (state.last_voted) + last_voted = state.last_voted; + + if (state.vote_time) + vote_time = state.vote_time; + + utils.log('Restored saved bot state: ' + JSON.stringify(state)); +} + +// Check if members list has been saved to disk, in which case load it +if (fs.existsSync('members.json')) { + var members_file = JSON.parse(fs.readFileSync("members.json")); + members = members_file.members; + utils.log('Loaded ' + members.length + ' members.'); +} + + +// Use connect method to connect to the server +MongoClient.connect(url, function(err, client) { + if(!err) { + utils.log("Connected successfully to server "+url); + + db = client.db(db_name); + + // Get the documents collection + collection = db.collection(collection_name); + + /*let test = await db.collection('afit_price').find().sort({'date': -1}).limit(1).next(); + console.log(test);*/ + + /*let gadgetId = new ObjectId("5db77cb5e279c25040134953") + let test = await db.collection('user_gadgets').findOne({ benefic: {$in: ['silvertop', '@silvertop']}, status: "active", gadget: gadgetId, }) + console.log(test);*/ + + + //testBoostData(); + //only start the process once we connected to the DB + startProcess(); + + // Load updated STEEM and SBD prices every 30 minutes + /*loadPrices(); + setInterval( function (){ + if (!is_voting){ + loadPrices() + } + }, 30 * 60 * 1000);*/ + + //updateUserTokens(); + } else { + utils.log(err, 'api'); + } + +}); + +// Schedule to run every minute +if (!config.testing){ + setInterval(startProcess, 60 * 1000); +}else{ + setTimeout(startProcess, 20 * 1000); +} + + + + +async function testBoostData(){ + await fetchGadgets(); + + let postData = []; + + let boost_res; + + /*let boost_res = await grabConsumeUserBoostByType('@mcfarhat', 'SPORTS', 'unit', {author: 'mcfarhat', permlink: 'bingo'}, true); + console.log('testBoostData 1'); + postData = boost_res.user_post_boosts; + + //check if user has a SPORTS boost as percent increments + let appendNetPercTokens = boost_res.extra_boost; + console.log(appendNetPercTokens); + console.log(postData); + + //let test = await grabConsumeUserBoostByType('@mcfarhat', 'AFIT', 'percent_reward', {author: 'mcfarhat', 'permlink': 'bingo'}, true); + boost_res = await grabConsumeUserBoostByType('@mcfarhat', 'AFIT', 'unit', {author: 'mcfarhat', permlink: 'bingo'}, true); + + postData = postData.concat(boost_res.user_post_boosts); + appendNetPercTokens = boost_res.extra_boost; + console.log('testBoostData 2');*/ + + /* + let boost_res = await grabConsumeUserBoostByType('@mcfarhat', 'AFIT', 'range', {author: 'mcfarhat', permlink: 'bingo'}, true); + + postData = postData.concat(boost_res.user_post_boosts); + + //check if user has a User Rank boost as percent increments + let appendTokens = boost_res.extra_boost; + console.log('>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<'); + //console.log(test); + //store used boosts to the post + //console.log(test.user_post_boosts); + console.log(appendTokens); + console.log(postData); + + console.log(boost_res.user_post_boosts[0].productdetails[0].benefits.boosts); + */ + + + boost_res = await grabConsumeUserBoostByType('@mcfarhat', 'APX', 'percent', {author: 'mcfarhat', permlink: 'bingo'}, true); + + postData = postData.concat(boost_res.user_post_boosts); + + //check if user has a User Rank boost as percent increments + appendTokens = boost_res.extra_boost; + console.log('>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<'); + //console.log(test); + //store used boosts to the post + //console.log(test.user_post_boosts); + console.log(appendTokens); + console.log(postData); + + console.log(boost_res.user_post_boosts[0].productdetails[0].benefits.boosts); + + /*boost_res = await grabConsumeUserBoostByType('@mcfarhat', 'SPORTS', 'percent_reward', {author: 'mcfarhat', permlink: 'bingo'}, true); + + postData = postData.concat(boost_res.user_post_boosts); + + //check if user has a User Rank boost as percent increments + appendTokens = boost_res.extra_boost; + console.log('>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<'); + //console.log(test); + //store used boosts to the post + //console.log(test.user_post_boosts); + console.log(appendTokens); + console.log(postData); + + console.log(boost_res.user_post_boosts[0].productdetails[0].benefits.boosts); + + boost_res = await grabConsumeUserBoostByType('@mcfarhat', 'User Rank', 'unit', {author: 'mcfarhat', permlink: 'bingo'}, true); + + postData = postData.concat(boost_res.user_post_boosts); + + //check if user has a User Rank boost as percent increments + appendTokens = boost_res.extra_boost; + console.log('>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<'); + //console.log(test); + //store used boosts to the post + //console.log(test.user_post_boosts); + console.log(appendTokens); + console.log(postData); + + console.log(boost_res.user_post_boosts[0].productdetails[0].benefits.boosts);*/ + + //check if user has a User Rank boost as percent increments + //console.log(test.extra_boost); +} + +//unit contains values such as SPORTS, AFIT, User Rank +//type contains values such as unit, percent +async function grabConsumeUserBoostByType(user, unit, type, post, consume){ + + //console.log(activeGadgets); + let extra_boost = 0; + user = user.replace('@',''); + let userSteem = '@' + user; + let userNamesVar = [userSteem, user]; + let user_post_boosts = []; + + //apply boosts by user + if (Array.isArray(activeGadgets) && activeGadgets.length > 0){ + let matchingGadgets = activeGadgets.filter( gadget => userNamesVar.includes (gadget.user) ); + let matchingFriendGadgets = activeGadgets.filter( gadget => userNamesVar.includes (gadget.benefic) ); + //console.log(matchingGadgets); + //console.log(matchingFriendGadgets); + if (!Array.isArray(matchingGadgets)){ + matchingGadgets = matchingFriendGadgets; + }else if (Array.isArray(matchingFriendGadgets)){ + matchingGadgets = matchingGadgets.concat(matchingFriendGadgets); + } + console.log('>>>>matchingGadgets'); + console.log(matchingGadgets); + let maxCount = matchingGadgets.length; + //go through each gadget and process its boosts + for (let i=0;i 0){ + let boosts = matchingGadgets[i].productdetails[0].benefits.boosts; + if (Array.isArray( boosts) && boosts.length > 0){ + let maxBoosts = boosts.length; + //to ensure we only consume proper matching boosts + let match = false; + + let is_benefic = false; + for (let j=0;j rwd_post.author == post.author && rwd_post.permlink == post.permlink))){ + skip = true; + } + //check if this is a benefic case and which has multiple boosts, so that we dont consume it more than once. + + if (maxBoosts > 1 && is_benefic){ + try{ + //find its index in the original gadgets array, and make sure it gets only consumed this time + let gad_index = activeGadgets.findIndex(gadget => userNamesVar.includes (gadget.benefic) ); + + if (activeGadgets[gad_index].roundConsumed){ + skipConsumption = true; + } + activeGadgets[gad_index].roundConsumed = true; + }catch(boostErr){ + console.log(boostErr); + } + } + if (!skip){ + if (!Array.isArray(gadget_match.posts_consumed)){ + gadget_match.posts_consumed = []; + } + let consumed_pst = new Object(); + consumed_pst.author = post.author; + consumed_pst.permlink = post.permlink; + //consumed_pst.benefic = post.benefic; + + gadget_match.posts_consumed.push(consumed_pst); + if (!skipConsumption){ + gadget_match.consumed += 1; + if (gadget_match.consumed >= gadget_match.span){ + gadget_match.status="consumed"; + } + } + gadget_match.last_updated = new Date(); + console.log('updating user gadget'); + console.log(gadget_match); + if (!config.testing){ + let transaction = db.collection('user_gadgets').save(gadget_match); + console.log('success inserting post data'); + } + } + } + }catch(err){ + console.log(err); + return; + } + } + } + } + } + } + console.log('user_post_boosts'); + console.log(user_post_boosts); + return {'extra_boost': extra_boost, 'user_post_boosts':user_post_boosts}; +} + + + +var votePosts; +var lastIterationCount = 0; + +let queryCount = 0; + +let properties, rewardFund, rewardBalance, recentClaims, totalSteem, totalVests, votePowerReserveRate, sbd_print_percentage; + +async function setIsVoting(running){ + try{ + is_voting = running; + let votingStatus = await db.collection('voting_status').findOne({}); + if (!votingStatus){ + votingStatus = new Object(); + } + votingStatus.is_voting = running; + if (running){ + votingStatus.voting_start = new Date(); + }else{ + votingStatus.voting_end = new Date(); + } + db.collection('voting_status').save(votingStatus); + }catch(err){ + console.log(err); + } +} + + +//handles grabbing the vote value /STEEM + function getVoteValue(voteWeight, account, currentVotingPower, steem_price) { + if (rewardBalance && recentClaims && steem_price && votePowerReserveRate) { + let voteValue = getVoteRShares(voteWeight, account, currentVotingPower * 100) + * rewardBalance / recentClaims + * steem_price; + + return voteValue; + } + } + //calculate voting value based on rshares contribution + function getVoteRShares (voteWeight, account, power) { + + let effective_vesting_shares = Math.round(getVestingShares(account) * 1000000); + let voting_power = account.voting_power; + let weight = voteWeight * 100; + let last_vote_time = new Date((account.last_vote_time) + 'Z'); + + let elapsed_seconds = (new Date() - last_vote_time) / 1000; + + let regenerated_power = Math.round((STEEMIT_100_PERCENT * elapsed_seconds) / STEEMIT_VOTE_REGENERATION_SECONDS); + + let current_power = power || Math.min(voting_power + regenerated_power, STEEMIT_100_PERCENT); + let max_vote_denom = votePowerReserveRate * STEEMIT_VOTE_REGENERATION_SECONDS / (60 * 60 * 24); + let used_power = Math.round((current_power * weight) / STEEMIT_100_PERCENT); + used_power = Math.round((used_power + max_vote_denom - 1) / max_vote_denom); + + let rshares = Math.round((effective_vesting_shares * used_power) / (STEEMIT_100_PERCENT)) + + return rshares; + } + //grab account vesting shares value + function getVestingShares(account) { + var effective_vesting_shares = parseFloat(account.vesting_shares.replace(" VESTS", "")) + + parseFloat(account.received_vesting_shares.replace(" VESTS", "")) + - parseFloat(account.delegated_vesting_shares.replace(" VESTS", "")); + return effective_vesting_shares; + } + //handles display vote value in USD + function getVoteValueUSD(voteWeight, account, currentVotingPower, sbd_price) { + let vote_value = getVoteValue(voteWeight, account, currentVotingPower, steem_price); + const steempower_value = vote_value * 0.5 + const sbd_print_percentage_half = (0.5 * sbd_print_percentage) + const sbd_value = vote_value * sbd_print_percentage_half + const steem_value = vote_value * (0.5 - sbd_print_percentage_half) + let vote_value_usd = ((sbd_value * sbd_price) + steem_value + steempower_value).toFixed(3); + return vote_value_usd + } + + +async function startProcess() { + if(!botNames) + botNames = await utils.loadBots(); + //if (config.detailed_logging) + utils.log('Start process'); + // Load the settings from the config file each time so we can pick up any changes + loadConfig(); + + + //load steem account data + + + + await steem.api.getAccounts([config.account], function (err, result) { + if (err || !result) + utils.log(err, result); + else { + actSteemAccount = result[0]; + //console.log(actSteemAccount); + } + }); + + + + /*await steem.api.setOptions({ + url: config.active_hive_node , + //useAppbaseApi: true + });*/ + + // Load hive account info + hive.api.getAccounts([config.account], function (err, result) { + if (err || !result) + utils.log(err, result); + else { + account = result[0]; + //console.log(account); + } + }); + + var oneMoreDay = new Date(new Date(vote_time).getTime() + (24 * 60 * 60 * 1000)); + var today = new Date(); + //deactivating condition of 24 hrs to pass + var passedOneDay = true;//today >= oneMoreDay; + + //utils.log('found banned users'); + //utils.log(banned_users); + + /*for (var n = 0; n < banned_users.length; n++) { + utils.log(banned_users[n].user); + //if (post.author == banned_users[n].user){ + //utils.log('User '+post.author+' is banned, skipping his post:' + post.url); + //user_banned = true; + //break; + //} + } + return;*/ + + + + //utils.updateSteemVariables(); + console.log('account'); + console.log(account!=null); + console.log('skip:'+skip); + console.log('is_voting:'+is_voting); + console.log('passedOneDay:'+passedOneDay); + + /* + + let newbieList= await fetch('http://localhost:3120/activeVerifiedNewbies/'); + console.log(newbieList); + let newbieEligListRes = await newbieList.json(); + console.log(newbieEligListRes); + let interimEligList = []; + + let votePosts = []; + votePosts.push({author: 'mcfarhat'}); + votePosts.push({author: 'mcfarhat1'}); + votePosts.push({author: 'mcfarhat2'}); + votePosts.push({author: 'mcf'}); + votePosts.push({author: 'mcfabc'}); + votePosts.push({author: 'mcf1'}); + votePosts.push({author: 'mcf2'}); + votePosts.push({author: 'mcf3'}); + votePosts.push({author: 'mcf4'}); + + + console.log(votePosts); + + for (let lpr=0;lpr user_post.author === newbieEligListRes[lpr].user); + if (matchPst){ + interimEligList.push(newbieEligListRes[lpr].user); + } + console.log(matchPst); + } + + console.log('current full eligible list'); + console.log(interimEligList); + if (interimEligList.length<=config.max_newbie_reward_count){ + //we have all our list already + finalEligNewbieList = interimEligList; + }else{ + while(finalEligNewbieList.length < config.max_newbie_reward_count){ + let r = Math.floor(Math.random() * (interimEligList.length)); //generate random number between 0 and array length + //only append the item if not already added + if (finalEligNewbieList.indexOf(interimEligList[r]) === -1){ + finalEligNewbieList.push(interimEligList[r]); + } + } + } + console.log('final selected list'); + console.log(finalEligNewbieList); + + if (finalEligNewbieList.length > 0){ + console.log('we have eligible newbies for extra rewards!'); + let entryIdx = finalEligNewbieList.indexOf('mcfabc'); + if (entryIdx !== -1){ + vote_weight = config.max_newbie_vote_pct; + console.log('Newbie user '+'mcfabc'+' eligible for extra vote. Vote weight:'+vote_weight); + finalEligNewbieList.splice(entryIdx, 1); + } + } + + console.log(finalEligNewbieList); + + return; + */ + + //BuyAndBurn(true); + + + + if (account && !skip && !is_voting && passedOneDay) { + // Load the current voting power of the account + var vp = utils.getVotingPower(account); + let vpRestart = parseFloat(config.vp_kickstart) - 125; + let vpRestartLimit = vpRestart + 2; + console.log('vpRestart:'+vpRestart); + + utils.log('Voting Power: ' + utils.format(vp) + '% | Time until next vote: ' + utils.toTimer(utils.timeTilFullPower(vp*100))); + + + //disabling buying and burning tokens + /*console.log(config.vpTokenBurn); + if (vp >= config.vpTokenBurn/100 && vp < (config.vpTokenBurn+10)/100){ + console.log('Buy AFIT & Burn em Case'); + if (!tokensBurntLastRound){ + BuyAndBurn(false); + tokensBurntLastRound = true; + } + }else{ + tokensBurntLastRound = false; + }*/ + + + if (vp >= vpRestart/100 && vp < vpRestartLimit/100) { + //restart server to avoid voting round breakdown + //https://devcenter.heroku.com/articles/platform-api-reference + console.log('contacting heroku server'); + + request.post( + { + url: 'https://api.heroku.com/apps/' + config.heroku_app_id + '/dynos/' + config.heroku_app_dyno + '/actions/stop', + //url: 'https://api.heroku.com/apps/' + config.heroku_app_id + '/dynos', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/vnd.heroku+json; version=3', + 'Authorization': 'Bearer ' + config.heroku_app_token + } + }, + function(error, response, body) { + // Do stuff + console.log(response); + console.log(body); + } + ); + ///////////////// + } + + // We are at voting power kick start - time to vote! + //utils.log(vp >= parseFloat(config.vp_kickstart)/100); + if (vp >= parseFloat(config.vp_kickstart)/100 || config.testing) { + + // Check if there are any rewards to claim before voting + if (!config.testing){ + await claimRewards(); + } + + //fetch gadget assignments + let fet_res = await fetchGadgets(); + + //console.log(activeGadgets); + + //reset number of helping votes case + helping_accounts_votes = 0; + + // Load Steem global variables + + properties = await hive.api.getDynamicGlobalPropertiesAsync(); + //grab reward fund data + rewardFund = await hive.api.getRewardFundAsync("post"); + rewardBalance = parseFloat(rewardFund.reward_balance.replace(" STEEM", "").replace(" HIVE", "")); + recentClaims = rewardFund.recent_claims; + + totalSteem = Number(properties.total_vesting_fund_steem.split(' ')[0]); + totalVests = Number(properties.total_vesting_shares.split(' ')[0]); + + votePowerReserveRate = properties.vote_power_reserve_rate; + sbd_print_percentage = properties.sbd_print_rate / 10000; + + utils.log('lets vote'); + skip = true; + + utils.log('fetch banned users list'); + //grab banned user list before rewarding + banned_users = await db.collection('banned_accounts').find({ban_status:"active"}).toArray(); + + //grab list of moderators + var moderator_api_url = config.api_url+'moderators'; + var moderator_info = await axios.get(moderator_api_url); + utils.log(moderator_info.data); + var moderator_array = moderator_info.data; + moderator_list = []; + for (var mod_it=0;mod_itconfig.account_claim_rc_min){ + //if we reached min threshold, claim more spots for discounted accounts + utils.claimDiscountedAccount(); + } + }, function(err) { + utils.log("Error fetching RC"); + utils.log(err); + }); + } + + } else if(skip) + skip = false; + else if (!account) + utils.log('Loading account data...'); + else utils.log('Voting... or waiting for a day to pass'); +} + +function BuyAndBurn(test){ + console.log('init'); + //fetch sell book + ssc.find('market', 'sellBook', {symbol: 'AFIT'}, 10, 0, [{ index: 'priceDec', descending: false }], (err, result) => { + console.log(result); + //fetch enough sell orders to place our own buy order + let totalSteemSold = 0; + const target = config.targetTokenBurnSteem; + let targetPrice = 0; + for (let i=0, max=result.length;i= target){ + targetPrice = result[i].price; + break; + } + } + console.log('targetPrice:'+targetPrice); + //place order for buying tokens + //calculate needed AFIT quantity to match target + let quantity = Math.ceil(target / targetPrice); + console.log('AFIT to buy:'+quantity); + if (test){ + quantity = 0.1; + } + let json = "{\"contractName\":\"market\",\"contractAction\":\"buy\",\"contractPayload\":{\"symbol\":\"AFIT\",\"quantity\":\"" + quantity + "\",\"price\":\"" + targetPrice + "\"}}"; + + steem.broadcast.customJson(config.active_key, [config.account], [], 'ssc-mainnet1', json, (err, result) => { + if (!err && result) { + console.log('success buyin'); + console.log(result); + + json = "{\"contractName\":\"tokens\",\"contractAction\":\"transfer\",\"contractPayload\":{\"symbol\":\"AFIT\",\"to\":\"null\",\"quantity\":\"" + quantity + "\",\"memo\":\"\"}}"; + //burn those tokens + steem.broadcast.customJson(config.active_key, [config.account], [], 'ssc-mainnet1', json, (err, result) => { + if (!err && result) { + console.log('success burnin'); + console.log(result); + } else { + console.log('err'); + console.log(err); + } + }); + + } else { + console.log('err'); + console.log(err); + } + }); + + + }); +} + +function setSteemPrice(json){ + steem_price = parseFloat(json.steem.usd); + console.log('STEEM price:'+steem_price) + //witness deactivated. No further need to broadcast + /*if (!config.testing){ + broadcastFeed('STEEM') + }*/ +} + +function setSbdPrice(json){ + sbd_price = parseFloat(json['steem-dollars'].usd); + console.log('SBD price:'+sbd_price) +} + +function setHivePrice(json){ + hive_price = parseFloat(json.hive.usd); + console.log('HIVE price:'+hive_price) + if (!config.testing){ + broadcastFeed('HIVE') + } +} + +function setHbdPrice(json){ + hbd_price = parseFloat(json['hive_dollar'].usd); + console.log('HBD price:'+hbd_price) +} + +function loadHivePrices() { + fetch('https://api.coingecko.com/api/v3/simple/price?ids=hive&vs_currencies=usd').then( + res => {res.json().then(json => setHivePrice(json)).catch(e => console.log('Error loading STEEM price: ' + e)) + }).catch(e => console.log('Error loading HIVE price: ' + e)) + + //grab SBD price + fetch('https://api.coingecko.com/api/v3/simple/price?ids=hive_dollar&vs_currencies=usd').then( + res => {res.json().then(json => setHbdPrice(json)).catch(e => console.log('Error loading SBD price: ' + e)) + }).catch(e => console.log('Error loading HBD price: ' + e)) +} + +function loadSteemPrices() { + fetch('https://api.coingecko.com/api/v3/simple/price?ids=steem&vs_currencies=usd').then( + res => {res.json().then(json => setSteemPrice(json)).catch(e => console.log('Error loading STEEM price: ' + e)) + }).catch(e => console.log('Error loading STEEM price: ' + e)) + + //grab SBD price + fetch('https://api.coingecko.com/api/v3/simple/price?ids=steem-dollars&vs_currencies=usd').then( + res => {res.json().then(json => setSbdPrice(json)).catch(e => console.log('Error loading SBD price: ' + e)) + }).catch(e => console.log('Error loading SBD price: ' + e)) + + /* + if(config.price_source == 'coinmarketcap') { + // Load the price feed data + request.get('https://api.coinmarketcap.com/v1/ticker/steem/', function (e, r, data) { + try { + steem_price = parseFloat(JSON.parse(data)[0].price_usd); + + utils.log("Loaded STEEM price: " + steem_price); + } catch (err) { + utils.log('Error loading STEEM price: ' + err); + } + }); + + // Load the price feed data + request.get('https://api.coinmarketcap.com/v1/ticker/steem-dollars/', function (e, r, data) { + try { + sbd_price = parseFloat(JSON.parse(data)[0].price_usd); + + utils.log("Loaded SBD price: " + sbd_price); + } catch (err) { + utils.log('Error loading SBD price: ' + err); + } + }); + } else if (config.price_source && config.price_source.startsWith('http')) { + request.get(config.price_source, function (e, r, data) { + try { + sbd_price = parseFloat(JSON.parse(data).sbd_price); + steem_price = parseFloat(JSON.parse(data).steem_price); + + utils.log("Loaded STEEM price: " + steem_price); + utils.log("Loaded SBD price: " + sbd_price); + } catch (err) { + utils.log('Error loading STEEM/SBD prices: ' + err); + } + }); + } else { + // Load STEEM price in BTC from bittrex and convert that to USD using BTC price in coinmarketcap + request.get('https://api.coinmarketcap.com/v1/ticker/bitcoin/', function (e, r, data) { + request.get('https://bittrex.com/api/v1.1/public/getticker?market=BTC-STEEM', function (e, r, btc_data) { + try { + steem_price = parseFloat(JSON.parse(data)[0].price_usd) * parseFloat(JSON.parse(btc_data).result.Last); + utils.log('Loaded STEEM Price from Bittrex: ' + steem_price); + } catch (err) { + utils.log('Error loading STEEM price from Bittrex: ' + err); + } + }); + + request.get('https://bittrex.com/api/v1.1/public/getticker?market=BTC-SBD', function (e, r, btc_data) { + try { + sbd_price = parseFloat(JSON.parse(data)[0].price_usd) * parseFloat(JSON.parse(btc_data).result.Last); + utils.log('Loaded SBD Price from Bittrex: ' + sbd_price); + } catch (err) { + utils.log('Error loading SBD price from Bittrex: ' + err); + } + }); + }); + } + + */ +} + + +//handles sending out price feed for witness nodes +function broadcastFeed (type) { + //STEEM witness + let peg_multi = config.peg_multi ? config.peg_multi : 1; + let price_val = steem_price; + let pegged_cur = ' SBD'; + let origType = type; + if (type == 'HIVE'){ + price_val = hive_price; + //below two lines are hacks since steem-js is not yet accepting HIVE and HBD + //pegged_cur = ' HBD'; + type = 'STEEM'; + /*steem.api.setOptions({ + url: config.active_hive_node , + //useAppbaseApi: true + });*/ + }else{ + /*steem.api.setOptions({ + url: config.active_node , + //useAppbaseApi: true + });*/ + } + let exchange_rate = { base: price_val.toFixed(3) + pegged_cur, quote: (1 / peg_multi).toFixed(3) + ' ' + type }; + utils.log('Broadcasting ' + origType + ' feed_publish transaction: ' + JSON.stringify(exchange_rate)); + hive.broadcast.feedPublish(config.active_key, config.account, exchange_rate, function (err, result) { + if (result && !err) { + console.log(result); + utils.log('Broadcast successful!'); + } else { + utils.log('Error broadcasting feed_publish transaction: ' + err); + + /*if (retries == 5) + failover(); + + if (retries < 2) + setTimeout(function () { publishFeed(price, retries + 1); }, 10 * 1000);*/ + } + }); +} + + + +//var post_scores = []; +function processVotes(query, subsequent) { + + utils.log('processVotes'); + + //fetch top AFITX holders as of current + //top 25 will be stored in topUsersAFITX + //fetchAFITXBal(0); + + /* + steem.api.setOptions({ + url: config.active_hive_node , + //useAppbaseApi: true +});*/ + + hive.api.getDiscussionsByCreated(query, async function (err, result) { + //track how many queries were ran + queryCount += 1; + if (result && !err) { + //is_voting = true; + setIsVoting(true); + + utils.log(result.length + ' posts to process...'); + + //initialize inserting posts to db + + var bulk = db.collection('posts').initializeUnorderedBulkOp(); + + //connect to the token_transactions table to start rewarding + var bulk_transactions = db.collection('token_transactions').initializeUnorderedBulkOp(); + + var bulk_posts_skip = db.collection('posts_to_skip').initializeUnorderedBulkOp(); + + let proceed_bulk = false; + let proceed_bulk_transactions = false; + let proceed_bulk_posts_skip = false; + + for(var i = 0; i < result.length; i++) { + + var post = result[i]; + if (config.testing && i == 0){ + console.log('switch author'); + console.log(post.author); + //post.author = 'mcfarhat'; + //post.permlink = 'actifit-witness-vote-application-msp'; + } + //if this is a subsequent call, we need to skip first post + if (subsequent && i==0){ + // utils.log('skip post:'+post.title); + //continue to next element + continue; + } + + //if this is the last post, save it to skip it in next iteration + if (i == result.length - 1){ + utils.log('storing last post iteration: ' + post.url); + //update query element to include the most recent post for a starting point of the next iteration + query['start_permlink'] = post.permlink; + query['start_author'] = post.author; + } + + if (!config.testing){ + // Make sure the post is older than config time + if (new Date(post.created) >= new Date(new Date().getTime() - (config.min_hours * 60 * 60 * 1000))) { + utils.log('This post is too new for a vote: ' + post.url); + continue; + } + } + + // Check if the bot already voted on this post + if (post.active_votes.find(v => v.voter == 'actifit')) { + utils.log('Bot already voted on: ' + post.url); + continue; + } + + + //post.json_metadata = JSON.parse(body); + + // Check if any tags on this post are blacklisted in the settings + if ((config.blacklisted_tags && config.blacklisted_tags.length > 0) || (config.whitelisted_tags && config.whitelisted_tags.length > 0) && post.json_metadata && post.json_metadata != '') { + var tags = JSON.parse(post.json_metadata).tags; + + if((config.blacklisted_tags && config.blacklisted_tags.length > 0) && tags && tags.length > 0 && tags.find(t => config.blacklisted_tags.indexOf(t) >= 0)) { + utils.log('Post contains one or more blacklisted tags. ' + post.url); + continue; + } + + if((config.whitelisted_tags && config.whitelisted_tags.length > 0) && tags && tags.length > 0 && !tags.find(t => config.whitelisted_tags.indexOf(t) >= 0)) { + utils.log('Post does not contain a whitelisted tag. ' + post.url); + continue; + } + } + + // Check if post category is main tag + /*if (post.category != config.main_tag) { + utils.log('Post does not match category tag. ' + post.url); + continue; + }*/ + + // Check if this post has been flagged by any flag signal accounts + if(config.flag_signal_accounts) { + if(post.active_votes.find(function(v) { return v.percent < 0 && config.flag_signal_accounts.indexOf(v.voter) >= 0; })) { + utils.log('Post was downvoted by a flag signal account. ' + post.url); + continue; + } + } + + // Check if this post has been voted by any type of paid bot + if(botNames && config.no_paid_bots) { + if(post.active_votes.find(function(v) { return botNames.includes(v.voter); })) { + utils.log('Post was vote by a paid bot account. ' + post.url); + continue; + } + } + + /* + let referrer_reward_acct = ''; + let reward_pct = 0; + let referrer_reward_amt = 0; + let activity_afit_reward = 20; + for (var x = 0; x < post.beneficiaries.length; x++) { + let testAccount = post.beneficiaries[x].account; + if (testAccount != config.beneficiaries[0] + && testAccount != config.beneficiaries[1] + && testAccount != config.full_pay_benef_account){ + referrer_reward_acct = testAccount; + reward_pct = parseInt(post.beneficiaries[x].weight)/100; + referrer_reward_amt = reward_pct * activity_afit_reward; + activity_afit_reward = activity_afit_reward * (100-reward_pct); + break; + } + } + if (referrer_reward_acct){ + console.log(referrer_reward_acct); + console.log(reward_pct); + console.log(referrer_reward_amt); + console.log(activity_afit_reward); + return; + } + */ + + // Check if account is beneficiary + var benefit = 0; + for (var x = 0; x < post.beneficiaries.length; x++) { + for (var n = 0; n < config.beneficiaries.length; n++) { + if (post.beneficiaries[x].account === config.beneficiaries[n]) + benefit ++; + } + if (benefit === config.beneficiaries.length) { + benefit = true; + break; + } + } + if (!benefit) { + utils.log('Post does not match account beneficiary. ' + post.url); + continue; + } + + //check if user is banned + var user_banned = false; + for (var n = 0; n < banned_users.length; n++) { + if (post.author == banned_users[n].user){ + utils.log('User '+post.author+' is banned, skipping his post:' + post.url); + user_banned = true; + break; + } + } + if (user_banned) continue; + + //skip any posts that are more than max days old + if((new Date() - new Date(post.created + 'Z')) >= (config.max_days * 24 * 60 * 60 * 1000)) { + continue; + } + + //skip any posts that are flagged as skippable in prior iterations + + var post_skippable = false; + for (var n = 0; n < skippable_posts.length; n++) { + if (post.author == skippable_posts[n].author && post.permlink == skippable_posts[n].permlink){ + utils.log('>>>>>>>Post by '+post.author+' is skippable, move ahead:' + post.url); + post_skippable = true; + break; + } + } + if (post_skippable) continue; + + try { + + console.log('parsing data by post '+post.url); + + post.json = JSON.parse(post.json_metadata); + + //we need to fetch the proper json_metadata from our own DB to ensure those have not been changed + + if (!config.testing){ + + let ver_url = config.api_url + "fetchVerifiedPost"; + let critical_fields = ['step_count', 'actiCrVal', 'actifitUserID', 'activityDate']; + + let incons_detected = false; + let incons_field = ''; + + try{ + var verf_res = await axios.get(ver_url, { + params:{ + author: post.author, + permlink: post.permlink + } + }); + + + //let's compare mission critical data to find if manipulation was done + let auth_meta = verf_res.data.json_metadata; + //console.log(auth_meta); + //if either stored or current metadata is non-empty we need to investigate further + if (auth_meta != '' || post.json_metadata != ''){ + //check all critical values + critical_fields.some(function(element) { + //initialize incons field in case we find a match (or lack of) + incons_field = element; + let stored_meta = eval("auth_meta."+element); + let new_meta = eval("post.json."+element); + /*console.log('stored_meta'); + console.log(stored_meta); + console.log('new_meta'); + console.log(new_meta);*/ + //if old data is not empty + if (typeof stored_meta != 'undefined' && stored_meta != ''){ + if (stored_meta instanceof Array ){ + if (new_meta instanceof Array){ + //our arrays are single valued, compare first entry + if (stored_meta[0] != new_meta[0]){ + //different value, manipulation + incons_detected = true; + return true; + } + }else{ + //different object types, manipulation + incons_detected = true; + return true; + } + }else{ + if (stored_meta != new_meta){ + //different value, manipulation + incons_detected = true; + return true; + } + } + }else{ + //original data is empty, need to check if new data is not + if (typeof new_meta != 'undefined' && new_meta != ''){ + incons_detected = true; + return true; + } + } + }); + } + }catch(verf_err){ + console.log('error finding matching post on DB'); + console.dir(verf_err); + } + //console.log('data inconsistency: ' + incons_detected); + //check if we found metadata issue + if (incons_detected){ + console.log('***********************'); + console.log('***********************'); + console.log('***********************'); + console.log('***********************'); + console.log('***********************'); + console.log('problematic field:' + incons_field); + console.log('***********************'); + console.log('***********************'); + console.log('***********************'); + console.log('***********************'); + console.log('***********************'); + //we've got a problem, skip this post/guy. We might want to report too. + continue; + } + + + + //check if the post has an encryption key val, and ensure it is the proper one + if (post.json.actiCrVal){ + var txt_to_encr = post.author + post.permlink + post.json.step_count ; + var cipher = crypto.createCipher(config.encr_mode, config.encr_key); + let encr_txt = cipher.update(txt_to_encr, 'utf8', 'hex'); + encr_txt += cipher.final('hex'); + //test the result to the post's relevant data + if (post.json.actiCrVal != encr_txt){ + //wrong, skip post + utils.log('post has incorrect actiCrVal'); + continue; + } + //utils.log('post is valid'); + }else{ + utils.log('post does not contain actiCrVal'); + continue; + } + + } + + //console.log('still here'); + //moving this section before the actual token rewards + + //due to the difference in server times, a user's post might have same date created. + //to avoid this issue, we will accept 2 posts for every user + //so we will check if 2 posts are already accumulated for the user, and if so reject the third + + let last_index = _.findLastIndex(votePosts, ['author', post.author]); + let first_index = _.findIndex(votePosts, ['author', post.author]); + let skip_date_diff = false; + + //if both posts have the same date using new json metadata format, definitely bail out + if (last_index != -1){ + utils.log('---- User has 2 posts, lets check if they have same target date ------'); + let last_voted = votePosts[last_index]; + let json_meta_vals = JSON.parse(last_voted.json_metadata); + if ((typeof json_meta_vals.activityDate != 'undefined' && json_meta_vals.activityDate != '' && json_meta_vals.activityDate != 'undefined') && + (typeof post.json.activityDate != 'undefined' && post.json.activityDate != '' && post.json.activityDate != 'undefined')){ + //both posts have values and particularly same value + if (json_meta_vals.activityDate.length == post.json.activityDate.length + && json_meta_vals.activityDate.length > 0 + && json_meta_vals.activityDate[0] == post.json.activityDate[0]){ + utils.log('same target date with value '+ json_meta_vals.activityDate + ' ...skipping'); + //skip new post + skip_date_diff = true + }else{ + utils.log('posts okay different dates'); + } + }else{ + if (first_index!=last_index) { + utils.log('---- User already has more than 2 posts in 24 hours ------'); + var last_date = moment(last_voted.created).format('D'); + let first_voted = votePosts[first_index]; + var first_date = moment(first_voted.created).format('D'); + var this_date = moment(post.created).format('D'); + //if all 3 dates match, skip it + if ((last_date == this_date) && (first_date == this_date)) { + utils.log('---- Last voted -----'); + utils.log(new Date (last_voted.created)); + utils.log('---- First voted -----'); + utils.log(new Date (first_voted.created)); + utils.log('---- This voted -----'); + utils.log(new Date (post.created)); + utils.log('---- Moment-----'); + utils.log(last_date); + utils.log(first_date); + utils.log(this_date); + + //skip new post + skip_date_diff = true; + } + }else{ + utils.log('reject a post if a prior one exists that is less than 6 hours away'); + utils.log(post.author+post.url); + //adding condition to reject a post if a prior one exists that is less than 6 hours away + //utils.log(last_voted.author+last_voted.url); + var last_date = moment(last_voted.created).toDate(); + var this_date = moment(post.created).toDate(); + //check the hours difference + var hours_diff = Math.abs(this_date - last_date) / 36e5; + if (hours_diff'+post.body+''); + + //grab text without HTML, and remove extra spacing + var pure_text = $('.actifit_container').text().replace(/\s+/g,' '); + + //calculate content score + post.content_score = utils.calcScore(content_rules, config.content_factor, pure_text.length); + + /******************* media criteria *********************/ + + + //grab proper images, skipping our default images + + var new_imgs = 0; + + var recorded_imgs = []; + //go through each image from the content and check if it matches one of our existing images + $('img').each(function(i, elem) { + //if this image is not part of ours, add it + if (!actifit_img_urls.includes($(this).attr('src'))){ + new_imgs += 1; + recorded_imgs.push($(this).attr('src')); + //utils.log($(this).attr('src')); + } + }); + + //grab listing of recorded images as part of json + var json_img_list = post.json.image; + + //utils.log(json_img_list); + + //try to see if some images were not captured by our approach for HTML content, and grab them from json meta + if (json_img_list.length>0){ + for (let img_entry of json_img_list) { + //if this image is not part of ours, add it + if (!actifit_img_urls.includes(img_entry) && !recorded_imgs.includes(img_entry)){ + new_imgs += 1; + recorded_imgs.push(img_entry); + } + }; + } + + //utils.log('2>>>new_imgs:'+new_imgs); + + //utils.log('>>>> unique images:'+new_imgs); + //calculate img score + post.media_score = utils.calcScore(img_rules, config.media_factor, new_imgs); + + /******************* upvote criteria *********************/ + + //calculate upvote score relying on positive votes only + post.upvote_score = utils.calcScore(upv_rules, config.upvotes_factor, post.net_votes); + //utils.log('upvotes:'+post.net_votes); + + /***************** moderator upvote factor ******************/ + + //check if a moderator upvoted the post to give it better reward + //we need to skip self-votes reward to avoid extra rewarding mods for self-votes + post.moderator_score = 0; + post.active_votes.some(function(vote){ + if (moderator_list.includes(vote.voter) && vote.voter != post.author){ + post.moderator_score = parseInt(config.moderator_upvote_factor); + //utils.log('found moderator upvote'+vote.voter); + return true; + } + }); + + //utils.log(post.moderator_score); + + /******************* comments criteria *********************/ + var matching_comment_count = 0; + //if (!config.testing){ + let comments = await hive.api.getContentRepliesAsync(post.author, post.permlink); + + for(var cmt_it = 0; cmt_it < comments.length; cmt_it++) { + //utils.log('>>>>>>'+comments[cmt_it].body); + const $ = cheerio.load('
'+comments[cmt_it].body+'
'); + var comment_pure = $('.comment_container').text().replace(/\s+/g,' '); + //utils.log(comment_pure); + if (comment_pure.length > 50){ + matching_comment_count += 1; + } + + //check if the comment is made by a moderator, if it is we need to reward the moderator + //we need to skip own comment reward to avoid extra rewarding mods + if (moderator_list.includes(comments[cmt_it].author) && comments[cmt_it].author != post.author){ + let comment_transaction = { + user: comments[cmt_it].author, + reward_activity: 'Moderator Comment', + token_count: parseInt(config.moderator_comment_reward), + url: post.url, + comment_url: comments[cmt_it].url, + date: new Date(comments[cmt_it].created) + } + bulk_transactions.find( + { + user: comment_transaction.user, + reward_activity: comment_transaction.reward_activity, + url: comment_transaction.url, + comment_url: comment_transaction.comment_url + }).upsert().replaceOne(comment_transaction); + + proceed_bulk_transactions = true; + utils.log('found comment>>>>'); + utils.log(comment_transaction); + } + } + //} + //utils.log("comments:"+matching_comment_count); + //calculate comment score + post.comment_score = utils.calcScore(cmts_rules, config.comments_factor, matching_comment_count); + + /******************* user rank criteria *********************/ + //var request = require('request'); + var rank_api_url = config.api_url+'getRank/'+post.author; + var user_rank_info = await axios.get(rank_api_url); + //utils.log(user_rank_info.user_rank); + post.user_rank = parseFloat(user_rank_info.data.user_rank); + + console.log('old user rank:'+post.user_rank); + + let boost_res = await grabConsumeUserBoostByType(post.author, 'User Rank', 'percent', post, true); + //store used boosts to the post + post.user_post_boosts = boost_res.user_post_boosts; + + //check if user has a User Rank boost as percent increments + let appendPercRank = boost_res.extra_boost; + + //append as percentage + post.user_rank += appendPercRank * post.user_rank / 100; + post.boost_user_rank_percent = appendPercRank; + + console.log('new user rank:'+post.user_rank); + + boost_res = await grabConsumeUserBoostByType(post.author, 'User Rank', 'unit', post, true); + post.user_post_boosts = post.user_post_boosts.concat(boost_res.user_post_boosts); + + //check if user has a User Rank boost as percent increments + let appendUnitRank = boost_res.extra_boost; + + //append as percentage + post.user_rank += appendUnitRank; + post.boost_user_rank_unit = appendUnitRank; + + console.log('new user rank:'+post.user_rank); + + //calculate user rank score relying on positive votes only + post.user_rank_score = parseFloat(user_rank_info.data.user_rank)*parseInt(config.rank_factor)/100; + //utils.log('rank'+post.user_rank_score); + + + //calculate total post score + post.post_score = post.activity_score + post.content_score + post.media_score + post.upvote_score + post.comment_score + post.moderator_score + post.user_rank_score; + + console.log('old post score:'+post.post_score); + + post.afit_pre_boost = post.post_score; + + //check if user has an AFIT boost as percent increments + boost_res = await grabConsumeUserBoostByType(post.author, 'AFIT', 'percent_reward', post, true); + + post.user_post_boosts = post.user_post_boosts.concat(boost_res.user_post_boosts); + + //check if user has a User Rank boost as percent increments + let appendPercTokens = boost_res.extra_boost; + + //append as percentage + post.post_score += appendPercTokens * post.post_score / 100; + post.boost_afit_percent_reward = appendPercTokens; + + console.log('new post score:'+post.post_score); + + //check if user has an AFIT boost as unit entries + + boost_res = await grabConsumeUserBoostByType(post.author, 'AFIT', 'unit', post, true); + + post.user_post_boosts = post.user_post_boosts.concat(boost_res.user_post_boosts); + + //check if user has a User Rank boost as percent increments + let appendTokens = boost_res.extra_boost; + + //append tokens + post.post_score += appendTokens; + post.boost_afit_units = appendTokens; + + console.log('new post score:'+post.post_score); + + //check if user has an AFIT boost as range entries + + boost_res = await grabConsumeUserBoostByType(post.author, 'AFIT', 'range', post, true); + + post.user_post_boosts = post.user_post_boosts.concat(boost_res.user_post_boosts); + + //check if user has a User Rank boost as percent increments + appendTokens = boost_res.extra_boost; + + //append tokens + post.post_score += appendTokens; + post.boost_afit_units = appendTokens; + + console.log('new post score:'+post.post_score); + + + //rate multiplier to allow assigning proper steem upvote value per each post according to its post_score/afit payout + post.rate_multiplier = post.post_score / 100; + //post_scores.push([post.url,post.post_score]); + //utils.log(post); + + } catch (err) { + utils.log('Error parsing json metadata'); + utils.log(err); + continue; + } + + //utils.log('Voting on: ' + post.url); + votePosts.push(post); + + try{ + utils.log('going through selected post '+post.url); + //insert post if not inserted before + bulk.find( { permlink: post.permlink } ).upsert().replaceOne( + post + ); + proceed_bulk = true; + //post token rewards DB transaction + + //by default the reward owner is the author + var reward_user = post.author; + var activity_type = 'Post'; + var note = ''; + var result; + //if we find this is a charity run, let's switch it to the actual charity name + if (typeof post.json.charity != 'undefined' && post.json.charity != '' && post.json.charity != 'undefined'){ + reward_user = post.json.charity[0]; + activity_type = 'Charity Post'; + note = 'Charity donation via activity by user '+post.author; + } + let activity_afit_reward = post.post_score; + //if this is a sponsored athlete, give them special reward + if (config.sponsored_athletes.includes(reward_user)){ + activity_afit_reward = config.sponsored_athlete_afit_reward; + } + + //check if the post has other beneficiaries (as a result of referral) so as to give them portion of AFIT rewards + let referrer_reward_acct = ''; + let reward_pct = 0; + let referrer_reward_amt = 0; + + for (var x = 0; x < post.beneficiaries.length; x++) { + let testAccount = post.beneficiaries[x].account; + if (testAccount != config.beneficiaries[0] + && testAccount != config.beneficiaries[1] + && testAccount != config.full_pay_benef_account){ + referrer_reward_acct = testAccount; + reward_pct = parseInt(post.beneficiaries[x].weight)/100; + referrer_reward_amt = parseFloat((reward_pct * activity_afit_reward / 100).toFixed(4)); + activity_afit_reward = parseFloat((activity_afit_reward * (100-reward_pct) / 100).toFixed(4)); + break; + } + } + + let post_transaction = { + user: reward_user, + reward_activity: activity_type, + token_count: activity_afit_reward, + url: post.url, + date: new Date(post.created), + note: note, + reward_system: reward_sys_version + } + //also in case of charity, we need to append the actual user + if (typeof post.json.charity != 'undefined' && post.json.charity != '' && post.json.charity != 'undefined'){ + post_transaction['giver'] = post.author; + } + + bulk_transactions.find( + { + user: post_transaction.user, + reward_activity: post_transaction.reward_activity, + url: post_transaction.url + }).upsert().replaceOne(post_transaction); + proceed_bulk_transactions = true; + + //reward back to referrer + try{ + if (referrer_reward_acct){ + note = "Referral Reward Share From User Activity Report" + let ref_trans = { + user: referrer_reward_acct, + reward_activity: 'Referral Beneficiary', + token_count: referrer_reward_amt, + referral_percent: reward_pct, + post_author: post.author, + url: post.url, + date: new Date(post.created), + note: note, + reward_system: reward_sys_version + } + + //we also need to insert another transaction to capture the actual activity/reward by the user + bulk_transactions.find( + { + user: ref_trans.user, + reward_activity: ref_trans.reward_activity, + post_author: ref_trans.post_author, + url: ref_trans.url + }).upsert().replaceOne(ref_trans); + proceed_bulk_transactions = true; + } + }catch(ref_benef_exc){ + console.log(ref_benef_exc); + } + + //the proper transaction without reward + if (typeof post.json.charity != 'undefined' && post.json.charity != '' && post.json.charity != 'undefined'){ + note = "Charity donation reference post transaction without rewards" + let charity_trans = { + user: post.author, + reward_activity: 'Post', + token_count: 0, + url: post.url, + date: new Date(post.created), + note: note, + charity: post.json.charity, + reward_system: reward_sys_version + } + + //we also need to insert another transaction to capture the actual activity/reward by the user + bulk_transactions.find( + { + user: charity_trans.user, + reward_activity: charity_trans.reward_activity, + url: charity_trans.url + }).upsert().replaceOne(charity_trans); + proceed_bulk_transactions = true; + } + + //reward upvoters + //make sure we already have a positive rshares + //switching to net_rshares as the older vote_rshares is deprecated + var total_post_upv_shares = parseInt(post.net_rshares); + //utils.log('total_post_upv_shares'+total_post_upv_shares); + if (total_post_upv_shares>0){ + + //calculate max token payment based upon post pending payout + var max_afits = Math.min(parseFloat(post.pending_payout_value) * parseFloat(config.per_post_alloc_afits), parseFloat(config.per_post_alloc_afits)); + //utils.log('max afits '+max_afits); + + //utils.log(post.active_votes); + post.active_votes.forEach(async vote => { + + //grab user's contribution to the upvote pool + var upv_tokens = parseInt(vote.rshares); + + //skip votes of banned users + var user_banned = false; + for (var n = 0; n < banned_users.length; n++) { + if (vote.voter == banned_users[n].user){ + utils.log('User '+vote.voter+' is banned, skipping his vote on post:' + post.url); + user_banned = true; + break; + } + } + + //skip self vote from rewards and make sure this is a positive upvote + if (post.author != vote.voter && upv_tokens>0 && !user_banned){ + //calculate the percentage of the user's contribution, and allocate him his AFIT tokens share + var voter_tokens = upv_tokens / total_post_upv_shares * max_afits; + //console.log(voter_tokens); + voter_tokens = parseFloat(voter_tokens.toFixed(3)); + let used_date = vote.time; + if (used_date == undefined || used_date == ''){ + used_date = post.created; + } + let vote_transaction = { + user: vote.voter, + reward_activity: 'Post Vote', + token_count: voter_tokens, + url: post.url, + date: new Date(used_date)//vote.time) + } + bulk_transactions.find( + { + user: vote_transaction.user, + reward_activity: vote_transaction.reward_activity, + url: vote_transaction.url + }).upsert().replaceOne(vote_transaction); + //transactions.push(vote_transaction); + proceed_bulk_transactions = true; + //utils.log(vote_transaction); + } + }); + } + //result = posts_collection.insert(post); + }catch(err){ + utils.log(err); + } + }//end of loop going through posts + + //properly stored any future skippable posts + utils.log(proceed_bulk_posts_skip); + try{ + if (proceed_bulk_posts_skip){ + await bulk_posts_skip.execute(); + } + }catch(bulkerr){ + utils.log(bulkerr); + } + + utils.log('votePosts.length:'+votePosts.length); + utils.log(proceed_bulk); + utils.log(proceed_bulk_transactions); + if (votePosts.length>0){ + try{ + //store posts + if (proceed_bulk){ + await bulk.execute(); + } + }catch(bulkerr){ + utils.log(bulkerr); + } + try{ + //award transaction tokens + if (proceed_bulk_transactions){ + await bulk_transactions.execute(); + } + }catch(bulkerr){ + utils.log(bulkerr); + } + } + + //if this is the first try, or the new count of posts is bigger than the one before, let's try adding again + if (!config.testing && (!subsequent || votePosts.length > lastIterationCount || queryCount < config.max_query_count)){ + + //update last count + lastIterationCount = votePosts.length; + //call again with subsequent enabled to avoid duplicate posts, disparse the calls by 1 sec to avoid API timeouts + utils.log("query:"+query['tag']); + utils.log("query:"+query['start_permlink']); + + setTimeout(processVotes, 1000, query, true); + + }else{ + if (votePosts.length > 0) { + utils.log(votePosts.length + ' posts to vote...'); + var vote_data = utils.calculateVotes(votePosts, config.vote_weight); + + + //if (!config.testing){ + /* + //Sort posts by reverse score, so as when popping them we get sorted by highest + votePosts.sort(function(post1, post2) { + + return post1.post_score - post2.post_score; + }); + */ + + //} + + + utils.log(vote_data.power_per_vote + ' power per full vote.'); + + + /************************* winner reward ******************************/ + + + //special pick verified newbie rewards + + //grab list of eligible verified newbie accounts + + let newbieList= await fetch(config.api_url+'activeVerifiedNewbies/'); + let newbieEligListRes = await newbieList.json(); + let interimEligList = []; + let finalEligNewbieList = []; + + for (let lpr=0;lpr user_post.author === newbieEligListRes[lpr].user); + if (matchPst){ + interimEligList.push(newbieEligListRes[lpr].user); + } + console.log(matchPst); + } + + console.log('current full eligible list'); + console.log(interimEligList); + if (interimEligList.length<=config.max_newbie_reward_count){ + //we have all our list already + finalEligNewbieList = interimEligList; + }else{ + while(finalEligNewbieList.length < config.max_newbie_reward_count){ + let r = Math.floor(Math.random() * (interimEligList.length)); //generate random number between 0 and array length + //only append the item if not already added + if (finalEligNewbieList.indexOf(interimEligList[r]) === -1){ + finalEligNewbieList.push(interimEligList[r]); + } + } + } + console.log('final selected list'); + console.log(finalEligNewbieList); + + //let's pick a random winner to double up his votes and adjust his AFIT reward score + + try{ + lucky_winner_id = utils.generateRandomNumber(1, votePosts.length); + let post = votePosts[lucky_winner_id]; + + utils.log('before'); + utils.log(votePosts[lucky_winner_id].post_score); + + let reward_user = post.author; + let activity_type = 'Post'; + let note = ''; + let reward_factor = 2; + + //if we find this is a charity run, let's switch it to the actual charity name + if (typeof post.json.charity != 'undefined' && post.json.charity != '' && post.json.charity != 'undefined'){ + reward_user = post.json.charity[0]; + activity_type = 'Charity Post'; + note = 'Charity donation via actifit post by user '+post.author; + } + + var bulk_transactions = db.collection('token_transactions').initializeUnorderedBulkOp(); + + let post_transaction = { + user: reward_user, + reward_activity: activity_type, + token_count: post.post_score * reward_factor, + orig_token_count: post.post_score, + url: post.url, + date: new Date(post.created), + note: note, + lucky_winner: 1, + reward_factor: reward_factor, + reward_system: reward_sys_version + } + + //adjust post_score according to reward + post.post_score = post.post_score * reward_factor; + post.rate_multiplier = post.post_score / 100; + post.reward_factor = reward_factor; + post.lucky_winner = 1; + + bulk_transactions.find( + { + user: post_transaction.user, + reward_activity: post_transaction.reward_activity, + url: post_transaction.url + }).upsert().replaceOne(post_transaction); + + + //award transaction tokens + await bulk_transactions.execute(); + }catch(bulkerr){ + utils.log(bulkerr); + } + utils.log('after'); + utils.log(votePosts[lucky_winner_id].post_score); + + /***************** Check for AFIT/STEEM upvote exchange ******************/ + try{ + + //assign an ID to this reward cycle + let reward_cycle_ID = 'RC' + new Date().toISOString().replace(/-|:|\./g, '').toLowerCase() + + //calculate current vote value, and relating voting percentage needed + //get vote value at 100% + let full_vote_value = getVoteValueUSD(100, account, 100, hive_price); + console.log('full_vote_value') + console.log(full_vote_value) + + console.log('topUsersAFITX') + //console.log(topUsersAFITX); + + //number of found exchanges to perform in coming round + let matched_exchanges = 0; + //number of entries, maximum set by config + let list_length = Math.min(topUsersAFITX.length, config.topAFITXCount); + + + //loop through top AFITX holders to get confirmed upvotes + for (let xx=0 ; xx < list_length && matched_exchanges < config.max_afit_steem_upvotes_per_session ; xx++){ + let cur_top_entry = topUsersAFITX[xx]; + //find a matching report card, if exists + console.log(cur_top_entry.account); + let result = votePosts.find( user_post => user_post.author === cur_top_entry.account); + console.log('matching priority post'); + //console.log(result); + + //also find matching exchange request + + let cur_upvote_entry = afit_steem_upvote_list.find( upvote_request => upvote_request.user === cur_top_entry.account); + + //console.log('cur_upvote_entry'); + + //console.log(cur_upvote_entry); + + if (result && cur_upvote_entry){ + console.log('>>match found'); + matched_exchanges += 1; + //found a match, need to increase rewards according to AFIT pay + //calculate total paid AFIT in USD (which should be equal to a 65% reward, since Actifit removes 10% benefic, and author reward removes 75% + let usd_val_no_benef = parseFloat(cur_upvote_entry.paid_afit) * parseFloat(cur_afit_price.unit_price_usd);//20*0.02=0.4 + + //expand the USD val to take into consideration 75% curation reward + let usd_val_no_curation = usd_val_no_benef * 0.75 / 0.65; //0.4*0.75/0.65=0.4615384615384615 + + //final upvote value after avoiding deductions + let usd_val = usd_val_no_benef / 0.5; //0.4/0.5=0.8 + + //emulate proper voting power to give user matching rewards + let user_added_vote_weight = usd_val * 100 / full_vote_value; //0.8*100/full_vote_value + + let entry_index = votePosts.findIndex( user_post => user_post.author === cur_upvote_entry.user); + + //decrease by 1% since assisting accounts will vote too (pay & funds) only if we still have room to use them + //only consume half of those now, leave the rest to other accounts + if (helping_accounts_votes < (config.max_helping_votes / 2)){ + user_added_vote_weight -= 1; + + helping_accounts_votes += 1; + + votePosts[entry_index].helperVotes = true; + } + + user_added_vote_weight = user_added_vote_weight.toFixed(2); + + votePosts[entry_index].additional_vote_weight = Math.floor(user_added_vote_weight * 100); + votePosts[entry_index].afit_swapped = cur_upvote_entry.paid_afit; + votePosts[entry_index].top_afitx_holder = 1; + console.log('Additional Vote Weight for AFIT/STEEM Upvote Exchange: '+votePosts[entry_index].author + ' ' + votePosts[entry_index].url); + console.log(votePosts[entry_index].additional_vote_weight); + + //we need to set params of this transaction + cur_upvote_entry.additional_vote_weight = votePosts[entry_index].additional_vote_weight / 100; + cur_upvote_entry.usd_val_no_benef = +usd_val_no_benef; + cur_upvote_entry.usd_val_no_curation = +usd_val_no_curation.toFixed(2); + cur_upvote_entry.usd_val = +usd_val.toFixed(2); + + cur_upvote_entry.reward_cycle_ID = reward_cycle_ID; + + cur_upvote_entry.top_afitx_holder = 1 + + cur_upvote_entry.post_author = votePosts[entry_index].author; + cur_upvote_entry.post_permlink = votePosts[entry_index].permlink; + + db.collection('exchange_afit_steem').save(cur_upvote_entry); + + //console.log(votePosts[entry_index]); + + } + } + + console.log('afit_steem_upvote_list'); + console.log(afit_steem_upvote_list); + + //number of entries + list_length = afit_steem_upvote_list.length; + + //loop through pending AFIT swaps, consuming older ones + for (let xx=0 ; xx < list_length && matched_exchanges < config.max_afit_steem_upvotes_per_session ; xx++){ + let cur_upvote_entry = afit_steem_upvote_list[xx]; + //find a matching report card, if exists + let result = votePosts.find( user_post => user_post.author === cur_upvote_entry.user); + console.log('matching second post'); + //console.log(result); + if (result != null){ + console.log('>>match found. Check if added already'); + //if this post has not been recorded yet + if (!result.additional_vote_weight){ + console.log('fresh game'); + matched_exchanges += 1; + //found a match, need to increase rewards according to AFIT pay + //calculate total paid AFIT in USD (which should be equal to a 65% reward, since Actifit removes 10% benefic, and author reward removes 75% + let usd_val_no_benef = parseFloat(cur_upvote_entry.paid_afit) * parseFloat(cur_afit_price.unit_price_usd); + + //expand the USD val to take into consideration 75% curation reward + let usd_val_no_curation = usd_val_no_benef * 0.75 / 0.65 + + //final upvote value after avoiding deductions + let usd_val = usd_val_no_benef / 0.5 + + //emulate proper voting power to give user matching rewards + let user_added_vote_weight = usd_val * 100 / full_vote_value; + + let entry_index = votePosts.findIndex( user_post => user_post.author === cur_upvote_entry.user); + + //decrease by 1% since assisting accounts will vote too (pay & funds) only if we still have room to use them + if (helping_accounts_votes < config.max_helping_votes){ + user_added_vote_weight -= 1; + + helping_accounts_votes += 1; + + votePosts[entry_index].helperVotes = true; + } + + user_added_vote_weight = user_added_vote_weight.toFixed(2); + + votePosts[entry_index].additional_vote_weight = Math.floor(user_added_vote_weight * 100); + votePosts[entry_index].afit_swapped = cur_upvote_entry.paid_afit; + console.log('Additional Vote Weight for AFIT/STEEM Upvote Exchange: '+votePosts[entry_index].author + ' ' + votePosts[entry_index].url); + console.log(votePosts[entry_index].additional_vote_weight); + + //we need to set params of this transaction + cur_upvote_entry.additional_vote_weight = votePosts[entry_index].additional_vote_weight / 100; + cur_upvote_entry.usd_val_no_benef = +usd_val_no_benef; + cur_upvote_entry.usd_val_no_curation = +usd_val_no_curation.toFixed(2); + cur_upvote_entry.usd_val = +usd_val.toFixed(2); + + cur_upvote_entry.reward_cycle_ID = reward_cycle_ID; + + cur_upvote_entry.post_author = votePosts[entry_index].author; + cur_upvote_entry.post_permlink = votePosts[entry_index].permlink; + + db.collection('exchange_afit_steem').save(cur_upvote_entry); + } + } + } + console.log('done going through pending AFIT to STEEM upvote exchange'); + console.log('matched_exchanges:'+matched_exchanges); + }catch(err){ + utils.log(err); + } + + /********************* proceed with STEEM upvotes ************************/ + + votingProcess(votePosts, vote_data.power_per_vote); + + } else { + utils.log('No posts to vote...'); + if(!error_sent) { + //errorEmail('No posts to vote...', config.report_emails); + error_sent = true; + } + } + } + last_voted++; + } else { + utils.log(err, result); + //since we encountered an error, we need to restart the process + //is_voting = false; + setIsVoting(false); + votePosts = Array(); + //errorEmail(err, config.report_emails); + } + }); +} + +async function fetchAFITXTopHolders(){ + + console.log('--- Fetch AFITX top users --- '); + let holderList = await fetch('https://actifitbot.herokuapp.com/topAFITXHolders?count='+config.topAFITXCount); + topUsersAFITX = await holderList.json(); + console.log(topUsersAFITX); +} + +var post_rank = 0; +function votingProcess(posts, power_per_vote) { + // Get the first bid in the list + sendVote(posts.pop(), 0, power_per_vote) + .then( res => { + // If there are more posts, vote on the next one after 5 seconds + if (posts.length > 0) { + setTimeout(function () { votingProcess(posts, power_per_vote); }, config.voting_posting_delay); + } else { + post_rank = 0; + setTimeout(function () { + utils.log('======================================================='); + utils.log('Voting Complete!'); + utils.log('======================================================='); + //is_voting = false; + setIsVoting(false); + error_sent = false; + saveState(); + + //since we're done voting, we need to update all user tokens to reflect new rewards + if (!config.testing){ + updateUserTokens(); + } + //reportEmail(config.report_emails) + }, config.voting_posting_delay); + } + }) + .catch(err => { + utils.log(err); + }) +} + + +async function sendVote(post, retries, power_per_vote) { + + + utils.log('Voting on: ' + post.url + ' with count'+post.json.step_count); + var token_count = post.post_score;//parseFloat(post.rate_multiplier)*100; + + console.log(power_per_vote); + + var vote_weight = Math.floor(post.rate_multiplier * power_per_vote); + let stdrd_vote_weight = vote_weight * config.partner_comm_vote_mult; + console.log('vote weight:'+vote_weight); + + //if user had paid AFIT for extra STEEM upvotes, add this to their upvote value + if (post.additional_vote_weight){ + vote_weight += post.additional_vote_weight + console.log('new vote weight:'+vote_weight); + } + + //check if this user is a newbie eligible for extra rewards + if (Array.isArray(finalEligNewbieList) && finalEligNewbieList.length > 0){ + console.log('we have eligible newbies for extra rewards!'); + let entryIdx = finalEligNewbieList.indexOf(post.author); + if (entryIdx !== -1){ + vote_weight = config.max_newbie_vote_pct; + console.log('Newbie user '+post.author+' eligible for extra vote. Vote weight:'+vote_weight); + finalEligNewbieList.splice(entryIdx, 1); + } + } + + post_rank += 1; + utils.log('|#'+post_rank+'|@'+post.author+'|'+ post.json.step_count +'|'+token_count+' Tokens|'+utils.format(vote_weight / 100)+'%|[post](https://www.steemit.com'+post.url+')'); + + //if this is a sponsored athlete, give them special reward + if (config.sponsored_athletes.includes(post.author)){ + vote_weight = config.sponsored_athlete_upvote_reward; + } + + + if (vote_weight > config.max_vote_per_post){ + vote_weight = config.max_vote_per_post; + } + + if (stdrd_vote_weight > config.max_vote_per_post){ + stdrd_vote_weight = config.max_vote_per_post; + } + + let net_rewards_vote_weight = stdrd_vote_weight; + + console.log('old sports percent:'+stdrd_vote_weight); + + /*if (config.testing){ + console.log('switch author'); + console.log(post.author); + post.author = 'mcfarhat'; + post.permlink = 'actifit-witness-vote-application-msp'; + }*/ + + let boost_res = await grabConsumeUserBoostByType(post.author, 'SPORTS', 'percent_reward', post, true); + + post.user_post_boosts = post.user_post_boosts.concat(boost_res.user_post_boosts); + + //check if user has a SPORTS boost as percent increments + let appendPercTokens = boost_res.extra_boost; + + //append as percentage + stdrd_vote_weight += appendPercTokens * stdrd_vote_weight / 100; + stdrd_vote_weight = Math.floor(stdrd_vote_weight); + post.boost_sports_percent_reward = appendPercTokens; + + console.log('new sports percent:'+stdrd_vote_weight); + + //check if user has a SPORTS boost as percent increments + + boost_res = await grabConsumeUserBoostByType(post.author, 'SPORTS', 'percent', post, true); + + post.user_post_boosts = post.user_post_boosts.concat(boost_res.user_post_boosts); + + //check if user has a SPORTS boost as percent increments + let appendNetPercTokens = boost_res.extra_boost; + + //append as percentage + stdrd_vote_weight += appendNetPercTokens * 100; + stdrd_vote_weight = Math.floor(stdrd_vote_weight); + post.boost_sports_percent = appendNetPercTokens; + + console.log('new sports percent:'+stdrd_vote_weight); + + + //check if user has an APPICS boost as percent + + //first need to make sure the post benefits from APX vote (meaning has APX tag) + + let tags = JSON.parse(post.json_metadata).tags; + + console.log('checking APX '); + //console.log(tags); + //console.log(tags.findIndex(item => config.appics_tag.toLowerCase() === item.toLowerCase())); + + if(tags && tags.length > 0 && tags.findIndex(item => config.appics_tag.toLowerCase() === item.toLowerCase()) != -1) { + + utils.log('Post contains APX tag for extra vote ' + post.url); + + let curAuthor = post.author; + if (config.testing){ + curAuthor= 'mcfarhat'; + } + boost_res = await grabConsumeUserBoostByType(curAuthor, 'APX', 'percent', post, true); + + post.user_post_boosts = post.user_post_boosts.concat(boost_res.user_post_boosts); + + let appendApxPercTokens = boost_res.extra_boost; + + //append as percentage + let boost_apx_percent = appendApxPercTokens * 100; + boost_apx_percent = Math.floor(boost_apx_percent); + post.boost_apx_percent = boost_apx_percent; + post.vote_appics = true; + + console.log('new APX percent:' + post.boost_apx_percent); + + } + + post.vote_weight = vote_weight; + last_votes.push(post); + + return new Promise(async (resolve, reject) => { + if(config.testing){ + //resolve(''); + + //console.log(afit_steem_upvote_list); + if (post.additional_vote_weight){ + console.log('Exchange vote'); + //store exchange transaction as complete + let cur_upvote_entry = afit_steem_upvote_list.find( entry => entry.post_author === post.author && + entry.post_permlink === post.permlink && + entry.user === post.author); + console.log(cur_upvote_entry); + cur_upvote_entry.upvote_processed = true; + db.collection('exchange_afit_steem').save(cur_upvote_entry); + } + + if(config.comment_location && config.comment){ + setTimeout(function () { + sendComment(post, 0, vote_weight) + .then( res => { + resolve('') + }) + .catch(err => { + reject(err); + }) + }, config.voting_posting_delay); + }else{ + resolve(''); + } + }else{ + + let res; + + + if (config.hive_voting_active){ + + //first bchain transactions + console.log('set HIVE node'); + /* + steem.api.setOptions({ + url: config.active_hive_node , + //useAppbaseApi: true + });*/ + //vote first using pay and funds accounts only if we have an AFIT/STEEM exchange operation and we have room to upvote using helping accounts + if (post.additional_vote_weight && post.helperVotes){ + let vote_percent_add_accounts = config.helping_account_percent;//at 50%: 5000 + try{ + + utils.log('voting with '+config.full_pay_benef_account+ ' '+utils.format(vote_percent_add_accounts / 100) + '% vote cast for: ' + post.url); + /*steem.api.setOptions({ + url: config.active_hive_node , + //useAppbaseApi: true + });*/ + res = await hive.broadcast.sendAsync( + { + operations: [ + ['vote', + { + "voter": config.full_pay_benef_account, + "author": post.author, + "permlink": post.permlink, + "weight": vote_percent_add_accounts + } + ] + ], + extensions: [] + }, + { posting: config.full_pay_posting_key } + ).catch(e => console.log(e)) + console.log(res); + if (res && res.block_num) { + utils.log('success'); + } + + }catch(err){ + utils.log(err); + } + + try{ + utils.log('voting with '+config.pay_account+ ' '+utils.format(vote_percent_add_accounts / 100) + '% vote cast for: ' + post.url); + /*steem.api.setOptions({ + url: config.active_hive_node , + //useAppbaseApi: true + });*/ + res = await hive.broadcast.sendAsync( + { + operations: [ + ['vote', + { + "voter": config.pay_account, + "author": post.author, + "permlink": post.permlink, + "weight": vote_percent_add_accounts + } + ] + ], + extensions: [] + }, + { posting: config.pay_account_post_key } + ).catch(e => console.log(e)) + console.log(res); + if (res && res.block_num) { + utils.log('success'); + } + + }catch(err){ + utils.log(err); + } + + } + + //if additional partner accounts enabled, vote using them as well + try{ + if (config.zzan_active){ + utils.log('voting with '+config.zzan_account+ ' '+utils.format(stdrd_vote_weight / 100) + '% vote cast for: ' + post.url); + /*steem.api.setOptions({ + url: config.active_hive_node , + //useAppbaseApi: true + });*/ + res = await hive.broadcast.sendAsync( + { + operations: [ + ['vote', + { + "voter": config.zzan_account, + "author": post.author, + "permlink": post.permlink, + "weight": stdrd_vote_weight + } + ] + ], + extensions: [] + }, + { posting: config.zzan_pk } + ).catch(e => console.log(e)) + console.log(res); + if (res && res.block_num) { + utils.log('success'); + } + + } + }catch(err){ + utils.log(err); + } + + try{ + if (config.sports_active){ + utils.log('voting with '+config.sports_active+ ' '+utils.format(stdrd_vote_weight / 100) + '% vote cast for: ' + post.url); + /*steem.api.setOptions({ + url: config.active_hive_node , + //useAppbaseApi: true + });*/ + res = await hive.broadcast.sendAsync( + { + operations: [ + ['vote', + { + "voter": config.sports_account, + "author": post.author, + "permlink": post.permlink, + "weight": stdrd_vote_weight + } + ] + ], + extensions: [] + }, + { posting: config.sports_pk } + ).catch(e => console.log(e)) + console.log(res); + if (res && res.block_num) { + utils.log('success'); + } + + } + }catch(err){ + utils.log(err); + } + + //append appics account gadget-based voting + + if (config.appics_active && post.vote_appics){ + + try{ + utils.log('voting with '+config.appics_account+ ' '+utils.format(post.boost_apx_percent / 100) + '% vote cast for: ' + post.url); + /*steem.api.setOptions({ + url: config.active_hive_node , + //useAppbaseApi: true + });*/ + res = await hive.broadcast.sendAsync( + { + operations: [ + ['vote', + { + "voter": config.appics_account, + "author": post.author, + "permlink": post.permlink, + "weight": post.boost_apx_percent + } + ] + ], + extensions: [] + }, + { posting: config.appics_pk } + ).catch(e => console.log(e)) + console.log(res); + if (res && res.block_num) { + utils.log('success'); + } + + }catch(err){ + utils.log(err); + } + } + + try{ + utils.log('voting with '+config.rewards_account+ ' '+utils.format(net_rewards_vote_weight / 100) + '% vote cast for: ' + post.url); + /*steem.api.setOptions({ + url: config.active_hive_node , + //useAppbaseApi: true + });*/ + res = await hive.broadcast.sendAsync( + { + operations: [ + ['vote', + { + "voter": config.rewards_account, + "author": post.author, + "permlink": post.permlink, + "weight": net_rewards_vote_weight + } + ] + ], + extensions: [] + }, + { posting: config.rewards_account_pk } + ).catch(e => console.log(e)) + console.log(res); + if (res && res.block_num) { + utils.log('success'); + } + + }catch(err){ + utils.log(err); + } + + try{ + utils.log('voting with '+account.name+ ' '+utils.format(vote_weight / 100) + '% vote cast for: ' + post.url); + /*steem.api.setOptions({ + url: config.active_hive_node , + //useAppbaseApi: true + });*/ + res = await hive.broadcast.sendAsync( + { + operations: [ + ['vote', + { + "voter": account.name, + "author": post.author, + "permlink": post.permlink, + "weight": vote_weight + } + ] + ], + extensions: [] + }, + { posting: config.posting_key } + ).then(async function(rest, err) { + + //store exchange transaction as complete + if (post.additional_vote_weight){ + console.log('Exchange vote'); + let cur_upvote_entry = afit_steem_upvote_list.find( entry => entry.post_author === post.author && + entry.post_permlink === post.permlink && + entry.user === post.author); + console.log(cur_upvote_entry); + cur_upvote_entry.upvote_processed = true; + db.collection('exchange_afit_steem').save(cur_upvote_entry); + } + + //notify user of voting success + utils.sendNotification(db, post.author, account.name, 'post_reward', 'Your activity report "'+ post.title + '" has been rewarded!', 'https://actifit.io'+post.url); + + + if(config.comment_location && config.comment){ + await sendComment(post, 0, vote_weight, 'HIVE') + .then( res => { + //resolve(res) + + + console.log(res) + }).catch(err => { + console.log(err); + }) + + } + }).catch(e => console.log(e)) + console.log(res); + if (res && res.block_num) { + utils.log('success'); + + + + + + //wait 5 seconds before commenting + //await delay(5000); + + //setTimeout(function () { + /* await sendComment(post, 0, vote_weight, config.active_hive_node) + .then( res => { + //resolve(res) + console.log(res) + }) + .catch(err => { + console.log(err); + })*/ + //}, config.voting_posting_delay); + /*}else{ + //resolve(result); + }*/ + }else{ + // Try again one time on error + /*if (retries < config.max_vote_comment_retries){ + //try to vote again + setTimeout(function () { + sendVote(post, retries + 1, power_per_vote) + .then( res => { + resolve(res) + }) + .catch(err => { + reject(err); + }) + }, config.voting_posting_delay); + }else { + var message = '============= Vote transaction failed '+retries+' times for: ' + post.url + ' ===============' + utils.log(message); + reject(err); + //errorEmail(message, config.report_emails); + }*/ + } + + + + }catch(mainerr){ + utils.log(mainerr); + } + } + + if (config.steem_voting_active){ + + /*************************************************/ + /*************************************************/ + /*************************************************/ + + //second blockchain transactions + console.log('set STEEM node'); + /*steem.api.setOptions({ + url: config.active_node , + //useAppbaseApi: true + });*/ + //vote first using pay and funds accounts only if we have an AFIT/STEEM exchange operation and we have room to upvote using helping accounts + if (post.additional_vote_weight && post.helperVotes){ + let vote_percent_add_accounts = config.helping_account_percent;//at 50%: 5000 + try{ + + utils.log('voting with '+config.full_pay_benef_account+ ' '+utils.format(vote_percent_add_accounts / 100) + '% vote cast for: ' + post.url); + + /*steem.api.setOptions({ + url: config.active_node , + //useAppbaseApi: true + });*/ + res = await steem.broadcast.sendAsync( + { + operations: [ + ['vote', + { + "voter": config.full_pay_benef_account, + "author": post.author, + "permlink": post.permlink, + "weight": vote_percent_add_accounts + } + ] + ], + extensions: [] + }, + { posting: config.full_pay_posting_key } + ).catch(e => console.log(e)) + console.log(res); + if (res && res.block_num) { + utils.log('success'); + } + + }catch(err){ + utils.log(err); + } + + try{ + utils.log('voting with '+config.pay_account+ ' '+utils.format(vote_percent_add_accounts / 100) + '% vote cast for: ' + post.url); + /*steem.api.setOptions({ + url: config.active_node , + //useAppbaseApi: true + });*/ + res = await steem.broadcast.sendAsync( + { + operations: [ + ['vote', + { + "voter": config.pay_account, + "author": post.author, + "permlink": post.permlink, + "weight": vote_percent_add_accounts + } + ] + ], + extensions: [] + }, + { posting: config.pay_account_post_key } + ).catch(e => console.log(e)) + console.log(res); + if (res && res.block_num) { + utils.log('success'); + } + + }catch(err){ + utils.log(err); + } + + } + + //if additional partner accounts enabled, vote using them as well + try{ + if (config.zzan_active){ + utils.log('voting with '+config.zzan_account+ ' '+utils.format(stdrd_vote_weight / 100) + '% vote cast for: ' + post.url); + /*steem.api.setOptions({ + url: config.active_node , + //useAppbaseApi: true + });*/ + res = await steem.broadcast.sendAsync( + { + operations: [ + ['vote', + { + "voter": config.zzan_account, + "author": post.author, + "permlink": post.permlink, + "weight": stdrd_vote_weight + } + ] + ], + extensions: [] + }, + { posting: config.zzan_pk } + ).catch(e => console.log(e)) + console.log(res); + if (res && res.block_num) { + utils.log('success'); + } + + } + }catch(err){ + utils.log(err); + } + + try{ + if (config.sports_active){ + utils.log('voting with '+config.sports_account+ ' '+utils.format(stdrd_vote_weight / 100) + '% vote cast for: ' + post.url); + /*steem.api.setOptions({ + url: config.active_node , + //useAppbaseApi: true + });*/ + res = await steem.broadcast.sendAsync( + { + operations: [ + ['vote', + { + "voter": config.sports_account, + "author": post.author, + "permlink": post.permlink, + "weight": stdrd_vote_weight + } + ] + ], + extensions: [] + }, + { posting: config.sports_pk } + ).catch(e => console.log(e)) + console.log(res); + if (res && res.block_num) { + utils.log('success'); + } + + } + }catch(err){ + utils.log(err); + } + + //append appics account gadget-based voting + + if (config.appics_active && post.vote_appics){ + + try{ + utils.log('voting with '+config.appics_account+ ' '+utils.format(post.boost_apx_percent / 100) + '% vote cast for: ' + post.url); + /*steem.api.setOptions({ + url: config.active_node , + //useAppbaseApi: true + });*/ + res = await steem.broadcast.sendAsync( + { + operations: [ + ['vote', + { + "voter": config.appics_account, + "author": post.author, + "permlink": post.permlink, + "weight": post.boost_apx_percent + } + ] + ], + extensions: [] + }, + { posting: config.appics_pk } + ).catch(e => console.log(e)) + console.log(res); + if (res && res.block_num) { + utils.log('success'); + } + + }catch(err){ + utils.log(err); + } + } + + try{ + utils.log('voting with '+config.rewards_account+ ' '+utils.format(net_rewards_vote_weight / 100) + '% vote cast for: ' + post.url); + /*steem.api.setOptions({ + url: config.active_node , + //useAppbaseApi: true + });*/ + res = await steem.broadcast.sendAsync( + { + operations: [ + ['vote', + { + "voter": config.rewards_account, + "author": post.author, + "permlink": post.permlink, + "weight": net_rewards_vote_weight + } + ] + ], + extensions: [] + }, + { posting: config.rewards_account_pk } + ).catch(e => console.log(e)) + console.log(res); + if (res && res.block_num) { + utils.log('success'); + } + + }catch(err){ + utils.log(err); + } + + try{ + + if (post.additional_vote_weight && vote_weight > config.min_vote_weight_decrease){ + //decrease amount by 1% across votes to be able to reward team + vote_weight -= config.extra_vote_weight_decrease;// + } + + utils.log('voting with '+account.name+ ' '+utils.format(vote_weight / 100) + '% vote cast for: ' + post.url); + /*steem.api.setOptions({ + url: config.active_node , + //useAppbaseApi: true + });*/ + res = await steem.broadcast.sendAsync( + { + operations: [ + ['vote', + { + "voter": account.name, + "author": post.author, + "permlink": post.permlink, + "weight": vote_weight + } + ] + ], + extensions: [] + }, + { posting: config.posting_key } + ) + .then(async function(rest, err) { + + //store exchange transaction as complete + if (post.additional_vote_weight){ + console.log('Exchange vote'); + let cur_upvote_entry = afit_steem_upvote_list.find( entry => entry.post_author === post.author && + entry.post_permlink === post.permlink && + entry.user === post.author); + console.log(cur_upvote_entry); + cur_upvote_entry.upvote_processed = true; + db.collection('exchange_afit_steem').save(cur_upvote_entry); + } + + if(config.comment_location && config.comment){ + + //setTimeout(function () { + + //wait 5 seconds before commenting + //await delay(5000); + + await sendComment(post, 0, vote_weight, 'STEEM') + .then( res => { + //resolve(res) + }).catch(err => { + //reject(err); + }) + //}, config.voting_posting_delay); + }else{ + //resolve(''); + } + }).catch(e => console.log(e)) + console.log(res); + if (res && res.block_num) { + utils.log('success'); + + //store exchange transaction as complete + /*if (post.additional_vote_weight){ + console.log('Exchange vote'); + let cur_upvote_entry = afit_steem_upvote_list.find( entry => entry.post_author === post.author && + entry.post_permlink === post.permlink && + entry.user === post.author); + console.log(cur_upvote_entry); + cur_upvote_entry.upvote_processed = true; + db.collection('exchange_afit_steem').save(cur_upvote_entry); + }*/ + + + }else{ + // Try again one time on error + /*if (retries < config.max_vote_comment_retries){ + //try to vote again + setTimeout(function () { + sendVote(post, retries + 1, power_per_vote) + .then( res => { + resolve(res) + }) + .catch(err => { + reject(err); + }) + }, config.voting_posting_delay); + }else { + var message = '============= Vote transaction failed '+retries+' times for: ' + post.url + ' ===============' + utils.log(message); + reject(err); + //errorEmail(message, config.report_emails); + }*/ + } + + }catch(mainerr){ + utils.log(mainerr); + } + + } + resolve(''); + } + }); +} + + +//function handles updating current user token count +async function updateUserTokens() { + utils.log('---- Updating Users ----'); + let insert_res + try{ + //group all token transactions per user, and sum them to generate new total count + let query = await db.collection('token_transactions').aggregate([ + { $group: { _id: "$user", tokens: { $sum: "$token_count" } } }, + { $sort: { tokens: -1 } }, + { $project: { + _id: "$_id", + user: "$_id", + tokens: "$tokens", + } + } + ]) + + let user_tokens = await query.toArray(); + console.log('grab query all items'); + //remove old token count per user + await db.collection('user_tokens').remove({}); + console.log('removed all entries'); + console.log(user_tokens); + //insert new count per user + insert_res = await db.collection('user_tokens').insertMany(user_tokens); + console.log(insert_res.insertedCount); + console.log('inserted all entries'); + }catch(err){ + console.log(insert_res); + console.log(err); + utils.log('>>save data error:'+err.message); + } +} + + +async function sendComment(post, retries, vote_weight, bchain_node) { + var parentAuthor = post.author; + var parentPermlink = post.permlink; + var rate_multiplier = post.rate_multiplier; + var post_step_count = post.json.step_count; + + var content = null; + // Return promise + //return new Promise( async (resolve, reject) => { + content = fs.readFileSync(config.comment_location, "utf8"); + let content_sign = fs.readFileSync(config.comment_sign_location, "utf8"); + + // If promotion content is specified in the config then use it to comment on the upvoted post + if (content && content != '') { + + // Generate the comment permlink via steemit standard convention + var permlink = 're-' + parentAuthor.replace(/\./g, '') + '-' + parentPermlink + '-' + new Date().toISOString().replace(/-|:|\./g, '').toLowerCase(); + + var token_count = post.post_score;//parseFloat(rate_multiplier)*100; + + //if this is a sponsored athlete, give them special reward + if (config.sponsored_athletes.includes(parentAuthor)){ + token_count = config.sponsored_athlete_afit_reward; + } + + token_count = Math.ceil(token_count * 10000) / 10000; + + // Replace variables in the promotion content + content = content.replace(/\{weight\}/g, utils.format(vote_weight / 100)).replace(/\{token_count\}/g,token_count).replace(/\{step_count\}/g,post_step_count); + + console.log('post.user_post_boosts'); + console.log(post.user_post_boosts); + + //create proper display for boosts consumed + let boost_content = ''; + let maxGadgets = post.user_post_boosts.length; + boost_content += '
'; + for (let i=0;i < maxGadgets;i++){ + let cur_boost = post.user_post_boosts[i]; + let prod_info = cur_boost.productdetails[0]; + boost_content += '
'; + boost_content += '
'; + //append standard images to display on the other front-ends + boost_content += ''; + + boost_content += '
'; + for (let iter=0;iter < cur_boost.gadget_level;iter++){ + boost_content += ''; + } + boost_content += '
'; + boost_content += '
'+cur_boost.gadget_name + ' - L'+cur_boost.gadget_level+'
'; + console.log('prod_info'); + console.log(prod_info); + let boosts = prod_info.benefits.boosts; + if (Array.isArray( boosts) && boosts.length > 0){ + let maxBoosts = boosts.length; + for (let j=0;j'; + if (cur_boost.benefic && (cur_boost.benefic == post.author || cur_boost.benefic == '@' + post.author)){ + boost_content += '
Thanks to your friend @'+ cur_boost.user + '
'; + } + } + } + boost_content += '
'; + } + boost_content += '
'; + let reward_diff = parseFloat(token_count) - parseFloat(post.afit_pre_boost); + if (reward_diff > 0){ + boost_content += '
Boosts increased your AFIT earnings by '+reward_diff.toFixed(4)+' AFIT
'; + } + content = content.replace(/\{boost_list\}/g, boost_content); + + //replace(/\{milestone\}/g, milestone_txt). + + //adding proper meta content for later relevant reward via afit_tokens data + var jsonMetadata = { tags: ['actifit'], app: 'actifit/v'+version, activity_count: post_step_count, user_rank: post.user_rank, content_score: post.content_score, media_score: post.media_score, upvote_score: post.upvote_score, comment_score: post.comment_score, user_rank_score: post.user_rank_score, moderator_score: post.moderator_score, post_activity_score: post.activity_score, afit_tokens: token_count, post_upvote: vote_weight }; + + //if this reward contains an exchange amount, list it here + if (post.additional_vote_weight != null){ + jsonMetadata.promoted_post = true; + jsonMetadata.additional_vote_weight = post.additional_vote_weight; + console.log('new vote weight:'+vote_weight); + content = content.replace(/\{exchange_vote}/g,'**'+utils.format(post.additional_vote_weight/100)+'% of this upvote value is a result of an exchange transaction you performed for '+post.afit_swapped+' AFIT tokens !**'); + }else{ + content = content.replace(/\{exchange_vote}/g,'') + } + + //if this is a top AFITX reward exchange + if (post.top_afitx_holder != null){ + jsonMetadata.top_afitx_holder = true; + } + //if user is lucky winner, add a relevant message + + if (typeof post.lucky_winner != 'undefined' && post.lucky_winner != '' && post.lucky_winner != 'undefined'){ + content = content.replace(/\{lucky_reward}/g,'**You were also selected randomly as a LUCKY WINNER for the day. Your rewards were DOUBLED - DOUBLE CONGRATS!!**'); + jsonMetadata.lucky_winner = 1; + }else{ + content = content.replace(/\{lucky_reward}/g,'') + } + + //only add signature if content is not lengthier than 10,000 characters + try{ + if (content.length < 10000){ + content += content_sign; + } + }catch(exc_len){ + console.log('error testing/appending comment signature'); + } + + if (!config.testing){ + // Broadcast the comment + + try{ + + /*steem.api.setOptions({ + url: bchain_node , + //useAppbaseApi: true + });*/ + let chainLnk = hive; + if (bchain_node == 'STEEM'){ + chainLnk = steem; + } + console.log(jsonMetadata); + const operations = [ + ['comment', + { + "parent_author": parentAuthor, + "parent_permlink": parentPermlink, + "author": account.name, + "permlink": permlink, + "title": '', + "body": content, + "json_metadata" : JSON.stringify(jsonMetadata) + } + ] + ]; + + console.log(operations); + + let res = await chainLnk.broadcast.sendAsync( + { + operations: operations, + extensions: [] + }, + { posting: config.posting_key } + ).catch(e => console.log(e)) + utils.log(res); + if (res && res.block_num) { + utils.log('Posted comment: ' + permlink); + //resolve(res); + } + + /*steem.broadcast.comment(config.posting_key, parentAuthor, parentPermlink, account.name, permlink, permlink, content, jsonMetadata, function (err, result) { + if (!err && result) { + utils.log('Posted comment: ' + permlink); + resolve(result); + } else { + utils.log('Error posting comment: ' + permlink); + if (retries < config.max_vote_comment_retries){ + utils.log('Try again'); + setTimeout(function () { + sendComment(post, retries + 1, vote_weight) + .then( res => { + resolve(res) + }) + .catch(err => { + reject(err); + }) + }, config.voting_posting_delay); + } + //reject(err); + } + });*/ + + }catch(com_err){ + utils.log(com_err); + } + + }else{ + utils.log('comment'); + console.log(content); + console.log(jsonMetadata); + //resolve(''); + } + }else{ + //reject('Failed to load content'); + } + //}); + // Check if the bot should resteem this post + /* if (config.resteem) + resteem(parentAuthor, parentPermlink); */ +} + +function reportEmail(to) { + + var data = {}; + data.posts = last_votes; + data.total_votes = _.sumBy(last_votes, 'net_votes'); + data.total_money = _.sumBy(last_votes, 'vote_weight'); + + mail.sendWithTemplate('Report Mail', data, to, 'votes'); + last_votes = Array(); + +} + +function errorEmail(message, to) { + + mail.sendPlainMail('Info Mail', message, to) + .then(function(res, err) { + if (!err) { + utils.log(res); + } else { + utils.log(err); + } + }); +} + +function resteem(author, permlink) { + var json = JSON.stringify(['reblog', { + account: config.account, + author: author, + permlink: permlink + }]); + + steem.broadcast.customJson(config.posting_key, [], [config.account], 'follow', json, (err, result) => { + if (!err && result) { + utils.log('Resteemed Post: @' + author + '/' + permlink); + } else { + utils.log('Error resteeming post: @' + author + '/' + permlink); + } + }); +} + +function saveState() { + var state = { + last_trans: last_trans, + last_voted: last_voted, + vote_time: new Date() + }; + + // Save the state of the bot to disk + fs.writeFile('state.json', JSON.stringify(state), function (err) { + if (err) + utils.log(err); + }); +} + +function loadConfig() { + config = JSON.parse(fs.readFileSync("config.json")); +} + +function sendPayment(to, amount, currency, reason, retries, data) { + if(!retries) + retries = 0; + + // Make sure the recipient isn't on the no-refund list (for exchanges and things like that). + if (reason != 'forward_payment' && config.no_refund && config.no_refund.indexOf(to) >= 0) { + utils.log("Payment not sent to: @" + to + " for: " + reason + ' because they are on the no_refund list.'); + return; + } + + // Replace variables in the memo text + var memo = config.transfer_memos[reason]; + memo = memo.replace(/{amount}/g, utils.format(amount, 3) + ' ' + currency); + memo = memo.replace(/{currency}/g, currency); + memo = memo.replace(/{account}/g, config.account); + memo = memo.replace(/{to}/g, to); + memo = memo.replace(/{tag}/g, data); + + // Issue the payment. + steem.broadcast.transfer(config.active_key, config.account, to, utils.format(amount, 3) + ' ' + currency, memo, function (err, response) { + if (err) { + utils.log('Error sending payment to @' + to + ' for: ' + amount + ' ' + currency + ', Error: ' + err); + + // Try again on error + if(retries < 2) + setTimeout(function() { refund(to, amount, currency, reason, retries + 1, data) }, (Math.floor(Math.random() * 10) + 3) * 1000); + else + utils.log('============= Payment failed three times for: @' + to + ' ==============='); + } else { + utils.log('Payment of ' + amount + ' ' + currency + ' sent to @' + to + ' for reason: ' + reason); + } + }); +} + +async function claimRewards(target_chain) { + console.log('>>>>> claiming rewards'); + if (!config.auto_claim_rewards) + return; + + let targetAccount = actSteemAccount; + + let claim_currency = targetAccount.reward_steem_balance + let claim_currency_stable = targetAccount.reward_sbd_balance + let chainLnk = steem; + if (target_chain == 'HIVE'){ + targetAccount = account; + claim_currency = targetAccount.reward_steem_balance.replace("HIVE", "STEEM"); + claim_currency_stable = targetAccount.reward_sbd_balance.replace("HBD", "SBD"); + chainLnk = hive; + + } + // Make api call only if you have actual reward + if (parseFloat(targetAccount.reward_steem_balance) > 0 || parseFloat(targetAccount.reward_sbd_balance) > 0 || parseFloat(targetAccount.reward_vesting_balance) > 0) { + + + await chainLnk.broadcast.claimRewardBalance(config.posting_key, config.account, claim_currency, claim_currency_stable, targetAccount.reward_vesting_balance, function (err, result) { + if (err) { + console.log('error claiming rewards'); + utils.log(err); + } + + if (result) { + + var rewards_message = "$$$ ==> Rewards Claim"; + if (parseFloat(targetAccount.reward_sbd_balance) > 0) { rewards_message = rewards_message + ' SBD: ' + parseFloat(targetAccount.reward_sbd_balance); } + if (parseFloat(targetAccount.reward_steem_balance) > 0) { rewards_message = rewards_message + ' STEEM: ' + parseFloat(targetAccount.reward_steem_balance); } + if (parseFloat(targetAccount.reward_vesting_balance) > 0) { rewards_message = rewards_message + ' VESTS: ' + parseFloat(targetAccount.reward_vesting_balance); } + + utils.log(rewards_message); + + //now attempt to claim rewards with HIVE + if (!target_chain){ + claimRewards('HIVE'); + } + + // If there are liquid post rewards, withdraw them to the specified account + if (parseFloat(targetAccount.reward_sbd_balance) > 0 && config.post_rewards_withdrawal_account && config.post_rewards_withdrawal_account != '') { + + // Send liquid post rewards to the specified account + steem.broadcast.transfer(config.active_key, config.account, config.post_rewards_withdrawal_account, targetAccount.reward_sbd_balance, 'Liquid Post Rewards Withdrawal', function (err, response) { + if (err){ + utils.log(err, response); + }else{ + utils.log('$$$ Auto withdrawal - liquid post rewards: ' + targetAccount.reward_sbd_balance + ' sent to @' + config.post_rewards_withdrawal_account); + } + }); + } + } + }); + }else{ + console.log('nothing to claim. Try other chain if possible'); + //now attempt to claim rewards with HIVE + if (!target_chain){ + claimRewards('HIVE'); + } + } +} diff --git a/delegations.js b/delegations.js new file mode 100644 index 0000000..3d5ced2 --- /dev/null +++ b/delegations.js @@ -0,0 +1,1412 @@ +const dsteem = require('dsteem') +//const client = new dsteem.Client('https://steemd.privex.io') +const _ = require('lodash') +const moment = require('moment') +const utils = require('./utils') +const mail = require('./mail') + +const config = utils.getConfig() + +const client = new dsteem.Client(config.active_node) + +const hiveClient = new dsteem.Client(config.active_hive_node) + +const MongoClient = require('mongodb').MongoClient + +const testRun = false; + +const hive = require('@hiveio/hive-js'); +hive.api.setOptions({ url: config.active_hive_node }); + +let db +let collection +let bulk_delegation_entries +let bulk_hive_delegation_entries + +// Database Name +const dbName = config.db_name +const delegationTrxCol = 'delegation_transactions' +const hiveDelegationTrxCol = 'hive_delegation_transactions' + +const actDelgCol = 'active_delegations' +const hiveActDelgCol = 'hive_active_delegations' + +let properties +let totalVests +let totalSteem + +let steemPrice = 1; +let sbdPrice = 1; +let newestTxId = -1; + +console.log('--- Reward script initialized ---'); + +let schedule = require('node-schedule') + +if (process.env.BOT_THREAD == 'MAIN'){ + + console.log('--- Main Bot Thread Detected ---'); + + + //console.log('pre-schedule'); + var j = schedule.scheduleJob({hour: 08, minute: 00}, function(){ + console.log('--- Start delegators reward ---'); + runRewards(false);//param steemOnlyReward + }); + + //utils.lookupAccountPay(); + + //param steemOnlyReward + runRewards(true); + //runRewards(false); + + +}else if (process.env.BOT_THREAD == 'SECOND_API'){ + + //let's schedule the AFIT to S-E token move event at 10:00 + let moveJob = schedule.scheduleJob({hour: 10, minute: 00}, function(){ + console.log('--- Start AFIT to S-E Move ---'); + moveAFITToSE(false);//param test + }); + + //schedule the prize event at 00:00 every X days + let prizeJob = schedule.scheduleJob({hour: 00, minute: 01}, function(){ + console.log('--- Reward Gadget Buy Contest ---'); + processGadgetBuyPrize();//param test + }); + + +}else{ + //processGadgetBuyPrize(); + runRewards(true); +} + +const SSC = require('sscjs'); +const ssc = new SSC(config.steem_engine_rpc); + +const hsc = new SSC(config.hive_engine_rpc); + +//airdropAFITX(); + +//moveAFITToSE(true); + +//testMove(); + + +async function processGadgetBuyPrize() { + console.log('processGadgetBuyPrize'); + + let mongo_conn = config.mongo_uri + if (config.testing){ + mongo_conn = config.mongo_local + } + // Use connect method to connect to the server + MongoClient.connect(mongo_conn, async function (err, dbClient) { + if (!err) { + console.log('Connected successfully to server: ') + + db = dbClient.db(dbName) + + //fetch data of last reward cycle + let lastDraw = await db.collection('gadget_buy_prize_draw').find().sort({'drawDate': -1}).toArray(); + let lastDrawDate = new Date(config.gadgetPrizeInitDate); + if (Array.isArray(lastDraw) && lastDraw.length > 0){ + lastDrawDate = lastDraw[0].drawDate; + } + let today = moment().utc().startOf('date').toDate() + let start = moment(lastDrawDate).utc().startOf('date').toDate() + let nextDrawDate = moment(start).add(config.contestBuyLen, 'days').toDate() + //let nextDrawDate = lastDrawDate+ + console.log(today); + console.log(nextDrawDate); + console.log((today.getTime() == nextDrawDate.getTime())); + //check if this is the proper date to kick off reward + if (today.getTime() == nextDrawDate.getTime()){ + console.log('kick off draw reward'); + //fetch list of ticket holders + let entries = await utils.getGadgetBuyTickets(db); + console.log(entries); + + //randomly pick winner and send rewards + if (Array.isArray(entries) && entries.length > 0){ + + //fetch reward pool + hive.api.getAccounts([config.gadget_buy_account], async function(err, response){ + //console.log(err, response); + if (!err){ + let prizePool = response[0].balance; + let prizePoolValue = parseFloat(prizePool.split(' ')[0]) * config.userRewardPrizePercent / 100; + + console.log('prize pool value: '+prizePoolValue); + + //pick winner + let lucky_winner_id = utils.generateRandomNumber(1, entries.length); + let winner_name = entries[lucky_winner_id].user; + console.log('Winner is ......'+winner_name); + let currency = 'HIVE'; + let memo= 'Congrats on winning Actifit random gadget purchase prize!'; + + //reward winner + + let res = await hive.broadcast.transferAsync(config.gadget_buy_account_ak, config.gadget_buy_account, winner_name, parseFloat(prizePoolValue).toFixed(3) + ' ' + currency, memo);/*.then( + res => { + //store last draw results + let drawInfo = { + drawDate: new Date(); + winner: [{name: winner_name, position: 1, reward: prizePoolValue, currency: currency}], + rewardPool: prizePoolValue, + participatingTickets: entries + } + db.collection('gadget_buy_prize_draw').insert(drawInfo); + }).catch(err=>console.log(err));*/ + + let buyBackAmount = parseFloat(prizePool.split(' ')[0]) * config.buyBackPrizePercent / 100; + + let projectSupportAmount = parseFloat(prizePool.split(' ')[0]) * config.actifitFundPrizePercent / 100; + console.log(res); + if (res){ + //store last draw results + let drawInfo = { + drawDate: new Date(), + winner: [{name: winner_name, position: 1, reward: prizePoolValue, currency: currency, buyback: buyBackAmount, projectSupportAmount: projectSupportAmount}], + rewardPool: prizePoolValue, + participatingTickets: entries + } + db.collection('gadget_buy_prize_draw').insert(drawInfo); + } + + memo= 'Funds to buy back from Actifit random gadget purchase prize!'; + + //send 25% of funds to buy back tokens + + res = await hive.broadcast.transferAsync(config.gadget_buy_account_ak, config.gadget_buy_account, config.buy_account, parseFloat(buyBackAmount).toFixed(3) + ' ' + currency, memo); + + console.log(res); + + //keep 25% of funds to actifit project + memo= 'Funds to keep as 25% based on prize results.'; + + + res = await hive.broadcast.transferAsync(config.gadget_buy_account_ak, config.gadget_buy_account, config.full_pay_benef_account, parseFloat(projectSupportAmount).toFixed(3) + ' ' + currency, memo); + + console.log(res); + + //send notification to the user about winning + utils.sendNotification(db, winner_name, 'actifit', 'prize_pool_draw_winner', 'Congratulations! You have won the prize pool of the gadget buy contest! ' + prizePoolValue + ' HIVE have been sent to your wallet', 'https://actifit.io/'+winner_name); + + } + }); + + + + }else{ + console.log('no ticket entries to reward'); + } + + } + + + + } + }); +} + +async function testMove(){ + + + //perform transaction, decrease sender amount + let moveTrans = { + user: 'mcfarhat', + reward_activity: 'test transaction', + token_count: -100, + note: 'User Automated transfer of 100 AFIT to S-E', + date: new Date(), + } + + console.log(moveTrans); + //update our DB + let mongo_conn = config.mongo_uri + if (config.testing){ + mongo_conn = config.mongo_local + } + // Use connect method to connect to the server + MongoClient.connect(mongo_conn, async function (err, dbClient) { + if (!err) { + console.log('Connected successfully to server: ') + + db = dbClient.db(dbName) + let transaction = await db.collection('token_transactions').insert(moveTrans); + + console.log('success inserting move AFIT data'); + + + let json_data = { + contractName: 'tokens', + contractAction: 'transfer', + contractPayload: { + symbol: 'AFIT', + to: 'mcfarhat', + quantity: '100',//needs to be string + memo: '' + } + } + + //broadcast to BC + console.log('broadcast to BC'); + + //sign key properly to function with dsteem requirement + let privateKey = dsteem.PrivateKey.fromString( + //config.token_dist_pkey + config.active_key + ); + let entry = new Object(); + + entry.user='mcfarhat'; + client.broadcast.json({ + required_auths: [config.account], + required_posting_auths: [], + id: 'ssc-mainnet1', + json: JSON.stringify(json_data), + }, privateKey).then( + result => { + console.log('success'); + console.log(result); + updateUserCount(entry); + }, + error => { + console.log('error') + console.error(error) + //roll back transaction + rollBackTrans(moveTrans); + } + ) + + } + }) + +} + +async function rollBackTrans(moveTrans){ + console.log('roll back') + try{ + let transaction = await db.collection('token_transactions').remove(moveTrans); + }catch(err){ + console.log(err); + } +} + +async function updateUserCount(entry){ + //update user total token count + console.log('>>> update user token count'); + let user_info = await db.collection('user_tokens').findOne({_id: entry.user}); + let cur_sender_token_count = parseFloat(user_info.tokens); + let new_token_count = cur_sender_token_count - parseFloat(entry.daily_afit_transfer); + user_info.tokens = new_token_count; + console.log('user:' + entry.user + 'new_token_count:'+new_token_count); + try{ + let trans = await db.collection('user_tokens').save(user_info); + console.log('success updating user token count'); + }catch(err){ + console.log(err); + } +} + +async function moveAFITToSE(testMode){ + console.log('*** process moving AFIT to SE ***'); + + let mongo_conn = config.mongo_uri + if (config.testing){ + mongo_conn = config.mongo_local + } + // Use connect method to connect to the server + MongoClient.connect(mongo_conn, async function (err, dbClient) { + if (!err) { + console.log('Connected successfully to server: ') + + db = dbClient.db(dbName) + // Get the documents collection + let poweringDown = await db.collection('powering_down_he').find().toArray(); + console.log (poweringDown) + + //sign key properly to function with dsteem requirement + let privateKey = dsteem.PrivateKey.fromString( + //config.token_dist_pkey + config.active_key + ); + + let delay = 0; + + //let's fetch banned accounts, to ensure they dont receive an airdrop + let banned_users = await db.collection('banned_accounts').find({ban_status:"active"}).toArray(); + + //loop through entries, and send over AFIT + poweringDown.forEach(async function(entry){ + + //let's make sure user is not banned + let user_banned = false; + for (let n = 0; n < banned_users.length; n++) { + if (entry.user == banned_users[n].user){ + //console.log('User '+entry.user+' is banned, skipping' ); + user_banned = true; + break; + } + } + if (!user_banned){ + + //let's make sure user still has proper AFITX amount + let userHasProperFunds = true; + let afitx_tot_bal = 0; + let afitx_se_balance = 0; + let afitx_he_balance = 0; + let bal = await ssc.findOne('tokens', 'balances', { account: entry.user, symbol: 'AFITX' }); + if (bal){ + afitx_se_balance = bal.balance; + }else{ + console.log('error - Unable to fetch S-E AFITX Funds for '+entry.user+' or funds are zero.'); + //return; + } + bal = await hsc.findOne('tokens', 'balances', { account: entry.user, symbol: 'AFITX' }); + if (bal){ + afitx_he_balance = bal.balance; + }else{ + console.log('error - Unable to fetch H-E AFITX Funds for '+entry.user+' or funds are zero.'); + //return; + } + afitx_tot_bal = parseFloat(afitx_se_balance) + parseFloat(afitx_he_balance); + //make sure user has at least 0.1 AFITX to move tokens + if (afitx_tot_bal < 0.1){ + userHasProperFunds = false; + } + //console.log(amount_to_powerdown); + //console.log(this.afitx_se_balance); + //calculate amount that can be transferred daily + if (parseFloat(entry.daily_afit_transfer) / 100 > afitx_tot_bal){ + userHasProperFunds = false; + } + + //make sure user has enough funds to send to SE + + let user = await db.collection('user_tokens').findOne({_id: entry.user}); + console.log(user); + //fixing token amount display for 3 digits + if (typeof user!= "undefined" && user!=null){ + if (typeof user.tokens!= "undefined"){ + user.tokens = user.tokens.toFixed(3) + }else{ + userHasProperFunds = false; + } + }else{ + userHasProperFunds = false; + } + let cur_user_token_count = 0; + try{ + cur_user_token_count = parseFloat(user.tokens); + if (cur_user_token_count < entry.daily_afit_transfer){ + userHasProperFunds = false; + } + }catch(err){ + userHasProperFunds = false; + } + + console.log('entry.user:'+entry.user+ ' afit bal:' + cur_user_token_count + ' bal:'+afitx_tot_bal+' userHasProperFunds:'+userHasProperFunds); + if (userHasProperFunds){ + setTimeout(async function(){ + + try{ + + /*setTimeout(async function(){ + + + }, 1);*/ + + console.log(entry); + + let amount = parseFloat(entry.daily_afit_transfer); + + //perform transaction, decrease sender amount + let moveTrans = { + user: entry.user, + reward_activity: 'Move AFIT to H-E', + token_count: -amount, + note: 'User Automated transfer of ' + entry.daily_afit_transfer + ' AFIT to H-E', + date: new Date(), + } + + console.log(moveTrans); + //update our DB + if (!testMode){ + let transaction = await db.collection('token_transactions').insert(moveTrans); + } + console.log('success inserting move AFIT data'); + + let json_data = { + contractName: 'tokens', + contractAction: 'transfer', + contractPayload: { + symbol: 'AFIT', + to: entry.user, + quantity: ''+entry.daily_afit_transfer,//needs to be string + memo: '' + } + } + + //broadcast to BC + console.log('broadcast to BC'); + if (!testMode){ + hiveClient.broadcast.json({ + required_auths: [config.account], + required_posting_auths: [], + id: 'ssc-mainnet-hive',//ssc-mainnet1 + json: JSON.stringify(json_data), + }, privateKey).then( + result => { + console.log(result) + //update user total count + updateUserCount(entry); + }, + error => { + console.error(error) + //roll back db transaction as there was issue sending to blockchain + rollBackTrans(moveTrans); + } + ) + } + + }catch(err){ + console.log(err); + console.log('error - Error inserting move AFIT data. DB storing issue'); + return; + } + + }, delay+=4500); + }else{ + console.log('error - user does not have enough funds'); + return; + } + }else{ + console.log('user ' + entry.user + ' is banned. Skip'); + } + }); + } else { + utils.log(err, 'delegations') + process.exit() + } + }) +} + +/* +//OUR AFITX AIRDROP FUNCTION +async function airdropAFITX(){ + let mongo_conn = config.mongo_uri + if (config.testing){ + mongo_conn = config.mongo_local + } + // Use connect method to connect to the server + MongoClient.connect(mongo_conn, async function (err, dbClient) { + if (!err) { + + console.log('Connected successfully to server: ') + db = dbClient.db(dbName) + + //let's fetch all users with proper AFIT count + let tokenHolders = await db.collection('user_tokens').find( { tokens: { $gte: 100 } }).toArray();//sort({tokens: -1}). + + //let's also fetch banned accounts, to ensure they dont receive an airdrop + let banned_users = await db.collection('banned_accounts').find({ban_status:"active"}).toArray(); + + let delay = 0; + + let totalAFITXSpent = 0; + let totalUsersRewarded = 0; + + //sign key properly to function with dsteem requirement + let privateKey = dsteem.PrivateKey.fromString( + //config.token_dist_pkey + config.active_key + ); + + tokenHolders.forEach(function(entry){ + //check if user is banned + //check if user is banned + let user_banned = false; + for (let n = 0; n < banned_users.length; n++) { + if (entry.user == banned_users[n].user){ + //console.log('User '+entry.user+' is banned, skipping' ); + user_banned = true; + break; + } + } + if (!user_banned){ + + setTimeout(function(){ + console.log(entry); + let rewardAFITX = 0; + let userAFIT = parseFloat(entry.tokens); + if (userAFIT >= 10000){ + rewardAFITX = 10; + }else if (userAFIT >= 100){ + rewardAFITX = (userAFIT/1000).toFixed(3); + } + let json_data = { + contractName: 'tokens', + contractAction: 'transfer', + contractPayload: { + symbol: 'AFITX', + to: entry.user, + quantity: '' + rewardAFITX,//needs to be string + memo: '' + } + } + console.log(json_data); + totalAFITXSpent += parseFloat(rewardAFITX); + totalUsersRewarded += 1; + client.broadcast.json({ + //required_auths: [config.token_dist_account], + required_auths: [config.account], + required_posting_auths: [], + id: 'ssc-mainnet1', + json: JSON.stringify(json_data), + }, privateKey).then( + result => { console.log(result) }, + error => { console.error(error) } + ) + //console.log('total airdrop:'+totalAFITXSpent); + //console.log('total recipients:'+totalUsersRewarded); + + }, delay+=3300); + } + }); + + + }else { + utils.log(err, 'delegations') + process.exit() + } + }); +} + +*/ + +function runRewards(steemOnlyReward){ + let mongo_conn = config.mongo_uri + if (config.testing){ + mongo_conn = config.mongo_local + } + // Use connect method to connect to the server + MongoClient.connect(mongo_conn, async function (err, dbClient) { + if (!err) { + console.log('Connected successfully to server: ') + + db = dbClient.db(dbName) + // Get the documents collection + collection = db.collection(delegationTrxCol) + + /**** copy a collection to another *****/ + /*let documentsToMove = db.collection(delegationTrxCol).find({}); + documentsToMove.forEach(function(doc) { + console.log('inserting'); + db.collection(hiveDelegationTrxCol).insert(doc); + }); + console.log('done'); + + return;*/ + + bulk_delegation_entries = db.collection(delegationTrxCol).initializeUnorderedBulkOp(); + bulk_hive_delegation_entries = db.collection(hiveDelegationTrxCol).initializeUnorderedBulkOp(); + + //updateUserTokens(); + //return; + + //run for one day + var delegation_days = 1; + startProcess(delegation_days, steemOnlyReward); + + //grab steem prices and proceed checking for beneficiary payouts to AFIT token reward account (full_pay_benef_account) + setInterval(loadSteemPrices,5 * 60 * 1000); + + //claim rewards once per hour + setInterval(claimRewards,60 * 60 * 1000); + + //loadSteemPrices(); + } else { + utils.log(err, 'delegations') + mail.sendPlainMail('Database Error', err, config.report_emails) + .then(function (res, err) { + if (!err) { + console.log(res) + } else { + utils.log(err, 'import') + } + }) + process.exit() + } + }) +} + +//function to grab latest payouts for beneficiaries and reward with AFIT tokens +async function getBenefactorPosts (account, start) { + + //connect to the token_transactions table to start transactions to users + var bulk_transactions = db.collection('token_transactions').initializeUnorderedBulkOp(); + + let totalSBD = 0 + let totalSp = 0 + let limit = 2000; + let txStart = -1; + + start = moment(start).format() + + console.log('start date:'+start) + + //grab current AFIT price in USD + let curAFITPrice = await db.collection('afit_price').find().sort({'date': -1}).limit(1).next() + console.log('curAfitPrice:'+curAFITPrice.unit_price_usd); + + // Query account history for delegations + properties = await nodeLink.database.getDynamicGlobalProperties() + totalSteem = Number(properties.total_vesting_fund_steem.split(' ')[0]) + totalVests = Number(properties.total_vesting_shares.split(' ')[0]) + //console.log(properties); + const transactions = await client.database.call('get_account_history', [account, txStart, limit]) + transactions.reverse() + let foundTx = false; + console.log("newestTxId:"+newestTxId); + let counter = 0; + for (let txs of transactions) { + if (counter == 0){ + counter += 1; + //if this is not our first run, start from where we left + if (newestTxId==txs[0]){ + //no new transactions, bail + console.log('we already went through last transaction'); + break; + }else{ + newestTxId = txs[0]; + console.log('set newestTxId:'+newestTxId); + } + } + let date = moment(txs[1].timestamp).format() + + if (date >= start) { + //console.log(txs[0]); + let op = txs[1].op + // Look for beneficiary payments + if (op[0] === 'comment_benefactor_reward') { + foundTx = true; + console.log('---------------------------------------'); + //console.log(op); + let matchingAFIT = 0; + //console.log(op[1]); + let rewardedSP = parseFloat(vestsToSteemPower(op[1].vesting_payout).toFixed(3)) + console.log("rewardedSP:"+rewardedSP); + //calculate dollar value + let steemInUSD = rewardedSP * steemPrice; + console.log("steemInUSD:"+steemInUSD); + + //convert to AFIT and add to total + matchingAFIT = steemInUSD / curAFITPrice.unit_price_usd; + console.log("matchingAFIT:"+matchingAFIT); + + let rewardedSTEEM = parseFloat(op[1].steem_payout.split(' ')[0]) + + console.log("rewardedSTEEM:"+rewardedSTEEM); + let steemPureInUSD = rewardedSTEEM * steemPrice; + + let steemPureToAFIT = steemPureInUSD / curAFITPrice.unit_price_usd; + matchingAFIT += steemPureToAFIT; + + let rewardedSBD = parseFloat(op[1].sbd_payout.split(' ')[0]) + + console.log("rewardedSBD:"+rewardedSBD); + + //calculate dollar value + let sbdInUSD = rewardedSBD * sbdPrice; + console.log("sbdInUSD:"+sbdInUSD); + + //convert to AFIT and add to total + let sbdToAFIT = sbdInUSD / curAFITPrice.unit_price_usd; + matchingAFIT += sbdToAFIT; + + //format to 3 decimals + matchingAFIT = parseFloat(matchingAFIT.toFixed(3)); + + console.log("sbdToAFIT:"+sbdToAFIT); + + console.log("Total AFIT:"+matchingAFIT); + + let beneficSwapTansaction = { + user: op[1].author, + reward_activity: 'Full AFIT Payout', + url: op[1].permlink, + token_count: matchingAFIT, + orig_sbd_amount: rewardedSBD, + orig_sp_amount: rewardedSP, + orig_steem_amount: rewardedSTEEM, + date: new Date(date) + } + + //store this as a transaction + bulk_transactions.find( + { + user: beneficSwapTansaction.user, + reward_activity: beneficSwapTansaction.reward_activity, + url: beneficSwapTansaction.url + }).upsert().replaceOne(beneficSwapTansaction); + + } + } else if (date < start){ + break + } + } + //award transaction tokens + if (foundTx){ + bulk_transactions.execute(); + console.log('-- Processed Full AFIT Payouts --') + //once done, update user total token count + updateUserTokens(); + }else{ + console.log('-- No Posts to process --'); + } + +} + +//function to load relevant STEEM and SBD prices, and proceed with AFIT token swap/reward process +function loadSteemPrices() { + + console.log('-- start AFIT token swap process --') + + // Require the "request" library for making HTTP requests + var request = require("request"); + + // Load the price feed data + request.get('https://api.coinmarketcap.com/v1/ticker/steem/', function (e, r, data) { + try { + steemPrice = parseFloat(JSON.parse(data)[0].price_usd); + + console.log("Loaded STEEM price: " + steemPrice); + + // Load the price feed data + request.get('https://api.coinmarketcap.com/v1/ticker/steem-dollars/', function (e, r, data) { + try { + sbdPrice = parseFloat(JSON.parse(data)[0].price_usd); + + console.log("Loaded SBD price: " + sbdPrice); + + let afit_swap_days = 1; + let start = moment().utc().startOf('date').toDate() + let to = moment(start).subtract(afit_swap_days, 'days').toDate() + + //bring the action + getBenefactorPosts(config.full_pay_benef_account, to); + + } catch (err) { + console.log('Error loading SBD price: ' + err); + } + }); + + } catch (err) { + console.log('Error loading STEEM price: ' + err); + } + }); +} + + +async function startProcess (days, steemOnlyReward) { + let end = 0 + // Find last saved delegation transaction + //let lastTx = await collection.find().sort({'tx_number': -1}).limit(1).next() + console.log('last recorded delegation transaction'); + //console.log(lastTx) + //if (lastTx) end = lastTx.tx_number + await updateProperties() + if (!testRun){ + //update Steem delegations + console.log('>>>>>>>>>>STEEM<<<<<<<<<<<<'); + await processDelegations(client, bulk_delegation_entries, delegationTrxCol, actDelgCol, config.account, -1, end) + + //update hive delegations + console.log('>>>>>>>>>>HIVE<<<<<<<<<<<<'); + await processDelegations(hiveClient, bulk_hive_delegation_entries, hiveDelegationTrxCol, hiveActDelgCol, config.account, -1, end) + } + //TEMP BREAK + //return; + + + let start = moment().utc().startOf('date').subtract(days, 'days').toDate() + let txEnd = moment().utc().startOf('date').toDate() + if (!steemOnlyReward){ + console.log('processTokenRewards'); + //steem based rewards + await processTokenRewards('STEEM', client, bulk_delegation_entries, delegationTrxCol, actDelgCol, start, txEnd, days) + + //hive based rewards + await processTokenRewards('HIVE', hiveClient, bulk_hive_delegation_entries, hiveDelegationTrxCol, hiveActDelgCol, start, txEnd, days) + //update our user token count post reward + if (!testRun){ + updateUserTokens(); + } + } + var d = new Date(); + var dayId = d.getDay(); + // Check if today is Monday, to calculate steem rewards + if (dayId == 1){ + //console.log('processSteemRewards'); + //processTokenRewards (chain, nodeLink, dbDelegLink, delTrxCol, activeDelColLink, start, end, days) { + let resSt = await processSteemRewards('STEEM', client, bulk_delegation_entries, delegationTrxCol, actDelgCol, txEnd) + //console.log('>>>>>STEEM REWARDS COMPLETE'); + let resHv = await processSteemRewards('HIVE', hiveClient, bulk_hive_delegation_entries, hiveDelegationTrxCol, hiveActDelgCol, txEnd) + //console.log('>>>>>HIVE REWARDS COMPLETE'); + } +} + +async function processTokenRewards (chain, nodeLink, dbDelegLink, delTrxCol, activeDelColLink, start, end, days) { + if (!start) start = moment().utc().startOf('date').subtract(days, 'days').toDate() + if (!end) end = moment().utc().startOf('date').toDate() + let note = 'Delegation Reward On ' + chain + ' For ' + moment(end).subtract(1, 'days').format('MMMM Do YYYY') + + let acumulatedSteemPower = await getAcumulatedSteemPower(nodeLink, dbDelegLink, delTrxCol, activeDelColLink, start, end, config.exclude_enabled); + + //console.log(acumulatedSteemPower.users); + + //handles maintaining max CAP for payments + let multiplier = 1 + + let currentSteemPower = await getCurrentTotalSP(activeDelColLink, end); + console.log("currentSteemPower:"+currentSteemPower); + + //check if max CAP is reached, and apply multplier accordingly + if (currentSteemPower > config.weekly_rewards_limit) { + multiplier = config.weekly_rewards_limit / currentSteemPower; + console.log(">>>>went beyond rewards limit. Apply multiplier"); + } + console.log(">>>>multiplier:"+multiplier); + + //load list of alt accounts to reward them instead of actual delegators + let altAccounts = await db.collection('delegation_alt_beneficiaries').find().toArray(); + //console.log(altAccounts); + + //go through all delegators, and send out AFIT rewards + for (let user of acumulatedSteemPower.users) { + + //skip opt out users from reward + var user_opted_out = false; + for (var n = 0; n < config.exclude_rewards.length; n++) { + if (user.user == config.exclude_rewards[n]){ + console.log('User '+user.user+' opted out from rewards'); + user_opted_out = true; + break; + } + } + if (user_opted_out){ + continue; + } + + let reward_user = user.user; + let reward_activity = 'Delegation'; + + //check if this user has an alt account with delegated rewards enabled + let delegator_entry = _.find(altAccounts, {'delegator': user.user, 'reward_benefit': '1'}); + + //if so reward the alt account instead + if (delegator_entry != null) { + reward_user = delegator_entry.alt_account; + reward_activity += ' On Behalf'; + } + + let reward = { + user: reward_user, + chain: chain, + token_count: parseFloat((user.totalSteem * multiplier).toFixed(3)), + reward_activity: reward_activity, + orig_account: user.user, + note: note, + date: end + } + //console.log(reward) + //only send out funds if not a test run + if (!testRun){ + upsertRewardTransaction(reward) + } + } +} + +async function processSteemRewards (chain, nodeLink, dbDelegLink, delTrxCol, activeDelColLink, start) { + if (!start) start = moment().utc().startOf('date').toDate() + // Get active delegations for the week + console.log(config.pay_account) + const to = moment(start).subtract(7, 'days').toDate() + const from = moment(to).subtract(7, 'days').toDate() + + //load list of alt accounts to reward them instead of actual delegators + let altAccounts = await db.collection('delegation_alt_beneficiaries').find().toArray(); + console.log('loading alt accounts'); + //console.log(altAccounts); + + Promise.all( + [ + getAcumulatedSteemPower(nodeLink, dbDelegLink, delTrxCol, activeDelColLink, from, to, config.exclude_enabled), //(nodeLink, dbDelegLink, delTrxCol, activeDelColLink, start, end, config.exclude_enabled); + getBenefactorRewards(nodeLink, to, start, -1) + ] + ).then(values => { + const activeDelegations = values[0].users + //console.log('***'); + //console.log(values[1]); + const steemRewards = values[1].split(' ')[0] + const sbdRewards = values[1].split(' ')[1] + const totalDelegatedSteem = values[0].totalSteem + const rewardPerSteem = steemRewards / totalDelegatedSteem + const rewardPerSBD = sbdRewards / totalDelegatedSteem + const rewards = _.map(activeDelegations, function (o) { + //skip opt out users from reward + var user_opted_out = false; + for (var n = 0; n < config.exclude_rewards.length; n++) { + if (o.user == config.exclude_rewards[n]){ + console.log('User '+o.user+' opted out from Steem rewards'); + user_opted_out = true; + break; + } + } + + + let reward_user = o.user; + + //check if this user has an alt account with delegated rewards enabled + let delegator_entry = _.find(altAccounts, {'delegator': o.user, 'steem_reward_benefit': '1'}); + + //if so reward the alt account instead + if (delegator_entry != null) { + reward_user = delegator_entry.alt_account; + } + + let reward = {}; + if (!user_opted_out){ + reward = { + user: reward_user, + steem: +(o.totalSteem * rewardPerSteem).toFixed(3), + sbd: +(o.totalSteem * rewardPerSBD).toFixed(3) + } + } + + + + /*let url = 'https://v2.steemconnect.com/sign/transfer?from=[PAY_ACCOUNT]&to=[TO_ACCOUNT]&amount=[AMOUNT]%20STEEM&memo=Delegation%20Rewards' + url = url.replace('[PAY_ACCOUNT]', config.pay_account) + url = url.replace('[TO_ACCOUNT]', reward.user) + url = url.replace('[AMOUNT]', reward.steem) + reward.url = url*/ + return reward + }) + //console.log(rewards) + console.log("steem total beneficiary reward:"+steemRewards) + console.log("SBD total beneficiary reward:"+sbdRewards) + const data = { + rewards: rewards, + totalSteem: steemRewards, + totalSBD: sbdRewards, + totalUsers: rewards.length + } + + var fs = require('fs'); + + var fileName = moment(moment().utc().startOf('date').toDate()).format('YYYY-MM-DD'); + fileName = chain+"rewards"+fileName+".json"; + console.log("fileName:"+fileName); + fs.writeFile(fileName, JSON.stringify(rewards), function(err) { + if(err) { + return console.log(err); + } + + console.log("The file was saved!"); + }); + /* + const attachment = { + filename: 'rewards.json', + content: JSON.stringify(rewards) + } + mail.sendWithTemplate('Rewards mail', data, config.report_emails, 'rewards', attachment)*/ + }) +} + + + + +function vestsToSteemPower (vests) { + vests = Number(vests.split(' ')[0]) + const steemPower = (totalSteem * (vests / totalVests)) + return steemPower +} + + +async function processDelegations (nodeLink, dbDelegLink, delTrxCol, activeDelColLink, account, start, end) { + let delegationTransactions = [] + let lastTrans = start + let ended = false + let limit = (start < 0) ? 3000 : Math.min(start, 3000) + console.log('Account: ' + account + ' - Start: ' + start + ' - Limit: ' + limit + ' - Last Txs: ' + end) + try { + // Query account history for delegations + const transactions = await nodeLink.database.call('get_account_history', [account, start, limit]) + transactions.reverse() + for (let txs of transactions) { + //let's only fetch a max of 5 days ago delegation transactions + let tx_date = moment(txs[1].timestamp).format() + + //today + let start = moment().utc().startOf('date').toDate() + + let to = moment(start).subtract(6, 'days').toDate() + let end = moment(to).format() + + if (txs[0] === end || tx_date < end) { + console.log('--- Found last transaction ---') + ended = true + break + } + let op = txs[1].op + lastTrans = txs[0] + // Look for delegation operations + if (op[0] === 'delegate_vesting_shares' && op[1].delegatee === account) { + //console.log(txs); + // Calculate in steem power + const steemPower = vestsToSteemPower(op[1].vesting_shares) + let data = op[1] + data.steem_power = +steemPower.toFixed(3) + data.tx_number = txs[0] + data.tx_date = new Date(txs[1].timestamp) + delegationTransactions.push(data) + + dbDelegLink.find( + { + delegator: data.delegator, + vesting_shares: data.vesting_shares, + }).upsert().replaceOne(data); + } + } + // Insert new transactions and update active ones + if (delegationTransactions.length > 0) { + try{ + await dbDelegLink.execute(); + }catch(bulkerr){ + utils.log(bulkerr); + } + //update relevant delegations collection + await updateActiveDelegations(delTrxCol, activeDelColLink) + + } else { + console.log('--- No new delegations ---') + return; + } + // If more pending delegations call process againg with new index + if (start !== limit && !ended){ + return processDelegations(nodeLink, dbDelegLink, delTrxCol, activeDelColLink, account, lastTrans, end) + } + // console.log(transactions) + return; + } catch (err) { + console.log(err) + // Consider exponential backoff if extreme cases start happening + if (err.type === 'request-timeout' || err.type === 'body-timeout'){ + return processDelegations(nodeLink, dbDelegLink, delTrxCol, activeDelColLink, account, start, end); + } + } +} + +async function getBenefactorRewards (nodeLink, start, end, txStart, totalSp, totalSBD) { + if (!totalSBD) totalSBD = 0 + if (!totalSp) totalSp = 0 + let limit = (txStart < 0) ? 10000 : Math.min(txStart, 10000) + start = moment(start).format() + end = moment(end).format() + console.log(start) + console.log(end) + // Query account history for delegations + properties = await nodeLink.database.getDynamicGlobalProperties() + totalSteem = Number(properties.total_vesting_fund_steem.split(' ')[0]) + totalVests = Number(properties.total_vesting_shares.split(' ')[0]) + const transactions = await nodeLink.database.call('get_account_history', [config.pay_account, txStart, limit]) + transactions.reverse() + for (let txs of transactions) { + let date = moment(txs[1].timestamp).format() + if (date >= start && date <= end) { + let op = txs[1].op + // Look for delegation operations + if (op[0] === 'comment_benefactor_reward') { + //console.log(op[1]); + //SP is the sum of conversting vesting payout to SP, and appending any STEEM payouts + let newSp = vestsToSteemPower(op[1].vesting_payout) + parseFloat(op[1].steem_payout.split(' ')[0]) + totalSp = totalSp + newSp + let newSBD = op[1].sbd_payout.split(' ')[0] + totalSBD += parseFloat(newSBD) + } + } else if (date < start) break + } + // Check last tx date to see if pagination is needed + let lastTx = transactions[transactions.length - 1] + let lastDate = moment(lastTx[1].timestamp).format() + // console.log(lastDate) + if (lastDate >= start) return getBenefactorRewards(nodeLink, start, totalSp, lastTx[0]) + + console.log('-- Processed rewards ---') + // console.log(totalSp.toFixed(3)) + return +totalSp.toFixed(3)+' ' +totalSBD.toFixed(3) +} + +async function getActiveDelegations (delTrxCol, start, excludeOn) { + start = new Date(start) + if (excludeOn){ + return db.collection(delTrxCol).aggregate( + [ + { $match: { 'tx_date': { '$lte': start } } }, + { $sort: { delegator: 1, tx_date: 1 } }, + { + $group: + { + _id: '$delegator', + steem_power: { $last: '$steem_power' }, + vests: { $last: '$vesting_shares' }, + tx_date: { $last: '$tx_date' } + } + }, + { $project: + { + _id: '$_id', + delegator: '$_id', + steem_power: 1, + tx_date: start + } + }, + { $match: { 'steem_power': { '$gt': 0 }, 'delegator': {$nin: config.exclude_rewards} } }, + { $sort: { tx_date: 1 } } + ] + ).toArray() + }else{ + return db.collection(delTrxCol).aggregate( + [ + { $match: { 'tx_date': { '$lte': start } } }, + { $sort: { delegator: 1, tx_date: 1 } }, + { + $group: + { + _id: '$delegator', + steem_power: { $last: '$steem_power' }, + vests: { $last: '$vesting_shares' }, + tx_date: { $last: '$tx_date' } + } + }, + { $project: + { + _id: '$_id', + delegator: '$_id', + steem_power: 1, + tx_date: start + } + }, + { $match: { 'steem_power': { '$gt': 0 } } }, + { $sort: { tx_date: 1 } } + ] + ).toArray() + } +} + +/* + * function handles grabbing the total current SP value before a specific date + * params: toDate - date before which all current SP is calculated + * returns: total value of current SP count up to passed date + */ +async function getCurrentTotalSP(actDelgCol, toDate){ + toDate = moment(toDate).toDate() + + //perform an aggregation based on max date, exluded delegators, and return back sum of SP and delegator count (we only need for now totalSP) + var results = await db.collection(actDelgCol).aggregate([ + { + $match: + { + 'tx_date': {$lt: toDate}, + 'delegator': {$nin: config.exclude_rewards} + } + }, + { + $group: + { + _id: null, + totalSP: { $sum: "$steem_power" }, + totalDelegators: { $sum: 1 } + } + } + ]).toArray(); + //function(err, results) { + //var output = 'tokens distributed:'+results[0].totalSP; + //console.log(results); + return results[0].totalSP; + //}); +} + +async function getAcumulatedSteemPower (nodeLink, dbDelegLink, delTrxCol, activeDelColLink, from, to, excludeOn) { + let result = { + users: [] + } + let totalSteem = 0 + from = moment(from).toDate() + to = moment(to).toDate() + //console.log('get Acc Power'); + //console.log(activeDelColLink); + // Get active delegations for the week + let activeDelegations = await getActiveDelegations(delTrxCol, from, excludeOn) + //console.log(activeDelegations); + // Get transactions of the processed week + let weekTxs + if (excludeOn){ + console.log('excluding users'); + weekTxs = await db.collection(delTrxCol).find( + {'tx_date': {$gt: from, $lt: to}, + 'delegator': {$nin: config.exclude_rewards}}) + .sort({tx_date: 1}).toArray() + }else{ + console.log('no exclude'); + weekTxs = await db.collection(delTrxCol).find( + {'tx_date': {$gt: from, $lt: to}}) + .sort({tx_date: 1}).toArray() + } + //console.log(weekTxs); + let allTxs = activeDelegations.concat(weekTxs) + let groupedTxs = _.groupBy(allTxs, 'delegator') + for (let index in groupedTxs) { + let totalReward = 0 + for (let i = 0; i < groupedTxs[index].length; i++) { + let txs = groupedTxs[index][i] + let endTxs + // If not last transaction calculate up to next one + if (i !== groupedTxs[index].length - 1) endTxs = new Date(groupedTxs[index][i + 1].tx_date) + else endTxs = to + var activeHours = Math.abs(txs.tx_date - endTxs) / 36e5 + let newReward = activeHours * (txs.steem_power / 24) + totalReward = totalReward + newReward + } + totalSteem = totalSteem + totalReward + let user = { + user: index, + totalSteem: totalReward + } + result.users.push(user) + } + result.totalSteem = totalSteem + return result +} + +async function updateActiveDelegations (delgTrxCol, targetCol) { + console.log('--- Updating active delegations ---') + let query = db.collection(delgTrxCol).aggregate( + [ + { $sort: { delegator: 1, tx_date: 1 } }, + { + $group: + { + _id: '$delegator', + steem_power: { $last: '$steem_power' }, + vests: { $last: '$vesting_shares' }, + tx_date: { $last: '$tx_date' } + } + }, + { $match: { 'steem_power': { '$gt': 0 } } } + ] + ) + console.log('collections'); + console.log(delgTrxCol); + console.log(targetCol); + let activeDelegations = await query.toArray() + console.log('activeDelegations fetched'); + try{ + await db.collection(targetCol).drop() + }catch(err){ + console.log(err); + } + console.log('activeDelegations dropped'); + await db.collection(targetCol).insert(activeDelegations) + console.log('done updating delegations '+targetCol); + return ; +} + +function upsertRewardTransaction (reward) { + return db.collection('token_transactions').update( + { user: reward.user, chain: reward.chain, date: reward.date, reward_activity: reward.reward_activity, orig_account: reward.orig_account }, + reward, + { upsert: true } + ) +} + + +async function updateProperties () { + // Set STEEM global properties + properties = await client.database.getDynamicGlobalProperties() + totalSteem = Number(properties.total_vesting_fund_steem.split(' ')[0]) + totalVests = Number(properties.total_vesting_shares.split(' ')[0]) +} + +//function handles updating current user token count +async function updateUserTokens() { + console.log('---- Updating User Tokens ----'); + + try{ + //group all token transactions per user, and sum them to generate new total count + let query = await db.collection('token_transactions').aggregate([ + { $group: { _id: "$user", tokens: { $sum: "$token_count" } } }, + { $sort: { tokens: -1 } }, + { $project: { + _id: "$_id", + user: "$_id", + tokens: "$tokens", + } + } + ]) + + let user_tokens = await query.toArray(); + //remove old token count per user + await db.collection('user_tokens').remove({}); + //insert new count per user + await db.collection('user_tokens').insert(user_tokens); + console.log('---- Updating User Tokens Complete ----'); + }catch(err){ + console.log('>>save data error:'+err.message); + } +} +//function handles fetching account details for later use when claiming rewards +async function grabAccountDetails(){ + console.log('grabbing fund account details'); + let account = await client.database.call('get_accounts', [[config.full_pay_benef_account]]); + console.log(account); + return account[0]; +} +//function handles claiming pending account rewards +async function claimRewards(){ + //sign key properly to function with dsteem requirement + let privateKey = dsteem.PrivateKey.fromString( + config.full_pay_posting_key + ); + //fetch account details first to use correct values for claim + let funds_account = await grabAccountDetails(); + console.log(funds_account.reward_steem_balance); + console.log(funds_account.reward_sbd_balance); + console.log(funds_account.reward_vesting_balance); + //if we have any value to claim, proceed + if (parseFloat(funds_account.reward_steem_balance) > 0 || parseFloat(funds_account.reward_sbd_balance) > 0 || parseFloat(funds_account.reward_vesting_balance) > 0) { + const op = [ + 'claim_reward_balance', + { + account: config.full_pay_benef_account, + reward_steem: funds_account.reward_steem_balance.split(' ')[0] + ' STEEM', + reward_sbd: funds_account.reward_sbd_balance.split(' ')[0] + ' SBD', + reward_vests: funds_account.reward_vesting_balance.split(' ')[0] + ' VESTS', + }, + ]; + client.broadcast.sendOperations([op], privateKey).then( + function(result) { + console.log(result); + }, + function(error) { + console.log(error); + } + ) + }else{ + console.log('no rewards to claim for now'); + } +} \ No newline at end of file diff --git a/mail.js b/mail.js index cbfe2de..22adbac 100644 --- a/mail.js +++ b/mail.js @@ -45,7 +45,7 @@ function sendPlainMail(subject, message, to) { } -function sendWithTemplate(subject, data, to, template) { +function sendWithTemplate(subject, data, to, template, attachment) { if(Array.isArray(to)) to = to.join(','); @@ -68,13 +68,16 @@ function sendWithTemplate(subject, data, to, template) { mailOptions.to = to; mailOptions.template = template; mailOptions.context = data; + if (attachment) { + mailOptions.attachments = [attachment] + } // send mail with defined transport object return new Promise((resolve, reject) => { transporter.sendMail(mailOptions, (error, info) => { if (error) { console.log(error); - return reject(errir); + return reject(error); } else { console.log('Message sent: %s', info.messageId); // Message sent: diff --git a/package-lock.json b/package-lock.json index 72e33ab..8da437e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,133 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@hiveio/dhive": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@hiveio/dhive/-/dhive-0.14.1.tgz", + "integrity": "sha512-80I1alxQW5Cnt61KvAuSa+dPH6V+JNrE/0vR+DgQGH9y8l3xmn+pr4TXNLtSvx37pI8Zt1UEPsgvren/R02WeQ==", + "requires": { + "bs58": "^4.0.1", + "bytebuffer": "^5.0.1", + "core-js": "^3.6.4", + "cross-fetch": "^3.0.4", + "node-fetch": "^2.6.0", + "secp256k1": "^3.8.0", + "verror": "^1.10.0", + "whatwg-fetch": "^3.0.0" + }, + "dependencies": { + "core-js": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "cross-fetch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.5.tgz", + "integrity": "sha512-FFLcLtraisj5eteosnX1gf01qYDCOc4fDy0+euOt8Kn9YBY2NtXL/pCoYPavw24NIQkQqm5ZOLsGD5Zzj0gyew==", + "requires": { + "node-fetch": "2.6.0" + } + }, + "nan": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==" + }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "secp256k1": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.8.0.tgz", + "integrity": "sha512-k5ke5avRZbtl9Tqx/SA7CbY3NF6Ro+Sj9cZxezFzuBlLDmyqPiL8hJJ+EmzD8Ig4LUDByHJ3/iPOVoRixs/hmw==", + "requires": { + "bindings": "^1.5.0", + "bip66": "^1.1.5", + "bn.js": "^4.11.8", + "create-hash": "^1.2.0", + "drbg.js": "^1.0.1", + "elliptic": "^6.5.2", + "nan": "^2.14.0", + "safe-buffer": "^5.1.2" + } + }, + "whatwg-fetch": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.2.0.tgz", + "integrity": "sha512-SdGPoQMMnzVYThUbSrEvqTlkvC1Ux27NehaJ/GUHBfNrh5Mjg+1/uRyFMwVnxO2MrikMWvWAqUGgQOfVU4hT7w==" + } + } + }, + "@hiveio/hive-js": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@hiveio/hive-js/-/hive-js-0.8.2.tgz", + "integrity": "sha512-059D7ElagsTbImVLJIKonLAEOS2WLloR85S/VocrwpQ5Q6EGtytX+VGbAOaQssab7JucD2gaLrrRghnsQff0QA==", + "requires": { + "@steemit/rpc-auth": "^1.1.1", + "bigi": "^1.4.2", + "bluebird": "^3.4.6", + "browserify-aes": "^1.0.6", + "bs58": "^4.0.0", + "buffer": "^5.0.6", + "bytebuffer": "^5.0.1", + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "cross-env": "^5.0.0", + "cross-fetch": "^1.1.1", + "debug": "^2.6.8", + "detect-node": "^2.0.3", + "ecurve": "^1.0.5", + "lodash": "^4.16.4", + "retry": "^0.12.0", + "secure-random": "^1.1.2", + "ws": "^3.3.2" + }, + "dependencies": { + "secure-random": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/secure-random/-/secure-random-1.1.2.tgz", + "integrity": "sha512-H2bdSKERKdBV1SwoqYm6C0y+9EA94v6SUBOWO8kDndc4NoUih7Dv6Tsgma7zO1lv27wIvjlD0ZpMQk7um5dheQ==" + } + } + }, + "@steemit/libcrypto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@steemit/libcrypto/-/libcrypto-1.0.1.tgz", + "integrity": "sha512-g2y4OrELuPGLLu3GjVaPbVvY/K+4oPGOrv9ec013o/ZB76R9UQ1ufYD9RM5tKxHXpFhzj2k0JgoKYWkdVheFVA==" + }, + "@steemit/rpc-auth": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@steemit/rpc-auth/-/rpc-auth-1.1.1.tgz", + "integrity": "sha512-Eb8BW3O1y4+/+Dbf+LqGVmgXYqyfHxP9mBlmzkpjXiIepTpxoK90NIGrneqcnEGq0TR2nSt4BVv9Ur9c+hxoig==", + "requires": { + "@steemit/libcrypto": "^1.0.1" + } + }, + "@types/node": { + "version": "12.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.7.tgz", + "integrity": "sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w==" + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -20,31 +147,16 @@ } }, "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", + "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "requires": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" - } - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" - }, "ansi-align": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", @@ -114,9 +226,12 @@ "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } }, "assert-plus": { "version": "1.0.0", @@ -129,11 +244,6 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" - }, "async-each": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", @@ -162,17 +272,24 @@ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, "axios": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz", - "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz", + "integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==", "requires": { - "follow-redirects": "^1.3.0", - "is-buffer": "^1.1.5" + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" + } } }, "balanced-match": { @@ -255,10 +372,9 @@ "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==" }, "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "optional": true, + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "requires": { "tweetnacl": "^0.14.3" } @@ -274,11 +390,32 @@ "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", "dev": true }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bip66": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz", + "integrity": "sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "bluebird": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + }, "body-parser": { "version": "1.18.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", @@ -296,6 +433,11 @@ "type-is": "~1.6.15" } }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, "boxen": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", @@ -357,6 +499,11 @@ } } }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, "browserify-aes": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.1.1.tgz", @@ -392,6 +539,11 @@ "ieee754": "^1.1.4" } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", @@ -421,18 +573,23 @@ "get-value": "^2.0.6", "has-value": "^1.0.0", "isobject": "^3.0.1", - "set-value": "^2.0.0", + "set-value": ">=2.0.1", "to-object-path": "^0.3.0", "union-value": "^1.0.0", "unset-value": "^1.0.0" + }, + "dependencies": { + "set-value": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-3.0.2.tgz", + "integrity": "sha512-npjkVoz+ank0zjlV9F47Fdbjfj/PfXyVhZvGALWsyIYU/qrMzpi6avjKW3/7KeSU2Df3I46BrN1xOI1+6vW0hA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } } }, - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", - "optional": true - }, "capture-stack-trace": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", @@ -444,16 +601,6 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "optional": true, - "requires": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" - } - }, "chalk": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", @@ -465,6 +612,19 @@ "supports-color": "^5.3.0" } }, + "cheerio": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", + "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.1", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" + } + }, "chokidar": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", @@ -530,30 +690,6 @@ "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", "dev": true }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "optional": true, - "requires": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", - "wordwrap": "0.0.2" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", - "optional": true - } - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" - }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -580,13 +716,19 @@ "dev": true }, "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "requires": { "delayed-stream": "~1.0.0" } }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "optional": true + }, "component-emitter": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", @@ -610,6 +752,17 @@ "unique-string": "^1.0.0", "write-file-atomic": "^2.0.0", "xdg-basedir": "^3.0.0" + }, + "dependencies": { + "dot-prop": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.1.tgz", + "integrity": "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==", + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } + } } }, "content-disposition": { @@ -638,6 +791,11 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", "dev": true }, + "core-js": { + "version": "2.6.10", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.10.tgz", + "integrity": "sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA==" + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -676,6 +834,15 @@ "sha.js": "^2.4.8" } }, + "cron-parser": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-2.5.0.tgz", + "integrity": "sha512-gzmXu16/prizIbKPPKJo+WgBpV7k8Rxxu9FgaANW+vx5DebCXavfRqbROjKkr9ETvVPqs+IO+NXj4GG/eLf8zQ==", + "requires": { + "is-nan": "^1.2.1", + "moment-timezone": "^0.5.0" + } + }, "cross-env": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.1.3.tgz", @@ -710,6 +877,22 @@ "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", "dev": true }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -726,12 +909,6 @@ "ms": "2.0.0" } }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "optional": true - }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", @@ -820,13 +997,66 @@ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.3.tgz", "integrity": "sha1-ogM8CcyOFY03dI+951B4Mr1s4Sc=" }, - "dot-prop": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", - "dev": true, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", "requires": { - "is-obj": "^1.0.0" + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "drbg.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz", + "integrity": "sha1-Pja2xCs3BDgjzbwzLVjzHiRFSAs=", + "requires": { + "browserify-aes": "^1.0.6", + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4" + } + }, + "dsteem": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/dsteem/-/dsteem-0.10.1.tgz", + "integrity": "sha512-IF8yMagK+id3qHABEmQSrn7FjNiCTZzbohzig8jE09TBUpwePBxx+1UUedms/EY4oUlziaDa6h7Znb/Pp2dXgQ==", + "requires": { + "bs58": "^4.0.1", + "bytebuffer": "^5.0.1", + "core-js": "^2.5.0", + "node-fetch": "^2.1.2", + "secp256k1": "^3.3.1", + "verror": "^1.10.0", + "whatwg-fetch": "^2.0.3" + }, + "dependencies": { + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + } } }, "duplexer": { @@ -842,12 +1072,20 @@ "dev": true }, "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "optional": true, + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "requires": { - "jsbn": "~0.1.0" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" } }, "ecurve": { @@ -864,6 +1102,20 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "elliptic": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -877,6 +1129,11 @@ "iconv-lite": "~0.4.13" } }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1017,9 +1274,9 @@ } }, "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extend-shallow": { "version": "3.0.2", @@ -1114,20 +1371,25 @@ } }, "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.0.tgz", + "integrity": "sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=" }, "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -1166,11 +1428,11 @@ } }, "follow-redirects": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.0.tgz", - "integrity": "sha512-fdrt472/9qQ6Kgjvb935ig6vJCuofpBUD14f9Vb+SLlm7xIe4Qva5gey8EKtv8lp7ahE1wilg3xL1znpVGtZIA==", + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", "requires": { - "debug": "^3.1.0" + "debug": "=3.1.0" }, "dependencies": { "debug": { @@ -1200,12 +1462,12 @@ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "requires": { "asynckit": "^0.4.0", - "combined-stream": "1.0.6", + "combined-stream": "^1.0.6", "mime-types": "^2.1.12" } }, @@ -1254,7 +1516,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -1275,12 +1538,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1295,17 +1560,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -1422,11 +1690,6 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, "dev": true, "optional": true }, @@ -1434,6 +1697,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1448,6 +1712,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1455,12 +1720,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -1479,6 +1746,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -1559,7 +1827,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -1571,6 +1840,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -1656,7 +1926,8 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -1692,6 +1963,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -1711,6 +1983,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -1754,12 +2027,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -1855,14 +2130,31 @@ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, "handlebars": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", - "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz", + "integrity": "sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA==", "requires": { - "async": "^1.4.0", + "neo-async": "^2.6.0", "optimist": "^0.6.1", - "source-map": "^0.4.4", - "uglify-js": "^2.6" + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "uglify-js": { + "version": "3.6.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.9.tgz", + "integrity": "sha512-pcnnhaoG6RtrvHJ1dFncAe8Od6Nuy30oaJ82ts6//sGSXOP5UjBMEthiProjXmMNHOfd93sqlkztifFMcb+4yw==", + "optional": true, + "requires": { + "commander": "~2.20.3", + "source-map": "~0.6.1" + } + } } }, "har-schema": { @@ -1871,11 +2163,11 @@ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "requires": { - "ajv": "^5.1.0", + "ajv": "^6.5.5", "har-schema": "^2.0.0" } }, @@ -1930,6 +2222,50 @@ "inherits": "^2.0.1" } }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", @@ -1994,9 +2330,9 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", + "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", "dev": true }, "ipaddr.js": { @@ -2025,7 +2361,8 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true }, "is-ci": { "version": "1.1.0", @@ -2101,6 +2438,14 @@ "is-path-inside": "^1.0.0" } }, + "is-nan": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.2.1.tgz", + "integrity": "sha1-n69ltvttskt/XAYoR16nH5iEAeI=", + "requires": { + "define-properties": "^1.1.1" + } + }, "is-npm": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", @@ -2189,11 +2534,15 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, + "jquery": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz", + "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==" + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "json-schema": { "version": "0.2.3", @@ -2201,15 +2550,44 @@ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -2219,12 +2597,39 @@ "extsprintf": "1.3.0", "json-schema": "0.2.3", "verror": "1.10.0" + }, + "dependencies": { + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + } + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" } }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -2238,16 +2643,10 @@ "package-json": "^4.0.0" } }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "optional": true - }, "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, "lodash.debounce": { "version": "4.0.8", @@ -2255,15 +2654,50 @@ "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", "dev": true }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, "long": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=" }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" + "long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha1-lyHXiLR+C8taJMLivuGg2lXatRQ=" }, "lowercase-keys": { "version": "1.0.1", @@ -2392,6 +2826,16 @@ "mime-db": "~1.33.0" } }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -2406,9 +2850,9 @@ "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" }, "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", "dev": true, "requires": { "for-in": "^1.0.2", @@ -2431,6 +2875,14 @@ "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" }, + "moment-timezone": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.21.tgz", + "integrity": "sha512-j96bAh4otsgj3lKydm3K7kdtA3iKf2m6MY2iSYCzCm5a1zmHo1g+aK3068dDEeocLZQIS9kU8bsdQHLqEvgW0A==", + "requires": { + "moment": ">= 2.9.0" + } + }, "mongodb": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.0.10.tgz", @@ -2498,6 +2950,11 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" }, + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==" + }, "node-fetch": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", @@ -2507,6 +2964,16 @@ "is-stream": "^1.0.1" } }, + "node-schedule": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-1.3.0.tgz", + "integrity": "sha512-NNwO9SUPjBwFmPh3vXiPVEhJLn4uqYmZYvJV358SRGM06BR4UoIqxJpeJwDDXB6atULsgQA97MfD1zMd5xsu+A==", + "requires": { + "cron-parser": "^2.4.0", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.0.0" + } + }, "nodemailer": { "version": "4.6.5", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-4.6.5.tgz", @@ -2576,10 +3043,18 @@ "path-key": "^2.0.0" } }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "requires": { + "boolbase": "~1.0.0" + } + }, "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "object-copy": { "version": "0.1.0", @@ -2668,6 +3143,11 @@ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", "dev": true }, + "p-iteration": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/p-iteration/-/p-iteration-1.1.7.tgz", + "integrity": "sha512-VsYvUPjm2edbKkX4QzlASC1qB2e4Z6IE9WPaRVHKwCtobmB6vfUcU9eBOwj1k5uMNi8O6w89QfsDatO5ePSjQg==" + }, "package-json": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", @@ -2680,6 +3160,14 @@ "semver": "^5.1.0" } }, + "parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", + "requires": { + "@types/node": "*" + } + }, "parseurl": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", @@ -2793,6 +3281,11 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, + "psl": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", + "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==" + }, "pstree.remy": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.0.tgz", @@ -2803,9 +3296,9 @@ } }, "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { "version": "6.5.1", @@ -2942,33 +3435,59 @@ "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true }, "request": { - "version": "2.87.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", - "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "requires": { "aws-sign2": "~0.7.0", - "aws4": "^1.6.0", + "aws4": "^1.8.0", "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.1", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~2.3.1", - "har-validator": "~5.0.3", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.17", - "oauth-sign": "~0.8.2", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", "performance-now": "^2.1.0", - "qs": "~6.5.1", - "safe-buffer": "^5.1.1", - "tough-cookie": "~2.3.3", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", "tunnel-agent": "^0.6.0", - "uuid": "^3.1.0" + "uuid": "^3.3.2" + }, + "dependencies": { + "mime-db": { + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz", + "integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==" + }, + "mime-types": { + "version": "2.1.25", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.25.tgz", + "integrity": "sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==", + "requires": { + "mime-db": "1.42.0" + } + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + } } }, "require_optional": { @@ -2997,14 +3516,10 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "optional": true, - "requires": { - "align-text": "^0.1.1" - } + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" }, "ripemd160": { "version": "2.0.1", @@ -3034,6 +3549,45 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "secp256k1": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.7.1.tgz", + "integrity": "sha512-1cf8sbnRreXrQFdH6qsg2H71Xw91fCCS9Yp021GnUNJzWJS/py96fS4lHbnTnouLp08Xj6jBoBB6V78Tdbdu5g==", + "requires": { + "bindings": "^1.5.0", + "bip66": "^1.1.5", + "bn.js": "^4.11.8", + "create-hash": "^1.2.0", + "drbg.js": "^1.0.1", + "elliptic": "^6.4.1", + "nan": "^2.14.0", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + } + } + }, "secure-random": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/secure-random/-/secure-random-1.1.1.tgz", @@ -3090,29 +3644,6 @@ "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", "dev": true }, - "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", @@ -3254,13 +3785,10 @@ "kind-of": "^3.2.0" } }, - "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "requires": { - "amdefine": ">=0.0.4" - } + "sorted-array-functions": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.2.0.tgz", + "integrity": "sha512-sWpjPhIZJtqO77GN+LD8dDsDKcWZ9GCOJNqKzi1tvtjGIzwfoyuRH8S0psunmc6Z5P+qfDqztSbwYR5X/e1UTg==" }, "source-map-resolve": { "version": "0.5.2", @@ -3299,10 +3827,34 @@ "extend-shallow": "^3.0.0" } }, + "sscjs": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/sscjs/-/sscjs-0.0.8.tgz", + "integrity": "sha512-uu7zaFHUqUL5YySNrRg0MKN9PtZA356mX080ETJgGXNy7Wdc1RgwxHWbZJySTyywZRjn3BdIZRrNCEId6AS+Yw==", + "requires": { + "axios": "^0.19.0" + }, + "dependencies": { + "axios": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", + "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", + "requires": { + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" + } + }, + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" + } + } + }, "sshpk": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", - "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -3388,7 +3940,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -3485,11 +4036,19 @@ } }, "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "requires": { + "psl": "^1.1.24", "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } } }, "tunnel-agent": { @@ -3503,8 +4062,7 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, "type-is": { "version": "1.6.16", @@ -3515,31 +4073,6 @@ "mime-types": "~2.1.18" } }, - "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", - "optional": true, - "requires": { - "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0", - "yargs": "~3.10.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "optional": true - } - } - }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "optional": true - }, "ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", @@ -3673,6 +4206,14 @@ "xdg-basedir": "^3.0.0" } }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", @@ -3708,8 +4249,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "utils-merge": { "version": "1.0.1", @@ -3717,9 +4257,9 @@ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" }, "vary": { "version": "1.1.2", @@ -3758,12 +4298,6 @@ "string-width": "^2.1.1" } }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", - "optional": true - }, "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", @@ -3805,18 +4339,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" - }, - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "optional": true, - "requires": { - "camelcase": "^1.0.2", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", - "window-size": "0.1.0" - } } } } diff --git a/package.json b/package.json index 5e5ed35..f19a24d 100644 --- a/package.json +++ b/package.json @@ -1,33 +1,41 @@ -{ - "name": "actifit-curation-bot", - "version": "0.0.1", - "description": "actifit curation bot", - "main": "curation-bot.js", - "dependencies": { - "axios": "^0.18.0", - "express": "^4.16.2", - "lodash": "^4.17.10", - "moment": "^2.22.2", - "mongodb": "^3.0.10", - "nodemailer": "^4.6.5", - "nodemailer-express-handlebars": "^3.0.0", - "request": "^2.83.0", - "steem": "^0.6.7" - }, - "devDependencies": { - "nodemon": "^1.17.5" - }, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "curate": "node curation-bot.js", - "import": "node save-data.js", - "api": "node app.js", - "all": "node app.js | node save-data.js | node curation-bot.js", - "start": "node app.js | node save-data.js | node curation-bot.js" - }, - "engines": { - "node": "10.3.0" - }, - "author": "cryptouru", - "license": "MIT" -} +{ + "name": "actifit-curation-bot", + "version": "0.0.1", + "description": "actifit curation bot", + "main": "curation-bot.js", + "dependencies": { + "@hiveio/dhive": "^0.14.0", + "@hiveio/hive-js": "^0.8.0", + "axios": "^0.18.1", + "cheerio": "^1.0.0-rc.2", + "dsteem": "^0.10.1", + "express": "^4.16.2", + "jquery": "^3.4.1", + "jsonwebtoken": "^8.5.1", + "lodash": ">=4.17.11", + "moment": "^2.22.2", + "mongodb": "^3.0.10", + "node-schedule": "^1.3.0", + "nodemailer": "^4.6.5", + "nodemailer-express-handlebars": "^3.0.0", + "p-iteration": "^1.1.7", + "request": "^2.88.0", + "sscjs": "0.0.8", + "steem": "^0.6.7" + }, + "devDependencies": { + "nodemon": "^1.17.5" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "delegate": "node delegations.js", + "api": "node app.js", + "all": "node app.js | node delegations.js ", + "start": "node app.js | node delegations.js " + }, + "engines": { + "node": "10.3.0" + }, + "author": "cryptouru", + "license": "MIT" +} diff --git a/save-data.js b/save-data.js index 3bc1a67..dbb3bf4 100644 --- a/save-data.js +++ b/save-data.js @@ -1,6 +1,7 @@ var utils = require('./utils'); var mail = require('./mail'); const steem = require('steem'); +const { forEach } = require('p-iteration'); const MongoClient = require('mongodb').MongoClient; const assert = require('assert'); @@ -10,7 +11,7 @@ var config = utils.getConfig(); // Connection URL const url = config.mongo_uri; - +var postsProcessing = false; var db; var collection; // Database Name @@ -28,15 +29,16 @@ MongoClient.connect(url, function(err, client) { // Get the documents collection collection = db.collection(collection_name); - // client.close(); - //processVotedPosts(); updateUserTokens(); - getPosts(); - setInterval(getPosts, 300 * 1000); - setInterval(updateUserTokens, 450 * 1000); + runPostsProcess(); + //run every 31 mins + setInterval(runPostsProcess, 31 * 60 * 1000); + //run every 40 mins + setInterval(updateUserTokens, 41 * 60 * 1000); + } else { utils.log(err, 'import'); - mail.sendPlainMail('Database Error', err, 'cryptouru@gmail.com') + /*mail.sendPlainMail('Database Error', err, '') .then(function(res, err) { if (!err) { console.log(res); @@ -44,12 +46,24 @@ MongoClient.connect(url, function(err, client) { utils.log(err, 'import'); } }); - process.exit(); + process.exit();*/ } }); -function getPosts(index) { + +function runPostsProcess(){ + if(postsProcessing){ + return; + } + postsProcessing = true; + getPosts(); +} + +async function getPosts(index) { + + console.log('>>>>>>>> attempt getPosts <<<<<<<<<<<'); + console.log('---- Getting Posts ----'); var query = {tag: config.main_tag, limit: 100}; if (index) { @@ -57,22 +71,41 @@ function getPosts(index) { query.start_author = index.start_author; query.start_permlink = index.start_permlink; } + + //grab banned user list before rewarding + var banned_users = await db.collection('banned_accounts').find({ban_status:"active"}).toArray(); + console.log('found banned users'); + steem.api.getDiscussionsByCreated(query, function (err, result) { if (result && !err) { if(result.length == 0 || !result[0]) { utils.log('No posts found for this tag: ' + config.main_tag, 'import'); + postsProcessing = false; return; } console.log('Post count: ' + result.length); - let posts = utils.filterPosts(result, config.account, config.main_tag); + + + let posts = utils.filterPosts(result, banned_users, config.account, config.main_tag); + + //if the result was not an array, bail out + if (posts == -1){ + console.log('done looking for posts'); + + postsProcessing = false; + return; + } + console.log('Filtered count: ' + posts.length); // Upsert posts var bulk = collection.initializeUnorderedBulkOp(); + console.log('check post'); + var step_count = -1; for(var i = 0; i < posts.length; i++) { let post = posts[i] try { post.json_metadata = JSON.parse(post.json_metadata); - let step_count = post.json_metadata.step_count; + step_count = post.json_metadata.step_count; if (step_count < 5000) continue; else if (step_count < 6000) @@ -85,6 +118,8 @@ function getPosts(index) { post.token_rewards = 65; else if(step_count < 10000) post.token_rewards = 80; + else if(step_count > 150000) + continue; else post.token_rewards = 100; } catch (err) { @@ -96,7 +131,13 @@ function getPosts(index) { post ); } - + //do not attempt insertion if no results found on this round + if (posts.length == 0){ + let last_post = result[result.length - 1]; + if (!index || (index.start_permlink != last_post.permlink && index.start_author != last_post.author && result.length >= 100)){ + return getPosts({start_author: last_post.author, start_permlink: last_post.permlink}); + } + }else{ bulk.execute() .then(async function (res) { var mes = res.nInserted + ' posts inserted - ' + res.nUpserted + ' posts upserted - ' + res.nModified + ' posts updated'; @@ -106,12 +147,12 @@ function getPosts(index) { console.log('Inserted transactions'); if (!index || (index.start_permlink != last_post.permlink && index.start_author != last_post.author && result.length >= 100)) return getPosts({start_author: last_post.author, start_permlink: last_post.permlink}); - console.log('No more new posts'); + console.log('No more new posts'); return; }) .catch(function (err) { utils.log(err, 'import'); - mail.sendPlainMail('Error en mongo upsert', err, config.report_emails) + /*mail.sendPlainMail('Error en mongo upsert', err, config.report_emails) .then(function(res, err) { if (!err) { console.log(res); @@ -120,11 +161,16 @@ function getPosts(index) { console.log(err); return; } + });*/ + }).finally(function() { + //making sure we don't get caught up in infinite loop after some error + postsProcessing = false; }); - }) + + } } else { utils.log(err, 'import'); - mail.sendPlainMail('0 posts...', err, config.report_emails) + /*mail.sendPlainMail('0 posts...', err, config.report_emails) .then(function(res, err) { if (!err) { console.log(res); @@ -133,7 +179,7 @@ function getPosts(index) { console.log(err); return; } - }); + });*/ } }); } @@ -143,26 +189,39 @@ async function processTransactions(posts) { let transactions = []; let collection = db.collection('token_transactions'); var bulk = collection.initializeUnorderedBulkOp(); - posts.forEach(async post => { + await forEach(posts, async (post) => { + //by default the reward owner is the author + var reward_user = post.author; + var activity_type = 'Post'; + var note = ''; + //if we find this is a charity run, let's switch it to the actual charity name + if (typeof post.json_metadata.charity != 'undefined' && post.json_metadata.charity != '' && post.json_metadata.charity != 'undefined'){ + reward_user = post.json_metadata.charity; + activity_type = 'Charity Post'; + note = 'Charity donation via activity by user '+post.author; + } let post_transaction = { - user: post.author, - reward_activty: 'Post', + user: reward_user, + reward_activity: activity_type, token_count: post.token_rewards, url: post.url, - date: post.created + date: post.created, + note: note } bulk.find( { user: post_transaction.user, - reward_activty: post_transaction.reward_activty, + reward_activity: post_transaction.reward_activity, url: post_transaction.url }) .upsert().replaceOne(post_transaction); transactions.push(post_transaction); post.active_votes.forEach(async vote => { + //skip self vote from rewards + if (post.author != vote.voter){ let vote_transaction = { user: vote.voter, - reward_activty: 'Post Vote', + reward_activity: 'Post Vote', token_count: 1, url: post.url, date: vote.time @@ -170,27 +229,63 @@ async function processTransactions(posts) { bulk.find( { user: vote_transaction.user, - reward_activty: vote_transaction.reward_activty, + reward_activity: vote_transaction.reward_activity, url: vote_transaction.url }) .upsert().replaceOne(vote_transaction); transactions.push(vote_transaction); - }); + } + }); + let reblogs = await steem.api.getRebloggedByAsync(post.author, post.permlink); + console.log('------------------ REBLOGS --------------------'); + console.log(reblogs); + reblogs.forEach(async reblog => { + if(reblog != post.author){ + let reblog_transaction = { + user: reblog, + reward_activity: 'Post Reblog', + token_count: 1, + url: post.url, + date: post.created + } + console.log('---Reblog transaction ----'); + console.log(reblog_transaction); + bulk.find( + { + user: reblog_transaction.user, + reward_activity: reblog_transaction.reward_activity, + url: reblog_transaction.url + }) + .upsert().replaceOne(reblog_transaction); + transactions.push(reblog_transaction); + } + }); }); + // console.log(transactions); return bulk.execute(); } async function updateUserTokens() { console.log('---- Updating Users ----'); - let query = await db.collection('token_transactions').aggregate( - [ - { $group: { _id: "$user", tokens: { $sum: "$token_count" } } }, - { $sort: { tokens: -1 } } - ]); - let user_tokens = await query.toArray(); - await db.collection('user_tokens').drop(); - return await db.collection('user_tokens').insert(user_tokens); + try{ + let query = await db.collection('token_transactions').aggregate([ + { $group: { _id: "$user", tokens: { $sum: "$token_count" } } }, + { $sort: { tokens: -1 } }, + { $project: { + _id: "$_id", + user: "$_id", + tokens: "$tokens", + } + } + ]) + + let user_tokens = await query.toArray(); + await db.collection('user_tokens').remove({}); + return await db.collection('user_tokens').insert(user_tokens); + }catch(err){ + console.log('>>save data error:'+err.message); + } } async function processVotedPosts() { @@ -198,11 +293,11 @@ async function processVotedPosts() { let transactions = await getAccountVotes(config.account); let postsData = []; - await utils.asyncForEach(transactions, async (txs) => { + await forEach(transactions, async (txs) => { await steem.api.getContentAsync(txs.author, txs.permlink) .then(postObject => { - if (utils.checkBeneficiary(postObject)) { - console.log('--- Post has correct beneficiaries -----'); + if (utils.checkBeneficiary(postObject) || postObject.author == config.account) { + console.log('--- Post has correct beneficiaries or was posted by config account -----'); postsData.push(postObject); } else { console.log('--- Post missing beneficiaries -----'); @@ -229,14 +324,23 @@ async function getAccountVotes(account) { return voteByAccount; } +async function getReblogs(post) { + console.log('----- Getting reblogs ----'); + let res; + res = await steem.api.getRebloggedByAsync(post.author, post.permlink); + console.log(res); + return res; +} + async function upsertPosts(posts) { // Upsert posts var bulk = collection.initializeUnorderedBulkOp(); for(var i = 0; i < posts.length; i++) { let post = posts[i] + var step_count = -1; try { post.json_metadata = JSON.parse(post.json_metadata); - let step_count = post.json_metadata.step_count; + step_count = post.json_metadata.step_count; if (step_count < 5000) continue; else if (step_count < 6000) @@ -266,6 +370,7 @@ async function upsertPosts(posts) { var mes = res.nInserted + ' posts inserted - ' + res.nUpserted + ' posts upserted - ' + res.nModified + ' posts updated'; utils.log(mes, 'import'); await processTransactions(posts); + console.log('----Super upsert ready ----'); return; }) .catch(function (err) { diff --git a/utils.js b/utils.js index dc05cc9..a2889a8 100644 --- a/utils.js +++ b/utils.js @@ -1,280 +1,1583 @@ -var fs = require("fs"); -const steem = require('steem'); -var _ = require('lodash'); -const axios = require('axios'); -var config; - -steem.api.setOptions({ url: 'https://api.steemit.com' }); - -var STEEMIT_100_PERCENT = 10000; -var STEEMIT_VOTE_REGENERATION_SECONDS = (5 * 60 * 60 * 24); -var HOURS = 60 * 60; - - var steemPrice; - var rewardBalance; - var recentClaims; - var currentUserAccount; - var votePowerReserveRate; - var totalVestingFund; - var totalVestingShares; - var botNames; - function updateSteemVariables() { - steem.api.getRewardFund("post", function (e, t) { - console.log(e,t); - rewardBalance = parseFloat(t.reward_balance.replace(" STEEM", "")); - recentClaims = t.recent_claims; - }); - steem.api.getCurrentMedianHistoryPrice(function (e, t) { - steemPrice = parseFloat(t.base.replace(" SBD", "")) / parseFloat(t.quote.replace(" STEEM", "")); - }); - steem.api.getDynamicGlobalProperties(function (e, t) { - votePowerReserveRate = t.vote_power_reserve_rate; - totalVestingFund = parseFloat(t.total_vesting_fund_steem.replace(" STEEM", "")); - totalVestingShares = parseFloat(t.total_vesting_shares.replace(" VESTS", "")); - }); - - setTimeout(updateSteemVariables, 180 * 1000) - } - // updateSteemVariables(); - - function getVotingPower(account) { - var voting_power = account.voting_power; - var last_vote_time = new Date((account.last_vote_time) + 'Z'); - var elapsed_seconds = (new Date() - last_vote_time) / 1000; - var regenerated_power = Math.round((STEEMIT_100_PERCENT * elapsed_seconds) / STEEMIT_VOTE_REGENERATION_SECONDS); - var current_power = Math.min(voting_power + regenerated_power, STEEMIT_100_PERCENT); - return current_power; - } - - function getVoteRShares(voteWeight, account, power) { - if (!account) { - return; - } - - if (rewardBalance && recentClaims && steemPrice && votePowerReserveRate) { - - var effective_vesting_shares = Math.round(getVestingShares(account) * 1000000); - var voting_power = account.voting_power; - var weight = voteWeight * 100; - var last_vote_time = new Date((account.last_vote_time) + 'Z'); - - - var elapsed_seconds = (new Date() - last_vote_time) / 1000; - var regenerated_power = Math.round((STEEMIT_100_PERCENT * elapsed_seconds) / STEEMIT_VOTE_REGENERATION_SECONDS); - var current_power = power || Math.min(voting_power + regenerated_power, STEEMIT_100_PERCENT); - var max_vote_denom = votePowerReserveRate * STEEMIT_VOTE_REGENERATION_SECONDS / (60 * 60 * 24); - var used_power = Math.round((current_power * weight) / STEEMIT_100_PERCENT); - used_power = Math.round((used_power + max_vote_denom - 1) / max_vote_denom); - - var rshares = Math.round((effective_vesting_shares * used_power) / (STEEMIT_100_PERCENT)) - - return rshares; - - } - } - - function getVoteValue(voteWeight, account, power) { - if (!account) { - return; - } - if (rewardBalance && recentClaims && steemPrice && votePowerReserveRate) { - var voteValue = getVoteRShares(voteWeight, account, power) - * rewardBalance / recentClaims - * steemPrice; - - return voteValue; - - } - } - -function timeTilFullPower(cur_power){ - return (STEEMIT_100_PERCENT - cur_power) * STEEMIT_VOTE_REGENERATION_SECONDS / STEEMIT_100_PERCENT; - } - - function getVestingShares(account) { - var effective_vesting_shares = parseFloat(account.vesting_shares.replace(" VESTS", "")) - + parseFloat(account.received_vesting_shares.replace(" VESTS", "")) - - parseFloat(account.delegated_vesting_shares.replace(" VESTS", "")); - return effective_vesting_shares; - } - - function getCurrency(amount) { - return amount.substr(amount.indexOf(' ') + 1); - } - - function loadUserList(location, callback) { - if(!location) { - if(callback) - callback(null); - - return; - } - - if (location.startsWith('http://') || location.startsWith('https://')) { - // Require the "request" library for making HTTP requests - var request = require("request"); - - request.get(location, function (e, r, data) { - try { - if(callback) - callback(data.replace(/[\r]/g, '').split('\n')); - } catch (err) { - utils.log('Error loading blacklist from: ' + location + ', Error: ' + err); - - if(callback) - callback(null); - } - }); - } else if (fs.existsSync(location)) { - if(callback) - callback(fs.readFileSync(location, "utf8").replace(/[\r]/g, '').split('\n')); - } else if(callback) - callback([]); -} - -function format(n, c, d, t) { - var c = isNaN(c = Math.abs(c)) ? 2 : c, - d = d == undefined ? "." : d, - t = t == undefined ? "," : t, - s = n < 0 ? "-" : "", - i = String(parseInt(n = Math.abs(Number(n) || 0).toFixed(c))), - j = (j = i.length) > 3 ? j % 3 : 0; - return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : ""); - } - - function toTimer(ts) { - var h = Math.floor(ts / HOURS); - var m = Math.floor((ts % HOURS) / 60); - var s = Math.floor((ts % 60)); - return padLeft(h, 2) + ':' + padLeft(m, 2) + ':' + padLeft(s, 2); - } - - function padLeft(v, d) { - var l = (v + '').length; - if (l >= d) return v + ''; - for(var i = l; i < d; i++) - v = '0' + v; - return v; - } - - async function loadBots() { - var query = await axios.get('https://steembottracker.net/bid_bots'); - var bidBots = query.data; - var query = await axios.get('https://steembottracker.net/other_bots'); - var otherBots = query.data; - // console.log(bidBots); - // console.log(otherBots); - var allBots = bidBots.concat(otherBots); - botNames = _.map(allBots, 'name'); - // console.log(botNames); - return botNames; - } - - function calculateVotes(posts, weight) { - if(typeof weight == 'undefined') { - weight = 10000; - } - var data = {}; - var x = 0; - // Rate multiplier post count - var rmc = _.countBy(posts, 'rate_multiplier'); - console.log(rmc); - _.forEach(rmc, function(value, key) { - x += key * value; - }); - console.log(x); - data.power_per_vote = Math.floor(weight / x); - return data - } - - function filterPosts(posts) { - var results = Array(); - let config = getConfig(); - for(var i = 0; i < posts.length; i++) { - var post = posts[i]; - - // Check if post category is main tag - if (post.category != config.main_tag) { - console.log('Post does not match category tag. ' + post.url); - continue; - } - //check if account was voted - let voted = _.findIndex(post.active_votes, ['voter', config.account]); - if (voted == -1) { - console.log('Post was not voted. ' + post.url); - continue; - } - // Check if account is beneficiary - var benefit = checkBeneficiary(post); - - if(!benefit) - continue; - - results.push(post); - } - return results; - - } - - function checkBeneficiary(post) { - let config = getConfig(); - // Check if account is beneficiary - var benefit = 0; - for (var x = 0; x < post.beneficiaries.length; x++) { - for (var n = 0; n < config.beneficiaries.length; n++) { - if (post.beneficiaries[x].account === config.beneficiaries[n]) - benefit ++; - } - if (benefit === config.beneficiaries.length) { - benefit = true; - break; - } - } - if(!benefit) - return false; - - return true; - - } - - function log(msg, name) { - if (!name) - var name = 'log'; - console.log(new Date().toString() + ' - ' + msg); - fs.appendFileSync( name + '.log', new Date().toString() + ' - ' + msg + "\n"); - } - - function getConfig() { - if (config) - return config; - else { - console.log('I get config'); - config = JSON.parse(fs.readFileSync("config.json")); - return config; - } - } - - async function asyncForEach(array, callback) { - for (let index = 0; index < array.length; index++) { - await callback(array[index], index, array) - } -} - - - module.exports = { - getVotingPower: getVotingPower, - getVoteValue: getVoteValue, - timeTilFullPower: timeTilFullPower, - getVestingShares: getVestingShares, - loadUserList: loadUserList, - getCurrency: getCurrency, - format: format, - toTimer: toTimer, - log: log, - calculateVotes: calculateVotes, - filterPosts: filterPosts, - getConfig: getConfig, - loadBots: loadBots, - checkBeneficiary: checkBeneficiary, - asyncForEach: asyncForEach - } +var fs = require("fs"); +const steem = require('steem'); + +const hive = require('@hiveio/hive-js'); + +var _ = require('lodash'); +const axios = require('axios'); +const dsteem = require('dsteem'); + +const dhive = require('@hiveio/dhive'); + +const moment = require('moment') + +getConfig(); + +const client = new dsteem.Client(config.active_node); +const hiveClient = new dhive.Client(config.alt_hive_nodes); + +var config; + +let th_id = -1; + +steem.api.setOptions({ url: config.active_node }); + +hive.api.setOptions({ url: config.active_hive_node }); + +var STEEMIT_100_PERCENT = 10000; +var STEEMIT_VOTE_REGENERATION_SECONDS = (5 * 60 * 60 * 24); +var HOURS = 60 * 60; + + var steemPrice; + var rewardBalance; + var recentClaims; + var currentUserAccount; + var votePowerReserveRate; + var totalVestingFund; + var totalVestingShares; + var steem_per_mvests; + var sbd_print_percentage; + var botNames; + let properties + let totalVests + let totalSteem + let hiveProps + let totalHive + let totalHiveVests + + function setProperNode(bchain){ + if (bchain == "STEEM"){ + return steem + }else{ + return hive + } + } + + async function getAccountData(account_name, bchain){ + let account = null; + let chainLnk = await setProperNode(bchain); + //attempt to load account data + try{ + let account_res = await chainLnk.api.getAccountsAsync([config.account]); + account = account_res[0]; + }catch(err){ + console.log(err); + } + return account; + } + + async function validateAccountLogin(username, priv_pkey, bchain){ + let chainLnk = await setProperNode(bchain); + console.log('validateAccountLogin'); + let account_res = await chainLnk.api.getAccountsAsync([username]); + console.log(account_res[0]); + let pub_pkey = account_res[0].posting.key_auths[0][0]; + try{ + let res = await chainLnk.auth.wifIsValid(priv_pkey, pub_pkey); + //console.log(res); + return {result: res, account: account_res[0]}; + }catch(err){ + console.log(err); + return {result:false}; + } + } + + async function processSteemTrx(operation, userKey, bchain){ + console.log('utils processSteemTrx'); + console.log(operation); + const ops = [ operation ]; + console.log('>>>>>>>>>>>> selected bchain <<<<<<<<<<'); + console.log(bchain); + let chainLnk = await setProperNode(bchain); + let tx = await chainLnk.broadcast.sendAsync( + { operations: ops, extensions: [] }, + { posting: userKey } + ).catch(err => { + console.log(err.message); + return {error: err.message}; + }); + + console.log(tx); + return {tx: tx}; + } + + function updateSteemVariables(bchain) { + let chainLnk = setProperNode(bchain); + chainLnk.api.getRewardFund("post", function (e, t) { + console.log(e,t); + rewardBalance = parseFloat(t.reward_balance.replace(" STEEM", "").replace(" HIVE", "")); + recentClaims = t.recent_claims; + }); + chainLnk.api.getCurrentMedianHistoryPrice(function (e, t) { + steemPrice = parseFloat(t.base.replace(" SBD", "")) / parseFloat(t.quote.replace(" STEEM", "").replace(" HIVE", "")); + }); + chainLnk.api.getDynamicGlobalProperties(function (e, t) { + votePowerReserveRate = t.vote_power_reserve_rate; + totalVestingFund = parseFloat(t.total_vesting_fund_steem.replace(" STEEM", "").replace(" HIVE", "")); + totalVestingShares = parseFloat(t.total_vesting_shares.replace(" VESTS", "")); + steem_per_mvests = ((totalVestingFund / totalVestingShares) * 1000000); + sbd_print_percentage = t.sbd_print_rate / 10000 + console.log(steem_per_mvests); + console.log(sbd_print_percentage); + }); + + setTimeout(updateSteemVariables, 180 * 1000, bchain) + } + // updateSteemVariables(); + + /*function getVotingPower(account) { + var voting_power = account.voting_power; + var last_vote_time = new Date((account.last_vote_time) + 'Z'); + var elapsed_seconds = (new Date() - last_vote_time) / 1000; + var regenerated_power = Math.round((STEEMIT_100_PERCENT * elapsed_seconds) / STEEMIT_VOTE_REGENERATION_SECONDS); + var current_power = Math.min(voting_power + regenerated_power, STEEMIT_100_PERCENT); + return current_power; + }*/ + + //fixed implementation of proper voting power calculation + function getVotingPower(account) { + const totalShares = parseFloat(account.vesting_shares) + parseFloat(account.received_vesting_shares) - parseFloat(account.delegated_vesting_shares) - parseFloat(account.vesting_withdraw_rate); + + const elapsed = Math.floor(Date.now() / 1000) - account.voting_manabar.last_update_time; + const maxMana = totalShares * 1000000; + // 432000 sec = 5 days + let currentMana = parseFloat(account.voting_manabar.current_mana) + elapsed * maxMana / 432000; + + if (currentMana > maxMana) { + currentMana = maxMana; + } + + const currentManaPerc = currentMana * 100 / maxMana; + console.log(currentManaPerc); + console.log(account.name); + return currentManaPerc; + } + + //implement a get current Resource Credits function for normal operations consumption + async function getRC(account_name){ + var data={"jsonrpc":"2.0","id":1,"method":"condenser_api.get_account_count","params":{}}; + //return new Promise(function(fulfill,reject){ + //var request = require("request"); + let location = config.active_node; + var response = await axios.post(location, {"jsonrpc":"2.0","id":1,"method":"rc_api.find_rc_accounts","params":{"accounts":[account_name]}}); + + console.log(response.data.result.rc_accounts); + + + + const STEEM_RC_MANA_REGENERATION_SECONDS =432000; + const estimated_max = parseFloat(response.data.result.rc_accounts["0"].max_rc); + const current_mana = parseFloat(response.data.result.rc_accounts["0"].rc_manabar.current_mana); + const last_update_time = parseFloat(response.data.result.rc_accounts["0"].rc_manabar.last_update_time); + const diff_in_seconds = Math.round(Date.now()/1000-last_update_time); + let estimated_mana = (current_mana + diff_in_seconds * estimated_max / STEEM_RC_MANA_REGENERATION_SECONDS); + if (estimated_mana > estimated_max) + estimated_mana = estimated_max; + const estimated_pct = estimated_mana / estimated_max * 100; + const res= {"current_mana": current_mana, "last_update_time": last_update_time, + "estimated_mana": estimated_mana, "estimated_max": estimated_max, "estimated_pct": estimated_pct.toFixed(2),"fullin":timeTilFullPower(estimated_pct*100)}; + return res; + + //}); + } + + //function handles confirming if AFIT from SE were received + async function confirmAFITXTransition (targetUser, txid, amount, bchain, standardAfit) { + getConfig(); + //track attempts for timeout + let attempts = 1; + let max_attempts = 15; + return new Promise((resolve, reject) => { + th_id = setInterval(async function(){ + if (attempts < max_attempts){ + attempts += 1; + console.log('Check Move'); + //let's call the service by S-E + let url = new URL(config.hive_engine_afitx_trx); + if (standardAfit == 1){ + url = new URL(config.hive_engine_afit_trx); + } + if (bchain == 'STEEM'){ + url = new URL(config.steem_engine_afitx_trx); + if (standardAfit == 1){ + url = new URL(config.steem_engine_afit_trx); + } + } + //connect with our service to confirm AFIT received to proper wallet + try{ + let se_connector = await fetch(url); + let trx_entries = await se_connector.json(); + console.log(trx_entries); + let match_trx; + + //check if we have a proper entry matching user transfer + if (match_trx = trx_entries.find(trx => (trx.from == targetUser && trx.quantity == amount && trx.transactionId == txid))) { + //found match, let's make sure transaction is recent enough + console.log('found match'); + paymentFound = true; + if (paymentFound){ + //need to look again + console.log('found'); + clearInterval(th_id); + resolve(match_trx); + } + } + }catch(err){ + console.log(err); + } + }else{ + //return error + resolve(null); + } + }, 5000); + }); + } + + async function proceedAfitxMove (targetAcct, amount, chain, standardAfit){ + + let transId = 'ssc-mainnet-hive'; + //let targetBchain = 'STEEM'; + //other option is moving tokens from H-E to S-E + if (chain == 'STEEM'){ + //if (this.cur_bchain == 'STEEM'){ + transId = 'ssc-mainnet1'; + //targetBchain = 'HIVE'; + } + let tokenSymbol = 'AFITX'; + if (standardAfit == 1){ + tokenSymbol = 'AFIT'; + } + + let json_data = { + contractName: 'tokens', + contractAction: 'transfer', + contractPayload: { + symbol: tokenSymbol, + to: targetAcct, + quantity: amount.toFixed(6),//needs to be string and a max of 6 digits supported + memo: '' + } + } + + //send out transaction to blockchain + let chainLnk = await setProperNode(chain); + let tx = await chainLnk.broadcast.customJsonAsync( + config.active_key, + [ config.account ] , + [], + transId, + JSON.stringify(json_data) + ).catch(err => { + console.log(err.message); + }); + } + + //function handles confirming if AFIT from SE were received + async function confirmSEAFITReceived (targetUser, bchain) { + getConfig(); + //track attempts for timeout + let attempts = 1; + let max_attempts = 15; + return new Promise((resolve, reject) => { + th_id = setInterval(async function(){ + if (attempts < max_attempts){ + attempts += 1; + console.log('Check AFIT Power Up'); + //let's call the service by S-E + let url = new URL(config.hive_engine_trans_acct_his); + if (bchain == 'STEEM'){ + url = new URL(config.steem_engine_trans_acct_his); + } + //connect with our service to confirm AFIT received to proper wallet + try{ + let se_connector = await fetch(url); + let trx_entries = await se_connector.json(); + + let match_trx; + + //check if we have a proper entry matching user transfer + if (match_trx = trx_entries.find(trx => trx.from == targetUser)) { + //found match, let's make sure transaction is recent enough + console.log('found match'); + paymentFound = true; + if (paymentFound){ + //need to look again + console.log('found'); + clearInterval(th_id); + resolve(match_trx); + } + } + }catch(err){ + console.log(err); + } + }else{ + //return error + resolve(null); + } + }, 5000); + }); + } + + //function handles confirming if payment was received + async function confirmPaymentReceived (req, bchain) { + getConfig(); + return new Promise((resolve, reject) => { + th_id = setInterval(async function(){ + let chainLnk = await setProperNode(bchain); + console.log('check funds'); + chainLnk.api.getAccountHistory(config.signup_account, -1, 3000, (err, transactions) => { + let tx_id = ''; + let paymentFound = false; + for (let txs of transactions) { + let op = txs[1].op + //check if we received a transfer to our target account + //if we found a transfer operation sent to our target account, with the correct memo and the proper amount, proceed + if (op[0] === 'transfer'){ + let sentAmount = op[1].amount.split(' ')[0]; + let sentCur = op[1].amount.split(' ')[1]; + if (op[1].to === config.signup_account + && op[1].memo === req.query.memo + && sentAmount >= (parseFloat(req.query.steem_invest)-0.1) + && sentCur === req.query.sent_cur){ + console.log('in'); + console.log(op[1]); + + let now = moment(new Date()); //todays date + let end = moment(txs[1].timestamp); // last update date + let duration = moment.duration(now.diff(end)); + let hrs = duration.asHours(); + //transaction needs to have been concluded within 5 hours. + if (hrs < 5){ + tx_id = txs[1].trx_id; + paymentFound = true; + break; + } + } + } + } + if (paymentFound){ + //need to look again + console.log('found'); + clearInterval(th_id); + resolve(tx_id); + } + }); + }, 5000); + }); + } + + //function handles confirming if payment was received + async function confirmPaymentReceivedPassword (req, bchain) { + getConfig(); + console.log('confirmPaymentReceivedPassword'); + return new Promise((resolve, reject) => { + let th_id = setInterval(async function(){ + console.log('check funds'); + console.log(bchain); + let chainLnk = await setProperNode(bchain); + chainLnk.api.getAccountHistory(config.exchange_account, -1, 300, (err, transactions) => { + let tx_id = ''; + let paymentFound = false; + for (let txs of transactions) { + let op = txs[1].op + //check if we received a transfer to our target account + //if we found a transfer operation sent to our target account, with the correct memo and the proper amount, proceed + if (op[0] === 'transfer'){ + //console.log('transfer op '); + //console.log(op[1]); + let sentAmount = op[1].amount.split(' ')[0]; + if (op[1].to === config.exchange_account && op[1].from === req.query.from && sentAmount >= 1){ + console.log('in'); + console.log(op[1]); + //console.log(txs); + /*let now = moment(new Date()); //todays date + let end = moment(txs[1].timestamp); // last update date + let duration = moment.duration(now.diff(end)); + let hrs = duration.asHours(); + //transaction needs to have been concluded within 5 hours. + if (hrs < 24){*/ + tx_id = txs[1].trx_id; + paymentFound = true; + break; + //} + } + } + } + if (paymentFound){ + //need to look again + console.log('found'); + clearInterval(th_id); + resolve(tx_id); + } + }); + }, 5000); + }); + } + + //function handles confirming if payment was received + async function confirmPaymentReceivedBuy (req, bchain) { + getConfig(); + console.log('confirmPaymentReceivedBuy'); + return new Promise((resolve, reject) => { + let th_id = setInterval(async function(){ + console.log('check buy funds'); + let chainLnk = await setProperNode(bchain); + chainLnk.api.getAccountHistory(config.buy_account, -1, 800, (err, transactions) => { + let tx_id = ''; + let paymentFound = false; + for (let txs of transactions) { + let op = txs[1].op + //check if we received a transfer to our target account + //if we found a transfer operation sent to our target account, with the correct sender and the proper amount, proceed + if (op[0] === 'transfer'){ + let sentAmount = op[1].amount.split(' ')[0]; + if (op[1].to === config.buy_account && op[1].from === req.query.from && sentAmount === req.query.steem_amount){ + console.log('in'); + console.log(op[1]); + //console.log(txs); + + let now = moment(new Date()); //todays date + let end = moment(txs[1].timestamp); // last update date + let duration = moment.duration(now.diff(end)); + let hrs = duration.asHours(); + //transaction needs to have been concluded within 5 hours. + if (hrs < 5){ + tx_id = txs[1].trx_id; + paymentFound = true; + break; + } + } + } + } + if (paymentFound){ + //need to look again + console.log('found'); + clearInterval(th_id); + resolve(tx_id); + } + }); + }, 5000); + }); + } + + + + //function handles claiming spots for accounts + async function claimDiscountedAccount(chain){ + console.log('claimDiscountedAccount'); + if (typeof config == 'undefined' || config == null){ + getConfig(); + } + const claim_op = [ + 'claim_account', + { + creator: config.account, + fee: '0.000 STEEM', + extensions: [], + } + ]; + const ops = [claim_op]; + + let result = ''; + let outcSteem = false; + let outcHive = false; + if (!chain || chain == 'STEEM'){ + + try{ + const privateKey = dsteem.PrivateKey.fromString( + config.active_key + ); + result = await client.broadcast.sendOperations(ops, privateKey); + console.log('success'); + outcSteem = true; + }catch(err){ + console.log(err); + outcSteem = false; + } + } + if (!chain || chain == 'HIVE'){ + try{ + const privateKey = dhive.PrivateKey.fromString( + config.active_key + ); + result = await hiveClient.broadcast.sendOperations(ops, privateKey); + console.log('success'); + outcHive = true; + }catch(err){ + console.log(err); + outcHive = false; + } + } + return (outcSteem || outcHive); + } + + //function handles creating accounts via discounted claimed spots or normal paid method + async function createAccount (username, password, chain){ + if (typeof config == 'undefined' || config == null){ + getConfig(); + } + + if (!chain || chain == 'STEEM'){ + //check if account exists + const _account = await client.database.call('get_accounts', [[username]]); + //account not available to register + if (_account.length>0) { + console.log('account already exists'); + console.log(_account); + return false; + } + } + + if (!chain || chain == 'HIVE'){ + //check if account exists + const _account = await hiveClient.database.call('get_accounts', [[username]]); + //account not available to register + if (_account.length>0) { + console.log('account already exists'); + console.log(_account); + return false; + } + } + + console.log('account available'); + + + //container for required ops + let ops = []; + let hiveOps = []; + + //if we have discounted accounts still available, let's do that, otherwise let's pay for account + let creator = config.account; + + let steemAccountSuccess = false; + let hiveAccountSuccess = false; + + if (!chain || chain == 'STEEM'){ + + //create keys for new account + const ownerKey = dsteem.PrivateKey.fromLogin(username, password, 'owner'); + const activeKey = dsteem.PrivateKey.fromLogin(username, password, 'active'); + const postingKey = dsteem.PrivateKey.fromLogin(username, password, 'posting'); + let memoKey = dsteem.PrivateKey.fromLogin(username, password, 'memo').createPublic(); + + //create auth values for passing to account creation + const ownerAuth = { + weight_threshold: 1, + account_auths: [], + key_auths: [[ownerKey.createPublic(), 1]], + }; + const activeAuth = { + weight_threshold: 1, + account_auths: [], + key_auths: [[activeKey.createPublic(), 1]], + }; + const postingAuth = { + weight_threshold: 1, + account_auths: [], + key_auths: [[postingKey.createPublic(), 1]], + }; + + + const _creator_account = await client.database.call('get_accounts', [ + [creator], + ]); + console.log('current pending claimed accounts: ' + _creator_account[0].pending_claimed_accounts); + + if (_creator_account[0].pending_claimed_accounts > 0) { + + //the create discounted account operation + const create_op = [ + 'create_claimed_account', + { + creator: creator, + new_account_name: username, + owner: ownerAuth, + active: activeAuth, + posting: postingAuth, + memo_key: memoKey, + json_metadata: '', + extensions: [], + } + ]; + ops.push(create_op); + }else{ + + const create_op = [ + 'account_create', + { + fee: '3.000 STEEM', + creator: creator, + new_account_name: username, + owner: ownerAuth, + active: activeAuth, + posting: postingAuth, + memo_key: memoKey, + json_metadata: '', + extensions: [], + } + ]; + ops.push(create_op); + } + + const privateKey = dhive.PrivateKey.fromString(config.active_key); + //proceed executing the selected operation(s) + let result = ''; + try{ + result = await client.broadcast.sendOperations(ops, privateKey); + console.log('success'); + steemAccountSuccess = true; + }catch(err){ + console.log(err); + steemAccountSuccess = false; + } + } + + if (!chain || chain == 'HIVE'){ + const _creator_account = await hiveClient.database.call('get_accounts', [ + [creator], + ]); + console.log('current pending claimed accounts: ' + _creator_account[0].pending_claimed_accounts); + + //create keys for new account + const ownerKey = dhive.PrivateKey.fromLogin(username, password, 'owner'); + const activeKey = dhive.PrivateKey.fromLogin(username, password, 'active'); + const postingKey = dhive.PrivateKey.fromLogin(username, password, 'posting'); + let memoKey = dhive.PrivateKey.fromLogin(username, password, 'memo').createPublic(); + + //create auth values for passing to account creation + const ownerAuth = { + weight_threshold: 1, + account_auths: [], + key_auths: [[ownerKey.createPublic(), 1]], + }; + const activeAuth = { + weight_threshold: 1, + account_auths: [], + key_auths: [[activeKey.createPublic(), 1]], + }; + const postingAuth = { + weight_threshold: 1, + account_auths: [], + key_auths: [[postingKey.createPublic(), 1]], + }; + + if (_creator_account[0].pending_claimed_accounts > 0) { + + //the create discounted account operation + const create_op = [ + 'create_claimed_account', + { + creator: creator, + new_account_name: username, + owner: ownerAuth, + active: activeAuth, + posting: postingAuth, + memo_key: memoKey, + json_metadata: '', + extensions: [], + } + ]; + hiveOps.push(create_op); + }else{ + + const create_op = [ + 'account_create', + { + fee: '3.000 STEEM', + creator: creator, + new_account_name: username, + owner: ownerAuth, + active: activeAuth, + posting: postingAuth, + memo_key: memoKey, + json_metadata: '', + extensions: [], + } + ]; + hiveOps.push(create_op); + } + + const privateKey = dhive.PrivateKey.fromString(config.active_key); + //proceed executing the selected operation(s) + let result = ''; + try{ + result = await hiveClient.broadcast.sendOperations(hiveOps, privateKey); + console.log('success'); + hiveAccountSuccess = true; + }catch(err){ + console.log(err); + hiveAccountSuccess = false; + } + } + return (steemAccountSuccess || hiveAccountSuccess); + } + + //function handles delegating to a specific account + async function delegateToAccount (delegatee, steemPowerAmount, chain){ + if (typeof config == 'undefined' || config == null){ + getConfig(); + } + const privateKey = dhive.PrivateKey.fromString( + config.full_pay_ac_key + ); + + let result = ''; + let steemDg = false; + let hiveDg = false; + if (!chain || chain == 'STEEM'){ + try{ + //grab matching amount of Vests to delegate + let matchingVests = await steemPowerToVests(steemPowerAmount); + console.log('matchingVests:'+matchingVests); + const op = [ + 'delegate_vesting_shares', + { + delegator: config.full_pay_benef_account, + delegatee: delegatee, + vesting_shares: matchingVests+' VESTS', + }, + ]; + + result = await client.broadcast.sendOperations([op], privateKey); + console.log('Included in block:'+ result.block_num); + console.log('returning back'); + steemDg = true; + }catch(err){ + console.log(err); + console.log('returning back err'); + steemDg = false; + } + } + if (!chain || chain == 'HIVE'){ + try{ + //grab matching amount of Vests to delegate + let matchingHiveVests = await hivePowerToVests(steemPowerAmount); + console.log('matchingHiveVests:'+matchingHiveVests); + const op = [ + 'delegate_vesting_shares', + { + delegator: config.full_pay_benef_account, + delegatee: delegatee, + vesting_shares: matchingHiveVests+' VESTS', + }, + ]; + + result = await hiveClient.broadcast.sendOperations([op], privateKey); + console.log('Included in block:'+ result.block_num); + console.log('returning back'); + hiveDg = true; + }catch(err){ + console.log(err); + console.log('returning back err'); + hiveDg = false; + } + } + return (steemDg || hiveDg); + } + + function getVoteRShares(voteWeight, account, power) { + if (!account) { + return; + } + + if (rewardBalance && recentClaims && steemPrice && votePowerReserveRate) { + + var effective_vesting_shares = Math.round(getVestingShares(account) * 1000000); + var voting_power = account.voting_power; + var weight = voteWeight * 100; + var last_vote_time = new Date((account.last_vote_time) + 'Z'); + + + var elapsed_seconds = (new Date() - last_vote_time) / 1000; + var regenerated_power = Math.round((STEEMIT_100_PERCENT * elapsed_seconds) / STEEMIT_VOTE_REGENERATION_SECONDS); + var current_power = power || Math.min(voting_power + regenerated_power, STEEMIT_100_PERCENT); + var max_vote_denom = votePowerReserveRate * STEEMIT_VOTE_REGENERATION_SECONDS / (60 * 60 * 24); + var used_power = Math.round((current_power * weight) / STEEMIT_100_PERCENT); + used_power = Math.round((used_power + max_vote_denom - 1) / max_vote_denom); + + var rshares = Math.round((effective_vesting_shares * used_power) / (STEEMIT_100_PERCENT)) + + return rshares; + + } + } + + function getVoteValue(voteWeight, account, power, steem_price) { + if (!account) { + return; + } + if (rewardBalance && recentClaims && steemPrice && votePowerReserveRate) { + var voteValue = getVoteRShares(voteWeight, account, power) + * rewardBalance / recentClaims + * steemPrice; + + return voteValue; + + } + } + + function getVoteValueUSD(vote_value, sbd_price) { + const steempower_value = vote_value * 0.5 + const sbd_print_percentage_half = (0.5 * sbd_print_percentage) + const sbd_value = vote_value * sbd_print_percentage_half + const steem_value = vote_value * (0.5 - sbd_print_percentage_half) + return (sbd_value * sbd_price) + steem_value + steempower_value + } + +function timeTilFullPower(cur_power){ + return (STEEMIT_100_PERCENT - cur_power) * STEEMIT_VOTE_REGENERATION_SECONDS / STEEMIT_100_PERCENT; + } + + function timeTilKickOffVoting(cur_power){ + return (parseInt(config.vp_kickstart) - cur_power) * STEEMIT_VOTE_REGENERATION_SECONDS / parseInt(config.vp_kickstart); + } + + function getVestingShares(account) { + var effective_vesting_shares = parseFloat(account.vesting_shares.replace(" VESTS", "")) + + parseFloat(account.received_vesting_shares.replace(" VESTS", "")) + - parseFloat(account.delegated_vesting_shares.replace(" VESTS", "")); + return effective_vesting_shares; + } + + function getCurrency(amount) { + return amount.substr(amount.indexOf(' ') + 1); + } + + function loadUserList(location, callback) { + if(!location) { + if(callback) + callback(null); + + return; + } + + if (location.startsWith('http://') || location.startsWith('https://')) { + // Require the "request" library for making HTTP requests + var request = require("request"); + + request.get(location, function (e, r, data) { + try { + if(callback) + callback(data.replace(/[\r]/g, '').split('\n')); + } catch (err) { + console.log('Error loading blacklist from: ' + location + ', Error: ' + err); + + if(callback) + callback(null); + } + }); + } else if (fs.existsSync(location)) { + if(callback) + callback(fs.readFileSync(location, "utf8").replace(/[\r]/g, '').split('\n')); + } else if(callback) + callback([]); +} + +function format(n, c, d, t) { + var c = isNaN(c = Math.abs(c)) ? 2 : c, + d = d == undefined ? "." : d, + t = t == undefined ? "," : t, + s = n < 0 ? "-" : "", + i = String(parseInt(n = Math.abs(Number(n) || 0).toFixed(c))), + j = (j = i.length) > 3 ? j % 3 : 0; + return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : ""); + } + + function toTimer(ts) { + var h = Math.floor(ts / HOURS); + var m = Math.floor((ts % HOURS) / 60); + var s = Math.floor((ts % 60)); + return padLeft(h, 2) + ':' + padLeft(m, 2) + ':' + padLeft(s, 2); + } + + function toHrMn(ts) { + var h = Math.floor(ts / HOURS); + var m = Math.floor((ts % HOURS) / 60); + return padLeft(h, 2) + 'hr(s):' + padLeft(m, 2) + 'min(s)'; + } + + function padLeft(v, d) { + var l = (v + '').length; + if (l >= d) return v + ''; + for(var i = l; i < d; i++) + v = '0' + v; + return v; + } + + async function loadBots() { + var query = await axios.get('https://steembottracker.net/bid_bots'); + var bidBots = query.data; + var query = await axios.get('https://steembottracker.net/other_bots'); + var otherBots = query.data; + // console.log(bidBots); + // console.log(otherBots); + var allBots = bidBots.concat(otherBots); + botNames = _.map(allBots, 'name'); + // console.log(botNames); + return botNames; + } + + // the weight param is actually 100*1,000 at max to consume 20% VP + // with 100 being the max 100% per single vote, and 1,000 being the max potentially used votes + // so if we were to only consume 10 % of our VP, the weight would be set at 50,000 instead of default value of 100,000 + function calculateVotes(posts, weight) { + console.log('calculateVotes'); + if(typeof weight == 'undefined') { + weight = 100000; + } + var data = {}; + var x = 0; + // Rate multiplier post count + var rmc = _.countBy(posts, 'rate_multiplier'); + console.log(rmc); + _.forEach(rmc, function(value, key) { + x += key * value; + }); + console.log(x); + data.power_per_vote = Math.floor(weight / x); + return data + } + + function filterPosts(posts, banned_users) { + var results = Array(); + let config = getConfig(); + //takes care of making sure if we reached too far back in history + var dateSurpassed = 0; + + + for(var i = 0; i < posts.length; i++) { + var post = posts[i]; + + // Check if post category is main tag + if (post.category != config.main_tag) { + console.log('Post does not match category tag. ' + post.url); + continue; + } + //check if account was voted + let voted = _.findIndex(post.active_votes, ['voter', config.account]); + if (voted == -1) { + console.log('Post was not voted. ' + post.url); + continue; + } + // Check if account is beneficiary + var benefit = checkBeneficiary(post); + + if(!benefit) + continue; + + + //check if user is banned + var user_banned = false; + for (var n = 0; n < banned_users.length; n++) { + if (post.author == banned_users[n].user){ + console.log('User '+post.author+' is banned, skipping his post:' + post.url); + user_banned = true; + break; + } + } + if (user_banned) continue; + + //go back only to predefined days in history + if((new Date() - new Date(post.created + 'Z')) >= (config.max_days * 24 * 60 * 60 * 1000)) { + dateSurpassed += 1; + continue; + } + + results.push(post); + } + //if we got to old posts and received at least 10 posts, inform calling function that no need to move forward further + if (results.length == 0 && dateSurpassed>10){ + return -1; + } + return results; + + } + + function checkBeneficiary(post) { + let config = getConfig(); + // Check if account is beneficiary + var benefit = 0; + for (var x = 0; x < post.beneficiaries.length; x++) { + for (var n = 0; n < config.beneficiaries.length; n++) { + if (post.beneficiaries[x].account === config.beneficiaries[n]) + benefit ++; + } + if (benefit === config.beneficiaries.length) { + benefit = true; + break; + } + } + if(!benefit) + return false; + + return true; + + } + + /** + * function handles mapping and calculating relevant score + * params: + * * 2D array providing couplets of rules + * * factor multipier for data + * * current value to compare + */ + function calcScore(rules_array, factor, value){ + var result; + //console.log("rules_array.length:"+rules_array.length); + for (var i=0; i= end) + //console.log(date <= start) + + if (date >= end && date <= start) { + //console.log(txs[0]); + let op = txs[1].op + + //console.log(op[0]); + // Look for beneficiary payments + if (!opsArr.includes(op[0])){ + opsArr.push(op[0]); + } + if (op[0] === 'comment_benefactor_reward') { + //console.log('---------------------------------------'); + //console.log(op); + //console.log(op[1]); + let rewardedSP = parseFloat(vestsToSteemPower(op[1].vesting_payout).toFixed(3)) + totalSp += rewardedSP; + //console.log("rewardedSP:"+rewardedSP); + //calculate dollar value + //let steemInUSD = rewardedSP * steemPrice; + //console.log("steemInUSD:"+steemInUSD); + + let rewardedSTEEM = parseFloat(op[1].steem_payout.split(' ')[0]) + total_STEEM += rewardedSTEEM ; + //console.log("rewardedSTEEM:"+rewardedSTEEM); + + //let steemPureInUSD = rewardedSTEEM * steemPrice; + + + let rewardedSBD = parseFloat(op[1].sbd_payout.split(' ')[0]) + + //console.log("rewardedSBD:"+rewardedSBD); + + totalSBD += rewardedSBD; + + //calculate dollar value + //let sbdInUSD = rewardedSBD * sbdPrice; + + }else if (op[0] === 'producer_reward') { + //console.log('date:'+txs[1].timestamp+'op:'+op[1]); + let rewardedSP = parseFloat(vestsToSteemPower(op[1].vesting_shares.split(' ')[0]).toFixed(3)) + producerSPRewards += rewardedSP; + }else if (op[0] === 'curation_reward') { + //console.log(op[1]); + let rewardedSP = parseFloat(vestsToSteemPower(op[1].reward.split(' ')[0]).toFixed(3)) + curTotalSp += rewardedSP; + + //let rewardedSTEEM = parseFloat(op[1].steem_payout.split(' ')[0]) + //curTotalSTEEM += rewardedSTEEM ; + + + //let rewardedSBD = parseFloat(op[1].sbd_payout.split(' ')[0]) + + //console.log("rewardedSBD:"+rewardedSBD); + + //curTotalSBD += rewardedSBD; + }else if (op[0] === 'author_reward') { + //console.log('caught one author_reward'); + //console.log(op); + + authorRewardedSBD += parseFloat(op[1].sbd_payout.split(' ')[0]) + authorRewardedSp += parseFloat(vestsToSteemPower(op[1].vesting_payout.split(' ')[0]).toFixed(3)) + authorRewardedSTEEM += parseFloat((op[1].steem_payout.split(' ')[0])) + }else if (op[0] === 'comment_reward') { + console.log('comment_reward FOUND'); + console.log(op); + }else if (op[0] === 'transfer' && op[1].from === account && + (op[1].to !== 'bittrex' && !op[1].to.includes('actifit'))){//skip bittrex and actifit account transfers + let amountWithCur = op[1].amount; + let amount = amountWithCur.split(' ')[0] + let cur = amountWithCur.split(' ')[1] + if (cur == 'SBD'){ + accountSBDTransfer += parseFloat(amount) + }else{ + accountSTEEMTransfer += parseFloat(amount) + } + }else if (op[0] === 'transfer' && op[1].to === account && + (!op[1].from.includes('actifit'))){//skip intra account transfer + let amountWithCur = op[1].amount; + let amount = amountWithCur.split(' ')[0] + let cur = amountWithCur.split(' ')[1] + if (cur == 'SBD'){ + accountSBDTransferIn += parseFloat(amount) + }else{ + accountSTEEMTransferIn += parseFloat(amount) + } + } + } else if (date < end){ + break + } + } + + let lastTx = transactions[transactions.length - 1] + //console.log(lastTx[0]); + let lastDate = moment(lastTx[1].timestamp).format() + // console.log(lastDate) + if (lastDate >= end && (txStart == -1 || txStart > limit)){ + txStart = lastTx[0]; + return getAccountPayTransactions(account, start, end, period) + } + console.log ('querying complete'); + console.log ('>>period: '+period + ' days') + console.log ('---benefic---'); + console.log ('totalSP:'+totalSp); + if (period>0 && totalSp>0){ + console.log ('AVG Daily:'+totalSp/period); + } + console.log ('total_STEEM:'+total_STEEM); + if (period>0 && total_STEEM>0){ + console.log ('AVG Daily:'+total_STEEM/period); + } + console.log ('totalSBD:'+totalSBD); + if (period>0 && totalSBD>0){ + console.log ('AVG Daily:'+totalSBD/period); + } + console.log ('---curation---'); + console.log ('totalSP:'+curTotalSp); + if (period>0 && curTotalSp>0){ + console.log ('AVG Daily:'+curTotalSp/period); + } + + console.log ('---author---'); + console.log ('totalSP:'+authorRewardedSp); + console.log ('total_STEEM:'+authorRewardedSTEEM); + console.log ('totalSBD:'+authorRewardedSBD); + if (period>0 && curTotalSp>0){ + console.log ('AVG Daily:'+curTotalSp/period); + } + + //console.log ('totalSTEEM:'+curTotalSTEEM); + // console.log ('totalSBD:'+curTotalSBD); + console.log ('---witness---'); + console.log ('producerSPRewards:'+producerSPRewards); + if (period>0 && producerSPRewards>0){ + console.log ('AVG Daily:'+producerSPRewards/period); + } + console.log ('---totals---'); + let comSP = parseFloat(totalSp.toFixed(3))+parseFloat(curTotalSp.toFixed(3))+parseFloat(producerSPRewards.toFixed(3))+parseFloat(total_STEEM.toFixed(3)) + + parseFloat(authorRewardedSp.toFixed(3))+parseFloat(authorRewardedSTEEM.toFixed(3)) + parseFloat(accountSTEEMTransferIn.toFixed(3)); + console.log ('totalSTEEM:'+comSP.toFixed(3)); + if (period>0 && comSP>0){ + console.log ('AVG Daily:'+comSP/period); + } + //console.log ('totalSTEEM:'+totalSTEEM.toFixed(3)); + let comSBD = parseFloat(totalSBD.toFixed(3))+parseFloat(authorRewardedSBD.toFixed(3))+ parseFloat(accountSBDTransferIn.toFixed(3));; + console.log ('totalSBD:'+comSBD); + if (period>0 && comSBD>0){ + console.log ('AVG Daily:'+comSBD/period); + } + console.log ('---delegator pay---'); + console.log ('delegatorPaySTEEM:'+accountSTEEMTransfer); + console.log ('delegatorPaySBD:'+accountSBDTransfer); + console.log ('------------'); + //console.log (opsArr); +} + + +function vestsToSteemPower (vests) { + vests = Number(vests.split(' ')[0]) + const steemPower = (totalSteem * (vests / totalVests)) + return steemPower +} + +//function handles conversting SP to Vests +async function steemPowerToVests (steemPower) { + + if (isNaN(totalSteem) || isNaN(totalVests) ){ + properties = await client.database.getDynamicGlobalProperties() + totalSteem = Number(properties.total_vesting_fund_steem.split(' ')[0]) + totalVests = Number(properties.total_vesting_shares.split(' ')[0]) + } + return parseFloat(steemPower * totalVests / totalSteem).toFixed(6); +} + +//function handles conversting SP to Vests +async function hivePowerToVests (hivePower) { + + if (isNaN(totalHive) || isNaN(totalHiveVests) ){ + hiveProps = await hiveClient.database.getDynamicGlobalProperties() + totalHive = Number(hiveProps.total_vesting_fund_steem.split(' ')[0]) + totalHiveVests = Number(hiveProps.total_vesting_shares.split(' ')[0]) + } + return parseFloat(hivePower * totalHiveVests / totalHive).toFixed(6); +} + +function sortArrLodash (arrToSort) { + return _.orderBy(arrToSort, function (o) { return new Number(o.balance)},['desc']); +} + +function removeArrMatchLodash (arrToClean, arrToMatch, field) { + let removedEntries = _.remove(arrToClean, obj => arrToMatch.includes(obj[field])); + //console.log("removedEntries"); + //console.log(removedEntries); + return arrToClean; +} + +async function rewardPost(post_url, vp, bchain){ + //extract author and permalink from full url + //check if string ends with /, remove it + if (post_url.slice(-1) == '/'){ + post_url = post_url.slice(0, -1); + } + //last portion is URL + let permalink = post_url.split('/').reverse()[0]; + //before last portion is author, and remove the starting @ + let author = post_url.split('/').reverse()[1].replace('@',''); + let chainLnk = await setProperNode(bchain); + //cast vote + let result = await chainLnk.broadcast.voteAsync( + config.rewards_account_pkey, //postingWIF + config.rewards_account, // Voter + author, // Author + permalink, // Permlink + parseFloat(vp)*100, // Weight (10000 = 100%) + ); + return result; +} + +async function verifyGadgetPayTransaction(userA, gadget_id, item_price, item_price_alt, tx_type, block_num, tx_id, bchain){ + let trx; + console.log('verifyGadgetTransaction'); + try{ + if (bchain == 'STEEM'){ + trx = await client.database.getTransaction({id: tx_id, block_num: block_num}); + }else{ + trx = await hiveClient.database.getTransaction({id: tx_id, block_num: block_num}); + } + console.log(trx); + if (trx && trx.operations + && trx.operations.length > 0){ + console.log(trx.operations[0][1]); + let trx_details = trx.operations[0][1]; + let amnt = trx_details.amount.split(' ')[0];; + //let json_data = JSON.parse(trx_details.json); + console.log(trx_details); + if (trx_details.to == config.gadget_buy_account && trx_details.memo == tx_type + ':' + gadget_id + && (amnt >= item_price || amnt >= item_price_alt)){ + return {'success': true, 'amount_hive': amnt}; + } + } + }catch(err){ + console.log(err); + } + return false; +} + +async function verifyGadgetTransaction(userA, gadget_id, tx_type, block_num, tx_id, bchain){ + let trx; + console.log('verifyGadgetTransaction'); + try{ + if (bchain == 'STEEM'){ + trx = await client.database.getTransaction({id: tx_id, block_num: block_num}); + }else if (bchain == 'HIVE'){ + trx = await hiveClient.database.getTransaction({id: tx_id, block_num: block_num}); + } + console.log(trx); + if (trx && trx.operations + && trx.operations.length > 0){ + console.log(trx.operations[0][1]); + let trx_details = trx.operations[0][1]; + let json_data = JSON.parse(trx_details.json); + console.log(trx_details); + if (trx_details.required_posting_auths.length > 0 && trx_details.required_posting_auths[0] == userA + && json_data.transaction == tx_type && json_data.gadget == gadget_id){ + return true; + } + } + }catch(err){ + console.log(err); + } + return false; +} + +async function verifyFriendTransaction(userA, userB, tx_type, block_num, tx_id, bchain){ + let trx + try{ + if (bchain == 'STEEM'){ + trx = await client.database.getTransaction({id: tx_id, block_num: block_num}); + }else if (bchain == 'HIVE'){ + trx = await hiveClient.database.getTransaction({id: tx_id, block_num: block_num}); + } + if (trx && trx.operations + && trx.operations.length > 0){ + console.log(trx.operations[0][1]); + let trx_details = trx.operations[0][1]; + let json_data = JSON.parse(trx_details.json); + console.log(trx_details); + if (trx_details.required_posting_auths.length > 0 && trx_details.required_posting_auths[0] == userA + && json_data.transaction == tx_type && json_data.target == userB){ + return true; + } + + } + }catch(err){ + console.log(err); + } + return false; +} + +async function sendNotification(db, user, action_taker, type, details, url){ + let notification_entry = { + user: user, + action_taker: action_taker, + type: type, + details: details, + url: url, + date: new Date(), + status: 'unread', + }; + try{ + let transaction = await db.collection('notifications').insert(notification_entry); + console.log('success inserting notification data'); + return true; + }catch(err){ + console.log('error'); + return false; + } +} + + +async function grabLastDrawData(db){ + let lastDraw = await db.collection('gadget_buy_prize_draw').find().sort({'drawDate': -1}).toArray(); + let drawData = {}; + if (Array.isArray(lastDraw) && lastDraw.length > 0){ + let start = moment(lastDraw[0].drawDate).utc().startOf('date').toDate(); + let nextDrawDate = moment(start).add(config.contestBuyLen, 'days').toDate(); + lastDraw[0].nextDrawDate = nextDrawDate; + drawData = lastDraw[0]; + }else{ + let start = moment(config.gadgetPrizeInitDate).utc().startOf('date').toDate(); + let nextDrawDate = moment(start).add(config.contestBuyLen, 'days').toDate(); + drawData = {'drawDate': start, 'nextDrawDate': nextDrawDate}; + } + return drawData; +} + +async function getGadgetBuyTickets(db){ + let drawData = await grabLastDrawData(db); + + let startDate = moment(drawData.drawDate).format('YYYY-MM-DD'); + + //let endDate = moment(moment(startDate).utc().subtract(config.contestBuyLen, 'days').toDate()).format('YYYY-MM-DD'); + + console.log("startDate:"+startDate);//+" endDate:"+endDate); + + let query = { + date: { + $gte: new Date(startDate), + } + } + + let result = await db.collection('gadget_buy_tickets').find(query).toArray(); + return result; +} + + module.exports = { + updateSteemVariables: updateSteemVariables, + getVotingPower: getVotingPower, + getRC: getRC, + claimDiscountedAccount: claimDiscountedAccount, + getVoteValueUSD: getVoteValueUSD, + getVoteValue: getVoteValue, + timeTilFullPower: timeTilFullPower, + timeTilKickOffVoting: timeTilKickOffVoting, + getVestingShares: getVestingShares, + loadUserList: loadUserList, + getCurrency: getCurrency, + format: format, + toTimer: toTimer, + toHrMn: toHrMn, + log: log, + calcScore: calcScore, + calculateVotes: calculateVotes, + filterPosts: filterPosts, + getConfig: getConfig, + loadBots: loadBots, + checkBeneficiary: checkBeneficiary, + asyncForEach: asyncForEach, + generateRandomNumber: generateRandomNumber, + lookupAccountPay: lookupAccountPay, + vestsToSteemPower: vestsToSteemPower, + createAccount: createAccount, + delegateToAccount: delegateToAccount, + confirmPaymentReceived: confirmPaymentReceived, + confirmPaymentReceivedPassword: confirmPaymentReceivedPassword, + confirmSEAFITReceived: confirmSEAFITReceived, + confirmPaymentReceivedBuy: confirmPaymentReceivedBuy, + sortArrLodash: sortArrLodash, + getAccountData: getAccountData, + rewardPost: rewardPost, + verifyFriendTransaction: verifyFriendTransaction, + verifyGadgetTransaction: verifyGadgetTransaction, + removeArrMatchLodash: removeArrMatchLodash, + validateAccountLogin: validateAccountLogin, + processSteemTrx: processSteemTrx, + sendNotification: sendNotification, + confirmAFITXTransition: confirmAFITXTransition, + proceedAfitxMove: proceedAfitxMove, + verifyGadgetPayTransaction: verifyGadgetPayTransaction, + getGadgetBuyTickets: getGadgetBuyTickets, + grabLastDrawData: grabLastDrawData + } diff --git a/views/rewards.handlebars b/views/rewards.handlebars new file mode 100644 index 0000000..d9a4cff --- /dev/null +++ b/views/rewards.handlebars @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + +
+This week token rewards! +
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+

Actifit

+
+
+ +
+ + + + +
+ + + + +
+

{{curDate}}

+
+
+
+ +
+ + + + + + + + + + + + + + +
+

+ Weekly benefactor steem rewards! +

+
+

+ INFO: +

+
+ + + + + + + {{#each rewards}} + + + + + + {{/each}} +
+ Username + + Steem Reward + + URL +
+ {{user}} + + {{steem}} + + Pay now! +
+
+ + + + + + +
+ TOTAL + + Active delegations: {{totalUsers}} + + Benefactor rewards: {{total}} +
+
+ +
+ + + + + +
+ +
+ + + + + +
+

Courtesy of:

+

@cryptouru

+ +
+
+ + +
+ +
+ +
+ + +