[V3 Everything] Package bot and write setup scripts (#964)

Ya'll are gonna hate me.

* Initial modifications

* Add initial setup.py

* working setup py help

* Modify setup file to package stuff

* Move a bunch of shit and fix imports

* Fix or skip tests

* Must add init files for find_packages to work

* Move main to scripts folder and rename

* Add shebangs

* Copy over translation files

* WORKING PIP INSTALL

* add dependency information

* Hardcoded version for now, will need to figure out a better way to do this

* OKAY ITS FINALLY FUCKING WORKING

* Add this guy

* Fix stuff

* Change readme to rst

* Remove double sentry opt in

* Oopsie

* Fix this thing

* Aaaand fix test

* Aaaand fix test

* Fix core cog importing and default cog install path

* Adjust readme

* change instance name from optional to required

* Ayyy let's do more dependency injection
This commit is contained in:
Will
2017-09-08 23:14:32 -04:00
committed by GitHub
parent 6b1fc786ee
commit d69fd63da7
85 changed files with 451 additions and 255 deletions

View File

@@ -0,0 +1,6 @@
from .alias import Alias
from discord.ext import commands
def setup(bot: commands.Bot):
bot.add_cog(Alias(bot))

368
redbot/cogs/alias/alias.py Normal file
View File

@@ -0,0 +1,368 @@
from copy import copy
from typing import Generator, Tuple, Iterable
import discord
from redbot.core import Config
from redbot.core.i18n import CogI18n
from redbot.core.utils.chat_formatting import box
from discord.ext import commands
from redbot.core.bot import Red
from .alias_entry import AliasEntry
_ = CogI18n("Alias", __file__)
class Alias:
"""
Alias
Aliases are per server shortcuts for commands. They
can act as both a lambda (storing arguments for repeated use)
or as simply a shortcut to saying "x y z".
When run, aliases will accept any additional arguments
and append them to the stored alias
"""
default_global_settings = {
"entries": []
}
default_guild_settings = {
"enabled": False,
"entries": [] # Going to be a list of dicts
}
def __init__(self, bot: Red):
self.bot = bot
self.file_path = "data/alias/aliases.json"
self._aliases = Config.get_conf(self, 8927348724)
self._aliases.register_global(**self.default_global_settings)
self._aliases.register_guild(**self.default_guild_settings)
async def unloaded_aliases(self, guild: discord.Guild) -> Generator[AliasEntry, None, None]:
return (AliasEntry.from_json(d) for d in (await self._aliases.guild(guild).entries()))
async def unloaded_global_aliases(self) -> Generator[AliasEntry, None, None]:
return (AliasEntry.from_json(d) for d in (await self._aliases.entries()))
async def loaded_aliases(self, guild: discord.Guild) -> Generator[AliasEntry, None, None]:
return (AliasEntry.from_json(d, bot=self.bot)
for d in (await self._aliases.guild(guild).entries()))
async def loaded_global_aliases(self) -> Generator[AliasEntry, None, None]:
return (AliasEntry.from_json(d, bot=self.bot) for d in (await self._aliases.entries()))
async def is_alias(self, guild: discord.Guild, alias_name: str,
server_aliases: Iterable[AliasEntry]=()) -> (bool, AliasEntry):
if not server_aliases:
server_aliases = await self.unloaded_aliases(guild)
global_aliases = await self.unloaded_global_aliases()
for aliases in (server_aliases, global_aliases):
for alias in aliases:
if alias.name == alias_name:
return True, alias
return False, None
def is_command(self, alias_name: str) -> bool:
command = self.bot.get_command(alias_name)
return command is not None
@staticmethod
def is_valid_alias_name(alias_name: str) -> bool:
return alias_name.isidentifier()
async def add_alias(self, ctx: commands.Context, alias_name: str,
command: Tuple[str], global_: bool=False) -> AliasEntry:
alias = AliasEntry(alias_name, command, ctx.author, global_=global_)
if global_:
curr_aliases = await self._aliases.entries()
curr_aliases.append(alias.to_json())
await self._aliases.entries.set(curr_aliases)
else:
curr_aliases = await self._aliases.guild(ctx.guild).entries()
curr_aliases.append(alias.to_json())
await self._aliases.guild(ctx.guild).entries.set(curr_aliases)
await self._aliases.guild(ctx.guild).enabled.set(True)
return alias
async def delete_alias(self, ctx: commands.Context, alias_name: str,
global_: bool=False) -> bool:
if global_:
aliases = await self.unloaded_global_aliases()
setter_func = self._aliases.entries.set
else:
aliases = await self.unloaded_aliases(ctx.guild)
setter_func = self._aliases.guild(ctx.guild).entries.set
did_delete_alias = False
to_keep = []
for alias in aliases:
if alias.name != alias_name:
to_keep.append(alias)
else:
did_delete_alias = True
await setter_func(
[a.to_json() for a in to_keep]
)
return did_delete_alias
def get_prefix(self, message: discord.Message) -> str:
"""
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:
"""
guild = message.guild
content = message.content
prefixes = sorted(self.bot.command_prefix(self.bot, message),
key=lambda pfx: len(pfx),
reverse=True)
for p in prefixes:
if content.startswith(p):
return p
raise ValueError(_("No prefix found."))
def get_extra_args_from_alias(self, message: discord.Message, prefix: str,
alias: AliasEntry) -> str:
"""
When an alias is executed by a user in chat this function tries
to get any extra arguments passed in with the call.
Whitespace will be trimmed from both ends.
:param message:
:param prefix:
:param alias:
:return:
"""
known_content_length = len(prefix) + len(alias.name)
extra = message.content[known_content_length:].strip()
return extra
async def maybe_call_alias(self, message: discord.Message,
aliases: Iterable[AliasEntry]=None):
try:
prefix = self.get_prefix(message)
except ValueError:
return
try:
potential_alias = message.content[len(prefix):].split(" ")[0]
except IndexError:
return False
is_alias, alias = await self.is_alias(message.guild, potential_alias, server_aliases=aliases)
if is_alias:
await self.call_alias(message, prefix, alias)
async def call_alias(self, message: discord.Message, prefix: str,
alias: AliasEntry):
new_message = copy(message)
args = self.get_extra_args_from_alias(message, prefix, alias)
# noinspection PyDunderSlots
new_message.content = "{}{} {}".format(prefix, alias.command, args)
await self.bot.process_commands(new_message)
@commands.group()
@commands.guild_only()
async def alias(self, ctx: commands.Context):
"""Manage per-server aliases for commands"""
if ctx.invoked_subcommand is None:
await self.bot.send_cmd_help(ctx)
@alias.group(name="global")
async def global_(self, ctx: commands.Context):
"""
Manage global aliases.
"""
if ctx.invoked_subcommand is None or \
isinstance(ctx.invoked_subcommand, commands.Group):
await self.bot.send_cmd_help(ctx)
@alias.command(name="add")
@commands.guild_only()
async def _add_alias(self, ctx: commands.Context,
alias_name: str, *, command):
"""
Add an alias for a command.
"""
#region Alias Add Validity Checking
is_command = self.is_command(alias_name)
if is_command:
await ctx.send(("You attempted to create a new alias"
" with the name {} but that"
" name is already a command on this bot.").format(alias_name))
return
is_alias, _ = await self.is_alias(ctx.guild, alias_name)
if is_alias:
await ctx.send(("You attempted to create a new alias"
" with the name {} but that"
" alias already exists on this server.").format(alias_name))
return
is_valid_name = self.is_valid_alias_name(alias_name)
if not is_valid_name:
await ctx.send(("You attempted to create a new alias"
" with the name {} but that"
" name is an invalid alias name. Alias"
" names may only contain letters, numbers,"
" and underscores and must start with a letter.").format(alias_name))
return
#endregion
# At this point we know we need to make a new alias
# and that the alias name is valid.
await self.add_alias(ctx, alias_name, command)
await ctx.send(_("A new alias with the trigger `{}`"
" has been created.").format(alias_name))
@global_.command(name="add")
async def _add_global_alias(self, ctx: commands.Context,
alias_name: str, *, command):
"""
Add a global alias for a command.
"""
# region Alias Add Validity Checking
is_command = self.is_command(alias_name)
if is_command:
await ctx.send(("You attempted to create a new global alias"
" with the name {} but that"
" name is already a command on this bot.").format(alias_name))
return
is_alias, _ = self.is_alias(ctx.guild, alias_name)
if is_alias:
await ctx.send(("You attempted to create a new alias"
" with the name {} but that"
" alias already exists on this server.").format(alias_name))
return
is_valid_name = self.is_valid_alias_name(alias_name)
if not is_valid_name:
await ctx.send(("You attempted to create a new alias"
" with the name {} but that"
" name is an invalid alias name. Alias"
" names may only contain letters, numbers,"
" and underscores and must start with a letter.").format(alias_name))
return
# endregion
await self.add_alias(ctx, alias_name, command, global_=True)
await ctx.send(_("A new global alias with the trigger `{}`"
" has been created.").format(alias_name))
@alias.command(name="help")
@commands.guild_only()
async def _help_alias(self, ctx: commands.Context, alias_name: str):
"""Tries to execute help for the base command of the alias"""
is_alias, alias = self.is_alias(ctx.guild, alias_name=alias_name)
if is_alias:
base_cmd = alias.command[0]
new_msg = copy(ctx.message)
new_msg.content = "{}help {}".format(ctx.prefix, base_cmd)
await self.bot.process_commands(new_msg)
else:
ctx.send(_("No such alias exists."))
@alias.command(name="show")
@commands.guild_only()
async def _show_alias(self, ctx: commands.Context, alias_name: str):
"""Shows what command the alias executes."""
is_alias, alias = await self.is_alias(ctx.guild, alias_name)
if is_alias:
await ctx.send(_("The `{}` alias will execute the"
" command `{}`").format(alias_name, alias.command))
else:
await ctx.send(_("There is no alias with the name `{}`").format(alias_name))
@alias.command(name="del")
@commands.guild_only()
async def _del_alias(self, ctx: commands.Context, alias_name: str):
"""
Deletes an existing alias on this server.
"""
aliases = await self.unloaded_aliases(ctx.guild)
try:
next(aliases)
except StopIteration:
await ctx.send(_("There are no aliases on this guild."))
return
if await self.delete_alias(ctx, alias_name):
await ctx.send(_("Alias with the name `{}` was successfully"
" deleted.").format(alias_name))
else:
await ctx.send(_("Alias with name `{}` was not found.").format(alias_name))
@global_.command(name="del")
async def _del_global_alias(self, ctx: commands.Context, alias_name: str):
"""
Deletes an existing global alias.
"""
aliases = await self.unloaded_global_aliases()
try:
next(aliases)
except StopIteration:
await ctx.send(_("There are no aliases on this bot."))
return
if await self.delete_alias(ctx, alias_name, global_=True):
await ctx.send(_("Alias with the name `{}` was successfully"
" deleted.").format(alias_name))
else:
await ctx.send(_("Alias with name `{}` was not found.").format(alias_name))
@alias.command(name="list")
@commands.guild_only()
async def _list_alias(self, ctx: commands.Context):
"""
Lists the available aliases on this server.
"""
names = [_("Aliases:"), ] + sorted(["+ " + a.name for a in (await self.unloaded_aliases(ctx.guild))])
if len(names) == 0:
await ctx.send(_("There are no aliases on this server."))
else:
await ctx.send(box("\n".join(names), "diff"))
@global_.command(name="list")
async def _list_global_alias(self, ctx: commands.Context):
"""
Lists the available global aliases on this bot.
"""
names = [_("Aliases:"), ] + sorted(["+ " + a.name for a in await self.unloaded_global_aliases()])
if len(names) == 0:
await ctx.send(_("There are no aliases on this server."))
else:
await ctx.send(box("\n".join(names), "diff"))
async def on_message(self, message: discord.Message):
aliases = list(await self.unloaded_global_aliases())
if message.guild is not None:
aliases = aliases + list(await self.unloaded_aliases(message.guild))
if len(aliases) == 0:
return
await self.maybe_call_alias(message, aliases=aliases)

View File

@@ -0,0 +1,63 @@
from typing import Tuple
from discord.ext import commands
import discord
class AliasEntry:
def __init__(self, name: str, command: Tuple[str],
creator: discord.Member, global_: bool=False):
super().__init__()
self.has_real_data = False
self.name = name
self.command = command
self.creator = creator
self.global_ = global_
self.guild = None
if hasattr(creator, "guild"):
self.guild = creator.guild
self.uses = 0
def inc(self):
"""
Increases the `uses` stat by 1.
:return: new use count
"""
self.uses += 1
return self.uses
def to_json(self) -> dict:
try:
creator = str(self.creator.id)
guild = str(self.guild.id)
except AttributeError:
creator = self.creator
guild = self.guild
return {
"name": self.name,
"command": self.command,
"creator": creator,
"guild": guild,
"global": self.global_,
"uses": self.uses
}
@classmethod
def from_json(cls, data: dict, bot: commands.Bot=None):
ret = cls(data["name"], data["command"],
data["creator"], global_=data["global"])
if bot:
ret.has_real_data = True
ret.creator = bot.get_user(int(data["creator"]))
guild = bot.get_guild(int(data["guild"]))
ret.guild = guild
else:
ret.guild = data["guild"]
ret.uses = data.get("uses", 0)
return ret

View File

@@ -0,0 +1,65 @@
# Copyright (C) 2017 Red-DiscordBot
# UltimatePancake <pier.gaetani@gmail.com>, 2017.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2017-08-26 17:23+EDT\n"
"PO-Revision-Date: 2017-08-26 18:37-0600\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 2.0.3\n"
"Last-Translator: UltimatePancake <pier.gaetani@gmail.com>\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Language: es\n"
#: ../alias.py:138
msgid "No prefix found."
msgstr "No se encontró prefijo."
#: ../alias.py:234
msgid "A new alias with the trigger `{}` has been created."
msgstr "Un nuevo alias con el disparador `{}` ha sido creado."
#: ../alias.py:270
msgid "A new global alias with the trigger `{}` has been created."
msgstr "Un nuevo alias global con el disparador `{}` ha sido creado."
#: ../alias.py:285
msgid "No such alias exists."
msgstr "Dicho alias no existe."
#: ../alias.py:294
msgid "The `{}` alias will execute the command `{}`"
msgstr "El alias `{}` ejecutará el comando `{}`"
#: ../alias.py:297
msgid "There is no alias with the name `{}`"
msgstr "No existe un alias con el nombre `{}`"
#: ../alias.py:309
msgid "There are no aliases on this guild."
msgstr "No hay alias en este gremio."
#: ../alias.py:313 ../alias.py:331
msgid "Alias with the name `{}` was successfully deleted."
msgstr "El alias `{}` fue eliminado exitósamente."
#: ../alias.py:316 ../alias.py:334
msgid "Alias with name `{}` was not found."
msgstr "El alias `{}` no fue encontrado."
#: ../alias.py:327
msgid "There are no aliases on this bot."
msgstr "No hay alias en este bot."
#: ../alias.py:342 ../alias.py:353
msgid "Aliases:"
msgstr "Alias:"
#: ../alias.py:344 ../alias.py:355
msgid "There are no aliases on this server."
msgstr "No hay alias en este servidor."

View File

@@ -0,0 +1,65 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2017-08-26 17:23+EDT\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: ENCODING\n"
"Generated-By: pygettext.py 1.5\n"
#: ../alias.py:138
msgid "No prefix found."
msgstr ""
#: ../alias.py:234
msgid "A new alias with the trigger `{}` has been created."
msgstr ""
#: ../alias.py:270
msgid "A new global alias with the trigger `{}` has been created."
msgstr ""
#: ../alias.py:285
msgid "No such alias exists."
msgstr ""
#: ../alias.py:294
msgid "The `{}` alias will execute the command `{}`"
msgstr ""
#: ../alias.py:297
msgid "There is no alias with the name `{}`"
msgstr ""
#: ../alias.py:309
msgid "There are no aliases on this guild."
msgstr ""
#: ../alias.py:313 ../alias.py:331
msgid "Alias with the name `{}` was successfully deleted."
msgstr ""
#: ../alias.py:316 ../alias.py:334
msgid "Alias with name `{}` was not found."
msgstr ""
#: ../alias.py:327
msgid "There are no aliases on this bot."
msgstr ""
#: ../alias.py:342 ../alias.py:353
msgid "Aliases:"
msgstr ""
#: ../alias.py:344 ../alias.py:355
msgid "There are no aliases on this server."
msgstr ""