From afe1cb68f671ffcf21b417fb5a0b6c16a4b0e8cd Mon Sep 17 00:00:00 2001 From: Benexl Date: Thu, 24 Jul 2025 00:07:26 +0300 Subject: [PATCH] feat: results menu --- fastanime/cli/interactive/menus/auth.py | 8 +- .../cli/interactive/menus/media_actions.py | 22 +- .../cli/interactive/menus/player_controls.py | 4 +- fastanime/cli/interactive/menus/results.py | 293 ++++++-------- fastanime/cli/interactive/menus/servers.py | 2 +- .../cli/interactive/menus/watch_history.py | 2 +- fastanime/cli/interactive/session.py | 35 +- fastanime/cli/interactive/state.py | 5 +- fastanime/cli/services/feedback/service.py | 29 +- fastanime/cli/services/registry/service.py | 7 +- fastanime/libs/api/anilist/api.py | 4 +- fastanime/libs/api/types.py | 379 ++++++++++-------- tags.json | 1 + 13 files changed, 398 insertions(+), 393 deletions(-) create mode 100644 tags.json diff --git a/fastanime/cli/interactive/menus/auth.py b/fastanime/cli/interactive/menus/auth.py index 0a40331..e4a895c 100644 --- a/fastanime/cli/interactive/menus/auth.py +++ b/fastanime/cli/interactive/menus/auth.py @@ -66,11 +66,11 @@ def auth(ctx: Context, state: State) -> State | InternalDirective: elif "View Profile Details" in choice: _display_user_profile_details(console, user_profile, icons) feedback.pause_for_user("Press Enter to continue") - return InternalDirective.CONTINUE + return InternalDirective.RELOAD elif "How to Get Token" in choice: _display_token_help(console, icons) feedback.pause_for_user("Press Enter to continue") - return InternalDirective.CONTINUE + return InternalDirective.RELOAD else: # Back to Main Menu return InternalDirective.BACK @@ -164,7 +164,7 @@ def _handle_login( ) feedback.pause_for_user("Press Enter to continue") - return InternalDirective.CONTINUE + return InternalDirective.RELOAD def _handle_logout( @@ -176,7 +176,7 @@ def _handle_logout( "This will remove your saved AniList token and log you out", default=False, ): - return InternalDirective.CONTINUE + return InternalDirective.RELOAD def perform_logout(): # Clear from auth manager diff --git a/fastanime/cli/interactive/menus/media_actions.py b/fastanime/cli/interactive/menus/media_actions.py index 7484240..99bd11f 100644 --- a/fastanime/cli/interactive/menus/media_actions.py +++ b/fastanime/cli/interactive/menus/media_actions.py @@ -57,7 +57,7 @@ def _watch_trailer(ctx: Context, state: State) -> MenuAction: feedback = ctx.services.feedback anime = state.media_api.anime if not anime: - return InternalDirective.CONTINUE + return InternalDirective.RELOAD if not anime.trailer or not anime.trailer.id: feedback.warning( "No trailer available for this anime", @@ -68,7 +68,7 @@ def _watch_trailer(ctx: Context, state: State) -> MenuAction: ctx.player.play(PlayerParams(url=trailer_url, title="")) - return InternalDirective.CONTINUE + return InternalDirective.RELOAD return action @@ -78,10 +78,10 @@ def _add_to_list(ctx: Context, state: State) -> MenuAction: feedback = ctx.services.feedback anime = state.media_api.anime if not anime: - return InternalDirective.CONTINUE + return InternalDirective.RELOAD if not ctx.media_api.is_authenticated(): - return InternalDirective.CONTINUE + return InternalDirective.RELOAD choices = [ "watching", @@ -99,7 +99,7 @@ def _add_to_list(ctx: Context, state: State) -> MenuAction: UpdateListEntryParams(media_id=anime.id, status=status), # pyright:ignore feedback, ) - return InternalDirective.CONTINUE + return InternalDirective.RELOAD return action @@ -109,11 +109,11 @@ def _score_anime(ctx: Context, state: State) -> MenuAction: feedback = ctx.services.feedback anime = state.media_api.anime if not anime: - return InternalDirective.CONTINUE + return InternalDirective.RELOAD # Check authentication before proceeding if not ctx.media_api.is_authenticated(): - return InternalDirective.CONTINUE + return InternalDirective.RELOAD score_str = ctx.selector.ask("Enter score (0.0 - 10.0):") try: @@ -130,7 +130,7 @@ def _score_anime(ctx: Context, state: State) -> MenuAction: feedback.error( "Invalid score entered", "Please enter a number between 0.0 and 10.0" ) - return InternalDirective.CONTINUE + return InternalDirective.RELOAD return action @@ -139,7 +139,7 @@ def _view_info(ctx: Context, state: State) -> MenuAction: def action(): anime = state.media_api.anime if not anime: - return InternalDirective.CONTINUE + return InternalDirective.RELOAD # TODO: Make this nice and include all other media item fields from rich import box @@ -161,7 +161,7 @@ def _view_info(ctx: Context, state: State) -> MenuAction: console.print(Panel(panel_content, title=title, box=box.ROUNDED, expand=True)) ctx.selector.ask("Press Enter to continue...") - return InternalDirective.CONTINUE + return InternalDirective.RELOAD return action @@ -170,6 +170,6 @@ def _update_user_list( ctx: Context, anime: MediaItem, params: UpdateListEntryParams, feedback ): if ctx.media_api.is_authenticated(): - return InternalDirective.CONTINUE + return InternalDirective.RELOAD ctx.media_api.update_list_entry(params) diff --git a/fastanime/cli/interactive/menus/player_controls.py b/fastanime/cli/interactive/menus/player_controls.py index 14aed9c..5356b2b 100644 --- a/fastanime/cli/interactive/menus/player_controls.py +++ b/fastanime/cli/interactive/menus/player_controls.py @@ -79,7 +79,7 @@ def player_controls(ctx: Context, state: State) -> State | InternalDirective: ), ) console.print("[bold yellow]This is the last available episode.[/bold yellow]") - return InternalDirective.CONTINUE + return InternalDirective.RELOAD def replay() -> State | InternalDirective: # We don't need to change state, just re-trigger the SERVERS menu's logic. @@ -101,7 +101,7 @@ def player_controls(ctx: Context, state: State) -> State | InternalDirective: update={"selected_server": server_map[new_server_name]} ), ) - return InternalDirective.CONTINUE + return InternalDirective.RELOAD # --- Menu Options --- icons = config.general.icons diff --git a/fastanime/cli/interactive/menus/results.py b/fastanime/cli/interactive/menus/results.py index 2484cec..42fd4f6 100644 --- a/fastanime/cli/interactive/menus/results.py +++ b/fastanime/cli/interactive/menus/results.py @@ -1,108 +1,103 @@ +from dataclasses import asdict +from typing import Callable, Dict, Union + from ....libs.api.params import MediaSearchParams, UserMediaListSearchParams from ....libs.api.types import MediaItem, MediaStatus, UserMediaListStatus from ..session import Context, session -from ..state import InternalDirective, MediaApiState, State +from ..state import InternalDirective, MediaApiState, MenuName, State @session.menu def results(ctx: Context, state: State) -> State | InternalDirective: - search_results = state.media_api.search_results feedback = ctx.services.feedback feedback.clear_console() - if not search_results or not search_results.media: + search_result = state.media_api.search_result + page_info = state.media_api.page_info + + if not search_result: feedback.info("No anime found for the given criteria") return InternalDirective.BACK - anime_items = search_results.media - formatted_titles = [ - _format_anime_choice(anime, ctx.config) for anime in anime_items - ] - - anime_map = dict(zip(formatted_titles, anime_items)) + _formatted_titles = [_format_title(ctx, anime) for anime in search_result.values()] preview_command = None if ctx.config.general.preview != "none": from ...utils.previews import get_anime_preview - preview_command = get_anime_preview(anime_items, formatted_titles, ctx.config) - - choices = formatted_titles - page_info = search_results.page_info - - # Add pagination controls if available with more descriptive text - if page_info.has_next_page: - choices.append( - f"{'➡️ ' if ctx.config.general.icons else ''}Next Page (Page {page_info.current_page + 1})" + preview_command = get_anime_preview( + list(search_result.values()), _formatted_titles, ctx.config ) - if page_info.current_page > 1: - choices.append( - f"{'⬅️ ' if ctx.config.general.icons else ''}Previous Page (Page {page_info.current_page - 1})" - ) - choices.append("Back") - # Create header with auth status and pagination info - pagination_info = f"Page {page_info.current_page}" - if page_info.total > 0 and page_info.per_page > 0: - total_pages = (page_info.total + page_info.per_page - 1) // page_info.per_page - pagination_info += f" of ~{total_pages}" + choices: Dict[str, Callable[[], Union[int, State, InternalDirective]]] = dict( + zip(_formatted_titles, [lambda: item for item in search_result.keys()]) + ) - choice_str = ctx.selector.choose( + if page_info: + if page_info.has_next_page: + choices.update( + { + f"{'➡️ ' if ctx.config.general.icons else ''}Next Page (Page {page_info.current_page + 1})": lambda: _handle_pagination( + ctx, state, 1 + ) + } + ) + if page_info.current_page > 1: + choices.update( + { + f"{'⬅️ ' if ctx.config.general.icons else ''}Previous Page (Page {page_info.current_page - 1})": lambda: _handle_pagination( + ctx, state, -1 + ) + } + ) + choices.update( + {"Back": lambda: InternalDirective.MAIN, "Exit": lambda: InternalDirective.EXIT} + ) + + choice = ctx.selector.choose( prompt="Select Anime", - choices=choices, + choices=list(choices), preview=preview_command, ) - if not choice_str: - return InternalDirective.EXIT + if not choice: + return InternalDirective.RELOAD - if choice_str == "Back": - return InternalDirective.BACK - - if ( - choice_str == "Next Page" - or choice_str == "Previous Page" - or choice_str.startswith("Next Page (") - or choice_str.startswith("Previous Page (") - ): - page_delta = 1 if choice_str.startswith("Next Page") else -1 - - return _handle_pagination(ctx, state, page_delta) - - selected_anime = anime_map.get(choice_str) - if selected_anime: + next_step = choices[choice]() + if isinstance(next_step, State) or isinstance(next_step, InternalDirective): + return next_step + else: return State( - menu_name="MEDIA_ACTIONS", + menu_name=MenuName.MEDIA_ACTIONS, media_api=MediaApiState( - search_results=state.media_api.search_results, # Carry over the list - anime=selected_anime, # Set the newly selected item + media_id=next_step, + search_result=state.media_api.search_result, + page_info=state.media_api.page_info, ), - provider=state.provider, ) - # Fallback - return InternalDirective.CONTINUE +def _format_title(ctx: Context, media_item: MediaItem) -> str: + config = ctx.config -def _format_anime_choice(anime: MediaItem, config) -> str: - """Creates a display string for a single anime item for the selector.""" - title = anime.title.english or anime.title.romaji + title = media_item.title.english or media_item.title.romaji progress = "0" - if anime.user_status: - progress = str(anime.user_status.progress or 0) - episodes_total = str(anime.episodes or "??") + if media_item.user_status: + progress = str(media_item.user_status.progress or 0) + + episodes_total = str(media_item.episodes or "??") display_title = f"{title} ({progress} of {episodes_total})" # Add a visual indicator for new episodes if applicable if ( - anime.status == MediaStatus.RELEASING - and anime.next_airing - and anime.user_status - and anime.user_status.status == UserMediaListStatus.WATCHING + media_item.status == MediaStatus.RELEASING + and media_item.next_airing + and media_item.user_status + and media_item.user_status.status == UserMediaListStatus.WATCHING ): - last_aired = anime.next_airing.episode - 1 - unwatched = last_aired - (anime.user_status.progress or 0) + last_aired = media_item.next_airing.episode - 1 + unwatched = last_aired - (media_item.user_status.progress or 0) if unwatched > 0: icon = "🔹" if config.general.icons else "!" display_title += f" {icon}{unwatched} new{icon}" @@ -113,123 +108,83 @@ def _format_anime_choice(anime: MediaItem, config) -> str: def _handle_pagination( ctx: Context, state: State, page_delta: int ) -> State | InternalDirective: - """ - Handle pagination by fetching the next or previous page of results. - - Args: - ctx: The application context - state: Current state containing search results and original parameters - page_delta: +1 for next page, -1 for previous page - - Returns: - New State with updated search results or ControlFlow.CONTINUE on error - """ feedback = ctx.services.feedback - if not state.media_api.search_results: - feedback.error("No search results available for pagination") - return InternalDirective.CONTINUE + search_params = state.media_api.search_params - current_page = state.media_api.search_results.page_info.current_page + if ( + not state.media_api.search_result + or not state.media_api.page_info + or not search_params + ): + feedback.error("No search results available for pagination") + return InternalDirective.RELOAD + + current_page = state.media_api.page_info.current_page new_page = current_page + page_delta # Validate page bounds if new_page < 1: feedback.warning("Already at the first page") - return InternalDirective.CONTINUE + return InternalDirective.RELOAD - if page_delta > 0 and not state.media_api.search_results.page_info.has_next_page: + if page_delta == -1: + return InternalDirective.BACK + if page_delta > 0 and not state.media_api.page_info.has_next_page: feedback.warning("No more pages available") - return InternalDirective.CONTINUE + return InternalDirective.RELOAD # Determine which type of search to perform based on stored parameters - if state.media_api.original_api_params: - # Media search (trending, popular, search, etc.) - return _fetch_media_page(ctx, state, new_page, feedback) - elif state.media_api.original_user_list_params: - # User list search (watching, completed, etc.) - return _fetch_user_list_page(ctx, state, new_page, feedback) + if isinstance(search_params, UserMediaListSearchParams): + if not ctx.media_api.is_authenticated(): + feedback.error("You haven't logged in") + return InternalDirective.RELOAD + + search_params_dict = asdict(search_params) + search_params_dict.pop("page") + + loading_message = f"Fetching media list" + result = None + new_search_params = UserMediaListSearchParams( + **search_params_dict, page=new_page + ) + with feedback.progress(loading_message): + result = ctx.media_api.search_media_list(new_search_params) + + if result: + return State( + menu_name=MenuName.RESULTS, + media_api=MediaApiState( + search_result={ + media_item.id: media_item for media_item in result.media + }, + search_params=new_search_params, + page_info=result.page_info, + ), + ) else: - feedback.error("No original search parameters found for pagination") - return InternalDirective.CONTINUE + search_params_dict = asdict(search_params) + search_params_dict.pop("page") + loading_message = f"Fetching media list" + result = None + new_search_params = MediaSearchParams(**search_params_dict, page=new_page) + with feedback.progress(loading_message): + result = ctx.media_api.search_media(new_search_params) -def _fetch_media_page( - ctx: Context, state: State, page: int, feedback -) -> State | InternalDirective: - """Fetch a specific page for media search results.""" - original_params = state.media_api.original_api_params - if not original_params: - feedback.error("No original API parameters found") - return InternalDirective.CONTINUE + if result: + return State( + menu_name=MenuName.RESULTS, + media_api=MediaApiState( + search_result={ + media_item.id: media_item for media_item in result.media + }, + search_params=new_search_params, + page_info=result.page_info, + ), + ) - # Create new parameters with updated page number - new_params = MediaSearchParams( - query=original_params.query, - page=page, - per_page=original_params.per_page, - sort=original_params.sort, - id_in=original_params.id_in, - genre_in=original_params.genre_in, - genre_not_in=original_params.genre_not_in, - tag_in=original_params.tag_in, - tag_not_in=original_params.tag_not_in, - status_in=original_params.status_in, - status=original_params.status, - status_not_in=original_params.status_not_in, - popularity_greater=original_params.popularity_greater, - popularity_lesser=original_params.popularity_lesser, - averageScore_greater=original_params.averageScore_greater, - averageScore_lesser=original_params.averageScore_lesser, - seasonYear=original_params.seasonYear, - season=original_params.season, - startDate_greater=original_params.startDate_greater, - startDate_lesser=original_params.startDate_lesser, - startDate=original_params.startDate, - endDate_greater=original_params.endDate_greater, - endDate_lesser=original_params.endDate_lesser, - format_in=original_params.format_in, - type=original_params.type, - on_list=original_params.on_list, - ) - - result = ctx.media_api.search_media(new_params) - - return State( - menu_name="RESULTS", - media_api=MediaApiState( - search_results=result, - original_api_params=original_params, # Keep original params for further pagination - original_user_list_params=state.media_api.original_user_list_params, - ), - provider=state.provider, # Preserve provider state if it exists - ) - - -def _fetch_user_list_page( - ctx: Context, state: State, page: int, feedback -) -> State | InternalDirective: - """Fetch a specific page for user list results.""" - original_params = state.media_api.original_user_list_params - if not original_params: - feedback.error("No original user list parameters found") - return InternalDirective.CONTINUE - - # Create new parameters with updated page number - new_params = UserMediaListSearchParams( - status=original_params.status, - page=page, - per_page=original_params.per_page, - ) - - result = ctx.media_api.search_media_list(new_params) - - return State( - menu_name="RESULTS", - media_api=MediaApiState( - search_results=result, - original_api_params=state.media_api.original_api_params, - original_user_list_params=original_params, # Keep original params for further pagination - ), - provider=state.provider, # Preserve provider state if it exists - ) + # print(new_search_params) + # print(result) + feedback.warning("Failed to load page") + return InternalDirective.RELOAD diff --git a/fastanime/cli/interactive/menus/servers.py b/fastanime/cli/interactive/menus/servers.py index 540ef44..af315f2 100644 --- a/fastanime/cli/interactive/menus/servers.py +++ b/fastanime/cli/interactive/menus/servers.py @@ -91,7 +91,7 @@ def servers(ctx: Context, state: State) -> State | InternalDirective: console.print( f"[bold red]No stream of quality '{config.stream.quality}' found on server '{selected_server.name}'.[/bold red]" ) - return InternalDirective.CONTINUE + return InternalDirective.RELOAD # --- Launch Player --- final_title = f"{provider_anime.title} - Ep {episode_number}" diff --git a/fastanime/cli/interactive/menus/watch_history.py b/fastanime/cli/interactive/menus/watch_history.py index e1f04ed..58cb0c2 100644 --- a/fastanime/cli/interactive/menus/watch_history.py +++ b/fastanime/cli/interactive/menus/watch_history.py @@ -90,7 +90,7 @@ def watch_history(ctx: Context, state: State) -> State | InternalDirective: if result == "BACK": return InternalDirective.BACK else: - return InternalDirective.CONTINUE + return InternalDirective.RELOAD def _display_history_stats( diff --git a/fastanime/cli/interactive/session.py b/fastanime/cli/interactive/session.py index 3e89d7e..b7c366b 100644 --- a/fastanime/cli/interactive/session.py +++ b/fastanime/cli/interactive/session.py @@ -2,8 +2,7 @@ import importlib.util import logging import os from dataclasses import dataclass -from pathlib import Path -from typing import Callable, List, Optional +from typing import Callable, List, Optional, Union import click @@ -23,12 +22,11 @@ from ..services.feedback import FeedbackService from ..services.registry import MediaRegistryService from ..services.session import SessionsService from ..services.watch_history import WatchHistoryService -from .state import InternalDirective, State +from .state import InternalDirective, MenuName, State logger = logging.getLogger(__name__) # A type alias for the signature all menu functions must follow. -MenuFunction = Callable[["Context", State], "State | ControlFlow"] MENUS_DIR = APP_DIR / "cli" / "interactive" / "menus" @@ -52,16 +50,19 @@ class Context: services: Services +MenuFunction = Callable[[Context, State], Union[State, InternalDirective]] + + @dataclass(frozen=True) class Menu: - name: str + name: MenuName execute: MenuFunction class Session: _context: Context _history: List[State] = [] - _menus: dict[str, Menu] = {} + _menus: dict[MenuName, Menu] = {} def _load_context(self, config: AppConfig): """Initializes all shared services based on the provided configuration.""" @@ -122,7 +123,7 @@ class Session: logger.warning("Failed to continue from history. No sessions found") if not self._history: - self._history.append(State(menu_name="MAIN")) + self._history.append(State(menu_name=MenuName.MAIN)) try: self._run_main_loop() @@ -141,8 +142,12 @@ class Session: ) if isinstance(next_step, InternalDirective): - if next_step == InternalDirective.EXIT: - break + if next_step == InternalDirective.MAIN: + self._history = [self._history[0]] + if next_step == InternalDirective.RELOAD: + continue + elif next_step == InternalDirective.CONFIG_EDIT: + self._edit_config() elif next_step == InternalDirective.BACK: if len(self._history) > 1: self._history.pop() @@ -155,21 +160,17 @@ class Session: self._history.pop() self._history.pop() self._history.pop() - elif next_step == InternalDirective.CONFIG_EDIT: - self._edit_config() + elif next_step == InternalDirective.EXIT: + break else: - # if the state is main menu we should reset the history - if next_step.menu_name == "MAIN": - self._history = [next_step] - else: - self._history.append(next_step) + self._history.append(next_step) @property def menu(self) -> Callable[[MenuFunction], MenuFunction]: """A decorator to register a function as a menu.""" def decorator(func: MenuFunction) -> MenuFunction: - menu_name = func.__name__.upper() + menu_name = MenuName(func.__name__.upper()) if menu_name in self._menus: logger.warning(f"Menu '{menu_name}' is being redefined.") self._menus[menu_name] = Menu(name=menu_name, execute=func) diff --git a/fastanime/cli/interactive/state.py b/fastanime/cli/interactive/state.py index 6142a63..217e5d2 100644 --- a/fastanime/cli/interactive/state.py +++ b/fastanime/cli/interactive/state.py @@ -10,6 +10,8 @@ from ...libs.providers.anime.types import Anime, SearchResults, Server # TODO: is internal directive a good name class InternalDirective(Enum): + MAIN = "MAIN" + BACK = auto() BACKX2 = auto() @@ -20,7 +22,7 @@ class InternalDirective(Enum): CONFIG_EDIT = auto() - CONTINUE = auto() + RELOAD = auto() class MenuName(Enum): @@ -34,6 +36,7 @@ class MenuName(Enum): PLAYER_CONTROLS = "PLAYER_CONTROLS" USER_MEDIA_LIST = "USER_MEDIA_LIST" SESSION_MANAGEMENT = "SESSION_MANAGEMENT" + MEDIA_ACTIONS = "MEDIA_ACTIONS" class StateModel(BaseModel): diff --git a/fastanime/cli/services/feedback/service.py b/fastanime/cli/services/feedback/service.py index 823e179..b98a533 100644 --- a/fastanime/cli/services/feedback/service.py +++ b/fastanime/cli/services/feedback/service.py @@ -1,3 +1,4 @@ +import time from contextlib import contextmanager from typing import Optional @@ -24,6 +25,7 @@ class FeedbackService: console.print(f"{main_msg}\n[dim]{details}[/dim]") else: console.print(main_msg) + time.sleep(5) def error(self, message: str, details: Optional[str] = None) -> None: """Show an error message with optional details.""" @@ -34,6 +36,7 @@ class FeedbackService: console.print(f"{main_msg}\n[dim]{details}[/dim]") else: console.print(main_msg) + time.sleep(5) def warning(self, message: str, details: Optional[str] = None) -> None: """Show a warning message with optional details.""" @@ -44,6 +47,7 @@ class FeedbackService: console.print(f"{main_msg}\n[dim]{details}[/dim]") else: console.print(main_msg) + time.sleep(5) def info(self, message: str, details: Optional[str] = None) -> None: """Show an informational message with optional details.""" @@ -54,24 +58,10 @@ class FeedbackService: console.print(f"{main_msg}\n[dim]{details}[/dim]") else: console.print(main_msg) - - def notify_operation_result( - self, - operation_name: str, - success: bool, - success_msg: Optional[str] = None, - error_msg: Optional[str] = None, - ) -> None: - """Notify user of operation result with standardized messaging.""" - if success: - msg = success_msg or f"{operation_name} completed successfully" - self.success(msg) - else: - msg = error_msg or f"{operation_name} failed" - self.error(msg) + time.sleep(5) @contextmanager - def loading_operation( + def progress( self, message: str, success_msg: Optional[str] = None, @@ -100,12 +90,5 @@ class FeedbackService: icon = "⏸️ " if self.icons_enabled else "" click.pause(f"{icon}{message}...") - def show_detailed_panel( - self, title: str, content: str, style: str = "blue" - ) -> None: - """Show detailed information in a styled panel.""" - console.print(Panel(content, title=title, border_style=style, expand=True)) - self.pause_for_user() - def clear_console(self): console.clear() diff --git a/fastanime/cli/services/registry/service.py b/fastanime/cli/services/registry/service.py index f68a992..40e8c6d 100644 --- a/fastanime/cli/services/registry/service.py +++ b/fastanime/cli/services/registry/service.py @@ -192,7 +192,8 @@ class MediaRegistryService: index.media_index[f"{self._media_api}_{media_id}"] = index_entry self._save_index(index) - def get_recently_watched(self, limit: int) -> MediaSearchResult: + # TODO: standardize params passed to this + def get_recently_watched(self, limit: Optional[int] = None) -> MediaSearchResult: """Get recently watched anime.""" index = self._load_index() @@ -205,8 +206,8 @@ class MediaRegistryService: record = self.get_media_record(entry.media_id) if record: recent_media.append(record.media_item) - if len(recent_media) == limit: - break + # if len(recent_media) == limit: + # break page_info = PageInfo( total=len(sorted_entries), diff --git a/fastanime/libs/api/anilist/api.py b/fastanime/libs/api/anilist/api.py index d149d07..47a3bcd 100644 --- a/fastanime/libs/api/anilist/api.py +++ b/fastanime/libs/api/anilist/api.py @@ -111,7 +111,7 @@ class AniListApi(BaseApiClient): { 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) + if v is not None and isinstance(v, list) and isinstance(v[0], Enum) } ) @@ -143,7 +143,7 @@ class AniListApi(BaseApiClient): variables = { "sort": params.sort.value if params.sort - else self.config.media_list_sort_by, + else self.config.media_list_sort_by.value, "userId": self.user_profile.id, "status": user_list_status_map[params.status] if params.status else None, "page": params.page, diff --git a/fastanime/libs/api/types.py b/fastanime/libs/api/types.py index c8fb839..340c50b 100644 --- a/fastanime/libs/api/types.py +++ b/fastanime/libs/api/types.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from datetime import datetime from enum import Enum from typing import List, Optional @@ -62,6 +64,141 @@ class MediaFormat(Enum): ONE_SHOT = "ONE_SHOT" +# MODELS +class BaseMediaApiModel(BaseModel): + model_config = ConfigDict(frozen=True) + + +class MediaImage(BaseMediaApiModel): + """A generic representation of media imagery URLs.""" + + large: str + medium: Optional[str] = None + extra_large: Optional[str] = None + + +class MediaTitle(BaseMediaApiModel): + """A generic representation of media titles.""" + + english: str + romaji: Optional[str] = None + native: Optional[str] = None + + +class MediaTrailer(BaseMediaApiModel): + """A generic representation of a media trailer.""" + + id: str + site: str # e.g., "youtube" + thumbnail_url: Optional[str] = None + + +class AiringSchedule(BaseMediaApiModel): + """A generic representation of the next airing episode.""" + + episode: int + airing_at: Optional[datetime] = None + + +class Studio(BaseMediaApiModel): + """A generic representation of an animation studio.""" + + id: Optional[int] = None + name: Optional[str] = None + favourites: Optional[int] = None + is_animation_studio: Optional[bool] = None + + +class MediaTagItem(BaseMediaApiModel): + """A generic representation of a descriptive tag.""" + + name: MediaTag + rank: Optional[int] = None # Percentage relevance from 0-100 + + +class StreamingEpisode(BaseMediaApiModel): + """A generic representation of a streaming episode.""" + + title: str + thumbnail: Optional[str] = None + + +class UserListItem(BaseMediaApiModel): + """Generic representation of a user's list status for a media item.""" + + id: Optional[int] = None + status: Optional[UserMediaListStatus] = None + progress: Optional[int] = None + score: Optional[float] = None + repeat: Optional[int] = None + notes: Optional[str] = None + start_date: Optional[datetime] = None + completed_at: Optional[datetime] = None + created_at: Optional[str] = None + + +class MediaItem(BaseMediaApiModel): + id: int + title: MediaTitle + id_mal: Optional[int] = None + type: MediaType = MediaType.ANIME + status: MediaStatus = MediaStatus.FINISHED + format: MediaFormat = MediaFormat.TV + + cover_image: Optional[MediaImage] = None + banner_image: Optional[str] = None + trailer: Optional[MediaTrailer] = None + + description: Optional[str] = None + episodes: Optional[int] = None + duration: Optional[int] = None # In minutes + genres: List[MediaGenre] = Field(default_factory=list) + tags: List[MediaTagItem] = Field(default_factory=list) + studios: List[Studio] = Field(default_factory=list) + synonymns: List[str] = Field(default_factory=list) + + average_score: Optional[float] = None + popularity: Optional[int] = None + favourites: Optional[int] = None + + start_date: Optional[datetime] = None + end_date: Optional[datetime] = None + + next_airing: Optional[AiringSchedule] = None + + # streaming episodes + streaming_episodes: List[StreamingEpisode] = Field(default_factory=list) + + # user related + user_status: Optional[UserListItem] = None + + +class PageInfo(BaseMediaApiModel): + """Generic pagination information.""" + + total: int = 1 + current_page: int = 1 + has_next_page: bool = False + per_page: int = 15 + + +class MediaSearchResult(BaseMediaApiModel): + """A generic representation of a page of media search results.""" + + page_info: PageInfo + media: List[MediaItem] = Field(default_factory=list) + + +class UserProfile(BaseMediaApiModel): + """A generic representation of a user's profile.""" + + id: int + name: str + avatar_url: Optional[str] = None + banner_url: Optional[str] = None + + +# ENUMS class MediaTag(Enum): # Cast POLYAMOROUS = "Polyamorous" @@ -91,6 +228,7 @@ class MediaTag(Enum): ARRANGED_MARRIAGE = "Arranged Marriage" ARTIFICIAL_INTELLIGENCE = "Artificial Intelligence" ASEXUAL = "Asexual" + BISEXUAL = "Bisexual" BUTLER = "Butler" CENTAUR = "Centaur" CHIMERA = "Chimera" @@ -109,7 +247,6 @@ class MediaTag(Enum): DRAGONS = "Dragons" DULLAHAN = "Dullahan" ELF = "Elf" - EXHIBITIONISM = "Exhibitionism" FAIRY = "Fairy" FEMBOY = "Femboy" GHOST = "Ghost" @@ -119,7 +256,6 @@ class MediaTag(Enum): HIKIKOMORI = "Hikikomori" HOMELESS = "Homeless" IDOL = "Idol" - INSEKI = "Inseki" KEMONOMIMI = "Kemonomimi" KUUDERE = "Kuudere" MAIDS = "Maids" @@ -150,13 +286,12 @@ class MediaTag(Enum): VETERINARIAN = "Veterinarian" VIKINGS = "Vikings" VILLAINESS = "Villainess" - VIRGINITY = "Virginity" VTUBER = "VTuber" WEREWOLF = "Werewolf" WITCH = "Witch" YANDERE = "Yandere" + YOUKAI = "Youkai" ZOMBIE = "Zombie" - YOUKAI = "Youkai" # Added # Demographic JOSEI = "Josei" @@ -171,6 +306,7 @@ class MediaTag(Enum): # Setting Scene BAR = "Bar" BOARDING_SCHOOL = "Boarding School" + CAMPING = "Camping" CIRCUS = "Circus" COASTAL = "Coastal" COLLEGE = "College" @@ -181,7 +317,7 @@ class MediaTag(Enum): KONBINI = "Konbini" NATURAL_DISASTER = "Natural Disaster" OFFICE = "Office" - OUTDOOR = "Outdoor" + OUTDOOR_ACTIVITIES = "Outdoor Activities" PRISON = "Prison" RESTAURANT = "Restaurant" RURAL = "Rural" @@ -189,6 +325,7 @@ class MediaTag(Enum): SCHOOL_CLUB = "School Club" SNOWSCAPE = "Snowscape" URBAN = "Urban" + WILDERNESS = "Wilderness" WORK = "Work" # Setting Time @@ -197,6 +334,7 @@ class MediaTag(Enum): ANCIENT_CHINA = "Ancient China" DYSTOPIAN = "Dystopian" HISTORICAL = "Historical" + MEDIEVAL = "Medieval" TIME_SKIP = "Time Skip" # Setting Universe @@ -209,6 +347,72 @@ class MediaTag(Enum): URBAN_FANTASY = "Urban Fantasy" VIRTUAL_WORLD = "Virtual World" + # Sexual Content + AHEGAO = "Ahegao" + AMPUTATION = "Amputation" + ANAL_SEX = "Anal Sex" + ARMPITS = "Armpits" + ASHIKOKI = "Ashikoki" + ASPHYXIATION = "Asphyxiation" + BONDAGE = "Bondage" + BOOBJOB = "Boobjob" + CERVIX_PENETRATION = "Cervix Penetration" + CHEATING = "Cheating" + CUMFLATION = "Cumflation" + CUNNILINGUS = "Cunnilingus" + DEEPTHROAT = "Deepthroat" + DEFLORATION = "Defloration" + DILF = "DILF" + DOUBLE_PENETRATION = "Double Penetration" + EROTIC_PIERCINGS = "Erotic Piercings" + EXHIBITIONISM = "Exhibitionism" + FACIAL = "Facial" + FEET = "Feet" + FELLATIO = "Fellatio" + FEMDOM = "Femdom" + FISTING = "Fisting" + FLAT_CHEST = "Flat Chest" + FUTANARI = "Futanari" + GROUP_SEX = "Group Sex" + HAIR_PULLING = "Hair Pulling" + HANDJOB = "Handjob" + HUMAN_PET = "Human Pet" + HYPERSEXUALITY = "Hypersexuality" + INCEST = "Incest" + INSEKI = "Inseki" + IRRUMATIO = "Irrumatio" + LACTATION = "Lactation" + LARGE_BREASTS = "Large Breasts" + MALE_PREGNANCY = "Male Pregnancy" + MASOCHISM = "Masochism" + MASTURBATION = "Masturbation" + MATING_PRESS = "Mating Press" + MILF = "MILF" + NAKADASHI = "Nakadashi" + NETORARE = "Netorare" + NETORASE = "Netorase" + NETORI = "Netori" + PET_PLAY = "Pet Play" + PROSTITUTION = "Prostitution" + PUBLIC_SEX = "Public Sex" + RAPE = "Rape" + RIMJOB = "Rimjob" + SADISM = "Sadism" + SCAT = "Scat" + SCISSORING = "Scissoring" + SEX_TOYS = "Sex Toys" + SHIMAIDON = "Shimaidon" + SQUIRTING = "Squirting" + SUMATA = "Sumata" + SWEAT = "Sweat" + TENTACLES = "Tentacles" + THREESOME = "Threesome" + VIRGINITY = "Virginity" + VORE = "Vore" + VOYEUR = "Voyeur" + WATERSPORTS = "Watersports" + ZOOPHILIA = "Zoophilia" + # Technical _4_KOMA = "4-koma" ACHROMATIC = "Achromatic" @@ -219,12 +423,15 @@ class MediaTag(Enum): FLASH = "Flash" FULL_CGI = "Full CGI" FULL_COLOR = "Full Color" + LONG_STRIP = "Long Strip" + MIXED_MEDIA = "Mixed Media" NO_DIALOGUE = "No Dialogue" NON_FICTION = "Non-fiction" POV = "POV" PUPPETRY = "Puppetry" ROTOSCOPING = "Rotoscoping" STOP_MOTION = "Stop Motion" + VERTICAL_VIDEO = "Vertical Video" # Theme Action ARCHERY = "Archery" @@ -272,9 +479,6 @@ class MediaTag(Enum): ECO_HORROR = "Eco-Horror" FAKE_RELATIONSHIP = "Fake Relationship" KINGDOM_MANAGEMENT = "Kingdom Management" - MASTURBATION = "Masturbation" - PREGNANCY = "Pregnancy" - RAPE = "Rape" REHABILITATION = "Rehabilitation" REVENGE = "Revenge" SUICIDE = "Suicide" @@ -283,8 +487,8 @@ class MediaTag(Enum): # Theme Fantasy ALCHEMY = "Alchemy" BODY_SWAPPING = "Body Swapping" - CURSES = "Curses" CULTIVATION = "Cultivation" + CURSES = "Curses" EXORCISM = "Exorcism" FAIRY_TALE = "Fairy Tale" HENSHIN = "Henshin" @@ -292,7 +496,6 @@ class MediaTag(Enum): KAIJU = "Kaiju" MAGIC = "Magic" MYTHOLOGY = "Mythology" - MEDIEVAL = "Medieval" NECROMANCY = "Necromancy" SHAPESHIFTING = "Shapeshifting" STEAMPUNK = "Steampunk" @@ -352,18 +555,17 @@ class MediaTag(Enum): ASTRONOMY = "Astronomy" AUTOBIOGRAPHICAL = "Autobiographical" BIOGRAPHICAL = "Biographical" + BLACKMAIL = "Blackmail" 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" @@ -374,23 +576,21 @@ class MediaTag(Enum): GAMBLING = "Gambling" GENDER_BENDING = "Gender Bending" GORE = "Gore" - HYPERSEXUALITY = "Hypersexuality" + INDIGENOUS_CULTURES = "Indigenous Cultures" 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" + PREGNANCY = "Pregnancy" PROXY_BATTLE = "Proxy Battle" PSYCHOSEXUAL = "Psychosexual" REINCARNATION = "Reincarnation" @@ -401,12 +601,10 @@ class MediaTag(Enum): SOFTWARE_DEVELOPMENT = "Software Development" SURVIVAL = "Survival" TERRORISM = "Terrorism" - THREESOME = "Threesome" TORTURE = "Torture" TRAVEL = "Travel" + VOCAL_SYNTH = "Vocal Synth" WAR = "War" - WILDERNESS = "Wilderness" - VORE = "Vore" # Added # Theme Other-Organisations ASSASSINS = "Assassins" @@ -431,28 +629,26 @@ class MediaTag(Enum): # Theme Romance AGE_GAP = "Age Gap" - BISEXUAL = "Bisexual" BOYS_LOVE = "Boys' Love" + COHABITATION = "Cohabitation" 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 + # Theme Sci-Fi CYBERPUNK = "Cyberpunk" SPACE_OPERA = "Space Opera" TIME_LOOP = "Time Loop" TIME_MANIPULATION = "Time Manipulation" TOKUSATSU = "Tokusatsu" - # Theme Sci Fi-Mecha + # Theme Sci-Fi-Mecha REAL_ROBOT = "Real Robot" SUPER_ROBOT = "Super Robot" @@ -466,141 +662,6 @@ class MediaTag(Enum): PARENTHOOD = "Parenthood" -# MODELS -class BaseApiModel(BaseModel): - model_config = ConfigDict(frozen=True) - - -class MediaImage(BaseApiModel): - """A generic representation of media imagery URLs.""" - - large: str - medium: Optional[str] = None - extra_large: Optional[str] = None - - -class MediaTitle(BaseApiModel): - """A generic representation of media titles.""" - - english: str - romaji: Optional[str] = None - native: Optional[str] = None - - -class MediaTrailer(BaseApiModel): - """A generic representation of a media trailer.""" - - id: str - site: str # e.g., "youtube" - thumbnail_url: Optional[str] = None - - -class AiringSchedule(BaseApiModel): - """A generic representation of the next airing episode.""" - - episode: int - airing_at: Optional[datetime] = None - - -class Studio(BaseApiModel): - """A generic representation of an animation studio.""" - - id: Optional[int] = None - name: Optional[str] = None - favourites: Optional[int] = None - is_animation_studio: Optional[bool] = None - - -class MediaTagItem(BaseApiModel): - """A generic representation of a descriptive tag.""" - - name: MediaTag - rank: Optional[int] = None # Percentage relevance from 0-100 - - -class StreamingEpisode(BaseApiModel): - """A generic representation of a streaming episode.""" - - title: str - thumbnail: Optional[str] = None - - -class UserListItem(BaseApiModel): - """Generic representation of a user's list status for a media item.""" - - id: Optional[int] = None - status: Optional[UserMediaListStatus] = None - progress: Optional[int] = None - score: Optional[float] = None - repeat: Optional[int] = None - notes: Optional[str] = None - start_date: Optional[datetime] = None - completed_at: Optional[datetime] = None - created_at: Optional[str] = None - - -class MediaItem(BaseApiModel): - id: int - title: MediaTitle - id_mal: Optional[int] = None - type: MediaType = MediaType.ANIME - status: MediaStatus = MediaStatus.FINISHED - format: MediaFormat = MediaFormat.TV - - cover_image: Optional[MediaImage] = None - banner_image: Optional[str] = None - trailer: Optional[MediaTrailer] = None - - description: Optional[str] = None - episodes: Optional[int] = None - duration: Optional[int] = None # In minutes - genres: List[MediaGenre] = Field(default_factory=list) - tags: List[MediaTagItem] = Field(default_factory=list) - studios: List[Studio] = Field(default_factory=list) - synonymns: List[str] = Field(default_factory=list) - - average_score: Optional[float] = None - popularity: Optional[int] = None - favourites: Optional[int] = None - - start_date: Optional[datetime] = None - end_date: Optional[datetime] = None - - next_airing: Optional[AiringSchedule] = None - - # streaming episodes - streaming_episodes: List[StreamingEpisode] = Field(default_factory=list) - - # user related - user_status: Optional[UserListItem] = None - - -class PageInfo(BaseApiModel): - """Generic pagination information.""" - - total: int = 1 - current_page: int = 1 - has_next_page: bool = False - per_page: int = 15 - - -class MediaSearchResult(BaseApiModel): - """A generic representation of a page of media search results.""" - - page_info: PageInfo - media: List[MediaItem] = Field(default_factory=list) - - -class UserProfile(BaseApiModel): - """A generic representation of a user's profile.""" - - id: int - name: str - avatar_url: Optional[str] = None - banner_url: Optional[str] = None - - -# ENUMS class MediaSort(Enum): ID = "ID" ID_DESC = "ID_DESC" diff --git a/tags.json b/tags.json new file mode 100644 index 0000000..b1d92e7 --- /dev/null +++ b/tags.json @@ -0,0 +1 @@ +{"data":{"MediaTagCollection":[{"name":"4-koma","description":"A manga in the 'yonkoma' format, which consists of four equal-sized panels arranged in a vertical strip.","category":"Technical","isAdult":false},{"name":"Achromatic","description":"Contains animation that is primarily done in black and white.","category":"Technical","isAdult":false},{"name":"Achronological Order","description":"Chapters or episodes do not occur in chronological order.","category":"Setting-Time","isAdult":false},{"name":"Acrobatics","description":"The art of jumping, tumbling, and balancing. Often paired with trapeze, trampolining, tightropes, or general gymnastics.","category":"Theme-Game-Sport","isAdult":false},{"name":"Acting","description":"Centers around actors or the acting industry.","category":"Theme-Arts","isAdult":false},{"name":"Adoption","description":"Features a character who has been adopted by someone who is neither of their biological parents.","category":"Theme-Other","isAdult":false},{"name":"Advertisement","description":"Produced in order to promote the products of a certain company.","category":"Technical","isAdult":false},{"name":"Afterlife","description":"Partly or completely set in the afterlife.","category":"Setting-Universe","isAdult":false},{"name":"Age Gap","description":"Prominently features romantic relations between people with a significant age difference.","category":"Theme-Romance","isAdult":false},{"name":"Age Regression","description":"Prominently features a character who was returned to a younger state.","category":"Cast-Traits","isAdult":false},{"name":"Agender","description":"Prominently features agender characters.","category":"Cast-Traits","isAdult":false},{"name":"Agriculture","description":"Prominently features agriculture practices.","category":"Theme-Slice of Life","isAdult":false},{"name":"Ahegao","description":"Features a character making an exaggerated orgasm face.","category":"Sexual Content","isAdult":true},{"name":"Airsoft","description":"Centers around the sport of airsoft.","category":"Theme-Game-Sport","isAdult":false},{"name":"Alchemy","description":"Features character(s) who practice alchemy.","category":"Theme-Fantasy","isAdult":false},{"name":"Aliens","description":"Prominently features extraterrestrial lifeforms.","category":"Cast-Traits","isAdult":false},{"name":"Alternate Universe","description":"Features multiple alternate universes in the same series.","category":"Setting-Universe","isAdult":false},{"name":"American Football","description":"Centers around the sport of American football.","category":"Theme-Game-Sport","isAdult":false},{"name":"Amnesia","description":"Prominently features a character(s) with memory loss.","category":"Cast-Traits","isAdult":false},{"name":"Amputation","description":"Features amputation or amputees.","category":"Sexual Content","isAdult":true},{"name":"Anachronism","description":"Prominently features elements that are out of place in the historical period the work takes place in, particularly modern elements in a historical setting.","category":"Setting-Time","isAdult":false},{"name":"Anal Sex","description":"Features sexual penetration of the anal cavity.","category":"Sexual Content","isAdult":true},{"name":"Ancient China","description":"Setting in ancient china, does not apply to fantasy settings.","category":"Setting-Time","isAdult":false},{"name":"Angels","description":"Prominently features spiritual beings usually represented with wings and halos and believed to be attendants of God.","category":"Cast-Traits","isAdult":false},{"name":"Animals","description":"Prominently features animal characters in a leading role.","category":"Theme-Other","isAdult":false},{"name":"Anthology","description":"A collection of separate works collated into a single release.","category":"Technical","isAdult":false},{"name":"Anthropomorphism","description":"Contains non-human character(s) that have attributes or characteristics of a human being.","category":"Cast-Traits","isAdult":false},{"name":"Anti-Hero","description":"Features a protagonist who lacks conventional heroic attributes and may be considered a borderline villain.","category":"Cast-Main Cast","isAdult":false},{"name":"Archery","description":"Centers around the sport of archery, or prominently features the use of archery in combat.","category":"Theme-Action","isAdult":false},{"name":"Armpits","description":"Features the sexual depiction or stimulation of a character's armpits.","category":"Sexual Content","isAdult":true},{"name":"Aromantic","description":"Features a character who experiences little to no romantic attraction.","category":"Cast-Traits","isAdult":false},{"name":"Arranged Marriage","description":"Features two characters made to marry each other, usually by their family.","category":"Cast-Traits","isAdult":false},{"name":"Artificial Intelligence","description":"Intelligent non-organic machines that work and react similarly to humans.","category":"Cast-Traits","isAdult":false},{"name":"Asexual","description":"Features a character who isn't sexually attracted to people of any sex or gender.","category":"Cast-Traits","isAdult":false},{"name":"Ashikoki","description":"Footjob; features stimulation of genitalia by feet.","category":"Sexual Content","isAdult":true},{"name":"Asphyxiation","description":"Features breath play.","category":"Sexual Content","isAdult":true},{"name":"Assassins","description":"Centers around characters who murder people as a profession.","category":"Theme-Other-Organisations","isAdult":false},{"name":"Astronomy","description":"Relating or centered around the study of celestial objects and phenomena, space, or the universe.","category":"Theme-Other","isAdult":false},{"name":"Athletics","description":"Centers around sporting events that involve competitive running, jumping, throwing, or walking.","category":"Theme-Game-Sport","isAdult":false},{"name":"Augmented Reality","description":"Prominently features events with augmented reality as the main setting.","category":"Setting-Universe","isAdult":false},{"name":"Autobiographical","description":"Real stories and anecdotes written by the author about their own life.","category":"Theme-Other","isAdult":false},{"name":"Aviation","description":"Regarding the flying or operation of aircraft.","category":"Theme-Other-Vehicle","isAdult":false},{"name":"Badminton","description":"Centers around the sport of badminton.","category":"Theme-Game-Sport","isAdult":false},{"name":"Band","description":"Main cast is a group of musicians.","category":"Theme-Arts-Music","isAdult":false},{"name":"Bar","description":"Partly or completely set in a bar.","category":"Setting-Scene","isAdult":false},{"name":"Baseball","description":"Centers around the sport of baseball.","category":"Theme-Game-Sport","isAdult":false},{"name":"Basketball","description":"Centers around the sport of basketball.","category":"Theme-Game-Sport","isAdult":false},{"name":"Battle Royale","description":"Centers around a fierce group competition, often violent and with only one winner.","category":"Theme-Action","isAdult":false},{"name":"Biographical","description":"Based on true stories of real persons living or dead, written by another.","category":"Theme-Other","isAdult":false},{"name":"Bisexual","description":"Features a character who is romantically or sexually attracted to people of more than one sex or gender.","category":"Cast-Traits","isAdult":false},{"name":"Blackmail","description":"Features a character blackmailing another.","category":"Theme-Other","isAdult":false},{"name":"Board Game","description":"Centers around characters playing board games.","category":"Theme-Game","isAdult":false},{"name":"Boarding School","description":"Features characters attending a boarding school.","category":"Setting-Scene","isAdult":false},{"name":"Body Horror","description":"Features characters who undergo horrific transformations or disfigurement, often to their own detriment.","category":"Theme-Other","isAdult":false},{"name":"Body Image","description":"Features themes of self-esteem concerning perceived defects or flaws in appearance, such as body weight or disfigurement, and may discuss topics such as eating disorders, fatphobia, and body dysmorphia.","category":"Theme-Other","isAdult":false},{"name":"Body Swapping","description":"Centers around individuals swapping bodies with one another.","category":"Theme-Fantasy","isAdult":false},{"name":"Bondage","description":"Features BDSM, with or without the use of accessories.","category":"Sexual Content","isAdult":true},{"name":"Boobjob","description":"Features the stimulation of male genitalia by breasts.","category":"Sexual Content","isAdult":true},{"name":"Bowling","description":"Centers around the sport of Bowling.","category":"Theme-Game-Sport","isAdult":false},{"name":"Boxing","description":"Centers around the sport of boxing.","category":"Theme-Game-Sport","isAdult":false},{"name":"Boys' Love","description":"Prominently features romance between two males, not inherently sexual.","category":"Theme-Romance","isAdult":false},{"name":"Bullying","description":"Prominently features the use of force for intimidation, often in a school setting.","category":"Theme-Drama","isAdult":false},{"name":"Butler","description":"Prominently features a character who is a butler.","category":"Cast-Traits","isAdult":false},{"name":"Calligraphy","description":"Centers around the art of calligraphy.","category":"Theme-Arts","isAdult":false},{"name":"Camping","description":"Features the recreational activity of camping, either in a tent, vehicle, or simply sleeping outdoors.","category":"Setting-Scene","isAdult":false},{"name":"Cannibalism","description":"Prominently features the act of consuming another member of the same species as food.","category":"Theme-Other","isAdult":false},{"name":"Card Battle","description":"Centers around individuals competing in card games.","category":"Theme-Game-Card & Board Game","isAdult":false},{"name":"Cars","description":"Centers around the use of automotive vehicles.","category":"Theme-Other-Vehicle","isAdult":false},{"name":"Centaur","description":"Prominently features a character with a human upper body and the lower body of a horse.","category":"Cast-Traits","isAdult":false},{"name":"Cervix Penetration","description":"A sexual act in which the cervix is visibly penetrated.","category":"Sexual Content","isAdult":true},{"name":"CGI","description":"Prominently features scenes created with computer-generated imagery.","category":"Technical","isAdult":false},{"name":"Cheating","description":"Features a character with a partner shown being intimate with someone else consensually.","category":"Sexual Content","isAdult":true},{"name":"Cheerleading","description":"Centers around the activity of cheerleading.","category":"Theme-Game-Sport","isAdult":false},{"name":"Chibi","description":"Features \"super deformed\" character designs with smaller, rounder proportions and a cute look.","category":"Theme-Other","isAdult":false},{"name":"Chimera","description":"Features a beast made by combining animals, usually with humans.","category":"Cast-Traits","isAdult":false},{"name":"Chuunibyou","description":"Prominently features a character with \"Middle School 2nd Year Syndrome\", who either acts like a know-it-all adult or falsely believes they have special powers.","category":"Cast-Traits","isAdult":false},{"name":"Circus","description":"Prominently features a circus.","category":"Setting-Scene","isAdult":false},{"name":"Class Struggle","description":"Contains conflict born between the different social classes. Generally between an dominant elite and a suffering oppressed group.","category":"Theme-Drama","isAdult":false},{"name":"Classic Literature","description":"Discusses or adapts a work of classic world literature.","category":"Theme-Arts","isAdult":false},{"name":"Classical Music","description":"Centers on the musical style of classical, not to be applied to anime that use classical in its soundtrack.","category":"Theme-Arts-Music","isAdult":false},{"name":"Clone","description":"Prominently features a character who is an artificial exact copy of another organism.","category":"Cast-Traits","isAdult":false},{"name":"Coastal","description":"Story prominently takes place near the beach or around a coastal area\/town. Setting is near the ocean.","category":"Setting-Scene","isAdult":false},{"name":"Cohabitation","description":"Features two or more people who live in the same household and develop a romantic or sexual relationship.","category":"Theme-Romance","isAdult":false},{"name":"College","description":"Partly or completely set in a college or university.","category":"Setting-Scene","isAdult":false},{"name":"Coming of Age","description":"Centers around a character's transition from childhood to adulthood.","category":"Theme-Drama","isAdult":false},{"name":"Conspiracy","description":"Contains one or more factions controlling or attempting to control the world from the shadows.","category":"Theme-Drama","isAdult":false},{"name":"Cosmic Horror","description":"A type of horror that emphasizes human insignificance in the grand scope of cosmic reality; fearing the unknown and being powerless to fight it.","category":"Theme-Other","isAdult":false},{"name":"Cosplay","description":"Features dressing up as a different character or profession.","category":"Cast-Traits","isAdult":false},{"name":"Cowboys","description":"Features Western or Western-inspired cowboys.","category":"Cast-Traits","isAdult":false},{"name":"Creature Taming","description":"Features the taming of animals, monsters, or other creatures.","category":"Theme-Other","isAdult":false},{"name":"Crime","description":"Centers around unlawful activities punishable by the state or other authority.","category":"Theme-Other","isAdult":false},{"name":"Criminal Organization","description":"Prominently features a group of people who commit crimes for illicit or violent purposes.","category":"Theme-Other-Organisations","isAdult":false},{"name":"Crossdressing","description":"Prominently features a character dressing up as the opposite sex.","category":"Cast-Traits","isAdult":false},{"name":"Crossover","description":"Centers around the placement of two or more otherwise discrete fictional characters, settings, or universes into the context of a single story.","category":"Theme-Other","isAdult":false},{"name":"Cult","description":"Features a social group with unorthodox religious, spiritual, or philosophical beliefs and practices.","category":"Theme-Other-Organisations","isAdult":false},{"name":"Cultivation","description":"Features characters using training, often martial arts-related, and other special methods to cultivate qi (a component of traditional Chinese philosophy, described as \"life force\") and gain strength or immortality.","category":"Theme-Fantasy","isAdult":false},{"name":"Cumflation","description":"The stomach area expands outward like a balloon due to being filled specifically with semen.","category":"Sexual Content","isAdult":true},{"name":"Cunnilingus","description":"Features oral sex performed on female genitalia.","category":"Sexual Content","isAdult":true},{"name":"Curses","description":"Features a character, object or area that has been cursed, usually by a malevolent supernatural force.","category":"Theme-Fantasy","isAdult":false},{"name":"Cute Boys Doing Cute Things","description":"Centers around male characters doing cute activities, usually with little to no emphasis on drama and conflict.","category":"Theme-Slice of Life","isAdult":false},{"name":"Cute Girls Doing Cute Things","description":"Centers around female characters doing cute activities, usually with little to no emphasis on drama and conflict.\n","category":"Theme-Slice of Life","isAdult":false},{"name":"Cyberpunk","description":"Set in a future of advanced technological and scientific achievements that have resulted in social disorder.","category":"Theme-Sci-Fi","isAdult":false},{"name":"Cyborg","description":"Prominently features a human character whose physiological functions are aided or enhanced by artificial means.","category":"Cast-Traits","isAdult":false},{"name":"Cycling","description":"Centers around the sport of cycling.","category":"Theme-Game-Sport","isAdult":false},{"name":"Dancing","description":"Centers around the art of dance.","category":"Theme-Arts-Music","isAdult":false},{"name":"Death Game","description":"Features characters participating in a game, where failure results in death.","category":"Theme-Other","isAdult":false},{"name":"Deepthroat","description":"Features oral sex where the majority of the erect male genitalia is inside another person's mouth, usually stimulating some gagging in the back of their throat.","category":"Sexual Content","isAdult":true},{"name":"Defloration","description":"Features a female character who has never had sexual relations (until now).","category":"Sexual Content","isAdult":true},{"name":"Delinquents","description":"Features characters with a notorious image and attitude, sometimes referred to as \"yankees\".","category":"Cast-Traits","isAdult":false},{"name":"Demons","description":"Prominently features malevolent otherworldly creatures.","category":"Cast-Traits","isAdult":false},{"name":"Denpa","description":"Works that feature themes of social dissociation, delusions, and other issues like suicide, bullying, self-isolation, paranoia, and technological necessity in daily lives. Classic iconography: telephone poles, rooftops, and trains.","category":"Theme-Other","isAdult":false},{"name":"Desert","description":"Prominently features a desert environment.","category":"Setting-Scene","isAdult":false},{"name":"Detective","description":"Features a character who investigates and solves crimes.","category":"Cast-Traits","isAdult":false},{"name":"DILF","description":"Features sexual intercourse with older men.","category":"Sexual Content","isAdult":true},{"name":"Dinosaurs","description":"Prominently features Dinosaurs, prehistoric reptiles that went extinct millions of years ago.","category":"Cast-Traits","isAdult":false},{"name":"Disability","description":"A work that features one or more characters with a physical, mental, cognitive, or developmental condition that impairs, interferes with, or limits the person's ability to engage in certain tasks or actions.","category":"Cast-Traits","isAdult":false},{"name":"Dissociative Identities","description":"A case where one or more people share the same body.","category":"Cast-Traits","isAdult":false},{"name":"Double Penetration","description":"A sexual act in which the vagina\/anus are penetrated by two penises\/toys.","category":"Sexual Content","isAdult":true},{"name":"Dragons","description":"Prominently features mythical reptiles which generally have wings and can breathe fire.","category":"Cast-Traits","isAdult":false},{"name":"Drawing","description":"Centers around the art of drawing, including manga and doujinshi.","category":"Theme-Arts","isAdult":false},{"name":"Drugs","description":"Prominently features the usage of drugs such as opioids, stimulants, hallucinogens etc.","category":"Theme-Other","isAdult":false},{"name":"Dullahan","description":"Prominently features a character who is a Dullahan, a creature from Irish Folklore with a head that can be detached from its main body.","category":"Cast-Traits","isAdult":false},{"name":"Dungeon","description":"Prominently features a dungeon environment.","category":"Setting-Scene","isAdult":false},{"name":"Dystopian","description":"Partly or completely set in a society characterized by poverty, squalor or oppression.","category":"Setting-Time","isAdult":false},{"name":"E-Sports","description":"Prominently features professional video game competitions, tournaments, players, etc.","category":"Theme-Game","isAdult":false},{"name":"Eco-Horror","description":"Utilizes a horrifying depiction of ecology to explore man and its relationship with nature.","category":"Theme-Drama","isAdult":false},{"name":"Economics","description":"Centers around the field of economics.","category":"Theme-Other","isAdult":false},{"name":"Educational","description":"Primary aim is to educate the audience.","category":"Theme-Other","isAdult":false},{"name":"Elderly Protagonist","description":"The protagonist is either over 60 years of age, has an elderly appearance, or, in the case of non-humans, is considered elderly for their species.","category":"Cast-Main Cast","isAdult":false},{"name":"Elf","description":"Prominently features a character who is an elf.","category":"Cast-Traits","isAdult":false},{"name":"Ensemble Cast","description":"Features a large cast of characters with (almost) equal screen time and importance to the plot.","category":"Cast-Main Cast","isAdult":false},{"name":"Environmental","description":"Concern with the state of the natural world and how humans interact with it.","category":"Theme-Other","isAdult":false},{"name":"Episodic","description":"Features story arcs that are loosely tied or lack an overarching plot.","category":"Technical","isAdult":false},{"name":"Ero Guro","description":"Japanese literary and artistic movement originating in the 1930's. Works have a focus on grotesque eroticism, sexual corruption, and decadence.","category":"Theme-Other","isAdult":false},{"name":"Erotic Piercings","description":"Features a type of body modification designed to enhance sexual pleasure and intimacy, and\/or decoratively adorns portions of the body considered sexual in nature.","category":"Sexual Content","isAdult":true},{"name":"Espionage","description":"Prominently features characters infiltrating an organization in order to steal sensitive information.","category":"Theme-Action","isAdult":false},{"name":"Estranged Family","description":"At least one family member of the MC intentionally distances themselves or a family distances themselves from a person related to the MC.","category":"Cast-Main Cast","isAdult":false},{"name":"Exhibitionism","description":"Features the act of exposing oneself in public for sexual pleasure.","category":"Sexual Content","isAdult":true},{"name":"Exorcism","description":"Involving religious methods of vanquishing youkai, demons, or other supernatural entities.","category":"Theme-Fantasy","isAdult":false},{"name":"Facial","description":"Features sexual ejaculation onto an individual's face.","category":"Sexual Content","isAdult":true},{"name":"Fairy","description":"Prominently features a character who is a fairy.","category":"Cast-Traits","isAdult":false},{"name":"Fairy Tale","description":"This work tells a fairy tale, centers around fairy tales, or is based on a classic fairy tale.","category":"Theme-Fantasy","isAdult":false},{"name":"Fake Relationship","description":"When two characters enter a fake relationship that mutually benefits one or both involved.","category":"Theme-Drama","isAdult":false},{"name":"Family Life","description":"Centers around the activities of a family unit.","category":"Theme-Slice of Life","isAdult":false},{"name":"Fashion","description":"Centers around the fashion industry.","category":"Theme-Arts","isAdult":false},{"name":"Feet","description":"Features the sexual depiction or stimulation of a character's feet.","category":"Sexual Content","isAdult":true},{"name":"Fellatio","description":"Blowjob; features oral sex performed on male genitalia.","category":"Sexual Content","isAdult":true},{"name":"Female Harem","description":"Main cast features the protagonist plus several female characters who are romantically interested in them.","category":"Theme-Romance","isAdult":false},{"name":"Female Protagonist","description":"Main character is female.","category":"Cast-Main Cast","isAdult":false},{"name":"Femboy","description":"Features a boy who exhibits characteristics or behaviors considered in many cultures to be typical of girls.","category":"Cast-Traits","isAdult":false},{"name":"Femdom","description":"Female Dominance. Features sexual acts with a woman in a dominant position.","category":"Sexual Content","isAdult":true},{"name":"Fencing","description":"Centers around the sport of fencing.","category":"Theme-Game-Sport","isAdult":false},{"name":"Filmmaking","description":"Centers around the art of filmmaking.","category":"Theme-Other","isAdult":false},{"name":"Firefighters","description":"Centered around the life and activities of rescuers specialised in firefighting.","category":"Theme-Other-Organisations","isAdult":false},{"name":"Fishing","description":"Centers around the sport of fishing.","category":"Theme-Game-Sport","isAdult":false},{"name":"Fisting","description":"A sexual activity that involves inserting one or more hands into the vagina or rectum.","category":"Sexual Content","isAdult":true},{"name":"Fitness","description":"Centers around exercise with the aim of improving physical health.","category":"Theme-Game-Sport","isAdult":false},{"name":"Flash","description":"Created using Flash animation techniques.","category":"Technical","isAdult":false},{"name":"Flat Chest","description":"Features a female character with smaller-than-average breasts.","category":"Sexual Content","isAdult":true},{"name":"Food","description":"Centers around cooking or food appraisal.","category":"Theme-Arts","isAdult":false},{"name":"Football","description":"Centers around the sport of football (known in the USA as \"soccer\").","category":"Theme-Game-Sport","isAdult":false},{"name":"Foreign","description":"Partly or completely set in a country outside the country of origin.","category":"Setting-Scene","isAdult":false},{"name":"Found Family","description":"Features a group of characters with no biological relations that are united in a group providing social support.","category":"Theme-Other","isAdult":false},{"name":"Fugitive","description":"Prominently features a character evading capture by an individual or organization.","category":"Theme-Action","isAdult":false},{"name":"Full CGI","description":"Almost entirely created with computer-generated imagery.","category":"Technical","isAdult":false},{"name":"Full Color","description":"Manga that were initially published in full color.","category":"Technical","isAdult":false},{"name":"Futanari","description":"Features female characters with male genitalia.","category":"Sexual Content","isAdult":true},{"name":"Gambling","description":"Centers around the act of gambling.","category":"Theme-Other","isAdult":false},{"name":"Gangs","description":"Centers around gang organizations.","category":"Theme-Other-Organisations","isAdult":false},{"name":"Gender Bending","description":"Prominently features a character who dresses and behaves in a way characteristic of another gender, or has been transformed into a person of another gender.","category":"Theme-Other","isAdult":false},{"name":"Ghost","description":"Prominently features a character who is a ghost.","category":"Cast-Traits","isAdult":false},{"name":"Go","description":"Centered around the game of Go.","category":"Theme-Game-Card & Board Game","isAdult":false},{"name":"Goblin","description":"A goblin is a monstrous creature from European folklore. They are almost always small and grotesque, mischievous or outright malicious, and greedy. Sometimes with magical abilities.","category":"Cast-Traits","isAdult":false},{"name":"Gods","description":"Prominently features a character of divine or religious nature.","category":"Cast-Traits","isAdult":false},{"name":"Golf","description":"Centers around the sport of golf.","category":"Theme-Game-Sport","isAdult":false},{"name":"Gore","description":"Prominently features graphic bloodshed and violence.","category":"Theme-Other","isAdult":false},{"name":"Group Sex","description":"Features more than two participants engaged in sex simultaneously.","category":"Sexual Content","isAdult":true},{"name":"Guns","description":"Prominently features the use of guns in combat.","category":"Theme-Action","isAdult":false},{"name":"Gyaru","description":"Prominently features a female character who has a distinct American-emulated fashion style, such as tanned skin, bleached hair, and excessive makeup. Also known as gal.","category":"Cast-Traits","isAdult":false},{"name":"Hair Pulling","description":"A sexual act in which the giver will grab the receivers hair and tug whilst giving pleasure from behind.","category":"Sexual Content","isAdult":true},{"name":"Handball","description":"Centers around the sport of handball.","category":"Theme-Game-Sport","isAdult":false},{"name":"Handjob","description":"Features the stimulation of genitalia by another's hands.","category":"Sexual Content","isAdult":true},{"name":"Henshin","description":"Prominently features character or costume transformations which often grant special abilities.","category":"Theme-Fantasy","isAdult":false},{"name":"Heterosexual","description":"Prominently features a romance between a man and a woman, not inherently sexual.","category":"Theme-Romance","isAdult":false},{"name":"Hikikomori","description":"Prominently features a character who withdraws from social life, often seeking extreme isolation.","category":"Cast-Traits","isAdult":false},{"name":"Hip-hop Music","description":"Centers on the musical style of hip-hop, not to be applied to anime that use hip-hop in its soundtrack.","category":"Theme-Arts-Music","isAdult":false},{"name":"Historical","description":"Partly or completely set during a real period of world history.","category":"Setting-Time","isAdult":false},{"name":"Homeless","description":"Prominently features a character that is homeless.","category":"Cast-Traits","isAdult":false},{"name":"Horticulture","description":"The story prominently features plant care and gardening.","category":"Theme-Slice of Life","isAdult":false},{"name":"Human Pet","description":"Features characters in a master-slave relationship where one is the \"owner\" and the other is a \"pet.\"","category":"Sexual Content","isAdult":true},{"name":"Hypersexuality","description":"Portrays a character with a hypersexuality disorder, compulsive sexual behavior, or sex addiction.","category":"Sexual Content","isAdult":true},{"name":"Ice Skating","description":"Centers around the sport of ice skating.","category":"Theme-Game-Sport","isAdult":false},{"name":"Idol","description":"Centers around the life and activities of an idol.","category":"Cast-Traits","isAdult":false},{"name":"Incest","description":"Features sexual or romantic relations between characters who are related by blood.","category":"Sexual Content","isAdult":true},{"name":"Indigenous Cultures","description":"Prominently features real-life indigenous cultures.","category":"Theme-Other","isAdult":false},{"name":"Inn","description":"Partially or completely set in an Inn or Hotel.","category":"Setting-Scene","isAdult":false},{"name":"Inseki","description":"Features sexual or romantic relations among step, adopted, and other non-blood related family members.","category":"Sexual Content","isAdult":true},{"name":"Irrumatio","description":"Oral rape; features a character thrusting their genitalia or a phallic object into the mouth of another character.","category":"Sexual Content","isAdult":true},{"name":"Isekai","description":"Features characters being transported into an alternate world setting and having to adapt to their new surroundings.","category":"Theme-Fantasy","isAdult":false},{"name":"Iyashikei","description":"Primary aim is to heal the audience through serene depictions of characters' daily lives.","category":"Theme-Slice of Life","isAdult":false},{"name":"Jazz Music","description":"Centers on the musical style of jazz, not to be applied to anime that use jazz in its soundtrack.","category":"Theme-Arts-Music","isAdult":false},{"name":"Josei","description":"Target demographic is adult females.","category":"Demographic","isAdult":false},{"name":"Judo","description":"Centers around the sport of judo.","category":"Theme-Game-Sport","isAdult":false},{"name":"Kaiju","description":"Prominently features giant monsters.","category":"Theme-Fantasy","isAdult":false},{"name":"Karuta","description":"Centers around the game of karuta.","category":"Theme-Game-Card & Board Game","isAdult":false},{"name":"Kemonomimi","description":"Prominently features humanoid characters with animal ears.","category":"Cast-Traits","isAdult":false},{"name":"Kids","description":"Target demographic is young children.","category":"Demographic","isAdult":false},{"name":"Kingdom Management","description":"Characters in these series take on the responsibility of running a town or kingdom, whether they take control of an existing one, or build their own from the ground up.","category":"Theme-Drama","isAdult":false},{"name":"Konbini","description":"Predominantly features a convenience store.","category":"Setting-Scene","isAdult":false},{"name":"Kuudere","description":"Prominently features a character who generally retains a cold, blunt and cynical exterior, but once one gets to know them, they have a very warm and loving interior.","category":"Cast-Traits","isAdult":false},{"name":"Lacrosse","description":"A team game played with a ball and lacrosse sticks.","category":"Theme-Game-Sport","isAdult":false},{"name":"Lactation","description":"Features breast milk play and production.","category":"Sexual Content","isAdult":true},{"name":"Language Barrier","description":"A barrier to communication between people who are unable to speak a common language.","category":"Theme-Other","isAdult":false},{"name":"Large Breasts","description":"Features a character with larger-than-average breasts.","category":"Sexual Content","isAdult":true},{"name":"LGBTQ+ Themes","description":"Prominently features characters or themes associated with the LGBTQ+ community, such as sexuality or gender identity.","category":"Theme-Other","isAdult":false},{"name":"Long Strip","description":"Manga originally published in a vertical, long-strip format, designed for viewing on smartphones. Also known as webtoons.","category":"Technical","isAdult":false},{"name":"Lost Civilization","description":"Featuring a civilization with few ruins or records that exist in present day knowledge.","category":"Theme-Other","isAdult":false},{"name":"Love Triangle","description":"Centered around romantic feelings between more than two people. Includes all love polygons.","category":"Theme-Romance","isAdult":false},{"name":"Mafia","description":"Centered around Italian organised crime syndicates.","category":"Theme-Other-Organisations","isAdult":false},{"name":"Magic","description":"Prominently features magical elements or the use of magic.","category":"Theme-Fantasy","isAdult":false},{"name":"Mahjong","description":"Centered around the game of mahjong.","category":"Theme-Game-Card & Board Game","isAdult":false},{"name":"Maids","description":"Prominently features a character who is a maid.","category":"Cast-Traits","isAdult":false},{"name":"Makeup","description":"Centers around the makeup industry.","category":"Theme-Arts","isAdult":false},{"name":"Male Harem","description":"Main cast features the protagonist plus several male characters who are romantically interested in them.","category":"Theme-Romance","isAdult":false},{"name":"Male Pregnancy","description":"Features pregnant male characters in a sexual context.","category":"Sexual Content","isAdult":true},{"name":"Male Protagonist","description":"Main character is male.","category":"Cast-Main Cast","isAdult":false},{"name":"Marriage","description":"Centers around marriage between two or more characters.","category":"Theme-Other","isAdult":false},{"name":"Martial Arts","description":"Centers around the use of traditional hand-to-hand combat.","category":"Theme-Action","isAdult":false},{"name":"Masochism","description":"Prominently features characters who get sexual pleasure from being hurt or controlled by others.","category":"Sexual Content","isAdult":true},{"name":"Masturbation","description":"Features erotic stimulation of one's own genitalia or other erogenous regions.","category":"Sexual Content","isAdult":true},{"name":"Matchmaking","description":"Prominently features either a matchmaker or events with the intent of matchmaking with eventual marriage in sight.","category":"Theme-Romance","isAdult":false},{"name":"Mating Press","description":"Features the sex position in which two partners face each other, with one of them thrusting downwards and the other's legs tucked up towards their head.","category":"Sexual Content","isAdult":true},{"name":"Matriarchy","description":"Prominently features a country that is ruled by a Queen or a society that is dominated by female inheritance.","category":"Setting","isAdult":false},{"name":"Medicine","description":"Centered around the activities of people working in the field of medicine.","category":"Theme-Other","isAdult":false},{"name":"Medieval","description":"Partially or completely set in the Middle Ages or a Middle Ages-inspired setting. Commonly features elements such as European castles and knights.","category":"Setting-Time","isAdult":false},{"name":"Memory Manipulation","description":"Prominently features a character(s) who has had their memories altered.","category":"Theme-Other","isAdult":false},{"name":"Mermaid","description":"A mythological creature with the body of a human and the tail of a fish.","category":"Cast-Traits","isAdult":false},{"name":"Meta","description":"Features fourth wall-breaking references to itself or genre tropes.","category":"Theme-Other","isAdult":false},{"name":"Metal Music","description":"Centers on the musical style of metal, not to be applied to anime that use metal in its soundtrack.","category":"Theme-Arts-Music","isAdult":false},{"name":"MILF","description":"Features sexual intercourse with older women.","category":"Sexual Content","isAdult":true},{"name":"Military","description":"Centered around the life and activities of military personnel.","category":"Theme-Other-Organisations","isAdult":false},{"name":"Mixed Gender Harem","description":"Main cast features the protagonist plus several people, regardless of gender, who are romantically interested in them.","category":"Theme-Romance","isAdult":false},{"name":"Mixed Media","description":"Features a combination of different media and animation techniques. Often seen with puppetry, textiles, live action footage, stop motion, and more. This does not include works with normal usage of CGI in their production.","category":"Technical","isAdult":false},{"name":"Monster Boy","description":"Prominently features a male character who is a part-monster.","category":"Cast-Traits","isAdult":false},{"name":"Monster Girl","description":"Prominently features a female character who is part-monster.","category":"Cast-Traits","isAdult":false},{"name":"Mopeds","description":"Prominently features mopeds.","category":"Theme-Other-Vehicle","isAdult":false},{"name":"Motorcycles","description":"Prominently features the use of motorcycles.","category":"Theme-Other-Vehicle","isAdult":false},{"name":"Mountaineering","description":"Prominently features characters discussing or hiking mountains.","category":"Theme-Other","isAdult":false},{"name":"Musical Theater","description":"Features a performance that combines songs, spoken dialogue, acting, and dance.","category":"Theme-Arts-Music","isAdult":false},{"name":"Mythology","description":"Prominently features mythological elements, especially those from religious or cultural tradition.","category":"Theme-Fantasy","isAdult":false},{"name":"Nakadashi","description":"Creampie; features sexual ejaculation inside of a character.","category":"Sexual Content","isAdult":true},{"name":"Natural Disaster","description":"It focuses on catastrophic events of natural origin, such as earthquakes, tsunamis, volcanic eruptions, and severe storms. These works often present situations of extreme danger in which the characters struggle to survive and overcome the adversity.","category":"Setting-Scene","isAdult":false},{"name":"Necromancy","description":"When the dead are summoned as spirits, skeletons, or the undead, usually for the purpose of gaining information or to be used as a weapon.","category":"Theme-Fantasy","isAdult":false},{"name":"Nekomimi","description":"Humanoid characters with cat-like features such as cat ears and a tail.","category":"Cast-Traits","isAdult":false},{"name":"Netorare","description":"Netorare is what happens when the protagonist gets their partner stolen from them by someone else. It is a sexual fetish designed to cause sexual jealousy by way of having the partner indulge in sexual activity with someone other than the protagonist.","category":"Sexual Content","isAdult":true},{"name":"Netorase","description":"Features characters in a romantic relationship who agree to be sexually intimate with others.","category":"Sexual Content","isAdult":true},{"name":"Netori","description":"Features the protagonist stealing the partner of someone else. The opposite of netorare.","category":"Sexual Content","isAdult":true},{"name":"Ninja","description":"Prominently features Japanese warriors traditionally trained in espionage, sabotage and assasination.","category":"Cast-Traits","isAdult":false},{"name":"No Dialogue","description":"This work contains no dialogue.","category":"Technical","isAdult":false},{"name":"Noir","description":"Stylized as a cynical crime drama with low-key visuals.","category":"Theme-Other","isAdult":false},{"name":"Non-fiction","description":"A work that provides information regarding a real world topic and does not focus on an imaginary narrative.","category":"Technical","isAdult":false},{"name":"Nudity","description":"Features a character wearing no clothing or exposing intimate body parts.","category":"Cast-Traits","isAdult":false},{"name":"Nun","description":"Prominently features a character who is a nun.","category":"Cast-Traits","isAdult":false},{"name":"Office","description":"Features people who work in a business office.","category":"Setting-Scene","isAdult":false},{"name":"Office Lady","description":"Prominently features a female office worker or OL.","category":"Cast-Traits","isAdult":false},{"name":"Oiran","description":"Prominently features a courtesan character of the Japanese Edo Period.","category":"Cast-Traits","isAdult":false},{"name":"Ojou-sama","description":"Features a wealthy, high-class, oftentimes stuck up and demanding female character.","category":"Cast-Traits","isAdult":false},{"name":"Omegaverse","description":"Alternative universe that prominently features dynamics modeled after wolves in which there are alphas, betas, and omegas and heat cycles as well as impregnation, regardless of gender.","category":"Setting-Universe","isAdult":true},{"name":"Orphan","description":"Prominently features a character that is an orphan.","category":"Cast-Traits","isAdult":false},{"name":"Otaku Culture","description":"Centers around the culture of a fanatical fan-base.","category":"Theme-Other","isAdult":false},{"name":"Outdoor Activities","description":"Centers around hiking, camping or other outdoor activities.","category":"Setting-Scene","isAdult":false},{"name":"Pandemic","description":"Prominently features a disease prevalent over a whole country or the world.","category":"Theme-Other","isAdult":false},{"name":"Parenthood","description":"Centers around the experience of raising a child.","category":"Theme-Slice of Life","isAdult":false},{"name":"Parkour","description":"Centers around the sport of parkour.","category":"Theme-Game-Sport","isAdult":false},{"name":"Parody","description":"Features deliberate exaggeration of popular tropes or a particular genre to comedic effect.","category":"Theme-Comedy","isAdult":false},{"name":"Pet Play","description":"Treating a participant as though they were a pet animal. Often involves a collar and possibly BDSM.","category":"Sexual Content","isAdult":true},{"name":"Philosophy","description":"Relating or devoted to the study of the fundamental nature of knowledge, reality, and existence.","category":"Theme-Other","isAdult":false},{"name":"Photography","description":"Centers around the use of cameras to capture photos.","category":"Theme-Arts","isAdult":false},{"name":"Pirates","description":"Prominently features sea-faring adventurers branded as criminals by the law.","category":"Cast-Traits","isAdult":false},{"name":"Poker","description":"Centers around the game of poker or its variations.","category":"Theme-Game-Card & Board Game","isAdult":false},{"name":"Police","description":"Centers around the life and activities of law enforcement officers.","category":"Theme-Other-Organisations","isAdult":false},{"name":"Politics","description":"Centers around politics, politicians, or government activities.","category":"Theme-Other","isAdult":false},{"name":"Polyamorous","description":"Features a character who is in a consenting relationship with multiple people at one time.","category":"Theme-Romance","isAdult":false},{"name":"Post-Apocalyptic","description":"Partly or completely set in a world or civilization after a global disaster.","category":"Setting-Universe","isAdult":false},{"name":"POV","description":"Point of View; features scenes shown from the perspective of the series protagonist.","category":"Technical","isAdult":false},{"name":"Pregnancy","description":"Features pregnant female characters or discusses the topic of pregnancy.","category":"Theme-Other","isAdult":false},{"name":"Primarily Adult Cast","description":"Main cast is mostly composed of characters above a high school age.","category":"Cast-Main Cast","isAdult":false},{"name":"Primarily Animal Cast","description":"Main cast is mostly composed animal or animal-like characters.","category":"Cast-Main Cast","isAdult":false},{"name":"Primarily Child Cast","description":"Main cast is mostly composed of characters below a high school age.","category":"Cast-Main Cast","isAdult":false},{"name":"Primarily Female Cast","description":"Main cast is mostly composed of female characters.","category":"Cast-Main Cast","isAdult":false},{"name":"Primarily Male Cast","description":"Main cast is mostly composed of male characters.","category":"Cast-Main Cast","isAdult":false},{"name":"Primarily Teen Cast","description":"Main cast is mostly composed of teen characters.","category":"Cast-Main Cast","isAdult":false},{"name":"Prison","description":"Partly or completely set in a prison.","category":"Setting-Scene","isAdult":false},{"name":"Prostitution","description":"Features characters who are paid for sexual favors.","category":"Sexual Content","isAdult":true},{"name":"Proxy Battle","description":"A proxy battle is a battle where humans use creatures\/robots to do the fighting for them, either by commanding those creatures\/robots or by simply evolving them\/changing them into battle mode.","category":"Theme-Other","isAdult":false},{"name":"Psychosexual","description":"Work that involves the psychological aspects of sexual impulses.","category":"Theme-Other","isAdult":false},{"name":"Public Sex","description":"Features sexual acts performed in public settings.","category":"Sexual Content","isAdult":true},{"name":"Puppetry","description":"Animation style involving the manipulation of puppets to act out scenes.","category":"Technical","isAdult":false},{"name":"Rakugo","description":"Rakugo is the traditional Japanese performance art of comic storytelling.","category":"Theme-Arts","isAdult":false},{"name":"Rape","description":"Features non-consensual sexual penetration.","category":"Sexual Content","isAdult":true},{"name":"Real Robot","description":"Prominently features mechanical designs loosely influenced by real-world robotics.","category":"Theme-Sci-Fi-Mecha","isAdult":false},{"name":"Rehabilitation","description":"Prominently features the recovery of a character who became incapable of social life or work.","category":"Theme-Drama","isAdult":false},{"name":"Reincarnation","description":"Features a character being born again after death, typically as another person or in another world.","category":"Theme-Other","isAdult":false},{"name":"Religion","description":"Centers on the belief that humanity is related to supernatural, transcendental, and spiritual elements.","category":"Theme-Other","isAdult":false},{"name":"Rescue","description":"Centers around operations that carry out urgent treatment of injuries, remove people from danger, or save lives. This includes series that are about search-and-rescue teams, trauma surgeons, firefighters, and more.","category":"Theme-Other","isAdult":false},{"name":"Restaurant","description":"Features a business that prepares and serves food and drinks to customers. Also encompasses cafes and bistros.","category":"Setting-Scene","isAdult":false},{"name":"Revenge","description":"Prominently features a character who aims to exact punishment in a resentful or vindictive manner.","category":"Theme-Drama","isAdult":false},{"name":"Rimjob","description":"Features oral sex performed on the anus.","category":"Sexual Content","isAdult":true},{"name":"Robots","description":"Prominently features humanoid machines.","category":"Cast-Traits","isAdult":false},{"name":"Rock Music","description":"Centers on the musical style of rock, not to be applied to anime that use rock in its soundtrack.","category":"Theme-Arts-Music","isAdult":false},{"name":"Rotoscoping","description":"Animation technique that animators use to trace over motion picture footage, frame by frame, to produce realistic action.","category":"Technical","isAdult":false},{"name":"Royal Affairs","description":"Features nobility, alliances, arranged marriage, succession disputes, religious orders and other elements of royal politics.","category":"Theme-Other","isAdult":false},{"name":"Rugby","description":"Centers around the sport of rugby.","category":"Theme-Game-Sport","isAdult":false},{"name":"Rural","description":"Partly or completely set in the countryside.","category":"Setting-Scene","isAdult":false},{"name":"Sadism","description":"Prominently features characters deriving pleasure, especially sexual gratification, from inflicting pain, suffering, or humiliation on others.","category":"Sexual Content","isAdult":true},{"name":"Samurai","description":"Prominently features warriors of medieval Japanese nobility bound by a code of honor.","category":"Cast-Traits","isAdult":false},{"name":"Satire","description":"Prominently features the use of comedy or ridicule to expose and criticise social phenomena.","category":"Theme-Comedy","isAdult":false},{"name":"Scat","description":"Lots of feces.","category":"Sexual Content","isAdult":true},{"name":"School","description":"Partly or completely set in a primary or secondary educational institution.","category":"Setting-Scene","isAdult":false},{"name":"School Club","description":"Partly or completely set in a school club scene.","category":"Setting-Scene","isAdult":false},{"name":"Scissoring","description":"A form of sexual activity between women in which the genitals are stimulated by being rubbed against one another.","category":"Sexual Content","isAdult":true},{"name":"Scuba Diving","description":"Prominently features characters diving with the aid of special breathing equipment.","category":"Theme-Game-Sport","isAdult":false},{"name":"Seinen","description":"Target demographic is adult males.","category":"Demographic","isAdult":false},{"name":"Sex Toys","description":"Features objects that are designed to stimulate sexual pleasure.","category":"Sexual Content","isAdult":true},{"name":"Shapeshifting","description":"Features character(s) who changes one's appearance or form.","category":"Theme-Fantasy","isAdult":false},{"name":"Shimaidon","description":"Features a character who gets their way with two sisters.","category":"Sexual Content","isAdult":true},{"name":"Ships","description":"Prominently features the use of sea-based transportation vessels.","category":"Theme-Other-Vehicle","isAdult":false},{"name":"Shogi","description":"Centers around the game of shogi.","category":"Theme-Game-Card & Board Game","isAdult":false},{"name":"Shoujo","description":"Target demographic is teenage and young adult females.","category":"Demographic","isAdult":false},{"name":"Shounen","description":"Target demographic is teenage and young adult males.","category":"Demographic","isAdult":false},{"name":"Shrine Maiden","description":"Prominently features a character who is a shrine maiden.","category":"Cast-Traits","isAdult":false},{"name":"Skateboarding","description":"Centers around or prominently features skateboarding as a sport.","category":"Theme-Game-Sport","isAdult":false},{"name":"Skeleton","description":"Prominently features skeleton(s) as a character.","category":"Cast-Traits","isAdult":false},{"name":"Slapstick","description":"Prominently features comedy based on deliberately clumsy actions or embarrassing events.","category":"Theme-Comedy","isAdult":false},{"name":"Slavery","description":"Prominently features slaves, slavery, or slave trade.","category":"Theme-Other","isAdult":false},{"name":"Snowscape","description":"Prominently or partially set in a snowy environment.","category":"Setting-Scene","isAdult":false},{"name":"Software Development","description":"Centers around characters developing or programming a piece of technology, software, gaming, etc.","category":"Theme-Other","isAdult":false},{"name":"Space","description":"Partly or completely set in outer space.","category":"Setting-Universe","isAdult":false},{"name":"Space Opera","description":"Centers around space warfare, advanced technology, chivalric romance and adventure.","category":"Theme-Sci-Fi","isAdult":false},{"name":"Spearplay","description":"Prominently features the use of spears in combat.","category":"Theme-Action","isAdult":false},{"name":"Squirting","description":"Female ejaculation; features the expulsion of liquid from the female genitalia.","category":"Sexual Content","isAdult":true},{"name":"Steampunk","description":"Prominently features technology and designs inspired by 19th-century industrial steam-powered machinery.","category":"Theme-Fantasy","isAdult":false},{"name":"Stop Motion","description":"Animation style characterized by physical objects being moved incrementally between frames to create the illusion of movement.","category":"Technical","isAdult":false},{"name":"Succubus","description":"Prominently features a character who is a succubus, a creature in medieval folklore that typically uses their sexual prowess to trap and seduce people to feed off them.","category":"Cast-Traits","isAdult":false},{"name":"Suicide","description":"The act or an instance of taking or attempting to take one's own life voluntarily and intentionally.","category":"Theme-Drama","isAdult":false},{"name":"Sumata","description":"Pussyjob; features the stimulation of male genitalia by the thighs and labia majora of a female character.","category":"Sexual Content","isAdult":true},{"name":"Sumo","description":"Centers around the sport of sumo.","category":"Theme-Game-Sport","isAdult":false},{"name":"Super Power","description":"Prominently features characters with special abilities that allow them to do what would normally be physically or logically impossible.","category":"Theme-Fantasy","isAdult":false},{"name":"Super Robot","description":"Prominently features large robots often piloted by hot-blooded protagonists.","category":"Theme-Sci-Fi-Mecha","isAdult":false},{"name":"Superhero","description":"Prominently features super-powered humans who aim to serve the greater good.","category":"Theme-Fantasy","isAdult":false},{"name":"Surfing","description":"Centers around surfing as a sport.","category":"Theme-Game-Sport","isAdult":false},{"name":"Surreal Comedy","description":"Prominently features comedic moments that defy casual reasoning, resulting in illogical events.","category":"Theme-Comedy","isAdult":false},{"name":"Survival","description":"Centers around the struggle to live in spite of extreme obstacles.","category":"Theme-Other","isAdult":false},{"name":"Sweat","description":"Lots of sweat.","category":"Sexual Content","isAdult":true},{"name":"Swimming","description":"Centers around the sport of swimming.","category":"Theme-Game-Sport","isAdult":false},{"name":"Swordplay","description":"Prominently features the use of swords in combat.","category":"Theme-Action","isAdult":false},{"name":"Table Tennis","description":"Centers around the sport of table tennis (also known as \"ping pong\").","category":"Theme-Game-Sport","isAdult":false},{"name":"Tanks","description":"Prominently features the use of tanks or other armoured vehicles.","category":"Theme-Other-Vehicle","isAdult":false},{"name":"Tanned Skin","description":"Prominently features characters with tanned skin.","category":"Cast-Traits","isAdult":false},{"name":"Teacher","description":"Protagonist is an educator, usually in a school setting.","category":"Cast-Traits","isAdult":false},{"name":"Teens' Love","description":"Sexually explicit love-story between individuals of the opposite sex, specifically targeting females of teens and young adult age.","category":"Theme-Romance","isAdult":false},{"name":"Tennis","description":"Centers around the sport of tennis.","category":"Theme-Game-Sport","isAdult":false},{"name":"Tentacles","description":"Features the long appendages most commonly associated with octopuses or squid, often sexually penetrating a character.","category":"Sexual Content","isAdult":true},{"name":"Terrorism","description":"Centers around the activities of a terrorist or terrorist organization.","category":"Theme-Other","isAdult":false},{"name":"Threesome","description":"Features sexual acts between three people.","category":"Sexual Content","isAdult":true},{"name":"Time Loop","description":"A character is stuck in a repetitive cycle that they are attempting to break out of. This is distinct from a manipulating time of their own choice.","category":"Theme-Sci-Fi","isAdult":false},{"name":"Time Manipulation","description":"Prominently features time-traveling or other time-warping phenomena.","category":"Theme-Sci-Fi","isAdult":false},{"name":"Time Skip","description":"Features a gap in time used to advance the story.","category":"Setting-Time","isAdult":false},{"name":"Tokusatsu","description":"Prominently features elements that resemble special effects in Japanese live-action shows","category":"Theme-Sci-Fi","isAdult":false},{"name":"Tomboy","description":"Features a girl who exhibits characteristics or behaviors considered in many cultures to be typical of boys.","category":"Cast-Traits","isAdult":false},{"name":"Torture","description":"The act of deliberately inflicting severe pain or suffering upon another individual or oneself as a punishment or with a specific purpose.","category":"Theme-Other","isAdult":false},{"name":"Tragedy","description":"Centers around tragic events and unhappy endings.","category":"Theme-Drama","isAdult":false},{"name":"Trains","description":"Prominently features trains.","category":"Theme-Other-Vehicle","isAdult":false},{"name":"Transgender","description":"Features a character whose gender identity differs from the sex they were assigned at birth.","category":"Cast-Traits","isAdult":false},{"name":"Travel","description":"Centers around character(s) moving between places a significant distance apart.","category":"Theme-Other","isAdult":false},{"name":"Triads","description":"Centered around Chinese organised crime syndicates.","category":"Theme-Other-Organisations","isAdult":false},{"name":"Tsundere","description":"Prominently features a character who acts cold and hostile in order to mask warmer emotions.","category":"Cast-Traits","isAdult":false},{"name":"Twins","description":"Prominently features two or more siblings that were born at one birth.","category":"Cast-Traits","isAdult":false},{"name":"Unrequited Love","description":"One or more characters are experiencing an unrequited love that may or may not be reciprocated.","category":"Theme-Romance","isAdult":false},{"name":"Urban","description":"Partly or completely set in a city.","category":"Setting-Scene","isAdult":false},{"name":"Urban Fantasy","description":"Set in a world similar to the real world, but with the existence of magic or other supernatural elements.","category":"Setting-Universe","isAdult":false},{"name":"Vampire","description":"Prominently features a character who is a vampire.","category":"Cast-Traits","isAdult":false},{"name":"Vertical Video","description":"Animated works originally created in a vertical aspect ratio (such as 9:16), intended for viewing on smartphones.","category":"Technical","isAdult":false},{"name":"Veterinarian","description":"Prominently features a veterinarian or one of the main characters is a veterinarian.","category":"Cast-Traits","isAdult":false},{"name":"Video Games","description":"Centers around characters playing video games.","category":"Theme-Game","isAdult":false},{"name":"Vikings","description":"Prominently features Scandinavian seafaring pirates and warriors.","category":"Cast-Traits","isAdult":false},{"name":"Villainess","description":"Centers around or prominently features a villainous noble lady.","category":"Cast-Traits","isAdult":false},{"name":"Virginity","description":"Features a male character who has never had sexual relations (until now).","category":"Sexual Content","isAdult":true},{"name":"Virtual World","description":"Partly or completely set in the world inside a video game.","category":"Setting-Universe","isAdult":false},{"name":"Vocal Synth","description":"Features one or more singers or characters that are products of a synthesize singing program. Popular examples are Vocaloids, UTAUloids, and CeVIOs.","category":"Theme-Other","isAdult":false},{"name":"Volleyball","description":"Centers around the sport of volleyball.","category":"Theme-Game-Sport","isAdult":false},{"name":"Vore","description":"Features a character being swallowed or swallowing another creature whole.","category":"Sexual Content","isAdult":true},{"name":"Voyeur","description":"Features a character who enjoys seeing the sex acts or sex organs of others.","category":"Sexual Content","isAdult":true},{"name":"VTuber","description":"Prominently features a character who is either an actual or fictive VTuber.","category":"Cast-Traits","isAdult":false},{"name":"War","description":"Partly or completely set during wartime.","category":"Theme-Other","isAdult":false},{"name":"Watersports","description":"Features sexual situations involving urine.","category":"Sexual Content","isAdult":true},{"name":"Werewolf","description":"Prominently features a character who is a werewolf.","category":"Cast-Traits","isAdult":false},{"name":"Wilderness","description":"Predominantly features a location with little to no human activity, such as a deserted island, a jungle, or a snowy mountain range.","category":"Setting-Scene","isAdult":false},{"name":"Witch","description":"Prominently features a character who is a witch.","category":"Cast-Traits","isAdult":false},{"name":"Work","description":"Centers around the activities of a certain occupation.","category":"Setting-Scene","isAdult":false},{"name":"Wrestling","description":"Centers around the sport of wrestling.","category":"Theme-Game-Sport","isAdult":false},{"name":"Writing","description":"Centers around the profession of writing books or novels.","category":"Theme-Arts","isAdult":false},{"name":"Wuxia","description":"Chinese fiction concerning the adventures of martial artists in Ancient China.","category":"Theme-Fantasy","isAdult":false},{"name":"Yakuza","description":"Centered around Japanese organised crime syndicates.","category":"Theme-Other-Organisations","isAdult":false},{"name":"Yandere","description":"Prominently features a character who is obsessively in love with another, to the point of acting deranged or violent.","category":"Cast-Traits","isAdult":false},{"name":"Youkai","description":"Prominently features supernatural creatures from Japanese folklore.","category":"Theme-Fantasy","isAdult":false},{"name":"Yuri","description":"Prominently features romance between two females, not inherently sexual. Also known as Girls' Love.","category":"Theme-Romance","isAdult":false},{"name":"Zombie","description":"Prominently features reanimated corpses which often prey on live humans and turn them into zombies.","category":"Cast-Traits","isAdult":false},{"name":"Zoophilia","description":"Features a character who has a sexual attraction for non-human animals.","category":"Sexual Content","isAdult":true}]}} \ No newline at end of file