Fix /leave not working for anyone who's paused in the past
We now clear the re-activate value when a user is unpaused. Added bonus here is various bits of refactor and cleanup, with some tests
This commit is contained in:
parent
946de66f52
commit
f926a36069
5 changed files with 91 additions and 37 deletions
|
@ -79,18 +79,30 @@ class MatcherCog(commands.Cog):
|
||||||
logger.info("Handling /list command in %s %s from %s",
|
logger.info("Handling /list command in %s %s from %s",
|
||||||
interaction.guild.name, interaction.channel, interaction.user.name)
|
interaction.guild.name, interaction.channel, interaction.user.name)
|
||||||
|
|
||||||
matchees = matching.get_matchees_in_channel(
|
(matchees, paused) = matching.get_matchees_in_channel(
|
||||||
self.state, interaction.channel)
|
self.state, interaction.channel)
|
||||||
mentions = [m.mention for m in matchees]
|
|
||||||
msg = "Current matchees in this channel:\n" + \
|
msg = ""
|
||||||
f"{util.format_list(mentions)}"
|
|
||||||
|
if matchees:
|
||||||
|
mentions = [m.mention for m in matchees]
|
||||||
|
msg += f"There are {len(matchees)} active matchees:\n"
|
||||||
|
msg += f"{util.format_list(mentions)}\n"
|
||||||
|
|
||||||
|
if paused:
|
||||||
|
mentions = [m.mention for m in paused]
|
||||||
|
msg += f"\nThere are {len(mentions)} paused matchees:\n"
|
||||||
|
msg += f"{util.format_list([m.mention for m in paused])}\n"
|
||||||
|
|
||||||
tasks = self.state.get_channel_match_tasks(interaction.channel.id)
|
tasks = self.state.get_channel_match_tasks(interaction.channel.id)
|
||||||
for (day, hour, min) in tasks:
|
for (day, hour, min) in tasks:
|
||||||
next_run = util.get_next_datetime(day, hour)
|
next_run = util.get_next_datetime(day, hour)
|
||||||
date_str = util.datetime_as_discord_time(next_run)
|
date_str = util.datetime_as_discord_time(next_run)
|
||||||
msg += f"\nNext scheduled at {date_str}"
|
msg += f"\nA match is scheduled at {date_str}"
|
||||||
msg += f" with {min} members per group"
|
msg += f" with {min} members per group\n"
|
||||||
|
|
||||||
|
if not msg:
|
||||||
|
msg = "There are no matchees in this channel and no scheduled matches :("
|
||||||
|
|
||||||
await interaction.response.send_message(msg, ephemeral=True, silent=True)
|
await interaction.response.send_message(msg, ephemeral=True, silent=True)
|
||||||
|
|
||||||
|
|
|
@ -207,7 +207,9 @@ def get_matchees_in_channel(state: State, channel: discord.channel):
|
||||||
# Reactivate any unpaused users
|
# Reactivate any unpaused users
|
||||||
state.reactivate_users(channel.id)
|
state.reactivate_users(channel.id)
|
||||||
# Gather up the prospective matchees
|
# Gather up the prospective matchees
|
||||||
return [m for m in channel.members if state.get_user_active_in_channel(m.id, channel.id)]
|
active = [m for m in channel.members if state.get_user_active_in_channel(m.id, channel.id)]
|
||||||
|
paused = [m for m in channel.members if state.get_user_paused_in_channel(m.id, channel.id)]
|
||||||
|
return (active, paused)
|
||||||
|
|
||||||
|
|
||||||
def active_members_to_groups(state: State, channel: discord.channel, min_members: int):
|
def active_members_to_groups(state: State, channel: discord.channel, min_members: int):
|
||||||
|
|
|
@ -10,6 +10,7 @@ import pathlib
|
||||||
import copy
|
import copy
|
||||||
import logging
|
import logging
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
import matchy.util as util
|
||||||
|
|
||||||
logger = logging.getLogger("state")
|
logger = logging.getLogger("state")
|
||||||
logger.setLevel(logging.INFO)
|
logger.setLevel(logging.INFO)
|
||||||
|
@ -275,40 +276,42 @@ class State():
|
||||||
Check if a user has an auth scope
|
Check if a user has an auth scope
|
||||||
"owner" users have all scopes
|
"owner" users have all scopes
|
||||||
"""
|
"""
|
||||||
user = self._users.get(str(id), {})
|
scopes = util.get_nested_value(
|
||||||
scopes = user.get(_Key.SCOPES, [])
|
self._users, str(id), _Key.SCOPES, default=[])
|
||||||
return scope in scopes
|
return scope in scopes
|
||||||
|
|
||||||
|
@safe_write
|
||||||
def set_user_active_in_channel(self, id: str, channel_id: str, active: bool = True):
|
def set_user_active_in_channel(self, id: str, channel_id: str, active: bool = True):
|
||||||
"""Set a user as active (or not) on a given channel"""
|
"""Set a user as active (or not) on a given channel"""
|
||||||
self._set_user_channel_prop(id, channel_id, _Key.ACTIVE, active)
|
util.set_nested_value(
|
||||||
|
self._users, str(id), _Key.CHANNELS, str(channel_id), _Key.ACTIVE, value=active)
|
||||||
|
util.set_nested_value(
|
||||||
|
self._users, str(id), _Key.CHANNELS, str(channel_id), _Key.REACTIVATE, value=None)
|
||||||
|
|
||||||
def get_user_active_in_channel(self, id: str, channel_id: str) -> bool:
|
def get_user_active_in_channel(self, id: str, channel_id: str) -> bool:
|
||||||
"""Get a users active channels"""
|
"""Get a if a user is active in a channel"""
|
||||||
user = self._users.get(str(id), {})
|
return util.get_nested_value(self._users, str(id), _Key.CHANNELS, str(channel_id), _Key.ACTIVE)
|
||||||
channels = user.get(_Key.CHANNELS, {})
|
|
||||||
return str(channel_id) in [channel for (channel, props) in channels.items() if props.get(_Key.ACTIVE, False)]
|
|
||||||
|
|
||||||
|
def get_user_paused_in_channel(self, id: str, channel_id: str) -> str:
|
||||||
|
"""Get a the user reactivate time if it exists"""
|
||||||
|
return util.get_nested_value(self._users, str(id), _Key.CHANNELS, str(channel_id), _Key.REACTIVATE)
|
||||||
|
|
||||||
|
@safe_write
|
||||||
def set_user_paused_in_channel(self, id: str, channel_id: str, until: datetime):
|
def set_user_paused_in_channel(self, id: str, channel_id: str, until: datetime):
|
||||||
"""Sets a user as paused in a channel"""
|
"""Sets a user as inactive in a channel with a reactivation time"""
|
||||||
# Deactivate the user in the channel first
|
util.set_nested_value(
|
||||||
self.set_user_active_in_channel(id, channel_id, False)
|
self._users, str(id), _Key.CHANNELS, str(channel_id), _Key.ACTIVE, value=False)
|
||||||
|
util.set_nested_value(
|
||||||
self._set_user_channel_prop(
|
self._users, str(id), _Key.CHANNELS, str(channel_id), _Key.REACTIVATE, value=datetime_to_ts(until))
|
||||||
id, channel_id, _Key.REACTIVATE, datetime_to_ts(until))
|
|
||||||
|
|
||||||
@safe_write
|
@safe_write
|
||||||
def reactivate_users(self, channel_id: str):
|
def reactivate_users(self, channel_id: str):
|
||||||
"""Reactivate any users who've passed their reactivation time on this channel"""
|
"""Reactivate any users who've passed their reactivation time on this channel"""
|
||||||
for user in self._users.values():
|
for user in self._users:
|
||||||
channels = user.get(_Key.CHANNELS, {})
|
reactivate = self.get_user_paused_in_channel(
|
||||||
channel = channels.get(str(channel_id), {})
|
str(user), str(channel_id))
|
||||||
if channel and not channel[_Key.ACTIVE]:
|
if reactivate and datetime.now() > ts_to_datetime(reactivate):
|
||||||
reactivate = channel.get(_Key.REACTIVATE, None)
|
self.set_user_active_in_channel(str(user), str(channel_id))
|
||||||
# Check if we've gone past the reactivation time and re-activate
|
|
||||||
if reactivate and datetime.now() > ts_to_datetime(reactivate):
|
|
||||||
channel[_Key.ACTIVE] = True
|
|
||||||
del channel[_Key.REACTIVATE]
|
|
||||||
|
|
||||||
def get_active_match_tasks(self, time: datetime | None = None) -> Generator[str, int]:
|
def get_active_match_tasks(self, time: datetime | None = None) -> Generator[str, int]:
|
||||||
"""
|
"""
|
||||||
|
@ -376,14 +379,6 @@ class State():
|
||||||
def _tasks(self) -> dict[str]:
|
def _tasks(self) -> dict[str]:
|
||||||
return self._dict[_Key.TASKS]
|
return self._dict[_Key.TASKS]
|
||||||
|
|
||||||
@safe_write
|
|
||||||
def _set_user_channel_prop(self, id: str, channel_id: str, key: str, value):
|
|
||||||
"""Set a user channel property helper"""
|
|
||||||
user = self._users.setdefault(str(id), {})
|
|
||||||
channels = user.setdefault(_Key.CHANNELS, {})
|
|
||||||
channel = channels.setdefault(str(channel_id), {})
|
|
||||||
channel[key] = value
|
|
||||||
|
|
||||||
|
|
||||||
def load_from_file(file: str) -> State:
|
def load_from_file(file: str) -> State:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
|
|
||||||
def get_day_with_suffix(day):
|
def get_day_with_suffix(day):
|
||||||
|
@ -42,3 +43,19 @@ def iterate_all_shifts(list: list):
|
||||||
for _ in range(len(list)-1):
|
for _ in range(len(list)-1):
|
||||||
list = list[1:] + [list[0]]
|
list = list[1:] + [list[0]]
|
||||||
yield list
|
yield list
|
||||||
|
|
||||||
|
|
||||||
|
def get_nested_value(d, *keys, default=None):
|
||||||
|
"""Helper method for walking down an optional set of nested dicts to get a value"""
|
||||||
|
return reduce(lambda d, key: d.get(key, {}), keys, d) or default
|
||||||
|
|
||||||
|
|
||||||
|
def set_nested_value(d, *keys, value=None):
|
||||||
|
"""Helper method for walking down an optional set of nested dicts to set a value"""
|
||||||
|
for key in keys[:-1]:
|
||||||
|
d = d.setdefault(key, {})
|
||||||
|
leaf = keys[-1]
|
||||||
|
if value is not None:
|
||||||
|
d[leaf] = value
|
||||||
|
elif leaf in d:
|
||||||
|
del d[leaf]
|
||||||
|
|
|
@ -10,3 +10,31 @@ def test_iterate_all_shifts():
|
||||||
[3, 4, 1, 2],
|
[3, 4, 1, 2],
|
||||||
[4, 1, 2, 3],
|
[4, 1, 2, 3],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_nested_dict_value():
|
||||||
|
d = {
|
||||||
|
"x": {
|
||||||
|
"y": {
|
||||||
|
"z": {
|
||||||
|
"val": 42
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert 42 == util.get_nested_value(d, "x", "y", "z", "val")
|
||||||
|
assert 16 == util.get_nested_value(d, "x", "y", "z", "vol", default=16)
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_nested_dict_value():
|
||||||
|
d = {
|
||||||
|
"x": {
|
||||||
|
"y": {
|
||||||
|
"z": {
|
||||||
|
"val": 42
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
util.set_nested_value(d, "x", "y", "z", "val", value=52)
|
||||||
|
assert 52 == util.get_nested_value(d, "x", "y", "z", "val")
|
||||||
|
|
Loading…
Add table
Reference in a new issue