feat(schedule): Complete schedule cmd impl.

This commit is contained in:
2023-09-30 12:13:50 +03:00
parent ca963ee8b1
commit 1f92957393
2 changed files with 199 additions and 12 deletions

View File

@@ -29,7 +29,7 @@ from .settings import ScheduleSettings, ScheduleConfig
from .ui.scheduleui import ScheduleUI
from .ui.settingui import ScheduleSettingUI
from .core import TimeSlot, ScheduledSession, SessionMember
from .lib import slotid_to_utc, time_to_slotid
from .lib import slotid_to_utc, time_to_slotid, format_until
_p, _np = babel._p, babel._np
@@ -732,12 +732,29 @@ class ScheduleCog(LionCog):
"View and manage your scheduled session."
)
)
@appcmds.rename(
cancel=_p(
'cmd:schedule|param:cancel', "cancel"
),
book=_p(
'cmd:schedule|param:book', "book"
),
)
@appcmds.describe(
cancel=_p(
'cmd:schedule|param:cancel|desc',
"Select a booked timeslot to cancel."
),
book=_p(
'cmd:schedule|param:book|desc',
"Select a timeslot to schedule. (Times shown in your set timezone.)"
),
)
@appcmds.guild_only
async def schedule_cmd(self, ctx: LionContext):
# TODO: Auotocomplete for book and cancel options
# Will require TTL caching for member schedules.
book = None
cancel = None
async def schedule_cmd(self, ctx: LionContext,
cancel: Optional[str] = None,
book: Optional[str] = None,
):
if not ctx.guild:
return
if not ctx.interaction:
@@ -750,6 +767,9 @@ class ScheduleCog(LionCog):
now = utc_now()
lines: list[tuple[bool, str]] = [] # (error_status, msg)
if book or cancel:
await ctx.interaction.response.defer(thinking=True, ephemeral=True)
if cancel is not None:
schedule = await self._fetch_schedule(ctx.author.id)
# Validate provided
@@ -759,7 +779,7 @@ class ScheduleCog(LionCog):
'cmd:schedule|cancel_booking|error:parse_slot',
"Time slot `{provided}` not recognised. "
"Please select a session to cancel from the autocomplete options."
))
)).format(provided=cancel)
line = (True, error)
elif (slotid := int(cancel)) not in schedule:
# Can't cancel slot because it isn't booked
@@ -802,8 +822,8 @@ class ScheduleCog(LionCog):
'cmd:schedule|create_booking|error:parse_slot',
"Time slot `{provided}` not recognised. "
"Please select a session to cancel from the autocomplete options."
))
lines = (True, error)
)).format(provided=book)
line = (True, error)
elif (slotid := int(book)) in schedule:
# Can't book because the slot is already booked
error = t(_p(
@@ -812,7 +832,7 @@ class ScheduleCog(LionCog):
)).format(
time=discord.utils.format_dt(slotid_to_utc(slotid), style='t')
)
lines = (True, error)
line = (True, error)
elif (slotid_to_utc(slotid) - now).total_seconds() < 60:
# Can't book because it is running or about to start
error = t(_p(
@@ -826,7 +846,7 @@ class ScheduleCog(LionCog):
# The slotid is valid and bookable
# Run the booking
try:
await self.create_booking(guildid, ctx.author.id)
await self.create_booking(guildid, ctx.author.id, slotid)
ack = t(_p(
'cmd:schedule|create_booking|success',
"You have successfully scheduled a session at {time}."
@@ -859,6 +879,155 @@ class ScheduleCog(LionCog):
await ui.run(ctx.interaction)
await ui.wait()
@schedule_cmd.autocomplete('book')
async def schedule_cmd_book_acmpl(self, interaction: discord.Interaction, partial: str):
"""
List the sessions available for the member to book.
"""
# TODO: Warning about setting timezone?
userid = interaction.user.id
schedule = await self._fetch_schedule(userid)
t = self.bot.translator.t
if not interaction.guild or not isinstance(interaction.user, discord.Member):
choice = appcmds.Choice(
name=_p(
'cmd:schedule|acmpl:book|error:not_in_guild',
"You need to be in a server to book sessions!"
),
value='None'
)
choices = [choice]
else:
member = interaction.user
# Check blacklist role
blacklist_role = (await self.settings.BlacklistRole.get(interaction.guild.id)).value
if blacklist_role and blacklist_role in member.roles:
choice = appcmds.Choice(
name=_p(
'cmd:schedule|acmpl:book|error:blacklisted',
"Cannot Book -- Blacklisted"
),
value='None'
)
choices = [choice]
else:
nowid = self.nowid
if ((slotid_to_utc(nowid + 3600) - utc_now()).total_seconds() < 60):
# Start from next session instead
nowid += 3600
upcoming = [nowid + 3600 * i for i in range(1, 25)]
upcoming = [slotid for slotid in upcoming if slotid not in schedule]
choices = []
# We can have a max of 25 acmpl choices
# But there are at most 24 sessions to book
# So we can use the top choice for a message
lion = await self.bot.core.lions.fetch_member(interaction.guild.id, member.id, member=member)
tz = lion.timezone
tzstring = t(_p(
'cmd:schedule|acmpl:book|timezone_info',
"Using timezone '{timezone}' where it is '{now}'. Change with '/my timezone'"
)).format(
timezone=str(tz),
now=dt.datetime.now(tz).strftime('%H:%M')
)
choices.append(
appcmds.Choice(
name=tzstring, value='None',
)
)
slot_format = t(_p(
'cmd:schedule|acmpl:book|format',
"{start} - {end} ({until})"
))
for slotid in upcoming:
slot_start = slotid_to_utc(slotid).astimezone(tz).strftime('%H:%M')
slot_end = slotid_to_utc(slotid + 3600).astimezone(tz).strftime('%H:%M')
distance = int((slotid - nowid) // 3600)
until = format_until(t, distance)
name = slot_format.format(
start=slot_start,
end=slot_end,
until=until
)
if partial.lower() in name.lower():
choices.append(
appcmds.Choice(
name=name,
value=str(slotid)
)
)
if len(choices) == 1:
choices.append(
appcmds.Choice(
name=t(_p(
"cmd:schedule|acmpl:book|no_matching",
"No bookable sessions matching '{partial}'"
)).format(partial=partial[:25]),
value=partial
)
)
return choices
@schedule_cmd.autocomplete('cancel')
async def schedule_cmd_cancel_acmpl(self, interaction: discord.Interaction, partial: str):
user = interaction.user
schedule = await self._fetch_schedule(user.id)
t = self.bot.translator.t
choices = []
minid = self.nowid
if ((slotid_to_utc(self.nowid + 3600) - utc_now()).total_seconds() < 60):
minid = minid + 3600
can_cancel = list(slotid for slotid in schedule if slotid > minid)
if not can_cancel:
choice = appcmds.Choice(
name=_p(
'cmd:schedule|acmpl:cancel|error:empty_schedule',
"You do not have any upcoming sessions to cancel!"
),
value='None'
)
choices.append(choice)
else:
lion = await self.bot.core.lions.fetch_member(interaction.guild.id, user.id)
tz = lion.timezone
for slotid in can_cancel:
slot_format = t(_p(
'cmd:schedule|acmpl:book|format',
"{start} - {end} ({until})"
))
slot_start = slotid_to_utc(slotid).astimezone(tz).strftime('%H:%M')
slot_end = slotid_to_utc(slotid + 3600).astimezone(tz).strftime('%H:%M')
distance = int((slotid - minid) // 3600)
until = format_until(t, distance)
name = slot_format.format(
start=slot_start,
end=slot_end,
until=until
)
if partial.lower() in name.lower():
choices.append(
appcmds.Choice(
name=name,
value=str(slotid)
)
)
if not choices:
choice = appcmds.Choice(
name=t(_p(
'cmd:schedule|acmpl:cancel|error:no_matching',
"No cancellable sessions matching '{partial}'"
)).format(partial=partial[:25]),
value='None'
)
choices.append(choice)
return choices
async def _fetch_schedule(self, userid, **kwargs):
"""
Fetch the given user's schedule (i.e. booking map)
@@ -869,6 +1038,7 @@ class ScheduleCog(LionCog):
bookings = await booking_model.fetch_where(
booking_model.slotid >= nowid,
userid=userid,
**kwargs
).order_by('slotid', ORDER.ASC)
return {

View File

@@ -2,9 +2,11 @@ import asyncio
import itertools
import datetime as dt
from . import logger
from . import logger, babel
from utils.ratelimits import Bucket
_p, _np = babel._p, babel._np
def time_to_slotid(time: dt.datetime) -> int:
"""
@@ -71,3 +73,18 @@ async def limit_concurrency(aws, limit):
while done:
yield done.pop()
logger.debug(f"Completed {count} tasks")
def format_until(t, distance):
if distance:
return t(_np(
'ui:schedule|format_until|positive',
"in <1 hour",
"in {number} hours",
distance
)).format(number=distance)
else:
return t(_p(
'ui:schedule|format_until|now',
"right now!"
))