From b26368c10caad43841290c3bb17495d75643c32e Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Fri, 23 Apr 2021 20:40:29 +0200 Subject: Nominations: automate post archiving This commit adds a new reaction handler to the talentpool cog to automatically unpin and archive mesage in #nomination-voting --- bot/constants.py | 3 ++ bot/exts/recruitment/talentpool/_cog.py | 22 +++++++++++++- bot/exts/recruitment/talentpool/_review.py | 46 ++++++++++++++++++++++++++++-- bot/utils/messages.py | 24 +++++++++++++++- config-default.yml | 3 ++ 5 files changed, 94 insertions(+), 4 deletions(-) diff --git a/bot/constants.py b/bot/constants.py index cc3aa41a5..4189e938e 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -295,6 +295,8 @@ class Emojis(metaclass=YAMLGetter): status_offline: str status_online: str + ducky_dave: str + trashcan: str bullet: str @@ -417,6 +419,7 @@ class Channels(metaclass=YAMLGetter): attachment_log: int message_log: int mod_log: int + nomination_archive: int user_log: int voice_log: int diff --git a/bot/exts/recruitment/talentpool/_cog.py b/bot/exts/recruitment/talentpool/_cog.py index 72604be51..03326cab2 100644 --- a/bot/exts/recruitment/talentpool/_cog.py +++ b/bot/exts/recruitment/talentpool/_cog.py @@ -5,7 +5,7 @@ from io import StringIO from typing import Union import discord -from discord import Color, Embed, Member, User +from discord import Color, Embed, Member, PartialMessage, RawReactionActionEvent, User from discord.ext.commands import Cog, Context, group, has_any_role from bot.api import ResponseCodeError @@ -360,6 +360,26 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"): """Remove `user` from the talent pool after they are banned.""" await self.unwatch(user.id, "User was banned.") + @Cog.listener() + async def on_raw_reaction_add(self, payload: RawReactionActionEvent) -> None: + """ + Watch for reactions in the #nomination-voting channel to automate it. + + Adding a ticket emoji will unpin the message. + Adding an incident reaction will archive the message. + """ + if payload.channel_id != Channels.nomination_voting: + return + + message: PartialMessage = self.bot.get_channel(payload.channel_id).get_partial_message(payload.message_id) + emoji = str(payload.emoji) + + if emoji == "\N{TICKET}": + await message.unpin(reason="Admin task created.") + elif emoji in {Emojis.incident_actioned, Emojis.incident_unactioned}: + log.info(f"Archiving nomination {message.id}") + await self.reviewer.archive_vote(message, emoji == Emojis.incident_actioned) + async def unwatch(self, user_id: int, reason: str) -> bool: """End the active nomination of a user with the given reason and return True on success.""" active_nomination = await self.bot.api_client.get( diff --git a/bot/exts/recruitment/talentpool/_review.py b/bot/exts/recruitment/talentpool/_review.py index 11aa3b62b..987e40665 100644 --- a/bot/exts/recruitment/talentpool/_review.py +++ b/bot/exts/recruitment/talentpool/_review.py @@ -1,6 +1,7 @@ import asyncio import logging import random +import re import textwrap import typing from collections import Counter @@ -9,12 +10,13 @@ from typing import List, Optional, Union from dateutil.parser import isoparse from dateutil.relativedelta import relativedelta -from discord import Emoji, Member, Message, TextChannel +from discord import Embed, Emoji, Member, Message, PartialMessage, TextChannel from discord.ext.commands import Context from bot.api import ResponseCodeError from bot.bot import Bot -from bot.constants import Channels, Guild, Roles +from bot.constants import Channels, Colours, Emojis, Guild, Roles +from bot.utils.messages import count_unique_users_reaction from bot.utils.scheduling import Scheduler from bot.utils.time import get_time_delta, humanize_delta, time_since @@ -120,6 +122,46 @@ class Reviewer: review = "\n\n".join((opening, current_nominations, review_body, vote_request)) return review, seen_emoji + async def archive_vote(self, message: PartialMessage, passed: bool) -> None: + """Archive this vote to #nomination-archive.""" + message = await message.fetch() + + # We assume that the first user mentioned is the user that we are voting on + user_id = int(re.search(r"<@!?(\d+?)>", message.content).group(1)) + + # Get reaction counts + seen = await count_unique_users_reaction( + message, + lambda r: "ducky" in str(r) or str(r) == "\N{EYES}" + ) + upvotes = await count_unique_users_reaction(message, lambda r: str(r) == "\N{THUMBS UP SIGN}", False) + downvotes = await count_unique_users_reaction(message, lambda r: str(r) == "\N{THUMBS DOWN SIGN}", False) + + # Remove the first and last paragraphs + content = message.content.split("\n\n", maxsplit=1)[1].rsplit("\n\n", maxsplit=1)[0] + + result = f"**Passed** {Emojis.incident_actioned}" if passed else f"**Rejected** {Emojis.incident_unactioned}" + colour = Colours.soft_green if passed else Colours.soft_red + timestamp = datetime.utcnow().strftime("%d/%m/%Y") + + embed_content = ( + f"{result} on {timestamp}\n" + f"With {seen} {Emojis.ducky_dave} {upvotes} :+1: {downvotes} :-1:\n\n" + f"{content}" + ) + + if user := await self.bot.fetch_user(user_id): + embed_title = f"Vote for {user} (`{user.id}`)" + else: + embed_title = f"Vote for `{user_id}`" + + await self.bot.get_channel(Channels.nomination_archive).send(embed=Embed( + title=embed_title, + description=embed_content, + colour=colour + )) + await message.delete() + async def _construct_review_body(self, member: Member) -> str: """Formats the body of the nomination, with details of activity, infractions, and previous nominations.""" activity = await self._activity_review(member) diff --git a/bot/utils/messages.py b/bot/utils/messages.py index 2beead6af..2fcc5a01f 100644 --- a/bot/utils/messages.py +++ b/bot/utils/messages.py @@ -5,9 +5,10 @@ import random import re from functools import partial from io import BytesIO -from typing import List, Optional, Sequence, Union +from typing import Callable, List, Optional, Sequence, Union import discord +from discord import Reaction from discord.errors import HTTPException from discord.ext.commands import Context @@ -164,6 +165,27 @@ async def send_attachments( return urls +async def count_unique_users_reaction( + message: discord.Message, + predicate: Callable[[Reaction], bool] = lambda _: True, + count_bots: bool = True +) -> int: + """ + Count the amount of unique users who reacted to the message. + + A predicate function can be passed to check if this reaction should be counted, along with a count_bot flag. + """ + unique_users = set() + + for reaction in message.reactions: + if predicate(reaction): + async for user in reaction.users(): + if count_bots or not user.bot: + unique_users.add(user.id) + + return len(unique_users) + + def sub_clyde(username: Optional[str]) -> Optional[str]: """ Replace "e"/"E" in any "clyde" in `username` with a Cyrillic "е"/"E" and return the new string. diff --git a/config-default.yml b/config-default.yml index b7c446889..1610e7c75 100644 --- a/config-default.yml +++ b/config-default.yml @@ -62,6 +62,8 @@ style: status_offline: "<:status_offline:470326266537705472>" status_online: "<:status_online:470326272351010816>" + ducky_dave: "<:ducky_dave:742058418692423772>" + trashcan: "<:trashcan:637136429717389331>" bullet: "\u2022" @@ -172,6 +174,7 @@ guild: attachment_log: &ATTACH_LOG 649243850006855680 message_log: &MESSAGE_LOG 467752170159079424 mod_log: &MOD_LOG 282638479504965634 + nomination_archive: 833371042046148738 user_log: 528976905546760203 voice_log: 640292421988646961 -- cgit v1.2.3 From d6098bc8dbba1e7cbc3f182284f02c5dec3f5ff7 Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Fri, 23 Apr 2021 20:45:33 +0200 Subject: Nomination: ping the whole team --- bot/exts/recruitment/talentpool/_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/recruitment/talentpool/_review.py b/bot/exts/recruitment/talentpool/_review.py index 987e40665..04581ec13 100644 --- a/bot/exts/recruitment/talentpool/_review.py +++ b/bot/exts/recruitment/talentpool/_review.py @@ -103,7 +103,7 @@ class Reviewer: f"I tried to review the user with ID `{user_id}`, but they don't appear to be on the server :pensive:" ), None - opening = f"<@&{Roles.moderators}> <@&{Roles.admins}>\n{member.mention} ({member}) for Helper!" + opening = f"<@&{Roles.mod_team}> <@&{Roles.admins}>\n{member.mention} ({member}) for Helper!" current_nominations = "\n\n".join( f"**<@{entry['actor']}>:** {entry['reason'] or '*no reason given*'}" for entry in nomination['entries'] -- cgit v1.2.3 From 63cbedebaa2fd8abc67524cc944fb6ad0809016a Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Sat, 24 Apr 2021 18:02:44 +0200 Subject: Nomination: pin nomination announcements --- bot/exts/recruitment/talentpool/_review.py | 10 +++++++--- bot/utils/messages.py | 17 ++++++++++++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/bot/exts/recruitment/talentpool/_review.py b/bot/exts/recruitment/talentpool/_review.py index 04581ec13..ddad5c9c0 100644 --- a/bot/exts/recruitment/talentpool/_review.py +++ b/bot/exts/recruitment/talentpool/_review.py @@ -16,7 +16,7 @@ from discord.ext.commands import Context from bot.api import ResponseCodeError from bot.bot import Bot from bot.constants import Channels, Colours, Emojis, Guild, Roles -from bot.utils.messages import count_unique_users_reaction +from bot.utils.messages import count_unique_users_reaction, pin_no_system_message from bot.utils.scheduling import Scheduler from bot.utils.time import get_time_delta, humanize_delta, time_since @@ -77,10 +77,14 @@ class Reviewer: channel = guild.get_channel(Channels.nomination_voting) log.trace(f"Posting the review of {user_id}") - message = (await self._bulk_send(channel, review))[-1] + messages = await self._bulk_send(channel, review) + + await pin_no_system_message(messages[0]) + + last_message = messages[-1] if seen_emoji: for reaction in (seen_emoji, "\N{THUMBS UP SIGN}", "\N{THUMBS DOWN SIGN}"): - await message.add_reaction(reaction) + await last_message.add_reaction(reaction) if update_database: nomination = self._pool.watched_users[user_id] diff --git a/bot/utils/messages.py b/bot/utils/messages.py index 2fcc5a01f..d0d56e273 100644 --- a/bot/utils/messages.py +++ b/bot/utils/messages.py @@ -8,7 +8,7 @@ from io import BytesIO from typing import Callable, List, Optional, Sequence, Union import discord -from discord import Reaction +from discord import Message, MessageType, Reaction from discord.errors import HTTPException from discord.ext.commands import Context @@ -186,6 +186,21 @@ async def count_unique_users_reaction( return len(unique_users) +async def pin_no_system_message(message: Message) -> bool: + """Pin the given message, wait a couple of seconds and try to delete the system message.""" + await message.pin() + + # Make sure that we give it enough time to deliver the message + await asyncio.sleep(2) + # Search for the system message in the last 10 messages + async for historical_message in message.channel.history(limit=10): + if historical_message.type == MessageType.pins_add: + await historical_message.delete() + return True + + return False + + def sub_clyde(username: Optional[str]) -> Optional[str]: """ Replace "e"/"E" in any "clyde" in `username` with a Cyrillic "е"/"E" and return the new string. -- cgit v1.2.3 From 77176b086dc30cd3c626ccfaad07e70b5676f77b Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Sat, 24 Apr 2021 18:32:29 +0200 Subject: Nomination: properly handle multi messages nominations --- bot/exts/recruitment/talentpool/_review.py | 36 ++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/bot/exts/recruitment/talentpool/_review.py b/bot/exts/recruitment/talentpool/_review.py index ddad5c9c0..4f6945043 100644 --- a/bot/exts/recruitment/talentpool/_review.py +++ b/bot/exts/recruitment/talentpool/_review.py @@ -1,4 +1,5 @@ import asyncio +import contextlib import logging import random import re @@ -10,7 +11,7 @@ from typing import List, Optional, Union from dateutil.parser import isoparse from dateutil.relativedelta import relativedelta -from discord import Embed, Emoji, Member, Message, PartialMessage, TextChannel +from discord import Embed, Emoji, Member, Message, NoMoreItems, PartialMessage, TextChannel from discord.ext.commands import Context from bot.api import ResponseCodeError @@ -130,19 +131,34 @@ class Reviewer: """Archive this vote to #nomination-archive.""" message = await message.fetch() + # We consider that if a message has been sent less than 2 second before the one being archived + # it is part of the same nomination. + # For that we try to get messages sent in this timeframe until none is returned and NoMoreItems is raised. + messages = [message] + with contextlib.suppress(NoMoreItems): + while True: + new_message = await message.channel.history( # noqa: B305 - yes flake8, .next() is a thing here. + before=messages[-1].created_at, + after=messages[-1].created_at - timedelta(seconds=2) + ).next() + messages.append(new_message) + + content = "".join(message_.content for message_ in messages[::-1]) + # We assume that the first user mentioned is the user that we are voting on - user_id = int(re.search(r"<@!?(\d+?)>", message.content).group(1)) + user_id = int(re.search(r"<@!?(\d+?)>", content).group(1)) # Get reaction counts seen = await count_unique_users_reaction( - message, - lambda r: "ducky" in str(r) or str(r) == "\N{EYES}" + messages[0], + lambda r: "ducky" in str(r) or str(r) == "\N{EYES}", + False ) - upvotes = await count_unique_users_reaction(message, lambda r: str(r) == "\N{THUMBS UP SIGN}", False) - downvotes = await count_unique_users_reaction(message, lambda r: str(r) == "\N{THUMBS DOWN SIGN}", False) + upvotes = await count_unique_users_reaction(messages[0], lambda r: str(r) == "\N{THUMBS UP SIGN}", False) + downvotes = await count_unique_users_reaction(messages[0], lambda r: str(r) == "\N{THUMBS DOWN SIGN}", False) # Remove the first and last paragraphs - content = message.content.split("\n\n", maxsplit=1)[1].rsplit("\n\n", maxsplit=1)[0] + stripped_content = content.split("\n\n", maxsplit=1)[1].rsplit("\n\n", maxsplit=1)[0] result = f"**Passed** {Emojis.incident_actioned}" if passed else f"**Rejected** {Emojis.incident_unactioned}" colour = Colours.soft_green if passed else Colours.soft_red @@ -151,7 +167,7 @@ class Reviewer: embed_content = ( f"{result} on {timestamp}\n" f"With {seen} {Emojis.ducky_dave} {upvotes} :+1: {downvotes} :-1:\n\n" - f"{content}" + f"{textwrap.shorten(stripped_content, 2048, replace_whitespace=False)}" ) if user := await self.bot.fetch_user(user_id): @@ -164,7 +180,9 @@ class Reviewer: description=embed_content, colour=colour )) - await message.delete() + + for message_ in messages: + await message_.delete() async def _construct_review_body(self, member: Member) -> str: """Formats the body of the nomination, with details of activity, infractions, and previous nominations.""" -- cgit v1.2.3 From b8429c75eb4366c98fd66fa68eb5b7f06aa2be61 Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Sat, 24 Apr 2021 18:47:13 +0200 Subject: Nominations: send many archive messages if the nomination is too long --- bot/exts/recruitment/talentpool/_review.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/bot/exts/recruitment/talentpool/_review.py b/bot/exts/recruitment/talentpool/_review.py index 4f6945043..81c9516ac 100644 --- a/bot/exts/recruitment/talentpool/_review.py +++ b/bot/exts/recruitment/talentpool/_review.py @@ -167,7 +167,7 @@ class Reviewer: embed_content = ( f"{result} on {timestamp}\n" f"With {seen} {Emojis.ducky_dave} {upvotes} :+1: {downvotes} :-1:\n\n" - f"{textwrap.shorten(stripped_content, 2048, replace_whitespace=False)}" + f"{stripped_content}" ) if user := await self.bot.fetch_user(user_id): @@ -175,11 +175,13 @@ class Reviewer: else: embed_title = f"Vote for `{user_id}`" - await self.bot.get_channel(Channels.nomination_archive).send(embed=Embed( - title=embed_title, - description=embed_content, - colour=colour - )) + channel = self.bot.get_channel(Channels.nomination_archive) + for number, part in enumerate(textwrap.wrap(embed_content, width=MAX_MESSAGE_SIZE, replace_whitespace=False)): + await channel.send(embed=Embed( + title=embed_title if number == 0 else None, + description="[...] " + part if number != 0 else part, + colour=colour + )) for message_ in messages: await message_.delete() -- cgit v1.2.3 From c111dd7db591450d62dfa590b11f2fc7078a16e3 Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Sun, 9 May 2021 17:48:19 +0200 Subject: Nomination: simplify history loop Co-authored-by: mbaruh --- bot/exts/recruitment/talentpool/_review.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/bot/exts/recruitment/talentpool/_review.py b/bot/exts/recruitment/talentpool/_review.py index 81c9516ac..b0b9061db 100644 --- a/bot/exts/recruitment/talentpool/_review.py +++ b/bot/exts/recruitment/talentpool/_review.py @@ -136,11 +136,9 @@ class Reviewer: # For that we try to get messages sent in this timeframe until none is returned and NoMoreItems is raised. messages = [message] with contextlib.suppress(NoMoreItems): - while True: - new_message = await message.channel.history( # noqa: B305 - yes flake8, .next() is a thing here. - before=messages[-1].created_at, - after=messages[-1].created_at - timedelta(seconds=2) - ).next() + async for new_message in message.channel.history(before=message.created_at): + if messages[-1].created_at - new_message.created_at > timedelta(seconds=2): + break messages.append(new_message) content = "".join(message_.content for message_ in messages[::-1]) -- cgit v1.2.3 From 536d14a6adab060403ec9cfc2f288bc31ed048c5 Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Sun, 9 May 2021 17:50:31 +0200 Subject: Nominations: promote the mention regex to a constant --- bot/exts/recruitment/talentpool/_review.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bot/exts/recruitment/talentpool/_review.py b/bot/exts/recruitment/talentpool/_review.py index b0b9061db..2df5b2496 100644 --- a/bot/exts/recruitment/talentpool/_review.py +++ b/bot/exts/recruitment/talentpool/_review.py @@ -32,6 +32,9 @@ MAX_DAYS_IN_POOL = 30 # Maximum amount of characters allowed in a message MAX_MESSAGE_SIZE = 2000 +# Regex finding the user ID of a user mention +MENTION_RE = re.compile(r"<@!?(\d+?)>") + class Reviewer: """Schedules, formats, and publishes reviews of helper nominees.""" @@ -144,7 +147,7 @@ class Reviewer: content = "".join(message_.content for message_ in messages[::-1]) # We assume that the first user mentioned is the user that we are voting on - user_id = int(re.search(r"<@!?(\d+?)>", content).group(1)) + user_id = int(MENTION_RE.search(content).group(1)) # Get reaction counts seen = await count_unique_users_reaction( -- cgit v1.2.3 From 68daca390201f3b20d38ead73d769c51e462d20b Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Sun, 9 May 2021 18:02:31 +0200 Subject: Duckpond: make use of count_unique_users_reaction --- bot/exts/fun/duck_pond.py | 20 +++++++------------- bot/exts/recruitment/talentpool/_review.py | 14 +++++++++++--- bot/utils/messages.py | 12 +++++++----- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/bot/exts/fun/duck_pond.py b/bot/exts/fun/duck_pond.py index ee440dec2..c78b9c141 100644 --- a/bot/exts/fun/duck_pond.py +++ b/bot/exts/fun/duck_pond.py @@ -9,7 +9,7 @@ from discord.ext.commands import Cog, Context, command from bot import constants from bot.bot import Bot from bot.utils.checks import has_any_role -from bot.utils.messages import send_attachments +from bot.utils.messages import count_unique_users_reaction, send_attachments from bot.utils.webhooks import send_webhook log = logging.getLogger(__name__) @@ -78,18 +78,12 @@ class DuckPond(Cog): Only counts ducks added by staff members. """ - duck_reactors = set() - - # iterate over all reactions - for reaction in message.reactions: - # check if the current reaction is a duck - if not self._is_duck_emoji(reaction.emoji): - continue - - # update the set of reactors with all staff reactors - duck_reactors |= {user.id async for user in reaction.users() if self.is_staff(user)} - - return len(duck_reactors) + return await count_unique_users_reaction( + message, + lambda r: self._is_duck_emoji(r.emoji), + self.is_staff, + False + ) async def relay_message(self, message: Message) -> None: """Relays the message's content and attachments to the duck pond channel.""" diff --git a/bot/exts/recruitment/talentpool/_review.py b/bot/exts/recruitment/talentpool/_review.py index 2df5b2496..10bdac988 100644 --- a/bot/exts/recruitment/talentpool/_review.py +++ b/bot/exts/recruitment/talentpool/_review.py @@ -153,10 +153,18 @@ class Reviewer: seen = await count_unique_users_reaction( messages[0], lambda r: "ducky" in str(r) or str(r) == "\N{EYES}", - False + count_bots=False + ) + upvotes = await count_unique_users_reaction( + messages[0], + lambda r: str(r) == "\N{THUMBS UP SIGN}", + count_bots=False + ) + downvotes = await count_unique_users_reaction( + messages[0], + lambda r: str(r) == "\N{THUMBS DOWN SIGN}", + count_bots=False ) - upvotes = await count_unique_users_reaction(messages[0], lambda r: str(r) == "\N{THUMBS UP SIGN}", False) - downvotes = await count_unique_users_reaction(messages[0], lambda r: str(r) == "\N{THUMBS DOWN SIGN}", False) # Remove the first and last paragraphs stripped_content = content.split("\n\n", maxsplit=1)[1].rsplit("\n\n", maxsplit=1)[0] diff --git a/bot/utils/messages.py b/bot/utils/messages.py index d0d56e273..e886204dd 100644 --- a/bot/utils/messages.py +++ b/bot/utils/messages.py @@ -8,7 +8,7 @@ from io import BytesIO from typing import Callable, List, Optional, Sequence, Union import discord -from discord import Message, MessageType, Reaction +from discord import Message, MessageType, Reaction, User from discord.errors import HTTPException from discord.ext.commands import Context @@ -167,20 +167,22 @@ async def send_attachments( async def count_unique_users_reaction( message: discord.Message, - predicate: Callable[[Reaction], bool] = lambda _: True, + reaction_predicate: Callable[[Reaction], bool] = lambda _: True, + user_predicate: Callable[[User], bool] = lambda _: True, count_bots: bool = True ) -> int: """ Count the amount of unique users who reacted to the message. - A predicate function can be passed to check if this reaction should be counted, along with a count_bot flag. + A reaction_predicate function can be passed to check if this reaction should be counted, + another user_predicate to check if the user should also be counted along with a count_bot flag. """ unique_users = set() for reaction in message.reactions: - if predicate(reaction): + if reaction_predicate(reaction): async for user in reaction.users(): - if count_bots or not user.bot: + if (count_bots or not user.bot) and user_predicate(user): unique_users.add(user.id) return len(unique_users) -- cgit v1.2.3 From 77f80ca44b397ba165f3d5a3877a708a62e13371 Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Mon, 10 May 2021 19:03:56 +0200 Subject: Nomination: fix formatting issue --- bot/exts/recruitment/talentpool/_review.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bot/exts/recruitment/talentpool/_review.py b/bot/exts/recruitment/talentpool/_review.py index 10bdac988..caf0ed7e7 100644 --- a/bot/exts/recruitment/talentpool/_review.py +++ b/bot/exts/recruitment/talentpool/_review.py @@ -144,7 +144,11 @@ class Reviewer: break messages.append(new_message) - content = "".join(message_.content for message_ in messages[::-1]) + parts = [] + for message_ in messages[::-1]: + parts.append(message_.content) + parts.append("\n" if message_.content.endswith(".") else " ") + content = "".join(parts) # We assume that the first user mentioned is the user that we are voting on user_id = int(MENTION_RE.search(content).group(1)) @@ -185,7 +189,9 @@ class Reviewer: embed_title = f"Vote for `{user_id}`" channel = self.bot.get_channel(Channels.nomination_archive) - for number, part in enumerate(textwrap.wrap(embed_content, width=MAX_MESSAGE_SIZE, replace_whitespace=False)): + for number, part in enumerate( + textwrap.wrap(embed_content, width=MAX_MESSAGE_SIZE, replace_whitespace=False, placeholder="") + ): await channel.send(embed=Embed( title=embed_title if number == 0 else None, description="[...] " + part if number != 0 else part, -- cgit v1.2.3 From 75b3b140a8bd11000d3278c60409daad8ee55f9f Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Sat, 15 May 2021 18:42:44 +0200 Subject: Utils: fix indentation --- bot/utils/messages.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bot/utils/messages.py b/bot/utils/messages.py index e886204dd..b6f6c1f66 100644 --- a/bot/utils/messages.py +++ b/bot/utils/messages.py @@ -166,10 +166,10 @@ async def send_attachments( async def count_unique_users_reaction( - message: discord.Message, - reaction_predicate: Callable[[Reaction], bool] = lambda _: True, - user_predicate: Callable[[User], bool] = lambda _: True, - count_bots: bool = True + message: discord.Message, + reaction_predicate: Callable[[Reaction], bool] = lambda _: True, + user_predicate: Callable[[User], bool] = lambda _: True, + count_bots: bool = True ) -> int: """ Count the amount of unique users who reacted to the message. -- cgit v1.2.3 From 4ec0ec959957689318e25f034c6b7d3876f8ba10 Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Sat, 15 May 2021 18:43:54 +0200 Subject: Nomination: change archive timestamp to %Y/%m/%d --- bot/exts/recruitment/talentpool/_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/recruitment/talentpool/_review.py b/bot/exts/recruitment/talentpool/_review.py index caf0ed7e7..483354869 100644 --- a/bot/exts/recruitment/talentpool/_review.py +++ b/bot/exts/recruitment/talentpool/_review.py @@ -175,7 +175,7 @@ class Reviewer: result = f"**Passed** {Emojis.incident_actioned}" if passed else f"**Rejected** {Emojis.incident_unactioned}" colour = Colours.soft_green if passed else Colours.soft_red - timestamp = datetime.utcnow().strftime("%d/%m/%Y") + timestamp = datetime.utcnow().strftime("%Y/%m/%d") embed_content = ( f"{result} on {timestamp}\n" -- cgit v1.2.3 From b5f56689fc88e569c61f7f24762c5228ed59566e Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Sat, 15 May 2021 18:53:27 +0200 Subject: Nomination: consider that the first message has two role pings --- bot/exts/recruitment/talentpool/_review.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/bot/exts/recruitment/talentpool/_review.py b/bot/exts/recruitment/talentpool/_review.py index 483354869..e895c2a4d 100644 --- a/bot/exts/recruitment/talentpool/_review.py +++ b/bot/exts/recruitment/talentpool/_review.py @@ -34,6 +34,8 @@ MAX_MESSAGE_SIZE = 2000 # Regex finding the user ID of a user mention MENTION_RE = re.compile(r"<@!?(\d+?)>") +# Regex matching role pings +ROLE_MENTION_RE = re.compile(r"<@&\d+>") class Reviewer: @@ -134,15 +136,17 @@ class Reviewer: """Archive this vote to #nomination-archive.""" message = await message.fetch() - # We consider that if a message has been sent less than 2 second before the one being archived - # it is part of the same nomination. - # For that we try to get messages sent in this timeframe until none is returned and NoMoreItems is raised. + # We consider the first message in the nomination to contain the two role pings messages = [message] - with contextlib.suppress(NoMoreItems): - async for new_message in message.channel.history(before=message.created_at): - if messages[-1].created_at - new_message.created_at > timedelta(seconds=2): - break - messages.append(new_message) + if not len(ROLE_MENTION_RE.findall(message.content)) >= 2: + with contextlib.suppress(NoMoreItems): + async for new_message in message.channel.history(before=message.created_at): + messages.append(new_message) + + if len(ROLE_MENTION_RE.findall(new_message.content)) >= 2: + break + + log.debug(f"Found {len(messages)} messages: {', '.join(str(m.id) for m in messages)}") parts = [] for message_ in messages[::-1]: -- cgit v1.2.3