Skip to content

Commit 351ee43

Browse files
committed
Add ability to disable DM
1 parent 2e98510 commit 351ee43

File tree

6 files changed

+293
-48
lines changed

6 files changed

+293
-48
lines changed

CHANGELOG.md

+12
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@ however, insignificant breaking changes does not guarantee a major version bump,
2727
- `?logs responded [user]` command, it will show all logs that the user has sent an reply. (Thanks to papiersnipper PR#288)
2828
- `user` when not provided, defaults to the user who ran the command.
2929
- Open threads in limbo now auto closes if the channel cannot be found. This check is done every time the bot restarts.
30+
- Ability to disable new threads from getting created.
31+
- `?disable`
32+
- Ability to fully disable Modmail DM.
33+
- `?disable all`
34+
- To re-enable DM: `?enable`, and to see the current status: `?isenable`.
35+
- This disabled Modmail interface is customizable with the following config vars:
36+
- `disabled_new_thread_title`
37+
- `disabled_new_thread_response`
38+
- `disabled_new_thread_footer`
39+
- `disabled_current_thread_title`
40+
- `disabled_current_thread_response`
41+
- `disabled_current_thread_footer`
3042

3143
### Changed
3244

bot.py

+89-38
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "3.3.0-dev4"
1+
__version__ = "3.3.0-dev5"
22

33
import asyncio
44
import logging
@@ -46,8 +46,10 @@
4646

4747
ch = logging.StreamHandler(stream=sys.stdout)
4848
ch.setLevel(logging.INFO)
49-
formatter = logging.Formatter("%(asctime)s %(filename)s[%(lineno)d] - %(levelname)s: %(message)s",
50-
datefmt="%b %d %H:%M:%S")
49+
formatter = logging.Formatter(
50+
"%(asctime)s %(filename)s[%(lineno)d] - %(levelname)s: %(message)s",
51+
datefmt="%b %d %H:%M:%S",
52+
)
5153
ch.setFormatter(formatter)
5254
logger.addHandler(ch)
5355

@@ -123,7 +125,7 @@ def startup(self):
123125
logger.info("┴ ┴└─┘─┴┘┴ ┴┴ ┴┴┴─┘")
124126
logger.info("v%s", __version__)
125127
logger.info("Authors: kyb3r, fourjr, Taaku18")
126-
logger.line('debug')
128+
logger.line("debug")
127129

128130
for cog in self.loaded_cogs:
129131
logger.debug("Loading %s.", cog)
@@ -132,7 +134,7 @@ def startup(self):
132134
logger.debug("Successfully loaded %s.", cog)
133135
except Exception:
134136
logger.exception("Failed to load %s.", cog)
135-
logger.line('debug')
137+
logger.line("debug")
136138

137139
def _configure_logging(self):
138140
level_text = self.config["log_level"].upper()
@@ -144,9 +146,7 @@ def _configure_logging(self):
144146
"DEBUG": logging.DEBUG,
145147
}
146148

147-
ch_debug = logging.FileHandler(
148-
self.log_file_name, mode="a+"
149-
)
149+
ch_debug = logging.FileHandler(self.log_file_name, mode="a+")
150150

151151
ch_debug.setLevel(logging.DEBUG)
152152
formatter_debug = FileFormatter(
@@ -460,7 +460,10 @@ async def on_ready(self):
460460
logger.debug("Client ready.")
461461
logger.info("Logged in as: %s", self.user)
462462
logger.info("Bot ID: %s", self.user.id)
463-
owners = ", ".join(getattr(self.get_user(owner_id), "name", str(owner_id)) for owner_id in self.owner_ids)
463+
owners = ", ".join(
464+
getattr(self.get_user(owner_id), "name", str(owner_id))
465+
for owner_id in self.owner_ids
466+
)
464467
logger.info("Owners: %s", owners)
465468
logger.info("Prefix: %s", self.prefix)
466469
logger.info("Guild Name: %s", self.guild.name)
@@ -504,10 +507,12 @@ async def on_ready(self):
504507
)
505508

506509
for log in await self.api.get_open_logs():
507-
if self.get_channel(int(log['channel_id'])) is None:
508-
logger.debug("Unable to resolve thread with channel %s.", log['channel_id'])
510+
if self.get_channel(int(log["channel_id"])) is None:
511+
logger.debug(
512+
"Unable to resolve thread with channel %s.", log["channel_id"]
513+
)
509514
log_data = await self.api.post_log(
510-
log['channel_id'],
515+
log["channel_id"],
511516
{
512517
"open": False,
513518
"closed_at": str(datetime.utcnow()),
@@ -522,9 +527,14 @@ async def on_ready(self):
522527
},
523528
)
524529
if log_data:
525-
logger.debug("Successfully closed thread with channel %s.", log['channel_id'])
530+
logger.debug(
531+
"Successfully closed thread with channel %s.", log["channel_id"]
532+
)
526533
else:
527-
logger.debug("Failed to close thread with channel %s, skipping.", log['channel_id'])
534+
logger.debug(
535+
"Failed to close thread with channel %s, skipping.",
536+
log["channel_id"],
537+
)
528538

529539
self.metadata_loop = tasks.Loop(
530540
self.post_metadata,
@@ -573,21 +583,17 @@ async def retrieve_emoji(self) -> typing.Tuple[str, str]:
573583

574584
return sent_emoji, blocked_emoji
575585

576-
async def _process_blocked(self, message: discord.Message) -> bool:
586+
async def _process_blocked(
587+
self, message: discord.Message
588+
) -> typing.Tuple[bool, str]:
577589
sent_emoji, blocked_emoji = await self.retrieve_emoji()
578590

579591
if str(message.author.id) in self.blocked_whitelisted_users:
580592
if str(message.author.id) in self.blocked_users:
581593
self.blocked_users.pop(str(message.author.id))
582594
await self.config.update()
583595

584-
if sent_emoji != "disable":
585-
try:
586-
await message.add_reaction(sent_emoji)
587-
except (discord.HTTPException, discord.InvalidArgument):
588-
logger.warning("Failed to add sent_emoji.", exc_info=True)
589-
590-
return False
596+
return False, sent_emoji
591597

592598
now = datetime.utcnow()
593599

@@ -682,8 +688,10 @@ async def _process_blocked(self, message: discord.Message) -> bool:
682688
# backwards compat
683689
end_time = re.search(r"%([^%]+?)%", reason)
684690
if end_time is not None:
685-
logger.warning(r"Deprecated time message for user %s, block and unblock again to update.",
686-
message.author)
691+
logger.warning(
692+
r"Deprecated time message for user %s, block and unblock again to update.",
693+
message.author,
694+
)
687695

688696
if end_time is not None:
689697
after = (
@@ -702,19 +710,62 @@ async def _process_blocked(self, message: discord.Message) -> bool:
702710
reaction = sent_emoji
703711

704712
await self.config.update()
713+
return str(message.author.id) in self.blocked_users, reaction
714+
715+
@staticmethod
716+
async def add_reaction(msg, reaction):
705717
if reaction != "disable":
706718
try:
707-
await message.add_reaction(reaction)
719+
await msg.add_reaction(reaction)
708720
except (discord.HTTPException, discord.InvalidArgument):
709721
logger.warning("Failed to add reaction %s.", reaction, exc_info=True)
710-
return str(message.author.id) in self.blocked_users
711722

712723
async def process_dm_modmail(self, message: discord.Message) -> None:
713724
"""Processes messages sent to the bot."""
714-
blocked = await self._process_blocked(message)
715-
if not blocked:
716-
thread = await self.threads.find_or_create(message.author)
717-
await thread.send(message)
725+
blocked, reaction = await self._process_blocked(message)
726+
if blocked:
727+
return await self.add_reaction(message, reaction)
728+
thread = await self.threads.find(recipient=message.author)
729+
if thread is None:
730+
if self.config["dm_disabled"] >= 1:
731+
embed = discord.Embed(
732+
title=self.config["disabled_new_thread_title"],
733+
color=self.error_color,
734+
description=self.config["disabled_new_thread_response"],
735+
)
736+
embed.set_footer(
737+
text=self.config["disabled_new_thread_footer"],
738+
icon_url=self.guild.icon_url,
739+
)
740+
logger.info(
741+
"A new thread was blocked from %s due to disabled Modmail.",
742+
message.author,
743+
)
744+
_, blocked_emoji = await self.retrieve_emoji()
745+
await self.add_reaction(message, blocked_emoji)
746+
return await message.channel.send(embed=embed)
747+
thread = self.threads.create(message.author)
748+
else:
749+
if self.config["dm_disabled"] == 2:
750+
embed = discord.Embed(
751+
title=self.config["disabled_current_thread_title"],
752+
color=self.error_color,
753+
description=self.config["disabled_current_thread_response"],
754+
)
755+
embed.set_footer(
756+
text=self.config["disabled_current_thread_footer"],
757+
icon_url=self.guild.icon_url,
758+
)
759+
logger.info(
760+
"A message was blocked from %s due to disabled Modmail.",
761+
message.author,
762+
)
763+
_, blocked_emoji = await self.retrieve_emoji()
764+
await self.add_reaction(message, blocked_emoji)
765+
return await message.channel.send(embed=embed)
766+
767+
await self.add_reaction(message, reaction)
768+
await thread.send(message)
718769

719770
async def get_contexts(self, message, *, cls=commands.Context):
720771
"""
@@ -898,13 +949,13 @@ async def _void(*_args, **_kwargs):
898949

899950
thread = await self.threads.find(channel=channel)
900951
if thread is not None and thread.recipient:
901-
if await self._process_blocked(
902-
SimpleNamespace(
903-
author=thread.recipient,
904-
channel=SimpleNamespace(send=_void),
905-
add_reaction=_void,
952+
if (
953+
await self._process_blocked(
954+
SimpleNamespace(
955+
author=thread.recipient, channel=SimpleNamespace(send=_void)
956+
)
906957
)
907-
):
958+
)[0]:
908959
return
909960
await thread.recipient.trigger_typing()
910961

@@ -1111,7 +1162,7 @@ async def validate_database_connection(self):
11111162
raise
11121163
else:
11131164
logger.debug("Successfully connected to the database.")
1114-
logger.line('debug')
1165+
logger.line("debug")
11151166

11161167
async def post_metadata(self):
11171168
owner = (await self.application_info()).owner
@@ -1137,7 +1188,7 @@ async def post_metadata(self):
11371188
async def before_post_metadata(self):
11381189
await self.wait_for_connected()
11391190
logger.debug("Starting metadata loop.")
1140-
logger.line('debug')
1191+
logger.line("debug")
11411192
if not self.guild:
11421193
self.metadata_loop.cancel()
11431194

cogs/modmail.py

+95-4
Original file line numberDiff line numberDiff line change
@@ -626,9 +626,9 @@ def format_log_embeds(self, logs, avatar_url):
626626
embed.add_field(
627627
name="Created", value=duration(created_at, now=datetime.utcnow())
628628
)
629-
closer = entry.get('closer')
629+
closer = entry.get("closer")
630630
if closer is None:
631-
closer_msg = 'Unknown'
631+
closer_msg = "Unknown"
632632
else:
633633
closer_msg = f"<@{closer['id']}>"
634634
embed.add_field(name="Closed By", value=closer_msg)
@@ -916,6 +916,9 @@ async def contact(
916916
thread = self.bot.threads.create(
917917
user, creator=ctx.author, category=category
918918
)
919+
if self.bot.config["dm_disabled"] >= 1:
920+
logger.info("Contacting user %s when Modmail DM is disabled.", user)
921+
919922
embed = discord.Embed(
920923
title="Created Thread",
921924
description=f"Thread started by {ctx.author.mention} "
@@ -1089,7 +1092,7 @@ async def block(
10891092
embed = discord.Embed(
10901093
title="Success",
10911094
description=f"{mention} was previously blocked "
1092-
f'{old_reason}.\n{mention} is now blocked {reason}',
1095+
f"{old_reason}.\n{mention} is now blocked {reason}",
10931096
color=self.bot.main_color,
10941097
)
10951098
else:
@@ -1136,7 +1139,7 @@ async def unblock(self, ctx, *, user: User = None):
11361139
embed = discord.Embed(
11371140
title="Success",
11381141
description=f"{mention} was previously blocked internally "
1139-
f'{reason}.\n{mention} is no longer blocked.',
1142+
f"{reason}.\n{mention} is no longer blocked.",
11401143
color=self.bot.main_color,
11411144
)
11421145
embed.set_footer(
@@ -1197,6 +1200,94 @@ async def delete(self, ctx, message_id: Optional[int] = None):
11971200
except (discord.HTTPException, discord.InvalidArgument):
11981201
pass
11991202

1203+
@commands.command()
1204+
@checks.has_permissions(PermissionLevel.ADMINISTRATOR)
1205+
async def enable(self, ctx):
1206+
"""
1207+
Re-enables DM functionalities of Modmail.
1208+
1209+
Undo's the `{prefix}disable` command, all DM will be relayed after running this command.
1210+
"""
1211+
embed = discord.Embed(
1212+
title="Success",
1213+
description=f"Modmail will now accept all DM messages.",
1214+
color=self.bot.main_color,
1215+
)
1216+
1217+
if self.bot.config["dm_disabled"] != 0:
1218+
self.bot.config["dm_disabled"] = 0
1219+
await self.bot.config.update()
1220+
1221+
return await ctx.send(embed=embed)
1222+
1223+
@commands.group(invoke_without_command=True)
1224+
@checks.has_permissions(PermissionLevel.ADMINISTRATOR)
1225+
async def disable(self, ctx):
1226+
"""
1227+
Stop accepting new Modmail threads.
1228+
1229+
No new threads can be created through DM.
1230+
To stop all existing threads from DMing Modmail, do `{prefix}disable all`.
1231+
"""
1232+
embed = discord.Embed(
1233+
title="Success",
1234+
description=f"Modmail will not create any new threads.",
1235+
color=self.bot.main_color,
1236+
)
1237+
if self.bot.config["dm_disabled"] < 1:
1238+
self.bot.config["dm_disabled"] = 1
1239+
await self.bot.config.update()
1240+
1241+
return await ctx.send(embed=embed)
1242+
1243+
@disable.command(name="all")
1244+
@checks.has_permissions(PermissionLevel.ADMINISTRATOR)
1245+
async def disable_all(self, ctx):
1246+
"""
1247+
Disables all DM functionalities of Modmail.
1248+
1249+
No new threads can be created through DM nor no further DM messages will be relayed.
1250+
"""
1251+
embed = discord.Embed(
1252+
title="Success",
1253+
description=f"Modmail will not accept any DM messages.",
1254+
color=self.bot.main_color,
1255+
)
1256+
1257+
if self.bot.config["dm_disabled"] != 2:
1258+
self.bot.config["dm_disabled"] = 2
1259+
await self.bot.config.update()
1260+
1261+
return await ctx.send(embed=embed)
1262+
1263+
@commands.command()
1264+
@checks.has_permissions(PermissionLevel.ADMINISTRATOR)
1265+
async def isenable(self, ctx):
1266+
"""
1267+
Check if the DM functionalities of Modmail is enabled.
1268+
"""
1269+
1270+
if self.bot.config["dm_disabled"] == 1:
1271+
embed = discord.Embed(
1272+
title="New Threads Disabled",
1273+
description=f"Modmail will not create any new threads.",
1274+
color=self.bot.main_color,
1275+
)
1276+
elif self.bot.config["dm_disabled"] == 2:
1277+
embed = discord.Embed(
1278+
title="All DM Disabled",
1279+
description=f"Modmail will accept any DM messages for new and existing threads.",
1280+
color=self.bot.main_color,
1281+
)
1282+
else:
1283+
embed = discord.Embed(
1284+
title="Enabled",
1285+
description=f"Modmail is receiving all DM messages.",
1286+
color=self.bot.main_color,
1287+
)
1288+
1289+
return await ctx.send(embed=embed)
1290+
12001291

12011292
def setup(bot):
12021293
bot.add_cog(Modmail(bot))

0 commit comments

Comments
 (0)