#!/usr/bin/bash

# heos-histlist  Copyright 2025-2026  Norman Carver

# Script program to Browse the HEOS History Tracks or Stations,
# i.e., to print (to stdout) the History list.
#
# Sends commands to the default HEOS master device, but can select alternative
# using the MASTER_IPNUM argument option.

# The HEOS History is NOT exactly the same as the HEOS app Recents.
# The app Recents consists of four categories: Tracks, Stations,
# Albums, Playlists, as well as All (that shows all categories).
# History allows one to view only Tracks and Stations, so recent
# plays under Albums and Playlists cannot be listed.  Playing items
# from Local (DLNA) Server seems to put things under Albums even when
# just a single track is played, so don't show in History.


# Source ID (SID) for History (from HEOS Protocol doc):
# (Set to empty string to cause it to be determined from Music Sources.)
sid_history=1026


function print_usage()
{
    echo -e "Print a list of the HEOS History Tracks (or Stations).\n" >&2
    echo    "Usage: heos-histlist [-nN] [-s] [MASTER_IPNUM]" >&2
    echo    "where:" >&2
    echo    "  MASTER_IPNUM -- use a non-default HEOS master device;" >&2
    echo    "  (IPNUM means the last quad of an IP address only: i.e., 0--255.)" >&2
    echo    "Options:" >&2
    echo    "  -nN -- print up to N items (default is 25);" >&2
    echo    "  -s  -- show Stations History instead of Tracks History;" >&2
}


if [[ "$1" == --help ]]; then
    print_usage
    exit 0
fi

# Process supplied options:
numitems=25
cid=TRACKS
debugopt=""
while [[ "$1" == -* ]]; do
    if [[ "$1" == -n* ]]; then
        numitems=${1#-n}
        if [[ ! ("$numitems" =~ ^[0-9]+$ && "$numitems" -ge 1) ]]; then
            echo "Error: -nN must be an integer >= 1 ('$numitems')!" >&2
            exit 1
        fi
    elif [[ "$1" == -s ]]; then
        cid=STATIONS
    elif [[ "$1" == -- ]]; then
        shift;break
    elif [[ "$1" == --debug ]]; then
        debugopt=--debug
    else
        echo "Error: invalid option '$1'" >&2
        echo "(use '--' to signal end of options, with -* arguments)" >&2
        exit 1 
    fi
    shift
done

# Process command arguments:
if [[ $# -gt 1 ]]; then
    print_usage
    exit 1
fi

master=""
if [[ $# == 1 ]]; then
    master=${1}
fi
heosdir=$(dirname "$0")


# heosutil-run-command error checking function (one json output):
# args: 1) command result variable; 2) error message; [3) error action]
# (default action on error is 'exit 1', use $3 of 'return 1' to continue execution)
function error_check_run_command()
{
    cmdresult=${!1}
    if [[ ($? != 0) || ("$cmdresult" != *'"result": "success"'*) ]]; then
        if [[ "$cmdresult" == Error:* || ($debugopt == --debug && "$cmdresult" == ERROR:*) ]]; then
            echo "$cmdresult" >&2
        fi
        if [[ -n "$2" ]]; then echo "$2" >&2; fi
        if [[ $# == 3 ]]; then $3; else exit 1; fi
    else
        return 0
    fi
}


# heosutil-run-command error checking function (two json outputs):
# args: 1) command result variable; 2) error message; [3) error action]
# (default action on error is 'exit 1', use $3 of 'return 1' to continue execution)
function error_check_run_command_2json()
{
    cmdresult=${!1}
    if [[ ($? != 0) || ("$cmdresult" != *'"result": "success"'*'"result": "success"'*) ]]; then
        if [[ "$cmdresult" == Error:* || ($debugopt == --debug && "$cmdresult" == ERROR:*) ]]; then
            echo "$cmdresult" >&2
        fi
        if [[ -n "$2" ]]; then echo "$2" >&2; fi
        if [[ $# == 3 ]]; then $3; else exit 1; fi
    else
        return 0
    fi
}


# If necessary, determine History SID from Music Sources:
if [[ -z "$sid_history" ]]; then
    # Get Music Sources info so can get History SID:
    sources=$("$heosdir"/heosutils/heosutil-run-command "heos://browse/get_music_sources" $master)
    error_check_run_command sources "Error: failed getting Music Sources listing!"

    sid=${sources#*\{\"name\": \"History\"*\"sid\":\ }
    sid_history=${sid%%[,\}]*}
fi

# Get History browse list (action result in 2nd JSON object):
browse=$("$heosdir"/heosutils/heosutil-run-command --numjson=2 "heos://browse/browse?sid=${sid_history}&cid=${cid}&range=0,$((numitems-1))" $master)
error_check_run_command_2json browse "Error: failed getting $cid History listing!"

# Get number of available items:
count=$(grep -Eo 'count=[0-9]+' <<<"$browse")
if [[ "$count" == "count=0" ]]; then
    echo "Error: HEOS $cid History is Empty!" >&2
    exit 1
fi

# Extract Payload info:
payload=$(grep -Eo '"payload": \[.+\][,}]' <<<"$browse")

# Use grep to make line-based list of the History items:
itemslist=$(grep -Eo '\{[^}]*"container":[^}]+\}' <<<"$payload")
if [[ -z "$itemslist" ]]; then
    echo "Error: failed extracting HEOS $cid History, try again!" >&2
    exit 1
fi

# Reformat and print out History list:
{ cnt=0
  while read line; do
      linetype=${line#*\"type\":\ \"}
      type=${linetype%%\",*}
      linename=${line##*\"name\":\ \"}
      name=${linename%%\",*}
      cnt=$((cnt + 1))
      if [[ "$cid" == STATIONS ]]; then
          printf "%2d) %s: \"%s\"\n" $cnt "$type" "$name"
      else
          linealbum=${line#*\"album\":\ \"}
          album=${linealbum%%\",*}
          lineartist=${line#*\"artist\":\ \"}
          artist=${lineartist%%\",*}
          printf "%2d) %s: \"%s\" (by %s from \"%s\")\n" $cnt "$type" "$name" "$artist" "$album"
      fi
  done 
} <<<"$itemslist"

#EOF
