Steem-JS Following Feed Voting Bot, featuring html GUI and dynamic blacklist #2

Words
1581
Reading
8 min
Listen
Play
9y

The first tutorial of this voting bot containing the html GUI, CSS, and set-up JS:

Following Feed Voting Bot #1

What Will You Learn?

  • How to get and format your exact voting power based on time-stamps using Steem-JS.
  • How to obtain the full following list, including everyone ever followed, or currently muted using Steem-JS.
  • A process for setting up a function chain in JavaScript for handling Steem-JS requests that need to be done multiple times, and take time to return.
  • Error handling.

While this same functionality could be done in many ways, I'm going to show a functional chain/callback style which should be easy to digest. For this, I'll have two types of functions, all the ones that are part of getting to a final result ie. making a vote; and several ones that simplify actions or are used as an as-needed utility.

Requirements

  • The index.html, main.js, and main.css files covered in the first tutorial linked at the top.
  • Steem-JS
  • A text editor of your choice (I use Brackets).

Difficulty

  • Basic/intermediate

Tutorial Contents

The first thing I'm going to do is create a function to handle errors that could be returned by Steem-JS or the code in general. Since I'll be using quite a few functions/Steem-JS calls where errors are a possibility, I would want to see whats wrong in the case of one, and prevent it from stopping the bot entirely. Here is my simple error handing function:

main.js

function errorHandler(err) {
    totalErrs++;
    document.getElementById('TotalErrs').value = totalErrs;
    document.getElementById('Log').innerHTML = err;
    var staticErr = document.createTextNode(err),
        domErrLog = document.getElementById("ErrLog");
    domErrLog.appendChild(staticErr),
        domErrLog.insertAdjacentHTML('beforeend', '

'
); }

Within this function the first thing I do is increment the total error counter variable with totalErrs++ then log its value in the html input element. document.getElementById('Log').innerHTML = err; logs the error itself in our "Log" element to see if there is an error currently or if the error is stopping the progression of the code for some reason. In the last four lines of code, I log the error permanently adding to any previous errors to see everything that goes wrong. Although I will not be doing it, you could wrap all functions in try/catch to negate bigger issues such as:

function anyFunction() {
try {
 // stuff to do in function
} catch(err) {
 errorHandler(err);
}
}

The next function I will create is the first Steem-JS call which will begin the cascade towards making a vote. The main thing this function will be responsible for is determining the voting power you have and if it is high enough to continue down the chain. To start off, I'll use steem.api.getAccounts(['guest123'], function(err, result) {}); to get the account information, the name being in brackets because this function can return more than one account. What you'll notice is that this does not give your real-time voting power but the voting power of your last vote and the time-stamp at which it was done.

Because of this, I'm going to create a vpFormater utility function that takes those two pieces of information and calculates your current exact vp:

main.js

function vpFormater(vp, vptime) {
    let lastVoteTime = new Date(vptime + 'Z'),
        nowDate = new Date,
        addedVP = Number(0);
    lastVoteTime = lastVoteTime.getTime();
    nowDate = Date.UTC(nowDate.getUTCFullYear(), nowDate.getUTCMonth(), nowDate.getUTCDate(), nowDate.getUTCHours(), nowDate.getUTCMinutes(), nowDate.getUTCSeconds(), nowDate.getUTCMilliseconds());
    addedVP = Number(((nowDate - lastVoteTime) * (20 / 86400)) / 10);
    return ((addedVP + vp) > 10000) ? 10000 : (addedVP + vp); // 10000 = 100.00%
}

Here I pass in that latest vp and vp-time-stamp and create 3 variables, because the steem blockchain stores universal time, I use + 'Z' to tell javascript to keep that date as UTC. Then I create a variable which will automatically store the date of its creation and finally a number. To compare the dates easily, I use .getTime on the blockchain time to return a standardized number signifying the amount of milliseconds since 1970. The process is a bit different for the current time because it will not be in UTC. Date.UTC() also returns the milliseconds but we need to pass in the converted UTC pieces of the date in the correct order as shown. The next bit finds the vp accumulated since the time-stamp by dividing the 20% you regain per day by the amount of seconds in a day to get the amount regained per second, then multiply by the time since, then divide by 10 to standardize. Mine forgoes milliseconds so it won't be pinpoint accurate. I finally use a ternary operator to check if the added amount is over 10000 meaning you've let it stay at 100% for a bit and depending on that either return 10000 meaning 100% or return the calculated amount.

With that done, we can finally move to the complete checkVotingPower function which will be the start of our chain:

main.js

function checkVotingPower() {
    totalIts++;
    document.getElementById('TotalIts').value = totalIts;
    
    steem.api.getAccounts(['guest123'], function(err, result) {
        document.getElementById('TrueVPower').value = err ? 'getAccounts API error' : vpFormater(result[0].voting_power, result[0].last_vote_time);
        document.getElementById('VPower').value = err ? 'getAccounts API error' : result[0].voting_power;
        if (!err) {
            if (vpFormater(result[0].voting_power, result[0].last_vote_time) > document.getElementById('VPT').value) {
                document.getElementById('Log').innerHTML = JSON.stringify(result);
                
                let blackListArr = Array.from(document.getElementsByClassName('Blacklist'));
                blackListArr = blackListArr.map(function(elem) {
                    return elem.value;
                });
                console.log(blackListArr);
                getFollowingList('0', [], blackListArr);
            } else {
                document.getElementById('Log').innerHTML = 'Voting Power Below Threshold';
            }
        } else {
            errorHandler(err);
        }
    });
}

The first thing is simply logging the amount of times we've tried to make a vote since this is the function which will be called by our timer. Then I get my account information with the getAccounts Steem-JS function which returns an array of accounts but we only need one. The next two lines of code are ternary operators which determine what to display in the html GUI. by writing x = err ? a : b;, x (our html reference) will check if there is an err, and if there is one choose a, if not choose b. Anytime I use vpFormater(result[0].voting_power, result[0].last_vote_time) it means I want to use the calculated voting power from my utility function. The rest is simply checking if my calculated vp is above what I typed in the html input, logging the result by using JSON.stringify(result) because otherwise it'll log as "[Object]", making my blacklist array using the method from the first tutorial, and preparing to call the next function in our chain which takes 3 arguments.

Here is the completed function for obtaining the following list:

main.js

function getFollowingList(nextFollow, flistArg, blacklist) {
    var account = document.getElementById('Account').value;
    steem.api.getFollowing(account, nextFollow, null, 100, function(err, result) {
        if (!err) {
            document.getElementById('Log').innerHTML = JSON.stringify(result);
            let followsList = flistArg;
            
            for (let follows of result) {
                if (blacklist.indexOf(follows.following) == -1 && follows.what.length > 0 && follows.what[0] == 'blog') {
                    followsList.push(follows.following);
                }
            }
            
            if (result.length == 100) {
                getFollowingList(result[result.length - 1].following, followsList, blacklist);
            } else {
                console.log(followsList);
                //processStates(followsList, blacklist);
            }
        } else {
            errorHandler(err);
        }
    });
}

As you can see from our checkVotingPower function, the 3 arguments is where we say from who to start from since the most we can ask for at a time is 100. I first pass in '0' as a string character because it should be one of, if not the first character any account name can begin with, correct me if I'm wrong. Imagine an alphabetic index with numbers categorized first, if we input 'b', then my returned list would start with "biddle". Next is a transient value flistArg which represents the compounded list of follows we are creating here, an empty array at first. Instead of making it global, I simply re-pass the value as needed while one function in the chain hosts it. Lastly is the blacklist we created in the checkVotingPower function. Now, the null value I'm using may be for the type of follow such as if they are muted or not, but as I'm not sure I use null, and determine following type based on the what array returned per following by the Steem-JS function which could be:

  • empty likely meaning unfollowed.
  • "ignore" likely meaning muted, and
  • "blog" likely meaning standard follow.

So, first I log the result and you could log whatever portion you wanted as long as JSON or js objects are stringified. I created a reference to the current compounded list to keep things clean, and then I use for (let follows of result) to iterate over each value in an array with the follows name being arbitrary and acting as a reference for each entry in the returned array. Within that loop, I check each follow if they equal one of our blacklist names with .indexOf which will return -1 (ie. could not find an index because it didn't exist in array) if the name is not there which is good. Coupled with that check, I also check if the length of the "what" array is not zero meaning the person is not unfollowed, and if the first entry in the array is "blog" meaning a standard follow. If those criteria are met, the name gets added into our followsList array with followsList.push(follows.following); again with "follows" just being one of the entries in the "result" array.

Finally, I check if the result from the Steem-JS function has a length of 100 which we requested. If it does, then it's likely that there is more follows we do not yet have. In that case I chain the same function again passing the 99th entry as the starting point for the next call. Since arrays start at 0, 99 would be the last entry, so then result.length would return 100 and -1 would give 99 the final entry. I also pass the followsList as it is for more entries to get added to it, and the blacklist because the information is not global and needs to be re-passed and re-hosted essentially.

If the resulting array is less than 100, then the else statement will get activated and the recursion/self-chaining ends. Think of the entire getFollowing function as a while loop that waits for a specific thing to change before ending the looping, just a bit fancier. For this tutorial I'll end it here but the next thing I'll be demonstrating is how I process the feed which can be done in several ways. For now, at the end of the following chain, console.log your followsList to see how things went and if it correlates with the people and number of people followed by several accounts. If there are a few more in the array than it says on Steemit, don't worry. Those are replicated names that get returned twice because they were used as the starting point for the next request. This even happens several times per single call for some reason based on how Steem-JS works. Because of the way I process my followsList though, repeated entries do not matter so I will simply leave them in.

Postface

Hope you enjoyed this tutorial and learned something new! The next one should be out soon and will finish this little series at least in terms of base functionality. I may continue on with things like trying to analyze the best votes or adding trails and such. Have a good one!



Posted on Utopian.io - Rewarding Open Source Contributors

Steem-JS Following Feed Voting Bot, featuring html GUI and dynamic ... | Ecency