.
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | Haya早nime
3 |
4 |
5 | Yet Another Simple but Powerful Anime **Streamer/Downloader**
6 |
7 |
8 | [](https://github.com/Rendevior)
9 | [](https://github.com/fearocanity)
10 | 
11 | 
12 | 
13 |
14 | https://user-images.githubusercontent.com/93582751/174440162-86fe9078-678c-46f4-ba56-19cffb8d8cc2.mp4
15 |
16 |
17 |
18 | ## Installation
19 | ### Android (Termux)
20 | Install Dependencies needed
21 | ```sh
22 | pkg up
23 | pkg in aria2 ffmpeg openssl-tool -y
24 | ```
25 |
26 | Now Download and Install
27 | ```sh
28 | curl -sL -o "${PREFIX}/bin/hayanime" https://raw.githubusercontent.com/TheGlockMisc/hayanime/master/hayanime
29 | chmod +x "${PREFIX}/bin/hayanime"
30 | ```
31 | ___
32 | > **Note**: Setupping MPV for Android is Optional
33 |
34 | Setup MPV (For Android Users)
35 | ```sh
36 | curl -sL -o "${PREFIX}/bin/mpv" "https://raw.githubusercontent.com/TheGlockMisc/mpv-android-argparse/master/mpv"
37 | chmod +x "${PREFIX}/bin/mpv"
38 | ```
39 |
40 | - Open MPV app
41 | - Click ⋮ at the top
42 | - Settings
43 | - Advanced
44 | - Edit mpv.conf
45 | - And insert this:
46 | ```sh
47 | include="/storage/emulated/0/mpv/mpv.tmp"
48 | ```
49 | - Click Save
50 |
51 | ### Windows (Gitbash)
52 |
53 | > **Note**: Install Dependencies using scoop
54 |
55 | • Install Gitbash [here](https://git-scm.com/download/win)
56 | • Run "Gitbash" as Administrator
57 |
58 | Now Download and Install
59 | ```sh
60 | curl -sL -o "/bin/hayanime" https://raw.githubusercontent.com/TheGlockMisc/hayanime/master/hayanime
61 | chmod +x "/bin/hayanime"
62 | ```
63 |
64 | ### Linux | WSL
65 |
66 | Install Dependencies needed
67 | ```sh
68 | sudo aria2 ffmpeg mpv openssl
69 | ```
70 |
71 | Download and Install
72 | ```sh
73 | curl -sL -o "${PREFIX}/bin/hayanime" https://raw.githubusercontent.com/TheGlockMisc/hayanime/master/hayanime
74 | chmod +x "${PREFIX}/bin/hayanime"
75 | ```
76 |
77 | ## Usage
78 | Its Simple..
79 | ```
80 | Usage:
81 | hayanime [-s ] [-q ] [-p ] [--dub] [anime]
82 | hayanime [--hist-limit="n"] [-H] [-s ]
83 | hayanime -v | -h | -D | -u
84 |
85 | Sample Input:
86 | hayanime -s 1 -q high -p vlc One Piece
87 | ```
88 |
89 | To make your life easier, and save your option
90 | ```sh
91 | echo 'alias hayanime="hayanime "' >> ~/.bashrc
92 | ```
93 |
94 | ## Prerequisites
95 | + Essentials
96 | + grep
97 | + curl
98 | + aria2c & FFmpeg (Downloading) / openssl (Decoding)
99 | + sed
100 | + awk
101 | + Players
102 | + MPV **(Linux/Android Users)**
103 | + VLC **(Windows Users)**
104 | + IINA **(MacOS Users)**
105 |
106 | ## Option / Args
107 | ```
108 | Options:
109 | -q [low|normal|high] : Choose specific quality
110 | -s [default|(1-5)] : Select server
111 | -p [vlc|built-in] : Use specific video player (default based on os)
112 | --dub : Watch Dubbed Anime
113 | -d : Download Mode
114 | -H : Play Anime from History
115 | -D : Reset History File
116 | --hist-limit=[n] : Limit the number of history choices to be showed up in stdout (default: 15)
117 | --histup : Update the entries (for checking new episode released)
118 | -u : Update to the latest Version
119 | -v : Print Version and Exit
120 | -h : Print Help
121 | Debugging tools:
122 | -t : Dry Run (for testing servers availability)
123 | --debug : Show Debug (for testing purposes)
124 | ```
125 |
126 | Additional Infos...
127 |
128 | + The quality selection on `-p built-in` and in some servers are not available. (default: Auto)
129 | + Server Selection is optional, Server 1 is fast but most of the time it always break
130 | + Incase of the default ones is not working, please choose the backup.
131 | + Built-in video player is only for termux (Android) users.
132 |
133 | Vibration/Notification Signal, you can identify it when:
134 | - `1 Vibration/s` : Something succeeds without errors
135 | - `2 Vibration/s` : An error or something was failed
136 |
137 | | Exit Codes: | Description |
138 | | ------- | ------- |
139 | | 0 | No Error |
140 | | 1 | An Error occured or the process was destructed by an Exception |
141 | | 2 | Exited using "Iterrupt"
142 |
143 |
144 |
145 | ## Features
146 | + Supported Special Episodes and Range
147 | `Ex. 6.5` | `Ex. 1-5`
148 |
149 | + History Sub-Commands
150 |
151 | | Command | What does it do? |
152 | | -------- | --------- |
153 | | `next` \| `n` | Go to the next page |
154 | | `prev` \| `p` \| `back` | Go to the previous page |
155 | | `update` \| `up` | Update entries |
156 | | `[n]n` | Pick and proceed to next episode |
157 | | `[n]p` | Pick and proceed to previous episode |
158 | | `[n]d` | Delete a specific entry from history |
159 |
160 | + Keybindings on Picker
161 | - Q or ESC — Exit
162 | - W or ↑ — to go up
163 | - S or ↓ — to go down
164 | - PGUP — to go to the First Choice
165 | - PGDN — to go to the Last Choice
166 |
167 | *Other Features will be available near future.*
168 |
169 | ## License
170 | [GPL-3.0 license](https://github.com/TheGlockMisc/hayanime/blob/master/LICENSE)
171 |
172 | ## Bugs & Issues
173 | - If you found some bugs, feel free to create a New Issue (PRs OPEN)
174 |
--------------------------------------------------------------------------------
/hayanime:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # hayanime
4 | # Easy and Lightweight Program that lets you watch anime without interruptions.
5 | #
6 | # Author: Rendevior
7 | # Date: November 23, 2021
8 | # License: GPL-3.0
9 | #
10 | # Github: https://github.com/Rendevior/hayanime
11 | #
12 | # DISCLAIMER
13 | # I don't own or manage any server on the internet that scrapes by this tool,
14 | # and i'm not reponsible of any illegal uses of this tool.
15 | # This tool is for educational purposes only. Use at your own Risk.
16 |
17 | VERSION="v2.4.7"
18 |
19 | # Variables (Default Options)
20 | OWNPID="${$}"
21 | base_url="https://animixplay.to"
22 | base_url_ani="https://6ani.me"
23 | base_url_go="https://gogohd.net"
24 | search_base_api="http://v1.ij7p9towl8uj4qafsopjtrjk.workers.dev/"
25 | user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0"
26 | termcols="${COLUMNS:-$(stty size)}" termcols="${termcols##* }"
27 | currtrunc="${PWD:-$(pwd)}" currtrunc="../${currtrunc##*/}"
28 | defifs="${IFS}"
29 | userguest="$(id -un)" saveterm="$(stty -g)"
30 | server="default"
31 | download_mode="false"
32 | debug_mode="false"
33 | dry_run="false"
34 | hist_file=".hayanihist"
35 | ffmp_temp=".piece" ffmp_purl=".piecesUrl"
36 |
37 | # Default player depends on platform
38 | case "$(uname -a)" in
39 | *Android*) player="built-in" ;;
40 | *Darwin*) player="iina" ;;
41 | *MINGW*|*MSYS*|*WSL*|*[Mm]icrosoft*) player="vlc" ;;
42 | *) player="mpv" ;;
43 | esac
44 |
45 | # PATH env for VLC
46 | export PATH="${PATH}:/mnt/c/Program Files/VideoLAN/VLC:/mnt/c/Program Files (x86)/VideoLAN/VLC:/c/Program Files/VideoLAN/VLC:/c/Program Files (x86)/VideoLAN/VLC"
47 |
48 | # Receive Terminal Signal
49 | trap 'save_terminal ; cleanup_trap ; printf "%b" "\n" ; exit 2' INT HUP
50 |
51 |
52 | # Terminal States and Clean Up
53 | save_terminal(){
54 | printf "\033[?25h" # Show Cursor
55 | printf "\033[0m" # Normal State of Colors
56 | stty "${saveterm}" # Rollback stty settings
57 | IFS="${defifs}" # Set to default IFS
58 | }
59 |
60 | sanitize_terminal(){
61 | printf '%b' "\033[${1}A"
62 | [ -n "${4}" ] && printf '%b' "\033[${4}B"
63 | printf "%${2}s" | sed "s_[[:space:]]_\n$(printf "\033[2K\r")_g"
64 | [ -n "${3}" ] && printf '%b' "\033[${3}A"
65 | }
66 |
67 | hideCursor(){
68 | printf "\033[?25l"
69 | }
70 |
71 | cleanup_trap(){
72 | for temps in tmpvid.ts ~/"${ffmp_temp}" ~/.vstats ~/"${ffmp_purl}" ~/"${hist_file}".tmp; do
73 | [ -e "${temps}" ] && rm -rdf "${temps}"
74 | done
75 | }
76 |
77 |
78 | # Color Processors
79 | err(){
80 | [ -n "${1}" ] && printf "\033[38;5;1m%b\007\007\033[0m\n" "${1}" >&2
81 | return 1
82 | }
83 |
84 | die(){ err "${2}" ; save_terminal ; exit "${1}" ;}
85 | inf(){ printf "\033[38;5;3m%b\033[0m\n" "${1}" ;}
86 | suc(){ printf "\033[38;5;2m%b\033[0m\n" "${1}" ;}
87 |
88 | input_questions(){
89 | printf '\033[38;5;140m ┌─[\033[38;5;175mhayanime%s@%s\033[38;5;140m]─[\033[38;5;175m%s\033[38;5;140m]\n └──╼ \033[0m\033[38;5;147m[+] \033[0m%s' "${VERSION}" "${userguest}" "${currtrunc}" "${1}"
90 | }
91 |
92 |
93 | # Misc and Tools
94 | update_script(){
95 | script_ind="$(curl -sLfk -A "${user_agent}" "https://raw.githubusercontent.com/Rendevior/hayanime/master/hayanime")" || die 1 "An error occured while retrieving from github"
96 | script_ind="$(printf '%s\n' "${script_ind}" | diff -u "${0}" -)"
97 | [ -z "${script_ind}" ] && { suc "Cool, Script is up to date!" ; die 0 ;}
98 | if printf '%s\n' "${script_ind}" | patch -s "${0}" - ; then
99 | suc "\007Script has been sucessfully Updated, Enjoy!"
100 | die 0
101 | else
102 | die 1 "Can't update, An Exception Occured. Or it might be can't write the update to file (Make sure you run it as an Administrator/Sudo user)"
103 | fi
104 | }
105 |
106 | dep_check(){
107 | for deppack; do
108 | if ! command -v "${deppack}" >/dev/null ; then
109 | case "${deppack}" in
110 | xdg-open) err "Built-in player is only for Android Users" ;;
111 | *) err "Program \"${deppack}\" is not installed." ;;
112 | esac
113 | is_err="1"
114 | fi
115 | done
116 | [ "${is_err}" = "1" ] && die 1
117 | }
118 |
119 | dl_manager(){
120 | case "${2}" in
121 | *m3u8*srv=*client=*)
122 | dep_check "ffmpeg"
123 | idx_comp "${main_streaming}"
124 | ffmpeg_prog -user_agent "${user_agent}" -referer "${1}" -i "${2}" -map "0:v:$((${qualvid:-1}-1))" -map "0:a:$((${qualvid:-1}-1))" -c copy "${3}"
125 | xco="${?}"
126 | ;;
127 | *.m3u8*)
128 | dep_check "ffmpeg" "openssl" "aria2c"
129 | ffmpegdler "${2}" "${1}" "${qualvid}" "${3}"
130 | xco="${?}"
131 | ;;
132 | *)
133 | dep_check "aria2c" ; printf '\n'
134 | aria2c -x 5 -s 5 --console-log-level="error" --user-agent="${user_agent}" --referer="${1}" --check-certificate="false" --summary-interval="0" --download-result="hide" --auto-save-interval="0" "${2}" -o "${3}"
135 | xco="${?}"
136 | ;;
137 | esac
138 | if [ "${xco}" = "0" ]; then
139 | suc "\007\nDownloading Episode ${epi_selected} Done!!"
140 | else
141 | err "Downloading Episode ${epi_selected} Failed..."
142 | fi
143 | }
144 |
145 | ffmpegdler(){
146 | ts_data_fir="$(curl -sLfk "${1}")" || return 1
147 | quallist_vid_ind="$(printf '%s' "${ts_data_fir}" | sed -nE '/#EXT-X-STREAM/!d;s_.*x([0-9]*),.*_\1_p' | sort -nr)"
148 | [ "${3}" = "last" ] && selc_pix="$(printf '%s' "${quallist_vid_ind}" | tail -n 1)"
149 | [ -z "${selc_pix}" ] && selc_pix="$(printf '%s' "${quallist_vid_ind}" | sed -n "${3}"p)"
150 | selc_qual="$(printf '%s' "${ts_data_fir}" | sed -nE "/x${selc_pix}/{n;p;q}")"
151 | [ -z "${selc_qual}" ] && inf "Quality Not Found, Switching to High" && selc_qual="$(printf '%s' "${ts_data_fir}" | sed -nE "/x$(printf '%s' "${quallist_vid_ind}" | head -n 1)/{n;p;q}")"
152 | [ -d ~/"${ffmp_temp}" ] || mkdir -p ~/"${ffmp_temp}"
153 | # Get Relative Path, when it is not fully sourced into stream url
154 | printf '%s' "${selc_qual}" | grep -q 'http' || relpath="${1%/*}/"
155 | aud_perf="$(printf '%s' "${ts_data_fir}" | sed -nE 's_^#EXT-X-MEDIA:TYPE=AUDIO.*audio-0.*URI="([^\"]*)".*_\1_p')"
156 | # 2nd step get the 2nd layer which is the TS files
157 | ts_data_sec="$(curl -sLfk "${relpath}${selc_qual}")" || return 1
158 | enc_key="$(printf "%s" "${ts_data_sec}" | sed -nE 's_^#EXT-X-KEY.*URI="([^"]*)"_\1_p')"
159 | if [ -n "${enc_key}" ]; then
160 | dec_key="$(curl -sLk "${enc_key}" | od -A n -t x1 | tr -d ' |\n')"
161 | enc_iv="$(printf "%s" "${ts_data_sec}" | sed -nE 's_^#EXT-X-IV.*URI="([^"]*)"_\1_p')"
162 | [ -z "${enc_iv}" ] && dec_iv="$(openssl rand -hex 16)" || dec_iv="$(curl -sLk "${enc_iv}" | od -A n -t x1 | tr -d ' |\n')"
163 | fi
164 | pieces_data="$(printf "%s" "${ts_data_sec}" | sed '/^#/d')"
165 | if printf '%s' "${pieces_data}" | grep -q 'http'; then
166 | unset relpath
167 | else
168 | relpath="${1%/*}/"
169 | fi
170 | total_pieces="$(printf '%s\n' "${pieces_data}" | awk 'BEGIN{c=0} //{c++} END{print c}')"
171 | printf '%s' "${pieces_data}" | awk -v "r=${relpath}" '{printf("%s%s\n %s%04d%s\n", r, $1, "out=", NR, ".ts")}' > ~/"${ffmp_purl}"
172 | aria2c -x 16 -s 16 -j 16 -i ~/"${ffmp_purl}" -d ~/"${ffmp_temp}" --console-log-level="error" --check-certificate="false" --download-result="hide" --summary-interval="0"
173 | if [ -n "${enc_key}" ]; then
174 | for i in ~/"${ffmp_temp}"/*; do
175 | : "$((in+=1))"
176 | progbar "${in}" "${total_pieces}" "Decrypting..."
177 | openssl aes-128-cbc -d -K "${dec_key}" -iv "${dec_iv}" -nopad < "${i}" >> "tmpvid.ts"
178 | done
179 | else
180 | cat ~/"${ffmp_temp}"/* >> "tmpvid.ts"
181 | fi
182 | # Check if audio was seperated to ts streams to merge it
183 | if [ -n "${aud_perf}" ]; then
184 | ffmpeg_prog -i "${aud_perf}" -i "tmpvid.ts" -c copy "${4}"
185 | else
186 | ffmpeg_prog -i "tmpvid.ts" -c copy "${4}"
187 | fi
188 | cleanup_trap
189 | # Unset all variables for reusing function
190 | unset ts_data_fir quallist_vid_ind selc_pix selc_qual relpath ts_data_sec enc_key dec_key enc_iv dec_iv pieces_data total_pieces in
191 | return 0
192 | }
193 |
194 | hist_handler(){
195 | [ -e ~/"${hist_file}" ] || { : > ~/"${hist_file}";} >/dev/null 2>&1
196 | if [ "${1}" = "append" ]; then
197 | [ -w ~/"${hist_file}" ] || err "Hist file is Unwritable, Make sure you have access to write to HOME directory/file" || return 1
198 | printf '%s\n' "$(sed "/${3}/d" ~/"${hist_file}")" > ~/"${hist_file}"
199 | printf '%s¢%s€%s£%s\n' "${2##*"${base_url}"}" "${3}" "${4}" "${5}" >> ~/"${hist_file}"
200 | else
201 | hist_limt="${hist_limt:-15}"
202 | while true; do
203 | hist_count_or="$(grep -c '[^[:space:]]' ~/"${hist_file}")"
204 | main_interface
205 | case "${hist_picked}" in
206 | cont|[Nn]ext|[Pp]rev|[Bb]ack|[Uu]pdate|[Nn]|[Pp]|[Bb]|[Uu][Pp])
207 | [ "${hist_picked}" = "cont" ] && [ "${hist_count_persi}" = "1" ] && : "$((page_comp -= hist_limt))" "$((cur_page_hist -= 1))"
208 | hist_data="$(sed -E '1!G;h;$!d' ~/"${hist_file}" | sed -E "$((page_comp + 1)),$((page_comp + hist_limt))"'!d')"
209 | ;;
210 | *)
211 | hist_data="$(sed -E '1!G;h;$!d' ~/"${hist_file}" | head -n "${hist_limt}")"
212 | ;;
213 | esac
214 | hist_count="${hist_count_or}"
215 | hist_count_persi="$(printf '%s' "${hist_data}" | grep -c '[^[:space:]]')"
216 | unset pages_total
217 | until [ -z "${hist_count%%-*}" ] || [ "${hist_count}" = "0" ]; do
218 | : "$((hist_count -= hist_limt))" "$((pages_total += 1))"
219 | done
220 | [ "${hist_count_persi}" = "0" ] && die 1 "History File is Empty, Watch anime first"
221 | table_ui "hist" "${hist_data}"
222 | input_questions ">> "
223 | read -r hist_picked
224 | while true; do
225 | if printf '%s' "${hist_picked}" | grep -qE '(^[1-9][0-9]*$|^[1-9][0-9]*[NPDnpd]$)'; then
226 | hist_picked_d="${hist_picked%%[NPDnpd]*}"
227 | if [ "${hist_picked_d:-$((hist_count_persi + 2))}" -gt "${hist_count_persi}" ]; then
228 | err "Out of Bounds... Pick between 1-${hist_count_persi}, or type 0 to exit."
229 | input_questions ">> "
230 | read -r hist_picked
231 | continue
232 | else
233 | break
234 | fi
235 | elif printf '%s' "${hist_picked}" | grep -qE '^(0.*|[Nn]ext|[Pp]rev|[Bb]ack|[Uu]pdate|[Nn]|[Pp]|[Bb]|[Uu][Pp])$'; then
236 | break
237 | else
238 | err "Invalid Input... Pick between 1-${hist_count_persi}, or type 0 to exit."
239 | input_questions ">> "
240 | read -r hist_picked
241 | fi
242 | done
243 | if printf '%s' "${hist_picked}" | grep -qE '^[1-9][0-9]*[Nn]$'; then
244 | solv="1" hist_picked="${hist_picked_d}"
245 | elif printf '%s' "${hist_picked}" | grep -qE '^[1-9][0-9]*[Pp]$'; then
246 | solv="-1" hist_picked="${hist_picked_d}"
247 | elif printf '%s' "${hist_picked}" | grep -qE '^[1-9][0-9]*[Dd]$'; then
248 | calc_indhist="$(( 1 + hist_count_or - (hist_limt * ${cur_page_hist:-1} - hist_limt + hist_picked_d) ))"
249 | printf '%s\n' "$(sed "${calc_indhist}"d ~/"${hist_file}")" > ~/"${hist_file}"
250 | suc "The #${hist_picked_d} entry on Page ${cur_page_hist:-1} was successfully deleted"
251 | sleep 0.5 && hist_picked="cont"
252 | continue
253 | fi
254 | case "${hist_picked}" in
255 | [Nn]ext|[Nn])
256 | if [ "${cur_page_hist}" -ge "${pages_total}" ]; then
257 | err "Theres no next page"
258 | sleep 0.5
259 | else
260 | : "$((page_comp += hist_limt))" "$((cur_page_hist += 1))"
261 | fi
262 | ;;
263 | [Pp]rev|[Pp]|[Bb]ack|[Bb])
264 | if [ "${cur_page_hist}" -le "1" ]; then
265 | err "Theres no previous page"
266 | sleep 0.5
267 | else
268 | : "$((page_comp -= hist_limt))" "$((cur_page_hist -= 1))"
269 | fi
270 | ;;
271 | [Uu]pdate|[Uu][Pp]) hist_update ; sleep 1 ;;
272 | 0*) save_terminal && exit 0 ;;
273 | *) break ;;
274 | esac
275 | done
276 | # Assign the Infos by declaring
277 | hist_data="$(printf '%s' "${hist_data}" | sed -E "${hist_picked}"'!d')"
278 | query_result="${hist_data%%¢*}#"
279 | ani_searched="${hist_data##"${hist_data%%¢*}"¢}" ani_searched="${ani_searched%%€*}"
280 | ani_selected="1" qual_stream="high" query_count="1"
281 | epi_selected="${hist_data#*€}" epi_selected="${epi_selected%%£*}"
282 | if printf '%s' "${epi_selected%%/*}" | grep -q '[0-9]*\.[0-9]*'; then
283 | [ -n "${solv}" ] && epi_selected="${epi_selected%%/*}" && epi_selected="$(( ${epi_selected%%.[0-9]*} + ${solv:-0} ))" || epi_selected="${epi_selected%%/*}"
284 | else
285 | epi_selected="$(( ${epi_selected%%/*} + ${solv:-0} ))"
286 | fi
287 | player="${hist_data##*£}"
288 | fi
289 | }
290 |
291 | hist_update(){
292 | [ -e ~/"${hist_file}".tmp ] && rm -f ~/"${hist_file}".tmp
293 | hist_count="$(grep -c '[^[:space:]]' ~/"${hist_file}")"
294 | [ "${hist_count}" = "0" ] && die 1 "Theres no to update, History is Empty"
295 | printf "\033[38;5;3m\033[2K\r%b\033[0m" "Checking..."
296 | curl -sLfk "${base_url}/v1/spy-x-family" -A "${user_agent}" > /dev/null 2>&1 || die 1 "\033[2K\rServer can't be reach..."
297 | while read -r entry; do
298 | : "$((entriter += 1))"
299 | cur_ep="${entry#*€}" cur_ep="${cur_ep%%£*}" last_tot_ep="${cur_ep##*/}" cur_ep="${cur_ep%%/*}"
300 | tot_ep="$(curl -sLk "${base_url}${entry%%¢*}" -A "${user_agent}" | sed -nE 's_.*eptotal":([^\,\}]*)(,.*|\}> ~/"${hist_file}".tmp
302 | [ "${debug_mode}" = "true" ] && [ -n "${tot_ep}" ] && [ "${last_tot_ep}" -lt "${tot_ep}" ] && ani_updated="${entry##"${entry%%¢*}"¢}" ani_updated="${ani_updated%%€*}" && debug_handler ani-updated "${ani_updated}" "$(( tot_ep - last_tot_ep ))" "${tot_ep}"
303 | progbar "${entriter}" "${hist_count}" "Updating..."
304 | unset cur_ep tot_ep last_tot_ep
305 | done < ~/"${hist_file}"
306 | if grep -qE '\€([0-9]*)/\£' ~/"${hist_file}".tmp || grep -qv '[^[:space:]]' ~/"${hist_file}".tmp; then
307 | cleanup_trap
308 | die 1 "\033[2K\rFailed to update for some reason..."
309 | fi
310 | mv ~/"${hist_file}".tmp ~/"${hist_file}" && cleanup_trap
311 | unset entriter
312 | suc "\033[2K\rHistory file successfully updated!"
313 | }
314 |
315 | idx_comp(){
316 | stream_ind="$(curl -sLfk "${1}" -A "${user_agent}" | grep '#EXT-X-STREAM-INF')" || qualvid="auto"
317 | [ "${qualvid}" = "auto" ] && return 1
318 | for qual_var in ${qualvidman} 1080 720 480 360 240; do
319 | qualvid="$(printf '%s' "${stream_ind}" | grep -Fn "${qual_var}")" qualvid="${qualvid%%:*}"
320 | [ -n "${qualvid}" ] && break
321 | inf "Quality not found, Switching to highest quality as possible"
322 | unset qualvid
323 | done
324 | [ -z "${qualvid}" ] && err "Can't get the Quality, switching to auto" && qualvid="auto"
325 | }
326 |
327 | est_rel_time(){
328 | malid="$(curl -sLk "${1}" -A "${user_agent}" | sed -nE 's_.*var malid.=.\x27([^\x27]*)\x27\;.*_\1_p')"
329 | epoch_active="$(curl -sLk "${base_url}/assets/s/schedule.json" | sed -nE 's_.*malid":"'"${malid}"'","time":"([^\"]*)".*_\1_p')"
330 | [ -z "${epoch_active}" ] && return 1
331 | epoch_now="$(($(awk 'BEGIN {srand(); print srand()}') - epoch_active))"
332 | printf '%02dd:%02dh:%02dm:%02ds' "$((epoch_now / 24 / 60 / 60 % 60))" "$((epoch_now / 60 / 60 % 60))" "$((epoch_now / 60 % 60))" "$((epoch_now % 60))"
333 | }
334 |
335 |
336 | # Debuggers and Testing
337 | debug_handler(){
338 | case "${1}" in
339 | # Searchers
340 | total-quer) printf '\033[38;5;226m[\033[0m\033[38;5;8mDebugger\033[38;5;66m@\033[38;5;72msearch\033[38;5;226m] \033[0m%s\n' "Total API search response: ${2}" ;;
341 | total-eps) printf '\033[38;5;226m[\033[0m\033[38;5;8mDebugger\033[38;5;66m@\033[38;5;72msearch\033[38;5;226m] \033[0m%s\n' "Total Episodes: ${2}" ;;
342 | speci-eps) printf '\033[38;5;226m[\033[0m\033[38;5;8mDebugger\033[38;5;66m@\033[38;5;72msearch\033[38;5;226m] \033[0m%s\n' "Special Episodes: ${2}" ;;
343 | # Misc
344 | took-dur)
345 | took_dur="$(($(awk 'BEGIN {srand(); print srand()}') - took_dur))"
346 | printf '\033[38;5;226m[\033[0m\033[38;5;8mDebugger\033[38;5;66m@\033[38;5;72mmisc\033[38;5;226m] \033[0m%s\n' "Took $(printf '%02dm:%02ds' "$((took_dur / 60 % 60))" "$((took_dur % 60))")"
347 | unset took_dur
348 | ;;
349 | serv-current) printf '\033[38;5;226m[\033[0m\033[38;5;8mDebugger\033[38;5;66m@\033[38;5;72mmisc\033[38;5;226m] \033[0m%s\n' "Current Server: ${2}" ;;
350 | show-inf)
351 | shift 1
352 | while IFS= read -r line; do
353 | printf '\033[38;5;226m[\033[0m\033[38;5;8mDebugger\033[38;5;66m@\033[38;5;72mmisc\033[38;5;226m] \033[0m%s\n' "${line}"
354 | done <<-EOF
355 | ${*}
356 | EOF
357 | ;;
358 | # Scrapers
359 | ani-id) printf '\033[38;5;226m[\033[0m\033[38;5;8mDebugger\033[38;5;66m@\033[38;5;72mscraper\033[38;5;226m] \033[0m%s\n' "Anime ID: ${2}" ;;
360 | ani-url) printf '\033[38;5;226m[\033[0m\033[38;5;8mDebugger\033[38;5;66m@\033[38;5;72mscraper\033[38;5;226m] \033[0m%s\n' "Anime Index URL: ${2}" ;;
361 | vid-url) printf '\033[38;5;226m[\033[0m\033[38;5;8mDebugger\033[38;5;66m@\033[38;5;72mscraper\033[38;5;226m] \033[0m%s\n\n' "Direct Stream URL: ${2}" ;;
362 | # History
363 | ani-updated) printf '\033[2K\r\033[38;5;226m[\033[0m\033[38;5;8mDebugger\033[38;5;66m@\033[38;5;72mhist\033[38;5;226m] \033[0m%b\n' "\"${2}\" has ${3} new episode, Latest EP is: ${4}" ;;
364 | esac
365 | }
366 |
367 |
368 | # UI Setup Design
369 | main_interface(){
370 | clear
371 | while IFS= read -r line; do
372 | rnum="$(($$ % 60))"
373 | printf "\033[1m\033[38;5;${rnum}m%*s\033[0m\n" "$(((${#line}+termcols)/2))" "${line}"
374 | done <<-"EOF"
375 | __ __ _
376 | / / / /___ ___ ______ _____ (_)___ ___ ___
377 | / /_/ / __ `/ / / / __ `/ __ \/ / __ `__ \/ _ \
378 | / __ / /_/ / /_/ / /_/ / / / / / / / / / / __/
379 | /_/ /_/\__,_/\__, /\__,_/_/ /_/_/_/ /_/ /_/\___/
380 | /____/
381 | EOF
382 | while IFS= read -r line; do
383 | printf "%*s\n" "$(((${#line}+termcols)/2))" "${line}"
384 | done <<-EOF
385 |
386 | Version: ${VERSION}
387 | Lightweight yet Super fast...
388 |
389 | EOF
390 | }
391 |
392 | show_help(){
393 | printf "%b\n" "Easy and Lightweight Program that lets you watch anime without interruptions.
394 |
395 | Usage:
396 | ${0##*/} [-s ] [-q ] [-p ] [--dub] [anime]
397 | ${0##*/} [--hist-limit=\"n\"] [-H] [-s ]
398 | ${0##*/} -v | -h | -D | -u
399 |
400 | Options:
401 | -q [low|normal|high]\t: Choose specific quality
402 | -s [default|(1-5)]\t\t: Select server
403 | -p [vlc|built-in]\t\t: Use specific video player (default based on OS)
404 | --dub\t\t\t: Watch Dubbed Anime
405 | -d\t\t\t\t: Download Mode
406 | -H\t\t\t\t: Play Anime from History
407 | -D\t\t\t\t: Reset History File
408 | --hist-limit=[n]\t\t: Limit the number of history choices to be showed up in stdout (default: 15)
409 | --histup\t\t\t: Update the entries (for checking new episode released)
410 | -u\t\t\t\t: Update to the latest Version
411 | -v\t\t\t\t: Print Version and Exit
412 | -h\t\t\t\t: Print Help
413 |
414 | Debugging Tools:
415 | -t\t\t\t\t: Dry Run (for testing servers availability)
416 | --debug\t\t\t: Show Debug (for testing purposes)
417 |
418 | The quality selection on \"-p built-in\" and in some servers are not available.
419 | Server Selection is optional, Server 1 is fast but most of the time it
420 | always break. Incase of the default ones is not working, please choose
421 | the backup. Built-in video player is only for termux (Android) users.
422 |
423 | Vibration/Notification Signal, you can identify it when:
424 | | 1 Vibration/s | : Something succeeds without errors
425 | | 2 Vibration/s | : An error or something was failed
426 |
427 | Exit Codes:
428 | [0] : No Error
429 | [1] : An Error occured or the process was destructed by an Exception
430 | [2] : Exited using \"Iterrupt\" signal
431 |
432 | Servers:
433 | [any] : Default server (superfast server | m3u8)
434 | [1] : Gogoanime Server (fast server | m3u8)
435 | [2] : SBPlay Server (mid fast-ish server 2-4mbps | m3u8)
436 | [3] : FEmbed Server (fast server | mp4)
437 | [4] : Dood Server (mid speed server | mp4)
438 | [5] : MP4up server (mid fast-ish server | mp4)
439 |
440 | More info: ."
441 | }
442 |
443 | table_ui(){
444 | [ "${1}" = "hist" ] && { printf '\n%*s\n' "$((termcols - 5))" "Page ${cur_page_hist:=1} of ${pages_total:-1}" ; printf "\033[48;5;59m%3s#%3s\033[0m\033[48;5;238m%2sAnime Name%$((termcols - 30))s\033[0m\033[48;5;59m%2sEpisode%2s\033[0m\n" ;} || printf "\n\033[48;5;59m%3s#%3s\033[0m\033[48;5;238m%2sAnime Name%$((termcols - 30))s\033[0m\033[48;5;59m%2sReleased%1s\033[0m\n"
445 | while IFS= read -r line; do
446 | : "$((coldep += 1))"
447 | len="$((termcols - ${#line}))"
448 | [ "${coldep}" = "1" ] && printf "\033[48;5;236m%s%${len}s\033[0m" "${line}" || printf "\033[48;5;235m%s%${len}s\033[0m" "${line}"
449 | [ "${coldep}" = "2" ] && unset coldep
450 | done <<-EOF
451 | $(printf '%s' "${2}" | sed -E 's_.*¢(.*)£.*_\1_g' | sed -E "s_(.{$((termcols-26))}).*(.{5})(€.*)_\\1...\\2\\3_g;\$a\\$(printf "%*s" "$((termcols-11))" "€2022")\\" | nl -w4 -s" €" | column -s "€" -t | sed '$d')
452 | EOF
453 | printf "\033[48;5;59m%7s\033[0m\033[48;5;238m%$((termcols - 18))s\033[0m\033[48;5;59m%11s\033[0m\n\n"
454 | unset line len coldep
455 | }
456 |
457 | choose_from_menu(){
458 | prompt="${1}"
459 | shift 1
460 | options="${*}" cur="0" index="0"
461 | count="$(printf '%s' "${options}" | awk 'END{print NR}')"
462 | up="$(printf "\033[A")" down="$(printf "\033[B")" escchar="$(printf "\033")" pgup="$(printf "\033[5~")" pgdn="$(printf "\033[6~")"
463 | hideCursor
464 | stty -echo -icanon min 1 time 0
465 | printf '%b\n' "${prompt}"
466 | while true; do
467 | index="0"
468 | IFS='
469 | '
470 | unset func_off
471 | { [ "${player}" = "vlc" ] || [ "${player}" = "built-in" ] ;} && [ "${server}" != "fembed" ] && [ "${cur}" = "6" ] && func_off="yes"
472 | for o in ${options}; do
473 | if [ "${index}" = "${cur}" ]; then
474 | [ "${func_off}" = "yes" ] && printf '%b\n' " \033[0;32mᐅ\033[0m \033[48;5;1m${o}\033[0m" || printf '%b\n' " \033[0;32mᐅ\033[0m \033[7m${o}\033[0m"
475 | else
476 | printf '%s\n' " ${o}"
477 | fi
478 | : "$(( index += 1 ))"
479 | done
480 | IFS="${defifs}"
481 | key="$(dd bs=10 count=1 2>/dev/null)"
482 | case "${key}" in
483 | "${up}"|[Ww])
484 | : "$(( cur -= 1 ))"
485 | [ "${cur}" -lt "0" ] && cur="0"
486 | ;;
487 | "${down}"|[Ss])
488 | : "$(( cur += 1 ))"
489 | [ "${cur}" -ge "${count}" ] && cur="$(( count - 1 ))"
490 | ;;
491 | "${escchar}"|[Qq]) die 0 ;;
492 | [Hh])
493 | sanitize_terminal "$((count + 1))" "$((count + 1))" "$((count + 1))"
494 | while IFS= read -r lines; do
495 | suc "${lines}"
496 | done <<-EOF
497 |
498 | Keybindings:
499 | Q or ESC\t\t: Exit
500 | W or Button UP\t: to go up
501 | S or Button Down\t: to go down
502 | PGUP\t\t\t: to go to the First Choice
503 | PGDN\t\t\t: to go to the Last Choice
504 |
505 | Press ENTER to Continue...
506 | EOF
507 | read -r at && : "${at}" && unset at
508 | sanitize_terminal 9 10 # Sanitize Terminal
509 | [ "${count}" -ge "8" ] && printf '%b' "\033[$((8-count))A" || printf '%b' "\033[$((9-count))A"
510 | ;;
511 | "${pgup}") cur="0" ;;
512 | "${pgdn}") cur="$((count - 1))" ;;
513 | "") menu_result_menu="${cur}" && save_terminal && break ;;
514 | esac
515 | printf '%b\r' "\033[${count}A"
516 | done
517 | }
518 |
519 | ffmpeg_prog(){
520 | inf_meta="$(ffmpeg -user_agent "${user_agent}" -referer "${cust_referer}" -i "${main_streaming}" 2>&1)"
521 | vid_dur="$(printf '%s' "${inf_meta}" | sed -n "s/.* Duration: \([^,]*\), start: .*/\1/p")"
522 | vid_fps="$(printf '%s' "${inf_meta}" | sed -nE '/fps/ {s_.*, (.*) fps.*_\1_p;q}')"
523 | [ -n "${vid_dur}" ] && [ -z "${vid_fps}" ] && vid_fps="$(printf '%s' "${inf_meta}" | sed -nE '/tbr/ {s_.*, (.*) tbr.*_\1_p;q}')"
524 | { [ -z "${vid_dur}" ] || [ -z "${vid_fps}" ] ;} && { err "An Error Occured" ; return 1 ;}
525 | hrs="${vid_dur%%:*}"
526 | mins="$(printf '%s' "${vid_dur}" | cut -d':' -f2)"
527 | secs="${vid_dur##*:}" secs="${secs%%.*}" secs="${secs##0}"
528 | vid_time="$((hrs * 3600 + mins * 60 + secs))"
529 | total_frames="$(awk -v "m=${vid_fps}" -v "s=${vid_time}" 'BEGIN{print int(s*m)}')" total_frames="${total_frames%%.*}"
530 | ffmpeg -progress ~/.vstats -y "${@}" 2>/dev/null &
531 | ffmpeg_pid="${!}"
532 | printf "\033[?25l"
533 | while [ -e "/proc/${ffmpeg_pid}" ]; do
534 | if [ -e ~/.vstats ]; then
535 | cur_frame="$(grep "frame=" ~/.vstats | tail -n 1)" cur_frame="${cur_frame##*=}"
536 | last_size="${cur_size}"
537 | cur_size="$(grep "total_size=" ~/.vstats | tail -n 1)" cur_size="${cur_size##*=}"
538 | if [ -n "${cur_frame}" ]; then
539 | progbar "${cur_frame:-0}" "${total_frames}" "Status: ${cur_frame:-0}/${total_frames}"
540 | printf '\n\033[2K%s\n\033[2K%s\n\033[2K%s\033[3A' "ffmpeg PID: ${ffmpeg_pid:-0}" "Length: ${vid_dur:-00:00}" "Speed: $(awk -v "byte=$((cur_size - ${last_size:-0}))" 'BEGIN{ x=1024; y=byte/x/x; printf("%.2f", y)}')mb/hs"
541 | fi
542 | sleep 0.5 # Interval to avoid using too much memory (adjust if you want)
543 | fi
544 | done
545 | save_terminal
546 | sanitize_terminal 5 4 4 4
547 | cleanup_trap
548 | unset inf_meta vid_dur vid_fps hrs mins secs vid_time total_frames ffmpeg_pid cur_frame cur_bitr
549 | }
550 |
551 | progbar(){
552 | prog="$(( ${1} * 100 / ${2} * 100 / 100 ))"
553 | done="$(( (prog * 4) / 10 ))" left="$(( 40 - done ))"
554 | fill="$(printf "\033[7m\033[38;5;$(( (done % 103 / 20) + 101 ))m%${done}s\033[0m")" empty="$(printf "%${left}s")"
555 | printf '\033[2K\r%s — %s : %s' "¦${fill}${empty}¦" "${prog}%" "${3}"
556 | unset prog "done" left fill empty
557 | }
558 |
559 |
560 | # Parsers and Searching
561 | search(){
562 | [ -z "${ani_searched}" ] && { input_questions 'Search Anime Here: ' ; read -r ani_searched ;}
563 | ani_searched="$(printf '%s' "${ani_searched}" | tr -d '[:punct:]')"
564 | }
565 |
566 | result_searched(){
567 | [ "${debug_mode}" = "true" ] && took_dur="$(awk 'BEGIN {srand(); print srand()}')"
568 | query_result="$(curl -sL "${search_base_api}" -d "q2=${1}&root=animixplay.to&d=gogoanime.tel" | tr -d "\\\\" | grep -oP 'name.*?(Released: [0-9]*|\ \;)' | sed -E 's_\ \;_Released: TBA_g;s_.*href="([^\"]*)".*title="([^\"]*)".*Released:.(TBA|[0-9]*).*_\1\#\2 €\3_')"
569 | [ -z "${query_result}" ] && die 1 "Anime \"${ani_searched:-}\" Not found"
570 | [ "${dub_mode}" = 'true' ] && query_result="$(printf '%s' "${query_result}" | grep -E '(-|\()[Dd]ub')" || query_result="$(printf '%s' "${query_result}" | grep -Ev '(-|\()[Dd]ub')"
571 | query_scraped="$(printf '%s' "${query_result}" | cut -d '#' -f2)"
572 | query_count="$(printf '%s' "${query_scraped}" | awk 'BEGIN{c=0} //{c++} END{print c}')"
573 | [ "${query_count}" = "0" ] && die 1 "No Results was Found"
574 | table_ui "normal" "${query_scraped}"
575 | [ "${debug_mode}" = "true" ] && debug_handler took-dur "${took_dur}" && debug_handler total-quer "${query_count}"
576 | }
577 |
578 | selector(){
579 | [ -z "${ani_selected}" ] && { result_searched "${ani_searched}" ; input_questions "Pick Here By Number: " ; read -r ani_selected ;}
580 | ani_selected="$(printf '%s' "${ani_selected}" | tr -dc '0-9')"
581 | while [ "${ani_selected:-$((query_count + 2))}" -gt "${query_count}" ]; do
582 | err "Out of Bounds... Pick between 1-${query_count}, or type 0 to exit."
583 | input_questions 'Pick Here By Number: '
584 | read -r ani_selected
585 | ani_selected="$(printf '%s' "${ani_selected}" | tr -dc '0-9')"
586 | done
587 | printf '%s' "${ani_selected}" | grep -q '^0' && save_terminal && exit 0
588 | anime_selected="$(printf '%s' "${query_result}" | sed -E -e "${ani_selected}"'!d' -e "s@(.*)#.*@${base_url}\1@")"
589 | }
590 |
591 | epi_counter(){
592 | epilist_index="$(curl -sLk "${1}" -A "${user_agent}" | sed -E '/]*>/,/<\/div>/!d;s_"ep0"_"-1"_g')"
593 | epi_total="$(printf '%s' "${epilist_index}" | sed -nE 's_.*eptotal":([^\,\}]*)(,.*|\}([^\<]*).+br>_Genre : \1_p; s_.*span.*status">([^\<]*)<.*_\1_p')"
597 | debug_handler total-eps "${epi_total}"
598 | special_ep="$(printf '%s' "${epilist_index}" | grep -o 'extra.*\},' | grep -oE '"[0-9]*\.[0-9]' | sed -nE 's_"__g;s_.*_&,_;H;$x;$s_,\n_, _g;$s_\n(.*)_\1_;$s_(.*),_\1_;$p')"
599 | [ -n "${special_ep}" ] && debug_handler speci-eps "${special_ep}"
600 | fi
601 | }
602 |
603 | episode_selector(){
604 | epi_counter "${anime_selected}"
605 | if [ -z "${epi_selected}" ]; then
606 | [ "${debug_mode}" = "true" ] && debug_handler show-inf "${aniinf}"
607 | [ "${epi_total}" = '1' ] && inf "Feat: Autoselect when only one episode exist" && epi_selected="1"
608 | [ -z "${epi_selected}" ] && input_questions "Episode [1-${epi_total}]: " && read -r epi_selected
609 | fi
610 | epi_selected="$(printf '%s' "${epi_selected}" | tr -dc '0-9\.\-')"
611 | epi_whole="$(printf '%s' "${epi_selected}" | sed -E 's_(\.[0-9]*|^[0-9]*\-)__g')"
612 | while [ "${epi_whole:-$((epi_total + 2))}" -gt "${epi_total}" ]; do
613 | err "Episode does not exist, or Out of Range"
614 | input_questions "Episode [1-${epi_total}]: "
615 | read -r epi_selected
616 | epi_selected="$(printf '%s' "${epi_selected}" | tr -dc '0-9\.\-')"
617 | epi_whole="$(printf '%s' "${epi_selected}" | sed -E 's_(\.[0-9]*|^[0-9]*\-)__g')"
618 | done
619 | [ -z "${end_ep}" ] && end_ep="${epi_selected##*-}" end_ep="${end_ep%%.[0-9]*}"
620 | epi_selected="${epi_selected%%-*}"
621 | printf '%s' "${epi_selected}" | grep -q '[0-9]*\.[0-9]*' && is_half="true"
622 | [ "${is_half}" != "true" ] && [ "${epi_selected}" -gt "${end_ep:-${epi_selected}}" ] && die 1 "Invalid Range"
623 | anime_name_crafted="$(printf '%s' "${anime_selected}" | sed -E "s@${base_url}/v1/@@g;s_-_ _g;s_\b(.)_\u\1_g")"
624 | file_name_crafted="$(printf '%s' "${anime_selected}" | sed -E "s@${base_url}/v1/@@g;s@\$@-episode-${epi_selected}@g").mp4"
625 | title_crafted="$(printf '%s %s' "${anime_name_crafted}" "Episode ${epi_selected}" )"
626 | }
627 |
628 | qualitypicker(){
629 | if [ -z "${qual_stream}" ]; then
630 | if printf '%s' "${server}" | grep -qE 'dood|mpup' || [ "${player}" = "built-in" ] || { [ "${player}" = "vlc" ] && [ "${server}" = "default" ]; }; then
631 | qual_stream="high"
632 | fi
633 | [ "${download_mode}" = "true" ] && unset qual_stream
634 | [ -z "${qual_stream}" ] && input_questions 'Quality: ' && read -r qual_stream
635 | fi
636 | }
637 |
638 |
639 | # Scraper & Streams
640 | id_grepper(){
641 | if printf '%s' "${epi_selected}" | grep -q '[0-9]*\.[0-9]'; then
642 | idserv="$(printf '%s' "${epilist_index}" | sed -E 's_,"_\n_g;s_\{"_\n_g' | sed -E -e '/^([0-9]*|[0-9]*\.[0-9]*|\-[0-9]*)\x22/!d' -e "/^(${epi_selected})\x22/"'!d' -e 's_.*:"(.*?)".*_\1_' -e 's_\#\#_\n_g' | grep -oE '(ed/video/|com/video/|id=|/videoembed/|player.html#)([^\&\?\#]*)' | sort -V | head -n 1)"
643 | else
644 | idserv="$(printf '%s' "${epilist_index}" | sed -E 's_,"_\n_g;s_\{"_\n_g' | sed -E -e '/^([0-9]*|[0-9]*\.[0-9]*|\-[0-9]*)\x22/!d' -e "/^($((epi_selected - 1)))\x22/"'!d' -e 's_.*:"(.*?)".*_\1_' -e 's_\#\#_\n_g' | grep -oE '(ed/video/|com/video/|id=|/videoembed/|player.html#)([^\&\?\#]*)' | sort -V | head -n 1)"
645 | fi
646 | [ -z "${idserv}" ] && die 1 "No ID Returned, Episode does not Exist."
647 | case "${idserv}" in
648 | *id=*) idserv="${idserv##*id=}" idena="yes" ;;
649 | *player.html#*) idserv="${idserv##*\#}" server="defdir" ;;
650 | *ed/video/*) idserv="${idserv##*/}" server="daily" ;;
651 | *com/video/*) idserv="${idserv##*/}" server="vim" ;;
652 | */videoembed/*) idserv="${idserv##*/}" server="ok" player="mpv" ;;
653 | *) die 1 "Unknown Anime ID returned, please report this bug in Github." ;;
654 | esac
655 | case "${server:-default}" in
656 | gogo|sb|fembed|dood|mpup)
657 | [ "${idena}" = "yes" ] || die 1 "No ID Returned, Default server is only available at this time"
658 | [ "${server}" != "gogo" ] && ind_gogoend="$(curl -m 5 -sLk "${base_url_go}/streaming.php" -d "id=${idserv}")"
659 | ;;
660 | default) [ "${dry_run}" = "true" ] && ind_gogoend="$(curl -m 5 -sLk "${base_url_go}/streaming.php" -d "id=${idserv}")" ;;
661 | esac
662 | }
663 |
664 | streaming_url_server(){
665 | if [ "${download_mode}" != "true" ]; then
666 | case "${server:=default}" in
667 | sb|dood|mpup) [ "${player}" = "built-in" ] && die 1 "Built-in player is not supported in this Server" ;;
668 | ok) [ "${player}" = "built-in" ] && player="mpv" && dep_check "mpv" ;;
669 | esac
670 | fi
671 | case "${server:=default}" in
672 | gogo)
673 | cust_referer="gogoanime.la"
674 | main_streaming="$(curl -m 5 -sLk "${base_url_ani}/stream/gogo.php?id=${1}" | sed -nE '/file:/ {N;s_.*"([^"]*)",.*_\1_g;/[^[:space:]]/p}')"
675 | ;;
676 | sb)
677 | cust_referer="sbplay2.com"
678 | sb_id="$(printf '%s' "${ind_gogoend}" | sed -E '/StreamSB/!d;s_.*data-video="(.*?)".*_\1_;s_.*/e/(.*)_\1_' | tr -d '\n' | od -A n -t x1 | tr -d " |\n")"
679 | sbdata="616e696d646c616e696d646c7c7c${sb_id}7c7c616e696d646c616e696d646c7c7c73747265616d7362"
680 | for try_sources in sources48 sources43 sources40 sources41 sourcesx42 sourcesx38 sources36 sources37; do
681 | main_streaming="$(curl -m 5 -sLk "https://sbplay2.com/${try_sources}/${sbdata}" -H "watchsb: sbstream" -e "${cust_referer}" -A "${user_agent}" | sed -nE 's_.*file":"([^\"]*)",.*_\1_p')"
682 | printf '%s' "${main_streaming}" | grep -q "client=" && break
683 | done
684 | ;;
685 | fembed)
686 | cust_referer="fembed.com"
687 | fm_url="$(printf '%s' "${ind_gogoend}" | sed -E '/Xstreamcdn/!d;s_.*data-video="(.*?)".*_\1_;s_/v/_/api/source/_g')"
688 | resp_ind="$(curl -m 5 -sLk -X POST "${fm_url}" -e "${fm_url}" | sed -E 's_\{"file_\n\{"file_g')"
689 | for qual_var in ${qualvidman} 1080 720 480 360 240; do
690 | main_streaming="$(printf '%s' "${resp_ind}" | sed -E "/${qual_var}p/"'!d'';s_.*file.*(https.*?)\x22,.*label.*_\1_;s_[\]__g')"
691 | [ -n "${main_streaming}" ] && break
692 | unset main_streaming
693 | done
694 | ;;
695 | dood)
696 | dood_id="$(printf '%s' "${ind_gogoend}" | sed -E '/Doodstream/!d;s_.*data-video="(.*?)".*_\1_;s_.*/e/__g')"
697 | cust_referer="https://dood.pm/e/${dood_id}"
698 | dood_path="$(curl -m 10 -kA "${user_agent}" -s "${cust_referer}" | sed -nE 's_.*get\(\x27(/pass\_md5[^\x27]*)\x27,.*_\1_p')"
699 | dood_param="${dood_path##*"/pass_md5/"}" dood_ex="$(awk 'BEGIN {srand(); print srand()}')"
700 | main_streaming="$(curl -m 10 -kA "${user_agent}" -s "https://dood.pm${dood_path}" -e "${cust_referer}")doodstream?token=${dood_param##*/}&expiry=${dood_ex}000"
701 | printf '%s' "${main_streaming}" | grep -vqE 'dood.vid' && unset main_streaming
702 | ;;
703 | mpup)
704 | mpupurl="$(printf '%s' "${ind_gogoend}" | sed -E '/Mp4upload/!d;s_.*data-video="(.*?)".*_\1_')"
705 | main_streaming="$(curl -m 10 -skLA "${user_agent}" "${mpupurl}" | sed -nE 's_.*embed\|(.*)\|.*blank.*\|(.*)\|(.*)\|(.*)\|(.*)\|src.*_https://\1.mp4upload.com:\5/d/\4/\3.\2_p')"
706 | cust_referer="${main_streaming}"
707 | ;;
708 | # Substitute Servers
709 | daily)
710 | cust_referer="https://www.dailymotion.com/"
711 | main_streaming="$(curl -m 5 -sLk "https://www.dailymotion.com/player/metadata/video/${1}" -e "${cust_referer}" | sed -E 's_.*x-mpegURL","url":"(.+?)"}].*_\1_;s_[\]__g')"
712 | ;;
713 | ok)
714 | cust_referer="ok.ru"
715 | main_streaming="$(curl -m 5 -skL "https://ok.ru/videoembed/${1}" | sed -nE 's_.*data-module="OKVideo".*data-options="(.*)".*data-player-container-id.*_\1_p' | sed -E 's_\\u0026_\&_g;s_"\;_\x22_g;s_[\]__g;s_.*(https://.*videoPlayerCdn[^\x22\x27<>]*).*_\1_')"
716 | ;;
717 | vim)
718 | cust_referer="vimeo.com"
719 | [ "${qualvidman}" = "480" ] && qualvidman="520"
720 | main_streaming="$(curl -m 5 -skL "https://player.vimeo.com/video/${1}" | sed -nE 's_.*\{"akfire\_interconnect\_quic":\{"url":"([^\"]*)","origin":"gcs","avc\_url":"[^\"]*".*fastly\_skyfire":\{"url":"([^\"]*)".*_\1€\2_p')"
721 | strip_main="${main_streaming%%€*}"
722 | [ -z "${strip_main}" ] && strip_main="${main_streaming##*€}"
723 | main_streaming="${strip_main}"
724 | ;;
725 | # Catch Direct
726 | defdir)
727 | main_streaming="$(printf '%s' "${1}" | base64 -d)"
728 | cust_referer="${main_streaming}"
729 | ;;
730 | # Main Server
731 | default|*)
732 | [ "${idena}" = "yes" ] || die 1 "No ID Returned, Default server is only available at this time"
733 | encodeb64="$(printf '%s' "${1}" | base64)"
734 | col_craft_st1="$(printf '%s%s%s' "${1}" "LTXs3GrU8we9O" "${encodeb64}" | base64)"
735 | main_streaming="$(curl -m 5 -sk -w "%{redirect_url}" "${base_url}/api/cW9${col_craft_st1}" -A "${user_agent}" | sed -nE 's_.*(https://.*player.html[^\x22\x27<>]*).*_\1_p' | awk -F'#' '{print $2}' | base64 -d)"
736 | cust_referer="${main_streaming}"
737 | ;;
738 | esac
739 | [ "${dry_run}" != "true" ] && [ -z "${main_streaming}" ] && die 1 "Failed to retrieve streams from server"
740 | }
741 |
742 | quality_selector(){
743 | case ${qual_stream:-high} in
744 | [Nn]ormal|[Nn]|720) qualvidman="720" qualvid="2" ;;
745 | [Ww]orst|[Ll]ow|[Ll]|360) [ "${server}" = "fembed" ] && qualvidman="480" || qualvidman="360"
746 | qualvid="last"
747 | ;;
748 | [Bb]est|[Hh]igh|[Hh]|1080|*) qualvidman="1080" qualvid="1" ;;
749 | esac
750 | }
751 |
752 | launch_vid(){
753 | quality_selector
754 | [ "${debug_mode}" = "true" ] && took_dur="$(awk 'BEGIN {srand(); print srand()}')"
755 | streaming_url_server "${idserv}"
756 | case "${player}" in
757 | builtin|built-in)
758 | nohup xdg-open --content-type video/* "${main_streaming}" > /dev/null 2>&1 &
759 | ;;
760 | vlc)
761 | nohup vlc.exe "${main_streaming}" --http-user-agent="${user_agent}" --http-referrer="${cust_referer}" --meta-title="${title_crafted}" > /dev/null 2>&1 &
762 | ;;
763 | iina)
764 | printf '%s' "${main_streaming}" | grep -qE 'manifest|m3u8|master.m3u8' && idx_comp "${main_streaming}"
765 | nohup iina "${main_streaming}" --keep-running --mpv-referrer="${cust_referer}" --mpv-vid="${qualvid:-auto}" --no-stdin --mpv-media-title="${title_crafted}" --mpv-user-agent="${user_agent}" --mpv-tls-verify="no" > /dev/null 2>&1 &
766 | ;;
767 | mpv|*)
768 | printf '%s' "${main_streaming}" | grep -qE 'manifest|m3u8|master.m3u8' && idx_comp "${main_streaming}"
769 | nohup mpv "${main_streaming}" --save-position-on-quit --media-title="${title_crafted}" --referrer="${cust_referer}" --vid="${qualvid:-auto}" --user-agent="${user_agent}" --tls-verify="no" > /dev/null 2>&1 &
770 | ;;
771 | esac
772 | }
773 |
774 |
775 | # Loops and Handlers
776 | loop(){
777 | main_interface
778 | while [ -e "/proc/${OWNPID}" ]; do
779 | [ "${isdont_launch}" = "1" ] || { main ; launch_vid ; unset isdont_launch ;}
780 | main_interface
781 | hist_handler append "${anime_selected}" "${anime_name_crafted}" "${epi_selected}/${epi_total}" "${player}"
782 | trunc_aniname="$(printf '%s' "${anime_name_crafted}" | sed -E "/.{$((termcols-18))}/ s_(.{$((termcols-31))}).*(.{7}\$)_\\1...\\2_g")"
783 | formatter_title="$(printf '%b' "Now Playing: \033[4m\033[48;5;240m${trunc_aniname}\033[0m [${epi_selected}/${epi_total}]")"
784 | printf "%*s\n\n" "$(((${#formatter_title}+termcols+18)/2))" "${formatter_title}"
785 | [ "${debug_mode}" = "true" ] && debug_handler took-dur "${took_dur}" && debug_handler serv-current "${server}" && debug_handler ani-id "${idserv}" && debug_handler ani-url "${anime_selected}" && debug_handler vid-url "${main_streaming}"
786 | [ "${is_half}" != "true" ] && [ "${end_ep:-${epi_selected}}" -gt "${epi_total}" ] && die 1 "Out of Range"
787 | if [ "${epi_selected%%.[0-9]*}" -lt "${end_ep:-${epi_selected%%.[0-9]*}}" ]; then
788 | : "$((epi_selected += 1))" && continue
789 | else
790 | unset end_ep
791 | fi
792 | inf "Press H to show keybindings..."
793 | menu_text="$(printf '%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n' "Exit This Program" "Relaunch Episode Again" "Next Episode" "Back to Previous Episode" "Select Episode" "Search For Another Anime" "Change Quality" "Download Episode")"
794 | choose_from_menu "Pick based on your suit needs:" "${menu_text}"
795 | [ -z "${menu_result_menu}" ] && die 1 "Unknown Error Appeared"
796 | case "${menu_result_menu}" in
797 | 0) die 0 ;;
798 | 1) continue ;;
799 | 2)
800 | printf '%s' "${epi_selected}" | grep -q '[0-9]*\.[0-9]*' && epi_selected="${epi_selected%%.[0-9]*}"
801 | : "$((epi_selected += 1))"
802 | [ "${epi_selected}" -gt "${epi_total}" ] && { err "\nCannot be proceed!! \033[38;5;3mnext episodes estimated release: $(est_rel_time "${anime_selected}" || printf '%s' "(The series is completed)")" ; sleep 3 ; isdont_launch="1" ; : "$((epi_selected -= 1))" ;}
803 | unset main_streaming encodeb64 idserv
804 | ;;
805 | 3)
806 | printf '%s' "${epi_selected}" | grep -q '[0-9]*\.[0-9]*' && epi_selected="${epi_selected%%.[0-9]*}"
807 | : "$((epi_selected -= 1))"
808 | unset main_streaming encodeb64 idserv
809 | ;;
810 | 4)
811 | unset epi_selected main_streaming encodeb64 idserv end_ep
812 | main_interface
813 | continue
814 | ;;
815 | 5)
816 | unset ani_searched epi_selected ani_selected qual_stream main_streaming encodeb64 idserv
817 | main_interface
818 | continue
819 | ;;
820 | 6)
821 | unset menu_result_menu qual_stream main_streaming encodeb64 idserv
822 | if [ "${player}" = "vlc" ] || [ "${player}" = "built-in" ]; then
823 | [ "${server}" = "fembed" ] || die 1 "Switching quality in (VLC & builtin) players is not available"
824 | fi
825 | menu_text="$(printf '%s\n%s\n%s\n' "High (1080p/Auto)" "Normal (720p)" "Low (480p)")"
826 | choose_from_menu "\nPick and change your quality:" "${menu_text}"
827 | case "${menu_result_menu}" in
828 | 0) qual_stream="high" ;;
829 | 1) qual_stream="normal" ;;
830 | 2) qual_stream="low" ;;
831 | esac
832 | continue
833 | ;;
834 | 7)
835 | menu_text="$(printf '%s\n%s\n%s\n%s\n%s\n%s\n' "Current Server" "Server 1 (mp4)" "Server 2 (mp4)" "Server 3 (mp4)" "Server 4 (m3u8)" "Server 5 (m3u8)")"
836 | choose_from_menu "\nWhat server do you want to use?:" "${menu_text}"
837 | case "${menu_result_menu}" in
838 | 0) curserv="true" && dl_manager "${cust_referer}" "${main_streaming}" "${file_name_crafted}" ;;
839 | 1) server="mpup" ;;
840 | 2) server="dood" ;;
841 | 3) server="fembed" ;;
842 | 4) server="default" ;;
843 | 5) server="gogo" ;;
844 | esac
845 | download_mode="true"
846 | { [ "${curserv}" = "true" ] && die 0 ;} || { unset menu_result_menu qual_stream main_streaming encodeb64 idserv ; range_dl_handler ;}
847 | ;;
848 | esac
849 | unset menu_result_menu
850 | done
851 | }
852 |
853 | range_dl_handler(){
854 | search
855 | selector
856 | episode_selector
857 | qualitypicker
858 | [ "${is_half}" != "true" ] && [ "${end_ep:-${epi_selected}}" -gt "${epi_total}" ] && die 1 "Out of Range"
859 | while [ "${epi_selected%%.[0-9]*}" -le "${end_ep:-${epi_selected%%.[0-9]*}}" ]; do
860 | episode_selector
861 | qualitypicker
862 | id_grepper
863 | quality_selector
864 | streaming_url_server "${idserv}"
865 | dl_manager "${cust_referer}" "${main_streaming}" "${file_name_crafted}"
866 | : "$((epi_selected += 1))"
867 | done
868 | die 0
869 | }
870 |
871 | dryrun_handler(){
872 | main_interface
873 | qual_stream="high"
874 | main
875 | main_interface
876 | [ "${debug_mode}" = "true" ] && debug_handler ani-url "${anime_selected}" && debug_handler ani-id "${idserv}"
877 | inf "\nM3u8 Servers"
878 | unset server player
879 | for server in default gogo sb mp4_var fembed dood mpup; do
880 | [ "${server}" = "mp4_var" ] && { inf "MP4 Servers" ; continue ;}
881 | streaming_url_server "${idserv}"
882 | if [ -n "${main_streaming}" ]; then
883 | suc "Server (${server}) Available"
884 | [ "${debug_mode}" = "true" ] && debug_handler vid-url "${main_streaming}"
885 | else
886 | err "Server (${server}) Failed"
887 | fi
888 | [ -z "${idena}" ] && break
889 | done
890 | die 0
891 | }
892 |
893 |
894 | # Main Functions
895 | main(){
896 | search
897 | selector
898 | episode_selector
899 | qualitypicker
900 | inf "[!] Please Wait... Processing"
901 | id_grepper
902 | }
903 |
904 | main_exec(){
905 | if [ "${dry_run}" = "true" ]; then
906 | dryrun_handler
907 | elif [ "${download_mode}" = "true" ]; then
908 | main_interface
909 | range_dl_handler
910 | else
911 | [ "${player}" = "mpv" ] && dep_check "mpv"
912 | loop
913 | fi
914 | }
915 |
916 |
917 | # Arguments Parser
918 | arg_parser(){
919 | while getopts ":vhduHDtq:-:p:s:" opt; do
920 | case "${opt}" in
921 | v) printf '%s\n' "Version: ${VERSION}" && exit 0 ;;
922 | h) show_help && exit 0 ;;
923 | d) download_mode="true" ;;
924 | u) update_script ;;
925 | H) hist_handler && hist_open="true" ;;
926 | D) [ -e ~/"${hist_file}" ] || die 1 "Can't clear the file, File does not exist."
927 | : > ~/"${hist_file}" && exit 0 ;;
928 | t) dry_run="true" ;;
929 | q) qual_stream="${OPTARG}" ;;
930 | -)
931 | case "${OPTARG}" in
932 | dub) dub_mode="true" ;;
933 | help) show_help && exit 0 ;;
934 | debug) debug_mode="true" ;;
935 | *hist-limit*)
936 | hist_limt="$(printf '%s' "${OPTARG}" | tr -dc '0-9')"
937 | if ! printf '%s' "${hist_limt}" | grep -qE '^[1-9][0-9]*$'; then
938 | die 1 "Format Error, it must be digits and must not have trailing zeros."
939 | fi
940 | ;;
941 | histup) hist_update && die 0 ;;
942 | *)
943 | err "[--${OPTARG}]: Illegal Option"
944 | show_help
945 | exit 1
946 | ;;
947 | esac
948 | ;;
949 | p)
950 | case "${OPTARG}" in
951 | built-in|builtin) dep_check "xdg-open" && player="built-in" ;;
952 | vlc) dep_check "vlc.exe" && player="vlc" ;;
953 | iina) dep_check "iina" && player="iina" ;;
954 | *) player="mpv" ;;
955 | esac
956 | ;;
957 | s)
958 | case "${OPTARG}" in
959 | 1) server="gogo" ;;
960 | 2) server="sb" ;;
961 | 3) server="fembed" ;;
962 | 4) server="dood" ;;
963 | 5) server="mpup" ;;
964 | *) server="default" ;;
965 | esac
966 | ;;
967 | \?)
968 | err "[-${OPTARG}]: is not valid Argument"
969 | show_help
970 | exit 1
971 | ;;
972 | :)
973 | err "[-${OPTARG}]: Argument must have a Value"
974 | show_help
975 | exit 1
976 | ;;
977 | esac
978 | done
979 | shift "$((OPTIND - 1))"
980 | [ "${hist_open}" = "true" ] && return 0 # Avoid overwriting history selected anime when searching through args
981 | [ -z "${ani_searched}" ] && ani_searched="${*}"
982 | }
983 |
984 |
985 | # Call Functions
986 | arg_parser "${@}"
987 | dep_check "curl" "sed" "grep" "awk" "base64" "od" "diff" "patch"
988 | main_exec
989 |
--------------------------------------------------------------------------------