From 7cd98c8a63b374bc8ce7489e3a9ec399fbffdde1 Mon Sep 17 00:00:00 2001 From: Michael H Date: Fri, 4 May 2018 00:38:58 -0400 Subject: [PATCH] Report fixes + improvements (#1541) * WIP * fix perms issue * better * more work * working * working, tessted * docs * mutable default fix --- redbot/cogs/reports/reports.py | 131 ++++++++++++++++++++------------- redbot/core/utils/tunnel.py | 122 +++++++++++++++++++++++------- 2 files changed, 177 insertions(+), 76 deletions(-) diff --git a/redbot/cogs/reports/reports.py b/redbot/cogs/reports/reports.py index 539e058c6..337636c13 100644 --- a/redbot/cogs/reports/reports.py +++ b/redbot/cogs/reports/reports.py @@ -2,7 +2,8 @@ import logging import asyncio from typing import Union from datetime import timedelta - +from copy import copy +import contextlib import discord from discord.ext import commands @@ -109,10 +110,11 @@ class Reports: ret |= await self.bot.is_owner(m) return ret - async def discover_guild(self, author: discord.User, *, - mod: bool=False, - permissions: Union[discord.Permissions, dict]={}, - prompt: str=""): + async def discover_guild( + self, author: discord.User, *, + mod: bool=False, + permissions: Union[discord.Permissions, dict]=None, + prompt: str=""): """ discovers which of shared guilds between the bot and provided user based on conditions (mod or permissions is an or) @@ -120,10 +122,12 @@ class Reports: prompt is for providing a user prompt for selection """ shared_guilds = [] - if isinstance(permissions, discord.Permissions): + if permissions is None: + perms = discord.Permissions() + elif isinstance(permissions, discord.Permissions): perms = permissions else: - permissions = discord.Permissions(**perms) + perms = discord.Permissions(**permissions) for guild in self.bot.guilds: x = guild.get_member(author.id) @@ -170,26 +174,40 @@ class Reports: author = guild.get_member(msg.author.id) report = msg.clean_content - avatar = author.avatar_url - - em = discord.Embed(description=report) - em.set_author( - name=_('Report from {0.display_name}').format(author), - icon_url=avatar - ) - - ticket_number = await self.config.guild(guild).next_ticket() - await self.config.guild(guild).next_ticket.set(ticket_number + 1) - em.set_footer(text=_("Report #{}").format(ticket_number)) channel_id = await self.config.guild(guild).output_channel() channel = guild.get_channel(channel_id) - if channel is not None: - try: - await channel.send(embed=em) - except (discord.Forbidden, discord.HTTPException): - return None + if channel is None: + return None + + files = await Tunnel.files_from_attatch(msg) + + ticket_number = await self.config.guild(guild).next_ticket() + await self.config.guild(guild).next_ticket.set(ticket_number + 1) + + if await self.bot.embed_requested(channel, author): + em = discord.Embed(description=report) + em.set_author( + name=_('Report from {0.display_name}').format(author), + icon_url=author.avatar_url + ) + em.set_footer(text=_("Report #{}").format(ticket_number)) + send_content = None else: + em = None + send_content = _( + 'Report from {author.mention} (Ticket #{number})' + ).format(author=author, number=ticket_number) + send_content += "\n" + report + + try: + await Tunnel.message_forwarder( + destination=channel, + content=send_content, + embed=em, + files=files + ) + except (discord.Forbidden, discord.HTTPException): return None await self.config.custom('REPORT', guild.id, ticket_number).report.set( @@ -198,8 +216,13 @@ class Reports: return ticket_number @commands.group(name="report", invoke_without_command=True) - async def report(self, ctx: RedContext): - "Follow the prompts to make a report" + async def report(self, ctx: RedContext, *, _report: str=""): + """ + Follow the prompts to make a report + + optionally use with a report message + to use it non interactively + """ author = ctx.author guild = ctx.guild if guild is None: @@ -243,31 +266,39 @@ class Reports: pass self.user_cache.append(author.id) - try: - dm = await author.send( - _("Please respond to this message with your Report." - "\nYour report should be a single message") - ) - except discord.Forbidden: - await ctx.send( - _("This requires DMs enabled.") - ) - self.user_cache.remove(author.id) - return - - def pred(m): - return m.author == author and m.channel == dm.channel - - try: - message = await self.bot.wait_for( - 'message', check=pred, timeout=180 - ) - except asyncio.TimeoutError: - await author.send( - _("You took too long. Try again later.") - ) + if _report: + _m = copy(ctx.message) + _m.content = _report + _m.content = _m.clean_content + val = await self.send_report(_m, guild) else: - val = await self.send_report(message, guild) + try: + dm = await author.send( + _("Please respond to this message with your Report." + "\nYour report should be a single message") + ) + except discord.Forbidden: + await ctx.send( + _("This requires DMs enabled.") + ) + self.user_cache.remove(author.id) + return + + def pred(m): + return m.author == author and m.channel == dm.channel + + try: + message = await self.bot.wait_for( + 'message', check=pred, timeout=180 + ) + except asyncio.TimeoutError: + await author.send( + _("You took too long. Try again later.") + ) + else: + val = await self.send_report(message, guild) + + with contextlib.suppress(discord.Forbidden, discord.HTTPException): if val is None: await author.send( _("There was an error sending your report.") @@ -276,7 +307,7 @@ class Reports: await author.send( _("Your report was submitted. (Ticket #{})").format(val) ) - self.antispam[guild.id][author.id].stamp() + self.antispam[guild.id][author.id].stamp() self.user_cache.remove(author.id) diff --git a/redbot/core/utils/tunnel.py b/redbot/core/utils/tunnel.py index 89be01cba..46380155f 100644 --- a/redbot/core/utils/tunnel.py +++ b/redbot/core/utils/tunnel.py @@ -4,6 +4,7 @@ from redbot.core.utils.chat_formatting import pagify import io import sys import weakref +from typing import List _instances = weakref.WeakValueDictionary({}) @@ -94,6 +95,88 @@ class Tunnel(metaclass=TunnelMeta): def minutes_since(self): return int((self.last_interaction - datetime.utcnow()).seconds / 60) + @staticmethod + async def message_forwarder( + *, destination: discord.abc.Messageable, + content: str=None, embed=None, files=[]) -> List[discord.Message]: + """ + This does the actual sending, use this instead of a full tunnel + if you are using command initiated reactions instead of persistent + event based ones + + Parameters + ---------- + destination: `discord.abc.Messageable` + Where to send + content: `str` + The message content + embed: `discord.Embed` + The embed to send + files: `List[discord.Files]` + A list of files to send. + + Returns + ------- + list of `discord.Message` + The `discord.Message`(s) sent as a result + + Raises + ------ + discord.Forbidden + see `discord.abc.Messageable.send` + discord.HTTPException + see `discord.abc.Messageable.send` + """ + rets = [] + files = files if files else None + if content: + for page in pagify(content): + rets.append( + await destination.send( + page, files=files, embed=embed) + ) + if files: + del files + if embed: + del embed + elif embed or files: + rets.append( + await destination.send(files=files, embed=embed) + ) + return rets + + @staticmethod + async def files_from_attatch(m: discord.Message) -> List[discord.File]: + """ + makes a list of file objects from a message + returns an empty list if none, or if the sum of file sizes + is too large for the bot to send + + Parameters + --------- + m: `discord.Message` + A message to get attachments from + + Returns + ------- + list of `discord.File` + A list of `discord.File` objects + + """ + files = [] + size = 0 + max_size = 8 * 1024 * 1024 + for a in m.attachments: + _fp = io.BytesIO() + await a.save(_fp) + size += sys.getsizeof(_fp) + if size > max_size: + return [] + files.append( + discord.File(_fp, filename=a.filename) + ) + return files + async def communicate(self, *, message: discord.Message, topic: str=None, @@ -140,35 +223,22 @@ class Tunnel(metaclass=TunnelMeta): else: content = topic - attach = None if message.attachments: - files = [] - size = 0 - max_size = 8 * 1024 * 1024 - for a in message.attachments: - _fp = io.BytesIO() - await a.save(_fp) - size += sys.getsizeof(_fp) - if size > max_size: - await send_to.send( - "Could not forward attatchments. " - "Total size of attachments in a single " - "message must be less than 8MB." - ) - break - files.append( - discord.File(_fp, filename=a.filename) + attach = await self.files_from_attatch(message) + if not attach: + await message.channel.send( + "Could not forward attatchments. " + "Total size of attachments in a single " + "message must be less than 8MB." ) - else: - attach = files + else: + attach = [] - rets = [] - for page in pagify(content): - rets.append( - await send_to.send(content, files=attach) - ) - if attach: - del attach + rets = await self.message_forwarder( + destination=send_to, + content=content, + files=attach + ) await message.add_reaction("\N{WHITE HEAVY CHECK MARK}") await message.add_reaction("\N{NEGATIVE SQUARED CROSS MARK}")