Skip to content

Commit 26202b2

Browse files
authored
Merge branch 'development' into master
2 parents e75582f + 77fbb69 commit 26202b2

File tree

13 files changed

+334
-194
lines changed

13 files changed

+334
-194
lines changed

.env.example

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
TOKEN=MyBotToken
22
LOG_URL=https://logviewername.herokuapp.com/
33
GUILD_ID=1234567890
4-
MODMAIL_GUILD_ID=1234567890
54
OWNERS=Owner1ID,Owner2ID,Owner3ID
65
CONNECTION_URI=mongodb+srv://mongodburi

CHANGELOG.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,29 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66
This project mostly adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html);
77
however, insignificant breaking changes do not guarantee a major version bump, see the reasoning [here](https://github.com/modmail-dev/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section.
88

9-
109
# [UNRELEASED]
1110

11+
### Fixed
12+
- `?alias make/create` as aliases to `?alias add`. This improves continuity between the bot and its command structure. ([PR #3195](https://github.com/kyb3r/modmail/pull/3195))
13+
- Loading the blocked list with the `?blocked` command takes a long time when the list is large. ([PR #3242](https://github.com/kyb3r/modmail/pull/3242))
14+
- Reply not being forwarded from DM. (PR [#3239](https://github.com/modmail-dev/modmail/pull/3239))
15+
- Cleanup imports after removing/unloading a plugin. ([PR #3226](https://github.com/modmail-dev/Modmail/pull/3226))
16+
- Fixed a syntactic error in the close message when a thread is closed after a certain duration. ([PR #3233](https://github.com/modmail-dev/Modmail/pull/3233))
17+
- Removed an extra space in the help command title when the command has no parameters. ([PR #3271](https://github.com/modmail-dev/Modmail/pull/3271))
18+
1219
### Added
13-
- New .env config option: `REGISTRY_PLUGINS_ONLY`, restricts to only allow adding registry plugins. ([PR #3247](https://github.com/modmail-dev/modmail/pull/3247))
20+
- `?log key <key>` to retrieve the log link and view a preview using a log key. ([PR #3196](https://github.com/modmail-dev/Modmail/pull/3196))
21+
- `REGISTRY_PLUGINS_ONLY`, environment variable, when set, restricts to only allow adding registry plugins. ([PR #3247](https://github.com/modmail-dev/modmail/pull/3247))
22+
- `DISCORD_LOG_LEVEL` environment variable to set the log level of discord.py. ([PR #3216](https://github.com/modmail-dev/Modmail/pull/3216))
1423

1524
### Changed
1625
- Repo moved to https://github.com/modmail-dev/modmail.
26+
- Guild icons in embed footers and author urls now have a fixed size of 128. ([PR #3261](https://github.com/modmail-dev/modmail/pull/3261))
27+
- Discord.py internal logging is now enabled by default. ([PR #3216](https://github.com/modmail-dev/Modmail/pull/3216))
28+
- The confirm-thread-creation dialog now uses buttons instead of reactions. ([PR #3273](https://github.com/modmail-dev/Modmail/pull/3273))
29+
30+
### Internal
31+
- Renamed `Bot.log_file_name` to `Bot.log_file_path`. Log files are now created at `temp/logs/modmail.log`. ([PR #3216](https://github.com/modmail-dev/Modmail/pull/3216))
1732

1833
# v4.0.2
1934

app.json

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,6 @@
1111
"description": "The id for the server you are hosting this bot for.",
1212
"required": true
1313
},
14-
"MODMAIL_GUILD_ID": {
15-
"description": "The ID of the discord server where the threads channels should be created (receiving server). Default to GUILD_ID.",
16-
"required": false
17-
},
1814
"OWNERS": {
1915
"description": "Comma separated user IDs of people that are allowed to use owner only commands. (eval).",
2016
"required": true
@@ -40,4 +36,4 @@
4036
"required": false
4137
}
4238
}
43-
}
39+
}

bot.py

Lines changed: 15 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@
5252

5353
logger = getLogger(__name__)
5454

55-
5655
temp_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "temp")
5756
if not os.path.exists(temp_dir):
5857
os.mkdir(temp_dir)
@@ -84,18 +83,25 @@ def __init__(self):
8483

8584
self.threads = ThreadManager(self)
8685

87-
self.log_file_name = os.path.join(temp_dir, f"{self.token.split('.')[0]}.log")
88-
self._configure_logging()
86+
log_dir = os.path.join(temp_dir, "logs")
87+
if not os.path.exists(log_dir):
88+
os.mkdir(log_dir)
89+
self.log_file_path = os.path.join(log_dir, "modmail.log")
90+
configure_logging(self)
8991

9092
self.plugin_db = PluginDatabaseClient(self) # Deprecated
9193
self.startup()
9294

93-
def get_guild_icon(self, guild: typing.Optional[discord.Guild]) -> str:
95+
def get_guild_icon(
96+
self, guild: typing.Optional[discord.Guild], *, size: typing.Optional[int] = None
97+
) -> str:
9498
if guild is None:
9599
guild = self.guild
96100
if guild.icon is None:
97101
return "https://cdn.discordapp.com/embed/avatars/0.png"
98-
return guild.icon.url
102+
if size is None:
103+
return guild.icon.url
104+
return guild.icon.with_size(size).url
99105

100106
def _resolve_snippet(self, name: str) -> typing.Optional[str]:
101107
"""
@@ -178,29 +184,6 @@ async def load_extensions(self):
178184
logger.exception("Failed to load %s.", cog)
179185
logger.line("debug")
180186

181-
def _configure_logging(self):
182-
level_text = self.config["log_level"].upper()
183-
logging_levels = {
184-
"CRITICAL": logging.CRITICAL,
185-
"ERROR": logging.ERROR,
186-
"WARNING": logging.WARNING,
187-
"INFO": logging.INFO,
188-
"DEBUG": logging.DEBUG,
189-
}
190-
logger.line()
191-
192-
log_level = logging_levels.get(level_text)
193-
if log_level is None:
194-
log_level = self.config.remove("log_level")
195-
logger.warning("Invalid logging level set: %s.", level_text)
196-
logger.warning("Using default logging level: INFO.")
197-
else:
198-
logger.info("Logging level: %s", level_text)
199-
200-
logger.info("Log file: %s", self.log_file_name)
201-
configure_logging(self.log_file_name, log_level)
202-
logger.debug("Successfully configured logging.")
203-
204187
@property
205188
def version(self):
206189
return parse_version(__version__)
@@ -888,7 +871,7 @@ async def process_dm_modmail(self, message: discord.Message) -> None:
888871
return
889872
sent_emoji, blocked_emoji = await self.retrieve_emoji()
890873

891-
if message.type != discord.MessageType.default:
874+
if message.type not in [discord.MessageType.default, discord.MessageType.reply]:
892875
return
893876

894877
thread = await self.threads.find(recipient=message.author)
@@ -912,7 +895,7 @@ async def process_dm_modmail(self, message: discord.Message) -> None:
912895
)
913896
embed.set_footer(
914897
text=self.config["disabled_new_thread_footer"],
915-
icon_url=self.get_guild_icon(guild=message.guild),
898+
icon_url=self.get_guild_icon(guild=message.guild, size=128),
916899
)
917900
logger.info("A new thread was blocked from %s due to disabled Modmail.", message.author)
918901
await self.add_reaction(message, blocked_emoji)
@@ -928,7 +911,7 @@ async def process_dm_modmail(self, message: discord.Message) -> None:
928911
)
929912
embed.set_footer(
930913
text=self.config["disabled_current_thread_footer"],
931-
icon_url=self.get_guild_icon(guild=message.guild),
914+
icon_url=self.get_guild_icon(guild=message.guild, size=128),
932915
)
933916
logger.info("A message was blocked from %s due to disabled Modmail.", message.author)
934917
await self.add_reaction(message, blocked_emoji)
@@ -1335,7 +1318,7 @@ async def handle_react_to_contact(self, payload):
13351318
)
13361319
embed.set_footer(
13371320
text=self.config["disabled_new_thread_footer"],
1338-
icon_url=self.get_guild_icon(guild=channel.guild),
1321+
icon_url=self.get_guild_icon(guild=channel.guild, size=128),
13391322
)
13401323
logger.info(
13411324
"A new thread using react to contact was blocked from %s due to disabled Modmail.",
@@ -1797,16 +1780,6 @@ def main():
17971780
)
17981781
sys.exit(0)
17991782

1800-
# Set up discord.py internal logging
1801-
if os.environ.get("LOG_DISCORD"):
1802-
logger.debug(f"Discord logging enabled: {os.environ['LOG_DISCORD'].upper()}")
1803-
d_logger = logging.getLogger("discord")
1804-
1805-
d_logger.setLevel(os.environ["LOG_DISCORD"].upper())
1806-
handler = logging.FileHandler(filename="discord.log", encoding="utf-8", mode="w")
1807-
handler.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s"))
1808-
d_logger.addHandler(handler)
1809-
18101783
bot = ModmailBot()
18111784
bot.run()
18121785

cogs/modmail.py

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -160,15 +160,15 @@ async def snippet(self, ctx, *, name: str.lower = None):
160160
color=self.bot.error_color, description="You dont have any snippets at the moment."
161161
)
162162
embed.set_footer(text=f'Check "{self.bot.prefix}help snippet add" to add a snippet.')
163-
embed.set_author(name="Snippets", icon_url=self.bot.get_guild_icon(guild=ctx.guild))
163+
embed.set_author(name="Snippets", icon_url=self.bot.get_guild_icon(guild=ctx.guild, size=128))
164164
return await ctx.send(embed=embed)
165165

166166
embeds = []
167167

168168
for i, names in enumerate(zip_longest(*(iter(sorted(self.bot.snippets)),) * 15)):
169169
description = format_description(i, names)
170170
embed = discord.Embed(color=self.bot.main_color, description=description)
171-
embed.set_author(name="Snippets", icon_url=self.bot.get_guild_icon(guild=ctx.guild))
171+
embed.set_author(name="Snippets", icon_url=self.bot.get_guild_icon(guild=ctx.guild, size=128))
172172
embeds.append(embed)
173173

174174
session = EmbedPaginatorSession(ctx, *embeds)
@@ -444,11 +444,9 @@ async def move(self, ctx, *, arguments):
444444
async def send_scheduled_close_message(self, ctx, after, silent=False):
445445
human_delta = human_timedelta(after.dt)
446446

447-
silent = "*silently* " if silent else ""
448-
449447
embed = discord.Embed(
450448
title="Scheduled close",
451-
description=f"This thread will close {silent}{human_delta}.",
449+
description=f"This thread will{' silently' if silent else ''} close in {human_delta}.",
452450
color=self.bot.error_color,
453451
)
454452

@@ -483,7 +481,7 @@ async def close(
483481
484482
Silently close a thread (no message)
485483
- `{prefix}close silently`
486-
- `{prefix}close in 10m silently`
484+
- `{prefix}close silently in 10m`
487485
488486
Stop a thread from closing:
489487
- `{prefix}close cancel`
@@ -1031,7 +1029,7 @@ async def anonadduser(self, ctx, *users_arg: Union[discord.Member, discord.Role,
10311029
name = tag
10321030
avatar_url = self.bot.config["anon_avatar_url"]
10331031
if avatar_url is None:
1034-
avatar_url = self.bot.get_guild_icon(guild=ctx.guild)
1032+
avatar_url = self.bot.get_guild_icon(guild=ctx.guild, size=128)
10351033
em.set_footer(text=name, icon_url=avatar_url)
10361034

10371035
for u in users:
@@ -1120,7 +1118,7 @@ async def anonremoveuser(self, ctx, *users_arg: Union[discord.Member, discord.Ro
11201118
name = tag
11211119
avatar_url = self.bot.config["anon_avatar_url"]
11221120
if avatar_url is None:
1123-
avatar_url = self.bot.get_guild_icon(guild=ctx.guild)
1121+
avatar_url = self.bot.get_guild_icon(guild=ctx.guild, size=128)
11241122
em.set_footer(text=name, icon_url=avatar_url)
11251123

11261124
for u in users:
@@ -1212,6 +1210,28 @@ async def logs_closed_by(self, ctx, *, user: User = None):
12121210
session = EmbedPaginatorSession(ctx, *embeds)
12131211
await session.run()
12141212

1213+
@logs.command(name="key", aliases=["id"])
1214+
@checks.has_permissions(PermissionLevel.SUPPORTER)
1215+
async def logs_key(self, ctx, key: str):
1216+
"""
1217+
Get the log link for the specified log key.
1218+
"""
1219+
icon_url = ctx.author.avatar.url
1220+
1221+
logs = await self.bot.api.find_log_entry(key)
1222+
1223+
if not logs:
1224+
embed = discord.Embed(
1225+
color=self.bot.error_color,
1226+
description=f"Log entry `{key}` not found.",
1227+
)
1228+
return await ctx.send(embed=embed)
1229+
1230+
embeds = self.format_log_embeds(logs, avatar_url=icon_url)
1231+
1232+
session = EmbedPaginatorSession(ctx, *embeds)
1233+
await session.run()
1234+
12151235
@logs.command(name="delete", aliases=["wipe"])
12161236
@checks.has_permissions(PermissionLevel.OWNER)
12171237
async def logs_delete(self, ctx, key_or_link: str):
@@ -1665,13 +1685,7 @@ async def blocked(self, ctx):
16651685
self.bot.blocked_users.pop(str(id_))
16661686
logger.debug("No longer blocked, user %s.", id_)
16671687
continue
1668-
1669-
try:
1670-
user = await self.bot.get_or_fetch_user(int(id_))
1671-
except discord.NotFound:
1672-
users.append((id_, reason))
1673-
else:
1674-
users.append((user.mention, reason))
1688+
users.append((f"<@{id_}>", reason))
16751689

16761690
blocked_roles = list(self.bot.blocked_roles.items())
16771691
for id_, reason in blocked_roles:

cogs/plugins.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,17 @@ async def load_plugin(self, plugin):
264264
logger.error("Plugin load failure: %s", plugin.ext_string, exc_info=True)
265265
raise InvalidPluginError("Cannot load extension, plugin invalid.") from exc
266266

267+
async def unload_plugin(self, plugin: Plugin) -> None:
268+
try:
269+
await self.bot.unload_extension(plugin.ext_string)
270+
except commands.ExtensionError as exc:
271+
raise exc
272+
273+
ext_parent = ".".join(plugin.ext_string.split(".")[:-1])
274+
for module in list(sys.modules.keys()):
275+
if module == ext_parent or module.startswith(ext_parent + "."):
276+
del sys.modules[module]
277+
267278
async def parse_user_input(self, ctx, plugin_name, check_version=False):
268279

269280
if not self.bot.config["enable_plugins"]:
@@ -378,7 +389,7 @@ async def plugins_add(self, ctx, *, plugin_name: str):
378389
logger.warning("Unable to download plugin %s.", plugin, exc_info=True)
379390

380391
embed = discord.Embed(
381-
description=f"Failed to download plugin, check logs for error.\n{type(e)}: {e}",
392+
description=f"Failed to download plugin, check logs for error.\n{type(e).__name__}: {e}",
382393
color=self.bot.error_color,
383394
)
384395

@@ -397,7 +408,7 @@ async def plugins_add(self, ctx, *, plugin_name: str):
397408
logger.warning("Unable to load plugin %s.", plugin, exc_info=True)
398409

399410
embed = discord.Embed(
400-
description=f"Failed to download plugin, check logs for error.\n{type(e)}: {e}",
411+
description=f"Failed to load plugin, check logs for error.\n{type(e).__name__}: {e}",
401412
color=self.bot.error_color,
402413
)
403414

@@ -438,7 +449,7 @@ async def plugins_remove(self, ctx, *, plugin_name: str):
438449

439450
if self.bot.config.get("enable_plugins"):
440451
try:
441-
await self.bot.unload_extension(plugin.ext_string)
452+
await self.unload_plugin(plugin)
442453
self.loaded_plugins.remove(plugin)
443454
except (commands.ExtensionNotLoaded, KeyError):
444455
logger.warning("Plugin was never loaded.")
@@ -480,9 +491,10 @@ async def update_plugin(self, ctx, plugin_name):
480491
await self.download_plugin(plugin, force=True)
481492
if self.bot.config.get("enable_plugins"):
482493
try:
483-
await self.bot.unload_extension(plugin.ext_string)
494+
await self.unload_plugin(plugin)
484495
except commands.ExtensionError:
485496
logger.warning("Plugin unload fail.", exc_info=True)
497+
486498
try:
487499
await self.load_plugin(plugin)
488500
except Exception:
@@ -529,17 +541,20 @@ async def plugins_reset(self, ctx):
529541
for ext in list(self.bot.extensions):
530542
if not ext.startswith("plugins."):
531543
continue
544+
logger.error("Unloading plugin: %s.", ext)
532545
try:
533-
logger.error("Unloading plugin: %s.", ext)
534-
await self.bot.unload_extension(ext)
535-
except Exception:
536-
logger.error("Failed to unload plugin: %s.", ext)
537-
else:
538-
if not self.loaded_plugins:
539-
continue
540546
plugin = next((p for p in self.loaded_plugins if p.ext_string == ext), None)
541547
if plugin:
548+
await self.unload_plugin(plugin)
542549
self.loaded_plugins.remove(plugin)
550+
else:
551+
await self.bot.unload_extension(ext)
552+
except Exception:
553+
logger.error("Failed to unload plugin: %s.", ext)
554+
555+
for module in list(sys.modules.keys()):
556+
if module.startswith("plugins."):
557+
del sys.modules[module]
543558

544559
self.bot.config["plugins"].clear()
545560
await self.bot.config.update()

0 commit comments

Comments
 (0)