async def convert(self, ctx: commands.Context, arg: str) -> None: link_pattern = "^https://discord.com/channels/[0-9]+/[0-9]+/[0-9]+" special_id_pattern = "^[0-9]+-[0-9]*[0-9]$" normal_id_pattern = "^[0-9][0-9]+[0-9]$" channel_id: int = None message_id: int = None message: discord.Message = None if arg == "^": try: message = (await ctx.channel.history(limit=2).flatten())[1] except discord.Forbidden: raise discord.Forbidden( "I can't read the messae history of this channel, " "so I don't know what message you want me to force.") elif re.match(normal_id_pattern, arg): channel_id = ctx.channel.id message_id = int(arg) elif re.match(link_pattern, arg): split = arg.split("/") channel_id = int(split[-2]) message_id = int(split[-1]) elif re.match(special_id_pattern, arg): split = arg.split("-") channel_id = int(split[0]) message_id = int(split[1]) else: raise discord.InvalidArgument( f"The argument, `{arg}`, does not appear to be " "a message link.") if not message: channel = ctx.guild.get_channel(channel_id) if not channel: raise discord.InvalidArgument( "I couldn't find a channel with the id " f"`{channel_id}`. Please make sure the message " "link is valid, and is in this server.") try: message = await channel.fetch_message(message_id) except discord.NotFound: raise discord.InvalidArgument( "I couldn't find a message that matches " f"the link `{arg}`. Please make sure the " "message link is valid.") except discord.Forbidden: raise discord.Forbidden( "I don't have permission to read message history " f"in {channel.mention}, so I can't fetch the message " f"`{arg}`") return message
async def test_handle_raised_forbidden(): # Given clientside_error_message = "The bot has insufficient permissions to do this." message_provider = MagicMock(MessageProvider) message_provider.bot_missing_access.return_value = clientside_error_message route_handler = _route_handler(message_provider=message_provider) event = DiscordEvent(command=DiscordCommand(command_name="ping", )) mock_route = AsyncMock(Route) mock_route.call.side_effect = discord.Forbidden( MagicMock(aiohttp.ClientResponse), "Missing Access.") route_definition = RouteDefinition( route=mock_route, command="ping", ) # When response = await route_handler.handle(event, route_definition) # Then assert response.is_ephemeral assert response.content == clientside_error_message
async def get_meme_image(self, image_type: str, url: str = None, text: str = None, timeout: int = 300, **args): """ Basic get_image function using aiohttp Returns a Discord File """ if text: text = quote(text) url = '{}/{}{}{}'.format( memer, image_type.lower(), '?avatar1={}'.format(url) if url else '', '{}text={}'.format('&' if url else '?', text) if text else '') for p, v in args.items(): url += '&{}={}'.format(p, v) async with self.session.get(url, headers=self.dankmemer_header, timeout=timeout) as response: if response.status != 200: raise discord.Forbidden( 'You are not allowed to access this resource.') ext = response.content_type.split('/')[-1] img = BytesIO(await response.read()) await response.release() return discord.File(img, filename="image.{}".format(ext))
async def cleanup(self, ctx, num_messages: int, *users): try: to_clean = [(await commands.MemberConverter().convert(ctx, user)) for user in users] except commands.BadArgument: try: to_clean = [(await commands.RoleConverter().convert(ctx, user)) for user in users] except commands.BadArgument: print(users) if users[0] in ['bots', 'humans']: to_clean = [] members = ctx.channel.members if users[0] == 'bots': for m in members: if m.bot: to_clean.append(m) if users[0] == 'humans': for m in members: if not m.bot: to_clean.append(m) else: raise commands.BadArgument("That was not a correct mention(s), role(s) or `bots` or `humans`") if num_messages > 100: num_messages = 100 def check(m): return m.author in to_clean try: deleted = await ctx.channel.purge(limit=num_messages, check=check) except discord.Forbidden: raise discord.Forbidden("I don't have the required `manage_messages` permission to run this command!") send = await ctx.send(f"Deleted {len(deleted)} messages from `{[n.name for n in to_clean]}`.") await asyncio.sleep(5) await send.delete()
async def get_webhook(self, ctx: commands.Context, channel: discord.TextChannel): link = self.cache.get(channel.id) if link: return link if channel.permissions_for(ctx.me).manage_webhooks: webhook_list = [ w for w in (await channel.webhooks()) if w.type == discord.WebhookType.incoming ] if webhook_list: webhook = webhook_list[0] else: creation_reason = f"Webhook creation requested by {ctx.author} ({ctx.author.id}) for the `{ctx.command.qualified_name}` command." webhook = await channel.create_webhook( name=f"{ctx.me.name} Webhook", reason=creation_reason, avatar=await ctx.me.avatar_url.read(), ) self.cache[channel.id] = webhook.url return webhook.url else: raise discord.Forbidden( "Missing Permissions", f"I need permissions to `manage_webhooks` in #{channel.name}.", )
async def purge(self, ctx, num: int): try: await ctx.message.delete() await ctx.channel.purge(limit=num) resp = await ctx.send(f"Succesfully deleted {num} messages.") await asyncio.sleep(5) await resp.delete() except discord.Forbidden(): await ctx.send( "I don't have the proper permissions to delete messages.")
async def purge(self, ctx, num_messages: int=None): if num_messages is None: num_messages = 100 if num_messages > 100: num_messages = 100 try: deleted = await ctx.channel.purge(limit=num_messages) except discord.Forbidden: raise discord.Forbidden("I don't have `manage_messages` permission to run this command!") msg = await ctx.send(f"Purged {len(deleted)} messages from this channel!") await asyncio.sleep(5) await msg.delete()
async def get_image_ksoft(self, tag: str, nsfw: bool = True): params = f'?tag={tag}&nsfw={nsfw}' url = ksoft_api + params async with self.session.get(url, headers=self.ksoft_headers, timeout=20) as response: if response.status != 200: raise discord.Forbidden( 'You are not allowed to access this resource.') ext = response.content_type.split('/')[-1] img = await response.read() await response.release() img = json.loads(img) return img['url']
async def get_webhook( self, *, channel: discord.TextChannel = None, me: discord.Member = None, author: discord.Member = None, reason: str = None, ctx: commands.Context = None, ): if ctx: channel = channel or ctx.channel me = me or ctx.me author = author or ctx.author reason = (reason or f"For the {ctx.command.qualified_name} command", ) link = self.cache.get(channel.id) if link: return link if channel.permissions_for(me).manage_webhooks: webhook_list = [ w for w in (await channel.webhooks()) if w.type == discord.WebhookType.incoming ] if webhook_list: webhook = webhook_list[0] else: creation_reason = f"Webhook creation requested by {author} ({author.id})" if reason: creation_reason += f" Reason: {reason}" webhook = await channel.create_webhook( name=f"{me.name} Webhook", reason=creation_reason, avatar=await me.avatar_url.read(), ) self.cache[channel.id] = webhook.url return webhook.url else: raise discord.Forbidden( "Missing Permissions", f"I need permissions to `manage_webhooks` in #{channel.name}.", )
async def get_from_cdn(url) -> bytes: """ A method that downloads an image from a url. Parameters: url (str): The url of the image. Returns: (bytes): A bytes object of the downloaded image. """ async with ClientSession() as session: async with session.get(url) as resp: if resp.status == 200: return await resp.read() elif resp.status == 404: raise discord.NotFound(resp, 'Not Found') elif resp.status == 403: raise discord.Forbidden(resp, 'Forbidden') else: raise discord.HTTPException(resp, 'Failed')
async def ban(self, ctx, user, *, reason=None): mod_log = await self.bot.db.config.find_one({"_id": ctx.guild.id}) if user.startswith("<@") and user.endswith(">"): user = ctx.guild.get_member(int(user.lstrip("<@").strip(">"))) else: user = ctx.guild.get_member(int(user)) try: await user.ban(reason=reason) except discord.Forbidden(): await ctx.send( "I don't have the proper permissions to ban this member") await ctx.send(f"**{user} was banned**") if mod_log["mod_log"]: channel = self.bot.get_channel(mod_log["mod_log"]) embed = discord.Embed( title="Member banned!", color=discord.Color.red(), description=f"{user} was banned by {ctx.author}") embed.add_field(name="Reason", value=reason) embed.set_image(url=user.avatar_url) await channel.send(embed=embed)
async def _process_message(self, rift, message, dest): send = message.channel == rift.source destination = rift.destination if send else rift.source me = self.bot.user if destination.is_private else destination.server.me author = message.author sauthor = self.bot.user if destination.is_private else destination.server.get_member(author.id) perms = destination.permissions_for(sauthor) isowner = author.id == self.bot.settings.owner if send and (sauthor is None or not isowner and not perms.send_messages): raise discord.Forbidden(403, "Forbidden") content = message.content embed = None if not isowner or not send: content = "{}: {}".format(author, content) if not isowner and not destination.permissions_for( sauthor).mention_everyone: content = escape(content, mass_mentions=True) if message.attachments and (isowner or destination.permissions_for( sauthor).attach_files): if destination.permissions_for(me).embed_links: attach = message.attachments[0] embed = discord.Embed(description="{}\n**[{}]({})**".format( xbytes(attach["size"]), attach["filename"], attach["url"]), colour=randint(0, 0xFFFFFF)) embed.set_image(url=message.attachments[0]["url"]) if len(message.attachments) > 1: rest = message.attachments[1:] embed.set_footer(" | ".join("**[{}]({})** ({})".format( a["filename"], a["url"], xbytes(a["size"])) for a in rest)) else: content += "\n\n" + "\n".join("{} ({})".format(a["url"], xbytes(a["size"])) for a in message.attachments) if isinstance(dest, discord.Message): return await self.bot.edit_message(dest, content, embed=embed) else: return await self.bot.send_message(dest, content, embed=embed)
class Webhook(commands.Cog): """Webhook utility commands.""" __author__ = "PhenoM4n4n" def __init__(self, bot): self.bot = bot self.cache = {} self.session = aiohttp.ClientSession() def cog_unload(self): self.bot.loop.create_task(self.session.close()) async def red_delete_data_for_user(self, **kwargs): return @staticmethod async def delete_quietly(ctx: commands.Context): if ctx.channel.permissions_for(ctx.me).manage_messages: try: await ctx.message.delete() except discord.HTTPException: pass @commands.guild_only() @commands.group() async def webhook(self, ctx): """Webhook related commands.""" @commands.bot_has_permissions(manage_webhooks=True) @commands.admin_or_permissions(manage_webhooks=True) @webhook.command() async def create( self, ctx: commands.Context, channel: discord.TextChannel = None, *, webhook_name: str = None, ): """Creates a webhook in the channel specified with the name specified. If no channel is specified then it will default to the current channel.""" channel = channel or ctx.channel webhook_name = webhook_name or f"{ctx.bot.user.name} Webhook" creation_reason = f"Webhook creation requested by {ctx.author} ({ctx.author.id})" await channel.create_webhook(name=webhook_name, reason=creation_reason) await ctx.tick() @commands.admin_or_permissions(manage_webhooks=True) @webhook.command() async def send(self, ctx: commands.Context, webhook_link: str, *, message: str): """Sends a message to the specified webhook using your avatar and display name.""" await self.delete_quietly(ctx) try: await self.webhook_link_send(webhook_link, ctx.author.display_name, ctx.author.avatar_url, content=message) except InvalidWebhook: await ctx.send("You need to provide a valid webhook link.") @commands.bot_has_permissions(manage_webhooks=True) @webhook.command() async def say(self, ctx: commands.Context, *, message: str): """Sends a message to the channel as a webhook with your avatar and display name.""" await self.delete_quietly(ctx) await self.send_to_channel( ctx.channel, ctx.me, ctx.author, ctx=ctx, content=message, avatar_url=ctx.author.avatar_url, username=ctx.author.display_name, ) @commands.admin_or_permissions(manage_webhooks=True) @commands.bot_has_permissions(manage_webhooks=True) @webhook.command() async def sudo(self, ctx: commands.Context, member: discord.Member, *, message: str): """Sends a message to the channel as a webhook with the specified member's avatar and display name.""" await self.delete_quietly(ctx) await self.send_to_channel( ctx.channel, ctx.me, ctx.author, ctx=ctx, content=message, avatar_url=member.avatar_url, username=member.display_name, ) @commands.admin_or_permissions(manage_webhooks=True, manage_guild=True) @commands.bot_has_permissions(manage_webhooks=True) @webhook.command(hidden=True) async def loudsudo(self, ctx: commands.Context, member: discord.Member, *, message: str): """Sends a message to the channel as a webhook with the specified member's avatar and display name.""" await self.send_to_channel( ctx.channel, ctx.me, ctx.author, ctx=ctx, content=message, avatar_url=member.avatar_url, username=member.display_name, allowed_mentions=discord.AllowedMentions(everyone=False, roles=False, users=True), ) @commands.admin_or_permissions(manage_webhooks=True, manage_guild=True) @commands.bot_has_permissions(manage_webhooks=True) @webhook.command(hidden=True) async def clyde(self, ctx: commands.Context, *, message: str): """Sends a message to the channel as a webhook with Clyde's avatar and name.""" await self.delete_quietly(ctx) await self.send_to_channel( ctx.channel, ctx.me, ctx.author, ctx=ctx, content=message, avatar_url= "https://discordapp.com/assets/f78426a064bc9dd24847519259bc42af.png", username="******", allowed_mentions=discord.AllowedMentions(everyone=False, roles=False, users=True), ) @commands.max_concurrency(1, commands.BucketType.guild) @commands.has_permissions(manage_webhooks=True) @commands.bot_has_permissions(manage_webhooks=True) @webhook.command() async def clear(self, ctx): """Delete all webhooks in the server.""" webhooks = await ctx.guild.webhooks() if not webhooks: await ctx.send("There are no webhooks in this server.") return msg = await ctx.send( "This will delete all webhooks in the server. Are you sure you want to do this?" ) start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(msg, ctx.author) try: await ctx.bot.wait_for("reaction_add", check=pred, timeout=60) except asyncio.TimeoutError: await ctx.send("Action Cancelled.") return if pred.result is False: return await ctx.send("Action Cancelled.") msg = await ctx.send("Deleting webhooks..") count = 0 async with ctx.typing(): for webhook in webhooks: try: await webhook.delete( reason= f"Guild Webhook Deletion requested by {ctx.author} ({ctx.author.id})" ) except discord.InvalidArgument: pass else: count += 1 try: await msg.edit(content=f"{count} webhooks deleted.") except discord.NotFound: await ctx.send(f"{count} webhooks deleted.") @commands.mod_or_permissions(ban_members=True) @webhook.command() async def perms(self, ctx): """Show all members in the server that have `manage_webhook` permissions.""" await ctx.trigger_typing() members = [] strings = [] roles = [] for role in ctx.guild.roles: if role.permissions.is_superset(discord.Permissions( 536870912)) or role.permissions.is_superset( discord.Permissions(8)): roles.append(role) for member in role.members: if member not in members: members.append(member) string = ( f"[{member.mention} - {member}](https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstleyVEVO 'This user is a bot')" if member.bot else f"{member.mention} - {member}") strings.append(string) if not members: await ctx.send( "No one here has `manage_webhook` permissions other than the owner." ) strings = "\n".join(strings) if len(strings) > 2000: embeds = [] for page in pagify(strings): embed = discord.Embed( color=await ctx.embed_color(), title="Users with `manage_webhook` Permissions", description=page, ) if roles: embed.add_field( name="Roles:", value=humanize_list([role.mention for role in roles]), inline=False, ) embeds.append(embed) await menu(ctx, embeds, DEFAULT_CONTROLS) else: embed = discord.Embed( color=await ctx.embed_color(), title="Users with `manage_webhook` Permissions", description=strings, ) if roles: embed.add_field( name="Roles:", value=humanize_list([role.mention for role in roles]), inline=False, ) emoji = self.bot.get_emoji(736038541364297738) or "❌" await menu(ctx, [embed], {emoji: close_menu}) @commands.max_concurrency(1, commands.BucketType.channel) @commands.admin_or_permissions(manage_webhooks=True) @webhook.command() async def session(self, ctx: commands.Context, webhook_link: str): """Initiate a session within this channel sending messages to a specified webhook link.""" if ctx.channel.permissions_for(ctx.me).manage_messages: try: await ctx.message.delete() except discord.NotFound: pass e = discord.Embed( color=0x49FC95, title="Webhook Session Initiated", description=f"Session Created by `{ctx.author}`.", ) initial_result = await self.webhook_link_send( webhook_link, "Webhook Session", "https://imgur.com/BMeddyn.png", embed=e) if initial_result is not True: return await ctx.send(initial_result) await ctx.send( "I will send all messages in this channel to the webhook until " "the session is closed by saying 'close' or there are 2 minutes of inactivity.", embed=e, ) while True: try: result = await self.bot.wait_for( "message_without_command", check=lambda x: x.channel == ctx.channel and not x.author. bot and x.content, timeout=120, ) except asyncio.TimeoutError: return await ctx.send("Session closed.") if result.content.lower() == "close": return await ctx.send("Session closed.") send_result = await self.webhook_link_send( webhook_link, result.author.display_name, result.author.avatar_url, content=result.content, ) if send_result is not True: return await ctx.send( "The webhook was deleted so this session has been closed.") @commands.cooldown(5, 10, commands.BucketType.guild) @commands.admin_or_permissions(manage_webhooks=True) @webhook.command(name="edit") async def webhook_edit(self, ctx: commands.Context, message: discord.Message, *, content: str): """Edit a message sent by a webhook.""" if not message.webhook_id: raise commands.BadArgument if not message.channel.permissions_for(ctx.me).manage_webhooks: return await ctx.send( f"I need `Manage Webhook` permission in {message.channel}.") webhooks = await message.channel.webhooks() webhook = None for chan_webhook in webhooks: if (chan_webhook.type == discord.WebhookType.incoming and chan_webhook.id == message.webhook_id): webhook = chan_webhook break if not webhook: raise commands.BadArgument await webhook.edit_message(message.id, content=content) await self.delete_quietly(ctx) async def webhook_link_send( self, link: str, username: str, avatar_url: str, *, allowed_mentions: discord.AllowedMentions = discord.AllowedMentions( users=False, everyone=False, roles=False), **kwargs, ): try: async with aiohttp.ClientSession() as session: webhook = discord.Webhook.from_url( link, adapter=discord.AsyncWebhookAdapter(session)) await webhook.send( username=username, avatar_url=avatar_url, allowed_mentions=allowed_mentions, **kwargs, ) return True except (discord.InvalidArgument, discord.NotFound): raise InvalidWebhook("You need to provide a valid webhook link.") async def get_webhook( self, *, channel: discord.TextChannel = None, me: discord.Member = None, author: discord.Member = None, reason: str = None, ctx: commands.Context = None, ) -> discord.Webhook: if ctx: channel = channel or ctx.channel me = me or ctx.me author = author or ctx.author reason = (reason or f"For the {ctx.command.qualified_name} command", ) if webhook := self.cache.get(channel.id): return webhook if me and not channel.permissions_for(me).manage_webhooks: raise discord.Forbidden( FakeResponse(), f"I need permissions to `manage_webhooks` in #{channel.name}.", ) chan_hooks = await channel.webhooks() webhook_list = [ w for w in chan_hooks if w.type == discord.WebhookType.incoming ] if webhook_list: webhook = webhook_list[0] else: creation_reason = f"Webhook creation requested by {author} ({author.id})" if reason: creation_reason += f" Reason: {reason}" if len(chan_hooks) == 10: await chan_hooks[-1].delete() webhook = await channel.create_webhook( name=f"{me.name} Webhook", reason=creation_reason, avatar=await me.avatar_url.read(), ) self.cache[channel.id] = webhook return webhook
me: discord.Member = None, author: discord.Member = None, reason: str = None, ctx: commands.Context = None, ) -> discord.Webhook: if ctx: channel = channel or ctx.channel me = me or ctx.me author = author or ctx.author reason = (reason or f"For the {ctx.command.qualified_name} command",) if webhook := self.channel_cache.get(channel.id): return webhook if me and not channel.permissions_for(me).manage_webhooks: raise discord.Forbidden( FakeResponse(), f"I need permissions to `manage_webhooks` in #{channel}.", ) chan_hooks = await channel.webhooks() webhook_list = [w for w in chan_hooks if await self.webhook_check(w)] if webhook_list: webhook = webhook_list[0] else: if len(chan_hooks) == 10: # delete_hook = chan_hooks[-1] # await delete_hook.delete() return # can't delete follower type webhooks creation_reason = ( f"Webhook creation requested by {author} ({author.id})" if author else "" ) if reason: creation_reason += f" Reason: {reason}"
def test_forbidden(self): message = AsyncMock() message.delete.side_effect = discord.Forbidden(Mock(), "") self.assertFalse(self._await(delete_message(message))) message.delete.assert_awaited_once()