From aee29de34a8079fe8cb85c502b2fd976197c05a2 Mon Sep 17 00:00:00 2001 From: JakeBreath Date: Fri, 18 Sep 2026 13:14:41 -0500 Subject: [PATCH] Port the fish_greeting shell greeting to the new API The original script (J621-Django/extras/fish_greeting system) downloaded image bytes from /random/?rating=X and read the X-File-* headers. The new API answers with JSON and a signed link, so the greeting now: - asks /api/random/?fastfetch=1&rating= for JSON; - parses url/j_id/filename/md5 with jq, python3, or a grep/sed fallback; - downloads the signed link and keeps the original display path (fastfetch kitty/kitty-icat logos, gifsicle preprocessing for GIFs, recursion guard, logging with rotation, greeting_mode 0/1/2 = NSFW/SFW/Questionable); - is configured through ~/.config/j621Greeting/config.fish or universal variables (J621_BASE, J621_WEB, J621_TOKEN, J621_FASTFETCH_CONFIG) instead of a hardcoded host, and prints the /detail/ link on J621_WEB; - reports the API's own 404 message when a rating has no images, and keeps curl quiet so failures do not spill into the greeting. extras/fish_greeting/ contains the function, an install.fish (copies it into the fish functions dir, creates the config once, checks dependencies) and a README with the old-vs-new table and troubleshooting. The existing gm-switch helper and .desktop launchers keep working (same mode file). Tested with fish 4.9.3: syntax check on every file, guest and token runs against the dev API (unsigned vs signed URLs), the empty-rating and unreachable-API paths, and the grep/sed fallback with jq and python3 unavailable. --- README.md | 6 +- extras/fish_greeting/README.md | 74 ++++++++ extras/fish_greeting/config.example.fish | 17 ++ extras/fish_greeting/fish_greeting.fish | 219 +++++++++++++++++++++++ extras/fish_greeting/install.fish | 52 ++++++ 5 files changed, 366 insertions(+), 2 deletions(-) create mode 100644 extras/fish_greeting/README.md create mode 100644 extras/fish_greeting/config.example.fish create mode 100644 extras/fish_greeting/fish_greeting.fish create mode 100644 extras/fish_greeting/install.fish diff --git a/README.md b/README.md index 44087dc..49e1dc8 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,10 @@ curl -H "Authorization: Token " \ - `url` is absolute, and signed for authenticated callers, so fastfetch can load it without headers. Guests get an unsigned URL and only see guest-visible items. -- `/random` and `/random/` are aliases of `/api/random/` for scripts; 404 when - nothing matches the filters. +- `/random` and `/random/` are aliases of `/api/random/` for scripts. Behind + the bundled nginx those aliases negotiate on `Accept`: browsers get the SPA + page, requesters like curl/wget/fastfetch get the JSON. `/api/random/` is + the unambiguous path for scripts; 404 when nothing matches the filters. ## Licence diff --git a/extras/fish_greeting/README.md b/extras/fish_greeting/README.md new file mode 100644 index 0000000..b123909 --- /dev/null +++ b/extras/fish_greeting/README.md @@ -0,0 +1,74 @@ +# fish_greeting (updated for the J621 rewrite) + +Shows a random library image as your shell greeting, using fastfetch. This is +the updated version of the script from +`J621-Django/extras/fish_greeting system/fish_greeting.fish`, adapted to the +new REST API. + +## What changed from the old script + +| | Old J621-Django | This version | +| --- | --- | --- | +| Endpoint | `GET /random/?rating=X` returned **image bytes** | `GET /api/random/?fastfetch=1&rating=X` returns **JSON** | +| Image access | same response, `X-File-MD5`/`X-File-Name` headers | signed `url` from the JSON, downloaded separately | +| Unsupported types | rolled again (recursion) | server only returns png/jpg/gif in fastfetch mode | +| Config | hardcoded `https://j621.jake.i` | `J621_BASE` / `J621_TOKEN` / `J621_WEB` | +| Printed link | `/view/` on the old host | `/detail/` on `J621_WEB` | +| Modes, logging, gifsicle, fastfetch flags | same | same (`~/.config/fish/greeting_mode`, `~/.config/j621Logos/logs.log`) | + +The `gm-switch` helper and the `.desktop` launchers from the old repo keep +working unchanged: they write the same `~/.config/fish/greeting_mode` file +(0 = NSFW, 1 = SFW, 2 = Questionable). + +## Install + +```fish +cd extras/fish_greeting +fish install.fish +``` + +Then edit `~/.config/j621Greeting/config.fish` (at least `J621_BASE`) and test: + +```fish +fish_greeting +``` + +Requirements: `curl` (or `wget`), `fastfetch`, `file`. Optional: `gifsicle` +(GIF downscaling), `jq` or `python3` (JSON parsing — without either it falls +back to grep/sed). + +## Configuration + +Any of these work; the config file is read by the function on every run: + +```fish +# ~/.config/j621Greeting/config.fish, or universal variables +set -g J621_BASE https://j621.rainbow-herring.ts.net # API origin +set -g J621_WEB https://j621.example.ts.net # link origin (split deploys) +set -g J621_TOKEN # signed URLs + hidden items +set -g J621_FASTFETCH_CONFIG jake # fastfetch config name +``` + +`J621_TOKEN` is the same token the SPA uses (`Authorization: Token …`). With +it the API signs the image URL for your account, so you also get items that +are hidden from guests. Without it the greeting runs as a guest and sees the +public library only. + +## Rating filters + +| `greeting_mode` | Rating requested | Label | +| --- | --- | --- | +| `0` | `e` | NSFW | +| `1` | `s` | SFW | +| `2` (default) | `q` | Questionable | + +## Troubleshooting + +- `No logo (No image matches those filters.)` — the library has no images for + that rating; try another mode or add files. +- `No logo (API error)` — check `J621_BASE`, and that the deployment is up + (`https:///health`). +- `No logo (download failed)` — the signed URL expired (they last 24 h) or the + item was deleted between the two requests; just run it again. +- The greeting is slow — the API and image fetch have `--max-time` guards; a + slow tailnet link is usually the cause. diff --git a/extras/fish_greeting/config.example.fish b/extras/fish_greeting/config.example.fish new file mode 100644 index 0000000..a1a2004 --- /dev/null +++ b/extras/fish_greeting/config.example.fish @@ -0,0 +1,17 @@ +# Copy to ~/.config/j621Greeting/config.fish and adjust. All of these can also +# be universal variables, e.g. `set -Ux J621_BASE https://j621.example.ts.net`. + +# API origin (no trailing slash needed). +set -g J621_BASE https://j621.rainbow-herring.ts.net + +# Web origin used in the printed link. Only needed when the SPA is served +# from a different host than the API (split deployment). +# set -g J621_WEB https://j621-frontend.rainbow-herring.ts.net + +# API token. Optional: gives you signed image URLs and access to items that +# are hidden from guests. Create one with `manage.py drf_create_token ` +# or copy it from the app (it is the same token the SPA stores). +# set -g J621_TOKEN paste-your-token-here + +# fastfetch config name used for the greeting logo. +# set -g J621_FASTFETCH_CONFIG jake diff --git a/extras/fish_greeting/fish_greeting.fish b/extras/fish_greeting/fish_greeting.fish new file mode 100644 index 0000000..769fbfe --- /dev/null +++ b/extras/fish_greeting/fish_greeting.fish @@ -0,0 +1,219 @@ +# fish_greeting — show a random library image as the shell greeting. +# +# Updated for the J621 Docker/REST rewrite: the API answers with JSON that +# carries a signed URL instead of streaming the image, so this function asks +# for one random png/jpg/gif (fastfetch mode), downloads the signed link and +# hands the file to fastfetch. Based on the original script from +# J621-Django/extras/fish_greeting system/fish_greeting.fish. +# +# Requires: curl (or wget) and fastfetch; gifsicle is used for animated GIFs +# (without it, the GIF is shown as-is). +# +# Rating modes (same file as the original script): +# ~/.config/fish/greeting_mode 0 = NSFW (explicit), 1 = SFW (safe), +# 2 = Questionable (default) +# +# Configuration, any of these (all optional): +# ~/.config/j621Greeting/config.fish with `set -g VAR value` lines, or +# universal variables, e.g. `set -Ux J621_BASE https://j621.example.ts.net` +# J621_BASE API origin (default https://j621.rainbow-herring.ts.net) +# J621_TOKEN API token, sent as `Authorization: Token …`; needed for +# signed URLs and for hidden-from-guests items (optional) +# J621_WEB Web origin used in the printed link (defaults to J621_BASE) +# J621_FASTFETCH_CONFIG fastfetch config name (default "jake") + +function __j621_json_field --argument-names json field + if command -v jq >/dev/null 2>&1 + printf '%s' "$json" | jq -r --arg field "$field" '.[$field] // empty' + else if command -v python3 >/dev/null 2>&1 + printf '%s' "$json" | python3 -c "import json,sys; value = json.load(sys.stdin).get(sys.argv[1], ''); print(value if value is not None else '')" $field + else + printf '%s' "$json" | grep -o "\"$field\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | head -1 | sed -E 's/.*:[[:space:]]*"//; s/"$//' + end +end + +function __j621_fetch_random --argument-names rating + set base (string trim --right --chars=/ "$J621_BASE") + set api_url "$base/api/random/?fastfetch=1&rating=$rating" + set curl_args -s -L --max-time 20 + if set -q J621_TOKEN; and test -n "$J621_TOKEN" + set -a curl_args -H "Authorization: Token $J621_TOKEN" + end + if command -v curl >/dev/null 2>&1 + curl $curl_args "$api_url" | string collect + else if command -v wget >/dev/null 2>&1 + wget -qO- "$api_url" | string collect + end +end + +function fish_greeting --argument-names depth + # Initialize depth to 0 if not provided or empty + if not set -q depth; or test -z "$depth" + set depth 0 + end + set MAX_DEPTH 3 + + # --- Configuration --- + set config_file ~/.config/j621Greeting/config.fish + if test -f $config_file + source $config_file + end + set -q J621_BASE; or set -g J621_BASE https://j621.rainbow-herring.ts.net + set -q J621_WEB; or set -g J621_WEB $J621_BASE + set -q J621_FASTFETCH_CONFIG; or set -g J621_FASTFETCH_CONFIG jake + set web_base (string trim --right --chars=/ "$J621_WEB") + + # --- Setup logging --- + set log_dir ~/.config/j621Logos + if not test -d $log_dir + mkdir -p $log_dir + end + set log_file $log_dir/logs.log + + # --- Mode selection --- + if test -f ~/.config/fish/greeting_mode + set mode (cat ~/.config/fish/greeting_mode | string trim) + else + set mode 2 + end + + switch $mode + case "0" + set rating e + set modename "NSFW" + case "1" + set rating s + set modename "SFW" + case "2" + set rating q + set modename "Questionable" + case "*" + echo "Unknown mode, using default NSFW" + set rating e + set modename "NSFW" + end + + # --- Ask the API for a random image (JSON with a signed URL) --- + set json (__j621_fetch_random $rating) + set image_url (__j621_json_field "$json" url) + set j_id (__j621_json_field "$json" j_id) + set md5 (__j621_json_field "$json" md5) + set filename (__j621_json_field "$json" filename) + + if test -z "$image_url" + set detail (__j621_json_field "$json" detail) + fastfetch --config "$J621_FASTFETCH_CONFIG" + if test -n "$detail" + printf '\e[4;2;38;2;138;0;222;49mNo logo (%s)\e[0m\n' "$detail" + else + printf '\e[4;2;38;2;138;0;222;49mNo logo (API error)\e[0m\n' + end + echo "$(date '+%Y-%m-%d %H:%M:%S') No logo for rating=$rating: $detail" >> "$log_file" + return + end + + # --- Download the signed image --- + set tempfile (mktemp /tmp/fastfetch_logo_XXXXXX) + set download_success 0 + if command -v curl >/dev/null 2>&1 + curl -s -L --max-time 60 -o "$tempfile" "$image_url" + if test $status -eq 0 -a -s "$tempfile" + set download_success 1 + end + else if command -v wget >/dev/null 2>&1 + wget -q -O "$tempfile" "$image_url" + if test $status -eq 0 -a -s "$tempfile" + set download_success 1 + end + end + + if test $download_success -ne 1 + fastfetch --config "$J621_FASTFETCH_CONFIG" + printf '\e[4;2;38;2;138;0;222;49mNo logo (download failed)\e[0m\n' + echo "$(date '+%Y-%m-%d %H:%M:%S') Download failed for $j_id" >> "$log_file" + rm -f "$tempfile" + return + end + + # --- Determine MIME type --- + set mime (file --mime-type -b "$tempfile" 2>/dev/null | string trim | string collect) + + # --- Accept only PNG, JPEG, GIF; everything else causes one more roll --- + if not string match -q "image/png" "$mime"; and not string match -q "image/jpeg" "$mime"; and not string match -q "image/gif" "$mime" + if test $depth -lt $MAX_DEPTH + fish_greeting (math $depth + 1) + else + printf '\e[31mMax recursion depth reached, skipping unsupported file type: %s\e[0m\n' "$mime" + end + if test $depth -eq 0 + printf '\e[31mForked (Got unsupported type: %s - %s)\e[0m\n' "$mime" "$filename" + if test -n "$md5" + echo "Link: $web_base/view/$md5" + end + end + rm -f "$tempfile" "$tempfile.tmp" 2>/dev/null + return + end + + # --- Process the file (only PNG, JPEG, GIF reach here) --- + set logo_type "kitty" + set processing_success 1 + + switch "$mime" + case "image/png" "image/jpeg" + set logo_type "kitty" + + case "image/gif" + set processed_ok 0 + set simple_file (mktemp /tmp/fastfetch_simple_XXXXXX) + set gif_err (mktemp) + if command -v gifsicle >/dev/null 2>&1 + gifsicle --colors 255 "$tempfile" > "$simple_file" 2> "$gif_err" + set gif_exit $status + if test $gif_exit -eq 0 -a -s "$simple_file" + gifsicle --unoptimize --resize-fit 360x360 "$simple_file" > "$tempfile.tmp" 2> "$gif_err" + set final_exit $status + set gif_warnings (cat "$gif_err" | string trim) + if test $final_exit -eq 0 -a -s "$tempfile.tmp" -a -z "$gif_warnings" + mv "$tempfile.tmp" "$tempfile" + set processed_ok 1 + set logo_type "kitty-icat" + echo "$(date '+%Y-%m-%d %H:%M:%S') GIF processed with gifsicle" >> "$log_file" + else + echo "$(date '+%Y-%m-%d %H:%M:%S') gifsicle final failed: $gif_warnings" >> "$log_file" + end + else + echo "$(date '+%Y-%m-%d %H:%M:%S') gifsicle --colors failed" >> "$log_file" + end + end + rm -f "$simple_file" "$gif_err" + if test $processed_ok -eq 0 + set logo_type "kitty-icat" + echo "$(date '+%Y-%m-%d %H:%M:%S') Using original GIF (processing failed)" >> "$log_file" + end + end + + # --- Display result --- + if test $processing_success -eq 1 + fastfetch --config "$J621_FASTFETCH_CONFIG" --logo-type "$logo_type" --logo "$tempfile" --logo-width 35 + set timestamp (date '+%Y-%m-%d %H:%M:%S') + echo "$timestamp Downloaded: $j_id $filename [$modename]" >> "$log_file" + if test -n "$filename" + printf '\e[4;2;38;2;138;0;222;49mLogo from API: %s (%s, %s)\e[0m\n' "$filename" "$modename" "$j_id" + echo "Link: $web_base/detail/$j_id" + else + printf '\e[4;2;38;2;138;0;222;49mLogo from API (%s)\e[0m\n' "$modename" + end + else + fastfetch --config "$J621_FASTFETCH_CONFIG" + printf '\e[4;2;38;2;138;0;222;49mNo logo (Image processing error)\e[0m\n' + end + + # --- Keep only the last 1000 lines of the log --- + set log_tmp (mktemp) + tail -n 1000 "$log_file" > "$log_tmp" 2>/dev/null + mv "$log_tmp" "$log_file" + + # --- Cleanup --- + rm -f "$tempfile" "$tempfile.tmp" 2>/dev/null +end diff --git a/extras/fish_greeting/install.fish b/extras/fish_greeting/install.fish new file mode 100644 index 0000000..181b848 --- /dev/null +++ b/extras/fish_greeting/install.fish @@ -0,0 +1,52 @@ +#!/usr/bin/env fish +# Install the J621 fish greeting: +# * copies fish_greeting.fish into ~/.config/fish/functions/ +# * creates ~/.config/j621Greeting/config.fish from the example (once) +# * checks the tools it needs +# +# Usage: fish install.fish + +set -l here (path resolve (dirname (status --current-filename))) +set -l functions_dir ~/.config/fish/functions +set -l config_dir ~/.config/j621Greeting +set -l problems 0 + +mkdir -p $functions_dir +cp "$here/fish_greeting.fish" "$functions_dir/fish_greeting.fish" +echo "Installed $functions_dir/fish_greeting.fish" + +mkdir -p $config_dir +if test -f "$config_dir/config.fish" + echo "Keeping existing $config_dir/config.fish" +else + cp "$here/config.example.fish" "$config_dir/config.fish" + echo "Created $config_dir/config.fish — edit J621_BASE (and J621_TOKEN) there" +end + +for tool in curl fastfetch file + if not command -v $tool >/dev/null 2>&1 + set problems 1 + echo "Missing required tool: $tool" + end +end + +if not command -v curl >/dev/null 2>&1; and not command -v wget >/dev/null 2>&1 + set problems 1 + echo "Missing required tool: curl (or wget)" +end + +for tool in gifsicle jq python3 + if not command -v $tool >/dev/null 2>&1 + echo "Optional tool not found: $tool (the greeting still works)" + end +end + +if test $problems -eq 0 + echo + echo "Done. Test it now with: fish_greeting" + echo "Rating mode lives in ~/.config/fish/greeting_mode (0=NSFW, 1=SFW, 2=Questionable)." + echo "The old gm-switch tool / .desktop launchers keep working — same file." +else + echo + echo "Install the missing tools, then run: fish_greeting" +end