How to use a URL command outside the browser

I have created a swift application and I would like to build a direct export out of my application into DT. In other words I want to build native support for DT in my application.

For now I only do this on MacOS.

When I check the URL commands help I can see that I need to use the createHTML command. Before I can start using this I want to make certain I have the basics right, so I start with the example of the help:

x-devonthink://createRTF?title=New%20bookmark&location=http%3A%2F%2Fwww.devontechnologies.com&noselector=1

If I go into my browser it works. When I put this command in the URL bar, the command is executed and the document is created. This works both in Safari and Waterfox.

I don’t want to use a browser as intermediary. I want to call DT directly. So I tried the following command from the command line (terminal):

curl 'x-devonthink://createRTF?title=New%20bookmark&location=http%3A%2F%2Fwww.devontechnologies.com&noselector=1'

This fails with the message: curl: (1) Protocol “x-devonthink” not supported or disabled in libcurl
I also tried curl from brew with similar results.

I then finally tried this from Swift which is my ultimate goal:

let url = URL(string: "x-devonthink://createRTF?title=New%20bookmark&location=http%3A%2F%2Fwww.devontechnologies.com&noselector=1")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
    if let error {
        print("Error: \(error)")
        return
    }
    guard let data = data else { return }
    print(String(data: data, encoding: .utf8)!)
}

I get this error:

Error: Error Domain=NSURLErrorDomain Code=-1002 "unsupported URL" 

Note that I left the UserInfo part of the error out for brevity.

When I read other posts about this the seem to mention that I need to add x-devonthink to the Queried URL Schemes in the Info.plist of my applicaiton. Just to be certain I have done this but it does not seem to have any effect.

So does someone know how I can call this command directly without using a browser as intermediate?

1 Like

Are you sure that URLSession is the right class here? I’m just asking, I have no idea of Swift programming whatsoever. But perhaps using NSWorkspace and its openURL method would help?

If I understand the documentation correctly, URLSession is for connections, which x-devonthink-item URLs are not about – they’re more like commands. This short JXA script seems to do what you want:

ObjC.import('AppKit');
(() => {
    const url = $.NSURL.URLWithString($("x-devonthink://createRTF?title=New%20bookmark&location=http%3A%2F%2Fwww.devontechnologies.com&noselector=1"));
    const sharedWS = $.NSWorkspace.sharedWorkspace;
    sharedWS.openURL(url);
})();

It should be trivial to re-write in Swift, perhaps using more sophisticated variants of openURL.

1 Like

You’re right, application-specific URL schemes like the one of item links don’t support up/downloading. They’re only intended to launch an app to perform certain tasks. But for directly supporting DEVONthink I would recommend AppleScript, JXA or the scripting bridge instead.

Thanks. I will investigate further using these tips.

That’s it. This works:

let url = URL(string: "x-devonthink://createRTF?title=New%20bookmark&location=http%3A%2F%2Fwww.devontechnologies.com&noselector=1")!
NSWorkspace.shared.open(url)

I spent many, many hours to find find this solution. There was a solution for IOS but not for MacOS.

Appreciated.

For future reference, this is the solution I ended up with:

static let devonThinkCommand = "x-devonthink://createHTML?title=%Title&location=%Location&source=%Source"

private func encodedString(_ string: String) -> String? {
    string.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)?
        .replacingOccurrences(of: ";", with: "%3B")
        .replacingOccurrences(of: "&", with: "%26")
}

func exportToDevonThink(title: String, html: String, url: URL) {
    if let html = encodedString(html), let title = encodedString(title) {
        let command = Self.devonThinkCommand
            .replacingOccurrences(of: "%Title", with: title)
            .replacingOccurrences(of: "%Source", with: html)
            .replacingOccurrences(of: "%Location", with: url.absoluteString)
        if let url = URL(string: command) {
            NSWorkspace.shared.open(url)
        }
    }
}

Please note that I ended up encoding the title and source using percent encoding for the category urlQueryAllowed but through trial and error I found out that I also needed to encode the semicolon and ampersand.

The documentation does not specify how to encode the values for title and source. When I initially tried out my solution I got incomplete content. This lead to adding the semicolon and ampersand encoding. Other encodings may be needed, I just need to run into problems to find out :slight_smile:.

That’s Swift? The encoding of the parameters is standard URL stuff: the query string is appended after a question mark, its parts are separated with ampersands. There’s a variant using semicolons instead of ampersand.

Yes this is swift and I understand that the command is a “standard” URL with ampersands and a question mark to separate the query string from the rest.

It is just that using addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) I would expect that the semicolon and ampersand encoding would be automatically handled if they are not allowed in a query string. Apparently that is not true, I have to explicitly encode them to not break the DT importer.
Having a plain semicolon in the text causes DT to ignore the rest even if the separators are ampersands.

I just read the “documentation” of addingPercentEncoding:withAllowedCharacters: Mind blowing. Is it actually forbidden at Apple’s to provide examples, maybe for religious reasons? Anyway, I have no idea what that piece of software should do. OTOH, DT is probably using something similar to parse the incoming URL, so that stumbling upon some weirdness there is to be expected.

In JavaScript, I’d use encodeURIComponent, which does what it says. Perhaps too simple a concept for Apple :wink:

1 Like

:+1:t2: me too :slight_smile: