#!/bin/sh

#shellcheck disable=SC2030,SC2031,SC2059

help() {
	case $1 in
		search*)
			name="search_play"
			args='"search_query"'
			description="this function first searches the $args, then opens the menu(fzf,[d|be]menu,[t|r|w]ofi) with the results,then calls the play function to play the music"
			;;
		play)
			name="play"
			args='"valid_youtube_url_or_id"'
			description="immediately plays the $args in mpv(audio only), for only id prefix it with ID:"
			;;
		loop)
			name="loop"
			args='"print"'
			description="this function runs in a loop to play the next music if the current music is successfully finished
			this function should be put as background process or put it in startup
			passing print argument will show the progress to stdout"
			;;
		play*)
			name="play_next"
			args='"menu"'
			description="immediately plays the next song stored in $logdir/next file in mpv if argument is empty
			passing $args argument will show the $args(fzf,[d|be]menu,[t|r|w]ofi) for selecting what should be played"
			;;
		*)
			name="[ <function_name> ]"
			args="[ <function_arg> ]"
			description="
			<function_name>	<function_arg>	<function_description>

			search_play	search_query	searches first then plays the music in mpv
			play		youtube_url	plays the music in mpv
			play_next	menu(optional)	plays the next music in $logdir/next file
			loop		print(optional)	plays the next music after the current is finished (run it as background process)

			tip:
			type ${0##*/} help <function_name> to get individual help
			"
			;;
	esac
	while read -r line; do
		printf "%s\n" "$line"
	done <<-EOF

		Usage :
		${0##*/} $name $args

		Description :
		$description
	EOF
	exit 0
}

cleanup_shit() {
	rm -rdf "$logdir"
	exit 0
}

get_cookies() {
	# the user has firefox installed
	if [ ! -f "$HOME/.config/google-chrome/Default/Cookies" ]; then
		cp "$(find "$HOME/.mozilla" -type f -iname 'cookies.sqlite' | head -1)" "$logdir/cookies.sqlite"
		sqlite3 "$logdir/cookies.sqlite" "SELECT name, value FROM moz_cookies WHERE host='.youtube.com' and name NOT like 'ST-%' and name NOT like 'VISITOR%' and name NOT like 'PREF%' and name NOT like '%Secure-%';" | tr '|\n' '=;' >"$cookie"
		rm "$logdir/cookies.sqlite"
		return 0
	fi
	for i in $(sqlite3 "$HOME/.config/google-chrome/Default/Cookies" "SELECT name,REPLACE(base64(SUBSTR(encrypted_value,4)),CHAR(10),'') FROM cookies WHERE host_key='.youtube.com';"); do
		printf "%s" "$i" | grep -qE "VISITOR_INFO1_LIVE|VISITOR_PRIVACY_METADATA|Secure-ROLLOUT_TOKEN" && continue
		printf "%s=%s; " "$(printf '%s' "$i" | cut -d'|' -f1)" "$(printf '%s' "$i" | cut -d'|' -f2 | base64 -d | openssl enc -d -aes-128-cbc -K fd621fe5a2b402539dfa147ca9272778 -iv 20202020202020202020202020202020 | cut -c33-)"
	done >"$cookie"
}

get_data() {
	lol=$(date +%s)
	grep -q "SAPISID" "$logdir/cookies" 2>/dev/null && sapisid_hash=$(printf '%s_%s' "$lol" "$(printf '%s %s %s' "$lol" "$(sed -nE 's|.*SAPISID=([^;]*);.*|\1|p' "$logdir/cookies")" "$base_url" | sha1sum | cut -d' ' -f1)") && sapisid_header="Authorization: SAPISIDHASH $sapisid_hash" || sapisid_header=""
	curl -X POST -A "${3:-$agent}" -s "$base_url/youtubei/v1/$1?prettyPrint=false" -H "content-type:application/json" -d "$2" -e "$base_url" -b "$(cat "$cookie")" -H "$sapisid_header"
}

get_music_list() {
	json_next="{
	\"enablePersistentPlaylistPanel\": true,
	\"tunerSettingValue\": \"AUTOMIX_SETTING_NORMAL\",
	\"playlistId\": \"RDAMVM$(cat "$logdir/start")\",
	\"index\": $(cat "$logdir/counter"),
	\"params\": \"wAEB\",
	$([ -e "$logdir/continue_token" ] && cat "$logdir/continue_token")
	\"isAudioOnly\": true,
	\"context\": $(cat "$logdir/context")
	}"
	next_data=$(get_data "next" "$json_next" | sed 's/playlistPanelVideoRenderer/\n/g;s/hasPersistentPlaylistPanel/\n/g' | sed -nE 's|.*text":"(.*)"}.*longBylineText":\{"runs":\[\{"text":"([^"]*)","navigationEndpoint.*videoId":"([^"]*)".*|\1 - \2\t\3|p;s|.*nextRadioContinuationData":\{([^,]*).*|\1,|p')
	#shellcheck disable=SC1091,SC2094
	printf '%s' "$next_data" | sed -e "$(cut -f2 "$logdir/next" | sed 's|^|/|g;s|$|/d|g')" -e '/"continuation"/d' -e "/Slowed/d;/Reverb/d;/Sped/d;/Speed/d;/slowed/d;/Slow/d;/SLOWED/d;/REVERB/d;/SPEED/d;/SPED/d;/EXTENDED/d;/extended/d" >>"$logdir/next"
	printf '%s' "$next_data" | sed -n '/"continuation"/p' >"$logdir/continue_token"
}

get_song_lyrics() {
	json_next="{
	\"enablePersistentPlaylistPanel\": true,
	\"tunerSettingValue\": \"AUTOMIX_SETTING_NORMAL\",
	\"playlistId\": \"RDAMVM$(cat "$logdir/start")\",
	\"index\": $(cat "$logdir/counter"),
	\"videoId\": \"$1\",
	\"isAudioOnly\": true,
	\"context\": $(cat "$logdir/context")
	}"

	browseId=$(get_data "next" "$json_next" "$yt_music_agent" | sed -nE 's|.*"browseId":"(MPLYt[^"]*)".*TRACK_LYRICS.*|\1|p')
	if [ -n "$browseId" ]; then
		json_lyrics="{
		  \"context\": {
		    \"client\": {
		      \"clientName\": \"ANDROID_MUSIC\",
		      \"clientVersion\": \"$yt_music_ver\",
		      \"androidSdkVersion\": $((random_no + extra_up + 29)),
		      \"userAgent\": \"$yt_music_agent\",
		      \"hl\": \"en\",
		      \"timeZone\": \"UTC\",
		      \"utcOffsetMinutes\": 0
		    }
		  },
		  \"browseId\": \"$browseId\"
  		}"
		get_data "browse" "$json_lyrics" "$yt_music_agent" | sed 's/metadata"/\n/g' | sed -nE 's|.*lyricLine":"([^"]*)","cueRange".*"endTimeMilliseconds":"([^"]*)".*|\2\t\1|p' | sed 's/\(\(\w\w*\W*\)\{10\}\)/\1\\n/g' >"$logdir/lyrics"
	fi
}

loop() {
	#this function does exactly what it says, it should run in the background
	#it plays next song after the current song get played completely,it does nothing until u run the search_play function then this code kicks in
	trap cleanup_shit INT HUP TERM
	socat - "UNIX-CONNECT:$socket" | while read -r event; do
		#look for eof event
		if printf "%s" "$event" | grep -q "end-file.*eof"; then
			i=$(cat "$logdir/counter")
			: $((i += 1))
			pgrep -f "$socket" >/dev/null || continue
			[ -n "$(cat "$logdir/next")" ]
			play "$(sed -n "$((i += 1))p" "$logdir/next")" "$1"
			printf '%s' "$i" >"$logdir/counter"
			# shellcheck source=/tmp/yt-music/current
			# shellcheck disable=SC1091
			. "$logdir/current"
			tail -1 "$logdir/next" | grep -q "$ID" && get_music_list
		fi
	done
	cleanup_shit
}

play() {
	#this function does all the heavy lifting of extracting url from given videoId
	#it's also callable, u can use this function to play ur custom youtube URLs
	title=$(printf "%s" "$1" | cut -f1)
	id=$(printf "%s" "$1" | cut -f2 | cut -d"=" -f2 | cut -d"/" -f4 | cut -d'&' -f1)
	[ -z "$id" ] && printf "[ youtube ] Invalid link\n" && exit 1

	#get song's audio url
	json="{
	  \"context\": {
	    \"client\": {
	      \"clientName\": \"ANDROID\",
	      \"clientVersion\": \"$yt_ver\",
	      \"androidSdkVersion\": $((random_no + extra_up + 29)),
	      \"userAgent\": \"$yt_agent\",
	      \"hl\": \"en\",
	      \"timeZone\": \"UTC\",
	      \"utcOffsetMinutes\": 0
	    }
	  },
	  \"videoId\": \"$id\",
	  \"playbackContext\": {
	    \"contentPlaybackContext\": {
	      \"html5Preference\": \"HTML5_PREF_WANTS\"
	    }
	  },
	  \"contentCheckOk\": true,
	  \"racyCheckOk\": true
	}"

	# finding a fix, until then, using yt-dlp (it's slow when getting url)
	#audio_url=$(get_data "player" "$json" "$yt_agent" | sed -nE 's_.*itag":240,"url":"([^"]*)".*_\1_p')
	audio_url=$(yt-dlp "$base_url/watch?v=$id" -f'ba' --get-url)
	[ -z "$audio_url" ] && return 0
	if [ -n "$2" ]; then
		printf "Name >> %s\n" "$title"
		printf "videoID >> %s\n" "$id"
		printf "Audio URL >> %s\n" "$audio_url"
	fi

	curl -s "https://i.ytimg.com/vi/$id/hqdefault.jpg" -o - | magick convert - -crop 270x270+105+45 "$logdir/default.jpg" && notify-send -e -h "string:x-canonical-private-synchronous:${0##*/}" -i "$logdir/default.jpg" "Now Playing" "$title" -t 5000
	pgrep -f "$socket" >/dev/null || (setsid -f mpv --really-quiet --input-ipc-server="$socket" --idle --quiet --user-agent="$yt_agent" >/dev/null && sleep 1)
	printf '{"command":["loadfile","%s","replace"]}\n' "$audio_url" | socat - "$socket"
	printf 'SONG="%s"\nARTIST="%s"\nID="%s"' "$(printf '%s' "$title" | sed 's|[^-]*$||g;s|-$||g;s| $||g;s|^ ||g;s/\\//g;s|"||g')" "$(printf '%s' "$title" | sed 's_.* - __;s| $||;s|"||g')" "$id" >"$logdir/current"

	#self explainatory
	get_song_lyrics "$id" &

	#next songs data
	[ -n "$3" ] && get_music_list &

	pgrep -f "${0##*/} loop" >/dev/null || setsid -f "$0" loop
}

search_play() {
	#run this if u r starting the script first time like this
	#call this by "script-name" "search_play" [ search_query | youtube_id by prefixing with ID:<youtube_id> | youtube url ]
	[ -z "$1" ] && query=$(: | menu "Yt-music [Search]:" "" "60") || query="$1"
	[ -z "$query" ] && notify-send -e "Err.. Search query empty" -u critical -h "string:x-canonical-private-synchronous:${0##*/}" && exit 1

	#storing context
	printf '{"client":{"clientName":"WEB_REMIX","clientVersion":"1.20250616.03.00"}}' >"$logdir/context"
	#extracting your cookies so that the song list are according to your taste
	get_cookies

	if ! (printf '%s' "$query" | grep -q 'https:' || printf '%s' "$query" | grep -q 'ID:'); then
		#json for song search
		json_search="{
		\"context\" : $(cat "$logdir/context"),
		\"query\": \"$query\",
		\"params\": \"EgWKAQIIAWoKEAMQBBAJEAoQBQ%3D%3D\"
		}"

		res=$(get_data "search" "$json_search" "$agent" | sed 's/watchEndpoint"/\n/g' | sed -nE 's_.*videoId":"([^"]*)",.*label":"Play ([^}]*)".*_\2\t\1_p' | menu "Yt-music [Play]:")
	else
		id=$(printf '%s' "$query" | cut -d':' -f2 | cut -d"=" -f2 | cut -d"/" -f4 | cut -d'&' -f1)
		title=$(curl -s "https://www.youtube.com/oembed?url=http://www.youtube.com/watch?v=$id&format=xml" | sed -nE 's|.*<title>([^<]*)<.*|\1|p' | sed 's|&amp;|\&|g')
		res=$(printf '%s\t%s' "$title" "$id")
	fi

	printf '' >"$logdir/next"
	printf "%s\n" "$res" >>"$logdir/next"
	printf "%s" "$res" | cut -f2 >"$logdir/start"
	printf "0" >"$logdir/counter"
	rm -f "$logdir/continue_token"
	[ -z "$res" ] || play "$res" "verbose" "1"
}

play_next() {
	#call this by script-name "play_next" for playing next song immediately
	#or add "menu" after "play_next" to show menu for selecting and playing next song immediately
	#like this script-name "play_next" "menu"
	pgrep -f "$socket" || return 0
	i=$(cat "$logdir/counter")
	if [ -z "$1" ]; then
		: $((i += 1))
		play "$(sed -n "$((i += 1))p" "$logdir/next")" "$1"
	else
		#shellcheck source=/tmp/yt-music/current
		#shellcheck disable=SC1091
		[ -f "$logdir/current" ] && . "$logdir/current"
		notify-send -e -h "string:x-canonical-private-synchronous:${0##*/}" -i "$logdir/default.jpg" "Listening @ $(basename "$0")" "$SONG - $ARTIST"
		next=$(nl -n'ln' -v0 "$logdir/next" | sed "s/^$i /& /" | menu "YT-music [play-next]: " "$i")
		[ -z "$next" ] && return 0
		i=$(printf '%s' "$next" | sed 's///g' | cut -f1 | tr -d ' ')
		play "$(printf '%s' "$next" | cut -f2-)" "verbose"
	fi
	printf '%s' "$i" >"$logdir/counter"
	tail -1 "$logdir/next" | grep -q "$(cut -d'>' -f2 <"$logdir/current")" && get_music_list
}

menu() {
	if command -v bemenu >/dev/null; then
		bemenu -R 20 --fn 'IBM Plex Sans 15' -i -c -W 0.5 -B 3 -p "$1" -l 26 -I "${2:-0}" -P ">>" --bdr="#$GLOBAL_ACCENT" --tf="#$GLOBAL_ACCENT" --hf="#$GLOBAL_ACCENT"
	else
		fzf --prompt="$1" --height=25 --reverse --border=horizontal --header="${2:-0}" --marker=">>"
	fi
}

logdir="/tmp/${0##*/}"
socket="$logdir/${0##*/}-mpvsocket"
base_url="https://music.youtube.com"
[ -d "$logdir" ] || mkdir "$logdir"
cookie="$logdir/cookies"

# user agents, used by script
random_no=$(head /dev/urandom | tr -dc '4-7' | cut -c1)
# web
agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/13$random_no.0.0.0 Safari/537.36"
# random number for android
random_no=$(head /dev/urandom | tr -dc '1-5' | cut -c1)
# android
extra_up=$((random_no > 1))
# youtube
yt_ver="20.23.40"
yt_agent="com.google.android.youtube/$yt_ver (Linux; U; Android 1$random_no) gzip"
# youtube music
yt_music_ver="8.23.51"
yt_music_agent="com.google.android.apps.youtube.music/$yt_music_ver (Linux; U; Android 1$random_no) gzip"

#call this script by script-name "function_name" "query"
[ -z "$1" ] && help "$@"
$1 "$2" "$3"
