Skip to content

Commit 0110130

Browse files
committed
?contact accepts multiple users, or role #3082
1 parent e31cc76 commit 0110130

File tree

6 files changed

+107
-69
lines changed

6 files changed

+107
-69
lines changed

CHANGELOG.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,26 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66
This project mostly adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html);
77
however, insignificant breaking changes 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.
88

9-
# v3.10.0-dev8
9+
# v3.10.0-dev9
1010

1111
v3.10 adds group conversations while resolving other bugs and QOL changes. It is potentially breaking to some plugins that adds functionality to threads.
1212

1313
### Breaking
1414

1515
- `Thread.recipient` (`str`) is now `Thread.recipients` (`List[str]`).
16-
- `Thread.reply` now returns mod_message, user_message1, user_message2... It is no longer limited at a size 2 tuple.
16+
- `Thread.reply` now returns `mod_message, user_message1, user_message2`... It is no longer limited at a size 2 tuple.
1717

1818
### Added
1919

20-
- Ability to have group conversations. ([GH #143](https://github.com/kyb3r/modmail/issues/143))
20+
- Ability to have group conversations with up to 5 users. ([GH #143](https://github.com/kyb3r/modmail/issues/143))
2121
- Snippets are invoked case insensitively. ([GH #3077](https://github.com/kyb3r/modmail/issues/3077), [PR #3080](https://github.com/kyb3r/modmail/pull/3080))
2222
- Default tags now use top hoisted role. ([GH #3014](https://github.com/kyb3r/modmail/issues/3014))
2323
- 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))
2424
- `use_timestamp_channel_name` config to create thread channels by timestamp.
2525

26+
### Improved
27+
- `?contact` now accepts a role or multiple users (creates a group conversation). ([GH #3082](https://github.com/kyb3r/modmail/issues/3082))
28+
2629
### Fixed
2730

2831
- 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))

README.md

Lines changed: 1 addition & 1 deletion
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.10.0-dev8-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.10.0-dev9-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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "3.10.0-dev8"
1+
__version__ = "3.10.0-dev9"
22

33

44
import asyncio

cogs/modmail.py

Lines changed: 95 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,16 @@ async def adduser(self, ctx, *users_arg: Union[discord.Member, discord.Role, str
718718
ctx.command.reset_cooldown(ctx)
719719
return
720720

721+
if len(users + ctx.thread.recipients) > 5:
722+
em = discord.Embed(
723+
title="Error",
724+
description="Only 5 users are allowed in a group conversation",
725+
color=self.bot.error_color,
726+
)
727+
await ctx.send(embed=em)
728+
ctx.command.reset_cooldown(ctx)
729+
return
730+
721731
if not silent:
722732
description = self.bot.formatter.format(
723733
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):
13081318
@checks.has_permissions(PermissionLevel.REGULAR)
13091319
async def selfcontact(self, ctx):
13101320
"""Creates a thread with yourself"""
1311-
await ctx.invoke(self.contact, user=ctx.author)
1321+
await ctx.invoke(self.contact, users=[ctx.author])
13121322

13131323
@commands.command(usage="<user> [category] [options]")
13141324
@checks.has_permissions(PermissionLevel.SUPPORTER)
13151325
async def contact(
13161326
self,
13171327
ctx,
1318-
user: Union[discord.Member, discord.User],
1328+
users: commands.Greedy[Union[discord.Member, discord.User, discord.Role]],
13191329
*,
13201330
category: Union[SimilarCategoryConverter, str] = None,
13211331
manual_trigger=True,
@@ -1327,7 +1337,8 @@ async def contact(
13271337
will be created in that specified category.
13281338
13291339
`category`, if specified, may be a category ID, mention, or name.
1330-
`user` may be a user ID, mention, or name.
1340+
`users` may be a user ID, mention, or name. If multiple users are specified, a group thread will start.
1341+
A maximum of 5 users are allowed.
13311342
`options` can be `silent` or `silently`.
13321343
"""
13331344
silent = False
@@ -1345,75 +1356,99 @@ async def contact(
13451356
if isinstance(category, str):
13461357
category = None
13471358

1348-
if user.bot:
1349-
embed = discord.Embed(color=self.bot.error_color, description="Cannot start a thread with a bot.")
1350-
return await ctx.send(embed=embed, delete_after=3)
1359+
errors = []
1360+
for u in list(users):
1361+
if isinstance(u, discord.Role):
1362+
users += u.members
1363+
users.remove(u)
13511364

1352-
exists = await self.bot.threads.find(recipient=user)
1353-
if exists:
1354-
desc = "A thread for this user already exists"
1355-
if exists.channel:
1356-
desc += f" in {exists.channel.mention}"
1357-
desc += "."
1358-
embed = discord.Embed(color=self.bot.error_color, description=desc)
1359-
await ctx.channel.send(embed=embed, delete_after=3)
1365+
for u in list(users):
1366+
exists = await self.bot.threads.find(recipient=u)
1367+
if exists:
1368+
errors.append(f"A thread for {u} already exists.")
1369+
if exists.channel:
1370+
errors[-1] += f" in {exists.channel.mention}"
1371+
errors[-1] += "."
1372+
users.remove(u)
1373+
elif u.bot:
1374+
errors.append(f"{u} is a bot, cannot add to thread.")
1375+
users.remove(u)
1376+
elif await self.bot.is_blocked(u):
1377+
ref = f"{u.mention} is" if ctx.author != u else "You are"
1378+
errors.append(f"{ref} currently blocked from contacting {self.bot.user.name}.")
1379+
users.remove(u)
13601380

1361-
else:
1362-
creator = ctx.author if manual_trigger else user
1363-
if await self.bot.is_blocked(user):
1364-
if not manual_trigger: # react to contact
1365-
return
1381+
if len(users) > 5:
1382+
errors.append("Group conversations only support 5 users.")
1383+
users = []
13661384

1367-
ref = f"{user.mention} is" if creator != user else "You are"
1368-
embed = discord.Embed(
1369-
color=self.bot.error_color,
1370-
description=f"{ref} currently blocked from contacting {self.bot.user.name}.",
1371-
)
1372-
return await ctx.send(embed=embed)
1385+
if errors or not users:
1386+
if not users:
1387+
# no users left
1388+
title = "Thread not created"
1389+
else:
1390+
title = None
13731391

1374-
thread = await self.bot.threads.create(
1375-
recipient=user,
1376-
creator=creator,
1377-
category=category,
1378-
manual_trigger=manual_trigger,
1379-
)
1380-
if thread.cancelled:
1392+
if manual_trigger: # not react to contact
1393+
embed = discord.Embed(title=title, color=self.bot.error_color, description="\n".join(errors))
1394+
await ctx.send(embed=embed, delete_after=10)
1395+
1396+
if not users:
1397+
# end
13811398
return
13821399

1383-
if self.bot.config["dm_disabled"] in (DMDisabled.NEW_THREADS, DMDisabled.ALL_THREADS):
1384-
logger.info("Contacting user %s when Modmail DM is disabled.", user)
1400+
creator = ctx.author if manual_trigger else users[0]
13851401

1386-
if not silent and not self.bot.config.get("thread_contact_silently"):
1387-
if creator.id == user.id:
1388-
description = self.bot.config["thread_creation_self_contact_response"]
1389-
else:
1390-
description = self.bot.formatter.format(
1391-
self.bot.config["thread_creation_contact_response"], creator=creator
1392-
)
1402+
thread = await self.bot.threads.create(
1403+
recipient=users[0],
1404+
creator=creator,
1405+
category=category,
1406+
manual_trigger=manual_trigger,
1407+
)
13931408

1394-
em = discord.Embed(
1395-
title=self.bot.config["thread_creation_contact_title"],
1396-
description=description,
1397-
color=self.bot.main_color,
1409+
if thread.cancelled:
1410+
return
1411+
1412+
if self.bot.config["dm_disabled"] in (DMDisabled.NEW_THREADS, DMDisabled.ALL_THREADS):
1413+
logger.info("Contacting user %s when Modmail DM is disabled.", users[0])
1414+
1415+
if not silent and not self.bot.config.get("thread_contact_silently"):
1416+
if creator.id == users[0].id:
1417+
description = self.bot.config["thread_creation_self_contact_response"]
1418+
else:
1419+
description = self.bot.formatter.format(
1420+
self.bot.config["thread_creation_contact_response"], creator=creator
13981421
)
1399-
if self.bot.config["show_timestamp"]:
1400-
em.timestamp = datetime.utcnow()
1401-
em.set_footer(text=f"{creator}", icon_url=creator.avatar_url)
1402-
await user.send(embed=em)
14031422

1404-
embed = discord.Embed(
1405-
title="Created Thread",
1406-
description=f"Thread started by {creator.mention} for {user.mention}.",
1423+
em = discord.Embed(
1424+
title=self.bot.config["thread_creation_contact_title"],
1425+
description=description,
14071426
color=self.bot.main_color,
14081427
)
1409-
await thread.wait_until_ready()
1410-
await thread.channel.send(embed=embed)
1411-
1412-
if manual_trigger:
1413-
sent_emoji, _ = await self.bot.retrieve_emoji()
1414-
await self.bot.add_reaction(ctx.message, sent_emoji)
1415-
await asyncio.sleep(5)
1416-
await ctx.message.delete()
1428+
if self.bot.config["show_timestamp"]:
1429+
em.timestamp = datetime.utcnow()
1430+
em.set_footer(text=f"{creator}", icon_url=creator.avatar_url)
1431+
1432+
for u in users:
1433+
await u.send(embed=em)
1434+
1435+
embed = discord.Embed(
1436+
title="Created Thread",
1437+
description=f"Thread started by {creator.mention} for {', '.join(u.mention for u in users)}.",
1438+
color=self.bot.main_color,
1439+
)
1440+
await thread.wait_until_ready()
1441+
1442+
if users[1:]:
1443+
await thread.add_users(users[1:])
1444+
1445+
await thread.channel.send(embed=embed)
1446+
1447+
if manual_trigger:
1448+
sent_emoji, _ = await self.bot.retrieve_emoji()
1449+
await self.bot.add_reaction(ctx.message, sent_emoji)
1450+
await asyncio.sleep(5)
1451+
await ctx.message.delete()
14171452

14181453
@commands.group(invoke_without_command=True)
14191454
@checks.has_permissions(PermissionLevel.MODERATOR)

cogs/utility.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -695,7 +695,7 @@ async def mention(self, ctx, *user_or_role: Union[discord.Role, discord.Member,
695695
option = user_or_role[0].lower()
696696
if option == "disable":
697697
embed = discord.Embed(
698-
description=f"Disabled mention on thread creation.",
698+
description="Disabled mention on thread creation.",
699699
color=self.bot.main_color,
700700
)
701701
self.bot.config["mention"] = None
@@ -893,7 +893,7 @@ async def config_help(self, ctx, key: str.lower = None):
893893
description=f"`{key}` is an invalid key.",
894894
)
895895
if closest:
896-
embed.add_field(name=f"Perhaps you meant:", value="\n".join(f"`{x}`" for x in closest))
896+
embed.add_field(name="Perhaps you meant:", value="\n".join(f"`{x}`" for x in closest))
897897
return await ctx.send(embed=embed)
898898

899899
config_help = self.bot.config.config_help
@@ -1850,7 +1850,7 @@ async def autotrigger_test(self, ctx, *, text):
18501850
embed = discord.Embed(
18511851
title="Keyword Not Found",
18521852
color=self.bot.error_color,
1853-
description=f"No autotrigger keyword found.",
1853+
description="No autotrigger keyword found.",
18541854
)
18551855
return await ctx.send(embed=embed)
18561856

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ extend-exclude = '''
2121

2222
[tool.poetry]
2323
name = 'Modmail'
24-
version = '3.10.0-dev8'
24+
version = '3.10.0-dev9'
2525
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."
2626
license = 'AGPL-3.0-only'
2727
authors = [

0 commit comments

Comments
 (0)