Skip to content

Commit f6044de

Browse files
committed
fixed reaction / delete
1 parent 52ec750 commit f6044de

File tree

9 files changed

+145
-93
lines changed

9 files changed

+145
-93
lines changed

CHANGELOG.md

+5-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.4.0-dev6
10+
# v3.4.0-dev7
1111

1212
(Development update, very likely to be unstable!)
1313

@@ -25,6 +25,8 @@ however, insignificant breaking changes does not guarantee a major version bump,
2525
- New command `?freply`, which behaves exactly like `?reply` with the addition that you can substitute `{channel}`, `{recipient}`, and `{author}` to be their respective values.
2626
- New command `?repair`, repair any broken Modmail thread (with help from @officialpiyush).
2727
- Recipient get feedback when they edit message.
28+
- Chained delete for DMs now comes with a message.
29+
- poetry (in case someone needs it).
2830

2931
### Changed
3032

@@ -42,6 +44,8 @@ however, insignificant breaking changes does not guarantee a major version bump,
4244
- Fixed a lot of issues with `?edit` and `?delete` and recipient message edit.
4345
- Masked the error: "AttributeError: 'int' object has no attribute 'name'"
4446
- Channel delete event will not be checked until discord.py fixes this issue.
47+
- Chained reaction.
48+
- Chained delete for thread channels.
4549

4650
### Internal
4751

Pipfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ motor = ">=2.0.0"
1818
natural = "==0.2.0"
1919
isodate = ">=0.6.0"
2020
dnspython = "~=1.16.0"
21-
parsedatetime = "==2.4"
21+
parsedatetime = "==2.5"
2222
aiohttp = "<3.6.0,>=3.3.0"
2323
python-dotenv = ">=0.10.3"
2424
pipenv = "*"

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<br>
77

88
<a href="#">
9-
<img src="https://img.shields.io/badge/Latest%20Version-v3.3.2-7289da?style=for-the-badge&logo=data:image/gif;base64,R0lGODlhGAAYAPcAAAAAADQ+Yj5MdThCaEFOekNQfWt5e1lqfEdVhVpriVx1iVtsnFZpll1xlF1znFx5mGNtjGR7hWNzjGd5iWJunGNslmN0lGl1lGN5lWt7lWR0nGp1nWR5nGt8nHJ9l3J9jFxto1xtqlZqqF5zpF15pV10rVx5q1Z2ql1us150s114sV50uVd1smJupGNurGN0pGp1pGR6o2t7o2F0rml0rGR5rGt8q3J9pGNus2Fvu2Bzs2p2smR5s2t7tGF0u2N6vGp7u2l1uXJ+uGJ1wWV7w2l+xWl+yl5zwHWDiWyCmHODnHiGmHSLkW2CpGuEqnSCpHqMo3ODrHWKrXyMq3iHp3yTqGyEtWqFunKDs3uLtHSGvXSMvHuMu3eItnyTs3yTu3iUuW+RsV+ApmuCxG2Dy22LxnSMxHuMxHGGzXSLzXqLzXWFxXyTw3uSy3WSx26E0W+N2m+L1XKG03SL1XmL1HOL2HmL23WF2XiS1m+QyHOK4XmL4XaH4nGM5ImXm4WOlJSjnoOOq4OOpYSUrImXqJOcqYONt4OUtIybtIOUu4ycvImYt5OcuIyiqZekqYyiupOjvJurvJimtp+yvqKruae2ua23trLCucPHvIOMxoOUxIuaw4eYyJKbyISW1YiW0o2kyJWlxJqpxZWkzJury5aoxp6xxZ2zyZio1Jyy2Yuk0aOsyKOyxKOzzKy8zKq2yLO7xqOr1KSz1Ku706W026y73Ki21bK716u75LS95KSu4Zyx6bPDzLjGx63B2LPD1LTE3LvL3brI17/S2bbKzrTE47rF47zM5L3M67bH577R5L7R67zK8rjM8MLM3MPK1sTT3crU3MjW2dLZ2srUy8PN5MTO68nP68jL48TS5MnT5cbR68jS7M3b7Mva5dTb6MTO8srO8szF8MXR8snS8c3a8cnY+NXb9tDL7dnm6dzk8tro99Pq9uTp6+r17fr77eTr/efr+Oz09OXz/Ov0/e37/uj39fX19fX89fz+9vP1/fX9/f7//vr2/ODl4sK75yH5BAEAAAAALAAAAAAYABgAAAj/AAEAmFOnoMGDCAvGmRNHYMKHEOdIhEgRIcGKGDMeTJVO00WNBj194/frFik5IOvgqdaP371N26xpefORYq57/HI+s7Vtmy0eKCmmapcz575FPXt+SfHmociiRae1SroNWIoVTQ2uxAk15yJg1awl7ZSCaUE82uRxhTpt1TNXq0Ip2pSobIqCc0iVoqsI0ipXr1bhywfVXxYdiA3Sourz2dqc8lbNQJz4bDWqix73E7ZjhgrKOjYmvfWLbZcZqCdTPjjHGLdtibi2U5Q6NWiEqqq64ifvVe3at1nX2tTuWQ/QyIMfxHOGi+rkyeMklPMDuvWaBqlbj469IPUU2ykPCOw+53t4AAEBADs=">
9+
<img src="https://img.shields.io/badge/Latest%20Version-v3.4-7289da?style=for-the-badge&logo=data:image/gif;base64,R0lGODlhGAAYAPcAAAAAADQ+Yj5MdThCaEFOekNQfWt5e1lqfEdVhVpriVx1iVtsnFZpll1xlF1znFx5mGNtjGR7hWNzjGd5iWJunGNslmN0lGl1lGN5lWt7lWR0nGp1nWR5nGt8nHJ9l3J9jFxto1xtqlZqqF5zpF15pV10rVx5q1Z2ql1us150s114sV50uVd1smJupGNurGN0pGp1pGR6o2t7o2F0rml0rGR5rGt8q3J9pGNus2Fvu2Bzs2p2smR5s2t7tGF0u2N6vGp7u2l1uXJ+uGJ1wWV7w2l+xWl+yl5zwHWDiWyCmHODnHiGmHSLkW2CpGuEqnSCpHqMo3ODrHWKrXyMq3iHp3yTqGyEtWqFunKDs3uLtHSGvXSMvHuMu3eItnyTs3yTu3iUuW+RsV+ApmuCxG2Dy22LxnSMxHuMxHGGzXSLzXqLzXWFxXyTw3uSy3WSx26E0W+N2m+L1XKG03SL1XmL1HOL2HmL23WF2XiS1m+QyHOK4XmL4XaH4nGM5ImXm4WOlJSjnoOOq4OOpYSUrImXqJOcqYONt4OUtIybtIOUu4ycvImYt5OcuIyiqZekqYyiupOjvJurvJimtp+yvqKruae2ua23trLCucPHvIOMxoOUxIuaw4eYyJKbyISW1YiW0o2kyJWlxJqpxZWkzJury5aoxp6xxZ2zyZio1Jyy2Yuk0aOsyKOyxKOzzKy8zKq2yLO7xqOr1KSz1Ku706W026y73Ki21bK716u75LS95KSu4Zyx6bPDzLjGx63B2LPD1LTE3LvL3brI17/S2bbKzrTE47rF47zM5L3M67bH577R5L7R67zK8rjM8MLM3MPK1sTT3crU3MjW2dLZ2srUy8PN5MTO68nP68jL48TS5MnT5cbR68jS7M3b7Mva5dTb6MTO8srO8szF8MXR8snS8c3a8cnY+NXb9tDL7dnm6dzk8tro99Pq9uTp6+r17fr77eTr/efr+Oz09OXz/Ov0/e37/uj39fX19fX89fz+9vP1/fX9/f7//vr2/ODl4sK75yH5BAEAAAAALAAAAAAYABgAAAj/AAEAmFOnoMGDCAvGmRNHYMKHEOdIhEgRIcGKGDMeTJVO00WNBj194/frFik5IOvgqdaP371N26xpefORYq57/HI+s7Vtmy0eKCmmapcz575FPXt+SfHmociiRae1SroNWIoVTQ2uxAk15yJg1awl7ZSCaUE82uRxhTpt1TNXq0Ip2pSobIqCc0iVoqsI0ipXr1bhywfVXxYdiA3Sourz2dqc8lbNQJz4bDWqix73E7ZjhgrKOjYmvfWLbZcZqCdTPjjHGLdtibi2U5Q6NWiEqqq64ifvVe3at1nX2tTuWQ/QyIMfxHOGi+rkyeMklPMDuvWaBqlbj469IPUU2ykPCOw+53t4AAEBADs=">
1010
</a>
1111

1212
<br>

bot.py

+65-39
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "3.4.0-dev6"
1+
__version__ = "3.4.0-dev7"
22

33

44
import asyncio
@@ -78,7 +78,7 @@ def __init__(self):
7878
"Your MONGO_URI might be copied wrong, try re-copying from the source again. "
7979
"Otherwise noted in the following message:"
8080
)
81-
logger.critical(str(e))
81+
logger.critical(e)
8282
sys.exit(0)
8383

8484
self.plugin_db = PluginDatabaseClient(self)
@@ -506,7 +506,7 @@ async def convert_emoji(self, name: str) -> str:
506506
try:
507507
name = await converter.convert(ctx, name.strip(":"))
508508
except commands.BadArgument as e:
509-
logger.warning("%s is not a valid emoji. %s.", str(e))
509+
logger.warning("%s is not a valid emoji. %s.", e)
510510
raise
511511
return name
512512

@@ -697,12 +697,14 @@ async def get_thread_cooldown(self, author: discord.Member):
697697
return
698698

699699
@staticmethod
700-
async def add_reaction(msg, reaction):
700+
async def add_reaction(msg, reaction: discord.Reaction) -> bool:
701701
if reaction != "disable":
702702
try:
703703
await msg.add_reaction(reaction)
704-
except (discord.HTTPException, discord.InvalidArgument):
705-
logger.warning("Failed to add reaction %s.", reaction, exc_info=True)
704+
except (discord.HTTPException, discord.InvalidArgument) as e:
705+
logger.warning("Failed to add reaction %s: %s.", reaction, e)
706+
return False
707+
return True
706708

707709
async def process_dm_modmail(self, message: discord.Message) -> None:
708710
"""Processes messages sent to the bot."""
@@ -955,26 +957,43 @@ async def on_raw_reaction_add(self, payload):
955957
close_emoji = await self.convert_emoji(self.config["close_emoji"])
956958

957959
if isinstance(channel, discord.DMChannel):
958-
if str(reaction) == str(close_emoji): # closing thread
959-
if not self.config.get("recipient_thread_close"):
960-
return
961-
thread = await self.threads.find(recipient=user)
962-
ts = message.embeds[0].timestamp if message.embeds else None
960+
thread = await self.threads.find(recipient=user)
961+
if not thread:
962+
return
963+
964+
if (
965+
message.embeds
966+
and str(reaction) == str(close_emoji)
967+
and self.config.get("recipient_thread_close")
968+
):
969+
ts = message.embeds[0].timestamp
963970
if thread and ts == thread.channel.created_at:
964971
# the reacted message is the corresponding thread creation embed
965-
await thread.close(closer=user)
972+
# closing thread
973+
return await thread.close(closer=user)
974+
if not thread.recipient.dm_channel:
975+
await thread.recipient.create_dm()
976+
try:
977+
linked_message = await thread.find_linked_message_from_dm(
978+
message, either_direction=True
979+
)
980+
except ValueError as e:
981+
logger.warning("Failed to find linked message for reactions: %s", e)
982+
return
966983
else:
967-
if not message.embeds:
984+
thread = await self.threads.find(channel=channel)
985+
if not thread:
986+
return
987+
try:
988+
_, linked_message = await thread.find_linked_messages(
989+
message.id, either_direction=True
990+
)
991+
except ValueError as e:
992+
logger.warning("Failed to find linked message for reactions: %s", e)
968993
return
969-
message_id = str(message.embeds[0].author.url).split("/")[-1]
970-
if message_id.isdigit():
971-
thread = await self.threads.find(channel=message.channel)
972-
channel = thread.recipient.dm_channel
973-
if not channel:
974-
channel = await thread.recipient.create_dm()
975-
async for msg in channel.history():
976-
if msg.id == int(message_id):
977-
await msg.add_reaction(reaction)
994+
995+
if await self.add_reaction(linked_message, reaction):
996+
await self.add_reaction(message, reaction)
978997

979998
async def on_guild_channel_delete(self, channel):
980999
if channel.guild != self.modmail_guild:
@@ -986,7 +1005,8 @@ async def on_guild_channel_delete(self, channel):
9861005
mod = entry.user
9871006
except AttributeError as e:
9881007
# discord.py broken implementation with discord API
989-
logger.warning("Failed to retrieve audit log: %s.", str(e))
1008+
# TODO: waiting for dpy
1009+
logger.warning("Failed to retrieve audit log: %s.", e)
9901010
return
9911011

9921012
if mod == self.user:
@@ -1035,35 +1055,37 @@ async def on_member_join(self, member):
10351055

10361056
async def on_message_delete(self, message):
10371057
"""Support for deleting linked messages"""
1058+
# TODO: use audit log to check if modmail deleted the message
10381059
if message.embeds and not isinstance(message.channel, discord.DMChannel):
1039-
message_id = str(message.embeds[0].author.url).split("/")[-1]
1040-
if message_id.isdigit():
1041-
thread = await self.threads.find(channel=message.channel)
1042-
1043-
channel = thread.recipient.dm_channel
1044-
1045-
async for msg in channel.history():
1046-
if msg.embeds and msg.embeds[0].author:
1047-
url = str(msg.embeds[0].author.url)
1048-
if message_id == url.split("/")[-1]:
1049-
return await msg.delete()
1060+
thread = await self.threads.find(channel=message.channel)
1061+
try:
1062+
await thread.delete_message(message)
1063+
except ValueError as e:
1064+
if str(e) not in {"DM message not found.", " Malformed thread message."}:
1065+
logger.warning("Failed to find linked message to delete: %s", e)
1066+
else:
1067+
thread = await self.threads.find(recipient=message.author)
1068+
message = await thread.find_linked_message_from_dm(message)
1069+
embed = message.embeds[0]
1070+
embed.set_footer(text=f"{embed.footer.text} (deleted)", icon_url=embed.footer.icon_url)
1071+
await message.edit(embed=embed)
10501072

10511073
async def on_bulk_message_delete(self, messages):
10521074
await discord.utils.async_all(self.on_message_delete(msg) for msg in messages)
10531075

10541076
async def on_message_edit(self, before, after):
10551077
if after.author.bot:
10561078
return
1079+
if before.content == after.content:
1080+
return
1081+
10571082
if isinstance(after.channel, discord.DMChannel):
10581083
thread = await self.threads.find(recipient=before.author)
10591084
try:
10601085
await thread.edit_dm_message(after, after.content)
10611086
except ValueError:
10621087
_, blocked_emoji = await self.retrieve_emoji()
1063-
try:
1064-
await after.add_reaction(blocked_emoji)
1065-
except (discord.HTTPException, discord.InvalidArgument):
1066-
pass
1088+
await self.add_reaction(after, blocked_emoji)
10671089
else:
10681090
embed = discord.Embed(
10691091
description="Successfully Edited Message", color=self.main_color
@@ -1173,7 +1195,7 @@ async def before_post_metadata(self):
11731195
self.metadata_loop.cancel()
11741196

11751197

1176-
if __name__ == "__main__":
1198+
def main():
11771199
try:
11781200
# noinspection PyUnresolvedReferences
11791201
import uvloop
@@ -1185,3 +1207,7 @@ async def before_post_metadata(self):
11851207

11861208
bot = ModmailBot()
11871209
bot.run()
1210+
1211+
1212+
if __name__ == "__main__":
1213+
main()

cogs/modmail.py

+11-23
Original file line numberDiff line numberDiff line change
@@ -312,10 +312,7 @@ async def move(self, ctx, category: discord.CategoryChannel, *, specifics: str =
312312
await thread.recipient.send(embed=embed)
313313

314314
sent_emoji, _ = await self.bot.retrieve_emoji()
315-
try:
316-
await ctx.message.add_reaction(sent_emoji)
317-
except (discord.HTTPException, discord.InvalidArgument):
318-
pass
315+
await self.bot.add_reaction(ctx.message, sent_emoji)
319316

320317
async def send_scheduled_close_message(self, ctx, after, silent=False):
321318
human_delta = human_timedelta(after.dt)
@@ -561,10 +558,7 @@ async def nsfw(self, ctx):
561558
"""Flags a Modmail thread as NSFW (not safe for work)."""
562559
await ctx.channel.edit(nsfw=True)
563560
sent_emoji, _ = await self.bot.retrieve_emoji()
564-
try:
565-
await ctx.message.add_reaction(sent_emoji)
566-
except (discord.HTTPException, discord.InvalidArgument):
567-
pass
561+
await self.bot.add_reaction(ctx.message, sent_emoji)
568562

569563
@commands.command()
570564
@checks.has_permissions(PermissionLevel.SUPPORTER)
@@ -573,10 +567,7 @@ async def sfw(self, ctx):
573567
"""Flags a Modmail thread as SFW (safe for work)."""
574568
await ctx.channel.edit(nsfw=False)
575569
sent_emoji, _ = await self.bot.retrieve_emoji()
576-
try:
577-
await ctx.message.add_reaction(sent_emoji)
578-
except (discord.HTTPException, discord.InvalidArgument):
579-
pass
570+
await self.bot.add_reaction(ctx.message, sent_emoji)
580571

581572
@commands.command()
582573
@checks.has_permissions(PermissionLevel.SUPPORTER)
@@ -872,7 +863,7 @@ async def edit(self, ctx, message_id: Optional[int] = None, *, message: str):
872863
)
873864

874865
sent_emoji, _ = await self.bot.retrieve_emoji()
875-
return await ctx.message.add_reaction(sent_emoji)
866+
await self.bot.add_reaction(ctx.message, sent_emoji)
876867

877868
@commands.command()
878869
@checks.has_permissions(PermissionLevel.SUPPORTER)
@@ -921,10 +912,7 @@ async def contact(
921912
await thread.wait_until_ready()
922913
await thread.channel.send(embed=embed)
923914
sent_emoji, _ = await self.bot.retrieve_emoji()
924-
try:
925-
await ctx.message.add_reaction(sent_emoji)
926-
except (discord.HTTPException, discord.InvalidArgument):
927-
pass
915+
await self.bot.add_reaction(ctx.message, sent_emoji)
928916
await asyncio.sleep(3)
929917
await ctx.message.delete()
930918

@@ -1173,7 +1161,7 @@ async def delete(self, ctx, message_id: int = None):
11731161
)
11741162

11751163
sent_emoji, _ = await self.bot.retrieve_emoji()
1176-
return await ctx.message.add_reaction(sent_emoji)
1164+
await self.bot.add_reaction(ctx.message, sent_emoji)
11771165

11781166
@commands.command()
11791167
@checks.has_permissions(PermissionLevel.SUPPORTER)
@@ -1188,7 +1176,7 @@ async def repair(self, ctx):
11881176
if user_id == -1:
11891177
logger.info("Setting current channel's topic to User ID.")
11901178
await ctx.channel.edit(topic=f"User ID: {ctx.thread.id}")
1191-
return await ctx.message.add_reaction(sent_emoji)
1179+
return await self.bot.add_reaction(ctx.message, sent_emoji)
11921180

11931181
logger.info("Attempting to fix a broken thread %s.", ctx.channel.name)
11941182

@@ -1200,7 +1188,7 @@ async def repair(self, ctx):
12001188
if thread is not None:
12011189
logger.debug("Found thread with tempered ID.")
12021190
await ctx.channel.edit(reason="Fix broken Modmail thread", topic=f"User ID: {user_id}")
1203-
return await ctx.message.add_reaction(sent_emoji)
1191+
return await self.bot.add_reaction(ctx.message, sent_emoji)
12041192

12051193
# find genesis message to retrieve User ID
12061194
async for message in ctx.channel.history(limit=10, oldest_first=True):
@@ -1229,7 +1217,7 @@ async def repair(self, ctx):
12291217
await ctx.channel.edit(
12301218
reason="Fix broken Modmail thread", topic=f"User ID: {user_id}"
12311219
)
1232-
return await ctx.message.add_reaction(sent_emoji)
1220+
return await self.bot.add_reaction(ctx.message, sent_emoji)
12331221

12341222
else:
12351223
logger.warning("No genesis message found.")
@@ -1280,11 +1268,11 @@ async def repair(self, ctx):
12801268
await ctx.channel.edit(
12811269
reason="Fix broken Modmail thread", name=name, topic=f"User ID: {user.id}"
12821270
)
1283-
return await ctx.message.add_reaction(sent_emoji)
1271+
return await self.bot.add_reaction(ctx.message, sent_emoji)
12841272

12851273
elif len(users) >= 2:
12861274
logger.info("Multiple users with the same name and discriminator.")
1287-
return await ctx.message.add_reaction(blocked_emoji)
1275+
return await self.bot.add_reaction(ctx.message, blocked_emoji)
12881276

12891277
@commands.command()
12901278
@checks.has_permissions(PermissionLevel.ADMINISTRATOR)

cogs/utility.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ async def send_error_message(self, error):
201201
)
202202
return await self.get_destination().send(embed=embed)
203203

204-
logger.warning("CommandNotFound: %s", str(error))
204+
logger.warning("CommandNotFound: %s", error)
205205

206206
embed = discord.Embed(color=self.context.bot.error_color)
207207
embed.set_footer(text=f'Command/Category "{command}" not found.')
@@ -1723,7 +1723,7 @@ def paginate(text: str):
17231723
exec(to_compile, env) # pylint: disable=exec-used
17241724
except Exception as exc:
17251725
await ctx.send(f"```py\n{exc.__class__.__name__}: {exc}\n```")
1726-
return await ctx.message.add_reaction("\u2049")
1726+
return await self.bot.add_reaction(ctx.message, "\u2049")
17271727

17281728
func = env["func"]
17291729
try:
@@ -1732,7 +1732,7 @@ def paginate(text: str):
17321732
except Exception:
17331733
value = stdout.getvalue()
17341734
await ctx.send(f"```py\n{value}{traceback.format_exc()}\n```")
1735-
return await ctx.message.add_reaction("\u2049")
1735+
return await self.bot.add_reaction(ctx.message, "\u2049")
17361736

17371737
else:
17381738
value = stdout.getvalue()

core/paginator.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ async def create_base(self, item) -> None:
7373
for reaction in self.reaction_map:
7474
if len(self.pages) == 2 and reaction in "⏮⏭":
7575
continue
76-
await self.base.add_reaction(reaction)
76+
await self.ctx.bot.add_reaction(self.base, reaction)
7777

7878
async def _create_base(self, item) -> None:
7979
raise NotImplementedError
@@ -177,10 +177,7 @@ async def close(self, delete: bool = True) -> typing.Optional[Message]:
177177
self.running = False
178178

179179
sent_emoji, _ = await self.ctx.bot.retrieve_emoji()
180-
try:
181-
await self.ctx.message.add_reaction(sent_emoji)
182-
except (HTTPException, InvalidArgument):
183-
pass
180+
await self.ctx.bot.add_reaction(self.ctx.message, sent_emoji)
184181

185182
if delete:
186183
return await self.base.delete()

0 commit comments

Comments
 (0)