Files
dra-cla/dra-cla
2022-03-07 21:57:33 +05:30

624 lines
16 KiB
Bash
Executable File

#!/bin/sh
# dra-cla
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Project repository: https://github.com/CoolnsX/dra-cla
# Version number
VERSION="1.1.0"
#######################
# 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:
Add 'h' on beginning for episodes like '6.5' -> 'h6'
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
}
update_script () {
# get the newest version of this script from github and replace it
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
}
dep_ch () {
# checks if dependencies are present
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
}
#############
# SEARCHING #
#############
search_drama () {
# get drama name along with its id for search term
search=$(printf '%s' "$1" | tr ' ' '+' )
curl -s "$base_url/search?type=movies&keyword=$search" |
sed -n -E 's_^[[:space:]]*<a href="/drama-detail/([^"]*)".*_\1_p'
}
search_for_unwatched () {
# compares history with dramacool, only shows unfinished drama
search_results="$*"
while read -r drama_id; do
temp=$(curl -s "$base_url/drama-detail/$drama_id")
current_ep_number=$(printf "%s" "$temp" | sed -nE "s/.*href.*episode-(.*).html.*class.*img.*/\1/p" | head -1)
drama_id=$(printf "%s" "$temp" | sed -nE "s/.*href=\"\/(.*)-episode-${current_ep_number}.html.*class.*img.*/\1/p" | head -1)
history_ep_number=$(sed -n -E "s/${drama_id}\t//p" "$logfile")
[ "$current_ep_number" -ge "$history_ep_number" ] && printf '%s\n' "$drama_id"
done <<-EOF
$search_results
EOF
}
search_another_drama () {
tput clear
prompt "Search Drama"
query="$REPLY $REPLY2"
process_search
}
search_history () {
tput clear
search_results=$(sed -n -E 's/\t[0-9]*//p' "$logfile")
[ -z "$search_results" ] && die "History is empty"
search_results=$(search_for_unwatched "$search_results")
[ -z "$search_results" ] && die "No unwatched episodes"
drama_selection "$search_results"
ep_choice_start=$(sed -n -E "s/${selection_id}\t//p" "$logfile")
}
##################
# URL PROCESSING #
##################
get_dpage_link() {
# get the download page url
drama_id="$1"
ep_no="$2"
for params in "-episode-$ep_no.html" "-$ep_no.html" "-episode-$ep_no-1.html" "-camrip-episode-$ep_no.html"; do
drama_page=$(curl -s "$base_url/$drama_id$params")
printf '%s' "$drama_page" | grep -q '<h1 class="entry-title">404</h1>' || break
done
printf '%s' "$drama_page" |
sed -n -E 's/.*class="Standard Server selected" rel="1" data-video="([^"]*)".*/\1/p' | sed 's/^/https:/g'
}
decrypt_link() {
secret_key='3933343232313932343333393532343839373532333432393038353835373532'
iv='39323632383539323332343335383235'
ajax_url="https://asianembed.io/encrypt-ajax.php"
crypto_data=$(curl -s "$1" | sed -nE 's/.*data-value="([^"]*)".*/\1/p')
id=$(printf '%s' "$crypto_data" | base64 -d | openssl enc -d -aes256 -K "$secret_key" -iv "$iv" | cut -d '&' -f1)
#encrypt and create the final ajax
ajax=$(printf "%s\010\016\003\010\t\003\004\t" "$id" | openssl enc -aes256 -K "$secret_key" -iv "$iv" -a)
data=$(curl -s -H "X-Requested-With:XMLHttpRequest" "$ajax_url" -d "id=$ajax" | sed -e 's/{"data":"//' -e 's/"}/\n/' -e 's/\\//g')
#decrypt the data to get final links
printf '%s' "$data" | base64 -d | openssl enc -d -aes256 -K "$secret_key" -iv "$iv" | sed -e 's/\].*/\]/' -e 's/\\//g' |
grep -Eo 'https:\/\/[-a-zA-Z0-9@:%._\+~#=][a-zA-Z0-9][-a-zA-Z0-9@:%_\+.~#?&\/\/=]*'
}
get_video_quality() {
# chooses the link for the set quality
dpage_url="$1"
video_links=$(decrypt_link "$dpage_url")
case $quality in
best)
video_link=$(printf '%s' "$video_links" | head -n 4 | tail -n 1)
;;
worst)
video_link=$(printf '%s' "$video_links" | head -n 1)
;;
*)
video_link=$(printf '%s' "$video_links" | grep -i "${quality}p" | head -n 1)
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)
fi
;;
esac
printf '%s' "$video_link"
}
###############
# TEXT OUTPUT #
###############
err () {
# display an error message to stderr (in red)
printf "\033[1;31m%s\033[0m\n" "$*" >&2
}
inf () {
# display an informational message (first argument in green, second in magenta)
printf "\033[1;32m%s \033[1;35m%s\033[0m\n" "$1" "$2"
}
prompt () {
# 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
printf "\033[1;34m%s\033[1;35m%s\033[1;34m: \033[0m" "$1" "$2"
read -r REPLY REPLY2
}
menu_line_even () {
# displays an even (cyan) line of a menu line with $2 as an indicator in [] and $1 as the option
printf "\033[1;34m[\033[1;36m%s\033[1;34m] \033[1;36m%s\033[0m\n" "$2" "$1"
}
menu_line_odd() {
# displays an odd (yellow) line of a menu line with $2 as an indicator in [] and $1 as the option
printf "\033[1;34m[\033[1;33m%s\033[1;34m] \033[1;33m%s\033[0m\n" "$2" "$1"
}
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
}
menu_line_strong() {
# displays a warning (red) line of a menu line with $2 as an indicator in [] and $1 as the option
printf "\033[1;34m[\033[1;31m%s\033[1;34m] \033[1;31m%s\033[0m\n" "$2" "$1"
}
#################
# INPUT PARSING #
#################
is_number () {
[ "$1" -eq "$1" ] 2>/dev/null || die 'Invalid number entered'
}
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 () {
count=1
while read -r drama_id; do
menu_line_alternate "$drama_id" "$count"
: $((count+=1))
done <<-EOF
$search_results
EOF
if [ -n "$ep_choice_to_start" ] && [ -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 "Enter choice"
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 "Enter choice"
choice="$REPLY"
done
fi
# Select respective drama_id
count=1
while read -r drama_id; do
if [ "$count" -eq "$choice" ]; then
selection_id="$drama_id"
break
fi
count=$((count+1))
done <<-EOF
$search_results
EOF
temp=$(curl -s "$base_url/drama-detail/$selection_id")
search_ep_result=$(printf "%s" "$temp" | sed -nE "s/.*href.*episode-(.*).html.*class.*img.*/\1/p" | head -1)
selection_id=$(printf "%s" "$temp" | sed -nE "s/.*href=\"\/(.*)-episode-${search_ep_result}.html.*class.*img.*/\1/p" | head -1)
read -r last_ep_number <<-EOF
$search_ep_result
EOF
}
episode_selection () {
# using get_dpage_link to get confirmation from episode 0 if it exists,else first_ep_number becomes "1"
first_ep_number=0
result=$(get_dpage_link "$drama_id" "$first_ep_number")
[ -z "$result" ] && first_ep_number=1
if [ "$last_ep_number" -gt "$first_ep_number" ]; then
inf "Range of episodes can be specified: start_number end_number"
if [ -z "$ep_choice_to_start" ]; then
prompt "Choose episode" "[$first_ep_number-$last_ep_number]"
ep_choice_start="$REPLY"
ep_choice_end="$REPLY2"
while [ "$ep_choice_start" -lt "$first_ep_number" ] 2> /dev/null || [ "$ep_choice_end" -gt "$last_ep_number" ] 2> /dev/null || [ "$ep_choice_start" -gt "$last_ep_number" ] 2> /dev/null || [ -z "$ep_choice_start" ] 2> /dev/null; do
if [ "$ep_choice_end" != -1 ]; then
err "Invalid number chosen"
prompt "Choose episode" "[$first_ep_number-$last_ep_number]"
ep_choice_start="$REPLY"
ep_choice_end="$REPLY2"
fi
done
else
ep_choice_start="$ep_choice_to_start" && unset ep_choice_to_start
fi
whether_half="$(printf '%s' "$ep_choice_start" | cut -c1-1)"
if [ "$whether_half" = "h" ]; then
half_ep=1
ep_choice_start="$(printf '%s' "$ep_choice_start" | cut -c2-)"
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
[ "$ep_choice_end" = "-1" ] && ep_choice_end="$last_ep_number"
fi
}
check_input() {
# checks if input is number, creates $episodes from $ep_choice_start and $ep_choice_end
is_number "$ep_choice_start"
episodes=$ep_choice_start
if [ -n "$ep_choice_end" ]; then
is_number "$ep_choice_end"
# create list of episodes to download/watch
episodes=$(seq "$ep_choice_start" "$ep_choice_end")
fi
}
##################
# VIDEO PLAYBACK #
##################
append_history () {
grep -q -w "${selection_id}" "$logfile" || printf "%s\t%s\n" "$selection_id" $((episode+1)) >> "$logfile"
}
open_selection() {
# opens selected episodes one-by-one
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"
# cool way of clearing screen
tput clear
# checking if episode is in range
while [ "$episode" -gt "$last_ep_number" ] || [ -z "$episode" ]; do
is_number "$ep_choice_start"
if [ "$last_ep_number" -eq 0 ]; then
die "Episodes not released yet!"
else
err "Episode out of range"
fi
prompt "Choose episode" "[$first_ep_number-$last_ep_number]"
episode="$REPLY $REPLY2"
done
#processing half episodes
if [ "$half_ep" -eq 1 ]; then
temp_ep="$episode"
episode="${episode}-5"
fi
inf "Getting data for episode $episode"
# decrypting url
dpage_link=$(get_dpage_link "$drama_id" "$episode")
printf "%s" "$dpage_link"
video_url=$(get_video_quality "$dpage_link")
printf "%s" "$video_url"
if [ "$half_ep" -eq 1 ]; then
episode="$temp_ep"
half_ep=0
fi
# Download or play episodes
if [ "$is_download" -eq 0 ]; then
# write drama and episode number and save to temporary history
sed -E "
s/^${selection_id}\t[0-9]+/${selection_id}\t$((episode+1))/
" "$logfile" > "${logfile}.new"
[ "$PID" -ne 0 ] && kill "$PID" >/dev/null 2>&1
[ -z "$video_url" ] && die "Video URL not found"
case "$player_fn" in
vlc)
if [ "$auto_play" -eq 0 ]; then
nohup "$player_fn" --http-referrer="$dpage_link" "$video_url" > /dev/null 2>&1 &
else
inf "Currently playing $selection_id episode" "$episode/$last_ep_number, Range: $ep_choice_start-$ep_choice_end"
"$player_fn" --play-and-exit --http-referrer="$dpage_link" "$video_url" > /dev/null 2>&1
sleep 2
fi
;;
*)
if [ "$auto_play" -eq 0 ]; then
nohup "$player_fn" --referrer="$dpage_link" "$video_url" --force-media-title="dra-cla: $drama_id ep $episode" > /dev/null 2>&1 &
else
inf "Currently playing $selection_id episode" "$episode/$last_ep_number, Range: $ep_choice_start-$ep_choice_end"
"$player_fn" --referrer="$dpage_link" "$video_url" --force-media-title="dra-cla: $drama_id ep $episode" > /dev/null 2>&1
sleep 2
fi
;;
esac
PID=$!
mv "${logfile}.new" "$logfile"
else
mkdir -p "$download_dir"
inf "Downloading episode $episode ..."
# add 0 padding to the episode name
episode=$(printf "%03d" "$episode")
{
if aria2c -x 16 -s 16 --referer="$dpage_link" "$video_url" --dir="$download_dir" -o "${drama_id}-${episode}.mp4" --download-result=hide ; then
inf "Downloaded episode: $episode"
else
err "Download failed episode: $episode , please retry or check your internet connection"
fi
}
fi
}
############
# START UP #
############
# to clear the colors 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
half_ep=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
h)
help_text
exit 0
;;
d)
is_download=1
select_first=1
;;
a)
ep_choice_to_start=$OPTARG
;;
D)
: > "$logfile"
exit 0
;;
p)
is_download=1
download_dir=$OPTARG
select_first=1
;;
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))
# check for main dependencies
dep_ch "curl" "sed" "grep" "git" "openssl"
# check for optional dependencies
if [ "$is_download" -eq 0 ]; then
dep_ch "$player_fn"
else
dep_ch "aria2c"
fi
# dramacool likes to change domains but keep the olds as redirects
base_url="https://dramacool.fo"
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=0
result=$(get_dpage_link "$drama_id" "$first_ep_number")
[ -z "$result" ] && first_ep_number=1
;;
*)
die "Unexpected scrape type"
esac
check_input
append_history
open_selection
########
# LOOP #
########
while :; do
if [ -z "$select_first" ]; then
if [ "$auto_play" -eq 0 ]; then
inf "Currently playing $selection_id episode" "$episode/$last_ep_number"
else
auto_play=0
fi
[ "$episode" -ne "$last_ep_number" ] && menu_line_alternate 'next episode' 'n'
[ "$episode" -ne "$first_ep_number" ] && menu_line_alternate 'previous episode' 'p'
[ "$last_ep_number" -ne "$first_ep_number" ] && menu_line_alternate 'select episode' 's'
menu_line_alternate "replay current episode" "r"
menu_line_alternate "search for another drama" "a"
menu_line_alternate "search history" "h"
menu_line_alternate "select quality (current: $quality)" "b"
menu_line_strong "exit" "q"
prompt "Enter choice"
# process user choice
choice="$REPLY"
case $choice in
n)
ep_choice_start=$((episode + 1))
ep_choice_end=
;;
b)
prompt "Select quality. Options (best|worst|360|480|720|1080)"
quality="$REPLY"
;;
p)
ep_choice_start=$((episode - 1))
ep_choice_end=
;;
s)
episode_selection
;;
r)
ep_choice_start=$((episode))
ep_choice_end=
;;
a)
search_another_drama
;;
h)
search_history
;;
q)
break
;;
*)
tput clear
err "Invalid choice"
continue
;;
esac
check_input
append_history
open_selection
else
wait $!
exit
fi
done