from __future__ import annotations import re from typing import Any, Optional import discord from anilist.types import Anime, Character, Manga from utils.common import trim ANILIST_COLOR = 0x02A9FF FOOTER = "Powered by AniList APIv2" media_formats_map = { "TV": "TV", "TV_SHORT": "TV Short", "MOVIE": "Movie", "SPECIAL": "Special", "OVA": "OVA", "ONA": "ONA", "MUSIC": "Music", "MANGA": "Manga", "NOVEL": "Novel", "ONE_SHOT": "One Shot", } class BaseEmbed(discord.Embed): def __init__(self, *args, **kwargs): super().__init__(color=ANILIST_COLOR, *args, **kwargs) self.set_footer(text=FOOTER) def get(obj: Any, prop: str, default: Optional[str] = None): return getattr(obj, prop, default) def get_character_summary(character: Character) -> Optional[str]: if not get(character, "description"): return "No description available." clean, spoilers = [], [] description = character.description def repl(m): spoilers.append(f"||{m.group(1)}||") return "" description = re.sub(r"~!(.+?)!~", repl, description, re.S) lines = description.split("\n") lines = [line for line in lines if line] for line in lines: if re.search(r"^__.+(:__|__:|__.+:)|^\*\*.+(:\*\*|\*\*:|\*\*.+:)", line): continue else: clean.append(line.strip()) if clean: ret = clean[0] if len(ret) < 100 and len(clean) > 1: ret += f"\n{clean[1]}" return trim(ret, 512) elif spoilers: return spoilers[0] else: return "No description available." def build_anilist_embed(result: Anime | Manga) -> discord.Embed: title = get(result.title, "english", result.title.romaji) description = result.description.split("
")[0] if get(result, "description") else "" description = re.sub(r"<.+?>", "", description) source = result.source.replace("_", " ").title() if get(result, "source") else "N/A" status = result.status.replace("_", " ").title() if get(result, "status") else "N/A" start_date: Any = get(result, "start_date") url = result.url mid = result.id embed = BaseEmbed(title=title, url=url, description=description) embed.set_image(url=f"https://img.anili.st/media/{mid}") embed.add_field(name="Source", value=source) embed.add_field(name="Status", value=status) if start_date and get(start_date, "year"): embed.add_field(name="Release Year", value=start_date.year, inline=True) if result is Anime: nextairing = get(result, "next_airing", None) episodes = get(result, "episodes") or get(nextairing, "episode") duration = get(result, "duration") media_format = media_formats_map.get(result.format) embed.add_field(name="Type", value=media_format) if episodes: embed.add_field(name="Episodes", value=episodes) if duration: duration = ( str(duration) + " mins per ep." if media_format == "TV" else str(duration) + " mins" ) embed.add_field(name="Duration", value=duration) elif result is Manga: volumes = get(result, "volumes") chapters = get(result, "chapters") if volumes: embed.add_field(name="Volumes", value=volumes) if chapters: embed.add_field(name="Chapters", value=chapters) return embed def build_character_embed(character: Character) -> discord.Embed: name = character.name.full if get(character.name, "native"): name += f" ({character.name.native})" url = character.url image = character.image.large if get(character, "image") else None media = character.media if get(character, "media") else None description = get_character_summary(character) or "" if media: media_joined = "**Featured in:**\n" for entry in media[:3]: title = entry["title"].get("english") or entry["title"].get("romaji") entry_url = f"https://anilist.co/{entry['type'].lower()}/{entry['id']}" media_joined += f"[{title}]({entry_url})\n" if len(media) > 3: media_joined += f"[**+ {len(media) - 3} more**]({url})" description += f"\n\n{media_joined}" embed = BaseEmbed(title=name, url=url, description=description) if image: embed.set_image(url=image) return embed