Report fixes + improvements (#1541)

* WIP

* fix perms issue

* better

* more work

* working

* working, tessted

* docs

* mutable default fix
This commit is contained in:
Michael H 2018-05-04 00:38:58 -04:00 committed by Kowlin
parent fca7686701
commit 7cd98c8a63
2 changed files with 177 additions and 76 deletions

View File

@ -2,7 +2,8 @@ import logging
import asyncio import asyncio
from typing import Union from typing import Union
from datetime import timedelta from datetime import timedelta
from copy import copy
import contextlib
import discord import discord
from discord.ext import commands from discord.ext import commands
@ -109,10 +110,11 @@ class Reports:
ret |= await self.bot.is_owner(m) ret |= await self.bot.is_owner(m)
return ret return ret
async def discover_guild(self, author: discord.User, *, async def discover_guild(
mod: bool=False, self, author: discord.User, *,
permissions: Union[discord.Permissions, dict]={}, mod: bool=False,
prompt: str=""): permissions: Union[discord.Permissions, dict]=None,
prompt: str=""):
""" """
discovers which of shared guilds between the bot discovers which of shared guilds between the bot
and provided user based on conditions (mod or permissions is an or) 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 prompt is for providing a user prompt for selection
""" """
shared_guilds = [] shared_guilds = []
if isinstance(permissions, discord.Permissions): if permissions is None:
perms = discord.Permissions()
elif isinstance(permissions, discord.Permissions):
perms = permissions perms = permissions
else: else:
permissions = discord.Permissions(**perms) perms = discord.Permissions(**permissions)
for guild in self.bot.guilds: for guild in self.bot.guilds:
x = guild.get_member(author.id) x = guild.get_member(author.id)
@ -170,26 +174,40 @@ class Reports:
author = guild.get_member(msg.author.id) author = guild.get_member(msg.author.id)
report = msg.clean_content 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_id = await self.config.guild(guild).output_channel()
channel = guild.get_channel(channel_id) channel = guild.get_channel(channel_id)
if channel is not None: if channel is None:
try: return None
await channel.send(embed=em)
except (discord.Forbidden, discord.HTTPException): files = await Tunnel.files_from_attatch(msg)
return None
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: 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 return None
await self.config.custom('REPORT', guild.id, ticket_number).report.set( await self.config.custom('REPORT', guild.id, ticket_number).report.set(
@ -198,8 +216,13 @@ class Reports:
return ticket_number return ticket_number
@commands.group(name="report", invoke_without_command=True) @commands.group(name="report", invoke_without_command=True)
async def report(self, ctx: RedContext): async def report(self, ctx: RedContext, *, _report: str=""):
"Follow the prompts to make a report" """
Follow the prompts to make a report
optionally use with a report message
to use it non interactively
"""
author = ctx.author author = ctx.author
guild = ctx.guild guild = ctx.guild
if guild is None: if guild is None:
@ -243,31 +266,39 @@ class Reports:
pass pass
self.user_cache.append(author.id) self.user_cache.append(author.id)
try: if _report:
dm = await author.send( _m = copy(ctx.message)
_("Please respond to this message with your Report." _m.content = _report
"\nYour report should be a single message") _m.content = _m.clean_content
) val = await self.send_report(_m, guild)
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: 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: if val is None:
await author.send( await author.send(
_("There was an error sending your report.") _("There was an error sending your report.")
@ -276,7 +307,7 @@ class Reports:
await author.send( await author.send(
_("Your report was submitted. (Ticket #{})").format(val) _("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) self.user_cache.remove(author.id)

View File

@ -4,6 +4,7 @@ from redbot.core.utils.chat_formatting import pagify
import io import io
import sys import sys
import weakref import weakref
from typing import List
_instances = weakref.WeakValueDictionary({}) _instances = weakref.WeakValueDictionary({})
@ -94,6 +95,88 @@ class Tunnel(metaclass=TunnelMeta):
def minutes_since(self): def minutes_since(self):
return int((self.last_interaction - datetime.utcnow()).seconds / 60) 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, *, async def communicate(self, *,
message: discord.Message, message: discord.Message,
topic: str=None, topic: str=None,
@ -140,35 +223,22 @@ class Tunnel(metaclass=TunnelMeta):
else: else:
content = topic content = topic
attach = None
if message.attachments: if message.attachments:
files = [] attach = await self.files_from_attatch(message)
size = 0 if not attach:
max_size = 8 * 1024 * 1024 await message.channel.send(
for a in message.attachments: "Could not forward attatchments. "
_fp = io.BytesIO() "Total size of attachments in a single "
await a.save(_fp) "message must be less than 8MB."
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)
) )
else: else:
attach = files attach = []
rets = [] rets = await self.message_forwarder(
for page in pagify(content): destination=send_to,
rets.append( content=content,
await send_to.send(content, files=attach) files=attach
) )
if attach:
del attach
await message.add_reaction("\N{WHITE HEAVY CHECK MARK}") await message.add_reaction("\N{WHITE HEAVY CHECK MARK}")
await message.add_reaction("\N{NEGATIVE SQUARED CROSS MARK}") await message.add_reaction("\N{NEGATIVE SQUARED CROSS MARK}")