Poor Developer's Bufferapp

If you have several Facebook pages and Twitter accounts, you will eventually start looking for a semi-automated solution.

One way would be to go with a product like bufferapp.com – like the title implies – but there are advantages to rolling your own solution.

  1. Buffer’s multiaccount-support costs money.
  2. It looks a little less mass produced if the “posted by” is you and not Buffer. This argument may not be valid, I’m not sure what’s shown to the end users.
  3. It’s easier to integrate with your own automated work-flow.
  4. It’s fun to make it on your own!

Golang To the Rescue!

So now, if I combine at for scheduling and my cheekingly named Go CLI-program gobuffer, I can do this:

at now +2 hours  <<< "gobuffer broadcast -s cheekycoder_com -m 'Auto Social Publisher for the Poor Developer, Thanks to #Golang, @Spf13 and Others!' -l http://cheekycoder.com/2015/01/poor-mans-bufferapp/"

The above says that in 2 hours I want to broadcast the message and link to all Facebook pages and Twitter accounts that are connected to the cheekycoder_com site (my own custom mapping).

Photo: Marcus Quigmire

Photo: Marcus Quigmire

I may post more source code than the snippets below at one point, but what I have now – even if it works – is some half-smudgy piece of software, and I will keep it for myself until properly cleaned up.

But I will outline what I did, and point out some of the pitfalls. Some of the information needed isn’t obviously available doing the Google dance. Maybe someone will pick up the idea and make a proper open source project of it.

As Go is the natural implementation language, I even have some naming suggestions: Gosocial, Gosocialpost, Gobuffer

Why Go? It’s very powerful and easy to use – and the cross-platform support and the static linking to a single binary is perfect for both local use on a PC and to copy it to a server somewhere.

The Tricky Pieces of the Puzzle

To implement this is mainly straight forward. The Twitter part of this could be simpler1, but for practical reasons you often need to do the 3-legged Oauth authorization dance for both Facebook and Twitter, then persist the tokens for later scheduled use.

Good thing there are free Go-libraries out there to help you:

For Twitter I used Tweetlib for both authentication and posting status updates. For Facebook I had to integrate with two: Goauth Facebook for authentication and Huandu’s Facebook for API-access.

The libraries above do a fairly good job of explaining the API, i.e. how to post a message to a page or to a Twitter feed. The tricky parts are the authentication and token handling.

What I call the 3-legged Oauth authorisation dance could be done more manually, of course, but it is nice to have one app for all. And since this involve manual interactions from you and a web server for the Oauth callback, this one is a little tricky to implement – and Golang’s current HTTP server implementation doesn’t make it easier, either. But searching for Golang graceful http shutdown shows that I’m not alone, and I notice changes in the Go core to make this easier.

I will explain shortly how I solved this later, but first, lets start with the short story:

  1. Send the user (me) to Twitter/Facebook in a browser for authentication and authorization (“Approve this application to gain access to your account”).If you have several Twitter accounts it is important that you now log into the relevant account.2
  2. Then Twitter/Facebook sends a verifier/token back via a callback url.
    1. Facebook: Use the token received to fetch a list of accounts (/me/accounts) with corresponding access tokens: Pick the token for the page you’re going to post to.
    2. Twitter: Use the verifier to get a access token. NOTE: This is the token you must persist for future re-use. This token contains both a token and a secret. You will need both.
  3. Persist the token for future use. It will expire at some point, but then the steps above must be repeated.

You will have to set up one Twitter application and one or more Facebook applications. You will need the key and secret for all of these.

In the Facebook-application(s) you must set up some permissions: App Details > Configure App Center Permissions: manage_pages and publish_actions.

For the Twitter app, set permission to read and write and set the callback URL, an example would be http://mysite.local:8001/oauth/twitter3

The above requires that you have an actually web server that is ready to receive the token on that URL. But you only want it running for the length of the “Oauth conversation”, and this is a little tricky to achieve in Go.

http.ListenAndServe(":8001", nil) is a common way to fire up a server. But this blocks, and I want it to continue after It receives my token. There’s no convenience methods in Go to create and get both a Listener and a Server, but they can be created separately:

ln, err := net.Listen("tcp", ":8001")
check(err)
handler := &twitterCallbackHandler{ln: ln, authUrl: authUrl}
http.Handle("/oauth/twitter", handler)
server := &http.Server{Addr: ":8001", Handler: nil}
server.Serve(ln.(*net.TCPListener))

authUrl above is https://api.twitter.com/oauth/authorize with a temporary oauth_token appended. This is the URL the user must be sent to / asked to go to.

And then my simple Twitter callback handler could look something like this:

func (h *twitterCallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	qs := r.URL.Query()

	h.token = qs.Get("oauth_token")

	if h.token == "" {
		http.Redirect(w, r, h.authUrl, 302)
		return
	}

	h.verifier = qs.Get("oauth_verifier")
	h.ln.Close()
}

The above isn’t very robust – there are some scenarios where the listener’s Close() will not be called – but it will have to do for now.

By now, you should be ready to interact with the APIs, and that is the easy part (look at the documentation). For Facebook it’s as easy as making a POST request to /<pageId>/feed4 with message and/or link as parameters.

The Core Pieces of the Puzzle

Of course, to write a CLI program in Go, there are some core framework stuff that you really don’t want to write from scratch: The CLI commander and the configuration support.

Steve Francia (aka spf13) has written some good tools for both.

Cobra is an easy-to-use CLI commander written in Go. One example is the function that creates the command used to broadcast a message:

func broadcastCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "broadcast",
		Short: "post to both Facebook page and Twitter feed",
		Long:  "post to both Facebook page and Twitter feed",
		Run: func(cmd *cobra.Command, args []string) {
			tweet(newMessage)
			postToFacebookPage(newMessage)
		},
	}

	cmd.Flags().StringVarP(&newMessage.site, "site", "s", "", "The site(s) to post to")
	cmd.Flags().StringVarP(&newMessage.message, "message", "m", "", "The message to post")
	cmd.Flags().StringVarP(&newMessage.link, "link", "l", "", "The link to post")

	return cmd
}

And since this application will have to be wired up with some secret keys – you do not want to push these to GitHub – a way to externalise the configuration is a must. Spf13 has a good tool for that, too, called Viper.

It’s easy to setup:

func initConfig() {
	viper.SetConfigName("config")
	viper.AddConfigPath(filepath.Join(os.Getenv("HOME"), ".gobuffer"))
	viper.ReadInConfig()
}

And some relevant usage from this application:

config := &tweetlib.Config{
		ConsumerKey:    viper.GetString("twitterConsumerKey"),
		ConsumerSecret: viper.GetString("twitterConsumerSecret"),
		Callback:       viper.GetString("twitterCallback"),
}

  1. It’s possible to set up each Twitter account with it’s own consumer key and secret (and avoid the 3-legged Oauth dance), but then you would have to have a separate phone number for each. [return]
  2. The flow differs a little between Twitter and Facebook. The Twitter flow involves getting a temporary token first, that is appended to the user’s url to the authentication endpoint. [return]
  3. The Oauth callback host is typically, for local use, an entry in the hosts file that points to localhost. [return]
  4. The Facebook PageId can be found on the page itself, by following the menu item “About”. [return]


Please follow me on Twitter and let my my feed sit idle in your RSS reader.