Making a Joplin API in C#

If you've been here before, you might know I like Joplin and C#. Naturally, I want to manipulate Joplin using C#. I've been doing that since Joplin's API was first released; mostly by sending plain HTTP requests. Then, when working on Ghoplin, I've created the first draft of an API - but that was mostly a collection of methods that did what Ghoplin needed and not much else. And it did not interop with LINQ at all.

The more I integrate with Joplin, the more I miss a decent API, and especially LINQ integration, so I've started making one. (This is of course done in my spare time and in the evenings, so it's gonna take some time.)

The post on generating a query from an Expression in C# was the first stab at this. (In there I just extract a string description of the Expression, but it can be trivially re-formatted into a URL representing the query.)  It kinda sorta worked, but I disliked it. So while I'm rewriting it for the third time, I thought I might share the non-LINQ-related part that already works: the simple CRUD operations.

Parse an HTML note, modify Markdown

Today I migrated the simplest of the workflows to use the new API. It's a script that fetches the table of contents for a specific issue of the LEVEL magazine and makes a note in Joplin.

I want to go from this HTML page:

To a note with a Table of Contents such as this:

The basic flow is simple: fetch the HTML, fix it up a little and send it to Joplin. Joplin's Web Clipper API has a great feature: when making a new note, if you send it text in the body field, it uses it directly; but if you send it in body_html, it first does an html-to-markdown conversion and stores the result.

// Create the API
var joplin = new JoplinApi("API_TOKEN_HERE");
// Load HTML
using var client = new HttpClient();
var issue = 318;
var notebook = "d4f608ec2f6e4afda6cb0cb313373ee0";
var url = $"http://www.level.cz/starsi-cisla/level-{issue}/";
string downloadString = await client.GetStringAsync(url);
var html = new HtmlDocument();
html.LoadHtml(downloadString);

var document = html.DocumentNode;
var note = document.QuerySelector(".theme-info > div:nth-child(1)").InnerHtml;
// ... further HTML processing removed for brevity
// Send the note to Joplin
var joplinNote = await joplin.Add(new Note 
    { 
        title = $"Level {level}", 
        body_html = note, 
        is_todo = 1, 
        parent_id = notebook 
    });

Until today, that is all the script did. The resulting MD required some manual modifications.

However, with the new API, I can easily extend it. The Add call returns the resulting Note, so I can immediately access its ID, or the body now converted to Markdown.

I want to do some regex manipulations and update the note, to avoid doing them manually.

var fixedBody = joplinNote.body.Replace("####", "#");
var fixedBody = Regex.Replace(fixedBody,
    @"(^\p{L}.*)",
    "## $1",
    RegexOptions.IgnoreCase
    | RegexOptions.Multiline
    | RegexOptions.CultureInvariant);
// more operations omitted
joplinNote.body = fixedBody;
await joplin.Update(joplinNote);

That is - I only need to modify the Note object returned from the first call, and pass it to Update.

At the moment, the Add/Update/Delete methods work for Notes and Notebooks. Not sure if I'm even going to need more. Perhaps tags?