UI (accountability): rooms command improvements.
This commit is contained in:
@@ -6,6 +6,7 @@ import asyncio
|
|||||||
from settings import GuildSettings
|
from settings import GuildSettings
|
||||||
from utils.lib import tick, cross
|
from utils.lib import tick, cross
|
||||||
from core import Lion
|
from core import Lion
|
||||||
|
from meta import client
|
||||||
|
|
||||||
from .lib import utc_now
|
from .lib import utc_now
|
||||||
from .data import accountability_members, accountability_rooms
|
from .data import accountability_members, accountability_rooms
|
||||||
@@ -94,7 +95,9 @@ class TimeSlot:
|
|||||||
),
|
),
|
||||||
colour=discord.Colour.orange(),
|
colour=discord.Colour.orange(),
|
||||||
timestamp=self.start_time
|
timestamp=self.start_time
|
||||||
).set_footer(text="About to start!")
|
).set_footer(
|
||||||
|
text="About to start!\nJoin the session with {}rooms book".format(client.prefix)
|
||||||
|
)
|
||||||
|
|
||||||
if self.members:
|
if self.members:
|
||||||
embed.description = "Starting <t:{}:R>.".format(timestamp)
|
embed.description = "Starting <t:{}:R>.".format(timestamp)
|
||||||
@@ -119,7 +122,7 @@ class TimeSlot:
|
|||||||
description="Finishing <t:{}:R>.".format(timestamp + 3600),
|
description="Finishing <t:{}:R>.".format(timestamp + 3600),
|
||||||
colour=discord.Colour.orange(),
|
colour=discord.Colour.orange(),
|
||||||
timestamp=self.start_time
|
timestamp=self.start_time
|
||||||
).set_footer(text="Running")
|
).set_footer(text="Join the next session using {}rooms book".format(client.prefix))
|
||||||
|
|
||||||
if self.members:
|
if self.members:
|
||||||
classifications = {
|
classifications = {
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import discord
|
|||||||
import asyncio
|
import asyncio
|
||||||
from cmdClient.checks import in_guild
|
from cmdClient.checks import in_guild
|
||||||
|
|
||||||
from utils.lib import multiselect_regex, parse_ranges
|
from utils.lib import multiselect_regex, parse_ranges, prop_tabulate
|
||||||
from data import NOTNULL
|
from data import NOTNULL
|
||||||
from data.conditions import GEQ
|
from data.conditions import GEQ, LEQ
|
||||||
|
|
||||||
from .module import module
|
from .module import module
|
||||||
from .lib import utc_now
|
from .lib import utc_now
|
||||||
@@ -16,6 +16,28 @@ from .TimeSlot import SlotMember
|
|||||||
from .data import accountability_members, accountability_member_info, accountability_rooms
|
from .data import accountability_members, accountability_member_info, accountability_rooms
|
||||||
|
|
||||||
|
|
||||||
|
hint_icon = "https://projects.iamcal.com/emoji-data/img-apple-64/1f4a1.png"
|
||||||
|
|
||||||
|
|
||||||
|
def time_format(time):
|
||||||
|
diff = (time - utc_now()).total_seconds()
|
||||||
|
if diff < 0:
|
||||||
|
diffstr = "`Right Now!!`"
|
||||||
|
elif diff < 600:
|
||||||
|
diffstr = "`Very soon!!`"
|
||||||
|
elif diff < 3600:
|
||||||
|
diffstr = "`In <1 hour `"
|
||||||
|
else:
|
||||||
|
hours = round(diff / 3600)
|
||||||
|
diffstr = "`In {:>2} hour{}`".format(hours, 's' if hours > 1 else ' ')
|
||||||
|
|
||||||
|
return "{} | <t:{:.0f}:t> - <t:{:.0f}:t>".format(
|
||||||
|
diffstr,
|
||||||
|
time.timestamp(),
|
||||||
|
time.timestamp() + 3600,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@module.cmd(
|
@module.cmd(
|
||||||
name="rooms",
|
name="rooms",
|
||||||
desc="Book an accountability timeslot",
|
desc="Book an accountability timeslot",
|
||||||
@@ -29,7 +51,7 @@ async def cmd_rooms(ctx):
|
|||||||
{prefix}rooms book
|
{prefix}rooms book
|
||||||
{prefix}rooms cancel
|
{prefix}rooms cancel
|
||||||
Description:
|
Description:
|
||||||
Book an accountability session timeslot.
|
View, book, or cancel your accountability sessions.
|
||||||
"""
|
"""
|
||||||
lower = ctx.args.lower()
|
lower = ctx.args.lower()
|
||||||
splits = lower.split()
|
splits = lower.split()
|
||||||
@@ -51,27 +73,28 @@ async def cmd_rooms(ctx):
|
|||||||
|
|
||||||
# Show unbooking menu
|
# Show unbooking menu
|
||||||
lines = [
|
lines = [
|
||||||
"`[{:>2}]` | <t:{}:d> <t:{}:t> - <t:{}:t>".format(
|
"`[{:>2}]` | {}".format(i, time_format(row['start_at']))
|
||||||
i,
|
|
||||||
int(row['start_at'].timestamp()),
|
|
||||||
int(row['start_at'].timestamp()),
|
|
||||||
int(row['start_at'].timestamp()) + 3600
|
|
||||||
)
|
|
||||||
for i, row in enumerate(joined_rows)
|
for i, row in enumerate(joined_rows)
|
||||||
]
|
]
|
||||||
out_msg = await ctx.reply(
|
out_msg = await ctx.reply(
|
||||||
|
content="Please reply with the number(s) of the rooms you want to cancel. E.g. `1, 3, 5` or `1-3, 7-8`.",
|
||||||
embed=discord.Embed(
|
embed=discord.Embed(
|
||||||
title="Please choose the bookings you want to cancel.",
|
title="Please choose the bookings you want to cancel.",
|
||||||
description='\n'.join(lines),
|
description='\n'.join(lines),
|
||||||
colour=discord.Colour.orange()
|
colour=discord.Colour.orange()
|
||||||
).set_footer(
|
).set_footer(
|
||||||
text=(
|
text=(
|
||||||
"Reply with the number(s) of the rooms you want to join.\n"
|
"All times are in your own timezone! Hover over a time to see the date."
|
||||||
"E.g. 1, 3, 5 or 1-5, 7-8."
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await ctx.cancellable(
|
||||||
|
out_msg,
|
||||||
|
cancel_message="Cancel menu closed, no accountability sessions were cancelled.",
|
||||||
|
timeout=70
|
||||||
|
)
|
||||||
|
|
||||||
def check(msg):
|
def check(msg):
|
||||||
valid = msg.channel == ctx.ch and msg.author == ctx.author
|
valid = msg.channel == ctx.ch and msg.author == ctx.author
|
||||||
valid = valid and (re.search(multiselect_regex, msg.content) or msg.content.lower() == 'c')
|
valid = valid and (re.search(multiselect_regex, msg.content) or msg.content.lower() == 'c')
|
||||||
@@ -81,8 +104,14 @@ async def cmd_rooms(ctx):
|
|||||||
message = await ctx.client.wait_for('message', check=check, timeout=60)
|
message = await ctx.client.wait_for('message', check=check, timeout=60)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
try:
|
try:
|
||||||
await out_msg.delete()
|
await out_msg.edit(
|
||||||
await ctx.error_reply("Session timed out. No accountability bookings were cancelled.")
|
content=None,
|
||||||
|
embed=discord.Embed(
|
||||||
|
description="Cancel menu timed out, no accountability sessions were cancelled.",
|
||||||
|
colour=discord.Colour.red()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
await out_msg.clear_reactions()
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
pass
|
pass
|
||||||
return
|
return
|
||||||
@@ -112,6 +141,7 @@ async def cmd_rooms(ctx):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Handle case where the slot has already opened
|
# Handle case where the slot has already opened
|
||||||
|
# TODO: Possible race condition if they open over the hour border? Might never cancel
|
||||||
for row in to_cancel:
|
for row in to_cancel:
|
||||||
aguild = AGuild.cache.get(row['guildid'], None)
|
aguild = AGuild.cache.get(row['guildid'], None)
|
||||||
if aguild and aguild.upcoming_slot and aguild.upcoming_slot.data:
|
if aguild and aguild.upcoming_slot and aguild.upcoming_slot.data:
|
||||||
@@ -129,13 +159,29 @@ async def cmd_rooms(ctx):
|
|||||||
break
|
break
|
||||||
|
|
||||||
ctx.alion.addCoins(cost)
|
ctx.alion.addCoins(cost)
|
||||||
await ctx.embed_reply(
|
|
||||||
"Successfully canceled your bookings."
|
|
||||||
)
|
|
||||||
# elif command == 'book':
|
|
||||||
else:
|
|
||||||
# Show booking menu
|
|
||||||
|
|
||||||
|
remaining = [row for row in joined_rows if row['slotid'] not in slotids]
|
||||||
|
if not remaining:
|
||||||
|
await ctx.embed_reply("Cancelled all your upcoming accountability sessions!")
|
||||||
|
else:
|
||||||
|
next_booked_time = min(row['start_at'] for row in remaining)
|
||||||
|
if len(to_cancel) > 1:
|
||||||
|
await ctx.embed_reply(
|
||||||
|
"Cancelled `{}` upcoming sessions!\nYour next session is at <t:{:.0f}>.".format(
|
||||||
|
len(to_cancel),
|
||||||
|
next_booked_time.timestamp()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await ctx.embed_reply(
|
||||||
|
"Cancelled your session at <t:{:.0f}>!\n"
|
||||||
|
"Your next session is at <t:{:.0f}>.".format(
|
||||||
|
to_cancel[0]['start_at'].timestamp(),
|
||||||
|
next_booked_time.timestamp()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif command == 'book':
|
||||||
|
# Show booking menu
|
||||||
# Get attendee count
|
# Get attendee count
|
||||||
rows = accountability_member_info.select_where(
|
rows = accountability_member_info.select_where(
|
||||||
guildid=ctx.guild.id,
|
guildid=ctx.guild.id,
|
||||||
@@ -157,29 +203,34 @@ async def cmd_rooms(ctx):
|
|||||||
start_time + datetime.timedelta(hours=n)
|
start_time + datetime.timedelta(hours=n)
|
||||||
for n in range(1, 25)
|
for n in range(1, 25)
|
||||||
)
|
)
|
||||||
times = list(time for time in times if time not in already_joined_times)
|
times = [time for time in times if time not in already_joined_times]
|
||||||
lines = [
|
lines = [
|
||||||
"`[{:>2}]` | `{:>{}}` attending | <t:{}:d> <t:{}:t> - <t:{}:t>".format(
|
"`[{num:>2}]` | `{count:>{count_pad}}` attending | {time}".format(
|
||||||
i,
|
num=i,
|
||||||
attendees.get(time, 0), attendee_pad,
|
count=attendees.get(time, 0), count_pad=attendee_pad,
|
||||||
int(time.timestamp()), int(time.timestamp()), int(time.timestamp()) + 3600
|
time=time_format(time),
|
||||||
)
|
)
|
||||||
for i, time in enumerate(times)
|
for i, time in enumerate(times)
|
||||||
]
|
]
|
||||||
# TODO: Nicer embed
|
# TODO: Nicer embed
|
||||||
# TODO: Don't allow multi bookings if the member has a bad attendence rate
|
# TODO: Don't allow multi bookings if the member has a bad attendance rate
|
||||||
out_msg = await ctx.reply(
|
out_msg = await ctx.reply(
|
||||||
|
content="Please reply with the number(s) of the rooms you want to join. E.g. `1, 3, 5` or `1-3, 7-8`.",
|
||||||
embed=discord.Embed(
|
embed=discord.Embed(
|
||||||
title="Please choose the sessions you want to book.",
|
title="Please choose the sessions you want to book.",
|
||||||
description='\n'.join(lines),
|
description='\n'.join(lines),
|
||||||
colour=discord.Colour.orange()
|
colour=discord.Colour.orange()
|
||||||
).set_footer(
|
).set_footer(
|
||||||
text=(
|
text=(
|
||||||
"Reply with the number(s) of the rooms you want to join.\n"
|
"All times are in your own timezone! Hover over a time to see the date."
|
||||||
"E.g. 1, 3, 5 or 1-5, 7-8."
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
await ctx.cancellable(
|
||||||
|
out_msg,
|
||||||
|
cancel_message="Booking menu cancelled, no sessions were booked.",
|
||||||
|
timeout=70
|
||||||
|
)
|
||||||
|
|
||||||
def check(msg):
|
def check(msg):
|
||||||
valid = msg.channel == ctx.ch and msg.author == ctx.author
|
valid = msg.channel == ctx.ch and msg.author == ctx.author
|
||||||
@@ -190,12 +241,19 @@ async def cmd_rooms(ctx):
|
|||||||
message = await ctx.client.wait_for('message', check=check, timeout=60)
|
message = await ctx.client.wait_for('message', check=check, timeout=60)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
try:
|
try:
|
||||||
await out_msg.delete()
|
await out_msg.edit(
|
||||||
await ctx.error_reply("Session timed out. No accountability slots were booked.")
|
content=None,
|
||||||
|
embed=discord.Embed(
|
||||||
|
description="Booking menu timed out, no sessions were booked.",
|
||||||
|
colour=discord.Colour.red()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
await out_msg.clear_reactions()
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
pass
|
pass
|
||||||
return
|
return
|
||||||
|
|
||||||
|
print(message)
|
||||||
try:
|
try:
|
||||||
await out_msg.delete()
|
await out_msg.delete()
|
||||||
await message.delete()
|
await message.delete()
|
||||||
@@ -260,21 +318,224 @@ async def cmd_rooms(ctx):
|
|||||||
pass
|
pass
|
||||||
await slot.update_status()
|
await slot.update_status()
|
||||||
ctx.alion.addCoins(-cost)
|
ctx.alion.addCoins(-cost)
|
||||||
await ctx.embed_reply(
|
|
||||||
"You have booked the following accountability sessions.\n{}".format(
|
# Ack purchase
|
||||||
'\n'.join(
|
embed = discord.Embed(
|
||||||
"<t:{}:d> <t:{}:t> - <t:{}:t>".format(
|
title="You have booked the following session{}!".format('s' if len(to_book) > 1 else ''),
|
||||||
int(time.timestamp()), int(time.timestamp()), int(time.timestamp() + 3600)
|
description=(
|
||||||
) for time in to_book
|
"*Please attend all your booked sessions!*\n"
|
||||||
)
|
"*If you can't attend, cancel with* `{}rooms cancel`\n\n{}"
|
||||||
)
|
).format(
|
||||||
|
ctx.best_prefix,
|
||||||
|
'\n'.join(time_format(time) for time in to_book),
|
||||||
|
),
|
||||||
|
colour=discord.Colour.orange()
|
||||||
|
).set_footer(
|
||||||
|
text=(
|
||||||
|
"Use {prefix}rooms to see your current bookings.\n"
|
||||||
|
).format(prefix=ctx.best_prefix)
|
||||||
)
|
)
|
||||||
# else:
|
try:
|
||||||
# # Show current booking information
|
await ctx.reply(
|
||||||
# embed = discord.Embed(
|
embed=embed,
|
||||||
# title="Accountability Room Information"
|
reference=ctx.msg
|
||||||
# )
|
)
|
||||||
# ...
|
except discord.NotFound:
|
||||||
|
await ctx.reply(embed=embed)
|
||||||
|
else:
|
||||||
|
# Show accountability room information for this user
|
||||||
|
# Accountability profile
|
||||||
|
# Author
|
||||||
|
# Special case for no past bookings, emphasis hint
|
||||||
|
# Hint on Bookings section for booking/cancelling as applicable
|
||||||
|
# Description has stats
|
||||||
|
# Footer says that all times are in their timezone
|
||||||
|
# TODO: attendance requirement shouldn't be retroactive! Add attended data column
|
||||||
|
# Attended `{}` out of `{}` booked (`{}%` attendance rate!)
|
||||||
|
# Attendance streak: `{}` days attended with no missed sessions!
|
||||||
|
# Add explanation for first time users
|
||||||
|
|
||||||
|
# Get all slots the member has ever booked
|
||||||
|
history = accountability_member_info.select_where(
|
||||||
|
userid=ctx.author.id,
|
||||||
|
# start_at=LEQ(utc_now() - datetime.timedelta(hours=1)),
|
||||||
|
start_at=LEQ(utc_now()),
|
||||||
|
select_columns=("*", "(duration > 0 OR last_joined_at IS NOT NULL) AS attended"),
|
||||||
|
_extra="ORDER BY start_at DESC"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not (history or joined_rows):
|
||||||
|
# First-timer information
|
||||||
|
about = (
|
||||||
|
"You haven't joined any accountability sessions yet!\n"
|
||||||
|
"Book a session by typing **`{}rooms book`** and selecting "
|
||||||
|
"the hours you intend to study, "
|
||||||
|
"then attend by joining the accountability voice channel when the session starts!\n"
|
||||||
|
"Only if everyone attends will they get the bonus of `{}` LionCoins!\n"
|
||||||
|
"Let's all do our best and keep each other accountable 🔥"
|
||||||
|
).format(
|
||||||
|
ctx.best_prefix,
|
||||||
|
ctx.guild_settings.accountability_bonus.value
|
||||||
|
)
|
||||||
|
embed = discord.Embed(
|
||||||
|
description=about,
|
||||||
|
colour=discord.Colour.orange()
|
||||||
|
)
|
||||||
|
embed.set_footer(
|
||||||
|
text="Please keep your DMs open so I can notify you when the session starts!\n",
|
||||||
|
icon_url=hint_icon
|
||||||
|
)
|
||||||
|
await ctx.reply(embed=embed)
|
||||||
|
else:
|
||||||
|
# Build description with stats
|
||||||
|
if history:
|
||||||
|
# First get the counts
|
||||||
|
attended_count = sum(row['attended'] for row in history)
|
||||||
|
total_count = len(history)
|
||||||
|
total_duration = sum(row['duration'] for row in history)
|
||||||
|
|
||||||
|
# Add current session to duration if it exists
|
||||||
|
if history[0]['last_joined_at'] and (utc_now() - history[0]['start_at']).total_seconds() < 3600:
|
||||||
|
total_duration += int((utc_now() - history[0]['last_joined_at']).total_seconds())
|
||||||
|
|
||||||
|
# Calculate the streak
|
||||||
|
timezone = ctx.alion.settings.timezone.value
|
||||||
|
|
||||||
|
streak = 0
|
||||||
|
current_streak = None
|
||||||
|
max_streak = 0
|
||||||
|
day_attended = None
|
||||||
|
date = utc_now().astimezone(timezone).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
daydiff = datetime.timedelta(days=1)
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
while i < len(history):
|
||||||
|
row = history[i]
|
||||||
|
print(date, row['start_at'])
|
||||||
|
i += 1
|
||||||
|
if not row['attended']:
|
||||||
|
# Not attended, streak broken
|
||||||
|
pass
|
||||||
|
elif row['start_at'] > date:
|
||||||
|
# They attended this day
|
||||||
|
day_attended = True
|
||||||
|
continue
|
||||||
|
elif day_attended is None:
|
||||||
|
# Didn't attend today, but don't break streak
|
||||||
|
day_attended = False
|
||||||
|
date -= daydiff
|
||||||
|
i -= 1
|
||||||
|
continue
|
||||||
|
elif not day_attended:
|
||||||
|
# Didn't attend the day, streak broken
|
||||||
|
date -= daydiff
|
||||||
|
i -= 1
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# Attended the day
|
||||||
|
streak += 1
|
||||||
|
|
||||||
|
# Move window to the previous day and try the row again
|
||||||
|
date -= daydiff
|
||||||
|
day_attended = False
|
||||||
|
i -= 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
max_streak = max(max_streak, streak)
|
||||||
|
if current_streak is None:
|
||||||
|
current_streak = streak
|
||||||
|
streak = 0
|
||||||
|
|
||||||
|
# Handle loop exit state, i.e. the last streak
|
||||||
|
if day_attended:
|
||||||
|
streak += 1
|
||||||
|
max_streak = max(max_streak, streak)
|
||||||
|
if current_streak is None:
|
||||||
|
current_streak = streak
|
||||||
|
|
||||||
|
# Build the stats
|
||||||
|
table = {
|
||||||
|
"Sessions": "**{}** attended out of **{}**, `{:.0f}%` attendance rate.".format(
|
||||||
|
attended_count,
|
||||||
|
total_count,
|
||||||
|
(attended_count * 100) / total_count,
|
||||||
|
),
|
||||||
|
"Time": "**{:02}:{:02}** spent in accountability rooms.".format(
|
||||||
|
total_duration // 60,
|
||||||
|
total_duration % 60
|
||||||
|
),
|
||||||
|
"Streak": "**{}** day{} with no missed sessions! (Longest: **{}** day{}.)".format(
|
||||||
|
current_streak,
|
||||||
|
's' if current_streak != 1 else '',
|
||||||
|
max_streak,
|
||||||
|
's' if max_streak != 1 else '',
|
||||||
|
),
|
||||||
|
}
|
||||||
|
desc = prop_tabulate(*zip(*table.items()))
|
||||||
|
else:
|
||||||
|
desc = (
|
||||||
|
"Good luck with your next session!\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build currently booked list
|
||||||
|
|
||||||
|
if joined_rows:
|
||||||
|
# TODO: (Future) calendar link
|
||||||
|
# Get attendee counts for currently booked sessions
|
||||||
|
rows = accountability_member_info.select_where(
|
||||||
|
slotid=[row["slotid"] for row in joined_rows],
|
||||||
|
userid=NOTNULL,
|
||||||
|
select_columns=(
|
||||||
|
'slotid',
|
||||||
|
'start_at',
|
||||||
|
'COUNT(*) as num'
|
||||||
|
),
|
||||||
|
_extra="GROUP BY start_at, slotid ORDER BY start_at ASC"
|
||||||
|
)
|
||||||
|
attendees = {row['start_at']: row['num'] for row in rows}
|
||||||
|
attendee_pad = max((len(str(num)) for num in attendees.values()), default=1)
|
||||||
|
|
||||||
|
# TODO: Allow cancel to accept multiselect keys as args
|
||||||
|
|
||||||
|
booked_list = '\n'.join(
|
||||||
|
"`{:>{}}` attendees | {}".format(
|
||||||
|
num,
|
||||||
|
attendee_pad,
|
||||||
|
time_format(start)
|
||||||
|
) for start, num in attendees.items()
|
||||||
|
)
|
||||||
|
booked_field = (
|
||||||
|
"{}\n\n"
|
||||||
|
"*If you can't make your booking, please cancel using `{}rooms cancel`!*"
|
||||||
|
).format(booked_list, ctx.best_prefix)
|
||||||
|
|
||||||
|
# Temporary footer for acclimatisation
|
||||||
|
# footer = "All times are displayed in your own timezone!"
|
||||||
|
footer = "Book another session using {}rooms book".format(ctx.best_prefix)
|
||||||
|
else:
|
||||||
|
booked_field = (
|
||||||
|
"Your schedule is empty!\n"
|
||||||
|
"Book another session using `{}rooms book`."
|
||||||
|
).format(ctx.best_prefix)
|
||||||
|
footer = "Please keep your DMs open for notifications!"
|
||||||
|
|
||||||
|
# Finally, build embed
|
||||||
|
embed = discord.Embed(
|
||||||
|
colour=discord.Colour.orange(),
|
||||||
|
description=desc,
|
||||||
|
).set_author(
|
||||||
|
name="Accountability profile for {}".format(ctx.author.name),
|
||||||
|
icon_url=ctx.author.avatar_url
|
||||||
|
).set_footer(
|
||||||
|
text=footer,
|
||||||
|
icon_url=hint_icon
|
||||||
|
).add_field(
|
||||||
|
name="Upcoming sessions",
|
||||||
|
value=booked_field
|
||||||
|
)
|
||||||
|
|
||||||
|
# And send it!
|
||||||
|
await ctx.reply(embed=embed)
|
||||||
|
|
||||||
|
|
||||||
# TODO: roomadmin
|
# TODO: roomadmin
|
||||||
|
|||||||
@@ -21,3 +21,14 @@ accountability_members = RowTable(
|
|||||||
|
|
||||||
accountability_member_info = Table('accountability_member_info')
|
accountability_member_info = Table('accountability_member_info')
|
||||||
accountability_open_slots = Table('accountability_open_slots')
|
accountability_open_slots = Table('accountability_open_slots')
|
||||||
|
|
||||||
|
# @accountability_member_info.save_query
|
||||||
|
# def user_streaks(userid, min_duration):
|
||||||
|
# with accountability_member_info.conn as conn:
|
||||||
|
# cursor = conn.cursor()
|
||||||
|
# with cursor:
|
||||||
|
# cursor.execute(
|
||||||
|
# """
|
||||||
|
|
||||||
|
# """
|
||||||
|
# )
|
||||||
|
|||||||
Reference in New Issue
Block a user