Skip to content

Commit

Permalink
Merge branch 'development' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
Taaku18 authored Jul 15, 2023
2 parents e75582f + 77fbb69 commit 26202b2
Show file tree
Hide file tree
Showing 13 changed files with 334 additions and 194 deletions.
1 change: 0 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
TOKEN=MyBotToken
LOG_URL=https://logviewername.herokuapp.com/
GUILD_ID=1234567890
MODMAIL_GUILD_ID=1234567890
OWNERS=Owner1ID,Owner2ID,Owner3ID
CONNECTION_URI=mongodb+srv://mongodburi
19 changes: 17 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,29 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
This project mostly adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html);
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.


# [UNRELEASED]

### Fixed
- `?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))
- 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))
- Reply not being forwarded from DM. (PR [#3239](https://github.com/modmail-dev/modmail/pull/3239))
- Cleanup imports after removing/unloading a plugin. ([PR #3226](https://github.com/modmail-dev/Modmail/pull/3226))
- 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))
- 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))

### Added
- New .env config option: `REGISTRY_PLUGINS_ONLY`, restricts to only allow adding registry plugins. ([PR #3247](https://github.com/modmail-dev/modmail/pull/3247))
- `?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))
- `REGISTRY_PLUGINS_ONLY`, environment variable, when set, restricts to only allow adding registry plugins. ([PR #3247](https://github.com/modmail-dev/modmail/pull/3247))
- `DISCORD_LOG_LEVEL` environment variable to set the log level of discord.py. ([PR #3216](https://github.com/modmail-dev/Modmail/pull/3216))

### Changed
- Repo moved to https://github.com/modmail-dev/modmail.
- 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))
- Discord.py internal logging is now enabled by default. ([PR #3216](https://github.com/modmail-dev/Modmail/pull/3216))
- The confirm-thread-creation dialog now uses buttons instead of reactions. ([PR #3273](https://github.com/modmail-dev/Modmail/pull/3273))

### Internal
- 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))

# v4.0.2

Expand Down
6 changes: 1 addition & 5 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@
"description": "The id for the server you are hosting this bot for.",
"required": true
},
"MODMAIL_GUILD_ID": {
"description": "The ID of the discord server where the threads channels should be created (receiving server). Default to GUILD_ID.",
"required": false
},
"OWNERS": {
"description": "Comma separated user IDs of people that are allowed to use owner only commands. (eval).",
"required": true
Expand All @@ -40,4 +36,4 @@
"required": false
}
}
}
}
57 changes: 15 additions & 42 deletions bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@

logger = getLogger(__name__)


temp_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "temp")
if not os.path.exists(temp_dir):
os.mkdir(temp_dir)
Expand Down Expand Up @@ -84,18 +83,25 @@ def __init__(self):

self.threads = ThreadManager(self)

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

self.plugin_db = PluginDatabaseClient(self) # Deprecated
self.startup()

def get_guild_icon(self, guild: typing.Optional[discord.Guild]) -> str:
def get_guild_icon(
self, guild: typing.Optional[discord.Guild], *, size: typing.Optional[int] = None
) -> str:
if guild is None:
guild = self.guild
if guild.icon is None:
return "https://cdn.discordapp.com/embed/avatars/0.png"
return guild.icon.url
if size is None:
return guild.icon.url
return guild.icon.with_size(size).url

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

def _configure_logging(self):
level_text = self.config["log_level"].upper()
logging_levels = {
"CRITICAL": logging.CRITICAL,
"ERROR": logging.ERROR,
"WARNING": logging.WARNING,
"INFO": logging.INFO,
"DEBUG": logging.DEBUG,
}
logger.line()

log_level = logging_levels.get(level_text)
if log_level is None:
log_level = self.config.remove("log_level")
logger.warning("Invalid logging level set: %s.", level_text)
logger.warning("Using default logging level: INFO.")
else:
logger.info("Logging level: %s", level_text)

logger.info("Log file: %s", self.log_file_name)
configure_logging(self.log_file_name, log_level)
logger.debug("Successfully configured logging.")

@property
def version(self):
return parse_version(__version__)
Expand Down Expand Up @@ -888,7 +871,7 @@ async def process_dm_modmail(self, message: discord.Message) -> None:
return
sent_emoji, blocked_emoji = await self.retrieve_emoji()

if message.type != discord.MessageType.default:
if message.type not in [discord.MessageType.default, discord.MessageType.reply]:
return

thread = await self.threads.find(recipient=message.author)
Expand All @@ -912,7 +895,7 @@ async def process_dm_modmail(self, message: discord.Message) -> None:
)
embed.set_footer(
text=self.config["disabled_new_thread_footer"],
icon_url=self.get_guild_icon(guild=message.guild),
icon_url=self.get_guild_icon(guild=message.guild, size=128),
)
logger.info("A new thread was blocked from %s due to disabled Modmail.", message.author)
await self.add_reaction(message, blocked_emoji)
Expand All @@ -928,7 +911,7 @@ async def process_dm_modmail(self, message: discord.Message) -> None:
)
embed.set_footer(
text=self.config["disabled_current_thread_footer"],
icon_url=self.get_guild_icon(guild=message.guild),
icon_url=self.get_guild_icon(guild=message.guild, size=128),
)
logger.info("A message was blocked from %s due to disabled Modmail.", message.author)
await self.add_reaction(message, blocked_emoji)
Expand Down Expand Up @@ -1335,7 +1318,7 @@ async def handle_react_to_contact(self, payload):
)
embed.set_footer(
text=self.config["disabled_new_thread_footer"],
icon_url=self.get_guild_icon(guild=channel.guild),
icon_url=self.get_guild_icon(guild=channel.guild, size=128),
)
logger.info(
"A new thread using react to contact was blocked from %s due to disabled Modmail.",
Expand Down Expand Up @@ -1797,16 +1780,6 @@ def main():
)
sys.exit(0)

# Set up discord.py internal logging
if os.environ.get("LOG_DISCORD"):
logger.debug(f"Discord logging enabled: {os.environ['LOG_DISCORD'].upper()}")
d_logger = logging.getLogger("discord")

d_logger.setLevel(os.environ["LOG_DISCORD"].upper())
handler = logging.FileHandler(filename="discord.log", encoding="utf-8", mode="w")
handler.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s"))
d_logger.addHandler(handler)

bot = ModmailBot()
bot.run()

Expand Down
44 changes: 29 additions & 15 deletions cogs/modmail.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,15 +160,15 @@ async def snippet(self, ctx, *, name: str.lower = None):
color=self.bot.error_color, description="You dont have any snippets at the moment."
)
embed.set_footer(text=f'Check "{self.bot.prefix}help snippet add" to add a snippet.')
embed.set_author(name="Snippets", icon_url=self.bot.get_guild_icon(guild=ctx.guild))
embed.set_author(name="Snippets", icon_url=self.bot.get_guild_icon(guild=ctx.guild, size=128))
return await ctx.send(embed=embed)

embeds = []

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

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

silent = "*silently* " if silent else ""

embed = discord.Embed(
title="Scheduled close",
description=f"This thread will close {silent}{human_delta}.",
description=f"This thread will{' silently' if silent else ''} close in {human_delta}.",
color=self.bot.error_color,
)

Expand Down Expand Up @@ -483,7 +481,7 @@ async def close(
Silently close a thread (no message)
- `{prefix}close silently`
- `{prefix}close in 10m silently`
- `{prefix}close silently in 10m`
Stop a thread from closing:
- `{prefix}close cancel`
Expand Down Expand Up @@ -1031,7 +1029,7 @@ async def anonadduser(self, ctx, *users_arg: Union[discord.Member, discord.Role,
name = tag
avatar_url = self.bot.config["anon_avatar_url"]
if avatar_url is None:
avatar_url = self.bot.get_guild_icon(guild=ctx.guild)
avatar_url = self.bot.get_guild_icon(guild=ctx.guild, size=128)
em.set_footer(text=name, icon_url=avatar_url)

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

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

@logs.command(name="key", aliases=["id"])
@checks.has_permissions(PermissionLevel.SUPPORTER)
async def logs_key(self, ctx, key: str):
"""
Get the log link for the specified log key.
"""
icon_url = ctx.author.avatar.url

logs = await self.bot.api.find_log_entry(key)

if not logs:
embed = discord.Embed(
color=self.bot.error_color,
description=f"Log entry `{key}` not found.",
)
return await ctx.send(embed=embed)

embeds = self.format_log_embeds(logs, avatar_url=icon_url)

session = EmbedPaginatorSession(ctx, *embeds)
await session.run()

@logs.command(name="delete", aliases=["wipe"])
@checks.has_permissions(PermissionLevel.OWNER)
async def logs_delete(self, ctx, key_or_link: str):
Expand Down Expand Up @@ -1665,13 +1685,7 @@ async def blocked(self, ctx):
self.bot.blocked_users.pop(str(id_))
logger.debug("No longer blocked, user %s.", id_)
continue

try:
user = await self.bot.get_or_fetch_user(int(id_))
except discord.NotFound:
users.append((id_, reason))
else:
users.append((user.mention, reason))
users.append((f"<@{id_}>", reason))

blocked_roles = list(self.bot.blocked_roles.items())
for id_, reason in blocked_roles:
Expand Down
37 changes: 26 additions & 11 deletions cogs/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,17 @@ async def load_plugin(self, plugin):
logger.error("Plugin load failure: %s", plugin.ext_string, exc_info=True)
raise InvalidPluginError("Cannot load extension, plugin invalid.") from exc

async def unload_plugin(self, plugin: Plugin) -> None:
try:
await self.bot.unload_extension(plugin.ext_string)
except commands.ExtensionError as exc:
raise exc

ext_parent = ".".join(plugin.ext_string.split(".")[:-1])
for module in list(sys.modules.keys()):
if module == ext_parent or module.startswith(ext_parent + "."):
del sys.modules[module]

async def parse_user_input(self, ctx, plugin_name, check_version=False):

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

embed = discord.Embed(
description=f"Failed to download plugin, check logs for error.\n{type(e)}: {e}",
description=f"Failed to download plugin, check logs for error.\n{type(e).__name__}: {e}",
color=self.bot.error_color,
)

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

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

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

if self.bot.config.get("enable_plugins"):
try:
await self.bot.unload_extension(plugin.ext_string)
await self.unload_plugin(plugin)
self.loaded_plugins.remove(plugin)
except (commands.ExtensionNotLoaded, KeyError):
logger.warning("Plugin was never loaded.")
Expand Down Expand Up @@ -480,9 +491,10 @@ async def update_plugin(self, ctx, plugin_name):
await self.download_plugin(plugin, force=True)
if self.bot.config.get("enable_plugins"):
try:
await self.bot.unload_extension(plugin.ext_string)
await self.unload_plugin(plugin)
except commands.ExtensionError:
logger.warning("Plugin unload fail.", exc_info=True)

try:
await self.load_plugin(plugin)
except Exception:
Expand Down Expand Up @@ -529,17 +541,20 @@ async def plugins_reset(self, ctx):
for ext in list(self.bot.extensions):
if not ext.startswith("plugins."):
continue
logger.error("Unloading plugin: %s.", ext)
try:
logger.error("Unloading plugin: %s.", ext)
await self.bot.unload_extension(ext)
except Exception:
logger.error("Failed to unload plugin: %s.", ext)
else:
if not self.loaded_plugins:
continue
plugin = next((p for p in self.loaded_plugins if p.ext_string == ext), None)
if plugin:
await self.unload_plugin(plugin)
self.loaded_plugins.remove(plugin)
else:
await self.bot.unload_extension(ext)
except Exception:
logger.error("Failed to unload plugin: %s.", ext)

for module in list(sys.modules.keys()):
if module.startswith("plugins."):
del sys.modules[module]

self.bot.config["plugins"].clear()
await self.bot.config.update()
Expand Down
Loading

0 comments on commit 26202b2

Please sign in to comment.