Skip to content

Commit

Permalink
?contact accepts multiple users, or role modmail-dev#3082
Browse files Browse the repository at this point in the history
  • Loading branch information
fourjr committed Aug 17, 2021
1 parent e31cc76 commit 0110130
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 69 deletions.
9 changes: 6 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<br>

<a href="#">
<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=">
<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=">
</a>

<br>
Expand Down
2 changes: 1 addition & 1 deletion bot.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "3.10.0-dev8"
__version__ = "3.10.0-dev9"


import asyncio
Expand Down
155 changes: 95 additions & 60 deletions cogs/modmail.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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="<user> [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,
Expand All @@ -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
Expand All @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions cogs/utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down

0 comments on commit 0110130

Please sign in to comment.