feature (reaction-roles): Configuration command.
New monolithic `rroles` configuration command. Complete logging and setting implementation in RR tracker. Improve strings in RR settings. Add special `NEWPAGE` manual section parsed by the help command. Add `tabulated` method for `ObjectSettings` for easy display. Add `_parse_create` switch for the `Role` setting type.
This commit is contained in:
@@ -1,2 +1,2 @@
|
|||||||
CONFIG_FILE = "config/bot.conf"
|
CONFIG_FILE = "config/bot.conf"
|
||||||
DATA_VERSION = 3
|
DATA_VERSION = 4
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -37,6 +37,10 @@ class required_role(setting_types.Role, RoleMessageSetting):
|
|||||||
else:
|
else:
|
||||||
return "All members can now use these reaction roles."
|
return "All members can now use these reaction roles."
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _get_guildid(cls, id: int, **kwargs):
|
||||||
|
return reaction_role_messages.fetch(id).guildid
|
||||||
|
|
||||||
|
|
||||||
@RoleMessageSettings.attach_setting
|
@RoleMessageSettings.attach_setting
|
||||||
class removable(setting_types.Boolean, RoleMessageSetting):
|
class removable(setting_types.Boolean, RoleMessageSetting):
|
||||||
@@ -88,7 +92,7 @@ class maximum(setting_types.Integer, RoleMessageSetting):
|
|||||||
@property
|
@property
|
||||||
def success_response(self):
|
def success_response(self):
|
||||||
if self.value:
|
if self.value:
|
||||||
return "Members can get a maximum of `{}` roles from this message."
|
return "Members can get a maximum of `{}` roles from this message.".format(self.value)
|
||||||
else:
|
else:
|
||||||
return "Members can now get all the roles from this mesage."
|
return "Members can now get all the roles from this mesage."
|
||||||
|
|
||||||
@@ -205,7 +209,7 @@ class price(setting_types.Integer, ReactionSetting):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _format_data(cls, id, data, **kwargs):
|
def _format_data(cls, id, data, **kwargs):
|
||||||
if data is None:
|
if not data:
|
||||||
return "Free"
|
return "Free"
|
||||||
else:
|
else:
|
||||||
return "`{}` coins".format(data)
|
return "`{}` coins".format(data)
|
||||||
@@ -213,9 +217,9 @@ class price(setting_types.Integer, ReactionSetting):
|
|||||||
@property
|
@property
|
||||||
def success_response(self):
|
def success_response(self):
|
||||||
if self.value is not None:
|
if self.value is not None:
|
||||||
return "This role now costs `{}` coins.".format(self.value)
|
return "{{reaction.emoji}} {{reaction.role.mention}} now costs `{}` coins.".format(self.value)
|
||||||
else:
|
else:
|
||||||
return "This role is free."
|
return "{reaction.emoji} {reaction.role.mention} is now free."
|
||||||
|
|
||||||
|
|
||||||
@ReactionSettings.attach_setting
|
@ReactionSettings.attach_setting
|
||||||
@@ -243,6 +247,8 @@ class timeout(setting_types.Duration, ReactionSetting):
|
|||||||
@property
|
@property
|
||||||
def success_response(self):
|
def success_response(self):
|
||||||
if self.value is not None:
|
if self.value is not None:
|
||||||
return "Roles will timeout `{}` after selection.".format(self.formatted)
|
return "{{reaction.emoji}} {{reaction.role.mention}} will timeout `{}` after selection.".format(
|
||||||
|
self.formatted
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return "Roles will never timeout after selection."
|
return "{reaction.emoji} {reaction.role.mention} will never timeout after selection."
|
||||||
|
|||||||
@@ -18,17 +18,17 @@ from .data import reaction_role_messages, reaction_role_reactions # , reaction_
|
|||||||
from .settings import RoleMessageSettings, ReactionSettings
|
from .settings import RoleMessageSettings, ReactionSettings
|
||||||
|
|
||||||
|
|
||||||
class ReactionRoleReaction(PartialEmoji):
|
class ReactionRoleReaction:
|
||||||
"""
|
"""
|
||||||
Light data class representing a reaction role reaction.
|
Light data class representing a reaction role reaction.
|
||||||
Extends PartialEmoji for comparison with discord Emojis.
|
|
||||||
"""
|
"""
|
||||||
__slots__ = ('reactionid', '_message', '_role')
|
__slots__ = ('reactionid', '_emoji', '_message', '_role')
|
||||||
|
|
||||||
def __init__(self, reactionid, message=None, **kwargs):
|
def __init__(self, reactionid, message=None, **kwargs):
|
||||||
self.reactionid = reactionid
|
self.reactionid = reactionid
|
||||||
self._message: ReactionRoleMessage = None
|
self._message: ReactionRoleMessage = None
|
||||||
self._role = None
|
self._role = None
|
||||||
|
self._emoji = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, messageid, roleid, emoji: PartialEmoji, message=None, **kwargs) -> 'ReactionRoleReaction':
|
def create(cls, messageid, roleid, emoji: PartialEmoji, message=None, **kwargs) -> 'ReactionRoleReaction':
|
||||||
@@ -47,6 +47,17 @@ class ReactionRoleReaction(PartialEmoji):
|
|||||||
)
|
)
|
||||||
return cls(row.reactionid, message=message)
|
return cls(row.reactionid, message=message)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def emoji(self) -> PartialEmoji:
|
||||||
|
if self._emoji is None:
|
||||||
|
data = self.data
|
||||||
|
self._emoji = PartialEmoji(
|
||||||
|
name=data.emoji_name,
|
||||||
|
animated=data.emoji_animated,
|
||||||
|
id=data.emoji_id,
|
||||||
|
)
|
||||||
|
return self._emoji
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def data(self) -> Row:
|
def data(self) -> Row:
|
||||||
return reaction_role_reactions.fetch(self.reactionid)
|
return reaction_role_reactions.fetch(self.reactionid)
|
||||||
@@ -69,31 +80,6 @@ class ReactionRoleReaction(PartialEmoji):
|
|||||||
self._role = guild.get_role(self.data.roleid)
|
self._role = guild.get_role(self.data.roleid)
|
||||||
return self._role
|
return self._role
|
||||||
|
|
||||||
# PartialEmoji properties
|
|
||||||
@property
|
|
||||||
def animated(self) -> bool:
|
|
||||||
return self.data.emoji_animated
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self) -> str:
|
|
||||||
return self.data.emoji_name
|
|
||||||
|
|
||||||
@name.setter
|
|
||||||
def name(self, value):
|
|
||||||
"""
|
|
||||||
Name setter.
|
|
||||||
The emoji name may get updated while the emoji itself remains the same.
|
|
||||||
"""
|
|
||||||
self.data.emoji_name = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def id(self) -> Optional[int]:
|
|
||||||
return self.data.emoji_id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _state(self):
|
|
||||||
return client._connection
|
|
||||||
|
|
||||||
|
|
||||||
class ReactionRoleMessage:
|
class ReactionRoleMessage:
|
||||||
"""
|
"""
|
||||||
@@ -147,6 +133,20 @@ class ReactionRoleMessage:
|
|||||||
# Return the constructed ReactionRoleMessage
|
# Return the constructed ReactionRoleMessage
|
||||||
return rmsg
|
return rmsg
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
"""
|
||||||
|
Delete this ReactionRoleMessage.
|
||||||
|
"""
|
||||||
|
# Remove message from cache
|
||||||
|
self._messages.pop(self.messageid, None)
|
||||||
|
|
||||||
|
# Remove reactions from cache
|
||||||
|
reactionids = [reaction.reactionid for reaction in self.reactions]
|
||||||
|
[self._reactions.pop(reactionid, None) for reactionid in reactionids]
|
||||||
|
|
||||||
|
# Remove message from data
|
||||||
|
reaction_role_messages.delete_where(messageid=self.messageid)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def data(self) -> Row:
|
def data(self) -> Row:
|
||||||
"""
|
"""
|
||||||
@@ -242,7 +242,7 @@ class ReactionRoleMessage:
|
|||||||
"""
|
"""
|
||||||
event_log = GuildSettings(self.guild.id).event_log
|
event_log = GuildSettings(self.guild.id).event_log
|
||||||
async with self._locks[payload.user_id]:
|
async with self._locks[payload.user_id]:
|
||||||
reaction = next((reaction for reaction in self.reactions if reaction == payload.emoji), None)
|
reaction = next((reaction for reaction in self.reactions if reaction.emoji == payload.emoji), None)
|
||||||
if reaction:
|
if reaction:
|
||||||
# User pressed a live reaction. Process!
|
# User pressed a live reaction. Process!
|
||||||
member = payload.member
|
member = payload.member
|
||||||
@@ -428,7 +428,7 @@ class ReactionRoleMessage:
|
|||||||
if self.settings.removable.value:
|
if self.settings.removable.value:
|
||||||
event_log = GuildSettings(self.guild.id).event_log
|
event_log = GuildSettings(self.guild.id).event_log
|
||||||
async with self._locks[payload.user_id]:
|
async with self._locks[payload.user_id]:
|
||||||
reaction = next((reaction for reaction in self.reactions if reaction == payload.emoji), None)
|
reaction = next((reaction for reaction in self.reactions if reaction.emoji == payload.emoji), None)
|
||||||
if reaction:
|
if reaction:
|
||||||
# User removed a live reaction. Process!
|
# User removed a live reaction. Process!
|
||||||
member = self.guild.get_member(payload.user_id)
|
member = self.guild.get_member(payload.user_id)
|
||||||
|
|||||||
@@ -78,15 +78,17 @@ async def cmd_help(ctx):
|
|||||||
help_map = {field_name: i for i, (field_name, _) in enumerate(help_fields)}
|
help_map = {field_name: i for i, (field_name, _) in enumerate(help_fields)}
|
||||||
|
|
||||||
if not help_map:
|
if not help_map:
|
||||||
await ctx.reply("No documentation has been written for this command yet!")
|
return await ctx.reply("No documentation has been written for this command yet!")
|
||||||
|
|
||||||
|
field_pages = [[]]
|
||||||
|
page_fields = field_pages[0]
|
||||||
for name, pos in help_map.items():
|
for name, pos in help_map.items():
|
||||||
if name.endswith("``"):
|
if name.endswith("``"):
|
||||||
# Handle codeline help fields
|
# Handle codeline help fields
|
||||||
help_fields[pos] = (
|
page_fields.append((
|
||||||
name.strip("`"),
|
name.strip("`"),
|
||||||
"`{}`".format('`\n`'.join(help_fields[pos][1].splitlines()))
|
"`{}`".format('`\n`'.join(help_fields[pos][1].splitlines()))
|
||||||
)
|
))
|
||||||
elif name.endswith(":"):
|
elif name.endswith(":"):
|
||||||
# Handle property/value help fields
|
# Handle property/value help fields
|
||||||
lines = help_fields[pos][1].splitlines()
|
lines = help_fields[pos][1].splitlines()
|
||||||
@@ -98,10 +100,10 @@ async def cmd_help(ctx):
|
|||||||
names.append(split[0] if len(split) > 1 else "")
|
names.append(split[0] if len(split) > 1 else "")
|
||||||
values.append(split[-1])
|
values.append(split[-1])
|
||||||
|
|
||||||
help_fields[pos] = (
|
page_fields.append((
|
||||||
name.strip(':'),
|
name.strip(':'),
|
||||||
prop_tabulate(names, values)
|
prop_tabulate(names, values)
|
||||||
)
|
))
|
||||||
elif name == "Related":
|
elif name == "Related":
|
||||||
# Handle the related field
|
# Handle the related field
|
||||||
names = [cmd_name.strip() for cmd_name in help_fields[pos][1].split(',')]
|
names = [cmd_name.strip() for cmd_name in help_fields[pos][1].split(',')]
|
||||||
@@ -110,32 +112,46 @@ async def cmd_help(ctx):
|
|||||||
(getattr(ctx.client.cmd_names.get(cmd_name, None), 'desc', '') or '').format(ctx=ctx)
|
(getattr(ctx.client.cmd_names.get(cmd_name, None), 'desc', '') or '').format(ctx=ctx)
|
||||||
for cmd_name in names
|
for cmd_name in names
|
||||||
]
|
]
|
||||||
help_fields[pos] = (
|
page_fields.append((
|
||||||
name,
|
name,
|
||||||
prop_tabulate(names, values)
|
prop_tabulate(names, values)
|
||||||
)
|
))
|
||||||
|
elif name == "PAGEBREAK":
|
||||||
|
page_fields = []
|
||||||
|
field_pages.append(page_fields)
|
||||||
|
else:
|
||||||
|
page_fields.append((name, help_fields[pos][1]))
|
||||||
|
|
||||||
|
# Build the aliases
|
||||||
aliases = getattr(command, 'aliases', [])
|
aliases = getattr(command, 'aliases', [])
|
||||||
alias_str = "(Aliases `{}`.)".format("`, `".join(aliases)) if aliases else ""
|
alias_str = "(Aliases `{}`.)".format("`, `".join(aliases)) if aliases else ""
|
||||||
|
|
||||||
# Build the embed
|
# Build the embeds
|
||||||
embed = discord.Embed(
|
pages = []
|
||||||
title="`{}` command documentation. {}".format(command.name, alias_str),
|
for i, page_fields in enumerate(field_pages):
|
||||||
colour=discord.Colour(0x9b59b6)
|
embed = discord.Embed(
|
||||||
)
|
title="`{}` command documentation. {}".format(
|
||||||
for fieldname, fieldvalue in help_fields:
|
command.name,
|
||||||
embed.add_field(
|
alias_str
|
||||||
name=fieldname,
|
),
|
||||||
value=fieldvalue.format(ctx=ctx, prefix=ctx.best_prefix),
|
colour=discord.Colour(0x9b59b6)
|
||||||
inline=False
|
|
||||||
)
|
)
|
||||||
|
for fieldname, fieldvalue in page_fields:
|
||||||
|
embed.add_field(
|
||||||
|
name=fieldname,
|
||||||
|
value=fieldvalue.format(ctx=ctx, prefix=ctx.best_prefix),
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
|
||||||
embed.set_footer(
|
embed.set_footer(
|
||||||
text="[optional] and <required> denote optional and required arguments, respectively."
|
text="{}\n[optional] and <required> denote optional and required arguments, respectively.".format(
|
||||||
)
|
"Page {} of {}".format(i + 1, len(field_pages)) if len(field_pages) > 1 else '',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
pages.append(embed)
|
||||||
|
|
||||||
# Post the embed
|
# Post the embed
|
||||||
await ctx.reply(embed=embed)
|
await ctx.pager(pages)
|
||||||
else:
|
else:
|
||||||
# Build the command groups
|
# Build the command groups
|
||||||
cmd_groups = {}
|
cmd_groups = {}
|
||||||
|
|||||||
@@ -257,6 +257,16 @@ class ObjectSettings:
|
|||||||
cls.settings[name] = setting
|
cls.settings[name] = setting
|
||||||
return setting
|
return setting
|
||||||
|
|
||||||
|
def tabulated(self):
|
||||||
|
"""
|
||||||
|
Convenience method to provide a complete setting property-table.
|
||||||
|
"""
|
||||||
|
formatted = {
|
||||||
|
setting.display_name: setting.get(self.id, **dict(self.params)).formatted
|
||||||
|
for name, setting in self.settings.items()
|
||||||
|
}
|
||||||
|
return prop_tabulate(*zip(*formatted.items()))
|
||||||
|
|
||||||
|
|
||||||
class ColumnData:
|
class ColumnData:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -327,6 +327,8 @@ class Role(SettingType):
|
|||||||
# Whether to disallow returning roles which don't exist as `discord.Object`s
|
# Whether to disallow returning roles which don't exist as `discord.Object`s
|
||||||
_strict = True
|
_strict = True
|
||||||
|
|
||||||
|
_parse_create = False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _data_from_value(cls, id: int, value: Optional[discord.Role], **kwargs):
|
def _data_from_value(cls, id: int, value: Optional[discord.Role], **kwargs):
|
||||||
"""
|
"""
|
||||||
@@ -370,7 +372,7 @@ class Role(SettingType):
|
|||||||
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=False, interactive=True)
|
role = await ctx.find_role(userstr, create=cls._parse_create, interactive=True)
|
||||||
if role is None:
|
if role is None:
|
||||||
raise SafeCancellation
|
raise SafeCancellation
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user