diff --git a/fastanime/Utility/anilist_data_helper.py b/fastanime/Utility/anilist_data_helper.py index d8266ad..1365e5d 100644 --- a/fastanime/Utility/anilist_data_helper.py +++ b/fastanime/Utility/anilist_data_helper.py @@ -30,6 +30,6 @@ def format_list_data_with_comma(data: list | None): def extract_next_airing_episode(airing_episode: AnilistMediaNextAiringEpisode): if airing_episode: - return f"Episode: {airing_episode['episode']} on {format_anilist_timestamp(airing_episode['airingAt'])}" + return f"{airing_episode['episode']} on {format_anilist_timestamp(airing_episode['airingAt'])}" else: return "Completed" diff --git a/fastanime/Utility/utils.py b/fastanime/Utility/utils.py index d27d564..6324650 100644 --- a/fastanime/Utility/utils.py +++ b/fastanime/Utility/utils.py @@ -7,6 +7,11 @@ from datetime import datetime # from .kivy_markup_helper import color_text +def remove_html_tags(text): + clean = re.compile("<.*?>") + return re.sub(clean, "", text) + + # utility functions def write_crash(e: Exception): index = datetime.today() diff --git a/fastanime/__init__.py b/fastanime/__init__.py index 47e5e06..50dcb2a 100644 --- a/fastanime/__init__.py +++ b/fastanime/__init__.py @@ -36,6 +36,7 @@ if not APP_DATA_DIR: USER_DATA_PATH = os.path.join(APP_DATA_DIR, "user_data.json") USER_CONFIG_PATH = os.path.join(APP_DATA_DIR, "config.ini") +USER_WATCH_HISTORY = os.path.join(APP_DATA_DIR, "watch_history.json") # video dir diff --git a/fastanime/cli/__init__.py b/fastanime/cli/__init__.py index 6215f45..4046ff3 100644 --- a/fastanime/cli/__init__.py +++ b/fastanime/cli/__init__.py @@ -24,7 +24,7 @@ commands = { "--server", type=click.Choice(SERVERS_AVAILABLE, case_sensitive=False), ) -@click.option("-h", "--hist", type=bool) +@click.option("-c-h/-no-h", "--continue_h/--no-continue_h", type=bool) @click.option("-q", "--quality", type=int) @click.option("-t-t", "--translation_type") @click.option("-a-n", "--auto-next", type=bool) @@ -38,7 +38,7 @@ commands = { def run_cli( ctx: click.Context, server, - hist, + continue_h, translation_type, quality, auto_next, @@ -48,8 +48,8 @@ def run_cli( ctx.obj = Config() if server: ctx.obj.server = server - if hist: - ctx.obj.continue_from_history = hist + if continue_h: + ctx.obj.continue_from_history = continue_h if quality: ctx.obj.quality = quality if auto_next: diff --git a/fastanime/cli/commands/anilist/__init__.py b/fastanime/cli/commands/anilist/__init__.py index 5f1ae65..3266395 100644 --- a/fastanime/cli/commands/anilist/__init__.py +++ b/fastanime/cli/commands/anilist/__init__.py @@ -1,6 +1,7 @@ import click -from ...interfaces import anilist as anilist_interface +from ...interfaces.anilist_interfaces import anilist as anilist_interface +from ...utils.tools import QueryDict from .favourites import favourites from .popular import popular from .recent import recent @@ -19,6 +20,8 @@ commands = { @click.group(commands=commands, invoke_without_command=True) -@click.pass_obj -def anilist(config): - anilist_interface(config=config) +@click.pass_context +def anilist(ctx: click.Context): + if ctx.invoked_subcommand is None: + anilist_config = QueryDict() + anilist_interface(ctx.obj, anilist_config) diff --git a/fastanime/cli/commands/anilist/favourites.py b/fastanime/cli/commands/anilist/favourites.py index 1825149..e10c99e 100644 --- a/fastanime/cli/commands/anilist/favourites.py +++ b/fastanime/cli/commands/anilist/favourites.py @@ -1,6 +1,15 @@ import click +from ....libs.anilist.anilist import AniList +from ...interfaces.anilist_interfaces import select_anime +from ...utils.tools import QueryDict + @click.command() -def favourites(): - print("favourites") +@click.pass_obj +def favourites(config): + anime_data = AniList.get_most_favourite() + if anime_data[0]: + anilist_config = QueryDict() + anilist_config.data = anime_data[1] + select_anime(config, anilist_config) diff --git a/fastanime/cli/commands/search.py b/fastanime/cli/commands/search.py index bd92bb6..1ccac30 100644 --- a/fastanime/cli/commands/search.py +++ b/fastanime/cli/commands/search.py @@ -1,7 +1,5 @@ import click -from ..interfaces import anime_provider_ - @click.command() @click.pass_obj @@ -9,7 +7,4 @@ def search( config, anime_title, ): - anime_provider_( - config, - anime_title, - ) + pass diff --git a/fastanime/cli/config.py b/fastanime/cli/config.py index 0566005..a688306 100644 --- a/fastanime/cli/config.py +++ b/fastanime/cli/config.py @@ -1,7 +1,8 @@ +import json import os from configparser import ConfigParser -from .. import USER_CONFIG_PATH, USER_DOWNLOADS_DIR +from .. import USER_CONFIG_PATH, USER_DOWNLOADS_DIR, USER_WATCH_HISTORY class Config(object): @@ -15,6 +16,7 @@ class Config(object): "sort_by": "search match", "downloads_dir": USER_DOWNLOADS_DIR, "translation_type": "sub", + "preferred_language": "romaji", } ) self.configparser.add_section("stream") @@ -33,10 +35,26 @@ class Config(object): self.auto_next = self.get_auto_next() self.quality = self.get_quality() self.server = self.get_server() + self.preferred_language = self.get_preferred_language() + + # ---- setup history ------ + if not os.path.exists(USER_WATCH_HISTORY): + self.watch_history = {} + else: + with open(USER_WATCH_HISTORY, "r") as history: + self.watch_history = json.load(history) + + def update_watch_history(self, title, episode): + with open(USER_WATCH_HISTORY, "w") as history: + self.watch_history[title] = episode + json.dump(self.watch_history, history) def get_downloads_dir(self): return self.configparser.get("general", "downloads_dir") + def get_preferred_language(self): + return self.configparser.get("general", "preferred_language") + def get_sort_by(self): return self.configparser.get("anilist", "sort_by") diff --git a/fastanime/cli/interfaces.py b/fastanime/cli/interfaces.py deleted file mode 100644 index e9cc467..0000000 --- a/fastanime/cli/interfaces.py +++ /dev/null @@ -1,215 +0,0 @@ -from __future__ import annotations - -from InquirerPy import inquirer -from rich import print - -from ..libs.anilist.anilist import AniList -from ..libs.anilist.anilist_data_schema import AnilistDataSchema -from ..libs.anime_provider.allanime.api import anime_provider -from .config import Config -from .utils.mpv import mpv -from .utils.utils import clear, fuzzy_inquirer, get_selected_anime, get_selected_server - - -def fetch_episode(config: Config, anime, translation_type, selected_anime): - # fetch episode - episode_number = fuzzy_inquirer( - "Select Episode:", - [*anime["show"]["availableEpisodesDetail"][translation_type], "back"], - ) - if episode_number == "back": - anime_provider_( - config, - selected_anime[0]["name"], - ) - return - episode = anime_provider.get_anime_episode( - selected_anime[0]["_id"], episode_number, config.translation_type - ) - - fetch_streams(config, episode, anime, translation_type, selected_anime) - - -def fetch_streams(config: Config, episode, *args): - episode_streams = list(anime_provider.get_episode_streams(episode)) - - server = fuzzy_inquirer( - "Select Server:", [episode_stream[0] for episode_stream in episode_streams] - ) - selected_server = get_selected_server(server, episode_streams) - - quality = config.quality - links = selected_server[1]["links"] - if quality > len(links) - 1: - quality = config.quality = len(links) - 1 - elif quality < 0: - quality = config.quality = 0 - stream_link = links[quality]["link"] - print( - "[bold magenta]Now playing:[/]", - args[-1][0]["name"], - "[bold magenta] Episode: [/]", - episode["episode"]["episodeString"], - ) - mpv(stream_link) - clear() - player_controls(config, episode, links, *args) - - -def player_controls(config: Config, episode, links: list, *args): - anime = args[0] - selected_anime = args[-1] - episodes = [*anime["show"]["availableEpisodesDetail"][config.translation_type]] - episodes = sorted(episodes, key=int) - current_episode = episode["episode"]["episodeString"] - - def _back(): - fetch_streams(config, episode, *args) - - def _replay(): - stream_link = links[config.quality]["link"] - print( - "[bold magenta]Now playing:[/]", - args[-1][0]["name"], - "[bold magenta] Episode: [/]", - episode["episode"]["episodeString"], - ) - mpv(stream_link) - clear() - player_controls(config, episode, links, *args) - - def _next_episode(): - next_episode = episodes.index(current_episode) + 1 - if next_episode >= len(episodes): - next_episode = len(episodes) - 1 - episode = anime_provider.get_anime_episode( - selected_anime[0]["_id"], episodes[next_episode], config.translation_type - ) - - fetch_streams(config, episode, *args) - - def _episodes(): - fetch_episode(config, *args) - - def _previous_episode(): - prev_episode = episodes.index(current_episode) - 1 - if prev_episode <= 0: - prev_episode = 0 - episode = anime_provider.get_anime_episode( - selected_anime[0]["_id"], episodes[prev_episode], config.translation_type - ) - - fetch_streams(config, episode, *args) - - def _change_quality(): - options = [link["link"] for link in links] - quality = fuzzy_inquirer("Select Quality:", options) - config.quality = options.index(quality) # set quality - player_controls(config, episode, links, *args) - - def _change_translation_type(): - options = ["sub", "dub"] - translation_type = fuzzy_inquirer("Select Translation Type:", options) - config.translation_type = translation_type # set trannslation type - player_controls(config, episode, links, *args) - - options = { - "Replay": _replay, - "Next Episode": _next_episode, - "Episodes": _episodes, - "Previous Episode": _previous_episode, - "Change Quality": _change_quality, - "Change Translation Type": _change_translation_type, - "Back": _back, - } - - action = fuzzy_inquirer("Select Action:", options.keys()) - options[action]() - - -def anime_provider_(config: Config, anime_title, **kwargs): - translation_type = config.translation_type - search_results = anime_provider.search_for_anime(anime_title, translation_type) - search_results_anime_titles = [ - anime["name"] for anime in search_results["shows"]["edges"] - ] - selected_anime_title = fuzzy_inquirer( - "Select Search Result:", - [*search_results_anime_titles, "back"], - default=kwargs.get("default_anime_title", ""), - ) - if selected_anime_title == "back": - anilist(config) - return - fetch_anime_epiosode( - config, - selected_anime_title, - search_results, - ) - - -def fetch_anime_epiosode(config, selected_anime_title, search_results): - translation_type = config.translation_type - selected_anime = get_selected_anime(selected_anime_title, search_results) - anime = anime_provider.get_anime(selected_anime[0]["_id"]) - - fetch_episode(config, anime, translation_type, selected_anime) - - -def _stream(config, anilist_data: AnilistDataSchema, preferred_lang="romaji"): - anime_titles = [ - str(anime["title"][preferred_lang]) - for anime in anilist_data["data"]["Page"]["media"] - ] - selected_anime_title = fuzzy_inquirer("Select Anime:", anime_titles) - anime_provider_( - config, selected_anime_title, default_anime_title=selected_anime_title - ) - - -def anilist_options(config, anilist_data: AnilistDataSchema): - def _watch_trailer(): - pass - - def _add_to_list(): - pass - - def _remove_from_list(): - pass - - def _view_info(): - pass - - options = { - "stream": _stream, - "watch trailer": _watch_trailer, - "add to list": _add_to_list, - "remove from list": _remove_from_list, - "view info": _view_info, - "back": anilist, - } - action = fuzzy_inquirer("Select Action:", options.keys()) - options[action](config, anilist_data) - - -def anilist(config, *args, **kwargs): - def _anilist_search(): - search_term = inquirer.text( - "Search:", instruction="Enter anime to search for" - ).execute() - - return AniList.search(query=search_term) - - options = { - "trending": AniList.get_trending, - "search": _anilist_search, - "most popular anime": AniList.get_most_popular, - "most favourite anime": AniList.get_most_favourite, - "most scored anime": AniList.get_most_scored, - "upcoming anime": AniList.get_most_favourite, - "recently updated anime": AniList.get_most_recently_updated, - } - action = fuzzy_inquirer("Select Action:", options.keys()) - anilist_data = options[action]() - if anilist_data[0]: - anilist_options(config, anilist_data[1]) diff --git a/fastanime/cli/interfaces/anilist_interfaces.py b/fastanime/cli/interfaces/anilist_interfaces.py new file mode 100644 index 0000000..48ed54e --- /dev/null +++ b/fastanime/cli/interfaces/anilist_interfaces.py @@ -0,0 +1,404 @@ +from __future__ import annotations + +import sys + +from InquirerPy import inquirer +from rich import print + +from ...libs.anilist.anilist import AniList +from ...libs.anilist.anilist_data_schema import AnilistBaseMediaDataSchema +from ...libs.anime_provider.allanime.api import anime_provider +from ...libs.anime_provider.allanime.data_types import AllAnimeEpisode, AllAnimeShow +from ..config import Config +from ..utils.mpv import mpv +from ..utils.tools import QueryDict +from ..utils.utils import clear, fuzzy_inquirer + + +# FIXME: BACK brocken +def player_controls(config: Config, anilist_config: QueryDict): + # user config + translation_type: str = config.translation_type + + # internal config + _anime: AllAnimeShow = anilist_config._anime + current_episode: str = anilist_config.episode_number + episodes: list = anilist_config.episodes + links: list = anilist_config.current_stream_links + current_link: str = anilist_config.current_stream_link + anime_title: str = anilist_config.anime_title + + def _back(): + fetch_streams(config, anilist_config) + + def _replay(): + print( + "[bold magenta]Now Replaying:[/]", + anime_title, + "[bold magenta] Episode: [/]", + current_episode, + ) + + config.update_watch_history(anime_title, current_episode) + + mpv(current_link) + clear() + player_controls(config, anilist_config) + + def _next_episode(): + next_episode = episodes.index(current_episode) + 1 + if next_episode >= len(episodes): + next_episode = len(episodes) - 1 + episode = anime_provider.get_anime_episode( + _anime["_id"], episodes[next_episode], translation_type + ) + + # update internal config + anilist_config.episode = episode + anilist_config.episode_number = episodes[next_episode] + + # update user config + config.update_watch_history(anime_title, episodes[next_episode]) + + # call interface + fetch_streams(config, anilist_config) + + def _episodes(): + # reset watch_history + config.update_watch_history(anime_title, None) + + # call interface + fetch_episode(config, anilist_config) + + def _previous_episode(): + prev_episode = episodes.index(current_episode) - 1 + if prev_episode <= 0: + prev_episode = 0 + episode = anime_provider.get_anime_episode( + _anime["_id"], episodes[prev_episode], config.translation_type + ) + + # update internal config + anilist_config.episode = episode + # anilist_config.episode_title = episode["title"] + anilist_config.episode_number = episodes[prev_episode] + + # update user config + config.update_watch_history(anime_title, episodes[prev_episode]) + + # call interface + fetch_streams(config, anilist_config) + + def _change_quality(): + # extract the actual link urls + options = [link["link"] for link in links] + + # prompt for new quality + quality = fuzzy_inquirer("Select Quality:", options) + config.quality = options.index(quality) # set quality + player_controls(config, anilist_config) + + def _change_translation_type(): + # prompt for new translation type + options = ["sub", "dub"] + translation_type = fuzzy_inquirer("Select Translation Type:", options) + + # update internal config + config.translation_type = translation_type + + # reload to controls + player_controls(config, anilist_config) + + options = { + "Replay": _replay, + "Next Episode": _next_episode, + "Episodes": _episodes, + "Previous Episode": _previous_episode, + "Change Quality": _change_quality, + "Change Translation Type": _change_translation_type, + "Back to servers": _back, + "Go to Main Menu": lambda: anilist(config, anilist_config), + "Go to Anime Options Menu": lambda: anilist_options(config, anilist_config), + "Go to Search Results": lambda: select_anime(config, anilist_config), + "exit": sys.exit, + } + + action = fuzzy_inquirer("Select Action:", options.keys()) + + # update_watch_history + config.update_watch_history(anime_title, current_episode) + options[action]() + + +def fetch_streams(config: Config, anilist_config: QueryDict): + # user config + quality: int = config.quality + + # internal config + episode: AllAnimeEpisode = anilist_config.episode + episode_number: str = anilist_config.episode_number + anime_title: str = anilist_config.anime_title + + # get streams for episode from provider + episode_streams = anime_provider.get_episode_streams(episode) + episode_streams = { + episode_stream[0]: episode_stream[1] for episode_stream in episode_streams + } + + # prompt for preferred server + server = fuzzy_inquirer("Select Server:", [*episode_streams.keys(), "back"]) + if server == "back": + # reset watch_history + config.update_watch_history(anime_title, None) + + fetch_episode(config, anilist_config) + return + selected_server = episode_streams[server] + + links = selected_server["links"] + if quality > len(links) - 1: + quality = config.quality = len(links) - 1 + elif quality < 0: + quality = config.quality = 0 + stream_link = links[quality]["link"] + + # update internal config + anilist_config.current_stream_links = links + anilist_config.current_stream_link = stream_link + anilist_config.current_server = selected_server + anilist_config.current_server_name = server + + # play video + print( + "[bold magenta]Now playing:[/]", + anime_title, + "[bold magenta] Episode: [/]", + episode_number, + ) + + mpv(stream_link) + + # switch to controls + clear() + player_controls(config, anilist_config) + + +def fetch_episode(config: Config, anilist_config: QueryDict): + # user config + translation_type: str = config.translation_type + continue_from_history: bool = config.continue_from_history + user_watch_history = config.watch_history + anime_title = anilist_config.anime_title + + # internal config + anime = anilist_config.anime + _anime: AllAnimeShow = anilist_config._anime + + # prompt for episode number + # TODO: Load episode number from cache + episodes = anime["show"]["availableEpisodesDetail"][translation_type] + if continue_from_history and user_watch_history.get(anime_title) in episodes: + episode_number = user_watch_history[anime_title] + print(f"[bold cyan]Continuing from Episode:[/] [bold]{episode_number}[/]") + else: + episode_number = fuzzy_inquirer( + "Select Episode:", + [*episodes, "back"], + ) + + if episode_number == "back": + provide_anime(config, anilist_config) + return + config.update_watch_history(anime_title, episode_number) + + # get the episode info from provider + episode = anime_provider.get_anime_episode( + _anime["_id"], episode_number, translation_type + ) + + # update internal config + anilist_config.episodes = episodes + anilist_config.episode = episode + # anilist_config.episode_title = episode["title"] + anilist_config.episode_number = episode_number + + # next interface + fetch_streams(config, anilist_config) + + +def fetch_anime_epiosode(config, anilist_config: QueryDict): + selected_anime: AllAnimeShow = anilist_config._anime + anilist_config.anime = anime_provider.get_anime(selected_anime["_id"]) + + fetch_episode(config, anilist_config) + + +def provide_anime(config: Config, anilist_config: QueryDict): + # user config + translation_type = config.translation_type + + # internal config + selected_anime_title = anilist_config.selected_anime_title + + # search and get the requested title from provider + search_results = anime_provider.search_for_anime( + selected_anime_title, translation_type + ) + + search_results = { + anime["name"]: anime for anime in search_results["shows"]["edges"] + } + anime_title = fuzzy_inquirer( + "Select Search Result:", + [*search_results.keys(), "back"], + default=selected_anime_title, + ) + + if anime_title == "back": + anilist_options(config, anilist_config) + return + anilist_config.anime_title = anime_title + anilist_config._anime = search_results[anime_title] + fetch_anime_epiosode(config, anilist_config) + + +def anilist_options(config, anilist_config: QueryDict): + selected_anime: AnilistBaseMediaDataSchema = anilist_config.selected_anime_anilist + selected_anime_title: str = anilist_config.selected_anime_title + + def _watch_trailer(config, anilist_config): + if trailer := selected_anime.get("trailer"): + trailer_url = "https://youtube.com/watch?v=" + trailer["id"] + print("[bold magenta]Watching Trailer of:[/]", selected_anime_title) + mpv(trailer_url) + anilist_options(config, anilist_config) + + def _add_to_list(config, anilist_config): + pass + + def _remove_from_list(): + pass + + def _change_translation_type(config, anilist_config): + # prompt for new translation type + options = ["sub", "dub"] + translation_type = fuzzy_inquirer("Select Translation Type:", options) + + # update internal config + config.translation_type = translation_type + + anilist_options(config, anilist_config) + + def _view_info(config, anilist_config): + from InquirerPy import inquirer + from rich.console import Console + + from ...Utility import anilist_data_helper + from ...Utility.utils import remove_html_tags + from ..utils.print_img import print_img + + clear() + console = Console() + + print_img(selected_anime["coverImage"]["medium"]) + console.print("[bold cyan]Title(jp): ", selected_anime["title"]["romaji"]) + console.print("[bold cyan]Title(eng): ", selected_anime["title"]["english"]) + console.print("[bold cyan]Popularity: ", selected_anime["popularity"]) + console.print("[bold cyan]Favourites: ", selected_anime["favourites"]) + console.print("[bold cyan]Status: ", selected_anime["status"]) + console.print( + "[bold cyan]Start Date: ", + anilist_data_helper.format_anilist_date_object(selected_anime["startDate"]), + ) + console.print( + "[bold cyan]End Date: ", + anilist_data_helper.format_anilist_date_object(selected_anime["endDate"]), + ) + # console.print("[bold cyan]Season: ", selected_anime["season"]) + console.print("[bold cyan]Episodes: ", selected_anime["episodes"]) + console.print( + "[bold cyan]Tags: ", + anilist_data_helper.format_list_data_with_comma( + [tag["name"] for tag in selected_anime["tags"]] + ), + ) + console.print( + "[bold cyan]Genres: ", + anilist_data_helper.format_list_data_with_comma(selected_anime["genres"]), + ) + # console.print("[bold cyan]Type: ", selected_anime["st"]) + if selected_anime["nextAiringEpisode"]: + console.print( + "[bold cyan]Next Episode: ", + anilist_data_helper.extract_next_airing_episode( + selected_anime["nextAiringEpisode"] + ), + ) + console.print( + "[bold underline cyan]Description\n[/]", + remove_html_tags(str(selected_anime["description"])), + ) + if inquirer.confirm("Enter to continue", default=True).execute(): + anilist_options(config, anilist_config) + return + + options = { + "stream": provide_anime, + "watch trailer": _watch_trailer, + "add to list": _add_to_list, + "remove from list": _remove_from_list, + "view info": _view_info, + "Change Translation Type": _change_translation_type, + "back": select_anime, + } + action = fuzzy_inquirer("Select Action:", options.keys()) + options[action](config, anilist_config) + + +def select_anime(config: Config, anilist_config: QueryDict): + anime_data = { + str( + anime["title"][config.preferred_language] or anime["title"]["romaji"] + ): anime + for anime in anilist_config.data["data"]["Page"]["media"] + } + selected_anime_title = fuzzy_inquirer( + "Select Anime:", + [ + *anime_data.keys(), + "back", + ], + ) + if selected_anime_title == "back": + anilist(config, anilist_config) + return + + selected_anime = anime_data[selected_anime_title] + anilist_config.selected_anime_anilist = selected_anime + anilist_config.selected_anime_title = selected_anime_title + + anilist_options(config, anilist_config) + + +def anilist(config: Config, anilist_config: QueryDict): + def _anilist_search(): + search_term = inquirer.text( + "Search:", instruction="Enter anime to search for" + ).execute() + + return AniList.search(query=search_term) + + options = { + "trending": AniList.get_trending, + "search": _anilist_search, + "most popular anime": AniList.get_most_popular, + "most favourite anime": AniList.get_most_favourite, + "most scored anime": AniList.get_most_scored, + "upcoming anime": AniList.get_upcoming_anime, + "recently updated anime": AniList.get_most_recently_updated, + } + action = fuzzy_inquirer("Select Action:", options.keys()) + anilist_data = options[action]() + if anilist_data[0]: + anilist_config.data = anilist_data[1] + select_anime(config, anilist_config) diff --git a/fastanime/cli/utils/print_img.py b/fastanime/cli/utils/print_img.py new file mode 100644 index 0000000..68dd471 --- /dev/null +++ b/fastanime/cli/utils/print_img.py @@ -0,0 +1,24 @@ +import shutil +import subprocess + +import requests + + +def print_img(url: str): + executable = shutil.which("chafa") + curl = shutil.which("curl") + # curl -sL "$1" | chafa /dev/stdin + + if executable is None or curl is None: + print("chafa or curl not found") + return + + res = requests.get(url) + if res.status_code != 200: + print("Error fetching image") + return + img_bytes = res.content + if not img_bytes: + print("No image found") + img_bytes = subprocess.check_output([curl, "-sL", url]) + subprocess.run([executable, url, "--size=15x15"], input=img_bytes) diff --git a/fastanime/cli/utils/tools.py b/fastanime/cli/utils/tools.py new file mode 100644 index 0000000..6e1b0a7 --- /dev/null +++ b/fastanime/cli/utils/tools.py @@ -0,0 +1,13 @@ +class QueryDict(dict): + """dot.notation access to dictionary attributes""" + + def __getattr__(self, attr): + try: + return self.__getitem__(attr) + except KeyError: + raise AttributeError( + "%r object has no attribute %r" % (self.__class__.__name__, attr) + ) + + def __setattr__(self, attr, value): + self.__setitem__(attr, value) diff --git a/fastanime/cli/utils/utils.py b/fastanime/cli/utils/utils.py index 5428d84..96b681a 100644 --- a/fastanime/cli/utils/utils.py +++ b/fastanime/cli/utils/utils.py @@ -20,7 +20,12 @@ def clear(): def fuzzy_inquirer(prompt: str, choices, **kwargs): clear() action = inquirer.fuzzy( - prompt, choices, height="100%", border=True, **kwargs + prompt, + choices, + height="100%", + border=True, + validate=lambda result: result in choices, + **kwargs, ).execute() return action diff --git a/fastanime/libs/anilist/anilist.py b/fastanime/libs/anilist/anilist.py index 9552938..4fd0dda 100644 --- a/fastanime/libs/anilist/anilist.py +++ b/fastanime/libs/anilist/anilist.py @@ -175,7 +175,7 @@ class AniList: return airing_schedule @classmethod - def get_upcoming_anime(cls, page: int, *_, **kwargs): + def get_upcoming_anime(cls, page: int = 1, *_, **kwargs): """ Gets upcoming anime from anilist """ diff --git a/fastanime/libs/anime_provider/allanime/api.py b/fastanime/libs/anime_provider/allanime/api.py index 49124e1..06bb7a0 100644 --- a/fastanime/libs/anime_provider/allanime/api.py +++ b/fastanime/libs/anime_provider/allanime/api.py @@ -11,7 +11,6 @@ from .constants import ( ALLANIME_REFERER, USER_AGENT, ) -from .data_types import AllAnimeEpisode, AllAnimeSearchResults from .gql_queries import ALLANIME_EPISODES_GQL, ALLANIME_SEARCH_GQL, ALLANIME_SHOW_GQL from .utils import decode_hex_string @@ -27,7 +26,7 @@ class AllAnimeAPI: api_endpoint = ALLANIME_API_ENDPOINT - def _fetch_gql(self, query: str, variables: dict) -> dict: + def _fetch_gql(self, query: str, variables: dict): try: response = requests.get( self.api_endpoint, @@ -38,16 +37,12 @@ class AllAnimeAPI: headers={"Referer": ALLANIME_REFERER, "User-Agent": USER_AGENT}, timeout=10, ) - if response.status_code != 200: - return {} - return response.json().get("data", {}) + return response.json()["data"] except Exception as e: Logger.error(f"allanime:Error: {e}") return {} - def search_for_anime( - self, user_query: str, translation_type: str = "sub" - ) -> AllAnimeSearchResults | dict: + 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 @@ -75,7 +70,7 @@ class AllAnimeAPI: def get_anime_episode( self, allanime_show_id: str, episode_string: str, translation_type: str = "sub" - ) -> AllAnimeEpisode | dict: + ): variables = { "showId": allanime_show_id, "translationType": translation_type, diff --git a/pyproject.toml b/pyproject.toml index 50414b6..ddabb97 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ fuzzywuzzy = "^0.18.0" rich = "^13.7.1" click = "^8.1.7" python-levenshtein = "^0.25.1" -kivymd = [{url = "https://github.com/kivymd/KivyMD/archive/master.zip"}] +kivymd = [{ url = "https://github.com/kivymd/KivyMD/archive/master.zip" }] pyshortcuts = "^1.9.0" inquirerpy = "^0.3.4" @@ -40,4 +40,4 @@ fastanime = 'fastanime:FastAnime' [tool.bandit] #exclude = tests,path/to/file #tests = B201,B301 -skips = ["B311","B603","B607","B404"] +skips = ["B311", "B603", "B607", "B404"]