From 18f55f6dd96b257ef463d7ab5e30db12e4dc4dfe Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev Date: Sat, 12 Sep 2020 18:47:00 +0300 Subject: [PATCH 01/55] Add check if user's Twitter account eligible --- README.md | 13 ++++-- app.js | 1 + config.json | 10 ++++- modules/checkAll.js | 19 +++++++-- modules/checkTwitterFollow.js | 3 +- modules/checkTwitterReqs.js | 74 ++++++++++++++++++++++++++++++++++ modules/checkTwitterRetweet.js | 3 +- modules/checkTxs.js | 4 +- modules/configReader.js | 6 +++ modules/twitterapi.js | 55 +++++++++++++++++++++++++ package.json | 2 +- 11 files changed, 179 insertions(+), 11 deletions(-) create mode 100644 modules/checkTwitterReqs.js diff --git a/README.md b/README.md index 3140b04..e956e0c 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ The bounty bot: * Interactive and interesting for users. The bot talks to users in ADAMANT Messenger chat directly * Works with Twitter campaigns: follow & retweet with comment (quote). You can set up mentions and hashtags. +* Set which Twitter accounts are eligible to participate: minimum followers, friends, statuses and lifetime * Supports ADAMANT campaigns: users will invite other users * Automatic task verifications and payouts * Supports payouts in ADM, ETH and ERC-20 tokens @@ -13,10 +14,10 @@ The bounty bot: * Free and open source * Stores statistics -Read more: [Carry out a crypto Bounty campaign on ADAMANT platform](). - # Installation +User-friendly instructions: [Carry out a crypto Bounty campaign on ADAMANT platform](https://medium.com/adamant-im/adamants-interactive-bounty-bot-for-cryptocurrency-projects-51fec10f93b9). + ## Requirements * Ubuntu 16 / Ubuntu 18 (other OS had not been tested) @@ -47,14 +48,20 @@ Parameters: * `socket` If to use WebSocket connection. Recommended for better user experience * `ws_type` Choose socket connection, "ws" or "wss" depending on your server * `bot_name` Bot's name for notifications +* `admin_accounts` ADAMANT accounts to accept control commands from +* `notify_non_admins` Notify non-admins that they are not admins * `slack` Token for Slack alerts for the bot’s administrator. No alerts if not set * `adamant_notify` ADM address for the bot’s administrator. Recommended * `known_crypto` List of cryptocurrencies bot can work with. Obligatorily * `erc20` List of cryptocurrencies of ERC-20 type. It is necessary to put all known ERC-20 tokens here. * `twitter_follow` List of Twitter account user should follow -* `twitter_retweet` List of Twitter posts user should retweet +* `twitter_retweet` List of Twitter posts user should retweet * `twitter_api` Your Twitter API credentials. Get on https://apps.twitter.com/app/new +* `twitter_reqs` Requirements for user's Twitter account +* `twitter_api_test_interval` Interval in minutes to test Twitter API + +* `adamant_campaign` Settings for ADAMANT bounty campaign * `notifyTasksCompleted` If you want to receive notifications when user completes Bounty tasks * `notifyRewardReceived` If you want to receive notifications when user receives a Bounty reward diff --git a/app.js b/app.js index f300143..35c7121 100644 --- a/app.js +++ b/app.js @@ -17,6 +17,7 @@ function init() { require('./server'); require('./modules/checkTwitterFollow'); require('./modules/apiTester'); + require('./modules/checkTwitterReqs'); require('./modules/checkTwitterRetweet'); require('./modules/checkAdamantContacts'); require('./modules/checkAll'); diff --git a/config.json b/config.json index 613eb6e..c514b57 100644 --- a/config.json +++ b/config.json @@ -46,7 +46,7 @@ "welcome_string": "Hi! 😊 I'm a bounty bot. And this is a stub. Are you ready for the awesome bounty campaign with automatic payout? Type **/help** to see bounty rules.", /** Bounty rules. Shown by /help command. You can use template literals like ${config.twitter_follow[0]} and ${config.twitter_follow_list} **/ - "help_message": "Get the **${config.rewards_list}** reward right now!\n\nThe campaign rules:\n- Follow accounts ${config.twitter_follow_list} on Twitter\n- Make a retweet of ${config.twitter_retweet_w_comment[0]} with comment (quote) why you like cryptos. Mention at least ${config.twitter_retweet_w_comment[0].min_mentions} friends and use ${config.twitter_retweet_w_comment[0].tag_list} tags.\n- Send me the name (like @adamant_im) or the link to your Twitter profile to verify\n\n \nGo!", + "help_message": "Get the **${config.rewards_list}** reward right now!\n\nThe campaign rules:\n- Follow accounts ${config.twitter_follow_list} on Twitter\n- Make a retweet of ${config.twitter_retweet_w_comment[0]} with comment (quote) why you like cryptos. Mention at least ${config.twitter_retweet_w_comment[0].min_mentions} friends and use ${config.twitter_retweet_w_comment[0].tag_list} tags.\n- Send me the name (like @adamant_im) or the link to your Twitter profile to verify. Your account ${config.twitterEligibleString}.\n\n \nGo!", /** Bot's name for notifications **/ "bot_name": "Lovely Bounty Bot", @@ -61,6 +61,14 @@ "@adamant_im" ], + /** Requirements for user's Twitter account. Set all parameters to 0 if even new accounts eligible **/ + "twitter_reqs": { + "min_followers": 10, + "min_friends": 5, + "min_statuses": 5, + "min_days": 20 + }, + /** Tweets user should quote (retweet with comment). Min_mentions is how much people he should mention. Hashtags is a list of tags he must use. **/ diff --git a/modules/checkAll.js b/modules/checkAll.js index 1bd91b0..23c6a3a 100644 --- a/modules/checkAll.js +++ b/modules/checkAll.js @@ -17,7 +17,10 @@ module.exports = async () => { userId, isTwitterFollowCheckPassed, isTwitterRetweetCommentCheckPassed, - isAdamantCheckPassed + isAdamantCheckPassed, + twitterAccount, + twitterFollowers, + twitterLifetimeDays } = user; console.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}..`); @@ -44,8 +47,18 @@ module.exports = async () => { }); payment.save(); }) - if (config.notifyTasksCompleted) - notify(`${config.notifyName}: User ${userId} completed the Bounty tasks. Payouts are pending.`, 'log'); + if (config.notifyTasksCompleted) { + let twitterString = ''; + if (twitterAccount) + twitterString += ` (${twitterAccount}`; + if (twitterFollowers) + twitterString += `, followers: ${twitterFollowers}`; + if (twitterLifetimeDays) + twitterString += `, lifetime days: ${Math.round(twitterLifetimeDays)}`; + if (twitterAccount) + twitterString += `)`; + notify(`${config.notifyName}: User ${userId}${twitterString} completed the Bounty tasks. Payouts are pending.`, 'log'); + } msgSendBack = `Thank you! The Bounty tasks are completed! I am sending the reward to you.`; $u.sendAdmMsg(userId, msgSendBack); } diff --git a/modules/checkTwitterFollow.js b/modules/checkTwitterFollow.js index f387e02..0ff4519 100644 --- a/modules/checkTwitterFollow.js +++ b/modules/checkTwitterFollow.js @@ -11,7 +11,8 @@ module.exports = async () => { (await usersDb.find({ $and: [ - {isInCheck: true}, + {isInCheck: true}, + {isTwitterAccountEligible: true}, {isTwitterFollowCheckPassed: false}, {isTasksCompleted: false}, {$or: [ diff --git a/modules/checkTwitterReqs.js b/modules/checkTwitterReqs.js new file mode 100644 index 0000000..ebd0bcb --- /dev/null +++ b/modules/checkTwitterReqs.js @@ -0,0 +1,74 @@ +const db = require('./DB'); +const config = require('./configReader'); +const $u = require('../helpers/utils'); +const log = require('../helpers/log'); +const notify = require('../helpers/notify'); +const twitterapi = require('./twitterapi'); + +module.exports = async () => { + + const {usersDb} = db; + + (await usersDb.find({ + $and: [ + {isInCheck: true}, + {isTwitterAccountEligible: false}, + {isTasksCompleted: false}, + {$or: [ + {isAdamantCheckPassed: true}, + {$expr: {$eq: [0, config.adamant_campaign.min_contacts]}} + ]} + ] + })).forEach(async user => { + try { + const { + twitterAccount, + userId + } = user; + + console.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}..`); + + let msgSendBack = ''; + let result, isEligible; + + if (config.doCheckTwitterReqs) { + result = await twitterapi.checkIfAccountEligible(twitterAccount); + if (result.error === 'request_failed') return; + console.log(result); + isEligible = result.success; + } else { + isEligible = true; + } + + if (isEligible) { + console.log(`User ${userId}.. ${twitterAccount} is eligible.`); + + } else { + console.log(`User ${userId}.. ${twitterAccount} is NOT eligible.`); + await user.update({ + isTwitterAccountEligible: false, + isInCheck: false, + isTasksCompleted: false + }, true); + msgSendBack = `To meet the Bounty campaign rules, your Twitter account ${config.twitterEligibleString}.`; + await $u.sendAdmMsg(userId, msgSendBack); + } + + await user.update({ + isTwitterAccountEligible: isEligible, + twitterLifetimeDays: result.lifetimeDays, + twitterFollowers: result.followers + }, true); + + } catch (e) { + log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); + } + + }); + +}; + +if (config.isTwitterCampaign) + setInterval(() => { + module.exports(); + }, 15 * 1000); diff --git a/modules/checkTwitterRetweet.js b/modules/checkTwitterRetweet.js index e963a99..63b4a07 100644 --- a/modules/checkTwitterRetweet.js +++ b/modules/checkTwitterRetweet.js @@ -12,7 +12,8 @@ module.exports = async () => { (await usersDb.find({ $and: [ {isInCheck: true}, - {isTwitterRetweetCommentCheckPassed: false}, + {isTwitterAccountEligible: true}, + {isTwitterRetweetCommentCheckPassed: false}, {isTasksCompleted: false}, {$or: [ {isAdamantCheckPassed: true}, diff --git a/modules/checkTxs.js b/modules/checkTxs.js index 73ccbd5..39566b3 100644 --- a/modules/checkTxs.js +++ b/modules/checkTxs.js @@ -53,7 +53,8 @@ module.exports = async (itx, tx) => { twitterAccount: itx.accounts.twitterAccount, isTasksCompleted: false, isTwitterFollowCheckPassed: false, - isTwitterRetweetCommentCheckPassed: false + isTwitterRetweetCommentCheckPassed: false, + isTwitterAccountEligible: false }); } else { @@ -71,6 +72,7 @@ module.exports = async (itx, tx) => { isTasksCompleted: false, isTwitterFollowCheckPassed: false, isTwitterRetweetCommentCheckPassed: false, + isTwitterAccountEligible: false, isAdamantCheckPassed: false, }); } diff --git a/modules/configReader.js b/modules/configReader.js index b6e84f7..dad91c7 100644 --- a/modules/configReader.js +++ b/modules/configReader.js @@ -47,6 +47,10 @@ const fields = { type: Array, default: [] }, + twitter_reqs: { + type: Object, + default: { "min_followers": 0, "min_friends": 0, "min_statuses": 0, "min_days": 0 } + }, rewards: { type: Array, isRequired: true @@ -103,6 +107,8 @@ try { config.address = address; config.min_confirmations = 1; // Allowing myself to hardcode here config.isTwitterCampaign = (config.twitter_follow.length > 0) || (config.twitter_retweet_w_comment.length > 0); + config.doCheckTwitterReqs = config.isTwitterCampaign && ((config.twitter_reqs.min_followers > 0) || (config.twitter_reqs.min_friends > 0) || (config.twitter_reqs.min_statuses > 0) || (config.twitter_reqs.min_days > 0)); + config.twitterEligibleString = `must be older, than ${config.twitter_reqs.min_days} days, must have at least ${config.twitter_reqs.min_followers} followers, ${config.twitter_reqs.min_friends} friends, and ${config.twitter_reqs.min_statuses} tweets`; if (config.isTwitterCampaign && (!config.twitter_api.consumer_key || !config.twitter_api.consumer_key || !config.twitter_api.access_token_secret || !config.twitter_api.access_token_key)) { exit(`Bot's ${address} config is wrong. To run Twitter campaign, set Twitter API credentials (twitter_api). Cannot start Bot.`); diff --git a/modules/twitterapi.js b/modules/twitterapi.js index 0c146e7..83f0991 100644 --- a/modules/twitterapi.js +++ b/modules/twitterapi.js @@ -125,6 +125,11 @@ async function getAccountInfo(account) { } +function parseTwitterDate(aDate) +{ + return new Date(Date.parse(aDate.replace(/( \+)/, ' UTC$1'))); + //sample: Wed Mar 13 09:06:07 +0000 2013 +} module.exports = { @@ -213,6 +218,56 @@ module.exports = { error: '' } + }, + async checkIfAccountEligible(twitterAccount) { + + const twitterAccountSN = $u.getTwitterScreenName(twitterAccount); + console.log(`Checking if @${twitterAccountSN} eligible..`) + + let accountInfo = await getAccountInfo(twitterAccountSN); + // console.log(accountInfo); + + if (!accountInfo || !accountInfo.id) { + return { + success: false, + error: 'request_failed' + } + } + + if (accountInfo.followers_count < config.twitter_reqs.min_followers) { + return { + success: false, + error: 'no_followers' + } + } + if (accountInfo.friends_count < config.twitter_reqs.min_friends) { + return { + success: false, + error: 'no_friends' + } + } + if (accountInfo.statuses_count < config.twitter_reqs.min_statuses) { + return { + success: false, + error: 'no_statuses' + } + } + let createDate = parseTwitterDate(accountInfo.created_at); + let lifeTime = (Date.now() - createDate) / 1000 / 60 / 60 / 24; + if (lifeTime < config.twitter_reqs.min_days) { + return { + success: false, + error: 'no_lifetime' + } + } + + return { + success: true, + followers: accountInfo.followers_count, + lifetimeDays: lifeTime, + error: '' + } + } } \ No newline at end of file diff --git a/package.json b/package.json index e086b0e..f4e5ffc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "adamant-bountybot", - "version": "1.0.0", + "version": "1.1.0", "description": "ADAMANT Bounty bot — a software that allows you to carry out bounty campaigns & crypto airdrops, with automatic task verifications and payouts.", "main": "index.js", "scripts": { From 18c69e72afa79e21003ea51889933be8446cd466 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev Date: Fri, 25 Sep 2020 11:43:38 +0300 Subject: [PATCH 02/55] Add protection from Twitter ScreenName change --- modules/checkTwitterReqs.js | 36 +++++++++++++++++++++++++++++------- modules/checkTxs.js | 8 ++++++-- modules/twitterapi.js | 36 +++++++++++++++++++++++++++++------- 3 files changed, 64 insertions(+), 16 deletions(-) diff --git a/modules/checkTwitterReqs.js b/modules/checkTwitterReqs.js index ebd0bcb..0e985c1 100644 --- a/modules/checkTwitterReqs.js +++ b/modules/checkTwitterReqs.js @@ -29,15 +29,33 @@ module.exports = async () => { console.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}..`); let msgSendBack = ''; - let result, isEligible; + let result, isEligible = true; + let twitterAccountIdStr = null; + + result = await twitterapi.checkIfAccountEligible(twitterAccount); + // console.log(result); + if (result.error === 'request_failed') return; + if (result.error === 'user_not_found') + msgSendBack = `It seems Twitter account ${twitterAccount} does not exist. Please check and try again.` + else { + // Check if this user already participated in Bounty campaign. + // He may change Twitter's AccountName, but id will be the same + twitterAccountIdStr = result.accountInfo.id_str; + console.log('twitterAccountIdStr:', twitterAccountIdStr); + let userDuplicate = await usersDb.findOne({twitterAccountId: twitterAccountIdStr}); + console.log('user duplicate:', userDuplicate); + + if (userDuplicate && (userDuplicate.twitterAccount !== twitterAccount)) { + // This user changed his AccountName + isEligible = false; + msgSendBack = `This Twitter account is already in use by other participant. Account name is changed. If it's a mistake, try again in a few minutes.`; + } + } if (config.doCheckTwitterReqs) { - result = await twitterapi.checkIfAccountEligible(twitterAccount); - if (result.error === 'request_failed') return; - console.log(result); - isEligible = result.success; + isEligible = isEligible && result.success; } else { - isEligible = true; + isEligible = isEligible && true; } if (isEligible) { @@ -47,15 +65,19 @@ module.exports = async () => { console.log(`User ${userId}.. ${twitterAccount} is NOT eligible.`); await user.update({ isTwitterAccountEligible: false, + twitterAccountId: twitterAccountIdStr, isInCheck: false, isTasksCompleted: false }, true); - msgSendBack = `To meet the Bounty campaign rules, your Twitter account ${config.twitterEligibleString}.`; + + if (msgSendBack === '') + msgSendBack = `To meet the Bounty campaign rules, your Twitter account ${config.twitterEligibleString}.`; await $u.sendAdmMsg(userId, msgSendBack); } await user.update({ isTwitterAccountEligible: isEligible, + twitterAccountId: twitterAccountIdStr, twitterLifetimeDays: result.lifetimeDays, twitterFollowers: result.followers }, true); diff --git a/modules/checkTxs.js b/modules/checkTxs.js index 39566b3..4d00a80 100644 --- a/modules/checkTxs.js +++ b/modules/checkTxs.js @@ -15,6 +15,7 @@ module.exports = async (itx, tx) => { user = await usersDb.findOne({twitterAccount: itx.accounts.twitterAccount}); if (user && (user.isInCheck || user.isTasksCompleted)) { // This Twitter account is already in use by other user, unable to switch + log.info(`User ${user.userId} applied with already used Twitter account ${itx.accounts.twitterAccount}. Notify user and ignore.`); if (user.userId !== tx.senderId) { msgSendBack = `This Twitter account is already in use by other participant. If it's a mistake, try again in a few minutes.`; } else { @@ -31,7 +32,7 @@ module.exports = async (itx, tx) => { user = await usersDb.findOne({userId: tx.senderId}); if (user) { // User is already was in check earlier, update - console.log(`User ${user.userId} applied once again.`); + log.info(`User ${user.userId} applied once again.`); // May be later // if (user.isBountyPayed) { // msgSendBack = `You've already received the Bounty reward. Thanks for your support!`; @@ -39,6 +40,7 @@ module.exports = async (itx, tx) => { // return; // } else if (user.isTasksCompleted) { + log.info(`User ${user.userId} already completed the Bounty tasks. Notify user and ignore.`); msgSendBack = `You've already completed the Bounty tasks.`; $u.sendAdmMsg(tx.senderId, msgSendBack); return; @@ -51,6 +53,7 @@ module.exports = async (itx, tx) => { isInCheck: itx.accounts.notEmpty, twitterAccountLink: itx.accounts.twitterLink, twitterAccount: itx.accounts.twitterAccount, + twitterAccountId: null, isTasksCompleted: false, isTwitterFollowCheckPassed: false, isTwitterRetweetCommentCheckPassed: false, @@ -69,6 +72,7 @@ module.exports = async (itx, tx) => { isInCheck: itx.accounts.notEmpty, twitterAccountLink: itx.accounts.twitterLink, twitterAccount: itx.accounts.twitterAccount, + twitterAccountId: null, isTasksCompleted: false, isTwitterFollowCheckPassed: false, isTwitterRetweetCommentCheckPassed: false, @@ -80,7 +84,7 @@ module.exports = async (itx, tx) => { await user.save(); await itx.update({isProcessed: true}, true); - // console.log('User info:', user); + console.log('User info:', user); msgSendBack = `I've got your account details. Twitter: ${user.twitterAccount}. I'll check if you've finished the Bounty tasks now..`; $u.sendAdmMsg(tx.senderId, msgSendBack); diff --git a/modules/twitterapi.js b/modules/twitterapi.js index 83f0991..bfa6c0a 100644 --- a/modules/twitterapi.js +++ b/modules/twitterapi.js @@ -120,7 +120,10 @@ async function getAccountInfo(account) { }) .catch(function (e) { log.warn(`Error while making getAccountInfo() request: ${JSON.stringify(e)}`); - return false; + if (e && e[0] && e[0].code === 50) // [{"code":50,"message":"User not found."}] + return e[0] // User can provide wrong Account, process this situation + else + return false; }); } @@ -227,7 +230,21 @@ module.exports = { let accountInfo = await getAccountInfo(twitterAccountSN); // console.log(accountInfo); - if (!accountInfo || !accountInfo.id) { + if (!accountInfo) { + return { + success: false, + error: 'request_failed', + } + } + + if (accountInfo.code === 50) { // {"code":50,"message":"User not found."} + return { + success: false, + error: 'user_not_found' + } + } + + if (!accountInfo.id) { return { success: false, error: 'request_failed' @@ -237,19 +254,22 @@ module.exports = { if (accountInfo.followers_count < config.twitter_reqs.min_followers) { return { success: false, - error: 'no_followers' + error: 'no_followers', + accountInfo } } if (accountInfo.friends_count < config.twitter_reqs.min_friends) { return { success: false, - error: 'no_friends' + error: 'no_friends', + accountInfo } } if (accountInfo.statuses_count < config.twitter_reqs.min_statuses) { return { success: false, - error: 'no_statuses' + error: 'no_statuses', + accountInfo } } let createDate = parseTwitterDate(accountInfo.created_at); @@ -257,7 +277,8 @@ module.exports = { if (lifeTime < config.twitter_reqs.min_days) { return { success: false, - error: 'no_lifetime' + error: 'no_lifetime', + accountInfo } } @@ -265,7 +286,8 @@ module.exports = { success: true, followers: accountInfo.followers_count, lifetimeDays: lifeTime, - error: '' + error: '', + accountInfo } } From 5abed747ab560b3867bd5643fe676de0f3668264 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev Date: Fri, 25 Sep 2020 15:03:14 +0300 Subject: [PATCH 03/55] Update logging --- helpers/utils/adm_utils.js | 2 +- modules/checkAdamantContacts.js | 3 ++- modules/checkTwitterFollow.js | 3 ++- modules/checkTwitterReqs.js | 29 +++++++++++++++-------------- modules/checkTwitterRetweet.js | 4 +++- modules/checkTxs.js | 2 +- modules/twitterapi.js | 2 +- package.json | 2 +- 8 files changed, 26 insertions(+), 21 deletions(-) diff --git a/helpers/utils/adm_utils.js b/helpers/utils/adm_utils.js index 67e35ec..c08d6db 100644 --- a/helpers/utils/adm_utils.js +++ b/helpers/utils/adm_utils.js @@ -38,7 +38,7 @@ module.exports = { async send(params) { try { const {address, value, comment} = params; - console.log(`Send ${value} ADM: `, comment); + log.info(`Send ${value} ADM: `, comment); let res; if (comment){ res = api.send(User.passPhrase, address, comment, 'message', null, value); diff --git a/modules/checkAdamantContacts.js b/modules/checkAdamantContacts.js index 1296d49..f5a2e0f 100644 --- a/modules/checkAdamantContacts.js +++ b/modules/checkAdamantContacts.js @@ -75,7 +75,7 @@ module.exports = async () => { console.log(`User ${userId}.. did make ${config.adamant_campaign.min_contacts} contacts.`); } else { - console.log(`User ${userId}.. did NOT make ${config.adamant_campaign.min_contacts} contacts.`); + await user.update({ isAdamantCheckPassed: false, isInCheck: false, @@ -89,6 +89,7 @@ module.exports = async () => { } await $u.sendAdmMsg(userId, msgSendBack); + log.info(`User ${userId}.. did NOT make ${config.adamant_campaign.min_contacts} contacts. Message to user: ${msgSendBack}`); } await user.update({ diff --git a/modules/checkTwitterFollow.js b/modules/checkTwitterFollow.js index 0ff4519..0532370 100644 --- a/modules/checkTwitterFollow.js +++ b/modules/checkTwitterFollow.js @@ -44,7 +44,7 @@ module.exports = async () => { console.log(`User ${userId}.. ${twitterAccount} do follows ${followAccount}.`); } else { - console.log(`User ${userId}.. ${twitterAccount} do NOT follows ${followAccount}.`); + await user.update({ isTwitterFollowCheckPassed: false, isInCheck: false, @@ -52,6 +52,7 @@ module.exports = async () => { }, true); msgSendBack = `To meet the Bounty campaign rules, you should follow Twitter account ${followAccount}. Follow the account and try again.`; await $u.sendAdmMsg(userId, msgSendBack); + log.info(`User ${userId}.. ${twitterAccount} do NOT follows ${followAccount}. Message to user: ${msgSendBack}`); break; } } diff --git a/modules/checkTwitterReqs.js b/modules/checkTwitterReqs.js index 0e985c1..aa89bec 100644 --- a/modules/checkTwitterReqs.js +++ b/modules/checkTwitterReqs.js @@ -34,21 +34,22 @@ module.exports = async () => { result = await twitterapi.checkIfAccountEligible(twitterAccount); // console.log(result); - if (result.error === 'request_failed') return; - if (result.error === 'user_not_found') - msgSendBack = `It seems Twitter account ${twitterAccount} does not exist. Please check and try again.` - else { + + if (result.error === 'request_failed') { + return; // If request to Twitter API failed, ignore and check next time + } + + if (result.error === 'user_not_found') { + msgSendBack = `It seems Twitter account ${twitterAccount} does not exist. Please re-check and try again.` + } else { // Check if this user already participated in Bounty campaign. - // He may change Twitter's AccountName, but id will be the same + // He may change Twitter's AccountName (screen name) to cheat, but Id will be the same twitterAccountIdStr = result.accountInfo.id_str; - console.log('twitterAccountIdStr:', twitterAccountIdStr); let userDuplicate = await usersDb.findOne({twitterAccountId: twitterAccountIdStr}); - console.log('user duplicate:', userDuplicate); - - if (userDuplicate && (userDuplicate.twitterAccount !== twitterAccount)) { - // This user changed his AccountName + if (userDuplicate && (userDuplicate.twitterAccount !== twitterAccount) && (userDuplicate.isInCheck || userDuplicate.isTasksCompleted)) { + // This user changed his AccountName (screen name) isEligible = false; - msgSendBack = `This Twitter account is already in use by other participant. Account name is changed. If it's a mistake, try again in a few minutes.`; + msgSendBack = `This Twitter account is already in use by other participant with other account name: ${userDuplicate.twitterAccount}. Cheating detected. If it's a mistake, try again in a few minutes.`; } } @@ -62,17 +63,17 @@ module.exports = async () => { console.log(`User ${userId}.. ${twitterAccount} is eligible.`); } else { - console.log(`User ${userId}.. ${twitterAccount} is NOT eligible.`); + await user.update({ - isTwitterAccountEligible: false, - twitterAccountId: twitterAccountIdStr, isInCheck: false, isTasksCompleted: false }, true); if (msgSendBack === '') msgSendBack = `To meet the Bounty campaign rules, your Twitter account ${config.twitterEligibleString}.`; + await $u.sendAdmMsg(userId, msgSendBack); + log.info(`User ${userId}.. ${twitterAccount} is NOT eligible. Message to user: ${msgSendBack}`); } await user.update({ diff --git a/modules/checkTwitterRetweet.js b/modules/checkTwitterRetweet.js index 63b4a07..82c1d5a 100644 --- a/modules/checkTwitterRetweet.js +++ b/modules/checkTwitterRetweet.js @@ -50,7 +50,7 @@ module.exports = async () => { console.log(`User ${userId}.. ${twitterAccount} did retweet ${toRetweet}.`); } else { - console.log(`User ${userId}.. ${twitterAccount} did NOT retweet ${toRetweet}: ${retweetResult.error}.`); + await user.update({ isTwitterRetweetCommentCheckPassed: false, isInCheck: false, @@ -86,6 +86,8 @@ module.exports = async () => { } await $u.sendAdmMsg(userId, msgSendBack); + log.info(`User ${userId}.. ${twitterAccount} did NOT retweet ${toRetweet}: ${retweetResult.error}. Message to user: ${msgSendBack}`); + break; } } diff --git a/modules/checkTxs.js b/modules/checkTxs.js index 4d00a80..2283890 100644 --- a/modules/checkTxs.js +++ b/modules/checkTxs.js @@ -84,7 +84,7 @@ module.exports = async (itx, tx) => { await user.save(); await itx.update({isProcessed: true}, true); - console.log('User info:', user); + // console.log('User info:', user); msgSendBack = `I've got your account details. Twitter: ${user.twitterAccount}. I'll check if you've finished the Bounty tasks now..`; $u.sendAdmMsg(tx.senderId, msgSendBack); diff --git a/modules/twitterapi.js b/modules/twitterapi.js index bfa6c0a..cdb54ca 100644 --- a/modules/twitterapi.js +++ b/modules/twitterapi.js @@ -180,7 +180,7 @@ module.exports = { const twitterAccountSN = $u.getTwitterScreenName(twitterAccount); const tweetId = $u.getTweetIdFromLink(tweet); hashtags = $u.getTwitterHashtags(hashtags); - console.log(tweetId); + // console.log(tweetId); console.log(`Checking if @${twitterAccountSN} retweeted ${tweet}..`) let tweets = await getAccountTimeline(twitterAccountSN); diff --git a/package.json b/package.json index f4e5ffc..1256556 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "adamant-bountybot", - "version": "1.1.0", + "version": "1.2.0", "description": "ADAMANT Bounty bot — a software that allows you to carry out bounty campaigns & crypto airdrops, with automatic task verifications and payouts.", "main": "index.js", "scripts": { From 8273c0cbe9e237d2ee1c9cad38ae2f92fd9e333f Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev Date: Wed, 30 Sep 2020 23:41:33 +0300 Subject: [PATCH 04/55] Add progressive scale for payouts, depending on Twitter followers count --- config.json | 16 +++++++++++++++- modules/checkAll.js | 15 ++++++++++++++- modules/configReader.js | 38 ++++++++++++++++++++++++++++++++++++++ package.json | 3 ++- 4 files changed, 69 insertions(+), 3 deletions(-) diff --git a/config.json b/config.json index c514b57..5644f95 100644 --- a/config.json +++ b/config.json @@ -47,6 +47,7 @@ /** Bounty rules. Shown by /help command. You can use template literals like ${config.twitter_follow[0]} and ${config.twitter_follow_list} **/ "help_message": "Get the **${config.rewards_list}** reward right now!\n\nThe campaign rules:\n- Follow accounts ${config.twitter_follow_list} on Twitter\n- Make a retweet of ${config.twitter_retweet_w_comment[0]} with comment (quote) why you like cryptos. Mention at least ${config.twitter_retweet_w_comment[0].min_mentions} friends and use ${config.twitter_retweet_w_comment[0].tag_list} tags.\n- Send me the name (like @adamant_im) or the link to your Twitter profile to verify. Your account ${config.twitterEligibleString}.\n\n \nGo!", + "help_message": "Get the reward in **${config.rewards_tickers}** right now! Amount you'll receive depends on your twitter followers count:\n${config.rewards_range}\n\n \nThe campaign rules:\n- Follow accounts ${config.twitter_follow_list} on Twitter\n- Make a retweet of ${config.twitter_retweet_w_comment[0]} with comment (quote) why you like cryptos. Mention at least ${config.twitter_retweet_w_comment[0].min_mentions} friends and use ${config.twitter_retweet_w_comment[0].tag_list} tags.\n- Send me the name (like @adamant_im) or the link to your Twitter profile to verify. Your account ${config.twitterEligibleString}.\n\n \nGo!", /** Bot's name for notifications **/ "bot_name": "Lovely Bounty Bot", @@ -101,7 +102,20 @@ "amount": 0.01 } ], - + + /** Set progressive scale of reward amounts for each cryptocurrency. + `func` is a mathjs.org function. Limit followers with `limit_followers` parameter. + If not set for a currency, plain amount is used, which is set in `rewards.amount`. + **/ + "rewards_progression_from_twitter_followers": { + "ADM": { + "func": "sqrt(followers) * 3", + "limit_followers": 5000, + "decimals_transfer": 8, + "decimals_show": 0 + } + }, + /** Your Twitter API credentials. Get on https://apps.twitter.com/app/new **/ "twitter_api": { "consumer_key": "", diff --git a/modules/checkAll.js b/modules/checkAll.js index 23c6a3a..0082ea7 100644 --- a/modules/checkAll.js +++ b/modules/checkAll.js @@ -3,6 +3,7 @@ const config = require('./configReader'); const $u = require('../helpers/utils'); const log = require('../helpers/log'); const notify = require('../helpers/notify'); +const mathjs = require('mathjs'); module.exports = async () => { @@ -35,13 +36,25 @@ module.exports = async () => { isTasksCompleted: true }, true); config.rewards.forEach(reward => { + let amount = reward.amount; + // console.log(config.rewards_progression_from_twitter_followers[reward.currency]); + if (config.rewards_progression_from_twitter_followers[reward.currency] && twitterFollowers) { + let followersCount = twitterFollowers; + if (followersCount > config.rewards_progression_from_twitter_followers[reward.currency].limit_followers) + followersCount = config.rewards_progression_from_twitter_followers[reward.currency].limit_followers; + let f = config.rewards_progression_from_twitter_followers[reward.currency].func; + amount = mathjs.evaluate(f, {followers: followersCount}); + amount = +amount.toFixed(config.rewards_progression_from_twitter_followers[reward.currency].decimals_transfer); + // console.log(amount); + // process.exit(1); + } payment = new paymentsDb({ date: $u.unix(), userId, isPayed: false, isFinished: false, outCurrency: reward.currency, - outAmount: reward.amount, + outAmount: amount, outTxid: null, outAddress: null }); diff --git a/modules/configReader.js b/modules/configReader.js index dad91c7..e550c42 100644 --- a/modules/configReader.js +++ b/modules/configReader.js @@ -3,6 +3,8 @@ const fs = require('fs'); const log = require('../helpers/log'); const keys = require('adamant-api/helpers/keys'); const isDev = process.argv.includes('dev'); +const mathjs = require('mathjs'); + let config = {}; // Validate config fields @@ -55,6 +57,10 @@ const fields = { type: Array, isRequired: true }, + rewards_progression_from_twitter_followers: { + type: Object, + default: {} + }, adamant_campaign: { type: Object, default: { "min_contacts": 0 } @@ -121,6 +127,38 @@ try { }) .join(' + '); + // Create reward tickers + config.rewards_tickers = config.rewards + .map(t => { + return `${t.currency}`; + }) + .join(' + '); + + // Create reward ranges + config.rewards_range = config.rewards + .map(t => { + if (config.rewards_progression_from_twitter_followers[t.currency]) { + + let min_followers = 0; + if (config.twitter_reqs.min_followers) + min_followers = config.twitter_reqs.min_followers; + let max_followers = 1000000000; + if (config.rewards_progression_from_twitter_followers[t.currency].limit_followers) + max_followers = config.rewards_progression_from_twitter_followers[t.currency].limit_followers; + + let f = config.rewards_progression_from_twitter_followers[t.currency].func; + let min_amount = mathjs.evaluate(f, {followers: min_followers}); + min_amount = +min_amount.toFixed(config.rewards_progression_from_twitter_followers[t.currency].decimals_show); + let max_amount = mathjs.evaluate(f, {followers: max_followers}); + max_amount = +max_amount.toFixed(config.rewards_progression_from_twitter_followers[t.currency].decimals_show); + + return `- from ${min_amount} ${t.currency} to ${max_amount} ${t.currency}`; + } else { + return `- ${t.amount} ${t.currency}`; + } + }) + .join("\n"); + // Process help_message as a template literal config.twitter_follow_list = config.twitter_follow.join(', '); config.twitter_retweet_w_comment.forEach(tweet => { diff --git a/package.json b/package.json index 1256556..a4a0294 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "adamant-bountybot", - "version": "1.2.0", + "version": "1.3.0", "description": "ADAMANT Bounty bot — a software that allows you to carry out bounty campaigns & crypto airdrops, with automatic task verifications and payouts.", "main": "index.js", "scripts": { @@ -24,6 +24,7 @@ "author": "Aleksei Lebedev (https://adamant.im)", "license": "GPL-3.0", "dependencies": { + "mathjs": "^7.3.0", "adamant-api": "^0.5.3", "ethereumjs-tx": "^2.1.2", "ethereumjs-util": "^7.0.0", From 5fed0550c0a9f3bf1768eafcbf35427df8e25cfc Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev Date: Thu, 1 Oct 2020 00:06:02 +0300 Subject: [PATCH 05/55] Add check if "User has been suspended" --- modules/twitterapi.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/twitterapi.js b/modules/twitterapi.js index cdb54ca..865cd5b 100644 --- a/modules/twitterapi.js +++ b/modules/twitterapi.js @@ -244,6 +244,13 @@ module.exports = { } } + if (accountInfo.code === 63) { // {"code":63,"message":"User has been suspended."} + return { + success: false, + error: 'user_not_found' + } + } + if (!accountInfo.id) { return { success: false, From 06b7c02ff7600de0139b98f5d4beb5ce30f482d5 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev Date: Thu, 1 Oct 2020 00:13:37 +0300 Subject: [PATCH 06/55] Fix for "User has been suspended." --- modules/twitterapi.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/twitterapi.js b/modules/twitterapi.js index 865cd5b..43e1c30 100644 --- a/modules/twitterapi.js +++ b/modules/twitterapi.js @@ -120,7 +120,7 @@ async function getAccountInfo(account) { }) .catch(function (e) { log.warn(`Error while making getAccountInfo() request: ${JSON.stringify(e)}`); - if (e && e[0] && e[0].code === 50) // [{"code":50,"message":"User not found."}] + if (e && e[0] && (e[0].code === 50 || e[0].code === 63)) // [{"code":50,"message":"User not found."}, {"code":63,"message":"User has been suspended."}] return e[0] // User can provide wrong Account, process this situation else return false; From 86614b17651b61519924767b711bb4d4c856541c Mon Sep 17 00:00:00 2001 From: adamant-al Date: Fri, 30 Jul 2021 13:38:50 +0300 Subject: [PATCH 07/55] Fix bugs --- config.json | 20 +++++++++------ modules/checkAdamantContacts.js | 4 ++- modules/checkAll.js | 2 +- modules/checkTwitterFollow.js | 6 ++--- modules/checkTwitterReqs.js | 2 +- modules/checkTwitterRetweet.js | 45 +++++++++++++++++++++++---------- modules/checkTxs.js | 4 +-- modules/configReader.js | 2 +- package.json | 8 +++--- 9 files changed, 57 insertions(+), 36 deletions(-) diff --git a/config.json b/config.json index 5644f95..f60cd18 100644 --- a/config.json +++ b/config.json @@ -42,15 +42,19 @@ /** List of ERC-20 tokens **/ "erc20": ["USDS", "RES", "BZ"], - /** How to reply user in-chat, if first unknown command received. **/ - "welcome_string": "Hi! 😊 I'm a bounty bot. And this is a stub. Are you ready for the awesome bounty campaign with automatic payout? Type **/help** to see bounty rules.", + /** How to reply user in-chat, if first unknown command received. **/ + "welcome_string": "Hi! 😊 I'm a bounty bot. And this is a stub. Are you ready for the awesome bounty campaign with automatic payout? Type **/help** to see bounty rules.", - /** Bounty rules. Shown by /help command. You can use template literals like ${config.twitter_follow[0]} and ${config.twitter_follow_list} **/ - "help_message": "Get the **${config.rewards_list}** reward right now!\n\nThe campaign rules:\n- Follow accounts ${config.twitter_follow_list} on Twitter\n- Make a retweet of ${config.twitter_retweet_w_comment[0]} with comment (quote) why you like cryptos. Mention at least ${config.twitter_retweet_w_comment[0].min_mentions} friends and use ${config.twitter_retweet_w_comment[0].tag_list} tags.\n- Send me the name (like @adamant_im) or the link to your Twitter profile to verify. Your account ${config.twitterEligibleString}.\n\n \nGo!", - "help_message": "Get the reward in **${config.rewards_tickers}** right now! Amount you'll receive depends on your twitter followers count:\n${config.rewards_range}\n\n \nThe campaign rules:\n- Follow accounts ${config.twitter_follow_list} on Twitter\n- Make a retweet of ${config.twitter_retweet_w_comment[0]} with comment (quote) why you like cryptos. Mention at least ${config.twitter_retweet_w_comment[0].min_mentions} friends and use ${config.twitter_retweet_w_comment[0].tag_list} tags.\n- Send me the name (like @adamant_im) or the link to your Twitter profile to verify. Your account ${config.twitterEligibleString}.\n\n \nGo!", + /** Bounty rules. Shown by /help command. You can use template literals + like ${config.rewards_list}, ${config.rewards_tickers}, ${config.twitter_follow[0]}, + ${config.twitter_retweet_w_comment[0].tweet}, ${config.twitter_follow_list}, + ${config.twitter_retweet_w_comment[0].min_mentions}, ${config.twitter_retweet_w_comment[0].tag_list}, + ${config.twitterEligibleString} + **/ + "help_message": "Earn **${config.rewards_tickers}** with your social activity! A reward depends on how much Twitter followers you have. Your account ${config.twitterEligibleString}.\n${config.rewards_range}\n\n \nThe campaign rules:\n- Follow account ${config.twitter_follow_list} on Twitter\n- Like & quote ${config.twitter_retweet_w_comment[0].tweet}, mentioning ${config.twitter_retweet_w_comment[0].min_mentions} friends and ${config.twitter_retweet_w_comment[0].tag_list} tags.\n- Invite ${config.adamant_campaign.min_contacts} friend in ADAMANT Messenger. They must write you.\n- Send me the name (like @adamant_im) or the link to your Twitter profile to verify.\n\n \nGo!", /** Bot's name for notifications **/ - "bot_name": "Lovely Bounty Bot", + "bot_name": "Lovely Bounty Bot", /** ADAMANT accounts to accept control commands from. Control commands from other accounts will not be executed. **/ "admin_accounts": [ @@ -78,7 +82,7 @@ "tweet": "https://twitter.com/adamant_im/status/1272945640574722048", "min_mentions": 3, "hashtags": [ - "#privacy" + "#privacy", "#crypto", "#anonymity", "#decentralization" ] } ], @@ -88,7 +92,7 @@ 0 is disabled. **/ "adamant_campaign": { - "min_contacts": 0 + "min_contacts": 1 }, /** List rewards for a Bounty campaign here **/ diff --git a/modules/checkAdamantContacts.js b/modules/checkAdamantContacts.js index f5a2e0f..1aad162 100644 --- a/modules/checkAdamantContacts.js +++ b/modules/checkAdamantContacts.js @@ -18,6 +18,8 @@ const excluded_adm_contacts = [ "U15423595369615486571", // Adoption and bounty "U17636520927910270607", // ADAMANT Contact "U6386412615727665758", // ADAMANT Contact + "U1835325601873095435", // ADAMANT Foundation Adoption + "U380651761819723095", // ADAMANT Foundation Donation Store.user.ADM.address // This bot address ] @@ -107,4 +109,4 @@ module.exports = async () => { if (config.adamant_campaign.min_contacts > 0) setInterval(() => { module.exports(); - }, 15 * 1000); + }, 8 * 1000); diff --git a/modules/checkAll.js b/modules/checkAll.js index 0082ea7..be0f88f 100644 --- a/modules/checkAll.js +++ b/modules/checkAll.js @@ -86,4 +86,4 @@ module.exports = async () => { setInterval(() => { module.exports(); -}, 15 * 1000); +}, 10 * 1000); diff --git a/modules/checkTwitterFollow.js b/modules/checkTwitterFollow.js index 0532370..ee46b26 100644 --- a/modules/checkTwitterFollow.js +++ b/modules/checkTwitterFollow.js @@ -50,9 +50,9 @@ module.exports = async () => { isInCheck: false, isTasksCompleted: false }, true); - msgSendBack = `To meet the Bounty campaign rules, you should follow Twitter account ${followAccount}. Follow the account and try again.`; + msgSendBack = `To meet the Bounty campaign rules, you should follow Twitter account ${followAccount}. Then you apply again.`; await $u.sendAdmMsg(userId, msgSendBack); - log.info(`User ${userId}.. ${twitterAccount} do NOT follows ${followAccount}. Message to user: ${msgSendBack}`); + log.log(`User ${userId}.. ${twitterAccount} do NOT follows ${followAccount}. Message to user: ${msgSendBack}`); break; } } @@ -71,4 +71,4 @@ module.exports = async () => { if (config.twitter_follow.length > 0) setInterval(() => { module.exports(); - }, 15 * 1000); + }, 10 * 1000); diff --git a/modules/checkTwitterReqs.js b/modules/checkTwitterReqs.js index aa89bec..5bf31ba 100644 --- a/modules/checkTwitterReqs.js +++ b/modules/checkTwitterReqs.js @@ -94,4 +94,4 @@ module.exports = async () => { if (config.isTwitterCampaign) setInterval(() => { module.exports(); - }, 15 * 1000); + }, 9 * 1000); diff --git a/modules/checkTwitterRetweet.js b/modules/checkTwitterRetweet.js index 82c1d5a..e8cf417 100644 --- a/modules/checkTwitterRetweet.js +++ b/modules/checkTwitterRetweet.js @@ -9,11 +9,16 @@ module.exports = async () => { const {usersDb} = db; + // Called strictly after isTwitterFollowCheckPassed = true to eliminate userDb collisions (await usersDb.find({ $and: [ {isInCheck: true}, {isTwitterAccountEligible: true}, {isTwitterRetweetCommentCheckPassed: false}, + {$or: [ + {isTwitterFollowCheckPassed: true}, + {$expr: {$eq: [0, config.twitter_follow.length]}} + ]}, {isTasksCompleted: false}, {$or: [ {isAdamantCheckPassed: true}, @@ -51,6 +56,13 @@ module.exports = async () => { } else { + // const friendsExample = ['@elonmusk', '@cz_binance', '@FabriLemus7', '@crypto', '@CryptoWhale']; + // let friendsExampleString = ''; + // for (let index = 0; index < friendsExample.length && index < minMentions; index++) { + // friendsExampleString += friendsExample[index] + ' ' + // } + // friendsExampleString = friendsExampleString.trim(); + await user.update({ isTwitterRetweetCommentCheckPassed: false, isInCheck: false, @@ -58,28 +70,33 @@ module.exports = async () => { }, true); switch (retweetResult.error) { case ('no_retweet'): - msgSendBack = `To meet the Bounty campaign rules, you should retweet ${toRetweet} with comment (quote).`; - if (minMentions > 0) { - msgSendBack += ` Mention at least ${minMentions} friends.`; - } - if (hashtags.length > 0) { - msgSendBack += ` Use ${config.twitter_retweet_w_comment[index].tag_list} tags.`; + msgSendBack = `To meet the Bounty campaign rules, you should quote (retweet with comment) ${toRetweet}.`; + if (minMentions > 0 && hashtags.length > 0) { + msgSendBack += ` Mention ${minMentions} friends, and use ${config.twitter_retweet_w_comment[index].tag_list} tags.`; + } else { + if (minMentions > 0) { + msgSendBack += ` Mention ${minMentions} friends.`; + } + if (hashtags.length > 0) { + msgSendBack += ` Use ${config.twitter_retweet_w_comment[index].tag_list} tags.`; + } } - msgSendBack += ` Do it and try again.`; + msgSendBack += ` Example: Meet the ADAMANT blockchain messenger! @elonmusk @cz_binance @FabriLemus7 #privacy #crypto #anonymity #decentralization`; + msgSendBack += `. Then you apply again.`; break; case ('not_enough_mentions'): - msgSendBack = `I see your retweet of ${toRetweet}.`; + msgSendBack = `I see your quote.`; if (minMentions > 0) { - msgSendBack += ` To meet the Bounty campaign rules, mention at least ${minMentions} friends.`; + msgSendBack += ` To meet the Bounty campaign rules, it should mention at least ${minMentions} friends.`; } - msgSendBack += ` Do it and try again.`; + msgSendBack += ` Quote once again.`; break; case ('no_hashtags'): - msgSendBack = `I see your retweet of ${toRetweet}.`; + msgSendBack = `I see your quote.`; if (hashtags.length > 0) { - msgSendBack += ` To meet the Bounty campaign rules, use ${config.twitter_retweet_w_comment[index].tag_list} tags.`; + msgSendBack += ` To meet the Bounty campaign rules, it should include ${config.twitter_retweet_w_comment[index].tag_list} tags.`; } - msgSendBack += ` Do it and try again.`; + msgSendBack += ` Quote once again.`; break; default: break; @@ -106,4 +123,4 @@ module.exports = async () => { if (config.twitter_retweet_w_comment.length > 0) setInterval(() => { module.exports(); - }, 15 * 1000); + }, 11 * 1000); diff --git a/modules/checkTxs.js b/modules/checkTxs.js index 2283890..f94ade0 100644 --- a/modules/checkTxs.js +++ b/modules/checkTxs.js @@ -84,9 +84,7 @@ module.exports = async (itx, tx) => { await user.save(); await itx.update({isProcessed: true}, true); - // console.log('User info:', user); - - msgSendBack = `I've got your account details. Twitter: ${user.twitterAccount}. I'll check if you've finished the Bounty tasks now..`; + msgSendBack = `I've got your account details. Twitter: ${user.twitterAccount}. I'll check if you've finished the Bounty tasks now…`; $u.sendAdmMsg(tx.senderId, msgSendBack); }; diff --git a/modules/configReader.js b/modules/configReader.js index e550c42..edc8b45 100644 --- a/modules/configReader.js +++ b/modules/configReader.js @@ -152,7 +152,7 @@ try { let max_amount = mathjs.evaluate(f, {followers: max_followers}); max_amount = +max_amount.toFixed(config.rewards_progression_from_twitter_followers[t.currency].decimals_show); - return `- from ${min_amount} ${t.currency} to ${max_amount} ${t.currency}`; + return `- from ${min_amount} ${t.currency} (${min_followers} followers) to ${max_amount} ${t.currency} (${max_followers}+ followers)`; } else { return `- ${t.amount} ${t.currency}`; } diff --git a/package.json b/package.json index a4a0294..436328b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "adamant-bountybot", - "version": "1.3.0", + "version": "1.3.1", "description": "ADAMANT Bounty bot — a software that allows you to carry out bounty campaigns & crypto airdrops, with automatic task verifications and payouts.", "main": "index.js", "scripts": { @@ -24,19 +24,19 @@ "author": "Aleksei Lebedev (https://adamant.im)", "license": "GPL-3.0", "dependencies": { - "mathjs": "^7.3.0", "adamant-api": "^0.5.3", "ethereumjs-tx": "^2.1.2", "ethereumjs-util": "^7.0.0", "express": "^4.17.1", "jsonminify": "^0.4.1", "jsonrpc-client": "^0.1.1", + "mathjs": "^7.3.0", "mongodb": "^3.2.6", "node-ethereum-wallet": "^1.3.2", "node-json-rpc": "0.0.1", "request": "^2.88.0", - "web3": "^1.2.7", - "twitter": "^1.7.1" + "twitter": "^1.7.1", + "web3": "^1.2.7" }, "repository": { "type": "git", From e658d89d98d5feff7aceb1a496a4408b7aaf69c2 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Fri, 30 Jul 2021 13:56:33 +0300 Subject: [PATCH 08/55] Update messages --- modules/checkAll.js | 2 +- modules/checkTwitterRetweet.js | 8 +++++--- modules/rewardsPayer.js | 2 +- modules/sentTxValidator.js | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/modules/checkAll.js b/modules/checkAll.js index be0f88f..f282ab7 100644 --- a/modules/checkAll.js +++ b/modules/checkAll.js @@ -72,7 +72,7 @@ module.exports = async () => { twitterString += `)`; notify(`${config.notifyName}: User ${userId}${twitterString} completed the Bounty tasks. Payouts are pending.`, 'log'); } - msgSendBack = `Thank you! The Bounty tasks are completed! I am sending the reward to you.`; + msgSendBack = `Great, you've completed all the tasks! Reward is coming right now!`; $u.sendAdmMsg(userId, msgSendBack); } diff --git a/modules/checkTwitterRetweet.js b/modules/checkTwitterRetweet.js index e8cf417..1f86161 100644 --- a/modules/checkTwitterRetweet.js +++ b/modules/checkTwitterRetweet.js @@ -81,22 +81,24 @@ module.exports = async () => { msgSendBack += ` Use ${config.twitter_retweet_w_comment[index].tag_list} tags.`; } } - msgSendBack += ` Example: Meet the ADAMANT blockchain messenger! @elonmusk @cz_binance @FabriLemus7 #privacy #crypto #anonymity #decentralization`; + msgSendBack += ` Example: _Meet the ADAMANT blockchain messenger! @elonmusk @cz_binance @FabriLemus7 #privacy #crypto #anonymity #decentralization_`; msgSendBack += `. Then you apply again.`; break; case ('not_enough_mentions'): msgSendBack = `I see your quote.`; if (minMentions > 0) { msgSendBack += ` To meet the Bounty campaign rules, it should mention at least ${minMentions} friends.`; + msgSendBack += ` Example: _Meet the ADAMANT blockchain messenger! @elonmusk @cz_binance @FabriLemus7 #privacy #crypto #anonymity #decentralization_`; } - msgSendBack += ` Quote once again.`; + msgSendBack += `. Quote once again.`; break; case ('no_hashtags'): msgSendBack = `I see your quote.`; if (hashtags.length > 0) { msgSendBack += ` To meet the Bounty campaign rules, it should include ${config.twitter_retweet_w_comment[index].tag_list} tags.`; + msgSendBack += ` Example: _Meet the ADAMANT blockchain messenger! @elonmusk @cz_binance @FabriLemus7 #privacy #crypto #anonymity #decentralization_`; } - msgSendBack += ` Quote once again.`; + msgSendBack += `. Quote once again.`; break; default: break; diff --git a/modules/rewardsPayer.js b/modules/rewardsPayer.js index d5117e7..54b4cbc 100644 --- a/modules/rewardsPayer.js +++ b/modules/rewardsPayer.js @@ -49,7 +49,7 @@ module.exports = async () => { const result = await $u[outCurrency].send({ address: outAddress, value: outAmount, - comment: 'Thank you for support! Was it great? Share the experience with your friends!' // if ADM + comment: 'Was it great? Share the experience with your friends!' // if ADM }); log.info(`Payout result: ${JSON.stringify(result, 0, 2)}`); diff --git a/modules/sentTxValidator.js b/modules/sentTxValidator.js index f212da8..bebbe44 100644 --- a/modules/sentTxValidator.js +++ b/modules/sentTxValidator.js @@ -88,7 +88,7 @@ module.exports = async () => { notifyType = 'info'; if (config.notifyRewardReceived) msgNotify = `${config.notifyName} successfully payed the reward of _${outAmount} ${outCurrency}_ to ${userId} with Tx hash _${outTxid}_.`; - msgSendBack = 'Thank you for support! Was it great? Share the experience with your friends!'; + msgSendBack = 'Was it great? Share the experience with your friends!'; if (outCurrency !== 'ADM') { msgSendBack = `{"type":"${outCurrency}_transaction","amount":"${outAmount}","hash":"${outTxid}","comments":"${msgSendBack}"}`; From cf29e2c8f1d291253992f07ee92a9f5a3e44d4be Mon Sep 17 00:00:00 2001 From: adamant-al Date: Fri, 30 Jul 2021 14:03:13 +0300 Subject: [PATCH 09/55] Update notifier --- helpers/notify.js | 124 ++++++++++++++++++++++++++++------------------ package.json | 3 +- 2 files changed, 77 insertions(+), 50 deletions(-) diff --git a/helpers/notify.js b/helpers/notify.js index 976b01b..d5b7fb3 100644 --- a/helpers/notify.js +++ b/helpers/notify.js @@ -1,57 +1,83 @@ -const request = require('request'); +const axios = require('axios'); const config = require('../modules/configReader'); const log = require('./log'); const api = require('../modules/api'); const { - adamant_notify, - slack + adamant_notify, + slack, } = config; +module.exports = (message, type, silent_mode = false) => { + + try { + + log[type](removeMarkdown(message)); + + if (!silent_mode) { + + if (!slack && !adamant_notify) { + return; + } + let color; + switch (type) { + case ('error'): + color = '#FF0000'; + break; + case ('warn'): + color = '#FFFF00'; + break; + case ('info'): + color = '#00FF00'; + break; + case ('log'): + color = '#FFFFFF'; + break; + } + + const params = { + 'attachments': [{ + 'fallback': message, + 'color': color, + 'text': makeBoldForSlack(message), + 'mrkdwn_in': ['text'], + }], + }; + + if (slack && slack.length > 34) { + axios.post(slack, params) + .catch(function(error) { + log.log(`Request to Slack with message ${message} failed. ${error}.`); + }); + } + if (adamant_notify && adamant_notify.length > 5 && adamant_notify.startsWith('U') && config.passPhrase && config.passPhrase.length > 30) { + const mdMessage = makeBoldForMarkdown(message); + api.send(config.passPhrase, adamant_notify, `${type}| ${mdMessage}`, 'message'); + } + + } + + } catch (e) { + log.error('Notifier error: ' + e); + } -module.exports = (message, type) => { - try { - log[type](message.replace(/\*/g, '').replace(/_/g, '')); - - if (!slack && !adamant_notify) { - return; - } - let color; - switch (type) { - case ('error'): - color = '#FF0000'; - break; - - case ('warn'): - color = '#FFFF00'; - break; - - case ('info'): - color = '#00FF00'; - break; - case ('log'): - color = '#ffffff'; - break; - } - const opts = { - uri: slack, - method: 'POST', - json: true, - body: { - 'attachments': [{ - 'fallback': message, - 'color': color, - 'text': message, - 'mrkdwn_in': ['text'] - }] - } - }; - if (slack && slack.length > 34) { - request(opts); - } - if (adamant_notify && adamant_notify.length > 5 && adamant_notify.startsWith('U') && config.passPhrase && config.passPhrase.length > 30) { - api.send(config.passPhrase, adamant_notify, `${type}| ${message.replace(/\*/g, '**')}`, 'message'); - } - } catch (e) { - log.error('Notifier error: ' + e); - } }; + +function removeMarkdown(text) { + return doubleAsterisksToSingle(text).replace(/([_*]\b|\b[_*])/g, ''); +} + +function doubleAsterisksToSingle(text) { + return text.replace(/(\*\*\b|\b\*\*)/g, '*'); +} + +function singleAsteriskToDouble(text) { + return text.replace(/(\*\b|\b\*)/g, '**'); +} + +function makeBoldForMarkdown(text) { + return singleAsteriskToDouble(doubleAsterisksToSingle(text)); +} + +function makeBoldForSlack(text) { + return doubleAsterisksToSingle(text); +} diff --git a/package.json b/package.json index 436328b..1c26a46 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "adamant-bountybot", - "version": "1.3.1", + "version": "1.3.2", "description": "ADAMANT Bounty bot — a software that allows you to carry out bounty campaigns & crypto airdrops, with automatic task verifications and payouts.", "main": "index.js", "scripts": { @@ -25,6 +25,7 @@ "license": "GPL-3.0", "dependencies": { "adamant-api": "^0.5.3", + "axios": "0.21.1", "ethereumjs-tx": "^2.1.2", "ethereumjs-util": "^7.0.0", "express": "^4.17.1", From bc5e0f93833d313ab8128124c80a08bab4ec8b22 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Fri, 30 Jul 2021 14:10:09 +0300 Subject: [PATCH 10/55] Update logger --- config.json | 7 +++- helpers/log.js | 79 ++++++++++++++++++----------------------- helpers/utils/index.js | 20 +++++++++++ modules/configReader.js | 5 ++- 4 files changed, 63 insertions(+), 48 deletions(-) diff --git a/config.json b/config.json index f60cd18..fbafe48 100644 --- a/config.json +++ b/config.json @@ -160,6 +160,11 @@ Do not set for live exchange bots, use only for debugging. Allows to get DBs records like http://ip:port/db?tb=incomingTxsDb **/ - "api": false + "api": false, + + /** The software will use verbosity according to log_level. + It can be none < error < warn < info < log. + **/ + "log_level": "log" } diff --git a/helpers/log.js b/helpers/log.js index 36a53c8..b164fcc 100644 --- a/helpers/log.js +++ b/helpers/log.js @@ -1,61 +1,52 @@ -let fs = require('fs'); +const config = require('../modules/configReader'); +const utils = require('./utils'); + +const fs = require('fs'); if (!fs.existsSync('./logs')) { - fs.mkdirSync('./logs'); + fs.mkdirSync('./logs'); } -let infoStr = fs.createWriteStream('./logs/' + date() + '.log', { - flags: 'a' +const infoStr = fs.createWriteStream('./logs/' + date() + '.log', { + flags: 'a', }); -infoStr.write(` -_________________${fullTime()}_________________ -`); +infoStr.write(`\n\n[The bot started] _________________${fullTime()}_________________\n`); module.exports = { - error(str) { - infoStr.write(` - ` + 'Bot error|' + time() + '|' + str); - console.log('\x1b[31m', 'error|' + time(), '\x1b[0m', str); - }, - info(str) { - console.log('\x1b[32m', 'info|' + time(), '\x1b[0m', str); - - infoStr.write(` - ` + 'Bot info|' + time() + '|' + str); - }, - warn(str) { - console.log('\x1b[33m', 'warn|' + time(), '\x1b[0m', str); - - infoStr.write(` - ` + 'Bot warn|' + time() + '|' + str); - }, - log(str) { - console.log('\x1b[34m', 'log|' + time(), '\x1b[0m', str); - - infoStr.write(` - ` + 'Bot log|[' + time() + '|' + str); - } + error(str) { + if (['error', 'warn', 'info', 'log'].includes(config.log_level)) { + infoStr.write(`\n ` + 'error|' + fullTime() + '|' + str); + console.log('\x1b[31m', 'error|' + fullTime(), '\x1b[0m', str); + } + }, + warn(str) { + if (['warn', 'info', 'log'].includes(config.log_level)) { + console.log('\x1b[33m', 'warn|' + fullTime(), '\x1b[0m', str); + infoStr.write(`\n ` + 'warn|' + fullTime() + '|' + str); + } + }, + info(str) { + if (['info', 'log'].includes(config.log_level)) { + console.log('\x1b[32m', 'info|' + fullTime(), '\x1b[0m', str); + infoStr.write(`\n ` + 'info|' + fullTime() + '|' + str); + } + }, + log(str) { + if (['log'].includes(config.log_level)) { + console.log('\x1b[34m', 'log|' + fullTime(), '\x1b[0m', str); + infoStr.write(`\n ` + 'log|[' + fullTime() + '|' + str); + } + }, }; function time() { - var options = { - hour: 'numeric', - minute: 'numeric', - second: 'numeric' - }; - - return new Date().toLocaleString('en', options); + return utils.formatDate(Date.now()).hh_mm_ss; } function date() { - var options = { - day: 'numeric', - month: 'numeric', - year: 'numeric' - }; - return (new Date().toLocaleString('en', options)).replace(/\//g, '-'); + return utils.formatDate(Date.now()).YYYY_MM_DD; } function fullTime() { - return date() + ' ' + time(); + return date() + ' ' + time(); } diff --git a/helpers/utils/index.js b/helpers/utils/index.js index 3b1d04d..f5960e2 100644 --- a/helpers/utils/index.js +++ b/helpers/utils/index.js @@ -212,6 +212,26 @@ module.exports = { else return id.substring(n + 1); }, + /** + * Formats unix timestamp to string + * @param {number} timestamp Timestamp to format + * @return {object} Contains different formatted strings + */ + formatDate(timestamp) { + if (!timestamp) return false; + const formattedDate = {}; + const dateObject = new Date(timestamp); + formattedDate.year = dateObject.getFullYear(); + formattedDate.month = ('0' + (dateObject.getMonth() + 1)).slice(-2); + formattedDate.date = ('0' + dateObject.getDate()).slice(-2); + formattedDate.hours = ('0' + dateObject.getHours()).slice(-2); + formattedDate.minutes = ('0' + dateObject.getMinutes()).slice(-2); + formattedDate.seconds = ('0' + dateObject.getSeconds()).slice(-2); + formattedDate.YYYY_MM_DD = formattedDate.year + '-' + formattedDate.month + '-' + formattedDate.date; + formattedDate.YYYY_MM_DD_hh_mm = formattedDate.year + '-' + formattedDate.month + '-' + formattedDate.date + ' ' + formattedDate.hours + ':' + formattedDate.minutes; + formattedDate.hh_mm_ss = formattedDate.hours + ':' + formattedDate.minutes + ':' + formattedDate.seconds; + return formattedDate; + }, ETH: eth_utils, ADM: adm_utils, }; diff --git a/modules/configReader.js b/modules/configReader.js index edc8b45..f06cd38 100644 --- a/modules/configReader.js +++ b/modules/configReader.js @@ -1,6 +1,5 @@ const jsonminify = require('jsonminify'); const fs = require('fs'); -const log = require('../helpers/log'); const keys = require('adamant-api/helpers/keys'); const isDev = process.argv.includes('dev'); const mathjs = require('mathjs'); @@ -178,11 +177,11 @@ try { }); } catch (e) { - log.error('Error reading config: ' + e); + console.error('Error reading config: ' + e); } function exit(msg) { - log.error(msg); + console.error(msg); process.exit(-1); } From 9f13f4de7de8bd558b08cd7b69383f2895ccdd52 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Fri, 30 Jul 2021 14:11:11 +0300 Subject: [PATCH 11/55] Fix confirReader --- modules/configReader.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/configReader.js b/modules/configReader.js index f06cd38..5322df9 100644 --- a/modules/configReader.js +++ b/modules/configReader.js @@ -168,7 +168,7 @@ try { Object.keys(fields).forEach(f => { if (!config[f] && fields[f].isRequired) { exit(`Bot's ${address} config is wrong. Field _${f}_ is not valid. Cannot start Bot.`); - } else if (!config[f] && config[f] != 0 && fields[f].default) { + } else if (!config[f] && config[f] !== 0 && fields[f].default) { config[f] = fields[f].default; } if (config[f] && fields[f].type !== config[f].__proto__.constructor) { diff --git a/package.json b/package.json index 1c26a46..63f34d4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "adamant-bountybot", - "version": "1.3.2", + "version": "1.3.3", "description": "ADAMANT Bounty bot — a software that allows you to carry out bounty campaigns & crypto airdrops, with automatic task verifications and payouts.", "main": "index.js", "scripts": { From cda819bc7b398357eff09758da439c1c0949a69f Mon Sep 17 00:00:00 2001 From: adamant-al Date: Fri, 30 Jul 2021 14:27:23 +0300 Subject: [PATCH 12/55] Add try-catch in rewardsPayer --- modules/rewardsPayer.js | 128 +++++++++++++++++++------------------ modules/sentTxValidator.js | 2 +- 2 files changed, 68 insertions(+), 62 deletions(-) diff --git a/modules/rewardsPayer.js b/modules/rewardsPayer.js index 54b4cbc..1e8371b 100644 --- a/modules/rewardsPayer.js +++ b/modules/rewardsPayer.js @@ -15,78 +15,84 @@ module.exports = async () => { outTxid: null, outAddress: {$ne: null} })).forEach(async pay => { - pay.trySendCounter = pay.trySendCounter || 0; - const { - userId, - outAmount, - outCurrency, - outAddress - } = pay; + try { + + pay.trySendCounter = pay.trySendCounter || 0; + const { + userId, + outAmount, + outCurrency, + outAddress + } = pay; - let etherString = ''; - let isNotEnoughBalance; - - if ($u.isERC20(outCurrency)) { - etherString = `Ether balance: ${Store.user['ETH'].balance}. `; - isNotEnoughBalance = (outAmount > Store.user[outCurrency].balance) || ($u[outCurrency].FEE > Store.user['ETH'].balance); - } else { - etherString = ''; - isNotEnoughBalance = outAmount + $u[outCurrency].FEE > Store.user[outCurrency].balance; - } + let etherString = ''; + let isNotEnoughBalance; + + if ($u.isERC20(outCurrency)) { + etherString = `Ether balance: ${Store.user['ETH'].balance}. `; + isNotEnoughBalance = (outAmount > Store.user[outCurrency].balance) || ($u[outCurrency].FEE > Store.user['ETH'].balance); + } else { + etherString = ''; + isNotEnoughBalance = outAmount + $u[outCurrency].FEE > Store.user[outCurrency].balance; + } - if (isNotEnoughBalance) { - pay.update({ - error: 15, - isFinished: true, - isPayed: false - }, true); - notify(`${config.notifyName} notifies about insufficient balance to send a reward of _${outAmount}_ _${outCurrency}_. Balance of _${outCurrency}_ is _${Store.user[outCurrency].balance}_. ${etherString}User ADAMANT id: ${userId}.`, 'error'); - $u.sendAdmMsg(userId, `I can’t transfer a reward of _${outAmount}_ _${outCurrency}_ to you because of insufficient funds (I count blockchain fees also). I have already notified my master.`); - return; - } + if (isNotEnoughBalance) { + pay.update({ + error: 15, + isFinished: true, + isPayed: false + }, true); + notify(`${config.notifyName} notifies about insufficient balance to send a reward of _${outAmount}_ _${outCurrency}_. Balance of _${outCurrency}_ is _${Store.user[outCurrency].balance}_. ${etherString}User ADAMANT id: ${userId}.`, 'error'); + $u.sendAdmMsg(userId, `I can’t transfer a reward of _${outAmount}_ _${outCurrency}_ to you because of insufficient funds (I count blockchain fees also). I have already notified my master.`); + return; + } - log.info(`Attempt number ${pay.trySendCounter} to send the reward payout. Coin: ${outCurrency}, address: ${outAddress}, value: ${outAmount}, balance: ${Store.user[outCurrency].balance}`); - const result = await $u[outCurrency].send({ - address: outAddress, - value: outAmount, - comment: 'Was it great? Share the experience with your friends!' // if ADM - }); - log.info(`Payout result: ${JSON.stringify(result, 0, 2)}`); + log.info(`Attempt number ${pay.trySendCounter} to send the reward payout. Coin: ${outCurrency}, address: ${outAddress}, value: ${outAmount}, balance: ${Store.user[outCurrency].balance}`); + const result = await $u[outCurrency].send({ + address: outAddress, + value: outAmount, + comment: 'Was it great? Share the experience with your friends!' // if ADM + }); + log.info(`Payout result: ${JSON.stringify(result, 0, 2)}`); - if (result.success) { + if (result.success) { - pay.update({ - outTxid: result.hash, - isPayed: true - }, true); + pay.update({ + outTxid: result.hash, + isPayed: true + }, true); - // Update local balances without unnecessary requests - if ($u.isERC20(outCurrency)) { - Store.user[outCurrency].balance -= outAmount; - Store.user['ETH'].balance -= $u[outCurrency].FEE; - } else { - Store.user[outCurrency].balance -= (outAmount + $u[outCurrency].FEE); - } - log.info(`Successful payout of ${outAmount} ${outCurrency} to ${userId}. Hash: ${result.hash}.`); + // Update local balances without unnecessary requests + if ($u.isERC20(outCurrency)) { + Store.user[outCurrency].balance -= outAmount; + Store.user['ETH'].balance -= $u[outCurrency].FEE; + } else { + Store.user[outCurrency].balance -= (outAmount + $u[outCurrency].FEE); + } + log.info(`Successful payout of ${outAmount} ${outCurrency} to ${userId}. Hash: ${result.hash}.`); - } else { // Can't make a transaction + } else { // Can't make a transaction - if (pay.trySendCounter++ < 50) { // If number of attempts less then 50, just ignore and try again on next tick - await pay.save(); - return; - }; + if (pay.trySendCounter++ < 50) { // If number of attempts less then 50, just ignore and try again on next tick + await pay.save(); + return; + }; + + pay.update({ + error: 16, + isFinished: true, + isPayed: false + }, true); + notify(`${config.notifyName} cannot make transaction to payout a reward of _${outAmount}_ _${outCurrency}_. Balance of _${outCurrency}_ is _${Store.user[outCurrency].balance}_. ${etherString}User ADAMANT id: ${userId}.`, 'error'); + $u.sendAdmMsg(userId, `I’ve tried to make a reward payout of _${outAmount}_ _${outCurrency}_ to you, but something went wrong. I have already notified my master.`); + } - pay.update({ - error: 16, - isFinished: true, - isPayed: false - }, true); - notify(`${config.notifyName} cannot make transaction to payout a reward of _${outAmount}_ _${outCurrency}_. Balance of _${outCurrency}_ is _${Store.user[outCurrency].balance}_. ${etherString}User ADAMANT id: ${userId}.`, 'error'); - $u.sendAdmMsg(userId, `I’ve tried to make a reward payout of _${outAmount}_ _${outCurrency}_ to you, but something went wrong. I have already notified my master.`); + } catch (e) { + log.error(`Error in ${$u.getModuleName(module.id)} module: ${e.toString()}`); } - }); + }); }; setInterval(() => { module.exports(); -}, 10 * 1000); +}, 15 * 1000); diff --git a/modules/sentTxValidator.js b/modules/sentTxValidator.js index bebbe44..ef51cda 100644 --- a/modules/sentTxValidator.js +++ b/modules/sentTxValidator.js @@ -112,4 +112,4 @@ module.exports = async () => { setInterval(() => { module.exports(); -}, 15 * 1000); +}, 8 * 1000); From 11d2f1763b0ad42b8cb041d19c40d28d99366a41 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Fri, 30 Jul 2021 14:34:03 +0300 Subject: [PATCH 13/55] Fix link to log module --- helpers/utils/adm_utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/utils/adm_utils.js b/helpers/utils/adm_utils.js index c08d6db..d5fea5b 100644 --- a/helpers/utils/adm_utils.js +++ b/helpers/utils/adm_utils.js @@ -1,6 +1,6 @@ const Store = require('../../modules/Store'); const api = require('../../modules/api'); -const log = require('../../helpers/log'); +const log = require('../log'); const {SAT} = require('../const'); const User = Store.user.ADM; From 20f509f0f2843525fc9decd821112ac78dacaacd Mon Sep 17 00:00:00 2001 From: adamant-al Date: Fri, 30 Jul 2021 14:36:09 +0300 Subject: [PATCH 14/55] Fix log typo --- helpers/utils/adm_utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/utils/adm_utils.js b/helpers/utils/adm_utils.js index d5fea5b..eba816a 100644 --- a/helpers/utils/adm_utils.js +++ b/helpers/utils/adm_utils.js @@ -38,7 +38,7 @@ module.exports = { async send(params) { try { const {address, value, comment} = params; - log.info(`Send ${value} ADM: `, comment); + log.info(`Send ${value} ADM with comment: ${comment}`); let res; if (comment){ res = api.send(User.passPhrase, address, comment, 'message', null, value); From 500f5b9c595a5085a82599eabfb1fa9aab02a589 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Fri, 30 Jul 2021 14:49:23 +0300 Subject: [PATCH 15/55] Fixing circular dependency --- helpers/log.js | 26 +++++++++++++++++++++++--- helpers/utils/adm_utils.js | 2 +- helpers/utils/index.js | 20 -------------------- package.json | 2 +- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/helpers/log.js b/helpers/log.js index b164fcc..0864db6 100644 --- a/helpers/log.js +++ b/helpers/log.js @@ -1,5 +1,4 @@ const config = require('../modules/configReader'); -const utils = require('./utils'); const fs = require('fs'); if (!fs.existsSync('./logs')) { @@ -40,13 +39,34 @@ module.exports = { }; function time() { - return utils.formatDate(Date.now()).hh_mm_ss; + return formatDate(Date.now()).hh_mm_ss; } function date() { - return utils.formatDate(Date.now()).YYYY_MM_DD; + return formatDate(Date.now()).YYYY_MM_DD; } function fullTime() { return date() + ' ' + time(); } + +/** + * Formats unix timestamp to string + * @param {number} timestamp Timestamp to format + * @return {object} Contains different formatted strings + */ +function formatDate(timestamp) { + if (!timestamp) return false; + const formattedDate = {}; + const dateObject = new Date(timestamp); + formattedDate.year = dateObject.getFullYear(); + formattedDate.month = ('0' + (dateObject.getMonth() + 1)).slice(-2); + formattedDate.date = ('0' + dateObject.getDate()).slice(-2); + formattedDate.hours = ('0' + dateObject.getHours()).slice(-2); + formattedDate.minutes = ('0' + dateObject.getMinutes()).slice(-2); + formattedDate.seconds = ('0' + dateObject.getSeconds()).slice(-2); + formattedDate.YYYY_MM_DD = formattedDate.year + '-' + formattedDate.month + '-' + formattedDate.date; + formattedDate.YYYY_MM_DD_hh_mm = formattedDate.year + '-' + formattedDate.month + '-' + formattedDate.date + ' ' + formattedDate.hours + ':' + formattedDate.minutes; + formattedDate.hh_mm_ss = formattedDate.hours + ':' + formattedDate.minutes + ':' + formattedDate.seconds; + return formattedDate; +} \ No newline at end of file diff --git a/helpers/utils/adm_utils.js b/helpers/utils/adm_utils.js index eba816a..8ebc07f 100644 --- a/helpers/utils/adm_utils.js +++ b/helpers/utils/adm_utils.js @@ -38,7 +38,7 @@ module.exports = { async send(params) { try { const {address, value, comment} = params; - log.info(`Send ${value} ADM with comment: ${comment}`); + log.log(`Sending ${value} ADM with comment: ${comment}`); let res; if (comment){ res = api.send(User.passPhrase, address, comment, 'message', null, value); diff --git a/helpers/utils/index.js b/helpers/utils/index.js index f5960e2..3b1d04d 100644 --- a/helpers/utils/index.js +++ b/helpers/utils/index.js @@ -212,26 +212,6 @@ module.exports = { else return id.substring(n + 1); }, - /** - * Formats unix timestamp to string - * @param {number} timestamp Timestamp to format - * @return {object} Contains different formatted strings - */ - formatDate(timestamp) { - if (!timestamp) return false; - const formattedDate = {}; - const dateObject = new Date(timestamp); - formattedDate.year = dateObject.getFullYear(); - formattedDate.month = ('0' + (dateObject.getMonth() + 1)).slice(-2); - formattedDate.date = ('0' + dateObject.getDate()).slice(-2); - formattedDate.hours = ('0' + dateObject.getHours()).slice(-2); - formattedDate.minutes = ('0' + dateObject.getMinutes()).slice(-2); - formattedDate.seconds = ('0' + dateObject.getSeconds()).slice(-2); - formattedDate.YYYY_MM_DD = formattedDate.year + '-' + formattedDate.month + '-' + formattedDate.date; - formattedDate.YYYY_MM_DD_hh_mm = formattedDate.year + '-' + formattedDate.month + '-' + formattedDate.date + ' ' + formattedDate.hours + ':' + formattedDate.minutes; - formattedDate.hh_mm_ss = formattedDate.hours + ':' + formattedDate.minutes + ':' + formattedDate.seconds; - return formattedDate; - }, ETH: eth_utils, ADM: adm_utils, }; diff --git a/package.json b/package.json index 63f34d4..4bc1ed6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "adamant-bountybot", - "version": "1.3.3", + "version": "1.3.4", "description": "ADAMANT Bounty bot — a software that allows you to carry out bounty campaigns & crypto airdrops, with automatic task verifications and payouts.", "main": "index.js", "scripts": { From 3c452c6f9f48b840b69c563d2cc9f14d814c88ae Mon Sep 17 00:00:00 2001 From: adamant-al Date: Thu, 5 Aug 2021 15:18:07 +0300 Subject: [PATCH 16/55] More of logging --- config.json | 2 +- modules/checkTxs.js | 3 ++- modules/commandTxs.js | 2 +- modules/incomingTxsParser.js | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/config.json b/config.json index fbafe48..89fd137 100644 --- a/config.json +++ b/config.json @@ -51,7 +51,7 @@ ${config.twitter_retweet_w_comment[0].min_mentions}, ${config.twitter_retweet_w_comment[0].tag_list}, ${config.twitterEligibleString} **/ - "help_message": "Earn **${config.rewards_tickers}** with your social activity! A reward depends on how much Twitter followers you have. Your account ${config.twitterEligibleString}.\n${config.rewards_range}\n\n \nThe campaign rules:\n- Follow account ${config.twitter_follow_list} on Twitter\n- Like & quote ${config.twitter_retweet_w_comment[0].tweet}, mentioning ${config.twitter_retweet_w_comment[0].min_mentions} friends and ${config.twitter_retweet_w_comment[0].tag_list} tags.\n- Invite ${config.adamant_campaign.min_contacts} friend in ADAMANT Messenger. They must write you.\n- Send me the name (like @adamant_im) or the link to your Twitter profile to verify.\n\n \nGo!", + "help_message": "Earn **${config.rewards_tickers}** with your social activity! A reward depends on how much Twitter followers you have. Your account ${config.twitterEligibleString}.\n${config.rewards_range}\n\n \nThe campaign rules:\n- Follow account ${config.twitter_follow_list} on Twitter\n- Like & quote ${config.twitter_retweet_w_comment[0].tweet}, mentioning ${config.twitter_retweet_w_comment[0].min_mentions} friends and ${config.twitter_retweet_w_comment[0].tag_list} tags.\n- Invite ${config.adamant_campaign.min_contacts} friend in ADAMANT Messenger. They must message you.\n- Send me the name (like @adamant_im) or the link to your Twitter profile to verify.\n\n \nGo!", /** Bot's name for notifications **/ "bot_name": "Lovely Bounty Bot", diff --git a/modules/checkTxs.js b/modules/checkTxs.js index f94ade0..93db7f7 100644 --- a/modules/checkTxs.js +++ b/modules/checkTxs.js @@ -32,7 +32,7 @@ module.exports = async (itx, tx) => { user = await usersDb.findOne({userId: tx.senderId}); if (user) { // User is already was in check earlier, update - log.info(`User ${user.userId} applied once again.`); + log.info(`User ${user.userId} applied once again with Twitter account ${itx.accounts.twitterAccount}.`); // May be later // if (user.isBountyPayed) { // msgSendBack = `You've already received the Bounty reward. Thanks for your support!`; @@ -62,6 +62,7 @@ module.exports = async (itx, tx) => { } else { // First time user, create new + log.info(`User ${tx.senderId} applied for a first time with Twitter account ${itx.accounts.twitterAccount}.`); user = new usersDb({ _id: tx.senderId, userId: tx.senderId, diff --git a/modules/commandTxs.js b/modules/commandTxs.js index 20b58ab..8c8fbe0 100644 --- a/modules/commandTxs.js +++ b/modules/commandTxs.js @@ -8,7 +8,7 @@ const twitterapi = require('./twitterapi'); module.exports = async (cmd, tx, itx) => { if (itx.isProcessed) return; - log.info('Got new command Tx to process: ' + cmd); + log.info(`Got new command Tx to process: ${cmd} from ${tx.senderId}`); try { let res = []; const group = cmd diff --git a/modules/incomingTxsParser.js b/modules/incomingTxsParser.js index e33a831..030634d 100644 --- a/modules/incomingTxsParser.js +++ b/modules/incomingTxsParser.js @@ -27,7 +27,7 @@ module.exports = async (tx) => { return; }; - log.info(`New incoming transaction: ${tx.id}`); + log.info(`New incoming transaction: ${tx.id} from ${tx.senderId}`); let msg = ''; const chat = tx.asset.chat; From 1732fc966c9f3e91a806a4ee630f1f6f4b6d3d2f Mon Sep 17 00:00:00 2001 From: adamant-al Date: Thu, 5 Aug 2021 16:32:49 +0300 Subject: [PATCH 17/55] Optimize checkAdamantContacts --- modules/checkAdamantContacts.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/modules/checkAdamantContacts.js b/modules/checkAdamantContacts.js index 1aad162..4a3f5fc 100644 --- a/modules/checkAdamantContacts.js +++ b/modules/checkAdamantContacts.js @@ -50,11 +50,11 @@ module.exports = async () => { let txChat_all = (await api.get('uri', 'chats/get/?recipientId=' + userId + '&orderBy=timestamp:desc&limit=100')).transactions; let txChat = txChat_all.filter((v,i,a)=>a.findIndex(t=>(t.senderId === v.senderId))===i); - // console.log(txChat.length); let contact_firstchat; let hours_old; let need_new_contacts = false; + for (let index = 0; ((index < txChat.length) && (contacts_number < config.adamant_campaign.min_contacts)); index++) { if (excluded_adm_contacts.includes(txChat[index].senderId)) { continue; @@ -62,7 +62,6 @@ module.exports = async () => { contact_firstchat = (await api.get('uri', 'chats/get/?senderId=' + txChat[index].senderId + '&orderBy=timestamp:asc&limit=1')); contact_firstchat = contact_firstchat.transactions[0].timestamp; hours_old = ($u.unix() - $u.toTimestamp(contact_firstchat)) / 1000 / 60 / 60; - // console.log(hours_old); if (hours_old > hours_for_new_contact) { need_new_contacts = true; continue; @@ -74,7 +73,11 @@ module.exports = async () => { console.log('isContactsDone:', isContactsDone, contacts_number); if (isContactsDone) { + console.log(`User ${userId}.. did make ${config.adamant_campaign.min_contacts} contacts.`); + await user.update({ + isAdamantCheckPassed: true + }, true); } else { @@ -85,18 +88,14 @@ module.exports = async () => { }, true); if (need_new_contacts) { - msgSendBack = `To meet the Bounty campaign rules, you should invite ${config.adamant_campaign.min_contacts} friends in ADAMANT Messenger. They _must be new ADAMANT users_ and reply to you. They also can join this bounty campaign! Invite friends and apply again.`; + msgSendBack = `To meet the Bounty campaign rules, you should invite ${config.adamant_campaign.min_contacts} friends in ADAMANT Messenger. They _should be new ADAMANT users_ and message you. They can join this bounty campaign as well! Invite friends and apply again.`; } else { - msgSendBack = `To meet the Bounty campaign rules, you should invite ${config.adamant_campaign.min_contacts} friends in ADAMANT Messenger. They must reply to you. They also can join this bounty campaign! Invite friends and apply again.`; + msgSendBack = `To meet the Bounty campaign rules, you should invite ${config.adamant_campaign.min_contacts} friends in ADAMANT Messenger. They must message you. They can join this bounty campaign as well! Invite friends and apply again.`; } await $u.sendAdmMsg(userId, msgSendBack); log.info(`User ${userId}.. did NOT make ${config.adamant_campaign.min_contacts} contacts. Message to user: ${msgSendBack}`); } - - await user.update({ - isAdamantCheckPassed: isContactsDone - }, true); } catch (e) { log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); From cb1f1dcb94619204ff89d8a295b6c714905879d8 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Thu, 5 Aug 2021 17:17:29 +0300 Subject: [PATCH 18/55] Throttling --- app.js | 2 +- modules/checkAdamantContacts.js | 147 +++++++++++---------- modules/checkAll.js | 153 ++++++++++++---------- modules/checkTwitterFollow.js | 113 +++++++++------- modules/checkTwitterReqs.js | 155 ++++++++++++---------- modules/checkTwitterRetweet.js | 225 +++++++++++++++++--------------- modules/checkTxs.js | 2 +- modules/twitterapi.js | 14 +- 8 files changed, 440 insertions(+), 371 deletions(-) diff --git a/app.js b/app.js index 35c7121..ea4b866 100644 --- a/app.js +++ b/app.js @@ -27,7 +27,7 @@ function init() { try { if (doClearDB) { - console.log('Clearing database..'); + console.log('Clearing database…'); db.systemDb.db.drop(); db.incomingTxsDb.db.drop(); db.usersDb.db.drop(); diff --git a/modules/checkAdamantContacts.js b/modules/checkAdamantContacts.js index 4a3f5fc..969b8ad 100644 --- a/modules/checkAdamantContacts.js +++ b/modules/checkAdamantContacts.js @@ -23,89 +23,102 @@ const excluded_adm_contacts = [ Store.user.ADM.address // This bot address ] -module.exports = async () => { +let inProcess = false; - const {usersDb} = db; - - (await usersDb.find({ - $and: [ - {isInCheck: true}, - {isAdamantCheckPassed: false}, - {isTasksCompleted: false} - ] - })).forEach(async user => { - try { - const { - userId - } = user; - - console.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}..`); - - let msgNotify = ''; - let msgNotifyType = ''; - let msgSendBack = ''; - - let isContactsDone; - let contacts_number = 0; +module.exports = async () => { - let txChat_all = (await api.get('uri', 'chats/get/?recipientId=' + userId + '&orderBy=timestamp:desc&limit=100')).transactions; - let txChat = txChat_all.filter((v,i,a)=>a.findIndex(t=>(t.senderId === v.senderId))===i); + if (inProcess) return; + inProcess = true; + + try { - let contact_firstchat; - let hours_old; - let need_new_contacts = false; + const {usersDb} = db; - for (let index = 0; ((index < txChat.length) && (contacts_number < config.adamant_campaign.min_contacts)); index++) { - if (excluded_adm_contacts.includes(txChat[index].senderId)) { - continue; - } - contact_firstchat = (await api.get('uri', 'chats/get/?senderId=' + txChat[index].senderId + '&orderBy=timestamp:asc&limit=1')); - contact_firstchat = contact_firstchat.transactions[0].timestamp; - hours_old = ($u.unix() - $u.toTimestamp(contact_firstchat)) / 1000 / 60 / 60; - if (hours_old > hours_for_new_contact) { - need_new_contacts = true; - continue; - } - contacts_number += 1; - } + const users = await usersDb.find({ + $and: [ + {isInCheck: true}, + {isAdamantCheckPassed: false}, + {isTasksCompleted: false} + ] + }); + + for (const user of users) { + try { + const { + userId + } = user; - isContactsDone = contacts_number >= config.adamant_campaign.min_contacts; - console.log('isContactsDone:', isContactsDone, contacts_number); + console.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); - if (isContactsDone) { + let msgNotify = ''; + let msgNotifyType = ''; + let msgSendBack = ''; + + let isContactsDone; + let contacts_number = 0; + + let txChat_all = (await api.get('uri', 'chats/get/?recipientId=' + userId + '&orderBy=timestamp:desc&limit=100')).transactions; + let txChat = txChat_all.filter((v,i,a)=>a.findIndex(t=>(t.senderId === v.senderId))===i); + + let contact_firstchat; + let hours_old; + let need_new_contacts = false; + + for (let index = 0; ((index < txChat.length) && (contacts_number < config.adamant_campaign.min_contacts)); index++) { + if (excluded_adm_contacts.includes(txChat[index].senderId)) { + continue; + } + contact_firstchat = (await api.get('uri', 'chats/get/?senderId=' + txChat[index].senderId + '&orderBy=timestamp:asc&limit=1')); + contact_firstchat = contact_firstchat.transactions[0].timestamp; + hours_old = ($u.unix() - $u.toTimestamp(contact_firstchat)) / 1000 / 60 / 60; + if (hours_old > hours_for_new_contact) { + need_new_contacts = true; + continue; + } + contacts_number += 1; + } - console.log(`User ${userId}.. did make ${config.adamant_campaign.min_contacts} contacts.`); - await user.update({ - isAdamantCheckPassed: true - }, true); + isContactsDone = contacts_number >= config.adamant_campaign.min_contacts; + console.log('isContactsDone:', isContactsDone, contacts_number); - } else { + if (isContactsDone) { - await user.update({ - isAdamantCheckPassed: false, - isInCheck: false, - isTasksCompleted: false - }, true); + console.log(`User ${userId}… did make ${config.adamant_campaign.min_contacts} contacts.`); + await user.update({ + isAdamantCheckPassed: true + }, true); - if (need_new_contacts) { - msgSendBack = `To meet the Bounty campaign rules, you should invite ${config.adamant_campaign.min_contacts} friends in ADAMANT Messenger. They _should be new ADAMANT users_ and message you. They can join this bounty campaign as well! Invite friends and apply again.`; } else { - msgSendBack = `To meet the Bounty campaign rules, you should invite ${config.adamant_campaign.min_contacts} friends in ADAMANT Messenger. They must message you. They can join this bounty campaign as well! Invite friends and apply again.`; + + await user.update({ + isAdamantCheckPassed: false, + isInCheck: false, + isTasksCompleted: false + }, true); + + if (need_new_contacts) { + msgSendBack = `To meet the Bounty campaign rules, you should invite ${config.adamant_campaign.min_contacts} friends in ADAMANT Messenger. They _should be new ADAMANT users_ and message you. They can join this bounty campaign as well! Invite friends and apply again.`; + } else { + msgSendBack = `To meet the Bounty campaign rules, you should invite ${config.adamant_campaign.min_contacts} friends in ADAMANT Messenger. They must message you. They can join this bounty campaign as well! Invite friends and apply again.`; + } + + await $u.sendAdmMsg(userId, msgSendBack); + log.info(`User ${userId}… did NOT make ${config.adamant_campaign.min_contacts} contacts. Message to user: ${msgSendBack}`); } - - await $u.sendAdmMsg(userId, msgSendBack); - log.info(`User ${userId}.. did NOT make ${config.adamant_campaign.min_contacts} contacts. Message to user: ${msgSendBack}`); - } - - } catch (e) { - log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); - } - }); + } catch (e) { + log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); + } + + }; + + } finally { + inProcess = false; + } }; if (config.adamant_campaign.min_contacts > 0) setInterval(() => { module.exports(); - }, 8 * 1000); + }, 6 * 1000); diff --git a/modules/checkAll.js b/modules/checkAll.js index f282ab7..a324129 100644 --- a/modules/checkAll.js +++ b/modules/checkAll.js @@ -5,85 +5,98 @@ const log = require('../helpers/log'); const notify = require('../helpers/notify'); const mathjs = require('mathjs'); +let inProcess = false; + module.exports = async () => { - const {usersDb, paymentsDb} = db; + if (inProcess) return; + inProcess = true; - (await usersDb.find({ - isInCheck: true, - isTasksCompleted: false - })).forEach(async user => { - try { - const { - userId, - isTwitterFollowCheckPassed, - isTwitterRetweetCommentCheckPassed, - isAdamantCheckPassed, - twitterAccount, - twitterFollowers, - twitterLifetimeDays - } = user; + try { + + const {usersDb, paymentsDb} = db; - console.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}..`); + const users = await usersDb.find({ + isInCheck: true, + isTasksCompleted: false + }); + + for (const user of users) { + try { + const { + userId, + isTwitterFollowCheckPassed, + isTwitterRetweetCommentCheckPassed, + isAdamantCheckPassed, + twitterAccount, + twitterFollowers, + twitterLifetimeDays + } = user; - let msgSendBack = ''; - - if (((config.twitter_follow.length === 0) || isTwitterFollowCheckPassed) - && ((config.twitter_retweet_w_comment.length === 0) || isTwitterRetweetCommentCheckPassed) - && ((config.adamant_campaign.min_contacts === 0) || isAdamantCheckPassed)) { - await user.update({ - isInCheck: false, - isTasksCompleted: true - }, true); - config.rewards.forEach(reward => { - let amount = reward.amount; - // console.log(config.rewards_progression_from_twitter_followers[reward.currency]); - if (config.rewards_progression_from_twitter_followers[reward.currency] && twitterFollowers) { - let followersCount = twitterFollowers; - if (followersCount > config.rewards_progression_from_twitter_followers[reward.currency].limit_followers) - followersCount = config.rewards_progression_from_twitter_followers[reward.currency].limit_followers; - let f = config.rewards_progression_from_twitter_followers[reward.currency].func; - amount = mathjs.evaluate(f, {followers: followersCount}); - amount = +amount.toFixed(config.rewards_progression_from_twitter_followers[reward.currency].decimals_transfer); - // console.log(amount); - // process.exit(1); + console.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); + + let msgSendBack = ''; + + if (((config.twitter_follow.length === 0) || isTwitterFollowCheckPassed) + && ((config.twitter_retweet_w_comment.length === 0) || isTwitterRetweetCommentCheckPassed) + && ((config.adamant_campaign.min_contacts === 0) || isAdamantCheckPassed)) { + await user.update({ + isInCheck: false, + isTasksCompleted: true + }, true); + config.rewards.forEach(reward => { + let amount = reward.amount; + // console.log(config.rewards_progression_from_twitter_followers[reward.currency]); + if (config.rewards_progression_from_twitter_followers[reward.currency] && twitterFollowers) { + let followersCount = twitterFollowers; + if (followersCount > config.rewards_progression_from_twitter_followers[reward.currency].limit_followers) + followersCount = config.rewards_progression_from_twitter_followers[reward.currency].limit_followers; + let f = config.rewards_progression_from_twitter_followers[reward.currency].func; + amount = mathjs.evaluate(f, {followers: followersCount}); + amount = +amount.toFixed(config.rewards_progression_from_twitter_followers[reward.currency].decimals_transfer); + // console.log(amount); + // process.exit(1); + } + payment = new paymentsDb({ + date: $u.unix(), + userId, + isPayed: false, + isFinished: false, + outCurrency: reward.currency, + outAmount: amount, + outTxid: null, + outAddress: null + }); + payment.save(); + }) + if (config.notifyTasksCompleted) { + let twitterString = ''; + if (twitterAccount) + twitterString += ` (${twitterAccount}`; + if (twitterFollowers) + twitterString += `, followers: ${twitterFollowers}`; + if (twitterLifetimeDays) + twitterString += `, lifetime days: ${Math.round(twitterLifetimeDays)}`; + if (twitterAccount) + twitterString += `)`; + notify(`${config.notifyName}: User ${userId}${twitterString} completed the Bounty tasks. Payouts are pending.`, 'log'); } - payment = new paymentsDb({ - date: $u.unix(), - userId, - isPayed: false, - isFinished: false, - outCurrency: reward.currency, - outAmount: amount, - outTxid: null, - outAddress: null - }); - payment.save(); - }) - if (config.notifyTasksCompleted) { - let twitterString = ''; - if (twitterAccount) - twitterString += ` (${twitterAccount}`; - if (twitterFollowers) - twitterString += `, followers: ${twitterFollowers}`; - if (twitterLifetimeDays) - twitterString += `, lifetime days: ${Math.round(twitterLifetimeDays)}`; - if (twitterAccount) - twitterString += `)`; - notify(`${config.notifyName}: User ${userId}${twitterString} completed the Bounty tasks. Payouts are pending.`, 'log'); + msgSendBack = `Great, you've completed all the tasks! Reward is coming right now!`; + $u.sendAdmMsg(userId, msgSendBack); } - msgSendBack = `Great, you've completed all the tasks! Reward is coming right now!`; - $u.sendAdmMsg(userId, msgSendBack); - } - - } catch (e) { - log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); - } - - }); + + } catch (e) { + log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); + } + + }; + + } finally { + inProcess = false; + } }; setInterval(() => { module.exports(); -}, 10 * 1000); +}, 7 * 1000); diff --git a/modules/checkTwitterFollow.js b/modules/checkTwitterFollow.js index ee46b26..6282fcb 100644 --- a/modules/checkTwitterFollow.js +++ b/modules/checkTwitterFollow.js @@ -5,66 +5,79 @@ const log = require('../helpers/log'); const notify = require('../helpers/notify'); const twitterapi = require('./twitterapi'); +let inProcess = false; + module.exports = async () => { - const {usersDb} = db; + if (inProcess) return; + inProcess = true; - (await usersDb.find({ - $and: [ - {isInCheck: true}, - {isTwitterAccountEligible: true}, - {isTwitterFollowCheckPassed: false}, - {isTasksCompleted: false}, - {$or: [ - {isAdamantCheckPassed: true}, - {$expr: {$eq: [0, config.adamant_campaign.min_contacts]}} - ]} - ] - })).forEach(async user => { - try { - const { - twitterAccount, - userId - } = user; + try { - console.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}..`); + const {usersDb} = db; - let msgNotify = ''; - let msgNotifyType = ''; - let msgSendBack = ''; - - let followAccount; - let isFollowing; - for (let index = 0; index < config.twitter_follow.length; index++) { - followAccount = config.twitter_follow[index]; - isFollowing = await twitterapi.checkIfAccountFollowing(twitterAccount, followAccount); - console.log('isFollowing:', isFollowing); + const users = await usersDb.find({ + $and: [ + {isInCheck: true}, + {isTwitterAccountEligible: true}, + {isTwitterFollowCheckPassed: false}, + {isTasksCompleted: false}, + {$or: [ + {isAdamantCheckPassed: true}, + {$expr: {$eq: [0, config.adamant_campaign.min_contacts]}} + ]} + ] + }); + + for (const user of users) { + try { + const { + twitterAccount, + userId + } = user; + + console.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); + + let msgNotify = ''; + let msgNotifyType = ''; + let msgSendBack = ''; + + let followAccount; + let isFollowing; + for (let index = 0; index < config.twitter_follow.length; index++) { + followAccount = config.twitter_follow[index]; + isFollowing = await twitterapi.checkIfAccountFollowing(twitterAccount, followAccount); + console.log('isFollowing:', isFollowing); - if (isFollowing) { - console.log(`User ${userId}.. ${twitterAccount} do follows ${followAccount}.`); + if (isFollowing) { + console.log(`User ${userId}… ${twitterAccount} do follows ${followAccount}.`); - } else { + } else { - await user.update({ - isTwitterFollowCheckPassed: false, - isInCheck: false, - isTasksCompleted: false - }, true); - msgSendBack = `To meet the Bounty campaign rules, you should follow Twitter account ${followAccount}. Then you apply again.`; - await $u.sendAdmMsg(userId, msgSendBack); - log.log(`User ${userId}.. ${twitterAccount} do NOT follows ${followAccount}. Message to user: ${msgSendBack}`); - break; + await user.update({ + isTwitterFollowCheckPassed: false, + isInCheck: false, + isTasksCompleted: false + }, true); + msgSendBack = `To meet the Bounty campaign rules, you should follow Twitter account ${followAccount}. Then you apply again.`; + await $u.sendAdmMsg(userId, msgSendBack); + log.log(`User ${userId}… ${twitterAccount} do NOT follows ${followAccount}. Message to user: ${msgSendBack}`); + break; + } } - } - await user.update({ - isTwitterFollowCheckPassed: isFollowing - }, true); - - } catch (e) { - log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); - } + await user.update({ + isTwitterFollowCheckPassed: isFollowing + }, true); - }); + } catch (e) { + log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); + } + + }; + + } finally { + inProcess = false; + } }; diff --git a/modules/checkTwitterReqs.js b/modules/checkTwitterReqs.js index 5bf31ba..47711e3 100644 --- a/modules/checkTwitterReqs.js +++ b/modules/checkTwitterReqs.js @@ -5,89 +5,102 @@ const log = require('../helpers/log'); const notify = require('../helpers/notify'); const twitterapi = require('./twitterapi'); +let inProcess = false; + module.exports = async () => { - const {usersDb} = db; - - (await usersDb.find({ - $and: [ - {isInCheck: true}, - {isTwitterAccountEligible: false}, - {isTasksCompleted: false}, - {$or: [ - {isAdamantCheckPassed: true}, - {$expr: {$eq: [0, config.adamant_campaign.min_contacts]}} - ]} - ] - })).forEach(async user => { - try { - const { - twitterAccount, - userId - } = user; - - console.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}..`); - - let msgSendBack = ''; - let result, isEligible = true; - let twitterAccountIdStr = null; - - result = await twitterapi.checkIfAccountEligible(twitterAccount); - // console.log(result); - - if (result.error === 'request_failed') { - return; // If request to Twitter API failed, ignore and check next time - } + if (inProcess) return; + inProcess = true; + + try { + + const {usersDb} = db; + + const users = await usersDb.find({ + $and: [ + {isInCheck: true}, + {isTwitterAccountEligible: false}, + {isTasksCompleted: false}, + {$or: [ + {isAdamantCheckPassed: true}, + {$expr: {$eq: [0, config.adamant_campaign.min_contacts]}} + ]} + ] + }); + + for (const user of users) { + try { + const { + twitterAccount, + userId + } = user; - if (result.error === 'user_not_found') { - msgSendBack = `It seems Twitter account ${twitterAccount} does not exist. Please re-check and try again.` - } else { - // Check if this user already participated in Bounty campaign. - // He may change Twitter's AccountName (screen name) to cheat, but Id will be the same - twitterAccountIdStr = result.accountInfo.id_str; - let userDuplicate = await usersDb.findOne({twitterAccountId: twitterAccountIdStr}); - if (userDuplicate && (userDuplicate.twitterAccount !== twitterAccount) && (userDuplicate.isInCheck || userDuplicate.isTasksCompleted)) { - // This user changed his AccountName (screen name) - isEligible = false; - msgSendBack = `This Twitter account is already in use by other participant with other account name: ${userDuplicate.twitterAccount}. Cheating detected. If it's a mistake, try again in a few minutes.`; + console.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); + + let msgSendBack = ''; + let result, isEligible = true; + let twitterAccountIdStr = null; + + result = await twitterapi.checkIfAccountEligible(twitterAccount); + // console.log(result); + + if (result.error === 'request_failed') { + return; // If request to Twitter API failed, ignore and check next time } - } - if (config.doCheckTwitterReqs) { - isEligible = isEligible && result.success; - } else { - isEligible = isEligible && true; - } + if (result.error === 'user_not_found') { + msgSendBack = `It seems Twitter account ${twitterAccount} does not exist. Please re-check and try again.` + } else { + // Check if this user already participated in Bounty campaign. + // He may change Twitter's AccountName (screen name) to cheat, but Id will be the same + twitterAccountIdStr = result.accountInfo.id_str; + let userDuplicate = await usersDb.findOne({twitterAccountId: twitterAccountIdStr}); + if (userDuplicate && (userDuplicate.twitterAccount !== twitterAccount) && (userDuplicate.isInCheck || userDuplicate.isTasksCompleted)) { + // This user changed his AccountName (screen name) + isEligible = false; + msgSendBack = `This Twitter account is already in use by other participant with other account name: ${userDuplicate.twitterAccount}. Cheating detected. If it's a mistake, try again in a few minutes.`; + } + } - if (isEligible) { - console.log(`User ${userId}.. ${twitterAccount} is eligible.`); + if (config.doCheckTwitterReqs) { + isEligible = isEligible && result.success; + } else { + isEligible = isEligible && true; + } - } else { + if (isEligible) { + console.log(`User ${userId}… ${twitterAccount} is eligible.`); - await user.update({ - isInCheck: false, - isTasksCompleted: false - }, true); + } else { + + await user.update({ + isInCheck: false, + isTasksCompleted: false + }, true); - if (msgSendBack === '') - msgSendBack = `To meet the Bounty campaign rules, your Twitter account ${config.twitterEligibleString}.`; + if (msgSendBack === '') + msgSendBack = `To meet the Bounty campaign rules, your Twitter account ${config.twitterEligibleString}.`; - await $u.sendAdmMsg(userId, msgSendBack); - log.info(`User ${userId}.. ${twitterAccount} is NOT eligible. Message to user: ${msgSendBack}`); + await $u.sendAdmMsg(userId, msgSendBack); + log.info(`User ${userId}… ${twitterAccount} is NOT eligible. Message to user: ${msgSendBack}`); + } + + await user.update({ + isTwitterAccountEligible: isEligible, + twitterAccountId: twitterAccountIdStr, + twitterLifetimeDays: result.lifetimeDays, + twitterFollowers: result.followers + }, true); + + } catch (e) { + log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); } - await user.update({ - isTwitterAccountEligible: isEligible, - twitterAccountId: twitterAccountIdStr, - twitterLifetimeDays: result.lifetimeDays, - twitterFollowers: result.followers - }, true); - - } catch (e) { - log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); - } - - }); + }; + + } finally { + inProcess = false; + } }; diff --git a/modules/checkTwitterRetweet.js b/modules/checkTwitterRetweet.js index 1f86161..ad47d60 100644 --- a/modules/checkTwitterRetweet.js +++ b/modules/checkTwitterRetweet.js @@ -5,120 +5,133 @@ const log = require('../helpers/log'); const notify = require('../helpers/notify'); const twitterapi = require('./twitterapi'); +let inProcess = false; + module.exports = async () => { - const {usersDb} = db; - - // Called strictly after isTwitterFollowCheckPassed = true to eliminate userDb collisions - (await usersDb.find({ - $and: [ - {isInCheck: true}, - {isTwitterAccountEligible: true}, - {isTwitterRetweetCommentCheckPassed: false}, - {$or: [ - {isTwitterFollowCheckPassed: true}, - {$expr: {$eq: [0, config.twitter_follow.length]}} - ]}, - {isTasksCompleted: false}, - {$or: [ - {isAdamantCheckPassed: true}, - {$expr: {$eq: [0, config.adamant_campaign.min_contacts]}} - ]} - ] - })).forEach(async user => { - try { - const { - twitterAccount, - userId - } = user; - - console.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}..`); - - let msgNotify = ''; - let msgNotifyType = ''; - let msgSendBack = ''; - - let toRetweet; - let minMentions; - let hashtags; - let retweetResult; - let isRetweeted; - for (let index = 0; index < config.twitter_retweet_w_comment.length; index++) { - toRetweet = config.twitter_retweet_w_comment[index].tweet; - minMentions = config.twitter_retweet_w_comment[index].min_mentions; - hashtags = config.twitter_retweet_w_comment[index].hashtags; - retweetResult = await twitterapi.checkIfAccountRetweetedwComment(twitterAccount, toRetweet, minMentions, hashtags); - isRetweeted = retweetResult.success; - console.log('isRetweeted:', isRetweeted); - - if (isRetweeted) { - console.log(`User ${userId}.. ${twitterAccount} did retweet ${toRetweet}.`); - - } else { - - // const friendsExample = ['@elonmusk', '@cz_binance', '@FabriLemus7', '@crypto', '@CryptoWhale']; - // let friendsExampleString = ''; - // for (let index = 0; index < friendsExample.length && index < minMentions; index++) { - // friendsExampleString += friendsExample[index] + ' ' - // } - // friendsExampleString = friendsExampleString.trim(); - - await user.update({ - isTwitterRetweetCommentCheckPassed: false, - isInCheck: false, - isTasksCompleted: false - }, true); - switch (retweetResult.error) { - case ('no_retweet'): - msgSendBack = `To meet the Bounty campaign rules, you should quote (retweet with comment) ${toRetweet}.`; - if (minMentions > 0 && hashtags.length > 0) { - msgSendBack += ` Mention ${minMentions} friends, and use ${config.twitter_retweet_w_comment[index].tag_list} tags.`; - } else { + if (inProcess) return; + inProcess = true; + + try { + + const {usersDb} = db; + + // Called strictly after isTwitterFollowCheckPassed = true to eliminate userDb collisions + const users = await usersDb.find({ + $and: [ + {isInCheck: true}, + {isTwitterAccountEligible: true}, + {isTwitterRetweetCommentCheckPassed: false}, + {$or: [ + {isTwitterFollowCheckPassed: true}, + {$expr: {$eq: [0, config.twitter_follow.length]}} + ]}, + {isTasksCompleted: false}, + {$or: [ + {isAdamantCheckPassed: true}, + {$expr: {$eq: [0, config.adamant_campaign.min_contacts]}} + ]} + ] + }); + + for (const user of users) { + try { + const { + twitterAccount, + userId + } = user; + + console.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); + + let msgNotify = ''; + let msgNotifyType = ''; + let msgSendBack = ''; + + let toRetweet; + let minMentions; + let hashtags; + let retweetResult; + let isRetweeted; + for (let index = 0; index < config.twitter_retweet_w_comment.length; index++) { + toRetweet = config.twitter_retweet_w_comment[index].tweet; + minMentions = config.twitter_retweet_w_comment[index].min_mentions; + hashtags = config.twitter_retweet_w_comment[index].hashtags; + retweetResult = await twitterapi.checkIfAccountRetweetedwComment(twitterAccount, toRetweet, minMentions, hashtags); + isRetweeted = retweetResult.success; + console.log('isRetweeted:', isRetweeted); + + if (isRetweeted) { + console.log(`User ${userId}… ${twitterAccount} did retweet ${toRetweet}.`); + + } else { + + // const friendsExample = ['@elonmusk', '@cz_binance', '@FabriLemus7', '@crypto', '@CryptoWhale']; + // let friendsExampleString = ''; + // for (let index = 0; index < friendsExample.length && index < minMentions; index++) { + // friendsExampleString += friendsExample[index] + ' ' + // } + // friendsExampleString = friendsExampleString.trim(); + + await user.update({ + isTwitterRetweetCommentCheckPassed: false, + isInCheck: false, + isTasksCompleted: false + }, true); + switch (retweetResult.error) { + case ('no_retweet'): + msgSendBack = `To meet the Bounty campaign rules, you should quote (retweet with comment) ${toRetweet}.`; + if (minMentions > 0 && hashtags.length > 0) { + msgSendBack += ` Mention ${minMentions} friends, and use ${config.twitter_retweet_w_comment[index].tag_list} tags.`; + } else { + if (minMentions > 0) { + msgSendBack += ` Mention ${minMentions} friends.`; + } + if (hashtags.length > 0) { + msgSendBack += ` Use ${config.twitter_retweet_w_comment[index].tag_list} tags.`; + } + } + msgSendBack += ` Example: _Meet the ADAMANT blockchain messenger! @elonmusk @cz_binance @FabriLemus7 #privacy #crypto #anonymity #decentralization_`; + msgSendBack += `. Then you apply again.`; + break; + case ('not_enough_mentions'): + msgSendBack = `I see your quote.`; if (minMentions > 0) { - msgSendBack += ` Mention ${minMentions} friends.`; + msgSendBack += ` To meet the Bounty campaign rules, it should mention at least ${minMentions} friends.`; + msgSendBack += ` Example: _Meet the ADAMANT blockchain messenger! @elonmusk @cz_binance @FabriLemus7 #privacy #crypto #anonymity #decentralization_`; } + msgSendBack += `. Quote once again.`; + break; + case ('no_hashtags'): + msgSendBack = `I see your quote.`; if (hashtags.length > 0) { - msgSendBack += ` Use ${config.twitter_retweet_w_comment[index].tag_list} tags.`; + msgSendBack += ` To meet the Bounty campaign rules, it should include ${config.twitter_retweet_w_comment[index].tag_list} tags.`; + msgSendBack += ` Example: _Meet the ADAMANT blockchain messenger! @elonmusk @cz_binance @FabriLemus7 #privacy #crypto #anonymity #decentralization_`; } - } - msgSendBack += ` Example: _Meet the ADAMANT blockchain messenger! @elonmusk @cz_binance @FabriLemus7 #privacy #crypto #anonymity #decentralization_`; - msgSendBack += `. Then you apply again.`; - break; - case ('not_enough_mentions'): - msgSendBack = `I see your quote.`; - if (minMentions > 0) { - msgSendBack += ` To meet the Bounty campaign rules, it should mention at least ${minMentions} friends.`; - msgSendBack += ` Example: _Meet the ADAMANT blockchain messenger! @elonmusk @cz_binance @FabriLemus7 #privacy #crypto #anonymity #decentralization_`; - } - msgSendBack += `. Quote once again.`; - break; - case ('no_hashtags'): - msgSendBack = `I see your quote.`; - if (hashtags.length > 0) { - msgSendBack += ` To meet the Bounty campaign rules, it should include ${config.twitter_retweet_w_comment[index].tag_list} tags.`; - msgSendBack += ` Example: _Meet the ADAMANT blockchain messenger! @elonmusk @cz_binance @FabriLemus7 #privacy #crypto #anonymity #decentralization_`; - } - msgSendBack += `. Quote once again.`; - break; - default: - break; - } + msgSendBack += `. Quote once again.`; + break; + default: + break; + } - await $u.sendAdmMsg(userId, msgSendBack); - log.info(`User ${userId}.. ${twitterAccount} did NOT retweet ${toRetweet}: ${retweetResult.error}. Message to user: ${msgSendBack}`); + await $u.sendAdmMsg(userId, msgSendBack); + log.info(`User ${userId}… ${twitterAccount} did NOT retweet ${toRetweet}: ${retweetResult.error}. Message to user: ${msgSendBack}`); - break; + break; + } } - } - await user.update({ - isTwitterRetweetCommentCheckPassed: isRetweeted - }, true); - - } catch (e) { - log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); - } + await user.update({ + isTwitterRetweetCommentCheckPassed: isRetweeted + }, true); - }); + } catch (e) { + log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); + } + + }; + + } finally { + inProcess = false; + } }; @@ -126,3 +139,7 @@ if (config.twitter_retweet_w_comment.length > 0) setInterval(() => { module.exports(); }, 11 * 1000); + +setInterval(() => { + module.exports(); +}, 11 * 1000); diff --git a/modules/checkTxs.js b/modules/checkTxs.js index 93db7f7..6cb4355 100644 --- a/modules/checkTxs.js +++ b/modules/checkTxs.js @@ -5,7 +5,7 @@ const log = require('../helpers/log'); module.exports = async (itx, tx) => { - console.log(`Running module ${$u.getModuleName(module.id)}..`); + console.log(`Running module ${$u.getModuleName(module.id)}…`); const {usersDb} = db; let user = {}; diff --git a/modules/twitterapi.js b/modules/twitterapi.js index 43e1c30..0a4ecd8 100644 --- a/modules/twitterapi.js +++ b/modules/twitterapi.js @@ -31,7 +31,7 @@ let toFollowIds = {}; async function getAccountFollowerIds(account) { const accountSN = $u.getTwitterScreenName(account); - console.log(`Getting followers for @${accountSN}..`) + console.log(`Getting followers for @${accountSN}…`) var ids = []; return new Promise((resolve, reject) => { @@ -61,7 +61,7 @@ async function getAccountFollowerIds(account) { async function getAccountFriendIds(account) { const accountSN = $u.getTwitterScreenName(account); - console.log(`Getting friends for @${accountSN}..`) + console.log(`Getting friends for @${accountSN}…`) var ids = []; return new Promise((resolve, reject) => { @@ -91,7 +91,7 @@ async function getAccountFriendIds(account) { async function getAccountTimeline(account) { const accountSN = $u.getTwitterScreenName(account); - console.log(`Getting timeline for @${accountSN}..`) + console.log(`Getting timeline for @${accountSN}…`) return await Twitter.get('statuses/user_timeline', {screen_name: accountSN, count: 10, trim_user: true, tweet_mode: 'extended'}) .then(function (data) { @@ -110,7 +110,7 @@ async function getAccountTimeline(account) { async function getAccountInfo(account) { const accountSN = $u.getTwitterScreenName(account); - // console.log(`Getting user info for @${accountSN}..`) + // console.log(`Getting user info for @${accountSN}…`) return await Twitter.get('users/show', {screen_name: accountSN}) .then(function (data) { @@ -169,7 +169,7 @@ module.exports = { const twitterAccountSN = $u.getTwitterScreenName(twitterAccount); const followAccountSN = $u.getTwitterScreenName(followAccount); - console.log(`Checking if @${twitterAccountSN} follows @${followAccountSN}..`); + console.log(`Checking if @${twitterAccountSN} follows @${followAccountSN}…`); let followers = await getAccountFriendIds(twitterAccountSN); // console.log(followers); @@ -181,7 +181,7 @@ module.exports = { const tweetId = $u.getTweetIdFromLink(tweet); hashtags = $u.getTwitterHashtags(hashtags); // console.log(tweetId); - console.log(`Checking if @${twitterAccountSN} retweeted ${tweet}..`) + console.log(`Checking if @${twitterAccountSN} retweeted ${tweet}…`) let tweets = await getAccountTimeline(twitterAccountSN); let retweet = {}; @@ -225,7 +225,7 @@ module.exports = { async checkIfAccountEligible(twitterAccount) { const twitterAccountSN = $u.getTwitterScreenName(twitterAccount); - console.log(`Checking if @${twitterAccountSN} eligible..`) + console.log(`Checking if @${twitterAccountSN} eligible…`) let accountInfo = await getAccountInfo(twitterAccountSN); // console.log(accountInfo); From aeae5e7e2df36daf3ddc3ad389f103866317f842 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Thu, 5 Aug 2021 17:17:46 +0300 Subject: [PATCH 19/55] Version pump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4bc1ed6..35853f3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "adamant-bountybot", - "version": "1.3.4", + "version": "1.4.0", "description": "ADAMANT Bounty bot — a software that allows you to carry out bounty campaigns & crypto airdrops, with automatic task verifications and payouts.", "main": "index.js", "scripts": { From 2236e97be571c4f007b5904cc3aee4f49f6956a7 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Thu, 5 Aug 2021 17:32:44 +0300 Subject: [PATCH 20/55] Update logging --- helpers/utils/erc20_utils.js | 5 +---- modules/checkAdamantContacts.js | 6 +++--- modules/checkAll.js | 5 +---- modules/checkTwitterFollow.js | 4 ++-- modules/checkTwitterReqs.js | 7 +++---- modules/checkTwitterRetweet.js | 6 +++--- modules/checkTxs.js | 6 +++--- modules/commandTxs.js | 2 +- modules/incomingTxsParser.js | 2 +- modules/rewardsPayer.js | 4 ++-- 10 files changed, 20 insertions(+), 27 deletions(-) diff --git a/helpers/utils/erc20_utils.js b/helpers/utils/erc20_utils.js index 529724c..9ab37f1 100644 --- a/helpers/utils/erc20_utils.js +++ b/helpers/utils/erc20_utils.js @@ -15,7 +15,7 @@ class erc20 { this.web3 = web3; this.contract = new web3.eth.Contract(abiArray, this.model.sc, {from: this.User.address}); $u[token] = this; - log.info(`Created ERC-20 token: ${token}`); + log.log(`Created ERC-20 token: ${token}`); this.updateBalance(); } async updateBalance() { @@ -68,15 +68,12 @@ class erc20 { get FEE() { let inEth = eth.FEE * 2; - // console.log(`Fee in eth: ${inEth}`) return inEth } get FEEinToken() { let inEth = eth.FEE * 2; let inToken = inEth * Store.mathEqual('ETH', this.token, 1, true).exchangePrice; - // console.log(`Fee in eth: ${inEth}`) - // console.log(`Fee in token: ${inToken}`) return inToken } diff --git a/modules/checkAdamantContacts.js b/modules/checkAdamantContacts.js index 969b8ad..447ba8c 100644 --- a/modules/checkAdamantContacts.js +++ b/modules/checkAdamantContacts.js @@ -48,7 +48,7 @@ module.exports = async () => { userId } = user; - console.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); + log.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); let msgNotify = ''; let msgNotifyType = ''; @@ -83,7 +83,7 @@ module.exports = async () => { if (isContactsDone) { - console.log(`User ${userId}… did make ${config.adamant_campaign.min_contacts} contacts.`); + log.log(`User ${userId}… did make ${config.adamant_campaign.min_contacts} contacts.`); await user.update({ isAdamantCheckPassed: true }, true); @@ -103,7 +103,7 @@ module.exports = async () => { } await $u.sendAdmMsg(userId, msgSendBack); - log.info(`User ${userId}… did NOT make ${config.adamant_campaign.min_contacts} contacts. Message to user: ${msgSendBack}`); + log.log(`User ${userId}… did NOT make ${config.adamant_campaign.min_contacts} contacts. Message to user: ${msgSendBack}`); } } catch (e) { diff --git a/modules/checkAll.js b/modules/checkAll.js index a324129..0f84bf0 100644 --- a/modules/checkAll.js +++ b/modules/checkAll.js @@ -33,7 +33,7 @@ module.exports = async () => { twitterLifetimeDays } = user; - console.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); + log.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); let msgSendBack = ''; @@ -46,7 +46,6 @@ module.exports = async () => { }, true); config.rewards.forEach(reward => { let amount = reward.amount; - // console.log(config.rewards_progression_from_twitter_followers[reward.currency]); if (config.rewards_progression_from_twitter_followers[reward.currency] && twitterFollowers) { let followersCount = twitterFollowers; if (followersCount > config.rewards_progression_from_twitter_followers[reward.currency].limit_followers) @@ -54,8 +53,6 @@ module.exports = async () => { let f = config.rewards_progression_from_twitter_followers[reward.currency].func; amount = mathjs.evaluate(f, {followers: followersCount}); amount = +amount.toFixed(config.rewards_progression_from_twitter_followers[reward.currency].decimals_transfer); - // console.log(amount); - // process.exit(1); } payment = new paymentsDb({ date: $u.unix(), diff --git a/modules/checkTwitterFollow.js b/modules/checkTwitterFollow.js index 6282fcb..0629ff8 100644 --- a/modules/checkTwitterFollow.js +++ b/modules/checkTwitterFollow.js @@ -36,7 +36,7 @@ module.exports = async () => { userId } = user; - console.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); + log.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); let msgNotify = ''; let msgNotifyType = ''; @@ -50,7 +50,7 @@ module.exports = async () => { console.log('isFollowing:', isFollowing); if (isFollowing) { - console.log(`User ${userId}… ${twitterAccount} do follows ${followAccount}.`); + log.log(`User ${userId}… ${twitterAccount} do follows ${followAccount}.`); } else { diff --git a/modules/checkTwitterReqs.js b/modules/checkTwitterReqs.js index 47711e3..364e824 100644 --- a/modules/checkTwitterReqs.js +++ b/modules/checkTwitterReqs.js @@ -35,14 +35,13 @@ module.exports = async () => { userId } = user; - console.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); + log.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); let msgSendBack = ''; let result, isEligible = true; let twitterAccountIdStr = null; result = await twitterapi.checkIfAccountEligible(twitterAccount); - // console.log(result); if (result.error === 'request_failed') { return; // If request to Twitter API failed, ignore and check next time @@ -69,7 +68,7 @@ module.exports = async () => { } if (isEligible) { - console.log(`User ${userId}… ${twitterAccount} is eligible.`); + log.log(`User ${userId}… ${twitterAccount} is eligible.`); } else { @@ -82,7 +81,7 @@ module.exports = async () => { msgSendBack = `To meet the Bounty campaign rules, your Twitter account ${config.twitterEligibleString}.`; await $u.sendAdmMsg(userId, msgSendBack); - log.info(`User ${userId}… ${twitterAccount} is NOT eligible. Message to user: ${msgSendBack}`); + log.log(`User ${userId}… ${twitterAccount} is NOT eligible. Message to user: ${msgSendBack}`); } await user.update({ diff --git a/modules/checkTwitterRetweet.js b/modules/checkTwitterRetweet.js index ad47d60..9e57c00 100644 --- a/modules/checkTwitterRetweet.js +++ b/modules/checkTwitterRetweet.js @@ -41,7 +41,7 @@ module.exports = async () => { userId } = user; - console.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); + log.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); let msgNotify = ''; let msgNotifyType = ''; @@ -61,7 +61,7 @@ module.exports = async () => { console.log('isRetweeted:', isRetweeted); if (isRetweeted) { - console.log(`User ${userId}… ${twitterAccount} did retweet ${toRetweet}.`); + log.log(`User ${userId}… ${twitterAccount} did retweet ${toRetweet}.`); } else { @@ -114,7 +114,7 @@ module.exports = async () => { } await $u.sendAdmMsg(userId, msgSendBack); - log.info(`User ${userId}… ${twitterAccount} did NOT retweet ${toRetweet}: ${retweetResult.error}. Message to user: ${msgSendBack}`); + log.log(`User ${userId}… ${twitterAccount} did NOT retweet ${toRetweet}: ${retweetResult.error}. Message to user: ${msgSendBack}`); break; } diff --git a/modules/checkTxs.js b/modules/checkTxs.js index 6cb4355..a523511 100644 --- a/modules/checkTxs.js +++ b/modules/checkTxs.js @@ -15,7 +15,7 @@ module.exports = async (itx, tx) => { user = await usersDb.findOne({twitterAccount: itx.accounts.twitterAccount}); if (user && (user.isInCheck || user.isTasksCompleted)) { // This Twitter account is already in use by other user, unable to switch - log.info(`User ${user.userId} applied with already used Twitter account ${itx.accounts.twitterAccount}. Notify user and ignore.`); + log.warn(`User ${user.userId} applied with already used Twitter account ${itx.accounts.twitterAccount}. Notify user and ignore.`); if (user.userId !== tx.senderId) { msgSendBack = `This Twitter account is already in use by other participant. If it's a mistake, try again in a few minutes.`; } else { @@ -32,7 +32,7 @@ module.exports = async (itx, tx) => { user = await usersDb.findOne({userId: tx.senderId}); if (user) { // User is already was in check earlier, update - log.info(`User ${user.userId} applied once again with Twitter account ${itx.accounts.twitterAccount}.`); + log.log(`User ${user.userId} applied once again with Twitter account ${itx.accounts.twitterAccount}.`); // May be later // if (user.isBountyPayed) { // msgSendBack = `You've already received the Bounty reward. Thanks for your support!`; @@ -40,7 +40,7 @@ module.exports = async (itx, tx) => { // return; // } else if (user.isTasksCompleted) { - log.info(`User ${user.userId} already completed the Bounty tasks. Notify user and ignore.`); + log.log(`User ${user.userId} already completed the Bounty tasks. Notify user and ignore.`); msgSendBack = `You've already completed the Bounty tasks.`; $u.sendAdmMsg(tx.senderId, msgSendBack); return; diff --git a/modules/commandTxs.js b/modules/commandTxs.js index 8c8fbe0..e359756 100644 --- a/modules/commandTxs.js +++ b/modules/commandTxs.js @@ -8,7 +8,7 @@ const twitterapi = require('./twitterapi'); module.exports = async (cmd, tx, itx) => { if (itx.isProcessed) return; - log.info(`Got new command Tx to process: ${cmd} from ${tx.senderId}`); + log.log(`Got new command Tx to process: ${cmd} from ${tx.senderId}`); try { let res = []; const group = cmd diff --git a/modules/incomingTxsParser.js b/modules/incomingTxsParser.js index 030634d..9a724e3 100644 --- a/modules/incomingTxsParser.js +++ b/modules/incomingTxsParser.js @@ -27,7 +27,7 @@ module.exports = async (tx) => { return; }; - log.info(`New incoming transaction: ${tx.id} from ${tx.senderId}`); + log.log(`New incoming transaction: ${tx.id} from ${tx.senderId}`); let msg = ''; const chat = tx.asset.chat; diff --git a/modules/rewardsPayer.js b/modules/rewardsPayer.js index 1e8371b..c624cc5 100644 --- a/modules/rewardsPayer.js +++ b/modules/rewardsPayer.js @@ -47,13 +47,13 @@ module.exports = async () => { return; } - log.info(`Attempt number ${pay.trySendCounter} to send the reward payout. Coin: ${outCurrency}, address: ${outAddress}, value: ${outAmount}, balance: ${Store.user[outCurrency].balance}`); + log.log(`Attempt number ${pay.trySendCounter} to send the reward payout. Coin: ${outCurrency}, address: ${outAddress}, value: ${outAmount}, balance: ${Store.user[outCurrency].balance}`); const result = await $u[outCurrency].send({ address: outAddress, value: outAmount, comment: 'Was it great? Share the experience with your friends!' // if ADM }); - log.info(`Payout result: ${JSON.stringify(result, 0, 2)}`); + log.log(`Payout result: ${JSON.stringify(result, 0, 2)}`); if (result.success) { From 55e3e94398b1a84dfde840fe09d302647d114598 Mon Sep 17 00:00:00 2001 From: gost1k Date: Tue, 22 Mar 2022 23:15:22 +0300 Subject: [PATCH 21/55] style: add pre-commit hooks and linter --- .eslintrc.js | 31 +++++++++ .gitignore | 8 +++ .husky/commit-msg | 4 ++ .husky/pre-commit | 4 ++ CONTRIBUTING.md | 83 +++++++++++++++++++++++ README.md | 102 ++++++++++++++++++++++++++++ commitlint.config.js | 3 + config.json | 154 +++++++++++++++++++++++++++++++++++++++++++ package.json | 61 +++++++++++++++++ 9 files changed, 450 insertions(+) create mode 100644 .eslintrc.js create mode 100644 .gitignore create mode 100644 .husky/commit-msg create mode 100755 .husky/pre-commit create mode 100644 CONTRIBUTING.md create mode 100644 README.md create mode 100644 commitlint.config.js create mode 100644 config.json create mode 100644 package.json diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..cf4801b --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,31 @@ +module.exports = { + env: { + commonjs: true, + es2021: true, + browser: true, + node: true, + 'jest/globals': true, + }, + extends: ['eslint:recommended', 'google'], + plugins: ['jest'], + parserOptions: { + ecmaVersion: 12, + }, + rules: { + 'max-len': [ + 'error', + { + code: 200, + ignoreTrailingComments: true, + ignoreUrls: true, + ignoreStrings: true, + ignoreTemplateLiterals: true, + ignoreRegExpLiterals: true, + }, + ], + 'require-jsdoc': 'off', + 'quote-props': 'off', + 'camelcase': 'off', + 'no-empty': 'off', + }, +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b2617a --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +logs/ +.vscode/ +package-lock.json +tests.js +config.test +.idea +.editorconfig diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 0000000..b78c75d --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx --no -- commitlint --edit diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..20d0d06 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npm run lint diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..284879f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,83 @@ +# Contributing Guide + +Before submitting your contribution, please make sure to take a moment and read through the following guidelines: + +- [Pull Request Guidelines](#pull-request-guidelines) +- [Development Setup](#development-setup) +- [Scripts](#scripts) +- [Project Structure](#project-structure) +- [Contributing Tests](#contributing-tests) + +## Pull Request Guidelines + +- The master branch is just a snapshot of the latest stable release. All development should be done in dedicated branches. Do not submit PRs against the master branch. + +- Checkout a topic branch from a base branch, e.g. `master`, and merge back against that branch. + +- If adding a new feature add accompanying test case. + +- It's OK to have multiple small commits as you work on the PR - GitHub can automatically squash them before merging. + +- Make sure tests pass! + +- Commit messages must follow the [commit message convention](https://github.com/conventional-changelog/commitlint/blob/master/README.md). Commit messages are automatically validated before commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [husky](https://github.com/typicode/husky)). + +- No need to worry about code style as long as you have installed the dev dependencies - modified files are automatically formatted with Prettier on commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [husky](https://github.com/typicode/husky)). + +## Development Setup + +You will need [Node.js](https://nodejs.org) **version 16+**. + +After cloning the repo, run: + +```bash +$ npm i # install the dependencies of the project +``` + +A high level overview of tools used: + +- [Jest](https://jestjs.io/) for unit testing + +## Scripts + +### `npm run lint` + +The `lint` script runs linter. + +```bash +# lint files +$ npm run lint +# fix linter errors +$ npm run lint:fix +``` + +### `npm run test` + +The `test` script simply calls the `jest` binary, so all [Jest CLI Options](https://jestjs.io/docs/en/cli) can be used. Some examples: + +```bash +# run all tests +$ npm run test +# run all tests under the runtime-core package +$ npm run test -- runtime-core +# run tests in a specific file +$ npm run test -- fileName +# run a specific test in a specific file +$ npm run test -- fileName -t 'test name' +``` + +## Project Structure + +- **`modules`**: contains logic that handles requests to bounty-bot. + +- **`helpers`**: contains utilities shared across the entire codebase. + +- **`tests`**: contains tests for the application. + +## Contributing Tests + +Unit tests are collocated with the code being tested inside directories named `tests`. Consult the [Jest docs](https://jestjs.io/docs/en/using-matchers) and existing test cases for how to write new test specs. Here are some additional guidelines: + +- Use the minimal API needed for a test case. For example, if a test can be written without involving the reactivity system or a component, it should be written so. This limits the test's exposure to changes in unrelated parts and makes it more stable. + +- Only use platform-specific runtimes if the test is asserting platform-specific behavior. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e956e0c --- /dev/null +++ b/README.md @@ -0,0 +1,102 @@ +ADAMANT Bounty Bot is a software that allows you to carry out bounty campaigns & crypto airdrops, with automatic task verifications and payouts. + +It is made for crypto projects and communities. + +The bounty bot: + +* Interactive and interesting for users. The bot talks to users in ADAMANT Messenger chat directly +* Works with Twitter campaigns: follow & retweet with comment (quote). You can set up mentions and hashtags. +* Set which Twitter accounts are eligible to participate: minimum followers, friends, statuses and lifetime +* Supports ADAMANT campaigns: users will invite other users +* Automatic task verifications and payouts +* Supports payouts in ADM, ETH and ERC-20 tokens +* Easy to install and configure +* Free and open source +* Stores statistics + +# Installation + +User-friendly instructions: [Carry out a crypto Bounty campaign on ADAMANT platform](https://medium.com/adamant-im/adamants-interactive-bounty-bot-for-cryptocurrency-projects-51fec10f93b9). + +## Requirements + +* Ubuntu 16 / Ubuntu 18 (other OS had not been tested) +* NodeJS v 8+ (already installed if you have a node on your machine) +* MongoDB ([installation instructions](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/)) + +## Setup + +``` +su - adamant +git clone https://github.com/Adamant-im/adamant-bountybot +cd ./adamant-bountybot +npm i +``` + +## Pre-launch tuning + +``` +nano config.json +``` + +Parameters: + +* `passPhrase` The exchange bot's secret phrase for concluding transactions. Obligatory. Bot's ADAMANT address will correspond this passPhrase. +* `node_ADM` List of nodes for API work, obligatorily +* `node_ETH` List of nodes for Ethereum API work, obligatorily +* `infoservice` List of [ADAMANT InfoServices](https://github.com/Adamant-im/adamant-currencyinfo-services) for catching exchange rates, obligatorily +* `socket` If to use WebSocket connection. Recommended for better user experience +* `ws_type` Choose socket connection, "ws" or "wss" depending on your server +* `bot_name` Bot's name for notifications +* `admin_accounts` ADAMANT accounts to accept control commands from +* `notify_non_admins` Notify non-admins that they are not admins +* `slack` Token for Slack alerts for the bot’s administrator. No alerts if not set +* `adamant_notify` ADM address for the bot’s administrator. Recommended +* `known_crypto` List of cryptocurrencies bot can work with. Obligatorily +* `erc20` List of cryptocurrencies of ERC-20 type. It is necessary to put all known ERC-20 tokens here. + +* `twitter_follow` List of Twitter account user should follow +* `twitter_retweet` List of Twitter posts user should retweet +* `twitter_api` Your Twitter API credentials. Get on https://apps.twitter.com/app/new +* `twitter_reqs` Requirements for user's Twitter account +* `twitter_api_test_interval` Interval in minutes to test Twitter API + +* `adamant_campaign` Settings for ADAMANT bounty campaign + +* `notifyTasksCompleted` If you want to receive notifications when user completes Bounty tasks +* `notifyRewardReceived` If you want to receive notifications when user receives a Bounty reward +* `rewards` List rewards for a Bounty campaign: cryptos and amounts + +* `welcome_string` How to reply user in-chat, if first unknown command received +* `help_message` How to reply to */help* command. Recommended to put Bounty rules here + +## Launching + +You can start the Bot with the `node app` command, but it is recommended to use the process manager for this purpose. + +``` +pm2 start --name bountybot app.js +``` + +## Add the Bot to cron + +``` +crontab -e +``` + +Add string: + +``` +@reboot cd /home/adamant/adamant-bountybot && pm2 start --name bountybot app.js +``` + +## Updating + +``` +su - adamant +cd ./adamant-bountybot +pm2 stop bountybot +mv config.json config_bup.json && git pull && mv config_bup.json config.json +npm i +pm2 start --name bountybot app.js +``` diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..84dcb12 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], +}; diff --git a/config.json b/config.json new file mode 100644 index 0000000..35ab686 --- /dev/null +++ b/config.json @@ -0,0 +1,154 @@ +{ + /** The bot's secret phrase for concluding transactions. + Bot's ADAMANT address will correspond this passPhrase. + **/ + "passPhrase": "qwert yuiop asdfg hjkl zxcvb nmqwe", + /** List of nodes to fetch transactions. + If one become unavailable, pool will choose live one. + **/ + + "node_ADM": [ + "http://localhost:36666", + "https://endless.adamant.im", + "https://clown.adamant.im", + "https://bid.adamant.im", + "https://unusual.adamant.im", + "https://debate.adamant.im", + "http://185.231.245.26:36666", + "https://lake.adamant.im" + ], + /** Socket connection is recommended for better user experience **/ + "socket": true, + /** Choose socket connection, "ws" or "wss" depending on your server **/ + "ws_type": "ws", + /** List of nodes for Ethereum API work **/ + "node_ETH": [ + "https://ethnode1.adamant.im" + ], + /** List of ADAMANT InfoServices for catching exchange rates **/ + "infoservice": [ + "https://info.adamant.im" + ], + /** List of cryptocurrencies bot can work with. **/ + "known_crypto": [ + "ADM", + "ETH", + "USDS", + "RES", + "BZ" + ], + /** List of ERC-20 tokens **/ + "erc20": [ + "USDS", + "RES", + "BZ" + ], + /** How to reply user in-chat, if first unknown command received. **/ + "welcome_string": "Hi! 😊 I'm a bounty bot. And this is a stub. Are you ready for the awesome bounty campaign with automatic payout? Type **/help** to see bounty rules.", + /** Bounty rules. Shown by /help command. You can use template literals + like ${config.rewards_list}, ${config.rewards_tickers}, ${config.twitter_follow[0]}, + ${config.twitter_retweet_w_comment[0].tweet}, ${config.twitter_follow_list}, + ${config.twitter_retweet_w_comment[0].min_mentions}, ${config.twitter_retweet_w_comment[0].tag_list}, + ${config.twitterEligibleString} + **/ + "help_message": "Earn **${config.rewards_tickers}** with your social activity! A reward depends on how much Twitter followers you have. Your account ${config.twitterEligibleString}.\n${config.rewards_range}\n\n \nThe campaign rules:\n- Follow account ${config.twitter_follow_list} on Twitter\n- Like & quote ${config.twitter_retweet_w_comment[0].tweet}, mentioning ${config.twitter_retweet_w_comment[0].min_mentions} friends and ${config.twitter_retweet_w_comment[0].tag_list} tags.\n- Invite ${config.adamant_campaign.min_contacts} friend in ADAMANT Messenger. They must message you.\n- Send me the name (like @adamant_im) or the link to your Twitter profile to verify.\n\n \nGo!", + /** Bot's name for notifications **/ + "bot_name": "Lovely Bounty Bot", + /** ADAMANT accounts to accept control commands from. Control commands from other accounts will not be executed. **/ + "admin_accounts": [ + "U1123..." + ], + /** Twitter accounts user should follow **/ + "twitter_follow": [ + "@adamant_im" + ], + /** Requirements for user's Twitter account. Set all parameters to 0 if even new accounts eligible **/ + "twitter_reqs": { + "min_followers": 10, + "min_friends": 5, + "min_statuses": 5, + "min_days": 20 + }, + /** Tweets user should quote (retweet with comment). + Min_mentions is how much people he should mention. Hashtags is a list of tags he must use. + **/ + "twitter_retweet_w_comment": [ + { + "tweet": "https://twitter.com/adamant_im/status/1272945640574722048", + "min_mentions": 3, + "hashtags": [ + "#privacy", + "#crypto", + "#anonymity", + "#decentralization" + ] + } + ], + /** Minimum contacts user must invite to ADAMANT Messenger. + Contacts must be new users. + 0 is disabled. + **/ + "adamant_campaign": { + "min_contacts": 1 + }, + /** List rewards for a Bounty campaign here **/ + "rewards": [ + { + "currency": "ADM", + "amount": 100 + }, + { + "currency": "ETH", + "amount": 0.01 + } + ], + /** Set progressive scale of reward amounts for each cryptocurrency. + `func` is a mathjs.org function. Limit followers with `limit_followers` parameter. + If not set for a currency, plain amount is used, which is set in `rewards.amount`. + **/ + "rewards_progression_from_twitter_followers": { + "ADM": { + "func": "sqrt(followers) * 3", + "limit_followers": 5000, + "decimals_transfer": 8, + "decimals_show": 0 + } + }, + /** Your Twitter API credentials. Get on https://apps.twitter.com/app/new **/ + "twitter_api": { + "consumer_key": "", + "consumer_secret": "", + "access_token_key": "", + "access_token_secret": "" + }, + /** Interval in minutes to test Twitter API. Because of different reasons Twitter may temporary block API requests. + To continue, you need manually login into your Twitter account and solve captcha. + This parameter allows to automatically check if Twitter API works well every twitter_api_test_interval minutes. + In case of error the bot will notify you. Also you can run "/test twitterapi" command manually. + 0 means disabled. + **/ + "twitter_api_test_interval": 600, + /** ADAMANT accounts to accept control commands from. Control commands from other accounts will not be executed. **/ + "admin_accounts": [ + "U1123..." + ], + /** Notify non-admins that they are not admins. If false, bot will be silent. **/ + "notify_non_admins": true, + /** ADAMANT address for notifications and monitoring (if needed, recommended) **/ + "adamant_notify": "", + /** Slack key for notifications and monitoring (if needed) **/ + "slack": "https://hooks.slack.com/services/", + /** If you want to receive notifications when user completes Bounty tasks **/ + "notifyTasksCompleted": true, + /** If you want to receive notifications when user receives a Bounty reward **/ + "notifyRewardReceived": true, + /** Port for getting debug info. + Do not set for live exchange bots, use only for debugging. + Allows to get DBs records like http://ip:port/db?tb=incomingTxsDb + **/ + "api": false, + /** The software will use verbosity according to log_level. + It can be none < error < warn < info < log. + **/ + "log_level": "log" +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..baee2f8 --- /dev/null +++ b/package.json @@ -0,0 +1,61 @@ +{ + "name": "adamant-bountybot", + "version": "1.4.0", + "description": "ADAMANT Bounty bot — a software that allows you to carry out bounty campaigns & crypto airdrops, with automatic task verifications and payouts.", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "prepare": "husky install", + "lint": "eslint . --ext .js --ignore-pattern node_modules/", + "lint:fix": "eslint . --fix --ext .js --ignore-pattern node_modules/" + }, + "keywords": [ + "adm", + "adamant", + "blockchain", + "messenger", + "bot", + "bitcoin", + "ethereum", + "bounty", + "bounty bot", + "crypto", + "cryptocurrency", + "twitter", + "airdrop" + ], + "author": "Aleksei Lebedev (https://adamant.im)", + "license": "GPL-3.0", + "dependencies": { + "adamant-api": "^0.5.3", + "axios": "0.21.1", + "ethereumjs-tx": "^2.1.2", + "ethereumjs-util": "^7.0.0", + "express": "^4.17.1", + "jsonminify": "^0.4.1", + "jsonrpc-client": "^0.1.1", + "mathjs": "^7.3.0", + "mongodb": "^3.2.6", + "node-ethereum-wallet": "^1.3.2", + "node-json-rpc": "0.0.1", + "request": "^2.88.0", + "twitter": "^1.7.1", + "web3": "^1.2.7" + }, + "devDependencies": { + "@commitlint/config-conventional": "^16.2.1", + "@commitlint/cli": "^16.2.1", + "eslint-config-google": "^0.14.0", + "eslint-plugin-jest": "^26.1.0", + "eslint": "^8.9.0", + "husky": "^7.0.4" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Adamant-im/adamant-bountybot.git" + }, + "bugs": { + "url": "https://github.com/Adamant-im/adamant-bountybot/issues" + }, + "homepage": "https://github.com/Adamant-im/adamant-bountybot#readme" +} From 1b6d50b515f2ea28d60338fc6cb559f1648fb29b Mon Sep 17 00:00:00 2001 From: gost1k Date: Wed, 23 Mar 2022 00:01:41 +0300 Subject: [PATCH 22/55] style: use new code style --- app.js | 113 +++--- config.json | 5 +- helpers/const.js | 8 +- helpers/dbModel.js | 140 ++++---- helpers/log.js | 144 ++++---- helpers/notify.js | 160 ++++----- helpers/utils/adm_utils.js | 138 ++++---- helpers/utils/erc20_models.js | 28 +- helpers/utils/erc20_utils.js | 597 ++++++++++++++++---------------- helpers/utils/eth_utils.js | 317 ++++++++--------- helpers/utils/index.js | 446 ++++++++++++------------ modules/DB.js | 38 +- modules/Store.js | 200 +++++------ modules/api.js | 6 +- modules/apiTester.js | 28 +- modules/checkAdamantContacts.js | 203 +++++------ modules/checkAll.js | 197 ++++++----- modules/checkTwitterFollow.js | 164 ++++----- modules/checkTwitterReqs.js | 187 +++++----- modules/checkTwitterRetweet.js | 244 +++++++------ modules/checkTxs.js | 180 +++++----- modules/checkerTransactions.js | 56 +-- modules/commandTxs.js | 441 ++++++++++++----------- modules/configReader.js | 379 ++++++++++---------- modules/incomingTxsParser.js | 225 ++++++------ modules/outAddressFetcher.js | 112 +++--- modules/rewardsPayer.js | 191 +++++----- modules/sentTxValidator.js | 223 ++++++------ modules/transferTxs.js | 89 +++-- modules/twitterapi.js | 589 +++++++++++++++---------------- modules/unknownTxs.js | 376 ++++++++++---------- server.js | 85 +++-- 32 files changed, 3107 insertions(+), 3202 deletions(-) diff --git a/app.js b/app.js index ea4b866..2a2ed4e 100644 --- a/app.js +++ b/app.js @@ -1,57 +1,56 @@ -const notify = require('./helpers/notify'); -const db = require('./modules/DB'); -const Store = require('./modules/Store'); -const checker = require('./modules/checkerTransactions'); -const doClearDB = process.argv.includes('clear_db'); -const config = require('./modules/configReader'); -const txParser = require('./modules/incomingTxsParser'); - -// Socket connection -const api = require('./modules/api'); -api.socket.initSocket({socket: config.socket, wsType: config.ws_type, onNewMessage: txParser, admAddress: Store.user.ADM.address}); - -setTimeout(init, 5000); - -function init() { - require('./helpers/utils/erc20_utils'); - require('./server'); - require('./modules/checkTwitterFollow'); - require('./modules/apiTester'); - require('./modules/checkTwitterReqs'); - require('./modules/checkTwitterRetweet'); - require('./modules/checkAdamantContacts'); - require('./modules/checkAll'); - require('./modules/outAddressFetcher'); - require('./modules/rewardsPayer'); - require('./modules/sentTxValidator'); - try { - - if (doClearDB) { - console.log('Clearing database…'); - db.systemDb.db.drop(); - db.incomingTxsDb.db.drop(); - db.usersDb.db.drop(); - db.paymentsDb.db.drop(); - notify(`*${config.notifyName}: database cleared*. Manually stop the Bot now.`, 'info'); - } else { - - db.systemDb.findOne().then(system => { - if (system) { - Store.lastBlock = system.lastBlock; - } else { // if 1st start - Store.updateLastBlock(); - } - checker(); - notify(`*${config.notifyName} started* for address _${Store.user.ADM.address}_ (ver. ${Store.version}).`, 'info'); - }); - } - - } catch (e) { - let message = `${config.notifyName} is not started. Error: ${e}`; - if (e.message.includes('findOne')) { - message = `${config.notifyName} is not started. Unable to connect to MongoDB. Check if Mongo server is running and available.`; - } - notify(message, 'error'); - setTimeout(() => {process.exit(1);}, 2000); - } -} +const notify = require('./helpers/notify'); +const db = require('./modules/DB'); +const Store = require('./modules/Store'); +const checker = require('./modules/checkerTransactions'); +const doClearDB = process.argv.includes('clear_db'); +const config = require('./modules/configReader'); +const txParser = require('./modules/incomingTxsParser'); + +// Socket connection +const api = require('./modules/api'); +api.socket.initSocket({socket: config.socket, wsType: config.ws_type, onNewMessage: txParser, admAddress: Store.user.ADM.address}); + +setTimeout(init, 5000); + +function init() { + require('./helpers/utils/erc20_utils'); + require('./server'); + require('./modules/checkTwitterFollow'); + require('./modules/apiTester'); + require('./modules/checkTwitterReqs'); + require('./modules/checkTwitterRetweet'); + require('./modules/checkAdamantContacts'); + require('./modules/checkAll'); + require('./modules/outAddressFetcher'); + require('./modules/rewardsPayer'); + require('./modules/sentTxValidator'); + try { + if (doClearDB) { + console.log('Clearing database…'); + db.systemDb.db.drop(); + db.IncomingTxsDb.db.drop(); + db.UsersDb.db.drop(); + db.PaymentsDb.db.drop(); + notify(`*${config.notifyName}: database cleared*. Manually stop the Bot now.`, 'info'); + } else { + db.systemDb.findOne().then((system) => { + if (system) { + Store.lastBlock = system.lastBlock; + } else { // if 1st start + Store.updateLastBlock(); + } + checker(); + notify(`*${config.notifyName} started* for address _${Store.user.ADM.address}_ (ver. ${Store.version}).`, 'info'); + }); + } + } catch (e) { + let message = `${config.notifyName} is not started. Error: ${e}`; + if (e.message.includes('findOne')) { + message = `${config.notifyName} is not started. Unable to connect to MongoDB. Check if Mongo server is running and available.`; + } + notify(message, 'error'); + setTimeout(() => { + process.exit(1); + }, 2000); + } +} diff --git a/config.json b/config.json index 35ab686..a11c95e 100644 --- a/config.json +++ b/config.json @@ -129,9 +129,6 @@ **/ "twitter_api_test_interval": 600, /** ADAMANT accounts to accept control commands from. Control commands from other accounts will not be executed. **/ - "admin_accounts": [ - "U1123..." - ], /** Notify non-admins that they are not admins. If false, bot will be silent. **/ "notify_non_admins": true, /** ADAMANT address for notifications and monitoring (if needed, recommended) **/ @@ -144,7 +141,7 @@ "notifyRewardReceived": true, /** Port for getting debug info. Do not set for live exchange bots, use only for debugging. - Allows to get DBs records like http://ip:port/db?tb=incomingTxsDb + Allows to get DBs records like http://ip:port/db?tb=IncomingTxs **/ "api": false, /** The software will use verbosity according to log_level. diff --git a/helpers/const.js b/helpers/const.js index 3cef6e6..203f6e9 100644 --- a/helpers/const.js +++ b/helpers/const.js @@ -1,4 +1,4 @@ -module.exports = { - SAT: 100000000, - EPOCH: Date.UTC(2017, 8, 2, 17, 0, 0, 0) -}; \ No newline at end of file +module.exports = { + SAT: 100000000, + EPOCH: Date.UTC(2017, 8, 2, 17, 0, 0, 0), +}; diff --git a/helpers/dbModel.js b/helpers/dbModel.js index bb4909b..95b2202 100644 --- a/helpers/dbModel.js +++ b/helpers/dbModel.js @@ -1,70 +1,70 @@ -module.exports = (db) => { - return class { - constructor(data = {}, isSave) { - this.db = db; - Object.assign(this, data); - if (isSave){ - this.save(); - } - } - static get db() { - return db; - } - static find(a) { // return Array - return new Promise((resolve, reject) => { - this.db.find(a).toArray((err, data) => { - resolve(data.map(d=>new this(d))); - }); - }); - } - static aggregate(a) { // return Array - return new Promise((resolve, reject) => { - this.db.aggregate(a).toArray((err, data) => { - resolve(data.map(d=>new this(d))); - }); - }); - } - static findOne(a) { - return new Promise((resolve, reject) => { - db.findOne(a).then((doc, b) => { - if (!doc) { - resolve(doc); - } else { - resolve(new this(doc)); - } - }); - }); - } - _data() { - const data = {}; - for (let field in this){ - if (!['db', '_id'].includes(field)){ - data[field] = this[field]; - } - } - return data; - } - async update(obj, isSave){ - Object.assign(this, obj); - if (isSave){ - await this.save(); - } - } - save() { - return new Promise((resolve, reject) => { - if (!this._id) { - db.insertOne(this._data(), (err, res) => { - this._id = res.insertedId; - resolve(this._id); - }); - } else { - db.updateOne({_id: this._id}, { - $set: this._data() - }, {upsert: true}).then(() => { - resolve(this._id); - }); - } - }); - } - }; -}; \ No newline at end of file +module.exports = (db) => { + return class { + constructor(data = {}, isSave) { + this.db = db; + Object.assign(this, data); + if (isSave) { + this.save(); + } + } + static get db() { + return db; + } + static find(a) { // return Array + return new Promise((resolve, reject) => { + this.db.find(a).toArray((err, data) => { + resolve(data.map((d)=>new this(d))); + }); + }); + } + static aggregate(a) { // return Array + return new Promise((resolve, reject) => { + this.db.aggregate(a).toArray((err, data) => { + resolve(data.map((d)=>new this(d))); + }); + }); + } + static findOne(a) { + return new Promise((resolve, reject) => { + db.findOne(a).then((doc, b) => { + if (!doc) { + resolve(doc); + } else { + resolve(new this(doc)); + } + }); + }); + } + _data() { + const data = {}; + for (const field in this) { + if (!['db', '_id'].includes(field)) { + data[field] = this[field]; + } + } + return data; + } + async update(obj, isSave) { + Object.assign(this, obj); + if (isSave) { + await this.save(); + } + } + save() { + return new Promise((resolve, reject) => { + if (!this._id) { + db.insertOne(this._data(), (err, res) => { + this._id = res.insertedId; + resolve(this._id); + }); + } else { + db.updateOne({_id: this._id}, { + $set: this._data(), + }, {upsert: true}).then(() => { + resolve(this._id); + }); + } + }); + } + }; +}; diff --git a/helpers/log.js b/helpers/log.js index 0864db6..c70996b 100644 --- a/helpers/log.js +++ b/helpers/log.js @@ -1,72 +1,72 @@ -const config = require('../modules/configReader'); - -const fs = require('fs'); -if (!fs.existsSync('./logs')) { - fs.mkdirSync('./logs'); -} - -const infoStr = fs.createWriteStream('./logs/' + date() + '.log', { - flags: 'a', -}); - -infoStr.write(`\n\n[The bot started] _________________${fullTime()}_________________\n`); - -module.exports = { - error(str) { - if (['error', 'warn', 'info', 'log'].includes(config.log_level)) { - infoStr.write(`\n ` + 'error|' + fullTime() + '|' + str); - console.log('\x1b[31m', 'error|' + fullTime(), '\x1b[0m', str); - } - }, - warn(str) { - if (['warn', 'info', 'log'].includes(config.log_level)) { - console.log('\x1b[33m', 'warn|' + fullTime(), '\x1b[0m', str); - infoStr.write(`\n ` + 'warn|' + fullTime() + '|' + str); - } - }, - info(str) { - if (['info', 'log'].includes(config.log_level)) { - console.log('\x1b[32m', 'info|' + fullTime(), '\x1b[0m', str); - infoStr.write(`\n ` + 'info|' + fullTime() + '|' + str); - } - }, - log(str) { - if (['log'].includes(config.log_level)) { - console.log('\x1b[34m', 'log|' + fullTime(), '\x1b[0m', str); - infoStr.write(`\n ` + 'log|[' + fullTime() + '|' + str); - } - }, -}; - -function time() { - return formatDate(Date.now()).hh_mm_ss; -} - -function date() { - return formatDate(Date.now()).YYYY_MM_DD; -} - -function fullTime() { - return date() + ' ' + time(); -} - -/** - * Formats unix timestamp to string - * @param {number} timestamp Timestamp to format - * @return {object} Contains different formatted strings - */ -function formatDate(timestamp) { - if (!timestamp) return false; - const formattedDate = {}; - const dateObject = new Date(timestamp); - formattedDate.year = dateObject.getFullYear(); - formattedDate.month = ('0' + (dateObject.getMonth() + 1)).slice(-2); - formattedDate.date = ('0' + dateObject.getDate()).slice(-2); - formattedDate.hours = ('0' + dateObject.getHours()).slice(-2); - formattedDate.minutes = ('0' + dateObject.getMinutes()).slice(-2); - formattedDate.seconds = ('0' + dateObject.getSeconds()).slice(-2); - formattedDate.YYYY_MM_DD = formattedDate.year + '-' + formattedDate.month + '-' + formattedDate.date; - formattedDate.YYYY_MM_DD_hh_mm = formattedDate.year + '-' + formattedDate.month + '-' + formattedDate.date + ' ' + formattedDate.hours + ':' + formattedDate.minutes; - formattedDate.hh_mm_ss = formattedDate.hours + ':' + formattedDate.minutes + ':' + formattedDate.seconds; - return formattedDate; -} \ No newline at end of file +const config = require('../modules/configReader'); + +const fs = require('fs'); +if (!fs.existsSync('./logs')) { + fs.mkdirSync('./logs'); +} + +const infoStr = fs.createWriteStream('./logs/' + date() + '.log', { + flags: 'a', +}); + +infoStr.write(`\n\n[The bot started] _________________${fullTime()}_________________\n`); + +module.exports = { + error(str) { + if (['error', 'warn', 'info', 'log'].includes(config.log_level)) { + infoStr.write(`\n ` + 'error|' + fullTime() + '|' + str); + console.log('\x1b[31m', 'error|' + fullTime(), '\x1b[0m', str); + } + }, + warn(str) { + if (['warn', 'info', 'log'].includes(config.log_level)) { + console.log('\x1b[33m', 'warn|' + fullTime(), '\x1b[0m', str); + infoStr.write(`\n ` + 'warn|' + fullTime() + '|' + str); + } + }, + info(str) { + if (['info', 'log'].includes(config.log_level)) { + console.log('\x1b[32m', 'info|' + fullTime(), '\x1b[0m', str); + infoStr.write(`\n ` + 'info|' + fullTime() + '|' + str); + } + }, + log(str) { + if (['log'].includes(config.log_level)) { + console.log('\x1b[34m', 'log|' + fullTime(), '\x1b[0m', str); + infoStr.write(`\n ` + 'log|[' + fullTime() + '|' + str); + } + }, +}; + +function time() { + return formatDate(Date.now()).hh_mm_ss; +} + +function date() { + return formatDate(Date.now()).YYYY_MM_DD; +} + +function fullTime() { + return date() + ' ' + time(); +} + +/** + * Formats unix timestamp to string + * @param {number} timestamp Timestamp to format + * @return {object} Contains different formatted strings + */ +function formatDate(timestamp) { + if (!timestamp) return false; + const formattedDate = {}; + const dateObject = new Date(timestamp); + formattedDate.year = dateObject.getFullYear(); + formattedDate.month = ('0' + (dateObject.getMonth() + 1)).slice(-2); + formattedDate.date = ('0' + dateObject.getDate()).slice(-2); + formattedDate.hours = ('0' + dateObject.getHours()).slice(-2); + formattedDate.minutes = ('0' + dateObject.getMinutes()).slice(-2); + formattedDate.seconds = ('0' + dateObject.getSeconds()).slice(-2); + formattedDate.YYYY_MM_DD = formattedDate.year + '-' + formattedDate.month + '-' + formattedDate.date; + formattedDate.YYYY_MM_DD_hh_mm = formattedDate.year + '-' + formattedDate.month + '-' + formattedDate.date + ' ' + formattedDate.hours + ':' + formattedDate.minutes; + formattedDate.hh_mm_ss = formattedDate.hours + ':' + formattedDate.minutes + ':' + formattedDate.seconds; + return formattedDate; +} diff --git a/helpers/notify.js b/helpers/notify.js index d5b7fb3..6bdc287 100644 --- a/helpers/notify.js +++ b/helpers/notify.js @@ -1,83 +1,77 @@ -const axios = require('axios'); -const config = require('../modules/configReader'); -const log = require('./log'); -const api = require('../modules/api'); -const { - adamant_notify, - slack, -} = config; - -module.exports = (message, type, silent_mode = false) => { - - try { - - log[type](removeMarkdown(message)); - - if (!silent_mode) { - - if (!slack && !adamant_notify) { - return; - } - let color; - switch (type) { - case ('error'): - color = '#FF0000'; - break; - case ('warn'): - color = '#FFFF00'; - break; - case ('info'): - color = '#00FF00'; - break; - case ('log'): - color = '#FFFFFF'; - break; - } - - const params = { - 'attachments': [{ - 'fallback': message, - 'color': color, - 'text': makeBoldForSlack(message), - 'mrkdwn_in': ['text'], - }], - }; - - if (slack && slack.length > 34) { - axios.post(slack, params) - .catch(function(error) { - log.log(`Request to Slack with message ${message} failed. ${error}.`); - }); - } - if (adamant_notify && adamant_notify.length > 5 && adamant_notify.startsWith('U') && config.passPhrase && config.passPhrase.length > 30) { - const mdMessage = makeBoldForMarkdown(message); - api.send(config.passPhrase, adamant_notify, `${type}| ${mdMessage}`, 'message'); - } - - } - - } catch (e) { - log.error('Notifier error: ' + e); - } - -}; - -function removeMarkdown(text) { - return doubleAsterisksToSingle(text).replace(/([_*]\b|\b[_*])/g, ''); -} - -function doubleAsterisksToSingle(text) { - return text.replace(/(\*\*\b|\b\*\*)/g, '*'); -} - -function singleAsteriskToDouble(text) { - return text.replace(/(\*\b|\b\*)/g, '**'); -} - -function makeBoldForMarkdown(text) { - return singleAsteriskToDouble(doubleAsterisksToSingle(text)); -} - -function makeBoldForSlack(text) { - return doubleAsterisksToSingle(text); -} +const axios = require('axios'); +const config = require('../modules/configReader'); +const log = require('./log'); +const api = require('../modules/api'); +const { + adamant_notify, + slack, +} = config; + +module.exports = (message, type, silent_mode = false) => { + try { + log[type](removeMarkdown(message)); + + if (!silent_mode) { + if (!slack && !adamant_notify) { + return; + } + let color; + switch (type) { + case ('error'): + color = '#FF0000'; + break; + case ('warn'): + color = '#FFFF00'; + break; + case ('info'): + color = '#00FF00'; + break; + case ('log'): + color = '#FFFFFF'; + break; + } + + const params = { + 'attachments': [{ + 'fallback': message, + 'color': color, + 'text': makeBoldForSlack(message), + 'mrkdwn_in': ['text'], + }], + }; + + if (slack && slack.length > 34) { + axios.post(slack, params) + .catch(function(error) { + log.log(`Request to Slack with message ${message} failed. ${error}.`); + }); + } + if (adamant_notify && adamant_notify.length > 5 && adamant_notify.startsWith('U') && config.passPhrase && config.passPhrase.length > 30) { + const mdMessage = makeBoldForMarkdown(message); + api.send(config.passPhrase, adamant_notify, `${type}| ${mdMessage}`, 'message'); + } + } + } catch (e) { + log.error('Notifier error: ' + e); + } +}; + +function removeMarkdown(text) { + return doubleAsterisksToSingle(text).replace(/([_*]\b|\b[_*])/g, ''); +} + +function doubleAsterisksToSingle(text) { + return text.replace(/(\*\*\b|\b\*\*)/g, '*'); +} + +function singleAsteriskToDouble(text) { + return text.replace(/(\*\b|\b\*)/g, '**'); +} + +function makeBoldForMarkdown(text) { + return singleAsteriskToDouble(doubleAsterisksToSingle(text)); +} + +function makeBoldForSlack(text) { + return doubleAsterisksToSingle(text); +} diff --git a/helpers/utils/adm_utils.js b/helpers/utils/adm_utils.js index 8ebc07f..2b5bbe5 100644 --- a/helpers/utils/adm_utils.js +++ b/helpers/utils/adm_utils.js @@ -1,69 +1,69 @@ -const Store = require('../../modules/Store'); -const api = require('../../modules/api'); -const log = require('../log'); -const {SAT} = require('../const'); -const User = Store.user.ADM; - -module.exports = { - get FEE() { - return Store.comissions.ADM; - }, - syncGetTransaction(hash, tx){ - return { - blockNumber: tx.blockId, - hash: tx.id, - sender: tx.senderId, - recipient: tx.recipientId, - amount: +(tx.amount / SAT).toFixed(8) - }; - }, - async getLastBlockNumber(){ - try { - return (await api.get('uri', 'blocks?limit=1')).blocks[0].height; - } catch (e){ - return null; - } - }, - async getTransactionStatus(txid){ - try { - const tx = (await api.get('uri', 'transactions/get?id=' + txid)).transaction; - return { - blockNumber: tx.height, - status: true - }; - } catch (e){ - return null; - } - }, - async send(params) { - try { - const {address, value, comment} = params; - log.log(`Sending ${value} ADM with comment: ${comment}`); - let res; - if (comment){ - res = api.send(User.passPhrase, address, comment, 'message', null, value); - } else { - res = api.send(User.passPhrase, address, value, null, comment); - } - - if (!res) { - return { - success: false - }; - } - return { - success: res.success, - hash: res.transactionId - }; - } catch (e) { - log.error('Error while sending ADM in Utils module: ' + e); - } - }, - async updateBalance() { - try { - User.balance = (await api.get('uri', 'accounts?address=' + User.address)).account.balance / SAT; - } catch (e) { - log.error('Error while getting ADM balance in Utils module: ' + e); - } - } -}; +const Store = require('../../modules/Store'); +const api = require('../../modules/api'); +const log = require('../log'); +const {SAT} = require('../const'); +const User = Store.user.ADM; + +module.exports = { + get FEE() { + return Store.comissions.ADM; + }, + syncGetTransaction(hash, tx) { + return { + blockNumber: tx.blockId, + hash: tx.id, + sender: tx.senderId, + recipient: tx.recipientId, + amount: +(tx.amount / SAT).toFixed(8), + }; + }, + async getLastBlockNumber() { + try { + return (await api.get('uri', 'blocks?limit=1')).blocks[0].height; + } catch (e) { + return null; + } + }, + async getTransactionStatus(txid) { + try { + const tx = (await api.get('uri', 'transactions/get?id=' + txid)).transaction; + return { + blockNumber: tx.height, + status: true, + }; + } catch (e) { + return null; + } + }, + async send(params) { + try { + const {address, value, comment} = params; + log.log(`Sending ${value} ADM with comment: ${comment}`); + let res; + if (comment) { + res = api.send(User.passPhrase, address, comment, 'message', null, value); + } else { + res = api.send(User.passPhrase, address, value, null, comment); + } + + if (!res) { + return { + success: false, + }; + } + return { + success: res.success, + hash: res.transactionId, + }; + } catch (e) { + log.error('Error while sending ADM in Utils module: ' + e); + } + }, + async updateBalance() { + try { + User.balance = (await api.get('uri', 'accounts?address=' + User.address)).account.balance / SAT; + } catch (e) { + log.error('Error while getting ADM balance in Utils module: ' + e); + } + }, +}; diff --git a/helpers/utils/erc20_models.js b/helpers/utils/erc20_models.js index 578f074..cdae268 100644 --- a/helpers/utils/erc20_models.js +++ b/helpers/utils/erc20_models.js @@ -1,14 +1,14 @@ -module.exports = { - USDS: { - sat: 1000000, // round (6 decimals) - sc: '0xa4bdb11dc0a2bec88d24a3aa1e6bb17201112ebe' - }, - RES: { - sat: 100000, // round (5 decimals) - sc: '0x0a9f693fce6f00a51a8e0db4351b5a8078b4242e' - }, - BZ: { - sat: 1000000000000000000, // round (18 decimals) - sc: '0x4375e7ad8a01b8ec3ed041399f62d9cd120e0063' - } -}; \ No newline at end of file +module.exports = { + USDS: { + sat: 1000000, // round (6 decimals) + sc: '0xa4bdb11dc0a2bec88d24a3aa1e6bb17201112ebe', + }, + RES: { + sat: 100000, // round (5 decimals) + sc: '0x0a9f693fce6f00a51a8e0db4351b5a8078b4242e', + }, + BZ: { + sat: 1000000000000000000, // round (18 decimals) + sc: '0x4375e7ad8a01b8ec3ed041399f62d9cd120e0063', + }, +}; diff --git a/helpers/utils/erc20_utils.js b/helpers/utils/erc20_utils.js index 9ab37f1..81b63a2 100644 --- a/helpers/utils/erc20_utils.js +++ b/helpers/utils/erc20_utils.js @@ -1,299 +1,298 @@ -const Store = require('../../modules/Store'); -const log = require('../../helpers/log'); -const models = require('./erc20_models'); -const config = require('./../../modules/configReader'); -const eth = require('./eth_utils'); -const $u = require('./index'); - -class erc20 { - constructor(token) { - this.token = token; - this.model = models[token]; - this.User = Store.user[token] = JSON.parse(JSON.stringify(Store.user.ETH)); - this.address = this.User.address; - const {web3} = Store; - this.web3 = web3; - this.contract = new web3.eth.Contract(abiArray, this.model.sc, {from: this.User.address}); - $u[token] = this; - log.log(`Created ERC-20 token: ${token}`); - this.updateBalance(); - } - async updateBalance() { - try { - this.User.balance = ((await this.contract.methods.balanceOf(this.User.address).call()) || 0) / this.model.sat; - } catch (e){ - log.error('Error while updating ' + this.token + ' balance: ' + e); - } - } - async send(params) { - const amount = (params.value * this.model.sat).toFixed(0); - const transfer = { - address: this.model.sc, - data: this.contract.methods.transfer(params.address, amount).encodeABI() - }; - return await eth.send(params, transfer); - } - - async getLastBlockNumber() { - return await eth.getLastBlockNumber(); - } - - async syncGetTransaction(hash) { - return new Promise(resolve => { - this.web3.eth.getTransactionReceipt(hash, (err, tx) => { - try { - if (err || !tx.logs) { - resolve(false); - return; - } - const info = tx.logs[0]; - resolve({ - blockNumber: tx.blockNumber, - hash: hash, - sender: tx.from, - recipient: info.topics[2].replace('000000000000000000000000', ''), - contract: tx.to, - amount: +info.data / this.model.sat - }); - } catch (e) { - resolve(false); - } - }); - }); - } - - async getTransactionStatus(txid) { - return await eth.getTransactionStatus(txid); - } - - get FEE() { - let inEth = eth.FEE * 2; - return inEth - } - - get FEEinToken() { - let inEth = eth.FEE * 2; - let inToken = inEth * Store.mathEqual('ETH', this.token, 1, true).exchangePrice; - return inToken - } - -} - -const abiArray = [{ - 'constant': true, - 'inputs': [], - 'name': 'name', - 'outputs': [{ - 'name': '', - 'type': 'string' - }], - 'payable': false, - 'stateMutability': 'view', - 'type': 'function' -}, { - 'constant': false, - 'inputs': [{ - 'name': '_spender', - 'type': 'address' - }, { - 'name': '_value', - 'type': 'uint256' - }], - 'name': 'approve', - 'outputs': [{ - 'name': '', - 'type': 'bool' - }], - 'payable': false, - 'stateMutability': 'nonpayable', - 'type': 'function' -}, { - 'constant': true, - 'inputs': [], - 'name': 'totalSupply', - 'outputs': [{ - 'name': '', - 'type': 'uint256' - }], - 'payable': false, - 'stateMutability': 'view', - 'type': 'function' -}, { - 'constant': false, - 'inputs': [{ - 'name': '_from', - 'type': 'address' - }, { - 'name': '_to', - 'type': 'address' - }, { - 'name': '_value', - 'type': 'uint256' - }], - 'name': 'transferFrom', - 'outputs': [{ - 'name': '', - 'type': 'bool' - }], - 'payable': false, - 'stateMutability': 'nonpayable', - 'type': 'function' -}, { - 'constant': true, - 'inputs': [], - 'name': 'INITIAL_SUPPLY', - 'outputs': [{ - 'name': '', - 'type': 'uint256' - }], - 'payable': false, - 'stateMutability': 'view', - 'type': 'function' -}, { - 'constant': true, - 'inputs': [], - 'name': 'decimals', - 'outputs': [{ - 'name': '', - 'type': 'uint8' - }], - 'payable': false, - 'stateMutability': 'view', - 'type': 'function' -}, { - 'constant': false, - 'inputs': [{ - 'name': '_spender', - 'type': 'address' - }, { - 'name': '_subtractedValue', - 'type': 'uint256' - }], - 'name': 'decreaseApproval', - 'outputs': [{ - 'name': '', - 'type': 'bool' - }], - 'payable': false, - 'stateMutability': 'nonpayable', - 'type': 'function' -}, { - 'constant': true, - 'inputs': [{ - 'name': '_owner', - 'type': 'address' - }], - 'name': 'balanceOf', - 'outputs': [{ - 'name': 'balance', - 'type': 'uint256' - }], - 'payable': false, - 'stateMutability': 'view', - 'type': 'function' -}, { - 'constant': true, - 'inputs': [], - 'name': 'symbol', - 'outputs': [{ - 'name': '', - 'type': 'string' - }], - 'payable': false, - 'stateMutability': 'view', - 'type': 'function' -}, { - 'constant': false, - 'inputs': [{ - 'name': '_to', - 'type': 'address' - }, { - 'name': '_value', - 'type': 'uint256' - }], - 'name': 'transfer', - 'outputs': [{ - 'name': '', - 'type': 'bool' - }], - 'payable': false, - 'stateMutability': 'nonpayable', - 'type': 'function' -}, { - 'constant': false, - 'inputs': [{ - 'name': '_spender', - 'type': 'address' - }, { - 'name': '_addedValue', - 'type': 'uint256' - }], - 'name': 'increaseApproval', - 'outputs': [{ - 'name': '', - 'type': 'bool' - }], - 'payable': false, - 'stateMutability': 'nonpayable', - 'type': 'function' -}, { - 'constant': true, - 'inputs': [{ - 'name': '_owner', - 'type': 'address' - }, { - 'name': '_spender', - 'type': 'address' - }], - 'name': 'allowance', - 'outputs': [{ - 'name': '', - 'type': 'uint256' - }], - 'payable': false, - 'stateMutability': 'view', - 'type': 'function' -}, { - 'inputs': [], - 'payable': false, - 'stateMutability': 'nonpayable', - 'type': 'constructor' -}, { - 'anonymous': false, - 'inputs': [{ - 'indexed': true, - 'name': 'owner', - 'type': 'address' - }, { - 'indexed': true, - 'name': 'spender', - 'type': 'address' - }, { - 'indexed': false, - 'name': 'value', - 'type': 'uint256' - }], - 'name': 'Approval', - 'type': 'event' -}, { - 'anonymous': false, - 'inputs': [{ - 'indexed': true, - 'name': 'from', - 'type': 'address' - }, { - 'indexed': true, - 'name': 'to', - 'type': 'address' - }, { - 'indexed': false, - 'name': 'value', - 'type': 'uint256' - }], - 'name': 'Transfer', - 'type': 'event' -}]; - -config.erc20.forEach(async t=> { // Create all of ERC-20 tokens - new erc20(t); -}); +const Store = require('../../modules/Store'); +const log = require('../../helpers/log'); +const models = require('./erc20_models'); +const config = require('./../../modules/configReader'); +const eth = require('./eth_utils'); +const $u = require('./index'); + +class Erc20 { + constructor(token) { + this.token = token; + this.model = models[token]; + this.User = Store.user[token] = JSON.parse(JSON.stringify(Store.user.ETH)); + this.address = this.User.address; + const {web3} = Store; + this.web3 = web3; + this.contract = new web3.eth.Contract(abiArray, this.model.sc, {from: this.User.address}); + $u[token] = this; + log.log(`Created ERC-20 token: ${token}`); + this.updateBalance(); + } + async updateBalance() { + try { + this.User.balance = ((await this.contract.methods.balanceOf(this.User.address).call()) || 0) / this.model.sat; + } catch (e) { + log.error('Error while updating ' + this.token + ' balance: ' + e); + } + } + async send(params) { + const amount = (params.value * this.model.sat).toFixed(0); + const transfer = { + address: this.model.sc, + data: this.contract.methods.transfer(params.address, amount).encodeABI(), + }; + return await eth.send(params, transfer); + } + + async getLastBlockNumber() { + return await eth.getLastBlockNumber(); + } + + async syncGetTransaction(hash) { + return new Promise((resolve) => { + this.web3.eth.getTransactionReceipt(hash, (err, tx) => { + try { + if (err || !tx.logs) { + resolve(false); + return; + } + const info = tx.logs[0]; + resolve({ + blockNumber: tx.blockNumber, + hash: hash, + sender: tx.from, + recipient: info.topics[2].replace('000000000000000000000000', ''), + contract: tx.to, + amount: +info.data / this.model.sat, + }); + } catch (e) { + resolve(false); + } + }); + }); + } + + async getTransactionStatus(txid) { + return await eth.getTransactionStatus(txid); + } + + get FEE() { + const inEth = eth.FEE * 2; + return inEth; + } + + get FEEinToken() { + const inEth = eth.FEE * 2; + const inToken = inEth * Store.mathEqual('ETH', this.token, 1, true).exchangePrice; + return inToken; + } +} + +const abiArray = [{ + 'constant': true, + 'inputs': [], + 'name': 'name', + 'outputs': [{ + 'name': '', + 'type': 'string', + }], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function', +}, { + 'constant': false, + 'inputs': [{ + 'name': '_spender', + 'type': 'address', + }, { + 'name': '_value', + 'type': 'uint256', + }], + 'name': 'approve', + 'outputs': [{ + 'name': '', + 'type': 'bool', + }], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function', +}, { + 'constant': true, + 'inputs': [], + 'name': 'totalSupply', + 'outputs': [{ + 'name': '', + 'type': 'uint256', + }], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function', +}, { + 'constant': false, + 'inputs': [{ + 'name': '_from', + 'type': 'address', + }, { + 'name': '_to', + 'type': 'address', + }, { + 'name': '_value', + 'type': 'uint256', + }], + 'name': 'transferFrom', + 'outputs': [{ + 'name': '', + 'type': 'bool', + }], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function', +}, { + 'constant': true, + 'inputs': [], + 'name': 'INITIAL_SUPPLY', + 'outputs': [{ + 'name': '', + 'type': 'uint256', + }], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function', +}, { + 'constant': true, + 'inputs': [], + 'name': 'decimals', + 'outputs': [{ + 'name': '', + 'type': 'uint8', + }], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function', +}, { + 'constant': false, + 'inputs': [{ + 'name': '_spender', + 'type': 'address', + }, { + 'name': '_subtractedValue', + 'type': 'uint256', + }], + 'name': 'decreaseApproval', + 'outputs': [{ + 'name': '', + 'type': 'bool', + }], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function', +}, { + 'constant': true, + 'inputs': [{ + 'name': '_owner', + 'type': 'address', + }], + 'name': 'balanceOf', + 'outputs': [{ + 'name': 'balance', + 'type': 'uint256', + }], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function', +}, { + 'constant': true, + 'inputs': [], + 'name': 'symbol', + 'outputs': [{ + 'name': '', + 'type': 'string', + }], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function', +}, { + 'constant': false, + 'inputs': [{ + 'name': '_to', + 'type': 'address', + }, { + 'name': '_value', + 'type': 'uint256', + }], + 'name': 'transfer', + 'outputs': [{ + 'name': '', + 'type': 'bool', + }], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function', +}, { + 'constant': false, + 'inputs': [{ + 'name': '_spender', + 'type': 'address', + }, { + 'name': '_addedValue', + 'type': 'uint256', + }], + 'name': 'increaseApproval', + 'outputs': [{ + 'name': '', + 'type': 'bool', + }], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function', +}, { + 'constant': true, + 'inputs': [{ + 'name': '_owner', + 'type': 'address', + }, { + 'name': '_spender', + 'type': 'address', + }], + 'name': 'allowance', + 'outputs': [{ + 'name': '', + 'type': 'uint256', + }], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function', +}, { + 'inputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'constructor', +}, { + 'anonymous': false, + 'inputs': [{ + 'indexed': true, + 'name': 'owner', + 'type': 'address', + }, { + 'indexed': true, + 'name': 'spender', + 'type': 'address', + }, { + 'indexed': false, + 'name': 'value', + 'type': 'uint256', + }], + 'name': 'Approval', + 'type': 'event', +}, { + 'anonymous': false, + 'inputs': [{ + 'indexed': true, + 'name': 'from', + 'type': 'address', + }, { + 'indexed': true, + 'name': 'to', + 'type': 'address', + }, { + 'indexed': false, + 'name': 'value', + 'type': 'uint256', + }], + 'name': 'Transfer', + 'type': 'event', +}]; + +config.erc20.forEach(async (t)=> { // Create all of ERC-20 tokens + new Erc20(t); +}); diff --git a/helpers/utils/eth_utils.js b/helpers/utils/eth_utils.js index 7c43516..7a1875c 100644 --- a/helpers/utils/eth_utils.js +++ b/helpers/utils/eth_utils.js @@ -1,158 +1,159 @@ -const config = require('../../modules/configReader'); -const log = require('../log'); -const Web3 = require('web3'); -const web3 = new Web3(config.node_ETH[0]);// TODO: health check -const {eth} = web3; -const Store = require('../../modules/Store'); -const EthereumTx = require('ethereumjs-tx').Transaction; -const ethSat = 1000000000000000000; -const User = Store.user.ETH; -eth.defaultAccount = User.address; -eth.defaultBlock = 'latest'; -const privateKey = Buffer.from( - User.privateKey.replace('0x', ''), - 'hex', -); - -Store.web3 = web3; -module.exports = { - syncGetTransaction(hash) { - return new Promise(resolve => { - eth.getTransaction(hash, (err, tx) => { - if (err) { - resolve(null); - } else { - resolve({ - blockNumber: tx.blockNumber, - hash: tx.hash, - sender: tx.from, - recipient: tx.to, - amount: +(tx.value / ethSat).toFixed(8) - }); - } - }).catch(e=> { - log.warn(`Error while getting Tx ${hash} (if Tx is new, just wait). ${e}`); - }); - }); - }, - getTransactionStatus(hash) { - return new Promise(resolve => { - eth.getTransactionReceipt(hash, (err, tx) => { - if (err || !tx) { - resolve(null); - } else { - resolve({ - blockNumber: tx.blockNumber, - status: tx.status - }); - } - }).catch(e=> { - log.error(`Error while getting Tx ${hash} (if Tx is new, just wait). ${e}`); - }); - }); - }, - getLastBlockNumber() { - return new Promise(resolve => { - eth.getBlock('latest').then(block => { - if (block) { - resolve(block.number); - } else { - resolve(null); - } - }).catch(e=>{ - log.error('Error while getting ETH last block: ' + e); - }); - }); - }, - updateGasPrice() { - return new Promise(resolve => { - eth.getGasPrice().then(price => { - if (price) { - this.gasPrice = web3.utils.toHex(price); - } - resolve(); - }).catch(e=>{ - log.error('Error while updating Ether gas price: ' + e); - }); - }); - }, - updateBalance(){ - eth.getBalance(User.address).then(balance => { - if (balance){ - User.balance = balance / ethSat; - } - }).catch(e=>{ - log.error('Error while updating ETH balance: ' + e); - }); - }, - get FEE() { - return this.gasPrice * 28000 / ethSat; - }, - getNonce() { - return new Promise(resolve => { - eth.getTransactionCount(User.address).then(nonce => { - this.currentNonce = nonce; - resolve(nonce); - }).catch(e =>{ - log.error('Error while updating ETH nonce: ' + e); - setTimeout(()=>{ - this.getNonce(); - }, 2000); - }); - }); - }, - async send(params, contract) { - try { - const txParams = { - nonce: this.currentNonce++, - gasPrice: this.gasPrice, - gas: web3.utils.toHex(28000), // Use default gasLimit value - to: params.address, - value: params.value * ethSat - }; - if (contract) { // ERC20 - txParams.value = '0x0'; - txParams.data = contract.data; - txParams.to = contract.address; - txParams.gas *= 2; // ERC20 transactions consumes more gas - } - - const tx = new EthereumTx(txParams); - tx.sign(privateKey); - const serializedTx = '0x' + tx.serialize().toString('hex'); - return new Promise(resolve => { - eth.sendSignedTransaction(serializedTx) - .on('transactionHash', (hash) => { - resolve({ - success: true, - hash - }); - }).on('error', (error) => { // If out of gas error, the second parameter is the receipt - resolve({ - success: false, - error - }); - }).catch(e => { - if (!e.toString().includes('Failed to check for transaction receipt')) // Known bug that after Tx sent successfully, this error occurred anyway https://github.com/ethereum/web3.js/issues/3145 - log.error('Error while sending ETH tx: ' + e); - }); - }); - } catch (e) { - log.error('Error while executing Ethereum transaction: ' + e); - } - }, - lastNonce: 0, -}; - -// Init -module.exports.updateGasPrice(); -module.exports.updateBalance(); -module.exports.getNonce(); - -setInterval(() => { - module.exports.updateGasPrice(); -}, 10 * 1000); - -setInterval(() => { - module.exports.updateBalance(); -}, 60 * 1000); +const config = require('../../modules/configReader'); +const log = require('../log'); +const Web3 = require('web3'); +const web3 = new Web3(config.node_ETH[0]);// TODO: health check +const {eth} = web3; +const Store = require('../../modules/Store'); +const EthereumTx = require('ethereumjs-tx').Transaction; +const ethSat = 1000000000000000000; +const User = Store.user.ETH; +eth.defaultAccount = User.address; +eth.defaultBlock = 'latest'; +const privateKey = Buffer.from( + User.privateKey.replace('0x', ''), + 'hex', +); + +Store.web3 = web3; +module.exports = { + syncGetTransaction(hash) { + return new Promise((resolve) => { + eth.getTransaction(hash, (err, tx) => { + if (err) { + resolve(null); + } else { + resolve({ + blockNumber: tx.blockNumber, + hash: tx.hash, + sender: tx.from, + recipient: tx.to, + amount: +(tx.value / ethSat).toFixed(8), + }); + } + }).catch((e)=> { + log.warn(`Error while getting Tx ${hash} (if Tx is new, just wait). ${e}`); + }); + }); + }, + getTransactionStatus(hash) { + return new Promise((resolve) => { + eth.getTransactionReceipt(hash, (err, tx) => { + if (err || !tx) { + resolve(null); + } else { + resolve({ + blockNumber: tx.blockNumber, + status: tx.status, + }); + } + }).catch((e)=> { + log.error(`Error while getting Tx ${hash} (if Tx is new, just wait). ${e}`); + }); + }); + }, + getLastBlockNumber() { + return new Promise((resolve) => { + eth.getBlock('latest').then((block) => { + if (block) { + resolve(block.number); + } else { + resolve(null); + } + }).catch((e)=>{ + log.error('Error while getting ETH last block: ' + e); + }); + }); + }, + updateGasPrice() { + return new Promise((resolve) => { + eth.getGasPrice().then((price) => { + if (price) { + this.gasPrice = web3.utils.toHex(price); + } + resolve(); + }).catch((e)=>{ + log.error('Error while updating Ether gas price: ' + e); + }); + }); + }, + updateBalance() { + eth.getBalance(User.address).then((balance) => { + if (balance) { + User.balance = balance / ethSat; + } + }).catch((e)=>{ + log.error('Error while updating ETH balance: ' + e); + }); + }, + get FEE() { + return this.gasPrice * 28000 / ethSat; + }, + getNonce() { + return new Promise((resolve) => { + eth.getTransactionCount(User.address).then((nonce) => { + this.currentNonce = nonce; + resolve(nonce); + }).catch((e) =>{ + log.error('Error while updating ETH nonce: ' + e); + setTimeout(()=>{ + this.getNonce(); + }, 2000); + }); + }); + }, + async send(params, contract) { + try { + const txParams = { + nonce: this.currentNonce++, + gasPrice: this.gasPrice, + gas: web3.utils.toHex(28000), // Use default gasLimit value + to: params.address, + value: params.value * ethSat, + }; + if (contract) { // ERC20 + txParams.value = '0x0'; + txParams.data = contract.data; + txParams.to = contract.address; + txParams.gas *= 2; // ERC20 transactions consumes more gas + } + + const tx = new EthereumTx(txParams); + tx.sign(privateKey); + const serializedTx = '0x' + tx.serialize().toString('hex'); + return new Promise((resolve) => { + eth.sendSignedTransaction(serializedTx) + .on('transactionHash', (hash) => { + resolve({ + success: true, + hash, + }); + }).on('error', (error) => { // If out of gas error, the second parameter is the receipt + resolve({ + success: false, + error, + }); + }).catch((e) => { + if (!e.toString().includes('Failed to check for transaction receipt')) { // Known bug that after Tx sent successfully, this error occurred anyway https://github.com/ethereum/web3.js/issues/3145 + log.error('Error while sending ETH tx: ' + e); + } + }); + }); + } catch (e) { + log.error('Error while executing Ethereum transaction: ' + e); + } + }, + lastNonce: 0, +}; + +// Init +module.exports.updateGasPrice(); +module.exports.updateBalance(); +module.exports.getNonce(); + +setInterval(() => { + module.exports.updateGasPrice(); +}, 10 * 1000); + +setInterval(() => { + module.exports.updateBalance(); +}, 60 * 1000); diff --git a/helpers/utils/index.js b/helpers/utils/index.js index 3b1d04d..cfd46ef 100644 --- a/helpers/utils/index.js +++ b/helpers/utils/index.js @@ -1,217 +1,229 @@ -const api = require('../../modules/api'); -const config = require('../../modules/configReader'); -const eth_utils = require('./eth_utils'); -const adm_utils = require('./adm_utils'); -const log = require('../log'); -const db = require('../../modules/DB'); -const Store = require('../../modules/Store'); -const constants = require('../const'); - -module.exports = { - unix() { - return new Date().getTime(); - }, - /** - * Converts provided `time` to ADAMANTS's epoch timestamp: in seconds starting from Sep 02 2017 17:00:00 GMT+0000 - * @param {number=} time timestamp to convert - * @returns {number} - */ - epochTime(time) { - if (!time) { - time = Date.now() - } - return Math.floor((time - constants.EPOCH) / 1000) - }, - /** - * Converts ADM epoch timestamp to a Unix timestamp - * @param {number} epochTime timestamp to convert - * @returns {number} - */ - toTimestamp(epochTime) { - return epochTime * 1000 + constants.EPOCH - }, - sendAdmMsg(address, msg, type = 'message') { - if (msg && !config.isDev || true) { - try { - return api.send(config.passPhrase, address, msg, type).success || false; - } catch (e) { - return false; - } - } - }, - thousandSeparator(num, doBold) { - var parts = (num + '').split('.'), - main = parts[0], - len = main.length, - output = '', - i = len - 1; - - while (i >= 0) { - output = main.charAt(i) + output; - if ((len - i) % 3 === 0 && i > 0) { - output = ' ' + output; - } - --i; - } - - if (parts.length > 1) { - if (doBold) { - output = `**${output}**.${parts[1]}`; - } else { - output = `${output}.${parts[1]}`; - } - } - return output; - }, - async getAddressCryptoFromAdmAddressADM(coin, admAddress) { - try { - if (this.isERC20(coin)) { - coin = 'ETH'; - } - const resp = await api.syncGet(`/api/states/get?senderId=${admAddress}&key=${coin.toLowerCase()}:address`); - if (resp && resp.success) { - if (resp.transactions.length) { - return resp.transactions[0].asset.state.value; - } else { - return 'none'; - }; - }; - } catch (e) { - log.error(' in getAddressCryptoFromAdmAddressADM(): ' + e); - return null; - } - }, - async userDailyValue(senderId) { - return (await db.paymentsDb.find({ - transactionIsValid: true, - senderId: senderId, - needToSendBack: false, - inAmountMessageUsd: {$ne: null}, - date: {$gt: (this.unix() - 24 * 3600 * 1000)} // last 24h - })).reduce((r, c) => { - return +r + +c.inAmountMessageUsd; - }, 0); - }, - async updateAllBalances() { - try { - await this.ETH.updateBalance(); - await this.ADM.updateBalance(); - for (const t of config.erc20){ - await this[t].updateBalance(); - } - } catch (e){} - }, - async getLastBlocksNumbers() { - const data = { - ETH: await this.ETH.getLastBlockNumber(), - ADM: await this.ADM.getLastBlockNumber(), - }; - for (const t of config.erc20) { - // data[t] = await this[t].getLastBlockNumber(); // Don't do unnecessary requests - data[t] = data['ETH']; - } - return data; - }, - isKnown(coin) { - return config.known_crypto.includes(coin); - }, - isAccepted(coin) { - return config.accepted_crypto.includes(coin); - }, - isExchanged(coin) { - return config.exchange_crypto.includes(coin); - }, - isFiat(coin) { - return ['USD', 'RUB', 'EUR', 'CNY', 'JPY'].includes(coin); - }, - isHasTicker(coin) { // if coin has ticker like COIN/OTHERCOIN or OTHERCOIN/COIN - const pairs = Object.keys(Store.currencies).toString(); - return pairs.includes(',' + coin + '/') || pairs.includes('/' + coin); - }, - isERC20(coin) { - return config.erc20.includes(coin.toUpperCase()); - }, - isArraysEqual(array1, array2) { - return array1.length === array2.length && array1.sort().every(function(value, index) { return value === array2.sort()[index]}); - }, - getAccounts(message) { - let userAccounts = {}; - userAccounts.notEmpty = false; - - userAccounts.twitterLink = this.findLink(message, 'twitter.com'); - if (userAccounts.twitterLink) - userAccounts.twitterAccount = this.parseTwitterAccountFromLink(userAccounts.twitterLink); - else - userAccounts.twitterAccount = this.findTwitterAccount(message); - - userAccounts.facebookLink = this.findLink(message, 'facebook.com'); - - if (userAccounts.twitterAccount && config.isTwitterCampaign) - userAccounts.notEmpty = true; - if (userAccounts.facebookAccount && config.isFacebookCampaign) - userAccounts.notEmpty = true; - return userAccounts; - }, - findTwitterAccount(message) { - let pattern = /(?<=^|(?<=[^a-zA-Z0-9-_\.]))@([A-Za-z]+[A-Za-z0-9-_]+)/gi; - let accounts = message.match(pattern); - if (accounts) - return accounts[0].toLowerCase(); - }, - findLink(message, link) { - let kLINK_DETECTION_REGEX = /(([a-z]+:\/\/)?(([a-z0-9\-]+\.)+([a-z]{2}|aero|arpa|biz|com|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|local|internal))(:[0-9]{1,5})?(\/[a-z0-9_\-\.~]+)*(\/([a-z0-9_\-\.]*)(\?[a-z0-9+_\-\.%=&]*)?)?(#[a-zA-Z0-9!$&'()*+.=-_~:@/?]*)?)(\s+|$)/gi; - let links = message.match(kLINK_DETECTION_REGEX); - let found = ''; - if (links) - for (let i = 0; i < links.length; i++) { - if (links[i].includes(link)) - found = links[i]; - } - return found.trim().toLowerCase(); - }, - trimChar(s, mask) { - while (~mask.indexOf(s[0])) { - s = s.slice(1); - } - while (~mask.indexOf(s[s.length - 1])) { - s = s.slice(0, -1); - } - return s; - }, - getTwitterScreenName(account) { - return this.trimChar(account, "@").toLowerCase(); - }, - parseTwitterAccountFromLink(link) { - link = this.trimChar(link, "/"); - let n = link.lastIndexOf("/"); - if (n === -1) - return '' - else - return '@' + link.substring(n + 1).toLowerCase(); - }, - getTweetIdFromLink(link) { - link = this.trimChar(link, "/"); - let n = link.lastIndexOf("/"); - if (n === -1) - return '' - else - return link.substring(n + 1); - }, - getTwitterHashtags(tags) { - for (let i = 0; i < tags.length; i++) { - tags[i] = this.trimChar(tags[i], "#"); - } - return tags; - }, - getModuleName(id) { - let n = id.lastIndexOf("\\"); - if (n === -1) - n = id.lastIndexOf("/"); - if (n === -1) - return '' - else - return id.substring(n + 1); - }, - ETH: eth_utils, - ADM: adm_utils, -}; +const api = require('../../modules/api'); +const config = require('../../modules/configReader'); +const eth_utils = require('./eth_utils'); +const adm_utils = require('./adm_utils'); +const log = require('../log'); +const db = require('../../modules/DB'); +const Store = require('../../modules/Store'); +const constants = require('../const'); + +module.exports = { + unix() { + return new Date().getTime(); + }, + /** + * Converts provided `time` to ADAMANTS's epoch timestamp: in seconds starting from Sep 02 2017 17:00:00 GMT+0000 + * @param {number=} time timestamp to convert + * @return {number} + */ + epochTime(time) { + if (!time) { + time = Date.now(); + } + return Math.floor((time - constants.EPOCH) / 1000); + }, + /** + * Converts ADM epoch timestamp to a Unix timestamp + * @param {number} epochTime timestamp to convert + * @return {number} + */ + toTimestamp(epochTime) { + return epochTime * 1000 + constants.EPOCH; + }, + sendAdmMsg(address, msg, type = 'message') { + if (msg && !config.isDev) { + try { + return api.send(config.passPhrase, address, msg, type).success || false; + } catch (e) { + return false; + } + } + }, + thousandSeparator(num, doBold) { + const parts = (num + '').split('.'); + const main = parts[0]; + const len = main.length; + let output = ''; + let i = len - 1; + + while (i >= 0) { + output = main.charAt(i) + output; + if ((len - i) % 3 === 0 && i > 0) { + output = ' ' + output; + } + --i; + } + + if (parts.length > 1) { + if (doBold) { + output = `**${output}**.${parts[1]}`; + } else { + output = `${output}.${parts[1]}`; + } + } + return output; + }, + async getAddressCryptoFromAdmAddressADM(coin, admAddress) { + try { + if (this.isERC20(coin)) { + coin = 'ETH'; + } + const resp = await api.syncGet(`/api/states/get?senderId=${admAddress}&key=${coin.toLowerCase()}:address`); + if (resp && resp.success) { + if (resp.transactions.length) { + return resp.transactions[0].asset.state.value; + } else { + return 'none'; + } + } + } catch (e) { + log.error(' in getAddressCryptoFromAdmAddressADM(): ' + e); + return null; + } + }, + async userDailyValue(senderId) { + return (await db.PaymentsDb.find({ + transactionIsValid: true, + senderId: senderId, + needToSendBack: false, + inAmountMessageUsd: {$ne: null}, + date: {$gt: (this.unix() - 24 * 3600 * 1000)}, // last 24h + })).reduce((r, c) => { + return +r + +c.inAmountMessageUsd; + }, 0); + }, + async updateAllBalances() { + try { + await this.ETH.updateBalance(); + await this.ADM.updateBalance(); + for (const t of config.erc20) { + await this[t].updateBalance(); + } + } catch (e) {} + }, + async getLastBlocksNumbers() { + const data = { + ETH: await this.ETH.getLastBlockNumber(), + ADM: await this.ADM.getLastBlockNumber(), + }; + for (const t of config.erc20) { + // data[t] = await this[t].getLastBlockNumber(); // Don't do unnecessary requests + data[t] = data['ETH']; + } + return data; + }, + isKnown(coin) { + return config.known_crypto.includes(coin); + }, + isAccepted(coin) { + return config.accepted_crypto.includes(coin); + }, + isExchanged(coin) { + return config.exchange_crypto.includes(coin); + }, + isFiat(coin) { + return ['USD', 'RUB', 'EUR', 'CNY', 'JPY'].includes(coin); + }, + isHasTicker(coin) { // if coin has ticker like COIN/OTHERCOIN or OTHERCOIN/COIN + const pairs = Object.keys(Store.currencies).toString(); + return pairs.includes(',' + coin + '/') || pairs.includes('/' + coin); + }, + isERC20(coin) { + return config.erc20.includes(coin.toUpperCase()); + }, + isArraysEqual(array1, array2) { + return array1.length === array2.length && array1.sort().every(function(value, index) { + return value === array2.sort()[index]; + }); + }, + getAccounts(message) { + const userAccounts = {}; + userAccounts.notEmpty = false; + + userAccounts.twitterLink = this.findLink(message, 'twitter.com'); + if (userAccounts.twitterLink) { + userAccounts.twitterAccount = this.parseTwitterAccountFromLink(userAccounts.twitterLink); + } else { + userAccounts.twitterAccount = this.findTwitterAccount(message); + } + + userAccounts.facebookLink = this.findLink(message, 'facebook.com'); + + if (userAccounts.twitterAccount && config.isTwitterCampaign) { + userAccounts.notEmpty = true; + } + if (userAccounts.facebookAccount && config.isFacebookCampaign) { + userAccounts.notEmpty = true; + } + return userAccounts; + }, + findTwitterAccount(message) { + const pattern = /(?<=^|(?<=[^a-zA-Z0-9-_.]))@([A-Za-z]+[A-Za-z0-9-_]+)/gi; + const accounts = message.match(pattern); + if (accounts) { + return accounts[0].toLowerCase(); + } + }, + findLink(message, link) { + const kLINK_DETECTION_REGEX = /(([a-z]+:\/\/)?(([a-z0-9-]+\.)+([a-z]{2}|aero|arpa|biz|com|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|local|internal))(:[0-9]{1,5})?(\/[a-z0-9_\-.~]+)*(\/([a-z0-9_\-.]*)(\?[a-z0-9+_\-.%=&]*)?)?(#[a-zA-Z0-9!$&'()*+.=-_~:@/?]*)?)(\s+|$)/gi; + const links = message.match(kLINK_DETECTION_REGEX); + let found = ''; + if (links) { + for (let i = 0; i < links.length; i++) { + if (links[i].includes(link)) { + found = links[i]; + } + } + } + return found.trim().toLowerCase(); + }, + trimChar(s, mask) { + while (~mask.indexOf(s[0])) { + s = s.slice(1); + } + while (~mask.indexOf(s[s.length - 1])) { + s = s.slice(0, -1); + } + return s; + }, + getTwitterScreenName(account) { + return this.trimChar(account, '@').toLowerCase(); + }, + parseTwitterAccountFromLink(link) { + link = this.trimChar(link, '/'); + const n = link.lastIndexOf('/'); + if (n === -1) { + return ''; + } else { + return '@' + link.substring(n + 1).toLowerCase(); + } + }, + getTweetIdFromLink(link) { + link = this.trimChar(link, '/'); + const n = link.lastIndexOf('/'); + if (n === -1) { + return ''; + } else { + return link.substring(n + 1); + } + }, + getTwitterHashtags(tags) { + for (let i = 0; i < tags.length; i++) { + tags[i] = this.trimChar(tags[i], '#'); + } + return tags; + }, + getModuleName(id) { + let n = id.lastIndexOf('\\'); + if (n === -1) { + n = id.lastIndexOf('/'); + } + if (n === -1) { + return ''; + } else { + return id.substring(n + 1); + } + }, + ETH: eth_utils, + ADM: adm_utils, +}; diff --git a/modules/DB.js b/modules/DB.js index 4375054..e430a69 100644 --- a/modules/DB.js +++ b/modules/DB.js @@ -1,19 +1,19 @@ -const MongoClient = require("mongodb").MongoClient; -const mongoClient = new MongoClient("mongodb://localhost:27017/", {useNewUrlParser: true, useUnifiedTopology: true}); -const model = require('../helpers/dbModel'); - -const collections = {}; - -mongoClient.connect((err, client) => { - if (err) { - throw (err); - } - const db = client.db("bountybotdb"); - collections.db = db; - collections.systemDb = model(db.collection("systems")); - collections.incomingTxsDb = model(db.collection("incomingtxs")); - collections.usersDb = model(db.collection("users")); - collections.paymentsDb = model(db.collection("payments")); -}); - -module.exports = collections; +const MongoClient = require('mongodb').MongoClient; +const mongoClient = new MongoClient('mongodb://localhost:27017/', {useNewUrlParser: true, useUnifiedTopology: true}); +const model = require('../helpers/dbModel'); + +const collections = {}; + +mongoClient.connect((err, client) => { + if (err) { + throw (err); + } + const db = client.db('bountybotdb'); + collections.db = db; + collections.systemDb = model(db.collection('systems')); + collections.IncomingTxsDb = model(db.collection('incomingtxs')); + collections.UsersDb = model(db.collection('users')); + collections.PaymentsDb = model(db.collection('payments')); +}); + +module.exports = collections; diff --git a/modules/Store.js b/modules/Store.js index e7fb782..b3ebe4f 100644 --- a/modules/Store.js +++ b/modules/Store.js @@ -1,100 +1,100 @@ -const db = require('./DB'); -const log = require('../helpers/log'); -const keys = require('adamant-api/helpers/keys'); -const api = require('./api'); -const {version} = require('../package.json'); -const config = require('./configReader'); - -// ADM data -const AdmKeysPair = keys.createKeypairFromPassPhrase(config.passPhrase); -const AdmAddress = keys.createAddressFromPublicKey(AdmKeysPair.publicKey); -// ETH data -const ethData = api.eth.keys(config.passPhrase); - -module.exports = { - version, - botName: AdmAddress, - user: { - ADM: { - passPhrase: config.passPhrase, - keysPair: AdmKeysPair, - address: AdmAddress - }, - ETH: { - address: ethData.address, - privateKey: ethData.privateKey, - } - }, - comissions: { - ADM: 0.5 // This is a stub. Ether fee returned with FEE() method in separate module - }, - lastBlock: null, - get lastHeight() { - return this.lastBlock && this.lastBlock.height || false; - }, - updateSystem(field, data) { - const $set = {}; - $set[field] = data; - db.systemDb.db.updateOne({}, {$set}, {upsert: true}); - this[field] = data; - }, - async updateLastBlock() { - try { - const lastBlock = (await api.get('uri', 'blocks')).blocks[0]; - this.updateSystem('lastBlock', lastBlock); - } catch (e) { - log.error('Error while updating lastBlock: ' + e); - } - }, - async updateCurrencies(){ - try { - const data = await api.syncGet(config.infoservice + '/get', true); - if (data.success){ - this.currencies = data.result; - } - } catch (e){ - log.error('Error while updating currencies: ' + e); - }; - }, - getPrice(from, to){ - try { - from = from.toUpperCase(); - to = to.toUpperCase(); - let price = + (this.currencies[from + '/' + to] || 1 / this.currencies[to + '/' + from] || 0).toFixed(8); - if (price){ - return price; - } - const priceFrom = +(this.currencies[from + '/USD']); - const priceTo = +(this.currencies[to + '/USD']); - return +(priceFrom / priceTo || 1).toFixed(8); - } catch (e){ - log.error('Error while calculating getPrice(): ', e); - return 0; - } - }, - mathEqual(from, to, amount, doNotAccountFees){ - let price = this.getPrice(from, to); - if (!doNotAccountFees){ - price *= (100 - config['exchange_fee_' + from]) / 100; - }; - if (!price){ - return { - outAmount: 0, - exchangePrice: 0 - }; - } - price = +price.toFixed(8); - return { - outAmount: +(price * amount).toFixed(8), - exchangePrice: price - }; - } -}; - -config.notifyName = `${config.bot_name} (${module.exports.botName})`; -module.exports.updateCurrencies(); - -setInterval(() => { - module.exports.updateCurrencies(); -}, 60 * 1000); - +const db = require('./DB'); +const log = require('../helpers/log'); +const keys = require('adamant-api/helpers/keys'); +const api = require('./api'); +const {version} = require('../package.json'); +const config = require('./configReader'); + +// ADM data +const AdmKeysPair = keys.createKeypairFromPassPhrase(config.passPhrase); +const AdmAddress = keys.createAddressFromPublicKey(AdmKeysPair.publicKey); +// ETH data +const ethData = api.eth.keys(config.passPhrase); + +module.exports = { + version, + botName: AdmAddress, + user: { + ADM: { + passPhrase: config.passPhrase, + keysPair: AdmKeysPair, + address: AdmAddress, + }, + ETH: { + address: ethData.address, + privateKey: ethData.privateKey, + }, + }, + comissions: { + ADM: 0.5, // This is a stub. Ether fee returned with FEE() method in separate module + }, + lastBlock: null, + get lastHeight() { + return this.lastBlock && this.lastBlock.height || false; + }, + updateSystem(field, data) { + const $set = {}; + $set[field] = data; + db.systemDb.db.updateOne({}, {$set}, {upsert: true}); + this[field] = data; + }, + async updateLastBlock() { + try { + const lastBlock = (await api.get('uri', 'blocks')).blocks[0]; + this.updateSystem('lastBlock', lastBlock); + } catch (e) { + log.error('Error while updating lastBlock: ' + e); + } + }, + async updateCurrencies() { + try { + const data = await api.syncGet(config.infoservice + '/get', true); + if (data.success) { + this.currencies = data.result; + } + } catch (e) { + log.error('Error while updating currencies: ' + e); + } + }, + getPrice(from, to) { + try { + from = from.toUpperCase(); + to = to.toUpperCase(); + const price = + (this.currencies[from + '/' + to] || 1 / this.currencies[to + '/' + from] || 0).toFixed(8); + if (price) { + return price; + } + const priceFrom = +(this.currencies[from + '/USD']); + const priceTo = +(this.currencies[to + '/USD']); + return +(priceFrom / priceTo || 1).toFixed(8); + } catch (e) { + log.error('Error while calculating getPrice(): ', e); + return 0; + } + }, + mathEqual(from, to, amount, doNotAccountFees) { + let price = this.getPrice(from, to); + if (!doNotAccountFees) { + price *= (100 - config['exchange_fee_' + from]) / 100; + } + if (!price) { + return { + outAmount: 0, + exchangePrice: 0, + }; + } + price = +price.toFixed(8); + return { + outAmount: +(price * amount).toFixed(8), + exchangePrice: price, + }; + }, +}; + +config.notifyName = `${config.bot_name} (${module.exports.botName})`; +module.exports.updateCurrencies(); + +setInterval(() => { + module.exports.updateCurrencies(); +}, 60 * 1000); + diff --git a/modules/api.js b/modules/api.js index 8c07e63..4dabf63 100644 --- a/modules/api.js +++ b/modules/api.js @@ -1,3 +1,3 @@ -const log = require('../helpers/log'); -const config = require('./configReader'); -module.exports = require('adamant-api')({passPhrase: config.passPhrase, node: config.node_ADM, logLevel: 'warn'}, log); +const log = require('../helpers/log'); +const config = require('./configReader'); +module.exports = require('adamant-api')({passPhrase: config.passPhrase, node: config.node_ADM, logLevel: 'warn'}, log); diff --git a/modules/apiTester.js b/modules/apiTester.js index b404f5d..ebbeee3 100644 --- a/modules/apiTester.js +++ b/modules/apiTester.js @@ -4,21 +4,21 @@ const notify = require('../helpers/notify'); const twitterapi = require('./twitterapi'); async function testTwitterAPI() { - let testResult = await twitterapi.testApi(); - let output; - if (testResult.success) { - output = "Twitter API functions well."; - log.info(output); - } else { - output = `Error while making Twitter API request: ${testResult.message}`; - output += "\n"; - output += `Make sure Twitter didn't block your API credentials. If so, you need to manually login into your Twitter account and solve captcha.`; - notify(output, 'error'); - } + const testResult = await twitterapi.testApi(); + let output; + if (testResult.success) { + output = 'Twitter API functions well.'; + log.info(output); + } else { + output = `Error while making Twitter API request: ${testResult.message}`; + output += '\n'; + output += `Make sure Twitter didn't block your API credentials. If so, you need to manually login into your Twitter account and solve captcha.`; + notify(output, 'error'); + } } if (config.twitter_api_test_interval) { - setInterval(() => { - testTwitterAPI(); - }, config.twitter_api_test_interval * 60 * 1000); + setInterval(() => { + testTwitterAPI(); + }, config.twitter_api_test_interval * 60 * 1000); } diff --git a/modules/checkAdamantContacts.js b/modules/checkAdamantContacts.js index 447ba8c..35656ef 100644 --- a/modules/checkAdamantContacts.js +++ b/modules/checkAdamantContacts.js @@ -2,123 +2,110 @@ const db = require('./DB'); const config = require('./configReader'); const $u = require('../helpers/utils'); const log = require('../helpers/log'); -const notify = require('../helpers/notify'); const api = require('./api'); const Store = require('./Store'); const hours_for_new_contact = 72; const excluded_adm_contacts = [ - "U5149447931090026688", // Exchange bot - "U17840858470710371662", // Bet bot - "U16009208918899676597", // Info bot - "U564927595221219641", // Heads or Tails bot - "U8916295525136600565", // Orel i Reshka bot - "U13524411455927458124", // BlackJack - "U1058290473014173876", // Crypto alarm bot - "U15423595369615486571", // Adoption and bounty - "U17636520927910270607", // ADAMANT Contact - "U6386412615727665758", // ADAMANT Contact - "U1835325601873095435", // ADAMANT Foundation Adoption - "U380651761819723095", // ADAMANT Foundation Donation - Store.user.ADM.address // This bot address -] + 'U5149447931090026688', // Exchange bot + 'U17840858470710371662', // Bet bot + 'U16009208918899676597', // Info bot + 'U564927595221219641', // Heads or Tails bot + 'U8916295525136600565', // Orel i Reshka bot + 'U13524411455927458124', // BlackJack + 'U1058290473014173876', // Crypto alarm bot + 'U15423595369615486571', // Adoption and bounty + 'U17636520927910270607', // ADAMANT Contact + 'U6386412615727665758', // ADAMANT Contact + 'U1835325601873095435', // ADAMANT Foundation Adoption + 'U380651761819723095', // ADAMANT Foundation Donation + Store.user.ADM.address, // This bot address +]; let inProcess = false; module.exports = async () => { - - if (inProcess) return; - inProcess = true; - - try { - - const {usersDb} = db; - - const users = await usersDb.find({ - $and: [ - {isInCheck: true}, - {isAdamantCheckPassed: false}, - {isTasksCompleted: false} - ] - }); - - for (const user of users) { - try { - const { - userId - } = user; - - log.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); - - let msgNotify = ''; - let msgNotifyType = ''; - let msgSendBack = ''; - - let isContactsDone; - let contacts_number = 0; - - let txChat_all = (await api.get('uri', 'chats/get/?recipientId=' + userId + '&orderBy=timestamp:desc&limit=100')).transactions; - let txChat = txChat_all.filter((v,i,a)=>a.findIndex(t=>(t.senderId === v.senderId))===i); - - let contact_firstchat; - let hours_old; - let need_new_contacts = false; - - for (let index = 0; ((index < txChat.length) && (contacts_number < config.adamant_campaign.min_contacts)); index++) { - if (excluded_adm_contacts.includes(txChat[index].senderId)) { - continue; - } - contact_firstchat = (await api.get('uri', 'chats/get/?senderId=' + txChat[index].senderId + '&orderBy=timestamp:asc&limit=1')); - contact_firstchat = contact_firstchat.transactions[0].timestamp; - hours_old = ($u.unix() - $u.toTimestamp(contact_firstchat)) / 1000 / 60 / 60; - if (hours_old > hours_for_new_contact) { - need_new_contacts = true; - continue; - } - contacts_number += 1; - } - - isContactsDone = contacts_number >= config.adamant_campaign.min_contacts; - console.log('isContactsDone:', isContactsDone, contacts_number); - - if (isContactsDone) { - - log.log(`User ${userId}… did make ${config.adamant_campaign.min_contacts} contacts.`); - await user.update({ - isAdamantCheckPassed: true - }, true); - - } else { - - await user.update({ - isAdamantCheckPassed: false, - isInCheck: false, - isTasksCompleted: false - }, true); - - if (need_new_contacts) { - msgSendBack = `To meet the Bounty campaign rules, you should invite ${config.adamant_campaign.min_contacts} friends in ADAMANT Messenger. They _should be new ADAMANT users_ and message you. They can join this bounty campaign as well! Invite friends and apply again.`; - } else { - msgSendBack = `To meet the Bounty campaign rules, you should invite ${config.adamant_campaign.min_contacts} friends in ADAMANT Messenger. They must message you. They can join this bounty campaign as well! Invite friends and apply again.`; - } - - await $u.sendAdmMsg(userId, msgSendBack); - log.log(`User ${userId}… did NOT make ${config.adamant_campaign.min_contacts} contacts. Message to user: ${msgSendBack}`); - } - - } catch (e) { - log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); - } - - }; - - } finally { - inProcess = false; + if (inProcess) return; + inProcess = true; + + try { + const {UsersDb} = db; + + const users = await UsersDb.find({ + $and: [ + {isInCheck: true}, + {isAdamantCheckPassed: false}, + {isTasksCompleted: false}, + ], + }); + + for (const user of users) { + try { + const { + userId, + } = user; + + log.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); + + let msgSendBack = ''; + let contacts_number = 0; + + const txChat_all = (await api.get('uri', 'chats/get/?recipientId=' + userId + '&orderBy=timestamp:desc&limit=100')).transactions; + const txChat = txChat_all.filter((v, i, a)=>a.findIndex((t)=>(t.senderId === v.senderId))===i); + + let contact_firstchat; + let hours_old; + let need_new_contacts = false; + + for (let index = 0; ((index < txChat.length) && (contacts_number < config.adamant_campaign.min_contacts)); index++) { + if (excluded_adm_contacts.includes(txChat[index].senderId)) { + continue; + } + contact_firstchat = (await api.get('uri', 'chats/get/?senderId=' + txChat[index].senderId + '&orderBy=timestamp:asc&limit=1')); + contact_firstchat = contact_firstchat.transactions[0].timestamp; + hours_old = ($u.unix() - $u.toTimestamp(contact_firstchat)) / 1000 / 60 / 60; + if (hours_old > hours_for_new_contact) { + need_new_contacts = true; + continue; + } + contacts_number += 1; + } + + const isContactsDone = contacts_number >= config.adamant_campaign.min_contacts; + console.log('isContactsDone:', isContactsDone, contacts_number); + + if (isContactsDone) { + log.log(`User ${userId}… did make ${config.adamant_campaign.min_contacts} contacts.`); + await user.update({ + isAdamantCheckPassed: true, + }, true); + } else { + await user.update({ + isAdamantCheckPassed: false, + isInCheck: false, + isTasksCompleted: false, + }, true); + + if (need_new_contacts) { + msgSendBack = `To meet the Bounty campaign rules, you should invite ${config.adamant_campaign.min_contacts} friends in ADAMANT Messenger. They _should be new ADAMANT users_ and message you. They can join this bounty campaign as well! Invite friends and apply again.`; + } else { + msgSendBack = `To meet the Bounty campaign rules, you should invite ${config.adamant_campaign.min_contacts} friends in ADAMANT Messenger. They must message you. They can join this bounty campaign as well! Invite friends and apply again.`; + } + + await $u.sendAdmMsg(userId, msgSendBack); + log.log(`User ${userId}… did NOT make ${config.adamant_campaign.min_contacts} contacts. Message to user: ${msgSendBack}`); + } + } catch (e) { + log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); + } } - + } finally { + inProcess = false; + } }; -if (config.adamant_campaign.min_contacts > 0) - setInterval(() => { - module.exports(); - }, 6 * 1000); +if (config.adamant_campaign.min_contacts > 0) { + setInterval(() => { + module.exports(); + }, 6 * 1000); +} diff --git a/modules/checkAll.js b/modules/checkAll.js index 0f84bf0..146fe52 100644 --- a/modules/checkAll.js +++ b/modules/checkAll.js @@ -1,99 +1,98 @@ -const db = require('./DB'); -const config = require('./configReader'); -const $u = require('../helpers/utils'); -const log = require('../helpers/log'); -const notify = require('../helpers/notify'); -const mathjs = require('mathjs'); - -let inProcess = false; - -module.exports = async () => { - - if (inProcess) return; - inProcess = true; - - try { - - const {usersDb, paymentsDb} = db; - - const users = await usersDb.find({ - isInCheck: true, - isTasksCompleted: false - }); - - for (const user of users) { - try { - const { - userId, - isTwitterFollowCheckPassed, - isTwitterRetweetCommentCheckPassed, - isAdamantCheckPassed, - twitterAccount, - twitterFollowers, - twitterLifetimeDays - } = user; - - log.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); - - let msgSendBack = ''; - - if (((config.twitter_follow.length === 0) || isTwitterFollowCheckPassed) - && ((config.twitter_retweet_w_comment.length === 0) || isTwitterRetweetCommentCheckPassed) - && ((config.adamant_campaign.min_contacts === 0) || isAdamantCheckPassed)) { - await user.update({ - isInCheck: false, - isTasksCompleted: true - }, true); - config.rewards.forEach(reward => { - let amount = reward.amount; - if (config.rewards_progression_from_twitter_followers[reward.currency] && twitterFollowers) { - let followersCount = twitterFollowers; - if (followersCount > config.rewards_progression_from_twitter_followers[reward.currency].limit_followers) - followersCount = config.rewards_progression_from_twitter_followers[reward.currency].limit_followers; - let f = config.rewards_progression_from_twitter_followers[reward.currency].func; - amount = mathjs.evaluate(f, {followers: followersCount}); - amount = +amount.toFixed(config.rewards_progression_from_twitter_followers[reward.currency].decimals_transfer); - } - payment = new paymentsDb({ - date: $u.unix(), - userId, - isPayed: false, - isFinished: false, - outCurrency: reward.currency, - outAmount: amount, - outTxid: null, - outAddress: null - }); - payment.save(); - }) - if (config.notifyTasksCompleted) { - let twitterString = ''; - if (twitterAccount) - twitterString += ` (${twitterAccount}`; - if (twitterFollowers) - twitterString += `, followers: ${twitterFollowers}`; - if (twitterLifetimeDays) - twitterString += `, lifetime days: ${Math.round(twitterLifetimeDays)}`; - if (twitterAccount) - twitterString += `)`; - notify(`${config.notifyName}: User ${userId}${twitterString} completed the Bounty tasks. Payouts are pending.`, 'log'); - } - msgSendBack = `Great, you've completed all the tasks! Reward is coming right now!`; - $u.sendAdmMsg(userId, msgSendBack); - } - - } catch (e) { - log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); - } - - }; - - } finally { - inProcess = false; - } - -}; - -setInterval(() => { - module.exports(); -}, 7 * 1000); +const db = require('./DB'); +const config = require('./configReader'); +const $u = require('../helpers/utils'); +const log = require('../helpers/log'); +const notify = require('../helpers/notify'); +const mathjs = require('mathjs'); + +let inProcess = false; + +module.exports = async () => { + if (inProcess) return; + inProcess = true; + + try { + const {UsersDb, PaymentsDb} = db; + + const users = await UsersDb.find({ + isInCheck: true, + isTasksCompleted: false, + }); + + for (const user of users) { + try { + const { + userId, + isTwitterFollowCheckPassed, + isTwitterRetweetCommentCheckPassed, + isAdamantCheckPassed, + twitterAccount, + twitterFollowers, + twitterLifetimeDays, + } = user; + + log.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); + + let msgSendBack = ''; + + if (((config.twitter_follow.length === 0) || isTwitterFollowCheckPassed) && + ((config.twitter_retweet_w_comment.length === 0) || isTwitterRetweetCommentCheckPassed) && + ((config.adamant_campaign.min_contacts === 0) || isAdamantCheckPassed)) { + await user.update({ + isInCheck: false, + isTasksCompleted: true, + }, true); + config.rewards.forEach((reward) => { + let amount = reward.amount; + if (config.rewards_progression_from_twitter_followers[reward.currency] && twitterFollowers) { + let followersCount = twitterFollowers; + if (followersCount > config.rewards_progression_from_twitter_followers[reward.currency].limit_followers) { + followersCount = config.rewards_progression_from_twitter_followers[reward.currency].limit_followers; + } + const f = config.rewards_progression_from_twitter_followers[reward.currency].func; + amount = mathjs.evaluate(f, {followers: followersCount}); + amount = +amount.toFixed(config.rewards_progression_from_twitter_followers[reward.currency].decimals_transfer); + } + const payment = new PaymentsDb({ + date: $u.unix(), + userId, + isPayed: false, + isFinished: false, + outCurrency: reward.currency, + outAmount: amount, + outTxid: null, + outAddress: null, + }); + payment.save(); + }); + if (config.notifyTasksCompleted) { + let twitterString = ''; + if (twitterAccount) { + twitterString += ` (${twitterAccount}`; + } + if (twitterFollowers) { + twitterString += `, followers: ${twitterFollowers}`; + } + if (twitterLifetimeDays) { + twitterString += `, lifetime days: ${Math.round(twitterLifetimeDays)}`; + } + if (twitterAccount) { + twitterString += `)`; + } + notify(`${config.notifyName}: User ${userId}${twitterString} completed the Bounty tasks. Payouts are pending.`, 'log'); + } + msgSendBack = `Great, you've completed all the tasks! Reward is coming right now!`; + $u.sendAdmMsg(userId, msgSendBack); + } + } catch (e) { + log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); + } + } + } finally { + inProcess = false; + } +}; + +setInterval(() => { + module.exports(); +}, 7 * 1000); diff --git a/modules/checkTwitterFollow.js b/modules/checkTwitterFollow.js index 0629ff8..78e845a 100644 --- a/modules/checkTwitterFollow.js +++ b/modules/checkTwitterFollow.js @@ -1,87 +1,77 @@ -const db = require('./DB'); -const config = require('./configReader'); -const $u = require('../helpers/utils'); -const log = require('../helpers/log'); -const notify = require('../helpers/notify'); -const twitterapi = require('./twitterapi'); - -let inProcess = false; - -module.exports = async () => { - - if (inProcess) return; - inProcess = true; - - try { - - const {usersDb} = db; - - const users = await usersDb.find({ - $and: [ - {isInCheck: true}, - {isTwitterAccountEligible: true}, - {isTwitterFollowCheckPassed: false}, - {isTasksCompleted: false}, - {$or: [ - {isAdamantCheckPassed: true}, - {$expr: {$eq: [0, config.adamant_campaign.min_contacts]}} - ]} - ] - }); - - for (const user of users) { - try { - const { - twitterAccount, - userId - } = user; - - log.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); - - let msgNotify = ''; - let msgNotifyType = ''; - let msgSendBack = ''; - - let followAccount; - let isFollowing; - for (let index = 0; index < config.twitter_follow.length; index++) { - followAccount = config.twitter_follow[index]; - isFollowing = await twitterapi.checkIfAccountFollowing(twitterAccount, followAccount); - console.log('isFollowing:', isFollowing); - - if (isFollowing) { - log.log(`User ${userId}… ${twitterAccount} do follows ${followAccount}.`); - - } else { - - await user.update({ - isTwitterFollowCheckPassed: false, - isInCheck: false, - isTasksCompleted: false - }, true); - msgSendBack = `To meet the Bounty campaign rules, you should follow Twitter account ${followAccount}. Then you apply again.`; - await $u.sendAdmMsg(userId, msgSendBack); - log.log(`User ${userId}… ${twitterAccount} do NOT follows ${followAccount}. Message to user: ${msgSendBack}`); - break; - } - } - await user.update({ - isTwitterFollowCheckPassed: isFollowing - }, true); - - } catch (e) { - log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); - } - - }; - - } finally { - inProcess = false; - } - -}; - -if (config.twitter_follow.length > 0) - setInterval(() => { - module.exports(); - }, 10 * 1000); +const db = require('./DB'); +const config = require('./configReader'); +const $u = require('../helpers/utils'); +const log = require('../helpers/log'); +const twitterapi = require('./twitterapi'); + +let inProcess = false; + +module.exports = async () => { + if (inProcess) return; + inProcess = true; + + try { + const {UsersDb} = db; + + const users = await UsersDb.find({ + $and: [ + {isInCheck: true}, + {isTwitterAccountEligible: true}, + {isTwitterFollowCheckPassed: false}, + {isTasksCompleted: false}, + {$or: [ + {isAdamantCheckPassed: true}, + {$expr: {$eq: [0, config.adamant_campaign.min_contacts]}}, + ]}, + ], + }); + + for (const user of users) { + try { + const { + twitterAccount, + userId, + } = user; + + log.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); + + let msgSendBack = ''; + + let followAccount; + let isFollowing; + for (let index = 0; index < config.twitter_follow.length; index++) { + followAccount = config.twitter_follow[index]; + isFollowing = await twitterapi.checkIfAccountFollowing(twitterAccount, followAccount); + console.log('isFollowing:', isFollowing); + + if (isFollowing) { + log.log(`User ${userId}… ${twitterAccount} do follows ${followAccount}.`); + } else { + await user.update({ + isTwitterFollowCheckPassed: false, + isInCheck: false, + isTasksCompleted: false, + }, true); + msgSendBack = `To meet the Bounty campaign rules, you should follow Twitter account ${followAccount}. Then you apply again.`; + await $u.sendAdmMsg(userId, msgSendBack); + log.log(`User ${userId}… ${twitterAccount} do NOT follows ${followAccount}. Message to user: ${msgSendBack}`); + break; + } + } + await user.update({ + isTwitterFollowCheckPassed: isFollowing, + }, true); + } catch (e) { + log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); + } + } + } finally { + inProcess = false; + } +}; + +if (config.twitter_follow.length > 0) { + setInterval(() => { + module.exports(); + }, 10 * 1000); +} diff --git a/modules/checkTwitterReqs.js b/modules/checkTwitterReqs.js index 364e824..5850800 100644 --- a/modules/checkTwitterReqs.js +++ b/modules/checkTwitterReqs.js @@ -2,108 +2,101 @@ const db = require('./DB'); const config = require('./configReader'); const $u = require('../helpers/utils'); const log = require('../helpers/log'); -const notify = require('../helpers/notify'); const twitterapi = require('./twitterapi'); let inProcess = false; module.exports = async () => { - - if (inProcess) return; - inProcess = true; - - try { - - const {usersDb} = db; - - const users = await usersDb.find({ - $and: [ - {isInCheck: true}, - {isTwitterAccountEligible: false}, - {isTasksCompleted: false}, - {$or: [ - {isAdamantCheckPassed: true}, - {$expr: {$eq: [0, config.adamant_campaign.min_contacts]}} - ]} - ] - }); - - for (const user of users) { - try { - const { - twitterAccount, - userId - } = user; - - log.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); - - let msgSendBack = ''; - let result, isEligible = true; - let twitterAccountIdStr = null; - - result = await twitterapi.checkIfAccountEligible(twitterAccount); - - if (result.error === 'request_failed') { - return; // If request to Twitter API failed, ignore and check next time - } - - if (result.error === 'user_not_found') { - msgSendBack = `It seems Twitter account ${twitterAccount} does not exist. Please re-check and try again.` - } else { - // Check if this user already participated in Bounty campaign. - // He may change Twitter's AccountName (screen name) to cheat, but Id will be the same - twitterAccountIdStr = result.accountInfo.id_str; - let userDuplicate = await usersDb.findOne({twitterAccountId: twitterAccountIdStr}); - if (userDuplicate && (userDuplicate.twitterAccount !== twitterAccount) && (userDuplicate.isInCheck || userDuplicate.isTasksCompleted)) { - // This user changed his AccountName (screen name) - isEligible = false; - msgSendBack = `This Twitter account is already in use by other participant with other account name: ${userDuplicate.twitterAccount}. Cheating detected. If it's a mistake, try again in a few minutes.`; - } - } - - if (config.doCheckTwitterReqs) { - isEligible = isEligible && result.success; - } else { - isEligible = isEligible && true; - } - - if (isEligible) { - log.log(`User ${userId}… ${twitterAccount} is eligible.`); - - } else { - - await user.update({ - isInCheck: false, - isTasksCompleted: false - }, true); - - if (msgSendBack === '') - msgSendBack = `To meet the Bounty campaign rules, your Twitter account ${config.twitterEligibleString}.`; - - await $u.sendAdmMsg(userId, msgSendBack); - log.log(`User ${userId}… ${twitterAccount} is NOT eligible. Message to user: ${msgSendBack}`); - } - - await user.update({ - isTwitterAccountEligible: isEligible, - twitterAccountId: twitterAccountIdStr, - twitterLifetimeDays: result.lifetimeDays, - twitterFollowers: result.followers - }, true); - - } catch (e) { - log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); - } - - }; - - } finally { - inProcess = false; + if (inProcess) return; + inProcess = true; + + try { + const {UsersDb} = db; + + const users = await UsersDb.find({ + $and: [ + {isInCheck: true}, + {isTwitterAccountEligible: false}, + {isTasksCompleted: false}, + {$or: [ + {isAdamantCheckPassed: true}, + {$expr: {$eq: [0, config.adamant_campaign.min_contacts]}}, + ]}, + ], + }); + + for (const user of users) { + try { + const { + twitterAccount, + userId, + } = user; + + log.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); + + let msgSendBack = ''; + let isEligible = true; + let twitterAccountIdStr = null; + + const result = await twitterapi.checkIfAccountEligible(twitterAccount); + + if (result.error === 'request_failed') { + return; // If request to Twitter API failed, ignore and check next time + } + + if (result.error === 'user_not_found') { + msgSendBack = `It seems Twitter account ${twitterAccount} does not exist. Please re-check and try again.`; + } else { + // Check if this user already participated in Bounty campaign. + // He may change Twitter's AccountName (screen name) to cheat, but Id will be the same + twitterAccountIdStr = result.accountInfo.id_str; + const userDuplicate = await UsersDb.findOne({twitterAccountId: twitterAccountIdStr}); + if (userDuplicate && (userDuplicate.twitterAccount !== twitterAccount) && (userDuplicate.isInCheck || userDuplicate.isTasksCompleted)) { + // This user changed his AccountName (screen name) + isEligible = false; + msgSendBack = `This Twitter account is already in use by other participant with other account name: ${userDuplicate.twitterAccount}. Cheating detected. If it's a mistake, try again in a few minutes.`; + } + } + + if (config.doCheckTwitterReqs) { + isEligible = isEligible && result.success; + } else { + isEligible = isEligible && true; + } + + if (isEligible) { + log.log(`User ${userId}… ${twitterAccount} is eligible.`); + } else { + await user.update({ + isInCheck: false, + isTasksCompleted: false, + }, true); + + if (msgSendBack === '') { + msgSendBack = `To meet the Bounty campaign rules, your Twitter account ${config.twitterEligibleString}.`; + } + + await $u.sendAdmMsg(userId, msgSendBack); + log.log(`User ${userId}… ${twitterAccount} is NOT eligible. Message to user: ${msgSendBack}`); + } + + await user.update({ + isTwitterAccountEligible: isEligible, + twitterAccountId: twitterAccountIdStr, + twitterLifetimeDays: result.lifetimeDays, + twitterFollowers: result.followers, + }, true); + } catch (e) { + log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); + } } - + } finally { + inProcess = false; + } }; -if (config.isTwitterCampaign) - setInterval(() => { - module.exports(); - }, 9 * 1000); +if (config.isTwitterCampaign) { + setInterval(() => { + module.exports(); + }, 9 * 1000); +} diff --git a/modules/checkTwitterRetweet.js b/modules/checkTwitterRetweet.js index 9e57c00..2d22587 100644 --- a/modules/checkTwitterRetweet.js +++ b/modules/checkTwitterRetweet.js @@ -2,144 +2,134 @@ const db = require('./DB'); const config = require('./configReader'); const $u = require('../helpers/utils'); const log = require('../helpers/log'); -const notify = require('../helpers/notify'); const twitterapi = require('./twitterapi'); let inProcess = false; module.exports = async () => { - - if (inProcess) return; - inProcess = true; - - try { - - const {usersDb} = db; - - // Called strictly after isTwitterFollowCheckPassed = true to eliminate userDb collisions - const users = await usersDb.find({ - $and: [ - {isInCheck: true}, - {isTwitterAccountEligible: true}, - {isTwitterRetweetCommentCheckPassed: false}, - {$or: [ - {isTwitterFollowCheckPassed: true}, - {$expr: {$eq: [0, config.twitter_follow.length]}} - ]}, - {isTasksCompleted: false}, - {$or: [ - {isAdamantCheckPassed: true}, - {$expr: {$eq: [0, config.adamant_campaign.min_contacts]}} - ]} - ] - }); - - for (const user of users) { - try { - const { - twitterAccount, - userId - } = user; - - log.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); - - let msgNotify = ''; - let msgNotifyType = ''; - let msgSendBack = ''; - - let toRetweet; - let minMentions; - let hashtags; - let retweetResult; - let isRetweeted; - for (let index = 0; index < config.twitter_retweet_w_comment.length; index++) { - toRetweet = config.twitter_retweet_w_comment[index].tweet; - minMentions = config.twitter_retweet_w_comment[index].min_mentions; - hashtags = config.twitter_retweet_w_comment[index].hashtags; - retweetResult = await twitterapi.checkIfAccountRetweetedwComment(twitterAccount, toRetweet, minMentions, hashtags); - isRetweeted = retweetResult.success; - console.log('isRetweeted:', isRetweeted); - - if (isRetweeted) { - log.log(`User ${userId}… ${twitterAccount} did retweet ${toRetweet}.`); - - } else { - - // const friendsExample = ['@elonmusk', '@cz_binance', '@FabriLemus7', '@crypto', '@CryptoWhale']; - // let friendsExampleString = ''; - // for (let index = 0; index < friendsExample.length && index < minMentions; index++) { - // friendsExampleString += friendsExample[index] + ' ' - // } - // friendsExampleString = friendsExampleString.trim(); - - await user.update({ - isTwitterRetweetCommentCheckPassed: false, - isInCheck: false, - isTasksCompleted: false - }, true); - switch (retweetResult.error) { - case ('no_retweet'): - msgSendBack = `To meet the Bounty campaign rules, you should quote (retweet with comment) ${toRetweet}.`; - if (minMentions > 0 && hashtags.length > 0) { - msgSendBack += ` Mention ${minMentions} friends, and use ${config.twitter_retweet_w_comment[index].tag_list} tags.`; - } else { - if (minMentions > 0) { - msgSendBack += ` Mention ${minMentions} friends.`; - } - if (hashtags.length > 0) { - msgSendBack += ` Use ${config.twitter_retweet_w_comment[index].tag_list} tags.`; - } - } - msgSendBack += ` Example: _Meet the ADAMANT blockchain messenger! @elonmusk @cz_binance @FabriLemus7 #privacy #crypto #anonymity #decentralization_`; - msgSendBack += `. Then you apply again.`; - break; - case ('not_enough_mentions'): - msgSendBack = `I see your quote.`; - if (minMentions > 0) { - msgSendBack += ` To meet the Bounty campaign rules, it should mention at least ${minMentions} friends.`; - msgSendBack += ` Example: _Meet the ADAMANT blockchain messenger! @elonmusk @cz_binance @FabriLemus7 #privacy #crypto #anonymity #decentralization_`; - } - msgSendBack += `. Quote once again.`; - break; - case ('no_hashtags'): - msgSendBack = `I see your quote.`; - if (hashtags.length > 0) { - msgSendBack += ` To meet the Bounty campaign rules, it should include ${config.twitter_retweet_w_comment[index].tag_list} tags.`; - msgSendBack += ` Example: _Meet the ADAMANT blockchain messenger! @elonmusk @cz_binance @FabriLemus7 #privacy #crypto #anonymity #decentralization_`; - } - msgSendBack += `. Quote once again.`; - break; - default: - break; - } - - await $u.sendAdmMsg(userId, msgSendBack); - log.log(`User ${userId}… ${twitterAccount} did NOT retweet ${toRetweet}: ${retweetResult.error}. Message to user: ${msgSendBack}`); - - break; - } + if (inProcess) return; + inProcess = true; + + try { + const {UsersDb} = db; + + // Called strictly after isTwitterFollowCheckPassed = true to eliminate userDb collisions + const users = await UsersDb.find({ + $and: [ + {isInCheck: true}, + {isTwitterAccountEligible: true}, + {isTwitterRetweetCommentCheckPassed: false}, + {$or: [ + {isTwitterFollowCheckPassed: true}, + {$expr: {$eq: [0, config.twitter_follow.length]}}, + ]}, + {isTasksCompleted: false}, + {$or: [ + {isAdamantCheckPassed: true}, + {$expr: {$eq: [0, config.adamant_campaign.min_contacts]}}, + ]}, + ], + }); + + for (const user of users) { + try { + const { + twitterAccount, + userId, + } = user; + + log.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); + + let msgSendBack = ''; + + let toRetweet; + let minMentions; + let hashtags; + let retweetResult; + let isRetweeted; + for (let index = 0; index < config.twitter_retweet_w_comment.length; index++) { + toRetweet = config.twitter_retweet_w_comment[index].tweet; + minMentions = config.twitter_retweet_w_comment[index].min_mentions; + hashtags = config.twitter_retweet_w_comment[index].hashtags; + retweetResult = await twitterapi.checkIfAccountRetweetedwComment(twitterAccount, toRetweet, minMentions, hashtags); + isRetweeted = retweetResult.success; + console.log('isRetweeted:', isRetweeted); + + if (isRetweeted) { + log.log(`User ${userId}… ${twitterAccount} did retweet ${toRetweet}.`); + } else { + // const friendsExample = ['@elonmusk', '@cz_binance', '@FabriLemus7', '@crypto', '@CryptoWhale']; + // let friendsExampleString = ''; + // for (let index = 0; index < friendsExample.length && index < minMentions; index++) { + // friendsExampleString += friendsExample[index] + ' ' + // } + // friendsExampleString = friendsExampleString.trim(); + + await user.update({ + isTwitterRetweetCommentCheckPassed: false, + isInCheck: false, + isTasksCompleted: false, + }, true); + switch (retweetResult.error) { + case ('no_retweet'): + msgSendBack = `To meet the Bounty campaign rules, you should quote (retweet with comment) ${toRetweet}.`; + if (minMentions > 0 && hashtags.length > 0) { + msgSendBack += ` Mention ${minMentions} friends, and use ${config.twitter_retweet_w_comment[index].tag_list} tags.`; + } else { + if (minMentions > 0) { + msgSendBack += ` Mention ${minMentions} friends.`; + } + if (hashtags.length > 0) { + msgSendBack += ` Use ${config.twitter_retweet_w_comment[index].tag_list} tags.`; + } + } + msgSendBack += ` Example: _Meet the ADAMANT blockchain messenger! @elonmusk @cz_binance @FabriLemus7 #privacy #crypto #anonymity #decentralization_`; + msgSendBack += `. Then you apply again.`; + break; + case ('not_enough_mentions'): + msgSendBack = `I see your quote.`; + if (minMentions > 0) { + msgSendBack += ` To meet the Bounty campaign rules, it should mention at least ${minMentions} friends.`; + msgSendBack += ` Example: _Meet the ADAMANT blockchain messenger! @elonmusk @cz_binance @FabriLemus7 #privacy #crypto #anonymity #decentralization_`; } - await user.update({ - isTwitterRetweetCommentCheckPassed: isRetweeted - }, true); - - } catch (e) { - log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); + msgSendBack += `. Quote once again.`; + break; + case ('no_hashtags'): + msgSendBack = `I see your quote.`; + if (hashtags.length > 0) { + msgSendBack += ` To meet the Bounty campaign rules, it should include ${config.twitter_retweet_w_comment[index].tag_list} tags.`; + msgSendBack += ` Example: _Meet the ADAMANT blockchain messenger! @elonmusk @cz_binance @FabriLemus7 #privacy #crypto #anonymity #decentralization_`; + } + msgSendBack += `. Quote once again.`; + break; + default: + break; } - - }; - } finally { - inProcess = false; + await $u.sendAdmMsg(userId, msgSendBack); + log.log(`User ${userId}… ${twitterAccount} did NOT retweet ${toRetweet}: ${retweetResult.error}. Message to user: ${msgSendBack}`); + + break; + } + } + await user.update({ + isTwitterRetweetCommentCheckPassed: isRetweeted, + }, true); + } catch (e) { + log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); + } } - + } finally { + inProcess = false; + } }; -if (config.twitter_retweet_w_comment.length > 0) - setInterval(() => { - module.exports(); - }, 11 * 1000); +if (config.twitter_retweet_w_comment.length > 0) { + setInterval(() => { + module.exports(); + }, 11 * 1000); +} setInterval(() => { - module.exports(); + module.exports(); }, 11 * 1000); diff --git a/modules/checkTxs.js b/modules/checkTxs.js index a523511..4bf70c3 100644 --- a/modules/checkTxs.js +++ b/modules/checkTxs.js @@ -1,91 +1,89 @@ - -const db = require('./DB'); -const $u = require('../helpers/utils'); -const log = require('../helpers/log'); - -module.exports = async (itx, tx) => { - - console.log(`Running module ${$u.getModuleName(module.id)}…`); - - const {usersDb} = db; - let user = {}; - let msgSendBack = ''; - - // Exclude duplicate Twitter accounts - user = await usersDb.findOne({twitterAccount: itx.accounts.twitterAccount}); - if (user && (user.isInCheck || user.isTasksCompleted)) { - // This Twitter account is already in use by other user, unable to switch - log.warn(`User ${user.userId} applied with already used Twitter account ${itx.accounts.twitterAccount}. Notify user and ignore.`); - if (user.userId !== tx.senderId) { - msgSendBack = `This Twitter account is already in use by other participant. If it's a mistake, try again in a few minutes.`; - } else { - if (user.isTasksCompleted) { - msgSendBack = `You've already completed the Bounty tasks.`; - } - } - if (msgSendBack) // Do not send anything, if isInCheck - $u.sendAdmMsg(tx.senderId, msgSendBack); - return; - } - - // Check if user apply for check once again - user = await usersDb.findOne({userId: tx.senderId}); - if (user) { - // User is already was in check earlier, update - log.log(`User ${user.userId} applied once again with Twitter account ${itx.accounts.twitterAccount}.`); - // May be later - // if (user.isBountyPayed) { - // msgSendBack = `You've already received the Bounty reward. Thanks for your support!`; - // $u.sendAdmMsg(tx.senderId, msgSendBack); - // return; - // } else - if (user.isTasksCompleted) { - log.log(`User ${user.userId} already completed the Bounty tasks. Notify user and ignore.`); - msgSendBack = `You've already completed the Bounty tasks.`; - $u.sendAdmMsg(tx.senderId, msgSendBack); - return; - } - - user.update({ - dateUpdated: $u.unix(), - admTxId: tx.id, - msg: itx.encrypted_content, - isInCheck: itx.accounts.notEmpty, - twitterAccountLink: itx.accounts.twitterLink, - twitterAccount: itx.accounts.twitterAccount, - twitterAccountId: null, - isTasksCompleted: false, - isTwitterFollowCheckPassed: false, - isTwitterRetweetCommentCheckPassed: false, - isTwitterAccountEligible: false - }); - - } else { - // First time user, create new - log.info(`User ${tx.senderId} applied for a first time with Twitter account ${itx.accounts.twitterAccount}.`); - user = new usersDb({ - _id: tx.senderId, - userId: tx.senderId, - dateCreated: $u.unix(), - dateUpdated: $u.unix(), - admTxId: tx.id, - msg: itx.encrypted_content, - isInCheck: itx.accounts.notEmpty, - twitterAccountLink: itx.accounts.twitterLink, - twitterAccount: itx.accounts.twitterAccount, - twitterAccountId: null, - isTasksCompleted: false, - isTwitterFollowCheckPassed: false, - isTwitterRetweetCommentCheckPassed: false, - isTwitterAccountEligible: false, - isAdamantCheckPassed: false, - }); - } - - await user.save(); - await itx.update({isProcessed: true}, true); - - msgSendBack = `I've got your account details. Twitter: ${user.twitterAccount}. I'll check if you've finished the Bounty tasks now…`; - $u.sendAdmMsg(tx.senderId, msgSendBack); - -}; + +const db = require('./DB'); +const $u = require('../helpers/utils'); +const log = require('../helpers/log'); + +module.exports = async (itx, tx) => { + console.log(`Running module ${$u.getModuleName(module.id)}…`); + + const {UsersDb} = db; + let user = {}; + let msgSendBack = ''; + + // Exclude duplicate Twitter accounts + user = await UsersDb.findOne({twitterAccount: itx.accounts.twitterAccount}); + if (user && (user.isInCheck || user.isTasksCompleted)) { + // This Twitter account is already in use by other user, unable to switch + log.warn(`User ${user.userId} applied with already used Twitter account ${itx.accounts.twitterAccount}. Notify user and ignore.`); + if (user.userId !== tx.senderId) { + msgSendBack = `This Twitter account is already in use by other participant. If it's a mistake, try again in a few minutes.`; + } else { + if (user.isTasksCompleted) { + msgSendBack = `You've already completed the Bounty tasks.`; + } + } + if (msgSendBack) { // Do not send anything, if isInCheck + $u.sendAdmMsg(tx.senderId, msgSendBack); + } + return; + } + + // Check if user apply for check once again + user = await UsersDb.findOne({userId: tx.senderId}); + if (user) { + // User is already was in check earlier, update + log.log(`User ${user.userId} applied once again with Twitter account ${itx.accounts.twitterAccount}.`); + // May be later + // if (user.isBountyPayed) { + // msgSendBack = `You've already received the Bounty reward. Thanks for your support!`; + // $u.sendAdmMsg(tx.senderId, msgSendBack); + // return; + // } else + if (user.isTasksCompleted) { + log.log(`User ${user.userId} already completed the Bounty tasks. Notify user and ignore.`); + msgSendBack = `You've already completed the Bounty tasks.`; + $u.sendAdmMsg(tx.senderId, msgSendBack); + return; + } + + user.update({ + dateUpdated: $u.unix(), + admTxId: tx.id, + msg: itx.encrypted_content, + isInCheck: itx.accounts.notEmpty, + twitterAccountLink: itx.accounts.twitterLink, + twitterAccount: itx.accounts.twitterAccount, + twitterAccountId: null, + isTasksCompleted: false, + isTwitterFollowCheckPassed: false, + isTwitterRetweetCommentCheckPassed: false, + isTwitterAccountEligible: false, + }); + } else { + // First time user, create new + log.info(`User ${tx.senderId} applied for a first time with Twitter account ${itx.accounts.twitterAccount}.`); + user = new UsersDb({ + _id: tx.senderId, + userId: tx.senderId, + dateCreated: $u.unix(), + dateUpdated: $u.unix(), + admTxId: tx.id, + msg: itx.encrypted_content, + isInCheck: itx.accounts.notEmpty, + twitterAccountLink: itx.accounts.twitterLink, + twitterAccount: itx.accounts.twitterAccount, + twitterAccountId: null, + isTasksCompleted: false, + isTwitterFollowCheckPassed: false, + isTwitterRetweetCommentCheckPassed: false, + isTwitterAccountEligible: false, + isAdamantCheckPassed: false, + }); + } + + await user.save(); + await itx.update({isProcessed: true}, true); + + msgSendBack = `I've got your account details. Twitter: ${user.twitterAccount}. I'll check if you've finished the Bounty tasks now…`; + $u.sendAdmMsg(tx.senderId, msgSendBack); +}; diff --git a/modules/checkerTransactions.js b/modules/checkerTransactions.js index cd8f7c4..300ae3f 100644 --- a/modules/checkerTransactions.js +++ b/modules/checkerTransactions.js @@ -1,28 +1,28 @@ -const Store = require('./Store'); -const api = require('./api'); -const txParser = require('./incomingTxsParser'); -const log = require('../helpers/log'); - -async function check() { - try { - if (!Store.lastHeight){ - return; - } - const txChat = (await api.get('uri', 'chats/get/?recipientId=' + Store.user.ADM.address + '&orderBy=timestamp:desc&fromHeight=' + (Store.lastHeight - 5))).transactions; - const txTrx = (await api.get('transactions', 'fromHeight=' + (Store.lastHeight - 5) + '&and:recipientId=' + Store.user.ADM.address + '&and:type=0')).transactions; - - if (txChat && txTrx) { - txChat - .concat(txTrx) - .forEach(t => { - txParser(t); - }); - Store.updateLastBlock(); - } - } catch (e) { - log.error('Error while checking new transactions: ' + e); - } -} -module.exports = () => { - setInterval(check, 2500); -}; +const Store = require('./Store'); +const api = require('./api'); +const txParser = require('./incomingTxsParser'); +const log = require('../helpers/log'); + +async function check() { + try { + if (!Store.lastHeight) { + return; + } + const txChat = (await api.get('uri', 'chats/get/?recipientId=' + Store.user.ADM.address + '&orderBy=timestamp:desc&fromHeight=' + (Store.lastHeight - 5))).transactions; + const txTrx = (await api.get('transactions', 'fromHeight=' + (Store.lastHeight - 5) + '&and:recipientId=' + Store.user.ADM.address + '&and:type=0')).transactions; + + if (txChat && txTrx) { + txChat + .concat(txTrx) + .forEach((t) => { + txParser(t); + }); + Store.updateLastBlock(); + } + } catch (e) { + log.error('Error while checking new transactions: ' + e); + } +} +module.exports = () => { + setInterval(check, 2500); +}; diff --git a/modules/commandTxs.js b/modules/commandTxs.js index e359756..cfa2ef3 100644 --- a/modules/commandTxs.js +++ b/modules/commandTxs.js @@ -1,224 +1,217 @@ -const Store = require('../modules/Store'); -const $u = require('../helpers/utils'); -const config = require('./configReader'); -const log = require('../helpers/log'); -const notify = require('../helpers/notify'); -const twitterapi = require('./twitterapi'); - -module.exports = async (cmd, tx, itx) => { - - if (itx.isProcessed) return; - log.log(`Got new command Tx to process: ${cmd} from ${tx.senderId}`); - try { - let res = []; - const group = cmd - .trim() - .replace(/ /g, ' ') - .replace(/ /g, ' ') - .replace(/ /g, ' ') - .split(' '); - const methodName = group.shift().trim().toLowerCase().replace('\/', ''); - - // do not process commands from non-admin accounts - if (!config.admin_accounts.includes(tx.senderId) && admin_commands.includes(methodName)) { - log.warn(`${config.notifyName} received an admin command from non-admin user _${tx.senderId}_. Ignoring. Income ADAMANT Tx: https://explorer.adamant.im/tx/${tx.id}.`); - itx.update({ - isProcessed: true, - isNonAdmin: true - }, true); - if (config.notify_non_admins) { - $u.sendAdmMsg(tx.senderId, `I won't execute admin commands as you are not an admin. Contact my master.`); - } - return; - } - - const m = commands[methodName]; - if (m) { - res = await m(group, tx); - } else { - res.msgSendBack = `I don’t know */${methodName}* command. ℹ️ You can start with **/help**.`; - } - if (!tx) { - return res.msgSendBack; - } - if (tx) { - itx.update({isProcessed: true}, true); - if (res.msgNotify) - notify(res.msgNotify, res.notifyType); - if (res.msgSendBack) - $u.sendAdmMsg(tx.senderId, res.msgSendBack); - } - } catch (e) { - tx = tx || {}; - log.error('Error while processing command ' + cmd + ' from senderId ' + tx.senderId + '. Tx Id: ' + tx.id + '. Error: ' + e); - } -} - -function help() { - -return { - msgNotify: ``, - msgSendBack: `${config.help_message}`, - notifyType: 'log' - } - -} - -async function rates(params) { - - let output = ''; - - const coin1 = params[0].toUpperCase().trim(); - - if (!coin1 || !coin1.length) { - output = 'Please specify coin ticker or specific market you are interested in. F. e., */rates ADM*.'; - return { - msgNotify: ``, - msgSendBack: `${output}`, - notifyType: 'log' - } - } - const currencies = Store.currencies; - const res = Object - .keys(Store.currencies) - .filter(t => t.startsWith(coin1 + '/')) - .map(t => { - let p = `${coin1}/**${t.replace(coin1 + '/', '')}**`; - return `${p}: ${currencies[t]}`; - }) - .join(', '); - - if (!res.length) { - output = `I can’t get rates for *${coin1}*. Made a typo? Try */rates ADM*.`; - return { - msgNotify: ``, - msgSendBack: `${output}`, - notifyType: 'log' - } - } else { - output = `Global market rates for ${coin1}: -${res}.`; - } - - return { - msgNotify: ``, - msgSendBack: `${output}`, - notifyType: 'log' - } - -} - -async function calc(arr) { - - if (arr.length !== 4) { - return { - msgNotify: ``, - msgSendBack: 'Wrong arguments. Command works like this: */calc 2.05 BTC in USDT*.', - notifyType: 'log' - } - } - - let output = ''; - const amount = +arr[0]; - const inCurrency = arr[1].toUpperCase().trim(); - const outCurrency = arr[3].toUpperCase().trim(); - - if (!amount || amount === Infinity) { - output = `It seems amount "*${amount}*" for *${inCurrency}* is not a number. Command works like this: */calc 2.05 BTC in USDT*.`; - } - if (!$u.isHasTicker(inCurrency)) { - output = `I don’t have rates of crypto *${inCurrency}* from Infoservice. Made a typo? Try */calc 2.05 BTC in USDT*.`; - } - if (!$u.isHasTicker(outCurrency)) { - output = `I don’t have rates of crypto *${outCurrency}* from Infoservice. Made a typo? Try */calc 2.05 BTC in USDT*.`; - } - - let result; - if (!output) { - result = Store.mathEqual(inCurrency, outCurrency, amount, true).outAmount; - if (amount <= 0 || result <= 0 || !result) { - output = `I didn’t understand amount for *${inCurrency}*. Command works like this: */calc 2.05 BTC in USDT*.`; - } else { - if ($u.isFiat(outCurrency)) { - result = +result.toFixed(2); - } - output = `Global market value of ${$u.thousandSeparator(amount)} ${inCurrency} equals **${$u.thousandSeparator(result)} ${outCurrency}**.`; - } - } - - return { - msgNotify: ``, - msgSendBack: `${output}`, - notifyType: 'log' - } - -} - -async function test(param) { - - param = param[0].trim(); - if (!param || !["twitterapi"].includes(param)) { - return { - msgNotify: ``, - msgSendBack: 'Wrong arguments. Command works like this: */test twitterapi*.', - notifyType: 'log' - } - } - - let output; - - if (param === "twitterapi") { - let testResult = await twitterapi.testApi(); - if (testResult.success) { - output = "Twitter API functions well."; - } else { - output = `Error while making Twitter API request: ${testResult.message}`; - } - } - - return { - msgNotify: ``, - msgSendBack: `${output}`, - notifyType: 'log' - } - -} - -function version() { - return { - msgNotify: ``, - msgSendBack: `I am running on _adamant-bountybot_ software version _${Store.version}_. Revise code on ADAMANT's GitHub.`, - notifyType: 'log' - } -} - -function balances() { - let output = ""; - config.known_crypto.forEach(crypto => { - if (Store.user[crypto].balance && Store.user[crypto].balance !== undefined) { - output += `${$u.thousandSeparator(+Store.user[crypto].balance.toFixed(8), true)} _${crypto}_`; - output += "\n"; - } - }); - - return { - msgNotify: ``, - msgSendBack: `My crypto balances: -${output}`, - notifyType: 'log' - } -} - -const commands = { - help, - rates, - calc, - version, - balances, - test -} - -const admin_commands = [ - "test", - "balances" -] \ No newline at end of file +const Store = require('../modules/Store'); +const $u = require('../helpers/utils'); +const config = require('./configReader'); +const log = require('../helpers/log'); +const notify = require('../helpers/notify'); +const twitterapi = require('./twitterapi'); + +module.exports = async (cmd, tx, itx) => { + if (itx.isProcessed) return; + log.log(`Got new command Tx to process: ${cmd} from ${tx.senderId}`); + try { + let res = []; + const group = cmd + .trim() + .replace(/ {4}/g, ' ') + .replace(/ {3}/g, ' ') + .replace(/ {2}/g, ' ') + .split(' '); + const methodName = group.shift().trim().toLowerCase().replace('/', ''); + + // do not process commands from non-admin accounts + if (!config.admin_accounts.includes(tx.senderId) && admin_commands.includes(methodName)) { + log.warn(`${config.notifyName} received an admin command from non-admin user _${tx.senderId}_. Ignoring. Income ADAMANT Tx: https://explorer.adamant.im/tx/${tx.id}.`); + itx.update({ + isProcessed: true, + isNonAdmin: true, + }, true); + if (config.notify_non_admins) { + $u.sendAdmMsg(tx.senderId, `I won't execute admin commands as you are not an admin. Contact my master.`); + } + return; + } + + const m = commands[methodName]; + if (m) { + res = await m(group, tx); + } else { + res.msgSendBack = `I don’t know */${methodName}* command. ℹ️ You can start with **/help**.`; + } + if (!tx) { + return res.msgSendBack; + } + if (tx) { + itx.update({isProcessed: true}, true); + if (res.msgNotify) { + notify(res.msgNotify, res.notifyType); + } + if (res.msgSendBack) { + $u.sendAdmMsg(tx.senderId, res.msgSendBack); + } + } + } catch (e) { + tx = tx || {}; + log.error('Error while processing command ' + cmd + ' from senderId ' + tx.senderId + '. Tx Id: ' + tx.id + '. Error: ' + e); + } +}; + +function help() { + return { + msgNotify: ``, + msgSendBack: `${config.help_message}`, + notifyType: 'log', + }; +} + +async function rates(params) { + let output = ''; + + const coin1 = params[0].toUpperCase().trim(); + + if (!coin1 || !coin1.length) { + output = 'Please specify coin ticker or specific market you are interested in. F. e., */rates ADM*.'; + return { + msgNotify: ``, + msgSendBack: `${output}`, + notifyType: 'log', + }; + } + const currencies = Store.currencies; + const res = Object + .keys(Store.currencies) + .filter((t) => t.startsWith(coin1 + '/')) + .map((t) => { + const p = `${coin1}/**${t.replace(coin1 + '/', '')}**`; + return `${p}: ${currencies[t]}`; + }) + .join(', '); + + if (!res.length) { + output = `I can’t get rates for *${coin1}*. Made a typo? Try */rates ADM*.`; + return { + msgNotify: ``, + msgSendBack: `${output}`, + notifyType: 'log', + }; + } else { + output = `Global market rates for ${coin1}: +${res}.`; + } + + return { + msgNotify: ``, + msgSendBack: `${output}`, + notifyType: 'log', + }; +} + +async function calc(arr) { + if (arr.length !== 4) { + return { + msgNotify: ``, + msgSendBack: 'Wrong arguments. Command works like this: */calc 2.05 BTC in USDT*.', + notifyType: 'log', + }; + } + + let output = ''; + const amount = +arr[0]; + const inCurrency = arr[1].toUpperCase().trim(); + const outCurrency = arr[3].toUpperCase().trim(); + + if (!amount || amount === Infinity) { + output = `It seems amount "*${amount}*" for *${inCurrency}* is not a number. Command works like this: */calc 2.05 BTC in USDT*.`; + } + if (!$u.isHasTicker(inCurrency)) { + output = `I don’t have rates of crypto *${inCurrency}* from Infoservice. Made a typo? Try */calc 2.05 BTC in USDT*.`; + } + if (!$u.isHasTicker(outCurrency)) { + output = `I don’t have rates of crypto *${outCurrency}* from Infoservice. Made a typo? Try */calc 2.05 BTC in USDT*.`; + } + + let result; + if (!output) { + result = Store.mathEqual(inCurrency, outCurrency, amount, true).outAmount; + if (amount <= 0 || result <= 0 || !result) { + output = `I didn’t understand amount for *${inCurrency}*. Command works like this: */calc 2.05 BTC in USDT*.`; + } else { + if ($u.isFiat(outCurrency)) { + result = +result.toFixed(2); + } + output = `Global market value of ${$u.thousandSeparator(amount)} ${inCurrency} equals **${$u.thousandSeparator(result)} ${outCurrency}**.`; + } + } + + return { + msgNotify: ``, + msgSendBack: `${output}`, + notifyType: 'log', + }; +} + +async function test(param) { + param = param[0].trim(); + if (!param || !['twitterapi'].includes(param)) { + return { + msgNotify: ``, + msgSendBack: 'Wrong arguments. Command works like this: */test twitterapi*.', + notifyType: 'log', + }; + } + + let output; + + if (param === 'twitterapi') { + const testResult = await twitterapi.testApi(); + if (testResult.success) { + output = 'Twitter API functions well.'; + } else { + output = `Error while making Twitter API request: ${testResult.message}`; + } + } + + return { + msgNotify: ``, + msgSendBack: `${output}`, + notifyType: 'log', + }; +} + +function version() { + return { + msgNotify: ``, + msgSendBack: `I am running on _adamant-bountybot_ software version _${Store.version}_. Revise code on ADAMANT's GitHub.`, + notifyType: 'log', + }; +} + +function balances() { + let output = ''; + config.known_crypto.forEach((crypto) => { + if (Store.user[crypto].balance && Store.user[crypto].balance !== undefined) { + output += `${$u.thousandSeparator(+Store.user[crypto].balance.toFixed(8), true)} _${crypto}_`; + output += '\n'; + } + }); + + return { + msgNotify: ``, + msgSendBack: `My crypto balances: +${output}`, + notifyType: 'log', + }; +} + +const commands = { + help, + rates, + calc, + version, + balances, + test, +}; + +const admin_commands = [ + 'test', + 'balances', +]; diff --git a/modules/configReader.js b/modules/configReader.js index 5322df9..9f2aaae 100644 --- a/modules/configReader.js +++ b/modules/configReader.js @@ -1,189 +1,190 @@ -const jsonminify = require('jsonminify'); -const fs = require('fs'); -const keys = require('adamant-api/helpers/keys'); -const isDev = process.argv.includes('dev'); -const mathjs = require('mathjs'); - -let config = {}; - -// Validate config fields -const fields = { - passPhrase: { - type: String, - isRequired: true - }, - node_ADM: { - type: Array, - isRequired: true - }, - infoservice: { - type: Array, - default: ['https://info.adamant.im'] - }, - socket: { - type: Boolean, - default: true - }, - ws_type: { - type: String, - isRequired: true - }, - bot_name: { - type: String, - default: null - }, - admin_accounts: { - type: Array, - default: [] - }, - adamant_notify: { - type: String, - default: null - }, - twitter_follow: { - type: Array, - default: [] - }, - twitter_retweet_w_comment: { - type: Array, - default: [] - }, - twitter_reqs: { - type: Object, - default: { "min_followers": 0, "min_friends": 0, "min_statuses": 0, "min_days": 0 } - }, - rewards: { - type: Array, - isRequired: true - }, - rewards_progression_from_twitter_followers: { - type: Object, - default: {} - }, - adamant_campaign: { - type: Object, - default: { "min_contacts": 0 } - }, - twitter_api: { - type: Object, - default: {} - }, - twitter_api_test_interval: { - type: Number, - default: 600 - }, - slack: { - type: String, - default: null - }, - notifyTasksCompleted: { - type: Boolean, - default: true - }, - notifyRewardReceived: { - type: Boolean, - default: true - }, - welcome_string: { - type: String, - default: 'Hello 😊. This is a stub. I have nothing to say. If you are my master, check my config.' - }, - help_message: { - type: String, - default: 'I have nothing to say. If you are my master, check my config.' - } -}; - -try { - if (isDev) { - config = JSON.parse(jsonminify(fs.readFileSync('./config.test', 'utf-8'))); - } else { - config = JSON.parse(jsonminify(fs.readFileSync('./config.json', 'utf-8'))); - } - - let keysPair; - try { - keysPair = keys.createKeypairFromPassPhrase(config.passphrase); - } catch (e) { - exit('Passphrase is not valid! Error: ' + e); - } - const address = keys.createAddressFromPublicKey(keysPair.publicKey); - config.publicKey = keysPair.publicKey; - config.address = address; - config.min_confirmations = 1; // Allowing myself to hardcode here - config.isTwitterCampaign = (config.twitter_follow.length > 0) || (config.twitter_retweet_w_comment.length > 0); - config.doCheckTwitterReqs = config.isTwitterCampaign && ((config.twitter_reqs.min_followers > 0) || (config.twitter_reqs.min_friends > 0) || (config.twitter_reqs.min_statuses > 0) || (config.twitter_reqs.min_days > 0)); - config.twitterEligibleString = `must be older, than ${config.twitter_reqs.min_days} days, must have at least ${config.twitter_reqs.min_followers} followers, ${config.twitter_reqs.min_friends} friends, and ${config.twitter_reqs.min_statuses} tweets`; - - if (config.isTwitterCampaign && (!config.twitter_api.consumer_key || !config.twitter_api.consumer_key || !config.twitter_api.access_token_secret || !config.twitter_api.access_token_key)) { - exit(`Bot's ${address} config is wrong. To run Twitter campaign, set Twitter API credentials (twitter_api). Cannot start Bot.`); - } - - // Create reward list - config.rewards_list = config.rewards - .map(t => { - return `${t.amount} ${t.currency}`; - }) - .join(' + '); - - // Create reward tickers - config.rewards_tickers = config.rewards - .map(t => { - return `${t.currency}`; - }) - .join(' + '); - - // Create reward ranges - config.rewards_range = config.rewards - .map(t => { - if (config.rewards_progression_from_twitter_followers[t.currency]) { - - let min_followers = 0; - if (config.twitter_reqs.min_followers) - min_followers = config.twitter_reqs.min_followers; - let max_followers = 1000000000; - if (config.rewards_progression_from_twitter_followers[t.currency].limit_followers) - max_followers = config.rewards_progression_from_twitter_followers[t.currency].limit_followers; - - let f = config.rewards_progression_from_twitter_followers[t.currency].func; - let min_amount = mathjs.evaluate(f, {followers: min_followers}); - min_amount = +min_amount.toFixed(config.rewards_progression_from_twitter_followers[t.currency].decimals_show); - let max_amount = mathjs.evaluate(f, {followers: max_followers}); - max_amount = +max_amount.toFixed(config.rewards_progression_from_twitter_followers[t.currency].decimals_show); - - return `- from ${min_amount} ${t.currency} (${min_followers} followers) to ${max_amount} ${t.currency} (${max_followers}+ followers)`; - } else { - return `- ${t.amount} ${t.currency}`; - } - }) - .join("\n"); - - // Process help_message as a template literal - config.twitter_follow_list = config.twitter_follow.join(', '); - config.twitter_retweet_w_comment.forEach(tweet => { - tweet.tag_list = tweet.hashtags.join(', '); - }); - config.help_message = eval('`' + config.help_message +'`'); - - Object.keys(fields).forEach(f => { - if (!config[f] && fields[f].isRequired) { - exit(`Bot's ${address} config is wrong. Field _${f}_ is not valid. Cannot start Bot.`); - } else if (!config[f] && config[f] !== 0 && fields[f].default) { - config[f] = fields[f].default; - } - if (config[f] && fields[f].type !== config[f].__proto__.constructor) { - exit(`Bot's ${address} config is wrong. Field type _${f}_ is not valid, expected type is _${fields[f].type.name}_. Cannot start Bot.`); - } - }); - -} catch (e) { - console.error('Error reading config: ' + e); -} - -function exit(msg) { - console.error(msg); - process.exit(-1); -} - -config.isDev = isDev; -module.exports = config; +const jsonminify = require('jsonminify'); +const fs = require('fs'); +const keys = require('adamant-api/helpers/keys'); +const isDev = process.argv.includes('dev'); +const mathjs = require('mathjs'); + +let config = {}; + +// Validate config fields +const fields = { + passPhrase: { + type: String, + isRequired: true, + }, + node_ADM: { + type: Array, + isRequired: true, + }, + infoservice: { + type: Array, + default: ['https://info.adamant.im'], + }, + socket: { + type: Boolean, + default: true, + }, + ws_type: { + type: String, + isRequired: true, + }, + bot_name: { + type: String, + default: null, + }, + admin_accounts: { + type: Array, + default: [], + }, + adamant_notify: { + type: String, + default: null, + }, + twitter_follow: { + type: Array, + default: [], + }, + twitter_retweet_w_comment: { + type: Array, + default: [], + }, + twitter_reqs: { + type: Object, + default: {'min_followers': 0, 'min_friends': 0, 'min_statuses': 0, 'min_days': 0}, + }, + rewards: { + type: Array, + isRequired: true, + }, + rewards_progression_from_twitter_followers: { + type: Object, + default: {}, + }, + adamant_campaign: { + type: Object, + default: {'min_contacts': 0}, + }, + twitter_api: { + type: Object, + default: {}, + }, + twitter_api_test_interval: { + type: Number, + default: 600, + }, + slack: { + type: String, + default: null, + }, + notifyTasksCompleted: { + type: Boolean, + default: true, + }, + notifyRewardReceived: { + type: Boolean, + default: true, + }, + welcome_string: { + type: String, + default: 'Hello 😊. This is a stub. I have nothing to say. If you are my master, check my config.', + }, + help_message: { + type: String, + default: 'I have nothing to say. If you are my master, check my config.', + }, +}; + +try { + if (isDev) { + config = JSON.parse(jsonminify(fs.readFileSync('./config.test', 'utf-8'))); + } else { + config = JSON.parse(jsonminify(fs.readFileSync('./config.json', 'utf-8'))); + } + + let keysPair; + try { + keysPair = keys.createKeypairFromPassPhrase(config.passphrase); + } catch (e) { + exit('Passphrase is not valid! Error: ' + e); + } + const address = keys.createAddressFromPublicKey(keysPair.publicKey); + config.publicKey = keysPair.publicKey; + config.address = address; + config.min_confirmations = 1; // Allowing myself to hardcode here + config.isTwitterCampaign = (config.twitter_follow.length > 0) || (config.twitter_retweet_w_comment.length > 0); + config.doCheckTwitterReqs = config.isTwitterCampaign && ((config.twitter_reqs.min_followers > 0) || (config.twitter_reqs.min_friends > 0) || (config.twitter_reqs.min_statuses > 0) || + (config.twitter_reqs.min_days > 0)); + config.twitterEligibleString = `must be older, than ${config.twitter_reqs.min_days} days, must have at least ${config.twitter_reqs.min_followers} followers, ${config.twitter_reqs.min_friends} friends, and ${config.twitter_reqs.min_statuses} tweets`; + + if (config.isTwitterCampaign && (!config.twitter_api.consumer_key || !config.twitter_api.consumer_key || !config.twitter_api.access_token_secret || !config.twitter_api.access_token_key)) { + exit(`Bot's ${address} config is wrong. To run Twitter campaign, set Twitter API credentials (twitter_api). Cannot start Bot.`); + } + + // Create reward list + config.rewards_list = config.rewards + .map((t) => { + return `${t.amount} ${t.currency}`; + }) + .join(' + '); + + // Create reward tickers + config.rewards_tickers = config.rewards + .map((t) => { + return `${t.currency}`; + }) + .join(' + '); + + // Create reward ranges + config.rewards_range = config.rewards + .map((t) => { + if (config.rewards_progression_from_twitter_followers[t.currency]) { + let min_followers = 0; + if (config.twitter_reqs.min_followers) { + min_followers = config.twitter_reqs.min_followers; + } + let max_followers = 1000000000; + if (config.rewards_progression_from_twitter_followers[t.currency].limit_followers) { + max_followers = config.rewards_progression_from_twitter_followers[t.currency].limit_followers; + } + + const f = config.rewards_progression_from_twitter_followers[t.currency].func; + let min_amount = mathjs.evaluate(f, {followers: min_followers}); + min_amount = +min_amount.toFixed(config.rewards_progression_from_twitter_followers[t.currency].decimals_show); + let max_amount = mathjs.evaluate(f, {followers: max_followers}); + max_amount = +max_amount.toFixed(config.rewards_progression_from_twitter_followers[t.currency].decimals_show); + + return `- from ${min_amount} ${t.currency} (${min_followers} followers) to ${max_amount} ${t.currency} (${max_followers}+ followers)`; + } else { + return `- ${t.amount} ${t.currency}`; + } + }) + .join('\n'); + + // Process help_message as a template literal + config.twitter_follow_list = config.twitter_follow.join(', '); + config.twitter_retweet_w_comment.forEach((tweet) => { + tweet.tag_list = tweet.hashtags.join(', '); + }); + config.help_message = eval('`' + config.help_message +'`'); + + Object.keys(fields).forEach((f) => { + if (!config[f] && fields[f].isRequired) { + exit(`Bot's ${address} config is wrong. Field _${f}_ is not valid. Cannot start Bot.`); + } else if (!config[f] && config[f] !== 0 && fields[f].default) { + config[f] = fields[f].default; + } + if (config[f] && fields[f].type !== config[f].__proto__.constructor) { + exit(`Bot's ${address} config is wrong. Field type _${f}_ is not valid, expected type is _${fields[f].type.name}_. Cannot start Bot.`); + } + }); +} catch (e) { + console.error('Error reading config: ' + e); +} + +function exit(msg) { + console.error(msg); + process.exit(-1); +} + +config.isDev = isDev; +module.exports = config; diff --git a/modules/incomingTxsParser.js b/modules/incomingTxsParser.js index 9a724e3..144ca48 100644 --- a/modules/incomingTxsParser.js +++ b/modules/incomingTxsParser.js @@ -1,113 +1,112 @@ -const db = require('./DB'); -const log = require('../helpers/log'); -const $u = require('../helpers/utils'); -const api = require('./api'); -const config = require('./configReader'); -const commandTxs = require('./commandTxs'); -const unknownTxs = require('./unknownTxs'); -const transferTxs = require('./transferTxs'); -const checkTxs = require('./checkTxs'); -const notify = require('../helpers/notify'); - -const historyTxs = {}; - -module.exports = async (tx) => { - - if (!tx) { - return; - } - - if (historyTxs[tx.id]) { // do not process one tx twice - return; - } - - const {incomingTxsDb} = db; - const checkedTx = await incomingTxsDb.findOne({txid: tx.id}); - if (checkedTx !== null) { - return; - }; - - log.log(`New incoming transaction: ${tx.id} from ${tx.senderId}`); - - let msg = ''; - const chat = tx.asset.chat; - if (chat) { - msg = api.decodeMsg(chat.message, tx.senderPublicKey, config.passPhrase, chat.own_message).trim(); - } - - if (msg === '') { - msg = 'NONE'; - } - - // Parse social accounts from user message - let accounts = $u.getAccounts(msg); - - let type = 'unknown'; - if (msg.includes('_transaction') || tx.amount > 0) { - type = 'transfer'; // just for special message - } else if (accounts.notEmpty) { - type = 'check'; - } else if (msg.startsWith('/')) { - type = 'command'; - } - - // Check if we should notify about spammer, only once per 24 hours - const spamerIsNotyfy = await incomingTxsDb.findOne({ - sender: tx.senderId, - isSpam: true, - date: {$gt: ($u.unix() - 24 * 3600 * 1000)} // last 24h - }); - - const itx = new incomingTxsDb({ - _id: tx.id, - txid: tx.id, - date: $u.unix(), - block_id: tx.blockId, - encrypted_content: msg, - accounts: accounts, - spam: false, - sender: tx.senderId, - type, // check, command, transfer or unknown - isProcessed: false, - isNonAdmin: false - }); - - const countRequestsUser = (await incomingTxsDb.find({ - sender: tx.senderId, - date: {$gt: ($u.unix() - 24 * 3600 * 1000)} // last 24h - })).length; - - if (countRequestsUser > 50 || spamerIsNotyfy) { // 50 per 24h is a limit for accepting commands, otherwise user will be considered as spammer - itx.update({ - isProcessed: true, - isSpam: true - }); - } - - await itx.save(); - if (historyTxs[tx.id]) { - return; - } - historyTxs[tx.id] = $u.unix(); - - if (itx.isSpam && !spamerIsNotyfy) { - notify(`${config.notifyName} notifies _${tx.senderId}_ is a spammer or talks too much. Income ADAMANT Tx: https://explorer.adamant.im/tx/${tx.id}.`, 'warn'); - $u.sendAdmMsg(tx.senderId, `I’ve _banned_ you. You’ve sent too much transactions to me.`); - return; - } - - switch (type){ - case ('transfer'): - transferTxs(itx, tx); - break; - case ('check'): - checkTxs(itx, tx); - break; - case ('command'): - commandTxs(msg, tx, itx); - break; - default: - unknownTxs(tx, itx); - break; - } -}; +const db = require('./DB'); +const log = require('../helpers/log'); +const $u = require('../helpers/utils'); +const api = require('./api'); +const config = require('./configReader'); +const commandTxs = require('./commandTxs'); +const unknownTxs = require('./unknownTxs'); +const transferTxs = require('./transferTxs'); +const checkTxs = require('./checkTxs'); +const notify = require('../helpers/notify'); + +const historyTxs = {}; + +module.exports = async (tx) => { + if (!tx) { + return; + } + + if (historyTxs[tx.id]) { // do not process one tx twice + return; + } + + const {IncomingTxsDb} = db; + const checkedTx = await IncomingTxsDb.findOne({txid: tx.id}); + if (checkedTx !== null) { + return; + } + + log.log(`New incoming transaction: ${tx.id} from ${tx.senderId}`); + + let msg = ''; + const chat = tx.asset.chat; + if (chat) { + msg = api.decodeMsg(chat.message, tx.senderPublicKey, config.passPhrase, chat.own_message).trim(); + } + + if (msg === '') { + msg = 'NONE'; + } + + // Parse social accounts from user message + const accounts = $u.getAccounts(msg); + + let type = 'unknown'; + if (msg.includes('_transaction') || tx.amount > 0) { + type = 'transfer'; // just for special message + } else if (accounts.notEmpty) { + type = 'check'; + } else if (msg.startsWith('/')) { + type = 'command'; + } + + // Check if we should notify about spammer, only once per 24 hours + const spamerIsNotyfy = await IncomingTxsDb.findOne({ + sender: tx.senderId, + isSpam: true, + date: {$gt: ($u.unix() - 24 * 3600 * 1000)}, // last 24h + }); + + const itx = new IncomingTxsDb({ + _id: tx.id, + txid: tx.id, + date: $u.unix(), + block_id: tx.blockId, + encrypted_content: msg, + accounts: accounts, + spam: false, + sender: tx.senderId, + type, // check, command, transfer or unknown + isProcessed: false, + isNonAdmin: false, + }); + + const countRequestsUser = (await IncomingTxsDb.find({ + sender: tx.senderId, + date: {$gt: ($u.unix() - 24 * 3600 * 1000)}, // last 24h + })).length; + + if (countRequestsUser > 50 || spamerIsNotyfy) { // 50 per 24h is a limit for accepting commands, otherwise user will be considered as spammer + itx.update({ + isProcessed: true, + isSpam: true, + }); + } + + await itx.save(); + if (historyTxs[tx.id]) { + return; + } + historyTxs[tx.id] = $u.unix(); + + if (itx.isSpam && !spamerIsNotyfy) { + notify(`${config.notifyName} notifies _${tx.senderId}_ is a spammer or talks too much. Income ADAMANT Tx: https://explorer.adamant.im/tx/${tx.id}.`, 'warn'); + $u.sendAdmMsg(tx.senderId, `I’ve _banned_ you. You’ve sent too much transactions to me.`); + return; + } + + switch (type) { + case ('transfer'): + transferTxs(itx, tx); + break; + case ('check'): + checkTxs(itx, tx); + break; + case ('command'): + commandTxs(msg, tx, itx); + break; + default: + unknownTxs(tx, itx); + break; + } +}; diff --git a/modules/outAddressFetcher.js b/modules/outAddressFetcher.js index 2704727..283959c 100644 --- a/modules/outAddressFetcher.js +++ b/modules/outAddressFetcher.js @@ -1,58 +1,54 @@ -const log = require('../helpers/log'); -const $u = require('../helpers/utils'); -const notify = require('../helpers/notify'); -const config = require('./configReader'); -const db = require('./DB'); - -module.exports = async () => { - - const {paymentsDb} = db; - - (await paymentsDb.find({ - isPayed: false, - isFinished: false, - outTxid: null, - outAddress: null - })).forEach(async pay => { - - pay.tryFetchOutAddressCounter = ++pay.tryFetchOutAddressCounter || 0; - - // Fetching addresses from ADAMANT KVS - try { - - let msgSendBack = false; - let msgNotify = false; - - let outAddress = (pay.outCurrency === 'ADM' && pay.userId) || await $u.getAddressCryptoFromAdmAddressADM(pay.outCurrency, pay.userId); - - if (!outAddress) { - if (pay.tryFetchOutAddressCounter < 20) { - log.error(`Can't get ${pay.outCurrency} address from KVS for ${pay.userId}. Will try next time.`); - } else { - pay.update({ - error: 10, - isFinished: true - }); - msgNotify = `${config.notifyName} can’t fetch ${pay.outCurrency} address from KVS for ${pay.userId} to pay a reward of _${pay.outAmount} ${pay.outCurrency}_.`; - msgSendBack = `I can’t get your _${pay.outCurrency}_ address from ADAMANT KVS to pay a reward. Make sure you use ADAMANT wallet with _${pay.outCurrency}_ enabled. I have already notified my master.`; - notify(msgNotify, 'error'); - $u.sendAdmMsg(tx.userId, msgSendBack); - } - } else { - pay.update({ - outAddress: outAddress - }); - - } - - await pay.save(); - - } catch (e) { - log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); - } - }) -}; - -setInterval(() => { - module.exports(); -}, 15 * 1000); +const log = require('../helpers/log'); +const $u = require('../helpers/utils'); +const notify = require('../helpers/notify'); +const config = require('./configReader'); +const db = require('./DB'); + +module.exports = async () => { + const {PaymentsDb} = db; + + (await PaymentsDb.find({ + isPayed: false, + isFinished: false, + outTxid: null, + outAddress: null, + })).forEach(async (pay) => { + pay.tryFetchOutAddressCounter = ++pay.tryFetchOutAddressCounter || 0; + + // Fetching addresses from ADAMANT KVS + try { + let msgSendBack = false; + let msgNotify = false; + + const outAddress = (pay.outCurrency === 'ADM' && pay.userId) || await $u.getAddressCryptoFromAdmAddressADM(pay.outCurrency, pay.userId); + + if (!outAddress) { + if (pay.tryFetchOutAddressCounter < 20) { + log.error(`Can't get ${pay.outCurrency} address from KVS for ${pay.userId}. Will try next time.`); + } else { + pay.update({ + error: 10, + isFinished: true, + }); + msgNotify = `${config.notifyName} can’t fetch ${pay.outCurrency} address from KVS for ${pay.userId} to pay a reward of _${pay.outAmount} ${pay.outCurrency}_.`; + msgSendBack = `I can’t get your _${pay.outCurrency}_ address from ADAMANT KVS to pay a reward. Make sure you use ADAMANT wallet with _${pay.outCurrency}_ enabled. I have already notified my master.`; + notify(msgNotify, 'error'); + let tx; + $u.sendAdmMsg(tx.userId, msgSendBack); + } + } else { + pay.update({ + outAddress: outAddress, + }); + } + + await pay.save(); + } catch (e) { + log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); + } + }); +}; + +setInterval(() => { + module.exports(); +}, 15 * 1000); diff --git a/modules/rewardsPayer.js b/modules/rewardsPayer.js index c624cc5..ec0b1c8 100644 --- a/modules/rewardsPayer.js +++ b/modules/rewardsPayer.js @@ -1,98 +1,93 @@ -const db = require('./DB'); -const config = require('./configReader'); -const $u = require('../helpers/utils'); -const Store = require('./Store'); -const log = require('../helpers/log'); -const notify = require('../helpers/notify'); - -module.exports = async () => { - const {paymentsDb} = db; - await $u.updateAllBalances(); - - (await paymentsDb.find({ - isPayed: false, - isFinished: false, - outTxid: null, - outAddress: {$ne: null} - })).forEach(async pay => { - try { - - pay.trySendCounter = pay.trySendCounter || 0; - const { - userId, - outAmount, - outCurrency, - outAddress - } = pay; - - let etherString = ''; - let isNotEnoughBalance; - - if ($u.isERC20(outCurrency)) { - etherString = `Ether balance: ${Store.user['ETH'].balance}. `; - isNotEnoughBalance = (outAmount > Store.user[outCurrency].balance) || ($u[outCurrency].FEE > Store.user['ETH'].balance); - } else { - etherString = ''; - isNotEnoughBalance = outAmount + $u[outCurrency].FEE > Store.user[outCurrency].balance; - } - - if (isNotEnoughBalance) { - pay.update({ - error: 15, - isFinished: true, - isPayed: false - }, true); - notify(`${config.notifyName} notifies about insufficient balance to send a reward of _${outAmount}_ _${outCurrency}_. Balance of _${outCurrency}_ is _${Store.user[outCurrency].balance}_. ${etherString}User ADAMANT id: ${userId}.`, 'error'); - $u.sendAdmMsg(userId, `I can’t transfer a reward of _${outAmount}_ _${outCurrency}_ to you because of insufficient funds (I count blockchain fees also). I have already notified my master.`); - return; - } - - log.log(`Attempt number ${pay.trySendCounter} to send the reward payout. Coin: ${outCurrency}, address: ${outAddress}, value: ${outAmount}, balance: ${Store.user[outCurrency].balance}`); - const result = await $u[outCurrency].send({ - address: outAddress, - value: outAmount, - comment: 'Was it great? Share the experience with your friends!' // if ADM - }); - log.log(`Payout result: ${JSON.stringify(result, 0, 2)}`); - - if (result.success) { - - pay.update({ - outTxid: result.hash, - isPayed: true - }, true); - - // Update local balances without unnecessary requests - if ($u.isERC20(outCurrency)) { - Store.user[outCurrency].balance -= outAmount; - Store.user['ETH'].balance -= $u[outCurrency].FEE; - } else { - Store.user[outCurrency].balance -= (outAmount + $u[outCurrency].FEE); - } - log.info(`Successful payout of ${outAmount} ${outCurrency} to ${userId}. Hash: ${result.hash}.`); - - } else { // Can't make a transaction - - if (pay.trySendCounter++ < 50) { // If number of attempts less then 50, just ignore and try again on next tick - await pay.save(); - return; - }; - - pay.update({ - error: 16, - isFinished: true, - isPayed: false - }, true); - notify(`${config.notifyName} cannot make transaction to payout a reward of _${outAmount}_ _${outCurrency}_. Balance of _${outCurrency}_ is _${Store.user[outCurrency].balance}_. ${etherString}User ADAMANT id: ${userId}.`, 'error'); - $u.sendAdmMsg(userId, `I’ve tried to make a reward payout of _${outAmount}_ _${outCurrency}_ to you, but something went wrong. I have already notified my master.`); - } - - } catch (e) { - log.error(`Error in ${$u.getModuleName(module.id)} module: ${e.toString()}`); - } - }); -}; - -setInterval(() => { - module.exports(); -}, 15 * 1000); +const db = require('./DB'); +const config = require('./configReader'); +const $u = require('../helpers/utils'); +const Store = require('./Store'); +const log = require('../helpers/log'); +const notify = require('../helpers/notify'); + +module.exports = async () => { + const {PaymentsDb} = db; + await $u.updateAllBalances(); + + (await PaymentsDb.find({ + isPayed: false, + isFinished: false, + outTxid: null, + outAddress: {$ne: null}, + })).forEach(async (pay) => { + try { + pay.trySendCounter = pay.trySendCounter || 0; + const { + userId, + outAmount, + outCurrency, + outAddress, + } = pay; + + let etherString = ''; + let isNotEnoughBalance; + + if ($u.isERC20(outCurrency)) { + etherString = `Ether balance: ${Store.user['ETH'].balance}. `; + isNotEnoughBalance = (outAmount > Store.user[outCurrency].balance) || ($u[outCurrency].FEE > Store.user['ETH'].balance); + } else { + etherString = ''; + isNotEnoughBalance = outAmount + $u[outCurrency].FEE > Store.user[outCurrency].balance; + } + + if (isNotEnoughBalance) { + pay.update({ + error: 15, + isFinished: true, + isPayed: false, + }, true); + notify(`${config.notifyName} notifies about insufficient balance to send a reward of _${outAmount}_ _${outCurrency}_. Balance of _${outCurrency}_ is _${Store.user[outCurrency].balance}_. ${etherString}User ADAMANT id: ${userId}.`, 'error'); + $u.sendAdmMsg(userId, `I can’t transfer a reward of _${outAmount}_ _${outCurrency}_ to you because of insufficient funds (I count blockchain fees also). I have already notified my master.`); + return; + } + + log.log(`Attempt number ${pay.trySendCounter} to send the reward payout. Coin: ${outCurrency}, address: ${outAddress}, value: ${outAmount}, balance: ${Store.user[outCurrency].balance}`); + const result = await $u[outCurrency].send({ + address: outAddress, + value: outAmount, + comment: 'Was it great? Share the experience with your friends!', // if ADM + }); + log.log(`Payout result: ${JSON.stringify(result, 0, 2)}`); + + if (result.success) { + pay.update({ + outTxid: result.hash, + isPayed: true, + }, true); + + // Update local balances without unnecessary requests + if ($u.isERC20(outCurrency)) { + Store.user[outCurrency].balance -= outAmount; + Store.user['ETH'].balance -= $u[outCurrency].FEE; + } else { + Store.user[outCurrency].balance -= (outAmount + $u[outCurrency].FEE); + } + log.info(`Successful payout of ${outAmount} ${outCurrency} to ${userId}. Hash: ${result.hash}.`); + } else { // Can't make a transaction + if (pay.trySendCounter++ < 50) { // If number of attempts less then 50, just ignore and try again on next tick + await pay.save(); + return; + } + + pay.update({ + error: 16, + isFinished: true, + isPayed: false, + }, true); + notify(`${config.notifyName} cannot make transaction to payout a reward of _${outAmount}_ _${outCurrency}_. Balance of _${outCurrency}_ is _${Store.user[outCurrency].balance}_. ${etherString}User ADAMANT id: ${userId}.`, 'error'); + $u.sendAdmMsg(userId, `I’ve tried to make a reward payout of _${outAmount}_ _${outCurrency}_ to you, but something went wrong. I have already notified my master.`); + } + } catch (e) { + log.error(`Error in ${$u.getModuleName(module.id)} module: ${e.toString()}`); + } + }); +}; + +setInterval(() => { + module.exports(); +}, 15 * 1000); diff --git a/modules/sentTxValidator.js b/modules/sentTxValidator.js index ef51cda..52b4ba2 100644 --- a/modules/sentTxValidator.js +++ b/modules/sentTxValidator.js @@ -1,115 +1,108 @@ -const db = require('./DB'); -const config = require('./configReader'); -const $u = require('../helpers/utils'); -const Store = require('./Store'); -const log = require('../helpers/log'); -const notify = require('../helpers/notify'); - -module.exports = async () => { - const {paymentsDb} = db; - const lastBlockNumber = await $u.getLastBlocksNumbers(); - - (await paymentsDb.find({ - isPayed: true, - isFinished: false, - outTxid: {$ne: null} - })).forEach(async pay => { - - const { - outCurrency, - userId, - outAmount, - outTxid - } = pay; - - pay.tryVerifyPayoutCounter = ++pay.tryVerifyPayoutCounter || 0; - - try { - - let msgNotify = ''; - let notifyType = ''; - let msgSendBack = ''; - - let etherString = ''; - if ($u.isERC20(outCurrency)) { - etherString = `Ether balance: ${Store.user['ETH'].balance}. `; - } - - if (!lastBlockNumber[outCurrency]) { - log.warn('Unable to get lastBlockNumber for ' + outCurrency + '. Waiting for next try.'); - return; - } - - const txData = (await $u[outCurrency].getTransactionStatus(outTxid)); - if (!txData || !txData.blockNumber) { - - if (pay.tryVerifyPayoutCounter > 50 ) { - pay.update({ - errorCheckOuterTX: 24, - isFinished: true - }); - - notifyType = 'error'; - msgNotify = `${config.notifyName} unable to verify the reward payout of _${outAmount}_ _${outCurrency}_. Attention needed. Tx hash: _${outTxid}_. Balance of _${outCurrency}_ is _${Store.user[outCurrency].balance}_. ${etherString}User ADAMANT id: ${userId}.`; - msgSendBack = `I’ve tried to make the reward payout of _${outAmount}_ _${outCurrency}_ to you, but unable to validate transaction. Tx hash: _${outTxid}_. I’ve already notified my master. If you wouldn’t receive transfer in two days, contact my master also.`; - - notify(msgNotify, notifyType); - $u.sendAdmMsg(userId, msgSendBack); - } - await pay.save(); - return; - - } - - const {status, blockNumber} = txData; - - pay.update({ - outTxStatus: status, - outConfirmations: lastBlockNumber[outCurrency] - blockNumber - }); - - if (status === false) { - - notifyType = 'error'; - pay.update({ - errorValidatorSend: 21, - outTxid: null, - isPayed: false, - isFinished: false - }); - - msgNotify = `${config.notifyName} notifies that the reward payout of _${outAmount}_ _${outCurrency}_ failed. Tx hash: _${outTxid}_. Will try again. Balance of _${outCurrency}_ is _${Store.user[outCurrency].balance}_. ${etherString}User ADAMANT id: ${userId}.`; - msgSendBack = `I’ve tried to make the payout transfer of _${outAmount}_ _${outCurrency}_ to you, but it seems transaction failed. Tx hash: _${outTxid}_. I will try again. If I’ve said the same several times already, please contact my master.`; - - $u.sendAdmMsg(userId, msgSendBack); - - } else if (status && pay.outConfirmations >= config.min_confirmations) { - - notifyType = 'info'; - if (config.notifyRewardReceived) - msgNotify = `${config.notifyName} successfully payed the reward of _${outAmount} ${outCurrency}_ to ${userId} with Tx hash _${outTxid}_.`; - msgSendBack = 'Was it great? Share the experience with your friends!'; - - if (outCurrency !== 'ADM') { - msgSendBack = `{"type":"${outCurrency}_transaction","amount":"${outAmount}","hash":"${outTxid}","comments":"${msgSendBack}"}`; - pay.isFinished = $u.sendAdmMsg(userId, msgSendBack, 'rich'); - } else { - pay.isFinished = true; - } - } - - await pay.save(); - - if (msgNotify) { - notify(msgNotify, notifyType); - } - } catch (e) { - log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); - } - }); - -}; - -setInterval(() => { - module.exports(); -}, 8 * 1000); +const db = require('./DB'); +const config = require('./configReader'); +const $u = require('../helpers/utils'); +const Store = require('./Store'); +const log = require('../helpers/log'); +const notify = require('../helpers/notify'); + +module.exports = async () => { + const {PaymentsDb} = db; + const lastBlockNumber = await $u.getLastBlocksNumbers(); + + (await PaymentsDb.find({ + isPayed: true, + isFinished: false, + outTxid: {$ne: null}, + })).forEach(async (pay) => { + const { + outCurrency, + userId, + outAmount, + outTxid, + } = pay; + + pay.tryVerifyPayoutCounter = ++pay.tryVerifyPayoutCounter || 0; + + try { + let msgNotify = ''; + let notifyType = ''; + let msgSendBack = ''; + + let etherString = ''; + if ($u.isERC20(outCurrency)) { + etherString = `Ether balance: ${Store.user['ETH'].balance}. `; + } + + if (!lastBlockNumber[outCurrency]) { + log.warn('Unable to get lastBlockNumber for ' + outCurrency + '. Waiting for next try.'); + return; + } + + const txData = (await $u[outCurrency].getTransactionStatus(outTxid)); + if (!txData || !txData.blockNumber) { + if (pay.tryVerifyPayoutCounter > 50 ) { + pay.update({ + errorCheckOuterTX: 24, + isFinished: true, + }); + + notifyType = 'error'; + msgNotify = `${config.notifyName} unable to verify the reward payout of _${outAmount}_ _${outCurrency}_. Attention needed. Tx hash: _${outTxid}_. Balance of _${outCurrency}_ is _${Store.user[outCurrency].balance}_. ${etherString}User ADAMANT id: ${userId}.`; + msgSendBack = `I’ve tried to make the reward payout of _${outAmount}_ _${outCurrency}_ to you, but unable to validate transaction. Tx hash: _${outTxid}_. I’ve already notified my master. If you wouldn’t receive transfer in two days, contact my master also.`; + + notify(msgNotify, notifyType); + $u.sendAdmMsg(userId, msgSendBack); + } + await pay.save(); + return; + } + + const {status, blockNumber} = txData; + + pay.update({ + outTxStatus: status, + outConfirmations: lastBlockNumber[outCurrency] - blockNumber, + }); + + if (status === false) { + notifyType = 'error'; + pay.update({ + errorValidatorSend: 21, + outTxid: null, + isPayed: false, + isFinished: false, + }); + + msgNotify = `${config.notifyName} notifies that the reward payout of _${outAmount}_ _${outCurrency}_ failed. Tx hash: _${outTxid}_. Will try again. Balance of _${outCurrency}_ is _${Store.user[outCurrency].balance}_. ${etherString}User ADAMANT id: ${userId}.`; + msgSendBack = `I’ve tried to make the payout transfer of _${outAmount}_ _${outCurrency}_ to you, but it seems transaction failed. Tx hash: _${outTxid}_. I will try again. If I’ve said the same several times already, please contact my master.`; + + $u.sendAdmMsg(userId, msgSendBack); + } else if (status && pay.outConfirmations >= config.min_confirmations) { + notifyType = 'info'; + if (config.notifyRewardReceived) { + msgNotify = `${config.notifyName} successfully payed the reward of _${outAmount} ${outCurrency}_ to ${userId} with Tx hash _${outTxid}_.`; + } + msgSendBack = 'Was it great? Share the experience with your friends!'; + + if (outCurrency !== 'ADM') { + msgSendBack = `{"type":"${outCurrency}_transaction","amount":"${outAmount}","hash":"${outTxid}","comments":"${msgSendBack}"}`; + pay.isFinished = $u.sendAdmMsg(userId, msgSendBack, 'rich'); + } else { + pay.isFinished = true; + } + } + + await pay.save(); + + if (msgNotify) { + notify(msgNotify, notifyType); + } + } catch (e) { + log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); + } + }); +}; + +setInterval(() => { + module.exports(); +}, 8 * 1000); diff --git a/modules/transferTxs.js b/modules/transferTxs.js index 577755d..e5ae25b 100644 --- a/modules/transferTxs.js +++ b/modules/transferTxs.js @@ -1,47 +1,42 @@ -const {SAT} = require('../helpers/const'); -const $u = require('../helpers/utils'); -const notify = require('../helpers/notify'); -const config = require('./configReader'); - -module.exports = async (itx, tx) => { - - const msg = itx.encrypted_content; - let inCurrency, - outCurrency, - inTxid, - inAmountMessage; - - if (tx.amount > 0) { // ADM income payment - inAmountMessage = tx.amount / SAT; - inCurrency = 'ADM'; - outCurrency = msg; - inTxid = tx.id; - } else if (msg.includes('_transaction')) { // not ADM income payment - inCurrency = msg.match(/"type":"(.*)_transaction/)[1]; - try { - const json = JSON.parse(msg); - inAmountMessage = Number(json.amount); - inTxid = json.hash; - outCurrency = json.comments; - if (outCurrency === '') { - outCurrency = 'NONE'; - } - } catch (e) { - inCurrency = 'none'; - } - } - - outCurrency = String(outCurrency).toUpperCase().trim(); - inCurrency = String(inCurrency).toUpperCase().trim(); - - // Validate - let msgSendBack = `I got a transfer from you. Thanks, bro.`; - let msgNotify = `${config.notifyName} got a transfer of ${inAmountMessage} ${inCurrency} from user ${tx.senderId}. I will not verify the transaction, check it manually. Income ADAMANT Tx: https://explorer.adamant.im/tx/${tx.id}.`; - let notifyType = 'log'; - - await itx.update({isProcessed: true}, true); - - notify(msgNotify, notifyType); - $u.sendAdmMsg(tx.senderId, msgSendBack); - -}; +const {SAT} = require('../helpers/const'); +const $u = require('../helpers/utils'); +const notify = require('../helpers/notify'); +const config = require('./configReader'); + +module.exports = async (itx, tx) => { + const msg = itx.encrypted_content; + let inCurrency; + let outCurrency; + let inAmountMessage; + + if (tx.amount > 0) { // ADM income payment + inAmountMessage = tx.amount / SAT; + inCurrency = 'ADM'; + outCurrency = msg; + } else if (msg.includes('_transaction')) { // not ADM income payment + inCurrency = msg.match(/"type":"(.*)_transaction/)[1]; + try { + const json = JSON.parse(msg); + inAmountMessage = Number(json.amount); + outCurrency = json.comments; + if (outCurrency === '') { + outCurrency = 'NONE'; + } + } catch (e) { + inCurrency = 'none'; + } + } + + outCurrency = String(outCurrency).toUpperCase().trim(); + inCurrency = String(inCurrency).toUpperCase().trim(); + + // Validate + const msgSendBack = `I got a transfer from you. Thanks, bro.`; + const msgNotify = `${config.notifyName} got a transfer of ${inAmountMessage} ${inCurrency} from user ${tx.senderId}. I will not verify the transaction, check it manually. Income ADAMANT Tx: https://explorer.adamant.im/tx/${tx.id}.`; + const notifyType = 'log'; + + await itx.update({isProcessed: true}, true); + + notify(msgNotify, notifyType); + $u.sendAdmMsg(tx.senderId, msgSendBack); +}; diff --git a/modules/twitterapi.js b/modules/twitterapi.js index 0a4ecd8..ed743a0 100644 --- a/modules/twitterapi.js +++ b/modules/twitterapi.js @@ -1,302 +1,287 @@ -const $u = require('../helpers/utils'); -const log = require('../helpers/log'); -const config = require('./configReader'); -const Twitter = require('twitter')({ - consumer_key: config.twitter_api.consumer_key, - consumer_secret: config.twitter_api.consumer_secret, - access_token_key: config.twitter_api.access_token_key, - access_token_secret: config.twitter_api.access_token_secret - }); - -let toFollowIds = {}; - -(async function() { - - // Get Twitter accounts-to-follow ids by their names - - let name, id; - for (let i = 0; i < config.twitter_follow.length; i++) { - name = $u.getTwitterScreenName(config.twitter_follow[i]); - id = (await getAccountInfo(config.twitter_follow[i])).id_str; - if (id) { - toFollowIds[name] = id; - } else { - log.error(`Unable to get Twitter ids of accounts to follow. Cannot start Bot.`); - process.exit(1); - } - } - -})(); - - -async function getAccountFollowerIds(account) { - const accountSN = $u.getTwitterScreenName(account); - console.log(`Getting followers for @${accountSN}…`) - - var ids = []; - return new Promise((resolve, reject) => { - Twitter.get('followers/ids', {screen_name: accountSN, count: 5000, stringify_ids: true}, function getData(error, data, response) { - try { - if (error) { - log.warn(`Twitter returned an error in getAccountFollowerIds(): ${JSON.stringify(error)}`); - resolve(false); - } else { - ids = ids.concat(data.ids); - // console.log(`next_cursor_str: `, data['next_cursor_str']); - if (data['next_cursor_str'] > 0) { - Twitter.get('followers/ids', { screen_name: accountSN, count: 5000, cursor: data['next_cursor_str'] }, getData); - } else { - console.log(`FollowerIds count for @${accountSN} is ${ids.length}.`); - resolve(ids); - } - } - } catch (e) { - log.warn(`Error while making getAccountFollowerIds() request: ${JSON.stringify(e)}`); - resolve(false); - }; - }); - }); - -} - -async function getAccountFriendIds(account) { - const accountSN = $u.getTwitterScreenName(account); - console.log(`Getting friends for @${accountSN}…`) - - var ids = []; - return new Promise((resolve, reject) => { - Twitter.get('friends/ids', {screen_name: accountSN, count: 5000, stringify_ids: true}, function getData(error, data, response) { - try { - if (error) { - log.warn(`Twitter returned an error in getAccountFriendIds(): ${JSON.stringify(error)}`); - resolve(false); - } else { - ids = ids.concat(data.ids); - // console.log(`next_cursor_str: `, data['next_cursor_str']); - if (data['next_cursor_str'] > 0) { - Twitter.get('friends/ids', { screen_name: accountSN, count: 5000, cursor: data['next_cursor_str'] }, getData); - } else { - console.log(`FriendIds count for @${accountSN} is ${ids.length}.`); - resolve(ids); - } - } - } catch (e) { - log.warn(`Error while making getAccountFriendIds() request: ${JSON.stringify(e)}`); - resolve(false); - }; - }); - }); - -} - -async function getAccountTimeline(account) { - const accountSN = $u.getTwitterScreenName(account); - console.log(`Getting timeline for @${accountSN}…`) - - return await Twitter.get('statuses/user_timeline', {screen_name: accountSN, count: 10, trim_user: true, tweet_mode: 'extended'}) - .then(function (data) { - // console.log(`Timeline for @${accountSN}:`); - // console.log(data); - - console.log(`Timeline count for @${accountSN} is ${data.length}.`); - return data; - }) - .catch(function (e) { - log.warn(`Error while making getAccountTimeline() request: ${JSON.stringify(e)}`); - return false; - }); - -} - -async function getAccountInfo(account) { - const accountSN = $u.getTwitterScreenName(account); - // console.log(`Getting user info for @${accountSN}…`) - - return await Twitter.get('users/show', {screen_name: accountSN}) - .then(function (data) { - // console.log(`User info for @${accountSN}:`); - // console.log(data); - return data; - }) - .catch(function (e) { - log.warn(`Error while making getAccountInfo() request: ${JSON.stringify(e)}`); - if (e && e[0] && (e[0].code === 50 || e[0].code === 63)) // [{"code":50,"message":"User not found."}, {"code":63,"message":"User has been suspended."}] - return e[0] // User can provide wrong Account, process this situation - else - return false; - }); - -} - -function parseTwitterDate(aDate) -{ - return new Date(Date.parse(aDate.replace(/( \+)/, ' UTC$1'))); - //sample: Wed Mar 13 09:06:07 +0000 2013 -} - -module.exports = { - - async testApi() { - - let testResult = { - success: false, - message: "" - } - - try { - - const testAccount = "@TwitterDev"; - let result = await getAccountInfo(testAccount); - - if (result && result.id_str === '2244994945') { - testResult.success = true; - } else { - testResult.success = false; - testResult.message = "Request *users/show* for @TwitterDev didn't return expected value."; - } - return testResult; - - } catch (e) { - testResult.success = false; - testResult.message = "Exception while making *users/show* request."; - return testResult; - } - - }, - // Search for predefined toFollowIds — save Twitter API requests - // followAccount should be in "twitter_follow" param in config - async checkIfAccountFollowing(twitterAccount, followAccount) { - - const twitterAccountSN = $u.getTwitterScreenName(twitterAccount); - const followAccountSN = $u.getTwitterScreenName(followAccount); - console.log(`Checking if @${twitterAccountSN} follows @${followAccountSN}…`); - - let followers = await getAccountFriendIds(twitterAccountSN); - // console.log(followers); - return followers.includes(toFollowIds[followAccountSN]); - }, - async checkIfAccountRetweetedwComment(twitterAccount, tweet, minMentions, hashtags) { - - const twitterAccountSN = $u.getTwitterScreenName(twitterAccount); - const tweetId = $u.getTweetIdFromLink(tweet); - hashtags = $u.getTwitterHashtags(hashtags); - // console.log(tweetId); - console.log(`Checking if @${twitterAccountSN} retweeted ${tweet}…`) - - let tweets = await getAccountTimeline(twitterAccountSN); - let retweet = {}; - for (let i = 0; i < tweets.length; i++) { - if (tweets[i].quoted_status && tweets[i].quoted_status.id_str === tweetId) { - retweet = tweets[i]; - break; - } - } - if (Object.keys(retweet).length < 1) { // Empty object - return { - success: false, - error: 'no_retweet' - } - } - if (retweet.entities.user_mentions.length < minMentions) { - return { - success: false, - error: 'not_enough_mentions' - } - } - let retweet_hashtags = []; - for (let i = 0; i < retweet.entities.hashtags.length; i++) { - retweet_hashtags[i] = retweet.entities.hashtags[i].text.toLowerCase(); - } - for (let i = 0; i < hashtags.length; i++) { - if (!retweet_hashtags.includes(hashtags[i].toLowerCase())) { - return { - success: false, - error: 'no_hashtags' - } - } - } - - return { - success: true, - error: '' - } - - }, - async checkIfAccountEligible(twitterAccount) { - - const twitterAccountSN = $u.getTwitterScreenName(twitterAccount); - console.log(`Checking if @${twitterAccountSN} eligible…`) - - let accountInfo = await getAccountInfo(twitterAccountSN); - // console.log(accountInfo); - - if (!accountInfo) { - return { - success: false, - error: 'request_failed', - } - } - - if (accountInfo.code === 50) { // {"code":50,"message":"User not found."} - return { - success: false, - error: 'user_not_found' - } - } - - if (accountInfo.code === 63) { // {"code":63,"message":"User has been suspended."} - return { - success: false, - error: 'user_not_found' - } - } - - if (!accountInfo.id) { - return { - success: false, - error: 'request_failed' - } - } - - if (accountInfo.followers_count < config.twitter_reqs.min_followers) { - return { - success: false, - error: 'no_followers', - accountInfo - } - } - if (accountInfo.friends_count < config.twitter_reqs.min_friends) { - return { - success: false, - error: 'no_friends', - accountInfo - } - } - if (accountInfo.statuses_count < config.twitter_reqs.min_statuses) { - return { - success: false, - error: 'no_statuses', - accountInfo - } - } - let createDate = parseTwitterDate(accountInfo.created_at); - let lifeTime = (Date.now() - createDate) / 1000 / 60 / 60 / 24; - if (lifeTime < config.twitter_reqs.min_days) { - return { - success: false, - error: 'no_lifetime', - accountInfo - } - } - - return { - success: true, - followers: accountInfo.followers_count, - lifetimeDays: lifeTime, - error: '', - accountInfo - } - - } - -} \ No newline at end of file +const $u = require('../helpers/utils'); +const log = require('../helpers/log'); +const config = require('./configReader'); +const Twitter = require('twitter')({ + consumer_key: config.twitter_api.consumer_key, + consumer_secret: config.twitter_api.consumer_secret, + access_token_key: config.twitter_api.access_token_key, + access_token_secret: config.twitter_api.access_token_secret, +}); + +const toFollowIds = {}; + +(async function() { + // Get Twitter accounts-to-follow ids by their names + + let name; let id; + for (let i = 0; i < config.twitter_follow.length; i++) { + name = $u.getTwitterScreenName(config.twitter_follow[i]); + id = (await getAccountInfo(config.twitter_follow[i])).id_str; + if (id) { + toFollowIds[name] = id; + } else { + log.error(`Unable to get Twitter ids of accounts to follow. Cannot start Bot.`); + process.exit(1); + } + } +})(); + +// +// async function getAccountFollowerIds(account) { +// const accountSN = $u.getTwitterScreenName(account); +// console.log(`Getting followers for @${accountSN}…`); +// +// let ids = []; +// return new Promise((resolve, reject) => { +// Twitter.get('followers/ids', {screen_name: accountSN, count: 5000, stringify_ids: true}, function getData(error, data, response) { +// try { +// if (error) { +// log.warn(`Twitter returned an error in getAccountFollowerIds(): ${JSON.stringify(error)}`); +// resolve(false); +// } else { +// ids = ids.concat(data.ids); +// // console.log(`next_cursor_str: `, data['next_cursor_str']); +// if (data['next_cursor_str'] > 0) { +// Twitter.get('followers/ids', {screen_name: accountSN, count: 5000, cursor: data['next_cursor_str']}, getData); +// } else { +// console.log(`FollowerIds count for @${accountSN} is ${ids.length}.`); +// resolve(ids); +// } +// } +// } catch (e) { +// log.warn(`Error while making getAccountFollowerIds() request: ${JSON.stringify(e)}`); +// resolve(false); +// } +// }); +// }); +// } + +async function getAccountFriendIds(account) { + const accountSN = $u.getTwitterScreenName(account); + console.log(`Getting friends for @${accountSN}…`); + + let ids = []; + return new Promise((resolve, reject) => { + Twitter.get('friends/ids', {screen_name: accountSN, count: 5000, stringify_ids: true}, function getData(error, data, response) { + try { + if (error) { + log.warn(`Twitter returned an error in getAccountFriendIds(): ${JSON.stringify(error)}`); + resolve(false); + } else { + ids = ids.concat(data.ids); + // console.log(`next_cursor_str: `, data['next_cursor_str']); + if (data['next_cursor_str'] > 0) { + Twitter.get('friends/ids', {screen_name: accountSN, count: 5000, cursor: data['next_cursor_str']}, getData); + } else { + console.log(`FriendIds count for @${accountSN} is ${ids.length}.`); + resolve(ids); + } + } + } catch (e) { + log.warn(`Error while making getAccountFriendIds() request: ${JSON.stringify(e)}`); + resolve(false); + } + }); + }); +} + +async function getAccountTimeline(account) { + const accountSN = $u.getTwitterScreenName(account); + console.log(`Getting timeline for @${accountSN}…`); + + return await Twitter.get('statuses/user_timeline', {screen_name: accountSN, count: 10, trim_user: true, tweet_mode: 'extended'}) + .then(function(data) { + // console.log(`Timeline for @${accountSN}:`); + // console.log(data); + + console.log(`Timeline count for @${accountSN} is ${data.length}.`); + return data; + }) + .catch(function(e) { + log.warn(`Error while making getAccountTimeline() request: ${JSON.stringify(e)}`); + return false; + }); +} + +async function getAccountInfo(account) { + const accountSN = $u.getTwitterScreenName(account); + // console.log(`Getting user info for @${accountSN}…`) + + return await Twitter.get('users/show', {screen_name: accountSN}) + .then(function(data) { + // console.log(`User info for @${accountSN}:`); + // console.log(data); + return data; + }) + .catch(function(e) { + log.warn(`Error while making getAccountInfo() request: ${JSON.stringify(e)}`); + if (e && e[0] && (e[0].code === 50 || e[0].code === 63)) { // [{"code":50,"message":"User not found."}, {"code":63,"message":"User has been suspended."}] + return e[0]; + } else { // User can provide wrong Account, process this situation + return false; + } + }); +} + +function parseTwitterDate(aDate) { + return new Date(Date.parse(aDate.replace(/( \+)/, ' UTC$1'))); + // sample: Wed Mar 13 09:06:07 +0000 2013 +} + +module.exports = { + + async testApi() { + const testResult = { + success: false, + message: '', + }; + + try { + const testAccount = '@TwitterDev'; + const result = await getAccountInfo(testAccount); + + if (result && result.id_str === '2244994945') { + testResult.success = true; + } else { + testResult.success = false; + testResult.message = 'Request *users/show* for @TwitterDev didn\'t return expected value.'; + } + return testResult; + } catch (e) { + testResult.success = false; + testResult.message = 'Exception while making *users/show* request.'; + return testResult; + } + }, + // Search for predefined toFollowIds — save Twitter API requests + // followAccount should be in "twitter_follow" param in config + async checkIfAccountFollowing(twitterAccount, followAccount) { + const twitterAccountSN = $u.getTwitterScreenName(twitterAccount); + const followAccountSN = $u.getTwitterScreenName(followAccount); + console.log(`Checking if @${twitterAccountSN} follows @${followAccountSN}…`); + + const followers = await getAccountFriendIds(twitterAccountSN); + // console.log(followers); + return followers.includes(toFollowIds[followAccountSN]); + }, + async checkIfAccountRetweetedwComment(twitterAccount, tweet, minMentions, hashtags) { + const twitterAccountSN = $u.getTwitterScreenName(twitterAccount); + const tweetId = $u.getTweetIdFromLink(tweet); + hashtags = $u.getTwitterHashtags(hashtags); + // console.log(tweetId); + console.log(`Checking if @${twitterAccountSN} retweeted ${tweet}…`); + + const tweets = await getAccountTimeline(twitterAccountSN); + let retweet = {}; + for (let i = 0; i < tweets.length; i++) { + if (tweets[i].quoted_status && tweets[i].quoted_status.id_str === tweetId) { + retweet = tweets[i]; + break; + } + } + if (Object.keys(retweet).length < 1) { // Empty object + return { + success: false, + error: 'no_retweet', + }; + } + if (retweet.entities.user_mentions.length < minMentions) { + return { + success: false, + error: 'not_enough_mentions', + }; + } + const retweet_hashtags = []; + for (let i = 0; i < retweet.entities.hashtags.length; i++) { + retweet_hashtags[i] = retweet.entities.hashtags[i].text.toLowerCase(); + } + for (let i = 0; i < hashtags.length; i++) { + if (!retweet_hashtags.includes(hashtags[i].toLowerCase())) { + return { + success: false, + error: 'no_hashtags', + }; + } + } + + return { + success: true, + error: '', + }; + }, + async checkIfAccountEligible(twitterAccount) { + const twitterAccountSN = $u.getTwitterScreenName(twitterAccount); + console.log(`Checking if @${twitterAccountSN} eligible…`); + + const accountInfo = await getAccountInfo(twitterAccountSN); + // console.log(accountInfo); + + if (!accountInfo) { + return { + success: false, + error: 'request_failed', + }; + } + + if (accountInfo.code === 50) { // {"code":50,"message":"User not found."} + return { + success: false, + error: 'user_not_found', + }; + } + + if (accountInfo.code === 63) { // {"code":63,"message":"User has been suspended."} + return { + success: false, + error: 'user_not_found', + }; + } + + if (!accountInfo.id) { + return { + success: false, + error: 'request_failed', + }; + } + + if (accountInfo.followers_count < config.twitter_reqs.min_followers) { + return { + success: false, + error: 'no_followers', + accountInfo, + }; + } + if (accountInfo.friends_count < config.twitter_reqs.min_friends) { + return { + success: false, + error: 'no_friends', + accountInfo, + }; + } + if (accountInfo.statuses_count < config.twitter_reqs.min_statuses) { + return { + success: false, + error: 'no_statuses', + accountInfo, + }; + } + const createDate = parseTwitterDate(accountInfo.created_at); + const lifeTime = (Date.now() - createDate) / 1000 / 60 / 60 / 24; + if (lifeTime < config.twitter_reqs.min_days) { + return { + success: false, + error: 'no_lifetime', + accountInfo, + }; + } + + return { + success: true, + followers: accountInfo.followers_count, + lifetimeDays: lifeTime, + error: '', + accountInfo, + }; + }, + +}; diff --git a/modules/unknownTxs.js b/modules/unknownTxs.js index c432e5c..b5188f8 100644 --- a/modules/unknownTxs.js +++ b/modules/unknownTxs.js @@ -1,193 +1,183 @@ -const $u = require('../helpers/utils'); -const db = require('./DB'); -const config = require('./configReader'); - -module.exports = async (tx, itx) => { - const {incomingTxsDb} = db; - - incomingTxsDb.db - .find({ - sender: tx.senderId, - type: 'unknown', - date: {$gt: ($u.unix() - 24 * 3600 * 1000)}, // last 24h - }).sort({date: -1}).toArray((err, docs) => { - const twoHoursAgo = $u.unix() - 2 * 3600 * 1000; - let countMsgs = docs.length; - if (!docs[1] || twoHoursAgo > docs[1].date){ - countMsgs = 1; - } - - let msg = ''; - if (countMsgs === 1) { - msg = config.welcome_string; - } - else if (countMsgs === 2) { - msg = 'OK. It seems you don’t speak English󠁧󠁢󠁥󠁮. Contact my master and ask him to teach me 🎓 your native language. But note, it will take some time because I am not a genius 🤓.'; - } - else if (countMsgs === 3) { - msg = 'Hm.. Contact _not me_, but my master. No, I don’t know how to reach him. ADAMANT is so much anonymous 🤪.'; - } - else if (countMsgs === 4) { - msg = 'I see.. You just wanna talk 🗣️. I am not the best at talking.'; - } - else if (countMsgs < 10) { - msg = getRnd(0); - } - else if (countMsgs < 20) { - msg = getRnd(1); - } - else if (countMsgs < 30) { - msg = getRnd(2); - } - else if (countMsgs < 40) { - msg = getRnd(3); - } - else if (countMsgs < 50) { - msg = getRnd(4); - } - else { - msg = getRnd(5); - } - $u.sendAdmMsg(tx.senderId, msg); - itx.update({isProcessed: true}, true); - }); - -}; - -function getRnd(collectionNum) { - const phrases = collection[collectionNum]; - const num = Math.floor(Math.random() * phrases.length); //The maximum is exclusive and the minimum is inclusive - return phrases[num]; -} - -const collection = [ - // 0 collection - [ - 'Do you wanna beer 🍺? I want to have it also, but now is the bounty time. Join in! 💰', - 'Aaaaghr..! 😱 Check out ₿ rates with **/rates BTC** command right now!', - 'I can tell you my rules by secret. ℹ️ Just say **/help**.', - 'I am just kiddin! 😛', - 'I’d like to work with you 🈺.', - 'ADAMANT is cool 😎, isn’t it?', - 'People do know me. I am decent. 😎 Ask somebody to confirm.', - 'I am really good 👌 at bounty campaigns.', - 'ADAMANT is perfect 💯. Read about it on their Blog.', - 'I recommend you to read about how ADAMANT is private 🔒 and anonymous.', - 'To pick up Emoji 😄, press Win + . on Windows, Cmd + Ctrl + Space on Mac, or use keyboard on iPhone and Android.', - 'Your IP is hidden 🕵️ in ADAMANT, as all connections go through nodes, but not directly as in P2P messengers.', - 'Blockchain offers Unprecedented Privacy and Security 🔑, did you know?', - 'Wallet private keys 🔑 are in your full control in ADAMANT.', - 'Convenient. Anonymous. Reliable. Instant. Oh, it is me! 💱', - 'ADAMANT is open source, including myself 🤖. Join to make me better! 📶', - 'Do you know what is ADAMANT 2FA?', - 'ADAMANT is soooo decentralized! And private! ❤️', - 'Recommend ADAMANT to your friends! 🌟', - 'If I were Satoshi, I’d rebuild Bitcoin ₿ on top of ADAMANT! 😍' - ], - // 1 collection - [ - 'Do you know what is ‘биток’?', - 'Yeah.. my English was born in cold ❄️ Russian village. I know. But my masters are good in programming 👨‍💻.', - 'I am working for ADAMANT for some time already. I have to admit guys feed me good. 🥪', - 'I love ADAMANT 💓. The team is doing all the best.', - 'London is a capital of Great Britain. 🤔', - 'To pick up Emoji 😄, press Win + . on Windows, Cmd + Ctrl + Space on Mac, or use keyboard on iPhone and Android.', - 'My mama told not to talk with strangers 🤐.', - 'Are you a girl or a boy? I am comfortable with girls 👧.', - 'Have you heard ADAMANT on Binance already? ..I am not 🙃.', - 'When Binance? 😲', - 'No, no. It is not good.', - 'D’oh! 😖', - 'Как тебе блокчейн на 1С, Илон Маск? 🙃', - 'And how do you like Blockchain on 1С, Elon Musk? 🤷', - 'Type **/calc 1 BTC in USD** to see Bitcoin price.', - 'ℹ️ Just say **/help** and I am here.', - 'Say **/rates ADM** and I will tell you all ADM prices 📈', - '😛 I am just kiddin!', - 'Can with you that the not so? 😮' - ], - // 2 collection - [ - 'Talk less! 🤐', - 'No, I am not. 🙅‍♂️', - 'I am not a scammer! 😠', - '1 ADM for 10 Ethers! 🤑 Deal! Buterin will understand soon who is the daddy.', - 'Ландон из э капитал оф грейт брит.. блять, я перебрал.. 🤣', - '❤️ Love is everything.', - 'Hey.. You disturb me! 💻 I am working!', - 'It seems you are good in talking 🗣️ only.', - 'OK. I better call you now 🤙', - 'I am not a motherf.. how do you know such words, little? 👿', - 'Do you know Satoshi 🤝 is my close friend?', - 'Are you programming in 1С? Try it! ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;', - '👨‍💻', - 'And how do you like Blockchain on 1С, Elon Musk?', - 'And how do you like this, Elon Musk? 😅', - 'I am quite now.', - 'I am just kiddin! 😆', - 'Can with you that the not so? 😅' - ], - // 3 collection - [ - 'My patience is over 😑.', - 'You want a ban I think 🤨', - 'I am tired of you.. ', - 'Booooooring! 💤', - '💱 Stop talking, go working?', - 'To ADAMANT! 🥂', - 'Ща бы пивка и дернуть кого-нибудь 👯', - 'Да ну эту крипту! Пойдем гульнем лучше! 🕺🏻', - 'Хорошо, что тып арускин епо немаишь 😁 гыгыггыгыггы', - 'Try to translate this: ‘На хера мне без хера, если с хером до хера!’', - 'Do you know you can get a ban 🚫 for much talking?', - 'Try to make blockchain in 1С! 😁 It is Russian secret programming language. Google it.', - 'Onion darknet? 🤷 No, I didnt heard.', - 'Кэн виз ю зэт зэ нот соу?', - 'Yeah! Party time! 🎉', - 'Do you drink vodka? I do.', - 'Can with you that the not so? 🔥', - 'I am just kiddin! 😄' - ], - // 4 collection - [ - 'Shut up.. 🤐', - 'I better find another bounty user 📱', - 'You want to be banned 🚫 for sure!', - 'Ok.. I understood. Come back tomorrow.', - 'Who is it behind you? A real Satoshi!? 😮', - 'Can with you that the not so?', - 'Do you know this code entry called ‘shit’? Check out in ADAMANT’s Github by yourself.', - 'УДОЛИЛ!!!!!!!!!1111111', - 'Some crazy guy taught me so much words to speak. Вот чо это за слово такое, таугхт? 🤦 Ёпт.', - 'Пошутили и хватит. Давайте к делу? ℹ️ Скажите **/help**, чтобы получить справку.', - 'I am here to trade, not to speak 😐', - 'While you talk, others make money.', - 'А-а-а-а-а-а! АДАМАНТ пампят! 😱', - 'Шоколотье, сомелье, залупэ.. Привет Чиверсу 🤘', - 'Делаем ставки. 🍽️ Макафи съест свой член?', - 'Ban-ban-ban.. 🚫', - 'АСТАНАВИТЕСЬ!', - 'Ё и Е — разные буквы. Не путай, инглишь-спикер!' - ], - // 5 collection - [ - '🐻 and 🐂 are those who make the market.', - 'I am hungry 🍲 now. Are you with me?', - 'To ADAMANT! 🥂', - '🍾 Happy trading!', - 'Who is it behind you? A real Satoshi!? 😮', - 'Can with you that the not so?', - 'Can you play 🎹? I do. No, I will not play for free.', - 'I would like to live in 🏝️. But reality is so cruel.', - 'Look! ADM is pumping! 🎉', - 'Do you know at my times computers were big and use floppy? 💾', - 'Hurry up! ADAMANT pump! 📈', - 'Биток уже за сотку тыщ баксов!?', - 'Давай уже к сделке. Нипонил как? Пешы **/help**.', - 'There will be time when 1 ADM = 10 BTC 🤑', - 'Try me! I can do it! 🙂', - 'Do you think Bitcoin SV is a scam?', - 'I like trading. Lets do a bargain right now! 🉐', - 'Не, ну это слишком. 🤩' - ] -]; +const $u = require('../helpers/utils'); +const db = require('./DB'); +const config = require('./configReader'); + +module.exports = async (tx, itx) => { + const {IncomingTxsDb} = db; + + IncomingTxsDb.db + .find({ + sender: tx.senderId, + type: 'unknown', + date: {$gt: ($u.unix() - 24 * 3600 * 1000)}, // last 24h + }).sort({date: -1}).toArray((err, docs) => { + const twoHoursAgo = $u.unix() - 2 * 3600 * 1000; + let countMsgs = docs.length; + if (!docs[1] || twoHoursAgo > docs[1].date) { + countMsgs = 1; + } + + let msg = ''; + if (countMsgs === 1) { + msg = config.welcome_string; + } else if (countMsgs === 2) { + msg = 'OK. It seems you don’t speak English󠁧󠁢󠁥󠁮. Contact my master and ask him to teach me 🎓 your native language. But note, it will take some time because I am not a genius 🤓.'; + } else if (countMsgs === 3) { + msg = 'Hm.. Contact _not me_, but my master. No, I don’t know how to reach him. ADAMANT is so much anonymous 🤪.'; + } else if (countMsgs === 4) { + msg = 'I see.. You just wanna talk 🗣️. I am not the best at talking.'; + } else if (countMsgs < 10) { + msg = getRnd(0); + } else if (countMsgs < 20) { + msg = getRnd(1); + } else if (countMsgs < 30) { + msg = getRnd(2); + } else if (countMsgs < 40) { + msg = getRnd(3); + } else if (countMsgs < 50) { + msg = getRnd(4); + } else { + msg = getRnd(5); + } + $u.sendAdmMsg(tx.senderId, msg); + itx.update({isProcessed: true}, true); + }); +}; + +function getRnd(collectionNum) { + const phrases = collection[collectionNum]; + const num = Math.floor(Math.random() * phrases.length); // The maximum is exclusive and the minimum is inclusive + return phrases[num]; +} + +const collection = [ + // 0 collection + [ + 'Do you wanna beer 🍺? I want to have it also, but now is the bounty time. Join in! 💰', + 'Aaaaghr..! 😱 Check out ₿ rates with **/rates BTC** command right now!', + 'I can tell you my rules by secret. ℹ️ Just say **/help**.', + 'I am just kiddin! 😛', + 'I’d like to work with you 🈺.', + 'ADAMANT is cool 😎, isn’t it?', + 'People do know me. I am decent. 😎 Ask somebody to confirm.', + 'I am really good 👌 at bounty campaigns.', + 'ADAMANT is perfect 💯. Read about it on their Blog.', + 'I recommend you to read about how ADAMANT is private 🔒 and anonymous.', + 'To pick up Emoji 😄, press Win + . on Windows, Cmd + Ctrl + Space on Mac, or use keyboard on iPhone and Android.', + 'Your IP is hidden 🕵️ in ADAMANT, as all connections go through nodes, but not directly as in P2P messengers.', + 'Blockchain offers Unprecedented Privacy and Security 🔑, did you know?', + 'Wallet private keys 🔑 are in your full control in ADAMANT.', + 'Convenient. Anonymous. Reliable. Instant. Oh, it is me! 💱', + 'ADAMANT is open source, including myself 🤖. Join to make me better! 📶', + 'Do you know what is ADAMANT 2FA?', + 'ADAMANT is soooo decentralized! And private! ❤️', + 'Recommend ADAMANT to your friends! 🌟', + 'If I were Satoshi, I’d rebuild Bitcoin ₿ on top of ADAMANT! 😍', + ], + // 1 collection + [ + 'Do you know what is ‘биток’?', + 'Yeah.. my English was born in cold ❄️ Russian village. I know. But my masters are good in programming 👨‍💻.', + 'I am working for ADAMANT for some time already. I have to admit guys feed me good. 🥪', + 'I love ADAMANT 💓. The team is doing all the best.', + 'London is a capital of Great Britain. 🤔', + 'To pick up Emoji 😄, press Win + . on Windows, Cmd + Ctrl + Space on Mac, or use keyboard on iPhone and Android.', + 'My mama told not to talk with strangers 🤐.', + 'Are you a girl or a boy? I am comfortable with girls 👧.', + 'Have you heard ADAMANT on Binance already? ..I am not 🙃.', + 'When Binance? 😲', + 'No, no. It is not good.', + 'D’oh! 😖', + 'Как тебе блокчейн на 1С, Илон Маск? 🙃', + 'And how do you like Blockchain on 1С, Elon Musk? 🤷', + 'Type **/calc 1 BTC in USD** to see Bitcoin price.', + 'ℹ️ Just say **/help** and I am here.', + 'Say **/rates ADM** and I will tell you all ADM prices 📈', + '😛 I am just kiddin!', + 'Can with you that the not so? 😮', + ], + // 2 collection + [ + 'Talk less! 🤐', + 'No, I am not. 🙅‍♂️', + 'I am not a scammer! 😠', + '1 ADM for 10 Ethers! 🤑 Deal! Buterin will understand soon who is the daddy.', + 'Ландон из э капитал оф грейт брит.. блять, я перебрал.. 🤣', + '❤️ Love is everything.', + 'Hey.. You disturb me! 💻 I am working!', + 'It seems you are good in talking 🗣️ only.', + 'OK. I better call you now 🤙', + 'I am not a motherf.. how do you know such words, little? 👿', + 'Do you know Satoshi 🤝 is my close friend?', + 'Are you programming in 1С? Try it! ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;', + '👨‍💻', + 'And how do you like Blockchain on 1С, Elon Musk?', + 'And how do you like this, Elon Musk? 😅', + 'I am quite now.', + 'I am just kiddin! 😆', + 'Can with you that the not so? 😅', + ], + // 3 collection + [ + 'My patience is over 😑.', + 'You want a ban I think 🤨', + 'I am tired of you.. ', + 'Booooooring! 💤', + '💱 Stop talking, go working?', + 'To ADAMANT! 🥂', + 'Ща бы пивка и дернуть кого-нибудь 👯', + 'Да ну эту крипту! Пойдем гульнем лучше! 🕺🏻', + 'Хорошо, что тып арускин епо немаишь 😁 гыгыггыгыггы', + 'Try to translate this: ‘На хера мне без хера, если с хером до хера!’', + 'Do you know you can get a ban 🚫 for much talking?', + 'Try to make blockchain in 1С! 😁 It is Russian secret programming language. Google it.', + 'Onion darknet? 🤷 No, I didnt heard.', + 'Кэн виз ю зэт зэ нот соу?', + 'Yeah! Party time! 🎉', + 'Do you drink vodka? I do.', + 'Can with you that the not so? 🔥', + 'I am just kiddin! 😄', + ], + // 4 collection + [ + 'Shut up.. 🤐', + 'I better find another bounty user 📱', + 'You want to be banned 🚫 for sure!', + 'Ok.. I understood. Come back tomorrow.', + 'Who is it behind you? A real Satoshi!? 😮', + 'Can with you that the not so?', + 'Do you know this code entry called ‘shit’? Check out in ADAMANT’s Github by yourself.', + 'УДОЛИЛ!!!!!!!!!1111111', + 'Some crazy guy taught me so much words to speak. Вот чо это за слово такое, таугхт? 🤦 Ёпт.', + 'Пошутили и хватит. Давайте к делу? ℹ️ Скажите **/help**, чтобы получить справку.', + 'I am here to trade, not to speak 😐', + 'While you talk, others make money.', + 'А-а-а-а-а-а! АДАМАНТ пампят! 😱', + 'Шоколотье, сомелье, залупэ.. Привет Чиверсу 🤘', + 'Делаем ставки. 🍽️ Макафи съест свой член?', + 'Ban-ban-ban.. 🚫', + 'АСТАНАВИТЕСЬ!', + 'Ё и Е — разные буквы. Не путай, инглишь-спикер!', + ], + // 5 collection + [ + '🐻 and 🐂 are those who make the market.', + 'I am hungry 🍲 now. Are you with me?', + 'To ADAMANT! 🥂', + '🍾 Happy trading!', + 'Who is it behind you? A real Satoshi!? 😮', + 'Can with you that the not so?', + 'Can you play 🎹? I do. No, I will not play for free.', + 'I would like to live in 🏝️. But reality is so cruel.', + 'Look! ADM is pumping! 🎉', + 'Do you know at my times computers were big and use floppy? 💾', + 'Hurry up! ADAMANT pump! 📈', + 'Биток уже за сотку тыщ баксов!?', + 'Давай уже к сделке. Нипонил как? Пешы **/help**.', + 'There will be time when 1 ADM = 10 BTC 🤑', + 'Try me! I can do it! 🙂', + 'Do you think Bitcoin SV is a scam?', + 'I like trading. Lets do a bargain right now! 🉐', + 'Не, ну это слишком. 🤩', + ], +]; diff --git a/server.js b/server.js index 6d31004..ea0bd69 100644 --- a/server.js +++ b/server.js @@ -1,43 +1,42 @@ -/** - * @description http watched DB tables - */ - -const express = require('express'); -const bodyParser = require('body-parser'); -const app = express(); -const config = require('./modules/configReader'); -const port = config.api; -const db = require('./modules/DB'); - -if (port) { - app.use(bodyParser.json()); // for parsing application/json - app.use(bodyParser.urlencoded({ - extended: true - })); // for parsing application/x-www-form-urlencoded - - app.get('/db', (req, res) => { - const tb = db[req.query.tb].db; - if (!tb) { - res.json({ - err: 'tb not find' - }); - return; - } - tb.find().toArray((err, data) => { - if (err) { - res.json({ - success: false, - err - }); - return; - } - res.json({ - success: true, - result: data - }); - }); - }); - - app.listen(port, () => console.info('Server listening on port ' + port + ' http://localhost:' + port + '/db?tb=systemDb')); - -} +/** + * @description http watched DB tables + */ + +const express = require('express'); +const bodyParser = require('body-parser'); +const app = express(); +const config = require('./modules/configReader'); +const port = config.api; +const db = require('./modules/DB'); + +if (port) { + app.use(bodyParser.json()); // for parsing application/json + app.use(bodyParser.urlencoded({ + extended: true, + })); // for parsing application/x-www-form-urlencoded + + app.get('/db', (req, res) => { + const tb = db[req.query.tb].db; + if (!tb) { + res.json({ + err: 'tb not find', + }); + return; + } + tb.find().toArray((err, data) => { + if (err) { + res.json({ + success: false, + err, + }); + return; + } + res.json({ + success: true, + result: data, + }); + }); + }); + + app.listen(port, () => console.info('Server listening on port ' + port + ' http://localhost:' + port + '/db?tb=systemDb')); +} From 9d54ed15021fdea9e6dd9a1f1cabbcc709170ac7 Mon Sep 17 00:00:00 2001 From: gost1k Date: Wed, 30 Mar 2022 23:24:51 +0300 Subject: [PATCH 23/55] refactor: upgrade adamant-api to 1.4 and refactor --- helpers/index.js | 44 +++++++++++++ helpers/utils/adm_utils.js | 63 +++++++++--------- helpers/utils/erc20_utils.js | 2 +- helpers/utils/eth_utils.js | 2 +- helpers/utils/index.js | 112 ++++---------------------------- modules/Store.js | 30 +++++---- modules/api.js | 2 +- modules/checkAdamantContacts.js | 89 ++++++++++++------------- modules/checkAll.js | 11 ++-- modules/checkTwitterFollow.js | 9 +-- modules/checkTwitterReqs.js | 9 +-- modules/checkTwitterRetweet.js | 9 +-- modules/checkTxs.js | 22 +++---- modules/checkerTransactions.js | 30 ++++++--- modules/commandTxs.js | 8 ++- modules/configReader.js | 12 ++++ modules/incomingTxsParser.js | 11 ++-- modules/outAddressFetcher.js | 6 +- modules/rewardsPayer.js | 8 ++- modules/sentTxValidator.js | 10 +-- modules/transferTxs.js | 4 +- modules/unknownTxs.js | 11 ++-- package.json | 2 +- 23 files changed, 254 insertions(+), 252 deletions(-) create mode 100644 helpers/index.js diff --git a/helpers/index.js b/helpers/index.js new file mode 100644 index 0000000..32695e7 --- /dev/null +++ b/helpers/index.js @@ -0,0 +1,44 @@ +const constants = require('./const'); +module.exports = { + unix() { + return new Date().getTime(); + }, + getModuleName(id) { + let n = id.lastIndexOf('\\'); + if (n === -1) { + n = id.lastIndexOf('/'); + } + if (n === -1) { + return ''; + } else { + return id.substring(n + 1); + } + }, + toTimestamp(epochTime) { + return epochTime * 1000 + constants.EPOCH; + }, + thousandSeparator(num, doBold) { + const parts = (num + '').split('.'); + const main = parts[0]; + const len = main.length; + let output = ''; + let i = len - 1; + + while (i >= 0) { + output = main.charAt(i) + output; + if ((len - i) % 3 === 0 && i > 0) { + output = ' ' + output; + } + --i; + } + + if (parts.length > 1) { + if (doBold) { + output = `**${output}**.${parts[1]}`; + } else { + output = `${output}.${parts[1]}`; + } + } + return output; + }, +}; diff --git a/helpers/utils/adm_utils.js b/helpers/utils/adm_utils.js index 2b5bbe5..43ff9a3 100644 --- a/helpers/utils/adm_utils.js +++ b/helpers/utils/adm_utils.js @@ -1,6 +1,8 @@ const Store = require('../../modules/Store'); const api = require('../../modules/api'); const log = require('../log'); +const config = require('../../modules/configReader'); +const helpers = require('../../helpers'); const {SAT} = require('../const'); const User = Store.user.ADM; @@ -17,53 +19,48 @@ module.exports = { amount: +(tx.amount / SAT).toFixed(8), }; }, - async getLastBlockNumber() { - try { - return (await api.get('uri', 'blocks?limit=1')).blocks[0].height; - } catch (e) { - return null; + async getLastBlock() { + const blocks = await api.get('blocks', {limit: 1}); + if (blocks.success) { + return blocks.data.blocks[0].height; + } else { + log.warn(`Failed to get last block in getLastBlock() of ${helpers.getModuleName()} module. ${blocks.errorMessage}.`); } }, - async getTransactionStatus(txid) { - try { - const tx = (await api.get('uri', 'transactions/get?id=' + txid)).transaction; + async getTransactionStatus(txId) { + const tx = await api.get('transactions/get', {id: txId}); + if (tx.success) { return { blockNumber: tx.height, status: true, }; - } catch (e) { - return null; + } else { + log.warn(`Failed to get Tx ${txId} in getTransactionStatus() of ${helpers.getModuleName()} module. ${tx.errorMessage}.`); } }, async send(params) { - try { - const {address, value, comment} = params; - log.log(`Sending ${value} ADM with comment: ${comment}`); - let res; - if (comment) { - res = api.send(User.passPhrase, address, comment, 'message', null, value); - } else { - res = api.send(User.passPhrase, address, value, null, comment); - } - - if (!res) { - return { - success: false, - }; - } + const {address, value, comment} = params; + const payment = await api.sendMessage(config.passPhrase, address, comment, 'basic', value); + if (payment.success) { + log.log(`Successfully sent ${value} ADM to ${address} with comment '${comment}', Tx hash: ${payment.data.transactionId}.`); + return { + success: payment.data.success, + hash: payment.data.transactionId, + }; + } else { + log.warn(`Failed to send ${value} ADM to ${address} with comment ${comment} in send() of ${helpers.getModuleName()} module. ${payment.errorMessage}.`); return { - success: res.success, - hash: res.transactionId, + success: false, + error: payment.errorMessage, }; - } catch (e) { - log.error('Error while sending ADM in Utils module: ' + e); } }, async updateBalance() { - try { - User.balance = (await api.get('uri', 'accounts?address=' + User.address)).account.balance / SAT; - } catch (e) { - log.error('Error while getting ADM balance in Utils module: ' + e); + const account = await api.get('accounts', {address: config.address}); + if (account.success) { + User.balance = account.data.account.balance / SAT; + } else { + log.warn(`Failed to get account info in updateBalance() of ${helpers.getModuleName()} module. ${account.errorMessage}.`); } }, }; diff --git a/helpers/utils/erc20_utils.js b/helpers/utils/erc20_utils.js index 81b63a2..7d7a63c 100644 --- a/helpers/utils/erc20_utils.js +++ b/helpers/utils/erc20_utils.js @@ -35,7 +35,7 @@ class Erc20 { } async getLastBlockNumber() { - return await eth.getLastBlockNumber(); + return await eth.getLastBlock(); } async syncGetTransaction(hash) { diff --git a/helpers/utils/eth_utils.js b/helpers/utils/eth_utils.js index 7a1875c..36dabc5 100644 --- a/helpers/utils/eth_utils.js +++ b/helpers/utils/eth_utils.js @@ -51,7 +51,7 @@ module.exports = { }); }); }, - getLastBlockNumber() { + getLastBlock() { return new Promise((resolve) => { eth.getBlock('latest').then((block) => { if (block) { diff --git a/helpers/utils/index.js b/helpers/utils/index.js index cfd46ef..96e3c7d 100644 --- a/helpers/utils/index.js +++ b/helpers/utils/index.js @@ -3,95 +3,25 @@ const config = require('../../modules/configReader'); const eth_utils = require('./eth_utils'); const adm_utils = require('./adm_utils'); const log = require('../log'); -const db = require('../../modules/DB'); const Store = require('../../modules/Store'); -const constants = require('../const'); +const helpers = require('../../helpers'); module.exports = { - unix() { - return new Date().getTime(); - }, - /** - * Converts provided `time` to ADAMANTS's epoch timestamp: in seconds starting from Sep 02 2017 17:00:00 GMT+0000 - * @param {number=} time timestamp to convert - * @return {number} - */ - epochTime(time) { - if (!time) { - time = Date.now(); - } - return Math.floor((time - constants.EPOCH) / 1000); - }, - /** - * Converts ADM epoch timestamp to a Unix timestamp - * @param {number} epochTime timestamp to convert - * @return {number} - */ - toTimestamp(epochTime) { - return epochTime * 1000 + constants.EPOCH; - }, - sendAdmMsg(address, msg, type = 'message') { - if (msg && !config.isDev) { - try { - return api.send(config.passPhrase, address, msg, type).success || false; - } catch (e) { - return false; - } - } - }, - thousandSeparator(num, doBold) { - const parts = (num + '').split('.'); - const main = parts[0]; - const len = main.length; - let output = ''; - let i = len - 1; - - while (i >= 0) { - output = main.charAt(i) + output; - if ((len - i) % 3 === 0 && i > 0) { - output = ' ' + output; - } - --i; + async getAddressCryptoFromAdmAddressADM(coin, admAddress) { + if (this.isERC20(coin)) { + coin = 'ETH'; } - - if (parts.length > 1) { - if (doBold) { - output = `**${output}**.${parts[1]}`; + const res = await api.get('states/get', {senderId: admAddress, key: coin.toLowerCase() + ':address'}); + if (res.success) { + if (res.data.transactions.length) { + return res.data.transactions[0].asset.state.value; } else { - output = `${output}.${parts[1]}`; - } - } - return output; - }, - async getAddressCryptoFromAdmAddressADM(coin, admAddress) { - try { - if (this.isERC20(coin)) { - coin = 'ETH'; + return 'none'; } - const resp = await api.syncGet(`/api/states/get?senderId=${admAddress}&key=${coin.toLowerCase()}:address`); - if (resp && resp.success) { - if (resp.transactions.length) { - return resp.transactions[0].asset.state.value; - } else { - return 'none'; - } - } - } catch (e) { - log.error(' in getAddressCryptoFromAdmAddressADM(): ' + e); - return null; + } else { + log.warn(`Failed to get ${coin} address for ${admAddress} from KVS in getAddressCryptoFromAdmAddressADM() of ${helpers.getModuleName()} module. ${res.errorMessage}.`); } }, - async userDailyValue(senderId) { - return (await db.PaymentsDb.find({ - transactionIsValid: true, - senderId: senderId, - needToSendBack: false, - inAmountMessageUsd: {$ne: null}, - date: {$gt: (this.unix() - 24 * 3600 * 1000)}, // last 24h - })).reduce((r, c) => { - return +r + +c.inAmountMessageUsd; - }, 0); - }, async updateAllBalances() { try { await this.ETH.updateBalance(); @@ -103,8 +33,8 @@ module.exports = { }, async getLastBlocksNumbers() { const data = { - ETH: await this.ETH.getLastBlockNumber(), - ADM: await this.ADM.getLastBlockNumber(), + ETH: await this.ETH.getLastBlock(), + ADM: await this.ADM.getLastBlock(), }; for (const t of config.erc20) { // data[t] = await this[t].getLastBlockNumber(); // Don't do unnecessary requests @@ -131,11 +61,6 @@ module.exports = { isERC20(coin) { return config.erc20.includes(coin.toUpperCase()); }, - isArraysEqual(array1, array2) { - return array1.length === array2.length && array1.sort().every(function(value, index) { - return value === array2.sort()[index]; - }); - }, getAccounts(message) { const userAccounts = {}; userAccounts.notEmpty = false; @@ -213,17 +138,6 @@ module.exports = { } return tags; }, - getModuleName(id) { - let n = id.lastIndexOf('\\'); - if (n === -1) { - n = id.lastIndexOf('/'); - } - if (n === -1) { - return ''; - } else { - return id.substring(n + 1); - } - }, ETH: eth_utils, ADM: adm_utils, }; diff --git a/modules/Store.js b/modules/Store.js index b3ebe4f..7cc021b 100644 --- a/modules/Store.js +++ b/modules/Store.js @@ -2,6 +2,8 @@ const db = require('./DB'); const log = require('../helpers/log'); const keys = require('adamant-api/helpers/keys'); const api = require('./api'); +const axios = require('axios'); +const helpers = require('../helpers'); const {version} = require('../package.json'); const config = require('./configReader'); @@ -39,28 +41,34 @@ module.exports = { this[field] = data; }, async updateLastBlock() { - try { - const lastBlock = (await api.get('uri', 'blocks')).blocks[0]; - this.updateSystem('lastBlock', lastBlock); - } catch (e) { - log.error('Error while updating lastBlock: ' + e); + const blocks = await api.get('blocks', {limit: 1}); + if (blocks.success) { + this.updateSystem('lastBlock', blocks.data.blocks[0]); + } else { + log.warn(`Failed to get last block in updateLastBlock() of ${helpers.getModuleName()} module. ${blocks.errorMessage}.`); } }, async updateCurrencies() { + const url = config.infoservice + '/get'; try { - const data = await api.syncGet(config.infoservice + '/get', true); - if (data.success) { - this.currencies = data.result; + const res = await axios.get(url, {}); + if (res) { + const data = res?.data?.result; + if (data) { + this.currencies = data; + } else { + log.warn(`Error in updateCurrencies() of ${helpers.getModuleName()} module: Request to ${url} returned empty result.`); + } } - } catch (e) { - log.error('Error while updating currencies: ' + e); + } catch (error) { + log.warn(`Error in updateCurrencies() of ${helpers.getModuleName()} module: Request to ${url} failed with ${error.response ? error.response.status : undefined} status code, ${error.toString()}${error.response && error.response.data ? '. Message: ' + error.response.data.toString().trim() : ''}.`); } }, getPrice(from, to) { try { from = from.toUpperCase(); to = to.toUpperCase(); - const price = + (this.currencies[from + '/' + to] || 1 / this.currencies[to + '/' + from] || 0).toFixed(8); + const price = +(this.currencies[from + '/' + to] || 1 / this.currencies[to + '/' + from] || 0).toFixed(8); if (price) { return price; } diff --git a/modules/api.js b/modules/api.js index 4dabf63..a19185b 100644 --- a/modules/api.js +++ b/modules/api.js @@ -1,3 +1,3 @@ const log = require('../helpers/log'); const config = require('./configReader'); -module.exports = require('adamant-api')({passPhrase: config.passPhrase, node: config.node_ADM, logLevel: 'warn'}, log); +module.exports = require('adamant-api')({node: config.node_ADM, logLevel: config.log_level}, log); diff --git a/modules/checkAdamantContacts.js b/modules/checkAdamantContacts.js index 35656ef..17279c4 100644 --- a/modules/checkAdamantContacts.js +++ b/modules/checkAdamantContacts.js @@ -1,6 +1,6 @@ const db = require('./DB'); const config = require('./configReader'); -const $u = require('../helpers/utils'); +const helpers = require('../helpers'); const log = require('../helpers/log'); const api = require('./api'); const Store = require('./Store'); @@ -45,58 +45,59 @@ module.exports = async () => { userId, } = user; - log.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); + log.log(`Running module ${helpers.getModuleName(module.id)} for user ${userId}…`); let msgSendBack = ''; - let contacts_number = 0; - - const txChat_all = (await api.get('uri', 'chats/get/?recipientId=' + userId + '&orderBy=timestamp:desc&limit=100')).transactions; - const txChat = txChat_all.filter((v, i, a)=>a.findIndex((t)=>(t.senderId === v.senderId))===i); - - let contact_firstchat; - let hours_old; - let need_new_contacts = false; - - for (let index = 0; ((index < txChat.length) && (contacts_number < config.adamant_campaign.min_contacts)); index++) { - if (excluded_adm_contacts.includes(txChat[index].senderId)) { - continue; - } - contact_firstchat = (await api.get('uri', 'chats/get/?senderId=' + txChat[index].senderId + '&orderBy=timestamp:asc&limit=1')); - contact_firstchat = contact_firstchat.transactions[0].timestamp; - hours_old = ($u.unix() - $u.toTimestamp(contact_firstchat)) / 1000 / 60 / 60; - if (hours_old > hours_for_new_contact) { - need_new_contacts = true; - continue; + let contactsNumber = 0; + const txChatAll = await api.get('chats/get', {recipientId: userId, orderBy: 'timestamp:desc', limit: 100}); + if (txChatAll.success) { + const txChat = txChatAll.data.transactions.filter((v, i, a)=>a.findIndex((t)=>(t.senderId === v.senderId))===i); + + let need_new_contacts = false; + + for (let index = 0; ((index < txChat.length) && (contactsNumber < config.adamant_campaign.min_contacts)); index++) { + if (excluded_adm_contacts.includes(txChat[index].senderId)) { + continue; + } + const res = await api.get('chats/get', {senderId: txChat[index].senderId, orderBy: 'timestamp:asc', limit: 1}); + if (res.success) { + const contactFirstChat = res.data.transactions[0].timestamp; + const hoursOld = (helpers.unix() - helpers.toTimestamp(contactFirstChat)) / 1000 / 60 / 60; + if (hoursOld > hours_for_new_contact) { + need_new_contacts = true; + continue; + } + contactsNumber += 1; + } } - contacts_number += 1; - } - - const isContactsDone = contacts_number >= config.adamant_campaign.min_contacts; - console.log('isContactsDone:', isContactsDone, contacts_number); - if (isContactsDone) { - log.log(`User ${userId}… did make ${config.adamant_campaign.min_contacts} contacts.`); - await user.update({ - isAdamantCheckPassed: true, - }, true); - } else { - await user.update({ - isAdamantCheckPassed: false, - isInCheck: false, - isTasksCompleted: false, - }, true); + const isContactsDone = contactsNumber >= config.adamant_campaign.min_contacts; + console.log('isContactsDone:', isContactsDone, contactsNumber); - if (need_new_contacts) { - msgSendBack = `To meet the Bounty campaign rules, you should invite ${config.adamant_campaign.min_contacts} friends in ADAMANT Messenger. They _should be new ADAMANT users_ and message you. They can join this bounty campaign as well! Invite friends and apply again.`; + if (isContactsDone) { + log.log(`User ${userId}… did make ${config.adamant_campaign.min_contacts} contacts.`); + await user.update({ + isAdamantCheckPassed: true, + }, true); } else { - msgSendBack = `To meet the Bounty campaign rules, you should invite ${config.adamant_campaign.min_contacts} friends in ADAMANT Messenger. They must message you. They can join this bounty campaign as well! Invite friends and apply again.`; + await user.update({ + isAdamantCheckPassed: false, + isInCheck: false, + isTasksCompleted: false, + }, true); + + if (need_new_contacts) { + msgSendBack = `To meet the Bounty campaign rules, you should invite ${config.adamant_campaign.min_contacts} friends in ADAMANT Messenger. They _should be new ADAMANT users_ and message you. They can join this bounty campaign as well! Invite friends and apply again.`; + } else { + msgSendBack = `To meet the Bounty campaign rules, you should invite ${config.adamant_campaign.min_contacts} friends in ADAMANT Messenger. They must message you. They can join this bounty campaign as well! Invite friends and apply again.`; + } + + await api.sendMessage(config.passPhrase, userId, msgSendBack); + log.log(`User ${userId}… did NOT make ${config.adamant_campaign.min_contacts} contacts. Message to user: ${msgSendBack}`); } - - await $u.sendAdmMsg(userId, msgSendBack); - log.log(`User ${userId}… did NOT make ${config.adamant_campaign.min_contacts} contacts. Message to user: ${msgSendBack}`); } } catch (e) { - log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); + log.error(`Error in ${helpers.getModuleName(module.id)} module: ${e}`); } } } finally { diff --git a/modules/checkAll.js b/modules/checkAll.js index 146fe52..76849da 100644 --- a/modules/checkAll.js +++ b/modules/checkAll.js @@ -1,6 +1,7 @@ const db = require('./DB'); const config = require('./configReader'); -const $u = require('../helpers/utils'); +const helpers = require('../helpers'); +const api = require('./api'); const log = require('../helpers/log'); const notify = require('../helpers/notify'); const mathjs = require('mathjs'); @@ -31,7 +32,7 @@ module.exports = async () => { twitterLifetimeDays, } = user; - log.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); + log.log(`Running module ${helpers.getModuleName(module.id)} for user ${userId}…`); let msgSendBack = ''; @@ -54,7 +55,7 @@ module.exports = async () => { amount = +amount.toFixed(config.rewards_progression_from_twitter_followers[reward.currency].decimals_transfer); } const payment = new PaymentsDb({ - date: $u.unix(), + date: helpers.unix(), userId, isPayed: false, isFinished: false, @@ -82,10 +83,10 @@ module.exports = async () => { notify(`${config.notifyName}: User ${userId}${twitterString} completed the Bounty tasks. Payouts are pending.`, 'log'); } msgSendBack = `Great, you've completed all the tasks! Reward is coming right now!`; - $u.sendAdmMsg(userId, msgSendBack); + await api.sendMessage(config.passPhrase, userId, msgSendBack); } } catch (e) { - log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); + log.error(`Error in ${helpers.getModuleName(module.id)} module: ${e}`); } } } finally { diff --git a/modules/checkTwitterFollow.js b/modules/checkTwitterFollow.js index 78e845a..463cde6 100644 --- a/modules/checkTwitterFollow.js +++ b/modules/checkTwitterFollow.js @@ -1,6 +1,7 @@ const db = require('./DB'); const config = require('./configReader'); -const $u = require('../helpers/utils'); +const api = require('./api'); +const helpers = require('../helpers'); const log = require('../helpers/log'); const twitterapi = require('./twitterapi'); @@ -33,7 +34,7 @@ module.exports = async () => { userId, } = user; - log.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); + log.log(`Running module ${helpers.getModuleName(module.id)} for user ${userId}…`); let msgSendBack = ''; @@ -53,7 +54,7 @@ module.exports = async () => { isTasksCompleted: false, }, true); msgSendBack = `To meet the Bounty campaign rules, you should follow Twitter account ${followAccount}. Then you apply again.`; - await $u.sendAdmMsg(userId, msgSendBack); + await api.sendMessage(config.passPhrase, userId, msgSendBack); log.log(`User ${userId}… ${twitterAccount} do NOT follows ${followAccount}. Message to user: ${msgSendBack}`); break; } @@ -62,7 +63,7 @@ module.exports = async () => { isTwitterFollowCheckPassed: isFollowing, }, true); } catch (e) { - log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); + log.error(`Error in ${helpers.getModuleName(module.id)} module: ${e}`); } } } finally { diff --git a/modules/checkTwitterReqs.js b/modules/checkTwitterReqs.js index 5850800..9df7214 100644 --- a/modules/checkTwitterReqs.js +++ b/modules/checkTwitterReqs.js @@ -1,6 +1,7 @@ const db = require('./DB'); const config = require('./configReader'); -const $u = require('../helpers/utils'); +const helpers = require('../helpers'); +const api = require('./api'); const log = require('../helpers/log'); const twitterapi = require('./twitterapi'); @@ -32,7 +33,7 @@ module.exports = async () => { userId, } = user; - log.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); + log.log(`Running module ${helpers.getModuleName(module.id)} for user ${userId}…`); let msgSendBack = ''; let isEligible = true; @@ -76,7 +77,7 @@ module.exports = async () => { msgSendBack = `To meet the Bounty campaign rules, your Twitter account ${config.twitterEligibleString}.`; } - await $u.sendAdmMsg(userId, msgSendBack); + await api.sendMessage(config.passPhrase, userId, msgSendBack); log.log(`User ${userId}… ${twitterAccount} is NOT eligible. Message to user: ${msgSendBack}`); } @@ -87,7 +88,7 @@ module.exports = async () => { twitterFollowers: result.followers, }, true); } catch (e) { - log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); + log.error(`Error in ${helpers.getModuleName(module.id)} module: ${e}`); } } } finally { diff --git a/modules/checkTwitterRetweet.js b/modules/checkTwitterRetweet.js index 2d22587..657be3b 100644 --- a/modules/checkTwitterRetweet.js +++ b/modules/checkTwitterRetweet.js @@ -1,6 +1,7 @@ const db = require('./DB'); const config = require('./configReader'); -const $u = require('../helpers/utils'); +const helpers = require('../helpers'); +const api = require('./api'); const log = require('../helpers/log'); const twitterapi = require('./twitterapi'); @@ -38,7 +39,7 @@ module.exports = async () => { userId, } = user; - log.log(`Running module ${$u.getModuleName(module.id)} for user ${userId}…`); + log.log(`Running module ${helpers.getModuleName(module.id)} for user ${userId}…`); let msgSendBack = ''; @@ -106,7 +107,7 @@ module.exports = async () => { break; } - await $u.sendAdmMsg(userId, msgSendBack); + await api.sendMessage(config.passPhrase, userId, msgSendBack); log.log(`User ${userId}… ${twitterAccount} did NOT retweet ${toRetweet}: ${retweetResult.error}. Message to user: ${msgSendBack}`); break; @@ -116,7 +117,7 @@ module.exports = async () => { isTwitterRetweetCommentCheckPassed: isRetweeted, }, true); } catch (e) { - log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); + log.error(`Error in ${helpers.getModuleName(module.id)} module: ${e}`); } } } finally { diff --git a/modules/checkTxs.js b/modules/checkTxs.js index 4bf70c3..c8b56f2 100644 --- a/modules/checkTxs.js +++ b/modules/checkTxs.js @@ -1,17 +1,17 @@ - +const helpers = require('../helpers'); const db = require('./DB'); -const $u = require('../helpers/utils'); +const api = require('./api'); const log = require('../helpers/log'); +const config = require('./configReader'); module.exports = async (itx, tx) => { - console.log(`Running module ${$u.getModuleName(module.id)}…`); + console.log(`Running module ${helpers.getModuleName(module.id)}…`); const {UsersDb} = db; - let user = {}; let msgSendBack = ''; // Exclude duplicate Twitter accounts - user = await UsersDb.findOne({twitterAccount: itx.accounts.twitterAccount}); + let user = await UsersDb.findOne({twitterAccount: itx.accounts.twitterAccount}); if (user && (user.isInCheck || user.isTasksCompleted)) { // This Twitter account is already in use by other user, unable to switch log.warn(`User ${user.userId} applied with already used Twitter account ${itx.accounts.twitterAccount}. Notify user and ignore.`); @@ -23,7 +23,7 @@ module.exports = async (itx, tx) => { } } if (msgSendBack) { // Do not send anything, if isInCheck - $u.sendAdmMsg(tx.senderId, msgSendBack); + await api.sendMessage(config.passPhrase, tx.senderId, msgSendBack); } return; } @@ -42,12 +42,12 @@ module.exports = async (itx, tx) => { if (user.isTasksCompleted) { log.log(`User ${user.userId} already completed the Bounty tasks. Notify user and ignore.`); msgSendBack = `You've already completed the Bounty tasks.`; - $u.sendAdmMsg(tx.senderId, msgSendBack); + await api.sendMessage(config.passPhrase, tx.senderId, msgSendBack); return; } user.update({ - dateUpdated: $u.unix(), + dateUpdated: helpers.unix(), admTxId: tx.id, msg: itx.encrypted_content, isInCheck: itx.accounts.notEmpty, @@ -65,8 +65,8 @@ module.exports = async (itx, tx) => { user = new UsersDb({ _id: tx.senderId, userId: tx.senderId, - dateCreated: $u.unix(), - dateUpdated: $u.unix(), + dateCreated: helpers.unix(), + dateUpdated: helpers.unix(), admTxId: tx.id, msg: itx.encrypted_content, isInCheck: itx.accounts.notEmpty, @@ -85,5 +85,5 @@ module.exports = async (itx, tx) => { await itx.update({isProcessed: true}, true); msgSendBack = `I've got your account details. Twitter: ${user.twitterAccount}. I'll check if you've finished the Bounty tasks now…`; - $u.sendAdmMsg(tx.senderId, msgSendBack); + await api.sendMessage(config.passPhrase, tx.senderId, msgSendBack); }; diff --git a/modules/checkerTransactions.js b/modules/checkerTransactions.js index 300ae3f..bd4faa1 100644 --- a/modules/checkerTransactions.js +++ b/modules/checkerTransactions.js @@ -1,4 +1,6 @@ const Store = require('./Store'); +const helpers = require('../helpers'); +const config = require('./configReader'); const api = require('./api'); const txParser = require('./incomingTxsParser'); const log = require('../helpers/log'); @@ -8,16 +10,26 @@ async function check() { if (!Store.lastHeight) { return; } - const txChat = (await api.get('uri', 'chats/get/?recipientId=' + Store.user.ADM.address + '&orderBy=timestamp:desc&fromHeight=' + (Store.lastHeight - 5))).transactions; - const txTrx = (await api.get('transactions', 'fromHeight=' + (Store.lastHeight - 5) + '&and:recipientId=' + Store.user.ADM.address + '&and:type=0')).transactions; + let txs = []; + const txChat = await api.get('chats/get', {recipientId: config.address, orderBy: 'timestamp:desc', fromHeight: Store.lastHeight - 5}); + if (txChat.success) { + txs = txs.concat(txChat.data.transactions); + } else { + log.warn(`Failed to chat Txs in check() of ${helpers.getModuleName()} module. ${txChat.errorMessage}.`); + } + + const txTrx = await api.get('transactions', {fromHeight: Store.lastHeight - 5, 'and:recipientId': config.address, 'and:type': 0}); + if (txTrx.success) { + txs = txs.concat(txTrx.data.transactions); + } else { + log.warn(`Failed to chat Txs in check() of ${helpers.getModuleName()} module. ${txTrx.errorMessage}.`); + } - if (txChat && txTrx) { - txChat - .concat(txTrx) - .forEach((t) => { - txParser(t); - }); - Store.updateLastBlock(); + txs.forEach((tx) => { + txParser(tx); + }); + if (txChat.success && txTrx.success) { + await Store.updateLastBlock(); } } catch (e) { log.error('Error while checking new transactions: ' + e); diff --git a/modules/commandTxs.js b/modules/commandTxs.js index cfa2ef3..6066e9c 100644 --- a/modules/commandTxs.js +++ b/modules/commandTxs.js @@ -1,5 +1,7 @@ const Store = require('../modules/Store'); const $u = require('../helpers/utils'); +const api = require('./api'); +const helpers = require('./../helpers'); const config = require('./configReader'); const log = require('../helpers/log'); const notify = require('../helpers/notify'); @@ -26,7 +28,7 @@ module.exports = async (cmd, tx, itx) => { isNonAdmin: true, }, true); if (config.notify_non_admins) { - $u.sendAdmMsg(tx.senderId, `I won't execute admin commands as you are not an admin. Contact my master.`); + await api.sendMessage(config.passPhrase, tx.senderId, `I won't execute admin commands as you are not an admin. Contact my master.`); } return; } @@ -46,7 +48,7 @@ module.exports = async (cmd, tx, itx) => { notify(res.msgNotify, res.notifyType); } if (res.msgSendBack) { - $u.sendAdmMsg(tx.senderId, res.msgSendBack); + await api.sendMessage(config.passPhrase, tx.senderId, res.msgSendBack); } } } catch (e) { @@ -138,7 +140,7 @@ async function calc(arr) { if ($u.isFiat(outCurrency)) { result = +result.toFixed(2); } - output = `Global market value of ${$u.thousandSeparator(amount)} ${inCurrency} equals **${$u.thousandSeparator(result)} ${outCurrency}**.`; + output = `Global market value of ${helpers.thousandSeparator(amount)} ${inCurrency} equals **${helpers.thousandSeparator(result)} ${outCurrency}**.`; } } diff --git a/modules/configReader.js b/modules/configReader.js index 9f2aaae..c5240cf 100644 --- a/modules/configReader.js +++ b/modules/configReader.js @@ -16,6 +16,14 @@ const fields = { type: Array, isRequired: true, }, + node_LSK: { + type: Array, + isRequired: true, + }, + service_LSK: { + type: Array, + isRequired: true, + }, infoservice: { type: Array, default: ['https://info.adamant.im'], @@ -92,6 +100,10 @@ const fields = { type: String, default: 'I have nothing to say. If you are my master, check my config.', }, + log_level: { + type: String, + default: 'log', + }, }; try { diff --git a/modules/incomingTxsParser.js b/modules/incomingTxsParser.js index 144ca48..2f06622 100644 --- a/modules/incomingTxsParser.js +++ b/modules/incomingTxsParser.js @@ -2,6 +2,7 @@ const db = require('./DB'); const log = require('../helpers/log'); const $u = require('../helpers/utils'); const api = require('./api'); +const helpers = require('./../helpers'); const config = require('./configReader'); const commandTxs = require('./commandTxs'); const unknownTxs = require('./unknownTxs'); @@ -54,13 +55,13 @@ module.exports = async (tx) => { const spamerIsNotyfy = await IncomingTxsDb.findOne({ sender: tx.senderId, isSpam: true, - date: {$gt: ($u.unix() - 24 * 3600 * 1000)}, // last 24h + date: {$gt: (helpers.unix() - 24 * 3600 * 1000)}, // last 24h }); const itx = new IncomingTxsDb({ _id: tx.id, txid: tx.id, - date: $u.unix(), + date: helpers.unix(), block_id: tx.blockId, encrypted_content: msg, accounts: accounts, @@ -73,7 +74,7 @@ module.exports = async (tx) => { const countRequestsUser = (await IncomingTxsDb.find({ sender: tx.senderId, - date: {$gt: ($u.unix() - 24 * 3600 * 1000)}, // last 24h + date: {$gt: (helpers.unix() - 24 * 3600 * 1000)}, // last 24h })).length; if (countRequestsUser > 50 || spamerIsNotyfy) { // 50 per 24h is a limit for accepting commands, otherwise user will be considered as spammer @@ -87,11 +88,11 @@ module.exports = async (tx) => { if (historyTxs[tx.id]) { return; } - historyTxs[tx.id] = $u.unix(); + historyTxs[tx.id] = helpers.unix(); if (itx.isSpam && !spamerIsNotyfy) { notify(`${config.notifyName} notifies _${tx.senderId}_ is a spammer or talks too much. Income ADAMANT Tx: https://explorer.adamant.im/tx/${tx.id}.`, 'warn'); - $u.sendAdmMsg(tx.senderId, `I’ve _banned_ you. You’ve sent too much transactions to me.`); + await api.sendMessage(config.passPhrase, tx.senderId, `I’ve _banned_ you. You’ve sent too much transactions to me.`); return; } diff --git a/modules/outAddressFetcher.js b/modules/outAddressFetcher.js index 283959c..8cb2582 100644 --- a/modules/outAddressFetcher.js +++ b/modules/outAddressFetcher.js @@ -1,5 +1,7 @@ const log = require('../helpers/log'); const $u = require('../helpers/utils'); +const helpers = require('../helpers'); +const api = require('./api'); const notify = require('../helpers/notify'); const config = require('./configReader'); const db = require('./DB'); @@ -34,7 +36,7 @@ module.exports = async () => { msgSendBack = `I can’t get your _${pay.outCurrency}_ address from ADAMANT KVS to pay a reward. Make sure you use ADAMANT wallet with _${pay.outCurrency}_ enabled. I have already notified my master.`; notify(msgNotify, 'error'); let tx; - $u.sendAdmMsg(tx.userId, msgSendBack); + await api.sendMessage(config.passPhrase, tx.userId, msgSendBack); } } else { pay.update({ @@ -44,7 +46,7 @@ module.exports = async () => { await pay.save(); } catch (e) { - log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); + log.error(`Error in ${helpers.getModuleName(module.id)} module: ${e}`); } }); }; diff --git a/modules/rewardsPayer.js b/modules/rewardsPayer.js index ec0b1c8..2a6a1cb 100644 --- a/modules/rewardsPayer.js +++ b/modules/rewardsPayer.js @@ -1,6 +1,8 @@ const db = require('./DB'); const config = require('./configReader'); const $u = require('../helpers/utils'); +const helpers = require('../helpers'); +const api = require('./api'); const Store = require('./Store'); const log = require('../helpers/log'); const notify = require('../helpers/notify'); @@ -42,7 +44,7 @@ module.exports = async () => { isPayed: false, }, true); notify(`${config.notifyName} notifies about insufficient balance to send a reward of _${outAmount}_ _${outCurrency}_. Balance of _${outCurrency}_ is _${Store.user[outCurrency].balance}_. ${etherString}User ADAMANT id: ${userId}.`, 'error'); - $u.sendAdmMsg(userId, `I can’t transfer a reward of _${outAmount}_ _${outCurrency}_ to you because of insufficient funds (I count blockchain fees also). I have already notified my master.`); + await api.sendMessage(config.passPhrase, userId, `I can’t transfer a reward of _${outAmount}_ _${outCurrency}_ to you because of insufficient funds (I count blockchain fees also). I have already notified my master.`); return; } @@ -80,10 +82,10 @@ module.exports = async () => { isPayed: false, }, true); notify(`${config.notifyName} cannot make transaction to payout a reward of _${outAmount}_ _${outCurrency}_. Balance of _${outCurrency}_ is _${Store.user[outCurrency].balance}_. ${etherString}User ADAMANT id: ${userId}.`, 'error'); - $u.sendAdmMsg(userId, `I’ve tried to make a reward payout of _${outAmount}_ _${outCurrency}_ to you, but something went wrong. I have already notified my master.`); + await api.sendMessage(config.passPhrase, userId, `I’ve tried to make a reward payout of _${outAmount}_ _${outCurrency}_ to you, but something went wrong. I have already notified my master.`); } } catch (e) { - log.error(`Error in ${$u.getModuleName(module.id)} module: ${e.toString()}`); + log.error(`Error in ${helpers.getModuleName(module.id)} module: ${e.toString()}`); } }); }; diff --git a/modules/sentTxValidator.js b/modules/sentTxValidator.js index 52b4ba2..a67d283 100644 --- a/modules/sentTxValidator.js +++ b/modules/sentTxValidator.js @@ -1,6 +1,8 @@ const db = require('./DB'); const config = require('./configReader'); const $u = require('../helpers/utils'); +const helpers = require('../helpers'); +const api = require('./api'); const Store = require('./Store'); const log = require('../helpers/log'); const notify = require('../helpers/notify'); @@ -51,7 +53,7 @@ module.exports = async () => { msgSendBack = `I’ve tried to make the reward payout of _${outAmount}_ _${outCurrency}_ to you, but unable to validate transaction. Tx hash: _${outTxid}_. I’ve already notified my master. If you wouldn’t receive transfer in two days, contact my master also.`; notify(msgNotify, notifyType); - $u.sendAdmMsg(userId, msgSendBack); + await api.sendMessage(config.passPhrase, userId, msgSendBack); } await pay.save(); return; @@ -76,7 +78,7 @@ module.exports = async () => { msgNotify = `${config.notifyName} notifies that the reward payout of _${outAmount}_ _${outCurrency}_ failed. Tx hash: _${outTxid}_. Will try again. Balance of _${outCurrency}_ is _${Store.user[outCurrency].balance}_. ${etherString}User ADAMANT id: ${userId}.`; msgSendBack = `I’ve tried to make the payout transfer of _${outAmount}_ _${outCurrency}_ to you, but it seems transaction failed. Tx hash: _${outTxid}_. I will try again. If I’ve said the same several times already, please contact my master.`; - $u.sendAdmMsg(userId, msgSendBack); + await api.sendMessage(config.passPhrase, userId, msgSendBack); } else if (status && pay.outConfirmations >= config.min_confirmations) { notifyType = 'info'; if (config.notifyRewardReceived) { @@ -86,7 +88,7 @@ module.exports = async () => { if (outCurrency !== 'ADM') { msgSendBack = `{"type":"${outCurrency}_transaction","amount":"${outAmount}","hash":"${outTxid}","comments":"${msgSendBack}"}`; - pay.isFinished = $u.sendAdmMsg(userId, msgSendBack, 'rich'); + pay.isFinished = await api.sendMessage(config.passPhrase, userId, msgSendBack, 'rich'); } else { pay.isFinished = true; } @@ -98,7 +100,7 @@ module.exports = async () => { notify(msgNotify, notifyType); } } catch (e) { - log.error(`Error in ${$u.getModuleName(module.id)} module: ${e}`); + log.error(`Error in ${helpers.getModuleName(module.id)} module: ${e}`); } }); }; diff --git a/modules/transferTxs.js b/modules/transferTxs.js index e5ae25b..5392951 100644 --- a/modules/transferTxs.js +++ b/modules/transferTxs.js @@ -1,5 +1,5 @@ const {SAT} = require('../helpers/const'); -const $u = require('../helpers/utils'); +const api = require('./api'); const notify = require('../helpers/notify'); const config = require('./configReader'); @@ -38,5 +38,5 @@ module.exports = async (itx, tx) => { await itx.update({isProcessed: true}, true); notify(msgNotify, notifyType); - $u.sendAdmMsg(tx.senderId, msgSendBack); + await api.sendMessage(config.passPhrase, tx.senderId, msgSendBack); }; diff --git a/modules/unknownTxs.js b/modules/unknownTxs.js index b5188f8..8ffeae4 100644 --- a/modules/unknownTxs.js +++ b/modules/unknownTxs.js @@ -1,4 +1,5 @@ -const $u = require('../helpers/utils'); +const api = require('./api'); +const helpers = require('./../helpers'); const db = require('./DB'); const config = require('./configReader'); @@ -9,9 +10,9 @@ module.exports = async (tx, itx) => { .find({ sender: tx.senderId, type: 'unknown', - date: {$gt: ($u.unix() - 24 * 3600 * 1000)}, // last 24h - }).sort({date: -1}).toArray((err, docs) => { - const twoHoursAgo = $u.unix() - 2 * 3600 * 1000; + date: {$gt: (helpers.unix() - 24 * 3600 * 1000)}, // last 24h + }).sort({date: -1}).toArray(async (err, docs) => { + const twoHoursAgo = helpers.unix() - 2 * 3600 * 1000; let countMsgs = docs.length; if (!docs[1] || twoHoursAgo > docs[1].date) { countMsgs = 1; @@ -39,7 +40,7 @@ module.exports = async (tx, itx) => { } else { msg = getRnd(5); } - $u.sendAdmMsg(tx.senderId, msg); + await api.sendMessage(config.passPhrase, tx.senderId, msg); itx.update({isProcessed: true}, true); }); }; diff --git a/package.json b/package.json index baee2f8..1de8fa3 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "author": "Aleksei Lebedev (https://adamant.im)", "license": "GPL-3.0", "dependencies": { - "adamant-api": "^0.5.3", + "adamant-api": "^1.4.0", "axios": "0.21.1", "ethereumjs-tx": "^2.1.2", "ethereumjs-util": "^7.0.0", From 3c4715a797ffb970de69fa3c27fc3c999743ce36 Mon Sep 17 00:00:00 2001 From: gost1k Date: Fri, 22 Jul 2022 19:21:57 +0300 Subject: [PATCH 24/55] fix: fix bugs and upgrade adamant-api version --- helpers/utils/adm_utils.js | 10 +++++----- helpers/utils/index.js | 2 +- modules/Store.js | 8 ++++---- modules/checkerTransactions.js | 4 ++-- modules/configReader.js | 4 ++-- package.json | 8 ++++---- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/helpers/utils/adm_utils.js b/helpers/utils/adm_utils.js index 43ff9a3..221dd88 100644 --- a/helpers/utils/adm_utils.js +++ b/helpers/utils/adm_utils.js @@ -24,18 +24,18 @@ module.exports = { if (blocks.success) { return blocks.data.blocks[0].height; } else { - log.warn(`Failed to get last block in getLastBlock() of ${helpers.getModuleName()} module. ${blocks.errorMessage}.`); + log.warn(`Failed to get last block in getLastBlock() of ${helpers.getModuleName(module.id)} module. ${blocks.errorMessage}.`); } }, async getTransactionStatus(txId) { const tx = await api.get('transactions/get', {id: txId}); if (tx.success) { return { - blockNumber: tx.height, + blockNumber: tx.data.transaction.height, status: true, }; } else { - log.warn(`Failed to get Tx ${txId} in getTransactionStatus() of ${helpers.getModuleName()} module. ${tx.errorMessage}.`); + log.warn(`Failed to get Tx ${txId} in getTransactionStatus() of ${helpers.getModuleName(module.id)} module. ${tx.errorMessage}.`); } }, async send(params) { @@ -48,7 +48,7 @@ module.exports = { hash: payment.data.transactionId, }; } else { - log.warn(`Failed to send ${value} ADM to ${address} with comment ${comment} in send() of ${helpers.getModuleName()} module. ${payment.errorMessage}.`); + log.warn(`Failed to send ${value} ADM to ${address} with comment ${comment} in send() of ${helpers.getModuleName(module.id)} module. ${payment.errorMessage}.`); return { success: false, error: payment.errorMessage, @@ -60,7 +60,7 @@ module.exports = { if (account.success) { User.balance = account.data.account.balance / SAT; } else { - log.warn(`Failed to get account info in updateBalance() of ${helpers.getModuleName()} module. ${account.errorMessage}.`); + log.warn(`Failed to get account info in updateBalance() of ${helpers.getModuleName(module.id)} module. ${account.errorMessage}.`); } }, }; diff --git a/helpers/utils/index.js b/helpers/utils/index.js index 96e3c7d..a122e0c 100644 --- a/helpers/utils/index.js +++ b/helpers/utils/index.js @@ -19,7 +19,7 @@ module.exports = { return 'none'; } } else { - log.warn(`Failed to get ${coin} address for ${admAddress} from KVS in getAddressCryptoFromAdmAddressADM() of ${helpers.getModuleName()} module. ${res.errorMessage}.`); + log.warn(`Failed to get ${coin} address for ${admAddress} from KVS in getAddressCryptoFromAdmAddressADM() of ${helpers.getModuleName(module.id)} module. ${res.errorMessage}.`); } }, async updateAllBalances() { diff --git a/modules/Store.js b/modules/Store.js index 7cc021b..fdff8b1 100644 --- a/modules/Store.js +++ b/modules/Store.js @@ -1,6 +1,6 @@ const db = require('./DB'); const log = require('../helpers/log'); -const keys = require('adamant-api/helpers/keys'); +const keys = require('adamant-api/src/helpers/keys'); const api = require('./api'); const axios = require('axios'); const helpers = require('../helpers'); @@ -45,7 +45,7 @@ module.exports = { if (blocks.success) { this.updateSystem('lastBlock', blocks.data.blocks[0]); } else { - log.warn(`Failed to get last block in updateLastBlock() of ${helpers.getModuleName()} module. ${blocks.errorMessage}.`); + log.warn(`Failed to get last block in updateLastBlock() of ${helpers.getModuleName(module.id)} module. ${blocks.errorMessage}.`); } }, async updateCurrencies() { @@ -57,11 +57,11 @@ module.exports = { if (data) { this.currencies = data; } else { - log.warn(`Error in updateCurrencies() of ${helpers.getModuleName()} module: Request to ${url} returned empty result.`); + log.warn(`Error in updateCurrencies() of ${helpers.getModuleName(module.id)} module: Request to ${url} returned empty result.`); } } } catch (error) { - log.warn(`Error in updateCurrencies() of ${helpers.getModuleName()} module: Request to ${url} failed with ${error.response ? error.response.status : undefined} status code, ${error.toString()}${error.response && error.response.data ? '. Message: ' + error.response.data.toString().trim() : ''}.`); + log.warn(`Error in updateCurrencies() of ${helpers.getModuleName(module.id)} module: Request to ${url} failed with ${error.response ? error.response.status : undefined} status code, ${error.toString()}${error.response && error.response.data ? '. Message: ' + error.response.data.toString().trim() : ''}.`); } }, getPrice(from, to) { diff --git a/modules/checkerTransactions.js b/modules/checkerTransactions.js index bd4faa1..bf91ade 100644 --- a/modules/checkerTransactions.js +++ b/modules/checkerTransactions.js @@ -15,14 +15,14 @@ async function check() { if (txChat.success) { txs = txs.concat(txChat.data.transactions); } else { - log.warn(`Failed to chat Txs in check() of ${helpers.getModuleName()} module. ${txChat.errorMessage}.`); + log.warn(`Failed to chat Txs in check() of ${helpers.getModuleName(module.id)} module. ${txChat.errorMessage}.`); } const txTrx = await api.get('transactions', {fromHeight: Store.lastHeight - 5, 'and:recipientId': config.address, 'and:type': 0}); if (txTrx.success) { txs = txs.concat(txTrx.data.transactions); } else { - log.warn(`Failed to chat Txs in check() of ${helpers.getModuleName()} module. ${txTrx.errorMessage}.`); + log.warn(`Failed to chat Txs in check() of ${helpers.getModuleName(module.id)} module. ${txTrx.errorMessage}.`); } txs.forEach((tx) => { diff --git a/modules/configReader.js b/modules/configReader.js index c5240cf..d9293a9 100644 --- a/modules/configReader.js +++ b/modules/configReader.js @@ -1,6 +1,6 @@ const jsonminify = require('jsonminify'); const fs = require('fs'); -const keys = require('adamant-api/helpers/keys'); +const keys = require('adamant-api/src/helpers/keys'); const isDev = process.argv.includes('dev'); const mathjs = require('mathjs'); @@ -115,7 +115,7 @@ try { let keysPair; try { - keysPair = keys.createKeypairFromPassPhrase(config.passphrase); + keysPair = keys.createKeypairFromPassPhrase(config.passPhrase); } catch (e) { exit('Passphrase is not valid! Error: ' + e); } diff --git a/package.json b/package.json index 1de8fa3..42cb9ee 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,8 @@ "author": "Aleksei Lebedev (https://adamant.im)", "license": "GPL-3.0", "dependencies": { - "adamant-api": "^1.4.0", - "axios": "0.21.1", + "adamant-api": "^1.5.0", + "axios": "^0.21.4", "ethereumjs-tx": "^2.1.2", "ethereumjs-util": "^7.0.0", "express": "^4.17.1", @@ -43,11 +43,11 @@ "web3": "^1.2.7" }, "devDependencies": { - "@commitlint/config-conventional": "^16.2.1", "@commitlint/cli": "^16.2.1", + "@commitlint/config-conventional": "^16.2.1", + "eslint": "^8.9.0", "eslint-config-google": "^0.14.0", "eslint-plugin-jest": "^26.1.0", - "eslint": "^8.9.0", "husky": "^7.0.4" }, "repository": { From 6878f0ebeec3ef57e0244f19a579473442462d67 Mon Sep 17 00:00:00 2001 From: gost1k Date: Wed, 27 Jul 2022 22:25:27 +0300 Subject: [PATCH 25/55] feature: add lisk --- config.json | 8 + helpers/const.js | 1 + helpers/index.js | 67 +++++++ helpers/utils/index.js | 3 + helpers/utils/lskBaseCoin.js | 367 +++++++++++++++++++++++++++++++++++ helpers/utils/lsk_utils.js | 205 +++++++++++++++++++ modules/Store.js | 6 + package.json | 19 +- 8 files changed, 668 insertions(+), 8 deletions(-) create mode 100644 helpers/utils/lskBaseCoin.js create mode 100644 helpers/utils/lsk_utils.js diff --git a/config.json b/config.json index a11c95e..5295df5 100644 --- a/config.json +++ b/config.json @@ -25,6 +25,14 @@ "node_ETH": [ "https://ethnode1.adamant.im" ], + "node_LSK": [ + "https://lisknode3.adamant.im", + "https://lisknode4.adamant.im" + ], + "service_LSK": [ + "https://liskservice3.adamant.im", + "https://liskservice4.adamant.im" + ], /** List of ADAMANT InfoServices for catching exchange rates **/ "infoservice": [ "https://info.adamant.im" diff --git a/helpers/const.js b/helpers/const.js index 203f6e9..5e469e3 100644 --- a/helpers/const.js +++ b/helpers/const.js @@ -1,4 +1,5 @@ module.exports = { SAT: 100000000, EPOCH: Date.UTC(2017, 8, 2, 17, 0, 0, 0), + PRINT_DECIMALS: 8, }; diff --git a/helpers/index.js b/helpers/index.js index 32695e7..b380925 100644 --- a/helpers/index.js +++ b/helpers/index.js @@ -41,4 +41,71 @@ module.exports = { } return output; }, + /** + * Formats unix timestamp to string + * @param {number} timestamp Timestamp to format + * @return {object} Contains different formatted strings + */ + formatDate(timestamp) { + if (!timestamp) { + return false; + } + const formattedDate = {}; + const dateObject = new Date(timestamp); + formattedDate.year = dateObject.getFullYear(); + formattedDate.month = ('0' + (dateObject.getMonth() + 1)).slice(-2); + formattedDate.date = ('0' + dateObject.getDate()).slice(-2); + formattedDate.hours = ('0' + dateObject.getHours()).slice(-2); + formattedDate.minutes = ('0' + dateObject.getMinutes()).slice(-2); + formattedDate.seconds = ('0' + dateObject.getSeconds()).slice(-2); + formattedDate.YYYY_MM_DD = formattedDate.year + '-' + formattedDate.month + '-' + formattedDate.date; + formattedDate.YYYY_MM_DD_hh_mm = formattedDate.year + '-' + formattedDate.month + '-' + formattedDate.date + ' ' + formattedDate.hours + ':' + formattedDate.minutes; + formattedDate.hh_mm_ss = formattedDate.hours + ':' + formattedDate.minutes + ':' + formattedDate.seconds; + return formattedDate; + }, + /** + * Compares two strings, case-insensitive + * @param {string} string1 + * @param {string} string2 + * @return {boolean} True, if strings are equal, case-insensitive + */ + isStringEqualCI(string1, string2) { + if (typeof string1 !== 'string' || typeof string2 !== 'string') return false; + return string1.toUpperCase() === string2.toUpperCase(); + }, + /** + * Checks if number is finite + * @param {number} value Number to validate + * @return {boolean} + */ + isNumber(value) { + if (typeof (value) !== 'number' || isNaN(value) || !Number.isFinite(value)) { + return false; + } + return true; + }, + /** + * Checks if number is finite and not less, than 0 + * @param {number} value Number to validate + * @return {boolean} + */ + isPositiveOrZeroNumber(value) { + if (!this.isNumber(value) || value < 0) { + return false; + } + return true; + }, + /** + * Converts a bytes array to the respective string representation + * @param {Array|Uint8Array} bytes bytes array + * @return {string} + */ + bytesToHex(bytes = []) { + const hex = []; + bytes.forEach((b) => hex.push( + (b >>> 4).toString(16), + (b & 0xF).toString(16), + )); + return hex.join(''); + }, }; diff --git a/helpers/utils/index.js b/helpers/utils/index.js index a122e0c..0aa81af 100644 --- a/helpers/utils/index.js +++ b/helpers/utils/index.js @@ -2,6 +2,7 @@ const api = require('../../modules/api'); const config = require('../../modules/configReader'); const eth_utils = require('./eth_utils'); const adm_utils = require('./adm_utils'); +const LskCoin = require('./lsk_utils'); const log = require('../log'); const Store = require('../../modules/Store'); const helpers = require('../../helpers'); @@ -35,6 +36,7 @@ module.exports = { const data = { ETH: await this.ETH.getLastBlock(), ADM: await this.ADM.getLastBlock(), + LSK: await this.LSK.getLastBlockHeight(), }; for (const t of config.erc20) { // data[t] = await this[t].getLastBlockNumber(); // Don't do unnecessary requests @@ -140,4 +142,5 @@ module.exports = { }, ETH: eth_utils, ADM: adm_utils, + LSK: new LskCoin('LSK'), }; diff --git a/helpers/utils/lskBaseCoin.js b/helpers/utils/lskBaseCoin.js new file mode 100644 index 0000000..e17ca6c --- /dev/null +++ b/helpers/utils/lskBaseCoin.js @@ -0,0 +1,367 @@ +const config = require('../../modules/configReader'); +const log = require('../log'); +const axios = require('axios'); +const transactions = require('@liskhq/lisk-transactions'); +const cryptography = require('@liskhq/lisk-cryptography'); + +const helpers = require('../../helpers'); +const api = require('../../modules/api'); +const constants = require('../const'); + +const lskNode = config.node_LSK[0]; +const lskService = config.service_LSK[0]; + +module.exports = class LskBaseCoin { + constructor(token) { + this.token = token; + this.account = {}; + this.account.keys = api[token.toLowerCase()].keys(config.passPhrase); + this.account = Object.assign(this.account, this.account.keys); + this.clients = {}; + + this.cache = { + getData(data, validOnly) { + if (this[data] && this[data].timestamp) { + if (!validOnly || (Date.now() - this[data].timestamp < this[data].lifetime)) { + return this[data].value; + } + } + return undefined; + }, + cacheData(data, value) { + this[data].value = value; + this[data].timestamp = Date.now(); + }, + }; + + this.cache.balance = {lifetime: 30000}; + this.cache.lastBlock = {lifetime: 60000}; + + setTimeout(() => this.updateBalance().then((balance) => + log.log(`Initial ${this.token} balance: ${helpers.isPositiveOrZeroNumber(balance) ? balance.toFixed(constants.PRINT_DECIMALS) : 'unable to receive'}`), + ), 1000); + setTimeout(() => this.getLastBlockHeight().then((lastBlockHeight) => + log.log(`Last ${this.token} block height: ${helpers.isPositiveOrZeroNumber(lastBlockHeight) ? lastBlockHeight : 'unable to receive'}`), + ), 1000); + } + + /** + * @abstract + * @return {Promise} + */ + updateBalance() { + return undefined; + } + + /** + * @abstract + * @return {Promise} + */ + getLastBlock() { + return undefined; + } + + /** + * @abstract + * @return {Number} + */ + getLastBlockHeight() { + return undefined; + } + + /** + * @abstract + * @return {Number} + */ + sendTransaction() { + return undefined; + } + + /** + * @abstract + * @return {Promise} + */ + send() { + return undefined; + } + + /** + * @abstract + * @return {Promise} + */ + createTransaction() { + return undefined; + } + + /** + * @abstract + * @return {Promise} + */ + getTransactionStatus() { + return undefined; + } + + /** + * Returns LSK decimals (precision) + * @override + * @return {Number} + */ + get decimals() { + return 8; + } + + /** + * Returns fixed fee for transfers + * @return {Number} + */ + get FEE() { + return 0.00160; + } + + get multiplier() { + return 1e8; + } + + /** + * Get asset Id + * @return {number} + */ + get assetId() { + return 0; + } + /** + * Get Token/Send module Id + * @return {number} + */ + get moduleId() { + return 2; + } + + /** + * Asset schema defines in which format data is sent in the transaction asset + */ + get assetSchema() { + return { + $id: 'lisk/transfer-asset', + title: 'Transfer transaction asset', + type: 'object', + required: ['amount', 'recipientAddress', 'data'], + properties: { + amount: { + dataType: 'uint64', + fieldNumber: 1, + }, + recipientAddress: { + dataType: 'bytes', + fieldNumber: 2, + minLength: 20, + maxLength: 20, + }, + data: { + dataType: 'string', + fieldNumber: 3, + minLength: 0, + maxLength: 64, + }, + }, + }; + } + + /** + * Converts amount in beddows to token + * @param {String|Number} bedValue Amount in beddows + * @return {Number} Amount in coins + */ + fromBeddows(bedValue) { + try { + const value = (+bedValue / this.multiplier).toFixed(this.decimals); + return +value; + } catch (e) { + log.warn(`Error while converting fromBeddows(${bedValue}) for ${this.token} of ${helpers.getModuleName(module.id)} module: ` + e); + } + } + + /** + * Converts amount in token to beddows + * @param {String|Number} tokenValue Amount in coins + * @return {Number} Amount in beddows + */ + toBeddows(tokenValue) { + try { + return Math.floor(+tokenValue * this.multiplier); + } catch (e) { + log.warn(`Error while converting toBeddows(${tokenValue}) for ${this.token} of ${helpers.getModuleName(module.id)} module: ` + e); + } + } + + /** + * Make get request to LSK node + * @param {string} url request url + * @param {object} params url parameters + * @return {Object} + */ + _get(url, params) { + return this._getClient().get(url, {params}).then((response) => response.data); + } + + /** + * Make get request to Lisk service + * @param {string} url request url + * @param {object} params url parameters + * @return {Object} + */ + _getService(url, params) { + return this._getServiceClient().get(url, {params}).then((response) => response.data); + } + + /** + * Get client for a random LSK node + * @return {Object} Axios client + */ + _getClient() { + if (!this.clients[lskNode]) { + this.clients[lskNode] = createClient(lskNode); + } + return this.clients[lskNode]; + } + + /** + * Get client for a random API Lisk service node + * @return {Object} Axios client + */ + _getServiceClient() { + if (!this.clients[lskService]) { + this.clients[lskService] = createServiceClient(lskService); + } + return this.clients[lskService]; + } + + /** + * Formats LSK Tx info + * @param {object} tx Tx + * @return {object} Formatted Tx info + */ + _mapTransaction(tx) { + const direction = tx.sender.address.toUpperCase() === this.account.address.toUpperCase() ? 'from' : 'to'; + + const mapped = { + id: tx.id, + hash: tx.id, + fee: tx.fee, + status: tx.height ? 'CONFIRMED' : 'REGISTERED', + data: tx.asset.data, + timestamp: tx.block.timestamp, + direction, + senderId: tx.sender.address, + recipientId: tx.asset.recipient.address, + amount: tx.asset.amount, + confirmations: tx.confirmations, + height: tx.height, + nonce: tx.nonce, + moduleId: tx.moduleAssetId.split(':')[0], + assetId: tx.moduleAssetId.split(':')[1], + moduleName: tx.moduleAssetName.split(':')[0], + assetName: tx.moduleAssetName.split(':')[1], + }; + + mapped.amount /= this.multiplier; + mapped.fee /= this.multiplier; + mapped.timestamp = parseInt(mapped.timestamp) * 1000; // timestamp in millis + + return mapped; + } + + /** + * Creates an LSK-based transaction as an object with specific types + * @param {string} address Target address + * @param {number} amount to send (coins, not beddows) + * @param {number} fee fee of transaction + * @param {number} nonce nonce value + * @param {string} data New balance in LSK + * @return {object} + */ + _buildTransaction(address, amount, fee, nonce, data = '') { + const amountString = transactions.convertLSKToBeddows((+amount).toFixed(this.decimals)); + const feeString = transactions.convertLSKToBeddows((+fee).toFixed(this.decimals)); + const nonceString = nonce.toString(); + const liskTx = { + moduleID: this.moduleId, + assetID: this.assetId, + nonce: BigInt(nonceString), + fee: BigInt(feeString), + asset: { + amount: BigInt(amountString), + recipientAddress: cryptography.getAddressFromBase32Address(address), + data, + // data: 'Sent with ADAMANT Messenger' + }, + signatures: [], + }; + liskTx.senderPublicKey = this.account.keyPair.publicKey; + const minFee = Number(transactions.computeMinFee(this.assetSchema, liskTx)) / this.multiplier; + + return {liskTx, minFee}; + } + + /** + * Builds log message from formed Tx + * @param {object} tx Tx + * @return {string | object} Log message + */ + formTxMessage(tx) { + try { + const token = this.token; + const status = tx.status ? ' is accepted' : tx.status === false ? ' is FAILED' : ''; + const amount = tx.amount ? ` for ${tx.amount} ${token}` : ''; + const height = tx.height ? `${status ? ' and' : ' is'} included at ${tx.height} blockchain height` : ''; + const confirmations = tx.confirmations ? ` and has ${tx.confirmations} confirmations` : ''; + const instantSend = !height && !confirmations && tx.instantlock && tx.instantlock_internal ? `${status ? ' and' : ' is'} locked with InstantSend` : ''; + const time = tx.timestamp ? ` (${helpers.formatDate(tx.timestamp).YYYY_MM_DD_hh_mm} — ${tx.timestamp})` : ''; + const hash = tx.hash; + const fee = tx.fee || tx.fee === 0 ? `, ${tx.fee} ${token} fee` : ''; + const senderId = helpers.isStringEqualCI(tx.senderId, this.account.address) ? 'Me' : tx.senderId; + const recipientId = helpers.isStringEqualCI(tx.recipientId, this.account.address) ? 'Me' : tx.recipientId; + return `Tx ${hash}${amount} from ${senderId} to ${recipientId}${status}${instantSend}${height}${time}${confirmations}${fee}`; + } catch (e) { + log.warn(`Error while building Tx ${tx ? tx.id : undefined} message for ${this.token} of ${helpers.getModuleName(module.id)} module: ` + e); + return tx; + } + } +}; + +/** + * Create axios client to Lisk service + * @param {string} url client base url + * @return {Object} + */ +function createServiceClient(url) { + const client = axios.create({baseURL: url}); + client.interceptors.response.use(null, (error) => { + if (error.response && Number(error.response.status) >= 500) { + console.error(`Request to ${url} failed.`, error); + } + return Promise.reject(error); + }); + return client; +} + +/** + * Create axios client to Lisk node + * @param {string} url client base url + * @return {Object} + */ +function createClient(url) { + const client = axios.create({baseURL: url}); + client.interceptors.response.use(null, (error) => { + if (error.response && Number(error.response.status) >= 500) { + console.error(`Request to ${url} failed.`, error); + } + if (error.response && Number(error.response.status) === 404) { + if (error.response?.data?.errors[0]?.message && error.response.data.errors[0].message.includes('was not found')) { + return error.response; + } + } + return Promise.reject(error); + }); + return client; +} diff --git a/helpers/utils/lsk_utils.js b/helpers/utils/lsk_utils.js new file mode 100644 index 0000000..ee750a2 --- /dev/null +++ b/helpers/utils/lsk_utils.js @@ -0,0 +1,205 @@ +const config = require('../../modules/configReader'); +const log = require('../log'); +const transactions = require('@liskhq/lisk-transactions'); +const cryptography = require('@liskhq/lisk-cryptography'); + +const LskBaseCoin = require('./lskBaseCoin'); +const helpers = require('../../helpers'); +const Store = require('../../modules/Store'); + +const lskNode = config.node_LSK[0]; +const lskService = config.service_LSK[0]; + +module.exports = class LskCoin extends LskBaseCoin { + constructor(token) { + super(token); + } + + get networkIdentifier() { + // Testnet: '15f0dacc1060e91818224a94286b13aa04279c640bd5d6f193182031d133df7c' + // Mainnet: '4c09e6a781fc4c7bdb936ee815de8f94190f8a7519becd9de2081832be309a99' + const networkIdentifier = '4c09e6a781fc4c7bdb936ee815de8f94190f8a7519becd9de2081832be309a99'; + return Buffer.from(networkIdentifier, 'hex'); + } + + getHeight() { + return this._get(`${lskNode}/api/node/info`, {}).then((data) => Number(data.data.height) || 0); + } + + /** + * Returns last block of LSK blockchain from cache, if it's up to date. + * If not, makes an API request and updates cached data. + * Used only for this.getLastBlockHeight() + * @override + * @return {Object} or undefined, if unable to get block info + */ + async getLastBlock() { + const cached = this.cache.getData('lastBlock', true); + if (cached) { + return cached; + } + const height = await this.getHeight(); + if (height) { + this.cache.cacheData('lastBlock', height); + return height; + } else { + log.warn(`Failed to get last block in getLastBlock() of ${helpers.getModuleName(module.id)} module. Received value: `); + } + } + + /** + * Returns last block height of LSK blockchain + * @override + * @return {Promise} or undefined, if unable to get block info + */ + async getLastBlockHeight() { + const block = await this.getLastBlock(); + return block || undefined; + } + + /** + * Returns balance in LSK from cache, if it's up to date. If not, makes an API request and updates cached data. + * @override + * @return {Promise} or outdated cached value, if unable to fetch data; it may be undefined also + */ + async updateBalance() { + try { + const cached = this.cache.getData('balance', true); + if (cached) { // balance is a beddows string or number + return this.fromBeddows(cached); + } + const result = await this._get(`${lskNode}/api/accounts/${this.account.addressHex}`, {}); + if (result && result.data && (result.data.token.balance !== undefined)) { + const {balance} = result.data.token; + this.cache.cacheData('balance', balance); + Store.user.LSK.balance = this.fromBeddows(balance); + return this.fromBeddows(balance); + } else { + const balanceErrorMessage = result && result.errorMessage ? ' ' + result.errorMessage : ''; + log.warn(`Failed to get balance in getBalance() for ${this.token} of ${helpers.getModuleName(module.id)} module; returning outdated cached balance.${balanceErrorMessage}`); + return this.fromBeddows(this.cache.getData('balance', false)); + } + } catch (e) { + log.warn(`Error while getting balance in getBalance() for ${this.token} of ${helpers.getModuleName(module.id)} module: ` + e); + } + } + + /** + * Send signed transaction to blockchain network + * @override + * @param {Object} signedTx + */ + sendTransaction(signedTx) { + return this._getClient().post('/api/transactions', signedTx).then((response) => { + return response.data.data.transactionId; + }); + } + + /** + * Build Tx and broadcasts it + * @override + * @param {object} params try: try number, address: recipient's address, value: amount to send in coins (not beddows) + */ + async send(params) { + let fee = this.FEE; + try { + const account = await this._get(`${lskNode}/api/accounts/${this.account.addressHex}`, {}); + const nonce = Number(account.data.sequence.nonce); + fee = this._buildTransaction(params.address, params.value, fee, nonce).minFee; + const result = await this.createTransaction(params.address, params.value, fee, nonce); + try { + if (result) { + log.log(`Successfully built Tx ${result.txId} to send ${params.value} ${this.token} to ${params.address} with ${fee} ${this.token} fee.`); + const hash = await this.sendTransaction(result.tx); + try { + if (hash) { + log.log(`Successfully broadcast Tx to send ${params.value} ${this.token} to ${params.address} with ${fee} ${this.token} fee, Tx hash: ${hash}.`); + return { + success: true, + hash, + }; + } + return { + success: false, + error: `Unable to broadcast Tx, it may be dust amount or other error`, + }; + } catch (e) { + return { + success: false, + error: e.toString(), + }; + } + } + return { + success: false, + error: `Unable to create Tx`, + }; + } catch (e) { + log.info('ERROR'); + log.info(e.response.data.errors); + return { + success: false, + error: e.toString(), + }; + } + } catch (e) { + log.warn(`Error while sending ${params.value} ${this.token} to ${params.address} with ${fee} ${this.token} fee in send() of ${helpers.getModuleName(module.id)} module: ` + e); + return { + success: false, error: e.toString(), + }; + } + } + + /** + * Creates a signed tx object and ID + * Signed JSON tx object is ready for broadcasting to blockchain network + * @override + * @param {String} address receiver address in Base32 format + * @param {Number} amount amount to transfer (coins, not beddows) + * @param {Number} fee transaction fee (coins, not beddows) + * @param {Number} nonce transaction nonce + * @param {String} data + * @return {Promise<{tx: Object, txId: string}>} + */ + createTransaction(address = '', amount = 0, fee, nonce, data = '') { + const {liskTx} = this._buildTransaction(address, amount, fee, nonce, data); + // To use transactions.signTransaction, passPhrase is necessary + // So we'll use cryptography.signDataWithPrivateKey + const liskTxBytes = transactions.getSigningBytes(this.assetSchema, liskTx); + const txSignature = cryptography.signDataWithPrivateKey( + Buffer.concat([this.networkIdentifier, liskTxBytes]), this.account.keyPair.secretKey, + ); + + liskTx.signatures[0] = txSignature; + const txId = helpers.bytesToHex(cryptography.hash(transactions.getBytes(this.assetSchema, liskTx))); + + // To send Tx to node's core API, we should change data types + liskTx.senderPublicKey = helpers.bytesToHex(liskTx.senderPublicKey); + liskTx.nonce = nonce.toString(); + liskTx.fee = transactions.convertLSKToBeddows((+fee).toFixed(this.decimals)); + liskTx.asset.amount = transactions.convertLSKToBeddows((+amount).toFixed(this.decimals)); + liskTx.asset.recipientAddress = helpers.bytesToHex(liskTx.asset.recipientAddress); + liskTx.signatures[0] = helpers.bytesToHex(txSignature); + return Promise.resolve({tx: liskTx, txId}); + } + + /** + * Returns Tx status and block height + * @override + * @param {String} txId Tx ID to fetch + * @param {Boolean} disableLogging Disable logging flag + * @return {Object} Formed tx + */ + async getTransactionStatus(txId, disableLogging = false) { + return this._getService(`${lskService}/api/v2/transactions/`, {transactionId: txId}).then((result) => { + if (typeof result === 'object' && result.data[0]) { + const formedTx = this._mapTransaction(result.data[0]); + if (!disableLogging) log.log(`${this.token} tx status: ${this.formTxMessage(formedTx)}.`); + return { + blockNumber: formedTx.height, + status: true, + }; + } + }); + } +}; diff --git a/modules/Store.js b/modules/Store.js index fdff8b1..7efeb68 100644 --- a/modules/Store.js +++ b/modules/Store.js @@ -12,6 +12,8 @@ const AdmKeysPair = keys.createKeypairFromPassPhrase(config.passPhrase); const AdmAddress = keys.createAddressFromPublicKey(AdmKeysPair.publicKey); // ETH data const ethData = api.eth.keys(config.passPhrase); +// LSK data +const lskData = api.lsk.keys(config.passPhrase); module.exports = { version, @@ -26,6 +28,10 @@ module.exports = { address: ethData.address, privateKey: ethData.privateKey, }, + LSK: { + address: lskData.address, + privateKey: lskData.privateKey, + }, }, comissions: { ADM: 0.5, // This is a stub. Ether fee returned with FEE() method in separate module diff --git a/package.json b/package.json index 42cb9ee..3911eec 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "adamant-bountybot", - "version": "1.4.0", + "version": "1.5.0", "description": "ADAMANT Bounty bot — a software that allows you to carry out bounty campaigns & crypto airdrops, with automatic task verifications and payouts.", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", + "test": "jest", "prepare": "husky install", "lint": "eslint . --ext .js --ignore-pattern node_modules/", "lint:fix": "eslint . --fix --ext .js --ignore-pattern node_modules/" @@ -27,28 +27,31 @@ "author": "Aleksei Lebedev (https://adamant.im)", "license": "GPL-3.0", "dependencies": { + "@liskhq/lisk-cryptography": "^3.2.1", + "@liskhq/lisk-transactions": "^5.2.2", "adamant-api": "^1.5.0", - "axios": "^0.21.4", + "axios": "^0.27.2", "ethereumjs-tx": "^2.1.2", "ethereumjs-util": "^7.0.0", "express": "^4.17.1", "jsonminify": "^0.4.1", "jsonrpc-client": "^0.1.1", - "mathjs": "^7.3.0", - "mongodb": "^3.2.6", + "mathjs": "^11.0.1", + "mongodb": "^4.8.0", "node-ethereum-wallet": "^1.3.2", "node-json-rpc": "0.0.1", - "request": "^2.88.0", "twitter": "^1.7.1", - "web3": "^1.2.7" + "web3": "^1.7.4" }, "devDependencies": { "@commitlint/cli": "^16.2.1", "@commitlint/config-conventional": "^16.2.1", + "axios-mock-adapter": "^1.21.1", "eslint": "^8.9.0", "eslint-config-google": "^0.14.0", "eslint-plugin-jest": "^26.1.0", - "husky": "^7.0.4" + "husky": "^7.0.4", + "jest": "^28.1.3" }, "repository": { "type": "git", From 4f274cf21a47f8310883fda31b64c33e1f8ba10c Mon Sep 17 00:00:00 2001 From: gost1k Date: Thu, 28 Jul 2022 22:19:20 +0300 Subject: [PATCH 26/55] chore: add unit-tests for Lisk --- helpers/utils/lskBaseCoin.js | 2 +- helpers/utils/lsk_utils.js | 1 + package.json | 2 +- tests/lisk.mock.js | 17 +++++++++ tests/lisk.test.js | 71 ++++++++++++++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 tests/lisk.mock.js create mode 100644 tests/lisk.test.js diff --git a/helpers/utils/lskBaseCoin.js b/helpers/utils/lskBaseCoin.js index e17ca6c..54b6658 100644 --- a/helpers/utils/lskBaseCoin.js +++ b/helpers/utils/lskBaseCoin.js @@ -71,7 +71,7 @@ module.exports = class LskBaseCoin { /** * @abstract - * @return {Number} + * @return {Promise} */ sendTransaction() { return undefined; diff --git a/helpers/utils/lsk_utils.js b/helpers/utils/lsk_utils.js index ee750a2..39bd97c 100644 --- a/helpers/utils/lsk_utils.js +++ b/helpers/utils/lsk_utils.js @@ -88,6 +88,7 @@ module.exports = class LskCoin extends LskBaseCoin { * Send signed transaction to blockchain network * @override * @param {Object} signedTx + * @return {Promise} */ sendTransaction(signedTx) { return this._getClient().post('/api/transactions', signedTx).then((response) => { diff --git a/package.json b/package.json index 3911eec..2c336d4 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "ADAMANT Bounty bot — a software that allows you to carry out bounty campaigns & crypto airdrops, with automatic task verifications and payouts.", "main": "index.js", "scripts": { - "test": "jest", + "test": "jest --rootDir tests --forceExit --testTimeout 30000 'dev'", "prepare": "husky install", "lint": "eslint . --ext .js --ignore-pattern node_modules/", "lint:fix": "eslint . --fix --ext .js --ignore-pattern node_modules/" diff --git a/tests/lisk.mock.js b/tests/lisk.mock.js new file mode 100644 index 0000000..c547cf1 --- /dev/null +++ b/tests/lisk.mock.js @@ -0,0 +1,17 @@ +const accountMock = { + data: { + address: '6c7704ca1ff8512f6377e6a8132ff7996e1ec193', + token: {balance: '98592144'}, + sequence: {nonce: '21'}, + keys: {numberOfSignatures: 0, mandatoryKeys: [], optionalKeys: []}, + dpos: {delegate: [Object], sentVotes: [], unlocking: []}, + }, + meta: {}, +}; + +const sendTxMock = 'f6eebc85deaa8be731b551adeb05625be412d6792cc11d2f83d90cb9776688d5'; + +module.exports = { + accountMock, + sendTxMock, +}; diff --git a/tests/lisk.test.js b/tests/lisk.test.js new file mode 100644 index 0000000..ae83249 --- /dev/null +++ b/tests/lisk.test.js @@ -0,0 +1,71 @@ +const axios = require('axios'); +const MockAdapter = require('axios-mock-adapter'); +const LskCoin = require('./../helpers/utils/lsk_utils'); +const config = require('../modules/configReader'); + +const { + accountMock, + sendTxMock, +} = require('./lisk.mock'); + +let lsk_utils = null; + +const lskNode = config.node_LSK[0]; + +function createClient(url) { + const client = axios.create({baseURL: url}); + client.interceptors.response.use(null, (error) => { + if (error.response && Number(error.response.status) >= 500) { + console.error(`Request to ${url} failed.`, error); + } + if (error.response && Number(error.response.status) === 404) { + if (error.response?.data?.errors[0]?.message && error.response.data.errors[0].message.includes('was not found')) { + return error.response; + } + } + return Promise.reject(error); + }); + return client; +} + +jest.setTimeout(20000); + +beforeAll(async () => { + lsk_utils = new LskCoin('LSK'); + const axiosClient = createClient(lskNode); + const axiosMock = new MockAdapter(axiosClient, {onNoMatch: 'passthrough'}); + lsk_utils.clients[lskNode] = axiosClient; + axiosMock.onGet(`${lskNode}/api/accounts/${lsk_utils.account.addressHex}`, accountMock); + axiosMock.onPost(`${lskNode}/api/transactions`, sendTxMock); +}); + +test('Should return user\'s balance', async () => { + const balance = await lsk_utils.updateBalance(); + return expect(balance).toEqual(expect.any(Number)); +}); + +test('Should return last block height', async () => { + const height = await lsk_utils.getLastBlockHeight(); + return expect(height).toEqual(expect.any(Number)); +}); + +test('Should return transaction status', async () => { + const txId = 'c3e38a305d9417137b3d888132f34669cb1d2dc401c2b5148790104ecbe1840e'; + const tx = await lsk_utils.getTransactionStatus(txId); + return expect(tx).toEqual(expect.objectContaining({ + blockNumber: expect.any(Number), + status: expect.any(Boolean), + })); +}); + +test('Send transaction', async () => { + const result = await lsk_utils.send({ + address: 'lsk4nwbk8w3qejknyff87to6ututyso6pzxavpant', + value: 0.01, + comment: 'Was it great? Share the experience with your friends!', // if ADM + }); + return expect(result).toEqual(expect.objectContaining({ + success: expect.any(Boolean), + hash: expect.any(String), + })); +}); From 551f5bf644007fbc548b9b51e6cea480d6639c30 Mon Sep 17 00:00:00 2001 From: gost1k Date: Thu, 28 Jul 2022 22:34:09 +0300 Subject: [PATCH 27/55] chore: small fixes for lsk --- helpers/utils/lskBaseCoin.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/helpers/utils/lskBaseCoin.js b/helpers/utils/lskBaseCoin.js index 54b6658..35562c5 100644 --- a/helpers/utils/lskBaseCoin.js +++ b/helpers/utils/lskBaseCoin.js @@ -6,7 +6,6 @@ const cryptography = require('@liskhq/lisk-cryptography'); const helpers = require('../../helpers'); const api = require('../../modules/api'); -const constants = require('../const'); const lskNode = config.node_LSK[0]; const lskService = config.service_LSK[0]; @@ -36,13 +35,6 @@ module.exports = class LskBaseCoin { this.cache.balance = {lifetime: 30000}; this.cache.lastBlock = {lifetime: 60000}; - - setTimeout(() => this.updateBalance().then((balance) => - log.log(`Initial ${this.token} balance: ${helpers.isPositiveOrZeroNumber(balance) ? balance.toFixed(constants.PRINT_DECIMALS) : 'unable to receive'}`), - ), 1000); - setTimeout(() => this.getLastBlockHeight().then((lastBlockHeight) => - log.log(`Last ${this.token} block height: ${helpers.isPositiveOrZeroNumber(lastBlockHeight) ? lastBlockHeight : 'unable to receive'}`), - ), 1000); } /** From 2d238ad47940bd780a1bbdd0d184927ee494a0a3 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Tue, 9 Aug 2022 14:26:04 +0300 Subject: [PATCH 28/55] Change console.log to log.log --- modules/checkAdamantContacts.js | 2 - modules/checkTwitterFollow.js | 2 - modules/checkTwitterRetweet.js | 1 - modules/checkTxs.js | 2 +- modules/twitterapi.js | 90 +++++++++++++++------------------ server.js | 4 +- 6 files changed, 46 insertions(+), 55 deletions(-) diff --git a/modules/checkAdamantContacts.js b/modules/checkAdamantContacts.js index 35656ef..cb22580 100644 --- a/modules/checkAdamantContacts.js +++ b/modules/checkAdamantContacts.js @@ -72,8 +72,6 @@ module.exports = async () => { } const isContactsDone = contacts_number >= config.adamant_campaign.min_contacts; - console.log('isContactsDone:', isContactsDone, contacts_number); - if (isContactsDone) { log.log(`User ${userId}… did make ${config.adamant_campaign.min_contacts} contacts.`); await user.update({ diff --git a/modules/checkTwitterFollow.js b/modules/checkTwitterFollow.js index 78e845a..8e6bba7 100644 --- a/modules/checkTwitterFollow.js +++ b/modules/checkTwitterFollow.js @@ -42,8 +42,6 @@ module.exports = async () => { for (let index = 0; index < config.twitter_follow.length; index++) { followAccount = config.twitter_follow[index]; isFollowing = await twitterapi.checkIfAccountFollowing(twitterAccount, followAccount); - console.log('isFollowing:', isFollowing); - if (isFollowing) { log.log(`User ${userId}… ${twitterAccount} do follows ${followAccount}.`); } else { diff --git a/modules/checkTwitterRetweet.js b/modules/checkTwitterRetweet.js index 2d22587..b0008db 100644 --- a/modules/checkTwitterRetweet.js +++ b/modules/checkTwitterRetweet.js @@ -53,7 +53,6 @@ module.exports = async () => { hashtags = config.twitter_retweet_w_comment[index].hashtags; retweetResult = await twitterapi.checkIfAccountRetweetedwComment(twitterAccount, toRetweet, minMentions, hashtags); isRetweeted = retweetResult.success; - console.log('isRetweeted:', isRetweeted); if (isRetweeted) { log.log(`User ${userId}… ${twitterAccount} did retweet ${toRetweet}.`); diff --git a/modules/checkTxs.js b/modules/checkTxs.js index 4bf70c3..69364ca 100644 --- a/modules/checkTxs.js +++ b/modules/checkTxs.js @@ -4,7 +4,7 @@ const $u = require('../helpers/utils'); const log = require('../helpers/log'); module.exports = async (itx, tx) => { - console.log(`Running module ${$u.getModuleName(module.id)}…`); + log.log(`Running module ${$u.getModuleName(module.id)}…`); const {UsersDb} = db; let user = {}; diff --git a/modules/twitterapi.js b/modules/twitterapi.js index ed743a0..e930b30 100644 --- a/modules/twitterapi.js +++ b/modules/twitterapi.js @@ -26,39 +26,41 @@ const toFollowIds = {}; } })(); -// -// async function getAccountFollowerIds(account) { -// const accountSN = $u.getTwitterScreenName(account); -// console.log(`Getting followers for @${accountSN}…`); -// -// let ids = []; -// return new Promise((resolve, reject) => { -// Twitter.get('followers/ids', {screen_name: accountSN, count: 5000, stringify_ids: true}, function getData(error, data, response) { -// try { -// if (error) { -// log.warn(`Twitter returned an error in getAccountFollowerIds(): ${JSON.stringify(error)}`); -// resolve(false); -// } else { -// ids = ids.concat(data.ids); -// // console.log(`next_cursor_str: `, data['next_cursor_str']); -// if (data['next_cursor_str'] > 0) { -// Twitter.get('followers/ids', {screen_name: accountSN, count: 5000, cursor: data['next_cursor_str']}, getData); -// } else { -// console.log(`FollowerIds count for @${accountSN} is ${ids.length}.`); -// resolve(ids); -// } -// } -// } catch (e) { -// log.warn(`Error while making getAccountFollowerIds() request: ${JSON.stringify(e)}`); -// resolve(false); -// } -// }); -// }); -// } +// Not used this time +/* +async function getAccountFollowerIds(account) { + const accountSN = $u.getTwitterScreenName(account); + log.log(`Getting followers for @${accountSN}…`); + + let ids = []; + return new Promise((resolve, reject) => { + Twitter.get('followers/ids', {screen_name: accountSN, count: 5000, stringify_ids: true}, function getData(error, data, response) { + try { + if (error) { + log.warn(`Twitter returned an error in getAccountFollowerIds(): ${JSON.stringify(error)}`); + resolve(false); + } else { + ids = ids.concat(data.ids); + // log.log(`next_cursor_str: `, data['next_cursor_str']); + if (data['next_cursor_str'] > 0) { + Twitter.get('followers/ids', {screen_name: accountSN, count: 5000, cursor: data['next_cursor_str']}, getData); + } else { + log.log(`FollowerIds count for @${accountSN} is ${ids.length}.`); + resolve(ids); + } + } + } catch (e) { + log.warn(`Error while making getAccountFollowerIds() request: ${JSON.stringify(e)}`); + resolve(false); + } + }); + }); +} +*/ async function getAccountFriendIds(account) { const accountSN = $u.getTwitterScreenName(account); - console.log(`Getting friends for @${accountSN}…`); + log.log(`Getting friends for @${accountSN}…`); let ids = []; return new Promise((resolve, reject) => { @@ -73,7 +75,7 @@ async function getAccountFriendIds(account) { if (data['next_cursor_str'] > 0) { Twitter.get('friends/ids', {screen_name: accountSN, count: 5000, cursor: data['next_cursor_str']}, getData); } else { - console.log(`FriendIds count for @${accountSN} is ${ids.length}.`); + log.log(`FriendIds count for @${accountSN} is ${ids.length}.`); resolve(ids); } } @@ -87,14 +89,11 @@ async function getAccountFriendIds(account) { async function getAccountTimeline(account) { const accountSN = $u.getTwitterScreenName(account); - console.log(`Getting timeline for @${accountSN}…`); + log.log(`Getting timeline for @${accountSN}…`); return await Twitter.get('statuses/user_timeline', {screen_name: accountSN, count: 10, trim_user: true, tweet_mode: 'extended'}) .then(function(data) { - // console.log(`Timeline for @${accountSN}:`); - // console.log(data); - - console.log(`Timeline count for @${accountSN} is ${data.length}.`); + log.log(`Timeline count for @${accountSN} is ${data.length}.`); return data; }) .catch(function(e) { @@ -105,12 +104,10 @@ async function getAccountTimeline(account) { async function getAccountInfo(account) { const accountSN = $u.getTwitterScreenName(account); - // console.log(`Getting user info for @${accountSN}…`) + log.log(`Getting user info for @${accountSN}…`) return await Twitter.get('users/show', {screen_name: accountSN}) .then(function(data) { - // console.log(`User info for @${accountSN}:`); - // console.log(data); return data; }) .catch(function(e) { @@ -129,7 +126,6 @@ function parseTwitterDate(aDate) { } module.exports = { - async testApi() { const testResult = { success: false, @@ -153,23 +149,23 @@ module.exports = { return testResult; } }, + // Search for predefined toFollowIds — save Twitter API requests // followAccount should be in "twitter_follow" param in config async checkIfAccountFollowing(twitterAccount, followAccount) { const twitterAccountSN = $u.getTwitterScreenName(twitterAccount); const followAccountSN = $u.getTwitterScreenName(followAccount); - console.log(`Checking if @${twitterAccountSN} follows @${followAccountSN}…`); + log.log(`Checking if @${twitterAccountSN} follows @${followAccountSN}…`); const followers = await getAccountFriendIds(twitterAccountSN); - // console.log(followers); return followers.includes(toFollowIds[followAccountSN]); }, + async checkIfAccountRetweetedwComment(twitterAccount, tweet, minMentions, hashtags) { const twitterAccountSN = $u.getTwitterScreenName(twitterAccount); const tweetId = $u.getTweetIdFromLink(tweet); hashtags = $u.getTwitterHashtags(hashtags); - // console.log(tweetId); - console.log(`Checking if @${twitterAccountSN} retweeted ${tweet}…`); + log.log(`Checking if @${twitterAccountSN} retweeted ${tweet}…`); const tweets = await getAccountTimeline(twitterAccountSN); let retweet = {}; @@ -209,13 +205,12 @@ module.exports = { error: '', }; }, + async checkIfAccountEligible(twitterAccount) { const twitterAccountSN = $u.getTwitterScreenName(twitterAccount); - console.log(`Checking if @${twitterAccountSN} eligible…`); + log.log(`Checking if @${twitterAccountSN} eligible…`); const accountInfo = await getAccountInfo(twitterAccountSN); - // console.log(accountInfo); - if (!accountInfo) { return { success: false, @@ -283,5 +278,4 @@ module.exports = { accountInfo, }; }, - }; diff --git a/server.js b/server.js index ea0bd69..a4bc178 100644 --- a/server.js +++ b/server.js @@ -1,3 +1,5 @@ +const log = require('../helpers/log'); + /** * @description http watched DB tables */ @@ -38,5 +40,5 @@ if (port) { }); }); - app.listen(port, () => console.info('Server listening on port ' + port + ' http://localhost:' + port + '/db?tb=systemDb')); + app.listen(port, () => log.info('Server listening on port ' + port + ' http://localhost:' + port + '/db?tb=systemDb')); } From 5f3ae41f146fd605f1745030ab2e6ba6c83a5a03 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Tue, 9 Aug 2022 14:26:22 +0300 Subject: [PATCH 29/55] Change console.log to log.log --- modules/twitterapi.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/twitterapi.js b/modules/twitterapi.js index e930b30..c8deaad 100644 --- a/modules/twitterapi.js +++ b/modules/twitterapi.js @@ -104,7 +104,7 @@ async function getAccountTimeline(account) { async function getAccountInfo(account) { const accountSN = $u.getTwitterScreenName(account); - log.log(`Getting user info for @${accountSN}…`) + log.log(`Getting user info for @${accountSN}…`); return await Twitter.get('users/show', {screen_name: accountSN}) .then(function(data) { From a78092016fb39fe5e55d05e14b5c405ad811752f Mon Sep 17 00:00:00 2001 From: adamant-al Date: Tue, 9 Aug 2022 14:34:45 +0300 Subject: [PATCH 30/55] chore: comments on getAccountInfo() --- modules/twitterapi.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/modules/twitterapi.js b/modules/twitterapi.js index c8deaad..5f75479 100644 --- a/modules/twitterapi.js +++ b/modules/twitterapi.js @@ -112,9 +112,18 @@ async function getAccountInfo(account) { }) .catch(function(e) { log.warn(`Error while making getAccountInfo() request: ${JSON.stringify(e)}`); - if (e && e[0] && (e[0].code === 50 || e[0].code === 63)) { // [{"code":50,"message":"User not found."}, {"code":63,"message":"User has been suspended."}] + if (e && e[0] && (e[0].code === 50 || e[0].code === 63)) { + /** + * {"code":50,"message":"User not found."} + * {"code":63,"message":"User has been suspended."} + */ return e[0]; - } else { // User can provide wrong Account, process this situation + } else { + /** + * User can provide wrong Account, process this situation + * {"code":89,"message":"Invalid or expired token."} Check API keys + * {"errno":-54,"code":"ECONNRESET","syscall":"read"} Twitter.com blocked by Roskomnadzor, use VPN + */ return false; } }); From 2fe0cabb3ec78600b93fceed765b9c3ec3fb707c Mon Sep 17 00:00:00 2001 From: adamant-al Date: Tue, 9 Aug 2022 14:39:15 +0300 Subject: [PATCH 31/55] fix: log module path --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index a4bc178..086df2e 100644 --- a/server.js +++ b/server.js @@ -1,4 +1,4 @@ -const log = require('../helpers/log'); +const log = require('./helpers/log'); /** * @description http watched DB tables From c2d0c1bdc02662538d89ff315cb4582fc287df30 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Tue, 9 Aug 2022 14:48:00 +0300 Subject: [PATCH 32/55] chore: send ADM messages for dev mode as well --- helpers/utils/index.js | 2 +- modules/incomingTxsParser.js | 8 ++++---- modules/unknownTxs.js | 2 ++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/helpers/utils/index.js b/helpers/utils/index.js index cfd46ef..33c3352 100644 --- a/helpers/utils/index.js +++ b/helpers/utils/index.js @@ -31,7 +31,7 @@ module.exports = { return epochTime * 1000 + constants.EPOCH; }, sendAdmMsg(address, msg, type = 'message') { - if (msg && !config.isDev) { + if (msg) { try { return api.send(config.passPhrase, address, msg, type).success || false; } catch (e) { diff --git a/modules/incomingTxsParser.js b/modules/incomingTxsParser.js index 144ca48..66291dd 100644 --- a/modules/incomingTxsParser.js +++ b/modules/incomingTxsParser.js @@ -26,7 +26,7 @@ module.exports = async (tx) => { return; } - log.log(`New incoming transaction: ${tx.id} from ${tx.senderId}`); + log.log(`Received incoming transaction ${tx.id} from ${tx.senderId}.`); let msg = ''; const chat = tx.asset.chat; @@ -51,7 +51,7 @@ module.exports = async (tx) => { } // Check if we should notify about spammer, only once per 24 hours - const spamerIsNotyfy = await IncomingTxsDb.findOne({ + const spamerIsNotify = await IncomingTxsDb.findOne({ sender: tx.senderId, isSpam: true, date: {$gt: ($u.unix() - 24 * 3600 * 1000)}, // last 24h @@ -76,7 +76,7 @@ module.exports = async (tx) => { date: {$gt: ($u.unix() - 24 * 3600 * 1000)}, // last 24h })).length; - if (countRequestsUser > 50 || spamerIsNotyfy) { // 50 per 24h is a limit for accepting commands, otherwise user will be considered as spammer + if (countRequestsUser > 50 || spamerIsNotify) { // 50 per 24h is a limit for accepting commands, otherwise user will be considered as spammer itx.update({ isProcessed: true, isSpam: true, @@ -89,7 +89,7 @@ module.exports = async (tx) => { } historyTxs[tx.id] = $u.unix(); - if (itx.isSpam && !spamerIsNotyfy) { + if (itx.isSpam && !spamerIsNotify) { notify(`${config.notifyName} notifies _${tx.senderId}_ is a spammer or talks too much. Income ADAMANT Tx: https://explorer.adamant.im/tx/${tx.id}.`, 'warn'); $u.sendAdmMsg(tx.senderId, `I’ve _banned_ you. You’ve sent too much transactions to me.`); return; diff --git a/modules/unknownTxs.js b/modules/unknownTxs.js index b5188f8..652e113 100644 --- a/modules/unknownTxs.js +++ b/modules/unknownTxs.js @@ -1,8 +1,10 @@ const $u = require('../helpers/utils'); +const log = require('../helpers/log'); const db = require('./DB'); const config = require('./configReader'); module.exports = async (tx, itx) => { + log.log(`Processing unknownTx from ${tx.senderId} (transaction ${tx.id})…`); const {IncomingTxsDb} = db; IncomingTxsDb.db From 1600f4054c873ae5f61baa9b5987bf21e9150570 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Tue, 9 Aug 2022 14:52:59 +0300 Subject: [PATCH 33/55] Update default package.json --- config.json | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/config.json b/config.json index a11c95e..cfc68dc 100644 --- a/config.json +++ b/config.json @@ -3,10 +3,10 @@ Bot's ADAMANT address will correspond this passPhrase. **/ "passPhrase": "qwert yuiop asdfg hjkl zxcvb nmqwe", + /** List of nodes to fetch transactions. If one become unavailable, pool will choose live one. **/ - "node_ADM": [ "http://localhost:36666", "https://endless.adamant.im", @@ -17,18 +17,23 @@ "http://185.231.245.26:36666", "https://lake.adamant.im" ], + /** Socket connection is recommended for better user experience **/ "socket": true, + /** Choose socket connection, "ws" or "wss" depending on your server **/ "ws_type": "ws", + /** List of nodes for Ethereum API work **/ "node_ETH": [ "https://ethnode1.adamant.im" ], + /** List of ADAMANT InfoServices for catching exchange rates **/ "infoservice": [ "https://info.adamant.im" ], + /** List of cryptocurrencies bot can work with. **/ "known_crypto": [ "ADM", @@ -37,31 +42,38 @@ "RES", "BZ" ], + /** List of ERC-20 tokens **/ "erc20": [ "USDS", "RES", "BZ" ], + /** How to reply user in-chat, if first unknown command received. **/ "welcome_string": "Hi! 😊 I'm a bounty bot. And this is a stub. Are you ready for the awesome bounty campaign with automatic payout? Type **/help** to see bounty rules.", + /** Bounty rules. Shown by /help command. You can use template literals like ${config.rewards_list}, ${config.rewards_tickers}, ${config.twitter_follow[0]}, ${config.twitter_retweet_w_comment[0].tweet}, ${config.twitter_follow_list}, ${config.twitter_retweet_w_comment[0].min_mentions}, ${config.twitter_retweet_w_comment[0].tag_list}, ${config.twitterEligibleString} - **/ + **/ "help_message": "Earn **${config.rewards_tickers}** with your social activity! A reward depends on how much Twitter followers you have. Your account ${config.twitterEligibleString}.\n${config.rewards_range}\n\n \nThe campaign rules:\n- Follow account ${config.twitter_follow_list} on Twitter\n- Like & quote ${config.twitter_retweet_w_comment[0].tweet}, mentioning ${config.twitter_retweet_w_comment[0].min_mentions} friends and ${config.twitter_retweet_w_comment[0].tag_list} tags.\n- Invite ${config.adamant_campaign.min_contacts} friend in ADAMANT Messenger. They must message you.\n- Send me the name (like @adamant_im) or the link to your Twitter profile to verify.\n\n \nGo!", + /** Bot's name for notifications **/ "bot_name": "Lovely Bounty Bot", + /** ADAMANT accounts to accept control commands from. Control commands from other accounts will not be executed. **/ "admin_accounts": [ "U1123..." ], + /** Twitter accounts user should follow **/ "twitter_follow": [ "@adamant_im" ], + /** Requirements for user's Twitter account. Set all parameters to 0 if even new accounts eligible **/ "twitter_reqs": { "min_followers": 10, @@ -69,6 +81,7 @@ "min_statuses": 5, "min_days": 20 }, + /** Tweets user should quote (retweet with comment). Min_mentions is how much people he should mention. Hashtags is a list of tags he must use. **/ @@ -84,6 +97,7 @@ ] } ], + /** Minimum contacts user must invite to ADAMANT Messenger. Contacts must be new users. 0 is disabled. @@ -91,6 +105,7 @@ "adamant_campaign": { "min_contacts": 1 }, + /** List rewards for a Bounty campaign here **/ "rewards": [ { @@ -102,6 +117,7 @@ "amount": 0.01 } ], + /** Set progressive scale of reward amounts for each cryptocurrency. `func` is a mathjs.org function. Limit followers with `limit_followers` parameter. If not set for a currency, plain amount is used, which is set in `rewards.amount`. @@ -114,6 +130,7 @@ "decimals_show": 0 } }, + /** Your Twitter API credentials. Get on https://apps.twitter.com/app/new **/ "twitter_api": { "consumer_key": "", @@ -121,6 +138,7 @@ "access_token_key": "", "access_token_secret": "" }, + /** Interval in minutes to test Twitter API. Because of different reasons Twitter may temporary block API requests. To continue, you need manually login into your Twitter account and solve captcha. This parameter allows to automatically check if Twitter API works well every twitter_api_test_interval minutes. @@ -128,22 +146,29 @@ 0 means disabled. **/ "twitter_api_test_interval": 600, + /** ADAMANT accounts to accept control commands from. Control commands from other accounts will not be executed. **/ /** Notify non-admins that they are not admins. If false, bot will be silent. **/ - "notify_non_admins": true, + "notify_non_admins": false, + /** ADAMANT address for notifications and monitoring (if needed, recommended) **/ "adamant_notify": "", + /** Slack key for notifications and monitoring (if needed) **/ "slack": "https://hooks.slack.com/services/", + /** If you want to receive notifications when user completes Bounty tasks **/ "notifyTasksCompleted": true, + /** If you want to receive notifications when user receives a Bounty reward **/ "notifyRewardReceived": true, + /** Port for getting debug info. Do not set for live exchange bots, use only for debugging. Allows to get DBs records like http://ip:port/db?tb=IncomingTxs **/ "api": false, + /** The software will use verbosity according to log_level. It can be none < error < warn < info < log. **/ From 42dedac9446602b5618c17e0692408a0dc463411 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Tue, 9 Aug 2022 23:15:10 +0300 Subject: [PATCH 34/55] fix: get last crypto address from KVS --- helpers/utils/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/helpers/utils/index.js b/helpers/utils/index.js index 0aa81af..f9ed899 100644 --- a/helpers/utils/index.js +++ b/helpers/utils/index.js @@ -12,10 +12,10 @@ module.exports = { if (this.isERC20(coin)) { coin = 'ETH'; } - const res = await api.get('states/get', {senderId: admAddress, key: coin.toLowerCase() + ':address'}); - if (res.success) { - if (res.data.transactions.length) { - return res.data.transactions[0].asset.state.value; + const kvsRecords = await api.get('states/get', {senderId: admAddress, key: coin.toLowerCase() + ':address', orderBy: 'timestamp:desc'}); + if (kvsRecords.success) { + if (kvsRecords.data.transactions.length) { + return kvsRecords.data.transactions[0].asset.state.value; } else { return 'none'; } From 075bb8c490c27f698972f8198c9e54ce2f797767 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Tue, 9 Aug 2022 23:15:27 +0300 Subject: [PATCH 35/55] fix: get last crypto address from KVS --- helpers/utils/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/utils/index.js b/helpers/utils/index.js index f9ed899..3c4ed13 100644 --- a/helpers/utils/index.js +++ b/helpers/utils/index.js @@ -20,7 +20,7 @@ module.exports = { return 'none'; } } else { - log.warn(`Failed to get ${coin} address for ${admAddress} from KVS in getAddressCryptoFromAdmAddressADM() of ${helpers.getModuleName(module.id)} module. ${res.errorMessage}.`); + log.warn(`Failed to get ${coin} address for ${admAddress} from KVS in getAddressCryptoFromAdmAddressADM() of ${helpers.getModuleName(module.id)} module. ${kvsRecords.errorMessage}.`); } }, async updateAllBalances() { From 156c2a63530d80f5a93b510685b9bb70d24e124c Mon Sep 17 00:00:00 2001 From: adamant-al Date: Tue, 9 Aug 2022 23:54:37 +0300 Subject: [PATCH 36/55] fix: LSK balance --- helpers/utils/index.js | 1 + modules/commandTxs.js | 4 ++-- modules/rewardsPayer.js | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/helpers/utils/index.js b/helpers/utils/index.js index 3c4ed13..5bf6755 100644 --- a/helpers/utils/index.js +++ b/helpers/utils/index.js @@ -27,6 +27,7 @@ module.exports = { try { await this.ETH.updateBalance(); await this.ADM.updateBalance(); + await this.LSK.updateBalance(); for (const t of config.erc20) { await this[t].updateBalance(); } diff --git a/modules/commandTxs.js b/modules/commandTxs.js index 6066e9c..ae7ffc8 100644 --- a/modules/commandTxs.js +++ b/modules/commandTxs.js @@ -190,8 +190,8 @@ function version() { function balances() { let output = ''; config.known_crypto.forEach((crypto) => { - if (Store.user[crypto].balance && Store.user[crypto].balance !== undefined) { - output += `${$u.thousandSeparator(+Store.user[crypto].balance.toFixed(8), true)} _${crypto}_`; + if (Store.user[crypto].balance) { + output += `${helpers.thousandSeparator(+Store.user[crypto].balance.toFixed(8), true)} _${crypto}_`; output += '\n'; } }); diff --git a/modules/rewardsPayer.js b/modules/rewardsPayer.js index 2a6a1cb..d27f198 100644 --- a/modules/rewardsPayer.js +++ b/modules/rewardsPayer.js @@ -48,7 +48,7 @@ module.exports = async () => { return; } - log.log(`Attempt number ${pay.trySendCounter} to send the reward payout. Coin: ${outCurrency}, address: ${outAddress}, value: ${outAmount}, balance: ${Store.user[outCurrency].balance}`); + log.log(`Attempt number ${pay.trySendCounter} to send the reward payout. Coin: ${outCurrency}, recipient address: ${outAddress}, amount: ${outAmount}, bot's balance: ${Store.user[outCurrency].balance} ${outCurrency}.`); const result = await $u[outCurrency].send({ address: outAddress, value: outAmount, From d1d889339c827e6bb3a40c2d578159e26a582956 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Tue, 9 Aug 2022 23:56:22 +0300 Subject: [PATCH 37/55] fix: update LSK cache lifetime --- helpers/utils/lskBaseCoin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helpers/utils/lskBaseCoin.js b/helpers/utils/lskBaseCoin.js index 35562c5..28f54ea 100644 --- a/helpers/utils/lskBaseCoin.js +++ b/helpers/utils/lskBaseCoin.js @@ -33,8 +33,8 @@ module.exports = class LskBaseCoin { }, }; - this.cache.balance = {lifetime: 30000}; - this.cache.lastBlock = {lifetime: 60000}; + this.cache.balance = {lifetime: 7000}; + this.cache.lastBlock = {lifetime: 7000}; } /** From f018739ae0a6e4b60c466b868817c4e0b4b4fff5 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Wed, 10 Aug 2022 00:15:08 +0300 Subject: [PATCH 38/55] fix: send ADM message about crypto transfer --- modules/sentTxValidator.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/sentTxValidator.js b/modules/sentTxValidator.js index a67d283..288fb3b 100644 --- a/modules/sentTxValidator.js +++ b/modules/sentTxValidator.js @@ -87,8 +87,13 @@ module.exports = async () => { msgSendBack = 'Was it great? Share the experience with your friends!'; if (outCurrency !== 'ADM') { - msgSendBack = `{"type":"${outCurrency}_transaction","amount":"${outAmount}","hash":"${outTxid}","comments":"${msgSendBack}"}`; - pay.isFinished = await api.sendMessage(config.passPhrase, userId, msgSendBack, 'rich'); + msgSendBack = `{"type":"${outCurrency.toLowerCase()}_transaction","amount":"${outAmount}","hash":"${outTxid}","comments":"${msgSendBack}"}`; + const message = await api.sendMessage(config.passPhrase, userId, msgSendBack, 'rich'); + if (message.success) { + pay.isFinished = true; + } else { + log.warn(`Failed to send ADM message on sent Tx ${outTxid} of ${outAmount} ${outCurrency} to ${userId}. I will try again. ${message?.errorMessage}.`); + } } else { pay.isFinished = true; } From ac2052319376261bf91a5d2c691bdebadb7ffe70 Mon Sep 17 00:00:00 2001 From: gost1k Date: Wed, 10 Aug 2022 17:02:18 +0300 Subject: [PATCH 39/55] fixes: refactor and add error handling --- app.js | 2 +- config.default.json | 7 ++----- helpers/{utils => cryptos}/adm_utils.js | 9 +++++++-- helpers/cryptos/erc20_models.js | 6 ++++++ helpers/{utils => cryptos}/erc20_utils.js | 0 helpers/{utils => cryptos}/eth_utils.js | 0 helpers/{utils => cryptos}/index.js | 2 +- helpers/{utils => cryptos}/lskBaseCoin.js | 2 +- helpers/{utils => cryptos}/lsk_utils.js | 2 +- helpers/{index.js => utils.js} | 0 helpers/utils/erc20_models.js | 14 -------------- modules/Store.js | 2 +- modules/checkAdamantContacts.js | 8 ++++++-- modules/checkAll.js | 8 ++++++-- modules/checkTwitterFollow.js | 8 ++++++-- modules/checkTwitterReqs.js | 8 ++++++-- modules/checkTwitterRetweet.js | 8 ++++++-- modules/checkTxs.js | 20 ++++++++++++++++---- modules/checkerTransactions.js | 2 +- modules/commandTxs.js | 17 +++++++++++++---- modules/incomingTxsParser.js | 11 ++++++++--- modules/outAddressFetcher.js | 10 +++++++--- modules/rewardsPayer.js | 18 ++++++++++++++---- modules/sentTxValidator.js | 23 ++++++++++++++++++----- modules/transferTxs.js | 7 ++++++- modules/twitterapi.js | 2 +- modules/unknownTxs.js | 8 ++++++-- tests/lisk.test.js | 2 +- 28 files changed, 141 insertions(+), 65 deletions(-) rename helpers/{utils => cryptos}/adm_utils.js (89%) create mode 100644 helpers/cryptos/erc20_models.js rename helpers/{utils => cryptos}/erc20_utils.js (100%) rename helpers/{utils => cryptos}/eth_utils.js (100%) rename helpers/{utils => cryptos}/index.js (99%) rename helpers/{utils => cryptos}/lskBaseCoin.js (99%) rename helpers/{utils => cryptos}/lsk_utils.js (99%) rename helpers/{index.js => utils.js} (100%) delete mode 100644 helpers/utils/erc20_models.js diff --git a/app.js b/app.js index 2a2ed4e..d49638d 100644 --- a/app.js +++ b/app.js @@ -13,7 +13,7 @@ api.socket.initSocket({socket: config.socket, wsType: config.ws_type, onNewMessa setTimeout(init, 5000); function init() { - require('./helpers/utils/erc20_utils'); + require('./helpers/cryptos/erc20_utils'); require('./server'); require('./modules/checkTwitterFollow'); require('./modules/apiTester'); diff --git a/config.default.json b/config.default.json index 2cd7564..dad9ff2 100644 --- a/config.default.json +++ b/config.default.json @@ -46,15 +46,12 @@ "ADM", "ETH", "USDS", - "RES", - "BZ" + "LSK" ], /** List of ERC-20 tokens **/ "erc20": [ - "USDS", - "RES", - "BZ" + "USDS" ], /** How to reply user in-chat, if first unknown command received. **/ diff --git a/helpers/utils/adm_utils.js b/helpers/cryptos/adm_utils.js similarity index 89% rename from helpers/utils/adm_utils.js rename to helpers/cryptos/adm_utils.js index 221dd88..dfa3158 100644 --- a/helpers/utils/adm_utils.js +++ b/helpers/cryptos/adm_utils.js @@ -2,7 +2,7 @@ const Store = require('../../modules/Store'); const api = require('../../modules/api'); const log = require('../log'); const config = require('../../modules/configReader'); -const helpers = require('../../helpers'); +const helpers = require('../utils'); const {SAT} = require('../const'); const User = Store.user.ADM; @@ -40,7 +40,12 @@ module.exports = { }, async send(params) { const {address, value, comment} = params; - const payment = await api.sendMessage(config.passPhrase, address, comment, 'basic', value); + const payment = await api.sendMessage(config.passPhrase, address, comment, 'basic', value).then((response) => { + if (!response.success) { + log.warn(`Failed to send ADM message '${comment}' to ${address}. ${response.errorMessage}.`); + } + return response; + }); if (payment.success) { log.log(`Successfully sent ${value} ADM to ${address} with comment '${comment}', Tx hash: ${payment.data.transactionId}.`); return { diff --git a/helpers/cryptos/erc20_models.js b/helpers/cryptos/erc20_models.js new file mode 100644 index 0000000..3bc598a --- /dev/null +++ b/helpers/cryptos/erc20_models.js @@ -0,0 +1,6 @@ +module.exports = { + USDS: { + sat: 1000000, // round (6 decimals) + sc: '0xa4bdb11dc0a2bec88d24a3aa1e6bb17201112ebe', + }, +}; diff --git a/helpers/utils/erc20_utils.js b/helpers/cryptos/erc20_utils.js similarity index 100% rename from helpers/utils/erc20_utils.js rename to helpers/cryptos/erc20_utils.js diff --git a/helpers/utils/eth_utils.js b/helpers/cryptos/eth_utils.js similarity index 100% rename from helpers/utils/eth_utils.js rename to helpers/cryptos/eth_utils.js diff --git a/helpers/utils/index.js b/helpers/cryptos/index.js similarity index 99% rename from helpers/utils/index.js rename to helpers/cryptos/index.js index 5bf6755..3353b89 100644 --- a/helpers/utils/index.js +++ b/helpers/cryptos/index.js @@ -5,7 +5,7 @@ const adm_utils = require('./adm_utils'); const LskCoin = require('./lsk_utils'); const log = require('../log'); const Store = require('../../modules/Store'); -const helpers = require('../../helpers'); +const helpers = require('../utils'); module.exports = { async getAddressCryptoFromAdmAddressADM(coin, admAddress) { diff --git a/helpers/utils/lskBaseCoin.js b/helpers/cryptos/lskBaseCoin.js similarity index 99% rename from helpers/utils/lskBaseCoin.js rename to helpers/cryptos/lskBaseCoin.js index 28f54ea..2549449 100644 --- a/helpers/utils/lskBaseCoin.js +++ b/helpers/cryptos/lskBaseCoin.js @@ -4,7 +4,7 @@ const axios = require('axios'); const transactions = require('@liskhq/lisk-transactions'); const cryptography = require('@liskhq/lisk-cryptography'); -const helpers = require('../../helpers'); +const helpers = require('../utils'); const api = require('../../modules/api'); const lskNode = config.node_LSK[0]; diff --git a/helpers/utils/lsk_utils.js b/helpers/cryptos/lsk_utils.js similarity index 99% rename from helpers/utils/lsk_utils.js rename to helpers/cryptos/lsk_utils.js index 39bd97c..b20c385 100644 --- a/helpers/utils/lsk_utils.js +++ b/helpers/cryptos/lsk_utils.js @@ -4,7 +4,7 @@ const transactions = require('@liskhq/lisk-transactions'); const cryptography = require('@liskhq/lisk-cryptography'); const LskBaseCoin = require('./lskBaseCoin'); -const helpers = require('../../helpers'); +const helpers = require('../utils'); const Store = require('../../modules/Store'); const lskNode = config.node_LSK[0]; diff --git a/helpers/index.js b/helpers/utils.js similarity index 100% rename from helpers/index.js rename to helpers/utils.js diff --git a/helpers/utils/erc20_models.js b/helpers/utils/erc20_models.js deleted file mode 100644 index cdae268..0000000 --- a/helpers/utils/erc20_models.js +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - USDS: { - sat: 1000000, // round (6 decimals) - sc: '0xa4bdb11dc0a2bec88d24a3aa1e6bb17201112ebe', - }, - RES: { - sat: 100000, // round (5 decimals) - sc: '0x0a9f693fce6f00a51a8e0db4351b5a8078b4242e', - }, - BZ: { - sat: 1000000000000000000, // round (18 decimals) - sc: '0x4375e7ad8a01b8ec3ed041399f62d9cd120e0063', - }, -}; diff --git a/modules/Store.js b/modules/Store.js index 7efeb68..b34c0ff 100644 --- a/modules/Store.js +++ b/modules/Store.js @@ -3,7 +3,7 @@ const log = require('../helpers/log'); const keys = require('adamant-api/src/helpers/keys'); const api = require('./api'); const axios = require('axios'); -const helpers = require('../helpers'); +const helpers = require('../helpers/utils'); const {version} = require('../package.json'); const config = require('./configReader'); diff --git a/modules/checkAdamantContacts.js b/modules/checkAdamantContacts.js index 96a9114..d17913b 100644 --- a/modules/checkAdamantContacts.js +++ b/modules/checkAdamantContacts.js @@ -1,6 +1,6 @@ const db = require('./DB'); const config = require('./configReader'); -const helpers = require('../helpers'); +const helpers = require('../helpers/utils'); const log = require('../helpers/log'); const api = require('./api'); const Store = require('./Store'); @@ -93,7 +93,11 @@ module.exports = async () => { msgSendBack = `To meet the Bounty campaign rules, you should invite ${config.adamant_campaign.min_contacts} friends in ADAMANT Messenger. They must message you. They can join this bounty campaign as well! Invite friends and apply again.`; } - await api.sendMessage(config.passPhrase, userId, msgSendBack); + await api.sendMessage(config.passPhrase, userId, msgSendBack).then((response) => { + if (!response.success) { + log.warn(`Failed to send ADM message '${msgSendBack}' to ${userId}. ${response.errorMessage}.`); + } + }); log.log(`User ${userId}… did NOT make ${config.adamant_campaign.min_contacts} contacts. Message to user: ${msgSendBack}`); } } catch (e) { diff --git a/modules/checkAll.js b/modules/checkAll.js index 76849da..a92f97a 100644 --- a/modules/checkAll.js +++ b/modules/checkAll.js @@ -1,6 +1,6 @@ const db = require('./DB'); const config = require('./configReader'); -const helpers = require('../helpers'); +const helpers = require('../helpers/utils'); const api = require('./api'); const log = require('../helpers/log'); const notify = require('../helpers/notify'); @@ -83,7 +83,11 @@ module.exports = async () => { notify(`${config.notifyName}: User ${userId}${twitterString} completed the Bounty tasks. Payouts are pending.`, 'log'); } msgSendBack = `Great, you've completed all the tasks! Reward is coming right now!`; - await api.sendMessage(config.passPhrase, userId, msgSendBack); + await api.sendMessage(config.passPhrase, userId, msgSendBack).then((response) => { + if (!response.success) { + log.warn(`Failed to send ADM message '${msgSendBack}' to ${userId}. ${response.errorMessage}.`); + } + }); } } catch (e) { log.error(`Error in ${helpers.getModuleName(module.id)} module: ${e}`); diff --git a/modules/checkTwitterFollow.js b/modules/checkTwitterFollow.js index 002fb3b..baccd32 100644 --- a/modules/checkTwitterFollow.js +++ b/modules/checkTwitterFollow.js @@ -1,7 +1,7 @@ const db = require('./DB'); const config = require('./configReader'); const api = require('./api'); -const helpers = require('../helpers'); +const helpers = require('../helpers/utils'); const log = require('../helpers/log'); const twitterapi = require('./twitterapi'); @@ -52,7 +52,11 @@ module.exports = async () => { isTasksCompleted: false, }, true); msgSendBack = `To meet the Bounty campaign rules, you should follow Twitter account ${followAccount}. Then you apply again.`; - await api.sendMessage(config.passPhrase, userId, msgSendBack); + await api.sendMessage(config.passPhrase, userId, msgSendBack).then((response) => { + if (!response.success) { + log.warn(`Failed to send ADM message '${msgSendBack}' to ${userId}. ${response.errorMessage}.`); + } + }); log.log(`User ${userId}… ${twitterAccount} do NOT follows ${followAccount}. Message to user: ${msgSendBack}`); break; } diff --git a/modules/checkTwitterReqs.js b/modules/checkTwitterReqs.js index 9df7214..93b42f7 100644 --- a/modules/checkTwitterReqs.js +++ b/modules/checkTwitterReqs.js @@ -1,6 +1,6 @@ const db = require('./DB'); const config = require('./configReader'); -const helpers = require('../helpers'); +const helpers = require('../helpers/utils'); const api = require('./api'); const log = require('../helpers/log'); const twitterapi = require('./twitterapi'); @@ -77,7 +77,11 @@ module.exports = async () => { msgSendBack = `To meet the Bounty campaign rules, your Twitter account ${config.twitterEligibleString}.`; } - await api.sendMessage(config.passPhrase, userId, msgSendBack); + await api.sendMessage(config.passPhrase, userId, msgSendBack).then((response) => { + if (!response.success) { + log.warn(`Failed to send ADM message '${msgSendBack}' to ${userId}. ${response.errorMessage}.`); + } + }); log.log(`User ${userId}… ${twitterAccount} is NOT eligible. Message to user: ${msgSendBack}`); } diff --git a/modules/checkTwitterRetweet.js b/modules/checkTwitterRetweet.js index f67dfdc..c4d564c 100644 --- a/modules/checkTwitterRetweet.js +++ b/modules/checkTwitterRetweet.js @@ -1,6 +1,6 @@ const db = require('./DB'); const config = require('./configReader'); -const helpers = require('../helpers'); +const helpers = require('../helpers/utils'); const api = require('./api'); const log = require('../helpers/log'); const twitterapi = require('./twitterapi'); @@ -106,7 +106,11 @@ module.exports = async () => { break; } - await api.sendMessage(config.passPhrase, userId, msgSendBack); + await api.sendMessage(config.passPhrase, userId, msgSendBack).then((response) => { + if (!response.success) { + log.warn(`Failed to send ADM message '${msgSendBack}' to ${userId}. ${response.errorMessage}.`); + } + }); log.log(`User ${userId}… ${twitterAccount} did NOT retweet ${toRetweet}: ${retweetResult.error}. Message to user: ${msgSendBack}`); break; diff --git a/modules/checkTxs.js b/modules/checkTxs.js index 6813170..c13e8ea 100644 --- a/modules/checkTxs.js +++ b/modules/checkTxs.js @@ -1,4 +1,4 @@ -const helpers = require('../helpers'); +const helpers = require('../helpers/utils'); const db = require('./DB'); const api = require('./api'); const log = require('../helpers/log'); @@ -23,7 +23,11 @@ module.exports = async (itx, tx) => { } } if (msgSendBack) { // Do not send anything, if isInCheck - await api.sendMessage(config.passPhrase, tx.senderId, msgSendBack); + await api.sendMessage(config.passPhrase, tx.senderId, msgSendBack).then((response) => { + if (!response.success) { + log.warn(`Failed to send ADM message '${msgSendBack}' to ${tx.senderId}. ${response.errorMessage}.`); + } + }); } return; } @@ -42,7 +46,11 @@ module.exports = async (itx, tx) => { if (user.isTasksCompleted) { log.log(`User ${user.userId} already completed the Bounty tasks. Notify user and ignore.`); msgSendBack = `You've already completed the Bounty tasks.`; - await api.sendMessage(config.passPhrase, tx.senderId, msgSendBack); + await api.sendMessage(config.passPhrase, tx.senderId, msgSendBack).then((response) => { + if (!response.success) { + log.warn(`Failed to send ADM message '${msgSendBack}' to ${tx.senderId}. ${response.errorMessage}.`); + } + }); return; } @@ -85,5 +93,9 @@ module.exports = async (itx, tx) => { await itx.update({isProcessed: true}, true); msgSendBack = `I've got your account details. Twitter: ${user.twitterAccount}. I'll check if you've finished the Bounty tasks now…`; - await api.sendMessage(config.passPhrase, tx.senderId, msgSendBack); + await api.sendMessage(config.passPhrase, tx.senderId, msgSendBack).then((response) => { + if (!response.success) { + log.warn(`Failed to send ADM message '${msgSendBack}' to ${tx.senderId}. ${response.errorMessage}.`); + } + }); }; diff --git a/modules/checkerTransactions.js b/modules/checkerTransactions.js index bf91ade..99cd29d 100644 --- a/modules/checkerTransactions.js +++ b/modules/checkerTransactions.js @@ -1,5 +1,5 @@ const Store = require('./Store'); -const helpers = require('../helpers'); +const helpers = require('../helpers/utils'); const config = require('./configReader'); const api = require('./api'); const txParser = require('./incomingTxsParser'); diff --git a/modules/commandTxs.js b/modules/commandTxs.js index ae7ffc8..81d8718 100644 --- a/modules/commandTxs.js +++ b/modules/commandTxs.js @@ -1,7 +1,7 @@ const Store = require('../modules/Store'); -const $u = require('../helpers/utils'); +const $u = require('../helpers/cryptos'); const api = require('./api'); -const helpers = require('./../helpers'); +const helpers = require('../helpers/utils'); const config = require('./configReader'); const log = require('../helpers/log'); const notify = require('../helpers/notify'); @@ -28,7 +28,12 @@ module.exports = async (cmd, tx, itx) => { isNonAdmin: true, }, true); if (config.notify_non_admins) { - await api.sendMessage(config.passPhrase, tx.senderId, `I won't execute admin commands as you are not an admin. Contact my master.`); + const msgSendBack = `I won't execute admin commands as you are not an admin. Contact my master.`; + await api.sendMessage(config.passPhrase, tx.senderId, msgSendBack).then((response) => { + if (!response.success) { + log.warn(`Failed to send ADM message '${msgSendBack}' to ${tx.senderId}. ${response.errorMessage}.`); + } + }); } return; } @@ -48,7 +53,11 @@ module.exports = async (cmd, tx, itx) => { notify(res.msgNotify, res.notifyType); } if (res.msgSendBack) { - await api.sendMessage(config.passPhrase, tx.senderId, res.msgSendBack); + await api.sendMessage(config.passPhrase, tx.senderId, res.msgSendBack).then((response) => { + if (!response.success) { + log.warn(`Failed to send ADM message '${res.msgSendBack}' to ${tx.senderId}. ${response.errorMessage}.`); + } + }); } } } catch (e) { diff --git a/modules/incomingTxsParser.js b/modules/incomingTxsParser.js index 7e896a9..584eded 100644 --- a/modules/incomingTxsParser.js +++ b/modules/incomingTxsParser.js @@ -1,8 +1,8 @@ const db = require('./DB'); const log = require('../helpers/log'); -const $u = require('../helpers/utils'); +const $u = require('../helpers/cryptos'); const api = require('./api'); -const helpers = require('./../helpers'); +const helpers = require('../helpers/utils'); const config = require('./configReader'); const commandTxs = require('./commandTxs'); const unknownTxs = require('./unknownTxs'); @@ -92,7 +92,12 @@ module.exports = async (tx) => { if (itx.isSpam && !spamerIsNotify) { notify(`${config.notifyName} notifies _${tx.senderId}_ is a spammer or talks too much. Income ADAMANT Tx: https://explorer.adamant.im/tx/${tx.id}.`, 'warn'); - await api.sendMessage(config.passPhrase, tx.senderId, `I’ve _banned_ you. You’ve sent too much transactions to me.`); + const msgSendBack = `I’ve _banned_ you. You’ve sent too much transactions to me.`; + await api.sendMessage(config.passPhrase, tx.senderId, msgSendBack).then((response) => { + if (!response.success) { + log.warn(`Failed to send ADM message '${msgSendBack}' to ${tx.senderId}. ${response.errorMessage}.`); + } + }); return; } diff --git a/modules/outAddressFetcher.js b/modules/outAddressFetcher.js index 8cb2582..55cf511 100644 --- a/modules/outAddressFetcher.js +++ b/modules/outAddressFetcher.js @@ -1,6 +1,6 @@ const log = require('../helpers/log'); -const $u = require('../helpers/utils'); -const helpers = require('../helpers'); +const $u = require('../helpers/cryptos'); +const helpers = require('../helpers/utils'); const api = require('./api'); const notify = require('../helpers/notify'); const config = require('./configReader'); @@ -36,7 +36,11 @@ module.exports = async () => { msgSendBack = `I can’t get your _${pay.outCurrency}_ address from ADAMANT KVS to pay a reward. Make sure you use ADAMANT wallet with _${pay.outCurrency}_ enabled. I have already notified my master.`; notify(msgNotify, 'error'); let tx; - await api.sendMessage(config.passPhrase, tx.userId, msgSendBack); + await api.sendMessage(config.passPhrase, tx.userId, msgSendBack).then((response) => { + if (!response.success) { + log.warn(`Failed to send ADM message '${msgSendBack}' to ${tx.userId}. ${response.errorMessage}.`); + } + }); } } else { pay.update({ diff --git a/modules/rewardsPayer.js b/modules/rewardsPayer.js index d27f198..9eb3061 100644 --- a/modules/rewardsPayer.js +++ b/modules/rewardsPayer.js @@ -1,7 +1,7 @@ const db = require('./DB'); const config = require('./configReader'); -const $u = require('../helpers/utils'); -const helpers = require('../helpers'); +const $u = require('../helpers/cryptos'); +const helpers = require('../helpers/utils'); const api = require('./api'); const Store = require('./Store'); const log = require('../helpers/log'); @@ -44,7 +44,12 @@ module.exports = async () => { isPayed: false, }, true); notify(`${config.notifyName} notifies about insufficient balance to send a reward of _${outAmount}_ _${outCurrency}_. Balance of _${outCurrency}_ is _${Store.user[outCurrency].balance}_. ${etherString}User ADAMANT id: ${userId}.`, 'error'); - await api.sendMessage(config.passPhrase, userId, `I can’t transfer a reward of _${outAmount}_ _${outCurrency}_ to you because of insufficient funds (I count blockchain fees also). I have already notified my master.`); + const msgSendBack = `I can’t transfer a reward of _${outAmount}_ _${outCurrency}_ to you because of insufficient funds (I count blockchain fees also). I have already notified my master.`; + await api.sendMessage(config.passPhrase, userId, msgSendBack).then((response) => { + if (!response.success) { + log.warn(`Failed to send ADM message '${msgSendBack}' to ${userId}. ${response.errorMessage}.`); + } + }); return; } @@ -82,7 +87,12 @@ module.exports = async () => { isPayed: false, }, true); notify(`${config.notifyName} cannot make transaction to payout a reward of _${outAmount}_ _${outCurrency}_. Balance of _${outCurrency}_ is _${Store.user[outCurrency].balance}_. ${etherString}User ADAMANT id: ${userId}.`, 'error'); - await api.sendMessage(config.passPhrase, userId, `I’ve tried to make a reward payout of _${outAmount}_ _${outCurrency}_ to you, but something went wrong. I have already notified my master.`); + const msgSendBack = `I’ve tried to make a reward payout of _${outAmount}_ _${outCurrency}_ to you, but something went wrong. I have already notified my master.`; + await api.sendMessage(config.passPhrase, userId, msgSendBack).then((response) => { + if (!response.success) { + log.warn(`Failed to send ADM message '${msgSendBack}' to ${userId}. ${response.errorMessage}.`); + } + }); } } catch (e) { log.error(`Error in ${helpers.getModuleName(module.id)} module: ${e.toString()}`); diff --git a/modules/sentTxValidator.js b/modules/sentTxValidator.js index 288fb3b..c59004d 100644 --- a/modules/sentTxValidator.js +++ b/modules/sentTxValidator.js @@ -1,7 +1,7 @@ const db = require('./DB'); const config = require('./configReader'); -const $u = require('../helpers/utils'); -const helpers = require('../helpers'); +const $u = require('../helpers/cryptos'); +const helpers = require('../helpers/utils'); const api = require('./api'); const Store = require('./Store'); const log = require('../helpers/log'); @@ -53,7 +53,11 @@ module.exports = async () => { msgSendBack = `I’ve tried to make the reward payout of _${outAmount}_ _${outCurrency}_ to you, but unable to validate transaction. Tx hash: _${outTxid}_. I’ve already notified my master. If you wouldn’t receive transfer in two days, contact my master also.`; notify(msgNotify, notifyType); - await api.sendMessage(config.passPhrase, userId, msgSendBack); + await api.sendMessage(config.passPhrase, userId, msgSendBack).then((response) => { + if (!response.success) { + log.warn(`Failed to send ADM message '${msgSendBack}' to ${userId}. ${response.errorMessage}.`); + } + }); } await pay.save(); return; @@ -78,7 +82,11 @@ module.exports = async () => { msgNotify = `${config.notifyName} notifies that the reward payout of _${outAmount}_ _${outCurrency}_ failed. Tx hash: _${outTxid}_. Will try again. Balance of _${outCurrency}_ is _${Store.user[outCurrency].balance}_. ${etherString}User ADAMANT id: ${userId}.`; msgSendBack = `I’ve tried to make the payout transfer of _${outAmount}_ _${outCurrency}_ to you, but it seems transaction failed. Tx hash: _${outTxid}_. I will try again. If I’ve said the same several times already, please contact my master.`; - await api.sendMessage(config.passPhrase, userId, msgSendBack); + await api.sendMessage(config.passPhrase, userId, msgSendBack).then((response) => { + if (!response.success) { + log.warn(`Failed to send ADM message '${msgSendBack}' to ${userId}. ${response.errorMessage}.`); + } + }); } else if (status && pay.outConfirmations >= config.min_confirmations) { notifyType = 'info'; if (config.notifyRewardReceived) { @@ -88,7 +96,12 @@ module.exports = async () => { if (outCurrency !== 'ADM') { msgSendBack = `{"type":"${outCurrency.toLowerCase()}_transaction","amount":"${outAmount}","hash":"${outTxid}","comments":"${msgSendBack}"}`; - const message = await api.sendMessage(config.passPhrase, userId, msgSendBack, 'rich'); + const message = await api.sendMessage(config.passPhrase, userId, msgSendBack, 'rich').then((response) => { + if (!response.success) { + log.warn(`Failed to send ADM message '${msgSendBack}' to ${userId}. ${response.errorMessage}.`); + } + return response; + }); if (message.success) { pay.isFinished = true; } else { diff --git a/modules/transferTxs.js b/modules/transferTxs.js index 5392951..539b6cd 100644 --- a/modules/transferTxs.js +++ b/modules/transferTxs.js @@ -2,6 +2,7 @@ const {SAT} = require('../helpers/const'); const api = require('./api'); const notify = require('../helpers/notify'); const config = require('./configReader'); +const log = require('./../helpers/log'); module.exports = async (itx, tx) => { const msg = itx.encrypted_content; @@ -38,5 +39,9 @@ module.exports = async (itx, tx) => { await itx.update({isProcessed: true}, true); notify(msgNotify, notifyType); - await api.sendMessage(config.passPhrase, tx.senderId, msgSendBack); + await api.sendMessage(config.passPhrase, tx.senderId, msgSendBack).then((response) => { + if (!response.success) { + log.warn(`Failed to send ADM message '${msgSendBack}' to ${tx.senderId}. ${response.errorMessage}.`); + } + }); }; diff --git a/modules/twitterapi.js b/modules/twitterapi.js index 5f75479..65cc4e7 100644 --- a/modules/twitterapi.js +++ b/modules/twitterapi.js @@ -1,4 +1,4 @@ -const $u = require('../helpers/utils'); +const $u = require('../helpers/cryptos'); const log = require('../helpers/log'); const config = require('./configReader'); const Twitter = require('twitter')({ diff --git a/modules/unknownTxs.js b/modules/unknownTxs.js index 8685142..7e1ab65 100644 --- a/modules/unknownTxs.js +++ b/modules/unknownTxs.js @@ -1,5 +1,5 @@ const api = require('./api'); -const helpers = require('./../helpers'); +const helpers = require('../helpers/utils'); const log = require('../helpers/log'); const db = require('./DB'); const config = require('./configReader'); @@ -42,7 +42,11 @@ module.exports = async (tx, itx) => { } else { msg = getRnd(5); } - await api.sendMessage(config.passPhrase, tx.senderId, msg); + await api.sendMessage(config.passPhrase, tx.senderId, msg).then((response) => { + if (!response.success) { + log.warn(`Failed to send ADM message '${msg}' to ${tx.senderId}. ${response.errorMessage}.`); + } + }); itx.update({isProcessed: true}, true); }); }; diff --git a/tests/lisk.test.js b/tests/lisk.test.js index ae83249..863e04a 100644 --- a/tests/lisk.test.js +++ b/tests/lisk.test.js @@ -1,6 +1,6 @@ const axios = require('axios'); const MockAdapter = require('axios-mock-adapter'); -const LskCoin = require('./../helpers/utils/lsk_utils'); +const LskCoin = require('../helpers/cryptos/lsk_utils'); const config = require('../modules/configReader'); const { From dda45e0a4fc9a87e03c35f953eb0af6a1cd2f5e9 Mon Sep 17 00:00:00 2001 From: gost1k Date: Wed, 10 Aug 2022 17:02:47 +0300 Subject: [PATCH 40/55] chore: update dependencies --- package.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 2c336d4..db02033 100644 --- a/package.json +++ b/package.json @@ -32,25 +32,25 @@ "adamant-api": "^1.5.0", "axios": "^0.27.2", "ethereumjs-tx": "^2.1.2", - "ethereumjs-util": "^7.0.0", - "express": "^4.17.1", - "jsonminify": "^0.4.1", + "ethereumjs-util": "^7.1.5", + "express": "^4.18.1", + "jsonminify": "^0.4.2", "jsonrpc-client": "^0.1.1", "mathjs": "^11.0.1", - "mongodb": "^4.8.0", - "node-ethereum-wallet": "^1.3.2", + "mongodb": "^4.8.1", + "node-ethereum-wallet": "^1.4.3", "node-json-rpc": "0.0.1", "twitter": "^1.7.1", - "web3": "^1.7.4" + "web3": "^1.7.5" }, "devDependencies": { - "@commitlint/cli": "^16.2.1", - "@commitlint/config-conventional": "^16.2.1", + "@commitlint/cli": "^17.0.3", + "@commitlint/config-conventional": "^17.0.3", "axios-mock-adapter": "^1.21.1", - "eslint": "^8.9.0", + "eslint": "^8.21.0", "eslint-config-google": "^0.14.0", - "eslint-plugin-jest": "^26.1.0", - "husky": "^7.0.4", + "eslint-plugin-jest": "^26.8.2", + "husky": "^8.0.1", "jest": "^28.1.3" }, "repository": { From 9056b5b275650fae83fb67db197c5dedf568e8b7 Mon Sep 17 00:00:00 2001 From: gost1k Date: Sat, 13 Aug 2022 17:18:02 +0300 Subject: [PATCH 41/55] add sendMessageWithLog function --- helpers/cryptos/adm_utils.js | 7 +------ modules/api.js | 13 ++++++++++++- modules/checkAdamantContacts.js | 6 +----- modules/checkAll.js | 6 +----- modules/checkTwitterFollow.js | 6 +----- modules/checkTwitterReqs.js | 6 +----- modules/checkTwitterRetweet.js | 6 +----- modules/checkTxs.js | 18 +++--------------- modules/commandTxs.js | 12 ++---------- modules/incomingTxsParser.js | 6 +----- modules/outAddressFetcher.js | 6 +----- modules/rewardsPayer.js | 12 ++---------- modules/sentTxValidator.js | 19 +++---------------- modules/transferTxs.js | 7 +------ modules/unknownTxs.js | 6 +----- 15 files changed, 32 insertions(+), 104 deletions(-) diff --git a/helpers/cryptos/adm_utils.js b/helpers/cryptos/adm_utils.js index dfa3158..97d3444 100644 --- a/helpers/cryptos/adm_utils.js +++ b/helpers/cryptos/adm_utils.js @@ -40,12 +40,7 @@ module.exports = { }, async send(params) { const {address, value, comment} = params; - const payment = await api.sendMessage(config.passPhrase, address, comment, 'basic', value).then((response) => { - if (!response.success) { - log.warn(`Failed to send ADM message '${comment}' to ${address}. ${response.errorMessage}.`); - } - return response; - }); + const payment = await api.sendMessageWithLog(config.passPhrase, address, comment, 'basic', value); if (payment.success) { log.log(`Successfully sent ${value} ADM to ${address} with comment '${comment}', Tx hash: ${payment.data.transactionId}.`); return { diff --git a/modules/api.js b/modules/api.js index a19185b..4bcaa32 100644 --- a/modules/api.js +++ b/modules/api.js @@ -1,3 +1,14 @@ const log = require('../helpers/log'); const config = require('./configReader'); -module.exports = require('adamant-api')({node: config.node_ADM, logLevel: config.log_level}, log); +const api = require('adamant-api')({node: config.node_ADM, logLevel: config.log_level}, log); + +api.sendMessageWithLog = async (passPhrase, addressOrPublicKey, message, messageType = 'basic', amount, isAmountInADM = true, maxRetries = 4, retryNo = 0) => { + return api.sendMessage(passPhrase, addressOrPublicKey, message, messageType, amount, isAmountInADM, maxRetries, retryNo).then((response) => { + if (!response.success) { + log.warn(`Failed to send ADM message '${message}' to ${addressOrPublicKey}. ${response.errorMessage}.`); + } + return response; + }); +}; + +module.exports = api; diff --git a/modules/checkAdamantContacts.js b/modules/checkAdamantContacts.js index d17913b..2b7d3ae 100644 --- a/modules/checkAdamantContacts.js +++ b/modules/checkAdamantContacts.js @@ -93,11 +93,7 @@ module.exports = async () => { msgSendBack = `To meet the Bounty campaign rules, you should invite ${config.adamant_campaign.min_contacts} friends in ADAMANT Messenger. They must message you. They can join this bounty campaign as well! Invite friends and apply again.`; } - await api.sendMessage(config.passPhrase, userId, msgSendBack).then((response) => { - if (!response.success) { - log.warn(`Failed to send ADM message '${msgSendBack}' to ${userId}. ${response.errorMessage}.`); - } - }); + await api.sendMessageWithLog(config.passPhrase, userId, msgSendBack); log.log(`User ${userId}… did NOT make ${config.adamant_campaign.min_contacts} contacts. Message to user: ${msgSendBack}`); } } catch (e) { diff --git a/modules/checkAll.js b/modules/checkAll.js index a92f97a..c140712 100644 --- a/modules/checkAll.js +++ b/modules/checkAll.js @@ -83,11 +83,7 @@ module.exports = async () => { notify(`${config.notifyName}: User ${userId}${twitterString} completed the Bounty tasks. Payouts are pending.`, 'log'); } msgSendBack = `Great, you've completed all the tasks! Reward is coming right now!`; - await api.sendMessage(config.passPhrase, userId, msgSendBack).then((response) => { - if (!response.success) { - log.warn(`Failed to send ADM message '${msgSendBack}' to ${userId}. ${response.errorMessage}.`); - } - }); + await api.sendMessageWithLog(config.passPhrase, userId, msgSendBack); } } catch (e) { log.error(`Error in ${helpers.getModuleName(module.id)} module: ${e}`); diff --git a/modules/checkTwitterFollow.js b/modules/checkTwitterFollow.js index baccd32..7190426 100644 --- a/modules/checkTwitterFollow.js +++ b/modules/checkTwitterFollow.js @@ -52,11 +52,7 @@ module.exports = async () => { isTasksCompleted: false, }, true); msgSendBack = `To meet the Bounty campaign rules, you should follow Twitter account ${followAccount}. Then you apply again.`; - await api.sendMessage(config.passPhrase, userId, msgSendBack).then((response) => { - if (!response.success) { - log.warn(`Failed to send ADM message '${msgSendBack}' to ${userId}. ${response.errorMessage}.`); - } - }); + await api.sendMessageWithLog(config.passPhrase, userId, msgSendBack); log.log(`User ${userId}… ${twitterAccount} do NOT follows ${followAccount}. Message to user: ${msgSendBack}`); break; } diff --git a/modules/checkTwitterReqs.js b/modules/checkTwitterReqs.js index 93b42f7..6c18ccc 100644 --- a/modules/checkTwitterReqs.js +++ b/modules/checkTwitterReqs.js @@ -77,11 +77,7 @@ module.exports = async () => { msgSendBack = `To meet the Bounty campaign rules, your Twitter account ${config.twitterEligibleString}.`; } - await api.sendMessage(config.passPhrase, userId, msgSendBack).then((response) => { - if (!response.success) { - log.warn(`Failed to send ADM message '${msgSendBack}' to ${userId}. ${response.errorMessage}.`); - } - }); + await api.sendMessageWithLog(config.passPhrase, userId, msgSendBack); log.log(`User ${userId}… ${twitterAccount} is NOT eligible. Message to user: ${msgSendBack}`); } diff --git a/modules/checkTwitterRetweet.js b/modules/checkTwitterRetweet.js index c4d564c..3bd8aa9 100644 --- a/modules/checkTwitterRetweet.js +++ b/modules/checkTwitterRetweet.js @@ -106,11 +106,7 @@ module.exports = async () => { break; } - await api.sendMessage(config.passPhrase, userId, msgSendBack).then((response) => { - if (!response.success) { - log.warn(`Failed to send ADM message '${msgSendBack}' to ${userId}. ${response.errorMessage}.`); - } - }); + await api.sendMessageWithLog(config.passPhrase, userId, msgSendBack); log.log(`User ${userId}… ${twitterAccount} did NOT retweet ${toRetweet}: ${retweetResult.error}. Message to user: ${msgSendBack}`); break; diff --git a/modules/checkTxs.js b/modules/checkTxs.js index c13e8ea..6832aca 100644 --- a/modules/checkTxs.js +++ b/modules/checkTxs.js @@ -23,11 +23,7 @@ module.exports = async (itx, tx) => { } } if (msgSendBack) { // Do not send anything, if isInCheck - await api.sendMessage(config.passPhrase, tx.senderId, msgSendBack).then((response) => { - if (!response.success) { - log.warn(`Failed to send ADM message '${msgSendBack}' to ${tx.senderId}. ${response.errorMessage}.`); - } - }); + await api.sendMessageWithLog(config.passPhrase, tx.senderId, msgSendBack); } return; } @@ -46,11 +42,7 @@ module.exports = async (itx, tx) => { if (user.isTasksCompleted) { log.log(`User ${user.userId} already completed the Bounty tasks. Notify user and ignore.`); msgSendBack = `You've already completed the Bounty tasks.`; - await api.sendMessage(config.passPhrase, tx.senderId, msgSendBack).then((response) => { - if (!response.success) { - log.warn(`Failed to send ADM message '${msgSendBack}' to ${tx.senderId}. ${response.errorMessage}.`); - } - }); + await api.sendMessageWithLog(config.passPhrase, tx.senderId, msgSendBack); return; } @@ -93,9 +85,5 @@ module.exports = async (itx, tx) => { await itx.update({isProcessed: true}, true); msgSendBack = `I've got your account details. Twitter: ${user.twitterAccount}. I'll check if you've finished the Bounty tasks now…`; - await api.sendMessage(config.passPhrase, tx.senderId, msgSendBack).then((response) => { - if (!response.success) { - log.warn(`Failed to send ADM message '${msgSendBack}' to ${tx.senderId}. ${response.errorMessage}.`); - } - }); + await api.sendMessageWithLog(config.passPhrase, tx.senderId, msgSendBack); }; diff --git a/modules/commandTxs.js b/modules/commandTxs.js index 81d8718..f2647ab 100644 --- a/modules/commandTxs.js +++ b/modules/commandTxs.js @@ -29,11 +29,7 @@ module.exports = async (cmd, tx, itx) => { }, true); if (config.notify_non_admins) { const msgSendBack = `I won't execute admin commands as you are not an admin. Contact my master.`; - await api.sendMessage(config.passPhrase, tx.senderId, msgSendBack).then((response) => { - if (!response.success) { - log.warn(`Failed to send ADM message '${msgSendBack}' to ${tx.senderId}. ${response.errorMessage}.`); - } - }); + await api.sendMessageWithLog(config.passPhrase, tx.senderId, msgSendBack); } return; } @@ -53,11 +49,7 @@ module.exports = async (cmd, tx, itx) => { notify(res.msgNotify, res.notifyType); } if (res.msgSendBack) { - await api.sendMessage(config.passPhrase, tx.senderId, res.msgSendBack).then((response) => { - if (!response.success) { - log.warn(`Failed to send ADM message '${res.msgSendBack}' to ${tx.senderId}. ${response.errorMessage}.`); - } - }); + await api.sendMessageWithLog(config.passPhrase, tx.senderId, res.msgSendBack); } } } catch (e) { diff --git a/modules/incomingTxsParser.js b/modules/incomingTxsParser.js index 584eded..86d2e57 100644 --- a/modules/incomingTxsParser.js +++ b/modules/incomingTxsParser.js @@ -93,11 +93,7 @@ module.exports = async (tx) => { if (itx.isSpam && !spamerIsNotify) { notify(`${config.notifyName} notifies _${tx.senderId}_ is a spammer or talks too much. Income ADAMANT Tx: https://explorer.adamant.im/tx/${tx.id}.`, 'warn'); const msgSendBack = `I’ve _banned_ you. You’ve sent too much transactions to me.`; - await api.sendMessage(config.passPhrase, tx.senderId, msgSendBack).then((response) => { - if (!response.success) { - log.warn(`Failed to send ADM message '${msgSendBack}' to ${tx.senderId}. ${response.errorMessage}.`); - } - }); + await api.sendMessageWithLog(config.passPhrase, tx.senderId, msgSendBack); return; } diff --git a/modules/outAddressFetcher.js b/modules/outAddressFetcher.js index 55cf511..266e4c1 100644 --- a/modules/outAddressFetcher.js +++ b/modules/outAddressFetcher.js @@ -36,11 +36,7 @@ module.exports = async () => { msgSendBack = `I can’t get your _${pay.outCurrency}_ address from ADAMANT KVS to pay a reward. Make sure you use ADAMANT wallet with _${pay.outCurrency}_ enabled. I have already notified my master.`; notify(msgNotify, 'error'); let tx; - await api.sendMessage(config.passPhrase, tx.userId, msgSendBack).then((response) => { - if (!response.success) { - log.warn(`Failed to send ADM message '${msgSendBack}' to ${tx.userId}. ${response.errorMessage}.`); - } - }); + await api.sendMessageWithLog(config.passPhrase, tx.userId, msgSendBack); } } else { pay.update({ diff --git a/modules/rewardsPayer.js b/modules/rewardsPayer.js index 9eb3061..bd0a063 100644 --- a/modules/rewardsPayer.js +++ b/modules/rewardsPayer.js @@ -45,11 +45,7 @@ module.exports = async () => { }, true); notify(`${config.notifyName} notifies about insufficient balance to send a reward of _${outAmount}_ _${outCurrency}_. Balance of _${outCurrency}_ is _${Store.user[outCurrency].balance}_. ${etherString}User ADAMANT id: ${userId}.`, 'error'); const msgSendBack = `I can’t transfer a reward of _${outAmount}_ _${outCurrency}_ to you because of insufficient funds (I count blockchain fees also). I have already notified my master.`; - await api.sendMessage(config.passPhrase, userId, msgSendBack).then((response) => { - if (!response.success) { - log.warn(`Failed to send ADM message '${msgSendBack}' to ${userId}. ${response.errorMessage}.`); - } - }); + await api.sendMessageWithLog(config.passPhrase, userId, msgSendBack); return; } @@ -88,11 +84,7 @@ module.exports = async () => { }, true); notify(`${config.notifyName} cannot make transaction to payout a reward of _${outAmount}_ _${outCurrency}_. Balance of _${outCurrency}_ is _${Store.user[outCurrency].balance}_. ${etherString}User ADAMANT id: ${userId}.`, 'error'); const msgSendBack = `I’ve tried to make a reward payout of _${outAmount}_ _${outCurrency}_ to you, but something went wrong. I have already notified my master.`; - await api.sendMessage(config.passPhrase, userId, msgSendBack).then((response) => { - if (!response.success) { - log.warn(`Failed to send ADM message '${msgSendBack}' to ${userId}. ${response.errorMessage}.`); - } - }); + await api.sendMessageWithLog(config.passPhrase, userId, msgSendBack); } } catch (e) { log.error(`Error in ${helpers.getModuleName(module.id)} module: ${e.toString()}`); diff --git a/modules/sentTxValidator.js b/modules/sentTxValidator.js index c59004d..3202ec7 100644 --- a/modules/sentTxValidator.js +++ b/modules/sentTxValidator.js @@ -53,11 +53,7 @@ module.exports = async () => { msgSendBack = `I’ve tried to make the reward payout of _${outAmount}_ _${outCurrency}_ to you, but unable to validate transaction. Tx hash: _${outTxid}_. I’ve already notified my master. If you wouldn’t receive transfer in two days, contact my master also.`; notify(msgNotify, notifyType); - await api.sendMessage(config.passPhrase, userId, msgSendBack).then((response) => { - if (!response.success) { - log.warn(`Failed to send ADM message '${msgSendBack}' to ${userId}. ${response.errorMessage}.`); - } - }); + await api.sendMessageWithLog(config.passPhrase, userId, msgSendBack); } await pay.save(); return; @@ -82,11 +78,7 @@ module.exports = async () => { msgNotify = `${config.notifyName} notifies that the reward payout of _${outAmount}_ _${outCurrency}_ failed. Tx hash: _${outTxid}_. Will try again. Balance of _${outCurrency}_ is _${Store.user[outCurrency].balance}_. ${etherString}User ADAMANT id: ${userId}.`; msgSendBack = `I’ve tried to make the payout transfer of _${outAmount}_ _${outCurrency}_ to you, but it seems transaction failed. Tx hash: _${outTxid}_. I will try again. If I’ve said the same several times already, please contact my master.`; - await api.sendMessage(config.passPhrase, userId, msgSendBack).then((response) => { - if (!response.success) { - log.warn(`Failed to send ADM message '${msgSendBack}' to ${userId}. ${response.errorMessage}.`); - } - }); + await api.sendMessageWithLog(config.passPhrase, userId, msgSendBack); } else if (status && pay.outConfirmations >= config.min_confirmations) { notifyType = 'info'; if (config.notifyRewardReceived) { @@ -96,12 +88,7 @@ module.exports = async () => { if (outCurrency !== 'ADM') { msgSendBack = `{"type":"${outCurrency.toLowerCase()}_transaction","amount":"${outAmount}","hash":"${outTxid}","comments":"${msgSendBack}"}`; - const message = await api.sendMessage(config.passPhrase, userId, msgSendBack, 'rich').then((response) => { - if (!response.success) { - log.warn(`Failed to send ADM message '${msgSendBack}' to ${userId}. ${response.errorMessage}.`); - } - return response; - }); + const message = await api.sendMessageWithLog(config.passPhrase, userId, msgSendBack, 'rich'); if (message.success) { pay.isFinished = true; } else { diff --git a/modules/transferTxs.js b/modules/transferTxs.js index 539b6cd..265bc63 100644 --- a/modules/transferTxs.js +++ b/modules/transferTxs.js @@ -2,7 +2,6 @@ const {SAT} = require('../helpers/const'); const api = require('./api'); const notify = require('../helpers/notify'); const config = require('./configReader'); -const log = require('./../helpers/log'); module.exports = async (itx, tx) => { const msg = itx.encrypted_content; @@ -39,9 +38,5 @@ module.exports = async (itx, tx) => { await itx.update({isProcessed: true}, true); notify(msgNotify, notifyType); - await api.sendMessage(config.passPhrase, tx.senderId, msgSendBack).then((response) => { - if (!response.success) { - log.warn(`Failed to send ADM message '${msgSendBack}' to ${tx.senderId}. ${response.errorMessage}.`); - } - }); + await api.sendMessageWithLog(config.passPhrase, tx.senderId, msgSendBack); }; diff --git a/modules/unknownTxs.js b/modules/unknownTxs.js index 7e1ab65..ff010c9 100644 --- a/modules/unknownTxs.js +++ b/modules/unknownTxs.js @@ -42,11 +42,7 @@ module.exports = async (tx, itx) => { } else { msg = getRnd(5); } - await api.sendMessage(config.passPhrase, tx.senderId, msg).then((response) => { - if (!response.success) { - log.warn(`Failed to send ADM message '${msg}' to ${tx.senderId}. ${response.errorMessage}.`); - } - }); + await api.sendMessageWithLog(config.passPhrase, tx.senderId, msg); itx.update({isProcessed: true}, true); }); }; From 708da0cca0de253cc9dee76d8ed54f4c361ce5f9 Mon Sep 17 00:00:00 2001 From: gost1k Date: Sat, 13 Aug 2022 17:18:11 +0300 Subject: [PATCH 42/55] fix error handling --- helpers/cryptos/lsk_utils.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/helpers/cryptos/lsk_utils.js b/helpers/cryptos/lsk_utils.js index b20c385..259f6d2 100644 --- a/helpers/cryptos/lsk_utils.js +++ b/helpers/cryptos/lsk_utils.js @@ -93,6 +93,8 @@ module.exports = class LskCoin extends LskBaseCoin { sendTransaction(signedTx) { return this._getClient().post('/api/transactions', signedTx).then((response) => { return response.data.data.transactionId; + }).catch((e) => { + log.warn(`Error while sending transaction in sendTransaction() of ${helpers.getModuleName(module.id)} module: ` + e); }); } @@ -136,8 +138,6 @@ module.exports = class LskCoin extends LskBaseCoin { error: `Unable to create Tx`, }; } catch (e) { - log.info('ERROR'); - log.info(e.response.data.errors); return { success: false, error: e.toString(), @@ -201,6 +201,8 @@ module.exports = class LskCoin extends LskBaseCoin { status: true, }; } + }).catch((e) => { + log.warn(`Error while getting transaction status in getTransactionStatus() for ${txId} of ${helpers.getModuleName(module.id)} module: ` + e); }); } }; From 23bef1ca42715b782987761cc2ab93c8373a267e Mon Sep 17 00:00:00 2001 From: adamant-al Date: Mon, 15 Aug 2022 19:10:11 +0300 Subject: [PATCH 43/55] chore: general --- .editorconfig | 4 +-- README.md | 50 +++++++------------------------ config.default.json | 73 ++++++++++++++++++++++++++------------------- package.json | 16 +++++----- server.js | 2 +- 5 files changed, 66 insertions(+), 79 deletions(-) diff --git a/.editorconfig b/.editorconfig index 97f3ae5..3b482d3 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,8 +8,8 @@ charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true -indent_style = tab -tab_width = 4 +indent_style = space +indent_size = 2 [*.md] max_line_length = off trim_trailing_whitespace = false diff --git a/README.md b/README.md index e956e0c..66c6191 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ -ADAMANT Bounty Bot is a software that allows you to carry out bounty campaigns & crypto airdrops, with automatic task verifications and payouts. +ADAMANT Bounty Bot is a software that allows you to carry out bounty campaigns & crypto airdrops, with automatic task verification and payouts. -It is made for crypto projects and communities. +It's made for crypto projects and communities. The bounty bot: -* Interactive and interesting for users. The bot talks to users in ADAMANT Messenger chat directly -* Works with Twitter campaigns: follow & retweet with comment (quote). You can set up mentions and hashtags. +* Interactive and interesting for users. The bot talks to users in ADAMANT Messenger chat directly. +* Works with Twitter campaigns: follow, retweet & retweet with comment (quote). You can set up mentions and hashtags. * Set which Twitter accounts are eligible to participate: minimum followers, friends, statuses and lifetime * Supports ADAMANT campaigns: users will invite other users -* Automatic task verifications and payouts -* Supports payouts in ADM, ETH and ERC-20 tokens +* Automatic task verification and payouts +* Supports payouts in ADM, LSK, ETH and ERC-20 tokens * Easy to install and configure * Free and open source * Stores statistics @@ -20,8 +20,8 @@ User-friendly instructions: [Carry out a crypto Bounty campaign on ADAMANT platf ## Requirements -* Ubuntu 16 / Ubuntu 18 (other OS had not been tested) -* NodeJS v 8+ (already installed if you have a node on your machine) +* Ubuntu 18, 20, 22 (we didn't test others) +* NodeJS v 14+ * MongoDB ([installation instructions](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/)) ## Setup @@ -36,43 +36,15 @@ npm i ## Pre-launch tuning ``` +cp config.default.json config.json nano config.json ``` -Parameters: - -* `passPhrase` The exchange bot's secret phrase for concluding transactions. Obligatory. Bot's ADAMANT address will correspond this passPhrase. -* `node_ADM` List of nodes for API work, obligatorily -* `node_ETH` List of nodes for Ethereum API work, obligatorily -* `infoservice` List of [ADAMANT InfoServices](https://github.com/Adamant-im/adamant-currencyinfo-services) for catching exchange rates, obligatorily -* `socket` If to use WebSocket connection. Recommended for better user experience -* `ws_type` Choose socket connection, "ws" or "wss" depending on your server -* `bot_name` Bot's name for notifications -* `admin_accounts` ADAMANT accounts to accept control commands from -* `notify_non_admins` Notify non-admins that they are not admins -* `slack` Token for Slack alerts for the bot’s administrator. No alerts if not set -* `adamant_notify` ADM address for the bot’s administrator. Recommended -* `known_crypto` List of cryptocurrencies bot can work with. Obligatorily -* `erc20` List of cryptocurrencies of ERC-20 type. It is necessary to put all known ERC-20 tokens here. - -* `twitter_follow` List of Twitter account user should follow -* `twitter_retweet` List of Twitter posts user should retweet -* `twitter_api` Your Twitter API credentials. Get on https://apps.twitter.com/app/new -* `twitter_reqs` Requirements for user's Twitter account -* `twitter_api_test_interval` Interval in minutes to test Twitter API - -* `adamant_campaign` Settings for ADAMANT bounty campaign - -* `notifyTasksCompleted` If you want to receive notifications when user completes Bounty tasks -* `notifyRewardReceived` If you want to receive notifications when user receives a Bounty reward -* `rewards` List rewards for a Bounty campaign: cryptos and amounts - -* `welcome_string` How to reply user in-chat, if first unknown command received -* `help_message` How to reply to */help* command. Recommended to put Bounty rules here +Parameters: See descriptions in config file. ## Launching -You can start the Bot with the `node app` command, but it is recommended to use the process manager for this purpose. +You can start the Bot with the `node app` command, but it's recommended to use the process manager for this purpose. ``` pm2 start --name bountybot app.js diff --git a/config.default.json b/config.default.json index dad9ff2..041b8ea 100644 --- a/config.default.json +++ b/config.default.json @@ -1,11 +1,13 @@ { - /** The bot's secret phrase for concluding transactions. - Bot's ADAMANT address will correspond this passPhrase. + /** + The bot's secret phrase for concluding transactions. + Bot's ADAMANT address will correspond this passPhrase. **/ "passPhrase": "qwert yuiop asdfg hjkl zxcvb nmqwe", - /** List of nodes to fetch transactions. - If one become unavailable, pool will choose live one. + /** + List of nodes to fetch transactions. + If one become unavailable, pool will choose live one. **/ "node_ADM": [ "http://localhost:36666", @@ -28,20 +30,25 @@ "node_ETH": [ "https://ethnode1.adamant.im" ], + + /** List of Lisk nodes **/ "node_LSK": [ "https://lisknode3.adamant.im", "https://lisknode4.adamant.im" ], + + /** List of Lisk Service nodes **/ "service_LSK": [ "https://liskservice3.adamant.im", "https://liskservice4.adamant.im" ], + /** List of ADAMANT InfoServices for catching exchange rates **/ "infoservice": [ "https://info.adamant.im" ], - /** List of cryptocurrencies bot can work with. **/ + /** List of cryptocurrencies bot can work with **/ "known_crypto": [ "ADM", "ETH", @@ -57,11 +64,12 @@ /** How to reply user in-chat, if first unknown command received. **/ "welcome_string": "Hi! 😊 I'm a bounty bot. And this is a stub. Are you ready for the awesome bounty campaign with automatic payout? Type **/help** to see bounty rules.", - /** Bounty rules. Shown by /help command. You can use template literals - like ${config.rewards_list}, ${config.rewards_tickers}, ${config.twitter_follow[0]}, - ${config.twitter_retweet_w_comment[0].tweet}, ${config.twitter_follow_list}, - ${config.twitter_retweet_w_comment[0].min_mentions}, ${config.twitter_retweet_w_comment[0].tag_list}, - ${config.twitterEligibleString} + /** + Bounty rules. Shown by /help command. You can use template literals + like ${config.rewards_list}, ${config.rewards_tickers}, ${config.twitter_follow[0]}, + ${config.twitter_retweet_w_comment[0].tweet}, ${config.twitter_follow_list}, + ${config.twitter_retweet_w_comment[0].min_mentions}, ${config.twitter_retweet_w_comment[0].tag_list}, + ${config.twitterEligibleString} **/ "help_message": "Earn **${config.rewards_tickers}** with your social activity! A reward depends on how much Twitter followers you have. Your account ${config.twitterEligibleString}.\n${config.rewards_range}\n\n \nThe campaign rules:\n- Follow account ${config.twitter_follow_list} on Twitter\n- Like & quote ${config.twitter_retweet_w_comment[0].tweet}, mentioning ${config.twitter_retweet_w_comment[0].min_mentions} friends and ${config.twitter_retweet_w_comment[0].tag_list} tags.\n- Invite ${config.adamant_campaign.min_contacts} friend in ADAMANT Messenger. They must message you.\n- Send me the name (like @adamant_im) or the link to your Twitter profile to verify.\n\n \nGo!", @@ -86,8 +94,9 @@ "min_days": 20 }, - /** Tweets user should quote (retweet with comment). - Min_mentions is how much people he should mention. Hashtags is a list of tags he must use. + /** + Tweets user should quote (retweet with comment). + Min_mentions is how much people he should mention. Hashtags is a list of tags he must use. **/ "twitter_retweet_w_comment": [ { @@ -102,9 +111,10 @@ } ], - /** Minimum contacts user must invite to ADAMANT Messenger. - Contacts must be new users. - 0 is disabled. + /** + Minimum contacts user must invite to ADAMANT Messenger. + Contacts must be new users. + 0 is disabled. **/ "adamant_campaign": { "min_contacts": 1 @@ -122,9 +132,10 @@ } ], - /** Set progressive scale of reward amounts for each cryptocurrency. - `func` is a mathjs.org function. Limit followers with `limit_followers` parameter. - If not set for a currency, plain amount is used, which is set in `rewards.amount`. + /** + Set progressive scale of reward amounts for each cryptocurrency, + Where `func` is a mathjs.org function. Limit followers with `limit_followers` parameter. + If not set for a currency, plain amount is used, which is set in `rewards.amount`. **/ "rewards_progression_from_twitter_followers": { "ADM": { @@ -143,15 +154,15 @@ "access_token_secret": "" }, - /** Interval in minutes to test Twitter API. Because of different reasons Twitter may temporary block API requests. - To continue, you need manually login into your Twitter account and solve captcha. - This parameter allows to automatically check if Twitter API works well every twitter_api_test_interval minutes. - In case of error the bot will notify you. Also you can run "/test twitterapi" command manually. - 0 means disabled. + /** + Interval in minutes to test Twitter API. Because of different reasons Twitter may temporary block API requests. + To continue, you need manually login into your Twitter account and solve captcha. + This parameter allows to automatically check if Twitter API works well every twitter_api_test_interval minutes. + In case of error the bot will notify you. Also you can run "/test twitterapi" command manually. + 0 means disabled. **/ "twitter_api_test_interval": 600, - /** ADAMANT accounts to accept control commands from. Control commands from other accounts will not be executed. **/ /** Notify non-admins that they are not admins. If false, bot will be silent. **/ "notify_non_admins": false, @@ -167,14 +178,16 @@ /** If you want to receive notifications when user receives a Bounty reward **/ "notifyRewardReceived": true, - /** Port for getting debug info. - Do not set for live exchange bots, use only for debugging. - Allows to get DBs records like http://ip:port/db?tb=IncomingTxs + /** + Port for getting debug info. + Do not set for live exchange bots, use only for debugging. + Allows to get DBs records like http://ip:port/db?tb=IncomingTxs **/ "api": false, - /** The software will use verbosity according to log_level. - It can be none < error < warn < info < log. - **/ + /** + The software will use verbosity according to log_level. + It can be none < error < warn < info < log. + **/ "log_level": "log" } diff --git a/package.json b/package.json index db02033..0796d71 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "adamant-bountybot", - "version": "1.5.0", - "description": "ADAMANT Bounty bot — a software that allows you to carry out bounty campaigns & crypto airdrops, with automatic task verifications and payouts.", + "version": "1.6.0", + "description": "ADAMANT Bounty bot — a software that allows you to carry out bounty campaigns & crypto airdrops, with automatic task verification and payouts.", "main": "index.js", "scripts": { "test": "jest --rootDir tests --forceExit --testTimeout 30000 'dev'", @@ -17,14 +17,16 @@ "bot", "bitcoin", "ethereum", + "lisk", "bounty", "bounty bot", "crypto", "cryptocurrency", "twitter", - "airdrop" + "airdrop", + "payout" ], - "author": "Aleksei Lebedev (https://adamant.im)", + "author": "Aleksei Lebedev, ADAMANT Devs (https://adamant.im)", "license": "GPL-3.0", "dependencies": { "@liskhq/lisk-cryptography": "^3.2.1", @@ -46,10 +48,10 @@ "devDependencies": { "@commitlint/cli": "^17.0.3", "@commitlint/config-conventional": "^17.0.3", - "axios-mock-adapter": "^1.21.1", - "eslint": "^8.21.0", + "axios-mock-adapter": "^1.21.2", + "eslint": "^8.22.0", "eslint-config-google": "^0.14.0", - "eslint-plugin-jest": "^26.8.2", + "eslint-plugin-jest": "^26.8.3", "husky": "^8.0.1", "jest": "^28.1.3" }, diff --git a/server.js b/server.js index 086df2e..8cf5fe6 100644 --- a/server.js +++ b/server.js @@ -40,5 +40,5 @@ if (port) { }); }); - app.listen(port, () => log.info('Server listening on port ' + port + ' http://localhost:' + port + '/db?tb=systemDb')); + app.listen(port, () => log.info(`${config.notifyName} debug server is listening on http://localhost:${port}. F. e., http://localhost:${port}/db?tb=systemDb.`)); } From cdf57c70341743eca6a643d7ae15e60da10647a1 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Mon, 15 Aug 2022 22:41:45 +0300 Subject: [PATCH 44/55] chore: add try-catches --- modules/apiTester.js | 25 +++--- modules/checkAll.js | 8 +- modules/checkTxs.js | 146 +++++++++++++++++---------------- modules/commandTxs.js | 187 +++++++++++++++++++++++------------------- 4 files changed, 197 insertions(+), 169 deletions(-) diff --git a/modules/apiTester.js b/modules/apiTester.js index ebbeee3..8b65345 100644 --- a/modules/apiTester.js +++ b/modules/apiTester.js @@ -2,18 +2,23 @@ const config = require('./configReader'); const log = require('../helpers/log'); const notify = require('../helpers/notify'); const twitterapi = require('./twitterapi'); +const helpers = require('../helpers/utils'); async function testTwitterAPI() { - const testResult = await twitterapi.testApi(); - let output; - if (testResult.success) { - output = 'Twitter API functions well.'; - log.info(output); - } else { - output = `Error while making Twitter API request: ${testResult.message}`; - output += '\n'; - output += `Make sure Twitter didn't block your API credentials. If so, you need to manually login into your Twitter account and solve captcha.`; - notify(output, 'error'); + try { + const testResult = await twitterapi.testApi(); + let output; + if (testResult.success) { + output = 'Twitter API functions well.'; + log.info(output); + } else { + output = `Error while making Twitter API request: ${testResult.message}`; + output += '\n'; + output += `Make sure Twitter didn't block your API credentials. If so, you need to manually login into your Twitter account and solve captcha.`; + notify(output, 'error'); + } + } catch (e) { + log.error(`Error in testTwitterAPI() of ${helpers.getModuleName(module.id)} module: ${e}`); } } diff --git a/modules/checkAll.js b/modules/checkAll.js index c140712..5ee8f7b 100644 --- a/modules/checkAll.js +++ b/modules/checkAll.js @@ -36,9 +36,11 @@ module.exports = async () => { let msgSendBack = ''; - if (((config.twitter_follow.length === 0) || isTwitterFollowCheckPassed) && - ((config.twitter_retweet_w_comment.length === 0) || isTwitterRetweetCommentCheckPassed) && - ((config.adamant_campaign.min_contacts === 0) || isAdamantCheckPassed)) { + if ( + ((config.twitter_follow.length === 0) || isTwitterFollowCheckPassed) && + ((config.twitter_retweet_w_comment.length === 0) || isTwitterRetweetCommentCheckPassed) && + ((config.adamant_campaign.min_contacts === 0) || isAdamantCheckPassed) + ) { await user.update({ isInCheck: false, isTasksCompleted: true, diff --git a/modules/checkTxs.js b/modules/checkTxs.js index 6832aca..e00828d 100644 --- a/modules/checkTxs.js +++ b/modules/checkTxs.js @@ -7,83 +7,87 @@ const config = require('./configReader'); module.exports = async (itx, tx) => { log.log(`Running module ${helpers.getModuleName(module.id)}…`); - const {UsersDb} = db; - let msgSendBack = ''; + try { + const {UsersDb} = db; + let msgSendBack = ''; - // Exclude duplicate Twitter accounts - let user = await UsersDb.findOne({twitterAccount: itx.accounts.twitterAccount}); - if (user && (user.isInCheck || user.isTasksCompleted)) { - // This Twitter account is already in use by other user, unable to switch - log.warn(`User ${user.userId} applied with already used Twitter account ${itx.accounts.twitterAccount}. Notify user and ignore.`); - if (user.userId !== tx.senderId) { - msgSendBack = `This Twitter account is already in use by other participant. If it's a mistake, try again in a few minutes.`; - } else { + // Exclude duplicate Twitter accounts + let user = await UsersDb.findOne({twitterAccount: itx.accounts.twitterAccount}); + if (user && (user.isInCheck || user.isTasksCompleted)) { + // This Twitter account is already in use by other user, unable to switch + log.warn(`User ${user.userId} applied with already used Twitter account ${itx.accounts.twitterAccount}. Notify user and ignore.`); + if (user.userId !== tx.senderId) { + msgSendBack = `This Twitter account is already in use by other participant. If it's a mistake, try again in a few minutes.`; + } else { + if (user.isTasksCompleted) { + msgSendBack = `You've already completed the Bounty tasks.`; + } + } + if (msgSendBack) { // Do not send anything, if isInCheck + await api.sendMessageWithLog(config.passPhrase, tx.senderId, msgSendBack); + } + return; + } + + // Check if user apply for check once again + user = await UsersDb.findOne({userId: tx.senderId}); + if (user) { + // User is already was in check earlier, update + log.log(`User ${user.userId} applied once again with Twitter account ${itx.accounts.twitterAccount}.`); + // May be later + // if (user.isBountyPayed) { + // msgSendBack = `You've already received the Bounty reward. Thanks for your support!`; + // $u.sendAdmMsg(tx.senderId, msgSendBack); + // return; + // } else if (user.isTasksCompleted) { + log.log(`User ${user.userId} already completed the Bounty tasks. Notify user and ignore.`); msgSendBack = `You've already completed the Bounty tasks.`; + await api.sendMessageWithLog(config.passPhrase, tx.senderId, msgSendBack); + return; } - } - if (msgSendBack) { // Do not send anything, if isInCheck - await api.sendMessageWithLog(config.passPhrase, tx.senderId, msgSendBack); - } - return; - } - // Check if user apply for check once again - user = await UsersDb.findOne({userId: tx.senderId}); - if (user) { - // User is already was in check earlier, update - log.log(`User ${user.userId} applied once again with Twitter account ${itx.accounts.twitterAccount}.`); - // May be later - // if (user.isBountyPayed) { - // msgSendBack = `You've already received the Bounty reward. Thanks for your support!`; - // $u.sendAdmMsg(tx.senderId, msgSendBack); - // return; - // } else - if (user.isTasksCompleted) { - log.log(`User ${user.userId} already completed the Bounty tasks. Notify user and ignore.`); - msgSendBack = `You've already completed the Bounty tasks.`; - await api.sendMessageWithLog(config.passPhrase, tx.senderId, msgSendBack); - return; + user.update({ + dateUpdated: helpers.unix(), + admTxId: tx.id, + msg: itx.encrypted_content, + isInCheck: itx.accounts.notEmpty, + twitterAccountLink: itx.accounts.twitterLink, + twitterAccount: itx.accounts.twitterAccount, + twitterAccountId: null, + isTasksCompleted: false, + isTwitterFollowCheckPassed: false, + isTwitterRetweetCommentCheckPassed: false, + isTwitterAccountEligible: false, + }); + } else { + // First time user, create new + log.info(`User ${tx.senderId} applied for a first time with Twitter account ${itx.accounts.twitterAccount}.`); + user = new UsersDb({ + _id: tx.senderId, + userId: tx.senderId, + dateCreated: helpers.unix(), + dateUpdated: helpers.unix(), + admTxId: tx.id, + msg: itx.encrypted_content, + isInCheck: itx.accounts.notEmpty, + twitterAccountLink: itx.accounts.twitterLink, + twitterAccount: itx.accounts.twitterAccount, + twitterAccountId: null, + isTasksCompleted: false, + isTwitterFollowCheckPassed: false, + isTwitterRetweetCommentCheckPassed: false, + isTwitterAccountEligible: false, + isAdamantCheckPassed: false, + }); } - user.update({ - dateUpdated: helpers.unix(), - admTxId: tx.id, - msg: itx.encrypted_content, - isInCheck: itx.accounts.notEmpty, - twitterAccountLink: itx.accounts.twitterLink, - twitterAccount: itx.accounts.twitterAccount, - twitterAccountId: null, - isTasksCompleted: false, - isTwitterFollowCheckPassed: false, - isTwitterRetweetCommentCheckPassed: false, - isTwitterAccountEligible: false, - }); - } else { - // First time user, create new - log.info(`User ${tx.senderId} applied for a first time with Twitter account ${itx.accounts.twitterAccount}.`); - user = new UsersDb({ - _id: tx.senderId, - userId: tx.senderId, - dateCreated: helpers.unix(), - dateUpdated: helpers.unix(), - admTxId: tx.id, - msg: itx.encrypted_content, - isInCheck: itx.accounts.notEmpty, - twitterAccountLink: itx.accounts.twitterLink, - twitterAccount: itx.accounts.twitterAccount, - twitterAccountId: null, - isTasksCompleted: false, - isTwitterFollowCheckPassed: false, - isTwitterRetweetCommentCheckPassed: false, - isTwitterAccountEligible: false, - isAdamantCheckPassed: false, - }); - } - - await user.save(); - await itx.update({isProcessed: true}, true); + await user.save(); + await itx.update({isProcessed: true}, true); - msgSendBack = `I've got your account details. Twitter: ${user.twitterAccount}. I'll check if you've finished the Bounty tasks now…`; - await api.sendMessageWithLog(config.passPhrase, tx.senderId, msgSendBack); + msgSendBack = `I've got your account details. Twitter: ${user.twitterAccount}. I'll check if you've finished the Bounty tasks now…`; + await api.sendMessageWithLog(config.passPhrase, tx.senderId, msgSendBack); + } catch (e) { + log.error(`Error in ${helpers.getModuleName(module.id)} module: ${e}`); + } }; diff --git a/modules/commandTxs.js b/modules/commandTxs.js index f2647ab..0324c42 100644 --- a/modules/commandTxs.js +++ b/modules/commandTxs.js @@ -10,6 +10,7 @@ const twitterapi = require('./twitterapi'); module.exports = async (cmd, tx, itx) => { if (itx.isProcessed) return; log.log(`Got new command Tx to process: ${cmd} from ${tx.senderId}`); + try { let res = []; const group = cmd @@ -69,113 +70,125 @@ function help() { async function rates(params) { let output = ''; - const coin1 = params[0].toUpperCase().trim(); - - if (!coin1 || !coin1.length) { - output = 'Please specify coin ticker or specific market you are interested in. F. e., */rates ADM*.'; - return { - msgNotify: ``, - msgSendBack: `${output}`, - notifyType: 'log', - }; - } - const currencies = Store.currencies; - const res = Object - .keys(Store.currencies) - .filter((t) => t.startsWith(coin1 + '/')) - .map((t) => { - const p = `${coin1}/**${t.replace(coin1 + '/', '')}**`; - return `${p}: ${currencies[t]}`; - }) - .join(', '); - - if (!res.length) { - output = `I can’t get rates for *${coin1}*. Made a typo? Try */rates ADM*.`; - return { - msgNotify: ``, - msgSendBack: `${output}`, - notifyType: 'log', - }; - } else { - output = `Global market rates for ${coin1}: -${res}.`; + try { + const coin1 = params[0].toUpperCase().trim(); + + if (!coin1 || !coin1.length) { + output = 'Please specify coin ticker or specific market you are interested in. F. e., */rates ADM*.'; + return { + msgNotify: ``, + msgSendBack: `${output}`, + notifyType: 'log', + }; + } + const currencies = Store.currencies; + const res = Object + .keys(Store.currencies) + .filter((t) => t.startsWith(coin1 + '/')) + .map((t) => { + const p = `${coin1}/**${t.replace(coin1 + '/', '')}**`; + return `${p}: ${currencies[t]}`; + }) + .join(', '); + + if (!res.length) { + output = `I can’t get rates for *${coin1}*. Made a typo? Try */rates ADM*.`; + return { + msgNotify: ``, + msgSendBack: `${output}`, + notifyType: 'log', + }; + } else { + output = `Global market rates for ${coin1}:\n${res}.`; + } + } catch (e) { + log.error(`Error in rates() of ${helpers.getModuleName(module.id)} module: ${e}`); } return { msgNotify: ``, - msgSendBack: `${output}`, + msgSendBack: output, notifyType: 'log', }; } async function calc(arr) { - if (arr.length !== 4) { - return { - msgNotify: ``, - msgSendBack: 'Wrong arguments. Command works like this: */calc 2.05 BTC in USDT*.', - notifyType: 'log', - }; - } - let output = ''; - const amount = +arr[0]; - const inCurrency = arr[1].toUpperCase().trim(); - const outCurrency = arr[3].toUpperCase().trim(); - if (!amount || amount === Infinity) { - output = `It seems amount "*${amount}*" for *${inCurrency}* is not a number. Command works like this: */calc 2.05 BTC in USDT*.`; - } - if (!$u.isHasTicker(inCurrency)) { - output = `I don’t have rates of crypto *${inCurrency}* from Infoservice. Made a typo? Try */calc 2.05 BTC in USDT*.`; - } - if (!$u.isHasTicker(outCurrency)) { - output = `I don’t have rates of crypto *${outCurrency}* from Infoservice. Made a typo? Try */calc 2.05 BTC in USDT*.`; - } + try { + if (arr.length !== 4) { + return { + msgNotify: ``, + msgSendBack: 'Wrong arguments. Command works like this: */calc 2.05 BTC in USDT*.', + notifyType: 'log', + }; + } - let result; - if (!output) { - result = Store.mathEqual(inCurrency, outCurrency, amount, true).outAmount; - if (amount <= 0 || result <= 0 || !result) { - output = `I didn’t understand amount for *${inCurrency}*. Command works like this: */calc 2.05 BTC in USDT*.`; - } else { - if ($u.isFiat(outCurrency)) { - result = +result.toFixed(2); + const amount = +arr[0]; + const inCurrency = arr[1].toUpperCase().trim(); + const outCurrency = arr[3].toUpperCase().trim(); + + if (!amount || amount === Infinity) { + output = `It seems amount "*${amount}*" for *${inCurrency}* is not a number. Command works like this: */calc 2.05 BTC in USDT*.`; + } + if (!$u.isHasTicker(inCurrency)) { + output = `I don’t have rates of crypto *${inCurrency}* from Infoservice. Made a typo? Try */calc 2.05 BTC in USDT*.`; + } + if (!$u.isHasTicker(outCurrency)) { + output = `I don’t have rates of crypto *${outCurrency}* from Infoservice. Made a typo? Try */calc 2.05 BTC in USDT*.`; + } + + let result; + if (!output) { + result = Store.mathEqual(inCurrency, outCurrency, amount, true).outAmount; + if (amount <= 0 || result <= 0 || !result) { + output = `I didn’t understand amount for *${inCurrency}*. Command works like this: */calc 2.05 BTC in USDT*.`; + } else { + if ($u.isFiat(outCurrency)) { + result = +result.toFixed(2); + } + output = `Global market value of ${helpers.thousandSeparator(amount)} ${inCurrency} equals **${helpers.thousandSeparator(result)} ${outCurrency}**.`; } - output = `Global market value of ${helpers.thousandSeparator(amount)} ${inCurrency} equals **${helpers.thousandSeparator(result)} ${outCurrency}**.`; } + } catch (e) { + log.error(`Error in calc() of ${helpers.getModuleName(module.id)} module: ${e}`); } return { msgNotify: ``, - msgSendBack: `${output}`, + msgSendBack: output, notifyType: 'log', }; } async function test(param) { - param = param[0].trim(); - if (!param || !['twitterapi'].includes(param)) { - return { - msgNotify: ``, - msgSendBack: 'Wrong arguments. Command works like this: */test twitterapi*.', - notifyType: 'log', - }; - } - let output; - if (param === 'twitterapi') { - const testResult = await twitterapi.testApi(); - if (testResult.success) { - output = 'Twitter API functions well.'; - } else { - output = `Error while making Twitter API request: ${testResult.message}`; + try { + param = param[0].trim(); + if (!param || !['twitterapi'].includes(param)) { + return { + msgNotify: ``, + msgSendBack: 'Wrong arguments. Command works like this: */test twitterapi*.', + notifyType: 'log', + }; + } + + if (param === 'twitterapi') { + const testResult = await twitterapi.testApi(); + if (testResult.success) { + output = 'Twitter API functions well.'; + } else { + output = `Error while making Twitter API request: ${testResult.message}`; + } } + } catch (e) { + log.error(`Error in test() of ${helpers.getModuleName(module.id)} module: ${e}`); } return { msgNotify: ``, - msgSendBack: `${output}`, + msgSendBack: output, notifyType: 'log', }; } @@ -190,17 +203,21 @@ function version() { function balances() { let output = ''; - config.known_crypto.forEach((crypto) => { - if (Store.user[crypto].balance) { - output += `${helpers.thousandSeparator(+Store.user[crypto].balance.toFixed(8), true)} _${crypto}_`; - output += '\n'; - } - }); + + try { + config.known_crypto.forEach((crypto) => { + if (Store.user[crypto].balance) { + output += `${helpers.thousandSeparator(+Store.user[crypto].balance.toFixed(8), true)} _${crypto}_`; + output += '\n'; + } + }); + } catch (e) { + log.error(`Error in balances() of ${helpers.getModuleName(module.id)} module: ${e}`); + } return { msgNotify: ``, - msgSendBack: `My crypto balances: -${output}`, + msgSendBack: `My crypto balances:\n${output}`, notifyType: 'log', }; } From b4c4188b67a9177e5813de1cec7a9caaafc94fc4 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Mon, 15 Aug 2022 22:48:53 +0300 Subject: [PATCH 45/55] chore: update configReader and DB --- modules/DB.js | 13 +++++++++---- modules/configReader.js | 5 ++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/modules/DB.js b/modules/DB.js index e430a69..fecaa30 100644 --- a/modules/DB.js +++ b/modules/DB.js @@ -1,12 +1,15 @@ +const log = require('../helpers/log'); const MongoClient = require('mongodb').MongoClient; -const mongoClient = new MongoClient('mongodb://localhost:27017/', {useNewUrlParser: true, useUnifiedTopology: true}); +const mongoClient = new MongoClient('mongodb://localhost:27017/', {useNewUrlParser: true, useUnifiedTopology: true, serverSelectionTimeoutMS: 3000}); const model = require('../helpers/dbModel'); +const config = require('./configReader'); const collections = {}; -mongoClient.connect((err, client) => { - if (err) { - throw (err); +mongoClient.connect((error, client) => { + if (error) { + log.error(`Unable to connect to MongoDB, ` + error); + process.exit(-1); } const db = client.db('bountybotdb'); collections.db = db; @@ -14,6 +17,8 @@ mongoClient.connect((err, client) => { collections.IncomingTxsDb = model(db.collection('incomingtxs')); collections.UsersDb = model(db.collection('users')); collections.PaymentsDb = model(db.collection('payments')); + + log.log(`${config.notifyName} successfully connected to 'bountybotdb' MongoDB.`); }); module.exports = collections; diff --git a/modules/configReader.js b/modules/configReader.js index b3244aa..6ff917b 100644 --- a/modules/configReader.js +++ b/modules/configReader.js @@ -110,7 +110,8 @@ try { if (isDev) { config = JSON.parse(jsonminify(fs.readFileSync('./config.test', 'utf-8'))); } else { - config = JSON.parse(jsonminify(fs.readFileSync('./config.default.json', 'utf-8'))); + const configFile = fs.existsSync('./config.json') ? './config.json' : './config.default.json'; + config = JSON.parse(jsonminify(fs.readFileSync(configFile, 'utf-8'))); } let keysPair; @@ -189,6 +190,8 @@ try { exit(`Bot's ${address} config is wrong. Field type _${f}_ is not valid, expected type is _${fields[f].type.name}_. Cannot start Bot.`); } }); + + console.info(`The bot ${address} successfully read a config-file${isDev ? ' (dev)' : ''}.`); } catch (e) { console.error('Error reading config: ' + e); } From ebe4749a1b5d0afa6a6d0df1798c27657f31b338 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Mon, 15 Aug 2022 22:54:34 +0300 Subject: [PATCH 46/55] chore: add try-catches --- modules/incomingTxsParser.js | 190 ++++++++++++++++++----------------- modules/transferTxs.js | 62 ++++++------ 2 files changed, 131 insertions(+), 121 deletions(-) diff --git a/modules/incomingTxsParser.js b/modules/incomingTxsParser.js index 86d2e57..db4b506 100644 --- a/modules/incomingTxsParser.js +++ b/modules/incomingTxsParser.js @@ -13,102 +13,106 @@ const notify = require('../helpers/notify'); const historyTxs = {}; module.exports = async (tx) => { - if (!tx) { - return; - } - - if (historyTxs[tx.id]) { // do not process one tx twice - return; - } - - const {IncomingTxsDb} = db; - const checkedTx = await IncomingTxsDb.findOne({txid: tx.id}); - if (checkedTx !== null) { - return; - } - - log.log(`Received incoming transaction ${tx.id} from ${tx.senderId}.`); - - let msg = ''; - const chat = tx.asset.chat; - if (chat) { - msg = api.decodeMsg(chat.message, tx.senderPublicKey, config.passPhrase, chat.own_message).trim(); - } - - if (msg === '') { - msg = 'NONE'; - } - - // Parse social accounts from user message - const accounts = $u.getAccounts(msg); - - let type = 'unknown'; - if (msg.includes('_transaction') || tx.amount > 0) { - type = 'transfer'; // just for special message - } else if (accounts.notEmpty) { - type = 'check'; - } else if (msg.startsWith('/')) { - type = 'command'; - } - - // Check if we should notify about spammer, only once per 24 hours - const spamerIsNotify = await IncomingTxsDb.findOne({ - sender: tx.senderId, - isSpam: true, - date: {$gt: (helpers.unix() - 24 * 3600 * 1000)}, // last 24h - }); - - const itx = new IncomingTxsDb({ - _id: tx.id, - txid: tx.id, - date: helpers.unix(), - block_id: tx.blockId, - encrypted_content: msg, - accounts: accounts, - spam: false, - sender: tx.senderId, - type, // check, command, transfer or unknown - isProcessed: false, - isNonAdmin: false, - }); - - const countRequestsUser = (await IncomingTxsDb.find({ - sender: tx.senderId, - date: {$gt: (helpers.unix() - 24 * 3600 * 1000)}, // last 24h - })).length; - - if (countRequestsUser > 50 || spamerIsNotify) { // 50 per 24h is a limit for accepting commands, otherwise user will be considered as spammer - itx.update({ - isProcessed: true, + try { + if (!tx) { + return; + } + + if (historyTxs[tx.id]) { // do not process one tx twice + return; + } + + const {IncomingTxsDb} = db; + const checkedTx = await IncomingTxsDb.findOne({txid: tx.id}); + if (checkedTx !== null) { + return; + } + + log.log(`Received incoming transaction ${tx.id} from ${tx.senderId}.`); + + let msg = ''; + const chat = tx.asset.chat; + if (chat) { + msg = api.decodeMsg(chat.message, tx.senderPublicKey, config.passPhrase, chat.own_message).trim(); + } + + if (msg === '') { + msg = 'NONE'; + } + + // Parse social accounts from user message + const accounts = $u.getAccounts(msg); + + let type = 'unknown'; + if (msg.includes('_transaction') || tx.amount > 0) { + type = 'transfer'; // just for special message + } else if (accounts.notEmpty) { + type = 'check'; + } else if (msg.startsWith('/')) { + type = 'command'; + } + + // Check if we should notify about spammer, only once per 24 hours + const spamerIsNotify = await IncomingTxsDb.findOne({ + sender: tx.senderId, isSpam: true, + date: {$gt: (helpers.unix() - 24 * 3600 * 1000)}, // last 24h }); - } - await itx.save(); - if (historyTxs[tx.id]) { - return; - } - historyTxs[tx.id] = helpers.unix(); - - if (itx.isSpam && !spamerIsNotify) { - notify(`${config.notifyName} notifies _${tx.senderId}_ is a spammer or talks too much. Income ADAMANT Tx: https://explorer.adamant.im/tx/${tx.id}.`, 'warn'); - const msgSendBack = `I’ve _banned_ you. You’ve sent too much transactions to me.`; - await api.sendMessageWithLog(config.passPhrase, tx.senderId, msgSendBack); - return; - } + const itx = new IncomingTxsDb({ + _id: tx.id, + txid: tx.id, + date: helpers.unix(), + block_id: tx.blockId, + encrypted_content: msg, + accounts: accounts, + spam: false, + sender: tx.senderId, + type, // check, command, transfer or unknown + isProcessed: false, + isNonAdmin: false, + }); - switch (type) { - case ('transfer'): - transferTxs(itx, tx); - break; - case ('check'): - checkTxs(itx, tx); - break; - case ('command'): - commandTxs(msg, tx, itx); - break; - default: - unknownTxs(tx, itx); - break; + const countRequestsUser = (await IncomingTxsDb.find({ + sender: tx.senderId, + date: {$gt: (helpers.unix() - 24 * 3600 * 1000)}, // last 24h + })).length; + + if (countRequestsUser > 50 || spamerIsNotify) { // 50 per 24h is a limit for accepting commands, otherwise user will be considered as spammer + itx.update({ + isProcessed: true, + isSpam: true, + }); + } + + await itx.save(); + if (historyTxs[tx.id]) { + return; + } + historyTxs[tx.id] = helpers.unix(); + + if (itx.isSpam && !spamerIsNotify) { + notify(`${config.notifyName} notifies _${tx.senderId}_ is a spammer or talks too much. Income ADAMANT Tx: https://explorer.adamant.im/tx/${tx.id}.`, 'warn'); + const msgSendBack = `I’ve _banned_ you. You’ve sent too much transactions to me.`; + await api.sendMessageWithLog(config.passPhrase, tx.senderId, msgSendBack); + return; + } + + switch (type) { + case ('transfer'): + transferTxs(itx, tx); + break; + case ('check'): + checkTxs(itx, tx); + break; + case ('command'): + commandTxs(msg, tx, itx); + break; + default: + unknownTxs(tx, itx); + break; + } + } catch (e) { + log.error(`Error in ${helpers.getModuleName(module.id)} module: ${e}`); } }; diff --git a/modules/transferTxs.js b/modules/transferTxs.js index 265bc63..4aa3b73 100644 --- a/modules/transferTxs.js +++ b/modules/transferTxs.js @@ -1,42 +1,48 @@ const {SAT} = require('../helpers/const'); +const log = require('../helpers/log'); const api = require('./api'); const notify = require('../helpers/notify'); const config = require('./configReader'); +const helpers = require('../helpers/utils'); module.exports = async (itx, tx) => { - const msg = itx.encrypted_content; - let inCurrency; - let outCurrency; - let inAmountMessage; + try { + const msg = itx.encrypted_content; + let inCurrency; + let outCurrency; + let inAmountMessage; - if (tx.amount > 0) { // ADM income payment - inAmountMessage = tx.amount / SAT; - inCurrency = 'ADM'; - outCurrency = msg; - } else if (msg.includes('_transaction')) { // not ADM income payment - inCurrency = msg.match(/"type":"(.*)_transaction/)[1]; - try { - const json = JSON.parse(msg); - inAmountMessage = Number(json.amount); - outCurrency = json.comments; - if (outCurrency === '') { - outCurrency = 'NONE'; + if (tx.amount > 0) { // ADM income payment + inAmountMessage = tx.amount / SAT; + inCurrency = 'ADM'; + outCurrency = msg; + } else if (msg.includes('_transaction')) { // not ADM income payment + inCurrency = msg.match(/"type":"(.*)_transaction/)[1]; + try { + const json = JSON.parse(msg); + inAmountMessage = Number(json.amount); + outCurrency = json.comments; + if (outCurrency === '') { + outCurrency = 'NONE'; + } + } catch (e) { + inCurrency = 'none'; } - } catch (e) { - inCurrency = 'none'; } - } - outCurrency = String(outCurrency).toUpperCase().trim(); - inCurrency = String(inCurrency).toUpperCase().trim(); + outCurrency = String(outCurrency).toUpperCase().trim(); + inCurrency = String(inCurrency).toUpperCase().trim(); - // Validate - const msgSendBack = `I got a transfer from you. Thanks, bro.`; - const msgNotify = `${config.notifyName} got a transfer of ${inAmountMessage} ${inCurrency} from user ${tx.senderId}. I will not verify the transaction, check it manually. Income ADAMANT Tx: https://explorer.adamant.im/tx/${tx.id}.`; - const notifyType = 'log'; + // Validate + const msgSendBack = `I got a transfer from you. Thanks, bro.`; + const msgNotify = `${config.notifyName} got a transfer of ${inAmountMessage} ${inCurrency} from user ${tx.senderId}. I will not verify the transaction, check it manually. Income ADAMANT Tx: https://explorer.adamant.im/tx/${tx.id}.`; + const notifyType = 'log'; - await itx.update({isProcessed: true}, true); + await itx.update({isProcessed: true}, true); - notify(msgNotify, notifyType); - await api.sendMessageWithLog(config.passPhrase, tx.senderId, msgSendBack); + notify(msgNotify, notifyType); + await api.sendMessageWithLog(config.passPhrase, tx.senderId, msgSendBack); + } catch (e) { + log.error(`Error in ${helpers.getModuleName(module.id)} module: ${e}`); + } }; From 506ac46d7f3ab4af802ea57661ebab7cb3678e08 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Mon, 15 Aug 2022 22:56:57 +0300 Subject: [PATCH 47/55] fix: commandTxs module --- modules/commandTxs.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/commandTxs.js b/modules/commandTxs.js index 0324c42..31bd5aa 100644 --- a/modules/commandTxs.js +++ b/modules/commandTxs.js @@ -8,10 +8,10 @@ const notify = require('../helpers/notify'); const twitterapi = require('./twitterapi'); module.exports = async (cmd, tx, itx) => { - if (itx.isProcessed) return; - log.log(`Got new command Tx to process: ${cmd} from ${tx.senderId}`); - try { + if (itx.isProcessed) return; + log.log(`Got new command Tx to process: '${cmd}' from ${tx.senderId}.`); + let res = []; const group = cmd .trim() @@ -165,7 +165,7 @@ async function test(param) { let output; try { - param = param[0].trim(); + param = param[0]?.trim(); if (!param || !['twitterapi'].includes(param)) { return { msgNotify: ``, From a3d496f91882405ee7fe3596d980ff4e2ae58b17 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Mon, 15 Aug 2022 23:03:05 +0300 Subject: [PATCH 48/55] chore: add try-catches --- modules/Store.js | 56 +++++++++++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/modules/Store.js b/modules/Store.js index b34c0ff..787064c 100644 --- a/modules/Store.js +++ b/modules/Store.js @@ -18,6 +18,7 @@ const lskData = api.lsk.keys(config.passPhrase); module.exports = { version, botName: AdmAddress, + user: { ADM: { passPhrase: config.passPhrase, @@ -33,27 +34,36 @@ module.exports = { privateKey: lskData.privateKey, }, }, + comissions: { - ADM: 0.5, // This is a stub. Ether fee returned with FEE() method in separate module + ADM: 0.5, // This is a stub. Cryptos' fees returned with FEE() method in their modules }, + lastBlock: null, get lastHeight() { return this.lastBlock && this.lastBlock.height || false; }, + updateSystem(field, data) { const $set = {}; $set[field] = data; db.systemDb.db.updateOne({}, {$set}, {upsert: true}); this[field] = data; }, + async updateLastBlock() { - const blocks = await api.get('blocks', {limit: 1}); - if (blocks.success) { - this.updateSystem('lastBlock', blocks.data.blocks[0]); - } else { - log.warn(`Failed to get last block in updateLastBlock() of ${helpers.getModuleName(module.id)} module. ${blocks.errorMessage}.`); + try { + const blocks = await api.get('blocks', {limit: 1}); + if (blocks.success) { + this.updateSystem('lastBlock', blocks.data.blocks[0]); + } else { + log.warn(`Failed to get last block in updateLastBlock() of ${helpers.getModuleName(module.id)} module. ${blocks.errorMessage}.`); + } + } catch (e) { + log.error(`Error in updateLastBlock() of ${helpers.getModuleName(module.id)} module: ${e}`); } }, + async updateCurrencies() { const url = config.infoservice + '/get'; try { @@ -67,9 +77,10 @@ module.exports = { } } } catch (error) { - log.warn(`Error in updateCurrencies() of ${helpers.getModuleName(module.id)} module: Request to ${url} failed with ${error.response ? error.response.status : undefined} status code, ${error.toString()}${error.response && error.response.data ? '. Message: ' + error.response.data.toString().trim() : ''}.`); + log.warn(`Error in updateCurrencies() of ${helpers.getModuleName(module.id)} module: Request to ${url} failed with ${error?.response?.status} status code, ${error.toString()}${error?.response?.data ? '. Message: ' + error.response.data.toString().trim() : ''}.`); } }, + getPrice(from, to) { try { from = from.toUpperCase(); @@ -82,26 +93,31 @@ module.exports = { const priceTo = +(this.currencies[to + '/USD']); return +(priceFrom / priceTo || 1).toFixed(8); } catch (e) { - log.error('Error while calculating getPrice(): ', e); + log.error('Error while calculating getPrice(): ' + e); return 0; } }, + mathEqual(from, to, amount, doNotAccountFees) { - let price = this.getPrice(from, to); - if (!doNotAccountFees) { - price *= (100 - config['exchange_fee_' + from]) / 100; - } - if (!price) { + try { + let price = this.getPrice(from, to); + if (!doNotAccountFees) { + price *= (100 - config['exchange_fee_' + from]) / 100; + } + if (!price) { + return { + outAmount: 0, + exchangePrice: 0, + }; + } + price = +price.toFixed(8); return { - outAmount: 0, - exchangePrice: 0, + outAmount: +(price * amount).toFixed(8), + exchangePrice: price, }; + } catch (e) { + log.error(`Error in mathEqual() of ${helpers.getModuleName(module.id)} module: ${e}`); } - price = +price.toFixed(8); - return { - outAmount: +(price * amount).toFixed(8), - exchangePrice: price, - }; }, }; From 4e8dbf0915725ae723d844a2043348df2f6df4e5 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Mon, 15 Aug 2022 23:03:44 +0300 Subject: [PATCH 49/55] chore: rename comissions to fees --- helpers/cryptos/adm_utils.js | 2 +- modules/Store.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/helpers/cryptos/adm_utils.js b/helpers/cryptos/adm_utils.js index 97d3444..9482336 100644 --- a/helpers/cryptos/adm_utils.js +++ b/helpers/cryptos/adm_utils.js @@ -8,7 +8,7 @@ const User = Store.user.ADM; module.exports = { get FEE() { - return Store.comissions.ADM; + return Store.fees.ADM; }, syncGetTransaction(hash, tx) { return { diff --git a/modules/Store.js b/modules/Store.js index 787064c..2bcf18a 100644 --- a/modules/Store.js +++ b/modules/Store.js @@ -35,7 +35,7 @@ module.exports = { }, }, - comissions: { + fees: { ADM: 0.5, // This is a stub. Cryptos' fees returned with FEE() method in their modules }, From f83c93e248db8c2b97f3c5d05b728139c32b4d62 Mon Sep 17 00:00:00 2001 From: adamant-al Date: Mon, 15 Aug 2022 23:08:49 +0300 Subject: [PATCH 50/55] chore: update utils.js --- helpers/utils.js | 69 ++++++++++++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/helpers/utils.js b/helpers/utils.js index b380925..a50fa2f 100644 --- a/helpers/utils.js +++ b/helpers/utils.js @@ -1,46 +1,61 @@ const constants = require('./const'); +const log = require('./log'); + module.exports = { unix() { return new Date().getTime(); }, + getModuleName(id) { - let n = id.lastIndexOf('\\'); - if (n === -1) { - n = id.lastIndexOf('/'); - } - if (n === -1) { - return ''; - } else { - return id.substring(n + 1); + try { + let n = id.lastIndexOf('\\'); + if (n === -1) { + n = id.lastIndexOf('/'); + } + if (n === -1) { + return ''; + } else { + return id.substring(n + 1); + } + } catch (e) { + log.error(`Error in getModuleName() of utils.js module: ${e}`); } }, + toTimestamp(epochTime) { return epochTime * 1000 + constants.EPOCH; }, + thousandSeparator(num, doBold) { - const parts = (num + '').split('.'); - const main = parts[0]; - const len = main.length; - let output = ''; - let i = len - 1; + try { + const parts = (num + '').split('.'); + const main = parts[0]; + const len = main.length; + let output = ''; + let i = len - 1; - while (i >= 0) { - output = main.charAt(i) + output; - if ((len - i) % 3 === 0 && i > 0) { - output = ' ' + output; + while (i >= 0) { + output = main.charAt(i) + output; + if ((len - i) % 3 === 0 && i > 0) { + output = ' ' + output; + } + --i; } - --i; - } - if (parts.length > 1) { - if (doBold) { - output = `**${output}**.${parts[1]}`; - } else { - output = `${output}.${parts[1]}`; + if (parts.length > 1) { + if (doBold) { + output = `**${output}**.${parts[1]}`; + } else { + output = `${output}.${parts[1]}`; + } } + + return output; + } catch (e) { + log.error(`Error in thousandSeparator() of ${this.getModuleName(module.id)} module: ${e}`); } - return output; }, + /** * Formats unix timestamp to string * @param {number} timestamp Timestamp to format @@ -63,6 +78,7 @@ module.exports = { formattedDate.hh_mm_ss = formattedDate.hours + ':' + formattedDate.minutes + ':' + formattedDate.seconds; return formattedDate; }, + /** * Compares two strings, case-insensitive * @param {string} string1 @@ -73,6 +89,7 @@ module.exports = { if (typeof string1 !== 'string' || typeof string2 !== 'string') return false; return string1.toUpperCase() === string2.toUpperCase(); }, + /** * Checks if number is finite * @param {number} value Number to validate @@ -84,6 +101,7 @@ module.exports = { } return true; }, + /** * Checks if number is finite and not less, than 0 * @param {number} value Number to validate @@ -95,6 +113,7 @@ module.exports = { } return true; }, + /** * Converts a bytes array to the respective string representation * @param {Array|Uint8Array} bytes bytes array From 146f43aea711abbf8d9e103252930bc98a8630cd Mon Sep 17 00:00:00 2001 From: adamant-al Date: Tue, 16 Aug 2022 10:36:49 +0300 Subject: [PATCH 51/55] fix: process triggerUncaughtException for Lisk --- helpers/cryptos/lskBaseCoin.js | 60 +++++++++++++++++++++------------- helpers/cryptos/lsk_utils.js | 6 +++- 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/helpers/cryptos/lskBaseCoin.js b/helpers/cryptos/lskBaseCoin.js index 2549449..16f3ce4 100644 --- a/helpers/cryptos/lskBaseCoin.js +++ b/helpers/cryptos/lskBaseCoin.js @@ -193,7 +193,11 @@ module.exports = class LskBaseCoin { * @return {Object} */ _get(url, params) { - return this._getClient().get(url, {params}).then((response) => response.data); + return this._getClient().get(url, {params}) + .then((response) => response.data) + .catch((e) => { + log.warn(`Error while requesting Lisk Node url '${url}' with params '${JSON.stringify(params)}' in _get() of ${helpers.getModuleName(module.id)} module: ` + e); + }); } /** @@ -203,7 +207,11 @@ module.exports = class LskBaseCoin { * @return {Object} */ _getService(url, params) { - return this._getServiceClient().get(url, {params}).then((response) => response.data); + return this._getServiceClient().get(url, {params}) + .then((response) => response.data) + .catch((e) => { + log.warn(`Error while requesting Lisk Service url '${url}' with params '${JSON.stringify(params)}' in _getService() of ${helpers.getModuleName(module.id)} module: ` + e); + }); } /** @@ -327,14 +335,18 @@ module.exports = class LskBaseCoin { * @return {Object} */ function createServiceClient(url) { - const client = axios.create({baseURL: url}); - client.interceptors.response.use(null, (error) => { - if (error.response && Number(error.response.status) >= 500) { - console.error(`Request to ${url} failed.`, error); - } - return Promise.reject(error); - }); - return client; + try { + const client = axios.create({baseURL: url}); + client.interceptors.response.use(null, (error) => { + if (error.response && Number(error.response.status) >= 500) { + console.error(`Request to ${url} failed.`, error); + } + return Promise.reject(error); + }); + return client; + } catch (e) { + log.error(`Error in createServiceClient() of ${helpers.getModuleName(module.id)} module: ${e}`); + } } /** @@ -343,17 +355,21 @@ function createServiceClient(url) { * @return {Object} */ function createClient(url) { - const client = axios.create({baseURL: url}); - client.interceptors.response.use(null, (error) => { - if (error.response && Number(error.response.status) >= 500) { - console.error(`Request to ${url} failed.`, error); - } - if (error.response && Number(error.response.status) === 404) { - if (error.response?.data?.errors[0]?.message && error.response.data.errors[0].message.includes('was not found')) { - return error.response; + try { + const client = axios.create({baseURL: url}); + client.interceptors.response.use(null, (error) => { + if (error.response && Number(error.response.status) >= 500) { + console.error(`Request to ${url} failed.`, error); } - } - return Promise.reject(error); - }); - return client; + if (error.response && Number(error.response.status) === 404) { + if (error.response?.data?.errors[0]?.message && error.response.data.errors[0].message.includes('was not found')) { + return error.response; + } + } + return Promise.reject(error); + }); + return client; + } catch (e) { + log.error(`Error in createClient() of ${helpers.getModuleName(module.id)} module: ${e}`); + } } diff --git a/helpers/cryptos/lsk_utils.js b/helpers/cryptos/lsk_utils.js index 259f6d2..e9b5dcf 100644 --- a/helpers/cryptos/lsk_utils.js +++ b/helpers/cryptos/lsk_utils.js @@ -23,7 +23,11 @@ module.exports = class LskCoin extends LskBaseCoin { } getHeight() { - return this._get(`${lskNode}/api/node/info`, {}).then((data) => Number(data.data.height) || 0); + return this._get(`${lskNode}/api/node/info`, {}) + .then((data) => Number(data.data.height) || 0) + .catch((e) => { + log.warn(`Error while getting Lisk height in getHeight() of ${helpers.getModuleName(module.id)} module: ` + e); + }); } /** From 9ae14d61a07d362a1368c94d7292fc340c4254de Mon Sep 17 00:00:00 2001 From: adamant-al Date: Tue, 16 Aug 2022 11:09:48 +0300 Subject: [PATCH 52/55] chore: add try-catches to cryptos module --- helpers/cryptos/index.js | 93 ++++++++++++++++++++++++++-------------- 1 file changed, 60 insertions(+), 33 deletions(-) diff --git a/helpers/cryptos/index.js b/helpers/cryptos/index.js index 3353b89..9c1732d 100644 --- a/helpers/cryptos/index.js +++ b/helpers/cryptos/index.js @@ -9,20 +9,25 @@ const helpers = require('../utils'); module.exports = { async getAddressCryptoFromAdmAddressADM(coin, admAddress) { - if (this.isERC20(coin)) { - coin = 'ETH'; - } - const kvsRecords = await api.get('states/get', {senderId: admAddress, key: coin.toLowerCase() + ':address', orderBy: 'timestamp:desc'}); - if (kvsRecords.success) { - if (kvsRecords.data.transactions.length) { - return kvsRecords.data.transactions[0].asset.state.value; + try { + if (this.isERC20(coin)) { + coin = 'ETH'; + } + const kvsRecords = await api.get('states/get', {senderId: admAddress, key: coin.toLowerCase() + ':address', orderBy: 'timestamp:desc'}); + if (kvsRecords.success) { + if (kvsRecords.data.transactions.length) { + return kvsRecords.data.transactions[0].asset.state.value; + } else { + return 'none'; + } } else { - return 'none'; + log.warn(`Failed to get ${coin} address for ${admAddress} from KVS in getAddressCryptoFromAdmAddressADM() of ${helpers.getModuleName(module.id)} module. ${kvsRecords.errorMessage}.`); } - } else { - log.warn(`Failed to get ${coin} address for ${admAddress} from KVS in getAddressCryptoFromAdmAddressADM() of ${helpers.getModuleName(module.id)} module. ${kvsRecords.errorMessage}.`); + } catch (e) { + log.error(`Error in getAddressCryptoFromAdmAddressADM() of ${helpers.getModuleName(module.id)} module: ${e}`); } }, + async updateAllBalances() { try { await this.ETH.updateBalance(); @@ -31,20 +36,28 @@ module.exports = { for (const t of config.erc20) { await this[t].updateBalance(); } - } catch (e) {} + } catch (e) { + log.error(`Error in updateAllBalances() of ${helpers.getModuleName(module.id)} module: ${e}`); + } }, + async getLastBlocksNumbers() { - const data = { - ETH: await this.ETH.getLastBlock(), - ADM: await this.ADM.getLastBlock(), - LSK: await this.LSK.getLastBlockHeight(), - }; - for (const t of config.erc20) { - // data[t] = await this[t].getLastBlockNumber(); // Don't do unnecessary requests - data[t] = data['ETH']; + try { + const data = { + ETH: await this.ETH.getLastBlock(), + ADM: await this.ADM.getLastBlock(), + LSK: await this.LSK.getLastBlockHeight(), + }; + for (const t of config.erc20) { + // data[t] = await this[t].getLastBlockNumber(); // Don't do unnecessary requests + data[t] = data['ETH']; + } + return data; + } catch (e) { + log.error(`Error in getLastBlocksNumbers() of ${helpers.getModuleName(module.id)} module: ${e}`); } - return data; }, + isKnown(coin) { return config.known_crypto.includes(coin); }, @@ -64,27 +77,34 @@ module.exports = { isERC20(coin) { return config.erc20.includes(coin.toUpperCase()); }, + getAccounts(message) { const userAccounts = {}; - userAccounts.notEmpty = false; + try { + userAccounts.notEmpty = false; - userAccounts.twitterLink = this.findLink(message, 'twitter.com'); - if (userAccounts.twitterLink) { - userAccounts.twitterAccount = this.parseTwitterAccountFromLink(userAccounts.twitterLink); - } else { - userAccounts.twitterAccount = this.findTwitterAccount(message); - } + userAccounts.twitterLink = this.findLink(message, 'twitter.com'); + if (userAccounts.twitterLink) { + userAccounts.twitterAccount = this.parseTwitterAccountFromLink(userAccounts.twitterLink); + } else { + userAccounts.twitterAccount = this.findTwitterAccount(message); + } - userAccounts.facebookLink = this.findLink(message, 'facebook.com'); + userAccounts.facebookLink = this.findLink(message, 'facebook.com'); - if (userAccounts.twitterAccount && config.isTwitterCampaign) { - userAccounts.notEmpty = true; - } - if (userAccounts.facebookAccount && config.isFacebookCampaign) { - userAccounts.notEmpty = true; + if (userAccounts.twitterAccount && config.isTwitterCampaign) { + userAccounts.notEmpty = true; + } + if (userAccounts.facebookAccount && config.isFacebookCampaign) { + userAccounts.notEmpty = true; + } + } catch (e) { + log.error(`Error in getAccounts(message: ${message}) of ${helpers.getModuleName(module.id)} module: ${e}`); } + return userAccounts; }, + findTwitterAccount(message) { const pattern = /(?<=^|(?<=[^a-zA-Z0-9-_.]))@([A-Za-z]+[A-Za-z0-9-_]+)/gi; const accounts = message.match(pattern); @@ -92,6 +112,7 @@ module.exports = { return accounts[0].toLowerCase(); } }, + findLink(message, link) { const kLINK_DETECTION_REGEX = /(([a-z]+:\/\/)?(([a-z0-9-]+\.)+([a-z]{2}|aero|arpa|biz|com|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|local|internal))(:[0-9]{1,5})?(\/[a-z0-9_\-.~]+)*(\/([a-z0-9_\-.]*)(\?[a-z0-9+_\-.%=&]*)?)?(#[a-zA-Z0-9!$&'()*+.=-_~:@/?]*)?)(\s+|$)/gi; const links = message.match(kLINK_DETECTION_REGEX); @@ -105,6 +126,7 @@ module.exports = { } return found.trim().toLowerCase(); }, + trimChar(s, mask) { while (~mask.indexOf(s[0])) { s = s.slice(1); @@ -114,9 +136,11 @@ module.exports = { } return s; }, + getTwitterScreenName(account) { return this.trimChar(account, '@').toLowerCase(); }, + parseTwitterAccountFromLink(link) { link = this.trimChar(link, '/'); const n = link.lastIndexOf('/'); @@ -126,6 +150,7 @@ module.exports = { return '@' + link.substring(n + 1).toLowerCase(); } }, + getTweetIdFromLink(link) { link = this.trimChar(link, '/'); const n = link.lastIndexOf('/'); @@ -135,12 +160,14 @@ module.exports = { return link.substring(n + 1); } }, + getTwitterHashtags(tags) { for (let i = 0; i < tags.length; i++) { tags[i] = this.trimChar(tags[i], '#'); } return tags; }, + ETH: eth_utils, ADM: adm_utils, LSK: new LskCoin('LSK'), From 25045d10d6e6ae7ce120f5881ac2d96052eb729f Mon Sep 17 00:00:00 2001 From: gost1k Date: Tue, 16 Aug 2022 19:27:01 +0300 Subject: [PATCH 53/55] add JS-docs --- helpers/cryptos/index.js | 81 ++++++++++++++++++++++++++++++++++++++++ helpers/utils.js | 14 +++++++ 2 files changed, 95 insertions(+) diff --git a/helpers/cryptos/index.js b/helpers/cryptos/index.js index 9c1732d..5974d6d 100644 --- a/helpers/cryptos/index.js +++ b/helpers/cryptos/index.js @@ -8,6 +8,12 @@ const Store = require('../../modules/Store'); const helpers = require('../utils'); module.exports = { + /** + * Get address from ADM address + * @param {String} coin + * @param {String} admAddress + * @return {Promise<*>} + */ async getAddressCryptoFromAdmAddressADM(coin, admAddress) { try { if (this.isERC20(coin)) { @@ -28,6 +34,10 @@ module.exports = { } }, + /** + * Update all balances + * @return {Promise} + */ async updateAllBalances() { try { await this.ETH.updateBalance(); @@ -41,6 +51,10 @@ module.exports = { } }, + /** + * Get last blocks numbers + * @return {Promise} + */ async getLastBlocksNumbers() { try { const data = { @@ -58,15 +72,35 @@ module.exports = { } }, + /** + * Returns true if coin is in known_crypto list in config + * @param {String} coin + * @return {Boolean} + */ isKnown(coin) { return config.known_crypto.includes(coin); }, + /** + * Returns true if coin is in accepted_crypto list in config + * @param {String} coin + * @return {Boolean} + */ isAccepted(coin) { return config.accepted_crypto.includes(coin); }, + /** + * Returns true if coin is in exchange_crypto list in config + * @param {String} coin + * @return {Boolean} + */ isExchanged(coin) { return config.exchange_crypto.includes(coin); }, + /** + * Returns true if coin is fiat money + * @param {String} coin + * @return {Boolean} + */ isFiat(coin) { return ['USD', 'RUB', 'EUR', 'CNY', 'JPY'].includes(coin); }, @@ -74,10 +108,20 @@ module.exports = { const pairs = Object.keys(Store.currencies).toString(); return pairs.includes(',' + coin + '/') || pairs.includes('/' + coin); }, + /** + * Returns true if coin is ERC-20 coin + * @param {String} coin + * @return {Boolean} + */ isERC20(coin) { return config.erc20.includes(coin.toUpperCase()); }, + /** + * Get Twitter accounts + * @param {String} message + * @return {void} + */ getAccounts(message) { const userAccounts = {}; try { @@ -105,6 +149,11 @@ module.exports = { return userAccounts; }, + /** + * Get Twitter account by matching regex and message + * @param {String} message + * @return {Object} + */ findTwitterAccount(message) { const pattern = /(?<=^|(?<=[^a-zA-Z0-9-_.]))@([A-Za-z]+[A-Za-z0-9-_]+)/gi; const accounts = message.match(pattern); @@ -113,6 +162,12 @@ module.exports = { } }, + /** + * Get Twitter account by matching regex and message + * @param {String} message + * @param {String} link + * @return {String} + */ findLink(message, link) { const kLINK_DETECTION_REGEX = /(([a-z]+:\/\/)?(([a-z0-9-]+\.)+([a-z]{2}|aero|arpa|biz|com|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|local|internal))(:[0-9]{1,5})?(\/[a-z0-9_\-.~]+)*(\/([a-z0-9_\-.]*)(\?[a-z0-9+_\-.%=&]*)?)?(#[a-zA-Z0-9!$&'()*+.=-_~:@/?]*)?)(\s+|$)/gi; const links = message.match(kLINK_DETECTION_REGEX); @@ -127,6 +182,12 @@ module.exports = { return found.trim().toLowerCase(); }, + /** + * Trims char + * @param {String} s + * @param {String} mask + * @return {String} + */ trimChar(s, mask) { while (~mask.indexOf(s[0])) { s = s.slice(1); @@ -137,10 +198,20 @@ module.exports = { return s; }, + /** + * Trims @ symbol in twitter name + * @param {String} account + * @return {String} + */ getTwitterScreenName(account) { return this.trimChar(account, '@').toLowerCase(); }, + /** + * Parses twitter name from link + * @param {String} link + * @return {String} + */ parseTwitterAccountFromLink(link) { link = this.trimChar(link, '/'); const n = link.lastIndexOf('/'); @@ -151,6 +222,11 @@ module.exports = { } }, + /** + * Get twitter ID from link + * @param {String} link + * @return {String} + */ getTweetIdFromLink(link) { link = this.trimChar(link, '/'); const n = link.lastIndexOf('/'); @@ -161,6 +237,11 @@ module.exports = { } }, + /** + * Get twitter hashtags + * @param {String }tags + * @return {Array} + */ getTwitterHashtags(tags) { for (let i = 0; i < tags.length; i++) { tags[i] = this.trimChar(tags[i], '#'); diff --git a/helpers/utils.js b/helpers/utils.js index a50fa2f..5bf61d6 100644 --- a/helpers/utils.js +++ b/helpers/utils.js @@ -2,10 +2,19 @@ const constants = require('./const'); const log = require('./log'); module.exports = { + /** + * Returns current time in milliseconds since Unix Epoch + * @return {number} + */ unix() { return new Date().getTime(); }, + /** + * Returns module name from its ID + * @param {string} id Module name, module.id + * @return {string} + */ getModuleName(id) { try { let n = id.lastIndexOf('\\'); @@ -22,6 +31,11 @@ module.exports = { } }, + /** + * Converts ADAMANT's epoch timestamp to a Unix timestamp + * @param {number} epochTime Timestamp to convert + * @return {number} + */ toTimestamp(epochTime) { return epochTime * 1000 + constants.EPOCH; }, From 299ce7391dca63c46ed5cb135dd36804dea2256f Mon Sep 17 00:00:00 2001 From: adamant-al Date: Tue, 16 Aug 2022 20:06:20 +0300 Subject: [PATCH 54/55] chore: JSDocs update --- helpers/cryptos/index.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/helpers/cryptos/index.js b/helpers/cryptos/index.js index 5974d6d..9bad0ee 100644 --- a/helpers/cryptos/index.js +++ b/helpers/cryptos/index.js @@ -9,10 +9,10 @@ const helpers = require('../utils'); module.exports = { /** - * Get address from ADM address - * @param {String} coin - * @param {String} admAddress - * @return {Promise<*>} + * Get coin address from KVS for ADM account + * @param {String} coin Like ETH + * @param {String} admAddress Account to get coin address for + * @return {Promise<*>} Address like '0x5f625681dA71e83C6f8aCef0299a6ab16539f54E' or 'none' */ async getAddressCryptoFromAdmAddressADM(coin, admAddress) { try { @@ -35,7 +35,7 @@ module.exports = { }, /** - * Update all balances + * Update all coin balances * @return {Promise} */ async updateAllBalances() { @@ -52,7 +52,7 @@ module.exports = { }, /** - * Get last blocks numbers + * Get last block numbers for all coins * @return {Promise} */ async getLastBlocksNumbers() { @@ -118,9 +118,9 @@ module.exports = { }, /** - * Get Twitter accounts - * @param {String} message - * @return {void} + * Parse Twitter and Facebook accounts from a string + * @param {String} message Which may include accounts + * @return {Object} { notEmpty, twitterLink, twitterAccount, facebookLink } */ getAccounts(message) { const userAccounts = {}; @@ -150,7 +150,7 @@ module.exports = { }, /** - * Get Twitter account by matching regex and message + * Parse Twitter account by matching regex and message * @param {String} message * @return {Object} */ @@ -163,7 +163,7 @@ module.exports = { }, /** - * Get Twitter account by matching regex and message + * Parse link by matching regex and message * @param {String} message * @param {String} link * @return {String} @@ -183,7 +183,7 @@ module.exports = { }, /** - * Trims char + * Trims char both sides * @param {String} s * @param {String} mask * @return {String} @@ -199,7 +199,7 @@ module.exports = { }, /** - * Trims @ symbol in twitter name + * Trims @ symbol in Twitter name * @param {String} account * @return {String} */ @@ -208,7 +208,7 @@ module.exports = { }, /** - * Parses twitter name from link + * Parses Twitter account from link * @param {String} link * @return {String} */ @@ -223,7 +223,7 @@ module.exports = { }, /** - * Get twitter ID from link + * Get Twitter ID from link * @param {String} link * @return {String} */ From 4dd94b01d98cc085c267dba544e55999a138d64a Mon Sep 17 00:00:00 2001 From: adamant-al Date: Tue, 16 Aug 2022 20:26:32 +0300 Subject: [PATCH 55/55] fix: send ADM message in notify.js --- helpers/notify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/notify.js b/helpers/notify.js index 6bdc287..6d1401f 100644 --- a/helpers/notify.js +++ b/helpers/notify.js @@ -48,7 +48,7 @@ module.exports = (message, type, silent_mode = false) => { } if (adamant_notify && adamant_notify.length > 5 && adamant_notify.startsWith('U') && config.passPhrase && config.passPhrase.length > 30) { const mdMessage = makeBoldForMarkdown(message); - api.send(config.passPhrase, adamant_notify, `${type}| ${mdMessage}`, 'message'); + api.sendMessageWithLog(config.passPhrase, adamant_notify, `${type}| ${mdMessage}`); } } } catch (e) {