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|'|'|g" -e ' s|"|"|g' -e " s|&|\&|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|'|'|g" -e ' s|"|"|g' -e " s|&|\&|g" -e " s|'|'|g" -e " s|<|<|g" -e " s|>|>|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|'|'|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|'|'|g" -e ' s|"|"|g' -e ' s|&|\&|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
210280get_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
277347process_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
299369download () {
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
304374play_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"
389457ciphers=' 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'
390458tls13_ciphers=' TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256'
391459agent=" 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