hckr.fyi // thoughts

How to Create a Twitter Bot with the Microsoft Bot Framework

by Michael Szul on

My involvement with the Bot Builder Community is far from a secret, but one the things I've been working on quietly (until recently) was the Twitter Adapter for Microsoft's Bot Framework. This weekend, I finally put the finishing touches on it, which required a whole lot of helper code for managing webhooks and subscriptions thanks to using the Twitter Account Activity API.

With this milestone (as in v1.0.0 and it might just work) release, you can now create Twitter bots with the Microsoft Bot Framework.

Let's go over some of the sample code from the repository.

First, you're going to want to install the Bot Builder Community package:

npm install @botbuildercommunity/adapter-twitter --save
    

If you're just cloning and running the sample, you can npm install in the root of the "adapter-twitter" directory.

To build a simple Twitter bot, you'll need a handful of environment variables. Here's what we have listed in the README:

TWITTER_CONSUMER_KEY=<Your Consumer Key from Your App>
    TWITTER_CONSUMER_SECRET=<Your Consumer Secret from Your App>
    TWITTER_ACCESS_TOKEN=<Your Access Token from Your App>
    TWITTER_TOKEN_SECRET=<Your Access Token Secret from Your App>
    TWITTER_APPLICATION_USERNAME=<The Twitter Account Screen Name of Your Bot>
    TWITTER_ACTIVITY_ENV=<The Name You Gave to Your Twitter Environment>
    TWITTER_WEBHOOK_URL=<The API Endpoint You Create to Handle Twitter Messages>
    

The TWITTER_APPLICATION_USERNAME will be the name of your application and the screen name of the Twitter account you're using for the bot. The TWITTER_WEBHOOK_URL is the endpoint your bot will listen on for events from Twitter. The rest of the environment variables you can get from your Twitter developer account once you create an application. For TWITTER_ACTIVITY_ENV, this will be the name you give the environment where your account is sandboxed.

With variables in hand (or in file), you can now import some packages and components:

const { BotFrameworkAdapter } = require("botbuilder");
    const restify = require("restify");
    const { config } = require("dotenv");
    const { TwitterAdapter, TwitterWebhookManager } = require("@botbuildercommunity/adapter-twitter");
    

Then you can set up your Twitter Adapter:

const twitterAdapter = new TwitterAdapter({
        consumer_key: process.env.TWITTER_CONSUMER_KEY,
        consumer_secret: process.env.TWITTER_CONSUMER_SECRET,
        access_token_key: process.env.TWITTER_ACCESS_TOKEN,
        access_token_secret: process.env.TWITTER_TOKEN_SECRET,
        screen_name: process.env.TWITTER_APPLICATION_USERNAME
    });
    

Note that we imported the BotFrameworkAdapter too. In the sample code, we have the Twitter Adapter running in parallel with the Bot Framework Adapter. You can have multiple adapters in your chatbot: You just have to give them different endpoints.

Adapters are meant to be simple, and to handle the bridge between the Bot Framework and the target environment, so the rest of the code is pretty simple and standard:

server.post("/api/twitter/messages", (req, res) => {
        twitterAdapter.processActivity(req, res, async (context) => {
            if(context.activity.type === 'message') {
                await context.sendActivity('Hello from the Twitter adapters.');
            }
        });
    });
    

This is a simple endpoint listening on /api/twitter/messages like any Bot Framework endpoint, and we're using processActivity() to process incoming requests. If an incoming request is a message, we send a message back. So in the example above, if a person tweets at the bot's Twitter account, it will respond with the specified string.

This works with the more event-drive onMessage(), etc. methods when you inherit from the ActivityHandler as well. The processActivity() message here is used for simplicity.

Simple.

Now since this is a Twitter bot, we need to add another endpoint so Twitter can verify the endpoint URL. This endpoint needs to respond with the appropriate data when Twitter asks. We do that with the same exact URL, but looking for a GET request.

server.get('/api/twitter/messages', (req, res) => {
        try {
            const webHookResponse = TwitterWebhookManager.processWebhook(req, process.env.TWITTER_CONSUMER_SECRET);
            res.send(webHookResponse);
        }
        catch(e) {
            res.status(500);
            res.send({ error: e });
        }
    });
    

In the code above, the bot is listening for a GET request that Twitter will ping for a specific response. Normally, this would involve the end user (you) looking for a crc_token and creating a Base 64 encoded cryptographic response. Thanks to the static methods available in the Twitter Adapter package, you just have to call TwitterWebhookManager.processWebhook(), passing in the request and the consumer secret. You can then send the result back in the response.

As mentioned in the opener, the Twitter Account Activity API wants you to register a webhook and subscribe your account, but it doesn't offer an administrative interface for this. It wants you to handle it in your code. Thankfully, we've built in a handful of static methods to help with webhook and subscription management. Take a look at the README for more information.