Skip to content

Commit f9504c5

Browse files
authored
Merge branch 'development' into anon_reply_without_command
2 parents c399eff + efae942 commit f9504c5

File tree

15 files changed

+2051
-1920
lines changed

15 files changed

+2051
-1920
lines changed

CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,34 @@ 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 does not guarantee a major version bump, see the reasoning [here](https://github.com/kyb3r/modmail/issues/319).
88

9+
10+
# v3.3.0-dev1
11+
12+
### Added
13+
14+
- Two new config vars:
15+
- `ENABLE_PLUGINS` (yes/no default yes), when set to no, plugins will not be loaded into the bot.
16+
- `ERROR_COLOR` (color format, defaults discord red), the color of error messages.
17+
18+
### Changed
19+
20+
- `?contact` no longer send the "thread created" message to where the command is ran, instead, it's now sent to the newly created thread channel. (Thanks to DAzVise)
21+
- Plugins update (mostly internal).
22+
- `git` is no longer used to install plugins, it now downloads through zip files.
23+
- `?plugins enabled` renamed to `?plugins loaded` while `enabled` is still an alias to that command.
24+
- Reorganised plugins folder structure.
25+
- Logging / plugin-related messages changes.
26+
- Updating one plugin will not update all other plugins (plugins are no longer separated by repos, but the plugin name itself).
27+
- Help command is in alphabetical order grouped by permissions.
28+
- Notes are no longer always blurple, its set to `MAIN_COLOR` now.
29+
30+
### Internal
31+
32+
- Reworked `config.get` and `config.set`, it feeds through the converters before setting/getting.
33+
- To get/set the raw value, access through `config[]`.
34+
- Prerelease naming scheme is now `x.x.x-devN`.
35+
- `trigger_typing` has been moved to `core.utils.trigger_typing`, original location is deprecated.
36+
937
# v3.2.2
1038

1139
Security update!

Dockerfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
FROM python:3.7.4-alpine
2-
RUN apk add --no-cache git
32
WORKDIR /modmailbot
43
COPY . /modmailbot
54
RUN pip install --no-cache-dir -r requirements.min.txt

bot.py

Lines changed: 44 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "3.2.2"
1+
__version__ = "3.3.0-dev1"
22

33
import asyncio
44
import logging
@@ -19,6 +19,7 @@
1919
from aiohttp import ClientSession
2020
from emoji import UNICODE_EMOJI
2121
from motor.motor_asyncio import AsyncIOMotorClient
22+
from pkg_resources import parse_version
2223
from pymongo.errors import ConfigurationError
2324

2425
try:
@@ -32,7 +33,7 @@
3233
from core import checks
3334
from core.clients import ApiClient, PluginDatabaseClient
3435
from core.config import ConfigManager
35-
from core.utils import human_join, strtobool, parse_alias
36+
from core.utils import human_join, parse_alias
3637
from core.models import PermissionLevel, ModmailLogger, SafeFormatter
3738
from core.thread import ThreadManager
3839
from core.time import human_timedelta
@@ -70,6 +71,7 @@ def __init__(self):
7071
self._api = None
7172
self.metadata_loop = None
7273
self.formatter = SafeFormatter()
74+
self.loaded_cogs = ["cogs.modmail", "cogs.plugins", "cogs.utility"]
7375

7476
self._connected = asyncio.Event()
7577
self.start_time = datetime.utcnow()
@@ -97,17 +99,7 @@ def __init__(self):
9799
sys.exit(0)
98100

99101
self.plugin_db = PluginDatabaseClient(self)
100-
101-
logger.line()
102-
logger.info("┌┬┐┌─┐┌┬┐┌┬┐┌─┐┬┬")
103-
logger.info("││││ │ │││││├─┤││")
104-
logger.info("┴ ┴└─┘─┴┘┴ ┴┴ ┴┴┴─┘")
105-
logger.info("v%s", __version__)
106-
logger.info("Authors: kyb3r, fourjr, Taaku18")
107-
logger.line()
108-
109-
self._load_extensions()
110-
logger.line()
102+
self.startup()
111103

112104
@property
113105
def uptime(self) -> str:
@@ -123,6 +115,24 @@ def uptime(self) -> str:
123115

124116
return self.formatter.format(fmt, d=days, h=hours, m=minutes, s=seconds)
125117

118+
def startup(self):
119+
logger.line()
120+
logger.info("┌┬┐┌─┐┌┬┐┌┬┐┌─┐┬┬")
121+
logger.info("││││ │ │││││├─┤││")
122+
logger.info("┴ ┴└─┘─┴┘┴ ┴┴ ┴┴┴─┘")
123+
logger.info("v%s", __version__)
124+
logger.info("Authors: kyb3r, fourjr, Taaku18")
125+
logger.line()
126+
127+
for cog in self.loaded_cogs:
128+
logger.info("Loading %s.", cog)
129+
try:
130+
self.load_extension(cog)
131+
logger.info("Successfully loaded %s.", cog)
132+
except Exception:
133+
logger.exception("Failed to load %s.", cog)
134+
logger.line()
135+
126136
def _configure_logging(self):
127137
level_text = self.config["log_level"].upper()
128138
logging_levels = {
@@ -161,8 +171,8 @@ def _configure_logging(self):
161171
logger.debug("Successfully configured logging.")
162172

163173
@property
164-
def version(self) -> str:
165-
return __version__
174+
def version(self):
175+
return parse_version(__version__)
166176

167177
@property
168178
def session(self) -> ClientSession:
@@ -179,18 +189,6 @@ def api(self):
179189
async def get_prefix(self, message=None):
180190
return [self.prefix, f"<@{self.user.id}> ", f"<@!{self.user.id}> "]
181191

182-
def _load_extensions(self):
183-
"""Adds commands automatically"""
184-
for file in os.listdir("cogs"):
185-
if not file.endswith(".py"):
186-
continue
187-
cog = f"cogs.{file[:-3]}"
188-
logger.info("Loading %s.", cog)
189-
try:
190-
self.load_extension(cog)
191-
except Exception:
192-
logger.exception("Failed to load %s.", cog)
193-
194192
def run(self, *args, **kwargs):
195193
try:
196194
self.loop.run_until_complete(self.start(self.token))
@@ -366,25 +364,21 @@ def blocked_whitelisted_users(self) -> typing.List[str]:
366364
def prefix(self) -> str:
367365
return str(self.config["prefix"])
368366

369-
def _parse_color(self, conf_name):
370-
color = self.config[conf_name]
371-
try:
372-
return int(color.lstrip("#"), base=16)
373-
except ValueError:
374-
logger.error("Invalid %s provided.", conf_name)
375-
return int(self.config.remove(conf_name).lstrip("#"), base=16)
376-
377367
@property
378368
def mod_color(self) -> int:
379-
return self._parse_color("mod_color")
369+
return self.config.get("mod_color")
380370

381371
@property
382372
def recipient_color(self) -> int:
383-
return self._parse_color("recipient_color")
373+
return self.config.get("recipient_color")
384374

385375
@property
386376
def main_color(self) -> int:
387-
return self._parse_color("main_color")
377+
return self.config.get("main_color")
378+
379+
@property
380+
def error_color(self) -> int:
381+
return self.config.get("error_color")
388382

389383
def command_perm(self, command_name: str) -> PermissionLevel:
390384
level = self.config["override_command_level"].get(command_name)
@@ -518,7 +512,6 @@ async def on_ready(self):
518512
loop=None,
519513
)
520514
self.metadata_loop.before_loop(self.before_post_metadata)
521-
self.metadata_loop.after_loop(self.after_post_metadata)
522515
self.metadata_loop.start()
523516

524517
async def convert_emoji(self, name: str) -> str:
@@ -574,38 +567,14 @@ async def _process_blocked(self, message: discord.Message) -> bool:
574567

575568
now = datetime.utcnow()
576569

577-
account_age = self.config["account_age"]
578-
guild_age = self.config["guild_age"]
570+
account_age = self.config.get("account_age")
571+
guild_age = self.config.get("guild_age")
579572

580573
if account_age is None:
581574
account_age = isodate.Duration()
582575
if guild_age is None:
583576
guild_age = isodate.Duration()
584577

585-
if not isinstance(account_age, isodate.Duration):
586-
try:
587-
account_age = isodate.parse_duration(account_age)
588-
except isodate.ISO8601Error:
589-
logger.warning(
590-
"The account age limit needs to be a "
591-
"ISO-8601 duration formatted duration string "
592-
'greater than 0 days, not "%s".',
593-
str(account_age),
594-
)
595-
account_age = self.config.remove("account_age")
596-
597-
if not isinstance(guild_age, isodate.Duration):
598-
try:
599-
guild_age = isodate.parse_duration(guild_age)
600-
except isodate.ISO8601Error:
601-
logger.warning(
602-
"The guild join age limit needs to be a "
603-
"ISO-8601 duration formatted duration string "
604-
'greater than 0 days, not "%s".',
605-
str(guild_age),
606-
)
607-
guild_age = self.config.remove("guild_age")
608-
609578
reason = self.blocked_users.get(str(message.author.id)) or ""
610579
min_guild_age = min_account_age = now
611580

@@ -643,7 +612,7 @@ async def _process_blocked(self, message: discord.Message) -> bool:
643612
title="Message not sent!",
644613
description=f"Your must wait for {delta} "
645614
f"before you can contact me.",
646-
color=discord.Color.red(),
615+
color=self.error_color,
647616
)
648617
)
649618

@@ -667,7 +636,7 @@ async def _process_blocked(self, message: discord.Message) -> bool:
667636
title="Message not sent!",
668637
description=f"Your must wait for {delta} "
669638
f"before you can contact me.",
670-
color=discord.Color.red(),
639+
color=self.error_color,
671640
)
672641
)
673642

@@ -898,23 +867,15 @@ async def _void(*_args, **_kwargs):
898867
pass
899868

900869
if isinstance(channel, discord.DMChannel):
901-
try:
902-
user_typing = strtobool(self.config["user_typing"])
903-
except ValueError:
904-
user_typing = self.config.remove("user_typing")
905-
if not user_typing:
870+
if not self.config.get("user_typing"):
906871
return
907872

908873
thread = await self.threads.find(recipient=user)
909874

910875
if thread:
911876
await thread.channel.trigger_typing()
912877
else:
913-
try:
914-
mod_typing = strtobool(self.config["mod_typing"])
915-
except ValueError:
916-
mod_typing = self.config.remove("mod_typing")
917-
if not mod_typing:
878+
if not self.config.get("mod_typing"):
918879
return
919880

920881
thread = await self.threads.find(channel=channel)
@@ -952,15 +913,7 @@ async def on_raw_reaction_add(self, payload):
952913

953914
if isinstance(channel, discord.DMChannel):
954915
if str(reaction) == str(close_emoji): # closing thread
955-
try:
956-
recipient_thread_close = strtobool(
957-
self.config["recipient_thread_close"]
958-
)
959-
except ValueError:
960-
recipient_thread_close = self.config.remove(
961-
"recipient_thread_close"
962-
)
963-
if not recipient_thread_close:
916+
if not self.config.get("recipient_thread_close"):
964917
return
965918
thread = await self.threads.find(recipient=user)
966919
ts = message.embeds[0].timestamp if message.embeds else None
@@ -1018,8 +971,7 @@ async def on_member_remove(self, member):
1018971
thread = await self.threads.find(recipient=member)
1019972
if thread:
1020973
embed = discord.Embed(
1021-
description="The recipient has left the server.",
1022-
color=discord.Color.red(),
974+
description="The recipient has left the server.", color=self.error_color
1023975
)
1024976
await thread.channel.send(embed=embed)
1025977

@@ -1077,15 +1029,13 @@ async def on_command_error(self, context, exception):
10771029
)
10781030
await context.trigger_typing()
10791031
await context.send(
1080-
embed=discord.Embed(color=discord.Color.red(), description=msg)
1032+
embed=discord.Embed(color=self.error_color, description=msg)
10811033
)
10821034

10831035
elif isinstance(exception, commands.BadArgument):
10841036
await context.trigger_typing()
10851037
await context.send(
1086-
embed=discord.Embed(
1087-
color=discord.Color.red(), description=str(exception)
1088-
)
1038+
embed=discord.Embed(color=self.error_color, description=str(exception))
10891039
)
10901040
elif isinstance(exception, commands.CommandNotFound):
10911041
logger.warning("CommandNotFound: %s", exception)
@@ -1097,7 +1047,7 @@ async def on_command_error(self, context, exception):
10971047
if hasattr(check, "fail_msg"):
10981048
await context.send(
10991049
embed=discord.Embed(
1100-
color=discord.Color.red(), description=check.fail_msg
1050+
color=self.error_color, description=check.fail_msg
11011051
)
11021052
)
11031053
if hasattr(check, "permission_level"):
@@ -1157,7 +1107,7 @@ async def post_metadata(self):
11571107
"member_count": len(self.guild.members),
11581108
"uptime": (datetime.utcnow() - self.start_time).total_seconds(),
11591109
"latency": f"{self.ws.latency * 1000:.4f}",
1160-
"version": self.version,
1110+
"version": str(self.version),
11611111
"selfhosted": True,
11621112
"last_updated": str(datetime.utcnow()),
11631113
}
@@ -1172,10 +1122,6 @@ async def before_post_metadata(self):
11721122
if not self.guild:
11731123
self.metadata_loop.cancel()
11741124

1175-
@staticmethod
1176-
async def after_post_metadata():
1177-
logger.info("Metadata loop has been cancelled.")
1178-
11791125

11801126
if __name__ == "__main__":
11811127
try:

0 commit comments

Comments
 (0)