Files
dra-cla/dra-cla
2023-10-10 17:37:19 +02:00

545 lines
14 KiB
Bash
Executable File

#!/bin/sh
VERSION="2.2.2"
#######################
# AUXILIARY FUNCTIONS #
#######################
help_text () {
while IFS= read -r line; do
printf "%s\n" "$line"
done <<-EOF
Usage:
${0##*/} [-v] [-q <quality>] [-a <episode>] [-d | -p <download_dir>] [<query>]
${0##*/} [-v] [-q <quality>] -c
${0##*/} -h | -D | -U | -V
Options:
-c continue watching drama from history
-a specify episode to watch
-h show helptext
-d download episode
-p download episode to specified directory
-q set video quality (best|worst|360|480|720|1080)
-v use VLC as the media player
-D delete history
-U fetch update from github
-V print version number and exit
Episode selection:
Multiple episodes can be chosen given a range
Choose episode [1-13]: 1 6
This would choose episodes 1 2 3 4 5 6
To select the last episode use -1
When selecting non-interactively, the first result will be
selected, if drama is passed
EOF
}
version_text () {
inf "Version: $VERSION" >&2
}
die () {
err "$*"
exit 1
}
# get the newest version of this script from github and replace it
update_script () {
update="$(curl -s "https://raw.githubusercontent.com/CoolnsX/dra-cla/main/dra-cla" | diff -u "$0" -)"
if [ -z "$update" ]; then
inf "Script is up to date :)"
else
if printf '%s\n' "$update" | patch "$0" - ; then
inf "Script has been updated"
else
die "Can't update for some reason!"
fi
fi
}
# checks if dependencies are present
dep_ch () {
for dep; do
if ! command -v "$dep" >/dev/null ; then
err "Program \"$dep\" not found. Please install it."
#aria2c is in the package aria2
[ "$dep" = "aria2c" ] && err "To install aria2c, Type <your_package_manager> aria2"
die
fi
done
}
download () {
case $2 in
*m3u8*)
ffmpeg -loglevel error -stats -referer "$1" -i "$2" -c copy "$download_dir/${3}${4}.mp4" ;;
*)
aria2c --summary-interval=0 -x 16 -s 16 --referer="$1" "$2" --dir="$download_dir" -o "${3}${4}.mp4" --download-result=hide ;;
esac
}
#############
# SEARCHING #
#############
# gets drama names along with its id for search term
search_drama () {
search=$(printf '%s' "$1" | tr ' ' '-' )
curl -s "$base_url/search.html?keyword=$search" |
sed -nE 's_^[[:space:]]*<a href="/videos/([^"]*)">_\1_p'
}
check_episode () {
curl -s "$base_url/videos/$1" | sed '/Latest Episodes/,$d' | sed -nE "s_^[[:space:]]*<a href.*videos/${2}(.*)\">_\1_p"
}
process_hist_entry () {
temp_drama_id=$(printf "%s" "$drama_id" | sed 's/[0-9]*.$//')
latest_ep=$(printf "%s" "$drama_id" | sed "s/$temp_drama_id//g")
current_ep=$(check_episode "$drama_id" "$temp_drama_id" | head -n 1)
if [ -n "$current_ep" ] && [ "$current_ep" -ge "$latest_ep" ]; then
printf "%s\n" "$drama_id"
fi
}
# compares history with asianembed, only shows unfinished drama
search_history () {
tput clear
[ ! -s "$logfile" ] && die "History is empty"
search_results=$(while read -r drama_id; do process_hist_entry & done < "$logfile"; wait)
[ -z "$search_results" ] && die "No unwatched episodes"
one_hist=$(printf '%s\n' "$search_results" | grep -e "$" -c)
[ "$one_hist" = 1 ] && select_first=1
drama_selection "$search_results"
ep_choice_start=$(sed -n -E "s/${selection_id}(.*)/\1/p" "$logfile")
}
##################
# URL PROCESSING #
##################
# get the download page url
get_dpage_link() {
drama_id="$1"
ep_no="$2"
curl -s "$base_url/videos/${drama_id}${ep_no}" | sed -nE 's_^[[:space:]]*<iframe src="([^"]*)".*_\1_p' |
sed 's/^/https:/g'
}
decrypt_link() {
fembed_id=$(curl -A "uwu" -s "$1" | sed -nE 's_.*fembed.*/v/([^#]*).*_\1_p')
[ -z "$fembed_id" ] || video_links=$(curl -A "uwu" -s -X POST "https://fembed9hd.com/api/source/$fembed_id" -H "x-requested-with:XMLHttpRequest" | sed -e 's/\\//g' -e 's/.*data"://' | tr "}" "\n" | sed -nE 's/.*file":"(.*)","label":"([^"]*)".*/\2>\1/p')
[ -z "$video_links" ] || return 0
secret_key='3933343232313932343333393532343839373532333432393038353835373532'
iv='39323632383539323332343335383235'
ajax_url="$base_url/encrypt-ajax.php"
ajax=$(printf "%s" "$1" | sed -nE 's/.*id=([^&]*)&.*/\1/p' | openssl enc -e -aes256 -K "$secret_key" -iv "$iv" -a)
video_links=$(curl -s -H "X-Requested-With:XMLHttpRequest" "$ajax_url" -d "id=$ajax" | sed -e 's/{"data":"//' -e 's/"}/\n/' -e 's/\\//g' | base64 -d | openssl enc -d -aes256 -K "$secret_key" -iv "$iv" | sed -e 's/\].*/\]/' -e 's/\\//g' | tr '{|}' '\n' | sed -nE 's/\"file\":"([^"]*)".*label.*P.*/\1/p')
}
# chooses the link for the set quality
get_video_quality() {
dpage_url="$1"
decrypt_link "$dpage_url"
case $quality in
best)
video_link=$(printf '%s' "$video_links" | head -n 4 | tail -n 1 | cut -d'>' -f2)
;;
worst)
video_link=$(printf '%s' "$video_links" | head -n 1 | cut -d'>' -f2)
;;
*)
video_link=$(printf '%s' "$video_links" | grep -i "${quality}p" | head -n 1 | cut -d'>' -f2)
if [ -z "$video_link" ]; then
err "Current video quality is not available (defaulting to best quality)"
quality=best
video_link=$(printf '%s' "$video_links" | head -n 4 | tail -n 1 | cut -d'>' -f2)
fi
;;
esac
printf '%s' "$video_link"
}
###############
# TEXT OUTPUT #
###############
# display an error message to stderr (in red)
err () {
printf "\033[1;31m%s\033[0m\n" "$*" >&2
}
# display an informational message (first argument in green, second in magenta)
inf () {
printf "\033[1;35m%s \033[1;35m%s\033[0m\n" "$1" "$2"
}
# prompts the user with message in $1-2 ($1 in blue, $2 in magenta) and saves the input to the variables in $REPLY and $REPLY2
prompt () {
printf "\033[1;35m%s\033[1;35m%s\033[1;34m\033[0m" "$1" "$2"
read -r REPLY REPLY2
}
# displays an even (cyan) line of a menu line with $2 as an indicator in () and $1 as the option
menu_line_even () {
printf "\033[1;36m(\033[1;36m%s\033[1;36m) \033[1;36m%s\033[0m\n" "$2" "$1"
}
# displays an odd (yellow) line of a menu line with $2 as an indicator in () and $1 as the option
menu_line_odd() {
printf "\033[1;33m(\033[1;33m%s\033[1;33m) \033[1;33m%s\033[0m\n" "$2" "$1"
}
# display alternating menu lines (even and odd)
menu_line_alternate() {
menu_line_parity=${menu_line_parity:-0}
if [ "$menu_line_parity" -eq 0 ]; then
menu_line_odd "$1" "$2"
menu_line_parity=1
else
menu_line_even "$1" "$2"
menu_line_parity=0
fi
}
# displays a warning (red) line of a menu line with $2 as an indicator in [] and $1 as the option
menu_line_strong() {
printf "\033[1;31m(\033[1;31m%s\033[1;31m) \033[1;31m%s\033[0m\n" "$2" "$1"
}
#################
# INPUT PARSING #
#################
# only lets the user pass in case of a valid search
process_search () {
search_results=$(search_drama "$query")
while [ -z "$search_results" ]; do
err 'No search results found'
prompt 'Search Drama: '
query="$REPLY $REPLY2"
search_results=$(search_drama "$query")
done
drama_selection "$search_results"
episode_selection
}
# drama-selection menu handling function
drama_selection () {
count=1
while read -r drama_id; do
menu_line_alternate "$drama_id" "$count"
: $((count+=1))
done <<-EOF
$search_results
EOF
if [ -n "$select_first" ]; then
tput clear
choice=1
elif [ -z "$ep_choice_to_start" ] || { [ -n "$ep_choice_to_start" ] && [ -z "$select_first" ]; }; then
menu_line_strong "exit" "q"
prompt "> "
choice="$REPLY"
while ! [ "$choice" -eq "$choice" ] 2>/dev/null || [ "$choice" -lt 1 ] || [ "$choice" -ge "$count" ] || [ "$choice" = " " ]; do
[ "$choice" = "q" ] && exit 0
err "Invalid choice entered"
prompt "> "
choice="$REPLY"
done
fi
# Select respective drama_id
selection_id="$(printf "%s" "$search_results" | sed -n "${choice}p")"
temp_drama_id=$(printf "%s" "$selection_id" | sed 's/[0-9]*.$//')
select_ep_result=$(check_episode "$selection_id" "$temp_drama_id")
last_ep_number=$(printf "%s" "$select_ep_result" | head -n 1)
first_ep_number=$(printf "%s" "$select_ep_result" | tail -n 1)
selection_id=$temp_drama_id
}
# gets episode number from user, makes sure it's in range, skips input if only one episode exists
episode_selection () {
if [ "$last_ep_number" -gt "$first_ep_number" ]; then
if [ -z "$ep_choice_to_start" ]; then
# if branches, because order matters this time
while : ; do
inf "To specify a range, use: start_number end_number"
inf "Episodes:" "($first_ep_number-$last_ep_number)"
prompt "> "
ep_choice_start="$REPLY"
ep_choice_end="$REPLY2"
if [ "$REPLY" = q ]; then
exit 0
fi
[ "$ep_choice_end" = "-1" ] && ep_choice_end="$last_ep_number"
if ! [ "$ep_choice_start" -eq "$ep_choice_start" ] 2>/dev/null || { [ -n "$ep_choice_end" ] && ! [ "$ep_choice_end" -eq "$ep_choice_end" ] 2>/dev/null; }; then
err "Invalid number(s)"
continue
fi
if [ "$ep_choice_start" -gt "$last_ep_number" ] 2>/dev/null || [ "$ep_choice_end" -gt "$last_ep_number" ] 2>/dev/null || [ "$ep_choice_start" -lt "$first_ep_number" ] 2>/dev/null; then
err "Episode out of range"
continue
fi
if [ "$ep_choice_end" -le "$ep_choice_start" ]; then
err "Invalid range"
continue
fi
break
done
else
ep_choice_start="$ep_choice_to_start" && unset ep_choice_to_start
fi
else
# In case the drama contains only a single episode
ep_choice_start=1
fi
if [ -z "$ep_choice_end" ]; then
auto_play=0
else
auto_play=1
fi
}
# creates $episodes from $ep_choice_start and $ep_choice_end
generate_ep_list() {
episodes=$ep_choice_start
[ -n "$ep_choice_end" ] && episodes=$(seq "$ep_choice_start" "$ep_choice_end")
}
##################
# VIDEO PLAYBACK #
##################
append_history () { # todo: unite this with the temporary histfile writing
grep -q "${selection_id}" "$logfile" || printf "%s%s\n" "$selection_id" $((episode+1)) >> "$logfile"
}
# opens selected episodes one-by-one
open_selection() {
for ep in $episodes; do
open_episode "$selection_id" "$ep"
done
episode=${ep_choice_end:-$ep_choice_start}
}
open_episode () {
drama_id="$1"
episode="$2"
tput clear
inf "Loading episode $episode..."
# decrypting url
dpage_link=$(get_dpage_link "$drama_id" "$episode")
echo "$dpage_link"
video_url=$(get_video_quality "$dpage_link")
echo "$video_url"
if [ "$is_download" -eq 0 ]; then
# write drama and episode number and save to temporary history
sed -E "
s/^${selection_id}[0-9]*/${selection_id}$((episode+1))/
" "$logfile" > "${logfile}.new"
[ ! "$PID" = "0" ] && kill "$PID" >/dev/null 2>&1
[ -z "$video_url" ] && die "Video URL not found"
play_episode
# overwrite history with temporary history
mv "${logfile}.new" "$logfile"
else
mkdir -p "$download_dir"
inf "Downloading episode $episode ..."
episode=$(printf "%03d" "$episode")
{
if download "$dpage_link" "$video_url" "$drama_id" "$episode" ; then
inf "Downloaded episode: $episode"
else
err "Download failed episode: $episode , please retry or check your internet connection"
fi
}
fi
}
play_episode () {
# Build command
set -- "$player_fn" "$video_url"
case "$player_fn" in
vlc)
[ ! "$auto_play" -eq 0 ] && set -- "$@" "--play-and-exit"
set -- "$@" --http-referrer="$dpage_link"
;;
*)
set -- "$@" --referrer="$dpage_link" --force-media-title="${drama_id}${episode}"
;;
esac
# Run Command
if [ "$auto_play" -eq 0 ]; then
nohup "$@" > /dev/null 2>&1 &
else
inf "Currently playing $display_name episode" "$episode/$last_ep_number, Range: $ep_choice_start-$ep_choice_end"
"$@" > /dev/null 2>&1
sleep 2
fi
PID=$!
}
############
# START UP #
############
# clears the colors and deletes temporary logfile when exited using SIGINT
trap 'printf "\033[0m";[ -f "$logfile".new ] && rm "$logfile".new;exit 1' INT HUP
# default options
player_fn="mpv" #video player needs to be able to play urls
is_download=0
PID=0
quality=best
scrape=query
download_dir="."
choice=
auto_play=0
# history file path
logfile="${XDG_CACHE_HOME:-$HOME/.cache}/dra-hsts"
logdir="${XDG_CACHE_HOME:-$HOME/.cache}"
# create history file and history dir if none found
[ -d "$logdir" ] || mkdir "$logdir"
[ -f "$logfile" ] || : > "$logfile"
while getopts 'vq:dp:chDUVa:' OPT; do
case $OPT in
d)
is_download=1
;;
a)
ep_choice_to_start=$OPTARG
;;
D)
: > "$logfile"
exit 0
;;
p)
is_download=1
download_dir=$OPTARG
;;
q)
quality=$OPTARG
;;
c)
scrape=history
;;
v)
player_fn="vlc"
;;
U)
update_script
exit 0
;;
V)
version_text
exit 0
;;
*)
help_text
exit 1
;;
esac
done
shift $((OPTIND - 1))
dep_ch "curl" "sed" "grep" "openssl"
if [ "$is_download" -eq 0 ]; then
dep_ch "$player_fn"
else
dep_ch "aria2c" "ffmpeg"
fi
# Get the current working URL
base_url=$(curl -Ls -o /dev/null -w %{url_effective} https://asianembed.io)
case $scrape in
query)
if [ -z "$*" ]; then
prompt "Search Drama: "
query="$REPLY $REPLY2"
else
if [ -n "$ep_choice_to_start" ]; then
REPLY=1
select_first=1
fi
query="$*"
fi
process_search
;;
history)
search_history
[ "$REPLY" = "q" ] && exit 0
first_ep_number=$(check_episode "${selection_id}1" "$selection_id" | tail -1)
;;
*)
die "Unexpected scrape type"
esac
generate_ep_list
append_history
open_selection
########
# LOOP #
########
while :; do
if [ -z "$select_first" ]; then
if [ "$auto_play" -eq 0 ]; then
display_name=$(printf '%s' "$selection_id" | sed 's/-episode-//')
inf "Currently playing $display_name episode" "$episode/$last_ep_number"
else
auto_play=0
fi
[ "$episode" -ne "$last_ep_number" ] && menu_line_alternate 'next' 'n'
[ "$episode" -ne "$first_ep_number" ] && menu_line_alternate 'previous' 'p'
menu_line_alternate "replay" "r"
[ "$last_ep_number" -ne "$first_ep_number" ] && menu_line_alternate 'select' 's'
menu_line_strong "exit" "q"
prompt "> "
choice="$REPLY"
case $choice in
n)
ep_choice_start=$((episode + 1))
unset ep_choice_end
;;
p)
ep_choice_start=$((episode - 1))
unset ep_choice_end
;;
r)
ep_choice_start="$episode"
unset ep_choice_end
;;
s)
episode_selection
;;
q)
break
;;
*)
tput clear
err "Invalid choice"
continue
;;
esac
generate_ep_list
append_history
open_selection
else
wait $!
exit
fi
done