diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 176190148..b1c5cfb16 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -14,6 +14,7 @@ - [ ] any anime playing - [ ] bumped version +- [ ] `sh tests/parse.sh` passes --- - [ ] next, prev, replay and select work - [ ] `-c` history and continue work diff --git a/.github/workflows/ani-cli.yml b/.github/workflows/ani-cli.yml index 8951aab4d..e5cf08ae8 100644 --- a/.github/workflows/ani-cli.yml +++ b/.github/workflows/ani-cli.yml @@ -6,6 +6,7 @@ on: pull_request: paths: - "ani-cli" + - "tests/**" jobs: sh-checker: @@ -44,3 +45,11 @@ jobs: - uses: actions/checkout@v4 - name: verify that noone added wget run: '! grep wget "./ani-cli"' + + parse-tests: + name: Parsing Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: run the offline parsing tests + run: sh tests/parse.sh diff --git a/.github/workflows/inverse-ani.yml b/.github/workflows/inverse-ani.yml index 00dd2e920..876530364 100644 --- a/.github/workflows/inverse-ani.yml +++ b/.github/workflows/inverse-ani.yml @@ -6,6 +6,7 @@ on: pull_request: paths-ignore: - "ani-cli" + - "tests/**" jobs: sh-checker: name: Shellcheck + Shfmt @@ -22,3 +23,8 @@ jobs: runs-on: ubuntu-latest steps: - run: 'echo "Not required: did not modify ani-cli"' + parse-tests: + name: Parsing Tests + runs-on: ubuntu-latest + steps: + - run: 'echo "Not required: did not modify ani-cli"' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b84544bff..88f6e2a9c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,7 @@ - Appease the linter (run `shfmt -i 4 -ci -d -w ani-cli`) - Appease POSIX (run `shellcheck -s sh -o all -e 2250 ani-cli`) +- Run the offline parsing tests (`sh tests/parse.sh`) and update `tests/` when you change the scraping - Bump the version - Adjust the Readme according to your changes (if applicable) - No extra dependencies unless absolutely necessary diff --git a/README.md b/README.md index 20a7789c2..a4847e807 100644 --- a/README.md +++ b/README.md @@ -557,6 +557,9 @@ Ani-skip uses the external lua script function of mpv and as such – for now * 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`. +* Do I have to type the full name? - No, the search prompt shows what the site finds while you type (fzf 0.25 or newer), pick a result with Enter. Type a name on the command line (`ani-cli dandadan`) to skip the prompt. +* What do the columns in the search results mean? - The type (TV, Movie, OVA, ONA, Special), the number of episodes (`?` while a show is unreleased or still airing) and how many episodes have subtitles and a dub (`-` when there are none). +* What is the pane next to the search results? - The details of the highlighted anime (Japanese name, aired dates, status, score, genres, studios, rating, seasons, synopsis). It exists with fzf only, moves below the list on narrow terminals and can be turned off with `export ANI_CLI_MENU_FLAGS="--no-preview"`. **Note:** All features are documented in `ani-cli --help`. diff --git a/ani-cli b/ani-cli index c6e6299a2..e218195de 100755 --- a/ani-cli +++ b/ani-cli @@ -1,6 +1,6 @@ #!/bin/sh -version_number="5.1.4" +version_number="5.3.0" # UI @@ -8,14 +8,14 @@ version_number="5.1.4" # $1 = prompt, $2 = multi select flag (for fzf/rofi), $3 = extra flags by user menu() { case "$menu_program" in - fzf) fzf --reverse --cycle --prompt "$1" $2 $3 ;; + fzf) fzf --reverse --cycle --prompt "$1" --delimiter '\t' --with-nth 1,4,3 --tabstop 4 ${menu_preview:+--preview "$menu_preview" --preview-window "$preview_window"} ${menu_reload:+--disabled --print-query --bind "change:reload($menu_reload)"} $2 $3 ;; rofi) rofi -sort -dmenu -i -p "$1" $2 $3 ;; dmenu) dmenu -l 20 -p "$1" $3 ;; *) "$menu_program" $3 "$1" ;; esac } -# provide menu. input format is either (ep_list) or (number \t anime_id \t anime_name) +# provide menu. input format is either (ep_list) or (number \t anime_id \t anime_name [\t details]) nth() { _stdin=$(cat -) [ -z "$_stdin" ] && return 1 @@ -23,7 +23,13 @@ nth() { [ "$_line_count" -eq 1 ] && printf "%s" "$_stdin" | cut -f 2,3 && return 0 _prompt="$1" [ $# -ne 1 ] && _multi_flag="$2" - _line=$(printf "%s" "$_stdin" | cut -f 1,3 | tr '\t' ' ' | menu "$_prompt" "$_multi_flag" "$menu_extra_flags" | cut -d " " -f 1) + if [ "$menu_program" = "fzf" ]; then + # fzf gets the raw lines, it hides the id and reorders the columns itself (see menu) + _line=$(printf "%s" "$_stdin" | menu "$_prompt" "$_multi_flag" "$menu_extra_flags" | cut -f 1) + else + # other menus get plain text: number, details (if any), then the name. literal tabs, BSD sed has no \t in brackets + _line=$(printf "%s" "$_stdin" | sed -E 's|^([^ ]*) [^ ]* ([^ ]*) ([^ ]*)$|\1 \3 \2|' | cut -f 1,3 | tr '\t' ' ' | menu "$_prompt" "$_multi_flag" "$menu_extra_flags" | cut -d " " -f 1) + fi _line_start=$(printf "%s" "$_line" | head -n 1) _line_end=$(printf "%s" "$_line" | tail -n 1) [ -n "$_line" ] || return 1 @@ -34,6 +40,27 @@ nth() { fi } +# fzf reloads a list while typing since 0.25 +fzf_reloads() { + _fzf_version=$(fzf --version | cut -d ' ' -f 1) + case "$_fzf_version" in + 0.[0-9].* | 0.1[0-9].* | 0.2[0-4].*) return 1 ;; + *) return 0 ;; + esac +} + +# search while typing: fzf asks the site again after every change and shows what it found, Enter picks a result. +# sets query (what was typed) and result (id \t title of the pick, empty when nothing was picked) +live_search() { + menu_reload="sleep 0.3; sh '$script_path' --search {q}" + _picked=$(: | menu "Search anime: " "" "$menu_extra_flags") + _fzf_status=$? + unset menu_reload + [ "$_fzf_status" -eq 130 ] && exit 1 + query=$(printf "%s\n" "$_picked" | head -n 1) + result=$(printf "%s\n" "$_picked" | sed -n '2p' | cut -f 2,3) +} + die() { printf "\33[2K\r\033[1;31m%s\033[0m\n" "$*" >&2 exit 1 @@ -87,6 +114,10 @@ help_info() { Exit the player, and return the player exit code (useful for non interactive scenarios) -N, --nextep-countdown Display a countdown to the next episode + --info + Show the details of an anime id, this is what the preview pane displays + --search + List what the site finds for a query, this is what the search prompt reloads while typing -U, --update Update the script Some example usages: @@ -195,7 +226,17 @@ deobfuscate_blob() ( printf "%b" "$_output" ) -# search the query and give results. format is (id \t name) +# turns the html entities the site uses for titles and descriptions back into characters +decode_entities() { + sed -e "s|'|'|g" -e 's|"|"|g' -e 's|<|<|g' -e 's|>|>|g' -e "s|&|\&|g" +} + +# appends the first group of the regex $1 as a new tab separated column, "-" when the line has no match +tab_column() { + sed -E -e "s|^(.*$1.*)\$|\\1\\t\\2|" -e t -e 's|$|\t-|' +} + +# search the query and give results. format is (id \t name \t details) hianime_search() { #shellcheck disable=SC2059 _page="$(hianime_curl "$(printf "$search_api" "$1")")" || return 1 @@ -204,9 +245,50 @@ hianime_search() { die "Blocked by cloudflare." fi # the top 10 sidebar repeats the result markup, cut it off before flattening the page - printf "%s" "$_page" | sed '/id="main-sidebar"/,$d' | tr '\n' ' ' | sed 's|