From fc086bf731427846486cfa978c2178c68aba878e Mon Sep 17 00:00:00 2001 From: lorenzo132 <50767078+lorenzo132@users.noreply.github.com> Date: Tue, 24 Nov 2020 15:14:16 +0100 Subject: [PATCH 001/209] Update utility.py added new competing bot-presence to the help list --- cogs/utility.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cogs/utility.py b/cogs/utility.py index 03a90efe54..ee61509dc8 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -506,6 +506,7 @@ async def activity(self, ctx, activity_type: str.lower, *, message: str = ""): - `streaming` - `listening` - `watching` + - `competing` When activity type is set to `listening`, it must be followed by a "to": "listening to..." From 63461bbb276fe4f160d6da545fec7fd62d7338d5 Mon Sep 17 00:00:00 2001 From: fourjr <28086837+fourjr@users.noreply.github.com> Date: Thu, 26 Nov 2020 18:22:58 +0800 Subject: [PATCH 002/209] add update_notifications, resolves #2896 --- CHANGELOG.md | 10 ++++++++++ bot.py | 13 +++++++++---- core/config.py | 3 +++ core/config_help.json | 13 ++++++++++++- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab7f5adc6f..73767c86bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. +# v3.7.14-dev0 + +### Fixed + +- Mentioned `competing` as an activity type ([PR #2902](https://github.com/kyb3r/modmail/pull/2902)) + +### Added + +- `update_notifications` configuration option to toggle bot autoupdate notifications ([GH #2896](https://github.com/kyb3r/modmail/issues/2896)) + # v3.7.13 ### Fixed diff --git a/bot.py b/bot.py index f670e7e76d..e46d50f73b 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.7.13" +__version__ = "3.7.14-dev0" import asyncio @@ -1502,7 +1502,8 @@ async def autoupdate(self): ) logger.info("Bot has been updated.") channel = self.log_channel - await channel.send(embed=embed) + if self.bot.config["update_notifications"]: + await channel.send(embed=embed) else: try: # update fork if gh_token exists @@ -1527,14 +1528,18 @@ async def autoupdate(self): channel = self.update_channel if self.hosting_method == HostingMethod.PM2: embed = discord.Embed(title="Bot has been updated", color=self.main_color) - await channel.send(embed=embed) + embed.set_footer(text=f"Updating Modmail v{self.version} " f"-> v{latest.version}") + if self.bot.config["update_notifications"]: + await channel.send(embed=embed) else: embed = discord.Embed( title="Bot has been updated and is logging out.", description="If you do not have an auto-restart setup, please manually start the bot.", color=self.main_color, ) - await channel.send(embed=embed) + embed.set_footer(text=f"Updating Modmail v{self.version} " f"-> v{latest.version}") + if self.bot.config["update_notifications"]: + await channel.send(embed=embed) await self.logout() async def before_autoupdate(self): diff --git a/core/config.py b/core/config.py index 888275c1fb..8317ed7df6 100644 --- a/core/config.py +++ b/core/config.py @@ -44,6 +44,8 @@ class ConfigManager: "log_channel_id": None, "mention_channel_id": None, "update_channel_id": None, + # updates + "update_notifications": True, # threads "sent_emoji": "✅", "blocked_emoji": "🚫", @@ -171,6 +173,7 @@ class ConfigManager: "data_collection", "enable_eval", "disable_autoupdates", + "update_notifications", "thread_contact_silently", } diff --git a/core/config_help.json b/core/config_help.json index 549e3d9486..815d2a6b17 100644 --- a/core/config_help.json +++ b/core/config_help.json @@ -192,10 +192,21 @@ "`{prefix}config set update_channel_id 9234932582312` (9234932582312 is the channel ID)" ], "notes": [ - "This has no effect unless `disable_autoupdates` is set to no.", + "This has no effect unless `disable_autoupdates` is set to no and `update_notifications` is set to yes.", "See also: `log_channel_id`" ] }, + "update_notifications": { + "default": "Yes", + "description": "This is the channel where update notifications are sent to.", + "examples": [ + "`{prefix}config set update_notifications no" + ], + "notes": [ + "This has no effect unless `disable_autoupdates` is set to no.", + "See also: `update_channel_id`" + ] + }, "sent_emoji": { "default": "✅", "description": "This is the emoji added to the message when when a Modmail action is invoked successfully (ie. DM Modmail, edit message, etc.).", From a97119d160942f67b90198a610a8c1f908eb8a5c Mon Sep 17 00:00:00 2001 From: fourjr <28086837+fourjr@users.noreply.github.com> Date: Thu, 26 Nov 2020 18:35:48 +0800 Subject: [PATCH 003/209] Added command validation to autotrigger --- CHANGELOG.md | 12 ++++++---- bot.py | 9 +++++--- cogs/utility.py | 60 +++++++++++++++++++++++++++++++++++++------------ 3 files changed, 60 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73767c86bb..ae277a9b61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,13 +8,17 @@ however, insignificant breaking changes do not guarantee a major version bump, s # v3.7.14-dev0 -### Fixed +### Added -- Mentioned `competing` as an activity type ([PR #2902](https://github.com/kyb3r/modmail/pull/2902)) +- `update_notifications` configuration option to toggle bot autoupdate notifications. ([GH #2896](https://github.com/kyb3r/modmail/issues/2896)) -### Added +### Improved + +- Added command validation to `autotrigger add/edit`. + +### Fixed -- `update_notifications` configuration option to toggle bot autoupdate notifications ([GH #2896](https://github.com/kyb3r/modmail/issues/2896)) +- Mentioned `competing` as an activity type. ([PR #2902](https://github.com/kyb3r/modmail/pull/2902)) # v3.7.13 diff --git a/bot.py b/bot.py index e46d50f73b..c663f2bb84 100644 --- a/bot.py +++ b/bot.py @@ -937,7 +937,6 @@ async def trigger_auto_triggers(self, message, channel, *, cls=commands.Context) invoked_prefix = self.prefix invoker = None - # Check if there is any aliases being called. if self.config.get("use_regex_autotrigger"): trigger = next( filter(lambda x: re.match(x, message.content), self.auto_triggers.keys()) @@ -1528,7 +1527,9 @@ async def autoupdate(self): channel = self.update_channel if self.hosting_method == HostingMethod.PM2: embed = discord.Embed(title="Bot has been updated", color=self.main_color) - embed.set_footer(text=f"Updating Modmail v{self.version} " f"-> v{latest.version}") + embed.set_footer( + text=f"Updating Modmail v{self.version} " f"-> v{latest.version}" + ) if self.bot.config["update_notifications"]: await channel.send(embed=embed) else: @@ -1537,7 +1538,9 @@ async def autoupdate(self): description="If you do not have an auto-restart setup, please manually start the bot.", color=self.main_color, ) - embed.set_footer(text=f"Updating Modmail v{self.version} " f"-> v{latest.version}") + embed.set_footer( + text=f"Updating Modmail v{self.version} " f"-> v{latest.version}" + ) if self.bot.config["update_notifications"]: await channel.send(embed=embed) await self.logout() diff --git a/cogs/utility.py b/cogs/utility.py index ee61509dc8..eb17e9c212 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -1727,14 +1727,30 @@ async def autotrigger_add(self, ctx, keyword, *, command): description=f"Another autotrigger with the same name already exists: `{keyword}`.", ) else: - self.bot.auto_triggers[keyword] = command - await self.bot.config.update() + # command validation + valid = False + split_cmd = command.split(" ") + for n in range(1, len(split_cmd) + 1): + if self.bot.get_command(" ".join(split_cmd[0:n])): + print(self.bot.get_command(" ".join(split_cmd[0:n]))) + valid = True + break + + if valid: + self.bot.auto_triggers[keyword] = command + await self.bot.config.update() - embed = discord.Embed( - title="Success", - color=self.bot.main_color, - description=f"Keyword `{keyword}` has been linked to `{command}`.", - ) + embed = discord.Embed( + title="Success", + color=self.bot.main_color, + description=f"Keyword `{keyword}` has been linked to `{command}`.", + ) + else: + embed = discord.Embed( + title="Error", + color=self.bot.error_color, + description="Invalid command. Note that autotriggers do not work with aliases.", + ) await ctx.send(embed=embed) @@ -1747,14 +1763,30 @@ async def autotrigger_edit(self, ctx, keyword, *, command): keyword, self.bot.auto_triggers.keys(), "Autotrigger" ) else: - self.bot.auto_triggers[keyword] = command - await self.bot.config.update() + # command validation + valid = False + split_cmd = command.split(" ") + for n in range(1, len(split_cmd) + 1): + if self.bot.get_command(" ".join(split_cmd[0:n])): + print(self.bot.get_command(" ".join(split_cmd[0:n]))) + valid = True + break + + if valid: + self.bot.auto_triggers[keyword] = command + await self.bot.config.update() - embed = discord.Embed( - title="Success", - color=self.bot.main_color, - description=f"Keyword `{keyword}` has been linked to `{command}`.", - ) + embed = discord.Embed( + title="Success", + color=self.bot.main_color, + description=f"Keyword `{keyword}` has been linked to `{command}`.", + ) + else: + embed = discord.Embed( + title="Error", + color=self.bot.error_color, + description="Invalid command. Note that autotriggers do not work with aliases.", + ) await ctx.send(embed=embed) From ccde7b0f5740ad945f872404773e614849d36624 Mon Sep 17 00:00:00 2001 From: fourjr <28086837+fourjr@users.noreply.github.com> Date: Thu, 26 Nov 2020 18:45:44 +0800 Subject: [PATCH 004/209] Make use of `git branch --show-current` to retrieve branch --- CHANGELOG.md | 4 ++++ core/changelog.py | 14 +++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae277a9b61..4fe715b883 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,10 @@ however, insignificant breaking changes do not guarantee a major version bump, s - Mentioned `competing` as an activity type. ([PR #2902](https://github.com/kyb3r/modmail/pull/2902)) +### Internal + +- Make use of `git branch --show-current` to retrieve branch insteasd of using prerelease version check. + # v3.7.13 ### Fixed diff --git a/core/changelog.py b/core/changelog.py index 60d0179609..163aa0f340 100644 --- a/core/changelog.py +++ b/core/changelog.py @@ -1,4 +1,6 @@ +import asyncio import re +from subprocess import PIPE from typing import List from discord import Embed @@ -167,7 +169,17 @@ async def from_url(cls, bot, url: str = "") -> "Changelog": Changelog The newly created `Changelog` parsed from the `url`. """ - branch = "master" if not bot.version.is_prerelease else "development" + # get branch via git cli if available + proc = await asyncio.create_subprocess_shell( + "git branch --show-current", stderr=PIPE, stdout=PIPE, + ) + err = await proc.stderr.read() + err = err.decode("utf-8").rstrip() + res = await proc.stdout.read() + branch = res.decode("utf-8").rstrip() + if not branch or err: + branch = "master" if not bot.version.is_prerelease else "development" + url = url or f"https://raw.githubusercontent.com/kyb3r/modmail/{branch}/CHANGELOG.md" async with await bot.session.get(url) as resp: From f335d265c85aafd80d9d6d99721af5a5e20bddfd Mon Sep 17 00:00:00 2001 From: fourjr <28086837+fourjr@users.noreply.github.com> Date: Thu, 26 Nov 2020 18:57:37 +0800 Subject: [PATCH 005/209] Fix bug where level permissions were not being checked if cmd perms were set --- CHANGELOG.md | 1 + core/checks.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fe715b883..a1547479c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ however, insignificant breaking changes do not guarantee a major version bump, s ### Fixed - Mentioned `competing` as an activity type. ([PR #2902](https://github.com/kyb3r/modmail/pull/2902)) +- Level permissions were not checked if command permissions were set. ### Internal diff --git a/core/checks.py b/core/checks.py index 22de08f2ce..b7c56a1a7d 100644 --- a/core/checks.py +++ b/core/checks.py @@ -61,9 +61,10 @@ async def check_permissions(ctx, command_name) -> bool: if command_name in command_permissions: # -1 is for @everyone - return -1 in command_permissions[command_name] or any( + if -1 in command_permissions[command_name] or any( str(check.id) in command_permissions[command_name] for check in checkables - ) + ): + return True level_permissions = ctx.bot.config["level_permissions"] From 8b2bea4e533c11df3b1ac401f95a281549384ed4 Mon Sep 17 00:00:00 2001 From: fourjr <28086837+fourjr@users.noreply.github.com> Date: Thu, 26 Nov 2020 19:01:49 +0800 Subject: [PATCH 006/209] Resolve linting issues --- .bandit_baseline.json | 77 +++++++++++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 25 deletions(-) diff --git a/.bandit_baseline.json b/.bandit_baseline.json index 94fcfd0fc3..28a4e47b9c 100644 --- a/.bandit_baseline.json +++ b/.bandit_baseline.json @@ -1,6 +1,6 @@ { "errors": [], - "generated_at": "2020-11-12T15:17:38Z", + "generated_at": "2020-11-26T11:00:36Z", "metrics": { "./bot.py": { "CONFIDENCE.HIGH": 1.0, @@ -11,7 +11,7 @@ "SEVERITY.LOW": 1.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 1264, + "loc": 1321, "nosec": 0 }, "./cogs/modmail.py": { @@ -23,7 +23,7 @@ "SEVERITY.LOW": 0.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 1280, + "loc": 1273, "nosec": 0 }, "./cogs/plugins.py": { @@ -35,7 +35,7 @@ "SEVERITY.LOW": 1.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 572, + "loc": 578, "nosec": 0 }, "./cogs/utility.py": { @@ -47,7 +47,7 @@ "SEVERITY.LOW": 1.0, "SEVERITY.MEDIUM": 1.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 1710, + "loc": 1755, "nosec": 0 }, "./core/_color_data.py": { @@ -63,15 +63,15 @@ "nosec": 0 }, "./core/changelog.py": { - "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.HIGH": 1.0, "CONFIDENCE.LOW": 0.0, "CONFIDENCE.MEDIUM": 0.0, "CONFIDENCE.UNDEFINED": 0.0, "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, + "SEVERITY.LOW": 1.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 145, + "loc": 155, "nosec": 0 }, "./core/checks.py": { @@ -83,7 +83,7 @@ "SEVERITY.LOW": 0.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 89, + "loc": 90, "nosec": 0 }, "./core/clients.py": { @@ -95,7 +95,7 @@ "SEVERITY.LOW": 1.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 585, + "loc": 587, "nosec": 0 }, "./core/config.py": { @@ -107,7 +107,7 @@ "SEVERITY.LOW": 0.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 327, + "loc": 352, "nosec": 0 }, "./core/decorators.py": { @@ -131,7 +131,7 @@ "SEVERITY.LOW": 0.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 199, + "loc": 202, "nosec": 0 }, "./core/paginator.py": { @@ -155,7 +155,7 @@ "SEVERITY.LOW": 0.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 993, + "loc": 996, "nosec": 0 }, "./core/time.py": { @@ -179,19 +179,31 @@ "SEVERITY.LOW": 0.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 283, + "loc": 282, + "nosec": 0 + }, + "./plugins/kyb3r/modmail-plugins/profanity-filter-master/profanity-filter.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 81, "nosec": 0 }, "_totals": { - "CONFIDENCE.HIGH": 4.0, + "CONFIDENCE.HIGH": 5.0, "CONFIDENCE.LOW": 0.0, "CONFIDENCE.MEDIUM": 1.0, "CONFIDENCE.UNDEFINED": 0.0, "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 4.0, + "SEVERITY.LOW": 5.0, "SEVERITY.MEDIUM": 1.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 8989, + "loc": 9214, "nosec": 0 } }, @@ -226,41 +238,56 @@ "test_name": "blacklist" }, { - "code": "12 from json import JSONDecodeError, loads\n13 from subprocess import PIPE\n14 from textwrap import indent\n", + "code": "13 from json import JSONDecodeError, loads\n14 from subprocess import PIPE\n15 from textwrap import indent\n", "filename": "./cogs/utility.py", "issue_confidence": "HIGH", "issue_severity": "LOW", "issue_text": "Consider possible security implications associated with PIPE module.", - "line_number": 13, + "line_number": 14, "line_range": [ - 13 + 14 ], "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_imports.html#b404-import-subprocess", "test_id": "B404", "test_name": "blacklist" }, { - "code": "1985 try:\n1986 exec(to_compile, env) # pylint: disable=exec-used\n1987 except Exception as exc:\n", + "code": "2039 try:\n2040 exec(to_compile, env) # pylint: disable=exec-used\n2041 except Exception as exc:\n", "filename": "./cogs/utility.py", "issue_confidence": "HIGH", "issue_severity": "MEDIUM", "issue_text": "Use of exec detected.", - "line_number": 1986, + "line_number": 2040, "line_range": [ - 1986 + 2040 ], "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b102_exec_used.html", "test_id": "B102", "test_name": "exec_used" }, { - "code": "68 \n69 def __init__(self, bot, access_token: str = \"\", username: str = \"\", **kwargs):\n70 self.bot = bot\n71 self.session = bot.session\n72 self.headers: dict = None\n73 self.access_token = access_token\n74 self.username = username\n75 self.avatar_url: str = kwargs.pop(\"avatar_url\", \"\")\n76 self.url: str = kwargs.pop(\"url\", \"\")\n77 if self.access_token:\n78 self.headers = {\"Authorization\": \"token \" + str(access_token)}\n79 \n80 async def request(\n", + "code": "2 import re\n3 from subprocess import PIPE\n4 from typing import List\n", + "filename": "./core/changelog.py", + "issue_confidence": "HIGH", + "issue_severity": "LOW", + "issue_text": "Consider possible security implications associated with PIPE module.", + "line_number": 3, + "line_range": [ + 3 + ], + "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_imports.html#b404-import-subprocess", + "test_id": "B404", + "test_name": "blacklist" + }, + { + "code": "67 \n68 def __init__(self, bot, access_token: str = \"\", username: str = \"\", **kwargs):\n69 self.bot = bot\n70 self.session = bot.session\n71 self.headers: dict = None\n72 self.access_token = access_token\n73 self.username = username\n74 self.avatar_url: str = kwargs.pop(\"avatar_url\", \"\")\n75 self.url: str = kwargs.pop(\"url\", \"\")\n76 if self.access_token:\n77 self.headers = {\"Authorization\": \"token \" + str(access_token)}\n78 \n79 @property\n80 def BRANCH(self):\n", "filename": "./core/clients.py", "issue_confidence": "MEDIUM", "issue_severity": "LOW", "issue_text": "Possible hardcoded password: ''", - "line_number": 69, + "line_number": 68, "line_range": [ + 68, 69, 70, 71, From 812494aede7b4d62655fb5c4d20c2ae26cfa54e1 Mon Sep 17 00:00:00 2001 From: fourjr <28086837+fourjr@users.noreply.github.com> Date: Thu, 26 Nov 2020 20:14:54 +0800 Subject: [PATCH 007/209] trim 'in' from competing activity --- CHANGELOG.md | 2 +- bot.py | 2 +- cogs/utility.py | 10 ++++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1547479c9..c8ffdf67c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. -# v3.7.14-dev0 +# v3.7.14-dev1 ### Added diff --git a/bot.py b/bot.py index c663f2bb84..59b0e7341b 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.7.14-dev0" +__version__ = "3.7.14-dev1" import asyncio diff --git a/cogs/utility.py b/cogs/utility.py index eb17e9c212..64f9c679c9 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -511,6 +511,9 @@ async def activity(self, ctx, activity_type: str.lower, *, message: str = ""): When activity type is set to `listening`, it must be followed by a "to": "listening to..." + When activity type is set to `competing`, + it must be followed by a "in": "competing in..." + When activity type is set to `streaming`, you can set the linked twitch page: - `{prefix}config set twitch_url https://www.twitch.tv/somechannel/` @@ -545,6 +548,8 @@ async def activity(self, ctx, activity_type: str.lower, *, message: str = ""): msg = f"Activity set to: {activity.type.name.capitalize()} " if activity.type == ActivityType.listening: msg += f"to {activity.name}." + elif activity.type == ActivityType.competing: + msg += f"in {activity.name}." else: msg += f"{activity.name}." @@ -609,6 +614,11 @@ async def set_presence(self, *, status=None, activity_type=None, activity_messag # The actual message is after listening to [...] # discord automatically add the "to" activity_message = activity_message[3:].strip() + elif activity_type == ActivityType.competing: + if activity_message.lower().startswith("in "): + # The actual message is after listening to [...] + # discord automatically add the "in" + activity_message = activity_message[3:].strip() elif activity_type == ActivityType.streaming: url = self.bot.config["twitch_url"] From f5916be39e5af0731545c8d797913093fe30774d Mon Sep 17 00:00:00 2001 From: fourjr <28086837+fourjr@users.noreply.github.com> Date: Mon, 30 Nov 2020 20:37:41 +0800 Subject: [PATCH 008/209] fareply, anonsnippet config, disable_updates config --- CHANGELOG.md | 6 +++++- app.json | 2 +- bot.py | 7 +++++-- cogs/modmail.py | 23 +++++++++++++++++++++++ cogs/utility.py | 1 + core/checks.py | 16 ++++++++++++++++ core/config.py | 4 ++++ core/config_help.json | 9 +++++++++ 8 files changed, 64 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8ffdf67c9..bf908d4bb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,15 +6,19 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. -# v3.7.14-dev1 +# v3.7.14-dev2 ### Added - `update_notifications` configuration option to toggle bot autoupdate notifications. ([GH #2896](https://github.com/kyb3r/modmail/issues/2896)) +- `?fareply`, anonymously reply with variables. +- `anonymous_snippets` config variable to toggle if snippets should be anonymous. ([GH #2905](https://github.com/kyb3r/modmail/issues/2905)) +- `disable_updates` config variable to control if the update command should be disabled or not. ### Improved - Added command validation to `autotrigger add/edit`. +- `GITHUB_TOKEN` is now no longer required in Heroku setups. ### Fixed diff --git a/app.json b/app.json index 326c54273e..66b5c77752 100644 --- a/app.json +++ b/app.json @@ -33,7 +33,7 @@ }, "GITHUB_TOKEN": { "description": "A github personal access token with the repo scope.", - "required": true + "required": false } } } \ No newline at end of file diff --git a/bot.py b/bot.py index 59b0e7341b..e1955ff667 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.7.14-dev1" +__version__ = "3.7.14-dev2" import asyncio @@ -1069,7 +1069,10 @@ async def process_commands(self, message): # Process snippets if cmd in self.snippets: snippet = self.snippets[cmd] - message.content = f"{self.prefix}freply {snippet}" + if self.config["anonymous_snippets"]: + message.content = f"{self.prefix}fareply {snippet}" + else: + message.content = f"{self.prefix}freply {snippet}" ctxs = await self.get_contexts(message) for ctx in ctxs: diff --git a/cogs/modmail.py b/cogs/modmail.py index e71214d335..8ba509078c 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -1,4 +1,5 @@ import asyncio +from operator import truediv import re from datetime import datetime from itertools import zip_longest @@ -835,6 +836,28 @@ async def freply(self, ctx, *, msg: str = ""): async with ctx.typing(): await ctx.thread.reply(ctx.message) + @commands.command(aliases=["formatanonreply"]) + @checks.has_permissions(PermissionLevel.SUPPORTER) + @checks.thread_only() + async def fareply(self, ctx, *, msg: str = ""): + """ + Anonymously reply to a Modmail thread with variables. + + Works just like `{prefix}areply`, however with the addition of three variables: + - `{{channel}}` - the `discord.TextChannel` object + - `{{recipient}}` - the `discord.User` object of the recipient + - `{{author}}` - the `discord.User` object of the author + + Supports attachments and images as well as + automatically embedding image URLs. + """ + msg = self.bot.formatter.format( + msg, channel=ctx.channel, recipient=ctx.thread.recipient, author=ctx.message.author + ) + ctx.message.content = msg + async with ctx.typing(): + await ctx.thread.reply(ctx.message, anonymous=True) + @commands.command(aliases=["anonreply", "anonymousreply"]) @checks.has_permissions(PermissionLevel.SUPPORTER) @checks.thread_only() diff --git a/cogs/utility.py b/cogs/utility.py index 64f9c679c9..401f1aa37c 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -1896,6 +1896,7 @@ async def github(self, ctx): @commands.command() @checks.has_permissions(PermissionLevel.OWNER) @checks.github_token_required(ignore_if_not_heroku=True) + @checks.updates_enabled() @trigger_typing async def update(self, ctx, *, flag: str = ""): """ diff --git a/core/checks.py b/core/checks.py index b7c56a1a7d..1ff2a67126 100644 --- a/core/checks.py +++ b/core/checks.py @@ -121,3 +121,19 @@ async def predicate(ctx): "personal access token from developer settings." ) return commands.check(predicate) + +def updates_enabled(): + """ + A decorator that ensures + updates are enabled + """ + + async def predicate(ctx): + return not ctx.bot.config["disable_updates"] + + predicate.fail_msg = ( + "Updates are disabled on this bot instance. " + "View `?config help disable_updates` for " + "more information." + ) + return commands.check(predicate) diff --git a/core/config.py b/core/config.py index 8317ed7df6..6b827e74c0 100644 --- a/core/config.py +++ b/core/config.py @@ -80,6 +80,7 @@ class ConfigManager: "close_on_leave_reason": "The recipient has left the server.", "alert_on_mention": False, "show_timestamp": True, + "anonymous_snippets": False, # moderation "recipient_color": str(discord.Color.gold()), "mod_color": str(discord.Color.green()), @@ -143,6 +144,7 @@ class ConfigManager: # github access token for private repositories "github_token": None, "disable_autoupdates": False, + "disable_updates": False, # Logging "log_level": "INFO", # data collection @@ -173,8 +175,10 @@ class ConfigManager: "data_collection", "enable_eval", "disable_autoupdates", + "disable_updates", "update_notifications", "thread_contact_silently", + "anonymous_snippets", } enums = { diff --git a/core/config_help.json b/core/config_help.json index 815d2a6b17..506b9e57bd 100644 --- a/core/config_help.json +++ b/core/config_help.json @@ -849,5 +849,14 @@ "notes": [ "This configuration can only to be set through `.env` file or environment (config) variables." ] + }, + "disable_updates": { + "default": "No", + "description": "Controls if the update command should be disabled or not.", + "examples": [ + ], + "notes": [ + "This configuration can only to be set through `.env` file or environment (config) variables." + ] } } From 05b261b8ce87fb264e4fa992e0e769519f90c329 Mon Sep 17 00:00:00 2001 From: fourjr <28086837+fourjr@users.noreply.github.com> Date: Mon, 30 Nov 2020 20:37:41 +0800 Subject: [PATCH 009/209] fareply, anonsnippet config, disable_updates config, resolves #2905 --- CHANGELOG.md | 6 +++++- app.json | 2 +- bot.py | 7 +++++-- cogs/modmail.py | 23 +++++++++++++++++++++++ cogs/utility.py | 1 + core/checks.py | 16 ++++++++++++++++ core/config.py | 4 ++++ core/config_help.json | 9 +++++++++ 8 files changed, 64 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8ffdf67c9..bf908d4bb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,15 +6,19 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. -# v3.7.14-dev1 +# v3.7.14-dev2 ### Added - `update_notifications` configuration option to toggle bot autoupdate notifications. ([GH #2896](https://github.com/kyb3r/modmail/issues/2896)) +- `?fareply`, anonymously reply with variables. +- `anonymous_snippets` config variable to toggle if snippets should be anonymous. ([GH #2905](https://github.com/kyb3r/modmail/issues/2905)) +- `disable_updates` config variable to control if the update command should be disabled or not. ### Improved - Added command validation to `autotrigger add/edit`. +- `GITHUB_TOKEN` is now no longer required in Heroku setups. ### Fixed diff --git a/app.json b/app.json index 326c54273e..66b5c77752 100644 --- a/app.json +++ b/app.json @@ -33,7 +33,7 @@ }, "GITHUB_TOKEN": { "description": "A github personal access token with the repo scope.", - "required": true + "required": false } } } \ No newline at end of file diff --git a/bot.py b/bot.py index 59b0e7341b..e1955ff667 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.7.14-dev1" +__version__ = "3.7.14-dev2" import asyncio @@ -1069,7 +1069,10 @@ async def process_commands(self, message): # Process snippets if cmd in self.snippets: snippet = self.snippets[cmd] - message.content = f"{self.prefix}freply {snippet}" + if self.config["anonymous_snippets"]: + message.content = f"{self.prefix}fareply {snippet}" + else: + message.content = f"{self.prefix}freply {snippet}" ctxs = await self.get_contexts(message) for ctx in ctxs: diff --git a/cogs/modmail.py b/cogs/modmail.py index e71214d335..8ba509078c 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -1,4 +1,5 @@ import asyncio +from operator import truediv import re from datetime import datetime from itertools import zip_longest @@ -835,6 +836,28 @@ async def freply(self, ctx, *, msg: str = ""): async with ctx.typing(): await ctx.thread.reply(ctx.message) + @commands.command(aliases=["formatanonreply"]) + @checks.has_permissions(PermissionLevel.SUPPORTER) + @checks.thread_only() + async def fareply(self, ctx, *, msg: str = ""): + """ + Anonymously reply to a Modmail thread with variables. + + Works just like `{prefix}areply`, however with the addition of three variables: + - `{{channel}}` - the `discord.TextChannel` object + - `{{recipient}}` - the `discord.User` object of the recipient + - `{{author}}` - the `discord.User` object of the author + + Supports attachments and images as well as + automatically embedding image URLs. + """ + msg = self.bot.formatter.format( + msg, channel=ctx.channel, recipient=ctx.thread.recipient, author=ctx.message.author + ) + ctx.message.content = msg + async with ctx.typing(): + await ctx.thread.reply(ctx.message, anonymous=True) + @commands.command(aliases=["anonreply", "anonymousreply"]) @checks.has_permissions(PermissionLevel.SUPPORTER) @checks.thread_only() diff --git a/cogs/utility.py b/cogs/utility.py index 64f9c679c9..401f1aa37c 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -1896,6 +1896,7 @@ async def github(self, ctx): @commands.command() @checks.has_permissions(PermissionLevel.OWNER) @checks.github_token_required(ignore_if_not_heroku=True) + @checks.updates_enabled() @trigger_typing async def update(self, ctx, *, flag: str = ""): """ diff --git a/core/checks.py b/core/checks.py index b7c56a1a7d..1ff2a67126 100644 --- a/core/checks.py +++ b/core/checks.py @@ -121,3 +121,19 @@ async def predicate(ctx): "personal access token from developer settings." ) return commands.check(predicate) + +def updates_enabled(): + """ + A decorator that ensures + updates are enabled + """ + + async def predicate(ctx): + return not ctx.bot.config["disable_updates"] + + predicate.fail_msg = ( + "Updates are disabled on this bot instance. " + "View `?config help disable_updates` for " + "more information." + ) + return commands.check(predicate) diff --git a/core/config.py b/core/config.py index 8317ed7df6..6b827e74c0 100644 --- a/core/config.py +++ b/core/config.py @@ -80,6 +80,7 @@ class ConfigManager: "close_on_leave_reason": "The recipient has left the server.", "alert_on_mention": False, "show_timestamp": True, + "anonymous_snippets": False, # moderation "recipient_color": str(discord.Color.gold()), "mod_color": str(discord.Color.green()), @@ -143,6 +144,7 @@ class ConfigManager: # github access token for private repositories "github_token": None, "disable_autoupdates": False, + "disable_updates": False, # Logging "log_level": "INFO", # data collection @@ -173,8 +175,10 @@ class ConfigManager: "data_collection", "enable_eval", "disable_autoupdates", + "disable_updates", "update_notifications", "thread_contact_silently", + "anonymous_snippets", } enums = { diff --git a/core/config_help.json b/core/config_help.json index 815d2a6b17..506b9e57bd 100644 --- a/core/config_help.json +++ b/core/config_help.json @@ -849,5 +849,14 @@ "notes": [ "This configuration can only to be set through `.env` file or environment (config) variables." ] + }, + "disable_updates": { + "default": "No", + "description": "Controls if the update command should be disabled or not.", + "examples": [ + ], + "notes": [ + "This configuration can only to be set through `.env` file or environment (config) variables." + ] } } From 0eab4f3c2aaa000df923e96ecb3c304f3242635c Mon Sep 17 00:00:00 2001 From: fourjr <28086837+fourjr@users.noreply.github.com> Date: Mon, 30 Nov 2020 20:44:24 +0800 Subject: [PATCH 010/209] lint --- core/checks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/core/checks.py b/core/checks.py index 1ff2a67126..55eb4d4ea8 100644 --- a/core/checks.py +++ b/core/checks.py @@ -122,6 +122,7 @@ async def predicate(ctx): ) return commands.check(predicate) + def updates_enabled(): """ A decorator that ensures From e8be5d1855eaa8c3d2131fca57b632255082ece0 Mon Sep 17 00:00:00 2001 From: fourjr <28086837+fourjr@users.noreply.github.com> Date: Thu, 3 Dec 2020 21:25:48 +0800 Subject: [PATCH 011/209] Support only serverv members intent --- CHANGELOG.md | 1 + bot.py | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf908d4bb8..34a2b416f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ however, insignificant breaking changes do not guarantee a major version bump, s - `?fareply`, anonymously reply with variables. - `anonymous_snippets` config variable to toggle if snippets should be anonymous. ([GH #2905](https://github.com/kyb3r/modmail/issues/2905)) - `disable_updates` config variable to control if the update command should be disabled or not. +- Support for only the "Server Members" intent. ### Improved diff --git a/bot.py b/bot.py index e1955ff667..fa980fe5a3 100644 --- a/bot.py +++ b/bot.py @@ -192,9 +192,17 @@ def run(self, *args, **kwargs): except discord.LoginFailure: logger.critical("Invalid token") except discord.PrivilegedIntentsRequired: - logger.critical( - "Privileged intents are not explicitly granted in the discord developers dashboard." - ) + intents = discord.Intents.default() + intents.members = True + # Try again with members intent + super().__init__(command_prefix=None, intents=intents) # implemented in `get_prefix` + logger.warning("Attempting to login with only the server members privileged intent. Some plugins might not work correctly.") + try: + self.loop.run_until_complete(self.start(self.token)) + except discord.PrivilegedIntentsRequired: + logger.critical( + "Privileged intents are not explicitly granted in the discord developers dashboard." + ) except Exception: logger.critical("Fatal exception", exc_info=True) finally: From d922683283ab47206a5de761ddfd5560f225a4e7 Mon Sep 17 00:00:00 2001 From: fourjr <28086837+fourjr@users.noreply.github.com> Date: Thu, 3 Dec 2020 21:33:33 +0800 Subject: [PATCH 012/209] silent_alert_on_mention, resolve #2907 --- bot.py | 9 +++++++-- core/config.py | 2 ++ core/config_help.json | 15 +++++++++++++-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/bot.py b/bot.py index fa980fe5a3..f51899127e 100644 --- a/bot.py +++ b/bot.py @@ -195,7 +195,7 @@ def run(self, *args, **kwargs): intents = discord.Intents.default() intents.members = True # Try again with members intent - super().__init__(command_prefix=None, intents=intents) # implemented in `get_prefix` + self._connection._intents = intents logger.warning("Attempting to login with only the server members privileged intent. Some plugins might not work correctly.") try: self.loop.run_until_complete(self.start(self.token)) @@ -1060,7 +1060,12 @@ async def on_message(self, message): ) if self.config["show_timestamp"]: em.timestamp = datetime.utcnow() - await self.mention_channel.send(content=self.config["mention"], embed=em) + + if not self.config["silent_alert_on_mention"]: + content = self.config["mention"] + else: + content = "" + await self.mention_channel.send(content=content, embed=em) await self.process_commands(message) diff --git a/core/config.py b/core/config.py index 6b827e74c0..2c88b10d14 100644 --- a/core/config.py +++ b/core/config.py @@ -79,6 +79,7 @@ class ConfigManager: "close_on_leave": False, "close_on_leave_reason": "The recipient has left the server.", "alert_on_mention": False, + "silent_alert_on_mention": False, "show_timestamp": True, "anonymous_snippets": False, # moderation @@ -168,6 +169,7 @@ class ConfigManager: "transfer_reactions", "close_on_leave", "alert_on_mention", + "silent_alert_on_mention", "show_timestamp", "confirm_thread_creation", "use_regex_autotrigger", diff --git a/core/config_help.json b/core/config_help.json index 506b9e57bd..e5fc4c6d17 100644 --- a/core/config_help.json +++ b/core/config_help.json @@ -662,12 +662,23 @@ }, "alert_on_mention": { "default": "No", - "description": "Mentions all mods (mention) in logs channel when bot is mentioned", + "description": "Mentions all mods (mention) in mention channel when bot is mentioned", "examples":[ "`{prefix}config set alert_on_mention yes`" ], "notes": [ - "See also: `mention`" + "See also: `mention`, `mention_channel_id`" + ] + }, + "silent_alert_on_mention": { + "default": "No", + "description": "Send a message in the mention channel without mentioning all mods (mention).", + "examples":[ + "`{prefix}config set alert_on_mention yes`" + ], + "notes": [ + "This has no effect unless `alert_on_mention` is set to yes.", + "See also: `mention`, `mention_channel_id`" ] }, "show_timestamp": { From a318aa43d12d9f8e8ceaa75c66aec45d00dc0f17 Mon Sep 17 00:00:00 2001 From: fourjr <28086837+fourjr@users.noreply.github.com> Date: Thu, 3 Dec 2020 21:35:57 +0800 Subject: [PATCH 013/209] Linting --- CHANGELOG.md | 3 ++- bot.py | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34a2b416f5..f9a7c8afe9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. -# v3.7.14-dev2 +# v3.7.14-dev3 ### Added @@ -14,6 +14,7 @@ however, insignificant breaking changes do not guarantee a major version bump, s - `?fareply`, anonymously reply with variables. - `anonymous_snippets` config variable to toggle if snippets should be anonymous. ([GH #2905](https://github.com/kyb3r/modmail/issues/2905)) - `disable_updates` config variable to control if the update command should be disabled or not. +- `silent_alert_on_mention` to alert mods silently. ([GH #2907](https://github.com/kyb3r/modmail/issues/2907)) - Support for only the "Server Members" intent. ### Improved diff --git a/bot.py b/bot.py index f51899127e..a920011205 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.7.14-dev2" +__version__ = "3.7.14-dev3" import asyncio @@ -196,7 +196,9 @@ def run(self, *args, **kwargs): intents.members = True # Try again with members intent self._connection._intents = intents - logger.warning("Attempting to login with only the server members privileged intent. Some plugins might not work correctly.") + logger.warning( + "Attempting to login with only the server members privileged intent. Some plugins might not work correctly." + ) try: self.loop.run_until_complete(self.start(self.token)) except discord.PrivilegedIntentsRequired: From 460758f9f364b1c416b12bc69a340beb41a4e7db Mon Sep 17 00:00:00 2001 From: fourjr <28086837+fourjr@users.noreply.github.com> Date: Thu, 10 Dec 2020 00:07:34 +0800 Subject: [PATCH 014/209] use re.search not re.match in autotriggers --- CHANGELOG.md | 3 ++- bot.py | 6 +++--- cogs/utility.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9a7c8afe9..a4b3741dd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. -# v3.7.14-dev3 +# v3.7.14-dev4 ### Added @@ -26,6 +26,7 @@ however, insignificant breaking changes do not guarantee a major version bump, s - Mentioned `competing` as an activity type. ([PR #2902](https://github.com/kyb3r/modmail/pull/2902)) - Level permissions were not checked if command permissions were set. +- Regex autotriggers were not working if term was in the middle of strings. ### Internal diff --git a/bot.py b/bot.py index a920011205..c0388dcb3e 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.7.14-dev3" +__version__ = "3.7.14-dev4" import asyncio @@ -949,10 +949,10 @@ async def trigger_auto_triggers(self, message, channel, *, cls=commands.Context) if self.config.get("use_regex_autotrigger"): trigger = next( - filter(lambda x: re.match(x, message.content), self.auto_triggers.keys()) + filter(lambda x: re.search(x, message.content), self.auto_triggers.keys()) ) if trigger: - invoker = re.match(trigger, message.content).group(0) + invoker = re.search(trigger, message.content).group(0) else: trigger = next( filter(lambda x: x.lower() in message.content.lower(), self.auto_triggers.keys()) diff --git a/cogs/utility.py b/cogs/utility.py index 401f1aa37c..93fbc36ad8 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -1829,7 +1829,7 @@ async def autotrigger_test(self, ctx, *, text): """Tests a string against the current autotrigger setup""" for keyword in self.bot.auto_triggers: if self.bot.config.get("use_regex_autotrigger"): - check = re.match(keyword, text) + check = re.search(keyword, text) regex = True else: check = keyword.lower() in text.lower() From b8a8f48dfe602202bf67fc7033aa3c17771ab40b Mon Sep 17 00:00:00 2001 From: fourjr <28086837+fourjr@users.noreply.github.com> Date: Sun, 13 Dec 2020 20:51:53 +0800 Subject: [PATCH 015/209] Add anon snippets into config help --- bot.py | 2 +- core/config_help.json | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/bot.py b/bot.py index c0388dcb3e..aacae88639 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.7.14-dev4" +__version__ = "3.7.14-dev5" import asyncio diff --git a/core/config_help.json b/core/config_help.json index e5fc4c6d17..e6bf3730ab 100644 --- a/core/config_help.json +++ b/core/config_help.json @@ -689,6 +689,16 @@ ], "notes": [] }, + "anonymous_snippets": { + "default": "No", + "description": "Sends snippets anonymously.", + "examples":[ + "`{prefix}config set anonymous_snippets yes`" + ], + "notes": [ + "See also: `anon_avatar_url`, `anon_tag`." + ] + }, "confirm_thread_creation": { "default": "No", "description": "Ensure users confirm that they want to create a new thread", From 6e62ce86b717dd5123baff9fec456c77b579c150 Mon Sep 17 00:00:00 2001 From: fourjr <28086837+fourjr@users.noreply.github.com> Date: Tue, 15 Dec 2020 16:10:42 +0800 Subject: [PATCH 016/209] add Berkand Karadere sponsor --- README.md | 5 +++++ SPONSORS.json | 18 +++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e40426dd1..5741f1db6e 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,11 @@ Special thanks to our sponsors for supporting the project. + + + + + Become a sponsor on [Patreon](https://patreon.com/kyber). ## Plugins diff --git a/SPONSORS.json b/SPONSORS.json index ceed37be23..fcabb119ac 100644 --- a/SPONSORS.json +++ b/SPONSORS.json @@ -26,5 +26,21 @@ } ] } + }, + { + "embed": { + "title": "Berkand Karadere", + "description": "Berkand Karadere is an German Community Manager who integrated new systems into the game industry. He also is hosting and developing web servers and game servers. He also plays American Football for the Dortmund Giants and his journey has just begun.", + "color": 2968248, + "thumbnail": { + "url": "https://i.imgur.com/cs2QEcp.png" + }, + "fields": [ + { + "name": "Discord Server!", + "value": "[**Click here**](https://discord.gg/BanCwptMJV)" + } + ] + } } -] +] \ No newline at end of file From 7ac59e0d841c5bf81e43be667f234c9373c588a7 Mon Sep 17 00:00:00 2001 From: fourjr <28086837+fourjr@users.noreply.github.com> Date: Tue, 15 Dec 2020 23:59:33 +0800 Subject: [PATCH 017/209] `?blocked` now no longers show blocks that have expired. --- CHANGELOG.md | 1 + bot.py | 2 +- cogs/modmail.py | 53 ++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4b3741dd2..8e941cd863 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ however, insignificant breaking changes do not guarantee a major version bump, s - Mentioned `competing` as an activity type. ([PR #2902](https://github.com/kyb3r/modmail/pull/2902)) - Level permissions were not checked if command permissions were set. - Regex autotriggers were not working if term was in the middle of strings. +- `?blocked` now no longers show blocks that have expired. ### Internal diff --git a/bot.py b/bot.py index aacae88639..08a8cec02d 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.7.14-dev5" +__version__ = "3.7.14-dev6" import asyncio diff --git a/cogs/modmail.py b/cogs/modmail.py index 8ba509078c..29a3c79df4 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -1050,8 +1050,30 @@ async def blocked(self, ctx): roles = [] users = [] + now = ctx.message.created_at + + blocked_users = list(self.bot.blocked_users.items()) + for id_, reason in blocked_users: + # parse "reason" and check if block is expired + # etc "blah blah blah... until 2019-10-14T21:12:45.559948." + end_time = re.search(r"until ([^`]+?)\.$", reason) + if end_time is None: + # backwards compat + end_time = re.search(r"%([^%]+?)%", reason) + if end_time is not None: + logger.warning( + r"Deprecated time message for user %s, block and unblock again to update.", + id_, + ) + + if end_time is not None: + after = (datetime.fromisoformat(end_time.group(1)) - now).total_seconds() + if after <= 0: + # No longer blocked + self.bot.blocked_users.pop(str(id_)) + logger.debug("No longer blocked, user %s.", id_) + continue - for id_, reason in self.bot.blocked_users.items(): user = self.bot.get_user(int(id_)) if user: users.append((user.mention, reason)) @@ -1062,7 +1084,28 @@ async def blocked(self, ctx): except discord.NotFound: users.append((id_, reason)) - for id_, reason in self.bot.blocked_roles.items(): + blocked_roles = list(self.bot.blocked_roles.items()) + for id_, reason in blocked_roles: + # parse "reason" and check if block is expired + # etc "blah blah blah... until 2019-10-14T21:12:45.559948." + end_time = re.search(r"until ([^`]+?)\.$", reason) + if end_time is None: + # backwards compat + end_time = re.search(r"%([^%]+?)%", reason) + if end_time is not None: + logger.warning( + r"Deprecated time message for role %s, block and unblock again to update.", + id_, + ) + + if end_time is not None: + after = (datetime.fromisoformat(end_time.group(1)) - now).total_seconds() + if after <= 0: + # No longer blocked + self.bot.blocked_roles.pop(str(id_)) + logger.debug("No longer blocked, role %s.", id_) + continue + role = self.bot.guild.get_role(int(id_)) if role: roles.append((role.mention, reason)) @@ -1175,7 +1218,7 @@ async def block( after: UserFriendlyTime = None, ): """ - Block a user from using Modmail. + Block a user or role from using Modmail. You may choose to set a time as to when the user will automatically be unblocked. @@ -1190,9 +1233,9 @@ async def block( if thread: user_or_role = thread.recipient elif after is None: - raise commands.MissingRequiredArgument(SimpleNamespace(name="user")) + raise commands.MissingRequiredArgument(SimpleNamespace(name="user or role")) else: - raise commands.BadArgument(f'User "{after.arg}" not found.') + raise commands.BadArgument(f'User or role "{after.arg}" not found.') mention = getattr(user_or_role, "mention", f"`{user_or_role.id}`") From b0df2b3ccde968e9c6a2af0932e6d69f49fb2d50 Mon Sep 17 00:00:00 2001 From: lorenzo132 <50767078+lorenzo132@users.noreply.github.com> Date: Wed, 23 Dec 2020 22:19:46 +0100 Subject: [PATCH 018/209] Update utility.py (#2918) Change of domain for hastebin, old domain forwards to a different site now --- cogs/utility.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogs/utility.py b/cogs/utility.py index 03a90efe54..634ca71de9 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -441,7 +441,7 @@ async def debug(self, ctx): async def debug_hastebin(self, ctx): """Posts application-logs to Hastebin.""" - haste_url = os.environ.get("HASTE_URL", "https://hasteb.in") + haste_url = os.environ.get("HASTE_URL", "https://hastebin.cc") log_file_name = self.bot.token.split(".")[0] with open( From b33812159240f9e81bf899b9bc794bfce1b13478 Mon Sep 17 00:00:00 2001 From: fourjr <28086837+fourjr@users.noreply.github.com> Date: Sat, 2 Jan 2021 23:05:42 +0800 Subject: [PATCH 019/209] Blocked roles will no longer trigger an error during unblock --- CHANGELOG.md | 1 + bot.py | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e941cd863..f152053862 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ however, insignificant breaking changes do not guarantee a major version bump, s - Level permissions were not checked if command permissions were set. - Regex autotriggers were not working if term was in the middle of strings. - `?blocked` now no longers show blocks that have expired. +- Blocked roles will no longer trigger an error during unblock ### Internal diff --git a/bot.py b/bot.py index 08a8cec02d..623228bb19 100644 --- a/bot.py +++ b/bot.py @@ -673,18 +673,18 @@ def check_manual_blocked_roles(self, author: discord.Member) -> bool: end_time = re.search(r"%([^%]+?)%", blocked_reason) if end_time is not None: logger.warning( - r"Deprecated time message for user %s, block and unblock again to update.", - author.name, + r"Deprecated time message for role %s, block and unblock again to update.", + r.name, ) if end_time is not None: after = (datetime.fromisoformat(end_time.group(1)) - now).total_seconds() if after <= 0: # No longer blocked - self.blocked_users.pop(str(author.id)) - logger.debug("No longer blocked, user %s.", author.name) + self.blocked_roles.pop(str(r.id)) + logger.debug("No longer blocked, role %s.", r.name) return True - logger.debug("User blocked, user %s.", author.name) + logger.debug("User blocked, role %s.", r.name) return False return True From e31b07dda3e1a35e51c8f3d49c191acee7bd67d2 Mon Sep 17 00:00:00 2001 From: fourjr <28086837+fourjr@users.noreply.github.com> Date: Sat, 2 Jan 2021 23:10:11 +0800 Subject: [PATCH 020/209] Fix custom emojis in confirm_thread_creation_deny, resolves #2916 --- CHANGELOG.md | 3 ++- core/thread.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f152053862..48365b7701 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,8 @@ however, insignificant breaking changes do not guarantee a major version bump, s - Level permissions were not checked if command permissions were set. - Regex autotriggers were not working if term was in the middle of strings. - `?blocked` now no longers show blocks that have expired. -- Blocked roles will no longer trigger an error during unblock +- Blocked roles will no longer trigger an error during unblock. +- Custom emojis are now supported in `confirm_thread_creation_deny`. ([GH #2916](https://github.com/kyb3r/modmail/issues/2916)) ### Internal diff --git a/core/thread.py b/core/thread.py index 5e1c28f15f..b1248bd738 100644 --- a/core/thread.py +++ b/core/thread.py @@ -1201,7 +1201,7 @@ async def create( del self.cache[recipient.id] return thread else: - if r.emoji == deny_emoji: + if str(r.emoji) == deny_emoji: thread.cancelled = True await confirm.remove_reaction(accept_emoji, self.bot.user) From 99c3c99095b3beb09525c501ea7a006f59a16cfd Mon Sep 17 00:00:00 2001 From: fourjr <28086837+fourjr@users.noreply.github.com> Date: Sat, 2 Jan 2021 23:14:11 +0800 Subject: [PATCH 021/209] Finding linked messages in replies work now, resolves #2920 --- CHANGELOG.md | 1 + core/thread.py | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48365b7701..835ab48e02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ however, insignificant breaking changes do not guarantee a major version bump, s - `?blocked` now no longers show blocks that have expired. - Blocked roles will no longer trigger an error during unblock. - Custom emojis are now supported in `confirm_thread_creation_deny`. ([GH #2916](https://github.com/kyb3r/modmail/issues/2916)) +- Finding linked messages in replies work now. ([GH #2920](https://github.com/kyb3r/modmail/issues/2920), [Jerrie-Aries](https://github.com/kyb3r/modmail/issues/2920#issuecomment-751530495)) ### Internal diff --git a/core/thread.py b/core/thread.py index b1248bd738..adb122dd79 100644 --- a/core/thread.py +++ b/core/thread.py @@ -660,8 +660,10 @@ async def delete_message( async def find_linked_message_from_dm(self, message, either_direction=False): if either_direction and message.embeds: compare_url = message.embeds[0].author.url + compare_id = compare_url.split('#')[-1] else: compare_url = None + compare_id = None if self.channel is not None: async for linked_message in self.channel.history(): @@ -679,6 +681,11 @@ async def find_linked_message_from_dm(self, message, either_direction=False): msg_id = int(msg_id) if int(msg_id) == message.id: return linked_message + + if compare_id is not None and compare_id.isdigit(): + if int(msg_id) == int (compare_id): + return linked_message + raise ValueError("Thread channel message not found.") async def edit_dm_message(self, message: discord.Message, content: str) -> None: From 1b24283e674e2fbf6abf9e4880e572b92a31df4b Mon Sep 17 00:00:00 2001 From: fourjr <28086837+fourjr@users.noreply.github.com> Date: Sat, 2 Jan 2021 23:21:20 +0800 Subject: [PATCH 022/209] Up version to v3.8-dev7 --- CHANGELOG.md | 2 +- bot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 835ab48e02..ab29f6922d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. -# v3.7.14-dev4 +# v3.8.0-dev7 ### Added diff --git a/bot.py b/bot.py index 623228bb19..fdd348e269 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.7.14-dev6" +__version__ = "3.8.0-dev7" import asyncio From 528dcdbcfca681a76c45fc293425c584cd83595d Mon Sep 17 00:00:00 2001 From: fourjr <28086837+fourjr@users.noreply.github.com> Date: Sat, 2 Jan 2021 23:29:58 +0800 Subject: [PATCH 023/209] Linting --- core/thread.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/thread.py b/core/thread.py index adb122dd79..71acdb8610 100644 --- a/core/thread.py +++ b/core/thread.py @@ -660,7 +660,7 @@ async def delete_message( async def find_linked_message_from_dm(self, message, either_direction=False): if either_direction and message.embeds: compare_url = message.embeds[0].author.url - compare_id = compare_url.split('#')[-1] + compare_id = compare_url.split("#")[-1] else: compare_url = None compare_id = None @@ -683,7 +683,7 @@ async def find_linked_message_from_dm(self, message, either_direction=False): return linked_message if compare_id is not None and compare_id.isdigit(): - if int(msg_id) == int (compare_id): + if int(msg_id) == int(compare_id): return linked_message raise ValueError("Thread channel message not found.") From fe114483e7d1069ebe76be2cad51d56f9175fad7 Mon Sep 17 00:00:00 2001 From: fourjr <28086837+fourjr@users.noreply.github.com> Date: Sun, 3 Jan 2021 00:02:07 +0800 Subject: [PATCH 024/209] Fix typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab29f6922d..dc5893f651 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ however, insignificant breaking changes do not guarantee a major version bump, s ### Internal -- Make use of `git branch --show-current` to retrieve branch insteasd of using prerelease version check. +- Make use of `git branch --show-current` to retrieve branch instead of using prerelease version check. # v3.7.13 From c2b75dcaf17cad274828e862b86f421c2307d146 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Wed, 6 Jan 2021 21:12:12 +0800 Subject: [PATCH 025/209] Clearer error messages on reply fails. --- CHANGELOG.md | 1 + core/thread.py | 15 ++++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc5893f651..7b736a69ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ however, insignificant breaking changes do not guarantee a major version bump, s - Added command validation to `autotrigger add/edit`. - `GITHUB_TOKEN` is now no longer required in Heroku setups. +- Clearer error messages on reply fails. ### Fixed diff --git a/core/thread.py b/core/thread.py index 71acdb8610..c3918b2daa 100644 --- a/core/thread.py +++ b/core/thread.py @@ -747,16 +747,21 @@ async def reply( anonymous=anonymous, plain=plain, ) - except Exception: + except Exception as e: logger.error("Message delivery failed:", exc_info=True) + if isinstance(e, discord.Forbidden): + description = ("Your message could not be delivered as " + "the recipient is only accepting direct " + "messages from friends, or the bot was " + "blocked by the recipient.") + else: + description = ("Your message could not be delivered due " + "to an unknown error. ") tasks.append( message.channel.send( embed=discord.Embed( color=self.bot.error_color, - description="Your message could not be delivered as " - "the recipient is only accepting direct " - "messages from friends, or the bot was " - "blocked by the recipient.", + description=description, ) ) ) From a6ebd81d3fa40f713b2da188bc7a74fa47db8bcb Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Wed, 6 Jan 2021 21:18:14 +0800 Subject: [PATCH 026/209] Sending files in threads (non-images) now work. resolves #2926 --- CHANGELOG.md | 1 + core/thread.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b736a69ee..70109c97a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ however, insignificant breaking changes do not guarantee a major version bump, s - Blocked roles will no longer trigger an error during unblock. - Custom emojis are now supported in `confirm_thread_creation_deny`. ([GH #2916](https://github.com/kyb3r/modmail/issues/2916)) - Finding linked messages in replies work now. ([GH #2920](https://github.com/kyb3r/modmail/issues/2920), [Jerrie-Aries](https://github.com/kyb3r/modmail/issues/2920#issuecomment-751530495)) +- Sending files in threads (non-images) now work. ([GH #2926](https://github.com/kyb3r/modmail/issues/2926)) ### Internal diff --git a/core/thread.py b/core/thread.py index c3918b2daa..041a601c21 100644 --- a/core/thread.py +++ b/core/thread.py @@ -756,7 +756,8 @@ async def reply( "blocked by the recipient.") else: description = ("Your message could not be delivered due " - "to an unknown error. ") + "to an unknown error. Check `?debug` for " + "more information") tasks.append( message.channel.send( embed=discord.Embed( @@ -933,7 +934,7 @@ async def send( file_upload_count = 1 - for url, filename in attachments: + for url, filename, _ in attachments: embed.add_field( name=f"File upload ({file_upload_count})", value=f"[{filename}]({url})" ) From beb73d8d82960429631c0aef5aea31f4f75f23b1 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Wed, 6 Jan 2021 21:24:18 +0800 Subject: [PATCH 027/209] Deleting messages no longer shows a false error, resolves #2910 --- CHANGELOG.md | 1 + core/thread.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70109c97a6..404869adae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ however, insignificant breaking changes do not guarantee a major version bump, s - Custom emojis are now supported in `confirm_thread_creation_deny`. ([GH #2916](https://github.com/kyb3r/modmail/issues/2916)) - Finding linked messages in replies work now. ([GH #2920](https://github.com/kyb3r/modmail/issues/2920), [Jerrie-Aries](https://github.com/kyb3r/modmail/issues/2920#issuecomment-751530495)) - Sending files in threads (non-images) now work. ([GH #2926](https://github.com/kyb3r/modmail/issues/2926)) +- Deleting messages no longer shows a false error. ([GH #2910](https://github.com/kyb3r/modmail/issues/2910), [Jerrie-Aries](https://github.com/kyb3r/modmail/issues/2910#issuecomment-753557313)) ### Internal diff --git a/core/thread.py b/core/thread.py index 041a601c21..5f630654fe 100644 --- a/core/thread.py +++ b/core/thread.py @@ -650,7 +650,7 @@ async def delete_message( tasks = [] if not isinstance(message, discord.Message): tasks += [message1.delete()] - if message2 is not None: + elif message2 is not None: tasks += [message2.delete()] elif message1.embeds[0].author.name.startswith("Persistent Note"): tasks += [self.bot.api.delete_note(message1.id)] From 1de48fd617996846ae7b776f59e03c287ded1846 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Wed, 6 Jan 2021 21:27:56 +0800 Subject: [PATCH 028/209] Linting --- core/changelog.py | 2 +- core/checks.py | 2 +- core/thread.py | 23 ++++++++++++----------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/core/changelog.py b/core/changelog.py index 163aa0f340..ada9fe6984 100644 --- a/core/changelog.py +++ b/core/changelog.py @@ -91,7 +91,7 @@ def embed(self) -> Embed: """ embed = Embed(color=self.bot.main_color, description=self.description) embed.set_author( - name=f"v{self.version} - Changelog", icon_url=self.bot.user.avatar_url, url=self.url + name=f"v{self.version} - Changelog", icon_url=self.bot.user.avatar_url, url=self.url, ) for name, value in self.fields.items(): diff --git a/core/checks.py b/core/checks.py index 55eb4d4ea8..74b7dc38cd 100644 --- a/core/checks.py +++ b/core/checks.py @@ -5,7 +5,7 @@ logger = getLogger(__name__) -def has_permissions_predicate(permission_level: PermissionLevel = PermissionLevel.REGULAR): +def has_permissions_predicate(permission_level: PermissionLevel = PermissionLevel.REGULAR,): async def predicate(ctx): return await check_permissions(ctx, ctx.command.qualified_name) diff --git a/core/thread.py b/core/thread.py index 5f630654fe..6480160e8b 100644 --- a/core/thread.py +++ b/core/thread.py @@ -750,20 +750,21 @@ async def reply( except Exception as e: logger.error("Message delivery failed:", exc_info=True) if isinstance(e, discord.Forbidden): - description = ("Your message could not be delivered as " - "the recipient is only accepting direct " - "messages from friends, or the bot was " - "blocked by the recipient.") + description = ( + "Your message could not be delivered as " + "the recipient is only accepting direct " + "messages from friends, or the bot was " + "blocked by the recipient." + ) else: - description = ("Your message could not be delivered due " - "to an unknown error. Check `?debug` for " - "more information") + description = ( + "Your message could not be delivered due " + "to an unknown error. Check `?debug` for " + "more information" + ) tasks.append( message.channel.send( - embed=discord.Embed( - color=self.bot.error_color, - description=description, - ) + embed=discord.Embed(color=self.bot.error_color, description=description,) ) ) else: From 6cb26a70dd264edcef95205b0661774df8ba4311 Mon Sep 17 00:00:00 2001 From: Jerrie-Aries <70805800+Jerrie-Aries@users.noreply.github.com> Date: Thu, 7 Jan 2021 12:02:37 +0800 Subject: [PATCH 029/209] Fix `?perms get` command showing IDs instead of user or role mentions. --- cogs/utility.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cogs/utility.py b/cogs/utility.py index 93fbc36ad8..c8a0011a33 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -1453,15 +1453,15 @@ def _get_perm(self, ctx, name, type_): if perm == -1: values.insert(0, "**everyone**") continue - member = ctx.guild.get_member(perm) + member = ctx.guild.get_member(int(perm)) if member is not None: values.append(member.mention) continue - user = self.bot.get_user(perm) + user = self.bot.get_user(int(perm)) if user is not None: values.append(user.mention) continue - role = ctx.guild.get_role(perm) + role = ctx.guild.get_role(int(perm)) if role is not None: values.append(role.mention) else: From baf9a5b7044c9530cfd45412e277ff08ebfeb974 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Fri, 8 Jan 2021 16:18:30 +0800 Subject: [PATCH 030/209] Support discord.py v1.6 & LOTTIE stickers --- CHANGELOG.md | 4 +- Pipfile | 2 +- Pipfile.lock | 350 ++++++++++++++++++++++------------------ bot.py | 4 +- core/thread.py | 24 ++- discord.py-1.5.2.tar.gz | Bin 649426 -> 0 bytes 6 files changed, 217 insertions(+), 167 deletions(-) delete mode 100644 discord.py-1.5.2.tar.gz diff --git a/CHANGELOG.md b/CHANGELOG.md index 404869adae..bbc9a029b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. -# v3.8.0-dev7 +# v3.8.0-dev8 ### Added @@ -34,10 +34,12 @@ however, insignificant breaking changes do not guarantee a major version bump, s - Finding linked messages in replies work now. ([GH #2920](https://github.com/kyb3r/modmail/issues/2920), [Jerrie-Aries](https://github.com/kyb3r/modmail/issues/2920#issuecomment-751530495)) - Sending files in threads (non-images) now work. ([GH #2926](https://github.com/kyb3r/modmail/issues/2926)) - Deleting messages no longer shows a false error. ([GH #2910](https://github.com/kyb3r/modmail/issues/2910), [Jerrie-Aries](https://github.com/kyb3r/modmail/issues/2910#issuecomment-753557313)) +- Display an error on [Lottie](https://airbnb.io/lottie/#/) stickers, instead of failing the send. ### Internal - Make use of `git branch --show-current` to retrieve branch instead of using prerelease version check. +- Use discord.py 1.6.0 from PyPi instead of the development clone. # v3.7.13 diff --git a/Pipfile b/Pipfile index b878d053e9..f9621c3493 100644 --- a/Pipfile +++ b/Pipfile @@ -27,7 +27,7 @@ parsedatetime = "==2.6" aiohttp = ">=3.6.0,<3.7.0" python-dotenv = ">=0.10.3" pipenv = "*" -"discord.py" = {file = "./discord.py-1.5.2.tar.gz"} +"discord.py" = "==1.6.0" [scripts] bot = "python bot.py" diff --git a/Pipfile.lock b/Pipfile.lock index 0eacd81d2c..aaa491011e 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "1f660c7237deeaa50a7098002c0f3c05cce254d6c0ba8bc02993c5400e335a59" + "sha256": "bfe06c9fe1db25178b01413114093b26f0d32e9a90a031c048ff3ddc7b6644b7" }, "pipfile-spec": 6, "requires": {}, @@ -50,6 +50,7 @@ "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3" ], + "markers": "python_full_version >= '3.5.3'", "version": "==3.0.1" }, "attrs": { @@ -57,14 +58,15 @@ "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==20.3.0" }, "certifi": { "hashes": [ - "sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd", - "sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4" + "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", + "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" ], - "version": "==2020.11.8" + "version": "==2020.12.5" }, "chardet": { "hashes": [ @@ -82,7 +84,12 @@ "version": "==0.4.4" }, "discord.py": { - "file": "./discord.py-1.5.2.tar.gz" + "hashes": [ + "sha256:3df148daf6fbcc7ab5b11042368a3cd5f7b730b62f09fb5d3cbceff59bcfbb12", + "sha256:ba8be99ff1b8c616f7b6dcb700460d0222b29d4c11048e74366954c465fdd05f" + ], + "index": "pypi", + "version": "==1.6.0" }, "distlib": { "hashes": [ @@ -115,10 +122,11 @@ }, "idna": { "hashes": [ - "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", - "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" + "sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16", + "sha256:c5b02147e01ea9920e6b0a3f1f7bb833612d507592c837a6c49552768f4054e1" ], - "version": "==2.10" + "markers": "python_version >= '3.4'", + "version": "==3.1" }, "isodate": { "hashes": [ @@ -156,6 +164,7 @@ "sha256:fcfbb44c59af3f8ea984de67ec7c306f618a3ec771c2843804069917a8f2e255", "sha256:feed85993dbdb1dbc29102f50bca65bdc68f2c0c8d352468c25b54874f23c39d" ], + "markers": "python_version >= '3.5'", "version": "==4.7.6" }, "natural": { @@ -175,70 +184,80 @@ }, "pipenv": { "hashes": [ - "sha256:d6ac39d1721517b23aca12cdb4c726dc318ec4d7bdede5c1220bbb81775005c3", - "sha256:dce1fb1a6941f98764c62b00010f52143aed19e2fcd8f100aff4fb3bb1bbbbe3" + "sha256:4ab2f60742184d851ac44b9e1d423afe71dc2ea7a68bde07eb890c8b4ce5a420", + "sha256:8253fe6f9cfb3791a54da8a0571f73c918cb3457dd908684c1800a13a06ec4c1" ], "index": "pypi", - "version": "==2020.11.4" + "version": "==2020.11.15" }, "pymongo": { "hashes": [ - "sha256:03dc64a9aa7a5d405aea5c56db95835f6a2fa31b3502c5af1760e0e99210be30", - "sha256:05fcc6f9c60e6efe5219fbb5a30258adb3d3e5cbd317068f3d73c09727f2abb6", - "sha256:076a7f2f7c251635cf6116ac8e45eefac77758ee5a77ab7bd2f63999e957613b", - "sha256:137e6fa718c7eff270dbd2fc4b90d94b1a69c9e9eb3f3de9e850a7fd33c822dc", - "sha256:1f865b1d1c191d785106f54df9abdc7d2f45a946b45fd1ea0a641b4f982a2a77", - "sha256:213c445fe7e654621c6309e874627c35354b46ef3ee807f5a1927dc4b30e1a67", - "sha256:25e617daf47d8dfd4e152c880cd0741cbdb48e51f54b8de9ddbfe74ecd87dd16", - "sha256:3d9bb1ba935a90ec4809a8031efd988bdb13cdba05d9e9a3e9bf151bf759ecde", - "sha256:40696a9a53faa7d85aaa6fd7bef1cae08f7882640bad08c350fb59dee7ad069b", - "sha256:421aa1b92c291c429668bd8d8d8ec2bd00f183483a756928e3afbf2b6f941f00", - "sha256:4437300eb3a5e9cc1a73b07d22c77302f872f339caca97e9bf8cf45eca8fa0d2", - "sha256:455f4deb00158d5ec8b1d3092df6abb681b225774ab8a59b3510293b4c8530e3", - "sha256:475a34a0745c456ceffaec4ce86b7e0983478f1b6140890dff7b161e7bcd895b", - "sha256:4797c0080f41eba90404335e5ded3aa66731d303293a675ff097ce4ea3025bb9", - "sha256:4ae23fbbe9eadf61279a26eba866bbf161a6f7e2ffad14a42cf20e9cb8e94166", - "sha256:4b32744901ee9990aa8cd488ec85634f443526def1e5190a407dc107148249d7", - "sha256:50127b13b38e8e586d5e97d342689405edbd74ad0bd891d97ee126a8c7b6e45f", - "sha256:50531caa7b4be1c4ed5e2d5793a4e51cc9bd62a919a6fd3299ef7c902e206eab", - "sha256:63a5387e496a98170ffe638b435c0832c0f2011a6f4ff7a2880f17669fff8c03", - "sha256:68220b81850de8e966d4667d5c325a96c6ac0d6adb3d18935d6e3d325d441f48", - "sha256:689142dc0c150e9cb7c012d84cac2c346d40beb891323afb6caf18ec4caafae0", - "sha256:6a15e2bee5c4188369a87ed6f02de804651152634a46cca91966a11c8abd2550", - "sha256:7122ffe597b531fb065d3314e704a6fe152b81820ca5f38543e70ffcc95ecfd4", - "sha256:7307024b18266b302f4265da84bb1effb5d18999ef35b30d17592959568d5c0a", - "sha256:7a4a6f5b818988a3917ec4baa91d1143242bdfece8d38305020463955961266a", - "sha256:83c5a3ecd96a9f3f11cfe6dfcbcec7323265340eb24cc996acaecea129865a3a", - "sha256:890b0f1e18dbd898aeb0ab9eae1ab159c6bcbe87f0abb065b0044581d8614062", - "sha256:8deda1f7b4c03242f2a8037706d9584e703f3d8c74d6d9cac5833db36fe16c42", - "sha256:8ea13d0348b4c96b437d944d7068d59ed4a6c98aaa6c40d8537a2981313f1c66", - "sha256:91e96bf85b7c07c827d339a386e8a3cf2e90ef098c42595227f729922d0851df", - "sha256:96782ebb3c9e91e174c333208b272ea144ed2a684413afb1038e3b3342230d72", - "sha256:9755c726aa6788f076114dfdc03b92b03ff8860316cca00902cce88bcdb5fedd", - "sha256:9dbab90c348c512e03f146e93a5e2610acec76df391043ecd46b6b775d5397e6", - "sha256:9ee0eef254e340cc11c379f797af3977992a7f2c176f1a658740c94bf677e13c", - "sha256:9fc17fdac8f1973850d42e51e8ba6149d93b1993ed6768a24f352f926dd3d587", - "sha256:a2787319dc69854acdfd6452e6a8ba8f929aeb20843c7f090e04159fc18e6245", - "sha256:b7c522292407fa04d8195032493aac937e253ad9ae524aab43b9d9d242571f03", - "sha256:bd312794f51e37dcf77f013d40650fe4fbb211dd55ef2863839c37480bd44369", - "sha256:c0d660a186e36c526366edf8a64391874fe53cf8b7039224137aee0163c046df", - "sha256:c4869141e20769b65d2d72686e7a7eb141ce9f3168106bed3e7dcced54eb2422", - "sha256:cc4057f692ac35bbe82a0a908d42ce3a281c9e913290fac37d7fa3bd01307dfb", - "sha256:cccf1e7806f12300e3a3b48f219e111000c2538483e85c869c35c1ae591e6ce9", - "sha256:ce208f80f398522e49d9db789065c8ad2cd37b21bd6b23d30053474b7416af11", - "sha256:d0565481dc196986c484a7fb13214fc6402201f7fb55c65fd215b3324962fe6c", - "sha256:d1b3366329c45a474b3bbc9b9c95d4c686e03f35da7fd12bc144626d1f2a7c04", - "sha256:d226e0d4b9192d95079a9a29c04dd81816b1ce8903b8c174a39224fe978547cb", - "sha256:d38b35f6eef4237b1d0d8e845fc1546dad85c55eba447e28c211da8c7ef9697c", - "sha256:d64c98277ea80e4484f1332ab107e8dfd173a7dcf1bdbf10a9cccc97aaab145f", - "sha256:d9de8427a5601799784eb0e7fa1b031aa64086ce04de29df775a8ca37eedac41", - "sha256:e6a15cf8f887d9f578dd49c6fb3a99d53e1d922fdd67a245a67488d77bf56eb2", - "sha256:e8c446882cbb3774cd78c738c9f58220606b702b7c1655f1423357dc51674054", - "sha256:e8d188ee39bd0ffe76603da887706e4e7b471f613625899ddf1e27867dc6a0d3", - "sha256:ef76535776c0708a85258f6dc51d36a2df12633c735f6d197ed7dfcaa7449b99", - "sha256:f6efca006a81e1197b925a7d7b16b8f61980697bb6746587aad8842865233218" - ], - "version": "==3.11.0" + "sha256:019ddf7ced8e42cc6c8c608927c799be8097237596c94ffe551f6ef70e55237e", + "sha256:047c325c4a96e7be7d11acf58639bcf71a81ca212d9c6590e3369bc28678647a", + "sha256:047cc2007b280672ddfdf2e7b862aad8d898f481f65bbc9067bfa4e420a019a9", + "sha256:061d59f525831c4051af0b6dbafa62b0b8b168d4ef5b6e3c46d0811b8499d100", + "sha256:082832a59da18efab4d9148cca396451bac99da9757f31767f706e828b5b8500", + "sha256:0a53a751d977ad02f1bd22ddb6288bb4816c4758f44a50225462aeeae9cbf6a0", + "sha256:1222025db539641071a1b67f6950f65a6342a39db5b454bf306abd6954f1ad8a", + "sha256:1580fad512c678b720784e5c9018621b1b3bd37fb5b1633e874738862d6435c7", + "sha256:202ea1d4edc8a5439fc179802d807b49e7e563207fea5610779e56674ac770c6", + "sha256:21d7b48567a1c80f9266e0ab61c1218a31279d911da345679188733e354f81cc", + "sha256:264843ce2af0640994a4331148ef5312989bc004678c457460758766c9b4decc", + "sha256:270a1f6a331eac3a393090af06df68297cb31a8b2df0bdcbd97dc613c5758e78", + "sha256:29a6840c2ac778010547cad5870f3db2e080ad7fad01197b07fff993c08692c8", + "sha256:3646c2286d889618d43e01d9810ac1fc17709d2b4dec61366df5edc8ba228b3e", + "sha256:36b9b98a39565a8f33803c81569442b35e749a72fb1aa7d0bcdb1a33052f8bcc", + "sha256:3ec8f8e106a1476659d8c020228b45614daabdbdb6c6454a843a1d4f77d13339", + "sha256:422069f2cebf58c9dd9e8040b4768f7be4f228c95bc4505e8fa8e7b4f7191ad8", + "sha256:44376a657717de8847d5d71a9305f3595c7e78c91ac77edbb87058d12ede87a6", + "sha256:45728e6aae3023afb5b2829586d1d2bfd9f0d71cfd7d3c924b71a5e9aef617a8", + "sha256:46792b71ab802d9caf1fc9d52e83399ef8e1a36e91eef4d827c06e36b8df2230", + "sha256:4942a5659ae927bb764a123a6409870ca5dd572d83b3bfb71412c9a191bbf792", + "sha256:4be4fe9d18523da98deeb0b554ac76e1dc1562ee879d62572b34dda8593efcc1", + "sha256:523804bd8fcb5255508052b50073a27c701b90a73ea46e29be46dad5fe01bde6", + "sha256:540dafd6f4a0590fc966465c726b80fa7c0804490c39786ef29236fe68c94401", + "sha256:5980509801cbd2942df31714d055d89863684b4de26829c349362e610a48694e", + "sha256:5ad7b96c27acd7e256b33f47cf3d23bd7dd902f9c033ae43f32ffcbc37bebafd", + "sha256:6122470dfa61d4909b75c98012c1577404ba4ab860d0095e0c6980560cb3711f", + "sha256:6175fd105da74a09adb38f93be96e1f64873294c906e5e722cbbc5bd10c44e3b", + "sha256:646d4d30c5aa7c0ddbfe9b990f0f77a88621024a21ad0b792bd9d58caa9611f0", + "sha256:6700e251c6396cc05d7460dc05ef8e19e60a7b53b62c007725b48e123aaa2b1c", + "sha256:6aac7e0e8de92f11a410eb68c24a2decbac6f094e82fd95d22546d0168e7a18b", + "sha256:6e7a6057481a644970e43475292e1c0af095ca39a20fe83781196bd6e6690a38", + "sha256:76579fcf77052b39796fe4a11818d1289dd48cffe15951b3403288fa163c29f6", + "sha256:7e69fa025a1db189443428f345fea5555d16413df6addc056e17bb8c9794b006", + "sha256:7f0c507e1f108790840d6c4b594019ebf595025c324c9f7e9c9b2b15b41f884e", + "sha256:813db97e9955b6b1b50b5cebd18cb148580603bb9b067ea4c5cc656b333bc906", + "sha256:82d5ded5834b6c92380847860eb28dcaf20b847a27cee5811c4aaceef87fd280", + "sha256:82f6e42ba40440a7e0a20bfe12465a3b62d65966a4c7ad1a21b36ffff88de6fe", + "sha256:8d669c720891781e7c82d412cad39f9730ef277e3957b48a3344dae47d3caa03", + "sha256:944ed467feb949e103555863fa934fb84216a096b0004ca364d3ddf9d18e2b9e", + "sha256:96c6aef7ffb0d37206c0342abb82d874fa8cdc344267277ec63f562b94335c22", + "sha256:9be785bd4e1ba0148fb00ca84e4dbfbd1c74df3af3a648559adc60b0782f34de", + "sha256:9d19843568df9d263dc92ae4cc2279879add8a26996473f9155590cac635b321", + "sha256:a118a1df7280ffab7fe0f3eab325868339ff1c4d5b8e0750db0f0a796da8f849", + "sha256:b4294ddf76452459433ecfa6a93258608b5e462c76ef15e4695ed5e2762f009f", + "sha256:b50af6701b4a5288b77fb4db44a363aa9485caf2c3e7a40c0373fd45e34440af", + "sha256:b875bb4b438931dce550e170bfb558597189b8d0160f4ac60f14a21955161699", + "sha256:b95d2c2829b5956bf54d9a22ffec911dea75abf0f0f7e0a8a57423434bfbde91", + "sha256:c046e09e886f4539f8626afba17fa8f2e6552731f9384e2827154e3e3b7fda4e", + "sha256:c1d1992bbdf363b22b5a9543ab7d7c6f27a1498826d50d91319b803ddcf1142e", + "sha256:c2b67881392a9e85aa108e75f62cdbe372d5a3f17ea5f8d3436dcf4662052f14", + "sha256:c6cf288c9e03195d8e12b72a6388b32f18a5e9c2545622417a963e428e1fe496", + "sha256:c812b6e53344e92f10f12235219fb769c491a4a87a02c9c3f93fe632e493bda8", + "sha256:cc421babc687dc52ce0fc19787b2404518ca749d9db59576100946ff886f38ed", + "sha256:ce53c00be204ec4428d3c1f3c478ae89d388efec575544c27f57b61e9fa4a7f2", + "sha256:ce9964c117cbe5cf6269f30a2b334d28675956e988b7dbd0b4f7370924afda2e", + "sha256:d6f82e86896a8db70e8ae8fa4b7556a0f188f1d8a6c53b2ba229889d55a59308", + "sha256:d9d3ae537f61011191b2fd6f8527b9f9f8a848b37d4c85a0f7bb28004c42b546", + "sha256:e565d1e4388765c135052717f15f9e0314f9d172062444c6b3fc0002e93ed04b", + "sha256:ed98683d8f01f1c46ef2d02469e04e9a8fe9a73a9741a4e6e66677a73b59bec8", + "sha256:ef18aa15b1aa18c42933deed5233b3284186e9ed85c25d2704ceff5099a3964c", + "sha256:fa741e9c805567239f845c7e9a016aff797f9bb02ff9bc8ccd2fbd9eafefedd4", + "sha256:fc4946acb6cdada08f60aca103b61334995523da65be5fe816ea8571c9967d46", + "sha256:fcc66d17a3363b7bd6d2655de8706e25a3cd1be2bd1b8e8d8a5c504a6ef893ae" + ], + "version": "==3.11.2" }, "python-dateutil": { "hashes": [ @@ -261,6 +280,7 @@ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.15.0" }, "uvloop": { @@ -275,22 +295,23 @@ "sha256:e7514d7a48c063226b7d06617cbb12a14278d4323a065a8d46a7962686ce2e95", "sha256:f07909cd9fc08c52d294b1570bba92186181ca01fe3dc9ffba68955273dd7362" ], - "index": "pypi", "markers": "sys_platform != 'win32'", "version": "==0.14.0" }, "virtualenv": { "hashes": [ - "sha256:b0011228208944ce71052987437d3843e05690b2f23d1c7da4263fde104c97a2", - "sha256:b8d6110f493af256a40d65e29846c69340a947669eec8ce784fcf3dd3af28380" + "sha256:54b05fc737ea9c9ee9f8340f579e5da5b09fb64fd010ab5757eb90268616907c", + "sha256:b7a8ec323ee02fb2312f098b6b4c9de99559b462775bc8fe3627a73706603c1b" ], - "version": "==20.1.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.2.2" }, "virtualenv-clone": { "hashes": [ "sha256:07e74418b7cc64f4fda987bf5bc71ebd59af27a7bc9e8a8ee9fd54b1f2390a27", "sha256:665e48dd54c84b98b71a657acb49104c54e7652bce9c1c4f6c6976ed4c827a29" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.5.4" }, "yarl": { @@ -313,6 +334,7 @@ "sha256:f18d68f2be6bf0e89f1521af2b1bb46e66ab0018faafa81d70f358153170a317", "sha256:f379b7f83f23fe12823085cd6b906edc49df969eb99757f58ff382349a3303c6" ], + "markers": "python_version >= '3.5'", "version": "==1.5.1" } }, @@ -329,6 +351,7 @@ "sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703", "sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386" ], + "markers": "python_version >= '3.5'", "version": "==2.4.2" }, "attrs": { @@ -336,6 +359,7 @@ "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==20.3.0" }, "bandit": { @@ -359,6 +383,7 @@ "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==7.1.2" }, "colorama": { @@ -382,21 +407,24 @@ "sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac", "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9" ], + "markers": "python_version >= '3.4'", "version": "==4.0.5" }, "gitpython": { "hashes": [ - "sha256:6eea89b655917b500437e9668e4a12eabdcf00229a0df1762aabd692ef9b746b", - "sha256:befa4d101f91bad1b632df4308ec64555db684c360bd7d2130b4807d49ce86b8" + "sha256:42dbefd8d9e2576c496ed0059f3103dcef7125b9ce16f9d5f9c834aed44a1dac", + "sha256:867ec3dfb126aac0f8296b19fb63b8c4a399f32b4b6fafe84c4b10af5fa9f7b5" ], - "version": "==3.1.11" + "markers": "python_version >= '3.4'", + "version": "==3.1.12" }, "isort": { "hashes": [ - "sha256:dcab1d98b469a12a1a624ead220584391648790275560e1a43e54c5dceae65e7", - "sha256:dcaeec1b5f0eca77faea2a35ab790b4f3680ff75590bfcb7145986905aab2f58" + "sha256:c729845434366216d320e936b8ad6f9d681aab72dc7cbc2d51bedc3582f3ad1e", + "sha256:fff4f0c04e1825522ce6949973e83110a6e907750cd92d128b0d14aaaadbffdc" ], - "version": "==5.6.4" + "markers": "python_version >= '3.6' and python_version < '4.0'", + "version": "==5.7.0" }, "lazy-object-proxy": { "hashes": [ @@ -422,6 +450,7 @@ "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.4.3" }, "mccabe": { @@ -443,6 +472,7 @@ "sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9", "sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00" ], + "markers": "python_version >= '2.6'", "version": "==5.5.1" }, "pycodestyle": { @@ -450,6 +480,7 @@ "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.6.0" }, "pyflakes": { @@ -457,6 +488,7 @@ "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.2.0" }, "pylint": { @@ -472,11 +504,13 @@ "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", + "sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e", "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", + "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a", "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" @@ -485,57 +519,56 @@ }, "regex": { "hashes": [ - "sha256:03855ee22980c3e4863dc84c42d6d2901133362db5daf4c36b710dd895d78f0a", - "sha256:06b52815d4ad38d6524666e0d50fe9173533c9cc145a5779b89733284e6f688f", - "sha256:11116d424734fe356d8777f89d625f0df783251ada95d6261b4c36ad27a394bb", - "sha256:119e0355dbdd4cf593b17f2fc5dbd4aec2b8899d0057e4957ba92f941f704bf5", - "sha256:127a9e0c0d91af572fbb9e56d00a504dbd4c65e574ddda3d45b55722462210de", - "sha256:1ec66700a10e3c75f1f92cbde36cca0d3aaee4c73dfa26699495a3a30b09093c", - "sha256:227a8d2e5282c2b8346e7f68aa759e0331a0b4a890b55a5cfbb28bd0261b84c0", - "sha256:2564def9ce0710d510b1fc7e5178ce2d20f75571f788b5197b3c8134c366f50c", - "sha256:297116e79074ec2a2f885d22db00ce6e88b15f75162c5e8b38f66ea734e73c64", - "sha256:2dc522e25e57e88b4980d2bdd334825dbf6fa55f28a922fc3bfa60cc09e5ef53", - "sha256:3a5f08039eee9ea195a89e180c5762bfb55258bfb9abb61a20d3abee3b37fd12", - "sha256:3dfca201fa6b326239e1bccb00b915e058707028809b8ecc0cf6819ad233a740", - "sha256:49461446b783945597c4076aea3f49aee4b4ce922bd241e4fcf62a3e7c61794c", - "sha256:4afa350f162551cf402bfa3cd8302165c8e03e689c897d185f16a167328cc6dd", - "sha256:4b5a9bcb56cc146c3932c648603b24514447eafa6ce9295234767bf92f69b504", - "sha256:52e83a5f28acd621ba8e71c2b816f6541af7144b69cc5859d17da76c436a5427", - "sha256:625116aca6c4b57c56ea3d70369cacc4d62fead4930f8329d242e4fe7a58ce4b", - "sha256:654c1635f2313d0843028487db2191530bca45af61ca85d0b16555c399625b0e", - "sha256:8092a5a06ad9a7a247f2a76ace121183dc4e1a84c259cf9c2ce3bbb69fac3582", - "sha256:832339223b9ce56b7b15168e691ae654d345ac1635eeb367ade9ecfe0e66bee0", - "sha256:8ca9dca965bd86ea3631b975d63b0693566d3cc347e55786d5514988b6f5b84c", - "sha256:96f99219dddb33e235a37283306834700b63170d7bb2a1ee17e41c6d589c8eb9", - "sha256:9b6305295b6591e45f069d3553c54d50cc47629eb5c218aac99e0f7fafbf90a1", - "sha256:a62162be05edf64f819925ea88d09d18b09bebf20971b363ce0c24e8b4aa14c0", - "sha256:aacc8623ffe7999a97935eeabbd24b1ae701d08ea8f874a6ff050e93c3e658cf", - "sha256:b45bab9f224de276b7bc916f6306b86283f6aa8afe7ed4133423efb42015a898", - "sha256:b88fa3b8a3469f22b4f13d045d9bd3eda797aa4e406fde0a2644bc92bbdd4bdd", - "sha256:b8a686a6c98872007aa41fdbb2e86dc03b287d951ff4a7f1da77fb7f14113e4d", - "sha256:bd904c0dec29bbd0769887a816657491721d5f545c29e30fd9d7a1a275dc80ab", - "sha256:bf4f896c42c63d1f22039ad57de2644c72587756c0cfb3cc3b7530cfe228277f", - "sha256:c13d311a4c4a8d671f5860317eb5f09591fbe8259676b86a85769423b544451e", - "sha256:c2c6c56ee97485a127555c9595c069201b5161de9d05495fbe2132b5ac104786", - "sha256:c32c91a0f1ac779cbd73e62430de3d3502bbc45ffe5bb6c376015acfa848144b", - "sha256:c3466a84fce42c2016113101018a9981804097bacbab029c2d5b4fcb224b89de", - "sha256:c454ad88e56e80e44f824ef8366bb7e4c3def12999151fd5c0ea76a18fe9aa3e", - "sha256:c8a2b7ccff330ae4c460aff36626f911f918555660cc28163417cb84ffb25789", - "sha256:cb905f3d2e290a8b8f1579d3984f2cfa7c3a29cc7cba608540ceeed18513f520", - "sha256:cfcf28ed4ce9ced47b9b9670a4f0d3d3c0e4d4779ad4dadb1ad468b097f808aa", - "sha256:dd3e6547ecf842a29cf25123fbf8d2461c53c8d37aa20d87ecee130c89b7079b", - "sha256:de7fd57765398d141949946c84f3590a68cf5887dac3fc52388df0639b01eda4", - "sha256:ea37320877d56a7f0a1e6a625d892cf963aa7f570013499f5b8d5ab8402b5625", - "sha256:f1fce1e4929157b2afeb4bb7069204d4370bab9f4fc03ca1fbec8bd601f8c87d", - "sha256:f43109822df2d3faac7aad79613f5f02e4eab0fc8ad7932d2e70e2a83bd49c26" - ], - "version": "==2020.10.28" + "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538", + "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4", + "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc", + "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa", + "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444", + "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1", + "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af", + "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8", + "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9", + "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88", + "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba", + "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364", + "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e", + "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7", + "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0", + "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31", + "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683", + "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee", + "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b", + "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884", + "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c", + "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e", + "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562", + "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85", + "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c", + "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6", + "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d", + "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b", + "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70", + "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b", + "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b", + "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f", + "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0", + "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5", + "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5", + "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f", + "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e", + "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512", + "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d", + "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917", + "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f" + ], + "version": "==2020.11.13" }, "six": { "hashes": [ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.15.0" }, "smmap": { @@ -543,56 +576,59 @@ "sha256:54c44c197c819d5ef1991799a7e30b662d1e520f2ac75c9efbeb54a742214cf4", "sha256:9c98bbd1f9786d22f14b3d4126894d56befb835ec90cef151af566c7e19b5d24" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==3.0.4" }, "stevedore": { "hashes": [ - "sha256:5e1ab03eaae06ef6ce23859402de785f08d97780ed774948ef16c4652c41bc62", - "sha256:f845868b3a3a77a2489d226568abe7328b5c2d4f6a011cc759dfa99144a521f0" + "sha256:3a5bbd0652bf552748871eaa73a4a8dc2899786bc497a2aa1fcb4dcdb0debeee", + "sha256:50d7b78fbaf0d04cd62411188fa7eedcb03eb7f4c4b37005615ceebe582aa82a" ], - "version": "==3.2.2" + "markers": "python_version >= '3.6'", + "version": "==3.3.0" }, "toml": { "hashes": [ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==0.10.2" }, "typed-ast": { "hashes": [ - "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", - "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", - "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d", - "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", - "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", - "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", - "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c", - "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", - "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", - "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", - "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", - "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", - "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", - "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d", - "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", - "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", - "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c", - "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", - "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395", - "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", - "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", - "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", - "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", - "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", - "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072", - "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298", - "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91", - "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", - "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f", - "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" - ], - "version": "==1.4.1" + "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1", + "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d", + "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6", + "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd", + "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37", + "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151", + "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07", + "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440", + "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70", + "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496", + "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea", + "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400", + "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc", + "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606", + "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc", + "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581", + "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412", + "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a", + "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2", + "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787", + "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f", + "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937", + "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64", + "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487", + "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b", + "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41", + "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a", + "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3", + "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166", + "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10" + ], + "version": "==1.4.2" }, "wrapt": { "hashes": [ diff --git a/bot.py b/bot.py index fdd348e269..eff4000aca 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.8.0-dev7" +__version__ = "3.8.0-dev8" import asyncio @@ -1588,7 +1588,7 @@ def main(): pass # check discord version - if discord.__version__ != "1.5.2": + if discord.__version__ != "1.6.0": logger.error( "Dependencies are not updated, run pipenv install. discord.py version expected 1.5.2, recieved %s", discord.__version__, diff --git a/core/thread.py b/core/thread.py index 6480160e8b..aed4275abc 100644 --- a/core/thread.py +++ b/core/thread.py @@ -898,7 +898,11 @@ async def send( if is_image_url(url, convert_size=False) ] images.extend(image_urls) - images.extend((str(i.image_url), f"{i.name} Sticker", True) for i in message.stickers) + images.extend(( + str(i.image_url) if isinstance(i.image_url, discord.Asset) else i.image_url, + f"{i.name} Sticker", + True + ) for i in message.stickers) embedded_image = False @@ -908,11 +912,16 @@ async def send( additional_count = 1 for url, filename, is_sticker in images: - if not prioritize_uploads or (is_image_url(url) and not embedded_image and filename): - embed.set_image(url=url) + if not prioritize_uploads or ((url is None or is_image_url(url)) and not embedded_image and filename): + if url is not None: + embed.set_image(url=url) if filename: if is_sticker: - embed.add_field(name=filename, value=f"\u200b") + if url is None: + description = 'Unable to retrieve sticker image' + else: + description = "\u200b" + embed.add_field(name=filename, value=description) else: embed.add_field(name="Image", value=f"[{filename}]({url})") embedded_image = True @@ -925,9 +934,12 @@ async def send( color = self.bot.recipient_color img_embed = discord.Embed(color=color) - img_embed.set_image(url=url) + + if url is None: + img_embed.set_image(url=url) + img_embed.url = url + img_embed.title = filename - img_embed.url = url img_embed.set_footer(text=f"Additional Image Upload ({additional_count})") img_embed.timestamp = message.created_at additional_images.append(destination.send(embed=img_embed)) diff --git a/discord.py-1.5.2.tar.gz b/discord.py-1.5.2.tar.gz deleted file mode 100644 index df0732cccac17a0d0b0f878dde24c204773b8d80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 649426 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/Q@d9Q`4-0o)w7tj34MYxDAsHT3*DWV&mBT3N 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

1!ba;}Hg5IgWP`v9i@Qr%<+n_2AY)yvA5<(J*e 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 mVFvCv_D6N36TPC87JPG-A6Q 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�ggG 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 ie1wA4?^Yn9m_%khyPj;a-~ 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�|h-|zj*;OI+c?*30G=$_$! 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 Date: Fri, 8 Jan 2021 16:19:09 +0800 Subject: [PATCH 031/209] Release v3.8 --- CHANGELOG.md | 2 +- bot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbc9a029b9..fe8852c300 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. -# v3.8.0-dev8 +# v3.8.0 ### Added diff --git a/bot.py b/bot.py index eff4000aca..4641e2dfc2 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.8.0-dev8" +__version__ = "3.8.0" import asyncio From bb812252eaa9dc6dbedbcb3049a76d8086288dcd Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Fri, 8 Jan 2021 16:20:47 +0800 Subject: [PATCH 032/209] Linting & Changelog --- CHANGELOG.md | 1 + core/thread.py | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe8852c300..5db65bcb35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ however, insignificant breaking changes do not guarantee a major version bump, s - Sending files in threads (non-images) now work. ([GH #2926](https://github.com/kyb3r/modmail/issues/2926)) - Deleting messages no longer shows a false error. ([GH #2910](https://github.com/kyb3r/modmail/issues/2910), [Jerrie-Aries](https://github.com/kyb3r/modmail/issues/2910#issuecomment-753557313)) - Display an error on [Lottie](https://airbnb.io/lottie/#/) stickers, instead of failing the send. +- `?perms get` now shows role/user names. ([PR #2927](https://github.com/kyb3r/modmail/pull/2927)) ### Internal diff --git a/core/thread.py b/core/thread.py index aed4275abc..75dbda5739 100644 --- a/core/thread.py +++ b/core/thread.py @@ -898,11 +898,14 @@ async def send( if is_image_url(url, convert_size=False) ] images.extend(image_urls) - images.extend(( - str(i.image_url) if isinstance(i.image_url, discord.Asset) else i.image_url, - f"{i.name} Sticker", - True - ) for i in message.stickers) + images.extend( + ( + str(i.image_url) if isinstance(i.image_url, discord.Asset) else i.image_url, + f"{i.name} Sticker", + True, + ) + for i in message.stickers + ) embedded_image = False @@ -912,13 +915,15 @@ async def send( additional_count = 1 for url, filename, is_sticker in images: - if not prioritize_uploads or ((url is None or is_image_url(url)) and not embedded_image and filename): + if not prioritize_uploads or ( + (url is None or is_image_url(url)) and not embedded_image and filename + ): if url is not None: embed.set_image(url=url) if filename: if is_sticker: if url is None: - description = 'Unable to retrieve sticker image' + description = "Unable to retrieve sticker image" else: description = "\u200b" embed.add_field(name=filename, value=description) From 68fa13aabe1e2b7a821ed1e85f79f596e38c471a Mon Sep 17 00:00:00 2001 From: Jerrie-Aries <70805800+Jerrie-Aries@users.noreply.github.com> Date: Sun, 10 Jan 2021 22:23:25 +0800 Subject: [PATCH 033/209] Fix bug when sending multiple images at once. --- core/thread.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/core/thread.py b/core/thread.py index 75dbda5739..fcaeb025f1 100644 --- a/core/thread.py +++ b/core/thread.py @@ -915,9 +915,9 @@ async def send( additional_count = 1 for url, filename, is_sticker in images: - if not prioritize_uploads or ( - (url is None or is_image_url(url)) and not embedded_image and filename - ): + if ( + not prioritize_uploads or ((url is None or is_image_url(url)) and filename) + ) and not embedded_image: if url is not None: embed.set_image(url=url) if filename: @@ -930,7 +930,7 @@ async def send( else: embed.add_field(name="Image", value=f"[{filename}]({url})") embedded_image = True - elif filename is not None: + else: if note: color = self.bot.main_color elif from_mod: @@ -940,11 +940,11 @@ async def send( img_embed = discord.Embed(color=color) - if url is None: + if url is not None: img_embed.set_image(url=url) img_embed.url = url - - img_embed.title = filename + if filename is not None: + img_embed.title = filename img_embed.set_footer(text=f"Additional Image Upload ({additional_count})") img_embed.timestamp = message.created_at additional_images.append(destination.send(embed=img_embed)) From 8d72b79cec09ec93d8f1e8b49358907f69801761 Mon Sep 17 00:00:00 2001 From: Jerrie-Aries <70805800+Jerrie-Aries@users.noreply.github.com> Date: Sun, 10 Jan 2021 22:25:43 +0800 Subject: [PATCH 034/209] Fix error when reacting on confirm thread creation message. --- core/thread.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/thread.py b/core/thread.py index fcaeb025f1..9baa0c921c 100644 --- a/core/thread.py +++ b/core/thread.py @@ -658,7 +658,7 @@ async def delete_message( await asyncio.gather(*tasks) async def find_linked_message_from_dm(self, message, either_direction=False): - if either_direction and message.embeds: + if either_direction and message.embeds and message.embeds[0].author.url: compare_url = message.embeds[0].author.url compare_id = compare_url.split("#")[-1] else: From d19adcfab8588af2fd325d2122064e4d27885caf Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Mon, 11 Jan 2021 16:52:37 +0800 Subject: [PATCH 035/209] Update changelog --- CHANGELOG.md | 8 ++++++++ bot.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5db65bcb35..4407575641 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. +# v3.8.1 + +### Fixed + +- Additional image uploads now render properly. ([PR #2933](https://github.com/kyb3r/modmail/pull/2933))) +- `confirm_thread_creation` no longer raises unnecessary errors. ([GH #2931](https://github.com/kyb3r/modmail/issues/2931), [PR #2933](https://github.com/kyb3r/modmail/pull/2933)) + + # v3.8.0 ### Added diff --git a/bot.py b/bot.py index 4641e2dfc2..3b2d228505 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.8.0" +__version__ = "3.8.1" import asyncio From 80010ca5704f22f711060f65f97e2de50eecb62f Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Mon, 11 Jan 2021 16:56:31 +0800 Subject: [PATCH 036/209] Autotriggers no longer sends attachments back. resolves #2932 --- CHANGELOG.md | 1 + core/models.py | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4407575641..cf57a18a7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ however, insignificant breaking changes do not guarantee a major version bump, s - Additional image uploads now render properly. ([PR #2933](https://github.com/kyb3r/modmail/pull/2933))) - `confirm_thread_creation` no longer raises unnecessary errors. ([GH #2931](https://github.com/kyb3r/modmail/issues/2931), [PR #2933](https://github.com/kyb3r/modmail/pull/2933)) +- Autotriggers no longer sends attachments back. ([GH #2932](https://github.com/kyb3r/modmail/issues/2932)) # v3.8.0 diff --git a/core/models.py b/core/models.py index 66965a6d88..2f71f0f202 100644 --- a/core/models.py +++ b/core/models.py @@ -227,6 +227,7 @@ class DummyMessage: """ def __init__(self, message): + message.attachments = [] self._message = message def __getattr__(self, name: str): From 2efd4a50b857ca90af045ef0b22bf9521bd655b4 Mon Sep 17 00:00:00 2001 From: Jerrie-Aries <70805800+Jerrie-Aries@users.noreply.github.com> Date: Wed, 13 Jan 2021 19:33:55 +0800 Subject: [PATCH 037/209] Fix `logs` command without argument in thread channel when the recipient is not cached. --- cogs/modmail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogs/modmail.py b/cogs/modmail.py index 29a3c79df4..ee109420af 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -680,7 +680,7 @@ async def logs(self, ctx, *, user: User = None): thread = ctx.thread if not thread: raise commands.MissingRequiredArgument(SimpleNamespace(name="member")) - user = thread.recipient + user = thread.recipient or await self.bot.fetch_user(thread.id) default_avatar = "https://cdn.discordapp.com/embed/avatars/0.png" icon_url = getattr(user, "avatar_url", default_avatar) From 232accba33a700b48cda451980774fc7fe188cc5 Mon Sep 17 00:00:00 2001 From: Jerrie-Aries <70805800+Jerrie-Aries@users.noreply.github.com> Date: Wed, 13 Jan 2021 19:44:18 +0800 Subject: [PATCH 038/209] Fix error raised when recipient is not cached and reacts to reactions in DM channel. --- bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot.py b/bot.py index 3b2d228505..54006ad8fc 100644 --- a/bot.py +++ b/bot.py @@ -1153,7 +1153,7 @@ async def on_typing(self, channel, user, _): async def handle_reaction_events(self, payload): user = self.get_user(payload.user_id) - if user.bot: + if user is None or user.bot: return channel = self.get_channel(payload.channel_id) From cc170010782d7436f2198efddb01c08d5e010e7b Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Thu, 14 Jan 2021 15:20:59 +0800 Subject: [PATCH 039/209] Fixed bug with update notifiations --- bot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bot.py b/bot.py index 3b2d228505..0986880004 100644 --- a/bot.py +++ b/bot.py @@ -1519,7 +1519,7 @@ async def autoupdate(self): ) logger.info("Bot has been updated.") channel = self.log_channel - if self.bot.config["update_notifications"]: + if self.config["update_notifications"]: await channel.send(embed=embed) else: try: @@ -1548,7 +1548,7 @@ async def autoupdate(self): embed.set_footer( text=f"Updating Modmail v{self.version} " f"-> v{latest.version}" ) - if self.bot.config["update_notifications"]: + if self.config["update_notifications"]: await channel.send(embed=embed) else: embed = discord.Embed( @@ -1559,7 +1559,7 @@ async def autoupdate(self): embed.set_footer( text=f"Updating Modmail v{self.version} " f"-> v{latest.version}" ) - if self.bot.config["update_notifications"]: + if self.config["update_notifications"]: await channel.send(embed=embed) await self.logout() From d00b562a8f34d4943272cc3e499b1c7c10f57e62 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Thu, 14 Jan 2021 15:26:51 +0800 Subject: [PATCH 040/209] Retry with diff name if channel cant be created resolves #2934 --- CHANGELOG.md | 10 ++++++++-- bot.py | 2 +- core/thread.py | 33 +++++++++++++++++++++------------ core/utils.py | 5 ++++- 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf57a18a7f..ad59acb814 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,15 +6,21 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. +# v3.8.2 + +### Fixed + +- Retry with `null-discrim` if channel could not be created. ([GH #2934](https://github.com/kyb3r/modmail/issues/2934)) +- Fix update notifications. + # v3.8.1 ### Fixed -- Additional image uploads now render properly. ([PR #2933](https://github.com/kyb3r/modmail/pull/2933))) +- Additional image uploads now render properly. ([PR #2933](https://github.com/kyb3r/modmail/pull/2933)) - `confirm_thread_creation` no longer raises unnecessary errors. ([GH #2931](https://github.com/kyb3r/modmail/issues/2931), [PR #2933](https://github.com/kyb3r/modmail/pull/2933)) - Autotriggers no longer sends attachments back. ([GH #2932](https://github.com/kyb3r/modmail/issues/2932)) - # v3.8.0 ### Added diff --git a/bot.py b/bot.py index 0986880004..bd9f7eff1e 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.8.1" +__version__ = "3.8.2" import asyncio diff --git a/core/thread.py b/core/thread.py index 9baa0c921c..17eaaa34dc 100644 --- a/core/thread.py +++ b/core/thread.py @@ -125,18 +125,27 @@ async def setup(self, *, creator=None, category=None, initial_message=None): overwrites=overwrites, reason="Creating a thread channel.", ) - except discord.HTTPException as e: # Failed to create due to missing perms. - logger.critical("An error occurred while creating a thread.", exc_info=True) - self.manager.cache.pop(self.id) - - embed = discord.Embed(color=self.bot.error_color) - embed.title = "Error while trying to create a thread." - embed.description = str(e) - embed.add_field(name="Recipient", value=recipient.mention) - - if self.bot.log_channel is not None: - await self.bot.log_channel.send(embed=embed) - return + except discord.HTTPException as e: + # try again but null-discrim (name could be banned) + try: + channel = await self.bot.modmail_guild.create_text_channel( + name=format_channel_name(recipient, self.bot.modmail_guild, force_null=True), + category=category, + overwrites=overwrites, + reason="Creating a thread channel.", + ) + except discord.HTTPException as e: # Failed to create due to missing perms. + logger.critical("An error occurred while creating a thread.", exc_info=True) + self.manager.cache.pop(self.id) + + embed = discord.Embed(color=self.bot.error_color) + embed.title = "Error while trying to create a thread." + embed.description = str(e) + embed.add_field(name="Recipient", value=recipient.mention) + + if self.bot.log_channel is not None: + await self.bot.log_channel.send(embed=embed) + return self._channel = channel diff --git a/core/utils.py b/core/utils.py index 6803b97d1e..b34d253baf 100644 --- a/core/utils.py +++ b/core/utils.py @@ -339,9 +339,12 @@ def escape_code_block(text): return re.sub(r"```", "`\u200b``", text) -def format_channel_name(author, guild, exclude_channel=None): +def format_channel_name(author, guild, exclude_channel=None, force_null=False): """Sanitises a username for use with text channel names""" name = author.name.lower() + if force_null: + name = "null" + name = new_name = ( "".join(l for l in name if l not in string.punctuation and l.isprintable()) or "null" ) + f"-{author.discriminator}" From 0ab4eafff7cdea072225a893896714037a8ecd7e Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Thu, 14 Jan 2021 15:31:19 +0800 Subject: [PATCH 041/209] Retrieve user from Discord API if user has left the server, #2935 #2936 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad59acb814..bf81e46645 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ however, insignificant breaking changes do not guarantee a major version bump, s - Retry with `null-discrim` if channel could not be created. ([GH #2934](https://github.com/kyb3r/modmail/issues/2934)) - Fix update notifications. +- Retrieve user from Discord API if user has left the server, resolving issues in `?block`. ([GH #2935](https://github.com/kyb3r/modmail/issues/2935), [PR #2936](https://github.com/kyb3r/modmail/pull/2936)) # v3.8.1 From 9a8f61e7b1272472861562634eb19c278e3e13f3 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Thu, 14 Jan 2021 15:32:44 +0800 Subject: [PATCH 042/209] IDs in `` commands work now. --- CHANGELOG.md | 1 + core/utils.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf81e46645..e32f44d20f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ however, insignificant breaking changes do not guarantee a major version bump, s - Retry with `null-discrim` if channel could not be created. ([GH #2934](https://github.com/kyb3r/modmail/issues/2934)) - Fix update notifications. - Retrieve user from Discord API if user has left the server, resolving issues in `?block`. ([GH #2935](https://github.com/kyb3r/modmail/issues/2935), [PR #2936](https://github.com/kyb3r/modmail/pull/2936)) +- IDs in `` commands work now. # v3.8.1 diff --git a/core/utils.py b/core/utils.py index b34d253baf..5f88c21e80 100644 --- a/core/utils.py +++ b/core/utils.py @@ -47,7 +47,7 @@ def strtobool(val): raise -class User(commands.IDConverter): +class User(commands.MemberConverter): """ A custom discord.py `Converter` that supports `Member`, `User`, and string ID's. From a56f9479272bc6ce2a9e202663dfda9f89896ae4 Mon Sep 17 00:00:00 2001 From: Ayam Dobhal Date: Sun, 24 Jan 2021 19:04:12 +0530 Subject: [PATCH 043/209] update version in discord version error string --- bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot.py b/bot.py index fc0a607068..37148cb2bc 100644 --- a/bot.py +++ b/bot.py @@ -1590,7 +1590,7 @@ def main(): # check discord version if discord.__version__ != "1.6.0": logger.error( - "Dependencies are not updated, run pipenv install. discord.py version expected 1.5.2, recieved %s", + "Dependencies are not updated, run pipenv install. discord.py version expected 1.6.0, recieved %s", discord.__version__, ) sys.exit(0) From 8b893bd12fed422811d7b784ee2a9a5b6da811aa Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Mon, 1 Feb 2021 16:09:04 +0800 Subject: [PATCH 044/209] Hotfix: Corrupted data no longer saved to thread cache --- CHANGELOG.md | 8 ++++++++ core/thread.py | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e32f44d20f..d1d6209f17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. +# v3.8.3 + +This update is a quick hotfix for a weird behaviour experienced on 1 Feb 2021 where users were not properly cached. + +### Fixed + +- Corrupted data is no longer saved to thread cache. + # v3.8.2 ### Fixed diff --git a/core/thread.py b/core/thread.py index 17eaaa34dc..ca6d255c6a 100644 --- a/core/thread.py +++ b/core/thread.py @@ -1127,7 +1127,9 @@ async def find( ) if channel: thread = Thread(self, recipient or recipient_id, channel) - self.cache[recipient_id] = thread + if thread.recipient: + # only save if data is valid + self.cache[recipient_id] = thread thread.ready = True return thread From 308d6694156c1e182332b15fe1ec044f6bb5388e Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Mon, 1 Feb 2021 16:10:35 +0800 Subject: [PATCH 045/209] Bump version --- README.md | 2 +- bot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5741f1db6e..74e37838dc 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@
- +
diff --git a/bot.py b/bot.py index 37148cb2bc..447d41c6ec 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.8.2" +__version__ = "3.8.3" import asyncio From d81e67b9c3fe3efdbcfe98134aa4df48eed0711b Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Tue, 2 Feb 2021 20:00:00 +0800 Subject: [PATCH 046/209] 3.8.4 - Another fix attempt --- CHANGELOG.md | 2 +- bot.py | 2 +- core/thread.py | 10 +++++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1d6209f17..ebf7ecaa3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. -# v3.8.3 +# v3.8.4 This update is a quick hotfix for a weird behaviour experienced on 1 Feb 2021 where users were not properly cached. diff --git a/bot.py b/bot.py index 447d41c6ec..aa19d7b3d1 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.8.3" +__version__ = "3.8.4" import asyncio diff --git a/core/thread.py b/core/thread.py index ca6d255c6a..18acc88c7a 100644 --- a/core/thread.py +++ b/core/thread.py @@ -1092,7 +1092,7 @@ async def find( ) -> typing.Optional[Thread]: """Finds a thread from cache or from discord channel topics.""" if recipient is None and channel is not None: - thread = self._find_from_channel(channel) + thread = await self._find_from_channel(channel) if thread is None: user_id, thread = next( ((k, v) for k, v in self.cache.items() if v.channel == channel), (-1, None) @@ -1133,7 +1133,7 @@ async def find( thread.ready = True return thread - def _find_from_channel(self, channel): + async def _find_from_channel(self, channel): """ Tries to find a thread from a channel channel topic, if channel topic doesnt exist for some reason, falls back to @@ -1151,7 +1151,11 @@ def _find_from_channel(self, channel): if user_id in self.cache: return self.cache[user_id] - recipient = self.bot.get_user(user_id) + try: + recipient = self.bot.get_user(user_id) or await self.bot.fetch_user(user_id) + except discord.NotFound: + recipient = None + if recipient is None: self.cache[user_id] = thread = Thread(self, user_id, channel) else: From 7b2e63d1dac132162dc4a9e4c9c62dcc604b839a Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Tue, 2 Feb 2021 20:06:30 +0800 Subject: [PATCH 047/209] Improved cache saving methods --- core/thread.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/thread.py b/core/thread.py index 18acc88c7a..d534ef352e 100644 --- a/core/thread.py +++ b/core/thread.py @@ -1157,7 +1157,7 @@ async def _find_from_channel(self, channel): recipient = None if recipient is None: - self.cache[user_id] = thread = Thread(self, user_id, channel) + thread = Thread(self, user_id, channel) else: self.cache[user_id] = thread = Thread(self, recipient, channel) thread.ready = True From 4054c510f8694372173ff99eb48cde43c359490a Mon Sep 17 00:00:00 2001 From: Jerrie-Aries <70805800+Jerrie-Aries@users.noreply.github.com> Date: Tue, 2 Feb 2021 21:24:40 +0800 Subject: [PATCH 048/209] Ability to disable mention on thread creation. --- cogs/utility.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/cogs/utility.py b/cogs/utility.py index 65b3ef00f5..a89442464a 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -674,20 +674,41 @@ async def ping(self, ctx): @commands.command() @checks.has_permissions(PermissionLevel.ADMINISTRATOR) - async def mention(self, ctx, *mention: Union[discord.Role, discord.Member]): + async def mention(self, ctx, *mention: Union[discord.Role, discord.Member, str]): """ Change what the bot mentions at the start of each thread. Type only `{prefix}mention` to retrieve your current "mention" message. + `{prefix}mention disable` to disable mention. + `{prefix}mention reset` to reset it to default value. """ - # TODO: ability to disable mention. current = self.bot.config["mention"] - if not mention: embed = discord.Embed( title="Current mention:", color=self.bot.main_color, description=str(current) ) + elif ( + len(mention) == 1 + and isinstance(mention[0], str) + and mention[0] in ["disable", "reset"] + ): + option = mention[0] + if option == "disable": + embed = discord.Embed( + description=f"Disabled mention on thread creation.", + color=self.bot.main_color, + ) + self.bot.config["mention"] = None + else: + embed = discord.Embed( + description="`mention` had been reset to default.", color=self.bot.main_color, + ) + self.bot.config.remove("mention") + await self.bot.config.update() else: + for m in mention: + if not isinstance(m, (discord.Role, discord.Member)): + raise commands.BadArgument(f'Role or Member "{m}" not found.') mention = " ".join(i.mention for i in mention) embed = discord.Embed( title="Changed mention!", From 15a38bcfc11958291c24f36d3d9cbde44d032e3d Mon Sep 17 00:00:00 2001 From: Jerrie-Aries <70805800+Jerrie-Aries@users.noreply.github.com> Date: Tue, 2 Feb 2021 22:08:35 +0800 Subject: [PATCH 049/209] Update thread move message in case `mention` was set to disable/None. --- cogs/modmail.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cogs/modmail.py b/cogs/modmail.py index ee109420af..fee8e0b5fe 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -341,7 +341,11 @@ async def move(self, ctx, *, arguments): if self.bot.config["thread_move_notify_mods"]: mention = self.bot.config["mention"] - await thread.channel.send(f"{mention}, thread has been moved.") + if mention is not None: + msg = f"{mention}, thread has been moved." + else: + msg = "Thread has been moved." + await thread.channel.send(msg) sent_emoji, _ = await self.bot.retrieve_emoji() await self.bot.add_reaction(ctx.message, sent_emoji) From 0bfb02e01256f3c0a8b5b4ab872aa6a72e1a95b3 Mon Sep 17 00:00:00 2001 From: Jerrie-Aries <70805800+Jerrie-Aries@users.noreply.github.com> Date: Tue, 2 Feb 2021 22:16:43 +0800 Subject: [PATCH 050/209] Formatting... --- cogs/utility.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cogs/utility.py b/cogs/utility.py index a89442464a..0428f079d9 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -695,8 +695,7 @@ async def mention(self, ctx, *mention: Union[discord.Role, discord.Member, str]) option = mention[0] if option == "disable": embed = discord.Embed( - description=f"Disabled mention on thread creation.", - color=self.bot.main_color, + description=f"Disabled mention on thread creation.", color=self.bot.main_color, ) self.bot.config["mention"] = None else: From 270483fd65898b46433b7e4052476c276f1c590f Mon Sep 17 00:00:00 2001 From: Jerrie-Aries <70805800+Jerrie-Aries@users.noreply.github.com> Date: Wed, 3 Feb 2021 08:26:19 +0800 Subject: [PATCH 051/209] Apply suggestions from code review Co-authored-by: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> --- cogs/utility.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cogs/utility.py b/cogs/utility.py index 0428f079d9..f4c490b846 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -690,9 +690,9 @@ async def mention(self, ctx, *mention: Union[discord.Role, discord.Member, str]) elif ( len(mention) == 1 and isinstance(mention[0], str) - and mention[0] in ["disable", "reset"] + and mention[0].lower() in ["disable", "reset"] ): - option = mention[0] + option = mention[0].lower() if option == "disable": embed = discord.Embed( description=f"Disabled mention on thread creation.", color=self.bot.main_color, @@ -700,7 +700,7 @@ async def mention(self, ctx, *mention: Union[discord.Role, discord.Member, str]) self.bot.config["mention"] = None else: embed = discord.Embed( - description="`mention` had been reset to default.", color=self.bot.main_color, + description="`mention` is reset to default.", color=self.bot.main_color, ) self.bot.config.remove("mention") await self.bot.config.update() From c5a48f0cf9e28131ea36e8307bb35e7ee3bc2e45 Mon Sep 17 00:00:00 2001 From: Jerrie-Aries <70805800+Jerrie-Aries@users.noreply.github.com> Date: Sun, 28 Feb 2021 21:56:08 +0800 Subject: [PATCH 052/209] Fix typo in 'config_help.json' (#2957) --- core/config_help.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/config_help.json b/core/config_help.json index e6bf3730ab..271e726346 100644 --- a/core/config_help.json +++ b/core/config_help.json @@ -706,7 +706,7 @@ "`{prefix}config set confirm_thread_creation yes`" ], "notes": [ - "See also: `confirm_thread_creation_title`, `confirm_thread_response`, `confirm_thread_creation_accept`, `confirm_thread_creation_deny``" + "See also: `confirm_thread_creation_title`, `confirm_thread_response`, `confirm_thread_creation_accept`, `confirm_thread_creation_deny`" ] }, "confirm_thread_creation_title": { From 049508fd944b0fbbbb57b8001044ca1a652a6f04 Mon Sep 17 00:00:00 2001 From: lorenzo132 <50767078+lorenzo132@users.noreply.github.com> Date: Sun, 28 Feb 2021 14:59:15 +0100 Subject: [PATCH 053/209] Update README.md (#2955) fix a sentence which said to join the old deleted plugin server --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 74e37838dc..b5dfc9b4aa 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ You can find a list of third-party plugins using the `?plugins registry` comman To develop your own, check out the [plugins documentation](https://github.com/kyb3r/modmail/wiki/Plugins). -Plugins requests and support is available in our [Modmail Plugins Server](https://discord.gg/4JE4XSW). +Plugins requests and support are available in our [Modmail Support Server](https://discord.gg/j5e9p8w). ## Contributing From 907b10d466b5df16d6438c6735088c4d65cdf098 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Sun, 7 Mar 2021 22:53:56 +1000 Subject: [PATCH 054/209] Add msglink command to get DM message URLs. --- cogs/modmail.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cogs/modmail.py b/cogs/modmail.py index ee109420af..2392916fea 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -601,6 +601,18 @@ async def sfw(self, ctx): sent_emoji, _ = await self.bot.retrieve_emoji() await self.bot.add_reaction(ctx.message, sent_emoji) + @commands.command() + @checks.has_permissions(PermissionLevel.SUPPORTER) + @checks.thread_only() + async def loglink(self, ctx, message_id: int): + """Retrieves the link to a message in the current thread.""" + message = await ctx.thread.recipient.fetch_message(message_id) + if not message: + embed = discord.Embed(color=self.bot.main_color, description="Message no longer exists.") + else: + embed = discord.Embed(color=self.bot.main_color, description=message.jump_url) + await ctx.send(embed=embed) + @commands.command() @checks.has_permissions(PermissionLevel.SUPPORTER) @checks.thread_only() From 5482bed00fab2da73c1fae694271acd682967dca Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Sun, 7 Mar 2021 22:54:16 +1000 Subject: [PATCH 055/209] Add DM channel ID to genesis message footer. --- core/thread.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/thread.py b/core/thread.py index d534ef352e..98f67ca897 100644 --- a/core/thread.py +++ b/core/thread.py @@ -300,7 +300,11 @@ def _format_info_embed(self, user, log_url, log_count, color): # embed.add_field(name='Mention', value=user.mention) # embed.add_field(name='Registered', value=created + days(created)) - footer = "User ID: " + str(user.id) + if user.dm_channel: + footer = f"User ID: {user.id} • DM ID: {user.dm_channel}" + else: + footer = f"User ID: {user.id}" + embed.set_author(name=str(user), icon_url=user.avatar_url, url=log_url) # embed.set_thumbnail(url=avi) From 784aa4f950b2b6fe9e275860c03fa961ff1abd84 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Sun, 7 Mar 2021 23:07:54 +1000 Subject: [PATCH 056/209] Amend typo and black formatting. --- cogs/modmail.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cogs/modmail.py b/cogs/modmail.py index 2392916fea..d9965243be 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -604,11 +604,13 @@ async def sfw(self, ctx): @commands.command() @checks.has_permissions(PermissionLevel.SUPPORTER) @checks.thread_only() - async def loglink(self, ctx, message_id: int): + async def msglink(self, ctx, message_id: int): """Retrieves the link to a message in the current thread.""" message = await ctx.thread.recipient.fetch_message(message_id) if not message: - embed = discord.Embed(color=self.bot.main_color, description="Message no longer exists.") + embed = discord.Embed( + color=self.bot.main_color, description="Message no longer exists." + ) else: embed = discord.Embed(color=self.bot.main_color, description=message.jump_url) await ctx.send(embed=embed) From e0956c48061ed164af3ae3953589f9cba1e56cb7 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Sun, 7 Mar 2021 23:11:20 +0800 Subject: [PATCH 057/209] Improve messages --- cogs/modmail.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/cogs/modmail.py b/cogs/modmail.py index d9965243be..f0ee2815c8 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -606,13 +606,16 @@ async def sfw(self, ctx): @checks.thread_only() async def msglink(self, ctx, message_id: int): """Retrieves the link to a message in the current thread.""" - message = await ctx.thread.recipient.fetch_message(message_id) - if not message: + try: + message = await ctx.thread.recipient.fetch_message(message_id) + except discord.NotFound: embed = discord.Embed( - color=self.bot.main_color, description="Message no longer exists." + color=self.bot.error_color, description="Message not found or no longer exists." ) else: - embed = discord.Embed(color=self.bot.main_color, description=message.jump_url) + embed = discord.Embed( + color=self.bot.main_color, description=message.jump_url + ) await ctx.send(embed=embed) @commands.command() From 9d9a609b1f8a521161bf483eb5a8e7ab7f5594e4 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Sun, 7 Mar 2021 23:14:00 +0800 Subject: [PATCH 058/209] Changelog update --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebf7ecaa3e..da576ec027 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,18 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. +# v3.8.5 + +This update is a quick hotfix for a weird behaviour experienced on 1 Feb 2021 where users were not properly cached. + +### Added + +- `?msglink `, allows you to obtain channel + message ID for T&S reports. ([GH #2963](https://github.com/kyb3r/modmail/issues/2963), [PR #2964](https://github.com/kyb3r/modmail/pull/2964)) + +### Fixed + +- Non-master/development branch deployments no longer cause erros to be raised. + # v3.8.4 This update is a quick hotfix for a weird behaviour experienced on 1 Feb 2021 where users were not properly cached. From 41d5593d0370bece803628cb79bfd1a54809277d Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Sun, 7 Mar 2021 23:14:49 +0800 Subject: [PATCH 059/209] Non-master/development branch deployments no longer cause erros to be raised. --- CHANGELOG.md | 4 +--- core/changelog.py | 3 +++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da576ec027..83603ac880 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,15 +8,13 @@ however, insignificant breaking changes do not guarantee a major version bump, s # v3.8.5 -This update is a quick hotfix for a weird behaviour experienced on 1 Feb 2021 where users were not properly cached. - ### Added - `?msglink `, allows you to obtain channel + message ID for T&S reports. ([GH #2963](https://github.com/kyb3r/modmail/issues/2963), [PR #2964](https://github.com/kyb3r/modmail/pull/2964)) ### Fixed -- Non-master/development branch deployments no longer cause erros to be raised. +- Non-master/development branch deployments no longer cause errors to be raised. # v3.8.4 diff --git a/core/changelog.py b/core/changelog.py index ada9fe6984..878a19b26c 100644 --- a/core/changelog.py +++ b/core/changelog.py @@ -180,6 +180,9 @@ async def from_url(cls, bot, url: str = "") -> "Changelog": if not branch or err: branch = "master" if not bot.version.is_prerelease else "development" + if branch not in ("master", "development"): + branch = "master" + url = url or f"https://raw.githubusercontent.com/kyb3r/modmail/{branch}/CHANGELOG.md" async with await bot.session.get(url) as resp: From 3c42c22e34ebb5d439a0af36553db027f66b0a24 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Sun, 7 Mar 2021 23:16:12 +0800 Subject: [PATCH 060/209] Update changelog --- CHANGELOG.md | 1 + bot.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83603ac880..0ceeeeedf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ however, insignificant breaking changes do not guarantee a major version bump, s ### Added - `?msglink `, allows you to obtain channel + message ID for T&S reports. ([GH #2963](https://github.com/kyb3r/modmail/issues/2963), [PR #2964](https://github.com/kyb3r/modmail/pull/2964)) +- `?mention disable/reset`, disables or resets mention on thread creation. ([PR #2951](https://github.com/kyb3r/modmail/pull/2951)) ### Fixed diff --git a/bot.py b/bot.py index aa19d7b3d1..bc68584f58 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.8.4" +__version__ = "3.8.5" import asyncio From baa7e5fb677e35433586efe0cd63e97d95aa55c4 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Sun, 7 Mar 2021 23:44:25 +0800 Subject: [PATCH 061/209] Fix bug where autotriggers are in dm context, resolve #2961 --- CHANGELOG.md | 1 + bot.py | 3 ++- cogs/modmail.py | 4 +--- cogs/utility.py | 1 - 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ceeeeedf0..0af5009464 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ however, insignificant breaking changes do not guarantee a major version bump, s ### Fixed - Non-master/development branch deployments no longer cause errors to be raised. +- Autotriggers now can search for roles/channels in guild context. ([GH #2961](https://github.com/kyb3r/modmail/issues/2961)) # v3.8.4 diff --git a/bot.py b/bot.py index bc68584f58..1b474e40dd 100644 --- a/bot.py +++ b/bot.py @@ -939,6 +939,7 @@ async def get_contexts(self, message, *, cls=commands.Context): async def trigger_auto_triggers(self, message, channel, *, cls=commands.Context): message.author = self.modmail_guild.me message.channel = channel + message.guild = channel.guild view = StringView(message.content) ctx = cls(prefix=self.prefix, view=view, bot=self, message=message) @@ -967,7 +968,7 @@ async def trigger_auto_triggers(self, message, channel, *, cls=commands.Context) ctxs = [] aliases = normalize_alias(alias) if not aliases: - logger.warning("Alias %s is invalid as called in automove.", invoker) + logger.warning("Alias %s is invalid as called in autotrigger.", invoker) for alias in aliases: view = StringView(invoked_prefix + alias) diff --git a/cogs/modmail.py b/cogs/modmail.py index 9a659a1934..cbc6e4c2a8 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -617,9 +617,7 @@ async def msglink(self, ctx, message_id: int): color=self.bot.error_color, description="Message not found or no longer exists." ) else: - embed = discord.Embed( - color=self.bot.main_color, description=message.jump_url - ) + embed = discord.Embed(color=self.bot.main_color, description=message.jump_url) await ctx.send(embed=embed) @commands.command() diff --git a/cogs/utility.py b/cogs/utility.py index f4c490b846..8663e70e73 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -1798,7 +1798,6 @@ async def autotrigger_edit(self, ctx, keyword, *, command): split_cmd = command.split(" ") for n in range(1, len(split_cmd) + 1): if self.bot.get_command(" ".join(split_cmd[0:n])): - print(self.bot.get_command(" ".join(split_cmd[0:n]))) valid = True break From d24501bc2550de890b5389b8debc000bccad13b1 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Mon, 8 Mar 2021 02:28:01 +1000 Subject: [PATCH 062/209] Use the ID attribute of DM Channel. Originally used the channel instance by mistake, causing the `__str__` to be used instead of the intended ID. --- core/thread.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/thread.py b/core/thread.py index 98f67ca897..cd2797ac67 100644 --- a/core/thread.py +++ b/core/thread.py @@ -301,7 +301,7 @@ def _format_info_embed(self, user, log_url, log_count, color): # embed.add_field(name='Registered', value=created + days(created)) if user.dm_channel: - footer = f"User ID: {user.id} • DM ID: {user.dm_channel}" + footer = f"User ID: {user.id} • DM ID: {user.dm_channel.id}" else: footer = f"User ID: {user.id}" From 93b027000b3b775806ffb13c8fe189ea9ffe819e Mon Sep 17 00:00:00 2001 From: Jerrie-Aries <70805800+Jerrie-Aries@users.noreply.github.com> Date: Thu, 11 Mar 2021 21:59:45 +0800 Subject: [PATCH 063/209] Confirm thread creation when user opens a thread using react to contact. --- cogs/modmail.py | 7 ++++++- core/thread.py | 15 ++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/cogs/modmail.py b/cogs/modmail.py index cbc6e4c2a8..29552b7ca0 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -1027,7 +1027,12 @@ async def contact( await ctx.channel.send(embed=embed, delete_after=3) else: - thread = await self.bot.threads.create(user, creator=ctx.author, category=category) + thread = await self.bot.threads.create( + recipient=user, + creator=ctx.author, + category=category, + manual_trigger=manual_trigger, + ) if self.bot.config["dm_disabled"] in (DMDisabled.NEW_THREADS, DMDisabled.ALL_THREADS): logger.info("Contacting user %s when Modmail DM is disabled.", user) diff --git a/core/thread.py b/core/thread.py index 98f67ca897..e08ff3e5a9 100644 --- a/core/thread.py +++ b/core/thread.py @@ -1175,6 +1175,7 @@ async def create( message: discord.Message = None, creator: typing.Union[discord.Member, discord.User] = None, category: discord.CategoryChannel = None, + manual_trigger: bool = True, ) -> Thread: """Creates a Modmail thread""" @@ -1215,8 +1216,12 @@ async def create( self.bot.config.set("fallback_category_id", category.id) await self.bot.config.update() - if message and self.bot.config["confirm_thread_creation"]: - confirm = await message.channel.send( + if (message or not manual_trigger) and self.bot.config["confirm_thread_creation"]: + if not manual_trigger: + destination = recipient + else: + destination = message.channel + confirm = await destination.send( embed=discord.Embed( title=self.bot.config["confirm_thread_creation_title"], description=self.bot.config["confirm_thread_response"], @@ -1231,7 +1236,7 @@ async def create( try: r, _ = await self.bot.wait_for( "reaction_add", - check=lambda r, u: u.id == message.author.id + check=lambda r, u: u.id == recipient.id and r.message.id == confirm.id and r.message.channel.id == confirm.channel.id and str(r.emoji) in (accept_emoji, deny_emoji), @@ -1243,7 +1248,7 @@ async def create( await confirm.remove_reaction(accept_emoji, self.bot.user) await asyncio.sleep(0.2) await confirm.remove_reaction(deny_emoji, self.bot.user) - await message.channel.send( + await destination.send( embed=discord.Embed( title="Cancelled", description="Timed out", color=self.bot.error_color ) @@ -1257,7 +1262,7 @@ async def create( await confirm.remove_reaction(accept_emoji, self.bot.user) await asyncio.sleep(0.2) await confirm.remove_reaction(deny_emoji, self.bot.user) - await message.channel.send( + await destination.send( embed=discord.Embed(title="Cancelled", color=self.bot.error_color) ) del self.cache[recipient.id] From df41d314f6c1ec7200454c47df0b5d3063b8f2a2 Mon Sep 17 00:00:00 2001 From: Jerrie-Aries <70805800+Jerrie-Aries@users.noreply.github.com> Date: Thu, 11 Mar 2021 22:30:08 +0800 Subject: [PATCH 064/209] Block new thread from react to contact when Modmail DM is disabled (new or all). --- bot.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/bot.py b/bot.py index 1b474e40dd..06e5a62483 100644 --- a/bot.py +++ b/bot.py @@ -1238,6 +1238,26 @@ async def on_raw_reaction_add(self, payload): if not member.bot: message = await channel.fetch_message(payload.message_id) await message.remove_reaction(payload.emoji, member) + await message.add_reaction(emoji_fmt) # bot adds as well + + if self.config["dm_disabled"] in ( + DMDisabled.NEW_THREADS, + DMDisabled.ALL_THREADS, + ): + embed = discord.Embed( + title=self.config["disabled_new_thread_title"], + color=self.error_color, + description=self.config["disabled_new_thread_response"], + ) + embed.set_footer( + text=self.config["disabled_new_thread_footer"], + icon_url=self.guild.icon_url, + ) + logger.info( + "A new thread using react to contact was blocked from %s due to disabled Modmail.", + message.author, + ) + return await member.send(embed=embed) ctx = await self.get_context(message) ctx.author = member @@ -1245,8 +1265,6 @@ async def on_raw_reaction_add(self, payload): self.get_command("contact"), user=member, manual_trigger=False ) - await message.add_reaction(emoji_fmt) # bot adds as well - async def on_raw_reaction_remove(self, payload): if self.config["transfer_reactions"]: await self.handle_reaction_events(payload) From fd6dfb7deeec7bf8463434c8c6d71bd984bdb644 Mon Sep 17 00:00:00 2001 From: Jerrie-Aries <70805800+Jerrie-Aries@users.noreply.github.com> Date: Thu, 11 Mar 2021 22:39:34 +0800 Subject: [PATCH 065/209] Bot will not attempt to find for linked message when the recipient reacts to reactions on confirm thread creation message. --- bot.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bot.py b/bot.py index 06e5a62483..9a33922283 100644 --- a/bot.py +++ b/bot.py @@ -1188,6 +1188,14 @@ async def handle_reaction_events(self, payload): # the reacted message is the corresponding thread creation embed # closing thread return await thread.close(closer=user) + if ( + message.author == self.user + and message.embeds + and self.config.get("confirm_thread_creation") + and message.embeds[0].title == self.config["confirm_thread_creation_title"] + and message.embeds[0].description == self.config["confirm_thread_response"] + ): + return if not thread.recipient.dm_channel: await thread.recipient.create_dm() try: From ccc8e6f89ca202e2950d692c18be57cb735cb58b Mon Sep 17 00:00:00 2001 From: Jerrie-Aries <70805800+Jerrie-Aries@users.noreply.github.com> Date: Thu, 11 Mar 2021 23:03:48 +0800 Subject: [PATCH 066/209] Fix previous commit. --- bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot.py b/bot.py index 9a33922283..dd1008ae54 100644 --- a/bot.py +++ b/bot.py @@ -1263,7 +1263,7 @@ async def on_raw_reaction_add(self, payload): ) logger.info( "A new thread using react to contact was blocked from %s due to disabled Modmail.", - message.author, + member, ) return await member.send(embed=embed) From 69a272e6734fd38e50323bafebe748b823bb5cce Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Sat, 13 Mar 2021 11:03:42 +0800 Subject: [PATCH 067/209] Fix a few bad refs and typos --- bot.py | 4 ++-- cogs/modmail.py | 1 - cogs/plugins.py | 14 +++++++------- cogs/utility.py | 1 - core/config.py | 2 +- core/models.py | 3 +-- core/thread.py | 2 +- core/utils.py | 3 ++- 8 files changed, 14 insertions(+), 16 deletions(-) diff --git a/bot.py b/bot.py index 1b474e40dd..3112895225 100644 --- a/bot.py +++ b/bot.py @@ -46,7 +46,7 @@ ) from core.thread import ThreadManager from core.time import human_timedelta -from core.utils import human_join, normalize_alias, truncate +from core.utils import normalize_alias, truncate logger = getLogger(__name__) @@ -1591,7 +1591,7 @@ def main(): # check discord version if discord.__version__ != "1.6.0": logger.error( - "Dependencies are not updated, run pipenv install. discord.py version expected 1.6.0, recieved %s", + "Dependencies are not updated, run pipenv install. discord.py version expected 1.6.0, received %s", discord.__version__, ) sys.exit(0) diff --git a/cogs/modmail.py b/cogs/modmail.py index cbc6e4c2a8..64d6a2475c 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -1,5 +1,4 @@ import asyncio -from operator import truediv import re from datetime import datetime from itertools import zip_longest diff --git a/cogs/plugins.py b/cogs/plugins.py index ce21551831..1f7aa869d7 100644 --- a/cogs/plugins.py +++ b/cogs/plugins.py @@ -178,14 +178,14 @@ async def download_plugin(self, plugin, force=False): if raw == "Not Found": raise InvalidPluginError("Plugin not found") else: - raise InvalidPluginError("Invalid download recieved, non-bytes object") + raise InvalidPluginError("Invalid download received, non-bytes object") - plugin_io = io.BytesIO(raw) - if not plugin.cache_path.parent.exists(): - plugin.cache_path.parent.mkdir(parents=True) + plugin_io = io.BytesIO(raw) + if not plugin.cache_path.parent.exists(): + plugin.cache_path.parent.mkdir(parents=True) - with plugin.cache_path.open("wb") as f: - f.write(raw) + with plugin.cache_path.open("wb") as f: + f.write(raw) with zipfile.ZipFile(plugin_io) as zipf: for info in zipf.infolist(): @@ -253,7 +253,7 @@ async def parse_user_input(self, ctx, plugin_name, check_version=False): description="Plugins are disabled, enable them by setting `ENABLE_PLUGINS=true`", color=self.bot.main_color, ) - await ctx.send(embed=em) + await ctx.send(embed=embed) return if not self._ready_event.is_set(): diff --git a/cogs/utility.py b/cogs/utility.py index 8663e70e73..8ce7af5b69 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -3,7 +3,6 @@ import os import random import re -from sys import stdout import traceback from contextlib import redirect_stdout from datetime import datetime diff --git a/core/config.py b/core/config.py index 2c88b10d14..14197e112e 100644 --- a/core/config.py +++ b/core/config.py @@ -14,7 +14,7 @@ from core._color_data import ALL_COLORS from core.models import DMDisabled, InvalidConfigError, Default, getLogger from core.time import UserFriendlyTimeSync -from core.utils import strtobool, tryint +from core.utils import strtobool logger = getLogger(__name__) load_dotenv() diff --git a/core/models.py b/core/models.py index 2f71f0f202..24236e6279 100644 --- a/core/models.py +++ b/core/models.py @@ -190,14 +190,13 @@ def get_value(self, key, args, kwds): except KeyError: return "{" + key + "}" else: - return Formatter.get_value(key, args, kwds) + return super().get_value(key, args, kwds) class SimilarCategoryConverter(commands.CategoryChannelConverter): async def convert(self, ctx, argument): bot = ctx.bot guild = ctx.guild - result = None try: return await super().convert(ctx, argument) diff --git a/core/thread.py b/core/thread.py index 98f67ca897..069d2eb8e0 100644 --- a/core/thread.py +++ b/core/thread.py @@ -2,9 +2,9 @@ import copy import io import re +import time import typing from datetime import datetime, timedelta -import time from types import SimpleNamespace import isodate diff --git a/core/utils.py b/core/utils.py index 5f88c21e80..8f4b152ff5 100644 --- a/core/utils.py +++ b/core/utils.py @@ -30,6 +30,7 @@ "escape_code_block", "format_channel_name", "tryint", + "match_title", ] @@ -222,7 +223,7 @@ def cleanup_code(content: str) -> str: def match_title(text: str) -> int: """ - Matches a title in the foramt of "Title: XXXX" + Matches a title in the format of "Title: XXXX" Parameters ---------- From 695baa511df5a1b8de6b5860927e228cb9cbb32c Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Sat, 13 Mar 2021 11:34:49 +0800 Subject: [PATCH 068/209] Add local plugins --- .gitignore | 2 -- cogs/plugins.py | 80 ++++++++++++++++++++++++++++++++----------------- 2 files changed, 53 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index 463016ca95..37094114b7 100644 --- a/.gitignore +++ b/.gitignore @@ -131,8 +131,6 @@ node_modules/ # Modmail config.json -plugins/ -!plugins/registry.json temp/ test.py stack.yml diff --git a/cogs/plugins.py b/cogs/plugins.py index 1f7aa869d7..48de189bc2 100644 --- a/cogs/plugins.py +++ b/cogs/plugins.py @@ -31,16 +31,28 @@ class InvalidPluginError(commands.BadArgument): class Plugin: - def __init__(self, user, repo, name, branch=None): - self.user = user - self.repo = repo - self.name = name - self.branch = branch if branch is not None else "master" - self.url = f"https://github.com/{user}/{repo}/archive/{self.branch}.zip" - self.link = f"https://github.com/{user}/{repo}/tree/{self.branch}/{name}" + def __init__(self, user, repo=None, name=None, branch=None): + if repo is None: + self.user = "@local" + self.repo = "@local" + self.name = user + self.local = True + self.branch = "@local" + self.url = f"@local/{user}" + self.link = f"@local/{user}" + else: + self.user = user + self.repo = repo + self.name = name + self.local = False + self.branch = branch if branch is not None else "master" + self.url = f"https://github.com/{user}/{repo}/archive/{self.branch}.zip" + self.link = f"https://github.com/{user}/{repo}/tree/{self.branch}/{name}" @property def path(self): + if self.local: + return PurePath("plugins") / "@local" / self.name return PurePath("plugins") / self.user / self.repo / f"{self.name}-{self.branch}" @property @@ -49,6 +61,8 @@ def abs_path(self): @property def cache_path(self): + if self.local: + raise ValueError("No cache path for local plugins!") return ( Path(__file__).absolute().parent.parent / "temp" @@ -58,9 +72,13 @@ def cache_path(self): @property def ext_string(self): + if self.local: + return f"plugins.@local.{self.name}.{self.name}" return f"plugins.{self.user}.{self.repo}.{self.name}-{self.branch}.{self.name}" def __str__(self): + if self.local: + return f"@local/{self.name}" return f"{self.user}/{self.repo}/{self.name}@{self.branch}" def __lt__(self, other): @@ -68,10 +86,13 @@ def __lt__(self, other): @classmethod def from_string(cls, s, strict=False): - if not strict: - m = match(r"^(.+?)/(.+?)/(.+?)(?:@(.+?))?$", s) - else: - m = match(r"^(.+?)/(.+?)/(.+?)@(.+?)$", s) + m = match(r"^@local/(.+)$", s) + if m is None: + if not strict: + m = match(r"^(.+?)/(.+?)/(.+?)(?:@(.+?))?$", s) + else: + m = match(r"^(.+?)/(.+?)/(.+?)@(.+?)$", s) + if m is not None: return Plugin(*m.groups()) raise InvalidPluginError("Cannot decipher %s.", s) # pylint: disable=raising-format-tuple @@ -155,6 +176,9 @@ async def download_plugin(self, plugin, force=False): if plugin.abs_path.exists() and not force: return + if plugin.local: + raise InvalidPluginError(f"Local plugin {plugin} not found!") + plugin.abs_path.mkdir(parents=True, exist_ok=True) if plugin.cache_path.exists() and not force: @@ -290,7 +314,7 @@ async def parse_user_input(self, ctx, plugin_name, check_version=False): embed = discord.Embed( description="Invalid plugin name, double check the plugin name " "or use one of the following formats: " - "username/repo/plugin, username/repo/plugin@branch.", + "username/repo/plugin, username/repo/plugin@branch, @local/plugin.", color=self.bot.error_color, ) await ctx.send(embed=embed) @@ -314,7 +338,8 @@ async def plugins_add(self, ctx, *, plugin_name: str): Install a new plugin for the bot. `plugin_name` can be the name of the plugin found in `{prefix}plugin registry`, - or a direct reference to a GitHub hosted plugin (in the format `user/repo/name[@branch]`). + or a direct reference to a GitHub hosted plugin (in the format `user/repo/name[@branch]`) + or `@local/name` for local plugins. """ plugin = await self.parse_user_input(ctx, plugin_name, check_version=True) @@ -395,7 +420,7 @@ async def plugins_remove(self, ctx, *, plugin_name: str): Remove an installed plugin of the bot. `plugin_name` can be the name of the plugin found in `{prefix}plugin registry`, or a direct reference - to a GitHub hosted plugin (in the format `user/repo/name[@branch]`). + to a GitHub hosted plugin (in the format `user/repo/name[@branch]`) or `@local/name` for local plugins. """ plugin = await self.parse_user_input(ctx, plugin_name) if plugin is None: @@ -416,17 +441,18 @@ async def plugins_remove(self, ctx, *, plugin_name: str): self.bot.config["plugins"].remove(str(plugin)) await self.bot.config.update() - shutil.rmtree( - plugin.abs_path, - onerror=lambda *args: logger.warning( - "Failed to remove plugin files %s: %s", plugin, str(args[2]) - ), - ) - try: - plugin.abs_path.parent.rmdir() - plugin.abs_path.parent.parent.rmdir() - except OSError: - pass # dir not empty + if not plugin.local: + shutil.rmtree( + plugin.abs_path, + onerror=lambda *args: logger.warning( + "Failed to remove plugin files %s: %s", plugin, str(args[2]) + ), + ) + try: + plugin.abs_path.parent.rmdir() + plugin.abs_path.parent.parent.rmdir() + except OSError: + pass # dir not empty embed = discord.Embed( description="The plugin is successfully uninstalled.", color=self.bot.main_color @@ -477,7 +503,7 @@ async def plugins_update(self, ctx, *, plugin_name: str = None): Update a plugin for the bot. `plugin_name` can be the name of the plugin found in `{prefix}plugin registry`, or a direct reference - to a GitHub hosted plugin (in the format `user/repo/name[@branch]`). + to a GitHub hosted plugin (in the format `user/repo/name[@branch]`) or `@local/name` for local plugins. To update all plugins, do `{prefix}plugins update`. """ @@ -514,7 +540,7 @@ async def plugins_reset(self, ctx): shutil.rmtree(cache_path) for entry in os.scandir(Path(__file__).absolute().parent.parent / "plugins"): - if entry.is_dir(): + if entry.is_dir() and entry.name != "@local": shutil.rmtree(entry.path) logger.warning("Removing %s.", entry.name) From 51460971a6be32d4c9b2d7d4015536abe22d754f Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Sat, 13 Mar 2021 11:37:19 +0800 Subject: [PATCH 069/209] Add local plugin dir --- plugins/.gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/.gitignore diff --git a/plugins/.gitignore b/plugins/.gitignore new file mode 100644 index 0000000000..4522f5eb88 --- /dev/null +++ b/plugins/.gitignore @@ -0,0 +1,4 @@ +* +!registry.json +!@local/ +!.gitignore \ No newline at end of file From d6ba02e66aee2235a85c2c1f5cbebe5a14a3b1af Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Sat, 13 Mar 2021 11:44:24 +0800 Subject: [PATCH 070/209] Add local plugins --- .dockerignore | 1 + .gitignore | 3 +++ plugins/.gitignore | 4 ---- plugins/@local/.gitignore | 2 ++ 4 files changed, 6 insertions(+), 4 deletions(-) delete mode 100644 plugins/.gitignore create mode 100644 plugins/@local/.gitignore diff --git a/.dockerignore b/.dockerignore index bae84fb2e4..99fd4a241b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -133,6 +133,7 @@ node_modules/ config.json plugins/ !plugins/registry.json +!plugins/@local/ temp/ test.py diff --git a/.gitignore b/.gitignore index 37094114b7..635e3160e8 100644 --- a/.gitignore +++ b/.gitignore @@ -130,6 +130,9 @@ package-lock.json node_modules/ # Modmail +plugins/* +!plugins/registry.json +!plugins/@local config.json temp/ test.py diff --git a/plugins/.gitignore b/plugins/.gitignore deleted file mode 100644 index 4522f5eb88..0000000000 --- a/plugins/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -* -!registry.json -!@local/ -!.gitignore \ No newline at end of file diff --git a/plugins/@local/.gitignore b/plugins/@local/.gitignore new file mode 100644 index 0000000000..c96a04f008 --- /dev/null +++ b/plugins/@local/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file From b6b9e6c7a4c0bb76141cf1d193f4a980a8e259ef Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Sat, 13 Mar 2021 12:04:37 +0800 Subject: [PATCH 071/209] Updated version, deps, and plugins code --- CHANGELOG.md | 8 ++++++++ bot.py | 2 +- cogs/plugins.py | 26 ++++++++++++++++---------- pyproject.toml | 4 ++-- requirements.min.txt | 14 +++----------- 5 files changed, 30 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0af5009464..c065153e2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. + +# v3.8.6 + +### Added + +- Ability to install local plugins without relying on git / external sources + - Simply add your extension to plugins/@local, and use `?plugin add local/plugin-name` + # v3.8.5 ### Added diff --git a/bot.py b/bot.py index 3112895225..5efa4d521a 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.8.5" +__version__ = "3.8.6" import asyncio diff --git a/cogs/plugins.py b/cogs/plugins.py index 48de189bc2..94a3f425c0 100644 --- a/cogs/plugins.py +++ b/cogs/plugins.py @@ -86,7 +86,7 @@ def __lt__(self, other): @classmethod def from_string(cls, s, strict=False): - m = match(r"^@local/(.+)$", s) + m = match(r"^@?local/(.+)$", s) if m is None: if not strict: m = match(r"^(.+?)/(.+?)/(.+?)(?:@(.+?))?$", s) @@ -173,7 +173,7 @@ async def initial_load_plugins(self): await self.bot.config.update() async def download_plugin(self, plugin, force=False): - if plugin.abs_path.exists() and not force: + if plugin.abs_path.exists() and (not force or plugin.local): return if plugin.local: @@ -314,7 +314,7 @@ async def parse_user_input(self, ctx, plugin_name, check_version=False): embed = discord.Embed( description="Invalid plugin name, double check the plugin name " "or use one of the following formats: " - "username/repo/plugin, username/repo/plugin@branch, @local/plugin.", + "username/repo/plugin-name, username/repo/plugin-name@branch, local/plugin-name.", color=self.bot.error_color, ) await ctx.send(embed=embed) @@ -339,7 +339,7 @@ async def plugins_add(self, ctx, *, plugin_name: str): `plugin_name` can be the name of the plugin found in `{prefix}plugin registry`, or a direct reference to a GitHub hosted plugin (in the format `user/repo/name[@branch]`) - or `@local/name` for local plugins. + or `local/name` for local plugins. """ plugin = await self.parse_user_input(ctx, plugin_name, check_version=True) @@ -360,10 +360,16 @@ async def plugins_add(self, ctx, *, plugin_name: str): ) return await ctx.send(embed=embed) - embed = discord.Embed( - description=f"Starting to download plugin from {plugin.link}...", - color=self.bot.main_color, - ) + if plugin.local: + embed = discord.Embed( + description=f"Starting to load local plugin from {plugin.link}...", + color=self.bot.main_color, + ) + else: + embed = discord.Embed( + description=f"Starting to download plugin from {plugin.link}...", + color=self.bot.main_color, + ) msg = await ctx.send(embed=embed) try: @@ -420,7 +426,7 @@ async def plugins_remove(self, ctx, *, plugin_name: str): Remove an installed plugin of the bot. `plugin_name` can be the name of the plugin found in `{prefix}plugin registry`, or a direct reference - to a GitHub hosted plugin (in the format `user/repo/name[@branch]`) or `@local/name` for local plugins. + to a GitHub hosted plugin (in the format `user/repo/name[@branch]`) or `local/name` for local plugins. """ plugin = await self.parse_user_input(ctx, plugin_name) if plugin is None: @@ -503,7 +509,7 @@ async def plugins_update(self, ctx, *, plugin_name: str = None): Update a plugin for the bot. `plugin_name` can be the name of the plugin found in `{prefix}plugin registry`, or a direct reference - to a GitHub hosted plugin (in the format `user/repo/name[@branch]`) or `@local/name` for local plugins. + to a GitHub hosted plugin (in the format `user/repo/name[@branch]`) or `local/name` for local plugins. To update all plugins, do `{prefix}plugins update`. """ diff --git a/pyproject.toml b/pyproject.toml index e7c56eb749..60b86fdbc4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ exclude = ''' [tool.poetry] name = 'Modmail' -version = '3.6.0' +version = '3.8.6' description = "Modmail is similar to Reddit's Modmail, both in functionality and purpose. It serves as a shared inbox for server staff to communicate with their users in a seamless way." license = 'AGPL-3.0-only' authors = [ @@ -36,7 +36,7 @@ keywords = ['discord', 'modmail'] [tool.poetry.dependencies] python = "^3.7" -"discord.py" = "./discord.py-1.5.2.tar.gz" +"discord.py" = "discord.py==1.6.0" uvloop = {version = ">=0.12.0", markers = "sys_platform != 'win32'"} python-dotenv = ">=0.10.3" parsedatetime = "^2.6" diff --git a/requirements.min.txt b/requirements.min.txt index cf1b4bdd26..d756599661 100644 --- a/requirements.min.txt +++ b/requirements.min.txt @@ -1,24 +1,16 @@ -# Generated as of June, 2020 +# Generated as of March, 2021 # This is the bare minimum requirements.txt for running Modmail. # To install requirements.txt run: pip install -r requirements.min.txt aiohttp==3.6.2 -async-timeout==3.0.1 -attrs==19.3.0 -chardet==3.0.4 -./discord.py-1.5.2.tar.gz +discord.py==1.6.0 dnspython==1.16.0 emoji==0.5.4 -future==0.18.2 -idna==2.9 isodate==0.6.0 motor==2.1.0 -multidict==4.7.6 natural==0.2.0 parsedatetime==2.6 pymongo==3.10.1 python-dateutil==2.8.1 python-dotenv==0.14.0 -six==1.15.0 -websockets==8.1 -yarl==1.4.2 +websockets==8.1 \ No newline at end of file From bf1668a491205eeb0a4ef4a797bfe5bca8b4ea57 Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Sat, 13 Mar 2021 12:06:29 +0800 Subject: [PATCH 072/209] Forgot to finish sentence --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c065153e2e..6be578b8d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,8 @@ however, insignificant breaking changes do not guarantee a major version bump, s ### Added - Ability to install local plugins without relying on git / external sources - - Simply add your extension to plugins/@local, and use `?plugin add local/plugin-name` + - Simply add your extension to plugins/@local, and use `?plugin add local/plugin-name` to load the plugin as normal +- Updated deps for requirements.min.txt and pyproject.toml # v3.8.5 From dee522888bc97b28f968c365ea07ef719e7afef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9D=A5sora?= <60399731+6days9weeks@users.noreply.github.com> Date: Mon, 15 Mar 2021 17:18:09 +0900 Subject: [PATCH 073/209] new plugin A little plugin I was working on --- plugins/registry.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/registry.json b/plugins/registry.json index 609345c395..11ee857335 100644 --- a/plugins/registry.json +++ b/plugins/registry.json @@ -204,5 +204,13 @@ "title": "Countdowns", "icon_url": "https://cdn.discordapp.com/avatars/180314310298304512/7552e0089004079304cc9912d13ac81d.png", "thumbnail_url": "https://cdn.discordapp.com/avatars/180314310298304512/7552e0089004079304cc9912d13ac81d.png" + }, + "action": { + "repository": "6days9weeks/modmail-plugins", + "branch": "master", + "description": "Have fun with others by hugging them or giving them pats~!!", + "title": "Action", + "icon_url": "https://media.discordapp.net/attachments/720733784970100776/820933433579798528/689105042212388965.png", + "thumbnail_url": "https://data.whicdn.com/images/58526601/original.gif" } } From e42546a7c041e3575f7920da955b72e34e05538a Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Fri, 19 Mar 2021 20:18:00 +0800 Subject: [PATCH 074/209] Potentially breaking: improved plugin events --- CHANGELOG.md | 9 +++++++++ bot.py | 2 +- core/thread.py | 7 ++++--- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6be578b8d2..ca9c0ae183 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. +# v3.9.0 + +### Breaking + +- `on_thread_initiate` and `on_thread_ready` events now have `thread, creator, category, initial_message` as additional arguments. + +### Internal + +- `thread.reply` now returns (msg_to_user, msg_to_thread). Can be useful in plugins. # v3.8.6 diff --git a/bot.py b/bot.py index 5efa4d521a..ce91a3d326 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.8.6" +__version__ = "3.9.0" import asyncio diff --git a/core/thread.py b/core/thread.py index 069d2eb8e0..a647219e71 100644 --- a/core/thread.py +++ b/core/thread.py @@ -105,7 +105,7 @@ def cancelled(self, flag: bool): async def setup(self, *, creator=None, category=None, initial_message=None): """Create the thread channel and other io related initialisation tasks""" - self.bot.dispatch("thread_initiate", self) + self.bot.dispatch("thread_initiate", self, creator, category, initial_message) recipient = self.recipient # in case it creates a channel outside of category @@ -258,7 +258,7 @@ async def activate_auto_triggers(): activate_auto_triggers(), send_persistent_notes(), ) - self.bot.dispatch("thread_ready", self) + self.bot.dispatch("thread_ready", self, creator, category, initial_message) def _format_info_embed(self, user, log_url, log_count, color): """Get information about a member of a server @@ -753,7 +753,7 @@ async def reply( tasks = [] try: - await self.send( + user_msg = await self.send( message, destination=self.recipient, from_mod=True, @@ -809,6 +809,7 @@ async def reply( await asyncio.gather(*tasks) self.bot.dispatch("thread_reply", self, True, message, anonymous, plain) + return (user_msg, msg) # sent_to_user, sent_to_thread_channel async def send( self, From dd23edfe4872c77f6975cda1c862e52322322b5f Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Fri, 19 Mar 2021 20:30:53 +0800 Subject: [PATCH 075/209] Fix bug where cancelled still attempts to start thread --- cogs/modmail.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cogs/modmail.py b/cogs/modmail.py index 7ec111cee5..bd89571193 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -1032,6 +1032,9 @@ async def contact( category=category, manual_trigger=manual_trigger, ) + if thread.cancelled: + return + if self.bot.config["dm_disabled"] in (DMDisabled.NEW_THREADS, DMDisabled.ALL_THREADS): logger.info("Contacting user %s when Modmail DM is disabled.", user) From 9d4813d25e9d0d6cc2ac70d14dd63ebac043aea6 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Fri, 19 Mar 2021 20:35:58 +0800 Subject: [PATCH 076/209] Changelog & Ghost errors are no longer raised when threads are created using non-organic methods. --- CHANGELOG.md | 6 ++++++ core/models.py | 3 ++- core/thread.py | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca9c0ae183..d6d796246e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,12 @@ however, insignificant breaking changes do not guarantee a major version bump, s - `on_thread_initiate` and `on_thread_ready` events now have `thread, creator, category, initial_message` as additional arguments. +### Fixed + +- `confirm_thread_creation` now properly works when a user opens a thread using react to contact. ([GH #2930](https://github.com/kyb3r/modmail/issues/2930), [PR #2971](https://github.com/kyb3r/modmail/pull/2971)) +- `?disable all/new` now disables react to contact threads. ([GH #2969](https://github.com/kyb3r/modmail/issues/2969), [PR #2971](https://github.com/kyb3r/modmail/pull/2971)) +- Ghost errors are no longer raised when threads are created using non-organic methods. + ### Internal - `thread.reply` now returns (msg_to_user, msg_to_thread). Can be useful in plugins. diff --git a/core/models.py b/core/models.py index 24236e6279..81247d387d 100644 --- a/core/models.py +++ b/core/models.py @@ -226,7 +226,8 @@ class DummyMessage: """ def __init__(self, message): - message.attachments = [] + if message: + message.attachments = [] self._message = message def __getattr__(self, name: str): diff --git a/core/thread.py b/core/thread.py index 72ce5664d4..9fe23301e4 100644 --- a/core/thread.py +++ b/core/thread.py @@ -245,8 +245,8 @@ class Author: await self.bot.api.update_note_ids(ids) async def activate_auto_triggers(): - message = DummyMessage(copy.copy(initial_message)) - if message: + if initial_message: + message = DummyMessage(copy.copy(initial_message)) try: return await self.bot.trigger_auto_triggers(message, channel) except RuntimeError: From 5efdbbe80e586838c3088f118fbe630ab85544f8 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Fri, 19 Mar 2021 21:12:50 +0800 Subject: [PATCH 077/209] Add reaciton menu plugin --- plugins/registry.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugins/registry.json b/plugins/registry.json index 11ee857335..86d424e228 100644 --- a/plugins/registry.json +++ b/plugins/registry.json @@ -212,5 +212,14 @@ "title": "Action", "icon_url": "https://media.discordapp.net/attachments/720733784970100776/820933433579798528/689105042212388965.png", "thumbnail_url": "https://data.whicdn.com/images/58526601/original.gif" + }, + "menu": { + "repository": "fourjr/modmail-plugins", + "branch": "master", + "description": "Adds reaction-based menus into thread creates. Check out `?configmenu`", + "title": "Menus", + "bot_version": "3.9.0", + "icon_url": "https://cdn.discordapp.com/avatars/180314310298304512/7552e0089004079304cc9912d13ac81d.png", + "thumbnail_url": "https://cdn.discordapp.com/avatars/180314310298304512/7552e0089004079304cc9912d13ac81d.png" } } From b008d8a78fe94f2dde09c668cfc3bbb47982a7a4 Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Sun, 21 Mar 2021 22:30:57 +0800 Subject: [PATCH 078/209] better run command, 3.9.1 --- CHANGELOG.md | 7 +++ bot.py | 115 ++++++++++++++++++++++++++++++++++++++----------- pyproject.toml | 2 +- 3 files changed, 98 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6d796246e..c83219b367 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. +# v3.9.1 + +### Internal + +- `bot.run` now more gracefully handles closing, similar to how discord.py handles it. + - No longer displays `PrivilegedIntentsRequired` exception when exiting when presence intent is disabled. + # v3.9.0 ### Breaking diff --git a/bot.py b/bot.py index 28db42a453..24e7c717f7 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.9.0" +__version__ = "3.9.1" import asyncio @@ -6,6 +6,7 @@ import logging import os import re +import signal import sys import typing from datetime import datetime @@ -184,40 +185,104 @@ def db(self): async def get_prefix(self, message=None): return [self.prefix, f"<@{self.user.id}> ", f"<@!{self.user.id}> "] - def run(self, *args, **kwargs): + def run(self): + loop = self.loop + try: - self.loop.run_until_complete(self.start(self.token)) - except KeyboardInterrupt: + loop.add_signal_handler(signal.SIGINT, lambda: loop.stop()) + loop.add_signal_handler(signal.SIGTERM, lambda: loop.stop()) + except NotImplementedError: pass - except discord.LoginFailure: - logger.critical("Invalid token") - except discord.PrivilegedIntentsRequired: - intents = discord.Intents.default() - intents.members = True - # Try again with members intent - self._connection._intents = intents - logger.warning( - "Attempting to login with only the server members privileged intent. Some plugins might not work correctly." - ) + + async def runner(): try: - self.loop.run_until_complete(self.start(self.token)) + retry_intents = False + try: + await self.start(self.token) + except discord.PrivilegedIntentsRequired: + retry_intents = True + if retry_intents: + await self.http.close() + if self.ws is not None and self.ws.open: + await self.ws.close(code=1000) + self._ready.clear() + intents = discord.Intents.default() + intents.members = True + # Try again with members intent + self._connection._intents = intents + logger.warning( + "Attempting to login with only the server members privileged intent. Some plugins might not work correctly." + ) + await self.start(self.token) except discord.PrivilegedIntentsRequired: logger.critical( "Privileged intents are not explicitly granted in the discord developers dashboard." ) - except Exception: - logger.critical("Fatal exception", exc_info=True) - finally: - self.loop.run_until_complete(self.logout()) - for task in asyncio.all_tasks(self.loop): + except discord.LoginFailure: + logger.critical("Invalid token") + except Exception: + logger.critical("Fatal exception", exc_info=True) + finally: + if not self.is_closed(): + await self.close() + if self._session: + await self._session.close() + + def stop_loop_on_completion(f): + loop.stop() + + def _cancel_tasks(): + if sys.version_info < (3, 8): + task_retriever = asyncio.Task.all_tasks + else: + task_retriever = asyncio.all_tasks + + tasks = {t for t in task_retriever(loop=loop) if not t.done()} + + if not tasks: + return + + logger.info('Cleaning up after %d tasks.', len(tasks)) + for task in tasks: task.cancel() + + loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True)) + logger.info('All tasks finished cancelling.') + + for task in tasks: + if task.cancelled(): + continue + if task.exception() is not None: + loop.call_exception_handler({ + 'message': 'Unhandled exception during Client.run shutdown.', + 'exception': task.exception(), + 'task': task + }) + + future = asyncio.ensure_future(runner(), loop=loop) + future.add_done_callback(stop_loop_on_completion) + try: + loop.run_forever() + except KeyboardInterrupt: + logger.info('Received signal to terminate bot and event loop.') + finally: + future.remove_done_callback(stop_loop_on_completion) + logger.info('Cleaning up tasks.') + try: - self.loop.run_until_complete(asyncio.gather(*asyncio.all_tasks(self.loop))) - except asyncio.CancelledError: - logger.debug("All pending tasks has been cancelled.") + _cancel_tasks() + if sys.version_info >= (3, 6): + loop.run_until_complete(loop.shutdown_asyncgens()) finally: - self.loop.run_until_complete(self.session.close()) - logger.error(" - Shutting down bot - ") + logger.info('Closing the event loop.') + loop.close() + + if not future.cancelled(): + try: + return future.result() + except KeyboardInterrupt: + # I am unsure why this gets raised here but suppress it anyway + return None @property def bot_owner_ids(self): diff --git a/pyproject.toml b/pyproject.toml index 60b86fdbc4..5171e27b5e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ exclude = ''' [tool.poetry] name = 'Modmail' -version = '3.8.6' +version = '3.9.1' description = "Modmail is similar to Reddit's Modmail, both in functionality and purpose. It serves as a shared inbox for server staff to communicate with their users in a seamless way." license = 'AGPL-3.0-only' authors = [ From 380ea6ac04e4e285ef3b690cfa1db643ffdd1a4d Mon Sep 17 00:00:00 2001 From: lorenzo132 <50767078+lorenzo132@users.noreply.github.com> Date: Thu, 25 Mar 2021 16:46:51 +0100 Subject: [PATCH 079/209] add music plugin added music plugin on taki's request --- plugins/registry.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugins/registry.json b/plugins/registry.json index 86d424e228..65d91e3cf8 100644 --- a/plugins/registry.json +++ b/plugins/registry.json @@ -17,6 +17,15 @@ "icon_url": "https://cdn1.iconfinder.com/data/icons/web-hosting-2-4/52/200-512.png", "thumbnail_url": "https://cdn1.iconfinder.com/data/icons/web-hosting-2-4/52/200-512.png" }, + "Music": { + "repository": "Taaku18/modmail-plugins", + "branch": "master", + "description": "Play wonderfull jams through your modmail!", + "bot_version": "2.20.1", + "title": "Music", + "icon_url": "https://i.imgur.com/JmJPX5W.gif", + "thumbnail_url": "https://i.imgur.com/jrYL7F8.gif" + }, "media-only": { "repository": "lorenzo132/modmail-plugins", "branch": "master", From c434a1e70369ebfdc149779d56327b94757da068 Mon Sep 17 00:00:00 2001 From: lorenzo132 <50767078+lorenzo132@users.noreply.github.com> Date: Fri, 26 Mar 2021 19:55:52 +0100 Subject: [PATCH 080/209] Update registry.json try to fix install issue --- plugins/registry.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/registry.json b/plugins/registry.json index 65d91e3cf8..515871e916 100644 --- a/plugins/registry.json +++ b/plugins/registry.json @@ -17,12 +17,12 @@ "icon_url": "https://cdn1.iconfinder.com/data/icons/web-hosting-2-4/52/200-512.png", "thumbnail_url": "https://cdn1.iconfinder.com/data/icons/web-hosting-2-4/52/200-512.png" }, - "Music": { + "music": { "repository": "Taaku18/modmail-plugins", "branch": "master", "description": "Play wonderfull jams through your modmail!", "bot_version": "2.20.1", - "title": "Music", + "title": "music", "icon_url": "https://i.imgur.com/JmJPX5W.gif", "thumbnail_url": "https://i.imgur.com/jrYL7F8.gif" }, From 68d8d887a83ba52f9f3c5a6c9ca1607d100b0563 Mon Sep 17 00:00:00 2001 From: lorenzo132 <50767078+lorenzo132@users.noreply.github.com> Date: Sun, 28 Mar 2021 15:44:33 +0200 Subject: [PATCH 081/209] Update bot.py add hosting methods: screen and systemd --- bot.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bot.py b/bot.py index 28db42a453..9d2e3f7995 100644 --- a/bot.py +++ b/bot.py @@ -108,6 +108,12 @@ def hosting_method(self) -> HostingMethod: if os.environ.get("pm_id"): return HostingMethod.PM2 + if os.environ.get("INVOCATION_ID"): + return HostingMethod.SYSTEMD + + if os.environ.get("TERM"): + return HostingMethod.SCREEN + return HostingMethod.OTHER def startup(self): From 9fedb5b3a597d3852746e925e627111612a0bca0 Mon Sep 17 00:00:00 2001 From: lorenzo132 <50767078+lorenzo132@users.noreply.github.com> Date: Sun, 28 Mar 2021 15:45:24 +0200 Subject: [PATCH 082/209] Update models.py add hosting methods: screen and systemd --- core/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/models.py b/core/models.py index 81247d387d..37355a488a 100644 --- a/core/models.py +++ b/core/models.py @@ -277,3 +277,5 @@ class HostingMethod(IntEnum): HEROKU = 0 PM2 = 1 OTHER = 2 + SYSTEMD = 3 + SCREEN = 4 From e3d1ab8817fc3c638e8ea969471dad726b645ef2 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Wed, 31 Mar 2021 20:23:50 +0800 Subject: [PATCH 083/209] Force update dev? Weird history --- CHANGELOG.md | 7 +++ bot.py | 119 ++++++++++++++++++++++++++++++++++++++----------- pyproject.toml | 2 +- 3 files changed, 101 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6d796246e..c83219b367 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. +# v3.9.1 + +### Internal + +- `bot.run` now more gracefully handles closing, similar to how discord.py handles it. + - No longer displays `PrivilegedIntentsRequired` exception when exiting when presence intent is disabled. + # v3.9.0 ### Breaking diff --git a/bot.py b/bot.py index 9d2e3f7995..d55971cdf8 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.9.0" +__version__ = "3.9.1" import asyncio @@ -6,6 +6,7 @@ import logging import os import re +import signal import sys import typing from datetime import datetime @@ -190,40 +191,106 @@ def db(self): async def get_prefix(self, message=None): return [self.prefix, f"<@{self.user.id}> ", f"<@!{self.user.id}> "] - def run(self, *args, **kwargs): + def run(self): + loop = self.loop + try: - self.loop.run_until_complete(self.start(self.token)) - except KeyboardInterrupt: + loop.add_signal_handler(signal.SIGINT, lambda: loop.stop()) + loop.add_signal_handler(signal.SIGTERM, lambda: loop.stop()) + except NotImplementedError: pass - except discord.LoginFailure: - logger.critical("Invalid token") - except discord.PrivilegedIntentsRequired: - intents = discord.Intents.default() - intents.members = True - # Try again with members intent - self._connection._intents = intents - logger.warning( - "Attempting to login with only the server members privileged intent. Some plugins might not work correctly." - ) + + async def runner(): try: - self.loop.run_until_complete(self.start(self.token)) + retry_intents = False + try: + await self.start(self.token) + except discord.PrivilegedIntentsRequired: + retry_intents = True + if retry_intents: + await self.http.close() + if self.ws is not None and self.ws.open: + await self.ws.close(code=1000) + self._ready.clear() + intents = discord.Intents.default() + intents.members = True + # Try again with members intent + self._connection._intents = intents + logger.warning( + "Attempting to login with only the server members privileged intent. Some plugins might not work correctly." + ) + await self.start(self.token) except discord.PrivilegedIntentsRequired: logger.critical( "Privileged intents are not explicitly granted in the discord developers dashboard." ) - except Exception: - logger.critical("Fatal exception", exc_info=True) - finally: - self.loop.run_until_complete(self.logout()) - for task in asyncio.all_tasks(self.loop): + except discord.LoginFailure: + logger.critical("Invalid token") + except Exception: + logger.critical("Fatal exception", exc_info=True) + finally: + if not self.is_closed(): + await self.close() + if self._session: + await self._session.close() + + def stop_loop_on_completion(f): + loop.stop() + + def _cancel_tasks(): + if sys.version_info < (3, 8): + task_retriever = asyncio.Task.all_tasks + else: + task_retriever = asyncio.all_tasks + + tasks = {t for t in task_retriever(loop=loop) if not t.done()} + + if not tasks: + return + + logger.info("Cleaning up after %d tasks.", len(tasks)) + for task in tasks: task.cancel() + + loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True)) + logger.info("All tasks finished cancelling.") + + for task in tasks: + if task.cancelled(): + continue + if task.exception() is not None: + loop.call_exception_handler( + { + "message": "Unhandled exception during Client.run shutdown.", + "exception": task.exception(), + "task": task, + } + ) + + future = asyncio.ensure_future(runner(), loop=loop) + future.add_done_callback(stop_loop_on_completion) + try: + loop.run_forever() + except KeyboardInterrupt: + logger.info("Received signal to terminate bot and event loop.") + finally: + future.remove_done_callback(stop_loop_on_completion) + logger.info("Cleaning up tasks.") + try: - self.loop.run_until_complete(asyncio.gather(*asyncio.all_tasks(self.loop))) - except asyncio.CancelledError: - logger.debug("All pending tasks has been cancelled.") + _cancel_tasks() + if sys.version_info >= (3, 6): + loop.run_until_complete(loop.shutdown_asyncgens()) finally: - self.loop.run_until_complete(self.session.close()) - logger.error(" - Shutting down bot - ") + logger.info("Closing the event loop.") + loop.close() + + if not future.cancelled(): + try: + return future.result() + except KeyboardInterrupt: + # I am unsure why this gets raised here but suppress it anyway + return None @property def bot_owner_ids(self): @@ -1576,7 +1643,7 @@ async def autoupdate(self): elif res != "Already up to date.": logger.info("Bot has been updated.") channel = self.update_channel - if self.hosting_method == HostingMethod.PM2: + if self.hosting_method in (HostingMethod.PM2, HostingMethod.SYSTEMD): embed = discord.Embed(title="Bot has been updated", color=self.main_color) embed.set_footer( text=f"Updating Modmail v{self.version} " f"-> v{latest.version}" diff --git a/pyproject.toml b/pyproject.toml index 60b86fdbc4..5171e27b5e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ exclude = ''' [tool.poetry] name = 'Modmail' -version = '3.8.6' +version = '3.9.1' description = "Modmail is similar to Reddit's Modmail, both in functionality and purpose. It serves as a shared inbox for server staff to communicate with their users in a seamless way." license = 'AGPL-3.0-only' authors = [ From b6e2dada58773bcc0f9c6724bec8fa16c4e1e7f3 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Wed, 31 Mar 2021 20:31:51 +0800 Subject: [PATCH 084/209] Add docker hostingmethod --- CHANGELOG.md | 6 ++++++ Dockerfile | 1 + bot.py | 10 ++++++++++ core/models.py | 7 ++++--- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c83219b367..86292f94a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. +# v3.9.2 + +### Improved + +- Additional HostingMethods (i.e. DOCKER, PM2, SCREEN). Autoupdates are now disabled on all docker instances. ([GH #2977](https://github.com/kyb3r/modmail/issues/2977), [PR #2988](https://github.com/kyb3r/modmail/pull/2988)) + # v3.9.1 ### Internal diff --git a/Dockerfile b/Dockerfile index f616ef29f3..8165e06a53 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,5 @@ FROM python:3.7-alpine +ENV USING_DOCKER yes WORKDIR /modmailbot COPY . /modmailbot RUN export PIP_NO_CACHE_DIR=false \ diff --git a/bot.py b/bot.py index d55971cdf8..8fe8b6d8da 100644 --- a/bot.py +++ b/bot.py @@ -112,6 +112,12 @@ def hosting_method(self) -> HostingMethod: if os.environ.get("INVOCATION_ID"): return HostingMethod.SYSTEMD + if os.environ.get("USING_DOCKER"): + return HostingMethod.DOCKER + + if os.environ.get("INVOCATION_ID"): + return HostingMethod.SYSTEMD + if os.environ.get("TERM"): return HostingMethod.SCREEN @@ -1671,6 +1677,10 @@ async def before_autoupdate(self): logger.warning("Autoupdates disabled.") self.autoupdate_loop.cancel() + if self.hosting_method == HostingMethod.DOCKER: + logger.warning("Autoupdates disabled as using Docker.") + self.autoupdate_loop.cancel() + if not self.config.get("github_token") and self.hosting_method == HostingMethod.HEROKU: logger.warning("GitHub access token not found.") logger.warning("Autoupdates disabled.") diff --git a/core/models.py b/core/models.py index 37355a488a..0238e051d4 100644 --- a/core/models.py +++ b/core/models.py @@ -276,6 +276,7 @@ class DMDisabled(IntEnum): class HostingMethod(IntEnum): HEROKU = 0 PM2 = 1 - OTHER = 2 - SYSTEMD = 3 - SCREEN = 4 + SYSTEMD = 2 + SCREEN = 3 + DOCKER = 4 + OTHER = 5 From de36b95b159f6797ceb1b40e7da22380ecdfcff3 Mon Sep 17 00:00:00 2001 From: lorenzo132 <50767078+lorenzo132@users.noreply.github.com> Date: Sat, 20 Mar 2021 00:02:32 +0100 Subject: [PATCH 085/209] Update config_help.json Change disabled to enabled, as it is enabled on a new bot creation --- core/config_help.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/config_help.json b/core/config_help.json index 271e726346..e764c9255d 100644 --- a/core/config_help.json +++ b/core/config_help.json @@ -86,7 +86,7 @@ "thumbnail": "https://placehold.it/100/e74c3c?text=+" }, "user_typing": { - "default": "Disabled", + "default": "Enabled", "description": "When this is set to `yes`, whenever the recipient user starts to type in their DM channel, the moderator will see “{bot.user.display_name} is typing…” in the thread channel.", "examples": [ "`{prefix}config set user_typing yes`", From 31052354db1fe61224f5c2093946a9c9509a9cf4 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Wed, 31 Mar 2021 20:33:16 +0800 Subject: [PATCH 086/209] Bump versions --- CHANGELOG.md | 4 ++++ bot.py | 2 +- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86292f94a3..6d2ea15f89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ however, insignificant breaking changes do not guarantee a major version bump, s - Additional HostingMethods (i.e. DOCKER, PM2, SCREEN). Autoupdates are now disabled on all docker instances. ([GH #2977](https://github.com/kyb3r/modmail/issues/2977), [PR #2988](https://github.com/kyb3r/modmail/pull/2988)) +### Fixed + +- `user_typing` default in the config help is now correct. + # v3.9.1 ### Internal diff --git a/bot.py b/bot.py index 8fe8b6d8da..689832b73d 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.9.1" +__version__ = "3.9.2" import asyncio diff --git a/pyproject.toml b/pyproject.toml index 5171e27b5e..3e4865bc8c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ exclude = ''' [tool.poetry] name = 'Modmail' -version = '3.9.1' +version = '3.9.2' description = "Modmail is similar to Reddit's Modmail, both in functionality and purpose. It serves as a shared inbox for server staff to communicate with their users in a seamless way." license = 'AGPL-3.0-only' authors = [ From edf636498b9d29b706f79ec10930bc3ac2b73566 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Wed, 31 Mar 2021 20:49:46 +0800 Subject: [PATCH 087/209] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b5dfc9b4aa..10f7baf737 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@
- +
From 20877d9ffbc87aacf3fa31a31670c8447333f621 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Wed, 31 Mar 2021 21:49:32 +0800 Subject: [PATCH 088/209] did i not commit this --- bot.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/bot.py b/bot.py index 689832b73d..d4ef7f250c 100644 --- a/bot.py +++ b/bot.py @@ -115,9 +115,6 @@ def hosting_method(self) -> HostingMethod: if os.environ.get("USING_DOCKER"): return HostingMethod.DOCKER - if os.environ.get("INVOCATION_ID"): - return HostingMethod.SYSTEMD - if os.environ.get("TERM"): return HostingMethod.SCREEN From fa6cf626fea876630f2dfad5cfa2bb019e2c8bef Mon Sep 17 00:00:00 2001 From: Jerrie-Aries <70805800+Jerrie-Aries@users.noreply.github.com> Date: Sun, 4 Apr 2021 04:22:33 +0800 Subject: [PATCH 089/209] Update `mention` command. --- cogs/utility.py | 45 +++++++++++++++++++++++++++++++------------ core/config_help.json | 3 ++- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/cogs/utility.py b/cogs/utility.py index 8ce7af5b69..51ed2b476d 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -673,41 +673,62 @@ async def ping(self, ctx): @commands.command() @checks.has_permissions(PermissionLevel.ADMINISTRATOR) - async def mention(self, ctx, *mention: Union[discord.Role, discord.Member, str]): + async def mention(self, ctx, *user_or_role: Union[discord.Role, discord.Member, str]): """ Change what the bot mentions at the start of each thread. - Type only `{prefix}mention` to retrieve your current "mention" message. - `{prefix}mention disable` to disable mention. - `{prefix}mention reset` to reset it to default value. + `user_or_role` may be a user ID, mention, name, role ID, mention, or name. + You can also set it to mention multiple users or roles, just separate the arguments with space. + + Examples: + - `{prefix}mention @user` + - `{prefix}mention @user @role` + - `{prefix}mention 984301093849028 388218663326449` + - `{prefix}mention everyone` + + Do not ping `@everyone` to set mention to everyone, use "everyone" or "all" instead. + + Notes: + - Type only `{prefix}mention` to retrieve your current "mention" message. + - `{prefix}mention disable` to disable mention. + - `{prefix}mention reset` to reset it to default value, which is "@here". """ current = self.bot.config["mention"] - if not mention: + if not user_or_role: embed = discord.Embed( title="Current mention:", color=self.bot.main_color, description=str(current) ) elif ( - len(mention) == 1 - and isinstance(mention[0], str) - and mention[0].lower() in ["disable", "reset"] + len(user_or_role) == 1 + and isinstance(user_or_role[0], str) + and user_or_role[0].lower() in ("disable", "reset", "all", "everyone") ): - option = mention[0].lower() + option = user_or_role[0].lower() if option == "disable": embed = discord.Embed( description=f"Disabled mention on thread creation.", color=self.bot.main_color, ) self.bot.config["mention"] = None - else: + elif option == "reset": embed = discord.Embed( description="`mention` is reset to default.", color=self.bot.main_color, ) self.bot.config.remove("mention") + else: + embed = discord.Embed( + title="Changed mention!", + description=f'On thread creation the bot now says "@everyone".', + color=self.bot.main_color, + ) + self.bot.config["mention"] = "@everyone" await self.bot.config.update() else: - for m in mention: + for m in user_or_role: if not isinstance(m, (discord.Role, discord.Member)): raise commands.BadArgument(f'Role or Member "{m}" not found.') - mention = " ".join(i.mention for i in mention) + mention = " ".join( + i.mention if i is not ctx.guild.default_role else str(i) for i in user_or_role + ) embed = discord.Embed( title="Changed mention!", description=f'On thread creation the bot now says "{mention}".', diff --git a/core/config_help.json b/core/config_help.json index e764c9255d..d430b9ee4c 100644 --- a/core/config_help.json +++ b/core/config_help.json @@ -52,7 +52,8 @@ "`{prefix}mention Yo~ Here's a new thread for ya!`" ], "notes": [ - "Unfortunately, it's not currently possible to disable mention. You do not have to include a mention." + "To disable mention, use command `{prefix}mention disable`.", + "See also: `{prefix}help mention`." ] }, "main_color": { From a4389b43192edf44547e6cc6b8e0a32b15bf9aa3 Mon Sep 17 00:00:00 2001 From: Ralph Date: Mon, 5 Apr 2021 10:39:57 -0400 Subject: [PATCH 090/209] Add Coolguy (Prime Servers) to SPONSORS --- SPONSORS.json | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/SPONSORS.json b/SPONSORS.json index fcabb119ac..98abe37d81 100644 --- a/SPONSORS.json +++ b/SPONSORS.json @@ -42,5 +42,33 @@ } ] } - } -] \ No newline at end of file + }, + { + "embed": { + "description": "Quality Hosting at Prices You Deserve!", + "color": 50195251 + "footer": { + "icon_url": "http://primeserversinc.com/images/Prime_Logo_P_Sassy.png", + "text": "Prime Servers, Inc." + }, + "thumbnail": { + "url": "http://primeserversinc.com/images/Prime_Logo_P_Sassy.png" + }, + "author": { + "name": "Prime Servers, Inc.", + "url": "https://primeserversinc.com", + "icon_url": "http://primeserversinc.com/images/Prime_Logo_P_Sassy.png" + }, + "fields": [ + { + "name": "Twitter", + "value": "[**Click Here**](https://twitter.com/PrimeServersInc)" + }, + { + "name": "Discord Server", + "value": "[**Click Here**](https://discord.gg/cYM6Urn)" + } + ] + } + }, +] From d77d47dfdecc0e20f9868c4e62ec4baee932e66d Mon Sep 17 00:00:00 2001 From: Ralph Date: Mon, 5 Apr 2021 10:41:27 -0400 Subject: [PATCH 091/209] Small correction --- SPONSORS.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SPONSORS.json b/SPONSORS.json index 98abe37d81..a57ac2d60e 100644 --- a/SPONSORS.json +++ b/SPONSORS.json @@ -46,7 +46,7 @@ { "embed": { "description": "Quality Hosting at Prices You Deserve!", - "color": 50195251 + "color": 50195251, "footer": { "icon_url": "http://primeserversinc.com/images/Prime_Logo_P_Sassy.png", "text": "Prime Servers, Inc." @@ -70,5 +70,5 @@ } ] } - }, + } ] From b4ac84f78fd1cff8e1b83ce88660fa1242df1429 Mon Sep 17 00:00:00 2001 From: Ralph Date: Mon, 5 Apr 2021 10:42:25 -0400 Subject: [PATCH 092/209] Add Coolguy (Prime Servers) to SPONSORS --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 10f7baf737..19b9eed923 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,10 @@ Special thanks to our sponsors for supporting the project. + + + + Become a sponsor on [Patreon](https://patreon.com/kyber). From 4f52b685d1659d870bff2b0d8ad3bd6cc8c32698 Mon Sep 17 00:00:00 2001 From: Ralph Date: Tue, 6 Apr 2021 16:51:21 -0400 Subject: [PATCH 093/209] Update to HTTPS --- SPONSORS.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SPONSORS.json b/SPONSORS.json index a57ac2d60e..53034c57b3 100644 --- a/SPONSORS.json +++ b/SPONSORS.json @@ -48,16 +48,16 @@ "description": "Quality Hosting at Prices You Deserve!", "color": 50195251, "footer": { - "icon_url": "http://primeserversinc.com/images/Prime_Logo_P_Sassy.png", + "icon_url": "https://primeserversinc.com/images/Prime_Logo_P_Sassy.png", "text": "Prime Servers, Inc." }, "thumbnail": { - "url": "http://primeserversinc.com/images/Prime_Logo_P_Sassy.png" + "url": "https://primeserversinc.com/images/Prime_Logo_P_Sassy.png" }, "author": { "name": "Prime Servers, Inc.", "url": "https://primeserversinc.com", - "icon_url": "http://primeserversinc.com/images/Prime_Logo_P_Sassy.png" + "icon_url": "https://primeserversinc.com/images/Prime_Logo_P_Sassy.png" }, "fields": [ { From 4da97a7ecb5381f9568bef0e19c59df745f2b616 Mon Sep 17 00:00:00 2001 From: Ralph Date: Tue, 6 Apr 2021 16:51:45 -0400 Subject: [PATCH 094/209] Update to HTTPS --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 19b9eed923..a211cf22a0 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ Special thanks to our sponsors for supporting the project. - + From 23065593c2b03a4eb300411b90eb04ab1266871f Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Thu, 8 Apr 2021 21:51:22 +0800 Subject: [PATCH 095/209] v3.9.3 add use_user_id_channel_name --- CHANGELOG.md | 13 +++++++++++++ bot.py | 2 +- cogs/modmail.py | 2 +- core/config.py | 2 ++ core/config_help.json | 11 +++++++++++ core/thread.py | 4 ++-- core/utils.py | 20 ++++++++++++++------ pyproject.toml | 2 +- 8 files changed, 45 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d2ea15f89..df4c140165 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,19 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. +# v3.9.3 + +## Added + +- New config: ` use_user_id_channel_name`, when set to TRUE, channel names would get created with the recipient's ID instead of their name and discriminator. + - This is now an option to better suit the needs of servers in Server Discovery + +## Internal Change + +- Signature of `format_channel_name` in core/util.py changed to: + - `format_channel_name(bot, author, exclude_channel=None, force_null=False)` + + # v3.9.2 ### Improved diff --git a/bot.py b/bot.py index d4ef7f250c..994e956e11 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.9.2" +__version__ = "3.9.3" import asyncio diff --git a/cogs/modmail.py b/cogs/modmail.py index bd89571193..b5d48d61c2 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -1490,7 +1490,7 @@ async def repair(self, ctx): if len(users) == 1: user = users.pop() name = format_channel_name( - user, self.bot.modmail_guild, exclude_channel=ctx.channel + self.bot, user, exclude_channel=ctx.channel ) recipient = self.bot.get_user(user.id) if user.id in self.bot.threads.cache: diff --git a/core/config.py b/core/config.py index 14197e112e..890b3690f1 100644 --- a/core/config.py +++ b/core/config.py @@ -50,6 +50,7 @@ class ConfigManager: "sent_emoji": "✅", "blocked_emoji": "🚫", "close_emoji": "🔒", + "use_user_id_channel_name": False, "recipient_thread_close": False, "thread_auto_close_silently": False, "thread_auto_close": isodate.Duration(), @@ -157,6 +158,7 @@ class ConfigManager: time_deltas = {"account_age", "guild_age", "thread_auto_close", "thread_cooldown"} booleans = { + "use_user_id_channel_name", "user_typing", "mod_typing", "reply_without_command", diff --git a/core/config_help.json b/core/config_help.json index e764c9255d..2aef03ccf6 100644 --- a/core/config_help.json +++ b/core/config_help.json @@ -96,6 +96,17 @@ "See also: `mod_typing`." ] }, + "use_user_id_channel_name": { + "default": "No", + "description": "When this is set to `yes`, new thread channels will be named with the recipient's ID instead of the recipient's name.", + "examples": [ + "`{prefix}config set use_user_id_channel_name yes`", + "`{prefix}config set use_user_id_channel_name no`" + ], + "notes": [ + "This config is suitable for servers in Server Discovery to comply with channel name restrictions." + ] + }, "mod_typing": { "default": "Disabled", "description": "When this is set to `yes`, whenever a moderator starts to type in the thread channel, the recipient user will see \"{bot.user.display_name} is typing…\" in their DM channel.", diff --git a/core/thread.py b/core/thread.py index 9fe23301e4..7798454958 100644 --- a/core/thread.py +++ b/core/thread.py @@ -120,7 +120,7 @@ async def setup(self, *, creator=None, category=None, initial_message=None): try: channel = await self.bot.modmail_guild.create_text_channel( - name=format_channel_name(recipient, self.bot.modmail_guild), + name=format_channel_name(self.bot, recipient), category=category, overwrites=overwrites, reason="Creating a thread channel.", @@ -129,7 +129,7 @@ async def setup(self, *, creator=None, category=None, initial_message=None): # try again but null-discrim (name could be banned) try: channel = await self.bot.modmail_guild.create_text_channel( - name=format_channel_name(recipient, self.bot.modmail_guild, force_null=True), + name=format_channel_name(self.bot, recipient, force_null=True), category=category, overwrites=overwrites, reason="Creating a thread channel.", diff --git a/core/utils.py b/core/utils.py index 8f4b152ff5..af47c8e2e6 100644 --- a/core/utils.py +++ b/core/utils.py @@ -340,15 +340,23 @@ def escape_code_block(text): return re.sub(r"```", "`\u200b``", text) -def format_channel_name(author, guild, exclude_channel=None, force_null=False): +def format_channel_name(bot, author, exclude_channel=None, force_null=False): """Sanitises a username for use with text channel names""" - name = author.name.lower() + guild = bot.modmail_guild + if force_null: - name = "null" + name = new_name = "null" + else: + if bot.config["use_user_id_channel_name"]: + name = new_name = str(author.id) + else: + name = author.name.lower() + if force_null: + name = "null" - name = new_name = ( - "".join(l for l in name if l not in string.punctuation and l.isprintable()) or "null" - ) + f"-{author.discriminator}" + name = new_name = ( + "".join(l for l in name if l not in string.punctuation and l.isprintable()) or "null" + ) + f"-{author.discriminator}" counter = 1 existed = set(c.name for c in guild.text_channels if c != exclude_channel) diff --git a/pyproject.toml b/pyproject.toml index 3e4865bc8c..b74dfad111 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ exclude = ''' [tool.poetry] name = 'Modmail' -version = '3.9.2' +version = '3.9.3' description = "Modmail is similar to Reddit's Modmail, both in functionality and purpose. It serves as a shared inbox for server staff to communicate with their users in a seamless way." license = 'AGPL-3.0-only' authors = [ From a0e580ccb5db20002bdc8c5d99b506d18d8bf3e2 Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Thu, 8 Apr 2021 22:25:18 +0800 Subject: [PATCH 096/209] black --- cogs/modmail.py | 4 +--- core/utils.py | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cogs/modmail.py b/cogs/modmail.py index b5d48d61c2..a45da27166 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -1489,9 +1489,7 @@ async def repair(self, ctx): ) if len(users) == 1: user = users.pop() - name = format_channel_name( - self.bot, user, exclude_channel=ctx.channel - ) + name = format_channel_name(self.bot, user, exclude_channel=ctx.channel) recipient = self.bot.get_user(user.id) if user.id in self.bot.threads.cache: thread = self.bot.threads.cache[user.id] diff --git a/core/utils.py b/core/utils.py index af47c8e2e6..2dfb319a8e 100644 --- a/core/utils.py +++ b/core/utils.py @@ -355,7 +355,8 @@ def format_channel_name(bot, author, exclude_channel=None, force_null=False): name = "null" name = new_name = ( - "".join(l for l in name if l not in string.punctuation and l.isprintable()) or "null" + "".join(l for l in name if l not in string.punctuation and l.isprintable()) + or "null" ) + f"-{author.discriminator}" counter = 1 From 3e4d9b019ce748eac1fb8db2aefec5f51c33c755 Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Mon, 12 Apr 2021 14:56:43 +0800 Subject: [PATCH 097/209] Possibly fix emoji problem --- core/config.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/config.py b/core/config.py index 890b3690f1..30f364622d 100644 --- a/core/config.py +++ b/core/config.py @@ -47,9 +47,9 @@ class ConfigManager: # updates "update_notifications": True, # threads - "sent_emoji": "✅", - "blocked_emoji": "🚫", - "close_emoji": "🔒", + "sent_emoji": "\N{WHITE HEAVY CHECK MARK}", + "blocked_emoji": "\N{NO ENTRY SIGN}", + "close_emoji": "\N{LOCK}", "use_user_id_channel_name": False, "recipient_thread_close": False, "thread_auto_close_silently": False, @@ -93,13 +93,13 @@ class ConfigManager: "anon_tag": "Response", # react to contact "react_to_contact_message": None, - "react_to_contact_emoji": "\u2705", + "react_to_contact_emoji": "\N{WHITE HEAVY CHECK MARK}", # confirm thread creation "confirm_thread_creation": False, "confirm_thread_creation_title": "Confirm thread creation", "confirm_thread_response": "React to confirm thread creation which will directly contact the moderators", - "confirm_thread_creation_accept": "\u2705", - "confirm_thread_creation_deny": "\U0001F6AB", + "confirm_thread_creation_accept": "\N{WHITE HEAVY CHECK MARK}", + "confirm_thread_creation_deny": "\N{NO ENTRY SIGN}", # regex "use_regex_autotrigger": False, } From ac2e0c6e1423c485cebd3b50aeb44371102b1e42 Mon Sep 17 00:00:00 2001 From: Jerrie-Aries <70805800+Jerrie-Aries@users.noreply.github.com> Date: Tue, 13 Apr 2021 06:39:41 +0800 Subject: [PATCH 098/209] Cleaner code :D --- cogs/utility.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/cogs/utility.py b/cogs/utility.py index 51ed2b476d..7b39631dcd 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -701,7 +701,7 @@ async def mention(self, ctx, *user_or_role: Union[discord.Role, discord.Member, elif ( len(user_or_role) == 1 and isinstance(user_or_role[0], str) - and user_or_role[0].lower() in ("disable", "reset", "all", "everyone") + and user_or_role[0].lower() in ("disable", "reset") ): option = user_or_role[0].lower() if option == "disable": @@ -709,26 +709,24 @@ async def mention(self, ctx, *user_or_role: Union[discord.Role, discord.Member, description=f"Disabled mention on thread creation.", color=self.bot.main_color, ) self.bot.config["mention"] = None - elif option == "reset": + else: embed = discord.Embed( description="`mention` is reset to default.", color=self.bot.main_color, ) self.bot.config.remove("mention") - else: - embed = discord.Embed( - title="Changed mention!", - description=f'On thread creation the bot now says "@everyone".', - color=self.bot.main_color, - ) - self.bot.config["mention"] = "@everyone" await self.bot.config.update() else: + mention = [] + everyone = ("all", "everyone") for m in user_or_role: - if not isinstance(m, (discord.Role, discord.Member)): + if not isinstance(m, (discord.Role, discord.Member)) and m not in everyone: raise commands.BadArgument(f'Role or Member "{m}" not found.') - mention = " ".join( - i.mention if i is not ctx.guild.default_role else str(i) for i in user_or_role - ) + elif m == ctx.guild.default_role or m in everyone: + mention.append("@everyone") + continue + mention.append(m.mention) + + mention = " ".join(mention) embed = discord.Embed( title="Changed mention!", description=f'On thread creation the bot now says "{mention}".', From c74e5f4ccbf0695b545e5d362897230b24472002 Mon Sep 17 00:00:00 2001 From: codeinteger6 <44692189+codeinteger6@users.noreply.github.com> Date: Wed, 14 Apr 2021 13:25:19 +0600 Subject: [PATCH 099/209] Bump python runtime version Security update --- runtime.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime.txt b/runtime.txt index 67068f10fe..87665291b8 100644 --- a/runtime.txt +++ b/runtime.txt @@ -1 +1 @@ -python-3.9.0 \ No newline at end of file +python-3.9.4 From 404decc10454eb9fe2315ae1fd04beed7dfcd3c6 Mon Sep 17 00:00:00 2001 From: Taku 3 Animals <45324516+Taaku18@users.noreply.github.com> Date: Sun, 25 Apr 2021 01:34:10 +0800 Subject: [PATCH 100/209] Update sponsors --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 10f7baf737..7a5da956f5 100644 --- a/README.md +++ b/README.md @@ -162,13 +162,19 @@ $ docker run --env-file .env kyb3rr/modmail Special thanks to our sponsors for supporting the project. +Kingdom Gaming Discord: +
+ + + +
+
+SirReddit: +
- - - - +
Become a sponsor on [Patreon](https://patreon.com/kyber). From 58e8944bdebe61d68cf0916228dd5a2ab0458e65 Mon Sep 17 00:00:00 2001 From: redstonedesigner Date: Sat, 24 Apr 2021 20:46:00 +0100 Subject: [PATCH 101/209] Add docker-compose configuration for self hosting Adds a docker-compose configuration file for running both the bot and logviewer with a shared mongo database in a single container stack. Exposes port 8000 and ensure the mongo database can not be accessed remotely. --- docker-compose.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 docker-compose.yml diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000..7b4371b5c8 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +version: "3.7" +services: + bot: + image: kyb3rr/modmail + env_file: + - .env + depends_on: + - mongo + logviewer: + image: kyb3rr/logviewer + depends_on: + - mongo + environment: + - MONGO_URI=mongodb://mongo + ports: + - 8000:8000 + mongo: + image: mongo From b145cf1ad9c0118f6d647372c3880b109f78086a Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Sun, 25 Apr 2021 21:22:31 +0800 Subject: [PATCH 102/209] Change Max Channel per Category to 49, fix fallback save logic --- core/thread.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/thread.py b/core/thread.py index 7798454958..530e063e54 100644 --- a/core/thread.py +++ b/core/thread.py @@ -1205,16 +1205,16 @@ async def create( # Schedule thread setup for later cat = self.bot.main_category - if category is None and len(cat.channels) == 50: + if category is None and len(cat.channels) >= 49: fallback_id = self.bot.config["fallback_category_id"] if fallback_id: fallback = discord.utils.get(cat.guild.categories, id=int(fallback_id)) - if fallback and len(fallback.channels) != 50: + if fallback and len(fallback.channels) < 49: category = fallback if not category: category = await cat.clone(name="Fallback Modmail") - self.bot.config.set("fallback_category_id", category.id) + self.bot.config.set("fallback_category_id", str(category.id)) await self.bot.config.update() if (message or not manual_trigger) and self.bot.config["confirm_thread_creation"]: From b557a9bf16cfb37a78e1c932a30420cb1c551992 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Sun, 25 Apr 2021 21:37:23 +0800 Subject: [PATCH 103/209] There is now a proper message when trying to contact a bot. --- cogs/modmail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogs/modmail.py b/cogs/modmail.py index a45da27166..87b9d91e57 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -1014,7 +1014,7 @@ async def contact( embed = discord.Embed( color=self.bot.error_color, description="Cannot start a thread with a bot." ) - return await ctx.send(embed=embed, delete_afer=3) + return await ctx.send(embed=embed, delete_after=3) exists = await self.bot.threads.find(recipient=user) if exists: From ea52a8bf4079eee1293ebb2ccbef4dc45fe23416 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Sun, 25 Apr 2021 21:40:25 +0800 Subject: [PATCH 104/209] Changelog - v3.9.4 --- CHANGELOG.md | 20 ++++++++++++++++++-- bot.py | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df4c140165..ddb87eebc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,14 +6,30 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. +# v3.9.4 + +## Fixed + +- Certain cases where fallback categories were not working as intended. ([GH #3002](https://github.com/kyb3r/modmail/issues/3002), [PR #3003](https://github.com/kyb3r/modmail/pull/3003)) +- There is now a proper message when trying to contact a bot.+ + +## Improved + +- `?mention` can now be disabled with `?mention disable`. ([PR #2993](https://github.com/kyb3r/modmail/pull/2993/files)) +- `?mention` now allows vague entries such as `everyone` or `all`. ([PR #2993](https://github.com/kyb3r/modmail/pull/2993/files)) + +## Internal + +- Change heroku python version to 3.9.4 [PR #3001](https://github.com/kyb3r/modmail/pull/3001) + # v3.9.3 ## Added -- New config: ` use_user_id_channel_name`, when set to TRUE, channel names would get created with the recipient's ID instead of their name and discriminator. +- New config: `use_user_id_channel_name`, when set to TRUE, channel names would get created with the recipient's ID instead of their name and discriminator. - This is now an option to better suit the needs of servers in Server Discovery -## Internal Change +## Internal - Signature of `format_channel_name` in core/util.py changed to: - `format_channel_name(bot, author, exclude_channel=None, force_null=False)` diff --git a/bot.py b/bot.py index 994e956e11..1b9a9f2c1f 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.9.3" +__version__ = "3.9.4" import asyncio From 5241e5469a8631d2e95e9a89329c8d6758efca14 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Sun, 25 Apr 2021 21:54:47 +0800 Subject: [PATCH 105/209] Fix typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddb87eebc3..de46a80f38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ however, insignificant breaking changes do not guarantee a major version bump, s ## Fixed - Certain cases where fallback categories were not working as intended. ([GH #3002](https://github.com/kyb3r/modmail/issues/3002), [PR #3003](https://github.com/kyb3r/modmail/pull/3003)) -- There is now a proper message when trying to contact a bot.+ +- There is now a proper message when trying to contact a bot. ## Improved From 7ed1fcb8aafb83bc280a314643531c5c39ea72c2 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Sun, 25 Apr 2021 22:14:31 +0800 Subject: [PATCH 106/209] add fourjr claim --- CHANGELOG.md | 2 +- plugins/registry.json | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de46a80f38..12f1ddc9ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ however, insignificant breaking changes do not guarantee a major version bump, s ## Internal -- Change heroku python version to 3.9.4 [PR #3001](https://github.com/kyb3r/modmail/pull/3001) +- Change heroku python version to 3.9.4 ([PR #3001](https://github.com/kyb3r/modmail/pull/3001)) # v3.9.3 diff --git a/plugins/registry.json b/plugins/registry.json index 515871e916..579c3e3eb0 100644 --- a/plugins/registry.json +++ b/plugins/registry.json @@ -230,5 +230,13 @@ "bot_version": "3.9.0", "icon_url": "https://cdn.discordapp.com/avatars/180314310298304512/7552e0089004079304cc9912d13ac81d.png", "thumbnail_url": "https://cdn.discordapp.com/avatars/180314310298304512/7552e0089004079304cc9912d13ac81d.png" + }, + "claim": { + "repository": "fourjr/modmail-plugins", + "branch": "master", + "description": "Allows supporters to claim thread by sending ?claim in the thread channel", + "title": "Claim Thread", + "icon_url": "https://cdn.discordapp.com/avatars/180314310298304512/7552e0089004079304cc9912d13ac81d.png", + "thumbnail_url": "https://cdn.discordapp.com/avatars/180314310298304512/7552e0089004079304cc9912d13ac81d.png" } } From c1dcf7fb9264d8ebf6a658265a3a73ceb0dc4e77 Mon Sep 17 00:00:00 2001 From: redstonedesigner Date: Sun, 25 Apr 2021 15:46:49 +0100 Subject: [PATCH 107/209] Add volume for persistent data storage to Mongo --- docker-compose.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 7b4371b5c8..5d2061df6b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,3 +16,8 @@ services: - 8000:8000 mongo: image: mongo + volumes: + - mongodb:/data/db + +volumes: + mongodb: From 702486665202325687ba91eed961d2b066bb1de4 Mon Sep 17 00:00:00 2001 From: lorenzo132 <50767078+lorenzo132@users.noreply.github.com> Date: Sun, 25 Apr 2021 20:49:11 +0200 Subject: [PATCH 108/209] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b74dfad111..ff08d3ee31 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ exclude = ''' [tool.poetry] name = 'Modmail' -version = '3.9.3' +version = '3.9.4' description = "Modmail is similar to Reddit's Modmail, both in functionality and purpose. It serves as a shared inbox for server staff to communicate with their users in a seamless way." license = 'AGPL-3.0-only' authors = [ From aec76a045571b65208b6e6203c72a16d5ef6c317 Mon Sep 17 00:00:00 2001 From: lorenzo132 <50767078+lorenzo132@users.noreply.github.com> Date: Sun, 25 Apr 2021 20:49:44 +0200 Subject: [PATCH 109/209] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 457569b32e..ea07edb304 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@
- +
From 3717df646644159c5a0e713aa83bfcdbf768dcdb Mon Sep 17 00:00:00 2001 From: Taku 3 Animals <45324516+Taaku18@users.noreply.github.com> Date: Tue, 27 Apr 2021 07:32:00 +0800 Subject: [PATCH 110/209] Update sponsors --- README.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 457569b32e..ad07d59b1e 100644 --- a/README.md +++ b/README.md @@ -165,21 +165,29 @@ Special thanks to our sponsors for supporting the project. Kingdom Gaming Discord:
- +

SirReddit:
- +
- +
+Prime Servers Inc: +
- + + +
+
+Real Madrid: +
+ + - Become a sponsor on [Patreon](https://patreon.com/kyber). From b101530b72908098eb67e85bae82d1cff000fa6b Mon Sep 17 00:00:00 2001 From: Stephen <48072084+StephenDaDev@users.noreply.github.com> Date: Wed, 28 Apr 2021 07:58:49 -0400 Subject: [PATCH 111/209] Fix typo in config help. --- core/config_help.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/config_help.json b/core/config_help.json index ff6be5cba1..ede81617ed 100644 --- a/core/config_help.json +++ b/core/config_help.json @@ -421,7 +421,7 @@ "default": "Thread Moved", "description": "The title of the message embed when a thread is moved.", "examples": [ - "`{prefix}config set thread_move_title Thread transferred to another channel!" + "`{prefix}config set thread_move_title Thread transferred to another channel!`" ], "notes": [ "See also: `thread_move_notify`, `thread_move_notify_mods`, `thread_move_response`." From 9bb615bba932887f4ab0474ceb4a5bde240a010b Mon Sep 17 00:00:00 2001 From: Jerrie-Aries <70805800+Jerrie-Aries@users.noreply.github.com> Date: Wed, 28 Apr 2021 20:31:23 +0800 Subject: [PATCH 112/209] Update 'bot.py' and 'clients.py': - Fix return types, type hints, and unresolved references. --- bot.py | 7 +++++-- core/clients.py | 12 +++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/bot.py b/bot.py index 1b9a9f2c1f..cce02ceff2 100644 --- a/bot.py +++ b/bot.py @@ -237,6 +237,7 @@ async def runner(): if self._session: await self._session.close() + # noinspection PyUnusedLocal def stop_loop_on_completion(f): loop.stop() @@ -811,7 +812,7 @@ async def is_blocked( *, channel: discord.TextChannel = None, send_message: bool = False, - ) -> typing.Tuple[bool, str]: + ) -> bool: member = self.guild.get_member(author.id) if member is None: @@ -892,7 +893,9 @@ async def get_thread_cooldown(self, author: discord.Member): return @staticmethod - async def add_reaction(msg, reaction: discord.Reaction) -> bool: + async def add_reaction( + msg, reaction: typing.Union[discord.Emoji, discord.Reaction, discord.PartialEmoji, str] + ) -> bool: if reaction != "disable": try: await msg.add_reaction(reaction) diff --git a/core/clients.py b/core/clients.py index db4e9936b4..f3b2248c67 100644 --- a/core/clients.py +++ b/core/clients.py @@ -68,7 +68,7 @@ class GitHub: def __init__(self, bot, access_token: str = "", username: str = "", **kwargs): self.bot = bot self.session = bot.session - self.headers: dict = None + self.headers: Optional[dict] = None self.access_token = access_token self.username = username self.avatar_url: str = kwargs.pop("avatar_url", "") @@ -319,7 +319,7 @@ async def get_config(self) -> dict: async def update_config(self, data: dict): return NotImplemented - async def edit_message(self, message_id: Union[int, str], new_content: str) -> None: + async def edit_message(self, message_id: Union[int, str], new_content: str): return NotImplemented async def append_log( @@ -359,6 +359,12 @@ async def edit_note(self, message_id: Union[int, str], message: str): def get_plugin_partition(self, cog): return NotImplemented + async def update_repository(self) -> dict: + return NotImplemented + + async def get_user_info(self) -> Optional[dict]: + return NotImplemented + class MongoDBClient(ApiClient): def __init__(self, bot): @@ -659,7 +665,7 @@ async def update_repository(self) -> dict: "user": {"username": user.username, "avatar_url": user.avatar_url, "url": user.url,}, } - async def get_user_info(self) -> dict: + async def get_user_info(self) -> Optional[dict]: try: user = await GitHub.login(self.bot) except InvalidConfigError: From 6b006129aa6fec35e893605b787b5d8afb6ade6f Mon Sep 17 00:00:00 2001 From: Jerrie-Aries <70805800+Jerrie-Aries@users.noreply.github.com> Date: Thu, 29 Apr 2021 07:21:08 +0800 Subject: [PATCH 113/209] Update database after resetting/purging all plugins. --- cogs/plugins.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cogs/plugins.py b/cogs/plugins.py index 94a3f425c0..bc664aef44 100644 --- a/cogs/plugins.py +++ b/cogs/plugins.py @@ -539,6 +539,7 @@ async def plugins_reset(self, ctx): except Exception: logger.error("Failed to unload plugin: %s.", ext) self.bot.config["plugins"].clear() + await self.bot.config.update() cache_path = Path(__file__).absolute().parent.parent / "temp" / "plugins-cache" if cache_path.exists(): From fbb45e37829c2f17d81e5036794d197d14d30992 Mon Sep 17 00:00:00 2001 From: Jerrie-Aries <70805800+Jerrie-Aries@users.noreply.github.com> Date: Fri, 30 Apr 2021 19:40:17 +0800 Subject: [PATCH 114/209] `plugin reset`, remove unloaded plugins from `self.loaded_plugins` --- cogs/plugins.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cogs/plugins.py b/cogs/plugins.py index bc664aef44..a01f4bc61c 100644 --- a/cogs/plugins.py +++ b/cogs/plugins.py @@ -538,6 +538,13 @@ async def plugins_reset(self, ctx): 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: + self.loaded_plugins.remove(plugin) + self.bot.config["plugins"].clear() await self.bot.config.update() From 039b4b91ae7dd9e86b6ff06f582483bab57ec1ea Mon Sep 17 00:00:00 2001 From: Cyrus Yip Date: Tue, 4 May 2021 21:11:34 -0700 Subject: [PATCH 115/209] turn mentions into set --- core/thread.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/thread.py b/core/thread.py index 530e063e54..76516af8b3 100644 --- a/core/thread.py +++ b/core/thread.py @@ -1061,7 +1061,7 @@ def get_notifications(self) -> str: self.bot.config["notification_squad"].pop(key) self.bot.loop.create_task(self.bot.config.update()) - return " ".join(mentions) + return " ".join(set(mentions)) async def set_title(self, title) -> None: user_id = match_user_id(self.channel.topic) From ef4b5b2f1a8fbea7e1e201bf8d4078bb4170395b Mon Sep 17 00:00:00 2001 From: Jerrie-Aries <70805800+Jerrie-Aries@users.noreply.github.com> Date: Tue, 11 May 2021 21:05:51 +0800 Subject: [PATCH 116/209] Hopefully resolves #3022. --- core/thread.py | 58 ++++++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/core/thread.py b/core/thread.py index 530e063e54..8eccb04c02 100644 --- a/core/thread.py +++ b/core/thread.py @@ -1115,16 +1115,16 @@ async def find( try: await thread.wait_until_ready() except asyncio.CancelledError: - logger.warning("Thread for %s cancelled, abort creating", recipient) + logger.warning("Thread for %s cancelled.", recipient) return thread else: - if not thread.channel or not self.bot.get_channel(thread.channel.id): + if not thread.cancelled and ( + not thread.channel or not self.bot.get_channel(thread.channel.id) + ): logger.warning( "Found existing thread for %s but the channel is invalid.", recipient_id ) - self.bot.loop.create_task( - thread.close(closer=self.bot.user, silent=True, delete_channel=False) - ) + await thread.close(closer=self.bot.user, silent=True, delete_channel=False) thread = None else: channel = discord.utils.get( @@ -1186,7 +1186,7 @@ async def create( try: await thread.wait_until_ready() except asyncio.CancelledError: - logger.warning("Thread for %s cancelled, abort creating", recipient) + logger.warning("Thread for %s cancelled, abort creating.", recipient) return thread else: if thread.channel and self.bot.get_channel(thread.channel.id): @@ -1195,9 +1195,7 @@ async def create( logger.warning( "Found an existing thread for %s, closing previous thread.", recipient ) - self.bot.loop.create_task( - thread.close(closer=self.bot.user, silent=True, delete_channel=False) - ) + await thread.close(closer=self.bot.user, silent=True, delete_channel=False) thread = Thread(self, recipient) @@ -1231,9 +1229,11 @@ async def create( ) accept_emoji = self.bot.config["confirm_thread_creation_accept"] deny_emoji = self.bot.config["confirm_thread_creation_deny"] - await confirm.add_reaction(accept_emoji) - await asyncio.sleep(0.2) - await confirm.add_reaction(deny_emoji) + emojis = [accept_emoji, deny_emoji] + for emoji in emojis: + await confirm.add_reaction(emoji) + await asyncio.sleep(0.2) + try: r, _ = await self.bot.wait_for( "reaction_add", @@ -1245,29 +1245,31 @@ async def create( ) except asyncio.TimeoutError: thread.cancelled = True - - await confirm.remove_reaction(accept_emoji, self.bot.user) - await asyncio.sleep(0.2) - await confirm.remove_reaction(deny_emoji, self.bot.user) - await destination.send( - embed=discord.Embed( - title="Cancelled", description="Timed out", color=self.bot.error_color + self.bot.loop.create_task( + destination.send( + embed=discord.Embed( + title="Cancelled", description="Timed out", color=self.bot.error_color + ) ) ) - del self.cache[recipient.id] - return thread else: if str(r.emoji) == deny_emoji: thread.cancelled = True + self.bot.loop.create_task( + destination.send( + embed=discord.Embed(title="Cancelled", color=self.bot.error_color) + ) + ) - await confirm.remove_reaction(accept_emoji, self.bot.user) + async def remove_reactions(): + for emoji in emojis: + await confirm.remove_reaction(emoji, self.bot.user) await asyncio.sleep(0.2) - await confirm.remove_reaction(deny_emoji, self.bot.user) - await destination.send( - embed=discord.Embed(title="Cancelled", color=self.bot.error_color) - ) - del self.cache[recipient.id] - return thread + + self.bot.loop.create_task(remove_reactions()) + if thread.cancelled: + del self.cache[recipient.id] + return thread self.bot.loop.create_task( thread.setup(creator=creator, category=category, initial_message=message) From 08bc39b4c00b09a2502fbfdd1822021827668abc Mon Sep 17 00:00:00 2001 From: Jerrie-Aries <70805800+Jerrie-Aries@users.noreply.github.com> Date: Tue, 11 May 2021 21:22:45 +0800 Subject: [PATCH 117/209] Improve contact and react to contact: - Checks if user is blocked when `?contact` command or react to contact is used. --- bot.py | 83 +++++++++++++++++++++++++------------------------ cogs/modmail.py | 37 +++++++++++++--------- 2 files changed, 65 insertions(+), 55 deletions(-) diff --git a/bot.py b/bot.py index 1b9a9f2c1f..ccececf8d6 100644 --- a/bot.py +++ b/bot.py @@ -1304,50 +1304,51 @@ async def handle_reaction_events(self, payload): except (discord.HTTPException, discord.InvalidArgument) as e: logger.warning("Failed to remove reaction: %s", e) - async def on_raw_reaction_add(self, payload): - await self.handle_reaction_events(payload) - + async def handle_react_to_contact(self, payload): react_message_id = tryint(self.config.get("react_to_contact_message")) react_message_emoji = self.config.get("react_to_contact_emoji") - if all((react_message_id, react_message_emoji)): - if payload.message_id == react_message_id: - if payload.emoji.is_unicode_emoji(): - emoji_fmt = payload.emoji.name - else: - emoji_fmt = f"<:{payload.emoji.name}:{payload.emoji.id}>" - - if emoji_fmt == react_message_emoji: - channel = self.get_channel(payload.channel_id) - member = channel.guild.get_member(payload.user_id) - if not member.bot: - message = await channel.fetch_message(payload.message_id) - await message.remove_reaction(payload.emoji, member) - await message.add_reaction(emoji_fmt) # bot adds as well - - if self.config["dm_disabled"] in ( - DMDisabled.NEW_THREADS, - DMDisabled.ALL_THREADS, - ): - embed = discord.Embed( - title=self.config["disabled_new_thread_title"], - color=self.error_color, - description=self.config["disabled_new_thread_response"], - ) - embed.set_footer( - text=self.config["disabled_new_thread_footer"], - icon_url=self.guild.icon_url, - ) - logger.info( - "A new thread using react to contact was blocked from %s due to disabled Modmail.", - member, - ) - return await member.send(embed=embed) + if ( + not all((react_message_id, react_message_emoji)) + or payload.message_id != react_message_id + ): + return + if payload.emoji.is_unicode_emoji(): + emoji_fmt = payload.emoji.name + else: + emoji_fmt = f"<:{payload.emoji.name}:{payload.emoji.id}>" - ctx = await self.get_context(message) - ctx.author = member - await ctx.invoke( - self.get_command("contact"), user=member, manual_trigger=False - ) + if emoji_fmt != react_message_emoji: + return + channel = self.get_channel(payload.channel_id) + member = channel.guild.get_member(payload.user_id) + if member.bot: + return + message = await channel.fetch_message(payload.message_id) + await message.remove_reaction(payload.emoji, member) + await message.add_reaction(emoji_fmt) # bot adds as well + + if self.config["dm_disabled"] in (DMDisabled.NEW_THREADS, DMDisabled.ALL_THREADS): + embed = discord.Embed( + title=self.config["disabled_new_thread_title"], + color=self.error_color, + description=self.config["disabled_new_thread_response"], + ) + embed.set_footer( + text=self.config["disabled_new_thread_footer"], icon_url=self.guild.icon_url, + ) + logger.info( + "A new thread using react to contact was blocked from %s due to disabled Modmail.", + member, + ) + return await member.send(embed=embed) + + ctx = await self.get_context(message) + await ctx.invoke(self.get_command("contact"), user=member, manual_trigger=False) + + async def on_raw_reaction_add(self, payload): + await asyncio.gather( + self.handle_reaction_events(payload), self.handle_react_to_contact(payload), + ) async def on_raw_reaction_remove(self, payload): if self.config["transfer_reactions"]: diff --git a/cogs/modmail.py b/cogs/modmail.py index 87b9d91e57..08930aa946 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -1002,7 +1002,7 @@ async def contact( `category`, if specified, may be a category ID, mention, or name. `user` may be a user ID, mention, or name. - `options` can be `silent` + `options` can be `silent` or `silently`. """ silent = False if isinstance(category, str): @@ -1018,19 +1018,28 @@ async def contact( exists = await self.bot.threads.find(recipient=user) if exists: - embed = discord.Embed( - color=self.bot.error_color, - description="A thread for this user already " - f"exists in {exists.channel.mention}.", - ) + desc = "A thread for this user already exists" + if exists.channel: + desc += f" in {exists.channel.mention}" + desc += "." + embed = discord.Embed(color=self.bot.error_color, description=desc) await ctx.channel.send(embed=embed, delete_after=3) else: + creator = ctx.author if manual_trigger else user + if await self.bot.is_blocked(user): + if not manual_trigger: # react to contact + return + + ref = f"{user.mention} is" if creator != user else "You are" + embed = discord.Embed( + color=self.bot.error_color, + description=f"{ref} currently blocked from contacting {self.bot.user.name}.", + ) + return await ctx.send(embed=embed) + thread = await self.bot.threads.create( - recipient=user, - creator=ctx.author, - category=category, - manual_trigger=manual_trigger, + recipient=user, creator=creator, category=category, manual_trigger=manual_trigger, ) if thread.cancelled: return @@ -1039,22 +1048,22 @@ async def contact( logger.info("Contacting user %s when Modmail DM is disabled.", user) if not silent and not self.bot.config.get("thread_contact_silently"): - if ctx.author.id == user.id: + if creator.id == user.id: description = "You have opened a Modmail thread." else: - description = f"{ctx.author.name} has opened a Modmail thread." + description = f"{creator.name} has opened a Modmail thread." em = discord.Embed( title="New Thread", description=description, color=self.bot.main_color, ) if self.bot.config["show_timestamp"]: em.timestamp = datetime.utcnow() - em.set_footer(icon_url=ctx.author.avatar_url) + em.set_footer(text=f"{creator}", icon_url=creator.avatar_url) await user.send(embed=em) embed = discord.Embed( title="Created Thread", - description=f"Thread started by {ctx.author.mention} for {user.mention}.", + description=f"Thread started by {creator.mention} for {user.mention}.", color=self.bot.main_color, ) await thread.wait_until_ready() From a6c24d952ce287bc842ddb864b26fff28bd22d3c Mon Sep 17 00:00:00 2001 From: codeinteger6 <44692189+codeinteger6@users.noreply.github.com> Date: Fri, 14 May 2021 16:12:26 +0600 Subject: [PATCH 118/209] Remove publish plugin --- plugins/registry.json | 9 --------- 1 file changed, 9 deletions(-) diff --git a/plugins/registry.json b/plugins/registry.json index 579c3e3eb0..b74ff1f989 100644 --- a/plugins/registry.json +++ b/plugins/registry.json @@ -187,15 +187,6 @@ "icon_url": "https://cdn.discordapp.com/attachments/717029057635549274/717033838966210601/Slow_mode_-_icon.png", "thumbnail_url": "https://cdn.discordapp.com/attachments/717029057635549274/717029110907666482/Slow_mode_plugin_-_thumbnail.png" }, - "publish": { - "repository": "codeinteger6/modmail-plugins", - "branch": "master", - "description": "Publish messages sent in announcement channels.", - "bot_version": "3.5.0", - "title": "Publish", - "icon_url": "https://user-images.githubusercontent.com/44692189/89184422-96de3600-d5ba-11ea-98ea-d096aa385ad5.png", - "thumbnail_url": "https://user-images.githubusercontent.com/44692189/89184422-96de3600-d5ba-11ea-98ea-d096aa385ad5.png" - }, "translate": { "repository": "WebKide/modmail-plugins", "branch": "master", From 7073fcac2aaf1cea4cb493f450e4060d5a90362a Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Mon, 24 May 2021 15:39:30 +0800 Subject: [PATCH 119/209] Initial 3.9.5-dev1 --- CHANGELOG.md | 7 +++++++ README.md | 2 +- bot.py | 2 +- pyproject.toml | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12f1ddc9ef..bdb75a3ce6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. +# v3.9.5-dev1 + +## Fixed + +- Certain situations where the internal thread cache breaks and spams new channels. ([GH #3022](https://github.com/kyb3r/modmail/issues/3022), [PR #3028](https://github.com/kyb3r/modmail/pull/3028)) +- Blocked users are now no longer allowed to use `?contact` and react to contact. ([COMMENT #819004157](https://github.com/kyb3r/modmail/issues/2969#issuecomment-819004157), [PR #3027](https://github.com/kyb3r/modmail/pull/3027)) + # v3.9.4 ## Fixed diff --git a/README.md b/README.md index ad07d59b1e..c150bae3c5 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@
- +
diff --git a/bot.py b/bot.py index ccececf8d6..eccdfd3868 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.9.4" +__version__ = "3.9.5-dev1" import asyncio diff --git a/pyproject.toml b/pyproject.toml index b74dfad111..a4d4002580 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ exclude = ''' [tool.poetry] name = 'Modmail' -version = '3.9.3' +version = '3.9.5-dev1' description = "Modmail is similar to Reddit's Modmail, both in functionality and purpose. It serves as a shared inbox for server staff to communicate with their users in a seamless way." license = 'AGPL-3.0-only' authors = [ From 204c5385a8d88c676d2cc7f85ce7255bdf04201d Mon Sep 17 00:00:00 2001 From: lorenzo132 <50767078+lorenzo132@users.noreply.github.com> Date: Mon, 31 May 2021 10:55:07 +0200 Subject: [PATCH 120/209] Update registry.json removal of profanity filter, trying to download will result in a infinite download loop and will stop any plugin from loading/break the usage of removing/adding new plugins --- plugins/registry.json | 9 --------- 1 file changed, 9 deletions(-) diff --git a/plugins/registry.json b/plugins/registry.json index b74ff1f989..a9f565a013 100644 --- a/plugins/registry.json +++ b/plugins/registry.json @@ -1,13 +1,4 @@ { - "profanity-filter": { - "repository": "kyb3r/modmail-plugins", - "branch": "master", - "description": "Checks for profanity in a message using machine learning and deletes it.", - "bot_version": "2.20.1", - "title": "Profanity Filter Plugin", - "icon_url": "https://i.imgur.com/951szZ3.jpg", - "thumbnail_url": "https://i.imgur.com/951szZ3.jpg" - }, "dragory-migrate": { "repository": "kyb3r/modmail-plugins", "branch": "master", From 5648289fa7546a090e24fa7855b3f82431cbedce Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Wed, 2 Jun 2021 21:23:05 +0800 Subject: [PATCH 121/209] Group conversation ALPHA #143 --- CHANGELOG.md | 6 ++- bot.py | 28 ++++++++--- cogs/modmail.py | 59 ++++++++++++++++++++-- core/thread.py | 128 ++++++++++++++++++++++++++++++++++-------------- core/utils.py | 26 +++++++++- 5 files changed, 197 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdb75a3ce6..c003f1db37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,11 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. -# v3.9.5-dev1 +# v3.10.0-dev2 + +## Added + +- Ability to have group conversations. ([GH #143](https://github.com/kyb3r/modmail/issues/143)) ## Fixed diff --git a/bot.py b/bot.py index eccdfd3868..456e340218 100644 --- a/bot.py +++ b/bot.py @@ -964,6 +964,15 @@ async def process_dm_modmail(self, message: discord.Message) -> None: logger.error("Failed to send message:", exc_info=True) await self.add_reaction(message, blocked_emoji) else: + for user in thread.recipients: + # send to all other recipients + if user != message.author: + try: + await thread.send(message, user) + except Exception: + # silently ignore + logger.error("Failed to send message:", exc_info=True) + await self.add_reaction(message, sent_emoji) self.dispatch("thread_reply", thread, False, message, False, False) @@ -1224,9 +1233,10 @@ async def on_typing(self, channel, user, _): thread = await self.threads.find(channel=channel) if thread is not None and thread.recipient: - if await self.is_blocked(thread.recipient): - return - await thread.recipient.trigger_typing() + for user in thread.recipients: + if await self.is_blocked(user): + continue + await user.trigger_typing() async def handle_reaction_events(self, payload): user = self.get_user(payload.user_id) @@ -1286,20 +1296,22 @@ async def handle_reaction_events(self, payload): if not thread: return try: - _, linked_message = await thread.find_linked_messages( + _, *linked_message = await thread.find_linked_messages( message.id, either_direction=True ) except ValueError as e: logger.warning("Failed to find linked message for reactions: %s", e) return - if self.config["transfer_reactions"] and linked_message is not None: + if self.config["transfer_reactions"] and linked_message is not [None]: if payload.event_type == "REACTION_ADD": - if await self.add_reaction(linked_message, reaction): - await self.add_reaction(message, reaction) + for msg in linked_message: + await self.add_reaction(msg, reaction) + await self.add_reaction(message, reaction) else: try: - await linked_message.remove_reaction(reaction, self.user) + for msg in linked_message: + await msg.remove_reaction(reaction, self.user) await message.remove_reaction(reaction, self.user) except (discord.HTTPException, discord.InvalidArgument) as e: logger.warning("Failed to remove reaction: %s", e) diff --git a/cogs/modmail.py b/cogs/modmail.py index 08930aa946..ef816a99c3 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -681,6 +681,48 @@ async def title(self, ctx, *, name: str): await ctx.message.pin() await self.bot.add_reaction(ctx.message, sent_emoji) + @commands.command(cooldown_after_parsing=True) + @checks.has_permissions(PermissionLevel.SUPPORTER) + @checks.thread_only() + @commands.cooldown(1, 600, BucketType.channel) + async def adduser(self, ctx, *, user: discord.Member): + """Adds a user to a modmail thread""" + + curr_thread = await self.bot.threads.find(recipient=user) + if curr_thread: + em = discord.Embed( + title="Error", + description=f"User is already in a thread: {curr_thread.channel.mention}.", + color=self.bot.error_color, + ) + await ctx.send(embed=em) + else: + em = discord.Embed( + title="New Thread (Group)", + description=f"{ctx.author.name} has added you to a Modmail thread.", + color=self.bot.main_color, + ) + if self.bot.config["show_timestamp"]: + em.timestamp = datetime.utcnow() + em.set_footer(text=f"{ctx.author}", icon_url=ctx.author.avatar_url) + await user.send(embed=em) + + em = discord.Embed( + title="New User", + description=f"{ctx.author.name} has added {user.name} to the Modmail thread.", + color=self.bot.main_color, + ) + if self.bot.config["show_timestamp"]: + em.timestamp = datetime.utcnow() + em.set_footer(text=f"{user}", icon_url=user.avatar_url) + + for i in ctx.thread.recipients: + await i.send(embed=em) + + await ctx.thread.add_user(user) + sent_emoji, _ = await self.bot.retrieve_emoji() + await self.bot.add_reaction(ctx.message, sent_emoji) + @commands.group(invoke_without_command=True) @checks.has_permissions(PermissionLevel.SUPPORTER) async def logs(self, ctx, *, user: User = None): @@ -1463,15 +1505,19 @@ async def repair(self, ctx): and message.embeds[0].footer.text ): user_id = match_user_id(message.embeds[0].footer.text) + other_recipients = match_other_recipients(ctx.channel.topic) + for n, uid in enumerate(other_recipients): + other_recipients[n] = self.bot.get_user(uid) or await self.bot.fetch_user(uid) + if user_id != -1: recipient = self.bot.get_user(user_id) if recipient is None: self.bot.threads.cache[user_id] = thread = Thread( - self.bot.threads, user_id, ctx.channel + self.bot.threads, user_id, ctx.channel, other_recipients ) else: self.bot.threads.cache[user_id] = thread = Thread( - self.bot.threads, recipient, ctx.channel + self.bot.threads, recipient, ctx.channel, other_recipients ) thread.ready = True logger.info( @@ -1516,13 +1562,18 @@ async def repair(self, ctx): await thread.channel.send(embed=embed) except discord.HTTPException: pass + + other_recipients = match_other_recipients(ctx.channel.topic) + for n, uid in enumerate(other_recipients): + other_recipients[n] = self.bot.get_user(uid) or await self.bot.fetch_user(uid) + if recipient is None: self.bot.threads.cache[user.id] = thread = Thread( - self.bot.threads, user_id, ctx.channel + self.bot.threads, user_id, ctx.channel, other_recipients ) else: self.bot.threads.cache[user.id] = thread = Thread( - self.bot.threads, recipient, ctx.channel + self.bot.threads, recipient, ctx.channel, other_recipients ) thread.ready = True logger.info("Setting current channel's topic to User ID and created new thread.") diff --git a/core/thread.py b/core/thread.py index 8eccb04c02..5638acbe44 100644 --- a/core/thread.py +++ b/core/thread.py @@ -19,6 +19,7 @@ days, match_title, match_user_id, + match_other_recipients, truncate, format_channel_name, ) @@ -34,6 +35,7 @@ def __init__( manager: "ThreadManager", recipient: typing.Union[discord.Member, discord.User, int], channel: typing.Union[discord.DMChannel, discord.TextChannel] = None, + other_recipients: typing.List[typing.Union[discord.Member, discord.User]] = [], ): self.manager = manager self.bot = manager.bot @@ -45,6 +47,7 @@ def __init__( raise CommandError("Recipient cannot be a bot.") self._id = recipient.id self._recipient = recipient + self._other_recipients = other_recipients self._channel = channel self.genesis_message = None self._ready_event = asyncio.Event() @@ -54,7 +57,7 @@ def __init__( self._cancelled = False def __repr__(self): - return f'Thread(recipient="{self.recipient or self.id}", channel={self.channel.id})' + return f'Thread(recipient="{self.recipient or self.id}", channel={self.channel.id}, other_recipienets={len(self._other_recipients)})' async def wait_until_ready(self) -> None: """Blocks execution until the thread is fully set up.""" @@ -80,6 +83,10 @@ def channel(self) -> typing.Union[discord.TextChannel, discord.DMChannel]: def recipient(self) -> typing.Optional[typing.Union[discord.User, discord.Member]]: return self._recipient + @property + def recipients(self) -> typing.List[typing.Union[discord.User, discord.Member]]: + return [self._recipient] + self._other_recipients + @property def ready(self) -> bool: return self._ready_event.is_set() @@ -103,6 +110,23 @@ def cancelled(self, flag: bool): for i in self.wait_tasks: i.cancel() + @classmethod + async def from_channel( + cls, manager: "ThreadManager", channel: discord.TextChannel + ) -> "Thread": + recipient_id = match_user_id( + channel.topic + ) # there is a chance it grabs from another recipient's main thread + recipient = manager.bot.get_user(recipient_id) or await manager.bot.fetch_user( + recipient_id + ) + + other_recipients = match_other_recipients(channel.topic) + for n, uid in enumerate(other_recipients): + other_recipients[n] = manager.bot.get_user(uid) or await manager.bot.fetch_user(uid) + + return cls(manager, recipient or recipient_id, channel, other_recipients) + async def setup(self, *, creator=None, category=None, initial_message=None): """Create the thread channel and other io related initialisation tasks""" self.bot.dispatch("thread_initiate", self, creator, category, initial_message) @@ -619,23 +643,30 @@ async def find_linked_messages( except ValueError: raise ValueError("Malformed thread message.") - async for msg in self.recipient.history(): - if either_direction: - if msg.id == joint_id: - return message1, msg + messages = [message1] + for user in self.recipients: + async for msg in user.history(): + if either_direction: + if msg.id == joint_id: + return message1, msg - if not (msg.embeds and msg.embeds[0].author.url): - continue - try: - if int(msg.embeds[0].author.url.split("#")[-1]) == joint_id: - return message1, msg - except ValueError: - continue - raise ValueError("DM message not found. Plain messages are not supported.") + if not (msg.embeds and msg.embeds[0].author.url): + continue + try: + if int(msg.embeds[0].author.url.split("#")[-1]) == joint_id: + messages.append(msg) + break + except ValueError: + continue + + if len(messages) > 1: + return messages + + raise ValueError("DM message not found.") async def edit_message(self, message_id: typing.Optional[int], message: str) -> None: try: - message1, message2 = await self.find_linked_messages(message_id) + message1, *message2 = await self.find_linked_messages(message_id) except ValueError: logger.warning("Failed to edit message.", exc_info=True) raise @@ -644,10 +675,11 @@ async def edit_message(self, message_id: typing.Optional[int], message: str) -> embed1.description = message tasks = [self.bot.api.edit_message(message1.id, message), message1.edit(embed=embed1)] - if message2 is not None: - embed2 = message2.embeds[0] - embed2.description = message - tasks += [message2.edit(embed=embed2)] + if message2 is not [None]: + for m2 in message2: + embed2 = message2.embeds[0] + embed2.description = message + tasks += [m2.edit(embed=embed2)] elif message1.embeds[0].author.name.startswith("Persistent Note"): tasks += [self.bot.api.edit_note(message1.id, message)] @@ -657,14 +689,16 @@ async def delete_message( self, message: typing.Union[int, discord.Message] = None, note: bool = True ) -> None: if isinstance(message, discord.Message): - message1, message2 = await self.find_linked_messages(message1=message, note=note) + message1, *message2 = await self.find_linked_messages(message1=message, note=note) else: - message1, message2 = await self.find_linked_messages(message, note=note) + message1, *message2 = await self.find_linked_messages(message, note=note) + print(message1, message2) tasks = [] if not isinstance(message, discord.Message): tasks += [message1.delete()] - elif message2 is not None: - tasks += [message2.delete()] + elif message2 is not [None]: + for m2 in message2: + tasks += [m2.delete()] elif message1.embeds[0].author.name.startswith("Persistent Note"): tasks += [self.bot.api.delete_note(message1.id)] if tasks: @@ -750,16 +784,18 @@ async def reply( ) ) + user_msg_tasks = [] tasks = [] - try: - user_msg = await self.send( - message, - destination=self.recipient, - from_mod=True, - anonymous=anonymous, - plain=plain, + for user in self.recipients: + user_msg_tasks.append( + self.send( + message, destination=user, from_mod=True, anonymous=anonymous, plain=plain, + ) ) + + try: + user_msg = await asyncio.gather(*user_msg_tasks) except Exception as e: logger.error("Message delivery failed:", exc_info=True) if isinstance(e, discord.Forbidden): @@ -1063,9 +1099,23 @@ def get_notifications(self) -> str: return " ".join(mentions) - async def set_title(self, title) -> None: + async def set_title(self, title: str) -> None: + user_id = match_user_id(self.channel.topic) + ids = ",".join(i.id for i in self._other_recipients) + + await self.channel.edit( + topic=f"Title: {title}\nUser ID: {user_id}\nOther Recipients: {ids}" + ) + + async def add_user(self, user: typing.Union[discord.Member, discord.User]) -> None: + title = match_title(self.channel.topic) user_id = match_user_id(self.channel.topic) - await self.channel.edit(topic=f"Title: {title}\nUser ID: {user_id}") + self._other_recipients.append(user) + + ids = ",".join(str(i.id) for i in self._other_recipients) + await self.channel.edit( + topic=f"Title: {title}\nUser ID: {user_id}\nOther Recipients: {ids}" + ) class ThreadManager: @@ -1127,11 +1177,13 @@ async def find( await thread.close(closer=self.bot.user, silent=True, delete_channel=False) thread = None else: - channel = discord.utils.get( - self.bot.modmail_guild.text_channels, topic=f"User ID: {recipient_id}" + channel = discord.utils.find( + lambda x: str(recipient_id) in x.topic if x.topic else False, + self.bot.modmail_guild.text_channels, ) + if channel: - thread = Thread(self, recipient or recipient_id, channel) + thread = await Thread.from_channel(self, channel) if thread.recipient: # only save if data is valid self.cache[recipient_id] = thread @@ -1161,10 +1213,14 @@ async def _find_from_channel(self, channel): except discord.NotFound: recipient = None + other_recipients = match_other_recipients(channel.topic) + for n, uid in enumerate(other_recipients): + other_recipients[n] = self.bot.get_user(uid) or await self.bot.fetch_user(uid) + if recipient is None: - thread = Thread(self, user_id, channel) + thread = Thread(self, user_id, channel, other_recipients) else: - self.cache[user_id] = thread = Thread(self, recipient, channel) + self.cache[user_id] = thread = Thread(self, recipient, channel, other_recipients) thread.ready = True return thread diff --git a/core/utils.py b/core/utils.py index 2dfb319a8e..b95200cdfe 100644 --- a/core/utils.py +++ b/core/utils.py @@ -21,7 +21,9 @@ "human_join", "days", "cleanup_code", + "match_title", "match_user_id", + "match_other_recipients", "create_not_found_embed", "parse_alias", "normalize_alias", @@ -30,7 +32,6 @@ "escape_code_block", "format_channel_name", "tryint", - "match_title", ] @@ -217,6 +218,9 @@ def cleanup_code(content: str) -> str: return content.strip("` \n") +TOPIC_OTHER_RECIPIENTS_REGEX = re.compile( + r"Other Recipients:\s*((?:\d{17,21},*)+)", flags=re.IGNORECASE +) TOPIC_TITLE_REGEX = re.compile(r"\bTitle: (.*)\n(?:User ID: )\b", flags=re.IGNORECASE | re.DOTALL) TOPIC_UID_REGEX = re.compile(r"\bUser ID:\s*(\d{17,21})\b", flags=re.IGNORECASE) @@ -260,6 +264,26 @@ def match_user_id(text: str) -> int: return -1 +def match_other_recipients(text: str) -> int: + """ + Matches a title in the format of "Other Recipients: XXXX,XXXX" + + Parameters + ---------- + text : str + The text of the user ID. + + Returns + ------- + Optional[str] + The title if found + """ + match = TOPIC_OTHER_RECIPIENTS_REGEX.search(text) + if match is not None: + return list(map(int, match.group(1).split(","))) + return [] + + def create_not_found_embed(word, possibilities, name, n=2, cutoff=0.6) -> discord.Embed: # Single reference of Color.red() embed = discord.Embed( From 82047db09e2c726b61a3360507cf26f5c3a29474 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Wed, 2 Jun 2021 21:23:23 +0800 Subject: [PATCH 122/209] Remove debug --- cogs/utility.py | 1 - core/thread.py | 1 - 2 files changed, 2 deletions(-) diff --git a/cogs/utility.py b/cogs/utility.py index 7b39631dcd..4ac02b9073 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -1780,7 +1780,6 @@ async def autotrigger_add(self, ctx, keyword, *, command): split_cmd = command.split(" ") for n in range(1, len(split_cmd) + 1): if self.bot.get_command(" ".join(split_cmd[0:n])): - print(self.bot.get_command(" ".join(split_cmd[0:n]))) valid = True break diff --git a/core/thread.py b/core/thread.py index 5638acbe44..5c1e596051 100644 --- a/core/thread.py +++ b/core/thread.py @@ -692,7 +692,6 @@ async def delete_message( message1, *message2 = await self.find_linked_messages(message1=message, note=note) else: message1, *message2 = await self.find_linked_messages(message, note=note) - print(message1, message2) tasks = [] if not isinstance(message, discord.Message): tasks += [message1.delete()] From f6b5bf73a0c603e9ade170c452bea1f1a5304785 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Wed, 2 Jun 2021 21:41:04 +0800 Subject: [PATCH 123/209] Push ver --- README.md | 2 +- bot.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c150bae3c5..9e662a7fb3 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@
- +
diff --git a/bot.py b/bot.py index 456e340218..277050a627 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.9.5-dev1" +__version__ = "v3.10.0-dev2" import asyncio diff --git a/pyproject.toml b/pyproject.toml index a4d4002580..4e6959fe70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ exclude = ''' [tool.poetry] name = 'Modmail' -version = '3.9.5-dev1' +version = '3.10.0-dev2' description = "Modmail is similar to Reddit's Modmail, both in functionality and purpose. It serves as a shared inbox for server staff to communicate with their users in a seamless way." license = 'AGPL-3.0-only' authors = [ From 975b15805df6fbe60b22855507de919b827208d5 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Thu, 3 Jun 2021 10:24:34 +0800 Subject: [PATCH 124/209] Fix closes for group conversations --- core/thread.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/core/thread.py b/core/thread.py index 5c1e596051..04c16661c7 100644 --- a/core/thread.py +++ b/core/thread.py @@ -502,22 +502,24 @@ async def _close( if self.bot.config["show_timestamp"]: embed.timestamp = datetime.utcnow() - if not message: - if self.id == closer.id: - message = self.bot.config["thread_self_close_response"] - else: - message = self.bot.config["thread_close_response"] - message = self.bot.formatter.format( message, closer=closer, loglink=log_url, logkey=log_data["key"] if log_data else None ) - embed.description = message footer = self.bot.config["thread_close_footer"] embed.set_footer(text=footer, icon_url=self.bot.guild.icon_url) - if not silent and self.recipient is not None: - tasks.append(self.recipient.send(embed=embed)) + if not silent: + for user in self.recipients: + if not message: + if user.id == closer.id: + message = self.bot.config["thread_self_close_response"] + else: + message = self.bot.config["thread_close_response"] + embed.description = message + + if user is not None: + tasks.append(user.send(embed=embed)) if delete_channel: tasks.append(self.channel.delete()) From e79432525fde5efe69550fa7d70bf01eb2be80d3 Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Thu, 3 Jun 2021 10:41:27 +0800 Subject: [PATCH 125/209] Bump version --- CHANGELOG.md | 2 +- README.md | 2 +- bot.py | 2 +- pyproject.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c003f1db37..f83d08ca53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. -# v3.10.0-dev2 +# v3.10.0-dev3 ## Added diff --git a/README.md b/README.md index 9e662a7fb3..9d90568bc8 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@
- +
diff --git a/bot.py b/bot.py index 277050a627..07c654be3c 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "v3.10.0-dev2" +__version__ = "v3.10.0-dev3" import asyncio diff --git a/pyproject.toml b/pyproject.toml index 4e6959fe70..f41c8484fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ exclude = ''' [tool.poetry] name = 'Modmail' -version = '3.10.0-dev2' +version = '3.10.0-dev3' description = "Modmail is similar to Reddit's Modmail, both in functionality and purpose. It serves as a shared inbox for server staff to communicate with their users in a seamless way." license = 'AGPL-3.0-only' authors = [ From e6d682bf49e95b63f67e7c8d5cd9161ddfe06642 Mon Sep 17 00:00:00 2001 From: Cyrus <54488650+RealCyGuy@users.noreply.github.com> Date: Sat, 5 Jun 2021 22:03:28 -0700 Subject: [PATCH 126/209] Update registry.json --- plugins/registry.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/registry.json b/plugins/registry.json index a9f565a013..36b106bbc8 100644 --- a/plugins/registry.json +++ b/plugins/registry.json @@ -154,7 +154,7 @@ "suggest": { "repository": "realcyguy/modmail-plugins", "branch": "master", - "description": "Send suggestions to a selected server! It even has moderation...", + "description": "Send suggestions to a selected server! It has accepting, denying, and moderation-ing.", "bot_version": "3.4.1", "title": "Suggest stuff.", "icon_url": "https://i.imgur.com/qtE7AH8.png", From 882c60e7e11fe591301fe7cf630b9577f6f09cde Mon Sep 17 00:00:00 2001 From: Jia Rong Yee <28086837+fourjr@users.noreply.github.com> Date: Wed, 9 Jun 2021 22:47:31 +0800 Subject: [PATCH 127/209] Changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f83d08ca53..292a4f434f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ however, insignificant breaking changes do not guarantee a major version bump, s - Ability to have group conversations. ([GH #143](https://github.com/kyb3r/modmail/issues/143)) +### Internal + +- `thread.reply` now returns mod_message, user_message1, user_message2... It is no longer limited at a size 2 tuple. Potentially breaking if plugins depend on this behaviour. + ## Fixed - Certain situations where the internal thread cache breaks and spams new channels. ([GH #3022](https://github.com/kyb3r/modmail/issues/3022), [PR #3028](https://github.com/kyb3r/modmail/pull/3028)) From e48eed5ec8d9d577a1c0e4dcb6978739dd0d09b4 Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Mon, 21 Jun 2021 05:21:42 +0800 Subject: [PATCH 128/209] Add solution to CERTIFICATE_VERIFY_FAILED --- core/clients.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/core/clients.py b/core/clients.py index db4e9936b4..1d4183eabb 100644 --- a/core/clients.py +++ b/core/clients.py @@ -407,14 +407,24 @@ async def setup_indexes(self): ) logger.debug("Successfully configured and verified database indexes.") - async def validate_database_connection(self): + async def validate_database_connection(self, *, ssl_retry=True): try: await self.db.command("buildinfo") except Exception as exc: logger.critical("Something went wrong while connecting to the database.") message = f"{type(exc).__name__}: {str(exc)}" logger.critical(message) - + if "CERTIFICATE_VERIFY_FAILED" in message and ssl_retry: + mongo_uri = self.bot.config["connection_uri"] + if mongo_uri is None: + mongo_uri = self.bot.config["mongo_uri"] + for _ in range(3): + logger.warning("FAILED TO VERIFY SSL CERTIFICATE, ATTEMPTING TO START WITHOUT SSL (UNSAFE).") + logger.warning("To fix this warning, check there's no proxies blocking SSL cert verification, " + "run \"Certificate.command\" on MacOS, " + "and check certifi is up to date \"pip3 install --upgrade certifi\".") + self.db = AsyncIOMotorClient(mongo_uri, tlsAllowInvalidCertificates=True).modmail_bot + return await self.validate_database_connection(ssl_retry=False) if "ServerSelectionTimeoutError" in message: logger.critical( "This may have been caused by not whitelisting " From 1a6ae31737016ee3d2c12a0dce2eb47ae40c60ab Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sat, 26 Jun 2021 17:43:47 +0800 Subject: [PATCH 129/209] New issue forms --- .github/ISSUE_TEMPLATE/bug_report.md | 38 ---------------- .github/ISSUE_TEMPLATE/bug_report.yml | 51 ++++++++++++++++++++++ .github/ISSUE_TEMPLATE/command-request.md | 23 ---------- .github/ISSUE_TEMPLATE/config.yml | 5 +++ .github/ISSUE_TEMPLATE/feature_request.md | 23 ---------- .github/ISSUE_TEMPLATE/feature_request.yml | 46 +++++++++++++++++++ 6 files changed, 102 insertions(+), 84 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml delete mode 100644 .github/ISSUE_TEMPLATE/command-request.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 28a29c07b5..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: "[BUG] bug report title here" -labels: 'maybe: bug' -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**Bot Info** -Bot version (check with `@modmail about`): -Host method (Heroku, self-host, etc): - -**To Reproduce** -Steps to reproduce the behavior: -1. Who can reproduce (ex. anyone, owners)? -2. Where can it be reproduced (ex. in thread channels, recipient DM's)? -3. Done what to cause the error? -4. Any recently made changes to your bot? -5. Errored - -**Error Logs** -If your Modmail bot is online, type `@modmail debug hastebin` and include the link here. -If your Modmail bot is not online or the previous command did not generate a link, do the following: - -1. Select your *bot* application at https://dashboard.heroku.com -2. [Restart your bot](https://i.imgur.com/3FcrlKz.png) -3. Reproduce the error to populate the error logs -4. [Copy and paste the logs](https://i.imgur.com/TTrhitm.png) - -**Screenshots** -Add screenshots to help explain your problem. - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000000..77461f9d01 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,51 @@ +name: Bug Report +description: File a bug report +title: "[BUG]: your bug report title" +labels: "maybe: bug" +body: + - type: input + id: bot-info-version + attributes: + label: Bot Version + description: Check it with `@modmail about` + placeholder: eg. v3.9.4 + validations: + required: true + - type: dropdown + id: bot-info-hosting + attributes: + label: How are you hosting Modmail? + description: You can check it with `@modmail about` if you are unsure + options: + - Heroku + - Systemd + - PM2 + - Patreon + - Other + validations: + required: true + - type: input + id: logs + attributes: + label: Error logs + placeholder: https://hastebin.cc/placeholder + description: + "If your Modmail bot is online, type `@modmail debug hastebin` and include the link here. + If your Modmail bot is not online or the previous command did not generate a link, do the following: + + 1. Select your *bot* application at https://dashboard.heroku.com + 2. [Restart your bot](https://i.imgur.com/3FcrlKz.png) + 3. Reproduce the error to populate the error logs + 4. [Copy and paste the logs](https://i.imgur.com/TTrhitm.png)" + validations: + required: true + - type: textarea + id: screenshots + attributes: + label: Screenshots + description: "[optional] You may add screenshots to further explain your problem." + - type: textarea + id: additional-info + attributes: + label: Additional Information + description: "[optional] You may provide additional context for us to better understand how this issue occured." \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/command-request.md b/.github/ISSUE_TEMPLATE/command-request.md deleted file mode 100644 index d3c1673a6b..0000000000 --- a/.github/ISSUE_TEMPLATE/command-request.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: Command request -about: Request a new command -title: "your title here" -labels: command-request -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Who will this benefit** -Does this feature apply to a great portion of users? - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..e9a2d9fcd4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Discord Server + url: https://discord.gg/etJNHCQ + about: Please ask hosting-related questions here before creating an issue. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 7cd7a506cd..0000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: "your title here" -labels: feature-request -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Who will this benefit** -Does this feature apply to a great portion of users? - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000000..87daea485a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,46 @@ +name: Feature Request +description: Suggest an idea for this project +title: "your feature request title" +labels: "feature-request" +body: + - type: textarea + id: problem-relation + attributes: + label: Is your feature request related to a problem? Please elaborate. + description: A clear and concise description of what the problem is. + placeholder: eg. I'm always frustrated when... + validations: + required: true + - type: textarea + id: solution + attributes: + label: Describe the solution you'd like + description: A clear and concise description of what you want to happen. + validations: + required: true + - type: checkboxes + id: complications + attributes: + label: Does your solution involve any of the following? + options: + - label: Logviewer + - label: New config option + - type: textarea + id: alternatives + attributes: + label: Describe alternatives you've considered + description: A clear and concise description of any alternative solutions or features you've considered. + validations: + required: true + - type: textarea + id: benefit + attributes: + label: Who will this benefit? + description: Does this feature apply to a great portion of users? + validations: + required: true + - type: textarea + id: additional-info + attributes: + label: Additional Information + description: "[optional] You may provide additional context or screenshots for us to better understand the need of the feature." From bbe4c00d5c2157c27dfb604e906a47d55887fe3e Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sat, 26 Jun 2021 18:04:21 +0800 Subject: [PATCH 130/209] formatting --- .github/ISSUE_TEMPLATE/bug_report.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 77461f9d01..99779ab4f3 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -27,15 +27,19 @@ body: - type: input id: logs attributes: - label: Error logs + label: Error Logs placeholder: https://hastebin.cc/placeholder description: "If your Modmail bot is online, type `@modmail debug hastebin` and include the link here. + If your Modmail bot is not online or the previous command did not generate a link, do the following: - 1. Select your *bot* application at https://dashboard.heroku.com - 2. [Restart your bot](https://i.imgur.com/3FcrlKz.png) - 3. Reproduce the error to populate the error logs + 1. Select your *bot* application at https://dashboard.heroku.com + + 2. [Restart your bot](https://i.imgur.com/3FcrlKz.png) + + 3. Reproduce the error to populate the error logs + 4. [Copy and paste the logs](https://i.imgur.com/TTrhitm.png)" validations: required: true @@ -48,4 +52,4 @@ body: id: additional-info attributes: label: Additional Information - description: "[optional] You may provide additional context for us to better understand how this issue occured." \ No newline at end of file + description: "[optional] You may provide additional context for us to better understand how this issue occured." From 5567071fbcd8eeabcd2044b8551b0210788ffb20 Mon Sep 17 00:00:00 2001 From: Taku 3 Animals <45324516+Taaku18@users.noreply.github.com> Date: Sat, 3 Jul 2021 06:08:31 +0800 Subject: [PATCH 131/209] Update SPONSORS.json --- SPONSORS.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SPONSORS.json b/SPONSORS.json index 53034c57b3..1f1ed56d37 100644 --- a/SPONSORS.json +++ b/SPONSORS.json @@ -46,7 +46,7 @@ { "embed": { "description": "Quality Hosting at Prices You Deserve!", - "color": 50195251, + "color": 3137203, "footer": { "icon_url": "https://primeserversinc.com/images/Prime_Logo_P_Sassy.png", "text": "Prime Servers, Inc." From a69d79e4db86a691957eff79bc7a99e0cb24e638 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sun, 4 Jul 2021 18:20:51 +0800 Subject: [PATCH 132/209] fix issue template --- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 87daea485a..b6a4437f2d 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,7 +1,7 @@ name: Feature Request description: Suggest an idea for this project title: "your feature request title" -labels: "feature-request" +labels: "feature request" body: - type: textarea id: problem-relation From 04dc08b387f70d6e9df37bf6e059516678a91f02 Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Wed, 7 Jul 2021 12:13:06 +0800 Subject: [PATCH 133/209] Update requirements system and removed poetry --- Pipfile | 40 +-- Pipfile.lock | 791 +++++++++++++++++++++---------------------- poetry.lock | 455 ------------------------- pyproject.toml | 38 --- requirements.min.txt | 16 - requirements.txt | 29 ++ runtime.txt | 1 - 7 files changed, 436 insertions(+), 934 deletions(-) delete mode 100644 poetry.lock delete mode 100644 requirements.min.txt create mode 100644 requirements.txt delete mode 100644 runtime.txt diff --git a/Pipfile b/Pipfile index f9621c3493..eeb21b15e7 100644 --- a/Pipfile +++ b/Pipfile @@ -3,31 +3,27 @@ name = "pypi" url = "https://pypi.org/simple" verify_ssl = true -[[source]] -name = "pypi" -url = "https://pypi.org/simple" -verify_ssl = true - [dev-packages] -black = "==19.10b0" -pylint = "*" -bandit = "==1.6.2" -flake8 = "*" +black = "==21.6b0" +pylint = "~=2.9.3" +bandit = "~=1.7.0" +flake8 = "~=3.9.2" [packages] -colorama = ">=0.4.0" -python-dateutil = ">=2.7.0" -emoji = ">=0.2" -uvloop = {version = ">=0.12.0",sys_platform = "!= 'win32'"} -motor = ">=2.0.0" -natural = "==0.2.0" -isodate = ">=0.6.0" -dnspython = "~=1.16.0" -parsedatetime = "==2.6" -aiohttp = ">=3.6.0,<3.7.0" -python-dotenv = ">=0.10.3" -pipenv = "*" -"discord.py" = "==1.6.0" +colorama = "~=0.4.4" +python-dateutil = "~=2.8.1" +emoji = "~=1.2.0" +uvloop = {version = ">=0.15.2",sys_platform = "!= 'win32'"} +motor = "~=2.4.0" +natural = "~=0.2.0" +isodate = "~=0.6.0" +parsedatetime = "~=2.6" +aiohttp = "==3.7.4.post0" +python-dotenv = ">=0.18.0" +"discord.py" = "==1.7.3" [scripts] bot = "python bot.py" + +[requires] +python_version = "3.9" diff --git a/Pipfile.lock b/Pipfile.lock index aaa491011e..cfaece3094 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,16 +1,13 @@ { "_meta": { "hash": { - "sha256": "bfe06c9fe1db25178b01413114093b26f0d32e9a90a031c048ff3ddc7b6644b7" + "sha256": "0700f9473d2588f168a63141ac5f44596d303e7a55db173968edddd0ace2e2ac" }, "pipfile-spec": 6, - "requires": {}, + "requires": { + "python_version": "3.9" + }, "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - }, { "name": "pypi", "url": "https://pypi.org/simple", @@ -21,29 +18,46 @@ "default": { "aiohttp": { "hashes": [ - "sha256:1a4160579ffbc1b69e88cb6ca8bb0fbd4947dfcbf9fb1e2a4fc4c7a4a986c1fe", - "sha256:206c0ccfcea46e1bddc91162449c20c72f308aebdcef4977420ef329c8fcc599", - "sha256:2ad493de47a8f926386fa6d256832de3095ba285f325db917c7deae0b54a9fc8", - "sha256:319b490a5e2beaf06891f6711856ea10591cfe84fe9f3e71a721aa8f20a0872a", - "sha256:470e4c90da36b601676fe50c49a60d34eb8c6593780930b1aa4eea6f508dfa37", - "sha256:60f4caa3b7f7a477f66ccdd158e06901e1d235d572283906276e3803f6b098f5", - "sha256:66d64486172b032db19ea8522328b19cfb78a3e1e5b62ab6a0567f93f073dea0", - "sha256:687461cd974722110d1763b45c5db4d2cdee8d50f57b00c43c7590d1dd77fc5c", - "sha256:698cd7bc3c7d1b82bb728bae835724a486a8c376647aec336aa21a60113c3645", - "sha256:797456399ffeef73172945708810f3277f794965eb6ec9bd3a0c007c0476be98", - "sha256:a885432d3cabc1287bcf88ea94e1826d3aec57fd5da4a586afae4591b061d40d", - "sha256:c506853ba52e516b264b106321c424d03f3ddef2813246432fa9d1cefd361c81", - "sha256:fb83326d8295e8840e4ba774edf346e87eca78ba8a89c55d2690352842c15ba5" + "sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe", + "sha256:0563c1b3826945eecd62186f3f5c7d31abb7391fedc893b7e2b26303b5a9f3fe", + "sha256:114b281e4d68302a324dd33abb04778e8557d88947875cbf4e842c2c01a030c5", + "sha256:14762875b22d0055f05d12abc7f7d61d5fd4fe4642ce1a249abdf8c700bf1fd8", + "sha256:15492a6368d985b76a2a5fdd2166cddfea5d24e69eefed4630cbaae5c81d89bd", + "sha256:17c073de315745a1510393a96e680d20af8e67e324f70b42accbd4cb3315c9fb", + "sha256:209b4a8ee987eccc91e2bd3ac36adee0e53a5970b8ac52c273f7f8fd4872c94c", + "sha256:230a8f7e24298dea47659251abc0fd8b3c4e38a664c59d4b89cca7f6c09c9e87", + "sha256:2e19413bf84934d651344783c9f5e22dee452e251cfd220ebadbed2d9931dbf0", + "sha256:393f389841e8f2dfc86f774ad22f00923fdee66d238af89b70ea314c4aefd290", + "sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5", + "sha256:3d78619672183be860b96ed96f533046ec97ca067fd46ac1f6a09cd9b7484287", + "sha256:40eced07f07a9e60e825554a31f923e8d3997cfc7fb31dbc1328c70826e04cde", + "sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf", + "sha256:4b302b45040890cea949ad092479e01ba25911a15e648429c7c5aae9650c67a8", + "sha256:515dfef7f869a0feb2afee66b957cc7bbe9ad0cdee45aec7fdc623f4ecd4fb16", + "sha256:547da6cacac20666422d4882cfcd51298d45f7ccb60a04ec27424d2f36ba3eaf", + "sha256:5df68496d19f849921f05f14f31bd6ef53ad4b00245da3195048c69934521809", + "sha256:64322071e046020e8797117b3658b9c2f80e3267daec409b350b6a7a05041213", + "sha256:7615dab56bb07bff74bc865307aeb89a8bfd9941d2ef9d817b9436da3a0ea54f", + "sha256:79ebfc238612123a713a457d92afb4096e2148be17df6c50fb9bf7a81c2f8013", + "sha256:7b18b97cf8ee5452fa5f4e3af95d01d84d86d32c5e2bfa260cf041749d66360b", + "sha256:932bb1ea39a54e9ea27fc9232163059a0b8855256f4052e776357ad9add6f1c9", + "sha256:a00bb73540af068ca7390e636c01cbc4f644961896fa9363154ff43fd37af2f5", + "sha256:a5ca29ee66f8343ed336816c553e82d6cade48a3ad702b9ffa6125d187e2dedb", + "sha256:af9aa9ef5ba1fd5b8c948bb11f44891968ab30356d65fd0cc6707d989cd521df", + "sha256:bb437315738aa441251214dad17428cafda9cdc9729499f1d6001748e1d432f4", + "sha256:bdb230b4943891321e06fc7def63c7aace16095be7d9cf3b1e01be2f10fba439", + "sha256:c6e9dcb4cb338d91a73f178d866d051efe7c62a7166653a91e7d9fb18274058f", + "sha256:cffe3ab27871bc3ea47df5d8f7013945712c46a3cc5a95b6bee15887f1675c22", + "sha256:d012ad7911653a906425d8473a1465caa9f8dea7fcf07b6d870397b774ea7c0f", + "sha256:d9e13b33afd39ddeb377eff2c1c4f00544e191e1d1dee5b6c51ddee8ea6f0cf5", + "sha256:e4b2b334e68b18ac9817d828ba44d8fcb391f6acb398bcc5062b14b2cbeac970", + "sha256:e54962802d4b8b18b6207d4a927032826af39395a3bd9196a5af43fc4e60b009", + "sha256:f705e12750171c0ab4ef2a3c76b9a4024a62c4103e3a55dd6f99265b9bc6fcfc", + "sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a", + "sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95" ], "index": "pypi", - "version": "==3.6.3" - }, - "appdirs": { - "hashes": [ - "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", - "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" - ], - "version": "==1.4.4" + "version": "==3.7.4.post0" }, "async-timeout": { "hashes": [ @@ -55,25 +69,19 @@ }, "attrs": { "hashes": [ - "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", - "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.3.0" - }, - "certifi": { - "hashes": [ - "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", - "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" + "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", + "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" ], - "version": "==2020.12.5" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.2.0" }, "chardet": { "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", + "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" ], - "version": "==3.0.4" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==4.0.0" }, "colorama": { "hashes": [ @@ -85,48 +93,27 @@ }, "discord.py": { "hashes": [ - "sha256:3df148daf6fbcc7ab5b11042368a3cd5f7b730b62f09fb5d3cbceff59bcfbb12", - "sha256:ba8be99ff1b8c616f7b6dcb700460d0222b29d4c11048e74366954c465fdd05f" + "sha256:462cd0fe307aef8b29cbfa8dd613e548ae4b2cb581d46da9ac0d46fb6ea19408", + "sha256:c6f64db136de0e18e090f6752ea68bdd4ab0a61b82dfe7acecefa22d6477bb0c" ], "index": "pypi", - "version": "==1.6.0" - }, - "distlib": { - "hashes": [ - "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb", - "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1" - ], - "version": "==0.3.1" - }, - "dnspython": { - "hashes": [ - "sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01", - "sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d" - ], - "index": "pypi", - "version": "==1.16.0" + "version": "==1.7.3" }, "emoji": { "hashes": [ - "sha256:e42da4f8d648f8ef10691bc246f682a1ec6b18373abfd9be10ec0b398823bd11" + "sha256:496f432058567985838c13d67dde84ca081614a8286c0b9cdc7d63dfa89d51a3", + "sha256:6b19b65da8d6f30551eead1705539cc0eadcd9e33a6ecbc421a29b87f96287eb" ], "index": "pypi", - "version": "==0.6.0" - }, - "filelock": { - "hashes": [ - "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", - "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836" - ], - "version": "==3.0.12" + "version": "==1.2.0" }, "idna": { "hashes": [ - "sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16", - "sha256:c5b02147e01ea9920e6b0a3f1f7bb833612d507592c837a6c49552768f4054e1" + "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a", + "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3" ], - "markers": "python_version >= '3.4'", - "version": "==3.1" + "markers": "python_version >= '3.5'", + "version": "==3.2" }, "isodate": { "hashes": [ @@ -138,34 +125,54 @@ }, "motor": { "hashes": [ - "sha256:428d94750123d19fcd0a89b8671ff9b4656f205217bad9f44161748c64c5fc80", - "sha256:f1692b760d834707e3477996ce8d407af8cd61c1a2abedbf81c22ef14675e61a" + "sha256:1196db507142ef8f00d953efa2f37b39335ef2d72af6ce4fbccfd870b65c5e9f", + "sha256:839c11a43897dbec8e5ba0e87a9c9b877239803126877b2efa5cef89aa6b687a" ], "index": "pypi", - "version": "==2.3.0" + "version": "==2.4.0" }, "multidict": { "hashes": [ - "sha256:1ece5a3369835c20ed57adadc663400b5525904e53bae59ec854a5d36b39b21a", - "sha256:275ca32383bc5d1894b6975bb4ca6a7ff16ab76fa622967625baeebcf8079000", - "sha256:3750f2205b800aac4bb03b5ae48025a64e474d2c6cc79547988ba1d4122a09e2", - "sha256:4538273208e7294b2659b1602490f4ed3ab1c8cf9dbdd817e0e9db8e64be2507", - "sha256:5141c13374e6b25fe6bf092052ab55c0c03d21bd66c94a0e3ae371d3e4d865a5", - "sha256:51a4d210404ac61d32dada00a50ea7ba412e6ea945bbe992e4d7a595276d2ec7", - "sha256:5cf311a0f5ef80fe73e4f4c0f0998ec08f954a6ec72b746f3c179e37de1d210d", - "sha256:6513728873f4326999429a8b00fc7ceddb2509b01d5fd3f3be7881a257b8d463", - "sha256:7388d2ef3c55a8ba80da62ecfafa06a1c097c18032a501ffd4cabbc52d7f2b19", - "sha256:9456e90649005ad40558f4cf51dbb842e32807df75146c6d940b6f5abb4a78f3", - "sha256:c026fe9a05130e44157b98fea3ab12969e5b60691a276150db9eda71710cd10b", - "sha256:d14842362ed4cf63751648e7672f7174c9818459d169231d03c56e84daf90b7c", - "sha256:e0d072ae0f2a179c375f67e3da300b47e1a83293c554450b29c900e50afaae87", - "sha256:f07acae137b71af3bb548bd8da720956a3bc9f9a0b87733e0899226a2317aeb7", - "sha256:fbb77a75e529021e7c4a8d4e823d88ef4d23674a202be4f5addffc72cbb91430", - "sha256:fcfbb44c59af3f8ea984de67ec7c306f618a3ec771c2843804069917a8f2e255", - "sha256:feed85993dbdb1dbc29102f50bca65bdc68f2c0c8d352468c25b54874f23c39d" + "sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a", + "sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93", + "sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632", + "sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656", + "sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79", + "sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7", + "sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d", + "sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5", + "sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224", + "sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26", + "sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea", + "sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348", + "sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6", + "sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76", + "sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1", + "sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f", + "sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952", + "sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a", + "sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37", + "sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9", + "sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359", + "sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8", + "sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da", + "sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3", + "sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d", + "sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf", + "sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841", + "sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d", + "sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93", + "sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f", + "sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647", + "sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635", + "sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456", + "sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda", + "sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5", + "sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281", + "sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80" ], - "markers": "python_version >= '3.5'", - "version": "==4.7.6" + "markers": "python_version >= '3.6'", + "version": "==5.1.0" }, "natural": { "hashes": [ @@ -182,82 +189,74 @@ "index": "pypi", "version": "==2.6" }, - "pipenv": { - "hashes": [ - "sha256:4ab2f60742184d851ac44b9e1d423afe71dc2ea7a68bde07eb890c8b4ce5a420", - "sha256:8253fe6f9cfb3791a54da8a0571f73c918cb3457dd908684c1800a13a06ec4c1" - ], - "index": "pypi", - "version": "==2020.11.15" - }, "pymongo": { "hashes": [ - "sha256:019ddf7ced8e42cc6c8c608927c799be8097237596c94ffe551f6ef70e55237e", - "sha256:047c325c4a96e7be7d11acf58639bcf71a81ca212d9c6590e3369bc28678647a", - "sha256:047cc2007b280672ddfdf2e7b862aad8d898f481f65bbc9067bfa4e420a019a9", - "sha256:061d59f525831c4051af0b6dbafa62b0b8b168d4ef5b6e3c46d0811b8499d100", - "sha256:082832a59da18efab4d9148cca396451bac99da9757f31767f706e828b5b8500", - "sha256:0a53a751d977ad02f1bd22ddb6288bb4816c4758f44a50225462aeeae9cbf6a0", - "sha256:1222025db539641071a1b67f6950f65a6342a39db5b454bf306abd6954f1ad8a", - "sha256:1580fad512c678b720784e5c9018621b1b3bd37fb5b1633e874738862d6435c7", - "sha256:202ea1d4edc8a5439fc179802d807b49e7e563207fea5610779e56674ac770c6", - "sha256:21d7b48567a1c80f9266e0ab61c1218a31279d911da345679188733e354f81cc", - "sha256:264843ce2af0640994a4331148ef5312989bc004678c457460758766c9b4decc", - "sha256:270a1f6a331eac3a393090af06df68297cb31a8b2df0bdcbd97dc613c5758e78", - "sha256:29a6840c2ac778010547cad5870f3db2e080ad7fad01197b07fff993c08692c8", - "sha256:3646c2286d889618d43e01d9810ac1fc17709d2b4dec61366df5edc8ba228b3e", - "sha256:36b9b98a39565a8f33803c81569442b35e749a72fb1aa7d0bcdb1a33052f8bcc", - "sha256:3ec8f8e106a1476659d8c020228b45614daabdbdb6c6454a843a1d4f77d13339", - "sha256:422069f2cebf58c9dd9e8040b4768f7be4f228c95bc4505e8fa8e7b4f7191ad8", - "sha256:44376a657717de8847d5d71a9305f3595c7e78c91ac77edbb87058d12ede87a6", - "sha256:45728e6aae3023afb5b2829586d1d2bfd9f0d71cfd7d3c924b71a5e9aef617a8", - "sha256:46792b71ab802d9caf1fc9d52e83399ef8e1a36e91eef4d827c06e36b8df2230", - "sha256:4942a5659ae927bb764a123a6409870ca5dd572d83b3bfb71412c9a191bbf792", - "sha256:4be4fe9d18523da98deeb0b554ac76e1dc1562ee879d62572b34dda8593efcc1", - "sha256:523804bd8fcb5255508052b50073a27c701b90a73ea46e29be46dad5fe01bde6", - "sha256:540dafd6f4a0590fc966465c726b80fa7c0804490c39786ef29236fe68c94401", - "sha256:5980509801cbd2942df31714d055d89863684b4de26829c349362e610a48694e", - "sha256:5ad7b96c27acd7e256b33f47cf3d23bd7dd902f9c033ae43f32ffcbc37bebafd", - "sha256:6122470dfa61d4909b75c98012c1577404ba4ab860d0095e0c6980560cb3711f", - "sha256:6175fd105da74a09adb38f93be96e1f64873294c906e5e722cbbc5bd10c44e3b", - "sha256:646d4d30c5aa7c0ddbfe9b990f0f77a88621024a21ad0b792bd9d58caa9611f0", - "sha256:6700e251c6396cc05d7460dc05ef8e19e60a7b53b62c007725b48e123aaa2b1c", - "sha256:6aac7e0e8de92f11a410eb68c24a2decbac6f094e82fd95d22546d0168e7a18b", - "sha256:6e7a6057481a644970e43475292e1c0af095ca39a20fe83781196bd6e6690a38", - "sha256:76579fcf77052b39796fe4a11818d1289dd48cffe15951b3403288fa163c29f6", - "sha256:7e69fa025a1db189443428f345fea5555d16413df6addc056e17bb8c9794b006", - "sha256:7f0c507e1f108790840d6c4b594019ebf595025c324c9f7e9c9b2b15b41f884e", - "sha256:813db97e9955b6b1b50b5cebd18cb148580603bb9b067ea4c5cc656b333bc906", - "sha256:82d5ded5834b6c92380847860eb28dcaf20b847a27cee5811c4aaceef87fd280", - "sha256:82f6e42ba40440a7e0a20bfe12465a3b62d65966a4c7ad1a21b36ffff88de6fe", - "sha256:8d669c720891781e7c82d412cad39f9730ef277e3957b48a3344dae47d3caa03", - "sha256:944ed467feb949e103555863fa934fb84216a096b0004ca364d3ddf9d18e2b9e", - "sha256:96c6aef7ffb0d37206c0342abb82d874fa8cdc344267277ec63f562b94335c22", - "sha256:9be785bd4e1ba0148fb00ca84e4dbfbd1c74df3af3a648559adc60b0782f34de", - "sha256:9d19843568df9d263dc92ae4cc2279879add8a26996473f9155590cac635b321", - "sha256:a118a1df7280ffab7fe0f3eab325868339ff1c4d5b8e0750db0f0a796da8f849", - "sha256:b4294ddf76452459433ecfa6a93258608b5e462c76ef15e4695ed5e2762f009f", - "sha256:b50af6701b4a5288b77fb4db44a363aa9485caf2c3e7a40c0373fd45e34440af", - "sha256:b875bb4b438931dce550e170bfb558597189b8d0160f4ac60f14a21955161699", - "sha256:b95d2c2829b5956bf54d9a22ffec911dea75abf0f0f7e0a8a57423434bfbde91", - "sha256:c046e09e886f4539f8626afba17fa8f2e6552731f9384e2827154e3e3b7fda4e", - "sha256:c1d1992bbdf363b22b5a9543ab7d7c6f27a1498826d50d91319b803ddcf1142e", - "sha256:c2b67881392a9e85aa108e75f62cdbe372d5a3f17ea5f8d3436dcf4662052f14", - "sha256:c6cf288c9e03195d8e12b72a6388b32f18a5e9c2545622417a963e428e1fe496", - "sha256:c812b6e53344e92f10f12235219fb769c491a4a87a02c9c3f93fe632e493bda8", - "sha256:cc421babc687dc52ce0fc19787b2404518ca749d9db59576100946ff886f38ed", - "sha256:ce53c00be204ec4428d3c1f3c478ae89d388efec575544c27f57b61e9fa4a7f2", - "sha256:ce9964c117cbe5cf6269f30a2b334d28675956e988b7dbd0b4f7370924afda2e", - "sha256:d6f82e86896a8db70e8ae8fa4b7556a0f188f1d8a6c53b2ba229889d55a59308", - "sha256:d9d3ae537f61011191b2fd6f8527b9f9f8a848b37d4c85a0f7bb28004c42b546", - "sha256:e565d1e4388765c135052717f15f9e0314f9d172062444c6b3fc0002e93ed04b", - "sha256:ed98683d8f01f1c46ef2d02469e04e9a8fe9a73a9741a4e6e66677a73b59bec8", - "sha256:ef18aa15b1aa18c42933deed5233b3284186e9ed85c25d2704ceff5099a3964c", - "sha256:fa741e9c805567239f845c7e9a016aff797f9bb02ff9bc8ccd2fbd9eafefedd4", - "sha256:fc4946acb6cdada08f60aca103b61334995523da65be5fe816ea8571c9967d46", - "sha256:fcc66d17a3363b7bd6d2655de8706e25a3cd1be2bd1b8e8d8a5c504a6ef893ae" - ], - "version": "==3.11.2" + "sha256:03be7ad107d252bb7325d4af6309fdd2c025d08854d35f0e7abc8bf048f4245e", + "sha256:071552b065e809d24c5653fcc14968cfd6fde4e279408640d5ac58e3353a3c5f", + "sha256:08b8723248730599c9803ae4c97b8f3f76c55219104303c88cb962a31e3bb5ee", + "sha256:08bda7b2c522ff9f1e554570da16298271ebb0c56ab9699446aacba249008988", + "sha256:0aaf4d44f1f819360f9432df538d54bbf850f18152f34e20337c01b828479171", + "sha256:0cabfc297f4cf921f15bc789a8fbfd7115eb9f813d3f47a74b609894bc66ab0d", + "sha256:13acf6164ead81c9fc2afa0e1ea6d6134352973ce2bb35496834fee057063c04", + "sha256:15b083d1b789b230e5ac284442d9ecb113c93f3785a6824f748befaab803b812", + "sha256:161fcd3281c42f644aa8dec7753cca2af03ce654e17d76da4f0dab34a12480ca", + "sha256:1a994a42f49dab5b6287e499be7d3d2751776486229980d8857ad53b8333d469", + "sha256:20d75ea11527331a2980ab04762a9d960bcfea9475c54bbeab777af880de61cd", + "sha256:225c61e08fe517aede7912937939e09adf086c8e6f7e40d4c85ad678c2c2aea3", + "sha256:3135dd574ef1286189f3f04a36c8b7a256376914f8cbbce66b94f13125ded858", + "sha256:3491c7de09e44eded16824cb58cf9b5cc1dc6f066a0bb7aa69929d02aa53b828", + "sha256:3551912f5c34d8dd7c32c6bb00ae04192af47f7b9f653608f107d19c1a21a194", + "sha256:38a7b5140a48fc91681cdb5cb95b7cd64640b43d19259fdd707fa9d5a715f2b2", + "sha256:3a3498a8326111221560e930f198b495ea6926937e249f475052ffc6893a6680", + "sha256:3bfc7689a1bacb9bcd2f2d5185d99507aa29f667a58dd8adaa43b5a348139e46", + "sha256:421d13523d11c57f57f257152bc4a6bb463aadf7a3918e9c96fefdd6be8dbfb8", + "sha256:424799c71ff435094e5fb823c40eebb4500f0e048133311e9c026467e8ccebac", + "sha256:474e21d0e07cd09679e357d1dac76e570dab86665e79a9d3354b10a279ac6fb3", + "sha256:4c7e8c8e1e1918dcf6a652ac4b9d87164587c26fd2ce5dd81e73a5ab3b3d492f", + "sha256:506a6dab4c7ffdcacdf0b8e70bd20eb2e77fa994519547c9d88d676400fcad58", + "sha256:510cd3bfabb63a07405b7b79fae63127e34c118b7531a2cbbafc7a24fd878594", + "sha256:517ba47ca04a55b1f50ee8df9fd97f6c37df5537d118fb2718952b8623860466", + "sha256:539d4cb1b16b57026999c53e5aab857fe706e70ae5310cc8c232479923f932e6", + "sha256:5c36428cc4f7fae56354db7f46677fd21222fc3cb1e8829549b851172033e043", + "sha256:5db59223ed1e634d842a053325f85f908359c6dac9c8ddce8ef145061fae7df8", + "sha256:5e606846c049ed40940524057bfdf1105af6066688c0e6a1a3ce2038589bae70", + "sha256:6060794aac9f7b0644b299f46a9c6cbc0bc470bd01572f4134df140afd41ded6", + "sha256:62c29bc36a6d9be68fe7b5aaf1e120b4aa66a958d1e146601fcd583eb12cae7b", + "sha256:73326b211e7410c8bd6a74500b1e3f392f39cf10862e243d00937e924f112c01", + "sha256:78f07961f4f214ea8e80be63cffd5cc158eb06cd922ffbf6c7155b11728f28f9", + "sha256:7c97554ea521f898753d9773891d0347ebfaddcc1dee2ad94850b163171bf1f1", + "sha256:8898f6699f740ca93a0879ed07d8e6db02d68af889d0ebb3d13ab017e6b1af1e", + "sha256:8a41fdc751dc4707a4fafb111c442411816a7c225ebb5cadb57599534b5d5372", + "sha256:8e0004b0393d72d76de94b4792a006cb960c1c65c7659930fbf9a81ce4341982", + "sha256:977b1d4f868986b4ba5d03c317fde4d3b66e687d74473130cd598e3103db34fa", + "sha256:9a4f6e0b01df820ba9ed0b4e618ca83a1c089e48d4f268d0e00dcd49893d4549", + "sha256:9b9298964389c180a063a9e8bac8a80ed42de11d04166b20249bfa0a489e0e0f", + "sha256:a08c8b322b671857c81f4c30cd3c8df2895fd3c0e9358714f39e0ef8fb327702", + "sha256:ad31f184dcd3271de26ab1f9c51574afb99e1b0e484ab1da3641256b723e4994", + "sha256:aff3656af2add93f290731a6b8930b23b35c0c09569150130a58192b3ec6fc61", + "sha256:b2f41261b648cf5dee425f37ff14f4ad151c2f24b827052b402637158fd056ef", + "sha256:b413117210fa6d92664c3d860571e8e8727c3e8f2ff197276c5d0cb365abd3ad", + "sha256:b7efc7e7049ef366777cfd35437c18a4166bb50a5606a1c840ee3b9624b54fc9", + "sha256:b8f94acd52e530a38f25e4d5bf7ddfdd4bea9193e718f58419def0d4406b58d3", + "sha256:d0a70151d7de8a3194cdc906bcc1a42e14594787c64b0c1c9c975e5a2af3e251", + "sha256:d360e5d5dd3d55bf5d1776964625018d85b937d1032bae1926dd52253decd0db", + "sha256:d4e62417e89b717a7bcd8576ac3108cd063225942cc91c5b37ff5465fdccd386", + "sha256:d65bac5f6724d9ea6f0b5a0f0e4952fbbf209adcf6b5583b54c54bd2fcd74dc0", + "sha256:e02beaab433fd1104b2804f909e694cfbdb6578020740a9051597adc1cd4e19f", + "sha256:e4b631688dfbdd61b5610e20b64b99d25771c6d52d9da73349342d2a0f11c46a", + "sha256:e4e9db78b71db2b1684ee4ecc3e32c4600f18cdf76e6b9ae03e338e52ee4b168", + "sha256:eb4d176394c37a76e8b0afe54b12d58614a67a60a7f8c0dd3a5afbb013c01092", + "sha256:f08665d3cc5abc2f770f472a9b5f720a9b3ab0b8b3bb97c7c1487515e5653d39", + "sha256:f3d851af3852f16ad4adc7ee054fd9c90a7a5063de94d815b7f6a88477b9f4c6", + "sha256:f4ba58157e8ae33ee86fadf9062c506e535afd904f07f9be32731f4410a23b7f", + "sha256:f664ed7613b8b18f0ce5696b146776266a038c19c5cd6efffa08ecc189b01b73", + "sha256:f947b359cc4769af8b49be7e37af01f05fcf15b401da2528021148e4a54426d1", + "sha256:fe4189846448df013cd9df11bba38ddf78043f8c290a9f06430732a7a8601cce", + "sha256:fea5cb1c63efe1399f0812532c7cf65458d38fd011be350bc5021dfcac39fba8", + "sha256:fedf0dee7a412ca6d1d6d92c158fe9cbaa8ea0cae90d268f9ccc0744de7a97d0", + "sha256:fffff7bfb6799a763d3742c59c6ee7ffadda21abed557637bc44ed1080876484" + ], + "version": "==3.11.4" }, "python-dateutil": { "hashes": [ @@ -269,73 +268,87 @@ }, "python-dotenv": { "hashes": [ - "sha256:0c8d1b80d1a1e91717ea7d526178e3882732420b03f08afea0406db6402e220e", - "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0" + "sha256:dd8fe852847f4fbfadabf6183ddd4c824a9651f02d51714fa075c95561959c7d", + "sha256:effaac3c1e58d89b3ccb4d04a40dc7ad6e0275fda25fd75ae9d323e2465e202d" ], "index": "pypi", - "version": "==0.15.0" + "version": "==0.18.0" }, "six": { "hashes": [ - "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", - "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==1.15.0" - }, - "uvloop": { - "hashes": [ - "sha256:08b109f0213af392150e2fe6f81d33261bb5ce968a288eb698aad4f46eb711bd", - "sha256:123ac9c0c7dd71464f58f1b4ee0bbd81285d96cdda8bc3519281b8973e3a461e", - "sha256:4315d2ec3ca393dd5bc0b0089d23101276778c304d42faff5dc4579cb6caef09", - "sha256:4544dcf77d74f3a84f03dd6278174575c44c67d7165d4c42c71db3fdc3860726", - "sha256:afd5513c0ae414ec71d24f6f123614a80f3d27ca655a4fcf6cabe50994cc1891", - "sha256:b4f591aa4b3fa7f32fb51e2ee9fea1b495eb75b0b3c8d0ca52514ad675ae63f7", - "sha256:bcac356d62edd330080aed082e78d4b580ff260a677508718f88016333e2c9c5", - "sha256:e7514d7a48c063226b7d06617cbb12a14278d4323a065a8d46a7962686ce2e95", - "sha256:f07909cd9fc08c52d294b1570bba92186181ca01fe3dc9ffba68955273dd7362" + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "sys_platform != 'win32'", - "version": "==0.14.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" }, - "virtualenv": { + "typing-extensions": { "hashes": [ - "sha256:54b05fc737ea9c9ee9f8340f579e5da5b09fb64fd010ab5757eb90268616907c", - "sha256:b7a8ec323ee02fb2312f098b6b4c9de99559b462775bc8fe3627a73706603c1b" + "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", + "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", + "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.2.2" + "version": "==3.10.0.0" }, - "virtualenv-clone": { + "uvloop": { "hashes": [ - "sha256:07e74418b7cc64f4fda987bf5bc71ebd59af27a7bc9e8a8ee9fd54b1f2390a27", - "sha256:665e48dd54c84b98b71a657acb49104c54e7652bce9c1c4f6c6976ed4c827a29" + "sha256:114543c84e95df1b4ff546e6e3a27521580466a30127f12172a3278172ad68bc", + "sha256:19fa1d56c91341318ac5d417e7b61c56e9a41183946cc70c411341173de02c69", + "sha256:2bb0624a8a70834e54dde8feed62ed63b50bad7a1265c40d6403a2ac447bce01", + "sha256:42eda9f525a208fbc4f7cecd00fa15c57cc57646c76632b3ba2fe005004f051d", + "sha256:44cac8575bf168601424302045234d74e3561fbdbac39b2b54cc1d1d00b70760", + "sha256:6de130d0cb78985a5d080e323b86c5ecaf3af82f4890492c05981707852f983c", + "sha256:7ae39b11a5f4cec1432d706c21ecc62f9e04d116883178b09671aa29c46f7a47", + "sha256:90e56f17755e41b425ad19a08c41dc358fa7bf1226c0f8e54d4d02d556f7af7c", + "sha256:b45218c99795803fb8bdbc9435ff7f54e3a591b44cd4c121b02fa83affb61c7c", + "sha256:e5e5f855c9bf483ee6cd1eb9a179b740de80cb0ae2988e3fa22309b78e2ea0e7" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==0.5.4" + "index": "pypi", + "markers": "sys_platform != 'win32'", + "version": "==0.15.2" }, "yarl": { "hashes": [ - "sha256:040b237f58ff7d800e6e0fd89c8439b841f777dd99b4a9cca04d6935564b9409", - "sha256:17668ec6722b1b7a3a05cc0167659f6c95b436d25a36c2d52db0eca7d3f72593", - "sha256:3a584b28086bc93c888a6c2aa5c92ed1ae20932f078c46509a66dce9ea5533f2", - "sha256:4439be27e4eee76c7632c2427ca5e73703151b22cae23e64adb243a9c2f565d8", - "sha256:48e918b05850fffb070a496d2b5f97fc31d15d94ca33d3d08a4f86e26d4e7c5d", - "sha256:9102b59e8337f9874638fcfc9ac3734a0cfadb100e47d55c20d0dc6087fb4692", - "sha256:9b930776c0ae0c691776f4d2891ebc5362af86f152dd0da463a6614074cb1b02", - "sha256:b3b9ad80f8b68519cc3372a6ca85ae02cc5a8807723ac366b53c0f089db19e4a", - "sha256:bc2f976c0e918659f723401c4f834deb8a8e7798a71be4382e024bcc3f7e23a8", - "sha256:c22c75b5f394f3d47105045ea551e08a3e804dc7e01b37800ca35b58f856c3d6", - "sha256:c52ce2883dc193824989a9b97a76ca86ecd1fa7955b14f87bf367a61b6232511", - "sha256:ce584af5de8830d8701b8979b18fcf450cef9a382b1a3c8ef189bedc408faf1e", - "sha256:da456eeec17fa8aa4594d9a9f27c0b1060b6a75f2419fe0c00609587b2695f4a", - "sha256:db6db0f45d2c63ddb1a9d18d1b9b22f308e52c83638c26b422d520a815c4b3fb", - "sha256:df89642981b94e7db5596818499c4b2219028f2a528c9c37cc1de45bf2fd3a3f", - "sha256:f18d68f2be6bf0e89f1521af2b1bb46e66ab0018faafa81d70f358153170a317", - "sha256:f379b7f83f23fe12823085cd6b906edc49df969eb99757f58ff382349a3303c6" + "sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e", + "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434", + "sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366", + "sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3", + "sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec", + "sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959", + "sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e", + "sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c", + "sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6", + "sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a", + "sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6", + "sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424", + "sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e", + "sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f", + "sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50", + "sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2", + "sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc", + "sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4", + "sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970", + "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10", + "sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0", + "sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406", + "sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896", + "sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643", + "sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721", + "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478", + "sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724", + "sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e", + "sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8", + "sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96", + "sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25", + "sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76", + "sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2", + "sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2", + "sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c", + "sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a", + "sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71" ], - "markers": "python_version >= '3.5'", - "version": "==1.5.1" + "markers": "python_version >= '3.6'", + "version": "==1.6.3" } }, "develop": { @@ -348,110 +361,95 @@ }, "astroid": { "hashes": [ - "sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703", - "sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386" - ], - "markers": "python_version >= '3.5'", - "version": "==2.4.2" - }, - "attrs": { - "hashes": [ - "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", - "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" + "sha256:38b95085e9d92e2ca06cf8b35c12a74fa81da395a6f9e65803742e6509c05892", + "sha256:606b2911d10c3dcf35e58d2ee5c97360e8477d7b9f3efc3f24811c93e6fc2cd9" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.3.0" + "markers": "python_version ~= '3.6'", + "version": "==2.6.2" }, "bandit": { "hashes": [ - "sha256:336620e220cf2d3115877685e264477ff9d9abaeb0afe3dc7264f55fa17a3952", - "sha256:41e75315853507aa145d62a78a2a6c5e3240fe14ee7c601459d0df9418196065" + "sha256:216be4d044209fa06cf2a3e51b319769a51be8318140659719aa7a115c35ed07", + "sha256:8a4c7415254d75df8ff3c3b15cfe9042ecee628a1e40b44c15a98890fbfc2608" ], "index": "pypi", - "version": "==1.6.2" + "version": "==1.7.0" }, "black": { "hashes": [ - "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b", - "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539" + "sha256:dc132348a88d103016726fe360cb9ede02cecf99b76e3660ce6c596be132ce04", + "sha256:dfb8c5a069012b2ab1e972e7b908f5fb42b6bbabcba0a788b86dc05067c7d9c7" ], "index": "pypi", - "version": "==19.10b0" + "version": "==21.6b0" }, "click": { "hashes": [ - "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", - "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" + "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a", + "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==7.1.2" - }, - "colorama": { - "hashes": [ - "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b", - "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2" - ], - "index": "pypi", - "version": "==0.4.4" + "markers": "python_version >= '3.6'", + "version": "==8.0.1" }, "flake8": { "hashes": [ - "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839", - "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b" + "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b", + "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907" ], "index": "pypi", - "version": "==3.8.4" + "version": "==3.9.2" }, "gitdb": { "hashes": [ - "sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac", - "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9" + "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0", + "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005" ], "markers": "python_version >= '3.4'", - "version": "==4.0.5" + "version": "==4.0.7" }, "gitpython": { "hashes": [ - "sha256:42dbefd8d9e2576c496ed0059f3103dcef7125b9ce16f9d5f9c834aed44a1dac", - "sha256:867ec3dfb126aac0f8296b19fb63b8c4a399f32b4b6fafe84c4b10af5fa9f7b5" + "sha256:b838a895977b45ab6f0cc926a9045c8d1c44e2b653c1fcc39fe91f42c6e8f05b", + "sha256:fce760879cd2aebd2991b3542876dc5c4a909b30c9d69dfc488e504a8db37ee8" ], - "markers": "python_version >= '3.4'", - "version": "==3.1.12" + "markers": "python_version >= '3.6'", + "version": "==3.1.18" }, "isort": { "hashes": [ - "sha256:c729845434366216d320e936b8ad6f9d681aab72dc7cbc2d51bedc3582f3ad1e", - "sha256:fff4f0c04e1825522ce6949973e83110a6e907750cd92d128b0d14aaaadbffdc" + "sha256:83510593e07e433b77bd5bff0f6f607dbafa06d1a89022616f02d8b699cfcd56", + "sha256:8e2c107091cfec7286bc0f68a547d0ba4c094d460b732075b6fba674f1035c0c" ], - "markers": "python_version >= '3.6' and python_version < '4.0'", - "version": "==5.7.0" + "markers": "python_version < '4.0' and python_full_version >= '3.6.1'", + "version": "==5.9.1" }, "lazy-object-proxy": { "hashes": [ - "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d", - "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449", - "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08", - "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a", - "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50", - "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd", - "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239", - "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb", - "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea", - "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e", - "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156", - "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142", - "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442", - "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62", - "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db", - "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531", - "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383", - "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a", - "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357", - "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", - "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.4.3" + "sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653", + "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61", + "sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2", + "sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837", + "sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3", + "sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43", + "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726", + "sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3", + "sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587", + "sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8", + "sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a", + "sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd", + "sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f", + "sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad", + "sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4", + "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b", + "sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf", + "sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981", + "sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741", + "sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e", + "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93", + "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.6.0" }, "mccabe": { "hashes": [ @@ -460,6 +458,13 @@ ], "version": "==0.6.1" }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" + }, "pathspec": { "hashes": [ "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd", @@ -469,115 +474,132 @@ }, "pbr": { "hashes": [ - "sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9", - "sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00" + "sha256:42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd", + "sha256:c68c661ac5cc81058ac94247278eeda6d2e6aecb3e227b0387c30d277e7ef8d4" ], "markers": "python_version >= '2.6'", - "version": "==5.5.1" + "version": "==5.6.0" }, "pycodestyle": { "hashes": [ - "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", - "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" + "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068", + "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.6.0" + "version": "==2.7.0" }, "pyflakes": { "hashes": [ - "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", - "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" + "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3", + "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.2.0" + "version": "==2.3.1" }, "pylint": { "hashes": [ - "sha256:bb4a908c9dadbc3aac18860550e870f58e1a02c9f2c204fdf5693d73be061210", - "sha256:bfe68f020f8a0fece830a22dd4d5dddb4ecc6137db04face4c3420a46a52239f" + "sha256:23a1dc8b30459d78e9ff25942c61bb936108ccbe29dd9e71c01dc8274961709a", + "sha256:5d46330e6b8886c31b5e3aba5ff48c10f4aa5e76cbf9002c6544306221e63fbc" ], "index": "pypi", - "version": "==2.6.0" + "version": "==2.9.3" }, "pyyaml": { "hashes": [ - "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", - "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", - "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", - "sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e", - "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", - "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", - "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", - "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", - "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", - "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a", - "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", - "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", - "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" - ], - "version": "==5.3.1" + "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf", + "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696", + "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393", + "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77", + "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922", + "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5", + "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8", + "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10", + "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc", + "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018", + "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e", + "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253", + "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347", + "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183", + "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541", + "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb", + "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185", + "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc", + "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db", + "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa", + "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46", + "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122", + "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b", + "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63", + "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df", + "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc", + "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247", + "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6", + "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==5.4.1" }, "regex": { "hashes": [ - "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538", - "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4", - "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc", - "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa", - "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444", - "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1", - "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af", - "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8", - "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9", - "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88", - "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba", - "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364", - "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e", - "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7", - "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0", - "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31", - "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683", - "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee", - "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b", - "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884", - "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c", - "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e", - "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562", - "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85", - "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c", - "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6", - "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d", - "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b", - "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70", - "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b", - "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b", - "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f", - "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0", - "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5", - "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5", - "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f", - "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e", - "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512", - "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d", - "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917", - "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f" - ], - "version": "==2020.11.13" + "sha256:0eb2c6e0fcec5e0f1d3bcc1133556563222a2ffd2211945d7b1480c1b1a42a6f", + "sha256:15dddb19823f5147e7517bb12635b3c82e6f2a3a6b696cc3e321522e8b9308ad", + "sha256:173bc44ff95bc1e96398c38f3629d86fa72e539c79900283afa895694229fe6a", + "sha256:1c78780bf46d620ff4fff40728f98b8afd8b8e35c3efd638c7df67be2d5cddbf", + "sha256:2366fe0479ca0e9afa534174faa2beae87847d208d457d200183f28c74eaea59", + "sha256:2bceeb491b38225b1fee4517107b8491ba54fba77cf22a12e996d96a3c55613d", + "sha256:2ddeabc7652024803666ea09f32dd1ed40a0579b6fbb2a213eba590683025895", + "sha256:2fe5e71e11a54e3355fa272137d521a40aace5d937d08b494bed4529964c19c4", + "sha256:319eb2a8d0888fa6f1d9177705f341bc9455a2c8aca130016e52c7fe8d6c37a3", + "sha256:3f5716923d3d0bfb27048242a6e0f14eecdb2e2a7fac47eda1d055288595f222", + "sha256:422dec1e7cbb2efbbe50e3f1de36b82906def93ed48da12d1714cabcd993d7f0", + "sha256:4c9c3155fe74269f61e27617529b7f09552fbb12e44b1189cebbdb24294e6e1c", + "sha256:4f64fc59fd5b10557f6cd0937e1597af022ad9b27d454e182485f1db3008f417", + "sha256:564a4c8a29435d1f2256ba247a0315325ea63335508ad8ed938a4f14c4116a5d", + "sha256:59506c6e8bd9306cd8a41511e32d16d5d1194110b8cfe5a11d102d8b63cf945d", + "sha256:598c0a79b4b851b922f504f9f39a863d83ebdfff787261a5ed061c21e67dd761", + "sha256:59c00bb8dd8775473cbfb967925ad2c3ecc8886b3b2d0c90a8e2707e06c743f0", + "sha256:6110bab7eab6566492618540c70edd4d2a18f40ca1d51d704f1d81c52d245026", + "sha256:6afe6a627888c9a6cfbb603d1d017ce204cebd589d66e0703309b8048c3b0854", + "sha256:791aa1b300e5b6e5d597c37c346fb4d66422178566bbb426dd87eaae475053fb", + "sha256:8394e266005f2d8c6f0bc6780001f7afa3ef81a7a2111fa35058ded6fce79e4d", + "sha256:875c355360d0f8d3d827e462b29ea7682bf52327d500a4f837e934e9e4656068", + "sha256:89e5528803566af4df368df2d6f503c84fbfb8249e6631c7b025fe23e6bd0cde", + "sha256:99d8ab206a5270c1002bfcf25c51bf329ca951e5a169f3b43214fdda1f0b5f0d", + "sha256:9a854b916806c7e3b40e6616ac9e85d3cdb7649d9e6590653deb5b341a736cec", + "sha256:b85ac458354165405c8a84725de7bbd07b00d9f72c31a60ffbf96bb38d3e25fa", + "sha256:bc84fb254a875a9f66616ed4538542fb7965db6356f3df571d783f7c8d256edd", + "sha256:c92831dac113a6e0ab28bc98f33781383fe294df1a2c3dfd1e850114da35fd5b", + "sha256:cbe23b323988a04c3e5b0c387fe3f8f363bf06c0680daf775875d979e376bd26", + "sha256:ccb3d2190476d00414aab36cca453e4596e8f70a206e2aa8db3d495a109153d2", + "sha256:d8bbce0c96462dbceaa7ac4a7dfbbee92745b801b24bce10a98d2f2b1ea9432f", + "sha256:db2b7df831c3187a37f3bb80ec095f249fa276dbe09abd3d35297fc250385694", + "sha256:e586f448df2bbc37dfadccdb7ccd125c62b4348cb90c10840d695592aa1b29e0", + "sha256:e5983c19d0beb6af88cb4d47afb92d96751fb3fa1784d8785b1cdf14c6519407", + "sha256:e6a1e5ca97d411a461041d057348e578dc344ecd2add3555aedba3b408c9f874", + "sha256:eaf58b9e30e0e546cdc3ac06cf9165a1ca5b3de8221e9df679416ca667972035", + "sha256:ed693137a9187052fc46eedfafdcb74e09917166362af4cc4fddc3b31560e93d", + "sha256:edd1a68f79b89b0c57339bce297ad5d5ffcc6ae7e1afdb10f1947706ed066c9c", + "sha256:f080248b3e029d052bf74a897b9d74cfb7643537fbde97fe8225a6467fb559b5", + "sha256:f9392a4555f3e4cb45310a65b403d86b589adc773898c25a39184b1ba4db8985", + "sha256:f98dc35ab9a749276f1a4a38ab3e0e2ba1662ce710f6530f5b0a6656f1c32b58" + ], + "version": "==2021.7.6" }, "six": { "hashes": [ - "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", - "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==1.15.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" }, "smmap": { "hashes": [ - "sha256:54c44c197c819d5ef1991799a7e30b662d1e520f2ac75c9efbeb54a742214cf4", - "sha256:9c98bbd1f9786d22f14b3d4126894d56befb835ec90cef151af566c7e19b5d24" + "sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182", + "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==3.0.4" + "markers": "python_version >= '3.5'", + "version": "==4.0.0" }, "stevedore": { "hashes": [ @@ -592,44 +614,9 @@ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.10.2" }, - "typed-ast": { - "hashes": [ - "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1", - "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d", - "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6", - "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd", - "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37", - "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151", - "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07", - "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440", - "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70", - "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496", - "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea", - "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400", - "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc", - "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606", - "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc", - "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581", - "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412", - "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a", - "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2", - "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787", - "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f", - "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937", - "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64", - "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487", - "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b", - "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41", - "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a", - "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3", - "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166", - "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10" - ], - "version": "==1.4.2" - }, "wrapt": { "hashes": [ "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7" diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 4aa8248546..0000000000 --- a/poetry.lock +++ /dev/null @@ -1,455 +0,0 @@ -[[package]] -category = "main" -description = "Async http client/server framework (asyncio)" -name = "aiohttp" -optional = false -python-versions = ">=3.5.3" -version = "3.6.2" - -[package.dependencies] -async-timeout = ">=3.0,<4.0" -attrs = ">=17.3.0" -chardet = ">=2.0,<4.0" -multidict = ">=4.5,<5.0" -yarl = ">=1.0,<2.0" - -[[package]] -category = "dev" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -name = "appdirs" -optional = false -python-versions = "*" -version = "1.4.4" - -[[package]] -category = "dev" -description = "An abstract syntax tree for Python with inference support." -name = "astroid" -optional = false -python-versions = ">=3.5" -version = "2.4.1" - -[package.dependencies] -lazy-object-proxy = ">=1.4.0,<1.5.0" -six = ">=1.12,<2.0" -wrapt = ">=1.11,<2.0" - -[package.dependencies.typed-ast] -python = "<3.8" -version = ">=1.4.0,<1.5" - -[[package]] -category = "main" -description = "Timeout context manager for asyncio programs" -name = "async-timeout" -optional = false -python-versions = ">=3.5.3" -version = "3.0.1" - -[[package]] -category = "main" -description = "Classes Without Boilerplate" -name = "attrs" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "19.3.0" - -[[package]] -category = "dev" -description = "Security oriented static analyser for python code." -name = "bandit" -optional = false -python-versions = "*" -version = "1.6.2" - -[package.dependencies] -GitPython = ">=1.0.1" -PyYAML = ">=3.13" -colorama = ">=0.3.9" -six = ">=1.10.0" -stevedore = ">=1.20.0" - -[[package]] -category = "dev" -description = "The uncompromising code formatter." -name = "black" -optional = false -python-versions = ">=3.6" -version = "19.10b0" - -[package.dependencies] -appdirs = "*" -attrs = ">=18.1.0" -click = ">=6.5" -pathspec = ">=0.6,<1" -regex = "*" -toml = ">=0.9.4" -typed-ast = ">=1.4.0" - -[[package]] -category = "main" -description = "Universal encoding detector for Python 2 and 3" -name = "chardet" -optional = false -python-versions = "*" -version = "3.0.4" - -[[package]] -category = "dev" -description = "Composable command line interface toolkit" -name = "click" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "7.1.2" - -[[package]] -category = "main" -description = "Cross-platform colored terminal text." -name = "colorama" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.4.3" - -[[package]] -category = "main" -description = "A Python wrapper for the Discord API" -name = "discord.py" -optional = false -python-versions = ">=3.5.3" -version = "1.3.3" - -[package.dependencies] -aiohttp = ">=3.6.0,<3.7.0" -websockets = ">=6.0,<7.0 || >7.0,<8.0 || >8.0,<8.0.1 || >8.0.1,<9.0" - -[[package]] -category = "main" -description = "DNS toolkit" -name = "dnspython" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.16.0" - -[[package]] -category = "main" -description = "Emoji for Python" -name = "emoji" -optional = false -python-versions = "*" -version = "0.5.4" - -[[package]] -category = "main" -description = "Backport of the concurrent.futures package from Python 3.2" -name = "futures" -optional = true -python-versions = "*" -version = "3.1.1" - -[[package]] -category = "dev" -description = "Git Object Database" -name = "gitdb" -optional = false -python-versions = ">=3.4" -version = "4.0.5" - -[package.dependencies] -smmap = ">=3.0.1,<4" - -[[package]] -category = "dev" -description = "Python Git Library" -name = "gitpython" -optional = false -python-versions = ">=3.4" -version = "3.1.3" - -[package.dependencies] -gitdb = ">=4.0.1,<5" - -[[package]] -category = "main" -description = "Internationalized Domain Names in Applications (IDNA)" -name = "idna" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.9" - -[[package]] -category = "main" -description = "An ISO 8601 date/time/duration parser and formatter" -name = "isodate" -optional = false -python-versions = "*" -version = "0.6.0" - -[package.dependencies] -six = "*" - -[[package]] -category = "dev" -description = "A Python utility / library to sort Python imports." -name = "isort" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "4.3.21" - -[[package]] -category = "dev" -description = "A fast and thorough lazy object proxy." -name = "lazy-object-proxy" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.4.3" - -[[package]] -category = "dev" -description = "McCabe checker, plugin for flake8" -name = "mccabe" -optional = false -python-versions = "*" -version = "0.6.1" - -[[package]] -category = "main" -description = "Non-blocking MongoDB driver for Tornado or asyncio" -name = "motor" -optional = true -python-versions = "*" -version = "2.1.0" - -[package.dependencies] -futures = "*" -pymongo = ">=3.10,<4" - -[[package]] -category = "main" -description = "multidict implementation" -name = "multidict" -optional = false -python-versions = ">=3.5" -version = "4.7.6" - -[[package]] -category = "main" -description = "Convert data to their natural (human-readable) format" -name = "natural" -optional = false -python-versions = "*" -version = "0.2.0" - -[package.dependencies] -six = "*" - -[[package]] -category = "main" -description = "Parse human-readable date/time text." -name = "parsedatetime" -optional = false -python-versions = "*" -version = "2.6" - -[[package]] -category = "dev" -description = "Utility library for gitignore style pattern matching of file paths." -name = "pathspec" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.8.0" - -[[package]] -category = "dev" -description = "Python Build Reasonableness" -name = "pbr" -optional = false -python-versions = "*" -version = "5.4.5" - -[[package]] -category = "dev" -description = "python code static checker" -name = "pylint" -optional = false -python-versions = ">=3.5.*" -version = "2.5.2" - -[package.dependencies] -astroid = ">=2.4.0,<=2.5" -colorama = "*" -isort = ">=4.2.5,<5" -mccabe = ">=0.6,<0.7" -toml = ">=0.7.1" - -[[package]] -category = "main" -description = "Python driver for MongoDB " -name = "pymongo" -optional = true -python-versions = "*" -version = "3.10.1" - -[[package]] -category = "main" -description = "Extensions to the standard Python datetime module" -name = "python-dateutil" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -version = "2.8.1" - -[package.dependencies] -six = ">=1.5" - -[[package]] -category = "main" -description = "Add .env support to your django/flask apps in development and deployments" -name = "python-dotenv" -optional = false -python-versions = "*" -version = "0.10.5" - -[[package]] -category = "dev" -description = "YAML parser and emitter for Python" -name = "pyyaml" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "5.3.1" - -[[package]] -category = "dev" -description = "Alternative regular expression module, to replace re." -name = "regex" -optional = false -python-versions = "*" -version = "2020.6.7" - -[[package]] -category = "main" -description = "Python 2 and 3 compatibility utilities" -name = "six" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -version = "1.15.0" - -[[package]] -category = "dev" -description = "A pure Python implementation of a sliding window memory map manager" -name = "smmap" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "3.0.4" - -[[package]] -category = "dev" -description = "Manage dynamic plugins for Python applications" -name = "stevedore" -optional = false -python-versions = ">=3.6" -version = "2.0.0" - -[package.dependencies] -pbr = ">=2.0.0,<2.1.0 || >2.1.0" - -[[package]] -category = "dev" -description = "Python Library for Tom's Obvious, Minimal Language" -name = "toml" -optional = false -python-versions = "*" -version = "0.10.1" - -[[package]] -category = "dev" -description = "a fork of Python 2 and 3 ast modules with type comment support" -name = "typed-ast" -optional = false -python-versions = "*" -version = "1.4.1" - -[[package]] -category = "main" -description = "Fast implementation of asyncio event loop on top of libuv" -name = "uvloop" -optional = false -python-versions = "*" -version = "0.14.0" - -[[package]] -category = "main" -description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -name = "websockets" -optional = false -python-versions = ">=3.6.1" -version = "8.1" - -[[package]] -category = "dev" -description = "Module for decorators, wrappers and monkey patching." -name = "wrapt" -optional = false -python-versions = "*" -version = "1.12.1" - -[[package]] -category = "main" -description = "Yet another URL library" -name = "yarl" -optional = false -python-versions = ">=3.5" -version = "1.4.2" - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" - -[extras] -mongodb = ["motor"] - -[metadata] -content-hash = "68d5c15e62c4bf5f65fd3b0a9a2586b15557f724c3de5b756534bacd52cbfe40" -python-versions = "^3.7" - -[metadata.hashes] -aiohttp = ["1e984191d1ec186881ffaed4581092ba04f7c61582a177b187d3a2f07ed9719e", "259ab809ff0727d0e834ac5e8a283dc5e3e0ecc30c4d80b3cd17a4139ce1f326", "2f4d1a4fdce595c947162333353d4a44952a724fba9ca3205a3df99a33d1307a", "32e5f3b7e511aa850829fbe5aa32eb455e5534eaa4b1ce93231d00e2f76e5654", "344c780466b73095a72c616fac5ea9c4665add7fc129f285fbdbca3cccf4612a", "460bd4237d2dbecc3b5ed57e122992f60188afe46e7319116da5eb8a9dfedba4", "4c6efd824d44ae697814a2a85604d8e992b875462c6655da161ff18fd4f29f17", "50aaad128e6ac62e7bf7bd1f0c0a24bc968a0c0590a726d5a955af193544bcec", "6206a135d072f88da3e71cc501c59d5abffa9d0bb43269a6dcd28d66bfafdbdd", "65f31b622af739a802ca6fd1a3076fd0ae523f8485c52924a89561ba10c49b48", "ae55bac364c405caa23a4f2d6cfecc6a0daada500274ffca4a9230e7129eac59", "b778ce0c909a2653741cb4b1ac7015b5c130ab9c897611df43ae6a58523cb965"] -appdirs = ["7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", "a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"] -astroid = ["4c17cea3e592c21b6e222f673868961bad77e1f985cb1694ed077475a89229c1", "d8506842a3faf734b81599c8b98dcc423de863adcc1999248480b18bd31a0f38"] -async-timeout = ["0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", "4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"] -attrs = ["08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", "f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"] -bandit = ["336620e220cf2d3115877685e264477ff9d9abaeb0afe3dc7264f55fa17a3952", "41e75315853507aa145d62a78a2a6c5e3240fe14ee7c601459d0df9418196065"] -black = ["1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b", "c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"] -chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"] -click = ["d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", "dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"] -colorama = ["7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", "e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"] -"discord.py" = ["406871b06d86c3dc49fba63238519f28628dac946fef8a0e22988ff58ec05580", "ad00e34c72d2faa8db2157b651d05f3c415d7d05078e7e41dc9e8dc240051beb"] -dnspython = ["36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01", "f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d"] -emoji = ["60652d3a2dcee5b8af8acb097c31776fb6d808027aeb7221830f72cdafefc174"] -futures = ["3a44f286998ae64f0cc083682fcfec16c406134a81a589a5de445d7bb7c2751b", "51ecb45f0add83c806c68e4b06106f90db260585b25ef2abfcda0bd95c0132fd", "c4884a65654a7c45435063e14ae85280eb1f111d94e542396717ba9828c4337f"] -gitdb = ["91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac", "c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9"] -gitpython = ["e107af4d873daed64648b4f4beb89f89f0cfbe3ef558fc7821ed2331c2f8da1a", "ef1d60b01b5ce0040ad3ec20bc64f783362d41fa0822a2742d3586e1f49bb8ac"] -idna = ["7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", "a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"] -isodate = ["2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8", "aa4d33c06640f5352aca96e4b81afd8ab3b47337cc12089822d6f322ac772c81"] -isort = ["54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", "6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"] -lazy-object-proxy = ["0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d", "194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449", "1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08", "4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a", "48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50", "5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd", "59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239", "8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb", "9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea", "9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e", "97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156", "9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142", "a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442", "a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62", "ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db", "cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531", "d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383", "d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a", "eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357", "efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", "f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"] -mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"] -motor = ["599719bc6dcddc3b9ea4e09659fb0073d5fadcc24735999b2902f48cef33f909", "756c587985d166166e644ccd36fb8b586fb987eb42fc0fc60cce9a3d76d809b4", "97b4fc0a00a84df30f866d18693c503eef46c7642f75218a2c44d74d835be38a"] -multidict = ["1ece5a3369835c20ed57adadc663400b5525904e53bae59ec854a5d36b39b21a", "275ca32383bc5d1894b6975bb4ca6a7ff16ab76fa622967625baeebcf8079000", "3750f2205b800aac4bb03b5ae48025a64e474d2c6cc79547988ba1d4122a09e2", "4538273208e7294b2659b1602490f4ed3ab1c8cf9dbdd817e0e9db8e64be2507", "5141c13374e6b25fe6bf092052ab55c0c03d21bd66c94a0e3ae371d3e4d865a5", "51a4d210404ac61d32dada00a50ea7ba412e6ea945bbe992e4d7a595276d2ec7", "5cf311a0f5ef80fe73e4f4c0f0998ec08f954a6ec72b746f3c179e37de1d210d", "6513728873f4326999429a8b00fc7ceddb2509b01d5fd3f3be7881a257b8d463", "7388d2ef3c55a8ba80da62ecfafa06a1c097c18032a501ffd4cabbc52d7f2b19", "9456e90649005ad40558f4cf51dbb842e32807df75146c6d940b6f5abb4a78f3", "c026fe9a05130e44157b98fea3ab12969e5b60691a276150db9eda71710cd10b", "d14842362ed4cf63751648e7672f7174c9818459d169231d03c56e84daf90b7c", "e0d072ae0f2a179c375f67e3da300b47e1a83293c554450b29c900e50afaae87", "f07acae137b71af3bb548bd8da720956a3bc9f9a0b87733e0899226a2317aeb7", "fbb77a75e529021e7c4a8d4e823d88ef4d23674a202be4f5addffc72cbb91430", "fcfbb44c59af3f8ea984de67ec7c306f618a3ec771c2843804069917a8f2e255", "feed85993dbdb1dbc29102f50bca65bdc68f2c0c8d352468c25b54874f23c39d"] -natural = ["18c83662d2d33fd7e6eee4e3b0d7366e1ce86225664e3127a2aaf0a3233f7df2"] -parsedatetime = ["4cb368fbb18a0b7231f4d76119165451c8d2e35951455dfee97c62a87b04d455", "cb96edd7016872f58479e35879294258c71437195760746faffedb692aef000b"] -pathspec = ["7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0", "da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"] -pbr = ["07f558fece33b05caf857474a366dfcc00562bca13dd8b47b2b3e22d9f9bf55c", "579170e23f8e0c2f24b0de612f71f648eccb79fb1322c814ae6b3c07b5ba23e8"] -pylint = ["b95e31850f3af163c2283ed40432f053acbc8fc6eba6a069cb518d9dbf71848c", "dd506acce0427e9e08fb87274bcaa953d38b50a58207170dbf5b36cf3e16957b"] -pymongo = ["01b4e10027aef5bb9ecefbc26f5df3368ce34aef81df43850f701e716e3fe16d", "0fc5aa1b1acf7f61af46fe0414e6a4d0c234b339db4c03a63da48599acf1cbfc", "1396eb7151e0558b1f817e4b9d7697d5599e5c40d839a9f7270bd90af994ad82", "18e84a3ec5e73adcb4187b8e5541b2ad61d716026ed9863267e650300d8bea33", "19adf2848b80cb349b9891cc854581bbf24c338be9a3260e73159bdeb2264464", "20ee0475aa2ba437b0a14806f125d696f90a8433d820fb558fdd6f052acde103", "26798795097bdeb571f13942beef7e0b60125397811c75b7aa9214d89880dd1d", "26e707a4eb851ec27bb969b5f1413b9b2eac28fe34271fa72329100317ea7c73", "2a3c7ad01553b27ec553688a1e6445e7f40355fb37d925c11fcb50b504e367f8", "2f07b27dbf303ea53f4147a7922ce91a26b34a0011131471d8aaf73151fdee9a", "316f0cf543013d0c085e15a2c8abe0db70f93c9722c0f99b6f3318ff69477d70", "31d11a600eea0c60de22c8bdcb58cda63c762891facdcb74248c36713240987f", "334ef3ffd0df87ea83a0054454336159f8ad9c1b389e19c0032d9cb8410660e6", "358ba4693c01022d507b96a980ded855a32dbdccc3c9331d0667be5e967f30ed", "3a6568bc53103df260f5c7d2da36dffc5202b9a36c85540bba1836a774943794", "444bf2f44264578c4085bb04493bfed0e5c1b4fe7c2704504d769f955cc78fe4", "47a00b22c52ee59dffc2aad02d0bbfb20c26ec5b8de8900492bf13ad6901cf35", "4c067db43b331fc709080d441cb2e157114fec60749667d12186cc3fc8e7a951", "4c092310f804a5d45a1bcaa4191d6d016c457b6ed3982a622c35f729ff1c7f6b", "53b711b33134e292ef8499835a3df10909c58df53a2a0308f598c432e9a62892", "568d6bee70652d8a5af1cd3eec48b4ca1696fb1773b80719ebbd2925b72cb8f6", "56fa55032782b7f8e0bf6956420d11e2d4e9860598dfe9c504edec53af0fc372", "5a2c492680c61b440272341294172fa3b3751797b1ab983533a770e4fb0a67ac", "61235cc39b5b2f593086d1d38f3fc130b2d125bd8fc8621d35bc5b6bdeb92bd2", "619ac9aaf681434b4d4718d1b31aa2f0fce64f2b3f8435688fcbdc0c818b6c54", "6238ac1f483494011abde5286282afdfacd8926659e222ba9b74c67008d3a58c", "63752a72ca4d4e1386278bd43d14232f51718b409e7ac86bcf8810826b531113", "6fdc5ccb43864065d40dd838437952e9e3da9821b7eac605ba46ada77f846bdf", "7abc3a6825a346fa4621a6f63e3b662bbb9e0f6ffc32d30a459d695f20fb1a8b", "7aef381bb9ae8a3821abd7f9d4d93978dbd99072b48522e181baeffcd95b56ae", "80df3caf251fe61a3f0c9614adc6e2bfcffd1cd3345280896766712fb4b4d6d7", "95f970f34b59987dee6f360d2e7d30e181d58957b85dff929eee4423739bd151", "993257f6ca3cde55332af1f62af3e04ca89ce63c08b56a387cdd46136c72f2fa", "9c0a57390549affc2b5dda24a38de03a5c7cbc58750cd161ff5d106c3c6eec80", "a0794e987d55d2f719cc95fcf980fc62d12b80e287e6a761c4be14c60bd9fecc", "a3b98121e68bf370dd8ea09df67e916f93ea95b52fc010902312168c4d1aff5d", "a60756d55f0887023b3899e6c2923ba5f0042fb11b1d17810b4e07395404f33e", "a676bd2fbc2309092b9bbb0083d35718b5420af3a42135ebb1e4c3633f56604d", "a732838c78554c1257ff2492f5c8c4c7312d0aecd7f732149e255f3749edd5ee", "ad3dc88dfe61f0f1f9b99c6bc833ea2f45203a937a18f0d2faa57c6952656012", "ae65d65fde4135ef423a2608587c9ef585a3551fc2e4e431e7c7e527047581be", "b070a4f064a9edb70f921bfdc270725cff7a78c22036dd37a767c51393fb956f", "b6da85949aa91e9f8c521681344bd2e163de894a5492337fba8b05c409225a4f", "bbf47110765b2a999803a7de457567389253f8670f7daafb98e059c899ce9764", "bd9c1e6f92b4888ae3ef7ae23262c513b962f09f3fb3b48581dde5df7d7a860a", "c06b3f998d2d7160db58db69adfb807d2ec307e883e2f17f6b87a1ef6c723f11", "c318fb70542be16d3d4063cde6010b1e4d328993a793529c15a619251f517c39", "c4aef42e5fa4c9d5a99f751fb79caa880dac7eaf8a65121549318b984676a1b7", "c9ca545e93a9c2a3bdaa2e6e21f7a43267ff0813e8055adf2b591c13164c0c57", "da2c3220eb55c4239dd8b982e213da0b79023cac59fe54ca09365f2bc7e4ad32", "dd8055da300535eefd446b30995c0813cc4394873c9509323762a93e97c04c03", "e2b46e092ea54b732d98c476720386ff2ccd126de1e52076b470b117bff7e409", "e334c4f39a2863a239d38b5829e442a87f241a92da9941861ee6ec5d6380b7fe", "e5c54f04ca42bbb5153aec5d4f2e3d9f81e316945220ac318abd4083308143f5", "f4d06764a06b137e48db6d569dc95614d9d225c89842c885669ee8abc9f28c7a", "f96333f9d2517c752c20a35ff95de5fc2763ac8cdb1653df0f6f45d281620606"] -python-dateutil = ["73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", "75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"] -python-dotenv = ["440c7c23d53b7d352f9c94d6f70860242c2f071cf5c029dd661ccb22d64ae42b", "f254bfd0c970d64ccbb6c9ebef3667ab301a71473569c991253a481f1c98dddc"] -pyyaml = ["06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", "240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", "4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", "69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", "73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", "74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", "7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", "95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", "b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", "cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", "d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"] -regex = ["150125da109fccdcc8fec3b0b386b2a5d6ca7cff076f8b622486d1ca868b0c10", "163bc0805e46acfa098dfc8c0b07f371577d505f603e48afc425ff475cdac3a5", "20c513893ff80bdbe4b4ce11ea2e93d49481f05b270595d82af69ffc402010a6", "21fc17cb868c4264f0813f992f46f9ae6fc8c309d4741091de4153bd1f6a6176", "2c928bc8e0c453d73dffa3193a6e37ee752ea36df0dd4601e21024d98274dfad", "2d9beca70e36f9c60d679e108c5fe49f3d4da79d13a13f91e5e759443bd954f9", "5735f26cacdb50b3d6d35ebf8fdeb504bd8b381e2d079d2d9f12ce534fc14ecd", "6edc5c190248d3b612f2cca45448cf8ebc3621d41afcd1c5708853cbb1dbb3b3", "7606dba82435429641efe4fbc580574942f89cf2b9c5c1f8bc1eab2bacbf7e8b", "8d1ee3796795e609ef7a3a5a35eaf4728038d986aa12c06b3fd1b92ee81911f4", "8d9bb2d90e23c51aacbc58c1a11320f49b335cd67a91986cdbebcc3e843e4de8", "97d414c41f19fd2362e493810caa8445c05e0a2d63a14081c972aad66284a8d2", "9e37502817225ee99d91d8418f5119e98c380b00e772d06915690c05290f32ee", "af7209b2fcc79ee2b0ad4ea080d70bb748450ec4f282cc9e864861e469b1072e", "c0849b0864ff451f04c8afb5fc28e9ed592262e03debdd227cf0f53e04a55dcd", "c4ac9215650688e78dea29b46adbdafb7b85058eebe92ef6ea848e14466c915f", "dcda6d4e1bbfc939b177c237aee41c9678eaaf71df482688f8986e8251e12345", "dd8501b8d9ea1aba53c4bc7d47bc72933f9b4213d534cf400f16c1431f51c8ba", "ec0e509ed1877ff1cbc6f0864689bb60384a303502c4d72d9a635f8a4676fd3f", "f6c8c3f56fef719180464855346e6e80971b86dfd9e5a0e356664b5baca53072", "ffd4f80602490a309064cf2b203e220d581c51660e01055c64bf5da450485ee6"] -six = ["30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"] -smmap = ["54c44c197c819d5ef1991799a7e30b662d1e520f2ac75c9efbeb54a742214cf4", "9c98bbd1f9786d22f14b3d4126894d56befb835ec90cef151af566c7e19b5d24"] -stevedore = ["001e90cd704be6470d46cc9076434e2d0d566c1379187e7013eb296d3a6032d9", "471c920412265cc809540ae6fb01f3f02aba89c79bbc7091372f4745a50f9691"] -toml = ["926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", "bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"] -typed-ast = ["0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", "0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", "249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", "24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", "269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", "4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", "498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", "4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", "6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", "715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", "73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", "8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", "8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", "aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", "bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", "c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", "d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", "d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", "d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", "fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", "fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"] -uvloop = ["08b109f0213af392150e2fe6f81d33261bb5ce968a288eb698aad4f46eb711bd", "123ac9c0c7dd71464f58f1b4ee0bbd81285d96cdda8bc3519281b8973e3a461e", "4315d2ec3ca393dd5bc0b0089d23101276778c304d42faff5dc4579cb6caef09", "4544dcf77d74f3a84f03dd6278174575c44c67d7165d4c42c71db3fdc3860726", "afd5513c0ae414ec71d24f6f123614a80f3d27ca655a4fcf6cabe50994cc1891", "b4f591aa4b3fa7f32fb51e2ee9fea1b495eb75b0b3c8d0ca52514ad675ae63f7", "bcac356d62edd330080aed082e78d4b580ff260a677508718f88016333e2c9c5", "e7514d7a48c063226b7d06617cbb12a14278d4323a065a8d46a7962686ce2e95", "f07909cd9fc08c52d294b1570bba92186181ca01fe3dc9ffba68955273dd7362"] -websockets = ["0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5", "1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5", "20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308", "295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb", "2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a", "3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c", "3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170", "3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422", "4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8", "5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485", "5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f", "751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8", "7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc", "965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779", "9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989", "9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1", "c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092", "c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824", "ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d", "d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55", "e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36", "f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b"] -wrapt = ["b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"] -yarl = ["0c2ab325d33f1b824734b3ef51d4d54a54e0e7a23d13b86974507602334c2cce", "0ca2f395591bbd85ddd50a82eb1fde9c1066fafe888c5c7cc1d810cf03fd3cc6", "2098a4b4b9d75ee352807a95cdf5f10180db903bc5b7270715c6bbe2551f64ce", "25e66e5e2007c7a39541ca13b559cd8ebc2ad8fe00ea94a2aad28a9b1e44e5ae", "26d7c90cb04dee1665282a5d1a998defc1a9e012fdca0f33396f81508f49696d", "308b98b0c8cd1dfef1a0311dc5e38ae8f9b58349226aa0533f15a16717ad702f", "3ce3d4f7c6b69c4e4f0704b32eca8123b9c58ae91af740481aa57d7857b5e41b", "58cd9c469eced558cd81aa3f484b2924e8897049e06889e8ff2510435b7ef74b", "5b10eb0e7f044cf0b035112446b26a3a2946bca9d7d7edb5e54a2ad2f6652abb", "6faa19d3824c21bcbfdfce5171e193c8b4ddafdf0ac3f129ccf0cdfcb083e462", "944494be42fa630134bf907714d40207e646fd5a94423c90d5b514f7b0713fea", "a161de7e50224e8e3de6e184707476b5a989037dcb24292b391a3d66ff158e70", "a4844ebb2be14768f7994f2017f70aca39d658a96c786211be5ddbe1c68794c1", "c2b509ac3d4b988ae8769901c66345425e361d518aecbe4acbfc2567e416626a", "c9959d49a77b0e07559e579f38b2f3711c2b8716b8410b320bf9713013215a1b", "d8cdee92bc930d8b09d8bd2043cedd544d9c8bd7436a77678dd602467a993080", "e15199cdb423316e15f108f51249e44eb156ae5dba232cb73be555324a1d49c2"] diff --git a/pyproject.toml b/pyproject.toml index ff08d3ee31..a920956e85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,41 +18,3 @@ exclude = ''' )/ ) ''' - -[tool.poetry] -name = 'Modmail' -version = '3.9.4' -description = "Modmail is similar to Reddit's Modmail, both in functionality and purpose. It serves as a shared inbox for server staff to communicate with their users in a seamless way." -license = 'AGPL-3.0-only' -authors = [ - 'kyb3r ', - '4jr ', - 'Taki ' -] -readme = 'README.md' -repository = 'https://github.com/kyb3r/modmail' -homepage = 'https://github.com/kyb3r/modmail' -keywords = ['discord', 'modmail'] - -[tool.poetry.dependencies] -python = "^3.7" -"discord.py" = "discord.py==1.6.0" -uvloop = {version = ">=0.12.0", markers = "sys_platform != 'win32'"} -python-dotenv = ">=0.10.3" -parsedatetime = "^2.6" -dnspython = "^1.16" -isodate = "^0.6.0" -natural = "^0.2.0" -motor = {version = "^2.1", optional = true} -emoji = "^0.5.4" -python-dateutil = "^2.8" -colorama = "^0.4.3" -aiohttp = ">=3.6.0,<3.7.0" - -[tool.poetry.dev-dependencies] -black = {version = "=19.10b0", allow-prereleases = true} -pylint = "^2.4" -bandit = "^1.6" - -[tool.poetry.extras] -mongodb = ["motor"] diff --git a/requirements.min.txt b/requirements.min.txt deleted file mode 100644 index d756599661..0000000000 --- a/requirements.min.txt +++ /dev/null @@ -1,16 +0,0 @@ -# Generated as of March, 2021 -# This is the bare minimum requirements.txt for running Modmail. -# To install requirements.txt run: pip install -r requirements.min.txt - -aiohttp==3.6.2 -discord.py==1.6.0 -dnspython==1.16.0 -emoji==0.5.4 -isodate==0.6.0 -motor==2.1.0 -natural==0.2.0 -parsedatetime==2.6 -pymongo==3.10.1 -python-dateutil==2.8.1 -python-dotenv==0.14.0 -websockets==8.1 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000..7e1360e73e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,29 @@ +# +# These requirements were autogenerated by pipenv +# To regenerate from the project's Pipfile, run: +# +# pipenv lock --requirements +# + +-i https://pypi.org/simple +aiohttp==3.7.4.post0 +async-timeout==3.0.1; python_full_version >= '3.5.3' +attrs==21.2.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' +chardet==4.0.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' +colorama==0.4.4 +discord.py==1.7.3 +emoji==1.2.0 +idna==3.2; python_version >= '3.5' +isodate==0.6.0 +motor==2.4.0 +multidict==5.1.0; python_version >= '3.6' +natural==0.2.0 +parsedatetime==2.6 +pymongo==3.11.4 +python-dateutil==2.8.1 +python-dotenv==0.18.0 +six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +typing-extensions==3.10.0.0 +uvloop==0.15.2; sys_platform != 'win32' +wheel==0.36.2 +yarl==1.6.3; python_version >= '3.6' diff --git a/runtime.txt b/runtime.txt deleted file mode 100644 index 87665291b8..0000000000 --- a/runtime.txt +++ /dev/null @@ -1 +0,0 @@ -python-3.9.4 From f976165e8274a683c349c381ebbeabc851d7f915 Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Wed, 7 Jul 2021 12:42:32 +0800 Subject: [PATCH 134/209] Sort pipfile --- Pipfile | 16 ++++++++-------- Pipfile.lock | 10 +++++----- bot.py | 4 ++-- requirements.txt | 3 +-- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/Pipfile b/Pipfile index eeb21b15e7..5c1d2f8670 100644 --- a/Pipfile +++ b/Pipfile @@ -4,23 +4,23 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] -black = "==21.6b0" -pylint = "~=2.9.3" bandit = "~=1.7.0" +black = "==21.6b0" flake8 = "~=3.9.2" +pylint = "~=2.9.3" [packages] +aiohttp = "==3.7.4.post0" colorama = "~=0.4.4" -python-dateutil = "~=2.8.1" +"discord.py" = "==1.7.3" emoji = "~=1.2.0" -uvloop = {version = ">=0.15.2",sys_platform = "!= 'win32'"} +isodate = "~=0.6.0" motor = "~=2.4.0" natural = "~=0.2.0" -isodate = "~=0.6.0" parsedatetime = "~=2.6" -aiohttp = "==3.7.4.post0" -python-dotenv = ">=0.18.0" -"discord.py" = "==1.7.3" +python-dateutil = "~=2.8.1" +python-dotenv = "~=0.18.0" +uvloop = {version = ">=0.15.2", markers = "sys_platform != 'win32'"} [scripts] bot = "python bot.py" diff --git a/Pipfile.lock b/Pipfile.lock index cfaece3094..892d2fddfa 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "0700f9473d2588f168a63141ac5f44596d303e7a55db173968edddd0ace2e2ac" + "sha256": "0ffab0587a7d21d864d2c5a7a789e7c883caada73c9c23ff275877aad567729e" }, "pipfile-spec": 6, "requires": { @@ -279,7 +279,7 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.16.0" }, "typing-extensions": { @@ -420,7 +420,7 @@ "sha256:83510593e07e433b77bd5bff0f6f607dbafa06d1a89022616f02d8b699cfcd56", "sha256:8e2c107091cfec7286bc0f68a547d0ba4c094d460b732075b6fba674f1035c0c" ], - "markers": "python_version < '4.0' and python_full_version >= '3.6.1'", + "markers": "python_full_version >= '3.6.1' and python_version < '4.0'", "version": "==5.9.1" }, "lazy-object-proxy": { @@ -590,7 +590,7 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.16.0" }, "smmap": { @@ -614,7 +614,7 @@ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==0.10.2" }, "wrapt": { diff --git a/bot.py b/bot.py index 1b9a9f2c1f..6102caa915 100644 --- a/bot.py +++ b/bot.py @@ -1695,9 +1695,9 @@ def main(): pass # check discord version - if discord.__version__ != "1.6.0": + if discord.__version__ != "1.7.3": logger.error( - "Dependencies are not updated, run pipenv install. discord.py version expected 1.6.0, received %s", + "Dependencies are not updated, run pipenv install. discord.py version expected 1.7.3, received %s", discord.__version__, ) sys.exit(0) diff --git a/requirements.txt b/requirements.txt index 7e1360e73e..0cc483cc3c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,8 +22,7 @@ parsedatetime==2.6 pymongo==3.11.4 python-dateutil==2.8.1 python-dotenv==0.18.0 -six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2' typing-extensions==3.10.0.0 uvloop==0.15.2; sys_platform != 'win32' -wheel==0.36.2 yarl==1.6.3; python_version >= '3.6' From b5f593035b4a1137438b8ca29dd9f98701953adc Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Wed, 7 Jul 2021 19:37:11 +0800 Subject: [PATCH 135/209] Remove flake8 from req, use new rec line-width of 110, black format --- .github/workflows/lints.yml | 15 ++-- Pipfile | 1 - Pipfile.lock | 34 ++------- bot.py | 127 +++++++++----------------------- cogs/modmail.py | 76 ++++++------------- cogs/plugins.py | 36 +++------ cogs/utility.py | 143 ++++++++++++------------------------ core/changelog.py | 12 +-- core/checks.py | 4 +- core/clients.py | 30 +++----- core/config.py | 16 +--- core/models.py | 4 +- core/thread.py | 103 ++++++++------------------ core/time.py | 4 +- core/utils.py | 3 +- pyproject.toml | 12 ++- 16 files changed, 195 insertions(+), 425 deletions(-) diff --git a/.github/workflows/lints.yml b/.github/workflows/lints.yml index 94b322d968..145a83d489 100644 --- a/.github/workflows/lints.yml +++ b/.github/workflows/lints.yml @@ -14,20 +14,19 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - name: Set up Python 3.7 - uses: actions/setup-python@v1 + - uses: actions/checkout@v2 + - name: Set up Python 3.9 + uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: 3.9 - name: Install dependencies run: | - python -m pip install --upgrade pip - python -m pip install bandit==1.6.2 pylint black==19.10b0 - continue-on-error: true + python -m pip install --upgrade pip pipenv + pipenv install --dev --system - name: Bandit syntax check run: bandit -r . -b .bandit_baseline.json - name: Pylint - run: pylint ./bot.py cogs/*.py core/*.py --disable=import-error --exit-zero -r y + run: pylint ./bot.py cogs/*.py core/*.py --exit-zero -r y continue-on-error: true - name: Black run: | diff --git a/Pipfile b/Pipfile index 5c1d2f8670..813b6700aa 100644 --- a/Pipfile +++ b/Pipfile @@ -6,7 +6,6 @@ verify_ssl = true [dev-packages] bandit = "~=1.7.0" black = "==21.6b0" -flake8 = "~=3.9.2" pylint = "~=2.9.3" [packages] diff --git a/Pipfile.lock b/Pipfile.lock index 892d2fddfa..b10f516efc 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "0ffab0587a7d21d864d2c5a7a789e7c883caada73c9c23ff275877aad567729e" + "sha256": "dedff7c4534f29ac1560a57f358852e90d6e6b9d04eb07a35f6daa2d661bc0c2" }, "pipfile-spec": 6, "requires": { @@ -279,7 +279,7 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, "typing-extensions": { @@ -391,14 +391,6 @@ "markers": "python_version >= '3.6'", "version": "==8.0.1" }, - "flake8": { - "hashes": [ - "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b", - "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907" - ], - "index": "pypi", - "version": "==3.9.2" - }, "gitdb": { "hashes": [ "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0", @@ -420,7 +412,7 @@ "sha256:83510593e07e433b77bd5bff0f6f607dbafa06d1a89022616f02d8b699cfcd56", "sha256:8e2c107091cfec7286bc0f68a547d0ba4c094d460b732075b6fba674f1035c0c" ], - "markers": "python_full_version >= '3.6.1' and python_version < '4.0'", + "markers": "python_version < '4.0' and python_full_version >= '3.6.1'", "version": "==5.9.1" }, "lazy-object-proxy": { @@ -480,22 +472,6 @@ "markers": "python_version >= '2.6'", "version": "==5.6.0" }, - "pycodestyle": { - "hashes": [ - "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068", - "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.7.0" - }, - "pyflakes": { - "hashes": [ - "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3", - "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.3.1" - }, "pylint": { "hashes": [ "sha256:23a1dc8b30459d78e9ff25942c61bb936108ccbe29dd9e71c01dc8274961709a", @@ -590,7 +566,7 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, "smmap": { @@ -614,7 +590,7 @@ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.10.2" }, "wrapt": { diff --git a/bot.py b/bot.py index 6102caa915..ce7cfc529d 100644 --- a/bot.py +++ b/bot.py @@ -328,9 +328,7 @@ def log_channel(self) -> typing.Optional[discord.TextChannel]: try: channel = self.main_category.channels[0] self.config["log_channel_id"] = channel.id - logger.warning( - "No log channel set, setting #%s to be the log channel.", channel.name - ) + logger.warning("No log channel set, setting #%s to be the log channel.", channel.name) return channel except IndexError: pass @@ -392,9 +390,7 @@ def auto_triggers(self) -> typing.Dict[str, str]: def token(self) -> str: token = self.config["token"] if token is None: - logger.critical( - "TOKEN must be set, set this as bot token found on the Discord Developer Portal." - ) + logger.critical("TOKEN must be set, set this as bot token found on the Discord Developer Portal.") sys.exit(0) return token @@ -510,11 +506,7 @@ def command_perm(self, command_name: str) -> PermissionLevel: logger.debug("Command %s not found.", command_name) return PermissionLevel.INVALID level = next( - ( - check.permission_level - for check in command.checks - if hasattr(check, "permission_level") - ), + (check.permission_level for check in command.checks if hasattr(check, "permission_level")), None, ) if level is None: @@ -549,8 +541,7 @@ async def on_ready(self): logger.info("Logged in as: %s", self.user) logger.info("Bot ID: %s", self.user.id) owners = ", ".join( - getattr(self.get_user(owner_id), "name", str(owner_id)) - for owner_id in self.bot_owner_ids + getattr(self.get_user(owner_id), "name", str(owner_id)) for owner_id in self.bot_owner_ids ) logger.info("Owners: %s", owners) logger.info("Prefix: %s", self.prefix) @@ -573,9 +564,7 @@ async def on_ready(self): logger.debug("Closing thread for recipient %s.", recipient_id) after = 0 else: - logger.debug( - "Thread for recipient %s will be closed after %s seconds.", recipient_id, after - ) + logger.debug("Thread for recipient %s will be closed after %s seconds.", recipient_id, after) thread = await self.threads.find(recipient_id=int(recipient_id)) @@ -617,9 +606,7 @@ async def on_ready(self): if log_data: logger.debug("Successfully closed thread with channel %s.", log["channel_id"]) else: - logger.debug( - "Failed to close thread with channel %s, skipping.", log["channel_id"] - ) + logger.debug("Failed to close thread with channel %s, skipping.", log["channel_id"]) if self.config.get("data_collection"): self.metadata_loop = tasks.Loop( @@ -640,9 +627,7 @@ async def on_ready(self): self.autoupdate_loop.before_loop(self.before_autoupdate) self.autoupdate_loop.start() - other_guilds = [ - guild for guild in self.guilds if guild not in {self.guild, self.modmail_guild} - ] + other_guilds = [guild for guild in self.guilds if guild not in {self.guild, self.modmail_guild}] if any(other_guilds): logger.warning( "The bot is in more servers other than the main and staff server. " @@ -880,9 +865,7 @@ async def get_thread_cooldown(self, author: discord.Member): cooldown = datetime.fromisoformat(last_log_closed_at) + thread_cooldown except ValueError: logger.warning("Error with 'thread_cooldown'.", exc_info=True) - cooldown = datetime.fromisoformat(last_log_closed_at) + self.config.remove( - "thread_cooldown" - ) + cooldown = datetime.fromisoformat(last_log_closed_at) + self.config.remove("thread_cooldown") if cooldown > now: # User messaged before thread cooldown ended @@ -930,12 +913,8 @@ async def process_dm_modmail(self, message: discord.Message) -> None: color=self.error_color, description=self.config["disabled_new_thread_response"], ) - embed.set_footer( - text=self.config["disabled_new_thread_footer"], icon_url=self.guild.icon_url - ) - logger.info( - "A new thread was blocked from %s due to disabled Modmail.", message.author - ) + embed.set_footer(text=self.config["disabled_new_thread_footer"], icon_url=self.guild.icon_url) + logger.info("A new thread was blocked from %s due to disabled Modmail.", message.author) await self.add_reaction(message, blocked_emoji) return await message.channel.send(embed=embed) @@ -951,9 +930,7 @@ async def process_dm_modmail(self, message: discord.Message) -> None: text=self.config["disabled_current_thread_footer"], icon_url=self.guild.icon_url, ) - logger.info( - "A message was blocked from %s due to disabled Modmail.", message.author - ) + logger.info("A message was blocked from %s due to disabled Modmail.", message.author) await self.add_reaction(message, blocked_emoji) return await message.channel.send(embed=embed) @@ -1025,15 +1002,11 @@ async def trigger_auto_triggers(self, message, channel, *, cls=commands.Context) invoker = None if self.config.get("use_regex_autotrigger"): - trigger = next( - filter(lambda x: re.search(x, message.content), self.auto_triggers.keys()) - ) + trigger = next(filter(lambda x: re.search(x, message.content), self.auto_triggers.keys())) if trigger: invoker = re.search(trigger, message.content).group(0) else: - trigger = next( - filter(lambda x: x.lower() in message.content.lower(), self.auto_triggers.keys()) - ) + trigger = next(filter(lambda x: x.lower() in message.content.lower(), self.auto_triggers.keys())) if trigger: invoker = trigger.lower() @@ -1169,9 +1142,7 @@ async def process_commands(self, message): ctxs = await self.get_contexts(message) for ctx in ctxs: if ctx.command: - if not any( - 1 for check in ctx.command.checks if hasattr(check, "permission_level") - ): + if not any(1 for check in ctx.command.checks if hasattr(check, "permission_level")): logger.debug( "Command %s has no permissions check, adding invalid level.", ctx.command.qualified_name, @@ -1199,9 +1170,7 @@ async def process_commands(self, message): else: await self.api.append_log(message, type_="internal") elif ctx.invoked_with: - exc = commands.CommandNotFound( - 'Command "{}" is not found'.format(ctx.invoked_with) - ) + exc = commands.CommandNotFound('Command "{}" is not found'.format(ctx.invoked_with)) self.dispatch("command_error", ctx, exc) async def on_typing(self, channel, user, _): @@ -1275,9 +1244,7 @@ async def handle_reaction_events(self, payload): if not thread.recipient.dm_channel: await thread.recipient.create_dm() try: - linked_message = await thread.find_linked_message_from_dm( - message, either_direction=True - ) + linked_message = await thread.find_linked_message_from_dm(message, either_direction=True) except ValueError as e: logger.warning("Failed to find linked message for reactions: %s", e) return @@ -1286,9 +1253,7 @@ async def handle_reaction_events(self, payload): if not thread: return try: - _, linked_message = await thread.find_linked_messages( - message.id, either_direction=True - ) + _, linked_message = await thread.find_linked_messages(message.id, either_direction=True) except ValueError as e: logger.warning("Failed to find linked message for reactions: %s", e) return @@ -1345,9 +1310,7 @@ async def on_raw_reaction_add(self, payload): ctx = await self.get_context(message) ctx.author = member - await ctx.invoke( - self.get_command("contact"), user=member, manual_trigger=False - ) + await ctx.invoke(self.get_command("contact"), user=member, manual_trigger=False) async def on_raw_reaction_remove(self, payload): if self.config["transfer_reactions"]: @@ -1373,9 +1336,7 @@ async def on_guild_channel_delete(self, channel): await self.config.update() return - audit_logs = self.modmail_guild.audit_logs( - limit=10, action=discord.AuditLogAction.channel_delete - ) + audit_logs = self.modmail_guild.audit_logs(limit=10, action=discord.AuditLogAction.channel_delete) entry = await audit_logs.find(lambda a: int(a.target.id) == channel.id) if entry is None: @@ -1413,9 +1374,7 @@ async def on_member_join(self, member): return thread = await self.threads.find(recipient=member) if thread: - embed = discord.Embed( - description="The recipient has joined the server.", color=self.mod_color - ) + embed = discord.Embed(description="The recipient has joined the server.", color=self.mod_color) await thread.channel.send(embed=embed) async def on_message_delete(self, message): @@ -1448,9 +1407,7 @@ async def on_message_delete(self, message): if not thread: return - audit_logs = self.modmail_guild.audit_logs( - limit=10, action=discord.AuditLogAction.message_delete - ) + audit_logs = self.modmail_guild.audit_logs(limit=10, action=discord.AuditLogAction.message_delete) entry = await audit_logs.find(lambda a: a.target == self.user) @@ -1459,15 +1416,11 @@ async def on_message_delete(self, message): try: await thread.delete_message(message, note=False) - embed = discord.Embed( - description="Successfully deleted message.", color=self.main_color - ) + embed = discord.Embed(description="Successfully deleted message.", color=self.main_color) except ValueError as e: if str(e) not in {"DM message not found.", "Malformed thread message."}: logger.debug("Failed to find linked message to delete: %s", e) - embed = discord.Embed( - description="Failed to delete message.", color=self.error_color - ) + embed = discord.Embed(description="Failed to delete message.", color=self.error_color) else: return except discord.NotFound: @@ -1495,9 +1448,7 @@ async def on_message_edit(self, before, after): _, blocked_emoji = await self.retrieve_emoji() await self.add_reaction(after, blocked_emoji) else: - embed = discord.Embed( - description="Successfully Edited Message", color=self.main_color - ) + embed = discord.Embed(description="Successfully Edited Message", color=self.main_color) embed.set_footer(text=f"Message ID: {after.id}") await after.channel.send(embed=embed) @@ -1508,9 +1459,7 @@ async def on_error(self, event_method, *args, **kwargs): async def on_command_error(self, context, exception): if isinstance(exception, commands.BadArgument): await context.trigger_typing() - await context.send( - embed=discord.Embed(color=self.error_color, description=str(exception)) - ) + await context.send(embed=discord.Embed(color=self.error_color, description=str(exception))) elif isinstance(exception, commands.CommandNotFound): logger.warning("CommandNotFound: %s", exception) elif isinstance(exception, commands.MissingRequiredArgument): @@ -1531,9 +1480,7 @@ async def on_command_error(self, context, exception): embed=discord.Embed(color=self.error_color, description=check.fail_msg) ) if hasattr(check, "permission_level"): - corrected_permission_level = self.command_perm( - context.command.qualified_name - ) + corrected_permission_level = self.command_perm(context.command.qualified_name) logger.warning( "User %s does not have permission to use this command: `%s` (%s).", context.author.name, @@ -1542,9 +1489,7 @@ async def on_command_error(self, context, exception): ) logger.warning("CheckFailure: %s", exception) elif isinstance(exception, commands.DisabledCommand): - logger.info( - "DisabledCommand: %s is trying to run eval but it's disabled", context.author.name - ) + logger.info("DisabledCommand: %s is trying to run eval but it's disabled", context.author.name) else: logger.error("Unexpected exception:", exc_info=exception) @@ -1568,9 +1513,7 @@ async def post_metadata(self): if info.team is not None: data.update( { - "owner_name": info.team.owner.name - if info.team.owner is not None - else "No Owner", + "owner_name": info.team.owner.name if info.team.owner is not None else "No Owner", "owner_id": info.team.owner_id, "team": True, } @@ -1632,7 +1575,11 @@ async def autoupdate(self): pass command = "git pull" - proc = await asyncio.create_subprocess_shell(command, stderr=PIPE, stdout=PIPE,) + proc = await asyncio.create_subprocess_shell( + command, + stderr=PIPE, + stdout=PIPE, + ) err = await proc.stderr.read() err = err.decode("utf-8").rstrip() res = await proc.stdout.read() @@ -1648,9 +1595,7 @@ async def autoupdate(self): channel = self.update_channel if self.hosting_method in (HostingMethod.PM2, HostingMethod.SYSTEMD): embed = discord.Embed(title="Bot has been updated", color=self.main_color) - embed.set_footer( - text=f"Updating Modmail v{self.version} " f"-> v{latest.version}" - ) + embed.set_footer(text=f"Updating Modmail v{self.version} " f"-> v{latest.version}") if self.config["update_notifications"]: await channel.send(embed=embed) else: @@ -1659,9 +1604,7 @@ async def autoupdate(self): description="If you do not have an auto-restart setup, please manually start the bot.", color=self.main_color, ) - embed.set_footer( - text=f"Updating Modmail v{self.version} " f"-> v{latest.version}" - ) + embed.set_footer(text=f"Updating Modmail v{self.version} " f"-> v{latest.version}") if self.config["update_notifications"]: await channel.send(embed=embed) await self.logout() diff --git a/cogs/modmail.py b/cogs/modmail.py index 87b9d91e57..5b3f67bdd2 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -42,9 +42,7 @@ async def setup(self, ctx): """ if ctx.guild != self.bot.modmail_guild: - return await ctx.send( - f"You can only setup in the Modmail guild: {self.bot.modmail_guild}." - ) + return await ctx.send(f"You can only setup in the Modmail guild: {self.bot.modmail_guild}.") if self.bot.main_category is not None: logger.debug("Can't re-setup server, main_category is found.") @@ -79,15 +77,11 @@ async def setup(self, ctx): logger.info("Granting %s access to Modmail category.", key.name) overwrites[key] = discord.PermissionOverwrite(read_messages=True) - category = await self.bot.modmail_guild.create_category( - name="Modmail", overwrites=overwrites - ) + category = await self.bot.modmail_guild.create_category(name="Modmail", overwrites=overwrites) await category.edit(position=0) - log_channel = await self.bot.modmail_guild.create_text_channel( - name="bot-logs", category=category - ) + log_channel = await self.bot.modmail_guild.create_text_channel(name="bot-logs", category=category) embed = discord.Embed( title="Friendly Reminder", @@ -434,9 +428,7 @@ def parse_user_or_role(ctx, user_or_role): @commands.command(aliases=["alert"]) @checks.has_permissions(PermissionLevel.SUPPORTER) @checks.thread_only() - async def notify( - self, ctx, *, user_or_role: Union[discord.Role, User, str.lower, None] = None - ): + async def notify(self, ctx, *, user_or_role: Union[discord.Role, User, str.lower, None] = None): """ Notify a user or role when the next thread message received. @@ -474,9 +466,7 @@ async def notify( @commands.command(aliases=["unalert"]) @checks.has_permissions(PermissionLevel.SUPPORTER) @checks.thread_only() - async def unnotify( - self, ctx, *, user_or_role: Union[discord.Role, User, str.lower, None] = None - ): + async def unnotify(self, ctx, *, user_or_role: Union[discord.Role, User, str.lower, None] = None): """ Un-notify a user, role, or yourself from a thread. @@ -511,9 +501,7 @@ async def unnotify( @commands.command(aliases=["sub"]) @checks.has_permissions(PermissionLevel.SUPPORTER) @checks.thread_only() - async def subscribe( - self, ctx, *, user_or_role: Union[discord.Role, User, str.lower, None] = None - ): + async def subscribe(self, ctx, *, user_or_role: Union[discord.Role, User, str.lower, None] = None): """ Notify a user, role, or yourself for every thread message received. @@ -551,9 +539,7 @@ async def subscribe( @commands.command(aliases=["unsub"]) @checks.has_permissions(PermissionLevel.SUPPORTER) @checks.thread_only() - async def unsubscribe( - self, ctx, *, user_or_role: Union[discord.Role, User, str.lower, None] = None - ): + async def unsubscribe(self, ctx, *, user_or_role: Union[discord.Role, User, str.lower, None] = None): """ Unsubscribe a user, role, or yourself from a thread. @@ -638,7 +624,9 @@ def format_log_embeds(self, logs, avatar_url): prefix = self.bot.config["log_url_prefix"].strip("/") if prefix == "NONE": prefix = "" - log_url = f"{self.bot.config['log_url'].strip('/')}{'/' + prefix if prefix else ''}/{entry['key']}" + log_url = ( + f"{self.bot.config['log_url'].strip('/')}{'/' + prefix if prefix else ''}/{entry['key']}" + ) username = entry["recipient"]["name"] + "#" username += entry["recipient"]["discriminator"] @@ -946,9 +934,7 @@ async def note_persistent(self, ctx, *, msg: str = ""): async with ctx.typing(): msg = await ctx.thread.note(ctx.message, persistent=True) await msg.pin() - await self.bot.api.create_note( - recipient=ctx.thread.recipient, message=ctx.message, message_id=msg.id - ) + await self.bot.api.create_note(recipient=ctx.thread.recipient, message=ctx.message, message_id=msg.id) @commands.command() @checks.has_permissions(PermissionLevel.SUPPORTER) @@ -1011,17 +997,14 @@ async def contact( category = None if user.bot: - embed = discord.Embed( - color=self.bot.error_color, description="Cannot start a thread with a bot." - ) + embed = discord.Embed(color=self.bot.error_color, description="Cannot start a thread with a bot.") return await ctx.send(embed=embed, delete_after=3) exists = await self.bot.threads.find(recipient=user) if exists: embed = discord.Embed( color=self.bot.error_color, - description="A thread for this user already " - f"exists in {exists.channel.mention}.", + description="A thread for this user already " f"exists in {exists.channel.mention}.", ) await ctx.channel.send(embed=embed, delete_after=3) @@ -1045,7 +1028,9 @@ async def contact( description = f"{ctx.author.name} has opened a Modmail thread." em = discord.Embed( - title="New Thread", description=description, color=self.bot.main_color, + title="New Thread", + description=description, + color=self.bot.main_color, ) if self.bot.config["show_timestamp"]: em.timestamp = datetime.utcnow() @@ -1153,9 +1138,7 @@ async def blocked(self, ctx): else: embeds[0].description = "Currently there are no blocked users." - embeds.append( - discord.Embed(title="Blocked Roles", color=self.bot.main_color, description="") - ) + embeds.append(discord.Embed(title="Blocked Roles", color=self.bot.main_color, description="")) if roles: embed = embeds[-1] @@ -1341,10 +1324,7 @@ async def unblock(self, ctx, *, user_or_role: Union[User, Role] = None): mention = getattr(user_or_role, "mention", f"`{user_or_role.id}`") name = getattr(user_or_role, "name", f"`{user_or_role.id}`") - if ( - not isinstance(user_or_role, discord.Role) - and str(user_or_role.id) in self.bot.blocked_users - ): + if not isinstance(user_or_role, discord.Role) and str(user_or_role.id) in self.bot.blocked_users: msg = self.bot.blocked_users.pop(str(user_or_role.id)) or "" await self.bot.config.update() @@ -1369,10 +1349,7 @@ async def unblock(self, ctx, *, user_or_role: Union[User, Role] = None): color=self.bot.main_color, description=f"{mention} is no longer blocked.", ) - elif ( - isinstance(user_or_role, discord.Role) - and str(user_or_role.id) in self.bot.blocked_roles - ): + elif isinstance(user_or_role, discord.Role) and str(user_or_role.id) in self.bot.blocked_roles: msg = self.bot.blocked_roles.pop(str(user_or_role.id)) or "" await self.bot.config.update() @@ -1465,12 +1442,8 @@ async def repair(self, ctx): self.bot.threads, recipient, ctx.channel ) thread.ready = True - logger.info( - "Setting current channel's topic to User ID and created new thread." - ) - await ctx.channel.edit( - reason="Fix broken Modmail thread", topic=f"User ID: {user_id}" - ) + logger.info("Setting current channel's topic to User ID and created new thread.") + await ctx.channel.edit(reason="Fix broken Modmail thread", topic=f"User ID: {user_id}") return await self.bot.add_reaction(ctx.message, sent_emoji) else: @@ -1482,8 +1455,7 @@ async def repair(self, ctx): if m is not None: users = set( filter( - lambda member: member.name == m.group(1) - and member.discriminator == m.group(2), + lambda member: member.name == m.group(1) and member.discriminator == m.group(2), ctx.guild.members, ) ) @@ -1508,9 +1480,7 @@ async def repair(self, ctx): except discord.HTTPException: pass if recipient is None: - self.bot.threads.cache[user.id] = thread = Thread( - self.bot.threads, user_id, ctx.channel - ) + self.bot.threads.cache[user.id] = thread = Thread(self.bot.threads, user_id, ctx.channel) else: self.bot.threads.cache[user.id] = thread = Thread( self.bot.threads, recipient, ctx.channel diff --git a/cogs/plugins.py b/cogs/plugins.py index 94a3f425c0..aab5dc8bd6 100644 --- a/cogs/plugins.py +++ b/cogs/plugins.py @@ -251,12 +251,8 @@ async def load_plugin(self, plugin): if stderr: logger.debug("[stderr]\n%s.", stderr.decode()) - logger.error( - "Failed to download requirements for %s.", plugin.ext_string, exc_info=True - ) - raise InvalidPluginError( - f"Unable to download requirements: ```\n{stderr.decode()}\n```" - ) + logger.error("Failed to download requirements for %s.", plugin.ext_string, exc_info=True) + raise InvalidPluginError(f"Unable to download requirements: ```\n{stderr.decode()}\n```") if os.path.exists(USER_SITE): sys.path.insert(0, USER_SITE) @@ -347,9 +343,7 @@ async def plugins_add(self, ctx, *, plugin_name: str): return if str(plugin) in self.bot.config["plugins"]: - embed = discord.Embed( - description="This plugin is already installed.", color=self.bot.error_color - ) + embed = discord.Embed(description="This plugin is already installed.", color=self.bot.error_color) return await ctx.send(embed=embed) if plugin.name in self.bot.cogs: @@ -433,9 +427,7 @@ async def plugins_remove(self, ctx, *, plugin_name: str): return if str(plugin) not in self.bot.config["plugins"]: - embed = discord.Embed( - description="Plugin is not installed.", color=self.bot.error_color - ) + embed = discord.Embed(description="Plugin is not installed.", color=self.bot.error_color) return await ctx.send(embed=embed) if self.bot.config.get("enable_plugins"): @@ -472,9 +464,7 @@ async def update_plugin(self, ctx, plugin_name): return if str(plugin) not in self.bot.config["plugins"]: - embed = discord.Embed( - description="Plugin is not installed.", color=self.bot.error_color - ) + embed = discord.Embed(description="Plugin is not installed.", color=self.bot.error_color) return await ctx.send(embed=embed) async with ctx.typing(): @@ -598,9 +588,7 @@ async def plugins_loaded(self, ctx): embeds = [] for page in pages: - embed = discord.Embed( - title="Loaded plugins:", description=page, color=self.bot.main_color - ) + embed = discord.Embed(title="Loaded plugins:", description=page, color=self.bot.main_color) embeds.append(embed) paginator = EmbedPaginatorSession(ctx, *embeds) await paginator.run() @@ -641,9 +629,7 @@ async def plugins_registry(self, ctx, *, plugin_name: typing.Union[int, str] = N matches = get_close_matches(plugin_name, self.registry.keys()) if matches: - embed.add_field( - name="Perhaps you meant:", value="\n".join(f"`{m}`" for m in matches) - ) + embed.add_field(name="Perhaps you meant:", value="\n".join(f"`{m}`" for m in matches)) return await ctx.send(embed=embed) @@ -661,13 +647,9 @@ async def plugins_registry(self, ctx, *, plugin_name: typing.Union[int, str] = N title=details["repository"], ) - embed.add_field( - name="Installation", value=f"```{self.bot.prefix}plugins add {name}```" - ) + embed.add_field(name="Installation", value=f"```{self.bot.prefix}plugins add {name}```") - embed.set_author( - name=details["title"], icon_url=details.get("icon_url"), url=plugin.link - ) + embed.set_author(name=details["title"], icon_url=details.get("icon_url"), url=plugin.link) if details.get("thumbnail_url"): embed.set_thumbnail(url=details.get("thumbnail_url")) diff --git a/cogs/utility.py b/cogs/utility.py index 7b39631dcd..0e03ab1ba1 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -184,9 +184,7 @@ async def send_error_message(self, error): command = self.context.kwargs.get("command") val = self.context.bot.snippets.get(command) if val is not None: - embed = discord.Embed( - title=f"{command} is a snippet.", color=self.context.bot.main_color - ) + embed = discord.Embed(title=f"{command} is a snippet.", color=self.context.bot.main_color) embed.add_field(name=f"`{command}` will send:", value=val) return await self.get_destination().send(embed=embed) @@ -206,9 +204,7 @@ async def send_error_message(self, error): await self.context.bot.config.update() else: if len(values) == 1: - embed = discord.Embed( - title=f"{command} is an alias.", color=self.context.bot.main_color - ) + embed = discord.Embed(title=f"{command} is an alias.", color=self.context.bot.main_color) embed.add_field(name=f"`{command}` points to:", value=values[0]) else: embed = discord.Embed( @@ -330,12 +326,8 @@ async def about(self, ctx): latest = changelog.latest_version if self.bot.version.is_prerelease: - stable = next( - filter(lambda v: not parse_version(v.version).is_prerelease, changelog.versions) - ) - footer = ( - f"You are on the prerelease version • the latest version is v{stable.version}." - ) + stable = next(filter(lambda v: not parse_version(v.version).is_prerelease, changelog.versions)) + footer = f"You are on the prerelease version • the latest version is v{stable.version}." elif self.bot.version < parse_version(latest.version): footer = f"A newer version is available v{latest.version}." else: @@ -389,9 +381,7 @@ async def debug(self, ctx): log_file_name = self.bot.token.split(".")[0] with open( - os.path.join( - os.path.dirname(os.path.abspath(__file__)), f"../temp/{log_file_name}.log" - ), + os.path.join(os.path.dirname(os.path.abspath(__file__)), f"../temp/{log_file_name}.log"), "r+", ) as f: logs = f.read().strip() @@ -444,9 +434,7 @@ async def debug_hastebin(self, ctx): log_file_name = self.bot.token.split(".")[0] with open( - os.path.join( - os.path.dirname(os.path.abspath(__file__)), f"../temp/{log_file_name}.log" - ), + os.path.join(os.path.dirname(os.path.abspath(__file__)), f"../temp/{log_file_name}.log"), "rb+", ) as f: logs = BytesIO(f.read().strip()) @@ -482,16 +470,12 @@ async def debug_clear(self, ctx): log_file_name = self.bot.token.split(".")[0] with open( - os.path.join( - os.path.dirname(os.path.abspath(__file__)), f"../temp/{log_file_name}.log" - ), + os.path.join(os.path.dirname(os.path.abspath(__file__)), f"../temp/{log_file_name}.log"), "w", ): pass await ctx.send( - embed=discord.Embed( - color=self.bot.main_color, description="Cached logs are now cleared." - ) + embed=discord.Embed(color=self.bot.main_color, description="Cached logs are now cleared.") ) @commands.command(aliases=["presence"]) @@ -536,9 +520,7 @@ async def activity(self, ctx, activity_type: str.lower, *, message: str = ""): except KeyError: raise commands.MissingRequiredArgument(SimpleNamespace(name="activity")) - activity, _ = await self.set_presence( - activity_type=activity_type, activity_message=message - ) + activity, _ = await self.set_presence(activity_type=activity_type, activity_message=message) self.bot.config["activity_type"] = activity.type.value self.bot.config["activity_message"] = activity.name @@ -603,9 +585,7 @@ async def set_presence(self, *, status=None, activity_type=None, activity_messag url = None activity_message = (activity_message or self.bot.config["activity_message"]).strip() if activity_type is not None and not activity_message: - logger.warning( - 'No activity message found whilst activity is provided, defaults to "Modmail".' - ) + logger.warning('No activity message found whilst activity is provided, defaults to "Modmail".') activity_message = "Modmail" if activity_type == ActivityType.listening: @@ -706,12 +686,14 @@ async def mention(self, ctx, *user_or_role: Union[discord.Role, discord.Member, option = user_or_role[0].lower() if option == "disable": embed = discord.Embed( - description=f"Disabled mention on thread creation.", color=self.bot.main_color, + description=f"Disabled mention on thread creation.", + color=self.bot.main_color, ) self.bot.config["mention"] = None else: embed = discord.Embed( - description="`mention` is reset to default.", color=self.bot.main_color, + description="`mention` is reset to default.", + color=self.bot.main_color, ) self.bot.config.remove("mention") await self.bot.config.update() @@ -747,9 +729,7 @@ async def prefix(self, ctx, *, prefix=None): """ current = self.bot.prefix - embed = discord.Embed( - title="Current prefix", color=self.bot.main_color, description=f"{current}" - ) + embed = discord.Embed(title="Current prefix", color=self.bot.main_color, description=f"{current}") if prefix is None: await ctx.send(embed=embed) @@ -786,9 +766,7 @@ async def config_options(self, ctx): """Return a list of valid configuration names you can change.""" embeds = [] for names in zip_longest(*(iter(sorted(self.bot.config.public_keys)),) * 15): - description = "\n".join( - f"`{name}`" for name in takewhile(lambda x: x is not None, names) - ) + description = "\n".join(f"`{name}`" for name in takewhile(lambda x: x is not None, names)) embed = discord.Embed( title="Available configuration keys:", color=self.bot.main_color, @@ -906,9 +884,7 @@ async def config_help(self, ctx, key: str.lower = None): description=f"`{key}` is an invalid key.", ) if closest: - embed.add_field( - name=f"Perhaps you meant:", value="\n".join(f"`{x}`" for x in closest) - ) + embed.add_field(name=f"Perhaps you meant:", value="\n".join(f"`{x}`" for x in closest)) return await ctx.send(embed=embed) config_help = self.bot.config.config_help @@ -1401,9 +1377,7 @@ async def permissions_remove( Do not ping `@everyone` for granting permission to everyone, use "everyone" or "all" instead. """ - if type_ not in {"command", "level", "override"} or ( - type_ != "override" and user_or_role is None - ): + if type_ not in {"command", "level", "override"} or (type_ != "override" and user_or_role is None): return await ctx.send_help(ctx.command) if type_ == "override": @@ -1566,15 +1540,9 @@ async def permissions_get( levels.append(level.name) mention = getattr(user_or_role, "name", getattr(user_or_role, "id", user_or_role)) - desc_cmd = ( - ", ".join(map(lambda x: f"`{x}`", cmds)) - if cmds - else "No permission entries found." - ) + desc_cmd = ", ".join(map(lambda x: f"`{x}`", cmds)) if cmds else "No permission entries found." desc_level = ( - ", ".join(map(lambda x: f"`{x}`", levels)) - if levels - else "No permission entries found." + ", ".join(map(lambda x: f"`{x}`", levels)) if levels else "No permission entries found." ) embeds = [ @@ -1599,9 +1567,7 @@ async def permissions_get( for command in self.bot.walk_commands(): if command not in done: done.add(command) - level = self.bot.config["override_command_level"].get( - command.qualified_name - ) + level = self.bot.config["override_command_level"].get(command.qualified_name) if level is not None: overrides[command.qualified_name] = level @@ -1620,12 +1586,8 @@ async def permissions_get( ": ".join((f"`{name}`", level)) for name, level in takewhile(lambda x: x is not None, items) ) - embed = discord.Embed( - color=self.bot.main_color, description=description - ) - embed.set_author( - name="Permission Overrides", icon_url=ctx.guild.icon_url - ) + embed = discord.Embed(color=self.bot.main_color, description=description) + embed.set_author(name="Permission Overrides", icon_url=ctx.guild.icon_url) embeds.append(embed) session = EmbedPaginatorSession(ctx, *embeds) @@ -1727,9 +1689,7 @@ async def oauth_whitelist(self, ctx, target: Union[discord.Role, utils.User]): if not hasattr(target, "mention"): target = self.bot.get_user(target.id) or self.bot.modmail_guild.get_role(target.id) - embed.description = ( - f"{'Un-w' if removed else 'W'}hitelisted {target.mention} to view logs." - ) + embed.description = f"{'Un-w' if removed else 'W'}hitelisted {target.mention} to view logs." await ctx.send(embed=embed) @@ -1807,9 +1767,7 @@ async def autotrigger_add(self, ctx, keyword, *, command): async def autotrigger_edit(self, ctx, keyword, *, command): """Edits a pre-existing trigger to automatically trigger an alias-like command""" if keyword not in self.bot.auto_triggers: - embed = utils.create_not_found_embed( - keyword, self.bot.auto_triggers.keys(), "Autotrigger" - ) + embed = utils.create_not_found_embed(keyword, self.bot.auto_triggers.keys(), "Autotrigger") else: # command validation valid = False @@ -1895,7 +1853,11 @@ async def autotrigger_list(self, ctx): embeds = [] for keyword in self.bot.auto_triggers: command = self.bot.auto_triggers[keyword] - embed = discord.Embed(title=keyword, color=self.bot.main_color, description=command,) + embed = discord.Embed( + title=keyword, + color=self.bot.main_color, + description=command, + ) embeds.append(embed) if not embeds: @@ -1918,17 +1880,13 @@ async def github(self, ctx): data = await self.bot.api.get_user_info() if data: - embed = discord.Embed( - title="GitHub", description="Current User", color=self.bot.main_color - ) + embed = discord.Embed(title="GitHub", description="Current User", color=self.bot.main_color) user = data["user"] embed.set_author(name=user["username"], icon_url=user["avatar_url"], url=user["url"]) embed.set_thumbnail(url=user["avatar_url"]) await ctx.send(embed=embed) else: - await ctx.send( - embed=discord.Embed(title="Invalid Github Token", color=self.bot.error_color) - ) + await ctx.send(embed=discord.Embed(title="Invalid Github Token", color=self.bot.error_color)) @commands.command() @checks.has_permissions(PermissionLevel.OWNER) @@ -1950,16 +1908,12 @@ async def update(self, ctx, *, flag: str = ""): ) if self.bot.version >= parse_version(latest.version) and flag.lower() != "force": - embed = discord.Embed( - title="Already up to date", description=desc, color=self.bot.main_color - ) + embed = discord.Embed(title="Already up to date", description=desc, color=self.bot.main_color) data = await self.bot.api.get_user_info() if data: user = data["user"] - embed.set_author( - name=user["username"], icon_url=user["avatar_url"], url=user["url"] - ) + embed.set_author(name=user["username"], icon_url=user["avatar_url"], url=user["url"]) await ctx.send(embed=embed) else: if self.bot.hosting_method == HostingMethod.HEROKU: @@ -1971,9 +1925,7 @@ async def update(self, ctx, *, flag: str = ""): if commit_data and commit_data.get("html_url"): embed = discord.Embed(color=self.bot.main_color) - embed.set_footer( - text=f"Updating Modmail v{self.bot.version} " f"-> v{latest.version}" - ) + embed.set_footer(text=f"Updating Modmail v{self.bot.version} " f"-> v{latest.version}") embed.set_author( name=user["username"] + " - Updating bot", @@ -1995,9 +1947,7 @@ async def update(self, ctx, *, flag: str = ""): color=self.bot.main_color, ) embed.set_footer(text="Force update") - embed.set_author( - name=user["username"], icon_url=user["avatar_url"], url=user["url"] - ) + embed.set_author(name=user["username"], icon_url=user["avatar_url"], url=user["url"]) await ctx.send(embed=embed) else: # update fork if gh_token exists @@ -2008,25 +1958,28 @@ async def update(self, ctx, *, flag: str = ""): command = "git pull" - proc = await asyncio.create_subprocess_shell(command, stderr=PIPE, stdout=PIPE,) + proc = await asyncio.create_subprocess_shell( + command, + stderr=PIPE, + stdout=PIPE, + ) err = await proc.stderr.read() err = err.decode("utf-8").rstrip() res = await proc.stdout.read() res = res.decode("utf-8").rstrip() if err and not res: - embed = discord.Embed( - title="Update failed", description=err, color=self.bot.error_color - ) + embed = discord.Embed(title="Update failed", description=err, color=self.bot.error_color) await ctx.send(embed=embed) elif res != "Already up to date.": logger.info("Bot has been updated.") - embed = discord.Embed(title="Bot has been updated", color=self.bot.main_color,) - embed.set_footer( - text=f"Updating Modmail v{self.bot.version} " f"-> v{latest.version}" + embed = discord.Embed( + title="Bot has been updated", + color=self.bot.main_color, ) + embed.set_footer(text=f"Updating Modmail v{self.bot.version} " f"-> v{latest.version}") embed.description = latest.description for name, value in latest.fields.items(): embed.add_field(name=name, value=truncate(value, 200)) @@ -2040,7 +1993,9 @@ async def update(self, ctx, *, flag: str = ""): await self.bot.logout() else: embed = discord.Embed( - title="Already up to date", description=desc, color=self.bot.main_color, + title="Already up to date", + description=desc, + color=self.bot.main_color, ) embed.set_footer(text="Force update") await ctx.send(embed=embed) diff --git a/core/changelog.py b/core/changelog.py index 878a19b26c..7c9af2e1bb 100644 --- a/core/changelog.py +++ b/core/changelog.py @@ -65,9 +65,7 @@ def parse(self) -> None: Parse the lines and split them into `description` and `fields`. """ self.description = re.match(self.DESCRIPTION_REGEX, self.lines, re.DOTALL) - self.description = ( - self.description.group(1).strip() if self.description is not None else "" - ) + self.description = self.description.group(1).strip() if self.description is not None else "" matches = re.finditer(self.ACTION_REGEX, self.lines, re.DOTALL) for m in matches: @@ -91,7 +89,9 @@ def embed(self) -> Embed: """ embed = Embed(color=self.bot.main_color, description=self.description) embed.set_author( - name=f"v{self.version} - Changelog", icon_url=self.bot.user.avatar_url, url=self.url, + name=f"v{self.version} - Changelog", + icon_url=self.bot.user.avatar_url, + url=self.url, ) for name, value in self.fields.items(): @@ -171,7 +171,9 @@ async def from_url(cls, bot, url: str = "") -> "Changelog": """ # get branch via git cli if available proc = await asyncio.create_subprocess_shell( - "git branch --show-current", stderr=PIPE, stdout=PIPE, + "git branch --show-current", + stderr=PIPE, + stdout=PIPE, ) err = await proc.stderr.read() err = err.decode("utf-8").rstrip() diff --git a/core/checks.py b/core/checks.py index 74b7dc38cd..1079b55530 100644 --- a/core/checks.py +++ b/core/checks.py @@ -5,7 +5,9 @@ logger = getLogger(__name__) -def has_permissions_predicate(permission_level: PermissionLevel = PermissionLevel.REGULAR,): +def has_permissions_predicate( + permission_level: PermissionLevel = PermissionLevel.REGULAR, +): async def predicate(ctx): return await check_permissions(ctx, ctx.command.qualified_name) diff --git a/core/clients.py b/core/clients.py index db4e9936b4..1d90cbb0e3 100644 --- a/core/clients.py +++ b/core/clients.py @@ -305,9 +305,7 @@ async def get_log(self, channel_id: Union[str, int]) -> dict: async def get_log_link(self, channel_id: Union[str, int]) -> str: return NotImplemented - async def create_log_entry( - self, recipient: Member, channel: TextChannel, creator: Member - ) -> str: + async def create_log_entry(self, recipient: Member, channel: TextChannel, creator: Member) -> str: return NotImplemented async def delete_log_entry(self, key: str) -> bool: @@ -479,13 +477,9 @@ async def get_log_link(self, channel_id: Union[str, int]) -> str: prefix = self.bot.config["log_url_prefix"].strip("/") if prefix == "NONE": prefix = "" - return ( - f"{self.bot.config['log_url'].strip('/')}{'/' + prefix if prefix else ''}/{doc['key']}" - ) + return f"{self.bot.config['log_url'].strip('/')}{'/' + prefix if prefix else ''}/{doc['key']}" - async def create_log_entry( - self, recipient: Member, channel: TextChannel, creator: Member - ) -> str: + async def create_log_entry(self, recipient: Member, channel: TextChannel, creator: Member) -> str: key = secrets.token_hex(6) await self.logs.insert_one( @@ -536,9 +530,7 @@ async def get_config(self) -> dict: async def update_config(self, data: dict): toset = self.bot.config.filter_valid(data) - unset = self.bot.config.filter_valid( - {k: 1 for k in self.bot.config.all_keys if k not in data} - ) + unset = self.bot.config.filter_valid({k: 1 for k in self.bot.config.all_keys if k not in data}) if toset and unset: return await self.db.config.update_one( @@ -635,17 +627,13 @@ async def find_notes(self, recipient: Member): async def update_note_ids(self, ids: dict): for object_id, message_id in ids.items(): - await self.db.notes.update_one( - {"_id": object_id}, {"$set": {"message_id": message_id}} - ) + await self.db.notes.update_one({"_id": object_id}, {"$set": {"message_id": message_id}}) async def delete_note(self, message_id: Union[int, str]): await self.db.notes.delete_one({"message_id": str(message_id)}) async def edit_note(self, message_id: Union[int, str], message: str): - await self.db.notes.update_one( - {"message_id": str(message_id)}, {"$set": {"message": message}} - ) + await self.db.notes.update_one({"message_id": str(message_id)}, {"$set": {"message": message}}) def get_plugin_partition(self, cog): cls_name = cog.__class__.__name__ @@ -656,7 +644,11 @@ async def update_repository(self) -> dict: data = await user.update_repository() return { "data": data, - "user": {"username": user.username, "avatar_url": user.avatar_url, "url": user.url,}, + "user": { + "username": user.username, + "avatar_url": user.avatar_url, + "url": user.url, + }, } async def get_user_info(self) -> dict: diff --git a/core/config.py b/core/config.py index 30f364622d..255c3667de 100644 --- a/core/config.py +++ b/core/config.py @@ -210,28 +210,18 @@ def populate_cache(self) -> dict: # populate from env var and .env file data.update({k.lower(): v for k, v in os.environ.items() if k.lower() in self.all_keys}) - config_json = os.path.join( - os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "config.json" - ) + config_json = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "config.json") if os.path.exists(config_json): logger.debug("Loading envs from config.json.") with open(config_json, "r", encoding="utf-8") as f: # Config json should override env vars try: - data.update( - { - k.lower(): v - for k, v in json.load(f).items() - if k.lower() in self.all_keys - } - ) + data.update({k.lower(): v for k, v in json.load(f).items() if k.lower() in self.all_keys}) except json.JSONDecodeError: logger.critical("Failed to load config.json env values.", exc_info=True) self._cache = data - config_help_json = os.path.join( - os.path.dirname(os.path.abspath(__file__)), "config_help.json" - ) + config_help_json = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config_help.json") with open(config_help_json, "r", encoding="utf-8") as f: self.config_help = dict(sorted(json.load(f).items())) diff --git a/core/models.py b/core/models.py index 0238e051d4..d17e118e6c 100644 --- a/core/models.py +++ b/core/models.py @@ -203,9 +203,7 @@ async def convert(self, ctx, argument): except commands.ChannelNotFound: def check(c): - return isinstance(c, discord.CategoryChannel) and c.name.lower().startswith( - argument.lower() - ) + return isinstance(c, discord.CategoryChannel) and c.name.lower().startswith(argument.lower()) if guild: result = discord.utils.find(check, guild.categories) diff --git a/core/thread.py b/core/thread.py index 530e063e54..41d2cd96ba 100644 --- a/core/thread.py +++ b/core/thread.py @@ -109,9 +109,7 @@ async def setup(self, *, creator=None, category=None, initial_message=None): recipient = self.recipient # in case it creates a channel outside of category - overwrites = { - self.bot.modmail_guild.default_role: discord.PermissionOverwrite(read_messages=False) - } + overwrites = {self.bot.modmail_guild.default_role: discord.PermissionOverwrite(read_messages=False)} category = category or self.bot.main_category @@ -170,9 +168,7 @@ async def setup(self, *, creator=None, category=None, initial_message=None): mention = self.bot.config["mention"] async def send_genesis_message(): - info_embed = self._format_info_embed( - recipient, log_url, log_count, self.bot.main_color - ) + info_embed = self._format_info_embed(recipient, log_url, log_count, self.bot.main_color) try: msg = await channel.send(mention, embed=info_embed) self.bot.loop.create_task(msg.pin()) @@ -238,9 +234,7 @@ class Author: "author": Author(), } message = discord.Message(state=State(), channel=None, data=data) - ids[note["_id"]] = str( - (await self.note(message, persistent=True, thread_creation=True)).id - ) + ids[note["_id"]] = str((await self.note(message, persistent=True, thread_creation=True)).id) await self.bot.api.update_note_ids(ids) @@ -330,16 +324,12 @@ def _format_info_embed(self, user, log_url, log_count, color): mutual_guilds = [g for g in self.bot.guilds if user in g.members] if member is None or len(mutual_guilds) > 1: - embed.add_field( - name="Mutual Server(s)", value=", ".join(g.name for g in mutual_guilds) - ) + embed.add_field(name="Mutual Server(s)", value=", ".join(g.name for g in mutual_guilds)) return embed def _close_after(self, closer, silent, delete_channel, message): - return self.bot.loop.create_task( - self._close(closer, silent, delete_channel, message, True) - ) + return self.bot.loop.create_task(self._close(closer, silent, delete_channel, message, True)) async def close( self, @@ -372,9 +362,7 @@ async def close( self.bot.config["closures"][str(self.id)] = items await self.bot.config.update() - task = self.bot.loop.call_later( - after, self._close_after, closer, silent, delete_channel, message - ) + task = self.bot.loop.call_later(after, self._close_after, closer, silent, delete_channel, message) if auto_close: self.auto_close_task = task @@ -383,9 +371,7 @@ async def close( else: await self._close(closer, silent, delete_channel, message) - async def _close( - self, closer, silent=False, delete_channel=True, message=None, scheduled=False - ): + async def _close(self, closer, silent=False, delete_channel=True, message=None, scheduled=False): try: self.manager.cache.pop(self.id) except KeyError as e: @@ -425,7 +411,9 @@ async def _close( prefix = self.bot.config["log_url_prefix"].strip("/") if prefix == "NONE": prefix = "" - log_url = f"{self.bot.config['log_url'].strip('/')}{'/' + prefix if prefix else ''}/{log_data['key']}" + log_url = ( + f"{self.bot.config['log_url'].strip('/')}{'/' + prefix if prefix else ''}/{log_data['key']}" + ) if log_data["title"]: sneak_peak = log_data["title"] @@ -473,7 +461,8 @@ async def _close( # Thread closed message embed = discord.Embed( - title=self.bot.config["thread_close_title"], color=self.bot.error_color, + title=self.bot.config["thread_close_title"], + color=self.bot.error_color, ) if self.bot.config["show_timestamp"]: embed.timestamp = datetime.utcnow() @@ -531,9 +520,7 @@ async def _restart_close_timer(self): human_time = human_timedelta(dt=reset_time) if self.bot.config.get("thread_auto_close_silently"): - return await self.close( - closer=self.bot.user, silent=True, after=int(seconds), auto_close=True - ) + return await self.close(closer=self.bot.user, silent=True, after=int(seconds), auto_close=True) # Grab message close_message = self.bot.formatter.format( @@ -549,9 +536,7 @@ async def _restart_close_timer(self): time_marker_regex, ) - await self.close( - closer=self.bot.user, after=int(seconds), message=close_message, auto_close=True - ) + await self.close(closer=self.bot.user, after=int(seconds), message=close_message, auto_close=True) async def find_linked_messages( self, @@ -561,11 +546,7 @@ async def find_linked_messages( note: bool = True, ) -> typing.Tuple[discord.Message, typing.Optional[discord.Message]]: if message1 is not None: - if ( - not message1.embeds - or not message1.embeds[0].author.url - or message1.author != self.bot.user - ): + if not message1.embeds or not message1.embeds[0].author.url or message1.author != self.bot.user: raise ValueError("Malformed thread message.") elif message_id is not None: @@ -602,10 +583,7 @@ async def find_linked_messages( and message1.embeds[0].color and ( message1.embeds[0].color.value == self.bot.mod_color - or ( - either_direction - and message1.embeds[0].color.value == self.bot.recipient_color - ) + or (either_direction and message1.embeds[0].color.value == self.bot.recipient_color) ) and message1.embeds[0].author.url.split("#")[-1].isdigit() and message1.author == self.bot.user @@ -710,13 +688,9 @@ async def edit_dm_message(self, message: discord.Message, content: str) -> None: embed = linked_message.embeds[0] embed.add_field(name="**Edited, former message:**", value=embed.description) embed.description = content - await asyncio.gather( - self.bot.api.edit_message(message.id, content), linked_message.edit(embed=embed) - ) + await asyncio.gather(self.bot.api.edit_message(message.id, content), linked_message.edit(embed=embed)) - async def note( - self, message: discord.Message, persistent=False, thread_creation=False - ) -> None: + async def note(self, message: discord.Message, persistent=False, thread_creation=False) -> None: if not message.content and not message.attachments: raise MissingRequiredArgument(SimpleNamespace(name="msg")) @@ -729,16 +703,12 @@ async def note( ) self.bot.loop.create_task( - self.bot.api.append_log( - message, message_id=msg.id, channel_id=self.channel.id, type_="system" - ) + self.bot.api.append_log(message, message_id=msg.id, channel_id=self.channel.id, type_="system") ) return msg - async def reply( - self, message: discord.Message, anonymous: bool = False, plain: bool = False - ) -> None: + async def reply(self, message: discord.Message, anonymous: bool = False, plain: bool = False) -> None: if not message.content and not message.attachments: raise MissingRequiredArgument(SimpleNamespace(name="msg")) if not any(g.get_member(self.id) for g in self.bot.guilds): @@ -777,7 +747,10 @@ async def reply( ) tasks.append( message.channel.send( - embed=discord.Embed(color=self.bot.error_color, description=description,) + embed=discord.Embed( + color=self.bot.error_color, + description=description, + ) ) ) else: @@ -825,9 +798,7 @@ async def send( thread_creation: bool = False, ) -> None: - self.bot.loop.create_task( - self._restart_close_timer() - ) # Start or restart thread auto close + self.bot.loop.create_task(self._restart_close_timer()) # Start or restart thread auto close if self.close_task is not None: # cancel closing if a thread message is sent. @@ -967,9 +938,7 @@ async def send( file_upload_count = 1 for url, filename, _ in attachments: - embed.add_field( - name=f"File upload ({file_upload_count})", value=f"[{filename}]({url})" - ) + embed.add_field(name=f"File upload ({file_upload_count})", value=f"[{filename}]({url})") file_upload_count += 1 if from_mod: @@ -1028,9 +997,7 @@ async def send( files = [] for i in embed.fields: if "Image" in i.name: - async with self.bot.session.get( - i.field[i.field.find("http") : -1] - ) as resp: + async with self.bot.session.get(i.field[i.field.find("http") : -1]) as resp: stream = io.BytesIO(await resp.read()) files.append(discord.File(stream)) @@ -1119,9 +1086,7 @@ async def find( return thread else: if not thread.channel or not self.bot.get_channel(thread.channel.id): - logger.warning( - "Found existing thread for %s but the channel is invalid.", recipient_id - ) + logger.warning("Found existing thread for %s but the channel is invalid.", recipient_id) self.bot.loop.create_task( thread.close(closer=self.bot.user, silent=True, delete_channel=False) ) @@ -1192,9 +1157,7 @@ async def create( if thread.channel and self.bot.get_channel(thread.channel.id): logger.warning("Found an existing thread for %s, abort creating.", recipient) return thread - logger.warning( - "Found an existing thread for %s, closing previous thread.", recipient - ) + logger.warning("Found an existing thread for %s, closing previous thread.", recipient) self.bot.loop.create_task( thread.close(closer=self.bot.user, silent=True, delete_channel=False) ) @@ -1263,15 +1226,11 @@ async def create( await confirm.remove_reaction(accept_emoji, self.bot.user) await asyncio.sleep(0.2) await confirm.remove_reaction(deny_emoji, self.bot.user) - await destination.send( - embed=discord.Embed(title="Cancelled", color=self.bot.error_color) - ) + await destination.send(embed=discord.Embed(title="Cancelled", color=self.bot.error_color)) del self.cache[recipient.id] return thread - self.bot.loop.create_task( - thread.setup(creator=creator, category=category, initial_message=message) - ) + self.bot.loop.create_task(thread.setup(creator=creator, category=category, initial_message=message)) return thread async def find_or_create(self, recipient) -> Thread: diff --git a/core/time.py b/core/time.py index 331e26349f..bdec8d2549 100644 --- a/core/time.py +++ b/core/time.py @@ -57,9 +57,7 @@ def __init__(self, argument): if not status.hasTime: # replace it with the current time - dt = dt.replace( - hour=now.hour, minute=now.minute, second=now.second, microsecond=now.microsecond - ) + dt = dt.replace(hour=now.hour, minute=now.minute, second=now.second, microsecond=now.microsecond) self.dt = dt self._past = dt < now diff --git a/core/utils.py b/core/utils.py index 2dfb319a8e..af47c8e2e6 100644 --- a/core/utils.py +++ b/core/utils.py @@ -355,8 +355,7 @@ def format_channel_name(bot, author, exclude_channel=None, force_null=False): name = "null" name = new_name = ( - "".join(l for l in name if l not in string.punctuation and l.isprintable()) - or "null" + "".join(l for l in name if l not in string.punctuation and l.isprintable()) or "null" ) + f"-{author.discriminator}" counter = 1 diff --git a/pyproject.toml b/pyproject.toml index a920956e85..994cdd7cca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,8 @@ [tool.black] -line-length = 99 -target-version = ['py37'] +line-length = "110" +target-version = ['py39'] include = '\.pyi?$' -exclude = ''' +extend-exclude = ''' ( /( \.eggs @@ -18,3 +18,9 @@ exclude = ''' )/ ) ''' + +[tool.pylint.messages_control] +disable = "C0330, C0326" + +[tool.pylint.format] +max-line-length = "110" From b3a2918c34587f1c45b590402e87f1c5ccde06ad Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Wed, 7 Jul 2021 19:46:36 +0800 Subject: [PATCH 136/209] Remove flake8 from req, use new rec line-width of 110, black format --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0cc483cc3c..39904e9180 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,7 @@ parsedatetime==2.6 pymongo==3.11.4 python-dateutil==2.8.1 python-dotenv==0.18.0 -six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2' +six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' typing-extensions==3.10.0.0 uvloop==0.15.2; sys_platform != 'win32' yarl==1.6.3; python_version >= '3.6' From 77a1ac41bd2799b8e81cd4e4f33fffbfabdabc9b Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Thu, 8 Jul 2021 13:45:26 +0800 Subject: [PATCH 137/209] Add dnspython (pymongo[srv]) as req --- Pipfile | 3 ++- Pipfile.lock | 14 +++++++++++++- requirements.txt | 3 ++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Pipfile b/Pipfile index 813b6700aa..9c7b4e03b7 100644 --- a/Pipfile +++ b/Pipfile @@ -10,13 +10,14 @@ pylint = "~=2.9.3" [packages] aiohttp = "==3.7.4.post0" -colorama = "~=0.4.4" +colorama = "~=0.4.4" # Doesn't officially support Python 3.9 yet, v0.4.5 will support 3.9 "discord.py" = "==1.7.3" emoji = "~=1.2.0" isodate = "~=0.6.0" motor = "~=2.4.0" natural = "~=0.2.0" parsedatetime = "~=2.6" +pymongo = {version = "*", extras = ['srv']} # Required by motor python-dateutil = "~=2.8.1" python-dotenv = "~=0.18.0" uvloop = {version = ">=0.15.2", markers = "sys_platform != 'win32'"} diff --git a/Pipfile.lock b/Pipfile.lock index b10f516efc..15fdcacc5c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "dedff7c4534f29ac1560a57f358852e90d6e6b9d04eb07a35f6daa2d661bc0c2" + "sha256": "0e726213f83b90d7c4e90a04cea6636dbdc5be2ad82049c96820535e5cc3d1ad" }, "pipfile-spec": 6, "requires": { @@ -99,6 +99,14 @@ "index": "pypi", "version": "==1.7.3" }, + "dnspython": { + "hashes": [ + "sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01", + "sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, "emoji": { "hashes": [ "sha256:496f432058567985838c13d67dde84ca081614a8286c0b9cdc7d63dfa89d51a3", @@ -190,6 +198,9 @@ "version": "==2.6" }, "pymongo": { + "extras": [ + "srv" + ], "hashes": [ "sha256:03be7ad107d252bb7325d4af6309fdd2c025d08854d35f0e7abc8bf048f4245e", "sha256:071552b065e809d24c5653fcc14968cfd6fde4e279408640d5ac58e3353a3c5f", @@ -256,6 +267,7 @@ "sha256:fedf0dee7a412ca6d1d6d92c158fe9cbaa8ea0cae90d268f9ccc0744de7a97d0", "sha256:fffff7bfb6799a763d3742c59c6ee7ffadda21abed557637bc44ed1080876484" ], + "index": "pypi", "version": "==3.11.4" }, "python-dateutil": { diff --git a/requirements.txt b/requirements.txt index 39904e9180..d802cc0c46 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,6 +12,7 @@ attrs==21.2.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, chardet==4.0.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' colorama==0.4.4 discord.py==1.7.3 +dnspython==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' emoji==1.2.0 idna==3.2; python_version >= '3.5' isodate==0.6.0 @@ -19,7 +20,7 @@ motor==2.4.0 multidict==5.1.0; python_version >= '3.6' natural==0.2.0 parsedatetime==2.6 -pymongo==3.11.4 +pymongo[srv]==3.11.4 python-dateutil==2.8.1 python-dotenv==0.18.0 six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' From 79acb3509127cad21d98457d313dcecfd6ac5b7d Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Thu, 8 Jul 2021 13:48:33 +0800 Subject: [PATCH 138/209] Updates channel.move, bot.close, emoji changes --- bot.py | 8 ++++---- cogs/modmail.py | 3 ++- cogs/utility.py | 2 +- core/clients.py | 3 +-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bot.py b/bot.py index ce7cfc529d..3cb5bb2d5f 100644 --- a/bot.py +++ b/bot.py @@ -519,7 +519,7 @@ async def on_connect(self): await self.api.validate_database_connection() except Exception: logger.debug("Logging out due to failed database connection.") - return await self.logout() + return await self.close() logger.debug("Connected to gateway.") await self.config.refresh() @@ -534,7 +534,7 @@ async def on_ready(self): if self.guild is None: logger.error("Logging out due to invalid GUILD_ID.") - return await self.logout() + return await self.close() logger.line() logger.debug("Client ready.") @@ -640,7 +640,7 @@ async def convert_emoji(self, name: str) -> str: ctx = SimpleNamespace(bot=self, guild=self.modmail_guild) converter = commands.EmojiConverter() - if name not in UNICODE_EMOJI: + if name not in UNICODE_EMOJI['en']: try: name = await converter.convert(ctx, name.strip(":")) except commands.BadArgument as e: @@ -1607,7 +1607,7 @@ async def autoupdate(self): embed.set_footer(text=f"Updating Modmail v{self.version} " f"-> v{latest.version}") if self.config["update_notifications"]: await channel.send(embed=embed) - await self.logout() + return await self.close() async def before_autoupdate(self): await self.wait_for_connected() diff --git a/cogs/modmail.py b/cogs/modmail.py index 5b3f67bdd2..55110c323e 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -322,7 +322,8 @@ async def move(self, ctx, *, arguments): silent_words = ["silent", "silently"] silent = any(word in silent_words for word in options.split()) - await thread.channel.edit(category=category, sync_permissions=True) + await thread.channel.move(category=category, end=True, sync_permissions=True, + reason=f"{ctx.author} moved this thread.") if self.bot.config["thread_move_notify"] and not silent: embed = discord.Embed( diff --git a/cogs/utility.py b/cogs/utility.py index 0e03ab1ba1..a00027eca3 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -1990,7 +1990,7 @@ async def update(self, ctx, *, flag: str = ""): ) await ctx.send(embed=embed) - await self.bot.logout() + return await self.bot.close() else: embed = discord.Embed( title="Already up to date", diff --git a/core/clients.py b/core/clients.py index 1d90cbb0e3..c8798e5423 100644 --- a/core/clients.py +++ b/core/clients.py @@ -377,9 +377,8 @@ def __init__(self, bot): except ConfigurationError as e: logger.critical( "Your MongoDB CONNECTION_URI might be copied wrong, try re-copying from the source again. " - "Otherwise noted in the following message:" + "Otherwise noted in the following message:\n%s", e ) - logger.critical(e) sys.exit(0) super().__init__(bot, db) From 69d69e63b8f336ce203aaef1854b32fbdbbab527 Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Thu, 8 Jul 2021 13:58:15 +0800 Subject: [PATCH 139/209] Keep more log files --- core/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/models.py b/core/models.py index d17e118e6c..76889809f7 100644 --- a/core/models.py +++ b/core/models.py @@ -127,7 +127,7 @@ def format(self, record): def configure_logging(name, level=None): global ch_debug, log_level - ch_debug = RotatingFileHandler(name, mode="a+", maxBytes=48000, backupCount=1) + ch_debug = RotatingFileHandler(name, mode="a+", maxBytes=78000, backupCount=10) formatter_debug = FileFormatter( "%(asctime)s %(name)s[%(lineno)d] - %(levelname)s: %(message)s", From 703f04f2d63d78facf6fb6808ac05e18b8aaff7c Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Thu, 8 Jul 2021 13:59:33 +0800 Subject: [PATCH 140/209] Bump version --- CHANGELOG.md | 7 +++++++ README.md | 2 +- bot.py | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12f1ddc9ef..fb9ee01487 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. +# v3.9.5dev1 + +## Misc + +- Bumped discord.py to v1.7.3, updated all other packages to latest. +- More debug log files are now kept. + # v3.9.4 ## Fixed diff --git a/README.md b/README.md index f61512d608..ea70af1ad2 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@
- +
diff --git a/bot.py b/bot.py index 3cb5bb2d5f..dab03836a1 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.9.4" +__version__ = "3.9.5dev1" import asyncio From 3ed0eb7f870e9b056ea7901ea68558126b1b784d Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Thu, 8 Jul 2021 14:01:45 +0800 Subject: [PATCH 141/209] Black format --- bot.py | 2 +- cogs/modmail.py | 5 +++-- core/clients.py | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/bot.py b/bot.py index dab03836a1..1deb9b7d2a 100644 --- a/bot.py +++ b/bot.py @@ -640,7 +640,7 @@ async def convert_emoji(self, name: str) -> str: ctx = SimpleNamespace(bot=self, guild=self.modmail_guild) converter = commands.EmojiConverter() - if name not in UNICODE_EMOJI['en']: + if name not in UNICODE_EMOJI["en"]: try: name = await converter.convert(ctx, name.strip(":")) except commands.BadArgument as e: diff --git a/cogs/modmail.py b/cogs/modmail.py index 55110c323e..81fd2a911e 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -322,8 +322,9 @@ async def move(self, ctx, *, arguments): silent_words = ["silent", "silently"] silent = any(word in silent_words for word in options.split()) - await thread.channel.move(category=category, end=True, sync_permissions=True, - reason=f"{ctx.author} moved this thread.") + await thread.channel.move( + category=category, end=True, sync_permissions=True, reason=f"{ctx.author} moved this thread." + ) if self.bot.config["thread_move_notify"] and not silent: embed = discord.Embed( diff --git a/core/clients.py b/core/clients.py index c8798e5423..5c5acc8fad 100644 --- a/core/clients.py +++ b/core/clients.py @@ -377,7 +377,8 @@ def __init__(self, bot): except ConfigurationError as e: logger.critical( "Your MongoDB CONNECTION_URI might be copied wrong, try re-copying from the source again. " - "Otherwise noted in the following message:\n%s", e + "Otherwise noted in the following message:\n%s", + e, ) sys.exit(0) From 2ace13fc959bf6642aedff629a765096107f29c4 Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Thu, 8 Jul 2021 14:28:03 +0800 Subject: [PATCH 142/209] Updated sponsors --- README.md | 7 +++++++ SPONSORS.json | 24 ++++++++++++++++++++++-- cogs/utility.py | 17 ++++++++++++----- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f61512d608..81305c51db 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,13 @@ Real Madrid: +
+
+Discord Advice Center: +
+ + + Become a sponsor on [Patreon](https://patreon.com/kyber). diff --git a/SPONSORS.json b/SPONSORS.json index 1f1ed56d37..049998ca55 100644 --- a/SPONSORS.json +++ b/SPONSORS.json @@ -21,7 +21,7 @@ "value": "[**Click Here**](https://www.youtube.com/channel/UCgSmBJD9imASmJRleycTCwQ?sub_confirmation=1)" }, { - "name": "Discord Server!", + "name": "Discord Server", "value": "[**Click Here**](https://discord.gg/V8ErqHb)" } ] @@ -37,7 +37,7 @@ }, "fields": [ { - "name": "Discord Server!", + "name": "Discord Server", "value": "[**Click here**](https://discord.gg/BanCwptMJV)" } ] @@ -70,5 +70,25 @@ } ] } + }, + { + "embed": { + "description": "──── 《𝐃𝐢𝐬𝐜𝐨𝐫𝐝 𝐀𝐝𝐯𝐢𝐜𝐞 𝐂𝐞𝐧𝐭𝐞𝐫 》 ────\n\n◈ We are a server aimed to meet your discord needs. We have tools, tricks and tips to grow your server and advertise your server. We offer professional server reviews and suggestions how to run it successfully as a part of our courtesy. Join the server and get the chance to add our very own BUMP BOT called DAC Advertise where you can advertise your server to other servers!\n", + "color": 53380, + "author": { + "name": "Discord Advice Center", + "url": "https://discord.gg/nkMDQfuK", + "icon_url": "https://i.imgur.com/cjVtRw5.jpg" + }, + "image": { + "url": "https://i.imgur.com/1hrjcHd.png" + }, + "fields": [ + { + "name": "Discord Server", + "value": "[**Click Here**](https://discord.gg/nkMDQfuK)" + } + ] + } } ] diff --git a/cogs/utility.py b/cogs/utility.py index 7b39631dcd..58b0449d7d 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -356,18 +356,25 @@ async def about(self, ctx): inline=False, ) + embed.add_field( + name="Project Sponsors", + value=f"Checkout the people who supported Modmail with command `{self.bot.prefix}sponsors`!", + inline=False, + ) + embed.set_footer(text=footer) await ctx.send(embed=embed) - @commands.command() + @commands.command(aliases=["sponsor"]) @checks.has_permissions(PermissionLevel.REGULAR) @utils.trigger_typing async def sponsors(self, ctx): - """Shows a list of sponsors.""" - resp = await self.bot.session.get( + """Shows the sponsors of this project.""" + + async with self.bot.session.get( "https://raw.githubusercontent.com/kyb3r/modmail/master/SPONSORS.json" - ) - data = loads(await resp.text()) + ) as resp: + data = loads(await resp.text()) embeds = [] From 1efdc3403a622d70892f30dbffda894228f93ae8 Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Fri, 9 Jul 2021 09:50:47 +0800 Subject: [PATCH 143/209] Improved dockerfile --- .dockerignore | 12 +++--------- Dockerfile | 21 +++++++++++++-------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/.dockerignore b/.dockerignore index 99fd4a241b..74003e7e30 100644 --- a/.dockerignore +++ b/.dockerignore @@ -140,17 +140,11 @@ test.py # Other stuff .env.example .gitignore -.lint.py -.pylintrc -.travis.yml +.github/ app.json CHANGELOG.md -CODE_OF_CONDUCT.md -CONTRIBUTING.md -requirements.min.txt Procfile pyproject.toml README.md -runtime.txt -SPONSORS.json -stack.yml \ No newline at end of file +Pipfile +Pipfile.lock diff --git a/Dockerfile b/Dockerfile index 8165e06a53..232e84fb20 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,16 @@ -FROM python:3.7-alpine +FROM python:3.9-slim as py + +FROM py as build + +RUN apt update && apt install -y g++ +COPY requirements.min.txt / +RUN pip install --prefix=/inst -U -r /requirements.min.txt + +FROM py + ENV USING_DOCKER yes +COPY --from=build /inst /usr/local + WORKDIR /modmailbot +CMD ["python", "bot.py"] COPY . /modmailbot -RUN export PIP_NO_CACHE_DIR=false \ - && apk update \ - && apk add --update --no-cache --virtual .build-deps alpine-sdk \ - && pip install pipenv \ - && pipenv install --deploy --ignore-pipfile \ - && apk del .build-deps -CMD ["pipenv", "run", "bot"] \ No newline at end of file From 2c26227a12712c6b550245b9aa33e36b909c91b0 Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Fri, 9 Jul 2021 10:20:16 +0800 Subject: [PATCH 144/209] Add restart and environment for bot --- docker-compose.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 5d2061df6b..d3745c9b4c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,20 +2,25 @@ version: "3.7" services: bot: image: kyb3rr/modmail + restart: always env_file: - .env + environment: + - CONNECTION_URI=mongodb://mongo depends_on: - mongo logviewer: image: kyb3rr/logviewer + restart: always depends_on: - mongo environment: - MONGO_URI=mongodb://mongo ports: - - 8000:8000 + - 80:8000 mongo: image: mongo + restart: always volumes: - mongodb:/data/db From 05cde283d31af5622da8ded6b81f68620eafda59 Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Fri, 9 Jul 2021 10:26:03 +0800 Subject: [PATCH 145/209] Update sponsors --- README.md | 2 +- SPONSORS.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 81305c51db..701661c4d0 100644 --- a/README.md +++ b/README.md @@ -192,7 +192,7 @@ Real Madrid:
Discord Advice Center:
- + diff --git a/SPONSORS.json b/SPONSORS.json index 049998ca55..30081e24e5 100644 --- a/SPONSORS.json +++ b/SPONSORS.json @@ -86,7 +86,7 @@ "fields": [ { "name": "Discord Server", - "value": "[**Click Here**](https://discord.gg/nkMDQfuK)" + "value": "[**Click Here**](https://discord.gg/zmwZy5fd9v)" } ] } From 10f1bccf4722026d8c7aa0474e048d8a0b9ada2f Mon Sep 17 00:00:00 2001 From: lorenzo132 <50767078+lorenzo132@users.noreply.github.com> Date: Sat, 10 Jul 2021 15:49:28 +0200 Subject: [PATCH 146/209] Update modmail.py fix typo { https://github.com/kyb3r/modmail/pull/3067 } { https://usagi.xn--6frz82g/6DEshJ } --- cogs/modmail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogs/modmail.py b/cogs/modmail.py index 87b9d91e57..8b91342fa4 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -537,7 +537,7 @@ async def subscribe( if mention in mentions: embed = discord.Embed( color=self.bot.error_color, - description=f"{mention} is not subscribed to this thread.", + description=f"{mention} is already subscribed to this thread.", ) else: mentions.append(mention) From 1a238b3099b5593a5465479ef63c6ffbadd9b07e Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Thu, 22 Jul 2021 16:17:06 +0800 Subject: [PATCH 147/209] Formatting and final updates to deps --- Pipfile.lock | 225 +++++++++++++++++++++++++++------------------- bot.py | 49 +++------- cogs/modmail.py | 84 +++++------------ cogs/plugins.py | 19 ++-- cogs/utility.py | 86 +++++------------- core/changelog.py | 10 +-- core/checks.py | 4 +- core/clients.py | 34 ++----- core/models.py | 7 +- core/thread.py | 32 ++----- 10 files changed, 210 insertions(+), 340 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 15fdcacc5c..a9f820119e 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -202,81 +202,115 @@ "srv" ], "hashes": [ - "sha256:03be7ad107d252bb7325d4af6309fdd2c025d08854d35f0e7abc8bf048f4245e", - "sha256:071552b065e809d24c5653fcc14968cfd6fde4e279408640d5ac58e3353a3c5f", - "sha256:08b8723248730599c9803ae4c97b8f3f76c55219104303c88cb962a31e3bb5ee", - "sha256:08bda7b2c522ff9f1e554570da16298271ebb0c56ab9699446aacba249008988", - "sha256:0aaf4d44f1f819360f9432df538d54bbf850f18152f34e20337c01b828479171", - "sha256:0cabfc297f4cf921f15bc789a8fbfd7115eb9f813d3f47a74b609894bc66ab0d", - "sha256:13acf6164ead81c9fc2afa0e1ea6d6134352973ce2bb35496834fee057063c04", - "sha256:15b083d1b789b230e5ac284442d9ecb113c93f3785a6824f748befaab803b812", - "sha256:161fcd3281c42f644aa8dec7753cca2af03ce654e17d76da4f0dab34a12480ca", - "sha256:1a994a42f49dab5b6287e499be7d3d2751776486229980d8857ad53b8333d469", - "sha256:20d75ea11527331a2980ab04762a9d960bcfea9475c54bbeab777af880de61cd", - "sha256:225c61e08fe517aede7912937939e09adf086c8e6f7e40d4c85ad678c2c2aea3", - "sha256:3135dd574ef1286189f3f04a36c8b7a256376914f8cbbce66b94f13125ded858", - "sha256:3491c7de09e44eded16824cb58cf9b5cc1dc6f066a0bb7aa69929d02aa53b828", - "sha256:3551912f5c34d8dd7c32c6bb00ae04192af47f7b9f653608f107d19c1a21a194", - "sha256:38a7b5140a48fc91681cdb5cb95b7cd64640b43d19259fdd707fa9d5a715f2b2", - "sha256:3a3498a8326111221560e930f198b495ea6926937e249f475052ffc6893a6680", - "sha256:3bfc7689a1bacb9bcd2f2d5185d99507aa29f667a58dd8adaa43b5a348139e46", - "sha256:421d13523d11c57f57f257152bc4a6bb463aadf7a3918e9c96fefdd6be8dbfb8", - "sha256:424799c71ff435094e5fb823c40eebb4500f0e048133311e9c026467e8ccebac", - "sha256:474e21d0e07cd09679e357d1dac76e570dab86665e79a9d3354b10a279ac6fb3", - "sha256:4c7e8c8e1e1918dcf6a652ac4b9d87164587c26fd2ce5dd81e73a5ab3b3d492f", - "sha256:506a6dab4c7ffdcacdf0b8e70bd20eb2e77fa994519547c9d88d676400fcad58", - "sha256:510cd3bfabb63a07405b7b79fae63127e34c118b7531a2cbbafc7a24fd878594", - "sha256:517ba47ca04a55b1f50ee8df9fd97f6c37df5537d118fb2718952b8623860466", - "sha256:539d4cb1b16b57026999c53e5aab857fe706e70ae5310cc8c232479923f932e6", - "sha256:5c36428cc4f7fae56354db7f46677fd21222fc3cb1e8829549b851172033e043", - "sha256:5db59223ed1e634d842a053325f85f908359c6dac9c8ddce8ef145061fae7df8", - "sha256:5e606846c049ed40940524057bfdf1105af6066688c0e6a1a3ce2038589bae70", - "sha256:6060794aac9f7b0644b299f46a9c6cbc0bc470bd01572f4134df140afd41ded6", - "sha256:62c29bc36a6d9be68fe7b5aaf1e120b4aa66a958d1e146601fcd583eb12cae7b", - "sha256:73326b211e7410c8bd6a74500b1e3f392f39cf10862e243d00937e924f112c01", - "sha256:78f07961f4f214ea8e80be63cffd5cc158eb06cd922ffbf6c7155b11728f28f9", - "sha256:7c97554ea521f898753d9773891d0347ebfaddcc1dee2ad94850b163171bf1f1", - "sha256:8898f6699f740ca93a0879ed07d8e6db02d68af889d0ebb3d13ab017e6b1af1e", - "sha256:8a41fdc751dc4707a4fafb111c442411816a7c225ebb5cadb57599534b5d5372", - "sha256:8e0004b0393d72d76de94b4792a006cb960c1c65c7659930fbf9a81ce4341982", - "sha256:977b1d4f868986b4ba5d03c317fde4d3b66e687d74473130cd598e3103db34fa", - "sha256:9a4f6e0b01df820ba9ed0b4e618ca83a1c089e48d4f268d0e00dcd49893d4549", - "sha256:9b9298964389c180a063a9e8bac8a80ed42de11d04166b20249bfa0a489e0e0f", - "sha256:a08c8b322b671857c81f4c30cd3c8df2895fd3c0e9358714f39e0ef8fb327702", - "sha256:ad31f184dcd3271de26ab1f9c51574afb99e1b0e484ab1da3641256b723e4994", - "sha256:aff3656af2add93f290731a6b8930b23b35c0c09569150130a58192b3ec6fc61", - "sha256:b2f41261b648cf5dee425f37ff14f4ad151c2f24b827052b402637158fd056ef", - "sha256:b413117210fa6d92664c3d860571e8e8727c3e8f2ff197276c5d0cb365abd3ad", - "sha256:b7efc7e7049ef366777cfd35437c18a4166bb50a5606a1c840ee3b9624b54fc9", - "sha256:b8f94acd52e530a38f25e4d5bf7ddfdd4bea9193e718f58419def0d4406b58d3", - "sha256:d0a70151d7de8a3194cdc906bcc1a42e14594787c64b0c1c9c975e5a2af3e251", - "sha256:d360e5d5dd3d55bf5d1776964625018d85b937d1032bae1926dd52253decd0db", - "sha256:d4e62417e89b717a7bcd8576ac3108cd063225942cc91c5b37ff5465fdccd386", - "sha256:d65bac5f6724d9ea6f0b5a0f0e4952fbbf209adcf6b5583b54c54bd2fcd74dc0", - "sha256:e02beaab433fd1104b2804f909e694cfbdb6578020740a9051597adc1cd4e19f", - "sha256:e4b631688dfbdd61b5610e20b64b99d25771c6d52d9da73349342d2a0f11c46a", - "sha256:e4e9db78b71db2b1684ee4ecc3e32c4600f18cdf76e6b9ae03e338e52ee4b168", - "sha256:eb4d176394c37a76e8b0afe54b12d58614a67a60a7f8c0dd3a5afbb013c01092", - "sha256:f08665d3cc5abc2f770f472a9b5f720a9b3ab0b8b3bb97c7c1487515e5653d39", - "sha256:f3d851af3852f16ad4adc7ee054fd9c90a7a5063de94d815b7f6a88477b9f4c6", - "sha256:f4ba58157e8ae33ee86fadf9062c506e535afd904f07f9be32731f4410a23b7f", - "sha256:f664ed7613b8b18f0ce5696b146776266a038c19c5cd6efffa08ecc189b01b73", - "sha256:f947b359cc4769af8b49be7e37af01f05fcf15b401da2528021148e4a54426d1", - "sha256:fe4189846448df013cd9df11bba38ddf78043f8c290a9f06430732a7a8601cce", - "sha256:fea5cb1c63efe1399f0812532c7cf65458d38fd011be350bc5021dfcac39fba8", - "sha256:fedf0dee7a412ca6d1d6d92c158fe9cbaa8ea0cae90d268f9ccc0744de7a97d0", - "sha256:fffff7bfb6799a763d3742c59c6ee7ffadda21abed557637bc44ed1080876484" + "sha256:02dc0b0f48ed3cd06c13b7e31b066bf91e00dac5f8147b0a0a45f9009bfab857", + "sha256:053b4ebf91c7395d1fcd2ce6a9edff0024575b7b2de6781554a4114448a8adc9", + "sha256:070a4ef689c9438a999ec3830e69b208ff0d12251846e064d947f97d819d1d05", + "sha256:072ba7cb65c8aa4d5c5659bf6722ee85781c9d7816dc00679b8b6f3dff1ddafc", + "sha256:0b6055e0ef451ff73c93d0348d122a0750dddf323b9361de5835dac2f6cf7fc1", + "sha256:11f9e0cfc84ade088a38df2708d0b958bb76360181df1b2e1e1a41beaa57952b", + "sha256:18290649759f9db660972442aa606f845c368db9b08c4c73770f6da14113569b", + "sha256:186104a94d39b8412f8e3de385acd990a628346a4402d4f3a288a82b8660bd22", + "sha256:1970cfe2aec1bf74b40cf30c130ad10cd968941694630386db33e1d044c22a2e", + "sha256:19d4bd0fc29aa405bb1781456c9cfff9fceabb68543741eb17234952dbc2bbb0", + "sha256:1bab889ae7640eba739f67fcbf8eff252dddc60d4495e6ddd3a87cd9a95fdb52", + "sha256:1bc6fe7279ff40c6818db002bf5284aa03ec181ea1b1ceaeee33c289d412afa7", + "sha256:208debdcf76ed39ebf24f38509f50dc1c100e31e8653817fedb8e1f867850a13", + "sha256:2399a85b54f68008e483b2871f4a458b4c980469c7fe921595ede073e4844f1e", + "sha256:246ec420e4c8744fceb4e259f906211b9c198e1f345e6158dcd7cbad3737e11e", + "sha256:24f8aeec4d6b894a6128844e50ff423dd02462ee83addf503c598ee3a80ddf3d", + "sha256:255a35bf29185f44b412e31a927d9dcedda7c2c380127ecc4fbf2f61b72fa978", + "sha256:2dbfbbded947a83a3dffc2bd1ec4750c17e40904692186e2c55a3ad314ca0222", + "sha256:2e92aa32300a0b5e4175caec7769f482b292769807024a86d674b3f19b8e3755", + "sha256:316c1b8723afa9870567cd6dff35d440b2afeda53aa13da6c5ab85f98ed6f5ca", + "sha256:333bfad77aa9cd11711febfb75eed0bb537a1d022e1c252714dad38993590240", + "sha256:39dafa2eaf577d1969f289dc9a44501859a1897eb45bd589e93ce843fc610800", + "sha256:3ce83f17f641a62a4dfb0ba1b8a3c1ced7c842f511b5450d90c030c7828e3693", + "sha256:46d5ec90276f71af3a29917b30f2aec2315a2759b5f8d45b3b63a07ca8a070a3", + "sha256:48d5bc80ab0af6b60c4163c5617f5cd23f2f880d7600940870ea5055816af024", + "sha256:4ba0def4abef058c0e5101e05e3d5266e6fffb9795bbf8be0fe912a7361a0209", + "sha256:5af390fa9faf56c93252dab09ea57cd020c9123aa921b63a0ed51832fdb492e7", + "sha256:5e574664f1468872cd40f74e4811e22b1aa4de9399d6bcfdf1ee6ea94c017fcf", + "sha256:625befa3bc9b40746a749115cc6a15bf20b9bd7597ca55d646205b479a2c99c7", + "sha256:6261bee7c5abadeac7497f8f1c43e521da78dd13b0a2439f526a7b0fc3788824", + "sha256:657ad80de8ec9ed656f28844efc801a0802961e8c6a85038d97ff6f555ef4919", + "sha256:6b89dc51206e4971c5568c797991eaaef5dc2a6118d67165858ad11752dba055", + "sha256:6e66780f14c2efaf989cd3ac613b03ee6a8e3a0ba7b96c0bb14adca71a427e55", + "sha256:6fb3f85870ae26896bb44e67db94045f2ebf00c5d41e6b66cdcbb5afd644fc18", + "sha256:701e08457183da70ed96b35a6b43e6ba1df0b47c837b063cde39a1fbe1aeda81", + "sha256:70761fd3c576b027eec882b43ee0a8e5b22ff9c20cdf4d0400e104bc29e53e34", + "sha256:73b400fdc22de84bae0dbf1a22613928a41612ec0a3d6ed47caf7ad4d3d0f2ff", + "sha256:7412a36798966624dc4c57d64aa43c2d1100b348abd98daaac8e99e57d87e1d7", + "sha256:78ecb8d42f50d393af912bfb1fb1dcc9aabe9967973efb49ee577e8f1cea494c", + "sha256:7c6a9948916a7bbcc6d3a9f6fb75db1acb5546078023bfb3db6efabcd5a67527", + "sha256:7c72d08acdf573455b2b9d2b75b8237654841d63a48bc2327dc102c6ee89b75a", + "sha256:7d98ce3c42921bb91566121b658e0d9d59a9082a9bd6f473190607ff25ab637f", + "sha256:845a8b83798b2fb11b09928413cb32692866bfbc28830a433d9fa4c8c3720dd0", + "sha256:94d38eba4d1b5eb3e6bfece0651b855a35c44f32fd91f512ab4ba41b8c0d3e66", + "sha256:9a13661681d17e43009bb3e85e837aa1ec5feeea1e3654682a01b8821940f8b3", + "sha256:a0e5dff6701fa615f165306e642709e1c1550d5b237c5a7a6ea299886828bd50", + "sha256:a2239556ff7241584ce57be1facf25081669bb457a9e5cbe68cce4aae6567aa1", + "sha256:a325600c83e61e3c9cebc0c2b1c8c4140fa887f789085075e8f44c8ff2547eb9", + "sha256:a3566acfbcde46911c52810374ecc0354fdb841284a3efef6ff7105bc007e9a8", + "sha256:a634a4730ce0b0934ed75e45beba730968e12b4dafbb22f69b3b2f616d9e644e", + "sha256:a6d055f01b83b1a4df8bb0c61983d3bdffa913764488910af3620e5c2450bf83", + "sha256:a752ecd1a26000a6d67be7c9a2e93801994a8b3f866ac95b672fbc00225ca91a", + "sha256:a9ba2a63777027b06b116e1ea8248e66fd1bedc2c644f93124b81a91ddbf6d88", + "sha256:aaa038eafb7186a4abbb311fcf20724be9363645882bbce540bef4797e812a7a", + "sha256:af586e85144023686fb0af09c8cdf672484ea182f352e7ceead3d832de381e1b", + "sha256:b0a0cf39f589e52d801fdef418305562bc030cdf8929217463c8433c65fd5c2f", + "sha256:b1c4874331ab960429caca81acb9d2932170d66d6d6f87e65dc4507a85aca152", + "sha256:b3b5b3cbc3fdf4fcfa292529df2a85b5d9c7053913a739d3069af1e12e12219f", + "sha256:b542d56ed1b8d5cf3bb36326f814bd2fbe8812dfd2582b80a15689ea433c0e35", + "sha256:b6ea08758b6673610b3c5bdf47189286cf9c58b1077558706a2f6f8744922527", + "sha256:b754240daafecd9d5fce426b0fbaaed03f4ebb130745c8a4ae9231fffb8d75e5", + "sha256:b772bab31cbd9cb911e41e1a611ebc9497f9a32a7348e2747c38210f75c00f41", + "sha256:b88d1742159bc93a078733f9789f563cef26f5e370eba810476a71aa98e5fbc2", + "sha256:b8bf42d3b32f586f4c9e37541769993783a534ad35531ce8a4379f6fa664fba9", + "sha256:bc9ac81e73573516070d24ce15da91281922811f385645df32bd3c8a45ab4684", + "sha256:c188db6cf9e14dbbb42f5254292be96f05374a35e7dfa087cc2140f0ff4f10f6", + "sha256:c55782a55f4a013a78ac5b6ee4b8731a192dea7ab09f1b6b3044c96d5128edd4", + "sha256:c5cab230e7cabdae9ff23c12271231283efefb944c1b79bed79a91beb65ba547", + "sha256:cbf8672edeb7b7128c4a939274801f0e32bbf5159987815e3d1eace625264a46", + "sha256:cc2894fe91f31a513860238ede69fe47fada21f9e7ddfe73f7f9fef93a971e41", + "sha256:cda9e628b1315beec8341e8c04aac9a0b910650b05e0751e42e399d5694aeacb", + "sha256:ceae3ab9e11a27aaab42878f1d203600dfd24f0e43678b47298219a0f10c0d30", + "sha256:ced944dcdd561476deef7cb7bfd4987c69fffbfeff6d02ca4d5d4fd592d559b7", + "sha256:d04ca462cb99077e6c059e97c072957caf2918e6e4191e3161c01c439e0193de", + "sha256:d1131562ddc2ea8a446f66c2648d7dabec2b3816fc818528eb978a75a6d23b2e", + "sha256:d1740776b70367277323fafb76bcf09753a5cc9824f5d705bac22a34ff3668ea", + "sha256:d6e11ffd43184d529d6752d6dcb62b994f903038a17ea2168ef1910c96324d26", + "sha256:d73e10772152605f6648ba4410318594f1043bbfe36d2fadee7c4b8912eff7c5", + "sha256:da8288bc4a7807c6715416deed1c57d94d5e03e93537889e002bf985be503f1a", + "sha256:db93608a246da44d728842b8fa9e45aa9782db76955f634a707739a8d53ff544", + "sha256:dcd3d0009fbb6e454d729f8b22d0063bd9171c31a55e0f0271119bd4f2700023", + "sha256:dd1f49f949a658c4e8f81ed73f9aad25fcc7d4f62f767f591e749e30038c4e1d", + "sha256:dd6ff2192f34bd622883c745a56f492b1c9ccd44e14953e8051c33024a2947d5", + "sha256:e018a4921657c2d3f89c720b7b90b9182e277178a04a7e9542cc79d7d787ca51", + "sha256:e2b7670c0c8c6b501464150dd49dd0d6be6cb7f049e064124911cec5514fa19e", + "sha256:e7a33322e08021c37e89cae8ff06327503e8a1719e97c69f32c31cbf6c30d72c", + "sha256:e8a82e35d52ad6f867e88096a1a2b9bdc7ec4d5e65c7b4976a248bf2d1a32a93", + "sha256:e9faf8d4712d5ea301d74abfcf6dafe4b7f4af7936e91f283b0ad7bf69ed3e3a", + "sha256:ec5ca7c0007ce268048bbe0ffc6846ed1616cf3d8628b136e81d5e64ff3f52a2", + "sha256:eee42a1cc06565f6b21caa1f504ec15e07de7ebfd520ab57f8cb3308bc118e22", + "sha256:f2acf9bbcd514e901f82c4ca6926bbd2ae61716728f110b4343eb0a69612d018", + "sha256:f55c1ddcc1f6050b07d468ce594f55dbf6107b459e16f735d26818d7be1e9538", + "sha256:f6977a520bd96e097c8a37a8cbb9faa1ea99d21bf84190195056e25f688af73d", + "sha256:f94c7d22fb36b184734dded7345a04ec5f95130421c775b8b0c65044ef073f34", + "sha256:fa8957e9a1b202cb45e6b839c241cd986c897be1e722b81d2f32e9c6aeee80b0", + "sha256:fd3854148005c808c485c754a184c71116372263709958b42aefbef2e5dd373a", + "sha256:fe5872ce6f9627deac8314bdffd3862624227c3de4c17ef0cc78bbf0402999eb", + "sha256:ffbae429ba9e42d0582d3ac63fdb410338892468a2107d8ff68228ec9a39a0ed" ], "index": "pypi", - "version": "==3.11.4" + "version": "==3.12.0" }, "python-dateutil": { "hashes": [ - "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", - "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" + "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", + "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" ], "index": "pypi", - "version": "==2.8.1" + "version": "==2.8.2" }, "python-dotenv": { "hashes": [ @@ -304,20 +338,19 @@ }, "uvloop": { "hashes": [ - "sha256:114543c84e95df1b4ff546e6e3a27521580466a30127f12172a3278172ad68bc", - "sha256:19fa1d56c91341318ac5d417e7b61c56e9a41183946cc70c411341173de02c69", - "sha256:2bb0624a8a70834e54dde8feed62ed63b50bad7a1265c40d6403a2ac447bce01", - "sha256:42eda9f525a208fbc4f7cecd00fa15c57cc57646c76632b3ba2fe005004f051d", - "sha256:44cac8575bf168601424302045234d74e3561fbdbac39b2b54cc1d1d00b70760", - "sha256:6de130d0cb78985a5d080e323b86c5ecaf3af82f4890492c05981707852f983c", - "sha256:7ae39b11a5f4cec1432d706c21ecc62f9e04d116883178b09671aa29c46f7a47", - "sha256:90e56f17755e41b425ad19a08c41dc358fa7bf1226c0f8e54d4d02d556f7af7c", - "sha256:b45218c99795803fb8bdbc9435ff7f54e3a591b44cd4c121b02fa83affb61c7c", - "sha256:e5e5f855c9bf483ee6cd1eb9a179b740de80cb0ae2988e3fa22309b78e2ea0e7" + "sha256:0de811931e90ae2da9e19ce70ffad73047ab0c1dba7c6e74f9ae1a3aabeb89bd", + "sha256:1ff05116ede1ebdd81802df339e5b1d4cab1dfbd99295bf27e90b4cec64d70e9", + "sha256:2d8ffe44ae709f839c54bacf14ed283f41bee90430c3b398e521e10f8d117b3a", + "sha256:5cda65fc60a645470b8525ce014516b120b7057b576fa876cdfdd5e60ab1efbb", + "sha256:63a3288abbc9c8ee979d7e34c34e780b2fbab3e7e53d00b6c80271119f277399", + "sha256:7522df4e45e4f25b50adbbbeb5bb9847495c438a628177099d2721f2751ff825", + "sha256:7f4b8a905df909a407c5791fb582f6c03b0d3b491ecdc1cdceaefbc9bf9e08f6", + "sha256:905f0adb0c09c9f44222ee02f6b96fd88b493478fffb7a345287f9444e926030", + "sha256:ae2b325c0f6d748027f7463077e457006b4fdb35a8788f01754aadba825285ee", + "sha256:e71fb9038bfcd7646ca126c5ef19b17e48d4af9e838b2bcfda7a9f55a6552a32" ], - "index": "pypi", "markers": "sys_platform != 'win32'", - "version": "==0.15.2" + "version": "==0.15.3" }, "yarl": { "hashes": [ @@ -373,11 +406,11 @@ }, "astroid": { "hashes": [ - "sha256:38b95085e9d92e2ca06cf8b35c12a74fa81da395a6f9e65803742e6509c05892", - "sha256:606b2911d10c3dcf35e58d2ee5c97360e8477d7b9f3efc3f24811c93e6fc2cd9" + "sha256:7b963d1c590d490f60d2973e57437115978d3a2529843f160b5003b721e1e925", + "sha256:83e494b02d75d07d4e347b27c066fd791c0c74fc96c613d1ea3de0c82c48168f" ], "markers": "python_version ~= '3.6'", - "version": "==2.6.2" + "version": "==2.6.5" }, "bandit": { "hashes": [ @@ -403,6 +436,14 @@ "markers": "python_version >= '3.6'", "version": "==8.0.1" }, + "colorama": { + "hashes": [ + "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b", + "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2" + ], + "index": "pypi", + "version": "==0.4.4" + }, "gitdb": { "hashes": [ "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0", @@ -421,11 +462,11 @@ }, "isort": { "hashes": [ - "sha256:83510593e07e433b77bd5bff0f6f607dbafa06d1a89022616f02d8b699cfcd56", - "sha256:8e2c107091cfec7286bc0f68a547d0ba4c094d460b732075b6fba674f1035c0c" + "sha256:eed17b53c3e7912425579853d078a0832820f023191561fcee9d7cae424e0813", + "sha256:f65ce5bd4cbc6abdfbe29afc2f0245538ab358c14590912df638033f157d555e" ], "markers": "python_version < '4.0' and python_full_version >= '3.6.1'", - "version": "==5.9.1" + "version": "==5.9.2" }, "lazy-object-proxy": { "hashes": [ @@ -471,10 +512,10 @@ }, "pathspec": { "hashes": [ - "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd", - "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d" + "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", + "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1" ], - "version": "==0.8.1" + "version": "==0.9.0" }, "pbr": { "hashes": [ @@ -486,11 +527,11 @@ }, "pylint": { "hashes": [ - "sha256:23a1dc8b30459d78e9ff25942c61bb936108ccbe29dd9e71c01dc8274961709a", - "sha256:5d46330e6b8886c31b5e3aba5ff48c10f4aa5e76cbf9002c6544306221e63fbc" + "sha256:1f333dc72ef7f5ea166b3230936ebcfb1f3b722e76c980cb9fe6b9f95e8d3172", + "sha256:748f81e5776d6273a6619506e08f1b48ff9bcb8198366a56821cf11aac14fc87" ], "index": "pypi", - "version": "==2.9.3" + "version": "==2.9.5" }, "pyyaml": { "hashes": [ diff --git a/bot.py b/bot.py index 1deb9b7d2a..131032ec80 100644 --- a/bot.py +++ b/bot.py @@ -506,8 +506,7 @@ def command_perm(self, command_name: str) -> PermissionLevel: logger.debug("Command %s not found.", command_name) return PermissionLevel.INVALID level = next( - (check.permission_level for check in command.checks if hasattr(check, "permission_level")), - None, + (check.permission_level for check in command.checks if hasattr(check, "permission_level")), None, ) if level is None: logger.debug("Command %s does not have a permission level.", command_name) @@ -610,13 +609,7 @@ async def on_ready(self): if self.config.get("data_collection"): self.metadata_loop = tasks.Loop( - self.post_metadata, - seconds=0, - minutes=0, - hours=1, - count=None, - reconnect=True, - loop=None, + self.post_metadata, seconds=0, minutes=0, hours=1, count=None, reconnect=True, loop=None, ) self.metadata_loop.before_loop(self.before_post_metadata) self.metadata_loop.start() @@ -769,8 +762,7 @@ def check_manual_blocked(self, author: discord.Member) -> bool: end_time = re.search(r"%([^%]+?)%", blocked_reason) if end_time is not None: logger.warning( - r"Deprecated time message for user %s, block and unblock again to update.", - author.name, + r"Deprecated time message for user %s, block and unblock again to update.", author.name, ) if end_time is not None: @@ -791,11 +783,7 @@ async def _process_blocked(self, message): return False async def is_blocked( - self, - author: discord.User, - *, - channel: discord.TextChannel = None, - send_message: bool = False, + self, author: discord.User, *, channel: discord.TextChannel = None, send_message: bool = False, ) -> typing.Tuple[bool, str]: member = self.guild.get_member(author.id) @@ -826,9 +814,7 @@ async def is_blocked( if send_message: await channel.send( embed=discord.Embed( - title="Message not sent!", - description=new_reason, - color=self.error_color, + title="Message not sent!", description=new_reason, color=self.error_color, ) ) return True @@ -927,8 +913,7 @@ async def process_dm_modmail(self, message: discord.Message) -> None: description=self.config["disabled_current_thread_response"], ) embed.set_footer( - text=self.config["disabled_current_thread_footer"], - icon_url=self.guild.icon_url, + text=self.config["disabled_current_thread_footer"], icon_url=self.guild.icon_url, ) logger.info("A message was blocked from %s due to disabled Modmail.", message.author) await self.add_reaction(message, blocked_emoji) @@ -1289,18 +1274,14 @@ async def on_raw_reaction_add(self, payload): await message.remove_reaction(payload.emoji, member) await message.add_reaction(emoji_fmt) # bot adds as well - if self.config["dm_disabled"] in ( - DMDisabled.NEW_THREADS, - DMDisabled.ALL_THREADS, - ): + if self.config["dm_disabled"] in (DMDisabled.NEW_THREADS, DMDisabled.ALL_THREADS,): embed = discord.Embed( title=self.config["disabled_new_thread_title"], color=self.error_color, description=self.config["disabled_new_thread_response"], ) embed.set_footer( - text=self.config["disabled_new_thread_footer"], - icon_url=self.guild.icon_url, + text=self.config["disabled_new_thread_footer"], icon_url=self.guild.icon_url, ) logger.info( "A new thread using react to contact was blocked from %s due to disabled Modmail.", @@ -1359,9 +1340,7 @@ async def on_member_remove(self, member): if thread: if self.config["close_on_leave"]: await thread.close( - closer=member.guild.me, - message=self.config["close_on_leave_reason"], - silent=True, + closer=member.guild.me, message=self.config["close_on_leave_reason"], silent=True, ) else: embed = discord.Embed( @@ -1544,9 +1523,7 @@ async def autoupdate(self): commit_data = data["data"] user = data["user"] embed.set_author( - name=user["username"] + " - Updating Bot", - icon_url=user["avatar_url"], - url=user["url"], + name=user["username"] + " - Updating Bot", icon_url=user["avatar_url"], url=user["url"], ) embed.set_footer(text=f"Updating Modmail v{self.version} " f"-> v{latest.version}") @@ -1575,11 +1552,7 @@ async def autoupdate(self): pass command = "git pull" - proc = await asyncio.create_subprocess_shell( - command, - stderr=PIPE, - stdout=PIPE, - ) + proc = await asyncio.create_subprocess_shell(command, stderr=PIPE, stdout=PIPE,) err = await proc.stderr.read() err = err.decode("utf-8").rstrip() res = await proc.stdout.read() diff --git a/cogs/modmail.py b/cogs/modmail.py index 0e41b76677..0db8a85e2e 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -50,9 +50,7 @@ async def setup(self, ctx): if self.bot.modmail_guild is None: embed = discord.Embed( - title="Error", - description="Modmail functioning guild not found.", - color=self.bot.error_color, + title="Error", description="Modmail functioning guild not found.", color=self.bot.error_color, ) return await ctx.send(embed=embed) @@ -184,9 +182,7 @@ async def snippet_raw(self, ctx, *, name: str.lower): else: val = truncate(escape_code_block(val), 2048 - 7) embed = discord.Embed( - title=f'Raw snippet - "{name}":', - description=f"```\n{val}```", - color=self.bot.main_color, + title=f'Raw snippet - "{name}":', description=f"```\n{val}```", color=self.bot.main_color, ) return await ctx.send(embed=embed) @@ -208,9 +204,7 @@ async def snippet_add(self, ctx, name: str.lower, *, value: commands.clean_conte """ if name in self.bot.snippets: embed = discord.Embed( - title="Error", - color=self.bot.error_color, - description=f"Snippet `{name}` already exists.", + title="Error", color=self.bot.error_color, description=f"Snippet `{name}` already exists.", ) return await ctx.send(embed=embed) @@ -234,9 +228,7 @@ async def snippet_add(self, ctx, name: str.lower, *, value: commands.clean_conte await self.bot.config.update() embed = discord.Embed( - title="Added snippet", - color=self.bot.main_color, - description="Successfully created snippet.", + title="Added snippet", color=self.bot.main_color, description="Successfully created snippet.", ) return await ctx.send(embed=embed) @@ -453,8 +445,7 @@ async def notify(self, ctx, *, user_or_role: Union[discord.Role, User, str.lower if mention in mentions: embed = discord.Embed( - color=self.bot.error_color, - description=f"{mention} is already going to be mentioned.", + color=self.bot.error_color, description=f"{mention} is already going to be mentioned.", ) else: mentions.append(mention) @@ -489,8 +480,7 @@ async def unnotify(self, ctx, *, user_or_role: Union[discord.Role, User, str.low if mention not in mentions: embed = discord.Embed( - color=self.bot.error_color, - description=f"{mention} does not have a pending notification.", + color=self.bot.error_color, description=f"{mention} does not have a pending notification.", ) else: mentions.remove(mention) @@ -526,8 +516,7 @@ async def subscribe(self, ctx, *, user_or_role: Union[discord.Role, User, str.lo if mention in mentions: embed = discord.Embed( - color=self.bot.error_color, - description=f"{mention} is already subscribed to this thread.", + color=self.bot.error_color, description=f"{mention} is already subscribed to this thread.", ) else: mentions.append(mention) @@ -562,15 +551,13 @@ async def unsubscribe(self, ctx, *, user_or_role: Union[discord.Role, User, str. if mention not in mentions: embed = discord.Embed( - color=self.bot.error_color, - description=f"{mention} is not subscribed to this thread.", + color=self.bot.error_color, description=f"{mention} is not subscribed to this thread.", ) else: mentions.remove(mention) await self.bot.config.update() embed = discord.Embed( - color=self.bot.main_color, - description=f"{mention} is now unsubscribed from this thread.", + color=self.bot.main_color, description=f"{mention} is now unsubscribed from this thread.", ) return await ctx.send(embed=embed) @@ -697,8 +684,7 @@ async def logs(self, ctx, *, user: User = None): if not any(not log["open"] for log in logs): embed = discord.Embed( - color=self.bot.error_color, - description="This user does not have any previous logs.", + color=self.bot.error_color, description="This user does not have any previous logs.", ) return await ctx.send(embed=embed) @@ -725,8 +711,7 @@ async def logs_closed_by(self, ctx, *, user: User = None): if not embeds: embed = discord.Embed( - color=self.bot.error_color, - description="No log entries have been found for that query.", + color=self.bot.error_color, description="No log entries have been found for that query.", ) return await ctx.send(embed=embed) @@ -745,9 +730,7 @@ async def logs_delete(self, ctx, key_or_link: str): if not success: embed = discord.Embed( - title="Error", - description=f"Log entry `{key}` not found.", - color=self.bot.error_color, + title="Error", description=f"Log entry `{key}` not found.", color=self.bot.error_color, ) else: embed = discord.Embed( @@ -800,8 +783,7 @@ async def logs_search(self, ctx, limit: Optional[int] = None, *, query): if not embeds: embed = discord.Embed( - color=self.bot.error_color, - description="No log entries have been found for that query.", + color=self.bot.error_color, description="No log entries have been found for that query.", ) return await ctx.send(embed=embed) @@ -1012,10 +994,7 @@ async def contact( else: thread = await self.bot.threads.create( - recipient=user, - creator=ctx.author, - category=category, - manual_trigger=manual_trigger, + recipient=user, creator=ctx.author, category=category, manual_trigger=manual_trigger, ) if thread.cancelled: return @@ -1029,11 +1008,7 @@ async def contact( else: description = f"{ctx.author.name} has opened a Modmail thread." - em = discord.Embed( - title="New Thread", - description=description, - color=self.bot.main_color, - ) + em = discord.Embed(title="New Thread", description=description, color=self.bot.main_color,) if self.bot.config["show_timestamp"]: em.timestamp = datetime.utcnow() em.set_footer(icon_url=ctx.author.avatar_url) @@ -1075,8 +1050,7 @@ async def blocked(self, ctx): end_time = re.search(r"%([^%]+?)%", reason) if end_time is not None: logger.warning( - r"Deprecated time message for user %s, block and unblock again to update.", - id_, + r"Deprecated time message for user %s, block and unblock again to update.", id_, ) if end_time is not None: @@ -1107,8 +1081,7 @@ async def blocked(self, ctx): end_time = re.search(r"%([^%]+?)%", reason) if end_time is not None: logger.warning( - r"Deprecated time message for role %s, block and unblock again to update.", - id_, + r"Deprecated time message for role %s, block and unblock again to update.", id_, ) if end_time is not None: @@ -1130,9 +1103,7 @@ async def blocked(self, ctx): line = mention + f" - {reason or 'No Reason Provided'}\n" if len(embed.description) + len(line) > 2048: embed = discord.Embed( - title="Blocked Users (Continued)", - color=self.bot.main_color, - description=line, + title="Blocked Users (Continued)", color=self.bot.main_color, description=line, ) embeds.append(embed) else: @@ -1149,9 +1120,7 @@ async def blocked(self, ctx): line = mention + f" - {reason or 'No Reason Provided'}\n" if len(embed.description) + len(line) > 2048: embed = discord.Embed( - title="Blocked Roles (Continued)", - color=self.bot.main_color, - description=line, + title="Blocked Roles (Continued)", color=self.bot.main_color, description=line, ) embeds.append(embed) else: @@ -1211,9 +1180,7 @@ async def blocked_whitelist(self, ctx, *, user: User = None): ) else: embed = discord.Embed( - title="Success", - color=self.bot.main_color, - description=f"{mention} is now whitelisted.", + title="Success", color=self.bot.main_color, description=f"{mention} is now whitelisted.", ) return await ctx.send(embed=embed) @@ -1291,9 +1258,7 @@ async def block( ) else: embed = discord.Embed( - title="Success", - color=self.bot.main_color, - description=f"{mention} is now blocked {reason}", + title="Success", color=self.bot.main_color, description=f"{mention} is now blocked {reason}", ) if isinstance(user_or_role, discord.Role): @@ -1356,9 +1321,7 @@ async def unblock(self, ctx, *, user_or_role: Union[User, Role] = None): await self.bot.config.update() embed = discord.Embed( - title="Success", - color=self.bot.main_color, - description=f"{mention} is no longer blocked.", + title="Success", color=self.bot.main_color, description=f"{mention} is no longer blocked.", ) else: embed = discord.Embed( @@ -1415,8 +1378,7 @@ async def repair(self, ctx): # Search cache for channel user_id, thread = next( - ((k, v) for k, v in self.bot.threads.cache.items() if v.channel == ctx.channel), - (-1, None), + ((k, v) for k, v in self.bot.threads.cache.items() if v.channel == ctx.channel), (-1, None), ) if thread is not None: logger.debug("Found thread with tempered ID.") diff --git a/cogs/plugins.py b/cogs/plugins.py index aab5dc8bd6..a9a4d247da 100644 --- a/cogs/plugins.py +++ b/cogs/plugins.py @@ -159,9 +159,7 @@ async def initial_load_plugins(self): except Exception: self.bot.config["plugins"].remove(plugin_name) logger.error( - "Error when loading plugin %s. Plugin removed from config.", - plugin, - exc_info=True, + "Error when loading plugin %s. Plugin removed from config.", plugin, exc_info=True, ) continue @@ -278,8 +276,7 @@ async def parse_user_input(self, ctx, plugin_name, check_version=False): if not self._ready_event.is_set(): embed = discord.Embed( - description="Plugins are still loading, please try again later.", - color=self.bot.main_color, + description="Plugins are still loading, please try again later.", color=self.bot.main_color, ) await ctx.send(embed=embed) return @@ -349,20 +346,17 @@ async def plugins_add(self, ctx, *, plugin_name: str): if plugin.name in self.bot.cogs: # another class with the same name embed = discord.Embed( - description="Cannot install this plugin (dupe cog name).", - color=self.bot.error_color, + description="Cannot install this plugin (dupe cog name).", color=self.bot.error_color, ) return await ctx.send(embed=embed) if plugin.local: embed = discord.Embed( - description=f"Starting to load local plugin from {plugin.link}...", - color=self.bot.main_color, + description=f"Starting to load local plugin from {plugin.link}...", color=self.bot.main_color, ) else: embed = discord.Embed( - description=f"Starting to download plugin from {plugin.link}...", - color=self.bot.main_color, + description=f"Starting to download plugin from {plugin.link}...", color=self.bot.main_color, ) msg = await ctx.send(embed=embed) @@ -562,8 +556,7 @@ async def plugins_loaded(self, ctx): if not self._ready_event.is_set(): embed = discord.Embed( - description="Plugins are still loading, please try again later.", - color=self.bot.main_color, + description="Plugins are still loading, please try again later.", color=self.bot.main_color, ) return await ctx.send(embed=embed) diff --git a/cogs/utility.py b/cogs/utility.py index 759b573748..6f6ed89b47 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -305,9 +305,7 @@ async def about(self, ctx): """Shows information about this bot.""" embed = discord.Embed(color=self.bot.main_color, timestamp=datetime.utcnow()) embed.set_author( - name="Modmail - About", - icon_url=self.bot.user.avatar_url, - url="https://discord.gg/F34cRU8", + name="Modmail - About", icon_url=self.bot.user.avatar_url, url="https://discord.gg/F34cRU8", ) embed.set_thumbnail(url=self.bot.user.avatar_url) @@ -388,8 +386,7 @@ async def debug(self, ctx): log_file_name = self.bot.token.split(".")[0] with open( - os.path.join(os.path.dirname(os.path.abspath(__file__)), f"../temp/{log_file_name}.log"), - "r+", + os.path.join(os.path.dirname(os.path.abspath(__file__)), f"../temp/{log_file_name}.log"), "r+", ) as f: logs = f.read().strip() @@ -441,8 +438,7 @@ async def debug_hastebin(self, ctx): log_file_name = self.bot.token.split(".")[0] with open( - os.path.join(os.path.dirname(os.path.abspath(__file__)), f"../temp/{log_file_name}.log"), - "rb+", + os.path.join(os.path.dirname(os.path.abspath(__file__)), f"../temp/{log_file_name}.log"), "rb+", ) as f: logs = BytesIO(f.read().strip()) @@ -455,9 +451,7 @@ async def debug_hastebin(self, ctx): logger.error(data["message"]) raise embed = discord.Embed( - title="Debug Logs", - color=self.bot.main_color, - description=f"{haste_url}/" + key, + title="Debug Logs", color=self.bot.main_color, description=f"{haste_url}/" + key, ) except (JSONDecodeError, ClientResponseError, IndexError, KeyError): embed = discord.Embed( @@ -477,8 +471,7 @@ async def debug_clear(self, ctx): log_file_name = self.bot.token.split(".")[0] with open( - os.path.join(os.path.dirname(os.path.abspath(__file__)), f"../temp/{log_file_name}.log"), - "w", + os.path.join(os.path.dirname(os.path.abspath(__file__)), f"../temp/{log_file_name}.log"), "w", ): pass await ctx.send( @@ -693,14 +686,12 @@ async def mention(self, ctx, *user_or_role: Union[discord.Role, discord.Member, option = user_or_role[0].lower() if option == "disable": embed = discord.Embed( - description=f"Disabled mention on thread creation.", - color=self.bot.main_color, + description=f"Disabled mention on thread creation.", color=self.bot.main_color, ) self.bot.config["mention"] = None else: embed = discord.Embed( - description="`mention` is reset to default.", - color=self.bot.main_color, + description="`mention` is reset to default.", color=self.bot.main_color, ) self.bot.config.remove("mention") await self.bot.config.update() @@ -775,9 +766,7 @@ async def config_options(self, ctx): for names in zip_longest(*(iter(sorted(self.bot.config.public_keys)),) * 15): description = "\n".join(f"`{name}`" for name in takewhile(lambda x: x is not None, names)) embed = discord.Embed( - title="Available configuration keys:", - color=self.bot.main_color, - description=description, + title="Available configuration keys:", color=self.bot.main_color, description=description, ) embeds.append(embed) @@ -820,9 +809,7 @@ async def config_remove(self, ctx, *, key: str.lower): self.bot.config.remove(key) await self.bot.config.update() embed = discord.Embed( - title="Success", - color=self.bot.main_color, - description=f"`{key}` had been reset to default.", + title="Success", color=self.bot.main_color, description=f"`{key}` had been reset to default.", ) else: embed = discord.Embed( @@ -851,9 +838,7 @@ async def config_get(self, ctx, *, key: str.lower = None): else: embed = discord.Embed( - title="Error", - color=self.bot.error_color, - description=f"`{key}` is an invalid key.", + title="Error", color=self.bot.error_color, description=f"`{key}` is an invalid key.", ) embed.set_footer( text=f'Type "{self.bot.prefix}config options" for a list of config variables.' @@ -886,9 +871,7 @@ async def config_help(self, ctx, key: str.lower = None): key, {**self.bot.config.public_keys, **self.bot.config.protected_keys} ) embed = discord.Embed( - title="Error", - color=self.bot.error_color, - description=f"`{key}` is an invalid key.", + title="Error", color=self.bot.error_color, description=f"`{key}` is an invalid key.", ) if closest: embed.add_field(name=f"Perhaps you meant:", value="\n".join(f"`{x}`" for x in closest)) @@ -898,9 +881,7 @@ async def config_help(self, ctx, key: str.lower = None): if key is not None and key not in config_help: embed = discord.Embed( - title="Error", - color=self.bot.error_color, - description=f"No help details found for `{key}`.", + title="Error", color=self.bot.error_color, description=f"No help details found for `{key}`.", ) return await ctx.send(embed=embed) @@ -992,9 +973,7 @@ async def alias(self, ctx, *, name: str.lower = None): embeds = [] for i, val in enumerate(values, start=1): embed = discord.Embed( - color=self.bot.main_color, - title=f'Alias - "{name}" - Step {i}:', - description=val, + color=self.bot.main_color, title=f'Alias - "{name}" - Step {i}:', description=val, ) embeds += [embed] session = EmbedPaginatorSession(ctx, *embeds) @@ -1269,9 +1248,7 @@ async def permissions_override(self, ctx, command_name: str.lower, *, level_name ) else: logger.info( - "Updated command permission level for `%s` to `%s`.", - command.qualified_name, - level.name, + "Updated command permission level for `%s` to `%s`.", command.qualified_name, level.name, ) self.bot.config["override_command_level"][command.qualified_name] = level.name @@ -1287,12 +1264,7 @@ async def permissions_override(self, ctx, command_name: str.lower, *, level_name @permissions.command(name="add", usage="[command/level] [name] [user/role]") @checks.has_permissions(PermissionLevel.OWNER) async def permissions_add( - self, - ctx, - type_: str.lower, - name: str, - *, - user_or_role: Union[discord.Role, utils.User, str], + self, ctx, type_: str.lower, name: str, *, user_or_role: Union[discord.Role, utils.User, str], ): """ Add a permission to a command or a permission level. @@ -1361,12 +1333,7 @@ async def permissions_add( ) @checks.has_permissions(PermissionLevel.OWNER) async def permissions_remove( - self, - ctx, - type_: str.lower, - name: str, - *, - user_or_role: Union[discord.Role, utils.User, str] = None, + self, ctx, type_: str.lower, name: str, *, user_or_role: Union[discord.Role, utils.User, str] = None, ): """ Remove permission to use a command, permission level, or command level override. @@ -1860,11 +1827,7 @@ async def autotrigger_list(self, ctx): embeds = [] for keyword in self.bot.auto_triggers: command = self.bot.auto_triggers[keyword] - embed = discord.Embed( - title=keyword, - color=self.bot.main_color, - description=command, - ) + embed = discord.Embed(title=keyword, color=self.bot.main_color, description=command,) embeds.append(embed) if not embeds: @@ -1965,11 +1928,7 @@ async def update(self, ctx, *, flag: str = ""): command = "git pull" - proc = await asyncio.create_subprocess_shell( - command, - stderr=PIPE, - stdout=PIPE, - ) + proc = await asyncio.create_subprocess_shell(command, stderr=PIPE, stdout=PIPE,) err = await proc.stderr.read() err = err.decode("utf-8").rstrip() res = await proc.stdout.read() @@ -1982,10 +1941,7 @@ async def update(self, ctx, *, flag: str = ""): elif res != "Already up to date.": logger.info("Bot has been updated.") - embed = discord.Embed( - title="Bot has been updated", - color=self.bot.main_color, - ) + embed = discord.Embed(title="Bot has been updated", color=self.bot.main_color,) embed.set_footer(text=f"Updating Modmail v{self.bot.version} " f"-> v{latest.version}") embed.description = latest.description for name, value in latest.fields.items(): @@ -2000,9 +1956,7 @@ async def update(self, ctx, *, flag: str = ""): return await self.bot.close() else: embed = discord.Embed( - title="Already up to date", - description=desc, - color=self.bot.main_color, + title="Already up to date", description=desc, color=self.bot.main_color, ) embed.set_footer(text="Force update") await ctx.send(embed=embed) diff --git a/core/changelog.py b/core/changelog.py index 7c9af2e1bb..5c23020eb6 100644 --- a/core/changelog.py +++ b/core/changelog.py @@ -89,9 +89,7 @@ def embed(self) -> Embed: """ embed = Embed(color=self.bot.main_color, description=self.description) embed.set_author( - name=f"v{self.version} - Changelog", - icon_url=self.bot.user.avatar_url, - url=self.url, + name=f"v{self.version} - Changelog", icon_url=self.bot.user.avatar_url, url=self.url, ) for name, value in self.fields.items(): @@ -170,11 +168,7 @@ async def from_url(cls, bot, url: str = "") -> "Changelog": The newly created `Changelog` parsed from the `url`. """ # get branch via git cli if available - proc = await asyncio.create_subprocess_shell( - "git branch --show-current", - stderr=PIPE, - stdout=PIPE, - ) + proc = await asyncio.create_subprocess_shell("git branch --show-current", stderr=PIPE, stdout=PIPE,) err = await proc.stderr.read() err = err.decode("utf-8").rstrip() res = await proc.stdout.read() diff --git a/core/checks.py b/core/checks.py index 1079b55530..74b7dc38cd 100644 --- a/core/checks.py +++ b/core/checks.py @@ -5,9 +5,7 @@ logger = getLogger(__name__) -def has_permissions_predicate( - permission_level: PermissionLevel = PermissionLevel.REGULAR, -): +def has_permissions_predicate(permission_level: PermissionLevel = PermissionLevel.REGULAR,): async def predicate(ctx): return await check_permissions(ctx, ctx.command.qualified_name) diff --git a/core/clients.py b/core/clients.py index 5c5acc8fad..61fcfdb487 100644 --- a/core/clients.py +++ b/core/clients.py @@ -321,12 +321,7 @@ async def edit_message(self, message_id: Union[int, str], new_content: str) -> N return NotImplemented async def append_log( - self, - message: Message, - *, - message_id: str = "", - channel_id: str = "", - type_: str = "thread_message", + self, message: Message, *, message_id: str = "", channel_id: str = "", type_: str = "thread_message", ) -> dict: return NotImplemented @@ -548,12 +543,7 @@ async def edit_message(self, message_id: Union[int, str], new_content: str) -> N ) async def append_log( - self, - message: Message, - *, - message_id: str = "", - channel_id: str = "", - type_: str = "thread_message", + self, message: Message, *, message_id: str = "", channel_id: str = "", type_: str = "thread_message", ) -> dict: channel_id = str(channel_id) or str(message.channel.id) message_id = str(message_id) or str(message.id) @@ -599,11 +589,7 @@ async def search_closed_by(self, user_id: Union[int, str]): async def search_by_text(self, text: str, limit: Optional[int]): return await self.bot.db.logs.find( - { - "guild_id": str(self.bot.guild_id), - "open": False, - "$text": {"$search": f'"{text}"'}, - }, + {"guild_id": str(self.bot.guild_id), "open": False, "$text": {"$search": f'"{text}"'},}, {"messages": {"$slice": 5}}, ).to_list(limit) @@ -644,11 +630,7 @@ async def update_repository(self) -> dict: data = await user.update_repository() return { "data": data, - "user": { - "username": user.username, - "avatar_url": user.avatar_url, - "url": user.url, - }, + "user": {"username": user.username, "avatar_url": user.avatar_url, "url": user.url,}, } async def get_user_info(self) -> dict: @@ -657,13 +639,7 @@ async def get_user_info(self) -> dict: except InvalidConfigError: return None else: - return { - "user": { - "username": user.username, - "avatar_url": user.avatar_url, - "url": user.url, - } - } + return {"user": {"username": user.username, "avatar_url": user.avatar_url, "url": user.url,}} class PluginDatabaseClient: diff --git a/core/models.py b/core/models.py index 76889809f7..19c8ccf1fc 100644 --- a/core/models.py +++ b/core/models.py @@ -87,9 +87,7 @@ def line(self, level="info"): level = logging.INFO if self.isEnabledFor(level): self._log( - level, - Fore.BLACK + Style.BRIGHT + "-------------------------" + Style.RESET_ALL, - [], + level, Fore.BLACK + Style.BRIGHT + "-------------------------" + Style.RESET_ALL, [], ) @@ -130,8 +128,7 @@ def configure_logging(name, level=None): ch_debug = RotatingFileHandler(name, mode="a+", maxBytes=78000, backupCount=10) formatter_debug = FileFormatter( - "%(asctime)s %(name)s[%(lineno)d] - %(levelname)s: %(message)s", - datefmt="%Y-%m-%d %H:%M:%S", + "%(asctime)s %(name)s[%(lineno)d] - %(levelname)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) ch_debug.setFormatter(formatter_debug) ch_debug.setLevel(logging.DEBUG) diff --git a/core/thread.py b/core/thread.py index 41d2cd96ba..7c49ee7425 100644 --- a/core/thread.py +++ b/core/thread.py @@ -181,9 +181,7 @@ async def send_recipient_genesis_message(): thread_creation_response = self.bot.config["thread_creation_response"] embed = discord.Embed( - color=self.bot.mod_color, - description=thread_creation_response, - timestamp=channel.created_at, + color=self.bot.mod_color, description=thread_creation_response, timestamp=channel.created_at, ) recipient_thread_close = self.bot.config.get("recipient_thread_close") @@ -460,10 +458,7 @@ async def _close(self, closer, silent=False, delete_channel=True, message=None, # Thread closed message - embed = discord.Embed( - title=self.bot.config["thread_close_title"], - color=self.bot.error_color, - ) + embed = discord.Embed(title=self.bot.config["thread_close_title"], color=self.bot.error_color,) if self.bot.config["show_timestamp"]: embed.timestamp = datetime.utcnow() @@ -695,11 +690,7 @@ async def note(self, message: discord.Message, persistent=False, thread_creation raise MissingRequiredArgument(SimpleNamespace(name="msg")) msg = await self.send( - message, - self.channel, - note=True, - persistent_note=persistent, - thread_creation=thread_creation, + message, self.channel, note=True, persistent_note=persistent, thread_creation=thread_creation, ) self.bot.loop.create_task( @@ -724,11 +715,7 @@ async def reply(self, message: discord.Message, anonymous: bool = False, plain: try: user_msg = await self.send( - message, - destination=self.recipient, - from_mod=True, - anonymous=anonymous, - plain=plain, + message, destination=self.recipient, from_mod=True, anonymous=anonymous, plain=plain, ) except Exception as e: logger.error("Message delivery failed:", exc_info=True) @@ -747,10 +734,7 @@ async def reply(self, message: discord.Message, anonymous: bool = False, plain: ) tasks.append( message.channel.send( - embed=discord.Embed( - color=self.bot.error_color, - description=description, - ) + embed=discord.Embed(color=self.bot.error_color, description=description,) ) ) else: @@ -774,8 +758,7 @@ async def reply(self, message: discord.Message, anonymous: bool = False, plain: tasks.append( self.channel.send( embed=discord.Embed( - color=self.bot.error_color, - description="Scheduled close has been cancelled.", + color=self.bot.error_color, description="Scheduled close has been cancelled.", ) ) ) @@ -806,8 +789,7 @@ async def send( self.bot.loop.create_task( self.channel.send( embed=discord.Embed( - color=self.bot.error_color, - description="Scheduled close has been cancelled.", + color=self.bot.error_color, description="Scheduled close has been cancelled.", ) ) ) From 8db612b1b983e895860835233af1b46fbca8aea2 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Thu, 22 Jul 2021 17:08:17 +0800 Subject: [PATCH 148/209] formatting --- Pipfile | 2 +- bot.py | 49 +++++++++++++++++++++------ cogs/modmail.py | 84 ++++++++++++++++++++++++++++++++------------- cogs/plugins.py | 19 +++++++---- cogs/utility.py | 86 ++++++++++++++++++++++++++++++++++++----------- core/changelog.py | 10 ++++-- core/checks.py | 4 ++- core/clients.py | 34 ++++++++++++++++--- core/models.py | 7 ++-- core/thread.py | 32 ++++++++++++++---- 10 files changed, 249 insertions(+), 78 deletions(-) diff --git a/Pipfile b/Pipfile index 9c7b4e03b7..4ed01f5b70 100644 --- a/Pipfile +++ b/Pipfile @@ -17,7 +17,7 @@ isodate = "~=0.6.0" motor = "~=2.4.0" natural = "~=0.2.0" parsedatetime = "~=2.6" -pymongo = {version = "*", extras = ['srv']} # Required by motor +pymongo = {extras = ["srv"], version = "*"} # Required by motor python-dateutil = "~=2.8.1" python-dotenv = "~=0.18.0" uvloop = {version = ">=0.15.2", markers = "sys_platform != 'win32'"} diff --git a/bot.py b/bot.py index 131032ec80..1deb9b7d2a 100644 --- a/bot.py +++ b/bot.py @@ -506,7 +506,8 @@ def command_perm(self, command_name: str) -> PermissionLevel: logger.debug("Command %s not found.", command_name) return PermissionLevel.INVALID level = next( - (check.permission_level for check in command.checks if hasattr(check, "permission_level")), None, + (check.permission_level for check in command.checks if hasattr(check, "permission_level")), + None, ) if level is None: logger.debug("Command %s does not have a permission level.", command_name) @@ -609,7 +610,13 @@ async def on_ready(self): if self.config.get("data_collection"): self.metadata_loop = tasks.Loop( - self.post_metadata, seconds=0, minutes=0, hours=1, count=None, reconnect=True, loop=None, + self.post_metadata, + seconds=0, + minutes=0, + hours=1, + count=None, + reconnect=True, + loop=None, ) self.metadata_loop.before_loop(self.before_post_metadata) self.metadata_loop.start() @@ -762,7 +769,8 @@ def check_manual_blocked(self, author: discord.Member) -> bool: end_time = re.search(r"%([^%]+?)%", blocked_reason) if end_time is not None: logger.warning( - r"Deprecated time message for user %s, block and unblock again to update.", author.name, + r"Deprecated time message for user %s, block and unblock again to update.", + author.name, ) if end_time is not None: @@ -783,7 +791,11 @@ async def _process_blocked(self, message): return False async def is_blocked( - self, author: discord.User, *, channel: discord.TextChannel = None, send_message: bool = False, + self, + author: discord.User, + *, + channel: discord.TextChannel = None, + send_message: bool = False, ) -> typing.Tuple[bool, str]: member = self.guild.get_member(author.id) @@ -814,7 +826,9 @@ async def is_blocked( if send_message: await channel.send( embed=discord.Embed( - title="Message not sent!", description=new_reason, color=self.error_color, + title="Message not sent!", + description=new_reason, + color=self.error_color, ) ) return True @@ -913,7 +927,8 @@ async def process_dm_modmail(self, message: discord.Message) -> None: description=self.config["disabled_current_thread_response"], ) embed.set_footer( - text=self.config["disabled_current_thread_footer"], icon_url=self.guild.icon_url, + text=self.config["disabled_current_thread_footer"], + icon_url=self.guild.icon_url, ) logger.info("A message was blocked from %s due to disabled Modmail.", message.author) await self.add_reaction(message, blocked_emoji) @@ -1274,14 +1289,18 @@ async def on_raw_reaction_add(self, payload): await message.remove_reaction(payload.emoji, member) await message.add_reaction(emoji_fmt) # bot adds as well - if self.config["dm_disabled"] in (DMDisabled.NEW_THREADS, DMDisabled.ALL_THREADS,): + if self.config["dm_disabled"] in ( + DMDisabled.NEW_THREADS, + DMDisabled.ALL_THREADS, + ): embed = discord.Embed( title=self.config["disabled_new_thread_title"], color=self.error_color, description=self.config["disabled_new_thread_response"], ) embed.set_footer( - text=self.config["disabled_new_thread_footer"], icon_url=self.guild.icon_url, + text=self.config["disabled_new_thread_footer"], + icon_url=self.guild.icon_url, ) logger.info( "A new thread using react to contact was blocked from %s due to disabled Modmail.", @@ -1340,7 +1359,9 @@ async def on_member_remove(self, member): if thread: if self.config["close_on_leave"]: await thread.close( - closer=member.guild.me, message=self.config["close_on_leave_reason"], silent=True, + closer=member.guild.me, + message=self.config["close_on_leave_reason"], + silent=True, ) else: embed = discord.Embed( @@ -1523,7 +1544,9 @@ async def autoupdate(self): commit_data = data["data"] user = data["user"] embed.set_author( - name=user["username"] + " - Updating Bot", icon_url=user["avatar_url"], url=user["url"], + name=user["username"] + " - Updating Bot", + icon_url=user["avatar_url"], + url=user["url"], ) embed.set_footer(text=f"Updating Modmail v{self.version} " f"-> v{latest.version}") @@ -1552,7 +1575,11 @@ async def autoupdate(self): pass command = "git pull" - proc = await asyncio.create_subprocess_shell(command, stderr=PIPE, stdout=PIPE,) + proc = await asyncio.create_subprocess_shell( + command, + stderr=PIPE, + stdout=PIPE, + ) err = await proc.stderr.read() err = err.decode("utf-8").rstrip() res = await proc.stdout.read() diff --git a/cogs/modmail.py b/cogs/modmail.py index 0db8a85e2e..0e41b76677 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -50,7 +50,9 @@ async def setup(self, ctx): if self.bot.modmail_guild is None: embed = discord.Embed( - title="Error", description="Modmail functioning guild not found.", color=self.bot.error_color, + title="Error", + description="Modmail functioning guild not found.", + color=self.bot.error_color, ) return await ctx.send(embed=embed) @@ -182,7 +184,9 @@ async def snippet_raw(self, ctx, *, name: str.lower): else: val = truncate(escape_code_block(val), 2048 - 7) embed = discord.Embed( - title=f'Raw snippet - "{name}":', description=f"```\n{val}```", color=self.bot.main_color, + title=f'Raw snippet - "{name}":', + description=f"```\n{val}```", + color=self.bot.main_color, ) return await ctx.send(embed=embed) @@ -204,7 +208,9 @@ async def snippet_add(self, ctx, name: str.lower, *, value: commands.clean_conte """ if name in self.bot.snippets: embed = discord.Embed( - title="Error", color=self.bot.error_color, description=f"Snippet `{name}` already exists.", + title="Error", + color=self.bot.error_color, + description=f"Snippet `{name}` already exists.", ) return await ctx.send(embed=embed) @@ -228,7 +234,9 @@ async def snippet_add(self, ctx, name: str.lower, *, value: commands.clean_conte await self.bot.config.update() embed = discord.Embed( - title="Added snippet", color=self.bot.main_color, description="Successfully created snippet.", + title="Added snippet", + color=self.bot.main_color, + description="Successfully created snippet.", ) return await ctx.send(embed=embed) @@ -445,7 +453,8 @@ async def notify(self, ctx, *, user_or_role: Union[discord.Role, User, str.lower if mention in mentions: embed = discord.Embed( - color=self.bot.error_color, description=f"{mention} is already going to be mentioned.", + color=self.bot.error_color, + description=f"{mention} is already going to be mentioned.", ) else: mentions.append(mention) @@ -480,7 +489,8 @@ async def unnotify(self, ctx, *, user_or_role: Union[discord.Role, User, str.low if mention not in mentions: embed = discord.Embed( - color=self.bot.error_color, description=f"{mention} does not have a pending notification.", + color=self.bot.error_color, + description=f"{mention} does not have a pending notification.", ) else: mentions.remove(mention) @@ -516,7 +526,8 @@ async def subscribe(self, ctx, *, user_or_role: Union[discord.Role, User, str.lo if mention in mentions: embed = discord.Embed( - color=self.bot.error_color, description=f"{mention} is already subscribed to this thread.", + color=self.bot.error_color, + description=f"{mention} is already subscribed to this thread.", ) else: mentions.append(mention) @@ -551,13 +562,15 @@ async def unsubscribe(self, ctx, *, user_or_role: Union[discord.Role, User, str. if mention not in mentions: embed = discord.Embed( - color=self.bot.error_color, description=f"{mention} is not subscribed to this thread.", + color=self.bot.error_color, + description=f"{mention} is not subscribed to this thread.", ) else: mentions.remove(mention) await self.bot.config.update() embed = discord.Embed( - color=self.bot.main_color, description=f"{mention} is now unsubscribed from this thread.", + color=self.bot.main_color, + description=f"{mention} is now unsubscribed from this thread.", ) return await ctx.send(embed=embed) @@ -684,7 +697,8 @@ async def logs(self, ctx, *, user: User = None): if not any(not log["open"] for log in logs): embed = discord.Embed( - color=self.bot.error_color, description="This user does not have any previous logs.", + color=self.bot.error_color, + description="This user does not have any previous logs.", ) return await ctx.send(embed=embed) @@ -711,7 +725,8 @@ async def logs_closed_by(self, ctx, *, user: User = None): if not embeds: embed = discord.Embed( - color=self.bot.error_color, description="No log entries have been found for that query.", + color=self.bot.error_color, + description="No log entries have been found for that query.", ) return await ctx.send(embed=embed) @@ -730,7 +745,9 @@ async def logs_delete(self, ctx, key_or_link: str): if not success: embed = discord.Embed( - title="Error", description=f"Log entry `{key}` not found.", color=self.bot.error_color, + title="Error", + description=f"Log entry `{key}` not found.", + color=self.bot.error_color, ) else: embed = discord.Embed( @@ -783,7 +800,8 @@ async def logs_search(self, ctx, limit: Optional[int] = None, *, query): if not embeds: embed = discord.Embed( - color=self.bot.error_color, description="No log entries have been found for that query.", + color=self.bot.error_color, + description="No log entries have been found for that query.", ) return await ctx.send(embed=embed) @@ -994,7 +1012,10 @@ async def contact( else: thread = await self.bot.threads.create( - recipient=user, creator=ctx.author, category=category, manual_trigger=manual_trigger, + recipient=user, + creator=ctx.author, + category=category, + manual_trigger=manual_trigger, ) if thread.cancelled: return @@ -1008,7 +1029,11 @@ async def contact( else: description = f"{ctx.author.name} has opened a Modmail thread." - em = discord.Embed(title="New Thread", description=description, color=self.bot.main_color,) + em = discord.Embed( + title="New Thread", + description=description, + color=self.bot.main_color, + ) if self.bot.config["show_timestamp"]: em.timestamp = datetime.utcnow() em.set_footer(icon_url=ctx.author.avatar_url) @@ -1050,7 +1075,8 @@ async def blocked(self, ctx): end_time = re.search(r"%([^%]+?)%", reason) if end_time is not None: logger.warning( - r"Deprecated time message for user %s, block and unblock again to update.", id_, + r"Deprecated time message for user %s, block and unblock again to update.", + id_, ) if end_time is not None: @@ -1081,7 +1107,8 @@ async def blocked(self, ctx): end_time = re.search(r"%([^%]+?)%", reason) if end_time is not None: logger.warning( - r"Deprecated time message for role %s, block and unblock again to update.", id_, + r"Deprecated time message for role %s, block and unblock again to update.", + id_, ) if end_time is not None: @@ -1103,7 +1130,9 @@ async def blocked(self, ctx): line = mention + f" - {reason or 'No Reason Provided'}\n" if len(embed.description) + len(line) > 2048: embed = discord.Embed( - title="Blocked Users (Continued)", color=self.bot.main_color, description=line, + title="Blocked Users (Continued)", + color=self.bot.main_color, + description=line, ) embeds.append(embed) else: @@ -1120,7 +1149,9 @@ async def blocked(self, ctx): line = mention + f" - {reason or 'No Reason Provided'}\n" if len(embed.description) + len(line) > 2048: embed = discord.Embed( - title="Blocked Roles (Continued)", color=self.bot.main_color, description=line, + title="Blocked Roles (Continued)", + color=self.bot.main_color, + description=line, ) embeds.append(embed) else: @@ -1180,7 +1211,9 @@ async def blocked_whitelist(self, ctx, *, user: User = None): ) else: embed = discord.Embed( - title="Success", color=self.bot.main_color, description=f"{mention} is now whitelisted.", + title="Success", + color=self.bot.main_color, + description=f"{mention} is now whitelisted.", ) return await ctx.send(embed=embed) @@ -1258,7 +1291,9 @@ async def block( ) else: embed = discord.Embed( - title="Success", color=self.bot.main_color, description=f"{mention} is now blocked {reason}", + title="Success", + color=self.bot.main_color, + description=f"{mention} is now blocked {reason}", ) if isinstance(user_or_role, discord.Role): @@ -1321,7 +1356,9 @@ async def unblock(self, ctx, *, user_or_role: Union[User, Role] = None): await self.bot.config.update() embed = discord.Embed( - title="Success", color=self.bot.main_color, description=f"{mention} is no longer blocked.", + title="Success", + color=self.bot.main_color, + description=f"{mention} is no longer blocked.", ) else: embed = discord.Embed( @@ -1378,7 +1415,8 @@ async def repair(self, ctx): # Search cache for channel user_id, thread = next( - ((k, v) for k, v in self.bot.threads.cache.items() if v.channel == ctx.channel), (-1, None), + ((k, v) for k, v in self.bot.threads.cache.items() if v.channel == ctx.channel), + (-1, None), ) if thread is not None: logger.debug("Found thread with tempered ID.") diff --git a/cogs/plugins.py b/cogs/plugins.py index a9a4d247da..aab5dc8bd6 100644 --- a/cogs/plugins.py +++ b/cogs/plugins.py @@ -159,7 +159,9 @@ async def initial_load_plugins(self): except Exception: self.bot.config["plugins"].remove(plugin_name) logger.error( - "Error when loading plugin %s. Plugin removed from config.", plugin, exc_info=True, + "Error when loading plugin %s. Plugin removed from config.", + plugin, + exc_info=True, ) continue @@ -276,7 +278,8 @@ async def parse_user_input(self, ctx, plugin_name, check_version=False): if not self._ready_event.is_set(): embed = discord.Embed( - description="Plugins are still loading, please try again later.", color=self.bot.main_color, + description="Plugins are still loading, please try again later.", + color=self.bot.main_color, ) await ctx.send(embed=embed) return @@ -346,17 +349,20 @@ async def plugins_add(self, ctx, *, plugin_name: str): if plugin.name in self.bot.cogs: # another class with the same name embed = discord.Embed( - description="Cannot install this plugin (dupe cog name).", color=self.bot.error_color, + description="Cannot install this plugin (dupe cog name).", + color=self.bot.error_color, ) return await ctx.send(embed=embed) if plugin.local: embed = discord.Embed( - description=f"Starting to load local plugin from {plugin.link}...", color=self.bot.main_color, + description=f"Starting to load local plugin from {plugin.link}...", + color=self.bot.main_color, ) else: embed = discord.Embed( - description=f"Starting to download plugin from {plugin.link}...", color=self.bot.main_color, + description=f"Starting to download plugin from {plugin.link}...", + color=self.bot.main_color, ) msg = await ctx.send(embed=embed) @@ -556,7 +562,8 @@ async def plugins_loaded(self, ctx): if not self._ready_event.is_set(): embed = discord.Embed( - description="Plugins are still loading, please try again later.", color=self.bot.main_color, + description="Plugins are still loading, please try again later.", + color=self.bot.main_color, ) return await ctx.send(embed=embed) diff --git a/cogs/utility.py b/cogs/utility.py index 6f6ed89b47..759b573748 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -305,7 +305,9 @@ async def about(self, ctx): """Shows information about this bot.""" embed = discord.Embed(color=self.bot.main_color, timestamp=datetime.utcnow()) embed.set_author( - name="Modmail - About", icon_url=self.bot.user.avatar_url, url="https://discord.gg/F34cRU8", + name="Modmail - About", + icon_url=self.bot.user.avatar_url, + url="https://discord.gg/F34cRU8", ) embed.set_thumbnail(url=self.bot.user.avatar_url) @@ -386,7 +388,8 @@ async def debug(self, ctx): log_file_name = self.bot.token.split(".")[0] with open( - os.path.join(os.path.dirname(os.path.abspath(__file__)), f"../temp/{log_file_name}.log"), "r+", + os.path.join(os.path.dirname(os.path.abspath(__file__)), f"../temp/{log_file_name}.log"), + "r+", ) as f: logs = f.read().strip() @@ -438,7 +441,8 @@ async def debug_hastebin(self, ctx): log_file_name = self.bot.token.split(".")[0] with open( - os.path.join(os.path.dirname(os.path.abspath(__file__)), f"../temp/{log_file_name}.log"), "rb+", + os.path.join(os.path.dirname(os.path.abspath(__file__)), f"../temp/{log_file_name}.log"), + "rb+", ) as f: logs = BytesIO(f.read().strip()) @@ -451,7 +455,9 @@ async def debug_hastebin(self, ctx): logger.error(data["message"]) raise embed = discord.Embed( - title="Debug Logs", color=self.bot.main_color, description=f"{haste_url}/" + key, + title="Debug Logs", + color=self.bot.main_color, + description=f"{haste_url}/" + key, ) except (JSONDecodeError, ClientResponseError, IndexError, KeyError): embed = discord.Embed( @@ -471,7 +477,8 @@ async def debug_clear(self, ctx): log_file_name = self.bot.token.split(".")[0] with open( - os.path.join(os.path.dirname(os.path.abspath(__file__)), f"../temp/{log_file_name}.log"), "w", + os.path.join(os.path.dirname(os.path.abspath(__file__)), f"../temp/{log_file_name}.log"), + "w", ): pass await ctx.send( @@ -686,12 +693,14 @@ async def mention(self, ctx, *user_or_role: Union[discord.Role, discord.Member, option = user_or_role[0].lower() if option == "disable": embed = discord.Embed( - description=f"Disabled mention on thread creation.", color=self.bot.main_color, + description=f"Disabled mention on thread creation.", + color=self.bot.main_color, ) self.bot.config["mention"] = None else: embed = discord.Embed( - description="`mention` is reset to default.", color=self.bot.main_color, + description="`mention` is reset to default.", + color=self.bot.main_color, ) self.bot.config.remove("mention") await self.bot.config.update() @@ -766,7 +775,9 @@ async def config_options(self, ctx): for names in zip_longest(*(iter(sorted(self.bot.config.public_keys)),) * 15): description = "\n".join(f"`{name}`" for name in takewhile(lambda x: x is not None, names)) embed = discord.Embed( - title="Available configuration keys:", color=self.bot.main_color, description=description, + title="Available configuration keys:", + color=self.bot.main_color, + description=description, ) embeds.append(embed) @@ -809,7 +820,9 @@ async def config_remove(self, ctx, *, key: str.lower): self.bot.config.remove(key) await self.bot.config.update() embed = discord.Embed( - title="Success", color=self.bot.main_color, description=f"`{key}` had been reset to default.", + title="Success", + color=self.bot.main_color, + description=f"`{key}` had been reset to default.", ) else: embed = discord.Embed( @@ -838,7 +851,9 @@ async def config_get(self, ctx, *, key: str.lower = None): else: embed = discord.Embed( - title="Error", color=self.bot.error_color, description=f"`{key}` is an invalid key.", + title="Error", + color=self.bot.error_color, + description=f"`{key}` is an invalid key.", ) embed.set_footer( text=f'Type "{self.bot.prefix}config options" for a list of config variables.' @@ -871,7 +886,9 @@ async def config_help(self, ctx, key: str.lower = None): key, {**self.bot.config.public_keys, **self.bot.config.protected_keys} ) embed = discord.Embed( - title="Error", color=self.bot.error_color, description=f"`{key}` is an invalid key.", + title="Error", + color=self.bot.error_color, + description=f"`{key}` is an invalid key.", ) if closest: embed.add_field(name=f"Perhaps you meant:", value="\n".join(f"`{x}`" for x in closest)) @@ -881,7 +898,9 @@ async def config_help(self, ctx, key: str.lower = None): if key is not None and key not in config_help: embed = discord.Embed( - title="Error", color=self.bot.error_color, description=f"No help details found for `{key}`.", + title="Error", + color=self.bot.error_color, + description=f"No help details found for `{key}`.", ) return await ctx.send(embed=embed) @@ -973,7 +992,9 @@ async def alias(self, ctx, *, name: str.lower = None): embeds = [] for i, val in enumerate(values, start=1): embed = discord.Embed( - color=self.bot.main_color, title=f'Alias - "{name}" - Step {i}:', description=val, + color=self.bot.main_color, + title=f'Alias - "{name}" - Step {i}:', + description=val, ) embeds += [embed] session = EmbedPaginatorSession(ctx, *embeds) @@ -1248,7 +1269,9 @@ async def permissions_override(self, ctx, command_name: str.lower, *, level_name ) else: logger.info( - "Updated command permission level for `%s` to `%s`.", command.qualified_name, level.name, + "Updated command permission level for `%s` to `%s`.", + command.qualified_name, + level.name, ) self.bot.config["override_command_level"][command.qualified_name] = level.name @@ -1264,7 +1287,12 @@ async def permissions_override(self, ctx, command_name: str.lower, *, level_name @permissions.command(name="add", usage="[command/level] [name] [user/role]") @checks.has_permissions(PermissionLevel.OWNER) async def permissions_add( - self, ctx, type_: str.lower, name: str, *, user_or_role: Union[discord.Role, utils.User, str], + self, + ctx, + type_: str.lower, + name: str, + *, + user_or_role: Union[discord.Role, utils.User, str], ): """ Add a permission to a command or a permission level. @@ -1333,7 +1361,12 @@ async def permissions_add( ) @checks.has_permissions(PermissionLevel.OWNER) async def permissions_remove( - self, ctx, type_: str.lower, name: str, *, user_or_role: Union[discord.Role, utils.User, str] = None, + self, + ctx, + type_: str.lower, + name: str, + *, + user_or_role: Union[discord.Role, utils.User, str] = None, ): """ Remove permission to use a command, permission level, or command level override. @@ -1827,7 +1860,11 @@ async def autotrigger_list(self, ctx): embeds = [] for keyword in self.bot.auto_triggers: command = self.bot.auto_triggers[keyword] - embed = discord.Embed(title=keyword, color=self.bot.main_color, description=command,) + embed = discord.Embed( + title=keyword, + color=self.bot.main_color, + description=command, + ) embeds.append(embed) if not embeds: @@ -1928,7 +1965,11 @@ async def update(self, ctx, *, flag: str = ""): command = "git pull" - proc = await asyncio.create_subprocess_shell(command, stderr=PIPE, stdout=PIPE,) + proc = await asyncio.create_subprocess_shell( + command, + stderr=PIPE, + stdout=PIPE, + ) err = await proc.stderr.read() err = err.decode("utf-8").rstrip() res = await proc.stdout.read() @@ -1941,7 +1982,10 @@ async def update(self, ctx, *, flag: str = ""): elif res != "Already up to date.": logger.info("Bot has been updated.") - embed = discord.Embed(title="Bot has been updated", color=self.bot.main_color,) + embed = discord.Embed( + title="Bot has been updated", + color=self.bot.main_color, + ) embed.set_footer(text=f"Updating Modmail v{self.bot.version} " f"-> v{latest.version}") embed.description = latest.description for name, value in latest.fields.items(): @@ -1956,7 +2000,9 @@ async def update(self, ctx, *, flag: str = ""): return await self.bot.close() else: embed = discord.Embed( - title="Already up to date", description=desc, color=self.bot.main_color, + title="Already up to date", + description=desc, + color=self.bot.main_color, ) embed.set_footer(text="Force update") await ctx.send(embed=embed) diff --git a/core/changelog.py b/core/changelog.py index 5c23020eb6..7c9af2e1bb 100644 --- a/core/changelog.py +++ b/core/changelog.py @@ -89,7 +89,9 @@ def embed(self) -> Embed: """ embed = Embed(color=self.bot.main_color, description=self.description) embed.set_author( - name=f"v{self.version} - Changelog", icon_url=self.bot.user.avatar_url, url=self.url, + name=f"v{self.version} - Changelog", + icon_url=self.bot.user.avatar_url, + url=self.url, ) for name, value in self.fields.items(): @@ -168,7 +170,11 @@ async def from_url(cls, bot, url: str = "") -> "Changelog": The newly created `Changelog` parsed from the `url`. """ # get branch via git cli if available - proc = await asyncio.create_subprocess_shell("git branch --show-current", stderr=PIPE, stdout=PIPE,) + proc = await asyncio.create_subprocess_shell( + "git branch --show-current", + stderr=PIPE, + stdout=PIPE, + ) err = await proc.stderr.read() err = err.decode("utf-8").rstrip() res = await proc.stdout.read() diff --git a/core/checks.py b/core/checks.py index 74b7dc38cd..1079b55530 100644 --- a/core/checks.py +++ b/core/checks.py @@ -5,7 +5,9 @@ logger = getLogger(__name__) -def has_permissions_predicate(permission_level: PermissionLevel = PermissionLevel.REGULAR,): +def has_permissions_predicate( + permission_level: PermissionLevel = PermissionLevel.REGULAR, +): async def predicate(ctx): return await check_permissions(ctx, ctx.command.qualified_name) diff --git a/core/clients.py b/core/clients.py index 61fcfdb487..5c5acc8fad 100644 --- a/core/clients.py +++ b/core/clients.py @@ -321,7 +321,12 @@ async def edit_message(self, message_id: Union[int, str], new_content: str) -> N return NotImplemented async def append_log( - self, message: Message, *, message_id: str = "", channel_id: str = "", type_: str = "thread_message", + self, + message: Message, + *, + message_id: str = "", + channel_id: str = "", + type_: str = "thread_message", ) -> dict: return NotImplemented @@ -543,7 +548,12 @@ async def edit_message(self, message_id: Union[int, str], new_content: str) -> N ) async def append_log( - self, message: Message, *, message_id: str = "", channel_id: str = "", type_: str = "thread_message", + self, + message: Message, + *, + message_id: str = "", + channel_id: str = "", + type_: str = "thread_message", ) -> dict: channel_id = str(channel_id) or str(message.channel.id) message_id = str(message_id) or str(message.id) @@ -589,7 +599,11 @@ async def search_closed_by(self, user_id: Union[int, str]): async def search_by_text(self, text: str, limit: Optional[int]): return await self.bot.db.logs.find( - {"guild_id": str(self.bot.guild_id), "open": False, "$text": {"$search": f'"{text}"'},}, + { + "guild_id": str(self.bot.guild_id), + "open": False, + "$text": {"$search": f'"{text}"'}, + }, {"messages": {"$slice": 5}}, ).to_list(limit) @@ -630,7 +644,11 @@ async def update_repository(self) -> dict: data = await user.update_repository() return { "data": data, - "user": {"username": user.username, "avatar_url": user.avatar_url, "url": user.url,}, + "user": { + "username": user.username, + "avatar_url": user.avatar_url, + "url": user.url, + }, } async def get_user_info(self) -> dict: @@ -639,7 +657,13 @@ async def get_user_info(self) -> dict: except InvalidConfigError: return None else: - return {"user": {"username": user.username, "avatar_url": user.avatar_url, "url": user.url,}} + return { + "user": { + "username": user.username, + "avatar_url": user.avatar_url, + "url": user.url, + } + } class PluginDatabaseClient: diff --git a/core/models.py b/core/models.py index 19c8ccf1fc..76889809f7 100644 --- a/core/models.py +++ b/core/models.py @@ -87,7 +87,9 @@ def line(self, level="info"): level = logging.INFO if self.isEnabledFor(level): self._log( - level, Fore.BLACK + Style.BRIGHT + "-------------------------" + Style.RESET_ALL, [], + level, + Fore.BLACK + Style.BRIGHT + "-------------------------" + Style.RESET_ALL, + [], ) @@ -128,7 +130,8 @@ def configure_logging(name, level=None): ch_debug = RotatingFileHandler(name, mode="a+", maxBytes=78000, backupCount=10) formatter_debug = FileFormatter( - "%(asctime)s %(name)s[%(lineno)d] - %(levelname)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S", + "%(asctime)s %(name)s[%(lineno)d] - %(levelname)s: %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", ) ch_debug.setFormatter(formatter_debug) ch_debug.setLevel(logging.DEBUG) diff --git a/core/thread.py b/core/thread.py index 7c49ee7425..41d2cd96ba 100644 --- a/core/thread.py +++ b/core/thread.py @@ -181,7 +181,9 @@ async def send_recipient_genesis_message(): thread_creation_response = self.bot.config["thread_creation_response"] embed = discord.Embed( - color=self.bot.mod_color, description=thread_creation_response, timestamp=channel.created_at, + color=self.bot.mod_color, + description=thread_creation_response, + timestamp=channel.created_at, ) recipient_thread_close = self.bot.config.get("recipient_thread_close") @@ -458,7 +460,10 @@ async def _close(self, closer, silent=False, delete_channel=True, message=None, # Thread closed message - embed = discord.Embed(title=self.bot.config["thread_close_title"], color=self.bot.error_color,) + embed = discord.Embed( + title=self.bot.config["thread_close_title"], + color=self.bot.error_color, + ) if self.bot.config["show_timestamp"]: embed.timestamp = datetime.utcnow() @@ -690,7 +695,11 @@ async def note(self, message: discord.Message, persistent=False, thread_creation raise MissingRequiredArgument(SimpleNamespace(name="msg")) msg = await self.send( - message, self.channel, note=True, persistent_note=persistent, thread_creation=thread_creation, + message, + self.channel, + note=True, + persistent_note=persistent, + thread_creation=thread_creation, ) self.bot.loop.create_task( @@ -715,7 +724,11 @@ async def reply(self, message: discord.Message, anonymous: bool = False, plain: try: user_msg = await self.send( - message, destination=self.recipient, from_mod=True, anonymous=anonymous, plain=plain, + message, + destination=self.recipient, + from_mod=True, + anonymous=anonymous, + plain=plain, ) except Exception as e: logger.error("Message delivery failed:", exc_info=True) @@ -734,7 +747,10 @@ async def reply(self, message: discord.Message, anonymous: bool = False, plain: ) tasks.append( message.channel.send( - embed=discord.Embed(color=self.bot.error_color, description=description,) + embed=discord.Embed( + color=self.bot.error_color, + description=description, + ) ) ) else: @@ -758,7 +774,8 @@ async def reply(self, message: discord.Message, anonymous: bool = False, plain: tasks.append( self.channel.send( embed=discord.Embed( - color=self.bot.error_color, description="Scheduled close has been cancelled.", + color=self.bot.error_color, + description="Scheduled close has been cancelled.", ) ) ) @@ -789,7 +806,8 @@ async def send( self.bot.loop.create_task( self.channel.send( embed=discord.Embed( - color=self.bot.error_color, description="Scheduled close has been cancelled.", + color=self.bot.error_color, + description="Scheduled close has been cancelled.", ) ) ) From cf65018c5bea0228a8ce38dd112308e7ea6e9958 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Thu, 22 Jul 2021 22:45:01 +0800 Subject: [PATCH 149/209] Fix SSL Error --- core/clients.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/core/clients.py b/core/clients.py index 0a527b59ce..667faf71bd 100644 --- a/core/clients.py +++ b/core/clients.py @@ -417,10 +417,14 @@ async def validate_database_connection(self, *, ssl_retry=True): if mongo_uri is None: mongo_uri = self.bot.config["mongo_uri"] for _ in range(3): - logger.warning("FAILED TO VERIFY SSL CERTIFICATE, ATTEMPTING TO START WITHOUT SSL (UNSAFE).") - logger.warning("To fix this warning, check there's no proxies blocking SSL cert verification, " - "run \"Certificate.command\" on MacOS, " - "and check certifi is up to date \"pip3 install --upgrade certifi\".") + logger.warning( + "FAILED TO VERIFY SSL CERTIFICATE, ATTEMPTING TO START WITHOUT SSL (UNSAFE)." + ) + logger.warning( + "To fix this warning, check there's no proxies blocking SSL cert verification, " + 'run "Certificate.command" on MacOS, ' + 'and check certifi is up to date "pip3 install --upgrade certifi".' + ) self.db = AsyncIOMotorClient(mongo_uri, tlsAllowInvalidCertificates=True).modmail_bot return await self.validate_database_connection(ssl_retry=False) if "ServerSelectionTimeoutError" in message: From 22fd835a76f48608f59146ac35776e2aace8b70b Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Thu, 22 Jul 2021 22:46:26 +0800 Subject: [PATCH 150/209] Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb9ee01487..67bdc5a892 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,13 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. -# v3.9.5dev1 +# v3.9.5 -## Misc +## Internal - Bumped discord.py to v1.7.3, updated all other packages to latest. - More debug log files are now kept. +- Resolve SSL errors by retrying without SSL # v3.9.4 From 52c0fb3d5107c361a7317f1b8b5174c6cdc40202 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Fri, 23 Jul 2021 00:03:27 +0800 Subject: [PATCH 151/209] bump v --- bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot.py b/bot.py index 1deb9b7d2a..1c4fdd5354 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.9.5dev1" +__version__ = "3.9.5" import asyncio From c7514b1899c60d4456ab339199d15883f0f49c69 Mon Sep 17 00:00:00 2001 From: Matt Nikkel Date: Sun, 25 Jul 2021 23:00:55 -0400 Subject: [PATCH 152/209] Fix failing Docker build --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 232e84fb20..34aba25ce6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,8 +3,8 @@ FROM python:3.9-slim as py FROM py as build RUN apt update && apt install -y g++ -COPY requirements.min.txt / -RUN pip install --prefix=/inst -U -r /requirements.min.txt +COPY requirements.txt / +RUN pip install --prefix=/inst -U -r /requirements.txt FROM py From 3e04dd0627ce9aad9949bad4d600aaf53fe39fe0 Mon Sep 17 00:00:00 2001 From: Jerrie <70805800+Jerrie-Aries@users.noreply.github.com> Date: Sun, 1 Aug 2021 23:23:29 +0800 Subject: [PATCH 153/209] Fix UnicodeEncodeError on Windows. (#3043) * Fix UnionEncodeError on Windows when logging unicode emojis or special characters. * Formatting... --- bot.py | 11 ++++------- cogs/utility.py | 2 ++ core/models.py | 4 +++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/bot.py b/bot.py index 07c654be3c..8961001ab1 100644 --- a/bot.py +++ b/bot.py @@ -122,12 +122,9 @@ def hosting_method(self) -> HostingMethod: def startup(self): logger.line() - if os.name != "nt": - logger.info("┌┬┐┌─┐┌┬┐┌┬┐┌─┐┬┬") - logger.info("││││ │ │││││├─┤││") - logger.info("┴ ┴└─┘─┴┘┴ ┴┴ ┴┴┴─┘") - else: - logger.info("MODMAIL") + logger.info("┌┬┐┌─┐┌┬┐┌┬┐┌─┐┬┬") + logger.info("││││ │ │││││├─┤││") + logger.info("┴ ┴└─┘─┴┘┴ ┴┴ ┴┴┴─┘") logger.info("v%s", __version__) logger.info("Authors: kyb3r, fourjr, Taaku18") logger.line() @@ -659,7 +656,7 @@ async def convert_emoji(self, name: str) -> str: try: name = await converter.convert(ctx, name.strip(":")) except commands.BadArgument as e: - logger.warning("%s is not a valid emoji. %s.", e) + logger.warning("%s is not a valid emoji. %s.", name, e) raise return name diff --git a/cogs/utility.py b/cogs/utility.py index 4ac02b9073..0790274c57 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -393,6 +393,7 @@ async def debug(self, ctx): os.path.dirname(os.path.abspath(__file__)), f"../temp/{log_file_name}.log" ), "r+", + encoding="utf-8", ) as f: logs = f.read().strip() @@ -448,6 +449,7 @@ async def debug_hastebin(self, ctx): os.path.dirname(os.path.abspath(__file__)), f"../temp/{log_file_name}.log" ), "rb+", + encoding="utf-8", ) as f: logs = BytesIO(f.read().strip()) diff --git a/core/models.py b/core/models.py index 0238e051d4..44e75043d1 100644 --- a/core/models.py +++ b/core/models.py @@ -127,7 +127,9 @@ def format(self, record): def configure_logging(name, level=None): global ch_debug, log_level - ch_debug = RotatingFileHandler(name, mode="a+", maxBytes=48000, backupCount=1) + ch_debug = RotatingFileHandler( + name, mode="a+", maxBytes=48000, backupCount=1, encoding="utf-8" + ) formatter_debug = FileFormatter( "%(asctime)s %(name)s[%(lineno)d] - %(levelname)s: %(message)s", From d50ec339ca56f2eb0d3809bc525a66e1740b1fef Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Mon, 2 Aug 2021 09:51:36 +0200 Subject: [PATCH 154/209] Plugins: add Python Discord's MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - **Case insensitive snippets**: Allow snippets to be ran even if with the wrong case. For example, `?Dm-RePort` will be recognized as `?dm-report`. - **Close message:** Add a `?closemessage` command that will close the thread after 15 minutes with a default message. - **MDLink**: Generate a ready to paste link to the thread logs. - **Reply cooldown**: Forbid you from sending the same message twice in ten seconds. - **Tagging**: Add a `?tag` command capable of adding a `$message|` header to the channel name. --- plugins/registry.json | 45 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/plugins/registry.json b/plugins/registry.json index a9f565a013..0ba98f499c 100644 --- a/plugins/registry.json +++ b/plugins/registry.json @@ -1,4 +1,49 @@ { + "case_insensitive_snippets": { + "repository": "python-discord/modmail-plugins", + "branch": "main", + "description": "Allow snippets to be ran even if with the wrong case. For example, ?Dm-RePort will be recognized as ?dm-report.", + "bot_version": "2.20.1", + "title": "Case insensitive snippets", + "icon_url": "https://i.imgur.com/kaJYlMB.png", + "thumbnail_url": "https://i.imgur.com/kaJYlMB.png" + }, + "close_message": { + "repository": "python-discord/modmail-plugins", + "branch": "main", + "description": "Add a ?closemessage command that will close the thread after 15 minutes with a default message.", + "bot_version": "2.20.1", + "title": "Close message", + "icon_url": "https://i.imgur.com/ev7BFMz.png", + "thumbnail_url": "https://i.imgur.com/ev7BFMz.png" + }, + "mdlink": { + "repository": "python-discord/modmail-plugins", + "branch": "main", + "description": "Generate a ready to paste link to the thread logs.", + "bot_version": "2.20.1", + "title": "MDLink", + "icon_url": "https://i.imgur.com/JA2E63R.png", + "thumbnail_url": "https://i.imgur.com/JA2E63R.png" + }, + "reply_cooldown": { + "repository": "python-discord/modmail-plugins", + "branch": "main", + "description": "Forbid you from sending the same message twice in ten seconds.", + "bot_version": "2.20.1", + "title": "Reply cooldown", + "icon_url": "https://i.imgur.com/FtRQveT.png", + "thumbnail_url": "https://i.imgur.com/FtRQveT.png" + }, + "tagging": { + "repository": "python-discord/modmail-plugins", + "branch": "main", + "description": "Add a ?tag command capable of adding a $message| header to the channel name.", + "bot_version": "2.20.1", + "title": "Tagging", + "icon_url": "https://i.imgur.com/WajSLB1.png", + "thumbnail_url": "https://i.imgur.com/WajSLB1.png" + }, "dragory-migrate": { "repository": "kyb3r/modmail-plugins", "branch": "master", From d07b1a8f486a91de1a0105ae5a558360496da910 Mon Sep 17 00:00:00 2001 From: Qwerty-133 <74311372+Qwerty-133@users.noreply.github.com> Date: Wed, 4 Aug 2021 01:31:57 +0530 Subject: [PATCH 155/209] Let snippets be invoked case-insensitively. --- bot.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bot.py b/bot.py index 1c4fdd5354..b140dfe7d1 100644 --- a/bot.py +++ b/bot.py @@ -1132,8 +1132,11 @@ async def process_commands(self, message): cmd = message.content[len(self.prefix) :].strip() # Process snippets - if cmd in self.snippets: - snippet = self.snippets[cmd] + try: + snippet = self.snippets[cmd.lower()] + except KeyError: + pass + else: if self.config["anonymous_snippets"]: message.content = f"{self.prefix}fareply {snippet}" else: From f99245fb0e6fb86211e3e3d832aaf5d346a70d50 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Fri, 6 Aug 2021 18:58:17 +0800 Subject: [PATCH 156/209] Update changelogs --- CHANGELOG.md | 3 ++- README.md | 2 +- bot.py | 2 +- pyproject.toml | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 073db1ef6e..8a9f22988c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. -# v3.10.0-dev3 +# v3.10.0-dev4 ## Added @@ -20,6 +20,7 @@ however, insignificant breaking changes do not guarantee a major version bump, s - Certain situations where the internal thread cache breaks and spams new channels. ([GH #3022](https://github.com/kyb3r/modmail/issues/3022), [PR #3028](https://github.com/kyb3r/modmail/pull/3028)) - Blocked users are now no longer allowed to use `?contact` and react to contact. ([COMMENT #819004157](https://github.com/kyb3r/modmail/issues/2969#issuecomment-819004157), [PR #3027](https://github.com/kyb3r/modmail/pull/3027)) +- UnicodeEncodeError will no longer be raised on Windows. ([PR #3043](https://github.com/kyb3r/modmail/pull/3043)) # v3.9.5 diff --git a/README.md b/README.md index 19931575c9..bd9d0b8f69 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ <<<<<<< HEAD - + ======= >>>>>>> master diff --git a/bot.py b/bot.py index 4c235dedad..837fe304cd 100644 --- a/bot.py +++ b/bot.py @@ -1,5 +1,5 @@ <<<<<<< HEAD -__version__ = "v3.10.0-dev3" +__version__ = "v3.10.0-dev4" ======= __version__ = "3.9.5" >>>>>>> master diff --git a/pyproject.toml b/pyproject.toml index 4bc1a22707..556c7b277c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ extend-exclude = ''' <<<<<<< HEAD [tool.poetry] name = 'Modmail' -version = '3.10.0-dev3' +version = '3.10.0-dev4' description = "Modmail is similar to Reddit's Modmail, both in functionality and purpose. It serves as a shared inbox for server staff to communicate with their users in a seamless way." license = 'AGPL-3.0-only' authors = [ From dc7a61d1e09c27ccda3f40948d3b7ab8a1936e9f Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Fri, 6 Aug 2021 19:00:01 +0800 Subject: [PATCH 157/209] Update changelog --- CHANGELOG.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a9f22988c..8429a26203 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,16 +12,17 @@ however, insignificant breaking changes do not guarantee a major version bump, s - Ability to have group conversations. ([GH #143](https://github.com/kyb3r/modmail/issues/143)) -### Internal - -- `thread.reply` now returns mod_message, user_message1, user_message2... It is no longer limited at a size 2 tuple. Potentially breaking if plugins depend on this behaviour. - ## Fixed - Certain situations where the internal thread cache breaks and spams new channels. ([GH #3022](https://github.com/kyb3r/modmail/issues/3022), [PR #3028](https://github.com/kyb3r/modmail/pull/3028)) - Blocked users are now no longer allowed to use `?contact` and react to contact. ([COMMENT #819004157](https://github.com/kyb3r/modmail/issues/2969#issuecomment-819004157), [PR #3027](https://github.com/kyb3r/modmail/pull/3027)) - UnicodeEncodeError will no longer be raised on Windows. ([PR #3043](https://github.com/kyb3r/modmail/pull/3043)) +## Internal + +- `thread.reply` now returns mod_message, user_message1, user_message2... It is no longer limited at a size 2 tuple. Potentially breaking if plugins depend on this behaviour. +- Fix return types, type hints, and unresolved references ([PR #3009](https://github.com/kyb3r/modmail/pull/3009)) + # v3.9.5 ## Internal From 3358622fa0f997ac49062d23ae772229faf948c0 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Fri, 6 Aug 2021 19:03:03 +0800 Subject: [PATCH 158/209] Improved way to do case insensitive snippets --- bot.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/bot.py b/bot.py index b140dfe7d1..a054bbc83a 100644 --- a/bot.py +++ b/bot.py @@ -1132,11 +1132,9 @@ async def process_commands(self, message): cmd = message.content[len(self.prefix) :].strip() # Process snippets - try: - snippet = self.snippets[cmd.lower()] - except KeyError: - pass - else: + cmd = cmd.lower() + if cmd in self.snippets: + snippet = self.snippets[cmd] if self.config["anonymous_snippets"]: message.content = f"{self.prefix}fareply {snippet}" else: From c00e6be6cd1082a6e2c1ee3fbf4c8983c0caf744 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Fri, 6 Aug 2021 19:14:21 +0800 Subject: [PATCH 159/209] Update changelog and merge issues --- CHANGELOG.md | 2 ++ README.md | 4 ---- bot.py | 14 -------------- cogs/modmail.py | 11 ----------- core/models.py | 4 ---- core/thread.py | 22 ---------------------- pyproject.toml | 5 ----- 7 files changed, 2 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8429a26203..c8ae76fbb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,12 +11,14 @@ however, insignificant breaking changes do not guarantee a major version bump, s ## Added - Ability to have group conversations. ([GH #143](https://github.com/kyb3r/modmail/issues/143)) +- Snippets are invoked case insensitively. ([GH #3077](https://github.com/kyb3r/modmail/issues/3077), [PR #3080](https://github.com/kyb3r/modmail/pull/3080)) ## Fixed - Certain situations where the internal thread cache breaks and spams new channels. ([GH #3022](https://github.com/kyb3r/modmail/issues/3022), [PR #3028](https://github.com/kyb3r/modmail/pull/3028)) - Blocked users are now no longer allowed to use `?contact` and react to contact. ([COMMENT #819004157](https://github.com/kyb3r/modmail/issues/2969#issuecomment-819004157), [PR #3027](https://github.com/kyb3r/modmail/pull/3027)) - UnicodeEncodeError will no longer be raised on Windows. ([PR #3043](https://github.com/kyb3r/modmail/pull/3043)) +- Notifications are no longer duplicated when using both `?notify` and `subscribe`. ([PR #3015](https://github.com/kyb3r/modmail/pull/3015)) ## Internal diff --git a/README.md b/README.md index bd9d0b8f69..7659898399 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,7 @@
-<<<<<<< HEAD -======= - ->>>>>>> master
diff --git a/bot.py b/bot.py index 93f407bcfa..fce5afa73c 100644 --- a/bot.py +++ b/bot.py @@ -1,8 +1,4 @@ -<<<<<<< HEAD __version__ = "v3.10.0-dev4" -======= -__version__ = "3.9.5" ->>>>>>> master import asyncio @@ -1268,13 +1264,9 @@ async def handle_reaction_events(self, payload): if not thread: return try: -<<<<<<< HEAD _, *linked_message = await thread.find_linked_messages( message.id, either_direction=True ) -======= - _, linked_message = await thread.find_linked_messages(message.id, either_direction=True) ->>>>>>> master except ValueError as e: logger.warning("Failed to find linked message for reactions: %s", e) return @@ -1305,7 +1297,6 @@ async def handle_react_to_contact(self, payload): else: emoji_fmt = f"<:{payload.emoji.name}:{payload.emoji.id}>" -<<<<<<< HEAD if emoji_fmt != react_message_emoji: return channel = self.get_channel(payload.channel_id) @@ -1338,11 +1329,6 @@ async def on_raw_reaction_add(self, payload): await asyncio.gather( self.handle_reaction_events(payload), self.handle_react_to_contact(payload), ) -======= - ctx = await self.get_context(message) - ctx.author = member - await ctx.invoke(self.get_command("contact"), user=member, manual_trigger=False) ->>>>>>> master async def on_raw_reaction_remove(self, payload): if self.config["transfer_reactions"]: diff --git a/cogs/modmail.py b/cogs/modmail.py index 4e6c1fc126..f10a9e50d3 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -1046,18 +1046,11 @@ async def contact( exists = await self.bot.threads.find(recipient=user) if exists: -<<<<<<< HEAD desc = "A thread for this user already exists" if exists.channel: desc += f" in {exists.channel.mention}" desc += "." embed = discord.Embed(color=self.bot.error_color, description=desc) -======= - embed = discord.Embed( - color=self.bot.error_color, - description="A thread for this user already " f"exists in {exists.channel.mention}.", - ) ->>>>>>> master await ctx.channel.send(embed=embed, delete_after=3) else: @@ -1550,13 +1543,9 @@ async def repair(self, ctx): other_recipients[n] = self.bot.get_user(uid) or await self.bot.fetch_user(uid) if recipient is None: -<<<<<<< HEAD self.bot.threads.cache[user.id] = thread = Thread( self.bot.threads, user_id, ctx.channel, other_recipients ) -======= - self.bot.threads.cache[user.id] = thread = Thread(self.bot.threads, user_id, ctx.channel) ->>>>>>> master else: self.bot.threads.cache[user.id] = thread = Thread( self.bot.threads, recipient, ctx.channel, other_recipients diff --git a/core/models.py b/core/models.py index 3b1e9ce481..7036498910 100644 --- a/core/models.py +++ b/core/models.py @@ -127,13 +127,9 @@ def format(self, record): def configure_logging(name, level=None): global ch_debug, log_level -<<<<<<< HEAD ch_debug = RotatingFileHandler( name, mode="a+", maxBytes=48000, backupCount=1, encoding="utf-8" ) -======= - ch_debug = RotatingFileHandler(name, mode="a+", maxBytes=78000, backupCount=10) ->>>>>>> master formatter_debug = FileFormatter( "%(asctime)s %(name)s[%(lineno)d] - %(levelname)s: %(message)s", diff --git a/core/thread.py b/core/thread.py index 2d24bd9132..b99a638a88 100644 --- a/core/thread.py +++ b/core/thread.py @@ -1136,7 +1136,6 @@ async def find( logger.warning("Thread for %s cancelled.", recipient) return thread else: -<<<<<<< HEAD if not thread.cancelled and ( not thread.channel or not self.bot.get_channel(thread.channel.id) ): @@ -1144,13 +1143,6 @@ async def find( "Found existing thread for %s but the channel is invalid.", recipient_id ) await thread.close(closer=self.bot.user, silent=True, delete_channel=False) -======= - if not thread.channel or not self.bot.get_channel(thread.channel.id): - logger.warning("Found existing thread for %s but the channel is invalid.", recipient_id) - self.bot.loop.create_task( - thread.close(closer=self.bot.user, silent=True, delete_channel=False) - ) ->>>>>>> master thread = None else: channel = discord.utils.find( @@ -1224,17 +1216,10 @@ async def create( if thread.channel and self.bot.get_channel(thread.channel.id): logger.warning("Found an existing thread for %s, abort creating.", recipient) return thread -<<<<<<< HEAD - logger.warning( - "Found an existing thread for %s, closing previous thread.", recipient - ) - await thread.close(closer=self.bot.user, silent=True, delete_channel=False) -======= logger.warning("Found an existing thread for %s, closing previous thread.", recipient) self.bot.loop.create_task( thread.close(closer=self.bot.user, silent=True, delete_channel=False) ) ->>>>>>> master thread = Thread(self, recipient) @@ -1304,18 +1289,11 @@ async def remove_reactions(): for emoji in emojis: await confirm.remove_reaction(emoji, self.bot.user) await asyncio.sleep(0.2) -<<<<<<< HEAD self.bot.loop.create_task(remove_reactions()) if thread.cancelled: del self.cache[recipient.id] return thread -======= - await confirm.remove_reaction(deny_emoji, self.bot.user) - await destination.send(embed=discord.Embed(title="Cancelled", color=self.bot.error_color)) - del self.cache[recipient.id] - return thread ->>>>>>> master self.bot.loop.create_task(thread.setup(creator=creator, category=category, initial_message=message)) return thread diff --git a/pyproject.toml b/pyproject.toml index 556c7b277c..e038685a61 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,6 @@ extend-exclude = ''' ) ''' -<<<<<<< HEAD [tool.poetry] name = 'Modmail' version = '3.10.0-dev4' @@ -34,10 +33,6 @@ readme = 'README.md' repository = 'https://github.com/kyb3r/modmail' homepage = 'https://github.com/kyb3r/modmail' keywords = ['discord', 'modmail'] -======= -[tool.pylint.messages_control] -disable = "C0330, C0326" ->>>>>>> master [tool.pylint.format] max-line-length = "110" From 0c9c129793d044438bc15ebdc68364d434b8e3d6 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Fri, 6 Aug 2021 19:14:38 +0800 Subject: [PATCH 160/209] Formatting --- bot.py | 15 ++++++--------- cogs/modmail.py | 5 ++++- core/models.py | 4 +--- core/thread.py | 30 +++++++++++------------------- core/utils.py | 4 +--- 5 files changed, 23 insertions(+), 35 deletions(-) diff --git a/bot.py b/bot.py index fce5afa73c..fb9a1a9f9d 100644 --- a/bot.py +++ b/bot.py @@ -1264,9 +1264,7 @@ async def handle_reaction_events(self, payload): if not thread: return try: - _, *linked_message = await thread.find_linked_messages( - message.id, either_direction=True - ) + _, *linked_message = await thread.find_linked_messages(message.id, either_direction=True) except ValueError as e: logger.warning("Failed to find linked message for reactions: %s", e) return @@ -1287,10 +1285,7 @@ async def handle_reaction_events(self, payload): async def handle_react_to_contact(self, payload): react_message_id = tryint(self.config.get("react_to_contact_message")) react_message_emoji = self.config.get("react_to_contact_emoji") - if ( - not all((react_message_id, react_message_emoji)) - or payload.message_id != react_message_id - ): + if not all((react_message_id, react_message_emoji)) or payload.message_id != react_message_id: return if payload.emoji.is_unicode_emoji(): emoji_fmt = payload.emoji.name @@ -1314,7 +1309,8 @@ async def handle_react_to_contact(self, payload): description=self.config["disabled_new_thread_response"], ) embed.set_footer( - text=self.config["disabled_new_thread_footer"], icon_url=self.guild.icon_url, + text=self.config["disabled_new_thread_footer"], + icon_url=self.guild.icon_url, ) logger.info( "A new thread using react to contact was blocked from %s due to disabled Modmail.", @@ -1327,7 +1323,8 @@ async def handle_react_to_contact(self, payload): async def on_raw_reaction_add(self, payload): await asyncio.gather( - self.handle_reaction_events(payload), self.handle_react_to_contact(payload), + self.handle_reaction_events(payload), + self.handle_react_to_contact(payload), ) async def on_raw_reaction_remove(self, payload): diff --git a/cogs/modmail.py b/cogs/modmail.py index f10a9e50d3..adec3f5447 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -1067,7 +1067,10 @@ async def contact( return await ctx.send(embed=embed) thread = await self.bot.threads.create( - recipient=user, creator=creator, category=category, manual_trigger=manual_trigger, + recipient=user, + creator=creator, + category=category, + manual_trigger=manual_trigger, ) if thread.cancelled: return diff --git a/core/models.py b/core/models.py index 7036498910..445f7793ae 100644 --- a/core/models.py +++ b/core/models.py @@ -127,9 +127,7 @@ def format(self, record): def configure_logging(name, level=None): global ch_debug, log_level - ch_debug = RotatingFileHandler( - name, mode="a+", maxBytes=48000, backupCount=1, encoding="utf-8" - ) + ch_debug = RotatingFileHandler(name, mode="a+", maxBytes=48000, backupCount=1, encoding="utf-8") formatter_debug = FileFormatter( "%(asctime)s %(name)s[%(lineno)d] - %(levelname)s: %(message)s", diff --git a/core/thread.py b/core/thread.py index b99a638a88..44ff156aed 100644 --- a/core/thread.py +++ b/core/thread.py @@ -111,15 +111,11 @@ def cancelled(self, flag: bool): i.cancel() @classmethod - async def from_channel( - cls, manager: "ThreadManager", channel: discord.TextChannel - ) -> "Thread": + async def from_channel(cls, manager: "ThreadManager", channel: discord.TextChannel) -> "Thread": recipient_id = match_user_id( channel.topic ) # there is a chance it grabs from another recipient's main thread - recipient = manager.bot.get_user(recipient_id) or await manager.bot.fetch_user( - recipient_id - ) + recipient = manager.bot.get_user(recipient_id) or await manager.bot.fetch_user(recipient_id) other_recipients = match_other_recipients(channel.topic) for n, uid in enumerate(other_recipients): @@ -761,7 +757,11 @@ async def reply(self, message: discord.Message, anonymous: bool = False, plain: for user in self.recipients: user_msg_tasks.append( self.send( - message, destination=user, from_mod=True, anonymous=anonymous, plain=plain, + message, + destination=user, + from_mod=True, + anonymous=anonymous, + plain=plain, ) ) @@ -1071,9 +1071,7 @@ async def set_title(self, title: str) -> None: user_id = match_user_id(self.channel.topic) ids = ",".join(i.id for i in self._other_recipients) - await self.channel.edit( - topic=f"Title: {title}\nUser ID: {user_id}\nOther Recipients: {ids}" - ) + await self.channel.edit(topic=f"Title: {title}\nUser ID: {user_id}\nOther Recipients: {ids}") async def add_user(self, user: typing.Union[discord.Member, discord.User]) -> None: title = match_title(self.channel.topic) @@ -1081,9 +1079,7 @@ async def add_user(self, user: typing.Union[discord.Member, discord.User]) -> No self._other_recipients.append(user) ids = ",".join(str(i.id) for i in self._other_recipients) - await self.channel.edit( - topic=f"Title: {title}\nUser ID: {user_id}\nOther Recipients: {ids}" - ) + await self.channel.edit(topic=f"Title: {title}\nUser ID: {user_id}\nOther Recipients: {ids}") class ThreadManager: @@ -1139,9 +1135,7 @@ async def find( if not thread.cancelled and ( not thread.channel or not self.bot.get_channel(thread.channel.id) ): - logger.warning( - "Found existing thread for %s but the channel is invalid.", recipient_id - ) + logger.warning("Found existing thread for %s but the channel is invalid.", recipient_id) await thread.close(closer=self.bot.user, silent=True, delete_channel=False) thread = None else: @@ -1280,9 +1274,7 @@ async def create( if str(r.emoji) == deny_emoji: thread.cancelled = True self.bot.loop.create_task( - destination.send( - embed=discord.Embed(title="Cancelled", color=self.bot.error_color) - ) + destination.send(embed=discord.Embed(title="Cancelled", color=self.bot.error_color)) ) async def remove_reactions(): diff --git a/core/utils.py b/core/utils.py index e80e298931..173eda25d5 100644 --- a/core/utils.py +++ b/core/utils.py @@ -218,9 +218,7 @@ def cleanup_code(content: str) -> str: return content.strip("` \n") -TOPIC_OTHER_RECIPIENTS_REGEX = re.compile( - r"Other Recipients:\s*((?:\d{17,21},*)+)", flags=re.IGNORECASE -) +TOPIC_OTHER_RECIPIENTS_REGEX = re.compile(r"Other Recipients:\s*((?:\d{17,21},*)+)", flags=re.IGNORECASE) TOPIC_TITLE_REGEX = re.compile(r"\bTitle: (.*)\n(?:User ID: )\b", flags=re.IGNORECASE | re.DOTALL) TOPIC_UID_REGEX = re.compile(r"\bUser ID:\s*(\d{17,21})\b", flags=re.IGNORECASE) From 323a5574eb412fc9bd9f7bcaa73e4493250e8db6 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Fri, 6 Aug 2021 19:39:37 +0800 Subject: [PATCH 161/209] Fix contact with category and silent, #3076 --- CHANGELOG.md | 1 + bot.py | 2 +- cogs/modmail.py | 9 ++++++++- core/thread.py | 7 +++++++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8ae76fbb6..41da5adcee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ however, insignificant breaking changes do not guarantee a major version bump, s - Blocked users are now no longer allowed to use `?contact` and react to contact. ([COMMENT #819004157](https://github.com/kyb3r/modmail/issues/2969#issuecomment-819004157), [PR #3027](https://github.com/kyb3r/modmail/pull/3027)) - UnicodeEncodeError will no longer be raised on Windows. ([PR #3043](https://github.com/kyb3r/modmail/pull/3043)) - Notifications are no longer duplicated when using both `?notify` and `subscribe`. ([PR #3015](https://github.com/kyb3r/modmail/pull/3015)) +- `?contact` now works properly with both category and silent. ([GH #3076](https://github.com/kyb3r/modmail/issues/3076)) ## Internal diff --git a/bot.py b/bot.py index fb9a1a9f9d..36675a1ae4 100644 --- a/bot.py +++ b/bot.py @@ -284,7 +284,7 @@ def _cancel_tasks(): loop.run_until_complete(loop.shutdown_asyncgens()) finally: logger.info("Closing the event loop.") - loop.close() + # loop.close() if not future.cancelled(): try: diff --git a/cogs/modmail.py b/cogs/modmail.py index adec3f5447..1dee52d941 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -1038,7 +1038,14 @@ async def contact( if isinstance(category, str): if "silent" in category or "silently" in category: silent = True - category = None + category = category.strip("silently").strip("silent").strip() + try: + category = await SimilarCategoryConverter().convert(ctx, category) # attempt to find a category again + except commands.BadArgument: + category = None + + if isinstance(category, str): + category = None if user.bot: embed = discord.Embed(color=self.bot.error_color, description="Cannot start a thread with a bot.") diff --git a/core/thread.py b/core/thread.py index 44ff156aed..0c5339ff83 100644 --- a/core/thread.py +++ b/core/thread.py @@ -487,10 +487,17 @@ async def _close(self, closer, silent=False, delete_channel=True, message=None, if self.bot.config["show_timestamp"]: embed.timestamp = datetime.utcnow() + if not message: + if self.id == closer.id: + message = self.bot.config["thread_self_close_response"] + else: + message = self.bot.config["thread_close_response"] + message = self.bot.formatter.format( message, closer=closer, loglink=log_url, logkey=log_data["key"] if log_data else None ) + embed.description = message footer = self.bot.config["thread_close_footer"] embed.set_footer(text=footer, icon_url=self.bot.guild.icon_url) From 0c49598ec8c7758ed5b64ad3ffa4544f793bbf18 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Fri, 6 Aug 2021 19:48:43 +0800 Subject: [PATCH 162/209] Resolve close_on_leave_reason not properly working --- CHANGELOG.md | 1 + core/thread.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41da5adcee..ad69c7b7ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ however, insignificant breaking changes do not guarantee a major version bump, s - UnicodeEncodeError will no longer be raised on Windows. ([PR #3043](https://github.com/kyb3r/modmail/pull/3043)) - Notifications are no longer duplicated when using both `?notify` and `subscribe`. ([PR #3015](https://github.com/kyb3r/modmail/pull/3015)) - `?contact` now works properly with both category and silent. ([GH #3076](https://github.com/kyb3r/modmail/issues/3076)) +- Resolves `close_on_leave_reason` not properly working when `close_on_leave` is enabled. ([GH #3075](https://github.com/kyb3r/modmail/issues/3075)) ## Internal diff --git a/core/thread.py b/core/thread.py index 0c5339ff83..2d1d5d18f9 100644 --- a/core/thread.py +++ b/core/thread.py @@ -414,7 +414,7 @@ async def _close(self, closer, silent=False, delete_channel=True, message=None, "title": match_title(self.channel.topic), "closed_at": str(datetime.utcnow()), "nsfw": self.channel.nsfw, - "close_message": message if not silent else None, + "close_message": message, "closer": { "id": str(closer.id), "name": closer.name, From adac1de293cd188a8b2e25d22ddb1fa01f0d6fc8 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Fri, 6 Aug 2021 19:51:02 +0800 Subject: [PATCH 163/209] Invalid arguments are now properly catched and a proper error message is sent (BadUnionArg) --- CHANGELOG.md | 3 ++- bot.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad69c7b7ed..eaa0e668d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,8 @@ however, insignificant breaking changes do not guarantee a major version bump, s - UnicodeEncodeError will no longer be raised on Windows. ([PR #3043](https://github.com/kyb3r/modmail/pull/3043)) - Notifications are no longer duplicated when using both `?notify` and `subscribe`. ([PR #3015](https://github.com/kyb3r/modmail/pull/3015)) - `?contact` now works properly with both category and silent. ([GH #3076](https://github.com/kyb3r/modmail/issues/3076)) -- Resolves `close_on_leave_reason` not properly working when `close_on_leave` is enabled. ([GH #3075](https://github.com/kyb3r/modmail/issues/3075)) +- `close_on_leave_reason` now works properly when `close_on_leave` is enabled. ([GH #3075](https://github.com/kyb3r/modmail/issues/3075)) +- Invalid arguments are now properly catched and a proper error message is sent. ## Internal diff --git a/bot.py b/bot.py index 36675a1ae4..26fac619d2 100644 --- a/bot.py +++ b/bot.py @@ -1472,7 +1472,7 @@ async def on_error(self, event_method, *args, **kwargs): logger.error("Unexpected exception:", exc_info=sys.exc_info()) async def on_command_error(self, context, exception): - if isinstance(exception, commands.BadArgument): + if isinstance(exception, (commands.BadArgument, commands.BadUnionArgument)): await context.trigger_typing() await context.send(embed=discord.Embed(color=self.error_color, description=str(exception))) elif isinstance(exception, commands.CommandNotFound): From dcfdfead8959a27389eb3764ba15b5d299491268 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Fri, 6 Aug 2021 20:31:41 +0800 Subject: [PATCH 164/209] Removeuser (resolve #3065), silent adding/removing (#3054) --- CHANGELOG.md | 6 +++ cogs/modmail.py | 99 +++++++++++++++++++++++++++++++++++++++---------- core/thread.py | 37 +++++++++++++++--- 3 files changed, 117 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eaa0e668d2..d398e71f2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ however, insignificant breaking changes do not guarantee a major version bump, s # v3.10.0-dev4 +v3.10 adds group conversations while resolving othre bugs and QOL changes. It is potentially breaking to some plugins that adds functionality to threads. + +## Breaking + +- `Thread.recipient` (`str`) is now `Thread.recipients` (`List[str]`). + ## Added - Ability to have group conversations. ([GH #143](https://github.com/kyb3r/modmail/issues/143)) diff --git a/cogs/modmail.py b/cogs/modmail.py index 1dee52d941..8b86a8215c 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -675,8 +675,11 @@ async def title(self, ctx, *, name: str): @checks.has_permissions(PermissionLevel.SUPPORTER) @checks.thread_only() @commands.cooldown(1, 600, BucketType.channel) - async def adduser(self, ctx, *, user: discord.Member): - """Adds a user to a modmail thread""" + async def adduser(self, ctx, user: discord.Member, *, options: str.lower = ""): + """Adds a user to a modmail thread + + `options` can be `silent` or `silently`. + """ curr_thread = await self.bot.threads.find(recipient=user) if curr_thread: @@ -686,30 +689,88 @@ async def adduser(self, ctx, *, user: discord.Member): color=self.bot.error_color, ) await ctx.send(embed=em) + ctx.command.reset_cooldown(ctx) else: + if 'silent' not in options and 'silently' not in options: + em = discord.Embed( + title="New Thread (Group)", + description=f"{ctx.author.name} has added you to a Modmail thread.", + color=self.bot.main_color, + ) + if self.bot.config["show_timestamp"]: + em.timestamp = datetime.utcnow() + em.set_footer(text=f"{ctx.author}", icon_url=ctx.author.avatar_url) + await user.send(embed=em) + + em = discord.Embed( + title="New User", + description=f"{ctx.author.name} has added {user.name} to the Modmail thread.", + color=self.bot.main_color, + ) + if self.bot.config["show_timestamp"]: + em.timestamp = datetime.utcnow() + em.set_footer(text=f"{user}", icon_url=user.avatar_url) + + for i in ctx.thread.recipients: + if i != user: + await i.send(embed=em) + + await ctx.thread.add_user(user) + sent_emoji, _ = await self.bot.retrieve_emoji() + await self.bot.add_reaction(ctx.message, sent_emoji) + + @commands.command(cooldown_after_parsing=True) + @checks.has_permissions(PermissionLevel.SUPPORTER) + @checks.thread_only() + @commands.cooldown(1, 600, BucketType.channel) + async def removeuser(self, ctx, user: discord.Member, *, options: str.lower = ""): + """Removes a user from a modmail thread + + `options` can be `silent` or `silently`. + """ + + curr_thread = await self.bot.threads.find(recipient=user) + if ctx.thread != curr_thread: em = discord.Embed( - title="New Thread (Group)", - description=f"{ctx.author.name} has added you to a Modmail thread.", - color=self.bot.main_color, + title="Error", + description="User is not in this thread.", + color=self.bot.error_color, ) - if self.bot.config["show_timestamp"]: - em.timestamp = datetime.utcnow() - em.set_footer(text=f"{ctx.author}", icon_url=ctx.author.avatar_url) - await user.send(embed=em) - + await ctx.send(embed=em) + ctx.command.reset_cooldown(ctx) + elif ctx.thread.recipient == user: em = discord.Embed( - title="New User", - description=f"{ctx.author.name} has added {user.name} to the Modmail thread.", - color=self.bot.main_color, + title="Error", + description="User is the main recipient of the thread and cannot be removed.", + color=self.bot.error_color, ) - if self.bot.config["show_timestamp"]: - em.timestamp = datetime.utcnow() - em.set_footer(text=f"{user}", icon_url=user.avatar_url) + await ctx.send(embed=em) + ctx.command.reset_cooldown(ctx) + else: + if 'silent' not in options and 'silently' not in options: + em = discord.Embed( + title="Removed From Thread (Group)", + description=f"{ctx.author.name} has been removed from the Modmail thread.", + color=self.bot.main_color, + ) + if self.bot.config["show_timestamp"]: + em.timestamp = datetime.utcnow() + em.set_footer(text=f"{ctx.author}", icon_url=ctx.author.avatar_url) + await user.send(embed=em) - for i in ctx.thread.recipients: - await i.send(embed=em) + em = discord.Embed( + title="User Removed", + description=f"{ctx.author.name} has removed {user.name} from the Modmail thread.", + color=self.bot.main_color, + ) + if self.bot.config["show_timestamp"]: + em.timestamp = datetime.utcnow() + em.set_footer(text=f"{user}", icon_url=user.avatar_url) - await ctx.thread.add_user(user) + for i in ctx.thread.recipients: + await i.send(embed=em) + + await ctx.thread.remove_user(user) sent_emoji, _ = await self.bot.retrieve_emoji() await self.bot.add_reaction(ctx.message, sent_emoji) diff --git a/core/thread.py b/core/thread.py index 2d1d5d18f9..bc47f606ed 100644 --- a/core/thread.py +++ b/core/thread.py @@ -57,7 +57,10 @@ def __init__( self._cancelled = False def __repr__(self): - return f'Thread(recipient="{self.recipient or self.id}", channel={self.channel.id}, other_recipienets={len(self._other_recipients)})' + return f'Thread(recipient="{self.recipient or self.id}", channel={self.channel.id}, other_recipients={len(self._other_recipients)})' + + def __eq__(self, other): + return self.id == other.id async def wait_until_ready(self) -> None: """Blocks execution until the thread is fully set up.""" @@ -115,13 +118,19 @@ async def from_channel(cls, manager: "ThreadManager", channel: discord.TextChann recipient_id = match_user_id( channel.topic ) # there is a chance it grabs from another recipient's main thread - recipient = manager.bot.get_user(recipient_id) or await manager.bot.fetch_user(recipient_id) - other_recipients = match_other_recipients(channel.topic) - for n, uid in enumerate(other_recipients): - other_recipients[n] = manager.bot.get_user(uid) or await manager.bot.fetch_user(uid) + if recipient_id in manager.cache: + thread = manager.cache[recipient_id] + else: + recipient = manager.bot.get_user(recipient_id) or await manager.bot.fetch_user(recipient_id) - return cls(manager, recipient or recipient_id, channel, other_recipients) + other_recipients = match_other_recipients(channel.topic) + for n, uid in enumerate(other_recipients): + other_recipients[n] = manager.bot.get_user(uid) or await manager.bot.fetch_user(uid) + + thread = cls(manager, recipient or recipient_id, channel, other_recipients) + + return thread async def setup(self, *, creator=None, category=None, initial_message=None): """Create the thread channel and other io related initialisation tasks""" @@ -1088,6 +1097,16 @@ async def add_user(self, user: typing.Union[discord.Member, discord.User]) -> No ids = ",".join(str(i.id) for i in self._other_recipients) await self.channel.edit(topic=f"Title: {title}\nUser ID: {user_id}\nOther Recipients: {ids}") + async def remove_user(self, user: typing.Union[discord.Member, discord.User]) -> None: + title = match_title(self.channel.topic) + user_id = match_user_id(self.channel.topic) + self._other_recipients.remove(user) + + ids = ",".join(str(i.id) for i in self._other_recipients) + await self.channel.edit(topic=f"Title: {title}\nUser ID: {user_id}\nOther Recipients: {ids}") + # if user.id in self.manager.cache: + # self.manager.cache.pop(self.id) + class ThreadManager: """Class that handles storing, finding and creating Modmail threads.""" @@ -1146,6 +1165,7 @@ async def find( await thread.close(closer=self.bot.user, silent=True, delete_channel=False) thread = None else: + print('in here') channel = discord.utils.find( lambda x: str(recipient_id) in x.topic if x.topic else False, self.bot.modmail_guild.text_channels, @@ -1157,6 +1177,11 @@ async def find( # only save if data is valid self.cache[recipient_id] = thread thread.ready = True + + if thread and recipient_id not in [x.id for x in thread.recipients]: + self.cache.pop(recipient_id) + thread = None + return thread async def _find_from_channel(self, channel): From 00eb779f2f9abe5f52c7413dad9656971fef9b3c Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Fri, 6 Aug 2021 20:32:32 +0800 Subject: [PATCH 165/209] Formatting --- cogs/modmail.py | 8 +++++--- core/checks.py | 2 +- core/thread.py | 1 - 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cogs/modmail.py b/cogs/modmail.py index 8b86a8215c..bbff3b491a 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -691,7 +691,7 @@ async def adduser(self, ctx, user: discord.Member, *, options: str.lower = ""): await ctx.send(embed=em) ctx.command.reset_cooldown(ctx) else: - if 'silent' not in options and 'silently' not in options: + if "silent" not in options and "silently" not in options: em = discord.Embed( title="New Thread (Group)", description=f"{ctx.author.name} has added you to a Modmail thread.", @@ -747,7 +747,7 @@ async def removeuser(self, ctx, user: discord.Member, *, options: str.lower = "" await ctx.send(embed=em) ctx.command.reset_cooldown(ctx) else: - if 'silent' not in options and 'silently' not in options: + if "silent" not in options and "silently" not in options: em = discord.Embed( title="Removed From Thread (Group)", description=f"{ctx.author.name} has been removed from the Modmail thread.", @@ -1101,7 +1101,9 @@ async def contact( silent = True category = category.strip("silently").strip("silent").strip() try: - category = await SimilarCategoryConverter().convert(ctx, category) # attempt to find a category again + category = await SimilarCategoryConverter().convert( + ctx, category + ) # attempt to find a category again except commands.BadArgument: category = None diff --git a/core/checks.py b/core/checks.py index 1079b55530..3f3666538d 100644 --- a/core/checks.py +++ b/core/checks.py @@ -31,7 +31,7 @@ def has_permissions(permission_level: PermissionLevel = PermissionLevel.REGULAR) :: @has_permissions(PermissionLevel.OWNER) async def setup(ctx): - print("Success") + await ctx.send('Success') """ return commands.check(has_permissions_predicate(permission_level)) diff --git a/core/thread.py b/core/thread.py index bc47f606ed..29446a32c3 100644 --- a/core/thread.py +++ b/core/thread.py @@ -1165,7 +1165,6 @@ async def find( await thread.close(closer=self.bot.user, silent=True, delete_channel=False) thread = None else: - print('in here') channel = discord.utils.find( lambda x: str(recipient_id) in x.topic if x.topic else False, self.bot.modmail_guild.text_channels, From 417f30326b315d85513b3426258a7f103d300c36 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Fri, 6 Aug 2021 21:16:55 +0800 Subject: [PATCH 166/209] add multiple users to a thread (resolve #3066), new group config options --- cogs/modmail.py | 309 ++++++++++++++++++++++++++++++++---------- core/config.py | 13 ++ core/config_help.json | 144 ++++++++++++++++++++ core/thread.py | 15 +- 4 files changed, 401 insertions(+), 80 deletions(-) diff --git a/cogs/modmail.py b/cogs/modmail.py index bbff3b491a..aeace73192 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -671,108 +671,271 @@ async def title(self, ctx, *, name: str): await ctx.message.pin() await self.bot.add_reaction(ctx.message, sent_emoji) - @commands.command(cooldown_after_parsing=True) + @commands.command(usage=" [options]", cooldown_after_parsing=True) @checks.has_permissions(PermissionLevel.SUPPORTER) @checks.thread_only() @commands.cooldown(1, 600, BucketType.channel) - async def adduser(self, ctx, user: discord.Member, *, options: str.lower = ""): + async def adduser(self, ctx, *users: Union[discord.Member, str]): """Adds a user to a modmail thread `options` can be `silent` or `silently`. """ - - curr_thread = await self.bot.threads.find(recipient=user) - if curr_thread: + silent = False + for u in users: + if isinstance(u, str): + if "silent" in u or "silently" in u: + silent = True + users.remove(u) # remove all strings so users is a List[Member] + else: + # u is a discord.Member + curr_thread = await self.bot.threads.find(recipient=u) + if curr_thread: + em = discord.Embed( + title="Error", + description=f"{u.mention} is already in a thread: {curr_thread.channel.mention}.", + color=self.bot.error_color, + ) + await ctx.send(embed=em) + ctx.command.reset_cooldown(ctx) + return + if not silent: em = discord.Embed( - title="Error", - description=f"User is already in a thread: {curr_thread.channel.mention}.", - color=self.bot.error_color, + title=self.bot.config["private_added_to_group_title"], + description=self.bot.config["private_added_to_group_description"].format(moderator=ctx.author), + color=self.bot.main_color, ) - await ctx.send(embed=em) - ctx.command.reset_cooldown(ctx) - else: - if "silent" not in options and "silently" not in options: - em = discord.Embed( - title="New Thread (Group)", - description=f"{ctx.author.name} has added you to a Modmail thread.", - color=self.bot.main_color, - ) - if self.bot.config["show_timestamp"]: - em.timestamp = datetime.utcnow() - em.set_footer(text=f"{ctx.author}", icon_url=ctx.author.avatar_url) - await user.send(embed=em) + if self.bot.config["show_timestamp"]: + em.timestamp = datetime.utcnow() + em.set_footer(text=str(ctx.author), icon_url=ctx.author.avatar_url) + for u in users: + await u.send(embed=em) - em = discord.Embed( - title="New User", - description=f"{ctx.author.name} has added {user.name} to the Modmail thread.", - color=self.bot.main_color, - ) - if self.bot.config["show_timestamp"]: - em.timestamp = datetime.utcnow() - em.set_footer(text=f"{user}", icon_url=user.avatar_url) + em = discord.Embed( + title=self.bot.config["public_added_to_group_title"], + description=self.bot.config["private_added_to_group_description"].format(moderator=ctx.author, users=', '.join(u.name for u in users)), + color=self.bot.main_color, + ) + if self.bot.config["show_timestamp"]: + em.timestamp = datetime.utcnow() + em.set_footer(text=f"{users[0]}", icon_url=users[0].avatar_url) - for i in ctx.thread.recipients: - if i != user: - await i.send(embed=em) + for i in ctx.thread.recipients: + if i not in users: + await i.send(embed=em) - await ctx.thread.add_user(user) - sent_emoji, _ = await self.bot.retrieve_emoji() - await self.bot.add_reaction(ctx.message, sent_emoji) + await ctx.thread.add_users(users) + sent_emoji, _ = await self.bot.retrieve_emoji() + await self.bot.add_reaction(ctx.message, sent_emoji) - @commands.command(cooldown_after_parsing=True) + @commands.command(usage=" [options]", cooldown_after_parsing=True) @checks.has_permissions(PermissionLevel.SUPPORTER) @checks.thread_only() @commands.cooldown(1, 600, BucketType.channel) - async def removeuser(self, ctx, user: discord.Member, *, options: str.lower = ""): + async def removeuser(self, ctx, *users: Union[discord.Member, str]): """Removes a user from a modmail thread `options` can be `silent` or `silently`. """ + silent = False + for u in users: + if isinstance(u, str): + if "silent" in u or "silently" in u: + silent = True + users.remove(u) # remove all strings so users is a List[Member] + else: + # u is a discord.Member + curr_thread = await self.bot.threads.find(recipient=u) + if ctx.thread != curr_thread: + em = discord.Embed( + title="Error", + description=f"{u.mention} is not in this thread.", + color=self.bot.error_color, + ) + await ctx.send(embed=em) + ctx.command.reset_cooldown(ctx) + return + elif ctx.thread.recipient == u: + em = discord.Embed( + title="Error", + description=f"{u.mention} is the main recipient of the thread and cannot be removed.", + color=self.bot.error_color, + ) + await ctx.send(embed=em) + ctx.command.reset_cooldown(ctx) + return - curr_thread = await self.bot.threads.find(recipient=user) - if ctx.thread != curr_thread: + if not silent: em = discord.Embed( - title="Error", - description="User is not in this thread.", - color=self.bot.error_color, + title=self.bot.config["private_removed_from_group_title"], + description=self.bot.config["private_removed_from_group_description"].format(moderator=ctx.author), + color=self.bot.main_color, ) - await ctx.send(embed=em) - ctx.command.reset_cooldown(ctx) - elif ctx.thread.recipient == user: + if self.bot.config["show_timestamp"]: + em.timestamp = datetime.utcnow() + em.set_footer(text=str(ctx.author), icon_url=ctx.author.avatar_url) + for u in users: + await u.send(embed=em) + em = discord.Embed( - title="Error", - description="User is the main recipient of the thread and cannot be removed.", - color=self.bot.error_color, + title=self.bot.config["public_removed_from_group_title"], + description=self.bot.config["private_removed_from_group_description"].format(moderator=ctx.author, users=', '.join(u.name for u in users)), + color=self.bot.main_color, ) - await ctx.send(embed=em) - ctx.command.reset_cooldown(ctx) - else: - if "silent" not in options and "silently" not in options: - em = discord.Embed( - title="Removed From Thread (Group)", - description=f"{ctx.author.name} has been removed from the Modmail thread.", - color=self.bot.main_color, - ) - if self.bot.config["show_timestamp"]: - em.timestamp = datetime.utcnow() - em.set_footer(text=f"{ctx.author}", icon_url=ctx.author.avatar_url) - await user.send(embed=em) + if self.bot.config["show_timestamp"]: + em.timestamp = datetime.utcnow() + em.set_footer(text=f"{users[0]}", icon_url=users[0].avatar_url) - em = discord.Embed( - title="User Removed", - description=f"{ctx.author.name} has removed {user.name} from the Modmail thread.", - color=self.bot.main_color, - ) - if self.bot.config["show_timestamp"]: - em.timestamp = datetime.utcnow() - em.set_footer(text=f"{user}", icon_url=user.avatar_url) + for i in ctx.thread.recipients: + if i not in users: + await i.send(embed=em) + + await ctx.thread.remove_users(users) + sent_emoji, _ = await self.bot.retrieve_emoji() + await self.bot.add_reaction(ctx.message, sent_emoji) + + @commands.command(usage=" [options]", cooldown_after_parsing=True) + @checks.has_permissions(PermissionLevel.SUPPORTER) + @checks.thread_only() + @commands.cooldown(1, 600, BucketType.channel) + async def anonadduser(self, ctx, *users: Union[discord.Member, str]): + """Adds a user to a modmail thread anonymously + + `options` can be `silent` or `silently`. + """ + silent = False + for u in users: + if isinstance(u, str): + if "silent" in u or "silently" in u: + silent = True + users.remove(u) # remove all strings so users is a List[Member] + else: + # u is a discord.Member + curr_thread = await self.bot.threads.find(recipient=u) + if curr_thread: + em = discord.Embed( + title="Error", + description=f"{u.mention} is already in a thread: {curr_thread.channel.mention}.", + color=self.bot.error_color, + ) + await ctx.send(embed=em) + ctx.command.reset_cooldown(ctx) + return + if not silent: + em = discord.Embed( + title=self.bot.config["private_added_to_group_title"], + description=self.bot.config["private_added_to_group_description_anon"], + color=self.bot.main_color, + ) + if self.bot.config["show_timestamp"]: + em.timestamp = datetime.utcnow() + + tag = self.bot.config["mod_tag"] + if tag is None: + tag = str(ctx.author.top_role) + name = self.bot.config["anon_username"] + if name is None: + name = tag + avatar_url = self.bot.config["anon_avatar_url"] + if avatar_url is None: + avatar_url = self.bot.guild.icon_url + em.set_footer(text=name, icon_url=avatar_url) + + for u in users: + await u.send(embed=em) - for i in ctx.thread.recipients: + em = discord.Embed( + title=self.bot.config["public_added_to_group_title"], + description=self.bot.config["private_added_to_group_description_anon"].format(moderator=ctx.author, users=', '.join(u.name for u in users)), + color=self.bot.main_color, + ) + if self.bot.config["show_timestamp"]: + em.timestamp = datetime.utcnow() + em.set_footer(text=f"{users[0]}", icon_url=users[0].avatar_url) + + for i in ctx.thread.recipients: + if i not in users: await i.send(embed=em) - await ctx.thread.remove_user(user) - sent_emoji, _ = await self.bot.retrieve_emoji() - await self.bot.add_reaction(ctx.message, sent_emoji) + await ctx.thread.add_users(users) + sent_emoji, _ = await self.bot.retrieve_emoji() + await self.bot.add_reaction(ctx.message, sent_emoji) + + @commands.command(usage=" [options]", cooldown_after_parsing=True) + @checks.has_permissions(PermissionLevel.SUPPORTER) + @checks.thread_only() + @commands.cooldown(1, 600, BucketType.channel) + async def anonremoveuser(self, ctx, *users: Union[discord.Member, str]): + """Removes a user from a modmail thread anonymously + + `options` can be `silent` or `silently`. + """ + silent = False + for u in users: + if isinstance(u, str): + if "silent" in u or "silently" in u: + silent = True + users.remove(u) # remove all strings so users is a List[Member] + else: + # u is a discord.Member + curr_thread = await self.bot.threads.find(recipient=u) + if ctx.thread != curr_thread: + em = discord.Embed( + title="Error", + description=f"{u.mention} is not in this thread.", + color=self.bot.error_color, + ) + await ctx.send(embed=em) + ctx.command.reset_cooldown(ctx) + return + elif ctx.thread.recipient == u: + em = discord.Embed( + title="Error", + description=f"{u.mention} is the main recipient of the thread and cannot be removed.", + color=self.bot.error_color, + ) + await ctx.send(embed=em) + ctx.command.reset_cooldown(ctx) + return + + if not silent: + em = discord.Embed( + title=self.bot.config["private_removed_from_group_title"], + description=self.bot.config["private_removed_from_group_description_anon"].format(moderator=ctx.author), + color=self.bot.main_color, + ) + if self.bot.config["show_timestamp"]: + em.timestamp = datetime.utcnow() + + tag = self.bot.config["mod_tag"] + if tag is None: + tag = str(ctx.author.top_role) + name = self.bot.config["anon_username"] + if name is None: + name = tag + avatar_url = self.bot.config["anon_avatar_url"] + if avatar_url is None: + avatar_url = self.bot.guild.icon_url + em.set_footer(text=name, icon_url=avatar_url) + + for u in users: + await u.send(embed=em) + + em = discord.Embed( + title=self.bot.config["public_removed_from_group_title"], + description=self.bot.config["private_removed_from_group_description"].format(moderator=ctx.author, users=', '.join(u.name for u in users)), + color=self.bot.main_color, + ) + if self.bot.config["show_timestamp"]: + em.timestamp = datetime.utcnow() + em.set_footer(text=f"{users[0]}", icon_url=users[0].avatar_url) + + for i in ctx.thread.recipients: + if i not in users: + await i.send(embed=em) + + await ctx.thread.remove_users(users) + sent_emoji, _ = await self.bot.retrieve_emoji() + await self.bot.add_reaction(ctx.message, sent_emoji) @commands.group(invoke_without_command=True) @checks.has_permissions(PermissionLevel.SUPPORTER) diff --git a/core/config.py b/core/config.py index 255c3667de..829ce7ff3b 100644 --- a/core/config.py +++ b/core/config.py @@ -83,6 +83,19 @@ class ConfigManager: "silent_alert_on_mention": False, "show_timestamp": True, "anonymous_snippets": False, + # group conversations + "private_added_to_group_title": "New Thread (Group)", + "private_added_to_group_description": "{moderator.name} has added you to a Modmail thread.", + "private_added_to_group_description_anon": "A moderator has added you to a Modmail thread.", + "public_added_to_group_title": "New User", + "public_added_to_group_description": "{moderator.name} has added {users} to the Modmail thread.", + "public_added_to_group_description_anon": "A moderator has added {users} to the Modmail thread.", + "private_removed_from_group_title": "Removed From Thread (Group)", + "private_removed_from_group_description": "{moderator.name} has removed you from the Modmail thread.", + "private_removed_from_group_description_anon": "A moderator has removed you from the Modmail thread.", + "public_removed_from_group_title": "User Removed", + "public_removed_from_group_description": "{moderator.name} has removed {users} from the Modmail thread.", + "public_removed_from_group_description_anon": "A moderator has removed {users} from the Modmail thread.", # moderation "recipient_color": str(discord.Color.gold()), "mod_color": str(discord.Color.green()), diff --git a/core/config_help.json b/core/config_help.json index ede81617ed..d6a4627d96 100644 --- a/core/config_help.json +++ b/core/config_help.json @@ -711,6 +711,150 @@ "See also: `anon_avatar_url`, `anon_tag`." ] }, + "private_added_to_group_title": { + "default": "New Thread (Group)", + "description": "This is the message embed title sent to the recipient that is just added to a thread.", + "examples": [ + "`{prefix}config set private_added_to_group_title Welcome to this new group thread!`" + ], + "notes": [ + "The public_ variant is used when sending to other thread recipients.", + "See also: `private_added_to_group_description`, `public_added_to_group_title`" + ] + }, + "private_added_to_group_description": { + "default": "\"{{moderator.name}} has added you to a Modmail thread.\"", + "description": "This is the message embed content sent to the recipient that is just added to a thread.", + "examples": [ + "`{prefix}config set private_added_to_group_description Any message sent here will be sent to all otherthread recipients.`" + ], + "notes": [ + "You may use the `{{moderator}}` variable for access to the [Member](https://discordpy.readthedocs.io/en/latest/api.html#discord.Member) that added the user.", + "When anonadduser is used, `private_added_to_group_description_anon` is used instead.", + "The public_ variant is used when sending to other thread recipients.", + "See also: `private_added_to_group_title`, `public_added_to_group_description`" + ] + }, + "private_added_to_group_description_anon": { + "default": "A moderator has added you to a Modmail thread.", + "description": "This is the message embed content sent to the recipient that is just added to a thread when adduser is used anonymously.", + "examples": [ + "`{prefix}config set private_added_to_group_description_anon Any message sent here will be sent to all other thread recipients.`" + ], + "notes": [ + "When adduser (no anon) is used, `private_added_to_group_description` is used instead.", + "The public_ variant is used when sending to other thread recipients.", + "See also: `private_added_to_group_title`, `public_added_to_group_description_anon`" + ] + }, + "public_added_to_group_title": { + "default": "New User", + "description": "This is the message embed title sent to all other recipients when someone is added to the thread.", + "examples": [ + "`{prefix}config set public_added_to_group_title Welcome to our new user!`" + ], + "notes": [ + "The private_ variant is used when sending to the new user.", + "See also: `private_added_to_group_title`, `private_added_to_group_title`" + ] + }, + "public_added_to_group_description": { + "default": "\"{{moderator.name}} has added {{users}} to the Modmail thread.\"", + "description": "This is the message embed content sent to all other recipients when someone is added to the thread.", + "examples": [ + "`{prefix}config set public_added_to_group_description Welcome {users}!`" + ], + "notes": [ + "You may use the `{{moderator}}` variable for access to the [Member](https://discordpy.readthedocs.io/en/latest/api.html#discord.Member) that added the user.", + "When anonadduser is used, `public_added_to_group_description_anon` is used instead.", + "The private_ variant is used when sending to the new user.", + "See also: `public_added_to_group_title`, `private_added_to_group_description`" + ] + }, + "public_added_to_group_description_anon": { + "default": "\"A moderator has added {{users}} to the Modmail thread.\"", + "description": "This is the message embed content sent to all other recipients when someone is added to the thread when adduser is used anonymously.", + "examples": [ + "`{prefix}config set public_added_to_group_description_anon Any message sent here will be sent to all other thread recipients.`" + ], + "notes": [ + "When adduser (no anon) is used, `public_added_to_group_description` is used instead.", + "The private_ variant is used when sending to the new user.", + "See also: `public_added_to_group_title`, `private_added_to_group_description_anon`" + ] + }, + "private_removed_from_group_title": { + "default": "Removed From Thread (Group)", + "description": "This is the message embed title sent to the recipient that is just removed from a thread.", + "examples": [ + "`{prefix}config set private_removed_from_group_title Welcome to this new group thread!`" + ], + "notes": [ + "The public_ variant is used when sending to other thread recipients.", + "See also: `private_removed_from_group_description`, `public_removed_from_group_title`" + ] + }, + "private_removed_from_group_description": { + "default": "\"{{moderator.name}} has removed you from the Modmail thread.\"", + "description": "This is the message embed content sent to the recipient that is just removed from a thread.", + "examples": [ + "`{prefix}config set private_removed_from_group_description Bye`" + ], + "notes": [ + "You may use the `{{moderator}}` variable for access to the [Member](https://discordpy.readthedocs.io/en/latest/api.html#discord.Member) that added the user.", + "When anonremoveuser is used, `private_removed_from_group_description_anon` is used instead.", + "The public_ variant is used when sending to other thread recipients.", + "See also: `private_removed_from_group_title`, `public_removed_from_group_description`" + ] + }, + "private_removed_from_group_description_anon": { + "default": "A moderator has removed you from the Modmail thread.", + "description": "This is the message embed content sent to the recipient that is just removed from a thread when removeuser is used anonymously.", + "examples": [ + "`{prefix}config set private_removed_from_group_description_anon You are permenantly removed from this thread.`" + ], + "notes": [ + "When adduser (no anon) is used, `private_removed_from_group_description` is used instead.", + "The public_ variant is used when sending to other thread recipients.", + "See also: `private_removed_from_group_title`, `public_removed_from_group_description_anon`" + ] + }, + "public_removed_from_group_title": { + "default": "User Removed", + "description": "This is the message embed title sent to all other recipients when someone is removed from the thread.", + "examples": [ + "`{prefix}config set public_removed_from_group_title User is now gone!`" + ], + "notes": [ + "The private_ variant is used when sending to the new user.", + "See also: `private_removed_from_group_title`, `private_removed_from_group_title`" + ] + }, + "public_removed_from_group_description": { + "default": "\"{{moderator.name}} has removed {{users}} from the Modmail thread.\"", + "description": "This is the message embed content sent to all other recipients when someone is removed from the thread.", + "examples": [ + "`{prefix}config set public_removed_from_group_description Goodbye {users}!`" + ], + "notes": [ + "You may use the `{{moderator}}` variable for access to the [Member](https://discordpy.readthedocs.io/en/latest/api.html#discord.Member) that added the user.", + "When anonremoveuser is used, `public_removed_from_group_description_anon` is used instead.", + "The private_ variant is used when sending to the new user.", + "See also: `public_removed_from_group_title`, `private_removed_from_group_description`" + ] + }, + "public_removed_from_group_description_anon": { + "default": "\"A moderator has removed {{users}} from the Modmail thread.\"", + "description": "This is the message embed content sent to all other recipients when someone is removed from the thread when removeuser is used anonymously.", + "examples": [ + "`{prefix}config set public_removed_from_group_description_anon Goodbye {users}!`" + ], + "notes": [ + "When adduser (no anon) is used, `public_removed_from_group_description` is used instead.", + "The private_ variant is used when sending to the new user.", + "See also: `public_removed_from_group_title`, `private_removed_from_group_description_anon`" + ] + }, "confirm_thread_creation": { "default": "No", "description": "Ensure users confirm that they want to create a new thread", diff --git a/core/thread.py b/core/thread.py index 29446a32c3..91f30417a8 100644 --- a/core/thread.py +++ b/core/thread.py @@ -60,7 +60,9 @@ def __repr__(self): return f'Thread(recipient="{self.recipient or self.id}", channel={self.channel.id}, other_recipients={len(self._other_recipients)})' def __eq__(self, other): - return self.id == other.id + if isinstance(other, Thread): + return self.id == other.id + return super().__eq__(other) async def wait_until_ready(self) -> None: """Blocks execution until the thread is fully set up.""" @@ -1089,23 +1091,22 @@ async def set_title(self, title: str) -> None: await self.channel.edit(topic=f"Title: {title}\nUser ID: {user_id}\nOther Recipients: {ids}") - async def add_user(self, user: typing.Union[discord.Member, discord.User]) -> None: + async def add_users(self, users: typing.List[typing.Union[discord.Member, discord.User]]) -> None: title = match_title(self.channel.topic) user_id = match_user_id(self.channel.topic) - self._other_recipients.append(user) + self._other_recipients += users ids = ",".join(str(i.id) for i in self._other_recipients) await self.channel.edit(topic=f"Title: {title}\nUser ID: {user_id}\nOther Recipients: {ids}") - async def remove_user(self, user: typing.Union[discord.Member, discord.User]) -> None: + async def remove_users(self, users: typing.List[typing.Union[discord.Member, discord.User]]) -> None: title = match_title(self.channel.topic) user_id = match_user_id(self.channel.topic) - self._other_recipients.remove(user) + for u in users: + self._other_recipients.remove(u) ids = ",".join(str(i.id) for i in self._other_recipients) await self.channel.edit(topic=f"Title: {title}\nUser ID: {user_id}\nOther Recipients: {ids}") - # if user.id in self.manager.cache: - # self.manager.cache.pop(self.id) class ThreadManager: From d240e567bfffb9b80260e913b88256236040f0fb Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Fri, 6 Aug 2021 21:17:10 +0800 Subject: [PATCH 167/209] Formatting --- cogs/modmail.py | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/cogs/modmail.py b/cogs/modmail.py index aeace73192..e8e0a7cc02 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -701,7 +701,9 @@ async def adduser(self, ctx, *users: Union[discord.Member, str]): if not silent: em = discord.Embed( title=self.bot.config["private_added_to_group_title"], - description=self.bot.config["private_added_to_group_description"].format(moderator=ctx.author), + description=self.bot.config["private_added_to_group_description"].format( + moderator=ctx.author + ), color=self.bot.main_color, ) if self.bot.config["show_timestamp"]: @@ -712,7 +714,9 @@ async def adduser(self, ctx, *users: Union[discord.Member, str]): em = discord.Embed( title=self.bot.config["public_added_to_group_title"], - description=self.bot.config["private_added_to_group_description"].format(moderator=ctx.author, users=', '.join(u.name for u in users)), + description=self.bot.config["private_added_to_group_description"].format( + moderator=ctx.author, users=", ".join(u.name for u in users) + ), color=self.bot.main_color, ) if self.bot.config["show_timestamp"]: @@ -767,7 +771,9 @@ async def removeuser(self, ctx, *users: Union[discord.Member, str]): if not silent: em = discord.Embed( title=self.bot.config["private_removed_from_group_title"], - description=self.bot.config["private_removed_from_group_description"].format(moderator=ctx.author), + description=self.bot.config["private_removed_from_group_description"].format( + moderator=ctx.author + ), color=self.bot.main_color, ) if self.bot.config["show_timestamp"]: @@ -778,7 +784,9 @@ async def removeuser(self, ctx, *users: Union[discord.Member, str]): em = discord.Embed( title=self.bot.config["public_removed_from_group_title"], - description=self.bot.config["private_removed_from_group_description"].format(moderator=ctx.author, users=', '.join(u.name for u in users)), + description=self.bot.config["private_removed_from_group_description"].format( + moderator=ctx.author, users=", ".join(u.name for u in users) + ), color=self.bot.main_color, ) if self.bot.config["show_timestamp"]: @@ -845,7 +853,9 @@ async def anonadduser(self, ctx, *users: Union[discord.Member, str]): em = discord.Embed( title=self.bot.config["public_added_to_group_title"], - description=self.bot.config["private_added_to_group_description_anon"].format(moderator=ctx.author, users=', '.join(u.name for u in users)), + description=self.bot.config["private_added_to_group_description_anon"].format( + moderator=ctx.author, users=", ".join(u.name for u in users) + ), color=self.bot.main_color, ) if self.bot.config["show_timestamp"]: @@ -900,7 +910,9 @@ async def anonremoveuser(self, ctx, *users: Union[discord.Member, str]): if not silent: em = discord.Embed( title=self.bot.config["private_removed_from_group_title"], - description=self.bot.config["private_removed_from_group_description_anon"].format(moderator=ctx.author), + description=self.bot.config["private_removed_from_group_description_anon"].format( + moderator=ctx.author + ), color=self.bot.main_color, ) if self.bot.config["show_timestamp"]: @@ -922,7 +934,9 @@ async def anonremoveuser(self, ctx, *users: Union[discord.Member, str]): em = discord.Embed( title=self.bot.config["public_removed_from_group_title"], - description=self.bot.config["private_removed_from_group_description"].format(moderator=ctx.author, users=', '.join(u.name for u in users)), + description=self.bot.config["private_removed_from_group_description"].format( + moderator=ctx.author, users=", ".join(u.name for u in users) + ), color=self.bot.main_color, ) if self.bot.config["show_timestamp"]: From ebcfe4ea118ba9907ed403e04f5fbb3d33393027 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Fri, 6 Aug 2021 21:43:47 +0800 Subject: [PATCH 168/209] Fix formatter, allow add by roles (resolve #3053) --- cogs/modmail.py | 264 +++++++++++++++++++++++++++++------------------- 1 file changed, 161 insertions(+), 103 deletions(-) diff --git a/cogs/modmail.py b/cogs/modmail.py index e8e0a7cc02..74b99dde76 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -671,39 +671,60 @@ async def title(self, ctx, *, name: str): await ctx.message.pin() await self.bot.add_reaction(ctx.message, sent_emoji) - @commands.command(usage=" [options]", cooldown_after_parsing=True) + @commands.command(usage=" [options]", cooldown_after_parsing=True) @checks.has_permissions(PermissionLevel.SUPPORTER) @checks.thread_only() @commands.cooldown(1, 600, BucketType.channel) - async def adduser(self, ctx, *users: Union[discord.Member, str]): + async def adduser(self, ctx, *users_arg: Union[discord.Member, discord.Role, str]): """Adds a user to a modmail thread `options` can be `silent` or `silently`. """ silent = False - for u in users: + users = [] + for u in users_arg: if isinstance(u, str): if "silent" in u or "silently" in u: silent = True - users.remove(u) # remove all strings so users is a List[Member] - else: - # u is a discord.Member - curr_thread = await self.bot.threads.find(recipient=u) - if curr_thread: - em = discord.Embed( - title="Error", - description=f"{u.mention} is already in a thread: {curr_thread.channel.mention}.", - color=self.bot.error_color, - ) - await ctx.send(embed=em) - ctx.command.reset_cooldown(ctx) - return + elif isinstance(u, discord.Role): + users += u.members + elif isinstance(u, discord.Member): + users.append(u) + + for u in users: + # u is a discord.Member + curr_thread = await self.bot.threads.find(recipient=u) + if curr_thread == ctx.thread: + users.remove(u) + continue + + if curr_thread: + em = discord.Embed( + title="Error", + description=f"{u.mention} is already in a thread: {curr_thread.channel.mention}.", + color=self.bot.error_color, + ) + await ctx.send(embed=em) + ctx.command.reset_cooldown(ctx) + return + + if not users: + em = discord.Embed( + title="Error", + description="All users are already in the thread.", + color=self.bot.error_color, + ) + await ctx.send(embed=em) + ctx.command.reset_cooldown(ctx) + return + if not silent: + description = self.bot.formatter.format( + self.bot.config["private_added_to_group_description"], moderator=ctx.author + ) em = discord.Embed( title=self.bot.config["private_added_to_group_title"], - description=self.bot.config["private_added_to_group_description"].format( - moderator=ctx.author - ), + description=description, color=self.bot.main_color, ) if self.bot.config["show_timestamp"]: @@ -712,11 +733,14 @@ async def adduser(self, ctx, *users: Union[discord.Member, str]): for u in users: await u.send(embed=em) + description = self.bot.formatter.format( + self.bot.config["public_added_to_group_description"], + moderator=ctx.author, + users=", ".join(u.name for u in users), + ) em = discord.Embed( title=self.bot.config["public_added_to_group_title"], - description=self.bot.config["private_added_to_group_description"].format( - moderator=ctx.author, users=", ".join(u.name for u in users) - ), + description=description, color=self.bot.main_color, ) if self.bot.config["show_timestamp"]: @@ -731,49 +755,55 @@ async def adduser(self, ctx, *users: Union[discord.Member, str]): sent_emoji, _ = await self.bot.retrieve_emoji() await self.bot.add_reaction(ctx.message, sent_emoji) - @commands.command(usage=" [options]", cooldown_after_parsing=True) + @commands.command(usage=" [options]", cooldown_after_parsing=True) @checks.has_permissions(PermissionLevel.SUPPORTER) @checks.thread_only() @commands.cooldown(1, 600, BucketType.channel) - async def removeuser(self, ctx, *users: Union[discord.Member, str]): + async def removeuser(self, ctx, *users_arg: Union[discord.Member, discord.Role, str]): """Removes a user from a modmail thread `options` can be `silent` or `silently`. """ silent = False - for u in users: + users = [] + for u in users_arg: if isinstance(u, str): if "silent" in u or "silently" in u: silent = True - users.remove(u) # remove all strings so users is a List[Member] - else: - # u is a discord.Member - curr_thread = await self.bot.threads.find(recipient=u) - if ctx.thread != curr_thread: - em = discord.Embed( - title="Error", - description=f"{u.mention} is not in this thread.", - color=self.bot.error_color, - ) - await ctx.send(embed=em) - ctx.command.reset_cooldown(ctx) - return - elif ctx.thread.recipient == u: - em = discord.Embed( - title="Error", - description=f"{u.mention} is the main recipient of the thread and cannot be removed.", - color=self.bot.error_color, - ) - await ctx.send(embed=em) - ctx.command.reset_cooldown(ctx) - return + elif isinstance(u, discord.Role): + users += u.members + elif isinstance(u, discord.Member): + users.append(u) + + for u in users: + # u is a discord.Member + curr_thread = await self.bot.threads.find(recipient=u) + if ctx.thread != curr_thread: + em = discord.Embed( + title="Error", + description=f"{u.mention} is not in this thread.", + color=self.bot.error_color, + ) + await ctx.send(embed=em) + ctx.command.reset_cooldown(ctx) + return + elif ctx.thread.recipient == u: + em = discord.Embed( + title="Error", + description=f"{u.mention} is the main recipient of the thread and cannot be removed.", + color=self.bot.error_color, + ) + await ctx.send(embed=em) + ctx.command.reset_cooldown(ctx) + return if not silent: + description = self.bot.formatter.format( + self.bot.config["private_removed_from_group_description"], moderator=ctx.author + ) em = discord.Embed( title=self.bot.config["private_removed_from_group_title"], - description=self.bot.config["private_removed_from_group_description"].format( - moderator=ctx.author - ), + description=description, color=self.bot.main_color, ) if self.bot.config["show_timestamp"]: @@ -782,11 +812,14 @@ async def removeuser(self, ctx, *users: Union[discord.Member, str]): for u in users: await u.send(embed=em) + description = self.bot.formatter.format( + self.bot.config["public_removed_from_group_description"], + moderator=ctx.author, + users=", ".join(u.name for u in users), + ) em = discord.Embed( title=self.bot.config["public_removed_from_group_title"], - description=self.bot.config["private_removed_from_group_description"].format( - moderator=ctx.author, users=", ".join(u.name for u in users) - ), + description=description, color=self.bot.main_color, ) if self.bot.config["show_timestamp"]: @@ -801,33 +834,52 @@ async def removeuser(self, ctx, *users: Union[discord.Member, str]): sent_emoji, _ = await self.bot.retrieve_emoji() await self.bot.add_reaction(ctx.message, sent_emoji) - @commands.command(usage=" [options]", cooldown_after_parsing=True) + @commands.command(usage=" [options]", cooldown_after_parsing=True) @checks.has_permissions(PermissionLevel.SUPPORTER) @checks.thread_only() @commands.cooldown(1, 600, BucketType.channel) - async def anonadduser(self, ctx, *users: Union[discord.Member, str]): + async def anonadduser(self, ctx, *users_arg: Union[discord.Member, discord.Role, str]): """Adds a user to a modmail thread anonymously `options` can be `silent` or `silently`. """ silent = False - for u in users: + users = [] + for u in users_arg: if isinstance(u, str): if "silent" in u or "silently" in u: silent = True - users.remove(u) # remove all strings so users is a List[Member] - else: - # u is a discord.Member - curr_thread = await self.bot.threads.find(recipient=u) - if curr_thread: - em = discord.Embed( - title="Error", - description=f"{u.mention} is already in a thread: {curr_thread.channel.mention}.", - color=self.bot.error_color, - ) - await ctx.send(embed=em) - ctx.command.reset_cooldown(ctx) - return + elif isinstance(u, discord.Role): + users += u.members + elif isinstance(u, discord.Member): + users.append(u) + + for u in users: + curr_thread = await self.bot.threads.find(recipient=u) + if curr_thread == ctx.thread: + users.remove(u) + continue + + if curr_thread: + em = discord.Embed( + title="Error", + description=f"{u.mention} is already in a thread: {curr_thread.channel.mention}.", + color=self.bot.error_color, + ) + await ctx.send(embed=em) + ctx.command.reset_cooldown(ctx) + return + + if not users: + em = discord.Embed( + title="Error", + description="All users are already in the thread.", + color=self.bot.error_color, + ) + await ctx.send(embed=em) + ctx.command.reset_cooldown(ctx) + return + if not silent: em = discord.Embed( title=self.bot.config["private_added_to_group_title"], @@ -851,11 +903,13 @@ async def anonadduser(self, ctx, *users: Union[discord.Member, str]): for u in users: await u.send(embed=em) + description = self.bot.formatter.format( + self.bot.config["public_added_to_group_description_anon"], + users=", ".join(u.name for u in users), + ) em = discord.Embed( title=self.bot.config["public_added_to_group_title"], - description=self.bot.config["private_added_to_group_description_anon"].format( - moderator=ctx.author, users=", ".join(u.name for u in users) - ), + description=description, color=self.bot.main_color, ) if self.bot.config["show_timestamp"]: @@ -870,49 +924,51 @@ async def anonadduser(self, ctx, *users: Union[discord.Member, str]): sent_emoji, _ = await self.bot.retrieve_emoji() await self.bot.add_reaction(ctx.message, sent_emoji) - @commands.command(usage=" [options]", cooldown_after_parsing=True) + @commands.command(usage=" [options]", cooldown_after_parsing=True) @checks.has_permissions(PermissionLevel.SUPPORTER) @checks.thread_only() @commands.cooldown(1, 600, BucketType.channel) - async def anonremoveuser(self, ctx, *users: Union[discord.Member, str]): + async def anonremoveuser(self, ctx, *users_arg: Union[discord.Member, discord.Role, str]): """Removes a user from a modmail thread anonymously `options` can be `silent` or `silently`. """ silent = False - for u in users: + users = [] + for u in users_arg: if isinstance(u, str): if "silent" in u or "silently" in u: silent = True - users.remove(u) # remove all strings so users is a List[Member] - else: - # u is a discord.Member - curr_thread = await self.bot.threads.find(recipient=u) - if ctx.thread != curr_thread: - em = discord.Embed( - title="Error", - description=f"{u.mention} is not in this thread.", - color=self.bot.error_color, - ) - await ctx.send(embed=em) - ctx.command.reset_cooldown(ctx) - return - elif ctx.thread.recipient == u: - em = discord.Embed( - title="Error", - description=f"{u.mention} is the main recipient of the thread and cannot be removed.", - color=self.bot.error_color, - ) - await ctx.send(embed=em) - ctx.command.reset_cooldown(ctx) - return + elif isinstance(u, discord.Role): + users += u.members + elif isinstance(u, discord.Member): + users.append(u) + + for u in users: + curr_thread = await self.bot.threads.find(recipient=u) + if ctx.thread != curr_thread: + em = discord.Embed( + title="Error", + description=f"{u.mention} is not in this thread.", + color=self.bot.error_color, + ) + await ctx.send(embed=em) + ctx.command.reset_cooldown(ctx) + return + elif ctx.thread.recipient == u: + em = discord.Embed( + title="Error", + description=f"{u.mention} is the main recipient of the thread and cannot be removed.", + color=self.bot.error_color, + ) + await ctx.send(embed=em) + ctx.command.reset_cooldown(ctx) + return if not silent: em = discord.Embed( title=self.bot.config["private_removed_from_group_title"], - description=self.bot.config["private_removed_from_group_description_anon"].format( - moderator=ctx.author - ), + description=self.bot.config["private_removed_from_group_description_anon"], color=self.bot.main_color, ) if self.bot.config["show_timestamp"]: @@ -932,11 +988,13 @@ async def anonremoveuser(self, ctx, *users: Union[discord.Member, str]): for u in users: await u.send(embed=em) + description = self.bot.formatter.format( + self.bot.config["public_removed_from_group_description_anon"], + users=", ".join(u.name for u in users), + ) em = discord.Embed( title=self.bot.config["public_removed_from_group_title"], - description=self.bot.config["private_removed_from_group_description"].format( - moderator=ctx.author, users=", ".join(u.name for u in users) - ), + description=description, color=self.bot.main_color, ) if self.bot.config["show_timestamp"]: From 835390b80c8f4a36bd19aa77d19370b899f5c8b6 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Fri, 6 Aug 2021 21:53:04 +0800 Subject: [PATCH 169/209] Remove tagging and case insensitive plugins --- plugins/registry.json | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/plugins/registry.json b/plugins/registry.json index 0ba98f499c..a805360b57 100644 --- a/plugins/registry.json +++ b/plugins/registry.json @@ -1,13 +1,4 @@ { - "case_insensitive_snippets": { - "repository": "python-discord/modmail-plugins", - "branch": "main", - "description": "Allow snippets to be ran even if with the wrong case. For example, ?Dm-RePort will be recognized as ?dm-report.", - "bot_version": "2.20.1", - "title": "Case insensitive snippets", - "icon_url": "https://i.imgur.com/kaJYlMB.png", - "thumbnail_url": "https://i.imgur.com/kaJYlMB.png" - }, "close_message": { "repository": "python-discord/modmail-plugins", "branch": "main", @@ -35,15 +26,6 @@ "icon_url": "https://i.imgur.com/FtRQveT.png", "thumbnail_url": "https://i.imgur.com/FtRQveT.png" }, - "tagging": { - "repository": "python-discord/modmail-plugins", - "branch": "main", - "description": "Add a ?tag command capable of adding a $message| header to the channel name.", - "bot_version": "2.20.1", - "title": "Tagging", - "icon_url": "https://i.imgur.com/WajSLB1.png", - "thumbnail_url": "https://i.imgur.com/WajSLB1.png" - }, "dragory-migrate": { "repository": "kyb3r/modmail-plugins", "branch": "master", From fe11759e5b3e11dc5f7623904a7a98c5e6d61d09 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Fri, 6 Aug 2021 21:54:28 +0800 Subject: [PATCH 170/209] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d398e71f2f..28e5151b07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ v3.10 adds group conversations while resolving othre bugs and QOL changes. It is - `?contact` now works properly with both category and silent. ([GH #3076](https://github.com/kyb3r/modmail/issues/3076)) - `close_on_leave_reason` now works properly when `close_on_leave` is enabled. ([GH #3075](https://github.com/kyb3r/modmail/issues/3075)) - Invalid arguments are now properly catched and a proper error message is sent. +- Update database after resetting/purging all plugins. ([GH #3011](https://github.com/kyb3r/modmail/pull/3011)) ## Internal From 1a885bc56ef82e87fdec72ed540e3fb021567069 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sat, 7 Aug 2021 20:14:32 +0800 Subject: [PATCH 171/209] Use highest hoisted role as default tag, resolves #3014 --- CHANGELOG.md | 3 ++- README.md | 2 +- bot.py | 2 +- cogs/modmail.py | 4 ++-- core/thread.py | 5 +++-- core/utils.py | 8 ++++++++ pyproject.toml | 2 +- 7 files changed, 18 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28e5151b07..a18c8d8eb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. -# v3.10.0-dev4 +# v3.10.0-dev5 v3.10 adds group conversations while resolving othre bugs and QOL changes. It is potentially breaking to some plugins that adds functionality to threads. @@ -18,6 +18,7 @@ v3.10 adds group conversations while resolving othre bugs and QOL changes. It is - Ability to have group conversations. ([GH #143](https://github.com/kyb3r/modmail/issues/143)) - Snippets are invoked case insensitively. ([GH #3077](https://github.com/kyb3r/modmail/issues/3077), [PR #3080](https://github.com/kyb3r/modmail/pull/3080)) +- Default tags now use top hoisted role. ([GH #3014](https://github.com/kyb3r/modmail/issues/3014)) ## Fixed diff --git a/README.md b/README.md index 7659898399..42a8f812ec 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@
- +
diff --git a/bot.py b/bot.py index 26fac619d2..9a61101707 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "v3.10.0-dev4" +__version__ = "v3.10.0-dev5" import asyncio diff --git a/cogs/modmail.py b/cogs/modmail.py index 74b99dde76..c7be64e046 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -891,7 +891,7 @@ async def anonadduser(self, ctx, *users_arg: Union[discord.Member, discord.Role, tag = self.bot.config["mod_tag"] if tag is None: - tag = str(ctx.author.top_role) + tag = str(get_top_hoisted_role(ctx.author)) name = self.bot.config["anon_username"] if name is None: name = tag @@ -976,7 +976,7 @@ async def anonremoveuser(self, ctx, *users_arg: Union[discord.Member, discord.Ro tag = self.bot.config["mod_tag"] if tag is None: - tag = str(ctx.author.top_role) + tag = str(get_top_hoisted_role(ctx.author)) name = self.bot.config["anon_username"] if name is None: name = tag diff --git a/core/thread.py b/core/thread.py index 91f30417a8..b2adbffe76 100644 --- a/core/thread.py +++ b/core/thread.py @@ -22,6 +22,7 @@ match_other_recipients, truncate, format_channel_name, + get_top_hoisted_role, ) logger = getLogger(__name__) @@ -888,7 +889,7 @@ async def send( # Anonymously sending to the user. tag = self.bot.config["mod_tag"] if tag is None: - tag = str(author.top_role) + tag = str(get_top_hoisted_role(author)) name = self.bot.config["anon_username"] if name is None: name = tag @@ -1005,7 +1006,7 @@ async def send( elif not anonymous: mod_tag = self.bot.config["mod_tag"] if mod_tag is None: - mod_tag = str(message.author.top_role) + mod_tag = str(get_top_hoisted_role(message.author)) embed.set_footer(text=mod_tag) # Normal messages else: embed.set_footer(text=self.bot.config["anon_tag"]) diff --git a/core/utils.py b/core/utils.py index 173eda25d5..8f4e1a01ff 100644 --- a/core/utils.py +++ b/core/utils.py @@ -32,6 +32,7 @@ "escape_code_block", "format_channel_name", "tryint", + "get_top_hoisted_role", ] @@ -394,3 +395,10 @@ def tryint(x): return int(x) except (ValueError, TypeError): return x + + +def get_top_hoisted_role(member: discord.Member): + roles = sorted(member.roles, key=lambda r: r.position, reverse=True) + for role in roles: + if role.hoist: + return role diff --git a/pyproject.toml b/pyproject.toml index e038685a61..1d97e42e33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ extend-exclude = ''' [tool.poetry] name = 'Modmail' -version = '3.10.0-dev4' +version = '3.10.0-dev5' description = "Modmail is similar to Reddit's Modmail, both in functionality and purpose. It serves as a shared inbox for server staff to communicate with their users in a seamless way." license = 'AGPL-3.0-only' authors = [ From 3407e0510208a5b509d3f5059ef5f3d6598edc11 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sat, 7 Aug 2021 20:24:11 +0800 Subject: [PATCH 172/209] Reload thread cache only when it's the first on_ready trigger. Resolves #3037 --- CHANGELOG.md | 1 + bot.py | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a18c8d8eb7..d8768247c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ v3.10 adds group conversations while resolving othre bugs and QOL changes. It is - `thread.reply` now returns mod_message, user_message1, user_message2... It is no longer limited at a size 2 tuple. Potentially breaking if plugins depend on this behaviour. - Fix return types, type hints, and unresolved references ([PR #3009](https://github.com/kyb3r/modmail/pull/3009)) +- Reload thread cache only when it's the first on_ready trigger. ([GH #3037](https://github.com/kyb3r/modmail/issues/3037)) # v3.9.5 diff --git a/bot.py b/bot.py index 9a61101707..8aa3f00d1a 100644 --- a/bot.py +++ b/bot.py @@ -74,6 +74,7 @@ def __init__(self): self.loaded_cogs = ["cogs.modmail", "cogs.plugins", "cogs.utility"] self._connected = asyncio.Event() self.start_time = datetime.utcnow() + self._started = False self.config = ConfigManager(self) self.config.populate_cache() @@ -534,6 +535,13 @@ async def on_ready(self): logger.error("Logging out due to invalid GUILD_ID.") return await self.close() + if self._started: + # Bot has started before + logger.line() + logger.warning("Bot restarted due to internal discord reloading.") + logger.line() + return + logger.line() logger.debug("Client ready.") logger.info("Logged in as: %s", self.user) @@ -634,6 +642,8 @@ async def on_ready(self): ) logger.warning("If the external servers are valid, you may ignore this message.") + self._started = True + async def convert_emoji(self, name: str) -> str: ctx = SimpleNamespace(bot=self, guild=self.modmail_guild) converter = commands.EmojiConverter() From 20b31f8e8b5497943513997fef788d72ae668438 Mon Sep 17 00:00:00 2001 From: Jerrie-Aries <70805800+Jerrie-Aries@users.noreply.github.com> Date: Sat, 7 Aug 2021 20:48:35 +0800 Subject: [PATCH 173/209] Resolve linked messages issues, improve group functionality (bugfixes), resolves #3041 --- CHANGELOG.md | 1 + bot.py | 17 ++--- core/thread.py | 165 +++++++++++++++++++++++++++++-------------------- core/utils.py | 79 +++++++++++++++++++++-- 4 files changed, 179 insertions(+), 83 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8768247c9..6952839164 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ v3.10 adds group conversations while resolving othre bugs and QOL changes. It is - `close_on_leave_reason` now works properly when `close_on_leave` is enabled. ([GH #3075](https://github.com/kyb3r/modmail/issues/3075)) - Invalid arguments are now properly catched and a proper error message is sent. - Update database after resetting/purging all plugins. ([GH #3011](https://github.com/kyb3r/modmail/pull/3011)) +- `thread_auto_close` timer now only resets on non-note and replies from mods. ([GH #3030](https://github.com/kyb3r/modmail/issues/3030)) ## Internal diff --git a/bot.py b/bot.py index 8aa3f00d1a..b1b4ad046c 100644 --- a/bot.py +++ b/bot.py @@ -1265,7 +1265,7 @@ async def handle_reaction_events(self, payload): if not thread.recipient.dm_channel: await thread.recipient.create_dm() try: - linked_message = await thread.find_linked_message_from_dm(message, either_direction=True) + linked_messages = await thread.find_linked_message_from_dm(message, either_direction=True) except ValueError as e: logger.warning("Failed to find linked message for reactions: %s", e) return @@ -1274,19 +1274,19 @@ async def handle_reaction_events(self, payload): if not thread: return try: - _, *linked_message = await thread.find_linked_messages(message.id, either_direction=True) + _, *linked_messages = await thread.find_linked_messages(message.id, either_direction=True) except ValueError as e: logger.warning("Failed to find linked message for reactions: %s", e) return - if self.config["transfer_reactions"] and linked_message is not [None]: + if self.config["transfer_reactions"] and linked_messages is not [None]: if payload.event_type == "REACTION_ADD": - for msg in linked_message: + for msg in linked_messages: await self.add_reaction(msg, reaction) await self.add_reaction(message, reaction) else: try: - for msg in linked_message: + for msg in linked_messages: await msg.remove_reaction(reaction, self.user) await message.remove_reaction(reaction, self.user) except (discord.HTTPException, discord.InvalidArgument) as e: @@ -1432,13 +1432,6 @@ async def on_message_delete(self, message): if not thread: return - audit_logs = self.modmail_guild.audit_logs(limit=10, action=discord.AuditLogAction.message_delete) - - entry = await audit_logs.find(lambda a: a.target == self.user) - - if entry is None: - return - try: await thread.delete_message(message, note=False) embed = discord.Embed(description="Successfully deleted message.", color=self.main_color) diff --git a/core/thread.py b/core/thread.py index b2adbffe76..89cdceb903 100644 --- a/core/thread.py +++ b/core/thread.py @@ -21,8 +21,9 @@ match_user_id, match_other_recipients, truncate, - format_channel_name, get_top_hoisted_role, + create_thread_channel, + get_joint_id, ) logger = getLogger(__name__) @@ -36,7 +37,7 @@ def __init__( manager: "ThreadManager", recipient: typing.Union[discord.Member, discord.User, int], channel: typing.Union[discord.DMChannel, discord.TextChannel] = None, - other_recipients: typing.List[typing.Union[discord.Member, discord.User]] = [], + other_recipients: typing.List[typing.Union[discord.Member, discord.User]] = None, ): self.manager = manager self.bot = manager.bot @@ -48,7 +49,7 @@ def __init__( raise CommandError("Recipient cannot be a bot.") self._id = recipient.id self._recipient = recipient - self._other_recipients = other_recipients + self._other_recipients = other_recipients or [] self._channel = channel self.genesis_message = None self._ready_event = asyncio.Event() @@ -127,9 +128,13 @@ async def from_channel(cls, manager: "ThreadManager", channel: discord.TextChann else: recipient = manager.bot.get_user(recipient_id) or await manager.bot.fetch_user(recipient_id) - other_recipients = match_other_recipients(channel.topic) - for n, uid in enumerate(other_recipients): - other_recipients[n] = manager.bot.get_user(uid) or await manager.bot.fetch_user(uid) + other_recipients = [] + for uid in match_other_recipients(channel.topic): + try: + other_recipient = manager.bot.get_user(uid) or await manager.bot.fetch_user(uid) + except discord.NotFound: + continue + other_recipients.append(other_recipient) thread = cls(manager, recipient or recipient_id, channel, other_recipients) @@ -149,33 +154,19 @@ async def setup(self, *, creator=None, category=None, initial_message=None): overwrites = None try: - channel = await self.bot.modmail_guild.create_text_channel( - name=format_channel_name(self.bot, recipient), - category=category, - overwrites=overwrites, - reason="Creating a thread channel.", - ) - except discord.HTTPException as e: - # try again but null-discrim (name could be banned) - try: - channel = await self.bot.modmail_guild.create_text_channel( - name=format_channel_name(self.bot, recipient, force_null=True), - category=category, - overwrites=overwrites, - reason="Creating a thread channel.", - ) - except discord.HTTPException as e: # Failed to create due to missing perms. - logger.critical("An error occurred while creating a thread.", exc_info=True) - self.manager.cache.pop(self.id) + channel = await create_thread_channel(self.bot, recipient, category, overwrites) + except discord.HTTPException as e: # Failed to create due to missing perms. + logger.critical("An error occurred while creating a thread.", exc_info=True) + self.manager.cache.pop(self.id) - embed = discord.Embed(color=self.bot.error_color) - embed.title = "Error while trying to create a thread." - embed.description = str(e) - embed.add_field(name="Recipient", value=recipient.mention) + embed = discord.Embed(color=self.bot.error_color) + embed.title = "Error while trying to create a thread." + embed.description = str(e) + embed.add_field(name="Recipient", value=recipient.mention) - if self.bot.log_channel is not None: - await self.bot.log_channel.send(embed=embed) - return + if self.bot.log_channel is not None: + await self.bot.log_channel.send(embed=embed) + return self._channel = channel @@ -209,7 +200,7 @@ async def send_genesis_message(): logger.error("Failed unexpectedly:", exc_info=True) async def send_recipient_genesis_message(): - # Once thread is ready, tell the recipient. + # Once thread is ready, tell the recipient (don't send if using contact on others) thread_creation_response = self.bot.config["thread_creation_response"] embed = discord.Embed( @@ -585,7 +576,7 @@ async def find_linked_messages( either_direction: bool = False, message1: discord.Message = None, note: bool = True, - ) -> typing.Tuple[discord.Message, typing.Optional[discord.Message]]: + ) -> typing.Tuple[discord.Message, typing.List[typing.Optional[discord.Message]]]: if message1 is not None: if not message1.embeds or not message1.embeds[0].author.url or message1.author != self.bot.user: raise ValueError("Malformed thread message.") @@ -698,49 +689,84 @@ async def delete_message( if tasks: await asyncio.gather(*tasks) - async def find_linked_message_from_dm(self, message, either_direction=False): - if either_direction and message.embeds and message.embeds[0].author.url: - compare_url = message.embeds[0].author.url - compare_id = compare_url.split("#")[-1] - else: - compare_url = None - compare_id = None + async def find_linked_message_from_dm( + self, message, either_direction=False + ) -> typing.List[discord.Message]: + + joint_id = None + if either_direction: + joint_id = get_joint_id(message) + # could be None too, if that's the case we'll reassign this variable from + # thread message we fetch in the next step + linked_messages = [] if self.channel is not None: - async for linked_message in self.channel.history(): - if not linked_message.embeds: + async for msg in self.channel.history(): + if not msg.embeds: continue - url = linked_message.embeds[0].author.url - if not url: - continue - if url == compare_url: - return linked_message - msg_id = url.split("#")[-1] - if not msg_id.isdigit(): + msg_joint_id = get_joint_id(msg) + if msg_joint_id is None: continue - msg_id = int(msg_id) - if int(msg_id) == message.id: - return linked_message - if compare_id is not None and compare_id.isdigit(): - if int(msg_id) == int(compare_id): - return linked_message + if msg_joint_id == message.id: + linked_messages.append(msg) + break + if joint_id is not None and msg_joint_id == joint_id: + linked_messages.append(msg) + break + else: + raise ValueError("Thread channel message not found.") + else: raise ValueError("Thread channel message not found.") + if joint_id is None: + joint_id = get_joint_id(linked_messages[0]) + if joint_id is None: + # still None, supress this and return the thread message + logger.error("Malformed thread message.") + return linked_messages + + for user in self.recipients: + if user.dm_channel == message.channel: + continue + async for other_msg in user.history(): + if either_direction: + if other_msg.id == joint_id: + linked_messages.append(other_msg) + break + + if not other_msg.embeds: + continue + + other_joint_id = get_joint_id(other_msg) + if other_joint_id is not None and other_joint_id == joint_id: + linked_messages.append(other_msg) + break + else: + logger.error("Linked message from recipient %s not found.", user) + + return linked_messages + async def edit_dm_message(self, message: discord.Message, content: str) -> None: try: - linked_message = await self.find_linked_message_from_dm(message) + linked_messages = await self.find_linked_message_from_dm(message) except ValueError: logger.warning("Failed to edit message.", exc_info=True) raise - embed = linked_message.embeds[0] - embed.add_field(name="**Edited, former message:**", value=embed.description) - embed.description = content - await asyncio.gather(self.bot.api.edit_message(message.id, content), linked_message.edit(embed=embed)) - async def note(self, message: discord.Message, persistent=False, thread_creation=False) -> None: + for msg in linked_messages: + embed = msg.embeds[0] + if isinstance(msg.channel, discord.TextChannel): + # just for thread channel, we put the old message in embed field + embed.add_field(name="**Edited, former message:**", value=embed.description) + embed.description = content + await asyncio.gather(self.bot.api.edit_message(message.id, content), msg.edit(embed=embed)) + + async def note( + self, message: discord.Message, persistent=False, thread_creation=False + ) -> discord.Message: if not message.content and not message.attachments: raise MissingRequiredArgument(SimpleNamespace(name="msg")) @@ -758,7 +784,9 @@ async def note(self, message: discord.Message, persistent=False, thread_creation return msg - async def reply(self, message: discord.Message, anonymous: bool = False, plain: bool = False) -> None: + async def reply( + self, message: discord.Message, anonymous: bool = False, plain: bool = False + ) -> typing.Tuple[discord.Message, discord.Message]: if not message.content and not message.attachments: raise MissingRequiredArgument(SimpleNamespace(name="msg")) if not any(g.get_member(self.id) for g in self.bot.guilds): @@ -854,7 +882,8 @@ async def send( thread_creation: bool = False, ) -> None: - self.bot.loop.create_task(self._restart_close_timer()) # Start or restart thread auto close + if not note and from_mod: + self.bot.loop.create_task(self._restart_close_timer()) # Start or restart thread auto close if self.close_task is not None: # cancel closing if a thread message is sent. @@ -1208,9 +1237,13 @@ async def _find_from_channel(self, channel): except discord.NotFound: recipient = None - other_recipients = match_other_recipients(channel.topic) - for n, uid in enumerate(other_recipients): - other_recipients[n] = self.bot.get_user(uid) or await self.bot.fetch_user(uid) + other_recipients = [] + for uid in match_other_recipients(channel.topic): + try: + other_recipient = self.bot.get_user(uid) or await self.bot.fetch_user(uid) + except discord.NotFound: + continue + other_recipients.append(other_recipient) if recipient is None: thread = Thread(self, user_id, channel, other_recipients) diff --git a/core/utils.py b/core/utils.py index 8f4e1a01ff..3b75e11014 100644 --- a/core/utils.py +++ b/core/utils.py @@ -33,6 +33,7 @@ "format_channel_name", "tryint", "get_top_hoisted_role", + "get_joint_id", ] @@ -224,7 +225,7 @@ def cleanup_code(content: str) -> str: TOPIC_UID_REGEX = re.compile(r"\bUser ID:\s*(\d{17,21})\b", flags=re.IGNORECASE) -def match_title(text: str) -> int: +def match_title(text: str) -> str: """ Matches a title in the format of "Title: XXXX" @@ -236,7 +237,7 @@ def match_title(text: str) -> int: Returns ------- Optional[str] - The title if found + The title if found. """ match = TOPIC_TITLE_REGEX.search(text) if match is not None: @@ -263,7 +264,7 @@ def match_user_id(text: str) -> int: return -1 -def match_other_recipients(text: str) -> int: +def match_other_recipients(text: str) -> typing.List[int]: """ Matches a title in the format of "Other Recipients: XXXX,XXXX" @@ -274,8 +275,8 @@ def match_other_recipients(text: str) -> int: Returns ------- - Optional[str] - The title if found + List[int] + The list of other recipients IDs. """ match = TOPIC_OTHER_RECIPIENTS_REGEX.search(text) if match is not None: @@ -402,3 +403,71 @@ def get_top_hoisted_role(member: discord.Member): for role in roles: if role.hoist: return role + + +async def create_thread_channel(bot, recipient, category, overwrites, *, name=None, errors_raised=[]): + name = name or format_channel_name(bot, recipient) + try: + channel = await bot.modmail_guild.create_text_channel( + name=name, + category=category, + overwrites=overwrites, + reason="Creating a thread channel.", + ) + except discord.HTTPException as e: + if (e.text, (category, name)) in errors_raised: + # Just raise the error to prevent infinite recursion after retrying + raise + + errors_raised.append((e.text, (category, name))) + + if "Maximum number of channels in category reached" in e.text: + fallback_id = bot.config["fallback_category_id"] + if fallback_id: + fallback = discord.utils.get(cat.guild.categories, id=int(fallback_id)) + if fallback and len(fallback.channels) < 49: + category = fallback + + if not category: + category = await category.clone(name="Fallback Modmail") + bot.config.set("fallback_category_id", str(category.id)) + await bot.config.update() + + return await create_thread_channel( + bot, recipient, category, overwrites, errors_raised=errors_raised + ) + + if "Contains words not allowed" in e.text: + # try again but null-discrim (name could be banned) + return await create_thread_channel( + bot, + recipient, + category, + overwrites, + name=format_channel_name(bot, recipient, force_null=True), + errors_raised=errors_raised, + ) + + raise + + return channel + + +def get_joint_id(message: discord.Message) -> typing.Optional[int]: + """ + Get the joint ID from `discord.Embed().author.url`. + Parameters + ----------- + message : discord.Message + The discord.Message object. + Returns + ------- + int + The joint ID if found. Otherwise, None. + """ + if message.embeds: + try: + return int(getattr(message.embeds[0].author, "url", "").split("#")[-1]) + except ValueError: + pass + return None From ddbee35bc15e858e5b22e622f86ea6b9ea639213 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sat, 7 Aug 2021 21:23:24 +0800 Subject: [PATCH 174/209] Persistent notes are now properly deleted,resolve #3013 --- CHANGELOG.md | 2 ++ core/thread.py | 10 +++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6952839164..e7dc84c3d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,8 @@ v3.10 adds group conversations while resolving othre bugs and QOL changes. It is - Invalid arguments are now properly catched and a proper error message is sent. - Update database after resetting/purging all plugins. ([GH #3011](https://github.com/kyb3r/modmail/pull/3011)) - `thread_auto_close` timer now only resets on non-note and replies from mods. ([GH #3030](https://github.com/kyb3r/modmail/issues/3030)) +- Deleted messages are now deleted on both ends. ([GH #3041](https://github.com/kyb3r/modmail/issues/3041), [@JerrieAries](https://github.com/kyb3r/modmail/commit/20b31f8e8b5497943513997fef788d72ae668438)) +- Persistent notes are now properly deleted from the database. ([GH #3013](https://github.com/kyb3r/modmail/issues/3013)) ## Internal diff --git a/core/thread.py b/core/thread.py index 89cdceb903..b34e14ed01 100644 --- a/core/thread.py +++ b/core/thread.py @@ -679,13 +679,17 @@ async def delete_message( else: message1, *message2 = await self.find_linked_messages(message, note=note) tasks = [] + if not isinstance(message, discord.Message): tasks += [message1.delete()] - elif message2 is not [None]: - for m2 in message2: + + for m2 in message2: + if m2 is not None: tasks += [m2.delete()] - elif message1.embeds[0].author.name.startswith("Persistent Note"): + + if message1.embeds[0].author.name.startswith("Persistent Note"): tasks += [self.bot.api.delete_note(message1.id)] + if tasks: await asyncio.gather(*tasks) From a71c1c79fa183be387dbe3cea274fa327ab89e35 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sat, 7 Aug 2021 21:32:24 +0800 Subject: [PATCH 175/209] Fix changelog formatting --- CHANGELOG.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7dc84c3d1..1ba3fd326b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,19 +8,20 @@ however, insignificant breaking changes do not guarantee a major version bump, s # v3.10.0-dev5 -v3.10 adds group conversations while resolving othre bugs and QOL changes. It is potentially breaking to some plugins that adds functionality to threads. +v3.10 adds group conversations while resolving other bugs and QOL changes. It is potentially breaking to some plugins that adds functionality to threads. -## Breaking +### Breaking - `Thread.recipient` (`str`) is now `Thread.recipients` (`List[str]`). +- `thread.reply` now returns mod_message, user_message1, user_message2... It is no longer limited at a size 2 tuple. -## Added +### Added - Ability to have group conversations. ([GH #143](https://github.com/kyb3r/modmail/issues/143)) - Snippets are invoked case insensitively. ([GH #3077](https://github.com/kyb3r/modmail/issues/3077), [PR #3080](https://github.com/kyb3r/modmail/pull/3080)) - Default tags now use top hoisted role. ([GH #3014](https://github.com/kyb3r/modmail/issues/3014)) -## Fixed +### Fixed - Certain situations where the internal thread cache breaks and spams new channels. ([GH #3022](https://github.com/kyb3r/modmail/issues/3022), [PR #3028](https://github.com/kyb3r/modmail/pull/3028)) - Blocked users are now no longer allowed to use `?contact` and react to contact. ([COMMENT #819004157](https://github.com/kyb3r/modmail/issues/2969#issuecomment-819004157), [PR #3027](https://github.com/kyb3r/modmail/pull/3027)) @@ -34,15 +35,14 @@ v3.10 adds group conversations while resolving othre bugs and QOL changes. It is - Deleted messages are now deleted on both ends. ([GH #3041](https://github.com/kyb3r/modmail/issues/3041), [@JerrieAries](https://github.com/kyb3r/modmail/commit/20b31f8e8b5497943513997fef788d72ae668438)) - Persistent notes are now properly deleted from the database. ([GH #3013](https://github.com/kyb3r/modmail/issues/3013)) -## Internal - -- `thread.reply` now returns mod_message, user_message1, user_message2... It is no longer limited at a size 2 tuple. Potentially breaking if plugins depend on this behaviour. -- Fix return types, type hints, and unresolved references ([PR #3009](https://github.com/kyb3r/modmail/pull/3009)) +### Internal +def c +- Fix return types, type hints and unresolved references ([PR #3009](https://github.com/kyb3r/modmail/pull/3009)) - Reload thread cache only when it's the first on_ready trigger. ([GH #3037](https://github.com/kyb3r/modmail/issues/3037)) # v3.9.5 -## Internal +### Internal - Bumped discord.py to v1.7.3, updated all other packages to latest. - More debug log files are now kept. @@ -50,28 +50,28 @@ v3.10 adds group conversations while resolving othre bugs and QOL changes. It is # v3.9.4 -## Fixed +### Fixed - Certain cases where fallback categories were not working as intended. ([GH #3002](https://github.com/kyb3r/modmail/issues/3002), [PR #3003](https://github.com/kyb3r/modmail/pull/3003)) - There is now a proper message when trying to contact a bot. -## Improved +### Improved - `?mention` can now be disabled with `?mention disable`. ([PR #2993](https://github.com/kyb3r/modmail/pull/2993/files)) - `?mention` now allows vague entries such as `everyone` or `all`. ([PR #2993](https://github.com/kyb3r/modmail/pull/2993/files)) -## Internal +### Internal - Change heroku python version to 3.9.4 ([PR #3001](https://github.com/kyb3r/modmail/pull/3001)) # v3.9.3 -## Added +### Added - New config: `use_user_id_channel_name`, when set to TRUE, channel names would get created with the recipient's ID instead of their name and discriminator. - This is now an option to better suit the needs of servers in Server Discovery -## Internal +### Internal - Signature of `format_channel_name` in core/util.py changed to: - `format_channel_name(bot, author, exclude_channel=None, force_null=False)` From 476994a7cb22c586dc2d44ef85b1d2ac36446eb4 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sat, 7 Aug 2021 21:33:54 +0800 Subject: [PATCH 176/209] Fix changelog formatting --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ba3fd326b..cfdb98fce3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,6 @@ v3.10 adds group conversations while resolving other bugs and QOL changes. It is - Persistent notes are now properly deleted from the database. ([GH #3013](https://github.com/kyb3r/modmail/issues/3013)) ### Internal -def c - Fix return types, type hints and unresolved references ([PR #3009](https://github.com/kyb3r/modmail/pull/3009)) - Reload thread cache only when it's the first on_ready trigger. ([GH #3037](https://github.com/kyb3r/modmail/issues/3037)) From 68ede9d5d41448a76666ca47ffd816fa83f3fc2d Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sat, 7 Aug 2021 21:34:03 +0800 Subject: [PATCH 177/209] Fix changelog formatting --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfdb98fce3..5cfaddb926 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ v3.10 adds group conversations while resolving other bugs and QOL changes. It is - Persistent notes are now properly deleted from the database. ([GH #3013](https://github.com/kyb3r/modmail/issues/3013)) ### Internal + - Fix return types, type hints and unresolved references ([PR #3009](https://github.com/kyb3r/modmail/pull/3009)) - Reload thread cache only when it's the first on_ready trigger. ([GH #3037](https://github.com/kyb3r/modmail/issues/3037)) From daf04b053be29b615b783e92992ec16dc5df2297 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sat, 7 Aug 2021 22:06:50 +0800 Subject: [PATCH 178/209] New thread related config, resolves #3072 --- CHANGELOG.md | 3 +- README.md | 2 +- bot.py | 2 +- cogs/modmail.py | 16 +++++---- core/config.py | 15 +++++--- core/config_help.json | 79 ++++++++++++++++++++++++++++++++++++++++--- core/thread.py | 31 ++++++++++------- pyproject.toml | 2 +- 8 files changed, 119 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cfaddb926..93ee687cf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. -# v3.10.0-dev5 +# v3.10.0-dev6 v3.10 adds group conversations while resolving other bugs and QOL changes. It is potentially breaking to some plugins that adds functionality to threads. @@ -20,6 +20,7 @@ v3.10 adds group conversations while resolving other bugs and QOL changes. It is - Ability to have group conversations. ([GH #143](https://github.com/kyb3r/modmail/issues/143)) - Snippets are invoked case insensitively. ([GH #3077](https://github.com/kyb3r/modmail/issues/3077), [PR #3080](https://github.com/kyb3r/modmail/pull/3080)) - Default tags now use top hoisted role. ([GH #3014](https://github.com/kyb3r/modmail/issues/3014)) +- New thread-related config - `thread_show_roles`, `thread_show_account_age`, `thread_show_join_age`, `thread_cancelled`, `thread_creation_contact_title`, `thread_creation_self_contact_response`, `thread_creation_contact_response`. ([GH #3072](https://github.com/kyb3r/modmail/issues/3072)) ### Fixed diff --git a/README.md b/README.md index 42a8f812ec..2751e10fe0 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@
- +
diff --git a/bot.py b/bot.py index b1b4ad046c..dd7dbd32f0 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "v3.10.0-dev5" +__version__ = "v3.10.0-dev6" import asyncio diff --git a/cogs/modmail.py b/cogs/modmail.py index c7be64e046..5e9813423c 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -720,7 +720,7 @@ async def adduser(self, ctx, *users_arg: Union[discord.Member, discord.Role, str if not silent: description = self.bot.formatter.format( - self.bot.config["private_added_to_group_description"], moderator=ctx.author + self.bot.config["private_added_to_group_response"], moderator=ctx.author ) em = discord.Embed( title=self.bot.config["private_added_to_group_title"], @@ -734,7 +734,7 @@ async def adduser(self, ctx, *users_arg: Union[discord.Member, discord.Role, str await u.send(embed=em) description = self.bot.formatter.format( - self.bot.config["public_added_to_group_description"], + self.bot.config["public_added_to_group_response"], moderator=ctx.author, users=", ".join(u.name for u in users), ) @@ -799,7 +799,7 @@ async def removeuser(self, ctx, *users_arg: Union[discord.Member, discord.Role, if not silent: description = self.bot.formatter.format( - self.bot.config["private_removed_from_group_description"], moderator=ctx.author + self.bot.config["private_removed_from_group_response"], moderator=ctx.author ) em = discord.Embed( title=self.bot.config["private_removed_from_group_title"], @@ -813,7 +813,7 @@ async def removeuser(self, ctx, *users_arg: Union[discord.Member, discord.Role, await u.send(embed=em) description = self.bot.formatter.format( - self.bot.config["public_removed_from_group_description"], + self.bot.config["public_removed_from_group_response"], moderator=ctx.author, users=", ".join(u.name for u in users), ) @@ -1385,12 +1385,14 @@ async def contact( if not silent and not self.bot.config.get("thread_contact_silently"): if creator.id == user.id: - description = "You have opened a Modmail thread." + description = self.bot.config["thread_creation_self_contact_response"] else: - description = f"{creator.name} has opened a Modmail thread." + description = self.bot.formatter.format( + self.bot.config["thread_creation_contact_response"], creator=creator + ) em = discord.Embed( - title="New Thread", + title=self.bot.config["thread_creation_contact_title"], description=description, color=self.bot.main_color, ) diff --git a/core/config.py b/core/config.py index 829ce7ff3b..cb350b0b76 100644 --- a/core/config.py +++ b/core/config.py @@ -52,6 +52,10 @@ class ConfigManager: "close_emoji": "\N{LOCK}", "use_user_id_channel_name": False, "recipient_thread_close": False, + "thread_show_roles": True, + "thread_show_account_age": True, + "thread_show_join_age": True, + "thread_cancelled": "Cancelled", "thread_auto_close_silently": False, "thread_auto_close": isodate.Duration(), "thread_auto_close_response": "This thread has been closed automatically due to inactivity after {timeout}.", @@ -59,6 +63,9 @@ class ConfigManager: "thread_creation_footer": "Your message has been sent", "thread_contact_silently": False, "thread_self_closable_creation_footer": "Click the lock to close the thread", + "thread_creation_contact_title": "New Thread", + "thread_creation_self_contact_response": "You have opened a Modmail thread.", + "thread_creation_contact_response": "{creator.name} has opened a Modmail thread.", "thread_creation_title": "Thread Created", "thread_close_footer": "Replying will create a new thread", "thread_close_title": "Thread Closed", @@ -85,16 +92,16 @@ class ConfigManager: "anonymous_snippets": False, # group conversations "private_added_to_group_title": "New Thread (Group)", - "private_added_to_group_description": "{moderator.name} has added you to a Modmail thread.", + "private_added_to_group_response": "{moderator.name} has added you to a Modmail thread.", "private_added_to_group_description_anon": "A moderator has added you to a Modmail thread.", "public_added_to_group_title": "New User", - "public_added_to_group_description": "{moderator.name} has added {users} to the Modmail thread.", + "public_added_to_group_response": "{moderator.name} has added {users} to the Modmail thread.", "public_added_to_group_description_anon": "A moderator has added {users} to the Modmail thread.", "private_removed_from_group_title": "Removed From Thread (Group)", - "private_removed_from_group_description": "{moderator.name} has removed you from the Modmail thread.", + "private_removed_from_group_response": "{moderator.name} has removed you from the Modmail thread.", "private_removed_from_group_description_anon": "A moderator has removed you from the Modmail thread.", "public_removed_from_group_title": "User Removed", - "public_removed_from_group_description": "{moderator.name} has removed {users} from the Modmail thread.", + "public_removed_from_group_response": "{moderator.name} has removed {users} from the Modmail thread.", "public_removed_from_group_description_anon": "A moderator has removed {users} from the Modmail thread.", # moderation "recipient_color": str(discord.Color.gold()), diff --git a/core/config_help.json b/core/config_help.json index d6a4627d96..a679ba1c7c 100644 --- a/core/config_help.json +++ b/core/config_help.json @@ -266,6 +266,36 @@ "See also: `close_emoji`." ] }, + "thread_show_roles": { + "default": "Yes", + "description": "Shows roles on first message sent in thread channels to mods", + "examples":[ + "`{prefix}config set thread_show_roles no`" + ], + "notes": [ + "See also: `thread_show_account_age`, `thread_show_join_age`." + ] + }, + "thread_show_account_age": { + "default": "Yes", + "description": "Shows account age on first message sent in thread channels to mods", + "examples":[ + "`{prefix}config set thread_show_account_age no`" + ], + "notes": [ + "See also: `thread_show_roles`, `thread_show_join_age`." + ] + }, + "thread_show_join_age": { + "default": "Yes", + "description": "Shows join age on first message sent in thread channels to mods", + "examples":[ + "`{prefix}config set thread_show_join_age no`" + ], + "notes": [ + "See also: `thread_show_account_age`, `thread_show_roles`." + ] + }, "thread_auto_close_silently": { "default": "No", "description": "Setting this configuration will close silently when the thread auto-closes.", @@ -302,6 +332,14 @@ "To disable thread cooldown, do `{prefix}config del thread_cooldown`." ] }, + "thread_cancelled": { + "default": "\"Cancelled\"", + "description": "This is the message to display when a thread times out and creation is cancelled.", + "examples": [ + "`{prefix}config set thread_cancelled Gone.`" + ], + "notes": [] + }, "thread_auto_close_response": { "default": "\"This thread has been closed automatically due to inactivity after {{timeout}}.\"", "description": "This is the message to display when the thread when the thread auto-closes.", @@ -359,6 +397,39 @@ "See also: `thread_creation_title`, `thread_creation_response`, `thread_creation_footer`." ] }, + "thread_creation_contact_title": { + "default": "\"New Thread\"", + "description": "This is the message embed title sent to recipients when contacted.", + "examples": [ + "`{prefix}config set thread_creation_contact_title New Message!`" + ], + "notes": [ + "See also: `thread_creation_self_contact_response`, `thread_creation_contact_response`." + ] + }, + "thread_creation_self_contact_response": { + "default": "\"You have opened a Modmail thread.\"", + "description": "This is the message embed description sent to recipients when self-contacted.", + "examples": [ + "`{prefix}config set thread_creation_contact_title You contacted yourself.`" + ], + "notes": [ + "`thread_creation_contact_response` is used when contacted by another user.", + "See also: `thread_creation_contact_title`, `thread_creation_contact_response`." + ] + }, + "thread_creation_contact_response": { + "default": "\"{creator.name} has opened a Modmail thread.\"", + "description": "This is the message embed description sent to recipients when contacted by a mod.", + "examples": [ + "`{prefix}config set thread_creation_contact_response New thread opened.`" + ], + "notes": [ + "You may use the `{{creator}}` variable for access to the [Member](https://discordpy.readthedocs.io/en/latest/api.html#discord.Member) that created the thread.", + "`thread_creation_self_contact_response` is used when contacted by self.", + "See also: `thread_creation_contact_title`, `thread_creation_self_contact_response`." + ] + }, "thread_creation_title": { "default": "\"Thread Created\"", "description": "This is the message embed title sent to the recipient upon the creation of a new thread.", @@ -722,7 +793,7 @@ "See also: `private_added_to_group_description`, `public_added_to_group_title`" ] }, - "private_added_to_group_description": { + "private_added_to_group_response": { "default": "\"{{moderator.name}} has added you to a Modmail thread.\"", "description": "This is the message embed content sent to the recipient that is just added to a thread.", "examples": [ @@ -758,7 +829,7 @@ "See also: `private_added_to_group_title`, `private_added_to_group_title`" ] }, - "public_added_to_group_description": { + "public_added_to_group_response": { "default": "\"{{moderator.name}} has added {{users}} to the Modmail thread.\"", "description": "This is the message embed content sent to all other recipients when someone is added to the thread.", "examples": [ @@ -794,7 +865,7 @@ "See also: `private_removed_from_group_description`, `public_removed_from_group_title`" ] }, - "private_removed_from_group_description": { + "private_removed_from_group_response": { "default": "\"{{moderator.name}} has removed you from the Modmail thread.\"", "description": "This is the message embed content sent to the recipient that is just removed from a thread.", "examples": [ @@ -830,7 +901,7 @@ "See also: `private_removed_from_group_title`, `private_removed_from_group_title`" ] }, - "public_removed_from_group_description": { + "public_removed_from_group_response": { "default": "\"{{moderator.name}} has removed {{users}} from the Modmail thread.\"", "description": "This is the message embed content sent to all other recipients when someone is removed from the thread.", "examples": [ diff --git a/core/thread.py b/core/thread.py index b34e14ed01..a013ed8eff 100644 --- a/core/thread.py +++ b/core/thread.py @@ -286,7 +286,7 @@ def _format_info_embed(self, user, log_url, log_count, color): # key = log_url.split('/')[-1] role_names = "" - if member is not None: + if member is not None and self.bot.config["thread_show_roles"]: sep_server = self.bot.using_multiple_server_setup separator = ", " if sep_server else " " @@ -309,13 +309,11 @@ def _format_info_embed(self, user, log_url, log_count, color): role_names = separator.join(roles) created = str((time - user.created_at).days) - embed = discord.Embed( - color=color, description=f"{user.mention} was created {days(created)}", timestamp=time - ) + user_info = [] + if self.bot.config["thread_show_account_age"]: + user_info.append(f"was created {days(created)}") - # if not role_names: - # embed.add_field(name='Mention', value=user.mention) - # embed.add_field(name='Registered', value=created + days(created)) + embed = discord.Embed(color=color, description=user.mention, timestamp=time) if user.dm_channel: footer = f"User ID: {user.id} • DM ID: {user.dm_channel.id}" @@ -328,7 +326,8 @@ def _format_info_embed(self, user, log_url, log_count, color): if member is not None: joined = str((time - member.joined_at).days) # embed.add_field(name='Joined', value=joined + days(joined)) - embed.description += f", joined {days(joined)}" + if self.bot.config["thread_show_join_age"]: + user_info.append(f"joined {days(joined)}") if member.nick: embed.add_field(name="Nickname", value=member.nick, inline=True) @@ -338,10 +337,12 @@ def _format_info_embed(self, user, log_url, log_count, color): else: embed.set_footer(text=f"{footer} • (not in main server)") + embed.description += ", ".join(user_info) + if log_count is not None: - # embed.add_field(name="Past logs", value=f"{log_count}") + connector = "with" if user_info else "has" thread = "thread" if log_count == 1 else "threads" - embed.description += f" with **{log_count or 'no'}** past {thread}." + embed.description += f" {connector} **{log_count or 'no'}** past {thread}." else: embed.description += "." @@ -1336,7 +1337,9 @@ async def create( self.bot.loop.create_task( destination.send( embed=discord.Embed( - title="Cancelled", description="Timed out", color=self.bot.error_color + title=self.bot.config["thread_cancelled"], + description="Timed out", + color=self.bot.error_color, ) ) ) @@ -1344,7 +1347,11 @@ async def create( if str(r.emoji) == deny_emoji: thread.cancelled = True self.bot.loop.create_task( - destination.send(embed=discord.Embed(title="Cancelled", color=self.bot.error_color)) + destination.send( + embed=discord.Embed( + title=self.bot.config["thread_cancelled"], color=self.bot.error_color + ) + ) ) async def remove_reactions(): diff --git a/pyproject.toml b/pyproject.toml index 1d97e42e33..20e1a16ac4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ extend-exclude = ''' [tool.poetry] name = 'Modmail' -version = '3.10.0-dev5' +version = '3.10.0-dev6' description = "Modmail is similar to Reddit's Modmail, both in functionality and purpose. It serves as a shared inbox for server staff to communicate with their users in a seamless way." license = 'AGPL-3.0-only' authors = [ From 145788e5e46a909f3c0e4224dce1ba3c89c16c11 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sat, 7 Aug 2021 22:11:54 +0800 Subject: [PATCH 179/209] Resolve issues with deleting messages --- bot.py | 3 ++- core/thread.py | 6 +++++- core/utils.py | 4 +++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/bot.py b/bot.py index dd7dbd32f0..33508e5eab 100644 --- a/bot.py +++ b/bot.py @@ -1415,11 +1415,12 @@ async def on_message_delete(self, message): if not thread: return try: - message = await thread.find_linked_message_from_dm(message) + message = await thread.find_linked_message_from_dm(message, get_thread_channel=True) except ValueError as e: if str(e) != "Thread channel message not found.": logger.debug("Failed to find linked message to delete: %s", e) return + message = message[0] embed = message.embeds[0] embed.set_footer(text=f"{embed.footer.text} (deleted)", icon_url=embed.footer.icon_url) await message.edit(embed=embed) diff --git a/core/thread.py b/core/thread.py index a013ed8eff..700654e314 100644 --- a/core/thread.py +++ b/core/thread.py @@ -695,7 +695,7 @@ async def delete_message( await asyncio.gather(*tasks) async def find_linked_message_from_dm( - self, message, either_direction=False + self, message, either_direction=False, get_thread_channel=False ) -> typing.List[discord.Message]: joint_id = None @@ -726,6 +726,10 @@ async def find_linked_message_from_dm( else: raise ValueError("Thread channel message not found.") + if get_thread_channel: + # end early as we only want the main message from thread channel + return linked_messages + if joint_id is None: joint_id = get_joint_id(linked_messages[0]) if joint_id is None: diff --git a/core/utils.py b/core/utils.py index 3b75e11014..30948f1b0a 100644 --- a/core/utils.py +++ b/core/utils.py @@ -467,7 +467,9 @@ def get_joint_id(message: discord.Message) -> typing.Optional[int]: """ if message.embeds: try: - return int(getattr(message.embeds[0].author, "url", "").split("#")[-1]) + url = getattr(message.embeds[0].author, "url", "") + if url: + return int(url.split("#")[-1]) except ValueError: pass return None From 93432988e7fc0cdbcc4489dcf7ef1d0e2514ac06 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sat, 7 Aug 2021 22:20:06 +0800 Subject: [PATCH 180/209] use_timestamp_channel_name config --- core/config.py | 6 ++++++ core/utils.py | 3 +++ 2 files changed, 9 insertions(+) diff --git a/core/config.py b/core/config.py index cb350b0b76..d8593a02ef 100644 --- a/core/config.py +++ b/core/config.py @@ -51,6 +51,7 @@ class ConfigManager: "blocked_emoji": "\N{NO ENTRY SIGN}", "close_emoji": "\N{LOCK}", "use_user_id_channel_name": False, + "use_timestamp_channel_name": False, "recipient_thread_close": False, "thread_show_roles": True, "thread_show_account_age": True, @@ -179,6 +180,7 @@ class ConfigManager: booleans = { "use_user_id_channel_name", + "use_timestamp_channel_name", "user_typing", "mod_typing", "reply_without_command", @@ -203,6 +205,10 @@ class ConfigManager: "update_notifications", "thread_contact_silently", "anonymous_snippets", + "recipient_thread_close", + "thread_show_roles", + "thread_show_account_age", + "thread_show_join_age", } enums = { diff --git a/core/utils.py b/core/utils.py index 30948f1b0a..5ae1977f6e 100644 --- a/core/utils.py +++ b/core/utils.py @@ -1,4 +1,5 @@ import base64 +from datetime import datetime import functools import re import string @@ -373,6 +374,8 @@ def format_channel_name(bot, author, exclude_channel=None, force_null=False): else: if bot.config["use_user_id_channel_name"]: name = new_name = str(author.id) + elif bot.config["use_timestamp_channel_name"]: + name = new_name = datetime.utcnow().isoformat(sep='-', timesep='minutes') else: name = author.name.lower() if force_null: From 0ebf364a8350a64c60be3994601d87fa9945dafc Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sat, 7 Aug 2021 22:20:17 +0800 Subject: [PATCH 181/209] formatting --- core/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/utils.py b/core/utils.py index 5ae1977f6e..cf5aac5ec2 100644 --- a/core/utils.py +++ b/core/utils.py @@ -375,7 +375,7 @@ def format_channel_name(bot, author, exclude_channel=None, force_null=False): if bot.config["use_user_id_channel_name"]: name = new_name = str(author.id) elif bot.config["use_timestamp_channel_name"]: - name = new_name = datetime.utcnow().isoformat(sep='-', timesep='minutes') + name = new_name = datetime.utcnow().isoformat(sep="-", timesep="minutes") else: name = author.name.lower() if force_null: From fcc3fad65c411c5d012b19c4741695de51af354a Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sat, 7 Aug 2021 22:27:16 +0800 Subject: [PATCH 182/209] add documentation and fix timestamp related bugs --- CHANGELOG.md | 1 + core/config_help.json | 17 ++++++++++++++++- core/utils.py | 3 +-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93ee687cf6..1febcceb03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ v3.10 adds group conversations while resolving other bugs and QOL changes. It is - Snippets are invoked case insensitively. ([GH #3077](https://github.com/kyb3r/modmail/issues/3077), [PR #3080](https://github.com/kyb3r/modmail/pull/3080)) - Default tags now use top hoisted role. ([GH #3014](https://github.com/kyb3r/modmail/issues/3014)) - New thread-related config - `thread_show_roles`, `thread_show_account_age`, `thread_show_join_age`, `thread_cancelled`, `thread_creation_contact_title`, `thread_creation_self_contact_response`, `thread_creation_contact_response`. ([GH #3072](https://github.com/kyb3r/modmail/issues/3072)) +- `use_timestamp_channel_name` config to create thread channels by timestamp. ### Fixed diff --git a/core/config_help.json b/core/config_help.json index a679ba1c7c..1a57d3a3e8 100644 --- a/core/config_help.json +++ b/core/config_help.json @@ -105,7 +105,22 @@ "`{prefix}config set use_user_id_channel_name no`" ], "notes": [ - "This config is suitable for servers in Server Discovery to comply with channel name restrictions." + "This config is suitable for servers in Server Discovery to comply with channel name restrictions.", + "This cannot be applied with `use_timestamp_channel_name`.", + "See also: `use_timestamp_channel_name`." + ] + }, + "use_timestamp_channel_name": { + "default": "No", + "description": "When this is set to `yes`, new thread channels will be named with the recipient's account creation date instead of the recipient's name.", + "examples": [ + "`{prefix}config set use_timestamp_channel_name yes`", + "`{prefix}config set use_timestamp_channel_name no`" + ], + "notes": [ + "This config is suitable for servers in Server Discovery to comply with channel name restrictions.", + "This cannot be applied with `use_user_id_channel_name`.", + "See also: `use_user_id_channel_name`." ] }, "mod_typing": { diff --git a/core/utils.py b/core/utils.py index cf5aac5ec2..474bc9db4f 100644 --- a/core/utils.py +++ b/core/utils.py @@ -1,5 +1,4 @@ import base64 -from datetime import datetime import functools import re import string @@ -375,7 +374,7 @@ def format_channel_name(bot, author, exclude_channel=None, force_null=False): if bot.config["use_user_id_channel_name"]: name = new_name = str(author.id) elif bot.config["use_timestamp_channel_name"]: - name = new_name = datetime.utcnow().isoformat(sep="-", timesep="minutes") + name = new_name = author.created_at.isoformat(sep="-", timespec="minutes") else: name = author.name.lower() if force_null: From 55b311f294bb7570d2b5ae5b77eccea4e0518cd6 Mon Sep 17 00:00:00 2001 From: popeeyy <29686338+popeeyy@users.noreply.github.com> Date: Sat, 7 Aug 2021 19:44:29 -0700 Subject: [PATCH 183/209] Initial commit --- bot.py | 28 +++++++++++++++++++++++++--- cogs/modmail.py | 3 +++ cogs/utility.py | 18 ++++++++++++++++++ core/thread.py | 1 + 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/bot.py b/bot.py index 1c4fdd5354..12e28c8571 100644 --- a/bot.py +++ b/bot.py @@ -1010,6 +1010,7 @@ async def trigger_auto_triggers(self, message, channel, *, cls=commands.Context) if trigger: invoker = trigger.lower() + print("looking for auto trigger", trigger, self.auto_triggers[trigger]) alias = self.auto_triggers[trigger] ctxs = [] @@ -1019,24 +1020,45 @@ async def trigger_auto_triggers(self, message, channel, *, cls=commands.Context) if not aliases: logger.warning("Alias %s is invalid as called in autotrigger.", invoker) + print("Aliases", aliases) + for alias in aliases: view = StringView(invoked_prefix + alias) + invoked_with = view.get_word().lower() + invoked_with = invoked_with[1:] + print("Looking for", invoked_with) + found_command = self.all_commands.get(invoked_with) + + # Check for alias + if not found_command: + print("INVOKED WITH", invoked_with) + command_alias = self.aliases.get(invoked_with)[1:-1] + view = StringView(invoked_prefix + command_alias) + split_cmd = command_alias.split(" ") + found_command = self.all_commands.get(split_cmd[0]) + ctx_ = cls(prefix=self.prefix, view=view, bot=self, message=message) + ctx_.command = found_command + ctx_.invoked_with = invoked_with ctx_.thread = thread discord.utils.find(view.skip_string, await self.get_prefix()) - ctx_.invoked_with = view.get_word().lower() - ctx_.command = self.all_commands.get(ctx_.invoked_with) + + print("Command info:", view, ctx_, ctx_.thread, ctx_.invoked_with, ctx_.command) ctxs += [ctx_] for ctx in ctxs: if ctx.command: + print("Found command") old_checks = copy.copy(ctx.command.checks) + print("Old checks set") ctx.command.checks = [checks.has_permissions(PermissionLevel.INVALID)] - + print("Command checks added, invoking...", ctx) await self.invoke(ctx) ctx.command.checks = old_checks continue + else: + print("unable to find command") async def get_context(self, message, *, cls=commands.Context): """ diff --git a/cogs/modmail.py b/cogs/modmail.py index 0e41b76677..35e80bdf9b 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -818,7 +818,10 @@ async def reply(self, ctx, *, msg: str = ""): Supports attachments and images as well as automatically embedding image URLs. """ + ctx.message.content = msg + print("MSG IS", msg) + print("Sending", ctx.message, msg) async with ctx.typing(): await ctx.thread.reply(ctx.message) diff --git a/cogs/utility.py b/cogs/utility.py index 759b573748..d068b6cc1e 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -1750,6 +1750,16 @@ async def autotrigger_add(self, ctx, keyword, *, command): print(self.bot.get_command(" ".join(split_cmd[0:n]))) valid = True break + + print("Split command", split_cmd, "Range", range(1, len(split_cmd) + 1), self.bot.aliases) + + if not valid and self.bot.aliases: + for n in range(1, len(split_cmd) + 1): + print(" ".join(split_cmd[0:n]), self.bot.aliases.get(" ".join(split_cmd[0:n]))) + if self.bot.aliases.get(" ".join(split_cmd[0:n])): + print(self.bot.aliases.get(" ".join(split_cmd[0:n]))) + valid = True + break if valid: self.bot.auto_triggers[keyword] = command @@ -1784,6 +1794,14 @@ async def autotrigger_edit(self, ctx, keyword, *, command): valid = True break + if not valid and self.bot.aliases: + for n in range(1, len(split_cmd) + 1): + print(" ".join(split_cmd[0:n]), self.bot.aliases.get(" ".join(split_cmd[0:n]))) + if self.bot.aliases.get(" ".join(split_cmd[0:n])): + print(self.bot.aliases.get(" ".join(split_cmd[0:n]))) + valid = True + break + if valid: self.bot.auto_triggers[keyword] = command await self.bot.config.update() diff --git a/core/thread.py b/core/thread.py index 41d2cd96ba..a494891965 100644 --- a/core/thread.py +++ b/core/thread.py @@ -241,6 +241,7 @@ class Author: async def activate_auto_triggers(): if initial_message: message = DummyMessage(copy.copy(initial_message)) + try: return await self.bot.trigger_auto_triggers(message, channel) except RuntimeError: From 115ccfadb797d876b8d7cbba91f311576695365a Mon Sep 17 00:00:00 2001 From: popeeyy <29686338+popeeyy@users.noreply.github.com> Date: Sat, 7 Aug 2021 20:17:11 -0700 Subject: [PATCH 184/209] Bug fixes and debug removal --- bot.py | 22 +++++++--------------- cogs/modmail.py | 5 ++--- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/bot.py b/bot.py index 12e28c8571..8c9d20dd87 100644 --- a/bot.py +++ b/bot.py @@ -1010,7 +1010,6 @@ async def trigger_auto_triggers(self, message, channel, *, cls=commands.Context) if trigger: invoker = trigger.lower() - print("looking for auto trigger", trigger, self.auto_triggers[trigger]) alias = self.auto_triggers[trigger] ctxs = [] @@ -1020,39 +1019,32 @@ async def trigger_auto_triggers(self, message, channel, *, cls=commands.Context) if not aliases: logger.warning("Alias %s is invalid as called in autotrigger.", invoker) - print("Aliases", aliases) - for alias in aliases: + print("Initial view", invoked_prefix + alias) view = StringView(invoked_prefix + alias) - invoked_with = view.get_word().lower() - invoked_with = invoked_with[1:] - print("Looking for", invoked_with) + invoked_with = view.get_word().lower()[1:] found_command = self.all_commands.get(invoked_with) # Check for alias if not found_command: - print("INVOKED WITH", invoked_with) - command_alias = self.aliases.get(invoked_with)[1:-1] - view = StringView(invoked_prefix + command_alias) - split_cmd = command_alias.split(" ") - found_command = self.all_commands.get(split_cmd[0]) + invoked_with = self.aliases.get(invoked_with)[1:-1] # Get command linked to alias + view = StringView(invoked_prefix + invoked_with) # Create StringView for new command + invoked_with = view.get_word().lower()[1:] # Parse the new command + found_command = self.all_commands.get(invoked_with) # Get the command function ctx_ = cls(prefix=self.prefix, view=view, bot=self, message=message) ctx_.command = found_command + ctx_.invoked_with = invoked_with ctx_.thread = thread discord.utils.find(view.skip_string, await self.get_prefix()) - print("Command info:", view, ctx_, ctx_.thread, ctx_.invoked_with, ctx_.command) ctxs += [ctx_] for ctx in ctxs: if ctx.command: - print("Found command") old_checks = copy.copy(ctx.command.checks) - print("Old checks set") ctx.command.checks = [checks.has_permissions(PermissionLevel.INVALID)] - print("Command checks added, invoking...", ctx) await self.invoke(ctx) ctx.command.checks = old_checks diff --git a/cogs/modmail.py b/cogs/modmail.py index 35e80bdf9b..66d2f675da 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -818,10 +818,9 @@ async def reply(self, ctx, *, msg: str = ""): Supports attachments and images as well as automatically embedding image URLs. """ - + print("MSG IS", msg, ctx.message.content) ctx.message.content = msg - print("MSG IS", msg) - print("Sending", ctx.message, msg) + async with ctx.typing(): await ctx.thread.reply(ctx.message) From a24cae3dd6c0a0b6a88e2bdb405374458d8e12d3 Mon Sep 17 00:00:00 2001 From: popeeyy <29686338+popeeyy@users.noreply.github.com> Date: Sat, 7 Aug 2021 20:18:22 -0700 Subject: [PATCH 185/209] Remove debug --- bot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bot.py b/bot.py index 8c9d20dd87..20b8483128 100644 --- a/bot.py +++ b/bot.py @@ -1020,7 +1020,6 @@ async def trigger_auto_triggers(self, message, channel, *, cls=commands.Context) logger.warning("Alias %s is invalid as called in autotrigger.", invoker) for alias in aliases: - print("Initial view", invoked_prefix + alias) view = StringView(invoked_prefix + alias) invoked_with = view.get_word().lower()[1:] found_command = self.all_commands.get(invoked_with) From 4c0d8717192da6d57dbb13bfc8e4890849e6ef24 Mon Sep 17 00:00:00 2001 From: popeeyy <29686338+popeeyy@users.noreply.github.com> Date: Sat, 7 Aug 2021 20:20:27 -0700 Subject: [PATCH 186/209] Remove additional debug --- bot.py | 2 -- cogs/modmail.py | 2 +- cogs/utility.py | 3 --- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/bot.py b/bot.py index 20b8483128..a9b463fb0d 100644 --- a/bot.py +++ b/bot.py @@ -1048,8 +1048,6 @@ async def trigger_auto_triggers(self, message, channel, *, cls=commands.Context) ctx.command.checks = old_checks continue - else: - print("unable to find command") async def get_context(self, message, *, cls=commands.Context): """ diff --git a/cogs/modmail.py b/cogs/modmail.py index 66d2f675da..efbb90c0b1 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -818,7 +818,7 @@ async def reply(self, ctx, *, msg: str = ""): Supports attachments and images as well as automatically embedding image URLs. """ - print("MSG IS", msg, ctx.message.content) + ctx.message.content = msg async with ctx.typing(): diff --git a/cogs/utility.py b/cogs/utility.py index d068b6cc1e..3c3318cb6d 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -1750,12 +1750,9 @@ async def autotrigger_add(self, ctx, keyword, *, command): print(self.bot.get_command(" ".join(split_cmd[0:n]))) valid = True break - - print("Split command", split_cmd, "Range", range(1, len(split_cmd) + 1), self.bot.aliases) if not valid and self.bot.aliases: for n in range(1, len(split_cmd) + 1): - print(" ".join(split_cmd[0:n]), self.bot.aliases.get(" ".join(split_cmd[0:n]))) if self.bot.aliases.get(" ".join(split_cmd[0:n])): print(self.bot.aliases.get(" ".join(split_cmd[0:n]))) valid = True From ed0631c07cb3b551c5b2862de9a68b65afd56a44 Mon Sep 17 00:00:00 2001 From: popeeyy <29686338+popeeyy@users.noreply.github.com> Date: Sat, 7 Aug 2021 20:21:40 -0700 Subject: [PATCH 187/209] Update bot.py --- bot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bot.py b/bot.py index a9b463fb0d..01e9503c46 100644 --- a/bot.py +++ b/bot.py @@ -1044,6 +1044,7 @@ async def trigger_auto_triggers(self, message, channel, *, cls=commands.Context) if ctx.command: old_checks = copy.copy(ctx.command.checks) ctx.command.checks = [checks.has_permissions(PermissionLevel.INVALID)] + await self.invoke(ctx) ctx.command.checks = old_checks From 25cba86522b261ac6be406728313e31a416adb85 Mon Sep 17 00:00:00 2001 From: popeeyy <29686338+popeeyy@users.noreply.github.com> Date: Sat, 7 Aug 2021 20:24:16 -0700 Subject: [PATCH 188/209] Edit error message --- cogs/utility.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cogs/utility.py b/cogs/utility.py index 3c3318cb6d..59e5d507dd 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -1771,7 +1771,7 @@ async def autotrigger_add(self, ctx, keyword, *, command): embed = discord.Embed( title="Error", color=self.bot.error_color, - description="Invalid command. Note that autotriggers do not work with aliases.", + description="Invalid command. Please provide a valid command or alias.", ) await ctx.send(embed=embed) @@ -1812,7 +1812,7 @@ async def autotrigger_edit(self, ctx, keyword, *, command): embed = discord.Embed( title="Error", color=self.bot.error_color, - description="Invalid command. Note that autotriggers do not work with aliases.", + description="Invalid command. Please provide a valid command or alias.", ) await ctx.send(embed=embed) From bc451185aaabb1d97b510d82bb4894e9537ffce2 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sun, 8 Aug 2021 15:27:14 +0800 Subject: [PATCH 189/209] move format_channel_name to Bot, resolve #2982 --- CHANGELOG.md | 5 +++-- bot.py | 32 +++++++++++++++++++++++++++++++- cogs/modmail.py | 2 +- core/utils.py | 34 ++-------------------------------- 4 files changed, 37 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1febcceb03..e7be4a292f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,14 +6,14 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. -# v3.10.0-dev6 +# v3.10.0-dev7 v3.10 adds group conversations while resolving other bugs and QOL changes. It is potentially breaking to some plugins that adds functionality to threads. ### Breaking - `Thread.recipient` (`str`) is now `Thread.recipients` (`List[str]`). -- `thread.reply` now returns mod_message, user_message1, user_message2... It is no longer limited at a size 2 tuple. +- `Thread.reply` now returns mod_message, user_message1, user_message2... It is no longer limited at a size 2 tuple. ### Added @@ -41,6 +41,7 @@ v3.10 adds group conversations while resolving other bugs and QOL changes. It is - Fix return types, type hints and unresolved references ([PR #3009](https://github.com/kyb3r/modmail/pull/3009)) - Reload thread cache only when it's the first on_ready trigger. ([GH #3037](https://github.com/kyb3r/modmail/issues/3037)) +- `format_channel_name` is now extendable to plugins. Modify `Bot.format_channel_name(bot, author, exclude_channel=None, force_null=False):`. ([GH #2982](https://github.com/kyb3r/modmail/issues/2982)) # v3.9.5 diff --git a/bot.py b/bot.py index 33508e5eab..4ec694a1a5 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "v3.10.0-dev6" +__version__ = "3.10.0-dev7" import asyncio @@ -1645,6 +1645,36 @@ async def before_autoupdate(self): logger.warning("Autoupdates disabled.") self.autoupdate_loop.cancel() + def format_channel_name(self, author, exclude_channel=None, force_null=False): + """Sanitises a username for use with text channel names + + Placed in main bot class to be extendable to plugins""" + guild = self.modmail_guild + + if force_null: + name = new_name = "null" + else: + if self.config["use_user_id_channel_name"]: + name = new_name = str(author.id) + elif self.config["use_timestamp_channel_name"]: + name = new_name = author.created_at.isoformat(sep="-", timespec="minutes") + else: + name = author.name.lower() + if force_null: + name = "null" + + name = new_name = ( + "".join(l for l in name if l not in string.punctuation and l.isprintable()) or "null" + ) + f"-{author.discriminator}" + + counter = 1 + existed = set(c.name for c in guild.text_channels if c != exclude_channel) + while new_name in existed: + new_name = f"{name}_{counter}" # multiple channels with same name + counter += 1 + + return new_name + def main(): try: diff --git a/cogs/modmail.py b/cogs/modmail.py index 5e9813423c..57f7f017d6 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -1829,7 +1829,7 @@ async def repair(self, ctx): ) if len(users) == 1: user = users.pop() - name = format_channel_name(self.bot, user, exclude_channel=ctx.channel) + name = self.bot.format_channel_name(user, exclude_channel=ctx.channel) recipient = self.bot.get_user(user.id) if user.id in self.bot.threads.cache: thread = self.bot.threads.cache[user.id] diff --git a/core/utils.py b/core/utils.py index 474bc9db4f..717b683546 100644 --- a/core/utils.py +++ b/core/utils.py @@ -30,7 +30,6 @@ "format_description", "trigger_typing", "escape_code_block", - "format_channel_name", "tryint", "get_top_hoisted_role", "get_joint_id", @@ -364,35 +363,6 @@ def escape_code_block(text): return re.sub(r"```", "`\u200b``", text) -def format_channel_name(bot, author, exclude_channel=None, force_null=False): - """Sanitises a username for use with text channel names""" - guild = bot.modmail_guild - - if force_null: - name = new_name = "null" - else: - if bot.config["use_user_id_channel_name"]: - name = new_name = str(author.id) - elif bot.config["use_timestamp_channel_name"]: - name = new_name = author.created_at.isoformat(sep="-", timespec="minutes") - else: - name = author.name.lower() - if force_null: - name = "null" - - name = new_name = ( - "".join(l for l in name if l not in string.punctuation and l.isprintable()) or "null" - ) + f"-{author.discriminator}" - - counter = 1 - existed = set(c.name for c in guild.text_channels if c != exclude_channel) - while new_name in existed: - new_name = f"{name}_{counter}" # multiple channels with same name - counter += 1 - - return new_name - - def tryint(x): try: return int(x) @@ -408,7 +378,7 @@ def get_top_hoisted_role(member: discord.Member): async def create_thread_channel(bot, recipient, category, overwrites, *, name=None, errors_raised=[]): - name = name or format_channel_name(bot, recipient) + name = name or bot.format_channel_name(recipient) try: channel = await bot.modmail_guild.create_text_channel( name=name, @@ -446,7 +416,7 @@ async def create_thread_channel(bot, recipient, category, overwrites, *, name=No recipient, category, overwrites, - name=format_channel_name(bot, recipient, force_null=True), + name=bot.format_channel_name(recipient, force_null=True), errors_raised=errors_raised, ) From cc890c4b5fb1b642c34874a9c8cb4c37c12325f4 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sun, 8 Aug 2021 15:28:02 +0800 Subject: [PATCH 190/209] Formatting --- bot.py | 12 ++++++------ cogs/modmail.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bot.py b/bot.py index 01e9503c46..e066bc068a 100644 --- a/bot.py +++ b/bot.py @@ -1026,14 +1026,14 @@ async def trigger_auto_triggers(self, message, channel, *, cls=commands.Context) # Check for alias if not found_command: - invoked_with = self.aliases.get(invoked_with)[1:-1] # Get command linked to alias - view = StringView(invoked_prefix + invoked_with) # Create StringView for new command - invoked_with = view.get_word().lower()[1:] # Parse the new command - found_command = self.all_commands.get(invoked_with) # Get the command function + invoked_with = self.aliases.get(invoked_with)[1:-1] # Get command linked to alias + view = StringView(invoked_prefix + invoked_with) # Create StringView for new command + invoked_with = view.get_word().lower()[1:] # Parse the new command + found_command = self.all_commands.get(invoked_with) # Get the command function ctx_ = cls(prefix=self.prefix, view=view, bot=self, message=message) ctx_.command = found_command - + ctx_.invoked_with = invoked_with ctx_.thread = thread discord.utils.find(view.skip_string, await self.get_prefix()) @@ -1044,7 +1044,7 @@ async def trigger_auto_triggers(self, message, channel, *, cls=commands.Context) if ctx.command: old_checks = copy.copy(ctx.command.checks) ctx.command.checks = [checks.has_permissions(PermissionLevel.INVALID)] - + await self.invoke(ctx) ctx.command.checks = old_checks diff --git a/cogs/modmail.py b/cogs/modmail.py index efbb90c0b1..367c30f3c8 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -820,7 +820,7 @@ async def reply(self, ctx, *, msg: str = ""): """ ctx.message.content = msg - + async with ctx.typing(): await ctx.thread.reply(ctx.message) From f3c22461d4a4eaf331ae637fbae9f017fa0270d6 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sun, 8 Aug 2021 15:57:24 +0800 Subject: [PATCH 191/209] bump ver --- README.md | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2751e10fe0..e71432b5ca 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@
- +
diff --git a/pyproject.toml b/pyproject.toml index 20e1a16ac4..1c3ba056de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ extend-exclude = ''' [tool.poetry] name = 'Modmail' -version = '3.10.0-dev6' +version = '3.10.0-dev7' description = "Modmail is similar to Reddit's Modmail, both in functionality and purpose. It serves as a shared inbox for server staff to communicate with their users in a seamless way." license = 'AGPL-3.0-only' authors = [ From 8ab48fc935f0597819d6227f2760356c44cb2100 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sun, 8 Aug 2021 18:49:23 +0800 Subject: [PATCH 192/209] Bug fixes --- CHANGELOG.md | 2 +- README.md | 2 +- bot.py | 3 ++- core/utils.py | 3 +-- pyproject.toml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7be4a292f..b1745e32aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. -# v3.10.0-dev7 +# v3.10.0-dev8 v3.10 adds group conversations while resolving other bugs and QOL changes. It is potentially breaking to some plugins that adds functionality to threads. diff --git a/README.md b/README.md index e71432b5ca..21e655a23c 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@
- +
diff --git a/bot.py b/bot.py index 4ec694a1a5..8f581ab4e3 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.10.0-dev7" +__version__ = "3.10.0-dev8" import asyncio @@ -7,6 +7,7 @@ import os import re import signal +import string import sys import typing from datetime import datetime diff --git a/core/utils.py b/core/utils.py index 717b683546..0fa74e457a 100644 --- a/core/utils.py +++ b/core/utils.py @@ -1,7 +1,6 @@ import base64 import functools import re -import string import typing from difflib import get_close_matches from distutils.util import strtobool as _stb # pylint: disable=import-error @@ -396,7 +395,7 @@ async def create_thread_channel(bot, recipient, category, overwrites, *, name=No if "Maximum number of channels in category reached" in e.text: fallback_id = bot.config["fallback_category_id"] if fallback_id: - fallback = discord.utils.get(cat.guild.categories, id=int(fallback_id)) + fallback = discord.utils.get(category.guild.categories, id=int(fallback_id)) if fallback and len(fallback.channels) < 49: category = fallback diff --git a/pyproject.toml b/pyproject.toml index 1c3ba056de..67a9dbd4f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ extend-exclude = ''' [tool.poetry] name = 'Modmail' -version = '3.10.0-dev7' +version = '3.10.0-dev8' description = "Modmail is similar to Reddit's Modmail, both in functionality and purpose. It serves as a shared inbox for server staff to communicate with their users in a seamless way." license = 'AGPL-3.0-only' authors = [ From 736863b49f981992f2e9f13add9552231f0d8c9b Mon Sep 17 00:00:00 2001 From: popeeyy <29686338+popeeyy@users.noreply.github.com> Date: Sun, 8 Aug 2021 19:39:57 -0700 Subject: [PATCH 193/209] Redo alias conversion --- bot.py | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/bot.py b/bot.py index e066bc068a..e5008d51d1 100644 --- a/bot.py +++ b/bot.py @@ -1013,32 +1013,20 @@ async def trigger_auto_triggers(self, message, channel, *, cls=commands.Context) alias = self.auto_triggers[trigger] ctxs = [] + if alias is not None: ctxs = [] aliases = normalize_alias(alias) if not aliases: logger.warning("Alias %s is invalid as called in autotrigger.", invoker) - for alias in aliases: - view = StringView(invoked_prefix + alias) - invoked_with = view.get_word().lower()[1:] - found_command = self.all_commands.get(invoked_with) - - # Check for alias - if not found_command: - invoked_with = self.aliases.get(invoked_with)[1:-1] # Get command linked to alias - view = StringView(invoked_prefix + invoked_with) # Create StringView for new command - invoked_with = view.get_word().lower()[1:] # Parse the new command - found_command = self.all_commands.get(invoked_with) # Get the command function + message.author = thread.recipient # Allow for get_contexts to work - ctx_ = cls(prefix=self.prefix, view=view, bot=self, message=message) - ctx_.command = found_command - - ctx_.invoked_with = invoked_with - ctx_.thread = thread - discord.utils.find(view.skip_string, await self.get_prefix()) + for alias in aliases: + message.content = invoked_prefix + alias + ctxs += await self.get_contexts(message) - ctxs += [ctx_] + message.author = self.modmail_guild.me # Fix message so commands execute properly for ctx in ctxs: if ctx.command: From e31cc7681c2ea7ec23ca0e8b5fb696206a3bfe01 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Tue, 10 Aug 2021 23:07:44 +0800 Subject: [PATCH 194/209] Remove redundant category checks --- core/thread.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/core/thread.py b/core/thread.py index 700654e314..4d627226b7 100644 --- a/core/thread.py +++ b/core/thread.py @@ -1294,20 +1294,6 @@ async def create( self.cache[recipient.id] = thread - # Schedule thread setup for later - cat = self.bot.main_category - if category is None and len(cat.channels) >= 49: - fallback_id = self.bot.config["fallback_category_id"] - if fallback_id: - fallback = discord.utils.get(cat.guild.categories, id=int(fallback_id)) - if fallback and len(fallback.channels) < 49: - category = fallback - - if not category: - category = await cat.clone(name="Fallback Modmail") - self.bot.config.set("fallback_category_id", str(category.id)) - await self.bot.config.update() - if (message or not manual_trigger) and self.bot.config["confirm_thread_creation"]: if not manual_trigger: destination = recipient From 0110130600ce86240a197dcd719e892dfc47c09b Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Tue, 17 Aug 2021 15:01:20 +0800 Subject: [PATCH 195/209] ?contact accepts multiple users, or role #3082 --- CHANGELOG.md | 9 ++- README.md | 2 +- bot.py | 2 +- cogs/modmail.py | 155 +++++++++++++++++++++++++++++------------------- cogs/utility.py | 6 +- pyproject.toml | 2 +- 6 files changed, 107 insertions(+), 69 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1745e32aa..18d56cc081 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,23 +6,26 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. -# v3.10.0-dev8 +# v3.10.0-dev9 v3.10 adds group conversations while resolving other bugs and QOL changes. It is potentially breaking to some plugins that adds functionality to threads. ### Breaking - `Thread.recipient` (`str`) is now `Thread.recipients` (`List[str]`). -- `Thread.reply` now returns mod_message, user_message1, user_message2... It is no longer limited at a size 2 tuple. +- `Thread.reply` now returns `mod_message, user_message1, user_message2`... It is no longer limited at a size 2 tuple. ### Added -- Ability to have group conversations. ([GH #143](https://github.com/kyb3r/modmail/issues/143)) +- Ability to have group conversations with up to 5 users. ([GH #143](https://github.com/kyb3r/modmail/issues/143)) - Snippets are invoked case insensitively. ([GH #3077](https://github.com/kyb3r/modmail/issues/3077), [PR #3080](https://github.com/kyb3r/modmail/pull/3080)) - Default tags now use top hoisted role. ([GH #3014](https://github.com/kyb3r/modmail/issues/3014)) - New thread-related config - `thread_show_roles`, `thread_show_account_age`, `thread_show_join_age`, `thread_cancelled`, `thread_creation_contact_title`, `thread_creation_self_contact_response`, `thread_creation_contact_response`. ([GH #3072](https://github.com/kyb3r/modmail/issues/3072)) - `use_timestamp_channel_name` config to create thread channels by timestamp. +### Improved +- `?contact` now accepts a role or multiple users (creates a group conversation). ([GH #3082](https://github.com/kyb3r/modmail/issues/3082)) + ### Fixed - Certain situations where the internal thread cache breaks and spams new channels. ([GH #3022](https://github.com/kyb3r/modmail/issues/3022), [PR #3028](https://github.com/kyb3r/modmail/pull/3028)) diff --git a/README.md b/README.md index 21e655a23c..d45da9823e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@
- +
diff --git a/bot.py b/bot.py index 8f581ab4e3..f8fa9e2b35 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.10.0-dev8" +__version__ = "3.10.0-dev9" import asyncio diff --git a/cogs/modmail.py b/cogs/modmail.py index 57f7f017d6..2c8dedcbe7 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -718,6 +718,16 @@ async def adduser(self, ctx, *users_arg: Union[discord.Member, discord.Role, str ctx.command.reset_cooldown(ctx) return + if len(users + ctx.thread.recipients) > 5: + em = discord.Embed( + title="Error", + description="Only 5 users are allowed in a group conversation", + color=self.bot.error_color, + ) + await ctx.send(embed=em) + ctx.command.reset_cooldown(ctx) + return + if not silent: description = self.bot.formatter.format( self.bot.config["private_added_to_group_response"], moderator=ctx.author @@ -1308,14 +1318,14 @@ async def edit(self, ctx, message_id: Optional[int] = None, *, message: str): @checks.has_permissions(PermissionLevel.REGULAR) async def selfcontact(self, ctx): """Creates a thread with yourself""" - await ctx.invoke(self.contact, user=ctx.author) + await ctx.invoke(self.contact, users=[ctx.author]) @commands.command(usage=" [category] [options]") @checks.has_permissions(PermissionLevel.SUPPORTER) async def contact( self, ctx, - user: Union[discord.Member, discord.User], + users: commands.Greedy[Union[discord.Member, discord.User, discord.Role]], *, category: Union[SimilarCategoryConverter, str] = None, manual_trigger=True, @@ -1327,7 +1337,8 @@ async def contact( will be created in that specified category. `category`, if specified, may be a category ID, mention, or name. - `user` may be a user ID, mention, or name. + `users` may be a user ID, mention, or name. If multiple users are specified, a group thread will start. + A maximum of 5 users are allowed. `options` can be `silent` or `silently`. """ silent = False @@ -1345,75 +1356,99 @@ async def contact( if isinstance(category, str): category = None - if user.bot: - embed = discord.Embed(color=self.bot.error_color, description="Cannot start a thread with a bot.") - return await ctx.send(embed=embed, delete_after=3) + errors = [] + for u in list(users): + if isinstance(u, discord.Role): + users += u.members + users.remove(u) - exists = await self.bot.threads.find(recipient=user) - if exists: - desc = "A thread for this user already exists" - if exists.channel: - desc += f" in {exists.channel.mention}" - desc += "." - embed = discord.Embed(color=self.bot.error_color, description=desc) - await ctx.channel.send(embed=embed, delete_after=3) + for u in list(users): + exists = await self.bot.threads.find(recipient=u) + if exists: + errors.append(f"A thread for {u} already exists.") + if exists.channel: + errors[-1] += f" in {exists.channel.mention}" + errors[-1] += "." + users.remove(u) + elif u.bot: + errors.append(f"{u} is a bot, cannot add to thread.") + users.remove(u) + elif await self.bot.is_blocked(u): + ref = f"{u.mention} is" if ctx.author != u else "You are" + errors.append(f"{ref} currently blocked from contacting {self.bot.user.name}.") + users.remove(u) - else: - creator = ctx.author if manual_trigger else user - if await self.bot.is_blocked(user): - if not manual_trigger: # react to contact - return + if len(users) > 5: + errors.append("Group conversations only support 5 users.") + users = [] - ref = f"{user.mention} is" if creator != user else "You are" - embed = discord.Embed( - color=self.bot.error_color, - description=f"{ref} currently blocked from contacting {self.bot.user.name}.", - ) - return await ctx.send(embed=embed) + if errors or not users: + if not users: + # no users left + title = "Thread not created" + else: + title = None - thread = await self.bot.threads.create( - recipient=user, - creator=creator, - category=category, - manual_trigger=manual_trigger, - ) - if thread.cancelled: + if manual_trigger: # not react to contact + embed = discord.Embed(title=title, color=self.bot.error_color, description="\n".join(errors)) + await ctx.send(embed=embed, delete_after=10) + + if not users: + # end return - if self.bot.config["dm_disabled"] in (DMDisabled.NEW_THREADS, DMDisabled.ALL_THREADS): - logger.info("Contacting user %s when Modmail DM is disabled.", user) + creator = ctx.author if manual_trigger else users[0] - if not silent and not self.bot.config.get("thread_contact_silently"): - if creator.id == user.id: - description = self.bot.config["thread_creation_self_contact_response"] - else: - description = self.bot.formatter.format( - self.bot.config["thread_creation_contact_response"], creator=creator - ) + thread = await self.bot.threads.create( + recipient=users[0], + creator=creator, + category=category, + manual_trigger=manual_trigger, + ) - em = discord.Embed( - title=self.bot.config["thread_creation_contact_title"], - description=description, - color=self.bot.main_color, + if thread.cancelled: + return + + if self.bot.config["dm_disabled"] in (DMDisabled.NEW_THREADS, DMDisabled.ALL_THREADS): + logger.info("Contacting user %s when Modmail DM is disabled.", users[0]) + + if not silent and not self.bot.config.get("thread_contact_silently"): + if creator.id == users[0].id: + description = self.bot.config["thread_creation_self_contact_response"] + else: + description = self.bot.formatter.format( + self.bot.config["thread_creation_contact_response"], creator=creator ) - if self.bot.config["show_timestamp"]: - em.timestamp = datetime.utcnow() - em.set_footer(text=f"{creator}", icon_url=creator.avatar_url) - await user.send(embed=em) - embed = discord.Embed( - title="Created Thread", - description=f"Thread started by {creator.mention} for {user.mention}.", + em = discord.Embed( + title=self.bot.config["thread_creation_contact_title"], + description=description, color=self.bot.main_color, ) - await thread.wait_until_ready() - await thread.channel.send(embed=embed) - - if manual_trigger: - sent_emoji, _ = await self.bot.retrieve_emoji() - await self.bot.add_reaction(ctx.message, sent_emoji) - await asyncio.sleep(5) - await ctx.message.delete() + if self.bot.config["show_timestamp"]: + em.timestamp = datetime.utcnow() + em.set_footer(text=f"{creator}", icon_url=creator.avatar_url) + + for u in users: + await u.send(embed=em) + + embed = discord.Embed( + title="Created Thread", + description=f"Thread started by {creator.mention} for {', '.join(u.mention for u in users)}.", + color=self.bot.main_color, + ) + await thread.wait_until_ready() + + if users[1:]: + await thread.add_users(users[1:]) + + await thread.channel.send(embed=embed) + + if manual_trigger: + sent_emoji, _ = await self.bot.retrieve_emoji() + await self.bot.add_reaction(ctx.message, sent_emoji) + await asyncio.sleep(5) + await ctx.message.delete() @commands.group(invoke_without_command=True) @checks.has_permissions(PermissionLevel.MODERATOR) diff --git a/cogs/utility.py b/cogs/utility.py index db3fa8e746..41bf5ce655 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -695,7 +695,7 @@ async def mention(self, ctx, *user_or_role: Union[discord.Role, discord.Member, option = user_or_role[0].lower() if option == "disable": embed = discord.Embed( - description=f"Disabled mention on thread creation.", + description="Disabled mention on thread creation.", color=self.bot.main_color, ) self.bot.config["mention"] = None @@ -893,7 +893,7 @@ async def config_help(self, ctx, key: str.lower = None): description=f"`{key}` is an invalid key.", ) if closest: - embed.add_field(name=f"Perhaps you meant:", value="\n".join(f"`{x}`" for x in closest)) + embed.add_field(name="Perhaps you meant:", value="\n".join(f"`{x}`" for x in closest)) return await ctx.send(embed=embed) config_help = self.bot.config.config_help @@ -1850,7 +1850,7 @@ async def autotrigger_test(self, ctx, *, text): embed = discord.Embed( title="Keyword Not Found", color=self.bot.error_color, - description=f"No autotrigger keyword found.", + description="No autotrigger keyword found.", ) return await ctx.send(embed=embed) diff --git a/pyproject.toml b/pyproject.toml index 67a9dbd4f7..8288e6dd2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ extend-exclude = ''' [tool.poetry] name = 'Modmail' -version = '3.10.0-dev8' +version = '3.10.0-dev9' description = "Modmail is similar to Reddit's Modmail, both in functionality and purpose. It serves as a shared inbox for server staff to communicate with their users in a seamless way." license = 'AGPL-3.0-only' authors = [ From 936585a5ba96bd61cde91ffc69edce5c542e27ac Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sat, 21 Aug 2021 12:18:19 +0800 Subject: [PATCH 196/209] Changelog and fix bot perm level --- CHANGELOG.md | 4 +++- README.md | 2 +- bot.py | 2 +- cogs/utility.py | 3 --- core/checks.py | 2 +- pyproject.toml | 2 +- 6 files changed, 7 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18d56cc081..d965aa8b18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. -# v3.10.0-dev9 +# v3.10.0-dev10 v3.10 adds group conversations while resolving other bugs and QOL changes. It is potentially breaking to some plugins that adds functionality to threads. @@ -25,6 +25,7 @@ v3.10 adds group conversations while resolving other bugs and QOL changes. It is ### Improved - `?contact` now accepts a role or multiple users (creates a group conversation). ([GH #3082](https://github.com/kyb3r/modmail/issues/3082)) +- Aliases are now supported in autotrigger. ([GH #3081](https://github.com/kyb3r/modmail/pull/3081)) ### Fixed @@ -39,6 +40,7 @@ v3.10 adds group conversations while resolving other bugs and QOL changes. It is - `thread_auto_close` timer now only resets on non-note and replies from mods. ([GH #3030](https://github.com/kyb3r/modmail/issues/3030)) - Deleted messages are now deleted on both ends. ([GH #3041](https://github.com/kyb3r/modmail/issues/3041), [@JerrieAries](https://github.com/kyb3r/modmail/commit/20b31f8e8b5497943513997fef788d72ae668438)) - Persistent notes are now properly deleted from the database. ([GH #3013](https://github.com/kyb3r/modmail/issues/3013)) +- Modmail Bot is now recognized to have `OWNER` permission level. This affects what can be run in autotriggers. ### Internal diff --git a/README.md b/README.md index d45da9823e..62e9496873 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@
- +
diff --git a/bot.py b/bot.py index 27231da3cc..98af158c5a 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.10.0-dev9" +__version__ = "3.10.0-dev10" import asyncio diff --git a/cogs/utility.py b/cogs/utility.py index ca5c9acfae..b0b2a344fc 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -1755,7 +1755,6 @@ async def autotrigger_add(self, ctx, keyword, *, command): if not valid and self.bot.aliases: for n in range(1, len(split_cmd) + 1): if self.bot.aliases.get(" ".join(split_cmd[0:n])): - print(self.bot.aliases.get(" ".join(split_cmd[0:n]))) valid = True break @@ -1794,9 +1793,7 @@ async def autotrigger_edit(self, ctx, keyword, *, command): if not valid and self.bot.aliases: for n in range(1, len(split_cmd) + 1): - print(" ".join(split_cmd[0:n]), self.bot.aliases.get(" ".join(split_cmd[0:n]))) if self.bot.aliases.get(" ".join(split_cmd[0:n])): - print(self.bot.aliases.get(" ".join(split_cmd[0:n]))) valid = True break diff --git a/core/checks.py b/core/checks.py index 3f3666538d..15dcb098da 100644 --- a/core/checks.py +++ b/core/checks.py @@ -39,7 +39,7 @@ async def setup(ctx): async def check_permissions(ctx, command_name) -> bool: """Logic for checking permissions for a command for a user""" - if await ctx.bot.is_owner(ctx.author): + if await ctx.bot.is_owner(ctx.author) or ctx.author.id == ctx.bot.user.id: # Bot owner(s) (and creator) has absolute power over the bot return True diff --git a/pyproject.toml b/pyproject.toml index 8288e6dd2f..d06f3ea7c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ extend-exclude = ''' [tool.poetry] name = 'Modmail' -version = '3.10.0-dev9' +version = '3.10.0-dev10' description = "Modmail is similar to Reddit's Modmail, both in functionality and purpose. It serves as a shared inbox for server staff to communicate with their users in a seamless way." license = 'AGPL-3.0-only' authors = [ From 7a4582d65df21eca107c967ebe0f098614afc6dc Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sat, 21 Aug 2021 12:19:22 +0800 Subject: [PATCH 197/209] Remove extra loop.close --- bot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bot.py b/bot.py index 98af158c5a..9894913126 100644 --- a/bot.py +++ b/bot.py @@ -286,7 +286,6 @@ def _cancel_tasks(): loop.run_until_complete(loop.shutdown_asyncgens()) finally: logger.info("Closing the event loop.") - # loop.close() if not future.cancelled(): try: From 4e54fd45d3e9cc84d3890994eac22a58a8cb4784 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sun, 5 Sep 2021 00:24:45 +0800 Subject: [PATCH 198/209] Push version to 3.10 --- CHANGELOG.md | 2 +- README.md | 2 +- bot.py | 2 +- pyproject.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d965aa8b18..6164ce1dbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. -# v3.10.0-dev10 +# v3.10.0 v3.10 adds group conversations while resolving other bugs and QOL changes. It is potentially breaking to some plugins that adds functionality to threads. diff --git a/README.md b/README.md index 62e9496873..a57b07f33b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@
- +
diff --git a/bot.py b/bot.py index 9894913126..0f178989cd 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.10.0-dev10" +__version__ = "3.10.0" import asyncio diff --git a/pyproject.toml b/pyproject.toml index d06f3ea7c4..2c5870d4a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ extend-exclude = ''' [tool.poetry] name = 'Modmail' -version = '3.10.0-dev10' +version = '3.10.0' description = "Modmail is similar to Reddit's Modmail, both in functionality and purpose. It serves as a shared inbox for server staff to communicate with their users in a seamless way." license = 'AGPL-3.0-only' authors = [ From b30e0d7634e7ec3ddac329904c242ff65c329e29 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sun, 5 Sep 2021 00:36:21 +0800 Subject: [PATCH 199/209] Quick bugfix on config help and debug hastebin --- cogs/utility.py | 1 - core/config_help.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/cogs/utility.py b/cogs/utility.py index b0b2a344fc..bc813e9d6e 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -444,7 +444,6 @@ async def debug_hastebin(self, ctx): with open( os.path.join(os.path.dirname(os.path.abspath(__file__)), f"../temp/{log_file_name}.log"), "rb+", - encoding="utf-8", ) as f: logs = BytesIO(f.read().strip()) diff --git a/core/config_help.json b/core/config_help.json index 1a57d3a3e8..ee91e3d37d 100644 --- a/core/config_help.json +++ b/core/config_help.json @@ -434,7 +434,7 @@ ] }, "thread_creation_contact_response": { - "default": "\"{creator.name} has opened a Modmail thread.\"", + "default": "\"{{creator.name}} has opened a Modmail thread.\"", "description": "This is the message embed description sent to recipients when contacted by a mod.", "examples": [ "`{prefix}config set thread_creation_contact_response New thread opened.`" From 4ef803337b5c6359d6711cbd71f3c5188ec54918 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sun, 5 Sep 2021 00:44:00 +0800 Subject: [PATCH 200/209] Fix bug where snippet add did not check command name --- cogs/modmail.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cogs/modmail.py b/cogs/modmail.py index c4244a5b12..bb3860bfde 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -206,7 +206,13 @@ async def snippet_add(self, ctx, name: str.lower, *, value: commands.clean_conte {prefix}snippet add "two word" this is a two word snippet. ``` """ - if name in self.bot.snippets: + if self.bot.get_command(name): + embed = discord.Embed( + title="Error", + color=self.bot.error_color, + description=f"A command with the same name already exists: `{name}`.", + ) + elif name in self.bot.snippets: embed = discord.Embed( title="Error", color=self.bot.error_color, From 83225e939b514d3e7b1b9baab6d692a26410e453 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sun, 5 Sep 2021 01:06:29 +0800 Subject: [PATCH 201/209] v3.10.1 - fix edit cmd --- CHANGELOG.md | 9 +++++++++ README.md | 2 +- bot.py | 2 +- core/thread.py | 2 +- pyproject.toml | 2 +- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6164ce1dbf..73cafb190f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. +# v3.10.1 + +This is a hotfix for the edit command. + +### Fixed + +- `?edit` now works properly. + # v3.10.0 v3.10 adds group conversations while resolving other bugs and QOL changes. It is potentially breaking to some plugins that adds functionality to threads. @@ -24,6 +32,7 @@ v3.10 adds group conversations while resolving other bugs and QOL changes. It is - `use_timestamp_channel_name` config to create thread channels by timestamp. ### Improved + - `?contact` now accepts a role or multiple users (creates a group conversation). ([GH #3082](https://github.com/kyb3r/modmail/issues/3082)) - Aliases are now supported in autotrigger. ([GH #3081](https://github.com/kyb3r/modmail/pull/3081)) diff --git a/README.md b/README.md index a57b07f33b..2604990a87 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@
- +
diff --git a/bot.py b/bot.py index 0f178989cd..7917bb73cf 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.10.0" +__version__ = "3.10.1" import asyncio diff --git a/core/thread.py b/core/thread.py index 1cde667c6a..54509cdc36 100644 --- a/core/thread.py +++ b/core/thread.py @@ -665,7 +665,7 @@ async def edit_message(self, message_id: typing.Optional[int], message: str) -> tasks = [self.bot.api.edit_message(message1.id, message), message1.edit(embed=embed1)] if message2 is not [None]: for m2 in message2: - embed2 = message2.embeds[0] + embed2 = m2.embeds[0] embed2.description = message tasks += [m2.edit(embed=embed2)] elif message1.embeds[0].author.name.startswith("Persistent Note"): diff --git a/pyproject.toml b/pyproject.toml index 2c5870d4a2..ec51bb12a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ extend-exclude = ''' [tool.poetry] name = 'Modmail' -version = '3.10.0' +version = '3.10.1' description = "Modmail is similar to Reddit's Modmail, both in functionality and purpose. It serves as a shared inbox for server staff to communicate with their users in a seamless way." license = 'AGPL-3.0-only' authors = [ From a2f57311ade38a6554579dad0b4259d7bc4f5469 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sun, 5 Sep 2021 01:19:48 +0800 Subject: [PATCH 202/209] Fix contact --- bot.py | 4 ++-- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bot.py b/bot.py index 7917bb73cf..20b7b05cb7 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.10.1" +__version__ = "3.10.0" import asyncio @@ -1329,7 +1329,7 @@ async def handle_react_to_contact(self, payload): return await member.send(embed=embed) ctx = await self.get_context(message) - await ctx.invoke(self.get_command("contact"), user=member, manual_trigger=False) + await ctx.invoke(self.get_command("contact"), users=[member], manual_trigger=False) async def on_raw_reaction_add(self, payload): await asyncio.gather( diff --git a/pyproject.toml b/pyproject.toml index ec51bb12a4..1d60fc9d97 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,5 +34,5 @@ repository = 'https://github.com/kyb3r/modmail' homepage = 'https://github.com/kyb3r/modmail' keywords = ['discord', 'modmail'] -[tool.pylint.format] +[tool.pylint.format]react_to_contact_message max-line-length = "110" From ed7a2ae7031b78cc647db9979b1a7bb885562c16 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sun, 5 Sep 2021 01:21:03 +0800 Subject: [PATCH 203/209] bump ver --- CHANGELOG.md | 7 +++++++ README.md | 2 +- bot.py | 2 +- pyproject.toml | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73cafb190f..af6e0cc407 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. +# v3.10.2 +This is a hotfix for react to contact. + +### Fixed + +- React to contact now works properly. + # v3.10.1 This is a hotfix for the edit command. diff --git a/README.md b/README.md index 2604990a87..f4b8e8d2ff 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@
- +
diff --git a/bot.py b/bot.py index 20b7b05cb7..7d4248173c 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.10.0" +__version__ = "3.10.2" import asyncio diff --git a/pyproject.toml b/pyproject.toml index 1d60fc9d97..e7438ca373 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ extend-exclude = ''' [tool.poetry] name = 'Modmail' -version = '3.10.1' +version = '3.10.2' description = "Modmail is similar to Reddit's Modmail, both in functionality and purpose. It serves as a shared inbox for server staff to communicate with their users in a seamless way." license = 'AGPL-3.0-only' authors = [ From 778574ef52de95b593e22014c9aa36f402ae0b27 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sun, 5 Sep 2021 01:21:46 +0800 Subject: [PATCH 204/209] oops --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e7438ca373..4dc7d411a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,5 +34,5 @@ repository = 'https://github.com/kyb3r/modmail' homepage = 'https://github.com/kyb3r/modmail' keywords = ['discord', 'modmail'] -[tool.pylint.format]react_to_contact_message +[tool.pylint.format] max-line-length = "110" From 3d494bb32b7b0578406c5a8f5e62b1c20d642791 Mon Sep 17 00:00:00 2001 From: kato <78689486+TheDiscordHistorian@users.noreply.github.com> Date: Thu, 21 Oct 2021 09:39:16 +0600 Subject: [PATCH 205/209] Add phish checker plugin --- plugins/registry.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/registry.json b/plugins/registry.json index b39ac345bd..ad8dbebaf9 100644 --- a/plugins/registry.json +++ b/plugins/registry.json @@ -247,5 +247,13 @@ "title": "Claim Thread", "icon_url": "https://cdn.discordapp.com/avatars/180314310298304512/7552e0089004079304cc9912d13ac81d.png", "thumbnail_url": "https://cdn.discordapp.com/avatars/180314310298304512/7552e0089004079304cc9912d13ac81d.png" + }, + "phishchecker": { + "repository": "TheDiscordHistorian/historian-cogs", + "branch": "main", + "description": "Deletes scam links from your server and optionally kick / ban the user.", + "title": "Scam Link Detector", + "icon_url": "https://cdn.discordapp.com/attachments/576521645540245505/895661244743299102/antifish.png", + "thumbnail_url": "https://cdn.discordapp.com/attachments/576521645540245505/895661244743299102/antifish.png" } } From b02934dc64ad4fd272bd37370eb613ab66b94ef4 Mon Sep 17 00:00:00 2001 From: Taku 3 Animals <45324516+Taaku18@users.noreply.github.com> Date: Sun, 7 Nov 2021 18:05:15 -0800 Subject: [PATCH 206/209] Added sponsor --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index f4b8e8d2ff..2b4f44ea62 100644 --- a/README.md +++ b/README.md @@ -190,12 +190,20 @@ Real Madrid:

+Advertise Your Server: +
+ + + +
+
Discord Advice Center:
+ Become a sponsor on [Patreon](https://patreon.com/kyber). ## Plugins From 80dafcad0c689888d3e1a8e86f99b04f045ab7ed Mon Sep 17 00:00:00 2001 From: Taku 3 Animals <45324516+Taaku18@users.noreply.github.com> Date: Sun, 7 Nov 2021 18:14:07 -0800 Subject: [PATCH 207/209] Update SPONSORS.json --- SPONSORS.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/SPONSORS.json b/SPONSORS.json index 30081e24e5..a1ec35d6dd 100644 --- a/SPONSORS.json +++ b/SPONSORS.json @@ -90,5 +90,20 @@ } ] } + }, + { + "embed": { + "title": "Advertise Your Server", + "description": "Advertise Your Server is the leading advertising and growth Discord Server. With over 60,000 members we can help grow your community with our range of services.\n\n__**Advertise Your Server offers everything you need to grow and find servers:**__\n\n:chart_with_upwards_trend: **Discord Growth Experts** to give you advice on how to __grow your server.__ (server/advert reviews, growth tips)\n:dividers: Over 40 different channels for **different server categories.**\n:robot: Our own __custom__ **bump bot.** (Liam)\n:bar_chart: Currently the __BIGGEST__ advertising server on Discord.\n:computer: Our own server __Listing Site__!\n:ticket: Small Servers Program for servers with less than 300 members.\n:dvd: Weekly Podcast, Blog, Email Newsletter and YouTube Tutorials. \n\nhttps://discord.gg/zP8KcF4VQz\nhttps://aysdiscord.com", + "author": { + "name": "Advertise Your Server", + "icon_url": "https://cdn.discordapp.com/attachments/563522692418895872/907067815486427176/logo4.png" + }, + "color": 431075, + "footer": { + "text": "Grow Your Discord Server" + }, + "image": "https://cdn.discordapp.com/attachments/472811257913933834/907068966311166043/unknown_2.png" + } } ] From a1aacbfb817f6410d9c8b4fce41bbd0e1e55b6b3 Mon Sep 17 00:00:00 2001 From: Yee Jia Rong <28086837+fourjr@users.noreply.github.com> Date: Sat, 20 Nov 2021 19:07:11 +0200 Subject: [PATCH 208/209] Update bandit baseline: --- .bandit_baseline.json | 60 +++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/.bandit_baseline.json b/.bandit_baseline.json index 28a4e47b9c..76153c873f 100644 --- a/.bandit_baseline.json +++ b/.bandit_baseline.json @@ -1,6 +1,6 @@ { "errors": [], - "generated_at": "2020-11-26T11:00:36Z", + "generated_at": "2021-11-20T17:06:28Z", "metrics": { "./bot.py": { "CONFIDENCE.HIGH": 1.0, @@ -11,7 +11,7 @@ "SEVERITY.LOW": 1.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 1321, + "loc": 1406, "nosec": 0 }, "./cogs/modmail.py": { @@ -23,7 +23,7 @@ "SEVERITY.LOW": 0.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 1273, + "loc": 1678, "nosec": 0 }, "./cogs/plugins.py": { @@ -35,7 +35,7 @@ "SEVERITY.LOW": 1.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 578, + "loc": 597, "nosec": 0 }, "./cogs/utility.py": { @@ -47,7 +47,7 @@ "SEVERITY.LOW": 1.0, "SEVERITY.MEDIUM": 1.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 1755, + "loc": 1768, "nosec": 0 }, "./core/_color_data.py": { @@ -71,7 +71,7 @@ "SEVERITY.LOW": 1.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 155, + "loc": 159, "nosec": 0 }, "./core/checks.py": { @@ -83,7 +83,7 @@ "SEVERITY.LOW": 0.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 90, + "loc": 105, "nosec": 0 }, "./core/clients.py": { @@ -95,7 +95,7 @@ "SEVERITY.LOW": 1.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 587, + "loc": 598, "nosec": 0 }, "./core/config.py": { @@ -107,7 +107,7 @@ "SEVERITY.LOW": 0.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 352, + "loc": 375, "nosec": 0 }, "./core/decorators.py": { @@ -131,7 +131,7 @@ "SEVERITY.LOW": 0.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 202, + "loc": 204, "nosec": 0 }, "./core/paginator.py": { @@ -155,7 +155,7 @@ "SEVERITY.LOW": 0.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 996, + "loc": 1097, "nosec": 0 }, "./core/time.py": { @@ -167,7 +167,7 @@ "SEVERITY.LOW": 0.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 158, + "loc": 156, "nosec": 0 }, "./core/utils.py": { @@ -179,19 +179,7 @@ "SEVERITY.LOW": 0.0, "SEVERITY.MEDIUM": 0.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 282, - "nosec": 0 - }, - "./plugins/kyb3r/modmail-plugins/profanity-filter-master/profanity-filter.py": { - "CONFIDENCE.HIGH": 0.0, - "CONFIDENCE.LOW": 0.0, - "CONFIDENCE.MEDIUM": 0.0, - "CONFIDENCE.UNDEFINED": 0.0, - "SEVERITY.HIGH": 0.0, - "SEVERITY.LOW": 0.0, - "SEVERITY.MEDIUM": 0.0, - "SEVERITY.UNDEFINED": 0.0, - "loc": 81, + "loc": 351, "nosec": 0 }, "_totals": { @@ -203,20 +191,20 @@ "SEVERITY.LOW": 5.0, "SEVERITY.MEDIUM": 1.0, "SEVERITY.UNDEFINED": 0.0, - "loc": 9214, + "loc": 9878, "nosec": 0 } }, "results": [ { - "code": "11 from datetime import datetime\n12 from subprocess import PIPE\n13 from types import SimpleNamespace\n", + "code": "13 from datetime import datetime\n14 from subprocess import PIPE\n15 from types import SimpleNamespace\n", "filename": "./bot.py", "issue_confidence": "HIGH", "issue_severity": "LOW", "issue_text": "Consider possible security implications associated with PIPE module.", - "line_number": 12, + "line_number": 14, "line_range": [ - 12 + 14 ], "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_imports.html#b404-import-subprocess", "test_id": "B404", @@ -238,28 +226,28 @@ "test_name": "blacklist" }, { - "code": "13 from json import JSONDecodeError, loads\n14 from subprocess import PIPE\n15 from textwrap import indent\n", + "code": "12 from json import JSONDecodeError, loads\n13 from subprocess import PIPE\n14 from textwrap import indent\n", "filename": "./cogs/utility.py", "issue_confidence": "HIGH", "issue_severity": "LOW", "issue_text": "Consider possible security implications associated with PIPE module.", - "line_number": 14, + "line_number": 13, "line_range": [ - 14 + 13 ], "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_imports.html#b404-import-subprocess", "test_id": "B404", "test_name": "blacklist" }, { - "code": "2039 try:\n2040 exec(to_compile, env) # pylint: disable=exec-used\n2041 except Exception as exc:\n", + "code": "2061 try:\n2062 exec(to_compile, env) # pylint: disable=exec-used\n2063 except Exception as exc:\n", "filename": "./cogs/utility.py", "issue_confidence": "HIGH", "issue_severity": "MEDIUM", "issue_text": "Use of exec detected.", - "line_number": 2040, + "line_number": 2062, "line_range": [ - 2040 + 2062 ], "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b102_exec_used.html", "test_id": "B102", @@ -280,7 +268,7 @@ "test_name": "blacklist" }, { - "code": "67 \n68 def __init__(self, bot, access_token: str = \"\", username: str = \"\", **kwargs):\n69 self.bot = bot\n70 self.session = bot.session\n71 self.headers: dict = None\n72 self.access_token = access_token\n73 self.username = username\n74 self.avatar_url: str = kwargs.pop(\"avatar_url\", \"\")\n75 self.url: str = kwargs.pop(\"url\", \"\")\n76 if self.access_token:\n77 self.headers = {\"Authorization\": \"token \" + str(access_token)}\n78 \n79 @property\n80 def BRANCH(self):\n", + "code": "67 \n68 def __init__(self, bot, access_token: str = \"\", username: str = \"\", **kwargs):\n69 self.bot = bot\n70 self.session = bot.session\n71 self.headers: Optional[dict] = None\n72 self.access_token = access_token\n73 self.username = username\n74 self.avatar_url: str = kwargs.pop(\"avatar_url\", \"\")\n75 self.url: str = kwargs.pop(\"url\", \"\")\n76 if self.access_token:\n77 self.headers = {\"Authorization\": \"token \" + str(access_token)}\n78 \n79 @property\n80 def BRANCH(self):\n", "filename": "./core/clients.py", "issue_confidence": "MEDIUM", "issue_severity": "LOW", From 9b58b99cbc2dc7deb84aad73bfbab09ed696ef80 Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Tue, 8 Feb 2022 01:59:54 -0800 Subject: [PATCH 209/209] 3.10.3 contact fix --- CHANGELOG.md | 8 ++++++++ README.md | 2 +- bot.py | 2 +- core/models.py | 13 ++++++++----- pyproject.toml | 2 +- 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af6e0cc407..8f9b461169 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ 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/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section. +# v3.10.3 +This is a hotfix for contact command. + +### Fixed + +- Fixed a bug where contacting with no category argument defaults to the top category. + + # v3.10.2 This is a hotfix for react to contact. diff --git a/README.md b/README.md index 2b4f44ea62..465b13f1ab 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@
- +
diff --git a/bot.py b/bot.py index 7d4248173c..0ee38be340 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,4 @@ -__version__ = "3.10.2" +__version__ = "3.10.3" import asyncio diff --git a/core/models.py b/core/models.py index 445f7793ae..670a23c743 100644 --- a/core/models.py +++ b/core/models.py @@ -2,6 +2,7 @@ import re import sys import os +from difflib import get_close_matches from enum import IntEnum from logging.handlers import RotatingFileHandler from string import Formatter @@ -202,13 +203,15 @@ async def convert(self, ctx, argument): return await super().convert(ctx, argument) except commands.ChannelNotFound: - def check(c): - return isinstance(c, discord.CategoryChannel) and c.name.lower().startswith(argument.lower()) - if guild: - result = discord.utils.find(check, guild.categories) + categories = {c.name.casefold(): c for c in guild.categories} else: - result = discord.utils.find(check, bot.get_all_channels()) + categories = {c.name.casefold(): c for c in bot.get_all_channels() + if isinstance(c, discord.CategoryChannel)} + + result = get_close_matches(argument.casefold(), categories.keys(), n=1, cutoff=0.75) + if result: + result = categories[result[0]] if not isinstance(result, discord.CategoryChannel): raise commands.ChannelNotFound(argument) diff --git a/pyproject.toml b/pyproject.toml index 4dc7d411a5..341e506413 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ extend-exclude = ''' [tool.poetry] name = 'Modmail' -version = '3.10.2' +version = '3.10.3' description = "Modmail is similar to Reddit's Modmail, both in functionality and purpose. It serves as a shared inbox for server staff to communicate with their users in a seamless way." license = 'AGPL-3.0-only' authors = [

gdXl9 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ⅇ1p+pdQ)u;Sc{UHl6K@KMEPq0RP}} zcW9eGC%`PR0pcnn@u!~6DYRbuflN_FQfwZ)8C>JMyWju~!_1>*x|qHtU$FUZH-7>h57?Mb+3(=d+f z^xM;q&T7FcCqDlcTxs8=q#ai6iOzWSEVwC+;TcaqfmuT!V)tlGGbZeNhngeaaI*k(59_e0?Pb^Gs3(3;5rf7WWDDS6GrBjYKHCj1N!E2LVqkaxRR)(E( zOG1_hKE`h(T@nJgv3dxS(J$N9AJ}Hjo_nhu%bRebId+;1yf^#rI$VGJ4j!&8+3$6@ z#CYI*b&7*^!wj%<;X&(2Ny3TTw`}_|`3HMB;Kv$Yf%FfC6eht)Z%=Ru09^iBMeSq@}&pqr_ zqX_j-yi|?7o~c|ks*iq1svKi*4Sb!mh6hyJE6{$U2v%1&(Xleuc4nCeTAU>=L%l|C zZ^$v+N71d4y`_v1%VN7!Wdo%f# z7Pb%XOZunI1^-i5%K!8o^h)%IIzK~yN_gLd`RlI**XiNScYa7LVC@p}9HxAUqL%H1 z`XYYrE^JRMmp!8sVnR?C@Xr(i4;=69v}ixR_`NKqh_^*kN&imKUu~V>SftM*^qKfT zlAodXHT;2Gd@&(=7thN113CCCp5{+X?;7%PZgsb^(ri(oYck~@OW(TDM_0T8Mv7cU z|A_u2|2u!tD{hhgqCb)TqEExXyA02xq=-&)3zaaiPwJ!53+66RC@oaMLDa>*?M`}s zk{!KNmK8DC0!9-#`J%gd;9RHs%v39CvyS=4(>sx8a0*J&h49#v^Ue{wwWfj!DJw92}DUG z)QobyluNJlDXkgBnhD^^D7%LfxUIH$FMizCdtcjn8~VzZie@GW`6A#XK{$tm5HZyZ zgM3;tiIKc(t$k)PQ2V^U_j&*M=|g7EoPGAkT5GSp*52zw+{)#d3o80sV325!m9Oy5 z=ohE1=0CZB^|HbmXDJ7Tvea3MMQxd+$~1ANeF>E|3_5Mw$0IPzQnkng$+PAWfg}7u zx~I}v%9gfUY%`(r#VAFiR{l*)nGNrzvqs!aP-o<0&_=NeZy9G-CzN~zq%oPs(95>` zYnqwgR^3beI5e}LOn0AZYxIQKuUh!KV<+y2x;hKxZ)vvqOqnYY^_D6zNp%pERL3_x zk)NJ(8PtjU&}zQcnaP^}auPIby%XrcJ`{FE_)jL4wa!vWIVaONYnLa7qJ2-ISD8i` z%FR}7n%TH&^8jj}-ba=t>?!nLpq5;bL|ZUVd&j~_9K8Q`D#q%D>1%c0h~oi@w)i&5 zJ%GRBd|Jl_?l4@PK!LnhIVJT*fvtF3*j|9rm@XZ6bxUFQad+ox0D_%0A7{-Gn1+2d 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�JhjeX~FdYcGA@_0J}{@M@@mFG(j4>V>;w} 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≪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 z zwX4ITG1Tz4;feZ+X8VV7==rR+55eQNMW;9>*}N2yyB%_YaiqcD-MXa&gKk?M`Jg&bCs4vNcXuGft+?P+l<+sVt@kq0X=BTr?3Raz|nZ9>-{M>}Mti z0^@S_AFB~cGy!>)e92q5F6^8?E&(6Q#xSl^~2P0j{7AqWmn)p z!cwHq8sK6)QBsNZv>YZnBZqwb_-gy|%2;r)ZFuaDj_&28F-xvibKxbfJX&X)#KS^2n0UkngPx~tq6=}uQpls`ISwnK(Q>)5Xn%U7Q(Dkpl;?+mL zOlWn_i{bXP4%B;W1Dy5{&QFO@7$l$}p}QB5nfxu9$jt;GsM z(v>eS&C$LlXQoIs%U7`TB&^Ns{}RY>TbpB}A_tA17_2!JF>?^BDQX_5@t%afppi_) z_VNDvl{n|sy@=J*PC_KdvTMYmtbS+OZjRa5TXtY$8I3^VNy9otce+X}<}}>0n^R21kuvoBi8t zvnbQ&->YBv>{)LsA{96?TkjpPh-M~~A;_r82)x?s8tHeSOZ}fLu^)&yoxEP$V}i39 zv|PJVzH{R-4Qa+7Nw_jgt1MM0FDq=E^_l|@Ko*ilP z$Srh_c6OI(9J+=xOf*?^?K5&Sm2#xBL;070?A=uG>K8v$7Hgr;-EQmLSH}=)Cdz(}!Upd*zsPj`((*rrJ_d7+2E4*;>SkuKALjFyjM&3L-HA41B z!Y^Amp+h8Ed0&b+y>rbcR|ruMamT5ik5%boRr}P1v~Tu!__qOY%*2cEsw*oVj8= zC_WqW%8xIT#hbUO9h8ZN`rlGROgimQT8YoVb4Z9Z&`Ea;grMn~80de{8&C5h zoWWmT?>Wy-CCmIbsVX24>vcdpX>Z%)Y-jh~{im)+jfZ}!7g$nq%zhlBJ)r2g-Bd6Zl?}Y3;H9)T zK4`kP#VaOoaM3W+&>MMt&F3csnwYF0OrRc#!l06nqz8vG!_Y9$PDT)Mcz7xQr+eF1 z?|{|P`Ju0%`=7A%VqsyF#;+NT@2#7(tF7(+hYZ-IW224@FR8W`COY-Gr6Ck(V={tZ zBz{sUCC*fD;{Wd7UN+IUWs`X+*rSq;VxMCwC!$fFa9vMlx7RaA{@cIbf1JXH?7Em* zMKZ!uzRbzV)J);hKC-p4M5BGfu@PCir+rs53w=y+g^DtcrW!ahfTtTlP)TQ${ygmDvkYJ$G&8`%$wy$t|GW?7TXW-Rk zP0C@=)5})jFDIgX{?+o^EG2P5=v6?y`ww@pq*21?OQq?!u{_u4`ca*QQ*!v+Vv{4Z zW^s0P$@waQ3vIjQTJ$E)f3yW@VyT@TA98Y*lr7B(TBAug#G+zFmKFqbU1_pL>|b4C zso8&nmJ=V0A428e;FDDI(~HNI zaGSNR6p1F+9v^9d#R&#s3dXn{Is;MG&xWq<;AGMXt-Pnoq&U1+skVxYPN9T@+PBo5 z*jWiEs4L%P@l$5~rdGkA15??39k}s%P^qosY?t!+2Eq@eypnddw6yEty@+Yu_(IV7B zAo3fA$F~}X%@5lg)qE%TD_ghZ8PW(0GUz<*t>caCfW!&?9fBidvST&(8tGuC*{6gb zkqE}OvD-RtzI3^p_*?5a%TvgC40r0My9dWkAl0&9eT&xhw)WpTu-_iIivB)<)7#eR z4`#O=7u~L>A=|jA!fW3`m4NAzx}FJ`IaRW zKdUgR-(tF1+30gq1N)Exug!&IjI-VZ!j>?QqV|RxgOwM9)!K1`*;?**^IL^^CuD|s zi{9ovuQu9kY{!J~eaed84Vt?oojh7T?y;Ooe(*p-4Xv|XBE1vsy@&64A`(1|4|@3urk7Ic2NPxnbct$zrr_7uq_$E}PNvdNLw@md;{rpKB|2He zo`-w}eG1*$5wM;px;YF#iho!3O{{fu>T$;U+DeAld`xZ@|Ev72d}6zr-~_Xk%|<~d zmRoV{Ekd4cmCLKKQ&xtLiWnbQ?`~qe_*^X?nGig%*a5$-pSrgGWRKcm0-J$7MlN!M z0P3OZ$YFG5394~rn;k7xgC?POJ7Y_-;KI(wIo{G$Kt!8Bk7xNhB?nQ=jF1BV;g6e_ zihKm~PQ!LLEi5{Qg@mTyzp{4=*wXrLv_Q85yEjSjaEwZ#e%xm*N6(6=9{NQ`Qs=9&L1OBr<=S%f z^V_s%vuAyz_H)&B1acX32qE8wQ;mf~+(a-DLmUg(Z~0 z-%eGPZ!;_${y#8e%M|SYpt9KZ%sM*@@GeC@B94RgZaZ&jvyXVjXC?wtz@JAZ{PX}{ z8}l7wFTm?qP8_^vTDGVZuzk+96EjcqFrsgqfz}opdM#FSUYNXdWo5_vk6Fh5Rt}nP z^_G{XTmOvlhWqlkqH9bjK?=IBfN9IR4N~q^k=dQ&4&wcjbH!zNwtLpy zPP{;Mf883W*Sw_Odx7c;;dqH5AX)VBrsrRdsE5Nulj}9#n?0WV**>y{uM2lJYJJy~F`U?XgGI zO-EEUV-Grc>xplfx%V5&q0I!zAs&Wg2eqVv`uK|~?dt0aB3>G4u0CF^)22p2_q~3C z!`7^gq^9wKaeO6u8i8P5yG~&Ab*+{XG4YYaJ!j@45G0rXh<3HOn;!%|XppDk+o zS>tL3#cWn}Zbu%IdHXe~+jcB2EQYk8ln~F6eJvK@lO?Ll$QzhTBarN3YqCK7{{Oes ztA(iinlT`EV9*CYW-y|asWw_yXsIpJx-!(W{2nCo;2S2X_6lJF_VhNF>l*b<01Y-# zN$a+Ja+pG!*it2I_}J|PS&lUGStj3eToZO~=dd_~WdqsXPi9$y;MuI%i2!ks9l>|{ z>?2s3U$!t-!&-t=TL@2onnG9tZFc*pcwUTkJl#;adxsc^4DON4#4l&T={ZjkIHCc8 z&5u_%U;Wdy!&8_ZM3)e1s24C2GaUm-gCsZg$*39kLAB$`P%StSWJ|gbdNqmt3MdYx zngxChe^hFvz)e@r*SLpJC3Rm0CjCKK;J~h3u~4wpa+IKdGw=N-<6k#Jsj261Z4L_U>x;LcA4`mtmM24|B@H}h9@vj@AJs}0V)M2AEW z@3hpyTeb3>zuA$d(=br}IsVkY4G>Rl;6%73MUI`0_D5c;v)AdTNEP5l*}jVo4dkH` zr1_2RY7GeDyrxcVhyhT{<09 z#IC_;riDWhULrVm`+F881HqSz8#^0aIxsDxxX-9~&0gd`b_#*f5a;a$1LH5J)$?1Y z)*dAkZK51VfvZXXjP0XdF%-*ElH3p$=EXSe!Vr$w(!2p;wvYX20^KsD0TMbJpg_z2 zsCvt|HoE71I6#XQcXueIKyi16Qrw-=;_j}+-95NVad+21u@;I;iaR6_;GggPd-FW6 z^4ZO1XHU+YYv!7rnKN7X>eg`-2ry)19n~}BGs~$)JYAbps6A?D&x#5%{O*AJGTOykJ)rkQz2u= zjt}nb`t14a`t0~@+w2|fLbsv2{@ecWh@Z%A&yL7W&$h^CU58Oe;-4s{`U6Bg~Zz&A4~Z9yij~mHvL$_b;cq+S#>(ITE~@ctullY#Jo2 zv!n2_%Xajg7c)~KY@>od zkM*!b{P~XeF+EpBG*eZXFKP0& zucYAo0NYIx#D)19<6;7aPTw8nqF!7#4W}DwtGQT zs=LieY9aZ%#!geBg{xS}btA*n9>E#5pY2KP`R(BW+akdnkgxnnua5AOT%gU-j(b68 zCw|Go^XcWq$3_apn2nAEuQlxe(%m)tjS@|RZlhRGJ+#NC3wc9K8Qg_=x_8TUEY~G` zz~6>cGQ`t9mU5!xMVH91-c&V%ttGj~x`u4qJp~l&XC_S3w8c7NUi;i(b$`?@UeH+G z|9Stc#l^{cP=`m1-ca+kb}#o!FLxAwe_;IqS+AGhtFF(IIgrSCHk%>fANZTQ*M@`! zn)ED-9lI@uc>xcldb)+^Sw{1BjE2j}xty8r51G5=&86F2xpyB@kTfAWmh7u5TEI$l z0)p&)fWp&;U;)KimxQoMrs->&Pi1+;?gWmlsm`nIuNmjn4|mfaf`XQI&O}r7<|}0D zCWAc2zc1Qd5I3|=e@3>ca0T?n^!|n^oyf^C1hgGdx+3L&Z~G;*(^{bj@6ns-2xQ&7 z_&#S9S6=U|5n@j5xz)X}qq`}S+AP$(Vt%$^!u1PrEpROLcx!f&NcJp6@PWJ} zms&Fjyr%hJsHNq{U%*q=TAuCO`?%aD-0*8GoNLtG>Z^-KT$Pe)(X747vQt?)H?#gN zgVW)Ul~=hxbGZ+VC=GdgN*}6zuUg;4OfxP_97-!9)j@KS-Q$wgfa?Nz71r;{Pb*C8 z-Ad~gh*G$7mlG0Mbps7X_3j-HD%|3nRc209**4z0oCQ>|-l`hz-%;_Eu#lV}^~%rI zfpI3*C=cr`W%aFGnm;B4`B$8k>CgWDb82vKWZ_hx2$fz8!994ar(5ny@rl{73N)tv zGqq%I?K5~2pglvO-)gkl{2k>7w6Vz|GCMUvpOrgb^=yRv>Avo)?YO#&#DKUCvsXrS zLGId-AEtD4f6e356LDRC*xb&x@8IB%tdqJsfl=$V_M+&!u>G!SvcbgDT-2`CY{TYM zG3e5D)K2wq`DxE?TN*HQUKYa5%X)aeEm-RTg5D>{Ss#uw=5BMU>fzv$ZiM=A9DzaeY+3kAYqFX5Du{ko!xK%*yUnCNh0gf@R6U-y?o4dZ+RV+G%< zo%XTRyuamvY`r#zwNI@?YqqncIyTd|1K-WI18zv4xyzMI%g)AbYp*O}a+ITmu1wUuqAr9GqXX}=NLnB(6mgEL*u^=SsB;2C&a z1#QieRr;f(v(SP3tx#TO1K+T8gOjzi!@D{^{pr>Q7II#bTHZL-x|jGLv&m_QI6ABH zhQEa(y*dm`&#POTHm@^YnC=!Xv9)W%>KaZ=mW7V}xVo-4^-Q?Wo{d&XN`4b{RlXPulY%EO>PGh|Z2RSj=mx-wh! zYrF4A_FkSk^M;s&R?+jyeFI&}AT2r8VoOZ?TD8G~eT+N%5X2NUX^^8ZZ5&V#kK>N8h2&5i9jYs9s>yZb^J zi5h@815_ zuldzKU;C?|!qm*3_Kx96olcP9_;O9fxl^II^(m3IpxypVij=hx{pzh&6`BGx??mx#qGw`R>-O$9db`D_Xv~uUgxX zy*&U$gzKQ4q#oi&m1l5K{@v@8@S4KjL_@?NJHYFP97dM<0Ol2#2>|rvke@*V1);QSqP_qR@{(X$hie6GH>YK~mX z=`#22(**IYdTB1(s~(zUvgF?bMJrLN zDeXbX!GiDf%eKNswx)@h=n?)mm)jJ7GEi~t?_Y_v#b4=5<1ISviEI_UHVKOy+YTiG zJEtY++1P)&z5|6K3Y=Wh)4#-6@gC)z<{Iw0CszW!ZLM0}mlEbr&3d*si+vWr|ESxw zGz6AQjQDxP5c-=_Cp~#n2YyDRTZ%uvEAO+hiRE=--J^Z-Sk_MotJigKpEf&YEUXHe zxGKH>DpFRvytTBy&5DzY{dV(Xfn%8oY@yZ1Y+B_J>-wj!{F;VCW(L&~xg&nn&$3LD zYEyNm1=pG?$^)V73O{59ODnVTqhVu6(N`6{fNwSU_;yvRA!7?3S8ScPo~GY9Se5PlRi)WNRp?1(pLrtB7p8B}wl*YG`9dEZ^1e}Bu8#c3*@mHy>SvY|q& zHI7*Y;ph>N!YG(0lE)SDn08g^QGV~$#3&eiTFQd5>JdB1Ov1kOfEmV1=wYQ&uW7pa zbd>rJm)D1v*pBbW!+?}A^cR{tGw7F>MV^^^`S}Th-3D6aY~Ufb0yHP4x;-tYy@rIA zi2hjaU$wxEp{rro`|)Zd8!)*6`TKp^+OL>DW&Tdm%sv?}I7gFhHEFNTr%V~h znAD!3UAqqs5LtfewB*=usPS7utnT2HeeK%8tq5NF+Y;%TwxhNc?-}yca@_e{D~zKx~0pI$zc2MkNm2LJ4`gq91507BikCupwe()2%XJBaS;fOe)iWAR#Y6G4YqSb#e8On~8EFAgZW^kQk=#gmX<6hLwqoMvY>Oxo z(jC0?wB22*9XvipHm+qxo>|7*2Ca9%f|gMyAiOe@# z(17}THPwYH#v{u|%2iyhP|wMOVFPAbLF!kE+3YMu(F@5A>L`i-L|Z_?gY=?hEl4c_~H?#uJSjV7Cf-t{!LSt z;0+~J>V?s~?VylqF@7Jv@gB?dMtOnI97Nh(f(!jz(yTg)PWRv_VsT}*7frFc!laa!6U})iPX`o=Cr(kO>TwGLaa3<4)U;e$w|)oy=} zSksHrii)uj;nGRt_f_>@9GTuc^BBF1>Pj$^uP>?-iVR71-^!Q0=yDLD<#=3T@wqPD3a7@F~@T^6`^rIH94 z1Ec(So_oDGejPixIDU!9OMAh$joQKOT)=jc+Esjh<&SL2d2H{-npOW7xT)JR8#VSS z^=$t4ZB!rWj=+#gI6tb;8%In+cd1KBl9*3u=6pkP;b*AKxb<{B((_9si{Do@LMWy9 z-v~-^gwuqpqvGQ-(U#Kg)67Wgh5jUh*S&lR4};eiWa6IFiAZ~EDnGvx0)s+LlCP3& zl0`Kw7A=OQ_rjvRzP1*6Sr}MO1*HtiYDicbsPa70<1dD4qzut(Y7UlN*aXlIv^G=@ zTg?B9l8_mop+63uUuxMLW3L>$2&*V9mt}evs}gmqe1=a>9o;Q~d(QolPo$n1kH-e! z&-5UhFUH41b$9e?S;PNL!qR%o%hEZ?!n>82AR7+PDZ%}@oD4+Sh(u~(^otS9bfg5h z)T9f-tRh^biJ`9J0jp|Zd-U|FgF44QC(^=y%kVU>&8 z7O3?H=%Nm|9j`i3T&b2wOC>R-rjca-7;_WpQjY(utSak6wpEDmlO#NxsZ=hK1Tol< z=8ZajY7*IJt_eVzD&nQ&dFdyBPO{W)>d;;=wqUrz)kjh^KBO~(x62r=2$#Wl>2X>i zE$=Vm@Y3-}(FP2aw3w;|_nQjGm}M~_11UKY;g0euQJ+y&<$Bu2jnNcIlLx50Xyp(t zOX$q;sH1JWn9r5osVX41rOp2=P2)+DoVA{w_{qX`7liisKqXZ90B|ajL>LM33%7n1 zPD;wbEl63(nC>5cZ5#dwZP;qty|@YcpgH_fm-b4&CMr6EK>PB=Ad6DrwSD%r!Pd`A zk9RuxVhVd#{<7lo1`-Q>fRc%+n6WtHLQu!hzN6}(&Y{+$Q=xu%V_B3wZ}MC6J+8%2 z-%#1m@{rPwos+7%mrH$z48{BP%1L&VNH24Zkhf&-XmJg7$*3x-h5dDZNgNZuFKSyJEk>!*tqp@^$vL6qFWf+!Sr*-;$NPtWH>_|-O23RpS8~~LnPr($8{>`gI}FBPL0CHs1j88e8~S`9xY_U5 z9YYnp7K`D(2@y&Dtx!^?&ZYY!vADh1gjSbbeT8+l5q*aAak%jl!R|@Yj`-oI*VWJc zhh63posYW>g#NhRXwTuCLo&3JxjVBr_cv6hl*jpJDtLD|@i?QgVljCm`J)zBGFNn0 z&Wb4`rb3b#4BfOhnO>R@*0J~BS--rW`#{CI$vS7&zvFYtx$;aENFS85-BPId$w)va zYuC+CDIGIib`opSaWdjVocIUNPq#Jau+<@|6z4x0`aw{i88p?s@+lpe+>pTi|Ayrh%lCVGy8%4Rtx*ehz_EQM=Ysf@>38e#ri1l)Fs%cH_5NT zD@%bn1|J+dJ)$~#;*SOWdw;~#$)?X}m-L4vhQK36N4Dd-<+`ycSE*8?daSBg%S!9} zZ}mKE?vx^xX2{^s)h-F=mQ^W;07w(vKVw+%5hUrrYY2_J2DD*gpIE%hoit_t&HRrk zd?-*V=i$qUX`gkgHPh5AM1v zvZE60Amo|^stlEfqROI^;X`9QV&Y@Cv_NNR=SuMXV4M26l=Lf&D&F)lNaydzZpI45 z{IFoAE>aaZzUzlVl;?J9+}{^F*f`mA@Mw1#bt(Gl_-Y!^FDnaAj5PH%;WmBzL$S2kcl`#c1!aO#L4nswoz0;Q zBn?@mdf~qFZy6d52<*$+n<}^rvdjLK!;GPyq3Sl(u4-?qBlh-sb1Wen0Z!&sAa$U) zfBmC-IwE&d5qrhN&Hh`3Vv%H&0riML8NN86(F?^3R$mzaRd6V_*sd6xuAn3Jw^Eb3 zuhA8iUE+Q*XJ7k(e&wAf0j6|72bv=0wld#7?_$LaxH!=uBO)M_Cd&RFJtIs>%=8oT zWQB3)`lf1cZZ9p_o;Q$&Jn*SMr$7Hy_!aM!w?7AjBY@uLZP$AydtNLu890frh!5|` z){^Mnf7`kw#gv4ZtRnYZPNhha^qDX2Tg1H4zmKOZe_6?Pnspxy%J@(JI(|%SWia%V zcrjY2{G{!vTb+iKPCK<;y;|+dAi4fE|I^QKdp*-{Z?aFIw;9} zz$2NX{G$kB%Kqn1Y7A$L?09`LAN1qBk2rvA;w<8kk7xJ{=@^8gG0f<_p(QO^Laa#X z=1Td$G?%59H9Zg%e~nkSe-N6Q#ki}>)Dz_!p&`}WAqcZ)K=QZy;pk6mI`sf<*xNcx z#F@eSh*L{sPhc;favHjwvzr(5YfBXw-E>Vz{{Scma%g~Sd$@bl+1XFe~Qa?g+Er($p0~I6S zED$EJKE)&@J4HK1<@))WNVdopfAe)}GU95Uj;r1}WIF6j19t@*C_EeYPy(&dN`3bX z@&+=_;1cb+Knaj8)I-ZcTSM1}95)Sktj1#&N39rmD{s3S?S4}-umMRuyNgKHeA!F$Hn098}&{T=H&^P&g_x1{6cdK&8<4 zMhRJQjE1Gp(Dkz`f?^gp<-?@^k;M^*hH!S`j47W2pCl1&E}LPz9EeJvG)0}-a&f=n zpnad45QPB!K;c`cYoA8tqwJlWc2$#9FE6%Xuq~8bE{wt&^M^CZOvD4`4a4Y9F?s$M zlD-TlMr)3Lv?O#B(gB{m5{wifQ{IxlmkqajN1l?}Gv7$%IhYu+l}c0;ltKeCP% zC#{lHD^)W<=u2+w5S}MJr8Px{6uJ>8?WB*QRTbjo2#bc5zHYWbo5Fp~Y4B;wY5i$$ z+y}2r>~!iR>h#Pamx@#`9e3T&+B4c8wNKSx^)%+9jYoR8L=lH}=-xjd=VTKh#wdW)*J6G2GCKfgc#ZuI)~UfpgS**^O59P{I?%5M4eA`vD^B~o;A<55GZ>)_FxGK2_XrTlzTX_ zXD8m$wh2S`d$~k^n=P@^XFXya7=q} zVe{gqZRDrV#)hW+^ZaJm6cpZ|0cuRx*#ULmE_Fj;`@}Z!ep}fqt?06+$}{OV)|pVnlWJH0*0cQszc1B^kKcv7-)sHDbnn)`XUB^~vv&bxbWg1T94CI-Te1PU zk84klgO5R&#RF1X!25uri<1D*eg1Ng`fK@1{fi=r@x_^{y`BmPu>?s=Cso)cpEkIIw!`fis~)dr?#sP|TCfa;y- zVtaXz@oU73+Y{(Q_mTge`3XO@X0pQvC5{o>gZjh-#VxF{V6s5w8*g+x_ASL}XD=A* z@_nS=`B`TJ_@#jUQw>B4MC?Zu+HoqN0QCmpi!%G-_15^U-qxRN09?*;`aA;s9v%Np z-)*c0iOZk@kAn(d2VW>N|1e^b(W$ih1rW&C{ye-=eWN;~lWg zWj8O7DB>#c*ryOI8S>C@FM?Ro!8(}wskr2|S)gM;;664udn-e{8yp_kDmc= z#=4n>RNck?KnY}iS?-9tFp<))lz~)DgB@B!#2UUdC^jfN3H0*s1^ouug3muPHdeF1 zgujHni!}N2Ojg7rn$iCwBCLf9agdbk9bT>0)C#e~PC>o43bE7z1^%) zz@>MGFK>@k22V6s(H_J#^2&oR+9fD23h`=feQnG{nLKp0VsM^ee8*O;x*7)Pc75Sn ztZg3^b%j%qHG?=d9|`G40|fMqC^x2p1`T}D7t3FR7bCex`JNG`JTg#ykk81A2 z@2FE~J_UTC$e^iW^7Y8Z2&r=19UyBwjAqKgb%rkdT$+~2`yi1}z_D`kJ($9$4AqDK z`sM@fHD_=s2M#SZT_mw%V z!PM?u_w83>hQmB+S%jW)^|6jj-8bn%wp+yH6_QvodP zWvEd+Wq8FD7B1M~@+AC?4>c@wvS00370^VxxBTG%r|dVjl3+!Vfwq9SiuJUezjqL} z4D#2ON8SN-u7&5@r{L6XrKK_M%af=#KAbqDe_5p1O=L9?LdS%*SB-0AP(NScc_}C5 zU!G8*HCYj6yQ1UR5waag1rlFW!U`3hhXH>s&4H0Kmn(vXKMH;=O{}=i^s&+r2^6W69VLxKn7-}&|1{N1(&<*VjV+-Cb`ht zTBe;G04c&mV-Uf~AB?0y4j*3CG6}`k=CYm52)Exnc$J)}bNDx$IsI>)f0-uS^Ev6O z$g}E_Um#EH>&iI5a3!#W1rqrBW?Dnq`ud92oCWMP=2RF=cH5;Zw^sy&M|58L7uF!? z+Z5hqaFvLGyf>9;`IREHl$$1jMIDBYHLCE~5Zt_Dw;)=N#^bwhm-2w5@Mw;EtrV=q zpn#{Wm_!wvmqa7{vp`;Ef<2fbiH2M67}x2wSfs?r^k&1LHKh@VwsaA{uroWcvs&We z&Ad(V81bI?J_R3dza;8L1^1eC3Dhn#u~nkOI6)7X3@{A$r=}Taoaisn>8F=kk&f%! z0m@9g1!TjR8w#q7-am~slaqdJ2i&1p-2O!qm?~N5btQ35fGMQeAu>KUI$8RsP2Y{R z#;3$SEDnDWH^!g1!5r9;=4=8L8Hp!kJRdeYTSfn;{VzVLH`-UoE!&16k5{NHT1ZZ= z|E$HYQ=M5T3WA1w&J}r@ytXh!#7Rq_*a&mcKpDFi5P_f@@_IPJu8Hl25nqrq?_IlD1kD z_A#D#`1u_TyoLLVRqCfS_1VK1KnwJn2p=;MA@AOn4E{bk)sIVgVCOAo+f~i`QPq<1 z2AvTfjoTV{8S#@UF4xCzzCl0>;aUkx zB-rE7`l&~A;M4NK6UA7iHs2+c;O&Q@`uu43s4T6F|4>;{(b&6glfOA2c=tT8QvhE) z+yjE{CYq&nFQUg*?d`h1H1uvp!92bzhiXnj;z`3!2$7>Cia}9lcWc(dFw*UPc3V8x zw|2BI!t9bM07cV}Qpf<;7srL;dgO`5_~O<|fI2%aEnzT)BN1&op!64=l;gNYuFQle zK(J}j&O?hmt@n(gi)g;1&6&X=2i`heK4ch))8Wddr_0Vw)4o*RCA9ZSX-vWNP2hRV z4wUt`P;SW?*w32HP-Y!MYM`7Wokuc|d{SY>qr}dPz1>25ZslEueIaYc`7k;W$iA)h zYr$k!V?c0;Y3=x%UbS)fXGEE!O9PHN(kj#C`9F^oYC)f*G%EN#oCEPNTf077x_y+p zY0n>qMxV2=gTB`E`qU97Cf_?3V1$UgQeDFID1A5Q{cLaju`h3yFrt9iNa81(q#Nf! zu2^8^p7CeG?S0<>+$j3dt&j`B8kZZT(`il(k#_>)?Xt>O^eva>4FoL&E3gnIuXO)| zGH;?vuQ7$j@08wc$q+FGm!Ac_(Q=0xfrOAhxWhMP!EVJ^1*!rf{ayhj-|;O~@FAVF z5n%JB8!bxTf?W|)SGf`$!pTRLOU>vVeZxo>Z^{?JX`jIRI62vANb?&QMcfXQ=DMdF ztH8^x;MGmLm)Eof*61D^+s3j@KE@7erf_bs`zg3aulJiJb>kL)fBfWKA-Hht5?rxV9zwY}oF1;+1bn+q2 zMQgMeWfl>9Q?Jhl{4w)AD85U&_Z9q8`irKs9_cu7e4-5L%On4uL)@;JC~{Iyi@C_w zJD3Rm@QoAF+|E`4I2(RGGe27arnCpcZoA&#d_i$e=x1CD6vGq<^NPB^@v`>tf>`9X zJ9l=0tS#+tJPP|b18p?AZnEA2ay7xT$A;QiJjR2&>5%5)>{Gjew;d}9|H5$YH>SMK;nFp{*abh z<`3hQ^nUx=0YM+)ZjF)J`^*%OsTAoS3%% zEsktx4+;%RJw9zvoB-bbO%#2)F9{7ZLmoarkX_QG1))|q)aXsp?HyeyF-^=HQf3sN zUOS44CN?~yU**$tW?N^ef#?G}^qdan#i!NpS91Ye9lGH?*o^5k*ns`u(s&L82Cpzw zC|X7ejTI6`WSIsz0@rKLv2&6pyrtmxd8%#m?aOEq%7hXlw_+oo@O{|{2f(CaQa3ib$4airis8LvUkI;qS;3o$!uy`JqQhF^TC`s~5t-%_6wOuuXClY66>|iQ&MRoz z$`ibD)szcZ8RD{vlWXOFKTQ+vNzD-kW4!#YZz;3Wnux z3{4`siAEt{xM6%`#=VvYsDIeedAMpOOLuZvQTi~apSUeZ?<_!C65lV=NmWB18W1;q z*Ps%gSMrR1sTs8cmH#ay9`&E*UD#Onv0q)wSa3#w-2n1Xu+X9P>x|D_-a;j%5#)w! z8=OE=UIshUh<^XNicz?R$gJSla8ZNFrdE!;u6ejuJSRxZ`uz zIe%_dfa^s52Ia)(fi%7)UKc^UjuQ?P>;*OP|BW$dMXi4U8}@GOQY1#eZqKJ&Ko10D zBI^tb;J-|b-|<1_=Lh10xJL(lT``SI8W6;9I7O%dc@%qswFO>?a}mVAok{%z3KnO6 zi#@J&YNX@#F@-C@HifTj&mjvI>8$B5g^P#P3DtL?aX^Kuq6ml}vNo9C-4cdy>E`qf z^co>z>MOUOXMM&xV31;BS5)@%@S(x6lpyne&PJ&MePl!Ist8>Axl`q#d@wN5!n**F zSK@IApL@YAwF0*R(3RIX=y9>mDlg^P{wWy9{MEhVl`s#OrMI*LHN}fJA`jrcY(5m_ ztoamNV*h`Tf`qwz!yE#{u<(AN*xoLm_vVy1=Kz#IJp2pEaecP?+-C!r(vt&1Zb_r6fvVGl760S(4$Zkt?e*ERj<%{~R(f58yAngMn7?(GxiH?H^AJ9twcAY^=|BO_PBSayKxZ?Y#Nj|66$ce{aMcEQ%+_2B0TdoUdUb#jUTe<;8Al9QLogimUB4=yg-4_+Yr z4`LMydn%FtExG-Pl>SIN*lm~6Lq_Ie+x5Aps7~`uVB6SbPq4^qp!>;REP>Pu5orOH zOSqBApaBA+T-#Bh-< zA-B}{T-t)S_+e7-Kpb!Lklo+;$d)i2~D6= zyx}YH0nn|$&@@@0r?|+$WyU6UP@>@CZ9y|!*EsKEIaU`q`eqJ%A;oe9L@>Hb?$;N{ z^4U~SUe~h2H_4efC{`w8AEL+>Sqb=}C*p=etlt~`%z5y^10eqk$%^kW)&}b?sd>`M z1T_PP{s<1PSMHR(Pd4nUdzhlB>2MMeztf52oFGoVA*5S;s!|i4ZG0~ULoPzq@4)Hr zWRcoLPMZ2t)AJA@I{oeSFqW)UV|d-&mDYFcN^<)$gyh-(opS;g%g`+>8{_H;vnD*Z z{uO}+^fAhhn`&^@1sp9Pjx@3DQ#`gJ%{;A$^OhzuNH*LZ!SdQ4H)XIMYK!X{7K+=0 z%0j;{1ut8nL1CD9LU8>YDuS8>=j!IcrG&WGn!yz5G|{U=uf#s<`Nv6lynfnr3#;>< zAi=X2oH22+9$lnJY}uA>p$a&7&9D;7w)^273;HJiF3*HJYmKOXu#%+7Wfc%yh-B9* z5%xzI>E?0~Twxlx*R;VD)+tFc4qwifw_L7-6#t4_S zlc@}m@+O~)Pl+V3l#}=^M4x)s3VBm0Jf!JC-64;uoGF3-EGRNdp0-T^bF>N4eN8;Z ztc(d9QN7!_@Fbcg1G*o-2DIn4gOMV>`%ljDHnO1_{~y*9S1R0XOO%eim%u(0+qQqMWe+3=c__!`;alZ=i1^5)_j-0Q>Wt zK4oc`14NwE8@UW++Zi%DGJoIZ#Y_ASgoO~6%2llhTHTeX|UbT zufArup!}!s&FZo_2&=CAwtBwc$o~gYK;A2YLdF`C!mXR<)4qIY-j)PFNpzp zL-% zs8Cu7CdP>%a~8BF_O~qz6HLMTf!ot!f(C50=((~hCCh$a4Sbl@`f25A`~X(N*!17g zo71ZukJ`fa#v;Y{HEMmowBa(3zH`>A8rC6<1gc=}MJ|#(?M&Uq3j9%~c^b?jGJ2CY z8Aas2s~rh`DVMv384iFp;7pUy8B3H3PUq0FOX_Gq7bt<}aK=L)?AC!*fDNQ85XXd3 z#Bb}vlR=t9a|Qm72AOWE6};kkzt|FhR#dcIJ*%(^5(P#2>nEQ)q6tqf{F!0-3r~hr zh@Di4DXdO-2GP``)3Pp*&b_gz^j>b?_TyoiRc_Ab-EQkhj|q>ZX0Wqf#$CyaPK}PS>^BLBt{zZ4(=$0f$!=0vbV|s{3=@EDuhj zg&ybdjq01FFD_LZlH%FMZu3opjmS&(tFtg5PY~7JgJOXZ<^iqy9=HSZl)tM#7tCA8 z7A@nHX7oYnI|Cm~zQ>3Fd`qgyIMed3_*)P+k&HV*3Qxc`| zqLI3q>n=3LD7hj@b54vRNsd`quBZ=V)5U>_iH``>ck=#A{d*-Z6HS#{XC;9(iI2Jg zOZ~?sf&ThS7uV{hx9Z}DB`;oL$B7)VkF?Yt>+;XE)ORIf$IKbB&sfxVF^P}F0ZZa% zB`-_xJDiu8+^WBZCO$rk9Y+H>A|EBG?|lBdocdPXwE5#PivJRZ*fI8FW&gT-z%cw5 z$0Y?f>OlkY&q;}o(COm7eit{t8~5s}?6JNJ@&Ui( zpT(%}$OD!Zr;EidO2iIJ0vi&29e-33`!9JxDyi?)xAsalyFVTaQs3cH-}(73G3!P> zVu~Gyi5+`ARVG3zvDsrEsi^Pj;K$I)ho{O5J)*nXMBj!)_yF&8sP87fsZ-ymt3Lg2 zJbf!gVx}ufB=KDW&G-IwJ=m4--Hrd=+(3-Z-4eK2BG16CfjM(3khzQS$>qDA>%TsK zcYHiZzO0_9Gzy45T=O|xZ~2X^+toDmQYni7&(bSPdfOh?PJ9kJZ_FsqmJQ_n96Z+S z0=E4xx$>}IM11RlY-4|SU&$j)Bm-mZES2F7gybe5iTV6GpnqUt$4dI9n7LwjxkxbS z>ldujW)|z-r@Dcn=Ru*B(t?DCzz1j#@a!hg&wukG3iQ;wNqzP)_OUe4w<7V8QtTKk zyfjHAI}a*~q9pk8`T7>Vq@OHYiK*Qnl6l0?YX-=Tev3AWl?TlnF#`C$(o;N|vwpP# zT&fB}@%IgUe23zS*CG_q!uM#saa?i-X|gU0Xd$4klclbU%#f{FmOnwHW?Pp(ahonq zzg-a8lCRk! zpk)POec<0xo*{c;^#1^XKz_d;t<^qSH~46EFN@X&AFYIs)*c_NOP57!osZTt-kLkJ zK3eORMQe?Z)>A%OlRjF{G%bhLY9Fm9eYD2>ncHR2O897<@X^}gqjhXqw4PbzrS*i5 z)`*YRgUh1zl#kZqK3ao5TK6uC)|0CuV(hRuy!#G^mO5M2btm`5sO$XE{9rDe0wrWr^iiImFU!mTjh`Dr9qp z^0dg$_qXs&1;NQ{MWDqpW|TV8=9wI4_v02m%citwse}vBW=z~vaqKCv)U#W7W`N*W zcD0-@RUUatgcS8M%g=KuO6Gq3gjCOq(tP#AIcW=!!|WSR6q2M1?uq~YZw|Q(Ao&|b zoL>XTJs%Vy20#u<^&CjMUsYqL*nBmA5+FIL=D%S$XE>0r0pxxb=`qb?%_Uw6PXQRJ zmf$<0nZ^KzM@wy8uZ#^{7Q{Nw208^KTSO z{Q= zC5YYbmEx`K-ZuRJKn_TyJ`RwaMC)$=vgcY6@-3u(qu4q{HJ>T5da<_s6| z+77z!L$JdRsv8%w8zAROH5(lXk0sm!kX}iOw*ut$s>t(wQazOCn-hu)A#K{0$VzSc z0F*i)wa!Bw?ibT2fSo6~WSuBf?bGs9XPf64_10-jPkW9C?=4%P0GUGz#yS;jT zJwTq2dgXfoQgJ;J*mGW`r}|ut-48fL$y)vvAcqYR@;!juA!+_!yS?qcz6WefvW?~* zuV?E3$e4sP1&~$=G7FGTNw)ENfb=?J0qvEVub&nY7f5pb4wNcLJl_qF!_o-yhXA?X zp{1(BQ(1LqcAYft{3+lJN*w+z)+6=zM*%V^Y5rM&JRnK&A{0UDUl#!6uw)yT^}448 zS-bao{g?reqY^*2_L56q1X{QE(nN?W`}+Wrl}dfSH{r2_|BKWmoPP!g&nQyJnSEXz zo(qr@QqOJcb5HcJ@f0BEOZ;5l=e6)MK#oh)Z};K6zt3F@U^fB*q$oj-vJF0NsjmQJ zQo{LeANBJtGzIJC4k&3>F`YedEAml*nan` zY83M42$3x6ANt+AI`7LjCss=>_)WmMKfJFV}mqE-X(b@!%9g?Iy zmwBVFQGo1`aI%+q+iM;mBT~(8^5MK6AhuM`#{i8bSVSz&$g|qFi68CjfGTq_L*}vO$8p`f~V3 zvW=e0Y0lTJXX0{i%{hQ5Qa!)La9qfHFLzI)P{>CBa=awc`m4+FOK>9Ok;}c~;E#ON z&$`R*%3zd_pK3U~faA%Ev9 z_1!DHa;+YO{w#Tt*9^L6lfgEoi@IJJ?3C;20h~@riitt@lre=|2arLj@7ROhetHm- z5>k}=PJlcv$@QlIxmDuu%K%ZNn*S9bIfox-UlaRTbsBQg5LMO71r{6A8T+$?xc|GV zt9g~g(KN2gjAycgZS7CSY z)t&BZr>`WHvX^b?c1c@Qj~t}I%!;xiHVtS{D^-ff$1Lc^tP&Y#)LEm5ZJ9UA7N}1G zCe%4F7%SD)0|6+pJT(t8L3>te7G}ouY8C4tzwj9M1hBBwy>VCb7GPBnbOI*jL zLf*Dmo9gAf3C2Q-TE@*f1A`9rQe|EPE07S?1;d_K^*pk-pQ)#-r>iXie}ncxv-!d- zZv*xKjFi})t|`tN+{#kj9cU5mD|;T`^JcL?UbQpX@-@pgOAfzzyXcXC^i;WXcto(4 z%3zT0GuQ`;R5UGHg7~z-5FOdW#FKO&a?R^?x#kcb*G$iqTG7G|Fg!YzRVS~xTHU2i zP3%r^Rsvp+3}t1nqUTjx2ipXuP?uW@yAim8idzj)i2K%ZOG2}ISpf}z-B-bZnKe6wJ_4N;d*N_I^(6f1aD-?Q z3#1%pfxWey2f_k>sAzV6o)9s#qKs$FLxT9-{{i1O6cCv<&pHhMt${yQ7)JZ1Pf)dY^cL69Sm&?gtiAl!5a`M>;1EcRVNoFJiaDrlF6Pu2r#2RmUX_C{&xl5du64v^CTVQsno^ zp#dFvD|uG!tU; zRD;G=zV2=5ML<-Eory20CGOL!WgV9>R1^=o6X+G2vEUk&2nwyh!Zz?$L4e*$D9{)A zAUMRc$=U(L_7F_5kYyD2sl($}s#ADvg!}AAVxxs+%#|$$VnZ4Q`3jIgjE&d+x2!BE zM-P4J58TDKU^nm@RAYsrfX^}VdiZ-54urghSP`O<*?{*~0(^wwOp}41fHNR512f2o zZ!B^v7%c}@z`zVbpF&$BR6sc#^1UVjGw(VAq@ep@nUSEj1-)0yB94N-mt&LaeywU? zkPD-xZO+d6amL4{w+D>W^kvz^j%&?p6`k@38sZ8WpT64D!5lF7QxY5nyq+8zmMk2e z6Y7qksh#RjHmhFU)|H?rL*pX|86Dp>lpR%fT@(6>y>Tj^E7q93258%w*U88rHY|k& zbPvBevy-GHROsAk@>jr%wy+oNVK34_wFFh*g>B1Zr`4;w{1xyQZDB8bRT;x0+x_(r zXj>q(J;aiRx3#I`+ti)g)T?{F@ZEFswt97+7fk9SWyCiy291Q&)UHmCj*Sjy{e|#d zaCP{GNYAS55?_wMBxFe4nHlGA!J@*&+Z`y|wn*`I`(@i5A=vIXN%gLrP8|n@D2*EbL6@P0)`Bk+BlEkTF23UjOqLJq9wnW9s=QU$hKij-K`s- zx`6HqBduLEix3<#7@Lx#O$^o@B^zZ3 zJ;U2zdVDG*vf%F2Pg}QZ6`n`J=s>P9wA@fTO6!MfWsSsOXbJnkFZmE=4A3G@^=y(o zD(G!tFWQ65U}gcLTre^S1v=WJUPtx>9?_7=Re;8vz!IY3xiU;zth`nXuql{Vwd2(! z{ug)`0E{F7!~HPosJMiwPnMAjL^8^lWYg)KiTLzdDC;NwuD5Gqw;Kg46|&{SP$Sf9E*GgS|6CNotpYOT_)?zlSUwYpH%DwP-`Aeq3vJZl6932j%X4G<<6#fy^2}Wc#X;zk4?kx%}8e_gz4U<{&)Zjh2rw&BN7Pc(CGbnl!GY5X0 zz!`@#lVJGK(+Ob`5WoXTK34KDc$m1(Q8X$EH`RuTA&LNzyl&I=H8P?C$X)RkQ7R-k zEUcx7aGAN1Y1;GZtXAMvNNG29czSnsct@(YTYBp*^3lX?d&pZ4mH}< zNXMgX!PD{7Ph!+G3teP>X$Gbc2@0Sq$UW(FkBhF~R5SLI*DegYbgf*_%K0UAX30i= zG3+bBAj`<|N3xhl*!Qk%(`N1rdFufS&Fri=vu36vG?rnxc0o`YbBL|Px&XI_l36y@ zQq3~*ZhHy~-V9;CY2@_~1V$sO4u%p{%PcMq3(u^NwDopKe9JBJ5Amak>+|-K&@O?3UdFX)TPs_}9Os3Oyy?i=q_@4vMEN-}CG#!S*cq3aQz#X*@GN<5{E$)B80n7R;2iIenXExj~Wt6~MNG zznC0lYV|&ETN-Cur70^op`~xOo7JI00lb41^mZ8H4clAU=pyF25o>Z(n`Pd} znes4%D`4dW!>wB{*GlN>(MXEMOjWBcsb$|PI{&Omcwa0S`wNDgKOZR;N6U6~iS!4u ziClh(yUab{I+_8uZWrnP2XK-nTexV>1s8S{Qq>HyTMD{CmF!yDW$Bn0ogb|Vwu5V2 z0C&*b0&GUo?b%cecAiuCD1|2YPiDcZHSn zIf}t zxXl(Vkp3E6YH>x#IHU0}xDx|fin$&{8PSWn9rQv4Au1LbG?+}hK6=GQ5KrQsgzO}4 zf2r72OTD^BTsR!vlXVOQn`BEfwV&&WLYeVEkhuN0HKB?^g(~mYo|IFiqjK3>ck}33_m?OG&Q_K zY){OP&Fo`)3N5*xv2+QpHmT0q6V;2?fGKkkz0hZ*)Xkx%DcV*6Iuqu=^19qMBxfc! zknCw3d8u*?bB6!TZcq_6y74BRv4kE#-qy+nvfNpRYZ@INk(zm4*D6Bulj^3NX+ekD zsSZz!Zl6v#E-z@_b3s7av; zW*gWPV3?*QTBNug$lV`AQj~Zrj9Wx zJBd*qh)B7)x(z*(<7FAv)pV01T|=6n4D)4f*8sa61N69Mat?qGTbwr{Exja3Rfoj^ zjN94_LJ$V^C;)NSqR3|RNz3jY=v8TJ^E1DPa z&Z0f^iVjtz*eOwDXI=Cn^iMI`VF8sKU(#1j03Bouq0tnrd=$+)T;cS3vk>zc-{uaN zPMq)BQ+6JY@KJLb*)=rgj|1hgPmFMA&$7iRMw~CYp^qwz(s-+xJBCz9o)Y?anBF^_ z0hLt1E@g9F=Kg%X2#Qg0xtPq0dGDMiq_ajzAF?2X*6}aY*>q;?D!S-9nx%`rf;)35 zx4V>mL!rlOL+>l$Q0TD0P0Kg1ira}x+9LO!Y~+t%Ixuty7}K`VF-;1#teVMVt;o2W znJM1X$Nr9)js)8q3dp)Q9pj}QZfcnKraOZjtB9QQ$o|Ro&cy!a_gVHncYycC3TbCA zC3aLXC={J94bf;+6d0}t*4^%u@ry~d$8J$zkM0aw4uzM>ymh<-i`X>-r%~ZEC118O z+-JwezTOSpJmT{v991Xz<%~PPccve(^d%(M9XmX6JQfXC|wR8F^f5Rg&<~f^Ub-$`aY^qa^b!N;|aK=Js@ZTA6|w zq^x_&K?Dd<{JG0FOSCCmOJj zd58^K738#4A^U6bw@xIh;0*!Re5+ExwR&TwW&_YdAWqK0a zzg8kHkg1?jZKftdcwycwA@2IMjxeC6*;=&>M0HZAV1n8R%^M-sKnT`7Yr=$N0j0x> z3rHYt^<%?fn`<-Xex1le8@X&ktU{GenxW!0E_-FogU3*2bfz_+Y=Dw9L8K~j0Nt&O zk-UQIY)H~8YKpM1V^o=**q+@zG&QPZrWKgfU6UCZ9Z@bCnuh0#T9w_I?2d_DSp_hr zhQ_nkDihn4q48^#t1{ywt;*=0$*Ixl>Gf+TrWBkcWkvy*86O_oHIf;>QrQLt$0xw5 zGhoh8Yj#3Gima~8=rq>0b98EW2RsgK%Zz2R*FrmO&t%7;R%JWXI;2bvO=UB~yT*p5 zl*wIFlM~aUz`zL9JDwTeJ_Te(caDx{Q$QAA%IG!lpiJ)=8XH4e>(>tL0s*Fg(v&hh zF?sD&=E@yeWyi$W$S44}jRJ3Y*#V^mat)6SWp=hIBSSlft{f$$CZL+B^=lE2GN|m{ zF^VvxI0XL%wwb>;mu~E;xY8$m=kfMtg^%Fe`>G z3C@lfHLnFzYFL15jMa)j4tgQ2t6#J2YJZL~mIcHVGLUnK4cW|VSoC?b`$GXR+kmM8 zDibXDYv>al6`@Sz zJ$ZpoL|OBiMY%}PYRRo&d7}bd-Rg(2!?GP#a1B|cj?M=6H=rB98J9(?MmOfvVxyBH z9vf7M1Q{8o-F}5v&XkywtLKd%j27t6-Rd|-%CQQr`+`2J(Cm|{Cc!_?wsPE7Wz#0S zpl8l%>37K?N{Z7P;JBXmp7sJfV)7}vUmoE}T61bnx0B5t^yZcpPko}cp)#?o_^P>u zOEp-jn&80gC0EEiy)XtS5iv!XKpkAaGp1S0^(z^?>H+i!ePA}o2#5lC@hZ%utL0$H zLH{s%DC1^XkK&DP&Ga!=6*#*Rd+snr%-zsW+MONrWHWg!8CBm(?p9~*Mdoo8ml7|xd74KWlJMJ+YXMufIlr|;l!+ih~3bPHgi+-6VYZ;$IOV#Xr*<47r zD4RXaMz)L=RLyoP(u-C|y@8xW)X0YxYJSjOfddPwLCfQ+vN*y-jx1Up*POJn!HrdO z+C><~atdCi=%^ZZO=8N5YP-(S19|BWtX%e@(x@2Nm0a0aTi43ke$6Oi&e5ene2821 z2nxVGiBt)yL8yV%iHEJpTqSZ;844RySigZ`dqzjKRcoj_Q^7==*cqu!V5H%4E`%Q* z-OSaDVu9h4T4X6Gsd>{veK|cLdYB0vT}Y*}mq$>ucT^Bm=Rp_V_)4)OCuA8H2a{WF z6Wzxw#_;$GM`N){u~c)ba{b;GPyeEo(_02b8cFhRi%3L7O*(3jRK;G#Xo|JRfD^6uG))g#LlA{ z1)y$}?IgavzL~+A_X@hYSizG(#PUg{e8{~*a`UvjbDjsP2yya$OH5VFN|I4-B}G~y z-MPPp>se{kvO75-G7aEAt(@lp+G6w}GGsCVBX0*sOab1hAG{K;g$LGTG*+-jhx#cg z?)63~Ak6hX!}QstGsX%E!KIsItwjvM_6C}6fYpXoSJI{W{6lOCPd1keh$!}WHu%D? z6^;T~xI-N~ivs=b*(irce`3!S`;b?3+JCSSJ%#`*K;XBZg?`ddLM$*xT2E7~3N#m< z%2p(+35p9P1QsB&en*`%ixNp1s7!KM+|=B=b(&@|W-WQT-amXtLLV9UR>`Tx`F zf2Y^~POtx+UjOs2|FLm@1y%qf*ZO19NWnW(Xe~vUtA8rCY>#x3eb{GZs6|s-Cg+k_C2HCYPxURp*Gu;#Dl@GxMgo zkJquAKB7)ka_OH6Q}QI8nlxwiMVkrN+{$2NBIE^*?DRs0!ClUo)g>={+6E52KygCt z0G+!s=xK!|d_l$p(u_34gco2H@qj+=^Tf^5NOdX6^2biD4(GllbjJLJFnQJ%H1Kq# z6s;rK;%3_g>63z?q*&q_o3T~2pwyL^pJgRlou+OHSp{Y}d6w11v#C6J;7nFiDRi1R zM!__=U%^}jy@5`gJOWZdxANffb--Cv1_qQR@jd@?0<-vHu4qOtn&mkhW?3zUP=!&pL&#=EQhaTN_B9pD`7#$+!Cvno3oRg2#j<}s zj#{ye@tR8`FD#GPFxKV+XrYew26EeM(LyOFC4?12&nuX^Rc;28S%kQSFYKFi$9|L+ zI1Z1oIO~|ePPq$Q3br{|+OkK6A%)x@ixub6 zYza|kxo4;mP{DViO%;U1*ftZ}2f{y^s3r+Cwsqz8!Z4gFX=Q8#?s#&b24QJAya4Ar zr_3x>(8+T>`B%$SynNTT=z!RaeZ09oO=n&Immd!0u>wT>>OW8`@kbfVEX3wsWzi?lGS3;qtk&=)L+8%xE4YJhp_>Q<9UbV zo#rQ+-|JgmcEofxCXp+dG)uM0=E7`I+eeqpu%l?WNX1NBs)lqwMv1OS(hlj{yTn~N zXUb`8FJsuAQiZIsw`)a9m)0UjOybROB}#{BXx`MTc{(0}hbDgZ3JAzyEY~$kpU0xi`UogkySzU0n4U7wlamGSj$U;DMB$X7#o ztg=_M5gbjxBh|5eQkbF%;W2K7nBKlSd?^}h_Y%b6>p2jFARKClRF&w^2BfZnMdhv% z(7K|V3gUFTZs+IO;aW@G&19t>Dj2)_|q$8QH#m42+KKZjr9g(2Is0&hC+2@&23IllO<^KjfV zlz6Jq_+4=?o%*&gNvRkKVcB!lE)q&b(V+2+AM{>6N<6JINInD=vi+TnjHH zRD|O}=(bu?i>cr(my*-h=qQ6-3c8UP`Yz$OlUd+L9kVm0orM764Y9)pXR%IDDAOVG3ds}xpE$SNkp|CSth~m8|XYA1QY!Jo?e5qE- zoGZh8M`J5JHU%;;hrn}Kp7E8sId5>DBT1-$ml%%ZP!Nc|aRCJ!1Wh)|)$&tY?&fG{ zWfK1JAd=?w(F#U)PveMF=Hy7En_~AITcWC{>yOX7azmu}q?vIHp#gwsiqrEZ1ZK60 z!ZwuS2LNrQXwC^lU1#jJ5`If5=QRxJC21aV^@);XVT7hsO;7}hSlApz8a2dIA?~O% z!O4@3LO!^}mzIMDvMIbx)aRXr&5hnwF_C&^R!A#b3fMJit+J9vuII>!j4FlLQs$J8 zh^>fc%*wSQsxVMG`V8N8Xu1Ck~nf1_6h%TbhNQgu$EGluL75Xb6F_6cLx!m9e2SOIw2ysrp zY+9s9>LAyrCvTA@VdDwcCes;LY z@r|M#KHyd z>Y2ejf`uW^~|)Ma0<<>;W=+Y$Ylg$X085?3g==oCFQIbY@V}br!Q>kXSV|WzN`-gUu3+B*~XdrDfS% zj)(v=SnRc$`^=+Ca3cE+=U}&R!j|sXVSU%FkncmqprmS$%Q(fuzAHc0cuh%1foTx1 zL|6o822OUNtR!n?5|#siywJ!5Rxlds-dgB9G>9iLx32P>rpTBfj0e!RCRyL3TU?zZ zM%)Fm^78p9_O%0?gOcf%4w0$3ge{KW6w}FxdSTZE-L+KRR6@6!(c%Kw)UTE-ThOcNp*g`L&CR=EI(Bq7 z>h&b)y-o)2alj-L69^3zYs1JrPtsFjG7Od2PwurL@tHr--YRGD0z`362x5$kQjkii z{W@ErfQ4c~;L4MF!c9@=acl?&g^LQDOA4?**tm7SsJn^Wlz3YO`gTG6yUOr8B;0IO zB5t+u2sn+~9e{c+M@g`3j=X9jFha(ahVnru#Iiw#w8rv5NW?KB4yOUVBgm#)5@O*v zLg*ZdKabg*6g(5P%ErnKC$QuxwfZ@d23`mkYGNlcm^@vjw6M3-e$*MC+cW zv8zh~F;2;vb0QYrG^w(_XeT|f3Cp4hr#S*@yBLq-ljMRJ98^rNJK&Z?TH*2mr9Gf7 zTA!P#Xm{(}e7WcYgNxcG(-MdW?SK7dLacvpFgBak@v|CE;TAu-UUWDQm`uBzGwNDs zQqNDH@Y%LkI`$C~l#&Z0WVU=afk;|LUVT$)m)ixsc|Td3$>YH}4Pqm3*v&W9^qSsW zr-HHi=dNB)?+mYv9h^#8cw>9AEl^q9TP4BFTS8>Vp~afRdH+_)1B9ScbpC|C}NYc4#js;Ba;2 z-FFZgXW*7yXJGQB+xRIwsSQ(xXUr$t9T{6GY$kT$Q-Pf~7!c}hA-s``Op)~7fJH&F z(#lE7tAnk$rfzH1=vBf%!x|>lV!+D}K?W#k#qBA0OOmH64HZ~=4nlh>Qs~yCle$Xh zB=;mSY21@?j#DB!51O1U;V!MczP$kW8J+%IWvQPaY{u`@vb!5|{>vL(vIV*^htW%U zImB%hi@g|vb7X-SmI}io?j`T;B;SHd zQlyrXaBqHBLlzUr0zMusrH6&M)1kloi~1f{7~~1bfeODf^m{*gV}k4TaN5OE@KOFw zGzY8z{T$!D!5097T6zM#i?~7CVktnzh<}GA9^~_9cm@CA+zLzcjKUHG@L-$!a2=6M z<`?+sbE%>8Z10HX9^AlFqEbDE9pYrkrcJ)JbQw8XLEEmk6pn%inK{!ku1PGA942vh zAR^TuiCb326S=o(h`V}x`?v%l=&mzxt>WG;nMRQEI#aAqCGD)WxwZT0M)(0b3h)H4z};>yuIYNt6p~A=Knq?6u$+4dm<0Hz%WKFz0?y3^ z;)Mj#^#k(N1uua61%8Py4{%$CAhJh4p4e>cs&xnQOL3C`9)+WK+0vaH>OOrb>gYE+ z9gE{TwPTbeq(*upaz!($b=Jt5_W3$qE}kGE68sY1xlsRI3fzr~R}^sb_kBmeF>VQ6 z=mjTt^}x!U(|C6WBK_yw@L{RB|FBry}02PA-wRF>h*;?S%^F7RV>J z>E6UjQOi4f9+_Errn3v~9&REKIqL5J1j z29|zy)DR6H9$VRC9Y&AFN05Wf{I*kdL5I}8E$7&XPR?*aWIBVl)|z;T1`QBiH{1N% z5bQi$Nvtko)4i*BD$2+k=uOCl9MtESf-F;clZ6GdYij8lc z)8k~bG=5%$K=EiG1$pXt;oK^6$_}mZF|uD8M!g5qgrTwDJBB_#XVFgIQ}BFWB;a4T ziy?PDR_SZ{kdeud2P=%HO}h^%8j3FGMQb0`M)Yt>?B7$ehRE%;FdH_ z3~5{qf8+1hR&R6kj4=N9ZRGwWf(5^CyWCkiCG>uYvw-#9lX{XV!&8yebH<0byxu8G z33HBC@{#kOplO<=CzRcOuK+LsS;oqx`UwrbqNO2iTXjEYxs#c~4Z$!)W zT#C!4RTjAjkKa&X&dwU-E+;+)?#i^X>v+fth2_n4W5m5cFZL5}sSZo~)2|}qBn?l{ zCG#P>+~}~B^lXxIZa5S-$hEpR2=H}#&*xcjeJxM-f_oMf94YW*kQ`k(iwN?fdkWQ+ z1SdS;N^!D_Sg0tq?zR`VuX>tYz85^Q?Ss}Fqjln2PYhF#X9IA#AT^oXSYgU!=V0^w zN;2=XBeX2;=vdwwVcq%_Hwbza?6LJ1s)j`oR$j%vqdlVivOtW+eGrbh1D`+eJel}x z2yc|A+WDHzZ^CvDiqJbY1DMH4TZ)4^kawzH*K(C zMwhuTF_To=f~ws=TOH8`Xv;{0k=O#E%`5ev=e>O;Zh{+F>cK12!cv{5rio>}uUH$? zjIH?TzT`^ncE%eCPVaU;z1#V@?{+?^6m8fZ=NI;-X2%uOm9~A3pCps=y8jb0xuX9% zoyKj{hp|ps8rS~~rROt+OUAKNwO33UH2=qc&FPeX_ULpn{Fg8peu_TLa{9X}r*}gA zZ`}!1*IxG1AK-YQe}IF|=0|qMe%rp@nS8;h;x2T4kL&5l{EAQJHGGd2;ar_Vjnx}IPHkhxC>r(>H8*;u$;qmN{ym==%G==1J-Tux&!8wgQRmE0V8kMqu$r@>7rPPm zs=`T5EQiZFLDO&O2R4n0>CMgTl2_@(-{!zs5#CL&+P39h0^KiuSO{M$+3d9NjU&87 zU}R@1u43=q(o(hT;!2`aE2!>eT&;vw5<28aqk(flyr@A;4N#@KdzLWCI#&kY=~>`} z(NEcj?dLO=+?iGZ)GcZpL*n zxE=$zR;UC#O?mZlN2IT7#e`$}7EDOfipwBGkuBltH*B#4)hu;! z{~hsiq)e`<5icbZrU*59g>H>sp({@p_WHiZc$L01TCJK@55Wppp65zAS3?85E&O*q zkQW3~aj*oM{@c-|0#x&}M!Aq=`-y7(N{yYm=k!~>+Gy}RhJO)&^v=MIgwE@LkEwZi zjre+8^R6;h;@)f{zH8-|?3A<$SI!~~U@5u`;3adC>!0FyAHM$i0-Q#XE*-%Lc0sjP zEk-djWI+I)>kk>ANw3xrgL_-hMT_jku-tx!$^6Ttq+{dNO^f(-5$x7&X@13)&}=YG z(n}S($YLVL^y@d!D*Qrss8HvtGbtr2tfe8g=Ca3E%m^c`WVYlkb!*f|pdri~75NLB zZdWqtV9K*JQ$i6k0M1r{P$9RxUUtJKan2Xf+lD}Bwkqd*dV|gYnTo=j;kjH84UJ7N zC--QS6N)`ebFq@07@6o-$PyUureIUhjVmVkoTjpSntW2!mTmYZ9=?j#5~KAJx~h=0 z7VlMa*)+=HbUNt!l0d_~*x11phgrAV(6#F4*mI^xB~s^cKbqoiw^M9HY&kxPqvv8t zM0l?)d5E22YhsQ1ycm?YuV>S&p%)7$vyJCD7)yOA4sllpnJ?Qk4L7F?85w6su9LRo z-v|xf=HcD06OzMOk*}5@<5>Sma>$CTjLC~T1?sbJf8K#ipNPOg8G~DP#qlGn=#oOWDryiytlDUSWSqL^y)QE{vooH!rR(9yE+oe;;B$T z4Nv|6`IQ#(m*l6tT`6IfaiFa`-PzsU+t=BXN}uk9|7(8u?t;qF=C)KR1=4v!D$MQr2B!g13Fz^LVTa^-Ma~6N=M6yb4rf696MwykGu+uHHWGu*Ll+qP}nvt!$~ect5cB#KoPIsk~?oO)y^($F< zIogeqz3-+28L)i2hALE}zwmIDo%jlA7ga82j`vR=Sxc`C{HrDW{vk#(0CdH5T@ z(R`cqdf8`fi_)v~ORmKV>rm~`r1AW6Ycsc%)!8JLb}OKRkp~(UmcPd+>%m5;-_+^9 zxjSo^F;H3T?%xDSXZRw<#*$WF>-l;_eQ1gq${~~#wb>K8?WC2LX;`?}5W6i(yEYaZ zt5pB%DynLwfs_*i2G^X(<4rv7-k}_N^~t z6M0n6khMi6ix&GFy*+qKnJn|zJhQKH(n}&M)uUPn;Lf#;OKEYlRqJ_bhY>57yLEl* zFub?$Bx0?S_H57SeS{lMYRA2F7Z2Wi9YX?z!swIlM-@^mkC6TEPO_!z0m=en*tN-@ zQ1Ih8ydpXOq`ORH_Q)++*5VmtyD?F4H98BKp8MCM*{?RmDShh&I`6Q4?YAv5yl_mE zUZo+}d1&wWV<0t73$JhVIO-ey#|X><_Lns%6Tk*Gdc+{n2?KKelEjx?DCd=zjo;BF zr58`Hst+%vt=Z-=+8E)ov2v5(RYNU3e~qWzcp@Q+WH2x0`c?$5v1GwMS^77@+4!V!2l^TP+98!La?yk3F!bna)VuAAc)IM8m%Z&|PFliCUzI;E3$ZWTc)Q zC_h$jmiW~M+H|Jv=+B7iToup-mZl{O<+t4?0RSyfaTu0~H2k#(RKtPQxOZVy*o zo>*1Q7E^q7j=^Bu5NRU~IE8Ie2n3OC4?Et!_6y&~d_ixc!+Z+3F&1r$T&0RMpK-?y z75Yq(u`o)9${h`Hxm|K^uqr<1OOcB)YeauJ_Svi#Lx#N$s8cSBr*B_`&Qm64Dqn+e z%+G@9uqa&nuR_$L9brHMFNCJITkh1xg0VRwqCo7i{G@$PYOuC^^ z?aUTh+0gF`i3`(!e3=cB*nFN@HPEYCvP`bYePO@dtf54_JPen{auOsb2Q7xN;u5w) z^Y@mx@*{E9gWpvp1$f3-Y()5*y0l91#s!s$N+rp8LKRqDCw5i^RmlZNm(1@99eOV0 zm;O-7zhGtaj(VLRl93QzvvE6LBos$yq4qkT28d*MJY%3QAcQ#JBN;m5Mw%>=Q033v z-|#86FOjEJRA@hA8p@}4j z78WcA+XrNLer6}$Z=s@ph2$A;xzoB3ceW(<_2!6p1|PBueheA^wTwZZJQ#mKn+^59DU zCm5C0=g|^X>ZqmAH>(>6eLMyj>c_SS%Y;9HTY@U=n5p|Ogt8ss|Q>d`B>yhPI(>yb}I8is&%@ardL3WQHvQ?&FtOzhpf2JmTMD1lJ85Miu9dHNT zG%`7L`ij}2Ag%2_T5s{s&!IYDroUU%vAIBmjB2q| zS~r|WXYm*ue;)gZsb@XL4%`%_RrKY*Zf)^AC>R>kB<%~r>E^9en9=DKAuC&k=<0pf zqxP2Rc7ej|_Fy>e?ISF8(XVY}y2txorD(rRvZ;dEow~Ktmaaa#U{JLzM6&HT;k!G` zptyo_k}1PAt1T?Z%2OwgPOOf+X{0Ac_}!&C*yzSv(`Y9ujA+Aicw%B1r_UU^CF6pq7RmZS zi%d9TKh}1cw_-NMs6G#*^5%nnDI%2h`3D*>y1ZR0)iA`K7slly`m(} zuLi9*sR0NSme=zt+5h2G^~_sC@a$2yU)lmvzRFulBqPmH81t7*2b=YUPr={*TDgm8 zsy*y)9;}#CM>(c}CKtA|8)zXBmM#G9M5y#Eqst;5Hc6>Is3(Am@Y7Qf!wPJ=V`~Su zprl2>%gqEhMm$GmY1Y3}pL?&T!K*#JVGB{)LUIb<+PqC*Rwg>} zh=}-Cl!YWZbhz6 zdWik8DJS7(g0st=4x@OTPut-=-PKmog&s+?`M~PQ19rEM`O6U=D1~`i&wg5wx|0Yg z&wqZIai~XJgH`B5zH3dyMu;2i<`!(f zK{FL57({xy6%;?v$uJ2<4!l}4kqhAh>^p*gZeXzZQ`Cq#(aMjC5c)n+cxG&TMZC){ zut)n+oau)@(^7|G&4#Yvt)3GWE7u2>MEwz|v3{v6Gx|HaByST?%HW#}zyQmmAxCGg z{uA1FYbMjetS7e7ef-llNRI}yOPKX!M^ItBb8l-Xe9OSdWn^0W#Ow55+3g#$!6ASU z(Zn-KYe?(Xl;hiVeQ{hxLTBnpCyv zPyz>aI0@nE#s%@A*I}<_;c|KCM3)bO3zG6h+!|D}an2|c9ugE{EoEqdBQ`nAsHg=e za5FN5zMN@uB8QlAaZCW)ENt1{2Ow8=K^>dV^9c_~|F2tdW`xy~bm~*=Y(fe1tj>g3 zS?_>89y__@eSn&UAeyvBT$f9_b-IGJ6*HwGl4|Q5ObPTFZ2pw!Dv(Pz0bus7Ki7JO zbjg(QI~^Hk*F|c>+kaV-?vSt47DuF{)|J!`RB3&0g=)_-S(k`zWa_J}re||@=GBxygv}Pe%6)*F&d)B!Mzy|-Aaz6GZ~);#yr$il9i%>gyOaS1DgO9gly!Z1vyHlsdd14C53GAn8r%zuPpRdm9Gja}$!<}j=)3VYNf)#E~E)CTBEWskX7OXYb}-5wAlZT+IN&1H;w z!kS9H{Ui&uuivNp1-5DmrC;XZ0lLdgM=UU3P zD*`p;&?r#Jn8r2^h|Z7XwuY{UTaHIOqu z@HrU8M~^1oTQxS3WLO&6<4K^wNM%wXN|eBmn0{9!nU@BemGH2mb1$eEB`56%sMF**_ zr>(B1o%{sS23B@ST;$R;DeZYYIF!HaP!F8iGMaQ04s~qE1cThl2XU$n?*UQTb%)ib zv1(Q5IvRVHjFO|fD`C<|YIy+@|7FyyucAYL2jH?@h}%3f++>^K&~@WiZ3bue=$|45MY7Yx!#t(w_;K4eM1>0*09XC&Rv#Rs~q5 zn!T1go(cV)6{$Kq^nX0R(FYxu3WVLW=YaFOu=T{r1O9wcJiFS03OG0NTr$B5A|;daHrhc`;kM8|IwE!&!FK-lW*c+^VK(%u? zc+zNf7}5r=hLzS4+TcOvNZH+252&C8+&`Cs?RX-?U`g`SRUtu8JzkyRH89e-K?bmF z1nasB!UHuWYSCS-sLA(%h1O#c@Lv*`7?zE z{1!9zo(;TuFt>bcftRZ$6m$N}YSB|O^P#skV{MhqItg(&TSG}Ln;|~N%~9qz>O55y z>ZJ&ua&RcKm+mRG(m{y(G|h%uyR%Ll+j!1oY1N5cIG1gpN4yL@MSx#6)@HruG)|Q$ z^Lr^KrQU<@2d5_GT_>j@Y_e+|$;_AWBPP}CKROrXtb(sC455C$>MelwZGX0aR#uno z7Qka_%Tu`(gI0TVeozML8e5EaoRVd?dy1#u)o%sHOYD|qki_i#!dx;=-nL#$T7ZTS zznHF3Z5`=e+lA;2q};W4a}C=iX1_4j+YT)Wg-l)QAN(^d+%HC0`mskFI_LUaz77OU zMe2Bm=D>=>YvUo?f#LJ~GS;weI_JQMR%D#6C!i)j`1%ihuKZDe)?fllMG;^4Rc(B( zlB$UF%tFuT^#}FiI(-^$C(1QuNzw^Z+$wvM67Ht+lKd@=W;?Y`Oj8x01?{V72SHca zN={&4GbtgL?R)>ib&NYWn@FKR8o_}c-xQjUG+17^Z^i;Yr1z~rG!Ke&NHOwi!Vi)B zQp)DW*qMnK)X0bVBmIs| zlu}9|HUsyKlZaE-J!Md{YZFfcLYEJFM;yrrwXvp zC|BBgF!FDk5U-CU9%V>S2yPrh#p?WOSp7qj(N1i}EVVR4Fu85_M zMaag>0bit4-(4gJZbY)V$wj(zbL%czeOBBxi*Kk9j9}}a*EsPQE-}Vk(o9yMfy7dB zK{+ldUSC?_+2n6FkML4sZd3X8fT&fE_^Qe3nWfT^@hp!-Dho#}Lgr?vfRi$t{QsCu zUDCr=H>TQIh5sI{nlMwkd0mH|acDu@lV+~7qOXe{HY)EC&KgTC-OYV}zPGpgzY7C9 zUl4n+AG?<_3%f+}ncdm`1DS~R972QUMA)Mqk|af~=J1P}*5A}TYqPLQgeQroXqX&z zPKN(xf@yv^kDNQmr8$uYZWR{$(hP}HY1Z+5rB z#=lfrH;|STL^pE;H6ozD%2BU96&=EJ74GqEzMLA$uY3!o@0s5TJQQ58?c~&g-TaU z7OU5lqi{M`JD+PvT4-^8T1~=S3qZ0r*D4uyJIv1|<6FOObC+#nL8~gu_NLn*N0>(2 z=%Vh&q03ys)}hy%Z$2r54q=lIAC{i)-KD4-6xXb0@X14@#)|JONd3^&O=Il8cq-y-P|ItIRHa5nk>CHrPcEDg zb!wz>+yDYxqV9wGWQhxYXE=iVGit#rjBQ+Tt&T*5TX(>YaVUz_LlaZeyx}jkahLN>s*fTdj5}`x+U9xuWT|Tb_Rea~g~*1p7lAXc`C1aH z0*j&x^>!G#mD1`EDUEHqez%yhwDK@XolDq)$oV4G!}$0(`tcp+#INBFYiMR(uR4kS zZi7hMz3806Rzm|3R;R219Pi$XftE4k!BN+qCbm~bG|oCd)(I-rcJA-q&~Sw&6zH;V znOHqiP!$|rMaF+wmYo)pOu_04i$i#25S0T2tf4@!b0H148$+B2L_O!;mWY(7b=K=3 z$pJ{&hU2oY1(^oB#aAYtdp$Yq*y6>cdrn6=W{6rMip!81%QQj?A)0k&iV0c9qrs0G-er_P zq40zmiI9BOK|f*h&CbQX({``M%7I=>VIjvtXQJkhEw5uD`VYmJW=uA?d9-aE89Sw0 zzw&>wK&UN;-A|E5+cEk#Gcpv&estoyG(Du|_-!6KXzcdlYVsI{ODOszCuX>6RBPf~ zC6|E)+;EhD`PF=cII0Z%oI@ihP}aSPly>szGdawP9!bhsFk!AeCZ~xgWXlZI7r$z% zk_O0&E%?i5cRRZu{lSq#_%MPn`y9FXdOwD)U8r_uj_P1PgveK0K?S`91M5TRjr+n8 z@0i&TtX-l4CD~Jq{EXXGwjKh`Dpg>(mguIh!3bpOQ8dHKX8FUl1A2(FshzllifU^+ z+%=I=L4MoMlv2?U$PIW?K)1j-?ph9FM8Mwun5YFnLlEw7yT%3@3 z_KEIUT_UI)MCt#?G)<9Hb3)D$HlIg#@~+CE`yrjWA!<))2KJGOR!y*N2WHShepmPf81Tkc(Gh;-EehB<*dms<2_C9|Oj!>r89 zP8Y{M(U77h*Wi5!o5%u``ATebj$HDhh2f3}!P1geSCe;*8gUNKV5mQ84qef%mozIT zlN6*5g{MrWGY9m&v{xQ@ml=k)bT7?ErIkA+bCF*g0?b7+cI&&^^Y8UxiA&rzH+HJTQSgw4WFwUdI6}i?> z;G!B7Dg6MR=u7#aoW4MrhAHBS3MCD%@0s)#dwH32LFLIy_FmQiKRC+>Yes=zol<@} zPOU^447yZL7rwC~|2$1l#WtLlma>3uu?zfW_i?}f8gJCl!zZ2co00+lb$Y2iS9yRb zU|S(&aj(?_P|`=2LngvlU?GU5U3DE!S(l4k34WPsDWW;greDH-Y9Sp~`fCPdnU84H za>?0+n*BS!(VYN4JURYCPLX>o>^5&^W&@Zopt!LA9bFmT_Hbw!?Aq!gSIdJ-4d)Ko z@kG?+O_}uG7!QGMa}LX0OWyhWO1C(%bTEUB-LUEAc7I%xJoCC{@QHX7eyS($!qXIN z>>PwLE!rR7&`7<104RFc=IdU9h`C9XPRtLkJU_gq?SNm{A8(Sxd?XqXayr=mq{qn#iev!e4 zY%~(MEBV|-vO_o&?t}+9r1wXpDL>Vb3<;%DAN%C*6F4bvilq`O%ed*<-$tra@C*>u zr}q}xRTi`Zqw?vp^_oP5;zpUPNAIqw4?zfoW=JC{uknuJ3u11Nn&9*aV%wKd2M2iM z;L!bc^uMQWfiQigv(N(SR5_C4rE4Z?Wkey>M3}8HDo>$*PKP~}iZf?-1wg-EpDKd4 zB_+1KT=>OBbY|lXA`^JKuaR7TOc}3V!V%uT7-3L}1`?M=fdT%TLulw`PBTKI>W5%x z!*2QD#`%x@`;86keUI#P`&kztpS|ewH^zcUA4(x}d*~FUUzbQ(`d6 zWC!c#oC0g^aVm&PhWCa@F#r$)r}h?|NIo3T9X32AeHhI~Vg8%)IlBMKO&b>&J3fw? z){C&R)e6zu43@;FP^X*pXZ8#*;r$1w&@hBKfuhoGI{%Te+<2A~|7$Eu8eYdc7L#ON z8&;g99ytv18#h(D0Pp(%V`5aKoMV{uL$h&<(-9}lI+}ZdYvr)~GnaRXfU=@R^57%w zP;LPy%~sfNjcdnoB~oIx#jbisyD%HL z{Rs3m0@_bLL`h2L=WqR zh!?tW{U;zZJa(m%BS3Epv9-USSBd--HH;z6xtVhlzLsedY>GB!A6Bp*OZ$=Hb zbDZ0wEB*p1y3}gtI$AvE8CJ3*PGF|}?%&JSImr|gM_!hF5-p8=d`Pf#crGQmIIj|6 z|3bWj_ik%FU;<=)@avx^ZXKK$hUU?-F9q~CoRDy1kS=Np)VtI>{7#XaOfYmUF&zWt zkTP(LGIEMp=>Br9X1URFG;uik(Kas5@kuu&bgIkk{2CHnNN%SrpQI^T5r<@Hl zuP?PC|Lv#^Ej+$l(ZPMBUkR4I9=G?`A|MMb{%n`TM%3mg?Yr zh#8=mOLc!qY`69W74jW8qRZpZ{OwEYw>pj%-KMkhdAeNlch z=E#+G3vII_xkP~QME&rsWF^#BaJ4<9&gqEN<1DA0Flk5X( zqOX=GO`@d}Ex*MoRZ^h>yv*XL)*I#R63uPdR7iz<2Qikm_NfT6yKwcv!MQ$Ws+c-5XJQ?U4*n5Za;!4-=ECZZn5DwFznGW*mQgEcRuo1KQg9s_;IBMZeV_9i z=i-<37+LQr1-?FINAy>KtUwLNCl4dbamGM4!3|@TE;d5Ys1FUZM(YeT9tAplwe!^? zBG)JuM11v}OzsR!bk*M?uYD8wP{MaBdOFGtLqghuwE64CaoBT&FLHRJdYo{x8j1vl zOV%ofpPp{l4U^SNXqlFe<8m;*fVT=Mg{kz4Pjq*jRd7k=$JLAOfAH#aK1>bHf^SN> z#8nhq+5sb|Gt|XGS?klJjH+Vjrz7%IC8o>?(@C!S=z?yr$#Zj?jr^4tCjeJ+wya+= zzzc`60LoT~J0acr=;0hVNa|fCvvD!5f5&PAtA+|}o6pIGp)RlC!z^?)1Pt26MEnrd z_E8lgUOy=}T_^SKE^#igwMp}u_7EWh!(Xd;MvuL^GG!k4x7qsO0^E=LSkCo?edW*Xj%hay3>3!{( zZDrF*Sg^V|mU^d6%DP=211i3-yAM;TzpFKN(sm7L3H3>M>jIGOPIbN?bJjZ)hgti8fr|Lyen zc_>Z^wKkoZ&q4`sQNyJ?iEg}OYc7G#ytHPt)@~Ricen=*5v=sWY`uNrp{v(0(%5*5 zPEeS?TqeY)Ig3tUM}vze7NnKNIYcO7NM9qD20avk3h5+iO`nAEa3M{xZg@e6VxD3} zHaT~rq_K(arX!Io%(?oiTi(D~Z*c`7lV@09!HR1;tncZ@Q5FnW07OOt{$Pe5;Rfm`0*6x5{D?O-0Ie_GbeLTD;=57vhm zp&juw8qhtqBxdcX4yxKuE=;tQim~DvTDq-$a+FHusxuBJGKj;n5#L%fM(%26dKV$` z|5T-os`US5##iTyYPk_j1+9kyL(01Nijo(D~`@%tU zXFJq&vM_bJwPYc*T<}f*$@i8E(lCM}RMO0Qz$%Ugp*VhGHh1kCW!u{E zgU$TJoaIk^cfmSp|MAnKCwq68Lc8`3@^?u^h|5K=DgK^w)K=L1*<#P)L3;A!xF`20 z_W7UPS8>V=@v9hk*{M#N8azAkuCWRR$}SHa^vo@2NhYCA9S#3#&nCO6znCVaLqUCq zmxRe~h>Ph`-T&Ik*Zjk|+>_ID9}D~f_hd9tHQQawkMzQbFWe$ymJq*XvH3|Gi&PCv z#Ebk4(+2l;_N>Fe)~RR=D+~U39cu@|f1{Q>Jp+U&3f}#mspJ7cK_Bl+#x$p4mVyX` zzEQw1^j+0)7Igj?@qzxaz~H9XpCi()`t<8;kfh3!$1F$wRp>>QixHbISyoG;= zMNIJ3kT6sLP}osOKir4k2hPT#?-SyL0OLxZgZ%ZUW7~1-sUHzin2XxB4saz7)yjB5 z;S?6ojE;4Ew;@TOBUX@4o5mqlF2VT!1X+c~4o)!P70T=r?G^%y^+*&w$C2{?C>j|smFpG)50QBAhpi;>t3xPKVw|u( zKAnxXLt=>`R2~)ywVj4_T~zF0wi)HUlcsNZW^z>VE|Db!BKn>_*IuZCzGfb7TK5fj z8yRynV9m8H)nzsb5-1{8mKWG}!C6Hij{_HI#nO_@@2DAm;-LW7zRJz12%ignm6%E?NJ;;DF9E4^M6N|Bbx?;wTSxk6t3+svZ(bC*+D&tVPxlFtAx^s38AUq|LVncs9?B_POMpdpS0s#c%o??X#tqc`me| z6l-F%WlHi-0Rhpl^XEH`kFD3*bfH8I!U(?M(zsLvE&nyvSw>K;(!&SV$x4(((*z_My4MG)IrpNm9rIIXYD1Py>K76usw!(zk@0XlXAB_oW1@~bvSwVH`(jy$Xs$niD@0}OsJlg=$u zt%(a5y%Liz-J_I-a|?$L?Jr^MVG2ruXX7d+h4A%Y)hB1Fb;a9bCx|~ zdeu68K$#*83MnrGdj}V9QJX4OD#a<%t}M}>-$=o6(d5^e-DfA;eQEsMpSN;v(##U9 z_8;D4o7Y=zVR@(-bgtBldUubPYj9snKnqn>+kJl;q5fr?)}}3 zO;u~{48ug;E*{;d%9>M?8qg9_6s(2aSf9+V9%Ltd3cqHk$?}o;2NRbe+g40FXZ0tS zhrhgf)va6?_o7K{hD&2FyedFbg_1wB#9J)Um0YZoZl9tiW|*PWZ0CL5{xGjikwp=8 zIkRVG;>b4Zi}$kF2u}5BsjWk%fgOt;PKY>NiLrmj(z85TZLP7^@G(wI8o> zO17A}A%F>f|=Hbo8oY#P%$6CgSgCt`8TXt^B%S1F)ep-*R z3*aaRwOPcbuJ7OE>|0CG2sisSVmPv=j1_Gp z4^hGRKz{7*N>8U=-DRqC*mgS``%S_H79*4^tOmMRDHSzZvtKOW@ zczWZwf>$(F0^GKb;}+?dR-1O-+IBEUhXwaF)1KHn^ViL`+{y+2OvLAmF~*y9YDLJN zVZXong-Qazep0PJiPirgd7CVe(Y%TCF4;}}qeu+D1K#}yLyELD5zxL(`Y?|Z`lASz z6W%leu*c0*5A>JY`poy?S+ZbR5-}HcwsgjU?Bb4#{1O#^apB;H<8lv@5QS)W+oBS_ zFT>|J5e`yyvOc?|o7sVWNZDbS?(#jpY26#o45VeuSJ^*ioXlQ4$qnL_I@<)~o$b|8_SdVvmQ@jbYcz z$By358{=D{DsSY|)z_qL4L5!#57VvR*sP@Q)TkIGoym1Q~&K~Klfs;b&;>DL$}D}glP1-v3R$QE z6nZ7>M}m*C@)uDD%l9i@`vG7=pU^7DqG}V)2}7lYS;=PTOf#^GPNQnseP6o?;PT-y z94*+?Tz*(eIh1yn-Z~r>$CX&xz4-r0_A78Y2!moBpHN4KVqym_NR z9K7-MK+gVr{X83I1_0cjU(YTmDQ9|Odx8;jzraox$q#dOKJxTVVdm$3|L}uQUrgZh z@$o;OKA*APT^_F%jBQ~@o?g%9#$&fm$LC@tb_f?V1ccZbzi&P1M)9M3zW1rRFPEUu&Wlrg$u7?$7fBmEOx<_3CCFJKl z2HN+Knrr1jeSg0n)h}gIj!G!xx1_!{>+xXt+=uR%v+BM<&7>CSqUs`rFU}eB{5{MO zDI48~jn=?9`Y%|zz?RS`)pY*I?C?88_TYy4SBoA|sZf*D>Xe8q;(or0<=GELy>py!2y#`f;7BIK6xuG2Dmtqeo2e)Er0jf&;h?e=y4paT!IFZX@6FugzKXPtTv;4R~FAt+*{S;(@)Ws zCn$1!&^+k(q~LLx^t+Yl%ypQw)@k;7kX!tw=@=H{o%_MO>URB$^#1io=n>-Z2n)Yx zF^zSVZGSj2b={IUG6N#zVR^&Km5DmR6KnKxtcN$~}g*Ce)^V!}|B zzgkt|F_=Vl5U5V?`yY1g3;q9uuC82=y}z8Be)3Sin1KqnC$+bu)gEr@IgW@zP@%mu zj0=N=`rqWMgX2iA6)0v#7UhhQmFqCk6w9ctk+MxU{<#F3y*v3Tw|x5YyL#q>v)~8> z66zqFs=p!Xi^~tG6T}7xNi9QBrI=GmfBgoPTdW@14)N)KWBX#u_UO%plZX|U^G+nm zEMcJDeiAShcym0)O?*7cc)xz6=r?`k!iD<2Dq zA%Xo7)$8)SnbSBhG{$Tn<8XX~{&25*+3hd%oVJn|<9Kwer}0DeDUj#K+4g1gnq8Ip zZxs`t`5gYu-jChNW1D~f%|SDk-D7zR_ekeoS0LQ9xg^J2GvgPq(MY`e@4rFB?VJ$f zxSRIk`4-g!Y!Pm4@Y-CGV6H{j_;mQhAP~en&yV;x#KV70!HQyksusQN8hY5z%4VpV zWR8Cp@yYdcxVqZarnCI#ZqC5hxytm>pl4y{^eEYXn=lCIaT4l(u)9lV@Y_;~dROEFRz11CE3^dnNRBfm_zM1>Zrjw8 zU<`iZ*w7;nkfPO5aA9w!b{vXA z*R)T$twvjMUx^`1h*5>V#0f0&#fU0^FiBlM8|gJ6H0#{qhabo&nYY?VX$ zTtuP}yLle8V}d<0p@hP*7Dn$%)O&7H6gMuOaXs34*b}%%kz;;bjs%K72 zbF8#zzu<1gV|wh)iaM0F;o`-N)CD%ZrW?;|fPGpKu`dD68VfbEUSS0Y)?39aH)(m0 zmcKmbz%XDDb{kq1W3YR(@OwAs6r4}4E2p=$3=t!i zpfKC-qWpE9KJI}2tn0_hypPEGE!i17oilJxU0~FBfptO4yVme}_+~%0QcX16j;|<)rt(zMO6?OVX!(4^EDtI>lLpRuHuD_7OY3G$)sX1e<$#;%=bIf_i-{v$AxN(hg2B z?HkksbSaK~?OMS~T*{(>%-fJBc18xE?i+6e()imUUwW3q_U?ZaNqq9 zBCh=_ZXx9!z|D$`Z>Y$&@bpJ;PaZ7KH%KW%o{?Lj39%Zl-+4nzsJJ6*BCLQ7PbM=8 zg?|9HqNua{zJUhLF5gLW^}B@V&$_8w5y;i9K+yK@pXaB9IXe{8Xn~yLS&mOEL3isX z>TEP$-7!W0egJD);zhp9UThTKIau=LuGj`Sc!g3b@Q7chp zIA>J?c;RKIG*02>9nBT!EHzRPS$E_fWp1fTIB4M((^UdG;ZAO`pJEo9s=x&6sj zZo51^zFYdr=kMaYIcS5O>}Q&Zt+Zycu(0U#W4Cs)r_U}2KOTe<&b?KlJyTJyOsmi^A}UEVxqZr7#DI zRsDXLcGFl@3TgD#opaNn@Ed?W?I~A~5u;e2@knC@Q1VdG7WA|*8bYO4{{dC4kUz4p z@N!YIYau(EA;s3V3uZLvyJkYl>~L4})4369XuaD!tISbi`#B=Y>aI;o5QgbSX-qTjn>}g#ulV0Jg0i5B#XW}D&Hltw zpzuO{Jbj-eEI2ykf+2WQZ#-r(pxY|kKl5wS;r*$nB89_wa*=(%-ou-ld9FJaVD<+T zy4vStK8|fn>C5{C93oY(IM++%7-UV$LIQwmM`MHj9NSrNniQib@j zWZk)3Kkt<>C3D@QpBD4mW_g?czGW=vf_@RcQPJn%n+8Jtbc6#I zO%vvrGGC>CiC%O1wX-aaDy&ISK zfS;{s$TCPN7|8dizE56KS5KiRRNXF3;Yq;7c>$D1>4`BSmHSnA1>L=18YS<=qhOeD zJh^e?z}GB^$v|f^-mRNLfp=YEeaAvacW!EY!)fsesx43+=2-I+lRavM+y zv7VQ4&%PeYb-8E2&t(nzQJP?*ajBk9vq&8c$rK0sy}R9NOTGZ8L>aO#t@lj=c{&_ShkM_2%tb|@R%t+KPlj|FKv`GZC!7X3f&|7}D%if?Qxud@{N>5~0xhyyV74qezB5;t4 z*vX3Fjr{|d66188{xbkKq8<~J7S*Yri4~oGy@Sv=XZ)T8H_G--%Z$I}uQ1^T*X@OI zEHcqMGw4=Uew7~6wi3)8T>?{sc1niOh+0b|@P7c3KyAN{gZ#)VN>Bd{ZcR!(YqO%j zLMr9$=UGeDT6j5e2U7x2YdOg%H6tHOH0{$#f}S%)3Ju}?TTI=u!2bplZ>hsK%ss2~ zKF`dHu5ugY02loaQ4VN>L+Hr0CYa7!q!&7MFRLMtS^Q7Z5a5_M9h+E2UGl~AY^P(_ zK5r}EUqaC!(|komgI3Rb=OL@l`7p#Et<&&B`0U?8b)Z!9w${K`80@Z)P_ zD1!Ctxy|EgniM5dGbXN=*>`f*|0>d^!D6~s!yu}{ml3=!Z;#nk8e)T3JtKdbmCFz} zf7`&$^=`p@!UM*@YS8_Xt!@){#~4~F`R~Ns-&ovz%s$W?t&VSP`qrcJ8ND_FO|yA= zh{4c4D%p69GtHAHVIR!X*>U7I!qRDc)+%@@LBbVi=(SJ{Jf)@z3@GsG`1vfwToM*_ zmS@UCbDWh6^u!-aMfIGVzpS!w{tlQp|IppQ;r;h)edYqb(eJ}H?iWmqF`~}R$86K? z?8a&AdSHNW=%=?^uydd@;6D6iU-E4rk&n~&yU_j#Rb$A1n4Y5ROJb!!r8=E`@#`N1 z2bbD0G?Ka`IEvzVxIpn2K-q2Sc7KmA{b@(V*I zqr+}nU?XrG=mk9Fe^TEMUupHGCu1>R9^hzfd%0!0rov2n?g z1F;wEIux~p_-qP{0%ZU4g1ARo$yJYD?7f~JMD#OO2~uE6!jWcgYsDg$_EyJVDCd!O z_jBg%EIVCl4?#-0lPnl$XPUiBtymuv(&Ep+n#@jyUh(S1mk^D4uy3f^s<(67C8%z6 zg5~JvPJ_IxP4ZVfnSR9h;4IxmGE*}&!=+i|m>`V&pqc6xMS44Z4=aQOq1foZqhNOh zTHv7S-}nKwF&(?bfi%gwEgsD^GvbN7fQyXskFP%i1Fng3DsHu*v>TGWm%1L~4ky4f z<=ZXgkx2x=e3U@_Tb;^zt(*i!U*PFETqjH^;~&CONRD|d#XoL57L|``NY$Xi7R(VS zJ6c@h$!|XPX8$JM@sx))<}0ME@btk_TKgW{u=-78vpGY|LywEZxR13|9kdP;NLdj zzwJLbxO?E^zwO=IyT*UJ#(%rUf4jziyT*U}ZQ;K$t{d#!bg5w7j_{X87`R1JAkp;c zR8l4S$N1Z;WTHyJ9@J2AugkQsF~6W>TYu20f&Uu8X#f@R*{7~OPg1mj;y%flX5h z8e?#R*?g^HHwS}gkz_?Fs7j>pE`vxyf+jQ+2%^MPjXouCf}m044l3vFq)XCoaTU7~JtYe!~kc9eZEa#@umD1Lpi(w+e40r9`^Z$97Oqadryo~tAjx?Oivl;HV z+9s9ioxn(_>QaA%XAw}XRK+Qd#!a2KdswOu(B_szuRt~!ouRTeofI~{)nt`T$NlE{ zq%ts@*0K*%n5PLI?P{dwNj622+c;EH3652LM)U*ItU%C!G4`ULNVk+swbvH%OCJu5 z_$fIk3wyJu#XxY5N0YBM$LMJ(uA|%>MX2St8C~S*A7%QE?_yd z1bso4pawJ>*Y?B!GuWk0&?uqtI#GncVl{?=HQ5E*A-o#dWa0cIPHqrA5#G1uGW4lZ zmg{rI*OxQ??bk`~mpvlY5BNhi{_ssJ7V`sDS?SD;uPa~N@V?jqfA{NS=J(@6u8Yp~ zLuufP;)6c&@R|DggF3&o7=NaKM7yr^wVEqUQoVmT3x9{!neVV4ehqa67^8A64>|F@!HyAk zrytPMoP-GJ@2P#7#jsi0_!E-X+lEP|Dw-&a#5|xdBh^|mvnA7ph~8Rl&Jbl<)3T7W z$+NP*D)Fdp>JbUaJx4em0{cLbwP9gHKDl>m7Er*-U7;epQi3@j`jS^7{29t2mf;_J z%f-9VY^?B#ep;FFWWH{z3N#JiENi;ylLL|6WFVy$c_CWB62Rrke11p= zzPl*@3%A$&f9;8WLf?uXT1fArP&mowjnpPP-4B4+EJVwk*86f^YdosJoe6K%Z>vsY zT%ND(%1zUvw&>*6@(;->>TkJXP$Q5^-8Oz2EC--Gm z>=ot??VLKnvqp}QHzEi)vID(*yK!4%KeyY|v`yJlnzyN(TmELhw#{Gr|6JSuKePYO zqy76|UHkuB`~QsZr1RCR{2cy2ckk{!+^_imJh*r5|8wpCbM60g?f-M_|MQ#7|A+w- z%Dra_7ceyGvk+oZWq^Q48VN8v|7gVx3v%yi>XF1&rTjenEL^2zW6T02@qSn`6 zmhqQmFZ#0hhO%CAE&3)dKiYAKTpgJpE45K&p?W&X_l9)2qiLxJot1qRI+PdfeE4@Y zU@5D@_uvhBJCL%y-|8T5_Ft9tuSAE}Z`mnKVfubdmq{BpcBkjs^&~fsijW>+{xG%< zI~m7#t!5xW;Hl&SXwQhAnqOk=E(U<9l~=rmDqN%ZyL8=ErfGKaBFl5Bl9c*!tj?rk zBkn5rVW+jRm8d;(K5fkT;=a2Xy_D>EBg|!KCEdc~o+A1&h!9TNB416;D!r-ZFtDoV z?9dUat9cA66^&kmt~KSIFw`h5z#Ay`cKfb`flHAZmA8UJaBt|howM*%>r-TPXhwBd zH?~j@kTa=Dt6$i(uJCy{4%aQ~ZWx-n`ijl;x@9cAAC+kJ@m|>1z3x+}pfiY?PfU0C z8@GGSAJJHy&*+Ku(P+6!rn1M&6$q#8PP~cn1UN+8mzt<`cz{gVR9-csS+4@_D433{ z>VeRvhWtr+gFSpp{D%&mY#vq0@jO%fww z8oyWPsibb|sB)>ccxXZ$169wD=#_z<2bue&3XM46~6$zZA+$DGlzzjZ47 zMu90PX45svoIAgM|Rw) zbY^aA<5gy=nq~;~O_EW0wpyZPDE3_`$0$IXsw+9ax2Ut~oyn&jCD+#ON9w7Y4%Ip% z-*=F z&G!-Lz$>%wmtkxrr_6yG@pKx6|Cm|fS!aI2nE9s6J+}-&x%y>f=79f})$T@mE|q}A zEGJOrp;}wIv$Eq*AJt4&n$535WtJJRuzg+6quMeDW)Ami3*9w^_TAC5mdzO>6gn_V zZYd?4C0~{Oi?m$oZ(6JJHa=+S*J+w8YTW|S+yiH~$$XMfKrrzoY zQsA^m=I>5dg<=9N{Xhx~i{zi#)T*B!$lJ4gK6wZK>$mEGyf0Q|nI+cO{6OCRJy|4k zYrR|aKn)C+AvrCwQ6d8~59}iScQ93Uk=yh@ie&S#X&;Nw1HCvX6h-#h;RAU)UJaAX z{*)fb%kyl}H?(z#X;2rpUD}c&LLH`vMIBhgERhtkmuNR8skuIH8AoU5Y?^sKPkp|q zxxB>(OG4*6L(xFjux^Kz9ARdl4AJ9SFR(=ya4k^^+FxCKks>X8Q0hOQMvT@)hecq%PY z;K!;>BlFNPmdwZ2E4A3-m!(#VyFn^krvp>65g`Z19~7HOig(0a;NO`n{+RgKNZ)Yg2g9^`$v4E53Gwkn41yKH`*F)%psP5I@j zT**=TJL4NpSH)s#tcfU#urQ9c9q+{b7!JS23V;S`(X?i-7)Dkcf1C(gDoQ;E@seKh=L9H_Q$>I>UX zoJv_8Yz8G>vU(`1#hBmUt*h3lj<=G06y(QBER=?w5_@iuN;OsCQo0*?q9x4>Y~2eu z&QiZAD00^?;xz?D?)gQ$rl5$3@b9wGyPC_wfnrFkFjy3;xj!ujikd8W$uFYTVqQ}S zirjS?sxAN&x#tui7k8%|6uIvd@$N@l6id36|BGS+W+w(%?eSgm_5+LvU z`)-i{dEb|YI4&VDcW`aovx@{bNMP=u$bnjEVmYC{JoJhTS5vpoP~@n#?) z8|*!|$ZGBtf+F|T0_YfXcj$1(-K!Sy$os4PY7u8tP^2*;PBBpbXqm!Vtrk^4QkPN2 zHgoKS%P1ZlbE$4)eJ(5QHP**vwyV$S;Ya8n&vA2oT&B4`pQHD#I9{Lq_pUfz|E%a; zalCl2^nB(?F&&Mc*@sPU`_JHPsWi65XLKm_F55|WGhAWrtEXD;W7LO7U~l(fnPaiH z-Q4q0+1qgvt>d#7ZD1(dYzTMuuD&SN!Mw4)Z8n5+iubNQUgyaF2t??}2feG0*A*tN z&fs0i174CXh+}*w;*t_EH_Ql5D_N(=w>L5Tu;~SJEo8r!w=pDZ7qrtu+cb==zXV0Mp zfkn`->h^vOXh$-gwrn_c`rvE0fY+2K$u;!FLERT3`-Q%_TlWp2zyvH154@T2t%T>Zw6{O`dIXyK>7(^4C z6S|I4g`+8)mpRJ&c!dMVzr%At@k(TL!YYGIyoncal5}o1+kLa#nb$?hS zs7}SZ<-O{=Y&y*-2@9%lzN)-Wrte$f7&(tMQb za+%KP%Kzwr0wXN@h*SYBtYDwbm$u^htNlj@{#^8@sF&*>&=lPL%4qi~QBoiA6<~Px z@85k;eFIzrzQqkrI;j1>gTqs=HsutJE-dJXqCHqF^v@%c7l{Jc5y6h4YBVOL@&m?j zZgfQ(44@hduJhy+quEcBY+B+Ec6KH8DUf0^w&ChzmpZO98{B5zD(hRV!%S1Q9pgM> z+hMfdyfg0$d-;-uy@jUva+wKDUWZOz$A4ePe_!W+xc>7SjsM0o_Lb&I}YS}!lXZeqLk)BYv>(*mJw&M+oq52qj8H<@go+TKp1gx?U#Vy6w z#x?!<9$5TEvVN8?zeC5}ramErTBTcRj_mGnwri?4I$I6J-7PPIlYfy8OLTWzsvxUV z?1$-Y1?t_teTz;Foef`Ejly~;=ON_LJS0jcVj8eCq8!%f%>)O9W2~dJEp6#A_*0CS zO6PqN=QwNam?FFmTP>}%!Ks(9X&nRIjn0u0$13RA8V?3!VE))c$|C#6s1;+x{5uJ1 zW?0~NUgAkRzPL*XZT$eBaI`LW9D8?;L80_ZYv&kUCa(1T5`$#}QvzL*L3MPHx7S8E zYbjSOQMhfXqZ_qgPX3-}^Y(iNcvt#uc++Bt)%Q5LG9CvVK9t-w+{n2J;}iW3jfF~E zf%y*H;8i#jRun16O|z3-zSNMHqPN0*t+OPVmXs1E(UnQ-Wo+uvDn1~&rX(yB zq^1zKkrv|Uu{yCRZwZ2CqR>bQ*Ts^tT$N2JMYsft^m?wGNm9D*aoSXi&KKi*6lOP6BMs+Jb&2;X zK7){b9ruem+uGw7ifuU`2>+^rLGX(JgVFT_xR!8_>PKhkbaA>uAokYZ=E8|u zK-vUNLWm+uUPMB8=8Jxq?dh2!N#v>JtpY87zi~7ta|-2ug6Vp^ zyGGMMC=h;Z#9X<{MClyPFf`+q%B-qlOVIE~paGaxhHpVnbjGpG9Xu)~qlm1mf zsvt#>w#tcsTI&AM>5S5I8(3-_T@)CjDarH-`IQietecW^4}?9KaauKkfu1MRe3Gw9 z&JSn z^GjD6PRGSH#8a$w;LS5zF<}AVZM4o;cs$VPvKf2kV7$yYCH)QSb12y%rB|52sA5x% zCl>^+&V2Pjuc2vbefGAicFecCVVvaY( z?epwy5~QdDjx?t4k~)PRBc0caiGpnoYjB>8Wi(5v+e|cuvvR(<3T8TX%ggOXr8)B) zOveNhWAIdAwx`8nnvDp8!&}3EWXEG61b)?nE{(SWxqp-QqPd>KFN92`l1Eyv_$T|@ zB8BBW%_b{J99Sy0>k*DhcV}bh+u{N*MIrr;Y1DmuXg4MoglL?n<-CP>=jlu?>?OID zET?O=9S?czKsNc>>2{$lA z-lqj#Y9(7zin^RN?p+oTk@7qy`$^*v@~3~q%~6QtVCZC zhg3HagHltTr^1U<-1^wp3>Sb`O_N0{`kQ~TXj!C^7t3S{claeY3p6pq6sp86CcBeO zs5K%bzkk%Xz}RqGLeJqKEh4Mxzrp4~vW|6PQPC-e8eHTh1t(K_sks(kB%EAjh6psS z+>TleTm={GOS9P5PCeKDKfj9q&)vIK|DXM9|DVs||MSrJ|J>g@*nfEMQGD;g!GnXZ zu9?AqxBt)k<(-eX{~tblSik@MpZD+W-+%bK=>GNo|1aVH_p1E=;F|w`7XJU0$NwK3 z#CN}X^awWK^#SnT&j0ZS1vj4Z&egZZZv6lL!v}lU{Qtj(|6lw6|9brY+W-HW|8L~~ zN@H}naDZn2e>fWs?z{W{0G{2y_W%DJe>VC5EBXL(h!r{f;rIUv{lRa@`*NAi$uWGE zk5`=E_cR%0Q*@k98F7z%K~FgyyHrXF{+@O?@707A^u%kvhfxV=-bkaTCy$Sxy*wtn z5Bifv<8&!LX`YIEz&M)R=;LXH(c76U|@Bi!b|N76z zJO4Rh$|ZvVH=O?m_wIS;|Ng@V_YSVl|9^+`|2qEfI{xoE{_i^e@8iY)-PBPUus_o2 zI!dBt!cLpS1kUqp9F4MKw3;Sb1O`%Dr6|$gjQ)xl+?3k{d<&vALAjUtbVYj~U5i#z z5Nd!gK=qjfrlkZGu8YOW8LE#_hL8EA6L*(>zntdFk`vDUw%sDG<^OB>|62aPmj9dO z|6#to0uo50{J+0<|G|CV{_}AE;9CE8E&pH3|JU;Wwfz4ZlmA7cCuoQ)*T0W|8aZ~S z)X%axh957@@AS_!8^$2QPxa$+Z9Z8R$tWErqj&lV0!*lvsKv7SAZML15!kx>?=cnV zkJwoQgrrT;K_F80ri+I0wF`d5Ad@z*i}iTtY-n0|Koh3@E%;F zMSF;bjafNyATKe&r}(WW)Xix+BcJg^Sz{2__B4ARVNAq%GL^8rVT#N`S02D3nWc#; zO5tAYkIw8y!Xe^sb8SiLb29_V}li71V<97)7PH%CK(hrCLP%KLn-Q;`d z#f|ue6$sZC_WB}S6um%F`WnFJ*4L7c`8M+H+p=Qc5FkKbrD{HOYHwSu!uCU*Q`?*{ zonCDymb*{8gt^d{hli>$kS$Z4@tEQ+yV0#%IYU7?L0o*Nlp>f zys?Nq9-6AJVX2Wq3!LDBSqW94khtYa7EJ|Y)hg5~rNr!yl&{aGm zRJTX!RpGPU+1x>|68l%xwVOKBo?cU|w&8#vmM;*jUpOFf70NUZOf4xUCGxg+7x+_% zwdt>$zOzPg&Ut712xwzIu@GfYuE6K0GpH~-+GXi^7uZkDg2PJT5ys`YQ#htjp9FJV zOCl7%TRDVS&{0lSsK{7>%+s!L)9nD(=(E*)nkQrPZCn4;2|}&uVDLbN3g*G?F5OkU zj^cr^Ll-3-0DJ|a>*VU)Vu?)=4bOC72{0Lt9DHbeH8z#a++Q~wKB$^3s^ z5g9$xST<0*gYk$Mexfk=4nqn(rwff;!?$Z)0qbDk=E=&Z9>TWt25=Dup=S)W4x+RC z0<$-x`AdPHn3^zEUqKi{jEH%TJaL?zo~9%w>HAM2mNHM!Cqvq4R>1WK7z|41? zqlK+WlgBAI5Nn1hz#4JvOjY${sBEy7zYM@+eRK&UpZN?hDe*?tmNiq7O_z2(*S5JH zq~OEIR55XyfZux+OfR^V$9=p^nAz?l21`Ch3dTCzF?CcNIF77WcR#Q@MFh}polNFA zDj{rS4SkOQiK?g1Gj9qyT~d@D8oIIemDZ%h)J$Jj-lk+shVZRTVEqU?T-VU=hZ<5x z!DN~b;UM4>tw<-RB^4E`m3$Q6%I3p+j3brP6@Hw;mP?idje?jCV-zgh%H{6u@2exX z6e^jsS%%hU!Xi@lCVD&2n4%QXpIYz-IU+_8NRG_>Fex?MiI|~_)jVRcl=g@!$uh2~ zigqM{A*cj8tnQ{JX~$D$cKyAK3Q0Zo?!MK#y57b*^~nga1~a; zCb+LGvw7+?@zS0u)gO1t{wDVNW%4e?H32DaGDVe00SE0ewJwIJ3PwKwg>n&#@{EC$ zu$#nsSj4lyZe1`YQy&8^#Ui{gf|JvGN3r5unlGV3_!ANq4cUFJ=~wOdvOQY9@AT?b z0L%Aki^l(0B~wnJgklM9?1VDujQhqNyM~<-FNJ-;!eX1YP310&PwkCn%A3tb;6A8n zp}W>$MZCrRxBYfg?jQJWp3iTajY~q|DKHH>(-^NxXvjLak-qGA)G2papDWTT11qca zAtap8=-r)>dO3j*k#~j4yTl)1>?*6MBL>H5nCH`hTltA#m#X^9Gdh}ys0pNcifAWv zp68vu<{OEH5baEqvovxRi=EK!Ncm|pYu3O-7ZJ`D${peD801M)m9x=S-q=LS z8WEeq?Afkf5T1U(?RNT^i!5recb?bbtHVKDvAEcUrDK0R~ zbdcvVv6d-fO{Ne*eqv}Y1hJC`8A6NsgJvCoSPE=ZW^4F98rxa5#Nvy?sja zPWp&bN^XFSR7ahaebV{Lu+gTxQM>b)IVwW!Y3lu#>IWvJ9T4;M-*7I@f%Z_wW?>h2 z$n*a30Y^J*1u-Ej_4GiAEG43G@^$kxU84Fi0wT)c{-5C*tnJESM^DbE=@mok3tRaY zANU5euAj|M^MTJ?1kMp(SR+D_S%FrnIs(d@vdonrN*XNta3#ZTwKaFs&;HzXcX)n>qK~) zh(82wB&W2OXt~+s<5s1#;JSF@RRPvlo5z_kc{J6og5g7v7zv?H6>J;3`0rn4*fR+UpddLv)N)#DP|ip;0omIII`bp8%gdIHH#T`_KDFgg$7rdw%|-6tw|z;J6=$e! zwN7fjvM!4BwpchZYpwKzKsN>m~s((9Ps1h{`b~K*~27ptW-K^GBfNjNFzox)x{Fa>cgNQtC2BXORZB zNkNy>b8vFlR)sggv54v4j| zE1b@T$`Gx)a_?g)Sa+QGtSs}TSS?Z{u-YO$?F|fdeyxA}Cctz}Il3D&c_T$L;%Pli zzd-S@+9Qie-bA>bg~lp%D$)$@C&wm$z=ImVqvk zVvMaTCvR=`58j>eGqe}cRv_npNlz2Cvp9{t>)Z(qnlE*K>%@Tzt|o_!eE!J}sc4`a zrSqi7^77w>p*3xk-xNplH_Nsx&2!b-$kA?U6LhUr5pT(=kfWnUTT3N>0Zhp&wpmyV zFqslXFSB#UYQeQ$!78yUaXF06H3{Hg0ub_;XX0(OA~G|noMh*s_2S{s%}03!kr{F@ zz;@$48K?UF!Is*UEBWWb#i2-(@w(mV1tcL;Zno}=5=Cc4>{Z-A#5+26C_%N**-*~3 zz|I6JA6TbWH$#(q7&((hyNhglC7J$NKf+E(J8eNhcw1BRuY_N_4~X`Z0XEDN}kBf)EjmCt2L)LqfGpUS2U z=Y*->su!3XiMOoOXm3}yhS1)vQ8rwSY_}w$2IsE;X@#vm+gN@=)*oI9A*DJZYLa^; z(!r>@O13kyxMRB;;xeg~kE3sslC^Ryxsh$ZB$q0~b>XKO0vb51RD7+8ijC*iVw#MA z%qce(2ybWD52cJ(Ty5ZcrKwJrVr!7zjk#fUI5!FEU!T0CeS58zy)-X-kFX<%WxQf=Wo+OkpR!Rm~XL58X>Eel7V# zUu!2}2bLS~=0?4v7&)@iHJ2heBzq0sF-p|tdE1*WSPsJBB`zA1o_%Ya1TPj7=Sor2 z{j1#~CX@_IoFd5FCYkT-zFE{@8{ORcj^^6&Ri`H>|3!2{HmPpIN^dVt0Q!aDnUjrj z55=DlH-oK`DK`uF(%o~)&vO#PO#`E)+5I{g&hmze;g518zU5DCvP1t*Lv}>iF5P{f zE=y5vOiZp~Nt)y-8^onUh)`z<@4``z$JJoKiH{^SPGtbP6xr=6evBkzph~618J|Lz za&(Dw!b+E_D!HGYX9?yJzo5umhp#G41$wvA{xe{rDj^;|Wr5?==8(JEYbuu|B<{C0 zJ5Nb=X3=n^tmRRWe`=ZPuGzxPED|H{XJLm`*QzGEG7>NLvDSC$Xgbw=BQ?B=9Wy*; zMgt|J0eiQc(a3cT@(UZkE|0?m#hW11tSb0$8v&1m`gXgDN_ zdrXK~guO@@|HE-6BsKc3;Yvc@=w$9i&Q!zC>H~Z5fBQi!?N&Y0E2aF9HsWfj&HMP> zpEw8Ta&0+H!RRaTDKK3>0%M2m@2DEOk+YY2aGIhxdYjB3GyZ_|t`4Pjx!z=f35jM z^I&GiT#ViyS`w0xP~P6)P~gj0;G?ae2%_@fD0T7B2;HpkP_`RYLvQ^*SY?r7qGu2m zrfWqm_AuI=O|!`{b6C~pDYfEdn&y;4)Ua?k9<6z5rA;?lR#gr3oxbmyV7GJ*$1RA} zY1=efGrv^8BDAem&Ht!wDTE=5GKqRwxxwlFW5)|LNMKH3{xVeWIGtKk`G!7qv`l^S zSCX#5)=#F{KcQ0L4%2MdMIptmx~jjx+X~DzoZ%FJL?5+4CMyucyH#~o)d@lC9abB3 zXkq;c_Q987cM6%)6=jqeIp_**MIF*%2s2YT3#s(FlzrM_{Jz`(_tN?PFulg;^zoNS89QWBr5pZ)@g(d2W~11R6IzWt@z+8EgRn6i5B4c z4#UglbZK(E+*72kXpyrfFuI_N7~#6<^xMu@Xy+=kJ$-D&4Nxt1hr}OSU9TzCFmwv8 zii4dDm^)s*OplegkKLyH%}VP$CihNspg`YljG6Go8Fo~E&MU`kk-cz7Kf(Knk6TY)jSjW=Ma{K8*aJly zfyQY>HR1&c78U(a1?25wIuf_v!;zeHt1Dt1gdW!@>`$hDqa8s7Y7GcN(j2x;e zo_ZC%tI{~xx*`%bEtQjKj*uROrjEAx%z$xzBWbccb58-4m-FHI4SV1|wkgL^6WJ?gl{%6VI3h zZNvcJ7^~CMhLPJw(>hexkhxYWbZo9b9y;hfSa&xf>y6z6>Ia%@(UlkEr26gT8O>?XZaI1s_t67v znTIMfF|gbA`eXLkwZ7LXMuT4&Z#RY8o{Owi4Qsb>t(nRKE{Va|_7~~0$kOvvN8TYI zu##U<+eo*50@AQtjI|su&9Kk2LZG{pLx@&hNaqy`*Fe!8o3 z(Cay{k~io`@5WMeqlVCmBbp}MJQ_NlP&E`7hMHA&v5~3u&lWY$r}Y?$8m}3+l9J2n zpqmAi0rArX&^rKxsKd8iRE1fx9-@g`+(6q3pV}p@X6L;EkqTh0wu1rXzL2dCehiy4 zH5&_je-myqEeh5vGY$2h06EN4_UR^0DEUncj$sr%$>*(Qs6M&DqKrV)PBOxU@@^z4 zCc!4jV8*G>3<8B&N@Rh^5!@VwUehx& zcNld48{ux)e{S%RNSKt2E5j~Um?MYqmOtE3kd#o;U@seSv|`{F>Ve|5XPEK$Z{JXp z7KO$vRvoy|M!Irn-#=fN6iwaGXGeWzu3>=LpMIaDJLCH0u zMGDh4kzoqERA9KwxrF4J1E->>H8~noLKHquC#!^#K(R+T$rf-z69|JEx)YeoK)Who zhD>TqpA1Y869%+O$U^cCft8AsK~Um=|E$v=Zk#Su+0uBp^WzcBP=+Ei_toNN0>}in ze}l&KFB^{Ov!8L+*;YeEHi+D`40!%0BVdGU+q+%)b5ht6UNF$*Hnr!`<7x(_*hC{& zqn@Lee1)r~{ou}F`@>X2A`_sET5F|FPv?wYlUM8iTgw}rhL*f6a*M9II&0O*j_J9I zAYXEFlzGHBIppB%xY4re3sz(>FgApGjSHn8I0Be!{Mk(;a`U}hqsSD->yJZbp=hF5l+IO%Ge_p!&u}M3nRvoR83#Jjb{>5D5=Kj ztaYer$)qZ1$f1H05!1l6Wno-_5zVD- zf2O;#dI3+$8u*=hO4?EktcszEKUto9mRI}X)XZW+A~-HquM@}dnKFKyJ|OU+flKx^|^$axNQjEiv?lzbV(qjb<+ zCK-Z#vRqXo%Q&GBoIat{j?@}sgdLexJ`mtZSP?6Q$~%lEy@i34Q5qLDTLY}l)@ zLGh$hoV4VF7SHxJXAs)s%t@;N9tqmf!-%ZewfwaWQ7)@q?ZGBCZd-f^KoN~qAhy|UnXLkGE35@jRay~wcUw0jGK^p%Q*U3(9u{BS1K#y^>Y>cW#2fZRV7ow$uOCuKJZiJ znqwm44ThEEHY7x06NNvk+~hbC0`I42@gAM!c}dqf$sAH!LnP^hT-O4uLo8&1mg99W z6qBd)2*Xu2T?#HT&m!srSf#Mc^QpLYRiZc(7m}-jRAs44d zei`vqg!Lhc7SLwyDDbGg`p)Iy>pWe7@?YapFT27Dx@t_J<6X;O8a8LQSq5%kO1V|N zf>{L}-XBPwq~H%~xq?+!g_YPBQJp%mE$I`x`hM|vn&G9_U%0Up5M6No{3bNv^Dd^=oyLs9tq5 zwKOUwl5HTga;_MLVynqCQ?^V}kImU2;-M1gc!<(ZA1Z`gXGM5r$!;QZW~KP3`xY4G zd9Gw{y!U1vD6vjRrKKT0xMGd=iA}Iujg49^H!j--1y@eQnoUh_wKCMhX;zd=qpD#5(3-8Sz9}i#C*Ul~R(wkZEcZN|hn>w>_FCg2a7#g8s-6Ly9Iy%`wCY!2mz{WIHVvQF>+gDE62pIxG=U zP}GIj9V5QDR}xKJKQIR(jj`QVZwqmf`$~7tP_5{zq$Plje7ZtM4I&ObXagH72#e1A z4YD&p03xOw*OOyRrl8Miph_9+W&6Ek+m~wVzGI%HDXtY`o`bY+w zk#~IsSPqaBCrQ5cUGg}~bl+<8eC9VCLe}hl()r}ePH#9&*c}{x9eQUy7or?Bn1Mr$ zjXI0^c7P%k7Ieqm&2bzsE1y@oK$V~l4+h6MUq1PAIJAr$ob$tT%G{SC(tp$O(7S^u7LuGjetfT`aHG{hjKq}T|xRI#oMlQ0u1nNmd zJw9q7CN9uZAxKo8f!5Jdk=f#s?Omn0QT(7Yk^?w(v9uZ4C^?A4u7{(Zp=0^2z1Nhn zz6$Z2T!c)v;5t9v<8ynw@)i-P=A9HN)`Y09ec-E7JI(4wG8|HRpuX#}j9N1bP-e!Y z?P4du3!r{=&4-nep3lG>bf#G@aS5qyw}AC~~XWvY6(`R0V8= z(*$xi10Mq{35~Pz{G*#bp5`;#CGxtKb%Bv4XEZm((~Y$4I2dV&8k1choE}AA{;Xj7 zg2Ttea8=RDK+cfq)j|{F(CHe9tE-spq9E*_ASNc&`8bBsNE=OULHE|0sG3+d3pvJ0 z4AQ2TcE=kZa1|S|X0jsq$IsZ|o2JA0sY#ImsU_Jdmghsn>DNz8yZL}WKwwf_A`?=j zvsmUpCMYs7wTr8c>iW=ytRNn(C)9`@dYmUE9!+Le=&CgonLg_Bj$k@7nRUcmg*Cn- zU7q&TRJ=UT7V7Kh1yZ~Z?vk-()C@vZG}Jmr&R&J-=OWpN{M$j$f)Kg098cke&=><`JiKl}oJRv;p&=C6B@zmq@r z?j=(=eRl6Y+`j+t;a~oG|Ig|6M}N5a^S|Ve=123(t-I-i^xmiZ;8P_Ue5yr*PXR@0 ze)&FIVA?ZUG)p5y?Sh@Zgy2weY^=yJNqIXz^y0G~;|>MEh50ulBE)PpFzszY2gW&EhB@^pET zEppb!R;9YY!cjXeM=L9X1AuQu*N9s$xx}qr|KpD!<{NTB@HJn*GYNDSIb3czZoHU{ zCabZ&KEvyYk^+|OqKFYmFas3}LEySOzr+Bs;sA9(ioX>8&2eP&)rq=woTIqG*ULVr zawjyUs#9txpDH;?-C@9Oa}FuK8In-Mi>~KK_RM84+CqSoxnY0WENQoYpS~}lY+L$E-t^S&NI5v)2!hQ zR1HlGX#qU|?-00$C7^Ghd@JryQPw;D_8@ur{OjZI_l^#d!-m~K$lILG1d(VDI{&x86aX&fuuNOxLFJFHA(etC^@ac;u zhX?B6;j{fG-#tEj_D%9g6@2zwQT3rBXI1O*^8{Mtx(*LsLTyhEj`qJ*zwSLcd~$gF zgIcGr509UzT9dC;t$WFfy`$s9{qLUa9VIWmJ9_c_<$>zJV^#07!)IR~sb&tI9y~i% zSD$Cd+r1}GpskNT+WSro;7GM}l?pTO_m zy*v=Td3><WsIbj0rfg!O$JQ8hpi zzCp5^bV)+)?3ZUx^JUiQ+uz}5VK?G0x=^tY?)Ly~G^0=8rh@o8bY+GkPv1J8mCM{( zcv#CcUz8`=$%H_=Ru!wRb=D#boyItVd0A=A@_e-bg*3M0uq*WfJ_9pPsg-rp3}e5^ zJ=XS8`O`@sjovF{EBYl4o1n%W|5d z#jI;4VE0OuKo_tcQ)54o{p;Hq-nBz$sz0g^|K3x=S}kP>=|(LH(;c)j#06@&T&)Xw zwK<8CnUqn5iMpj?NybeEUZ#Ld1Gvm5h6}nZL%`qU9vLtZ?OXx#pIUi(xsPC^ayBtB zL+9xw!FI1n>#)|I*@TRsv7O#0I+H$b&aR6c9rx47)fo3 z4H7eJjN2VlHJ4V&Y^_u5UP3^>+@!AH(NVpphI7wMX*M z(@`P+Ls0EY*+t>!VgQK^k+P`8+=5mABImR8aRF17O=}>>XX;BOle%8|au#V9CbD~8 zjK}$m9O7aW_m#l3r3l`L51jRgI4#S#ccf3n5TJ}rCtF_x$&TwA=k8Ko=BAFEWQ!|w z1nU+0DS>}EnB^CPL3acWLU$D7fPe11xqzQ!^#q{+>Ps-x!aRfH$>Pj2!c?z15?H?j zG4fOy;)ZtMaVG#Fa7tF>iCb}LXKlUOoOi0)pMLgo02u1g#OdE@jCuF{ps${y^fQbM@_q$ba3EZDWSaE zOP!?%=Uc3ctEN36JCsGw<*EUpcTtA$yog3T$)|nmA`Vrk&tc~b1~ov{8ZlUvL?C~j zp9bhv{mCmR^SZG%Gs6wlnQwG`ks7N3V2Bi>0k6Ss)UzGFpbvUAk)3WeHJzgA+tqRE z18~dm0dK>w7Vd?Vj(hda`7OH=1q9eP$BiXPpu|rWKU+dR$(;#FjqB+Ns@h?yd%fE3 z!D|_Cg84cIU_po7(CKXwLSXk_`!2X77KafqzPfyMiRNGnkQ>%}WDBA*8Nn=wEiW@O zHT{8NYBeKigKq1N9E^mqv99snzJ`^%uGMq6vA=`?tK^g|#_l=LQRU_|)!^wpb@-w1 zp`8^_lYtvyA93}O(Yjzr+>1d*tLZ_lpNdn!;a7El?Xti?gIt>QVIEPydo`YT>^t63 zZSpoUHi4-55A=QG+CEusspl%Xqt?Uqj$jbB3-#Y4Q}p z*FDDwBtmw|pv*J+B8!)z&?^Fl0#Ugl>>lsqJTMWWseN@xBciWU)j1$Ss7))fpP=@I z9f(yI^M6tzUWzyup?Fu6v2WuI2|XN>`}ZF9AuHsJ!|h?8lW(oosoN`CiR$4>R;0v@ zQ4Zxvr)~By@q}au8)C?tT1J=O24|LWiL>IgPjT2eYuvGUH6b>U6Z?RtE@)+3x|K0; zQ!Clqono&5E^gcbIl>YamNNyWQ$V0P_*`H9QHIBfE0$?2Bba-pP|&Q}eALbWGjo`Z z_9#ON<+2LTB!{6*%JK~&+k0qrkXXcJ9y{3Bd(s9>41OIy6jwX|8!GJqV(``MYK~DvaVJ*f_uu zT$AO1DK(-~B8GxOUGP)eiKs6h=wu_D1qV(kjkw*>!J)r^^I<5%HB6Bo^*OZ2{k#bT zuJ3Ej{8g#-Nj4!FA`v2*x-c-q##2NCb9fS8jqJXUGY$v_Of8)PjlOO`rt^e{2ri%0 zaPBx*Gpc1pYgXSz(iIHMO{SMNXQ>ksNlhn(59?KWD2n$1Rl{V=%?;AyxEzsJvfJ12 z#ufISs~D&%BsXkxk*cc&Pa*2quDLF%{i0sRVBw62hehNbyT~SQ>Ngo!usF^@RN zP^2SEk&S$Mual=Z^$v|1d|LG7hABycWPEW2jq$f#pKOfk#UbjxxGUfD8LyF#V??2M zP!_UF&Ud8s6+L?!ooB_uvQEJ*O3bENj=}E4?X)p40u9UNI%pl{xz^pgU*@C zx5D;)qG!>pyao6T$s)klFg$ie+VOaktm)JW zLeo|p4`i(}ys;5b;hF|s`ykyHIfW}@(gftPLzupy)UXbz{-K&@j6{vpLHZF58b^U) zLrFnH6G~wea(*@79$1XtfQelRRVS;Z`+2gOovs#>%3(K2Ajp7PcSRr3kf#_(KeKp^ zq(;;9t80piwC-Nvhi0FdQzdfxI7x;Fl*1z`04H2dpPAYTh9NkNLK=>ZGf1}1m*%t{ zhUCs23QxIn$J-fBLJSi}EyPcoh`~O5X`}WTDU48KDc%RqkxR;Qp+`ca1zvoTE8;53 zRXyCV@OWvSMzpFxa4|ys)M7Nn=Tk$jRCGqm1J~9m&Psf^Zia*DO$_2VMBu}TR6@|N z$*N>)8xB>^F(whV9iu%sek~SivROWqz7;Je@S5lD-+bEe@^uZJ!3>`$Dd4tMXgb&#lq(-j*-(}6G}(E zhv4$&1aoR9L>pU3xL zn_cGXTmeK$xW)x2tL6Kwfb7oidjU{CL&i&7{kZ9? z#;2+=Yn7cpoLI2gsmdv;D*h$j=gI+oUZ<;mwS$xSy1|JGhT)n0xYjzepTOR9Nt>WH z)$A$~SQw?Eb_-&Sl?z%rsZt{sYlRP}Ys^Eh=VsE4L})+t0mq%n2L$2~X#wAC&Kge#|{3+4gSZ!CH{v#co6Twdxi<4WE2Vk(TgG)U0deGT)4>ZPNu&ZtI-6gc*Slz zP9nb;^g3ge6Lc{88-YnZ8E5J*04s+W6o`w*3V^)FlsHjsq1DpHmc+BfNgR5Fn?=Bl zF`%?lPzd*xy$LKNAb931Fn~p(uQJ+)hff~Gz1Ik#E9ND&_C;k!nxdIULMd#9?&nJt-NH(JbEDDj|wy$90%=>(tN;OLc@* zpa{h!AcPE^ED=<*KGK3#07St)vk@jdQRmmn#NV-jEMgyf>%=rHjiML3Hqyu|iUCu$ z8Lbwg5TORt35ywVJp+6hV4gw;hCec7wl{P-iv?RfQ&bL|{&xhP4+YiCM`nr(|gS(kkoxj1^0JD+V!f&naVfgYG6oHi%dqaLW|WY1`PYQeV9Vn^&gNPrW>0Gj*`=jwqTHTa?v5 zA;VteF3V=CA+j36P3xl)Fk38xG^hIShmoXuBDazj+Jx4l{Cd{UEF>qVYS@>9@@oF?5O%~NM5us0lCtF_~5FAbTk&QV0! z?idu$Z(uXl{&zqzs^#q_FSF(cn)BfeuFjPZa5Q!uLDRltncK+UE{mOxYdlAYwcE}@ z?qhP@Bh97kRmbyELYF*^h(ox2OEL+lOCR}gjh!4}?3rg0h;f#2evCiscYaE8b3X{~ z4B?l~WuWGo5WB5f$v=K2@chFxwFhx$NX@$lkFqGs<$zsx2nfaf+KwN3X^BWw*7`0O za6Qh3O)cm|)H>@VUWQ^12Bkwjv{^n0!CFL1{7u75D0#1G9so)=MDdWOTE694E@u%b zQWFKQNZMR0^LGFS7e*QT!;i678(Uv9UMt0#F*|lBC3QlS%OISHvsE#Pl0=wF?i=`Y z_}ZNoEWh!@pvUQ3C5(Vsrk-N7;sP7lbYyjeBA5AC^>3fP8}0}y7drWo|g;A z4md`&wN5_DmLpA>>*A)I(ficga{QD!J2re5^u$=pKvtH>Cr|U~iF)$7W@0!^`Sr{T zRW{sUR;a|LlM+W4@=#!S;9@E$y%|oBoQ}{$M4Lo)(%gZHC8h%AA-F+wwpP^4sH+Kx zK~A#icgv~`t(bIG#BvkK%BauqB9z5{0yno1q-u8N_D444)z-e zZCPIKey(Qh3rB7Zuh5FVFRC!;D$lwrOm_4K1?5(ifXO|noWc4+KV?ViAWb;9&E~kPVC&EQ#7^)l=S=l0h07cwXu7DMsTQmKI5E`{T<4HQCA;dj7QHn=4NA6#3!;<% zmN}efVWJe<^EvDX>JdoQ5QV@`{p1f6X^3|XXW9V1#KRFzXH58ZP!8(a1B0S*BVkcw3#*9r`OTF zDA0fO`rMMWv}Y?fVHh{@UpMhzzm52>zii*cf8E4?h4EkH&~v2-u;%!$2lpTTdE1Zw z`s~3?{^y(cubcR-LQFjfqn#MD%G>as%fp=I(ylW`(^ z4rYYd*Jw)!UkJ-r`0{Y}wpd0A9hH;F({HkAxcO`e1`c82B?smE$t8$qaR!s{(~QTWMyb)4_TilQtL9a47hNZlu>WjrblVs#IJjMem$sS^ANpKD3>| zxKxv_J?)IV5KSl}O<+t^s)_|D*Tonu?SObuzgD!NG$Vtv#;%)?B28_Q# z*FSI^)scWfi^g6cl>f#KqQU1z88kK=@~DTygwbwIoBKL%In+sBgd%4agBdw#fTfcl z3!dD!p^h&k#5+9Oie<%J$Ma}Xo)sew+TrgkWN%aIfiMt7qVi$$5>wNs(^?PFV+-gH z*?i1EvGYO4J%f@4-^4>5<2?zRiaUhHrI_QT*b3-jmT9+62U{|9>J+Y*0zwasoRdrZ zAv+$V`@9Et!-pmg%Htl{!lrunX zDn?=z$`M8hGJdsPY-g}((8{B1yvHx2F8jQjM35FgtE<3wW#nUaT71lO zq>-*ozBj{MV6(FaBXK%$gmleMdNTzfU_s>9pcoGp`5C&PDX9@+Kh>DJi%$2|_SRot z|8)03|K8o7d;j6YQ~Bu`oxCb|EU8+jfNRS;J+{f66hgWeDU}lEiXNvYb#%qJMJh#p zM%gmMaBTLguo2iwuBKdL6{c%>TV#anhJc^s(g5rOf@hM)PxXfB>wv(VOLLrOg^r>c zknU_!o*-u*o#*1@&}G2z0`dWwxGTO$RG70`^_7N2h3=-w6Dp&sK77oCbx%Hp*`ml8 zc=efl1ty8xFawKXO0Vzxuj%mv=W#K5L$By%nkLL)!5io-AMfm_emxM|1NX4rLNwEg z$iGJ8wKS6JZ5rKdjg6&uyPdvspv|~f=hKKUnCC_8Ikj9>%nUV2acmNoE%nTYZs>U*soMIeMe!wmQ!iWA*JW z&4K>4cRd7pQKdyS0B(qrsP;oIF!Ua3LobOD@NvRl>r@)wzhuUIiaHbw*epftir({n z)h|nkk5H_;vou*qaKZoQ;kG)TK(A97H!7T2_(H8(QJuGOIG{=J$OyR<&rescMAZ2} z6AN_TfamVCb|cUZBd`RKQoLROJj$J+f8vBo@7(wavoe&xK3JV4#ZjO~nT}&Jl!XU1 zra)0#7w6Vi-Ddyst~113?Y`olUf1nI_WtD>Ht=-j66lI?#hVTJ{HWKi%MVID28-1Z zpe*B71gIo5db;R*{?#X6l8pAJ-WUJT5zPt5MZDZodx7s_pbpS1U$q@PS93;vj60m% zW`7GUg`{aff7pJMYPE_1R~N?aoQ#oSHCo!awzzVn<5wS_w@Br7Vv3nMYG5u>bZY3{ z?e*S&u!aM`DqA&37gkRD8}sCX$KiOlhGV?%taR+D5QYhHXjx?a^^hBIed*TYiiNJ6 z?^UR0ygAFT85*$&+(41TFLvy0Hg$FN6P5asTVy;ih^S_4v)KuhW^_Grl&@F29HATd z+N%-YYB1{a_i?qk()|S{WAodKbJySaaQHx{Kn<(c03E<$TwD&H;n!@x4OpV-0-t=y z`JntY@V&^!Rg)lYDE?l&og5(sHj;Das}34Zl?(!IIhV<|b*Y_BOeCp~M5c zPJPvhCPnAAy{aSbqaL14ML2|Aa+`uiVYe_-w0m96_b zufuQHpA$aVQI$n9q%U%UIfCd=y63ERl9jpV;J?tFRq9`$JGYX{*Ec+!{^P9UDR3 zX2~t^%}}4->P62aT@$%S=-(x(OfhrNGr1?d$T6l%e|PlcipLdO7|n^*Pli0#_7GG1 zX=uMYQs)^#Qf_>>UGaFU(=`9T?w32=FJFAl6QsEN7mn_EtM?_m=1B@(JF4kPF?$nh z#oGv-|98Gz&Z{qXKK&FLpD$1TTis5x#bxiuwD%=bt9p~o=e;j~gjzo3-c_G={JNk9 zr$_(Y{R;9#1Rs9dG|Eq##`x)%{~>DYg(5?p{u~zF&Pk|U_wU5;KFT#bYKqliJ0B4V zZrQiDB>T0~p$a(#?3-I*BSMq_Q~4|>T&MxvWbl)&ZP6X5Mpdm*aNx_JgP!c7LwjME zwxhisYFg}&FbojNnFv~M!uff0%Dp`;+i40faNO0ojL|kV%#5dP(#>9#8ocVhE*B@5 zS$)k=HCyUhcB#*wn8V1`ZaOmP`hTA-=$ek0U+7ZgY0Td5wsiZ7ccq9T?W(%5&2>eg zrGtnOs82H@ZS~kpTSg!SkO*khYZi4y$jzvqMD|gSfLR06#b$ClcBXIQ%fJ^+56}_X zBja%nl55t{X>K3o!0)Md<2pP2#Hl?vDZ~m;wee+Wb&_l%^+(;}uEus{RX2S1_r-T_ zxGr;D#(MZQ*{Y(V7+zYwzHVPBo0F7Qj7+%(MfLexKL5_A)5FUac?W2x^4%V zR0mH^u9lBALeMhdb!ovHpiBQ;%d~Ng91$!xyBeIwge-S*V&SeP6+#s^qF`I*0<-l? z%Yh9pe7v7y=eZ6sc|m}Vtgd&t&y~DQ{&Srqsinf58XLD2E*5GwyAgqZAKLgkR>>lD)DyLJ?9AvWcqjD%$+~r>^|c=YIs|3;SWSZ?N@H4ztd=RbN2x zqu}5?kU@qpk{Hzn6Vc_2CaQp4c{gtPYG(-6QGbXa1!(7aagvY+6ag_B9A?7)%$CkU z0#SCA#r~wk9pqYph^w9Impxq6kq1s&K);8Ntxs4E^Ki{44$kfG!3{C{a_3dH^{>6H z|ErFN9|y1hv~xw3Lyg+`{WKBB6tSVx2jEKSl}Yh!K$z+EDJ-zLs_7tVa~Q2@3Rp{B zLk-6%7HQBO)XXGoD}diMGg*v3z=Rd!UERMAIbC(lv=a}UExM6*A&MPwmBUw7RC5Ho zYztjjXT=mDe;_KWZ(T5BEbU6XD;ypa{aP{e)gu3;d70!@-R#SrVowgc>eceAwW2N2 zj+xF~Oz_#Pg`*lp*K55!KMS{~C-cb#Na2=CUf$h^<5b^}!>&6VW+t}7{kY0> z&7Zn0Mq8+nQev^-4n)eD9pfMol=^0vRHMb}q4%?*sX0U_P;+@uw4z%ZcZ6+$2la_89tVgewe3eIO{bxA z#*WG8`hI`u(*PSGR;${HvBzFXr%hGF`Q7{0>k9xQyHBp{aO?U?2A+*(K{GwFpMH*| zhKWuCT0OKd=5a(y5LlIyx4Ctc9OFWPIJs-eNh3o{k%p`r<3R$;gWz}F&gZxPSbfn+ z{;2qjy6{67dh#b#n2a*$kx8DIo!jFaGMkULwDlq2q28pVtN9psrav6rX}sC!O;;HA z(R7hr1asDlO|DBRZD*B(2J0u4V|Zi1$Ze(+qGJA(Od%R$KFKZzdLpMhk6-C|glL3L zqlDR_+cF^Ny}P`wt1dQobg|tw6nTBc(0^}8=8b>UpQ1vUU-}dha*y7Xy7$Si z(?8f*Er=BreFMGdVDI#Z6+VdS(P7=uJ~~*x@(Z)_3xDOI{!Yt{9<~A#{GU9*P?Iyg zI5vJ7iz^O6bLKk%Uv{0?F6~kt?g&z z8-1xpwZ6qNpPEWBxwVr+C^VkaH~nO}nuoiMUcxx|vt9ro!)+v=>?U{DdX=W> z>!h54pq#zQF^`Xu9Vb>Gmj*pKY-&s3EJ*Vu;P+F6fRXzX1a`CD8u~@wA_d|%I_-k^ zpfebhi{h-9LC%HVCIidgfjtmLwNxKak|Ari)1C3MjTWb3pa9#qTu@>P@eHc#B54G# zTmoFz%|BMJp-?g7yr+tRSqSO@9_oXE-#u+ht(haXr*c0bcQ@t%B1l`?@nYsQ1sO~R zgSOpX)OS0kS0O#+0Jo^x5_WsJrKol|Qs}4dHKt6s!T-O(|99~J@BjG*|NjR6KfwRz zRP!GI|Nq{-`}hAG;{V^f!T-O(|G&Zizrp{%!T~_o|_fs>CwvnRzm_}2pm!_a+!|X*bmSep6N8)TQ>1K zdmU$$P-aivl)yi(-zM}{RIP8n^cJy_|qb&evyxg{4Gi> z$>XQGe$`jZJ*j`cI4{84XQn1ao#M!eydJ>}jLWr4>!m~$Z~9)1JyI$QDqEIw(p)1M z$e!(Ezjq*&PI;4D1{m3i0OJ+s~14Xs|&{wTx$wgm-Ri z^m-Qdw!oLJ;O046P%?~iwJ`J@o%Jo0?PN_=p7TJ?0FPDTRF7Ajj|FtwxB%qmMz_Y| z)TyRq!Xh+lnmxE!mIe74LD%%qfN<0)FpSH2)pW#@;-_*Z9^AzLnJS9K#($h2g z`R6Y>DH$%Qx$soxyTnfIkxG`1Y!c3>Mz{wr8SjlA!LBLZW=b)*%js6vtA~;)GbagIX{8gy7`%sC@5h%Z3WVF%A$sj=u4K7cK z>LF&?AP9qGe3COim$hIVotbB%>NHWkO_@cJ7@g?6^}5Om)OmtC>l)PM;o&wp5f$_u zA@b{^M70O+Ia*IurKUtD4pR~myGvZ-b67$z9_%`1YXebZ0gLW`1C!DwA|d#ROM`V9i&Pu^Ux#o`G?2vUa87ogHtmWZ}jx?TQJ2V zLYl5_y_~jo(cEUmqWdL#HN8l~rloAgt{*S+X*O5)zFg)UgcL;h8sdtCD>1k_=O3?F$EwvFHF_uI@;n(r02_MF4uQE)HS`c2 zXN7z7%#7y$tnwA8zJa+QAG3Ujbm=H(DIRqJ`ueRcR1aVZKB@}QL!j^Z>1i>7I4|%X z8QCGCx*~%}LP&~uQ9|~R75h-)a-0-QuaGP7O`%!=hH{~^(PdRJ!did_ub|Q}XOnw* z$C-nR`J^o8bZu92pccLHjy_xYu9!%uI%uC>doQ5@h#cg(1ftH>L9!gci|`J@-2l%i z(BO7A6u>NP6LIV+feL^4IpLajf4b|5R(*5|a+-IK>j|WHX_hf~3Q!|!3l`M?!c^Yo zb!Eeb)hXgsagn{r2djBd0p)yj%6dHPbhQNEv0eA0bY9LU`RTG7M^vCfDFoVdV}6YM zzQTJqDB!vgOaeQ=(BC^7Va`(PLT2|~L_dMyrmzddB!3=WFVrQbI0II}cT0W4qQ)?F zaO8l)MkPZRb6VS!JF7s$sk*`eyQMer^G8p<5{>iOxJ#e*+9X_}h+8TUOQ}@9MkCNW zO}cS>cri#ta5%+c`Kjn6lDx_pmZr^K4Q@mrGf;zD#!4X;16Z4CDh*Y?)$`PYeQ#>LyU8USQVW}-JdgmMqLT@Jhks+&{u-kj%g;5VRIS;&q7k*5*>R*wA-vbs7H3{cXf$UJ6F*DP z9tObQ^2-=>PFNDF!vG3%$jcRU`orTIi)=^JR1Y>-TaTV~;W%D+%rNXylAiy_d`q?# zo$uSpaXvbqu{?*ivJPB+RmlweU%Lh3L+edVESwCRc$@h+aY{FUe8*Rsx z%8b5m;_iEwL0y}!#Kt>jmHnmCpAA5NVOktKQn#0VI$v$K&TNOy;@-zx#CQ~=ccJ^+ z)8buWU6Ds#fitJTc~+q-p!2})?aFzs__O;lde^wW!AIuFNv`g%U@d%GzOkjycgufx zQZ5HUXY|8ZhxEI!Q@%5{r*tyU$I}70j@p?4ms0nUx+toeKuc&J%&OB1XIiLSQy;@G z*zp@x{)=orfcyVR2GH*1qSTktugsoE;iHOP`Z@-9HP6T0I}obcPww1#a{)g!Lp+iX z2je@cT7%NTU7iUqcAsbMuU9~ufcCX^Ia<`e?QZ}S$vrwmLLvpD`c#5N#ym^J?>uvQ zAFsS@#=qGO-Q=OTDDmn%5 zx%9)t-Px6ro33r4WZOMfOlTSQckl`-UifZ*`Dg_LywE`X z+M^I0m@+tyxV`gyGH>eXq*NDO_=sj+<2t*YK3Np1ugP;x@$pJl_u8$mItMv@U4<*m z*IU!AJ)J-VUBZzxm_=1uKcR#FOgmgJCRqVMM8q(8^`xklZfDh158!g#hu#n-sua3> zQDx5baV(&RC% z1u9*JLv$(|a)0L;#?^#`qucdnp1yazV^OW5pPb}Cuh3Mg+5&zE$!Jc=LMRMg=tZaA z>P=UkMLTikcL1)M8@3uYhH&OP2@>BvV;cd&P~W8tNp^dN=;rDKX1c(Rpnqu*Qprd= z`D#`m9C#r54>NSABip9*N%twc$B@GhlM13*5t%6mUpb&GMVz@7I^}c<*FrM1F3%Sj zkX34*qf-KRWmIXahAo0*loTT^R0|Q2E zM)4k$txt_=v?#EyCO)d6Io0}|7Vl7m1>%7EGzWOJS-Cm<&lBLZHAcUiI^AHOUr{G;HiU52Y$i1W!ScFgrWS z$#uczjKF!~$)ijn=&UbgDQahm_;(QJfUvgU?laG@QXCXa8%(^0rc=|G%5{?6bLYXE zvI}ruoujAm%+|~zgE{ZS9;DC$*)aFj=fJ#Ryv}?$464-$(@T4HY|F4qVP*~+O7k$g zG}MF>K6slg2J^BisQxyD&2*&-7MWsvi7oAt3IGZ#K8Q$-*jD9CH3k0zJHijuL@yJ# zS0ZD!H@VuWvXxm-^h|9|1BuSFc{T&h9U`x{+_qGtBDD5dUapAl;XHxmC%%-~^ZXPe zPYSvAJYtf7K)h89JLzIr$#D4PX$j>|l98Is3u3+?I3SoEknM1oNxFnahQpKLu$Ly! zfygfq;=PV3;`fwH0Uz*=TU0VrTFx>j9X-4kkIV9@q7c0Yt6`(4pw*Ln!B@vpg{HSw zb8G@;6c>VX3WlwL^07WkOI&f^@pm1a;{c?3I(4}p?&gMOr1}KnJ!#Jv*<9Bu=c>xV zzSO*Xh37im-Ddd(AmTwNR)gN`Hjq&?G+`-n2=v|fgNxh*3lQL@7azs3OPU9nz_>)) zA$3pi_#_-xk24hIwti*my86y&2xS40LnbYr;47MtA>`v5VU_5YDPIK5bXsay{;*%uP1-??nr5pKebLZ&bIW{fdBA4o8?8hl4+Pg3` z$6{PI&uN1=QPPT4!{dwWl2`G91Ee7=FZKP12YP7+v@~c7q95zxC!t`2|1$8u_-4}X zkgV@TTN+)*wGN>RKdeVF;G4eyFfcWFprYiUN7*F!A08ZNk2>&(G8;m}2KckdBN;P2 zz`?V&o6lHvVUHx+UplyAoz-d$K%ZDi7feD4rdzBx41U__qZZVu6XeywxEy&U;Bm+G zR5f4LE1^YYI_D$iR#1<+`dxEUok$I;KTsZ5KG2kliPuHdmbg?D9*ZOpwd)^K&{d7| zZU;S2BJ+&`z5PCTj~W|q_V8J!7i2b|i~{mYd=`8ut_0F+;|OHYYvxDeefBJXz8tR>cxdHMp1Hj;!{c*>(2^Z z0DUUQhhid|3}SpQ-qox?`=$-(h2#qMbG4Kp<2qqFY;!WI&!{;|HzvDF5A_l@Tak!Y z=vwH9HRH39_`wLZ$T-+=6t+rd9NIwMZWJ7pT18_1MYMm@(0nsmI707>O$Qn_Ajj+5 z|62|_tkioFY?%1vFkm6EkLtLIyoY%e$yfGh@c`Llca0iya6hW+5GW3J9Y_U|YfjWU zLjEDG20zVEw#=3!P8E0!XD&A0r6dnnPvzT<7f zcv$`rLb7$dS1i#wk2j@M-cer7ZX&^G!~-04vQ*dkZOsOh9yfL%^6Aya;nmx27_v}T z{?coJ{V>iL&B!18l|}pD2N1!!)G>8IeSKpji$SM2@}8?N#!u7@Vl~Oddo{K63?@~4 z4Eke^Ce??jdDb;O2ubPYPiiVX(^<@n(695KlQ?B)NNIy?h`U}q9qWAS>)RCHMUCp zMXrPYg)PnK6Ts26T)asp8E84=A|TdAe%aUX05dsp8n|PpNpvN|0XvB3{lQxi9ipa# z;i(Rt_TARC3zHscK@bb2fK?JqENvy)0dmaQMi>BU4fNf0PD%&GPFFKS6ve;Pq;kdp z7yph2?)L@la8?}@&C?LQKz$c&lu}HvWe!M?ygV%Vy3w`ScuW$U*okuL!LU9a!}Xk? zyjV}-8_s3gNO(UhXIs_`ZVca;_l`AySl@b;=x6c+L-Il^7}(4yC|lkQwVv4q@!_4H zw+bRlWM+t|Gx+cVN`kEkEyrt_45S9##JVXx!msSCn!K5{^J=;db3D$cq*TG(jpLXh zCK*sjjcM(aV-RgM4E!RR?NnNB|8-WSeF}lO_lhD4yy)4K1)>zXwz_^fy;WCBBe2Lq$`%YD^YVl6_XF% z#%OA0rxj}sz?%IomooNU>q8ZXQcQU@QVptGaYe|2xlR6RPI_;tJaw8-g&9>2Oe*+# zb?a1aV%8%uz5|pIY?`EcZMEvqe}g9MzvZOwqdwk2z%eq1o;GupKUbo9usPH|%6E4B zqk|HEUg|Sq0+}@d6|kt3II*=QVq1tk;Z%vOx^M_IfshSs9>hYOVwSOVGVbGQtPpB_eXQ2+DW@dnm<6rje8wpX_oS z?DgS}UB1%wG7t2%)CQ@H)e0#h`kC--JvjL?+L%yFxb@XG=wIl~ko-6C_CkF?wAzSF z9v%`@rCMh)HF8z@_sy6Om^FkoAxlkUGf!FcwUd>gpe%Gnb+zrYgYhp`AZ71~5Tbwr+_4XQipNivs z%>YQJH@|E#9d9mr4Jd#0gNMIbu{mavlGN>y2+3C=;Wv@9O!kWp9MIzRzWtoX^1XN&ng$(i6P?7k!dIvA)pXnK zI3wwJD-uY6ls$kWE87ekdb31m#5CV z?q*!Ct-y&dYQcagzYOq#*!=esy?ipJDcqcw!8fuZ9QVJQym}qJ8~|H6wi#mTFilT6 zhjFWvz>Y^E=V;I>jd`{Zn$EF~9Uz5B{H%HlLlrE0ve1fg;93bvu->%3f4ku-1CH%LOsZ*^W`O(ft@e> zUE0h=^dD^k5}KcgL}>O|xW(dCM>t}f$AyHitpmWLYbvil$Hg?_$YPViR1R*p2<*cY zeWA$~^^L9tug;*Pcp&a#!w7Ls`jH*S0(ccmozrseYj~V<-G?iVJ*q^Sdgt2e=X@6i zwR)jmP+!R00$K$LsJ?{X3&_VFs|B|Lk~-n1%J=PVmtHGjA`JaLJa8Zqpn{W19yY;b zvP;a^%w>GT#%G__++8$(TY>RFE%-pY?_ul&UTOtb+SR}P?cj&+pC3IQJb(7&2LT)D zItJRxfiJ-c$SlfzkydYt`QW1Rjr3r5IwK>kLm?Fg4x@e^l3e8TN}VJKrmwg(L`&cX zLz{|r=SKu7u`%f=sLWXahV}8Kv8m4>Sk+1sgj}Tqi(Pvi(2Kp>cy{7J)KF|F(4A7t z20Xz#wF0i(&4z>qG+*E})wOhF!eqw%P@v93{Ygy+*PcXedd4nYPVd_EDZ1+f zixnIe9niDHAyI58V(ZsX>2Pnb@2b5yoqA|>U-xt+-DOpXEOkAuiT3Np#0RdfR<1D;=Pu2zn!)^? ze`LkDrSDUw1C6*I;4)aG+eUi_R9kY|L3Y)0s>FD7|YW5Ui4f4*enOQmz}mE zfY9niIgfviMfs0h>e}OhGn%@06bdte1E+@cZzCheFedWZB5(-g`QC*pzR>PR{u$LG zLqYp>YY>n(U7Uzu+?A4XKhc}w1@WK*-_YQU80W&FB>ywy39T44t)mvXajY>9f1SXV z<%t@m65qXn{-+X~x`|@-i4;=pX0*D7X+%6uH=~kkq>!sqM+?e`9w4Z1K`~n&CGIg& zZ$xwMS23?wh_x7y!(q&L`y7G+^D&?mIY1v4fI%xxjf>Rq^^RlFT@?9ShW@ZZp{Um& zpYm-Eu}%o9R66-MhT`uCcK#H7gDM@KYdrvK4ZX||rT=D~HNV+zrL&RRA%Sd-fpL+o zJ1^(pCfbE$Zo6$_QMmd~DYDgVh3BY!Lv;$xwaGkKDln^Jy3r9d895_e_7NdF5CsS1 zI*;nh80krJ+wlmAM9F`uV+kYKeY6v(GW0vcaDc4JozLvm5Q23H07S{aY4WwAt^8dE zm!-ag9WWez2|E$8YAw$d6Qq!4-36@kLL+bthy1AtLk?(C6ViIC^rxuojjV1b>7@TqSH>=fK8?FvL9fZbo*Ira83$X)04VH) z%mW>|njA#r-QfRwZpP6R}NKmiGa+75?(r-$I6 zHXOc!Kt*+qyl#PtaQF8{95K4!FB@oJQI<>I_R+(p7EHsZ1*k~xxa*?_4M=o#3XuIO zD#D9M>%9n#+K17dqDQ!UZF4g6&^TUg-F@9K>4r;Q3#V;nv4t(D>y}cskL?4!Gd5Atw}; zahEnCu7kQH9~fs?E2aAmQV7bRxQzPwwJ!l#{q8I4xC$S-RWP!Hn~#hi=_W*KpbPPe zevzwEdryG0cjyzoi$8WFkvL((o)*%aM&;#>!h;rTQ{IkLqg=XgFxmW z2u+RFs+Hkb$yr@pNBmm)a#_v~@r$@=@^lL1Y(Wx0N`~ZMY79S1u)8Tsceh~3w*-7y zr%p?x-DvhFkaN_?kzLiIlVi+NMWaGkPcY0;N3E-wV^>|=Box=pO^bQicurIg8>Zwv zrURiotVtcXoF3BkYP!u;-AP&oY3Pl~L>Nn=Y8Z3si@MtCyQKm-s(i*xPY6pi6XP7> z3@SDEq#i^ZuUnWbSlxz4RlRalBCy3(pqturTIwO=1&E!!f>nNfeM+e{M%DU8TdsFh zA9YndxX`TIbgMPcW;IIdjrG#4w43b=?(e&IHrqoUACtvq%=PzGO$=o%YnWuzWgD2b@OF_?*?NCl^fEo``MuDuHJ+Wi=2mzRk=tU`=UlxM7ghPi&&vzA z>Gx&-ZspbPBQ1EdMcruO;}0J;jSRprC>God&7jc=#3<^wc3H0r_*&nwnofC~ zK&Byx-%r+UM%j8I0?&IXGW%H?q!GzpBY z#33EMZ%EN~Psn5VyMWl;FY7>c))s8n zp%ziI>o##bC|2j<_M7pdrCq_(X9bXrYi33mrz!|(r7+}+^PEvjYf=uB7U(v;5}br; zdM8Ak4*@2CAC3rXMceAw?GE8wkV-Y~w$Wf4@HWLdmaFQjk+>e5Foogj{0y9$0Rdfu zYzyx6tW}aX@?AGkkA1fh2Ncx&eti78csP%Wc`kH%sm?vvddzI$O1|x)vs9?qSVO@_ z@R`oX!DN7_F*SyiUx;F{_inFbUXWTOhDOr3Dhb%KDfX-DCoHnCiQ+4}q&PmD5R?#oD*i2KyzM7u0FkoY9o^vxdusgL==jwE?C0k~FHp#k) ziA_$J_0gVb4w#0>LaJf%1SA*gW!1JRDoVy6GOHobTqMtha~AByzJTqcPQ3aI8kkLp zQlz8SSNDZ~l6+Zty|ttVguvZw^m4OSRgDC zB=pE7m6p)|0s#rWi-eZJL%RiBE%CK%1u#qqfHQ9!cyDdi!DOtivm>}h8p%+~CW5Re zNg;XQOtUwTkO@w%6+6ia@IjESS93^go`EN%6TddgS%-nX3k2;+@+p?lev+Znk3WJX zb|LeUqV?62K)`e=dzq_!3aUl*!J~5NS{R08uPE`vJ}rME8?b3)C;2wQSmg-s{*NB=zStY(ez6-`aE&VyQLT&>zKh?L)a9ztL^jw7&mV z`_NzGwj0rdWn8-tW1%ADS*-_YJe}=MB!TZ)T|Q8g2G57)Ly!pw6xJ3%N_>5$QI={v z{6Y-=$e1w2E@XXK?-u)w)|cJFLbnoC3hycw?mVhFdvs~xF=YCt9WX%d3^9|;(1FW! zbQF+$Pc5|C`HBOK5yaHEtuy|)QCyI>;snT*=3D4hs5J<~6^&`Ly$;S^r{JJeIBOwK z?YChSm~E$B4e)7N%_qgO+xZcaxZLf%+I}5|BXk^NUL;PI&X9PVYy;wqEpTPXnL4es zgERwE(SSBnSBH*AmAxbw3vdW$2pa=EQ@;gFf`B%j;S@KKfbZ9TZA}WrOl;DDl?}>r zlB#mD)F?$!%A|JmfZxJM9OrG$U4R@w1hWPFmmbHfwHXCu{BaWzs(CHv%kfOaa7H807a2MkQUnACohC{m^d(58vI1l9uq@8sq%V%KKn=4o2F@P z)xBCj*BY#)2G`MWR5+v{{_DM6La*Qj>UQn(KUQi|z7V~8uVF$WiJH1_oM*L}D#97# z#j307y+9nA7ii-Ma+t<1qAMIm_*Der6MAlut3;i#D}q8#HgiAsIvKch=5@$mYK+yw zv6J(-OC25N0~LZ4fq_d&impZK*erFnHDRz1qv<%I2;@<$F|q-;{aJ_&FGg465uoc$ zFpcN(gIeg+cUIcwNj44%CplW;Nx`|_`%Gkqx zGw8FX-9~>;dD4Jjz9ED6q z-Cb!;XzTnGMV+! zwC#it8=@)if@LJ&JttWd<#av`nTrhjUsa?Khl7HwA$_AnKq;LJ3jdSZDYBWqr0PBP z;Tds0RJ^Ktg0=h*)`q9TTqnBU_f`f)-(&}1Z_PX$Az@M|@c}yu@Muc#{hQ^BVr0s= z@xQ|>8bz?-oi!P5j)iCny5RAqV*Hw6M%?6Vg8;V#_$IK8(N|+a$e(r_gS0*_PET`* z$CIwLwRr7##ul5SP0f8kv1>*0*hbwCH$|<{#p{(dxyWvDg&p5%$xFLl+p>q+5)D_7 zn?B04Y7qA0OI?ri6V&@ke)`%1#<=X1$--cy=oIrI>oi}k8$pXvU|oHx5&{BFC+bYy z^>S6mM}=ja;e=T(9af4n70YuO<)kLXqr*v^9ESBQb%fFmDleP_o)_7r$Rf!zd^IyH zjK;DXQ1OSX#x>p5 z#d>3xdueusf_XV-ZLDKf(dzKLVnw&J&UWW&_s}MwAD4acPH)~(x7n2yXNWQ)^A;lz ze_;o(q(cdjt7T0h4QE5hM(D!H4WTEyZfTMObnrM=|Jv%Ac&6!|3dlN*NKVK9tE!&M&|La=w6CR*)^>G z5{45VELRq`sXpwwrZJZeBGDXcc>Oo|@)u|HXZbS91(l%)YZ+?vs7`w&S|Nt z&K8~Fut&<)Gnw57`YrsRheP$LVwlYHi2&?NRhPP10}IaF6uOqdA(TM5nh`&avtR;I z;#82_3dXSP86@^v4 zWNUr5L)W?v#pLv2(7pB=5~%6?^zK$N63QN9s*TW?i^nTom~l8~A*|*#Q`(u(UH?Yu ztVSBT0+nG+zfl^a#r>5iOju63b}a*)2vQb(>swMd9uMvwYVw!msGLY46D2RMM?LJPSg-XL5_R7o^2S}mhvR&$`b(Na{~c_#*#jYfS>i4d7V z6rr<;lFuvQ7(mXqIszciBnLtwUTU~6N2_cp+R*N$_#5Q<%t_Q8e@pQA3I6aOY=kE^wYQRDBC%icXguIivdrCTMm z$5mRB&Yp+sr#C=uIbC}lSCu%-Ulrl1dJ(w)#xh8rL9PW`+fW_iv0Sud%u%G7~qbDTg?Ib1(ow{415-Sa4Nbc-}H^$D#aM9!Qd z0xkF6q5my*`G?uRO}GP_Vh>y;%)SwW;D&RoTfUmiU{``*sD)wJ0E!_3$58J&)`ng- zfoAwsxtZDcgjQ|$yZ!O^#pc=#=;M!WE2&nynZWN-4QB#fR0WfYfAXZ*Sd6m4fUX2O zu2vh=IMS61O-zb5aT#5uN?_12@}~_w#K|5~_@5X5x5!7i66Vz{d9^I3>fSd(KOSw< zV41iaaj+q4(@ozs<{fxZt4h>G$>jO~__>nV?$G23!!K2RY(~KJ;ih)Hik)045nEry z;2sGM(K)0wAL}cza61IeziJ9I*e*}E{*vS~^(h3?nJe~WVs2gllf_?4SP*ZY6dUM)gl29DsEd_5J7;~Hw|vcbk(oL{0Fv%h&N@W=m=3+2VZ4`Y5P7+fUQ?OpA7w6au-zwB#908I?rLqBysy^l3SHYN zylwCNWU^&MJKc%c-X|3|feey7CH(t!OfhKH&VsXE8$b2fqIOvnKGqH|(8@d8*2zk9 zP$Unk#>@P-l)ljHs(oU%fN4DL7I4?yQ{0Yd0-{g=4*x_W5lEU5d&5x(t4ca-FY7sT z?;K;{D&a{7Ow0Py>qS_^Rv{x_QVN66vl-|@$$607+ga6`SI9vtqZ%{T>}&+MzAXijWVHH*)x`C)Ad&78dj1?u4l%vv;Co+PMVLTLZj3$AcRd)SI(a@A; z)anp9;7RCIGvk6O!e)jnpfca!XemmZF|($UW+W>F$1-yQCia$(U56r0$_q{`Rl?6i z8p}mVDNI?MLcBthP|zi0OlN>$A0P;AolMHn8^t>2mulW-pUIm7>|V9L>LyVx26F&M ztbpBA-8*;Sx2hM#JD@-^eRVs}s%X*FjMtN}jh;a5mRyPKV+vlX3mk(JPF*+7R>h>w zDJ~wqRP)wUmeR-z3{1wG;Yv-=A}!E%Kob}SV`C`o>^O=9h$PU@f0u`goYpOe-jC{UK zs$Zj0oWeR!vZ@%_gP_Xu0QMR0UU+6I0Xf}}uCeApaXKh4-_taYVzpbE(6FeM3KUuV z<-up$e|_-R&px|<|1WnR+}(B%bHBo{M?N0~b$sQ~M9>q7uW5Dz7|;evKB?)cy?9p` zmwlMLfkR$R%qP~7{8-C4l!+H3$@r*4K?f{Gz|Rf0*_2t(MEZSWbbS#{x6`=&+D`TQ z?Pi6JRtv-3ubuvOQ~!KMS2i*6YcFG_qdV56Ksbnz=FzCeS2-}T(%pfe0PDVbC z^*FeqKw41gd-sV!h?QNqy(2hhRoJ&lkWpQ8JB* zL(e;jS=^|8VZK6WiV-Co&^f`+-kv@b?$9BPW2`~i&Hs}OeC}EnAMRcFe7jN?g zy|}{Ri-PL_OTYIiY$R#}gQu+pYsBLZa4@^2k+YabkZ6Sl+z+OpS(3t!sy0XZx4F7w zK}Of5UYt*~&Ptq}s~Ov*1>hHKV*Rwx(pDpA988U2wd2?|sTEwDuYvoc#W~jfIl1(f z#nL-K`tWBx;%%O#H9WS>=;h%V&$AS#LA-re5wJAan=pB%g;o|m(w}IM45%Y=OXI1O z92@-VVmC&9ys{f){St-p@rBMsD^B*b|=ZoSkNL>28NswX^i{N&>Fkth% zuF_#ndZv=95s%NLWK7SC(x|tLzD)^IEW_w*xvJAcy{6z@$>Od}Z=Lacs&ymt@;GowO*haimN%lOIjRse!0%DhmM;os)mZf?%sRw*9Y5wzIXq#`*;8HFnWD~ z6k=`eEZ_wJPABj^fD-2U*txU(mJSNVoGB%&Rd3C+#Zqn0ZotL+iOZVsEzdb(HamQ}QJ{B@4BmNND&F#hP-Hf!lg*o$roP{}#{;My ziX}J;YAKvMAtltS@=nFSVzT2&xeQpR6~=!LoYNoJ^>WTlB)HMJM(U&;*SKZRuXm?T z5hw1PbwJbn`h>k{emz2-t>>!3ox9SF^4$h|w?@LN-@&>-t6j`m7OO6&(+*BaadCeC zP3_A>v3>nJ6Nw^&yXxM?sbwRD(ijs`#6n*MK=d>%L`kBJIwps+2BCN zu;5c`6c9}%vmBh+lniOe{aJ|)SJ_ftvH*h7rY~7#pR$H%A%N8W7XXPa zK9c@%PJe2yV3W%Po!B_E9S}i_93AR(9%gpJ((ZAZpkCOzo0M2t$gTX_o2*ICC9MIo z675gc$V}Fo&T#3Q?4DNn%%wECgXW(