@@ -13,7 +13,7 @@ class LionCommand(Command):
|
|||||||
"""
|
"""
|
||||||
Subclass to allow easy attachment of custom hooks and structure to commands.
|
Subclass to allow easy attachment of custom hooks and structure to commands.
|
||||||
"""
|
"""
|
||||||
...
|
allow_before_ready = False
|
||||||
|
|
||||||
|
|
||||||
class LionModule(Module):
|
class LionModule(Module):
|
||||||
@@ -72,25 +72,38 @@ class LionModule(Module):
|
|||||||
"""
|
"""
|
||||||
Lion pre-command hook.
|
Lion pre-command hook.
|
||||||
"""
|
"""
|
||||||
|
if not self.ready and not ctx.cmd.allow_before_ready:
|
||||||
|
try:
|
||||||
|
await ctx.embed_reply(
|
||||||
|
"I am currently restarting! Please try again in a couple of minutes."
|
||||||
|
)
|
||||||
|
except discord.HTTPException:
|
||||||
|
pass
|
||||||
|
raise SafeCancellation(details="Module '{}' is not ready.".format(self.name))
|
||||||
|
|
||||||
|
# Check that the channel and guild still exists
|
||||||
|
if not ctx.client.get_guild(ctx.guild.id) or not ctx.guild.get_channel(ctx.ch.id):
|
||||||
|
raise SafeCancellation(details='Command channel is no longer reachable.')
|
||||||
|
|
||||||
# Check global user blacklist
|
# Check global user blacklist
|
||||||
if ctx.author.id in ctx.client.objects['blacklisted_users']:
|
if ctx.author.id in ctx.client.objects['blacklisted_users']:
|
||||||
raise SafeCancellation
|
raise SafeCancellation(details='User is blacklisted.')
|
||||||
|
|
||||||
if ctx.guild:
|
if ctx.guild:
|
||||||
# Check global guild blacklist
|
# Check global guild blacklist
|
||||||
if ctx.guild.id in ctx.client.objects['blacklisted_guilds']:
|
if ctx.guild.id in ctx.client.objects['blacklisted_guilds']:
|
||||||
raise SafeCancellation
|
raise SafeCancellation(details='Guild is blacklisted.')
|
||||||
|
|
||||||
# Check guild's own member blacklist
|
# Check guild's own member blacklist
|
||||||
if ctx.author.id in ctx.client.objects['ignored_members'][ctx.guild.id]:
|
if ctx.author.id in ctx.client.objects['ignored_members'][ctx.guild.id]:
|
||||||
raise SafeCancellation
|
raise SafeCancellation(details='User is ignored in this guild.')
|
||||||
|
|
||||||
# Check channel permissions are sane
|
# Check channel permissions are sane
|
||||||
if not ctx.ch.permissions_for(ctx.guild.me).send_messages:
|
if not ctx.ch.permissions_for(ctx.guild.me).send_messages:
|
||||||
raise SafeCancellation
|
raise SafeCancellation(details='I cannot send messages in this channel.')
|
||||||
if not ctx.ch.permissions_for(ctx.guild.me).embed_links:
|
if not ctx.ch.permissions_for(ctx.guild.me).embed_links:
|
||||||
await ctx.reply("I need permission to send embeds in this channel before I can run any commands!")
|
await ctx.reply("I need permission to send embeds in this channel before I can run any commands!")
|
||||||
raise SafeCancellation
|
raise SafeCancellation(details='I cannot send embeds in this channel.')
|
||||||
|
|
||||||
# Start typing
|
# Start typing
|
||||||
await ctx.ch.trigger_typing()
|
await ctx.ch.trigger_typing()
|
||||||
|
|||||||
Submodule bot/cmdClient updated: 75410acc12...6eb4269034
@@ -53,13 +53,19 @@ def add_pending(pending):
|
|||||||
|
|
||||||
guild_config = RowTable(
|
guild_config = RowTable(
|
||||||
'guild_config',
|
'guild_config',
|
||||||
('guildid', 'admin_role', 'mod_role', 'event_log_channel', 'alert_channel',
|
('guildid', 'admin_role', 'mod_role', 'event_log_channel', 'mod_log_channel', 'alert_channel',
|
||||||
|
'studyban_role',
|
||||||
'min_workout_length', 'workout_reward',
|
'min_workout_length', 'workout_reward',
|
||||||
'max_tasks', 'task_reward', 'task_reward_limit',
|
'max_tasks', 'task_reward', 'task_reward_limit',
|
||||||
'study_hourly_reward', 'study_hourly_live_bonus',
|
'study_hourly_reward', 'study_hourly_live_bonus',
|
||||||
'study_ban_role', 'max_study_bans'),
|
'renting_price', 'renting_category', 'renting_cap', 'renting_role', 'renting_sync_perms',
|
||||||
|
'accountability_category', 'accountability_lobby', 'accountability_bonus',
|
||||||
|
'accountability_reward', 'accountability_price',
|
||||||
|
'video_studyban', 'video_grace_period',
|
||||||
|
'greeting_channel', 'greeting_message', 'returning_message',
|
||||||
|
'starting_funds', 'persist_roles'),
|
||||||
'guildid',
|
'guildid',
|
||||||
cache=TTLCache(1000, ttl=60*5)
|
cache=TTLCache(2500, ttl=60*5)
|
||||||
)
|
)
|
||||||
|
|
||||||
unranked_roles = Table('unranked_roles')
|
unranked_roles = Table('unranked_roles')
|
||||||
@@ -72,6 +78,7 @@ lions = RowTable(
|
|||||||
('guildid', 'userid',
|
('guildid', 'userid',
|
||||||
'tracked_time', 'coins',
|
'tracked_time', 'coins',
|
||||||
'workout_count', 'last_workout_start',
|
'workout_count', 'last_workout_start',
|
||||||
|
'revision_mute_count',
|
||||||
'last_study_badgeid',
|
'last_study_badgeid',
|
||||||
'video_warned',
|
'video_warned',
|
||||||
'_timestamp'
|
'_timestamp'
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ class TimeSlot:
|
|||||||
|
|
||||||
_everyone_overwrite = discord.PermissionOverwrite(
|
_everyone_overwrite = discord.PermissionOverwrite(
|
||||||
view_channel=False,
|
view_channel=False,
|
||||||
connect=False
|
connect=False,
|
||||||
|
speak=False
|
||||||
)
|
)
|
||||||
|
|
||||||
happy_lion = "https://media.discordapp.net/stickers/898266283559227422.png"
|
happy_lion = "https://media.discordapp.net/stickers/898266283559227422.png"
|
||||||
@@ -218,6 +219,9 @@ class TimeSlot:
|
|||||||
"""
|
"""
|
||||||
Load data and update applicable caches.
|
Load data and update applicable caches.
|
||||||
"""
|
"""
|
||||||
|
if not self.guild:
|
||||||
|
return self
|
||||||
|
|
||||||
# Load setting data
|
# Load setting data
|
||||||
self.category = GuildSettings(self.guild.id).accountability_category.value
|
self.category = GuildSettings(self.guild.id).accountability_category.value
|
||||||
self.lobby = GuildSettings(self.guild.id).accountability_lobby.value
|
self.lobby = GuildSettings(self.guild.id).accountability_lobby.value
|
||||||
@@ -389,13 +393,14 @@ class TimeSlot:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# Reward members appropriately
|
# Reward members appropriately
|
||||||
guild_settings = GuildSettings(self.guild.id)
|
if self.guild:
|
||||||
reward = guild_settings.accountability_reward.value
|
guild_settings = GuildSettings(self.guild.id)
|
||||||
if all(mem.has_attended for mem in self.members.values()):
|
reward = guild_settings.accountability_reward.value
|
||||||
reward += guild_settings.accountability_bonus.value
|
if all(mem.has_attended for mem in self.members.values()):
|
||||||
|
reward += guild_settings.accountability_bonus.value
|
||||||
|
|
||||||
for memid in self.members:
|
for memid in self.members:
|
||||||
Lion.fetch(self.guild.id, memid).addCoins(reward)
|
Lion.fetch(self.guild.id, memid).addCoins(reward)
|
||||||
|
|
||||||
async def cancel(self):
|
async def cancel(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -374,14 +374,15 @@ async def _accountability_system_resume():
|
|||||||
None, mow.slotid, mow.userid)
|
None, mow.slotid, mow.userid)
|
||||||
for mow in slot_members[row.slotid] if mow.last_joined_at
|
for mow in slot_members[row.slotid] if mow.last_joined_at
|
||||||
)
|
)
|
||||||
slot = TimeSlot(client.get_guild(row.guildid), row.start_at, data=row).load(
|
if client.get_guild(row.guildid):
|
||||||
memberids=[mow.userid for mow in slot_members[row.slotid]]
|
slot = TimeSlot(client.get_guild(row.guildid), row.start_at, data=row).load(
|
||||||
)
|
memberids=[mow.userid for mow in slot_members[row.slotid]]
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await slot.close()
|
||||||
|
except discord.HTTPException:
|
||||||
|
pass
|
||||||
row.closed_at = now
|
row.closed_at = now
|
||||||
try:
|
|
||||||
await slot.close()
|
|
||||||
except discord.HTTPException:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Load the in-progress room data
|
# Load the in-progress room data
|
||||||
if current_room_data:
|
if current_room_data:
|
||||||
@@ -451,7 +452,7 @@ async def launch_accountability_system(client):
|
|||||||
guilds = tables.guild_config.fetch_rows_where(
|
guilds = tables.guild_config.fetch_rows_where(
|
||||||
accountability_category=NOTNULL
|
accountability_category=NOTNULL
|
||||||
)
|
)
|
||||||
[AccountabilityGuild(guild.guildid) for guild in guilds]
|
[AccountabilityGuild(guild.guildid) for guild in guilds if client.get_guild(guild.guildid)]
|
||||||
await _accountability_system_resume()
|
await _accountability_system_resume()
|
||||||
asyncio.create_task(_accountability_loop())
|
asyncio.create_task(_accountability_loop())
|
||||||
|
|
||||||
|
|||||||
@@ -485,7 +485,7 @@ async def cmd_reactionroles(ctx, flags):
|
|||||||
await ctx.error_reply(
|
await ctx.error_reply(
|
||||||
"The provided channel no longer exists!"
|
"The provided channel no longer exists!"
|
||||||
)
|
)
|
||||||
elif channel.type != discord.ChannelType.text:
|
elif not isinstance(channel, discord.TextChannel):
|
||||||
await ctx.error_reply(
|
await ctx.error_reply(
|
||||||
"The provided channel is not a text channel!"
|
"The provided channel is not a text channel!"
|
||||||
)
|
)
|
||||||
@@ -821,8 +821,8 @@ async def cmd_reactionroles(ctx, flags):
|
|||||||
setting = await setting_class.parse(target.messageid, ctx, flags[flag])
|
setting = await setting_class.parse(target.messageid, ctx, flags[flag])
|
||||||
except UserInputError as e:
|
except UserInputError as e:
|
||||||
return await ctx.error_reply(
|
return await ctx.error_reply(
|
||||||
title="Couldn't save settings!",
|
"{} {}\nNo settings were modified.".format(cross, e.msg),
|
||||||
description="{} {}\nNo settings were modified.".format(cross, e.msg)
|
title="Couldn't save settings!"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
update_lines.append(
|
update_lines.append(
|
||||||
@@ -861,8 +861,8 @@ async def cmd_reactionroles(ctx, flags):
|
|||||||
setting = await setting_class.parse(reaction.reactionid, ctx, flags[flag])
|
setting = await setting_class.parse(reaction.reactionid, ctx, flags[flag])
|
||||||
except UserInputError as e:
|
except UserInputError as e:
|
||||||
return await ctx.error_reply(
|
return await ctx.error_reply(
|
||||||
|
"{} {}\nNo reaction roles were modified.".format(cross, e.msg),
|
||||||
title="Couldn't save reaction role settings!",
|
title="Couldn't save reaction role settings!",
|
||||||
description="{} {}\nNo reaction roles were modified.".format(cross, e.msg)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
update_lines.append(
|
update_lines.append(
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ class price(setting_types.Integer, ReactionSetting):
|
|||||||
)
|
)
|
||||||
|
|
||||||
accepts = "An integer number of coins. Use `0` to make the role free, or `None` to use the message default."
|
accepts = "An integer number of coins. Use `0` to make the role free, or `None` to use the message default."
|
||||||
|
_max = 2 ** 20
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def default(self):
|
def default(self):
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ class ReactionRoleMessage:
|
|||||||
# Fetch the number of applicable roles the user has
|
# Fetch the number of applicable roles the user has
|
||||||
roleids = set(reaction.data.roleid for reaction in self.reactions)
|
roleids = set(reaction.data.roleid for reaction in self.reactions)
|
||||||
member_roleids = set(role.id for role in member.roles)
|
member_roleids = set(role.id for role in member.roles)
|
||||||
if len(roleids.intersection(member_roleids)) > maximum:
|
if len(roleids.intersection(member_roleids)) >= maximum:
|
||||||
# Notify the user
|
# Notify the user
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title="Maximum group roles reached!",
|
title="Maximum group roles reached!",
|
||||||
|
|||||||
@@ -178,6 +178,8 @@ class Room:
|
|||||||
"""
|
"""
|
||||||
Expire the room.
|
Expire the room.
|
||||||
"""
|
"""
|
||||||
|
guild_settings = GuildSettings(self.data.guildid)
|
||||||
|
|
||||||
if self.channel:
|
if self.channel:
|
||||||
# Delete the discord channel
|
# Delete the discord channel
|
||||||
try:
|
try:
|
||||||
@@ -188,7 +190,6 @@ class Room:
|
|||||||
# Delete the room from data (cascades to member deletion)
|
# Delete the room from data (cascades to member deletion)
|
||||||
self.delete()
|
self.delete()
|
||||||
|
|
||||||
guild_settings = GuildSettings(self.data.guildid)
|
|
||||||
guild_settings.event_log.log(
|
guild_settings.event_log.log(
|
||||||
title="Private study room expired!",
|
title="Private study room expired!",
|
||||||
description="<@{}>'s private study room expired.".format(self.data.ownerid)
|
description="<@{}>'s private study room expired.".format(self.data.ownerid)
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ from .module import module
|
|||||||
@module.cmd(
|
@module.cmd(
|
||||||
"stats",
|
"stats",
|
||||||
group="Statistics",
|
group="Statistics",
|
||||||
desc="View a summary of your study statistics!"
|
desc="View a summary of your study statistics!",
|
||||||
|
allow_before_ready=True
|
||||||
)
|
)
|
||||||
@in_guild()
|
@in_guild()
|
||||||
async def cmd_stats(ctx):
|
async def cmd_stats(ctx):
|
||||||
|
|||||||
@@ -26,21 +26,22 @@ async def embed_reply(ctx, desc, colour=discord.Colour.orange(), **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
@Context.util
|
@Context.util
|
||||||
async def error_reply(ctx, error_str, **kwargs):
|
async def error_reply(ctx, error_str, send_args={}, **kwargs):
|
||||||
"""
|
"""
|
||||||
Notify the user of a user level error.
|
Notify the user of a user level error.
|
||||||
Typically, this will occur in a red embed, posted in the command channel.
|
Typically, this will occur in a red embed, posted in the command channel.
|
||||||
"""
|
"""
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
colour=discord.Colour.red(),
|
colour=discord.Colour.red(),
|
||||||
description=error_str
|
description=error_str,
|
||||||
|
**kwargs
|
||||||
)
|
)
|
||||||
message = None
|
message = None
|
||||||
try:
|
try:
|
||||||
message = await ctx.ch.send(
|
message = await ctx.ch.send(
|
||||||
embed=embed,
|
embed=embed,
|
||||||
reference=ctx.msg.to_reference(fail_if_not_exists=False),
|
reference=ctx.msg.to_reference(fail_if_not_exists=False),
|
||||||
**kwargs
|
**send_args
|
||||||
)
|
)
|
||||||
ctx.sent_messages.append(message)
|
ctx.sent_messages.append(message)
|
||||||
return message
|
return message
|
||||||
|
|||||||
@@ -182,7 +182,11 @@ async def find_channel(ctx, userstr, interactive=False, collection=None, chan_ty
|
|||||||
# Create the collection to search from args or guild channels
|
# Create the collection to search from args or guild channels
|
||||||
collection = collection if collection else ctx.guild.channels
|
collection = collection if collection else ctx.guild.channels
|
||||||
if chan_type is not None:
|
if chan_type is not None:
|
||||||
collection = [chan for chan in collection if chan.type == chan_type]
|
if chan_type == discord.ChannelType.text:
|
||||||
|
# Hack to support news channels as text channels
|
||||||
|
collection = [chan for chan in collection if isinstance(chan, discord.TextChannel)]
|
||||||
|
else:
|
||||||
|
collection = [chan for chan in collection if chan.type == chan_type]
|
||||||
|
|
||||||
# If the user input was a number or possible channel mention, extract it
|
# If the user input was a number or possible channel mention, extract it
|
||||||
chanid = userstr.strip('<#@&!>')
|
chanid = userstr.strip('<#@&!>')
|
||||||
@@ -413,7 +417,7 @@ async def find_message(ctx, msgid, chlist=None, ignore=[]):
|
|||||||
|
|
||||||
|
|
||||||
async def _search_in_channel(channel: discord.TextChannel, msgid: int):
|
async def _search_in_channel(channel: discord.TextChannel, msgid: int):
|
||||||
if channel.type != discord.ChannelType.text:
|
if not isinstance(channel, discord.TextChannel):
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
message = await channel.fetch_message(msgid)
|
message = await channel.fetch_message(msgid)
|
||||||
|
|||||||
Reference in New Issue
Block a user