Skip to content

Instantly share code, notes, and snippets.

@Fraasi
Created March 31, 2024 14:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Fraasi/308b47337764194866c5e827ccb5f225 to your computer and use it in GitHub Desktop.
Save Fraasi/308b47337764194866c5e827ccb5f225 to your computer and use it in GitHub Desktop.
Failed pr to KevCui/soaper-dl, which I like to keep around
#!/usr/bin/env bash
#
# Download TV series and Movies from Soaper using CLI
#
#/ Usage:
#/ ./soaper-dl.sh [-n <name>] [-p <path>] [-e <num1,num2,num3-num4...>] [-l] [-s] [-d]
#/
#/ Options:
#/ -n <name> TV series or Movie name
#/ -p <path> media path, e.g: /tv_XXXXXXXX.html
#/ ingored when "-n" is enabled
#/ -e <num1,num3-num4...> optional, episode number to download
#/ e.g: episode number "3.2" means Season 3 Episode 2
#/ multiple episode numbers seperated by ","
#/ episode range using "-"
#/ -l optional, list video or subtitle link without downloading
#/ -s optional, download subtitle only
#/ -d enable debug mode
#/ -h | --help display this help message
set -e
set -u
usage() {
printf "%b\n" "$(grep '^#/' "$0" | cut -c4-)" && exit 1
}
set_var() {
_CURL="$(command -v curl)" || command_not_found "curl"
_JQ="$(command -v jq)" || command_not_found "jq"
_PUP="$(command -v pup)" || command_not_found "pup"
_FZF="$(command -v fzf)" || command_not_found "fzf"
_FFMPEG="$(command -v ffmpeg)" || command_not_found "ffmpeg"
_HOST="https://soaper.tv"
_SEARCH_URL="$_HOST/search/keyword/"
_SCRIPT_PATH=$(dirname "$(realpath "$0")")
_DOWNLOAD_PATH="${SOAPER_DOWNLOAD_PATH:-$_SCRIPT_PATH}"
_SEARCH_LIST_FILE="$(mktemp -t soaper-dl-search-list.XXXXXX)"
_SOURCE_FILE=".source.html"
_EPISODE_LINK_LIST=".episode.link"
_EPISODE_TITLE_LIST=".episode.title"
_MEDIA_HTML=".media.html"
_SUBTITLE_LANG="${SOAPER_SUBTITLE_LANG:-en}"
}
set_args() {
expr "$*" : ".*--help" > /dev/null && usage
while getopts ":hlsdn:x:p:e:" opt; do
case $opt in
n)
_INPUT_NAME="${OPTARG// /%20}"
;;
p)
_MEDIA_PATH="$OPTARG"
;;
e)
_MEDIA_EPISODE="$OPTARG"
;;
l)
_LIST_LINK_ONLY=true
;;
s)
_DOWNLOAD_SUBTITLE_ONLY=true
;;
d)
_DEBUG_MODE=true
set -x
;;
h)
usage
;;
\?)
print_error "Invalid option: -$OPTARG"
;;
esac
done
}
print_info() {
# $1: info message
printf "%b\n" "\033[32m[INFO]\033[0m $1" >&2
}
print_warn() {
# $1: warning message
printf "%b\n" "\033[33m[WARNING]\033[0m $1" >&2
}
print_error() {
# $1: error message
printf "%b\n" "\033[31m[ERROR]\033[0m $1" >&2
exit 1
}
command_not_found() {
# $1: command name
print_error "$1 command not found!"
}
sed_remove_space() {
sed -E '/^[[:space:]]*$/d;s/^[[:space:]]+//;s/[[:space:]]+$//'
}
download_media_html() {
# $1: media link
"$_CURL" -sS "${_HOST}${1}" > "$_DOWNLOAD_PATH/$_MEDIA_NAME/$_MEDIA_HTML"
}
get_media_name() {
# $1: media link
"$_CURL" -sS "${_HOST}${1}" \
| $_PUP ".panel-body h4 text{}" \
| head -1 \
| sed_remove_space
}
search_media_by_name() {
# $1: media name
local d t len l n lb
d="$("$_CURL" -sS "${_SEARCH_URL}$1")"
t="$($_PUP ".thumbnail" <<< "$d")"
len="$(grep -c "class=\"thumbnail" <<< "$t")"
[[ -z "$len" || "$len" == "0" ]] && print_error "Media not found!"
true > "$_SEARCH_LIST_FILE"
for i in $(seq 1 "$len"); do
n="$($_PUP ".thumbnail:nth-child($i) h5 a:nth-child(1) text{}" <<< "$t" | sed_remove_space)"
l="$($_PUP ".thumbnail:nth-child($i) h5 a:nth-child(1) attr{href}" <<< "$t" | sed_remove_space)"
lb="$($_PUP --charset UTF-8 ".thumbnail:nth-child($i) .label-info text{}" <<< "$t" | sed_remove_space)"
echo "[$l][$lb] $n" | tee -a "$_SEARCH_LIST_FILE"
done
}
is_movie() {
# $1: media path
[[ "$1" =~ ^/movie_.* ]] && return 0 || return 1
}
download_source() {
local d a
mkdir -p "$_DOWNLOAD_PATH/$_MEDIA_NAME"
d="$("$_CURL" -sS "${_HOST}${_MEDIA_PATH}")"
a="$($_PUP ".alert-info-ex" <<< "$d")"
if is_movie "$_MEDIA_PATH"; then
download_media "$_MEDIA_PATH" "$_MEDIA_NAME"
else
echo "$a" > "$_DOWNLOAD_PATH/$_MEDIA_NAME/$_SOURCE_FILE"
fi
}
download_episodes() {
# $1: episode number string
local origel el uniqel se
origel=()
if [[ "$1" == *","* ]]; then
IFS="," read -ra ADDR <<< "$1"
for n in "${ADDR[@]}"; do
origel+=("$n")
done
else
origel+=("$1")
fi
el=()
for i in "${origel[@]}"; do
if [[ "$i" == *"-"* ]]; then
se=$(awk -F '-' '{print $1}' <<< "$i" | awk -F '.' '{print $1}')
s=$(awk -F '-' '{print $1}' <<< "$i" | awk -F '.' '{print $2}')
e=$(awk -F '-' '{print $2}' <<< "$i" | awk -F '.' '{print $2}')
for n in $(seq "$s" "$e"); do
el+=("${se}.${n}")
done
else
el+=("$i")
fi
done
IFS=" " read -ra uniqel <<< "$(printf '%s\n' "${el[@]}" | sort -u -V | tr '\n' ' ')"
[[ ${#uniqel[@]} == 0 ]] && print_error "Wrong episode number!"
for e in "${uniqel[@]}"; do
download_episode "$e"
done
}
download_episode() {
# $1: episode number
local l
l=$(grep "\[$1\] " "$_DOWNLOAD_PATH/$_MEDIA_NAME/$_EPISODE_LINK_LIST" \
| awk -F '] ' '{print $2}')
[[ "$l" != *"/"* ]] && print_error "Wrong download link or episode not found!"
download_media "$l" "$1"
}
download_media() {
# $1: media link
# $2: media name
local u d el sl p
download_media_html "$1"
is_movie "$_MEDIA_PATH" && u="GetMInfoAjax" || u="GetEInfoAjax"
p="$(sed 's/.*e_//;s/.html//' <<< "$1")"
d="$("$_CURL" -sS "${_HOST}/home/index/${u}" \
-H "referer: https://${_HOST}${1}" \
--data-raw "pass=${p}")"
el="${_HOST}$($_JQ -r '.val' <<< "$d")"
[[ "$el" != *".m3u8" ]] && el="$($_JQ -r '.val_bak' <<< "$d")"
if [[ "$($_JQ '.subs | length' <<< "$d")" -gt "0" ]]; then
sl="$($_JQ -r '.subs[]| select(.name | ascii_downcase | contains ("'"$_SUBTITLE_LANG"'")) | .path' <<< "$d" | head -1)"
sl="${sl// /%20}"
sl="${sl//[/\\\[}"
sl="${sl//]/\\\]}"
sl="${_HOST}$sl"
fi
if [[ -z ${_LIST_LINK_ONLY:-} ]]; then
print_info "Downloading to $_DOWNLOAD_PATH/${_MEDIA_NAME}/..."
if [[ -n "${sl:-}" && "$sl" != "$_HOST" ]]; then
print_info "Downloading subtitle $2..."
"$_CURL" "${sl}" > "$_DOWNLOAD_PATH/${_MEDIA_NAME}/${2}_${_SUBTITLE_LANG}.srt"
fi
if [[ -z ${_DOWNLOAD_SUBTITLE_ONLY:-} ]]; then
print_info "Downloading video $2..."
"$_FFMPEG" -i "$el" -c copy -v error -y "$_DOWNLOAD_PATH/${_MEDIA_NAME}/${2}.mp4"
fi
else
if [[ -z ${_DOWNLOAD_SUBTITLE_ONLY:-} ]]; then
echo "$el"
else
if [[ -n "${sl:-}" ]]; then
echo "${sl}"
fi
fi
fi
}
create_episode_list() {
local slen sf t l sn et el
sf="$_DOWNLOAD_PATH/$_MEDIA_NAME/$_SOURCE_FILE"
el="$_DOWNLOAD_PATH/$_MEDIA_NAME/$_EPISODE_LINK_LIST"
et="$_DOWNLOAD_PATH/$_MEDIA_NAME/$_EPISODE_TITLE_LIST"
slen="$(grep 'alert alert-info-ex' -c "$sf")"
true > "$et"
true > "$el"
for i in $(seq "$slen" -1 1); do
sn=$((slen - i + 1))
t="$($_PUP ".alert-info-ex:nth-child($i) div text{}" < "$sf" \
| sed_remove_space \
| tac \
| awk '{print "[" num "." NR "] " $0}' num="${sn}")"
l="$($_PUP ".alert-info-ex:nth-child($i) div a attr{href}" < "$sf" \
| sed_remove_space \
| tac \
| awk '{print "[" num "." NR "] " $0}' num="${sn}")"
echo "$t" >> "$et"
echo "$l" >> "$el"
done
}
select_episodes_to_download() {
cat "$_DOWNLOAD_PATH/$_MEDIA_NAME/$_EPISODE_TITLE_LIST" >&2
echo -n "Which episode(s) to download: " >&2
read -r s
echo "$s"
}
main() {
set_args "$@"
set_var
local mlist=""
if [[ -n "${_INPUT_NAME:-}" ]]; then
mlist="$(search_media_by_name "$_INPUT_NAME")"
_MEDIA_PATH=$($_FZF -1 <<< "$(sort -u <<< "$mlist")" | awk -F']' '{print $1}' | sed -E 's/^\[//')
fi
[[ -z "${_MEDIA_PATH:-}" ]] && print_error "Media not found! Missing option -n <name> or -p <path>?"
[[ ! -s "$_SEARCH_LIST_FILE" ]] && print_error "$_SEARCH_LIST_FILE not found. Please run \`-n <name>\` to generate it."
_MEDIA_NAME=$(sort -u "$_SEARCH_LIST_FILE" \
| grep "$_MEDIA_PATH" \
| awk -F '] ' '{print $2}' \
| sed -E 's/\//_/g')
[[ "$_MEDIA_NAME" == "" ]] && _MEDIA_NAME="$(get_media_name "$_MEDIA_PATH")"
download_source
is_movie "$_MEDIA_PATH" && exit 0
create_episode_list
[[ -z "${_MEDIA_EPISODE:-}" ]] && _MEDIA_EPISODE=$(select_episodes_to_download)
download_episodes "$_MEDIA_EPISODE"
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment