diff --git a/fastanime/__main__.py b/fastanime/__main__.py index e1d06a5..59b8227 100644 --- a/fastanime/__main__.py +++ b/fastanime/__main__.py @@ -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: diff --git a/lscraping/allanime_api.py b/lscraping/allanime_api.py new file mode 100644 index 0000000..6575dcb --- /dev/null +++ b/lscraping/allanime_api.py @@ -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]) diff --git a/lscraping/anicli.sh b/lscraping/anicli.sh new file mode 100644 index 0000000..03e46b2 --- /dev/null +++ b/lscraping/anicli.sh @@ -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 + 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 diff --git a/lscraping/data.json b/lscraping/data.json new file mode 100644 index 0000000..c44b96b --- /dev/null +++ b/lscraping/data.json @@ -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" + } + ] + } +} diff --git a/lscraping/data2.json b/lscraping/data2.json new file mode 100644 index 0000000..649c61f --- /dev/null +++ b/lscraping/data2.json @@ -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"}]}} \ No newline at end of file diff --git a/lscraping/data3.json b/lscraping/data3.json new file mode 100644 index 0000000..b091db0 --- /dev/null +++ b/lscraping/data3.json @@ -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" + } + ] + } +} diff --git a/lscraping/f.py b/lscraping/f.py new file mode 100644 index 0000000..d5cf5dc --- /dev/null +++ b/lscraping/f.py @@ -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" + ) +) diff --git a/lscraping/final.json b/lscraping/final.json new file mode 100644 index 0000000..8a8a089 --- /dev/null +++ b/lscraping/final.json @@ -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 + } +} diff --git a/lscraping/output.html b/lscraping/output.html new file mode 100644 index 0000000..c1c8a59 --- /dev/null +++ b/lscraping/output.html @@ -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 diff --git a/lscraping/scraper.txt b/lscraping/scraper.txt new file mode 100644 index 0000000..814fd66 --- /dev/null +++ b/lscraping/scraper.txt @@ -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') + diff --git a/lscraping/scrapy.py b/lscraping/scrapy.py new file mode 100644 index 0000000..c4ec7af --- /dev/null +++ b/lscraping/scrapy.py @@ -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|,|\ diff --git a/pyrightconfig.json b/pyrightconfig.json index 2d49b3a..4703a54 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -1,4 +1,5 @@ { "venv": ".venv", - "venvPath": "." + "venvPath": ".", + "typeCheckingMode": "standard" }