aboutsummaryrefslogtreecommitdiffstats
path: root/bin
diff options
context:
space:
mode:
Diffstat (limited to 'bin')
-rwxr-xr-xbin/immich-fetch.sh96
1 files changed, 96 insertions, 0 deletions
diff --git a/bin/immich-fetch.sh b/bin/immich-fetch.sh
new file mode 100755
index 0000000..961cd63
--- /dev/null
+++ b/bin/immich-fetch.sh
@@ -0,0 +1,96 @@
+#!/bin/bash
+# Fetch today's Immich "N years ago" memories into a cache directory, for
+# widgets/immich.lua. Run from conky's ${execi}: prints nothing on success and
+# never blocks the dashboard, since the widget only ever reads the cache.
+#
+# Writes $CACHE_DIR/<asset id>.png per photo plus index.txt:
+# generated <epoch>
+# url <server URL>
+# photo <asset id>|<memory year>|<localDateTime>|<place>
+# An index with no photo lines means "no memories today", which the card shows
+# differently from a missing index ("the fetch never ran").
+#
+# Leaves any existing cache untouched when the server cannot be reached, rather
+# than replacing good data with an error.
+
+set -u
+
+ENV_FILE="${IMMICH_ENV:-$HOME/.config/udt/immich.env}"
+CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/udt/immich"
+INDEX="$CACHE_DIR/index.txt"
+
+if [ ! -r "$ENV_FILE" ]; then
+ echo "immich-fetch: no $ENV_FILE (copy immich.env.example)" >&2
+ exit 1
+fi
+
+set -a
+# shellcheck source=/dev/null
+. "$ENV_FILE"
+set +a
+
+if [ -z "${URL:-}" ] || [ -z "${KEY:-}" ]; then
+ echo "immich-fetch: URL or KEY is empty in $ENV_FILE" >&2
+ exit 1
+fi
+
+# Photos are personal data: keep the cache private from the first byte.
+umask 077
+mkdir -p "$CACHE_DIR"
+
+TMP="$INDEX.tmp.$$"
+MEM="$CACHE_DIR/memories.json.tmp.$$"
+trap 'rm -f "$TMP" "$MEM" "$CACHE_DIR"/*.part.$$' EXIT
+
+api() {
+ curl -fsS --max-time 20 -H "x-api-key: $KEY" "${URL%/}/api/$1"
+}
+
+if ! api "memories?for=$(date +%F)" > "$MEM" 2>/dev/null \
+ || ! jq -e 'type == "array"' "$MEM" > /dev/null 2>&1; then
+ echo "immich-fetch: memories request failed" >&2
+ exit 1
+fi
+
+echo "generated $(date +%s)" > "$TMP"
+# For the card's click-through to the photo in Immich's web UI.
+echo "url ${URL%/}" >> "$TMP"
+
+# One line per asset: id, the memory's year, and when the photo was taken.
+# localDateTime is the camera's wall clock, which is what "taken at" means;
+# the UTC fileCreatedAt would shift an evening photo into the next day.
+jq -r '.[] | select(.type == "on_this_day") | .data.year as $y
+ | .assets[] | [.id, $y, .localDateTime] | @tsv' "$MEM" |
+while IFS=$'\t' read -r id year taken; do
+ png="$CACHE_DIR/$id.png"
+ if [ ! -s "$png" ]; then
+ # The preview is a JPEG (or WebP); cairo only loads PNG. Shrunk here
+ # so the widget holds a ~1MB surface rather than an 11MB one.
+ # -auto-orient: previews already come rotated, this is belt and braces.
+ if ! api "assets/$id/thumbnail?size=preview" 2>/dev/null \
+ | magick - -auto-orient -resize '800x800>' "png:$png.part.$$" 2>/dev/null; then
+ continue
+ fi
+ mv -f "$png.part.$$" "$png"
+ fi
+
+ # Location needs the key's asset.read permission. Without it this 403s and
+ # the place is simply empty: the card shows '--', the photo still shows.
+ place=$(api "assets/$id" 2>/dev/null | jq -r '.exifInfo // {} |
+ ([.city, .country] | map(select(. != null and . != "")) | join(", ")) as $p
+ | if $p != "" then $p
+ elif .latitude != null and .longitude != null
+ then "\(.latitude * 1000 | round / 1000), \(.longitude * 1000 | round / 1000)"
+ else "" end' 2>/dev/null)
+
+ echo "photo $id|$year|$taken|$place" >> "$TMP"
+done
+
+mv -f "$TMP" "$INDEX"
+
+# Drop photos no longer in today's memories, so the directory holds one day.
+for f in "$CACHE_DIR"/*.png; do
+ [ -e "$f" ] || continue
+ id=$(basename "$f" .png)
+ grep -q "^photo $id|" "$INDEX" || rm -f "$f"
+done