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 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 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 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 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 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 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 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 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 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 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 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 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 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 mutes_active(self, ctx: Context): """See active mutes""" embed = Embed(ctx, title=t(ctx, "title")) await ctx.trigger_typing() mutes = Mute.filter(guild__id=ctx.guild.id, active=True) if not await mutes.count(): return await ctx.send(t(ctx, "no_mutes", guild=ctx.guild)) # 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) if i.reason: description = t( ctx, "entry_with_reason", given_at= f"{str(i.start.time())[:-10]} {str(i.start.date())[5:]}", duration=i.end - i.start, moderator=moderator.mention, reason=i.reason, ) else: description = t( ctx, "entry", given_at= f"{str(i.start.time())[:-10]} {str(i.start.date())[5:]}", duration=i.end - i.start, moderator=moderator.mention, ) embed.add_field(name=f"{user} [{i.id}]", value=description, inline=False) await ctx.send(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 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 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 = f"Warned {member.mention}, reason: *{reason}*" await ctx.send(embed=embed) try: await member.send(f"You have been warned in **{ctx.guild.name}**, " f"reason: *{warn.reason}*") except (Forbidden, HTTPException, AttributeError): pass
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 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 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 send_bot_help(self, mapping): ctx = self.context embed = Embed( ctx, title="Commands", description=self.get_opening_note(), color=Color.blue(), ) for cog, commands in mapping.items(): if not cog: # Don't show commands without a cog (e.g. the help command) continue category_name = cog.qualified_name filtered_commands = await self.filter_commands(commands) embed.add_field( name=category_name, value=", ".join(i.name for i in filtered_commands), inline=False, ) 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 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 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)