diff --git a/.github/workflows/ani-cli.yml b/.github/workflows/ani-cli.yml index 884fc4376..69292b5a4 100644 --- a/.github/workflows/ani-cli.yml +++ b/.github/workflows/ani-cli.yml @@ -4,7 +4,7 @@ on: branches: - master pull_request: - paths: + paths: - "**ani-cli" jobs: @@ -12,18 +12,20 @@ jobs: name: Shellcheck + Shfmt runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Run the sh-checker uses: luizm/action-sh-checker@master env: SHELLCHECK_OPTS: -s sh -o all -e 2250 SHFMT_OPTS: -i 4 -ci -d + with: + sh_checker_exclude: "_ani-cli-bash _ani-cli-zsh" check-exec: name: Executable Bit runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: test exec bit run: test -x "./ani-cli" @@ -31,6 +33,6 @@ jobs: name: No Awk Allowed runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: verify that noone added awk run: '! grep awk "./ani-cli"' diff --git a/.github/workflows/inverse-ani.yml b/.github/workflows/inverse-ani.yml index c19f9cd2a..75bb5076a 100644 --- a/.github/workflows/inverse-ani.yml +++ b/.github/workflows/inverse-ani.yml @@ -5,7 +5,7 @@ name: 'ani-cli checks' on: pull_request: paths-ignore: - - "**ani-cli" + - "/ani-cli" jobs: sh-checker: name: Shellcheck + Shfmt diff --git a/.github/workflows/markdown.yml b/.github/workflows/markdown.yml index 0dacb020b..6bc210753 100644 --- a/.github/workflows/markdown.yml +++ b/.github/workflows/markdown.yml @@ -22,5 +22,5 @@ jobs: name: Find Trailing Whitespace runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ocular-d/trailing-spaces@0.0.2 diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml index d8ce20d21..5bdb93046 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -2,14 +2,14 @@ name: 'ani-cli checks' on: pull_request: paths: - - "**ani-cli" + - "/ani-cli" jobs: version-bump: name: Version Bump runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: check version bump diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7e3919dec..7eeb31147 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,32 +20,3 @@ - Take part in troubleshooting and testing - Star the repo - Follow the maintainers - -## Development with nix - -When you develop with nix you can use the [dev shell](https://github.com/pystardust/ani-cli#nix-shell). - -To run the dev shell you can run the following command in the repository root: -```shell -nix-shell -``` - -The dev shell includes the following packages: -- runtime dependencies of ani-cli -- shfmt -- shellcheck - -Its also possible to use alternative packages for the video player or add features with this command: -```shell -nix-shell --arg true -``` -These are the packages available in the dev shell: -- `withVlc` -- `withIina` -- `chromecastSupport` -- `syncSupport` - -Just chain these commands together when you wanna multiple features for example: -```shell -nix-shell --arg withVlc true --arg chromecastSupport true -``` diff --git a/README.md b/README.md index 7225be8ca..23f700f0a 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@

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

@@ -39,8 +39,12 @@ A cli to browse and watch anime (alone AND with friends). This tool scrapes the - [Tier 2: Windows, WSL, iOS, Steam Deck, FreeBSD](#tier-2-support-windows-wsl-ios-steam-deck-FreeBSD) - [From Source](#installing-from-source) - [Uninstall](#uninstall) +- [Completion](#completion) + - [bash](#bash) + - [zsh](#zsh) - [Dependencies](#dependencies-1) - [Ani-Skip](#ani-skip) +- [FAQ](#faq) - [Homies](#homies) - [Contribution Guidelines](./CONTRIBUTING.md) - [Disclaimer](./disclaimer.md) @@ -495,6 +499,24 @@ apk del grep sed curl fzf git aria2 ffmpeg ncurses +## Completion + +### bash + +To add tab completions using bash run the following command inside the ani-cli directory +``` +cp _ani-cli-bash /path/to/your/completions +echo "source /path/to/your/completions/_ani-cli-bash" >> ~/.bashrc +``` + +### zsh + +To add tab completions using zsh run the following command inside the ani-cli directory +``` +cp _ani-cli-zsh /path/to/your/completions +echo "source /path/to/your/completions/_ani-cli-zsh" >> ~/.zshrc +``` + ## Dependencies - grep @@ -521,9 +543,26 @@ Ani-skip uses the external lua script function of mpv and as such – for now **Note:** It may be, that ani-skip won't know the anime you're trying to watch. Try using the `--skip-title ` command line argument. (It uses the [aniskip API](https://github.com/lexesjan/typescript-aniskip-extension/tree/main/src/api/aniskip-http-client) and you can contribute missing anime or ask for including it in the database on their [discord server](https://discord.com/invite/UqT55CbrbE)). +## FAQ +<details> + +* Can I change subtitle language or turn them off? - No, the subtitles are baked into the video. +* Can I watch dub? - Yes, use `--dub`. +* Can I change dub language? - No. +* Can I change media source? - No (unless you can scrape that source yourself). +* Can I use vlc? - Yes, use `--vlc` or `export ANI_CLI_PLAYER=vlc`. +* Can I adjust resolution? - Yes, use `-q resolution`, for example `ani-cli -q 1080`. +* How can I download? - Use `-d`, it will download into your working directory. +* Can i change download folder? - Yes, set the `ANI_CLI_DOWNLOAD_DIR` to your desired location. +* How can I bulk download? - `Use -d -e firstepisode-lastepisode`, for example `ani-cli onepiece -d -e 1-1000`. + +**Note:** All features are documented in `ani-cli --help`. + +</details> + ## Homies -* [animdl](https://github.com/justfoolingaround/animdl): Ridiculously efficient, fast and light-weight (supports most sources: allanime, zoro ... (Python) +* [animdl](https://github.com/justfoolingaround/animdl): Ridiculously efficient, fast and light-weight (supports most sources: allmanga, zoro ... (Python) * [jerry](https://github.com/justchokingaround/jerry): stream anime with anilist tracking and syncing, with discord presence (Shell) * [anipy-cli](https://github.com/sdaqo/anipy-cli): ani-cli rewritten in python (Python) * [Dantotsu](https://github.com/rebelonion/Dantotsu): Rebirth of Saikou, Best android app for anime/manga/LN with anilist integration (Kotlin) @@ -534,3 +573,5 @@ Ani-skip uses the external lua script function of mpv and as such – for now * [redqu](https://github.com/port19x/redqu): A media centric reddit client (Clojure) * [doccli](https://github.com/TowarzyszFatCat/doccli): A cli to watch anime with POLISH subtitles (Python) * [GoAnime](https://github.com/alvarorichard/GoAnime): A CLI tool to browse, play, and download anime in Portuguese(Go) +* [Curd](https://github.com/Wraient/curd): A CLI tool to watch anime with Anilist, Discord RPC, Skip Intro/Outro/Filler/Recap (Go) +- [FastAnime](https://github.com/Benex254/FastAnime): browser anime experience from the terminal (Python) diff --git a/ani-cli b/ani-cli index 7eadcefdd..0458eaa5a 100755 --- a/ani-cli +++ b/ani-cli @@ -1,6 +1,6 @@ #!/bin/sh -version_number="4.9.0" +version_number="4.9.6" # UI @@ -116,10 +116,13 @@ dep_ch() { } where_iina() { - [ -n "$ANI_CLI_PLAYER" ] && printf "%s" "$ANI_CLI_PLAYER" && return 0 - command -v iina >/dev/null && printf "iina" && return 0 [ -e "/Applications/IINA.app/Contents/MacOS/iina-cli" ] && echo "/Applications/IINA.app/Contents/MacOS/iina-cli" && return 0 - dep_ch iina # exit with formating + printf "%s" "iina" && return 0 +} + +where_mpv() { + command -v "flatpak" >/dev/null && flatpak info io.mpv.Mpv >/dev/null 2>&1 && printf "%s" "flatpak_mpv" && return 0 + printf "%s" "mpv" && return 0 } # SCRAPING @@ -130,6 +133,13 @@ get_links() { |g' | sed -nE 's|.*link":"([^"]*)".*"resolutionStr":"([^"]*)".*|\2 >\1|p;s|.*hls","url":"([^"]*)".*"hardsub_lang":"en-US".*|\1|p')" 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 + ;; *vipanicdn* | *anifastcdn*) if printf "%s" "$episode_link" | head -1 | grep -q "original.m3u"; then printf "%s" "$episode_link" @@ -145,7 +155,7 @@ get_links() { [ -z "$ANI_CLI_NON_INTERACTIVE" ] && printf "\033[1;32m%s\033[0m Links Fetched\n" "$provider_name" 1>&2 } -# innitialises provider_name and provider_id. First argument is the provider name, 2nd is the regex that matches that provider's link +# 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/../&\ @@ -155,9 +165,10 @@ provider_init() { # generates links based on given provider generate_link() { case $1 in - 1) provider_init "dropbox" "/Sak :/p" ;; # dropbox(mp4)(single) - 2) provider_init "wetransfer" "/Kir :/p" ;; # wetransfer(mp4)(single) - 3) provider_init "sharepoint" "/S-mp4 :/p" ;; # sharepoint(mp4)(single) + 1) provider_init "wixmp" "/Default :/p" ;; # wixmp(default)(m3u8)(multi) -> (mp4)(multi) + 2) provider_init "dropbox" "/Sak :/p" ;; # dropbox(mp4)(single) + 3) provider_init "wetransfer" "/Kir :/p" ;; # wetransfer(mp4)(single) + 4) provider_init "sharepoint" "/S-mp4 :/p" ;; # sharepoint(mp4)(single) *) provider_init "gogoanime" "/Luf-mp4 :/p" ;; # gogoanime(m3u8)(multi) esac [ -n "$provider_id" ] && get_links "$provider_id" @@ -181,7 +192,7 @@ get_episode_url() { resp=$(curl -e "$allanime_refr" -s -G "${allanime_api}/api" --data-urlencode "variables={\"showId\":\"$id\",\"translationType\":\"$mode\",\"episodeString\":\"$ep_no\"}" --data-urlencode "query=$episode_embed_gql" -A "$agent" | 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" + providers="1 2 3 4 5" for provider in $providers; do generate_link "$provider" >"$cache_dir"/"$provider" & done @@ -190,7 +201,11 @@ get_episode_url() { links=$(cat "$cache_dir"/* | sed 's|^Mp4-||g;/http/!d;/Alt/d' | sort -g -r -s) rm -r "$cache_dir" episode=$(select_quality "$quality") - [ -z "$episode" ] && die "Episode not released!" + 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 @@ -198,12 +213,12 @@ search_anime() { 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 __typename } }}" curl -e "$allanime_refr" -s -G "${allanime_api}/api" --data-urlencode "variables={\"search\":{\"allowAdult\":false,\"allowUnknown\":false,\"query\":\"$1\"},\"limit\":40,\"page\":1,\"translationType\":\"$mode\",\"countryOrigin\":\"ALL\"}" --data-urlencode "query=$search_gql" -A "$agent" | sed 's|Show|\ -|g' | sed -nE "s|.*_id\":\"([^\"]*)\",\"name\":\"([^\"]*)\".*${mode}\":([1-9][^,]*).*|\1 \2 (\3 episodes)|p" +| g' | sed -nE "s|.*_id\":\"([^\"]*)\",\"name\":\"(.+)\",.*${mode}\":([1-9][^,]*).*|\1 \2 (\3 episodes)|p" | sed 's/\\"//g' } time_until_next_ep() { animeschedule="https://animeschedule.net" - curl -s -G "$animeschedule/api/v3/anime" --data-urlencode "q=$1" | sed 's|"id"|\n|g' | sed -nE 's|.*,"route":"([^"]*)","premier.*|\1|p' | while read -r anime; do + curl -s -G "$animeschedule/api/v3/anime" --data "q=$1" | 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" @@ -234,7 +249,7 @@ process_hist_entry() { update_history() { if grep -q -- "$id" "$histfile"; then - sed -E "s|^[^\t]+\t${id}\t[^\t]+$|${ep_no}\t${id}\t${title}|" "$histfile" >"${histfile}.new" + sed -E "s|^[^ ]+ ${id} [^ ]+$|${ep_no} ${id} ${title}|" "$histfile" >"${histfile}.new" else cp "$histfile" "${histfile}.new" printf "%s\t%s\t%s\n" "$ep_no" "$id" "$title" >>"${histfile}.new" @@ -317,7 +332,7 @@ play() { else play_episode fi - # moves upto stored positon and deletes to end + # moves up to stored position and deletes to end [ "$player_function" != "debug" ] && [ "$player_function" != "download" ] && tput rc && tput ed } @@ -325,20 +340,19 @@ play() { # setup agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0" -allanime_refr="https://allanime.to" +allanime_refr="https://allmanga.to" allanime_base="allanime.day" allanime_api="https://api.${allanime_base}" 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)" in - *Darwin*) player_function="$(where_iina)" ;; # mac OS +case "$(uname -a | cut -d " " -f 1,3-)" in + *Darwin*) player_function="${ANI_CLI_PLAYER:-$(where_iina)}" ;; # mac OS *ndroid*) player_function="${ANI_CLI_PLAYER:-android_mpv}" ;; # Android OS (termux) - *neptune*) player_function="${ANI_CLI_PLAYER:-flatpak_mpv}" ;; # steamdeck OS *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:-mpv}" ;; # Linux OS + *) player_function="${ANI_CLI_PLAYER:-$(where_mpv)}" ;; # Linux OS esac no_detach="${ANI_CLI_NO_DETACH:-0}" @@ -357,7 +371,7 @@ search="${ANI_CLI_DEFAULT_SOURCE:-scrape}" while [ $# -gt 0 ]; do case "$1" in -v | --vlc) - case "$(uname -a)" in + case "$(uname -a | cut -d " " -f 1,3-)" in *ndroid*) player_function="android_vlc" ;; MINGW* | *WSL2*) player_function="vlc.exe" ;; *ish*) player_function="iSH" ;; @@ -431,13 +445,10 @@ if [ -z "$ANI_CLI_NON_INTERACTIVE" ]; then dep_ch fzf || true; fi 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 "\33[2K\rChecking of players on Android is disabled\n" ;; *iSH*) printf "\33[2K\rChecking of players on iOS is disabled\n" ;; - *iina*) true ;; # handled out of band in where_iina + *IINA*) true ;; # handled out of band in where_iina + flatpak_mpv) true ;; # handled out of band in where_mpv *) dep_ch "$player_function" ;; esac @@ -485,7 +496,7 @@ esac # moves the cursor up one line and clears that line tput cuu1 && tput el -# stores the positon of cursor +# stores the position of cursor tput sc # playback & loop diff --git a/hacking.md b/hacking.md index f361a4dc7..e291a2e4e 100644 --- a/hacking.md +++ b/hacking.md @@ -3,7 +3,7 @@ Ani-cli is set up to scrape one platform - currently allanime. Supporting multip However ani-cli being open-source and the pirate anime streaming sites being so similar you can hack ani-cli to support any site that follows a few conventions. -## Prequisites +## Prerequisites Here's the of skills you'll need and the guide will take for granted: - basic shell scripting - understanding of http(s) requests and proficiency with curl @@ -33,7 +33,7 @@ An adblocker can help with reducing traffic from the site, but beware of extensi Once you have the pages (urls) that you're interested in, it's easier to inspect them from less/an editor. The debugger's inspector can help you with finding what's what but finding patterns/urls is much easier in an editor. -Additionally the debugger doesn't always show you the html faithfully - I've experineced some escape sequences being rendered, capitalization changing - so be sure you see the response of the servers in raw format before you write your regexes. +Additionally the debugger doesn't always show you the html faithfully - I've experienced some escape sequences being rendered, capitalization changing - so be sure you see the response of the servers in raw format before you write your regexes. ### Core concepts If you navigate the site normally from the browser, you'll see that each anime is represented with an URL that compromises from an ID (that identifies a series/season of series) and an episode number. @@ -50,7 +50,7 @@ Just try searching for a few series and see how the URL changes (most of the tim If the site uses a POST request or a more roundabout way, use the debugger to analyze the traffic. Once you figured out how searching works, you'll have to replicate it in the `search_anime` function. -The `curl` in this function is responsible for the search request, and the following `sed` regexes mold the respons into many lines of `id\ttitle` format. +The `curl` in this function is responsible for the search request, and the following `sed` regexes mold the response into many lines of `id\ttitle` format. The reason for this is the `nth` function, see it for more details. You'll have to change some variables in the process (eg. allanime_base) too. @@ -83,7 +83,7 @@ From here they are separated and parsed by `provider_init` and the first half on Some sites (like allanime) have these urls not in plaintext but "encrypted". The decrypt allanime function does this post-processing, it might need to be changed or discarded completely. If there's only one embed source, the `generate links..` block can be reduced to a single call to `generate_link`. -The current structure does the agregation of many providers asynchronously, but this is not needed if there's only one source. +The current structure does the aggregation of many providers asynchronously, but this is not needed if there's only one source. ### Extracting the media links diff --git a/shell.nix b/shell.nix deleted file mode 100644 index 5d1624704..000000000 --- a/shell.nix +++ /dev/null @@ -1,22 +0,0 @@ -{ - pkgs ? import <nixpkgs> {}, - withMpv ? true, - withVlc ? false, - withIina ? false, - chromecastSupport ? false, - syncSupport ? false -}: - -# To start the dev shell use the comment nix-shell -# use --arg withVlc true to use VLC -# use --arg withIina true to use Iina -# use --arg chromecastSupport true to use chromecastSupport -# use --arg syncSupport true to use syncSupport - -assert withMpv || withVlc || withIina; - -with pkgs; -mkShell { - name = "ani-cli dev shell"; - buildInputs = [ shfmt shellcheck (ani-cli.override ({ withMpv = withMpv; withVlc = withVlc; withIina = withIina; chromecastSupport = chromecastSupport; syncSupport = syncSupport; })).runtimeDependencies ]; -}