11#! /bin/sh
22
3- version_number=" 4.13 .0"
3+ version_number=" 4.14 .0"
44
55# UI
66
@@ -181,9 +181,14 @@ generate_link() {
181181 1) provider_init " wixmp" " /Default :/p" ;; # wixmp(default)(m3u8)(multi) -> (mp4)(multi)
182182 2) provider_init " youtube" " /Yt-mp4 :/p" ;; # youtube(mp4)(single)
183183 3) provider_init " sharepoint" " /S-mp4 :/p" ;; # sharepoint(mp4)(single)
184+ 5) provider_init " filemoon" " /Fm-mp4 :/p" ;; # filemoon(m3u8)(single)
184185 * ) provider_init " hianime" " /Luf-Mp4 :/p" ;; # hianime(m3u8)(multi)
185186 esac
186- [ -n " $provider_id " ] && get_links " $provider_id "
187+ if [ " $1 " = " 5" ] && [ -n " $provider_id " ]; then
188+ get_filemoon_links " $provider_id "
189+ else
190+ [ -n " $provider_id " ] && get_links " $provider_id "
191+ fi
187192}
188193
189194select_quality () {
@@ -220,13 +225,66 @@ decode_tobeparsed() {
220225 printf ' %s' " $plain " | tr ' {}' ' \n' | sed -nE ' s|.*"sourceUrl":"--([^"]*)".*"sourceName":"([^"]*)".*|\2 :\1|p'
221226}
222227
228+ b64url_to_hex () {
229+ _len=$( printf ' %s' " $1 " | wc -c | tr -d ' ' )
230+ _mod=$(( _len % 4 ))
231+ case $_mod in
232+ 2) _pad=" ==" ;;
233+ 3) _pad=" =" ;;
234+ * ) _pad=" " ;;
235+ esac
236+ printf ' %s%s' " $1 " " $_pad " | tr -- ' -_' ' +/' | base64 -d | od -A n -t x1 | tr -d ' \n'
237+ }
238+
239+ get_filemoon_links () {
240+ response=" $( curl -e " $allanime_refr " -s " https://${allanime_base} $1 " -A " $agent " ) "
241+ _fm_json=" $( printf ' %s' " $response " | tr -d ' \n ' | tr ' ,' ' \n' ) "
242+ iv=" $( printf ' %s' " $_fm_json " | sed -nE ' s|^"iv":"([^"]*)"$|\1|p' ) "
243+ payload=" $( printf ' %s' " $_fm_json " | sed -nE ' s|^"payload":"([^"]*)"$|\1|p' ) "
244+ kp1=" $( printf ' %s' " $_fm_json " | sed -nE ' s|^"key_parts":\["([^"]*)"$|\1|p' ) "
245+ kp2=" $( printf ' %s' " $_fm_json " | sed -nE ' s|^"([A-Za-z0-9_-]+)"\]$|\1|p' | head -1) "
246+ _kp1_hex=" $( b64url_to_hex " $kp1 " ) "
247+ _kp2_hex=" $( b64url_to_hex " $kp2 " ) "
248+ key_hex=" $_kp1_hex$_kp2_hex "
249+ iv_hex=" $( b64url_to_hex " $iv " ) 00000002"
250+ tmp=" $( mktemp) "
251+ _fm_len=$( printf ' %s' " $payload " | wc -c | tr -d ' ' )
252+ _fm_mod=$(( _fm_len % 4 ))
253+ case $_fm_mod in
254+ 2) _fm_pad=" ==" ;;
255+ 3) _fm_pad=" =" ;;
256+ * ) _fm_pad=" " ;;
257+ esac
258+ printf ' %s%s' " $payload " " $_fm_pad " | tr -- ' -_' ' +/' | base64 -d > " $tmp "
259+ ct_len=$(( $(wc - c < "$tmp ") - 16 ))
260+ plain=" $( dd if=" $tmp " bs=1 count=" $ct_len " 2> /dev/null | openssl enc -d -aes-256-ctr -K " $key_hex " -iv " $iv_hex " -nosalt -nopad 2> /dev/null) "
261+ rm -f " $tmp "
262+ printf ' %s' " $plain " | tr ' {}[]' ' \n' |
263+ sed -nE ' s|.*"url":"([^"]*)".*"height":([0-9]+).*|\2 >\1|p;s|.*"height":([0-9]+).*"url":"([^"]*)".*|\1 >\2|p' |
264+ sed ' s|\\u0026|\&|g;s|\\u003D|=|g' | sort -rn
265+ printf " \033[1;32m%s\033[0m Links Fetched\n" " Filemoon" 1>&2
266+ }
267+
223268# gets embed urls, collects direct links into provider files, selects one with desired quality into $episode
224269get_episode_url () {
225- # get the embed urls of the selected episode
226270 # shellcheck disable=SC2016
227271 episode_embed_gql=' query ($showId: String!, $translationType: VaildTranslationTypeEnumType!, $episodeString: String!) { episode( showId: $showId translationType: $translationType episodeString: $episodeString ) { episodeString sourceUrls }}'
228272
229- 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 " ) "
273+ query_hash=" d405d0edd690624b66baba3068e0edc3ac90f1597d898a1ec8db4e5c43c00fec"
274+ query_vars=" {\" showId\" :\" $id \" ,\" translationType\" :\" $mode \" ,\" episodeString\" :\" $ep_no \" }"
275+ query_ext=" {\" persistedQuery\" :{\" version\" :1,\" sha256Hash\" :\" $query_hash \" }}"
276+
277+ encoded_vars=$( printf ' %s' " $query_vars " | sed ' s/"/%22/g; s/:/%3A/g; s/{/%7B/g; s/}/%7D/g; s/,/%2C/g' )
278+ encoded_ext=$( printf ' %s' " $query_ext " | sed ' s/"/%22/g; s/:/%3A/g; s/{/%7B/g; s/}/%7D/g; s/,/%2C/g; s/ /%20/g' )
279+
280+ api_url=" ${allanime_api} /api?variables=${encoded_vars} &extensions=${encoded_ext} "
281+
282+ api_resp=" $( curl -e " https://youtu-chan.com" -s -A " $agent " -H " Origin: https://youtu-chan.com" " $api_url " ) "
283+
284+ if [ -z " $api_resp " ] || ! printf " %s" " $api_resp " | grep -q " tobeparsed" ; then
285+ 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 " ) "
286+ fi
287+
230288 if printf " %s" " $api_resp " | grep -q ' "tobeparsed"' ; then
231289 blob=" $( printf " %s" " $api_resp " | sed -nE ' s|.*"tobeparsed":"([^"]*)".*|\1|p' ) "
232290 resp=" $( decode_tobeparsed " $blob " ) "
@@ -235,7 +293,7 @@ get_episode_url() {
235293 fi
236294 # generate links into sequential files
237295 cache_dir=" $( mktemp -d) "
238- providers=" 1 2 3 4"
296+ providers=" 1 2 3 4 5 "
239297 for provider in $providers ; do
240298 generate_link " $provider " > " $cache_dir " /" $provider " &
241299 done
@@ -263,14 +321,39 @@ search_anime() {
263321time_until_next_ep () {
264322 animeschedule=" https://animeschedule.net"
265323 query=" $( printf " %s\n" " $* " | tr ' ' ' +' ) "
266- curl -s -G " $animeschedule /api/v3/anime" --data " q=${query} " | sed ' s|"id"|\n|g' | sed -nE ' s|.*,"route":"([^"]*)","premier.*|\1|p' | while read -r anime; do
267- 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' )
268- status=" Ongoing"
269- color=" 33"
270- printf " %s\n" " $data "
271- ! (printf " %s\n" " $data " | grep -q " Next Raw Release:" ) && status=" Finished" && color=" 32"
272- printf " Status: \033[1;%sm%s\033[0m\n---\n" " $color " " $status "
273- done
324+
325+ curl -s -G " $animeschedule /api/v3/anime" --data " q=${query} " \
326+ | sed ' s|"id"|\n|g' \
327+ | sed -nE ' s|.*,"route":"([^"]*)","premier.*|\1|p' \
328+ | {
329+ found=0
330+
331+ while read -r anime; do
332+ data=$( curl -s " $animeschedule /anime/$anime " \
333+ | sed ' 1,/"anime-header-list-buttons-wrapper"/d' \
334+ | sed -nE '
335+ s|.*countdown-time-raw" datetime="([^"]*)">.*|Next Raw Release: \1|p;
336+ s|.*countdown-time" datetime="([^"]*)">.*|Next Sub Release: \1|p;
337+ s|.*english-title">([^<]*)<.*|English Title: \1|p;
338+ s|.*main-title".*>([^<]*)<.*|Japanese Title: \1|p
339+ ' )
340+
341+ if printf " %s\n" " $data " | grep -q " Next Sub Release:" ; then
342+ printf " %s\n" " $data "
343+ printf " Status: \033[1;33mOngoing\033[0m\n---\n"
344+ found=1
345+ break
346+ fi
347+
348+ if [ " $found " -eq 0 ]; then
349+ printf " %s\n" " $data "
350+ printf " Status: \033[1;32mFinished/Unknown\033[0m\n---\n"
351+ found=1
352+ break
353+ fi
354+ done
355+ }
356+
274357 exit 0
275358}
276359
@@ -288,9 +371,14 @@ episodes_list() {
288371process_hist_entry () {
289372 ep_list=$( episodes_list " $id " )
290373 latest_ep=$( printf " %s\n" " $ep_list " | tail -n1)
374+ # Update episode count in title
291375 title=$( printf " %s\n" " $title " | sed " s|[0-9]\+ episodes|${latest_ep} episodes|" )
292- ep_no=$( printf " %s" " $ep_list " | sed -n " /^${ep_no} $/{n;p;}" ) 2> /dev/null
293- [ -n " $ep_no " ] && printf " %s\t%s - episode %s\n" " $id " " $title " " $ep_no "
376+ next_ep=$( printf " %s" " $ep_list " | sed -n " /^${ep_no} $/{n;p;}" )
377+ if [ -n " $next_ep " ]; then
378+ printf " %s\t%s - episode %s\n" " $id " " $title " " $next_ep "
379+ else
380+ printf " %s\t%s - no new episode\n" " $id " " $title "
381+ fi
294382}
295383
296384update_history () {
@@ -329,10 +417,6 @@ play_episode() {
329417 [ -z " $episode " ] && get_episode_url
330418 # shellcheck disable=SC2086
331419 case " $player_function " in
332- debug)
333- printf " All links:\n%s\nSelected link:\n" " $links "
334- printf " %s\n" " $episode "
335- ;;
336420 mpv* )
337421 if [ " $no_detach " = 0 ]; then
338422 nohup $player_function $skip_flag --force-media-title=" ${allanime_title} Episode ${ep_no} " " $episode " $subs_flag $refr_flag > /dev/null 2>&1 &
@@ -423,8 +507,7 @@ exit_after_play="${ANI_CLI_EXIT_AFTER_PLAY:-0}"
423507use_external_menu=" ${ANI_CLI_EXTERNAL_MENU:- 0} "
424508external_menu_normal_window=" ${ANI_CLI_EXTERNAL_MENU_NORMAL_WINDOW:- 0} "
425509skip_intro=" ${ANI_CLI_SKIP_INTRO:- 0} "
426- # shellcheck disable=SC2154
427- skip_title=" $ANI_CLI_SKIP_TITLE "
510+ skip_title=" ${ANI_CLI_SKIP_TITLE:- } "
428511[ -t 0 ] || (command -v dmenu && use_external_menu=2)
429512[ -t 0 ] || (command -v rofi && use_external_menu=1)
430513hist_dir=" ${ANI_CLI_HIST_DIR:- ${XDG_STATE_HOME:- $HOME / .local/ state} / ani-cli} "
@@ -526,16 +609,33 @@ esac
526609
527610# searching
528611case " $search " in
529- history)
530- anime_list=$( while read -r ep_no id title; do process_hist_entry & done < " $histfile " )
531- wait
612+ history)
613+ anime_list=$( while read -r ep_no id title; do
614+ process_hist_entry
615+ done < " $histfile " )
532616 [ -z " $anime_list " ] && die " No unwatched series in history!"
617+
618+ # selection
533619 [ -z " ${index##* [!0-9]* } " ] && id=$( printf " %s" " $anime_list " | nl -w 2 | sed ' s/^[[:space:]]//' | nth " Select anime: " | cut -f1)
534620 [ -z " ${index##* [!0-9]* } " ] || id=$( printf " %s" " $anime_list " | sed -n " ${index} p" | cut -f1)
621+
535622 [ -z " $id " ] && exit 1
536- title=$( printf " %s" " $anime_list " | grep " $id " | cut -f2 | sed ' s/ - episode.*//' )
623+ # get original entry from history file (correct source of truth)
624+ hist_entry=$( awk -F ' \t' -v id=" $id " ' $2 == id {print; exit}' " $histfile " )
625+
626+ title=$( printf " %s" " $hist_entry " | cut -f3)
627+ ep_no=$( printf " %s" " $hist_entry " | cut -f1)
628+
537629 ep_list=$( episodes_list " $id " )
538- ep_no=$( printf " %s" " $anime_list " | grep " $id " | cut -f2 | sed -nE ' s/.*- episode (.+)$/\1/p' )
630+ next_ep=$( printf " %s" " $ep_list " | sed -n " /^${ep_no} $/{n;p;}" )
631+ if [ " $search " = " history" ] && [ -z " $next_ep " ]; then
632+ echo " "
633+ echo " Checking next episode for: $title "
634+ echo " -------------------------------------"
635+ time_until_next_ep " $title "
636+ exit 0
637+ fi
638+
539639 allanime_title=" $( printf " %s" " $title " | cut -d' (' -f1 | tr -d ' [:punct:]' ) "
540640 ;;
541641 * )
0 commit comments