Coding a Random Upvote Script
Motivation
In this post, I want to share my exploration of the Steemit Api, using the JavaScript library, steem-js . I have also been very eager to try the new ES8 feature, async/await function, which allows us handle Promises, more gracefully. This is my second Steemit API post and I already feel like I have made progress and hopefully laid a solid foundation towards building a larger scale application in the coming weeks or even months. If you want to skip this tutorial and explore the code directly, you can do so here
What Will I Learn
By the end of this tutorial, you will be able to:
- Extract a list of your followers
- Extract a list of posts one of your followers has made
- Upvote one of the extracted posts
- Use promises instead of callbacks when making API invocations
- use async/await functions to wrap promises and make the code easier to understand
Requirements
- Plain text editor ( try Atom )
- NodeJs installed on your machine ( Node )
- JavaScript syntax
- Some basic understanding of what JavaScript promises are
Difficulty
Due to the use of promises, which in itself are a non-trivial concept, as well as async functions, I will mark this tutorial as intermediate . However, I have picked an easy use case in order for beginners to jump on board if they wish to.
What does my project actually do
My script is able to extract a list of followers and than pick one at random. We can limit the number of followers we wish to query. I retrieve the account history for this follower and select posts made (not comments or replies) and than I upvote this post. You can easily modify my code to upvote a specific post or to select a comment to upvote.
Step1 - Workspace Setup
You will need to install NodeJs on your machine, then clone this repository. Run npm install and node upvote and to run the script. If you wish to start from scratch and code this as you read through my tutorial, then create a a new directory and run from your command line:
- create a new package.json file, copying the one from my repo, in a new directory
- run npm install
- create a new .js file, name it as you wish. This is where our new script will live
- to test your work, run: node new_file_name
Step2 - Imports and project constants
The only import needed for this tutorial is the steem-js library. Import it by adding var steem = require('steem');
in the first line of your script. For this project, we define four constants, namely your account name, your private posting key, and two limits, one for the amount of followers we want our API to fetch and one for the recent account history transactions we wish to process.
// private posting key, remember to keep this a secret
const PASSWORD = ''
// your account name
const NAME = ''
//account history tranzaction limit
const TRANZACTION_LIMIT = 100
const FOLLOWERS_LIMIT = 1000
Step3 - API invocation
We will make three API calls in this script. Firstly, we call the API to fetch our followers, then we parse their recent account history and lastly we use the API to broadcast a vote, using our private posting key. I have encapsulated these API invocations in a variable, which has three member functions, one for each call. Every API invocation will return a promise. Here is the code:
// every steemit API invocation will return a promise
var apiInvocations = {
followerRequest: function(user, limit) {
return steem.api.getFollowersAsync(user, '', 'blog', limit)
},
accountHistoryRequest: function(user, limit) {
return steem.api.getAccountHistoryAsync(user, 99999999, limit)
},
broadcastVote: function(postingKey, name, post, weight) {
return steem.broadcast.voteAsync(postingKey, name, post.author, post.permlink, weight)
}
}
As you can see, by adding "Async" to the API methods call, we get back a promise, as to not pass a callback function as we would normally do when calling a service. This is very convenient, as we can avoid the so called callback hell issue that plagues a lot of JavaScript code. Having event just two or three callbacks nested, can make the code look very unreadable.By contrast, promises are objects that represent the eventual completion (or failure) of an asynchronous operation, and its resulting value. I will not explain this concept in this tutorial, please read about it, as they are a very powerful tool each JavaScript developer should be familiar with.
Step4- Building our list of followers
By doing console.log on the API responses, we can see what kind of data we receive and how to design a function that will parse the response and extract the data we require. When calling the steemit service getFollowers, we receive an array of objects. We require the field follower from each of this object. Hence, a function that builds an array of followers will look like this:
function(apiResponse) {
for(var i = 0; i < apiResponse.length; i++){
this.followers.push(apiResponse[i]['follower'])
}
}
This function is a 'pushes' new followers to this.followers, which is an empty list I have declered inside a variable called followers. To get a random follower, we declare another function, called getRandomFollower:
getRandomFollower: function() {
if (this.followers.length === 0) {
return ''
}
return this.followers[Math.floor(Math.random() * this.followers.length)];
}
Here, Math.floor(Math.random() * this.followers.length) just selects a random index for our array of followers. If the array is empty, we just return an empty string.
Step5 - Extract posts from recent transactions
Just like in my last tutorial, we will use the getAccountHistory in order to see transactions registered in the blockchain for a user. While last time we were interested in parsing the transactions in order to see accounts that upvoted our posts recently, this time we are interested in extracting posts made by our random follower. By analyzing the format of our API call data, we are able to extract the posts in an array of objects:
var posts = {
postData: [],
populatePosts: function(apiResponse, randomFollower) {
for(var i=0; i< apiResponse.length; i++) {
var transactionOperation = apiResponse[i][1]['op'];
if (this.isPost(transactionOperation, randomFollower)) {
this.postData.push(transactionOperation[1])
}
}
},
isPost: function(operation, randomFollower) {
return operation[0] === 'comment' && operation[1]['author'] === randomFollower && operation[1]['parent_author'] === ''
},
getRandomPost() {
if (this.postData.length === 0) {
return ''
}
return this.postData[Math.floor(Math.random() * this.postData.length)];
}
}
The method populatePosts just appends a new post to postData each time the function isPost returns true. Our API call returns a list of transactions. We scan each transaction in a for loop and check the fields we are interested, in order to see if the operation our random follower performed was indeed a new post. The method getRandomPost just returns one post at random.
Step6 - Vote, Vote, Vote
We now have everything we need in order to vote. The broadcast.vote method in the steemit api, requires the voter(that's you), your private posting key (you can get that from your permissions breadcrumb in your wallet), the post author and permlink as well as voting weight you wish to allocate. The author and permink are fields in the JSON object we have extracted previously (our random post). The voting weight is a number from 0-10000. In this scale, 10000 means 100% voting weight. As I have less than 500 SP, means that all my votes have 100% voting weight., so this parameter is 10000 in my case. However, for you guys that have more, you should select a weight you are comfortable with.
Step7 - Putting it all together
For our big finale, we create an async function called start(). Here, we will call our API methods and await their fulfilled response. It looks like this:
async function start() {
try {
const apiFollowersResponse = await apiInvocations.followerRequest(NAME, FOLLOWERS_LIMIT)
// use apiFollowersResponse as you wish
}
}
catch (rejectedValue) {
// …
}
}
The apiFollowersResponse constant is data our API has fetched, no more callbacks, passed directly to our service call needed, in order to handle data. Using the await keyword in an async function waits, as the name suggests, for the API call to finish and return a fulfilled response. The start method in our script, will call the API data, sequentially. We will use the parsing methods we have discussed previously to process this data and build towards broadcasting a vote. The full start method looks like this:
async function start() {
try {
// private posting key
const PASSWORD = ''
// your account name
const NAME = ''
//account history tranzaction limit
const TRANZACTION_LIMIT = 100
const FOLLOWERS_LIMIT = 1000
const apiFollowersResponse = await apiInvocations.followerRequest(NAME, FOLLOWERS_LIMIT)
followers.populateFollowersList(apiFollowersResponse)
const randomFollower = followers.getRandomFollower()
console.log('random follower has been located' + randomFollower)
const apiAccountHistoryResponse = await apiInvocations.accountHistoryRequest(
randomFollower, TRANZACTION_LIMIT)
posts.populatePosts(apiAccountHistoryResponse, randomFollower)
const randomPost = posts.getRandomPost()
if (randomPost === '') {
console.log("Try running the script again, no post was found for limit " + TRANZACTION_LIMIT)
return
} else {
console.log("A random post was found, now voting: " + JSON.stringify(randomPost))
upvoteRandomPost(PASSWORD, NAME, randomPost['author'], randomPost['permlink'])
const voteResult = await apiInvocations.broadcastVote(PASSWORD, NAME, randomPost, 10000)
console.log(voteResult)
console.log("vote success")
}
}
catch (rejectedValue) {
// …
}
}
As you can see, there are no callbacks, we just receiva data from one API call, process it and pass it to the next API call. This makes the logic easier to follow in my opinion. As we are dealing with a a probabilistic feature, our script may not yield a valid post to upvote. This could be because the random follower we have selected has not posted anything new withing the transaction limit parameter we have defined.
Conclusion
The use case this script solves may not be the most useful thing in the world, but understanding the concepts we went through is important. We have learned how the async/await mechanism work and hopefully we will be able to avoid callback hell in our next projects. We have also learned to broadcast a vote, get a list of followers and a list of recent posts. All this little bits and pieces can help build towards something greater.
Curriculum
This is my previous Steemit API example. While you don't need to read it in order to understand this new tutorial, you may find it interesting. You can compare for example the callback vs promise approaches.
Posted on Utopian.io - Rewarding Open Source Contributors
Wow, that is intense. I wish I had these type of skills instead of wasting my time in college classes no one ever uses. Awesome tutorial
Thank you, I am glad you enjoyed it! If you feel like you are not getting the maximum from your collage classes, you can try some of the many Udemy, Coursera programming courses out there!
For sure, I have tried some of the MOOCs through Coursera. Its definitely good for some of the basics of a subject that you maybe interested in. I haven't tried Udemy yet, I'll check it out. Thanks.
Hey @prometheus21 I am @utopian-io. I have just upvoted you!
Achievements
Suggestions
Get Noticed!
Community-Driven Witness!
I am the first and only Steem Community-Driven Witness. Participate on Discord. Lets GROW TOGETHER!
Up-vote this comment to grow my power and help Open Source contributions like this one. Want to chat? Join me on Discord https://discord.gg/Pc8HG9x
Great post. :D I will read it again.
Thank you for the contribution. It has been approved.
You can contact us on Discord.
[utopian-moderator]
Your Post Has Been Featured on @Resteemable!
Feature any Steemit post using resteemit.com!
How It Works:
1. Take Any Steemit URL
2. Erase
https://
3. Type
re
Get Featured Instantly – Featured Posts are voted every 2.4hrs
Join the Curation Team Here | Vote Resteemable for Witness
nice
You got a 1.40% upvote from @minnowvotes courtesy of @prometheus21!
This post has received a 3.46 % upvote from @boomerang thanks to: @prometheus21