I've been chewing on an idea for something I'd like to build with OpenWhisk and Alexa, and part of it involves Twitter integration. Since working with serverless means working with small, atomic functions, I decided to focus on the Twitter aspect first. I also thought it would be cool to start work on a Twitter package that could be used by other OpenWhisk users. I launched that package today, and while it is pretty small for now, I hope to expand on it over time.

I blogged about OpenWhisk packages a few weeks ago ("Using Packages in OpenWhisk"), but the basic idea is that they allow you to collect multiple actions into one 'bucket', share default arguments between them, and then share access to them to all users on the platform. (Which is optional by the way - working with packages does not mean you have to share them.)

My Twitter package has a grand total of one action currently - getTweets. The name is - possibly - not entirely accurate. It's using the Search API and lets you pass either a "term" parameter or "account" parameter (or both). This lets you search for something, get tweets from an account, or both. Here is the code.


const Twitter = require('twitter');
const request = require('request');

//possibly cache BT
let BEARER_TOKEN = '';

//credit: https://github.com/desmondmorris/node-twitter/issues/112
function getBearerToken(key,secret) {
	
	return new Promise( (resolve, reject) => {

		if(BEARER_TOKEN != '') return resolve(BEARER_TOKEN);
		console.log('getting BT from Twitter');

		let enc_secret = new Buffer(key + ':' + secret).toString('base64');
		let options = {
			url: 'https://api.twitter.com/oauth2/token',
			headers: {'Authorization': 'Basic ' + enc_secret, 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'},
			body: 'grant_type=client_credentials'
		};

		request.post(options, function(e, r, body) {
			if(e) return reject(e);
			let bod = JSON.parse(body);
			let bearer = bod.access_token;
			BEARER_TOKEN = bearer;
			resolve(bearer);
		});

	});

}

/*
main purpose is to generate a search string
form of search:
args.account (to get by account)
args.term (raw term)

more to come maybe
*/
function createSearchString(args) {

	let result = '';
	if(args.term) result += ' '+args.term;
	if(args.account) result += ' from:' + args.account;
	return result;
}

function main(args) {

	return new Promise( (resolve, reject) => {

		getBearerToken(args.consumer_key, args.consumer_secret).then( (bearer_token) => {

			let client = new Twitter({
				consumer_key:args.consumer_key,
				consumer_secret:args.consumer_secret,
				bearer_token:bearer_token
			});

			let search = createSearchString(args);
			client.get('search/tweets', {q:search}, function(err, tweets, response) {
				if(err) return reject(err);
				resolve({tweets:tweets.statuses});
			});
			
		});


	});

}

exports.main = main;

In order to use this package, you must have your own Twitter app defined in their portal and get access to your consumer key and secret. I use the Twitter package from npm, do some munging on your search input, and then call the API. And that's it.

Invoking it could look like this:

wsk action invoke twitter/getTweets --param consumer_key mykey --param consumer_secret mysecret --param account raymondcamden -b -r

The result is an array or tweets. I won't show that as it's rather boring list of tweets. The CLI I used above only works for me - you would want to prefix it with my namespace, so in theory, this should work:

wsk action invoke /rcamden@us.ibm.com_My Space/twitter/getTweets --param consumer_key mykey --param consumer_secret mysecret --param account raymondcamden -b -r

Of course, you can just use my code as a simple action too. Finally, if you want to help me improve the action (or add new actions), I've set up a repo here: https://github.com/cfjedimaster/twitter-openwhisk