diff --git a/bot/modules/accountability/TimeSlot.py b/bot/modules/accountability/TimeSlot.py index 34a43dcc..dd6442b4 100644 --- a/bot/modules/accountability/TimeSlot.py +++ b/bot/modules/accountability/TimeSlot.py @@ -170,7 +170,7 @@ class TimeSlot: "Attended": [], "Missing": [] } - for memid, mem in self.members.items(): + for memid, mem in sorted(self.members.items(), key=lambda mem: mem.data.duration, reverse=True): mention = '<@{}>'.format(memid) if mem.has_attended: classifications["Attended"].append( diff --git a/bot/modules/accountability/commands.py b/bot/modules/accountability/commands.py index a9320f19..e2ecafd6 100644 --- a/bot/modules/accountability/commands.py +++ b/bot/modules/accountability/commands.py @@ -6,6 +6,7 @@ from cmdClient.checks import in_guild from utils.lib import multiselect_regex, parse_ranges from data import NOTNULL +from data.conditions import GEQ from .module import module from .lib import utc_now @@ -25,7 +26,7 @@ async def cmd_rooms(ctx): {prefix}rooms book {prefix}rooms cancel Description: - ... + Book an accountability session timeslot. """ lower = ctx.args.lower() splits = lower.split() @@ -34,7 +35,79 @@ async def cmd_rooms(ctx): if not ctx.guild_settings.accountability_category.value: return await ctx.error_reply("The accountability system isn't set up!") - if command == 'book': + # First grab the sessions the member is booked in + joined_rows = accountability_member_info.select_where( + userid=ctx.author.id, + start_at=GEQ(utc_now()), + _extra="ORDER BY start_at ASC" + ) + + if command == 'cancel': + if not joined_rows: + return await ctx.error_reply("You have no bookings to cancel!") + + # Show unbooking menu + lines = [ + "`[{:>2}]` | - ".format( + i, + int(row['start_at'].timestamp()), + int(row['start_at'].timestamp()), + int(row['start_at'].timestamp()) + 3600 + ) + for i, row in enumerate(joined_rows) + ] + out_msg = await ctx.reply( + embed=discord.Embed( + title="Please choose the bookings you want to cancel.", + description='\n'.join(lines), + colour=discord.Colour.orange() + ).set_footer( + text=( + "Reply with the number(s) of the rooms you want to join.\n" + "E.g. 1, 3, 5 or 1-5, 7-8." + ) + ) + ) + + def check(msg): + valid = msg.channel == ctx.ch and msg.author == ctx.author + valid = valid and (re.search(multiselect_regex, msg.content) or msg.content.lower() == 'c') + return valid + + try: + message = await ctx.client.wait_for('message', check=check, timeout=60) + except asyncio.TimeoutError: + await out_msg.delete() + await ctx.error_reply("Session timed out. No accountability bookings were cancelled.") + return + + try: + await out_msg.delete() + await message.delete() + except discord.HTTPException: + pass + + if message.content.lower() == 'c': + return + + to_cancel = [ + joined_rows[index] + for index in parse_ranges(message.content) if index < len(joined_rows) + ] + if not to_cancel: + return await ctx.error_reply("No valid bookings selected for cancellation.") + cost = len(to_cancel) * ctx.guild_settings.accountability_price.value + + accountability_members.delete_where( + userid=ctx.author.id, + slotid=[row['slotid'] for row in to_cancel] + ) + ctx.alion.addCoins(-cost) + await ctx.embed_reply( + "Successfully canceled your bookings." + ) + # elif command == 'book': + else: # Show booking menu # Get attendee count @@ -52,8 +125,13 @@ async def cmd_rooms(ctx): attendee_pad = max((len(str(num)) for num in attendees.values()), default=1) # Build lines + already_joined_times = set(row['start_at'] for row in joined_rows) start_time = utc_now().replace(minute=0, second=0, microsecond=0) - times = list(start_time + datetime.timedelta(hours=n) for n in range(0, 25)) + times = ( + start_time + datetime.timedelta(hours=n) + for n in range(3, 28) + ) + times = list(time for time in times if time not in already_joined_times) lines = [ "`[{:>2}]` | `{:>{}}` attending | - ".format( i, @@ -63,12 +141,17 @@ async def cmd_rooms(ctx): for i, time in enumerate(times) ] # TODO: Nicer embed - # TODO: Remove the slots that the member already booked + # TODO: Don't allow multi bookings if the member has a bad attendence rate out_msg = await ctx.reply( embed=discord.Embed( title="Please choose the sessions you want to book.", description='\n'.join(lines), colour=discord.Colour.orange() + ).set_footer( + text=( + "Reply with the number(s) of the rooms you want to join.\n" + "E.g. 1, 3, 5 or 1-5, 7-8." + ) ) ) @@ -109,7 +192,6 @@ async def cmd_rooms(ctx): ) ) - # TODO: Make sure we aren't double-booking slot_rows = accountability_rooms.fetch_rows_where( start_at=to_book ) @@ -125,15 +207,21 @@ async def cmd_rooms(ctx): insert_keys=('slotid', 'userid', 'paid') ) ctx.alion.addCoins(-cost) - await ctx.embed_reply("You have booked `{}` accountability sessions.".format(len(to_book))) - elif command == 'cancel': - # Show unbooking menu - await ctx.reply("[Placeholder text for cancel menu]") - ... - else: - # Show current booking information - await ctx.reply("[Placeholder text for current booking information]") - ... + await ctx.embed_reply( + "You have booked the following accountability sessions.\n{}".format( + '\n'.join( + " - ".format( + int(time.timestamp()), int(time.timestamp()), int(time.timestamp() + 3600) + ) for time in to_book + ) + ) + ) + # else: + # # Show current booking information + # embed = discord.Embed( + # title="Accountability Room Information" + # ) + # ... # TODO: roomadmin diff --git a/bot/modules/accountability/tracker.py b/bot/modules/accountability/tracker.py index de9124a3..7dc93663 100644 --- a/bot/modules/accountability/tracker.py +++ b/bot/modules/accountability/tracker.py @@ -205,7 +205,7 @@ async def turnover(): # Update session data of all members in new channels member_session_data = [ - (0, slot.start_at, mem.slotid, mem.userid) + (0, slot.start_time, mem.slotid, mem.userid) for slot in current_slots for mem in slot.members.values() if mem.member.voice and mem.member.voice.channel == slot.channel