mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-20 09:56:05 -05:00
[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:
0
redbot/cogs/__init__.py
Normal file
0
redbot/cogs/__init__.py
Normal file
6
redbot/cogs/alias/__init__.py
Normal file
6
redbot/cogs/alias/__init__.py
Normal 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
368
redbot/cogs/alias/alias.py
Normal 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)
|
||||
63
redbot/cogs/alias/alias_entry.py
Normal file
63
redbot/cogs/alias/alias_entry.py
Normal 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
|
||||
65
redbot/cogs/alias/locales/es.po
Normal file
65
redbot/cogs/alias/locales/es.po
Normal 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."
|
||||
65
redbot/cogs/alias/locales/messages.pot
Normal file
65
redbot/cogs/alias/locales/messages.pot
Normal 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 ""
|
||||
|
||||
5
redbot/cogs/audio/__init__.py
Normal file
5
redbot/cogs/audio/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .audio import Audio
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(Audio(bot))
|
||||
112
redbot/cogs/audio/audio.py
Normal file
112
redbot/cogs/audio/audio.py
Normal file
@@ -0,0 +1,112 @@
|
||||
from discord.ext import commands
|
||||
from discord import FFmpegPCMAudio, PCMVolumeTransformer
|
||||
import os
|
||||
import youtube_dl
|
||||
import discord
|
||||
|
||||
|
||||
# Just a little experimental audio cog not meant for final release
|
||||
|
||||
|
||||
class Audio:
|
||||
"""Audio commands"""
|
||||
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
@commands.command()
|
||||
async def local(self, ctx, *, filename: str):
|
||||
"""Play mp3"""
|
||||
if ctx.author.voice is None:
|
||||
await ctx.send("Join a voice channel first!")
|
||||
return
|
||||
|
||||
if ctx.voice_client:
|
||||
if ctx.voice_client.channel != ctx.author.voice.channel:
|
||||
await ctx.voice_client.disconnect()
|
||||
path = os.path.join("cogs", "audio", "songs", filename + ".mp3")
|
||||
if not os.path.isfile(path):
|
||||
await ctx.send("Let's play a file that exists pls")
|
||||
return
|
||||
player = PCMVolumeTransformer(FFmpegPCMAudio(path), volume=1)
|
||||
voice = await ctx.author.voice.channel.connect()
|
||||
voice.play(player)
|
||||
await ctx.send("{} is playing a song...".format(ctx.author))
|
||||
|
||||
@commands.command()
|
||||
async def play(self, ctx, url: str):
|
||||
"""Play youtube url"""
|
||||
url = url.strip("<").strip(">")
|
||||
if ctx.author.voice is None:
|
||||
await ctx.send("Join a voice channel first!")
|
||||
return
|
||||
elif "youtube.com" not in url.lower():
|
||||
await ctx.send("Youtube links pls")
|
||||
return
|
||||
|
||||
if ctx.voice_client:
|
||||
if ctx.voice_client.channel != ctx.author.voice.channel:
|
||||
await ctx.voice_client.disconnect()
|
||||
yt = YoutubeSource(url)
|
||||
player = PCMVolumeTransformer(yt, volume=1)
|
||||
voice = await ctx.author.voice.channel.connect()
|
||||
voice.play(player)
|
||||
await ctx.send("{} is playing a song...".format(ctx.author))
|
||||
|
||||
@commands.command()
|
||||
async def stop(self, ctx):
|
||||
"""Stops the music and disconnects"""
|
||||
if ctx.voice_client:
|
||||
ctx.voice_client.source.cleanup()
|
||||
await ctx.voice_client.disconnect()
|
||||
else:
|
||||
await ctx.send("I'm not even connected to a voice channel!", delete_after=2)
|
||||
await ctx.message.delete()
|
||||
|
||||
@commands.command()
|
||||
async def pause(self, ctx):
|
||||
"""Pauses the music"""
|
||||
if ctx.voice_client:
|
||||
ctx.voice_client.pause()
|
||||
await ctx.send("👌", delete_after=2)
|
||||
else:
|
||||
await ctx.send("I'm not even connected to a voice channel!", delete_after=2)
|
||||
await ctx.message.delete()
|
||||
|
||||
@commands.command()
|
||||
async def resume(self, ctx):
|
||||
"""Resumes the music"""
|
||||
if ctx.voice_client:
|
||||
ctx.voice_client.resume()
|
||||
await ctx.send("👌", delete_after=2)
|
||||
else:
|
||||
await ctx.send("I'm not even connected to a voice channel!", delete_after=2)
|
||||
await ctx.message.delete()
|
||||
|
||||
@commands.command(hidden=True)
|
||||
async def volume(self, ctx, n: float):
|
||||
"""Sets the volume"""
|
||||
if ctx.voice_client:
|
||||
ctx.voice_client.source.volume = n
|
||||
await ctx.send("Volume set.", delete_after=2)
|
||||
else:
|
||||
await ctx.send("I'm not even connected to a voice channel!", delete_after=2)
|
||||
await ctx.message.delete()
|
||||
|
||||
def __unload(self):
|
||||
for vc in self.bot.voice_clients:
|
||||
if vc.source:
|
||||
vc.source.cleanup()
|
||||
self.bot.loop.create_task(vc.disconnect())
|
||||
|
||||
|
||||
class YoutubeSource(discord.FFmpegPCMAudio):
|
||||
def __init__(self, url):
|
||||
opts = {
|
||||
'format': 'webm[abr>0]/bestaudio/best',
|
||||
'prefer_ffmpeg': True,
|
||||
'quiet': True
|
||||
}
|
||||
ytdl = youtube_dl.YoutubeDL(opts)
|
||||
self.info = ytdl.extract_info(url, download=False)
|
||||
super().__init__(self.info['url'])
|
||||
5
redbot/cogs/bank/__init__.py
Normal file
5
redbot/cogs/bank/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .bank import Bank, check_global_setting_guildowner, check_global_setting_admin
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(Bank(bot))
|
||||
86
redbot/cogs/bank/bank.py
Normal file
86
redbot/cogs/bank/bank.py
Normal file
@@ -0,0 +1,86 @@
|
||||
from redbot.core import checks, bank
|
||||
from redbot.core.i18n import CogI18n
|
||||
from discord.ext import commands
|
||||
|
||||
from redbot.core.bot import Red # Only used for type hints
|
||||
|
||||
_ = CogI18n('Bank', __file__)
|
||||
|
||||
|
||||
def check_global_setting_guildowner():
|
||||
"""
|
||||
Command decorator. If the bank is not global, it checks if the author is
|
||||
either the guildowner or has the administrator permission.
|
||||
"""
|
||||
async def pred(ctx: commands.Context):
|
||||
author = ctx.author
|
||||
if await ctx.bot.is_owner(author):
|
||||
return True
|
||||
if not await bank.is_global():
|
||||
permissions = ctx.channel.permissions_for(author)
|
||||
return author == ctx.guild.owner or permissions.administrator
|
||||
|
||||
return commands.check(pred)
|
||||
|
||||
|
||||
def check_global_setting_admin():
|
||||
"""
|
||||
Command decorator. If the bank is not global, it checks if the author is
|
||||
either a bot admin or has the manage_guild permission.
|
||||
"""
|
||||
async def pred(ctx: commands.Context):
|
||||
author = ctx.author
|
||||
if await ctx.bot.is_owner(author):
|
||||
return True
|
||||
if not await bank.is_global():
|
||||
permissions = ctx.channel.permissions_for(author)
|
||||
is_guild_owner = author == ctx.guild.owner
|
||||
admin_role = await ctx.bot.db.guild(ctx.guild).admin_role()
|
||||
return admin_role in author.roles or is_guild_owner or permissions.manage_guild
|
||||
|
||||
return commands.check(pred)
|
||||
|
||||
|
||||
class Bank:
|
||||
"""Bank"""
|
||||
|
||||
def __init__(self, bot: Red):
|
||||
self.bot = bot
|
||||
|
||||
# SECTION commands
|
||||
|
||||
@commands.group()
|
||||
@checks.guildowner_or_permissions(administrator=True)
|
||||
async def bankset(self, ctx: commands.Context):
|
||||
"""Base command for bank settings"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await self.bot.send_cmd_help(ctx)
|
||||
|
||||
@bankset.command(name="toggleglobal")
|
||||
@checks.is_owner()
|
||||
async def bankset_toggleglobal(self, ctx: commands.Context):
|
||||
"""Toggles whether the bank is global or not
|
||||
If the bank is global, it will become per-guild
|
||||
If the bank is per-guild, it will become global"""
|
||||
cur_setting = await bank.is_global()
|
||||
await bank.set_global(not cur_setting, ctx.author)
|
||||
|
||||
word = _("per-guild") if cur_setting else _("global")
|
||||
|
||||
await ctx.send(_("The bank is now {}.").format(word))
|
||||
|
||||
@bankset.command(name="bankname")
|
||||
@check_global_setting_guildowner()
|
||||
async def bankset_bankname(self, ctx: commands.Context, *, name: str):
|
||||
"""Set the bank's name"""
|
||||
await bank.set_bank_name(name, ctx.guild)
|
||||
await ctx.send(_("Bank's name has been set to {}").format(name))
|
||||
|
||||
@bankset.command(name="creditsname")
|
||||
@check_global_setting_guildowner()
|
||||
async def bankset_creditsname(self, ctx: commands.Context, *, name: str):
|
||||
"""Set the name for the bank's currency"""
|
||||
await bank.set_currency_name(name, ctx.guild)
|
||||
await ctx.send(_("Currency name has been set to {}").format(name))
|
||||
|
||||
# ENDSECTION
|
||||
37
redbot/cogs/bank/errors.py
Normal file
37
redbot/cogs/bank/errors.py
Normal file
@@ -0,0 +1,37 @@
|
||||
class BankError(Exception):
|
||||
pass
|
||||
|
||||
class BankNotGlobal(BankError):
|
||||
pass
|
||||
|
||||
|
||||
class BankIsGlobal(BankError):
|
||||
pass
|
||||
|
||||
|
||||
class AccountAlreadyExists(BankError):
|
||||
pass
|
||||
|
||||
|
||||
class NoAccount(BankError):
|
||||
pass
|
||||
|
||||
|
||||
class NoSenderAccount(NoAccount):
|
||||
pass
|
||||
|
||||
|
||||
class NoReceiverAccount(NoAccount):
|
||||
pass
|
||||
|
||||
|
||||
class InsufficientBalance(BankError):
|
||||
pass
|
||||
|
||||
|
||||
class NegativeValue(BankError):
|
||||
pass
|
||||
|
||||
|
||||
class SameSenderAndReceiver(BankError):
|
||||
pass
|
||||
37
redbot/cogs/bank/locales/es.po
Normal file
37
redbot/cogs/bank/locales/es.po
Normal file
@@ -0,0 +1,37 @@
|
||||
# Copyright (C) 2017 Red-DiscordBot
|
||||
# UltimatePancake <pier.gaetani@gmail.com>, 2017.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: 2017-08-26 17:32+EDT\n"
|
||||
"PO-Revision-Date: 2017-08-26 20:35-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"
|
||||
|
||||
#: ../bank.py:68
|
||||
msgid "global"
|
||||
msgstr "global"
|
||||
|
||||
#: ../bank.py:68
|
||||
msgid "per-guild"
|
||||
msgstr "por gremio"
|
||||
|
||||
#: ../bank.py:70
|
||||
msgid "The bank is now {}."
|
||||
msgstr "El banco es ahora {}."
|
||||
|
||||
#: ../bank.py:77
|
||||
msgid "Bank's name has been set to {}"
|
||||
msgstr "Nombre del banco es ahora {}"
|
||||
|
||||
#: ../bank.py:84
|
||||
msgid "Currency name has been set to {}"
|
||||
msgstr "Nombre de la moneda es ahora {}"
|
||||
37
redbot/cogs/bank/locales/messages.pot
Normal file
37
redbot/cogs/bank/locales/messages.pot
Normal file
@@ -0,0 +1,37 @@
|
||||
# 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:32+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"
|
||||
|
||||
|
||||
#: ../bank.py:68
|
||||
msgid "global"
|
||||
msgstr ""
|
||||
|
||||
#: ../bank.py:68
|
||||
msgid "per-guild"
|
||||
msgstr ""
|
||||
|
||||
#: ../bank.py:70
|
||||
msgid "The bank is now {}."
|
||||
msgstr ""
|
||||
|
||||
#: ../bank.py:77
|
||||
msgid "Bank's name has been set to {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../bank.py:84
|
||||
msgid "Currency name has been set to {}"
|
||||
msgstr ""
|
||||
|
||||
6
redbot/cogs/downloader/__init__.py
Normal file
6
redbot/cogs/downloader/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from redbot.core.bot import Red
|
||||
from .downloader import Downloader
|
||||
|
||||
|
||||
def setup(bot: Red):
|
||||
bot.add_cog(Downloader(bot))
|
||||
45
redbot/cogs/downloader/checks.py
Normal file
45
redbot/cogs/downloader/checks.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import asyncio
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
__all__ = ["install_agreement", ]
|
||||
|
||||
REPO_INSTALL_MSG = (
|
||||
"You're about to add a 3rd party repository. The creator of Red"
|
||||
" and its community have no responsibility for any potential "
|
||||
"damage that the content of 3rd party repositories might cause."
|
||||
"\n\nBy typing '**I agree**' you declare that you have read and"
|
||||
" fully understand the above message. This message won't be "
|
||||
"shown again until the next reboot.\n\nYou have **30** seconds"
|
||||
" to reply to this message."
|
||||
)
|
||||
|
||||
|
||||
def install_agreement():
|
||||
async def pred(ctx: commands.Context):
|
||||
downloader = ctx.command.instance
|
||||
if downloader is None:
|
||||
return True
|
||||
elif downloader.already_agreed:
|
||||
return True
|
||||
elif ctx.invoked_subcommand is None or \
|
||||
isinstance(ctx.invoked_subcommand, commands.Group):
|
||||
return True
|
||||
|
||||
def does_agree(msg: discord.Message):
|
||||
return ctx.author == msg.author and \
|
||||
ctx.channel == msg.channel and \
|
||||
msg.content == "I agree"
|
||||
|
||||
await ctx.send(REPO_INSTALL_MSG)
|
||||
|
||||
try:
|
||||
await ctx.bot.wait_for('message', check=does_agree, timeout=30)
|
||||
except asyncio.TimeoutError:
|
||||
await ctx.send("Your response has timed out, please try again.")
|
||||
return False
|
||||
|
||||
downloader.already_agreed = True
|
||||
return True
|
||||
return commands.check(pred)
|
||||
24
redbot/cogs/downloader/converters.py
Normal file
24
redbot/cogs/downloader/converters.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from .repo_manager import RepoManager
|
||||
from .installable import Installable
|
||||
|
||||
|
||||
class RepoName(commands.Converter):
|
||||
async def convert(self, ctx: commands.Context, arg: str) -> str:
|
||||
return RepoManager.validate_and_normalize_repo_name(arg)
|
||||
|
||||
|
||||
class InstalledCog(commands.Converter):
|
||||
async def convert(self, ctx: commands.Context, arg: str) -> Installable:
|
||||
downloader = ctx.bot.get_cog("Downloader")
|
||||
if downloader is None:
|
||||
raise commands.CommandError("Downloader not loaded.")
|
||||
|
||||
cog = discord.utils.get(downloader.installed_cogs, name=arg)
|
||||
if cog is None:
|
||||
raise commands.BadArgument(
|
||||
"That cog is not installed"
|
||||
)
|
||||
|
||||
return cog
|
||||
394
redbot/cogs/downloader/downloader.py
Normal file
394
redbot/cogs/downloader/downloader.py
Normal file
@@ -0,0 +1,394 @@
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from sys import path as syspath
|
||||
from typing import Tuple, Union
|
||||
|
||||
import discord
|
||||
from redbot.core import Config
|
||||
from redbot.core import checks
|
||||
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 .checks import install_agreement
|
||||
from .converters import RepoName, InstalledCog
|
||||
from .errors import CloningError, ExistingGitRepo
|
||||
from .installable import Installable
|
||||
from .log import log
|
||||
from .repo_manager import RepoManager, Repo
|
||||
|
||||
_ = CogI18n('Downloader', __file__)
|
||||
|
||||
|
||||
class Downloader:
|
||||
def __init__(self, bot: Red):
|
||||
self.bot = bot
|
||||
|
||||
self.conf = Config.get_conf(self, identifier=998240343,
|
||||
force_registration=True)
|
||||
|
||||
self.conf.register_global(
|
||||
repos={},
|
||||
installed=[]
|
||||
)
|
||||
|
||||
self.already_agreed = False
|
||||
|
||||
self.LIB_PATH = self.bot.main_dir / "lib"
|
||||
self.SHAREDLIB_PATH = self.LIB_PATH / "cog_shared"
|
||||
self.SHAREDLIB_INIT = self.SHAREDLIB_PATH / "__init__.py"
|
||||
|
||||
self.LIB_PATH.mkdir(parents=True, exist_ok=True)
|
||||
self.SHAREDLIB_PATH.mkdir(parents=True, exist_ok=True)
|
||||
if not self.SHAREDLIB_INIT.exists():
|
||||
with self.SHAREDLIB_INIT.open(mode='w') as _:
|
||||
pass
|
||||
|
||||
if str(self.LIB_PATH) not in syspath:
|
||||
syspath.insert(1, str(self.LIB_PATH))
|
||||
|
||||
self._repo_manager = RepoManager(self.conf)
|
||||
|
||||
async def cog_install_path(self):
|
||||
"""
|
||||
Returns the current cog install path.
|
||||
:return:
|
||||
"""
|
||||
return await self.bot.cog_mgr.install_path()
|
||||
|
||||
async def installed_cogs(self) -> Tuple[Installable]:
|
||||
"""
|
||||
Returns the dictionary mapping cog name to install location
|
||||
and repo name.
|
||||
:return:
|
||||
"""
|
||||
installed = await self.conf.installed()
|
||||
# noinspection PyTypeChecker
|
||||
return tuple(Installable.from_json(v) for v in installed)
|
||||
|
||||
async def _add_to_installed(self, cog: Installable):
|
||||
"""
|
||||
Marks a cog as installed.
|
||||
:param cog:
|
||||
:return:
|
||||
"""
|
||||
installed = await self.conf.installed()
|
||||
cog_json = cog.to_json()
|
||||
|
||||
if cog_json not in installed:
|
||||
installed.append(cog_json)
|
||||
await self.conf.installed.set(installed)
|
||||
|
||||
async def _remove_from_installed(self, cog: Installable):
|
||||
"""
|
||||
Removes a cog from the saved list of installed cogs.
|
||||
:param cog:
|
||||
:return:
|
||||
"""
|
||||
installed = await self.conf.installed()
|
||||
cog_json = cog.to_json()
|
||||
|
||||
if cog_json in installed:
|
||||
installed.remove(cog_json)
|
||||
await self.conf.installed.set(installed)
|
||||
|
||||
async def _reinstall_cogs(self, cogs: Tuple[Installable]) -> Tuple[Installable]:
|
||||
"""
|
||||
Installs a list of cogs, used when updating.
|
||||
:param cogs:
|
||||
:return: Any cogs that failed to copy
|
||||
"""
|
||||
failed = []
|
||||
for cog in cogs:
|
||||
if not await cog.copy_to(await self.cog_install_path()):
|
||||
failed.append(cog)
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
return tuple(failed)
|
||||
|
||||
async def _reinstall_libraries(self, cogs: Tuple[Installable]) -> Tuple[Installable]:
|
||||
"""
|
||||
Reinstalls any shared libraries from the repos of cogs that
|
||||
were updated.
|
||||
:param cogs:
|
||||
:return: Any libraries that failed to copy
|
||||
"""
|
||||
repo_names = set(cog.repo_name for cog in cogs)
|
||||
unfiltered_repos = (self._repo_manager.get_repo(r) for r in repo_names)
|
||||
repos = filter(lambda r: r is not None, unfiltered_repos)
|
||||
|
||||
failed = []
|
||||
|
||||
for repo in repos:
|
||||
if not await repo.install_libraries(target_dir=self.SHAREDLIB_PATH):
|
||||
failed.extend(repo.available_libraries)
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
return tuple(failed)
|
||||
|
||||
async def _reinstall_requirements(self, cogs: Tuple[Installable]) -> bool:
|
||||
"""
|
||||
Reinstalls requirements for given cogs that have been updated.
|
||||
Returns a bool that indicates if all requirement installations
|
||||
were successful.
|
||||
:param cogs:
|
||||
:return:
|
||||
"""
|
||||
|
||||
# Reduces requirements to a single list with no repeats
|
||||
requirements = set(r for c in cogs for r in c.requirements)
|
||||
repo_names = self._repo_manager.get_all_repo_names()
|
||||
repos = [(self._repo_manager.get_repo(rn), []) for rn in repo_names]
|
||||
|
||||
# This for loop distributes the requirements across all repos
|
||||
# which will allow us to concurrently install requirements
|
||||
for i, req in enumerate(requirements):
|
||||
repo_index = i % len(repos)
|
||||
repos[repo_index][1].append(req)
|
||||
|
||||
has_reqs = list(filter(lambda item: len(item[1]) > 0, repos))
|
||||
|
||||
ret = True
|
||||
for repo, reqs in has_reqs:
|
||||
for req in reqs:
|
||||
# noinspection PyTypeChecker
|
||||
ret = ret and await repo.install_raw_requirements([req, ], self.LIB_PATH)
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
async def _delete_cog(target: Path):
|
||||
"""
|
||||
Removes an (installed) cog.
|
||||
:param target: Path pointing to an existing file or directory
|
||||
:return:
|
||||
"""
|
||||
if not target.exists():
|
||||
return
|
||||
|
||||
if target.is_dir():
|
||||
shutil.rmtree(str(target))
|
||||
elif target.is_file():
|
||||
os.remove(str(target))
|
||||
|
||||
@commands.group()
|
||||
@checks.is_owner()
|
||||
async def repo(self, ctx):
|
||||
"""
|
||||
Command group for managing Downloader repos.
|
||||
"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await self.bot.send_cmd_help(ctx)
|
||||
|
||||
@repo.command(name="add")
|
||||
@install_agreement()
|
||||
async def _repo_add(self, ctx, name: RepoName, repo_url: str, branch: str=None):
|
||||
"""
|
||||
Add a new repo to Downloader.
|
||||
|
||||
Name can only contain characters A-z, numbers and underscore
|
||||
Branch will default to master if not specified
|
||||
"""
|
||||
try:
|
||||
# noinspection PyTypeChecker
|
||||
await self._repo_manager.add_repo(
|
||||
name=name,
|
||||
url=repo_url,
|
||||
branch=branch
|
||||
)
|
||||
except ExistingGitRepo:
|
||||
await ctx.send(_("That git repo has already been added under another name."))
|
||||
except CloningError:
|
||||
await ctx.send(_("Something went wrong during the cloning process."))
|
||||
log.exception(_("Something went wrong during the cloning process."))
|
||||
else:
|
||||
await ctx.send(_("Repo `{}` successfully added.").format(name))
|
||||
|
||||
@repo.command(name="delete")
|
||||
async def _repo_del(self, ctx, repo_name: Repo):
|
||||
"""
|
||||
Removes a repo from Downloader and its' files.
|
||||
"""
|
||||
await self._repo_manager.delete_repo(repo_name.name)
|
||||
|
||||
await ctx.send(_("The repo `{}` has been deleted successfully.").format(repo_name.name))
|
||||
|
||||
@repo.command(name="list")
|
||||
async def _repo_list(self, ctx):
|
||||
"""
|
||||
Lists all installed repos.
|
||||
"""
|
||||
repos = self._repo_manager.get_all_repo_names()
|
||||
joined = _("Installed Repos:\n") + "\n".join(["+ " + r for r in repos])
|
||||
|
||||
await ctx.send(box(joined, lang="diff"))
|
||||
|
||||
@commands.group()
|
||||
@checks.is_owner()
|
||||
async def cog(self, ctx):
|
||||
"""
|
||||
Command group for managing installable Cogs.
|
||||
"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await self.bot.send_cmd_help(ctx)
|
||||
|
||||
@cog.command(name="install")
|
||||
async def _cog_install(self, ctx, repo_name: Repo, cog_name: str):
|
||||
"""
|
||||
Installs a cog from the given repo.
|
||||
"""
|
||||
cog = discord.utils.get(repo_name.available_cogs, name=cog_name)
|
||||
if cog is None:
|
||||
await ctx.send(_("Error, there is no cog by the name of"
|
||||
" `{}` in the `{}` repo.").format(cog_name, repo_name.name))
|
||||
return
|
||||
|
||||
if not await repo_name.install_requirements(cog, self.LIB_PATH):
|
||||
await ctx.send(_("Failed to install the required libraries for"
|
||||
" `{}`: `{}`").format(cog.name, cog.requirements))
|
||||
return
|
||||
|
||||
await repo_name.install_cog(cog, await self.cog_install_path())
|
||||
|
||||
await self._add_to_installed(cog)
|
||||
|
||||
await repo_name.install_libraries(self.SHAREDLIB_PATH)
|
||||
|
||||
await ctx.send(_("`{}` cog successfully installed.").format(cog_name))
|
||||
|
||||
@cog.command(name="uninstall")
|
||||
async def _cog_uninstall(self, ctx, cog_name: InstalledCog):
|
||||
"""
|
||||
Allows you to uninstall cogs that were previously installed
|
||||
through Downloader.
|
||||
"""
|
||||
# noinspection PyUnresolvedReferences,PyProtectedMember
|
||||
real_name = cog_name.name
|
||||
|
||||
poss_installed_path = (await self.cog_install_path()) / real_name
|
||||
if poss_installed_path.exists():
|
||||
await self._delete_cog(poss_installed_path)
|
||||
# noinspection PyTypeChecker
|
||||
await self._remove_from_installed(cog_name)
|
||||
await ctx.send(_("`{}` was successfully removed.").format(real_name))
|
||||
else:
|
||||
await ctx.send(_("That cog was installed but can no longer"
|
||||
" be located. You may need to remove it's"
|
||||
" files manually if it is still usable."))
|
||||
|
||||
@cog.command(name="update")
|
||||
async def _cog_update(self, ctx, cog_name: InstalledCog=None):
|
||||
"""
|
||||
Updates all cogs or one of your choosing.
|
||||
"""
|
||||
if cog_name is None:
|
||||
updated = await self._repo_manager.update_all_repos()
|
||||
installed_cogs = set(await self.installed_cogs())
|
||||
updated_cogs = set(cog for repo in updated.keys() for cog in repo.available_cogs)
|
||||
|
||||
installed_and_updated = updated_cogs & installed_cogs
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
await self._reinstall_requirements(installed_and_updated)
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
await self._reinstall_cogs(installed_and_updated)
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
await self._reinstall_libraries(installed_and_updated)
|
||||
await ctx.send(_("Cog update completed successfully."))
|
||||
|
||||
@cog.command(name="list")
|
||||
async def _cog_list(self, ctx, repo_name: Repo):
|
||||
"""
|
||||
Lists all available cogs from a single repo.
|
||||
"""
|
||||
cogs = repo_name.available_cogs
|
||||
cogs = _("Available Cogs:\n") + "\n".join(
|
||||
["+ {}: {}".format(c.name, c.short or "") for c in cogs])
|
||||
|
||||
await ctx.send(box(cogs, lang="diff"))
|
||||
|
||||
@cog.command(name="info")
|
||||
async def _cog_info(self, ctx, repo_name: Repo, cog_name: str):
|
||||
"""
|
||||
Lists information about a single cog.
|
||||
"""
|
||||
cog = discord.utils.get(repo_name.available_cogs, name=cog_name)
|
||||
if cog is None:
|
||||
await ctx.send(_("There is no cog `{}` in the repo `{}`").format(
|
||||
cog_name, repo_name.name
|
||||
))
|
||||
return
|
||||
|
||||
msg = _("Information on {}:\n{}").format(cog.name, cog.description or "")
|
||||
await ctx.send(box(msg))
|
||||
|
||||
async def is_installed(self, cog_name: str) -> (bool, Union[Installable, None]):
|
||||
"""
|
||||
Checks to see if a cog with the given name was installed
|
||||
through Downloader.
|
||||
:param cog_name:
|
||||
:return: is_installed, Installable
|
||||
"""
|
||||
for installable in await self.installed_cogs():
|
||||
if installable.name == cog_name:
|
||||
return True, installable
|
||||
return False, None
|
||||
|
||||
def format_findcog_info(self, command_name: str,
|
||||
cog_installable: Union[Installable, object]=None) -> str:
|
||||
"""
|
||||
Formats the info for output to discord
|
||||
:param command_name:
|
||||
:param cog_installable: Can be an Installable instance or a Cog instance.
|
||||
:return: str
|
||||
"""
|
||||
if isinstance(cog_installable, Installable):
|
||||
made_by = ", ".join(cog_installable.author) or _("Missing from info.json")
|
||||
repo = self._repo_manager.get_repo(cog_installable.repo_name)
|
||||
repo_url = repo.url
|
||||
cog_name = cog_installable.name
|
||||
else:
|
||||
made_by = "26 & co."
|
||||
repo_url = "https://github.com/Twentysix26/Red-DiscordBot"
|
||||
cog_name = cog_installable.__class__.__name__
|
||||
|
||||
msg = _("Command: {}\nMade by: {}\nRepo: {}\nCog name: {}")
|
||||
|
||||
return msg.format(command_name, made_by, repo_url, cog_name)
|
||||
|
||||
def cog_name_from_instance(self, instance: object) -> str:
|
||||
"""
|
||||
Determines the cog name that Downloader knows from the cog instance.
|
||||
|
||||
Probably.
|
||||
:param instance:
|
||||
:return:
|
||||
"""
|
||||
splitted = instance.__module__.split('.')
|
||||
return splitted[-2]
|
||||
|
||||
@commands.command()
|
||||
async def findcog(self, ctx: commands.Context, command_name: str):
|
||||
"""
|
||||
Figures out which cog a command comes from. Only works with loaded
|
||||
cogs.
|
||||
"""
|
||||
command = ctx.bot.all_commands.get(command_name)
|
||||
|
||||
if command is None:
|
||||
await ctx.send(_("That command doesn't seem to exist."))
|
||||
return
|
||||
|
||||
# Check if in installed cogs
|
||||
cog_name = self.cog_name_from_instance(command.instance)
|
||||
installed, cog_installable = await self.is_installed(cog_name)
|
||||
if installed:
|
||||
msg = self.format_findcog_info(command_name, cog_installable)
|
||||
else:
|
||||
# Assume it's in a base cog
|
||||
msg = self.format_findcog_info(command_name, command.instance)
|
||||
|
||||
await ctx.send(box(msg))
|
||||
84
redbot/cogs/downloader/errors.py
Normal file
84
redbot/cogs/downloader/errors.py
Normal file
@@ -0,0 +1,84 @@
|
||||
__all__ = ["DownloaderException", "GitException", "InvalidRepoName", "ExistingGitRepo",
|
||||
"MissingGitRepo", "CloningError", "CurrentHashError", "HardResetError",
|
||||
"UpdateError", "GitDiffError", "PipError"]
|
||||
|
||||
|
||||
class DownloaderException(Exception):
|
||||
"""
|
||||
Base class for Downloader exceptions.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class GitException(DownloaderException):
|
||||
"""
|
||||
Generic class for git exceptions.
|
||||
"""
|
||||
|
||||
|
||||
class InvalidRepoName(DownloaderException):
|
||||
"""
|
||||
Throw when a repo name is invalid. Check
|
||||
the message for a more detailed reason.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ExistingGitRepo(DownloaderException):
|
||||
"""
|
||||
Thrown when trying to clone into a folder where a
|
||||
git repo already exists.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class MissingGitRepo(DownloaderException):
|
||||
"""
|
||||
Thrown when a git repo is expected to exist but
|
||||
does not.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class CloningError(GitException):
|
||||
"""
|
||||
Thrown when git clone returns a non zero exit code.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class CurrentHashError(GitException):
|
||||
"""
|
||||
Thrown when git returns a non zero exit code attempting
|
||||
to determine the current commit hash.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class HardResetError(GitException):
|
||||
"""
|
||||
Thrown when there is an issue trying to execute a hard reset
|
||||
(usually prior to a repo update).
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class UpdateError(GitException):
|
||||
"""
|
||||
Thrown when git pull returns a non zero error code.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class GitDiffError(GitException):
|
||||
"""
|
||||
Thrown when a git diff fails.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class PipError(DownloaderException):
|
||||
"""
|
||||
Thrown when pip returns a non-zero return code.
|
||||
"""
|
||||
pass
|
||||
170
redbot/cogs/downloader/installable.py
Normal file
170
redbot/cogs/downloader/installable.py
Normal file
@@ -0,0 +1,170 @@
|
||||
import json
|
||||
import distutils.dir_util
|
||||
import shutil
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Union, MutableMapping, Any
|
||||
|
||||
from .log import log
|
||||
from .json_mixins import RepoJSONMixin
|
||||
|
||||
|
||||
class InstallableType(Enum):
|
||||
UNKNOWN = 0
|
||||
COG = 1
|
||||
SHARED_LIBRARY = 2
|
||||
|
||||
|
||||
class Installable(RepoJSONMixin):
|
||||
"""
|
||||
Base class for anything the Downloader cog can install.
|
||||
- Modules
|
||||
- Repo Libraries
|
||||
- Other stuff?
|
||||
"""
|
||||
|
||||
INFO_FILE_DESCRIPTION = """
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, location: Path):
|
||||
"""
|
||||
Base installable initializer.
|
||||
|
||||
:param location: Location (file or folder) to the installable.
|
||||
"""
|
||||
super().__init__(location)
|
||||
|
||||
self._location = location
|
||||
|
||||
self.repo_name = self._location.parent.stem
|
||||
|
||||
self.author = ()
|
||||
self.bot_version = (3, 0, 0)
|
||||
self.hidden = False
|
||||
self.required_cogs = {} # Cog name -> repo URL
|
||||
self.requirements = ()
|
||||
self.tags = ()
|
||||
self.type = InstallableType.UNKNOWN
|
||||
|
||||
if self._info_file.exists():
|
||||
self._process_info_file(self._info_file)
|
||||
|
||||
if self._info == {}:
|
||||
self.type = InstallableType.COG
|
||||
|
||||
def __eq__(self, other):
|
||||
# noinspection PyProtectedMember
|
||||
return self._location == other._location
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._location)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._location.stem
|
||||
|
||||
async def copy_to(self, target_dir: Path) -> bool:
|
||||
"""
|
||||
Copies this cog/shared_lib to the given directory. This
|
||||
will overwrite any files in the target directory.
|
||||
|
||||
:param pathlib.Path target_dir: The installation directory to install to.
|
||||
:return: Status of installation
|
||||
:rtype: bool
|
||||
"""
|
||||
if self._location.is_file():
|
||||
copy_func = shutil.copy2
|
||||
else:
|
||||
copy_func = distutils.dir_util.copy_tree
|
||||
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
copy_func(
|
||||
src=str(self._location),
|
||||
dst=str(target_dir / self._location.stem)
|
||||
)
|
||||
except:
|
||||
log.exception("Error occurred when copying path:"
|
||||
" {}".format(self._location))
|
||||
return False
|
||||
return True
|
||||
|
||||
def _read_info_file(self):
|
||||
super()._read_info_file()
|
||||
|
||||
if self._info_file.exists():
|
||||
self._process_info_file()
|
||||
|
||||
def _process_info_file(self, info_file_path: Path=None) -> MutableMapping[str, Any]:
|
||||
"""
|
||||
Processes an information file. Loads dependencies among other
|
||||
information into this object.
|
||||
|
||||
:type info_file_path:
|
||||
:param info_file_path: Optional path to information file, defaults to `self.__info_file`
|
||||
:return: Raw information dictionary
|
||||
"""
|
||||
info_file_path = info_file_path or self._info_file
|
||||
if info_file_path is None or not info_file_path.is_file():
|
||||
raise ValueError("No valid information file path was found.")
|
||||
|
||||
info = {}
|
||||
with info_file_path.open(encoding='utf-8') as f:
|
||||
try:
|
||||
info = json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
info = {}
|
||||
log.exception("Invalid JSON information file at path:"
|
||||
" {}".format(info_file_path))
|
||||
else:
|
||||
self._info = info
|
||||
|
||||
try:
|
||||
author = tuple(info.get("author", []))
|
||||
except ValueError:
|
||||
author = ()
|
||||
self.author = author
|
||||
|
||||
try:
|
||||
bot_version = tuple(info.get("bot_version", [3, 0, 0]))
|
||||
except ValueError:
|
||||
bot_version = 2
|
||||
self.bot_version = bot_version
|
||||
|
||||
try:
|
||||
hidden = bool(info.get("hidden", False))
|
||||
except ValueError:
|
||||
hidden = False
|
||||
self.hidden = hidden
|
||||
|
||||
self.required_cogs = info.get("required_cogs", {})
|
||||
|
||||
self.requirements = info.get("requirements", ())
|
||||
|
||||
try:
|
||||
tags = tuple(info.get("tags", ()))
|
||||
except ValueError:
|
||||
tags = ()
|
||||
self.tags = tags
|
||||
|
||||
installable_type = info.get("type", "")
|
||||
if installable_type in ("", "COG"):
|
||||
self.type = InstallableType.COG
|
||||
elif installable_type == "SHARED_LIBRARY":
|
||||
self.type = InstallableType.SHARED_LIBRARY
|
||||
self.hidden = True
|
||||
else:
|
||||
self.type = InstallableType.UNKNOWN
|
||||
|
||||
return info
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
"location": self._location.relative_to(Path.cwd()).parts
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, data: dict):
|
||||
location = Path.cwd() / Path(*data["location"])
|
||||
return cls(location=location)
|
||||
37
redbot/cogs/downloader/json_mixins.py
Normal file
37
redbot/cogs/downloader/json_mixins.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class RepoJSONMixin:
|
||||
INFO_FILE_NAME = "info.json"
|
||||
|
||||
def __init__(self, repo_folder: Path):
|
||||
self._repo_folder = repo_folder
|
||||
|
||||
self.author = None
|
||||
self.install_msg = None
|
||||
self.short = None
|
||||
self.description = None
|
||||
|
||||
self._info_file = repo_folder / self.INFO_FILE_NAME
|
||||
if self._info_file.exists():
|
||||
self._read_info_file()
|
||||
|
||||
self._info = {}
|
||||
|
||||
def _read_info_file(self):
|
||||
if not (self._info_file.exists() or self._info_file.is_file()):
|
||||
return
|
||||
|
||||
try:
|
||||
with self._info_file.open(encoding='utf-8') as f:
|
||||
info = json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
return
|
||||
else:
|
||||
self._info = info
|
||||
|
||||
self.author = info.get("author")
|
||||
self.install_msg = info.get("install_msg")
|
||||
self.short = info.get("short")
|
||||
self.description = info.get("description")
|
||||
97
redbot/cogs/downloader/locales/de.po
Normal file
97
redbot/cogs/downloader/locales/de.po
Normal file
@@ -0,0 +1,97 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: 2017-08-26 16:31+EDT\n"
|
||||
"PO-Revision-Date: 2017-08-26 17:00-0400\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"
|
||||
"Last-Translator: tekulvw\n"
|
||||
"Language-Team: \n"
|
||||
"Language: de\n"
|
||||
"X-Generator: Poedit 1.8.7.1\n"
|
||||
|
||||
#: ../downloader.py:202
|
||||
msgid "That git repo has already been added under another name."
|
||||
msgstr "Diese git repo wurder bereist unter einem anderem Namen hinzugefügt."
|
||||
|
||||
#: ../downloader.py:204 ../downloader.py:205
|
||||
msgid "Something went wrong during the cloning process."
|
||||
msgstr "Etwas ist beim klonen schief gelaufen."
|
||||
|
||||
#: ../downloader.py:207
|
||||
msgid "Repo `{}` successfully added."
|
||||
msgstr "Repo `{}` erfolgreich hinzugefügt."
|
||||
|
||||
#: ../downloader.py:216
|
||||
msgid "The repo `{}` has been deleted successfully."
|
||||
msgstr "Die Repo `{}` wurde erfolgreich gelöscht."
|
||||
|
||||
#: ../downloader.py:224
|
||||
msgid "Installed Repos:\n"
|
||||
msgstr "Installierte Repos:\n"
|
||||
|
||||
#: ../downloader.py:244
|
||||
msgid "Error, there is no cog by the name of `{}` in the `{}` repo."
|
||||
msgstr "Fehler: kein Cog mit dem Namen `{}` in der Repo `{}`."
|
||||
|
||||
#: ../downloader.py:249
|
||||
msgid "Failed to install the required libraries for `{}`: `{}`"
|
||||
msgstr "Installation erforderliche Abhängigkeiten für`{}` fehlgeschlagen: `{}`"
|
||||
|
||||
#: ../downloader.py:259
|
||||
msgid "`{}` cog successfully installed."
|
||||
msgstr "`{}` Cog erfolgreich installiert."
|
||||
|
||||
#: ../downloader.py:275
|
||||
msgid "`{}` was successfully removed."
|
||||
msgstr "`{}` erfolgreich entfernt."
|
||||
|
||||
#: ../downloader.py:277
|
||||
msgid "That cog was installed but can no longer be located. You may need to remove it's files manually if it is still usable."
|
||||
msgstr "Diese Cog ist installiert konnte aber nicht gefunden werden. Wenn es noch benutzbar ist kann es sein das du die Dateien manuell löschen musst."
|
||||
|
||||
#: ../downloader.py:301
|
||||
msgid "Cog update completed successfully."
|
||||
msgstr "Cog Update erfolgreich."
|
||||
|
||||
#: ../downloader.py:309
|
||||
msgid "Available Cogs:\n"
|
||||
msgstr "Vorhandene Cogs:\n"
|
||||
|
||||
#: ../downloader.py:321
|
||||
msgid "There is no cog `{}` in the repo `{}`"
|
||||
msgstr "Kein Cog namens `{}` in der Repo `{}"
|
||||
|
||||
#: ../downloader.py:326
|
||||
msgid ""
|
||||
"Information on {}:\n"
|
||||
"{}"
|
||||
msgstr ""
|
||||
"Information zu {}:\n"
|
||||
"{}"
|
||||
|
||||
#: ../downloader.py:350
|
||||
msgid "Missing from info.json"
|
||||
msgstr "Nicht in info.json"
|
||||
|
||||
#: ../downloader.py:359
|
||||
msgid ""
|
||||
"Command: {}\n"
|
||||
"Made by: {}\n"
|
||||
"Repo: {}\n"
|
||||
"Cog name: {}"
|
||||
msgstr ""
|
||||
"Befehl: {}\n"
|
||||
"Von: {}\n"
|
||||
"Repo: {}\n"
|
||||
"Cog Name: {}"
|
||||
|
||||
#: ../downloader.py:383
|
||||
msgid "That command doesn't seem to exist."
|
||||
msgstr "Dieser Befehl existiert nicht."
|
||||
96
redbot/cogs/downloader/locales/es.po
Normal file
96
redbot/cogs/downloader/locales/es.po
Normal file
@@ -0,0 +1,96 @@
|
||||
# Copyright (C) 2017 Red-DiscordBot
|
||||
# UltimatePancake <pier.gaetani@gmail.com>, 2017.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: 2017-08-26 16:31+EDT\n"
|
||||
"PO-Revision-Date: 2017-08-26 20:44-0600\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"
|
||||
"Last-Translator: UltimatePancake <pier.gaetani@gmail.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: es\n"
|
||||
"X-Generator: Poedit 2.0.3\n"
|
||||
|
||||
#: ../downloader.py:202
|
||||
msgid "That git repo has already been added under another name."
|
||||
msgstr "Ese repositorio ya ha sido agregado con otro nombre."
|
||||
|
||||
#: ../downloader.py:204 ../downloader.py:205
|
||||
msgid "Something went wrong during the cloning process."
|
||||
msgstr "Error durante la clonación."
|
||||
|
||||
#: ../downloader.py:207
|
||||
msgid "Repo `{}` successfully added."
|
||||
msgstr "Repositorio `{}` agregado exitósamente."
|
||||
|
||||
#: ../downloader.py:216
|
||||
msgid "The repo `{}` has been deleted successfully."
|
||||
msgstr "Repositorio `{}` eliminado exitósamente."
|
||||
|
||||
#: ../downloader.py:224
|
||||
msgid "Installed Repos:\n"
|
||||
msgstr "Repositorios instalados:\n"
|
||||
|
||||
#: ../downloader.py:244
|
||||
msgid "Error, there is no cog by the name of `{}` in the `{}` repo."
|
||||
msgstr "Error: No existe un cog llamado `{}` en el repositorio `{}`."
|
||||
|
||||
#: ../downloader.py:249
|
||||
msgid "Failed to install the required libraries for `{}`: `{}`"
|
||||
msgstr "Error instalando las librerías requeridas para `{}`: `{}`"
|
||||
|
||||
#: ../downloader.py:259
|
||||
msgid "`{}` cog successfully installed."
|
||||
msgstr "`{}` instalado exitósamente."
|
||||
|
||||
#: ../downloader.py:275
|
||||
msgid "`{}` was successfully removed."
|
||||
msgstr "`{}` eliminado exitósamente."
|
||||
|
||||
#: ../downloader.py:277
|
||||
msgid "That cog was installed but can no longer be located. You may need to remove it's files manually if it is still usable."
|
||||
msgstr "El cog fue instalado pero ya no se puede localizar. Puede ser necesario eliminar sus archivos manualmente si aun es utilizable."
|
||||
|
||||
#: ../downloader.py:301
|
||||
msgid "Cog update completed successfully."
|
||||
msgstr "Cog actualizado exitósamente."
|
||||
|
||||
#: ../downloader.py:309
|
||||
msgid "Available Cogs:\n"
|
||||
msgstr "Cogs disponibles:\n"
|
||||
|
||||
#: ../downloader.py:321
|
||||
msgid "There is no cog `{}` in the repo `{}`"
|
||||
msgstr "No existe un cog `{}` en el repositorio `{}`"
|
||||
|
||||
#: ../downloader.py:326
|
||||
msgid ""
|
||||
"Information on {}:\n"
|
||||
"{}"
|
||||
msgstr ""
|
||||
"Información sobre {}:\n"
|
||||
"{}"
|
||||
|
||||
#: ../downloader.py:350
|
||||
msgid "Missing from info.json"
|
||||
msgstr "Ausente de info.json"
|
||||
|
||||
#: ../downloader.py:359
|
||||
msgid ""
|
||||
"Command: {}\n"
|
||||
"Made by: {}\n"
|
||||
"Repo: {}\n"
|
||||
"Cog name: {}"
|
||||
msgstr ""
|
||||
"Comando: {}\n"
|
||||
"Creado por: {}\n"
|
||||
"Repositorio: {}\n"
|
||||
"Nombre del cog: {}"
|
||||
|
||||
#: ../downloader.py:383
|
||||
msgid "That command doesn't seem to exist."
|
||||
msgstr "Ese comando no parece existir."
|
||||
97
redbot/cogs/downloader/locales/fr.po
Normal file
97
redbot/cogs/downloader/locales/fr.po
Normal file
@@ -0,0 +1,97 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: 2017-08-26 16:31+EDT\n"
|
||||
"PO-Revision-Date: 2017-08-26 23:14+0200\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"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: fr\n"
|
||||
"X-Generator: Poedit 2.0.1\n"
|
||||
|
||||
#: ../downloader.py:202
|
||||
msgid "That git repo has already been added under another name."
|
||||
msgstr "Ce repo git a déjà été ajouté sous un autre nom"
|
||||
|
||||
#: ../downloader.py:204 ../downloader.py:205
|
||||
msgid "Something went wrong during the cloning process."
|
||||
msgstr "Quelque chose s'est mal passé pendant l'installation."
|
||||
|
||||
#: ../downloader.py:207
|
||||
msgid "Repo `{}` successfully added."
|
||||
msgstr "Le repo `{}` a été ajouté avec succès"
|
||||
|
||||
#: ../downloader.py:216
|
||||
msgid "The repo `{}` has been deleted successfully."
|
||||
msgstr "Le repo `{}` a été supprimé avec succès"
|
||||
|
||||
#: ../downloader.py:224
|
||||
msgid "Installed Repos:\n"
|
||||
msgstr "Repos installés:\n"
|
||||
|
||||
#: ../downloader.py:244
|
||||
msgid "Error, there is no cog by the name of `{}` in the `{}` repo."
|
||||
msgstr "Erreur, il n'y a pas de cog du nom de `{}` dans le repo `{}`."
|
||||
|
||||
#: ../downloader.py:249
|
||||
msgid "Failed to install the required libraries for `{}`: `{}`"
|
||||
msgstr "Échec lors de l'installation des bibliothèques de `{}`: `{}`"
|
||||
|
||||
#: ../downloader.py:259
|
||||
msgid "`{}` cog successfully installed."
|
||||
msgstr "Le cog `{}` a été ajouté avec succès"
|
||||
|
||||
#: ../downloader.py:275
|
||||
msgid "`{}` was successfully removed."
|
||||
msgstr "Le cog `{}` a été retiré avec succès"
|
||||
|
||||
#: ../downloader.py:277
|
||||
msgid "That cog was installed but can no longer be located. You may need to remove it's files manually if it is still usable."
|
||||
msgstr "Ce cog a été installé mais ne peut plus être trouvé. Vous devez retirer manuellement son dossier si il est encore utilisable."
|
||||
|
||||
#: ../downloader.py:301
|
||||
msgid "Cog update completed successfully."
|
||||
msgstr "Mise à jour du cog effectuée avec succès"
|
||||
|
||||
#: ../downloader.py:309
|
||||
msgid "Available Cogs:\n"
|
||||
msgstr "Cogs disponibles:\n"
|
||||
|
||||
#: ../downloader.py:321
|
||||
msgid "There is no cog `{}` in the repo `{}`"
|
||||
msgstr "Il n'y a pas de cog `{}` dans le repo `{}`"
|
||||
|
||||
#: ../downloader.py:326
|
||||
msgid ""
|
||||
"Information on {}:\n"
|
||||
"{}"
|
||||
msgstr ""
|
||||
"Informations sur {}:\n"
|
||||
"{}"
|
||||
|
||||
#: ../downloader.py:350
|
||||
msgid "Missing from info.json"
|
||||
msgstr "Informations manquantes de info.json"
|
||||
|
||||
#: ../downloader.py:359
|
||||
msgid ""
|
||||
"Command: {}\n"
|
||||
"Made by: {}\n"
|
||||
"Repo: {}\n"
|
||||
"Cog name: {}"
|
||||
msgstr ""
|
||||
"Commande: {]\n"
|
||||
"Créé par: {}\n"
|
||||
"Repo: {}\n"
|
||||
"Nom du cog: {}"
|
||||
|
||||
#: ../downloader.py:383
|
||||
msgid "That command doesn't seem to exist."
|
||||
msgstr "Cette commande ne semble pas exister"
|
||||
93
redbot/cogs/downloader/locales/it.po
Normal file
93
redbot/cogs/downloader/locales/it.po
Normal file
@@ -0,0 +1,93 @@
|
||||
# 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:05+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"
|
||||
|
||||
|
||||
#: ../downloader.py:202
|
||||
msgid "That git repo has already been added under another name."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:204 ../downloader.py:205
|
||||
msgid "Something went wrong during the cloning process."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:207
|
||||
msgid "Repo `{}` successfully added."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:216
|
||||
msgid "The repo `{}` has been deleted successfully."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:224
|
||||
msgid ""
|
||||
"Installed Repos:\n"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:244
|
||||
msgid "Error, there is no cog by the name of `{}` in the `{}` repo."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:249
|
||||
msgid "Failed to install the required libraries for `{}`: `{}`"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:259
|
||||
msgid "`{}` cog successfully installed."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:275
|
||||
msgid "`{}` was successfully removed."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:277
|
||||
msgid "That cog was installed but can no longer be located. You may need to remove it's files manually if it is still usable."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:301
|
||||
msgid "Cog update completed successfully."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:309
|
||||
msgid ""
|
||||
"Available Cogs:\n"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:321
|
||||
msgid "There is no cog `{}` in the repo `{}`"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:326
|
||||
msgid ""
|
||||
"Information on {}:\n"
|
||||
"{}"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:350
|
||||
msgid "Missing from info.json"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:359
|
||||
msgid ""
|
||||
"Command: {}\n"
|
||||
"Made by: {}\n"
|
||||
"Repo: {}\n"
|
||||
"Cog name: {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:383
|
||||
msgid "That command doesn't seem to exist."
|
||||
msgstr ""
|
||||
|
||||
93
redbot/cogs/downloader/locales/messages.pot
Normal file
93
redbot/cogs/downloader/locales/messages.pot
Normal file
@@ -0,0 +1,93 @@
|
||||
# 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 16:24+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"
|
||||
|
||||
|
||||
#: ../downloader.py:202
|
||||
msgid "That git repo has already been added under another name."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:204 ../downloader.py:205
|
||||
msgid "Something went wrong during the cloning process."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:207
|
||||
msgid "Repo `{}` successfully added."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:216
|
||||
msgid "The repo `{}` has been deleted successfully."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:224
|
||||
msgid ""
|
||||
"Installed Repos:\n"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:244
|
||||
msgid "Error, there is no cog by the name of `{}` in the `{}` repo."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:249
|
||||
msgid "Failed to install the required libraries for `{}`: `{}`"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:259
|
||||
msgid "`{}` cog successfully installed."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:275
|
||||
msgid "`{}` was successfully removed."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:277
|
||||
msgid "That cog was installed but can no longer be located. You may need to remove it's files manually if it is still usable."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:301
|
||||
msgid "Cog update completed successfully."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:309
|
||||
msgid ""
|
||||
"Available Cogs:\n"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:321
|
||||
msgid "There is no cog `{}` in the repo `{}`"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:326
|
||||
msgid ""
|
||||
"Information on {}:\n"
|
||||
"{}"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:350
|
||||
msgid "Missing from info.json"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:359
|
||||
msgid ""
|
||||
"Command: {}\n"
|
||||
"Made by: {}\n"
|
||||
"Repo: {}\n"
|
||||
"Cog name: {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:383
|
||||
msgid "That command doesn't seem to exist."
|
||||
msgstr ""
|
||||
|
||||
94
redbot/cogs/downloader/locales/nl.po
Normal file
94
redbot/cogs/downloader/locales/nl.po
Normal file
@@ -0,0 +1,94 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: 2017-08-26 16:35-0400\n"
|
||||
"PO-Revision-Date: 2017-08-26 16:42-0400\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 1.8.7.1\n"
|
||||
"Last-Translator: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Language: nl\n"
|
||||
|
||||
#: ../downloader.py:202
|
||||
msgid "That git repo has already been added under another name."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:204 ../downloader.py:205
|
||||
msgid "Something went wrong during the cloning process."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:207
|
||||
msgid "Repo `{}` successfully added."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:216
|
||||
msgid "The repo `{}` has been deleted successfully."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:224
|
||||
msgid "Installed Repos:\n"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:244
|
||||
msgid "Error, there is no cog by the name of `{}` in the `{}` repo."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:249
|
||||
msgid "Failed to install the required libraries for `{}`: `{}`"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:259
|
||||
msgid "`{}` cog successfully installed."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:275
|
||||
msgid "`{}` was successfully removed."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:277
|
||||
msgid ""
|
||||
"That cog was installed but can no longer be located. You may need to remove "
|
||||
"it's files manually if it is still usable."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:301
|
||||
msgid "Cog update completed successfully."
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:309
|
||||
msgid "Available Cogs:\n"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:321
|
||||
msgid "There is no cog `{}` in the repo `{}`"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:326
|
||||
msgid ""
|
||||
"Information on {}:\n"
|
||||
"{}"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:350
|
||||
msgid "Missing from info.json"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:359
|
||||
msgid ""
|
||||
"Command: {}\n"
|
||||
"Made by: {}\n"
|
||||
"Repo: {}\n"
|
||||
"Cog name: {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../downloader.py:383
|
||||
msgid "That command doesn't seem to exist."
|
||||
msgstr ""
|
||||
3
redbot/cogs/downloader/log.py
Normal file
3
redbot/cogs/downloader/log.py
Normal file
@@ -0,0 +1,3 @@
|
||||
import logging
|
||||
|
||||
log = logging.getLogger("red.downloader")
|
||||
559
redbot/cogs/downloader/repo_manager.py
Normal file
559
redbot/cogs/downloader/repo_manager.py
Normal file
@@ -0,0 +1,559 @@
|
||||
import asyncio
|
||||
import functools
|
||||
import os
|
||||
import pkgutil
|
||||
import shutil
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from pathlib import Path
|
||||
from subprocess import run as sp_run, PIPE
|
||||
from sys import executable
|
||||
from typing import Tuple, MutableMapping, Union
|
||||
|
||||
from discord.ext import commands
|
||||
|
||||
from redbot.core import Config
|
||||
from redbot.core import data_manager
|
||||
from .errors import *
|
||||
from .installable import Installable, InstallableType
|
||||
from .json_mixins import RepoJSONMixin
|
||||
from .log import log
|
||||
|
||||
|
||||
class Repo(RepoJSONMixin):
|
||||
GIT_CLONE = "git clone -b {branch} {url} {folder}"
|
||||
GIT_CLONE_NO_BRANCH = "git clone {url} {folder}"
|
||||
GIT_CURRENT_BRANCH = "git -C {path} rev-parse --abbrev-ref HEAD"
|
||||
GIT_LATEST_COMMIT = "git -C {path} rev-parse {branch}"
|
||||
GIT_HARD_RESET = "git -C {path} reset --hard origin/{branch} -q"
|
||||
GIT_PULL = "git -C {path} pull -q --ff-only"
|
||||
GIT_DIFF_FILE_STATUS = ("git -C {path} diff --no-commit-id --name-status"
|
||||
" {old_hash} {new_hash}")
|
||||
GIT_LOG = ("git -C {path} log --relative-date --reverse {old_hash}.."
|
||||
" {relative_file_path}")
|
||||
|
||||
PIP_INSTALL = "{python} -m pip install -U -t {target_dir} {reqs}"
|
||||
|
||||
def __init__(self, name: str, url: str, branch: str, folder_path: Path,
|
||||
available_modules: Tuple[Installable]=(), loop: asyncio.AbstractEventLoop=None):
|
||||
self.url = url
|
||||
self.branch = branch
|
||||
|
||||
self.name = name
|
||||
|
||||
self.folder_path = folder_path
|
||||
self.folder_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
super().__init__(self.folder_path)
|
||||
|
||||
self.available_modules = available_modules
|
||||
|
||||
self._executor = ThreadPoolExecutor(1)
|
||||
|
||||
self._repo_lock = asyncio.Lock()
|
||||
|
||||
self._loop = loop
|
||||
if self._loop is None:
|
||||
self._loop = asyncio.get_event_loop()
|
||||
|
||||
@classmethod
|
||||
async def convert(cls, ctx: commands.Context, argument: str):
|
||||
downloader_cog = ctx.bot.get_cog("Downloader")
|
||||
if downloader_cog is None:
|
||||
raise commands.CommandError("No Downloader cog found.")
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
repo_manager = downloader_cog._repo_manager
|
||||
poss_repo = repo_manager.get_repo(argument)
|
||||
if poss_repo is None:
|
||||
raise commands.BadArgument("Repo by the name {} does not exist.".format(argument))
|
||||
return poss_repo
|
||||
|
||||
def _existing_git_repo(self) -> (bool, Path):
|
||||
git_path = self.folder_path / '.git'
|
||||
return git_path.exists(), git_path
|
||||
|
||||
async def _get_file_update_statuses(
|
||||
self, old_hash: str, new_hash: str) -> MutableMapping[str, str]:
|
||||
"""
|
||||
Gets the file update status letters for each changed file between
|
||||
the two hashes.
|
||||
:param old_hash: Pre-update
|
||||
:param new_hash: Post-update
|
||||
:return: Mapping of filename -> status_letter
|
||||
"""
|
||||
p = await self._run(
|
||||
self.GIT_DIFF_FILE_STATUS.format(
|
||||
path=self.folder_path,
|
||||
old_hash=old_hash,
|
||||
new_hash=new_hash
|
||||
)
|
||||
)
|
||||
|
||||
if p.returncode != 0:
|
||||
raise GitDiffError("Git diff failed for repo at path:"
|
||||
" {}".format(self.folder_path))
|
||||
|
||||
stdout = p.stdout.strip().decode().split('\n')
|
||||
|
||||
ret = {}
|
||||
|
||||
for filename in stdout:
|
||||
# TODO: filter these filenames by ones in self.available_modules
|
||||
status, _, filepath = filename.partition('\t')
|
||||
ret[filepath] = status
|
||||
|
||||
return ret
|
||||
|
||||
async def _get_commit_notes(self, old_commit_hash: str,
|
||||
relative_file_path: str) -> str:
|
||||
"""
|
||||
Gets the commit notes from git log.
|
||||
:param old_commit_hash: Point in time to start getting messages
|
||||
:param relative_file_path: Path relative to the repo folder of the file
|
||||
to get messages for.
|
||||
:return: Git commit note log
|
||||
"""
|
||||
p = await self._run(
|
||||
self.GIT_LOG.format(
|
||||
path=self.folder_path,
|
||||
old_hash=old_commit_hash,
|
||||
relative_file_path=relative_file_path
|
||||
)
|
||||
)
|
||||
|
||||
if p.returncode != 0:
|
||||
raise GitException("An exception occurred while executing git log on"
|
||||
" this repo: {}".format(self.folder_path))
|
||||
|
||||
return p.stdout.decode().strip()
|
||||
|
||||
def _update_available_modules(self) -> Tuple[str]:
|
||||
"""
|
||||
Updates the available modules attribute for this repo.
|
||||
:return: List of available modules.
|
||||
"""
|
||||
curr_modules = []
|
||||
"""
|
||||
for name in self.folder_path.iterdir():
|
||||
if name.is_dir():
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
name.stem, location=str(name.parent)
|
||||
)
|
||||
if spec is not None:
|
||||
curr_modules.append(
|
||||
Installable(location=name)
|
||||
)
|
||||
"""
|
||||
for file_finder, name, is_pkg in pkgutil.walk_packages(path=[str(self.folder_path), ]):
|
||||
curr_modules.append(
|
||||
Installable(location=self.folder_path / name)
|
||||
)
|
||||
self.available_modules = curr_modules
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
return tuple(self.available_modules)
|
||||
|
||||
async def _run(self, *args, **kwargs):
|
||||
env = os.environ.copy()
|
||||
env['GIT_TERMINAL_PROMPT'] = '0'
|
||||
kwargs['env'] = env
|
||||
async with self._repo_lock:
|
||||
return await self._loop.run_in_executor(
|
||||
self._executor,
|
||||
functools.partial(sp_run, *args, stdout=PIPE, **kwargs)
|
||||
)
|
||||
|
||||
async def clone(self) -> Tuple[str]:
|
||||
"""
|
||||
Clones a new repo.
|
||||
|
||||
:return: List of available module names from this repo.
|
||||
"""
|
||||
exists, path = self._existing_git_repo()
|
||||
if exists:
|
||||
raise ExistingGitRepo(
|
||||
"A git repo already exists at path: {}".format(path)
|
||||
)
|
||||
|
||||
if self.branch is not None:
|
||||
p = await self._run(
|
||||
self.GIT_CLONE.format(
|
||||
branch=self.branch,
|
||||
url=self.url,
|
||||
folder=self.folder_path
|
||||
).split()
|
||||
)
|
||||
else:
|
||||
p = await self._run(
|
||||
self.GIT_CLONE_NO_BRANCH.format(
|
||||
url=self.url,
|
||||
folder=self.folder_path
|
||||
).split()
|
||||
)
|
||||
|
||||
if p.returncode != 0:
|
||||
raise CloningError("Error when running git clone.")
|
||||
|
||||
if self.branch is None:
|
||||
self.branch = await self.current_branch()
|
||||
|
||||
self._read_info_file()
|
||||
|
||||
return self._update_available_modules()
|
||||
|
||||
async def current_branch(self) -> str:
|
||||
"""
|
||||
Determines the current branch using git commands.
|
||||
|
||||
:return: Current branch name
|
||||
"""
|
||||
exists, _ = self._existing_git_repo()
|
||||
if not exists:
|
||||
raise MissingGitRepo(
|
||||
"A git repo does not exist at path: {}".format(self.folder_path)
|
||||
)
|
||||
|
||||
p = await self._run(
|
||||
self.GIT_CURRENT_BRANCH.format(
|
||||
path=self.folder_path
|
||||
).split()
|
||||
)
|
||||
|
||||
if p.returncode != 0:
|
||||
raise GitException("Could not determine current branch"
|
||||
" at path: {}".format(self.folder_path))
|
||||
|
||||
return p.stdout.decode().strip()
|
||||
|
||||
async def current_commit(self, branch: str=None) -> str:
|
||||
"""
|
||||
Determines the current commit hash of the repo.
|
||||
|
||||
:param branch: Override for repo's branch attribute
|
||||
:return: Commit hash string
|
||||
"""
|
||||
if branch is None:
|
||||
branch = self.branch
|
||||
|
||||
exists, _ = self._existing_git_repo()
|
||||
if not exists:
|
||||
raise MissingGitRepo(
|
||||
"A git repo does not exist at path: {}".format(self.folder_path)
|
||||
)
|
||||
|
||||
p = await self._run(
|
||||
self.GIT_LATEST_COMMIT.format(
|
||||
path=self.folder_path,
|
||||
branch=branch
|
||||
).split()
|
||||
)
|
||||
|
||||
if p.returncode != 0:
|
||||
raise CurrentHashError("Unable to determine old commit hash.")
|
||||
|
||||
return p.stdout.decode().strip()
|
||||
|
||||
async def hard_reset(self, branch: str=None) -> None:
|
||||
"""
|
||||
Performs a hard reset on the current repo.
|
||||
|
||||
:param branch: Override for repo branch attribute.
|
||||
"""
|
||||
if branch is None:
|
||||
branch = self.branch
|
||||
|
||||
exists, _ = self._existing_git_repo()
|
||||
if not exists:
|
||||
raise MissingGitRepo(
|
||||
"A git repo does not exist at path: {}".format(self.folder_path)
|
||||
)
|
||||
|
||||
p = await self._run(
|
||||
self.GIT_HARD_RESET.format(
|
||||
path=self.folder_path,
|
||||
branch=branch
|
||||
).split()
|
||||
)
|
||||
|
||||
if p.returncode != 0:
|
||||
raise HardResetError("Some error occurred when trying to"
|
||||
" execute a hard reset on the repo at"
|
||||
" the following path: {}".format(self.folder_path))
|
||||
|
||||
async def update(self) -> (str, str):
|
||||
"""
|
||||
Updates the current branch of this repo.
|
||||
|
||||
:return: tuple of (old commit hash, new commit hash)
|
||||
:rtype: tuple
|
||||
"""
|
||||
curr_branch = await self.current_branch()
|
||||
old_commit = await self.current_commit(branch=curr_branch)
|
||||
|
||||
await self.hard_reset(branch=curr_branch)
|
||||
|
||||
p = await self._run(
|
||||
self.GIT_PULL.format(
|
||||
path=self.folder_path
|
||||
).split()
|
||||
)
|
||||
|
||||
if p.returncode != 0:
|
||||
raise UpdateError("Git pull returned a non zero exit code"
|
||||
" for the repo located at path: {}".format(self.folder_path))
|
||||
|
||||
new_commit = await self.current_commit(branch=curr_branch)
|
||||
|
||||
self._update_available_modules()
|
||||
self._read_info_file()
|
||||
|
||||
return old_commit, new_commit
|
||||
|
||||
async def install_cog(self, cog: Installable, target_dir: Path) -> bool:
|
||||
"""
|
||||
Copies a cog to the target directory.
|
||||
|
||||
:param Installable cog: Cog to install.
|
||||
:param pathlib.Path target_dir: Directory to install the cog in.
|
||||
:return: Installation success status.
|
||||
:rtype: bool
|
||||
"""
|
||||
if cog not in self.available_cogs:
|
||||
raise DownloaderException("That cog does not exist in this repo")
|
||||
|
||||
if not target_dir.is_dir():
|
||||
raise ValueError("That target directory is not actually a directory.")
|
||||
|
||||
if not target_dir.exists():
|
||||
raise ValueError("That target directory does not exist.")
|
||||
|
||||
return await cog.copy_to(target_dir=target_dir)
|
||||
|
||||
async def install_libraries(self, target_dir: Path, libraries: Tuple[Installable]=()) -> bool:
|
||||
"""
|
||||
Copies all shared libraries (or a given subset) to the target
|
||||
directory.
|
||||
|
||||
:param pathlib.Path target_dir: Directory to install shared libraries to.
|
||||
:param tuple(Installable) libraries: A subset of available libraries.
|
||||
:return: Status of all installs.
|
||||
:rtype: bool
|
||||
"""
|
||||
if libraries:
|
||||
if not all([i in self.available_libraries for i in libraries]):
|
||||
raise ValueError("Some given libraries are not available in this repo.")
|
||||
else:
|
||||
libraries = self.available_libraries
|
||||
|
||||
if libraries:
|
||||
return all([lib.copy_to(target_dir=target_dir) for lib in libraries])
|
||||
return True
|
||||
|
||||
async def install_requirements(self, cog: Installable, target_dir: Path) -> bool:
|
||||
"""
|
||||
Installs the requirements defined by the requirements
|
||||
attribute on the cog object and puts them in the given
|
||||
target directory.
|
||||
|
||||
:param Installable cog: Cog for which to install requirements.
|
||||
:param pathlib.Path target_dir: Path to which to install requirements.
|
||||
:return: Status of requirements install.
|
||||
:rtype: bool
|
||||
"""
|
||||
if not target_dir.is_dir():
|
||||
raise ValueError("Target directory is not a directory.")
|
||||
target_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
return await self.install_raw_requirements(cog.requirements, target_dir)
|
||||
|
||||
async def install_raw_requirements(self, requirements: Tuple[str], target_dir: Path) -> bool:
|
||||
"""
|
||||
Installs a list of requirements using pip and places them into
|
||||
the given target directory.
|
||||
|
||||
:param tuple(str) requirements: List of requirement names to install via pip.
|
||||
:param pathlib.Path target_dir: Directory to install requirements to.
|
||||
:return: Status of all requirements install.
|
||||
:rtype: bool
|
||||
"""
|
||||
if len(requirements) == 0:
|
||||
return True
|
||||
|
||||
# TODO: Check and see if any of these modules are already available
|
||||
|
||||
p = await self._run(
|
||||
self.PIP_INSTALL.format(
|
||||
python=executable,
|
||||
target_dir=target_dir,
|
||||
reqs=" ".join(requirements)
|
||||
).split()
|
||||
)
|
||||
|
||||
if p.returncode != 0:
|
||||
log.error("Something went wrong when installing"
|
||||
" the following requirements:"
|
||||
" {}".format(", ".join(requirements)))
|
||||
return False
|
||||
return True
|
||||
|
||||
@property
|
||||
def available_cogs(self) -> Tuple[Installable]:
|
||||
"""
|
||||
Returns a list of available cogs (not shared libraries and not hidden).
|
||||
|
||||
:rtype: tuple(Installable)
|
||||
"""
|
||||
# noinspection PyTypeChecker
|
||||
return tuple(
|
||||
[m for m in self.available_modules
|
||||
if m.type == InstallableType.COG and not m.hidden]
|
||||
)
|
||||
|
||||
@property
|
||||
def available_libraries(self) -> Tuple[Installable]:
|
||||
"""
|
||||
Returns a list of available shared libraries in this repo.
|
||||
|
||||
:rtype: tuple(Installable)
|
||||
"""
|
||||
# noinspection PyTypeChecker
|
||||
return tuple(
|
||||
[m for m in self.available_modules
|
||||
if m.type == InstallableType.SHARED_LIBRARY]
|
||||
)
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
"url": self.url,
|
||||
"name": self.name,
|
||||
"branch": self.branch,
|
||||
"folder_path": self.folder_path.relative_to(Path.cwd()).parts,
|
||||
"available_modules": [m.to_json() for m in self.available_modules]
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, data):
|
||||
# noinspection PyTypeChecker
|
||||
return Repo(data['name'], data['url'], data['branch'],
|
||||
Path.cwd() / Path(*data['folder_path']),
|
||||
tuple([Installable.from_json(m) for m in data['available_modules']]))
|
||||
|
||||
|
||||
class RepoManager:
|
||||
def __init__(self, downloader_config: Config):
|
||||
self.downloader_config = downloader_config
|
||||
|
||||
self._repos = {}
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(self._load_repos(set=True)) # str_name: Repo
|
||||
|
||||
@property
|
||||
def repos_folder(self) -> Path:
|
||||
data_folder = data_manager.cog_data_path(self)
|
||||
return data_folder / 'repos'
|
||||
|
||||
def does_repo_exist(self, name: str) -> bool:
|
||||
return name in self._repos
|
||||
|
||||
@staticmethod
|
||||
def validate_and_normalize_repo_name(name: str) -> str:
|
||||
if not name.isidentifier():
|
||||
raise InvalidRepoName("Not a valid Python variable name.")
|
||||
return name.lower()
|
||||
|
||||
async def add_repo(self, url: str, name: str, branch: str="master") -> Repo:
|
||||
"""
|
||||
Adds a repo and clones it.
|
||||
|
||||
:param url: URL of git repo to clone.
|
||||
:param name: Internal name of repo.
|
||||
:param branch: Branch to clone.
|
||||
:return: New repo object representing cloned repo.
|
||||
:rtype: Repo
|
||||
"""
|
||||
name = self.validate_and_normalize_repo_name(name)
|
||||
if self.does_repo_exist(name):
|
||||
raise InvalidRepoName(
|
||||
"That repo name you provided already exists."
|
||||
" Please choose another."
|
||||
)
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
r = Repo(url=url, name=name, branch=branch,
|
||||
folder_path=self.repos_folder / name)
|
||||
await r.clone()
|
||||
|
||||
self._repos[name] = r
|
||||
await self._save_repos()
|
||||
|
||||
return r
|
||||
|
||||
def get_repo(self, name: str) -> Union[Repo, None]:
|
||||
"""
|
||||
Returns a repo object with the given name.
|
||||
|
||||
:param name: Repo name
|
||||
:return: Repo object or ``None`` if repo does not exist.
|
||||
:rtype: Union[Repo, None]
|
||||
"""
|
||||
return self._repos.get(name, None)
|
||||
|
||||
def get_all_repo_names(self) -> Tuple[str]:
|
||||
"""
|
||||
Returns a tuple of all repo names
|
||||
|
||||
:rtype: tuple(str)
|
||||
"""
|
||||
# noinspection PyTypeChecker
|
||||
return tuple(self._repos.keys())
|
||||
|
||||
async def delete_repo(self, name: str):
|
||||
"""
|
||||
Deletes a repo and its folders with the given name.
|
||||
|
||||
:param name: Name of the repo to delete.
|
||||
:raises MissingGitRepo: If the repo does not exist.
|
||||
"""
|
||||
repo = self.get_repo(name)
|
||||
if repo is None:
|
||||
raise MissingGitRepo("There is no repo with the name {}".format(name))
|
||||
|
||||
shutil.rmtree(str(repo.folder_path))
|
||||
|
||||
try:
|
||||
del self._repos[name]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
await self._save_repos()
|
||||
|
||||
async def update_all_repos(self) -> MutableMapping[Repo, Tuple[str, str]]:
|
||||
"""
|
||||
Calls :py:meth:`Repo.update` on all repos.
|
||||
|
||||
:return:
|
||||
A mapping of :py:class:`Repo` objects that received new commits to a tuple containing old and
|
||||
new commit hashes.
|
||||
"""
|
||||
ret = {}
|
||||
for _, repo in self._repos.items():
|
||||
old, new = await repo.update()
|
||||
if old != new:
|
||||
ret[repo] = (old, new)
|
||||
|
||||
await self._save_repos()
|
||||
return ret
|
||||
|
||||
async def _load_repos(self, set=False) -> MutableMapping[str, Repo]:
|
||||
ret = {
|
||||
name: Repo.from_json(data) for name, data in
|
||||
(await self.downloader_config.repos()).items()
|
||||
}
|
||||
if set:
|
||||
self._repos = ret
|
||||
return ret
|
||||
|
||||
async def _save_repos(self):
|
||||
repo_json_info = {name: r.to_json() for name, r in self._repos.items()}
|
||||
await self.downloader_config.repos.set(repo_json_info)
|
||||
2
redbot/cogs/downloader/repos/.gitignore
vendored
Normal file
2
redbot/cogs/downloader/repos/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
6
redbot/cogs/economy/__init__.py
Normal file
6
redbot/cogs/economy/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from redbot.core.bot import Red
|
||||
from .economy import Economy
|
||||
|
||||
|
||||
def setup(bot: Red):
|
||||
bot.add_cog(Economy(bot))
|
||||
533
redbot/cogs/economy/economy.py
Normal file
533
redbot/cogs/economy/economy.py
Normal file
@@ -0,0 +1,533 @@
|
||||
import calendar
|
||||
import logging
|
||||
import random
|
||||
from collections import defaultdict, deque
|
||||
from enum import Enum
|
||||
|
||||
import discord
|
||||
|
||||
from redbot.cogs.bank import check_global_setting_guildowner, check_global_setting_admin
|
||||
from redbot.core import Config, bank
|
||||
from redbot.core.i18n import CogI18n
|
||||
from redbot.core.utils.chat_formatting import pagify, box
|
||||
from discord.ext import commands
|
||||
|
||||
from redbot.core.bot import Red
|
||||
|
||||
_ = CogI18n("Economy", __file__)
|
||||
|
||||
logger = logging.getLogger("red.economy")
|
||||
|
||||
NUM_ENC = "\N{COMBINING ENCLOSING KEYCAP}"
|
||||
|
||||
|
||||
class SMReel(Enum):
|
||||
cherries = "\N{CHERRIES}"
|
||||
cookie = "\N{COOKIE}"
|
||||
two = "\N{DIGIT TWO}" + NUM_ENC
|
||||
flc = "\N{FOUR LEAF CLOVER}"
|
||||
cyclone = "\N{CYCLONE}"
|
||||
sunflower = "\N{SUNFLOWER}"
|
||||
six = "\N{DIGIT SIX}" + NUM_ENC
|
||||
mushroom = "\N{MUSHROOM}"
|
||||
heart = "\N{HEAVY BLACK HEART}"
|
||||
snowflake = "\N{SNOWFLAKE}"
|
||||
|
||||
|
||||
PAYOUTS = {
|
||||
(SMReel.two, SMReel.two, SMReel.six): {
|
||||
"payout": lambda x: x * 2500 + x,
|
||||
"phrase": _("JACKPOT! 226! Your bid has been multiplied * 2500!")
|
||||
},
|
||||
(SMReel.flc, SMReel.flc, SMReel.flc): {
|
||||
"payout": lambda x: x + 1000,
|
||||
"phrase": _("4LC! +1000!")
|
||||
},
|
||||
(SMReel.cherries, SMReel.cherries, SMReel.cherries): {
|
||||
"payout": lambda x: x + 800,
|
||||
"phrase": _("Three cherries! +800!")
|
||||
},
|
||||
(SMReel.two, SMReel.six): {
|
||||
"payout": lambda x: x * 4 + x,
|
||||
"phrase": _("2 6! Your bid has been multiplied * 4!")
|
||||
},
|
||||
(SMReel.cherries, SMReel.cherries): {
|
||||
"payout": lambda x: x * 3 + x,
|
||||
"phrase": _("Two cherries! Your bid has been multiplied * 3!")
|
||||
},
|
||||
"3 symbols": {
|
||||
"payout": lambda x: x + 500,
|
||||
"phrase": _("Three symbols! +500!")
|
||||
},
|
||||
"2 symbols": {
|
||||
"payout": lambda x: x * 2 + x,
|
||||
"phrase": _("Two consecutive symbols! Your bid has been multiplied * 2!")
|
||||
},
|
||||
}
|
||||
|
||||
SLOT_PAYOUTS_MSG = _("Slot machine payouts:\n"
|
||||
"{two.value} {two.value} {six.value} Bet * 2500\n"
|
||||
"{flc.value} {flc.value} {flc.value} +1000\n"
|
||||
"{cherries.value} {cherries.value} {cherries.value} +800\n"
|
||||
"{two.value} {six.value} Bet * 4\n"
|
||||
"{cherries.value} {cherries.value} Bet * 3\n\n"
|
||||
"Three symbols: +500\n"
|
||||
"Two symbols: Bet * 2").format(**SMReel.__dict__)
|
||||
|
||||
|
||||
def guild_only_check():
|
||||
async def pred(ctx: commands.Context):
|
||||
if await bank.is_global():
|
||||
return True
|
||||
elif not await bank.is_global() and ctx.guild is not None:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return commands.check(pred)
|
||||
|
||||
|
||||
class SetParser:
|
||||
def __init__(self, argument):
|
||||
allowed = ("+", "-")
|
||||
self.sum = int(argument)
|
||||
if argument and argument[0] in allowed:
|
||||
if self.sum < 0:
|
||||
self.operation = "withdraw"
|
||||
elif self.sum > 0:
|
||||
self.operation = "deposit"
|
||||
else:
|
||||
raise RuntimeError
|
||||
self.sum = abs(self.sum)
|
||||
elif argument.isdigit():
|
||||
self.operation = "set"
|
||||
else:
|
||||
raise RuntimeError
|
||||
|
||||
|
||||
class Economy:
|
||||
"""Economy
|
||||
|
||||
Get rich and have fun with imaginary currency!"""
|
||||
|
||||
default_guild_settings = {
|
||||
"PAYDAY_TIME": 300,
|
||||
"PAYDAY_CREDITS": 120,
|
||||
"SLOT_MIN": 5,
|
||||
"SLOT_MAX": 100,
|
||||
"SLOT_TIME": 0,
|
||||
"REGISTER_CREDITS": 0
|
||||
}
|
||||
|
||||
default_global_settings = default_guild_settings
|
||||
|
||||
default_member_settings = {
|
||||
"next_payday": 0,
|
||||
"last_slot": 0
|
||||
}
|
||||
|
||||
default_user_settings = default_member_settings
|
||||
|
||||
def __init__(self, bot: Red):
|
||||
self.bot = bot
|
||||
self.file_path = "data/economy/settings.json"
|
||||
self.config = Config.get_conf(self, 1256844281)
|
||||
self.config.register_guild(**self.default_guild_settings)
|
||||
self.config.register_global(**self.default_global_settings)
|
||||
self.config.register_member(**self.default_member_settings)
|
||||
self.config.register_user(**self.default_user_settings)
|
||||
self.slot_register = defaultdict(dict)
|
||||
|
||||
@commands.group(name="bank")
|
||||
async def _bank(self, ctx: commands.Context):
|
||||
"""Bank operations"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await self.bot.send_cmd_help(ctx)
|
||||
|
||||
@_bank.command()
|
||||
async def balance(self, ctx: commands.Context, user: discord.Member = None):
|
||||
"""Shows balance of user.
|
||||
|
||||
Defaults to yours."""
|
||||
if user is None:
|
||||
user = ctx.author
|
||||
|
||||
bal = await bank.get_balance(user)
|
||||
currency = await bank.get_currency_name(ctx.guild)
|
||||
|
||||
await ctx.send(_("{}'s balance is {} {}").format(
|
||||
user.display_name, bal, currency))
|
||||
|
||||
@_bank.command()
|
||||
async def transfer(self, ctx: commands.Context, to: discord.Member, amount: int):
|
||||
"""Transfer currency to other users"""
|
||||
from_ = ctx.author
|
||||
currency = await bank.get_currency_name(ctx.guild)
|
||||
|
||||
try:
|
||||
await bank.transfer_credits(from_, to, amount)
|
||||
except ValueError as e:
|
||||
await ctx.send(str(e))
|
||||
|
||||
await ctx.send(_("{} transferred {} {} to {}").format(
|
||||
from_.display_name, amount, currency, to.display_name
|
||||
))
|
||||
|
||||
@_bank.command(name="set")
|
||||
@check_global_setting_admin()
|
||||
async def _set(self, ctx: commands.Context, to: discord.Member, creds: SetParser):
|
||||
"""Sets balance of user's bank account. See help for more operations
|
||||
|
||||
Passing positive and negative values will add/remove currency instead
|
||||
|
||||
Examples:
|
||||
bank set @Twentysix 26 - Sets balance to 26
|
||||
bank set @Twentysix +2 - Increases balance by 2
|
||||
bank set @Twentysix -6 - Decreases balance by 6"""
|
||||
author = ctx.author
|
||||
currency = await bank.get_currency_name(ctx.guild)
|
||||
|
||||
if creds.operation == "deposit":
|
||||
await bank.deposit_credits(to, creds.sum)
|
||||
await ctx.send(_("{} added {} {} to {}'s account.").format(
|
||||
author.display_name, creds.sum, currency, to.display_name
|
||||
))
|
||||
elif creds.operation == "withdraw":
|
||||
await bank.withdraw_credits(to, creds.sum)
|
||||
await ctx.send(_("{} removed {} {} from {}'s account.").format(
|
||||
author.display_name, creds.sum, currency, to.display_name
|
||||
))
|
||||
else:
|
||||
await bank.set_balance(to, creds.sum)
|
||||
await ctx.send(_("{} set {}'s account to {} {}.").format(
|
||||
author.display_name, to.display_name, creds.sum, currency
|
||||
))
|
||||
|
||||
@_bank.command()
|
||||
@guild_only_check()
|
||||
@check_global_setting_guildowner()
|
||||
async def reset(self, ctx, confirmation: bool = False):
|
||||
"""Deletes all guild's bank accounts"""
|
||||
if confirmation is False:
|
||||
await ctx.send(
|
||||
_("This will delete all bank accounts for {}.\nIf you're sure, type "
|
||||
"{}bank reset yes").format(
|
||||
self.bot.user.name if await bank.is_global() else "this guild",
|
||||
ctx.prefix
|
||||
)
|
||||
)
|
||||
else:
|
||||
if await bank.is_global():
|
||||
# Bank being global means that the check would cause only
|
||||
# the owner and any co-owners to be able to run the command
|
||||
# so if we're in the function, it's safe to assume that the
|
||||
# author is authorized to use owner-only commands
|
||||
user = ctx.author
|
||||
else:
|
||||
user = ctx.guild.owner
|
||||
success = await bank.wipe_bank(user)
|
||||
if success:
|
||||
await ctx.send(_("All bank accounts of this guild have been "
|
||||
"deleted."))
|
||||
|
||||
@commands.command()
|
||||
@guild_only_check()
|
||||
async def payday(self, ctx: commands.Context):
|
||||
"""Get some free currency"""
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
|
||||
cur_time = calendar.timegm(ctx.message.created_at.utctimetuple())
|
||||
credits_name = await bank.get_currency_name(ctx.guild)
|
||||
if await bank.is_global():
|
||||
next_payday = await self.config.user(author).next_payday()
|
||||
if cur_time >= next_payday:
|
||||
await bank.deposit_credits(author, await self.config.PAYDAY_CREDITS())
|
||||
next_payday = cur_time + await self.config.PAYDAY_TIME()
|
||||
await self.config.user(author).next_payday.set(next_payday)
|
||||
await ctx.send(
|
||||
_("{} Here, take some {}. Enjoy! (+{}"
|
||||
" {}!)").format(
|
||||
author.mention, credits_name,
|
||||
str(await self.config.PAYDAY_CREDITS()),
|
||||
credits_name
|
||||
)
|
||||
)
|
||||
else:
|
||||
dtime = self.display_time(next_payday - cur_time)
|
||||
await ctx.send(
|
||||
_("{} Too soon. For your next payday you have to"
|
||||
" wait {}.").format(author.mention, dtime)
|
||||
)
|
||||
else:
|
||||
next_payday = await self.config.member(author).next_payday()
|
||||
if cur_time >= next_payday:
|
||||
await bank.deposit_credits(author, await self.config.guild(guild).PAYDAY_CREDITS())
|
||||
next_payday = cur_time + await self.config.guild(guild).PAYDAY_TIME()
|
||||
await self.config.member(author).next_payday.set(next_payday)
|
||||
await ctx.send(
|
||||
_("{} Here, take some {}. Enjoy! (+{}"
|
||||
" {}!)").format(
|
||||
author.mention, credits_name,
|
||||
str(await self.config.guild(guild).PAYDAY_CREDITS()),
|
||||
credits_name))
|
||||
else:
|
||||
dtime = self.display_time(next_payday - cur_time)
|
||||
await ctx.send(
|
||||
_("{} Too soon. For your next payday you have to"
|
||||
" wait {}.").format(author.mention, dtime))
|
||||
|
||||
@commands.command()
|
||||
@guild_only_check()
|
||||
async def leaderboard(self, ctx: commands.Context, top: int = 10):
|
||||
"""Prints out the leaderboard
|
||||
|
||||
Defaults to top 10"""
|
||||
# Originally coded by Airenkun - edited by irdumb, rewritten by Palm__ for v3
|
||||
guild = ctx.guild
|
||||
if top < 1:
|
||||
top = 10
|
||||
if bank.is_global():
|
||||
bank_sorted = sorted(await bank.get_global_accounts(ctx.author),
|
||||
key=lambda x: x.balance, reverse=True)
|
||||
else:
|
||||
bank_sorted = sorted(await bank.get_guild_accounts(guild),
|
||||
key=lambda x: x.balance, reverse=True)
|
||||
if len(bank_sorted) < top:
|
||||
top = len(bank_sorted)
|
||||
topten = bank_sorted[:top]
|
||||
highscore = ""
|
||||
place = 1
|
||||
for acc in topten:
|
||||
dname = str(acc.name)
|
||||
if len(dname) >= 23 - len(str(acc.balance)):
|
||||
dname = dname[:(23 - len(str(acc.balance))) - 3]
|
||||
dname += "... "
|
||||
highscore += str(place).ljust(len(str(top)) + 1)
|
||||
highscore += dname.ljust(23 - len(str(acc.balance)))
|
||||
highscore += str(acc.balance) + "\n"
|
||||
place += 1
|
||||
if highscore != "":
|
||||
for page in pagify(highscore, shorten_by=12):
|
||||
await ctx.send(box(page, lang="py"))
|
||||
else:
|
||||
await ctx.send(_("There are no accounts in the bank."))
|
||||
|
||||
@commands.command()
|
||||
@guild_only_check()
|
||||
async def payouts(self, ctx: commands.Context):
|
||||
"""Shows slot machine payouts"""
|
||||
await ctx.author.send(SLOT_PAYOUTS_MSG)
|
||||
|
||||
@commands.command()
|
||||
@guild_only_check()
|
||||
async def slot(self, ctx: commands.Context, bid: int):
|
||||
"""Play the slot machine"""
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
channel = ctx.channel
|
||||
if await bank.is_global():
|
||||
valid_bid = await self.config.SLOT_MIN() <= bid <= await self.config.SLOT_MAX()
|
||||
slot_time = await self.config.SLOT_TIME()
|
||||
last_slot = await self.config.user(author).last_slot()
|
||||
else:
|
||||
valid_bid = await self.config.guild(guild).SLOT_MIN() <= bid <= await self.config.guild(guild).SLOT_MAX()
|
||||
slot_time = await self.config.guild(guild).SLOT_TIME()
|
||||
last_slot = await self.config.member(author).last_slot()
|
||||
now = calendar.timegm(ctx.message.created_at.utctimetuple())
|
||||
|
||||
if (now - last_slot) < slot_time:
|
||||
await ctx.send(_("You're on cooldown, try again in a bit."))
|
||||
return
|
||||
if not valid_bid:
|
||||
await ctx.send(_("That's an invalid bid amount, sorry :/"))
|
||||
return
|
||||
if not await bank.can_spend(author, bid):
|
||||
await ctx.send(_("You ain't got enough money, friend."))
|
||||
return
|
||||
if await bank.is_global():
|
||||
await self.config.user(author).last_slot.set(now)
|
||||
else:
|
||||
await self.config.member(author).last_slot.set(now)
|
||||
await self.slot_machine(author, channel, bid)
|
||||
|
||||
async def slot_machine(self, author, channel, bid):
|
||||
default_reel = deque(SMReel)
|
||||
reels = []
|
||||
for i in range(3):
|
||||
default_reel.rotate(random.randint(-999, 999)) # weeeeee
|
||||
new_reel = deque(default_reel, maxlen=3) # we need only 3 symbols
|
||||
reels.append(new_reel) # for each reel
|
||||
rows = ((reels[0][0], reels[1][0], reels[2][0]),
|
||||
(reels[0][1], reels[1][1], reels[2][1]),
|
||||
(reels[0][2], reels[1][2], reels[2][2]))
|
||||
|
||||
slot = "~~\n~~" # Mobile friendly
|
||||
for i, row in enumerate(rows): # Let's build the slot to show
|
||||
sign = " "
|
||||
if i == 1:
|
||||
sign = ">"
|
||||
slot += "{}{} {} {}\n".format(sign, *[c.value for c in row])
|
||||
|
||||
payout = PAYOUTS.get(rows[1])
|
||||
if not payout:
|
||||
# Checks for two-consecutive-symbols special rewards
|
||||
payout = PAYOUTS.get((rows[1][0], rows[1][1]),
|
||||
PAYOUTS.get((rows[1][1], rows[1][2])))
|
||||
if not payout:
|
||||
# Still nothing. Let's check for 3 generic same symbols
|
||||
# or 2 consecutive symbols
|
||||
has_three = rows[1][0] == rows[1][1] == rows[1][2]
|
||||
has_two = (rows[1][0] == rows[1][1]) or (rows[1][1] == rows[1][2])
|
||||
if has_three:
|
||||
payout = PAYOUTS["3 symbols"]
|
||||
elif has_two:
|
||||
payout = PAYOUTS["2 symbols"]
|
||||
|
||||
if payout:
|
||||
then = await bank.get_balance(author)
|
||||
pay = payout["payout"](bid)
|
||||
now = then - bid + pay
|
||||
await bank.set_balance(author, now)
|
||||
await channel.send(_("{}\n{} {}\n\nYour bid: {}\n{} → {}!"
|
||||
"").format(slot, author.mention,
|
||||
payout["phrase"], bid, then, now))
|
||||
else:
|
||||
then = await bank.get_balance(author)
|
||||
await bank.withdraw_credits(author, bid)
|
||||
now = then - bid
|
||||
await channel.send(_("{}\n{} Nothing!\nYour bid: {}\n{} → {}!"
|
||||
"").format(slot, author.mention, bid, then, now))
|
||||
|
||||
@commands.group()
|
||||
@guild_only_check()
|
||||
@check_global_setting_admin()
|
||||
async def economyset(self, ctx: commands.Context):
|
||||
"""Changes economy module settings"""
|
||||
guild = ctx.guild
|
||||
if ctx.invoked_subcommand is None:
|
||||
await self.bot.send_cmd_help(ctx)
|
||||
if await bank.is_global():
|
||||
slot_min = await self.config.SLOT_MIN()
|
||||
slot_max = await self.config.SLOT_MAX()
|
||||
slot_time = await self.config.SLOT_TIME()
|
||||
payday_time = await self.config.PAYDAY_TIME()
|
||||
payday_amount = await self.config.PAYDAY_CREDITS()
|
||||
else:
|
||||
slot_min = await self.config.guild(guild).SLOT_MIN()
|
||||
slot_max = await self.config.guild(guild).SLOT_MAX()
|
||||
slot_time = await self.config.guild(guild).SLOT_TIME()
|
||||
payday_time = await self.config.guild(guild).PAYDAY_TIME()
|
||||
payday_amount = await self.config.guild(guild).PAYDAY_CREDITS()
|
||||
register_amount = await bank.get_default_balance(guild)
|
||||
msg = box(
|
||||
_("Minimum slot bid: {}\n"
|
||||
"Maximum slot bid: {}\n"
|
||||
"Slot cooldown: {}\n"
|
||||
"Payday amount: {}\n"
|
||||
"Payday cooldown: {}\n"
|
||||
"Amount given at account registration: {}"
|
||||
"").format(
|
||||
slot_min, slot_max, slot_time,
|
||||
payday_amount, payday_time, register_amount
|
||||
),
|
||||
_("Current Economy settings:")
|
||||
)
|
||||
await ctx.send(msg)
|
||||
|
||||
@economyset.command()
|
||||
async def slotmin(self, ctx: commands.Context, bid: int):
|
||||
"""Minimum slot machine bid"""
|
||||
if bid < 1:
|
||||
await ctx.send(_('Invalid min bid amount.'))
|
||||
return
|
||||
guild = ctx.guild
|
||||
if await bank.is_global():
|
||||
await self.config.SLOT_MIN.set(bid)
|
||||
else:
|
||||
await self.config.guild(guild).SLOT_MIN.set(bid)
|
||||
credits_name = await bank.get_currency_name(guild)
|
||||
await ctx.send(_("Minimum bid is now {} {}.").format(bid, credits_name))
|
||||
|
||||
@economyset.command()
|
||||
async def slotmax(self, ctx: commands.Context, bid: int):
|
||||
"""Maximum slot machine bid"""
|
||||
slot_min = await self.config.SLOT_MIN()
|
||||
if bid < 1 or bid < slot_min:
|
||||
await ctx.send(_('Invalid slotmax bid amount. Must be greater'
|
||||
' than slotmin.'))
|
||||
return
|
||||
guild = ctx.guild
|
||||
credits_name = await bank.get_currency_name(guild)
|
||||
if await bank.is_global():
|
||||
await self.config.SLOT_MAX.set(bid)
|
||||
else:
|
||||
await self.config.guild(guild).SLOT_MAX.set(bid)
|
||||
await ctx.send(_("Maximum bid is now {} {}.").format(bid, credits_name))
|
||||
|
||||
@economyset.command()
|
||||
async def slottime(self, ctx: commands.Context, seconds: int):
|
||||
"""Seconds between each slots use"""
|
||||
guild = ctx.guild
|
||||
if await bank.is_global():
|
||||
await self.config.SLOT_TIME.set(seconds)
|
||||
else:
|
||||
await self.config.guild(guild).SLOT_TIME.set(seconds)
|
||||
await ctx.send(_("Cooldown is now {} seconds.").format(seconds))
|
||||
|
||||
@economyset.command()
|
||||
async def paydaytime(self, ctx: commands.Context, seconds: int):
|
||||
"""Seconds between each payday"""
|
||||
guild = ctx.guild
|
||||
if await bank.is_global():
|
||||
await self.config.PAYDAY_TIME.set(seconds)
|
||||
else:
|
||||
await self.config.guild(guild).PAYDAY_TIME.set(seconds)
|
||||
await ctx.send(_("Value modified. At least {} seconds must pass "
|
||||
"between each payday.").format(seconds))
|
||||
|
||||
@economyset.command()
|
||||
async def paydayamount(self, ctx: commands.Context, creds: int):
|
||||
"""Amount earned each payday"""
|
||||
guild = ctx.guild
|
||||
credits_name = await bank.get_currency_name(guild)
|
||||
if creds <= 0:
|
||||
await ctx.send(_("Har har so funny."))
|
||||
return
|
||||
if await bank.is_global():
|
||||
await self.config.PAYDAY_CREDITS.set(creds)
|
||||
else:
|
||||
await self.config.guild(guild).PAYDAY_CREDITS.set(creds)
|
||||
await ctx.send(_("Every payday will now give {} {}."
|
||||
"").format(creds, credits_name))
|
||||
|
||||
@economyset.command()
|
||||
async def registeramount(self, ctx: commands.Context, creds: int):
|
||||
"""Amount given on registering an account"""
|
||||
guild = ctx.guild
|
||||
if creds < 0:
|
||||
creds = 0
|
||||
credits_name = await bank.get_currency_name(guild)
|
||||
await bank.set_default_balance(creds, guild)
|
||||
await ctx.send(_("Registering an account will now give {} {}."
|
||||
"").format(creds, credits_name))
|
||||
|
||||
# What would I ever do without stackoverflow?
|
||||
def display_time(self, seconds, granularity=2):
|
||||
intervals = ( # Source: http://stackoverflow.com/a/24542445
|
||||
(_('weeks'), 604800), # 60 * 60 * 24 * 7
|
||||
(_('days'), 86400), # 60 * 60 * 24
|
||||
(_('hours'), 3600), # 60 * 60
|
||||
(_('minutes'), 60),
|
||||
(_('seconds'), 1),
|
||||
)
|
||||
|
||||
result = []
|
||||
|
||||
for name, count in intervals:
|
||||
value = seconds // count
|
||||
if value:
|
||||
seconds -= value * count
|
||||
if value == 1:
|
||||
name = name.rstrip('s')
|
||||
result.append("{} {}".format(value, name))
|
||||
return ', '.join(result[:granularity])
|
||||
225
redbot/cogs/economy/locales/es.po
Normal file
225
redbot/cogs/economy/locales/es.po
Normal file
@@ -0,0 +1,225 @@
|
||||
# Copyright (C) 2017 Red-DiscordBot
|
||||
# UltimatePancake <pier.gaetani@gmail.com>, 2017.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: 2017-08-26 17:40+EDT\n"
|
||||
"PO-Revision-Date: 2017-08-26 20:48-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"
|
||||
|
||||
#: ../economy.py:38
|
||||
msgid "JACKPOT! 226! Your bid has been multiplied * 2500!"
|
||||
msgstr "¡PREMIO MAYOR! ¡226! ¡Apuesta multiplicada por 2500!"
|
||||
|
||||
#: ../economy.py:42
|
||||
msgid "4LC! +1000!"
|
||||
msgstr "¡4 tréboles! ¡+1000!"
|
||||
|
||||
#: ../economy.py:46
|
||||
msgid "Three cherries! +800!"
|
||||
msgstr "¡Triple cereza! ¡+800!"
|
||||
|
||||
#: ../economy.py:50
|
||||
msgid "2 6! Your bid has been multiplied * 4!"
|
||||
msgstr "¡2 6! ¡Apuesta multiplicada por 4!"
|
||||
|
||||
#: ../economy.py:54
|
||||
msgid "Two cherries! Your bid has been multiplied * 3!"
|
||||
msgstr "¡Doble cereza! ¡Apuesta multiplicada por 3!"
|
||||
|
||||
#: ../economy.py:58
|
||||
msgid "Three symbols! +500!"
|
||||
msgstr "¡Tres símbolos! ¡+500!"
|
||||
|
||||
#: ../economy.py:62
|
||||
msgid "Two consecutive symbols! Your bid has been multiplied * 2!"
|
||||
msgstr "¡Dos símbolos consecutivos! ¡Apuesta multiplicada por 2!"
|
||||
|
||||
#: ../economy.py:66
|
||||
msgid ""
|
||||
"Slot machine payouts:\n"
|
||||
"{two.value} {two.value} {six.value} Bet * 2500\n"
|
||||
"{flc.value} {flc.value} {flc.value} +1000\n"
|
||||
"{cherries.value} {cherries.value} {cherries.value} +800\n"
|
||||
"{two.value} {six.value} Bet * 4\n"
|
||||
"{cherries.value} {cherries.value} Bet * 3\n"
|
||||
"\n"
|
||||
"Three symbols: +500\n"
|
||||
"Two symbols: Bet * 2"
|
||||
msgstr ""
|
||||
"Premios de tragaperras:\n"
|
||||
"{two.value} {two.value} {six.value} Apuesta * 2500\n"
|
||||
"{flc.value} {flc.value} {flc.value} +1000\n"
|
||||
"{cherries.value} {cherries.value} {cherries.value} +800\n"
|
||||
"{two.value} {six.value} Apuesta * 4\n"
|
||||
"{cherries.value} {cherries.value} Apuesta * 3\n"
|
||||
"\n"
|
||||
"Tres símbolos: +500\n"
|
||||
"Dos símbolos: Apuesta * 2"
|
||||
|
||||
#: ../economy.py:155
|
||||
msgid "{}'s balance is {} {}"
|
||||
msgstr "El balance de {} es {} {}"
|
||||
|
||||
#: ../economy.py:169
|
||||
msgid "{} transferred {} {} to {}"
|
||||
msgstr "{} ha transferido {} {} a {}"
|
||||
|
||||
#: ../economy.py:189
|
||||
msgid "{} added {} {} to {}'s account."
|
||||
msgstr "{} agregó {} {} a la cuenta de {}."
|
||||
|
||||
#: ../economy.py:194
|
||||
msgid "{} removed {} {} from {}'s account."
|
||||
msgstr "{} quitó {} {} de la cuenta de {}."
|
||||
|
||||
#: ../economy.py:199
|
||||
msgid "{} set {}'s account to {} {}."
|
||||
msgstr "{} puso la cuenta de {} a {} {}."
|
||||
|
||||
#: ../economy.py:210
|
||||
msgid ""
|
||||
"This will delete all bank accounts for {}.\n"
|
||||
"If you're sure, type {}bank reset yes"
|
||||
msgstr ""
|
||||
"Esto eliminará todas las cuentas bancarias de {}.\n"
|
||||
"Si estás seguro, escribe {}bank reset yes"
|
||||
|
||||
#: ../economy.py:227
|
||||
msgid "All bank accounts of this guild have been deleted."
|
||||
msgstr "Todas las cuentas bancarias de este gremio han sido eliminadas."
|
||||
|
||||
#: ../economy.py:246 ../economy.py:266
|
||||
msgid "{} Here, take some {}. Enjoy! (+{} {}!)"
|
||||
msgstr "{}, toma, unos {}. Difruta! (+{} {}!)"
|
||||
|
||||
#: ../economy.py:256 ../economy.py:274
|
||||
msgid "{} Too soon. For your next payday you have to wait {}."
|
||||
msgstr "{} Muy pronto. Tu siguiente dia de pago es en {}."
|
||||
|
||||
#: ../economy.py:311
|
||||
msgid "There are no accounts in the bank."
|
||||
msgstr "No existen cuentas en el banco."
|
||||
|
||||
#: ../economy.py:337
|
||||
msgid "You're on cooldown, try again in a bit."
|
||||
msgstr "Estás en tiempo de espera, intenta de nuevo más tarde."
|
||||
|
||||
#: ../economy.py:340
|
||||
msgid "That's an invalid bid amount, sorry :/"
|
||||
msgstr "Cantidad de apuesta inválida, lo siento :/"
|
||||
|
||||
#: ../economy.py:343
|
||||
msgid "You ain't got enough money, friend."
|
||||
msgstr "No tienes suficiente dinero, amigo."
|
||||
|
||||
# dafuq is {} â {}!
|
||||
#: ../economy.py:389
|
||||
msgid ""
|
||||
"{}\n"
|
||||
"{} {}\n"
|
||||
"\n"
|
||||
"Your bid: {}\n"
|
||||
"{} → {}!"
|
||||
msgstr ""
|
||||
"{}\n"
|
||||
"{} {}\n"
|
||||
"Tu apuesta: {}\n"
|
||||
"{} → {}!"
|
||||
|
||||
#: ../economy.py:396
|
||||
msgid ""
|
||||
"{}\n"
|
||||
"{} Nothing!\n"
|
||||
"Your bid: {}\n"
|
||||
"{} → {}!"
|
||||
msgstr ""
|
||||
"{}\n"
|
||||
"{} Nada!\n"
|
||||
"Tu apuesta: {}\n"
|
||||
"{} → {}!"
|
||||
|
||||
#: ../economy.py:421
|
||||
msgid ""
|
||||
"Minimum slot bid: {}\n"
|
||||
"Maximum slot bid: {}\n"
|
||||
"Slot cooldown: {}\n"
|
||||
"Payday amount: {}\n"
|
||||
"Payday cooldown: {}\n"
|
||||
"Amount given at account registration: {}"
|
||||
msgstr ""
|
||||
"Apuesta mínima para tragaperras: {}\n"
|
||||
"Apuesta máxima para tragaperras: {}\n"
|
||||
"Tiempo de espera para tragaperras: {}\n"
|
||||
"Cantidad para día de pago: {}\n"
|
||||
"Tiempo de espera para día de pago: {}\n"
|
||||
"Cantidad al registrar: {}"
|
||||
|
||||
#: ../economy.py:431
|
||||
msgid "Current Economy settings:"
|
||||
msgstr "Configuración de economía actual:"
|
||||
|
||||
#: ../economy.py:439
|
||||
msgid "Invalid min bid amount."
|
||||
msgstr "Cantidad mínima de apuesta inválida."
|
||||
|
||||
#: ../economy.py:447
|
||||
msgid "Minimum bid is now {} {}."
|
||||
msgstr "Apuesta mínima es ahora {} {}."
|
||||
|
||||
#: ../economy.py:454
|
||||
msgid "Invalid slotmax bid amount. Must be greater than slotmin."
|
||||
msgstr "Cantidad de apuesta máxima para tragaperras inválido. Debe ser mayor al mínimo."
|
||||
|
||||
#: ../economy.py:463
|
||||
msgid "Maximum bid is now {} {}."
|
||||
msgstr "Apuesta máxima es ahora {} {}."
|
||||
|
||||
#: ../economy.py:473
|
||||
msgid "Cooldown is now {} seconds."
|
||||
msgstr "Tiempo de espera es ahora {} segundos."
|
||||
|
||||
#: ../economy.py:483
|
||||
msgid "Value modified. At least {} seconds must pass between each payday."
|
||||
msgstr "Valor modificado. Al menos {} segundos deben de pasar entre cada día de pago."
|
||||
|
||||
#: ../economy.py:492
|
||||
msgid "Har har so funny."
|
||||
msgstr "Muy gracioso..."
|
||||
|
||||
#: ../economy.py:498
|
||||
msgid "Every payday will now give {} {}."
|
||||
msgstr "Cada día de pago ahora dará {} {}."
|
||||
|
||||
#: ../economy.py:509
|
||||
msgid "Registering an account will now give {} {}."
|
||||
msgstr "Registrar una cuenta bancaria ahora dará {} {}."
|
||||
|
||||
#: ../economy.py:515
|
||||
msgid "weeks"
|
||||
msgstr "semanas"
|
||||
|
||||
#: ../economy.py:516
|
||||
msgid "days"
|
||||
msgstr "días"
|
||||
|
||||
#: ../economy.py:517
|
||||
msgid "hours"
|
||||
msgstr "horas"
|
||||
|
||||
#: ../economy.py:518
|
||||
msgid "minutes"
|
||||
msgstr "minutos"
|
||||
|
||||
#: ../economy.py:519
|
||||
msgid "seconds"
|
||||
msgstr "segundos"
|
||||
199
redbot/cogs/economy/locales/messages.pot
Normal file
199
redbot/cogs/economy/locales/messages.pot
Normal file
@@ -0,0 +1,199 @@
|
||||
# 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:40+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"
|
||||
|
||||
|
||||
#: ../economy.py:38
|
||||
msgid "JACKPOT! 226! Your bid has been multiplied * 2500!"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:42
|
||||
msgid "4LC! +1000!"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:46
|
||||
msgid "Three cherries! +800!"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:50
|
||||
msgid "2 6! Your bid has been multiplied * 4!"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:54
|
||||
msgid "Two cherries! Your bid has been multiplied * 3!"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:58
|
||||
msgid "Three symbols! +500!"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:62
|
||||
msgid "Two consecutive symbols! Your bid has been multiplied * 2!"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:66
|
||||
msgid ""
|
||||
"Slot machine payouts:\n"
|
||||
"{two.value} {two.value} {six.value} Bet * 2500\n"
|
||||
"{flc.value} {flc.value} {flc.value} +1000\n"
|
||||
"{cherries.value} {cherries.value} {cherries.value} +800\n"
|
||||
"{two.value} {six.value} Bet * 4\n"
|
||||
"{cherries.value} {cherries.value} Bet * 3\n"
|
||||
"\n"
|
||||
"Three symbols: +500\n"
|
||||
"Two symbols: Bet * 2"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:155
|
||||
msgid "{}'s balance is {} {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:169
|
||||
msgid "{} transferred {} {} to {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:189
|
||||
msgid "{} added {} {} to {}'s account."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:194
|
||||
msgid "{} removed {} {} from {}'s account."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:199
|
||||
msgid "{} set {}'s account to {} {}."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:210
|
||||
msgid ""
|
||||
"This will delete all bank accounts for {}.\n"
|
||||
"If you're sure, type {}bank reset yes"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:227
|
||||
msgid "All bank accounts of this guild have been deleted."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:246 ../economy.py:266
|
||||
msgid "{} Here, take some {}. Enjoy! (+{} {}!)"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:256 ../economy.py:274
|
||||
msgid "{} Too soon. For your next payday you have to wait {}."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:311
|
||||
msgid "There are no accounts in the bank."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:337
|
||||
msgid "You're on cooldown, try again in a bit."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:340
|
||||
msgid "That's an invalid bid amount, sorry :/"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:343
|
||||
msgid "You ain't got enough money, friend."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:389
|
||||
msgid ""
|
||||
"{}\n"
|
||||
"{} {}\n"
|
||||
"\n"
|
||||
"Your bid: {}\n"
|
||||
"{} → {}!"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:396
|
||||
msgid ""
|
||||
"{}\n"
|
||||
"{} Nothing!\n"
|
||||
"Your bid: {}\n"
|
||||
"{} → {}!"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:421
|
||||
msgid ""
|
||||
"Minimum slot bid: {}\n"
|
||||
"Maximum slot bid: {}\n"
|
||||
"Slot cooldown: {}\n"
|
||||
"Payday amount: {}\n"
|
||||
"Payday cooldown: {}\n"
|
||||
"Amount given at account registration: {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:431
|
||||
msgid "Current Economy settings:"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:439
|
||||
msgid "Invalid min bid amount."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:447
|
||||
msgid "Minimum bid is now {} {}."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:454
|
||||
msgid "Invalid slotmax bid amount. Must be greater than slotmin."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:463
|
||||
msgid "Maximum bid is now {} {}."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:473
|
||||
msgid "Cooldown is now {} seconds."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:483
|
||||
msgid "Value modified. At least {} seconds must pass between each payday."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:492
|
||||
msgid "Har har so funny."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:498
|
||||
msgid "Every payday will now give {} {}."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:509
|
||||
msgid "Registering an account will now give {} {}."
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:515
|
||||
msgid "weeks"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:516
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:517
|
||||
msgid "hours"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:518
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
|
||||
#: ../economy.py:519
|
||||
msgid "seconds"
|
||||
msgstr ""
|
||||
|
||||
5
redbot/cogs/general/__init__.py
Normal file
5
redbot/cogs/general/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .general import General
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(General())
|
||||
340
redbot/cogs/general/general.py
Normal file
340
redbot/cogs/general/general.py
Normal file
@@ -0,0 +1,340 @@
|
||||
import datetime
|
||||
import time
|
||||
from enum import Enum
|
||||
from random import randint, choice
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
import aiohttp
|
||||
import discord
|
||||
from redbot.core.i18n import CogI18n
|
||||
from discord.ext import commands
|
||||
|
||||
from redbot.core.utils.chat_formatting import escape, italics, pagify
|
||||
|
||||
_ = CogI18n("General", __file__)
|
||||
|
||||
|
||||
class RPS(Enum):
|
||||
rock = "\N{MOYAI}"
|
||||
paper = "\N{PAGE FACING UP}"
|
||||
scissors = "\N{BLACK SCISSORS}"
|
||||
|
||||
|
||||
class RPSParser:
|
||||
def __init__(self, argument):
|
||||
argument = argument.lower()
|
||||
if argument == "rock":
|
||||
self.choice = RPS.rock
|
||||
elif argument == "paper":
|
||||
self.choice = RPS.paper
|
||||
elif argument == "scissors":
|
||||
self.choice = RPS.scissors
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
class General:
|
||||
"""General commands."""
|
||||
|
||||
def __init__(self):
|
||||
self.stopwatches = {}
|
||||
self.ball = [
|
||||
_("As I see it, yes"), _("It is certain"), _("It is decidedly so"),
|
||||
_("Most likely"), _("Outlook good"), _("Signs point to yes"),
|
||||
_("Without a doubt"), _("Yes"), _("Yes – definitely"), _("You may rely on it"),
|
||||
_("Reply hazy, try again"), _("Ask again later"),
|
||||
_("Better not tell you now"), _("Cannot predict now"),
|
||||
_("Concentrate and ask again"), _("Don't count on it"), _("My reply is no"),
|
||||
_("My sources say no"), _("Outlook not so good"), _("Very doubtful")
|
||||
]
|
||||
|
||||
@commands.command(hidden=True)
|
||||
async def ping(self, ctx):
|
||||
"""Pong."""
|
||||
await ctx.send("Pong.")
|
||||
|
||||
@commands.command()
|
||||
async def choose(self, ctx, *choices):
|
||||
"""Chooses between multiple choices.
|
||||
|
||||
To denote multiple choices, you should use double quotes.
|
||||
"""
|
||||
choices = [escape(c, mass_mentions=True) for c in choices]
|
||||
if len(choices) < 2:
|
||||
await ctx.send(_('Not enough choices to pick from.'))
|
||||
else:
|
||||
await ctx.send(choice(choices))
|
||||
|
||||
@commands.command()
|
||||
async def roll(self, ctx, number : int = 100):
|
||||
"""Rolls random number (between 1 and user choice)
|
||||
|
||||
Defaults to 100.
|
||||
"""
|
||||
author = ctx.author
|
||||
if number > 1:
|
||||
n = randint(1, number)
|
||||
await ctx.send(
|
||||
_("{} :game_die: {} :game_die:").format(author.mention, n)
|
||||
)
|
||||
else:
|
||||
await ctx.send(_("{} Maybe higher than 1? ;P").format(author.mention))
|
||||
|
||||
@commands.command()
|
||||
async def flip(self, ctx, user: discord.Member=None):
|
||||
"""Flips a coin... or a user.
|
||||
|
||||
Defaults to coin.
|
||||
"""
|
||||
if user != None:
|
||||
msg = ""
|
||||
if user.id == ctx.bot.user.id:
|
||||
user = ctx.author
|
||||
msg = _("Nice try. You think this is funny?"
|
||||
"How about *this* instead:\n\n")
|
||||
char = "abcdefghijklmnopqrstuvwxyz"
|
||||
tran = "ɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz"
|
||||
table = str.maketrans(char, tran)
|
||||
name = user.display_name.translate(table)
|
||||
char = char.upper()
|
||||
tran = "∀qƆpƎℲפHIſʞ˥WNOԀQᴚS┴∩ΛMX⅄Z"
|
||||
table = str.maketrans(char, tran)
|
||||
name = name.translate(table)
|
||||
await ctx.send(msg + "(╯°□°)╯︵ " + name[::-1])
|
||||
else:
|
||||
await ctx.send(
|
||||
_("*flips a coin and... ") + choice([_("HEADS!*"), _("TAILS!*")])
|
||||
)
|
||||
|
||||
@commands.command()
|
||||
async def rps(self, ctx, your_choice : RPSParser):
|
||||
"""Play rock paper scissors"""
|
||||
author = ctx.author
|
||||
player_choice = your_choice.choice
|
||||
red_choice = choice((RPS.rock, RPS.paper, RPS.scissors))
|
||||
cond = {
|
||||
(RPS.rock, RPS.paper) : False,
|
||||
(RPS.rock, RPS.scissors) : True,
|
||||
(RPS.paper, RPS.rock) : True,
|
||||
(RPS.paper, RPS.scissors) : False,
|
||||
(RPS.scissors, RPS.rock) : False,
|
||||
(RPS.scissors, RPS.paper) : True
|
||||
}
|
||||
|
||||
if red_choice == player_choice:
|
||||
outcome = None # Tie
|
||||
else:
|
||||
outcome = cond[(player_choice, red_choice)]
|
||||
|
||||
if outcome is True:
|
||||
await ctx.send(_("{} You win {}!").format(
|
||||
red_choice.value, author.mention
|
||||
))
|
||||
elif outcome is False:
|
||||
await ctx.send(_("{} You lose {}!").format(
|
||||
red_choice.value, author.mention
|
||||
))
|
||||
else:
|
||||
await ctx.send(_("{} We're square {}!").format(
|
||||
red_choice.value, author.mention
|
||||
))
|
||||
|
||||
@commands.command(name="8", aliases=["8ball"])
|
||||
async def _8ball(self, ctx, *, question : str):
|
||||
"""Ask 8 ball a question
|
||||
|
||||
Question must end with a question mark.
|
||||
"""
|
||||
if question.endswith("?") and question != "?":
|
||||
await ctx.send("`" + choice(self.ball) + "`")
|
||||
else:
|
||||
await ctx.send(_("That doesn't look like a question."))
|
||||
|
||||
@commands.command(aliases=["sw"])
|
||||
async def stopwatch(self, ctx):
|
||||
"""Starts/stops stopwatch"""
|
||||
author = ctx.author
|
||||
if not author.id in self.stopwatches:
|
||||
self.stopwatches[author.id] = int(time.perf_counter())
|
||||
await ctx.send(author.mention + _(" Stopwatch started!"))
|
||||
else:
|
||||
tmp = abs(self.stopwatches[author.id] - int(time.perf_counter()))
|
||||
tmp = str(datetime.timedelta(seconds=tmp))
|
||||
await ctx.send(author.mention + _(" Stopwatch stopped! Time: **") + tmp + "**")
|
||||
self.stopwatches.pop(author.id, None)
|
||||
|
||||
@commands.command()
|
||||
async def lmgtfy(self, ctx, *, search_terms : str):
|
||||
"""Creates a lmgtfy link"""
|
||||
search_terms = escape(search_terms.replace(" ", "+"), mass_mentions=True)
|
||||
await ctx.send("https://lmgtfy.com/?q={}".format(search_terms))
|
||||
|
||||
@commands.command(hidden=True)
|
||||
@commands.guild_only()
|
||||
async def hug(self, ctx, user : discord.Member, intensity : int=1):
|
||||
"""Because everyone likes hugs
|
||||
|
||||
Up to 10 intensity levels."""
|
||||
name = italics(user.display_name)
|
||||
if intensity <= 0:
|
||||
msg = "(っ˘̩╭╮˘̩)っ" + name
|
||||
elif intensity <= 3:
|
||||
msg = "(っ´▽`)っ" + name
|
||||
elif intensity <= 6:
|
||||
msg = "╰(*´︶`*)╯" + name
|
||||
elif intensity <= 9:
|
||||
msg = "(つ≧▽≦)つ" + name
|
||||
elif intensity >= 10:
|
||||
msg = "(づ ̄ ³ ̄)づ{} ⊂(´・ω・`⊂)".format(name)
|
||||
await ctx.send(msg)
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
async def userinfo(self, ctx, *, user: discord.Member=None):
|
||||
"""Shows users's informations"""
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
|
||||
if not user:
|
||||
user = author
|
||||
|
||||
# A special case for a special someone :^)
|
||||
special_date = datetime.datetime(2016, 1, 10, 6, 8, 4, 443000)
|
||||
is_special = (user.id == 96130341705637888 and
|
||||
guild.id == 133049272517001216)
|
||||
|
||||
roles = sorted(user.roles)[1:]
|
||||
|
||||
joined_at = user.joined_at if not is_special else special_date
|
||||
since_created = (ctx.message.created_at - user.created_at).days
|
||||
since_joined = (ctx.message.created_at - joined_at).days
|
||||
user_joined = joined_at.strftime("%d %b %Y %H:%M")
|
||||
user_created = user.created_at.strftime("%d %b %Y %H:%M")
|
||||
member_number = sorted(guild.members,
|
||||
key=lambda m: m.joined_at).index(user) + 1
|
||||
|
||||
created_on = _("{}\n({} days ago)").format(user_created, since_created)
|
||||
joined_on = _("{}\n({} days ago)").format(user_joined, since_joined)
|
||||
|
||||
game = _("Chilling in {} status").format(user.status)
|
||||
|
||||
if user.game and user.game.name and user.game.url:
|
||||
game = _("Streaming: [{}]({})").format(user.game, user.game.url)
|
||||
elif user.game and user.game.name:
|
||||
game = _("Playing {}").format(user.game)
|
||||
|
||||
if roles:
|
||||
roles = ", ".join([x.name for x in roles])
|
||||
else:
|
||||
roles = _("None")
|
||||
|
||||
data = discord.Embed(description=game, colour=user.colour)
|
||||
data.add_field(name=_("Joined Discord on"), value=created_on)
|
||||
data.add_field(name=_("Joined this guild on"), value=joined_on)
|
||||
data.add_field(name=_("Roles"), value=roles, inline=False)
|
||||
data.set_footer(text=_("Member #{} | User ID: {}"
|
||||
"").format(member_number, user.id))
|
||||
|
||||
name = str(user)
|
||||
name = " ~ ".join((name, user.nick)) if user.nick else name
|
||||
|
||||
if user.avatar:
|
||||
data.set_author(name=name, url=user.avatar_url)
|
||||
data.set_thumbnail(url=user.avatar_url)
|
||||
else:
|
||||
data.set_author(name=name)
|
||||
|
||||
try:
|
||||
await ctx.send(embed=data)
|
||||
except discord.HTTPException:
|
||||
await ctx.send(_("I need the `Embed links` permission "
|
||||
"to send this."))
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
async def serverinfo(self, ctx):
|
||||
"""Shows guild's informations"""
|
||||
guild = ctx.guild
|
||||
online = len([m.status for m in guild.members
|
||||
if m.status == discord.Status.online or
|
||||
m.status == discord.Status.idle])
|
||||
total_users = len(guild.members)
|
||||
text_channels = len(guild.text_channels)
|
||||
voice_channels = len(guild.voice_channels)
|
||||
passed = (ctx.message.created_at - guild.created_at).days
|
||||
created_at = (_("Since {}. That's over {} days ago!"
|
||||
"").format(guild.created_at.strftime("%d %b %Y %H:%M"),
|
||||
passed))
|
||||
|
||||
colour = ''.join([choice('0123456789ABCDEF') for x in range(6)])
|
||||
colour = randint(0, 0xFFFFFF)
|
||||
|
||||
data = discord.Embed(
|
||||
description=created_at,
|
||||
colour=discord.Colour(value=colour))
|
||||
data.add_field(name=_("Region"), value=str(guild.region))
|
||||
data.add_field(name=_("Users"), value="{}/{}".format(online, total_users))
|
||||
data.add_field(name=_("Text Channels"), value=text_channels)
|
||||
data.add_field(name=_("Voice Channels"), value=voice_channels)
|
||||
data.add_field(name=_("Roles"), value=len(guild.roles))
|
||||
data.add_field(name=_("Owner"), value=str(guild.owner))
|
||||
data.set_footer(text=_("Guild ID: ") + str(guild.id))
|
||||
|
||||
if guild.icon_url:
|
||||
data.set_author(name=guild.name, url=guild.icon_url)
|
||||
data.set_thumbnail(url=guild.icon_url)
|
||||
else:
|
||||
data.set_author(name=guild.name)
|
||||
|
||||
try:
|
||||
await ctx.send(embed=data)
|
||||
except discord.HTTPException:
|
||||
await ctx.send(_("I need the `Embed links` permission "
|
||||
"to send this."))
|
||||
|
||||
@commands.command()
|
||||
async def urban(self, ctx, *, search_terms: str, definition_number: int=1):
|
||||
"""Urban Dictionary search
|
||||
|
||||
Definition number must be between 1 and 10"""
|
||||
def encode(s):
|
||||
return quote_plus(s, encoding='utf-8', errors='replace')
|
||||
|
||||
# definition_number is just there to show up in the help
|
||||
# all this mess is to avoid forcing double quotes on the user
|
||||
|
||||
search_terms = search_terms.split(" ")
|
||||
try:
|
||||
if len(search_terms) > 1:
|
||||
pos = int(search_terms[-1]) - 1
|
||||
search_terms = search_terms[:-1]
|
||||
else:
|
||||
pos = 0
|
||||
if pos not in range(0, 11): # API only provides the
|
||||
pos = 0 # top 10 definitions
|
||||
except ValueError:
|
||||
pos = 0
|
||||
|
||||
search_terms = {"term": "+".join([s for s in search_terms])}
|
||||
url = "http://api.urbandictionary.com/v0/define"
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url, params=search_terms) as r:
|
||||
result = await r.json()
|
||||
item_list = result["list"]
|
||||
if item_list:
|
||||
definition = item_list[pos]['definition']
|
||||
example = item_list[pos]['example']
|
||||
defs = len(item_list)
|
||||
msg = ("**Definition #{} out of {}:\n**{}\n\n"
|
||||
"**Example:\n**{}".format(pos+1, defs, definition,
|
||||
example))
|
||||
msg = pagify(msg, ["\n"])
|
||||
for page in msg:
|
||||
await ctx.send(page)
|
||||
else:
|
||||
await ctx.send(_("Your search terms gave no results."))
|
||||
except IndexError:
|
||||
await ctx.send(_("There is no definition #{}").format(pos+1))
|
||||
except:
|
||||
await ctx.send(_("Error."))
|
||||
237
redbot/cogs/general/locales/es.po
Normal file
237
redbot/cogs/general/locales/es.po
Normal file
@@ -0,0 +1,237 @@
|
||||
# Copyright (C) 2017 Red-DiscordBot
|
||||
# UltimatePancake <pier.gaetani@gmail.com>, 2017.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: 2017-08-26 17:50+EDT\n"
|
||||
"PO-Revision-Date: 2017-08-26 20:26-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"
|
||||
|
||||
#: ../general.py:40
|
||||
msgid "As I see it, yes"
|
||||
msgstr "Como lo veo, si"
|
||||
|
||||
#: ../general.py:40
|
||||
msgid "It is certain"
|
||||
msgstr "Es cierto"
|
||||
|
||||
#: ../general.py:40
|
||||
msgid "It is decidedly so"
|
||||
msgstr "Decididamente"
|
||||
|
||||
#: ../general.py:41
|
||||
msgid "Most likely"
|
||||
msgstr "Probablemente"
|
||||
|
||||
#: ../general.py:41
|
||||
msgid "Outlook good"
|
||||
msgstr "El panorama se ve bien"
|
||||
|
||||
#: ../general.py:41
|
||||
msgid "Signs point to yes"
|
||||
msgstr "Todo apunta a sí"
|
||||
|
||||
#: ../general.py:42
|
||||
msgid "Without a doubt"
|
||||
msgstr "Sin duda alguna"
|
||||
|
||||
#: ../general.py:42
|
||||
msgid "Yes"
|
||||
msgstr "Sí"
|
||||
|
||||
#: ../general.py:42
|
||||
msgid "Yes – definitely"
|
||||
msgstr "Sí – definitivamente"
|
||||
|
||||
#: ../general.py:42
|
||||
msgid "You may rely on it"
|
||||
msgstr "Puedes contar con ello"
|
||||
|
||||
#: ../general.py:43
|
||||
msgid "Ask again later"
|
||||
msgstr "Pregunta más tarde"
|
||||
|
||||
#: ../general.py:43
|
||||
msgid "Reply hazy, try again"
|
||||
msgstr "Respuesta borrosa, intenta de nuevo"
|
||||
|
||||
#: ../general.py:44
|
||||
msgid "Better not tell you now"
|
||||
msgstr "Mejor no te digo en este momento"
|
||||
|
||||
#: ../general.py:44
|
||||
msgid "Cannot predict now"
|
||||
msgstr "No puedo predecir en este momento"
|
||||
|
||||
#: ../general.py:45
|
||||
msgid "Concentrate and ask again"
|
||||
msgstr "Concéntrate y pregunta de nuevo"
|
||||
|
||||
#: ../general.py:45
|
||||
msgid "Don't count on it"
|
||||
msgstr "No cuentes con ello"
|
||||
|
||||
#: ../general.py:45
|
||||
msgid "My reply is no"
|
||||
msgstr "Mi respuesta es no"
|
||||
|
||||
#: ../general.py:46
|
||||
msgid "My sources say no"
|
||||
msgstr "Mis fuentes dicen no"
|
||||
|
||||
#: ../general.py:46
|
||||
msgid "Outlook not so good"
|
||||
msgstr "El panorama no se ve bien"
|
||||
|
||||
#: ../general.py:46
|
||||
msgid "Very doubtful"
|
||||
msgstr "Lo dudo mucho"
|
||||
|
||||
#: ../general.py:62
|
||||
msgid "Not enough choices to pick from."
|
||||
msgstr "Insuficientes opciones para elegir"
|
||||
|
||||
#: ../general.py:76
|
||||
msgid "{} :game_die: {} :game_die:"
|
||||
msgstr "{} :game_die: {} :game_die:"
|
||||
|
||||
#: ../general.py:79
|
||||
msgid "{} ¿Maybe higher than 1? ;P"
|
||||
msgstr "{} ¿Tal vez más que 1? ;P"
|
||||
|
||||
#: ../general.py:91
|
||||
msgid ""
|
||||
"Nice try. You think this is funny?How about *this* instead:\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
"Buen intento. ¿Te parece gracioso? Qué tal *esto* mejor:\n"
|
||||
"\n"
|
||||
|
||||
#: ../general.py:104
|
||||
msgid "*flips a coin and... "
|
||||
msgstr "*tira una moneda y..."
|
||||
|
||||
#: ../general.py:104
|
||||
msgid "HEADS!*"
|
||||
msgstr "¡CARA!*"
|
||||
|
||||
#: ../general.py:104
|
||||
msgid "TAILS!*"
|
||||
msgstr "¡CRUZ!*"
|
||||
|
||||
#: ../general.py:128
|
||||
msgid "{} You win {}!"
|
||||
msgstr "{} ¡Ganas {}!"
|
||||
|
||||
#: ../general.py:132
|
||||
msgid "{} You lose {}!"
|
||||
msgstr "{} ¡Pierdes {}!"
|
||||
|
||||
#: ../general.py:136
|
||||
msgid "{} We're square {}!"
|
||||
msgstr "{} ¡Empates {}!"
|
||||
|
||||
#: ../general.py:149
|
||||
msgid "That doesn't look like a question."
|
||||
msgstr "Eso no parece pregunta."
|
||||
|
||||
#: ../general.py:157
|
||||
msgid " Stopwatch started!"
|
||||
msgstr " ¡Cronómetro comenzado!"
|
||||
|
||||
#: ../general.py:161
|
||||
msgid " Stopwatch stopped! Time: **"
|
||||
msgstr " ¡Cronómetro detenido! Tiempo: **"
|
||||
|
||||
#: ../general.py:214 ../general.py:215
|
||||
msgid ""
|
||||
"{}\n"
|
||||
"({} days ago)"
|
||||
msgstr ""
|
||||
"{}\n"
|
||||
"(Hace {} días)"
|
||||
|
||||
#: ../general.py:217
|
||||
msgid "Chilling in {} status"
|
||||
msgstr "Ahí no mas en estado {}"
|
||||
|
||||
#: ../general.py:220
|
||||
msgid "Streaming: [{}]({})"
|
||||
msgstr "Transmitiendo: [{}]({})"
|
||||
|
||||
#: ../general.py:222
|
||||
msgid "Playing {}"
|
||||
msgstr "Jugando {}"
|
||||
|
||||
#: ../general.py:227
|
||||
msgid "None"
|
||||
msgstr "Nada"
|
||||
|
||||
#: ../general.py:230
|
||||
msgid "Joined Discord on"
|
||||
msgstr "Registrado a Discord en"
|
||||
|
||||
#: ../general.py:231
|
||||
msgid "Joined this guild on"
|
||||
msgstr "Registrado a gremio en"
|
||||
|
||||
#: ../general.py:232 ../general.py:277
|
||||
msgid "Roles"
|
||||
msgstr "Roles"
|
||||
|
||||
#: ../general.py:233
|
||||
msgid "Member #{} | User ID: {}"
|
||||
msgstr "Miembro #{} | ID de usuario: {}"
|
||||
|
||||
#: ../general.py:248 ../general.py:290
|
||||
msgid "I need the `Embed links` permission to send this."
|
||||
msgstr "Necesito el permiso `Insertar Enlaces` para enviar esto."
|
||||
|
||||
#: ../general.py:263
|
||||
msgid "Since {}. That's over {} days ago!"
|
||||
msgstr "Desde {}. Hace {} días!"
|
||||
|
||||
#: ../general.py:273
|
||||
msgid "Region"
|
||||
msgstr "Región"
|
||||
|
||||
#: ../general.py:274
|
||||
msgid "Users"
|
||||
msgstr "Usuarios"
|
||||
|
||||
#: ../general.py:275
|
||||
msgid "Text Channels"
|
||||
msgstr "Canales de texto"
|
||||
|
||||
#: ../general.py:276
|
||||
msgid "Voice Channels"
|
||||
msgstr "Canalez de voz"
|
||||
|
||||
#: ../general.py:278
|
||||
msgid "Owner"
|
||||
msgstr "Dueño"
|
||||
|
||||
#: ../general.py:279
|
||||
msgid "Guild ID: "
|
||||
msgstr "ID de gremio:"
|
||||
|
||||
#: ../general.py:334
|
||||
msgid "Your search terms gave no results."
|
||||
msgstr "Tu búsqueda no ha dado resultados."
|
||||
|
||||
#: ../general.py:336
|
||||
msgid "There is no definition #{}"
|
||||
msgstr "No existe la definición #{}"
|
||||
|
||||
#: ../general.py:338
|
||||
msgid "Error."
|
||||
msgstr "Error."
|
||||
233
redbot/cogs/general/locales/messages.pot
Normal file
233
redbot/cogs/general/locales/messages.pot
Normal file
@@ -0,0 +1,233 @@
|
||||
# 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:50+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"
|
||||
|
||||
|
||||
#: ../general.py:40
|
||||
msgid "As I see it, yes"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:40
|
||||
msgid "It is certain"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:40
|
||||
msgid "It is decidedly so"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:41
|
||||
msgid "Most likely"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:41
|
||||
msgid "Outlook good"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:41
|
||||
msgid "Signs point to yes"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:42
|
||||
msgid "Without a doubt"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:42
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:42
|
||||
msgid "Yes – definitely"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:42
|
||||
msgid "You may rely on it"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:43
|
||||
msgid "Ask again later"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:43
|
||||
msgid "Reply hazy, try again"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:44
|
||||
msgid "Better not tell you now"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:44
|
||||
msgid "Cannot predict now"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:45
|
||||
msgid "Concentrate and ask again"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:45
|
||||
msgid "Don't count on it"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:45
|
||||
msgid "My reply is no"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:46
|
||||
msgid "My sources say no"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:46
|
||||
msgid "Outlook not so good"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:46
|
||||
msgid "Very doubtful"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:62
|
||||
msgid "Not enough choices to pick from."
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:76
|
||||
msgid "{} :game_die: {} :game_die:"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:79
|
||||
msgid "{} Maybe higher than 1? ;P"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:91
|
||||
msgid ""
|
||||
"Nice try. You think this is funny?How about *this* instead:\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:104
|
||||
msgid "*flips a coin and... "
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:104
|
||||
msgid "HEADS!*"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:104
|
||||
msgid "TAILS!*"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:128
|
||||
msgid "{} You win {}!"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:132
|
||||
msgid "{} You lose {}!"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:136
|
||||
msgid "{} We're square {}!"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:149
|
||||
msgid "That doesn't look like a question."
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:157
|
||||
msgid " Stopwatch started!"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:161
|
||||
msgid " Stopwatch stopped! Time: **"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:214 ../general.py:215
|
||||
msgid ""
|
||||
"{}\n"
|
||||
"({} days ago)"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:217
|
||||
msgid "Chilling in {} status"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:220
|
||||
msgid "Streaming: [{}]({})"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:222
|
||||
msgid "Playing {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:227
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:230
|
||||
msgid "Joined Discord on"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:231
|
||||
msgid "Joined this guild on"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:232 ../general.py:277
|
||||
msgid "Roles"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:233
|
||||
msgid "Member #{} | User ID: {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:248 ../general.py:290
|
||||
msgid "I need the `Embed links` permission to send this."
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:263
|
||||
msgid "Since {}. That's over {} days ago!"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:273
|
||||
msgid "Region"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:274
|
||||
msgid "Users"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:275
|
||||
msgid "Text Channels"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:276
|
||||
msgid "Voice Channels"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:278
|
||||
msgid "Owner"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:279
|
||||
msgid "Guild ID: "
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:334
|
||||
msgid "Your search terms gave no results."
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:336
|
||||
msgid "There is no definition #{}"
|
||||
msgstr ""
|
||||
|
||||
#: ../general.py:338
|
||||
msgid "Error."
|
||||
msgstr ""
|
||||
|
||||
9
redbot/cogs/image/__init__.py
Normal file
9
redbot/cogs/image/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from .image import Image
|
||||
import asyncio
|
||||
|
||||
|
||||
def setup(bot):
|
||||
n = Image(bot)
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(n.set_giphy_key())
|
||||
bot.add_cog(n)
|
||||
159
redbot/cogs/image/image.py
Normal file
159
redbot/cogs/image/image.py
Normal file
@@ -0,0 +1,159 @@
|
||||
from random import shuffle
|
||||
|
||||
import aiohttp
|
||||
from discord.ext import commands
|
||||
|
||||
from redbot.core.i18n import CogI18n
|
||||
from redbot.core import checks, Config
|
||||
|
||||
_ = CogI18n("Image", __file__)
|
||||
|
||||
GIPHY_API_KEY = "dc6zaTOxFJmzC"
|
||||
|
||||
|
||||
class Image:
|
||||
"""Image related commands."""
|
||||
default_global = {
|
||||
"imgur_client_id": None
|
||||
}
|
||||
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.settings = Config.get_conf(self, identifier=2652104208, force_registration=True)
|
||||
self.settings.register_global(**self.default_global)
|
||||
self.session = aiohttp.ClientSession()
|
||||
self.imgur_base_url = "https://api.imgur.com/3/"
|
||||
|
||||
@commands.group(name="imgur")
|
||||
@commands.guild_only()
|
||||
async def _imgur(self, ctx):
|
||||
"""Retrieves pictures from imgur
|
||||
|
||||
Make sure to set the client ID using
|
||||
[p]imgurcreds"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await self.bot.send_cmd_help(ctx)
|
||||
|
||||
@_imgur.command(name="search")
|
||||
async def imgur_search(self, ctx, *, term: str):
|
||||
"""Searches Imgur for the specified term and returns up to 3 results"""
|
||||
url = self.imgur_base_url + "time/all/0"
|
||||
params = {"q": term}
|
||||
headers = {"Authorization": "Client-ID {}".format(await self.settings.imgur_client_id())}
|
||||
async with self.session.get(url, headers=headers, data=params) as search_get:
|
||||
data = await search_get.json()
|
||||
|
||||
if data["success"]:
|
||||
results = data["data"]
|
||||
if not results:
|
||||
await ctx.send(_("Your search returned no results"))
|
||||
return
|
||||
shuffle(results)
|
||||
msg = _("Search results...\n")
|
||||
for r in results[:3]:
|
||||
msg += r["gifv"] if "gifv" in r else r["link"]
|
||||
msg += "\n"
|
||||
await ctx.send(msg)
|
||||
else:
|
||||
await ctx.send(_("Something went wrong. Error code is {}").format(data["status"]))
|
||||
|
||||
@_imgur.command(name="subreddit")
|
||||
async def imgur_subreddit(self, ctx, subreddit: str, sort_type: str="top", window: str="day"):
|
||||
"""Gets images from the specified subreddit section
|
||||
|
||||
Sort types: new, top
|
||||
Time windows: day, week, month, year, all"""
|
||||
sort_type = sort_type.lower()
|
||||
window = window.lower()
|
||||
|
||||
if sort_type not in ("new", "top"):
|
||||
await ctx.send(_("Only 'new' and 'top' are a valid sort type."))
|
||||
return
|
||||
elif window not in ("day", "week", "month", "year", "all"):
|
||||
await self.bot.send_cmd_help(ctx)
|
||||
return
|
||||
|
||||
if sort_type == "new":
|
||||
sort = "time"
|
||||
elif sort_type == "top":
|
||||
sort = "top"
|
||||
|
||||
links = []
|
||||
headers = {"Authorization": "Client-ID {}".format(await self.settings.imgur_client_id())}
|
||||
url = self.imgur_base_url + "r/{}/{}/{}/0".format(subreddit, sort, window)
|
||||
|
||||
async with self.session.get(url, headers=headers) as sub_get:
|
||||
data = await sub_get.json()
|
||||
|
||||
if data["success"]:
|
||||
items = data["data"]
|
||||
if items:
|
||||
for item in items[:3]:
|
||||
link = item["gifv"] if "gifv" in item else item["link"]
|
||||
links.append("{}\n{}".format(item["title"], link))
|
||||
|
||||
if links:
|
||||
await ctx.send("\n".join(links))
|
||||
else:
|
||||
await ctx.send(_("No results found."))
|
||||
else:
|
||||
await ctx.send(_("Something went wrong. Error code is {}").format(data["status"]))
|
||||
|
||||
@checks.is_owner()
|
||||
@commands.command()
|
||||
async def imgurcreds(self, ctx, imgur_client_id: str):
|
||||
"""Sets the imgur client id
|
||||
You will need an account on Imgur to get this
|
||||
|
||||
You can get these by visiting https://api.imgur.com/oauth2/addclient
|
||||
and filling out the form. Enter a name for the application, select
|
||||
'Anonymous usage without user authorization' for the auth type,
|
||||
leave the app website blank, enter a valid email address, and
|
||||
enter a description. Check the box for the captcha, then click Next.
|
||||
Your client ID will be on the page that loads"""
|
||||
await self.settings.imgur_client_id.set(imgur_client_id)
|
||||
await ctx.send(_("Set the imgur client id!"))
|
||||
|
||||
@commands.command(pass_context=True, no_pm=True)
|
||||
async def gif(self, ctx, *keywords):
|
||||
"""Retrieves first search result from giphy"""
|
||||
if keywords:
|
||||
keywords = "+".join(keywords)
|
||||
else:
|
||||
await self.bot.send_cmd_help(ctx)
|
||||
return
|
||||
|
||||
url = ("http://api.giphy.com/v1/gifs/search?&api_key={}&q={}"
|
||||
"".format(GIPHY_API_KEY, keywords))
|
||||
|
||||
async with self.session.get(url) as r:
|
||||
result = await r.json()
|
||||
if r.status == 200:
|
||||
if result["data"]:
|
||||
await ctx.send(result["data"][0]["url"])
|
||||
else:
|
||||
await ctx.send(_("No results found."))
|
||||
else:
|
||||
await ctx.send(_("Error contacting the API"))
|
||||
|
||||
@commands.command(pass_context=True, no_pm=True)
|
||||
async def gifr(self, ctx, *keywords):
|
||||
"""Retrieves a random gif from a giphy search"""
|
||||
if keywords:
|
||||
keywords = "+".join(keywords)
|
||||
else:
|
||||
await self.bot.send_cmd_help(ctx)
|
||||
return
|
||||
|
||||
url = ("http://api.giphy.com/v1/gifs/random?&api_key={}&tag={}"
|
||||
"".format(GIPHY_API_KEY, keywords))
|
||||
|
||||
async with self.session.get(url) as r:
|
||||
result = await r.json()
|
||||
if r.status == 200:
|
||||
if result["data"]:
|
||||
await ctx.send(result["data"]["url"])
|
||||
else:
|
||||
await ctx.send(_("No results found."))
|
||||
else:
|
||||
await ctx.send(_("Error contacting the API"))
|
||||
45
redbot/cogs/image/locales/es.po
Normal file
45
redbot/cogs/image/locales/es.po
Normal file
@@ -0,0 +1,45 @@
|
||||
# Copyright (C) 2017 Red-DiscordBot
|
||||
# UltimatePancake <pier.gaetani@gmail.com>, 2017.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: 2017-08-26 17:57+EDT\n"
|
||||
"PO-Revision-Date: 2017-08-26 20:39-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"
|
||||
|
||||
#: ../image.py:48
|
||||
msgid "Your search returned no results"
|
||||
msgstr "Tu búsqueda no a dado resultados"
|
||||
|
||||
#: ../image.py:51
|
||||
msgid "Search results...\n"
|
||||
msgstr "Resultados de la búsqueda\n"
|
||||
|
||||
#: ../image.py:57 ../image.py:99
|
||||
msgid "Something went wrong. Error code is {}"
|
||||
msgstr "Algo malo ha ocurrido. Código de error: {}"
|
||||
|
||||
#: ../image.py:69
|
||||
msgid "Only 'new' and 'top' are a valid sort type."
|
||||
msgstr "Únicamente 'new' y 'top' son tipos de ordenamiento válidos."
|
||||
|
||||
#: ../image.py:97 ../image.py:134 ../image.py:156
|
||||
msgid "No results found."
|
||||
msgstr "No se han encontrado resultados."
|
||||
|
||||
#: ../image.py:114
|
||||
msgid "Set the imgur client id!"
|
||||
msgstr "Configurar el id de cliente de imgur!"
|
||||
|
||||
#: ../image.py:136 ../image.py:158
|
||||
msgid "Error contacting the API"
|
||||
msgstr "Error contactando al API"
|
||||
46
redbot/cogs/image/locales/messages.pot
Normal file
46
redbot/cogs/image/locales/messages.pot
Normal file
@@ -0,0 +1,46 @@
|
||||
# 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:57+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"
|
||||
|
||||
|
||||
#: ../image.py:48
|
||||
msgid "Your search returned no results"
|
||||
msgstr ""
|
||||
|
||||
#: ../image.py:51
|
||||
msgid ""
|
||||
"Search results...\n"
|
||||
msgstr ""
|
||||
|
||||
#: ../image.py:57 ../image.py:99
|
||||
msgid "Something went wrong. Error code is {}"
|
||||
msgstr ""
|
||||
|
||||
#: ../image.py:69
|
||||
msgid "Only 'new' and 'top' are a valid sort type."
|
||||
msgstr ""
|
||||
|
||||
#: ../image.py:97 ../image.py:134 ../image.py:156
|
||||
msgid "No results found."
|
||||
msgstr ""
|
||||
|
||||
#: ../image.py:114
|
||||
msgid "Set the imgur client id!"
|
||||
msgstr ""
|
||||
|
||||
#: ../image.py:136 ../image.py:158
|
||||
msgid "Error contacting the API"
|
||||
msgstr ""
|
||||
|
||||
Reference in New Issue
Block a user