async def profile(self, ctx: Context, *, member: Member = None): """User's profile""" if not member: member = ctx.author user, _ = await User.get_or_create(id=member.id) # Calculate current level progress: # (exp - curr lvl req) * 100 / (curr lvl req - next lvl req) current_level_exp = (user.level * 4)**2 next_level_exp = ((user.level + 1) * 4)**2 progress = round((user.exp - current_level_exp) * 100 / (next_level_exp - current_level_exp)) # Find position of profile in global user ranking rank = (await User.all().order_by("-exp")).index(user) embed = Embed(ctx, title=f"{member.name}'s profile", color=member.color) embed.set_thumbnail(url=member.avatar_url) embed.add_fields( ("Rank", str(rank + 1)), ("Level", f"{user.level}"), ("Experience", f"{user.exp}/{next_level_exp} ({progress}%)"), ("Balance", f"{user.balance} coins"), ) if mutes := await Mute.filter(guild__id=ctx.guild.id, user__id=member.id).count(): embed.add_field(name="Mutes", value=str(mutes))
async def mute(self, ctx: Context, member: Member, time: Timedelta, *, reason: str = None): """Mute someone Muting someone gives them the mute role specified by the muterole command and removes the role after the specified time has passed. Note: Mutes are checked every 10 seconds, so times are not perfect. """ mute = await Mute.filter(user__id=member.id, guild__id=ctx.guild.id, active=True).first() if mute: mute.end += time await mute.save() return await ctx.send( t(ctx, "message_extended", member=member, time=time)) # NOTE: Extensions don't add a mute entry, they just make the # active mute longer. # return await ctx.send(f"{member.name} is already muted.") user, _ = await User.get_or_create(id=member.id) guild, _ = await Guild.get_or_create(id=ctx.guild.id) if not guild.mute_role: return await ctx.send(t(ctx, "no_mute_role", guild=ctx.guild)) mute = await Mute.create( moderator=ctx.author.id, user=user, guild=guild, reason=reason, end=datetime.utcnow() + time, ) mute_role = ctx.guild.get_role(guild.mute_role) # TODO: Check if member has lower permissions required to mute them await member.add_roles( mute_role, reason=f"Muted by {ctx.author} for {time}, reason: {reason}") embed = Embed(ctx, title=f"Mute [{mute.id}]", color=member.color) embed.set_thumbnail(url=member.avatar_url) embed.description = t(ctx, "message", member=member.mention, time=time, reason=reason) await ctx.send(embed=embed) try: await member.send( t(ctx, "dm_message", guild=ctx.guild, time=time, reason=reason)) except (Forbidden, HTTPException, AttributeError): pass
async def character(self, ctx: Context, *, name: str): """Character info from AniList""" query = """ query ($name: String) { Character (search: $name) { name {full} image {large} description (asHtml: false) siteUrl favourites media (perPage: 10) { edges { node { title {romaji} siteUrl } characterRole } } } } """ character = (await anilist(query, {"name": name}))["data"]["Character"] embed = Embed(ctx, title=character["name"]["full"], description="", url=character["siteUrl"], footer="Via AniList", color=Color.blue()) embed.set_thumbnail(url=character["image"]["large"]) if character["favourites"]: embed.description += f"❤️ {character['favourites']} favorites \n\n" if character["description"]: description = clean_description(character["description"]) embed.description += f"Description: ||{description[:250]}...||" \ if len(description) >= 250 \ else f"Description: ||{description}||" appears_in = ["Main 🌕 Supporting 🌗 Background 🌑"] for i in character["media"]["edges"]: role = i["characterRole"] \ .replace("MAIN", "🌕") \ .replace("SUPPORTING", "🌗") \ .replace("BACKGROUND", "🌑") appears_in.append(f"{role} [{i['node']['title']['romaji']}]" f"({i['node']['siteUrl']})") embed.add_field(name="Appears in", value="\n".join(appears_in)) await ctx.send(embed=embed)
async def manga(self, ctx: Context, *, title: str): """Manga info from AniList""" query = """ query ($title: String) { Media (search: $title, type: MANGA) { title {romaji} coverImage {extraLarge color} description (asHtml: false) siteUrl rankings {rank allTime type context} status chapters volumes format averageScore genres } } """ manga = (await anilist(query, {"title": title}))["data"]["Media"] embed = Embed( ctx, title=manga["title"]["romaji"], description="", url=manga["siteUrl"], footer="Via AniList", ) embed.set_thumbnail(url=manga["coverImage"]["extraLarge"]) for i in manga["rankings"]: if not i["allTime"]: continue if i["type"] == "RATED": embed.description += "⭐" elif i["type"] == "POPULAR": embed.description += "❤️" embed.description += f" #{i['rank']} {i['context'].title()}\n" embed.description += "\n" if manga["description"]: description = clean_description(manga["description"]) embed.description += ( t(ctx, "synopsis_ellipsis", synopsis=description[:250]) if len(description) >= 250 else t(ctx, "synopsis", synopsis=description) ) if color_hex := manga["coverImage"]["color"]: embed.color = Color(int(color_hex.replace("#", ""), 16))
async def anime(self, ctx: Context, *, title: str): """Anime info from AniList""" query = """ query ($title: String) { Media (search: $title, type: ANIME) { title {romaji} coverImage {extraLarge color} description (asHtml: false) siteUrl rankings {rank allTime type context} status episodes duration season seasonYear format averageScore genres studios (isMain: true) {nodes {name}} } } """ anime = (await anilist(query, {"title": title}))["data"]["Media"] embed = Embed( ctx, title=anime["title"]["romaji"], description="", url=anime["siteUrl"], footer="Via AniList", ) embed.set_thumbnail(url=anime["coverImage"]["extraLarge"]) for i in anime["rankings"]: if not i["allTime"]: continue if i["type"] == "RATED": embed.description += "⭐" elif i["type"] == "POPULAR": embed.description += "❤️" embed.description += f" #{i['rank']} {i['context'].title()}\n" embed.description += "\n" if description := clean_description(anime["description"]): embed.description += ( t(ctx, "synopsis_ellipsis", synopsis=description[:250]) if len(description) >= 250 else t(ctx, "synopsis", synopsis=description) )
async def user(self, ctx: Context, *, user: Union[Member, User] = None): """Shows info about an user or a member""" if not user: user = ctx.author title = str(user) if not user.bot else f"{user} :robot:" embed = Embed(ctx, title=title, color=user.color) embed.set_thumbnail(url=user.avatar_url) embed.add_fields(("ID", user.id), ("Created at", user.created_at)) await ctx.send(embed=embed)
async def studio(self, ctx: Context, *, name: str): """Studio info from AniList""" query = """ query ($name: String) { Studio (search: $name, sort: SEARCH_MATCH) { name siteUrl isAnimationStudio media (sort: POPULARITY_DESC, perPage: 10) { nodes { title {romaji} coverImage {extraLarge} siteUrl popularity favourites } } } } """ studio = (await anilist(query, {"name": name}))["data"]["Studio"] embed = Embed( ctx, title=studio["name"], url=studio["siteUrl"], footer="Via AniList" ) embed.set_thumbnail(url=studio["media"]["nodes"][0]["coverImage"]["extraLarge"]) if studio["isAnimationStudio"]: embed.description = t(ctx, "animation_studio") # TODO: Observe, if this breaks when isAnimationStudio=False. most_popular = [t(ctx, "most_popular_header")] for i in studio["media"]["nodes"]: most_popular.append( t( ctx, "most_popular_item", popularity=i["popularity"], favorites=i["favourites"], title=i["title"]["romaji"], url=i["siteUrl"], ) ) embed.add_field( name=t(ctx, "most_popular_title"), value="\n".join(most_popular) ) await ctx.send(embed=embed)
async def server(self, ctx: Context): """Shows info about this server""" embed = Embed(ctx, title=ctx.guild.name) embed.set_thumbnail(url=ctx.guild.icon_url_as(size=2048)) embed.add_fields( (t(ctx, "id"), ctx.guild.id), (t(ctx, "owner"), ctx.guild.owner.mention), (t(ctx, "region"), ctx.guild.region), (t(ctx, "members"), str(ctx.guild.member_count)), (t(ctx, "text_channels"), str(len(ctx.guild.text_channels))), (t(ctx, "voice_channels"), str(len(ctx.guild.voice_channels))), ) await ctx.send(embed=embed)
async def studio(self, ctx: Context, *, name: str): """Studio info from AniList""" query = """ query ($name: String) { Studio (search: $name, sort: SEARCH_MATCH) { name siteUrl isAnimationStudio media (sort: POPULARITY_DESC, perPage: 10) { nodes { title {romaji} coverImage {extraLarge} siteUrl popularity favourites } } } } """ studio = (await anilist(query, {"name": name}))["data"]["Studio"] embed = Embed(ctx, title=studio["name"], url=studio["siteUrl"], footer="Via AniList") embed.set_thumbnail( url=studio["media"]["nodes"][0]["coverImage"]["extraLarge"]) if studio["isAnimationStudio"]: embed.description = "Animation Studio" # TODO: Observe, if this breaks when isAnimationStudio=False. most_popular = ["Popularity ⭐ Favorites ❤"] for i in studio["media"]["nodes"]: most_popular.append(f"{i['popularity']} ⭐ {i['favourites']} ❤️ " f"[{i['title']['romaji']}]({i['siteUrl']}) ") embed.add_field(name="Most popular productions", value="\n".join(most_popular)) await ctx.send(embed=embed)
async def anilist(self, ctx: Context, *, username: str): """AniList profile Shows anime/manga stats and favorites. """ query = """ query ($username: String) { User (name: $username) { name avatar {large} siteUrl favourites { anime { nodes {title {romaji} siteUrl} } manga { nodes {title {romaji} siteUrl} } characters { nodes {name {full} siteUrl} } staff { nodes {name {full} siteUrl} } studios { nodes {name siteUrl} } } } } """ list_query = """ query ($username: String, $type: MediaType) { MediaListCollection (userName: $username, type: $type) { lists {status name entries {id}} } } """ await ctx.trigger_typing() user = (await anilist(query, {"username": username}))["data"]["User"] anime_lists, manga_lists = [ (await anilist(list_query, {"username": username, "type": i}))["data"][ "MediaListCollection" ]["lists"] for i in ["ANIME", "MANGA"] ] embed = Embed( ctx, title=t(ctx, "title", user=user["name"]), url=user["siteUrl"], footer="Via AniList", ) embed.set_thumbnail(url=user["avatar"]["large"]) if anime_lists: anime_list_body = "" for i in anime_lists: if i["status"]: status = ( i["status"] .replace("COMPLETED", "✅") .replace("PLANNING", "🗓️") .replace("DROPPED", "🗑️") .replace("CURRENT", "📺") .replace("PAUSED", "⏸️") .replace("REPEATING", "🔁") ) else: status = "⚙️" anime_list_body += f"{status} **{len(i['entries'])}** " f"{i['name']}\n" embed.add_field(name=t(ctx, "anime"), value=anime_list_body) if manga_lists: manga_list_body = "" for i in manga_lists: if i["status"]: status = ( i["status"] .replace("COMPLETED", "✅") .replace("PLANNING", "🗓️") .replace("DROPPED", "🗑️️") .replace("CURRENT", "📖") .replace("PAUSED", "⏸️") .replace("REPEATING", "🔁") ) else: status = "⚙️" manga_list_body += f"{status} **{len(i['entries'])}** " f"{i['name']}\n" embed.add_field(name=t(ctx, "manga"), value=manga_list_body) if any(user["favourites"][i]["nodes"] for i in user["favourites"]): favorites_body = "" for i in user["favourites"]: if not user["favourites"][i]["nodes"]: continue # Section header favorites_body += f"__{i.title()}__" # Show total amount of favs if they exceed a limit (3) if (favorites_count := len(user["favourites"][i]["nodes"])) > 3: favorites_body += f" ({favorites_count})" favorites_body += "\n" for item in user["favourites"][i]["nodes"][:3]: # There are three types of names, so unify them. if i in ["anime", "manga"]: name = item["title"]["romaji"] elif i in ["characters", "staff"]: name = item["name"]["full"] else: name = item["name"] favorites_body += f"[{name}]({item['siteUrl']})\n" embed.add_field( name=t(ctx, "favorites"), value=favorites_body, inline=False )
async def character(self, ctx: Context, *, name: str): """Character info from AniList""" query = """ query ($name: String) { Character (search: $name) { name {full} image {large} description (asHtml: false) siteUrl favourites media (perPage: 10) { edges { node { title {romaji} siteUrl } characterRole } } } } """ character = (await anilist(query, {"name": name}))["data"]["Character"] embed = Embed( ctx, title=character["name"]["full"], description="", url=character["siteUrl"], footer="Via AniList", color=Color.blue(), ) embed.set_thumbnail(url=character["image"]["large"]) if character["favourites"]: embed.description += ( t(ctx, "favorites", favorites=character["favourites"]) + "\n\n" ) if character["description"]: description = clean_description(character["description"]) embed.description += ( t(ctx, "character_description_ellipsis", description=description[:250]) if len(description) >= 250 else t(ctx, "character_description", description=description) ) appears_in = [t(ctx, "appears_in_header")] for i in character["media"]["edges"]: role = ( i["characterRole"] .replace("MAIN", "🌕") .replace("SUPPORTING", "🌗") .replace("BACKGROUND", "🌑") ) appears_in.append( t( ctx, "appears_in_item", role=role, title=i["node"]["title"]["romaji"], url=i["node"]["siteUrl"], ) ) embed.add_field(name=t(ctx, "appears_in_title"), value="\n".join(appears_in)) await ctx.send(embed=embed)