#!/usr/bin/env bash set -u CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/waybar-spotify" ART_URL_FILE="$CACHE_DIR/art_url" ART_FILE="$CACHE_DIR/cover.jpg" PLAYER="" mkdir -p "$CACHE_DIR" json_escape() { local s="$1" s=${s//\\/\\\\} s=${s//\"/\\\"} s=${s//$'\n'/\\n} printf '%s' "$s" } markup_escape() { local s="$1" s=${s//&/&} s=${s///>} printf '%s' "$s" } icon_markup() { local icon="$1" local color="$2" printf '%s' "$color" "$icon" } truncate_text() { local text="$1" local max_len="$2" if (( ${#text} <= max_len )); then printf '%s' "$text" return fi printf '%s...' "${text:0:max_len-3}" } pick_player() { local candidates local p local c mapfile -t candidates < <(playerctl -l 2>/dev/null) if (( ${#candidates[@]} == 0 )); then return 1 fi for c in spotify spotifyd ncspot nspot; do for p in "${candidates[@]}"; do if [[ "$p" == "$c" || "$p" == "$c."* ]]; then PLAYER="$p" return 0 fi done done PLAYER="${candidates[0]}" return 0 } download_cover_if_needed() { local art_url="$1" if [[ -z "$art_url" ]]; then return fi local old_url="" if [[ -f "$ART_URL_FILE" ]]; then old_url=$(cat "$ART_URL_FILE" 2>/dev/null) fi if [[ "$art_url" == "$old_url" ]]; then return fi printf '%s' "$art_url" > "$ART_URL_FILE" if [[ "$art_url" == file://* ]]; then local local_path="${art_url#file://}" if [[ -f "$local_path" ]]; then cp -f "$local_path" "$ART_FILE" 2>/dev/null || true fi return fi if command -v curl >/dev/null 2>&1; then curl -LfsS --max-time 4 "$art_url" -o "$ART_FILE" 2>/dev/null || true fi } open_cover() { if [[ -f "$ART_FILE" ]]; then xdg-open "$ART_FILE" >/dev/null 2>&1 & fi } control_player() { local action="$1" case "$action" in --toggle) playerctl -p "$PLAYER" play-pause >/dev/null 2>&1 || true ;; --next) playerctl -p "$PLAYER" next >/dev/null 2>&1 || true ;; --prev) playerctl -p "$PLAYER" previous >/dev/null 2>&1 || true ;; --stop) playerctl -p "$PLAYER" stop >/dev/null 2>&1 || true ;; --open-art) open_cover ;; esac } if [[ $# -gt 0 ]]; then control_player "$1" exit 0 fi if ! command -v playerctl >/dev/null 2>&1; then printf '{"text":"%s","tooltip":"playerctl missing: install playerctl for media controls","class":"idle","alt":"idle"}\n' "$(json_escape "$(icon_markup "" "#A3BE8C")")" exit 0 fi if ! pick_player; then printf '{"text":"%s","tooltip":"No active player","class":"idle","alt":"idle"}\n' "$(json_escape "$(icon_markup "" "#A3BE8C")")" exit 0 fi status=$(playerctl -p "$PLAYER" status 2>/dev/null || true) if [[ -z "$status" ]]; then printf '{"text":"%s","tooltip":"No active player status","class":"idle","alt":"idle"}\n' "$(json_escape "$(icon_markup "" "#A3BE8C")")" exit 0 fi title=$(playerctl -p "$PLAYER" metadata xesam:title 2>/dev/null || true) artist=$(playerctl -p "$PLAYER" metadata xesam:artist 2>/dev/null || true) album=$(playerctl -p "$PLAYER" metadata xesam:album 2>/dev/null || true) art_url=$(playerctl -p "$PLAYER" metadata mpris:artUrl 2>/dev/null || true) state_class=$(printf '%s' "$status" | tr '[:upper:]' '[:lower:]') if [[ "$state_class" == "stopped" ]]; then text_markup="Stopped $(icon_markup "" "#D08770") $(icon_markup "" "#A3BE8C")" printf '{"text":"%s","class":"stopped","alt":"stopped"}\n' "$(json_escape "$text_markup")" exit 0 fi if [[ -z "$artist" && -z "$title" ]]; then printf '{"text":"%s","class":"idle","alt":"idle"}\n' "$(json_escape "$(icon_markup "" "#A3BE8C")")" exit 0 fi if [[ -z "$title" ]]; then title="Unknown title" fi if [[ -z "$artist" ]]; then artist="Unknown artist" fi if [[ -z "$album" ]]; then album="Unknown album" fi download_cover_if_needed "$art_url" display_track="$(truncate_text "$artist - $title" 52)" state_icon="" if [[ "$state_class" == "paused" ]]; then state_icon="" fi app_icon="" if [[ "$PLAYER" == spotify* || "$PLAYER" == spotifyd* || "$PLAYER" == *ncspot* || "$PLAYER" == *nspot* ]]; then app_icon="" fi track_color="#ECEFF4" state_color="#88C0D0" app_color="#A3BE8C" if [[ "$state_class" == "paused" ]]; then track_color="#E5E9F0" state_color="#EBCB8B" fi cover_line="Cover art: unavailable" if [[ -f "$ART_FILE" ]]; then cover_line="Cover art: cached (right-click opens image)" fi tooltip=$(cat <$(markup_escape "$display_track") $(icon_markup "$state_icon" "$state_color") $(icon_markup "$app_icon" "$app_color")" printf '{"text":"%s","tooltip":"%s","class":"%s","alt":"%s"}\n' \ "$(json_escape "$text_markup")" \ "$(json_escape "$tooltip")" \ "$(json_escape "$state_class")" \ "$(json_escape "$state_class")"