Skip to content

Commit 29238c5

Browse files
committed
Add status command
1 parent ad9752f commit 29238c5

1 file changed

Lines changed: 58 additions & 15 deletions

File tree

bot/exts/moderation/modpings.py

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import UTC, timedelta
1+
from datetime import UTC, datetime, timedelta
22

33
import arrow
44
import dateutil
@@ -89,6 +89,24 @@ async def reschedule_roles(self) -> None:
8989
else:
9090
await self.handle_moderator_state(mod)
9191

92+
@staticmethod
93+
async def _parse_schedule(schedule: str) -> tuple[datetime, datetime]:
94+
"""Parse the schedule string stored in the schedules cache into the closest start and end times."""
95+
start_time, shift_duration = schedule.split("|")
96+
start = dateutil_parse(start_time).replace(tzinfo=UTC)
97+
end = start + timedelta(seconds=int(shift_duration))
98+
now = arrow.utcnow()
99+
100+
# Move the shift's day such that the end time is in the future and is closest.
101+
if start - timedelta(days=1) < now < end - timedelta(days=1): # The shift started yesterday and is ongoing.
102+
start -= timedelta(days=1)
103+
end -= timedelta(days=1)
104+
elif now > end: # Today's shift already ended, next one is tomorrow.
105+
start += timedelta(days=1)
106+
end += timedelta(days=1)
107+
108+
return start, end
109+
92110
async def handle_moderator_state(self, mod: Member) -> None:
93111
"""Add/remove and/or schedule add/remove of the moderators role according to the mod's state in the caches."""
94112
expiry_iso = await self.pings_off_mods.get(mod.id, None)
@@ -103,23 +121,12 @@ async def handle_moderator_state(self, mod: Member) -> None:
103121
await mod.add_roles(self.moderators_role, reason="Pings off period expired.")
104122
return
105123

106-
start_time, shift_duration = schedule_str.split("|")
107-
start = dateutil_parse(start_time).replace(tzinfo=UTC)
108-
end = start + timedelta(seconds=int(shift_duration))
109-
now = arrow.utcnow()
110-
111-
# Move the shift's day such that the end time is in the future and is closest.
112-
if start - timedelta(days=1) < now < end - timedelta(days=1): # The shift started yesterday and is ongoing.
113-
start -= timedelta(days=1)
114-
end -= timedelta(days=1)
115-
elif now > end: # Today's shift already ended, next one is tomorrow.
116-
start += timedelta(days=1)
117-
end += timedelta(days=1)
124+
start, end = await self._parse_schedule(schedule_str)
118125

119126
# The calls to `handle_moderator_state` here aren't recursive as the scheduler creates separate tasks.
120127
# Start/end have to be differentiated in scheduler task ID. The task is removed from the scheduler only after
121128
# completion. That means that task with ID X can't schedule a task with the same ID X.
122-
if start < now < end:
129+
if start < arrow.utcnow() < end:
123130
if mod.get_role(self.moderators_role.id) is None:
124131
await mod.add_roles(self.moderators_role, reason="Mod active hours started.")
125132
if f"{mod.id}_end" not in self._shift_scheduler:
@@ -136,6 +143,33 @@ async def end_pings_off_period(self, mod: Member) -> None:
136143
await self.pings_off_mods.delete(mod.id)
137144
await self.handle_moderator_state(mod)
138145

146+
async def _get_current_status(self, mod_id: int) -> str:
147+
"""Build a string summarizing the moderator's current state and schedule (if one exists)."""
148+
state = "on"
149+
expiry_iso = await self.pings_off_mods.get(mod_id)
150+
if expiry_iso is not None:
151+
state = f"off until {discord_timestamp(isoparse(expiry_iso), format=TimestampFormats.DAY_TIME)}"
152+
153+
schedule = ""
154+
schedule_str = await self.modpings_schedules.get(mod_id, None)
155+
if schedule_str is not None:
156+
start, end = await self._parse_schedule(schedule_str)
157+
if state == "on":
158+
if start < arrow.utcnow() < end:
159+
state = "on according to schedule"
160+
else:
161+
state = "off according to schedule"
162+
if state.startswith("off until"):
163+
schedule = " Otherwise, pings are on every day between "
164+
else:
165+
schedule = " Pings are on every day between "
166+
schedule += (
167+
f"{discord_timestamp(start, TimestampFormats.TIME)} and "
168+
f"{discord_timestamp(end, TimestampFormats.TIME)}."
169+
)
170+
171+
return f"Pings are {state}.{schedule}"
172+
139173
@group(name="modpings", aliases=("modping",), invoke_without_command=True)
140174
async def modpings_group(self, ctx: Context) -> None:
141175
"""Allow the removal and re-addition of the pingable moderators role."""
@@ -201,7 +235,8 @@ async def on_command(self, ctx: Context) -> None:
201235

202236
await self.handle_moderator_state(mod)
203237

204-
await ctx.send(f"{Emojis.check_mark} Moderators role has been re-applied.") # TODO make message more accurate.
238+
status = await self._get_current_status(mod.id)
239+
await ctx.send(f"{Emojis.check_mark} {status}")
205240

206241
@modpings_group.group(name="schedule", aliases=("s",), invoke_without_command=True)
207242
async def schedule_modpings(self, ctx: Context, start_time: str, end_time: str, tz: float | None) -> None:
@@ -259,11 +294,19 @@ async def modpings_schedule_delete(self, ctx: Context) -> None:
259294
await self.handle_moderator_state(ctx.author)
260295
await ctx.reply(f"{Emojis.ok_hand} Deleted your modpings schedule.")
261296

297+
@modpings_group.command(name="status", aliases=("state",))
298+
async def status_command(self, ctx: Context) -> None:
299+
"""Show your current state and schedule (if one exists)."""
300+
status = await self._get_current_status(ctx.author.id)
301+
await ctx.reply(f":information: {status}")
302+
262303
@modpings_group.command(name="sync")
263304
async def sync_command(self, ctx: Context) -> None:
264305
"""
265306
Attempt to re-sync your pingable moderators role with the stored state.
266307
308+
You can view your stored state using the `modpings status` command.
309+
267310
If there is a reoccurring problem, please report it.
268311
"""
269312
await self.handle_moderator_state(ctx.author)

0 commit comments

Comments
 (0)