Tags

, , , , , ,

This is an article in a series of unknown length discussing a collection of tools written in Lua to publish posts on a WordPress blog from a PC. The source code is available in a public fossil repository.

In this installment we will implement the wpmedia.lua to upload media to the blog’s media library, and integrate that ability with the wpost.lua utility to allow uploading an image along with a new post, setting the post’s featured image.

Why?

Useful posts often contain in-line images and links to media of various sorts. WordPress.com supports a media library that allows storage of various image formats, and with a paid upgrade, also permits audio and video formats. These media assets can be referred to in posts, and also have an attachment page of their own where various metadata such as title, caption, and description can be specified. In general, it is easier to refer to media from posts written in Markdown if the media has already been uploaded to the blog. That gives you URLs for the image and its attachment page that will remain stable as long as the image is not deleted.

Creating a new script to upload images rather than re-using the existing new post tool makes sense because images have their own API and metadata and so will need a distinct collection of command line options. We will create the wpmedia.lua script for the purpose, and initially give it the ability to upload to the library. The wplist.lua script is already able to list content from the library with the --list media option, and the wpget.lua script can retrieve the full description of a piece of media with its --media option.

Posts themselves can have one or more attached images, and also can designate an image as the post’s “featured image“. The actual display of a featured image is entirely at the whim of the specific theme installed. Many themes available for use at WordPress.com have some form of support for them. They are normally used to provide a thumbnail image for the post and/or a primary image on the post’s page. The details will vary depending on image size, presence or absence of other images in the post body as well as the post’s format. (Posts formatted “image” are really likely to display it, for instance.) You will need to consult your theme’s documentation and experiment to find out how it works.

Given the special status of a featured image for a post, it seems appropriate to extend wppost.lua to add the ability to upload an image and feature it on a new post. Actually getting this to work involves reading between the lines of the existing API documentation so well will discuss it further below. While fiddling with wppost.lua it was easy to also add --status, --format, and --date options. --format is handy to immediately set the post to image format. The other two allow for making a post something other than a draft, so that it can be uploaded, categorized, tagged, titled, get a featured image, and be scheduled for future publication all with a single command.

Aside: REST API Source Code

The REST API to WordPress.com hosted (or any using the Jetpack plugin) blogs is implemented by the Jetpack plugin, which happens to be open source. Like the rest of the WP platform, it is written in PHP, which is in broad strokes fairly readable if you have experience with Perl and any C-like language, and a basic understanding of how the web building blocks of HTTP, HTML, CSS, and Javascript work together to befuddle, amuse, and amaze the typical user of a web site.

Implementation

The actual dirty work is found in the new file wpmedia.lua, as well as changes in cli/wp.lua and wppost.lua. As usual, see the repository for the complete source code, I’ll be quoting just the interesting bits here.

The best documentation of the media related endpoints described version 1.1 of the REST API, while most of the endpoints we are using in these tools is still based on version 1. Since the API version is reflected near the beginning of the API URL, we’ve extended the code in common.handleconfig() to also create args.baseurl_v1_1, and tweaked wp.getList() to use that base for the media-related lists.

With that change, the new function wp.editMedia() can be written to handle both new media uploads as well as edits to the description of an existing media item. A new upload results from setting id to “new”, and an edit from setting it to the ID of any existing media item. Either way, the request parameters are provided in the table passed in post.

---
-- Create or update a media file a WordPress blog found at args.baseurl, 
-- using the OAuth token.
function M.editMedia(id, post)
  if not id then return nil, "Not a valid post ID" end
    return M.REST(args.baseurl_v1_1..[[/media/]]..tostring(id)..[[?context=edit&http_envelope=true]], post)
end

The script wpmedia.lua is largely a copy of the top of any of the other scripts, with a block of command line argument documentation and the usual invocations of the functions from the cli.common module that implements most of the common options. It has a few command line options that are specific to media uploads, naturally.

Those options are used to build the request parameters for the new media item. The use of a table with file and type fields will signal the cURL wrapper to set up for a POST request with Content-Type: multipart/form-data; and an appropriate boundary string. The type field is the [MIME][] type of the media, which is most likely image/jpeg, image/png or image/gif. The media library supports other document types (the full list is given as “pdf, doc, ppt, odt, pptx, docx, pps, ppsx, xls, xlsx, key” without the upgrade to include video and audio types) but the aside from PDF I don’t have any personal incentive for supporting the others. Also, it is not obvious what Content-Type to apply to most of those formats.

---
-- Actual main body of the script. 
--
local post = {
  ["media[]"] = { 
    file = args.filename, 
    type = mimetype(args.filename)
  },
  ["attrs[][title]"]  = args.title or args.filename,
  ["attrs[]"] = args.caption,
  ["attrs[][description]"] = args.description,
}
local ok, res = wp.editMedia("new", post)
if ok then
  local body = ok.body or ok
  if body.error then
    lapp.error(body.error ..  "n" .. body.message, true)
  end
  print("wppost success: New post ID=".. body.ID)
  if args.container then
    args.out:write(pretty.write(body))
    args.out:close()
  end
else
  lapp.error(res)
end

The balance of the script calls wp.editMedia("new",...) and interprets the table it returns.

New post with featured image

Extending wppost.lua to include a featured image is relatively straightforward. The documentation is somewhat murky about some of the details, and so a little experimentation was used to produce a working sequence of events.

We first added the new command line option --featured to name the image file and signal the extra processing. If the option is present, we follow a similar pattern to wpmedia.lua to build up a call to wp.editMedia() naming the file to upload. We also fill out its title attribute to match the post title from --title for foolish consistency. If desired, it would be trivial to add support for the featured image’s caption and description, but that is left as an exercise.

local ok, feat, body, res

-- upload the featured image to the media library.
if args.featured and not args.featured:match"^https?://" then
  local post = {
    ['media[]'] = { 
      file = args.featured, 
      type = wp.mimetype(args.featured)
    },
    ['attrs[][title]']  = args.title or args.filename,
  }
  ok, res = wp.editMedia("new", post)
  if not ok then lapp.error(res) end
  feat = ok.body or ok
  if feat.error then
    lapp.error(feat.error ..  "n" .. feat.message, true)
  end
  print("wppost success: New media ID=".. feat.media[1].ID)
  print("    URL=".. feat.media[1].URL)
end

The actual post goes up next, using code pretty much unchanged from the previous version. The only substantive change is to actually implement some request parameters that were relegated to an argument that was simply not passed to wp.newPost(). This allows the introduction of the --format and --status, and --date options. Note that we are careful in this step to not set the featured_image request parameter.

Finally, we call wp.editPost() to edit the post we just created to include the featured image we uploaded at the first step.

With this specific sequence, and by using the integer post ID returned from the upload as the value of the featured_image request parameter, the uploaded image is referenced from the media library without duplication. Note that when the post object is examined (either the table returned from wp.editPost() or by retrieving the post container with wpget.lua), the featured_image field will contain an URL not a post ID. It is legal to pass an URL to wp.editPost(), but that will cause the image to be copied from that source URL into the library and be treated as an attachment to this post. While that might be desirable if you are referencing an image at an arbitrary URL (for instance, from Flickr) it isn’t the right answer for an image already in the blog’s media library.

What Next?

The next big step is to think about integrating a fossil repository full of posts written in Markdown with a blog, using the WP CLI tools as the communications channel. Doing this successfully will likely drive changes in the tools so that PC side scripting can avoid reposting document that have not been edited on the PC. Allowing for edits made in the WP web UI would be bonus.

Another big step to consider is handling for media in addition to the post’s featured image. WP supports pictures, audio, and video. Being able to parse a list of images out of the post body, and for those stored locally upload them and re-write the post body to refer to the uploaded copies would have value.

Repository and Checkins

All of the code supporting this tool is in a public fossil repository. See the discussion at the tools page for how to get started using this repository. This post documents work that was checked in as [49be3d080e].

The Rest of the Series

  • 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.
  • Part 6 implemented posting and added the ability to set title, tags, and categories from command line options.
  • Part 7 added a new utility and created modules common to all our utilities.
  • We digressed to build a tiny embedded web server to make authentication easier.
  • Part 8 used the Spoon server to make authentication go smoothly.
  • Part 9 built a tool to list various containers for a blog.

(Written with StackEdit.)