Org Mode: Format Libby book highlights exported as JSON

| emacs, org

The Toronto Public Library (and many other libraries) offers e-book access through Overdrive, which I can read through the Libby app on my phone. It turns out that I can select passages to highlight. It also turns out that I can use the Reading Journey view to export the highlights as JSON, even for books I've returned. This is what the JSON looks like.

{
  "version": 1,
  "readingJourney": {
    "cover": {
      "contentType": "image/jpeg",
      "url": "https://img1.od-cdn.com/ImageType-100/7635-1/{B41A3269-BC2A-4497-8C71-0A3F1FA3C694}Img100.jpg",
      "title": "How to Take Smart Notes",
      "color": "#D9D9D9",
      "format": "ebook"
    },
    "title": {
      "text": "How to Take Smart Notes",
      "url": "https://share.libbyapp.com/title/5796521",
      "titleId": "5796521"
    },
    "author": "Sönke Ahrens",
    "publisher": "Sönke Ahrens",
    "isbn": "9781393776819",
    "percent": 0.313229455252918
  },
  "highlights": [
    {
      "timestamp": 1729898852000,
      "chapter": "13       Share Your Insight",
      "percent": 0.824912451361868,
      "color": "#FFB",
      "quote": "For every document I write, I have another called “xy-rest.doc,” and every single time I cut something, I copy it into the other document, convincing myself that I will later look through it and add it back where it might fit. Of"
    },
    {
      "timestamp": 1729898760000,
      "chapter": "13       Share Your Insight",
      "percent": 0.801566108949416,
      "color": "#FFB",
      "quote": "I always work on different manuscripts at the same time. With this method, to work on different things simultaneously, I never encounter any mental blockages"
    },
    ...
 ]
}

I want to save those highlights in my books.org file for easy searching, grouping the highlights by chapter. The following code helps with that:

(defun my-org-insert-book-highlights-from-libby (url)
  (interactive "MURL: ")
  (let-alist (plz 'get url :as #'json-read)
    (insert
     "* "
     .readingJourney.title.text
     " - "
     .readingJourney.author
     "\n")
    (org-set-property "ISBN" .readingJourney.isbn)
    (org-set-property "COVER" .readingJourney.cover.url)
    (org-set-property "TITLE" .readingJourney.title.text)
    (org-set-property "AUTHOR" .readingJourney.author)
    (insert (org-link-make-string .readingJourney.title.url .readingJourney.cover.url)
            "\n")
    ;; sort the highlights by chapter
    (insert
     (mapconcat
      (lambda (row)
        (concat "** " (replace-regexp-in-string " +" " " (car row)) "\n"
                (mapconcat (lambda (quote)
                             (concat "#+begin_quote\n"
                                     (alist-get 'quote quote)
                                     "\n#+end_quote\n\n"))
                           (cdr row)
                           "")
                "\n\n"))
      (seq-group-by
       (lambda (o) (alist-get 'chapter o))
       (sort .highlights
             :key (lambda (o) (alist-get 'percent o))))))))

This is what the resulting document looks like:

* How to Take Smart Notes - Sönke Ahrens
:PROPERTIES:
:ISBN:     9781393776819
:COVER:    https://img1.od-cdn.com/ImageType-100/7635-1/{B41A3269-BC2A-4497-8C71-0A3F1FA3C694}Img100.jpg
:TITLE:    How to Take Smart Notes
:AUTHOR:   Sönke Ahrens
:END:
https://img1.od-cdn.com/ImageType-100/7635-1/{B41A3269-BC2A-4497-8C71-0A3F1FA3C694}Img100.jpg
** 1  Everything You Need to Know
#+begin_quote
 never force myself to do anything I don’t feel like. Whenever I am stuck, I do something else.”
#+end_quote

#+begin_quote
Luhmann’s only real help was a housekeeper who cooked for him and his children during the week, not that extraordinary considering he had to raise three children on his own after his wife died early.
#+end_quote

...
This is part of my Emacs configuration.
View org source for this post
You can comment with Disqus or you can e-mail me at sacha@sachachua.com.