Files
FastAnime/fastanime/cli/interactive/menus/main.py
2025-07-22 00:47:42 +03:00

183 lines
6.0 KiB
Python

import logging
import random
from typing import Callable, Dict, Tuple
from ....libs.api.params import ApiSearchParams, UserListParams
from ....libs.api.types import MediaSearchResult, MediaStatus, UserListStatusType
from ..session import Context, session
from ..state import ControlFlow, MediaApiState, State
logger = logging.getLogger(__name__)
MenuAction = Callable[
[],
Tuple[str, MediaSearchResult | None, ApiSearchParams | None, UserListParams | None],
]
@session.menu
def main(ctx: Context, state: State) -> State | ControlFlow:
"""
The main entry point menu for the interactive session.
Displays top-level categories for the user to browse and select.
"""
icons = ctx.config.general.icons
feedback = ctx.services.feedback
feedback.clear_console()
# TODO: Make them just return the modified state or control flow
options: Dict[str, MenuAction] = {
# --- Search-based Actions ---
f"{'🔥 ' if icons else ''}Trending": _create_media_list_action(
ctx, "TRENDING_DESC"
),
f"{'' if icons else ''}Popular": _create_media_list_action(
ctx, "POPULARITY_DESC"
),
f"{'💖 ' if icons else ''}Favourites": _create_media_list_action(
ctx, "FAVOURITES_DESC"
),
f"{'💯 ' if icons else ''}Top Scored": _create_media_list_action(
ctx, "SCORE_DESC"
),
f"{'🎬 ' if icons else ''}Upcoming": _create_media_list_action(
ctx, "POPULARITY_DESC", "NOT_YET_RELEASED"
),
f"{'🔔 ' if icons else ''}Recently Updated": _create_media_list_action(
ctx, "UPDATED_AT_DESC"
),
# --- special case media list --
f"{'🎲 ' if icons else ''}Random": _create_random_media_list(ctx),
f"{'🔎 ' if icons else ''}Search": _create_search_media_list(ctx),
# --- Authenticated User List Actions ---
f"{'📺 ' if icons else ''}Watching": _create_user_list_action(ctx, "watching"),
f"{'📑 ' if icons else ''}Planned": _create_user_list_action(ctx, "planning"),
f"{'' if icons else ''}Completed": _create_user_list_action(
ctx, "completed"
),
f"{'⏸️ ' if icons else ''}Paused": _create_user_list_action(ctx, "paused"),
f"{'🚮 ' if icons else ''}Dropped": _create_user_list_action(ctx, "dropped"),
f"{'🔁 ' if icons else ''}Rewatching": _create_user_list_action(
ctx, "repeating"
),
f"{'🔁 ' if icons else ''}Recent": lambda: (
"RESULTS",
ctx.services.media_registry.get_recently_watched(
ctx.config.anilist.per_page
),
None,
None,
),
f"{'' if icons else ''}Exit": lambda: ("EXIT", None, None, None),
}
choice_str = ctx.selector.choose(
prompt="Select Category",
choices=list(options.keys()),
)
if not choice_str:
return ControlFlow.EXIT
# --- Action Handling ---
selected_action = options[choice_str]
next_menu_name, result_data, api_params, user_list_params = selected_action()
if next_menu_name == "EXIT":
return ControlFlow.EXIT
if next_menu_name == "RELOAD_CONFIG":
return ControlFlow.CONFIG_EDIT
if next_menu_name == "SESSION_MANAGEMENT":
return State(menu_name="SESSION_MANAGEMENT")
if next_menu_name == "AUTH":
return State(menu_name="AUTH")
if next_menu_name == "ANILIST_LISTS":
return State(menu_name="ANILIST_LISTS")
if next_menu_name == "WATCH_HISTORY":
return State(menu_name="WATCH_HISTORY")
if next_menu_name == "CONTINUE":
return ControlFlow.CONTINUE
if not result_data:
feedback.error(
f"Failed to fetch data for '{choice_str.strip()}'",
"Please check your internet connection and try again.",
)
return ControlFlow.CONTINUE
# On success, transition to the RESULTS menu state.
return State(
menu_name="RESULTS",
media_api=MediaApiState(
search_results=result_data,
original_api_params=api_params,
original_user_list_params=user_list_params,
),
)
def _create_media_list_action(
ctx: Context, sort, status: MediaStatus | None = None
) -> MenuAction:
"""A factory to create menu actions for fetching media lists"""
def action():
# Create the search parameters
search_params = ApiSearchParams(
sort=sort, per_page=ctx.config.anilist.per_page, status=status
)
result = ctx.media_api.search_media(search_params)
return ("RESULTS", result, search_params, None)
return action
def _create_random_media_list(ctx: Context) -> MenuAction:
def action():
search_params = ApiSearchParams(
id_in=random.sample(range(1, 160000), k=50),
per_page=ctx.config.anilist.per_page,
)
result = ctx.media_api.search_media(search_params)
return ("RESULTS", result, search_params, None)
return action
def _create_search_media_list(ctx: Context) -> MenuAction:
def action():
query = ctx.selector.ask("Search for Anime")
if not query:
return "CONTINUE", None, None, None
search_params = ApiSearchParams(query=query)
result = ctx.media_api.search_media(search_params)
return ("RESULTS", result, search_params, None)
return action
def _create_user_list_action(ctx: Context, status: UserListStatusType) -> MenuAction:
"""A factory to create menu actions for fetching user lists, handling authentication."""
def action():
# Check authentication
if not ctx.media_api.is_authenticated():
logger.warning("Not authenticated")
return "CONTINUE", None, None, None
user_list_params = UserListParams(
status=status, per_page=ctx.config.anilist.per_page
)
result = ctx.media_api.fetch_user_list(user_list_params)
return ("RESULTS", result, None, user_list_params)
return action