From e95af6c180c8725dcad9236e1d39ac1ff65f6b78 Mon Sep 17 00:00:00 2001 From: port19 Date: Sat, 23 May 2026 01:00:10 +0200 Subject: [PATCH 1/4] docs: bump min version to 4.12 for packaging status (#1663) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fc73608da..4bb274036 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ If after this the issue persists then open an issue. ## Install -[![Packaging status](https://repology.org/badge/vertical-allrepos/ani-cli.svg?minversion=4.0)](https://repology.org/project/ani-cli/versions) +[![Packaging status](https://repology.org/badge/vertical-allrepos/ani-cli.svg?minversion=4.14)](https://repology.org/project/ani-cli/versions) ### Tier 1 Support: Linux, Mac, Android From b8032b72901721a1ce859ca2816e8e2c914bc616 Mon Sep 17 00:00:00 2001 From: Tanveer Ahmed Ansari Date: Sat, 23 May 2026 14:19:28 +0530 Subject: [PATCH 2/4] feat: mp4upload provider, refactor, better mpv android support (#1704) Coolnsx (2026-05-23): > I want to mention about mp4upload. > Requires referrer and tls verify to none only works with mpv and mpv frontends like iina. VLC is not working with this provider. iSH plebs may suffer playback issues and the people that uses VLC to watch anime, sorry in advance. > current state of providers > sharepoint is unstable for past few weeks. youtube one is working, but it requires referrer. hianime is gone for good (the code for this is removed to rule out this provider) wixmp is only on old anime (anime that release before 2020 I guess). mp4upload requires that referrer and tls-verify to none. --- README.md | 12 ++++ ani-cli | 171 ++++++++++++++++++++++++++++++++---------------------- 2 files changed, 113 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index 4bb274036..67b471ab8 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,18 @@ For players you can use the apk (playstore/fdroid) versions of mpv and vlc. Note pkg install openssl-tool ``` +**Important Note:** To get all providers working with android MPV, Please follow below steps: +- Run this command and allow storage permissions: +```sh +termux-setup-storage +``` +- Go to MPV > Settings > Advanced > mpv.conf +- add this line: +```txt +include="/storage/emulated/0/mpv/mpv.config.mp4" +``` +- Make sure to have storage (photos and videos on newer android) permission allowed to both MPV and termux. These permissions are asked by mpv if you click on the "file picker (legacy)" option. + ### Tier 2 Support: Windows, WSL, iOS, Steam Deck, FreeBSD diff --git a/ani-cli b/ani-cli index 1ef06a793..37a18368c 100755 --- a/ani-cli +++ b/ani-cli @@ -1,6 +1,6 @@ #!/bin/sh -version_number="4.14.0" +version_number="4.14.1" # UI @@ -105,15 +105,17 @@ version_info() { } update_script() { - update="$(curl -s -A "$agent" "https://raw.githubusercontent.com/pystardust/ani-cli/master/ani-cli")" || die "Connection error" + 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 "Script is up to date :)\n" + printf "[branch:%s] Script is up to date :)\n" "$branch" else if printf '%s\n' "$update" | patch "$0" -; then - printf "Script has been updated\n" + printf "[branch:%s] Script has been updated\n" "$branch" else - die "Can't update for some reason!" + die "[branch:$branch] Can't update for some reason!" fi fi exit 0 @@ -136,13 +138,29 @@ where_mpv() { printf "%s" "mpv" && return 0 } +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() { - 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')" + 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*) @@ -157,22 +175,19 @@ get_links() { 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|>|cc>${relative_link}|g" | sort -nr - printf '%s' "$response" | sed -nE 's|.*"subtitles":\[\{"lang":"en","label":"English","default":"default","src":"([^"]*)".*|subtitle >\1|p' >"$cache_dir/suburl" + 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 "%s" "$*" | grep -q "tools.fast4speed.rsvp" && printf "%s\n" "Yt >$*" 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 | sed 's/../&\ -/g' | sed '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/") + 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 @@ -181,20 +196,20 @@ generate_link() { 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) - 5) provider_init "filemoon" "/Fm-mp4 :/p" ;; # filemoon(m3u8)(single) - *) provider_init "hianime" "/Luf-Mp4 :/p" ;; # hianime(m3u8)(multi) + 4) provider_init "mp4upload" "/Mp4 :/p" ;; # mp4upload(mp4)(single) + *) + # provider_init "Filemoon" "/Fm-Hls :/p" + # get_filemoon_links "$provider_id" + # return 0 + ;; # filemoon(m3u8)(single) esac - if [ "$1" = "5" ] && [ -n "$provider_id" ]; then - get_filemoon_links "$provider_id" - else - [ -n "$provider_id" ] && get_links "$provider_id" - fi + [ -n "$provider_id" ] && get_links "$provider_id" } select_quality() { - # removing urls which have soft subs to avoid playing on android, iSH and vlc (m3u8 streams don't get correct referrer) - printf '%s' "$player_function" | cut -f1 -d" " | grep -qE '(android|iSH|vlc)' && links=$(printf '%s' "$links" | sed '/cc>/d;/subtitle >/d;/m3u8_refr >/d') - printf '%s' "$player_function" | cut -f1 -d" " | grep -qE '(android|iSH)' && links=$(printf '%s' "$links" | sed '/Yt >/d') + # 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) ;; @@ -202,27 +217,36 @@ select_quality() { esac [ -z "$result" ] && printf "Specified quality not found, defaulting to best\n" 1>&2 && result=$(printf "%s" "$links" | head -n1) - # add refr,sub flags for m3u8 and refr flag for yt - printf '%s' "$result" | grep -q "cc>" && subtitle="$(printf '%s' "$links" | sed -nE 's|subtitle >(.*)|\1|p')" && - [ -n "$subtitle" ] && subs_flag="--sub-file=$subtitle" - printf '%s' "$result" | grep -q "cc>" && m3u8_refr="$(printf '%s' "$links" | sed -nE 's|m3u8_refr >(.*)|\1|p')" && refr_flag="--referrer=$m3u8_refr" - printf "%s" "$result" | grep -q "tools.fast4speed.rsvp" && refr_flag="--referrer=$allanime_refr" + # 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 - ! (printf '%s' "$result" | grep -qE "(cc>|tools.fast4speed.rsvp)") && unset refr_flag - ! (printf '%s' "$result" | grep -q "cc>") && unset subs_flag episode=$(printf "%s" "$result" | cut -d'>' -f2) } -decode_tobeparsed() { +process_response() { + if ! printf '%s' "$1" | grep -q '"tobeparsed"'; then + printf '%s' "$1" + return 0 + fi + tmp="$(mktemp)" - printf '%s' "$1" | base64 -d >"$tmp" + 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')" ctr="${iv}00000002" ct_len=$((file_size - 13 - 16)) - plain="$(dd if="$tmp" bs=1 skip=13 count="$ct_len" 2>/dev/null | openssl enc -d -aes-256-ctr -K "$allanime_key" -iv "$ctr" -nosalt -nopad 2>/dev/null)" + dd if="$tmp" bs=1 skip=13 count="$ct_len" 2>/dev/null | openssl enc -d -aes-256-ctr -K "$allanime_key" -iv "$ctr" -nosalt -nopad 2>/dev/null rm -f "$tmp" - printf '%s' "$plain" | tr '{}' '\n' | sed -nE 's|.*"sourceUrl":"--([^"]*)".*"sourceName":"([^"]*)".*|\2 :\1|p' } b64url_to_hex() { @@ -274,26 +298,17 @@ get_episode_url() { query_vars="{\"showId\":\"$id\",\"translationType\":\"$mode\",\"episodeString\":\"$ep_no\"}" query_ext="{\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"$query_hash\"}}" - encoded_vars=$(printf '%s' "$query_vars" | sed 's/"/%22/g; s/:/%3A/g; s/{/%7B/g; s/}/%7D/g; s/,/%2C/g') - encoded_ext=$(printf '%s' "$query_ext" | sed 's/"/%22/g; s/:/%3A/g; s/{/%7B/g; s/}/%7D/g; s/,/%2C/g; s/ /%20/g') - - api_url="${allanime_api}/api?variables=${encoded_vars}&extensions=${encoded_ext}" - - api_resp="$(curl -e "https://youtu-chan.com" -s -A "$agent" -H "Origin: https://youtu-chan.com" "$api_url")" + api_resp="$(curl -e "$allanime_refr" -sG -A "$agent" -H "Origin: ${allanime_refr}" "${allanime_api}/api" --data-urlencode "variables=${query_vars}" --data-urlencode "extensions=${query_ext}")" if [ -z "$api_resp" ] || ! printf "%s" "$api_resp" | grep -q "tobeparsed"; then api_resp="$(curl -e "$allanime_refr" -s -H "Content-Type: application/json" -X POST "${allanime_api}/api" --data "{\"variables\":{\"showId\":\"$id\",\"translationType\":\"$mode\",\"episodeString\":\"$ep_no\"},\"query\":\"$episode_embed_gql\"}" -A "$agent")" fi - if printf "%s" "$api_resp" | grep -q '"tobeparsed"'; then - blob="$(printf "%s" "$api_resp" | sed -nE 's|.*"tobeparsed":"([^"]*)".*|\1|p')" - resp="$(decode_tobeparsed "$blob")" - else - resp="$(printf "%s" "$api_resp" | tr '{}' '\n' | sed 's|\\u002F|\/|g;s|\\||g' | sed -nE 's|.*sourceUrl":"--([^"]*)".*sourceName":"([^"]*)".*|\2 :\1|p')" - fi + resp="$(process_response "$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 5" + #providers="1 2 3 4 5" + providers="1 2 3 4" for provider in $providers; do generate_link "$provider" >"$cache_dir"/"$provider" & done @@ -362,8 +377,6 @@ update_history() { } download() { - # download subtitle if it's set - [ -n "$subtitle" ] && curl -s "$subtitle" -o "$download_dir/$2.vtt" case $1 in *m3u8*) if command -v "yt-dlp" >/dev/null; then @@ -371,16 +384,25 @@ download() { else ffmpeg -extension_picky 0 -referer "$m3u8_refr" -loglevel error -stats -i "$1" -c copy "$download_dir/$2.mp4" fi - # embed subs into downloads - # [ -e "$download_dir/$2.vtt" ] && ffmpeg -i "$download_dir/$2.mp4" -i "$download_dir/$2.vtt" -c copy -c:s mov_text "$download_dir/$2.bak.mp4" && mv "$download_dir/$2.bak.mp4" "$download_dir/$2.mp4" ;; *) # shellcheck disable=SC2086 - aria2c --referer="$allanime_refr" --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 + 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")" @@ -393,30 +415,29 @@ play_episode() { ;; mpv*) if [ "$no_detach" = 0 ]; then - nohup $player_function $skip_flag --force-media-title="${allanime_title}Episode ${ep_no}" "$episode" $subs_flag $refr_flag >/dev/null 2>&1 & + 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 $subs_flag $refr_flag --force-media-title="${allanime_title}Episode ${ep_no}" "$episode" + $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 ;; - android_mpv) nohup am start --user 0 -a android.intent.action.VIEW -d "$episode" -n is.xyz.mpv/.MPVActivity >/dev/null 2>&1 & ;; + 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 & ;; *iina*) - [ -n "$subs_flag" ] && subs_flag="--mpv-${subs_flag#--}" [ -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-force-media-title="${allanime_title}Episode ${ep_no}" $subs_flag $refr_flag "$episode" >/dev/null 2>&1 & + 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 --keep-running --mpv-force-media-title="${allanime_title}Episode ${ep_no}" $subs_flag $refr_flag "$episode" >/dev/null 2>&1 & + 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 ;; - flatpak_mpv) flatpak run io.mpv.Mpv --force-media-title="${allanime_title}Episode ${ep_no}" "$episode" $subs_flag $refr_flag >/dev/null 2>&1 & ;; - vlc*) nohup $player_function --http-referrer="${allanime_refr}" --play-and-exit --meta-title="${allanime_title}Episode ${ep_no}" "$episode" >/dev/null 2>&1 & ;; - *yncpla*) nohup $player_function "$episode" -- --force-media-title="${allanime_title}Episode ${ep_no}" $subs_flag $refr_flag >/dev/null 2>&1 & ;; - download) "$player_function" "$episode" "${allanime_title}Episode ${ep_no}" "$subtitle" ;; - catt) nohup catt cast "$episode" -s "$subtitle" >/dev/null 2>&1 & ;; + flatpak_mpv) flatpak run io.mpv.Mpv --tls-verify=no --force-media-title="${allanime_title}Episode ${ep_no}" "$episode" $refr_flag >/dev/null 2>&1 & ;; + 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 @@ -459,8 +480,8 @@ play() { # MAIN # setup -agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0" -allanime_refr="https://allmanga.to" +agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:150.0) Gecko/20100101 Firefox/150.0" +allanime_refr="https://youtu-chan.com" allanime_base="allanime.day" allanime_api="https://api.${allanime_base}" allanime_key="$(printf '%s' 'Xot36i3lK3:v1' | openssl dgst -sha256 -binary | od -A n -t x1 | tr -d ' \n')" @@ -489,6 +510,7 @@ hist_dir="${ANI_CLI_HIST_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}/ani-cli}" 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 @@ -556,7 +578,10 @@ while [ $# -gt 0 ]; do shift ;; -N | --nextep-countdown) search=nextep ;; - -U | --update) update_script ;; + -U | --update) + branch="${2:-$branch}" + update_script + ;; *) query="$(printf "%s" "$query $1" | sed "s|^ ||;s| |+|g")" ;; esac shift @@ -575,12 +600,15 @@ dep_ch "fzf" || true case "$player_function" in debug) ;; download) dep_ch "ffmpeg" "aria2c" ;; - android*) printf "\33[2K\rChecking of players on Android is disabled\n" ;; + 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" ;; flatpak_mpv) true ;; # handled out of band in where_mpv *) 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) @@ -642,7 +670,10 @@ while cmd=$(printf "next\nreplay\nprevious\nselect\nchange_quality\nquit" | nth new_quality="$(printf "%s" "$links" | launcher | cut -d\> -f1)" select_quality "$new_quality" ;; - *) exit 0 ;; + *) + cleanup + exit 0 + ;; esac [ -z "$ep_no" ] && die "Out of range" play From 8df665a8ace81376b2c2b3ab3d54a76062e70098 Mon Sep 17 00:00:00 2001 From: pucci Date: Sun, 24 May 2026 00:11:15 +0900 Subject: [PATCH 3/4] test: cover new mp4upload provider paths from upstream b8032b7 Upstream's mp4upload refactor removed the sharepoint/hianime/wixmp/youtube provider functions and added an mp4upload branch. Existing bash unit tests no longer reach 10 lines of script that previously contributed to the pure-coverage ratchet (78 -> 68). Pins the new behavior: - select_quality's per-provider refr_flag dispatch (mp4upload sets a literal mp4upload.com referer; sharepoint unsets refr_flag; everything else falls back to allanime_refr). - cleanup() - new helper invoked on SIGINT and normal exit. - process_response() - replaces decode_tobeparsed; non-encrypted responses short-circuit through a pass-through path. - provider_init's new raw-value branch - triggered when the matched line does not start with `--` (mp4upload and youtube providers). Also aligns lefthook.yml's shellcheck invocation with the two-pass setup in .github/workflows/bash.yml. Lefthook previously ran a single flag-less `shellcheck {staged_files}`, which flagged SC2034 / SC2154 / SC2030 / SC2031 on every bats file (test-context noise from the loader's sourced-globals pattern, already suppressed in CI). With that mismatch no .bats file could be committed without LEFTHOOK=0 - now local mirrors CI. --- lefthook.yml | 23 ++++-- tests/bash/unit/mp4upload_refactor.bats | 103 ++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 tests/bash/unit/mp4upload_refactor.bats diff --git a/lefthook.yml b/lefthook.yml index 7aa90cc42..910a15412 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -41,12 +41,23 @@ pre-commit: root: "gui/backend/" run: cargo fmt --all -- --check - shellcheck: - # No root: shellcheck is invoked from the repo root with the file - # list, so the glob and the {staged_files} substitution both stay - # at repo-root scope. - glob: "{ani-cli,tests/bash/**/*.{sh,bats},tests/arch/*.sh,tools/*.sh}" - run: shellcheck {staged_files} + # Two shellcheck passes mirror .github/workflows/bash.yml so local + # runs disagree with CI as little as possible. The disable lists are + # documented there — same noise (unused globals, subshell-scoped + # exports) the bats / sourced-script pattern legitimately produces. + shellcheck-sh: + # ani-cli and the POSIX-sh helpers run under /bin/sh. + glob: "{ani-cli,tests/bash/helpers/**/*.sh,tests/arch/*.sh,tools/*.sh}" + run: shellcheck -s sh -o all -e 2250 -e SC2034 -e SC2086 -e SC2312 {staged_files} + + shellcheck-bash: + # *.bats and *.bash helpers run under bash with the loader's globals. + glob: "tests/bash/**/*.{bats,bash}" + run: | + shellcheck -s bash \ + -e SC2034 -e SC2086 -e SC2154 -e SC2317 \ + -e SC2030 -e SC2031 -e SC2163 -e SC2181 -e SC2016 \ + {staged_files} pre-push: parallel: true diff --git a/tests/bash/unit/mp4upload_refactor.bats b/tests/bash/unit/mp4upload_refactor.bats new file mode 100644 index 000000000..30143253b --- /dev/null +++ b/tests/bash/unit/mp4upload_refactor.bats @@ -0,0 +1,103 @@ +#!/usr/bin/env bats +# +# Unit tests covering the new code paths upstream landed in pystardust/ani-cli +# commit b8032b7 (mp4upload provider, refactor): +# +# - select_quality's new per-provider refr_flag dispatch +# (mp4upload sets a literal mp4upload.com referer; sharepoint unsets +# refr_flag; everything else falls back to allanime_refr). +# - cleanup() — new top-level helper invoked on SIGINT and normal exit. +# - process_response() — replaces the old decode_tobeparsed entrypoint; +# non-encrypted responses short-circuit through a pass-through path. +# - provider_init's new "raw provider_id" branch — when the matched line +# does NOT start with "--", the hex-decode pipeline is skipped and the +# raw value is used directly. +# +# These tests pin the new behavior so future upstream syncs that touch the +# same surface flag intentional changes. + +load '../helpers/loader' + +setup() { + source_ani_cli_lib + unset episode subs_flag refr_flag subtitle m3u8_refr + player_function='mpv' +} + +@test "select_quality: mp4upload result sets the literal mp4upload.com referer" { + allanime_refr='https://allmanga.to' + links=$'1080 >https://example.mp4upload.com/v/abc' + select_quality "best" + [ "$episode" = "https://example.mp4upload.com/v/abc" ] + [ "$refr_flag" = "--referrer=https://www.mp4upload.com" ] +} + +@test "select_quality: sharepoint result unsets refr_flag entirely" { + allanime_refr='https://allmanga.to' + refr_flag='--referrer=stale-from-prior-call' + links=$'1080 >https://my.sharepoint.com/x/y/z.mp4' + select_quality "best" + [ "$episode" = "https://my.sharepoint.com/x/y/z.mp4" ] + [ -z "${refr_flag-}" ] +} + +@test "select_quality: default provider falls back to allanime_refr" { + allanime_refr='https://allmanga.to' + links=$'1080 >https://wixmp-cdn.example/v.mp4' + select_quality "best" + [ "$episode" = "https://wixmp-cdn.example/v.mp4" ] + [ "$refr_flag" = "--referrer=https://allmanga.to" ] +} + +@test "cleanup: removes histfile.new and emits the SGR reset sequence" { + tmp_hist="$(mktemp)" + histfile="$tmp_hist" + : >"${histfile}.new" + [ -f "${histfile}.new" ] + output=$(cleanup) + [ ! -f "${histfile}.new" ] + # The function prints exactly one ANSI reset sequence (`\033[0m`) to + # stdout — no trailing newline. + printf '%s' "$output" | grep -q $'\033\[0m' + rm -f "$tmp_hist" +} + +@test "cleanup: android_mpv player path truncates the mpv config bridge file" { + # The android_mpv branch writes to a fixed Termux path + # (/storage/emulated/0/mpv/mpv.config.mp4) that does not exist on a CI + # runner. We only assert that cleanup() does not error when the path is + # unwritable — the `:` builtin's redirect failure must be tolerated by + # the surrounding `&&` chain so the function still proceeds to the + # color reset and histfile cleanup. + tmp_hist="$(mktemp)" + histfile="$tmp_hist" + player_function='android_mpv' + : >"${histfile}.new" + run cleanup + [ "$status" -eq 0 ] + [ ! -f "${histfile}.new" ] + rm -f "$tmp_hist" +} + +@test "process_response: non-tobeparsed input passes through unchanged" { + payload='{"data":{"episode":{"sourceUrls":[{"sourceUrl":"--abc","sourceName":"wixmp"}]}}}' + output=$(process_response "$payload") + [ "$output" = "$payload" ] +} + +@test "process_response: empty input returns empty (non-tobeparsed path)" { + output=$(process_response "") + [ -z "$output" ] +} + +@test "provider_init: takes provider_id raw when the matched value lacks the -- prefix" { + # Old behavior: every matched line was assumed to be `--` + # and run through the hex-to-ascii decode table. The new behavior in + # upstream b8032b7 only triggers that decode when the value starts + # with `--`; otherwise the value is taken as-is, which is what the + # mp4upload and youtube providers now deliver. + resp=$'/Mp4 :https://example.mp4upload.com/v/raw123\n/Other :rest' + provider_init "mp4upload" "/Mp4 :/p" + [ "$provider_name" = "mp4upload" ] + [ "$provider_id" = "https://example.mp4upload.com/v/raw123" ] +} From b882e948439f14c190049f4d7286b122bc339a98 Mon Sep 17 00:00:00 2001 From: pucci Date: Sun, 24 May 2026 00:20:15 +0900 Subject: [PATCH 4/4] test: cover get_links fast4speed.rsvp dispatch branch Closes the remaining coverage-ratchet gap (76 -> 78) after the previous test commit. The fast4speed.rsvp case is the cleanest of the new get_links branches to unit-test because it short-circuits before any curl, so the assertion is a pure string round-trip. --- tests/bash/unit/mp4upload_refactor.bats | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/bash/unit/mp4upload_refactor.bats b/tests/bash/unit/mp4upload_refactor.bats index 30143253b..45dbd6c02 100644 --- a/tests/bash/unit/mp4upload_refactor.bats +++ b/tests/bash/unit/mp4upload_refactor.bats @@ -90,6 +90,15 @@ setup() { [ -z "$output" ] } +@test "get_links: tools.fast4speed.rsvp URL prints a Yt-tagged link without making a network call" { + # The new dispatch in get_links short-circuits the fast4speed.rsvp + # case before any curl runs. Verify the resulting stdout contains the + # Yt-tagged URL the downstream player pipeline expects. + provider_name="fast4speed" + output=$(get_links "https://tools.fast4speed.rsvp/abc123" 2>/dev/null) + [ "$output" = "Yt >https://tools.fast4speed.rsvp/abc123" ] +} + @test "provider_init: takes provider_id raw when the matched value lacks the -- prefix" { # Old behavior: every matched line was assumed to be `--` # and run through the hex-to-ascii decode table. The new behavior in