Random image from Flickr account widget screenshot

Script

Random image from Flickr account

by Brett Terpstra

Show a random photo from your Flickr photostream in a Terminal Widget (--image + --padding fill).

Uses the public Flickr REST API with your API key. Private photos are not included unless you add OAuth (out of scope for this recipe).

Randomness follows the approach in this Stack Overflow answer: learn how many pages exist, pick a random page (capped at 4000), fetch one photo from that page with size URL extras.

Requirements

  • Terminal Widget (terminal-widget CLI)
  • A Flickr API key
  • Your Flickr NSID (FLICKR_USER_ID) or username (FLICKR_USERNAME)
  • curl and jq (brew install jq)
  • Photostream must have at least one public photo

1. Create a Flickr API key

  1. Sign in at Flickr and open App Garden / API keys.
  2. Apply for a non-commercial key (or use an existing app).
  3. Copy the Key (the Secret is not required for public read calls).
  4. Store the key only in your local config file.

2. Find your user id

Either:

  • Use your Flickr NSID directly (looks like 12345678@N00), or
  • Set FLICKR_USERNAME to either:
    • your Flickr username (profile display / API name), or
    • your photostream URL alias (the path in flickr.com/photos/alias)

The script tries flickr.people.findByUsername first, then falls back to flickr.urls.lookupUser for path aliases. Those two strings are often different (e.g. alias circlesixdesign vs username ttscoff).

NSID also appears in your photostream HTML and in many third-party Flickr tools.

3. Create flickr.env

mkdir -p ~/.config/terminal-widget
chmod 700 ~/.config/terminal-widget

Create ~/.config/terminal-widget/flickr.env:

# Required
export FLICKR_API_KEY='your_api_key_here'

# One of these:
export FLICKR_USER_ID='12345678@N00'
# export FLICKR_USERNAME='yourflickrname'

# Optional defaults
# export FLICKR_TARGET='flickr'
# export FLICKR_TAGS='landscape,film'   # comma-separated; omit for any public photo
# export FLICKR_MAX_PAGE='4000'         # SO tip: keep random page in a unique range
# export FLICKR_TAP_SHORTCUT='Flickr Refresh'
# export TERMINAL_WIDGET='/opt/homebrew/bin/terminal-widget'
chmod 600 ~/.config/terminal-widget/flickr.env

Alternate path: FLICKR_ENV=/path/to/flickr.env.

4. Add a widget target

Target ID Purpose

| flickr | Default target | Use a square (or any) widget size; --padding fill crops the photo to fill the widget.

5. Install and run

Save the script as ~/bin/flickr-random.sh, then:

chmod +x ~/bin/flickr-random.sh
~/bin/flickr-random.sh
# optional tags:
~/bin/flickr-random.sh flickr "landscape,travel"

6. Schedule updates

Enable the launchd section (suggested: 1 hour or whatever you like — Flickr public reads are free within rate limits).

Customize LaunchAgent args

<key>ProgramArguments</key>
<array>
  <string>/bin/bash</string>
  <string>/Users/YOU/bin/flickr-random.sh</string>
  <string>flickr</string>
  <string>-</string>
</array>
Argument Meaning
1 — target Widget target name
2 — tags Comma-separated tags, or - for any public photo

Tap to refresh

Use a Shortcut + FLICKR_TAP_SHORTCUT (App Sandbox blocks run-command from running ~/Scripts / Homebrew).

Environment variables reference

Variable Required Default Description
FLICKR_API_KEY Yes Flickr API key
FLICKR_USER_ID One of id/username Flickr NSID
FLICKR_USERNAME One of id/username Username or URL alias (findByUsername, then urls.lookupUser)
FLICKR_TARGET No flickr Default widget target
FLICKR_TAGS No Default tag filter
FLICKR_MAX_PAGE No 4000 Cap for random page selection
FLICKR_TAP_SHORTCUT No Shortcut name for tap refresh
FLICKR_ENV No ~/.config/terminal-widget/flickr.env Config path
TERMINAL_WIDGET No terminal-widget CLI path

Troubleshooting

Symptom Likely cause
“no photos found” No public photos, wrong user id, or tags matched nothing
Invalid API key Typo / revoked key
Same image often Huge photostream + page beyond useful range — lower FLICKR_MAX_PAGE or add tags
Private photos missing Expected — this recipe only uses public APIs

Script (flickr-random.sh)

#!/usr/bin/env bash
# Random photo from your Flickr photostream → Terminal Widget (--image --padding fill).
# Config: ~/.config/terminal-widget/flickr.env (see recipe).
#
# Usage:
#   flickr-random.sh [TARGET] [TAGS|-]
#
# TAGS: optional comma-separated Flickr tags to filter; "-" or omit for any photo.
#
# Strategy (see https://stackoverflow.com/a/62865365):
#   1) Fetch page 1 with per_page=1 to learn total/pages
#   2) Pick a random page in 1..min(pages, 4000)
#   3) Fetch that single photo with size URL extras
#
# Public photos only (api_key). Private photos need OAuth (not covered here).
# Tap refresh: set FLICKR_TAP_SHORTCUT (run-command is sandboxed).
set -euo pipefail

export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin${PATH:+:$PATH}"

FLICKR_ENV="${FLICKR_ENV:-$HOME/.config/terminal-widget/flickr.env}"
if [[ -f "$FLICKR_ENV" ]]; then
	# shellcheck source=/dev/null
	source "$FLICKR_ENV"
fi

if [[ -z "${TERMINAL_WIDGET:-}" ]]; then
	if [[ -x /opt/homebrew/bin/terminal-widget ]]; then
		TW=/opt/homebrew/bin/terminal-widget
	elif [[ -x /usr/local/bin/terminal-widget ]]; then
		TW=/usr/local/bin/terminal-widget
	else
		TW=terminal-widget
	fi
else
	TW="$TERMINAL_WIDGET"
fi

TARGET="${1:-${FLICKR_TARGET:-flickr}}"
TAGS_ARG="${2:-${FLICKR_TAGS:--}}"
[[ "$TAGS_ARG" == "-" ]] && TAGS_ARG=""

API_KEY="${FLICKR_API_KEY:-}"
USER_ID="${FLICKR_USER_ID:-}"
USERNAME="${FLICKR_USERNAME:-}"
# Flickr search/list soft-cap called out in the SO answer
MAX_PAGE="${FLICKR_MAX_PAGE:-4000}"

widget_error() {
	local msg="$1"
	echo "Flickr: $msg" >&2
	if command -v "$TW" >/dev/null 2>&1; then
		"$TW" --target "$TARGET" \
			--icon exclamationmark.triangle.fill \
			--background "#422006" \
			--foreground "#fde68a" \
			--text "Flickr: $msg" || true
	fi
}

if [[ -z "$API_KEY" ]]; then
	widget_error "FLICKR_API_KEY not set"
	exit 1
fi

for cmd in jq curl; do
	if ! command -v "$cmd" >/dev/null 2>&1; then
		widget_error "$cmd required"
		exit 1
	fi
done

if ! command -v "$TW" >/dev/null 2>&1; then
	widget_error "terminal-widget not found (set TERMINAL_WIDGET)"
	exit 1
fi

flickr_get() {
	# usage: flickr_get method key=val ...
	local method="$1"
	shift
	local url="https://api.flickr.com/services/rest/?method=${method}&api_key=${API_KEY}&format=json&nojsoncallback=1"
	local arg
	for arg in "$@"; do
		url+="&${arg}"
	done
	curl -fsS "$url"
}

# Resolve username / URL alias → NSID if needed.
# Flickr "username" (e.g. ttscoff) often differs from the photostream
# path alias (e.g. circlesixdesign). findByUsername only accepts the former;
# urls.lookupUser accepts a photostream URL.
if [[ -z "$USER_ID" && -n "$USERNAME" ]]; then
	LOOKUP=$(flickr_get flickr.people.findByUsername "username=$(printf %s "$USERNAME" | jq -sRr @uri)")
	USER_ID=$(echo "$LOOKUP" | jq -r '.user.nsid // empty')
	if [[ -z "$USER_ID" ]]; then
		PHOTO_URL="https://www.flickr.com/photos/${USERNAME}"
		LOOKUP=$(flickr_get flickr.urls.lookupUser "url=$(printf %s "$PHOTO_URL" | jq -sRr @uri)")
		USER_ID=$(echo "$LOOKUP" | jq -r '.user.id // empty')
	fi
	if [[ -z "$USER_ID" ]]; then
		ERR=$(echo "$LOOKUP" | jq -r '.message // "username/alias lookup failed"' 2>/dev/null || echo "username/alias lookup failed")
		widget_error "$ERR"
		exit 1
	fi
fi

if [[ -z "$USER_ID" ]]; then
	widget_error "set FLICKR_USER_ID or FLICKR_USERNAME"
	exit 1
fi

USER_Q="user_id=$(printf %s "$USER_ID" | jq -sRr @uri)"
EXTRAS="extras=url_o,url_l,url_c,url_z,url_m,url_n,media"
COMMON=("$USER_Q" "media=photos" "$EXTRAS")

if [[ -n "$TAGS_ARG" ]]; then
	# Tag filter via search (still scoped to this user)
	METHOD_COUNT=flickr.photos.search
	METHOD_PAGE=flickr.photos.search
	TAG_Q="tags=$(printf %s "$TAGS_ARG" | jq -sRr @uri)"
	COMMON+=("$TAG_Q" "tag_mode=all")
else
	METHOD_COUNT=flickr.people.getPublicPhotos
	METHOD_PAGE=flickr.people.getPublicPhotos
fi

COUNT_JSON=$(flickr_get "$METHOD_COUNT" "${COMMON[@]}" "per_page=1" "page=1") || {
	widget_error "API request failed"
	exit 1
}

STAT=$(echo "$COUNT_JSON" | jq -r '.stat // empty')
if [[ "$STAT" != "ok" ]]; then
	ERR=$(echo "$COUNT_JSON" | jq -r '.message // "API error"' 2>/dev/null || echo "API error")
	widget_error "$ERR"
	exit 1
fi

TOTAL=$(echo "$COUNT_JSON" | jq -r '.photos.total // 0' | tr -d ',')
PAGES=$(echo "$COUNT_JSON" | jq -r '.photos.pages // 0')
if [[ "${TOTAL:-0}" -lt 1 || "${PAGES:-0}" -lt 1 ]]; then
	widget_error "no photos found"
	exit 1
fi

# Cap pages so random page stays in a range Flickr actually returns uniquely
if [[ "$PAGES" -gt "$MAX_PAGE" ]]; then
	PAGES="$MAX_PAGE"
fi

RAND_PAGE=$(( (RANDOM % PAGES) + 1 ))
# Prefer a fuller random when available
if command -v od >/dev/null 2>&1; then
	RAND_PAGE=$(( ($(od -An -N2 -tu2 </dev/urandom | tr -d ' ') % PAGES) + 1 ))
fi

PAGE_JSON=$(flickr_get "$METHOD_PAGE" "${COMMON[@]}" "per_page=1" "page=${RAND_PAGE}") || {
	widget_error "API request failed"
	exit 1
}

STAT=$(echo "$PAGE_JSON" | jq -r '.stat // empty')
if [[ "$STAT" != "ok" ]]; then
	ERR=$(echo "$PAGE_JSON" | jq -r '.message // "API error"' 2>/dev/null || echo "API error")
	widget_error "$ERR"
	exit 1
fi

# Prefer larger sizes; url_o may be missing without original download perms
IMAGE_URL=$(echo "$PAGE_JSON" | jq -r '
	.photos.photo[0] as $p
	| ($p.url_l // $p.url_c // $p.url_z // $p.url_o // $p.url_m // $p.url_n // empty)
')

if [[ -z "$IMAGE_URL" || "$IMAGE_URL" == "null" ]]; then
	widget_error "no image URL on page ${RAND_PAGE}"
	exit 1
fi

ACTION_ARGS=()
if [[ -n "${FLICKR_TAP_SHORTCUT:-}" ]]; then
	ACTION_ARGS=(--action-kind run-shortcut --action-value "$FLICKR_TAP_SHORTCUT")
fi

"$TW" \
	--target "$TARGET" \
	--image "$IMAGE_URL" \
	--padding fill \
	"${ACTION_ARGS[@]}"

Download script

Running in the background with launchd

Suggested interval: 1 hour (StartInterval = 3600 seconds).

Save the script from this recipe to ~/bin/random-image-from-flickr-account.sh, then create a Launch Agent plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.terminalwidget.random-image-from-flickr-account</string>
  <key>ProgramArguments</key>
  <array>
    <string>/bin/bash</string>
    <string>~/bin/random-image-from-flickr-account.sh</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
  <key>StartInterval</key>
  <integer>3600</integer>
  <key>StandardOutPath</key>
  <string>/tmp/com.terminalwidget.random-image-from-flickr-account.log</string>
  <key>StandardErrorPath</key>
  <string>/tmp/com.terminalwidget.random-image-from-flickr-account.err</string>
</dict>
</plist>

From Terminal:

mkdir -p ~/bin ~/Library/LaunchAgents
# Save your script to ~/bin/random-image-from-flickr-account.sh
cat > ~/Library/LaunchAgents/com.terminalwidget.random-image-from-flickr-account.plist <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.terminalwidget.random-image-from-flickr-account</string>
  <key>ProgramArguments</key>
  <array>
    <string>/bin/bash</string>
    <string>~/bin/random-image-from-flickr-account.sh</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
  <key>StartInterval</key>
  <integer>3600</integer>
  <key>StandardOutPath</key>
  <string>/tmp/com.terminalwidget.random-image-from-flickr-account.log</string>
  <key>StandardErrorPath</key>
  <string>/tmp/com.terminalwidget.random-image-from-flickr-account.err</string>
</dict>
</plist>
EOF
launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/com.terminalwidget.random-image-from-flickr-account.plist

For a GUI editor and troubleshooting, see LaunchControl from soma-zone.

← All recipes