async def cases(self, ctx: context.Context, user: typing.Union[discord.Member, int] = None): """Show list of cases of a user (mod only) Example usage -------------- !cases <@user/ID> Parameters ---------- user : typing.Union[discord.Member,int] "User we want to get cases of, doesn't have to be in guild" """ if user is None: # hack workaround user = ctx.author ctx.args[2] = user # users can only check their own cases if they aren't mods if not isinstance(user, int): if not ctx.permissions.hasAtLeast(ctx.guild, ctx.author, 5) and user.id != ctx.author.id: raise commands.BadArgument( f"You don't have permissions to check others' cases.") else: if not ctx.permissions.hasAtLeast(ctx.guild, ctx.author, 5): raise commands.BadArgument( f"You don't have permissions to check others' cases.") # if user not in guild, fetch their profile from the Discord API if isinstance(user, int): try: user = await self.bot.fetch_user(user) except Exception: raise commands.BadArgument( f"Couldn't find user with ID {user}") ctx.args[2] = user # fetch user's cases from our database results = await ctx.settings.cases(user.id) if len(results.cases) == 0: if isinstance(user, int): raise commands.BadArgument( f'User with ID {user.id} had no cases.') else: raise commands.BadArgument(f'{user.mention} had no cases.') # filter out unmute cases because they are irrelevant cases = [case for case in results.cases if case._type != "UNMUTE"]
async def repo(self, ctx: context.Context, *, repo): async with ctx.typing(): data = await self.repo_request(repo) if data is None: embed = discord.Embed(title="Error", color=discord.Color.red()) embed.description = f'An error occurred while searching for that repo' await ctx.message.delete(delay=5) await ctx.send(embed=embed, delete_after=5) return if not isinstance(data, list): data = [data] if len(data) == 0: embed = discord.Embed(title=":(\nYour command ran into a problem", color=discord.Color.red()) embed.description = f"Sorry, I couldn't find a repo by that name." await ctx.message.delete(delay=5) await ctx.send(embed=embed, delete_after=5) return menus = MenuPages(source=ReposSource(data, key=lambda t: 1, per_page=1), clear_reactions_after=True) await menus.start(ctx)
async def cases(self, ctx: context.Context, user: typing.Union[discord.Member, int] = None): """Show list of cases of a user (mod only) Example usage -------------- !cases <@user/ID> Parameters ---------- user : typing.Union[discord.Member,int] "User we want to get cases of, doesn't have to be in guild" """ if user is None: user = ctx.author ctx.args[2] = user if not isinstance(user, int): if not ctx.settings.permissions.hasAtLeast( ctx.guild, ctx.author, 2) and user.id != ctx.author.id: raise commands.BadArgument( f"You don't have permissions to check others' cases.") else: if not ctx.settings.permissions.hasAtLeast(ctx.guild, ctx.author, 2): raise commands.BadArgument( f"You don't have permissions to check others' cases.") if isinstance(user, int): try: user = await self.bot.fetch_user(user) except Exception: raise commands.BadArgument( f"Couldn't find user with ID {user}") ctx.args[2] = user results = await ctx.settings.cases(user.id) if len(results.cases) == 0: if isinstance(user, int): raise commands.BadArgument( f'User with ID {user.id} had no cases.') else: raise commands.BadArgument(f'{user.mention} had no cases.') cases = [case for case in results.cases if case._type != "UNMUTE"]
async def reset(self, ctx: context.Context): """Reset all points (mod only) """ async with ctx.typing(): members_reset = await ctx.settings.reset_trivia_points() if not members_reset: raise commands.BadArgument("There were no points to reset!") await ctx.message.delete(delay=5) await ctx.send_success( title="Done!", description=f"I reset {members_reset} users' points.", delete_after=5)
async def jumbo(self, ctx: context.Context, emoji: typing.Union[discord.Emoji, discord.PartialEmoji, str]): """Post large version of a given emoji Example usage ------------- !jumbo :ntwerk: Parameters ---------- emoji : typing.Union[discord.Emoji, discord.PartialEmoji] "Emoji to post" """ # non-mod users will be ratelimited bot_chan = self.bot.settings.guild().channel_botspam if not self.bot.settings.permissions.hasAtLeast( ctx.guild, ctx.author, 5) and ctx.channel.id != bot_chan: if await self.ratelimit(ctx.message): raise commands.BadArgument("This command is on cooldown.") # is this a regular Unicode emoji? if isinstance(emoji, str): # yes, read the bytes from our JSON file and turn it into an image async with ctx.typing(): emoji_url_file = self.emojis.get(emoji) if emoji_url_file is None: raise commands.BadArgument( "Couldn't find a suitable emoji.") im = Image.open(BytesIO(base64.b64decode(emoji_url_file))) image_container = BytesIO() im.save(image_container, 'png') image_container.seek(0) _file = discord.File(image_container, filename="image.png") await ctx.message.reply(file=_file, mention_author=False) else: # no, this is a custom emoji. send its URL await ctx.message.reply(emoji.url, mention_author=False)
async def jumbo(self, ctx: context.Context, emoji: typing.Union[discord.Emoji, discord.PartialEmoji, str]): """Post large version of a given emoji Example usage ------------- !jumbo :ntwerk: Parameters ---------- emoji : typing.Union[discord.Emoji, discord.PartialEmoji] "Emoji to post" """ bot_chan = ctx.settings.guild().channel_offtopic if not ctx.settings.permissions.hasAtLeast( ctx.guild, ctx.author, 2) and ctx.channel.id != bot_chan: if await self.ratelimit(ctx.message): raise commands.BadArgument("This command is on cooldown.") if isinstance(emoji, str): async with ctx.typing(): emoji_url_file = self.emojis.get(emoji) if emoji_url_file is None: raise commands.BadArgument( "Couldn't find a suitable emoji.") im = Image.open(BytesIO(base64.b64decode(emoji_url_file))) image_container = BytesIO() im.save(image_container, 'png') image_container.seek(0) _file = discord.File(image_container, filename="image.png") await ctx.message.reply(file=_file, mention_author=False) else: await ctx.message.reply(emoji.url, mention_author=False)
async def batchraid(self, ctx: context.Context, *, phrases: str) -> None: """Add a list of (newline-separated) phrases to the raid filter. Example usage -------------- !raid <phrase> Parameters ---------- phrases : str "Phrases to add, separated with enter" """ async with ctx.typing(): phrases = list(set(phrases.split("\n"))) phrases = [phrase.strip() for phrase in phrases] phrases_contenders = set(phrases) phrases_already_in_db = set( [phrase.word for phrase in ctx.settings.guild().raid_phrases]) duplicate_count = len( phrases_already_in_db & phrases_contenders) # count how many duplicates we have new_phrases = list(phrases_contenders - phrases_already_in_db) if not new_phrases: raise commands.BadArgument( "All the phrases you supplied are already in the database.") phrases_prompt_string = "\n".join( [f"**{i+1}**. {phrase}" for i, phrase in enumerate(new_phrases)]) if len(phrases_prompt_string) > 3900: phrases_prompt_string = phrases_prompt_string[:3500] + "\n... (and some more)" embed = Embed( title="Confirm raidphrase batch", color=discord.Color.dark_orange(), description= f"{phrases_prompt_string}\n\nShould we add these {len(new_phrases)} phrases?" ) if duplicate_count > 0: embed.set_footer( text= f"Note: we found {duplicate_count} duplicates in your list.") message = await ctx.send(embed=embed) prompt_data = context.PromptDataReaction(message=message, reactions=['✅', '❌'], timeout=120, delete_after=True) response, _ = await ctx.prompt_reaction(info=prompt_data) if response == '✅': async with ctx.typing(): for phrase in new_phrases: await ctx.settings.add_raid_phrase(phrase) await ctx.send_success( f"Added {len(new_phrases)} phrases to the raid filter.", delete_after=5) else: await ctx.send_warning("Cancelled.", delete_after=5) await ctx.message.delete(delay=5)
async def ban(self, ctx: context.Context, user: permissions.ModsAndAboveExternal, *, reason: str = "No reason."): """Ban a user (mod only) Example usage -------------- !ban <@user/ID> <reason (optional)> Parameters ---------- user : typing.Union[discord.Member, int] "The user to be banned, doesn't have to be part of the guild" reason : str, optional "Reason for ban, by default 'No reason.'" """ reason = discord.utils.escape_markdown(reason) reason = discord.utils.escape_mentions(reason) member_is_external = ctx.guild.get_member(user.id) is None if self.ban_list_cache is None: self.ban_list_cache = { user.id for _, user in await ctx.guild.bans() } # if the ID given is of a user who isn't in the guild, try to fetch the profile if member_is_external: async with ctx.typing(): # previous_bans = [user for _, user in await ctx.guild.bans()] if user.id in self.ban_list_cache: raise commands.BadArgument("That user is already banned!") self.ban_list_cache.add(user.id) log = await self.add_ban_case(ctx, user, reason) if not member_is_external: try: await user.send(f"You were banned from {ctx.guild.name}", embed=log) except Exception: pass if not member_is_external: await user.ban(reason=reason) else: # hackban for user not currently in guild await ctx.guild.ban(discord.Object(id=user.id)) await ctx.message.reply(embed=log, delete_after=10) await ctx.message.delete(delay=10) public_chan = ctx.guild.get_channel( ctx.settings.guild().channel_public) if public_chan: log.remove_author() log.set_thumbnail(url=user.avatar_url) await public_chan.send(embed=log)
async def editreason(self, ctx: context.Context, user: permissions.ModsAndAboveExternal, case_id: int, *, new_reason: str) -> None: """Edit case reason and the embed in #public-mod-logs. (mod only) Example usage -------------- !editreason <@user/ID> <case ID> <reason> Parameters ---------- user : discord.Member "User to edit case of" case_id : int "The ID of the case for which we want to edit reason" new_reason : str "New reason" """ # retrieve user's case with given ID cases = await ctx.settings.get_case(user.id, case_id) case = cases.cases.filter(_id=case_id).first() new_reason = discord.utils.escape_markdown(new_reason) new_reason = discord.utils.escape_mentions(new_reason) # sanity checks if case is None: raise commands.BadArgument( message=f"{user} has no case with ID {case_id}") old_reason = case.reason case.reason = new_reason case.date = datetime.datetime.now() cases.save() dmed = True log = await logging.prepare_editreason_log(ctx.author, user, case, old_reason) if isinstance(user, discord.Member): try: await user.send(f"Your case was updated in {ctx.guild.name}.", embed=log) except Exception: dmed = False public_chan = ctx.guild.get_channel( ctx.settings.guild().channel_public) found = False async with ctx.typing(): async for message in public_chan.history(limit=200): if message.author.id != ctx.me.id: continue if len(message.embeds) == 0: continue embed = message.embeds[0] # print(embed.footer.text) if embed.footer.text == discord.Embed.Empty: continue if len(embed.footer.text.split(" ")) < 2: continue if f"#{case_id}" == embed.footer.text.split(" ")[1]: for i, field in enumerate(embed.fields): if field.name == "Reason": embed.set_field_at(i, name="Reason", value=new_reason) await message.edit(embed=embed) found = True if found: await ctx.message.reply( f"We updated the case and edited the embed in {public_chan.mention}.", embed=log, delete_after=10) else: await ctx.message.reply( f"We updated the case but weren't able to find a corresponding message in {public_chan.mention}!", embed=log, delete_after=10) log.remove_author() log.set_thumbnail(url=user.avatar_url) await public_chan.send(user.mention if not dmed else "", embed=log) await ctx.message.delete(delay=10)
async def leaderboard(self, ctx: context.Context, full: str = None): """Get karma leaderboard for the server """ ctx.user_cache = self.user_cache class Source(menus.GroupByPageSource): async def format_page(self, menu, entry): embed = discord.Embed( title= f'Leaderboard: Page {menu.current_page +1}/{self.get_max_pages()}', color=discord.Color(value=0xfcba03)) embed.set_footer( icon_url=ctx.author.avatar_url, text= "Note: Nerds and Moderators were excluded from these results." ) embed.description = "" for i, user in enumerate(entry.items): member = menu.ctx.guild.get_member(user._id) member_found = member is not None if not member_found: if user._id not in menu.ctx.user_cache: try: member = await menu.ctx.bot.fetch_user(user._id ) menu.ctx.user_cache[user._id] = member except Exception: member = None else: member = menu.ctx.user_cache[user._id] embed.add_field( name=f"Rank {i+1}", value= f"{member.mention} ({member})\n{user.karma} karma", inline=False) return embed data = await ctx.settings.leaderboard() if (len(data) == 0): raise commands.BadArgument("No history in this guild!") else: data_final = [] for u in data: member = ctx.guild.get_member(u._id) if member: if full is None: if not ctx.settings.permissions.hasAtLeast( member.guild, member, 1): data_final.append(u) else: data_final.append(u) else: data_final.append(u) pages = NewMenuPages(source=Source(data_final, key=lambda t: 1, per_page=10), clear_reactions_after=True) await pages.start(ctx)