Files
FastAnime/fastanime/cli/interactive/menus/main.py
2025-07-14 02:24:44 +03:00

158 lines
5.4 KiB
Python

# fastanime/cli/interactive/menus/main.py
from __future__ import annotations
import random
from typing import TYPE_CHECKING, Callable, Dict, Tuple
import click
from rich.progress import Progress
from ....libs.api.params import ApiSearchParams, UserListParams
from ..session import Context, session
from ..state import ControlFlow, MediaApiState, State
if TYPE_CHECKING:
from ....libs.api.types import MediaSearchResult
# A type alias for the actions this menu can perform.
# It returns a tuple: (NextMenuNameOrControlFlow, Optional[DataPayload])
MenuAction = Callable[[], Tuple[str, MediaSearchResult | 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
api_client = ctx.media_api
per_page = ctx.config.anilist.per_page
# The lambdas now correctly use the versatile search_media for most actions.
options: Dict[str, MenuAction] = {
# --- Search-based Actions ---
f"{'🔥 ' if icons else ''}Trending": lambda: (
"RESULTS",
api_client.search_media(
ApiSearchParams(sort="TRENDING_DESC", per_page=per_page)
),
),
f"{'' if icons else ''}Popular": lambda: (
"RESULTS",
api_client.search_media(
ApiSearchParams(sort="POPULARITY_DESC", per_page=per_page)
),
),
f"{'💖 ' if icons else ''}Favourites": lambda: (
"RESULTS",
api_client.search_media(
ApiSearchParams(sort="FAVOURITES_DESC", per_page=per_page)
),
),
f"{'💯 ' if icons else ''}Top Scored": lambda: (
"RESULTS",
api_client.search_media(
ApiSearchParams(sort="SCORE_DESC", per_page=per_page)
),
),
f"{'🎬 ' if icons else ''}Upcoming": lambda: (
"RESULTS",
api_client.search_media(
ApiSearchParams(
status="NOT_YET_RELEASED", sort="POPULARITY_DESC", per_page=per_page
)
),
),
f"{'🔔 ' if icons else ''}Recently Updated": lambda: (
"RESULTS",
api_client.search_media(
ApiSearchParams(
status="RELEASING", sort="UPDATED_AT_DESC", per_page=per_page
)
),
),
f"{'🎲 ' if icons else ''}Random": lambda: (
"RESULTS",
api_client.search_media(
ApiSearchParams(
id_in=random.sample(range(1, 160000), k=50), per_page=per_page
)
),
),
f"{'🔎 ' if icons else ''}Search": lambda: (
"RESULTS",
api_client.search_media(
ApiSearchParams(query=ctx.selector.ask("Search for Anime"))
),
),
# --- 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"
),
# --- Control Flow and Utility Options ---
f"{'📝 ' if icons else ''}Edit Config": lambda: ("RELOAD_CONFIG", None),
f"{'' if icons else ''}Exit": lambda: ("EXIT", None),
}
choice_str = ctx.selector.choose(
prompt="Select Category",
choices=list(options.keys()),
header="FastAnime Main Menu",
)
if not choice_str:
return ControlFlow.EXIT
# --- Action Handling ---
selected_action = options[choice_str]
with Progress(transient=True) as progress:
task = progress.add_task(f"[cyan]Fetching {choice_str.strip()}...", total=None)
next_menu_name, result_data = selected_action()
progress.update(task, completed=True)
if next_menu_name == "EXIT":
return ControlFlow.EXIT
if next_menu_name == "RELOAD_CONFIG":
return ControlFlow.RELOAD_CONFIG
if next_menu_name == "CONTINUE":
return ControlFlow.CONTINUE
if not result_data:
click.echo(
f"[bold red]Error:[/bold red] Failed to fetch data for '{choice_str.strip()}'."
)
return ControlFlow.CONTINUE
# On success, transition to the RESULTS menu state.
return State(
menu_name="RESULTS",
media_api=MediaApiState(search_results=result_data),
)
def _create_user_list_action(ctx: Context, status: str) -> MenuAction:
"""A factory to create menu actions for fetching user lists, handling authentication."""
def action() -> Tuple[str, MediaSearchResult | None]:
if not ctx.media_api.user_profile:
click.echo(
f"[bold yellow]Please log in to view your '{status.title()}' list.[/]"
)
return "CONTINUE", None
return "RESULTS", ctx.media_api.fetch_user_list(
UserListParams(status=status, per_page=ctx.config.anilist.per_page)
)
return action