Merge pull request #2 from StudyLions/staging

Bugfixes and small UI updates
This commit is contained in:
Interitio
2021-10-20 09:24:30 +03:00
committed by GitHub
7 changed files with 78 additions and 40 deletions

View File

@@ -46,6 +46,7 @@ async def preload_guild_configuration(client):
Loads the plain guild configuration for all guilds the client is part of into data. Loads the plain guild configuration for all guilds the client is part of into data.
""" """
guildids = [guild.id for guild in client.guilds] guildids = [guild.id for guild in client.guilds]
if guildids:
rows = client.data.guild_config.fetch_rows_where(guildid=guildids) rows = client.data.guild_config.fetch_rows_where(guildid=guildids)
client.log( client.log(
"Preloaded guild configuration for {} guilds.".format(len(rows)), "Preloaded guild configuration for {} guilds.".format(len(rows)),
@@ -59,6 +60,7 @@ async def preload_studying_members(client):
Loads the member data for all members who are currently in voice channels. Loads the member data for all members who are currently in voice channels.
""" """
userids = list(set(member.id for guild in client.guilds for ch in guild.voice_channels for member in ch.members)) userids = list(set(member.id for guild in client.guilds for ch in guild.voice_channels for member in ch.members))
if userids:
rows = client.data.lions.fetch_rows_where(userid=userids) rows = client.data.lions.fetch_rows_where(userid=userids)
client.log( client.log(
"Preloaded member data for {} members.".format(len(rows)), "Preloaded member data for {} members.".format(len(rows)),

View File

@@ -72,6 +72,9 @@ class TimeSlot:
connect=False connect=False
) )
happy_lion = "https://media.discordapp.net/stickers/898266283559227422.png"
sad_lion = "https://media.discordapp.net/stickers/898266548421148723.png"
def __init__(self, guild, start_time, data=None): def __init__(self, guild, start_time, data=None):
self.guild: discord.Guild = guild self.guild: discord.Guild = guild
self.start_time: datetime.datetime = start_time self.start_time: datetime.datetime = start_time
@@ -139,13 +142,16 @@ class TimeSlot:
else: else:
classifications["Attended"].append(mention) classifications["Attended"].append(mention)
all_attended = all(mem.has_attended for mem in self.members.values())
bonus_line = ( bonus_line = (
"{tick} All members attended, and will get a `{bonus} LC` completion bonus!".format( "{tick} Everyone attended, and will get a `{bonus} LC` bonus!".format(
tick=tick, tick=tick,
bonus=GuildSettings(self.guild.id).accountability_bonus.value bonus=GuildSettings(self.guild.id).accountability_bonus.value
) )
if all(mem.has_attended for mem in self.members.values()) else "" if all_attended else ""
) )
if all_attended:
embed.set_thumbnail(url=self.happy_lion)
embed.description += "\n" + bonus_line embed.description += "\n" + bonus_line
for field, value in classifications.items(): for field, value in classifications.items():
@@ -182,16 +188,22 @@ class TimeSlot:
else: else:
classifications["Missing"].append(mention) classifications["Missing"].append(mention)
all_attended = all(mem.has_attended for mem in self.members.values())
bonus_line = ( bonus_line = (
"{tick} All members attended, and received a `{bonus} LC` completion bonus!".format( "{tick} Everyone attended, and received a `{bonus} LC` bonus!".format(
tick=tick, tick=tick,
bonus=GuildSettings(self.guild.id).accountability_bonus.value bonus=GuildSettings(self.guild.id).accountability_bonus.value
) )
if all(mem.has_attended for mem in self.members.values()) else if all_attended else
"{cross} Some members missed the session, so everyone missed out on the bonus!".format( "{cross} Some members missed the session, so everyone missed out on the bonus!".format(
cross=cross cross=cross
) )
) )
if all_attended:
embed.set_thumbnail(url=self.happy_lion)
else:
embed.set_thumbnail(url=self.sad_lion)
embed.description += "\n" + bonus_line embed.description += "\n" + bonus_line
for field, value in classifications.items(): for field, value in classifications.items():
@@ -292,7 +304,7 @@ class TimeSlot:
self.message = await self.lobby.send( self.message = await self.lobby.send(
embed=self.open_embed embed=self.open_embed
) )
except discord.HTTPException as e: except discord.HTTPException:
GuildSettings(self.guild.id).event_log.log( GuildSettings(self.guild.id).event_log.log(
"Failed to post the status message in the accountability lobby {}.\n" "Failed to post the status message in the accountability lobby {}.\n"
"Skipping this session.".format(self.lobby.mention), "Skipping this session.".format(self.lobby.mention),
@@ -322,10 +334,21 @@ class TimeSlot:
Update the status message, and launch the DM reminder. Update the status message, and launch the DM reminder.
""" """
if self.channel: if self.channel:
try:
await self.channel.edit(name="Accountability Study Room") await self.channel.edit(name="Accountability Study Room")
await self.channel.set_permissions(self.guild.default_role, view_channel=True, connect=False) await self.channel.set_permissions(self.guild.default_role, view_channel=True, connect=False)
except discord.HTTPException:
pass
asyncio.create_task(self.dm_reminder(delay=60)) asyncio.create_task(self.dm_reminder(delay=60))
try:
await self.message.edit(embed=self.status_embed) await self.message.edit(embed=self.status_embed)
except discord.NotFound:
try:
self.message = await self.lobby.send(
embed=self.status_embed
)
except discord.HTTPException:
self.message = None
async def dm_reminder(self, delay=60): async def dm_reminder(self, delay=60):
""" """

View File

@@ -232,7 +232,8 @@ async def turnover():
# Start all the current rooms # Start all the current rooms
await asyncio.gather( await asyncio.gather(
*(slot.start() for slot in current_slots) *(slot.start() for slot in current_slots),
return_exceptions=True
) )

View File

@@ -353,9 +353,10 @@ class Ticket:
Method used to revert the ticket action, e.g. unban or remove mute role. Method used to revert the ticket action, e.g. unban or remove mute role.
Generally called by `pardon` and `_expire`. Generally called by `pardon` and `_expire`.
Must be overriden by the Ticket type, if they implement any revert logic. May be overriden by the Ticket type, if they implement any revert logic.
Is a no-op by default.
""" """
raise NotImplementedError return
async def _expire(self): async def _expire(self):
""" """

View File

@@ -87,9 +87,9 @@ class video_studyban(settings.Boolean, GuildSetting):
@property @property
def success_response(self): def success_response(self):
if self.value: if self.value:
"Members will now be study banned if they don't enable their video in the configured video channels." return "Members will now be study-banned if they don't enable their video in the configured video channels."
else: else:
"Members will not be studybanned if they don't enable their video in video channels." return "Members will not be study-banned if they don't enable their video in video channels."
@GuildSettings.attach_setting @GuildSettings.attach_setting

View File

@@ -265,6 +265,13 @@ async def cmd_studybadges(ctx, flags):
# Parse the input # Parse the input
lines = ctx.args.splitlines() lines = ctx.args.splitlines()
results = [await parse_level(ctx, line) for line in lines] results = [await parse_level(ctx, line) for line in lines]
# Check for duplicates
_set = set()
duplicate = next((time for time, _ in results if time in _set or _set.add(time)), None)
if duplicate:
return await ctx.error_reply(
"Level `{}` provided twice!".format(strfdur(duplicate, short=False))
)
current_times = set(row.required_time for row in guild_roles) current_times = set(row.required_time for row in guild_roles)
# Split up the provided lines into levels to add and levels to edit # Split up the provided lines into levels to add and levels to edit

View File

@@ -1,6 +1,5 @@
import json import json
import asyncio import asyncio
import datetime
import itertools import itertools
from io import StringIO from io import StringIO
from enum import IntEnum from enum import IntEnum
@@ -12,7 +11,7 @@ from cmdClient.Context import Context
from cmdClient.lib import SafeCancellation from cmdClient.lib import SafeCancellation
from meta import client from meta import client
from utils.lib import parse_dur, strfdur, strfdelta, prop_tabulate, multiple_replace from utils.lib import parse_dur, strfdur, prop_tabulate, multiple_replace
from .base import UserInputError from .base import UserInputError
@@ -100,7 +99,7 @@ class Boolean(SettingType):
Looks up the provided string in the truthy and falsey tables. Looks up the provided string in the truthy and falsey tables.
""" """
_userstr = userstr.lower() _userstr = userstr.lower()
if _userstr == "none": if not _userstr or _userstr == "none":
return None return None
if _userstr in cls._truthy: if _userstr in cls._truthy:
return True return True
@@ -154,7 +153,7 @@ class Integer(SettingType):
""" """
Relies on integer casting to convert the user string Relies on integer casting to convert the user string
""" """
if userstr.lower() == "none": if not userstr or userstr.lower() == "none":
return None return None
try: try:
@@ -222,7 +221,7 @@ class String(SettingType):
Check that the user-entered string is of the correct length. Check that the user-entered string is of the correct length.
Accept "None" to unset. Accept "None" to unset.
""" """
if userstr.lower() == "none": if not userstr or userstr.lower() == "none":
# Unsetting case # Unsetting case
return None return None
elif cls._maxlen is not None and len(userstr) > cls._maxlen: elif cls._maxlen is not None and len(userstr) > cls._maxlen:
@@ -284,7 +283,7 @@ class Channel(SettingType):
Pass to the channel seeker utility to find the requested channel. Pass to the channel seeker utility to find the requested channel.
Handle `0` and variants of `None` to unset. Handle `0` and variants of `None` to unset.
""" """
if userstr.lower() in ('0', 'none'): if userstr.lower() in ('', '0', 'none'):
return None return None
else: else:
channel = await ctx.find_channel(userstr, interactive=True, chan_type=cls._chan_type) channel = await ctx.find_channel(userstr, interactive=True, chan_type=cls._chan_type)
@@ -296,13 +295,17 @@ class Channel(SettingType):
@classmethod @classmethod
def _format_data(cls, id: int, data: Optional[int], **kwargs): def _format_data(cls, id: int, data: Optional[int], **kwargs):
""" """
Retrieve an artificially created channel mention. Retrieve the channel mention, if the channel still exists.
If the channel does not exist, this will show up as invalid-channel. If the channel no longer exists, or cannot be seen by the client, returns None.
""" """
if data is None: if data is None:
return None return None
else: else:
return "<#{}>".format(data) channel = client.get_channel(data)
if channel:
return channel.mention
else:
return None
class VoiceChannel(Channel): class VoiceChannel(Channel):
@@ -375,7 +378,7 @@ class Role(SettingType):
Pass to the role seeker utility to find the requested role. Pass to the role seeker utility to find the requested role.
Handle `0` and variants of `None` to unset. Handle `0` and variants of `None` to unset.
""" """
if userstr.lower() in ('0', 'none'): if userstr.lower() in ('', '0', 'none'):
return None return None
else: else:
role = await ctx.find_role(userstr, create=cls._parse_create, interactive=True) role = await ctx.find_role(userstr, create=cls._parse_create, interactive=True)
@@ -452,7 +455,7 @@ class Emoji(SettingType):
Pass to the emoji string parser to get the emoji. Pass to the emoji string parser to get the emoji.
Handle `0` and variants of `None` to unset. Handle `0` and variants of `None` to unset.
""" """
if userstr.lower() in ('0', 'none'): if userstr.lower() in ('', '0', 'none'):
return None return None
else: else:
return cls._parse_emoji(userstr) return cls._parse_emoji(userstr)
@@ -505,7 +508,7 @@ class Timezone(SettingType):
Check that the user-entered string is of the correct length. Check that the user-entered string is of the correct length.
Accept "None" to unset. Accept "None" to unset.
""" """
if userstr.lower() == "none": if not userstr or userstr.lower() == "none":
# Unsetting case # Unsetting case
return None return None
try: try:
@@ -585,7 +588,7 @@ class IntegerEnum(SettingType):
options = {name.lower(): mem.value for name, mem in cls._enum.__members__.items()} options = {name.lower(): mem.value for name, mem in cls._enum.__members__.items()}
if userstr == "none": if not userstr or userstr == "none":
# Unsetting case # Unsetting case
return None return None
elif userstr not in options: elif userstr not in options:
@@ -654,7 +657,7 @@ class Duration(SettingType):
""" """
Parse the provided duration. Parse the provided duration.
""" """
if userstr.lower() == "none": if not userstr or userstr.lower() == "none":
return None return None
if cls._default_multiplier and userstr.isdigit(): if cls._default_multiplier and userstr.isdigit():
@@ -949,12 +952,13 @@ class SettingList(SettingType):
Splits the user string across `,` to break up the list. Splits the user string across `,` to break up the list.
Handle `0` and variants of `None` to unset. Handle `0` and variants of `None` to unset.
""" """
if userstr.lower() in ('0', 'none'): if userstr.lower() in ('', '0', 'none'):
return [] return []
else: else:
data = [] data = []
for item in userstr.split(','): items = (item.strip() for item in userstr.split(','))
data.append(await cls._setting._parse_userstr(ctx, id, item.strip())) items = (item for item in items if item)
data = [await cls._setting._parse_userstr(ctx, id, item, **kwargs) for item in items]
if cls._force_unique: if cls._force_unique:
data = list(set(data)) data = list(set(data))