[Mutes] Add ability to send DMs on mute/unmute (#4563)

* handle manual mutes/unmutes

Doing this in the Web-Editor is painful. Let's switch to VSC.

* embed version

* non embed

* config stuff

* testing done

* wow black

* Few things before I start local testing

* Fix new lines

* Make messages not depend on modlog

* Yay voicemutes

* black+import

* what is your ducking problem vscode

* adress review

* this is driving me mad

* Check the config in `_send_dm_notification` to avoid code repetition

* Fix incorrect type hints

* Remove no longer needed line changes

* Remove unused function

* Update the type hints from commit 946299 in the MixinMeta too

* Fixed wrong variable being passed to the method and ensure DMs aren't sent when we couldn't get the member object

* They call me dumb for a reason

* Stop overriding variable with duration + various formatting tweaks

* :(

* We need to differ between voice and text in two places, interesting...

* Show info about no reason provided in embed

* Apparently, the `reason` can also be an empty string :|

* Update redbot/cogs/mutes/mutes.py

Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>

Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>
This commit is contained in:
Dav 2021-01-23 10:29:18 +00:00 committed by GitHub
parent 2ce4a275fc
commit aa0ee3385d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 166 additions and 4 deletions

View File

@ -1,5 +1,5 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import List, Tuple, Optional, Dict from typing import List, Tuple, Optional, Dict, Union
from datetime import datetime from datetime import datetime
import discord import discord
@ -25,3 +25,15 @@ class MixinMeta(ABC):
ctx: commands.Context, user_voice_state: Optional[discord.VoiceState], **perms: bool ctx: commands.Context, user_voice_state: Optional[discord.VoiceState], **perms: bool
) -> bool: ) -> bool:
raise NotImplementedError() raise NotImplementedError()
@abstractmethod
async def _send_dm_notification(
self,
user: Union[discord.User, discord.Member],
moderator: Optional[Union[discord.User, discord.Member]],
guild: discord.Guild,
mute_type: str,
reason: Optional[str],
duration=None,
):
raise NotImplementedError()

View File

@ -13,7 +13,7 @@ from .voicemutes import VoiceMutes
from redbot.core.bot import Red from redbot.core.bot import Red
from redbot.core import commands, checks, i18n, modlog, Config from redbot.core import commands, checks, i18n, modlog, Config
from redbot.core.utils import bounded_gather from redbot.core.utils import bounded_gather
from redbot.core.utils.chat_formatting import humanize_timedelta, humanize_list, pagify from redbot.core.utils.chat_formatting import bold, humanize_timedelta, humanize_list, pagify
from redbot.core.utils.mod import get_audit_reason from redbot.core.utils.mod import get_audit_reason
from redbot.core.utils.menus import start_adding_reactions from redbot.core.utils.menus import start_adding_reactions
from redbot.core.utils.predicates import MessagePredicate, ReactionPredicate from redbot.core.utils.predicates import MessagePredicate, ReactionPredicate
@ -77,6 +77,8 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
"notification_channel": None, "notification_channel": None,
"muted_users": {}, "muted_users": {},
"default_time": 0, "default_time": 0,
"dm": False,
"show_mod": False,
} }
self.config.register_global(force_role_mutes=True) self.config.register_global(force_role_mutes=True)
# Tbh I would rather force everyone to use role mutes. # Tbh I would rather force everyone to use role mutes.
@ -256,6 +258,9 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
_("Automatic unmute"), _("Automatic unmute"),
until=None, until=None,
) )
await self._send_dm_notification(
member, author, guild, _("Server unmute"), _("Automatic unmute")
)
else: else:
chan_id = await self.config.guild(guild).notification_channel() chan_id = await self.config.guild(guild).notification_channel()
notification_channel = guild.get_channel(chan_id) notification_channel = guild.get_channel(chan_id)
@ -363,6 +368,9 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
modlog_reason, modlog_reason,
until=None, until=None,
) )
await self._send_dm_notification(
member, author, guild, _("Server unmute"), _("Automatic unmute")
)
self._channel_mute_events[guild.id].set() self._channel_mute_events[guild.id].set()
if any(results): if any(results):
reasons = {} reasons = {}
@ -424,8 +432,10 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
if create_case: if create_case:
if isinstance(channel, discord.VoiceChannel): if isinstance(channel, discord.VoiceChannel):
unmute_type = "vunmute" unmute_type = "vunmute"
notification_title = _("Voice unmute")
else: else:
unmute_type = "cunmute" unmute_type = "cunmute"
notification_title = _("Channel unmute")
await modlog.create_case( await modlog.create_case(
self.bot, self.bot,
channel.guild, channel.guild,
@ -437,6 +447,9 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
until=None, until=None,
channel=channel, channel=channel,
) )
await self._send_dm_notification(
member, author, channel.guild, notification_title, _("Automatic unmute")
)
return None return None
else: else:
error_msg = _( error_msg = _(
@ -457,6 +470,72 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
else: else:
return (member, channel, success["reason"]) return (member, channel, success["reason"])
async def _send_dm_notification(
self,
user: Union[discord.User, discord.Member],
moderator: Optional[Union[discord.User, discord.Member]],
guild: discord.Guild,
mute_type: str,
reason: Optional[str],
duration=None,
):
if not await self.config.guild(guild).dm():
return
show_mod = await self.config.guild(guild).show_mod()
title = bold(mute_type)
if duration:
duration_str = humanize_timedelta(timedelta=duration)
until = datetime.now(timezone.utc) + duration
until_str = until.strftime("%Y-%m-%d %H:%M:%S UTC")
if moderator is None:
moderator_str = _("Unknown")
else:
moderator_str = str(moderator)
if not reason:
reason = _("No reason provided.")
# okay, this is some poor API to require PrivateChannel here...
if await self.bot.embed_requested(await user.create_dm(), user):
em = discord.Embed(
title=title,
description=reason,
color=await self.bot.get_embed_color(user),
)
em.timestamp = datetime.utcnow()
if duration:
em.add_field(name=_("Until"), value=until_str)
em.add_field(name=_("Duration"), value=duration_str)
em.add_field(name=_("Guild"), value=guild.name, inline=False)
if show_mod:
em.add_field(name=_("Moderator"), value=moderator_str)
try:
await user.send(embed=em)
except discord.Forbidden:
pass
else:
message = f"{title}\n>>> "
message += reason
message += (
_("\n**Moderator**: {moderator}").format(moderator=moderator_str)
if show_mod
else ""
)
message += (
_("\n**Until**: {until}\n**Duration**: {duration}").format(
until=until_str, duration=duration_str
)
if duration
else ""
)
message += _("\n**Guild**: {guild_name}").format(guild_name=guild.name)
try:
await user.send(message)
except discord.Forbidden:
pass
@commands.Cog.listener() @commands.Cog.listener()
async def on_member_update(self, before: discord.Member, after: discord.Member): async def on_member_update(self, before: discord.Member, after: discord.Member):
""" """
@ -494,6 +573,9 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
) )
del self._server_mutes[guild.id][after.id] del self._server_mutes[guild.id][after.id]
should_save = True should_save = True
await self._send_dm_notification(
after, None, guild, _("Server unmute"), _("Manually removed mute role")
)
elif mute_role in roles_added: elif mute_role in roles_added:
# send modlog case for mute and add to cache # send modlog case for mute and add to cache
if guild.id not in self._server_mutes: if guild.id not in self._server_mutes:
@ -515,6 +597,9 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
"until": None, "until": None,
} }
should_save = True should_save = True
await self._send_dm_notification(
after, None, guild, _("Server mute"), _("Manually applied mute role")
)
if should_save: if should_save:
await self.config.guild(guild).muted_users.set(self._server_mutes[guild.id]) await self.config.guild(guild).muted_users.set(self._server_mutes[guild.id])
@ -555,15 +640,27 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
user_id not in after_perms or any((send_messages, speak)) user_id not in after_perms or any((send_messages, speak))
): ):
user = after.guild.get_member(user_id) user = after.guild.get_member(user_id)
send_dm_notification = True
if not user: if not user:
send_dm_notification = False
user = discord.Object(id=user_id) user = discord.Object(id=user_id)
log.debug(f"{user} - {type(user)}") log.debug(f"{user} - {type(user)}")
to_del.append(user_id) to_del.append(user_id)
log.debug("creating case") log.debug("creating case")
if isinstance(after, discord.VoiceChannel): if isinstance(after, discord.VoiceChannel):
unmute_type = "vunmute" unmute_type = "vunmute"
notification_title = _("Voice unmute")
else: else:
unmute_type = "cunmute" unmute_type = "cunmute"
notification_title = _("Channel unmute")
if send_dm_notification:
await self._send_dm_notification(
user,
None,
after.guild,
notification_title,
_("Manually removed channel overwrites"),
)
await modlog.create_case( await modlog.create_case(
self.bot, self.bot,
after.guild, after.guild,
@ -614,6 +711,34 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
"""Mute settings.""" """Mute settings."""
pass pass
@muteset.command()
@commands.guild_only()
async def senddm(self, ctx: commands.Context, true_or_false: bool):
"""Set whether mute notifications should be sent to users in DMs."""
await self.config.guild(ctx.guild).dm.set(true_or_false)
if true_or_false:
await ctx.send(_("I will now try to send mute notifications to users DMs."))
else:
await ctx.send(_("Mute notifications will no longer be sent to users DMs."))
@muteset.command()
@commands.guild_only()
async def showmoderator(self, ctx, true_or_false: bool):
"""Decide whether the name of the moderator muting a user should be included in the DM to that user."""
await self.config.guild(ctx.guild).show_mod.set(true_or_false)
if true_or_false:
await ctx.send(
_(
"I will include the name of the moderator who issued the mute when sending a DM to a user."
)
)
else:
await ctx.send(
_(
"I will not include the name of the moderator who issued the mute when sending a DM to a user."
)
)
@muteset.command(name="forcerole") @muteset.command(name="forcerole")
@commands.is_owner() @commands.is_owner()
async def force_role_mutes(self, ctx: commands.Context, force_role_mutes: bool): async def force_role_mutes(self, ctx: commands.Context, force_role_mutes: bool):
@ -638,11 +763,17 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
notification_channel = ctx.guild.get_channel(data["notification_channel"]) notification_channel = ctx.guild.get_channel(data["notification_channel"])
default_time = timedelta(seconds=data["default_time"]) default_time = timedelta(seconds=data["default_time"])
msg = _( msg = _(
"Mute Role: {role}\nNotification Channel: {channel}\n" "Default Time: {time}" "Mute Role: {role}\n"
"Notification Channel: {channel}\n"
"Default Time: {time}\n"
"Send DM: {dm}\n"
"Show moderator: {show_mod}"
).format( ).format(
role=mute_role.mention if mute_role else _("None"), role=mute_role.mention if mute_role else _("None"),
channel=notification_channel.mention if notification_channel else _("None"), channel=notification_channel.mention if notification_channel else _("None"),
time=humanize_timedelta(timedelta=default_time) if default_time else _("None"), time=humanize_timedelta(timedelta=default_time) if default_time else _("None"),
dm=data["dm"],
show_mod=data["show_mod"],
) )
await ctx.maybe_send_embed(msg) await ctx.maybe_send_embed(msg)
@ -1007,6 +1138,9 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
until=until, until=until,
channel=None, channel=None,
) )
await self._send_dm_notification(
user, author, guild, _("Server mute"), reason, duration
)
else: else:
issue_list.append(success) issue_list.append(success)
if success_list: if success_list:
@ -1151,6 +1285,9 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
until=until, until=until,
channel=channel, channel=channel,
) )
await self._send_dm_notification(
user, author, guild, _("Channel mute"), reason, duration
)
async with self.config.member(user).perms_cache() as cache: async with self.config.member(user).perms_cache() as cache:
cache[channel.id] = success["old_overs"] cache[channel.id] = success["old_overs"]
else: else:
@ -1217,6 +1354,9 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
reason, reason,
until=None, until=None,
) )
await self._send_dm_notification(
user, author, guild, _("Server unmute"), reason
)
else: else:
issue_list.append(success) issue_list.append(success)
self._channel_mute_events[guild.id].set() self._channel_mute_events[guild.id].set()
@ -1281,6 +1421,9 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
until=None, until=None,
channel=channel, channel=channel,
) )
await self._send_dm_notification(
user, author, guild, _("Channel unmute"), reason
)
else: else:
issue_list.append((user, success["reason"])) issue_list.append((user, success["reason"]))
if success_list: if success_list:

View File

@ -1,10 +1,11 @@
from typing import Optional, Tuple from typing import Optional, Tuple, Union
from datetime import timezone, timedelta, datetime from datetime import timezone, timedelta, datetime
from .abc import MixinMeta from .abc import MixinMeta
import discord import discord
from redbot.core import commands, checks, i18n, modlog from redbot.core import commands, checks, i18n, modlog
from redbot.core.utils.chat_formatting import ( from redbot.core.utils.chat_formatting import (
bold,
humanize_timedelta, humanize_timedelta,
humanize_list, humanize_list,
pagify, pagify,
@ -143,6 +144,9 @@ class VoiceMutes(MixinMeta):
until=until, until=until,
channel=channel, channel=channel,
) )
await self._send_dm_notification(
user, author, guild, _("Voice mute"), reason, duration
)
async with self.config.member(user).perms_cache() as cache: async with self.config.member(user).perms_cache() as cache:
cache[channel.id] = success["old_overs"] cache[channel.id] = success["old_overs"]
else: else:
@ -216,6 +220,9 @@ class VoiceMutes(MixinMeta):
until=None, until=None,
channel=channel, channel=channel,
) )
await self._send_dm_notification(
user, author, guild, _("Voice unmute"), reason
)
else: else:
issue_list.append((user, success["reason"])) issue_list.append((user, success["reason"]))
if success_list: if success_list: