This guide revolves around a basic twitter bot implementation which posts a single tweet to the same twitter account every 6 hours.
It allows any Node script (or other if you fancy doing the work to get PHP/Python etc running) to be triggered on any schedule, from every minute to once every year.
Several steps are involved in getting this all up and running:
- Get our Twitter auth details
- Write our bot code
- Create the Lambda in AWS Console
- Add Environmental variables to keep our security details safe
- Test our Lambda function
- Set up a Cloudwatch trigger to act as the scheduler
- Confirm the schedule is running as expected
Get our Twitter auth details
Open developer.twitter.com and consider which account you’re going to post tweets to.
You may need to sign up and verify your account. Do all that, or just login.
Once you’re in create a new app. Click to create or view keys / access details and ensure you have an App Token, App Secret, User Secret and User Token. So four hashes in total. Two tokens and two secrets.
Also check that your user details have read and write access so we can post tweet updates.
Write our bot code
Create a new folder in your Sites or Projects folder. Use command line to cd to it. Run npm init
fill in the details. Leave entry point as index.js.
Next we need a couple of packages installing.
dotenv will allow us to use a .env file locally to store our secure twitter access details in.
twitter-lite is a small(ish) option for allowing us to use Twitter API methods.
npm i dotenv
npm i twitter-lite
Now lets create and populate .gitignore
. So in there you want to ignore our local .env file so we don’t share our access details. *.zip will stop us accidentally version controlling a zipped version of our function. node_nodules will keep packages our of our repo too:
.env
*.zip
node_modules
Next create our .env file and put our variables in it. Something like (not real details):
# .env
NODE_ENV=development
# Twitter
APP_KEY=K0fpDklBoRyrlMHe8Xj4
APP_SECRET=YQYwRHP2Ju9DGGZecSzUfSlVnMIiGO9HgbosBiu8
USER_TOKEN=2115952283684943128-yukkiMtN9ysxVnDPwkq4
USER_SECRET=Tjgs5TN6WlKmAArfMsGHB4OdouEJLLklZtQzIP59
Next create our index.js file. In there we’ll need a fair amount of code. I’ll share in sections:
const Twitter = require("twitter-lite");
const dotenv = require('dotenv');
The above includes our twitter lite and dotenv packages
exports.handler = async (event, context)=>{
The above will allow AWS to trigger our function as index.handler
later on.
if(!process.env.APP_KEY){
dotenv.config();
}
On local machines the above triggers the dotenv
package to bring in variables from our .env file, while on AWS Lamda environmental variables are automatically made available
const client = new Twitter({
subdomain: "api", // api is the default
version: "1.1", // version 1.1 is the default
consumer_key: process.env.APP_KEY,
consumer_secret: process.env.APP_SECRET,
access_token_key: process.env.USER_TOKEN,
access_token_secret: process.env.USER_SECRET,
});
The above instanciates a new instance of Twitter ready for us to use to send our tweet. It uses process.env.VARIABLE_NAME
to make use of environmental variables from above.
await client.post("statuses/update", {
status: 'This status was sent using a scheduled Lambda function. Current UNIX is ' + (new Date()).getTime(),
}).catch(console.error);
This sends our Tweet including a Unix timestamp to ensure each is unique. Then we just need to close our exports.handler
method.
} // close exports.handler
Test locally
Now we have code, let’s check it works locally.
Because of the way we’ve coded our index.js file with a handler, we need to use a clever trick to test.
Open up the package.json file and add the following above ‘dependancies’:
"scripts": {
"locally": "node -e \"console.log(require('./index').handler({}));\""
},
That then allows use to run our handler using only npm run locally
on the command line. Do it.
The test should almost immediately have sent our tweet, so head to our target twitter account over at twitter.com and take a look to see if the tweet was published.
I’m assuming we’re all good so far.
Create the Lambda in AWS Console
Log into AWS console and use the services dropdown to open ‘Lambda’. It’s near the top left under the ‘Compute’ heading, or just search for it.
Click to ‘Create function’. Give it a name. I went with ‘scheduledTweets’. Stick with Node 12.x unless you know better.
Leave the permissions options alone and click ‘Create function’
One the page loads you should see three sections / links. Add Trigger. Your function. Add destination. Click your function name.
Scroll down a little to ‘Function code’ and on the right click ‘Actions’ then ‘Upload a zip file’.
On your local machine, on command line, cd yourself to your project and then run the following to create a zip of the project contents.
zip -r function.zip . -x ".*"
The above will exclude files begining with a dot, so .env and .gitignore for example.
Now head back to your lambda function browser tab and choose your zip file from the ‘Upload a zip’ option. Your index.js code should appear in the function code section.
Add Environmental variables to keep our security details safe
It’s a really really really really really really bad idea, really bad, to store access details in your code. Whether only stored in a Lambda function on AWS, or being version controlled on multiple developer machines and then pushed to GitHub, it’s not cool to put access details into code. So what’s the alternative?
Well on our own develer machines, it’s often a .gitignore’d file. Sometimes that’s a .env
file containing SOME_VARIABLE=SOME_VALUE
In Lamba it’s the same. Scroll down your Lambda function screen until you spot the Environment variables section. Click Edit. Duplicate the keys and values from the local .env file we created above.
Thanks to our use of the dotenv packaged the variables will be available locally for testing and to our Lambda function.
Test our Lambda function
Scroll down to Basic settings and check Handler is set to index.handler
. That means the hander method in the index.js file will be triggered when the Lamdba function runs.
At the top of our Lambda function page is a Test Events drop down. Choose to configure test events. Add a test called ‘Blank’ and for the data use {}
(an empty object). Save it.
Then from the Events drop down choose Blank and click ‘Test’ to trigger our Lambda method.
The test should almost immediately have sent our tweet, so head to our target twitter account over at twitter.com and take a look to see if the tweet was published.
Set up a CloudWatch trigger to act as the scheduler
There is no crontab in Lambda. Instead we lean on another AWS service called Cloudwatch Events (EventBridge) to trigger our Lambda function. Luckily it’s built into the Lambda function management screens and we don’t need to do much to add it.
Click Add trigger. Select EventBridge. Select Create a new rule. Give it a name. I used ‘Each6Hours’.
Choose ‘schedule expression’ from the rule type options.
You can then either use normal crontab syntax (I won’t go into that here) by wrapping it in cron()
, or less complex but also less powerful rate()
sytax. This is the way to go when you want a regular heartbeat. This example is every 6 hours, so lets go with rate(6 hours)
for our value.
You can choose to enabled the trigger immediately if you’re ready, or you could do that later.
Confirm the schedule is running as expected
You could initially set up another CloudWatch Trigger for every 2 minutes or similar to use while testing. However, assuming you know when your bot will do its tweeting, head over to the twitter.com account and keep hitting refresh. Right on time you should get a new tweet appear.