Files
FastAnime/fastanime/cli/interactive/menus/main.py
2025-07-16 00:46:02 +03:00

234 lines
8.6 KiB
Python

import random
from typing import Callable, Dict, Tuple
from rich.console import Console
from ....libs.api.params import ApiSearchParams, UserListParams
from ....libs.api.types import MediaSearchResult, MediaStatus, UserListStatusType
from ...utils.feedback import create_feedback_manager, execute_with_feedback
from ...utils.auth.utils import format_auth_menu_header, check_authentication_required
from ..session import Context, session
from ..state import ControlFlow, MediaApiState, State
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 = create_feedback_manager(icons)
console = Console()
console.clear()
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, "CURRENT"),
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"
),
# --- List Management ---
f"{'📚 ' if icons else ''}AniList Lists Manager": lambda: ("ANILIST_LISTS", None, None, None),
f"{'📖 ' if icons else ''}Local Watch History": lambda: ("WATCH_HISTORY", None, None, None),
# --- Authentication and Account Management ---
f"{'🔐 ' if icons else ''}Authentication": lambda: ("AUTH", None, None, None),
# --- Control Flow and Utility Options ---
f"{'🔧 ' if icons else ''}Session Management": lambda: ("SESSION_MANAGEMENT", None, None, None),
f"{'📝 ' if icons else ''}Edit Config": lambda: ("RELOAD_CONFIG", None, None, None),
f"{'' if icons else ''}Exit": lambda: ("EXIT", None, None, None),
}
choice_str = ctx.selector.choose(
prompt="Select Category",
choices=list(options.keys()),
header=format_auth_menu_header(ctx.media_api, "FastAnime Main Menu", icons),
)
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.RELOAD_CONFIG
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():
feedback = create_feedback_manager(ctx.config.general.icons)
# Create the search parameters
search_params = ApiSearchParams(
sort=sort, per_page=ctx.config.anilist.per_page, status=status
)
def fetch_data():
return ctx.media_api.search_media(search_params)
success, result = execute_with_feedback(
fetch_data,
feedback,
"fetch anime list",
loading_msg="Fetching anime",
success_msg="Anime list loaded successfully",
)
# Return the search parameters along with the result for pagination
return ("RESULTS", result, search_params, None) if success else ("CONTINUE", None, None, None)
return action
def _create_random_media_list(ctx: Context) -> MenuAction:
def action():
feedback = create_feedback_manager(ctx.config.general.icons)
# Create the search parameters
search_params = ApiSearchParams(
id_in=random.sample(range(1, 160000), k=50),
per_page=ctx.config.anilist.per_page,
)
def fetch_data():
return ctx.media_api.search_media(search_params)
success, result = execute_with_feedback(
fetch_data,
feedback,
"fetch random anime",
loading_msg="Fetching random anime",
success_msg="Random anime loaded successfully",
)
# Return the search parameters along with the result for pagination
return ("RESULTS", result, search_params, None) if success else ("CONTINUE", None, None, None)
return action
def _create_search_media_list(ctx: Context) -> MenuAction:
def action():
feedback = create_feedback_manager(ctx.config.general.icons)
query = ctx.selector.ask("Search for Anime")
if not query:
return "CONTINUE", None, None, None
# Create the search parameters
search_params = ApiSearchParams(query=query)
def fetch_data():
return ctx.media_api.search_media(search_params)
success, result = execute_with_feedback(
fetch_data,
feedback,
"search anime",
loading_msg=f"Searching for '{query}'",
success_msg=f"Search results for '{query}' loaded successfully",
)
# Return the search parameters along with the result for pagination
return ("RESULTS", result, search_params, None) if success else ("CONTINUE", None, None, 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():
feedback = create_feedback_manager(ctx.config.general.icons)
# Check authentication
if not check_authentication_required(
ctx.media_api, feedback, f"view your {status.lower()} list"
):
return "CONTINUE", None, None, None
# Create the user list parameters
user_list_params = UserListParams(status=status, per_page=ctx.config.anilist.per_page)
def fetch_data():
return ctx.media_api.fetch_user_list(user_list_params)
success, result = execute_with_feedback(
fetch_data,
feedback,
f"fetch {status.lower()} list",
loading_msg=f"Fetching your {status.lower()} list",
success_msg=f"Your {status.lower()} list loaded successfully",
)
# Return the user list parameters along with the result for pagination
return ("RESULTS", result, None, user_list_params) if success else ("CONTINUE", None, None, None)
return action