feature (guild admin): Reaction roles base.
This commit is contained in:
324
bot/modules/guild_admin/reaction_roles/command.py
Normal file
324
bot/modules/guild_admin/reaction_roles/command.py
Normal file
@@ -0,0 +1,324 @@
|
||||
import asyncio
|
||||
import discord
|
||||
|
||||
from cmdClient.lib import ResponseTimedOut
|
||||
from wards import guild_admin
|
||||
from settings import UserInputError
|
||||
|
||||
from ..module import module
|
||||
|
||||
from .tracker import ReactionRoleMessage
|
||||
from .data import reaction_role_reactions
|
||||
from . import settings
|
||||
|
||||
|
||||
example_str = "🧮 mathematics, 🫀 biology, 💻 computer science, 🖼️ design, 🩺 medicine"
|
||||
|
||||
|
||||
@module.cmd(
|
||||
"reactionroles",
|
||||
group="Guild Admin",
|
||||
desc="Create or configure reaction role messages.",
|
||||
aliases=('rroles',),
|
||||
flags=(
|
||||
'delete', 'remove',
|
||||
'enable', 'disable',
|
||||
'required_role==', 'removable=', 'maximum=', 'refunds=', 'log=', 'default_price=',
|
||||
'price=', 'timeout=='
|
||||
)
|
||||
)
|
||||
@guild_admin()
|
||||
async def cmd_reactionroles(ctx, flags):
|
||||
"""
|
||||
Usage``:
|
||||
{prefix}rroles messageid
|
||||
{prefix}rroles messageid emoji1 role1, emoji2 role2, emoji3 role3, ...
|
||||
{prefix}rroles messageid --message_setting value
|
||||
{prefix}rroles messageid emoji --emoji_setting value
|
||||
{prefix}rroles messageid --remove emoji1, emoji2, ...
|
||||
{prefix}rroles messageid --enable
|
||||
{prefix}rroles messageid --disable
|
||||
{prefix}rroles messageid --delete
|
||||
Description:
|
||||
Reaction roles are message reactions that give members specific roles when used.
|
||||
This commands allows you to create and configure messages with reaction roles.
|
||||
Getting started:
|
||||
To get started, choose the message you want to add reaction roles to, and copy the link to that message. \
|
||||
Then run the command `{prefix}rroles link` (replacing `link` with the link you copied), and \
|
||||
follow the prompts.
|
||||
Configuration:
|
||||
After creation, you can view the message configuration and reaction roles with `{prefix}rroles link` \
|
||||
(you can also use the message id instead of the link, or reply to the message).
|
||||
|
||||
You can set one of the configuration options with `{prefix}rroles link --setting value`.\
|
||||
For example to make it impossible to remove the reaction roles,\
|
||||
run `{prefix}rroles link --removable off`.
|
||||
|
||||
There are also some configurable per-reaction settings, such as the price of a role.\
|
||||
To see these, use `{prefix}rroles link emoji` (replacing `emoji` with the reaction emoji) \
|
||||
and set them with e.g. `{prefix}rroles link emoji --price 200`.
|
||||
Message Settings::
|
||||
maximum: Maximum number of roles obtainable from this message.
|
||||
log: Whether to log reaction role usage into the event log.
|
||||
removable: Whether the reactions roles can be remove by unreacting.
|
||||
refunds: Whether to refund the role price when removing the role.
|
||||
default_price: The default price of each role on this message.
|
||||
required_role: The role required to use these reactions roles.
|
||||
Reaction Settings::
|
||||
price: The price of this reaction role.
|
||||
timeout: The amount of time the role lasts. (TBD)
|
||||
Examples:
|
||||
...
|
||||
"""
|
||||
if not ctx.args and not ctx.msg.reference:
|
||||
# No target message provided, list the current reaction messages
|
||||
# Or give a brief guide if there are no current reaction messages
|
||||
...
|
||||
|
||||
target_id = None
|
||||
target_chid = None
|
||||
remaining = ""
|
||||
|
||||
if ctx.msg.reference:
|
||||
target_id = ctx.msg.reference.message_id
|
||||
target_chid = ctx.msg.reference.channel_id
|
||||
remaining = ctx.args
|
||||
elif ctx.args:
|
||||
# Try to parse the target message
|
||||
# Expect a link or a messageid as the first argument
|
||||
splits = ctx.args.split(maxsplit=1)
|
||||
maybe_target = splits[0]
|
||||
if len(splits) > 1:
|
||||
remaining = splits[1]
|
||||
if maybe_target.isdigit():
|
||||
# Assume it is a message id
|
||||
target_id = int(maybe_target)
|
||||
elif maybe_target.contains('/'):
|
||||
# Assume it is a link
|
||||
# Try and parse it
|
||||
link_splits = maybe_target.rsplit('/', maxsplit=2)
|
||||
if len(link_splits) > 1 and link_splits[-1].isdigit() and link_splits[-2].isdigit():
|
||||
# Definitely a link
|
||||
target_id = int(link_splits[-1])
|
||||
target_chid = int(link_splits[-2])
|
||||
|
||||
if not target_id:
|
||||
return await ctx.error_reply(
|
||||
"Please provide the message link or message id as the first argument."
|
||||
)
|
||||
|
||||
# We have a target, fetch the ReactionMessage if it exists
|
||||
target = ReactionRoleMessage.fetch(target_id)
|
||||
if target:
|
||||
# View or edit target
|
||||
|
||||
# Exclusive flags, delete and remove ignore all other flags
|
||||
if flags['delete']:
|
||||
# Handle deletion of the ReactionRoleMessage
|
||||
...
|
||||
if flags['remove']:
|
||||
# Handle emoji removal
|
||||
...
|
||||
|
||||
# Check whether we are editing a particular reaction
|
||||
# TODO: We might be updating roles or adding reactions as well
|
||||
if remaining:
|
||||
emojistr = remaining
|
||||
# TODO....
|
||||
|
||||
# Lines for edit output
|
||||
edit_lines = [] # (success_state, string)
|
||||
# Columns to update
|
||||
update = {}
|
||||
|
||||
# Message edit flags
|
||||
# Gets and modifies the settings, but doesn't write
|
||||
if flags['disable']:
|
||||
update['enabled'] = False
|
||||
edit_lines.append((True, "Reaction role message disabled."))
|
||||
elif flags['enable']:
|
||||
update['enabled'] = True
|
||||
edit_lines.append((True, "Reaction role message enabled."))
|
||||
|
||||
if flags['required_role']:
|
||||
try:
|
||||
setting = await settings.required_role.parse(target.messageid, ctx, flags['required_role'])
|
||||
except UserInputError as e:
|
||||
edit_lines.append((False, e.msg))
|
||||
else:
|
||||
edit_lines.append((True, setting.success_response))
|
||||
update[setting._data_column] = setting.data
|
||||
if flags['removable']:
|
||||
try:
|
||||
setting = await settings.removable.parse(target.messageid, ctx, flags['removable'])
|
||||
except UserInputError as e:
|
||||
edit_lines.append((False, e.msg))
|
||||
else:
|
||||
edit_lines.append((True, setting.success_response))
|
||||
update[setting._data_column] = setting.data
|
||||
if flags['maximum']:
|
||||
try:
|
||||
setting = await settings.maximum.parse(target.messageid, ctx, flags['maximum'])
|
||||
except UserInputError as e:
|
||||
edit_lines.append((False, e.msg))
|
||||
else:
|
||||
edit_lines.append((True, setting.success_response))
|
||||
update[setting._data_column] = setting.data
|
||||
if flags['refunds']:
|
||||
try:
|
||||
setting = await settings.refunds.parse(target.messageid, ctx, flags['refunds'])
|
||||
except UserInputError as e:
|
||||
edit_lines.append((False, e.msg))
|
||||
else:
|
||||
edit_lines.append((True, setting.success_response))
|
||||
update[setting._data_column] = setting.data
|
||||
if flags['log']:
|
||||
try:
|
||||
setting = await settings.log.parse(target.messageid, ctx, flags['log'])
|
||||
except UserInputError as e:
|
||||
edit_lines.append((False, e.msg))
|
||||
else:
|
||||
edit_lines.append((True, setting.success_response))
|
||||
update[setting._data_column] = setting.data
|
||||
if flags['default_price']:
|
||||
try:
|
||||
setting = await settings.default_price.parse(target.messageid, ctx, flags['default_price'])
|
||||
except UserInputError as e:
|
||||
edit_lines.append((False, e.msg))
|
||||
else:
|
||||
edit_lines.append((True, setting.success_response))
|
||||
update[setting._data_column] = setting.data
|
||||
|
||||
# Update the data all at once
|
||||
target.data.update(**update)
|
||||
|
||||
# Then format and respond with the edit message
|
||||
|
||||
# TODO: Emoji edit flags
|
||||
...
|
||||
else:
|
||||
# Start creation process
|
||||
# First find the target message
|
||||
message = None
|
||||
if target_chid:
|
||||
channel = ctx.guild.get_channel(target_chid)
|
||||
if channel:
|
||||
message = await channel.fetch_message(target_id)
|
||||
else:
|
||||
# We only have a messageid, need to search for it through all the guild channels
|
||||
message = await ctx.find_message(target_id)
|
||||
|
||||
if message is None:
|
||||
return await ctx.error_reply(
|
||||
"Could not find the specified message!"
|
||||
)
|
||||
|
||||
# Confirm that they want to create a new reaction role message.
|
||||
embed = discord.Embed(
|
||||
colour=discord.Colour.orange(),
|
||||
description="Do you want to set up new reaction roles for [this message]({}) (`y(es)`/`n(o)`)?".format(
|
||||
message.jump_url
|
||||
)
|
||||
)
|
||||
out_msg = await ctx.reply(embed=embed)
|
||||
if not await ctx.ask(msg=None, use_msg=out_msg):
|
||||
return
|
||||
|
||||
# Set up the message as a new ReactionRole message.
|
||||
# First obtain the initial emojis
|
||||
if not remaining:
|
||||
# Prompt for the initial emojis
|
||||
embed = discord.Embed(
|
||||
title="What reaction roles would you like to add?",
|
||||
description=(
|
||||
"Please now enter the reaction roles you would like to associate, "
|
||||
"in the form `emoji role`, where `role` is given by id or partial name. For example:"
|
||||
"```{}```".format(example_str)
|
||||
)
|
||||
)
|
||||
out_msg = await ctx.reply(embed=embed)
|
||||
|
||||
# Wait for a response
|
||||
def check(msg):
|
||||
return msg.author == ctx.author and msg.channel == ctx.ch and msg.content
|
||||
|
||||
try:
|
||||
reply = await ctx.client.wait_for('message', check=check, timeout=300)
|
||||
except asyncio.TimeoutError:
|
||||
raise ResponseTimedOut("Prompt timed out, no reaction roles were added.")
|
||||
finally:
|
||||
try:
|
||||
await out_msg.delete()
|
||||
except discord.HTTPException:
|
||||
pass
|
||||
|
||||
rolestrs = reply.content
|
||||
|
||||
try:
|
||||
await reply.delete()
|
||||
except discord.HTTPException:
|
||||
pass
|
||||
else:
|
||||
rolestrs = remaining
|
||||
|
||||
# Attempt to parse the emojis
|
||||
# First split based on newline and comma
|
||||
splits = (split.strip() for line in rolestrs.splitlines() for split in line.split(','))
|
||||
splits = [split for split in splits if split]
|
||||
|
||||
# Do a quick check to make sure everything is in the correct format
|
||||
unsplit = next((split for split in splits if ' ' not in split), None)
|
||||
if unsplit:
|
||||
return await ctx.error_reply(
|
||||
"Couldn't parse the reaction role `{}` into the form `emoji role`.".format(unsplit)
|
||||
)
|
||||
|
||||
# Now go through and extract the emojis and roles
|
||||
# TODO: Handle duplicate emojis?
|
||||
# TODO: Error handling on the roles, make sure we can actually add them
|
||||
reactions = {}
|
||||
for split in splits:
|
||||
emojistr, rolestr = split.split(maxsplit=1)
|
||||
# Parse emoji
|
||||
# TODO: Custom emoji handler, probably store in a PartialEmoji
|
||||
if ':' in emojistr:
|
||||
return ctx.error_reply(
|
||||
"Sorry, at this time we only support built-in emojis! Custom emoji support coming soon."
|
||||
)
|
||||
emoji = emojistr
|
||||
|
||||
# Parse role
|
||||
# TODO: More graceful error handling
|
||||
role = await ctx.find_role(rolestr, interactive=True, allow_notfound=False)
|
||||
|
||||
reactions[emoji] = role
|
||||
|
||||
# TODO: Parse any provided settings, and pass them to the data constructor
|
||||
|
||||
# Create the ReactionRoleMessage
|
||||
rmsg = ReactionRoleMessage.create(
|
||||
message.id,
|
||||
message.guild.id,
|
||||
message.channel.id
|
||||
)
|
||||
|
||||
# Insert the reaction data directly
|
||||
# TODO: Again consider emoji setting data here, for common settings?
|
||||
# TODO: Will need to be changed for custom emojis
|
||||
reaction_role_reactions.insert_many(
|
||||
*((message.id, role.id, emoji) for emoji, role in reactions.items()),
|
||||
insert_keys=('messageid', 'roleid', 'emoji_name')
|
||||
)
|
||||
|
||||
# Refresh the ReactionRoleMessage to pick up the new reactions
|
||||
rmsg.refresh()
|
||||
|
||||
# Ack the creation
|
||||
await ctx.embed_reply(
|
||||
(
|
||||
"Created `{}` new reaction roles.\n"
|
||||
"Please add the reactions to [the message]({}) to make them available for use!".format(
|
||||
len(reactions), rmsg.message_link
|
||||
)
|
||||
)
|
||||
)
|
||||
Reference in New Issue
Block a user