aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/constants.py3
-rw-r--r--bot/exts/recruitment/talentpool/_cog.py22
-rw-r--r--bot/exts/recruitment/talentpool/_review.py46
-rw-r--r--bot/utils/messages.py24
-rw-r--r--config-default.yml3
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