You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Fox-V3/ccrole/ccrole.py

413 lines
14 KiB

import asyncio
import json
import re
from typing import Any
import discord
from discord.ext.commands.view import StringView
from redbot.core import Config, checks
from redbot.core import commands
from redbot.core.bot import Red
from redbot.core.utils.chat_formatting import pagify, box
class CCRole(commands.Cog):
"""
Custom commands
Creates commands used to display text and adjust roles
"""
def __init__(self, bot: Red):
self.bot = bot
self.config = Config.get_conf(self, identifier=9999114111108101)
default_guild = {"cmdlist": {}, "settings": {}}
self.config.register_guild(**default_guild)
@commands.guild_only()
@commands.group()
async def ccrole(self, ctx):
"""Custom commands management with roles
Highly customizable custom commands with role management."""
if not ctx.invoked_subcommand:
pass
@ccrole.command(name="add")
@checks.mod_or_permissions(administrator=True)
async def ccrole_add(self, ctx, command: str):
"""Adds a custom command with roles
When adding text, put arguments in `{}` to eval them
Options: `{author}`, `{target}`, `{server}`, `{channel}`, `{message}`"""
command = command.lower()
if command in self.bot.all_commands:
await ctx.send("That command is already a standard command.")
return
guild = ctx.guild
author = ctx.author
channel = ctx.channel
cmd_list = self.config.guild(guild).cmdlist
if await cmd_list.get_raw(command, default=None):
await ctx.send(
"This command already exists. Delete it with `{}ccrole delete` first.".format(
ctx.prefix
)
)
return
# Roles to add
await ctx.send(
"What roles should it add? (Must be **comma separated**)\nSay `None` to skip adding roles"
)
def check(m):
return m.author == author and m.channel == channel
try:
answer = await self.bot.wait_for("message", timeout=120, check=check)
except asyncio.TimeoutError:
await ctx.send("Timed out, canceling")
return
arole_list = []
if answer.content.upper() != "NONE":
arole_list = await self._get_roles_from_content(ctx, answer.content)
if arole_list is None:
await ctx.send("Invalid answer, canceling")
return
# Roles to remove
await ctx.send(
"What roles should it remove? (Must be comma separated)\nSay `None` to skip removing roles"
)
try:
answer = await self.bot.wait_for("message", timeout=120, check=check)
except asyncio.TimeoutError:
await ctx.send("Timed out, canceling")
return
rrole_list = []
if answer.content.upper() != "NONE":
rrole_list = await self._get_roles_from_content(ctx, answer.content)
if rrole_list is None:
await ctx.send("Invalid answer, canceling")
return
# Roles to use
await ctx.send(
"What roles are allowed to use this command? (Must be comma separated)\nSay `None` to allow all roles"
)
try:
answer = await self.bot.wait_for("message", timeout=120, check=check)
except asyncio.TimeoutError:
await ctx.send("Timed out, canceling")
return
prole_list = []
if answer.content.upper() != "NONE":
prole_list = await self._get_roles_from_content(ctx, answer.content)
if prole_list is None:
await ctx.send("Invalid answer, canceling")
return
# Selfrole
await ctx.send("Is this a targeted command?(yes/no)\nNo will make this a selfrole command")
try:
answer = await self.bot.wait_for("message", timeout=120, check=check)
except asyncio.TimeoutError:
await ctx.send("Timed out, canceling")
return
if answer.content.upper() in ["Y", "YES"]:
targeted = True
await ctx.send("This command will be **`targeted`**")
else:
targeted = False
await ctx.send("This command will be **`selfrole`**")
# Message to send
await ctx.send(
"What message should the bot say when using this command?\n"
"Say `None` to send the default `Success!` message\n"
"Eval Options: `{author}`, `{target}`, `{server}`, `{channel}`, `{message}`\n"
"For example: `Welcome {target.mention} to {server.name}!`"
)
try:
answer = await self.bot.wait_for("message", timeout=120, check=check)
except asyncio.TimeoutError:
await ctx.send("Timed out, canceling")
return
text = "Success!"
if answer.content.upper() != "NONE":
text = answer.content
# Save the command
out = {
"text": text,
"aroles": arole_list,
"rroles": rrole_list,
"proles": prole_list,
"targeted": targeted,
}
await cmd_list.set_raw(command, value=out)
await ctx.send("Custom Command **`{}`** successfully added".format(command))
@ccrole.command(name="delete")
@checks.mod_or_permissions(administrator=True)
async def ccrole_delete(self, ctx, command: str):
"""Deletes a custom command
Example:
`[p]ccrole delete yourcommand`"""
guild = ctx.guild
command = command.lower()
if not await self.config.guild(guild).cmdlist.get_raw(command, default=None):
await ctx.send("That command doesn't exist")
else:
await self.config.guild(guild).cmdlist.set_raw(command, value=None)
await ctx.send("Custom command successfully deleted.")
@ccrole.command(name="details")
async def ccrole_details(self, ctx, command: str):
"""Provide details about passed custom command"""
guild = ctx.guild
command = command.lower()
cmd = await self.config.guild(guild).cmdlist.get_raw(command, default=None)
if cmd is None:
await ctx.send("That command doesn't exist")
return
embed = discord.Embed(
title=command,
description="{} custom command".format(
"Targeted" if cmd["targeted"] else "Non-Targeted"
),
)
def process_roles(role_list):
if not role_list:
return "None"
return ", ".join(
[discord.utils.get(ctx.guild.roles, id=roleid).name for roleid in role_list]
)
embed.add_field(name="Text", value="```{}```".format(cmd["text"]))
embed.add_field(name="Adds Roles", value=process_roles(cmd["aroles"]), inline=True)
embed.add_field(name="Removes Roles", value=process_roles(cmd["rroles"]), inline=True)
embed.add_field(name="Role Restrictions", value=process_roles(cmd["proles"]), inline=True)
await ctx.send(embed=embed)
@ccrole.command(name="list")
async def ccrole_list(self, ctx):
"""Shows custom commands list"""
guild = ctx.guild
cmd_list = await self.config.guild(guild).cmdlist()
cmd_list = {k: v for k, v in cmd_list.items() if v}
if not cmd_list:
await ctx.send(
"There are no custom commands in this server. Use `{}ccrole add` to start adding some.".format(
ctx.prefix
)
)
return
cmd_list = ", ".join([ctx.prefix + c for c in sorted(cmd_list.keys())])
cmd_list = "Custom commands:\n\n" + cmd_list
if (
len(cmd_list) < 1500
): # I'm allowed to have arbitrary numbers for when it's too much to dm dammit
await ctx.send(box(cmd_list))
else:
for page in pagify(cmd_list, delims=[" ", "\n"]):
await ctx.author.send(box(page))
await ctx.send("Command list DM'd")
@commands.Cog.listener()
async def on_command_error(self, ctx: commands.Context, exception):
cmd = ctx.invoked_with
guild = ctx.guild
message = ctx.message
cmdlist = self.config.guild(guild).cmdlist
# cmd = message.content[len(prefix) :].split()[0].lower()
cmd = await cmdlist.get_raw(cmd, default=None)
if cmd is not None:
await self.eval_cc(cmd, message, ctx)
# @commands.Cog.listener()
# async def on_message(self, message: discord.Message):
# if len(message.content) < 2 or message.guild is None:
# return
#
# ctx: commands.Context = await self.bot.get_context(message)
# cmd = ctx.invoked_with
# guild = message.guild
# # try:
# # prefix = await self.get_prefix(message)
# # except ValueError:
# # return
#
# # prefix = ctx.prefix
#
# cmdlist = self.config.guild(guild).cmdlist
# # cmd = message.content[len(prefix) :].split()[0].lower()
# cmd = await cmdlist.get_raw(cmd, default=None)
#
# if cmd is not None:
# await self.eval_cc(cmd, message, ctx)
async def _get_roles_from_content(self, ctx, content):
content_list = content.split(",")
try:
role_list = [
discord.utils.get(ctx.guild.roles, name=role.strip(" ")).id
for role in content_list
]
except (discord.HTTPException, AttributeError): # None.id is attribute error
return None
else:
return role_list
async def get_prefix(self, message: discord.Message) -> str:
"""
Borrowed from alias cog
Tries to determine what prefix is used in a message object.
Looks to identify from longest prefix to smallest.
Will raise ValueError if no prefix is found.
:param message: Message object
:return:
"""
content = message.content
prefix_list = await self.bot.command_prefix(self.bot, message)
prefixes = sorted(prefix_list, key=lambda pfx: len(pfx), reverse=True)
for p in prefixes:
if content.startswith(p):
return p
raise ValueError
async def eval_cc(self, cmd, message, ctx):
"""Does all the work"""
if cmd["proles"] and not (
set(role.id for role in message.author.roles) & set(cmd["proles"])
):
return # Not authorized, do nothing
if cmd["targeted"]:
# try:
# arg1 = message.content.split(maxsplit=1)[1]
# except IndexError: # .split() return list of len<2
# target = None
# else:
# target = discord.utils.get(
# message.guild.members, mention=arg1
# )
view: StringView = ctx.view
view.skip_ws()
guild: discord.Guild = ctx.guild
# print(f"Guild: {guild}")
target = view.get_quoted_word()
# print(f"Target: {target}")
if target:
# target = discord.utils.get(guild.members, mention=target)
try:
target = await commands.MemberConverter().convert(ctx, target)
except commands.BadArgument:
target = None
else:
target = None
# try:
# arg1 = ctx.args[1]
# except IndexError: # args is list of len<2
# target = None
# else:
# target = discord.utils.get(
# message.guild.members, mention=arg1
# )
if not target:
out_message = "This custom command is targeted! @mention a target\n`{} <target>`".format(
ctx.invoked_with
)
await message.channel.send(out_message)
return
else:
target = message.author
if cmd["aroles"]:
arole_list = [
discord.utils.get(message.guild.roles, id=roleid) for roleid in cmd["aroles"]
]
# await self.bot.send_message(message.channel, "Adding: "+str([str(arole) for arole in arole_list]))
try:
await target.add_roles(*arole_list)
except discord.Forbidden:
await message.channel.send("Permission error: Unable to add roles")
await asyncio.sleep(1)
if cmd["rroles"]:
rrole_list = [
discord.utils.get(message.guild.roles, id=roleid) for roleid in cmd["rroles"]
]
# await self.bot.send_message(message.channel, "Removing: "+str([str(rrole) for rrole in rrole_list]))
try:
await target.remove_roles(*rrole_list)
except discord.Forbidden:
await message.channel.send("Permission error: Unable to remove roles")
out_message = self.format_cc(cmd, message, target)
await message.channel.send(out_message)
def format_cc(self, cmd, message, target):
out = cmd["text"]
results = re.findall("{([^}]+)\}", out)
for result in results:
param = self.transform_parameter(result, message, target)
out = out.replace("{" + result + "}", param)
return out
def transform_parameter(self, result, message, target):
"""
For security reasons only specific objects are allowed
Internals are ignored
"""
raw_result = "{" + result + "}"
objects = {
"message": message,
"author": message.author,
"channel": message.channel,
"server": message.guild,
"guild": message.guild,
"target": target,
}
if result in objects:
return str(objects[result])
try:
first, second = result.split(".")
except ValueError:
return raw_result
if first in objects and not second.startswith("_"):
first = objects[first]
else:
return raw_result
return str(getattr(first, second, raw_result))