For this month’s “12 Apps in 12 Months” challenge I decided that I wanted to try and import my WordPress posts into the Day One app. Given that Day One is now owned by Automattic who also “own” WordPress I was surprised that something like this didn’t already exist as an option within the app.
Into the Unknown
As a local app, albeit one with cloud storage and syncing, there isn’t an API for Day One. Instead there is a CLI which you can use to create new entries from the command line. This was the approach I took to get the posts from WordPress to Day One.
My first concern was that I would be testing my script against my live Day One app – there isn’t any testing facility – and that I might jeopardise …
For this month’s “12 Apps in 12 Months” challenge I decided that I wanted to try and import my WordPress posts into the Day One app. Given that Day One is now owned by Automattic who also “own” WordPress I was surprised that something like this didn’t already exist as an option within the app.
Into the Unknown
As a local app, albeit one with cloud storage and syncing, there isn’t an API for Day One. Instead there is a CLI which you can use to create new entries from the command line. This was the approach I took to get the posts from WordPress to Day One.
My first concern was that I would be testing my script against my live Day One app – there isn’t any testing facility – and that I might jeopardise my nearly 7,000 existing entries. However, there wasn’t any way around this so I took the plunge but only wrote a very small number of posts at a time until I was confident in what I was doing.
But will it work?
Installation of the CLI is relatively straight forward. From a terminal prompt you execute:
sudo bash /Applications/Day\ One.app/Contents/Resources/install_cli.sh
Once installed creating a new entry (that’s pretty much all the CLI can do) is simply issuing a command such as this:
dayone2 --journal "Blogs" --date "2013-06-18 17:17" -- new "This is the entry text"
No!
Unfortunately I immediately found a problem in the form of this very unhelpful error message:
neilthompson@Neils-MacBook-Pro bookshelf % dayone2 -h
dyld[71355]: Library not loaded: u/rpath/CLIKit.framework/Versions/A/CLIKit
Referenced from: <A49787EE-6509-36FD-A5F4-035B84E9288B> /usr/local/bin/dayone2
Reason: tried: '/usr/local/Frameworks/CLIKit.framework/Versions/A/CLIKit' (no such file), '/usr/local/bin/../Frameworks/CLIKit.framework/Versions/A/CLIKit' (no such file), '/usr/local/bin/CLIKit.framework/Versions/A/CLIKit' (no such file), '/usr/local/Frameworks/CLIKit.framework/Versions/A/CLIKit' (no such file), '/usr/local/bin/../Frameworks/CLIKit.framework/Versions/A/CLIKit' (no such file), '/usr/local/bin/CLIKit.framework/Versions/A/CLIKit' (no such file)
A quick support conversation established that this was a known bug and would be fixed in the next release.
A Worrying Development
Ok, I was in no rush so I waited until the next release and then it started working. All was fine for a couple of weeks until I upgraded my Mac to the latest MacOS 26 (Tahoe), and from then on MacOS wouldn’t open dayone2 identifying it as malware. So I went back to support, and was told that this was a known bug and would be fixed in the next release…
When then next release came, I tried and now I got told that dayone2 didn’t exist! Once more back to support, and this time I was told that they had changed the name fo the command from dayone2 to dayone. Grrr!
At least Support were responsive both in responding to my requests and fixing the issues. I suppose I should be grateful for that.
WordPress to Day One
Now that I had got the command working and writing entries to Day One I could turn my attention to automating the process. As a starting point I used the code from my previous WordPress to PDF project which handled the extracting of posts from WordPress. The code from that project is available here.
Markdown
If you have used Day One you will know that it supports a very small subset of formatting options, basically not much more than bold, italic, underline and some heading styles. Similarly the CLI only accepts either plain text, if you aren’t worried about formatting, or Markdown. WordPress, however, spits out HTML and so there needed to be some massaging of that before it could be passed to Day One.
Fortunately, there is a Composer package to convert HTML to Markdown and so I used that. Setting it up and doing the conversion is simplicity itself:
use League\HTMLToMarkdown\HtmlConverter;
use League\HTMLToMarkdown\Converter\TableConverter;
$converter = new HtmlConverter([
'strip_tags' => true,
'hard_break' => true
]);
$converter->getEnvironment()->addConverter(new TableConverter());
// convert HTML to Markdown
$markdown = $converter->convert($html);
This, though, was only part of the solution.
What About Shortcodes, Script and Styles?
WordPress is such a rich environment supporting hundreds if not thousands of third-party plugins extending the functionality of the core platform. The issue here was how to deal with all of these? Sadly, the only real solution to this was to silently drop them. The same applies to inline scripts and styles which werent going to be supported in Day One.
// remove scripts, styles, inline images and shortcodes like [gallery ids="..."]
$html = preg_replace('#<script.*?<\/script>#is', '', $html);
$html = preg_replace('#<style.*?<\/style>#is', '', $html);
$html = preg_replace('/\[[^\]]+\]/', '', $html);
What About Images?
The other problematic area was that of images. Both WordPress and Day One support images (and other media too) although the latter only accepts ten attachments per post via the CLI. This left me with something of a problem – how would I deal with WordPress posts that had multiple images spread throughout the text and had more than ten? Which ones would I choose to keep? Which ones would I drop? Where would I put them?
Featured Image
The initial version released got round this issue by ignoring all inline images other than the featured image. This was achieved with this Regex:
$html = preg_replace('#<img[^>]*>#i', '', $html);
Multiple Images
I then worked out that you could pass ten images (the maximum Day One will accept) and place them (roughly) where they would be in the text using the [{attachment}] placeholder. Here’s an example of that:
# The Header
[{attachment}]
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ut ligula sed leo pharetra hendrerit.
[{attachment}]
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ut ligula sed leo pharetra hendrerit.
[{attachment}]
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ut ligula sed leo pharetra hendrerit.
[{attachment}]
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ut ligula sed leo pharetra hendrerit.
[{attachment}]
Then the call to the CLI would look like this:
dayone --journal "Blogs" --date "2013-06-18 17:17" --tags "2013" "Family" "Five Generations" --attachments "/Users/neilthompson/Downloads/image1.png" "/Users/neilthompson/Downloads/image2.png" "/Users/neilthompson/Downloads/image3.png" "/Users/neilthompson/Downloads/image4.png" "/Users/neilthompson/Downloads/image5.png" -- new < "/Users/neilthompson/Development/wordpress-to-dayone/src/input.txt"
This would mean that it would be possible to include the first nine inline images (or ten if there was no featured image) in the post. It still wouldn’t work for galleries or posts where there were more than ten images but it was better than nothing.
This means reworking the code which I am still in the process of doing so if you need this feature wait for the v2 release.
Extension snafu
One thing to note is that when passing attachements to the the CLI you MUST include an extension. If you don’t you will get an error like this full of meaningless junk not related to the actual issue at all.
<App> User requested to set logging to verbose.
<Core> [DOFeatureFlagsManager] Setting context <DOCore.DOManagedObjectContext: 0xc46c0a280> on manager DOCore.DOFeatureFlagsManager.
<Core> [Keychain] Unable to Fetch All Items: Error Domain=com.samsoffes.sskeychain Code=-34018 "A required entitlement isn't present." UserInfo={NSLocalizedDescription=A required entitlement isn't present.} - SSKeychainQuery Service [Day One UDID] AccessGroup [5U8NS4GX82.dayone-access-group]
<Core> [Keychain] Failure saving new Keychain Item: Error Domain=com.samsoffes.sskeychain Code=-34018 "A required entitlement isn't present." UserInfo={NSLocalizedDescription=A required entitlement isn't present.}<br><Core> Error saving to keychain: A required entitlement isn't present.
CoreData: debug: WAL checkpoint: Database did checkpoint. Log size: 1031 checkpointed: 1031
Wrapping up
This project has been interesting in that I have encountered a number of issues with the Day One CLI that have required me to go back to their support a number of times to get a resolution. This is a reminder that when undertaking projects such as this you are beholden to the third party to keep their API/CLI etc working and up-to-date, something you have no control over.
For now it is working and you can find the v1 code here and check back here for v2 which will support multiple images.