diff --git a/fastanime/cli/__init__.py b/fastanime/cli/__init__.py index bfcac23..06d7864 100644 --- a/fastanime/cli/__init__.py +++ b/fastanime/cli/__init__.py @@ -114,6 +114,16 @@ signal.signal(signal.SIGINT, handle_exit) @click.option("--sub", help="Set the translation type to sub", is_flag=True) @click.option("--rofi", help="Use rofi for the ui", is_flag=True) @click.option("--rofi-theme", help="Rofi theme to use", type=click.Path()) +@click.option( + "--rofi-theme-confirm", + help="Rofi theme to use for the confirm prompt", + type=click.Path(), +) +@click.option( + "--rofi-theme-input", + help="Rofi theme to use for the user input prompt", + type=click.Path(), +) @click.pass_context def run_cli( ctx: click.Context, @@ -137,6 +147,8 @@ def run_cli( sub, rofi, rofi_theme, + rofi_theme_confirm, + rofi_theme_input, ): ctx.obj = Config() if provider: @@ -183,8 +195,17 @@ def run_cli( if rofi: ctx.obj.use_fzf = False ctx.obj.use_rofi = True - if rofi_theme: - ctx.obj.rofi_theme = rofi_theme + if rofi: from ..libs.rofi import Rofi - Rofi.rofi_theme = rofi_theme + if rofi_theme: + ctx.obj.rofi_theme = rofi_theme + Rofi.rofi_theme = rofi_theme + + if rofi_theme_input: + ctx.obj.rofi_theme_input = rofi_theme_input + Rofi.rofi_theme_input = rofi_theme_input + + if rofi_theme_confirm: + ctx.obj.rofi_theme_confirm = rofi_theme_confirm + Rofi.rofi_theme_confirm = rofi_theme_confirm diff --git a/fastanime/cli/config.py b/fastanime/cli/config.py index c0808fb..c2d3d5e 100644 --- a/fastanime/cli/config.py +++ b/fastanime/cli/config.py @@ -41,6 +41,8 @@ class Config(object): "skip": "false", "use_rofi": "false", "rofi_theme": "", + "rofi_theme_input": "", + "rofi_theme_confirm": "", } ) self.configparser.add_section("stream") @@ -73,7 +75,10 @@ class Config(object): self.preferred_language = self.get_preferred_language() self.rofi_theme = self.get_rofi_theme() Rofi.rofi_theme = self.rofi_theme - + self.rofi_theme_input = self.get_rofi_theme_input() + Rofi.rofi_theme_input = self.rofi_theme_input + self.rofi_theme_confirm = self.get_rofi_theme_confirm() + Rofi.rofi_theme_confirm = self.rofi_theme_confirm # ---- setup user data ------ self.watch_history: dict = user_data_helper.user_data.get("watch_history", {}) self.anime_list: list = user_data_helper.user_data.get("animelist", []) @@ -118,6 +123,12 @@ class Config(object): def get_rofi_theme(self): return self.configparser.get("general", "rofi_theme") + def get_rofi_theme_input(self): + return self.configparser.get("general", "rofi_theme_input") + + def get_rofi_theme_confirm(self): + return self.configparser.get("general", "rofi_theme_confirm") + def get_downloads_dir(self): return self.configparser.get("general", "downloads_dir") diff --git a/fastanime/cli/interfaces/anilist_interfaces.py b/fastanime/cli/interfaces/anilist_interfaces.py index a66c99c..e9cdea7 100644 --- a/fastanime/cli/interfaces/anilist_interfaces.py +++ b/fastanime/cli/interfaces/anilist_interfaces.py @@ -108,12 +108,19 @@ def player_controls(config: Config, anilist_config: QueryDict): dt = delta.total_seconds() if dt > error: if config.auto_next: - if not Confirm.ask( - "Are you sure you wish to continue to the next episode you haven't completed the current episode?", - default=False, - ): - anilist_options(config, anilist_config) - return + if config.use_rofi: + if not Rofi.confirm( + "Are you sure you wish to continue to the next episode you haven't completed the current episode?" + ): + anilist_options(config, anilist_config) + return + else: + if not Confirm.ask( + "Are you sure you wish to continue to the next episode you haven't completed the current episode?", + default=False, + ): + anilist_options(config, anilist_config) + return elif not config.use_rofi: if not Confirm.ask( "Are you sure you wish to continue to the next episode, your progress for the current episodes will be erased?", @@ -245,8 +252,12 @@ def fetch_streams(config: Config, anilist_config: QueryDict): anime, episode_number, translation_type ) if not episode_streams: - print("Failed to fetch :cry:") - input("Enter to retry...") + if not config.use_rofi: + print("Failed to fetch :cry:") + input("Enter to retry...") + else: + if not Rofi.confirm("Sth went wrong!!Enter to continue..."): + exit(1) return fetch_streams(config, anilist_config) episode_streams = { @@ -406,11 +417,14 @@ def fetch_anime_episode(config, anilist_config: QueryDict): progress.add_task("Fetching Anime Info...", total=None) anilist_config.anime = anime_provider.get_anime(selected_anime["id"]) if not anilist_config.anime: - print( "Sth went wrong :cry: this could mean the provider is down or your internet" ) - input("Enter to continue...") + if not config.use_rofi: + input("Enter to continue...") + else: + if not Rofi.confirm("Sth went wrong!!Enter to continue..."): + exit(1) fetch_anime_episode(config, anilist_config) return @@ -437,7 +451,11 @@ def provide_anime(config: Config, anilist_config: QueryDict): print( "Sth went wrong :cry: while fetching this could mean you have poor internet connection or the provider is down" ) - input("Enter to continue...") + if not config.use_rofi: + input("Enter to continue...") + else: + if not Rofi.confirm("Sth went wrong!!Enter to continue..."): + exit(1) provide_anime(config, anilist_config) return @@ -494,8 +512,12 @@ def anilist_options(config, anilist_config: QueryDict): ) anilist_options(config, anilist_config) else: - print("no trailer available :confused:") - input("Enter to continue...") + if not config.use_rofi: + print("no trailer available :confused:") + input("Enter to continue...") + else: + if not Rofi.confirm("No trailler found!!Enter to continue"): + exit(0) anilist_options(config, anilist_config) def _add_to_list(config: Config, anilist_config: QueryDict): @@ -531,16 +553,21 @@ def anilist_options(config, anilist_config: QueryDict): print( f"Successfully added {selected_anime_title} to your {anime_list} list :smile:" ) - input("Enter to continue...") + if not config.use_rofi: + input("Enter to continue...") anilist_options(config, anilist_config) def _score_anime(config: Config, anilist_config: QueryDict): - score = inquirer.number( - message="Enter the score:", - min_allowed=0, - max_allowed=100, - validate=EmptyInputValidator(), - ).execute() + if config.use_rofi: + score = Rofi.ask("Enter Score", is_int=True) + score = max(100, min(0, score)) + else: + score = inquirer.number( + message="Enter the score:", + min_allowed=0, + max_allowed=100, + validate=EmptyInputValidator(), + ).execute() result = AniList.update_anime_list( {"scoreRaw": score, "mediaId": selected_anime["id"]} @@ -549,7 +576,8 @@ def anilist_options(config, anilist_config: QueryDict): print("Failed to update", result) else: print(f"Successfully scored {selected_anime_title}; score: {score}") - input("Enter to continue...") + if not config.use_rofi: + input("Enter to continue...") anilist_options(config, anilist_config) def _remove_from_list(config: Config, anilist_config: QueryDict): @@ -566,7 +594,8 @@ def anilist_options(config, anilist_config: QueryDict): print("Successfully deleted :cry:", selected_anime_title) else: print(selected_anime_title, ":relieved:") - input("Enter to continue...") + if not config.use_rofi: + input("Enter to continue...") anilist_options(config, anilist_config) def _change_translation_type(config: Config, anilist_config: QueryDict): @@ -639,6 +668,14 @@ def anilist_options(config, anilist_config: QueryDict): anilist_options(config, anilist_config) return + def _toggle_auto_select(config, anilist_config): + config.auto_select = not config.auto_select + anilist_options(config, anilist_config) + + def _toggle_auto_next(config, anilist_config): + config.auto_select = not config.auto_select + anilist_options(config, anilist_config) + icons = config.icons options = { f"{'📽️ ' if icons else ''}Stream": provide_anime, @@ -648,6 +685,8 @@ def anilist_options(config, anilist_config: QueryDict): f"{'📤 ' if icons else ''}Remove from List": _remove_from_list, f"{'📖 ' if icons else ''}View Info": _view_info, f"{'🎧 ' if icons else ''}Change Translation Type": _change_translation_type, + f"{'🔘 ' if icons else ''}Toggle auto select anime": _toggle_auto_select, # problematic if you choose an anime that doesnt match id + f"{'💠 ' if icons else ''}Toggle auto next episode": _toggle_auto_next, f"{'🔙 ' if icons else ''}Back": select_anime, f"{'❌ ' if icons else ''}Exit": exit_app, } @@ -691,10 +730,10 @@ def select_anime(config: Config, anilist_config: QueryDict): ) elif config.use_rofi: # TODO: Make this faster - if config.preview and False: - from .utils import SEARCH_RESULTS_CACHE, get_preview + if config.preview: + from .utils import IMAGES_DIR, get_icons - get_preview(search_results, config, wait=True) + get_icons(search_results, config) choices = [] for anime in search_results: title = sanitize_filename( @@ -703,8 +742,7 @@ def select_anime(config: Config, anilist_config: QueryDict): or anime["title"]["romaji"] ) ) - anime_cache = os.path.join(SEARCH_RESULTS_CACHE, title) - icon_path = f"{anime_cache}/image" + icon_path = os.path.join(IMAGES_DIR, title) choices.append(f"{title}\0icon\x1f{icon_path}") choices.append("Back") selected_anime_title = Rofi.run_with_icons(choices, "Select Anime") @@ -729,8 +767,12 @@ def select_anime(config: Config, anilist_config: QueryDict): def handle_animelist(anilist_config, config: Config, list_type: str): if not config.user: - print("You haven't logged in please run: fastanime anilist login") - input("Enter to continue...") + if not config.use_rofi: + print("You haven't logged in please run: fastanime anilist login") + input("Enter to continue...") + else: + if not Rofi.confirm("You haven't logged in!!Enter to continue"): + exit(1) anilist(config, anilist_config) return match list_type: @@ -751,12 +793,21 @@ def handle_animelist(anilist_config, config: Config, list_type: str): anime_list = AniList.get_anime_list(status) if not anime_list: print("Sth went wrong", anime_list) - input("Enter to continue") + if not config.use_rofi: + input("Enter to continue") + else: + if not Rofi.confirm("Sth went wrong!!Enter to continue..."): + exit(1) anilist(config, anilist_config) return if not anime_list[0]: print("Sth went wrong", anime_list) - input("Enter to continue") + if not config.use_rofi: + input("Enter to continue") + else: + if not Rofi.confirm("Sth went wrong!!Enter to continue..."): + exit(1) + anilist(config, anilist_config) return media = [ @@ -769,7 +820,10 @@ def handle_animelist(anilist_config, config: Config, list_type: str): def anilist(config: Config, anilist_config: QueryDict): def _anilist_search(): - search_term = Prompt.ask("[cyan]Search for[/]") + if config.use_rofi: + search_term = str(Rofi.ask("Search for")) + else: + search_term = Prompt.ask("[cyan]Search for[/]") return AniList.search(query=search_term) @@ -834,14 +888,12 @@ def anilist(config: Config, anilist_config: QueryDict): f"{'❌ ' if icons else ''}Exit": exit_app, } if config.use_fzf: - action = fzf.run( list(options.keys()), prompt="Select Action: ", header="Anilist Menu", ) elif config.use_rofi: - action = Rofi.run(list(options.keys()), "Select Action") else: action = fuzzy_inquirer("Select Action", options.keys()) @@ -852,5 +904,9 @@ def anilist(config: Config, anilist_config: QueryDict): else: print(anilist_data[1]) - input("Enter to continue...") + if not config.use_rofi: + input("Enter to continue...") + else: + if not Rofi.confirm("Sth went wrong!!Enter to continue..."): + exit(1) anilist(config, anilist_config) diff --git a/fastanime/cli/utils/tools.py b/fastanime/cli/utils/tools.py index d56c207..8db2b03 100644 --- a/fastanime/cli/utils/tools.py +++ b/fastanime/cli/utils/tools.py @@ -14,13 +14,36 @@ class QueryDict(dict): def exit_app(*args): + import os + import shutil import sys - from rich import print + from ...constants import APP_NAME, ICON_PATH, USER_NAME - from ...constants import USER_NAME + def is_running_in_terminal(): + try: + shutil.get_terminal_size() + return ( + sys.stdin.isatty() + and sys.stdout.isatty() + and os.getenv("TERM") is not None + ) + except OSError: + return False - print("Have a good day :smile:", USER_NAME) + if not is_running_in_terminal(): + from plyer import notification + + notification.notify( + app_name=APP_NAME, + app_icon=ICON_PATH, + message=f"Have a good day {USER_NAME}", + title="Shutting down", + ) # pyright:ignore + else: + from rich import print + + print("Have a good day :smile:", USER_NAME) sys.exit(0) diff --git a/fastanime/libs/rofi/__init__.py b/fastanime/libs/rofi/__init__.py index 8064f98..c11daa2 100644 --- a/fastanime/libs/rofi/__init__.py +++ b/fastanime/libs/rofi/__init__.py @@ -2,11 +2,19 @@ import subprocess from shutil import which from sys import exit +from plyer import notification + +from fastanime import APP_NAME + +from ...constants import ICON_PATH + class RofiApi: ROFI_EXECUTABLE = which("rofi") rofi_theme = "" + rofi_theme_confirm = "" + rofi_theme_input = "" def run_with_icons(self, options: list[str], prompt_text: str) -> str: rofi_input = "\n".join(options) @@ -27,6 +35,12 @@ class RofiApi: choice = result.stdout.strip() if not choice: + notification.notify( + app_name=APP_NAME, + app_icon=ICON_PATH, + message="FastAnime is shutting down", + title="No Valid Input Provided", + ) # pyright:ignore exit(1) return choice @@ -50,9 +64,75 @@ class RofiApi: choice = result.stdout.strip() if not choice or choice not in options: + notification.notify( + app_name=APP_NAME, + app_icon=ICON_PATH, + message="FastAnime is shutting down", + title="No Valid Input Provided", + ) # pyright:ignore exit(1) return choice + def confirm(self, prompt_text: str) -> bool: + rofi_choices = "Yes\nNo" + if not self.ROFI_EXECUTABLE: + raise Exception("Rofi not found") + args = [self.ROFI_EXECUTABLE] + if self.rofi_theme_confirm: + args.extend(["-no-config", "-theme", self.rofi_theme_confirm]) + args.extend(["-p", prompt_text, "-i", "", "-no-fixed-num-lines", "-dmenu"]) + result = subprocess.run( + args, + input=rofi_choices, + stdout=subprocess.PIPE, + text=True, + ) + + choice = result.stdout.strip() + if not choice: + notification.notify( + app_name=APP_NAME, + app_icon=ICON_PATH, + message="FastAnime is shutting down", + title="No Valid Input Provided", + ) # pyright:ignore + exit(1) + if choice == "Yes": + return True + else: + return False + + def ask( + self, prompt_text: str, is_int: bool = False, is_float: bool = False + ) -> str | float | int: + if not self.ROFI_EXECUTABLE: + raise Exception("Rofi not found") + args = [self.ROFI_EXECUTABLE] + if self.rofi_theme_input: + args.extend(["-no-config", "-theme", self.rofi_theme_input]) + args.extend(["-p", prompt_text, "-i", "-no-fixed-num-lines", "-dmenu"]) + result = subprocess.run( + args, + stdout=subprocess.PIPE, + text=True, + ) + + user_input = result.stdout.strip() + if not user_input: + notification.notify( + app_name=APP_NAME, + app_icon=ICON_PATH, + message="FastAnime is shutting down", + title="No Valid Input Provided", + ) # pyright:ignore + exit(1) + if is_float: + user_input = float(user_input) + elif is_int: + user_input = int(user_input) + + return user_input + Rofi = RofiApi()