async def role(self, ctx: Context, *, role: Role): """Shows info about a role""" embed = Embed(ctx, title=t(ctx, "title", role=role.name), color=role.color) embed.add_field(name=t(ctx, "id"), value=role.id) if len(role.members) > 1: embed.add_field(name=t(ctx, "members"), value=str(len(role.members))) embed.add_field( name=t(ctx, "mentionable"), value=t(ctx, "mentionable_yes") if role.mentionable else t(ctx, "mentionable_no"), ) if role.color != Color.default(): embed.add_field( name=t(ctx, "color"), value=t( ctx, "color_value", hex=str(role.color), rgb=str(role.color.to_rgb()), ), ) embed.add_field(name=t(ctx, "created_at"), value=role.created_at) await ctx.send(embed=embed)
async def mutes(self, ctx: Context, *, member: Member = None): """See someone's mutes If no member specified, this shows Your mutes. """ if not member: member = ctx.author embed = Embed( ctx, title=t(ctx, "title", member=member), description="", color=member.color, ) await ctx.trigger_typing() mutes = Mute.filter(user__id=member.id, guild__id=ctx.guild.id) if not await mutes.count(): return await ctx.send(t(ctx, "no_mutes", member=member)) async for i in mutes: moderator = await self.bot.fetch_user(i.moderator) embed.description += ( f"`{i.id}` {str(i.start.time())[:-10]} " f"{i.start.date()} ({str(i.end - i.start)[:-7]}) " f"**{moderator}**: *{i.reason or t(ctx, 'no_reason')}* ") # TODO: Format time and use timezones if i.active: embed.description += "🔴" embed.description += "\n" await ctx.send(embed=embed)
async def warn(self, ctx: Context, member: Member, *, reason: str): """Warn someone Warns do not give any punishments apart fron an entry in the warn list. """ guild, _ = await Guild.get_or_create(id=ctx.guild.id) user, _ = await User.get_or_create(id=member.id) warn = await Warn.create(moderator=ctx.author.id, guild=guild, user=user, reason=reason) embed = Embed(ctx, title=f"Warn [{warn.id}]", color=member.color) embed.description = t(ctx, "message", member=member.mention, reason=reason) await ctx.send(embed=embed) try: await member.send( t(ctx, "dm_message", guild=ctx.guild, reason=reason)) except (Forbidden, HTTPException, AttributeError): pass
async def mutes_active(self, ctx: Context): """See active mutes""" embed = Embed(ctx, title="Active mutes") await ctx.trigger_typing() mutes = Mute.filter(guild__id=ctx.guild.id, active=True) if not await mutes.count(): return await ctx.send( f"There are no active mutes in **{ctx.guild.name}**.") # TODO: Split active mutes into different embeds when more than 10 # and add scrolling (◀️ ▶️) async for i in mutes.prefetch_related("user"): moderator = ctx.guild.get_member(i.moderator) user = ctx.guild.get_member(i.user.id) description = ( f"**Given at**: {str(i.start.time())[:-10]} {str(i.start.date())[5:]}\n" f"**Duration**: {str(i.end - i.start)[:-7]}\n" f"**Moderator**: {moderator.mention}") if i.reason: description += f"\n**Reason**: *{i.reason}*" embed.add_field(name=f"{user} [{i.id}]", value=description, inline=False) await ctx.send(embed=embed)
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 warns(self, ctx: Context, *, member: Member = None): """See someone's warns If no member specified, this shows Your warns. """ if not member: member = ctx.author embed = Embed( ctx, title=t(ctx, "title", member=member), description="", color=member.color, ) await ctx.trigger_typing() warns = Warn.filter(user__id=member.id, guild__id=ctx.guild.id) if not await warns.count(): return await ctx.send(t(ctx, "no_mutes", member=member)) async for i in warns: moderator = ctx.bot.get_user(i.moderator) # TODO: Format time and use timezones (settings) embed.description += ( f"`{i.id}` {str(i.when.time())[:-10]} " f"{i.when.date()} **{moderator}**: *{i.reason}*\n") await ctx.send(embed=embed)
async def uptime(self, ctx: Context): """Bot uptime""" timestamp_difference = round(time() - self.bot.start_timestamp) uptime = timedelta(seconds=timestamp_difference) embed = Embed(ctx, title=t(ctx, "title"), description=str(uptime)) await ctx.send(embed=embed)
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 avatar(self, ctx: Context, *, user: User = None): """Shows an user's avatar""" if not user: user = ctx.author embed = Embed(ctx, title=t(ctx, "title", user=user.name)) embed.set_image(url=user.avatar_url_as(size=2048)) await ctx.send(embed=embed)
async def send_command_help(self, command): ctx = self.context embed = Embed( ctx, title=f"`{self.get_command_signature(command)}`", description=command.help, color=Color.blue(), ) await ctx.send(embed=embed)
async def bug(self, ctx: Context): """Where to report bugs and feature requests""" embed = Embed( ctx, title=t(ctx, "title"), description=t(ctx, "message"), ) await ctx.send(embed=embed)
async def ping(self, ctx: Context): """Bot connection latency This isn't very accurate, and mainly used as a "is this bot alive?" command. """ ping = round(self.bot.latency * 1000) embed = Embed(ctx, title="Ping", description=f":ping_pong:{ping}ms") await ctx.send(embed=embed)
async def pay(self, ctx: Context, amount: int, *, member: Member): """Give coins to someone You can't give money to yourself or any bots. Transfer amount should be more than 0. """ if member == ctx.author or member.bot: return await ctx.send(t(ctx, "other_users_only")) if amount <= 0: return await ctx.send(t(ctx, "at_least_one")) user, _ = await User.get_or_create(id=ctx.author.id) if user.balance < amount: return await ctx.send( t( ctx, "not_enough_funds", coins=user.balance, missing=amount - user.balance, ) ) embed = Embed( ctx, title=t(ctx, "title"), description=t(ctx, "confirmation", amount=amount, member=member.mention), ) message = await ctx.send(embed=embed) await message.add_reaction("✅") try: await self.bot.wait_for( "reaction_add", timeout=30, check=lambda r, u: u == ctx.message.author and str(r.emoji) == "✅", ) except TimeoutError: embed.description = t(ctx, "cancelled") return await message.edit(embed=embed) target_user, _ = await User.get_or_create(id=member.id) user.balance -= amount target_user.balance += amount await user.save() await target_user.save() try: await message.clear_reactions() except Forbidden: pass embed.description = t(ctx, "success", amount=amount, member=member.mention) await message.edit(embed=embed)
async def invite(self, ctx: Context): """Nagatoro's bot invite link""" embed = Embed( ctx, title=t(ctx, "title"), url=t(ctx, "invite_url"), description=t(ctx, "message"), ) 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 pay(self, ctx: Context, amount: int, *, member: Member): """Give coins to someone You can't give money to yourself or any bots. Transfer amount should be more than 0. """ if member == ctx.author or member.bot: return await ctx.send("You can give money to other users only.") if amount <= 0: return await ctx.send("You need to pay at least 1 coin.") user, _ = await User.get_or_create(id=ctx.author.id) if user.balance < amount: return await ctx.send( f"Not enough funds, you have " f"{user.balance} coins ({amount - user.balance} missing).") embed = Embed( ctx, title="Transfer", description=f"You are about to give **{amount}** " f"coin(s) to {member.mention}, are you sure?", ) message = await ctx.send(embed=embed) await message.add_reaction("✅") try: await self.bot.wait_for( "reaction_add", timeout=30, check=lambda r, u: u == ctx.message.author and str(r.emoji) == "✅", ) except TimeoutError: embed.description = "Transfer cancelled." return await message.edit(embed=embed) target_user, _ = await User.get_or_create(id=member.id) user.balance -= amount target_user.balance += amount await user.save() await target_user.save() try: await message.clear_reactions() except Forbidden: pass embed.description = f"Transferred **{amount}** coin(s) " f"to {member.mention}" await message.edit(embed=embed)
async def ranking_level(self, ctx: Context): """User ranking, by level""" embed = Embed(ctx, title=t(ctx, "title"), description="", color=Color.blue()) await ctx.trigger_typing() async for pos, i in aenumerate(User.all().order_by("-exp").limit(10), start=1): user = await self.bot.fetch_user(i.id) embed.description += t( ctx, "ranking_entry", pos=pos, user=user, lvl=i.level, exp=i.exp ) await ctx.send(embed=embed)
async def ping(self, ctx: Context): """Bot connection latency This isn't very accurate, and mainly used as a "is this bot alive?" command. """ embed = Embed( ctx, title=t(ctx, "title"), description=t(ctx, "message", ping=round(self.bot.latency * 1000)), ) 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 bug(self, ctx: Context): """Where to report bugs and feature requests""" embed = Embed( ctx, title="Bug reporting", description="You can report bugs directly to me (@Predator#xxxx) " "or preferrably, if you are familiar with GitHub, on the " "[issues page](https://github.com/stefankar1000/nagatoro/issues)" "\n\nPlease, provide any errors and context while reporting bugs " "and clearly explain the issue.", ) await ctx.send(embed=embed)
async def send_cog_help(self, cog): ctx = self.context embed = Embed( ctx, title=f"{cog.qualified_name} Commands", description=cog.description, color=Color.blue(), ) commands = self.get_formatted_commands(await self.filter_commands( cog.get_commands())) embed.add_field(name="Commands", value="\n".join(commands)) await ctx.send(embed=embed)
async def send_group_help(self, group): ctx = self.context embed = Embed( ctx, title=f"`{self.get_command_signature(group)}`", description=group.help, color=Color.blue(), ) commands = self.get_formatted_commands(await self.filter_commands( group.commands)) embed.add_field(name="Commands", value="\n".join(commands)) await ctx.send(embed=embed)
async def prefix(self, ctx: Context): """Custom bot prefix""" embed = Embed( ctx, title=t(ctx, "title", guild=ctx.guild.name), description="", color=Color.blue(), ) for i in (await ctx.bot.command_prefix(ctx.bot, ctx.message))[1:]: embed.description += f"- **{i}**\n" return await ctx.send(embed=embed)
async def ranking_balance(self, ctx: Context): """User ranking, sorted by balance""" embed = Embed(ctx, title="Balance Ranking", description="", color=Color.blue()) await ctx.trigger_typing() async for pos, i in aenumerate( User.all().order_by("-balance").limit(10), start=1): user = await self.bot.fetch_user(i.id) embed.description += f"{pos}. **{user}**: {i.balance} coins\n" await ctx.send(embed=embed)
async def ranking_level(self, ctx: Context): """User ranking, by level""" embed = Embed(ctx, title="Level Ranking", description="", color=Color.blue()) await ctx.trigger_typing() async for pos, i in aenumerate(User.all().order_by("-exp").limit(10), start=1): user = await self.bot.fetch_user(i.id) embed.description += f"{pos}. **{user}**: {i.level} ({i.exp} exp)\n" 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 action(self, ctx: Context): """Send an action gif Pressing the 🔁 reaction reloads the image for a new one. The option to refresh lasts 30 seconds and only you can use it. """ await ctx.trigger_typing() embed = Embed(ctx, footer="Via Tenor", color=ctx.author.color) embed.set_image( url=await get_gif(ctx.invoked_with, self.bot.config.tenor_key)) message = await ctx.send(embed=embed) if not ctx.channel.permissions_for(ctx.me).add_reactions: # Do not create the refresh loop if bot can't add reactions return refresh_emoji = "🔁" await message.add_reaction(refresh_emoji) def check(reaction, user): return user == ctx.message.author and str( reaction.emoji) == refresh_emoji while True: try: await self.bot.wait_for("reaction_add", timeout=30, check=check) embed.set_image(url=await get_gif(ctx.invoked_with, self.bot.config.tenor_key)) await message.edit(embed=embed) try: await message.remove_reaction(refresh_emoji, ctx.author) except Forbidden: # No manage_messages permission pass except TimeoutError: break try: await message.clear_reactions() except (Forbidden, NotFound): pass