If you've got a Twitter account, and a blog with a lot of content, sharing your posts from time to time can be a nice way to drive a little extra traffic to your site. Plus, sharing your experience and knowledge could very well help someone out who would not have found your post otherwise.

Manually selecting and sharing posts is time consuming, and using a third-party service could get costly and/or lack flexibility in selecting older posts. What if you could have the best of both worlds, and automate posting random blog posts for free? Thanks to some great OSS libraries, and AWS Lambda (which has a generous free tier plan), you can.

I'll explain a little more about how I did it, but here's the tl;dr if you just want to try it out. I get it - sometimes the best way to learn something is to dig in and get your hands dirty as soon as possible.


Usage

This is a C# console app that runs in AWS Lambda. You can schedule it to run as often as you like. Maybe once or twice a day to start.

Clean up existing tags

Before you do anything else, you might want to revisit your existing tags (under settings/tags) and clean certain things up. Here's what I'd recommend (or you can leave them as-is and modify my code to handle them).

  • Remove any leading # from tags, since the app prepends a # to each tag.
  • If you have any characters in tags that won't translate well to Twitter hashtags, either remove them in Ghost, or add them to var pattern = new Regex("[- ]"); in my project so they get removed before posting the tweet.
  • This is a good time to revisit all your tags and just remove/rename ones that won't look good in Twitter.
  • While you're at it, you might want to revisit your posts as well - anything you forgot about that you'd rather not post to Twitter?

Other stuff to think about:

  • You can leave spaces and hyphens - the app will remove them.
  • You can leave leading numbers, since Twitter seems to handle those just fine.
  • Any existing # will be replaced with sharp (c# » csharp), and . with dot (.net » dotnet).

Grab the code

  1. Clone the repo: git clone git@github.com:grantwinney/TweetRandomPostFromAGhostBlog.git
  2. Open the project in Visual Studio.
  3. Build the project (you may have to right-click the NuGet folder and choose 'restore' to download the dependencies, although VS usually takes care of that for you)
  4. Find the bin directory on disk, and drill down until you get to the assemblies (dll files), most likely in bin/Debug/netcoreapp2.0
  5. Select all the files inside netcoreapp2.0 (but not the directory itself) and zip them up. You'll be uploading these to an AWS Lambda function.

Create a Twitter app

You'll need to register a new app with Twitter (an app with one user - you!). The name of your app doesn't matter, but it does have to be unique system-wide (not just unique in your account).

To get the values you need for the app to post to Twitter, generate an "access" token under the "Keys and Access Tokens" section, and note these four pieces of data: Consumer Key, Consumer Secret, Access Token, and Access Token Secret

Setup AWS Lambda

Now you need to create a new Lambda function. Here's a brief intro to setting up AWS Lambda, which you may want to check out. Once you're signed up, continue on...

  1. Create a new function (author from scratch) and choose C# (.NET Core 2.0) for the runtime.
  2. The name of the function, and the role it makes you create, don't matter.
  3. Upload the zip file you previously created.
  4. Set the handler as TweetRandomFeedItem::TweetRandomFeedItem.Program::Main

function-code

  1. Under "Basic settings", decrease the memory to 128MB and increase the timeout to a minute. For me, it generally takes about 15-20 seconds to run, and uses 50MB or less of memory.

basic-settings

Create the environment variables

I make heavy use of environment variables, so that credentials and other settings can easily be changed without having to recompile the code and upload it again. Here's a description of each field you can set, and whether it's required or optional.

Field Required? Description
GHOST_CLIENT_ID
GHOST_CLIENT_SECRET
Yes If you don't have the public API enabled in Ghost, do it now. You can uncheck it afterwards if you'd like.

enable-ghost-public-api

Open any post on your blog and search the source code for something like this - you'll need both values.

ghost.init({clientId: "ghost-frontend", clientSecret: "55f1e0f55123"});
GHOST_USERNAME
GHOST_PASSWORD
No If you leave the public API enabled, you can omit these variables (or include them but leave the value blank).

If you have the public API disabled, you need to include your Ghost username and password too, so that the app can retrieve an auth token from Ghost.
GHOST_URI Yes Specify your blog URL, like: https://grantwinney.com

Without this, the app can't make an API call to your blog to select a random post.
GHOST_POST_RETRIEVAL_LIMIT No Specify the number of posts you want to retrieve. It uses the limit parameter of the API.

If you omit this, or leave the value empty, the app will consider all your posts when selecting a random one.
TWITTER_CONSUMER_KEY
TWITTER_CONSUMER_SECRET
TWITTER_USER_ACCESS_TOKEN
TWITTER_USER_ACCESS_TOKEN_SECRET
Yes These values all come from your Twitter account. You need to create a new app to get these values, which allows you to post tweets.
TWITTER_MAX_TAG_COUNT No Although tag spamming is somewhat prevalent on Facebook, and even more-so on Instagram, I don't see a lot of it on Twitter. If you tend to use a lot of tags for posts on your blog, then you can limit how many of those transfer to your tweet.

If you omit this, or leave the value empty, the app will use the first 3 tags.

When you're done, it should look something like this:

enviro-vars

Take it out for a spin!

That should be everything you need to run the job. To try it out, hit the "Test" button at the top of the screen. It might have you configure a new "test event". Just do it, name it whatever, and change the code to an empty set of curly braces like {}.

Hopefully everything goes smoothly and you get a screen like this one. Check your Twitter feed - did it post one of your posts from your Ghost blog?

successful-lambda-run

Schedule it

If you're ready to let it do your work for you, schedule it to run via cron.

  • Select "CloudWatch Events" under "Add Trigger" in the Lambda configuration screen, and a new "Configure triggers" panel appears just below it.
  • Select "Create a new rule" from the drop-down and give the new rule some random name.
  • Enter a cron command in the "Schedule expression" box, such as cron(0 12 * * ? *) to run your job at 12 UTC every day.

The Stack

This project makes use of some nice OSS libraries. Oh, and there's even one that's mine, albeit it's not as finished as I'd like.

Tweetinvi

Twitter has a page that references libraries in different languages for their API, including a handful for C#. Unfortunately, TweetSharp is dead. Fortunately, LinqToTwitter and Tweetinvi are both alive and kicking.

I looked at each, but they've both been recently updated, and each have a some open issues but a lot more closed ones. Someone's working on them, which is good! Both Tweetinvi and LinqToTwitter support .NET Core 2.0 too, which is required since AWS Lambda runs on the .NET Core 2.0 runtime.

After spending 15 minutes checking the two out, I decided to just go with Tweetinvi. I'm glad I did. It proved extremely easy to implement! I spent a couple evenings trying to write a my own library awhile back, but it's a complex task to tackle. I'm glad someone already did the work.

RestSharp

RestSharp is the defacto library for access REST API endpoints from a .NET project. It's easy to configure and implement. I'm using it to access the Ghost API, and to deserialize the JSON response into .NET classes defined in the GhostEntities.cs file. Nice.

My first attempt was actually to use the SyndicationFeedReaderWriter (available via NuGet) to read in the RSS feed, which worked okay, except there are shortcomings with the RSS feed in Ghost. You only get 15 items back, and have to append /2, /3, etc. to get more items. In the end, using the Ghost API is more flexible and returns more detail... it should, after all!

AWS Lambda

I'm only just starting out with AWS Lambda, so I'm not sure what all it's capable of yet. A few days ago I created a function that keeps my personal Twitter timeline clean. So far, it's awesome. Their free tier plan is generous enough to let you try out lots of different things before you need to pay a single penny.

GhostSharp

I took a crack at porting the Ghost API to C# some time back. I don't feel it's ready to be published to NuGet yet, but I borrowed heavily from it. Copy/paste ftw!

With it, I hit the API once to get all post IDs - and only IDs - and then I choose one at random. Then I hit the API again, this time to get enough details about the post to construct a message for the Tweet.

I ran into a few snags, one being that the limit parameter should be able to accept "all" to get all posts, but it doesn't work. So if you don't specify a GHOST_POST_RETRIEVAL_LIMIT in the Lambda function, the app defaults to "9999999" to effectively get all post. If you have 10 million posts on your blog, sorry, you'll have to change the code. 😜


Thoughts? Comments?

Let me know how it goes, what you think of it, and whether you have any problems.

This project is tightly coupled to the Ghost platform. I plan on writing a slightly more generic version, to use your blog's RSS feed and the SyndicationFeedReaderWriter I mentioned earlier. I'm also hoping to inject some kind of logic into the app that, when a random post is being selected, places a heavier weight on more recent posts so they have a greater chance of exposure.