{"slug": "generating-a-link-to-an-annotation-in-zotero", "title": "Generating a link to an annotation in Zotero", "summary": "Based on the provided code, this is a Bash script that generates formatted links or citations from Zotero annotations. It extracts a Zotero item ID from clipboard text, queries a BetterBibTeX JSON export of the user's Zotero library, and outputs a link, citekey, or formatted quote depending on the mode selected. The script requires `jq` and either `pbpaste` or `xsel` to function.", "body_md": "zotero-link.sh\n\n      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.\n      \nLearn more about bidirectional Unicode characters\n\n \n    Show hidden characters\n\n#!/usr/bin/env bash\n\nexport LC_ALL=en_US.UTF-8\n\n# Requirements:\n\n# 1. jq (https://jqlang.github.io/jq/)\n\n# 2. Export Zotero library as BetterBibTeX JSON\n\n# 3*. Espanso for quick link insertion (https://espanso.org/)\n\n#\n\n# Usage: zotero-link.sh [link|citekey|quote]\n\nmode=\"${1:-link}\"\n\nlibrary=\"$HOME/Base/Obsidian/.library.json\"\n\nfail() {\n\n  printf 'zotero-link: %s\\n' \"$*\" >&2\n\n  exit 1\n\n}\n\n#─────────────────────────────────────────────────────────────────────────────\n\n# Read clipboard\n\nif command -v pbpaste >/dev/null 2>&1; then\n\n  text=$(pbpaste)\n\nelif command -v xsel >/dev/null 2>&1; then\n\n  text=$(xsel --clipboard --output)\n\nelse\n\n  fail \"could not find pbpaste or xsel\"\n\nfi\n\ncommand -v jq >/dev/null 2>&1 || fail \"jq is not installed\"\n\n[[ -r \"$library\" ]] || fail \"library file is not readable: $library\"\n\n#─────────────────────────────────────────────────────────────────────────────\n\n# Extract item ID\n\nid=$(printf '%s\\n' \"$text\" |\n\n  grep -Eo 'zotero://select/library/items/[a-zA-Z0-9]+' |\n\n  sed 's|zotero://select/library/items/||' |\n\n  head -n 1)\n\nif [[ -z \"$id\" ]]; then\n\n  id=$(printf '%s\\n' \"$text\" |\n\n    grep -Eo 'zotero://open-pdf/library/items/[a-zA-Z0-9]+' |\n\n    sed 's|zotero://open-pdf/library/items/||' |\n\n    head -n 1)\n\nfi\n\nif [[ -z \"$id\" ]]; then\n\n  fail \"could not find Zotero item ID in the clipboard text\"\n\nfi\n\n#─────────────────────────────────────────────────────────────────────────────\n\n# Get item data from library\n\nitem=$(jq --arg key \"$id\" '\n\n  .items[]\n\n  | select(\n\n      .key == $key\n\n      or any(.attachments[]?; (.select == (\"zotero://select/library/items/\" + $key)) or (.uri | endswith(\"/items/\" + $key)))\n\n    )\n\n' \"$library\")\n\ntitle=$(jq -r 'if .shortTitle then .shortTitle else .title end' <<<\"$item\")\n\nif [[ -z \"$title\" ]]; then\n\n  fail \"could not find exported Zotero item or parent attachment for ID: $id\"\n\nfi\n\n#─────────────────────────────────────────────────────────────────────────────\n\n# Citekey mode\n\nif [[ \"$mode\" == \"citekey\" ]]; then\n\n  citekey=$(jq -r '.citationKey' <<<\"$item\")\n\n  echo \"[@${citekey#@}]\"\n\n  exit 0\n\nfi\n\n#─────────────────────────────────────────────────────────────────────────────\n\n# Link patterns\n\nlink_patterns=(\n\n  \"pdf:zotero:\\/\\/open-pdf\\/library\\/items\\/([a-zA-Z0-9]+)\\?page=([0-9]+)&annotation=([a-zA-Z0-9])+\"\n\n  \"snap:zotero:\\/\\/open-pdf\\/library\\/items\\/([a-zA-Z0-9]+)\\?sel=([^&]+)&annotation=([a-zA-Z0-9]+)\"\n\n  \"epub:zotero:\\/\\/open-pdf\\/library\\/items\\/([a-zA-Z0-9]+)\\?cfi=([^&]+)&annotation=([a-zA-Z0-9]+)\"\n\n)\n\n#─────────────────────────────────────────────────────────────────────────────\n\n# Match link and produce output\n\nfor pattern in \"${link_patterns[@]}\"; do\n\n  IFS=':' read -r link_type regex <<<\"$pattern\"\n\n  if [[ \"$text\" =~ $regex ]]; then\n\n    link=\"${BASH_REMATCH[0]}\"\n\n    page_number=\"${BASH_REMATCH[2]}\"\n\n    case \"$mode\" in\n\n    quote)\n\n      quote_text=$(printf '%s' \"$text\" | sed 's/ *(\\[.*//')\n\n      author_full=$(jq -r 'if .creators[0] then \"\\(.creators[0].firstName) \\(.creators[0].lastName)\" else empty end' <<<\"$item\")\n\n      echo \"> [!quote]\"\n\n      echo \"> ${quote_text//$'\\n'/$'\\n> '}\"\n\n      echo \">\"\n\n      case \"$link_type\" in\n\n      pdf) echo \"> - ${title}, [p. ${page_number}](${link})\" ;;\n\n      *) echo \"> - [${title}](${link})\" ;;\n\n      esac\n\n      [[ -n \"$author_full\" ]] && echo \"> - ${author_full}\"\n\n      ;;\n\n    *)\n\n      author=$(jq -r '.creators[0].lastName // empty' <<<\"$item\")\n\n      year=$(jq -r '.date // empty' <<<\"$item\" | grep -Eo '[0-9]{4}')\n\n      result=\"\"\n\n      [[ -n \"$author\" ]] && result=\"${author}, \"\n\n      case \"$link_type\" in\n\n      pdf)\n\n        result=\"${result}${title}\"\n\n        [[ -n \"$year\" ]] && result=\"${result} (${year})\"\n\n        result=\"${result}, [p. ${page_number}](${link})\"\n\n        ;;\n\n      *)\n\n        result=\"${result}[${title}](${link})\"\n\n        [[ -n \"$year\" ]] && result=\"${result} (${year})\"\n\n        ;;\n\n      esac\n\n      echo \"$result\"\n\n      ;;\n\n    esac\n\n    exit 0\n\n  fi\n\ndone\n\nfail \"no suitable Zotero PDF/snapshot/EPUB link found in the clipboard text\"", "url": "https://wpnews.pro/news/generating-a-link-to-an-annotation-in-zotero", "canonical_source": "https://gist.github.com/flowing-abyss/1129259a0854dbf65f58bba720d90809", "published_at": "2024-11-15 07:20:02+00:00", "updated_at": "2026-05-24 05:35:14.394240+00:00", "lang": "en", "topics": ["developer-tools", "open-source"], "entities": ["Zotero", "BetterBibTeX", "Espanso", "jq"], "alternates": {"html": "https://wpnews.pro/news/generating-a-link-to-an-annotation-in-zotero", "markdown": "https://wpnews.pro/news/generating-a-link-to-an-annotation-in-zotero.md", "text": "https://wpnews.pro/news/generating-a-link-to-an-annotation-in-zotero.txt", "jsonld": "https://wpnews.pro/news/generating-a-link-to-an-annotation-in-zotero.jsonld"}}