Tags

, , , , , , ,

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:

  1. Get the latest version of fossil from
  2. Install it in Windows by simply putting fossil.exe in a folder on your PATH. You will be using it from a command prompt, so if you’ve edited the system PATH to do this, you will want to restart any open command prompts so they are current.
  3. Create a working folder somewhere. Inside your My Documents folder is fine. We’ll assume it is C:\Users\You\Documents\wpcli, but it really could be anywhere. Open a command window and cd to that folder.
  4. Clone the repository with a command like this: fossil clone http://chiselapp.com/user/rberteig/repository/WPCLI wpcli.fossil
  5. 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
  6. You now have a tree of files rooted in the current folder. The wprpcxml folder has the version that we abandoned after part 3. The wprest folder has the version based on the REST 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.