def url(url_): url_ = url_.lower() if not url_.startswith(("http://", "https://")) or "." not in url_: raise commands.BadArgument(f"URL `{url_}` is invalid.") return url_
async def _pvp(self, ctx): """Handles pvp related commands""" if ctx.invoked_subcommand == None: raise commands.BadArgument()
async def convert(self, ctx, arg): if arg.lower() == "none": return None if not organizationExists(arg): raise commands.BadArgument("Unexisting organization provided") return arg
def __init__(self, argument, *, now=None): super().__init__(argument, now=now) if self._past: raise commands.BadArgument('this time is in the past')
async def random(self, ctx, *arg): """Get a random number or make the bot choose between something **Example:** `-random` will choose a random number between 1 and 100 `-random 6` will choose a random number between 1 and 6 `-random 50 200` will choose a random number between 50 and 200 `-random coin` will choose Heads or Tails `-random choice "England" "Rome"` will choose between England and Rome """ """ MIT License Copyright (c) 2016 - 2018 Eduard Nikoleisen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ if not arg: start = 1 end = 100 elif arg[0].lower() in ('flip', 'coin', 'coinflip'): coin = ['Heads', 'Tails'] msg = discord.utils.escape_mentions( f':arrows_counterclockwise: **{random.choice(coin)}**') return await ctx.send(msg) elif arg[0].lower() == 'choice': choices = list(arg) choices.pop(0) msg = discord.utils.escape_mentions( f':tada: The winner is: **{random.choice(choices)}**') return await ctx.send(msg) elif len(arg) == 1: start = 1 try: end = int(arg[0]) except ValueError: raise commands.BadArgument() elif len(arg) == 2: try: start = int(arg[0]) end = int(arg[1]) except ValueError: raise commands.BadArgument() else: start = 1 end = 100 try: result = random.randint(start, end) except Exception: raise commands.BadArgument() msg = discord.utils.escape_mentions( f':arrows_counterclockwise: Random number ({start} - {end}): **{result}**' ) await ctx.send(msg)
async def warn(self, ctx: commands.Context, user: typing.Union[discord.Member, int], points: int, *, reason: str = "No reason.") -> None: """Warn a user (mod only) Example usage: -------------- `!warn <@user/ID> <points> <reason (optional)> ` Parameters ---------- user : discord.Member The member to warn points : int Number of points to warn far reason : str, optional Reason for warning, by default "No reason." """ await self.check_permissions(ctx, user) if points < 1: # can't warn for negative/0 points raise commands.BadArgument(message="Points can't be lower than 1.") # if the ID given is of a user who isn't in the guild, try to fetch the profile if isinstance(user, int): try: user = await self.bot.fetch_user(user) except discord.NotFound: raise commands.BadArgument( f"Couldn't find user with ID {user}") guild = self.bot.settings.guild() reason = discord.utils.escape_markdown(reason) reason = discord.utils.escape_mentions(reason) # prepare the case object for database case = Case( _id=guild.case_id, _type="WARN", mod_id=ctx.author.id, mod_tag=str(ctx.author), reason=reason, punishment=str(points) ) # increment case ID in database for next available case ID await self.bot.settings.inc_caseid() # add new case to DB await self.bot.settings.add_case(user.id, case) # add warnpoints to the user in DB await self.bot.settings.inc_points(user.id, points) # fetch latest document about user from DB results = await self.bot.settings.user(user.id) cur_points = results.warn_points # prepare log embed, send to #public-mod-logs, user, channel where invoked log = await logging.prepare_warn_log(ctx.author, user, case) log.add_field(name="Current points", value=cur_points, inline=True) log_kickban = None if cur_points >= 600: # automatically ban user if more than 600 points try: await user.send("You were banned from r/Jailbreak for reaching 600 or more points.", embed=log) except Exception: pass log_kickban = await self.add_ban_case(ctx, user, "600 or more warn points reached.") await user.ban(reason="600 or more warn points reached.") elif cur_points >= 400 and not results.was_warn_kicked and isinstance(user, discord.Member): # kick user if >= 400 points and wasn't previously kicked await self.bot.settings.set_warn_kicked(user.id) try: await user.send("You were kicked from r/Jailbreak for reaching 400 or more points. Please note that you will be banned at 600 points.", embed=log) except Exception: pass log_kickban = await self.add_kick_case(ctx, user, "400 or more warn points reached.") await user.kick(reason="400 or more warn points reached.") else: if isinstance(user, discord.Member): try: await user.send("You were warned in r/Jailbreak. Please note that you will be kicked at 400 points and banned at 600 points.", embed=log) except Exception: pass # also send response in channel where command was called await ctx.message.reply(embed=log, delete_after=10) await ctx.message.delete(delay=10) public_chan = ctx.guild.get_channel( self.bot.settings.guild().channel_public) if public_chan: log.remove_author() log.set_thumbnail(url=user.avatar_url) await public_chan.send(embed=log) if log_kickban: log_kickban.remove_author() log_kickban.set_thumbnail(url=user.avatar_url) await public_chan.send(embed=log_kickban)
async def check_if_queue_exists_or_break(self, guild_id, queue): queues = await self.queue_dao.get_all_queues(guild_id) if not queue in queues: raise commands.BadArgument( f"Queue **{queue}** does not exist. Available queues: {', '.join(queues)}" )
async def help(self, ctx, *command): "Displays help about stuff." icon = self.bot.get_cog("TapTitansModule").emoji("orange_question") fields = {} if not command: title = f"Available modules for {self.bot.user}" description = ", ".join([ f"{c.replace('Module', '')}" for c in self.bot.cogs if str(c) != "PostgreModule" ]) else: command = list(command) cog = [ c for c, v in self.bot.cogs.items() if str(c).lower().startswith(command[0].lower()) or command[0].lower() in getattr(v, "aliases", []) ] if not cog and not self.bot.get_command(command[1:]): raise cmd.BadArgument( f"No module with name: **{command[0].lower()}**") if len(cog) > 1: raise cmd.BadArgument( "I found multiple modules. Please try to narrow your search: {}" .format(", ".join([ f"**{c.replace('Module', '').lower()}**" for c in cog ]))) command = [cog[0].replace("Module", "").lower()] + command[1:] cmx = self.bot.get_command(" ".join(command)) cmg = self.bot.get_cog(" ".join(command).title() + "Module") if cmx is None and cmg is None and len(command) > 1: cmx = self.bot.get_command(" ".join(command[1:])) # cmg = self.bot.get_cog(command[0].title() + "Module") if cmx is not None and cmg is None and len(command) > 1: title = f"Help for command **{ctx.prefix}{cmx}**" obj = HELP.get(str(cmx), {}) description = obj.get("desc", "Not found.").format(ctx=ctx) fields["Usage"] = ("\n".join( [f"{ctx.prefix}{u}" for u in obj.get("usage", [])]) or "Not found.") aliases = ", ".join(getattr(cmx, "aliases", [])) if aliases: fields["Aliases"] = aliases else: cg = self.bot.get_cog(cog[0]) subcommands = list(cg.walk_commands()) module = cg.qualified_name.replace("Module", "").lower() groups, commands = [], [] for sub in subcommands: strsub = str(sub) if hasattr(sub, "walk_commands") and not strsub == module: groups.append(strsub.replace(module, "").strip()) elif len(strsub.split()) < 3: if not module in strsub and not sub.hidden: commands.append(strsub) elif strsub.replace(module, "") and not sub.hidden: commands.append(strsub.replace(module, "")) aliases = ", ".join(getattr(cg, "aliases", [])) title = f"Help for **{module}** module" obj = HELP.get( str(cg.qualified_name).replace("Module", "").lower(), {}) description = (obj.get("desc", None) or cg.__doc__ or "No helpfile found.") description = description.format(ctx=ctx) if aliases: fields["Aliases"] = aliases if groups: fields["Command Groups"] = ", ".join(set(groups)) if commands: fields["Commands"] = ", ".join(set(commands)) embed = discord.Embed(title=title, description=description, color=0xF89D2A) if fields: for field in fields: embed.add_field(name=field, value=fields[field]) if not command: cstm = await self.bot.db.hget(f"{ctx.guild.id}:set", "pfx") if not cstm or cstm is None: cstm = "" pfx = str(cstm) if cstm else self.bot.config["DEFAULT"]["prefix"] embed.add_field(name="Current Prefix", value=pfx) embed.set_thumbnail(url=icon.url) await ctx.send("", embed=embed)
async def convert(self, ctx, argument): if not argument in ctx.bot.extensions: raise commands.BadArgument(f'Extension "{argument}" not found') return argument
async def convert(cls, ctx: commands.Context, argument: str) -> Any: result = await cls._type(argument) if result is None: raise commands.BadArgument(cls._error_message.format(argument)) return result
async def convert(self, ctx: commands.Context, argument: str): result = ctx.bot.get_command(argument) if result is None: raise commands.BadArgument(f'Command \'{argument}\' not found.') return result
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 stats(self, ctx, raid_id: Optional[int]): # TODO: check if raid is complete if not raid_id: last_cleared_raid = await self.raid_service.get_last_completed_raid( ctx.guild.id) if not last_cleared_raid: raise commands.BadArgument( "Could not find any completed ( including uploaded attacks ) raid for your guild" ) raid_id = last_cleared_raid.id has_permission_and_exists, attacks_exist = await asyncio.gather( self.raid_stat_service.has_raid_permission_and_raid_exists( ctx.guild.id, raid_id), self.raid_stat_service.check_if_attacks_exist( ctx.guild.id, raid_id), ) if not has_permission_and_exists: raise commands.BadArgument("Raid does not exist for your guild") if not attacks_exist: raise commands.BadArgument("Please upload player attacks") logger.debug("Loading raid stats for raid %s", raid_id) awaitables = [] # list to collect all messages raid_stats = await self.raid_stat_service.calculate_raid_stats(raid_id) raid_data = await self.raid_stat_service.load_raid_data_for_stats( ctx.guild.id, raid_id) duration_hms = get_hms(raid_stats.cleared_at - raid_stats.started_at) d_h, d_m, d_s = duration_hms[0], duration_hms[1], duration_hms[2] embed = create_embed(self.bot) embed.title = f"Raid conclusion (raid_id: {raid_id})" embed.add_field( name="**General Info**", value="{}".format("\n".join([ f"Attackers: {raid_stats.attackers}", f"Cycles: {math.ceil(raid_stats.max_hits / 4)}", f"Total Damage Dealt: {num_to_hum(raid_stats.total_dmg)}", f"Started at: {raid_stats.started_at.format(DATETIME_FORMAT)}", f"Cleared at: {raid_stats.cleared_at.format(DATETIME_FORMAT)}", f"Time needed: {d_h}h {d_m}m {d_s}s", ])), inline=False, ) embed.add_field( name="**Attacks**", value="\n".join( self._create_min_max_avg_texts(raid_stats.min_hits, raid_stats.max_hits)), ) embed.add_field( name="**Average Player Damage**", value="\n".join( self._create_min_max_avg_texts(raid_stats.min_avg, raid_stats.max_avg, raid_stats.total_avg)), ) embed.add_field( name="**Player Damage**", value="\n".join( self._create_min_max_avg_texts(raid_stats.min_dmg, raid_stats.max_dmg, raid_stats.avg_dmg)), ) awaitables.append(ctx.send(embed=embed)) raid_player_hits = [] string_length = 0 DISCORD_MAX_CONTENT_LENGHT = 2000 - 50 # some buffer latest_raid_data = raid_data[0] if len(raid_data) > 1: reference_raid_data = raid_data[1] else: reference_raid_data = None for idx, player_attack in enumerate( latest_raid_data.raid_player_attacks): if reference_raid_data: reference_player_attack = next( (raid_player_attack for raid_player_attack in reference_raid_data.raid_player_attacks if raid_player_attack.player_id == player_attack.player_id ), None, ) else: reference_player_attack = None if not reference_player_attack: logger.debug( "Did not found any previous attacks for player: %s (%s)", player_attack.player_name, player_attack.player_id, ) player_average = (player_attack.total_dmg / player_attack.total_hits if player_attack.total_hits else 0) player_reference_average = 0 if reference_player_attack and reference_player_attack.total_hits: player_reference_average = (reference_player_attack.total_dmg / reference_player_attack.total_hits) player_average_diff = player_average - player_reference_average player_average_diff_str = "({}{})".format( "+" if player_average_diff > 0 else "", num_to_hum(player_average_diff)) stat_string = "{:2}. {:<20}: {:>8}, {:2}, {:>8} {}".format( idx + 1, player_attack.player_name, num_to_hum(player_attack.total_dmg), player_attack.total_hits, num_to_hum(player_average), player_average_diff_str if reference_player_attack else "", ) if string_length + len(stat_string) >= DISCORD_MAX_CONTENT_LENGHT: awaitables.append( ctx.send("```{}```".format("\n".join(raid_player_hits)))) string_length = 0 raid_player_hits.clear() string_length += len(stat_string) raid_player_hits.append(stat_string) awaitables.append( ctx.send("```{}```".format("\n".join(raid_player_hits)))) await asyncio.gather(*(awaitables))
async def convert(self, ctx, argument): if argument not in ['>', '>=', '<', '<=', '=']: raise commands.BadArgument('Not a valid selector') return argument
class ModActions(commands.Cog): """This cog handles all the possible moderator actions. - Kick - Ban - Unban - Warn - Liftwarn - Mute - Unmute - Purge """ def __init__(self, bot): self.bot = bot async def check_permissions(self, ctx, user: typing.Union[discord.Member, int] = None): if isinstance(user, discord.Member): if user.id == ctx.author.id: await ctx.message.add_reaction("🤔") raise commands.BadArgument("You can't call that on yourself.") if user.id == self.bot.user.id: await ctx.message.add_reaction("🤔") raise commands.BadArgument("You can't call that on me :(") # must be at least a mod if not self.bot.settings.permissions.hasAtLeast(ctx.guild, ctx.author, 5): raise commands.BadArgument( "You do not have permission to use this command.") if user: if isinstance(user, discord.Member): if user.top_role >= ctx.author.top_role: raise commands.BadArgument( message=f"{user.mention}'s top role is the same or higher than yours!") @commands.guild_only() @commands.bot_has_guild_permissions(kick_members=True, ban_members=True) @commands.command(name="warn") async def warn(self, ctx: commands.Context, user: typing.Union[discord.Member, int], points: int, *, reason: str = "No reason.") -> None: """Warn a user (mod only) Example usage: -------------- `!warn <@user/ID> <points> <reason (optional)> ` Parameters ---------- user : discord.Member The member to warn points : int Number of points to warn far reason : str, optional Reason for warning, by default "No reason." """ await self.check_permissions(ctx, user) if points < 1: # can't warn for negative/0 points raise commands.BadArgument(message="Points can't be lower than 1.") # if the ID given is of a user who isn't in the guild, try to fetch the profile if isinstance(user, int): try: user = await self.bot.fetch_user(user) except discord.NotFound: raise commands.BadArgument( f"Couldn't find user with ID {user}") guild = self.bot.settings.guild() reason = discord.utils.escape_markdown(reason) reason = discord.utils.escape_mentions(reason) # prepare the case object for database case = Case( _id=guild.case_id, _type="WARN", mod_id=ctx.author.id, mod_tag=str(ctx.author), reason=reason, punishment=str(points) ) # increment case ID in database for next available case ID await self.bot.settings.inc_caseid() # add new case to DB await self.bot.settings.add_case(user.id, case) # add warnpoints to the user in DB await self.bot.settings.inc_points(user.id, points) # fetch latest document about user from DB results = await self.bot.settings.user(user.id) cur_points = results.warn_points # prepare log embed, send to #public-mod-logs, user, channel where invoked log = await logging.prepare_warn_log(ctx.author, user, case) log.add_field(name="Current points", value=cur_points, inline=True) log_kickban = None if cur_points >= 600: # automatically ban user if more than 600 points try: await user.send("You were banned from r/Jailbreak for reaching 600 or more points.", embed=log) except Exception: pass log_kickban = await self.add_ban_case(ctx, user, "600 or more warn points reached.") await user.ban(reason="600 or more warn points reached.") elif cur_points >= 400 and not results.was_warn_kicked and isinstance(user, discord.Member): # kick user if >= 400 points and wasn't previously kicked await self.bot.settings.set_warn_kicked(user.id) try: await user.send("You were kicked from r/Jailbreak for reaching 400 or more points. Please note that you will be banned at 600 points.", embed=log) except Exception: pass log_kickban = await self.add_kick_case(ctx, user, "400 or more warn points reached.") await user.kick(reason="400 or more warn points reached.") else: if isinstance(user, discord.Member): try: await user.send("You were warned in r/Jailbreak. Please note that you will be kicked at 400 points and banned at 600 points.", embed=log) except Exception: pass # also send response in channel where command was called await ctx.message.reply(embed=log, delete_after=10) await ctx.message.delete(delay=10) public_chan = ctx.guild.get_channel( self.bot.settings.guild().channel_public) if public_chan: log.remove_author() log.set_thumbnail(url=user.avatar_url) await public_chan.send(embed=log) if log_kickban: log_kickban.remove_author() log_kickban.set_thumbnail(url=user.avatar_url) await public_chan.send(embed=log_kickban) @commands.guild_only() @commands.command(name="liftwarn") async def liftwarn(self, ctx: commands.Context, user: discord.Member, case_id: int, *, reason: str = "No reason.") -> None: """Mark a warn as lifted and remove points. (mod only) Example usage: -------------- `!liftwarn <@user/ID> <case ID> <reason (optional)>` Parameters ---------- user : discord.Member User to remove warn from case_id : int The ID of the case for which we want to remove points reason : str, optional Reason for lifting warn, by default "No reason." """ await self.check_permissions(ctx, user) # retrieve user's case with given ID cases = await self.bot.settings.get_case(user.id, case_id) case = cases.cases.filter(_id=case_id).first() reason = discord.utils.escape_markdown(reason) reason = discord.utils.escape_mentions(reason) # sanity checks if case is None: raise commands.BadArgument( message=f"{user} has no case with ID {case_id}") elif case._type != "WARN": raise commands.BadArgument( message=f"{user}'s case with ID {case_id} is not a warn case.") elif case.lifted: raise commands.BadArgument( message=f"Case with ID {case_id} already lifted.")
def mention_converter(argument): try: return MentionMode[argument.lower()] except: raise commands.BadArgument('\U0001f52b Valid modes: ' + ', '.join(MentionMode.__members__))
async def removepoints(self, ctx: commands.Context, user: discord.Member, points: int, *, reason: str = "No reason.") -> None: """Remove warnpoints from a user. (mod only) Example usage: -------------- `!removepoints <@user/ID> <points> <reason (optional)>` Parameters ---------- user : discord.Member User to remove warn from points : int Amount of points to remove reason : str, optional Reason for lifting warn, by default "No reason." """ await self.check_permissions(ctx, user) reason = discord.utils.escape_markdown(reason) reason = discord.utils.escape_mentions(reason) if points < 1: raise commands.BadArgument("Points can't be lower than 1.") u = await self.bot.settings.user(id=user.id) if u.warn_points - points < 0: raise commands.BadArgument( message=f"Can't remove {points} points because it would make {user.mention}'s points negative.") # passed sanity checks, so update the case in DB # remove the warn points from the user in DB await self.bot.settings.inc_points(user.id, -1 * points) case = Case( _id=self.bot.settings.guild().case_id, _type="REMOVEPOINTS", mod_id=ctx.author.id, mod_tag=str(ctx.author), punishment=str(points), reason=reason, ) # increment DB's max case ID for next case await self.bot.settings.inc_caseid() # add case to db await self.bot.settings.add_case(user.id, case) # prepare log embed, send to #public-mod-logs, user, channel where invoked log = await logging.prepare_removepoints_log(ctx.author, user, case) try: await user.send("Your points were removed in r/Jailbreak.", embed=log) except Exception: pass await ctx.message.reply(embed=log, delete_after=10) await ctx.message.delete(delay=10) public_chan = ctx.guild.get_channel( self.bot.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 block(self, ctx, user: Optional[User] = None, *, after: UserFriendlyTime = None): """ Block a user from using Modmail. Note: reasons that start with "System Message: " are reserved for internal use only. """ reason = '' if user is None: thread = ctx.thread if thread: user = thread.recipient elif after is None: raise commands.MissingRequiredArgument(param(name='user')) else: raise commands.BadArgument(f'User "{after.arg}" not found') if after is not None: reason = after.arg if reason.startswith('System Message: '): raise commands.BadArgument('The reason cannot start with `System Message:`.') if re.search(r'%(.+?)%$', reason) is not None: raise commands.MissingRequiredArgument(param(name='reason')) if after.dt > after.now: reason = f'{reason} %{after.dt.isoformat()}%' if not reason: reason = None mention = user.mention if hasattr(user, 'mention') else f'`{user.id}`' extend = f' for `{reason}`' if reason is not None else '' msg = self.bot.blocked_users.get(str(user.id)) if msg is None: msg = '' if str(user.id) not in self.bot.blocked_users or extend or msg.startswith('System Message: '): if str(user.id) in self.bot.blocked_users: old_reason = msg.strip().rstrip('.') or 'no reason' embed = discord.Embed( title='Success', description=f'{mention} was previously blocked for ' f'"{old_reason}". {mention} is now blocked{extend}.', color=self.bot.main_color ) else: embed = discord.Embed( title='Success', color=self.bot.main_color, description=f'{mention} is now blocked{extend}.' ) self.bot.config.blocked[str(user.id)] = reason await self.bot.config.update() else: embed = discord.Embed( title='Error', color=discord.Color.red(), description=f'{mention} is already blocked.' ) return await ctx.send(embed=embed)
async def convert(self, ctx, argument): if argument in RAID_CONFIG_KEYS: return argument raise commands.BadArgument( f"Config key must be one of {', '.join(RAID_CONFIG_KEYS)} ")
async def mute( self, ctx, member: HierarchyMemberConverter, time: TimeConverter, *, reason="None Given", ): """ Mutes a member for the specified time format is `6d` Valid time specifiers are `d`, `m`, `s`, `h` """ muted_role = ctx.guild.get_role(ctx.cache.get("muteid")) if muted_role is None: raise commands.BadArgument( "I cannot find the muted role in the config, this is probably because the role was deleted." ) if muted_role >= ctx.me.top_role: raise commands.BadArgument( "The muted role is greater than or equal to my top role in the hierarchy, please move my role above it." ) if muted_role in member.roles: await ctx.reply(embed=CustomEmbed( description= "User was already muted, changing the mute to the new time.")) await member.remove_roles(muted_role, reason="Unmuting") await ctx.db.execute( "DELETE FROM mutes WHERE guildid = $1 and userid = $2", ctx.guild.id, member.id, ) sleep = (time - datetime.datetime.utcnow()).total_seconds() if sleep <= 1800: task = self.bot.loop.create_task( self.perform_unmute(member=member, role=muted_role, when=time, record=None)) task.add_done_callback(self.unmute_error) await ctx.db.execute( "INSERT INTO mutes(userid, guildid, starttime, endtime, reason) VALUES($1, $2, $3, $4, $5)", member.id, ctx.guild.id, datetime.datetime.utcnow(), time, reason, ) await member.add_roles( muted_role, reason=f"Mute done by {ctx.author} [{ctx.author.id}]") await ctx.reply(embed=CustomEmbed( description=(f"Muted {member}\n" f"For Reason: `{reason}`\n"), timestamp=time, ).set_footer(text="Ends at"))
async def convert(self, ctx, argument): try: calendar = HumanTime.calendar regex = ShortTime.compiled now = ctx.message.created_at match = regex.match(argument) if match is not None and match.group(0): data = {k: int(v) for k, v in match.groupdict(default=0).items()} remaining = argument[match.end():].strip() self.dt = now + relativedelta(**data) return await self.check_constraints(ctx, now, remaining) # apparently nlp does not like "from now" # it likes "from x" in other cases though so let me handle the 'now' case if argument.endswith('from now'): argument = argument[:-8].strip() if argument[0:2] == 'me': # starts with "me to", "me in", or "me at " if argument[0:6] in ('me to ', 'me in ', 'me at '): argument = argument[6:] elements = calendar.nlp(argument, sourceTime=now) if elements is None or len(elements) == 0: raise commands.BadArgument('Invalid time provided, try e.g. "tomorrow" or "3 days".') # handle the following cases: # "date time" foo # date time foo # foo date time # first the first two cases: dt, status, begin, end, dt_string = elements[0] if not status.hasDateOrTime: raise commands.BadArgument('Invalid time provided, try e.g. "tomorrow" or "3 days".') if begin not in (0, 1) and end != len(argument): raise commands.BadArgument('Time is either in an inappropriate location, which ' 'must be either at the end or beginning of your input, ' 'or I just flat out did not understand what you meant. Sorry.') if not status.hasTime: # replace it with the current time dt = dt.replace(hour=now.hour, minute=now.minute, second=now.second, microsecond=now.microsecond) # if midnight is provided, just default to next day if status.accuracy == pdt.pdtContext.ACU_HALFDAY: dt = dt.replace(day=now.day + 1) self.dt = dt if begin in (0, 1): if begin == 1: # check if it's quoted: if argument[0] != '"': raise commands.BadArgument('Expected quote before time input...') if not (end < len(argument) and argument[end] == '"'): raise commands.BadArgument('If the time is quoted, you must unquote it.') remaining = argument[end + 1:].lstrip(' ,.!') else: remaining = argument[end:].lstrip(' ,.!') elif len(argument) == end: remaining = argument[:begin].strip() return await self.check_constraints(ctx, now, remaining) except Exception: import traceback traceback.print_exc() raise
async def get_run_output(self, ctx): # Get parameters to call api depending on how the command was called (file <> codeblock) if ctx.message.attachments: alias, source, args, stdin = await self.get_api_parameters_with_file( ctx) else: alias, source, args, stdin = await self.get_api_parameters_with_codeblock( ctx) # Resolve aliases for language language = self.languages[alias] # Add boilerplate code to supported languages source = add_boilerplate(language, source) # Split args at newlines if args: args = [arg for arg in args.strip().split('\n') if arg] if not source: raise commands.BadArgument(f'No source code found') # Call piston API data = { 'language': alias, 'version': '*', 'files': [{ 'content': source }], 'args': args, 'stdin': stdin or "", 'log': 0 } headers = {'Authorization': self.client.config["emkc_key"]} async with self.client.session.post( 'https://emkc.org/api/v2/piston/execute', headers=headers, json=data) as response: try: r = await response.json() except ContentTypeError: raise PistonInvalidContentType('invalid content type') if not response.status == 200: raise PistonInvalidStatus( f'status {response.status}: {r.get("message", "")}') r = r['run'] if r['output'] is None: raise PistonNoOutput('no output') # Logging await self.send_to_log(ctx, language, source) # Return early if no output was received if len(r['output']) == 0: return f'Your code ran without output {ctx.author.mention}' # Limit output to 30 lines maximum output = '\n'.join(r['output'].split('\n')[:30]) # Prevent mentions in the code output output = escape_mentions(output) # Prevent code block escaping by adding zero width spaces to backticks output = output.replace("`", "`\u200b") # Truncate output to be below 2000 char discord limit. if len(r['stdout']) == 0 and len(r['stderr']) > 0: introduction = f'{ctx.author.mention} I only received error output\n' else: introduction = f'Here is your output {ctx.author.mention}\n' truncate_indicator = '[...]' len_codeblock = 7 # 3 Backticks + newline + 3 Backticks available_chars = 2000 - len(introduction) - len_codeblock if len(output) > available_chars: output = output[:available_chars - len(truncate_indicator)] + truncate_indicator return (introduction + '```\n' + output + '```')
async def convert(self, _: Context, argument: str) -> IO: try: return JLPT_LOOKUP[argument.lower().strip()] except KeyError: raise commands.BadArgument("Invalid key for JLPT level.")
async def base(self, ctx, b1: Base, b2: Base, *values): ''' Converts values (separated by spaces*) from one base to another. __Supported bases__ `2` - `36` (case-insensitive), `37` - `62` (0-9, A-Z, a-z), `63` - (list of values are returned), `unicode` *A single value in base Unicode is interpreted as one char instead of a block separated by spaces. *A single value in base 63+ is interpreted as one list instead of a block separated by spaces. ''' if b1 == b2: await ctx.send(' '.join(values)) return [b1, b1m, b2, b2m] = [*b1, *b2] chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' if b1m == 1 or b2m == 1 else '0123456789abcdefghijklmnopqrstuvwxyz' if b1 == 'u': ustr = ' '.join(values) # returns the unicode base string ord_lst = [ord(c) for c in ustr] if b2 == 10: await ctx.send(' '.join(str(l) for l in ord_lst)) return newb_lst = [self.b10_to_base(b2, v) for v in ord_lst] # converts ords to list of new base # converts list into display format & return if b2m != 2: disp_lst = [''.join(chars[v] for v in l) for l in newb_lst] else: disp_lst = [str(l) for l in newb_lst] await ctx.send(' '.join(disp_lst)) return try: # returns a list of numeric values if b1m == 0: val_lst = [int(s, base=b1) for s in values] if b1m == 1: val_lst = [[chars[:b1].index(c) for c in s] for s in values] val_lst = [self.base_to_b10(b1, v) for v in val_lst] if b1m == 2: # separates values by array values = (' '.join(values)).split('] [') values = [s if s.startswith('[') else f'[{s}' for s in values] values = [s if s.endswith(']') else f'{s}]' for s in values] val_lst = [loads(lst) for lst in values] val_lst = [self.base_to_b10(b1, v) for v in val_lst] except ValueError: print('\n\n\n') print_exc() raise commands.BadArgument('The input values are invalid.') if b2 == 'u': # convert vals to chrs and return chr_lst = [chr(v) for v in val_lst] await ctx.send(''.join(chr_lst)) return newb_lst = [self.b10_to_base(b2, v) for v in val_lst] # converts list to display format & return if b2m != 2: disp_lst = [''.join(chars[v] for v in l) for l in newb_lst] else: disp_lst = [str(l) for l in newb_lst] await ctx.send(' '.join(disp_lst))
async def roll(self, ctx, *, dices): """Roll some dice **Supported Notation** - Dice rolls take the form NdX, where N is the number of dice to roll, and X are the faces of the dice. For example, 1d6 is one six-sided die. - A dice roll can be followed by an Ln or Hn, where it will discard the lowest n rolls or highest n rolls, respectively. So 2d20L1 means to roll two d20s and discard the lower. I.E advantage. - A dice roll can be part of a mathematical expression, such as 1d4 +5. **Example:** `-roll 1d6` will roll a d6 `-roll (2d20L1) + 1d4 + 5` will roll 2d20s, discard the lower one, and add 1d4 and 5 to the result *Full notation can be found here: https://xdice.readthedocs.io/en/latest/dice_notation.html* """ #Todo: # []: Make the special message system more robust and logical try: # Attempt to parse the user command and roll the dice dice_pattern = xdice.Pattern(dices) dice_pattern.compile() roll = dice_pattern.roll() except (SyntaxError, TypeError, ValueError): raise commands.BadArgument() # Defining some helper variables special_message = "" roll_information = [] # Loop over each dice roll and add it to the intermediate text for score in roll.scores(): score_string = "" if len(score.detail) > 1: score_string = f"{score_string}{' + '.join(map(str,score.detail))}" else: score_string = f"{score_string}{score.detail[0]}" if score.dropped == []: pass elif len(score.dropped) > 1: score_string = f"{score_string} ~~+ {' + '.join(map(str,score.dropped))}~~" else: score_string = f"{score_string} ~~+ {score.dropped[0]}~~" # Add a special message if a user rolls a 20 or 1 if "d20" in score.name: if 1 in score.detail: special_message = "Aww, you rolled a natural 1" elif 20 in score.detail: special_message = "Yay! You rolled a natural 20" score_string = f"[{score_string}]" roll_information.append(score_string) # Put spaces between the operators in the xdice template format_string = dice_pattern.format_string for i in ["+", "-", "/", "*"]: format_string = format_string.replace(i, f" {i} ") #Format the intermediate text using the previous template rolls = format_string.format(*roll_information) #Create the final message msg = f"{ctx.author.mention}:\n> `{dices}` = {rolls} = {roll}" if special_message: msg = f"{msg}\n{special_message}" await ctx.send(msg)
async def infraction_edit( self, ctx: Context, infraction_id: t.Union[ int, allowed_strings("l", "last", "recent")], # noqa: F821 duration: t.Union[Expiry, allowed_strings("p", "permanent"), None], # noqa: F821 *, reason: str = None) -> None: """ Edit the duration and/or the reason of an infraction. Durations are relative to the time of updating and should be appended with a unit of time. Units (∗case-sensitive): \u2003`y` - years \u2003`m` - months∗ \u2003`w` - weeks \u2003`d` - days \u2003`h` - hours \u2003`M` - minutes∗ \u2003`s` - seconds Use "l", "last", or "recent" as the infraction ID to specify that the most recent infraction authored by the command invoker should be edited. Use "p" or "permanent" to mark the infraction as permanent. Alternatively, an ISO 8601 timestamp can be provided for the duration. """ if duration is None and reason is None: # Unlike UserInputError, the error handler will show a specified message for BadArgument raise commands.BadArgument( "Neither a new expiry nor a new reason was specified.") # Retrieve the previous infraction for its information. if isinstance(infraction_id, str): params = {"actor__id": ctx.author.id, "ordering": "-inserted_at"} infractions = await self.bot.api_client.get("bot/infractions", params=params) if infractions: old_infraction = infractions[0] infraction_id = old_infraction["id"] else: await ctx.send( ":x: Couldn't find most recent infraction; you have never given an infraction." ) return else: old_infraction = await self.bot.api_client.get( f"bot/infractions/{infraction_id}") request_data = {} confirm_messages = [] log_text = "" if duration is not None and not old_infraction['active']: if reason is None: await ctx.send( ":x: Cannot edit the expiration of an expired infraction.") return confirm_messages.append( "expiry unchanged (infraction already expired)") elif isinstance(duration, str): request_data['expires_at'] = None confirm_messages.append("marked as permanent") elif duration is not None: request_data['expires_at'] = duration.isoformat() expiry = time.format_infraction_with_duration( request_data['expires_at']) confirm_messages.append(f"set to expire on {expiry}") else: confirm_messages.append("expiry unchanged") if reason: request_data['reason'] = reason confirm_messages.append("set a new reason") log_text += f""" Previous reason: {old_infraction['reason']} New reason: {reason} """.rstrip() else: confirm_messages.append("reason unchanged") # Update the infraction new_infraction = await self.bot.api_client.patch( f'bot/infractions/{infraction_id}', json=request_data, ) # Re-schedule infraction if the expiration has been updated if 'expires_at' in request_data: # A scheduled task should only exist if the old infraction wasn't permanent if old_infraction['expires_at']: self.infractions_cog.scheduler.cancel(new_infraction['id']) # If the infraction was not marked as permanent, schedule a new expiration task if request_data['expires_at']: self.infractions_cog.schedule_expiration(new_infraction) log_text += f""" Previous expiry: {old_infraction['expires_at'] or "Permanent"} New expiry: {new_infraction['expires_at'] or "Permanent"} """.rstrip() changes = ' & '.join(confirm_messages) await ctx.send( f":ok_hand: Updated infraction #{infraction_id}: {changes}") # Get information about the infraction's user user_id = new_infraction['user'] user = ctx.guild.get_member(user_id) if user: user_text = messages.format_user(user) thumbnail = user.avatar_url_as(static_format="png") else: user_text = f"<@{user_id}>" thumbnail = None await self.mod_log.send_log_message(icon_url=constants.Icons.pencil, colour=discord.Colour.blurple(), title="Infraction edited", thumbnail=thumbnail, text=textwrap.dedent(f""" Member: {user_text} Actor: <@{new_infraction['actor']}> Edited by: {ctx.message.author.mention}{log_text} """))
async def updates(self, ctx: context.Context, *, board:str): """(alias !updates) Get ChromeOS version data for a specified Chromebook board name Example usage -------------- !updates edgar Parameters ---------- board : str "name of board to get updates for" """ # ensure the board arg is only alphabetical chars if (not board.isalpha()): raise commands.BadArgument("The board should only be alphabetical characters!") # case insensitivity board = board.lower() # fetch data from skylar's API data = "" async with aiohttp.ClientSession() as session: data = await fetch(session, 'https://raw.githubusercontent.com/skylartaylor/cros-updates/master/src/data/cros-updates.json') if data is None: return #parse response to json data = json.loads(data) # loop through response to find board for data_board in data: # did we find a match if data_board['Codename'] == board: # yes, send the data embed = Embed(title=f"ChromeOS update status for {board}", color=Color(value=0x37b83b)) version = data_board["Stable"].split("<br>") embed.add_field(name=f'Stable Channel', value=f'**Version**: {version[1]}\n**Platform**: {version[0]}') version = data_board["Beta"].split("<br>") if len(version) == 2: embed.add_field(name=f'Beta Channel', value=f'**Version**: {version[1]}\n**Platform**: {version[0]}') else: embed.add_field(name=f'Beta Channel', value=f'**Version**: {data_board["Beta"]}') version = data_board["Dev"].split("<br>") if len(version) == 2: embed.add_field(name=f'Dev Channel', value=f'**Version**: {version[1]}\n**Platform**: {version[0]}') else: embed.add_field(name=f'Dev Channel', value=f'**Version**: {data["Dev"]}') if (data_board["Canary"] is not None): version = data_board["Canary"].split("<br>") if len(version) == 2: embed.add_field(name=f'Canary Channel', value=f'**Version**: {version[1]}\n**Platform**: {version[0]}') embed.set_footer(text=f"Powered by https://cros.tech/ (by Skylar), requested by {ctx.author.name}#{ctx.author.discriminator}", icon_url=ctx.author.avatar_url) await ctx.message.reply(embed=embed) return # board not found, error raise commands.BadArgument("Couldn't find a result with that boardname!")
reason = discord.utils.escape_mentions(reason) # sanity checks if case is None: raise commands.BadArgument( message=f"{user} has no case with ID {case_id}") elif case._type != "WARN": raise commands.BadArgument( message=f"{user}'s case with ID {case_id} is not a warn case.") elif case.lifted: raise commands.BadArgument( message=f"Case with ID {case_id} already lifted.") u = await self.bot.settings.user(id=user.id) if u.warn_points - int(case.punishment) < 0: raise commands.BadArgument( message=f"Can't lift Case #{case_id} because it would make {user.mention}'s points negative.") # passed sanity checks, so update the case in DB case.lifted = True case.lifted_reason = reason case.lifted_by_tag = str(ctx.author) case.lifted_by_id = ctx.author.id case.lifted_date = datetime.datetime.now() cases.save() # remove the warn points from the user in DB await self.bot.settings.inc_points(user.id, -1 * int(case.punishment)) # prepare log embed, send to #public-mod-logs, user, channel where invoked log = await logging.prepare_liftwarn_log(ctx.author, user, case) try:
async def convert(self, ctx, arg): if re.match(r"\w+:\d\d?", arg): tag, value = arg.split(":") return tag, int(value) raise commands.BadArgument( "Invalid Battle Entity provided, cannot convert")
async def charinfo(self, ctx, *, data: str): """Shows information about one or several characters. 'data' can either be a character, a unicode escape sequence, a unicode character name or a string. If 'data' is a string only a summary of each character's info will be displayed. """ data = data.lower() if data.startswith('\\u'): # Let's interpret the unicode escape sequence hex_values = data.split('\\u')[1:] try: code_points = [int(val, 16) for val in hex_values] except ValueError: raise commands.BadArgument('Invalid unicode escape sequence.') else: data = ''.join(chr(cp) for cp in code_points) elif len(data) > 1: # Maybe we've been given the character's name ? try: data = unicodedata.lookup(data) except KeyError: pass # Normalise the input data = unicodedata.normalize('NFC', data) url_fmt = '<http://unicode-table.com/en/{:X}>' if len(data) == 1: # Detailed info on the character entries = [('Character', data), ('Name', unicodedata.name(data, 'None')), ('Code point', f'{ord(data):04x}')] decomposition = unicodedata.decomposition(data) if decomposition != '': entries.append(('Decomposition', decomposition)) combining = unicodedata.combining(data) if combining: entries.append(('Combining class', combining)) entries.append(('Category', unicodedata.category(data))) bidirectional = unicodedata.bidirectional(data) entries.append(('Bidirectional', bidirectional if bidirectional != '' else 'None')) entries.append( ('Mirrored', 'True' if unicodedata.mirrored(data) == 1 else 'False')) entries.append( ('East asian width', unicodedata.east_asian_width(data))) entries.append(('Url', url_fmt.format(ord(data)))) # Create the message's content and send it content = utils.indented_entry_to_str(entries) await ctx.send(utils.format_block(content)) else: # Minimal info for each character entries = [ f'`\N{ZERO WIDTH SPACE}{c}\N{ZERO WIDTH SPACE}` | `\\u{ord(c):04x}` | `{unicodedata.name(c, "None")}` | {url_fmt.format(ord(c))}' for c in data ] content = '\n'.join(entries) await ctx.send(content)