Skip to content

Commit 61f3a9a

Browse files
committed
fix: update HiAnime scraper and referrers
1 parent ebb1bdd commit 61f3a9a

1 file changed

Lines changed: 117 additions & 49 deletions

File tree

ani-cli

Lines changed: 117 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/bin/sh
22

3-
version_number="5.0.3"
3+
version_number="5.0.4"
44

55
# UI
66

@@ -149,49 +149,119 @@ cleanup() {
149149
# SCRAPING
150150

151151
#shellcheck disable=SC2086
152-
anidb_curl() {
152+
hianime_curl() {
153153
$curl_exe -sL -A "$agent" --max-time 10 $cipher_flag "$@"
154154
}
155155

156+
# Decode one base64 value. GNU, BSD and OpenSSL use different switches.
157+
b64_decode() {
158+
printf "%s\n" "$1" | base64 -d 2>/dev/null || printf "%s\n" "$1" | base64 -D 2>/dev/null || printf "%s\n" "$1" | openssl base64 -d -A 2>/dev/null
159+
}
160+
161+
# Deobfuscate the XOR blob with key otaku-embed-v1.
162+
# The subshell keeps the temporary positional parameters out of the caller.
163+
deobfuscate_blob() (
164+
_blob=$1
165+
set -- 111 116 97 107 117 45 101 109 98 101 100 45 118 49
166+
_bytes="$(b64_decode "$_blob" | od -v -An -tu1)"
167+
_output=""
168+
_count=0
169+
for _byte in $_bytes; do
170+
_key=$1
171+
shift
172+
set -- "$@" "$_key"
173+
_char=$((_byte ^ _key))
174+
_output="${_output}\\$((_char / 64))$(((_char / 8) % 8))$((_char % 8))"
175+
_count=$((_count + 1))
176+
if [ "$_count" -eq 256 ]; then
177+
printf "%b" "$_output"
178+
_output=""
179+
_count=0
180+
fi
181+
done
182+
printf "%b" "$_output" | sed -n 's|.*"src":"\([^"]*\.m3u8[^"]*\)".*|\1|p'
183+
)
184+
156185
# search the query and give results. format is (id \t name)
157-
anidb_search() {
186+
hianime_search() {
158187
#shellcheck disable=SC2059
159-
_page="$(anidb_curl "$(printf "$search_api" "$1")" | tr '\n' ' ' | sed 's|<a href|\n<a href|g')"
188+
_page="$(hianime_curl "$(printf "$search_api" "$1")")"
160189
if printf "%s" "$_page" | grep -qi "Just a moment"; then
161190
[ "$curl_exe" = "curl" ] && die "Blocked by cloudflare. Try installing curl-impersonate"
162191
die "Blocked by cloudflare."
163192
fi
164-
printf "%s" "$_page" | sed -nE 's|.*anime/([a-z0-9-]+-[0-9]+)".*alt="([^"]+)".*|\1\t\2|p' | sed -e "s|&#039;|'|g" -e 's|&quot;|"|g' -e "s|&amp;|\&|g"
193+
printf "%s" "$_page" | tr '\n' ' ' | sed 's|<div class="film-detail">|\n<div class="film-detail">|g' |
194+
sed -n 's|.*<h3 class="film-name">[[:space:]]*<a href="https://hianime\.at/\([^"]*\)"[[:space:]]*title="\([^"]*\)".*|\1\t\2|p' |
195+
sed -e "s|&#039;|'|g" -e 's|&quot;|"|g' -e "s|&amp;|\&|g" -e "s|&apos;|'|g" -e "s|&lt;|<|g" -e "s|&gt;|>|g"
165196
}
166197

167198
# extract mal_id and seasons metadata
168-
anidb_desc() {
199+
hianime_desc() {
169200
#shellcheck disable=SC2059
170-
_page="$(anidb_curl "$(printf "$desc_api" "$1")" | tr '\n' ' ' | sed 's|<a href|\n<a href|g')"
171-
mal_id="$(printf "%s" "$_page" | sed -nE 's|.*https://myanimelist.net/anime/([0-9]+)/.*|\1|p')"
172-
seasons="$(printf "%s" "$_page" | sed -n '/>Seasons</,/>Details</p' | sed -nE 's|.*anime/([^"]+-[0-9]+)"[^>]*title="([^"]+)".*|\1\t\2|p' | sed -e "s|&#039;|'|g")"
201+
_page="$(hianime_curl "$(printf "$desc_api" "$1")")"
202+
mal_id="$(printf "%s" "$_page" | sed -n 's|.*myanimelist\.net/anime/\([0-9][0-9]*\).*|\1|p')"
203+
# Related entries are grouped in the season list on HiAnime pages.
204+
seasons="$(printf "%s" "$_page" | tr '\n' ' ' | sed 's|<a href|\n<a href|g' | sed -n 's|.*href="https://hianime\.at/\([^"?][^"]*\)"[^>]*title="\([^"]*\)".*|\1\t\2|p' | sed -e "s|&#039;|'|g" -e 's|&quot;|"|g' -e 's|&amp;|\&|g')"
173205
[ -n "$seasons" ] && seasons_option="\nchange_season"
174206
}
175207

176208
# get the episodes list of the selected anime. format is (id \t ep_no)
177-
anidb_episodes() {
209+
hianime_episodes() {
178210
#shellcheck disable=SC2059
179-
_page="$(anidb_curl "$(printf "$episodes_api" "${1##*-}")")"
180-
printf "%s" "$_page" | sed 's|},{|}\n{|g' | sed -nE 's|.*"id":([0-9]+).*"number":([0-9]+).*|\1\t\2|p'
211+
_page="$(hianime_curl -H "X-Requested-With: XMLHttpRequest" "$(printf "$episodes_api" "${1##*-}")")"
212+
printf "%s" "$_page" | sed 's|\\\"|"|g; s|ep-item|\nep-item|g' |
213+
sed -n 's|.*data-number="\([^"]*\)".*data-id="\([0-9][0-9]*\)".*|\2\t\1|p'
181214
}
182215

183216
# extract the individual m3u8 links from response of embed urls
184-
anidb_m3u8() {
185-
_ep_id=$(printf "%s" "$episode_maps" | sed -nE "s|^([0-9]+)\t${1}$|\1|p")
186-
_lang="jpn"
187-
[ "$2" = "dub" ] && _lang="eng"
217+
hianime_m3u8() {
218+
_ep_id=$(printf "%s" "$episode_maps" | sed -n "s|^\([0-9][0-9]*\)\t${1}$|\1|p")
219+
_lang="sub"
220+
[ "$2" = "dub" ] && _lang="dub"
221+
[ -z "$_ep_id" ] && return 1
222+
223+
# 1. Get servers
188224
#shellcheck disable=SC2059
189-
_page="$(anidb_curl "$(printf "$m3u8_api" "${_ep_id}")")"
190-
_embed="$(printf "%s" "$_page" | sed 's|},{|}\n{|g' | sed -nE 's|.*'"${_lang}"'.*embed_url":"([^"]+)".*|\1|p' | sed 's|\\/|/|g' | head -n 1)"
191-
[ -z "$_embed" ] && return 1
192-
_embed_page="$(anidb_curl "$_embed")"
193-
_m3u8_master="$(printf "%s" "$_embed_page" | sed -nE "s|.*file: '([^']*)'.*|\1|p" | head -n 1)"
194-
anidb_curl "${_m3u8_master}" | sed 's|^#EXT-X-STREAM.*x||g; s|,.*|p|g; /^#/d; $!N; s|\n| >|;/EXT-X-I-FRAME/d'
225+
_servers_json="$(hianime_curl -H "X-Requested-With: XMLHttpRequest" "$(printf "$servers_api" "${_ep_id}")")"
226+
227+
# 2. Prefer HD-1; otherwise use the first server for the selected language.
228+
_servers="$(printf "%s" "$_servers_json" | sed 's|\\\"|"|g; s|server-item|\nserver-item|g')"
229+
_hash="$(printf "%s\n" "$_servers" | grep "data-type=\"${_lang}\"" | grep "data-server-name=\"HD-1\"" | sed -n 's|.*data-hash="\([^"]*\)".*|\1|p' | head -n 1)"
230+
[ -z "$_hash" ] && _hash="$(printf "%s\n" "$_servers" | grep "data-type=\"${_lang}\"" | sed -n 's|.*data-hash="\([^"]*\)".*|\1|p' | head -n 1)"
231+
232+
[ -z "$_hash" ] && return 1
233+
234+
# 3. Base64 decode embed URL
235+
_embed_url="$(b64_decode "$_hash")"
236+
[ -z "$_embed_url" ] && return 1
237+
238+
# Extract mal_id for ani-skip
239+
mal_id="$(printf "%s" "$_embed_url" | sed -n 's|.*/stream/mal/\([0-9][0-9]*\)/.*|\1|p')"
240+
241+
# 4. Fetch embed HTML
242+
_embed_page="$(hianime_curl -e "${base_api}/" "$_embed_url")"
243+
_blob="$(printf "%s" "$_embed_page" | sed -n 's|.*window\.__P="\([^"]*\)".*|\1|p')"
244+
[ -z "$_blob" ] && return 1
245+
246+
# 5. Deobfuscate
247+
_m3u8_master="$(deobfuscate_blob "$_blob")"
248+
[ -z "$_m3u8_master" ] && return 1
249+
250+
# 6. Fetch the master playlist and expand quality variants.
251+
_m3u8_content="$(hianime_curl -e "https://zokoanime.video/" "$_m3u8_master")"
252+
if printf "%s" "$_m3u8_content" | grep -q "#EXT-X-STREAM-INF"; then
253+
_base_url="${_m3u8_master%/*}"
254+
_parsed="$(printf "%s\n" "$_m3u8_content" | sed 's|^#EXT-X-STREAM.*x||g; s|,.*|p|g; /^#/d; $!N; s|\n| >|;/EXT-X-I-FRAME/d')"
255+
printf "%s\n" "$_parsed" | while IFS='>' read -r _quality _path; do
256+
_quality=${_quality% }
257+
case "$_path" in
258+
http*) printf "%s >%s\n" "$_quality" "$_path" ;;
259+
*) printf "%s >%s/%s\n" "$_quality" "$_base_url" "$_path" ;;
260+
esac
261+
done
262+
else
263+
printf "default >%s\n" "$_m3u8_master"
264+
fi
195265
}
196266

197267
# select the quality according to the setting
@@ -208,9 +278,9 @@ select_quality() {
208278

209279
# gets embed url, collects direct links and selects one with desired quality into $episode
210280
get_video_link() {
211-
links=$(anidb_m3u8 "$ep_no" "$mode" | sort -g -r -s)
281+
links=$(hianime_m3u8 "$ep_no" "$mode" | sort -g -r -s)
212282
[ -z "$links" ] && die "No sources found for $mode!"
213-
info "anidb.app links fetched"
283+
info "hianime.at links fetched"
214284
select_quality "$quality"
215285
# should never trigger but just in case
216286
if printf "%s" "$ep_list" | grep -q "^$ep_no$"; then
@@ -275,7 +345,7 @@ backup_old_hist() {
275345
}
276346

277347
process_hist_entry() {
278-
ep_list=$(anidb_episodes "$anime_id" | cut -f 2)
348+
ep_list=$(hianime_episodes "$anime_id" | cut -f 2)
279349
ep_no=$(printf "%s" "$ep_list" | sed -n "/^${ep_no}$/{n;p;}") 2>/dev/null
280350
[ -n "$ep_no" ] && printf "%s\t%s - episode %s\n" "$anime_id" "$anime_title" "$ep_no"
281351
}
@@ -297,8 +367,8 @@ update_history() {
297367
# shellcheck disable=SC2086
298368
# $1 = video link, $2 = video name, $3 = extra flags
299369
download() {
300-
command -v "yt-dlp" >/dev/null && yt-dlp "$1" --no-skip-unavailable-fragments --fragment-retries infinite -N 16 -o "$download_dir/$2.mp4" $3 && return 0
301-
ffmpeg -extension_picky 0 -loglevel error -stats -i "$1" -c copy "$download_dir/$2.mp4" $3
370+
command -v "yt-dlp" >/dev/null && yt-dlp --referer "https://zokoanime.video/" "$1" --no-skip-unavailable-fragments --fragment-retries infinite -N 16 -o "$download_dir/$2.mp4" $3 && return 0
371+
ffmpeg -extension_picky 0 -referer "https://zokoanime.video/" -loglevel error -stats -i "$1" -c copy "$download_dir/$2.mp4" $3
302372
}
303373

304374
play_episode() {
@@ -312,27 +382,27 @@ play_episode() {
312382
debug) printf "All links:\n%s\nSelected link:\n%s\n" "$links" "$video_link" ;;
313383
android_mpv) nohup am start --user 0 -a android.intent.action.VIEW -d "$video_link" -n is.xyz.mpv/.MPVActivity -e "title" "${anime_title} Episode ${ep_no}" $player_extra_flags >/dev/null 2>&1 & ;;
314384
android_vlc) nohup am start --user 0 -a android.intent.action.VIEW -d "$video_link" -n org.videolan.vlc/org.videolan.vlc.gui.video.VideoPlayerActivity -e "title" "${anime_title} Episode ${ep_no}" $player_extra_flags >/dev/null 2>&1 & ;;
315-
*flatpak*mpv*) flatpak run io.mpv.Mpv $skip_flag $player_extra_flags --force-media-title="${anime_title} Episode ${ep_no}" "$video_link" >/dev/null 2>&1 & ;;
385+
*flatpak*mpv*) flatpak run io.mpv.Mpv --referrer="https://zokoanime.video/" $skip_flag $player_extra_flags --force-media-title="${anime_title} Episode ${ep_no}" "$video_link" >/dev/null 2>&1 & ;;
316386
*mpv*)
317387
if [ "$no_detach" = 0 ]; then
318-
nohup $player_function $skip_flag $player_extra_flags --force-media-title="${anime_title} Episode ${ep_no}" "$video_link" >/dev/null 2>&1 &
388+
nohup $player_function --referrer="https://zokoanime.video/" $skip_flag $player_extra_flags --force-media-title="${anime_title} Episode ${ep_no}" "$video_link" >/dev/null 2>&1 &
319389
else
320-
$player_function $skip_flag $player_extra_flags --force-media-title="${anime_title} Episode ${ep_no}" "$video_link"
390+
$player_function --referrer="https://zokoanime.video/" $skip_flag $player_extra_flags --force-media-title="${anime_title} Episode ${ep_no}" "$video_link"
321391
mpv_exitcode=$?
322392
[ "$exit_after_play" = 1 ] && [ -z "$_range" ] && exit "$mpv_exitcode"
323393
fi
324394
;;
325395
*iina*)
326396
if pgrep -f "IINA" >/dev/null 2>&1; then
327397
# omit --keep-running when an IINA instance exists to prevent hanging
328-
nohup $player_function $player_extra_flags --no-stdin --mpv-force-media-title="${anime_title} Episode ${ep_no}" "$video_link" >/dev/null 2>&1 &
398+
nohup $player_function --mpv-referrer="https://zokoanime.video/" $player_extra_flags --no-stdin --mpv-force-media-title="${anime_title} Episode ${ep_no}" "$video_link" >/dev/null 2>&1 &
329399
else
330-
nohup $player_function $player_extra_flags --no-stdin --keep-running --mpv-force-media-title="${anime_title} Episode ${ep_no}" "$video_link" >/dev/null 2>&1 &
400+
nohup $player_function --mpv-referrer="https://zokoanime.video/" $player_extra_flags --no-stdin --keep-running --mpv-force-media-title="${anime_title} Episode ${ep_no}" "$video_link" >/dev/null 2>&1 &
331401
fi
332402
;;
333-
*vlc*) nohup $player_function $player_extra_flags --play-and-exit --meta-title="${anime_title} Episode ${ep_no}" "$video_link" >/dev/null 2>&1 & ;;
334-
*yncpla*) nohup $player_function $player_extra_flags "$video_link" -- --force-media-title="${anime_title} Episode ${ep_no}" >/dev/null 2>&1 & ;;
335-
download) "$player_function" "$video_link" "${anime_title} Episode ${ep_no}" $player_extra_flags ;;
403+
*vlc*) nohup $player_function --http-referrer="https://zokoanime.video/" $player_extra_flags --play-and-exit --meta-title="${anime_title} Episode ${ep_no}" "$video_link" >/dev/null 2>&1 & ;;
404+
*yncpla*) nohup $player_function $player_extra_flags "$video_link" -- --referrer="https://zokoanime.video/" --force-media-title="${anime_title} Episode ${ep_no}" >/dev/null 2>&1 & ;;
405+
download) "$player_function" "$video_link" "${anime_title} Episode ${ep_no}" "$player_extra_flags" ;;
336406
catt) nohup catt cast $player_extra_flags "$video_link" >/dev/null 2>&1 & ;;
337407
iSH)
338408
printf "\033]8;;vlc://%s\a~~~~~~~~~~~~~~~~~~~~\n~ Tap to open VLC ~\n~~~~~~~~~~~~~~~~~~~~\033]8;;\a\n" "$video_link"
@@ -379,13 +449,11 @@ play() {
379449
# MAIN
380450

381451
# setup
382-
base_api="https://anidb.app"
383-
search_api="${base_api}/browse?q=%s"
384-
# search_api_alt="${base_api}/search/suggestions?q=%s"
385-
desc_api="${base_api}/anime/%s"
386-
episodes_api="${base_api}/api/frontend/anime/%s/episodes"
387-
m3u8_api="${base_api}/api/frontend/episode/%s/languages"
388-
452+
base_api="https://hianime.at"
453+
search_api="${base_api}/search?keyword=%s"
454+
desc_api="${base_api}/%s"
455+
episodes_api="${base_api}/api/theme/episode/list/%s"
456+
servers_api="${base_api}/api/theme/episode/servers?episodeId=%s"
389457
ciphers='ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305'
390458
tls13_ciphers='TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256'
391459
agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
@@ -534,8 +602,8 @@ case "$source" in
534602
[ -z "${index##*[!0-9]*}" ] || anime_id=$(printf "%s" "$anime_list" | sed -n "${index}p" | cut -f 1)
535603
[ -z "$anime_id" ] && exit 1
536604
anime_title=$(printf "%s" "$anime_list" | grep "^$anime_id " | cut -f 2 | sed 's| - episode.*||')
537-
anidb_desc "$anime_id" || true
538-
episode_maps="$(anidb_episodes "$anime_id")"
605+
hianime_desc "$anime_id" || true
606+
episode_maps="$(hianime_episodes "$anime_id")"
539607
ep_list="$(printf "%s" "$episode_maps" | cut -f 2)"
540608
ep_no=$(printf "%s" "$anime_list" | grep "^$anime_id " | sed -nE 's/.*- episode (.+)$/\1/p')
541609
;;
@@ -553,15 +621,15 @@ case "$source" in
553621

554622
query=$(printf "%s" "$query" | sed 's| |+|g')
555623

556-
anime_list=$(anidb_search "$query")
624+
anime_list=$(hianime_search "$query")
557625
[ -z "$anime_list" ] && die "No results found!"
558626
[ "$index" -eq "$index" ] 2>/dev/null && result=$(printf "%s" "$anime_list" | sed -n "${index}p")
559627
[ -z "$index" ] && result=$(printf "%s" "$anime_list" | nl -w 2 | sed 's/^[[:space:]]//' | nth "Select anime: ")
560628
[ -z "$result" ] && die "Invalid anime selection"
561629
anime_title="$(printf "%s" "$result" | cut -f 2)"
562630
anime_id="$(printf "%s" "$result" | cut -f 1)"
563-
anidb_desc "$anime_id" || true
564-
episode_maps="$(anidb_episodes "$anime_id")"
631+
hianime_desc "$anime_id" || true
632+
episode_maps="$(hianime_episodes "$anime_id")"
565633
ep_list="$(printf '%s' "$episode_maps" | cut -f 2)"
566634
[ -z "$ep_no" ] && ep_no=$(printf "%s" "$ep_list" | nth "Select episode: " "$menu_multi_flag")
567635
[ -z "$ep_no" ] && die "Invalid episode selection"
@@ -589,8 +657,8 @@ while cmd=$(printf "next\nreplay\nprevious\nselect$seasons_option\nchange_qualit
589657
[ -z "$new_anime" ] && die "Invalid season selection"
590658
anime_title="$(printf "%s" "$new_anime" | cut -f 2)"
591659
anime_id="$(printf "%s" "$new_anime" | cut -f 1)"
592-
anidb_desc "$anime_id" || true
593-
episode_maps="$(anidb_episodes "$anime_id")"
660+
hianime_desc "$anime_id" || true
661+
episode_maps="$(hianime_episodes "$anime_id")"
594662
ep_list="$(printf '%s' "$episode_maps" | cut -f 2)"
595663
ep_no=$(printf "%s" "$ep_list" | nth "Select episode: " "$menu_multi_flag")
596664
[ -z "$ep_no" ] && die "Invalid episode selection"

0 commit comments

Comments
 (0)