async def check_constraints(self, ctx, now, remaining): if self.dt < now: raise commands.BadArgument('This time is in the past.') if not remaining: if self.default is None: raise commands.BadArgument('Missing argument after the time.') remaining = self.default if self.converter is not None: self.arg = await self.converter.convert(ctx, remaining) else: self.arg = remaining return self
async def jsk_invite(self, ctx: commands.Context, *perms: str): """ Retrieve the invite URL for this bot. If the names of permissions are provided, they are requested as part of the invite. """ scopes = ('bot', 'applications.commands') permissions = disnake.Permissions() for perm in perms: if perm not in dict(permissions): raise commands.BadArgument(f"Invalid permission: {perm}") setattr(permissions, perm, True) application_info = await self.bot.application_info() query = { "client_id": application_info.id, "scope": "+".join(scopes), "permissions": permissions.value } return await ctx.send( f"Link to invite this bot:\n<https://discordapp.com/oauth2/authorize?{urlencode(query, safe='+')}>" )
async def convert(self, ctx, argument): argument = await super().convert(ctx, argument) if argument != disnake.utils.escape_markdown(argument): raise commands.BadArgument('No markdown allowed in prefix.') return argument
def get_raw(self, link: str) -> str: """Returns the url to raw text version of certain pastebin services.""" link = link.strip("<>/") # Allow for no-embed links if not any(link.startswith(url) for url in self.authorized): raise commands.BadArgument( message= f"Only links from the following domains are accepted: {', '.join(self.authorized)}. " f"(Starting with 'https').") domain = link.split("/")[2] if domain == "hastebin.com": if "/raw/" in link: return link token = link.split("/")[-1] if "." in token: token = token[:token.rfind(".")] # removes extension return f"{self.hastebin_link}/raw/{token}" else: # Github uses redirection so raw -> user content and no raw -> normal # We still need to ensure we get a raw version after this potential redirection if "/raw" in link: return link return link + "/raw"
def __init__(self, argument, *, now=None): match = self.compiled.fullmatch(argument) if match is None or not match.group(0): raise commands.BadArgument('invalid time provided') data = {k: int(v) for k, v in match.groupdict(default=0).items()} now = now or datetime.datetime.now(datetime.timezone.utc) self.dt = now + relativedelta(**data)
async def convert(self, ctx, argument): value = _make_int(self, ctx, argument) if value > self.MAX: name = param_name(self, ctx) raise commands.BadArgument(f'{name} must be a lower value.') return value
async def convert(self, ctx, argument): value = _make_int(self, ctx, argument) if value > self.max: name = param_name(self, ctx) raise commands.BadArgument(f'{name} must be lower than {self.max}') return value
async def convert(self, ctx, argument): ret = f'{ctx.author} (ID: {ctx.author.id}): {argument}' if len(ret) > 512: reason_max = 512 - len(ret) - len(argument) raise commands.BadArgument( f'reason is too long ({len(argument)}/{reason_max})') return ret
async def convert(self, ctx, argument): guild_emojis = list(str(e) for e in ctx.guild.emojis) if argument not in emoji.UNICODE_EMOJI['en']: if argument not in guild_emojis: raise commands.BadArgument('Unknown emoji.') return argument
async def convert(self, ctx, action): try: value = SecurityAction[action.upper()] except KeyError: raise commands.BadArgument( '\'{0}\' is not a valid action. Valid actions are {1}'.format( action, self.valid_actions)) return value
async def convert(self, ctx, argument): length = len(argument) if length > self.max: name = param_name(self, ctx) raise commands.BadArgument( f'{name} must be shorter than {self.max} characters.') return argument
async def convert(self, ctx, argument): try: message = await super().convert(ctx, argument) _id = message.id except commands.BadArgument: try: _id = int(argument, base=10) except ValueError: name = param_name(self, ctx) raise commands.BadArgument( f'{name} doesn\'t seem to be a message or message id.') row = await ctx.bot.db.fetchrow( 'SELECT * FROM star_msg WHERE guild_id=$1 AND (message_id=$2 OR star_message_id=$2)', ctx.guild.id, _id) if row is None: name = param_name(self, ctx) raise commands.BadArgument(f'{name} is not a starred message.') return row
async def convert(self, ctx, argument): ban_list = await ctx.guild.bans() try: member_id = int(argument, base=10) entity = disnake.utils.find(lambda u: u.user.id == member_id, ban_list) except ValueError: entity = disnake.utils.find(lambda u: str(u.user) == argument, ban_list) if entity is None: raise commands.BadArgument("Not a valid previously-banned member.") return entity
async def scan_history(self, ctx, limit): """Scan current channel for images and save them as hashes limit: [all | <int>] """ # parse parameter if limit == "all": limit = None else: try: limit = int(limit) if limit < 1: raise ValueError except ValueError: raise commands.BadArgument("Expected 'all' or positive integer") messages = await ctx.channel.history(limit=limit).flatten() title = "**INITIATING...**\n\nLoaded {} messages" await asyncio.sleep(0.5) template = ( "**SCANNING IN PROGRESS**\n\n" "Processed **{}** of **{}** messages ({:.1f} %)\n" "Computed **{}** hashes" ) msg = await ctx.send(title.format(len(messages))) ctr_nofile = 0 ctr_hashes = 0 i = 0 now = time.time() for i, message in enumerate(messages): # update info on every 10th message if i % 50 == 0: await msg.edit( content=template.format(i, len(messages), (i / len(messages) * 100), ctr_hashes) ) if len(message.attachments) == 0: ctr_nofile += 1 continue hashes = [x async for x in self.saveMessageHashes(message)] ctr_hashes += len(hashes) await msg.edit( content="**SCAN COMPLETE**\n\n" f"Processed **{len(messages)}** messages.\n" f"Computed **{ctr_hashes}** hashes in {(time.time() - now):.1f} seconds." )
async def convert(self, ctx, argument): tag_name = await super().convert(ctx, argument.lower()) if tag_name in self._reserved: raise commands.BadArgument('Sorry, that tag name is reserved.') escape_converter = commands.clean_content(fix_channel_mentions=True, escape_markdown=True) if tag_name != await escape_converter.convert(ctx, tag_name): raise commands.BadArgument( 'Tag name has disallowed formatting in it.') if ctx.cog.tag_is_being_made(ctx, tag_name): raise commands.BadArgument( 'Tag with that name is currently being made elsewhere.') exist_id = await ctx.bot.db.fetchval( 'SELECT id FROM tag WHERE guild_id=$1 AND (name=$2 OR alias=$2)', ctx.guild.id, tag_name) if exist_id is not None: raise commands.BadArgument('Tag name is already in use.') return tag_name
async def convert(self, ctx, argument): try: m = await commands.MemberConverter().convert(ctx, argument) except commands.BadArgument: try: member_id = int(argument, base=10) except ValueError: raise commands.BadArgument( f"{argument} is not a valid member or member ID." ) from None else: m = await ctx.bot.get_or_fetch_member(ctx.guild, member_id) if m is None: # hackban case return type('_Hackban', (), { 'id': member_id, '__str__': lambda s: f'Member ID {s.id}' })() if not can_execute_action(ctx, ctx.author, m): raise commands.BadArgument( 'You cannot do this action on this user due to role hierarchy.' ) return m
async def convert(self, ctx, unit): unit = unit.lower() if unit in ('s', 'sec', 'secs', 'second', 'seconds'): return timedelta(seconds=1) elif unit in ('m', 'min', 'mins', 'minute', 'minutes'): return timedelta(minutes=1) elif unit in ('h', 'hr', 'hrs', 'hour', 'hours'): return timedelta(hours=1) elif unit in ('d', 'day', 'days'): return timedelta(days=1) elif unit in ('w', 'wk', 'week', 'weeks'): return timedelta(weeks=1) else: raise commands.BadArgument('Unknown time type.')
def __init__(self, argument, *, now=None): now = now or datetime.datetime.utcnow() dt, status = self.calendar.parseDT(argument, sourceTime=now) if not status.hasDateOrTime: raise commands.BadArgument( 'invalid time provided, try e.g. "tomorrow" or "3 days"') 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) self.dt = dt self._past = dt < now
async def fetch(self, ctx: KurisuContext, _id: int): """Fetch information about a specific discord user""" user = await self.bot.fetch_user(_id) flags = [ v.replace("_", " ").title() for v, b in user.public_flags if b ] if not user: raise commands.BadArgument(f"Failed converting {_id} to user.") await ctx.send( embed=disnake.Embed(title=f"Information for {user}", color=self.bot.info_color).set_thumbnail( url=user.display_avatar.url). add_field(name="ID", value=user.id).add_field( name="Avatar URL", value=f"[url]({user.display_avatar.url})").add_field( name="Account Creation", value=disnake.utils.format_dt(user.created_at, style="R") ).add_field(name="Bot", value=":white_check_mark:" if user.bot else ":x:"). add_field(name="Flags", value="".join(flags) if flags else "None"))
async def source_command(self, ctx: commands.Context, *, source_item: typing.Optional[str] = None) -> None: """Displays information about the bot's source code.""" if source_item is None: embed = Embed(title="Gurkbot's GitHub Repository") embed.add_field(name="Repository", value=f"[Go to GitHub]({BOT_REPO_URL})") embed.set_thumbnail(url=self.bot.user.display_avatar.url) await ctx.send(embed=embed) return elif not ctx.bot.get_command(source_item): raise commands.BadArgument( f"Unable to convert `{source_item}` to valid command or Cog.") github_source = _source.Source(self.bot.http_session, self.bot.user.display_avatar.url) embed = await github_source.inspect( cmd=ctx.bot.get_command(source_item)) await ctx.send(embed=embed)
async def jsk_cancel(self, ctx: commands.Context, *, index: typing.Union[int, str]): """ Cancels a task with the given index. If the index passed is -1, will cancel the last task instead. """ if not self.tasks: return await ctx.send("No tasks to cancel.") if index == "~": task_count = len(self.tasks) for task in self.tasks: task.task.cancel() self.tasks.clear() return await ctx.send(f"Cancelled {task_count} tasks.") if isinstance(index, str): raise commands.BadArgument('Literal for "index" not recognized.') if index == -1: task = self.tasks.pop() else: task = disnake.utils.get(self.tasks, index=index) if task: self.tasks.remove(task) else: return await ctx.send("Unknown task.") task.task.cancel() return await ctx.send( f"Cancelled task {task.index}: `{task.ctx.command.qualified_name}`," f" invoked at {task.ctx.message.created_at.strftime('%Y-%m-%d %H:%M:%S')} UTC" )
def __init__(self, argument, *, now=None): super().__init__(argument, now=now) if self._past: raise commands.BadArgument('this time is in the past')
def _make_error(self, ctx): name = param_name(self, ctx) return commands.BadArgument( f'{name} must be between {self.min} and {self.max} characters.')
def activity_type(argument) -> disnake.ActivityType: try: return disnake.ActivityType[argument] except Exception as e: raise commands.BadArgument(f'Activity not valid: {e}')
async def convert(self, ctx, argument): user_id = ctx.bot.user.id if argument.startswith((f'<@{user_id}>', f'<@!{user_id}>')): raise commands.BadArgument( 'That is a reserved prefix already in use.') return argument
def _make_int(converter, ctx, argument): try: return int(argument) except ValueError: name = param_name(converter, ctx) raise commands.BadArgument(f'{name} should be a number.')
async def convert(self, ctx: commands.Context, argument) -> list: try: return resolve_extensions(ctx.bot, argument) except UnbalancedBracesError as exc: raise commands.BadArgument(str(exc))
async def convert(self, ctx, argument): # Create a copy of ourselves to prevent race conditions from two # events modifying the same instance of a converter result = self.copy() 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() result.dt = now + relativedelta(**data) return await result.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) result.dt = dt.replace(tzinfo=datetime.timezone.utc) 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 result.check_constraints(ctx, now, remaining) except: import traceback traceback.print_exc() raise