diff --git a/.assets/ani-cli-spec.png b/.assets/ani-cli-spec.png deleted file mode 100644 index 2856c4dfe..000000000 Binary files a/.assets/ani-cli-spec.png and /dev/null differ diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 113d69db7..51eff285e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,5 @@ * @port19x -/.assets/ @iamchokerman -README.md @iamchokerman +/.assets/ @justchokingaround +README.md @justchokingaround hist_transition.sh @Derisis13 +ani-cli.1 @Derisis13 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 23a0361b3..ca63c00e3 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -14,19 +14,25 @@ - [ ] any anime playing - [ ] bumped version +--- - [ ] next, prev and replay work -- [ ] quality works -- [ ] downloads work -- [ ] quality works with downloads -- [ ] select episode -a and rapid resume work -- [ ] syncplay -s works -- [ ] autoplay, aka range selection, works +- [ ] `-c` history and continue work +- [ ] `-d` downloads work +- [ ] `-s` syncplay works +- [ ] `-q` quality works +- [ ] `-v` vlc works +- [ ] `-e` select episode works +- [ ] `-r` range selection works +- [ ] `--dub` both work +- [ ] all providers return links (not necessarily on a single anime, use debug mode to confirm) +--- +- [ ] `-h` help info is up to date +- [ ] Readme is up to date +- [ ] Man page is up to date ## Additional Testcases - The safe bet: One Piece - Episode 0: Saenai Heroine no Sodatekata ♭ - Unicode: Saenai Heroine no Sodatekata ♭ -- Not uploaded: one piece dub episode 590 -- Unreleased: soredemo ayumu wa yosetekuru -- Short id (for decryption): Log Horizon episode 1-2 +- Non-whole episodes: Tensei shitara slime datta ken (ep. 24.5, ep. 24.9) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 25b8e2642..a606fb4b2 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -13,4 +13,4 @@ jobs: - name: Run ShellCheck uses: ludeeus/action-shellcheck@master env: - SHELLCHECK_OPTS: -s sh -o all -e 2250 -e 2016 -x + SHELLCHECK_OPTS: -s sh -o all -e 2250 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5470cfbf3..7eeb31147 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,7 +20,3 @@ - Take part in troubleshooting and testing - Star the repo - Follow the maintainers - -## Architecture - -![spec](https://github.com/pystardust/ani-cli/blob/spec/.assets/ani-cli-spec.png) diff --git a/README.md b/README.md index 388bf4c78..b5c94748e 100644 --- a/README.md +++ b/README.md @@ -9,17 +9,17 @@

-
+

-A cli to browse and watch anime (alone AND with friends). This tool scrapes the site animixplay. +A cli to browse and watch anime (alone AND with friends). This tool scrapes the site allanime.

@@ -31,7 +31,6 @@ https://user-images.githubusercontent.com/44473782/160729779-41fe207c-b5aa-4fed- ## Table of Contents - [Fixing errors](#Fixing-errors) -- [New in v3](#New-in-v3) - [Install](#Install) - [Linux](#Linux) - [Debian](#Debian) @@ -42,6 +41,7 @@ https://user-images.githubusercontent.com/44473782/160729779-41fe207c-b5aa-4fed- - [MacOS](#MacOS) - [Windows](#Windows) - [Android](#Android) + - [Steam Deck](#Steam-deck) - [Uninstall](#Uninstall) - [Dependencies](#Dependencies) - [Homies](#Homies) @@ -54,6 +54,8 @@ If you encounter "Video url not found" or any breaking issue, then make sure you `sudo ani-cli -U` to update on Linux, Mac and Android. On Windows, run gitbash as administrator then there type `ani-cli -U`. If after this the issue persists then open an issue. +History has been reworked and relocated. We're working on a transition script, please be patient. Old history can be viewed with `less ${XDG_CACHE_HOME:-$HOME/.cache}/ani-hsts` + ## Install #### Users of V3.2 or the v3.2.x series should uninstall before upgrading @@ -61,7 +63,7 @@ Otherwise you're likely to see an error like the following: ` "/usr/bin/ani-cli: ### Native packages -[![Packaging status](https://repology.org/badge/vertical-allrepos/ani-cli.svg?minversion=3.0)](https://repology.org/project/ani-cli/versions) +[![Packaging status](https://repology.org/badge/vertical-allrepos/ani-cli.svg?minversion=4.0)](https://repology.org/project/ani-cli/versions) *Native packages have a more robust update cycle, but sometimes they are slow to upgrade. If the one for your platform is up-to-date we suggest going with it.* @@ -69,7 +71,7 @@ Otherwise you're likely to see an error like the following: ` "/usr/bin/ani-cli: #### Debian -``` +```sh wget -qO- https://Wiener234.github.io/ani-cli-ppa/KEY.gpg | sudo tee /etc/apt/trusted.gpg.d/ani-cli.asc wget -qO- https://Wiener234.github.io/ani-cli-ppa/ani-cli-debian.list | sudo tee /etc/apt/sources.list.d/ani-cli-debian.list sudo apt update @@ -94,7 +96,7 @@ Build and install from the AUR: ```sh yay -S ani-cli ``` -Also consider ani-cli-git +Also consider `ani-cli-git` #### OpenSuse Tumbleweed and Leap @@ -109,7 +111,7 @@ zypper install ani-cli ``` You'll get a warning about `Signature verification failed [4-Signatures public key is not available]` but this can be ignored from the prompt. -*Note: package is noarch, so any architecture should work, even though the repo is labled x86-64* +*Note: package is noarch, so any architecture should work, even though the repo is labelled x86-64* #### Installing from source @@ -117,12 +119,10 @@ Install dependencies [(See below)](#Dependencies) ```sh sudo rm -rf "/usr/local/share/ani-cli" "/usr/local/bin/ani-cli" "/usr/local/bin/UI" /usr/local/bin/player_* #If some of these aren't found, it's not a problem -git clone "https://github.com/pystardust/ani-cli.git" && cd ./ani-cli -sudo cp ./ani-cli /usr/local/bin -cd .. && rm -rf "./ani-cli" +git clone "https://github.com/pystardust/ani-cli.git" +sudo cp ani-cli/ani-cli /usr/local/bin +rm -rf ani-cli ``` -*Also note that mpv installed through flatpak is not compatible* - ### MacOS @@ -140,21 +140,20 @@ cd .. && rm -rf ./ani-cli *To install (with Homebrew) the dependencies required on Mac OS, you can run:* ```sh -brew install curl grep axel openssl@1.1 ffmpeg git && \ +brew install wget grep aria2 ffmpeg git fzf && \ brew install --cask iina ``` *Why iina and not mpv? Drop-in replacement for mpv for MacOS. Integrates well with OSX UI. Excellent support for M1. Open Source.* ### Windows -*Make sure git bash is installed [(Install)](https://git-scm.com/download/win)* +*ani-cli needs a posix shell and the current way is git bash. Unfortunately fzf can't run in git bash's default terminal. The solution is to use git bash in windows terminal* -*Note that the installation instruction below must be done inside **Git Bash**, not in Command Prompt or Powershell* +First, you'll need windows terminal preview. [(Install)](https://apps.microsoft.com/store/detail/windows-terminal-preview/9N8G5RFZ9XK3?hl=de-at&gl=at&rtc=1) -mpv is not added to $PATH automatically when installed and thus the script is unable to use it. You either have to do this manually, or install it via scoop (recommended): -```sh -scoop install mpv -``` +Then make sure git bash is installed. [(Install)](https://git-scm.com/download/win) It needs to be added to windows terminal [(Instructions)](https://stackoverflow.com/questions/56839307/adding-git-bash-to-the-new-windows-terminal) + +The following steps and ani-cli need to be run from git bash in windows terminal. #### Scoop bucket @@ -166,12 +165,14 @@ scoop install ani-cli #### From source ```sh rm -rf "/usr/local/share/ani-cli" "/usr/local/bin/ani-cli" "/usr/local/bin/UI" /usr/local/bin/player_* #If some of these aren't found, it's not a problem -git clone "https://github.com/pystardust/ani-cli.git" && cd ./ani-cli -cp ./ani-cli /usr/bin -cd .. && rm -rf ./ani-cli +git clone "https://github.com/pystardust/ani-cli.git" +cp ani-cli/ani-cli /usr/bin +rm -rf /ani-cli ``` -*Run ani-cli in Git Bash (Running it in cmd or powershell may or may not work)* +#### Dependencies + +All dependencies can be installed with scoop (from the extras bucket), however some users experienced that installed programs aren't always added to the path. If this happens installing from winget instead usually works. ### Android @@ -189,29 +190,106 @@ pkg install ani-cli ```sh pkg up -y rm -rf "$PREFIX/share/ani-cli" "$PREFIX/bin/ani-cli" "$PREFIX/bin/UI" "$PREFIX"/local/bin/player_* #If some of these aren't found, it's not a problem -git clone "https://github.com/pystardust/ani-cli.git" && cd ./ani-cli -cp ./ani-cli "$PREFIX"/bin -cd .. && rm -rf ./ani-cli +git clone "https://github.com/pystardust/ani-cli.git" +cp ani-cli/ani-cli "$PREFIX"/bin +rm -rf /ani-cli +``` + +For players you can use the apk (playstore/fdroid) versions of mpv and vlc. Note that these cannot be checked from termux so a warning is generated when checking dependencies. + +### Steam Deck + +#### Copypaste script: + +* Switch to Desktop mode (`STEAM` Button > Power > Switch to Desktop) +* Open `Konsole` (Steam Deck Icon in bottom left corner > System > Konsole) +* Copy the script, paste it in the CLI and press Enter("A" button on Steam Deck) + +```sh +[ ! -d ~/.local/bin ] && mkdir ~/.local/bin && echo "export $PATH=$HOME/.local/bin:$PATH" >> ".$(echo $SHELL | sed -nE "s|.*/(.*)\$|\1|p")rc" + +git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf +~/.fzf/install + +mkdir ~/.aria2c +wget -O ~/.aria2c/aria2-1.36.0.tar.bz2 https://github.com/q3aql/aria2-static-builds/releases/download/v1.36.0/aria2-1.36.0-linux-gnu-64bit-build1.tar.bz2 +tar xvf ~/.aria2c/aria2-1.36.0.tar.bz2 -C ~/.aria2c/ +cp ~/.aria2c/aria2-1.36.0-linux-gnu-64bit-build1/aria2c ~/.local/bin/ +chmod +x ~/.local/bin/aria2c + +git clone https://github.com/pystardust/ani-cli.git ~/.ani-cli +cp ~/.ani-cli/ani-cli ~/.local/bin/ + +flatpak install io.mpv.Mpv +``` +press enter("A" button on Steam Deck) on questions + +#### Installation in steps: + +##### Install mpv (Flatpak version): + +```sh +flatpak install io.mpv.Mpv +``` +press enter("A" button on Steam Deck) on questions + +##### Install [fzf](https://github.com/junegunn/fzf): + +```sh +git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf +~/.fzf/install +``` +press enter("A" button on Steam Deck) on questions + +##### Make a ~/.local/bin folder if doesn't exist and add it to $PATH + +```sh +[ ! -d ~/.local/bin ] && mkdir ~/.local/bin && echo "export $PATH=$HOME/.local/bin:$PATH" >> ".$(echo $SHELL | sed -nE "s|.*/(.*)\$|\1|p")rc" ``` -Note : Vlc Android now works too ;) +##### Install [aria2](https://github.com/aria2/aria2) (needed for download feature only): + +```sh +mkdir ~/.aria2c +wget -O ~/.aria2c/aria2-1.36.0.tar.bz2 https://github.com/q3aql/aria2-static-builds/releases/download/v1.36.0/aria2-1.36.0-linux-gnu-64bit-build1.tar.bz2 +tar xvf ~/.aria2c/aria2-1.36.0.tar.bz2 -C ~/.aria2c/ +cp ~/.aria2c/aria2-1.36.0-linux-gnu-64bit-build1/aria2c ~/.local/bin/ +chmod +x ~/.local/bin/aria2c +``` + +##### Install ani-cli: + +```sh +git clone https://github.com/pystardust/ani-cli.git ~/.ani-cli +cp ~/.ani-cli/ani-cli ~/.local/bin/ +``` -You need to add any referrer in mpv by opening mpv [(playstore version)](https://play.google.com/store/apps/details?id=is.xyz.mpv), going into Settings -> Advanced -> Edit mpv.conf and adding (for example): +##### Optional: add desktop entry: ``` -referrer="https://animixplay.to/" +echo '[Desktop Entry] +Encoding=UTF-8 +Version=4.0 +Type=Application +Exec=konsole -e ani-cli +Name=ani-cli' > ~/.local/share/applications/ani-cli.desktop ``` +The .desktop entry will allow to start ani-cli in Konsole directly from "Gaming Mode" +In Steam Desktop app: +`Add game` > `Add a non-steam game` > tick a box for `ani-cli` > `Add selected programs` +*Note: Konsole window size bugs out if launched from "Gaming Mode".* +*Note: this is not working the way it should yet.* ## Uninstall * apt: -``` +```sh sudo apt remove ani-cli -# to remove the repository from apt: +# to remove the repository from apt sudo rm -f /etc/apt/trusted.gpg.d/ani-cli.asc /etc/apt/sources.list.d/ani-cli-debian.list ``` * dnf: -``` +```sh sudo dnf remove ani-cli # for ani-cli # disable the repo in dnf dnf copr disable derisis13/ani-cli @@ -224,7 +302,7 @@ zypper removerepo ani-cli ``` You might want to remove `packman-essentials` if you don't need it otherwise * AUR: -``` +```sh yay -R ani-cli ``` * Scoop: @@ -245,30 +323,41 @@ In **Git Bash** run (as administrator): rm "/usr/bin/ani-cli" ``` * Termux package -``` +```sh pkg remove ani-cli ``` * Android: ```sh rm "$PREFIX/bin/ani-cli" ``` +* Steam Deck +```sh +rm "~/.local/bin/ani-cli" +rm -rf ~/.ani-cli +``` +optionally: remove dependencies: +```sh +rm ~/.local/bin/aria2c +rm -rf "~/.aria2" +rm -rf "~/.fzf" +flatpak uninstall io.mpv.Mpv +``` ## Dependencies - grep - sed -- awk -- curl -- openssl +- wget - mpv - Video Player - iina - mpv replacement for MacOS -- axel - Download manager +- aria2c - Download manager - ffmpeg - m3u8 Downloader -- fzf (optional) +- fzf - User interface -## Homies +## Homies -* [animdl](https://github.com/justfoolingaround/animdl): Ridiculously efficient, fast and light-weight (supports most sources: animixplay, 9anime...) (Python) +* [animdl](https://github.com/justfoolingaround/animdl): Ridiculously efficient, fast and light-weight (supports most sources: allanime, zoro ... (Python) +* [jerry](https://github.com/justchokingaround/jerry): stream anime with anilist tracking and syncing, with discord presence (Shell) * [anime-helper-shell](https://github.com/Atreyagaurav/anime-helper-shell): A python shell for searching, watching, and downloading anime (Python) * [anipy-cli](https://github.com/sdaqo/anipy-cli): ani-cli rewritten in python (Python) * [dra-cla](https://github.com/CoolnsX/dra-cla): ani-cli equivalent for korean dramas (Shell) diff --git a/ani-cli b/ani-cli old mode 100644 new mode 100755 index 1716e89b4..f8e073ca6 --- a/ani-cli +++ b/ani-cli @@ -1,131 +1,87 @@ #!/bin/sh -#--- -#dependencies: -# curl.se: ^7.8 -# openssl.org: ^1.1 -# ffmpeg.org: ^5.1 -#--- - -# License preamble at the end of the file -# Version number -VERSION="3.4.7" - - -####################### -# AUXILIARY FUNCTIONS # -####################### - -help_text () { - while read -r line; do - printf "%s\n" "$line" - done <<-EOF - - Usage: - ${0##*/} [-f] [-s] [-v] [-x] [-q ] [-a ] [-d | -p ] [] [-r ] - ${0##*/} [-f] [-s] [-v] [-x] [-q ] -c - ${0##*/} -h | -D | -U | -V - - Options: - -v use VLC as the media player - -q set video quality (best|worst|360|480|720|1080) - -s watch anime together with friends, using Syncplay (works with mpv only) - -f use fzf for anime selection - -a specify episode to watch - -d download episode - -p download episode to specified directory - -c continue watching anime from history - -h show helptext - -D delete history - -U fetch update from github - -V print version number and exit - -r select provider to scrape first [1-3] - -x print all video links from all providers to stdout (for debugging purpose) - - 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 anime is passed -EOF -} - -retry () { - err "$*" - prompt -} +version_number="4.0.0" -# display an error message to stderr (in red) -err () { - printf "\33[2K\r\033[1;31m%s\033[0m\n" "$*" >&2 -} +# UI -#display error message and exit -die () { - err "$*" - exit 1 +external_menu() { + rofi "$1" -sort -dmenu -i -width 1500 -p "$2" } -# display an informational message (first argument in green, second in magenta) -inf () { - printf "\33[2K\r\033[1;35m%s \033[1;35m%s\033[0m\n" "$1" "$2" +launcher() { + [ "$use_external_menu" = "0" ] && [ -z "$1" ] && set -- "+m" "$2" + [ "$use_external_menu" = "0" ] && fzf "$1" --reverse --prompt "$2" + [ "$use_external_menu" = "1" ] && external_menu "$1" "$2" } -progress() { - printf "\33[2K\r\033[1;34m%s\033[0m" "$1" +nth() { + stdin=$(cat -) + [ -z "$stdin" ] && return 1 + line_count="$(printf "%s\n" "$stdin" | wc -l)" + [ "$line_count" -eq 1 ] && printf "%s" "$stdin" | cut -f2,3 && return 0 + prompt="$1" + multi_flag="" + [ $# -ne 1 ] && shift && multi_flag="$1" + line=$(printf "%s" "$stdin" | cut -f1,3 --output-delimiter " " | launcher "$multi_flag" "$prompt" | cut -d " " -f 2-) + [ -n "$line" ] && printf "%s" "$stdin" | grep -w "${line}$" | cut -f2,3 || exit 1 } -debug () { - printf "\n\033[1;32mReferrer :\033[0m %s\n\033[1;32mlinks >>\n\033[0m%s\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 () { - [ -n "$*" ] && printf "\33[2K\r\033[1;35m%s\n" "$*" - printf "\033[1;35m> \033[0m" - read -r REPLY REPLY2 -} - -selection_menu() { - menu_line_parity=0 - while read -r option line; do - if [ "$option" = "q" ]; then - printf "\033[1;31m(\033[1;31m%s\033[1;31m) \033[1;31m%s\033[0m\n" "$option" "$line" - else - if [ "$menu_line_parity" -eq 0 ]; then - printf "\033[1;33m(\033[1;33m%s\033[1;33m) \033[1;33m%s\033[0m\n" "$option" "$line" - menu_line_parity=1 - else - printf "\033[1;36m(\033[1;36m%s\033[1;36m) \033[1;36m%s\033[0m\n" "$option" "$line" - menu_line_parity=0 - fi - fi - done <<-EOF - $* - EOF - prompt +die() { + printf "\33[2K\r\033[1;31m%s\033[0m\n" "$*" >&2 + exit 1 } -selection_menu_fzf() { - printf "%s\n%s" "$1" "$2" | fzf --height=30% --border -1 --layout=reverse --header-first --header-lines=1 --cycle --with-nth 2.. | cut -d\ -f1 +help_info() { + printf " + Usage: + %s [options] [query] + %s [query] [options] + %s [options] [query] [options] + + Options: + -c, --continue + Continue watching from history + -d, --download + Download the video instead of playing it + -s, --syncplay + Use Syncplay to watch with friends + -q, --quality + Specify the video quality + -v, --vlc + Use VLC to play the video + -V, --version + Show the version of the script + -h, --help + Show this help message and exit + -e, --episode, -r, --range + Specify the number of episodes to watch + --dub + play dubbed version + -U, --update + Update the script + Some example usages: + %s -q 720p banana fish + %s -d -e 2 cyberpunk edgerunners + %s --vlc cyberpunk edgerunners -q 1080p -e 4 + %s blue lock -e 5-6 + %s -e \"5 6\" blue lock + \n" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}" + exit 0 } -version_text () { - inf "Version: $VERSION" +version_info() { + printf "%s\n" "$version_number" + exit 0 } -# get the newest version of this script from github and replace it -update_script () { - update="$(curl -A "$agent" -s "https://raw.githubusercontent.com/pystardust/ani-cli/master/ani-cli")" || die "Connection error" +update_script() { + update="$(wget -q -O - -U "$agent" "https://raw.githubusercontent.com/pystardust/ani-cli/master/ani-cli")" || die "Connection error" update="$(printf '%s\n' "$update" | diff -u "$0" -)" if [ -z "$update" ]; then - inf "Script is up to date :)" + printf "Script is up to date :)\n" else - if printf '%s\n' "$update" | patch "$0" - ; then - inf "Script has been updated" + if printf '%s\n' "$update" | patch "$0" -; then + printf "Script has been updated\n" else die "Can't update for some reason!" fi @@ -134,523 +90,295 @@ update_script () { } # checks if dependencies are present -dep_ch () { +dep_ch() { for dep; do - if ! command -v "$dep" >/dev/null ; then - die "Program \"$dep\" not found. Please install it." - fi + command -v "$dep" >/dev/null || die "Program \"$dep\" not found. Please install it." done } -download () { - case $2 in - *m3u8*) - ffmpeg -loglevel error -stats -referer "$1" -i "$2" -c copy "$download_dir/$3.mp4" ;; - *) - axel -a -k -n 10 --header=Referer:"$1" "$2" -o "$download_dir/$3.mp4" ;; +# SCRAPING + +# extract the video links from reponse of embed urls, extract mp4 links form m3u8 lists +get_links() { + episode_link="$(wget -q -O - "https://blog.allanime.pro/apivtwo/clock.json?id=$*" -U "$agent" | sed 's|},{|\n|g' | sed -nE 's|.*link":"([^"]*)".*"resolutionStr":"([^"]*)".*|\2 >\1|p')" + case "$episode_link" in + *v.vrv.co*) + extract_link=$(printf "%s" "$episode_link" | grep "$media_locale" | cut -d'>' -f2) + wget -q -O - "$extract_link" -U "$agent" | sed 's|^#.*x||g; s|,.*|p|g; /^#/d; $!N; s|\n| >|; s|\/index-v1-a1\.m3u8||g' | sort -nr + ;; + *repackager.wixmp.com*) + extract_link=$(printf "%s" "$episode_link" | cut -d'>' -f2 | sed 's|repackager.wixmp.com/||g;s|\.urlset.*||g') + for j in $(printf "%s" "$episode_link" | sed -nE 's|.*/,([^/]*),/mp4.*|\1|p' | sed 's|,|\n|g'); do + printf "%s >%s\n" "$j" "$extract_link" | sed "s|,[^/]*|${j}|g" + done | sort -nr + ;; + *//cache.* | *gofcdn.com*) + if printf "%s" "$episode_link" | head -1 | grep -q "original.m3u"; then + printf "%s" "$episode_link" + else + extract_link=$(printf "%s" "$episode_link" | head -1 | cut -d'>' -f2) + relative_link=$(printf "%s" "$extract_link" | sed 's|[^/]*$||') + wget -q -O - "$extract_link" -U "$agent" | sed 's|^#.*x||g; s|,.*|p|g; /^#/d; $!N; s|\n| >|' | sed "s|>|>${relative_link}|g" | sort -nr + fi + ;; + *) [ -n "$episode_link" ] && printf "%s\n" "$episode_link" ;; esac + printf "Fetching %s Links\n" "$provider_name" 1>&2 } -############# -# SEARCHING # -############# - -# gets anime names along with its id for search term -search_anime () { - search=$(printf '%s' "$1" | tr ' ' '-' ) - curl -s "https://gogoanime.dk//search.html?keyword=$search" -L | - sed -nE "s_^[[:space:]]*\$_\1_p" -} - -#fetches all the episodes embed links in an anime from gogoanime server -episode_list () { - select_ep_result=$(curl -A "$agent" -s "$base_url/v1/$1" | sed -nE "s_.*epslistplace.*>(.*)_\1_p" | tr "," "\n" | sed -e '/extra/d' -e '/PV/d' | sed -nE 's_".*":"(.*)".*_\1_p') - first_ep_number=1 - [ -z "$select_ep_result" ] && last_ep_number=0 || last_ep_number=$(printf "%s\n" "$select_ep_result" | wc -l) -} - -process_hist_entry () { - temp_anime_id=$(printf "%s" "$anime_id" | sed 's/\t[0-9]*.$//') - current_ep=$(printf "%s" "$anime_id" | sed "s/$temp_anime_id\t//g") - episode_list "$temp_anime_id" - latest_ep=$last_ep_number - if [ -n "$latest_ep" ] && [ "$latest_ep" -ge "$current_ep" ]; then - printf "%s : %s / %s\n" "$temp_anime_id" "$current_ep" "$latest_ep" - fi +# innitialises provider_name and provider_id. First argument is the provider name, 2nd is the regex that matches that provider's link +provider_init() { + provider_name=$1 + provider_id=$(printf "%s" "$resp" | sed -n "$2" | head -1 | cut -d':' -f2) } -# compares history with gogoplay, only shows unfinished anime -search_history () { - [ ! -s "$logfile" ] && die "History is empty" - progress "Processing $scrape.." - search_results=$(while read -r anime_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 - anime_selection "$search_results" - ep_choice_start=$(sed -n -E "s/${selection_id}\t(.*)/\1/p" "$logfile") -} - -################## -# URL PROCESSING # -################## - +# generates links based on given provider generate_link() { case $1 in - 2) - provider_name='Xstreamcdn' - progress "Fetching $provider_name links.." - fb_id=$(printf "%s" "$resp" | sed -n "s_.*fembed.*/v/__p") - refr="https://fembed-hd.com/v/$fb_id" - [ -z "$fb_id" ] && return 0 - result_links="$(curl -A "$agent" -s -X POST "https://fembed-hd.com/api/source/$fb_id" -H "x-requested-with:XMLHttpRequest" | - sed -e 's/\\//g' -e 's/.*data"://' | tr "}" "\n" | sed -nE 's/.*file":"(.*)","label":"(.*)","type.*/\2>\1/p')" - ;; - 1) - provider_name='Animixplay' - progress "Fetching $provider_name Direct link.." - refr="$base_url" - [ -z "$id" ] && return 0 - enc_id=$(printf "%s" "$id" | base64) - ani_id=$(printf "%sLTXs3GrU8we9O%s" "$id" "$enc_id" | base64) - result_links="$(curl -s "$base_url/api/cW9${ani_id}" -A "$agent" -I | sed -nE 's_[L|l]ocation: https?://[^#]*#([^#]*).*_\1_p' | base64 -d)" - ;; - *) - provider_name='Gogoanime' - progress "Fetching $provider_name Direct link.." - refr="$gogohd_url" - [ -z "$id" ] && return 0 - secret_key=$(printf "%s" "$resp" | sed -n '2p' | tr -d "\n" | od -A n -t x1 | tr -d " |\n") - iv=$(printf "%s" "$resp" | sed -n '3p' | tr -d "\n" | od -A n -t x1 | tr -d " |\n") - second_key=$(printf "%s" "$resp" | sed -n '4p' | tr -d "\n" | od -A n -t x1 | tr -d " |\n") - token=$(printf "%s" "$resp" | head -1 | base64 -d | openssl enc -d -aes256 -K "$secret_key" -iv "$iv" | sed -nE 's/.*&(token.*)/\1/p') - ajax=$(printf '%s' "$id" | openssl enc -e -aes256 -K "$secret_key" -iv "$iv" -a) - data=$(curl -A "$agent" -sL -H "X-Requested-With:XMLHttpRequest" "${gogohd_url}encrypt-ajax.php?id=${ajax}&alias=${id}&${token}" | sed -e 's/{"data":"//' -e 's/"}/\n/' -e 's/\\//g') - result_links="$(printf '%s' "$data" | base64 -d 2>/dev/null | openssl enc -d -aes256 -K "$second_key" -iv "$iv" 2>/dev/null | - tr -d \\\\ | sed -nE "s_.*file\":\"([^\"]*)\".*source.*_\1_p")" - ;; + 1) provider_init 'vrv|wixmp' '/Default :/p' ;; # vrv,wixmp(default)(m3u8)(multi) -> (mp4)(multi) + 2) provider_init 'pstatic' '/Default B :/p' ;; # pstatic(default backup)(mp4)(multi) + 3) provider_init 'sharepoint' '/S-mp4 :/p' ;; # sharepoint(mp4)(single) + 4) provider_init 'usercloud' '/Uv-mp4 :/p' ;; # usercloud(mp4)(single) + *) provider_init 'gogoanime' '/Luf-mp4 :/p' ;; # gogoanime(m3u8)(multi) + esac + [ -n "$provider_id" ] && get_links "$provider_id" +} + +select_quality() { + case "$1" in + best) result=$(printf "%s" "$links" | head -n1) ;; + worst) result=$(printf "%s" "$links" | grep -E '^[0-9]{3,4}' | tail -n1) ;; + *) result=$(printf "%s" "$links" | grep -m 1 "$1") ;; esac + [ -z "$result" ] && printf "Specified quality not found, defaulting to best" 1>&2 && result=$(printf "%s" "$links" | head -n1) + printf "%s" "$result" | cut -d'>' -f2 } -# chooses the link for the set quality -get_video_link() { - dpage_url="$1" - id=$(printf "%s" "$dpage_url" | sed -nE 's/.*id=([^&]*).*/\1/p') - #multiple sed are used (regex seperated by ';') for extracting only required data from response of embed url - resp="$(curl -A "$agent" -sL "${gogohd_url}streaming.php?id=$id" | - sed -nE 's/.*class="container-(.*)">/\1/p ; - s/.*class="wrapper container-(.*)">/\1/p ; - s/.*class=".*videocontent-(.*)">/\1/p ; - s/.*data-value="(.*)">.*/\1/p ; - s/.*data-status="1".*data-video="(.*)">.*/\1/p')" +# gets embed urls, collects direct links into provider files, selects one with desired quality into $episode +get_episode_url() { + # get the embed urls of the selected episode + resp=$(wget -q -O - "https://allanime.site/watch/$id/$allanime_title/episode-$ep_no-$mode" -U "$agent" | tr '{}' '\n' | sed 's|\\u002F|\/|g;s|\\||g' | sed -nE 's|.*sourceUrl":".*clock\?id=([^"]*)".*sourceName":"([^"]*)".*|\2 :\1|p') + # generate links into sequential files provider=1 - [ -n "$select_provider" ] && provider="$select_provider" i=0 - while [ "$i" -lt 3 ] && [ -z "$result_links" ];do - generate_link "$provider" - provider=$((provider % 3 + 1)) - if [ "$debug" -eq 1 ]; then - debug "$refr" "$result_links" - unset result_links - fi - : $((i+=1)) + while [ "$i" -lt 5 ]; do + generate_link "$provider" >"$cache_dir"/"$i" & + provider=$((provider % 5 + 1)) + : $((i += 1)) done - [ "$debug" -eq 1 ] && return 0 - if printf '%s' "$result_links" | grep -q "m3u8"; then - get_video_quality_m3u8 "$result_links" - else - video_url=$(get_video_quality_mp4 "$result_links") - fi - unset result_links + wait + # select the link with matching quality + links=$(cat "$cache_dir"/* | sed 's|^Mp4-||g' | sort -g -r -s) + episode=$(select_quality "$quality") + [ -z "$episode" ] && die "Episode not released!" } -get_video_quality_mp4() { - case $quality in - best) - video_url=$(printf '%s' "$1" | tail -n 1 | cut -d">" -f2) ;; - worst) - video_url=$(printf '%s' "$1" | head -n 1 | cut -d">" -f2) ;; - *) - video_url=$(printf '%s' "$1" | grep -i "${quality}p" | head -n 1 | cut -d">" -f2) - if [ -z "$video_url" ]; then - err "Current video quality is not available (defaulting to best quality)" - video_url=$(printf '%s' "$1" | tail -n 1 | cut -d">" -f2) - fi - ;; - esac - printf '%s' "$video_url" +# search the query and give results +search_anime() { + wget -q -O - "https://allanime.site/allanimeapi?variables=%7B%22search%22%3A%7B%22allowAdult%22%3Atrue%2C%22allowUnknown%22%3Atrue%2C%22query%22%3A%22$*%22%7D%2C%22limit%22%3A40%2C%22page%22%3A1%2C%22translationType%22%3A%22$mode%22%2C%22countryOrigin%22%3A%22ALL%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%229c7a8bc1e095a34f2972699e8105f7aaf9082c6e1ccd56eab99c2f1a971152c6%22%7D%7D" -U "$agent" | sed 's|Show|\n|g' | sed -nE "s|.*_id\":\"([^\"]*)\",\"name\":\"(.*)\",\"english.*\"$mode\":([1-9][^,]*).*|\1\t\2 (\3 episode)|p" | sed 's/\\//g;s/"//g' } -get_video_quality_m3u8() { - printf '%s' "$1" | grep -qE "manifest.*m3u.*" && video_url=$1 && return 0 - m3u8_links=$(curl -A "$agent" -s --referer "$dpage_link" "$1") - case $quality in - best) - res_selector=$(printf "%s" "$m3u8_links" | sed -nE 's_.*RESOLUTION=.*x([^,]*).*_\1_p' | sort -nr | head -1);; - worst) - res_selector=$(printf "%s" "$m3u8_links" | sed -nE 's_.*RESOLUTION=.*x([^,]*).*_\1_p' | sort -nr | tail -1);; - *) - res_selector=$quality - if ! (printf '%s' "$m3u8_links" | grep -q "x$quality"); then - err "Current video quality is not available (defaulting to best quality)" - res_selector=$(printf "%s" "$m3u8_links" | sed -nE 's_.*RESOLUTION=.*x([^,]*).*_\1_p' | sort -nr | head -1) - fi - ;; - esac - video_url=$(printf '%s' "$m3u8_links" | sed -n "/x$res_selector/{n;p;}" | tr -d '\r') - printf "%s" "$m3u8_links" | grep -q "http" || video_url="$(printf "%s" "$1" | sed 's|[^/]*$||')$video_url" || true +# get the episodes list of the selected anime +episodes_list() { + wget -q -O - "https://allanime.site/anime/$*" -U "$agent" | sed 's|\\||g' | sed -nE "s|.*$mode\":\[([0-9.\",]*)\].*|\1|p" | sed 's|,|\n|g; s|"||g' } +# PLAYING -################# -# INPUT PARSING # -################# - -append () { - [ -z "$1" ] || printf "%s\n" "$1" - printf "%s %s" "$3" "$2" +process_hist_entry() { + ep_list=$(episodes_list "$id") + ep_no=$(printf "%s" "$ep_list" | sed -n "/^${ep_no}$/{g;1!p;};h") 2>/dev/null + [ -n "$ep_no" ] && printf "%s\t%s - episode %s\n" "$id" "$title" "$ep_no" } -# only lets the user pass in case of a valid search -process_search () { - progress "Searching $scrape.." - search_results=$(search_anime "$query") - while [ -z "$search_results" ]; do - retry 'No search results found' - query="$REPLY $REPLY2" - progress "Searching $scrape.." - search_results=$(search_anime "$query") - done - anime_selection "$search_results" - episode_selection -} - -# anime-selection menu handling function -anime_selection () { - if [ "$fzf" -eq 0 ];then - inf "$scrape Results >>" +update_history() { + if grep -q -- "$id" "$histfile"; then + sed -E "s/^[^\t]+\t${id}\t/${ep_no}\t${id}\t/" "$histfile" >"${histfile}.new" else - progress "" + cp "$histfile" "${histfile}.new" + printf "%s\t%s\t%s\n" "$ep_no" "$id" "$title" >>"${histfile}.new" fi - count=1 - unset selection_list - while read -r anime_id; do - displayed_title=$(printf '%s' "$anime_id" | tr '-' ' ') - selection_list=$(append "$selection_list" "$displayed_title" "$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 - selection_list=$(append "$selection_list" "quit" "q") - if [ "$fzf" -eq 1 ]; then - choice=$(selection_menu_fzf ". $scrape Results>>" "$selection_list") - [ -z "$choice" ] && exit 0 - else - selection_menu "$selection_list" - choice="$REPLY" - [ -z "$choice" ] && choice=1 - fi - while ! [ "$choice" -eq "$choice" ] 2>/dev/null || [ "$choice" -lt 1 ] || [ "$choice" -ge "$count" ] || [ "$choice" = " " ]; do - [ "$choice" = "q" ] && exit 0 - retry "Invalid choice entered" - choice="$REPLY" - done - fi - # Select respective anime_id - selection_id="$(printf "%s" "$search_results" | sed -n "${choice}p" | cut -d':' -f1 | tr -d ' ')" - progress "Searching Episodes.." - episode_list "$selection_id" + mv "${histfile}.new" "$histfile" } -# gets episode number from user, makes sure it's in range, skips input if only one episode exists -episode_selection () { - [ "$last_ep_number" -eq 0 ] && die "Episodes not released yet!" - if [ "$last_ep_number" -gt "$first_ep_number" ]; then - [ "$ep_choice_to_start" = "-1" ] && ep_choice_to_start="$last_ep_number" - if [ -z "$ep_choice_to_start" ]; then - # if branches, because order matters this time - while : ; do - last_ep_number=$(printf '%s' "$last_ep_number"|tr -d "[:space:]") - prompt "To specify a range, use: start_number end_number (Episodes: $first_ep_number-$last_ep_number)" - ep_choice_start="$REPLY" - ep_choice_end="$REPLY2" - [ "$REPLY" = q ] && exit 0 - [ "$ep_choice_start" = "-1" ] && ep_choice_start="$last_ep_number" || [ -z "$ep_choice_start" ] && ep_choice_start="$last_ep_number" - [ "$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 [ -n "$ep_choice_end" ] && [ "$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 anime contains only a single episode - ep_choice_start=1 - fi - [ -n "$ep_choice_end" ] && auto_play=1 -} - -# 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 # -################## - -# 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} +download() { + case $1 in + *m3u8*) ffmpeg -loglevel error -stats -i "$1" -c copy "$download_dir/$2.mp4" ;; + *) aria2c --check-certificate=false --summary-interval=0 -x 16 -s 16 "$1" --dir="$download_dir" -o "$2.mp4" --download-result=hide ;; + esac } -open_episode () { - anime_id="$1" - episode="$2" - tput clear - progress "Loading episode $episode..." - # decrypting url - dpage_link=$(printf "%s" "$select_ep_result" | sed -n "${episode}p") - if [ -z "$dpage_link" ];then - die "Episode doesn't exist!!" - else - get_video_link "$dpage_link" - fi - [ "$debug" -eq 1 ] && exit 0 - # write anime and episode number and save to temporary history - grep -q -- "$selection_id" "$logfile" || printf "%s\t%s\n" "$selection_id" $((episode+1)) >> "$logfile" - sed -E "s/^${selection_id}\t[0-9]*/${selection_id}\t$((episode+1))/" "$logfile" > "${logfile}.new" - [ ! "$PID" = "0" ] && kill "$PID" >/dev/null 2>&1 - [ -z "$video_url" ] && die "Video URL not found" - trackma_title="$(printf '%s' "$anime_id Episode $episode" | tr '-' ' ' | awk '{for(i=1;i<=NF;i++){ $i=toupper(substr($i,1,1)) substr($i,2) }}1')" - if [ "$auto_play" -eq 0 ]; then - play_episode "$video_url" "$refr" "$trackma_title" - else - printf "\n" - play_episode "$video_url" "$refr" "$trackma_title" - wait - sleep 2 - fi - PID=$! - # overwrite history with temporary history - mv "${logfile}.new" "$logfile" +play_episode() { + get_episode_url + case "$player_function" in + debug) printf "All links:\n%s\nSelected link:\n%s\n" "$links" "$episode" ;; + mpv*) nohup "$player_function" --force-media-title="${allanime_title}episode-${ep_no}-${mode}" "$episode" >/dev/null 2>&1 & ;; + android_mpv) nohup am start --user 0 -a android.intent.action.VIEW -d "$episode" -n is.xyz.mpv/.MPVActivity >/dev/null 2>&1 & ;; + android_vlc) nohup am start --user 0 -a android.intent.action.VIEW -d "$episode" -n org.videolan.vlc/org.videolan.vlc.gui.video.VideoPlayerActivity -e "title" "${allanime_title}episode-${ep_no}-${mode}" >/dev/null 2>&1 & ;; + iina) nohup "$player_function" --no-stdin --keep-running --mpv-force-media-title="${allanime_title}episode-${ep_no}-${mode}" "$episode" >/dev/null 2>&1 & ;; + flatpak_mpv) flatpak run io.mpv.Mpv --force-media-title="${allanime_title}episode-${ep_no}-${mode}" "$episode" >/dev/null 2>&1 & ;; + vlc*) nohup "$player_function" --video-title="${allanime_title}episode-${ep_no}-${mode}" "$episode" >/dev/null 2>&1 & ;; + *yncpla*) nohup "$player_function" "$episode" -- --force-media-title="${allanime_title}episode-${ep_no}-${mode}" >/dev/null 2>&1 & ;; + download) "$player_function" "$episode" "${allanime_title}episode-${ep_no}-${mode}" ;; + *) nohup "$player_function" >/dev/null 2>&1 & ;; + esac + update_history + [ "$use_external_menu" = "1" ] && wait } -play_episode () { - video_url="$1"; refr="$2" trackma_title="$3" - if [ "$player_fn" = "download" ];then - inf "Currently downloading $trackma_title ($provider_name)" +play() { + start=$(printf "%s" "$ep_no" | sed "s/[^0-9]*\([0-9]*\).*/\1/") + end=$(printf "%s" "$ep_no" | sed "s/[^0-9]*[0-9]*[^0-9]*\([0-9]*\).*/\1/") + [ -z "$start" ] || [ -z "$end" ] && unset start end + line_count=$(printf "%s\n" "$ep_no" | wc -l) + if [ "$line_count" != 1 ] || [ -n "$start" ] || [ -n "$end" ]; then + [ -z "$start" ] && start=$(printf "%s\n" "$ep_no" | tail -n1) + [ -z "$end" ] && end=$(printf "%s\n" "$ep_no" | head -n1) + range=$(printf "%s\n" "$ep_list" | sed '1!G;h;$!d' | sed -nE "/^${start}\$/,/^${end}\$/p") + [ -z "$range" ] && die "Invalid range!" + for i in $range; do + tput clear + ep_no=$i + printf "\33[2K\r\033[1;34mPlaying episode %s...\033[0m\n" "$ep_no" + play_episode + done else - inf "Currently playing $trackma_title ($provider_name)" + play_episode fi - case "$player_fn" in - download) - if download "$refr" "$video_url" "$trackma_title"; then - inf "Downloaded episode: $trackma_title" - else - err "Download failed episode: $trackma_title , please retry or check your internet connection" - fi - ;; - iina) - nohup "$player_fn" "$video_url" --no-stdin --keep-running --mpv-referrer="$refr" --mpv-force-media-title="$trackma_title" > /dev/null 2>&1 & ;; - vlc) - if uname -a | grep -qE '[Aa]ndroid';then - am start --user 0 -a android.intent.action.VIEW -d "$video_url" -n org.videolan.vlc/org.videolan.vlc.gui.video.VideoPlayerActivity -e "title" "$trackma_title" > /dev/null 2>&1 & - else - nohup "$player_fn" "$video_url" --http-referrer="$refr" --meta-title "$trackma_title" --play-and-exit > /dev/null 2>&1 & - fi - ;; - "syncplay"|"/Applications/Syncplay.app/Contents/MacOS/syncplay"|"/c/Program Files (x86)/Syncplay/Syncplay.exe") - nohup "$player_fn" "$video_url" -- --referrer="$refr" --force-media-title="$trackma_title" > /dev/null 2>&1 & ;; - *) - if uname -a | grep -qE '[Aa]ndroid';then - am start --user 0 -a android.intent.action.VIEW -d "$video_url" -n is.xyz.mpv/.MPVActivity > /dev/null 2>&1 & - else - nohup "$player_fn" "$video_url" --referrer="$refr" --force-media-title="$trackma_title" > /dev/null 2>&1 & - fi - ;; - esac } -############ -# START UP # -############ -# clears the colors and deletes temporary logfile when exited using SIGINT -trap 'printf "\033[0m";rm -f "$logfile".new;exit 1' INT HUP +# MAIN -# default options +# setup agent="Mozilla/5.0 (X11; Linux x86_64; rv:99.0) Gecko/20100101 Firefox/100.0" -PID=0 -quality=best -scrape=Query -debug=0 -choice= -fzf=0 -auto_play=0 -download_dir="$(pwd)" -case "$(uname)" in - Darwin*) player_fn='iina';; - *) player_fn='mpv';; +mode="${ANI_CLI_MODE:-sub}" +media_locale="${ANI_CLI_MEDIA_LOCALE:-en-US}" +download_dir="${ANI_CLI_DOWNLOAD_DIR:-.}" +quality="${ANI_CLI_QUALITY:-best}" +case "$(uname -a)" in +*Darwin*) player_function="${ANI_CLI_PLAYER:-iina}" ;; # mac OS +*ndroid*) player_function="${ANI_CLI_PLAYER:-android_mpv}" ;; # Android OS (termux) +*steamdeck*) player_function="${ANI_CLI_PLAYER:-flatpak_mpv}" ;; # steamdeck OS +*MINGW*) player_function="${ANI_CLI_PLAYER:-mpv.exe}" ;; # Windows OS +*) player_function="${ANI_CLI_PLAYER:-mpv}" ;; # Linux OS esac -# history file path -logdir="${XDG_CACHE_HOME:-$HOME/.cache}" -logfile="$logdir/ani-hsts" -# create history file and history dir if none found -[ -d "$logdir" ] || mkdir "$logdir" -[ -f "$logfile" ] || : > "$logfile" - -while getopts 'svq:dp:chDUVa:xr:fn' OPT; do - case $OPT in - d) player_fn='download' ;; - a) ep_choice_to_start=$OPTARG ;; - U) update_script ;; - D) - : > "$logfile" - exit 0 - ;; - p) - player_fn='download' - download_dir="$OPTARG" - if [ ! -d "$download_dir" ] ; then - mkdir -p "$download_dir" || die "Couldn't create $download_dir" - fi - ;; - n) scrape=New ;; - s) - case "$(uname -s)" in - Darwin*) player_fn="/Applications/Syncplay.app/Contents/MacOS/syncplay" ;; - MINGW*|*Msys) player_fn="/c/Program Files (x86)/Syncplay/Syncplay.exe" ;; - *) player_fn="syncplay" ;; - esac - ;; - q) quality=$OPTARG ;; - x) debug=1 ;; - r) select_provider=$OPTARG ;; - f) fzf=1 ;; - c) scrape=History ;; - v) player_fn='vlc';; - V) - version_text - exit 0 - ;; - h) - help_text - exit 0 - ;; - *) - help_text - exit 1 - ;; + +use_external_menu="${ANI_CLI_EXTERNAL_MENU:-0}" +[ "$use_external_menu" = "0" ] && multi_selection_flag="${ANI_CLI_MULTI_SELECTION:-"-m"}" +[ "$use_external_menu" = "1" ] && multi_selection_flag="${ANI_CLI_MULTI_SELECTION:-"-multi-select"}" +cache_dir="${ANI_CLI_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/ani-cli}" +[ ! -d "$cache_dir" ] && mkdir -p "$cache_dir" +hist_dir="${ANI_CLI_HIST_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}/ani-cli}" +[ ! -d "$hist_dir" ] && mkdir -p "$hist_dir" +histfile="$hist_dir/ani-hsts" +[ ! -f "$histfile" ] && : >"$histfile" +search="${ANI_CLI_DEFAULT_SOURCE:-scrape}" + +while [ $# -gt 0 ]; do + case "$1" in + -v | --vlc) + case "$(uname -a)" in + *ndroid*) player_function="android_vlc" ;; + MINGW*) player_function="vlc.exe" ;; + *) player_function="vlc" ;; + esac + ;; + -s | --syncplay) + case "$(uname -s)" in + Darwin*) player_function="/Applications/Syncplay.app/Contents/MacOS/syncplay" ;; + MINGW* | *Msys) player_function="/c/Program Files (x86)/Syncplay/Syncplay.exe" ;; + *) player_function="syncplay" ;; + esac + ;; + -q | --quality) + [ $# -lt 2 ] && die "missing argument!" + quality="$2" + shift + ;; + -c | --continue) search=history ;; + -d | --download) player_function=download ;; + -V | --version) version_info ;; + -h | --help) help_info ;; + -e | --episode | -r | --range) + [ $# -lt 2 ] && die "missing argument!" + ep_no="$2" + shift + ;; + --dub) mode="dub" ;; + -U | --update) update_script ;; + *) query="$(printf "%s" "$query $1" | sed "s|^ ||;s| |%20|g")" ;; esac + shift done -shift $((OPTIND - 1)) -progress "Checking dependencies.." -# shellcheck disable=SC2046 -dep_ch "curl" "sed" "grep" "openssl" || true - -if [ "$player_fn" = "download" ];then - dep_ch "ffmpeg" "axel" -elif ! (uname -a | grep -qE '[Aa]ndroid');then - dep_ch "$player_fn" -fi - -gogohd_url="https://gogohd.net/" -base_url="https://animixplay.to" -if [ "$scrape" = "Query" ];then - if [ -z "$*" ]; then - prompt "Search Anime" - query="$REPLY $REPLY2" - else - if [ -n "$ep_choice_to_start" ]; then - REPLY=1 - select_first=1 +printf "\33[2K\r\033[1;34mChecking dependencies...\033[0m\n" +dep_ch "wget" "sed" "grep" "fzf" || true +case "$player_function" in +debug) ;; +download) dep_ch "ffmpeg" "aria2c" ;; +flatpak*) + dep_ch "flatpak" + flatpak info io.mpv.Mpv >/dev/null 2>&1 || die "Program \"mpv (flatpak)\" not found. Please install it." + ;; +android*) printf "Checking of players on Android is disabled\n" ;; +*) dep_ch "$player_function" ;; +esac + +# searching +case "$search" in +history) + anime_list=$(while read -r ep_no id title; do process_hist_entry & done <"$histfile") + wait + [ -z "$anime_list" ] && die "No unwatched series in histroy!" + result=$(printf "%s" "$anime_list" | nl -w 1 | nth "Select anime: " | cut -f1) + [ -z "$result" ] && exit 1 + result=$(grep "$result" "$histfile") + read -r ep_no id title <<-EOF + $result + EOF + ep_list=$(episodes_list "$id") + ep_no=$(printf "%s" "$ep_list" | sed -n "/^${ep_no}$/{g;1!p;};h") 2>/dev/null + allanime_title="$(printf "%s" "$title" | cut -d'(' -f1 | tr -d '[:punct:]' | tr 'A-Z ' 'a-z-')" + tput cuu1 && tput el + ;; +*) + while [ -z "$query" ]; do + if [ "$use_external_menu" = "0" ]; then + printf "Search anime: " && read -r query + else + query=$(printf "" | external_menu "Search anime: ") fi - query="$*" - fi - process_search -elif [ "$scrape" = "New" ];then - progress "" - selection_id="$(curl -s "https://animixplay.to/rss.xml" | sed -nE 's_.*link.*animixplay.to/v1/([^<]*)/ep([^<]*)<.*_\1 episode \2_p' | tr '-' ' ' | fzf --height=30% --border -1 --layout=reverse --cycle)" - [ -z "$selection_id" ] && die "No anime Selected" - ep=$(printf "%s" "$selection_id" | sed 's_.*episode __') - selection_id=$(printf "%s" "$selection_id" | sed 's_ episode.*__' | tr ' ' '-') - episode_list "$selection_id" - open_episode "$selection_id" "$ep" - exit 0 -else - search_history -fi - -generate_ep_list -open_selection - -######## -# LOOP # -######## - -while : ; do - auto_play=0 - unset menu - unset options - [ "$episode" -ne "$last_ep_number" ] && menu=$(append "$menu" 'next' 'n') - [ "$episode" -ne "$first_ep_number" ] && menu=$(append "$menu" 'previous' 'p') - menu=$(append "$menu" 'replay' 'r') - [ "$first_ep_number" -ne "$last_ep_number" ] && menu=$(append "$menu" 'select' 's') - menu=$(append "$menu" 'quit' 'q') - if [ "$fzf" -eq 1 ];then - progress "" - choice="$(selection_menu_fzf ". Menu>>" "$menu")" - [ -z "$choice" ] && die "No anime Selected" - else - selection_menu "$menu" - choice="$REPLY" - fi - 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 - ;; + done + query=$(printf "%s" "$query" | sed "s| |%20|g") + anime_list=$(search_anime "$query") + [ -z "$anime_list" ] && die "No results found!" + result=$(printf "%s" "$anime_list" | nl -w 1 | nth "Select anime: ") || exit 1 + title=$(printf "%s" "$result" | cut -f2) + allanime_title="$(printf "%s" "$title" | cut -d'(' -f1 | tr -d '[:punct:]' | tr 'A-Z ' 'a-z-')" + id=$(printf "%s" "$result" | cut -f1) + ep_list=$(episodes_list "$id") + [ -z "$ep_no" ] && ep_no=$(printf "%s" "$ep_list" | nth "Select episode: " "$multi_selection_flag") + [ -z "$ep_no" ] && exit 1 + ;; +esac + +# moves the cursor up one line and clears that line +tput cuu1 && tput el + +# playback & loop +play +[ "$player_function" = "download" ] || [ "$player_function" = "debug" ] && exit 0 + +while cmd=$(printf "quit\nselect\nprevious\nreplay\nnext" | nth "Playing episode $ep_no of $title... "); do + case "$cmd" in + previous) ep_no=$(printf "%s" "$ep_list" | sed -n "/^${ep_no}$/{n;p;}") 2>/dev/null ;; + replay) ;; + next) ep_no=$(printf "%s" "$ep_list" | sed -n "/^${ep_no}$/{g;1!p;};h") 2>/dev/null ;; + select) ep_no=$(printf "%s" "$ep_list" | nth "Select episode: " "$multi_selection_flag") ;; + *) exit 0 ;; esac - generate_ep_list - open_selection + [ -z "$ep_no" ] && die "Out of range" + play done # ani-cli diff --git a/ani-cli.1 b/ani-cli.1 index f05e5e80c..44152cac9 100644 --- a/ani-cli.1 +++ b/ani-cli.1 @@ -1,4 +1,4 @@ -.TH "ANI-CLI" "1" "December 2022" "ani-cli" "User Commands" +.TH "ANI-CLI" "1" "January 2023" "ani-cli" "User Commands" .SH NAME ani-cli \- watch anime from the commandline .SH SYNOPSIS @@ -9,63 +9,81 @@ A shell script to browse and search anime from the command-line. .PD 0 .P .PD -This tool scrapes the site animixplay. +This tool scrapes the site allanime. .PD 0 .P .PD -\f[B]ani-cli\f[R] without options defaults to the mpv media player. +\f[B]ani-cli\f[R] without options defaults to iina on macOS, flatpak mpv on Steamdeck, mpv apk on android and mpv media player everywhere else. .SH OPTIONS .TP -\fB\-a\fR \fI\,\/\fR -Specify the episode number to watch. +\fB\-e | --episode | -r | --range\fR \fI\,\/\fR +Specify the episode numbers to watch. If range is specified it should be quoted or separated by a non-numeric character (eg. -). .TP -\fB\-c\fR -continue watching anime from history. +\fB\-c | --continue\fR +Continue watching anime from history. .TP -\fB\-d\fR -download episode. +\fB\-d | --download\fR +Download episode. .TP -\fB\-D\fR -delete history. +\fB\-h | --help\fR +Show summary of options. .TP -\fB\-f\fR -use fzf for anime selection. +\fB\-q | --quality\fR \fI\,\/\fR +Set the video quality. Default quality is best. .TP -\fB\-h\fR -Show summary of options. +\fB\-s | --syncplay\fR +Watch anime together with friends, using Syncplay (works with mpv only). +.TP +\fB\-U | --update\fR +Fetch update from github. .TP -\fB\-p\fR \fI\,DIRECTORY\/\fR -download episode to the specified DIRECTORY. +\fB\-v | --vlc\fR +Use VLC as the media player. .TP -\fB\-q\fR \fI\,\/\fR -set the video quality. +\fB\-V | --version\fR +Print version number and exit. .TP -\fB\-r\fR -select provider to scrape first. +\fB\--dub\fR +Play the dubbed version. Without this flag, it'll always play the subbed version. +.PP +.SH +ENVIRONMENT VARIABLES +.PP +ani-cli v4 uses environment variables to control unstable/untested and niche features in addition to everything that has an option. Command-line options take precedence over env vars. Note that these are all subject to change. .TP -\fB\-s\fR -watch anime together with friends, using Syncplay (works with mpv only). +\fBANI_CLI_MODE\fR +Controls the scraped media's mode, valid options are sub or dub. Default is sub. .TP -\fB\-U\fR -fetch update from github. +\fBANI_CLI_MEDIA_LOCALE\fR +Controls the scraped media's locale, check allanime for valid options. Default is en-US. .TP -\fB\-v\fR -use VLC as the media player. +\fBANI_CLI_DOWNLOAD_DIR\fR +Controls the directory where files are downloaded. Default is the current dir. .TP -\fB\-V\fR -print version number and exit. +\fBANI_CLI_QUALITY\fR +Controls the scraped media's quality, check allanime for valid options or set to worst/best. Default is best. .TP -\fB\-x\fR -print all video links from all providers to stdout (for debugging -purpose). -.SH EPISODE SELECTION +\fBANI_CLI_PLAYER\fR +Sets the player ani-cli uses. Can be debug (print links), download (equivalent to -d), android_mpv (apk and am start), android_vlc (apk and am start), flatpak_mpv (for flatpak) or any player that can play urls. For defaults see working without arguments. +.TP +\fBANI_CLI_EXTERNAL_MENU\fR +Controls the frontend of ani-cli. Can be 0 (uses fzf) or 1 (uses rofi dmenu). Default is 0. +.TP +\fBANI_CLI_MULTI_SELECTION\fR +Controls the multi flag for the chosen frontend. Default is -m for fzf and --multi-select for rofi dmenu. +.TP +\fBANI_CLI_CACHE_DIR\fR +Controls the directory ani-cli uses for caching results. A /ani-cli subfolder is created there for the cache files if doesn't exists. Default is $XDG_CACHE_HOME if set, $HOME/.cache/ if not. +.TP +\fBANI_CLI_HIST_DIR\fR +Controls the directory ani-cli uses for storing history. A /ani-cli subfolder is created there for the histfile if doesn't exists. Default is $XDG_STATE_HOME if set, $HOME/.local/state if not. +.TP +\fBANI_CLI_DEFAULT_SOURCE\fR +Controls the default source. Valid is history (equivalent to -c), everything else means search. Default is search. .PP -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 +.SH EPISODE SELECTION .PP -When selecting non-interactively, the first result will be selected, if -anime is passed +Multiple episodes can be chosen using fzf (or alternative frontend's) multi-selection mode. For this refer to their instructions. .SH BUGS .PP Use the GitHub issue tracker: