This is the sixth 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.
- We digressed to talk about OAuth and using it at WordPress.com.
- Part 4 switched to cURL, REST, and OAuth, but can’t yet post.
- Part 5 improved the token handling and user display and still can’t post.
In this installment we will restore the ability to make a post, and add the ability to include categories and tags with the new post which the original XML-RPC version was never extended to do.
Better cURL from Lua
First, we have to deal with a deficiency in the packages provided with Lua for Windows. The stock wrapper for the otherwise great and powerful cURL library is incomplete, and the key missing features relate to HTTP POST
. This is unfortunate for this tool because the REST API depends heavily on POST
for writing data of all sorts to the WP platform, as well as GET
for everything else.
The better option to the lua-curl
module is known as Lua-cURL. Yes the name similarity is a source of confusion and unfortunate. I’ve linked to what I believe is the current “official” home of Lua-cURL. There is a newer version, but this version is the one currently supported by the LuaDist project which is what I’m betting will eventually replace Lua for Windows. Of course, it hasn’t done that yet because it doesn’t yet successfully build all of the components they want to include in a “batteries included” distribution. I was able to make it build and install Lua-cURL after hand-patching one quirk in the sources. For simplicity, I’ll check the DLLs that resulted from that effort into the source repository for wppost
.
Switching from luacurl to Lua-cURL involves some simple changes. Both provide clean wrappers to the cURL easy interface, but some of the names of the parts change. For example, the new way we use the /me
endpoint looks like this:
-- Discover information about the user attached to the token, -- return it as a table decoded from the JSON data. function wp_showme() local c = cURL.easy_init() c:setopt_url "https://public-api.wordpress.com/rest/v1/me/" c:setopt_httpheader('Authorization: Bearer ' .. args.token) c:setopt_ssl_verifypeer(0) local t = {} c:perform{ writefunction = function(s) t[#t+1] = s end } c = nil local j = json.decode(table.concat(t)) return j end
Aside from the detail that I moved the decoding of the JSON string into the function, most of the changes are naming and style. If that were the only benefit, there would be no reason to change.
The real benefit of changing is the availability of the easy:post()
method which constructs the data needed to send with an HTTP POST request.
Creating a New Post
With a more complete wrapper for cURL in place, creating a post similar to what we did with the XML-RPC version is now easy:
--- -- Make a new post on a WordPress blog found at args.baseurl, using the OAuth -- token with the given title and body. The new post is always a draft and -- will have no Tags and the default Category. local function wp_newPost(title, body) local c = cURL.easy_init() local url = args.baseurl..[[/posts/new?context=edit&http_envelope=true]] c:setopt_url(url) c:setopt_httpheader('Authorization: Bearer ' .. args.token) c:post{ status = "draft", type = "post", title = htmlentities(title), content = body, } c:setopt_ssl_verifypeer(0) local t = {} c:perform{ writefunction = function(s) t[#t+1] = s end } c = nil local j = json.decode(table.concat(t)) return j end
This gets us a draft of a standard plain post with no bells and whistles, no tags, and no specified category.
Dress it up a bit
With a few tweaks to the fields provided along with the new post, we can easily add features we have been missing and wanted. Naturally, these features also require some tweaks to the command line to be useful. See the actual code as checked in the repository for the details.
--- -- Make a new post on a WordPress blog found at args.baseurl, using the OAuth -- token with the given title and body. The new post is always a draft and -- will have no Tags and the default Category. local function wp_newPost(title, body, category, tags, options) options = options or {} local c = cURL.easy_init() local url = args.baseurl..[[/posts/new?context=edit&http_envelope=true]] if args.verbose then print (url) end c:setopt_url(url) c:setopt_httpheader('Authorization: Bearer ' .. args.token) c:post{ status = options.status or "draft", categories = category or "Experiments", tags = tags, type = "post", title = htmlentities(title), content = body, format = options.format, date = options.date, } c:setopt_ssl_verifypeer(0) local t,h = {},{} c:perform{ writefunction = function(s) t[#t+1] = s end, headerfunction = function(s) h[#h+1] = s end, } c = nil if args.keepraw then args.keepraw:write( "POST ",url, "rnrn", table.concat(h), "rnrn", table.concat(t)) end local j = json.decode(table.concat(t)) return j end --- -- Make a new post from the body of a file, using the filename as -- the title. The file is posted as-is, with no substitutions or -- other changes. local function postFile(filename) local title = args.title or filename local content = utils.readfile(filename, false) if not content then return nil, "No such file" end content = content .. 'nrnr' .. '*(Posted by wprest/wppost.)*nr' local ok, err = wp_newPost( title, content, args.category, args.tags ) return ok, err end --- -- Actual main body of the script. Read the command line for file name, user, and password. -- Make the post as requested and show the result. local ok, res = postFile(args.filename) if ok then if args.debug then print(pretty.write(ok)) else local ID = ok and ok.body and ok.body.ID or ok.ID print("wppost success: New post ID=".. ID) end else print("wppost failed:", res) os.exit(1) end
This uses the new --title
, --tags
, and --category
options to let the user set the post title, tags, and category. WP will simply create tags and categories willy-nilly, so if you want to restrict your posts to a limited collection of allowed categories or tags, you’ll have to be careful what you type.
A skeleton for other post features is in place, but not wired up to command line options yet.
Other amenities added to the options include the --keepraw
option which allows you to name a file which will be overwritten with a complete record of the URL posted, the HTTP headers received, and the complete JSON object received. This is handy for debugging, and is more useful than just dumping all that text to the screen.
Note about the DLLs
This release includes two pre-built DLLs for Windows. These worked for me on my Windows 7 Pro 64-bit system. They are built as 32-bit DLLs for use with Lua for Windows.
cURL.dll
implements the module “cURL” Lua module. It should be placed on your PATH.
liblua.dll
is a proxy DLL that redirects to the lua5.1.dll
that came with Lua for Windows. It is actually a renamed copy of their lua51.dll
, renamed to support the assumptions under which cURL.dll
was linked. It should be put in the same folder where you put cURL.dll
.
I hope to help get the LuaDist project stable for use on Windows eventually. When that happens, cURL will get updated to its newer v3 API and become compatible with Lua 5.2 (and maybe even Lua 5.3 by that point).
Repository and Checkins
All of the code supporting this tool is in a public fossil repository. This post documents work that was checked in as [a6505fd2e2].
To play with this utility your self, you will want to do some of the following:
- Get the latest version of fossil from
- Install it in Windows by simply putting
fossil.exe
in a folder on yourPATH
. You will be using it from a command prompt, so if you’ve edited the systemPATH
to do this, you will want to restart any open command prompts so they are current. - Create a working folder somewhere. Inside your
My Documents
folder is fine. We’ll assume it isC:\Users\You\Documents\wpcli
, but it really could be anywhere. Open a command window andcd
to that folder. - Clone the repository with a command like this:
fossil clone http://chiselapp.com/user/rberteig/repository/WPCLI wpcli.fossil
- Open the repository in some folder so you can see its content and work with it. The easiest folder to use is the one you are standing in right now, and that is both safe and reasonable:
fossil open wpcli.fossil
- You now have a tree of files rooted in the current folder. The
wprpcxml
folder has the version that we abandoned after part 3. Thewprest
folder has the version based on theREST
API.
Later you will want to update that working copy to track changes made in the public repository. That is easy to do: with an open command window, cd
into the working folder, then say fossil update
.
Written with StackEdit.