From 61db9aeea68b433447b075e96a36c759ea52b36d Mon Sep 17 00:00:00 2001 From: Pixelizer09 <153782533+Pixelizer09@users.noreply.github.com> Date: Thu, 21 Nov 2024 10:57:02 +0800 Subject: [PATCH 01/16] Update data.py --- fastanime/Utility/data.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fastanime/Utility/data.py b/fastanime/Utility/data.py index 1f4b94f..6b0b2d4 100644 --- a/fastanime/Utility/data.py +++ b/fastanime/Utility/data.py @@ -12,7 +12,10 @@ anime_normalizer_raw = { "Re:Zero kara Hajimeru Isekai Seikatsu Season 3": "Re:Zero kara Hajimeru Isekai Seikatsu 3rd Season", }, "hianime": {"My Star": "Oshi no Ko"}, - "animepahe": {"Azumanga Daiou The Animation": "Azumanga Daioh"}, + "animepahe": { + "Azumanga Daiou The Animation": "Azumanga Daioh", + "Mairimashita! Iruma-kun 2": "Mairimashita! Iruma-kun 2nd Season" + }, "nyaa": {}, "yugen": {}, } From 33c06eab0ae9b769d8a2fbca23542cfe5a3cb537 Mon Sep 17 00:00:00 2001 From: Benex <81157281+Benex254@users.noreply.github.com> Date: Thu, 21 Nov 2024 18:07:13 +0300 Subject: [PATCH 02/16] Update README.md --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e643b29..dd78a05 100644 --- a/README.md +++ b/README.md @@ -28,14 +28,20 @@ My Rice - + + **Anilist results menu:** ![image](https://github.com/user-attachments/assets/240023a7-7e4e-47dd-80ff-017d65081ee1) +**Episodes menu preview:** ![image](https://github.com/user-attachments/assets/580f86ef-326f-4ab3-9bd8-c1cb312fbfa6) **Without preview images enabled:** ![image](https://github.com/user-attachments/assets/e1248a85-438f-4758-ae34-b0e0b224addd) +**Desktop notifications + episodes menu without image preview:** +![image](https://github.com/user-attachments/assets/111cf33e-0724-473a-a30d-65b0c20b5bd6) + +
From fe8cda094c9c1c6580087ecfd497d4287063a331 Mon Sep 17 00:00:00 2001 From: Benex <81157281+Benex254@users.noreply.github.com> Date: Thu, 21 Nov 2024 18:55:55 +0300 Subject: [PATCH 03/16] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dd78a05..fc35dfb 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ ![image](https://github.com/user-attachments/assets/e1248a85-438f-4758-ae34-b0e0b224addd) **Desktop notifications + episodes menu without image preview:** -![image](https://github.com/user-attachments/assets/111cf33e-0724-473a-a30d-65b0c20b5bd6) +![image](https://github.com/user-attachments/assets/b7802ef1-ca0d-45f5-a13a-e39c96a5d499)
From b10d9dc39aaff3beef0cc56dd518433ba48d3125 Mon Sep 17 00:00:00 2001 From: Pixelizer09 <153782533+Pixelizer09@users.noreply.github.com> Date: Fri, 22 Nov 2024 11:27:29 +0800 Subject: [PATCH 04/16] Update data.py --- fastanime/Utility/data.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fastanime/Utility/data.py b/fastanime/Utility/data.py index 6b0b2d4..ccef0ab 100644 --- a/fastanime/Utility/data.py +++ b/fastanime/Utility/data.py @@ -14,7 +14,8 @@ anime_normalizer_raw = { "hianime": {"My Star": "Oshi no Ko"}, "animepahe": { "Azumanga Daiou The Animation": "Azumanga Daioh", - "Mairimashita! Iruma-kun 2": "Mairimashita! Iruma-kun 2nd Season" + "Mairimashita! Iruma-kun 2nd Season": "Mairimashita! Iruma-kun 2", + "Mairimashita! Iruma-kun 3rd Season": "Mairimashita! Iruma-kun 3" }, "nyaa": {}, "yugen": {}, From 812d0110a78b45ee058b55e9b7a9b66e835679a8 Mon Sep 17 00:00:00 2001 From: Benex <81157281+Benex254@users.noreply.github.com> Date: Fri, 22 Nov 2024 08:41:04 +0300 Subject: [PATCH 05/16] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fc35dfb..f39c0b0 100644 --- a/README.md +++ b/README.md @@ -336,7 +336,7 @@ fastanime --manga search -t #### The anilist command :fire: :fire: :fire: -Stream, browse, and discover anime efficiently from the terminal using the [AniList API](https://github.com/AniList/ApiV2-GraphQL-Docs). +Uses the [AniList API](https://github.com/AniList/ApiV2-GraphQL-Docs) to create a terminal anilist client which is then intergrated with the scraping capabilities of the project. ##### Running without any subcommand From 7b6cc48b904176acb94dc0ea4e1a4912d4f25a3a Mon Sep 17 00:00:00 2001 From: Benex <81157281+Benexl@users.noreply.github.com> Date: Fri, 22 Nov 2024 09:56:26 +0300 Subject: [PATCH 06/16] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f39c0b0..5cac883 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ If you have any difficulty consult for help on the [discord channel](https://dis ![Static Badge](https://img.shields.io/badge/NixOs-black?style=flat&logo=nixos) ```bash -nix profile install github:Benex254/fastanime +nix profile install github:Benexl/fastanime ``` ### Installation using your favourite package manager @@ -195,7 +195,7 @@ Requirements: To build from the source, follow these steps: -1. Clone the repository: `git clone https://github.com/FastAnime/FastAnime.git --depth 1` +1. Clone the repository: `git clone https://github.com/Benexl/FastAnime.git --depth 1` 2. Navigate into the folder: `cd FastAnime` 3. Then build and Install the app: From 1c367c8aa1054b2f725b091af9252eb16604a044 Mon Sep 17 00:00:00 2001 From: benex Date: Fri, 22 Nov 2024 22:28:12 +0300 Subject: [PATCH 07/16] feat(anilist-interface): add resume flag to auto continue from the most recent anime --- fastanime/cli/commands/anilist/__init__.py | 37 +++++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/fastanime/cli/commands/anilist/__init__.py b/fastanime/cli/commands/anilist/__init__.py index 6ce7fc5..dbb2be9 100644 --- a/fastanime/cli/commands/anilist/__init__.py +++ b/fastanime/cli/commands/anilist/__init__.py @@ -80,15 +80,13 @@ commands = { fastanime --log-file anilist notifier """, ) +@click.option("--resume", is_flag=True, help="Resume from the last session") @click.pass_context -def anilist(ctx: click.Context): +def anilist(ctx: click.Context, resume: bool): from typing import TYPE_CHECKING from ....anilist import AniList from ....AnimeProvider import AnimeProvider - from ...interfaces.anilist_interfaces import ( - fastanime_main_menu as anilist_interface, - ) if TYPE_CHECKING: from ...config import Config @@ -98,4 +96,33 @@ def anilist(ctx: click.Context): AniList.update_login_info(user, user["token"]) if ctx.invoked_subcommand is None: fastanime_runtime_state = FastAnimeRuntimeState() - anilist_interface(ctx.obj, fastanime_runtime_state) + if resume: + from ...interfaces.anilist_interfaces import ( + anime_provider_search_results_menu, + ) + + if not config.user_data["recent_anime"]: + click.echo("No recent anime found", err=True, color=True) + return + fastanime_runtime_state.anilist_results_data = { + "data": {"Page": {"media": config.user_data["recent_anime"]}} + } + + fastanime_runtime_state.selected_anime_anilist = config.user_data[ + "recent_anime" + ][0] + fastanime_runtime_state.selected_anime_id_anilist = config.user_data[ + "recent_anime" + ][0]["id"] + fastanime_runtime_state.selected_anime_title_anilist = ( + config.user_data["recent_anime"][0]["title"]["romaji"] + or config.user_data["recent_anime"][0]["title"]["english"] + ) + anime_provider_search_results_menu(config, fastanime_runtime_state) + + else: + from ...interfaces.anilist_interfaces import ( + fastanime_main_menu as anilist_interface, + ) + + anilist_interface(ctx.obj, fastanime_runtime_state) From 27b4422ef3e2e7d95f63ef3c202d932382b000d1 Mon Sep 17 00:00:00 2001 From: benex Date: Fri, 22 Nov 2024 23:21:32 +0300 Subject: [PATCH 08/16] feat(requests_cacher): make more reliable by ordering the results by created_at --- fastanime/libs/common/requests_cacher.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/fastanime/libs/common/requests_cacher.py b/fastanime/libs/common/requests_cacher.py index b20e9fa..d7ec5ac 100644 --- a/fastanime/libs/common/requests_cacher.py +++ b/fastanime/libs/common/requests_cacher.py @@ -80,7 +80,8 @@ class CachedRequestsSession(requests.Session): response_headers TEXT, data BLOB, redirection_policy INT, - cache_expiry INTEGER + cache_expiry INTEGER, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )""" ) @@ -117,6 +118,8 @@ class CachedRequestsSession(requests.Session): url = ? AND redirection_policy = ? AND cache_expiry > ? + ORDER BY created_at DESC + LIMIT 1 """, (url, redirection_policy, int(time.time())), ) @@ -162,8 +165,15 @@ class CachedRequestsSession(requests.Session): logger.debug("Caching the current request") cursor.execute( f""" - INSERT INTO {self.table_name} - VALUES (?, ?, ?, ?, ?, ?, ?) + INSERT INTO {self.table_name} ( + url, + status_code, + request_headers, + response_headers, + data, + redirection_policy, + cache_expiry + ) VALUES (?, ?, ?, ?, ?, ?, ?) """, ( url, From 7cd97c78b18400e8808b86fc87bf0bcc5b8f96e6 Mon Sep 17 00:00:00 2001 From: benex Date: Fri, 22 Nov 2024 23:21:58 +0300 Subject: [PATCH 09/16] feat(config): make the max_cache_lifetime configurable --- fastanime/cli/config.py | 14 ++++++++++++++ fastanime/libs/anime_provider/base_provider.py | 5 ++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/fastanime/cli/config.py b/fastanime/cli/config.py index ef56893..32bdd68 100644 --- a/fastanime/cli/config.py +++ b/fastanime/cli/config.py @@ -51,6 +51,7 @@ class Config(object): "image_previews": "True" if S_PLATFORM != "win32" else "False", "normalize_titles": "True", "notification_duration": "2", + "max_cache_lifetime": "03:00:00", "player": "mpv", "preferred_history": "local", "preferred_language": "english", @@ -128,6 +129,14 @@ class Config(object): self.notification_duration = self.configparser.getint( "general", "notification_duration" ) + max_cache_lifetime = list( + map(int, self.configparser.get("general", "max_cache_lifetime").split(":")) + ) + self.max_cache_lifetime = ( + max_cache_lifetime[0] * 86400 + + max_cache_lifetime[1] * 3600 + + max_cache_lifetime[2] * 60 + ) self.player = self.configparser.get("stream", "player") self.preferred_history = self.configparser.get("stream", "preferred_history") self.preferred_language = self.configparser.get("general", "preferred_language") @@ -396,6 +405,11 @@ force_forward_tracking = {self.force_forward_tracking} # from the cached_requests_db cache_requests = {self.cache_requests} +# the max lifetime for a cached request +# defaults to 3days = 03:00:00 +# this is the time after which a cached request will be deleted (technically : ) +max_cache_lifetime = {self.max_cache_lifetime} + # whether to use a persistent store (basically a sqlitedb) for storing some data the provider requires # to enable a seamless experience [true/false] # this option exists primarily because i think it may help in the optimization diff --git a/fastanime/libs/anime_provider/base_provider.py b/fastanime/libs/anime_provider/base_provider.py index 8de9941..ad8c956 100644 --- a/fastanime/libs/anime_provider/base_provider.py +++ b/fastanime/libs/anime_provider/base_provider.py @@ -19,7 +19,10 @@ class AnimeProvider: from ..common.requests_cacher import CachedRequestsSession self.session = CachedRequestsSession( - os.path.join(APP_CACHE_DIR, "cached_requests.db") + os.path.join(APP_CACHE_DIR, "cached_requests.db"), + max_lifetime=int( + os.environ.get("FASTANIME_MAX_CACHE_LIFETIME", 259200) + ), ) else: self.session = requests.session() From 5129219e23e4041d7a127a89c24f34b30760ba23 Mon Sep 17 00:00:00 2001 From: Type-Delta <121682210+Type-Delta@users.noreply.github.com> Date: Sat, 23 Nov 2024 17:10:31 +0700 Subject: [PATCH 10/16] fix: added --force-ffmpeg & --hls-use-mpegts options to properly handle some m3u8 streams --- fastanime/Utility/downloader/downloader.py | 20 +++++++++++++++++++- fastanime/cli/commands/anilist/download.py | 16 ++++++++++++++++ fastanime/cli/commands/download.py | 20 ++++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/fastanime/Utility/downloader/downloader.py b/fastanime/Utility/downloader/downloader.py index 28c04d9..20f5bb5 100644 --- a/fastanime/Utility/downloader/downloader.py +++ b/fastanime/Utility/downloader/downloader.py @@ -43,6 +43,8 @@ class YtDLPDownloader: merge=False, clean=False, prompt=True, + force_ffmpeg=False, + hls_use_mpegts=False, ): """Helper function that downloads anime given url and path details @@ -91,7 +93,23 @@ class YtDLPDownloader: vid_path = "" sub_path = "" for i, url in enumerate(urls): - with yt_dlp.YoutubeDL(ydl_opts) as ydl: + options = ydl_opts + if i == 0: + if force_ffmpeg: + options = options | { + "external_downloader": { + 'default': 'ffmpeg' + }, + "external_downloader_args": { + "ffmpeg_i1": ["-v", "error", "-stats"], + }, + } + if hls_use_mpegts: + options = options | { + "hls_use_mpegts": hls_use_mpegts, + } + + with yt_dlp.YoutubeDL(options) as ydl: info = ydl.extract_info(url, download=True) if not info: continue diff --git a/fastanime/cli/commands/anilist/download.py b/fastanime/cli/commands/anilist/download.py index afcaa59..3ef29ee 100644 --- a/fastanime/cli/commands/anilist/download.py +++ b/fastanime/cli/commands/anilist/download.py @@ -109,6 +109,16 @@ from .data import ( help="Whether to prompt for anything instead just do the best thing", default=True, ) +@click.option( + "--force-ffmpeg", + is_flag=True, + help="Force the use of FFmpeg for downloading (supports large variety of streams but slower)", +) +@click.option( + "--hls-use-mpegts", + is_flag=True, + help="Use mpegts for hls streams (useful for some streams: see Docs) (this option forces --force-ffmpeg to be True)", +) @click.option( "--max-results", "-M", type=int, help="The maximum number of results to show" ) @@ -132,11 +142,15 @@ def download( clean, wait_time, prompt, + force_ffmpeg, + hls_use_mpegts, max_results, ): from ....anilist import AniList from rich import print + force_ffmpeg |= hls_use_mpegts + success, anilist_search_results = AniList.search( query=title, sort=sort, @@ -367,6 +381,8 @@ def download( merge=merge, clean=clean, prompt=prompt, + force_ffmpeg=force_ffmpeg, + hls_use_mpegts=hls_use_mpegts, ) except Exception as e: print(e) diff --git a/fastanime/cli/commands/download.py b/fastanime/cli/commands/download.py index 9af4ca0..c14a620 100644 --- a/fastanime/cli/commands/download.py +++ b/fastanime/cli/commands/download.py @@ -114,6 +114,16 @@ if TYPE_CHECKING: help="Whether to prompt for anything instead just do the best thing", default=True, ) +@click.option( + "--force-ffmpeg", + is_flag=True, + help="Force the use of FFmpeg for downloading (supports large variety of streams but slower)", +) +@click.option( + "--hls-use-mpegts", + is_flag=True, + help="Use mpegts for hls streams (useful for some streams: see Docs) (this option forces --force-ffmpeg to be True)", +) @click.pass_obj def download( config: "Config", @@ -127,6 +137,8 @@ def download( clean, wait_time, prompt, + force_ffmpeg, + hls_use_mpegts, ): import time @@ -146,6 +158,8 @@ def download( move_preferred_subtitle_lang_to_top, ) + force_ffmpeg |= hls_use_mpegts + anime_provider = AnimeProvider(config.provider) anilist_anime_info = None @@ -185,6 +199,8 @@ def download( clean, wait_time, prompt, + force_ffmpeg, + hls_use_mpegts, ) return search_results = search_results["results"] @@ -236,6 +252,8 @@ def download( clean, wait_time, prompt, + force_ffmpeg, + hls_use_mpegts, ) return @@ -369,6 +387,8 @@ def download( merge=merge, clean=clean, prompt=prompt, + force_ffmpeg=force_ffmpeg, + hls_use_mpegts=hls_use_mpegts, ) except Exception as e: print(e) From 25dc35eaafd21ff99bd348e0feba1339eeb12b17 Mon Sep 17 00:00:00 2001 From: benex Date: Sat, 23 Nov 2024 14:08:22 +0300 Subject: [PATCH 11/16] feat: print update message to stderr + disable auto check for updates (needs better implementation) --- fastanime/cli/__init__.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/fastanime/cli/__init__.py b/fastanime/cli/__init__.py index be4c294..f51829a 100644 --- a/fastanime/cli/__init__.py +++ b/fastanime/cli/__init__.py @@ -226,13 +226,18 @@ def run_cli( from .config import Config ctx.obj = Config() - if ctx.obj.check_for_updates and ctx.invoked_subcommand != "completions": + # TODO: only check once per week or day + # so as to not hit api limit + # for now i have disabled it + if ctx.obj.check_for_updates and ctx.invoked_subcommand != "completions" and False: from .app_updater import check_for_updates + import sys - print("Checking for updates...") - print("So you can enjoy the latest features and bug fixes") + print("Checking for updates...", file=sys.stderr) + print("So you can enjoy the latest features and bug fixes", file=sys.stderr) print( - "You can disable this by setting check_for_updates to False in the config" + "You can disable this by setting check_for_updates to False in the config", + file=sys.stderr, ) is_latest, github_release_data = check_for_updates() if not is_latest: From a33e47d205f040c268d435ba2dd7b075aa752fa3 Mon Sep 17 00:00:00 2001 From: benex Date: Sat, 23 Nov 2024 14:08:48 +0300 Subject: [PATCH 12/16] fix(config): use separate var for the config file val --- fastanime/cli/config.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fastanime/cli/config.py b/fastanime/cli/config.py index 32bdd68..b9e19d5 100644 --- a/fastanime/cli/config.py +++ b/fastanime/cli/config.py @@ -129,9 +129,10 @@ class Config(object): self.notification_duration = self.configparser.getint( "general", "notification_duration" ) - max_cache_lifetime = list( - map(int, self.configparser.get("general", "max_cache_lifetime").split(":")) + self._max_cache_lifetime = self.configparser.get( + "general", "max_cache_lifetime" ) + max_cache_lifetime = list(map(int, self._max_cache_lifetime.split(":"))) self.max_cache_lifetime = ( max_cache_lifetime[0] * 86400 + max_cache_lifetime[1] * 3600 @@ -408,7 +409,7 @@ cache_requests = {self.cache_requests} # the max lifetime for a cached request # defaults to 3days = 03:00:00 # this is the time after which a cached request will be deleted (technically : ) -max_cache_lifetime = {self.max_cache_lifetime} +max_cache_lifetime = {self._max_cache_lifetime} # whether to use a persistent store (basically a sqlitedb) for storing some data the provider requires # to enable a seamless experience [true/false] From eb99b7e6ba68d3c983d3b048641ed6e290e0d6dd Mon Sep 17 00:00:00 2001 From: benex Date: Sat, 23 Nov 2024 15:54:53 +0300 Subject: [PATCH 13/16] feat(anilist_api): also make the page configurable --- fastanime/libs/anilist/api.py | 38 +++++++++++------------ fastanime/libs/anilist/queries_graphql.py | 34 ++++++++++---------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/fastanime/libs/anilist/api.py b/fastanime/libs/anilist/api.py index 541f436..6aba74f 100644 --- a/fastanime/libs/anilist/api.py +++ b/fastanime/libs/anilist/api.py @@ -139,9 +139,7 @@ class AniListApi: return self._make_authenticated_request(media_list_mutation, variables) def get_anime_list( - self, - status: "AnilistMediaListStatus", - type="ANIME", + self, status: "AnilistMediaListStatus", type="ANIME", page=1, **kwargs ) -> tuple[bool, "AnilistMediaLists"] | tuple[bool, None]: """gets an anime list from your media list given the list status @@ -151,7 +149,12 @@ class AniListApi: Returns: a media list """ - variables = {"status": status, "userId": self.user_id, "type": type} + variables = { + "status": status, + "userId": self.user_id, + "type": type, + "page": page, + } return self._make_authenticated_request(media_list_query, variables) def get_medialist_entry( @@ -351,46 +354,43 @@ class AniListApi: variables = {"id": id} return self.get_data(anime_query, variables) - def get_trending(self, type="ANIME", *_, **kwargs): + def get_trending(self, type="ANIME", page=1, *_, **kwargs): """ Gets the currently trending anime """ - variables = {"type": type} + variables = {"type": type, "page": page} trending = self.get_data(trending_query, variables) return trending - def get_most_favourite(self, type="ANIME", *_, **kwargs): + def get_most_favourite(self, type="ANIME", page=1, *_, **kwargs): """ Gets the most favoured anime on anilist """ - variables = {"type": type} + variables = {"type": type, "page": page} most_favourite = self.get_data(most_favourite_query, variables) return most_favourite - def get_most_scored(self, type="ANIME", *_, **kwargs): + def get_most_scored(self, type="ANIME", page=1, *_, **kwargs): """ Gets most scored anime on anilist """ - variables = {"type": type} + variables = {"type": type, "page": page} most_scored = self.get_data(most_scored_query, variables) return most_scored - def get_most_recently_updated(self, type="ANIME", *_, **kwargs): + def get_most_recently_updated(self, type="ANIME", page=1, *_, **kwargs): """ Gets most recently updated anime from anilist """ - variables = {"type": type} + variables = {"type": type, "page": page} most_recently_updated = self.get_data(most_recently_updated_query, variables) return most_recently_updated - def get_most_popular( - self, - type="ANIME", - ): + def get_most_popular(self, type="ANIME", page=1, **kwargs): """ Gets most popular anime on anilist """ - variables = {"type": type} + variables = {"type": type, "page": page} most_popular = self.get_data(most_popular_query, variables) return most_popular @@ -403,8 +403,8 @@ class AniListApi: return upcoming_anime # NOTE: THe following methods will probably be scraped soon - def get_recommended_anime_for(self, id: int, type="ANIME", *_, **kwargs): - variables = {"type": type} + def get_recommended_anime_for(self, id: int, type="ANIME", page=1, *_, **kwargs): + variables = {"type": type, "page": page} recommended_anime = self.get_data(recommended_query, variables) return recommended_anime diff --git a/fastanime/libs/anilist/queries_graphql.py b/fastanime/libs/anilist/queries_graphql.py index 6131055..585e3da 100644 --- a/fastanime/libs/anilist/queries_graphql.py +++ b/fastanime/libs/anilist/queries_graphql.py @@ -193,8 +193,8 @@ mutation ( """ media_list_query = """ -query ($userId: Int, $status: MediaListStatus, $type: MediaType) { - Page { +query ($userId: Int, $status: MediaListStatus, $type: MediaType, $page: Int) { + Page(perPage: 15, page: $page) { pageInfo { currentPage total @@ -406,8 +406,8 @@ query($query:String,%s){ ) trending_query = """ -query ($type: MediaType) { - Page(perPage: 15) { +query ($type: MediaType, $page: Int) { + Page(perPage: 15, page: $page) { media(sort: TRENDING_DESC, type: $type, genre_not_in: ["hentai"]) { id idMal @@ -471,8 +471,8 @@ query ($type: MediaType) { # mosts most_favourite_query = """ -query ($type: MediaType) { - Page(perPage: 15) { +query ($type: MediaType, $page: Int) { + Page(perPage: 15, page: $page) { media(sort: FAVOURITES_DESC, type: $type, genre_not_in: ["hentai"]) { id idMal @@ -539,8 +539,8 @@ query ($type: MediaType) { """ most_scored_query = """ -query ($type: MediaType) { - Page(perPage: 15) { +query ($type: MediaType, $page: Int) { + Page(perPage: 15, page: $page) { media(sort: SCORE_DESC, type: $type, genre_not_in: ["hentai"]) { id idMal @@ -603,8 +603,8 @@ query ($type: MediaType) { """ most_popular_query = """ -query ($type: MediaType) { - Page(perPage: 15) { +query ($type: MediaType, $page: Int) { + Page(perPage: 15, page: $page) { media(sort: POPULARITY_DESC, type: $type, genre_not_in: ["hentai"]) { id idMal @@ -667,8 +667,8 @@ query ($type: MediaType) { """ most_recently_updated_query = """ -query ($type: MediaType) { - Page(perPage: 15) { +query ($type: MediaType, $page: Int) { + Page(perPage: 15, page: $page) { media( sort: UPDATED_AT_DESC type: $type @@ -738,8 +738,8 @@ query ($type: MediaType) { """ recommended_query = """ -query ($type: MediaType) { - Page(perPage: 15) { +query ($type: MediaType, $page: Int) { + Page(perPage: 15, page: $page) { media(type: $type, genre_not_in: ["hentai"]) { recommendations(sort: RATING_DESC) { nodes { @@ -838,8 +838,8 @@ query ($id: Int, $type: MediaType) { anime_relations_query = """ -query ($id: Int, $type: MediaType) { - Page(perPage: 20) { +query ($type: MediaType, $page: Int) { + Page(perPage: 15, page: $page) { media( id: $id sort: POPULARITY_DESC @@ -924,7 +924,7 @@ query ($id: Int,$type:MediaType) { upcoming_anime_query = """ query ($page: Int, $type: MediaType) { - Page(page: $page) { + Page(perPage: 15, page: $page) { pageInfo { total perPage From 8d85e301501aabe84d557f6b28fc2c01a9826d77 Mon Sep 17 00:00:00 2001 From: benex Date: Sat, 23 Nov 2024 15:55:49 +0300 Subject: [PATCH 14/16] feat(runtime): add current-page and current-data-loader to runtime --- fastanime/cli/utils/tools.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fastanime/cli/utils/tools.py b/fastanime/cli/utils/tools.py index d99034f..7433a65 100644 --- a/fastanime/cli/utils/tools.py +++ b/fastanime/cli/utils/tools.py @@ -5,6 +5,7 @@ if TYPE_CHECKING: from ...libs.anilist.types import AnilistBaseMediaDataSchema from ...libs.anime_provider.types import Anime, EpisodeStream, SearchResult, Server + from typing import Callable class FastAnimeRuntimeState(object): @@ -26,9 +27,11 @@ class FastAnimeRuntimeState(object): selected_anime_title_anilist: str # current_anilist_data: "AnilistDataSchema | AnilistMediaList" anilist_results_data: "Any" + current_page: int + current_data_loader: "Callable" -def exit_app(exit_code=0, *args): +def exit_app(exit_code=0, *args, **kwargs): import sys from rich.console import Console From 4867720ad25353f32e9089479a8b5f0544eb2773 Mon Sep 17 00:00:00 2001 From: benex Date: Sat, 23 Nov 2024 15:56:21 +0300 Subject: [PATCH 15/16] feat: implement experimental next and previous page. --- .../cli/interfaces/anilist_interfaces.py | 183 ++++++++++++------ 1 file changed, 121 insertions(+), 62 deletions(-) diff --git a/fastanime/cli/interfaces/anilist_interfaces.py b/fastanime/cli/interfaces/anilist_interfaces.py index 8c4b340..6c0881a 100644 --- a/fastanime/cli/interfaces/anilist_interfaces.py +++ b/fastanime/cli/interfaces/anilist_interfaces.py @@ -1421,7 +1421,7 @@ def anilist_results_menu( anime_data[title] = anime # prompt for the anime of choice - choices = [*anime_data.keys(), "Back"] + choices = [*anime_data.keys(), "Next Page", "Previous Page", "Back"] if config.use_fzf: if config.preview: from .utils import get_fzf_anime_preview @@ -1460,6 +1460,43 @@ def anilist_results_menu( if selected_anime_title == "Back": fastanime_main_menu(config, fastanime_runtime_state) return + if selected_anime_title == "Next Page": + fastanime_runtime_state.current_page = page = ( + fastanime_runtime_state.current_page + 1 + ) + success, data = fastanime_runtime_state.current_data_loader( + config=config, page=page + ) + if success: + fastanime_runtime_state.anilist_results_data = data + + anilist_results_menu(config, fastanime_runtime_state) + else: + print("Failed to get next page") + print(data) + input("Enter to continue...") + anilist_results_menu(config, fastanime_runtime_state) + + return + if selected_anime_title == "Previous Page": + fastanime_runtime_state.current_page = page = ( + (fastanime_runtime_state.current_page - 1) + if fastanime_runtime_state.current_page > 1 + else 1 + ) + success, data = fastanime_runtime_state.current_data_loader( + config=config, page=page + ) + if success: + fastanime_runtime_state.anilist_results_data = data + + anilist_results_menu(config, fastanime_runtime_state) + else: + print("Failed to get previous page") + print(data) + input("Enter to continue...") + anilist_results_menu(config, fastanime_runtime_state) + return selected_anime: "AnilistBaseMediaDataSchema" = anime_data[selected_anime_title] fastanime_runtime_state.selected_anime_anilist = selected_anime @@ -1475,7 +1512,10 @@ def anilist_results_menu( # ---- FASTANIME MAIN MENU ---- # def handle_animelist( - config: "Config", fastanime_runtime_state: "FastAnimeRuntimeState", list_type: str + config: "Config", + fastanime_runtime_state: "FastAnimeRuntimeState", + list_type: str, + page=1, ): """A helper function that handles user media lists @@ -1514,7 +1554,7 @@ def handle_animelist( return # get the media list - anime_list = AniList.get_anime_list(status) + anime_list = AniList.get_anime_list(status, page=page) # handle null if not anime_list: print("Sth went wrong", anime_list) @@ -1545,6 +1585,56 @@ def handle_animelist( return anime_list +def _anilist_search(config: "Config", page=1): + """A function that enables seaching of an anime + + Returns: + [TODO:return] + """ + # TODO: Add filters and other search features + 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, page=page) + + +def _anilist_random(config: "Config", page=1): + """A function that generates random anilist ids enabling random discovery of anime + + Returns: + [TODO:return] + """ + random_anime = range(1, 15000) + random_anime = random.sample(random_anime, k=50) + + return AniList.search(id_in=list(random_anime)) + + +def _watch_history(config: "Config", page=1): + """Function that lets you see all the anime that has locally been saved to your watch history + + Returns: + [TODO:return] + """ + watch_history = list(map(int, config.watch_history.keys())) + return AniList.search(id_in=watch_history, sort="TRENDING_DESC", page=page) + + +def _recent(config: "Config", page=1): + return ( + True, + {"data": {"Page": {"media": config.user_data["recent_anime"]}}}, + ) + + +# WARNING: Will probably be depracated +def _anime_list(config: "Config", page=1): + anime_list = config.anime_list + return AniList.search(id_in=anime_list, pages=page) + + def fastanime_main_menu( config: "Config", fastanime_runtime_state: "FastAnimeRuntimeState" ): @@ -1555,52 +1645,7 @@ def fastanime_main_menu( fastanime_runtime_state: A query dict used to store data during navigation of the ui # initially this was very messy """ - def _anilist_search(): - """A function that enables seaching of an anime - - Returns: - [TODO:return] - """ - # TODO: Add filters and other search features - 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) - - def _anilist_random(): - """A function that generates random anilist ids enabling random discovery of anime - - Returns: - [TODO:return] - """ - random_anime = range(1, 15000) - random_anime = random.sample(random_anime, k=50) - - return AniList.search(id_in=list(random_anime)) - - def _watch_history(): - """Function that lets you see all the anime that has locally been saved to your watch history - - Returns: - [TODO:return] - """ - watch_history = list(map(int, config.watch_history.keys())) - return AniList.search(id_in=watch_history, sort="TRENDING_DESC") - - def _recent(): - return ( - True, - {"data": {"Page": {"media": config.user_data["recent_anime"]}}}, - ) - - # WARNING: Will probably be depracated - def _anime_list(): - anime_list = config.anime_list - return AniList.search(id_in=anime_list) - - def _edit_config(): + def _edit_config(*args, **kwargs): """Helper function to edit your config when the ui is still running""" from click import edit @@ -1625,23 +1670,35 @@ def fastanime_main_menu( options = { f"{'🔥 ' if icons else ''}Trending": AniList.get_trending, f"{'🎞️ ' if icons else ''}Recent": _recent, - f"{'📺 ' if icons else ''}Watching": lambda media_list_type="Watching": handle_animelist( - config, fastanime_runtime_state, media_list_type + f"{'📺 ' if icons else ''}Watching": lambda config, + media_list_type="Watching", + page=1: handle_animelist( + config, fastanime_runtime_state, media_list_type, page=page ), - f"{'⏸ ' if icons else ''}Paused": lambda media_list_type="Paused": handle_animelist( - config, fastanime_runtime_state, media_list_type + f"{'⏸ ' if icons else ''}Paused": lambda config, + media_list_type="Paused", + page=1: handle_animelist( + config, fastanime_runtime_state, media_list_type, page=page ), - f"{'🚮 ' if icons else ''}Dropped": lambda media_list_type="Dropped": handle_animelist( - config, fastanime_runtime_state, media_list_type + f"{'🚮 ' if icons else ''}Dropped": lambda config, + media_list_type="Dropped", + page=1: handle_animelist( + config, fastanime_runtime_state, media_list_type, page=page ), - f"{'📑 ' if icons else ''}Planned": lambda media_list_type="Planned": handle_animelist( - config, fastanime_runtime_state, media_list_type + f"{'📑 ' if icons else ''}Planned": lambda config, + media_list_type="Planned", + page=1: handle_animelist( + config, fastanime_runtime_state, media_list_type, page=page ), - f"{'✅ ' if icons else ''}Completed": lambda media_list_type="Completed": handle_animelist( - config, fastanime_runtime_state, media_list_type + f"{'✅ ' if icons else ''}Completed": lambda config, + media_list_type="Completed", + page=1: handle_animelist( + config, fastanime_runtime_state, media_list_type, page=page ), - f"{'🔁 ' if icons else ''}Rewatching": lambda media_list_type="Repeating": handle_animelist( - config, fastanime_runtime_state, media_list_type + f"{'🔁 ' if icons else ''}Rewatching": lambda config, + media_list_type="Repeating", + page=1: handle_animelist( + config, fastanime_runtime_state, media_list_type, page=page ), f"{'🔔 ' if icons else ''}Recently Updated Anime": AniList.get_most_recently_updated, f"{'🔎 ' if icons else ''}Search": _anilist_search, @@ -1670,7 +1727,9 @@ def fastanime_main_menu( choices, "Select Action", ) - anilist_data = options[action]() + fastanime_runtime_state.current_data_loader = options[action] + fastanime_runtime_state.current_page = 1 + anilist_data = options[action](config=config) # anilist data is a (bool,data) # the bool indicated success if anilist_data[0]: From cef0bae52845dcc42cf25d04cd775cb3b2395e3f Mon Sep 17 00:00:00 2001 From: benex Date: Sat, 23 Nov 2024 17:44:02 +0300 Subject: [PATCH 16/16] feat(anilist-interface): finish next and previous page implementation --- fastanime/cli/commands/anilist/completed.py | 7 +++++++ fastanime/cli/commands/anilist/dropped.py | 7 +++++++ fastanime/cli/commands/anilist/favourites.py | 3 +++ fastanime/cli/commands/anilist/paused.py | 12 +++++++++--- fastanime/cli/commands/anilist/planning.py | 7 +++++++ fastanime/cli/commands/anilist/popular.py | 3 +++ fastanime/cli/commands/anilist/recent.py | 5 +++++ fastanime/cli/commands/anilist/rewatching.py | 7 +++++++ fastanime/cli/commands/anilist/scores.py | 3 +++ fastanime/cli/commands/anilist/search.py | 16 ++++++++++++++++ fastanime/cli/commands/anilist/trending.py | 3 +++ fastanime/cli/commands/anilist/upcoming.py | 3 +++ fastanime/cli/commands/anilist/watching.py | 7 +++++++ fastanime/cli/interfaces/anilist_interfaces.py | 18 +++++++++--------- 14 files changed, 89 insertions(+), 12 deletions(-) diff --git a/fastanime/cli/commands/anilist/completed.py b/fastanime/cli/commands/anilist/completed.py index 67b2c13..6996524 100644 --- a/fastanime/cli/commands/anilist/completed.py +++ b/fastanime/cli/commands/anilist/completed.py @@ -42,5 +42,12 @@ def completed(config: "Config", dump_json): from ...interfaces import anilist_interfaces fastanime_runtime_state = FastAnimeRuntimeState() + + fastanime_runtime_state.current_page = 1 + fastanime_runtime_state.current_data_loader = ( + lambda config, **kwargs: anilist_interfaces._handle_animelist( + config, fastanime_runtime_state, "Completed", **kwargs + ) + ) fastanime_runtime_state.anilist_results_data = anime_list[1] anilist_interfaces.anilist_results_menu(config, fastanime_runtime_state) diff --git a/fastanime/cli/commands/anilist/dropped.py b/fastanime/cli/commands/anilist/dropped.py index 812f324..438c31f 100644 --- a/fastanime/cli/commands/anilist/dropped.py +++ b/fastanime/cli/commands/anilist/dropped.py @@ -42,5 +42,12 @@ def dropped(config: "Config", dump_json): from ...utils.tools import FastAnimeRuntimeState fastanime_runtime_state = FastAnimeRuntimeState() + + fastanime_runtime_state.current_page = 1 + fastanime_runtime_state.current_data_loader = ( + lambda config, **kwargs: anilist_interfaces._handle_animelist( + config, fastanime_runtime_state, "Dropped", **kwargs + ) + ) fastanime_runtime_state.anilist_results_data = anime_list[1] anilist_interfaces.anilist_results_menu(config, fastanime_runtime_state) diff --git a/fastanime/cli/commands/anilist/favourites.py b/fastanime/cli/commands/anilist/favourites.py index 61ac291..bb890b4 100644 --- a/fastanime/cli/commands/anilist/favourites.py +++ b/fastanime/cli/commands/anilist/favourites.py @@ -26,6 +26,9 @@ def favourites(config, dump_json): from ...utils.tools import FastAnimeRuntimeState fastanime_runtime_state = FastAnimeRuntimeState() + + fastanime_runtime_state.current_page = 1 + fastanime_runtime_state.current_data_loader = AniList.get_most_favourite fastanime_runtime_state.anilist_results_data = anime_data[1] anilist_results_menu(config, fastanime_runtime_state) else: diff --git a/fastanime/cli/commands/anilist/paused.py b/fastanime/cli/commands/anilist/paused.py index a5fdcad..2e565fd 100644 --- a/fastanime/cli/commands/anilist/paused.py +++ b/fastanime/cli/commands/anilist/paused.py @@ -41,6 +41,12 @@ def paused(config: "Config", dump_json): from ...interfaces import anilist_interfaces from ...utils.tools import FastAnimeRuntimeState - anilist_config = FastAnimeRuntimeState() - anilist_config.anilist_results_data = anime_list[1] - anilist_interfaces.anilist_results_menu(config, anilist_config) + fastanime_runtime_state = FastAnimeRuntimeState() + fastanime_runtime_state.current_page = 1 + fastanime_runtime_state.current_data_loader = ( + lambda config, **kwargs: anilist_interfaces._handle_animelist( + config, fastanime_runtime_state, "Paused", **kwargs + ) + ) + fastanime_runtime_state.anilist_results_data = anime_list[1] + anilist_interfaces.anilist_results_menu(config, fastanime_runtime_state) diff --git a/fastanime/cli/commands/anilist/planning.py b/fastanime/cli/commands/anilist/planning.py index 85a6f2c..5673b99 100644 --- a/fastanime/cli/commands/anilist/planning.py +++ b/fastanime/cli/commands/anilist/planning.py @@ -42,5 +42,12 @@ def planning(config: "Config", dump_json): from ...utils.tools import FastAnimeRuntimeState fastanime_runtime_state = FastAnimeRuntimeState() + + fastanime_runtime_state.current_page = 1 + fastanime_runtime_state.current_data_loader = ( + lambda config, **kwargs: anilist_interfaces._handle_animelist( + config, fastanime_runtime_state, "Planned", **kwargs + ) + ) fastanime_runtime_state.anilist_results_data = anime_list[1] anilist_interfaces.anilist_results_menu(config, fastanime_runtime_state) diff --git a/fastanime/cli/commands/anilist/popular.py b/fastanime/cli/commands/anilist/popular.py index 6ea4993..36cbf95 100644 --- a/fastanime/cli/commands/anilist/popular.py +++ b/fastanime/cli/commands/anilist/popular.py @@ -25,6 +25,9 @@ def popular(config, dump_json): from ...utils.tools import FastAnimeRuntimeState fastanime_runtime_state = FastAnimeRuntimeState() + + fastanime_runtime_state.current_page = 1 + fastanime_runtime_state.current_data_loader = AniList.get_most_popular fastanime_runtime_state.anilist_results_data = anime_data[1] anilist_results_menu(config, fastanime_runtime_state) else: diff --git a/fastanime/cli/commands/anilist/recent.py b/fastanime/cli/commands/anilist/recent.py index efbb3bd..97059d2 100644 --- a/fastanime/cli/commands/anilist/recent.py +++ b/fastanime/cli/commands/anilist/recent.py @@ -26,6 +26,11 @@ def recent(config, dump_json): from ...utils.tools import FastAnimeRuntimeState fastanime_runtime_state = FastAnimeRuntimeState() + + fastanime_runtime_state.current_page = 1 + fastanime_runtime_state.current_data_loader = ( + AniList.get_most_recently_updated + ) fastanime_runtime_state.anilist_results_data = anime_data[1] anilist_results_menu(config, fastanime_runtime_state) else: diff --git a/fastanime/cli/commands/anilist/rewatching.py b/fastanime/cli/commands/anilist/rewatching.py index 9330b10..5b3a0c9 100644 --- a/fastanime/cli/commands/anilist/rewatching.py +++ b/fastanime/cli/commands/anilist/rewatching.py @@ -42,5 +42,12 @@ def rewatching(config: "Config", dump_json): from ...utils.tools import FastAnimeRuntimeState fastanime_runtime_state = FastAnimeRuntimeState() + + fastanime_runtime_state.current_page = 1 + fastanime_runtime_state.current_data_loader = ( + lambda config, **kwargs: anilist_interfaces._handle_animelist( + config, fastanime_runtime_state, "Rewatching", **kwargs + ) + ) fastanime_runtime_state.anilist_results_data = anime_list[1] anilist_interfaces.anilist_results_menu(config, fastanime_runtime_state) diff --git a/fastanime/cli/commands/anilist/scores.py b/fastanime/cli/commands/anilist/scores.py index 664413c..44bde7e 100644 --- a/fastanime/cli/commands/anilist/scores.py +++ b/fastanime/cli/commands/anilist/scores.py @@ -25,6 +25,9 @@ def scores(config, dump_json): from ...utils.tools import FastAnimeRuntimeState fastanime_runtime_state = FastAnimeRuntimeState() + + fastanime_runtime_state.current_page = 1 + fastanime_runtime_state.current_data_loader = AniList.get_most_scored fastanime_runtime_state.anilist_results_data = anime_data[1] anilist_results_menu(config, fastanime_runtime_state) else: diff --git a/fastanime/cli/commands/anilist/search.py b/fastanime/cli/commands/anilist/search.py index 207876d..4c6a3f8 100644 --- a/fastanime/cli/commands/anilist/search.py +++ b/fastanime/cli/commands/anilist/search.py @@ -111,6 +111,22 @@ def search( from ...utils.tools import FastAnimeRuntimeState fastanime_runtime_state = FastAnimeRuntimeState() + + fastanime_runtime_state.current_page = 1 + fastanime_runtime_state.current_data_loader = ( + lambda page=1, **kwargs: AniList.search( + query=title, + sort=sort, + status_in=list(status), + genre_in=list(genres), + season=season, + tag_in=list(tags), + seasonYear=year, + format_in=list(media_format), + on_list=on_list, + page=page, + ) + ) fastanime_runtime_state.anilist_results_data = search_results anilist_results_menu(config, fastanime_runtime_state) else: diff --git a/fastanime/cli/commands/anilist/trending.py b/fastanime/cli/commands/anilist/trending.py index 7aa2831..f40f16a 100644 --- a/fastanime/cli/commands/anilist/trending.py +++ b/fastanime/cli/commands/anilist/trending.py @@ -26,6 +26,9 @@ def trending(config, dump_json): from ...utils.tools import FastAnimeRuntimeState fastanime_runtime_state = FastAnimeRuntimeState() + + fastanime_runtime_state.current_page = 1 + fastanime_runtime_state.current_data_loader = AniList.get_trending fastanime_runtime_state.anilist_results_data = data anilist_results_menu(config, fastanime_runtime_state) else: diff --git a/fastanime/cli/commands/anilist/upcoming.py b/fastanime/cli/commands/anilist/upcoming.py index 263b064..8472e48 100644 --- a/fastanime/cli/commands/anilist/upcoming.py +++ b/fastanime/cli/commands/anilist/upcoming.py @@ -25,6 +25,9 @@ def upcoming(config, dump_json): from ...utils.tools import FastAnimeRuntimeState fastanime_runtime_state = FastAnimeRuntimeState() + + fastanime_runtime_state.current_page = 1 + fastanime_runtime_state.current_data_loader = AniList.get_upcoming_anime fastanime_runtime_state.anilist_results_data = data anilist_results_menu(config, fastanime_runtime_state) else: diff --git a/fastanime/cli/commands/anilist/watching.py b/fastanime/cli/commands/anilist/watching.py index 677f9cc..6b2f43e 100644 --- a/fastanime/cli/commands/anilist/watching.py +++ b/fastanime/cli/commands/anilist/watching.py @@ -42,5 +42,12 @@ def watching(config: "Config", dump_json): from ...utils.tools import FastAnimeRuntimeState fastanime_runtime_state = FastAnimeRuntimeState() + + fastanime_runtime_state.current_page = 1 + fastanime_runtime_state.current_data_loader = ( + lambda config, **kwargs: anilist_interfaces._handle_animelist( + config, fastanime_runtime_state, "Watching", **kwargs + ) + ) fastanime_runtime_state.anilist_results_data = anime_list[1] anilist_interfaces.anilist_results_menu(config, fastanime_runtime_state) diff --git a/fastanime/cli/interfaces/anilist_interfaces.py b/fastanime/cli/interfaces/anilist_interfaces.py index 6c0881a..efffffc 100644 --- a/fastanime/cli/interfaces/anilist_interfaces.py +++ b/fastanime/cli/interfaces/anilist_interfaces.py @@ -1511,7 +1511,7 @@ def anilist_results_menu( # # ---- FASTANIME MAIN MENU ---- # -def handle_animelist( +def _handle_animelist( config: "Config", fastanime_runtime_state: "FastAnimeRuntimeState", list_type: str, @@ -1548,7 +1548,7 @@ def handle_animelist( status = "DROPPED" case "Paused": status = "PAUSED" - case "Repeating": + case "Rewatching": status = "REPEATING" case _: return @@ -1672,32 +1672,32 @@ def fastanime_main_menu( f"{'🎞️ ' if icons else ''}Recent": _recent, f"{'📺 ' if icons else ''}Watching": lambda config, media_list_type="Watching", - page=1: handle_animelist( + page=1: _handle_animelist( config, fastanime_runtime_state, media_list_type, page=page ), f"{'⏸ ' if icons else ''}Paused": lambda config, media_list_type="Paused", - page=1: handle_animelist( + page=1: _handle_animelist( config, fastanime_runtime_state, media_list_type, page=page ), f"{'🚮 ' if icons else ''}Dropped": lambda config, media_list_type="Dropped", - page=1: handle_animelist( + page=1: _handle_animelist( config, fastanime_runtime_state, media_list_type, page=page ), f"{'📑 ' if icons else ''}Planned": lambda config, media_list_type="Planned", - page=1: handle_animelist( + page=1: _handle_animelist( config, fastanime_runtime_state, media_list_type, page=page ), f"{'✅ ' if icons else ''}Completed": lambda config, media_list_type="Completed", - page=1: handle_animelist( + page=1: _handle_animelist( config, fastanime_runtime_state, media_list_type, page=page ), f"{'🔁 ' if icons else ''}Rewatching": lambda config, - media_list_type="Repeating", - page=1: handle_animelist( + media_list_type="Rewatching", + page=1: _handle_animelist( config, fastanime_runtime_state, media_list_type, page=page ), f"{'🔔 ' if icons else ''}Recently Updated Anime": AniList.get_most_recently_updated,