mirror of
https://github.com/Benexl/FastAnime.git
synced 2025-12-10 23:00:41 -08:00
feat: improve api types
This commit is contained in:
@@ -18,7 +18,7 @@ from rich.table import Table
|
|||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
|
|
||||||
from ....libs.api.params import UpdateListEntryParams, UserListParams
|
from ....libs.api.params import UpdateListEntryParams, UserListParams
|
||||||
from ....libs.api.types import MediaItem, MediaSearchResult, UserListStatusType
|
from ....libs.api.types import MediaItem, MediaSearchResult, UserListItem
|
||||||
from ...utils.feedback import create_feedback_manager, execute_with_feedback
|
from ...utils.feedback import create_feedback_manager, execute_with_feedback
|
||||||
from ..session import Context, session
|
from ..session import Context, session
|
||||||
from ..state import ControlFlow, MediaApiState, State
|
from ..state import ControlFlow, MediaApiState, State
|
||||||
@@ -373,7 +373,7 @@ def _display_anime_list_details(console: Console, anime: MediaItem, icons: bool)
|
|||||||
console.print(panel)
|
console.print(panel)
|
||||||
|
|
||||||
|
|
||||||
def _navigate_to_list(ctx: Context, list_status: UserListStatusType) -> State:
|
def _navigate_to_list(ctx: Context, list_status: UserListItem) -> State:
|
||||||
"""Navigate to a specific list view."""
|
"""Navigate to a specific list view."""
|
||||||
return State(
|
return State(
|
||||||
menu_name="ANILIST_LIST_VIEW", data={"list_status": list_status, "page": 1}
|
menu_name="ANILIST_LIST_VIEW", data={"list_status": list_status, "page": 1}
|
||||||
|
|||||||
@@ -3,7 +3,12 @@ import random
|
|||||||
from typing import Callable, Dict, Tuple
|
from typing import Callable, Dict, Tuple
|
||||||
|
|
||||||
from ....libs.api.params import ApiSearchParams, UserListParams
|
from ....libs.api.params import ApiSearchParams, UserListParams
|
||||||
from ....libs.api.types import MediaSearchResult, MediaStatus, UserListStatusType
|
from ....libs.api.types import (
|
||||||
|
MediaSearchResult,
|
||||||
|
MediaSort,
|
||||||
|
MediaStatus,
|
||||||
|
UserMediaListStatus,
|
||||||
|
)
|
||||||
from ..session import Context, session
|
from ..session import Context, session
|
||||||
from ..state import ControlFlow, MediaApiState, State
|
from ..state import ControlFlow, MediaApiState, State
|
||||||
|
|
||||||
@@ -28,36 +33,44 @@ def main(ctx: Context, state: State) -> State | ControlFlow:
|
|||||||
options: Dict[str, MenuAction] = {
|
options: Dict[str, MenuAction] = {
|
||||||
# --- Search-based Actions ---
|
# --- Search-based Actions ---
|
||||||
f"{'🔥 ' if icons else ''}Trending": _create_media_list_action(
|
f"{'🔥 ' if icons else ''}Trending": _create_media_list_action(
|
||||||
ctx, "TRENDING_DESC"
|
ctx, MediaSort.TRENDING_DESC
|
||||||
),
|
),
|
||||||
f"{'✨ ' if icons else ''}Popular": _create_media_list_action(
|
f"{'✨ ' if icons else ''}Popular": _create_media_list_action(
|
||||||
ctx, "POPULARITY_DESC"
|
ctx, MediaSort.POPULARITY_DESC
|
||||||
),
|
),
|
||||||
f"{'💖 ' if icons else ''}Favourites": _create_media_list_action(
|
f"{'💖 ' if icons else ''}Favourites": _create_media_list_action(
|
||||||
ctx, "FAVOURITES_DESC"
|
ctx, MediaSort.FAVOURITES_DESC
|
||||||
),
|
),
|
||||||
f"{'💯 ' if icons else ''}Top Scored": _create_media_list_action(
|
f"{'💯 ' if icons else ''}Top Scored": _create_media_list_action(
|
||||||
ctx, "SCORE_DESC"
|
ctx, MediaSort.SCORE_DESC
|
||||||
),
|
),
|
||||||
f"{'🎬 ' if icons else ''}Upcoming": _create_media_list_action(
|
f"{'🎬 ' if icons else ''}Upcoming": _create_media_list_action(
|
||||||
ctx, "POPULARITY_DESC", "NOT_YET_RELEASED"
|
ctx, MediaSort.POPULARITY_DESC, MediaStatus.NOT_YET_RELEASED
|
||||||
),
|
),
|
||||||
f"{'🔔 ' if icons else ''}Recently Updated": _create_media_list_action(
|
f"{'🔔 ' if icons else ''}Recently Updated": _create_media_list_action(
|
||||||
ctx, "UPDATED_AT_DESC"
|
ctx, MediaSort.UPDATED_AT_DESC
|
||||||
),
|
),
|
||||||
# --- special case media list --
|
# --- special case media list --
|
||||||
f"{'🎲 ' if icons else ''}Random": _create_random_media_list(ctx),
|
f"{'🎲 ' if icons else ''}Random": _create_random_media_list(ctx),
|
||||||
f"{'🔎 ' if icons else ''}Search": _create_search_media_list(ctx),
|
f"{'🔎 ' if icons else ''}Search": _create_search_media_list(ctx),
|
||||||
# --- Authenticated User List Actions ---
|
# --- Authenticated User List Actions ---
|
||||||
f"{'📺 ' if icons else ''}Watching": _create_user_list_action(ctx, "watching"),
|
f"{'📺 ' if icons else ''}Watching": _create_user_list_action(
|
||||||
f"{'📑 ' if icons else ''}Planned": _create_user_list_action(ctx, "planning"),
|
ctx, UserMediaListStatus.WATCHING
|
||||||
f"{'✅ ' if icons else ''}Completed": _create_user_list_action(
|
),
|
||||||
ctx, "completed"
|
f"{'📑 ' if icons else ''}Planned": _create_user_list_action(
|
||||||
|
ctx, UserMediaListStatus.PLANNING
|
||||||
|
),
|
||||||
|
f"{'✅ ' if icons else ''}Completed": _create_user_list_action(
|
||||||
|
ctx, UserMediaListStatus.COMPLETED
|
||||||
|
),
|
||||||
|
f"{'⏸️ ' if icons else ''}Paused": _create_user_list_action(
|
||||||
|
ctx, UserMediaListStatus.PAUSED
|
||||||
|
),
|
||||||
|
f"{'🚮 ' if icons else ''}Dropped": _create_user_list_action(
|
||||||
|
ctx, UserMediaListStatus.DROPPED
|
||||||
),
|
),
|
||||||
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(
|
f"{'🔁 ' if icons else ''}Rewatching": _create_user_list_action(
|
||||||
ctx, "repeating"
|
ctx, UserMediaListStatus.REPEATING
|
||||||
),
|
),
|
||||||
f"{'🔁 ' if icons else ''}Recent": lambda: (
|
f"{'🔁 ' if icons else ''}Recent": lambda: (
|
||||||
"RESULTS",
|
"RESULTS",
|
||||||
@@ -123,7 +136,7 @@ def main(ctx: Context, state: State) -> State | ControlFlow:
|
|||||||
|
|
||||||
|
|
||||||
def _create_media_list_action(
|
def _create_media_list_action(
|
||||||
ctx: Context, sort, status: MediaStatus | None = None
|
ctx: Context, sort: MediaSort, status: MediaStatus | None = None
|
||||||
) -> MenuAction:
|
) -> MenuAction:
|
||||||
"""A factory to create menu actions for fetching media lists"""
|
"""A factory to create menu actions for fetching media lists"""
|
||||||
|
|
||||||
@@ -163,7 +176,7 @@ def _create_search_media_list(ctx: Context) -> MenuAction:
|
|||||||
return action
|
return action
|
||||||
|
|
||||||
|
|
||||||
def _create_user_list_action(ctx: Context, status: UserListStatusType) -> MenuAction:
|
def _create_user_list_action(ctx: Context, status: UserMediaListStatus) -> MenuAction:
|
||||||
"""A factory to create menu actions for fetching user lists, handling authentication."""
|
"""A factory to create menu actions for fetching user lists, handling authentication."""
|
||||||
|
|
||||||
def action():
|
def action():
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from typing import Callable, Dict
|
from typing import Callable, Dict
|
||||||
|
|
||||||
import click
|
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
|
||||||
from ....libs.api.params import UpdateListEntryParams
|
from ....libs.api.params import UpdateListEntryParams
|
||||||
@@ -152,7 +151,7 @@ def _view_info(ctx: Context, state: State) -> MenuAction:
|
|||||||
console = Console()
|
console = Console()
|
||||||
title = Text(anime.title.english or anime.title.romaji or "", style="bold cyan")
|
title = Text(anime.title.english or anime.title.romaji or "", style="bold cyan")
|
||||||
description = Text(anime.description or "NO description")
|
description = Text(anime.description or "NO description")
|
||||||
genres = Text(f"Genres: {', '.join(anime.genres)}")
|
genres = Text(f"Genres: {', '.join([v.value for v in anime.genres])}")
|
||||||
|
|
||||||
panel_content = f"{genres}\n\n{description}"
|
panel_content = f"{genres}\n\n{description}"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from ....libs.api.params import ApiSearchParams, UserListParams
|
from ....libs.api.params import ApiSearchParams, UserListParams
|
||||||
from ....libs.api.types import MediaItem
|
from ....libs.api.types import MediaItem, MediaStatus, UserMediaListStatus
|
||||||
from ..session import Context, session
|
from ..session import Context, session
|
||||||
from ..state import ControlFlow, MediaApiState, State
|
from ..state import ControlFlow, MediaApiState, State
|
||||||
|
|
||||||
@@ -96,10 +96,10 @@ def _format_anime_choice(anime: MediaItem, config) -> str:
|
|||||||
|
|
||||||
# Add a visual indicator for new episodes if applicable
|
# Add a visual indicator for new episodes if applicable
|
||||||
if (
|
if (
|
||||||
anime.status == "RELEASING"
|
anime.status == MediaStatus.RELEASING
|
||||||
and anime.next_airing
|
and anime.next_airing
|
||||||
and anime.user_status
|
and anime.user_status
|
||||||
and anime.user_status.status == "CURRENT"
|
and anime.user_status.status == UserMediaListStatus.WATCHING
|
||||||
):
|
):
|
||||||
last_aired = anime.next_airing.episode - 1
|
last_aired = anime.next_airing.episode - 1
|
||||||
unwatched = last_aired - (anime.user_status.progress or 0)
|
unwatched = last_aired - (anime.user_status.progress or 0)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from ...libs.api.types import (
|
|||||||
MediaItem,
|
MediaItem,
|
||||||
MediaSearchResult,
|
MediaSearchResult,
|
||||||
MediaStatus,
|
MediaStatus,
|
||||||
UserListStatusType,
|
UserListItem,
|
||||||
)
|
)
|
||||||
from ...libs.players.types import PlayerResult
|
from ...libs.players.types import PlayerResult
|
||||||
from ...libs.providers.anime.types import Anime, SearchResults, Server
|
from ...libs.providers.anime.types import Anime, SearchResults, Server
|
||||||
@@ -80,7 +80,7 @@ class MediaApiState(BaseModel):
|
|||||||
search_results_type: Optional[Literal["MEDIA_LIST", "USER_MEDIA_LIST"]] = None
|
search_results_type: Optional[Literal["MEDIA_LIST", "USER_MEDIA_LIST"]] = None
|
||||||
sort: Optional[str] = None
|
sort: Optional[str] = None
|
||||||
query: Optional[str] = None
|
query: Optional[str] = None
|
||||||
user_media_status: Optional[UserListStatusType] = None
|
user_media_status: Optional[UserListItem] = None
|
||||||
media_status: Optional[MediaStatus] = None
|
media_status: Optional[MediaStatus] = None
|
||||||
anime: Optional[MediaItem] = None
|
anime: Optional[MediaItem] = None
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
|
from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Literal, get_args, get_origin
|
from typing import Any, Literal, get_args, get_origin
|
||||||
|
|
||||||
@@ -130,6 +131,16 @@ def _get_click_type(field_info: FieldInfo) -> Any:
|
|||||||
"""Maps a Pydantic field's type to a corresponding click type."""
|
"""Maps a Pydantic field's type to a corresponding click type."""
|
||||||
field_type = field_info.annotation
|
field_type = field_info.annotation
|
||||||
|
|
||||||
|
# check if type is enum
|
||||||
|
if (
|
||||||
|
field_type is not None
|
||||||
|
and isinstance(field_type, type)
|
||||||
|
and issubclass(field_type, Enum)
|
||||||
|
):
|
||||||
|
# Get the string values of the enum members
|
||||||
|
enum_choices = [member.value for member in field_type]
|
||||||
|
return click.Choice(enum_choices)
|
||||||
|
|
||||||
# Check if the type is a Literal
|
# Check if the type is a Literal
|
||||||
if (
|
if (
|
||||||
field_type is not None
|
field_type is not None
|
||||||
|
|||||||
@@ -1,27 +1,33 @@
|
|||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Literal, Optional
|
from typing import Dict, Literal, Optional
|
||||||
|
|
||||||
from pydantic import BaseModel, Field, computed_field
|
from pydantic import BaseModel, Field, computed_field
|
||||||
|
|
||||||
from ....libs.api.types import MediaItem, UserListStatusType
|
from ....libs.api.types import MediaItem, UserMediaListStatus
|
||||||
from ...utils import converters
|
from ...utils import converters
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Type aliases
|
|
||||||
DownloadStatus = Literal[
|
class DownloadStatus(Enum):
|
||||||
"not_downloaded", "queued", "downloading", "completed", "failed", "paused"
|
NOT_DOWNLOADED = "not_downloaded"
|
||||||
]
|
QUEUED = "queued"
|
||||||
|
DOWNLOADING = "downloading"
|
||||||
|
COMPLETED = "completed"
|
||||||
|
FAILED = "failed"
|
||||||
|
PAUSED = "paused"
|
||||||
|
|
||||||
|
|
||||||
REGISTRY_VERSION = "1.0"
|
REGISTRY_VERSION = "1.0"
|
||||||
|
|
||||||
|
|
||||||
class MediaEpisode(BaseModel):
|
class MediaEpisode(BaseModel):
|
||||||
episode_number: str
|
episode_number: str
|
||||||
|
|
||||||
# Download tracking
|
download_status: DownloadStatus = DownloadStatus.NOT_DOWNLOADED
|
||||||
download_status: DownloadStatus = "not_downloaded"
|
|
||||||
file_path: Path
|
file_path: Path
|
||||||
download_date: datetime = Field(default_factory=datetime.now)
|
download_date: datetime = Field(default_factory=datetime.now)
|
||||||
|
|
||||||
@@ -35,7 +41,7 @@ class MediaRegistryIndexEntry(BaseModel):
|
|||||||
media_id: int
|
media_id: int
|
||||||
media_api: Literal["anilist", "NONE", "jikan"] = "NONE"
|
media_api: Literal["anilist", "NONE", "jikan"] = "NONE"
|
||||||
|
|
||||||
status: UserListStatusType = "watching"
|
status: UserMediaListStatus = UserMediaListStatus.WATCHING
|
||||||
progress: str = "0"
|
progress: str = "0"
|
||||||
last_watch_position: Optional[str] = None
|
last_watch_position: Optional[str] = None
|
||||||
last_watched: datetime = Field(default_factory=datetime.now)
|
last_watched: datetime = Field(default_factory=datetime.now)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from ....libs.api.types import (
|
|||||||
MediaItem,
|
MediaItem,
|
||||||
MediaSearchResult,
|
MediaSearchResult,
|
||||||
PageInfo,
|
PageInfo,
|
||||||
UserListStatusType,
|
UserMediaListStatus,
|
||||||
)
|
)
|
||||||
from .filters import MediaFilter
|
from .filters import MediaFilter
|
||||||
from .models import (
|
from .models import (
|
||||||
@@ -150,7 +150,7 @@ class MediaRegistryService:
|
|||||||
watched: bool = False,
|
watched: bool = False,
|
||||||
media_item: Optional[MediaItem] = None,
|
media_item: Optional[MediaItem] = None,
|
||||||
progress: Optional[str] = None,
|
progress: Optional[str] = None,
|
||||||
status: Optional[UserListStatusType] = None,
|
status: Optional[UserMediaListStatus] = None,
|
||||||
last_watch_position: Optional[str] = None,
|
last_watch_position: Optional[str] = None,
|
||||||
total_duration: Optional[str] = None,
|
total_duration: Optional[str] = None,
|
||||||
score: Optional[float] = None,
|
score: Optional[float] = None,
|
||||||
@@ -171,7 +171,7 @@ class MediaRegistryService:
|
|||||||
if status:
|
if status:
|
||||||
index_entry.status = status
|
index_entry.status = status
|
||||||
else:
|
else:
|
||||||
index_entry.status = "watching"
|
index_entry.status = UserMediaListStatus.WATCHING
|
||||||
|
|
||||||
if last_watch_position:
|
if last_watch_position:
|
||||||
index_entry.last_watch_position = last_watch_position
|
index_entry.last_watch_position = last_watch_position
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from typing import Optional
|
|||||||
from ....core.config.model import AppConfig
|
from ....core.config.model import AppConfig
|
||||||
from ....libs.api.base import BaseApiClient
|
from ....libs.api.base import BaseApiClient
|
||||||
from ....libs.api.params import UpdateListEntryParams
|
from ....libs.api.params import UpdateListEntryParams
|
||||||
from ....libs.api.types import MediaItem, UserListStatusType
|
from ....libs.api.types import MediaItem, UserMediaListStatus
|
||||||
from ....libs.players.types import PlayerResult
|
from ....libs.players.types import PlayerResult
|
||||||
from ..registry import MediaRegistryService
|
from ..registry import MediaRegistryService
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ class WatchHistoryService:
|
|||||||
self,
|
self,
|
||||||
media_item: MediaItem,
|
media_item: MediaItem,
|
||||||
progress: Optional[str] = None,
|
progress: Optional[str] = None,
|
||||||
status: Optional[UserListStatusType] = None,
|
status: Optional[UserMediaListStatus] = None,
|
||||||
score: Optional[float] = None,
|
score: Optional[float] = None,
|
||||||
notes: Optional[str] = None,
|
notes: Optional[str] = None,
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -1,21 +1,16 @@
|
|||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from io import StringIO
|
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from rich.console import Console
|
|
||||||
from rich.panel import Panel
|
|
||||||
from rich.text import Text
|
|
||||||
|
|
||||||
from ...core.config import AppConfig
|
from ...core.config import AppConfig
|
||||||
from ...core.constants import APP_CACHE_DIR, APP_DIR, PLATFORM
|
from ...core.constants import APP_CACHE_DIR, APP_DIR, PLATFORM
|
||||||
from ...core.utils.file import AtomicWriter
|
from ...core.utils.file import AtomicWriter
|
||||||
from ...libs.api.types import MediaItem, StreamingEpisode
|
from ...libs.api.types import MediaItem
|
||||||
from . import ansi, formatters
|
from . import ansi, formatters
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -76,8 +71,8 @@ def _populate_info_template(item: MediaItem, config: AppConfig) -> str:
|
|||||||
# plain text
|
# plain text
|
||||||
#
|
#
|
||||||
"TITLE": formatters.shell_safe(item.title.english or item.title.romaji),
|
"TITLE": formatters.shell_safe(item.title.english or item.title.romaji),
|
||||||
"STATUS": formatters.shell_safe(item.status),
|
"STATUS": formatters.shell_safe(item.status.value),
|
||||||
"FORMAT": formatters.shell_safe(item.format),
|
"FORMAT": formatters.shell_safe(item.format.value),
|
||||||
#
|
#
|
||||||
# numerical
|
# numerical
|
||||||
#
|
#
|
||||||
@@ -100,10 +95,10 @@ def _populate_info_template(item: MediaItem, config: AppConfig) -> str:
|
|||||||
# list
|
# list
|
||||||
#
|
#
|
||||||
"GENRES": formatters.shell_safe(
|
"GENRES": formatters.shell_safe(
|
||||||
formatters.format_list_with_commas(item.genres)
|
formatters.format_list_with_commas([v.value for v in item.genres])
|
||||||
),
|
),
|
||||||
"TAGS": formatters.shell_safe(
|
"TAGS": formatters.shell_safe(
|
||||||
formatters.format_list_with_commas([t.name for t in item.tags])
|
formatters.format_list_with_commas([t.name.value for t in item.tags])
|
||||||
),
|
),
|
||||||
"STUDIOS": formatters.shell_safe(
|
"STUDIOS": formatters.shell_safe(
|
||||||
formatters.format_list_with_commas([t.name for t in item.studios if t.name])
|
formatters.format_list_with_commas([t.name for t in item.studios if t.name])
|
||||||
@@ -115,7 +110,9 @@ def _populate_info_template(item: MediaItem, config: AppConfig) -> str:
|
|||||||
# user
|
# user
|
||||||
#
|
#
|
||||||
"USER_STATUS": formatters.shell_safe(
|
"USER_STATUS": formatters.shell_safe(
|
||||||
item.user_status.status if item.user_status else "NOT_ON_LIST"
|
item.user_status.status.value
|
||||||
|
if item.user_status and item.user_status.status
|
||||||
|
else "NOT_ON_LIST"
|
||||||
),
|
),
|
||||||
"USER_PROGRESS": formatters.shell_safe(
|
"USER_PROGRESS": formatters.shell_safe(
|
||||||
f"Episode {item.user_status.progress}" if item.user_status else "0"
|
f"Episode {item.user_status.progress}" if item.user_status else "0"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Literal
|
from typing import Literal
|
||||||
|
|
||||||
from pydantic import BaseModel, Field, PrivateAttr, computed_field, field_validator
|
from pydantic import BaseModel, Field, PrivateAttr, computed_field, field_validator
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ from ...core.constants import (
|
|||||||
ROFI_THEME_MAIN,
|
ROFI_THEME_MAIN,
|
||||||
ROFI_THEME_PREVIEW,
|
ROFI_THEME_PREVIEW,
|
||||||
)
|
)
|
||||||
from ...libs.api.anilist.constants import MEDIA_LIST_SORTS, SORTS_AVAILABLE
|
from ...libs.api.types import MediaSort, UserMediaListSort
|
||||||
from ...libs.providers.anime import PROVIDERS_AVAILABLE, SERVERS_AVAILABLE
|
from ...libs.providers.anime import PROVIDERS_AVAILABLE, SERVERS_AVAILABLE
|
||||||
from ..constants import APP_ASCII_ART
|
from ..constants import APP_ASCII_ART
|
||||||
from . import defaults
|
from . import defaults
|
||||||
@@ -21,12 +21,6 @@ from . import descriptions as desc
|
|||||||
class GeneralConfig(BaseModel):
|
class GeneralConfig(BaseModel):
|
||||||
"""Configuration for general application behavior and integrations."""
|
"""Configuration for general application behavior and integrations."""
|
||||||
|
|
||||||
per_page: int = Field(
|
|
||||||
default=defaults.ANILIST_PER_PAGE,
|
|
||||||
gt=0,
|
|
||||||
le=50,
|
|
||||||
description=desc.ANILIST_PER_PAGE,
|
|
||||||
)
|
|
||||||
pygment_style: str = Field(
|
pygment_style: str = Field(
|
||||||
default=defaults.GENERAL_PYGMENT_STYLE, description=desc.GENERAL_PYGMENT_STYLE
|
default=defaults.GENERAL_PYGMENT_STYLE, description=desc.GENERAL_PYGMENT_STYLE
|
||||||
)
|
)
|
||||||
@@ -311,44 +305,41 @@ class AnilistConfig(OtherConfig):
|
|||||||
le=50,
|
le=50,
|
||||||
description=desc.ANILIST_PER_PAGE,
|
description=desc.ANILIST_PER_PAGE,
|
||||||
)
|
)
|
||||||
sort_by: str = Field(
|
sort_by: MediaSort = Field(
|
||||||
default=defaults.ANILIST_SORT_BY,
|
default=MediaSort.SEARCH_MATCH,
|
||||||
description=desc.ANILIST_SORT_BY,
|
description=desc.ANILIST_SORT_BY,
|
||||||
examples=SORTS_AVAILABLE,
|
|
||||||
)
|
)
|
||||||
media_list_sort_by: str = Field(
|
media_list_sort_by: UserMediaListSort = Field(
|
||||||
default=defaults.ANILIST_MEDIA_LIST_SORT_BY,
|
default=UserMediaListSort.MEDIA_POPULARITY_DESC,
|
||||||
description=desc.ANILIST_MEDIA_LIST_SORT_BY,
|
description=desc.ANILIST_MEDIA_LIST_SORT_BY,
|
||||||
examples=MEDIA_LIST_SORTS,
|
|
||||||
)
|
)
|
||||||
preferred_language: Literal["english", "romaji"] = Field(
|
preferred_language: Literal["english", "romaji"] = Field(
|
||||||
default=defaults.ANILIST_PREFERRED_LANGUAGE,
|
default=defaults.ANILIST_PREFERRED_LANGUAGE,
|
||||||
description=desc.ANILIST_PREFERRED_LANGUAGE,
|
description=desc.ANILIST_PREFERRED_LANGUAGE,
|
||||||
)
|
)
|
||||||
|
|
||||||
@field_validator("sort_by")
|
|
||||||
@classmethod
|
|
||||||
def validate_sort_by(cls, v: str) -> str:
|
|
||||||
if v not in SORTS_AVAILABLE:
|
|
||||||
raise ValueError(
|
|
||||||
f"'{v}' is not a valid sort option. See documentation for available options."
|
|
||||||
)
|
|
||||||
return v
|
|
||||||
|
|
||||||
@field_validator("media_list_sort_by")
|
|
||||||
@classmethod
|
|
||||||
def validate_media_list_sort_by(cls, v: str) -> str:
|
|
||||||
if v not in MEDIA_LIST_SORTS:
|
|
||||||
raise ValueError(
|
|
||||||
f"'{v}' is not a valid sort option. See documentation for available options."
|
|
||||||
)
|
|
||||||
return v
|
|
||||||
|
|
||||||
|
|
||||||
class JikanConfig(OtherConfig):
|
class JikanConfig(OtherConfig):
|
||||||
"""Configuration for the Jikan API (currently none)."""
|
"""Configuration for the Jikan API (currently none)."""
|
||||||
|
|
||||||
pass
|
per_page: int = Field(
|
||||||
|
default=defaults.ANILIST_PER_PAGE,
|
||||||
|
gt=0,
|
||||||
|
le=50,
|
||||||
|
description=desc.ANILIST_PER_PAGE,
|
||||||
|
)
|
||||||
|
sort_by: MediaSort = Field(
|
||||||
|
default=MediaSort.SEARCH_MATCH,
|
||||||
|
description=desc.ANILIST_SORT_BY,
|
||||||
|
)
|
||||||
|
media_list_sort_by: UserMediaListSort = Field(
|
||||||
|
default=UserMediaListSort.MEDIA_POPULARITY_DESC,
|
||||||
|
description=desc.ANILIST_MEDIA_LIST_SORT_BY,
|
||||||
|
)
|
||||||
|
preferred_language: Literal["english", "romaji"] = Field(
|
||||||
|
default=defaults.ANILIST_PREFERRED_LANGUAGE,
|
||||||
|
description=desc.ANILIST_PREFERRED_LANGUAGE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DownloadsConfig(OtherConfig):
|
class DownloadsConfig(OtherConfig):
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
from enum import Enum
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
from httpx import Client
|
from httpx import Client
|
||||||
|
|
||||||
@@ -8,20 +9,20 @@ from ....core.utils.graphql import (
|
|||||||
execute_graphql,
|
execute_graphql,
|
||||||
)
|
)
|
||||||
from ..base import ApiSearchParams, BaseApiClient, UpdateListEntryParams, UserListParams
|
from ..base import ApiSearchParams, BaseApiClient, UpdateListEntryParams, UserListParams
|
||||||
from ..types import MediaSearchResult, UserProfile
|
from ..types import MediaSearchResult, UserMediaListStatus, UserProfile
|
||||||
from . import gql, mapper
|
from . import gql, mapper
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
ANILIST_ENDPOINT = "https://graphql.anilist.co"
|
ANILIST_ENDPOINT = "https://graphql.anilist.co"
|
||||||
|
|
||||||
|
|
||||||
status_map = {
|
user_list_status_map = {
|
||||||
"watching": "CURRENT",
|
UserMediaListStatus.WATCHING: "CURRENT",
|
||||||
"planning": "PLANNING",
|
UserMediaListStatus.PLANNING: "PLANNING",
|
||||||
"completed": "COMPLETED",
|
UserMediaListStatus.COMPLETED: "COMPLETED",
|
||||||
"dropped": "DROPPED",
|
UserMediaListStatus.DROPPED: "DROPPED",
|
||||||
"paused": "PAUSED",
|
UserMediaListStatus.PAUSED: "PAUSED",
|
||||||
"repeating": "REPEATING",
|
UserMediaListStatus.REPEATING: "REPEATING",
|
||||||
}
|
}
|
||||||
|
|
||||||
# TODO: Just remove and have consistent variable naming between the two
|
# TODO: Just remove and have consistent variable naming between the two
|
||||||
@@ -86,15 +87,40 @@ class AniListApi(BaseApiClient):
|
|||||||
|
|
||||||
def search_media(self, params: ApiSearchParams) -> Optional[MediaSearchResult]:
|
def search_media(self, params: ApiSearchParams) -> Optional[MediaSearchResult]:
|
||||||
variables = {
|
variables = {
|
||||||
search_params_map[k]: v for k, v in params.__dict__.items() if v is not None
|
search_params_map[k]: v
|
||||||
|
for k, v in params.__dict__.items()
|
||||||
|
if v is not None and not isinstance(v, Enum)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# handle case where value is an enum
|
||||||
|
variables.update(
|
||||||
|
{
|
||||||
|
search_params_map[k]: v.value
|
||||||
|
for k, v in params.__dict__.items()
|
||||||
|
if v is not None and isinstance(v, Enum)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# handle case where is a list of enums
|
||||||
|
variables.update(
|
||||||
|
{
|
||||||
|
search_params_map[k]: list(map(lambda item: item.value, v))
|
||||||
|
for k, v in params.__dict__.items()
|
||||||
|
if v is not None and isinstance(v, list)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
variables["per_page"] = params.per_page or self.config.per_page
|
variables["per_page"] = params.per_page or self.config.per_page
|
||||||
|
|
||||||
# ignore hentai by default
|
# ignore hentai by default
|
||||||
variables["genre_not_in"] = params.genre_not_in or ["Hentai"]
|
variables["genre_not_in"] = (
|
||||||
|
list(map(lambda item: item.value, params.genre_not_in))
|
||||||
|
if params.genre_not_in
|
||||||
|
else ["Hentai"]
|
||||||
|
)
|
||||||
|
|
||||||
# anime by default
|
# anime by default
|
||||||
variables["type"] = params.type or "ANIME"
|
variables["type"] = params.type.value if params.type else "ANIME"
|
||||||
response = execute_graphql(
|
response = execute_graphql(
|
||||||
ANILIST_ENDPOINT, self.http_client, gql.SEARCH_MEDIA, variables
|
ANILIST_ENDPOINT, self.http_client, gql.SEARCH_MEDIA, variables
|
||||||
)
|
)
|
||||||
@@ -108,12 +134,14 @@ class AniListApi(BaseApiClient):
|
|||||||
# TODO: use consistent variable naming btw graphql and params
|
# TODO: use consistent variable naming btw graphql and params
|
||||||
# so variables can be dynamically filled
|
# so variables can be dynamically filled
|
||||||
variables = {
|
variables = {
|
||||||
"sort": params.sort or self.config.media_list_sort_by,
|
"sort": params.sort.value
|
||||||
|
if params.sort
|
||||||
|
else self.config.media_list_sort_by,
|
||||||
"userId": self.user_profile.id,
|
"userId": self.user_profile.id,
|
||||||
"status": status_map[params.status] if params.status else None,
|
"status": user_list_status_map[params.status] if params.status else None,
|
||||||
"page": params.page,
|
"page": params.page,
|
||||||
"perPage": params.per_page or self.config.per_page,
|
"perPage": params.per_page or self.config.per_page,
|
||||||
"type": params.type or "ANIME",
|
"type": params.type.value if params.type else "ANIME",
|
||||||
}
|
}
|
||||||
response = execute_graphql(
|
response = execute_graphql(
|
||||||
ANILIST_ENDPOINT, self.http_client, gql.GET_USER_MEDIA_LIST, variables
|
ANILIST_ENDPOINT, self.http_client, gql.GET_USER_MEDIA_LIST, variables
|
||||||
@@ -126,7 +154,7 @@ class AniListApi(BaseApiClient):
|
|||||||
score_raw = int(params.score * 10) if params.score is not None else None
|
score_raw = int(params.score * 10) if params.score is not None else None
|
||||||
variables = {
|
variables = {
|
||||||
"mediaId": params.media_id,
|
"mediaId": params.media_id,
|
||||||
"status": status_map[params.status] if params.status else None,
|
"status": user_list_status_map[params.status] if params.status else None,
|
||||||
"progress": int(float(params.progress)) if params.progress else None,
|
"progress": int(float(params.progress)) if params.progress else None,
|
||||||
"scoreRaw": score_raw,
|
"scoreRaw": score_raw,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,515 +0,0 @@
|
|||||||
SORTS_AVAILABLE = [
|
|
||||||
"ID",
|
|
||||||
"ID_DESC",
|
|
||||||
"TITLE_ROMAJI",
|
|
||||||
"TITLE_ROMAJI_DESC",
|
|
||||||
"TITLE_ENGLISH",
|
|
||||||
"TITLE_ENGLISH_DESC",
|
|
||||||
"TITLE_NATIVE",
|
|
||||||
"TITLE_NATIVE_DESC",
|
|
||||||
"TYPE",
|
|
||||||
"TYPE_DESC",
|
|
||||||
"FORMAT",
|
|
||||||
"FORMAT_DESC",
|
|
||||||
"START_DATE",
|
|
||||||
"START_DATE_DESC",
|
|
||||||
"END_DATE",
|
|
||||||
"END_DATE_DESC",
|
|
||||||
"SCORE",
|
|
||||||
"SCORE_DESC",
|
|
||||||
"POPULARITY",
|
|
||||||
"POPULARITY_DESC",
|
|
||||||
"TRENDING",
|
|
||||||
"TRENDING_DESC",
|
|
||||||
"EPISODES",
|
|
||||||
"EPISODES_DESC",
|
|
||||||
"DURATION",
|
|
||||||
"DURATION_DESC",
|
|
||||||
"STATUS",
|
|
||||||
"STATUS_DESC",
|
|
||||||
"CHAPTERS",
|
|
||||||
"CHAPTERS_DESC",
|
|
||||||
"VOLUMES",
|
|
||||||
"VOLUMES_DESC",
|
|
||||||
"UPDATED_AT",
|
|
||||||
"UPDATED_AT_DESC",
|
|
||||||
"SEARCH_MATCH",
|
|
||||||
"FAVOURITES",
|
|
||||||
"FAVOURITES_DESC",
|
|
||||||
]
|
|
||||||
|
|
||||||
MEDIA_LIST_SORTS = [
|
|
||||||
"MEDIA_ID",
|
|
||||||
"MEDIA_ID_DESC",
|
|
||||||
"SCORE",
|
|
||||||
"SCORE_DESC",
|
|
||||||
"STATUS",
|
|
||||||
"STATUS_DESC",
|
|
||||||
"PROGRESS",
|
|
||||||
"PROGRESS_DESC",
|
|
||||||
"PROGRESS_VOLUMES",
|
|
||||||
"PROGRESS_VOLUMES_DESC",
|
|
||||||
"REPEAT",
|
|
||||||
"REPEAT_DESC",
|
|
||||||
"PRIORITY",
|
|
||||||
"PRIORITY_DESC",
|
|
||||||
"STARTED_ON",
|
|
||||||
"STARTED_ON_DESC",
|
|
||||||
"FINISHED_ON",
|
|
||||||
"FINISHED_ON_DESC",
|
|
||||||
"ADDED_TIME",
|
|
||||||
"ADDED_TIME_DESC",
|
|
||||||
"UPDATED_TIME",
|
|
||||||
"UPDATED_TIME_DESC",
|
|
||||||
"MEDIA_TITLE_ROMAJI",
|
|
||||||
"MEDIA_TITLE_ROMAJI_DESC",
|
|
||||||
"MEDIA_TITLE_ENGLISH",
|
|
||||||
"MEDIA_TITLE_ENGLISH_DESC",
|
|
||||||
"MEDIA_TITLE_NATIVE",
|
|
||||||
"MEDIA_TITLE_NATIVE_DESC",
|
|
||||||
"MEDIA_POPULARITY",
|
|
||||||
"MEDIA_POPULARITY_DESC",
|
|
||||||
"MEDIA_SCORE",
|
|
||||||
"MEDIA_SCORE_DESC",
|
|
||||||
"MEDIA_START_DATE",
|
|
||||||
"MEDIA_START_DATE_DESC",
|
|
||||||
"MEDIA_RATING",
|
|
||||||
"MEDIA_RATING_DESC",
|
|
||||||
]
|
|
||||||
media_statuses_available = [
|
|
||||||
"FINISHED",
|
|
||||||
"RELEASING",
|
|
||||||
"NOT_YET_RELEASED",
|
|
||||||
"CANCELLED",
|
|
||||||
"HIATUS",
|
|
||||||
]
|
|
||||||
seasons_available = ["WINTER", "SPRING", "SUMMER", "FALL"]
|
|
||||||
genres_available = [
|
|
||||||
"Action",
|
|
||||||
"Adventure",
|
|
||||||
"Comedy",
|
|
||||||
"Drama",
|
|
||||||
"Ecchi",
|
|
||||||
"Fantasy",
|
|
||||||
"Horror",
|
|
||||||
"Mahou Shoujo",
|
|
||||||
"Mecha",
|
|
||||||
"Music",
|
|
||||||
"Mystery",
|
|
||||||
"Psychological",
|
|
||||||
"Romance",
|
|
||||||
"Sci-Fi",
|
|
||||||
"Slice of Life",
|
|
||||||
"Sports",
|
|
||||||
"Supernatural",
|
|
||||||
"Thriller",
|
|
||||||
"Hentai",
|
|
||||||
]
|
|
||||||
media_formats_available = [
|
|
||||||
"TV",
|
|
||||||
"TV_SHORT",
|
|
||||||
"MOVIE",
|
|
||||||
"SPECIAL",
|
|
||||||
"OVA",
|
|
||||||
"MUSIC",
|
|
||||||
"NOVEL",
|
|
||||||
"ONE_SHOT",
|
|
||||||
]
|
|
||||||
years_available = [
|
|
||||||
"1900",
|
|
||||||
"1910",
|
|
||||||
"1920",
|
|
||||||
"1930",
|
|
||||||
"1940",
|
|
||||||
"1950",
|
|
||||||
"1960",
|
|
||||||
"1970",
|
|
||||||
"1980",
|
|
||||||
"1990",
|
|
||||||
"2000",
|
|
||||||
"2004",
|
|
||||||
"2005",
|
|
||||||
"2006",
|
|
||||||
"2007",
|
|
||||||
"2008",
|
|
||||||
"2009",
|
|
||||||
"2010",
|
|
||||||
"2011",
|
|
||||||
"2012",
|
|
||||||
"2013",
|
|
||||||
"2014",
|
|
||||||
"2015",
|
|
||||||
"2016",
|
|
||||||
"2017",
|
|
||||||
"2018",
|
|
||||||
"2019",
|
|
||||||
"2020",
|
|
||||||
"2021",
|
|
||||||
"2022",
|
|
||||||
"2023",
|
|
||||||
"2024",
|
|
||||||
"2025",
|
|
||||||
]
|
|
||||||
|
|
||||||
tags_available = {
|
|
||||||
"Cast": ["Polyamorous"],
|
|
||||||
"Cast Main Cast": [
|
|
||||||
"Anti-Hero",
|
|
||||||
"Elderly Protagonist",
|
|
||||||
"Ensemble Cast",
|
|
||||||
"Estranged Family",
|
|
||||||
"Female Protagonist",
|
|
||||||
"Male Protagonist",
|
|
||||||
"Primarily Adult Cast",
|
|
||||||
"Primarily Animal Cast",
|
|
||||||
"Primarily Child Cast",
|
|
||||||
"Primarily Female Cast",
|
|
||||||
"Primarily Male Cast",
|
|
||||||
"Primarily Teen Cast",
|
|
||||||
],
|
|
||||||
"Cast Traits": [
|
|
||||||
"Age Regression",
|
|
||||||
"Agender",
|
|
||||||
"Aliens",
|
|
||||||
"Amnesia",
|
|
||||||
"Angels",
|
|
||||||
"Anthropomorphism",
|
|
||||||
"Aromantic",
|
|
||||||
"Arranged Marriage",
|
|
||||||
"Artificial Intelligence",
|
|
||||||
"Asexual",
|
|
||||||
"Butler",
|
|
||||||
"Centaur",
|
|
||||||
"Chimera",
|
|
||||||
"Chuunibyou",
|
|
||||||
"Clone",
|
|
||||||
"Cosplay",
|
|
||||||
"Cowboys",
|
|
||||||
"Crossdressing",
|
|
||||||
"Cyborg",
|
|
||||||
"Delinquents",
|
|
||||||
"Demons",
|
|
||||||
"Detective",
|
|
||||||
"Dinosaurs",
|
|
||||||
"Disability",
|
|
||||||
"Dissociative Identities",
|
|
||||||
"Dragons",
|
|
||||||
"Dullahan",
|
|
||||||
"Elf",
|
|
||||||
"Fairy",
|
|
||||||
"Femboy",
|
|
||||||
"Ghost",
|
|
||||||
"Goblin",
|
|
||||||
"Gods",
|
|
||||||
"Gyaru",
|
|
||||||
"Hikikomori",
|
|
||||||
"Homeless",
|
|
||||||
"Idol",
|
|
||||||
"Kemonomimi",
|
|
||||||
"Kuudere",
|
|
||||||
"Maids",
|
|
||||||
"Mermaid",
|
|
||||||
"Monster Boy",
|
|
||||||
"Monster Girl",
|
|
||||||
"Nekomimi",
|
|
||||||
"Ninja",
|
|
||||||
"Nudity",
|
|
||||||
"Nun",
|
|
||||||
"Office Lady",
|
|
||||||
"Oiran",
|
|
||||||
"Ojou-sama",
|
|
||||||
"Orphan",
|
|
||||||
"Pirates",
|
|
||||||
"Robots",
|
|
||||||
"Samurai",
|
|
||||||
"Shrine Maiden",
|
|
||||||
"Skeleton",
|
|
||||||
"Succubus",
|
|
||||||
"Tanned Skin",
|
|
||||||
"Teacher",
|
|
||||||
"Tomboy",
|
|
||||||
"Transgender",
|
|
||||||
"Tsundere",
|
|
||||||
"Twins",
|
|
||||||
"Vampire",
|
|
||||||
"Veterinarian",
|
|
||||||
"Vikings",
|
|
||||||
"Villainess",
|
|
||||||
"VTuber",
|
|
||||||
"Werewolf",
|
|
||||||
"Witch",
|
|
||||||
"Yandere",
|
|
||||||
"Zombie",
|
|
||||||
],
|
|
||||||
"Demographic": ["Josei", "Kids", "Seinen", "Shoujo", "Shounen"],
|
|
||||||
"Setting": ["Matriarchy"],
|
|
||||||
"Setting Scene": [
|
|
||||||
"Bar",
|
|
||||||
"Boarding School",
|
|
||||||
"Circus",
|
|
||||||
"Coastal",
|
|
||||||
"College",
|
|
||||||
"Desert",
|
|
||||||
"Dungeon",
|
|
||||||
"Foreign",
|
|
||||||
"Inn",
|
|
||||||
"Konbini",
|
|
||||||
"Natural Disaster",
|
|
||||||
"Office",
|
|
||||||
"Outdoor",
|
|
||||||
"Prison",
|
|
||||||
"Restaurant",
|
|
||||||
"Rural",
|
|
||||||
"School",
|
|
||||||
"School Club",
|
|
||||||
"Snowscape",
|
|
||||||
"Urban",
|
|
||||||
"Work",
|
|
||||||
],
|
|
||||||
"Setting Time": [
|
|
||||||
"Achronological Order",
|
|
||||||
"Anachronism",
|
|
||||||
"Ancient China",
|
|
||||||
"Dystopian",
|
|
||||||
"Historical",
|
|
||||||
"Time Skip",
|
|
||||||
],
|
|
||||||
"Setting Universe": [
|
|
||||||
"Afterlife",
|
|
||||||
"Alternate Universe",
|
|
||||||
"Augmented Reality",
|
|
||||||
"Omegaverse",
|
|
||||||
"Post-Apocalyptic",
|
|
||||||
"Space",
|
|
||||||
"Urban Fantasy",
|
|
||||||
"Virtual World",
|
|
||||||
],
|
|
||||||
"Technical": [
|
|
||||||
"4-koma",
|
|
||||||
"Achromatic",
|
|
||||||
"Advertisement",
|
|
||||||
"Anthology",
|
|
||||||
"CGI",
|
|
||||||
"Episodic",
|
|
||||||
"Flash",
|
|
||||||
"Full CGI",
|
|
||||||
"Full Color",
|
|
||||||
"No Dialogue",
|
|
||||||
"Non-fiction",
|
|
||||||
"POV",
|
|
||||||
"Puppetry",
|
|
||||||
"Rotoscoping",
|
|
||||||
"Stop Motion",
|
|
||||||
],
|
|
||||||
"Theme Action": [
|
|
||||||
"Archery",
|
|
||||||
"Battle Royale",
|
|
||||||
"Espionage",
|
|
||||||
"Fugitive",
|
|
||||||
"Guns",
|
|
||||||
"Martial Arts",
|
|
||||||
"Spearplay",
|
|
||||||
"Swordplay",
|
|
||||||
],
|
|
||||||
"Theme Arts": [
|
|
||||||
"Acting",
|
|
||||||
"Calligraphy",
|
|
||||||
"Classic Literature",
|
|
||||||
"Drawing",
|
|
||||||
"Fashion",
|
|
||||||
"Food",
|
|
||||||
"Makeup",
|
|
||||||
"Photography",
|
|
||||||
"Rakugo",
|
|
||||||
"Writing",
|
|
||||||
],
|
|
||||||
"Theme Arts-Music": [
|
|
||||||
"Band",
|
|
||||||
"Classical Music",
|
|
||||||
"Dancing",
|
|
||||||
"Hip-hop Music",
|
|
||||||
"Jazz Music",
|
|
||||||
"Metal Music",
|
|
||||||
"Musical Theater",
|
|
||||||
"Rock Music",
|
|
||||||
],
|
|
||||||
"Theme Comedy": ["Parody", "Satire", "Slapstick", "Surreal Comedy"],
|
|
||||||
"Theme Drama": [
|
|
||||||
"Bullying",
|
|
||||||
"Class Struggle",
|
|
||||||
"Coming of Age",
|
|
||||||
"Conspiracy",
|
|
||||||
"Eco-Horror",
|
|
||||||
"Fake Relationship",
|
|
||||||
"Kingdom Management",
|
|
||||||
"Rehabilitation",
|
|
||||||
"Revenge",
|
|
||||||
"Suicide",
|
|
||||||
"Tragedy",
|
|
||||||
],
|
|
||||||
"Theme Fantasy": [
|
|
||||||
"Alchemy",
|
|
||||||
"Body Swapping",
|
|
||||||
"Cultivation",
|
|
||||||
"Fairy Tale",
|
|
||||||
"Henshin",
|
|
||||||
"Isekai",
|
|
||||||
"Kaiju",
|
|
||||||
"Magic",
|
|
||||||
"Mythology",
|
|
||||||
"Necromancy",
|
|
||||||
"Shapeshifting",
|
|
||||||
"Steampunk",
|
|
||||||
"Super Power",
|
|
||||||
"Superhero",
|
|
||||||
"Wuxia",
|
|
||||||
"Youkai",
|
|
||||||
],
|
|
||||||
"Theme Game": ["Board Game", "E-Sports", "Video Games"],
|
|
||||||
"Theme Game-Card & Board Game": [
|
|
||||||
"Card Battle",
|
|
||||||
"Go",
|
|
||||||
"Karuta",
|
|
||||||
"Mahjong",
|
|
||||||
"Poker",
|
|
||||||
"Shogi",
|
|
||||||
],
|
|
||||||
"Theme Game-Sport": [
|
|
||||||
"Acrobatics",
|
|
||||||
"Airsoft",
|
|
||||||
"American Football",
|
|
||||||
"Athletics",
|
|
||||||
"Badminton",
|
|
||||||
"Baseball",
|
|
||||||
"Basketball",
|
|
||||||
"Bowling",
|
|
||||||
"Boxing",
|
|
||||||
"Cheerleading",
|
|
||||||
"Cycling",
|
|
||||||
"Fencing",
|
|
||||||
"Fishing",
|
|
||||||
"Fitness",
|
|
||||||
"Football",
|
|
||||||
"Golf",
|
|
||||||
"Handball",
|
|
||||||
"Ice Skating",
|
|
||||||
"Judo",
|
|
||||||
"Lacrosse",
|
|
||||||
"Parkour",
|
|
||||||
"Rugby",
|
|
||||||
"Scuba Diving",
|
|
||||||
"Skateboarding",
|
|
||||||
"Sumo",
|
|
||||||
"Surfing",
|
|
||||||
"Swimming",
|
|
||||||
"Table Tennis",
|
|
||||||
"Tennis",
|
|
||||||
"Volleyball",
|
|
||||||
"Wrestling",
|
|
||||||
],
|
|
||||||
"Theme Other": [
|
|
||||||
"Adoption",
|
|
||||||
"Animals",
|
|
||||||
"Astronomy",
|
|
||||||
"Autobiographical",
|
|
||||||
"Biographical",
|
|
||||||
"Body Horror",
|
|
||||||
"Cannibalism",
|
|
||||||
"Chibi",
|
|
||||||
"Cosmic Horror",
|
|
||||||
"Crime",
|
|
||||||
"Crossover",
|
|
||||||
"Death Game",
|
|
||||||
"Denpa",
|
|
||||||
"Drugs",
|
|
||||||
"Economics",
|
|
||||||
"Educational",
|
|
||||||
"Environmental",
|
|
||||||
"Ero Guro",
|
|
||||||
"Filmmaking",
|
|
||||||
"Found Family",
|
|
||||||
"Gambling",
|
|
||||||
"Gender Bending",
|
|
||||||
"Gore",
|
|
||||||
"Language Barrier",
|
|
||||||
"LGBTQ+ Themes",
|
|
||||||
"Lost Civilization",
|
|
||||||
"Marriage",
|
|
||||||
"Medicine",
|
|
||||||
"Memory Manipulation",
|
|
||||||
"Meta",
|
|
||||||
"Mountaineering",
|
|
||||||
"Noir",
|
|
||||||
"Otaku Culture",
|
|
||||||
"Pandemic",
|
|
||||||
"Philosophy",
|
|
||||||
"Politics",
|
|
||||||
"Proxy Battle",
|
|
||||||
"Psychosexual",
|
|
||||||
"Reincarnation",
|
|
||||||
"Religion",
|
|
||||||
"Royal Affairs",
|
|
||||||
"Slavery",
|
|
||||||
"Software Development",
|
|
||||||
"Survival",
|
|
||||||
"Terrorism",
|
|
||||||
"Torture",
|
|
||||||
"Travel",
|
|
||||||
"War",
|
|
||||||
],
|
|
||||||
"Theme Other-Organisations": [
|
|
||||||
"Assassins",
|
|
||||||
"Criminal Organization",
|
|
||||||
"Cult",
|
|
||||||
"Firefighters",
|
|
||||||
"Gangs",
|
|
||||||
"Mafia",
|
|
||||||
"Military",
|
|
||||||
"Police",
|
|
||||||
"Triads",
|
|
||||||
"Yakuza",
|
|
||||||
],
|
|
||||||
"Theme Other-Vehicle": [
|
|
||||||
"Aviation",
|
|
||||||
"Cars",
|
|
||||||
"Mopeds",
|
|
||||||
"Motorcycles",
|
|
||||||
"Ships",
|
|
||||||
"Tanks",
|
|
||||||
"Trains",
|
|
||||||
],
|
|
||||||
"Theme Romance": [
|
|
||||||
"Age Gap",
|
|
||||||
"Bisexual",
|
|
||||||
"Boys' Love",
|
|
||||||
"Female Harem",
|
|
||||||
"Heterosexual",
|
|
||||||
"Love Triangle",
|
|
||||||
"Male Harem",
|
|
||||||
"Matchmaking",
|
|
||||||
"Mixed Gender Harem",
|
|
||||||
"Teens' Love",
|
|
||||||
"Unrequited Love",
|
|
||||||
"Yuri",
|
|
||||||
],
|
|
||||||
"Theme Sci Fi": [
|
|
||||||
"Cyberpunk",
|
|
||||||
"Space Opera",
|
|
||||||
"Time Loop",
|
|
||||||
"Time Manipulation",
|
|
||||||
"Tokusatsu",
|
|
||||||
],
|
|
||||||
"Theme Sci Fi-Mecha": ["Real Robot", "Super Robot"],
|
|
||||||
"Theme Slice of Life": [
|
|
||||||
"Agriculture",
|
|
||||||
"Cute Boys Doing Cute Things",
|
|
||||||
"Cute Girls Doing Cute Things",
|
|
||||||
"Family Life",
|
|
||||||
"Horticulture",
|
|
||||||
"Iyashikei",
|
|
||||||
"Parenthood",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
tags_available_list = []
|
|
||||||
for tag_category, tags_in_category in tags_available.items():
|
|
||||||
tags_available_list.extend(tags_in_category)
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict, List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from ....core.utils.formatting import renumber_titles, strip_original_episode_prefix
|
from ....core.utils.formatting import renumber_titles, strip_original_episode_prefix
|
||||||
from ..types import (
|
from ..types import (
|
||||||
@@ -8,13 +8,15 @@ from ..types import (
|
|||||||
MediaImage,
|
MediaImage,
|
||||||
MediaItem,
|
MediaItem,
|
||||||
MediaSearchResult,
|
MediaSearchResult,
|
||||||
MediaTag,
|
MediaStatus,
|
||||||
|
MediaTagItem,
|
||||||
MediaTitle,
|
MediaTitle,
|
||||||
MediaTrailer,
|
MediaTrailer,
|
||||||
PageInfo,
|
PageInfo,
|
||||||
StreamingEpisode,
|
StreamingEpisode,
|
||||||
Studio,
|
Studio,
|
||||||
UserListStatus,
|
UserListItem,
|
||||||
|
UserMediaListStatus,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
)
|
)
|
||||||
from .types import (
|
from .types import (
|
||||||
@@ -25,7 +27,6 @@ from .types import (
|
|||||||
AnilistImage,
|
AnilistImage,
|
||||||
AnilistMediaList,
|
AnilistMediaList,
|
||||||
AnilistMediaLists,
|
AnilistMediaLists,
|
||||||
AnilistMediaListStatus,
|
|
||||||
AnilistMediaNextAiringEpisode,
|
AnilistMediaNextAiringEpisode,
|
||||||
AnilistMediaTag,
|
AnilistMediaTag,
|
||||||
AnilistMediaTitle,
|
AnilistMediaTitle,
|
||||||
@@ -40,13 +41,19 @@ from .types import (
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
user_list_status_map = {
|
||||||
|
"CURRENT": UserMediaListStatus.WATCHING,
|
||||||
|
"PLANNING": UserMediaListStatus.PLANNING,
|
||||||
|
"COMPLETED": UserMediaListStatus.COMPLETED,
|
||||||
|
"PAUSED": UserMediaListStatus.PAUSED,
|
||||||
|
"REPEATING": UserMediaListStatus.REPEATING,
|
||||||
|
}
|
||||||
status_map = {
|
status_map = {
|
||||||
"CURRENT": "watching",
|
"FINISHED": MediaStatus.FINISHED,
|
||||||
"PLANNING": "planning",
|
"RELEASING": MediaStatus.RELEASING,
|
||||||
"COMPLETED": "completed",
|
"NOT_YET_RELEASED": MediaStatus.NOT_YET_RELEASED,
|
||||||
"DROPPED": "dropped",
|
"CANCELLED": MediaStatus.CANCELLED,
|
||||||
"PAUSED": "paused",
|
"HIATUS": MediaStatus.HIATUS,
|
||||||
"REPEATING": "repeating",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -123,10 +130,10 @@ def _to_generic_studios(anilist_studios: AnilistStudioNodes) -> List[Studio]:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def _to_generic_tags(anilist_tags: list[AnilistMediaTag]) -> List[MediaTag]:
|
def _to_generic_tags(anilist_tags: list[AnilistMediaTag]) -> List[MediaTagItem]:
|
||||||
"""Maps a list of AniList tags to generic MediaTag objects."""
|
"""Maps a list of AniList tags to generic MediaTag objects."""
|
||||||
return [
|
return [
|
||||||
MediaTag(name=t["name"], rank=t.get("rank"))
|
MediaTagItem(name=t["name"], rank=t.get("rank"))
|
||||||
for t in anilist_tags
|
for t in anilist_tags
|
||||||
if t.get("name")
|
if t.get("name")
|
||||||
]
|
]
|
||||||
@@ -200,11 +207,11 @@ def _to_generic_streaming_episodes(
|
|||||||
def _to_generic_user_status(
|
def _to_generic_user_status(
|
||||||
anilist_media: AnilistBaseMediaDataSchema,
|
anilist_media: AnilistBaseMediaDataSchema,
|
||||||
anilist_list_entry: Optional[AnilistMediaList],
|
anilist_list_entry: Optional[AnilistMediaList],
|
||||||
) -> Optional[UserListStatus]:
|
) -> Optional[UserListItem]:
|
||||||
"""Maps an AniList mediaListEntry to a generic UserListStatus."""
|
"""Maps an AniList mediaListEntry to a generic UserListStatus."""
|
||||||
if anilist_list_entry:
|
if anilist_list_entry:
|
||||||
return UserListStatus(
|
return UserListItem(
|
||||||
status=status_map[anilist_list_entry["status"]], # pyright: ignore
|
status=user_list_status_map[anilist_list_entry["status"]],
|
||||||
progress=anilist_list_entry["progress"],
|
progress=anilist_list_entry["progress"],
|
||||||
score=anilist_list_entry["score"],
|
score=anilist_list_entry["score"],
|
||||||
repeat=anilist_list_entry["repeat"],
|
repeat=anilist_list_entry["repeat"],
|
||||||
@@ -218,9 +225,9 @@ def _to_generic_user_status(
|
|||||||
if not anilist_media["mediaListEntry"]:
|
if not anilist_media["mediaListEntry"]:
|
||||||
return
|
return
|
||||||
|
|
||||||
return UserListStatus(
|
return UserListItem(
|
||||||
id=anilist_media["mediaListEntry"]["id"],
|
id=anilist_media["mediaListEntry"]["id"],
|
||||||
status=status_map[anilist_media["mediaListEntry"]["status"]] # pyright: ignore
|
status=user_list_status_map[anilist_media["mediaListEntry"]["status"]]
|
||||||
if anilist_media["mediaListEntry"]["status"]
|
if anilist_media["mediaListEntry"]["status"]
|
||||||
else None,
|
else None,
|
||||||
progress=anilist_media["mediaListEntry"]["progress"],
|
progress=anilist_media["mediaListEntry"]["progress"],
|
||||||
@@ -236,7 +243,7 @@ def _to_generic_media_item(
|
|||||||
id_mal=data.get("idMal"),
|
id_mal=data.get("idMal"),
|
||||||
type=data.get("type", "ANIME"),
|
type=data.get("type", "ANIME"),
|
||||||
title=_to_generic_media_title(data["title"]),
|
title=_to_generic_media_title(data["title"]),
|
||||||
status=data["status"],
|
status=status_map[data["status"]],
|
||||||
format=data.get("format"),
|
format=data.get("format"),
|
||||||
cover_image=_to_generic_media_image(data["coverImage"]),
|
cover_image=_to_generic_media_image(data["coverImage"]),
|
||||||
banner_image=data.get("bannerImage"),
|
banner_image=data.get("bannerImage"),
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
"""
|
|
||||||
This module defines the shape of the anilist data that can be received in order to enhance dev experience
|
|
||||||
"""
|
|
||||||
|
|
||||||
# TODO: rename this module to types
|
|
||||||
|
|
||||||
from typing import Literal, TypedDict
|
from typing import Literal, TypedDict
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ from ..types import (
|
|||||||
MediaItem,
|
MediaItem,
|
||||||
MediaSearchResult,
|
MediaSearchResult,
|
||||||
MediaStatus,
|
MediaStatus,
|
||||||
MediaTag,
|
MediaTagItem,
|
||||||
MediaTitle,
|
MediaTitle,
|
||||||
PageInfo,
|
PageInfo,
|
||||||
StreamingEpisode,
|
StreamingEpisode,
|
||||||
Studio,
|
Studio,
|
||||||
UserListStatus,
|
UserListItem,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ def _to_generic_title(jikan_titles: list[dict]) -> MediaTitle:
|
|||||||
romaji = None
|
romaji = None
|
||||||
english = None
|
english = None
|
||||||
native = None
|
native = None
|
||||||
|
|
||||||
# Jikan's default title is often the romaji one.
|
# Jikan's default title is often the romaji one.
|
||||||
# We prioritize specific types if available.
|
# We prioritize specific types if available.
|
||||||
for t in jikan_titles:
|
for t in jikan_titles:
|
||||||
@@ -48,12 +48,8 @@ def _to_generic_title(jikan_titles: list[dict]) -> MediaTitle:
|
|||||||
english = title_
|
english = title_
|
||||||
elif type_ == "Japanese":
|
elif type_ == "Japanese":
|
||||||
native = title_
|
native = title_
|
||||||
|
|
||||||
return MediaTitle(
|
return MediaTitle(romaji=romaji, english=english, native=native)
|
||||||
romaji=romaji,
|
|
||||||
english=english,
|
|
||||||
native=native
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _to_generic_image(jikan_images: dict) -> MediaImage:
|
def _to_generic_image(jikan_images: dict) -> MediaImage:
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import List, Literal, Optional, Union
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
from .types import UserListStatusType
|
from .types import (
|
||||||
|
MediaFormat,
|
||||||
|
MediaGenre,
|
||||||
|
MediaSeason,
|
||||||
|
MediaSort,
|
||||||
|
MediaStatus,
|
||||||
|
MediaTag,
|
||||||
|
MediaType,
|
||||||
|
UserMediaListSort,
|
||||||
|
UserMediaListStatus,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -9,23 +19,23 @@ class ApiSearchParams:
|
|||||||
query: Optional[str] = None
|
query: Optional[str] = None
|
||||||
page: int = 1
|
page: int = 1
|
||||||
per_page: Optional[int] = None
|
per_page: Optional[int] = None
|
||||||
sort: Optional[Union[str, List[str]]] = None
|
sort: Optional[Union[MediaSort, List[MediaSort]]] = None
|
||||||
|
|
||||||
# IDs
|
# IDs
|
||||||
id_in: Optional[List[int]] = None
|
id_in: Optional[List[int]] = None
|
||||||
|
|
||||||
# Genres
|
# Genres
|
||||||
genre_in: Optional[List[str]] = None
|
genre_in: Optional[List[MediaGenre]] = None
|
||||||
genre_not_in: Optional[List[str]] = None
|
genre_not_in: Optional[List[MediaGenre]] = None
|
||||||
|
|
||||||
# Tags
|
# Tags
|
||||||
tag_in: Optional[List[str]] = None
|
tag_in: Optional[List[MediaTag]] = None
|
||||||
tag_not_in: Optional[List[str]] = None
|
tag_not_in: Optional[List[MediaTag]] = None
|
||||||
|
|
||||||
# Status
|
# Status
|
||||||
status_in: Optional[List[str]] = None # Corresponds to [MediaStatus]
|
status_in: Optional[List[MediaStatus]] = None # Corresponds to [MediaStatus]
|
||||||
status: Optional[str] = None # Corresponds to MediaStatus
|
status: Optional[MediaStatus] = None # Corresponds to MediaStatus
|
||||||
status_not_in: Optional[List[str]] = None # Corresponds to [MediaStatus]
|
status_not_in: Optional[List[MediaStatus]] = None # Corresponds to [MediaStatus]
|
||||||
|
|
||||||
# Popularity
|
# Popularity
|
||||||
popularity_greater: Optional[int] = None
|
popularity_greater: Optional[int] = None
|
||||||
@@ -37,7 +47,7 @@ class ApiSearchParams:
|
|||||||
|
|
||||||
# Season and Year
|
# Season and Year
|
||||||
seasonYear: Optional[int] = None
|
seasonYear: Optional[int] = None
|
||||||
season: Optional[str] = None
|
season: Optional[MediaSeason] = None
|
||||||
|
|
||||||
# Start Date (FuzzyDateInt is often an integer representation like YYYYMMDD)
|
# Start Date (FuzzyDateInt is often an integer representation like YYYYMMDD)
|
||||||
startDate_greater: Optional[int] = None
|
startDate_greater: Optional[int] = None
|
||||||
@@ -49,8 +59,8 @@ class ApiSearchParams:
|
|||||||
endDate_lesser: Optional[int] = None
|
endDate_lesser: Optional[int] = None
|
||||||
|
|
||||||
# Format and Type
|
# Format and Type
|
||||||
format_in: Optional[List[str]] = None # Corresponds to [MediaFormat]
|
format_in: Optional[List[MediaFormat]] = None
|
||||||
type: Optional[str] = None # Corresponds to MediaType (e.g., "ANIME", "MANGA")
|
type: Optional[MediaType] = None
|
||||||
|
|
||||||
# On List
|
# On List
|
||||||
on_list: Optional[bool] = None
|
on_list: Optional[bool] = None
|
||||||
@@ -58,16 +68,16 @@ class ApiSearchParams:
|
|||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class UserListParams:
|
class UserListParams:
|
||||||
status: UserListStatusType
|
status: UserMediaListStatus
|
||||||
page: int = 1
|
page: int = 1
|
||||||
type: Optional[str] = None
|
type: Optional[MediaType] = None
|
||||||
sort: Optional[str] = None
|
sort: Optional[UserMediaListSort] = None
|
||||||
per_page: Optional[int] = None
|
per_page: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class UpdateListEntryParams:
|
class UpdateListEntryParams:
|
||||||
media_id: int
|
media_id: int
|
||||||
status: Optional[UserListStatusType] = None
|
status: Optional[UserMediaListStatus] = None
|
||||||
progress: Optional[str] = None
|
progress: Optional[str] = None
|
||||||
score: Optional[float] = None
|
score: Optional[float] = None
|
||||||
|
|||||||
@@ -1,25 +1,474 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import List, Literal, Optional
|
from enum import Enum
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
|
|
||||||
# --- Generic Enums and Type Aliases ---
|
|
||||||
|
|
||||||
MediaType = Literal["ANIME", "MANGA"]
|
|
||||||
MediaStatus = Literal[
|
|
||||||
"FINISHED", "RELEASING", "NOT_YET_RELEASED", "CANCELLED", "HIATUS"
|
|
||||||
]
|
|
||||||
|
|
||||||
UserListStatusType = Literal[
|
|
||||||
"planning", "watching", "completed", "dropped", "paused", "repeating"
|
|
||||||
]
|
|
||||||
# --- Generic Data Models ---
|
|
||||||
|
|
||||||
|
|
||||||
|
# ENUMS
|
||||||
|
class MediaStatus(Enum):
|
||||||
|
FINISHED = "FINISHED"
|
||||||
|
RELEASING = "RELEASING"
|
||||||
|
NOT_YET_RELEASED = "NOT_YET_RELEASED"
|
||||||
|
CANCELLED = "CANCELLED"
|
||||||
|
HIATUS = "HIATUS"
|
||||||
|
|
||||||
|
|
||||||
|
class MediaType(Enum):
|
||||||
|
ANIME = "ANIME"
|
||||||
|
MANGA = "MANGA"
|
||||||
|
|
||||||
|
|
||||||
|
class UserMediaListStatus(Enum):
|
||||||
|
PLANNING = "planning"
|
||||||
|
WATCHING = "watching"
|
||||||
|
COMPLETED = "completed"
|
||||||
|
DROPPED = "dropped"
|
||||||
|
PAUSED = "paused"
|
||||||
|
REPEATING = "repeating"
|
||||||
|
|
||||||
|
|
||||||
|
class MediaGenre(Enum):
|
||||||
|
ACTION = "Action"
|
||||||
|
ADVENTURE = "Adventure"
|
||||||
|
COMEDY = "Comedy"
|
||||||
|
DRAMA = "Drama"
|
||||||
|
ECCHI = "Ecchi"
|
||||||
|
FANTASY = "Fantasy"
|
||||||
|
HORROR = "Horror"
|
||||||
|
MAHOU_SHOUJO = "Mahou Shoujo"
|
||||||
|
MECHA = "Mecha"
|
||||||
|
MUSIC = "Music"
|
||||||
|
MYSTERY = "Mystery"
|
||||||
|
PSYCHOLOGICAL = "Psychological"
|
||||||
|
ROMANCE = "Romance"
|
||||||
|
SCI_FI = "Sci-Fi"
|
||||||
|
SLICE_OF_LIFE = "Slice of Life"
|
||||||
|
SPORTS = "Sports"
|
||||||
|
SUPERNATURAL = "Supernatural"
|
||||||
|
THRILLER = "Thriller"
|
||||||
|
HENTAI = "Hentai"
|
||||||
|
|
||||||
|
|
||||||
|
class MediaFormat(Enum):
|
||||||
|
TV = "TV"
|
||||||
|
TV_SHORT = "TV_SHORT"
|
||||||
|
MOVIE = "MOVIE"
|
||||||
|
SPECIAL = "SPECIAL"
|
||||||
|
OVA = "OVA"
|
||||||
|
ONA = "ONA"
|
||||||
|
MUSIC = "MUSIC"
|
||||||
|
NOVEL = "NOVEL"
|
||||||
|
ONE_SHOT = "ONE_SHOT"
|
||||||
|
|
||||||
|
|
||||||
|
class MediaTag(Enum):
|
||||||
|
# Cast
|
||||||
|
POLYAMOROUS = "Polyamorous"
|
||||||
|
|
||||||
|
# Cast Main Cast
|
||||||
|
ANTI_HERO = "Anti-Hero"
|
||||||
|
ELDERLY_PROTAGONIST = "Elderly Protagonist"
|
||||||
|
ENSEMBLE_CAST = "Ensemble Cast"
|
||||||
|
ESTRANGED_FAMILY = "Estranged Family"
|
||||||
|
FEMALE_PROTAGONIST = "Female Protagonist"
|
||||||
|
MALE_PROTAGONIST = "Male Protagonist"
|
||||||
|
PRIMARILY_ADULT_CAST = "Primarily Adult Cast"
|
||||||
|
PRIMARILY_ANIMAL_CAST = "Primarily Animal Cast"
|
||||||
|
PRIMARILY_CHILD_CAST = "Primarily Child Cast"
|
||||||
|
PRIMARILY_FEMALE_CAST = "Primarily Female Cast"
|
||||||
|
PRIMARILY_MALE_CAST = "Primarily Male Cast"
|
||||||
|
PRIMARILY_TEEN_CAST = "Primarily Teen Cast"
|
||||||
|
|
||||||
|
# Cast Traits
|
||||||
|
AGE_REGRESSION = "Age Regression"
|
||||||
|
AGENDER = "Agender"
|
||||||
|
ALIENS = "Aliens"
|
||||||
|
AMNESIA = "Amnesia"
|
||||||
|
ANGELS = "Angels"
|
||||||
|
ANTHROPOMORPHISM = "Anthropomorphism"
|
||||||
|
AROMANTIC = "Aromantic"
|
||||||
|
ARRANGED_MARRIAGE = "Arranged Marriage"
|
||||||
|
ARTIFICIAL_INTELLIGENCE = "Artificial Intelligence"
|
||||||
|
ASEXUAL = "Asexual"
|
||||||
|
BUTLER = "Butler"
|
||||||
|
CENTAUR = "Centaur"
|
||||||
|
CHIMERA = "Chimera"
|
||||||
|
CHUUNIBYOU = "Chuunibyou"
|
||||||
|
CLONE = "Clone"
|
||||||
|
COSPLAY = "Cosplay"
|
||||||
|
COWBOYS = "Cowboys"
|
||||||
|
CROSSDRESSING = "Crossdressing"
|
||||||
|
CYBORG = "Cyborg"
|
||||||
|
DELINQUENTS = "Delinquents"
|
||||||
|
DEMONS = "Demons"
|
||||||
|
DETECTIVE = "Detective"
|
||||||
|
DINOSAURS = "Dinosaurs"
|
||||||
|
DISABILITY = "Disability"
|
||||||
|
DISSOCIATIVE_IDENTITIES = "Dissociative Identities"
|
||||||
|
DRAGONS = "Dragons"
|
||||||
|
DULLAHAN = "Dullahan"
|
||||||
|
ELF = "Elf"
|
||||||
|
EXHIBITIONISM = "Exhibitionism"
|
||||||
|
FAIRY = "Fairy"
|
||||||
|
FEMBOY = "Femboy"
|
||||||
|
GHOST = "Ghost"
|
||||||
|
GOBLIN = "Goblin"
|
||||||
|
GODS = "Gods"
|
||||||
|
GYARU = "Gyaru"
|
||||||
|
HIKIKOMORI = "Hikikomori"
|
||||||
|
HOMELESS = "Homeless"
|
||||||
|
IDOL = "Idol"
|
||||||
|
INSEKI = "Inseki"
|
||||||
|
KEMONOMIMI = "Kemonomimi"
|
||||||
|
KUUDERE = "Kuudere"
|
||||||
|
MAIDS = "Maids"
|
||||||
|
MERMAID = "Mermaid"
|
||||||
|
MONSTER_BOY = "Monster Boy"
|
||||||
|
MONSTER_GIRL = "Monster Girl"
|
||||||
|
NEKOMIMI = "Nekomimi"
|
||||||
|
NINJA = "Ninja"
|
||||||
|
NUDITY = "Nudity"
|
||||||
|
NUN = "Nun"
|
||||||
|
OFFICE_LADY = "Office Lady"
|
||||||
|
OIRAN = "Oiran"
|
||||||
|
OJOU_SAMA = "Ojou-sama"
|
||||||
|
ORPHAN = "Orphan"
|
||||||
|
PIRATES = "Pirates"
|
||||||
|
ROBOTS = "Robots"
|
||||||
|
SAMURAI = "Samurai"
|
||||||
|
SHRINE_MAIDEN = "Shrine Maiden"
|
||||||
|
SKELETON = "Skeleton"
|
||||||
|
SUCCUBUS = "Succubus"
|
||||||
|
TANNED_SKIN = "Tanned Skin"
|
||||||
|
TEACHER = "Teacher"
|
||||||
|
TOMBOY = "Tomboy"
|
||||||
|
TRANSGENDER = "Transgender"
|
||||||
|
TSUNDERE = "Tsundere"
|
||||||
|
TWINS = "Twins"
|
||||||
|
VAMPIRE = "Vampire"
|
||||||
|
VETERINARIAN = "Veterinarian"
|
||||||
|
VIKINGS = "Vikings"
|
||||||
|
VILLAINESS = "Villainess"
|
||||||
|
VIRGINITY = "Virginity"
|
||||||
|
VTUBER = "VTuber"
|
||||||
|
WEREWOLF = "Werewolf"
|
||||||
|
WITCH = "Witch"
|
||||||
|
YANDERE = "Yandere"
|
||||||
|
ZOMBIE = "Zombie"
|
||||||
|
YOUKAI = "Youkai" # Added
|
||||||
|
|
||||||
|
# Demographic
|
||||||
|
JOSEI = "Josei"
|
||||||
|
KIDS = "Kids"
|
||||||
|
SEINEN = "Seinen"
|
||||||
|
SHOUJO = "Shoujo"
|
||||||
|
SHOUNEN = "Shounen"
|
||||||
|
|
||||||
|
# Setting
|
||||||
|
MATRIARCHY = "Matriarchy"
|
||||||
|
|
||||||
|
# Setting Scene
|
||||||
|
BAR = "Bar"
|
||||||
|
BOARDING_SCHOOL = "Boarding School"
|
||||||
|
CIRCUS = "Circus"
|
||||||
|
COASTAL = "Coastal"
|
||||||
|
COLLEGE = "College"
|
||||||
|
DESERT = "Desert"
|
||||||
|
DUNGEON = "Dungeon"
|
||||||
|
FOREIGN = "Foreign"
|
||||||
|
INN = "Inn"
|
||||||
|
KONBINI = "Konbini"
|
||||||
|
NATURAL_DISASTER = "Natural Disaster"
|
||||||
|
OFFICE = "Office"
|
||||||
|
OUTDOOR = "Outdoor"
|
||||||
|
PRISON = "Prison"
|
||||||
|
RESTAURANT = "Restaurant"
|
||||||
|
RURAL = "Rural"
|
||||||
|
SCHOOL = "School"
|
||||||
|
SCHOOL_CLUB = "School Club"
|
||||||
|
SNOWSCAPE = "Snowscape"
|
||||||
|
URBAN = "Urban"
|
||||||
|
WORK = "Work"
|
||||||
|
|
||||||
|
# Setting Time
|
||||||
|
ACHRONOLOGICAL_ORDER = "Achronological Order"
|
||||||
|
ANACHRONISM = "Anachronism"
|
||||||
|
ANCIENT_CHINA = "Ancient China"
|
||||||
|
DYSTOPIAN = "Dystopian"
|
||||||
|
HISTORICAL = "Historical"
|
||||||
|
TIME_SKIP = "Time Skip"
|
||||||
|
|
||||||
|
# Setting Universe
|
||||||
|
AFTERLIFE = "Afterlife"
|
||||||
|
ALTERNATE_UNIVERSE = "Alternate Universe"
|
||||||
|
AUGMENTED_REALITY = "Augmented Reality"
|
||||||
|
OMEGAVERSE = "Omegaverse"
|
||||||
|
POST_APOCALYPTIC = "Post-Apocalyptic"
|
||||||
|
SPACE = "Space"
|
||||||
|
URBAN_FANTASY = "Urban Fantasy"
|
||||||
|
VIRTUAL_WORLD = "Virtual World"
|
||||||
|
|
||||||
|
# Technical
|
||||||
|
_4_KOMA = "4-koma"
|
||||||
|
ACHROMATIC = "Achromatic"
|
||||||
|
ADVERTISEMENT = "Advertisement"
|
||||||
|
ANTHOLOGY = "Anthology"
|
||||||
|
CGI = "CGI"
|
||||||
|
EPISODIC = "Episodic"
|
||||||
|
FLASH = "Flash"
|
||||||
|
FULL_CGI = "Full CGI"
|
||||||
|
FULL_COLOR = "Full Color"
|
||||||
|
NO_DIALOGUE = "No Dialogue"
|
||||||
|
NON_FICTION = "Non-fiction"
|
||||||
|
POV = "POV"
|
||||||
|
PUPPETRY = "Puppetry"
|
||||||
|
ROTOSCOPING = "Rotoscoping"
|
||||||
|
STOP_MOTION = "Stop Motion"
|
||||||
|
|
||||||
|
# Theme Action
|
||||||
|
ARCHERY = "Archery"
|
||||||
|
BATTLE_ROYALE = "Battle Royale"
|
||||||
|
ESPIONAGE = "Espionage"
|
||||||
|
FUGITIVE = "Fugitive"
|
||||||
|
GUNS = "Guns"
|
||||||
|
MARTIAL_ARTS = "Martial Arts"
|
||||||
|
SPEARPLAY = "Spearplay"
|
||||||
|
SWORDPLAY = "Swordplay"
|
||||||
|
|
||||||
|
# Theme Arts
|
||||||
|
ACTING = "Acting"
|
||||||
|
CALLIGRAPHY = "Calligraphy"
|
||||||
|
CLASSIC_LITERATURE = "Classic Literature"
|
||||||
|
DRAWING = "Drawing"
|
||||||
|
FASHION = "Fashion"
|
||||||
|
FOOD = "Food"
|
||||||
|
MAKEUP = "Makeup"
|
||||||
|
PHOTOGRAPHY = "Photography"
|
||||||
|
RAKUGO = "Rakugo"
|
||||||
|
WRITING = "Writing"
|
||||||
|
|
||||||
|
# Theme Arts-Music
|
||||||
|
BAND = "Band"
|
||||||
|
CLASSICAL_MUSIC = "Classical Music"
|
||||||
|
DANCING = "Dancing"
|
||||||
|
HIP_HOP_MUSIC = "Hip-hop Music"
|
||||||
|
JAZZ_MUSIC = "Jazz Music"
|
||||||
|
METAL_MUSIC = "Metal Music"
|
||||||
|
MUSICAL_THEATER = "Musical Theater"
|
||||||
|
ROCK_MUSIC = "Rock Music"
|
||||||
|
|
||||||
|
# Theme Comedy
|
||||||
|
PARODY = "Parody"
|
||||||
|
SATIRE = "Satire"
|
||||||
|
SLAPSTICK = "Slapstick"
|
||||||
|
SURREAL_COMEDY = "Surreal Comedy"
|
||||||
|
|
||||||
|
# Theme Drama
|
||||||
|
BULLYING = "Bullying"
|
||||||
|
CLASS_STRUGGLE = "Class Struggle"
|
||||||
|
COMING_OF_AGE = "Coming of Age"
|
||||||
|
CONSPIRACY = "Conspiracy"
|
||||||
|
ECO_HORROR = "Eco-Horror"
|
||||||
|
FAKE_RELATIONSHIP = "Fake Relationship"
|
||||||
|
KINGDOM_MANAGEMENT = "Kingdom Management"
|
||||||
|
MASTURBATION = "Masturbation"
|
||||||
|
PREGNANCY = "Pregnancy"
|
||||||
|
RAPE = "Rape"
|
||||||
|
REHABILITATION = "Rehabilitation"
|
||||||
|
REVENGE = "Revenge"
|
||||||
|
SUICIDE = "Suicide"
|
||||||
|
TRAGEDY = "Tragedy"
|
||||||
|
|
||||||
|
# Theme Fantasy
|
||||||
|
ALCHEMY = "Alchemy"
|
||||||
|
BODY_SWAPPING = "Body Swapping"
|
||||||
|
CURSES = "Curses"
|
||||||
|
CULTIVATION = "Cultivation"
|
||||||
|
EXORCISM = "Exorcism"
|
||||||
|
FAIRY_TALE = "Fairy Tale"
|
||||||
|
HENSHIN = "Henshin"
|
||||||
|
ISEKAI = "Isekai"
|
||||||
|
KAIJU = "Kaiju"
|
||||||
|
MAGIC = "Magic"
|
||||||
|
MYTHOLOGY = "Mythology"
|
||||||
|
MEDIEVAL = "Medieval"
|
||||||
|
NECROMANCY = "Necromancy"
|
||||||
|
SHAPESHIFTING = "Shapeshifting"
|
||||||
|
STEAMPUNK = "Steampunk"
|
||||||
|
SUPER_POWER = "Super Power"
|
||||||
|
SUPERHERO = "Superhero"
|
||||||
|
WUXIA = "Wuxia"
|
||||||
|
|
||||||
|
# Theme Game
|
||||||
|
BOARD_GAME = "Board Game"
|
||||||
|
E_SPORTS = "E-Sports"
|
||||||
|
VIDEO_GAMES = "Video Games"
|
||||||
|
|
||||||
|
# Theme Game-Card & Board Game
|
||||||
|
CARD_BATTLE = "Card Battle"
|
||||||
|
GO = "Go"
|
||||||
|
KARUTA = "Karuta"
|
||||||
|
MAHJONG = "Mahjong"
|
||||||
|
POKER = "Poker"
|
||||||
|
SHOGI = "Shogi"
|
||||||
|
|
||||||
|
# Theme Game-Sport
|
||||||
|
ACROBATICS = "Acrobatics"
|
||||||
|
AIRSOFT = "Airsoft"
|
||||||
|
AMERICAN_FOOTBALL = "American Football"
|
||||||
|
ATHLETICS = "Athletics"
|
||||||
|
BADMINTON = "Badminton"
|
||||||
|
BASEBALL = "Baseball"
|
||||||
|
BASKETBALL = "Basketball"
|
||||||
|
BOWLING = "Bowling"
|
||||||
|
BOXING = "Boxing"
|
||||||
|
CHEERLEADING = "Cheerleading"
|
||||||
|
CYCLING = "Cycling"
|
||||||
|
FENCING = "Fencing"
|
||||||
|
FISHING = "Fishing"
|
||||||
|
FITNESS = "Fitness"
|
||||||
|
FOOTBALL = "Football"
|
||||||
|
GOLF = "Golf"
|
||||||
|
HANDBALL = "Handball"
|
||||||
|
ICE_SKATING = "Ice Skating"
|
||||||
|
JUDO = "Judo"
|
||||||
|
LACROSSE = "Lacrosse"
|
||||||
|
PARKOUR = "Parkour"
|
||||||
|
RUGBY = "Rugby"
|
||||||
|
SCUBA_DIVING = "Scuba Diving"
|
||||||
|
SKATEBOARDING = "Skateboarding"
|
||||||
|
SUMO = "Sumo"
|
||||||
|
SURFING = "Surfing"
|
||||||
|
SWIMMING = "Swimming"
|
||||||
|
TABLE_TENNIS = "Table Tennis"
|
||||||
|
TENNIS = "Tennis"
|
||||||
|
VOLLEYBALL = "Volleyball"
|
||||||
|
WRESTLING = "Wrestling"
|
||||||
|
|
||||||
|
# Theme Other
|
||||||
|
ADOPTION = "Adoption"
|
||||||
|
ANIMALS = "Animals"
|
||||||
|
ASTRONOMY = "Astronomy"
|
||||||
|
AUTOBIOGRAPHICAL = "Autobiographical"
|
||||||
|
BIOGRAPHICAL = "Biographical"
|
||||||
|
BODY_HORROR = "Body Horror"
|
||||||
|
BODY_IMAGE = "Body Image"
|
||||||
|
CANNIBALISM = "Cannibalism"
|
||||||
|
CHIBI = "Chibi"
|
||||||
|
COHABITATION = "Cohabitation"
|
||||||
|
COSMIC_HORROR = "Cosmic Horror"
|
||||||
|
CREATURE_TAMING = "Creature Taming"
|
||||||
|
CRIME = "Crime"
|
||||||
|
CROSSOVER = "Crossover"
|
||||||
|
DEATH_GAME = "Death Game"
|
||||||
|
DENPA = "Denpa"
|
||||||
|
DEFLORATION = "Defloration"
|
||||||
|
DRUGS = "Drugs"
|
||||||
|
ECONOMICS = "Economics"
|
||||||
|
EDUCATIONAL = "Educational"
|
||||||
|
ENVIRONMENTAL = "Environmental"
|
||||||
|
ERO_GURO = "Ero Guro"
|
||||||
|
FILMMAKING = "Filmmaking"
|
||||||
|
FOUND_FAMILY = "Found Family"
|
||||||
|
GAMBLING = "Gambling"
|
||||||
|
GENDER_BENDING = "Gender Bending"
|
||||||
|
GORE = "Gore"
|
||||||
|
HYPERSEXUALITY = "Hypersexuality"
|
||||||
|
LANGUAGE_BARRIER = "Language Barrier"
|
||||||
|
LARGE_BREASTS = "Large Breasts"
|
||||||
|
LGBTQ_PLUS_THEMES = "LGBTQ+ Themes"
|
||||||
|
LOST_CIVILIZATION = "Lost Civilization"
|
||||||
|
MARRIAGE = "Marriage"
|
||||||
|
MEDICINE = "Medicine"
|
||||||
|
MEMORY_MANIPULATION = "Memory Manipulation"
|
||||||
|
META = "Meta"
|
||||||
|
MIXED_MEDIA = "Mixed Media"
|
||||||
|
MOUNTAINEERING = "Mountaineering"
|
||||||
|
NOIR = "Noir"
|
||||||
|
OTAKU_CULTURE = "Otaku Culture"
|
||||||
|
OUTDOOR_ACTIVITIES = "Outdoor Activities"
|
||||||
|
PANDEMIC = "Pandemic"
|
||||||
|
PHILOSOPHY = "Philosophy"
|
||||||
|
POLITICS = "Politics"
|
||||||
|
PROXY_BATTLE = "Proxy Battle"
|
||||||
|
PSYCHOSEXUAL = "Psychosexual"
|
||||||
|
REINCARNATION = "Reincarnation"
|
||||||
|
RELIGION = "Religion"
|
||||||
|
RESCUE = "Rescue"
|
||||||
|
ROYAL_AFFAIRS = "Royal Affairs"
|
||||||
|
SLAVERY = "Slavery"
|
||||||
|
SOFTWARE_DEVELOPMENT = "Software Development"
|
||||||
|
SURVIVAL = "Survival"
|
||||||
|
TERRORISM = "Terrorism"
|
||||||
|
THREESOME = "Threesome"
|
||||||
|
TORTURE = "Torture"
|
||||||
|
TRAVEL = "Travel"
|
||||||
|
WAR = "War"
|
||||||
|
WILDERNESS = "Wilderness"
|
||||||
|
VORE = "Vore" # Added
|
||||||
|
|
||||||
|
# Theme Other-Organisations
|
||||||
|
ASSASSINS = "Assassins"
|
||||||
|
CRIMINAL_ORGANIZATION = "Criminal Organization"
|
||||||
|
CULT = "Cult"
|
||||||
|
FIREFIGHTERS = "Firefighters"
|
||||||
|
GANGS = "Gangs"
|
||||||
|
MAFIA = "Mafia"
|
||||||
|
MILITARY = "Military"
|
||||||
|
POLICE = "Police"
|
||||||
|
TRIADS = "Triads"
|
||||||
|
YAKUZA = "Yakuza"
|
||||||
|
|
||||||
|
# Theme Other-Vehicle
|
||||||
|
AVIATION = "Aviation"
|
||||||
|
CARS = "Cars"
|
||||||
|
MOPEDS = "Mopeds"
|
||||||
|
MOTORCYCLES = "Motorcycles"
|
||||||
|
SHIPS = "Ships"
|
||||||
|
TANKS = "Tanks"
|
||||||
|
TRAINS = "Trains"
|
||||||
|
|
||||||
|
# Theme Romance
|
||||||
|
AGE_GAP = "Age Gap"
|
||||||
|
BISEXUAL = "Bisexual"
|
||||||
|
BOYS_LOVE = "Boys' Love"
|
||||||
|
FEMALE_HAREM = "Female Harem"
|
||||||
|
HETEROSEXUAL = "Heterosexual"
|
||||||
|
INCEST = "Incest"
|
||||||
|
LOVE_TRIANGLE = "Love Triangle"
|
||||||
|
MALE_HAREM = "Male Harem"
|
||||||
|
MATCHMAKING = "Matchmaking"
|
||||||
|
MIXED_GENDER_HAREM = "Mixed Gender Harem"
|
||||||
|
PUBLIC_SEX = "Public Sex"
|
||||||
|
TEENS_LOVE = "Teens' Love"
|
||||||
|
UNREQUITED_LOVE = "Unrequited Love"
|
||||||
|
YURI = "Yuri"
|
||||||
|
|
||||||
|
# Theme Sci Fi
|
||||||
|
CYBERPUNK = "Cyberpunk"
|
||||||
|
SPACE_OPERA = "Space Opera"
|
||||||
|
TIME_LOOP = "Time Loop"
|
||||||
|
TIME_MANIPULATION = "Time Manipulation"
|
||||||
|
TOKUSATSU = "Tokusatsu"
|
||||||
|
|
||||||
|
# Theme Sci Fi-Mecha
|
||||||
|
REAL_ROBOT = "Real Robot"
|
||||||
|
SUPER_ROBOT = "Super Robot"
|
||||||
|
|
||||||
|
# Theme Slice of Life
|
||||||
|
AGRICULTURE = "Agriculture"
|
||||||
|
CUTE_BOYS_DOING_CUTE_THINGS = "Cute Boys Doing Cute Things"
|
||||||
|
CUTE_GIRLS_DOING_CUTE_THINGS = "Cute Girls Doing Cute Things"
|
||||||
|
FAMILY_LIFE = "Family Life"
|
||||||
|
HORTICULTURE = "Horticulture"
|
||||||
|
IYASHIKEI = "Iyashikei"
|
||||||
|
PARENTHOOD = "Parenthood"
|
||||||
|
|
||||||
|
|
||||||
|
# MODELS
|
||||||
class BaseApiModel(BaseModel):
|
class BaseApiModel(BaseModel):
|
||||||
"""Base model for all API types."""
|
model_config = ConfigDict(frozen=True)
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MediaImage(BaseApiModel):
|
class MediaImage(BaseApiModel):
|
||||||
@@ -50,22 +499,22 @@ class AiringSchedule(BaseApiModel):
|
|||||||
"""A generic representation of the next airing episode."""
|
"""A generic representation of the next airing episode."""
|
||||||
|
|
||||||
episode: int
|
episode: int
|
||||||
airing_at: datetime | None = None
|
airing_at: Optional[datetime] = None
|
||||||
|
|
||||||
|
|
||||||
class Studio(BaseApiModel):
|
class Studio(BaseApiModel):
|
||||||
"""A generic representation of an animation studio."""
|
"""A generic representation of an animation studio."""
|
||||||
|
|
||||||
id: int | None = None
|
id: Optional[int] = None
|
||||||
name: str | None = None
|
name: Optional[str] = None
|
||||||
favourites: int | None = None
|
favourites: Optional[int] = None
|
||||||
is_animation_studio: bool | None = None
|
is_animation_studio: Optional[bool] = None
|
||||||
|
|
||||||
|
|
||||||
class MediaTag(BaseApiModel):
|
class MediaTagItem(BaseApiModel):
|
||||||
"""A generic representation of a descriptive tag."""
|
"""A generic representation of a descriptive tag."""
|
||||||
|
|
||||||
name: str
|
name: MediaTag
|
||||||
rank: Optional[int] = None # Percentage relevance from 0-100
|
rank: Optional[int] = None # Percentage relevance from 0-100
|
||||||
|
|
||||||
|
|
||||||
@@ -76,12 +525,11 @@ class StreamingEpisode(BaseApiModel):
|
|||||||
thumbnail: Optional[str] = None
|
thumbnail: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class UserListStatus(BaseApiModel):
|
class UserListItem(BaseApiModel):
|
||||||
"""Generic representation of a user's list status for a media item."""
|
"""Generic representation of a user's list status for a media item."""
|
||||||
|
|
||||||
id: int | None = None
|
id: Optional[int] = None
|
||||||
|
status: Optional[UserMediaListStatus] = None
|
||||||
status: Optional[UserListStatusType] = None
|
|
||||||
progress: Optional[int] = None
|
progress: Optional[int] = None
|
||||||
score: Optional[float] = None
|
score: Optional[float] = None
|
||||||
repeat: Optional[int] = None
|
repeat: Optional[int] = None
|
||||||
@@ -95,9 +543,9 @@ class MediaItem(BaseApiModel):
|
|||||||
id: int
|
id: int
|
||||||
title: MediaTitle
|
title: MediaTitle
|
||||||
id_mal: Optional[int] = None
|
id_mal: Optional[int] = None
|
||||||
type: MediaType = "ANIME"
|
type: MediaType = MediaType.ANIME
|
||||||
status: Optional[str] = None
|
status: MediaStatus = MediaStatus.FINISHED
|
||||||
format: Optional[str] = None # e.g., TV, MOVIE, OVA
|
format: MediaFormat = MediaFormat.TV
|
||||||
|
|
||||||
cover_image: Optional[MediaImage] = None
|
cover_image: Optional[MediaImage] = None
|
||||||
banner_image: Optional[str] = None
|
banner_image: Optional[str] = None
|
||||||
@@ -106,8 +554,8 @@ class MediaItem(BaseApiModel):
|
|||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
episodes: Optional[int] = None
|
episodes: Optional[int] = None
|
||||||
duration: Optional[int] = None # In minutes
|
duration: Optional[int] = None # In minutes
|
||||||
genres: List[str] = Field(default_factory=list)
|
genres: List[MediaGenre] = Field(default_factory=list)
|
||||||
tags: List[MediaTag] = Field(default_factory=list)
|
tags: List[MediaTagItem] = Field(default_factory=list)
|
||||||
studios: List[Studio] = Field(default_factory=list)
|
studios: List[Studio] = Field(default_factory=list)
|
||||||
synonymns: List[str] = Field(default_factory=list)
|
synonymns: List[str] = Field(default_factory=list)
|
||||||
|
|
||||||
@@ -124,7 +572,7 @@ class MediaItem(BaseApiModel):
|
|||||||
streaming_episodes: List[StreamingEpisode] = Field(default_factory=list)
|
streaming_episodes: List[StreamingEpisode] = Field(default_factory=list)
|
||||||
|
|
||||||
# user related
|
# user related
|
||||||
user_status: Optional[UserListStatus] = None
|
user_status: Optional[UserListItem] = None
|
||||||
|
|
||||||
|
|
||||||
class PageInfo(BaseApiModel):
|
class PageInfo(BaseApiModel):
|
||||||
@@ -150,3 +598,126 @@ class UserProfile(BaseApiModel):
|
|||||||
name: str
|
name: str
|
||||||
avatar_url: Optional[str] = None
|
avatar_url: Optional[str] = None
|
||||||
banner_url: Optional[str] = None
|
banner_url: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
# ENUMS
|
||||||
|
class MediaSort(Enum):
|
||||||
|
ID = "ID"
|
||||||
|
ID_DESC = "ID_DESC"
|
||||||
|
TITLE_ROMAJI = "TITLE_ROMAJI"
|
||||||
|
TITLE_ROMAJI_DESC = "TITLE_ROMAJI_DESC"
|
||||||
|
TITLE_ENGLISH = "TITLE_ENGLISH"
|
||||||
|
TITLE_ENGLISH_DESC = "TITLE_ENGLISH_DESC"
|
||||||
|
TITLE_NATIVE = "TITLE_NATIVE"
|
||||||
|
TITLE_NATIVE_DESC = "TITLE_NATIVE_DESC"
|
||||||
|
TYPE = "TYPE"
|
||||||
|
TYPE_DESC = "TYPE_DESC"
|
||||||
|
FORMAT = "FORMAT"
|
||||||
|
FORMAT_DESC = "FORMAT_DESC"
|
||||||
|
START_DATE = "START_DATE"
|
||||||
|
START_DATE_DESC = "START_DATE_DESC"
|
||||||
|
END_DATE = "END_DATE"
|
||||||
|
END_DATE_DESC = "END_DATE_DESC"
|
||||||
|
SCORE = "SCORE"
|
||||||
|
SCORE_DESC = "SCORE_DESC"
|
||||||
|
POPULARITY = "POPULARITY"
|
||||||
|
POPULARITY_DESC = "POPULARITY_DESC"
|
||||||
|
TRENDING = "TRENDING"
|
||||||
|
TRENDING_DESC = "TRENDING_DESC"
|
||||||
|
EPISODES = "EPISODES"
|
||||||
|
EPISODES_DESC = "EPISODES_DESC"
|
||||||
|
DURATION = "DURATION"
|
||||||
|
DURATION_DESC = "DURATION_DESC"
|
||||||
|
STATUS = "STATUS"
|
||||||
|
STATUS_DESC = "STATUS_DESC"
|
||||||
|
CHAPTERS = "CHAPTERS"
|
||||||
|
CHAPTERS_DESC = "CHAPTERS_DESC"
|
||||||
|
VOLUMES = "VOLUMES"
|
||||||
|
VOLUMES_DESC = "VOLUMES_DESC"
|
||||||
|
UPDATED_AT = "UPDATED_AT"
|
||||||
|
UPDATED_AT_DESC = "UPDATED_AT_DESC"
|
||||||
|
SEARCH_MATCH = "SEARCH_MATCH"
|
||||||
|
FAVOURITES = "FAVOURITES"
|
||||||
|
FAVOURITES_DESC = "FAVOURITES_DESC"
|
||||||
|
|
||||||
|
|
||||||
|
class UserMediaListSort(Enum):
|
||||||
|
MEDIA_ID = "MEDIA_ID"
|
||||||
|
MEDIA_ID_DESC = "MEDIA_ID_DESC"
|
||||||
|
SCORE = "SCORE"
|
||||||
|
SCORE_DESC = "SCORE_DESC"
|
||||||
|
STATUS = "STATUS"
|
||||||
|
STATUS_DESC = "STATUS_DESC"
|
||||||
|
PROGRESS = "PROGRESS"
|
||||||
|
PROGRESS_DESC = "PROGRESS_DESC"
|
||||||
|
PROGRESS_VOLUMES = "PROGRESS_VOLUMES"
|
||||||
|
PROGRESS_VOLUMES_DESC = "PROGRESS_VOLUMES_DESC"
|
||||||
|
REPEAT = "REPEAT"
|
||||||
|
REPEAT_DESC = "REPEAT_DESC"
|
||||||
|
PRIORITY = "PRIORITY"
|
||||||
|
PRIORITY_DESC = "PRIORITY_DESC"
|
||||||
|
STARTED_ON = "STARTED_ON"
|
||||||
|
STARTED_ON_DESC = "STARTED_ON_DESC"
|
||||||
|
FINISHED_ON = "FINISHED_ON"
|
||||||
|
FINISHED_ON_DESC = "FINISHED_ON_DESC"
|
||||||
|
ADDED_TIME = "ADDED_TIME"
|
||||||
|
ADDED_TIME_DESC = "ADDED_TIME_DESC"
|
||||||
|
UPDATED_TIME = "UPDATED_TIME"
|
||||||
|
UPDATED_TIME_DESC = "UPDATED_TIME_DESC"
|
||||||
|
MEDIA_TITLE_ROMAJI = "MEDIA_TITLE_ROMAJI"
|
||||||
|
MEDIA_TITLE_ROMAJI_DESC = "MEDIA_TITLE_ROMAJI_DESC"
|
||||||
|
MEDIA_TITLE_ENGLISH = "MEDIA_TITLE_ENGLISH"
|
||||||
|
MEDIA_TITLE_ENGLISH_DESC = "MEDIA_TITLE_ENGLISH_DESC"
|
||||||
|
MEDIA_TITLE_NATIVE = "MEDIA_TITLE_NATIVE"
|
||||||
|
MEDIA_TITLE_NATIVE_DESC = "MEDIA_TITLE_NATIVE_DESC"
|
||||||
|
MEDIA_POPULARITY = "MEDIA_POPULARITY"
|
||||||
|
MEDIA_POPULARITY_DESC = "MEDIA_POPULARITY_DESC"
|
||||||
|
MEDIA_SCORE = "MEDIA_SCORE"
|
||||||
|
MEDIA_SCORE_DESC = "MEDIA_SCORE_DESC"
|
||||||
|
MEDIA_START_DATE = "MEDIA_START_DATE"
|
||||||
|
MEDIA_START_DATE_DESC = "MEDIA_START_DATE_DESC"
|
||||||
|
MEDIA_RATING = "MEDIA_RATING"
|
||||||
|
MEDIA_RATING_DESC = "MEDIA_RATING_DESC"
|
||||||
|
|
||||||
|
|
||||||
|
class MediaSeason(Enum):
|
||||||
|
WINTER = "WINTER"
|
||||||
|
SPRING = "SPRING"
|
||||||
|
SUMMER = "SUMMER"
|
||||||
|
FALL = "FALL"
|
||||||
|
|
||||||
|
|
||||||
|
class MediaYear(Enum):
|
||||||
|
_1900 = "1900"
|
||||||
|
_1910 = "1910"
|
||||||
|
_1920 = "1920"
|
||||||
|
_1930 = "1930"
|
||||||
|
_1940 = "1940"
|
||||||
|
_1950 = "1950"
|
||||||
|
_1960 = "1960"
|
||||||
|
_1970 = "1970"
|
||||||
|
_1980 = "1980"
|
||||||
|
_1990 = "1990"
|
||||||
|
_2000 = "2000"
|
||||||
|
_2004 = "2004"
|
||||||
|
_2005 = "2005"
|
||||||
|
_2006 = "2006"
|
||||||
|
_2007 = "2007"
|
||||||
|
_2008 = "2008"
|
||||||
|
_2009 = "2009"
|
||||||
|
_2010 = "2010"
|
||||||
|
_2011 = "2011"
|
||||||
|
_2012 = "2012"
|
||||||
|
_2013 = "2013"
|
||||||
|
_2014 = "2014"
|
||||||
|
_2015 = "2015"
|
||||||
|
_2016 = "2016"
|
||||||
|
_2017 = "2017"
|
||||||
|
_2018 = "2018"
|
||||||
|
_2019 = "2019"
|
||||||
|
_2020 = "2020"
|
||||||
|
_2021 = "2021"
|
||||||
|
_2022 = "2022"
|
||||||
|
_2023 = "2023"
|
||||||
|
_2024 = "2024"
|
||||||
|
_2025 = "2025"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from typing import Literal, Optional
|
from typing import Literal, Optional
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel, ConfigDict
|
||||||
|
|
||||||
|
|
||||||
class BaseAnimeProviderModel(BaseModel):
|
class BaseAnimeProviderModel(BaseModel):
|
||||||
pass
|
model_config = ConfigDict(frozen=True)
|
||||||
|
|
||||||
|
|
||||||
class PageInfo(BaseAnimeProviderModel):
|
class PageInfo(BaseAnimeProviderModel):
|
||||||
@@ -35,7 +35,6 @@ class SearchResult(BaseAnimeProviderModel):
|
|||||||
class SearchResults(BaseAnimeProviderModel):
|
class SearchResults(BaseAnimeProviderModel):
|
||||||
page_info: PageInfo
|
page_info: PageInfo
|
||||||
results: list[SearchResult]
|
results: list[SearchResult]
|
||||||
model_config = {"frozen": True}
|
|
||||||
|
|
||||||
|
|
||||||
class AnimeEpisodeInfo(BaseAnimeProviderModel):
|
class AnimeEpisodeInfo(BaseAnimeProviderModel):
|
||||||
|
|||||||
Reference in New Issue
Block a user