Skip to content

Allanime works again #1899

Description

@hjfghycvdt

Just copy this and save to ani-cli or whatever you like

#!/bin/sh

version_number="4.14.5"

allanime_key="43724f7d46135c6cdb2824f00c4ee272a0fff52f89681213140c6c2b80af8d21"
allanime_query_hash="c66a5306b7ab6cf4701e766cb352b25b70198e873c4e917f9388da75db5cdca2"
allanime_epoch="2957"
allanime_buildid="162"
allanime_lane="k7"

# UI

external_menu() {
    [ "$use_external_menu" = "1" ] && rofi "$1" -sort -dmenu -i -width 1500 -p "$2" "$3"
    [ "$use_external_menu" = "2" ] && dmenu -l 20 -p "$2"
}

launcher() {
    [ "$use_external_menu" = "0" ] && [ -z "$1" ] && set -- "+m" "$2"
    [ "$use_external_menu" = "0" ] && fzf "$1" --reverse --cycle --prompt "$2"
    [ "$use_external_menu" = "1" ] && external_menu "$1" "$2" "$external_menu_args"
    [ "$use_external_menu" = "2" ] && external_menu "$1" "$2"
}

nth() {
    stdin=$(cat -)
    [ -z "$stdin" ] && return 1
    line_count="$(printf "%s\n" "$stdin" | wc -l | tr -d "[:space:]")"
    [ "$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 | tr '\t' ' ' | launcher "$multi_flag" "$prompt" | cut -d " " -f 1)
    line_start=$(printf "%s" "$line" | head -n1)
    line_end=$(printf "%s" "$line" | tail -n1)
    [ -n "$line" ] || exit 1
    if [ "$line_start" = "$line_end" ]; then
        printf "%s" "$stdin" | grep -E '^'"${line}"'($|[[:space:]])' | cut -f2,3 || exit 1
    else
        printf "%s" "$stdin" | sed -n '/^'"${line_start}"'$/,/^'"${line_end}$"'/p' || exit 1
    fi
}

die() {
    printf "\33[2K\r\033[1;31m%s\033[0m\n" "$*" >&2
    exit 1
}

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
      -D, --delete
        Delete history
      -l, --logview
        Show logs
      -s, --syncplay
        Use Syncplay to watch with friends
      -S, --select-nth
        Select nth entry
      -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
      --rofi
        Use rofi instead of fzf for the interactive menu
      --dmenu
        Use dmenu instead of fzf for the interactive menu
      --skip
        Use ani-skip to skip the intro of the episode (mpv only)
      --no-detach
        Don't detach the player (useful for in-terminal playback, mpv only)
      --exit-after-play
        Exit the player, and return the player exit code (useful for non interactive scenarios, mpv only)
      --skip-title <title>
        Use given title as ani-skip query
      -N, --nextep-countdown
        Display a countdown to the next episode
      -U, --update
        Update the script
    Some example usages:
      %s -q 720p banana fish
      %s --skip --skip-title \"one piece\" -S 2 one piece
      %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##*/}" "${0##*/}"
    exit 0
}

version_info() {
    printf "%s\n" "$version_number"
    exit 0
}

update_script() {
    printf "[branch:%s] Checking for Update..\n" "$branch"
    update="$(curl --fail-with-body -sL -A "$agent" "https://raw.githubusercontent.com/pystardust/ani-cli/$branch/ani-cli")" || die "[branch:$branch] Connection error: $update"
    printf '%s' "$update" | grep -q "^version_number" || die "[branch:$branch] Invalid Response."
    update="$(printf '%s\n' "$update" | diff -u "$0" -)"
    if [ -z "$update" ]; then
        printf "[branch:%s] Script is up to date :)\n" "$branch"
    else
        if printf '%s\n' "$update" | patch "$0" -; then
            printf "[branch:%s] Script has been updated\n" "$branch"
        else
            die "[branch:$branch] Can't update for some reason!"
        fi
    fi
    exit 0
}

# checks if dependencies are present
dep_ch() {
    command -v "$1" >/dev/null || die "Program $1 not found. Please install it."
}

dep_ch_failover() {
    [ -z "$1" ] && return 1
    prog=$(printf "%s" "$1" | cut -d "," -f 1)
    rest=$(printf "%s" "$1" | cut -s -d "," -f 2-)
    command -v "$prog" >/dev/null 2>&1 && printf "%s" "$prog" && return 0
    [ -e "$prog" ] && printf "%s" "$prog" && return 0
    dep_ch_failover "$rest"
}

cleanup() {
    [ "$player_function" = "android_mpv" ] && : >"/storage/emulated/0/mpv/mpv.config.mp4"
    printf "\033[0m"        # clears colors
    rm -f "${histfile}.new" # remove temp logfile
}

# SCRAPING

# extract the video links from response of embed urls, extract mp4 links form m3u8 lists
get_links() {
    case "$*" in
        *mp4upload*)
            episode_link=$(curl --max-time 10 -sLk "$*" -A "$agent" -e "$allanime_refr" | sed -nE 's|.*src: "([^"]*)"[[:space:]]*|Mp4Upload >\1|p')
            ;;
        *tools.fast4speed.rsvp*)
            episode_link=$(printf "%s\n" "Yt >$*")
            ;;
        *)
            response="$(curl -e "$allanime_refr" -s "https://${allanime_base}$*" -A "$agent")"
            episode_link="$(printf '%s' "$response" | sed 's|},{|\
        |g' | sed -nE 's|.*link":"([^"]*)".*"resolutionStr":"([^"]*)".*|\2 >\1|p;s|.*hls","url":"([^"]*)".*"hardsub_lang":"en-US".*|\1|p')"
            ;;
    esac

    case "$episode_link" in
        *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|,|\
|g'); do
                printf "%s >%s\n" "$j" "$extract_link" | sed "s|,[^/]*|${j}|g"
            done | sort -nr
            ;;
        *master.m3u8*)
            m3u8_refr=$(printf '%s' "$response" | sed -nE 's|.*Referer":"([^"]*)".*|\1|p') && printf '%s\n' "m3u8_refr >$m3u8_refr" >"$cache_dir/m3u8_refr"
            extract_link=$(printf "%s" "$episode_link" | head -1 | cut -d'>' -f2)
            relative_link=$(printf "%s" "$extract_link" | sed 's|[^/]*$||')
            m3u8_streams="$(curl -e "$m3u8_refr" -s "$extract_link" -A "$agent")"
            printf "%s" "$m3u8_streams" | grep -q "EXTM3U" && printf "%s" "$m3u8_streams" | sed 's|^#EXT-X-STREAM.*x||g; s|,.*|p|g; /^#/d; $!N; s|\n| >|;/EXT-X-I-FRAME/d' | sed "s|>|>${relative_link}|g" | sort -nr
            ;;
        *) [ -n "$episode_link" ] && printf "%s\n" "$episode_link" ;;
    esac
    printf "\033[1;32m%s\033[0m Links Fetched\n" "$provider_name" 1>&2
}

# initialises 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-)
    printf '%s' "$provider_id" | grep -qE "^--" && provider_id=$(printf "%s" "$provider_id" | sed 's/../&\
/g' | sed 's/^--$/\n/g;s/^79$/A/g;s/^7a$/B/g;s/^7b$/C/g;s/^7c$/D/g;s/^7d$/E/g;s/^7e$/F/g;s/^7f$/G/g;s/^70$/H/g;s/^71$/I/g;s/^72$/J/g;s/^73$/K/g;s/^74$/L/g;s/^75$/M/g;s/^76$/N/g;s/^77$/O/g;s/^68$/P/g;s/^69$/Q/g;s/^6a$/R/g;s/^6b$/S/g;s/^6c$/T/g;s/^6d$/U/g;s/^6e$/V/g;s/^6f$/W/g;s/^60$/X/g;s/^61$/Y/g;s/^62$/Z/g;s/^59$/a/g;s/^5a$/b/g;s/^5b$/c/g;s/^5c$/d/g;s/^5d$/e/g;s/^5e$/f/g;s/^5f$/g/g;s/^50$/h/g;s/^51$/i/g;s/^52$/j/g;s/^53$/k/g;s/^54$/l/g;s/^55$/m/g;s/^56$/n/g;s/^57$/o/g;s/^48$/p/g;s/^49$/q/g;s/^4a$/r/g;s/^4b$/s/g;s/^4c$/t/g;s/^4d$/u/g;s/^4e$/v/g;s/^4f$/w/g;s/^40$/x/g;s/^41$/y/g;s/^42$/z/g;s/^08$/0/g;s/^09$/1/g;s/^0a$/2/g;s/^0b$/3/g;s/^0c$/4/g;s/^0d$/5/g;s/^0e$/6/g;s/^0f$/7/g;s/^00$/8/g;s/^01$/9/g;s/^15$/-/g;s/^16$/./g;s/^67$/_/g;s/^46$/~/g;s/^02$/:/g;s/^17$/\//g;s/^07$/?/g;s/^1b$/#/g;s/^63$/\[/g;s/^65$/\]/g;s/^78$/@/g;s/^19$/!/g;s/^1c$/$/g;s/^1e$/&/g;s/^10$/\(/g;s/^11$/\)/g;s/^12$/*/g;s/^13$/+/g;s/^14$/,/g;s/^03$/;/g;s/^05$/=/g;s/^1d$/%/g' | tr -d '\n' | sed "s/\/clock/\/clock\.json/")
}

# generates links based on given provider
generate_link() {
    case $1 in
        1) provider_init "wixmp" "/Default :/p" ;;    # wixmp(default)(m3u8)(multi) -> (mp4)(multi)
        2) provider_init "youtube" "/Yt-mp4 :/p" ;;   # youtube(mp4)(single)
        3) provider_init "sharepoint" "/S-mp4 :/p" ;; # sharepoint(mp4)(single)
        4) provider_init "mp4upload" "/Mp4 :/p" ;;    # mp4upload(mp4)(single)
        *) true ;;
    esac
    [ -n "$provider_id" ] && get_links "$provider_id"
}

select_quality() {
    # removing urls which have referrer to avoid playing on iSH and vlc (m3u8 streams don't get correct referrer)
    printf '%s' "$player_function" | cut -f1 -d" " | grep -qE '(android_vlc|iSH|vlc)' && links=$(printf '%s' "$links" | sed '/m3u8_refr >/d')
    printf '%s' "$player_function" | cut -f1 -d" " | grep -qE '(android_vlc|iSH)' && links=$(printf '%s' "$links" | sed '/Yt >/d')
    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\n" 1>&2 && result=$(printf "%s" "$links" | head -n1)

    # add refr for m3u8 and refr flag for yt
    case "$result" in
        *mp4upload*)
            refr_flag="--referrer=https://www.mp4upload.com"
            ;;
        *sharepoint*)
            unset refr_flag
            ;;
        *)
            refr_flag="--referrer=$allanime_refr"
            ;;
    esac

    episode=$(printf "%s" "$result" | cut -d'>' -f2)
}

process_tobeparsed() {
    if ! printf '%s' "$1" | grep -q '"tobeparsed"'; then
        printf '%s' "$1"
        return 0
    fi

    tmp="$(mktemp)"
    printf '%s' "$1" | sed -nE 's|.*"tobeparsed":"([^"]*)".*|\1|p' | base64 -d >"$tmp"
    file_size="$(wc -c <"$tmp")"
    iv="$(dd if="$tmp" bs=1 skip=1 count=12 2>/dev/null | od -A n -t x1 | tr -d ' \n')"
    gcm_len=$((file_size - 13))
    if [ "$botan_version" -eq 3 ]; then
        dd if="$tmp" bs=1 skip=13 count="$gcm_len" 2>/dev/null | "$botan_exe" cipher --decrypt --cipher='AES-256/GCM' --key="$allanime_key" --nonce="$iv" - 2>/dev/null
    else
        dd if="$tmp" bs=1 skip=13 count="$gcm_len" 2>/dev/null | "$botan_exe" encryption --decrypt --mode='aes-256-gcm' --key="$allanime_key" --iv="$iv" 2>/dev/null
    fi
    rm -f "$tmp"
}

get_aa_req() {
    ts="$(($(date +%s) / 300 * 300 * 1000))"

    payload_iv="$allanime_epoch:$allanime_buildid:$allanime_query_hash:$ts:$allanime_lane"
    payload="{\"v\":1,\"ts\":$ts,\"epoch\":$allanime_epoch,\"buildId\":\"$allanime_buildid\",\"qh\":\"$allanime_query_hash\",\"k\":\"$allanime_lane\"}"

    tmpdir="$(mktemp -d)"
    trap 'rm -rf "$tmpdir"' EXIT INT

    printf '%s' "$payload_iv" | "$botan_exe" hash --no-fsname | "$botan_exe" hex_dec - | dd bs=1 count=12 of="$tmpdir/iv.bin" 2>/dev/null

    iv_hex="$(od -An -t x1 "$tmpdir/iv.bin" | tr -d ' \n')"

    if [ "$botan_version" -eq 3 ]; then
        printf "%s" "$payload" | $botan_exe cipher --cipher=AES-256/GCM --key="$allanime_key" --nonce="$iv_hex" - >"$tmpdir/gcm_out.bin"
    else
        printf "%s" "$payload" | $botan_exe encryption --mode=aes-256-gcm --key="$allanime_key" --iv="$iv_hex" >"$tmpdir/gcm_out.bin"
    fi

    file_size="$(wc -c <"$tmpdir/gcm_out.bin")"
    ct_len=$((file_size - 16))

    dd if="$tmpdir/gcm_out.bin" of="$tmpdir/ct.bin" bs=1 skip=0 count="$ct_len" 2>/dev/null
    dd if="$tmpdir/gcm_out.bin" of="$tmpdir/tag.bin" bs=1 skip="$ct_len" count=16 2>/dev/null

    (
        printf '\001'
        cat "$tmpdir/iv.bin" "$tmpdir/ct.bin" "$tmpdir/tag.bin"
    ) | base64 -w 0
}

# gets embed urls, collects direct links into provider files, selects one with desired quality into $episode
get_episode_url() {
    query_vars="{\"showId\":\"$id\",\"translationType\":\"$mode\",\"episodeString\":\"$ep_no\"}"
    query_ext="{\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"$allanime_query_hash\"}, \"k\":\"$allanime_lane\", \"aaReq\":\"$(get_aa_req)\"}"

    api_resp="$(curl -e "$allanime_refr" -sG -A "$agent" -H "Origin: $allanime_refr" -H "x-build-id: $allanime_buildid" "${allanime_api}/api" --data-urlencode "variables=${query_vars}" --data-urlencode "extensions=${query_ext}")"

    resp="$(process_tobeparsed "$api_resp" | tr '{}' '\n' | sed 's|\\u002F|\/|g;s|\\||g' | sed -nE 's|.*sourceUrl":"([^"]*)".*sourceName":"([^"]*)".*|\2 :\1|p')"
    # generate links into sequential files
    cache_dir="$(mktemp -d)"
    providers="1 2 3 4"
    for provider in $providers; do
        generate_link "$provider" >"$cache_dir"/"$provider" &
    done
    wait
    # select the link with matching quality
    links=$(cat "$cache_dir"/* | sort -g -r -s)
    rm -r "$cache_dir"
    select_quality "$quality"
    if printf "%s" "$ep_list" | grep -q "^$ep_no$"; then
        [ -z "$episode" ] && die "Episode is released, but no valid sources!"
    else
        [ -z "$episode" ] && die "Episode not released!"
    fi
}

# search the query and give results
search_anime() {
    #shellcheck disable=SC2016
    search_gql='query( $search: SearchInput $limit: Int $page: Int $translationType: VaildTranslationTypeEnumType $countryOrigin: VaildCountryOriginEnumType ) { shows( search: $search limit: $limit page: $page translationType: $translationType countryOrigin: $countryOrigin ) { edges { _id name availableEpisodes airedStart __typename } }}'
    curl -e "$allanime_refr" -s -H "Content-Type: application/json" -X POST "${allanime_api}/api" --data "{\"variables\":{\"search\":{\"allowAdult\":false,\"allowUnknown\":false,\"query\":\"$1\"},\"limit\":40,\"page\":1,\"translationType\":\"$mode\",\"countryOrigin\":\"ALL\"},\"query\":\"$search_gql\"}" -A "$agent" | sed 's|Show|\
| g' | sed -e 's#\"airedStart\":{}#\"airedStart\":{"year":0,}#' | sed -nrE "s|.*_id\":\"([^\"]*)\",\"name\":\"([^\"]*)\",.*${mode}\":([1-9][^,]*).*(year\":([0-9][^,]*)).*|\1	\2 (\3 episodes) (\5)|p" | sed -e 's#(0)##' | sed 's/\\"//g'
}

time_until_next_ep() {
    animeschedule="https://animeschedule.net"
    query="$(printf "%s\n" "$*" | tr ' ' '+')"
    curl -s -G "$animeschedule/api/v3/anime" --data "q=${query}" | sed 's|"id"|\n|g' | sed -nE 's|.*,"route":"([^"]*)","premier.*|\1|p' | while read -r anime; do
        data=$(curl -s "$animeschedule/anime/$anime" | sed '1,/"anime-header-list-buttons-wrapper"/d' | sed -nE 's|.*countdown-time-raw" datetime="([^"]*)".*|Next Raw Release: \1|p;s|.*countdown-time" datetime="([^"]*)".*|Next Sub Release: \1|p;s|.*english-title">([^<]*)<.*|English Title: \1|p;s|.*main-title".*>([^<]*)<.*|Japanese Title: \1|p')
        status="Ongoing"
        color="33"
        printf "%s\n" "$data"
        ! (printf "%s\n" "$data" | grep -q "Next Raw Release:") && status="Finished" && color="32"
        printf "Status:  \033[1;%sm%s\033[0m\n---\n" "$color" "$status"
    done
    exit 0
}

# get the episodes list of the selected anime
episodes_list() {
    #shellcheck disable=SC2016
    episodes_list_gql='query ($showId: String!) { show( _id: $showId ) { _id availableEpisodesDetail }}'

    curl -e "$allanime_refr" -s -H "Content-Type: application/json" -X POST "${allanime_api}/api" --data "{\"variables\":{\"showId\":\"$*\"},\"query\":\"$episodes_list_gql\"}" -A "$agent" | sed -nE "s|.*$mode\":\[([0-9.\",]*)\].*|\1|p" | sed 's|,|\
|g; s|"||g' | sort -n -k 1
}

# PLAYING

process_hist_entry() {
    ep_list=$(episodes_list "$id")
    latest_ep=$(printf "%s\n" "$ep_list" | tail -n1)
    title=$(printf "%s\n" "$title" | sed "s|[0-9]\+ episodes|${latest_ep} episodes|")
    ep_no=$(printf "%s" "$ep_list" | sed -n "/^${ep_no}$/{n;p;}") 2>/dev/null
    [ -n "$ep_no" ] && printf "%s\t%s - episode %s\n" "$id" "$title" "$ep_no"
    [ -n "$ep_no" ] || printf "%s\t%s - episode %s (up to date)\n" "$id" "$title" "$latest_ep"
}

update_history() {
    if grep -q -- "$id" "$histfile"; then
        # escape sed special chars in replacement: & and \ and delimiter |
        safe_title=$(printf '%s' "$title" | sed 's/[&\|]/\\&/g')
        sed -E "s|^[^	]+	${id}	[^	]+$|${ep_no}	${id}	${safe_title}|" "$histfile" >"${histfile}.new"
    else
        cp "$histfile" "${histfile}.new"
        printf "%s\t%s\t%s\n" "$ep_no" "$id" "$title" >>"${histfile}.new"
    fi
    mv "${histfile}.new" "$histfile"
}

download() {
    case $1 in
        *m3u8*)
            if command -v "yt-dlp" >/dev/null; then
                yt-dlp --referer "$m3u8_refr" "$1" --no-skip-unavailable-fragments --fragment-retries infinite -N 16 -o "$download_dir/$2.mp4"
            else
                ffmpeg -extension_picky 0 -referer "$m3u8_refr" -loglevel error -stats -i "$1" -c copy "$download_dir/$2.mp4"
            fi
            ;;
        *)
            # shellcheck disable=SC2086
            aria2c --referer="${refr_flag#--referrer=}" --enable-rpc=false --check-certificate=false --continue $iSH_DownFix --summary-interval=0 -x 16 -s 16 "$1" --dir="$download_dir" -o "$2.mp4" --download-result=hide
            ;;
    esac
}

android_mpv() {
    dir="/storage/emulated/0/mpv"
    file="$dir/mpv.config.mp4"
    if [ -w "${dir%*/mpv}" ]; then
        [ -d "${dir}" ] || mkdir "${dir}"
        printf "%s" "$3" | sed 's| --|\n|g; s|^--||g' >"$file"
    fi
    am start --user 0 -a android.intent.action.VIEW -d "$1" -n is.xyz.mpv/.MPVActivity -e "title" "$2" >/dev/null 2>&1
    unset dir file
}

play_episode() {
    [ "$log_episode" = 1 ] && [ "$player_function" != "debug" ] && [ "$player_function" != "download" ] && command -v logger >/dev/null && logger -t ani-cli "${allanime_title}${ep_no}"
    [ "$skip_intro" = 1 ] && skip_flag="$(ani-skip -q "$mal_id" -e "$ep_no")"
    [ -z "$episode" ] && get_episode_url
    # shellcheck disable=SC2086
    case "$player_function" in
        debug)
            printf "All links:\n%s\nSelected link:\n" "$links"
            printf "%s\n" "$episode"
            ;;
        android_mpv) "$player_function" "$episode" "${allanime_title}Episode ${ep_no}" "--tls-verify=no $refr_flag" 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}" >/dev/null 2>&1 & ;;
        "$HOME"/.local/share/flatpak/app/io.mpv/Mpv/) flatpak run io.mpv.Mpv --tls-verify=no --force-media-title="${allanime_title}Episode ${ep_no}" "$episode" $refr_flag >/dev/null 2>&1 & ;;
        *mpv*)
            if [ "$no_detach" = 0 ]; then
                nohup $player_function $skip_flag --tls-verify=no --force-media-title="${allanime_title}Episode ${ep_no}" "$episode" $refr_flag >/dev/null 2>&1 &
            else
                $player_function $skip_flag $refr_flag --tls-verify=no --force-media-title="${allanime_title}Episode ${ep_no}" "$episode"
                mpv_exitcode=$?
                [ "$exit_after_play" = 1 ] && [ -z "$range" ] && exit "$mpv_exitcode"
            fi
            ;;
        *iina*)
            [ -n "$refr_flag" ] && refr_flag="--mpv-${refr_flag#--}"
            if pgrep -f "IINA" >/dev/null 2>&1; then
                # omit --keep-running when an IINA instance exists to prevent hanging
                nohup $player_function --no-stdin --mpv-tls-verify=no --mpv-force-media-title="${allanime_title}Episode ${ep_no}" $refr_flag "$episode" >/dev/null 2>&1 &
            else
                nohup $player_function --no-stdin --mpv-tls-verify=no --keep-running --mpv-force-media-title="${allanime_title}Episode ${ep_no}" $refr_flag "$episode" >/dev/null 2>&1 &
            fi
            ;;
        *vlc*) nohup $player_function --http-referrer="${refr_flag#--referrer=}" --play-and-exit --meta-title="${allanime_title}Episode ${ep_no}" "$episode" >/dev/null 2>&1 & ;;
        *yncpla*) nohup $player_function "$episode" -- --tls-verify=no --force-media-title="${allanime_title}Episode ${ep_no}" $refr_flag >/dev/null 2>&1 & ;;
        download) "$player_function" "$episode" "${allanime_title}Episode ${ep_no}" ;;
        catt) nohup catt cast "$episode" >/dev/null 2>&1 & ;;
        iSH)
            printf "\e]8;;vlc://%s\a~~~~~~~~~~~~~~~~~~~~\n~ Tap to open VLC ~\n~~~~~~~~~~~~~~~~~~~~\e]8;;\a\n" "$episode"
            sleep 5
            ;;
        *) nohup $player_function "$episode" >/dev/null 2>&1 & ;;
    esac
    replay="$episode"
    unset episode
    update_history
    [ "$use_external_menu" = "1" ] && wait
    [ "$use_external_menu" = "2" ] && wait
}

play() {
    start=$(printf "%s" "$ep_no" | grep -Eo '^(-1|[0-9]+(\.[0-9]+)?)')
    end=$(printf "%s" "$ep_no" | grep -Eo '(-1|[0-9]+(\.[0-9]+)?)$')
    [ "$start" = "-1" ] && ep_no=$(printf "%s" "$ep_list" | tail -n1) && unset start
    [ -z "$end" ] || [ "$end" = "$start" ] && unset start end
    [ "$end" = "-1" ] && end=$(printf "%s" "$ep_list" | tail -n1)
    line_count=$(printf "%s\n" "$ep_no" | wc -l | tr -d "[:space:]")
    if [ "$line_count" != 1 ] || [ -n "$start" ]; then
        [ -z "$start" ] && start=$(printf "%s\n" "$ep_no" | head -n1)
        [ -z "$end" ] && end=$(printf "%s\n" "$ep_no" | tail -n1)
        range=$(printf "%s\n" "$ep_list" | 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"
            [ "$i" = "$end" ] && unset range
            play_episode
        done
    else
        play_episode
    fi
    # moves up to stored position and deletes to end
    [ "$player_function" != "debug" ] && [ "$player_function" != "download" ] && tput rc && tput ed
}

# MAIN

# setup
agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:150.0) Gecko/20100101 Firefox/150.0"
allanime_refr="https://mkissa.to"
allanime_base="allanime.day"
allanime_api="https://api.mkissa.net"
mode="${ANI_CLI_MODE:-sub}"
download_dir="${ANI_CLI_DOWNLOAD_DIR:-.}"
log_episode="${ANI_CLI_LOG:-1}"
quality="${ANI_CLI_QUALITY:-best}"
case "$(uname -a | cut -d " " -f 1,3-)" in
    *Darwin*) player_function="${ANI_CLI_PLAYER:-$(dep_ch_failover "iina,/Applications/IINA.app/Contents/MacOS/iina-cli,mpv,vlc")}" || die 'No player found. Looked for iina, mpv and vlc' ;; # mac OS
    #TODO out of band mpv and vlc
    *ndroid*) player_function="${ANI_CLI_PLAYER:-android_mpv}" ;;                                                                                                       # Android OS (termux)
    *MINGW* | *WSL2*) player_function="${ANI_CLI_PLAYER:-mpv.exe}" ;;                                                                                                   # Windows OS
    *ish*) player_function="${ANI_CLI_PLAYER:-iSH}" ;;                                                                                                                  # iOS (iSH)
    *) player_function="${ANI_CLI_PLAYER:-$(dep_ch_failover "mpv,$HOME/.local/share/flatpak/app/io.mpv/Mpv/,vlc")}" || die 'No player found. Looked for mpv and vlc' ;; # Linux OS
esac

no_detach="${ANI_CLI_NO_DETACH:-0}"
exit_after_play="${ANI_CLI_EXIT_AFTER_PLAY:-0}"
use_external_menu="${ANI_CLI_EXTERNAL_MENU:-0}"
external_menu_normal_window="${ANI_CLI_EXTERNAL_MENU_NORMAL_WINDOW:-0}"
skip_intro="${ANI_CLI_SKIP_INTRO:-0}"
skip_title="${ANI_CLI_SKIP_TITLE:-}"
[ -t 0 ] || (command -v dmenu && use_external_menu=2)
[ -t 0 ] || (command -v rofi && use_external_menu=1)
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}"
branch="${ANI_CLI_BRANCH:-master}"

while [ $# -gt 0 ]; do
    case "$1" in
        -v | --vlc)
            case "$(uname -a | cut -d " " -f 1,3-)" in
                *ndroid*) player_function="android_vlc" ;;
                MINGW* | *WSL2*) player_function="vlc.exe" ;;
                *ish*) player_function="iSH" ;;
                *) player_function="vlc" ;;
            esac
            ;;
        -s | --syncplay)
            case "$(uname -s)" in
                Darwin*) player_function="/Applications/Syncplay.app/Contents/MacOS/syncplay" ;;
                MINGW* | *Msys)
                    export PATH="$PATH":"/c/Program Files (x86)/Syncplay/"
                    player_function="syncplay.exe"
                    ;;
                *) player_function="syncplay" ;;
            esac
            ;;
        -q | --quality)
            [ $# -lt 2 ] && die "missing argument!"
            quality="$2"
            shift
            ;;
        -S | --select-nth)
            [ $# -lt 2 ] && die "missing argument!"
            index="$2"
            shift
            ;;
        -c | --continue) search=history ;;
        -d | --download)
            [ "$player_function" = "iSH" ] && iSH_DownFix="--async-dns=false"
            player_function=download
            ;;
        -D | --delete)
            : >"$histfile"
            exit 0
            ;;
        -l | --logview)
            case "$(uname -s)" in
                Darwin*) log show --predicate 'process == "logger"' ;;
                Linux*) journalctl -t ani-cli ;;
                *) die "Logger not implemented for your platform" ;;
            esac
            exit 0
            ;;
        -V | --version) version_info ;;
        -h | --help) help_info ;;
        -e | --episode | -r | --range)
            [ $# -lt 2 ] && die "missing argument!"
            ep_no="$2"
            shift
            ;;
        --dub) mode="dub" ;;
        --no-detach) no_detach=1 ;;
        --exit-after-play) exit_after_play=1 && no_detach=1 ;;
        --rofi) use_external_menu=1 && dep_ch rofi ;;
        --dmenu) use_external_menu=2 && dep_ch dmenu ;;
        --skip) skip_intro=1 ;;
        --skip-title)
            [ $# -lt 2 ] && die "missing argument!"
            skip_title="$2"
            shift
            ;;
        -N | --nextep-countdown) search=nextep ;;
        -U | --update)
            branch="${2:-$branch}"
            update_script
            ;;
        *) query="$(printf "%s" "$query $1" | sed "s|^ ||;s| |+|g")" ;;
    esac
    shift
done
[ "$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"}"
[ "$external_menu_normal_window" = "1" ] && external_menu_args="-normal-window"
printf "\33[2K\r\033[1;34mChecking dependencies...\033[0m\n"
dep_ch "curl"
dep_ch "sed"
dep_ch "grep"
botan_exe=$(dep_ch_failover "botan3,botan,botan-cli") || die 'Program "botan" not found.'
botan_version=$("$botan_exe" --version | cut -c1)
[ "$skip_intro" = 1 ] && (dep_ch "ani-skip")
external_helper=$(dep_ch_failover "fzf,rofi,dmenu") || die 'No menu found. Install fzf, rofi or dmenu'
case "$external_helper" in
    fzf) true ;;
    rofi) [ "$use_external_menu" = "0" ] && use_external_menu=1 ;;
    dmenu) use_external_menu=2 ;;
    *) die 'No menu found. Install fzf, rofi or dmenu' ;;
esac
case "$player_function" in
    debug) ;;
    download)
        dep_ch_failover "yt-dlp,ffmpeg" >/dev/null || die 'Neither yt-dlp nor ffmpeg found'
        dep_ch "aria2c"
        ;;
    android*) printf "\33[2K\rChecking of players on Android is disabled.\nANI-CLI recommends Android MPV for full compatibility.\nAdd:\n include='/storage/emulated/0/mpv/mpv.config.mp4'\nto:\n MPV > Settings > Advanced > mpv.conf\nfor full functionality.\nIgnore, If Done.\n" ;;
    *iSH*) printf "\33[2K\rChecking of players on iOS is disabled\n" ;;
    *) dep_ch "$player_function" ;;
esac

# idk where to put this, so I put it here...
trap 'cleanup; exit 1' INT HUP

# 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 history!"
        [ -z "${index##*[!0-9]*}" ] && id=$(printf "%s" "$anime_list" | nl -w 2 | sed 's/^[[:space:]]//' | nth "Select anime: " | cut -f1)
        [ -z "${index##*[!0-9]*}" ] || id=$(printf "%s" "$anime_list" | sed -n "${index}p" | cut -f1)
        [ -z "$id" ] && exit 1
        title=$(printf "%s" "$anime_list" | grep "$id" | cut -f2 | sed 's/ - episode.*//')
        ep_list=$(episodes_list "$id")
        ep_no=$(printf "%s" "$anime_list" | grep "$id" | cut -f2 | sed -nE 's/.*- episode (.+)$/\1/p')
        allanime_title="$(printf "%s" "$title" | cut -d'(' -f1 | tr -d '[:punct:]')"
        printf "%s" "$anime_list" | grep "$id" | cut -f2 | grep -q "(up to date)" && time_until_next_ep "$allanime_title"
        ;;
    *)
        if [ "$use_external_menu" = "0" ]; then
            while [ -z "$query" ]; do
                printf "\33[2K\r\033[1;36mSearch anime: \033[0m" && read -r query
            done
        else
            [ -z "$query" ] && query=$(printf "" | external_menu "" "Search anime: " "$external_menu_args")
            [ -z "$query" ] && exit 1
        fi
        # for checking new releases by specifying anime name
        [ "$search" = "nextep" ] && time_until_next_ep "$query"

        query=$(printf "%s" "$query" | sed "s| |+|g")
        anime_list=$(search_anime "$query")
        [ -z "$anime_list" ] && die "No results found!"
        [ "$index" -eq "$index" ] 2>/dev/null && result=$(printf "%s" "$anime_list" | sed -n "${index}p")
        [ -z "$index" ] && result=$(printf "%s" "$anime_list" | nl -w 2 | sed 's/^[[:space:]]//' | nth "Select anime: ")
        [ -z "$result" ] && exit 1
        title=$(printf "%s" "$result" | cut -f2)
        allanime_title="$(printf "%s" "$title" | cut -d'(' -f1 | tr -d '[:punct:]')"
        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
[ "$skip_intro" = 1 ] && mal_id="$(ani-skip -q "${skip_title:-${title}}")"

# moves the cursor up one line and clears that line
tput cuu1 && tput el
# stores the position of cursor
tput sc

# playback & loop
play
[ "$player_function" = "download" ] || [ "$player_function" = "debug" ] && exit 0

while cmd=$(printf "next\nreplay\nprevious\nselect\nchange_quality\nquit" | nth "Playing episode $ep_no of $title... "); do
    case "$cmd" in
        next) ep_no=$(printf "%s" "$ep_list" | sed -n "/^${ep_no}$/{n;p;}") 2>/dev/null ;;
        replay) episode="$replay" ;;
        previous) 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") ;;
        change_quality)
            new_quality="$(printf "%s" "$links" | launcher | cut -d\> -f1)"
            select_quality "$new_quality"
            ;;
        *)
            cleanup
            exit 0
            ;;
    esac
    [ -z "$ep_no" ] && die "Out of range"
    play
done

# ani-cli
#
# 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/pystardust/ani-cli


Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions