Automating DT with JavaScript: an example

The following code is the solution (I hope) to a question in another thread (AppleScript problems with dates and custom metadata - #15 by chrillek). To summarize:

  • the OP imported entries from the journalling app Dayone into DT as markdown records
  • these records can contain links to other Dayone entries
  • the OP wanted a way to have these links resolved to the corresponding DT record (remember, DT contains the same stuff as Dayone)
  • His DT records are named like this “Wednesday 21 Juli 2021”

Now, Dayone does not support any sensible automation, and the links between journal entries contain only a UUID (i.e. a weird string without any useful information). But in order to find the approprate markdown record in DT, we need the date of the Dayone entry. Since Dayone uses SQLite to store its data, we can use a shell command and a SQL query to get at it using the UUID. So we can find out the date for a Dayone entry with a certain UUID that way. Once it’s available, the rest is a piece of cake. Kind of. In the end, it adds links to the corresponding DT record for every Dayone link. I decided to do it that way, because replacing the original Dayone link would have been far too cumbersome and error-prone. The OP wanted to use a custom meta data field for the DT links, which does not really help if there’s more than one link in a record.

The code is heavily commented (for my standards, anyway). I post it here because I found the challenge interesting and the solution not too bad. So it is kind of an educational project :wink:

Admittedly, the original problem is quite special. But maybe the script can answer some (not yet asked) questions about automating DT with JavaScript. And I’m of course happy to answer any additional ones that may arise.

(() => {
  const app = Application("DEVONthink 3");
  app.includeStandardAdditions = true; 
  const databaseName = ""; /* Add database name if you want to search for DT records only in this DB */
  const DBOption = {} /* Search all databases, otherwise see next if statement */
  if (databaseName !== "") {
    /* Get the database record and setup the "in" option for search (see below) */
    var DTdatabase = app.databases[databaseName].root;
    DBOption["in"] = DTdatabase;
  }
  const username = "ck" /* CHANGE to _your_ username ! */
  /* 
    The currentApplication is needed for doShellScript, 
    since this method is only avaiable at that object.
  */
  const curApp = Application.currentApplication(); 
  curApp.includeStandardAdditions = true;
  if (app.selectedRecords.length === 0) {
    app.displayAlert("No records selected!");
    return;
  }
  /* Setup formats for english alpha weekdays and months */
  const locale = "en-EN";
  const weekdayFmt = new Intl.DateTimeFormat(locale, {weekday: "long"});
  const monthFmt = new Intl.DateTimeFormat(locale, {month: "long"});
  
  /* setup constant part of the shell command */
  let baseCMD = `sqlite3 "/Users/${username}/Library/Group Containers/5U8NS4GX82.dayoneapp2/Data/Documents/DayOne.sqlite" 'select zgregorianyear||"-"||printf("%02d",zgregorianmonth)||"-"||printf("%02d",zgregorianday) from zentry where zuuid="`;
  
  
  const rec = app.selectedRecords();
  /* 
     For every selected record:
     - get its text 
     - find all occurences of links to dayone entries and loop over them
  */
  rec.forEach(r => {
    const pt = r. plainText();
    const DOlinks = pt.matchAll(/entryId=([A-F0-9]+)/g);
    /* 
     For every link to a dayone link in this record
      - extract the DO uuid from the link
      - find the corresponding entry in dayone using sqlite
      - return the DO entry's date in ISO format (yyyy-mm-dd)
      - convert this date to a "Weekday d[d] Month yyyy" format
      - search all records with this name, possibly only 
            in the database "databasename" (see start of script)
      - if exactly one record matches, 
            add a link to it to the current DT record (see outer loop
    */
    
    for (const l of DOlinks) { 
      /* l[1] contains the first capturing group, i.e. the DO uuid */
      const shellCmd = `${baseCMD}${l[1]}"'`;
      const DOdate = curApp.doShellScript(shellCmd);
      /* create Date object from string. Note: Apples JXA needs two-digit
          months and days, those are produced by the SQL statement */
      const DTdate = new Date(`${DOdate}`);

     /* Prepare parts for the name of the DT record for DTdate */
      const weekday = weekdayFmt.format(DTdate);
      const month = monthFmt.format(DTdate);

     /* Assemble the parts of the DT record's name: weekday day month year */
      const DTname = `${weekday} ${DTdate.getDate()} ${month} ${DTdate.getFullYear()}`
      const DTsearch = `name==${DTname}` 

      /* Search for a DT record with matching name, 
        barf if none or more than one are found */
      const foundRec = app.search(DTsearch, DBOption);
      if (!foundRec || foundRec.length === 0) {
        app.displayAlert(`No record found for "${DTsearch}"`);
      } else if (foundRec.length > 1) {
        app.displayAlert(`More than one record found for "${DTsearch}"`);
      } else {
        /* Add link to current record of external loop, format is
         Dayone Dayone UUID: [DT <DT record's name>](<DT URL>)*/
        let uuid = foundRec[0].uuid();
        let DTlink = `[DT ${DTname}](x-devonthink-item://${uuid})`;
        r.plainText = pt + `\n\nDayone ${l[1]}: ${DTlink}\n\n`;
      }
    } /* for DOlinks */
  }) /* rec.forEach */
  
})()
4 Likes

I want publicly to acknowledge the huge help @chrillek gave me with my niche project (a project also the subject of this tortuous thread). He gave unstintingly of his time and expertise and, in the process, has saved me hours of work.

If you ever need a plethora of brackets :grinning: and some real JavaScript expertise @chrillek is your man!

Stephen

2 Likes

Thanks for the warm words. It was actually an interesting exercise.