Skip to content

Commit e9e8992

Browse files
committedNov 30, 2019
v3.3.2-dev4 freply, fixed alias bugs, alias and snippet looks
1 parent faeaf49 commit e9e8992

File tree

7 files changed

+172
-166
lines changed

7 files changed

+172
-166
lines changed
 

‎CHANGELOG.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ This project mostly adheres to [Semantic Versioning](https://semver.org/spec/v2.
77
however, insignificant breaking changes does not guarantee a major version bump, see the reasoning [here](https://github.com/kyb3r/modmail/issues/319).
88

99

10-
# v3.3.2-dev3
10+
# v3.3.2-dev4
1111

1212
### Added
1313

@@ -20,10 +20,16 @@ however, insignificant breaking changes does not guarantee a major version bump,
2020
- "enable" and "disable" support for yes or no config vars.
2121
- Added "perhaps you meant" section to `?config help`.
2222
- Multi-command alias is now more stable. With support for a single quote escape `\"`.
23+
- New command `?freply`, which behaves exactly like `?reply` with the addition that you can substitute `{channel}`, `{recipient}`, and `{author}` to be their respective values.
24+
25+
### Changed
26+
27+
- The look of alias and snippet when previewing.
2328

2429
### Fixed
2530

2631
- Setting config vars using human time wasn't working.
32+
- Fixed some bugs with aliases.
2733

2834
### Internal
2935

‎bot.py

+13-14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "3.3.2-dev3"
1+
__version__ = "3.3.2-dev4"
22

33

44
import asyncio
@@ -612,7 +612,7 @@ def check_manual_blocked(self, author: discord.Member) -> bool:
612612
return False
613613

614614
async def _process_blocked(self, message):
615-
sent_emoji, blocked_emoji = await self.retrieve_emoji()
615+
_, blocked_emoji = await self.retrieve_emoji()
616616
if await self.is_blocked(message.author, channel=message.channel, send_message=True):
617617
await self.add_reaction(message, blocked_emoji)
618618
return True
@@ -770,7 +770,7 @@ async def get_contexts(self, message, *, cls=commands.Context):
770770

771771
view = StringView(message.content)
772772
ctx = cls(prefix=self.prefix, view=view, bot=self, message=message)
773-
ctx.thread = await self.threads.find(channel=ctx.channel)
773+
thread = await self.threads.find(channel=ctx.channel)
774774

775775
if self._skip_check(message.author.id, self.user.id):
776776
return [ctx]
@@ -791,16 +791,18 @@ async def get_contexts(self, message, *, cls=commands.Context):
791791
if not aliases:
792792
logger.warning("Alias %s is invalid, removing.", invoker)
793793
self.aliases.pop(invoker)
794+
794795
for alias in aliases:
795-
view = StringView(alias)
796-
ctx = cls(prefix=self.prefix, view=view, bot=self, message=message)
797-
ctx.thread = await self.threads.find(channel=ctx.channel)
798-
ctx.view = view
799-
ctx.invoked_with = view.get_word()
800-
ctx.command = self.all_commands.get(ctx.invoked_with)
801-
ctxs += [ctx]
796+
view = StringView(invoked_prefix + alias)
797+
ctx_ = cls(prefix=self.prefix, view=view, bot=self, message=message)
798+
ctx_.thread = thread
799+
discord.utils.find(view.skip_string, prefixes)
800+
ctx_.invoked_with = view.get_word().lower()
801+
ctx_.command = self.all_commands.get(ctx_.invoked_with)
802+
ctxs += [ctx_]
802803
return ctxs
803804

805+
ctx.thread = thread
804806
ctx.invoked_with = invoker
805807
ctx.command = self.all_commands.get(invoker)
806808
return [ctx]
@@ -872,11 +874,8 @@ async def process_commands(self, message):
872874

873875
# Process snippets
874876
if cmd in self.snippets:
875-
thread = await self.threads.find(channel=message.channel)
876877
snippet = self.snippets[cmd]
877-
if thread:
878-
snippet = self.formatter.format(snippet, recipient=thread.recipient)
879-
message.content = f"{self.prefix}reply {snippet}"
878+
message.content = f"{self.prefix}freply {snippet}"
880879

881880
ctxs = await self.get_contexts(message)
882881
for ctx in ctxs:

‎cogs/modmail.py

+36-9
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import discord
88
from discord.ext import commands
9-
from discord.utils import escape_markdown, escape_mentions
9+
from discord.utils import escape_markdown
1010

1111
from dateutil import parser
1212
from natural.date import duration
@@ -21,6 +21,7 @@
2121
create_not_found_embed,
2222
format_description,
2323
trigger_typing,
24+
escape_code_block,
2425
)
2526

2627
logger = getLogger(__name__)
@@ -155,8 +156,10 @@ async def snippet(self, ctx, *, name: str.lower = None):
155156
val = self.bot.snippets.get(name)
156157
if val is None:
157158
embed = create_not_found_embed(name, self.bot.snippets.keys(), "Snippet")
158-
return await ctx.send(embed=embed)
159-
return await ctx.send(escape_mentions(val))
159+
else:
160+
embed = discord.Embed(color=self.bot.main_color)
161+
embed.add_field(name=f"`{name}` will send:", value=val)
162+
return await ctx.send(embed=embed)
160163

161164
if not self.bot.snippets:
162165
embed = discord.Embed(
@@ -186,8 +189,11 @@ async def snippet_raw(self, ctx, *, name: str.lower):
186189
val = self.bot.snippets.get(name)
187190
if val is None:
188191
embed = create_not_found_embed(name, self.bot.snippets.keys(), "Snippet")
189-
return await ctx.send(embed=embed)
190-
return await ctx.send(escape_markdown(escape_mentions(val)).replace("<", "\\<"))
192+
else:
193+
embed = discord.Embed(color=self.bot.main_color)
194+
val = escape_code_block(val)
195+
embed.add_field(name=f"`{name}` will send:", value=f"```\n{val}```")
196+
return await ctx.send(embed=embed)
191197

192198
@snippet.command(name="add")
193199
@checks.has_permissions(PermissionLevel.SUPPORTER)
@@ -782,10 +788,32 @@ async def reply(self, ctx, *, msg: str = ""):
782788
async with ctx.typing():
783789
await ctx.thread.reply(ctx.message)
784790

785-
@commands.command()
791+
@commands.command(aliases=["formatreply"])
792+
@checks.has_permissions(PermissionLevel.SUPPORTER)
793+
@checks.thread_only()
794+
async def freply(self, ctx, *, msg: str = ""):
795+
"""
796+
Reply to a Modmail thread with variables.
797+
798+
Works just like `{prefix}reply`, however with the addition of three variables:
799+
- `{channel}` - the `discord.TextChannel` object
800+
- `{recipient}` - the `discord.User` object of the recipient
801+
- `{author}` - the `discord.User` object of the author
802+
803+
Supports attachments and images as well as
804+
automatically embedding image URLs.
805+
"""
806+
msg = self.bot.formatter.format(
807+
msg, channel=ctx.channel, recipient=ctx.thread.recipient, author=ctx.message.author
808+
)
809+
ctx.message.content = msg
810+
async with ctx.typing():
811+
await ctx.thread.reply(ctx.message)
812+
813+
@commands.command(aliases=["anonreply", "anonymousreply"])
786814
@checks.has_permissions(PermissionLevel.SUPPORTER)
787815
@checks.thread_only()
788-
async def anonreply(self, ctx, *, msg: str = ""):
816+
async def areply(self, ctx, *, msg: str = ""):
789817
"""
790818
Reply to a thread anonymously.
791819
@@ -823,11 +851,10 @@ async def find_linked_message(self, ctx, message_id):
823851
continue
824852
# TODO: use regex to find the linked message id
825853
linked_message_id = str(embed.author.url).split("/")[-1]
826-
break
854+
827855
elif message_id and msg.id == message_id:
828856
url = msg.embeds[0].author.url
829857
linked_message_id = str(url).split("/")[-1]
830-
break
831858

832859
return linked_message_id
833860

‎cogs/plugins.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -539,12 +539,12 @@ async def plugins_registry(self, ctx, *, plugin_name: typing.Union[int, str] = N
539539

540540
return await ctx.send(embed=embed)
541541

542-
for plugin_name, details in registry:
543-
details = self.registry[plugin_name]
542+
for name, details in registry:
543+
details = self.registry[name]
544544
user, repo = details["repository"].split("/", maxsplit=1)
545545
branch = details.get("branch")
546546

547-
plugin = Plugin(user, repo, plugin_name, branch)
547+
plugin = Plugin(user, repo, name, branch)
548548

549549
embed = discord.Embed(
550550
color=self.bot.main_color,
@@ -554,7 +554,7 @@ async def plugins_registry(self, ctx, *, plugin_name: typing.Union[int, str] = N
554554
)
555555

556556
embed.add_field(
557-
name="Installation", value=f"```{self.bot.prefix}plugins add {plugin_name}```"
557+
name="Installation", value=f"```{self.bot.prefix}plugins add {name}```"
558558
)
559559

560560
embed.set_author(

‎cogs/utility.py

+94-128
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
from discord.enums import ActivityType, Status
1818
from discord.ext import commands, tasks
1919
from discord.ext.commands.view import StringView
20-
from discord.utils import escape_markdown, escape_mentions
2120

2221
from aiohttp import ClientResponseError
2322
from pkg_resources import parse_version
@@ -161,28 +160,41 @@ async def send_error_message(self, error):
161160
command = self.context.kwargs.get("command")
162161
val = self.context.bot.snippets.get(command)
163162
if val is not None:
164-
return await self.get_destination().send(
165-
escape_mentions(f"**`{command}` is a snippet, content:**\n\n{val}")
163+
embed = discord.Embed(
164+
title=f"{command} is a snippet.", color=self.context.bot.main_color
166165
)
166+
embed.add_field(name=f"`{command}` will send:", value=val)
167+
return await self.get_destination().send(embed=embed)
167168

168169
val = self.context.bot.aliases.get(command)
169170
if val is not None:
170171
values = utils.parse_alias(val)
171172

172-
if len(values) == 1:
173+
if not values:
173174
embed = discord.Embed(
174-
title=f"{command} is an alias.",
175-
color=self.context.bot.main_color,
176-
description=f"`{command}` points to `{escape_markdown(values[0])}`.",
175+
title="Error",
176+
color=self.context.bot.error_color,
177+
description=f"Alias `{command}` is invalid, this alias will now be deleted."
178+
"This alias will now be deleted.",
177179
)
180+
embed.add_field(name=f"{command}` used to be:", value=val)
181+
self.context.bot.aliases.pop(command)
182+
await self.context.bot.config.update()
178183
else:
179-
embed = discord.Embed(
180-
title=f"{command} is an alias.",
181-
color=self.context.bot.main_color,
182-
description=f"**`{command}` points to the following steps:**",
183-
)
184-
for i, val in enumerate(values, start=1):
185-
embed.description += f"\n{i}: {escape_markdown(val)}"
184+
if len(values) == 1:
185+
embed = discord.Embed(
186+
title=f"{command} is an alias.", color=self.context.bot.main_color
187+
)
188+
embed.add_field(name=f"`{command}` points to:", value=values[0])
189+
else:
190+
embed = discord.Embed(
191+
title=f"{command} is an alias.",
192+
color=self.context.bot.main_color,
193+
description=f"**`{command}` points to the following steps:**",
194+
)
195+
for i, val in enumerate(values, start=1):
196+
embed.add_field(name=f"Step {i}:", value=val)
197+
186198
embed.set_footer(
187199
text=f'Type "{self.clean_prefix}{self.command_attrs["name"]} alias" '
188200
"for more details on aliases."
@@ -901,33 +913,32 @@ async def alias(self, ctx, *, name: str.lower = None):
901913
embed = discord.Embed(
902914
title="Error",
903915
color=self.bot.error_color,
904-
description=f"Alias `{name}` is invalid, it used to be `{escape_markdown(val)}`. "
916+
description=f"Alias `{name}` is invalid, this alias will now be deleted."
905917
"This alias will now be deleted.",
906918
)
919+
embed.add_field(name=f"{name}` used to be:", value=val)
907920
self.bot.aliases.pop(name)
908921
await self.bot.config.update()
909922
return await ctx.send(embed=embed)
910923

911924
if len(values) == 1:
912-
embed = discord.Embed(
913-
color=self.bot.main_color,
914-
description=f"`{name}` points to `{escape_markdown(values[0])}`.",
915-
)
925+
embed = discord.Embed(color=self.bot.main_color)
926+
embed.add_field(name=f"`{name}` points to:", value=values[0])
916927
else:
917928
embed = discord.Embed(
918929
color=self.bot.main_color,
919930
description=f"**`{name}` points to the following steps:**",
920931
)
921932
for i, val in enumerate(values, start=1):
922-
embed.description += f"\n{i}: {escape_markdown(val)}"
933+
embed.add_field(name=f"Step {i}:", value=val)
923934

924935
return await ctx.send(embed=embed)
925936

926937
if not self.bot.aliases:
927938
embed = discord.Embed(
928939
color=self.bot.error_color, description="You dont have any aliases at the moment."
929940
)
930-
embed.set_footer(text=f"Do {self.bot.prefix}help alias for more commands.")
941+
embed.set_footer(text=f'Do "{self.bot.prefix}help alias" for more commands.')
931942
embed.set_author(name="Aliases", icon_url=ctx.guild.icon_url)
932943
return await ctx.send(embed=embed)
933944

@@ -952,56 +963,13 @@ async def alias_raw(self, ctx, *, name: str.lower):
952963
if val is None:
953964
embed = utils.create_not_found_embed(name, self.bot.aliases.keys(), "Alias")
954965
return await ctx.send(embed=embed)
955-
return await ctx.send(escape_markdown(escape_mentions(val)).replace("<", "\\<"))
956-
957-
@alias.command(name="add")
958-
@checks.has_permissions(PermissionLevel.MODERATOR)
959-
async def alias_add(self, ctx, name: str.lower, *, value):
960-
"""
961-
Add an alias.
962-
963-
Alias also supports multi-step aliases, to create a multi-step alias use quotes
964-
to wrap each step and separate each step with `&&`. For example:
965-
966-
- `{prefix}alias add movenreply "move admin-category" && "reply Thanks for reaching out to the admins"`
967-
968-
However, if you run into problems, try wrapping the command with quotes. For example:
969-
970-
- This will fail: `{prefix}alias add reply You'll need to type && to work`
971-
- Correct method: `{prefix}alias add reply "You'll need to type && to work"`
972-
"""
973-
embed = None
974-
if self.bot.get_command(name):
975-
embed = discord.Embed(
976-
title="Error",
977-
color=self.bot.error_color,
978-
description=f"A command with the same name already exists: `{name}`.",
979-
)
980966

981-
elif name in self.bot.aliases:
982-
embed = discord.Embed(
983-
title="Error",
984-
color=self.bot.error_color,
985-
description=f"Another alias with the same name already exists: `{name}`.",
986-
)
987-
988-
elif name in self.bot.snippets:
989-
embed = discord.Embed(
990-
title="Error",
991-
color=self.bot.error_color,
992-
description=f"A snippet with the same name already exists: `{name}`.",
993-
)
994-
995-
elif len(name) > 120:
996-
embed = discord.Embed(
997-
title="Error",
998-
color=self.bot.error_color,
999-
description="Alias names cannot be longer than 120 characters.",
1000-
)
1001-
1002-
if embed is not None:
1003-
return await ctx.send(embed=embed)
967+
embed = discord.Embed(color=self.bot.main_color)
968+
val = utils.escape_code_block(val)
969+
embed.add_field(name=f"`{name}` points to:", value=f"```\n{val}```")
970+
return await ctx.send(embed=embed)
1004971

972+
async def make_alias(self, name, value, action):
1005973
values = utils.parse_alias(value)
1006974
save_aliases = []
1007975

@@ -1012,14 +980,14 @@ async def alias_add(self, ctx, name: str.lower, *, value):
1012980
description="Invalid multi-step alias, try wrapping each steps in quotes.",
1013981
)
1014982
embed.set_footer(text=f'See "{self.bot.prefix}alias add" for more details.')
1015-
return await ctx.send(embed=embed)
983+
return embed
1016984

1017985
multiple_alias = len(values) > 1
1018986

1019-
embed = discord.Embed(title="Added alias", color=self.bot.main_color)
987+
embed = discord.Embed(title=f"{action} alias", color=self.bot.main_color)
1020988

1021989
if not multiple_alias:
1022-
embed.description = f'`{name}` points to: "{values[0]}".'
990+
embed.add_field(name=f"`{name}` points to:", value=values[0])
1023991
else:
1024992
embed.description = f"`{name}` now points to the following steps:"
1025993

@@ -1043,17 +1011,66 @@ async def alias_add(self, ctx, name: str.lower, *, value):
10431011
else:
10441012
embed.description = (
10451013
"The command you are attempting to point "
1046-
f"to in step {i} does not exist: `{linked_command}`."
1014+
f"to on step {i} does not exist: `{linked_command}`."
10471015
)
10481016

1049-
return await ctx.send(embed=embed)
1017+
return embed
10501018
else:
10511019
save_aliases.append(val)
1052-
1053-
embed.description += f"\n{i}: {val}"
1020+
if multiple_alias:
1021+
embed.add_field(name=f"Step {i}:", value=val)
10541022

10551023
self.bot.aliases[name] = " && ".join(f'"{a}"' for a in save_aliases)
10561024
await self.bot.config.update()
1025+
return embed
1026+
1027+
@alias.command(name="add")
1028+
@checks.has_permissions(PermissionLevel.MODERATOR)
1029+
async def alias_add(self, ctx, name: str.lower, *, value):
1030+
"""
1031+
Add an alias.
1032+
1033+
Alias also supports multi-step aliases, to create a multi-step alias use quotes
1034+
to wrap each step and separate each step with `&&`. For example:
1035+
1036+
- `{prefix}alias add movenreply "move admin-category" && "reply Thanks for reaching out to the admins"`
1037+
1038+
However, if you run into problems, try wrapping the command with quotes. For example:
1039+
1040+
- This will fail: `{prefix}alias add reply You'll need to type && to work`
1041+
- Correct method: `{prefix}alias add reply "You'll need to type && to work"`
1042+
"""
1043+
embed = None
1044+
if self.bot.get_command(name):
1045+
embed = discord.Embed(
1046+
title="Error",
1047+
color=self.bot.error_color,
1048+
description=f"A command with the same name already exists: `{name}`.",
1049+
)
1050+
1051+
elif name in self.bot.aliases:
1052+
embed = discord.Embed(
1053+
title="Error",
1054+
color=self.bot.error_color,
1055+
description=f"Another alias with the same name already exists: `{name}`.",
1056+
)
1057+
1058+
elif name in self.bot.snippets:
1059+
embed = discord.Embed(
1060+
title="Error",
1061+
color=self.bot.error_color,
1062+
description=f"A snippet with the same name already exists: `{name}`.",
1063+
)
1064+
1065+
elif len(name) > 120:
1066+
embed = discord.Embed(
1067+
title="Error",
1068+
color=self.bot.error_color,
1069+
description="Alias names cannot be longer than 120 characters.",
1070+
)
1071+
1072+
if embed is None:
1073+
embed = await self.make_alias(name, value, "Added")
10571074
return await ctx.send(embed=embed)
10581075

10591076
@alias.command(name="remove", aliases=["del", "delete"])
@@ -1085,58 +1102,7 @@ async def alias_edit(self, ctx, name: str.lower, *, value):
10851102
embed = utils.create_not_found_embed(name, self.bot.aliases.keys(), "Alias")
10861103
return await ctx.send(embed=embed)
10871104

1088-
values = utils.parse_alias(value)
1089-
save_aliases = []
1090-
1091-
if not values:
1092-
embed = discord.Embed(
1093-
title="Error",
1094-
color=self.bot.error_color,
1095-
description="Invalid multi-step alias, try wrapping each steps in quotes.",
1096-
)
1097-
embed.set_footer(text=f'See "{self.bot.prefix}alias add" for more details.')
1098-
return await ctx.send(embed=embed)
1099-
1100-
multiple_alias = len(values) > 1
1101-
1102-
embed = discord.Embed(title="Edited alias", color=self.bot.main_color)
1103-
1104-
if not multiple_alias:
1105-
embed.description = f'`{name}` points to: "{values[0]}".'
1106-
else:
1107-
embed.description = f"`{name}` now points to the following steps:"
1108-
1109-
for i, val in enumerate(values, start=1):
1110-
view = StringView(val)
1111-
linked_command = view.get_word().lower()
1112-
message = view.read_rest()
1113-
1114-
if not self.bot.get_command(linked_command):
1115-
alias_command = self.bot.aliases.get(linked_command)
1116-
if alias_command is not None:
1117-
save_aliases.extend(utils.normalize_alias(alias_command, message))
1118-
else:
1119-
embed = discord.Embed(title="Error", color=self.bot.error_color)
1120-
1121-
if multiple_alias:
1122-
embed.description = (
1123-
"The command you are attempting to point "
1124-
f"to does not exist: `{linked_command}`."
1125-
)
1126-
else:
1127-
embed.description = (
1128-
"The command you are attempting to point "
1129-
f"to n step {i} does not exist: `{linked_command}`."
1130-
)
1131-
1132-
return await ctx.send(embed=embed)
1133-
else:
1134-
save_aliases.append(val)
1135-
1136-
embed.description += f"\n{i}: {val}"
1137-
1138-
self.bot.aliases[name] = " && ".join(f'"{a}"' for a in save_aliases)
1139-
await self.bot.config.update()
1105+
embed = await self.make_alias(name, value, "Edited")
11401106
return await ctx.send(embed=embed)
11411107

11421108
@commands.group(aliases=["perms"], invoke_without_command=True)

‎core/clients.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,7 @@ async def append_log(
235235

236236
async def post_log(self, channel_id: Union[int, str], data: dict) -> dict:
237237
return await self.logs.find_one_and_update(
238-
{"channel_id": str(channel_id)},
239-
{"$set": {k: v for k, v in data.items()}},
240-
return_document=True,
238+
{"channel_id": str(channel_id)}, {"$set": data}, return_document=True
241239
)
242240

243241

‎core/utils.py

+17-7
Original file line numberDiff line numberDiff line change
@@ -226,11 +226,17 @@ def decode_alias(m):
226226
r"(?:(?:\s*(?<!\\)(?:\")\s*)(?=&&)|(?:\s*(?<!\\)(?:\")\s*)(?=$))",
227227
encode_alias,
228228
alias,
229-
)
229+
).strip()
230230

231231
aliases = []
232-
for alias in re.split(r"\s*&&\s*", alias):
233-
aliases.append(re.sub("\x1AU(.+?)\x1AU", decode_alias, alias))
232+
if not alias:
233+
return aliases
234+
235+
for a in re.split(r"\s*&&\s*", alias):
236+
a = re.sub("\x1AU(.+?)\x1AU", decode_alias, a)
237+
if a[0] == a[-1] == '"':
238+
a = a[1:-1]
239+
aliases.append(a)
234240

235241
return aliases
236242

@@ -240,14 +246,14 @@ def normalize_alias(alias, message):
240246
contents = parse_alias(message)
241247

242248
final_aliases = []
243-
for alias, content in zip_longest(aliases, contents):
244-
if alias is None:
249+
for a, content in zip_longest(aliases, contents):
250+
if a is None:
245251
break
246252

247253
if content:
248-
final_aliases.append(f"{alias} {content}")
254+
final_aliases.append(f"{a} {content}")
249255
else:
250-
final_aliases.append(alias)
256+
final_aliases.append(a)
251257

252258
return final_aliases
253259

@@ -266,3 +272,7 @@ async def wrapper(self, ctx: commands.Context, *args, **kwargs):
266272
return await func(self, ctx, *args, **kwargs)
267273

268274
return wrapper
275+
276+
277+
def escape_code_block(text):
278+
return re.sub(r"```", "`\u200b``", text)

0 commit comments

Comments
 (0)
Please sign in to comment.