aniwrapper/ani-cli
ksyasuda 5d4e0f5a95 clean up code a bit and remove list history
cleaned up comments, unused code, and unnecessary verbose checks

also temporarily removing list history option becuase it is horrible and not
being used in aniwrapper currently
2021-11-24 18:24:17 -08:00

715 lines
18 KiB
Bash
Executable File

#!/usr/bin/env bash
# Set config directory if not already set
if [[ -z "$XDG_CONFIG_HOME" ]]; then
XDG_CONFIG_HOME="$HOME/.config"
fi
VERBOSE=0
BASE_URL="https://gogoanime.cm"
CFG_DIR="$XDG_CONFIG_HOME/aniwrapper"
ROFI_CFG="meh.rasi"
HISTORY_DB="$XDG_CONFIG_HOME/aniwrapper/history.sqlite3"
# dependencies: grep, sed, curl, video_player, rofi, sqlite3
# video_player ( needs to be able to play urls )
player_fn="mpv"
prog="ani-cli"
logfile="${XDG_CACHE_HOME:-$HOME/.cache}/ani-hsts"
c_red="\033[1;31m"
c_green="\033[1;32m"
c_yellow="\033[1;33m"
c_blue="\033[1;34m"
c_magenta="\033[1;35m"
c_cyan="\033[1;36m"
c_reset="\033[0m"
help_text() {
while IFS= read line; do
printf "%s\n" "$line"
done <<-EOF
USAGE: $prog <query>
-h show this help text
-d download episode
-H continue where you left off
EOF
}
die() {
printf "$c_red%s$c_reset\n" "$*" >&2
exit 1
}
err() {
printf "$c_red%s$c_reset\n" "$*" >&2
}
log() {
# prints passed in args to stdout if $VERBOSE is set to 1
[ "$VERBOSE" -eq 1 ] && printf "%s\n" "$*" >&2
}
search_anime() {
# get anime name along with its id
search=$(printf '%s' "$1" | tr ' ' '-')
curl -s "$BASE_URL//search.html" \
-G \
-d "keyword=$search" |
sed -n -E 's_^[[:space:]]*<a href="/category/([^"]*)" title="([^"]*)".*_\1_p'
}
search_eps() {
# get available episodes for anime_id
anime_id=$1
curl -s "$BASE_URL/category/$anime_id" |
sed -n -E '
/^[[:space:]]*<a href="#" class="active" ep_start/{
s/.* '\''([0-9]*)'\'' ep_end = '\''([0-9]*)'\''.*/\2/p
q
}
'
}
get_dpage_link() {
# get the download page url
anime_id=$1
ep_no=$2
curl -s "$BASE_URL/$anime_id-episode-$ep_no" |
sed -n -E '
/^[[:space:]]*<li class="dowloads">/{
s/.*href="([^"]*)".*/\1/p
q
}'
}
get_links() {
dpage_url="$1"
curl -s "$dpage_url" |
sed -n -E '
/href="([^"]*)" download>Download/{
s/href="([^"]*)" download>Download/\1/p
q
}' | tr -d ' '
}
dep_ch() {
for dep; do
if ! command -v "$dep" >/dev/null; then
die "Program \"$dep\" not found. Please install it."
fi
done
}
check_anime_name() {
# Check to make sure passed in name is not empty
log "VAR: $1"
if [[ "$1" == "" ]] || [[ "$1" == " " ]] || [[ "$1" == "\n" ]]; then
log "Passed in name is nothing"
return 1
fi
return 0
}
run_stmt() {
printf "%s\n" "$1" | sqlite3 -noheader "$HISTORY_DB"
}
#####################
## Database Code ##
#####################
check_db() {
# Return number of matches for anime/episode in db
if [[ "$2" == "search" ]]; then
stmt="SELECT DISTINCT COUNT(*) \
FROM search_history \
WHERE anime_name = '$1';"
res=$(run_stmt "$stmt")
return "$res"
else
stmt="SELECT DISTINCT COUNT(*) \
FROM watch_history \
WHERE anime_name = '$1' \
AND episode_number = $2;"
res=$(run_stmt "$stmt")
return "$res"
fi
}
update_date() {
# updates search/watch date for passed in anime
datetime=$(date +'%Y-%m-%d %H:%M:%S')
stmt=""
if [[ "$2" == "search" ]]; then
stmt="UPDATE search_history SET search_date = '$datetime' \
WHERE anime_name = '$1';"
else
stmt="UPDATE watch_history SET watch_date = '$datetime' \
WHERE anime_name = '$1' \
AND episode_number = $2;"
fi
run_stmt "$stmt"
}
insert_history() {
# inserts into search/watch history db
# check the anime_name/id
check_anime_name "$1"
if [[ $? -ne 0 ]]; then
log "ERROR: Anime name is none... exiting"
return 1
fi
datetime=$(date +'%Y-%m-%d %H:%M:%S')
check_db "$@"
if [[ $? -gt 0 ]]; then
if [[ "$2" == "search" ]]; then
log "Already in search db... Updating search_date"
else
log "Already in search db... Updating watch_date"
fi
update_date "$@"
else
if [[ "$2" == "search" ]]; then
stmt="INSERT INTO search_history(anime_name, search_date) \
VALUES('$1', '$datetime');"
run_stmt "$stmt"
else
stmt="INSERT INTO \
watch_history(anime_name, episode_number, watch_date) \
VALUES('$1', '$2', '$datetime');"
run_stmt "$stmt"
fi
fi
}
sync_search_history() {
cnt=0
while read -r line; do
anime_name=$(awk -F '|' '{print $2}' <<<"$line")
res=$(sqlite3 -noheader "$HISTORY_DB" <<<"SELECT anime_name FROM search_history WHERE anime_name = '$anime_name'")
if [[ "${res/ //}" == "" ]]; then
search_date=$(awk -F '|' '{print $3}' <<<"$line")
log "Adding ($anime_name|$search_date) to search history..."
sqlite3 "$HISTORY_DB" <<<"INSERT INTO search_history(anime_name, search_date) VALUES('$anime_name', '$search_date')"
if [[ "$?" -ne 0 ]]; then
err "Error inserting row $line"
fi
((++cnt))
fi
done <<<"$(sqlite3 -noheader "$temp_db" <<<"SELECT DISTINCT * FROM search_history")"
log "Inserted $cnt rows into search_history table"
}
sync_watch_history() {
cnt=0
while read -r line; do
# anime_name=$(awk -F '|' '{print $2}' <<<"$line")
anime_name="${line/ //}"
log "ANIME: $anime_name"
episodes=$(sqlite3 -noheader "$temp_db" <<<"SELECT episode_number, watch_date FROM watch_history WHERE anime_name = '$anime_name'")
# for each episode of $anime_name on the remote machine, check local
while read -r ep; do
episode_num=$(awk -F '|' '{print $1}' <<<"$ep")
# TODO: Fix inserting duplicate rows
run_stmt "SELECT COUNT(*) FROM watch_history WHERE anime_name = '$anime_name' AND episode_number = $episode_num"
num=$?
log "EP: $ep"
if [[ "$num" -eq 0 ]]; then
log "NOT IN DB"
watch_date=$(awk -F '|' '{print $NF}' <<<"$ep")
log "Adding ($anime_name|$episode_num|$watch_date) to watch history..."
sqlite3 "$HISTORY_DB" <<<"INSERT INTO watch_history(anime_name, episode_number, watch_date) VALUES('$anime_name', '$episode_num', '$watch_date')"
if [[ "$?" -ne 0 ]]; then
err "Error inserting row $ep"
fi
((++cnt))
else
log "Episode: $episode_num found in the db... skipping"
fi
done <<<"${episodes[@]}"
done <<<"$(sqlite3 -noheader "$temp_db" <<<"SELECT DISTINCT anime_name FROM watch_history")"
log "Inserted $cnt rows into watch_history table"
}
#####################
## END of db code ##
#####################
# get query
get_search_query() {
# Query the anime to stream/download
# Get search history
# Construct string "<id>. <anime_name>"
stmt="SELECT DISTINCT id || '. ' || anime_name \
FROM search_history \
ORDER BY id DESC;"
hist=$(run_stmt "$stmt")
msg="Choose from list of searched anime below, or enter a unique name of an anime to search for"
span="<span foreground='peachpuff' style='italic' size='small' weight='light'>$msg</span>"
if [ -z "$*" ]; then
query=$(printf "%s\n" "${hist[@]}" |
rofi -dmenu -l 12 -p "Search Anime:" \
-mesg "$span" \
-config "$CFG_DIR/${ROFI_CFG}")
query="${query//[1-9]*\. /}"
log "Query: $query"
else
query=$*
fi
}
# create history file
[ -f "$logfile" ] || : >"$logfile"
#####################
## Anime selection ##
#####################
anime_selection() {
# Select anime from query results
search_results=$*
menu_format_string='[%d] %s\n'
menu_format_string_c1="$c_blue[$c_cyan%d$c_blue] $c_reset%s\n"
menu_format_string_c2="$c_blue[$c_cyan%d$c_blue] $c_yellow%s$c_reset\n"
count=1
menu=()
res=()
while read anime_id; do
# alternating colors for menu
[ $((count % 2)) -eq 0 ] &&
menu_format_string=$menu_format_string_c1 ||
menu_format_string=$menu_format_string_c2
menu+="$count. $anime_id\n"
idx=$((count - 1))
res["$idx"]="$anime_id"
count=$((count + 1))
done <<-EOF
$search_results
EOF
searched=""
cnt=0
# Get the comma separated list of indexes of anime that has been searched before
for anime in "${res[@]}"; do
log "ANIME: $anime"
check_db "$anime" "search"
if [[ $? -gt 0 ]]; then
log "SEARCHED BEFORE"
if [[ "$searched" == "" ]]; then
searched="$((cnt++))"
else
searched="$searched, $((cnt++))"
fi
fi
done
log "SEARCHED: $searched"
# get the anime from indexed list
user_input=$(printf "${menu[@]}" |
rofi -dmenu -config "$CFG_DIR/${ROFI_CFG}" \
-a "$searched" \
-l 12 -i -p "Enter number:")
[ "$?" -ne 0 ] && return 1
choice=$(printf '%s\n' "$user_input" | awk '{print $1}')
# Remove period after number
choice="${choice::-1}"
name=$(printf '%s\n' "$user_input" | awk '{print $NF}')
log "CHOICE: $name"
log "NAME: $name"
# check both choice and name are set
if [[ ! "$choice" ]] || [[ ! "$name" ]]; then
die "Invalid choice... committing seppuku"
fi
# Check if input is a number
[ "$choice" -eq "$choice" ] 2>/dev/null || die "Invalid number entered"
insert_history "$name" "search"
printf "$c_reset"
# Select respective anime_id
count=1
while read anime_id; do
if [ $count -eq $choice ]; then
selection_id=$anime_id
break
fi
count=$((count + 1))
done <<-EOF
$search_results
EOF
[ -z "$selection_id" ] && die "Invalid number entered"
read last_ep_number <<-EOF
$(search_eps "$selection_id")
EOF
}
##################
## Ep selection ##
##################
episode_selection() {
# select episode number for anime
[ "$is_download" -eq 1 ] &&
printf "Range of episodes can be specified: start_number end_number\n"
log "Anime ID: $anime_id"
stmt="SELECT DISTINCT episode_number \
FROM watch_history \
WHERE anime_name = '$anime_id';"
hist=$(run_stmt "$stmt")
log "HISTORY: ${hist[*]}"
# Get Watch History for $anime_id as comma separated list
watch_history=""
for i in $hist; do
if [[ "$watch_history" == "" ]]; then
watch_history="$((--i))"
else
watch_history="$watch_history, $((--i))"
fi
done
log "WATCH HISTORY: %s\n" "$watch_history"
# get user choice and set the start and end
msg='<span foreground="peachpuff" style="italic" size="small" weight="light">Range of episodes can be provided as: START_EPISODE - END_EPISODE</span>'
choice=$(
seq 1 "$last_ep_number" |
rofi -dmenu -l 12 \
-a "$watch_history" \
-p "Select Episode [1, $last_ep_number]:" \
-mesg "$msg" \
-config "$CFG_DIR/${ROFI_CFG}"
)
ep_choice_start=$(printf '%s\n' "${choice}" | awk '{print $1}')
ep_choice_end=$(printf '%s\n' "${choice}" | awk '{print $NF}')
log "START: $ep_choice_start | END: $ep_choice_end"
if [[ -z "$ep_choice_start" ]] && [[ -z "$ep_choice_end" ]]; then
die "No episode range entered"
fi
# if only one episode was entered, set ep_choice_end to empty string so only selected episode plays
# otherwise plays from ep 1 - ep_choice_start
if [[ "$ep_choice_start" -eq "$ep_choice_end" ]]; then
ep_choice_end=""
fi
# read ep_choice_start ep_choice_end
printf "$c_reset"
}
open_episode() {
anime_id=$1
episode=$2
ddir="$3"
if [[ ! "$is_playlist" ]]; then
# clear the screen
printf '\x1B[2J\x1B[1;1H'
if [[ $episode -lt 1 ]] || [[ $episode -gt $last_ep_number ]]; then
err "Episode out of range"
stmt="SELECT DISTINCT episode_number \
FROM watch_history \
WHERE anime_name = '$anime_id' \
ORDER BY episode_number ASC;"
hist=$(run_stmt "$stmt")
log "HISTORY: ${hist[*]}"
episode=$(printf "%s\n" "${hist[@]}" |
rofi -dmenu -l 12 -p "Choose Episode:" \
-config "$CFG_DIR/${ROFI_CFG}")
printf "$c_reset"
fi
fi
log "Getting data for episode %d\n" $episode
insert_history "$anime_id" "$episode"
dpage_url=$(get_dpage_link "$anime_id" "$episode")
video_url=$(get_links "$dpage_url")
case $video_url in
*streamtape*)
# If direct download not available then scrape streamtape.com
BROWSER=${BROWSER:-firefox}
printf "scraping streamtape.com\n"
video_url=$(curl -s "$video_url" | sed -n -E '
/^<script>document/{
s/^[^"]*"([^"]*)" \+ '\''([^'\'']*).*/https:\1\2\&dl=1/p
q
}
')
;;
esac
if [ $is_download -eq 0 ]; then
# setsid -f $player_fn --http-header-fields="Referer: $dpage_url" "$video_url" >/dev/null 2>&1
nohup $player_fn --http-header-fields="Referer: $dpage_url" "$video_url" >/dev/null 2>&1 &
else
log "Downloading episode $episode ..."
log "$video_url"
# add 0 padding to the episode name
episode=$(printf "%03d" $episode)
{
cd "${ddir/ //}" || die "Could not enter directory $ddir"
mkdir -p "$anime_id" || die "Could not create directory"
cd "$anime_id" || die "Could not enter subdirectory $ddir/$anime_id"
ffmpeg -headers "Referer: $dpage_url" -i "$video_url" \
-codec copy "${anime_id}-${episode}.mkv" >/dev/null 2>&1 &&
notify-send "Downloaded episode: $episode" ||
notify-send "Download failed episode: $episode"
}
fi
}
############
# Start Up #
############
# to clear the colors when exited using SIGINT
trap "printf '$c_reset'" INT HUP
dep_ch "$player_fn" "curl" "sed" "grep" "sqlite3" "rofi"
# option parsing
is_download=0
scrape=query
download_dir="."
is_playlist=0
playlist_remove=0
playlist_add=0
while getopts 'hd:Hpa:P:sv' OPT; do
case "$OPT" in
h)
help_text
exit 0
;;
d)
is_download=1
download_dir="$OPTARG"
log "DOWNLOAD DIR: $download_dir"
;;
H)
scrape=history
;;
p)
scrape=playlist
is_playlist=1
;;
a)
is_add=1
scrape=add
playlist_file="${OPTARG/ //}"
;;
P)
is_playlist=1
# remove spaces from $OPTARG
playlist_file="${OPTARG/ //}"
[ -z "$playlist_file" ] && die "Enter in path to playlist"
log "$playlist_file"
$player_fn "$playlist_file"
exit 0
;;
s)
scrape=sync
;;
v)
VERBOSE=1
;;
*)
printf "%s\n" "Invalid option"
exit 1
;;
esac
done
shift $((OPTIND - 1))
########
# main #
########
case $scrape in
query)
get_search_query "$*"
search_results=$(search_anime "$query")
[ -z "$search_results" ] && die "No search results found"
anime_selection "$search_results"
[ $? -ne 0 ] && die "No anime selection found"
episode_selection
;;
history)
stmt="SELECT DISTINCT anime_name FROM watch_history ORDER BY watch_date DESC"
search_results=$(printf "%s\n" "$stmt" | sqlite3 -noheader "$HISTORY_DB")
[ -z "$search_results" ] && die "History is empty"
anime_selection "${search_results[@]}"
[ $? -ne 0 ] && die "No anime selection found"
log "SELECTION: $selection_id"
stmt="SELECT episode_number \
FROM watch_history \
WHERE anime_name = '$selection_id' \
ORDER BY watch_date DESC \
LIMIT 1"
ep_choice_start=$(run_stmt "$stmt")
log "Most recently watched episode: $ep_choice_start"
;;
playlist)
lines=$(cat "$playlist_file" | wc -l)
log "Num lines in playlist: " "$lines"
if [[ "$lines" -eq 0 ]]; then
get_search_query "$*"
search_results=$(search_anime "$query")
[ -z "$search_results" ] && die "No search results found"
anime_selection "$search_results"
[ $? -ne 0 ] && die "No anime selection found"
episode_selection
else
line=($(sed '1q;d' "$playlist_file"))
if [[ "${#line[@]}" -ne 2 ]]; then
die "Something went wrong with the playlist file... exiting"
fi
selection_id="${line[0]}"
episodes=($selection_id)
ep_choice_start="${line[1]}"
ep_choice_end=""
read last_ep_number <<-EOF
$(search_eps "$selection_id")
EOF
[ "$VERBOSE" -eq 1 ] && printf "Anime: %s Episode: %d\n" "$episodes" "$ep_choice_start"
[ "$VERBOSE" -eq 1 ] && printf "Episodes: %s\n" "${episodes[@]}"
fi
;;
add)
get_search_query "$*"
search_results=$(search_anime "$query")
[ -z "$search_results" ] && die "No search results found"
anime_selection "$search_results"
[ $? -ne 0 ] && die "No anime selection found"
episode_selection
;;
sync)
printf "%s" "Enter username for remote user: "
read -r username
printf "%s" "Enter host for remote user: "
read -r host
connection_str="$username@$host"
printf "%s" "Enter port to connect to remote host with or leave blank for default (22): "
read -r port
if [[ "${port/ //}" == "" ]]; then
PORT=22
else
PORT="$port"
fi
printf "%s" "Enter path to private key (leave blank if unsure or not needed): "
read -r key_path
printf "%s\n" "Syncing database with: $connection_str on port $PORT"
temp_db="/tmp/aniwrapper_tmp_history.sqlite3"
if [[ -z "$key_path" ]]; then
scp -P "$PORT" "$connection_str:$XDG_CONFIG_HOME/aniwrapper/history.sqlite3" "$temp_db"
else
scp -P "$PORT" -i "$key_path" "$connection_str:$XDG_CONFIG_HOME/aniwrapper/history.sqlite3" "$temp_db"
fi
if [[ "$?" -ne 0 ]]; then
die "Error getting database file from remote host"
fi
sync_search_history && sync_watch_history
exit 0
;;
esac
{ # checking input
[ "$ep_choice_start" -eq "$ep_choice_start" ] 2>/dev/null || die "Invalid number entered"
episodes=$ep_choice_start
if [ -n "$ep_choice_end" ]; then
[ "$ep_choice_end" -eq "$ep_choice_end" ] 2>/dev/null || die "Invalid number entered"
# create list of episodes to download/watch
episodes=$(seq $ep_choice_start $ep_choice_end)
fi
}
# plays selected episode(s)
for ep in $episodes; do
if [[ "$is_add" -eq 1 ]]; then
log "ID: $selection_id"
log "EPISODES: $episodes"
printf "%s\n" "$selection_id $ep" >>"$playlist_file"
log "Added to playlist file"
else
open_episode "$selection_id" "$ep" "$download_dir"
if [[ "$is_playlist" -eq 1 ]]; then
sed -i '1d' "$playlist_file"
fi
fi
done
if [[ "$is_add" -eq 1 ]]; then
log "Finished adding to playlist file... exiting"
exit 0
fi
episode=${ep_choice_end:-$ep_choice_start}
choice=''
while :; do
printf "\n${c_green}Currently playing %s episode ${c_cyan}%d/%d\n" "$selection_id" $episode $last_ep_number
printf "$c_blue[${c_cyan}%s$c_blue] $c_yellow%s$c_reset\n" "n" "next episode"
printf "$c_blue[${c_cyan}%s$c_blue] $c_magenta%s$c_reset\n" "p" "previous episode"
printf "$c_blue[${c_cyan}%s$c_blue] $c_yellow%s$c_reset\n" "s" "select episode"
printf "$c_blue[${c_cyan}%s$c_blue] $c_magenta%s$c_reset\n" "r" "replay current episode"
printf "$c_blue[${c_cyan}%s$c_blue] $c_red%s$c_reset\n" "q" "exit"
printf "${c_blue}Enter choice:${c_green} "
printf "$c_reset"
read choice
printf "$c_reset"
case $choice in
n)
episode=$((episode + 1))
;;
p)
episode=$((episode - 1))
;;
s)
printf "${c_blue}Choose episode $c_cyan[1-%d]$c_reset:$c_green " $last_ep_number
read episode
printf "$c_reset"
[ "$episode" -eq "$episode" ] 2>/dev/null || die "Invalid number entered"
;;
r) ;;
q)
break
;;
*)
die "invalid choice"
;;
esac
open_episode "$selection_id" "$episode" "$download_dir"
done