Tobotimus 14cc701b25 [V3] Update black version and reformat (#1745)
* Update black version and reformat

* Pin black in extras_require
2018-05-29 18:37:00 -08:00

355 lines
12 KiB
Python

import os
import re
import random
from datetime import datetime
import discord
from redbot.core import Config, checks, commands
from redbot.core.utils.chat_formatting import box, pagify
from redbot.core.i18n import Translator, cog_i18n
_ = Translator("CustomCommands", __file__)
class CCError(Exception):
pass
class NotFound(CCError):
pass
class AlreadyExists(CCError):
pass
class CommandObj:
def __init__(self, **kwargs):
config = kwargs.get("config")
self.bot = kwargs.get("bot")
self.db = config.guild
@staticmethod
async def get_commands(config) -> dict:
commands = await config.commands()
customcommands = {k: v for k, v in commands.items() if commands[k]}
if len(customcommands) == 0:
return None
return customcommands
async def get_responses(self, ctx):
intro = _(
"Welcome to the interactive random {} maker!\n"
"Every message you send will be added as one of the random "
"response to choose from once this {} is "
"triggered. To exit this interactive menu, type `{}`"
).format("customcommand", "customcommand", "exit()")
await ctx.send(intro)
def check(m):
return m.channel == ctx.channel and m.author == ctx.message.author
responses = []
while True:
await ctx.send(_("Add a random response:"))
msg = await self.bot.wait_for("message", check=check)
if msg.content.lower() == "exit()":
break
else:
responses.append(msg.content)
return responses
def get_now(self) -> str:
# Get current time as a string, for 'created_at' and 'edited_at' fields
# in the ccinfo dict
return "{:%d/%m/%Y %H:%M:%S}".format(datetime.utcnow())
async def get(self, message: discord.Message, command: str) -> str:
ccinfo = await self.db(message.guild).commands.get_raw(command, default=None)
if not ccinfo:
raise NotFound
else:
return ccinfo["response"]
async def create(self, ctx: commands.Context, command: str, response):
"""Create a customcommand"""
# Check if this command is already registered as a customcommand
if await self.db(ctx.guild).commands.get_raw(command, default=None):
raise AlreadyExists()
author = ctx.message.author
ccinfo = {
"author": {"id": author.id, "name": author.name},
"command": command,
"created_at": self.get_now(),
"editors": [],
"response": response,
}
await self.db(ctx.guild).commands.set_raw(command, value=ccinfo)
async def edit(self, ctx: commands.Context, command: str, response: None):
"""Edit an already existing custom command"""
# Check if this command is registered
if not await self.db(ctx.guild).commands.get_raw(command, default=None):
raise NotFound()
author = ctx.message.author
ccinfo = await self.db(ctx.guild).commands.get_raw(command, default=None)
def check(m):
return m.channel == ctx.channel and m.author == ctx.message.author
if not response:
await ctx.send(_("Do you want to create a 'randomized' cc? {}").format("y/n"))
msg = await self.bot.wait_for("message", check=check)
if msg.content.lower() == "y":
response = await self.get_responses(ctx=ctx)
else:
await ctx.send(_("What response do you want?"))
response = (await self.bot.wait_for("message", check=check)).content
ccinfo["response"] = response
ccinfo["edited_at"] = self.get_now()
if author.id not in ccinfo["editors"]:
# Add the person who invoked the `edit` coroutine to the list of
# editors, if the person is not yet in there
ccinfo["editors"].append(author.id)
await self.db(ctx.guild).commands.set_raw(command, value=ccinfo)
async def delete(self, ctx: commands.Context, command: str):
"""Delete an already exisiting custom command"""
# Check if this command is registered
if not await self.db(ctx.guild).commands.get_raw(command, default=None):
raise NotFound()
await self.db(ctx.guild).commands.set_raw(command, value=None)
@cog_i18n(_)
class CustomCommands:
"""Custom commands
Creates commands used to display text"""
def __init__(self, bot):
self.bot = bot
self.key = 414589031223512
self.config = Config.get_conf(self, self.key)
self.config.register_guild(commands={})
self.commandobj = CommandObj(config=self.config, bot=self.bot)
@commands.group(aliases=["cc"], no_pm=True)
@commands.guild_only()
async def customcom(self, ctx: commands.Context):
"""Custom commands management"""
if not ctx.invoked_subcommand:
await ctx.send_help()
@customcom.group(name="add")
@checks.mod_or_permissions(administrator=True)
async def cc_add(self, ctx: commands.Context):
"""
CCs can be enhanced with arguments:
Argument What it will be substituted with
{message} message
{author} message.author
{channel} message.channel
{guild} message.guild
{server} message.guild
"""
if not ctx.invoked_subcommand or isinstance(ctx.invoked_subcommand, commands.Group):
await ctx.send_help()
@cc_add.command(name="random")
@checks.mod_or_permissions(administrator=True)
async def cc_add_random(self, ctx: commands.Context, command: str):
"""
Create a CC where it will randomly choose a response!
Note: This is interactive
"""
channel = ctx.channel
responses = []
responses = await self.commandobj.get_responses(ctx=ctx)
try:
await self.commandobj.create(ctx=ctx, command=command, response=responses)
await ctx.send(_("Custom command successfully added."))
except AlreadyExists:
await ctx.send(
_("This command already exists. Use " "`{}` to edit it.").format(
"{}customcom edit".format(ctx.prefix)
)
)
# await ctx.send(str(responses))
@cc_add.command(name="simple")
@checks.mod_or_permissions(administrator=True)
async def cc_add_simple(self, ctx, command: str, *, text):
"""Adds a simple custom command
Example:
[p]customcom add simple yourcommand Text you want
"""
guild = ctx.guild
command = command.lower()
if command in self.bot.all_commands:
await ctx.send(_("That command is already a standard command."))
return
try:
await self.commandobj.create(ctx=ctx, command=command, response=text)
await ctx.send(_("Custom command successfully added."))
except AlreadyExists:
await ctx.send(
_("This command already exists. Use " "`{}` to edit it.").format(
"{}customcom edit".format(ctx.prefix)
)
)
@customcom.command(name="edit")
@checks.mod_or_permissions(administrator=True)
async def cc_edit(self, ctx, command: str, *, text=None):
"""Edits a custom command
Example:
[p]customcom edit yourcommand Text you want
"""
guild = ctx.message.guild
command = command.lower()
try:
await self.commandobj.edit(ctx=ctx, command=command, response=text)
await ctx.send(_("Custom command successfully edited."))
except NotFound:
await ctx.send(
_("That command doesn't exist. Use " "`{}` to add it.").format(
"{}customcom add".format(ctx.prefix)
)
)
@customcom.command(name="delete")
@checks.mod_or_permissions(administrator=True)
async def cc_delete(self, ctx, command: str):
"""Deletes a custom command
Example:
[p]customcom delete yourcommand"""
guild = ctx.message.guild
command = command.lower()
try:
await self.commandobj.delete(ctx=ctx, command=command)
await ctx.send(_("Custom command successfully deleted."))
except NotFound:
await ctx.send(_("That command doesn't exist."))
@customcom.command(name="list")
async def cc_list(self, ctx):
"""Shows custom commands list"""
response = await CommandObj.get_commands(self.config.guild(ctx.guild))
if not response:
await ctx.send(
_(
"There are no custom commands in this server."
" Use `{}` to start adding some."
).format("{}customcom add".format(ctx.prefix))
)
return
results = []
for command, body in response.items():
responses = body["response"]
if isinstance(responses, list):
result = ", ".join(responses)
elif isinstance(responses, str):
result = responses
else:
continue
results.append("{command:<15} : {result}".format(command=command, result=result))
commands = "\n".join(results)
if len(commands) < 1500:
await ctx.send(box(commands))
else:
for page in pagify(commands, delims=[" ", "\n"]):
await ctx.author.send(box(page))
async def on_message(self, message):
is_private = isinstance(message.channel, discord.abc.PrivateChannel)
if len(message.content) < 2 or is_private:
return
guild = message.guild
prefixes = await self.bot.db.guild(guild).get_raw("prefix", default=[])
if len(prefixes) < 1:
def_prefixes = await self.bot.get_prefix(message)
for prefix in def_prefixes:
prefixes.append(prefix)
# user_allowed check, will be replaced with self.bot.user_allowed or
# something similar once it's added
user_allowed = True
for prefix in prefixes:
if message.content.startswith(prefix):
break
else:
return
if user_allowed:
cmd = message.content[len(prefix) :]
try:
c = await self.commandobj.get(message=message, command=cmd)
if isinstance(c, list):
command = random.choice(c)
elif isinstance(c, str):
command = c
else:
raise NotFound()
except NotFound:
return
response = self.format_cc(command, message)
await message.channel.send(response)
def format_cc(self, command, message) -> str:
results = re.findall("\{([^}]+)\}", command)
for result in results:
param = self.transform_parameter(result, message)
command = command.replace("{" + result + "}", param)
return command
def transform_parameter(self, result, message) -> str:
"""
For security reasons only specific objects are allowed
Internals are ignored
"""
raw_result = "{" + result + "}"
objects = {
"message": message,
"author": message.author,
"channel": message.channel,
"guild": message.guild,
"server": message.guild,
}
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))