async def convert(self, ctx, argument): match = MESSAGE_ID_RE.match(argument) or MESSAGE_LINK_RE.match( argument) if not match: raise errors.BadArgument( "{} doesn't look like a message to me...".format(argument)) msg_id = int(match.group("message_id")) channel_id = int(match.group("channel_id") or ctx.channel.id) channel = ctx.guild.get_channel(channel_id) if not channel: channel = ctx.bot.get_channel(channel_id) if not channel: raise errors.BadArgument("Channel {} not found".format(channel_id)) author = channel.guild.get_member(ctx.author.id) if not channel.guild.me.permissions_in(channel).read_messages: raise errors.CheckFailure( "I don't have permission to view channel {0.mention}".format( channel)) if not author or not channel.permissions_for(author).read_messages: raise errors.CheckFailure( "You don't have permission to view channel {0.mention}".format( channel)) return (channel, msg_id)
async def convert(self, ctx, argument): match = MESSAGE_ID_RE.match(argument) or MESSAGE_LINK_RE.match( argument) if not match: cleaned = await clean_content().convert( ctx, f"{argument} doesn't look like a message to me…") raise errors.BadArgument(cleaned) msg_id = int(match['message_id']) channel_id = int(match['channel_id'] or ctx.channel.id) channel = ctx.guild.get_channel(channel_id) if not channel: channel = ctx.bot.get_channel(channel_id) if not channel: raise errors.BadArgument(f'Channel {channel_id} not found.') author = channel.guild.get_member(ctx.author.id) if not channel.guild.me.permissions_in(channel).read_messages: raise errors.CheckFailure( f"I don't have permission to view channel {channel.mention}.") if not author or not channel.permissions_for(author).read_messages: raise errors.CheckFailure( f"You don't have permission to view channel {channel.mention}." ) return (channel, msg_id)
async def star(self, ctx, *, msg): """Create a star out of a string 1-25 characters long.""" if (len(msg) > 25): raise errors.BadArgument("String must be less than 26 characters") elif (len(msg) == 0): raise errors.BadArgument("String must be at least 1 character") str = '```\n' mid = len(msg) - 1 for i in range(len(msg) * 2 - 1): if (mid == i): str += msg[::-1] + msg[1:] + "\n" else: let = abs(mid - i) str += " " * (mid - let) str += msg[let] str += " " * (let - 1) str += msg[let] str += " " * (let - 1) str += msg[let] str += "\n" str += "```" await ctx.send(str)
def resolve_color(value): """Resolve a custom or pre-defined color. This allows html style #RRGGBB and #AARRGGBB Returns (r, g, b) or (a, r, g, b) """ if value.startswith('#'): value = value[1:] # assumes no named color starts with # try: intval = int(value, 16) except ValueError: pass else: if intval >= (1 << 32): raise errors.BadArgument("Invalid color {} is too big!".format(value)) if len(value) > 6: color = discord.Colour(intval) return (color.r, color.g, color.b, color._get_byte(3)) return discord.Colour(intval).to_rgb() try: return getattr(discord.Colour, value)().to_rgb() except AttributeError: raise errors.BadArgument("Invalid color {}".format(value))
async def fetch_image(url): """Fetch the given image.""" url = str(url) # Workaround https://github.com/aio-libs/aiohttp/issues/3426 async with aiohttp.ClientSession( connector=aiohttp.TCPConnector(enable_cleanup_closed=True)) as sess: # proxy_url must be passed exactly - encoded=True # https://github.com/aio-libs/aiohttp/issues/3424#issuecomment-443760653 async with sess.get(yarl.URL(url, encoded=True)) as resp: resp.raise_for_status() content_length = int(resp.headers.get('Content-Length', 50<<20)) if content_length > 50<<20: raise errors.BadArgument("File too big") blocks = [] readlen = 0 tested_image = False # Read up to X bytes, raise otherwise while True: block = await resp.content.readany() if not block: break blocks.append(block) readlen += len(block) if readlen >= 10<<10 and not tested_image: try: Image.open(io.BytesIO(b''.join(blocks))) except OSError: raise errors.BadArgument("This doesn't look like an image to me") else: tested_image = True if readlen > content_length: raise errors.BadArgument("File too big") source_bytes = b''.join(blocks) return source_bytes
async def countdown(self, ctx, seconds: int = 3): """3... 2... 1... Go!""" if seconds > 10: raise errors.BadArgument("No more than 10 seconds, thanks") if seconds < 0: raise errors.BadArgument("No negative numbers, thanks") while seconds: await ctx.send("%d..." % seconds) await asyncio.sleep(1) seconds -= 1 await ctx.send("Go!")
async def msg(self, ctx, msg: converters.MessageConverter): """Delete a specific message created by the bot. Use developer mode to be able to copy a message id in context menu.""" if not msg: raise errors.BadArgument("Could not find a message by that id!") if msg.author != ctx.bot.user: raise errors.BadArgument("I didn't make that message!") await msg.delete() await ctx.send("Message {} deleted.".format(msg.id), delete_after=5)
async def osu(self, ctx, *, account: StringOrMentionConverter = None): """Show a user's osu profile. Use + to give a raw account name. e.g.: osu +cookiezi osu @ppy """ account = account or ctx.message.author mode = { 'osu': OsuMode.osu, 'taiko': OsuMode.taiko, 'mania': OsuMode.mania, 'ctb': OsuMode.ctb }[ctx.invoked_with] with ctx.typing(): if account is None: raise errors.BadArgument("Invalid mention...!") if isinstance(account, discord.abc.User): osu_acct = await self._get_osu_account(ctx, account, mode) else: osu_acct = await self._lookup_acct(account, mode=mode) usrscore = await self.osuapi.get_user_best(osu_acct.user_id, limit=100, mode=mode) embed = discord.Embed() embed.title = osu_acct.username embed.url = "https://osu.ppy.sh/u/%s" % osu_acct.user_id embed.color = hash(str(osu_acct.user_id)) % (1 << 24) if isinstance(account, discord.abc.User): embed.set_author( name=str(account), icon_url=account.avatar_url_as(static_format="png")) embed.set_thumbnail(url="http://a.ppy.sh/%s?_=%s" % (osu_acct.user_id, time.time())) if not usrscore: embed.description = "%s has never played %s" % (osu_acct.username, ctx.invoked_with) else: embed.description = "#{0.pp_rank:,} ({0.pp_raw} pp)".format( osu_acct) fave_mod = collections.Counter( play.enabled_mods for play in usrscore).most_common()[0][0] bplay = usrscore[0] embed.add_field(name="Plays", value="{:,}".format(osu_acct.playcount)) embed.add_field(name="Hits", value="{:,}".format(osu_acct.total_hits)) embed.add_field(name="Acc", value="{:.2f}".format(osu_acct.accuracy)) embed.add_field(name="Best Play", value="{:,}pp {:s}".format(bplay.pp, bplay.enabled_mods)) embed.add_field(name="Favorite Mod", value="{:l}".format(fave_mod)) await ctx.send(embed=embed)
async def test_handle_input_error_handler_errors(self): """Should handle each error probably.""" test_cases = ({ "error": errors.MissingRequiredArgument(MagicMock()), "call_prepared": True }, { "error": errors.TooManyArguments(), "call_prepared": True }, { "error": errors.BadArgument(), "call_prepared": True }, { "error": errors.BadUnionArgument(MagicMock(), MagicMock(), MagicMock()), "call_prepared": True }, { "error": errors.ArgumentParsingError(), "call_prepared": False }, { "error": errors.UserInputError(), "call_prepared": True }) for case in test_cases: with self.subTest(error=case["error"], call_prepared=case["call_prepared"]): self.ctx.reset_mock() self.assertIsNone(await self.cog.handle_user_input_error( self.ctx, case["error"])) self.ctx.send.assert_awaited_once() if case["call_prepared"]: self.ctx.send_help.assert_awaited_once() else: self.ctx.send_help.assert_not_awaited()
async def test_try_get_tag_convert_fail(self, tag_converter): """Converting tag should raise `BadArgument`.""" self.ctx.reset_mock() self.ctx.invoked_with = "bar" tag_converter.convert = AsyncMock(side_effect=errors.BadArgument()) self.assertIsNone(await self.cog.try_get_tag(self.ctx)) self.ctx.invoke.assert_not_awaited()
async def blame(self, ctx, message_id: int): """Show who caused a command response to show up. message_id must be a message said by the bot. Note you must enable developer mode in order to get message ids. See Discord's documentation here: https://waa.ai/j06h """ async with self.database.acquire() as conn: row = await conn.fetchrow( "SELECT blame.id, blame.message_id, blame.author_id, " "blame.channel_id, blame.server_id " "FROM blame where blame.id = $1", message_id) if not row: raise errors.BadArgument("No info for that message.") _, message_id, author_id, channel_id, server_id = row.values() usr = ctx.bot.get_user(author_id) srv = ctx.bot.get_guild(server_id) if usr: author_id = "%s (%s)" % (str(usr), author_id) if srv: server_id = "%s (%s)" % (srv, srv.id) await ctx.send("Server: %s\nChannel: <#%s>\nUser: %s\nMessage: %s" % (server_id, channel_id, author_id, message_id))
async def setbattletag(self, ctx, tag: battle_tag): """Sets the Blizzard BattleTag associated with this Discord account. Tags are of the format <platform>/<region>/username#number, or username#number. For example: "pc/us/noob#0001". If platform and region are not specified, I will search. If the incorrect one is chosen, please specify the platform and region. """ async with aiohttp.ClientSession() as client: if tag.is_complete: async with client.head(tag.profile_url) as resp: if resp.status == 404: raise errors.BadArgument( "That battletag does not match a user") elif resp.status != 200: raise errors.CommandError( "There was an unknown error setting your battletag" ) else: await self.attr.set_attributes( ctx.message.author, blizzard_battletag=tag.tag) await ctx.send( "Updated your battletag to {}".format(tag)) else: tag = await find_tag(client, tag) await self.attr.set_attributes(ctx.message.author, blizzard_battletag=tag.tag) await ctx.send("Updated your battletag to {}".format(tag))
async def overwatch(self, ctx, tag: user_or_name=None): """Get Overwatch stats for a given user. May supply a Discord mention, or a BattleTag. Tags are of the format <platform>/<region>/username#number, or username#number. For example: "pc/us/noob#0001". If platform and region are not specified, I will search. If the incorrect one is chosen, please specify the platform and region. """ if ctx.invoked_with == "owc": return await self.competitive.invoke(ctx) if not tag: tag = ctx.message.author prof = await self._get_overwatch_profile(ctx, tag) if not prof.quick_play: raise errors.BadArgument( "Player has no quick play stats available.") content = """ {0.tag.tag_private} Level {0.level} - {0.quick_play.all_heroes.game.games_won} Wins {1.game.time_played} played. {1.combat.eliminations} eliminations, {1.combat.deaths} deaths. Average: {1.average.damage_done} damage, {1.average.eliminations} elims, {1.average.final_blows} final blows, {1.average.deaths} deaths, {1.average.healing_done} healing """.format(prof, prof.quick_play.all_heroes) await ctx.send("```prolog{}```".format(content))
async def _get_osu_account(self, ctx, user, mode): osu_user_id = await self.attr.get_attribute(user, 'osu_id') if osu_user_id: return await self._lookup_acct(osu_user_id, mode=mode) if ctx.author.id != user.id: raise errors.BadArgument( "I don't know {}'s osu username!".format(user)) presence_username = self._osu_presence_username_cache.get(user.id) clean_prefix = utils.clean_double_backtick(ctx.prefix) if presence_username: await ctx.send( "I don't know your osu username! I'm setting your osu username " "to {}, which rich presence showed you recently playing as. " "If this is wrong use ``{}setosu <username>``".format( presence_username, clean_prefix)) return await self._set_osu_username(user, presence_username) await ctx.send( "I don't know your osu username! I'm setting your osu username " "to {}, if this is wrong use ``{}setosu <username>``".format( user.name, clean_prefix)) return await self._set_osu_username(user, user.name)
def get_cog_or_cmd_callback(ctx, *cmd_name): if len(cmd_name) == 1: cog = ctx.bot.get_cog(cmd_name[0]) if cog: return cog.__class__ try: cmd = resolve_command(ctx.bot, *cmd_name) except NoSubCommands as e: raise errors.BadArgument("`{}` has no subcommands".format( e.cmd.qualified_name)) except NoSuchSubCommand as e: raise errors.BadArgument("`{}` has no subcommand {}".format( e.cmd.qualified_name, e.sub)) except NoSuchCommand as e: raise errors.BadArgument("No such command or cog `{}`".format( e.cmd_name)) return cmd.callback
def return_time_info( self, pattern: Optional[str]) -> Tuple[datetime, timedelta, float, dict]: time_match = re.match(r'\d{1,4}[dhmsDHMS]', pattern) if time_match is None: raise errors.BadArgument('Введено некорректное время!') *num_str, time_letter = time_match.group(0) num = int(''.join(num_str)) __td, __time_units = None, { 'days': False, 'hours': False, 'minutes': False, 'seconds': False } if time_letter in ['d', 'D']: __td = timedelta(days=num) __time_units['days'], __time_units['hours'], __time_units[ 'minutes'] = True, True, True elif time_letter in ['h', 'H']: __td = timedelta(hours=num) __time_units['hours'], __time_units['minutes'], __time_units[ 'seconds'] = True, True, True if len(num_str) > 3: __time_units['days'], __time_units['seconds'] = True, False elif time_letter in ['m', 'M']: __td = timedelta(minutes=num) __time_units['minutes'], __time_units['seconds'] = True, True if len(num_str) >= 3: __time_units['hours'], __time_units['seconds'] = True, False elif time_letter in ['s', 'S']: if num < 30: raise errors.BadArgument( 'Укажите значение больше, чем 30! **(Для секунд)**') if num < 3600: __time_units['minutes'] = True elif num < 86400: __time_units['hours'] = True elif num > 86400: __time_units['days'] = True __td = timedelta(seconds=num) __time_units['seconds'] = True __dt = datetime.now(self._gmt) + __td __seconds = __td.total_seconds() return __dt, __td, __seconds, __time_units
async def convert(self, ctx, argument): match = await self.find_match(ctx, argument) if not match: raise errors.BadArgument( """Guild "{}" not found, or you aren't a member""".format( argument)) return match
def return_number_of_winners(pattern: Optional[str]) -> int: winners_match = re.match(r'\d+[wW]', pattern) if winners_match is None: raise errors.BadArgument( 'Введено некорректное кол-во победителей!') *winners, _ = winners_match.group(0) __champions = int(''.join(winners)) return __champions
async def convert(self, ctx: "IceTeaContext", argument): guild = discord.utils.get(ctx.bot.guilds, name=argument) if guild is None: guild = discord.utils.get(ctx.bot.guilds, id=int(argument)) if guild is None: raise errors.BadArgument(message="I am not in any guild with this ID") else: return guild
async def _lookup_acct(self, username, mode=OsuMode.osu): res = await self.osuapi.get_user(username, mode=mode) if len(res) == 0: raise errors.BadArgument( "There is no osu user by the name {}".format(username)) return res[0]
async def convert(self, ctx, argument): channel, msg_id = await MessageId().convert(ctx, argument) msg = discord.utils.get(ctx.bot.cached_messages, id=msg_id) if msg is None: try: msg = await channel.fetch_message(msg_id) except discord.NotFound: raise errors.BadArgument( f'Message {msg_id} not found in channel {channel.mention}.' ) except discord.Forbidden: raise errors.CheckFailure( f"I don't have permission to view channel {channel.mention}." ) elif msg.channel.id != channel.id: raise errors.BadArgument('Message not found.') return msg
def osu_map_url(value): match = re.match( r'https://osu.ppy.sh/(?:s/(?P<beatmapset>[0-9]+)|b/(?P<beatmap>0-9]+))/?.*', value) if match.group("beatmapset"): return dict(beatmapset=match.group("beatmapset")) elif match.group("beatmap"): return dict(beatmap=match.group("beatmap")) raise errors.BadArgument("Not recognized as a beatmap url!")
async def get_user(self, ctx, session, user_id): user = session.query( database.User).filter(database.User.discord_id == user_id).first() if user is None: await ctx.bot.whisper( f'Hello {ctx.message.author.name}, your profile doesn\'t exist yet. Please run `!user setup` to get started!' ) raise errors.BadArgument('No profile') return user
async def convert(self, ctx, argument): channel, msg_id = await MessageIdConverter().convert(ctx, argument) msg = discord.utils.get(ctx.bot.cached_messages, id=msg_id) if msg is None: try: msg = await channel.fetch_message(msg_id) except discord.NotFound: raise errors.BadArgument( "Message {0} not found in channel {1.mention}".format( msg_id, channel)) except discord.Forbidden: raise errors.CheckFailure( "I don't have permission to view channel {0.mention}". format(channel)) elif msg.channel.id != channel.id: raise errors.BadArgument("Message not found") return msg
async def bc(self, ctx, *, args): args = args.strip(' `') for banned_word in ['open', 'read', 'write', 'getline']: if banned_word in args: raise errors.CheckFailure(f'Banned word found') try: await ctx.bot.say(str(cexprtk.evaluate_expression(args, {}))) except: raise errors.BadArgument(f'Invalid expression')
async def convert(self, ctx, argument): if argument.startswith("http://") or argument.startswith("https://"): return argument member = await UserMemberConverter().convert(ctx, argument) if member: return member.avatar_url_as(format="png") raise errors.BadArgument( "{argument} isn't a member or url.".format(argument=argument))
def silent_convert_member(ctx, name, optional=False): if name and name.lower() == 'motsy': name = 'Motoko' if name and name.lower() == '@me': return ctx.message.author if name: return AndreMemberConverter(ctx, name).convert() elif optional: return None else: raise errors.BadArgument('No member specified')
async def poll(self, ctx, *options: clean_content): """Run a poll with up to 11 options. Poll ends 30 seconds after the last response. """ if len(options) > 11: raise errors.BadArgument("No more than 11 options") e = discord.Embed(title="Vote now!", description="\n".join( "%s %s" % (number_emoji(idx), text) for idx, text in enumerate(options))) e.set_footer(text="Voting ends 30 seconds after the last response!") msg = await ctx.send(embed=e) for i in range(len(options)): await msg.add_reaction(number_emoji(i)) while True: try: resp = await ctx.bot.wait_for( "raw_reaction_add", timeout=30, check=lambda r: r.message_id == msg.id and is_number_emoji( r.emoji.name)) except asyncio.TimeoutError: break msg = await ctx.get_message(msg.id) max_val = 0 results = [] for reaction in msg.reactions: if isinstance(reaction.emoji, str) and is_number_emoji( reaction.emoji): idx = emoji_number(reaction.emoji) if idx >= len(options): continue if reaction.count > max_val: max_val = reaction.count results = [idx] elif reaction.count == max_val: results.append(idx) if max_val == 1: await ctx.send("No one voted...") elif len(results) > 1: final = "all" if len(results) > 2 else "both" await ctx.send("Tie! %s %s won!" % (joinand([options[idx] for idx in results]), final)) else: await ctx.send("The best choice is clearly %s" % options[results[0]])
async def convert(self, ctx, argument): if ctx.command.parent is not None: g_command = await ctx.bot.factory.Command.get( argument, connection=ctx.bot.rethinkdb, bot=ctx.bot) if g_command is not None: return g_command else: raise myerrors.NotRootCommand(argument) else: raise errors.BadArgument( message= f"{ctx.command.invoked_with} cannot be blocked, only parent commands can be blocked." )
async def find(self, ctx, *, username): """Find users with simple matching.""" username = username.lower() if len(username) < 2: raise errors.BadArgument("Username must be at least 2 characters.") matches = [ member for member in ctx.message.guild.members if username in member.name.lower() ] await _send_find_results(ctx, matches)