Tags

, , , , , ,

This is the fourth article in a series of unknown length discussing using a tool written in Lua to publishing posts on a WordPress blog.

  • Part 1 showed how to use XML-RPC and wp.newPost
  • Part 2 added file reading to make a minimal working utility.
  • Part 3 added amenities and made the utility useful.

This installment will change pace by detailing how to use the REST API supported by WordPress.com hosted blogs. The REST API is largely easier to work with as it passes parameters through the URL and returns results formatted in JSON, mostly avoiding POST form encoding and XML. It also clearly documents support of the OAuth protocol for authentication.

Although I have only tested against WordPress.com hosted blogs, everything should also work with self-hosted blogs that have the Jetpack plugin installed and correctly configured.

First, get access

We paused for a brief digression on how to get OAuth setup for this application. This post will assume that you’ve followed along with that, and have captured the full redirected URL after authorizing the script. For our purposes, we are only interested in the chunk after #access_token= and before the first & which should be a 64 byte access token that has been URL encoded such that any unsafe characters have been replaced by a sequence %xx with the character represented in hex.

For my (now revoked) authorization of the script, I was redirected to the URL:

https://curiouser.cheshireeng.com/applications/wp-cli-tools/#access_token=tq5gXPd%24YZIIu%21D%40KBAv%21sdt%405Zdgy9i%5EJrMRkg3%24%23Sx%26X%25MlrmnRxgc%40Eyr%40uMT&expires_in=1209600&token_type=bearer&site_id=73256621

which contains the token

tq5gXPd%24YZIIu%21D%40KBAv%21sdt%405Zdgy9i%5EJrMRkg3%24%23Sx%26X%25MlrmnRxgc%40Eyr%40uMT

You will want to copy and paste that token into your command line in a bit. For the moment, copy and paste it into a scratch file so you have it for later.

Note: Get your own token! That one is mine, and it has been revoked in any case! As explained in the digression post, the token is unique to a user and a blog.

Also, don’t publish your token, capture it in revision control, or otherwise make it public. The token is a secret that WordPress has shared with your application to prove it still has the rights to act as you for that blog. A later version will automate the process of getting the token and will store it away in a safe place. For now, we are using brute force as a starting point.

You can revoke the permissions of your token at any time from the Security Settings of your WordPress user account profile. Also, you can view a list of your applications where you can access each to manage its settings, regenerate a lost secret, and even learn some statistics about its use in the wild.

REST API

REST is a current approach to discussing and designing APIs that interact with remote data services. It provides a framework that encompasses everything from ordinary interact browsing of static pages to fully interactive data sources viewed through custom client-side applications.

A key implementation difference from older techniques such as XML-RPC is that a typical RESTful service uses more elaborate URLs to represent the state, and relies on standard HTTP methods such as GET, PUT, POST, and DELETE to act on the current state, along with the standard mechanism for specifying media type to allow the data content to take a variety of formats.

For example, a summary of the publicly available information about a particular blog is available by fetching the page:

https://public-api.wordpress.com/rest/v1/sites/$site

which will return a JSON formatted document containing lots of details for the hosted site who’s domain or ID is $site.

For this blog, the returned string begins:

{
    "ID": 73256621,
    "name": "Words from Cheshire Engineering Corp.",
    "description": "Things we want to talk about",
    "URL": "http://curiouser.cheshireeng.com",
    ...
}

A First Taste

As a first example of using the REST API with OAuth, we will rewrite the wppost.lua script to use the cURL library for https: transfers from the appropriate REST endpoint URLs.

As a good first sample, our goal is to just retrieve the /me endpoint. That endpoint delivers a block of data about the user represented by the attached OAuth Access Token, and makes no change to any part of any blog. As such, it makes a good test for a first page to fetch that requires authorization.

Switch to cURL

The script file begins much as it did before. The big difference is that the Lua binding to the cURL library. This is simpler than using the XML-RPC module because the cURL binding is part of Lua for Windows and you should already have it installed.

local utils = require "pl.utils"
local app = require "pl.app"
local lapp = require "pl.lapp"
local config = require "pl.config"
require "luacurl"

As before, we are going to use Penlight’s app, lapp, utils, and config libraries to hide a bunch of details.

-- Put the pl.lapp based options handling near the top for easy visibility
local args = lapp [[
Post a file on a WordPres blog as a draft post. Part of the WP CLI Tools.
https://curiouser.cheshireeng.com/applications/wp-cli-tools/

  --blog (default "")            The blog at which to post.
  --token (default "")           The OAuth token from the redirect URL
  -v,--verbose                   Be more chatty about the process  
  --showconfig                   Just display the config file
  --writeconfig                  Write the config file with the options  
  <filename> (default "")        The file to post.  
]]

-- Also read a config file stored in a "home directory" folder
local configfile = app.appfile"rest.ini"

if args.showconfig then
    print("config:", configfile)
    local s = utils.readfile(configfile)
    io.write(s or "--config file empty--n")
end

local conf = config.read(configfile) or {
    blog = "curiouser.cheshireeng.com",
    token= 'X'
}

-- Add missing configuration fields to args
for k,v in pairs(conf) do
    if (not args[k]) or (#args[k] < 1) then args[k] = v end
end

-- Possibly write back the config file
if args.writeconfig then
    local f = io.open(configfile, "w")
    f:write"# configuration written by the --writeconfig optionnn"
    for _,k in ipairs({"blog","token"}) do
        f:write(k,"=",args[k],"n")
    end
    f:close()
end

This code is largely unchanged from the previous version. It specified our command line options using lapp, processes them, reads a configuration file and merges it with the args table, and potentially writes the configuration file.

For this version, the --token option will take the URL encoded access token that you manually chopped out of your authorization redirect URL as described above.

args.token = curl.unescape(args.token)

This removes the URL encoding. The URL encode was left in place for two reasons. First, it is easier for you to just cut and paste with it in place the way the token was delivered. Second, once decoded, the token often contains punctuation characters that cause problems for the command line processor. All in all it is better to use cURL to remove the encoding at run time. Note that if the token was written to the configuration file, it was written in the URL encoded form for much the same reason.

if #args.token ~= 64 then
    lapp.error("WP Access Token must be exactly 64 bytes long.", false) 
end

That just verifies that the token is the expected length once decoded for safety's sake. Of course, WP doesn't actually claim that 64 bytes is the right length. That is just the length I observed from every authorization grant I've done. (Two of them, but who's counting?) So it may be more appropriate to drop that test from future versions.

args.baseurl = "https://public-api.wordpress.com/rest/v1/sites/"..args.blog
if args.verbose then
    for k,v in pairs(args) do print(tostring(k)..": "..tostring(v)) end
end

That simply prints all the arguments to the script after we’ve done our processing of the configuration, but only if the --verbose option was given.

function wp_showme()
    --$access_key = "YOUR_API_TOKEN";
    c = curl.new()
    c:setopt(curl.OPT_URL, "https://public-api.wordpress.com/rest/v1/me/")
    c:setopt(curl.OPT_HTTPHEADER, 'Authorization: Bearer ' .. args.token)
    c:setopt(curl.OPT_SSL_VERIFYPEER, false)
    local t = {}
    c:setopt(curl.OPT_WRITEFUNCTION, function(_,s) t[#t+1]=s end)
    c:perform()
    c:close()
    return table.concat(t)
end

This uses cURL to set up for the authorized read of the /me endpoint. This demonstrates some interesting features of cURL. First, nearly every detail of a transfer is specified through setting options on an object you get by calling curl.new(). That object is generally useful for at least one transfer. See the cURL documentation.

So the URL to access is set via a call to c:setopt(). The token (now with the encoding stripped) is added to the HTTP headers with the OPT_HTTPHEADER option. Because of some complexity surrounding SSL certificates, I’ve turned off SSL verification for this example with the OPT_SSL_VERIFYPEER option. That is generally a bad idea, and I will have more to say about that when I’ve figured out what is actually wrong.

The clever bit is the setting of the OPT_WRITEFUNCTION option which specifies a callback function that will be called as cURL retrieves data from the remote server. That function might be called zero times if something goes wrong, once if the result is small, or many times for a large transfer. For this case, I’ve written a simple callback closure that appends buffers to the array in the local variable t. When c:perform() finishes, we concatenate them all together into a single string to return. Since the results from the /me endpoint aren’t going to be huge, this is a good strategy.

io.write("/me = ", wp_showme())

Finally, we print the string it returned. For my token, that string begins:

/me = {"ID":70019695,"display_name":"rberteig","username":"rberteig","email":...

This is a valid JSON datastructure which can be parsed to reveal a bunch of information about the user account.

Next steps

So at this point, we’ve switch from XML-RPC to REST, switched to cURL for accessing the site, and switched from passwords to OAuth. But we can’t quite yet make a post the new way.

But that is the next obvious step, along with parsing the JSON data that most of the API returns. With that, we will be able to easily support features like adding tags and categories to our posts, as well as some of the other features that we have discussed in past posts.

(Written with StackEdit.)