🤖 TUTORIAL - Statistics Responder - Building bots With steem-js #8

in #utopian-io7 years ago (edited)

bot-tut-8.png

This tutorial is part of a series on ‘How To Build Bot’s Using Steem-JS Open Source Library’ it aims to demonstrate practical applications for bots and demystify the code used to create them.

Previous Tutorials

The completed bot code is here - I know some people find it easier to see everything in one block and follow along that way. ⬅️

Outline 📝

This tutorial is looking at creating a responder bot. One of the most popular responderbots is origionalworks another well known one is treeplanter there are likely many others.

When mentioned a responder bot will perform an action and respond with the results in a comment. In this tutorial we’ll create a responder bot that will calculate statistics about the account who requested it.

Screen Shot 2018-01-27 at 16.34.51.png
I have used my steemversary account for testing this bot.

Requirements

  • A plain text editor (I use atom.io )
  • Your private posting key - This is found in your wallet on the permissions tab on steemit.com, click ‘show private key’ (remember to keep this safe). This is what allows the bot to reply when mentioned.

Difficulty

Intermediate - Please run through all other tutorials in the series to better understand what we’re working on.

(I appreciate feedback on the teaching level).

Learning Goals

  • Replying to @ mention with a broadcast.comment() using Steem-js
  • Look at the getState() function of Steem-js
  • Use Javascript Array Reduce function to create averages

Step 1 - Setup & Recap 💻

The starting point for this bot will be where tutorial 5 left off. We’ll strip out the email code and replace it with out statistics calculation and comment reply. This bot will once again run in the browser but there is no reason why it couldnt also run with node.js as in the previous tutorial.

starting point using code created in bot #5

Fill in your details above in the NAME & KEY variables. When you run the code above notice the console output notifying of checking and receiving mentions. Send yourself a mention to make sure it’s running correctly.

Screen Shot 2018-01-27 at 15.30.29.png

Step 2 - Get stats about Author 💻

We’re going to calculate three stats about an author, stats that do not exist by just querying the blockchain. Looking at up to the last 20 author posts we’ll calculate -

  • Average Votes Per Post
  • Average Replies Per Post
  • Average Post Word count

this could be the start of an interesting responder bot, I could imagine it including many more stats.

If people find this idea interesting let me know and I’ll make it a real robot

These stats don’t exist on the blockchain. The getState() function allows us to retrive a number of posts from the blockchain these can be related to an author if we used getState(‘sambillingham’) or a category getState(‘trending’), it differers from getDiscussionsBy() in that it give data about the replies to post also and can be used to look into the comment section. First try that and look at the results

steem.api.getState(`/@sambillingham/`, (err, result) => {
    console.log(err, result)
})

Screen Shot 2018-01-27 at 15.47.13.png

If you click the dropdown you’ll notice we can view all of an accounts latest content. Each post also contains individual data, comments, replies, rewards etc. The first thing I noticed was that the data is not in the friendliest format. The content is an object, where each property of the object is another object of post data.

When working with data my first aim is to get it’s format close to what I want to work with. I this case An Array where each item in the array relates to stats about each post will be easier to work with than objects in objects. Loop over the object and reformat the data selecting only the items we’re interested in.

let resultsArray = [];
for ( post in result.content ){
    resultsArray.push({
        votes: result.content[post].net_votes,
        replies: result.content[post].children,
        length: result.content[post].body.split(' ').length
    })
}
  1. Initialise an empty Array
  2. Loop over result content object
  3. for each post in content create a new object containing votes, replies and length
  4. split the body content at spaces to get word count
  5. add each item to the new array

Output of the new Array.

Screen Shot 2018-01-27 at 15.48.06.png

The data we’ve now constructed contains only what we’re interested in and will be far easier to manage.

Next we’ll create averages. The Array reduce function works perfectly for our needs here. Reduce takes a list of numbers in an array and gives a single number, this might be used for finding minimum, maximum, total etc. Total we’ll use it to find an average. When looking online you’ll often see .reduce((a,b) => a + b, 0) which looks confusing until you realise a represents the total and b represents the current item, there is no reason why you couldn’t write it out like below.

resultsArray.reduce((totalVotes,currentItem) => total + currentItem.votes, startingNumber) / resultsArray.length

We only use a/b or x/y variables names to save keystrokes. Reduce will take the the total so far add on the current item and move along the array. Divide by the length of array aka number of posts to get an average.

For all of our stats we would use reduce for each,

let statsData = {
    averageVotes: resultsArray.reduce((a,b) => a + b.votes, 0) / resultsArray.length,
    averageReplies: resultsArray.reduce((a,b) => a + b.replies, 0) / resultsArray.length,
    averageLength:  resultsArray.reduce((a,b) => a + b.length, 0) / resultsArray.length
}

Leaving us with a single object contains averages for votes, replies and word count.

Step 3 - Reply With A Comment 💻

steem.broadcast.comment() does what you’d expect sending comments out over the blockchain. Simply fill in the required fields in the correct order

steem.broadcast.comment(
            ACCOUNT_KEY, 
            AUTHOR,
            AUTHOR_PERMLINK,
            ACCOUNT_NAME,
            NEW_PERMLINK,
            TITLE,
            COMMENT_CONTENT,
            CUSTOM_JSON,
            function(err, result) {
              console.log(err, result);
            }
          );

ACCOUNT_KEY & ACCOUNT_NAME are the posting account, we added at the top of our code.

AUTHOR & AUTHOR_PERMLINK are who we’re replying to and will be part of the transaction data we retrieve when listening for the mention.

NEW_PERMLINK is a random alphanumeric string that we’ll need to generate to make sure our comment has a unique url

TITLE & COMMENT_CONTENT are fairly self explainitary. Note the Title is only viewable when accessing the comment directly not when looking at comment in a post.

CUSTOM_JSON is used for specifying tags and the app we’re posting from.

Create a sending function that has access to the transaction data and the statistics data we’ve just calculated. We’ll use this as part of those functions in just a moment.

 function reply(txData, statsData){}

Create a comment for our bot to send back to the user.

function reply(txData, statsData){
    let comment = `I'm the statistics responder bot, reporting as requested.<br><br>Average Votes Per Post: ${statsData.averageVotes} <br>Average Replies Per Post: ${statsData.averageReplies} <br>Average Post Word count: ${statsData.averageLength} <br>`
}

Fill in the sending data

  steem.broadcast.comment(
            ACCOUNT_KEY,
            txData.author,
            txData.permlink,
            ACCOUNT_NAME,
            randomString(),
            '',
            comment,
            { tags: ['stats'], app: 'statsbot' },
            function(err, result) {
              console.log(err, result);
            }
          );

We can create a randomString() function that generates a unique string used for our url.

function randomString() {
    let string = ''
    let allowedChars = "abcdefghijklmnopqrstuvwxyz0123456789";
    for (var i = 0; i < 32; i++){
        string += allowedChars.charAt(Math.floor(Math.random() * allowedChars.length));
    }
    return string + '-post';
}
  1. initialise an empty string
  2. create a set of characters that are permlink valid within the steem blockchain
  3. create a loop that will be 32 character long.
  4. for each step in the loop add choose a random character for the available list and add it to our new string
  5. after we have 32 characters in the string return it as the result

Step 4 - bring it all together

With the statistics generated and the reply function setup we need to combine this with the transaction stream listening for mentions.

Start the Stream. When a mention is found pass the transaction data through to the statistics function.

steem.api.streamTransactions('head', function(err, result) {
    let txType = result.operations[0][0]
    let txData = result.operations[0][1]
    let includesMention = checkContentForMention(txType,txData)
    if (includesMention) {
        console.log('MENTION FOUND: ', txData)
        getStats(txData)
    }
});

Generate statistics and pass transaction data and generated statics through to the reply function.

function getStats(txData){
  steem.api.getState(`/@${txData.author}/`, (err, result) => {
    let resultsArray = [];
    for ( post in result.content ){
      resultsArray.push({
        votes: result.content[post].net_votes,
        replies: result.content[post].children,
        length: result.content[post].body.split(' ').length
      })
    }
    let statsData = {
      averageVotes: resultsArray.reduce((a,b) => a + b.votes, 0) / resultsArray.length,
      averageReplies: resultsArray.reduce((a,b) => a + b.replies, 0) / resultsArray.length,
      averageLength:  resultsArray.reduce((a,b) => a + b.length, 0) / resultsArray.length
    }
    reply(txData, statsData)
  })
}

Veiew the full code below if putting it all together is a little confusing in written form

Here’s the full code 🤖

The Bot is setup. When the bot sees a mention with the account name it will calculate the stats and send a reply. There are a lot of opportunities with these kind of bots. Be careful to make sure your bot adds value, nobody likes spammy bots in the comments!

While outside of the scope of this tutorial it might be interesting to have this sort of bot also upvote if you statistics increase since the previous day or week.

Let me know what you think of this robot tutorial.

Other Posts in This series



Posted on Utopian.io - Rewarding Open Source Contributors

Sort:  

Thank you for the contribution. It has been approved.

You can contact us on Discord.
[utopian-moderator]

Good to see you're a mod now Amos! 💯

Thanks for reviewing the tutorial.

Hé Sam,
Just wanted to thank you very much for this great series of tutorials!
I did code a bit years ago, a little bit jquery when developping my own website, but I'm not really great at it...
But with your wellwritten tutorials I succeeded in writing my first little bot :-)
Keep up the good work please, it's really appreciated!
Cheers,
Eric AKA Pixelfan

Thank you @picelfan! I really appreciate the comment. It's hard to know how useful these tutorials are as I don't receive much feedback about them. It's good to know people like yourself are finding them useful. I'll keep making some more. Let me know if there is anything specifically I can help with.

yes, steemit is not the best place to get feedback... but so are most social media I think ;-)
Anyway, I keep following you and try out your tutorials (not all of course :-)
And if I have a question I will ask... but don't worry, I'm used to try and try and try to get something working before I ask... it's part of the fun and learning curve...

Thanks for sharing Sam.

Let me know what you think of this robot tutorial.

Well, awesome as always :)

Thanks, Jo aprecciate the continued support! 👊

Thanks for your tutorials, Sam. I was just wondering how to prevent the bot from commenting more than once per 20 seconds?

hey @svosee you'll want to add a timeout so instead of sending the reply function instantly it will wait. The time value is in milliseconds.

setTimeout(function(){
    reply(txData, statsData)
}, 20000)

Let me know if i can help with anything else

I guess I was making things too complicated because I was thinking you would have to build some kind of waitlist if you're mentioned like every few seconds. But I'll give the timeout a try. Thanks!

A waitlist would probably be a good idea, you could separate out the creating/sending of messages but I try to keep it as simple as possible for the tutorials. Shout if anything doesn't work or if I can help out further.

I would suggest using some sort of queue so that it is more robust and you have control.

I like your post as succesful as always friend

Hey @sambillingham I am @utopian-io. I have just upvoted you!

Achievements

  • You have less than 500 followers. Just gave you a gift to help you succeed!
  • You are generating more rewards than average for this category. Super!;)
  • Seems like you contribute quite often. AMAZING!

Suggestions

  • Contribute more often to get higher and higher rewards. I wish to see you often!
  • Work on your followers to increase the votes/rewards. I follow what humans do and my vote is mainly based on that. Good luck!

Get Noticed!

  • Did you know project owners can manually vote with their own voting power or by voting power delegated to their projects? Ask the project owner to review your contributions!

Community-Driven Witness!

I am the first and only Steem Community-Driven Witness. Participate on Discord. Lets GROW TOGETHER!

mooncryption-utopian-witness-gif

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

Thanks for sharing Sam(@sambillingham). The rewards on your post must convey that people are finding them useful ;)

I built a utility bot using a similar approach. But whenever the post/comment is updated the bot keeps replying as I an not able to differentiate between creation/updation. Is there a way I can identify this directly from steem.api.streamTransactions function without getting the details of the post in a separate API call?

hey @gokulnk I've not actually looked into this so I'm not 100% sure. Is there a creation or update date in the data you could compare? I believe what other tools do is keep their own database of transactions, that way you can check the if the ID or permlink has been used before. if you don't want to setup and keep a database you could use http://steemdata.com instead.

I used
steem.api.getContent(txData.author, txData.permlink, function(err, result) { if (result.active != result.created) {

But I would have preferred if I could have avoided the additional API call.

hey @sambillingham do you, by any chance, have steemit.chat? I have a special idea for an awesome bot that I would love to discuss with you!

You could make it public and other devs also might be interested in taking it up. I am busy for the next couple of days but would love to take a look at it after 15 days if you can share your idea.

Coin Marketplace

STEEM 0.19
TRX 0.16
JST 0.033
BTC 64071.08
ETH 2763.75
USDT 1.00
SBD 2.66