Tags
CLI, featured image, Lua, media, media library, WordPress.com, WP
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.)