1
0
Fork 0
mirror of synced 2024-04-29 18:22:36 +12:00
Bonfire/cogs/roles.py
2021-05-21 14:25:08 -08:00

535 lines
22 KiB
Python

from discord.ext import commands
import discord
import utils
import re
import asyncio
class Roles(commands.Cog):
"""Class to handle management of roles on the server"""
@commands.command(aliases=["color"])
@commands.guild_only()
@utils.can_run(send_messages=True)
async def colour(self, ctx, role_colour: discord.Colour):
"""Used to give yourself a role matching the colour given.
If the role doesn't exist, it will be created. Names such as red, blue, yellow, etc. can be used.
Additionally, hex codes can be used as well
EXAMPLE: !colour red
RESULT: A role that matches red (#e74c3c) will be given to you"""
result = await ctx.bot.db.fetchrow(
"SELECT colour_roles FROM guilds WHERE id = $1", ctx.guild.id
)
if result and not result["colour_roles"]:
await ctx.send(
"Colour roles not allowed on this server! "
"The command `allowcolours` must be ran to enable them!"
)
return
if not ctx.me.guild_permissions.manage_roles:
await ctx.send("Error: I need manage_roles to be able to use this command")
return
# The convention we'll use for the name
name = "Bonfire {}".format(role_colour)
# Try to find a role that matches our convention, Name #000000 with the colour matching
role = discord.utils.get(ctx.guild.roles, name=name, colour=role_colour)
# The colour roles they currently have, we need to remove them if they want a new colour
old_roles = [
r for r in ctx.author.roles if re.match(r"Bonfire #[0-9a-zA-Z]+", r.name)
]
if old_roles:
await ctx.author.remove_roles(*old_roles)
# If the role doesn't exist, we need to create it
if not role:
opts = {"name": name, "colour": role_colour}
try:
role = await ctx.guild.create_role(**opts)
except discord.HTTPException:
await ctx.send("{} is not a valid colour".format(role_colour))
return
# Now add the role
await ctx.author.add_roles(role)
await ctx.send("I have just given you your requested colour!")
@commands.group(aliases=["roles"], invoke_without_command=True)
@commands.guild_only()
@utils.can_run(send_messages=True)
async def role(self, ctx, *, role: discord.Role = None):
"""This command can be used to modify the roles on the server.
Pass no subcommands and this will print the roles currently available on this server
If you give a role as the argument then it will give some information about that role
EXAMPLE: !role
RESULT: A list of all your roles"""
if role:
# Create the embed object
opts = {
"title": role.name,
"colour": role.colour,
}
if role.managed:
opts["description"] = "This role is managed by a third party service"
embed = discord.Embed(**opts)
# Add details to it
embed.add_field(name="Created", value=role.created_at.date())
embed.add_field(
name="Mentionable", value="Yes" if role.mentionable else "No"
)
total_members = len(role.members)
embed.add_field(name="Total members", value=str(total_members))
# If there are only a few members in this role, display them
if 5 >= total_members > 0:
embed.add_field(
name="Members",
value="\n".join(m.display_name for m in role.members),
)
await ctx.send(embed=embed)
else:
# Don't include the colour roles
colour_role = re.compile("Bonfire #.+")
# Simply get a list of all roles in this server and send them
entries = [
r.name for r in ctx.guild.roles[1:] if not colour_role.match(r.name)
]
if len(entries) == 0:
await ctx.send(
"You do not have any roles setup on this server, other than the default role!"
)
return
try:
pages = utils.Pages(ctx, entries=entries)
await pages.paginate()
except utils.CannotPaginate as e:
await ctx.send(str(e))
@role.command(name="remove")
@commands.guild_only()
@utils.can_run(manage_roles=True)
async def remove_role(self, ctx):
"""Use this to remove roles from a number of members
EXAMPLE: !role remove @Jim @Bot @Joe
RESULT: A follow-along to remove the role(s) you want to, from these 3 members"""
# No use in running through everything if the bot cannot manage roles
if not ctx.message.guild.me.permissions_in(ctx.message.channel).manage_roles:
await ctx.send(
"I can't manage roles in this server, do you not trust me? :c"
)
return
check = (
lambda m: m.author == ctx.message.author
and m.channel == ctx.message.channel
)
server_roles = [
role for role in ctx.message.guild.roles if not role.is_default()
]
# First get the list of all mentioned users
members = ctx.message.mentions
# If no users are mentioned, ask the author for a list of the members they want to remove the role from
if len(members) == 0:
await ctx.send(
"Please provide the list of members you want to remove a role from"
)
try:
msg = await ctx.bot.wait_for("message", check=check, timeout=60)
except asyncio.TimeoutError:
await ctx.send("You took too long. I'm impatient, don't make me wait")
return
if len(msg.mentions) == 0:
await ctx.send(
"I cannot remove a role from someone if you don't provide someone..."
)
return
# Override members if everything has gone alright, and then continue
members = msg.mentions
# This allows the user to remove multiple roles from the list of users, if they want.
await ctx.send(
"Alright, please provide the roles you would like to remove from this member. "
"Make sure the roles, if more than one is provided, are separate by commas. "
)
try:
msg = await ctx.bot.wait_for("message", check=check, timeout=60)
except asyncio.TimeoutError:
await ctx.send("You took too long. I'm impatient, don't make me wait")
return
# Split the content based on commas, using regex so we can split if a space was not provided or if it was
role_names = re.split(", ?", msg.content)
roles = []
# This loop is just to get the actual role objects based on the name
for role in role_names:
_role = discord.utils.get(server_roles, name=role)
if _role is not None:
roles.append(_role)
# If no valid roles were given, let them know that and return
if len(roles) == 0:
await ctx.send("Please provide a valid role next time!")
return
# Otherwise, remove the roles from each member given
for member in members:
await member.remove_roles(*roles)
await ctx.send(
"I have just removed the following roles:```\n{}``` from the following members:"
"```\n{}```".format(
"\n".join(role_names), "\n".join([m.display_name for m in members])
)
)
@role.command(name="add", aliases=["give", "assign"])
@commands.guild_only()
@utils.can_run(manage_roles=True)
async def add_role(self, ctx):
"""Use this to add a role to multiple members.
Provide the list of members, and I'll ask for the role
If no members are provided, I'll first ask for them
EXAMPLE: !role add @Bob @Joe @jim
RESULT: A follow along to add the roles you want to these 3"""
# No use in running through everything if the bot cannot manage roles
if not ctx.message.guild.me.permissions_in(ctx.message.channel).manage_roles:
await ctx.send(
"I can't manage roles in this server, do you not trust me? :c"
)
return
check = (
lambda m: m.author == ctx.message.author
and m.channel == ctx.message.channel
)
# This is exactly the same as removing roles, except we call add_roles instead.
server_roles = [
role for role in ctx.message.guild.roles if not role.is_default()
]
members = ctx.message.mentions
if len(members) == 0:
await ctx.send(
"Please provide the list of members you want to add a role to"
)
try:
msg = await ctx.bot.wait_for("message", check=check, timeout=60)
except asyncio.TimeoutError:
await ctx.send("You took too long. I'm impatient, don't make me wait")
return
if len(msg.mentions) == 0:
await ctx.send(
"I cannot add a role to someone if you don't provide someone..."
)
return
members = msg.mentions
await ctx.send(
"Alright, please provide the roles you would like to add to this member. "
"Make sure the roles, if more than one is provided, are separate by commas. "
)
try:
msg = await ctx.bot.wait_for("message", check=check, timeout=60)
except asyncio.TimeoutError:
await ctx.send("You took too long. I'm impatient, don't make me wait")
return
role_names = re.split(", ?", msg.content)
roles = []
for role in role_names:
_role = discord.utils.get(server_roles, name=role)
if _role is not None:
roles.append(_role)
if len(roles) == 0:
await ctx.send("Please provide a valid role next time!")
return
for member in members:
await member.add_roles(*roles)
await ctx.send(
"I have just added the following roles:```\n{}``` to the following members:"
"```\n{}```".format(
"\n".join(role_names), "\n".join([m.display_name for m in members])
)
)
@role.command(name="delete")
@commands.guild_only()
@utils.can_run(manage_roles=True)
async def delete_role(self, ctx, *, role: discord.Role = None):
"""This command can be used to delete one of the roles from the server
EXAMPLE: !role delete StupidRole
RESULT: No more role called StupidRole"""
# No use in running through everything if the bot cannot manage roles
if not ctx.message.guild.me.permissions_in(ctx.message.channel).manage_roles:
await ctx.send(
"I can't delete roles in this server, do you not trust me? :c"
)
return
# If no role was given, get the current roles on the server and ask which ones they'd like to remove
if role is None:
server_roles = [
role for role in ctx.message.guild.roles if not role.is_default()
]
await ctx.send(
"Which role would you like to remove from the server? Here is a list of this server's roles:"
"```\n{}```".format("\n".join([r.name for r in server_roles]))
)
# For this method we're only going to delete one role at a time
# This check attempts to find a role based on the content provided, if it can't find one it returns None
# We can use that fact to simply use just that as our check
def check(m):
if m.author == ctx.message.author and m.channel == ctx.message.channel:
return discord.utils.get(server_roles, name=m.content) is not None
else:
return False
try:
msg = await ctx.bot.wait_for("message", timeout=60, check=check)
except asyncio.TimeoutError:
await ctx.send("You took too long. I'm impatient, don't make me wait")
return
# If we have gotten here, based on our previous check, we know that the content provided is a valid role.
# Due to that, no need for any error checking here
role = discord.utils.get(server_roles, name=msg.content)
await role.delete()
await ctx.send(
"I have just removed the role {} from this server".format(role.name)
)
@role.command(name="create")
@commands.guild_only()
@utils.can_run(manage_roles=True)
async def create_role(self, ctx):
"""This command can be used to create a new role for this server
A prompt will follow asking what settings you would like for this new role
I'll then ask if you'd like to set anyone to use this role
EXAMPLE: !role create
RESULT: A follow along in order to create a new role"""
# No use in running through everything if the bot cannot create the role
if not ctx.message.guild.me.permissions_in(ctx.message.channel).manage_roles:
await ctx.send(
"I can't create roles in this server, do you not trust me? :c"
)
return
# Save a couple variables that will be used repeatedly
author = ctx.message.author
server = ctx.message.guild
channel = ctx.message.channel
# A couple checks that will be used in the wait_for_message's
def num_seperated_check(m):
if m.author == author and m.channel == channel:
return re.search(r"(\d(, ?| )?|[nN]one)", m.content) is not None
else:
return False
def yes_no_check(m):
if m.author == author and m.channel == channel:
return re.search("(yes|no)", m.content.lower()) is not None
else:
return False
def members_check(m):
if m.author == author and m.channel == channel:
return len(m.mentions) > 0
else:
return False
# Start the checks for the role, get the name of the role first
await ctx.send(
"Alright! I'm ready to create a new role, please respond with the name of the role you want to create"
)
try:
msg = await ctx.bot.wait_for(
"message",
timeout=60.0,
check=lambda m: m.author == author and m.channel == channel,
)
except asyncio.TimeoutError:
await ctx.send("You took too long. I'm impatient, don't make me wait")
return
name = msg.content
# Print a list of all the permissions available, then ask for which ones need to be active on this new role
all_perms = list(discord.Permissions.VALID_FLAGS.keys())
fmt = "\n".join("{}) {}".format(i, perm) for i, perm in enumerate(all_perms))
await ctx.send(
"Sounds fancy! Here is a list of all the permissions available. Please respond with just "
"the numbers, seperated by commas, of the permissions you want this role to have.\n"
"```\n{}```".format(fmt)
)
# For this we're going to give a couple extra minutes before we timeout
# as it might take a bit to figure out which permissions they want
try:
msg = await ctx.bot.wait_for(
"message", timeout=180.0, check=num_seperated_check
)
except asyncio.TimeoutError:
await ctx.send("You took too long. I'm impatient, don't make me wait")
return
# Check if any integer's were provided that are within the length of the list of permissions
num_permissions = [
int(i)
for i in re.split(" ?,?", msg.content)
if i.isdigit() and int(i) < len(all_perms)
]
# Check if this role should be in a separate section on the sidebard, i.e. hoisted
await ctx.send(
"Do you want this role to be in a separate section on the sidebar? (yes or no)"
)
try:
msg = await ctx.bot.wait_for("message", timeout=60.0, check=yes_no_check)
except asyncio.TimeoutError:
await ctx.send("You took too long. I'm impatient, don't make me wait")
return
hoist = True if msg.content.lower() == "yes" else False
# Check if this role should be able to be mentioned
await ctx.send("Do you want this role to be mentionable? (yes or no)")
try:
msg = await ctx.bot.wait_for("message", timeout=60.0, check=yes_no_check)
except asyncio.TimeoutError:
await ctx.send("You took too long. I'm impatient, don't make me wait")
return
mentionable = True if msg.content.lower() == "yes" else False
# Ready to actually create the role
# First create a permissions object based on the numbers provided
perms = discord.Permissions.none()
for index in num_permissions:
setattr(perms, all_perms[index], True)
payload = {
"name": name,
"permissions": perms,
"hoist": hoist,
"mentionable": mentionable,
}
# Create the role, and wait a second, sometimes it goes too quickly and we get a role with 'new role' to print
role = await server.create_role(**payload)
await asyncio.sleep(1)
await ctx.send(
"We did it! You just created the new role {}\nIf you want to add this role"
" to some people, mention them now".format(role.name)
)
try:
msg = await ctx.bot.wait_for("message", timeout=60.0, check=members_check)
except asyncio.TimeoutError:
# There's no need to mention the users, so don't send a failure message if they didn't, just return
return
# Otherwise members were mentioned, add the new role to them now
for member in msg.mentions:
await member.add_roles(role)
fmt = "\n".join(m.display_name for m in msg.mentions)
await ctx.send("I have just added the role {} to: ```\n{}```".format(name, fmt))
@commands.group(invoke_without_command=True)
@commands.guild_only()
@utils.can_run(send_messages=True)
async def assign(self, ctx, *role: discord.Role):
"""Assigns the provided role(s) to you, if they can be assigned
EXAMPLE: !assign me Member
RESULT: You now have the Member role"""
if not ctx.message.guild.me.guild_permissions.manage_roles:
await ctx.send("I need to have manage roles permissions to assign roles")
return
author = ctx.message.author
result = await ctx.bot.db.fetchrow(
"SELECT assignable_roles FROM guilds WHERE id = $1", ctx.guild.id
)
if result is None:
await ctx.send("There are no self-assignable roles on this server")
return
self_assignable_roles = result["assignable_roles"]
if len(self_assignable_roles) == 0:
await ctx.send("There are no self-assignable roles on this server")
return
fmt = ""
roles = [r for r in role if r.id in self_assignable_roles]
fmt += "\n".join(
[
"Successfully added {}".format(r.name)
if r.id in self_assignable_roles
else "{} is not available to be self-assigned".format(r.name)
for r in role
]
)
try:
await author.add_roles(*roles)
await ctx.send(fmt)
except discord.HTTPException:
await ctx.send("I cannot assign roles to you {}".format(author.mention))
@commands.command()
@commands.guild_only()
@utils.can_run(send_messages=True)
async def unassign(self, ctx, *role: discord.Role):
"""Unassigns the provided role(s) to you, if they can be assigned
EXAMPLE: !unassign Member
RESULT: You now no longer have the Member role"""
if not ctx.message.guild.me.guild_permissions.manage_roles:
await ctx.send("I need to have manage roles permissions to assign roles")
return
author = ctx.message.author
result = await ctx.bot.db.fetchrow(
"SELECT assignable_roles FROM guilds WHERE id = $1", ctx.guild.id
)
if result is None:
await ctx.send("There are no self-assignable roles on this server")
return
self_assignable_roles = result["assignable_roles"]
if len(self_assignable_roles) == 0:
await ctx.send("There are no self-assignable roles on this server")
return
fmt = ""
roles = [r for r in role if r.id in self_assignable_roles]
fmt += "\n".join(
[
"Successfully removed {}".format(r.name)
if str(r.id) in self_assignable_roles
else "{} is not available to be self-assigned".format(r.name)
for r in role
]
)
try:
await author.remove_roles(*roles)
await ctx.send(fmt)
except discord.HTTPException:
await ctx.send("I cannot remove roles from you {}".format(author.mention))
def setup(bot):
bot.add_cog(Roles(bot))