diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b17979c60..191ceb258b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,11 +35,11 @@ v3.10 adds group conversations while resolving other bugs and QOL changes. It is ### 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)) diff --git a/cogs/modmail.py b/cogs/modmail.py index 42eb297a6d..b071cfc8ff 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -725,6 +725,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 @@ -1327,14 +1337,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, @@ -1346,7 +1356,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 @@ -1371,74 +1382,97 @@ 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) + + 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) - 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) + if len(users) > 5: + errors.append("Group conversations only support 5 users.") + users = [] - 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 errors or not users: + if not users: + # no users left + title = "Thread not created" + else: + title = None - 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 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) - thread = await self.bot.threads.create( - recipient=user, - creator=creator, - category=category, - manual_trigger=manual_trigger, - ) - if thread.cancelled: + 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 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) + if manual_trigger: + 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.MODERATOR) diff --git a/cogs/utility.py b/cogs/utility.py index bbeb8cffe0..bc813e9d6e 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -694,7 +694,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 @@ -892,7 +892,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 @@ -1861,7 +1861,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)