Files
GrPopPro/grpoppro
2025-01-01 22:08:50 +02:00

526 lines
14 KiB
Bash
Executable File

#!/usr/bin/env bash
## Definitions
GRPOPPRO_DATA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/grpoppro"
GRPOPPRO_DATA_FILE="$GRPOPPRO_DATA_DIR/lastPlayedSeries"
GRPOPPRO_PLAYLIST_FILE="$GRPOPPRO_DATA_DIR/lastPlaylistPlayed"
GRPOPPRO_COOKIE_FILE="$GRPOPPRO_DATA_DIR/cookies.txt"
GRPOPPRO_HISTORY_FILE="$GRPOPPRO_DATA_DIR/history"
INTERACTIVE="on"
DEBUG="on"
menu="fuzzel -d -p "
#menu="fzf -i --prompt="
apiurl="https://coverapi.store"
### Curl constants
USER_AGENT="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0"
ACCEPT="Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
ACCEPT_LANGUAGE="Accept-Language: en-US,en;q=0.5"
CONNECTION="Connection: keep-alive"
UP_IN_REQ="Upgrade-Insecure-Requests: 1"
### End of curl constants
player="mpv"
MPV_OPTS='--vo=gpu \
--gpu-api=opengl \
--opengl-es=yes \
--msg-level=all=no \
--really-quiet \
--profile=sw-fast \
--no-ytdl \
--http-header-fields="User-Agent: '"$USER_AGENT"'" \
--fs'
## End of definitions
# Display a message
function message {
if [[ "$INTERACTIVE" == "on" ]]; then
notify-send -t 2000 "$1"
else
echo -e "\n\t$1"
fi
}
# Standard curl request
function curlRequest {
local url="$1"
local custom_method="${2:-}"
local custom_accept="${3:-}"
local shifts=1 # Initialize shifts to account for the URL
shift # Shift out the URL
# Check for custom method and Accept header, and shift accordingly
if [[ -n "$custom_method" ]]; then
shifts=$((shifts + 1))
shift
fi
if [[ -n "$custom_accept" ]]; then
shifts=$((shifts + 1))
shift
fi
# Apply default values if not provided
custom_method="${custom_method:-GET}"
custom_accept="${custom_accept:-$ACCEPT}"
# Dynamically shift the total number of times needed
shift $((shifts - 1))
if [[ "$DEBUG" == "off" ]]; then
if [[ "$OSTYPE" == "linux-gnu" ]]; then
response="$(curl_ff117 -s -L \
-c"$GRPOPPRO_COOKIE_FILE" \
-b"$GRPOPPRO_COOKIE_FILE" \
"$url" \
--compressed \
-X "$custom_method" \
-A "$USER_AGENT" \
-H "$custom_accept" \
-H "$ACCEPT_LANGUAGE" \
-H "$CONNECTION" \
-H "$UP_IN_REQ" \
"$@"
)"
else
response="$(curl -s -L \
-c "$GRPOPPRO_COOKIE_FILE" \
-b "$GRPOPPRO_COOKIE_FILE" \
"$url" \
--compressed \
-X "$custom_method" \
-A "$USER_AGENT" \
-H "$custom_accept" \
-H "$ACCEPT_LANGUAGE" \
-H "$CONNECTION" \
-H "$UP_IN_REQ" \
"$@"
)"
fi
else
if [[ "$OSTYPE" == "linux-gnu" ]]; then
LOG_FILE="/tmp/grpoppro_curl_commands.log"
local curl_command=(
"curl_ff117"
"-s"
"-L"
"-c$GRPOPPRO_COOKIE_FILE"
"-b$GRPOPPRO_COOKIE_FILE"
"$url"
"-X"
"$custom_method"
"-H $custom_accept"
"$@"
)
else
LOG_FILE="$GRPOPPRO_DATA_DIR/grpoppro_curl_commands.log"
local curl_command=(
"curl"
"-s"
"-L"
"-c $GRPOPPRO_COOKIE_FILE"
"-b $GRPOPPRO_COOKIE_FILE"
"$url"
"--compressed"
"-X"
"$custom_method"
"-A $USER_AGENT"
"-H $custom_accept"
"-H $ACCEPT_LANGUAGE"
"-H $CONNECTION"
"-H $UP_IN_REQ"
"$@"
)
fi
{
echo -e "<===========================\n"
echo "Called by: ${FUNCNAME[1]}"
echo ""
echo -e "$(date '+%d-%m-%Y %H:%M:%S') -> ${curl_command[5]}\n"
printf "%s\n" "${curl_command[@]}"
echo ""
echo -e "===========================>\n"
} >> "$LOG_FILE"
response=$("${curl_command[@]}")
fi
exit_code="$?"
if [[ "$exit_code" -ne 0 ]]; then
message "Error: Failed to execute curl request."
exit 1
fi
echo "$response"
}
# Print usage information
function usage {
echo -e "\n\tUsage:\n\n\t""$(basename "$0")"" \"name of film or show\" [season] [episode(ex 01 02 .. 10 11)]"
echo -e "\tOr ""$(basename "$0")"" [--menu|--resume|--select|--finish|--history]"
echo -e "\tOr ""$(basename "$0")"" --open \"name of film or show\""
exit 0
}
# Ensure fzf is installed for Termux
function getFzfForTermux {
if ! command -v fzf >/dev/null 2>&1; then
apt install -y fzf
fi
}
# Retrieve IMDb ID
function getIMDBID {
response="$(curlRequest "https://www.imdb.com/find/?q=$title")"
imdbid="$(echo "$response" \
| grep -io ' href=['"'"'"][^"'"'"']*['"'"'"]' \
| grep 'href="/title/' \
| head -n1 \
| sed -e 's/href="\/title\///' -e 's/\/?.*//' -e 's/^ //'
)"
# response="$(curlRequest "http://imdb.konsthol.eu/find?q=$title")"
# imdbid="$(echo "$response" \
# | grep -io ' href=['"'"'"][^"'"'"']*['"'"'"]' \
# | grep title | head -n1 \
# | sed -e 's/^ //' -e 's/href="\/title\///' -e 's/"//')"
if ! [[ "$imdbid" == *"tt"* ]]; then
message "Film or show not found in imdb"
exit 0
fi
}
# Prepare basic steps for a request
function basics {
title="$1"
title="${title// /%20}"
getIMDBID "$title"
simpleurl="$apiurl/embed/$imdbid/"
}
# Retrieve internal ID
function getInternalID {
response="$(curlRequest "$simpleurl")"
if echo "$response" | grep -q "404 Not Found"; then
message "Non valid response"
exit 0
fi
if echo "$response" | grep -q "id='el-content'></div>"; then
message "No file found"
exit 0
fi
internalid="$(echo "$response" \
| grep news_id \
| awk '{print $5}' \
| awk -F\' '{print $2}'
)"
}
# Retrieve movie stream URL
function getMovieStreamUrl {
PHPSESSID="$(grep 'PHPSESSID' "$GRPOPPRO_COOKIE_FILE" | awk '{print $NF}')"
local headers=(
'-H Accept-Encoding: gzip, deflate, br, zstd'
'-H Content-Type: application/x-www-form-urlencoded; charset=UTF-8'
'-H X-Requested-With: XMLHttpRequest'
"-H Origin: $apiurl"
'-H DNT: 1'
'-H Sec-GPC: 1'
"-H Referer: $simpleurl"
"-H Cookie: PHPSESSID=$PHPSESSID"
'-H Sec-Fetch-Dest: empty'
'-H Sec-Fetch-Mode: cors'
'-H Sec-Fetch-Site: same-origin'
'-H TE: trailers'
'--data-raw'
"mod=players&news_id=$internalid"
)
response="$(curlRequest "$apiurl/engine/ajax/controller.php" \
"POST" \
'Accept: application/json, text/javascript, */*; q=0.01' \
"${headers[@]}"
)"
streamurl="$(echo "$response" \
| sed 's/\\//g' \
| grep -oP 'file:"\K[^"]+'
)"
}
# Stream the video
function play {
if [[ "$player" == "mpv" ]]; then
MPVPID="$(pidof mpv | cut -d' ' -f1)"
[[ -n "$MPVPID" ]] && kill -s SIGTERM "$MPVPID"
player="mpv --save-position-on-quit ${MPV_OPTS}"
fi
[[ "$OSTYPE" != "linux-gnu" ]] && INTERACTIVE="off" && streamurl="${streamurl//https/http}"
# No setsid -f here because we need to wait for mpv to finish
eval "$player" "$streamurl"
}
# Dump data to files
function dumpData {
echo "$title" > "$GRPOPPRO_DATA_FILE"
echo "$seasonEpisode" >> "$GRPOPPRO_DATA_FILE"
echo "$streamurl" >> "$GRPOPPRO_DATA_FILE"
message "Last Played -> $seasonEpisode"
echo "$title|$seasonEpisode|$streamurl" >> "$GRPOPPRO_HISTORY_FILE"
tac "$GRPOPPRO_HISTORY_FILE" | awk -F'|' '!seen[$1]++' | tac > "${GRPOPPRO_DATA_DIR}/temp.txt" && mv "${GRPOPPRO_DATA_DIR}/temp.txt" "$GRPOPPRO_HISTORY_FILE"
}
# Source data from files
function sourceData {
if [[ -f "$GRPOPPRO_DATA_FILE" ]]; then
lastSeriesPlayed="$(sed -n 1p "$GRPOPPRO_DATA_FILE")"
lastSeasonEpisodePlayed="$(sed -n 2p "$GRPOPPRO_DATA_FILE")"
lastEpisodePlayedURL="$(sed -n 3p "$GRPOPPRO_DATA_FILE")"
fi
}
# Perform menu search
function menuSearch {
# Two requests to the api
if [[ "$INTERACTIVE" == "on" ]]; then
title=$(echo "" | wofi --dmenu --insensitive --prompt="Search Series" --width=300 --height=50)
else
read -r -p "Search Series: " title
fi
if [[ -z "$title" ]]; then
message "Nothing given"
exit 0
fi
title="${title// /%20}"
getIMDBID "$title"
simpleurl="$apiurl/embed/$imdbid/"
getInternalID "$simpleurl"
response="$(curlRequest "$apiurl/uploads/playlists/$internalid.txt")"
echo "$response" > "$GRPOPPRO_PLAYLIST_FILE"
title="$(grep "mp4" "$GRPOPPRO_PLAYLIST_FILE" \
| head -n1 \
| awk -F/ '{print $(NF-1)}'
)"
title="${title//_/ }"
title="${title//-/ }"
seasonEpisode="$(jq '.[]' "$GRPOPPRO_PLAYLIST_FILE" \
| grep "mp4" \
| awk -F/ '{print $NF}' \
| awk -F. '{print $1}' \
| eval "$menu"\""$title" \"
)"
if [[ -z "$seasonEpisode" ]]; then
message "Nothing Selected"
exit 0
fi
streamurl="$(jq '.[]' "$GRPOPPRO_PLAYLIST_FILE" \
| grep "mp4" \
| grep "${seasonEpisode}" \
| head -n1 \
| awk -F\" '{print $4}'
)"
dumpData "$title" "$seasonEpisode"
play
resume
}
# Resume last watched series
function resume {
# Zero requests to the api
if [[ ! -f "$GRPOPPRO_DATA_FILE" ]]; then
message "Nothing to resume"
exit 0
fi
sourceData
title=$lastSeriesPlayed
while true
do
sourceData
seasonEpisode="$(jq '.[]' "$GRPOPPRO_PLAYLIST_FILE" \
| grep "mp4" \
| awk -F/ '{print $NF}' \
| awk -F. '{print $1}' \
| sed -n -e "0,/$lastSeasonEpisodePlayed/!p" \
| eval "$menu"\""$title" \"
)"
if [[ -z "$seasonEpisode" ]]; then
message "Nothing Selected"
exit 0
fi
streamurl="$(jq '.[]' "$GRPOPPRO_PLAYLIST_FILE" \
| grep "${seasonEpisode}" \
| head -n1 \
| awk -F\" '{print $4}'
)"
dumpData "$title" "$seasonEpisode"
play
done
}
# Select which series to resume
function resumeSeries {
# Zero requests to the api if it's the last series you watched
# Two requests to the api otherwise
if [[ ! -f "$GRPOPPRO_HISTORY_FILE" ]]; then
message "No history found"
exit 0
fi
resume_series="$(awk -F'|' '{print $1}' "$GRPOPPRO_HISTORY_FILE" \
| sort \
| uniq \
| eval "$menu"\"Select Series \"
)"
if [[ -z "$resume_series" ]]; then
message "No series selected"
exit 0
fi
resume_episode="$(grep "^$resume_series|" "$GRPOPPRO_HISTORY_FILE" | awk -F'|' '{print $2}')"
if [[ -n "$resume_episode" ]]; then
message "Continuing $resume_series from $resume_episode"
fi
resume_streamurl=$(grep "^$resume_series|$resume_episode|" "$GRPOPPRO_HISTORY_FILE" | awk -F'|' '{print $3}')
title="$resume_series"
lastTitle="$(sed -n 1p "$GRPOPPRO_DATA_FILE")"
if [[ ! "$title" == "$lastTitle" ]]; then
title="${title// /%20}"
getIMDBID "$title"
simpleurl="$apiurl/embed/$imdbid/"
getInternalID "$simpleurl"
response="$(curlRequest "$apiurl/uploads/playlists/$internalid.txt")"
echo "$response" > "$GRPOPPRO_PLAYLIST_FILE"
fi
title="$(grep "mp4" "$GRPOPPRO_PLAYLIST_FILE" | head -n1 | awk -F/ '{print $(NF-1)}')"
title="${title//_/ }"
title="${title//-/ }"
seasonEpisode="$resume_episode"
streamurl="$resume_streamurl"
dumpData "$title" "$seasonEpisode"
play
resume
}
# Resume last watched unfinished episode
function resumeUnfinishedEpisode {
# Zero requests to the api
if [[ ! -f "$GRPOPPRO_DATA_FILE" ]]; then
message "Nothing to resume"
exit 0
fi
sourceData
message "Continuing $lastSeriesPlayed from $lastSeasonEpisodePlayed"
streamurl="$lastEpisodePlayedURL"
if [[ "$player" == "mpv" ]]; then
MPVPID="$(pidof mpv | cut -d' ' -f1)"
[[ -n "$MPVPID" ]] && kill -s SIGTERM "$MPVPID"
player="mpv --resume-playback ${MPV_OPTS}"
fi
[[ "$OSTYPE" != "linux-gnu" ]] && INTERACTIVE="off" && streamurl="${streamurl//https/http}"
# No setsid -f here because we need to wait for mpv to finish
eval "$player" "$streamurl"
resume
}
# Open in browser to watch
function openInBrowser {
# One request to the api
basics "$2"
getInternalID "$simpleurl"
xdg-open "$simpleurl"
exit 0
}
function getHistory {
if [[ ! -f "$GRPOPPRO_HISTORY_FILE" ]]; then
message "No history found"
exit 0
fi
history="$(awk -F'|' '{print $1,":<=> At =>",$2}' "$GRPOPPRO_HISTORY_FILE" | column -t -s':')"
message "$history"
}
### Program starts here ###
# Ensure curl-impersonate is installed
if ! command -v curl-impersonate-ff >/dev/null 2>&1; then
message "You should get curl-impersonate to make it seem less obvious you use curl"
exit 0
fi
# Make sure the data directory exists
mkdir -p "$GRPOPPRO_DATA_DIR"
# Exit if a VPN connection is active
if [[ "$OSTYPE" == "linux-gnu" ]]; then
if nmcli connection show --active | grep -i -q wireguard; then
echo "Exiting because a vpn connection is active"
exit 0
fi
fi
# Prepare the necessary steps for Termux environment
[[ "$OSTYPE" != "linux-gnu" ]] && INTERACTIVE="off" && menu="fzf -i --prompt=seasonXepisode" && player="am start -n is.xyz.mpv/.MPVActivity -a android.intent.action.VIEW -d" && getFzfForTermux
[[ "$#" -lt 1 ]] && usage
[[ "$#" -gt 3 ]] && usage
[[ "$1" == "--open" ]] && openInBrowser "$@"
[[ "$1" == "--menu" ]] && menuSearch
[[ "$1" == "--resume" ]] && resume
[[ "$1" == "--finish" ]] && resumeUnfinishedEpisode
[[ "$1" == "--select" ]] && resumeSeries
[[ "$1" == "--history" ]] && getHistory
# One arguement and it contains the imdb id
# Two requests to the api
if [[ "$1" == *"tt"* ]] && [[ "$#" -eq 1 ]] && [[ "$1" != "--menu" ]] && [[ "$1" != "--resume" ]] && [[ "$1" != "--finish" ]] && [[ "$1" != "--select" ]] && [[ "$1" != "--history" ]]; then
simpleurl="$1"
getInternalID "$simpleurl"
getMovieStreamUrl "$internalid"
play
exit 0
fi
# Series URL
if [[ "$#" -eq 3 ]]; then
# Two requests to the api
basics "$1"
getInternalID "$simpleurl"
season="$2"
episode="$3"
response="$(curlRequest "$apiurl/uploads/playlists/$internalid.txt")"
streamurl="$(echo "$response" \
| jq '.[]' \
| grep "mp4" \
| grep "${season}x${episode}" \
| head -n1 \
| awk -F\" '{print $4}'
)"
title="${title//%20/ }"
capitalized_title="$(echo "$title" | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)}1')"
message "Loading $capitalized_title at season $season on episode $episode"
play
exit 0
fi
# Movies URL
if [[ "$#" -eq 1 ]] && [[ "$1" != "--menu" ]] && [[ "$1" != "--resume" ]] && [[ "$1" != "--finish" ]] && [[ "$1" != "--select" ]] && [[ "$1" != "--history" ]]; then
# Two requests to the api
basics "$1" # Gets the title, performs one request to imdb and gets the complete url
getInternalID "$simpleurl" # Gets the complete url and performs one request to get the internal id or inform us it does not exist
getMovieStreamUrl "$internalid" # Gets the internal id and performs one request to get the stream url
title="${title//%20/ }"
capitalized_title="$(echo "$title" | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)}1')"
message "Loading $capitalized_title"
play # It uses mpv to stream the file
exit 0
fi
exit 0