#!/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" 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:131.0) Gecko/20100101 Firefox/131.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" # --no-load-scripts \ 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)) # 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" \ # "$@" # )" LOG_FILE="/tmp/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" # log_string=$(printf "curl -s -L \n -c %s \n -b %s \n %s \n --compressed \n -X %s \n -A %s \n -H %s \n -H %s \n -H %s \n -H %s \n %s \n" \ # "$GRPOPPRO_COOKIE_FILE" \ # "$GRPOPPRO_COOKIE_FILE" \ # "$url" \ # "$custom_method" \ # "$USER_AGENT" \ # "$custom_accept" \ # "$ACCEPT_LANGUAGE" \ # "$CONNECTION" \ # "$UP_IN_REQ" \ # "$@") { echo -e "<===========================\n" echo "Called by: ${FUNCNAME[1]}" echo "" echo -e "$(date '+%d-%m-%Y %H:%M:%S') -> ${curl_command[5]}\n" # echo -e "$log_string" printf "%s\n" "${curl_command[@]}" echo "" echo -e "===========================>\n" } >> "$LOG_FILE" response=$("${curl_command[@]}") 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]" 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; then message "Non valid response" exit 0 fi if echo "$response" | grep -q "id='el-content'>"; 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 menu function goes from play to resume 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="$(grep "mp4" "$GRPOPPRO_PLAYLIST_FILE" \ | awk -F\" '{print $8}' \ | awk -F/ '{print $NF}' \ | awk -F. '{print $1}' \ | eval "$menu"\""$title" \" )" if [[ -z "$seasonEpisode" ]]; then message "Nothing Selected" exit 0 fi streamurl="$(grep "mp4" "$GRPOPPRO_PLAYLIST_FILE" \ | grep "${seasonEpisode}" \ | head -n1 \ | awk -F\" '{print $8}' )" 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="$(grep "mp4" "$GRPOPPRO_PLAYLIST_FILE" \ | awk -F\" '{print $8}' \ | 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="$(grep "mp4" "$GRPOPPRO_PLAYLIST_FILE" \ | grep "${seasonEpisode}" \ | head -n1 \ | awk -F\" '{print $8}' )" 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 menu function goes from play to resume 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 } ### Program starts here ### # 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 # One arguement and it contains the imdb id # Two requests to the api if [[ "$1" == *"tt"* ]] && [[ "$#" -eq 1 ]] && [[ "$1" != "--menu" ]] && [[ "$1" != "--resume" ]] && [[ "$1" != "--select" ]]; 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" \ | grep "mp4" \ | grep "${season}x${episode}" \ | head -n1 \ | awk -F\" '{print $8}' )" message "Loading $title at season $season on episode $episode" play exit 0 fi # Movies URL if [[ "$#" -eq 1 ]] && [[ "$1" != "--menu" ]] && [[ "$1" != "--resume" ]] && [[ "$1" != "--select" ]]; 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/ }" message "Loading $title" play # It uses mpv to stream the file exit 0 fi exit 0