feat(scraper): add allanime_api based on ani-cli

This is the main api thats going to interact with the allanime site to
scrape stream links. This will thus make the getting of video streams
faster and more efficient than using animdl as has been previously done.
This commit is contained in:
Benex254
2024-06-08 03:59:39 +03:00
parent 4cb5a5455c
commit 48d29bcfc2
12 changed files with 1446 additions and 3 deletions

View File

@@ -14,6 +14,7 @@ from kivy.resources import resource_add_path, resource_find, resource_remove_pat
from kivy.uix.screenmanager import FadeTransition, ScreenManager
from kivy.uix.settings import Settings, SettingsWithSidebar
from kivymd.app import MDApp
from dotenv import load_dotenv
from .libs.animdl import AnimdlApi
from .Utility import (
@@ -26,6 +27,7 @@ from .Utility.utils import write_crash
from .View.components.media_card.components.media_popup import MediaPopup
from .View.screens import screens
load_dotenv()
os.environ["KIVY_VIDEO"] = "ffpyplayer" # noqa: E402
Config.set("graphics", "width", "1000") # noqa: E402
@@ -428,8 +430,8 @@ def run_app():
if __name__ == "__main__":
in_development = True
in_development = bool(os.environ.get("IN_DEVELOPMENT", False))
print("In Development {}".format(in_development))
if in_development:
run_app()
else:

289
lscraping/allanime_api.py Normal file
View File

@@ -0,0 +1,289 @@
import json
import re
import requests
# TODO: move constants to own file
ALLANIME_BASE = "allanime.day"
ALLANIME_REFERER = "https://allanime.to/"
ALLANIME_API_ENDPOINT = "https://api.{}/api/".format(ALLANIME_BASE)
# TODO: move th gql queries to own files
ALLANIME_SEARCH_GQL = """
query(
$search: SearchInput
$limit: Int
$page: Int
$translationType: VaildTranslationTypeEnumType
$countryOrigin: VaildCountryOriginEnumType
) {
shows(
search: $search
limit: $limit
page: $page
translationType: $translationType
countryOrigin: $countryOrigin
) {
pageInfo {
total
}
edges {
_id
name
availableEpisodes
__typename
}
}
}
"""
ALLANIME_EPISODES_GQL = """\
query ($showId: String!, $translationType: VaildTranslationTypeEnumType!, $episodeString: String!) {
episode(
showId: $showId
translationType: $translationType
episodeString: $episodeString
) {
episodeString
sourceUrls
notes
}
}"""
ALLANIME_SHOW_GQL = """
query ($showId: String!) {
show(
_id: $showId
) {
_id
name
availableEpisodesDetail
}
}
"""
# TODO: creat a utility module for this
# Dictionary to map hex values to characters
hex_to_char = {
"01": "9",
"08": "0",
"05": "=",
"0a": "2",
"0b": "3",
"0c": "4",
"07": "?",
"00": "8",
"5c": "d",
"0f": "7",
"5e": "f",
"17": "/",
"54": "l",
"09": "1",
"48": "p",
"4f": "w",
"0e": "6",
"5b": "c",
"5d": "e",
"0d": "5",
"53": "k",
"1e": "&",
"5a": "b",
"59": "a",
"4a": "r",
"4c": "t",
"4e": "v",
"57": "o",
"51": "i",
}
def decode_hex_string(hex_string):
# Split the hex string into pairs of characters
hex_pairs = re.findall("..", hex_string)
# Decode each hex pair
decoded_chars = [hex_to_char.get(pair.lower(), pair) for pair in hex_pairs]
return "".join(decoded_chars)
# TODO: create tests for the api
#
class AllAnimeAPI:
"""
Provides a fast and effective interface to AllAnime site.
"""
api_endpoint = ALLANIME_API_ENDPOINT
def _fetch_gql(self, query: str, variables: dict):
response = requests.get(
self.api_endpoint,
params={
"variables": json.dumps(variables),
"query": query,
},
headers={
"Referer": ALLANIME_REFERER,
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0",
},
)
if response.status_code != 200:
print(response.content)
return {}
return response.json().get("data", {})
def search_for_anime(self, user_query: str, translation_type: str = "sub"):
search = {"allowAdult": False, "allowUnknown": False, "query": user_query}
limit = 40
translationtype = translation_type
countryorigin = "all"
page = 1
variables = {
"search": search,
"limit": limit,
"page": page,
"translationtype": translationtype,
"countryorigin": countryorigin,
}
return self._fetch_gql(ALLANIME_SEARCH_GQL, variables)
def get_anime(self, allanime_show_id: str):
variables = {"showId": allanime_show_id}
return api._fetch_gql(ALLANIME_SHOW_GQL, variables)
def get_anime_episode(
self, allanime_show_id: str, episode_string: str, translation_type: str = "sub"
):
variables = {
"showId": allanime_show_id,
"translationType": translation_type,
"episodeString": episode_string,
}
return api._fetch_gql(ALLANIME_EPISODES_GQL, variables)
def get_episode_streams(self, allanime_episode_embeds_data):
embeds = allanime_episode_embeds_data["episode"]["sourceUrls"]
for embed in embeds:
# filter the working streams
if embed.get("sourceName", "") not in ("Sak", "Kir", "S-mp4", "Luf-mp4"):
continue
url = embed.get("sourceUrl")
if not url:
continue
if url.startswith("--"):
url = url[2:]
# get the stream url for an episode of the defined source names
parsed_url = decode_hex_string(url)
embed_url = (
f"https://{ALLANIME_BASE}{parsed_url.replace('clock','clock.json')}"
)
resp = requests.get(
embed_url,
headers={
"Referer": ALLANIME_REFERER,
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0",
},
)
if resp.status_code == 200:
match embed["sourceName"]:
case "Luf-mp4":
return "gogoanime", resp.json()
case "Kir":
return "wetransfer", resp.json()
case "S-mp4":
return "sharepoint", resp.json()
case "Sak":
return "dropbox", resp.json()
case _:
return "Unknown", resp.json()
else:
return None
api = AllAnimeAPI()
if __name__ == "__main__":
# lets see if it works :)
import subprocess
import sys
def run_fzf(options):
"""
Run fzf with a list of options and return the selected option.
"""
# Join the list of options into a single string with newlines
options_str = "\n".join(options)
# Run fzf as a subprocess
result = subprocess.run(
["fzf"],
input=options_str,
text=True,
stdout=subprocess.PIPE,
)
# Check if fzf was successful
if result.returncode == 0:
# Return the selected option
return result.stdout.strip()
else:
# Handle the case where fzf fails or is canceled
print("fzf was canceled or failed")
return None
anime = input("Enter the anime name: ")
translation = input("Enter the translation type: ")
search_results = api.search_for_anime(anime, translation_type=translation.strip())
if not search_results:
raise Exception("No results found")
search_results = search_results["shows"]["edges"]
options = [show["name"] for show in search_results]
anime = run_fzf(options)
if anime is None:
print("No anime was selected")
sys.exit(1)
anime_result = list(filter(lambda x: x["name"] == anime, search_results))[0]
anime_data = api.get_anime(anime_result["_id"])
if anime_data is None:
raise Exception("Anime not found")
availableEpisodesDetail = anime_data["show"]["availableEpisodesDetail"]
if not availableEpisodesDetail.get(translation.strip()):
raise Exception("No episodes found")
print("select episode")
stream_link = True
while stream_link != "quit":
episode = run_fzf(availableEpisodesDetail[translation.strip()])
if episode is None:
print("No episode was selected")
sys.exit(1)
episode_data = api.get_anime_episode(anime_result["_id"], episode)
if episode_data is None:
raise Exception("Episode not found")
episode_streams = api.get_episode_streams(episode_data)
if not episode_streams:
raise Exception("No streams found")
stream_links = [stream["link"] for stream in episode_streams[1]["links"]]
stream_link = run_fzf([*stream_links, "quit"])
if stream_link == "quit":
print("Have a nice day")
sys.exit()
subprocess.run(["mpv", stream_link])

506
lscraping/anicli.sh Normal file
View File

@@ -0,0 +1,506 @@
#!/bin/sh
version_number="4.8.8"
# UI
external_menu() {
rofi "$1" -sort -dmenu -i -width 1500 -p "$2"
}
launcher() {
[ "$use_external_menu" = "0" ] && [ -z "$1" ] && set -- "+m" "$2"
[ "$use_external_menu" = "0" ] && fzf "$1" --reverse --cycle --prompt "$2"
[ "$use_external_menu" = "1" ] && external_menu "$1" "$2"
}
nth() {
stdin=$(cat -)
[ -z "$stdin" ] && return 1
line_count="$(printf "%s\n" "$stdin" | wc -l | tr -d "[:space:]")"
[ "$line_count" -eq 1 ] && printf "%s" "$stdin" | cut -f2,3 && return 0
prompt="$1"
multi_flag=""
[ $# -ne 1 ] && shift && multi_flag="$1"
line=$(printf "%s" "$stdin" | cut -f1,3 | tr '\t' ' ' | launcher "$multi_flag" "$prompt" | cut -d " " -f 1)
[ -n "$line" ] && printf "%s" "$stdin" | grep -E '^'"${line}"'($|[[:space:]])' | cut -f2,3 || exit 1
}
die() {
printf "\33[2K\r\033[1;31m%s\033[0m\n" "$*" >&2
exit 1
}
help_info() {
printf "
Usage:
%s [options] [query]
%s [query] [options]
%s [options] [query] [options]
Options:
-c, --continue
Continue watching from history
-d, --download
Download the video instead of playing it
-D, --delete
Delete history
-s, --syncplay
Use Syncplay to watch with friends
-S, --select-nth
Select nth entry
-q, --quality
Specify the video quality
-v, --vlc
Use VLC to play the video
-V, --version
Show the version of the script
-h, --help
Show this help message and exit
-e, --episode, -r, --range
Specify the number of episodes to watch
--dub
Play dubbed version
--rofi
Use rofi instead of fzf for the interactive menu
--skip
Use ani-skip to skip the intro of the episode (mpv only)
--no-detach
Don't detach the player (useful for in-terminal playback, mpv only)
--exit-after-play
Exit the player, and return the player exit code (useful for non interactive scenarios, works only if --no-detach is used, mpv only)
--skip-title <title>
Use given title as ani-skip query
-N, --nextep-countdown
Display a countdown to the next episode
-U, --update
Update the script
Some example usages:
%s -q 720p banana fish
%s --skip --skip-title \"one piece\" -S 2 one piece
%s -d -e 2 cyberpunk edgerunners
%s --vlc cyberpunk edgerunners -q 1080p -e 4
%s blue lock -e 5-6
%s -e \"5 6\" blue lock
\n" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}"
exit 0
}
version_info() {
printf "%s\n" "$version_number"
exit 0
}
update_script() {
update="$(curl -s -A "$agent" "https://raw.githubusercontent.com/pystardust/ani-cli/master/ani-cli")" || die "Connection error"
update="$(printf '%s\n' "$update" | diff -u "$0" -)"
if [ -z "$update" ]; then
printf "Script is up to date :)\n"
else
if printf '%s\n' "$update" | patch "$0" -; then
printf "Script has been updated\n"
else
die "Can't update for some reason!"
fi
fi
exit 0
}
# checks if dependencies are present
dep_ch() {
for dep; do
command -v "$dep" >/dev/null || die "Program \"$dep\" not found. Please install it."
done
}
# SCRAPING
# extract the video links from response of embed urls, extract mp4 links form m3u8 lists
get_links() {
episode_link="$(curl -e "$allanime_refr" -s "https://${allanime_base}$*" -A "$agent" | sed 's|},{|\
|g' | sed -nE 's|.*link":"([^"]*)".*"resolutionStr":"([^"]*)".*|\2 >\1|p;s|.*hls","url":"([^"]*)".*"hardsub_lang":"en-US".*|\1|p')"
case "$episode_link" in
*vipanicdn* | *anifastcdn*)
if printf "%s" "$episode_link" | head -1 | grep -q "original.m3u"; then
printf "%s" "$episode_link"
else
extract_link=$(printf "%s" "$episode_link" | head -1 | cut -d'>' -f2)
relative_link=$(printf "%s" "$extract_link" | sed 's|[^/]*$||')
curl -e "$allanime_refr" -s "$extract_link" -A "$agent" | sed 's|^#.*x||g; s|,.*|p|g; /^#/d; $!N; s|\
| >|' | sed "s|>|>${relative_link}|g" | sort -nr
fi
;;
*) [ -n "$episode_link" ] && printf "%s\n" "$episode_link" ;;
esac
[ -z "$ANI_CLI_NON_INTERACTIVE" ] && printf "\033[1;32m%s\033[0m Links Fetched\n" "$provider_name" 1>&2
}
# innitialises provider_name and provider_id. First argument is the provider name, 2nd is the regex that matches that provider's link
provider_init() {
provider_name=$1
provider_id=$(printf "%s" "$resp" | sed -n "$2" | head -1 | cut -d':' -f2 | sed 's/../&\
/g' | sed 's/^01$/9/g;s/^08$/0/g;s/^05$/=/g;s/^0a$/2/g;s/^0b$/3/g;s/^0c$/4/g;s/^07$/?/g;s/^00$/8/g;s/^5c$/d/g;s/^0f$/7/g;s/^5e$/f/g;s/^17$/\//g;s/^54$/l/g;s/^09$/1/g;s/^48$/p/g;s/^4f$/w/g;s/^0e$/6/g;s/^5b$/c/g;s/^5d$/e/g;s/^0d$/5/g;s/^53$/k/g;s/^1e$/\&/g;s/^5a$/b/g;s/^59$/a/g;s/^4a$/r/g;s/^4c$/t/g;s/^4e$/v/g;s/^57$/o/g;s/^51$/i/g;' | tr -d '\n' | sed "s/\/clock/\/clock\.json/")
}
# generates links based on given provider
generate_link() {
case $1 in
1) provider_init "dropbox" "/Sak :/p" ;; # dropbox(mp4)(single)
2) provider_init "wetransfer" "/Kir :/p" ;; # wetransfer(mp4)(single)
3) provider_init "sharepoint" "/S-mp4 :/p" ;; # sharepoint(mp4)(single)
*) provider_init "gogoanime" "/Luf-mp4 :/p" ;; # gogoanime(m3u8)(multi)
esac
[ -n "$provider_id" ] && get_links "$provider_id"
}
select_quality() {
case "$1" in
best) result=$(printf "%s" "$links" | head -n1) ;;
worst) result=$(printf "%s" "$links" | grep -E '^[0-9]{3,4}' | tail -n1) ;;
*) result=$(printf "%s" "$links" | grep -m 1 "$1") ;;
esac
[ -z "$result" ] && printf "Specified quality not found, defaulting to best\n" 1>&2 && result=$(printf "%s" "$links" | head -n1)
printf "%s" "$result" | cut -d'>' -f2
}
# gets embed urls, collects direct links into provider files, selects one with desired quality into $episode
get_episode_url() {
# get the embed urls of the selected episode
episode_embed_gql="query (\$showId: String!, \$translationType: VaildTranslationTypeEnumType!, \$episodeString: String!) { episode( showId: \$showId translationType: \$translationType episodeString: \$episodeString ) { episodeString sourceUrls }}"
resp=$(curl -e "$allanime_refr" -s -G "${allanime_api}/api" --data-urlencode "variables={\"showId\":\"$id\",\"translationType\":\"$mode\",\"episodeString\":\"$ep_no\"}" --data-urlencode "query=$episode_embed_gql" -A "$agent" | tr '{}' '\n' | sed 's|\\u002F|\/|g;s|\\||g' | sed -nE 's|.*sourceUrl":"--([^"]*)".*sourceName":"([^"]*)".*|\2 :\1|p')
# generate links into sequential files
cache_dir="$(mktemp -d)"
providers="1 2 3 4"
for provider in $providers; do
generate_link "$provider" >"$cache_dir"/"$provider" &
done
wait
# select the link with matching quality
links=$(cat "$cache_dir"/* | sed 's|^Mp4-||g;/http/!d' | sort -g -r -s)
rm -r "$cache_dir"
episode=$(select_quality "$quality")
[ -z "$episode" ] && die "Episode not released!"
}
# search the query and give results
search_anime() {
search_gql="query( \$search: SearchInput \$limit: Int \$page: Int \$translationType: VaildTranslationTypeEnumType \$countryOrigin: VaildCountryOriginEnumType ) { shows( search: \$search limit: \$limit page: \$page translationType: \$translationType countryOrigin: \$countryOrigin ) { edges { _id name availableEpisodes __typename } }}"
curl -e "$allanime_refr" -s -G "${allanime_api}/api" --data-urlencode "variables={\"search\":{\"allowAdult\":false,\"allowUnknown\":false,\"query\":\"$1\"},\"limit\":40,\"page\":1,\"translationType\":\"$mode\",\"countryOrigin\":\"ALL\"}" --data-urlencode "query=$search_gql" -A "$agent" | sed 's|Show|\
|g' | sed -nE "s|.*_id\":\"([^\"]*)\",\"name\":\"([^\"]*)\".*${mode}\":([1-9][^,]*).*|\1 \2 (\3 episodes)|p"
}
time_until_next_ep() {
animeschedule="https://animeschedule.net"
curl -s -G "$animeschedule/api/v3/anime" --data-urlencode "q=$1" | sed 's|"id"|\n|g' | sed -nE 's|.*,"route":"([^"]*)","premier.*|\1|p' | while read -r anime; do
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')
status="Ongoing"
color="33"
printf "%s\n" "$data"
! (printf "%s\n" "$data" | grep -q "Next Raw Release:") && status="Finished" && color="32"
printf "Status: \033[1;%sm%s\033[0m\n---\n" "$color" "$status"
done
exit 0
}
# get the episodes list of the selected anime
episodes_list() {
episodes_list_gql="query (\$showId: String!) { show( _id: \$showId ) { _id availableEpisodesDetail }}"
curl -e "$allanime_refr" -s -G "${allanime_api}/api" --data-urlencode "variables={\"showId\":\"$*\"}" --data-urlencode "query=$episodes_list_gql" -A "$agent" | sed -nE "s|.*$mode\":\[([0-9.\",]*)\].*|\1|p" | sed 's|,|\
|g; s|"||g' | sort -n -k 1
}
# PLAYING
process_hist_entry() {
ep_list=$(episodes_list "$id")
ep_no=$(printf "%s" "$ep_list" | sed -n "/^${ep_no}$/{n;p;}") 2>/dev/null
[ -n "$ep_no" ] && printf "%s\t%s - episode %s\n" "$id" "$title" "$ep_no"
}
update_history() {
if grep -q -- "$id" "$histfile"; then
sed -E "s/^[^\t]+\t${id}\t/${ep_no}\t${id}\t/" "$histfile" >"${histfile}.new"
else
cp "$histfile" "${histfile}.new"
printf "%s\t%s\t%s\n" "$ep_no" "$id" "$title" >>"${histfile}.new"
fi
mv "${histfile}.new" "$histfile"
}
download() {
case $1 in
*m3u8*)
if command -v "yt-dlp" >/dev/null; then
yt-dlp "$1" --no-skip-unavailable-fragments --fragment-retries infinite -N 16 -o "$download_dir/$2.mp4"
else
ffmpeg -loglevel error -stats -i "$1" -c copy "$download_dir/$2.mp4"
fi
;;
*)
aria2c --enable-rpc=false --check-certificate=false --continue --summary-interval=0 -x 16 -s 16 "$1" --dir="$download_dir" -o "$2.mp4" --download-result=hide
;;
esac
}
play_episode() {
[ "$skip_intro" = 1 ] && skip_flag="$(ani-skip -q "$mal_id" -e "$ep_no")"
[ -z "$episode" ] && get_episode_url
# shellcheck disable=SC2086
case "$player_function" in
debug)
[ -z "$ANI_CLI_NON_INTERACTIVE" ] && printf "All links:\n%s\nSelected link:\n" "$links"
printf "%s\n" "$episode"
;;
mpv*)
if [ "$no_detach" = 0 ]; then
nohup "$player_function" $skip_flag --force-media-title="${allanime_title}Episode ${ep_no}" "$episode" >/dev/null 2>&1 &
else
"$player_function" $skip_flag --force-media-title="${allanime_title}Episode ${ep_no}" "$episode"
mpv_exitcode=$?
[ "$exit_after_play" = 1 ] && exit "$mpv_exitcode"
fi
;;
android_mpv) nohup am start --user 0 -a android.intent.action.VIEW -d "$episode" -n is.xyz.mpv/.MPVActivity >/dev/null 2>&1 & ;;
android_vlc) nohup am start --user 0 -a android.intent.action.VIEW -d "$episode" -n org.videolan.vlc/org.videolan.vlc.gui.video.VideoPlayerActivity -e "title" "${allanime_title}Episode ${ep_no}" >/dev/null 2>&1 & ;;
iina) nohup "$player_function" --no-stdin --keep-running --mpv-force-media-title="${allanime_title}Episode ${ep_no}" "$episode" >/dev/null 2>&1 & ;;
flatpak_mpv) flatpak run io.mpv.Mpv --force-media-title="${allanime_title}Episode ${ep_no}" "$episode" >/dev/null 2>&1 & ;;
vlc*) nohup "$player_function" --play-and-exit --meta-title="${allanime_title}Episode ${ep_no}" "$episode" >/dev/null 2>&1 & ;;
*yncpla*) nohup "$player_function" "$episode" -- --force-media-title="${allanime_title}Episode ${ep_no}" >/dev/null 2>&1 & ;;
download) "$player_function" "$episode" "${allanime_title}Episode ${ep_no}" ;;
catt) nohup catt cast "$episode" >/dev/null 2>&1 & ;;
iSH)
printf "\e]8;;vlc://%s\a~~~~~~~~~~~~~~~~~~~~\n~ Tap to open VLC ~\n~~~~~~~~~~~~~~~~~~~~\e]8;;\a\n" "$episode"
sleep 5
;;
*) nohup "$player_function" "$episode" >/dev/null 2>&1 & ;;
esac
replay="$episode"
unset episode
update_history
[ "$use_external_menu" = "1" ] && wait
}
play() {
start=$(printf "%s" "$ep_no" | grep -Eo '^(-1|[0-9]+(\.[0-9]+)?)')
end=$(printf "%s" "$ep_no" | grep -Eo '(-1|[0-9]+(\.[0-9]+)?)$')
[ "$start" = "-1" ] && ep_no=$(printf "%s" "$ep_list" | tail -n1) && unset start
[ -z "$end" ] || [ "$end" = "$start" ] && unset start end
[ "$end" = "-1" ] && end=$(printf "%s" "$ep_list" | tail -n1)
line_count=$(printf "%s\n" "$ep_no" | wc -l | tr -d "[:space:]")
if [ "$line_count" != 1 ] || [ -n "$start" ]; then
[ -z "$start" ] && start=$(printf "%s\n" "$ep_no" | head -n1)
[ -z "$end" ] && end=$(printf "%s\n" "$ep_no" | tail -n1)
range=$(printf "%s\n" "$ep_list" | sed -nE "/^${start}\$/,/^${end}\$/p")
[ -z "$range" ] && die "Invalid range!"
for i in $range; do
tput clear
ep_no=$i
printf "\33[2K\r\033[1;34mPlaying episode %s...\033[0m\n" "$ep_no"
play_episode
done
else
play_episode
fi
# moves upto stored positon and deletes to end
[ "$player_function" != "debug" ] && [ "$player_function" != "download" ] && tput rc && tput ed
}
# MAIN
# setup
agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0"
allanime_refr="https://allanime.to"
allanime_base="allanime.day"
allanime_api="https://api.${allanime_base}"
mode="${ANI_CLI_MODE:-sub}"
download_dir="${ANI_CLI_DOWNLOAD_DIR:-.}"
quality="${ANI_CLI_QUALITY:-best}"
case "$(uname -a)" in
*Darwin*) player_function="${ANI_CLI_PLAYER:-iina}" ;; # mac OS
*ndroid*) player_function="${ANI_CLI_PLAYER:-android_mpv}" ;; # Android OS (termux)
*steamdeck*) player_function="${ANI_CLI_PLAYER:-flatpak_mpv}" ;; # steamdeck OS
*MINGW* | *WSL2*) player_function="${ANI_CLI_PLAYER:-mpv.exe}" ;; # Windows OS
*ish*) player_function="${ANI_CLI_PLAYER:-iSH}" ;; # iOS (iSH)
*) player_function="${ANI_CLI_PLAYER:-mpv}" ;; # Linux OS
esac
no_detach="${ANI_CLI_NO_DETACH:-0}"
exit_after_play="${ANI_CLI_EXIT_AFTER_PLAY:-0}"
use_external_menu="${ANI_CLI_EXTERNAL_MENU:-0}"
skip_intro="${ANI_CLI_SKIP_INTRO:-0}"
# shellcheck disable=SC2154
skip_title="$ANI_CLI_SKIP_TITLE"
[ -t 0 ] || use_external_menu=1
hist_dir="${ANI_CLI_HIST_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}/ani-cli}"
[ ! -d "$hist_dir" ] && mkdir -p "$hist_dir"
histfile="$hist_dir/ani-hsts"
[ ! -f "$histfile" ] && : >"$histfile"
search="${ANI_CLI_DEFAULT_SOURCE:-scrape}"
while [ $# -gt 0 ]; do
case "$1" in
-v | --vlc)
case "$(uname -a)" in
*ndroid*) player_function="android_vlc" ;;
MINGW* | *WSL2*) player_function="vlc.exe" ;;
*ish*) player_function="iSH" ;;
*) player_function="vlc" ;;
esac
;;
-s | --syncplay)
case "$(uname -s)" in
Darwin*) player_function="/Applications/Syncplay.app/Contents/MacOS/syncplay" ;;
MINGW* | *Msys)
export PATH="$PATH":"/c/Program Files (x86)/Syncplay/"
player_function="syncplay.exe"
;;
*) player_function="syncplay" ;;
esac
;;
-q | --quality)
[ $# -lt 2 ] && die "missing argument!"
quality="$2"
shift
;;
-S | --select-nth)
[ $# -lt 2 ] && die "missing argument!"
index="$2"
shift
;;
-c | --continue) search=history ;;
-d | --download) player_function=download ;;
-D | --delete)
: >"$histfile"
exit 0
;;
-V | --version) version_info ;;
-h | --help) help_info ;;
-e | --episode | -r | --range)
[ $# -lt 2 ] && die "missing argument!"
ep_no="$2"
[ -n "$index" ] && ANI_CLI_NON_INTERACTIVE=1 #Checks for -S presence
shift
;;
--dub) mode="dub" ;;
--no-detach) no_detach=1 ;;
--exit-after-play) exit_after_play=1 ;;
--rofi) use_external_menu=1 ;;
--skip) skip_intro=1 ;;
--skip-title)
[ $# -lt 2 ] && die "missing argument!"
skip_title="$2"
shift
;;
-N | --nextep-countdown) search=nextep ;;
-U | --update) update_script ;;
*) query="$(printf "%s" "$query $1" | sed "s|^ ||;s| |+|g")" ;;
esac
shift
done
[ "$use_external_menu" = "0" ] && multi_selection_flag="${ANI_CLI_MULTI_SELECTION:-"-m"}"
[ "$use_external_menu" = "1" ] && multi_selection_flag="${ANI_CLI_MULTI_SELECTION:-"-multi-select"}"
printf "\33[2K\r\033[1;34mChecking dependencies...\033[0m\n"
dep_ch "curl" "sed" "grep" || true
[ "$skip_intro" = 1 ] && (dep_ch "ani-skip" || true)
if [ -z "$ANI_CLI_NON_INTERACTIVE" ]; then dep_ch fzf || true; fi
case "$player_function" in
debug) ;;
download) dep_ch "ffmpeg" "aria2c" ;;
flatpak*)
dep_ch "flatpak"
flatpak info io.mpv.Mpv >/dev/null 2>&1 || die "Program \"mpv (flatpak)\" not found. Please install it."
;;
android*) printf "\33[2K\rChecking of players on Android is disabled\n" ;;
*iSH*) printf "\33[2K\rChecking of players on iOS is disabled\n" ;;
*) dep_ch "$player_function" ;;
esac
# searching
case "$search" in
history)
anime_list=$(while read -r ep_no id title; do process_hist_entry & done <"$histfile")
wait
[ -z "$anime_list" ] && die "No unwatched series in history!"
result=$(printf "%s" "$anime_list" | nl -w 2 | sed 's/^[[:space:]]//' | nth "Select anime: " | cut -f1)
[ -z "$result" ] && exit 1
resfile="$(mktemp)"
grep "$result" "$histfile" >"$resfile"
read -r ep_no id title <"$resfile"
ep_list=$(episodes_list "$id")
ep_no=$(printf "%s" "$ep_list" | sed -n "/^${ep_no}$/{n;p;}") 2>/dev/null
allanime_title="$(printf "%s" "$title" | cut -d'(' -f1 | tr -d '[:punct:]')"
;;
*)
if [ "$use_external_menu" = "0" ]; then
while [ -z "$query" ]; do
printf "\33[2K\r\033[1;36mSearch anime: \033[0m" && read -r query
done
else
[ -z "$query" ] && query=$(printf "" | external_menu "" "Search anime: ")
[ -z "$query" ] && exit 1
fi
# for checking new releases by specifying anime name
[ "$search" = "nextep" ] && time_until_next_ep "$query"
query=$(printf "%s" "$query" | sed "s| |+|g")
anime_list=$(search_anime "$query")
[ -z "$anime_list" ] && die "No results found!"
[ "$index" -eq "$index" ] 2>/dev/null && result=$(printf "%s" "$anime_list" | sed -n "${index}p")
[ -z "$index" ] && result=$(printf "%s" "$anime_list" | nl -w 2 | sed 's/^[[:space:]]//' | nth "Select anime: ")
[ -z "$result" ] && exit 1
title=$(printf "%s" "$result" | cut -f2)
allanime_title="$(printf "%s" "$title" | cut -d'(' -f1 | tr -d '[:punct:]')"
id=$(printf "%s" "$result" | cut -f1)
ep_list=$(episodes_list "$id")
[ -z "$ep_no" ] && ep_no=$(printf "%s" "$ep_list" | nth "Select episode: " "$multi_selection_flag")
[ -z "$ep_no" ] && exit 1
;;
esac
[ "$skip_intro" = 1 ] && mal_id="$(ani-skip -q "${skip_title:-${title}}")"
# moves the cursor up one line and clears that line
tput cuu1 && tput el
# stores the positon of cursor
tput sc
# playback & loop
play
[ "$player_function" = "download" ] || [ "$player_function" = "debug" ] && exit 0
while cmd=$(printf "next\nreplay\nprevious\nselect\nchange_quality\nquit" | nth "Playing episode $ep_no of $title... "); do
case "$cmd" in
next) ep_no=$(printf "%s" "$ep_list" | sed -n "/^${ep_no}$/{n;p;}") 2>/dev/null ;;
replay) episode="$replay" ;;
previous) ep_no=$(printf "%s" "$ep_list" | sed -n "/^${ep_no}$/{g;1!p;};h") 2>/dev/null ;;
select) ep_no=$(printf "%s" "$ep_list" | nth "Select episode: " "$multi_selection_flag") ;;
change_quality)
episode=$(printf "%s" "$links" | launcher)
quality=$(printf "%s" "$episode" | grep -oE "^[0-9]+")
episode=$(printf "%s" "$episode" | cut -d'>' -f2)
;;
*) exit 0 ;;
esac
[ -z "$ep_no" ] && die "Out of range"
play
done
# ani-cli
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Project repository: https://github.com/pystardust/ani-cli

209
lscraping/data.json Normal file
View File

@@ -0,0 +1,209 @@
{
"shows": {
"pageInfo": {
"total": null
},
"edges": [
{
"_id": "GoSNgenDWLm2B8C3H",
"name": "Highspeed Etoile",
"availableEpisodes": {
"sub": 10,
"dub": 0,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "8Suk8xq3YmvDq2s5C",
"name": "Girls Band Cry",
"availableEpisodes": {
"sub": 10,
"dub": 0,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "rwroGT45PMJiWkeYY",
"name": "The iDOLM@STER Shiny Colors",
"availableEpisodes": {
"sub": 10,
"dub": 0,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "pNPGCMYN5yA4yQH5J",
"name": "Maou Gakuin no Futekigousha: Shijou Saikyou no Maou no Shiso, Tensei shite Shison-tachi no Gakkou e Kayou 2nd Season Part 2",
"availableEpisodes": {
"sub": 8,
"dub": 2,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "Q7h4rxYmdHDADCTfY",
"name": "Mahouka Koukou no Rettousei Season 3",
"availableEpisodes": {
"sub": 10,
"dub": 3,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "KB5XDvwPdtLFEkoQZ",
"name": "Tensei shitara Slime Datta Ken Season 3",
"availableEpisodes": {
"sub": 11,
"dub": 8,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "QvmdS433kzi6gy8EQ",
"name": "Astro Note",
"availableEpisodes": {
"sub": 10,
"dub": 0,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "A3YuyeYdar4cPvix2",
"name": "Nyaaaanvy",
"availableEpisodes": {
"sub": 2,
"dub": 0,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "vHRnxWT83vEhRzcAG",
"name": "Nijiyon Animation 2",
"availableEpisodes": {
"sub": 10,
"dub": 0,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "ZDxMfy9mmxEbjryKP",
"name": "Ni Tian Zhizun",
"availableEpisodes": {
"sub": 308,
"dub": 0,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "ox2opWHHwh2d663NG",
"name": "Ling Jian Zun Season 4",
"availableEpisodes": {
"sub": 394,
"dub": 0,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "wcGJw5u7NRiabB4qz",
"name": "Exclusive Possession: Young Master Ji's Beloved Wife Season 3",
"availableEpisodes": {
"sub": 59,
"dub": 0,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "M5Z6D7DXveoMSKXN7",
"name": "Bai Lian Cheng Shen Season 2",
"availableEpisodes": {
"sub": 28,
"dub": 0,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "6oDA6BDxtLCJq5FZx",
"name": "Wanmei Shijie",
"availableEpisodes": {
"sub": 166,
"dub": 0,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "5hpioXkSfCNYX4k5M",
"name": "Da Zhu Zai Nian Fan",
"availableEpisodes": {
"sub": 52,
"dub": 0,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "FDbFjEnbDTbrGJ757",
"name": "Yuan Zun (2024)",
"availableEpisodes": {
"sub": 4,
"dub": 0,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "2z6h3kbiB4xSafQcj",
"name": "Dead Dead Demons Dededede Destruction",
"availableEpisodes": {
"sub": 3,
"dub": 3,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "gYNWJBu4rvSrmSfiB",
"name": "Henjin no Salad Bowl",
"availableEpisodes": {
"sub": 10,
"dub": 0,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "eDZ9AzEzXu3LQ3uTK",
"name": "Muu no Hakugei",
"availableEpisodes": {
"sub": 2,
"dub": 0,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "dBjYbRzRyWg59E57c",
"name": "Chiikawa",
"availableEpisodes": {
"sub": 95,
"dub": 0,
"raw": 0
},
"__typename": "Show"
}
]
}
}

1
lscraping/data2.json Normal file
View File

@@ -0,0 +1 @@
{"shows": {"pageInfo": {"total": null}, "edges": [{"_id": "GoSNgenDWLm2B8C3H", "name": "Highspeed Etoile", "availableEpisodes": {"sub": 10, "dub": 0, "raw": 0}, "__typename": "Show"}, {"_id": "8Suk8xq3YmvDq2s5C", "name": "Girls Band Cry", "availableEpisodes": {"sub": 10, "dub": 0, "raw": 0}, "__typename": "Show"}, {"_id": "rwroGT45PMJiWkeYY", "name": "The iDOLM@STER Shiny Colors", "availableEpisodes": {"sub": 10, "dub": 0, "raw": 0}, "__typename": "Show"}, {"_id": "pNPGCMYN5yA4yQH5J", "name": "Maou Gakuin no Futekigousha: Shijou Saikyou no Maou no Shiso, Tensei shite Shison-tachi no Gakkou e Kayou 2nd Season Part 2", "availableEpisodes": {"sub": 8, "dub": 2, "raw": 0}, "__typename": "Show"}, {"_id": "Q7h4rxYmdHDADCTfY", "name": "Mahouka Koukou no Rettousei Season 3", "availableEpisodes": {"sub": 10, "dub": 3, "raw": 0}, "__typename": "Show"}, {"_id": "KB5XDvwPdtLFEkoQZ", "name": "Tensei shitara Slime Datta Ken Season 3", "availableEpisodes": {"sub": 11, "dub": 8, "raw": 0}, "__typename": "Show"}, {"_id": "QvmdS433kzi6gy8EQ", "name": "Astro Note", "availableEpisodes": {"sub": 10, "dub": 0, "raw": 0}, "__typename": "Show"}, {"_id": "A3YuyeYdar4cPvix2", "name": "Nyaaaanvy", "availableEpisodes": {"sub": 2, "dub": 0, "raw": 0}, "__typename": "Show"}, {"_id": "vHRnxWT83vEhRzcAG", "name": "Nijiyon Animation 2", "availableEpisodes": {"sub": 10, "dub": 0, "raw": 0}, "__typename": "Show"}, {"_id": "ZDxMfy9mmxEbjryKP", "name": "Ni Tian Zhizun", "availableEpisodes": {"sub": 308, "dub": 0, "raw": 0}, "__typename": "Show"}, {"_id": "ox2opWHHwh2d663NG", "name": "Ling Jian Zun Season 4", "availableEpisodes": {"sub": 394, "dub": 0, "raw": 0}, "__typename": "Show"}, {"_id": "wcGJw5u7NRiabB4qz", "name": "Exclusive Possession: Young Master Ji's Beloved Wife Season 3", "availableEpisodes": {"sub": 59, "dub": 0, "raw": 0}, "__typename": "Show"}, {"_id": "M5Z6D7DXveoMSKXN7", "name": "Bai Lian Cheng Shen Season 2", "availableEpisodes": {"sub": 28, "dub": 0, "raw": 0}, "__typename": "Show"}, {"_id": "6oDA6BDxtLCJq5FZx", "name": "Wanmei Shijie", "availableEpisodes": {"sub": 166, "dub": 0, "raw": 0}, "__typename": "Show"}, {"_id": "5hpioXkSfCNYX4k5M", "name": "Da Zhu Zai Nian Fan", "availableEpisodes": {"sub": 52, "dub": 0, "raw": 0}, "__typename": "Show"}, {"_id": "FDbFjEnbDTbrGJ757", "name": "Yuan Zun (2024)", "availableEpisodes": {"sub": 4, "dub": 0, "raw": 0}, "__typename": "Show"}, {"_id": "2z6h3kbiB4xSafQcj", "name": "Dead Dead Demons Dededede Destruction", "availableEpisodes": {"sub": 3, "dub": 3, "raw": 0}, "__typename": "Show"}, {"_id": "gYNWJBu4rvSrmSfiB", "name": "Henjin no Salad Bowl", "availableEpisodes": {"sub": 10, "dub": 0, "raw": 0}, "__typename": "Show"}, {"_id": "eDZ9AzEzXu3LQ3uTK", "name": "Muu no Hakugei", "availableEpisodes": {"sub": 2, "dub": 0, "raw": 0}, "__typename": "Show"}, {"_id": "dBjYbRzRyWg59E57c", "name": "Chiikawa", "availableEpisodes": {"sub": 95, "dub": 0, "raw": 0}, "__typename": "Show"}]}}

159
lscraping/data3.json Normal file
View File

@@ -0,0 +1,159 @@
{
"shows": {
"pageInfo": {
"total": 22279
},
"edges": [
{
"_id": "JsbMBPoTsp3RkGfrf",
"name": "Sword Art Online: Progressive Movie: Kuraki Yuuyami no Scherzo",
"availableEpisodes": {
"sub": 1,
"dub": 1,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "YXEMgd5FLhWD2mn8Z",
"name": "Sword Art Online: Progressive Movie - Hoshi Naki Yoru no Aria",
"availableEpisodes": {
"sub": 1,
"dub": 1,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "ALj8oEW6mhWuQ8pHT",
"name": "Sword Art Online Movie: Ordinal Scale: Sword Art Offline",
"availableEpisodes": {
"sub": 1,
"dub": 0,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "DWZeL5vDhEokJQFXR",
"name": "Sword Art Offline II",
"availableEpisodes": {
"sub": 9,
"dub": 0,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "fgEBgLfkzEWaCSHAu",
"name": "Sword Art Online: Sword Art Offline",
"availableEpisodes": {
"sub": 9,
"dub": 0,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "BvzJ7btmDGBvJCC4P",
"name": "Sword Art Online: Extra Edition",
"availableEpisodes": {
"sub": 1,
"dub": 1,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "4mjsuWL9sAK97zQjL",
"name": "Sword Art Offline: Extra Edition",
"availableEpisodes": {
"sub": 1,
"dub": 0,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "np7xM323v5pH6KY9e",
"name": "Sword Art Online: Alicization",
"availableEpisodes": {
"sub": 25,
"dub": 25,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "36bqKyjS3tFybyz7j",
"name": "Sword Art Online",
"availableEpisodes": {
"sub": 25,
"dub": 25,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "ierXJrDYNiALG5haP",
"name": "Sword Art Online II",
"availableEpisodes": {
"sub": 26,
"dub": 25,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "tkywv9gMqH7mYw4N5",
"name": "Sword Art Online: Alicization - War of Underworld Season 2",
"availableEpisodes": {
"sub": 11,
"dub": 11,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "z2stShodurTBWbXgM",
"name": "Sword Art Online: Alicization - War of Underworld",
"availableEpisodes": {
"sub": 14,
"dub": 12,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "WwdepehfXq8gpgku9",
"name": "Sword Art Online Alternative: Gun Gale Online",
"availableEpisodes": {
"sub": 13,
"dub": 12,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "hjHnDp4NRh9zpcTLJ",
"name": "Sword Art Online: Ordinal Scale",
"availableEpisodes": {
"sub": 1,
"dub": 1,
"raw": 0
},
"__typename": "Show"
},
{
"_id": "TYTW9KtABBNiMcp5w",
"name": "Sword Art Online II: Debriefing",
"availableEpisodes": {
"sub": 1,
"dub": 0,
"raw": 0
},
"__typename": "Show"
}
]
}
}

52
lscraping/f.py Normal file
View File

@@ -0,0 +1,52 @@
import re
# Dictionary to map hex values to characters
hex_to_char = {
"01": "9",
"08": "0",
"05": "=",
"0a": "2",
"0b": "3",
"0c": "4",
"07": "?",
"00": "8",
"5c": "d",
"0f": "7",
"5e": "f",
"17": "/",
"54": "l",
"09": "1",
"48": "p",
"4f": "w",
"0e": "6",
"5b": "c",
"5d": "e",
"0d": "5",
"53": "k",
"1e": "&",
"5a": "b",
"59": "a",
"4a": "r",
"4c": "t",
"4e": "v",
"57": "o",
"51": "i",
}
def decode_hex_string(hex_string):
# Split the hex string into pairs of characters
hex_pairs = re.findall("..", hex_string)
# Decode each hex pair
decoded_chars = [hex_to_char.get(pair.lower(), pair) for pair in hex_pairs]
return "".join(decoded_chars)
print(
decode_hex_string(
"175948514e4c4f57175b54575b5307515c050f5c0a0c0f0b0f0c0e590a0c0b5b0a0c0a010f0d0e5e0f0a0e0b0f0d0a010e0f0e000e5e0e5a0e0b0a010d0d0e5d0e0f0f0c0e0b0e0a0a0e0c0a0e010e0d0f0b0e5a0e0b0e000f0a0f0d0a010e000f0e0b090f5d0c5a0b0d0b0c0b0d0f080b0b0f0e0c5d0b080c5c0d5e0b5e0e0b0d010b0f0b0c0d010f0d0f0b0e0c0a000e5a0f0e0b0a0a0c0a590a0c0f0d0f0a0f0c0e0b0e0f0e5a0e0b0f0c0c5e0e0a0a0c0b5b0a0c0d0d0e5d0e0f0f0c0e0b0f0e0e010e5e0e000f0a0a0c0a590a0c0e0a0e0f0f0a0e0b0a0c0b5b0a0c0b0c0b0e0b0c0b0a0a5a0b0e0b080a5a0b0e0b090d0a0b0f0b5e0b5b0b0b0b5e0b5b0b0e0b0e0a000b0e0b0e0b0e0d5b0a0c0a590a0c0f0a0f0c0e0f0e000f0d0e590e0f0f0a0e5e0e010e000d0a0f5e0f0e0e0b0a0c0b5b0a0c0f0d0f0b0e0c0a0c0a590a0c0e5c0e0b0f5e0a0c0b5b0a0c0e0b0f0e0a5a0e000f0e0b090f5d0c5a0b0d0b0c0b0d0f080b0b0f0e0c5d0b080c5c0d5e0b5e0e0b0d010b0f0b0c0d010f0d0f0b0e0c0a0c0f5a"
)
)

102
lscraping/final.json Normal file
View File

@@ -0,0 +1,102 @@
{
"episode": {
"episodeString": "12",
"sourceUrls": [
{
"sourceUrl": "--175948514e4c4f57175b54575b5307515c050f5c0a0c0f0b0f0c0e590a0c0b5b0a0c0a010a010f080e5e0e0a0f0d0f0a0f0c0e0b0e0f0e5a0e5e0e000e090a000e5e0e010a010e590e010e0f0e0a0a000f0e0e5d0f0e0b010e5e0e0a0b5a0c5a0d0a0c0b0f5d0c010d0a0e5c0b0d0a080f0a0e5e0f0a0e590e0b0b5a0d0d0f090e010f0c0e0a0a5c0c0f0f0c0f0a0a5c0c010e000e590e5e0e000e0b0a0b0b0d0c0f0a5c0c0f0e590e5e0e0d0e5e0f5b0e0f0f0a0e5e0e010e000a080f0a0f5e0f0e0e0b0f0d0f0b0e0c0b5a0d0d0d0b0c0c0a080f0d0f0b0e0c0b5a0a080e0d0e010f080e0b0f0c0b5a0d5e0b0c0b5e0b0c0d5b0d5d0c5e0f080e0d0b0d0e0a0f080e0d0e5a0d0f0f0a0d5e0d5d0c5b0b0e0c590d090b5e0f0b0e0c0c090e590f0b0d5b0d0d0b0f0e5d0e0c0c090e590e5b0e0f0d5d0f0e0e5d0e0a0c090e590f080e0c0e5e0b0b0f090e0c0e5a0e0d0b5a0a0c0a590a0c0f0d0f0a0f0c0e0b0e0f0e5a0e0b0f0c0c5e0e0a0a0c0b5b0a0c0f080e5e0e0a0e0b0e010f0d0f0a0f0c0e0b0e0f0e5a0e5e0e010a0c0a590a0c0e0a0e0f0f0a0e0b0a0c0b5b0a0c0b0c0b0e0b0c0b0a0a5a0b0e0b080a5a0b0e0b090d0a0b0f0b5e0b5b0b0b0b5e0b5b0b0e0b0e0a000b0e0b0e0b0e0d5b0a0c0f5a1e4a5d5e5d4a5d4a05",
"priority": 7.7,
"sourceName": "Luf-mp4",
"type": "iframe",
"className": "",
"streamerId": "allanime"
},
{
"sourceUrl": "//vidstreaming.io/load.php?id=MTExOTk3&title=Sword+Art+Online%3A+Alicization&typesub=SUB&sub=&cover=Y292ZXIvc3dvcmQtYXJ0LW9ubGluZS1hbGljaXphdGlvbi5wbmc=",
"priority": 4,
"sourceName": "Vid-mp4",
"type": "iframe",
"className": "",
"streamerId": "allanime",
"downloads": {
"sourceName": "Gl",
"downloadUrl": "https://embtaku.pro/download?id=MTExOTk3&title=Sword+Art+Online%3A+Alicization&typesub=SUB&sub=&cover=Y292ZXIvc3dvcmQtYXJ0LW9ubGluZS1hbGljaXphdGlvbi5wbmc=&sandbox=allow-forms%20allow-scripts%20allow-same-origin%20allow-downloads"
}
},
{
"sourceUrl": "https://streamsb.net/e/kag9w71l9jq0.html",
"priority": 5.5,
"sourceName": "Ss-Hls",
"type": "iframe",
"className": "text-danger",
"streamerId": "allanime",
"downloads": {
"sourceName": "StreamSB",
"downloadUrl": "https://streamsb.net/d/kag9w71l9jq0.html&sandbox=allow-forms%20allow-scripts%20allow-same-origin%20allow-downloads"
}
},
{
"sourceUrl": "--175948514e4c4f57175b54575b5307515c050f5c0a0c0f0b0f0c0e590a0c0b5b0a0c0a010f080e5e0e0a0e0b0e010f0d0a010e000f0e0b090f5d0c5a0b0d0b0c0b0d0f080b0b0f0e0c5d0b080c5c0d5e0b5e0e0b0a010f0d0f0b0e0c0a010b0f0b0c0a0c0a590a0c0f0d0f0a0f0c0e0b0e0f0e5a0e0b0f0c0c5e0e0a0a0c0b5b0a0c0c0a0f0c0e010f0e0e0c0e010f5d0a0c0a590a0c0e0a0e0f0f0a0e0b0a0c0b5b0a0c0b0c0b0e0b0c0b0a0a5a0b0e0b080a5a0b0e0b090d0a0b0f0b5e0b5b0b0b0b5e0b5b0b0e0b0e0a000b0e0b0e0b0e0d5b0a0c0a590a0c0f0a0f0c0e0f0e000f0d0e590e0f0f0a0e5e0e010e000d0a0f5e0f0e0e0b0a0c0b5b0a0c0f0d0f0b0e0c0a0c0a590a0c0e5c0e0b0f5e0a0c0b5b0a0c0e0b0f0e0a5a0a010f080e5e0e0a0e0b0e010f0d0a010e000f0e0b090f5d0c5a0b0d0b0c0b0d0f080b0b0f0e0c5d0b080c5c0d5e0b5e0e0b0a010f0d0f0b0e0c0a010b0f0b0c0a0c0f5a",
"priority": 7,
"sourceName": "Sak",
"type": "iframe",
"className": "",
"streamerId": "allanime"
},
{
"sourceUrl": "--504c4c484b0217174c5757544b165e594b4c0c4b485d5d5c164a4b4e4817174e515c5d574b1756480f40750b0a0b4e0d48700e7361015d174b4d5a17090a",
"priority": 7.9,
"sourceName": "Yt-mp4",
"type": "player",
"className": "",
"streamerId": "allanime"
},
{
"sourceUrl": "https://mp4upload.com/embed-bayej7b93in0.html",
"priority": 4,
"sourceName": "Mp4",
"type": "iframe",
"sandbox": "allow-forms allow-scripts allow-same-origin",
"className": "",
"streamerId": "allanime"
},
{
"sourceUrl": "https://ok.ru/videoembed/2731036445330",
"priority": 3.5,
"sourceName": "Ok",
"type": "iframe",
"sandbox": "allow-forms allow-scripts allow-same-origin",
"className": "text-info",
"streamerId": "allanime"
},
{
"sourceUrl": "--175948514e4c4f57175b54575b5307515c050f5c0a0c0f0b0f0c0e590a0c0b5b0a0c0b0b0b080e0d0b5e0b0a0b0d0d010e0c0b5e0b0c0b080b0f0b0d0e0c0b0a0b0b0b0e0b080b090b0a0b5e0b0f0b080b5e0b5d0e0a0b080e0f0b5e0b0b0e080b0f0b0e0e0c0b0f0b5d0e0f0b0c0e0a0a0e0f590a0e0b0d0e0f0b0d0e080b0a0e0f0e080b0b0b080e0a0b090b0a0b090e0b0b5d0b5e0e0d0b0d0b0e0b0f0b0b0b080e0b0e080b080e0d0b5d0b5d0e0d0b0b0b080e0c0a0e0f590a0e0e5a0e0b0e0a0e5e0e0f0a010b0b0b080e0d0b5e0b0a0b0d0d010e0c0b5e0b0c0b080b0f0b0d0e0c0b0a0b0b0b0e0b080b090b0a0b5e0b0f0b080b5e0b5d0e0a0b080e0f0b5e0b0b0e080b0f0b0e0e0c0b0f0b5d0e0f0b0c0e0a0e080b0e0b0e0b0f0a000e5b0f0e0e090a0e0f590a0e0a590b090b0c0b0e0f0e0a590b0f0b0e0b5d0b0e0f0e0a590b0a0b5d0b0e0f0e0a590a0c0a590a0c0f0d0f0a0f0c0e0b0e0f0e5a0e0b0f0c0c5e0e0a0a0c0b5b0a0c0d090e5e0f5d0a0c0a590a0c0e0a0e0f0f0a0e0b0a0c0b5b0a0c0b0c0b0e0b0c0b0a0a5a0b0e0b080a5a0b0e0b090d0a0b0f0b5e0b5b0b0b0b5e0b5b0b0e0b0e0a000b0e0b0e0b0e0d5b0a0c0a590a0c0f0a0f0c0e0f0e000f0d0e590e0f0f0a0e5e0e010e000d0a0f5e0f0e0e0b0a0c0b5b0a0c0f0d0f0b0e0c0a0c0a590a0c0e5c0e0b0f5e0a0c0b5b0a0c0e0b0f0e0a5a0e000f0e0b090f5d0c5a0b0d0b0c0b0d0f080b0b0f0e0c5d0b080c5c0d5e0b5e0e0b0d010b0f0b0c0d010f0d0f0b0e0c0a0c0f5a",
"priority": 8.5,
"sourceName": "Default",
"type": "iframe",
"className": "text-info",
"streamerId": "allanime"
},
{
"sourceUrl": "--175948514e4c4f57175b54575b5307515c050f5c0a0c0f0b0f0c0e590a0c0b5b0a0c0a010f0d0e5e0f0a0e0b0f0d0a010e0f0e000e5e0e5a0e0b0a010d0d0e5d0e0f0f0c0e0b0e0a0a0e0c0a0e010e0d0f0b0e5a0e0b0e000f0a0f0d0a010e000f0e0b090f5d0c5a0b0d0b0c0b0d0f080b0b0f0e0c5d0b080c5c0d5e0b5e0e0b0d010b0f0b0c0d010f0d0f0b0e0c0a000e5a0f0e0b0a0a0c0a590a0c0f0d0f0a0f0c0e0b0e0f0e5a0e0b0f0c0c5e0e0a0a0c0b5b0a0c0d0d0e5d0e0f0f0c0e0b0f0e0e010e5e0e000f0a0a0c0a590a0c0e0a0e0f0f0a0e0b0a0c0b5b0a0c0b0c0b0e0b0c0b0a0a5a0b0e0b080a5a0b0e0b090d0a0b0f0b5e0b5b0b0b0b5e0b5b0b0e0b0e0a000b0e0b0e0b0e0d5b0a0c0a590a0c0f0a0f0c0e0f0e000f0d0e590e0f0f0a0e5e0e010e000d0a0f5e0f0e0e0b0a0c0b5b0a0c0f0d0f0b0e0c0a0c0a590a0c0e5c0e0b0f5e0a0c0b5b0a0c0e0b0f0e0a5a0e000f0e0b090f5d0c5a0b0d0b0c0b0d0f080b0b0f0e0c5d0b080c5c0d5e0b5e0e0b0d010b0f0b0c0d010f0d0f0b0e0c0a0c0f5a",
"priority": 7.4,
"sourceName": "S-mp4",
"type": "iframe",
"className": "",
"streamerId": "allanime",
"downloads": {
"sourceName": "S-mp4",
"downloadUrl": "https://blog.allanime.day/apivtwo/clock/download?id=7d2473746a243c2429756f7263752967686f6b6329556e6774636226426965736b63687275296876317e4b3534357033764e304d5f3f6359373459757364286b7632242a2475727463676b63744f62243c24556e67746376696f6872242a2462677263243c24343634322b36302b363152373f3c333f3c3636283636365c242a24626971686a696762243c727473637b"
}
},
{
"sourceUrl": "--175948514e4c4f57175b54575b5307515c050f5c0a0c0f0b0f0c0e590a0c0b5b0a0c0e5d0f0a0f0a0f0e0f0d0b5b0a010a010f0b0f0d0e0b0f0c0f0d0e0d0e590e010f0b0e0a0a000e0d0e010e5a0a010e0b0e5a0e0c0e0b0e0a0a5a0f0f0b5e0e080e590f080e5b0e5b0b0d0e0c0b090f0d0f0f0a000e5d0f0a0e5a0e590a0c0a590a0c0f0d0f0a0f0c0e0b0e0f0e5a0e0b0f0c0c5e0e0a0a0c0b5b0a0c0d0b0f0d0e0b0f0c0f080e5e0e0a0a0c0a590a0c0e0a0e0f0f0a0e0b0a0c0b5b0a0c0b0c0b0e0b0c0b0a0a5a0b0e0b080a5a0b0e0b090d0a0b0f0b5e0b5b0b0b0b5e0b5b0b0e0b0e0a000b0e0b0e0b0e0d5b0a0c0a590a0c0f0a0f0c0e0f0e000f0d0e590e0f0f0a0e5e0e010e000d0a0f5e0f0e0e0b0a0c0b5b0a0c0f0d0f0b0e0c0a0c0a590a0c0e5c0e0b0f5e0a0c0b5b0a0c0e0b0f0e0a5a0e000f0e0b090f5d0c5a0b0d0b0c0b0d0f080b0b0f0e0c5d0b080c5c0d5e0b5e0e0b0d010b0f0b0c0d010f0d0f0b0e0c0a0c0f5a",
"priority": 1,
"sourceName": "Uv-mp4",
"type": "iframe",
"className": "",
"streamerId": "allanime"
}
],
"notes": null
}
}

1
lscraping/output.html Normal file
View File

@@ -0,0 +1 @@
b'{"data":{"shows":{"edges":[{"_id":"GoSNgenDWLm2B8C3H","name":"Highspeed Etoile","availableEpisodes":{"sub":10,"dub":0,"raw":0},"__typename":"Show"},{"_id":"8Suk8xq3YmvDq2s5C","name":"Girls Band Cry","availableEpisodes":{"sub":10,"dub":0,"raw":0},"__typename":"Show"},{"_id":"rwroGT45PMJiWkeYY","name":"The iDOLM@STER Shiny Colors","availableEpisodes":{"sub":10,"dub":0,"raw":0},"__typename":"Show"},{"_id":"pNPGCMYN5yA4yQH5J","name":"Maou Gakuin no Futekigousha: Shijou Saikyou no Maou no Shiso, Tensei shite Shison-tachi no Gakkou e Kayou 2nd Season Part 2","availableEpisodes":{"sub":8,"dub":2,"raw":0},"__typename":"Show"},{"_id":"Q7h4rxYmdHDADCTfY","name":"Mahouka Koukou no Rettousei Season 3","availableEpisodes":{"sub":10,"dub":3,"raw":0},"__typename":"Show"},{"_id":"KB5XDvwPdtLFEkoQZ","name":"Tensei shitara Slime Datta Ken Season 3","availableEpisodes":{"sub":11,"dub":8,"raw":0},"__typename":"Show"},{"_id":"QvmdS433kzi6gy8EQ","name":"Astro Note","availableEpisodes":{"sub":10,"dub":0,"raw":0},"__typename":"Show"},{"_id":"A3YuyeYdar4cPvix2","name":"Nyaaaanvy","availableEpisodes":{"sub":2,"dub":0,"raw":0},"__typename":"Show"},{"_id":"vHRnxWT83vEhRzcAG","name":"Nijiyon Animation 2","availableEpisodes":{"sub":10,"dub":0,"raw":0},"__typename":"Show"},{"_id":"ZDxMfy9mmxEbjryKP","name":"Ni Tian Zhizun","availableEpisodes":{"sub":308,"dub":0,"raw":0},"__typename":"Show"},{"_id":"ox2opWHHwh2d663NG","name":"Ling Jian Zun Season 4","availableEpisodes":{"sub":394,"dub":0,"raw":0},"__typename":"Show"},{"_id":"wcGJw5u7NRiabB4qz","name":"Exclusive Possession: Young Master Ji\'s Beloved Wife Season 3","availableEpisodes":{"sub":59,"dub":0,"raw":0},"__typename":"Show"},{"_id":"M5Z6D7DXveoMSKXN7","name":"Bai Lian Cheng Shen Season 2","availableEpisodes":{"sub":28,"dub":0,"raw":0},"__typename":"Show"},{"_id":"6oDA6BDxtLCJq5FZx","name":"Wanmei Shijie","availableEpisodes":{"sub":166,"dub":0,"raw":0},"__typename":"Show"},{"_id":"5hpioXkSfCNYX4k5M","name":"Da Zhu Zai Nian Fan","availableEpisodes":{"sub":52,"dub":0,"raw":0},"__typename":"Show"},{"_id":"FDbFjEnbDTbrGJ757","name":"Yuan Zun (2024)","availableEpisodes":{"sub":4,"dub":0,"raw":0},"__typename":"Show"},{"_id":"2z6h3kbiB4xSafQcj","name":"Dead Dead Demons Dededede Destruction","availableEpisodes":{"sub":3,"dub":3,"raw":0},"__typename":"Show"},{"_id":"gYNWJBu4rvSrmSfiB","name":"Henjin no Salad Bowl","availableEpisodes":{"sub":10,"dub":0,"raw":0},"__typename":"Show"},{"_id":"eDZ9AzEzXu3LQ3uTK","name":"Muu no Hakugei","availableEpisodes":{"sub":2,"dub":0,"raw":0},"__typename":"Show"},{"_id":"dBjYbRzRyWg59E57c","name":"Chiikawa","availableEpisodes":{"sub":95,"dub":0,"raw":0},"__typename":"Show"}]}}}\n' 200

49
lscraping/scraper.txt Normal file
View File

@@ -0,0 +1,49 @@
## the qraphql query
search_gql="query(
\$search: SearchInput
\$limit: Int
\$page: Int
\$translationType: VaildTranslationTypeEnumType
\$countryOrigin: VaildCountryOriginEnumType
)
{
shows(
search: \$search
limit: \$limit
page: \$page
translationType: \$translationType
countryOrigin: \$countryOrigin
) {
edges {
_id
name
availableEpisodes
__typename
}
}}"
##allanime api endpoint
allanime_base = allanime.day
allanime_api = api.$allanime_base
##allanime referer header
allanime_referer = "https://allanime.to/"
##post data
"variables={\"search\":{\"allowAdult\":false,\"allowUnknown\":false,\"query\":\"$1\"},\"limit\":40,\"page\":1,\"translationType\":\"$mode\",\"countryOrigin\":\"ALL\"}" --data-urlencode "query=$search_gql"
curl -e "$allanime_refr" -s -G "${allanime_api}/api" --data-urlencode "variables={\"search\":{\"allowAdult\":false,\"allowUnknown\":false,\"query\":\"$1\"},\"limit\":40,\"page\":1,\"translationType\":\"$mode\",\"countryOrigin\":\"ALL\"}" --data-urlencode "query=$search_gql" -A "$agent" | sed 's|Show|\
##required in request
user agent
referer
episodes_list_gql="query (\$showId: String!) { show( _id: \$showId ) { _id availableEpisodesDetail }}"
curl -e "$allanime_refr" -s -G "${allanime_api}/api" --data-urlencode "variables={\"showId\":\"$*\"}" --data-urlencode "query=$episodes_list_gql" -A "$agent" | sed -nE "s|.*$mode\":\[([0-9.\",]*)\].*|\1|p" | sed 's|,|\
episode_embed_gql="query (\$showId: String!, \$translationType: VaildTranslationTypeEnumType!, \$episodeString: String!) { episode( showId: \$showId translationType: \$translationType episodeString: \$episodeString ) { episodeString sourceUrls }}"
resp=$(curl -e "$allanime_refr" -s -G "${allanime_api}/api" --data-urlencode "variables={\"showId\":\"$id\",\"translationType\":\"$mode\",\"episodeString\":\"$ep_no\"}" --data-urlencode "query=$episode_embed_gql" -A "$agent" | tr '{}' '\n' | sed 's|\\u002F|\/|g;s|\\||g' | sed -nE 's|.*sourceUrl":"--([^"]*)".*sourceName":"([^"]*)".*|\2 :\1|p')

72
lscraping/scrapy.py Normal file
View File

@@ -0,0 +1,72 @@
import requests
# graphql queries
search_gql = """
query(
$search: SearchInput,
$limit: Int,
$page: Int,
$translationType: VaildTranslationTypeEnumType,
$countryOrigin: VaildCountryOriginEnumType
) {
shows(
search: $search,
limit: $limit,
page: $page,
translationType: $translationType,
countryOrigin: $countryOrigin
) {
edges {
_id
name
availableEpisodes
__typename
}
}
}
"""
# allanime constants
agent = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0"
)
allanime_base = "allanime.day"
allanime_api = f"https://api.{allanime_base}/api"
##allanime referer header
allanime_referer = "https://allanime.to"
search = {"allowAdult": False, "allowUnknown": False, "query": "one piece"}
limit = 40
translationType = "sub" # dub
countryOrigin = "ALL"
page = 1
variables = {
"search": search,
"limit": limit,
"page": page,
"translationType": translationType,
"countryOrigin": countryOrigin,
}
query = search_gql
resp = requests.get(
allanime_api,
params={"variables": variables, "query": query},
headers={"User-Agent": agent, "Referer": allanime_referer},
)
print(resp.json(), resp.status_code)
episodes_list_gql = """
query ($showId: String!) {
show(_id: $showId) {
_id availableEpisodesDetail
}
}
"""
# curl -e "$allanime_refr" -s -G "${allanime_api}/api" --data-urlencode "variables={\"showId\":\"$*\"}" --data-urlencode "query=$episodes_list_gql" -A "$agent" | sed -nE "s|.*$mode\":\[([0-9.\",]*)\].*|\1|p" | sed 's|,|\

View File

@@ -1,4 +1,5 @@
{
"venv": ".venv",
"venvPath": "."
"venvPath": ".",
"typeCheckingMode": "standard"
}