This week I decided to build a "Twitter post scheduler" to try and reach a different audience by tweeting when I'd normally be asleep. Now, I am sure this already exists but I want to give it a go with Firebase, Heroku hacks (more on that in a second) and Nextjs.
The code can be found on Github
Discovery
Initially, I wanted to use Cloud Scheduler for this, which is essentially Google's version of CRON jobs. However, that costs money and I ain't about that paying life... coffee is too expensive for that. So overcoming this was the first problem. Luckily for me, I built a twitter bot in early 2018 that I launched on Heroku on a free dino. The bot is set to do something every 30 minutes meaning it is never put to sleep. HACKERMAN! (I wish I had gifs in my posts) I am planning on using a similar approach here. Although, it might not be as accurate as CRON jobs, I really don't need it to be.
Setup
This project is split into two parts, cronish
and fe
.
cronish
will be deployed on Heroku and will handle all the twitter posting. fe
is a nextjs app that will give me a "self-hosted" front end app that I can use to schedule my posts. The posts will be added to a Firebase database. When a cronish
event runs, it will pull in the data from Firebase and filter through the posts that can be tweeted, and then post them. Not very elegant but hey, I have less than a week to build this. I also decided to use react-bootstrap
for all the UI work in order to save some time.
FE
The front end was my first attempt at writing a nextjs app, all in all, I think it went pretty well. I decided to stick with Firebase for the authentication and data storing because it is easy to set up and use. Both good things when you're on a tight schedule. I only have one page, index.js
, which renders two different views based on the authenticate state that is passed down from the _app.js
. I recommend looking through the resources at the bottom of the article to read more about the nextjs project structure.
const Home = ({ isAuth }) => (
<Layout>
{
isAuth ? <Dashboard /> : <SignIn />
}
</Layout>
)
How is the isAuth
prop defined? In my personal opinion, probably not very well BUT I'll think of a better way next time.
// _app.js
class App {
authListener = () => {
firebase.auth.onAuthStateChanged(user => {
this.props.pageProps.isAuth = user
})
}
componentDidMount() {
this.authListener()
}
}
This authListener
method will listen for changes in the authentication state and update the pageProps
(this is a special nextjs property), and the new isAuth
value will then pass down to all the children.
Start Scheduling
The <Dashboard />
I had in the code example above consists of two components, CreateTweet
and ScheduleTweets
. ScheduleTweets
is similar to the stats
page component I talked about last week. The CreateTweet
component, however, has a few cool things happening.
The CreateTweet
component has the form that is used to schedule your tweets. The two main parts here is the datetime-local
input field and the onSubmit
method for the form. The datetime-local
field allowed me to enter a date similar to 1/04/2019 16:33 pm
, this made it very easy to schedule posts when I need it and it was better than the other date select component that I was trying to use. I did have to find a way to set the default date for the field since new Date
wasn't enough or in the correct format; small hickup but didn't slow me down too much.
The onSubmit
method had an interesting bit of code.
const timestamp = +new Date(this.state.date)
Apparently passing the datetime-local
value to new Date
wasn't enough to convert the value to a timestamp value, however, all it ended up needing was a +. Not entirely sure what this does, I'll need to look into it more to know for sure.
Hacky CRON time
This is what I would call an "I have about 1 hour, let's build a CRON job" CRON job. The cronish
folder has one main file and one main method, index.js
& scheduler
. The scheduler fetches all the posts for a user, loops and checks if the post is in the past and posts a status update to Twitter. The full method for this can be found here.
databaseRef.once('value')
.then(snap => snap.val())
.then(tweets => {
tweets.loop() {
if (tweet.inThePast()) {
Twitter.Post()
}
}
})
It might not be pretty or super efficient but it works, which is slowly becoming the tagline for these challenges. Finally, the CRON part comes in the form of an interval that runs every 30 minutes. Making it very inaccurate but you can adjust it to whatever you need!
setInterval(schedular, 1800000)
Heroku gotchas
Due to using I am using the firebase-admin
package, I needed to pass a privateKey
value to the initializeApp
method. However, the one you get from Firebase won't work out of the box. Here is an example of what the key will look like;
"-----BEGIN PRIVATE KEY-----\nyoursecret\nkey-data\n8a8d8dS*SSHJJAS*8s8\n-----END PRIVATE KEY-----\n"
When you are entering your Heroku environment variables, change this to be;
-----BEGIN PRIVATE KEY-----
yoursecret
key-data
8a8d8dS*SSHJJAS*8s8
-----END PRIVATE KEY-----
Improvements
Boy, there are a lot! I have to confess I had about 6 hours in total to work on this project this week, so the quality isn't that great. My first improvement would be to the front end portion of the site. Customising the theme slightly would go a long way, also some more error checking to ensure that you are scheduling posts for the future and not for the past. Making the CRON job a proper CRON job would also be a good improvement to increase the accuracy of the Tweets. And finally adding additional methods, such as LinkedIn, Instagram or Facebook posts to make it a worthwhile product to use. However, given the timeframe, I think this is a good base to build those things on top of.