Fix an issue where channel permissions wouldn't get updated. Fix an issue where a member couldn't cancel an open session.
279 lines
9.9 KiB
Python
279 lines
9.9 KiB
Python
import re
|
|
import datetime
|
|
import discord
|
|
import asyncio
|
|
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
|
|
from .tracker import AccountabilityGuild as AGuild
|
|
from .TimeSlot import SlotMember
|
|
from .data import accountability_members, accountability_member_info, accountability_open_slots, accountability_rooms
|
|
|
|
|
|
@module.cmd(
|
|
name="rooms",
|
|
desc="Book an accountability timeslot",
|
|
group="Productivity"
|
|
)
|
|
@in_guild()
|
|
async def cmd_rooms(ctx):
|
|
"""
|
|
Usage``:
|
|
{prefix}rooms
|
|
{prefix}rooms book
|
|
{prefix}rooms cancel
|
|
Description:
|
|
Book an accountability session timeslot.
|
|
"""
|
|
lower = ctx.args.lower()
|
|
splits = lower.split()
|
|
command = splits[0] if splits else None
|
|
|
|
if not ctx.guild_settings.accountability_category.value:
|
|
return await ctx.error_reply("The accountability system isn't set up!")
|
|
|
|
# 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}]` | <t:{}:d><t:{}:t> - <t:{}:t>".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:
|
|
try:
|
|
await out_msg.delete()
|
|
await ctx.error_reply("Session timed out. No accountability bookings were cancelled.")
|
|
except discord.HTTPException:
|
|
pass
|
|
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
|
|
|
|
slotids = [row['slotid'] for row in to_cancel]
|
|
accountability_members.delete_where(
|
|
userid=ctx.author.id,
|
|
slotid=slotids
|
|
)
|
|
|
|
# Handle case where the slot has already opened
|
|
for row in to_cancel:
|
|
aguild = AGuild.cache.get(row['guildid'], None)
|
|
if aguild:
|
|
if aguild.upcoming_slot and aguild.upcoming_slot.data and (aguild.upcoming_slot.data.slotid in slotids):
|
|
aguild.upcoming_slot.members.pop(ctx.author.id, None)
|
|
if aguild.upcoming_slot.channel:
|
|
try:
|
|
await aguild.upcoming_slot.channel.set_permissions(
|
|
ctx.author,
|
|
overwrite=None
|
|
)
|
|
except discord.HTTPException:
|
|
pass
|
|
await aguild.upcoming_slot.update_status()
|
|
break
|
|
|
|
ctx.alion.addCoins(-cost)
|
|
await ctx.embed_reply(
|
|
"Successfully canceled your bookings."
|
|
)
|
|
# elif command == 'book':
|
|
else:
|
|
# Show booking menu
|
|
|
|
# Get attendee count
|
|
rows = accountability_member_info.select_where(
|
|
guildid=ctx.guild.id,
|
|
userid=NOTNULL,
|
|
select_columns=(
|
|
'slotid',
|
|
'start_at',
|
|
'COUNT(*) as num'
|
|
),
|
|
_extra="GROUP BY start_at, slotid"
|
|
)
|
|
attendees = {row['start_at']: row['num'] for row in rows}
|
|
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 = (
|
|
start_time + datetime.timedelta(hours=n)
|
|
for n in range(1, 25)
|
|
)
|
|
times = list(time for time in times if time not in already_joined_times)
|
|
lines = [
|
|
"`[{:>2}]` | `{:>{}}` attending | <t:{}:d><t:{}:t> - <t:{}:t>".format(
|
|
i,
|
|
attendees.get(time, 0), attendee_pad,
|
|
int(time.timestamp()), int(time.timestamp()), int(time.timestamp()) + 3600
|
|
)
|
|
for i, time in enumerate(times)
|
|
]
|
|
# TODO: Nicer embed
|
|
# 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."
|
|
)
|
|
)
|
|
)
|
|
|
|
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:
|
|
try:
|
|
await out_msg.delete()
|
|
await ctx.error_reply("Session timed out. No accountability slots were booked.")
|
|
except discord.HTTPException:
|
|
pass
|
|
return
|
|
|
|
try:
|
|
await out_msg.delete()
|
|
await message.delete()
|
|
except discord.HTTPException:
|
|
pass
|
|
|
|
if message.content.lower() == 'c':
|
|
return
|
|
|
|
to_book = [
|
|
times[index]
|
|
for index in parse_ranges(message.content) if index < len(times)
|
|
]
|
|
if not to_book:
|
|
return await ctx.error_reply("No valid sessions selected.")
|
|
cost = len(to_book) * ctx.guild_settings.accountability_price.value
|
|
if cost > ctx.alion.coins:
|
|
return await ctx.error_reply(
|
|
"Sorry, booking `{}` sessions costs `{}` coins, and you only have `{}`!".format(
|
|
len(to_book),
|
|
cost,
|
|
ctx.alion.coins
|
|
)
|
|
)
|
|
|
|
# Add the member to data, creating the row if required
|
|
slot_rows = accountability_rooms.fetch_rows_where(
|
|
guildid=ctx.guild.id,
|
|
start_at=to_book
|
|
)
|
|
slotids = [row.slotid for row in slot_rows]
|
|
to_add = set(to_book).difference((row.start_at for row in slot_rows))
|
|
if to_add:
|
|
slotids.extend(row['slotid'] for row in accountability_rooms.insert_many(
|
|
*((ctx.guild.id, start_at) for start_at in to_add),
|
|
insert_keys=('guildid', 'start_at'),
|
|
))
|
|
accountability_members.insert_many(
|
|
*((slotid, ctx.author.id, ctx.guild_settings.accountability_price.value) for slotid in slotids),
|
|
insert_keys=('slotid', 'userid', 'paid')
|
|
)
|
|
|
|
# Handle case where the slot has already opened
|
|
aguild = AGuild.cache.get(ctx.guild.id, None)
|
|
if aguild:
|
|
if aguild.upcoming_slot and aguild.upcoming_slot.start_time in to_book:
|
|
slot = aguild.upcoming_slot
|
|
if not slot.data:
|
|
# Handle slot activation
|
|
slot._refresh()
|
|
channelid, messageid = await slot.open()
|
|
accountability_rooms.update_where(
|
|
{'channelid': channelid, 'messageid': messageid},
|
|
slotid=slot.data.slotid
|
|
)
|
|
else:
|
|
slot.members[ctx.author.id] = SlotMember(slot.data.slotid, ctx.author.id, ctx.guild)
|
|
# Also update the channel permissions
|
|
try:
|
|
await slot.channel.set_permissions(ctx.author, view_channel=True, connect=True)
|
|
except discord.HTTPException:
|
|
pass
|
|
await slot.update_status()
|
|
ctx.alion.addCoins(-cost)
|
|
await ctx.embed_reply(
|
|
"You have booked the following accountability sessions.\n{}".format(
|
|
'\n'.join(
|
|
"<t:{}:d><t:{}:t> - <t:{}:t>".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
|