Bot-Tutorial: Automatically create Twitter tweets for new Posts (nodeJS, steem-js, bitly, twitter)

in #steemdev7 years ago

After the great response to my first steemit bot tutorial, I want to present you a second bot implementation based on "steem-js".

You can find my first bot tutorial here:
https://steemit.com/steemdev/@schererf/tutorial-create-a-bot-that-sends-you-an-email-on-received-votes-and-comments-nodejs-steem-js-nodemailer

The Mission

The requirements for the bot:

  • Listen the blockchain for new posts of a specified author
  • Shorten the posts URL using BITLINK
  • Create a new tweet on Twitter for the new post. The text of the Twitter tweet should include the title of the post, the BITLINK URL, the tag "steemit" and all tags of the post. The maximum length of a Twitter tweet is 140 characters. If the combined text is longer than 140 characters, the title of the post is shortened using "...".

The idea for this bot is originally from @pollux.one, many thanks for that!

System Requirements

Visual Studio Code

For developing the bot I used the free Code Editor "Visual Studio Code".
You can download it here: https://code.visualstudio.com/

nodeJS

As the runtime system for the bot I used "nodeJS" (LTS version).
You can download nodeJS here: https://nodejs.org

Write the bot

Preparations

First choose a folder on your filesystem where the source-code file of your new bot should be saved. Then install the required npm packages (nodepackage manager):

  1. Open the command line console
  2. Change to the folder of your bot
  3. Install npm packages
npm install steem --save
npm install bitly
npm install twitter

Create the bot file "steemBitlyTwitterBot.js"

Add a new file "steemBitlyTwitterBot.js" to your folder and open it with Visual Studio Code.

Here is the complete source-code of the bot. Please copy it to the "steemBitlyTwitterBot.js".

/*
** steemBitlyTwitterBot v0.0.1, by @schererf
** Feel free to use and modify.
*/

// INITIALIZE
var config = {
    bitly : { access_token: "todo" },
    twitter : {
        consumer_key: 'todo',
        consumer_secret: 'todo',
        access_token_key: 'todo',
        access_token_secret: 'todo'
    }
}

// REQUIREMENTS
// nodeJS: https://nodejs.org
// steem-js: npm install steem --save
// bitly-js: npm install bitly
// twitter-js: npm install twitter
var steem = require('steem');
var Bitly = require('bitly');
var bitly = new Bitly(config.bitly.access_token);
var Twitter = require('twitter');
var twitterClient = new Twitter({
    consumer_key: config.twitter.consumer_key,
    consumer_secret: config.twitter.consumer_secret,
    access_token_key: config.twitter.access_token_key,
    access_token_secret: config.twitter.access_token_secret
});

// FUNCTIONS
// helper function to identify if memo has a valid steemit link 
// (thanks to npm steem-bot for function 'isValidSteemitLink')
function isValidSteemitLink(link) {
    return link.indexOf("steemit.com") > -1;
}

/** (thanks to npm steem-bot for function 'extractUsernameFromLink')
 * Should input a full steemit article link and return the username of the author
 * @param {string} steemitLink 
 */
function extractUsernameFromLink(steemitLink) {
    if (isValidSteemitLink(steemitLink)) {
        const usernamePos = steemitLink.search(/\/@.+\//);
        if (usernamePos === -1) return;

        const firstPart = steemitLink.slice(usernamePos + 2); // adding 2 to remove "/@"
        return firstPart.slice(0, firstPart.search('/'));
    }
 }
  
/** (thanks to npm steem-bot for function 'extractPermlinkFromLink')
 * Should input a full steemit article link and return the permlink of the article
 * @param {string} steemitLink 
 */
function extractPermlinkFromLink(steemitLink) {
    if (isValidSteemitLink(steemitLink)) {
        const usernamePos = steemitLink.search(/\/@.+\//);
        if (usernamePos === -1) return;

        const firstPart = steemitLink.slice(usernamePos + 1); // adding 1 to remove the first "/"
        return firstPart.slice(firstPart.search('/') + 1).replace('/', '').replace('#', '');
    }
}

function twitterUpdateAsync(tweetMessage){
    return new Promise(function (resolve, reject) {
        try {
            twitterClient.post(
                'statuses/update', 
                { status: tweetMessage }, 
                function(error, tweet, response) {
                    if (error) { reject(error); }
                    console.log("> Tweet created for user '" + tweet.user.screen_name + "':\n" + tweet.text);
                    resolve(response);
                });
        } catch(ex) {
            console.log("> Error occured on creating Tweet:\n" + ex);   
            reject(ex);             
        }
    });
}

function getTwitterTextAsync(postData, shortUrl){
    return new Promise(function (resolve, reject) {
        try {
            // get list of tags
            var tagsText = "#steemit";
            var jsonMetadata = JSON.parse(postData.json_metadata);
            var tags = jsonMetadata.tags;
            for(tagIndex=0; tagIndex<tags.length; tagIndex++){
                tagsText += " #"+tags[tagIndex];
            }

            // get title
            var titleText = postData.title;

            // build result text
            const MAX_TWEET_LENGTH=140;
            const TWEET_URL_LENGTH=23;
            const NEWLINE = "\n";
            const TITLE_SHORTEN_TEXT = "...";
            var remainingLength = MAX_TWEET_LENGTH-NEWLINE.length-NEWLINE.length-tagsText.length-TWEET_URL_LENGTH;
            var tweetText = 
                ((titleText.length > remainingLength) ? titleText.substring(0,remainingLength-TITLE_SHORTEN_TEXT.length)+TITLE_SHORTEN_TEXT : titleText) + 
                NEWLINE +
                shortUrl + 
                NEWLINE +
                tagsText;
            console.log("> Tweet text prepared:\n" + tweetText);
            resolve(tweetText);
        } catch(ex) {
            console.log("> Error occured on preparing Tweet text:\n" + ex); 
            reject(ex);
        }
    });
}

function checkBitlyTwitterAndLog(steemLink){
    var author = extractUsernameFromLink(steemLink);
    var permlink = extractPermlinkFromLink(steemLink);
    console.log("> Getting post from steemit and creating a short link using BITLINK...");
    Promise.all(
        [
            steem.api.getContentAsync(author, permlink),
            bitly.shorten(steemLink)
        ]
    ).then( (resultArray) => {
        var postData = resultArray[0];
        var shortUrl = resultArray[1].data.url;     
        return getTwitterTextAsync(postData, shortUrl); }
    ).then((twitterText) => { return twitterUpdateAsync(twitterText); }
    ).catch(function (err) {
        console.log("> Error: " + err);
    });
}

function listenForPosts(pAuthor){
    steem.api.streamOperations((err, operations) => {
        if (err) {
          throw(new Error('Something went wrong with streamOperations method of Steem-js'));
          console.log(err);
        }
          
        const opType = operations[0];
        const op = operations[1];   
        if (opType === "comment" && op.author === pAuthor && op.parent_author === ""){
            // post received
            var url = "https://steemit.com/"+op.parent_permlink+"/@"+op.author+"/"+op.permlink;
            console.log( "Post received @" + op.author + "/" + op.permlink + "\n" + url);
            checkBitlyTwitterAndLog(url);
        } 
    });
}

// MAIN
// check if program was called with correct command line arguments
if (process.argv.length === 3 && isValidSteemitLink(process.argv[2])) {
    // create tweet for specified steemit URL
    var steemLink = process.argv[2];   
    checkBitlyTwitterAndLog(steemLink);
} else if (process.argv.length === 3 && process.argv[2][0]==="@") {
    // start the bot that listens for new posts of the specified author
    var author = process.argv[2].substring(1);
    console.log("Listening for posts of @" + author)
    listenForPosts(author);
} else {
    console.log("usage to create single tweet:                     process.argv[1] https://steemit.com/test/@author/permlink-post");
    console.log("usage to start bot to tweet new posts of a user:  process.argv[1] @author");
}

Example Visual Studio Code

steemBitlyTwitterBot Visual Studio Code.jpg

Configuration by replacing placeholders

No you have to replace the placeholders in the source-code marked with "todo".

STEEM

For accessing the Steemit post no configuration is needed, because the bot only reads data from the blockchain.

BITLINKS

Placeholder NameUsage
config.bitly.access_tokenThe BITLINKS access token

You can register an OAuth BITLINKS application here: https://bitly.com/a/oauth_apps

TWITTER

Placeholder NameUsage
config.twitter.consumer_keyThe Twitter consumer key
config.twitter.consumer_secretThe Twitter consumer secret
config.twitter.access_token_keyThe Twitter access tokenkey
config.twitter.access_token_secretThe Twitter access token secret

Your Twitter applications are managed under https://apps.twitter.com/.
If not already done, create a new app and copy & paste the required keys from "Key and Access Tokens".

Run the bot

To run the bot using node you have to do the following steps:

  1. Open the command line console
  2. Change to the folder where your bot is present
  3. Start the bot using nodeJS

You can use this nodeJS bot in two different ways:

  1. As a bot that listens for new posts of the specified user
  2. To create a Twitter tweet for a single specified Steemit post

Run bot that listens for new Steemit posts

node steemBitlyTwitterBot.js @author

Example:
node steemBitlyTwitterBot.js @steem.test

steemitBitlyTwitterBot Author.jpg

Result on Twitter:
steemitBitlyTwitterBot Author Twitter Result.jpg

Run once to create a tweet for a single Steemit post

node steemBitlyTwitterBot.js steemitPostUrl

Example:
node steemBitlyTwitterBot.js https://steemit.com/steemdev/@schererf/tutorial-create-a-bot-that-sends-you-an-email-on-received-votes-and-comments-nodejs-steem-js-nodemailer

steemitBitlyTwitterBot single post.jpg

Result on Twitter:
steemitBitlyTwitterBot single post Twitter Result.jpg

Enjoy the new bot.
Please give me feedback if this tutorial was helpful for you.

Thanks for reading and watching,
@schererf

Sort:  

Great work! I forget to post my stuff to Twitter so many times. THX for working it out! Works perfectly for me!

100% and Resteemed!
(thx for mentioning that this was my idea ;-) )

You're welcome and thanks for sharing the idea!
I'm glad that my tutorial is helpful for you...

At last I have found a genius, will be following you like a bee after honey, I actually understand your lesson which is a first for me with bots

Thats great i like it very nice post.

Valuable post you sharing @schererf. I want to more studying this tutorials. So i saved this article for further learning
Upvoted/ Resteemed

@schererf,
This is very useful article for me! Do you have any idea about how to write a bot system for STEEMIT? Thank you very much for sharing such great post with us!

Cheers~

Now that is something really cool :D

thanks for the information, very helpful.

Thanks to you @schererf.

Calling @originalworks :)
img credz: pixabay.com
Steem is behaving weird in low voting percentages, so actual votes can diverge by up to 1 voting percent point.
Nice, you got a 3.0% @minnowbooster upgoat, thanks to @schererf
Want a boost? Minnowbooster's got your back!

The @OriginalWorks bot has determined this post by @schererf to be original material and upvoted it!

ezgif.com-resize.gif

To call @OriginalWorks, simply reply to any post with @originalworks or !originalworks in your message!

To enter this post into the daily RESTEEM contest, upvote this comment! The user with the most upvotes on their @OriginalWorks comment will win!

For more information, Click Here! || Click here to participate in the @OriginalWorks sponsored writing contest(125 SBD in prizes)!!!
Special thanks to @reggaemuffin for being a supporter! Vote him as a witness to help make Steemit a better place!

Coin Marketplace

STEEM 0.18
TRX 0.16
JST 0.029
BTC 76024.68
ETH 2926.23
USDT 1.00
SBD 2.60