Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,8 @@ Ani-skip uses the external lua script function of mpv and as such – for now
* 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 change media source? - This fork adds **simindad.top** alongside allanime. Search covers both by default and each result is labelled with its provider (`[allanime]` / `[simindad]`). Use `--source allanime|simindad|all` (or `ANI_CLI_SOURCE`) to scope it.
* How do I use simindad? - simindad requires a (free) account. Run `ani-cli --login-simindad` once to log in (it shows the captcha and stores the session), or set `ANI_CLI_SIMINDAD_USER` / `ANI_CLI_SIMINDAD_PASS`; a login prompt also appears automatically the first time you pick a simindad title. It covers films, series and anime (movies play as a single item).
* 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.
Expand Down
221 changes: 216 additions & 5 deletions ani-cli
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/sh

version_number="4.14.1"
version_number="4.14.1-simindad"

# UI

Expand Down Expand Up @@ -86,6 +86,10 @@ help_info() {
Use given title as ani-skip query
-N, --nextep-countdown
Display a countdown to the next episode
--source <all|allanime|simindad>
Choose which source(s) to search (default: all). Results are labelled per provider.
--login-simindad
Log in to simindad.top (prompts for credentials + captcha) and store the session
-U, --update
Update the script
Some example usages:
Expand Down Expand Up @@ -291,17 +295,27 @@ get_filemoon_links() {

# gets embed urls, collects direct links into provider files, selects one with desired quality into $episode
get_episode_url() {
# dispatch to the simindad source when the id is tagged with the sd: prefix
case "$id" in
sd:*)
get_episode_url_simindad
return
;;
*) ;;
esac
showid="${id#aa:}" # strip the allanime source tag before querying

#shellcheck disable=SC2016
episode_embed_gql='query ($showId: String!, $translationType: VaildTranslationTypeEnumType!, $episodeString: String!) { episode( showId: $showId translationType: $translationType episodeString: $episodeString ) { episodeString sourceUrls }}'

query_hash="d405d0edd690624b66baba3068e0edc3ac90f1597d898a1ec8db4e5c43c00fec"
query_vars="{\"showId\":\"$id\",\"translationType\":\"$mode\",\"episodeString\":\"$ep_no\"}"
query_vars="{\"showId\":\"$showid\",\"translationType\":\"$mode\",\"episodeString\":\"$ep_no\"}"
query_ext="{\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"$query_hash\"}}"

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")"
api_resp="$(curl -e "$allanime_refr" -s -H "Content-Type: application/json" -X POST "${allanime_api}/api" --data "{\"variables\":{\"showId\":\"$showid\",\"translationType\":\"$mode\",\"episodeString\":\"$ep_no\"},\"query\":\"$episode_embed_gql\"}" -A "$agent")"
fi

resp="$(process_response "$api_resp" | tr '{}' '\n' | sed 's|\\u002F|\/|g;s|\\||g' | sed -nE 's|.*sourceUrl":"([^"]*)".*sourceName":"([^"]*)".*|\2 :\1|p')"
Expand Down Expand Up @@ -333,6 +347,177 @@ search_anime() {
| g' | sed -nE "s|.*_id\":\"([^\"]*)\",\"name\":\"(.+)\",.*${mode}\":([1-9][^,]*).*|\1 \2 (\3 episodes)|p" | sed 's/\\"//g'
}

# ---- simindad.top source ----------------------------------------------------
# simindad is a login-gated WordPress site (film2media theme). Search is open,
# but content pages sit behind an SSO login and expose direct .mp4/.mkv links.

# render the login captcha: inline terminal image if possible, else open it
simindad_show_captcha() {
if command -v chafa >/dev/null 2>&1; then
chafa -s 60x14 "$1" >&2
elif command -v imgcat >/dev/null 2>&1; then
imgcat "$1" >&2
elif command -v catimg >/dev/null 2>&1; then
catimg "$1" >&2
elif command -v viu >/dev/null 2>&1; then
viu -h 12 "$1" >&2
else
case "$(uname -s)" in
Darwin*) open "$1" >/dev/null 2>&1 ;;
*)
if command -v termux-open >/dev/null 2>&1; then
termux-open "$1" >/dev/null 2>&1
elif command -v xdg-open >/dev/null 2>&1; then
xdg-open "$1" >/dev/null 2>&1
else
printf "Open this image to read the captcha: %s\n" "$1" >&2
fi
;;
esac
fi
}

# interactive login; persists the WordPress session into $simindad_jar
simindad_login() {
printf "\33[2K\r\033[1;34mLogging in to simindad...\033[0m\n" >&2
profile_page="$(curl -sL -A "$agent" -c "$simindad_jar" -b "$simindad_jar" "$simindad_base/profile/")"
login_nonce="$(printf '%s' "$profile_page" | sed -nE 's/.*id="authbutton"[^>]*value="([^"]+)".*/\1/p' | head -n1)"
captcha_img="$(mktemp).png"
curl -s -A "$agent" -c "$simindad_jar" -b "$simindad_jar" -e "$simindad_base/profile/" "$simindad_base/captcha.php?w=150&h=50" -o "$captcha_img"
simindad_show_captcha "$captcha_img"

login_user="${ANI_CLI_SIMINDAD_USER:-}"
login_pass="${ANI_CLI_SIMINDAD_PASS:-}"
if [ -z "$login_user" ]; then
printf "\033[1;36mSimindad username: \033[0m" >&2
read -r login_user
fi
if [ -z "$login_pass" ]; then
printf "\033[1;36mSimindad password: \033[0m" >&2
stty -echo 2>/dev/null
read -r login_pass
stty echo 2>/dev/null
printf "\n" >&2
fi
printf "\033[1;36mCaptcha (5 digits, type the shown digits as 0-9): \033[0m" >&2
read -r login_code
rm -f "$captcha_img"

login_hdr="$(curl -s -A "$agent" -c "$simindad_jar" -b "$simindad_jar" -e "$simindad_base/profile/" -D - -o /dev/null \
--data-urlencode "username=$login_user" --data-urlencode "password=$login_pass" \
--data-urlencode "challenge_captcha=$login_code" --data-urlencode "native=" \
--data-urlencode "submit=$login_nonce" "$simindad_base/profile/")"
if printf '%s' "$login_hdr" | grep -qi 'wordpress_logged_in'; then
printf "\033[1;32mSimindad login successful\033[0m\n" >&2
else
die "Simindad login failed (wrong username/password or captcha)."
fi
}

# fetch a simindad path (following the SSO redirect chain); auto-login if needed
simindad_page() {
page="$(curl -sL -A "$agent" -c "$simindad_jar" -b "$simindad_jar" -e "$simindad_base/" "$simindad_base$1")"
if printf '%s' "$page" | grep -q 'id="authform"'; then
simindad_login
page="$(curl -sL -A "$agent" -c "$simindad_jar" -b "$simindad_jar" -e "$simindad_base/" "$simindad_base$1")"
fi
printf '%s' "$page"
}

# extract direct media links from a fetched page into "epno<TAB>mode<TAB>quality<TAB>url"
# lines. $2 is the title type (series|movie). Uses a handful of whole-stream passes
# (not one fork per link) so it stays fast even on series pages with 1000+ links.
simindad_extract_links() {
ex_dir="$(mktemp -d)"
printf '%s' "$1" | grep -oE 'https?://[^"'\'' <>]+\.(mkv|mp4)[^"'\'' <>]*' |
grep -viE 'trailer' | sort -u >"$ex_dir/url"
if [ "$2" = series ]; then
# episode number: prefer SxxExx, else the number just before the resolution
# (e.g. One.Piece.1156.1080p), else fall back to 1
sed -E 's/.*[Ss][0-9]+[Ee]0*([0-9]+).*/\1/; t
s/.*[._ ]0*([0-9]{1,4})[._ ][0-9]{3,4}p.*/\1/; t
s/.*/1/' "$ex_dir/url" >"$ex_dir/ep"
else
# movies are a single item; never mistake a year for an episode number
sed 's/.*/1/' "$ex_dir/url" >"$ex_dir/ep"
fi
# dubbed vs subbed, inferred from the filename
sed -E 's/.*([Dd]ubbed|[Dd]ubla|دوبله).*/dub/; t; s/.*/sub/' "$ex_dir/url" >"$ex_dir/mode"
# numeric quality (resolution) from the filename, else 0
sed -E 's/.*[^0-9]([0-9]{3,4})p.*/\1/; t; s/.*/0/' "$ex_dir/url" >"$ex_dir/q"
paste "$ex_dir/ep" "$ex_dir/mode" "$ex_dir/q" "$ex_dir/url"
rm -rf "$ex_dir"
}

# search simindad via its open /quick-search endpoint; emit "sd:<url><TAB>[simindad] <title> (<type>)"
search_simindad() {
search_q="$(printf '%s' "$1" | tr '+' ' ')"
# Parse the per-hit "_formatted" objects: they mirror the top-level fields
# (_kind/title/url) and never contain nested braces, so they survive even the
# occasional "_db_error" prefix the endpoint returns. Strip <em> highlight tags.
curl -s -A "$agent" -e "$simindad_base/" -X POST "$simindad_base/quick-search" \
--data-urlencode "q=$search_q" --data "sort=modified_at:desc" |
grep -oE '"_formatted":\{[^}]*\}' | sed 's/<[^>]*>//g' |
sed -nE 's/.*"_kind":"([^"]*)".*"title":"([^"]*)".*"url":"([^"]*)".*/\1\t\2\t\3/p' |
grep -v "^term${T}" | cut -f2- | while IFS="${T}" read -r sd_title sd_url; do
[ -z "$sd_url" ] && continue
case "$sd_url" in
*"/series/"*) sd_type=series ;;
*) sd_type=movie ;;
esac
printf 'sd:%s\t[simindad] %s (%s)\n' "$sd_url" "$sd_title" "$sd_type"
done
}

# episode list for a simindad title; caches extracted links in $simindad_links_file
episodes_list_simindad() {
simindad_links_file="$(mktemp)"
case "$1" in
*"/series/"*) simindad_type=series ;;
*) simindad_type=movie ;;
esac
simindad_html="$(simindad_page "${1#sd:}")"
simindad_extract_links "$simindad_html" "$simindad_type" >"$simindad_links_file"
cut -f1 "$simindad_links_file" | sort -n -u
}

# select the media url for the current $ep_no from the cached simindad links
get_episode_url_simindad() {
[ -f "$simindad_links_file" ] || episodes_list_simindad "$id" >/dev/null
sd_lines="$(grep "^${ep_no}${T}" "$simindad_links_file")"
sd_mode_lines="$(printf '%s\n' "$sd_lines" | grep "${T}${mode}${T}")"
[ -n "$sd_mode_lines" ] && sd_lines="$sd_mode_lines"
links="$(printf '%s\n' "$sd_lines" | while IFS="${T}" read -r _ _ lq lu; do
[ -n "$lu" ] && printf '%s >%s\n' "$lq" "$lu"
done | sort -nr -s)"
if printf "%s" "$ep_list" | grep -q "^$ep_no$"; then
[ -z "$links" ] && die "Episode is released, but no valid sources!"
else
[ -z "$links" ] && die "Episode not released!"
fi
select_quality "$quality"
refr_flag="--referrer=$simindad_refr"
}
# -----------------------------------------------------------------------------

# run the search across the configured sources and tag each row with its provider
search_sources() {
case "$source_provider" in
allanime)
search_anime "$1" | while IFS="$T" read -r aa_id aa_title; do
[ -n "$aa_id" ] && printf 'aa:%s\t[allanime] %s\n' "$aa_id" "$aa_title"
done
;;
simindad) search_simindad "$1" ;;
*)
search_anime "$1" | while IFS="$T" read -r aa_id aa_title; do
[ -n "$aa_id" ] && printf 'aa:%s\t[allanime] %s\n' "$aa_id" "$aa_title"
done
search_simindad "$1"
;;
esac
}

time_until_next_ep() {
animeschedule="https://animeschedule.net"
query="$(printf "%s\n" "$*" | tr ' ' '+')"
Expand All @@ -349,6 +534,16 @@ time_until_next_ep() {

# get the episodes list of the selected anime
episodes_list() {
# dispatch to the simindad source when the id is tagged with the sd: prefix
case "$1" in
sd:*)
episodes_list_simindad "$1"
return
;;
*) ;;
esac
set -- "${1#aa:}" # strip the allanime source tag before querying

#shellcheck disable=SC2016
episodes_list_gql='query ($showId: String!) { show( _id: $showId ) { _id availableEpisodesDetail }}'

Expand Down Expand Up @@ -511,6 +706,13 @@ histfile="$hist_dir/ani-hsts"
[ ! -f "$histfile" ] && : >"$histfile"
search="${ANI_CLI_DEFAULT_SOURCE:-scrape}"
branch="${ANI_CLI_BRANCH:-master}"
# simindad.top source configuration
simindad_base="https://www.simindad.top"
simindad_refr="https://www.simindad.top"
simindad_jar="$hist_dir/simindad_cookies.txt"
source_provider="${ANI_CLI_SOURCE:-all}" # all | allanime | simindad
# literal tab, used as a field separator
T="$(printf '\t')"

while [ $# -gt 0 ]; do
case "$1" in
Expand Down Expand Up @@ -578,6 +780,12 @@ while [ $# -gt 0 ]; do
shift
;;
-N | --nextep-countdown) search=nextep ;;
--source)
[ $# -lt 2 ] && die "missing argument!"
source_provider="$2"
shift
;;
--login-simindad) simindad_do_login=1 ;;
-U | --update)
branch="${2:-$branch}"
update_script
Expand Down Expand Up @@ -609,6 +817,9 @@ esac
# idk where to put this, so I put it here...
trap 'cleanup; exit 1' INT HUP

# explicit simindad login refresh
[ "$simindad_do_login" = 1 ] && simindad_login && exit 0

# searching
case "$search" in
history)
Expand Down Expand Up @@ -636,12 +847,12 @@ case "$search" in
[ "$search" = "nextep" ] && time_until_next_ep "$query"

query=$(printf "%s" "$query" | sed "s| |+|g")
anime_list=$(search_anime "$query")
anime_list=$(search_sources "$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)
title=$(printf "%s" "$result" | cut -f2 | sed 's/^\[[^]]*\] //')
allanime_title="$(printf "%s" "$title" | cut -d'(' -f1 | tr -d '[:punct:]')"
id=$(printf "%s" "$result" | cut -f1)
ep_list=$(episodes_list "$id")
Expand Down
15 changes: 15 additions & 0 deletions ani-cli.1
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ Exit the player, and return the player exit code (useful for non interactive sce
.TP
\fB\--skip-title\fR \fI\,<title>\/\fR
Specify the title to use for ani-skip
.TP
\fB\--source\fR \fI\,<all|allanime|simindad>\/\fR
Choose which source(s) to search. Results are labelled per provider. Default is all.
.TP
\fB\--login-simindad\fR
Log in to simindad.top (prompts for username, password and captcha) and store the session, then exit.
.PP
.SH
ENVIRONMENT VARIABLES
Expand Down Expand Up @@ -113,6 +119,15 @@ Controls if mpv is detached from the main process for playback, which can be use
.TP
\fBANI_CLI_SKIP_TITLE\fR
Overrides the anime title to query for skip times. Can be any string value. Default is empty, resolving to the anime title as given by ani-cli.
.TP
\fBANI_CLI_SOURCE\fR
Controls which source(s) are searched. Valid options are all, allanime or simindad. Default is all.
.TP
\fBANI_CLI_SIMINDAD_USER\fR
Username for simindad.top. If set (together with ANI_CLI_SIMINDAD_PASS), login is non-interactive except for the captcha.
.TP
\fBANI_CLI_SIMINDAD_PASS\fR
Password for simindad.top. See ANI_CLI_SIMINDAD_USER.
.PP
.SH EPISODE SELECTION
.PP
Expand Down
37 changes: 37 additions & 0 deletions packaging/aur/PKGBUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Maintainer: mgh12453 <algorier.otp@gmail.com>
_pkgname=awesome-ani-cli
pkgname=awesome-ani-cli-git
pkgver=r1
pkgrel=1
pkgdesc="ani-cli fork that adds the simindad.top source (films, series, anime) alongside allanime"
arch=('any')
url="https://github.com/mgh12453/awesome-ani-cli"
license=('GPL3')
depends=('grep' 'sed' 'curl' 'openssl')
optdepends=(
'mpv: default video player'
'fzf: interactive menu (required for interactive use)'
'vlc: alternative video player (--vlc)'
'aria2: downloading (-d)'
'ffmpeg: downloading m3u8 streams (-d)'
'yt-dlp: downloading m3u8 streams (-d)'
'ani-skip: skip intros (--skip)'
'chafa: render the simindad login captcha inline in the terminal'
)
provides=('ani-cli')
conflicts=('ani-cli')
makedepends=('git')
source=("$_pkgname::git+https://github.com/mgh12453/awesome-ani-cli.git")
sha256sums=('SKIP')

pkgver() {
cd "$srcdir/$_pkgname"
printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
}

package() {
cd "$srcdir/$_pkgname"
install -Dm755 ani-cli "$pkgdir/usr/bin/ani-cli"
install -Dm644 ani-cli.1 "$pkgdir/usr/share/man/man1/ani-cli.1"
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
}
Loading
Loading