Import bookmarks from Goodlinks JSON

Hi all,

Basically the title. I am trying to get my bookmarks from the Goodlinks app into DTP. However the only way goodlinks export the data is in .json format.

So is there a way to import it into DTP in one go? I couldn’t find a useful answer upon briefly searching in the forum history hence posting it here. Apologies if this has been already discussed.

A sample JSON would be helpful. In general, it should be a simple job for a JXA script. Perhaps also possible with Applescript.

Edit Never mind. The JSON structure seems to be

[{ url: , 
  state: ,
  labels:, 
  saved_at:, 
  published_at: ,
}, …]

So, a JXA script would look something like that

(() => {
  const goodlinksFile = '/path/to/JSONfile';
  const app = Application('DEVONthink 3');
  const err = $();
  const JSONString = $.NSString.withContentsOfFileEncodingError(
    $(goodlinksFile), $.NSUTF8StringEncoding, err);
  const goodlinks = JSON.parse(JSONString);
  goodlinks.forEach(link => {
     const r = app.createRecordWith({
       type: 'bookmark', 
       url: link.url, 
       creationDate: new Date(link.saved_at)
    });
    r.name = r.proposedFilename();
  })
})()

I can’t test this code since I don’t have a JSON export from Goodlinks.

You can run it in Script Editor (after copy/pasting it there and having set the language selector in its upper left to JavaScript) or with osascript -l JavaScript script.js on the command line after having saved the script to script.js.
Before doing any of that, you must adjust the path to your JSON file in the 2nd line.

The DT records are created in the global inbox by default. To change that, add

, {in: targetGroup}

to the end of the createRecordWith call and define the targetGroup before the forEach loop, eg with createLocation.

1 Like

Hey thanks for your help.

I am out right now so couldn’t share you the file. Once I am on my desk I will try this out, or will share the file if it doesn’t work.

I tried the above script as per the instructions however it failed to run, giving some syntax errors which I couldn’t understand. I have sent you the json file if you could kindly try it out at your convenient time. Thanks.

In general, even if you don’t understand the error message, someone else might. So, posting it is always a good idea. Except, maybe, in this case, since the script was riddled with bugs. My apologies.

Here’s a revised version that processes your bookmark samples. It saves the bookmarks in the database “Test”, group “goodlinks”. Change the parameters in app.createLocation to whatever database and group you want.
I’d have preferred to set the creationDate in the createRecordWith call, but that didn’t work (no error message, nothing). Doing it after the record is created with an assignment works. Perhaps @cgrunenberg knows what I did wrong in createRecordWith.

(() => {
  const goodlinksFile = '/Users/ck/Downloads/GoodLinks-Export-2025-02-06-16-59.json';
  const app = Application('DEVONthink 3');
  const targetGroup = app.createLocation('/goodlinks', {in: app.databases['Test']});
  const err = $();
  const JSONString = $.NSString.stringWithContentsOfFileEncodingError(
    $(goodlinksFile), $.NSUTF8StringEncoding, err);
  const goodlinks = JSON.parse(JSONString.js);
  goodlinks.forEach(link => {
     const seconds = link.addedAt | 0;
	 const microseconds = ((link.addedAt - seconds) * 1000000) | 0;
     const r = app.createRecordWith({
       type: 'bookmark', 
       URL: link.url, 
    }, {in: targetGroup} );
    r.name = link.title || r.proposedFilename();
    r.creationDate= new Date(seconds * 1000 + microseconds / 1000);
  })
})()

Edit Fixed the typo that @BLUEFROG discovered.

I tried this one and got this error:

P.s. I am not a programmer at all, so the mistake could be very basic. So kindy bear with me.

I’m bearing with you, but posting a screenshot of dimmed source code partially hidden behind an error message is not the best thing to do. Copy the code from script editor and post it here included in three backticks, like so
```
code goes here
```
You seem to have used curly single quotes in your createLocation parameters (and that would have been a lot easier to see in the code, btw). Compare the quotes there with the one in the Application call.

Don’t use an external program like TextEdit or Word or Pages or whatever cute and “intelligent” software proposes to use “typographic” or “smart” quotes. Type directly in script editor and everything will be fine.

Oh k, as you see I am pretty illiterate in code but trying to follow you here.
Yes I was copying the script in Text Edit first, but now I tried it again by directly copying it to script editor, however it still showed a syntax error as Expected expression but found “>”.
This was the script I used:
(I have just changed the file path, target database and target group names. Rest is the same)

(() => {
  const goodlinksFile = '/Users/addee/Desktop/GoodLinks-Export-2025-02-06-16-10.json';
  const app = Application('DEVONthink 3');
  const targetGroup = app.createLocation('/Goodlinks', {in: app.databases['PERSONAL']});
  const err = $();
  const JSONString = $.NSString.stringWithContentsOfFileEncodingError(
    $(goodlinksFile), $.NSUTF8StringEncoding, err);
  const goodlinks = JSON.parse(JSONString.js);
  goodlinks.forEach(link => {
     const seconds = link.addedAt | 0;
	 const microseconds = ((link.addedAt - seconds) * 1000000) | 0;
     const r = app.createRecordWith({
       type: 'bookmark', 
       URL: link.url, 
    }, {in: targetGroup} );
    r.name = link.title || r.proposedFilename();
    r.creationDate: new Date(seconds * 1000 + microseconds / 1000);
  })
})()

Is the language for the script in Script Editor set to JavaScript?

And this…

    r.creationDate: new Date(seconds * 1000 + microseconds / 1000);

… should be this…

    r.creationDate = new Date(seconds * 1000 + microseconds / 1000);

Ah… this did it finally.

Yes I had mistakenly forgot to select language as JavaScript. Also made the correction you suggested and it worked!

Thanks a lot to you and @chrillek for all your help.

You’re welcome though @chrillek did the heavily lifting. I would have recommended an AppleScript option as that’s my forte, but busy, busy day :slight_smile:

To parse JSON? Certainly feasible, with the appropriate TIDs.

You guys helped me run my first script ever haha. Its almost magical, making two programs interact with each other without having to touch either of them. Brilliant, but ofc a trivial thing for the programmers :wink:

2 Likes

That is part of the beauty and fun of automation. I’ve long said, “There are two kinds of computer users: those that do what the computer lets them and those that make the computer do what they want.”

But do realize, it also can also be a frustrating, hair-pulling pursuit at times. :slight_smile:

3 Likes

Indeed.

I’d like to add that creationDate can be set in the call to createRecordWith if it’s used like that:

const r = app.createRecordWith({
       type: 'bookmark', 
       URL: link.url, 
       'creation date': new Date(seconds * 1000 + microseconds / 1000);
    }, {in: targetGroup} )

That is, the property must be creation date in the call to createRecordWith. I didn’t read the scripting dictionary attentively enough to notice that at the time.