aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Xithrius <[email protected]>2021-09-03 14:31:22 -0700
committerGravatar GitHub <[email protected]>2021-09-03 14:31:22 -0700
commite180ef94c25ebcde42118f0de7714f1f787595c4 (patch)
treeb04c1a229034f3f61efb9c584e4aef2f9bfcd6ea
parentMerge pull request #1796 from Numerlor/doc-default-base_url (diff)
parentMerge branch 'main' into ks123/goodbye-talentpool-channel (diff)
Merge pull request #1487 from python-discord/ks123/goodbye-talentpool-channel
Goodbye, #talent-pool
-rw-r--r--bot/constants.py2
-rw-r--r--bot/exts/recruitment/talentpool/_cog.py164
-rw-r--r--bot/exts/recruitment/talentpool/_review.py28
-rw-r--r--config-default.yml3
4 files changed, 111 insertions, 86 deletions
diff --git a/bot/constants.py b/bot/constants.py
index 4e99df7f3..f99913b17 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -472,7 +472,6 @@ class Channels(metaclass=YAMLGetter):
voice_chat_1: int
big_brother_logs: int
- talent_pool: int
class Webhooks(metaclass=YAMLGetter):
@@ -483,7 +482,6 @@ class Webhooks(metaclass=YAMLGetter):
dev_log: int
duck_pond: int
incidents_archive: int
- talent_pool: int
class Roles(metaclass=YAMLGetter):
diff --git a/bot/exts/recruitment/talentpool/_cog.py b/bot/exts/recruitment/talentpool/_cog.py
index c297f70c2..a317c6645 100644
--- a/bot/exts/recruitment/talentpool/_cog.py
+++ b/bot/exts/recruitment/talentpool/_cog.py
@@ -1,6 +1,6 @@
import logging
import textwrap
-from collections import ChainMap
+from collections import ChainMap, defaultdict
from io import StringIO
from typing import Union
@@ -11,12 +11,12 @@ from discord.ext.commands import Cog, Context, group, has_any_role
from bot.api import ResponseCodeError
from bot.bot import Bot
-from bot.constants import Channels, Emojis, Guild, MODERATION_ROLES, Roles, STAFF_ROLES, Webhooks
+from bot.constants import Channels, Emojis, Guild, MODERATION_ROLES, Roles, STAFF_ROLES
from bot.converters import MemberOrUser
-from bot.exts.moderation.watchchannels._watchchannel import WatchChannel
from bot.exts.recruitment.talentpool._review import Reviewer
from bot.pagination import LinePaginator
from bot.utils import time
+from bot.utils.time import get_time_delta
AUTOREVIEW_ENABLED_KEY = "autoreview_enabled"
REASON_MAX_CHARS = 1000
@@ -24,25 +24,17 @@ REASON_MAX_CHARS = 1000
log = logging.getLogger(__name__)
-class TalentPool(WatchChannel, Cog, name="Talentpool"):
- """Relays messages of helper candidates to a watch channel to observe them."""
+class TalentPool(Cog, name="Talentpool"):
+ """Used to nominate potential helper candidates."""
# RedisCache[str, bool]
# Can contain a single key, "autoreview_enabled", with the value a bool indicating if autoreview is enabled.
talentpool_settings = RedisCache()
def __init__(self, bot: Bot) -> None:
- super().__init__(
- bot,
- destination=Channels.talent_pool,
- webhook_id=Webhooks.talent_pool,
- api_endpoint='bot/nominations',
- api_default_params={'active': 'true', 'ordering': '-inserted_at'},
- logger=log,
- disable_header=True,
- )
-
+ self.bot = bot
self.reviewer = Reviewer(self.__class__.__name__, bot, self)
+ self.api_default_params = {'active': 'true', 'ordering': '-inserted_at'}
self.bot.loop.create_task(self.schedule_autoreviews())
async def schedule_autoreviews(self) -> None:
@@ -50,12 +42,31 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):
if await self.autoreview_enabled():
await self.reviewer.reschedule_reviews()
else:
- self.log.trace("Not scheduling reviews as autoreview is disabled.")
+ log.trace("Not scheduling reviews as autoreview is disabled.")
async def autoreview_enabled(self) -> bool:
"""Return whether automatic posting of nomination reviews is enabled."""
return await self.talentpool_settings.get(AUTOREVIEW_ENABLED_KEY, True)
+ async def refresh_cache(self) -> bool:
+ """Updates TalentPool users cache."""
+ try:
+ data = await self.bot.api_client.get(
+ 'bot/nominations',
+ params=self.api_default_params
+ )
+ except ResponseCodeError as err:
+ log.exception("Failed to fetch the currently nominated users from the API", exc_info=err)
+ return False
+
+ self.cache = defaultdict(dict)
+
+ for entry in data:
+ user_id = entry.pop('user')
+ self.cache[user_id] = entry
+
+ return True
+
@group(name='talentpool', aliases=('tp', 'talent', 'nomination', 'n'), invoke_without_command=True)
@has_any_role(*MODERATION_ROLES)
async def nomination_group(self, ctx: Context) -> None:
@@ -106,25 +117,29 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):
else:
await ctx.send("Autoreview is currently disabled")
- @nomination_group.command(name='watched', aliases=('all', 'list'), root_aliases=("nominees",))
+ @nomination_group.command(
+ name="nominees",
+ aliases=("nominated", "all", "list", "watched"),
+ root_aliases=("nominees",)
+ )
@has_any_role(*MODERATION_ROLES)
- async def watched_command(
+ async def list_command(
self,
ctx: Context,
oldest_first: bool = False,
update_cache: bool = True
) -> None:
"""
- Shows the users that are currently being monitored in the talent pool.
+ Shows the users that are currently in the talent pool.
The optional kwarg `oldest_first` can be used to order the list by oldest nomination.
The optional kwarg `update_cache` can be used to update the user
cache using the API before listing the users.
"""
- await self.list_watched_users(ctx, oldest_first=oldest_first, update_cache=update_cache)
+ await self.list_nominated_users(ctx, oldest_first=oldest_first, update_cache=update_cache)
- async def list_watched_users(
+ async def list_nominated_users(
self,
ctx: Context,
oldest_first: bool = False,
@@ -141,16 +156,27 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):
The optional kwarg `update_cache` specifies whether the cache should
be refreshed by polling the API.
"""
- # TODO Once the watch channel is removed, this can be done in a smarter way, without splitting and overriding
- # the list_watched_users function.
- watched_data = await self.prepare_watched_users_data(ctx, oldest_first, update_cache)
+ successful_update = False
+ if update_cache:
+ if not (successful_update := await self.refresh_cache()):
+ await ctx.send(":warning: Unable to update cache. Data may be inaccurate.")
- if update_cache and not watched_data["updated"]:
- await ctx.send(f":x: Failed to update {self.__class__.__name__} user cache, serving from cache")
+ nominations = self.cache.items()
+ if oldest_first:
+ nominations = reversed(nominations)
lines = []
- for user_id, line in watched_data["info"].items():
- if self.watched_users[user_id]['reviewed']:
+
+ for user_id, user_data in nominations:
+ member = ctx.guild.get_member(user_id)
+ line = f"• `{user_id}`"
+ if member:
+ line += f" ({member.name}#{member.discriminator})"
+ inserted_at = user_data['inserted_at']
+ line += f", added {get_time_delta(inserted_at)}"
+ if not member: # Cross off users who left the server.
+ line = f"~~{line}~~"
+ if user_data['reviewed']:
line += " *(reviewed)*"
elif user_id in self.reviewer:
line += " *(scheduled)*"
@@ -160,7 +186,7 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):
lines = ("There's nothing here yet.",)
embed = Embed(
- title=watched_data["title"],
+ title=f"Talent Pool active nominations ({'updated' if update_cache and successful_update else 'cached'})",
color=Color.blue()
)
await LinePaginator.paginate(lines, ctx, embed, empty=False)
@@ -169,26 +195,30 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):
@has_any_role(*MODERATION_ROLES)
async def oldest_command(self, ctx: Context, update_cache: bool = True) -> None:
"""
- Shows talent pool monitored users ordered by oldest nomination.
+ Shows talent pool users ordered by oldest nomination.
The optional kwarg `update_cache` can be used to update the user
cache using the API before listing the users.
"""
- await ctx.invoke(self.watched_command, oldest_first=True, update_cache=update_cache)
+ await ctx.invoke(self.list_command, oldest_first=True, update_cache=update_cache)
- @nomination_group.command(name='forcewatch', aliases=('fw', 'forceadd', 'fa'), root_aliases=("forcenominate",))
+ @nomination_group.command(
+ name="forcenominate",
+ aliases=("fw", "forceadd", "fa", "fn", "forcewatch"),
+ root_aliases=("forcenominate",)
+ )
@has_any_role(*MODERATION_ROLES)
- async def force_watch_command(self, ctx: Context, user: MemberOrUser, *, reason: str = '') -> None:
+ async def force_nominate_command(self, ctx: Context, user: MemberOrUser, *, reason: str = '') -> None:
"""
Adds the given `user` to the talent pool, from any channel.
A `reason` for adding the user to the talent pool is optional.
"""
- await self._watch_user(ctx, user, reason)
+ await self._nominate_user(ctx, user, reason)
- @nomination_group.command(name='watch', aliases=('w', 'add', 'a'), root_aliases=("nominate",))
+ @nomination_group.command(name='nominate', aliases=("w", "add", "a", "watch"), root_aliases=("nominate",))
@has_any_role(*STAFF_ROLES)
- async def watch_command(self, ctx: Context, user: MemberOrUser, *, reason: str = '') -> None:
+ async def nominate_command(self, ctx: Context, user: MemberOrUser, *, reason: str = '') -> None:
"""
Adds the given `user` to the talent pool.
@@ -199,26 +229,26 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):
if any(role.id in MODERATION_ROLES for role in ctx.author.roles):
await ctx.send(
f":x: Nominations should be run in the <#{Channels.nominations}> channel. "
- "Use `!tp forcewatch` to override this check."
+ "Use `!tp forcenominate` to override this check."
)
else:
await ctx.send(f":x: Nominations must be run in the <#{Channels.nominations}> channel")
return
- await self._watch_user(ctx, user, reason)
+ await self._nominate_user(ctx, user, reason)
- async def _watch_user(self, ctx: Context, user: MemberOrUser, reason: str) -> None:
+ async def _nominate_user(self, ctx: Context, user: MemberOrUser, reason: str) -> None:
"""Adds the given user to the talent pool."""
if user.bot:
- await ctx.send(f":x: I'm sorry {ctx.author}, I'm afraid I can't do that. I only watch humans.")
+ await ctx.send(f":x: I'm sorry {ctx.author}, I'm afraid I can't do that. Only humans can be nominated.")
return
if isinstance(user, Member) and any(role.id in STAFF_ROLES for role in user.roles):
await ctx.send(":x: Nominating staff members, eh? Here's a cookie :cookie:")
return
- if not await self.fetch_user_cache():
- await ctx.send(f":x: Failed to update the user cache; can't add {user}")
+ if not await self.refresh_cache():
+ await ctx.send(f":x: Failed to update the cache; can't add {user}")
return
if len(reason) > REASON_MAX_CHARS:
@@ -227,7 +257,7 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):
# Manual request with `raise_for_status` as False because we want the actual response
session = self.bot.api_client.session
- url = self.bot.api_client._url_for(self.api_endpoint)
+ url = self.bot.api_client._url_for('bot/nominations')
kwargs = {
'json': {
'actor': ctx.author.id,
@@ -249,13 +279,13 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):
else:
resp.raise_for_status()
- self.watched_users[user.id] = response_data
+ self.cache[user.id] = response_data
if await self.autoreview_enabled() and user.id not in self.reviewer:
self.reviewer.schedule_review(user.id)
history = await self.bot.api_client.get(
- self.api_endpoint,
+ 'bot/nominations',
params={
"user__id": str(user.id),
"active": "false",
@@ -274,7 +304,7 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):
async def history_command(self, ctx: Context, user: MemberOrUser) -> None:
"""Shows the specified user's nomination history."""
result = await self.bot.api_client.get(
- self.api_endpoint,
+ 'bot/nominations',
params={
'user__id': str(user.id),
'ordering': "-active,-inserted_at"
@@ -298,20 +328,20 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):
max_size=1000
)
- @nomination_group.command(name='unwatch', aliases=('end', ), root_aliases=("unnominate",))
+ @nomination_group.command(name="end", aliases=("unwatch", "unnominate"), root_aliases=("unnominate",))
@has_any_role(*MODERATION_ROLES)
- async def unwatch_command(self, ctx: Context, user: MemberOrUser, *, reason: str) -> None:
+ async def end_nomination_command(self, ctx: Context, user: MemberOrUser, *, reason: str) -> None:
"""
Ends the active nomination of the specified user with the given reason.
Providing a `reason` is required.
"""
if len(reason) > REASON_MAX_CHARS:
- await ctx.send(f":x: Maxiumum allowed characters for the end reason is {REASON_MAX_CHARS}.")
+ await ctx.send(f":x: Maximum allowed characters for the end reason is {REASON_MAX_CHARS}.")
return
- if await self.unwatch(user.id, reason):
- await ctx.send(f":white_check_mark: Messages sent by {user.mention} will no longer be relayed")
+ if await self.end_nomination(user.id, reason):
+ await ctx.send(f":white_check_mark: Successfully un-nominated {user}")
else:
await ctx.send(":x: The specified user does not have an active nomination")
@@ -330,10 +360,10 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):
return
try:
- nomination = await self.bot.api_client.get(f"{self.api_endpoint}/{nomination_id}")
+ nomination = await self.bot.api_client.get(f"bot/nominations/{nomination_id}")
except ResponseCodeError as e:
if e.response.status == 404:
- self.log.trace(f"Nomination API 404: Can't find a nomination with id {nomination_id}")
+ log.trace(f"Nomination API 404: Can't find a nomination with id {nomination_id}")
await ctx.send(f":x: Can't find a nomination with id `{nomination_id}`")
return
else:
@@ -347,13 +377,13 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):
await ctx.send(f":x: {actor.mention} doesn't have an entry in this nomination.")
return
- self.log.trace(f"Changing reason for nomination with id {nomination_id} of actor {actor} to {repr(reason)}")
+ log.trace(f"Changing reason for nomination with id {nomination_id} of actor {actor} to {repr(reason)}")
await self.bot.api_client.patch(
- f"{self.api_endpoint}/{nomination_id}",
+ f"bot/nominations/{nomination_id}",
json={"actor": actor.id, "reason": reason}
)
- await self.fetch_user_cache() # Update cache
+ await self.refresh_cache() # Update cache
await ctx.send(":white_check_mark: Successfully updated nomination reason.")
@nomination_edit_group.command(name='end_reason')
@@ -365,10 +395,10 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):
return
try:
- nomination = await self.bot.api_client.get(f"{self.api_endpoint}/{nomination_id}")
+ nomination = await self.bot.api_client.get(f"bot/nominations/{nomination_id}")
except ResponseCodeError as e:
if e.response.status == 404:
- self.log.trace(f"Nomination API 404: Can't find a nomination with id {nomination_id}")
+ log.trace(f"Nomination API 404: Can't find a nomination with id {nomination_id}")
await ctx.send(f":x: Can't find a nomination with id `{nomination_id}`")
return
else:
@@ -378,13 +408,13 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):
await ctx.send(":x: Can't edit the end reason of an active nomination.")
return
- self.log.trace(f"Changing end reason for nomination with id {nomination_id} to {repr(reason)}")
+ log.trace(f"Changing end reason for nomination with id {nomination_id} to {repr(reason)}")
await self.bot.api_client.patch(
- f"{self.api_endpoint}/{nomination_id}",
+ f"bot/nominations/{nomination_id}",
json={"end_reason": reason}
)
- await self.fetch_user_cache() # Update cache.
+ await self.refresh_cache() # Update cache.
await ctx.send(":white_check_mark: Updated the end reason of the nomination!")
@nomination_group.command(aliases=('mr',))
@@ -419,7 +449,7 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):
@Cog.listener()
async def on_member_ban(self, guild: Guild, user: Union[MemberOrUser]) -> None:
"""Remove `user` from the talent pool after they are banned."""
- await self.unwatch(user.id, "User was banned.")
+ await self.end_nomination(user.id, "User was banned.")
@Cog.listener()
async def on_raw_reaction_add(self, payload: RawReactionActionEvent) -> None:
@@ -441,10 +471,10 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):
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:
+ async def end_nomination(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(
- self.api_endpoint,
+ 'bot/nominations',
params=ChainMap(
{"user__id": str(user_id)},
self.api_default_params,
@@ -459,11 +489,11 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):
nomination = active_nomination[0]
await self.bot.api_client.patch(
- f"{self.api_endpoint}/{nomination['id']}",
+ f"bot/nominations/{nomination['id']}",
json={'end_reason': reason, 'active': False}
)
- self._remove_user(user_id)
+ self.cache.pop(user_id)
if await self.autoreview_enabled():
self.reviewer.cancel(user_id)
@@ -512,7 +542,7 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):
{entries_string}
End date: {end_date}
- Unwatch reason: {nomination_object["end_reason"]}
+ Unnomination reason: {nomination_object["end_reason"]}
===============
"""
)
diff --git a/bot/exts/recruitment/talentpool/_review.py b/bot/exts/recruitment/talentpool/_review.py
index 4d496a1f7..3ffbf93f3 100644
--- a/bot/exts/recruitment/talentpool/_review.py
+++ b/bot/exts/recruitment/talentpool/_review.py
@@ -58,9 +58,9 @@ class Reviewer:
log.trace("Rescheduling reviews")
await self.bot.wait_until_guild_available()
# TODO Once the watch channel is removed, this can be done in a smarter way, e.g create a sync function.
- await self._pool.fetch_user_cache()
+ await self._pool.refresh_cache()
- for user_id, user_data in self._pool.watched_users.items():
+ for user_id, user_data in self._pool.cache.items():
if not user_data["reviewed"]:
self.schedule_review(user_id)
@@ -68,7 +68,7 @@ class Reviewer:
"""Schedules a single user for review."""
log.trace(f"Scheduling review of user with ID {user_id}")
- user_data = self._pool.watched_users.get(user_id)
+ user_data = self._pool.cache.get(user_id)
inserted_at = isoparse(user_data['inserted_at']).replace(tzinfo=None)
review_at = inserted_at + timedelta(days=MAX_DAYS_IN_POOL)
@@ -96,18 +96,18 @@ class Reviewer:
await last_message.add_reaction(reaction)
if update_database:
- nomination = self._pool.watched_users.get(user_id)
- await self.bot.api_client.patch(f"{self._pool.api_endpoint}/{nomination['id']}", json={"reviewed": True})
+ nomination = self._pool.cache.get(user_id)
+ await self.bot.api_client.patch(f"bot/nominations/{nomination['id']}", json={"reviewed": True})
async def make_review(self, user_id: int) -> typing.Tuple[str, Optional[Emoji]]:
"""Format a generic review of a user and return it with the reviewed emoji."""
log.trace(f"Formatting the review of {user_id}")
- # Since `watched_users` is a defaultdict, we should take care
+ # Since `cache` is a defaultdict, we should take care
# not to accidentally insert the IDs of users that have no
- # active nominated by using the `watched_users.get(user_id)`
- # instead of `watched_users[user_id]`.
- nomination = self._pool.watched_users.get(user_id)
+ # active nominated by using the `cache.get(user_id)`
+ # instead of `cache[user_id]`.
+ nomination = self._pool.cache.get(user_id)
if not nomination:
log.trace(f"There doesn't appear to be an active nomination for {user_id}")
return "", None
@@ -332,7 +332,7 @@ class Reviewer:
"""
log.trace(f"Fetching the nomination history data for {member.id}'s review")
history = await self.bot.api_client.get(
- self._pool.api_endpoint,
+ "bot/nominations",
params={
"user__id": str(member.id),
"active": "false",
@@ -390,18 +390,18 @@ class Reviewer:
Returns True if the user was successfully marked as reviewed, False otherwise.
"""
log.trace(f"Updating user {user_id} as reviewed")
- await self._pool.fetch_user_cache()
- if user_id not in self._pool.watched_users:
+ await self._pool.refresh_cache()
+ if user_id not in self._pool.cache:
log.trace(f"Can't find a nominated user with id {user_id}")
await ctx.send(f":x: Can't find a currently nominated user with id `{user_id}`")
return False
- nomination = self._pool.watched_users.get(user_id)
+ nomination = self._pool.cache.get(user_id)
if nomination["reviewed"]:
await ctx.send(":x: This nomination was already reviewed, but here's a cookie :cookie:")
return False
- await self.bot.api_client.patch(f"{self._pool.api_endpoint}/{nomination['id']}", json={"reviewed": True})
+ await self.bot.api_client.patch(f"bot/nominations/{nomination['id']}", json={"reviewed": True})
if user_id in self._review_scheduler:
self._review_scheduler.cancel(user_id)
diff --git a/config-default.yml b/config-default.yml
index baece5c51..a18fdafa5 100644
--- a/config-default.yml
+++ b/config-default.yml
@@ -232,7 +232,6 @@ guild:
# Watch
big_brother_logs: &BB_LOGS 468507907357409333
- talent_pool: &TALENT_POOL 534321732593647616
moderation_categories:
- *MODS_CATEGORY
@@ -305,7 +304,6 @@ guild:
duck_pond: 637821475327311927
incidents_archive: 720671599790915702
python_news: &PYNEWS_WEBHOOK 704381182279942324
- talent_pool: 569145364800602132
filter:
@@ -336,7 +334,6 @@ filter:
- *MESSAGE_LOG
- *MOD_LOG
- *STAFF_LOUNGE
- - *TALENT_POOL
role_whitelist:
- *ADMINS_ROLE