Blogger posts from markdown and CLI

There are plans to migrate Silly Bytes to Hakyll and GitHub pages, but till then I’m still stuck with Blogger and wanted to make the posting process as painless and automatic as possible.

Every post I write is currently a separate git repo hosted on the Silly Bytes GitHub organization. The post is written and maintained in Markdown using Pandoc and a convenient Makefile generated by the made script.

Writing posts in Markdown is nice but is not that advantageous if you still have to mess around with Blogger’s web interface, so here is the plan:

  • Write posts in Markdown
  • Use made to generate a Makefile
  • Generate HTML with $ make
  • Push the HTML post to Blogger using Google’s APIs

The first 3 steps are already covered so lets dig into the Blogger negotiation part.

API script

Google’s APIs come in handy here, the best language option was Python (I refuse tu use Java). So starting from an example I came up with this helper script:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import print_function
from googleapiclient import discovery
from oauth2client import client
from oauth2client import file
from oauth2client import tools
import sys
import os
import httplib2
import argparse

SILLYBYTESID="1318550761233559867"

def main(argv):
    if (len(argv) < 3):
        print("Post title must be provided as the first argument and html file as the second")
        exit(1)

    post_title = argv[1]
    input_file = argv[2]

    scope = 'https://www.googleapis.com/auth/blogger'

    parent_parsers = [tools.argparser]
    parent_parsers.extend([])
    parser = argparse.ArgumentParser(
        description=__doc__,
        formatter_class=argparse.RawDescriptionHelpFormatter,
        parents=parent_parsers)
    flags = parser.parse_args("")

    try:
        client_secrets = os.path.join(os.path.expanduser("~") + '/.sillybytes/',
                                    'secrets.json')
    except:
        print("Can't find secrets.json file maybe?")
        exit(1)

    flow = client.flow_from_clientsecrets(client_secrets,
                                        scope=scope,
                                        message=tools.message_if_missing(client_secrets))

    storage = file.Storage('auth_data' + '.dat')
    credentials = storage.get()
    if credentials is None or credentials.invalid:
        credentials = tools.run_flow(flow, storage, flags)

    http = credentials.authorize(http = httplib2.Http())
    service = discovery.build('blogger', 'v3', http=http)

    try:
        content = open(input_file, 'r').read()
    except FileNotFoundError:
        print("Input file not found")
        exit(1)

    body = {
        "kind": "blogger#post",
        "title": post_title,
        "content": content
    }

    try:
        posts = service.posts()
        request = posts.insert(blogId=SILLYBYTESID, body=body, isDraft=False)
        result = request.execute()
        print("Live: " + result['url'])
    except:
        print("Can't execute request")
        exit(1)

if __name__ == '__main__':
main(sys.argv)

The script will initiate an OAuth negotiation when needed and store the authentication tokens in the auth_data.dat file.

API project

Before we’re able to use this we need to create a new API project, configure it and get the client_secrets.json that the script will use to start the OAuth negotiation.

First, enable the Blogger API at: https://console.developers.google.com/apis/library

Then create a new project, a new set of credentials and download the JSON file from it. From the script above you’ll gather my client_secrets.json file will be located at ~/.sillybytes.

Now, running:

$ python deploy.py "post title" "post HTML"

Will push the post to Blogger!

CLI tool

This is good enough already, we could just invoke deploy.py from the Makefile, but we can still do better:

silly.

$ silly help

Now I can create all the post boilerplate by doing silly new and deploying the current post with silly deploy. Much better.