diff --git a/fastanime/cli/commands/anilist/__init__.py b/fastanime/cli/commands/anilist/__init__.py index 5c2d8de..855e76c 100644 --- a/fastanime/cli/commands/anilist/__init__.py +++ b/fastanime/cli/commands/anilist/__init__.py @@ -20,6 +20,7 @@ commands = { "completed": "completed.completed", "planning": "planning.planning", "notifier": "notifier.notifier", + "stats": "stats.stats", } diff --git a/fastanime/cli/commands/anilist/stats.py b/fastanime/cli/commands/anilist/stats.py new file mode 100644 index 0000000..19e23e3 --- /dev/null +++ b/fastanime/cli/commands/anilist/stats.py @@ -0,0 +1,63 @@ +from typing import TYPE_CHECKING + +import click + +if TYPE_CHECKING: + from ...config import Config + + +@click.command(help="Print out your anilist stats") +@click.pass_obj +def stats( + config: "Config", +): + import shutil + import subprocess + from sys import exit + + from rich.console import Console + + console = Console() + from rich.markdown import Markdown + from rich.panel import Panel + + from ....anilist import AniList + + user_data = AniList.get_user_info() + if not user_data[0] or not user_data[1]: + print("Failed to get user info") + print(user_data[1]) + exit(1) + + KITTEN_EXECUTABLE = shutil.which("kitten") + if not KITTEN_EXECUTABLE: + print("Kitten not found") + exit(1) + + image_url = user_data[1]["data"]["User"]["avatar"]["medium"] + user_name = user_data[1]["data"]["User"]["name"] + about = user_data[1]["data"]["User"]["about"] or "" + console.clear() + image_x = int(console.size.width * 0.1) + image_y = int(console.size.height * 0.1) + img_w = console.size.width // 3 + img_h = console.size.height // 3 + image_process = subprocess.run( + [ + KITTEN_EXECUTABLE, + "icat", + "--clear", + "--place", + f"{img_w}x{img_h}@{image_x}x{image_y}", + image_url, + ], + ) + if not image_process.returncode == 0: + print("failed to get image from icat") + exit(1) + console.print( + Panel( + Markdown(about), + title=user_name, + ) + ) diff --git a/fastanime/libs/anilist/api.py b/fastanime/libs/anilist/api.py index 3093768..1b4f5e9 100644 --- a/fastanime/libs/anilist/api.py +++ b/fastanime/libs/anilist/api.py @@ -15,6 +15,7 @@ from .queries_graphql import ( delete_list_entry_query, get_logged_in_user_query, get_medialist_item_query, + get_user_info, media_list_mutation, media_list_query, most_favourite_query, @@ -34,8 +35,9 @@ if TYPE_CHECKING: AnilistMediaLists, AnilistMediaListStatus, AnilistNotifications, - AnilistUser, + AnilistUser_, AnilistUserData, + AnilistViewerData, ) logger = logging.getLogger(__name__) ANILIST_ENDPOINT = "https://graphql.anilist.co" @@ -77,7 +79,7 @@ class AniListApi: return if not success or not user: return - user_info: AnilistUser = user["data"]["Viewer"] + user_info: "AnilistUser_" = user["data"]["Viewer"] self.user_id = user_info["id"] return user_info @@ -91,7 +93,7 @@ class AniListApi: """ return self._make_authenticated_request(notification_query) - def update_login_info(self, user: "AnilistUser", token: str): + def update_login_info(self, user: "AnilistUser_", token: str): """method used to login a user enabling authenticated requests Args: @@ -103,7 +105,18 @@ class AniListApi: self.session.headers.update(self.headers) self.user_id = user["id"] - def get_logged_in_user(self) -> tuple[bool, "AnilistUserData"] | tuple[bool, None]: + def get_user_info(self) -> tuple[bool, "AnilistUserData"] | tuple[bool, None]: + """get the details of the user who is currently logged in + + Returns: + an anilist user + """ + + return self._make_authenticated_request(get_user_info, {"userId": self.user_id}) + + def get_logged_in_user( + self, + ) -> tuple[bool, "AnilistViewerData"] | tuple[bool, None]: """get the details of the user who is currently logged in Returns: diff --git a/fastanime/libs/anilist/queries_graphql.py b/fastanime/libs/anilist/queries_graphql.py index da9fd1a..594326b 100644 --- a/fastanime/libs/anilist/queries_graphql.py +++ b/fastanime/libs/anilist/queries_graphql.py @@ -93,6 +93,70 @@ query{ } """ +get_user_info = """ +query ($userId: Int) { + User(id: $userId) { + name + about + avatar { + large + medium + } + bannerImage + statistics { + anime { + count + minutesWatched + episodesWatched + genres { + count + meanScore + genre + } + tags { + tag { + id + } + count + meanScore + } + } + manga { + count + meanScore + chaptersRead + volumesRead + tags { + count + meanScore + } + genres { + count + meanScore + } + } + } + favourites { + anime { + nodes { + title { + romaji + english + } + } + } + manga { + nodes { + title { + romaji + english + } + } + } + } + } +} +""" media_list_mutation = """ mutation ( $mediaId: Int diff --git a/fastanime/libs/anilist/types.py b/fastanime/libs/anilist/types.py index 5f12550..b353ba0 100644 --- a/fastanime/libs/anilist/types.py +++ b/fastanime/libs/anilist/types.py @@ -19,7 +19,7 @@ class AnilistImage(TypedDict): large: str -class AnilistUser(TypedDict): +class AnilistUser_(TypedDict): id: int name: str bannerImage: str | None @@ -28,11 +28,26 @@ class AnilistUser(TypedDict): class AnilistViewer(TypedDict): - Viewer: AnilistUser + Viewer: AnilistUser_ + + +class AnilistViewerData(TypedDict): + data: AnilistViewer + + +class AnilistUser(TypedDict): + name: str + about: str | None + avatar: AnilistImage + bannerImage: str | None + + +class AnilistUserInfo(TypedDict): + User: AnilistUser class AnilistUserData(TypedDict): - data: AnilistViewer + data: AnilistUserInfo class AnilistMediaTrailer(TypedDict): @@ -69,7 +84,7 @@ class AnilistMediaNextAiringEpisode(TypedDict): class AnilistReview(TypedDict): summary: str - user: AnilistUser + user: AnilistUser_ class AnilistReviewNodes(TypedDict):