diff options
| author | 2021-04-23 20:40:29 +0200 | |
|---|---|---|
| committer | 2021-04-23 20:40:29 +0200 | |
| commit | b26368c10caad43841290c3bb17495d75643c32e (patch) | |
| tree | a309e8a2eb4d3292f155c6b3f84321a5180040b8 | |
| parent | Merge pull request #1546 from ToxicKidz/remind-embed-timestamp (diff) | |
Nominations: automate post archiving
This commit adds a new reaction handler to the talentpool cog to automatically unpin and archive mesage in #nomination-voting
| -rw-r--r-- | bot/constants.py | 3 | ||||
| -rw-r--r-- | bot/exts/recruitment/talentpool/_cog.py | 22 | ||||
| -rw-r--r-- | bot/exts/recruitment/talentpool/_review.py | 46 | ||||
| -rw-r--r-- | bot/utils/messages.py | 24 | ||||
| -rw-r--r-- | 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 |