Help with integrating Zotero annotation script and Tinderbox JSON parsing

Hello everyone,

I’m trying to use Tinderbox to pull in PDF annotations from my Zotero library. I have a Bash script (pasted below) that finds a PDF in my Zotero storage/ folder based on a “citation key” (like geels2016), figures out the attachment key, and calls the Zotero Web API to retrieve annotations. It then prints only a JSON array of annotation data—no extra log lines—so tools like Tinderbox can parse it easily.

Here’s the script:

#!/usr/bin/env bash

###############################################################################
# zotero_annotations_json.sh
#
# 1) CITATION_KEY: The portion before the underscore in your PDF filename,
#    e.g., "geels2016".
# 2) This script searches your Zotero "storage" folder for a PDF that starts
#    with CITATION_KEY_ (case-insensitive). 
# 3) It extracts the folder name in 'storage/' => the attachment key.
# 4) It calls Zotero Web API to get annotations, printing ONLY the raw JSON
#    (so Tinderbox can parse it directly).
###############################################################################

# --------------------- CONFIGURATION ---------------------
CITATION_KEY="geels2016"        # E.g. "geels2016"
ZOTERO_STORAGE="$HOME/Zotero/storage"  # Path to your Zotero 'storage' folder
ZOTERO_USER_ID="1234567"         # Your Zotero user ID
ZOTERO_API_KEY="YOUR_ZOTERO_API_KEY"  # Must have read permission

# If the PDF is in a group library, replace "users/$ZOTERO_USER_ID" in the URL
# with "groups/<GROUP_ID>" below.

# ------------------------------------------------------------------------------

# 1) Find the matching PDF.
foundPDF=$(find "$ZOTERO_STORAGE" -type f -iname "*.pdf" 2>/dev/null | grep -i "${CITATION_KEY}_" | head -n 1)

if [ -z "$foundPDF" ]; then
  # If no PDF found, print an empty JSON array (or handle differently).
  echo "[]"
  exit 0
fi

# 2) The folder name in 'storage/' is the attachment key.
ATTACHMENT_KEY=$(basename "$(dirname "$foundPDF")")

# 3) Call Zotero Web API to get the annotation children for that attachment key.
ANNOTATIONS_JSON=$(curl -s \
  -H "Zotero-API-Key: $ZOTERO_API_KEY" \
  "https://api.zotero.org/users/$ZOTERO_USER_ID/items/$ATTACHMENT_KEY/children?itemType=annotation&format=json"
)

# If no annotations found or invalid key, print an empty array.
if [ -z "$ANNOTATIONS_JSON" ] || [ "$ANNOTATIONS_JSON" = "[]" ]; then
  echo "[]"
  exit 0
fi

# 4) Print ONLY the raw JSON. 
echo "$ANNOTATIONS_JSON"

Result (example):

[
    {
        "key": "VGK94386",
        "version": 48375,
        "library": {
            "type": "user",
            "id": 123,
            "name": "xxx",
            "links": {
                "alternate": {
                    "href": "https://www.zotero.org/xxx",
                    "type": "text/html"
                }
            }
        },
        "links": {
            "self": {
                "href": "https://api.zotero.org/users/123/items/VGK94386",
                "type": "application/json"
            },
            "alternate": {
                "href": "https://www.zotero.org/xxx/items/VGK94386",
                "type": "text/html"
            },
            "up": {
                "href": "https://api.zotero.org/users/123/items/V87689SF",
                "type": "application/json"
            }
        },
        "meta": {},
        "data": {
            "key": "VGK94386",
            "version": 48375,
            "parentItem": "V87689SF",
            "itemType": "annotation",
            "annotationType": "highlight",
            "annotationText": "The four-phased pattern, as specified above, came  out well in the case study, precisely because",
            "annotationComment": "sdasdasda",
            "annotationColor": "#ffd400",
            "annotationPageLabel": "274",
            "annotationSortIndex": "00009|001047|00511",
            "annotationPosition": "{\"pageIndex\":9,\"rects\":[[56.801,318.335,279.517,330.556],[45.401,306.334,260.498,318.556]]}",
            "tags": [
                {
                    "tag": "❌ nosource"
                }
            ],
            "relations": {},
            "dateAdded": "2024-12-29T08:33:19Z",
            "dateModified": "2024-12-29T08:33:23Z"
        }
    }
]

What I’m trying to do in Tinderbox:

  1. Call this script from Tinderbox (using something like runCommand()), passing along a citation key.
  2. Have the script’s JSON output placed into $Text.
  3. Parse that JSON array in a Tinderbox action or stamp, to produce user-friendly text like this one:
Quote: "some highlighted text..."
Annotation: "my note"
Annotation Color: "#FFD700"
Page(s): "12-13"
Tag(s): "important, highlight"
Date Added: "2024-12-11T17:40:54Z"
Date Modified: "2024-12-28T23:04:11Z"

Quote: "another highlight..."
Annotation: "another comment"
...

I understand the general approach:

  • The runCommand() operator can capture the script’s stdout into $Text.
  • Then I can run something like:
$Text.json.each {
   var:string quote = json["data.annotationText"];
   // etc.
};

to iterate through the array. Do I understand it correctly?
I’m still a bit unclear about the best way to dynamically pass $Name (my citation key) from Tinderbox into the script.

Could anyone offer tips or point me to an example of how to manage this workflow in Tinderbox? I’m tagging @satikusala because it seems you’ve been interested in getting annotations directly into your Tinderbox file.

Thanks in advance!
Arek

Yes, I think you’re correct on all fronts.

Have you tried runCommand(commandStr[, inputsStr, dirStr])? If so what did code did you try that failed.

Love this approach. Perhaps we can work though it on a future meetup. A bit tied up right now to try to work through this myself.

Thanks for all the replies! Due to some pressing matters, I currently can’t check the code (limited access to my Mac). I’ll notify you as soon as I’m back and do some more testing!

1 Like