feat(schedule): Complete schedule cmd impl.
This commit is contained in:
@@ -29,7 +29,7 @@ from .settings import ScheduleSettings, ScheduleConfig
|
|||||||
from .ui.scheduleui import ScheduleUI
|
from .ui.scheduleui import ScheduleUI
|
||||||
from .ui.settingui import ScheduleSettingUI
|
from .ui.settingui import ScheduleSettingUI
|
||||||
from .core import TimeSlot, ScheduledSession, SessionMember
|
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
|
_p, _np = babel._p, babel._np
|
||||||
|
|
||||||
@@ -732,12 +732,29 @@ class ScheduleCog(LionCog):
|
|||||||
"View and manage your scheduled session."
|
"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
|
@appcmds.guild_only
|
||||||
async def schedule_cmd(self, ctx: LionContext):
|
async def schedule_cmd(self, ctx: LionContext,
|
||||||
# TODO: Auotocomplete for book and cancel options
|
cancel: Optional[str] = None,
|
||||||
# Will require TTL caching for member schedules.
|
book: Optional[str] = None,
|
||||||
book = None
|
):
|
||||||
cancel = None
|
|
||||||
if not ctx.guild:
|
if not ctx.guild:
|
||||||
return
|
return
|
||||||
if not ctx.interaction:
|
if not ctx.interaction:
|
||||||
@@ -750,6 +767,9 @@ class ScheduleCog(LionCog):
|
|||||||
now = utc_now()
|
now = utc_now()
|
||||||
lines: list[tuple[bool, str]] = [] # (error_status, msg)
|
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:
|
if cancel is not None:
|
||||||
schedule = await self._fetch_schedule(ctx.author.id)
|
schedule = await self._fetch_schedule(ctx.author.id)
|
||||||
# Validate provided
|
# Validate provided
|
||||||
@@ -759,7 +779,7 @@ class ScheduleCog(LionCog):
|
|||||||
'cmd:schedule|cancel_booking|error:parse_slot',
|
'cmd:schedule|cancel_booking|error:parse_slot',
|
||||||
"Time slot `{provided}` not recognised. "
|
"Time slot `{provided}` not recognised. "
|
||||||
"Please select a session to cancel from the autocomplete options."
|
"Please select a session to cancel from the autocomplete options."
|
||||||
))
|
)).format(provided=cancel)
|
||||||
line = (True, error)
|
line = (True, error)
|
||||||
elif (slotid := int(cancel)) not in schedule:
|
elif (slotid := int(cancel)) not in schedule:
|
||||||
# Can't cancel slot because it isn't booked
|
# Can't cancel slot because it isn't booked
|
||||||
@@ -802,8 +822,8 @@ class ScheduleCog(LionCog):
|
|||||||
'cmd:schedule|create_booking|error:parse_slot',
|
'cmd:schedule|create_booking|error:parse_slot',
|
||||||
"Time slot `{provided}` not recognised. "
|
"Time slot `{provided}` not recognised. "
|
||||||
"Please select a session to cancel from the autocomplete options."
|
"Please select a session to cancel from the autocomplete options."
|
||||||
))
|
)).format(provided=book)
|
||||||
lines = (True, error)
|
line = (True, error)
|
||||||
elif (slotid := int(book)) in schedule:
|
elif (slotid := int(book)) in schedule:
|
||||||
# Can't book because the slot is already booked
|
# Can't book because the slot is already booked
|
||||||
error = t(_p(
|
error = t(_p(
|
||||||
@@ -812,7 +832,7 @@ class ScheduleCog(LionCog):
|
|||||||
)).format(
|
)).format(
|
||||||
time=discord.utils.format_dt(slotid_to_utc(slotid), style='t')
|
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:
|
elif (slotid_to_utc(slotid) - now).total_seconds() < 60:
|
||||||
# Can't book because it is running or about to start
|
# Can't book because it is running or about to start
|
||||||
error = t(_p(
|
error = t(_p(
|
||||||
@@ -826,7 +846,7 @@ class ScheduleCog(LionCog):
|
|||||||
# The slotid is valid and bookable
|
# The slotid is valid and bookable
|
||||||
# Run the booking
|
# Run the booking
|
||||||
try:
|
try:
|
||||||
await self.create_booking(guildid, ctx.author.id)
|
await self.create_booking(guildid, ctx.author.id, slotid)
|
||||||
ack = t(_p(
|
ack = t(_p(
|
||||||
'cmd:schedule|create_booking|success',
|
'cmd:schedule|create_booking|success',
|
||||||
"You have successfully scheduled a session at {time}."
|
"You have successfully scheduled a session at {time}."
|
||||||
@@ -859,6 +879,155 @@ class ScheduleCog(LionCog):
|
|||||||
await ui.run(ctx.interaction)
|
await ui.run(ctx.interaction)
|
||||||
await ui.wait()
|
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):
|
async def _fetch_schedule(self, userid, **kwargs):
|
||||||
"""
|
"""
|
||||||
Fetch the given user's schedule (i.e. booking map)
|
Fetch the given user's schedule (i.e. booking map)
|
||||||
@@ -869,6 +1038,7 @@ class ScheduleCog(LionCog):
|
|||||||
bookings = await booking_model.fetch_where(
|
bookings = await booking_model.fetch_where(
|
||||||
booking_model.slotid >= nowid,
|
booking_model.slotid >= nowid,
|
||||||
userid=userid,
|
userid=userid,
|
||||||
|
**kwargs
|
||||||
).order_by('slotid', ORDER.ASC)
|
).order_by('slotid', ORDER.ASC)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import asyncio
|
|||||||
import itertools
|
import itertools
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
|
|
||||||
from . import logger
|
from . import logger, babel
|
||||||
from utils.ratelimits import Bucket
|
from utils.ratelimits import Bucket
|
||||||
|
|
||||||
|
_p, _np = babel._p, babel._np
|
||||||
|
|
||||||
|
|
||||||
def time_to_slotid(time: dt.datetime) -> int:
|
def time_to_slotid(time: dt.datetime) -> int:
|
||||||
"""
|
"""
|
||||||
@@ -71,3 +73,18 @@ async def limit_concurrency(aws, limit):
|
|||||||
while done:
|
while done:
|
||||||
yield done.pop()
|
yield done.pop()
|
||||||
logger.debug(f"Completed {count} tasks")
|
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!"
|
||||||
|
))
|
||||||
|
|||||||
Reference in New Issue
Block a user