aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/cogs/dm_relay.py30
-rw-r--r--bot/converters.py19
2 files changed, 43 insertions, 6 deletions
diff --git a/bot/cogs/dm_relay.py b/bot/cogs/dm_relay.py
index f62d6105e..0dc15d4b1 100644
--- a/bot/cogs/dm_relay.py
+++ b/bot/cogs/dm_relay.py
@@ -1,4 +1,5 @@
import logging
+from typing import Optional
import discord
from discord import Color
@@ -7,6 +8,8 @@ from discord.ext.commands import Cog
from bot import constants
from bot.bot import Bot
+from bot.converters import UserMentionOrID
+from bot.utils import RedisCache
from bot.utils.checks import in_whitelist_check, with_role_check
from bot.utils.messages import send_attachments
from bot.utils.webhooks import send_webhook
@@ -17,6 +20,9 @@ log = logging.getLogger(__name__)
class DMRelay(Cog):
"""Relay direct messages to and from the bot."""
+ # RedisCache[str, t.Union[discord.User.id, discord.Member.id]]
+ dm_cache = RedisCache()
+
def __init__(self, bot: Bot):
self.bot = bot
self.webhook_id = constants.Webhooks.dm_log
@@ -24,11 +30,11 @@ class DMRelay(Cog):
self.bot.loop.create_task(self.fetch_webhook())
@commands.command(aliases=("reply",))
- async def send_dm(self, ctx: commands.Context, member: discord.Member, *, message: str) -> None:
+ async def send_dm(self, ctx: commands.Context, member: Optional[UserMentionOrID], *, message: str) -> None:
"""
Allows you to send a DM to a user from the bot.
- A `member` must be provided.
+ If `member` is not provided, it will send to the last user who DM'd the bot.
This feature should be used extremely sparingly. Use ModMail if you need to have a serious
conversation with a user. This is just for responding to extraordinary DMs, having a little
@@ -36,14 +42,24 @@ class DMRelay(Cog):
NOTE: This feature will be removed if it is overused.
"""
- try:
- await member.send(message)
- await ctx.message.add_reaction("✅")
+ if not member:
+ user_id = await self.dm_cache.get("last_user")
+ member = ctx.guild.get_member(user_id) if user_id else None
+
+ # If we still don't have a Member at this point, give up
+ if not member:
+ log.debug("This bot has never gotten a DM, or the RedisCache has been cleared.")
+ await ctx.message.add_reaction("❌")
return
+ try:
+ await member.send(message)
except discord.errors.Forbidden:
log.debug("User has disabled DMs.")
await ctx.message.add_reaction("❌")
+ else:
+ await ctx.message.add_reaction("✅")
+ self.bot.stats.incr("dm_relay.dm_sent")
async def fetch_webhook(self) -> None:
"""Fetches the webhook object, so we can post to it."""
@@ -65,9 +81,11 @@ class DMRelay(Cog):
await send_webhook(
webhook=self.webhook,
content=message.clean_content,
- username=message.author.display_name,
+ username=f"{message.author.display_name} ({message.author.id})",
avatar_url=message.author.avatar_url
)
+ await self.dm_cache.set("last_user", message.author.id)
+ self.bot.stats.incr("dm_relay.dm_received")
# Handle any attachments
if message.attachments:
diff --git a/bot/converters.py b/bot/converters.py
index 898822165..4a0633951 100644
--- a/bot/converters.py
+++ b/bot/converters.py
@@ -330,6 +330,25 @@ def proxy_user(user_id: str) -> discord.Object:
return user
+class UserMentionOrID(UserConverter):
+ """
+ Converts to a `discord.User`, but only if a mention or userID is provided.
+
+ Unlike the default `UserConverter`, it does allow conversion from name, or name#descrim.
+
+ This is useful in cases where that lookup strategy would lead to ambiguity.
+ """
+
+ async def convert(self, ctx: Context, argument: str) -> discord.User:
+ """Convert the `arg` to a `discord.User`."""
+ match = self._get_id_match(argument) or re.match(r'<@!?([0-9]+)>$', argument)
+
+ if match is not None:
+ return await super().convert(ctx, argument)
+ else:
+ raise BadArgument(f"`{argument}` is not a User mention or a User ID.")
+
+
class FetchedUser(UserConverter):
"""
Converts to a `discord.User` or, if it fails, a `discord.Object`.