mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 11:18:54 -05:00
[V3] Update code standards (black code format pass) (#1650)
* ran black: code formatter against `redbot/` with `-l 99` * badge
This commit is contained in:
parent
e7476edd68
commit
b88b5a2601
@ -14,6 +14,11 @@
|
|||||||
:target: https://www.patreon.com/Red_Devs
|
:target: https://www.patreon.com/Red_Devs
|
||||||
:alt: Patreon
|
:alt: Patreon
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||||
|
:target: https://github.com/ambv/black
|
||||||
|
:alt: Code style: black
|
||||||
|
|
||||||
|
|
||||||
********************
|
********************
|
||||||
Red - Discord Bot v3
|
Red - Discord Bot v3
|
||||||
********************
|
********************
|
||||||
|
|||||||
@ -5,7 +5,9 @@ import discord
|
|||||||
# Let's do all the dumb version checking in one place.
|
# Let's do all the dumb version checking in one place.
|
||||||
|
|
||||||
if discord.version_info.major < 1:
|
if discord.version_info.major < 1:
|
||||||
print("You are not running the rewritten version of discord.py.\n\n"
|
print(
|
||||||
|
"You are not running the rewritten version of discord.py.\n\n"
|
||||||
"In order to use Red v3 you MUST be running d.py version"
|
"In order to use Red v3 you MUST be running d.py version"
|
||||||
" >= 1.0.0.")
|
" >= 1.0.0."
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|||||||
@ -40,24 +40,25 @@ def init_loggers(cli_flags):
|
|||||||
logger = logging.getLogger("red")
|
logger = logging.getLogger("red")
|
||||||
|
|
||||||
red_format = logging.Formatter(
|
red_format = logging.Formatter(
|
||||||
'%(asctime)s %(levelname)s %(module)s %(funcName)s %(lineno)d: '
|
"%(asctime)s %(levelname)s %(module)s %(funcName)s %(lineno)d: " "%(message)s",
|
||||||
'%(message)s',
|
datefmt="[%d/%m/%Y %H:%M]",
|
||||||
datefmt="[%d/%m/%Y %H:%M]")
|
)
|
||||||
|
|
||||||
stdout_handler = logging.StreamHandler(sys.stdout)
|
stdout_handler = logging.StreamHandler(sys.stdout)
|
||||||
stdout_handler.setFormatter(red_format)
|
stdout_handler.setFormatter(red_format)
|
||||||
|
|
||||||
if cli_flags.debug:
|
if cli_flags.debug:
|
||||||
os.environ['PYTHONASYNCIODEBUG'] = '1'
|
os.environ["PYTHONASYNCIODEBUG"] = "1"
|
||||||
logger.setLevel(logging.DEBUG)
|
logger.setLevel(logging.DEBUG)
|
||||||
else:
|
else:
|
||||||
logger.setLevel(logging.WARNING)
|
logger.setLevel(logging.WARNING)
|
||||||
|
|
||||||
from redbot.core.data_manager import core_data_path
|
from redbot.core.data_manager import core_data_path
|
||||||
logfile_path = core_data_path() / 'red.log'
|
|
||||||
|
logfile_path = core_data_path() / "red.log"
|
||||||
fhandler = logging.handlers.RotatingFileHandler(
|
fhandler = logging.handlers.RotatingFileHandler(
|
||||||
filename=str(logfile_path), encoding='utf-8', mode='a',
|
filename=str(logfile_path), encoding="utf-8", mode="a", maxBytes=10 ** 7, backupCount=5
|
||||||
maxBytes=10**7, backupCount=5)
|
)
|
||||||
fhandler.setFormatter(red_format)
|
fhandler.setFormatter(red_format)
|
||||||
|
|
||||||
logger.addHandler(fhandler)
|
logger.addHandler(fhandler)
|
||||||
@ -76,15 +77,17 @@ async def _get_prefix_and_token(red, indict):
|
|||||||
:param indict:
|
:param indict:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
indict['token'] = await red.db.token()
|
indict["token"] = await red.db.token()
|
||||||
indict['prefix'] = await red.db.prefix()
|
indict["prefix"] = await red.db.prefix()
|
||||||
indict['enable_sentry'] = await red.db.enable_sentry()
|
indict["enable_sentry"] = await red.db.enable_sentry()
|
||||||
|
|
||||||
|
|
||||||
def list_instances():
|
def list_instances():
|
||||||
if not config_file.exists():
|
if not config_file.exists():
|
||||||
print("No instances have been configured! Configure one "
|
print(
|
||||||
"using `redbot-setup` before trying to run the bot!")
|
"No instances have been configured! Configure one "
|
||||||
|
"using `redbot-setup` before trying to run the bot!"
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
data = JsonIO(config_file)._load_json()
|
data = JsonIO(config_file)._load_json()
|
||||||
@ -118,29 +121,30 @@ def main():
|
|||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
tmp_data = {}
|
tmp_data = {}
|
||||||
loop.run_until_complete(_get_prefix_and_token(red, tmp_data))
|
loop.run_until_complete(_get_prefix_and_token(red, tmp_data))
|
||||||
token = os.environ.get("RED_TOKEN", tmp_data['token'])
|
token = os.environ.get("RED_TOKEN", tmp_data["token"])
|
||||||
prefix = cli_flags.prefix or tmp_data['prefix']
|
prefix = cli_flags.prefix or tmp_data["prefix"]
|
||||||
if token is None or not prefix:
|
if token is None or not prefix:
|
||||||
if cli_flags.no_prompt is False:
|
if cli_flags.no_prompt is False:
|
||||||
new_token = interactive_config(red, token_set=bool(token),
|
new_token = interactive_config(red, token_set=bool(token), prefix_set=bool(prefix))
|
||||||
prefix_set=bool(prefix))
|
|
||||||
if new_token:
|
if new_token:
|
||||||
token = new_token
|
token = new_token
|
||||||
else:
|
else:
|
||||||
log.critical("Token and prefix must be set in order to login.")
|
log.critical("Token and prefix must be set in order to login.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
loop.run_until_complete(_get_prefix_and_token(red, tmp_data))
|
loop.run_until_complete(_get_prefix_and_token(red, tmp_data))
|
||||||
if tmp_data['enable_sentry']:
|
if tmp_data["enable_sentry"]:
|
||||||
red.enable_sentry()
|
red.enable_sentry()
|
||||||
cleanup_tasks = True
|
cleanup_tasks = True
|
||||||
try:
|
try:
|
||||||
loop.run_until_complete(red.start(token, bot=not cli_flags.not_bot))
|
loop.run_until_complete(red.start(token, bot=not cli_flags.not_bot))
|
||||||
except discord.LoginFailure:
|
except discord.LoginFailure:
|
||||||
cleanup_tasks = False # No login happened, no need for this
|
cleanup_tasks = False # No login happened, no need for this
|
||||||
log.critical("This token doesn't seem to be valid. If it belongs to "
|
log.critical(
|
||||||
|
"This token doesn't seem to be valid. If it belongs to "
|
||||||
"a user account, remember that the --not-bot flag "
|
"a user account, remember that the --not-bot flag "
|
||||||
"must be used. For self-bot functionalities instead, "
|
"must be used. For self-bot functionalities instead, "
|
||||||
"--self-bot")
|
"--self-bot"
|
||||||
|
)
|
||||||
db_token = red.db.token()
|
db_token = red.db.token()
|
||||||
if db_token and not cli_flags.no_prompt:
|
if db_token and not cli_flags.no_prompt:
|
||||||
print("\nDo you want to reset the token? (y/n)")
|
print("\nDo you want to reset the token? (y/n)")
|
||||||
@ -159,12 +163,11 @@ def main():
|
|||||||
rpc.clean_up()
|
rpc.clean_up()
|
||||||
if cleanup_tasks:
|
if cleanup_tasks:
|
||||||
pending = asyncio.Task.all_tasks(loop=red.loop)
|
pending = asyncio.Task.all_tasks(loop=red.loop)
|
||||||
gathered = asyncio.gather(
|
gathered = asyncio.gather(*pending, loop=red.loop, return_exceptions=True)
|
||||||
*pending, loop=red.loop, return_exceptions=True)
|
|
||||||
gathered.cancel()
|
gathered.cancel()
|
||||||
|
|
||||||
sys.exit(red._shutdown_mode.value)
|
sys.exit(red._shutdown_mode.value)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@ -40,18 +40,16 @@ RUNNING_ANNOUNCEMENT = (
|
|||||||
|
|
||||||
|
|
||||||
class Admin:
|
class Admin:
|
||||||
def __init__(self, config=Config):
|
|
||||||
self.conf = config.get_conf(self, 8237492837454039,
|
|
||||||
force_registration=True)
|
|
||||||
|
|
||||||
self.conf.register_global(
|
def __init__(self, config=Config):
|
||||||
serverlocked=False
|
self.conf = config.get_conf(self, 8237492837454039, force_registration=True)
|
||||||
)
|
|
||||||
|
self.conf.register_global(serverlocked=False)
|
||||||
|
|
||||||
self.conf.register_guild(
|
self.conf.register_guild(
|
||||||
announce_ignore=False,
|
announce_ignore=False,
|
||||||
announce_channel=None, # Integer ID
|
announce_channel=None, # Integer ID
|
||||||
selfroles=[] # List of integer ID's
|
selfroles=[], # List of integer ID's
|
||||||
)
|
)
|
||||||
|
|
||||||
self.__current_announcer = None
|
self.__current_announcer = None
|
||||||
@ -63,8 +61,7 @@ class Admin:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def complain(ctx: commands.Context, message: str,
|
async def complain(ctx: commands.Context, message: str, **kwargs):
|
||||||
**kwargs):
|
|
||||||
await ctx.send(message.format(**kwargs))
|
await ctx.send(message.format(**kwargs))
|
||||||
|
|
||||||
def is_announcing(self) -> bool:
|
def is_announcing(self) -> bool:
|
||||||
@ -78,8 +75,7 @@ class Admin:
|
|||||||
return self.__current_announcer.active or False
|
return self.__current_announcer.active or False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def pass_heirarchy_check(ctx: commands.Context,
|
def pass_heirarchy_check(ctx: commands.Context, role: discord.Role) -> bool:
|
||||||
role: discord.Role) -> bool:
|
|
||||||
"""
|
"""
|
||||||
Determines if the bot has a higher role than the given one.
|
Determines if the bot has a higher role than the given one.
|
||||||
:param ctx:
|
:param ctx:
|
||||||
@ -89,8 +85,7 @@ class Admin:
|
|||||||
return ctx.guild.me.top_role > role
|
return ctx.guild.me.top_role > role
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def pass_user_heirarchy_check(ctx: commands.Context,
|
def pass_user_heirarchy_check(ctx: commands.Context, role: discord.Role) -> bool:
|
||||||
role: discord.Role) -> bool:
|
|
||||||
"""
|
"""
|
||||||
Determines if a user is allowed to add/remove/edit the given role.
|
Determines if a user is allowed to add/remove/edit the given role.
|
||||||
:param ctx:
|
:param ctx:
|
||||||
@ -99,43 +94,40 @@ class Admin:
|
|||||||
"""
|
"""
|
||||||
return ctx.author.top_role > role
|
return ctx.author.top_role > role
|
||||||
|
|
||||||
async def _addrole(self, ctx: commands.Context, member: discord.Member,
|
async def _addrole(self, ctx: commands.Context, member: discord.Member, role: discord.Role):
|
||||||
role: discord.Role):
|
|
||||||
try:
|
try:
|
||||||
await member.add_roles(role)
|
await member.add_roles(role)
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
if not self.pass_heirarchy_check(ctx, role):
|
if not self.pass_heirarchy_check(ctx, role):
|
||||||
await self.complain(ctx, HIERARCHY_ISSUE, role=role,
|
await self.complain(ctx, HIERARCHY_ISSUE, role=role, member=member)
|
||||||
member=member)
|
|
||||||
else:
|
else:
|
||||||
await self.complain(ctx, GENERIC_FORBIDDEN)
|
await self.complain(ctx, GENERIC_FORBIDDEN)
|
||||||
else:
|
else:
|
||||||
await ctx.send("I successfully added {role.name} to"
|
await ctx.send(
|
||||||
" {member.display_name}".format(
|
"I successfully added {role.name} to"
|
||||||
role=role, member=member
|
" {member.display_name}".format(role=role, member=member)
|
||||||
))
|
)
|
||||||
|
|
||||||
async def _removerole(self, ctx: commands.Context, member: discord.Member,
|
async def _removerole(self, ctx: commands.Context, member: discord.Member, role: discord.Role):
|
||||||
role: discord.Role):
|
|
||||||
try:
|
try:
|
||||||
await member.remove_roles(role)
|
await member.remove_roles(role)
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
if not self.pass_heirarchy_check(ctx, role):
|
if not self.pass_heirarchy_check(ctx, role):
|
||||||
await self.complain(ctx, HIERARCHY_ISSUE, role=role,
|
await self.complain(ctx, HIERARCHY_ISSUE, role=role, member=member)
|
||||||
member=member)
|
|
||||||
else:
|
else:
|
||||||
await self.complain(ctx, GENERIC_FORBIDDEN)
|
await self.complain(ctx, GENERIC_FORBIDDEN)
|
||||||
else:
|
else:
|
||||||
await ctx.send("I successfully removed {role.name} from"
|
await ctx.send(
|
||||||
" {member.display_name}".format(
|
"I successfully removed {role.name} from"
|
||||||
role=role, member=member
|
" {member.display_name}".format(role=role, member=member)
|
||||||
))
|
)
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.admin_or_permissions(manage_roles=True)
|
@checks.admin_or_permissions(manage_roles=True)
|
||||||
async def addrole(self, ctx: commands.Context, rolename: discord.Role, *,
|
async def addrole(
|
||||||
user: MemberDefaultAuthor=None):
|
self, ctx: commands.Context, rolename: discord.Role, *, user: MemberDefaultAuthor = None
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Adds a role to a user. If user is left blank it defaults to the
|
Adds a role to a user. If user is left blank it defaults to the
|
||||||
author of the command.
|
author of the command.
|
||||||
@ -151,8 +143,9 @@ class Admin:
|
|||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.admin_or_permissions(manage_roles=True)
|
@checks.admin_or_permissions(manage_roles=True)
|
||||||
async def removerole(self, ctx: commands.Context, rolename: discord.Role, *,
|
async def removerole(
|
||||||
user: MemberDefaultAuthor=None):
|
self, ctx: commands.Context, rolename: discord.Role, *, user: MemberDefaultAuthor = None
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Removes a role from a user. If user is left blank it defaults to the
|
Removes a role from a user. If user is left blank it defaults to the
|
||||||
author of the command.
|
author of the command.
|
||||||
@ -173,9 +166,10 @@ class Admin:
|
|||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
await ctx.send_help()
|
await ctx.send_help()
|
||||||
|
|
||||||
@editrole.command(name="colour", aliases=["color", ])
|
@editrole.command(name="colour", aliases=["color"])
|
||||||
async def editrole_colour(self, ctx: commands.Context, role: discord.Role,
|
async def editrole_colour(
|
||||||
value: discord.Colour):
|
self, ctx: commands.Context, role: discord.Role, value: discord.Colour
|
||||||
|
):
|
||||||
"""Edits a role's colour
|
"""Edits a role's colour
|
||||||
|
|
||||||
Use double quotes if the role contains spaces.
|
Use double quotes if the role contains spaces.
|
||||||
@ -185,8 +179,7 @@ class Admin:
|
|||||||
!editrole colour \"The Transistor\" #ff0000
|
!editrole colour \"The Transistor\" #ff0000
|
||||||
!editrole colour Test #ff9900"""
|
!editrole colour Test #ff9900"""
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
reason = "{}({}) changed the colour of role '{}'".format(
|
reason = "{}({}) changed the colour of role '{}'".format(author.name, author.id, role.name)
|
||||||
author.name, author.id, role.name)
|
|
||||||
|
|
||||||
if not self.pass_user_heirarchy_check(ctx, role):
|
if not self.pass_user_heirarchy_check(ctx, role):
|
||||||
await self.complain(ctx, USER_HIERARCHY_ISSUE)
|
await self.complain(ctx, USER_HIERARCHY_ISSUE)
|
||||||
@ -211,7 +204,8 @@ class Admin:
|
|||||||
author = ctx.message.author
|
author = ctx.message.author
|
||||||
old_name = role.name
|
old_name = role.name
|
||||||
reason = "{}({}) changed the name of role '{}' to '{}'".format(
|
reason = "{}({}) changed the name of role '{}' to '{}'".format(
|
||||||
author.name, author.id, old_name, name)
|
author.name, author.id, old_name, name
|
||||||
|
)
|
||||||
|
|
||||||
if not self.pass_user_heirarchy_check(ctx, role):
|
if not self.pass_user_heirarchy_check(ctx, role):
|
||||||
await self.complain(ctx, USER_HIERARCHY_ISSUE)
|
await self.complain(ctx, USER_HIERARCHY_ISSUE)
|
||||||
@ -240,8 +234,7 @@ class Admin:
|
|||||||
await ctx.send("The announcement has begun.")
|
await ctx.send("The announcement has begun.")
|
||||||
else:
|
else:
|
||||||
prefix = ctx.prefix
|
prefix = ctx.prefix
|
||||||
await self.complain(ctx, RUNNING_ANNOUNCEMENT,
|
await self.complain(ctx, RUNNING_ANNOUNCEMENT, prefix=prefix)
|
||||||
prefix=prefix)
|
|
||||||
|
|
||||||
@announce.command(name="cancel")
|
@announce.command(name="cancel")
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
@ -259,7 +252,7 @@ class Admin:
|
|||||||
@announce.command(name="channel")
|
@announce.command(name="channel")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.guildowner_or_permissions(administrator=True)
|
@checks.guildowner_or_permissions(administrator=True)
|
||||||
async def announce_channel(self, ctx, *, channel: discord.TextChannel=None):
|
async def announce_channel(self, ctx, *, channel: discord.TextChannel = None):
|
||||||
"""
|
"""
|
||||||
Changes the channel on which the bot makes announcements.
|
Changes the channel on which the bot makes announcements.
|
||||||
"""
|
"""
|
||||||
@ -267,14 +260,12 @@ class Admin:
|
|||||||
channel = ctx.channel
|
channel = ctx.channel
|
||||||
await self.conf.guild(ctx.guild).announce_channel.set(channel.id)
|
await self.conf.guild(ctx.guild).announce_channel.set(channel.id)
|
||||||
|
|
||||||
await ctx.send("The announcement channel has been set to {}".format(
|
await ctx.send("The announcement channel has been set to {}".format(channel.mention))
|
||||||
channel.mention
|
|
||||||
))
|
|
||||||
|
|
||||||
@announce.command(name="ignore")
|
@announce.command(name="ignore")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.guildowner_or_permissions(administrator=True)
|
@checks.guildowner_or_permissions(administrator=True)
|
||||||
async def announce_ignore(self, ctx, *, guild: discord.Guild=None):
|
async def announce_ignore(self, ctx, *, guild: discord.Guild = None):
|
||||||
"""
|
"""
|
||||||
Toggles whether the announcements will ignore the given server.
|
Toggles whether the announcements will ignore the given server.
|
||||||
Defaults to the current server if none is provided.
|
Defaults to the current server if none is provided.
|
||||||
@ -287,9 +278,7 @@ class Admin:
|
|||||||
|
|
||||||
verb = "will" if ignored else "will not"
|
verb = "will" if ignored else "will not"
|
||||||
|
|
||||||
await ctx.send("The server {} {} receive announcements.".format(
|
await ctx.send("The server {} {} receive announcements.".format(guild.name, verb))
|
||||||
guild.name, verb
|
|
||||||
))
|
|
||||||
|
|
||||||
async def _valid_selfroles(self, guild: discord.Guild) -> Tuple[discord.Role]:
|
async def _valid_selfroles(self, guild: discord.Guild) -> Tuple[discord.Role]:
|
||||||
"""
|
"""
|
||||||
@ -384,8 +373,10 @@ class Admin:
|
|||||||
|
|
||||||
await ctx.send("The bot {} serverlocked.".format(verb))
|
await ctx.send("The bot {} serverlocked.".format(verb))
|
||||||
|
|
||||||
# region Event Handlers
|
# region Event Handlers
|
||||||
async def on_guild_join(self, guild: discord.Guild):
|
async def on_guild_join(self, guild: discord.Guild):
|
||||||
if await self._serverlock_check(guild):
|
if await self._serverlock_check(guild):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|||||||
@ -5,9 +5,8 @@ from discord.ext import commands
|
|||||||
|
|
||||||
|
|
||||||
class Announcer:
|
class Announcer:
|
||||||
def __init__(self, ctx: commands.Context,
|
|
||||||
message: str,
|
def __init__(self, ctx: commands.Context, message: str, config=None):
|
||||||
config=None):
|
|
||||||
"""
|
"""
|
||||||
:param ctx:
|
:param ctx:
|
||||||
:param message:
|
:param message:
|
||||||
@ -65,10 +64,7 @@ class Announcer:
|
|||||||
try:
|
try:
|
||||||
await channel.send(self.message)
|
await channel.send(self.message)
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
await bot_owner.send("I could not announce to server: {}".format(
|
await bot_owner.send("I could not announce to server: {}".format(g.id))
|
||||||
g.id
|
|
||||||
))
|
|
||||||
await asyncio.sleep(0.5)
|
await asyncio.sleep(0.5)
|
||||||
|
|
||||||
self.active = False
|
self.active = False
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ from discord.ext import commands
|
|||||||
|
|
||||||
|
|
||||||
class MemberDefaultAuthor(commands.Converter):
|
class MemberDefaultAuthor(commands.Converter):
|
||||||
|
|
||||||
async def convert(self, ctx: commands.Context, arg: str) -> discord.Member:
|
async def convert(self, ctx: commands.Context, arg: str) -> discord.Member:
|
||||||
member_converter = commands.MemberConverter()
|
member_converter = commands.MemberConverter()
|
||||||
try:
|
try:
|
||||||
@ -16,6 +17,7 @@ class MemberDefaultAuthor(commands.Converter):
|
|||||||
|
|
||||||
|
|
||||||
class SelfRole(commands.Converter):
|
class SelfRole(commands.Converter):
|
||||||
|
|
||||||
async def convert(self, ctx: commands.Context, arg: str) -> discord.Role:
|
async def convert(self, ctx: commands.Context, arg: str) -> discord.Role:
|
||||||
admin = ctx.command.instance
|
admin = ctx.command.instance
|
||||||
if admin is None:
|
if admin is None:
|
||||||
@ -28,6 +30,5 @@ class SelfRole(commands.Converter):
|
|||||||
role = await role_converter.convert(ctx, arg)
|
role = await role_converter.convert(ctx, arg)
|
||||||
|
|
||||||
if role.id not in selfroles:
|
if role.id not in selfroles:
|
||||||
raise commands.BadArgument("The provided role is not a valid"
|
raise commands.BadArgument("The provided role is not a valid" " selfrole.")
|
||||||
" selfrole.")
|
|
||||||
return role
|
return role
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
TO_TRANSLATE = [
|
TO_TRANSLATE = ["../admin.py"]
|
||||||
'../admin.py'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def regen_messages():
|
def regen_messages():
|
||||||
subprocess.run(
|
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||||
['pygettext', '-n'] + TO_TRANSLATE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -26,14 +26,9 @@ class Alias:
|
|||||||
and append them to the stored alias
|
and append them to the stored alias
|
||||||
"""
|
"""
|
||||||
|
|
||||||
default_global_settings = {
|
default_global_settings = {"entries": []}
|
||||||
"entries": []
|
|
||||||
}
|
|
||||||
|
|
||||||
default_guild_settings = {
|
default_guild_settings = {"enabled": False, "entries": []} # Going to be a list of dicts
|
||||||
"enabled": False,
|
|
||||||
"entries": [] # Going to be a list of dicts
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, bot: Red):
|
def __init__(self, bot: Red):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
@ -49,14 +44,17 @@ class Alias:
|
|||||||
return (AliasEntry.from_json(d) for d in (await self._aliases.entries()))
|
return (AliasEntry.from_json(d) for d in (await self._aliases.entries()))
|
||||||
|
|
||||||
async def loaded_aliases(self, guild: discord.Guild) -> Generator[AliasEntry, None, None]:
|
async def loaded_aliases(self, guild: discord.Guild) -> Generator[AliasEntry, None, None]:
|
||||||
return (AliasEntry.from_json(d, bot=self.bot)
|
return (
|
||||||
for d in (await self._aliases.guild(guild).entries()))
|
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]:
|
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()))
|
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,
|
async def is_alias(
|
||||||
server_aliases: Iterable[AliasEntry]=()) -> (bool, AliasEntry):
|
self, guild: discord.Guild, alias_name: str, server_aliases: Iterable[AliasEntry] = ()
|
||||||
|
) -> (bool, AliasEntry):
|
||||||
|
|
||||||
if not server_aliases:
|
if not server_aliases:
|
||||||
server_aliases = await self.unloaded_aliases(guild)
|
server_aliases = await self.unloaded_aliases(guild)
|
||||||
@ -76,10 +74,11 @@ class Alias:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_valid_alias_name(alias_name: str) -> bool:
|
def is_valid_alias_name(alias_name: str) -> bool:
|
||||||
return not bool(search(r'\s', alias_name)) and alias_name.isprintable()
|
return not bool(search(r"\s", alias_name)) and alias_name.isprintable()
|
||||||
|
|
||||||
async def add_alias(self, ctx: commands.Context, alias_name: str,
|
async def add_alias(
|
||||||
command: Tuple[str], global_: bool=False) -> AliasEntry:
|
self, ctx: commands.Context, alias_name: str, command: Tuple[str], global_: bool = False
|
||||||
|
) -> AliasEntry:
|
||||||
alias = AliasEntry(alias_name, command, ctx.author, global_=global_)
|
alias = AliasEntry(alias_name, command, ctx.author, global_=global_)
|
||||||
|
|
||||||
if global_:
|
if global_:
|
||||||
@ -93,8 +92,9 @@ class Alias:
|
|||||||
|
|
||||||
return alias
|
return alias
|
||||||
|
|
||||||
async def delete_alias(self, ctx: commands.Context, alias_name: str,
|
async def delete_alias(
|
||||||
global_: bool=False) -> bool:
|
self, ctx: commands.Context, alias_name: str, global_: bool = False
|
||||||
|
) -> bool:
|
||||||
if global_:
|
if global_:
|
||||||
settings = self._aliases
|
settings = self._aliases
|
||||||
else:
|
else:
|
||||||
@ -120,16 +120,15 @@ class Alias:
|
|||||||
"""
|
"""
|
||||||
content = message.content
|
content = message.content
|
||||||
prefix_list = await self.bot.command_prefix(self.bot, message)
|
prefix_list = await self.bot.command_prefix(self.bot, message)
|
||||||
prefixes = sorted(prefix_list,
|
prefixes = sorted(prefix_list, key=lambda pfx: len(pfx), reverse=True)
|
||||||
key=lambda pfx: len(pfx),
|
|
||||||
reverse=True)
|
|
||||||
for p in prefixes:
|
for p in prefixes:
|
||||||
if content.startswith(p):
|
if content.startswith(p):
|
||||||
return p
|
return p
|
||||||
raise ValueError(_("No prefix found."))
|
raise ValueError(_("No prefix found."))
|
||||||
|
|
||||||
def get_extra_args_from_alias(self, message: discord.Message, prefix: str,
|
def get_extra_args_from_alias(
|
||||||
alias: AliasEntry) -> str:
|
self, message: discord.Message, prefix: str, alias: AliasEntry
|
||||||
|
) -> str:
|
||||||
"""
|
"""
|
||||||
When an alias is executed by a user in chat this function tries
|
When an alias is executed by a user in chat this function tries
|
||||||
to get any extra arguments passed in with the call.
|
to get any extra arguments passed in with the call.
|
||||||
@ -143,8 +142,9 @@ class Alias:
|
|||||||
extra = message.content[known_content_length:].strip()
|
extra = message.content[known_content_length:].strip()
|
||||||
return extra
|
return extra
|
||||||
|
|
||||||
async def maybe_call_alias(self, message: discord.Message,
|
async def maybe_call_alias(
|
||||||
aliases: Iterable[AliasEntry]=None):
|
self, message: discord.Message, aliases: Iterable[AliasEntry] = None
|
||||||
|
):
|
||||||
try:
|
try:
|
||||||
prefix = await self.get_prefix(message)
|
prefix = await self.get_prefix(message)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@ -155,13 +155,14 @@ class Alias:
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
is_alias, alias = await self.is_alias(message.guild, potential_alias, server_aliases=aliases)
|
is_alias, alias = await self.is_alias(
|
||||||
|
message.guild, potential_alias, server_aliases=aliases
|
||||||
|
)
|
||||||
|
|
||||||
if is_alias:
|
if is_alias:
|
||||||
await self.call_alias(message, prefix, alias)
|
await self.call_alias(message, prefix, alias)
|
||||||
|
|
||||||
async def call_alias(self, message: discord.Message, prefix: str,
|
async def call_alias(self, message: discord.Message, prefix: str, alias: AliasEntry):
|
||||||
alias: AliasEntry):
|
|
||||||
new_message = copy(message)
|
new_message = copy(message)
|
||||||
args = self.get_extra_args_from_alias(message, prefix, alias)
|
args = self.get_extra_args_from_alias(message, prefix, alias)
|
||||||
|
|
||||||
@ -181,83 +182,118 @@ class Alias:
|
|||||||
"""
|
"""
|
||||||
Manage global aliases.
|
Manage global aliases.
|
||||||
"""
|
"""
|
||||||
if ctx.invoked_subcommand is None or \
|
if ctx.invoked_subcommand is None or isinstance(ctx.invoked_subcommand, commands.Group):
|
||||||
isinstance(ctx.invoked_subcommand, commands.Group):
|
|
||||||
await ctx.send_help()
|
await ctx.send_help()
|
||||||
|
|
||||||
@alias.command(name="add")
|
@alias.command(name="add")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def _add_alias(self, ctx: commands.Context,
|
async def _add_alias(self, ctx: commands.Context, alias_name: str, *, command):
|
||||||
alias_name: str, *, command):
|
|
||||||
"""
|
"""
|
||||||
Add an alias for a command.
|
Add an alias for a command.
|
||||||
"""
|
"""
|
||||||
# region Alias Add Validity Checking
|
# region Alias Add Validity Checking
|
||||||
is_command = self.is_command(alias_name)
|
is_command = self.is_command(alias_name)
|
||||||
if is_command:
|
if is_command:
|
||||||
await ctx.send(_("You attempted to create a new alias"
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"You attempted to create a new alias"
|
||||||
" with the name {} but that"
|
" with the name {} but that"
|
||||||
" name is already a command on this bot.").format(alias_name))
|
" name is already a command on this bot."
|
||||||
|
).format(
|
||||||
|
alias_name
|
||||||
|
)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
is_alias, something_useless = await self.is_alias(ctx.guild, alias_name)
|
is_alias, something_useless = await self.is_alias(ctx.guild, alias_name)
|
||||||
if is_alias:
|
if is_alias:
|
||||||
await ctx.send(_("You attempted to create a new alias"
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"You attempted to create a new alias"
|
||||||
" with the name {} but that"
|
" with the name {} but that"
|
||||||
" alias already exists on this server.").format(alias_name))
|
" alias already exists on this server."
|
||||||
|
).format(
|
||||||
|
alias_name
|
||||||
|
)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
is_valid_name = self.is_valid_alias_name(alias_name)
|
is_valid_name = self.is_valid_alias_name(alias_name)
|
||||||
if not is_valid_name:
|
if not is_valid_name:
|
||||||
await ctx.send(_("You attempted to create a new alias"
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"You attempted to create a new alias"
|
||||||
" with the name {} but that"
|
" with the name {} but that"
|
||||||
" name is an invalid alias name. Alias"
|
" name is an invalid alias name. Alias"
|
||||||
" names may not contain spaces.").format(alias_name))
|
" names may not contain spaces."
|
||||||
|
).format(
|
||||||
|
alias_name
|
||||||
|
)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
# At this point we know we need to make a new alias
|
# At this point we know we need to make a new alias
|
||||||
# and that the alias name is valid.
|
# and that the alias name is valid.
|
||||||
|
|
||||||
await self.add_alias(ctx, alias_name, command)
|
await self.add_alias(ctx, alias_name, command)
|
||||||
|
|
||||||
await ctx.send(_("A new alias with the trigger `{}`"
|
await ctx.send(
|
||||||
" has been created.").format(alias_name))
|
_("A new alias with the trigger `{}`" " has been created.").format(alias_name)
|
||||||
|
)
|
||||||
|
|
||||||
@global_.command(name="add")
|
@global_.command(name="add")
|
||||||
async def _add_global_alias(self, ctx: commands.Context,
|
async def _add_global_alias(self, ctx: commands.Context, alias_name: str, *, command):
|
||||||
alias_name: str, *, command):
|
|
||||||
"""
|
"""
|
||||||
Add a global alias for a command.
|
Add a global alias for a command.
|
||||||
"""
|
"""
|
||||||
# region Alias Add Validity Checking
|
# region Alias Add Validity Checking
|
||||||
is_command = self.is_command(alias_name)
|
is_command = self.is_command(alias_name)
|
||||||
if is_command:
|
if is_command:
|
||||||
await ctx.send(_("You attempted to create a new global alias"
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"You attempted to create a new global alias"
|
||||||
" with the name {} but that"
|
" with the name {} but that"
|
||||||
" name is already a command on this bot.").format(alias_name))
|
" name is already a command on this bot."
|
||||||
|
).format(
|
||||||
|
alias_name
|
||||||
|
)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
is_alias, something_useless = await self.is_alias(ctx.guild, alias_name)
|
is_alias, something_useless = await self.is_alias(ctx.guild, alias_name)
|
||||||
if is_alias:
|
if is_alias:
|
||||||
await ctx.send(_("You attempted to create a new global alias"
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"You attempted to create a new global alias"
|
||||||
" with the name {} but that"
|
" with the name {} but that"
|
||||||
" alias already exists on this server.").format(alias_name))
|
" alias already exists on this server."
|
||||||
|
).format(
|
||||||
|
alias_name
|
||||||
|
)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
is_valid_name = self.is_valid_alias_name(alias_name)
|
is_valid_name = self.is_valid_alias_name(alias_name)
|
||||||
if not is_valid_name:
|
if not is_valid_name:
|
||||||
await ctx.send(_("You attempted to create a new global alias"
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"You attempted to create a new global alias"
|
||||||
" with the name {} but that"
|
" with the name {} but that"
|
||||||
" name is an invalid alias name. Alias"
|
" name is an invalid alias name. Alias"
|
||||||
" names may not contain spaces.").format(alias_name))
|
" names may not contain spaces."
|
||||||
|
).format(
|
||||||
|
alias_name
|
||||||
|
)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
await self.add_alias(ctx, alias_name, command, global_=True)
|
await self.add_alias(ctx, alias_name, command, global_=True)
|
||||||
|
|
||||||
await ctx.send(_("A new global alias with the trigger `{}`"
|
await ctx.send(
|
||||||
" has been created.").format(alias_name))
|
_("A new global alias with the trigger `{}`" " has been created.").format(alias_name)
|
||||||
|
)
|
||||||
|
|
||||||
@alias.command(name="help")
|
@alias.command(name="help")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@ -280,8 +316,11 @@ class Alias:
|
|||||||
is_alias, alias = await self.is_alias(ctx.guild, alias_name)
|
is_alias, alias = await self.is_alias(ctx.guild, alias_name)
|
||||||
|
|
||||||
if is_alias:
|
if is_alias:
|
||||||
await ctx.send(_("The `{}` alias will execute the"
|
await ctx.send(
|
||||||
" command `{}`").format(alias_name, alias.command))
|
_("The `{}` alias will execute the" " command `{}`").format(
|
||||||
|
alias_name, alias.command
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("There is no alias with the name `{}`").format(alias_name))
|
await ctx.send(_("There is no alias with the name `{}`").format(alias_name))
|
||||||
|
|
||||||
@ -299,8 +338,9 @@ class Alias:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if await self.delete_alias(ctx, alias_name):
|
if await self.delete_alias(ctx, alias_name):
|
||||||
await ctx.send(_("Alias with the name `{}` was successfully"
|
await ctx.send(
|
||||||
" deleted.").format(alias_name))
|
_("Alias with the name `{}` was successfully" " deleted.").format(alias_name)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Alias with name `{}` was not found.").format(alias_name))
|
await ctx.send(_("Alias with name `{}` was not found.").format(alias_name))
|
||||||
|
|
||||||
@ -317,8 +357,9 @@ class Alias:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if await self.delete_alias(ctx, alias_name, global_=True):
|
if await self.delete_alias(ctx, alias_name, global_=True):
|
||||||
await ctx.send(_("Alias with the name `{}` was successfully"
|
await ctx.send(
|
||||||
" deleted.").format(alias_name))
|
_("Alias with the name `{}` was successfully" " deleted.").format(alias_name)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Alias with name `{}` was not found.").format(alias_name))
|
await ctx.send(_("Alias with name `{}` was not found.").format(alias_name))
|
||||||
|
|
||||||
@ -328,7 +369,9 @@ class Alias:
|
|||||||
"""
|
"""
|
||||||
Lists the available aliases on this server.
|
Lists the available aliases on this server.
|
||||||
"""
|
"""
|
||||||
names = [_("Aliases:"), ] + sorted(["+ " + a.name for a in (await self.unloaded_aliases(ctx.guild))])
|
names = [_("Aliases:")] + sorted(
|
||||||
|
["+ " + a.name for a in (await self.unloaded_aliases(ctx.guild))]
|
||||||
|
)
|
||||||
if len(names) == 0:
|
if len(names) == 0:
|
||||||
await ctx.send(_("There are no aliases on this server."))
|
await ctx.send(_("There are no aliases on this server."))
|
||||||
else:
|
else:
|
||||||
@ -339,7 +382,9 @@ class Alias:
|
|||||||
"""
|
"""
|
||||||
Lists the available global aliases on this bot.
|
Lists the available global aliases on this bot.
|
||||||
"""
|
"""
|
||||||
names = [_("Aliases:"), ] + sorted(["+ " + a.name for a in await self.unloaded_global_aliases()])
|
names = [_("Aliases:")] + sorted(
|
||||||
|
["+ " + a.name for a in await self.unloaded_global_aliases()]
|
||||||
|
)
|
||||||
if len(names) == 0:
|
if len(names) == 0:
|
||||||
await ctx.send(_("There are no aliases on this server."))
|
await ctx.send(_("There are no aliases on this server."))
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -5,8 +5,10 @@ from redbot.core import commands
|
|||||||
|
|
||||||
|
|
||||||
class AliasEntry:
|
class AliasEntry:
|
||||||
def __init__(self, name: str, command: Tuple[str],
|
|
||||||
creator: discord.Member, global_: bool=False):
|
def __init__(
|
||||||
|
self, name: str, command: Tuple[str], creator: discord.Member, global_: bool = False
|
||||||
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.has_real_data = False
|
self.has_real_data = False
|
||||||
self.name = name
|
self.name = name
|
||||||
@ -43,13 +45,12 @@ class AliasEntry:
|
|||||||
"creator": creator,
|
"creator": creator,
|
||||||
"guild": guild,
|
"guild": guild,
|
||||||
"global": self.global_,
|
"global": self.global_,
|
||||||
"uses": self.uses
|
"uses": self.uses,
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_json(cls, data: dict, bot: commands.Bot=None):
|
def from_json(cls, data: dict, bot: commands.Bot = None):
|
||||||
ret = cls(data["name"], data["command"],
|
ret = cls(data["name"], data["command"], data["creator"], global_=data["global"])
|
||||||
data["creator"], global_=data["global"])
|
|
||||||
|
|
||||||
if bot:
|
if bot:
|
||||||
ret.has_real_data = True
|
ret.has_real_data = True
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
TO_TRANSLATE = [
|
TO_TRANSLATE = ["../alias.py"]
|
||||||
'../alias.py'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def regen_messages():
|
def regen_messages():
|
||||||
subprocess.run(
|
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||||
['pygettext', '-n'] + TO_TRANSLATE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -9,9 +9,10 @@ from redbot.core.data_manager import cog_data_path
|
|||||||
import redbot.core
|
import redbot.core
|
||||||
|
|
||||||
LAVALINK_DOWNLOAD_URL = (
|
LAVALINK_DOWNLOAD_URL = (
|
||||||
"https://github.com/Cog-Creators/Red-DiscordBot/"
|
"https://github.com/Cog-Creators/Red-DiscordBot/" "releases/download/{}/Lavalink.jar"
|
||||||
"releases/download/{}/Lavalink.jar"
|
).format(
|
||||||
).format(redbot.core.__version__)
|
redbot.core.__version__
|
||||||
|
)
|
||||||
|
|
||||||
LAVALINK_DOWNLOAD_DIR = cog_data_path(raw_name="Audio")
|
LAVALINK_DOWNLOAD_DIR = cog_data_path(raw_name="Audio")
|
||||||
LAVALINK_JAR_FILE = LAVALINK_DOWNLOAD_DIR / "Lavalink.jar"
|
LAVALINK_JAR_FILE = LAVALINK_DOWNLOAD_DIR / "Lavalink.jar"
|
||||||
@ -21,7 +22,7 @@ BUNDLED_APP_YML_FILE = Path(__file__).parent / "application.yml"
|
|||||||
|
|
||||||
|
|
||||||
async def download_lavalink(session):
|
async def download_lavalink(session):
|
||||||
with LAVALINK_JAR_FILE.open(mode='wb') as f:
|
with LAVALINK_JAR_FILE.open(mode="wb") as f:
|
||||||
async with session.get(LAVALINK_DOWNLOAD_URL) as resp:
|
async with session.get(LAVALINK_DOWNLOAD_URL) as resp:
|
||||||
while True:
|
while True:
|
||||||
chunk = await resp.content.read(512)
|
chunk = await resp.content.read(512)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,14 +1,10 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
TO_TRANSLATE = [
|
TO_TRANSLATE = ["../audio.py"]
|
||||||
'../audio.py'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def regen_messages():
|
def regen_messages():
|
||||||
subprocess.run(
|
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||||
['pygettext', '-n'] + TO_TRANSLATE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -5,7 +5,7 @@ from subprocess import Popen, DEVNULL, PIPE
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger('red.audio.manager')
|
log = logging.getLogger("red.audio.manager")
|
||||||
|
|
||||||
proc = None
|
proc = None
|
||||||
SHUTDOWN = asyncio.Event()
|
SHUTDOWN = asyncio.Event()
|
||||||
@ -13,7 +13,8 @@ SHUTDOWN = asyncio.Event()
|
|||||||
|
|
||||||
def has_java_error(pid):
|
def has_java_error(pid):
|
||||||
from . import LAVALINK_DOWNLOAD_DIR
|
from . import LAVALINK_DOWNLOAD_DIR
|
||||||
poss_error_file = LAVALINK_DOWNLOAD_DIR / 'hs_err_pid{}.log'.format(pid)
|
|
||||||
|
poss_error_file = LAVALINK_DOWNLOAD_DIR / "hs_err_pid{}.log".format(pid)
|
||||||
return poss_error_file.exists()
|
return poss_error_file.exists()
|
||||||
|
|
||||||
|
|
||||||
@ -29,14 +30,14 @@ async def monitor_lavalink_server(loop):
|
|||||||
log.info("Restarting Lavalink jar.")
|
log.info("Restarting Lavalink jar.")
|
||||||
await start_lavalink_server(loop)
|
await start_lavalink_server(loop)
|
||||||
else:
|
else:
|
||||||
log.error("Your Java is borked. Please find the hs_err_pid{}.log file"
|
log.error(
|
||||||
" in the Audio data folder and report this issue.".format(
|
"Your Java is borked. Please find the hs_err_pid{}.log file"
|
||||||
proc.pid
|
" in the Audio data folder and report this issue.".format(proc.pid)
|
||||||
))
|
)
|
||||||
|
|
||||||
|
|
||||||
async def has_java(loop):
|
async def has_java(loop):
|
||||||
java_available = shutil.which('java') is not None
|
java_available = shutil.which("java") is not None
|
||||||
if not java_available:
|
if not java_available:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -48,20 +49,18 @@ async def get_java_version(loop):
|
|||||||
"""
|
"""
|
||||||
This assumes we've already checked that java exists.
|
This assumes we've already checked that java exists.
|
||||||
"""
|
"""
|
||||||
proc = Popen(
|
proc = Popen(shlex.split("java -version", posix=os.name == "posix"), stdout=PIPE, stderr=PIPE)
|
||||||
shlex.split("java -version", posix=os.name == 'posix'),
|
|
||||||
stdout=PIPE, stderr=PIPE
|
|
||||||
)
|
|
||||||
_, err = proc.communicate()
|
_, err = proc.communicate()
|
||||||
|
|
||||||
version_info = str(err, encoding='utf-8')
|
version_info = str(err, encoding="utf-8")
|
||||||
|
|
||||||
version_line = version_info.split('\n')[0]
|
version_line = version_info.split("\n")[0]
|
||||||
version_start = version_line.find('"')
|
version_start = version_line.find('"')
|
||||||
version_string = version_line[version_start + 1:-1]
|
version_string = version_line[version_start + 1:-1]
|
||||||
major, minor = version_string.split('.')[:2]
|
major, minor = version_string.split(".")[:2]
|
||||||
return int(major), int(minor)
|
return int(major), int(minor)
|
||||||
|
|
||||||
|
|
||||||
async def start_lavalink_server(loop):
|
async def start_lavalink_server(loop):
|
||||||
java_available, java_version = await has_java(loop)
|
java_available, java_version = await has_java(loop)
|
||||||
if not java_available:
|
if not java_available:
|
||||||
@ -72,13 +71,15 @@ async def start_lavalink_server(loop):
|
|||||||
extra_flags = "-Dsun.zip.disableMemoryMapping=true"
|
extra_flags = "-Dsun.zip.disableMemoryMapping=true"
|
||||||
|
|
||||||
from . import LAVALINK_DOWNLOAD_DIR, LAVALINK_JAR_FILE
|
from . import LAVALINK_DOWNLOAD_DIR, LAVALINK_JAR_FILE
|
||||||
|
|
||||||
start_cmd = "java {} -jar {}".format(extra_flags, LAVALINK_JAR_FILE.resolve())
|
start_cmd = "java {} -jar {}".format(extra_flags, LAVALINK_JAR_FILE.resolve())
|
||||||
|
|
||||||
global proc
|
global proc
|
||||||
proc = Popen(
|
proc = Popen(
|
||||||
shlex.split(start_cmd, posix=os.name == 'posix'),
|
shlex.split(start_cmd, posix=os.name == "posix"),
|
||||||
cwd=str(LAVALINK_DOWNLOAD_DIR),
|
cwd=str(LAVALINK_DOWNLOAD_DIR),
|
||||||
stdout=DEVNULL, stderr=DEVNULL
|
stdout=DEVNULL,
|
||||||
|
stderr=DEVNULL,
|
||||||
)
|
)
|
||||||
|
|
||||||
log.info("Lavalink jar started. PID: {}".format(proc.pid))
|
log.info("Lavalink jar started. PID: {}".format(proc.pid))
|
||||||
|
|||||||
@ -6,7 +6,7 @@ from redbot.core.i18n import Translator, cog_i18n
|
|||||||
|
|
||||||
from redbot.core.bot import Red # Only used for type hints
|
from redbot.core.bot import Red # Only used for type hints
|
||||||
|
|
||||||
_ = Translator('Bank', __file__)
|
_ = Translator("Bank", __file__)
|
||||||
|
|
||||||
|
|
||||||
def check_global_setting_guildowner():
|
def check_global_setting_guildowner():
|
||||||
@ -14,6 +14,7 @@ def check_global_setting_guildowner():
|
|||||||
Command decorator. If the bank is not global, it checks if the author is
|
Command decorator. If the bank is not global, it checks if the author is
|
||||||
either the guildowner or has the administrator permission.
|
either the guildowner or has the administrator permission.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
async def pred(ctx: commands.Context):
|
async def pred(ctx: commands.Context):
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
if await ctx.bot.is_owner(author):
|
if await ctx.bot.is_owner(author):
|
||||||
@ -32,6 +33,7 @@ def check_global_setting_admin():
|
|||||||
Command decorator. If the bank is not global, it checks if the author is
|
Command decorator. If the bank is not global, it checks if the author is
|
||||||
either a bot admin or has the manage_guild permission.
|
either a bot admin or has the manage_guild permission.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
async def pred(ctx: commands.Context):
|
async def pred(ctx: commands.Context):
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
if await ctx.bot.is_owner(author):
|
if await ctx.bot.is_owner(author):
|
||||||
@ -73,19 +75,23 @@ class Bank:
|
|||||||
currency_name = await bank._conf.guild(ctx.guild).currency()
|
currency_name = await bank._conf.guild(ctx.guild).currency()
|
||||||
default_balance = await bank._conf.guild(ctx.guild).default_balance()
|
default_balance = await bank._conf.guild(ctx.guild).default_balance()
|
||||||
|
|
||||||
settings = (_(
|
settings = (
|
||||||
|
_(
|
||||||
"Bank settings:\n\n"
|
"Bank settings:\n\n"
|
||||||
"Bank name: {}\n"
|
"Bank name: {}\n"
|
||||||
"Currency: {}\n"
|
"Currency: {}\n"
|
||||||
"Default balance: {}"
|
"Default balance: {}"
|
||||||
"").format(bank_name, currency_name, default_balance)
|
""
|
||||||
|
).format(
|
||||||
|
bank_name, currency_name, default_balance
|
||||||
|
)
|
||||||
)
|
)
|
||||||
await ctx.send(box(settings))
|
await ctx.send(box(settings))
|
||||||
await ctx.send_help()
|
await ctx.send_help()
|
||||||
|
|
||||||
@bankset.command(name="toggleglobal")
|
@bankset.command(name="toggleglobal")
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def bankset_toggleglobal(self, ctx: commands.Context, confirm: bool=False):
|
async def bankset_toggleglobal(self, ctx: commands.Context, confirm: bool = False):
|
||||||
"""Toggles whether the bank is global or not
|
"""Toggles whether the bank is global or not
|
||||||
If the bank is global, it will become per-server
|
If the bank is global, it will become per-server
|
||||||
If the bank is per-server, it will become global"""
|
If the bank is per-server, it will become global"""
|
||||||
@ -94,8 +100,10 @@ class Bank:
|
|||||||
word = _("per-server") if cur_setting else _("global")
|
word = _("per-server") if cur_setting else _("global")
|
||||||
if confirm is False:
|
if confirm is False:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("This will toggle the bank to be {}, deleting all accounts "
|
_(
|
||||||
"in the process! If you're sure, type `{}`").format(
|
"This will toggle the bank to be {}, deleting all accounts "
|
||||||
|
"in the process! If you're sure, type `{}`"
|
||||||
|
).format(
|
||||||
word, "{}bankset toggleglobal yes".format(ctx.prefix)
|
word, "{}bankset toggleglobal yes".format(ctx.prefix)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
class BankError(Exception):
|
class BankError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BankNotGlobal(BankError):
|
class BankNotGlobal(BankError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
TO_TRANSLATE = [
|
TO_TRANSLATE = ["../bank.py"]
|
||||||
'../bank.py'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def regen_messages():
|
def regen_messages():
|
||||||
subprocess.run(
|
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||||
['pygettext', '-n'] + TO_TRANSLATE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -27,13 +27,16 @@ class Cleanup:
|
|||||||
|
|
||||||
Tries its best to cleanup after itself if the response is positive.
|
Tries its best to cleanup after itself if the response is positive.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def author_check(message):
|
def author_check(message):
|
||||||
return message.author == ctx.author
|
return message.author == ctx.author
|
||||||
|
|
||||||
prompt = await ctx.send(_('Are you sure you want to delete {} messages? (y/n)').format(number))
|
prompt = await ctx.send(
|
||||||
response = await ctx.bot.wait_for('message', check=author_check)
|
_("Are you sure you want to delete {} messages? (y/n)").format(number)
|
||||||
|
)
|
||||||
|
response = await ctx.bot.wait_for("message", check=author_check)
|
||||||
|
|
||||||
if response.content.lower().startswith('y'):
|
if response.content.lower().startswith("y"):
|
||||||
await prompt.delete()
|
await prompt.delete()
|
||||||
try:
|
try:
|
||||||
await response.delete()
|
await response.delete()
|
||||||
@ -41,14 +44,19 @@ class Cleanup:
|
|||||||
pass
|
pass
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
await ctx.send(_('Cancelled.'))
|
await ctx.send(_("Cancelled."))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def get_messages_for_deletion(
|
async def get_messages_for_deletion(
|
||||||
ctx: commands.Context, channel: discord.TextChannel, number,
|
ctx: commands.Context,
|
||||||
check=lambda x: True, limit=100, before=None, after=None,
|
channel: discord.TextChannel,
|
||||||
delete_pinned=False
|
number,
|
||||||
|
check=lambda x: True,
|
||||||
|
limit=100,
|
||||||
|
before=None,
|
||||||
|
after=None,
|
||||||
|
delete_pinned=False,
|
||||||
) -> list:
|
) -> list:
|
||||||
"""
|
"""
|
||||||
Gets a list of messages meeting the requirements to be deleted.
|
Gets a list of messages meeting the requirements to be deleted.
|
||||||
@ -65,9 +73,7 @@ class Cleanup:
|
|||||||
|
|
||||||
while not too_old and len(to_delete) - 1 < number:
|
while not too_old and len(to_delete) - 1 < number:
|
||||||
message = None
|
message = None
|
||||||
async for message in channel.history(limit=limit,
|
async for message in channel.history(limit=limit, before=before, after=after):
|
||||||
before=before,
|
|
||||||
after=after):
|
|
||||||
if (
|
if (
|
||||||
(not number or len(to_delete) - 1 < number)
|
(not number or len(to_delete) - 1 < number)
|
||||||
and check(message)
|
and check(message)
|
||||||
@ -96,7 +102,9 @@ class Cleanup:
|
|||||||
@cleanup.command()
|
@cleanup.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@commands.bot_has_permissions(manage_messages=True)
|
@commands.bot_has_permissions(manage_messages=True)
|
||||||
async def text(self, ctx: commands.Context, text: str, number: int, delete_pinned: bool=False):
|
async def text(
|
||||||
|
self, ctx: commands.Context, text: str, number: int, delete_pinned: bool = False
|
||||||
|
):
|
||||||
"""Deletes last X messages matching the specified text.
|
"""Deletes last X messages matching the specified text.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
@ -122,12 +130,18 @@ class Cleanup:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
to_delete = await self.get_messages_for_deletion(
|
to_delete = await self.get_messages_for_deletion(
|
||||||
ctx, channel, number, check=check, limit=1000, before=ctx.message,
|
ctx,
|
||||||
delete_pinned=delete_pinned)
|
channel,
|
||||||
|
number,
|
||||||
|
check=check,
|
||||||
|
limit=1000,
|
||||||
|
before=ctx.message,
|
||||||
|
delete_pinned=delete_pinned,
|
||||||
|
)
|
||||||
|
|
||||||
reason = "{}({}) deleted {} messages "\
|
reason = "{}({}) deleted {} messages " " containing '{}' in channel {}.".format(
|
||||||
" containing '{}' in channel {}.".format(author.name,
|
author.name, author.id, len(to_delete), text, channel.id
|
||||||
author.id, len(to_delete), text, channel.id)
|
)
|
||||||
log.info(reason)
|
log.info(reason)
|
||||||
|
|
||||||
if is_bot:
|
if is_bot:
|
||||||
@ -138,7 +152,9 @@ class Cleanup:
|
|||||||
@cleanup.command()
|
@cleanup.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@commands.bot_has_permissions(manage_messages=True)
|
@commands.bot_has_permissions(manage_messages=True)
|
||||||
async def user(self, ctx: commands.Context, user: str, number: int, delete_pinned: bool=False):
|
async def user(
|
||||||
|
self, ctx: commands.Context, user: str, number: int, delete_pinned: bool = False
|
||||||
|
):
|
||||||
"""Deletes last X messages from specified user.
|
"""Deletes last X messages from specified user.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
@ -174,13 +190,17 @@ class Cleanup:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
to_delete = await self.get_messages_for_deletion(
|
to_delete = await self.get_messages_for_deletion(
|
||||||
ctx, channel, number, check=check, limit=1000, before=ctx.message,
|
ctx,
|
||||||
delete_pinned=delete_pinned
|
channel,
|
||||||
|
number,
|
||||||
|
check=check,
|
||||||
|
limit=1000,
|
||||||
|
before=ctx.message,
|
||||||
|
delete_pinned=delete_pinned,
|
||||||
|
)
|
||||||
|
reason = "{}({}) deleted {} messages " " made by {}({}) in channel {}." "".format(
|
||||||
|
author.name, author.id, len(to_delete), member or "???", _id, channel.name
|
||||||
)
|
)
|
||||||
reason = "{}({}) deleted {} messages "\
|
|
||||||
" made by {}({}) in channel {}."\
|
|
||||||
"".format(author.name, author.id, len(to_delete),
|
|
||||||
member or '???', _id, channel.name)
|
|
||||||
log.info(reason)
|
log.info(reason)
|
||||||
|
|
||||||
if is_bot:
|
if is_bot:
|
||||||
@ -192,7 +212,7 @@ class Cleanup:
|
|||||||
@cleanup.command()
|
@cleanup.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@commands.bot_has_permissions(manage_messages=True)
|
@commands.bot_has_permissions(manage_messages=True)
|
||||||
async def after(self, ctx: commands.Context, message_id: int, delete_pinned: bool=False):
|
async def after(self, ctx: commands.Context, message_id: int, delete_pinned: bool = False):
|
||||||
"""Deletes all messages after specified message.
|
"""Deletes all messages after specified message.
|
||||||
|
|
||||||
To get a message id, enable developer mode in Discord's
|
To get a message id, enable developer mode in Discord's
|
||||||
@ -207,8 +227,7 @@ class Cleanup:
|
|||||||
is_bot = self.bot.user.bot
|
is_bot = self.bot.user.bot
|
||||||
|
|
||||||
if not is_bot:
|
if not is_bot:
|
||||||
await ctx.send(_("This command can only be used on bots with "
|
await ctx.send(_("This command can only be used on bots with " "bot accounts."))
|
||||||
"bot accounts."))
|
|
||||||
return
|
return
|
||||||
|
|
||||||
after = await channel.get_message(message_id)
|
after = await channel.get_message(message_id)
|
||||||
@ -221,9 +240,9 @@ class Cleanup:
|
|||||||
ctx, channel, 0, limit=None, after=after, delete_pinned=delete_pinned
|
ctx, channel, 0, limit=None, after=after, delete_pinned=delete_pinned
|
||||||
)
|
)
|
||||||
|
|
||||||
reason = "{}({}) deleted {} messages in channel {}."\
|
reason = "{}({}) deleted {} messages in channel {}." "".format(
|
||||||
"".format(author.name, author.id,
|
author.name, author.id, len(to_delete), channel.name
|
||||||
len(to_delete), channel.name)
|
)
|
||||||
log.info(reason)
|
log.info(reason)
|
||||||
|
|
||||||
await mass_purge(to_delete, channel)
|
await mass_purge(to_delete, channel)
|
||||||
@ -231,7 +250,7 @@ class Cleanup:
|
|||||||
@cleanup.command()
|
@cleanup.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@commands.bot_has_permissions(manage_messages=True)
|
@commands.bot_has_permissions(manage_messages=True)
|
||||||
async def messages(self, ctx: commands.Context, number: int, delete_pinned: bool=False):
|
async def messages(self, ctx: commands.Context, number: int, delete_pinned: bool = False):
|
||||||
"""Deletes last X messages.
|
"""Deletes last X messages.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
@ -248,14 +267,13 @@ class Cleanup:
|
|||||||
return
|
return
|
||||||
|
|
||||||
to_delete = await self.get_messages_for_deletion(
|
to_delete = await self.get_messages_for_deletion(
|
||||||
ctx, channel, number, limit=1000, before=ctx.message,
|
ctx, channel, number, limit=1000, before=ctx.message, delete_pinned=delete_pinned
|
||||||
delete_pinned=delete_pinned
|
|
||||||
)
|
)
|
||||||
to_delete.append(ctx.message)
|
to_delete.append(ctx.message)
|
||||||
|
|
||||||
reason = "{}({}) deleted {} messages in channel {}."\
|
reason = "{}({}) deleted {} messages in channel {}." "".format(
|
||||||
"".format(author.name, author.id,
|
author.name, author.id, number, channel.name
|
||||||
number, channel.name)
|
)
|
||||||
log.info(reason)
|
log.info(reason)
|
||||||
|
|
||||||
if is_bot:
|
if is_bot:
|
||||||
@ -263,10 +281,10 @@ class Cleanup:
|
|||||||
else:
|
else:
|
||||||
await slow_deletion(to_delete)
|
await slow_deletion(to_delete)
|
||||||
|
|
||||||
@cleanup.command(name='bot')
|
@cleanup.command(name="bot")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@commands.bot_has_permissions(manage_messages=True)
|
@commands.bot_has_permissions(manage_messages=True)
|
||||||
async def cleanup_bot(self, ctx: commands.Context, number: int, delete_pinned: bool=False):
|
async def cleanup_bot(self, ctx: commands.Context, number: int, delete_pinned: bool = False):
|
||||||
"""Cleans up command messages and messages from the bot."""
|
"""Cleans up command messages and messages from the bot."""
|
||||||
|
|
||||||
channel = ctx.message.channel
|
channel = ctx.message.channel
|
||||||
@ -283,8 +301,8 @@ class Cleanup:
|
|||||||
prefixes = [prefixes]
|
prefixes = [prefixes]
|
||||||
|
|
||||||
# In case some idiot sets a null prefix
|
# In case some idiot sets a null prefix
|
||||||
if '' in prefixes:
|
if "" in prefixes:
|
||||||
prefixes.remove('')
|
prefixes.remove("")
|
||||||
|
|
||||||
def check(m):
|
def check(m):
|
||||||
if m.author.id == self.bot.user.id:
|
if m.author.id == self.bot.user.id:
|
||||||
@ -293,20 +311,24 @@ class Cleanup:
|
|||||||
return True
|
return True
|
||||||
p = discord.utils.find(m.content.startswith, prefixes)
|
p = discord.utils.find(m.content.startswith, prefixes)
|
||||||
if p and len(p) > 0:
|
if p and len(p) > 0:
|
||||||
cmd_name = m.content[len(p):].split(' ')[0]
|
cmd_name = m.content[len(p):].split(" ")[0]
|
||||||
return bool(self.bot.get_command(cmd_name))
|
return bool(self.bot.get_command(cmd_name))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
to_delete = await self.get_messages_for_deletion(
|
to_delete = await self.get_messages_for_deletion(
|
||||||
ctx, channel, number, check=check, limit=1000, before=ctx.message,
|
ctx,
|
||||||
delete_pinned=delete_pinned
|
channel,
|
||||||
|
number,
|
||||||
|
check=check,
|
||||||
|
limit=1000,
|
||||||
|
before=ctx.message,
|
||||||
|
delete_pinned=delete_pinned,
|
||||||
)
|
)
|
||||||
to_delete.append(ctx.message)
|
to_delete.append(ctx.message)
|
||||||
|
|
||||||
reason = "{}({}) deleted {} "\
|
reason = "{}({}) deleted {} " " command messages in channel {}." "".format(
|
||||||
" command messages in channel {}."\
|
author.name, author.id, len(to_delete), channel.name
|
||||||
"".format(author.name, author.id, len(to_delete),
|
)
|
||||||
channel.name)
|
|
||||||
log.info(reason)
|
log.info(reason)
|
||||||
|
|
||||||
if is_bot:
|
if is_bot:
|
||||||
@ -314,10 +336,14 @@ class Cleanup:
|
|||||||
else:
|
else:
|
||||||
await slow_deletion(to_delete)
|
await slow_deletion(to_delete)
|
||||||
|
|
||||||
@cleanup.command(name='self')
|
@cleanup.command(name="self")
|
||||||
async def cleanup_self(
|
async def cleanup_self(
|
||||||
self, ctx: commands.Context, number: int,
|
self,
|
||||||
match_pattern: str = None, delete_pinned: bool=False):
|
ctx: commands.Context,
|
||||||
|
number: int,
|
||||||
|
match_pattern: str = None,
|
||||||
|
delete_pinned: bool = False,
|
||||||
|
):
|
||||||
"""Cleans up messages owned by the bot.
|
"""Cleans up messages owned by the bot.
|
||||||
|
|
||||||
By default, all messages are cleaned. If a third argument is specified,
|
By default, all messages are cleaned. If a third argument is specified,
|
||||||
@ -343,8 +369,7 @@ class Cleanup:
|
|||||||
me = ctx.guild.me
|
me = ctx.guild.me
|
||||||
can_mass_purge = channel.permissions_for(me).manage_messages
|
can_mass_purge = channel.permissions_for(me).manage_messages
|
||||||
|
|
||||||
use_re = (match_pattern and match_pattern.startswith('r(') and
|
use_re = (match_pattern and match_pattern.startswith("r(") and match_pattern.endswith(")"))
|
||||||
match_pattern.endswith(')'))
|
|
||||||
|
|
||||||
if use_re:
|
if use_re:
|
||||||
match_pattern = match_pattern[1:] # strip 'r'
|
match_pattern = match_pattern[1:] # strip 'r'
|
||||||
@ -352,10 +377,14 @@ class Cleanup:
|
|||||||
|
|
||||||
def content_match(c):
|
def content_match(c):
|
||||||
return bool(match_re.match(c))
|
return bool(match_re.match(c))
|
||||||
|
|
||||||
elif match_pattern:
|
elif match_pattern:
|
||||||
|
|
||||||
def content_match(c):
|
def content_match(c):
|
||||||
return match_pattern in c
|
return match_pattern in c
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
def content_match(_):
|
def content_match(_):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -367,8 +396,13 @@ class Cleanup:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
to_delete = await self.get_messages_for_deletion(
|
to_delete = await self.get_messages_for_deletion(
|
||||||
ctx, channel, number, check=check, limit=1000, before=ctx.message,
|
ctx,
|
||||||
delete_pinned=delete_pinned
|
channel,
|
||||||
|
number,
|
||||||
|
check=check,
|
||||||
|
limit=1000,
|
||||||
|
before=ctx.message,
|
||||||
|
delete_pinned=delete_pinned,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Selfbot convenience, delete trigger message
|
# Selfbot convenience, delete trigger message
|
||||||
@ -376,14 +410,13 @@ class Cleanup:
|
|||||||
to_delete.append(ctx.message)
|
to_delete.append(ctx.message)
|
||||||
|
|
||||||
if channel.name:
|
if channel.name:
|
||||||
channel_name = 'channel ' + channel.name
|
channel_name = "channel " + channel.name
|
||||||
else:
|
else:
|
||||||
channel_name = str(channel)
|
channel_name = str(channel)
|
||||||
|
|
||||||
reason = "{}({}) deleted {} messages "\
|
reason = "{}({}) deleted {} messages " "sent by the bot in {}." "".format(
|
||||||
"sent by the bot in {}."\
|
author.name, author.id, len(to_delete), channel_name
|
||||||
"".format(author.name, author.id, len(to_delete),
|
)
|
||||||
channel_name)
|
|
||||||
log.info(reason)
|
log.info(reason)
|
||||||
|
|
||||||
if is_bot and can_mass_purge:
|
if is_bot and can_mass_purge:
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
TO_TRANSLATE = [
|
TO_TRANSLATE = ["../cleanup.py"]
|
||||||
'../cleanup.py'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def regen_messages():
|
def regen_messages():
|
||||||
subprocess.run(
|
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||||
['pygettext', '-n'] + TO_TRANSLATE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -27,8 +27,8 @@ class AlreadyExists(CCError):
|
|||||||
class CommandObj:
|
class CommandObj:
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
config = kwargs.get('config')
|
config = kwargs.get("config")
|
||||||
self.bot = kwargs.get('bot')
|
self.bot = kwargs.get("bot")
|
||||||
self.db = config.guild
|
self.db = config.guild
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -40,22 +40,27 @@ class CommandObj:
|
|||||||
return customcommands
|
return customcommands
|
||||||
|
|
||||||
async def get_responses(self, ctx):
|
async def get_responses(self, ctx):
|
||||||
intro = (_("Welcome to the interactive random {} maker!\n"
|
intro = (
|
||||||
|
_(
|
||||||
|
"Welcome to the interactive random {} maker!\n"
|
||||||
"Every message you send will be added as one of the random "
|
"Every message you send will be added as one of the random "
|
||||||
"response to choose from once this {} is "
|
"response to choose from once this {} is "
|
||||||
"triggered. To exit this interactive menu, type `{}`").format(
|
"triggered. To exit this interactive menu, type `{}`"
|
||||||
|
).format(
|
||||||
"customcommand", "customcommand", "exit()"
|
"customcommand", "customcommand", "exit()"
|
||||||
))
|
)
|
||||||
|
)
|
||||||
await ctx.send(intro)
|
await ctx.send(intro)
|
||||||
|
|
||||||
def check(m):
|
def check(m):
|
||||||
return m.channel == ctx.channel and m.author == ctx.message.author
|
return m.channel == ctx.channel and m.author == ctx.message.author
|
||||||
|
|
||||||
responses = []
|
responses = []
|
||||||
while True:
|
while True:
|
||||||
await ctx.send(_("Add a random response:"))
|
await ctx.send(_("Add a random response:"))
|
||||||
msg = await self.bot.wait_for('message', check=check)
|
msg = await self.bot.wait_for("message", check=check)
|
||||||
|
|
||||||
if msg.content.lower() == 'exit()':
|
if msg.content.lower() == "exit()":
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
responses.append(msg.content)
|
responses.append(msg.content)
|
||||||
@ -64,44 +69,31 @@ class CommandObj:
|
|||||||
def get_now(self) -> str:
|
def get_now(self) -> str:
|
||||||
# Get current time as a string, for 'created_at' and 'edited_at' fields
|
# Get current time as a string, for 'created_at' and 'edited_at' fields
|
||||||
# in the ccinfo dict
|
# in the ccinfo dict
|
||||||
return '{:%d/%m/%Y %H:%M:%S}'.format(datetime.utcnow())
|
return "{:%d/%m/%Y %H:%M:%S}".format(datetime.utcnow())
|
||||||
|
|
||||||
async def get(self,
|
async def get(self, message: discord.Message, command: str) -> str:
|
||||||
message: discord.Message,
|
|
||||||
command: str) -> str:
|
|
||||||
ccinfo = await self.db(message.guild).commands.get_raw(command, default=None)
|
ccinfo = await self.db(message.guild).commands.get_raw(command, default=None)
|
||||||
if not ccinfo:
|
if not ccinfo:
|
||||||
raise NotFound
|
raise NotFound
|
||||||
else:
|
else:
|
||||||
return ccinfo['response']
|
return ccinfo["response"]
|
||||||
|
|
||||||
async def create(self,
|
async def create(self, ctx: commands.Context, command: str, response):
|
||||||
ctx: commands.Context,
|
|
||||||
command: str,
|
|
||||||
response):
|
|
||||||
"""Create a customcommand"""
|
"""Create a customcommand"""
|
||||||
# Check if this command is already registered as a customcommand
|
# Check if this command is already registered as a customcommand
|
||||||
if await self.db(ctx.guild).commands.get_raw(command, default=None):
|
if await self.db(ctx.guild).commands.get_raw(command, default=None):
|
||||||
raise AlreadyExists()
|
raise AlreadyExists()
|
||||||
author = ctx.message.author
|
author = ctx.message.author
|
||||||
ccinfo = {
|
ccinfo = {
|
||||||
'author': {
|
"author": {"id": author.id, "name": author.name},
|
||||||
'id': author.id,
|
"command": command,
|
||||||
'name': author.name
|
"created_at": self.get_now(),
|
||||||
},
|
"editors": [],
|
||||||
'command': command,
|
"response": response,
|
||||||
'created_at': self.get_now(),
|
|
||||||
'editors': [],
|
|
||||||
'response': response
|
|
||||||
|
|
||||||
}
|
}
|
||||||
await self.db(ctx.guild).commands.set_raw(
|
await self.db(ctx.guild).commands.set_raw(command, value=ccinfo)
|
||||||
command, value=ccinfo)
|
|
||||||
|
|
||||||
async def edit(self,
|
async def edit(self, ctx: commands.Context, command: str, response: None):
|
||||||
ctx: commands.Context,
|
|
||||||
command: str,
|
|
||||||
response: None):
|
|
||||||
"""Edit an already existing custom command"""
|
"""Edit an already existing custom command"""
|
||||||
# Check if this command is registered
|
# Check if this command is registered
|
||||||
if not await self.db(ctx.guild).commands.get_raw(command, default=None):
|
if not await self.db(ctx.guild).commands.get_raw(command, default=None):
|
||||||
@ -114,41 +106,31 @@ class CommandObj:
|
|||||||
return m.channel == ctx.channel and m.author == ctx.message.author
|
return m.channel == ctx.channel and m.author == ctx.message.author
|
||||||
|
|
||||||
if not response:
|
if not response:
|
||||||
await ctx.send(
|
await ctx.send(_("Do you want to create a 'randomized' cc? {}").format("y/n"))
|
||||||
_("Do you want to create a 'randomized' cc? {}").format("y/n")
|
|
||||||
)
|
|
||||||
|
|
||||||
msg = await self.bot.wait_for('message', check=check)
|
msg = await self.bot.wait_for("message", check=check)
|
||||||
if msg.content.lower() == 'y':
|
if msg.content.lower() == "y":
|
||||||
response = await self.get_responses(ctx=ctx)
|
response = await self.get_responses(ctx=ctx)
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("What response do you want?"))
|
await ctx.send(_("What response do you want?"))
|
||||||
response = (await self.bot.wait_for(
|
response = (await self.bot.wait_for("message", check=check)).content
|
||||||
'message', check=check)
|
|
||||||
).content
|
|
||||||
|
|
||||||
ccinfo['response'] = response
|
ccinfo["response"] = response
|
||||||
ccinfo['edited_at'] = self.get_now()
|
ccinfo["edited_at"] = self.get_now()
|
||||||
|
|
||||||
if author.id not in ccinfo['editors']:
|
if author.id not in ccinfo["editors"]:
|
||||||
# Add the person who invoked the `edit` coroutine to the list of
|
# Add the person who invoked the `edit` coroutine to the list of
|
||||||
# editors, if the person is not yet in there
|
# editors, if the person is not yet in there
|
||||||
ccinfo['editors'].append(
|
ccinfo["editors"].append(author.id)
|
||||||
author.id
|
|
||||||
)
|
|
||||||
|
|
||||||
await self.db(ctx.guild).commands.set_raw(
|
await self.db(ctx.guild).commands.set_raw(command, value=ccinfo)
|
||||||
command, value=ccinfo)
|
|
||||||
|
|
||||||
async def delete(self,
|
async def delete(self, ctx: commands.Context, command: str):
|
||||||
ctx: commands.Context,
|
|
||||||
command: str):
|
|
||||||
"""Delete an already exisiting custom command"""
|
"""Delete an already exisiting custom command"""
|
||||||
# Check if this command is registered
|
# Check if this command is registered
|
||||||
if not await self.db(ctx.guild).commands.get_raw(command, default=None):
|
if not await self.db(ctx.guild).commands.get_raw(command, default=None):
|
||||||
raise NotFound()
|
raise NotFound()
|
||||||
await self.db(ctx.guild).commands.set_raw(
|
await self.db(ctx.guild).commands.set_raw(command, value=None)
|
||||||
command, value=None)
|
|
||||||
|
|
||||||
|
|
||||||
@cog_i18n(_)
|
@cog_i18n(_)
|
||||||
@ -159,24 +141,20 @@ class CustomCommands:
|
|||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.key = 414589031223512
|
self.key = 414589031223512
|
||||||
self.config = Config.get_conf(self,
|
self.config = Config.get_conf(self, self.key)
|
||||||
self.key)
|
|
||||||
self.config.register_guild(commands={})
|
self.config.register_guild(commands={})
|
||||||
self.commandobj = CommandObj(config=self.config,
|
self.commandobj = CommandObj(config=self.config, bot=self.bot)
|
||||||
bot=self.bot)
|
|
||||||
|
|
||||||
@commands.group(aliases=["cc"], no_pm=True)
|
@commands.group(aliases=["cc"], no_pm=True)
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def customcom(self,
|
async def customcom(self, ctx: commands.Context):
|
||||||
ctx: commands.Context):
|
|
||||||
"""Custom commands management"""
|
"""Custom commands management"""
|
||||||
if not ctx.invoked_subcommand:
|
if not ctx.invoked_subcommand:
|
||||||
await ctx.send_help()
|
await ctx.send_help()
|
||||||
|
|
||||||
@customcom.group(name="add")
|
@customcom.group(name="add")
|
||||||
@checks.mod_or_permissions(administrator=True)
|
@checks.mod_or_permissions(administrator=True)
|
||||||
async def cc_add(self,
|
async def cc_add(self, ctx: commands.Context):
|
||||||
ctx: commands.Context):
|
|
||||||
"""
|
"""
|
||||||
CCs can be enhanced with arguments:
|
CCs can be enhanced with arguments:
|
||||||
|
|
||||||
@ -192,15 +170,12 @@ class CustomCommands:
|
|||||||
|
|
||||||
{server} message.guild
|
{server} message.guild
|
||||||
"""
|
"""
|
||||||
if not ctx.invoked_subcommand or isinstance(ctx.invoked_subcommand,
|
if not ctx.invoked_subcommand or isinstance(ctx.invoked_subcommand, commands.Group):
|
||||||
commands.Group):
|
|
||||||
await ctx.send_help()
|
await ctx.send_help()
|
||||||
|
|
||||||
@cc_add.command(name='random')
|
@cc_add.command(name="random")
|
||||||
@checks.mod_or_permissions(administrator=True)
|
@checks.mod_or_permissions(administrator=True)
|
||||||
async def cc_add_random(self,
|
async def cc_add_random(self, ctx: commands.Context, command: str):
|
||||||
ctx: commands.Context,
|
|
||||||
command: str):
|
|
||||||
"""
|
"""
|
||||||
Create a CC where it will randomly choose a response!
|
Create a CC where it will randomly choose a response!
|
||||||
Note: This is interactive
|
Note: This is interactive
|
||||||
@ -210,26 +185,20 @@ class CustomCommands:
|
|||||||
|
|
||||||
responses = await self.commandobj.get_responses(ctx=ctx)
|
responses = await self.commandobj.get_responses(ctx=ctx)
|
||||||
try:
|
try:
|
||||||
await self.commandobj.create(ctx=ctx,
|
await self.commandobj.create(ctx=ctx, command=command, response=responses)
|
||||||
command=command,
|
|
||||||
response=responses)
|
|
||||||
await ctx.send(_("Custom command successfully added."))
|
await ctx.send(_("Custom command successfully added."))
|
||||||
except AlreadyExists:
|
except AlreadyExists:
|
||||||
await ctx.send(_(
|
await ctx.send(
|
||||||
"This command already exists. Use "
|
_("This command already exists. Use " "`{}` to edit it.").format(
|
||||||
"`{}` to edit it.").format(
|
|
||||||
"{}customcom edit".format(ctx.prefix)
|
"{}customcom edit".format(ctx.prefix)
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# await ctx.send(str(responses))
|
# await ctx.send(str(responses))
|
||||||
|
|
||||||
@cc_add.command(name="simple")
|
@cc_add.command(name="simple")
|
||||||
@checks.mod_or_permissions(administrator=True)
|
@checks.mod_or_permissions(administrator=True)
|
||||||
async def cc_add_simple(self,
|
async def cc_add_simple(self, ctx, command: str, *, text):
|
||||||
ctx,
|
|
||||||
command: str,
|
|
||||||
*,
|
|
||||||
text):
|
|
||||||
"""Adds a simple custom command
|
"""Adds a simple custom command
|
||||||
Example:
|
Example:
|
||||||
[p]customcom add simple yourcommand Text you want
|
[p]customcom add simple yourcommand Text you want
|
||||||
@ -240,24 +209,18 @@ class CustomCommands:
|
|||||||
await ctx.send(_("That command is already a standard command."))
|
await ctx.send(_("That command is already a standard command."))
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
await self.commandobj.create(ctx=ctx,
|
await self.commandobj.create(ctx=ctx, command=command, response=text)
|
||||||
command=command,
|
|
||||||
response=text)
|
|
||||||
await ctx.send(_("Custom command successfully added."))
|
await ctx.send(_("Custom command successfully added."))
|
||||||
except AlreadyExists:
|
except AlreadyExists:
|
||||||
await ctx.send(_(
|
await ctx.send(
|
||||||
"This command already exists. Use "
|
_("This command already exists. Use " "`{}` to edit it.").format(
|
||||||
"`{}` to edit it.").format(
|
|
||||||
"{}customcom edit".format(ctx.prefix)
|
"{}customcom edit".format(ctx.prefix)
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@customcom.command(name="edit")
|
@customcom.command(name="edit")
|
||||||
@checks.mod_or_permissions(administrator=True)
|
@checks.mod_or_permissions(administrator=True)
|
||||||
async def cc_edit(self,
|
async def cc_edit(self, ctx, command: str, *, text=None):
|
||||||
ctx,
|
|
||||||
command: str,
|
|
||||||
*,
|
|
||||||
text=None):
|
|
||||||
"""Edits a custom command
|
"""Edits a custom command
|
||||||
Example:
|
Example:
|
||||||
[p]customcom edit yourcommand Text you want
|
[p]customcom edit yourcommand Text you want
|
||||||
@ -266,61 +229,57 @@ class CustomCommands:
|
|||||||
command = command.lower()
|
command = command.lower()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.commandobj.edit(ctx=ctx,
|
await self.commandobj.edit(ctx=ctx, command=command, response=text)
|
||||||
command=command,
|
|
||||||
response=text)
|
|
||||||
await ctx.send(_("Custom command successfully edited."))
|
await ctx.send(_("Custom command successfully edited."))
|
||||||
except NotFound:
|
except NotFound:
|
||||||
await ctx.send(_(
|
await ctx.send(
|
||||||
"That command doesn't exist. Use "
|
_("That command doesn't exist. Use " "`{}` to add it.").format(
|
||||||
"`{}` to add it.").format(
|
|
||||||
"{}customcom add".format(ctx.prefix)
|
"{}customcom add".format(ctx.prefix)
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@customcom.command(name="delete")
|
@customcom.command(name="delete")
|
||||||
@checks.mod_or_permissions(administrator=True)
|
@checks.mod_or_permissions(administrator=True)
|
||||||
async def cc_delete(self,
|
async def cc_delete(self, ctx, command: str):
|
||||||
ctx,
|
|
||||||
command: str):
|
|
||||||
"""Deletes a custom command
|
"""Deletes a custom command
|
||||||
Example:
|
Example:
|
||||||
[p]customcom delete yourcommand"""
|
[p]customcom delete yourcommand"""
|
||||||
guild = ctx.message.guild
|
guild = ctx.message.guild
|
||||||
command = command.lower()
|
command = command.lower()
|
||||||
try:
|
try:
|
||||||
await self.commandobj.delete(ctx=ctx,
|
await self.commandobj.delete(ctx=ctx, command=command)
|
||||||
command=command)
|
|
||||||
await ctx.send(_("Custom command successfully deleted."))
|
await ctx.send(_("Custom command successfully deleted."))
|
||||||
except NotFound:
|
except NotFound:
|
||||||
await ctx.send(_("That command doesn't exist."))
|
await ctx.send(_("That command doesn't exist."))
|
||||||
|
|
||||||
@customcom.command(name="list")
|
@customcom.command(name="list")
|
||||||
async def cc_list(self,
|
async def cc_list(self, ctx):
|
||||||
ctx):
|
|
||||||
"""Shows custom commands list"""
|
"""Shows custom commands list"""
|
||||||
|
|
||||||
response = await CommandObj.get_commands(self.config.guild(ctx.guild))
|
response = await CommandObj.get_commands(self.config.guild(ctx.guild))
|
||||||
|
|
||||||
if not response:
|
if not response:
|
||||||
await ctx.send(_(
|
await ctx.send(
|
||||||
|
_(
|
||||||
"There are no custom commands in this server."
|
"There are no custom commands in this server."
|
||||||
" Use `{}` to start adding some.").format(
|
" Use `{}` to start adding some."
|
||||||
|
).format(
|
||||||
"{}customcom add".format(ctx.prefix)
|
"{}customcom add".format(ctx.prefix)
|
||||||
))
|
)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
for command, body in response.items():
|
for command, body in response.items():
|
||||||
responses = body['response']
|
responses = body["response"]
|
||||||
if isinstance(responses, list):
|
if isinstance(responses, list):
|
||||||
result = ", ".join(responses)
|
result = ", ".join(responses)
|
||||||
elif isinstance(responses, str):
|
elif isinstance(responses, str):
|
||||||
result = responses
|
result = responses
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
results.append("{command:<15} : {result}".format(command=command,
|
results.append("{command:<15} : {result}".format(command=command, result=result))
|
||||||
result=result))
|
|
||||||
|
|
||||||
commands = "\n".join(results)
|
commands = "\n".join(results)
|
||||||
|
|
||||||
@ -330,14 +289,13 @@ class CustomCommands:
|
|||||||
for page in pagify(commands, delims=[" ", "\n"]):
|
for page in pagify(commands, delims=[" ", "\n"]):
|
||||||
await ctx.author.send(box(page))
|
await ctx.author.send(box(page))
|
||||||
|
|
||||||
async def on_message(self,
|
async def on_message(self, message):
|
||||||
message):
|
|
||||||
is_private = isinstance(message.channel, discord.abc.PrivateChannel)
|
is_private = isinstance(message.channel, discord.abc.PrivateChannel)
|
||||||
if len(message.content) < 2 or is_private:
|
if len(message.content) < 2 or is_private:
|
||||||
return
|
return
|
||||||
|
|
||||||
guild = message.guild
|
guild = message.guild
|
||||||
prefixes = await self.bot.db.guild(guild).get_raw('prefix', default=[])
|
prefixes = await self.bot.db.guild(guild).get_raw("prefix", default=[])
|
||||||
|
|
||||||
if len(prefixes) < 1:
|
if len(prefixes) < 1:
|
||||||
def_prefixes = await self.bot.get_prefix(message)
|
def_prefixes = await self.bot.get_prefix(message)
|
||||||
@ -358,8 +316,7 @@ class CustomCommands:
|
|||||||
if user_allowed:
|
if user_allowed:
|
||||||
cmd = message.content[len(prefix):]
|
cmd = message.content[len(prefix):]
|
||||||
try:
|
try:
|
||||||
c = await self.commandobj.get(message=message,
|
c = await self.commandobj.get(message=message, command=cmd)
|
||||||
command=cmd)
|
|
||||||
if isinstance(c, list):
|
if isinstance(c, list):
|
||||||
command = random.choice(c)
|
command = random.choice(c)
|
||||||
elif isinstance(c, str):
|
elif isinstance(c, str):
|
||||||
@ -371,18 +328,14 @@ class CustomCommands:
|
|||||||
response = self.format_cc(command, message)
|
response = self.format_cc(command, message)
|
||||||
await message.channel.send(response)
|
await message.channel.send(response)
|
||||||
|
|
||||||
def format_cc(self,
|
def format_cc(self, command, message) -> str:
|
||||||
command,
|
|
||||||
message) -> str:
|
|
||||||
results = re.findall("\{([^}]+)\}", command)
|
results = re.findall("\{([^}]+)\}", command)
|
||||||
for result in results:
|
for result in results:
|
||||||
param = self.transform_parameter(result, message)
|
param = self.transform_parameter(result, message)
|
||||||
command = command.replace("{" + result + "}", param)
|
command = command.replace("{" + result + "}", param)
|
||||||
return command
|
return command
|
||||||
|
|
||||||
def transform_parameter(self,
|
def transform_parameter(self, result, message) -> str:
|
||||||
result,
|
|
||||||
message) -> str:
|
|
||||||
"""
|
"""
|
||||||
For security reasons only specific objects are allowed
|
For security reasons only specific objects are allowed
|
||||||
Internals are ignored
|
Internals are ignored
|
||||||
@ -393,7 +346,7 @@ class CustomCommands:
|
|||||||
"author": message.author,
|
"author": message.author,
|
||||||
"channel": message.channel,
|
"channel": message.channel,
|
||||||
"guild": message.guild,
|
"guild": message.guild,
|
||||||
"server": message.guild
|
"server": message.guild,
|
||||||
}
|
}
|
||||||
if result in objects:
|
if result in objects:
|
||||||
return str(objects[result])
|
return str(objects[result])
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
TO_TRANSLATE = [
|
TO_TRANSLATE = ["../customcom.py"]
|
||||||
'../customcom.py'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def regen_messages():
|
def regen_messages():
|
||||||
subprocess.run(
|
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||||
['pygettext', '-n'] + TO_TRANSLATE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -16,49 +16,49 @@ class SpecResolver(object):
|
|||||||
self.v2path = path
|
self.v2path = path
|
||||||
self.resolved = set()
|
self.resolved = set()
|
||||||
self.available_core_conversions = {
|
self.available_core_conversions = {
|
||||||
'Bank Accounts': {
|
"Bank Accounts": {
|
||||||
'cfg': ('Bank', None, 384734293238749),
|
"cfg": ("Bank", None, 384734293238749),
|
||||||
'file': self.v2path / 'data' / 'economy' / 'bank.json',
|
"file": self.v2path / "data" / "economy" / "bank.json",
|
||||||
'converter': self.bank_accounts_conv_spec
|
"converter": self.bank_accounts_conv_spec,
|
||||||
},
|
},
|
||||||
'Economy Settings': {
|
"Economy Settings": {
|
||||||
'cfg': ('Economy', 'config', 1256844281),
|
"cfg": ("Economy", "config", 1256844281),
|
||||||
'file': self.v2path / 'data' / 'economy' / 'settings.json',
|
"file": self.v2path / "data" / "economy" / "settings.json",
|
||||||
'converter': self.economy_conv_spec
|
"converter": self.economy_conv_spec,
|
||||||
},
|
},
|
||||||
'Mod Log Cases': {
|
"Mod Log Cases": {
|
||||||
'cfg': ('ModLog', None, 1354799444),
|
"cfg": ("ModLog", None, 1354799444),
|
||||||
'file': self.v2path / 'data' / 'mod' / 'modlog.json',
|
"file": self.v2path / "data" / "mod" / "modlog.json",
|
||||||
'converter': None # prevents from showing as available
|
"converter": None, # prevents from showing as available
|
||||||
},
|
},
|
||||||
'Filter': {
|
"Filter": {
|
||||||
'cfg': ('Filter', 'settings', 4766951341),
|
"cfg": ("Filter", "settings", 4766951341),
|
||||||
'file': self.v2path / 'data' / 'mod' / 'filter.json',
|
"file": self.v2path / "data" / "mod" / "filter.json",
|
||||||
'converter': self.filter_conv_spec
|
"converter": self.filter_conv_spec,
|
||||||
},
|
},
|
||||||
'Past Names': {
|
"Past Names": {
|
||||||
'cfg': ('Mod', 'settings', 4961522000),
|
"cfg": ("Mod", "settings", 4961522000),
|
||||||
'file': self.v2path / 'data' / 'mod' / 'past_names.json',
|
"file": self.v2path / "data" / "mod" / "past_names.json",
|
||||||
'converter': self.past_names_conv_spec
|
"converter": self.past_names_conv_spec,
|
||||||
},
|
},
|
||||||
'Past Nicknames': {
|
"Past Nicknames": {
|
||||||
'cfg': ('Mod', 'settings', 4961522000),
|
"cfg": ("Mod", "settings", 4961522000),
|
||||||
'file': self.v2path / 'data' / 'mod' / 'past_nicknames.json',
|
"file": self.v2path / "data" / "mod" / "past_nicknames.json",
|
||||||
'converter': self.past_nicknames_conv_spec
|
"converter": self.past_nicknames_conv_spec,
|
||||||
|
},
|
||||||
|
"Custom Commands": {
|
||||||
|
"cfg": ("CustomCommands", "config", 414589031223512),
|
||||||
|
"file": self.v2path / "data" / "customcom" / "commands.json",
|
||||||
|
"converter": self.customcom_conv_spec,
|
||||||
},
|
},
|
||||||
'Custom Commands': {
|
|
||||||
'cfg': ('CustomCommands', 'config', 414589031223512),
|
|
||||||
'file': self.v2path / 'data' / 'customcom' / 'commands.json',
|
|
||||||
'converter': self.customcom_conv_spec
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self):
|
||||||
return sorted(
|
return sorted(
|
||||||
k for k, v in self.available_core_conversions.items()
|
k
|
||||||
if v['file'].is_file() and v['converter'] is not None
|
for k, v in self.available_core_conversions.items()
|
||||||
and k not in self.resolved
|
if v["file"].is_file() and v["converter"] is not None and k not in self.resolved
|
||||||
)
|
)
|
||||||
|
|
||||||
def unpack(self, parent_key, parent_value):
|
def unpack(self, parent_key, parent_value):
|
||||||
@ -75,15 +75,8 @@ class SpecResolver(object):
|
|||||||
"""Flatten a nested dictionary structure"""
|
"""Flatten a nested dictionary structure"""
|
||||||
dictionary = {(key,): value for key, value in dictionary.items()}
|
dictionary = {(key,): value for key, value in dictionary.items()}
|
||||||
while True:
|
while True:
|
||||||
dictionary = dict(
|
dictionary = dict(chain.from_iterable(starmap(self.unpack, dictionary.items())))
|
||||||
chain.from_iterable(
|
if not any(isinstance(value, dict) for value in dictionary.values()):
|
||||||
starmap(self.unpack, dictionary.items())
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if not any(
|
|
||||||
isinstance(value, dict)
|
|
||||||
for value in dictionary.values()
|
|
||||||
):
|
|
||||||
break
|
break
|
||||||
return dictionary
|
return dictionary
|
||||||
|
|
||||||
@ -97,11 +90,8 @@ class SpecResolver(object):
|
|||||||
outerkey, innerkey = tuple(k[:-1]), (k[-1],)
|
outerkey, innerkey = tuple(k[:-1]), (k[-1],)
|
||||||
if outerkey not in ret:
|
if outerkey not in ret:
|
||||||
ret[outerkey] = {}
|
ret[outerkey] = {}
|
||||||
if innerkey[0] == 'created_at':
|
if innerkey[0] == "created_at":
|
||||||
x = int(
|
x = int(datetime.strptime(v, "%Y-%m-%d %H:%M:%S").timestamp())
|
||||||
datetime.strptime(
|
|
||||||
v, "%Y-%m-%d %H:%M:%S").timestamp()
|
|
||||||
)
|
|
||||||
ret[outerkey].update({innerkey: x})
|
ret[outerkey].update({innerkey: x})
|
||||||
else:
|
else:
|
||||||
ret[outerkey].update({innerkey: v})
|
ret[outerkey].update({innerkey: v})
|
||||||
@ -121,16 +111,10 @@ class SpecResolver(object):
|
|||||||
raise NotImplementedError("This one isn't ready yet")
|
raise NotImplementedError("This one isn't ready yet")
|
||||||
|
|
||||||
def filter_conv_spec(self, data: dict):
|
def filter_conv_spec(self, data: dict):
|
||||||
return {
|
return {(Config.GUILD, k): {("filter",): v} for k, v in data.items()}
|
||||||
(Config.GUILD, k): {('filter',): v}
|
|
||||||
for k, v in data.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
def past_names_conv_spec(self, data: dict):
|
def past_names_conv_spec(self, data: dict):
|
||||||
return {
|
return {(Config.USER, k): {("past_names",): v} for k, v in data.items()}
|
||||||
(Config.USER, k): {('past_names',): v}
|
|
||||||
for k, v in data.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
def past_nicknames_conv_spec(self, data: dict):
|
def past_nicknames_conv_spec(self, data: dict):
|
||||||
flatscoped = self.apply_scope(Config.MEMBER, self.flatten_dict(data))
|
flatscoped = self.apply_scope(Config.MEMBER, self.flatten_dict(data))
|
||||||
@ -146,19 +130,16 @@ class SpecResolver(object):
|
|||||||
flatscoped = self.apply_scope(Config.GUILD, self.flatten_dict(data))
|
flatscoped = self.apply_scope(Config.GUILD, self.flatten_dict(data))
|
||||||
ret = {}
|
ret = {}
|
||||||
for k, v in flatscoped.items():
|
for k, v in flatscoped.items():
|
||||||
outerkey, innerkey = (*k[:-1],), ('commands', k[-1])
|
outerkey, innerkey = (*k[:-1],), ("commands", k[-1])
|
||||||
if outerkey not in ret:
|
if outerkey not in ret:
|
||||||
ret[outerkey] = {}
|
ret[outerkey] = {}
|
||||||
|
|
||||||
ccinfo = {
|
ccinfo = {
|
||||||
'author': {
|
"author": {"id": 42, "name": "Converted from a v2 instance"},
|
||||||
'id': 42,
|
"command": k[-1],
|
||||||
'name': 'Converted from a v2 instance'
|
"created_at": "{:%d/%m/%Y %H:%M:%S}".format(datetime.utcnow()),
|
||||||
},
|
"editors": [],
|
||||||
'command': k[-1],
|
"response": v,
|
||||||
'created_at': '{:%d/%m/%Y %H:%M:%S}'.format(datetime.utcnow()),
|
|
||||||
'editors': [],
|
|
||||||
'response': v
|
|
||||||
}
|
}
|
||||||
ret[outerkey].update({innerkey: ccinfo})
|
ret[outerkey].update({innerkey: ccinfo})
|
||||||
return ret
|
return ret
|
||||||
@ -168,8 +149,8 @@ class SpecResolver(object):
|
|||||||
raise NotImplementedError("No Conversion Specs for this")
|
raise NotImplementedError("No Conversion Specs for this")
|
||||||
|
|
||||||
info = self.available_core_conversions[prettyname]
|
info = self.available_core_conversions[prettyname]
|
||||||
filepath, converter = info['file'], info['converter']
|
filepath, converter = info["file"], info["converter"]
|
||||||
(cogname, attr, _id) = info['cfg']
|
(cogname, attr, _id) = info["cfg"]
|
||||||
try:
|
try:
|
||||||
config = getattr(bot.get_cog(cogname), attr)
|
config = getattr(bot.get_cog(cogname), attr)
|
||||||
except (TypeError, AttributeError):
|
except (TypeError, AttributeError):
|
||||||
|
|||||||
@ -7,7 +7,7 @@ from redbot.core.i18n import Translator, cog_i18n
|
|||||||
from redbot.cogs.dataconverter.core_specs import SpecResolver
|
from redbot.cogs.dataconverter.core_specs import SpecResolver
|
||||||
from redbot.core.utils.chat_formatting import box
|
from redbot.core.utils.chat_formatting import box
|
||||||
|
|
||||||
_ = Translator('DataConverter', __file__)
|
_ = Translator("DataConverter", __file__)
|
||||||
|
|
||||||
|
|
||||||
@cog_i18n(_)
|
@cog_i18n(_)
|
||||||
@ -34,13 +34,14 @@ class DataConverter:
|
|||||||
|
|
||||||
if not resolver.available:
|
if not resolver.available:
|
||||||
return await ctx.send(
|
return await ctx.send(
|
||||||
_("There don't seem to be any data files I know how to "
|
_(
|
||||||
|
"There don't seem to be any data files I know how to "
|
||||||
"handle here. Are you sure you gave me the base "
|
"handle here. Are you sure you gave me the base "
|
||||||
"installation path?")
|
"installation path?"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
while resolver.available:
|
while resolver.available:
|
||||||
menu = _("Please select a set of data to import by number"
|
menu = _("Please select a set of data to import by number" ", or 'exit' to exit")
|
||||||
", or 'exit' to exit")
|
|
||||||
for index, entry in enumerate(resolver.available, 1):
|
for index, entry in enumerate(resolver.available, 1):
|
||||||
menu += "\n{}. {}".format(index, entry)
|
menu += "\n{}. {}".format(index, entry)
|
||||||
|
|
||||||
@ -50,24 +51,17 @@ class DataConverter:
|
|||||||
return m.channel == ctx.channel and m.author == ctx.author
|
return m.channel == ctx.channel and m.author == ctx.author
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message = await self.bot.wait_for(
|
message = await self.bot.wait_for("message", check=pred, timeout=60)
|
||||||
'message', check=pred, timeout=60
|
|
||||||
)
|
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
return await ctx.send(
|
return await ctx.send(_("Try this again when you are more ready"))
|
||||||
_('Try this again when you are more ready'))
|
|
||||||
else:
|
else:
|
||||||
if message.content.strip().lower() in [
|
if message.content.strip().lower() in ["quit", "exit", "-1", "q", "cancel"]:
|
||||||
'quit', 'exit', '-1', 'q', 'cancel'
|
|
||||||
]:
|
|
||||||
return await ctx.tick()
|
return await ctx.tick()
|
||||||
try:
|
try:
|
||||||
message = int(message.content.strip())
|
message = int(message.content.strip())
|
||||||
to_conv = resolver.available[message - 1]
|
to_conv = resolver.available[message - 1]
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
await ctx.send(
|
await ctx.send(_("That wasn't a valid choice."))
|
||||||
_("That wasn't a valid choice.")
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
async with ctx.typing():
|
async with ctx.typing():
|
||||||
@ -76,6 +70,8 @@ class DataConverter:
|
|||||||
await menu_message.delete()
|
await menu_message.delete()
|
||||||
else:
|
else:
|
||||||
return await ctx.send(
|
return await ctx.send(
|
||||||
_("There isn't anything else I know how to convert here."
|
_(
|
||||||
"\nThere might be more things I can convert in the future.")
|
"There isn't anything else I know how to convert here."
|
||||||
|
"\nThere might be more things I can convert in the future."
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
TO_TRANSLATE = [
|
TO_TRANSLATE = ["../dataconverter.py"]
|
||||||
'../dataconverter.py'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def regen_messages():
|
def regen_messages():
|
||||||
subprocess.run(
|
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||||
['pygettext', '-n'] + TO_TRANSLATE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import asyncio
|
|||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
__all__ = ["install_agreement", ]
|
__all__ = ["install_agreement"]
|
||||||
|
|
||||||
REPO_INSTALL_MSG = (
|
REPO_INSTALL_MSG = (
|
||||||
"You're about to add a 3rd party repository. The creator of Red"
|
"You're about to add a 3rd party repository. The creator of Red"
|
||||||
@ -17,29 +17,28 @@ REPO_INSTALL_MSG = (
|
|||||||
|
|
||||||
|
|
||||||
def install_agreement():
|
def install_agreement():
|
||||||
|
|
||||||
async def pred(ctx: commands.Context):
|
async def pred(ctx: commands.Context):
|
||||||
downloader = ctx.command.instance
|
downloader = ctx.command.instance
|
||||||
if downloader is None:
|
if downloader is None:
|
||||||
return True
|
return True
|
||||||
elif downloader.already_agreed:
|
elif downloader.already_agreed:
|
||||||
return True
|
return True
|
||||||
elif ctx.invoked_subcommand is None or \
|
elif ctx.invoked_subcommand is None or isinstance(ctx.invoked_subcommand, commands.Group):
|
||||||
isinstance(ctx.invoked_subcommand, commands.Group):
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def does_agree(msg: discord.Message):
|
def does_agree(msg: discord.Message):
|
||||||
return ctx.author == msg.author and \
|
return ctx.author == msg.author and ctx.channel == msg.channel and msg.content == "I agree"
|
||||||
ctx.channel == msg.channel and \
|
|
||||||
msg.content == "I agree"
|
|
||||||
|
|
||||||
await ctx.send(REPO_INSTALL_MSG)
|
await ctx.send(REPO_INSTALL_MSG)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await ctx.bot.wait_for('message', check=does_agree, timeout=30)
|
await ctx.bot.wait_for("message", check=does_agree, timeout=30)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
await ctx.send("Your response has timed out, please try again.")
|
await ctx.send("Your response has timed out, please try again.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
downloader.already_agreed = True
|
downloader.already_agreed = True
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return commands.check(pred)
|
return commands.check(pred)
|
||||||
|
|||||||
@ -5,6 +5,7 @@ from .installable import Installable
|
|||||||
|
|
||||||
|
|
||||||
class InstalledCog(commands.Converter):
|
class InstalledCog(commands.Converter):
|
||||||
|
|
||||||
async def convert(self, ctx: commands.Context, arg: str) -> Installable:
|
async def convert(self, ctx: commands.Context, arg: str) -> Installable:
|
||||||
downloader = ctx.bot.get_cog("Downloader")
|
downloader = ctx.bot.get_cog("Downloader")
|
||||||
if downloader is None:
|
if downloader is None:
|
||||||
@ -12,8 +13,6 @@ class InstalledCog(commands.Converter):
|
|||||||
|
|
||||||
cog = discord.utils.get(await downloader.installed_cogs(), name=arg)
|
cog = discord.utils.get(await downloader.installed_cogs(), name=arg)
|
||||||
if cog is None:
|
if cog is None:
|
||||||
raise commands.BadArgument(
|
raise commands.BadArgument("That cog is not installed")
|
||||||
"That cog is not installed"
|
|
||||||
)
|
|
||||||
|
|
||||||
return cog
|
return cog
|
||||||
|
|||||||
@ -22,20 +22,18 @@ from .installable import Installable
|
|||||||
from .log import log
|
from .log import log
|
||||||
from .repo_manager import RepoManager, Repo
|
from .repo_manager import RepoManager, Repo
|
||||||
|
|
||||||
_ = Translator('Downloader', __file__)
|
_ = Translator("Downloader", __file__)
|
||||||
|
|
||||||
|
|
||||||
@cog_i18n(_)
|
@cog_i18n(_)
|
||||||
class Downloader:
|
class Downloader:
|
||||||
|
|
||||||
def __init__(self, bot: Red):
|
def __init__(self, bot: Red):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
|
||||||
self.conf = Config.get_conf(self, identifier=998240343,
|
self.conf = Config.get_conf(self, identifier=998240343, force_registration=True)
|
||||||
force_registration=True)
|
|
||||||
|
|
||||||
self.conf.register_global(
|
self.conf.register_global(installed=[])
|
||||||
installed=[]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.already_agreed = False
|
self.already_agreed = False
|
||||||
|
|
||||||
@ -46,7 +44,7 @@ class Downloader:
|
|||||||
self.LIB_PATH.mkdir(parents=True, exist_ok=True)
|
self.LIB_PATH.mkdir(parents=True, exist_ok=True)
|
||||||
self.SHAREDLIB_PATH.mkdir(parents=True, exist_ok=True)
|
self.SHAREDLIB_PATH.mkdir(parents=True, exist_ok=True)
|
||||||
if not self.SHAREDLIB_INIT.exists():
|
if not self.SHAREDLIB_INIT.exists():
|
||||||
with self.SHAREDLIB_INIT.open(mode='w', encoding='utf-8') as _:
|
with self.SHAREDLIB_INIT.open(mode="w", encoding="utf-8") as _:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if str(self.LIB_PATH) not in syspath:
|
if str(self.LIB_PATH) not in syspath:
|
||||||
@ -170,7 +168,7 @@ class Downloader:
|
|||||||
for repo, reqs in has_reqs:
|
for repo, reqs in has_reqs:
|
||||||
for req in reqs:
|
for req in reqs:
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
ret = ret and await repo.install_raw_requirements([req, ], self.LIB_PATH)
|
ret = ret and await repo.install_raw_requirements([req], self.LIB_PATH)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -200,8 +198,12 @@ class Downloader:
|
|||||||
if success:
|
if success:
|
||||||
await ctx.send(_("Libraries installed."))
|
await ctx.send(_("Libraries installed."))
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Some libraries failed to install. Please check"
|
await ctx.send(
|
||||||
" your logs for a complete list."))
|
_(
|
||||||
|
"Some libraries failed to install. Please check"
|
||||||
|
" your logs for a complete list."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
@ -214,7 +216,7 @@ class Downloader:
|
|||||||
|
|
||||||
@repo.command(name="add")
|
@repo.command(name="add")
|
||||||
@install_agreement()
|
@install_agreement()
|
||||||
async def _repo_add(self, ctx, name: str, repo_url: str, branch: str=None):
|
async def _repo_add(self, ctx, name: str, repo_url: str, branch: str = None):
|
||||||
"""
|
"""
|
||||||
Add a new repo to Downloader.
|
Add a new repo to Downloader.
|
||||||
|
|
||||||
@ -223,11 +225,7 @@ class Downloader:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
repo = await self._repo_manager.add_repo(
|
repo = await self._repo_manager.add_repo(name=name, url=repo_url, branch=branch)
|
||||||
name=name,
|
|
||||||
url=repo_url,
|
|
||||||
branch=branch
|
|
||||||
)
|
|
||||||
except ExistingGitRepo:
|
except ExistingGitRepo:
|
||||||
await ctx.send(_("That git repo has already been added under another name."))
|
await ctx.send(_("That git repo has already been added under another name."))
|
||||||
except CloningError:
|
except CloningError:
|
||||||
@ -275,20 +273,28 @@ class Downloader:
|
|||||||
"""
|
"""
|
||||||
cog = discord.utils.get(repo_name.available_cogs, name=cog_name) # type: Installable
|
cog = discord.utils.get(repo_name.available_cogs, name=cog_name) # type: Installable
|
||||||
if cog is None:
|
if cog is None:
|
||||||
await ctx.send(_("Error, there is no cog by the name of"
|
await ctx.send(
|
||||||
" `{}` in the `{}` repo.").format(cog_name, repo_name.name))
|
_("Error, there is no cog by the name of" " `{}` in the `{}` repo.").format(
|
||||||
|
cog_name, repo_name.name
|
||||||
|
)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
elif cog.min_python_version > sys.version_info:
|
elif cog.min_python_version > sys.version_info:
|
||||||
await ctx.send(_(
|
await ctx.send(
|
||||||
|
_(
|
||||||
"This cog requires at least python version {}, aborting install.".format(
|
"This cog requires at least python version {}, aborting install.".format(
|
||||||
'.'.join([str(n) for n in cog.min_python_version])
|
".".join([str(n) for n in cog.min_python_version])
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
))
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if not await repo_name.install_requirements(cog, self.LIB_PATH):
|
if not await repo_name.install_requirements(cog, self.LIB_PATH):
|
||||||
await ctx.send(_("Failed to install the required libraries for"
|
await ctx.send(
|
||||||
" `{}`: `{}`").format(cog.name, cog.requirements))
|
_("Failed to install the required libraries for" " `{}`: `{}`").format(
|
||||||
|
cog.name, cog.requirements
|
||||||
|
)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
await repo_name.install_cog(cog, await self.cog_install_path())
|
await repo_name.install_cog(cog, await self.cog_install_path())
|
||||||
@ -317,12 +323,16 @@ class Downloader:
|
|||||||
await self._remove_from_installed(cog_name)
|
await self._remove_from_installed(cog_name)
|
||||||
await ctx.send(_("`{}` was successfully removed.").format(real_name))
|
await ctx.send(_("`{}` was successfully removed.").format(real_name))
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("That cog was installed but can no longer"
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"That cog was installed but can no longer"
|
||||||
" be located. You may need to remove it's"
|
" be located. You may need to remove it's"
|
||||||
" files manually if it is still usable."))
|
" files manually if it is still usable."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@cog.command(name="update")
|
@cog.command(name="update")
|
||||||
async def _cog_update(self, ctx, cog_name: InstalledCog=None):
|
async def _cog_update(self, ctx, cog_name: InstalledCog = None):
|
||||||
"""
|
"""
|
||||||
Updates all cogs or one of your choosing.
|
Updates all cogs or one of your choosing.
|
||||||
"""
|
"""
|
||||||
@ -358,7 +368,8 @@ class Downloader:
|
|||||||
"""
|
"""
|
||||||
cogs = repo_name.available_cogs
|
cogs = repo_name.available_cogs
|
||||||
cogs = _("Available Cogs:\n") + "\n".join(
|
cogs = _("Available Cogs:\n") + "\n".join(
|
||||||
["+ {}: {}".format(c.name, c.short or "") for c in cogs])
|
["+ {}: {}".format(c.name, c.short or "") for c in cogs]
|
||||||
|
)
|
||||||
|
|
||||||
await ctx.send(box(cogs, lang="diff"))
|
await ctx.send(box(cogs, lang="diff"))
|
||||||
|
|
||||||
@ -369,9 +380,9 @@ class Downloader:
|
|||||||
"""
|
"""
|
||||||
cog = discord.utils.get(repo_name.available_cogs, name=cog_name)
|
cog = discord.utils.get(repo_name.available_cogs, name=cog_name)
|
||||||
if cog is None:
|
if cog is None:
|
||||||
await ctx.send(_("There is no cog `{}` in the repo `{}`").format(
|
await ctx.send(
|
||||||
cog_name, repo_name.name
|
_("There is no cog `{}` in the repo `{}`").format(cog_name, repo_name.name)
|
||||||
))
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
msg = _("Information on {}:\n{}").format(cog.name, cog.description or "")
|
msg = _("Information on {}:\n{}").format(cog.name, cog.description or "")
|
||||||
@ -397,8 +408,9 @@ class Downloader:
|
|||||||
return True, installable
|
return True, installable
|
||||||
return False, None
|
return False, None
|
||||||
|
|
||||||
def format_findcog_info(self, command_name: str,
|
def format_findcog_info(
|
||||||
cog_installable: Union[Installable, object]=None) -> str:
|
self, command_name: str, cog_installable: Union[Installable, object] = None
|
||||||
|
) -> str:
|
||||||
"""Format a cog's info for output to discord.
|
"""Format a cog's info for output to discord.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -444,7 +456,7 @@ class Downloader:
|
|||||||
The name of the cog according to Downloader..
|
The name of the cog according to Downloader..
|
||||||
|
|
||||||
"""
|
"""
|
||||||
splitted = instance.__module__.split('.')
|
splitted = instance.__module__.split(".")
|
||||||
return splitted[-2]
|
return splitted[-2]
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
|
|||||||
@ -1,6 +1,16 @@
|
|||||||
__all__ = ["DownloaderException", "GitException", "InvalidRepoName", "ExistingGitRepo",
|
__all__ = [
|
||||||
"MissingGitRepo", "CloningError", "CurrentHashError", "HardResetError",
|
"DownloaderException",
|
||||||
"UpdateError", "GitDiffError", "PipError"]
|
"GitException",
|
||||||
|
"InvalidRepoName",
|
||||||
|
"ExistingGitRepo",
|
||||||
|
"MissingGitRepo",
|
||||||
|
"CloningError",
|
||||||
|
"CurrentHashError",
|
||||||
|
"HardResetError",
|
||||||
|
"UpdateError",
|
||||||
|
"GitDiffError",
|
||||||
|
"PipError",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class DownloaderException(Exception):
|
class DownloaderException(Exception):
|
||||||
|
|||||||
@ -56,6 +56,7 @@ class Installable(RepoJSONMixin):
|
|||||||
:class:`InstallationType`.
|
:class:`InstallationType`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, location: Path):
|
def __init__(self, location: Path):
|
||||||
"""Base installable initializer.
|
"""Base installable initializer.
|
||||||
|
|
||||||
@ -114,13 +115,9 @@ class Installable(RepoJSONMixin):
|
|||||||
|
|
||||||
# noinspection PyBroadException
|
# noinspection PyBroadException
|
||||||
try:
|
try:
|
||||||
copy_func(
|
copy_func(src=str(self._location), dst=str(target_dir / self._location.stem))
|
||||||
src=str(self._location),
|
|
||||||
dst=str(target_dir / self._location.stem)
|
|
||||||
)
|
|
||||||
except:
|
except:
|
||||||
log.exception("Error occurred when copying path:"
|
log.exception("Error occurred when copying path:" " {}".format(self._location))
|
||||||
" {}".format(self._location))
|
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -130,7 +127,7 @@ class Installable(RepoJSONMixin):
|
|||||||
if self._info_file.exists():
|
if self._info_file.exists():
|
||||||
self._process_info_file()
|
self._process_info_file()
|
||||||
|
|
||||||
def _process_info_file(self, info_file_path: Path=None) -> MutableMapping[str, Any]:
|
def _process_info_file(self, info_file_path: Path = None) -> MutableMapping[str, Any]:
|
||||||
"""
|
"""
|
||||||
Processes an information file. Loads dependencies among other
|
Processes an information file. Loads dependencies among other
|
||||||
information into this object.
|
information into this object.
|
||||||
@ -144,13 +141,14 @@ class Installable(RepoJSONMixin):
|
|||||||
raise ValueError("No valid information file path was found.")
|
raise ValueError("No valid information file path was found.")
|
||||||
|
|
||||||
info = {}
|
info = {}
|
||||||
with info_file_path.open(encoding='utf-8') as f:
|
with info_file_path.open(encoding="utf-8") as f:
|
||||||
try:
|
try:
|
||||||
info = json.load(f)
|
info = json.load(f)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
info = {}
|
info = {}
|
||||||
log.exception("Invalid JSON information file at path:"
|
log.exception(
|
||||||
" {}".format(info_file_path))
|
"Invalid JSON information file at path:" " {}".format(info_file_path)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self._info = info
|
self._info = info
|
||||||
|
|
||||||
@ -167,7 +165,7 @@ class Installable(RepoJSONMixin):
|
|||||||
self.bot_version = bot_version
|
self.bot_version = bot_version
|
||||||
|
|
||||||
try:
|
try:
|
||||||
min_python_version = tuple(info.get('min_python_version', [3, 5, 1]))
|
min_python_version = tuple(info.get("min_python_version", [3, 5, 1]))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
min_python_version = self.min_python_version
|
min_python_version = self.min_python_version
|
||||||
self.min_python_version = min_python_version
|
self.min_python_version = min_python_version
|
||||||
@ -200,15 +198,12 @@ class Installable(RepoJSONMixin):
|
|||||||
return info
|
return info
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
return {
|
return {"repo_name": self.repo_name, "cog_name": self.name}
|
||||||
"repo_name": self.repo_name,
|
|
||||||
"cog_name": self.name
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_json(cls, data: dict, repo_mgr: "RepoManager"):
|
def from_json(cls, data: dict, repo_mgr: "RepoManager"):
|
||||||
repo_name = data['repo_name']
|
repo_name = data["repo_name"]
|
||||||
cog_name = data['cog_name']
|
cog_name = data["cog_name"]
|
||||||
|
|
||||||
repo = repo_mgr.get_repo(repo_name)
|
repo = repo_mgr.get_repo(repo_name)
|
||||||
if repo is not None:
|
if repo is not None:
|
||||||
|
|||||||
@ -24,7 +24,7 @@ class RepoJSONMixin:
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with self._info_file.open(encoding='utf-8') as f:
|
with self._info_file.open(encoding="utf-8") as f:
|
||||||
info = json.load(f)
|
info = json.load(f)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
return
|
return
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
TO_TRANSLATE = [
|
TO_TRANSLATE = ["../downloader.py"]
|
||||||
'../downloader.py'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def regen_messages():
|
def regen_messages():
|
||||||
subprocess.run(
|
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||||
['pygettext', '-n'] + TO_TRANSLATE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -27,16 +27,23 @@ class Repo(RepoJSONMixin):
|
|||||||
GIT_LATEST_COMMIT = "git -C {path} rev-parse {branch}"
|
GIT_LATEST_COMMIT = "git -C {path} rev-parse {branch}"
|
||||||
GIT_HARD_RESET = "git -C {path} reset --hard origin/{branch} -q"
|
GIT_HARD_RESET = "git -C {path} reset --hard origin/{branch} -q"
|
||||||
GIT_PULL = "git -C {path} pull -q --ff-only"
|
GIT_PULL = "git -C {path} pull -q --ff-only"
|
||||||
GIT_DIFF_FILE_STATUS = ("git -C {path} diff --no-commit-id --name-status"
|
GIT_DIFF_FILE_STATUS = (
|
||||||
" {old_hash} {new_hash}")
|
"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}")
|
GIT_LOG = ("git -C {path} log --relative-date --reverse {old_hash}.." " {relative_file_path}")
|
||||||
GIT_DISCOVER_REMOTE_URL = "git -C {path} config --get remote.origin.url"
|
GIT_DISCOVER_REMOTE_URL = "git -C {path} config --get remote.origin.url"
|
||||||
|
|
||||||
PIP_INSTALL = "{python} -m pip install -U -t {target_dir} {reqs}"
|
PIP_INSTALL = "{python} -m pip install -U -t {target_dir} {reqs}"
|
||||||
|
|
||||||
def __init__(self, name: str, url: str, branch: str, folder_path: Path,
|
def __init__(
|
||||||
available_modules: Tuple[Installable]=(), loop: asyncio.AbstractEventLoop=None):
|
self,
|
||||||
|
name: str,
|
||||||
|
url: str,
|
||||||
|
branch: str,
|
||||||
|
folder_path: Path,
|
||||||
|
available_modules: Tuple[Installable] = (),
|
||||||
|
loop: asyncio.AbstractEventLoop = None,
|
||||||
|
):
|
||||||
self.url = url
|
self.url = url
|
||||||
self.branch = branch
|
self.branch = branch
|
||||||
|
|
||||||
@ -71,11 +78,12 @@ class Repo(RepoJSONMixin):
|
|||||||
return poss_repo
|
return poss_repo
|
||||||
|
|
||||||
def _existing_git_repo(self) -> (bool, Path):
|
def _existing_git_repo(self) -> (bool, Path):
|
||||||
git_path = self.folder_path / '.git'
|
git_path = self.folder_path / ".git"
|
||||||
return git_path.exists(), git_path
|
return git_path.exists(), git_path
|
||||||
|
|
||||||
async def _get_file_update_statuses(
|
async def _get_file_update_statuses(
|
||||||
self, old_hash: str, new_hash: str) -> MutableMapping[str, str]:
|
self, old_hash: str, new_hash: str
|
||||||
|
) -> MutableMapping[str, str]:
|
||||||
"""
|
"""
|
||||||
Gets the file update status letters for each changed file between
|
Gets the file update status letters for each changed file between
|
||||||
the two hashes.
|
the two hashes.
|
||||||
@ -85,29 +93,25 @@ class Repo(RepoJSONMixin):
|
|||||||
"""
|
"""
|
||||||
p = await self._run(
|
p = await self._run(
|
||||||
self.GIT_DIFF_FILE_STATUS.format(
|
self.GIT_DIFF_FILE_STATUS.format(
|
||||||
path=self.folder_path,
|
path=self.folder_path, old_hash=old_hash, new_hash=new_hash
|
||||||
old_hash=old_hash,
|
|
||||||
new_hash=new_hash
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
raise GitDiffError("Git diff failed for repo at path:"
|
raise GitDiffError("Git diff failed for repo at path:" " {}".format(self.folder_path))
|
||||||
" {}".format(self.folder_path))
|
|
||||||
|
|
||||||
stdout = p.stdout.strip().decode().split('\n')
|
stdout = p.stdout.strip().decode().split("\n")
|
||||||
|
|
||||||
ret = {}
|
ret = {}
|
||||||
|
|
||||||
for filename in stdout:
|
for filename in stdout:
|
||||||
# TODO: filter these filenames by ones in self.available_modules
|
# TODO: filter these filenames by ones in self.available_modules
|
||||||
status, _, filepath = filename.partition('\t')
|
status, _, filepath = filename.partition("\t")
|
||||||
ret[filepath] = status
|
ret[filepath] = status
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
async def _get_commit_notes(self, old_commit_hash: str,
|
async def _get_commit_notes(self, old_commit_hash: str, relative_file_path: str) -> str:
|
||||||
relative_file_path: str) -> str:
|
|
||||||
"""
|
"""
|
||||||
Gets the commit notes from git log.
|
Gets the commit notes from git log.
|
||||||
:param old_commit_hash: Point in time to start getting messages
|
:param old_commit_hash: Point in time to start getting messages
|
||||||
@ -119,13 +123,15 @@ class Repo(RepoJSONMixin):
|
|||||||
self.GIT_LOG.format(
|
self.GIT_LOG.format(
|
||||||
path=self.folder_path,
|
path=self.folder_path,
|
||||||
old_hash=old_commit_hash,
|
old_hash=old_commit_hash,
|
||||||
relative_file_path=relative_file_path
|
relative_file_path=relative_file_path,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
raise GitException("An exception occurred while executing git log on"
|
raise GitException(
|
||||||
" this repo: {}".format(self.folder_path))
|
"An exception occurred while executing git log on"
|
||||||
|
" this repo: {}".format(self.folder_path)
|
||||||
|
)
|
||||||
|
|
||||||
return p.stdout.decode().strip()
|
return p.stdout.decode().strip()
|
||||||
|
|
||||||
@ -146,10 +152,8 @@ class Repo(RepoJSONMixin):
|
|||||||
Installable(location=name)
|
Installable(location=name)
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
for file_finder, name, is_pkg in pkgutil.walk_packages(path=[str(self.folder_path), ]):
|
for file_finder, name, is_pkg in pkgutil.walk_packages(path=[str(self.folder_path)]):
|
||||||
curr_modules.append(
|
curr_modules.append(Installable(location=self.folder_path / name))
|
||||||
Installable(location=self.folder_path / name)
|
|
||||||
)
|
|
||||||
self.available_modules = curr_modules
|
self.available_modules = curr_modules
|
||||||
|
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
@ -157,12 +161,11 @@ class Repo(RepoJSONMixin):
|
|||||||
|
|
||||||
async def _run(self, *args, **kwargs):
|
async def _run(self, *args, **kwargs):
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env['GIT_TERMINAL_PROMPT'] = '0'
|
env["GIT_TERMINAL_PROMPT"] = "0"
|
||||||
kwargs['env'] = env
|
kwargs["env"] = env
|
||||||
async with self._repo_lock:
|
async with self._repo_lock:
|
||||||
return await self._loop.run_in_executor(
|
return await self._loop.run_in_executor(
|
||||||
self._executor,
|
self._executor, functools.partial(sp_run, *args, stdout=PIPE, **kwargs)
|
||||||
functools.partial(sp_run, *args, stdout=PIPE, **kwargs)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def clone(self) -> Tuple[str]:
|
async def clone(self) -> Tuple[str]:
|
||||||
@ -176,24 +179,17 @@ class Repo(RepoJSONMixin):
|
|||||||
"""
|
"""
|
||||||
exists, path = self._existing_git_repo()
|
exists, path = self._existing_git_repo()
|
||||||
if exists:
|
if exists:
|
||||||
raise ExistingGitRepo(
|
raise ExistingGitRepo("A git repo already exists at path: {}".format(path))
|
||||||
"A git repo already exists at path: {}".format(path)
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.branch is not None:
|
if self.branch is not None:
|
||||||
p = await self._run(
|
p = await self._run(
|
||||||
self.GIT_CLONE.format(
|
self.GIT_CLONE.format(
|
||||||
branch=self.branch,
|
branch=self.branch, url=self.url, folder=self.folder_path
|
||||||
url=self.url,
|
|
||||||
folder=self.folder_path
|
|
||||||
).split()
|
).split()
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
p = await self._run(
|
p = await self._run(
|
||||||
self.GIT_CLONE_NO_BRANCH.format(
|
self.GIT_CLONE_NO_BRANCH.format(url=self.url, folder=self.folder_path).split()
|
||||||
url=self.url,
|
|
||||||
folder=self.folder_path
|
|
||||||
).split()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
@ -217,23 +213,18 @@ class Repo(RepoJSONMixin):
|
|||||||
"""
|
"""
|
||||||
exists, _ = self._existing_git_repo()
|
exists, _ = self._existing_git_repo()
|
||||||
if not exists:
|
if not exists:
|
||||||
raise MissingGitRepo(
|
raise MissingGitRepo("A git repo does not exist at path: {}".format(self.folder_path))
|
||||||
"A git repo does not exist at path: {}".format(self.folder_path)
|
|
||||||
)
|
|
||||||
|
|
||||||
p = await self._run(
|
p = await self._run(self.GIT_CURRENT_BRANCH.format(path=self.folder_path).split())
|
||||||
self.GIT_CURRENT_BRANCH.format(
|
|
||||||
path=self.folder_path
|
|
||||||
).split()
|
|
||||||
)
|
|
||||||
|
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
raise GitException("Could not determine current branch"
|
raise GitException(
|
||||||
" at path: {}".format(self.folder_path))
|
"Could not determine current branch" " at path: {}".format(self.folder_path)
|
||||||
|
)
|
||||||
|
|
||||||
return p.stdout.decode().strip()
|
return p.stdout.decode().strip()
|
||||||
|
|
||||||
async def current_commit(self, branch: str=None) -> str:
|
async def current_commit(self, branch: str = None) -> str:
|
||||||
"""Determine the current commit hash of the repo.
|
"""Determine the current commit hash of the repo.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -252,15 +243,10 @@ class Repo(RepoJSONMixin):
|
|||||||
|
|
||||||
exists, _ = self._existing_git_repo()
|
exists, _ = self._existing_git_repo()
|
||||||
if not exists:
|
if not exists:
|
||||||
raise MissingGitRepo(
|
raise MissingGitRepo("A git repo does not exist at path: {}".format(self.folder_path))
|
||||||
"A git repo does not exist at path: {}".format(self.folder_path)
|
|
||||||
)
|
|
||||||
|
|
||||||
p = await self._run(
|
p = await self._run(
|
||||||
self.GIT_LATEST_COMMIT.format(
|
self.GIT_LATEST_COMMIT.format(path=self.folder_path, branch=branch).split()
|
||||||
path=self.folder_path,
|
|
||||||
branch=branch
|
|
||||||
).split()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
@ -268,7 +254,7 @@ class Repo(RepoJSONMixin):
|
|||||||
|
|
||||||
return p.stdout.decode().strip()
|
return p.stdout.decode().strip()
|
||||||
|
|
||||||
async def current_url(self, folder: Path=None) -> str:
|
async def current_url(self, folder: Path = None) -> str:
|
||||||
"""
|
"""
|
||||||
Discovers the FETCH URL for a Git repo.
|
Discovers the FETCH URL for a Git repo.
|
||||||
|
|
||||||
@ -290,18 +276,14 @@ class Repo(RepoJSONMixin):
|
|||||||
if folder is None:
|
if folder is None:
|
||||||
folder = self.folder_path
|
folder = self.folder_path
|
||||||
|
|
||||||
p = await self._run(
|
p = await self._run(Repo.GIT_DISCOVER_REMOTE_URL.format(path=folder).split())
|
||||||
Repo.GIT_DISCOVER_REMOTE_URL.format(
|
|
||||||
path=folder
|
|
||||||
).split()
|
|
||||||
)
|
|
||||||
|
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
raise RuntimeError("Unable to discover a repo URL.")
|
raise RuntimeError("Unable to discover a repo URL.")
|
||||||
|
|
||||||
return p.stdout.decode().strip()
|
return p.stdout.decode().strip()
|
||||||
|
|
||||||
async def hard_reset(self, branch: str=None) -> None:
|
async def hard_reset(self, branch: str = None) -> None:
|
||||||
"""Perform a hard reset on the current repo.
|
"""Perform a hard reset on the current repo.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -315,21 +297,18 @@ class Repo(RepoJSONMixin):
|
|||||||
|
|
||||||
exists, _ = self._existing_git_repo()
|
exists, _ = self._existing_git_repo()
|
||||||
if not exists:
|
if not exists:
|
||||||
raise MissingGitRepo(
|
raise MissingGitRepo("A git repo does not exist at path: {}".format(self.folder_path))
|
||||||
"A git repo does not exist at path: {}".format(self.folder_path)
|
|
||||||
)
|
|
||||||
|
|
||||||
p = await self._run(
|
p = await self._run(
|
||||||
self.GIT_HARD_RESET.format(
|
self.GIT_HARD_RESET.format(path=self.folder_path, branch=branch).split()
|
||||||
path=self.folder_path,
|
|
||||||
branch=branch
|
|
||||||
).split()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
raise HardResetError("Some error occurred when trying to"
|
raise HardResetError(
|
||||||
|
"Some error occurred when trying to"
|
||||||
" execute a hard reset on the repo at"
|
" execute a hard reset on the repo at"
|
||||||
" the following path: {}".format(self.folder_path))
|
" the following path: {}".format(self.folder_path)
|
||||||
|
)
|
||||||
|
|
||||||
async def update(self) -> (str, str):
|
async def update(self) -> (str, str):
|
||||||
"""Update the current branch of this repo.
|
"""Update the current branch of this repo.
|
||||||
@ -345,15 +324,13 @@ class Repo(RepoJSONMixin):
|
|||||||
|
|
||||||
await self.hard_reset(branch=curr_branch)
|
await self.hard_reset(branch=curr_branch)
|
||||||
|
|
||||||
p = await self._run(
|
p = await self._run(self.GIT_PULL.format(path=self.folder_path).split())
|
||||||
self.GIT_PULL.format(
|
|
||||||
path=self.folder_path
|
|
||||||
).split()
|
|
||||||
)
|
|
||||||
|
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
raise UpdateError("Git pull returned a non zero exit code"
|
raise UpdateError(
|
||||||
" for the repo located at path: {}".format(self.folder_path))
|
"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)
|
new_commit = await self.current_commit(branch=curr_branch)
|
||||||
|
|
||||||
@ -389,7 +366,9 @@ class Repo(RepoJSONMixin):
|
|||||||
|
|
||||||
return await cog.copy_to(target_dir=target_dir)
|
return await cog.copy_to(target_dir=target_dir)
|
||||||
|
|
||||||
async def install_libraries(self, target_dir: Path, libraries: Tuple[Installable]=()) -> bool:
|
async def install_libraries(
|
||||||
|
self, target_dir: Path, libraries: Tuple[Installable] = ()
|
||||||
|
) -> bool:
|
||||||
"""Install shared libraries to the target directory.
|
"""Install shared libraries to the target directory.
|
||||||
|
|
||||||
If :code:`libraries` is not specified, all shared libraries in the repo
|
If :code:`libraries` is not specified, all shared libraries in the repo
|
||||||
@ -469,16 +448,16 @@ class Repo(RepoJSONMixin):
|
|||||||
|
|
||||||
p = await self._run(
|
p = await self._run(
|
||||||
self.PIP_INSTALL.format(
|
self.PIP_INSTALL.format(
|
||||||
python=executable,
|
python=executable, target_dir=target_dir, reqs=" ".join(requirements)
|
||||||
target_dir=target_dir,
|
|
||||||
reqs=" ".join(requirements)
|
|
||||||
).split()
|
).split()
|
||||||
)
|
)
|
||||||
|
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
log.error("Something went wrong when installing"
|
log.error(
|
||||||
|
"Something went wrong when installing"
|
||||||
" the following requirements:"
|
" the following requirements:"
|
||||||
" {}".format(", ".join(requirements)))
|
" {}".format(", ".join(requirements))
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -490,8 +469,7 @@ class Repo(RepoJSONMixin):
|
|||||||
"""
|
"""
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
return tuple(
|
return tuple(
|
||||||
[m for m in self.available_modules
|
[m for m in self.available_modules if m.type == InstallableType.COG and not m.hidden]
|
||||||
if m.type == InstallableType.COG and not m.hidden]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -501,8 +479,7 @@ class Repo(RepoJSONMixin):
|
|||||||
"""
|
"""
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
return tuple(
|
return tuple(
|
||||||
[m for m in self.available_modules
|
[m for m in self.available_modules if m.type == InstallableType.SHARED_LIBRARY]
|
||||||
if m.type == InstallableType.SHARED_LIBRARY]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -515,6 +492,7 @@ class Repo(RepoJSONMixin):
|
|||||||
|
|
||||||
|
|
||||||
class RepoManager:
|
class RepoManager:
|
||||||
|
|
||||||
def __init__(self, downloader_config: Config):
|
def __init__(self, downloader_config: Config):
|
||||||
self.downloader_config = downloader_config
|
self.downloader_config = downloader_config
|
||||||
|
|
||||||
@ -526,7 +504,7 @@ class RepoManager:
|
|||||||
@property
|
@property
|
||||||
def repos_folder(self) -> Path:
|
def repos_folder(self) -> Path:
|
||||||
data_folder = data_manager.cog_data_path(self)
|
data_folder = data_manager.cog_data_path(self)
|
||||||
return data_folder / 'repos'
|
return data_folder / "repos"
|
||||||
|
|
||||||
def does_repo_exist(self, name: str) -> bool:
|
def does_repo_exist(self, name: str) -> bool:
|
||||||
return name in self._repos
|
return name in self._repos
|
||||||
@ -537,7 +515,7 @@ class RepoManager:
|
|||||||
raise InvalidRepoName("Not a valid Python variable name.")
|
raise InvalidRepoName("Not a valid Python variable name.")
|
||||||
return name.lower()
|
return name.lower()
|
||||||
|
|
||||||
async def add_repo(self, url: str, name: str, branch: str="master") -> Repo:
|
async def add_repo(self, url: str, name: str, branch: str = "master") -> Repo:
|
||||||
"""Add and clone a git repository.
|
"""Add and clone a git repository.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -557,13 +535,11 @@ class RepoManager:
|
|||||||
"""
|
"""
|
||||||
if self.does_repo_exist(name):
|
if self.does_repo_exist(name):
|
||||||
raise InvalidRepoName(
|
raise InvalidRepoName(
|
||||||
"That repo name you provided already exists."
|
"That repo name you provided already exists." " Please choose another."
|
||||||
" Please choose another."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
r = Repo(url=url, name=name, branch=branch,
|
r = Repo(url=url, name=name, branch=branch, folder_path=self.repos_folder / name)
|
||||||
folder_path=self.repos_folder / name)
|
|
||||||
await r.clone()
|
await r.clone()
|
||||||
|
|
||||||
self._repos[name] = r
|
self._repos[name] = r
|
||||||
|
|||||||
@ -36,45 +36,44 @@ class SMReel(Enum):
|
|||||||
PAYOUTS = {
|
PAYOUTS = {
|
||||||
(SMReel.two, SMReel.two, SMReel.six): {
|
(SMReel.two, SMReel.two, SMReel.six): {
|
||||||
"payout": lambda x: x * 2500 + x,
|
"payout": lambda x: x * 2500 + x,
|
||||||
"phrase": _("JACKPOT! 226! Your bid has been multiplied * 2500!")
|
"phrase": _("JACKPOT! 226! Your bid has been multiplied * 2500!"),
|
||||||
},
|
},
|
||||||
(SMReel.flc, SMReel.flc, SMReel.flc): {
|
(SMReel.flc, SMReel.flc, SMReel.flc): {
|
||||||
"payout": lambda x: x + 1000,
|
"payout": lambda x: x + 1000, "phrase": _("4LC! +1000!")
|
||||||
"phrase": _("4LC! +1000!")
|
|
||||||
},
|
},
|
||||||
(SMReel.cherries, SMReel.cherries, SMReel.cherries): {
|
(SMReel.cherries, SMReel.cherries, SMReel.cherries): {
|
||||||
"payout": lambda x: x + 800,
|
"payout": lambda x: x + 800, "phrase": _("Three cherries! +800!")
|
||||||
"phrase": _("Three cherries! +800!")
|
|
||||||
},
|
},
|
||||||
(SMReel.two, SMReel.six): {
|
(SMReel.two, SMReel.six): {
|
||||||
"payout": lambda x: x * 4 + x,
|
"payout": lambda x: x * 4 + x, "phrase": _("2 6! Your bid has been multiplied * 4!")
|
||||||
"phrase": _("2 6! Your bid has been multiplied * 4!")
|
|
||||||
},
|
},
|
||||||
(SMReel.cherries, SMReel.cherries): {
|
(SMReel.cherries, SMReel.cherries): {
|
||||||
"payout": lambda x: x * 3 + x,
|
"payout": lambda x: x * 3 + x,
|
||||||
"phrase": _("Two cherries! Your bid has been multiplied * 3!")
|
"phrase": _("Two cherries! Your bid has been multiplied * 3!"),
|
||||||
},
|
|
||||||
"3 symbols": {
|
|
||||||
"payout": lambda x: x + 500,
|
|
||||||
"phrase": _("Three symbols! +500!")
|
|
||||||
},
|
},
|
||||||
|
"3 symbols": {"payout": lambda x: x + 500, "phrase": _("Three symbols! +500!")},
|
||||||
"2 symbols": {
|
"2 symbols": {
|
||||||
"payout": lambda x: x * 2 + x,
|
"payout": lambda x: x * 2 + x,
|
||||||
"phrase": _("Two consecutive symbols! Your bid has been multiplied * 2!")
|
"phrase": _("Two consecutive symbols! Your bid has been multiplied * 2!"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
SLOT_PAYOUTS_MSG = _("Slot machine payouts:\n"
|
SLOT_PAYOUTS_MSG = _(
|
||||||
|
"Slot machine payouts:\n"
|
||||||
"{two.value} {two.value} {six.value} Bet * 2500\n"
|
"{two.value} {two.value} {six.value} Bet * 2500\n"
|
||||||
"{flc.value} {flc.value} {flc.value} +1000\n"
|
"{flc.value} {flc.value} {flc.value} +1000\n"
|
||||||
"{cherries.value} {cherries.value} {cherries.value} +800\n"
|
"{cherries.value} {cherries.value} {cherries.value} +800\n"
|
||||||
"{two.value} {six.value} Bet * 4\n"
|
"{two.value} {six.value} Bet * 4\n"
|
||||||
"{cherries.value} {cherries.value} Bet * 3\n\n"
|
"{cherries.value} {cherries.value} Bet * 3\n\n"
|
||||||
"Three symbols: +500\n"
|
"Three symbols: +500\n"
|
||||||
"Two symbols: Bet * 2").format(**SMReel.__dict__)
|
"Two symbols: Bet * 2"
|
||||||
|
).format(
|
||||||
|
**SMReel.__dict__
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def guild_only_check():
|
def guild_only_check():
|
||||||
|
|
||||||
async def pred(ctx: commands.Context):
|
async def pred(ctx: commands.Context):
|
||||||
if await bank.is_global():
|
if await bank.is_global():
|
||||||
return True
|
return True
|
||||||
@ -82,10 +81,12 @@ def guild_only_check():
|
|||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return commands.check(pred)
|
return commands.check(pred)
|
||||||
|
|
||||||
|
|
||||||
class SetParser:
|
class SetParser:
|
||||||
|
|
||||||
def __init__(self, argument):
|
def __init__(self, argument):
|
||||||
allowed = ("+", "-")
|
allowed = ("+", "-")
|
||||||
self.sum = int(argument)
|
self.sum = int(argument)
|
||||||
@ -115,19 +116,14 @@ class Economy:
|
|||||||
"SLOT_MIN": 5,
|
"SLOT_MIN": 5,
|
||||||
"SLOT_MAX": 100,
|
"SLOT_MAX": 100,
|
||||||
"SLOT_TIME": 0,
|
"SLOT_TIME": 0,
|
||||||
"REGISTER_CREDITS": 0
|
"REGISTER_CREDITS": 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
default_global_settings = default_guild_settings
|
default_global_settings = default_guild_settings
|
||||||
|
|
||||||
default_member_settings = {
|
default_member_settings = {"next_payday": 0, "last_slot": 0}
|
||||||
"next_payday": 0,
|
|
||||||
"last_slot": 0
|
|
||||||
}
|
|
||||||
|
|
||||||
default_role_settings = {
|
default_role_settings = {"PAYDAY_CREDITS": 0}
|
||||||
"PAYDAY_CREDITS": 0
|
|
||||||
}
|
|
||||||
|
|
||||||
default_user_settings = default_member_settings
|
default_user_settings = default_member_settings
|
||||||
|
|
||||||
@ -159,8 +155,7 @@ class Economy:
|
|||||||
bal = await bank.get_balance(user)
|
bal = await bank.get_balance(user)
|
||||||
currency = await bank.get_currency_name(ctx.guild)
|
currency = await bank.get_currency_name(ctx.guild)
|
||||||
|
|
||||||
await ctx.send(_("{}'s balance is {} {}").format(
|
await ctx.send(_("{}'s balance is {} {}").format(user.display_name, bal, currency))
|
||||||
user.display_name, bal, currency))
|
|
||||||
|
|
||||||
@_bank.command()
|
@_bank.command()
|
||||||
async def transfer(self, ctx: commands.Context, to: discord.Member, amount: int):
|
async def transfer(self, ctx: commands.Context, to: discord.Member, amount: int):
|
||||||
@ -173,9 +168,11 @@ class Economy:
|
|||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
await ctx.send(str(e))
|
await ctx.send(str(e))
|
||||||
|
|
||||||
await ctx.send(_("{} transferred {} {} to {}").format(
|
await ctx.send(
|
||||||
|
_("{} transferred {} {} to {}").format(
|
||||||
from_.display_name, amount, currency, to.display_name
|
from_.display_name, amount, currency, to.display_name
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@_bank.command(name="set")
|
@_bank.command(name="set")
|
||||||
@check_global_setting_admin()
|
@check_global_setting_admin()
|
||||||
@ -193,19 +190,25 @@ class Economy:
|
|||||||
|
|
||||||
if creds.operation == "deposit":
|
if creds.operation == "deposit":
|
||||||
await bank.deposit_credits(to, creds.sum)
|
await bank.deposit_credits(to, creds.sum)
|
||||||
await ctx.send(_("{} added {} {} to {}'s account.").format(
|
await ctx.send(
|
||||||
|
_("{} added {} {} to {}'s account.").format(
|
||||||
author.display_name, creds.sum, currency, to.display_name
|
author.display_name, creds.sum, currency, to.display_name
|
||||||
))
|
)
|
||||||
|
)
|
||||||
elif creds.operation == "withdraw":
|
elif creds.operation == "withdraw":
|
||||||
await bank.withdraw_credits(to, creds.sum)
|
await bank.withdraw_credits(to, creds.sum)
|
||||||
await ctx.send(_("{} removed {} {} from {}'s account.").format(
|
await ctx.send(
|
||||||
|
_("{} removed {} {} from {}'s account.").format(
|
||||||
author.display_name, creds.sum, currency, to.display_name
|
author.display_name, creds.sum, currency, to.display_name
|
||||||
))
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await bank.set_balance(to, creds.sum)
|
await bank.set_balance(to, creds.sum)
|
||||||
await ctx.send(_("{} set {}'s account to {} {}.").format(
|
await ctx.send(
|
||||||
|
_("{} set {}'s account to {} {}.").format(
|
||||||
author.display_name, to.display_name, creds.sum, currency
|
author.display_name, to.display_name, creds.sum, currency
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@_bank.command()
|
@_bank.command()
|
||||||
@guild_only_check()
|
@guild_only_check()
|
||||||
@ -214,16 +217,17 @@ class Economy:
|
|||||||
"""Deletes bank accounts"""
|
"""Deletes bank accounts"""
|
||||||
if confirmation is False:
|
if confirmation is False:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("This will delete all bank accounts for {}.\nIf you're sure, type "
|
_(
|
||||||
"`{}bank reset yes`").format(
|
"This will delete all bank accounts for {}.\nIf you're sure, type "
|
||||||
self.bot.user.name if await bank.is_global() else "this server",
|
"`{}bank reset yes`"
|
||||||
ctx.prefix
|
).format(
|
||||||
|
self.bot.user.name if await bank.is_global() else "this server", ctx.prefix
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await bank.wipe_bank()
|
await bank.wipe_bank()
|
||||||
await ctx.send(_("All bank accounts for {} have been "
|
await ctx.send(
|
||||||
"deleted.").format(
|
_("All bank accounts for {} have been " "deleted.").format(
|
||||||
self.bot.user.name if await bank.is_global() else "this server"
|
self.bot.user.name if await bank.is_global() else "this server"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -245,50 +249,65 @@ class Economy:
|
|||||||
await self.config.user(author).next_payday.set(next_payday)
|
await self.config.user(author).next_payday.set(next_payday)
|
||||||
|
|
||||||
pos = await bank.get_leaderboard_position(author)
|
pos = await bank.get_leaderboard_position(author)
|
||||||
await ctx.send(_(
|
await ctx.send(
|
||||||
|
_(
|
||||||
"{0.mention} Here, take some {1}. Enjoy! (+{2} {1}!)\n\n"
|
"{0.mention} Here, take some {1}. Enjoy! (+{2} {1}!)\n\n"
|
||||||
"You currently have {3} {1}.\n\n"
|
"You currently have {3} {1}.\n\n"
|
||||||
"You are currently #{4} on the leaderboard!"
|
"You are currently #{4} on the leaderboard!"
|
||||||
).format(
|
).format(
|
||||||
author, credits_name, str(await self.config.PAYDAY_CREDITS()),
|
author,
|
||||||
str(await bank.get_balance(author)), pos
|
credits_name,
|
||||||
))
|
str(await self.config.PAYDAY_CREDITS()),
|
||||||
|
str(await bank.get_balance(author)),
|
||||||
|
pos,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
dtime = self.display_time(next_payday - cur_time)
|
dtime = self.display_time(next_payday - cur_time)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("{} Too soon. For your next payday you have to"
|
_("{} Too soon. For your next payday you have to" " wait {}.").format(
|
||||||
" wait {}.").format(author.mention, dtime)
|
author.mention, dtime
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
next_payday = await self.config.member(author).next_payday()
|
next_payday = await self.config.member(author).next_payday()
|
||||||
if cur_time >= next_payday:
|
if cur_time >= next_payday:
|
||||||
credit_amount = await self.config.guild(guild).PAYDAY_CREDITS()
|
credit_amount = await self.config.guild(guild).PAYDAY_CREDITS()
|
||||||
for role in author.roles:
|
for role in author.roles:
|
||||||
role_credits = await self.config.role(role).PAYDAY_CREDITS() # Nice variable name
|
role_credits = await self.config.role(
|
||||||
|
role
|
||||||
|
).PAYDAY_CREDITS() # Nice variable name
|
||||||
if role_credits > credit_amount:
|
if role_credits > credit_amount:
|
||||||
credit_amount = role_credits
|
credit_amount = role_credits
|
||||||
await bank.deposit_credits(author, credit_amount)
|
await bank.deposit_credits(author, credit_amount)
|
||||||
next_payday = cur_time + await self.config.guild(guild).PAYDAY_TIME()
|
next_payday = cur_time + await self.config.guild(guild).PAYDAY_TIME()
|
||||||
await self.config.member(author).next_payday.set(next_payday)
|
await self.config.member(author).next_payday.set(next_payday)
|
||||||
pos = await bank.get_leaderboard_position(author)
|
pos = await bank.get_leaderboard_position(author)
|
||||||
await ctx.send(_(
|
await ctx.send(
|
||||||
|
_(
|
||||||
"{0.mention} Here, take some {1}. Enjoy! (+{2} {1}!)\n\n"
|
"{0.mention} Here, take some {1}. Enjoy! (+{2} {1}!)\n\n"
|
||||||
"You currently have {3} {1}.\n\n"
|
"You currently have {3} {1}.\n\n"
|
||||||
"You are currently #{4} on the leaderboard!"
|
"You are currently #{4} on the leaderboard!"
|
||||||
).format(
|
).format(
|
||||||
author, credits_name, credit_amount,
|
author,
|
||||||
str(await bank.get_balance(author)), pos
|
credits_name,
|
||||||
))
|
credit_amount,
|
||||||
|
str(await bank.get_balance(author)),
|
||||||
|
pos,
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
dtime = self.display_time(next_payday - cur_time)
|
dtime = self.display_time(next_payday - cur_time)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("{} Too soon. For your next payday you have to"
|
_("{} Too soon. For your next payday you have to" " wait {}.").format(
|
||||||
" wait {}.").format(author.mention, dtime))
|
author.mention, dtime
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@guild_only_check()
|
@guild_only_check()
|
||||||
async def leaderboard(self, ctx: commands.Context, top: int = 10, show_global: bool=False):
|
async def leaderboard(self, ctx: commands.Context, top: int = 10, show_global: bool = False):
|
||||||
"""Prints out the leaderboard
|
"""Prints out the leaderboard
|
||||||
|
|
||||||
Defaults to top 10"""
|
Defaults to top 10"""
|
||||||
@ -296,7 +315,9 @@ class Economy:
|
|||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
if top < 1:
|
if top < 1:
|
||||||
top = 10
|
top = 10
|
||||||
if await bank.is_global() and show_global: # show_global is only applicable if bank is global
|
if (
|
||||||
|
await bank.is_global() and show_global
|
||||||
|
): # show_global is only applicable if bank is global
|
||||||
guild = None
|
guild = None
|
||||||
bank_sorted = await bank.get_leaderboard(positions=top, guild=guild)
|
bank_sorted = await bank.get_leaderboard(positions=top, guild=guild)
|
||||||
if len(bank_sorted) < top:
|
if len(bank_sorted) < top:
|
||||||
@ -310,8 +331,12 @@ class Economy:
|
|||||||
balance = acc[1]["balance"]
|
balance = acc[1]["balance"]
|
||||||
balwidth = 2
|
balwidth = 2
|
||||||
highscore += "{pos: <{poswidth}} {name: <{namewidth}s} {balance: >{balwidth}}\n".format(
|
highscore += "{pos: <{poswidth}} {name: <{namewidth}s} {balance: >{balwidth}}\n".format(
|
||||||
pos=pos, poswidth=poswidth, name=name, namewidth=namewidth,
|
pos=pos,
|
||||||
balance=balance, balwidth=balwidth
|
poswidth=poswidth,
|
||||||
|
name=name,
|
||||||
|
namewidth=namewidth,
|
||||||
|
balance=balance,
|
||||||
|
balwidth=balwidth,
|
||||||
)
|
)
|
||||||
if highscore != "":
|
if highscore != "":
|
||||||
for page in pagify(highscore, shorten_by=12):
|
for page in pagify(highscore, shorten_by=12):
|
||||||
@ -337,7 +362,11 @@ class Economy:
|
|||||||
slot_time = await self.config.SLOT_TIME()
|
slot_time = await self.config.SLOT_TIME()
|
||||||
last_slot = await self.config.user(author).last_slot()
|
last_slot = await self.config.user(author).last_slot()
|
||||||
else:
|
else:
|
||||||
valid_bid = await self.config.guild(guild).SLOT_MIN() <= bid <= await self.config.guild(guild).SLOT_MAX()
|
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()
|
slot_time = await self.config.guild(guild).SLOT_TIME()
|
||||||
last_slot = await self.config.member(author).last_slot()
|
last_slot = await self.config.member(author).last_slot()
|
||||||
now = calendar.timegm(ctx.message.created_at.utctimetuple())
|
now = calendar.timegm(ctx.message.created_at.utctimetuple())
|
||||||
@ -364,9 +393,11 @@ class Economy:
|
|||||||
default_reel.rotate(random.randint(-999, 999)) # weeeeee
|
default_reel.rotate(random.randint(-999, 999)) # weeeeee
|
||||||
new_reel = deque(default_reel, maxlen=3) # we need only 3 symbols
|
new_reel = deque(default_reel, maxlen=3) # we need only 3 symbols
|
||||||
reels.append(new_reel) # for each reel
|
reels.append(new_reel) # for each reel
|
||||||
rows = ((reels[0][0], reels[1][0], reels[2][0]),
|
rows = (
|
||||||
|
(reels[0][0], reels[1][0], reels[2][0]),
|
||||||
(reels[0][1], reels[1][1], reels[2][1]),
|
(reels[0][1], reels[1][1], reels[2][1]),
|
||||||
(reels[0][2], reels[1][2], reels[2][2]))
|
(reels[0][2], reels[1][2], reels[2][2]),
|
||||||
|
)
|
||||||
|
|
||||||
slot = "~~\n~~" # Mobile friendly
|
slot = "~~\n~~" # Mobile friendly
|
||||||
for i, row in enumerate(rows): # Let's build the slot to show
|
for i, row in enumerate(rows): # Let's build the slot to show
|
||||||
@ -378,8 +409,7 @@ class Economy:
|
|||||||
payout = PAYOUTS.get(rows[1])
|
payout = PAYOUTS.get(rows[1])
|
||||||
if not payout:
|
if not payout:
|
||||||
# Checks for two-consecutive-symbols special rewards
|
# Checks for two-consecutive-symbols special rewards
|
||||||
payout = PAYOUTS.get((rows[1][0], rows[1][1]),
|
payout = PAYOUTS.get((rows[1][0], rows[1][1]), PAYOUTS.get((rows[1][1], rows[1][2])))
|
||||||
PAYOUTS.get((rows[1][1], rows[1][2])))
|
|
||||||
if not payout:
|
if not payout:
|
||||||
# Still nothing. Let's check for 3 generic same symbols
|
# Still nothing. Let's check for 3 generic same symbols
|
||||||
# or 2 consecutive symbols
|
# or 2 consecutive symbols
|
||||||
@ -395,15 +425,20 @@ class Economy:
|
|||||||
pay = payout["payout"](bid)
|
pay = payout["payout"](bid)
|
||||||
now = then - bid + pay
|
now = then - bid + pay
|
||||||
await bank.set_balance(author, now)
|
await bank.set_balance(author, now)
|
||||||
await channel.send(_("{}\n{} {}\n\nYour bid: {}\n{} → {}!"
|
await channel.send(
|
||||||
"").format(slot, author.mention,
|
_("{}\n{} {}\n\nYour bid: {}\n{} → {}!" "").format(
|
||||||
payout["phrase"], bid, then, now))
|
slot, author.mention, payout["phrase"], bid, then, now
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
then = await bank.get_balance(author)
|
then = await bank.get_balance(author)
|
||||||
await bank.withdraw_credits(author, bid)
|
await bank.withdraw_credits(author, bid)
|
||||||
now = then - bid
|
now = then - bid
|
||||||
await channel.send(_("{}\n{} Nothing!\nYour bid: {}\n{} → {}!"
|
await channel.send(
|
||||||
"").format(slot, author.mention, bid, then, now))
|
_("{}\n{} Nothing!\nYour bid: {}\n{} → {}!" "").format(
|
||||||
|
slot, author.mention, bid, then, now
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@guild_only_check()
|
@guild_only_check()
|
||||||
@ -427,17 +462,18 @@ class Economy:
|
|||||||
payday_amount = await self.config.guild(guild).PAYDAY_CREDITS()
|
payday_amount = await self.config.guild(guild).PAYDAY_CREDITS()
|
||||||
register_amount = await bank.get_default_balance(guild)
|
register_amount = await bank.get_default_balance(guild)
|
||||||
msg = box(
|
msg = box(
|
||||||
_("Minimum slot bid: {}\n"
|
_(
|
||||||
|
"Minimum slot bid: {}\n"
|
||||||
"Maximum slot bid: {}\n"
|
"Maximum slot bid: {}\n"
|
||||||
"Slot cooldown: {}\n"
|
"Slot cooldown: {}\n"
|
||||||
"Payday amount: {}\n"
|
"Payday amount: {}\n"
|
||||||
"Payday cooldown: {}\n"
|
"Payday cooldown: {}\n"
|
||||||
"Amount given at account registration: {}"
|
"Amount given at account registration: {}"
|
||||||
"").format(
|
""
|
||||||
slot_min, slot_max, slot_time,
|
).format(
|
||||||
payday_amount, payday_time, register_amount
|
slot_min, slot_max, slot_time, payday_amount, payday_time, register_amount
|
||||||
),
|
),
|
||||||
_("Current Economy settings:")
|
_("Current Economy settings:"),
|
||||||
)
|
)
|
||||||
await ctx.send(msg)
|
await ctx.send(msg)
|
||||||
|
|
||||||
@ -445,7 +481,7 @@ class Economy:
|
|||||||
async def slotmin(self, ctx: commands.Context, bid: int):
|
async def slotmin(self, ctx: commands.Context, bid: int):
|
||||||
"""Minimum slot machine bid"""
|
"""Minimum slot machine bid"""
|
||||||
if bid < 1:
|
if bid < 1:
|
||||||
await ctx.send(_('Invalid min bid amount.'))
|
await ctx.send(_("Invalid min bid amount."))
|
||||||
return
|
return
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
if await bank.is_global():
|
if await bank.is_global():
|
||||||
@ -460,8 +496,7 @@ class Economy:
|
|||||||
"""Maximum slot machine bid"""
|
"""Maximum slot machine bid"""
|
||||||
slot_min = await self.config.SLOT_MIN()
|
slot_min = await self.config.SLOT_MIN()
|
||||||
if bid < 1 or bid < slot_min:
|
if bid < 1 or bid < slot_min:
|
||||||
await ctx.send(_('Invalid slotmax bid amount. Must be greater'
|
await ctx.send(_("Invalid slotmax bid amount. Must be greater" " than slotmin."))
|
||||||
' than slotmin.'))
|
|
||||||
return
|
return
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
credits_name = await bank.get_currency_name(guild)
|
credits_name = await bank.get_currency_name(guild)
|
||||||
@ -489,8 +524,11 @@ class Economy:
|
|||||||
await self.config.PAYDAY_TIME.set(seconds)
|
await self.config.PAYDAY_TIME.set(seconds)
|
||||||
else:
|
else:
|
||||||
await self.config.guild(guild).PAYDAY_TIME.set(seconds)
|
await self.config.guild(guild).PAYDAY_TIME.set(seconds)
|
||||||
await ctx.send(_("Value modified. At least {} seconds must pass "
|
await ctx.send(
|
||||||
"between each payday.").format(seconds))
|
_("Value modified. At least {} seconds must pass " "between each payday.").format(
|
||||||
|
seconds
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@economyset.command()
|
@economyset.command()
|
||||||
async def paydayamount(self, ctx: commands.Context, creds: int):
|
async def paydayamount(self, ctx: commands.Context, creds: int):
|
||||||
@ -504,8 +542,7 @@ class Economy:
|
|||||||
await self.config.PAYDAY_CREDITS.set(creds)
|
await self.config.PAYDAY_CREDITS.set(creds)
|
||||||
else:
|
else:
|
||||||
await self.config.guild(guild).PAYDAY_CREDITS.set(creds)
|
await self.config.guild(guild).PAYDAY_CREDITS.set(creds)
|
||||||
await ctx.send(_("Every payday will now give {} {}."
|
await ctx.send(_("Every payday will now give {} {}." "").format(creds, credits_name))
|
||||||
"").format(creds, credits_name))
|
|
||||||
|
|
||||||
@economyset.command()
|
@economyset.command()
|
||||||
async def rolepaydayamount(self, ctx: commands.Context, role: discord.Role, creds: int):
|
async def rolepaydayamount(self, ctx: commands.Context, role: discord.Role, creds: int):
|
||||||
@ -516,8 +553,11 @@ class Economy:
|
|||||||
await ctx.send("The bank must be per-server for per-role paydays to work.")
|
await ctx.send("The bank must be per-server for per-role paydays to work.")
|
||||||
else:
|
else:
|
||||||
await self.config.role(role).PAYDAY_CREDITS.set(creds)
|
await self.config.role(role).PAYDAY_CREDITS.set(creds)
|
||||||
await ctx.send(_("Every payday will now give {} {} to people with the role {}."
|
await ctx.send(
|
||||||
"").format(creds, credits_name, role.name))
|
_("Every payday will now give {} {} to people with the role {}." "").format(
|
||||||
|
creds, credits_name, role.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@economyset.command()
|
@economyset.command()
|
||||||
async def registeramount(self, ctx: commands.Context, creds: int):
|
async def registeramount(self, ctx: commands.Context, creds: int):
|
||||||
@ -527,17 +567,18 @@ class Economy:
|
|||||||
creds = 0
|
creds = 0
|
||||||
credits_name = await bank.get_currency_name(guild)
|
credits_name = await bank.get_currency_name(guild)
|
||||||
await bank.set_default_balance(creds, guild)
|
await bank.set_default_balance(creds, guild)
|
||||||
await ctx.send(_("Registering an account will now give {} {}."
|
await ctx.send(
|
||||||
"").format(creds, credits_name))
|
_("Registering an account will now give {} {}." "").format(creds, credits_name)
|
||||||
|
)
|
||||||
|
|
||||||
# What would I ever do without stackoverflow?
|
# What would I ever do without stackoverflow?
|
||||||
def display_time(self, seconds, granularity=2):
|
def display_time(self, seconds, granularity=2):
|
||||||
intervals = ( # Source: http://stackoverflow.com/a/24542445
|
intervals = ( # Source: http://stackoverflow.com/a/24542445
|
||||||
(_('weeks'), 604800), # 60 * 60 * 24 * 7
|
(_("weeks"), 604800), # 60 * 60 * 24 * 7
|
||||||
(_('days'), 86400), # 60 * 60 * 24
|
(_("days"), 86400), # 60 * 60 * 24
|
||||||
(_('hours'), 3600), # 60 * 60
|
(_("hours"), 3600), # 60 * 60
|
||||||
(_('minutes'), 60),
|
(_("minutes"), 60),
|
||||||
(_('seconds'), 1),
|
(_("seconds"), 1),
|
||||||
)
|
)
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
@ -547,6 +588,6 @@ class Economy:
|
|||||||
if value:
|
if value:
|
||||||
seconds -= value * count
|
seconds -= value * count
|
||||||
if value == 1:
|
if value == 1:
|
||||||
name = name.rstrip('s')
|
name = name.rstrip("s")
|
||||||
result.append("{} {}".format(value, name))
|
result.append("{} {}".format(value, name))
|
||||||
return ', '.join(result[:granularity])
|
return ", ".join(result[:granularity])
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
TO_TRANSLATE = [
|
TO_TRANSLATE = ["../economy.py"]
|
||||||
'../economy.py'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def regen_messages():
|
def regen_messages():
|
||||||
subprocess.run(
|
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||||
['pygettext', '-n'] + TO_TRANSLATE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -21,12 +21,9 @@ class Filter:
|
|||||||
"filterban_count": 0,
|
"filterban_count": 0,
|
||||||
"filterban_time": 0,
|
"filterban_time": 0,
|
||||||
"filter_names": False,
|
"filter_names": False,
|
||||||
"filter_default_name": "John Doe"
|
"filter_default_name": "John Doe",
|
||||||
}
|
|
||||||
default_member_settings = {
|
|
||||||
"filter_count": 0,
|
|
||||||
"next_reset_time": 0
|
|
||||||
}
|
}
|
||||||
|
default_member_settings = {"filter_count": 0, "next_reset_time": 0}
|
||||||
self.settings.register_guild(**default_guild_settings)
|
self.settings.register_guild(**default_guild_settings)
|
||||||
self.settings.register_member(**default_member_settings)
|
self.settings.register_member(**default_member_settings)
|
||||||
self.register_task = self.bot.loop.create_task(self.register_filterban())
|
self.register_task = self.bot.loop.create_task(self.register_filterban())
|
||||||
@ -37,8 +34,7 @@ class Filter:
|
|||||||
async def register_filterban(self):
|
async def register_filterban(self):
|
||||||
try:
|
try:
|
||||||
await modlog.register_casetype(
|
await modlog.register_casetype(
|
||||||
"filterban", False, ":filing_cabinet: :hammer:",
|
"filterban", False, ":filing_cabinet: :hammer:", "Filter ban", "ban"
|
||||||
"Filter ban", "ban"
|
|
||||||
)
|
)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
pass
|
pass
|
||||||
@ -79,13 +75,12 @@ class Filter:
|
|||||||
word_list = []
|
word_list = []
|
||||||
tmp = ""
|
tmp = ""
|
||||||
for word in split_words:
|
for word in split_words:
|
||||||
if not word.startswith("\"")\
|
if not word.startswith('"') and not word.endswith('"') and not tmp:
|
||||||
and not word.endswith("\"") and not tmp:
|
|
||||||
word_list.append(word)
|
word_list.append(word)
|
||||||
else:
|
else:
|
||||||
if word.startswith("\""):
|
if word.startswith('"'):
|
||||||
tmp += word[1:]
|
tmp += word[1:]
|
||||||
elif word.endswith("\""):
|
elif word.endswith('"'):
|
||||||
tmp += word[:-1]
|
tmp += word[:-1]
|
||||||
word_list.append(tmp)
|
word_list.append(tmp)
|
||||||
tmp = ""
|
tmp = ""
|
||||||
@ -110,13 +105,12 @@ class Filter:
|
|||||||
word_list = []
|
word_list = []
|
||||||
tmp = ""
|
tmp = ""
|
||||||
for word in split_words:
|
for word in split_words:
|
||||||
if not word.startswith("\"")\
|
if not word.startswith('"') and not word.endswith('"') and not tmp:
|
||||||
and not word.endswith("\"") and not tmp:
|
|
||||||
word_list.append(word)
|
word_list.append(word)
|
||||||
else:
|
else:
|
||||||
if word.startswith("\""):
|
if word.startswith('"'):
|
||||||
tmp += word[1:]
|
tmp += word[1:]
|
||||||
elif word.endswith("\""):
|
elif word.endswith('"'):
|
||||||
tmp += word[:-1]
|
tmp += word[:-1]
|
||||||
word_list.append(tmp)
|
word_list.append(tmp)
|
||||||
tmp = ""
|
tmp = ""
|
||||||
@ -139,14 +133,10 @@ class Filter:
|
|||||||
await self.settings.guild(guild).filter_names.set(not current_setting)
|
await self.settings.guild(guild).filter_names.set(not current_setting)
|
||||||
if current_setting:
|
if current_setting:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Names and nicknames will no longer be "
|
_("Names and nicknames will no longer be " "checked against the filter")
|
||||||
"checked against the filter")
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await ctx.send(
|
await ctx.send(_("Names and nicknames will now be checked against " "the filter"))
|
||||||
_("Names and nicknames will now be checked against "
|
|
||||||
"the filter")
|
|
||||||
)
|
|
||||||
|
|
||||||
@_filter.command(name="defaultname")
|
@_filter.command(name="defaultname")
|
||||||
async def filter_default_name(self, ctx: commands.Context, name: str):
|
async def filter_default_name(self, ctx: commands.Context, name: str):
|
||||||
@ -160,15 +150,15 @@ class Filter:
|
|||||||
await ctx.send(_("The name to use on filtered names has been set"))
|
await ctx.send(_("The name to use on filtered names has been set"))
|
||||||
|
|
||||||
@_filter.command(name="ban")
|
@_filter.command(name="ban")
|
||||||
async def filter_ban(
|
async def filter_ban(self, ctx: commands.Context, count: int, timeframe: int):
|
||||||
self, ctx: commands.Context, count: int, timeframe: int):
|
|
||||||
"""
|
"""
|
||||||
Sets up an autoban if the specified number of messages are
|
Sets up an autoban if the specified number of messages are
|
||||||
filtered in the specified amount of time (in seconds)
|
filtered in the specified amount of time (in seconds)
|
||||||
"""
|
"""
|
||||||
if (count <= 0) != (timeframe <= 0):
|
if (count <= 0) != (timeframe <= 0):
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Count and timeframe either both need to be 0 "
|
_(
|
||||||
|
"Count and timeframe either both need to be 0 "
|
||||||
"or both need to be greater than 0!"
|
"or both need to be greater than 0!"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -213,9 +203,7 @@ class Filter:
|
|||||||
if filter_count > 0 and filter_time > 0:
|
if filter_count > 0 and filter_time > 0:
|
||||||
if message.created_at.timestamp() >= next_reset_time:
|
if message.created_at.timestamp() >= next_reset_time:
|
||||||
next_reset_time = message.created_at.timestamp() + filter_time
|
next_reset_time = message.created_at.timestamp() + filter_time
|
||||||
await self.settings.member(author).next_reset_time.set(
|
await self.settings.member(author).next_reset_time.set(next_reset_time)
|
||||||
next_reset_time
|
|
||||||
)
|
|
||||||
if user_count > 0:
|
if user_count > 0:
|
||||||
user_count = 0
|
user_count = 0
|
||||||
await self.settings.member(author).filter_count.set(user_count)
|
await self.settings.member(author).filter_count.set(user_count)
|
||||||
@ -231,8 +219,10 @@ class Filter:
|
|||||||
if filter_count > 0 and filter_time > 0:
|
if filter_count > 0 and filter_time > 0:
|
||||||
user_count += 1
|
user_count += 1
|
||||||
await self.settings.member(author).filter_count.set(user_count)
|
await self.settings.member(author).filter_count.set(user_count)
|
||||||
if user_count >= filter_count and \
|
if (
|
||||||
message.created_at.timestamp() < next_reset_time:
|
user_count >= filter_count
|
||||||
|
and message.created_at.timestamp() < next_reset_time
|
||||||
|
):
|
||||||
reason = "Autoban (too many filtered messages)"
|
reason = "Autoban (too many filtered messages)"
|
||||||
try:
|
try:
|
||||||
await server.ban(author, reason=reason)
|
await server.ban(author, reason=reason)
|
||||||
@ -240,8 +230,13 @@ class Filter:
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
await modlog.create_case(
|
await modlog.create_case(
|
||||||
self.bot, server, message.created_at,
|
self.bot,
|
||||||
"filterban", author, server.me, reason
|
server,
|
||||||
|
message.created_at,
|
||||||
|
"filterban",
|
||||||
|
author,
|
||||||
|
server.me,
|
||||||
|
reason,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def on_message(self, message: discord.Message):
|
async def on_message(self, message: discord.Message):
|
||||||
@ -323,4 +318,3 @@ class Filter:
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
TO_TRANSLATE = [
|
TO_TRANSLATE = ["../filter.py"]
|
||||||
'../filter.py'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def regen_messages():
|
def regen_messages():
|
||||||
subprocess.run(
|
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||||
['pygettext', '-n'] + TO_TRANSLATE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -21,6 +21,7 @@ class RPS(Enum):
|
|||||||
|
|
||||||
|
|
||||||
class RPSParser:
|
class RPSParser:
|
||||||
|
|
||||||
def __init__(self, argument):
|
def __init__(self, argument):
|
||||||
argument = argument.lower()
|
argument = argument.lower()
|
||||||
if argument == "rock":
|
if argument == "rock":
|
||||||
@ -40,13 +41,26 @@ class General:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.stopwatches = {}
|
self.stopwatches = {}
|
||||||
self.ball = [
|
self.ball = [
|
||||||
_("As I see it, yes"), _("It is certain"), _("It is decidedly so"),
|
_("As I see it, yes"),
|
||||||
_("Most likely"), _("Outlook good"), _("Signs point to yes"),
|
_("It is certain"),
|
||||||
_("Without a doubt"), _("Yes"), _("Yes – definitely"), _("You may rely on it"),
|
_("It is decidedly so"),
|
||||||
_("Reply hazy, try again"), _("Ask again later"),
|
_("Most likely"),
|
||||||
_("Better not tell you now"), _("Cannot predict now"),
|
_("Outlook good"),
|
||||||
_("Concentrate and ask again"), _("Don't count on it"), _("My reply is no"),
|
_("Signs point to yes"),
|
||||||
_("My sources say no"), _("Outlook not so good"), _("Very doubtful")
|
_("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()
|
@commands.command()
|
||||||
@ -57,12 +71,12 @@ class General:
|
|||||||
"""
|
"""
|
||||||
choices = [escape(c, mass_mentions=True) for c in choices]
|
choices = [escape(c, mass_mentions=True) for c in choices]
|
||||||
if len(choices) < 2:
|
if len(choices) < 2:
|
||||||
await ctx.send(_('Not enough choices to pick from.'))
|
await ctx.send(_("Not enough choices to pick from."))
|
||||||
else:
|
else:
|
||||||
await ctx.send(choice(choices))
|
await ctx.send(choice(choices))
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def roll(self, ctx, number : int = 100):
|
async def roll(self, ctx, number: int = 100):
|
||||||
"""Rolls random number (between 1 and user choice)
|
"""Rolls random number (between 1 and user choice)
|
||||||
|
|
||||||
Defaults to 100.
|
Defaults to 100.
|
||||||
@ -70,14 +84,12 @@ class General:
|
|||||||
author = ctx.author
|
author = ctx.author
|
||||||
if number > 1:
|
if number > 1:
|
||||||
n = randint(1, number)
|
n = randint(1, number)
|
||||||
await ctx.send(
|
await ctx.send(_("{} :game_die: {} :game_die:").format(author.mention, n))
|
||||||
_("{} :game_die: {} :game_die:").format(author.mention, n)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("{} Maybe higher than 1? ;P").format(author.mention))
|
await ctx.send(_("{} Maybe higher than 1? ;P").format(author.mention))
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def flip(self, ctx, user: discord.Member=None):
|
async def flip(self, ctx, user: discord.Member = None):
|
||||||
"""Flips a coin... or a user.
|
"""Flips a coin... or a user.
|
||||||
|
|
||||||
Defaults to coin.
|
Defaults to coin.
|
||||||
@ -86,8 +98,7 @@ class General:
|
|||||||
msg = ""
|
msg = ""
|
||||||
if user.id == ctx.bot.user.id:
|
if user.id == ctx.bot.user.id:
|
||||||
user = ctx.author
|
user = ctx.author
|
||||||
msg = _("Nice try. You think this is funny?\n"
|
msg = _("Nice try. You think this is funny?\n" "How about *this* instead:\n\n")
|
||||||
"How about *this* instead:\n\n")
|
|
||||||
char = "abcdefghijklmnopqrstuvwxyz"
|
char = "abcdefghijklmnopqrstuvwxyz"
|
||||||
tran = "ɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz"
|
tran = "ɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz"
|
||||||
table = str.maketrans(char, tran)
|
table = str.maketrans(char, tran)
|
||||||
@ -98,23 +109,21 @@ class General:
|
|||||||
name = name.translate(table)
|
name = name.translate(table)
|
||||||
await ctx.send(msg + "(╯°□°)╯︵ " + name[::-1])
|
await ctx.send(msg + "(╯°□°)╯︵ " + name[::-1])
|
||||||
else:
|
else:
|
||||||
await ctx.send(
|
await ctx.send(_("*flips a coin and... ") + choice([_("HEADS!*"), _("TAILS!*")]))
|
||||||
_("*flips a coin and... ") + choice([_("HEADS!*"), _("TAILS!*")])
|
|
||||||
)
|
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def rps(self, ctx, your_choice : RPSParser):
|
async def rps(self, ctx, your_choice: RPSParser):
|
||||||
"""Play rock paper scissors"""
|
"""Play rock paper scissors"""
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
player_choice = your_choice.choice
|
player_choice = your_choice.choice
|
||||||
red_choice = choice((RPS.rock, RPS.paper, RPS.scissors))
|
red_choice = choice((RPS.rock, RPS.paper, RPS.scissors))
|
||||||
cond = {
|
cond = {
|
||||||
(RPS.rock, RPS.paper) : False,
|
(RPS.rock, RPS.paper): False,
|
||||||
(RPS.rock, RPS.scissors) : True,
|
(RPS.rock, RPS.scissors): True,
|
||||||
(RPS.paper, RPS.rock) : True,
|
(RPS.paper, RPS.rock): True,
|
||||||
(RPS.paper, RPS.scissors) : False,
|
(RPS.paper, RPS.scissors): False,
|
||||||
(RPS.scissors, RPS.rock) : False,
|
(RPS.scissors, RPS.rock): False,
|
||||||
(RPS.scissors, RPS.paper) : True
|
(RPS.scissors, RPS.paper): True,
|
||||||
}
|
}
|
||||||
|
|
||||||
if red_choice == player_choice:
|
if red_choice == player_choice:
|
||||||
@ -123,20 +132,14 @@ class General:
|
|||||||
outcome = cond[(player_choice, red_choice)]
|
outcome = cond[(player_choice, red_choice)]
|
||||||
|
|
||||||
if outcome is True:
|
if outcome is True:
|
||||||
await ctx.send(_("{} You win {}!").format(
|
await ctx.send(_("{} You win {}!").format(red_choice.value, author.mention))
|
||||||
red_choice.value, author.mention
|
|
||||||
))
|
|
||||||
elif outcome is False:
|
elif outcome is False:
|
||||||
await ctx.send(_("{} You lose {}!").format(
|
await ctx.send(_("{} You lose {}!").format(red_choice.value, author.mention))
|
||||||
red_choice.value, author.mention
|
|
||||||
))
|
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("{} We're square {}!").format(
|
await ctx.send(_("{} We're square {}!").format(red_choice.value, author.mention))
|
||||||
red_choice.value, author.mention
|
|
||||||
))
|
|
||||||
|
|
||||||
@commands.command(name="8", aliases=["8ball"])
|
@commands.command(name="8", aliases=["8ball"])
|
||||||
async def _8ball(self, ctx, *, question : str):
|
async def _8ball(self, ctx, *, question: str):
|
||||||
"""Ask 8 ball a question
|
"""Ask 8 ball a question
|
||||||
|
|
||||||
Question must end with a question mark.
|
Question must end with a question mark.
|
||||||
@ -160,14 +163,14 @@ class General:
|
|||||||
self.stopwatches.pop(author.id, None)
|
self.stopwatches.pop(author.id, None)
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def lmgtfy(self, ctx, *, search_terms : str):
|
async def lmgtfy(self, ctx, *, search_terms: str):
|
||||||
"""Creates a lmgtfy link"""
|
"""Creates a lmgtfy link"""
|
||||||
search_terms = escape(search_terms.replace(" ", "+"), mass_mentions=True)
|
search_terms = escape(search_terms.replace(" ", "+"), mass_mentions=True)
|
||||||
await ctx.send("https://lmgtfy.com/?q={}".format(search_terms))
|
await ctx.send("https://lmgtfy.com/?q={}".format(search_terms))
|
||||||
|
|
||||||
@commands.command(hidden=True)
|
@commands.command(hidden=True)
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def hug(self, ctx, user : discord.Member, intensity : int=1):
|
async def hug(self, ctx, user: discord.Member, intensity: int = 1):
|
||||||
"""Because everyone likes hugs
|
"""Because everyone likes hugs
|
||||||
|
|
||||||
Up to 10 intensity levels."""
|
Up to 10 intensity levels."""
|
||||||
@ -186,7 +189,7 @@ class General:
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def userinfo(self, ctx, *, user: discord.Member=None):
|
async def userinfo(self, ctx, *, user: discord.Member = None):
|
||||||
"""Shows users's informations"""
|
"""Shows users's informations"""
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
@ -196,8 +199,7 @@ class General:
|
|||||||
|
|
||||||
# A special case for a special someone :^)
|
# A special case for a special someone :^)
|
||||||
special_date = datetime.datetime(2016, 1, 10, 6, 8, 4, 443000)
|
special_date = datetime.datetime(2016, 1, 10, 6, 8, 4, 443000)
|
||||||
is_special = (user.id == 96130341705637888 and
|
is_special = (user.id == 96130341705637888 and guild.id == 133049272517001216)
|
||||||
guild.id == 133049272517001216)
|
|
||||||
|
|
||||||
roles = sorted(user.roles)[1:]
|
roles = sorted(user.roles)[1:]
|
||||||
|
|
||||||
@ -206,8 +208,7 @@ class General:
|
|||||||
since_joined = (ctx.message.created_at - joined_at).days
|
since_joined = (ctx.message.created_at - joined_at).days
|
||||||
user_joined = joined_at.strftime("%d %b %Y %H:%M")
|
user_joined = joined_at.strftime("%d %b %Y %H:%M")
|
||||||
user_created = user.created_at.strftime("%d %b %Y %H:%M")
|
user_created = user.created_at.strftime("%d %b %Y %H:%M")
|
||||||
member_number = sorted(guild.members,
|
member_number = sorted(guild.members, key=lambda m: m.joined_at).index(user) + 1
|
||||||
key=lambda m: m.joined_at).index(user) + 1
|
|
||||||
|
|
||||||
created_on = _("{}\n({} days ago)").format(user_created, since_created)
|
created_on = _("{}\n({} days ago)").format(user_created, since_created)
|
||||||
joined_on = _("{}\n({} days ago)").format(user_joined, since_joined)
|
joined_on = _("{}\n({} days ago)").format(user_joined, since_joined)
|
||||||
@ -233,15 +234,14 @@ class General:
|
|||||||
data.add_field(name=_("Joined Discord on"), value=created_on)
|
data.add_field(name=_("Joined Discord on"), value=created_on)
|
||||||
data.add_field(name=_("Joined this server on"), value=joined_on)
|
data.add_field(name=_("Joined this server on"), value=joined_on)
|
||||||
data.add_field(name=_("Roles"), value=roles, inline=False)
|
data.add_field(name=_("Roles"), value=roles, inline=False)
|
||||||
data.set_footer(text=_("Member #{} | User ID: {}"
|
data.set_footer(text=_("Member #{} | User ID: {}" "").format(member_number, user.id))
|
||||||
"").format(member_number, user.id))
|
|
||||||
|
|
||||||
name = str(user)
|
name = str(user)
|
||||||
name = " ~ ".join((name, user.nick)) if user.nick else name
|
name = " ~ ".join((name, user.nick)) if user.nick else name
|
||||||
|
|
||||||
if user.avatar:
|
if user.avatar:
|
||||||
avatar = user.avatar_url
|
avatar = user.avatar_url
|
||||||
avatar = avatar.replace('webp', 'png')
|
avatar = avatar.replace("webp", "png")
|
||||||
data.set_author(name=name, url=avatar)
|
data.set_author(name=name, url=avatar)
|
||||||
data.set_thumbnail(url=avatar)
|
data.set_thumbnail(url=avatar)
|
||||||
else:
|
else:
|
||||||
@ -250,31 +250,34 @@ class General:
|
|||||||
try:
|
try:
|
||||||
await ctx.send(embed=data)
|
await ctx.send(embed=data)
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
await ctx.send(_("I need the `Embed links` permission "
|
await ctx.send(_("I need the `Embed links` permission " "to send this."))
|
||||||
"to send this."))
|
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def serverinfo(self, ctx):
|
async def serverinfo(self, ctx):
|
||||||
"""Shows server's informations"""
|
"""Shows server's informations"""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
online = len([m.status for m in guild.members
|
online = len(
|
||||||
if m.status == discord.Status.online or
|
[
|
||||||
m.status == discord.Status.idle])
|
m.status
|
||||||
|
for m in guild.members
|
||||||
|
if m.status == discord.Status.online or m.status == discord.Status.idle
|
||||||
|
]
|
||||||
|
)
|
||||||
total_users = len(guild.members)
|
total_users = len(guild.members)
|
||||||
text_channels = len(guild.text_channels)
|
text_channels = len(guild.text_channels)
|
||||||
voice_channels = len(guild.voice_channels)
|
voice_channels = len(guild.voice_channels)
|
||||||
passed = (ctx.message.created_at - guild.created_at).days
|
passed = (ctx.message.created_at - guild.created_at).days
|
||||||
created_at = (_("Since {}. That's over {} days ago!"
|
created_at = (
|
||||||
"").format(guild.created_at.strftime("%d %b %Y %H:%M"),
|
_("Since {}. That's over {} days ago!" "").format(
|
||||||
passed))
|
guild.created_at.strftime("%d %b %Y %H:%M"), passed
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
colour = ''.join([choice('0123456789ABCDEF') for x in range(6)])
|
colour = "".join([choice("0123456789ABCDEF") for x in range(6)])
|
||||||
colour = randint(0, 0xFFFFFF)
|
colour = randint(0, 0xFFFFFF)
|
||||||
|
|
||||||
data = discord.Embed(
|
data = discord.Embed(description=created_at, colour=discord.Colour(value=colour))
|
||||||
description=created_at,
|
|
||||||
colour=discord.Colour(value=colour))
|
|
||||||
data.add_field(name=_("Region"), value=str(guild.region))
|
data.add_field(name=_("Region"), value=str(guild.region))
|
||||||
data.add_field(name=_("Users"), value="{}/{}".format(online, total_users))
|
data.add_field(name=_("Users"), value="{}/{}".format(online, total_users))
|
||||||
data.add_field(name=_("Text Channels"), value=text_channels)
|
data.add_field(name=_("Text Channels"), value=text_channels)
|
||||||
@ -292,16 +295,16 @@ class General:
|
|||||||
try:
|
try:
|
||||||
await ctx.send(embed=data)
|
await ctx.send(embed=data)
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
await ctx.send(_("I need the `Embed links` permission "
|
await ctx.send(_("I need the `Embed links` permission " "to send this."))
|
||||||
"to send this."))
|
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def urban(self, ctx, *, search_terms: str, definition_number: int=1):
|
async def urban(self, ctx, *, search_terms: str, definition_number: int = 1):
|
||||||
"""Urban Dictionary search
|
"""Urban Dictionary search
|
||||||
|
|
||||||
Definition number must be between 1 and 10"""
|
Definition number must be between 1 and 10"""
|
||||||
|
|
||||||
def encode(s):
|
def encode(s):
|
||||||
return quote_plus(s, encoding='utf-8', errors='replace')
|
return quote_plus(s, encoding="utf-8", errors="replace")
|
||||||
|
|
||||||
# definition_number is just there to show up in the help
|
# definition_number is just there to show up in the help
|
||||||
# all this mess is to avoid forcing double quotes on the user
|
# all this mess is to avoid forcing double quotes on the user
|
||||||
@ -326,18 +329,19 @@ class General:
|
|||||||
result = await r.json()
|
result = await r.json()
|
||||||
item_list = result["list"]
|
item_list = result["list"]
|
||||||
if item_list:
|
if item_list:
|
||||||
definition = item_list[pos]['definition']
|
definition = item_list[pos]["definition"]
|
||||||
example = item_list[pos]['example']
|
example = item_list[pos]["example"]
|
||||||
defs = len(item_list)
|
defs = len(item_list)
|
||||||
msg = ("**Definition #{} out of {}:\n**{}\n\n"
|
msg = (
|
||||||
"**Example:\n**{}".format(pos+1, defs, definition,
|
"**Definition #{} out of {}:\n**{}\n\n"
|
||||||
example))
|
"**Example:\n**{}".format(pos + 1, defs, definition, example)
|
||||||
|
)
|
||||||
msg = pagify(msg, ["\n"])
|
msg = pagify(msg, ["\n"])
|
||||||
for page in msg:
|
for page in msg:
|
||||||
await ctx.send(page)
|
await ctx.send(page)
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Your search terms gave no results."))
|
await ctx.send(_("Your search terms gave no results."))
|
||||||
except IndexError:
|
except IndexError:
|
||||||
await ctx.send(_("There is no definition #{}").format(pos+1))
|
await ctx.send(_("There is no definition #{}").format(pos + 1))
|
||||||
except:
|
except:
|
||||||
await ctx.send(_("Error."))
|
await ctx.send(_("Error."))
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
TO_TRANSLATE = [
|
TO_TRANSLATE = ["../general.py"]
|
||||||
'../general.py'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def regen_messages():
|
def regen_messages():
|
||||||
subprocess.run(
|
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||||
['pygettext', '-n'] + TO_TRANSLATE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -13,9 +13,7 @@ GIPHY_API_KEY = "dc6zaTOxFJmzC"
|
|||||||
@cog_i18n(_)
|
@cog_i18n(_)
|
||||||
class Image:
|
class Image:
|
||||||
"""Image related commands."""
|
"""Image related commands."""
|
||||||
default_global = {
|
default_global = {"imgur_client_id": None}
|
||||||
"imgur_client_id": None
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
@ -45,7 +43,9 @@ class Image:
|
|||||||
if not imgur_client_id:
|
if not imgur_client_id:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("A client ID has not been set! Please set one with {}").format(
|
_("A client ID has not been set! Please set one with {}").format(
|
||||||
"`{}imgurcreds`".format(ctx.prefix)))
|
"`{}imgurcreds`".format(ctx.prefix)
|
||||||
|
)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
headers = {"Authorization": "Client-ID {}".format(imgur_client_id)}
|
headers = {"Authorization": "Client-ID {}".format(imgur_client_id)}
|
||||||
async with self.session.get(url, headers=headers, params=params) as search_get:
|
async with self.session.get(url, headers=headers, params=params) as search_get:
|
||||||
@ -66,7 +66,9 @@ class Image:
|
|||||||
await ctx.send(_("Something went wrong. Error code is {}").format(data["status"]))
|
await ctx.send(_("Something went wrong. Error code is {}").format(data["status"]))
|
||||||
|
|
||||||
@_imgur.command(name="subreddit")
|
@_imgur.command(name="subreddit")
|
||||||
async def imgur_subreddit(self, ctx, subreddit: str, sort_type: str="top", window: str="day"):
|
async def imgur_subreddit(
|
||||||
|
self, ctx, subreddit: str, sort_type: str = "top", window: str = "day"
|
||||||
|
):
|
||||||
"""Gets images from the specified subreddit section
|
"""Gets images from the specified subreddit section
|
||||||
|
|
||||||
Sort types: new, top
|
Sort types: new, top
|
||||||
@ -90,7 +92,9 @@ class Image:
|
|||||||
if not imgur_client_id:
|
if not imgur_client_id:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("A client ID has not been set! Please set one with {}").format(
|
_("A client ID has not been set! Please set one with {}").format(
|
||||||
"`{}imgurcreds`".format(ctx.prefix)))
|
"`{}imgurcreds`".format(ctx.prefix)
|
||||||
|
)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
links = []
|
links = []
|
||||||
@ -139,8 +143,10 @@ class Image:
|
|||||||
await ctx.send_help()
|
await ctx.send_help()
|
||||||
return
|
return
|
||||||
|
|
||||||
url = ("http://api.giphy.com/v1/gifs/search?&api_key={}&q={}"
|
url = (
|
||||||
"".format(GIPHY_API_KEY, keywords))
|
"http://api.giphy.com/v1/gifs/search?&api_key={}&q={}"
|
||||||
|
"".format(GIPHY_API_KEY, keywords)
|
||||||
|
)
|
||||||
|
|
||||||
async with self.session.get(url) as r:
|
async with self.session.get(url) as r:
|
||||||
result = await r.json()
|
result = await r.json()
|
||||||
@ -161,8 +167,10 @@ class Image:
|
|||||||
await ctx.send_help()
|
await ctx.send_help()
|
||||||
return
|
return
|
||||||
|
|
||||||
url = ("http://api.giphy.com/v1/gifs/random?&api_key={}&tag={}"
|
url = (
|
||||||
"".format(GIPHY_API_KEY, keywords))
|
"http://api.giphy.com/v1/gifs/random?&api_key={}&tag={}"
|
||||||
|
"".format(GIPHY_API_KEY, keywords)
|
||||||
|
)
|
||||||
|
|
||||||
async with self.session.get(url) as r:
|
async with self.session.get(url) as r:
|
||||||
result = await r.json()
|
result = await r.json()
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
TO_TRANSLATE = [
|
TO_TRANSLATE = ["../image.py"]
|
||||||
'../image.py'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def regen_messages():
|
def regen_messages():
|
||||||
subprocess.run(
|
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||||
['pygettext', '-n'] + TO_TRANSLATE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import discord
|
|||||||
|
|
||||||
|
|
||||||
def mod_or_voice_permissions(**perms):
|
def mod_or_voice_permissions(**perms):
|
||||||
|
|
||||||
async def pred(ctx: commands.Context):
|
async def pred(ctx: commands.Context):
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
@ -23,10 +24,12 @@ def mod_or_voice_permissions(**perms):
|
|||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return commands.check(pred)
|
return commands.check(pred)
|
||||||
|
|
||||||
|
|
||||||
def admin_or_voice_permissions(**perms):
|
def admin_or_voice_permissions(**perms):
|
||||||
|
|
||||||
async def pred(ctx: commands.Context):
|
async def pred(ctx: commands.Context):
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
@ -42,10 +45,12 @@ def admin_or_voice_permissions(**perms):
|
|||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return commands.check(pred)
|
return commands.check(pred)
|
||||||
|
|
||||||
|
|
||||||
def bot_has_voice_permissions(**perms):
|
def bot_has_voice_permissions(**perms):
|
||||||
|
|
||||||
async def pred(ctx: commands.Context):
|
async def pred(ctx: commands.Context):
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
for vc in guild.voice_channels:
|
for vc in guild.voice_channels:
|
||||||
@ -55,4 +60,5 @@ def bot_has_voice_permissions(**perms):
|
|||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return commands.check(pred)
|
return commands.check(pred)
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
TO_TRANSLATE = [
|
TO_TRANSLATE = ["../mod.py"]
|
||||||
'../mod.py'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def regen_messages():
|
def regen_messages():
|
||||||
subprocess.run(
|
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||||
['pygettext', '-n'] + TO_TRANSLATE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,14 +1,10 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
TO_TRANSLATE = [
|
TO_TRANSLATE = ["../modlog.py"]
|
||||||
'../modlog.py'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def regen_messages():
|
def regen_messages():
|
||||||
subprocess.run(
|
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||||
['pygettext', '-n'] + TO_TRANSLATE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -5,7 +5,7 @@ from redbot.core.bot import Red
|
|||||||
from redbot.core.i18n import Translator, cog_i18n
|
from redbot.core.i18n import Translator, cog_i18n
|
||||||
from redbot.core.utils.chat_formatting import box
|
from redbot.core.utils.chat_formatting import box
|
||||||
|
|
||||||
_ = Translator('ModLog', __file__)
|
_ = Translator("ModLog", __file__)
|
||||||
|
|
||||||
|
|
||||||
@cog_i18n(_)
|
@cog_i18n(_)
|
||||||
@ -32,15 +32,12 @@ class ModLog:
|
|||||||
if channel:
|
if channel:
|
||||||
if channel.permissions_for(guild.me).send_messages:
|
if channel.permissions_for(guild.me).send_messages:
|
||||||
await modlog.set_modlog_channel(guild, channel)
|
await modlog.set_modlog_channel(guild, channel)
|
||||||
await ctx.send(
|
await ctx.send(_("Mod events will be sent to {}").format(channel.mention))
|
||||||
_("Mod events will be sent to {}").format(
|
|
||||||
channel.mention
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("I do not have permissions to "
|
_("I do not have permissions to " "send messages in {}!").format(
|
||||||
"send messages in {}!").format(channel.mention)
|
channel.mention
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
@ -51,7 +48,7 @@ class ModLog:
|
|||||||
await modlog.set_modlog_channel(guild, None)
|
await modlog.set_modlog_channel(guild, None)
|
||||||
await ctx.send(_("Mod log deactivated."))
|
await ctx.send(_("Mod log deactivated."))
|
||||||
|
|
||||||
@modlogset.command(name='cases')
|
@modlogset.command(name="cases")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def set_cases(self, ctx: commands.Context, action: str = None):
|
async def set_cases(self, ctx: commands.Context, action: str = None):
|
||||||
"""Enables or disables case creation for each type of mod action"""
|
"""Enables or disables case creation for each type of mod action"""
|
||||||
@ -64,8 +61,8 @@ class ModLog:
|
|||||||
msg = ""
|
msg = ""
|
||||||
for ct in casetypes:
|
for ct in casetypes:
|
||||||
enabled = await ct.is_enabled()
|
enabled = await ct.is_enabled()
|
||||||
value = 'enabled' if enabled else 'disabled'
|
value = "enabled" if enabled else "disabled"
|
||||||
msg += '%s : %s\n' % (ct.name, value)
|
msg += "%s : %s\n" % (ct.name, value)
|
||||||
|
|
||||||
msg = title + "\n" + box(msg)
|
msg = title + "\n" + box(msg)
|
||||||
await ctx.send(msg)
|
await ctx.send(msg)
|
||||||
@ -79,8 +76,8 @@ class ModLog:
|
|||||||
await casetype.set_enabled(True if not enabled else False)
|
await casetype.set_enabled(True if not enabled else False)
|
||||||
|
|
||||||
msg = (
|
msg = (
|
||||||
_('Case creation for {} actions is now {}.').format(
|
_("Case creation for {} actions is now {}.").format(
|
||||||
action, 'enabled' if not enabled else 'disabled'
|
action, "enabled" if not enabled else "disabled"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
await ctx.send(msg)
|
await ctx.send(msg)
|
||||||
@ -133,8 +130,10 @@ class ModLog:
|
|||||||
if audit_type:
|
if audit_type:
|
||||||
audit_case = None
|
audit_case = None
|
||||||
async for entry in guild.audit_logs(action=audit_type):
|
async for entry in guild.audit_logs(action=audit_type):
|
||||||
if entry.target.id == case_before.user.id and \
|
if (
|
||||||
entry.action == audit_type:
|
entry.target.id == case_before.user.id
|
||||||
|
and entry.action == audit_type
|
||||||
|
):
|
||||||
audit_case = entry
|
audit_case = entry
|
||||||
break
|
break
|
||||||
if audit_case:
|
if audit_case:
|
||||||
@ -145,9 +144,7 @@ class ModLog:
|
|||||||
if not (is_guild_owner or is_case_author or author_is_mod):
|
if not (is_guild_owner or is_case_author or author_is_mod):
|
||||||
await ctx.send(_("You are not authorized to modify that case!"))
|
await ctx.send(_("You are not authorized to modify that case!"))
|
||||||
return
|
return
|
||||||
to_modify = {
|
to_modify = {"reason": reason}
|
||||||
"reason": reason,
|
|
||||||
}
|
|
||||||
if case_before.moderator != author:
|
if case_before.moderator != author:
|
||||||
to_modify["amended_by"] = author
|
to_modify["amended_by"] = author
|
||||||
to_modify["modified_at"] = ctx.message.created_at.timestamp()
|
to_modify["modified_at"] = ctx.message.created_at.timestamp()
|
||||||
|
|||||||
@ -22,15 +22,9 @@ log = logging.getLogger("red.reports")
|
|||||||
@cog_i18n(_)
|
@cog_i18n(_)
|
||||||
class Reports:
|
class Reports:
|
||||||
|
|
||||||
default_guild_settings = {
|
default_guild_settings = {"output_channel": None, "active": False, "next_ticket": 1}
|
||||||
"output_channel": None,
|
|
||||||
"active": False,
|
|
||||||
"next_ticket": 1
|
|
||||||
}
|
|
||||||
|
|
||||||
default_report = {
|
default_report = {"report": {}}
|
||||||
'report': {}
|
|
||||||
}
|
|
||||||
|
|
||||||
# This can be made configureable later if it
|
# This can be made configureable later if it
|
||||||
# becomes an issue.
|
# becomes an issue.
|
||||||
@ -42,15 +36,14 @@ class Reports:
|
|||||||
(timedelta(seconds=5), 1),
|
(timedelta(seconds=5), 1),
|
||||||
(timedelta(minutes=5), 3),
|
(timedelta(minutes=5), 3),
|
||||||
(timedelta(hours=1), 10),
|
(timedelta(hours=1), 10),
|
||||||
(timedelta(days=1), 24)
|
(timedelta(days=1), 24),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, bot: Red):
|
def __init__(self, bot: Red):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.config = Config.get_conf(
|
self.config = Config.get_conf(self, 78631113035100160, force_registration=True)
|
||||||
self, 78631113035100160, force_registration=True)
|
|
||||||
self.config.register_guild(**self.default_guild_settings)
|
self.config.register_guild(**self.default_guild_settings)
|
||||||
self.config.register_custom('REPORT', **self.default_report)
|
self.config.register_custom("REPORT", **self.default_report)
|
||||||
self.antispam = {}
|
self.antispam = {}
|
||||||
self.user_cache = []
|
self.user_cache = []
|
||||||
self.tunnel_store = {}
|
self.tunnel_store = {}
|
||||||
@ -59,9 +52,7 @@ class Reports:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def tunnels(self):
|
def tunnels(self):
|
||||||
return [
|
return [x["tun"] for x in self.tunnel_store.values()]
|
||||||
x['tun'] for x in self.tunnel_store.values()
|
|
||||||
]
|
|
||||||
|
|
||||||
@checks.admin_or_permissions(manage_guild=True)
|
@checks.admin_or_permissions(manage_guild=True)
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@ -99,9 +90,7 @@ class Reports:
|
|||||||
admin_role = discord.utils.get(
|
admin_role = discord.utils.get(
|
||||||
guild.roles, id=await self.bot.db.guild(guild).admin_role()
|
guild.roles, id=await self.bot.db.guild(guild).admin_role()
|
||||||
)
|
)
|
||||||
mod_role = discord.utils.get(
|
mod_role = discord.utils.get(guild.roles, id=await self.bot.db.guild(guild).mod_role())
|
||||||
guild.roles, id=await self.bot.db.guild(guild).mod_role()
|
|
||||||
)
|
|
||||||
ret |= any(r in m.roles for r in (mod_role, admin_role))
|
ret |= any(r in m.roles for r in (mod_role, admin_role))
|
||||||
if perms:
|
if perms:
|
||||||
ret |= m.guild_permissions >= perms
|
ret |= m.guild_permissions >= perms
|
||||||
@ -111,10 +100,13 @@ class Reports:
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
async def discover_guild(
|
async def discover_guild(
|
||||||
self, author: discord.User, *,
|
self,
|
||||||
mod: bool=False,
|
author: discord.User,
|
||||||
permissions: Union[discord.Permissions, dict]=None,
|
*,
|
||||||
prompt: str=""):
|
mod: bool = False,
|
||||||
|
permissions: Union[discord.Permissions, dict] = None,
|
||||||
|
prompt: str = ""
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
discovers which of shared guilds between the bot
|
discovers which of shared guilds between the bot
|
||||||
and provided user based on conditions (mod or permissions is an or)
|
and provided user based on conditions (mod or permissions is an or)
|
||||||
@ -151,13 +143,9 @@ class Reports:
|
|||||||
return m.author == author and m.channel == dm.channel
|
return m.author == author and m.channel == dm.channel
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message = await self.bot.wait_for(
|
message = await self.bot.wait_for("message", check=pred, timeout=45)
|
||||||
'message', check=pred, timeout=45
|
|
||||||
)
|
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
await author.send(
|
await author.send(_("You took too long to select. Try again later."))
|
||||||
_("You took too long to select. Try again later.")
|
|
||||||
)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -187,35 +175,31 @@ class Reports:
|
|||||||
if await self.bot.embed_requested(channel, author):
|
if await self.bot.embed_requested(channel, author):
|
||||||
em = discord.Embed(description=report)
|
em = discord.Embed(description=report)
|
||||||
em.set_author(
|
em.set_author(
|
||||||
name=_('Report from {0.display_name}').format(author),
|
name=_("Report from {0.display_name}").format(author), icon_url=author.avatar_url
|
||||||
icon_url=author.avatar_url
|
|
||||||
)
|
)
|
||||||
em.set_footer(text=_("Report #{}").format(ticket_number))
|
em.set_footer(text=_("Report #{}").format(ticket_number))
|
||||||
send_content = None
|
send_content = None
|
||||||
else:
|
else:
|
||||||
em = None
|
em = None
|
||||||
send_content = _(
|
send_content = _("Report from {author.mention} (Ticket #{number})").format(
|
||||||
'Report from {author.mention} (Ticket #{number})'
|
author=author, number=ticket_number
|
||||||
).format(author=author, number=ticket_number)
|
)
|
||||||
send_content += "\n" + report
|
send_content += "\n" + report
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await Tunnel.message_forwarder(
|
await Tunnel.message_forwarder(
|
||||||
destination=channel,
|
destination=channel, content=send_content, embed=em, files=files
|
||||||
content=send_content,
|
|
||||||
embed=em,
|
|
||||||
files=files
|
|
||||||
)
|
)
|
||||||
except (discord.Forbidden, discord.HTTPException):
|
except (discord.Forbidden, discord.HTTPException):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
await self.config.custom('REPORT', guild.id, ticket_number).report.set(
|
await self.config.custom("REPORT", guild.id, ticket_number).report.set(
|
||||||
{'user_id': author.id, 'report': report}
|
{"user_id": author.id, "report": report}
|
||||||
)
|
)
|
||||||
return ticket_number
|
return ticket_number
|
||||||
|
|
||||||
@commands.group(name="report", invoke_without_command=True)
|
@commands.group(name="report", invoke_without_command=True)
|
||||||
async def report(self, ctx: commands.Context, *, _report: str=""):
|
async def report(self, ctx: commands.Context, *, _report: str = ""):
|
||||||
"""
|
"""
|
||||||
Follow the prompts to make a report
|
Follow the prompts to make a report
|
||||||
|
|
||||||
@ -226,8 +210,7 @@ class Reports:
|
|||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
if guild is None:
|
if guild is None:
|
||||||
guild = await self.discover_guild(
|
guild = await self.discover_guild(
|
||||||
author,
|
author, prompt=_("Select a server to make a report in by number.")
|
||||||
prompt=_("Select a server to make a report in by number.")
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
@ -238,24 +221,23 @@ class Reports:
|
|||||||
return
|
return
|
||||||
g_active = await self.config.guild(guild).active()
|
g_active = await self.config.guild(guild).active()
|
||||||
if not g_active:
|
if not g_active:
|
||||||
return await author.send(
|
return await author.send(_("Reporting has not been enabled for this server"))
|
||||||
_("Reporting has not been enabled for this server")
|
|
||||||
)
|
|
||||||
if guild.id not in self.antispam:
|
if guild.id not in self.antispam:
|
||||||
self.antispam[guild.id] = {}
|
self.antispam[guild.id] = {}
|
||||||
if author.id not in self.antispam[guild.id]:
|
if author.id not in self.antispam[guild.id]:
|
||||||
self.antispam[guild.id][author.id] = AntiSpam(self.intervals)
|
self.antispam[guild.id][author.id] = AntiSpam(self.intervals)
|
||||||
if self.antispam[guild.id][author.id].spammy:
|
if self.antispam[guild.id][author.id].spammy:
|
||||||
return await author.send(
|
return await author.send(
|
||||||
_("You've sent a few too many of these recently. "
|
_(
|
||||||
|
"You've sent a few too many of these recently. "
|
||||||
"Contact a server admin to resolve this, or try again "
|
"Contact a server admin to resolve this, or try again "
|
||||||
"later.")
|
"later."
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if author.id in self.user_cache:
|
if author.id in self.user_cache:
|
||||||
return await author.send(
|
return await author.send(
|
||||||
_("Finish making your prior report "
|
_("Finish making your prior report " "before making an additional one")
|
||||||
"before making an additional one")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if ctx.guild:
|
if ctx.guild:
|
||||||
@ -273,13 +255,13 @@ class Reports:
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
dm = await author.send(
|
dm = await author.send(
|
||||||
_("Please respond to this message with your Report."
|
_(
|
||||||
"\nYour report should be a single message")
|
"Please respond to this message with your Report."
|
||||||
|
"\nYour report should be a single message"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
await ctx.send(
|
await ctx.send(_("This requires DMs enabled."))
|
||||||
_("This requires DMs enabled.")
|
|
||||||
)
|
|
||||||
self.user_cache.remove(author.id)
|
self.user_cache.remove(author.id)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -287,25 +269,17 @@ class Reports:
|
|||||||
return m.author == author and m.channel == dm.channel
|
return m.author == author and m.channel == dm.channel
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message = await self.bot.wait_for(
|
message = await self.bot.wait_for("message", check=pred, timeout=180)
|
||||||
'message', check=pred, timeout=180
|
|
||||||
)
|
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
await author.send(
|
await author.send(_("You took too long. Try again later."))
|
||||||
_("You took too long. Try again later.")
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
val = await self.send_report(message, guild)
|
val = await self.send_report(message, guild)
|
||||||
|
|
||||||
with contextlib.suppress(discord.Forbidden, discord.HTTPException):
|
with contextlib.suppress(discord.Forbidden, discord.HTTPException):
|
||||||
if val is None:
|
if val is None:
|
||||||
await author.send(
|
await author.send(_("There was an error sending your report."))
|
||||||
_("There was an error sending your report.")
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
await author.send(
|
await author.send(_("Your report was submitted. (Ticket #{})").format(val))
|
||||||
_("Your report was submitted. (Ticket #{})").format(val)
|
|
||||||
)
|
|
||||||
self.antispam[guild.id][author.id].stamp()
|
self.antispam[guild.id][author.id].stamp()
|
||||||
|
|
||||||
self.user_cache.remove(author.id)
|
self.user_cache.remove(author.id)
|
||||||
@ -318,18 +292,14 @@ class Reports:
|
|||||||
return
|
return
|
||||||
|
|
||||||
_id = payload.message_id
|
_id = payload.message_id
|
||||||
t = next(filter(
|
t = next(filter(lambda x: _id in x[1]["msgs"], self.tunnel_store.items()), None)
|
||||||
lambda x: _id in x[1]['msgs'],
|
|
||||||
self.tunnel_store.items()
|
|
||||||
), None)
|
|
||||||
|
|
||||||
if t is None:
|
if t is None:
|
||||||
return
|
return
|
||||||
tun = t[1]['tun']
|
tun = t[1]["tun"]
|
||||||
if payload.user_id in [x.id for x in tun.members]:
|
if payload.user_id in [x.id for x in tun.members]:
|
||||||
await tun.react_close(
|
await tun.react_close(
|
||||||
uid=payload.user_id,
|
uid=payload.user_id, message=_("{closer} has closed the correspondence")
|
||||||
message=_("{closer} has closed the correspondence")
|
|
||||||
)
|
)
|
||||||
self.tunnel_store.pop(t[0], None)
|
self.tunnel_store.pop(t[0], None)
|
||||||
|
|
||||||
@ -337,12 +307,12 @@ class Reports:
|
|||||||
for k, v in self.tunnel_store.items():
|
for k, v in self.tunnel_store.items():
|
||||||
topic = _("Re: ticket# {1} in {0.name}").format(*k)
|
topic = _("Re: ticket# {1} in {0.name}").format(*k)
|
||||||
# Tunnels won't forward unintended messages, this is safe
|
# Tunnels won't forward unintended messages, this is safe
|
||||||
msgs = await v['tun'].communicate(message=message, topic=topic)
|
msgs = await v["tun"].communicate(message=message, topic=topic)
|
||||||
if msgs:
|
if msgs:
|
||||||
self.tunnel_store[k]['msgs'] = msgs
|
self.tunnel_store[k]["msgs"] = msgs
|
||||||
|
|
||||||
@checks.mod_or_permissions(manage_members=True)
|
@checks.mod_or_permissions(manage_members=True)
|
||||||
@report.command(name='interact')
|
@report.command(name="interact")
|
||||||
async def response(self, ctx, ticket_number: int):
|
async def response(self, ctx, ticket_number: int):
|
||||||
"""
|
"""
|
||||||
opens a message tunnel between things you say in this channel
|
opens a message tunnel between things you say in this channel
|
||||||
@ -353,27 +323,24 @@ class Reports:
|
|||||||
|
|
||||||
# note, mod_or_permissions is an implicit guild_only
|
# note, mod_or_permissions is an implicit guild_only
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
rec = await self.config.custom(
|
rec = await self.config.custom("REPORT", guild.id, ticket_number).report()
|
||||||
'REPORT', guild.id, ticket_number).report()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user = guild.get_member(rec.get('user_id'))
|
user = guild.get_member(rec.get("user_id"))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return await ctx.send(
|
return await ctx.send(_("That ticket doesn't seem to exist"))
|
||||||
_("That ticket doesn't seem to exist")
|
|
||||||
)
|
|
||||||
|
|
||||||
if user is None:
|
if user is None:
|
||||||
return await ctx.send(
|
return await ctx.send(_("That user isn't here anymore."))
|
||||||
_("That user isn't here anymore.")
|
|
||||||
)
|
|
||||||
|
|
||||||
tun = Tunnel(recipient=user, origin=ctx.channel, sender=ctx.author)
|
tun = Tunnel(recipient=user, origin=ctx.channel, sender=ctx.author)
|
||||||
|
|
||||||
if tun is None:
|
if tun is None:
|
||||||
return await ctx.send(
|
return await ctx.send(
|
||||||
_("Either you or the user you are trying to reach already "
|
_(
|
||||||
"has an open communication.")
|
"Either you or the user you are trying to reach already "
|
||||||
|
"has an open communication."
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
big_topic = _(
|
big_topic = _(
|
||||||
@ -387,18 +354,13 @@ class Reports:
|
|||||||
"\nTunnels are not persistent across bot restarts."
|
"\nTunnels are not persistent across bot restarts."
|
||||||
)
|
)
|
||||||
topic = big_topic.format(
|
topic = big_topic.format(
|
||||||
ticketnum=ticket_number,
|
ticketnum=ticket_number, who=_("A moderator in `{guild.name}` has").format(guild=guild)
|
||||||
who=_("A moderator in `{guild.name}` has").format(guild=guild)
|
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
m = await tun.communicate(
|
m = await tun.communicate(message=ctx.message, topic=topic, skip_message_content=True)
|
||||||
message=ctx.message, topic=topic, skip_message_content=True
|
|
||||||
)
|
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
await ctx.send(_("User has disabled DMs."))
|
await ctx.send(_("User has disabled DMs."))
|
||||||
tun.close()
|
tun.close()
|
||||||
else:
|
else:
|
||||||
self.tunnel_store[(guild, ticket_number)] = {'tun': tun, 'msgs': m}
|
self.tunnel_store[(guild, ticket_number)] = {"tun": tun, "msgs": m}
|
||||||
await ctx.send(
|
await ctx.send(big_topic.format(who=_("You have"), ticketnum=ticket_number))
|
||||||
big_topic.format(who=_("You have"), ticketnum=ticket_number)
|
|
||||||
)
|
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
TO_TRANSLATE = [
|
TO_TRANSLATE = ["../mod.py"]
|
||||||
'../mod.py'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def regen_messages():
|
def regen_messages():
|
||||||
subprocess.run(
|
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||||
['pygettext', '-n'] + TO_TRANSLATE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -3,9 +3,24 @@ from redbot.core import Config, checks, commands
|
|||||||
from redbot.core.utils.chat_formatting import pagify
|
from redbot.core.utils.chat_formatting import pagify
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
from redbot.core.i18n import Translator, cog_i18n
|
from redbot.core.i18n import Translator, cog_i18n
|
||||||
from .streamtypes import TwitchStream, HitboxStream, MixerStream, PicartoStream, TwitchCommunity, YoutubeStream
|
from .streamtypes import (
|
||||||
from .errors import (OfflineStream, StreamNotFound, APIError, InvalidYoutubeCredentials,
|
TwitchStream,
|
||||||
CommunityNotFound, OfflineCommunity, StreamsError, InvalidTwitchCredentials)
|
HitboxStream,
|
||||||
|
MixerStream,
|
||||||
|
PicartoStream,
|
||||||
|
TwitchCommunity,
|
||||||
|
YoutubeStream,
|
||||||
|
)
|
||||||
|
from .errors import (
|
||||||
|
OfflineStream,
|
||||||
|
StreamNotFound,
|
||||||
|
APIError,
|
||||||
|
InvalidYoutubeCredentials,
|
||||||
|
CommunityNotFound,
|
||||||
|
OfflineCommunity,
|
||||||
|
StreamsError,
|
||||||
|
InvalidTwitchCredentials,
|
||||||
|
)
|
||||||
from . import streamtypes as StreamClasses
|
from . import streamtypes as StreamClasses
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import asyncio
|
import asyncio
|
||||||
@ -20,21 +35,11 @@ _ = Translator("Streams", __file__)
|
|||||||
@cog_i18n(_)
|
@cog_i18n(_)
|
||||||
class Streams:
|
class Streams:
|
||||||
|
|
||||||
global_defaults = {
|
global_defaults = {"tokens": {}, "streams": [], "communities": []}
|
||||||
"tokens": {},
|
|
||||||
"streams": [],
|
|
||||||
"communities": []
|
|
||||||
}
|
|
||||||
|
|
||||||
guild_defaults = {
|
guild_defaults = {"autodelete": False, "mention_everyone": False, "mention_here": False}
|
||||||
"autodelete": False,
|
|
||||||
"mention_everyone": False,
|
|
||||||
"mention_here": False
|
|
||||||
}
|
|
||||||
|
|
||||||
role_defaults = {
|
role_defaults = {"mention": False}
|
||||||
"mention": False
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, bot: Red):
|
def __init__(self, bot: Red):
|
||||||
self.db = Config.get_conf(self, 26262626)
|
self.db = Config.get_conf(self, 26262626)
|
||||||
@ -67,8 +72,7 @@ class Streams:
|
|||||||
async def twitch(self, ctx: commands.Context, channel_name: str):
|
async def twitch(self, ctx: commands.Context, channel_name: str):
|
||||||
"""Checks if a Twitch channel is streaming"""
|
"""Checks if a Twitch channel is streaming"""
|
||||||
token = await self.db.tokens.get_raw(TwitchStream.__name__, default=None)
|
token = await self.db.tokens.get_raw(TwitchStream.__name__, default=None)
|
||||||
stream = TwitchStream(name=channel_name,
|
stream = TwitchStream(name=channel_name, token=token)
|
||||||
token=token)
|
|
||||||
await self.check_online(ctx, stream)
|
await self.check_online(ctx, stream)
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@ -110,14 +114,21 @@ class Streams:
|
|||||||
except StreamNotFound:
|
except StreamNotFound:
|
||||||
await ctx.send(_("The channel doesn't seem to exist."))
|
await ctx.send(_("The channel doesn't seem to exist."))
|
||||||
except InvalidTwitchCredentials:
|
except InvalidTwitchCredentials:
|
||||||
await ctx.send(_("The twitch token is either invalid or has not been set. "
|
await ctx.send(
|
||||||
"See `{}`.").format("{}streamset twitchtoken".format(ctx.prefix)))
|
_("The twitch token is either invalid or has not been set. " "See `{}`.").format(
|
||||||
|
"{}streamset twitchtoken".format(ctx.prefix)
|
||||||
|
)
|
||||||
|
)
|
||||||
except InvalidYoutubeCredentials:
|
except InvalidYoutubeCredentials:
|
||||||
await ctx.send(_("The Youtube API key is either invalid or has not been set. "
|
await ctx.send(
|
||||||
"See {}.").format("`{}streamset youtubekey`".format(ctx.prefix)))
|
_("The Youtube API key is either invalid or has not been set. " "See {}.").format(
|
||||||
|
"`{}streamset youtubekey`".format(ctx.prefix)
|
||||||
|
)
|
||||||
|
)
|
||||||
except APIError:
|
except APIError:
|
||||||
await ctx.send(_("Something went wrong whilst trying to contact the "
|
await ctx.send(
|
||||||
"stream service's API."))
|
_("Something went wrong whilst trying to contact the " "stream service's API.")
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@ -166,7 +177,7 @@ class Streams:
|
|||||||
await self.stream_alert(ctx, PicartoStream, channel_name)
|
await self.stream_alert(ctx, PicartoStream, channel_name)
|
||||||
|
|
||||||
@streamalert.command(name="stop")
|
@streamalert.command(name="stop")
|
||||||
async def streamalert_stop(self, ctx: commands.Context, _all: bool=False):
|
async def streamalert_stop(self, ctx: commands.Context, _all: bool = False):
|
||||||
"""Stops all stream notifications in the channel
|
"""Stops all stream notifications in the channel
|
||||||
|
|
||||||
Adding 'yes' will disable all notifications in the server"""
|
Adding 'yes' will disable all notifications in the server"""
|
||||||
@ -191,8 +202,9 @@ class Streams:
|
|||||||
self.streams = streams
|
self.streams = streams
|
||||||
await self.save_streams()
|
await self.save_streams()
|
||||||
|
|
||||||
msg = _("All {}'s stream alerts have been disabled."
|
msg = _("All {}'s stream alerts have been disabled." "").format(
|
||||||
"").format("server" if _all else "channel")
|
"server" if _all else "channel"
|
||||||
|
)
|
||||||
|
|
||||||
await ctx.send(msg)
|
await ctx.send(msg)
|
||||||
|
|
||||||
@ -226,23 +238,29 @@ class Streams:
|
|||||||
if is_yt and not self.check_name_or_id(channel_name):
|
if is_yt and not self.check_name_or_id(channel_name):
|
||||||
stream = _class(id=channel_name, token=token)
|
stream = _class(id=channel_name, token=token)
|
||||||
else:
|
else:
|
||||||
stream = _class(name=channel_name,
|
stream = _class(name=channel_name, token=token)
|
||||||
token=token)
|
|
||||||
try:
|
try:
|
||||||
exists = await self.check_exists(stream)
|
exists = await self.check_exists(stream)
|
||||||
except InvalidTwitchCredentials:
|
except InvalidTwitchCredentials:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("The twitch token is either invalid or has not been set. "
|
_("The twitch token is either invalid or has not been set. " "See {}.").format(
|
||||||
"See {}.").format("`{}streamset twitchtoken`".format(ctx.prefix)))
|
"`{}streamset twitchtoken`".format(ctx.prefix)
|
||||||
|
)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
except InvalidYoutubeCredentials:
|
except InvalidYoutubeCredentials:
|
||||||
await ctx.send(_("The Youtube API key is either invalid or has not been set. "
|
await ctx.send(
|
||||||
"See {}.").format("`{}streamset youtubekey`".format(ctx.prefix)))
|
_(
|
||||||
|
"The Youtube API key is either invalid or has not been set. " "See {}."
|
||||||
|
).format(
|
||||||
|
"`{}streamset youtubekey`".format(ctx.prefix)
|
||||||
|
)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
except APIError:
|
except APIError:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Something went wrong whilst trying to contact the "
|
_("Something went wrong whilst trying to contact the " "stream service's API.")
|
||||||
"stream service's API."))
|
)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
if not exists:
|
if not exists:
|
||||||
@ -260,16 +278,18 @@ class Streams:
|
|||||||
await community.get_community_streams()
|
await community.get_community_streams()
|
||||||
except InvalidTwitchCredentials:
|
except InvalidTwitchCredentials:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("The twitch token is either invalid or has not been set. "
|
_("The twitch token is either invalid or has not been set. " "See {}.").format(
|
||||||
"See {}.").format("`{}streamset twitchtoken`".format(ctx.prefix)))
|
"`{}streamset twitchtoken`".format(ctx.prefix)
|
||||||
|
)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
except CommunityNotFound:
|
except CommunityNotFound:
|
||||||
await ctx.send(_("That community doesn't seem to exist."))
|
await ctx.send(_("That community doesn't seem to exist."))
|
||||||
return
|
return
|
||||||
except APIError:
|
except APIError:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Something went wrong whilst trying to contact the "
|
_("Something went wrong whilst trying to contact the " "stream service's API.")
|
||||||
"stream service's API."))
|
)
|
||||||
return
|
return
|
||||||
except OfflineCommunity:
|
except OfflineCommunity:
|
||||||
pass
|
pass
|
||||||
@ -331,12 +351,21 @@ class Streams:
|
|||||||
current_setting = await self.db.guild(guild).mention_everyone()
|
current_setting = await self.db.guild(guild).mention_everyone()
|
||||||
if current_setting:
|
if current_setting:
|
||||||
await self.db.guild(guild).mention_everyone.set(False)
|
await self.db.guild(guild).mention_everyone.set(False)
|
||||||
await ctx.send(_("{} will no longer be mentioned "
|
await ctx.send(
|
||||||
"for a stream alert.").format("@\u200beveryone"))
|
_("{} will no longer be mentioned " "for a stream alert.").format(
|
||||||
|
"@\u200beveryone"
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await self.db.guild(guild).mention_everyone.set(True)
|
await self.db.guild(guild).mention_everyone.set(True)
|
||||||
await ctx.send(_("When a stream configured for stream alerts "
|
await ctx.send(
|
||||||
"comes online, {} will be mentioned").format("@\u200beveryone"))
|
_(
|
||||||
|
"When a stream configured for stream alerts "
|
||||||
|
"comes online, {} will be mentioned"
|
||||||
|
).format(
|
||||||
|
"@\u200beveryone"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@mention.command(aliases=["here"])
|
@mention.command(aliases=["here"])
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@ -346,12 +375,19 @@ class Streams:
|
|||||||
current_setting = await self.db.guild(guild).mention_here()
|
current_setting = await self.db.guild(guild).mention_here()
|
||||||
if current_setting:
|
if current_setting:
|
||||||
await self.db.guild(guild).mention_here.set(False)
|
await self.db.guild(guild).mention_here.set(False)
|
||||||
await ctx.send(_("{} will no longer be mentioned "
|
await ctx.send(
|
||||||
"for a stream alert.").format("@\u200bhere"))
|
_("{} will no longer be mentioned " "for a stream alert.").format("@\u200bhere")
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await self.db.guild(guild).mention_here.set(True)
|
await self.db.guild(guild).mention_here.set(True)
|
||||||
await ctx.send(_("When a stream configured for stream alerts "
|
await ctx.send(
|
||||||
"comes online, {} will be mentioned").format("@\u200bhere"))
|
_(
|
||||||
|
"When a stream configured for stream alerts "
|
||||||
|
"comes online, {} will be mentioned"
|
||||||
|
).format(
|
||||||
|
"@\u200bhere"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@mention.command()
|
@mention.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@ -363,13 +399,22 @@ class Streams:
|
|||||||
return
|
return
|
||||||
if current_setting:
|
if current_setting:
|
||||||
await self.db.role(role).mention.set(False)
|
await self.db.role(role).mention.set(False)
|
||||||
await ctx.send(_("{} will no longer be mentioned "
|
await ctx.send(
|
||||||
"for a stream alert").format("@\u200b{}".format(role.name)))
|
_("{} will no longer be mentioned " "for a stream alert").format(
|
||||||
|
"@\u200b{}".format(role.name)
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await self.db.role(role).mention.set(True)
|
await self.db.role(role).mention.set(True)
|
||||||
await ctx.send(_("When a stream configured for stream alerts "
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"When a stream configured for stream alerts "
|
||||||
"comes online, {} will be mentioned"
|
"comes online, {} will be mentioned"
|
||||||
"").format("@\u200b{}".format(role.name)))
|
""
|
||||||
|
).format(
|
||||||
|
"@\u200b{}".format(role.name)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@streamset.command()
|
@streamset.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@ -377,8 +422,7 @@ class Streams:
|
|||||||
"""Toggles automatic deletion of notifications for streams that go offline"""
|
"""Toggles automatic deletion of notifications for streams that go offline"""
|
||||||
await self.db.guild(ctx.guild).autodelete.set(on_off)
|
await self.db.guild(ctx.guild).autodelete.set(on_off)
|
||||||
if on_off:
|
if on_off:
|
||||||
await ctx.send("The notifications will be deleted once "
|
await ctx.send("The notifications will be deleted once " "streams go offline.")
|
||||||
"streams go offline.")
|
|
||||||
else:
|
else:
|
||||||
await ctx.send("Notifications will never be deleted.")
|
await ctx.send("Notifications will never be deleted.")
|
||||||
|
|
||||||
@ -387,14 +431,20 @@ class Streams:
|
|||||||
stream.channels.append(ctx.channel.id)
|
stream.channels.append(ctx.channel.id)
|
||||||
if stream not in self.streams:
|
if stream not in self.streams:
|
||||||
self.streams.append(stream)
|
self.streams.append(stream)
|
||||||
await ctx.send(_("I'll send a notification in this channel when {} "
|
await ctx.send(
|
||||||
"is online.").format(stream.name))
|
_("I'll send a notification in this channel when {} " "is online.").format(
|
||||||
|
stream.name
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
stream.channels.remove(ctx.channel.id)
|
stream.channels.remove(ctx.channel.id)
|
||||||
if not stream.channels:
|
if not stream.channels:
|
||||||
self.streams.remove(stream)
|
self.streams.remove(stream)
|
||||||
await ctx.send(_("I won't send notifications about {} in this "
|
await ctx.send(
|
||||||
"channel anymore.").format(stream.name))
|
_("I won't send notifications about {} in this " "channel anymore.").format(
|
||||||
|
stream.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
await self.save_streams()
|
await self.save_streams()
|
||||||
|
|
||||||
@ -403,16 +453,28 @@ class Streams:
|
|||||||
community.channels.append(ctx.channel.id)
|
community.channels.append(ctx.channel.id)
|
||||||
if community not in self.communities:
|
if community not in self.communities:
|
||||||
self.communities.append(community)
|
self.communities.append(community)
|
||||||
await ctx.send(_("I'll send a notification in this channel when a "
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"I'll send a notification in this channel when a "
|
||||||
"channel is streaming to the {} community"
|
"channel is streaming to the {} community"
|
||||||
"").format(community.name))
|
""
|
||||||
|
).format(
|
||||||
|
community.name
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
community.channels.remove(ctx.channel.id)
|
community.channels.remove(ctx.channel.id)
|
||||||
if not community.channels:
|
if not community.channels:
|
||||||
self.communities.remove(community)
|
self.communities.remove(community)
|
||||||
await ctx.send(_("I won't send notifications about channels streaming "
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"I won't send notifications about channels streaming "
|
||||||
"to the {} community in this channel anymore"
|
"to the {} community in this channel anymore"
|
||||||
"").format(community.name))
|
""
|
||||||
|
).format(
|
||||||
|
community.name
|
||||||
|
)
|
||||||
|
)
|
||||||
await self.save_communities()
|
await self.save_communities()
|
||||||
|
|
||||||
def get_stream(self, _class, name):
|
def get_stream(self, _class, name):
|
||||||
@ -499,13 +561,13 @@ class Streams:
|
|||||||
settings = self.db.guild(guild)
|
settings = self.db.guild(guild)
|
||||||
mentions = []
|
mentions = []
|
||||||
if await settings.mention_everyone():
|
if await settings.mention_everyone():
|
||||||
mentions.append('@everyone')
|
mentions.append("@everyone")
|
||||||
if await settings.mention_here():
|
if await settings.mention_here():
|
||||||
mentions.append('@here')
|
mentions.append("@here")
|
||||||
for role in guild.roles:
|
for role in guild.roles:
|
||||||
if await self.db.role(role).mention():
|
if await self.db.role(role).mention():
|
||||||
mentions.append(role.mention)
|
mentions.append(role.mention)
|
||||||
return ' '.join(mentions)
|
return " ".join(mentions)
|
||||||
|
|
||||||
async def check_communities(self):
|
async def check_communities(self):
|
||||||
for community in self.communities:
|
for community in self.communities:
|
||||||
@ -579,8 +641,7 @@ class Streams:
|
|||||||
# Fast dedupe below
|
# Fast dedupe below
|
||||||
seen = set()
|
seen = set()
|
||||||
seen_add = seen.add
|
seen_add = seen.add
|
||||||
return [x for x in streams
|
return [x for x in streams if not (x.name.lower() in seen or seen_add(x.name.lower()))]
|
||||||
if not (x.name.lower() in seen or seen_add(x.name.lower()))]
|
|
||||||
|
|
||||||
# return streams
|
# return streams
|
||||||
|
|
||||||
@ -604,8 +665,7 @@ class Streams:
|
|||||||
# Fast dedupe below
|
# Fast dedupe below
|
||||||
seen = set()
|
seen = set()
|
||||||
seen_add = seen.add
|
seen_add = seen.add
|
||||||
return [x for x in communities
|
return [x for x in communities if not (x.name.lower() in seen or seen_add(x.name.lower()))]
|
||||||
if not (x.name.lower() in seen or seen_add(x.name.lower()))]
|
|
||||||
# return communities
|
# return communities
|
||||||
|
|
||||||
async def save_streams(self):
|
async def save_streams(self):
|
||||||
|
|||||||
@ -1,5 +1,12 @@
|
|||||||
from .errors import StreamNotFound, APIError, OfflineStream, CommunityNotFound, OfflineCommunity, \
|
from .errors import (
|
||||||
InvalidYoutubeCredentials, InvalidTwitchCredentials
|
StreamNotFound,
|
||||||
|
APIError,
|
||||||
|
OfflineStream,
|
||||||
|
CommunityNotFound,
|
||||||
|
OfflineCommunity,
|
||||||
|
InvalidYoutubeCredentials,
|
||||||
|
InvalidTwitchCredentials,
|
||||||
|
)
|
||||||
from random import choice, sample
|
from random import choice, sample
|
||||||
from string import ascii_letters
|
from string import ascii_letters
|
||||||
import discord
|
import discord
|
||||||
@ -23,6 +30,7 @@ def rnd(url):
|
|||||||
|
|
||||||
|
|
||||||
class TwitchCommunity:
|
class TwitchCommunity:
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.name = kwargs.pop("name")
|
self.name = kwargs.pop("name")
|
||||||
self.id = kwargs.pop("id", None)
|
self.id = kwargs.pop("id", None)
|
||||||
@ -32,15 +40,12 @@ class TwitchCommunity:
|
|||||||
self.type = self.__class__.__name__
|
self.type = self.__class__.__name__
|
||||||
|
|
||||||
async def get_community_id(self):
|
async def get_community_id(self):
|
||||||
headers = {
|
headers = {"Accept": "application/vnd.twitchtv.v5+json", "Client-ID": str(self._token)}
|
||||||
"Accept": "application/vnd.twitchtv.v5+json",
|
params = {"name": self.name}
|
||||||
"Client-ID": str(self._token)
|
|
||||||
}
|
|
||||||
params = {
|
|
||||||
"name": self.name
|
|
||||||
}
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(TWITCH_COMMUNITIES_ENDPOINT, headers=headers, params=params) as r:
|
async with session.get(
|
||||||
|
TWITCH_COMMUNITIES_ENDPOINT, headers=headers, params=params
|
||||||
|
) as r:
|
||||||
data = await r.json()
|
data = await r.json()
|
||||||
if r.status == 200:
|
if r.status == 200:
|
||||||
return data["_id"]
|
return data["_id"]
|
||||||
@ -57,14 +62,8 @@ class TwitchCommunity:
|
|||||||
self.id = await self.get_community_id()
|
self.id = await self.get_community_id()
|
||||||
except CommunityNotFound:
|
except CommunityNotFound:
|
||||||
raise
|
raise
|
||||||
headers = {
|
headers = {"Accept": "application/vnd.twitchtv.v5+json", "Client-ID": str(self._token)}
|
||||||
"Accept": "application/vnd.twitchtv.v5+json",
|
params = {"community_id": self.id, "limit": 100}
|
||||||
"Client-ID": str(self._token)
|
|
||||||
}
|
|
||||||
params = {
|
|
||||||
"community_id": self.id,
|
|
||||||
"limit": 100
|
|
||||||
}
|
|
||||||
url = TWITCH_BASE_URL + "/kraken/streams"
|
url = TWITCH_BASE_URL + "/kraken/streams"
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(url, headers=headers, params=params) as r:
|
async with session.get(url, headers=headers, params=params) as r:
|
||||||
@ -82,14 +81,11 @@ class TwitchCommunity:
|
|||||||
raise APIError()
|
raise APIError()
|
||||||
|
|
||||||
async def make_embed(self, streams: list) -> discord.Embed:
|
async def make_embed(self, streams: list) -> discord.Embed:
|
||||||
headers = {
|
headers = {"Accept": "application/vnd.twitchtv.v5+json", "Client-ID": str(self._token)}
|
||||||
"Accept": "application/vnd.twitchtv.v5+json",
|
|
||||||
"Client-ID": str(self._token)
|
|
||||||
}
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(
|
async with session.get(
|
||||||
"{}/{}".format(TWITCH_COMMUNITIES_ENDPOINT, self.id),
|
"{}/{}".format(TWITCH_COMMUNITIES_ENDPOINT, self.id), headers=headers
|
||||||
headers=headers) as r:
|
) as r:
|
||||||
data = await r.json()
|
data = await r.json()
|
||||||
|
|
||||||
avatar = data["avatar_image_url"]
|
avatar = data["avatar_image_url"]
|
||||||
@ -102,9 +98,7 @@ class TwitchCommunity:
|
|||||||
else:
|
else:
|
||||||
stream_list = streams
|
stream_list = streams
|
||||||
for stream in stream_list:
|
for stream in stream_list:
|
||||||
name = "[{}]({})".format(
|
name = "[{}]({})".format(stream["channel"]["display_name"], stream["channel"]["url"])
|
||||||
stream["channel"]["display_name"], stream["channel"]["url"]
|
|
||||||
)
|
|
||||||
embed.add_field(name=stream["channel"]["status"], value=name, inline=False)
|
embed.add_field(name=stream["channel"]["status"], value=name, inline=False)
|
||||||
embed.color = 0x6441A4
|
embed.color = 0x6441A4
|
||||||
|
|
||||||
@ -125,10 +119,11 @@ class TwitchCommunity:
|
|||||||
|
|
||||||
|
|
||||||
class Stream:
|
class Stream:
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.name = kwargs.pop("name", None)
|
self.name = kwargs.pop("name", None)
|
||||||
self.channels = kwargs.pop("channels", [])
|
self.channels = kwargs.pop("channels", [])
|
||||||
#self.already_online = kwargs.pop("already_online", False)
|
# self.already_online = kwargs.pop("already_online", False)
|
||||||
self._messages_cache = kwargs.pop("_messages_cache", [])
|
self._messages_cache = kwargs.pop("_messages_cache", [])
|
||||||
self.type = self.__class__.__name__
|
self.type = self.__class__.__name__
|
||||||
|
|
||||||
@ -153,6 +148,7 @@ class Stream:
|
|||||||
|
|
||||||
|
|
||||||
class YoutubeStream(Stream):
|
class YoutubeStream(Stream):
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.id = kwargs.pop("id", None)
|
self.id = kwargs.pop("id", None)
|
||||||
self._token = kwargs.pop("token", None)
|
self._token = kwargs.pop("token", None)
|
||||||
@ -167,7 +163,7 @@ class YoutubeStream(Stream):
|
|||||||
"part": "snippet",
|
"part": "snippet",
|
||||||
"channelId": self.id,
|
"channelId": self.id,
|
||||||
"type": "video",
|
"type": "video",
|
||||||
"eventType": "live"
|
"eventType": "live",
|
||||||
}
|
}
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(url, params=params) as r:
|
async with session.get(url, params=params) as r:
|
||||||
@ -176,11 +172,7 @@ class YoutubeStream(Stream):
|
|||||||
raise OfflineStream()
|
raise OfflineStream()
|
||||||
elif "items" in data:
|
elif "items" in data:
|
||||||
vid_id = data["items"][0]["id"]["videoId"]
|
vid_id = data["items"][0]["id"]["videoId"]
|
||||||
params = {
|
params = {"key": self._token, "id": vid_id, "part": "snippet"}
|
||||||
"key": self._token,
|
|
||||||
"id": vid_id,
|
|
||||||
"part": "snippet"
|
|
||||||
}
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(YOUTUBE_VIDEOS_ENDPOINT, params=params) as r:
|
async with session.get(YOUTUBE_VIDEOS_ENDPOINT, params=params) as r:
|
||||||
data = await r.json()
|
data = await r.json()
|
||||||
@ -199,17 +191,16 @@ class YoutubeStream(Stream):
|
|||||||
return embed
|
return embed
|
||||||
|
|
||||||
async def fetch_id(self):
|
async def fetch_id(self):
|
||||||
params = {
|
params = {"key": self._token, "forUsername": self.name, "part": "id"}
|
||||||
"key": self._token,
|
|
||||||
"forUsername": self.name,
|
|
||||||
"part": "id"
|
|
||||||
}
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(YOUTUBE_CHANNELS_ENDPOINT, params=params) as r:
|
async with session.get(YOUTUBE_CHANNELS_ENDPOINT, params=params) as r:
|
||||||
data = await r.json()
|
data = await r.json()
|
||||||
|
|
||||||
if "error" in data and data["error"]["code"] == 400 and\
|
if (
|
||||||
data["error"]["errors"][0]["reason"] == "keyInvalid":
|
"error" in data
|
||||||
|
and data["error"]["code"] == 400
|
||||||
|
and data["error"]["errors"][0]["reason"] == "keyInvalid"
|
||||||
|
):
|
||||||
raise InvalidYoutubeCredentials()
|
raise InvalidYoutubeCredentials()
|
||||||
elif "items" in data and len(data["items"]) == 0:
|
elif "items" in data and len(data["items"]) == 0:
|
||||||
raise StreamNotFound()
|
raise StreamNotFound()
|
||||||
@ -222,6 +213,7 @@ class YoutubeStream(Stream):
|
|||||||
|
|
||||||
|
|
||||||
class TwitchStream(Stream):
|
class TwitchStream(Stream):
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.id = kwargs.pop("id", None)
|
self.id = kwargs.pop("id", None)
|
||||||
self._token = kwargs.pop("token", None)
|
self._token = kwargs.pop("token", None)
|
||||||
@ -232,19 +224,16 @@ class TwitchStream(Stream):
|
|||||||
self.id = await self.fetch_id()
|
self.id = await self.fetch_id()
|
||||||
|
|
||||||
url = TWITCH_STREAMS_ENDPOINT + self.id
|
url = TWITCH_STREAMS_ENDPOINT + self.id
|
||||||
header = {
|
header = {"Client-ID": str(self._token), "Accept": "application/vnd.twitchtv.v5+json"}
|
||||||
'Client-ID': str(self._token),
|
|
||||||
'Accept': 'application/vnd.twitchtv.v5+json'
|
|
||||||
}
|
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(url, headers=header) as r:
|
async with session.get(url, headers=header) as r:
|
||||||
data = await r.json(encoding='utf-8')
|
data = await r.json(encoding="utf-8")
|
||||||
if r.status == 200:
|
if r.status == 200:
|
||||||
if data["stream"] is None:
|
if data["stream"] is None:
|
||||||
#self.already_online = False
|
# self.already_online = False
|
||||||
raise OfflineStream()
|
raise OfflineStream()
|
||||||
#self.already_online = True
|
# self.already_online = True
|
||||||
# In case of rename
|
# In case of rename
|
||||||
self.name = data["stream"]["channel"]["name"]
|
self.name = data["stream"]["channel"]["name"]
|
||||||
return self.make_embed(data)
|
return self.make_embed(data)
|
||||||
@ -256,10 +245,7 @@ class TwitchStream(Stream):
|
|||||||
raise APIError()
|
raise APIError()
|
||||||
|
|
||||||
async def fetch_id(self):
|
async def fetch_id(self):
|
||||||
header = {
|
header = {"Client-ID": str(self._token), "Accept": "application/vnd.twitchtv.v5+json"}
|
||||||
'Client-ID': str(self._token),
|
|
||||||
'Accept': 'application/vnd.twitchtv.v5+json'
|
|
||||||
}
|
|
||||||
url = TWITCH_ID_ENDPOINT + self.name
|
url = TWITCH_ID_ENDPOINT + self.name
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
@ -280,8 +266,7 @@ class TwitchStream(Stream):
|
|||||||
url = channel["url"]
|
url = channel["url"]
|
||||||
logo = channel["logo"]
|
logo = channel["logo"]
|
||||||
if logo is None:
|
if logo is None:
|
||||||
logo = ("https://static-cdn.jtvnw.net/"
|
logo = ("https://static-cdn.jtvnw.net/" "jtv_user_pictures/xarth/404_user_70x70.png")
|
||||||
"jtv_user_pictures/xarth/404_user_70x70.png")
|
|
||||||
status = channel["status"]
|
status = channel["status"]
|
||||||
if not status:
|
if not status:
|
||||||
status = "Untitled broadcast"
|
status = "Untitled broadcast"
|
||||||
@ -303,21 +288,22 @@ class TwitchStream(Stream):
|
|||||||
|
|
||||||
|
|
||||||
class HitboxStream(Stream):
|
class HitboxStream(Stream):
|
||||||
|
|
||||||
async def is_online(self):
|
async def is_online(self):
|
||||||
url = "https://api.hitbox.tv/media/live/" + self.name
|
url = "https://api.hitbox.tv/media/live/" + self.name
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(url) as r:
|
async with session.get(url) as r:
|
||||||
#data = await r.json(encoding='utf-8')
|
# data = await r.json(encoding='utf-8')
|
||||||
data = await r.text()
|
data = await r.text()
|
||||||
data = json.loads(data, strict=False)
|
data = json.loads(data, strict=False)
|
||||||
if "livestream" not in data:
|
if "livestream" not in data:
|
||||||
raise StreamNotFound()
|
raise StreamNotFound()
|
||||||
elif data["livestream"][0]["media_is_live"] == "0":
|
elif data["livestream"][0]["media_is_live"] == "0":
|
||||||
#self.already_online = False
|
# self.already_online = False
|
||||||
raise OfflineStream()
|
raise OfflineStream()
|
||||||
elif data["livestream"][0]["media_is_live"] == "1":
|
elif data["livestream"][0]["media_is_live"] == "1":
|
||||||
#self.already_online = True
|
# self.already_online = True
|
||||||
return self.make_embed(data)
|
return self.make_embed(data)
|
||||||
|
|
||||||
raise APIError()
|
raise APIError()
|
||||||
@ -340,20 +326,21 @@ class HitboxStream(Stream):
|
|||||||
|
|
||||||
|
|
||||||
class MixerStream(Stream):
|
class MixerStream(Stream):
|
||||||
|
|
||||||
async def is_online(self):
|
async def is_online(self):
|
||||||
url = "https://mixer.com/api/v1/channels/" + self.name
|
url = "https://mixer.com/api/v1/channels/" + self.name
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(url) as r:
|
async with session.get(url) as r:
|
||||||
#data = await r.json(encoding='utf-8')
|
# data = await r.json(encoding='utf-8')
|
||||||
data = await r.text(encoding='utf-8')
|
data = await r.text(encoding="utf-8")
|
||||||
if r.status == 200:
|
if r.status == 200:
|
||||||
data = json.loads(data, strict=False)
|
data = json.loads(data, strict=False)
|
||||||
if data["online"] is True:
|
if data["online"] is True:
|
||||||
#self.already_online = True
|
# self.already_online = True
|
||||||
return self.make_embed(data)
|
return self.make_embed(data)
|
||||||
else:
|
else:
|
||||||
#self.already_online = False
|
# self.already_online = False
|
||||||
raise OfflineStream()
|
raise OfflineStream()
|
||||||
elif r.status == 404:
|
elif r.status == 404:
|
||||||
raise StreamNotFound()
|
raise StreamNotFound()
|
||||||
@ -361,8 +348,7 @@ class MixerStream(Stream):
|
|||||||
raise APIError()
|
raise APIError()
|
||||||
|
|
||||||
def make_embed(self, data):
|
def make_embed(self, data):
|
||||||
default_avatar = ("https://mixer.com/_latest/assets/images/main/"
|
default_avatar = ("https://mixer.com/_latest/assets/images/main/" "avatars/default.jpg")
|
||||||
"avatars/default.jpg")
|
|
||||||
user = data["user"]
|
user = data["user"]
|
||||||
url = "https://mixer.com/" + data["token"]
|
url = "https://mixer.com/" + data["token"]
|
||||||
embed = discord.Embed(title=data["name"], url=url)
|
embed = discord.Embed(title=data["name"], url=url)
|
||||||
@ -382,19 +368,20 @@ class MixerStream(Stream):
|
|||||||
|
|
||||||
|
|
||||||
class PicartoStream(Stream):
|
class PicartoStream(Stream):
|
||||||
|
|
||||||
async def is_online(self):
|
async def is_online(self):
|
||||||
url = "https://api.picarto.tv/v1/channel/name/" + self.name
|
url = "https://api.picarto.tv/v1/channel/name/" + self.name
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(url) as r:
|
async with session.get(url) as r:
|
||||||
data = await r.text(encoding='utf-8')
|
data = await r.text(encoding="utf-8")
|
||||||
if r.status == 200:
|
if r.status == 200:
|
||||||
data = json.loads(data)
|
data = json.loads(data)
|
||||||
if data["online"] is True:
|
if data["online"] is True:
|
||||||
#self.already_online = True
|
# self.already_online = True
|
||||||
return self.make_embed(data)
|
return self.make_embed(data)
|
||||||
else:
|
else:
|
||||||
#self.already_online = False
|
# self.already_online = False
|
||||||
raise OfflineStream()
|
raise OfflineStream()
|
||||||
elif r.status == 404:
|
elif r.status == 404:
|
||||||
raise StreamNotFound()
|
raise StreamNotFound()
|
||||||
@ -402,8 +389,9 @@ class PicartoStream(Stream):
|
|||||||
raise APIError()
|
raise APIError()
|
||||||
|
|
||||||
def make_embed(self, data):
|
def make_embed(self, data):
|
||||||
avatar = rnd("https://picarto.tv/user_data/usrimg/{}/dsdefault.jpg"
|
avatar = rnd(
|
||||||
"".format(data["name"].lower()))
|
"https://picarto.tv/user_data/usrimg/{}/dsdefault.jpg" "".format(data["name"].lower())
|
||||||
|
)
|
||||||
url = "https://picarto.tv/" + data["name"]
|
url = "https://picarto.tv/" + data["name"]
|
||||||
thumbnail = data["thumbnails"]["web"]
|
thumbnail = data["thumbnails"]["web"]
|
||||||
embed = discord.Embed(title=data["title"], url=url)
|
embed = discord.Embed(title=data["title"], url=url)
|
||||||
@ -424,6 +412,5 @@ class PicartoStream(Stream):
|
|||||||
data["adult"] = ""
|
data["adult"] = ""
|
||||||
|
|
||||||
embed.color = 0x4C90F3
|
embed.color = 0x4C90F3
|
||||||
embed.set_footer(text="{adult}Category: {category} | Tags: {tags}"
|
embed.set_footer(text="{adult}Category: {category} | Tags: {tags}" "".format(**data))
|
||||||
"".format(**data))
|
|
||||||
return embed
|
return embed
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
TO_TRANSLATE = [
|
TO_TRANSLATE = ["../mod.py"]
|
||||||
'../mod.py'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def regen_messages():
|
def regen_messages():
|
||||||
subprocess.run(
|
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||||
['pygettext', '-n'] + TO_TRANSLATE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -10,11 +10,13 @@ from .log import LOG
|
|||||||
|
|
||||||
__all__ = ["TriviaSession"]
|
__all__ = ["TriviaSession"]
|
||||||
|
|
||||||
_REVEAL_MESSAGES = ("I know this one! {}!", "Easy: {}.",
|
_REVEAL_MESSAGES = ("I know this one! {}!", "Easy: {}.", "Oh really? It's {} of course.")
|
||||||
"Oh really? It's {} of course.")
|
_FAIL_MESSAGES = (
|
||||||
_FAIL_MESSAGES = ("To the next one I guess...", "Moving on...",
|
"To the next one I guess...",
|
||||||
|
"Moving on...",
|
||||||
"I'm sure you'll know the answer of the next one.",
|
"I'm sure you'll know the answer of the next one.",
|
||||||
"\N{PENSIVE FACE} Next one.")
|
"\N{PENSIVE FACE} Next one.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TriviaSession():
|
class TriviaSession():
|
||||||
@ -49,10 +51,7 @@ class TriviaSession():
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self, ctx, question_list: dict, settings: dict):
|
||||||
ctx,
|
|
||||||
question_list: dict,
|
|
||||||
settings: dict):
|
|
||||||
self.ctx = ctx
|
self.ctx = ctx
|
||||||
list_ = list(question_list.items())
|
list_ = list(question_list.items())
|
||||||
random.shuffle(list_)
|
random.shuffle(list_)
|
||||||
@ -128,9 +127,9 @@ class TriviaSession():
|
|||||||
num_lists = len(list_names)
|
num_lists = len(list_names)
|
||||||
if num_lists > 2:
|
if num_lists > 2:
|
||||||
# at least 3 lists, join all but last with comma
|
# at least 3 lists, join all but last with comma
|
||||||
msg = ", ".join(list_names[:num_lists-1])
|
msg = ", ".join(list_names[:num_lists - 1])
|
||||||
# join onto last with "and"
|
# join onto last with "and"
|
||||||
msg = " and ".join((msg, list_names[num_lists-1]))
|
msg = " and ".join((msg, list_names[num_lists - 1]))
|
||||||
else:
|
else:
|
||||||
# either 1 or 2 lists, join together with "and"
|
# either 1 or 2 lists, join together with "and"
|
||||||
msg = " and ".join(list_names)
|
msg = " and ".join(list_names)
|
||||||
@ -150,10 +149,7 @@ class TriviaSession():
|
|||||||
answers = _parse_answers(answers)
|
answers = _parse_answers(answers)
|
||||||
yield question, answers
|
yield question, answers
|
||||||
|
|
||||||
async def wait_for_answer(self,
|
async def wait_for_answer(self, answers, delay: float, timeout: float):
|
||||||
answers,
|
|
||||||
delay: float,
|
|
||||||
timeout: float):
|
|
||||||
"""Wait for a correct answer, and then respond.
|
"""Wait for a correct answer, and then respond.
|
||||||
|
|
||||||
Scores are also updated in this method.
|
Scores are also updated in this method.
|
||||||
@ -178,7 +174,8 @@ class TriviaSession():
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
message = await self.ctx.bot.wait_for(
|
message = await self.ctx.bot.wait_for(
|
||||||
"message", check=self.check_answer(answers), timeout=delay)
|
"message", check=self.check_answer(answers), timeout=delay
|
||||||
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
if time.time() - self._last_response >= timeout:
|
if time.time() - self._last_response >= timeout:
|
||||||
await self.ctx.send("Guys...? Well, I guess I'll stop then.")
|
await self.ctx.send("Guys...? Well, I guess I'll stop then.")
|
||||||
@ -194,8 +191,7 @@ class TriviaSession():
|
|||||||
await self.ctx.send(reply)
|
await self.ctx.send(reply)
|
||||||
else:
|
else:
|
||||||
self.scores[message.author] += 1
|
self.scores[message.author] += 1
|
||||||
reply = "You got it {}! **+1** to you!".format(
|
reply = "You got it {}! **+1** to you!".format(message.author.display_name)
|
||||||
message.author.display_name)
|
|
||||||
await self.ctx.send(reply)
|
await self.ctx.send(reply)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -218,9 +214,11 @@ class TriviaSession():
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
answers = tuple(s.lower() for s in answers)
|
answers = tuple(s.lower() for s in answers)
|
||||||
|
|
||||||
def _pred(message: discord.Message):
|
def _pred(message: discord.Message):
|
||||||
early_exit = (message.channel != self.ctx.channel
|
early_exit = (
|
||||||
or message.author == self.ctx.guild.me)
|
message.channel != self.ctx.channel or message.author == self.ctx.guild.me
|
||||||
|
)
|
||||||
if early_exit:
|
if early_exit:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -260,8 +258,7 @@ class TriviaSession():
|
|||||||
"""Cancel whichever tasks this session is running."""
|
"""Cancel whichever tasks this session is running."""
|
||||||
self._task.cancel()
|
self._task.cancel()
|
||||||
channel = self.ctx.channel
|
channel = self.ctx.channel
|
||||||
LOG.debug("Force stopping trivia session; #%s in %s", channel,
|
LOG.debug("Force stopping trivia session; #%s in %s", channel, channel.guild.id)
|
||||||
channel.guild.id)
|
|
||||||
|
|
||||||
async def pay_winner(self, multiplier: float):
|
async def pay_winner(self, multiplier: float):
|
||||||
"""Pay the winner of this trivia session.
|
"""Pay the winner of this trivia session.
|
||||||
@ -275,8 +272,7 @@ class TriviaSession():
|
|||||||
paid.
|
paid.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
(winner, score) = next((tup for tup in self.scores.most_common(1)),
|
(winner, score) = next((tup for tup in self.scores.most_common(1)), (None, None))
|
||||||
(None, None))
|
|
||||||
me_ = self.ctx.guild.me
|
me_ = self.ctx.guild.me
|
||||||
if winner is not None and winner != me_ and score > 0:
|
if winner is not None and winner != me_ and score > 0:
|
||||||
contestants = list(self.scores.keys())
|
contestants = list(self.scores.keys())
|
||||||
@ -285,13 +281,12 @@ class TriviaSession():
|
|||||||
if len(contestants) >= 3:
|
if len(contestants) >= 3:
|
||||||
amount = int(multiplier * score)
|
amount = int(multiplier * score)
|
||||||
if amount > 0:
|
if amount > 0:
|
||||||
LOG.debug("Paying trivia winner: %d credits --> %s",
|
LOG.debug("Paying trivia winner: %d credits --> %s", amount, str(winner))
|
||||||
amount, str(winner))
|
|
||||||
await deposit_credits(winner, int(multiplier * score))
|
await deposit_credits(winner, int(multiplier * score))
|
||||||
await self.ctx.send(
|
await self.ctx.send(
|
||||||
"Congratulations, {0}, you have received {1} credits"
|
"Congratulations, {0}, you have received {1} credits"
|
||||||
" for coming first.".format(winner.display_name,
|
" for coming first.".format(winner.display_name, amount)
|
||||||
amount))
|
)
|
||||||
|
|
||||||
|
|
||||||
def _parse_answers(answers):
|
def _parse_answers(answers):
|
||||||
|
|||||||
@ -26,8 +26,7 @@ class Trivia:
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.trivia_sessions = []
|
self.trivia_sessions = []
|
||||||
self.conf = Config.get_conf(
|
self.conf = Config.get_conf(self, identifier=UNIQUE_ID, force_registration=True)
|
||||||
self, identifier=UNIQUE_ID, force_registration=True)
|
|
||||||
|
|
||||||
self.conf.register_guild(
|
self.conf.register_guild(
|
||||||
max_score=10,
|
max_score=10,
|
||||||
@ -36,10 +35,10 @@ class Trivia:
|
|||||||
bot_plays=False,
|
bot_plays=False,
|
||||||
reveal_answer=True,
|
reveal_answer=True,
|
||||||
payout_multiplier=0.0,
|
payout_multiplier=0.0,
|
||||||
allow_override=True)
|
allow_override=True,
|
||||||
|
)
|
||||||
|
|
||||||
self.conf.register_member(
|
self.conf.register_member(wins=0, games=0, total_score=0)
|
||||||
wins=0, games=0, total_score=0)
|
|
||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@ -60,7 +59,8 @@ class Trivia:
|
|||||||
"Payout multiplier: {payout_multiplier}\n"
|
"Payout multiplier: {payout_multiplier}\n"
|
||||||
"Allow lists to override settings: {allow_override}"
|
"Allow lists to override settings: {allow_override}"
|
||||||
"".format(**settings_dict),
|
"".format(**settings_dict),
|
||||||
lang="py")
|
lang="py",
|
||||||
|
)
|
||||||
await ctx.send(msg)
|
await ctx.send(msg)
|
||||||
|
|
||||||
@triviaset.command(name="maxscore")
|
@triviaset.command(name="maxscore")
|
||||||
@ -81,8 +81,7 @@ class Trivia:
|
|||||||
return
|
return
|
||||||
settings = self.conf.guild(ctx.guild)
|
settings = self.conf.guild(ctx.guild)
|
||||||
await settings.delay.set(seconds)
|
await settings.delay.set(seconds)
|
||||||
await ctx.send("Done. Maximum seconds to answer set to {}."
|
await ctx.send("Done. Maximum seconds to answer set to {}." "".format(seconds))
|
||||||
"".format(seconds))
|
|
||||||
|
|
||||||
@triviaset.command(name="stopafter")
|
@triviaset.command(name="stopafter")
|
||||||
async def triviaset_stopafter(self, ctx: commands.Context, seconds: float):
|
async def triviaset_stopafter(self, ctx: commands.Context, seconds: float):
|
||||||
@ -92,38 +91,41 @@ class Trivia:
|
|||||||
await ctx.send("Must be larger than the answer time limit.")
|
await ctx.send("Must be larger than the answer time limit.")
|
||||||
return
|
return
|
||||||
await settings.timeout.set(seconds)
|
await settings.timeout.set(seconds)
|
||||||
await ctx.send("Done. Trivia sessions will now time out after {}"
|
await ctx.send(
|
||||||
" seconds of no responses.".format(seconds))
|
"Done. Trivia sessions will now time out after {}"
|
||||||
|
" seconds of no responses.".format(seconds)
|
||||||
|
)
|
||||||
|
|
||||||
@triviaset.command(name="override")
|
@triviaset.command(name="override")
|
||||||
async def triviaset_allowoverride(self,
|
async def triviaset_allowoverride(self, ctx: commands.Context, enabled: bool):
|
||||||
ctx: commands.Context,
|
|
||||||
enabled: bool):
|
|
||||||
"""Allow/disallow trivia lists to override settings."""
|
"""Allow/disallow trivia lists to override settings."""
|
||||||
settings = self.conf.guild(ctx.guild)
|
settings = self.conf.guild(ctx.guild)
|
||||||
await settings.allow_override.set(enabled)
|
await settings.allow_override.set(enabled)
|
||||||
enabled = "now" if enabled else "no longer"
|
enabled = "now" if enabled else "no longer"
|
||||||
await ctx.send("Done. Trivia lists can {} override the trivia settings"
|
await ctx.send(
|
||||||
" for this server.".format(enabled))
|
"Done. Trivia lists can {} override the trivia settings"
|
||||||
|
" for this server.".format(enabled)
|
||||||
|
)
|
||||||
|
|
||||||
@triviaset.command(name="botplays")
|
@triviaset.command(name="botplays")
|
||||||
async def trivaset_bot_plays(self,
|
async def trivaset_bot_plays(self, ctx: commands.Context, true_or_false: bool):
|
||||||
ctx: commands.Context,
|
|
||||||
true_or_false: bool):
|
|
||||||
"""Set whether or not the bot gains points.
|
"""Set whether or not the bot gains points.
|
||||||
|
|
||||||
If enabled, the bot will gain a point if no one guesses correctly.
|
If enabled, the bot will gain a point if no one guesses correctly.
|
||||||
"""
|
"""
|
||||||
settings = self.conf.guild(ctx.guild)
|
settings = self.conf.guild(ctx.guild)
|
||||||
await settings.bot_plays.set(true_or_false)
|
await settings.bot_plays.set(true_or_false)
|
||||||
await ctx.send("Done. " + (
|
await ctx.send(
|
||||||
"I'll gain a point if users don't answer in time." if true_or_false
|
"Done. "
|
||||||
else "Alright, I won't embarass you at trivia anymore."))
|
+ (
|
||||||
|
"I'll gain a point if users don't answer in time."
|
||||||
|
if true_or_false
|
||||||
|
else "Alright, I won't embarass you at trivia anymore."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@triviaset.command(name="revealanswer")
|
@triviaset.command(name="revealanswer")
|
||||||
async def trivaset_reveal_answer(self,
|
async def trivaset_reveal_answer(self, ctx: commands.Context, true_or_false: bool):
|
||||||
ctx: commands.Context,
|
|
||||||
true_or_false: bool):
|
|
||||||
"""Set whether or not the answer is revealed.
|
"""Set whether or not the answer is revealed.
|
||||||
|
|
||||||
If enabled, the bot will reveal the answer if no one guesses correctly
|
If enabled, the bot will reveal the answer if no one guesses correctly
|
||||||
@ -131,15 +133,18 @@ class Trivia:
|
|||||||
"""
|
"""
|
||||||
settings = self.conf.guild(ctx.guild)
|
settings = self.conf.guild(ctx.guild)
|
||||||
await settings.reveal_answer.set(true_or_false)
|
await settings.reveal_answer.set(true_or_false)
|
||||||
await ctx.send("Done. " + (
|
await ctx.send(
|
||||||
"I'll reveal the answer if no one knows it." if true_or_false else
|
"Done. "
|
||||||
"I won't reveal the answer to the questions anymore."))
|
+ (
|
||||||
|
"I'll reveal the answer if no one knows it."
|
||||||
|
if true_or_false
|
||||||
|
else "I won't reveal the answer to the questions anymore."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@triviaset.command(name="payout")
|
@triviaset.command(name="payout")
|
||||||
@check_global_setting_admin()
|
@check_global_setting_admin()
|
||||||
async def triviaset_payout_multiplier(self,
|
async def triviaset_payout_multiplier(self, ctx: commands.Context, multiplier: float):
|
||||||
ctx: commands.Context,
|
|
||||||
multiplier: float):
|
|
||||||
"""Set the payout multiplier.
|
"""Set the payout multiplier.
|
||||||
|
|
||||||
This can be any positive decimal number. If a user wins trivia when at
|
This can be any positive decimal number. If a user wins trivia when at
|
||||||
@ -155,8 +160,7 @@ class Trivia:
|
|||||||
return
|
return
|
||||||
await settings.payout_multiplier.set(multiplier)
|
await settings.payout_multiplier.set(multiplier)
|
||||||
if not multiplier:
|
if not multiplier:
|
||||||
await ctx.send("Done. I will no longer reward the winner with a"
|
await ctx.send("Done. I will no longer reward the winner with a" " payout.")
|
||||||
" payout.")
|
|
||||||
return
|
return
|
||||||
await ctx.send("Done. Payout multiplier set to {}.".format(multiplier))
|
await ctx.send("Done. Payout multiplier set to {}.".format(multiplier))
|
||||||
|
|
||||||
@ -174,8 +178,7 @@ class Trivia:
|
|||||||
categories = [c.lower() for c in categories]
|
categories = [c.lower() for c in categories]
|
||||||
session = self._get_trivia_session(ctx.channel)
|
session = self._get_trivia_session(ctx.channel)
|
||||||
if session is not None:
|
if session is not None:
|
||||||
await ctx.send(
|
await ctx.send("There is already an ongoing trivia session in this channel.")
|
||||||
"There is already an ongoing trivia session in this channel.")
|
|
||||||
return
|
return
|
||||||
trivia_dict = {}
|
trivia_dict = {}
|
||||||
authors = []
|
authors = []
|
||||||
@ -185,21 +188,26 @@ class Trivia:
|
|||||||
try:
|
try:
|
||||||
dict_ = self.get_trivia_list(category)
|
dict_ = self.get_trivia_list(category)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
await ctx.send("Invalid category `{0}`. See `{1}trivia list`"
|
await ctx.send(
|
||||||
|
"Invalid category `{0}`. See `{1}trivia list`"
|
||||||
" for a list of trivia categories."
|
" for a list of trivia categories."
|
||||||
"".format(category, ctx.prefix))
|
"".format(category, ctx.prefix)
|
||||||
|
)
|
||||||
except InvalidListError:
|
except InvalidListError:
|
||||||
await ctx.send("There was an error parsing the trivia list for"
|
await ctx.send(
|
||||||
|
"There was an error parsing the trivia list for"
|
||||||
" the `{}` category. It may be formatted"
|
" the `{}` category. It may be formatted"
|
||||||
" incorrectly.".format(category))
|
" incorrectly.".format(category)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
trivia_dict.update(dict_)
|
trivia_dict.update(dict_)
|
||||||
authors.append(trivia_dict.pop("AUTHOR", None))
|
authors.append(trivia_dict.pop("AUTHOR", None))
|
||||||
continue
|
continue
|
||||||
return
|
return
|
||||||
if not trivia_dict:
|
if not trivia_dict:
|
||||||
await ctx.send("The trivia list was parsed successfully, however"
|
await ctx.send(
|
||||||
" it appears to be empty!")
|
"The trivia list was parsed successfully, however" " it appears to be empty!"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
settings = await self.conf.guild(ctx.guild).all()
|
settings = await self.conf.guild(ctx.guild).all()
|
||||||
config = trivia_dict.pop("CONFIG", None)
|
config = trivia_dict.pop("CONFIG", None)
|
||||||
@ -215,13 +223,16 @@ class Trivia:
|
|||||||
"""Stop an ongoing trivia session."""
|
"""Stop an ongoing trivia session."""
|
||||||
session = self._get_trivia_session(ctx.channel)
|
session = self._get_trivia_session(ctx.channel)
|
||||||
if session is None:
|
if session is None:
|
||||||
await ctx.send(
|
await ctx.send("There is no ongoing trivia session in this channel.")
|
||||||
"There is no ongoing trivia session in this channel.")
|
|
||||||
return
|
return
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
auth_checks = (await ctx.bot.is_owner(author), await
|
auth_checks = (
|
||||||
ctx.bot.is_mod(author), await ctx.bot.is_admin(author),
|
await ctx.bot.is_owner(author),
|
||||||
author == ctx.guild.owner, author == session.ctx.author)
|
await ctx.bot.is_mod(author),
|
||||||
|
await ctx.bot.is_admin(author),
|
||||||
|
author == ctx.guild.owner,
|
||||||
|
author == session.ctx.author,
|
||||||
|
)
|
||||||
if any(auth_checks):
|
if any(auth_checks):
|
||||||
await session.end_game()
|
await session.end_game()
|
||||||
session.force_stop()
|
session.force_stop()
|
||||||
@ -234,8 +245,7 @@ class Trivia:
|
|||||||
"""List available trivia categories."""
|
"""List available trivia categories."""
|
||||||
lists = set(p.stem for p in self._all_lists())
|
lists = set(p.stem for p in self._all_lists())
|
||||||
|
|
||||||
msg = box("**Available trivia lists**\n\n{}"
|
msg = box("**Available trivia lists**\n\n{}" "".format(", ".join(sorted(lists))))
|
||||||
"".format(", ".join(sorted(lists))))
|
|
||||||
if len(msg) > 1000:
|
if len(msg) > 1000:
|
||||||
await ctx.author.send(msg)
|
await ctx.author.send(msg)
|
||||||
return
|
return
|
||||||
@ -256,10 +266,9 @@ class Trivia:
|
|||||||
|
|
||||||
@trivia_leaderboard.command(name="server")
|
@trivia_leaderboard.command(name="server")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def trivia_leaderboard_server(self,
|
async def trivia_leaderboard_server(
|
||||||
ctx: commands.Context,
|
self, ctx: commands.Context, sort_by: str = "wins", top: int = 10
|
||||||
sort_by: str="wins",
|
):
|
||||||
top: int=10):
|
|
||||||
"""Leaderboard for this server.
|
"""Leaderboard for this server.
|
||||||
|
|
||||||
<sort_by> can be any of the following fields:
|
<sort_by> can be any of the following fields:
|
||||||
@ -271,9 +280,11 @@ class Trivia:
|
|||||||
"""
|
"""
|
||||||
key = self._get_sort_key(sort_by)
|
key = self._get_sort_key(sort_by)
|
||||||
if key is None:
|
if key is None:
|
||||||
await ctx.send("Unknown field `{}`, see `{}help trivia "
|
await ctx.send(
|
||||||
|
"Unknown field `{}`, see `{}help trivia "
|
||||||
"leaderboard server` for valid fields to sort by."
|
"leaderboard server` for valid fields to sort by."
|
||||||
"".format(sort_by, ctx.prefix))
|
"".format(sort_by, ctx.prefix)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
data = await self.conf.all_members(guild)
|
data = await self.conf.all_members(guild)
|
||||||
@ -282,10 +293,9 @@ class Trivia:
|
|||||||
await self.send_leaderboard(ctx, data, key, top)
|
await self.send_leaderboard(ctx, data, key, top)
|
||||||
|
|
||||||
@trivia_leaderboard.command(name="global")
|
@trivia_leaderboard.command(name="global")
|
||||||
async def trivia_leaderboard_global(self,
|
async def trivia_leaderboard_global(
|
||||||
ctx: commands.Context,
|
self, ctx: commands.Context, sort_by: str = "wins", top: int = 10
|
||||||
sort_by: str="wins",
|
):
|
||||||
top: int=10):
|
|
||||||
"""Global trivia leaderboard.
|
"""Global trivia leaderboard.
|
||||||
|
|
||||||
<sort_by> can be any of the following fields:
|
<sort_by> can be any of the following fields:
|
||||||
@ -298,9 +308,11 @@ class Trivia:
|
|||||||
"""
|
"""
|
||||||
key = self._get_sort_key(sort_by)
|
key = self._get_sort_key(sort_by)
|
||||||
if key is None:
|
if key is None:
|
||||||
await ctx.send("Unknown field `{}`, see `{}help trivia "
|
await ctx.send(
|
||||||
|
"Unknown field `{}`, see `{}help trivia "
|
||||||
"leaderboard global` for valid fields to sort by."
|
"leaderboard global` for valid fields to sort by."
|
||||||
"".format(sort_by, ctx.prefix))
|
"".format(sort_by, ctx.prefix)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
data = await self.conf.all_members()
|
data = await self.conf.all_members()
|
||||||
collated_data = {}
|
collated_data = {}
|
||||||
@ -327,11 +339,7 @@ class Trivia:
|
|||||||
elif key in ("total", "score", "answers", "correct"):
|
elif key in ("total", "score", "answers", "correct"):
|
||||||
return "total_score"
|
return "total_score"
|
||||||
|
|
||||||
async def send_leaderboard(self,
|
async def send_leaderboard(self, ctx: commands.Context, data: dict, key: str, top: int):
|
||||||
ctx: commands.Context,
|
|
||||||
data: dict,
|
|
||||||
key: str,
|
|
||||||
top: int):
|
|
||||||
"""Send the leaderboard from the given data.
|
"""Send the leaderboard from the given data.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -382,23 +390,34 @@ class Trivia:
|
|||||||
items = sorted(items, key=lambda t: t[1][key], reverse=True)
|
items = sorted(items, key=lambda t: t[1][key], reverse=True)
|
||||||
max_name_len = max(map(lambda m: len(str(m)), data.keys()))
|
max_name_len = max(map(lambda m: len(str(m)), data.keys()))
|
||||||
# Headers
|
# Headers
|
||||||
headers = ("Rank", "Member{}".format(" " * (max_name_len - 6)), "Wins",
|
headers = (
|
||||||
"Games Played", "Total Score", "Average Score")
|
"Rank",
|
||||||
|
"Member{}".format(" " * (max_name_len - 6)),
|
||||||
|
"Wins",
|
||||||
|
"Games Played",
|
||||||
|
"Total Score",
|
||||||
|
"Average Score",
|
||||||
|
)
|
||||||
lines = [" | ".join(headers)]
|
lines = [" | ".join(headers)]
|
||||||
# Header underlines
|
# Header underlines
|
||||||
lines.append(" | ".join(("-" * len(h) for h in headers)))
|
lines.append(" | ".join(("-" * len(h) for h in headers)))
|
||||||
for rank, tup in enumerate(items, 1):
|
for rank, tup in enumerate(items, 1):
|
||||||
member, m_data = tup
|
member, m_data = tup
|
||||||
# Align fields to header width
|
# Align fields to header width
|
||||||
fields = tuple(map(str, (rank,
|
fields = tuple(
|
||||||
|
map(
|
||||||
|
str,
|
||||||
|
(
|
||||||
|
rank,
|
||||||
member,
|
member,
|
||||||
m_data["wins"],
|
m_data["wins"],
|
||||||
m_data["games"],
|
m_data["games"],
|
||||||
m_data["total_score"],
|
m_data["total_score"],
|
||||||
round(m_data["average_score"], 2))))
|
round(m_data["average_score"], 2),
|
||||||
padding = [
|
),
|
||||||
" " * (len(h) - len(f)) for h, f in zip(headers, fields)
|
)
|
||||||
]
|
)
|
||||||
|
padding = [" " * (len(h) - len(f)) for h, f in zip(headers, fields)]
|
||||||
fields = tuple(f + padding[i] for i, f in enumerate(fields))
|
fields = tuple(f + padding[i] for i, f in enumerate(fields))
|
||||||
lines.append(" | ".join(fields).format(member=member, **m_data))
|
lines.append(" | ".join(fields).format(member=member, **m_data))
|
||||||
if rank == top:
|
if rank == top:
|
||||||
@ -418,8 +437,7 @@ class Trivia:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
channel = session.ctx.channel
|
channel = session.ctx.channel
|
||||||
LOG.debug("Ending trivia session; #%s in %s", channel,
|
LOG.debug("Ending trivia session; #%s in %s", channel, channel.guild.id)
|
||||||
channel.guild.id)
|
|
||||||
if session in self.trivia_sessions:
|
if session in self.trivia_sessions:
|
||||||
self.trivia_sessions.remove(session)
|
self.trivia_sessions.remove(session)
|
||||||
if session.scores:
|
if session.scores:
|
||||||
@ -462,10 +480,9 @@ class Trivia:
|
|||||||
try:
|
try:
|
||||||
path = next(p for p in self._all_lists() if p.stem == category)
|
path = next(p for p in self._all_lists() if p.stem == category)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
raise FileNotFoundError("Could not find the `{}` category"
|
raise FileNotFoundError("Could not find the `{}` category" "".format(category))
|
||||||
"".format(category))
|
|
||||||
|
|
||||||
with path.open(encoding='utf-8') as file:
|
with path.open(encoding="utf-8") as file:
|
||||||
try:
|
try:
|
||||||
dict_ = yaml.load(file)
|
dict_ = yaml.load(file)
|
||||||
except yaml.error.YAMLError as exc:
|
except yaml.error.YAMLError as exc:
|
||||||
@ -473,14 +490,13 @@ class Trivia:
|
|||||||
else:
|
else:
|
||||||
return dict_
|
return dict_
|
||||||
|
|
||||||
def _get_trivia_session(self,
|
def _get_trivia_session(self, channel: discord.TextChannel) -> TriviaSession:
|
||||||
channel: discord.TextChannel) -> TriviaSession:
|
return next(
|
||||||
return next((session for session in self.trivia_sessions
|
(session for session in self.trivia_sessions if session.ctx.channel == channel), None
|
||||||
if session.ctx.channel == channel), None)
|
)
|
||||||
|
|
||||||
def _all_lists(self):
|
def _all_lists(self):
|
||||||
personal_lists = tuple(p.resolve()
|
personal_lists = tuple(p.resolve() for p in cog_data_path(self).glob("*.yaml"))
|
||||||
for p in cog_data_path(self).glob("*.yaml"))
|
|
||||||
|
|
||||||
return personal_lists + tuple(ext_trivia.lists())
|
return personal_lists + tuple(ext_trivia.lists())
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,9 @@ from redbot.core.i18n import Translator
|
|||||||
_ = Translator("Warnings", __file__)
|
_ = Translator("Warnings", __file__)
|
||||||
|
|
||||||
|
|
||||||
async def warning_points_add_check(config: Config, ctx: commands.Context, user: discord.Member, points: int):
|
async def warning_points_add_check(
|
||||||
|
config: Config, ctx: commands.Context, user: discord.Member, points: int
|
||||||
|
):
|
||||||
"""Handles any action that needs to be taken or not based on the points"""
|
"""Handles any action that needs to be taken or not based on the points"""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
guild_settings = config.guild(guild)
|
guild_settings = config.guild(guild)
|
||||||
@ -24,7 +26,9 @@ async def warning_points_add_check(config: Config, ctx: commands.Context, user:
|
|||||||
await create_and_invoke_context(ctx, act["exceed_command"], user)
|
await create_and_invoke_context(ctx, act["exceed_command"], user)
|
||||||
|
|
||||||
|
|
||||||
async def warning_points_remove_check(config: Config, ctx: commands.Context, user: discord.Member, points: int):
|
async def warning_points_remove_check(
|
||||||
|
config: Config, ctx: commands.Context, user: discord.Member, points: int
|
||||||
|
):
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
guild_settings = config.guild(guild)
|
guild_settings = config.guild(guild)
|
||||||
act = {}
|
act = {}
|
||||||
@ -38,7 +42,9 @@ async def warning_points_remove_check(config: Config, ctx: commands.Context, use
|
|||||||
await create_and_invoke_context(ctx, act["drop_command"], user)
|
await create_and_invoke_context(ctx, act["drop_command"], user)
|
||||||
|
|
||||||
|
|
||||||
async def create_and_invoke_context(realctx: commands.Context, command_str: str, user: discord.Member):
|
async def create_and_invoke_context(
|
||||||
|
realctx: commands.Context, command_str: str, user: discord.Member
|
||||||
|
):
|
||||||
m = copy(realctx.message)
|
m = copy(realctx.message)
|
||||||
m.content = command_str.format(user=user.mention, prefix=realctx.prefix)
|
m.content = command_str.format(user=user.mention, prefix=realctx.prefix)
|
||||||
fctx = await realctx.bot.get_context(m, cls=commands.Context)
|
fctx = await realctx.bot.get_context(m, cls=commands.Context)
|
||||||
@ -54,7 +60,7 @@ def get_command_from_input(bot, userinput: str):
|
|||||||
while com is None:
|
while com is None:
|
||||||
com = bot.get_command(userinput)
|
com = bot.get_command(userinput)
|
||||||
if com is None:
|
if com is None:
|
||||||
userinput = ' '.join(userinput.split(' ')[:-1])
|
userinput = " ".join(userinput.split(" ")[:-1])
|
||||||
if len(userinput) == 0:
|
if len(userinput) == 0:
|
||||||
break
|
break
|
||||||
if com is None:
|
if com is None:
|
||||||
@ -63,8 +69,9 @@ def get_command_from_input(bot, userinput: str):
|
|||||||
check_str = inspect.getsource(checks.is_owner)
|
check_str = inspect.getsource(checks.is_owner)
|
||||||
if any(inspect.getsource(x) in check_str for x in com.checks):
|
if any(inspect.getsource(x) in check_str for x in com.checks):
|
||||||
# command the user specified has the is_owner check
|
# command the user specified has the is_owner check
|
||||||
return None, _("That command requires bot owner. I can't "
|
return None, _(
|
||||||
"allow you to use that for an action")
|
"That command requires bot owner. I can't " "allow you to use that for an action"
|
||||||
|
)
|
||||||
return "{prefix}" + orig, None
|
return "{prefix}" + orig, None
|
||||||
|
|
||||||
|
|
||||||
@ -72,13 +79,15 @@ async def get_command_for_exceeded_points(ctx: commands.Context):
|
|||||||
"""Gets the command to be executed when the user is at or exceeding
|
"""Gets the command to be executed when the user is at or exceeding
|
||||||
the points threshold for the action"""
|
the points threshold for the action"""
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Enter the command to be run when the user exceeds the points for "
|
_(
|
||||||
|
"Enter the command to be run when the user exceeds the points for "
|
||||||
"this action to occur.\nEnter it exactly as you would if you were "
|
"this action to occur.\nEnter it exactly as you would if you were "
|
||||||
"actually trying to run the command, except don't put a prefix and "
|
"actually trying to run the command, except don't put a prefix and "
|
||||||
"use {user} in place of any user/member arguments\n\n"
|
"use {user} in place of any user/member arguments\n\n"
|
||||||
"WARNING: The command entered will be run without regard to checks or cooldowns. "
|
"WARNING: The command entered will be run without regard to checks or cooldowns. "
|
||||||
"Commands requiring bot owner are not allowed for security reasons.\n\n"
|
"Commands requiring bot owner are not allowed for security reasons.\n\n"
|
||||||
"Please wait 15 seconds before entering your response.")
|
"Please wait 15 seconds before entering your response."
|
||||||
|
)
|
||||||
)
|
)
|
||||||
await asyncio.sleep(15)
|
await asyncio.sleep(15)
|
||||||
|
|
||||||
@ -110,7 +119,8 @@ async def get_command_for_dropping_points(ctx: commands.Context):
|
|||||||
when the user exceeded the threshold
|
when the user exceeded the threshold
|
||||||
"""
|
"""
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Enter the command to be run when the user returns to a value below "
|
_(
|
||||||
|
"Enter the command to be run when the user returns to a value below "
|
||||||
"the points for this action to occur. Please note that this is "
|
"the points for this action to occur. Please note that this is "
|
||||||
"intended to be used for reversal of the action taken when the user "
|
"intended to be used for reversal of the action taken when the user "
|
||||||
"exceeded the action's point value\nEnter it exactly as you would "
|
"exceeded the action's point value\nEnter it exactly as you would "
|
||||||
@ -118,7 +128,8 @@ async def get_command_for_dropping_points(ctx: commands.Context):
|
|||||||
"and use {user} in place of any user/member arguments\n\n"
|
"and use {user} in place of any user/member arguments\n\n"
|
||||||
"WARNING: The command entered will be run without regard to checks or cooldowns. "
|
"WARNING: The command entered will be run without regard to checks or cooldowns. "
|
||||||
"Commands requiring bot owner are not allowed for security reasons.\n\n"
|
"Commands requiring bot owner are not allowed for security reasons.\n\n"
|
||||||
"Please wait 15 seconds before entering your response.")
|
"Please wait 15 seconds before entering your response."
|
||||||
|
)
|
||||||
)
|
)
|
||||||
await asyncio.sleep(15)
|
await asyncio.sleep(15)
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,10 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
TO_TRANSLATE = [
|
TO_TRANSLATE = ["../warnings.py", "../helpers.py"]
|
||||||
'../warnings.py',
|
|
||||||
'../helpers.py'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def regen_messages():
|
def regen_messages():
|
||||||
subprocess.run(
|
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||||
['pygettext', '-n'] + TO_TRANSLATE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -3,8 +3,12 @@ from collections import namedtuple
|
|||||||
import discord
|
import discord
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from redbot.cogs.warnings.helpers import warning_points_add_check, get_command_for_exceeded_points, \
|
from redbot.cogs.warnings.helpers import (
|
||||||
get_command_for_dropping_points, warning_points_remove_check
|
warning_points_add_check,
|
||||||
|
get_command_for_exceeded_points,
|
||||||
|
get_command_for_dropping_points,
|
||||||
|
warning_points_remove_check,
|
||||||
|
)
|
||||||
from redbot.core import Config, modlog, checks, commands
|
from redbot.core import Config, modlog, checks, commands
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
from redbot.core.i18n import Translator, cog_i18n
|
from redbot.core.i18n import Translator, cog_i18n
|
||||||
@ -18,17 +22,9 @@ _ = Translator("Warnings", __file__)
|
|||||||
class Warnings:
|
class Warnings:
|
||||||
"""A warning system for Red"""
|
"""A warning system for Red"""
|
||||||
|
|
||||||
default_guild = {
|
default_guild = {"actions": [], "reasons": {}, "allow_custom_reasons": False}
|
||||||
"actions": [],
|
|
||||||
"reasons": {},
|
|
||||||
"allow_custom_reasons": False
|
|
||||||
}
|
|
||||||
|
|
||||||
default_member = {
|
default_member = {"total_points": 0, "status": "", "warnings": {}}
|
||||||
"total_points": 0,
|
|
||||||
"status": "",
|
|
||||||
"warnings": {}
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, bot: Red):
|
def __init__(self, bot: Red):
|
||||||
self.config = Config.get_conf(self, identifier=5757575755)
|
self.config = Config.get_conf(self, identifier=5757575755)
|
||||||
@ -41,9 +37,7 @@ class Warnings:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
async def register_warningtype():
|
async def register_warningtype():
|
||||||
try:
|
try:
|
||||||
await modlog.register_casetype(
|
await modlog.register_casetype("warning", True, "\N{WARNING SIGN}", "Warning", None)
|
||||||
"warning", True, "\N{WARNING SIGN}", "Warning", None
|
|
||||||
)
|
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -105,7 +99,7 @@ class Warnings:
|
|||||||
"action_name": name,
|
"action_name": name,
|
||||||
"points": points,
|
"points": points,
|
||||||
"exceed_command": exceed_command,
|
"exceed_command": exceed_command,
|
||||||
"drop_command": drop_command
|
"drop_command": drop_command,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Have all details for the action, now save the action
|
# Have all details for the action, now save the action
|
||||||
@ -138,9 +132,7 @@ class Warnings:
|
|||||||
registered_actions.remove(to_remove)
|
registered_actions.remove(to_remove)
|
||||||
await ctx.tick()
|
await ctx.tick()
|
||||||
else:
|
else:
|
||||||
await ctx.send(
|
await ctx.send(_("No action named {} exists!").format(action_name))
|
||||||
_("No action named {} exists!").format(action_name)
|
|
||||||
)
|
|
||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@ -159,13 +151,8 @@ class Warnings:
|
|||||||
if name.lower() == "custom":
|
if name.lower() == "custom":
|
||||||
await ctx.send("That cannot be used as a reason name!")
|
await ctx.send("That cannot be used as a reason name!")
|
||||||
return
|
return
|
||||||
to_add = {
|
to_add = {"points": points, "description": description}
|
||||||
"points": points,
|
completed = {name.lower(): to_add}
|
||||||
"description": description
|
|
||||||
}
|
|
||||||
completed = {
|
|
||||||
name.lower(): to_add
|
|
||||||
}
|
|
||||||
|
|
||||||
guild_settings = self.config.guild(guild)
|
guild_settings = self.config.guild(guild)
|
||||||
|
|
||||||
@ -219,8 +206,7 @@ class Warnings:
|
|||||||
msg_list.append(
|
msg_list.append(
|
||||||
"Name: {}\nPoints: {}\nExceed command: {}\n"
|
"Name: {}\nPoints: {}\nExceed command: {}\n"
|
||||||
"Drop command: {}".format(
|
"Drop command: {}".format(
|
||||||
r["action_name"], r["points"], r["exceed_command"],
|
r["action_name"], r["points"], r["exceed_command"], r["drop_command"]
|
||||||
r["drop_command"]
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if msg_list:
|
if msg_list:
|
||||||
@ -262,7 +248,7 @@ class Warnings:
|
|||||||
str(ctx.message.id): {
|
str(ctx.message.id): {
|
||||||
"points": reason_type["points"],
|
"points": reason_type["points"],
|
||||||
"description": reason_type["description"],
|
"description": reason_type["description"],
|
||||||
"mod": ctx.author.id
|
"mod": ctx.author.id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async with member_settings.warnings() as user_warnings:
|
async with member_settings.warnings() as user_warnings:
|
||||||
@ -275,7 +261,7 @@ class Warnings:
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def warnings(self, ctx: commands.Context, userid: int=None):
|
async def warnings(self, ctx: commands.Context, userid: int = None):
|
||||||
"""Show warnings for the specified user.
|
"""Show warnings for the specified user.
|
||||||
If userid is None, show warnings for the person running the command
|
If userid is None, show warnings for the person running the command
|
||||||
Note that showing warnings for users other than yourself requires
|
Note that showing warnings for users other than yourself requires
|
||||||
@ -285,10 +271,7 @@ class Warnings:
|
|||||||
else:
|
else:
|
||||||
if not await is_admin_or_superior(self.bot, ctx.author):
|
if not await is_admin_or_superior(self.bot, ctx.author):
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
warning(
|
warning(_("You are not allowed to check " "warnings for other users!"))
|
||||||
_("You are not allowed to check "
|
|
||||||
"warnings for other users!")
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
@ -305,22 +288,14 @@ class Warnings:
|
|||||||
mod = ctx.guild.get_member(user_warnings[key]["mod"])
|
mod = ctx.guild.get_member(user_warnings[key]["mod"])
|
||||||
if mod is None:
|
if mod is None:
|
||||||
mod = discord.utils.get(
|
mod = discord.utils.get(
|
||||||
self.bot.get_all_members(),
|
self.bot.get_all_members(), id=user_warnings[key]["mod"]
|
||||||
id=user_warnings[key]["mod"]
|
|
||||||
)
|
)
|
||||||
if mod is None:
|
if mod is None:
|
||||||
mod = await self.bot.get_user_info(
|
mod = await self.bot.get_user_info(user_warnings[key]["mod"])
|
||||||
user_warnings[key]["mod"]
|
|
||||||
)
|
|
||||||
msg += "{} point warning {} issued by {} for {}\n".format(
|
msg += "{} point warning {} issued by {} for {}\n".format(
|
||||||
user_warnings[key]["points"],
|
user_warnings[key]["points"], key, mod, user_warnings[key]["description"]
|
||||||
key,
|
|
||||||
mod,
|
|
||||||
user_warnings[key]["description"]
|
|
||||||
)
|
|
||||||
await ctx.send_interactive(
|
|
||||||
pagify(msg), box_lang="Warnings for {}".format(user)
|
|
||||||
)
|
)
|
||||||
|
await ctx.send_interactive(pagify(msg), box_lang="Warnings for {}".format(user))
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@ -348,10 +323,7 @@ class Warnings:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
async def custom_warning_reason(ctx: commands.Context):
|
async def custom_warning_reason(ctx: commands.Context):
|
||||||
"""Handles getting description and points for custom reasons"""
|
"""Handles getting description and points for custom reasons"""
|
||||||
to_add = {
|
to_add = {"points": 0, "description": ""}
|
||||||
"points": 0,
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
|
|
||||||
def same_author_check(m):
|
def same_author_check(m):
|
||||||
return m.author == ctx.author
|
return m.author == ctx.author
|
||||||
|
|||||||
@ -4,16 +4,15 @@ __all__ = ["Config", "__version__"]
|
|||||||
|
|
||||||
|
|
||||||
class VersionInfo:
|
class VersionInfo:
|
||||||
|
|
||||||
def __init__(self, major, minor, micro, releaselevel, serial):
|
def __init__(self, major, minor, micro, releaselevel, serial):
|
||||||
self._levels = ['alpha', 'beta', 'final']
|
self._levels = ["alpha", "beta", "final"]
|
||||||
self.major = major
|
self.major = major
|
||||||
self.minor = minor
|
self.minor = minor
|
||||||
self.micro = micro
|
self.micro = micro
|
||||||
|
|
||||||
if releaselevel not in self._levels:
|
if releaselevel not in self._levels:
|
||||||
raise TypeError("'releaselevel' must be one of: {}".format(
|
raise TypeError("'releaselevel' must be one of: {}".format(", ".join(self._levels)))
|
||||||
', '.join(self._levels)
|
|
||||||
))
|
|
||||||
|
|
||||||
self.releaselevel = releaselevel
|
self.releaselevel = releaselevel
|
||||||
self.serial = serial
|
self.serial = serial
|
||||||
@ -21,8 +20,9 @@ class VersionInfo:
|
|||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
my_index = self._levels.index(self.releaselevel)
|
my_index = self._levels.index(self.releaselevel)
|
||||||
other_index = self._levels.index(other.releaselevel)
|
other_index = self._levels.index(other.releaselevel)
|
||||||
return (self.major, self.minor, self.micro, my_index, self.serial) < \
|
return (self.major, self.minor, self.micro, my_index, self.serial) < (
|
||||||
(other.major, other.minor, other.micro, other_index, other.serial)
|
other.major, other.minor, other.micro, other_index, other.serial
|
||||||
|
)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "VersionInfo(major={}, minor={}, micro={}, releaselevel={}, serial={})".format(
|
return "VersionInfo(major={}, minor={}, micro={}, releaselevel={}, serial={})".format(
|
||||||
@ -32,5 +32,6 @@ class VersionInfo:
|
|||||||
def to_json(self):
|
def to_json(self):
|
||||||
return [self.major, self.minor, self.micro, self.releaselevel, self.serial]
|
return [self.major, self.minor, self.micro, self.releaselevel, self.serial]
|
||||||
|
|
||||||
|
|
||||||
__version__ = "3.0.0b14"
|
__version__ = "3.0.0b14"
|
||||||
version_info = VersionInfo(3, 0, 0, 'beta', 14)
|
version_info = VersionInfo(3, 0, 0, "beta", 14)
|
||||||
|
|||||||
@ -6,29 +6,36 @@ import discord
|
|||||||
|
|
||||||
from redbot.core import Config
|
from redbot.core import Config
|
||||||
|
|
||||||
__all__ = ["Account", "get_balance", "set_balance", "withdraw_credits", "deposit_credits",
|
__all__ = [
|
||||||
"can_spend", "transfer_credits", "wipe_bank", "get_account", "is_global",
|
"Account",
|
||||||
"set_global", "get_bank_name", "set_bank_name", "get_currency_name",
|
"get_balance",
|
||||||
"set_currency_name", "get_default_balance", "set_default_balance"]
|
"set_balance",
|
||||||
|
"withdraw_credits",
|
||||||
|
"deposit_credits",
|
||||||
|
"can_spend",
|
||||||
|
"transfer_credits",
|
||||||
|
"wipe_bank",
|
||||||
|
"get_account",
|
||||||
|
"is_global",
|
||||||
|
"set_global",
|
||||||
|
"get_bank_name",
|
||||||
|
"set_bank_name",
|
||||||
|
"get_currency_name",
|
||||||
|
"set_currency_name",
|
||||||
|
"get_default_balance",
|
||||||
|
"set_default_balance",
|
||||||
|
]
|
||||||
|
|
||||||
_DEFAULT_GLOBAL = {
|
_DEFAULT_GLOBAL = {
|
||||||
"is_global": False,
|
"is_global": False,
|
||||||
"bank_name": "Twentysix bank",
|
"bank_name": "Twentysix bank",
|
||||||
"currency": "credits",
|
"currency": "credits",
|
||||||
"default_balance": 100
|
"default_balance": 100,
|
||||||
}
|
}
|
||||||
|
|
||||||
_DEFAULT_GUILD = {
|
_DEFAULT_GUILD = {"bank_name": "Twentysix bank", "currency": "credits", "default_balance": 100}
|
||||||
"bank_name": "Twentysix bank",
|
|
||||||
"currency": "credits",
|
|
||||||
"default_balance": 100
|
|
||||||
}
|
|
||||||
|
|
||||||
_DEFAULT_MEMBER = {
|
_DEFAULT_MEMBER = {"name": "", "balance": 0, "created_at": 0}
|
||||||
"name": "",
|
|
||||||
"balance": 0,
|
|
||||||
"created_at": 0
|
|
||||||
}
|
|
||||||
|
|
||||||
_DEFAULT_USER = _DEFAULT_MEMBER
|
_DEFAULT_USER = _DEFAULT_MEMBER
|
||||||
|
|
||||||
@ -50,9 +57,9 @@ def _register_defaults():
|
|||||||
_conf.register_member(**_DEFAULT_MEMBER)
|
_conf.register_member(**_DEFAULT_MEMBER)
|
||||||
_conf.register_user(**_DEFAULT_USER)
|
_conf.register_user(**_DEFAULT_USER)
|
||||||
|
|
||||||
if not os.environ.get('BUILDING_DOCS'):
|
|
||||||
_conf = Config.get_conf(
|
if not os.environ.get("BUILDING_DOCS"):
|
||||||
None, 384734293238749, cog_name="Bank", force_registration=True)
|
_conf = Config.get_conf(None, 384734293238749, cog_name="Bank", force_registration=True)
|
||||||
_register_defaults()
|
_register_defaults()
|
||||||
|
|
||||||
|
|
||||||
@ -285,7 +292,7 @@ async def wipe_bank():
|
|||||||
await _conf.clear_all_members()
|
await _conf.clear_all_members()
|
||||||
|
|
||||||
|
|
||||||
async def get_leaderboard(positions: int=None, guild: discord.Guild=None) -> List[tuple]:
|
async def get_leaderboard(positions: int = None, guild: discord.Guild = None) -> List[tuple]:
|
||||||
"""
|
"""
|
||||||
Gets the bank's leaderboard
|
Gets the bank's leaderboard
|
||||||
|
|
||||||
@ -319,14 +326,16 @@ async def get_leaderboard(positions: int=None, guild: discord.Guild=None) -> Lis
|
|||||||
if guild is None:
|
if guild is None:
|
||||||
raise TypeError("Expected a guild, got NoneType object instead!")
|
raise TypeError("Expected a guild, got NoneType object instead!")
|
||||||
raw_accounts = await _conf.all_members(guild)
|
raw_accounts = await _conf.all_members(guild)
|
||||||
sorted_acc = sorted(raw_accounts.items(), key=lambda x: x[1]['balance'], reverse=True)
|
sorted_acc = sorted(raw_accounts.items(), key=lambda x: x[1]["balance"], reverse=True)
|
||||||
if positions is None:
|
if positions is None:
|
||||||
return sorted_acc
|
return sorted_acc
|
||||||
else:
|
else:
|
||||||
return sorted_acc[:positions]
|
return sorted_acc[:positions]
|
||||||
|
|
||||||
|
|
||||||
async def get_leaderboard_position(member: Union[discord.User, discord.Member]) -> Union[int, None]:
|
async def get_leaderboard_position(
|
||||||
|
member: Union[discord.User, discord.Member]
|
||||||
|
) -> Union[int, None]:
|
||||||
"""
|
"""
|
||||||
Get the leaderboard position for the specified user
|
Get the leaderboard position for the specified user
|
||||||
|
|
||||||
@ -387,13 +396,13 @@ async def get_account(member: Union[discord.Member, discord.User]) -> Account:
|
|||||||
|
|
||||||
if acc_data == {}:
|
if acc_data == {}:
|
||||||
acc_data = default
|
acc_data = default
|
||||||
acc_data['name'] = member.display_name
|
acc_data["name"] = member.display_name
|
||||||
try:
|
try:
|
||||||
acc_data['balance'] = await get_default_balance(member.guild)
|
acc_data["balance"] = await get_default_balance(member.guild)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
acc_data['balance'] = await get_default_balance()
|
acc_data["balance"] = await get_default_balance()
|
||||||
|
|
||||||
acc_data['created_at'] = _decode_time(acc_data['created_at'])
|
acc_data["created_at"] = _decode_time(acc_data["created_at"])
|
||||||
return Account(**acc_data)
|
return Account(**acc_data)
|
||||||
|
|
||||||
|
|
||||||
@ -444,7 +453,7 @@ async def set_global(global_: bool) -> bool:
|
|||||||
return global_
|
return global_
|
||||||
|
|
||||||
|
|
||||||
async def get_bank_name(guild: discord.Guild=None) -> str:
|
async def get_bank_name(guild: discord.Guild = None) -> str:
|
||||||
"""Get the current bank name.
|
"""Get the current bank name.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -472,7 +481,7 @@ async def get_bank_name(guild: discord.Guild=None) -> str:
|
|||||||
raise RuntimeError("Guild parameter is required and missing.")
|
raise RuntimeError("Guild parameter is required and missing.")
|
||||||
|
|
||||||
|
|
||||||
async def set_bank_name(name: str, guild: discord.Guild=None) -> str:
|
async def set_bank_name(name: str, guild: discord.Guild = None) -> str:
|
||||||
"""Set the bank name.
|
"""Set the bank name.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -499,12 +508,13 @@ async def set_bank_name(name: str, guild: discord.Guild=None) -> str:
|
|||||||
elif guild is not None:
|
elif guild is not None:
|
||||||
await _conf.guild(guild).bank_name.set(name)
|
await _conf.guild(guild).bank_name.set(name)
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("Guild must be provided if setting the name of a guild"
|
raise RuntimeError(
|
||||||
"-specific bank.")
|
"Guild must be provided if setting the name of a guild" "-specific bank."
|
||||||
|
)
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
||||||
async def get_currency_name(guild: discord.Guild=None) -> str:
|
async def get_currency_name(guild: discord.Guild = None) -> str:
|
||||||
"""Get the currency name of the bank.
|
"""Get the currency name of the bank.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -532,7 +542,7 @@ async def get_currency_name(guild: discord.Guild=None) -> str:
|
|||||||
raise RuntimeError("Guild must be provided.")
|
raise RuntimeError("Guild must be provided.")
|
||||||
|
|
||||||
|
|
||||||
async def set_currency_name(name: str, guild: discord.Guild=None) -> str:
|
async def set_currency_name(name: str, guild: discord.Guild = None) -> str:
|
||||||
"""Set the currency name for the bank.
|
"""Set the currency name for the bank.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -559,12 +569,13 @@ async def set_currency_name(name: str, guild: discord.Guild=None) -> str:
|
|||||||
elif guild is not None:
|
elif guild is not None:
|
||||||
await _conf.guild(guild).currency.set(name)
|
await _conf.guild(guild).currency.set(name)
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("Guild must be provided if setting the currency"
|
raise RuntimeError(
|
||||||
" name of a guild-specific bank.")
|
"Guild must be provided if setting the currency" " name of a guild-specific bank."
|
||||||
|
)
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
||||||
async def get_default_balance(guild: discord.Guild=None) -> int:
|
async def get_default_balance(guild: discord.Guild = None) -> int:
|
||||||
"""Get the current default balance amount.
|
"""Get the current default balance amount.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -592,7 +603,7 @@ async def get_default_balance(guild: discord.Guild=None) -> int:
|
|||||||
raise RuntimeError("Guild is missing and required!")
|
raise RuntimeError("Guild is missing and required!")
|
||||||
|
|
||||||
|
|
||||||
async def set_default_balance(amount: int, guild: discord.Guild=None) -> int:
|
async def set_default_balance(amount: int, guild: discord.Guild = None) -> int:
|
||||||
"""Set the default balance amount.
|
"""Set the default balance amount.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|||||||
@ -14,15 +14,11 @@ from discord.ext.commands import when_mentioned_or
|
|||||||
|
|
||||||
# This supresses the PyNaCl warning that isn't relevant here
|
# This supresses the PyNaCl warning that isn't relevant here
|
||||||
from discord.voice_client import VoiceClient
|
from discord.voice_client import VoiceClient
|
||||||
|
|
||||||
VoiceClient.warn_nacl = False
|
VoiceClient.warn_nacl = False
|
||||||
|
|
||||||
from .cog_manager import CogManager
|
from .cog_manager import CogManager
|
||||||
from . import (
|
from . import Config, i18n, commands, rpc
|
||||||
Config,
|
|
||||||
i18n,
|
|
||||||
commands,
|
|
||||||
rpc
|
|
||||||
)
|
|
||||||
from .help_formatter import Help, help as help_
|
from .help_formatter import Help, help as help_
|
||||||
from .sentry import SentryManager
|
from .sentry import SentryManager
|
||||||
from .utils import TYPE_CHECKING
|
from .utils import TYPE_CHECKING
|
||||||
@ -32,6 +28,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
class RpcMethodMixin:
|
class RpcMethodMixin:
|
||||||
|
|
||||||
async def rpc__cogs(self, request):
|
async def rpc__cogs(self, request):
|
||||||
return list(self.cogs.keys())
|
return list(self.cogs.keys())
|
||||||
|
|
||||||
@ -48,7 +45,8 @@ class RedBase(BotBase, RpcMethodMixin):
|
|||||||
|
|
||||||
Selfbots should inherit from this mixin along with `discord.Client`.
|
Selfbots should inherit from this mixin along with `discord.Client`.
|
||||||
"""
|
"""
|
||||||
def __init__(self, cli_flags, bot_dir: Path=Path.cwd(), **kwargs):
|
|
||||||
|
def __init__(self, cli_flags, bot_dir: Path = Path.cwd(), **kwargs):
|
||||||
self._shutdown_mode = ExitCodes.CRITICAL
|
self._shutdown_mode = ExitCodes.CRITICAL
|
||||||
self.db = Config.get_core_conf(force_registration=True)
|
self.db = Config.get_core_conf(force_registration=True)
|
||||||
self._co_owners = cli_flags.co_owner
|
self._co_owners = cli_flags.co_owner
|
||||||
@ -62,22 +60,15 @@ class RedBase(BotBase, RpcMethodMixin):
|
|||||||
whitelist=[],
|
whitelist=[],
|
||||||
blacklist=[],
|
blacklist=[],
|
||||||
enable_sentry=None,
|
enable_sentry=None,
|
||||||
locale='en',
|
locale="en",
|
||||||
embeds=True
|
embeds=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.db.register_guild(
|
self.db.register_guild(
|
||||||
prefix=[],
|
prefix=[], whitelist=[], blacklist=[], admin_role=None, mod_role=None, embeds=None
|
||||||
whitelist=[],
|
|
||||||
blacklist=[],
|
|
||||||
admin_role=None,
|
|
||||||
mod_role=None,
|
|
||||||
embeds=None
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.db.register_user(
|
self.db.register_user(embeds=None)
|
||||||
embeds=None
|
|
||||||
)
|
|
||||||
|
|
||||||
async def prefix_manager(bot, message):
|
async def prefix_manager(bot, message):
|
||||||
if not cli_flags.prefix:
|
if not cli_flags.prefix:
|
||||||
@ -88,9 +79,13 @@ class RedBase(BotBase, RpcMethodMixin):
|
|||||||
return global_prefix
|
return global_prefix
|
||||||
server_prefix = await bot.db.guild(message.guild).prefix()
|
server_prefix = await bot.db.guild(message.guild).prefix()
|
||||||
if cli_flags.mentionable:
|
if cli_flags.mentionable:
|
||||||
return when_mentioned_or(*server_prefix)(bot, message) \
|
return when_mentioned_or(*server_prefix)(
|
||||||
if server_prefix else \
|
bot, message
|
||||||
when_mentioned_or(*global_prefix)(bot, message)
|
) if server_prefix else when_mentioned_or(
|
||||||
|
*global_prefix
|
||||||
|
)(
|
||||||
|
bot, message
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return server_prefix if server_prefix else global_prefix
|
return server_prefix if server_prefix else global_prefix
|
||||||
|
|
||||||
@ -109,13 +104,13 @@ class RedBase(BotBase, RpcMethodMixin):
|
|||||||
|
|
||||||
self.main_dir = bot_dir
|
self.main_dir = bot_dir
|
||||||
|
|
||||||
self.cog_mgr = CogManager(paths=(str(self.main_dir / 'cogs'),))
|
self.cog_mgr = CogManager(paths=(str(self.main_dir / "cogs"),))
|
||||||
|
|
||||||
self.register_rpc_methods()
|
self.register_rpc_methods()
|
||||||
|
|
||||||
super().__init__(formatter=Help(), **kwargs)
|
super().__init__(formatter=Help(), **kwargs)
|
||||||
|
|
||||||
self.remove_command('help')
|
self.remove_command("help")
|
||||||
|
|
||||||
self.add_command(help_)
|
self.add_command(help_)
|
||||||
|
|
||||||
@ -124,7 +119,7 @@ class RedBase(BotBase, RpcMethodMixin):
|
|||||||
def enable_sentry(self):
|
def enable_sentry(self):
|
||||||
"""Enable Sentry logging for Red."""
|
"""Enable Sentry logging for Red."""
|
||||||
if self._sentry_mgr is None:
|
if self._sentry_mgr is None:
|
||||||
sentry_log = logging.getLogger('red.sentry')
|
sentry_log = logging.getLogger("red.sentry")
|
||||||
sentry_log.setLevel(logging.WARNING)
|
sentry_log.setLevel(logging.WARNING)
|
||||||
self._sentry_mgr = SentryManager(sentry_log)
|
self._sentry_mgr = SentryManager(sentry_log)
|
||||||
self._sentry_mgr.enable()
|
self._sentry_mgr.enable()
|
||||||
@ -143,7 +138,7 @@ class RedBase(BotBase, RpcMethodMixin):
|
|||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
indict['owner_id'] = await self.db.owner()
|
indict["owner_id"] = await self.db.owner()
|
||||||
i18n.set_locale(await self.db.locale())
|
i18n.set_locale(await self.db.locale())
|
||||||
|
|
||||||
async def embed_requested(self, channel, user, command=None) -> bool:
|
async def embed_requested(self, channel, user, command=None) -> bool:
|
||||||
@ -164,8 +159,9 @@ class RedBase(BotBase, RpcMethodMixin):
|
|||||||
bool
|
bool
|
||||||
:code:`True` if an embed is requested
|
:code:`True` if an embed is requested
|
||||||
"""
|
"""
|
||||||
if isinstance(channel, discord.abc.PrivateChannel) or (
|
if (
|
||||||
command and command == self.get_command("help")
|
isinstance(channel, discord.abc.PrivateChannel)
|
||||||
|
or (command and command == self.get_command("help"))
|
||||||
):
|
):
|
||||||
user_setting = await self.db.user(user).embeds()
|
user_setting = await self.db.user(user).embeds()
|
||||||
if user_setting is not None:
|
if user_setting is not None:
|
||||||
@ -214,14 +210,14 @@ class RedBase(BotBase, RpcMethodMixin):
|
|||||||
curr_pkgs.remove(pkg_name)
|
curr_pkgs.remove(pkg_name)
|
||||||
|
|
||||||
async def load_extension(self, spec: ModuleSpec):
|
async def load_extension(self, spec: ModuleSpec):
|
||||||
name = spec.name.split('.')[-1]
|
name = spec.name.split(".")[-1]
|
||||||
if name in self.extensions:
|
if name in self.extensions:
|
||||||
return
|
return
|
||||||
|
|
||||||
lib = spec.loader.load_module()
|
lib = spec.loader.load_module()
|
||||||
if not hasattr(lib, 'setup'):
|
if not hasattr(lib, "setup"):
|
||||||
del lib
|
del lib
|
||||||
raise discord.ClientException('extension does not have a setup function')
|
raise discord.ClientException("extension does not have a setup function")
|
||||||
|
|
||||||
if asyncio.iscoroutinefunction(lib.setup):
|
if asyncio.iscoroutinefunction(lib.setup):
|
||||||
await lib.setup(self)
|
await lib.setup(self)
|
||||||
@ -262,7 +258,7 @@ class RedBase(BotBase, RpcMethodMixin):
|
|||||||
del event_list[index]
|
del event_list[index]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
func = getattr(lib, 'teardown')
|
func = getattr(lib, "teardown")
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@ -279,19 +275,20 @@ class RedBase(BotBase, RpcMethodMixin):
|
|||||||
if m.startswith(pkg_name):
|
if m.startswith(pkg_name):
|
||||||
del sys.modules[m]
|
del sys.modules[m]
|
||||||
|
|
||||||
if pkg_name.startswith('redbot.cogs'):
|
if pkg_name.startswith("redbot.cogs"):
|
||||||
del sys.modules['redbot.cogs'].__dict__[name]
|
del sys.modules["redbot.cogs"].__dict__[name]
|
||||||
|
|
||||||
def register_rpc_methods(self):
|
def register_rpc_methods(self):
|
||||||
rpc.add_method('bot', self.rpc__cogs)
|
rpc.add_method("bot", self.rpc__cogs)
|
||||||
rpc.add_method('bot', self.rpc__extensions)
|
rpc.add_method("bot", self.rpc__extensions)
|
||||||
|
|
||||||
|
|
||||||
class Red(RedBase, discord.AutoShardedClient):
|
class Red(RedBase, discord.AutoShardedClient):
|
||||||
"""
|
"""
|
||||||
You're welcome Caleb.
|
You're welcome Caleb.
|
||||||
"""
|
"""
|
||||||
async def shutdown(self, *, restart: bool=False):
|
|
||||||
|
async def shutdown(self, *, restart: bool = False):
|
||||||
"""Gracefully quit Red.
|
"""Gracefully quit Red.
|
||||||
|
|
||||||
The program will exit with code :code:`0` by default.
|
The program will exit with code :code:`0` by default.
|
||||||
|
|||||||
@ -5,23 +5,22 @@ from discord.ext import commands
|
|||||||
async def check_overrides(ctx, *, level):
|
async def check_overrides(ctx, *, level):
|
||||||
if await ctx.bot.is_owner(ctx.author):
|
if await ctx.bot.is_owner(ctx.author):
|
||||||
return True
|
return True
|
||||||
perm_cog = ctx.bot.get_cog('Permissions')
|
perm_cog = ctx.bot.get_cog("Permissions")
|
||||||
if not perm_cog or ctx.cog == perm_cog:
|
if not perm_cog or ctx.cog == perm_cog:
|
||||||
return None
|
return None
|
||||||
# don't break if someone loaded a cog named
|
# don't break if someone loaded a cog named
|
||||||
# permissions that doesn't implement this
|
# permissions that doesn't implement this
|
||||||
func = getattr(perm_cog, 'check_overrides', None)
|
func = getattr(perm_cog, "check_overrides", None)
|
||||||
val = None if func is None else await func(ctx, level)
|
val = None if func is None else await func(ctx, level)
|
||||||
return val
|
return val
|
||||||
|
|
||||||
|
|
||||||
def is_owner(**kwargs):
|
def is_owner(**kwargs):
|
||||||
|
|
||||||
async def check(ctx):
|
async def check(ctx):
|
||||||
override = await check_overrides(ctx, level='owner')
|
override = await check_overrides(ctx, level="owner")
|
||||||
return (
|
return (override if override is not None else await ctx.bot.is_owner(ctx.author, **kwargs))
|
||||||
override if override is not None
|
|
||||||
else await ctx.bot.is_owner(ctx.author, **kwargs)
|
|
||||||
)
|
|
||||||
return commands.check(check)
|
return commands.check(check)
|
||||||
|
|
||||||
|
|
||||||
@ -32,10 +31,7 @@ async def check_permissions(ctx, perms):
|
|||||||
return False
|
return False
|
||||||
resolved = ctx.channel.permissions_for(ctx.author)
|
resolved = ctx.channel.permissions_for(ctx.author)
|
||||||
|
|
||||||
return all(
|
return all(getattr(resolved, name, None) == value for name, value in perms.items())
|
||||||
getattr(resolved, name, None) == value
|
|
||||||
for name, value in perms.items()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def is_mod_or_superior(ctx):
|
async def is_mod_or_superior(ctx):
|
||||||
@ -75,47 +71,49 @@ async def is_admin_or_superior(ctx):
|
|||||||
|
|
||||||
|
|
||||||
def mod_or_permissions(**perms):
|
def mod_or_permissions(**perms):
|
||||||
|
|
||||||
async def predicate(ctx):
|
async def predicate(ctx):
|
||||||
override = await check_overrides(ctx, level='mod')
|
override = await check_overrides(ctx, level="mod")
|
||||||
return (
|
return (
|
||||||
override if override is not None
|
override
|
||||||
else await check_permissions(ctx, perms)
|
if override is not None
|
||||||
or await is_mod_or_superior(ctx)
|
else await check_permissions(ctx, perms) or await is_mod_or_superior(ctx)
|
||||||
)
|
)
|
||||||
|
|
||||||
return commands.check(predicate)
|
return commands.check(predicate)
|
||||||
|
|
||||||
|
|
||||||
def admin_or_permissions(**perms):
|
def admin_or_permissions(**perms):
|
||||||
|
|
||||||
async def predicate(ctx):
|
async def predicate(ctx):
|
||||||
override = await check_overrides(ctx, level='admin')
|
override = await check_overrides(ctx, level="admin")
|
||||||
return (
|
return (
|
||||||
override if override is not None
|
override
|
||||||
else await check_permissions(ctx, perms)
|
if override is not None
|
||||||
or await is_admin_or_superior(ctx)
|
else await check_permissions(ctx, perms) or await is_admin_or_superior(ctx)
|
||||||
)
|
)
|
||||||
|
|
||||||
return commands.check(predicate)
|
return commands.check(predicate)
|
||||||
|
|
||||||
|
|
||||||
def bot_in_a_guild(**kwargs):
|
def bot_in_a_guild(**kwargs):
|
||||||
|
|
||||||
async def predicate(ctx):
|
async def predicate(ctx):
|
||||||
return len(ctx.bot.guilds) > 0
|
return len(ctx.bot.guilds) > 0
|
||||||
|
|
||||||
return commands.check(predicate)
|
return commands.check(predicate)
|
||||||
|
|
||||||
|
|
||||||
def guildowner_or_permissions(**perms):
|
def guildowner_or_permissions(**perms):
|
||||||
|
|
||||||
async def predicate(ctx):
|
async def predicate(ctx):
|
||||||
has_perms_or_is_owner = await check_permissions(ctx, perms)
|
has_perms_or_is_owner = await check_permissions(ctx, perms)
|
||||||
if ctx.guild is None:
|
if ctx.guild is None:
|
||||||
return has_perms_or_is_owner
|
return has_perms_or_is_owner
|
||||||
is_guild_owner = ctx.author == ctx.guild.owner
|
is_guild_owner = ctx.author == ctx.guild.owner
|
||||||
|
|
||||||
override = await check_overrides(ctx, level='guildowner')
|
override = await check_overrides(ctx, level="guildowner")
|
||||||
return (
|
return (override if override is not None else is_guild_owner or has_perms_or_is_owner)
|
||||||
override if override is not None
|
|
||||||
else is_guild_owner or has_perms_or_is_owner
|
|
||||||
)
|
|
||||||
|
|
||||||
return commands.check(predicate)
|
return commands.check(predicate)
|
||||||
|
|
||||||
|
|||||||
@ -26,16 +26,17 @@ def interactive_config(red, token_set, prefix_set):
|
|||||||
|
|
||||||
if not prefix_set:
|
if not prefix_set:
|
||||||
prefix = ""
|
prefix = ""
|
||||||
print("\nPick a prefix. A prefix is what you type before a "
|
print(
|
||||||
|
"\nPick a prefix. A prefix is what you type before a "
|
||||||
"command. Example:\n"
|
"command. Example:\n"
|
||||||
"!help\n^ The exclamation mark is the prefix in this case.\n"
|
"!help\n^ The exclamation mark is the prefix in this case.\n"
|
||||||
"Can be multiple characters. You will be able to change it "
|
"Can be multiple characters. You will be able to change it "
|
||||||
"later and add more of them.\nChoose your prefix:\n")
|
"later and add more of them.\nChoose your prefix:\n"
|
||||||
|
)
|
||||||
while not prefix:
|
while not prefix:
|
||||||
prefix = input("Prefix> ")
|
prefix = input("Prefix> ")
|
||||||
if len(prefix) > 10:
|
if len(prefix) > 10:
|
||||||
print("Your prefix seems overly long. Are you sure it "
|
print("Your prefix seems overly long. Are you sure it " "is correct? (y/n)")
|
||||||
"is correct? (y/n)")
|
|
||||||
if not confirm("> "):
|
if not confirm("> "):
|
||||||
prefix = ""
|
prefix = ""
|
||||||
if prefix:
|
if prefix:
|
||||||
@ -48,12 +49,14 @@ def interactive_config(red, token_set, prefix_set):
|
|||||||
|
|
||||||
def ask_sentry(red: Red):
|
def ask_sentry(red: Red):
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
print("\nThank you for installing Red V3 beta! The current version\n"
|
print(
|
||||||
|
"\nThank you for installing Red V3 beta! The current version\n"
|
||||||
" is not suited for production use and is aimed at testing\n"
|
" is not suited for production use and is aimed at testing\n"
|
||||||
" the current and upcoming featureset, that's why we will\n"
|
" the current and upcoming featureset, that's why we will\n"
|
||||||
" also collect the fatal error logs to help us fix any new\n"
|
" also collect the fatal error logs to help us fix any new\n"
|
||||||
" found issues in a timely manner. If you wish to opt in\n"
|
" found issues in a timely manner. If you wish to opt in\n"
|
||||||
" the process please type \"yes\":\n")
|
' the process please type "yes":\n'
|
||||||
|
)
|
||||||
if not confirm("> "):
|
if not confirm("> "):
|
||||||
loop.run_until_complete(red.db.enable_sentry.set(False))
|
loop.run_until_complete(red.db.enable_sentry.set(False))
|
||||||
else:
|
else:
|
||||||
@ -62,64 +65,82 @@ def ask_sentry(red: Red):
|
|||||||
|
|
||||||
|
|
||||||
def parse_cli_flags(args):
|
def parse_cli_flags(args):
|
||||||
parser = argparse.ArgumentParser(description="Red - Discord Bot",
|
parser = argparse.ArgumentParser(
|
||||||
usage="redbot <instance_name> [arguments]")
|
description="Red - Discord Bot", usage="redbot <instance_name> [arguments]"
|
||||||
parser.add_argument("--version", "-V", action="store_true",
|
)
|
||||||
help="Show Red's current version")
|
parser.add_argument("--version", "-V", action="store_true", help="Show Red's current version")
|
||||||
parser.add_argument("--list-instances", action="store_true",
|
parser.add_argument(
|
||||||
help="List all instance names setup "
|
"--list-instances",
|
||||||
"with 'redbot-setup'")
|
action="store_true",
|
||||||
parser.add_argument("--owner", type=int,
|
help="List all instance names setup " "with 'redbot-setup'",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--owner",
|
||||||
|
type=int,
|
||||||
help="ID of the owner. Only who hosts "
|
help="ID of the owner. Only who hosts "
|
||||||
"Red should be owner, this has "
|
"Red should be owner, this has "
|
||||||
"serious security implications if misused.")
|
"serious security implications if misused.",
|
||||||
parser.add_argument("--co-owner", type=int, default=[], nargs="*",
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--co-owner",
|
||||||
|
type=int,
|
||||||
|
default=[],
|
||||||
|
nargs="*",
|
||||||
help="ID of a co-owner. Only people who have access "
|
help="ID of a co-owner. Only people who have access "
|
||||||
"to the system that is hosting Red should be "
|
"to the system that is hosting Red should be "
|
||||||
"co-owners, as this gives them complete access "
|
"co-owners, as this gives them complete access "
|
||||||
"to the system's data. This has serious "
|
"to the system's data. This has serious "
|
||||||
"security implications if misused. Can be "
|
"security implications if misused. Can be "
|
||||||
"multiple.")
|
"multiple.",
|
||||||
parser.add_argument("--prefix", "-p", action="append",
|
)
|
||||||
help="Global prefix. Can be multiple")
|
parser.add_argument("--prefix", "-p", action="append", help="Global prefix. Can be multiple")
|
||||||
parser.add_argument("--no-prompt", action="store_true",
|
parser.add_argument(
|
||||||
|
"--no-prompt",
|
||||||
|
action="store_true",
|
||||||
help="Disables console inputs. Features requiring "
|
help="Disables console inputs. Features requiring "
|
||||||
"console interaction could be disabled as a "
|
"console interaction could be disabled as a "
|
||||||
"result")
|
"result",
|
||||||
parser.add_argument("--no-cogs",
|
)
|
||||||
action="store_true",
|
parser.add_argument(
|
||||||
help="Starts Red with no cogs loaded, only core")
|
"--no-cogs", action="store_true", help="Starts Red with no cogs loaded, only core"
|
||||||
parser.add_argument("--load-cogs", type=str, nargs="*",
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--load-cogs",
|
||||||
|
type=str,
|
||||||
|
nargs="*",
|
||||||
help="Force loading specified cogs from the installed packages. "
|
help="Force loading specified cogs from the installed packages. "
|
||||||
"Can be used with the --no-cogs flag to load these cogs exclusively.")
|
"Can be used with the --no-cogs flag to load these cogs exclusively.",
|
||||||
parser.add_argument("--self-bot",
|
)
|
||||||
action='store_true',
|
parser.add_argument(
|
||||||
help="Specifies if Red should log in as selfbot")
|
"--self-bot", action="store_true", help="Specifies if Red should log in as selfbot"
|
||||||
parser.add_argument("--not-bot",
|
)
|
||||||
action='store_true',
|
parser.add_argument(
|
||||||
help="Specifies if the token used belongs to a bot "
|
"--not-bot",
|
||||||
"account.")
|
action="store_true",
|
||||||
parser.add_argument("--dry-run",
|
help="Specifies if the token used belongs to a bot " "account.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--dry-run",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Makes Red quit with code 0 just before the "
|
help="Makes Red quit with code 0 just before the "
|
||||||
"login. This is useful for testing the boot "
|
"login. This is useful for testing the boot "
|
||||||
"process.")
|
"process.",
|
||||||
parser.add_argument("--debug",
|
)
|
||||||
|
parser.add_argument("--debug", action="store_true", help="Sets the loggers level as debug")
|
||||||
|
parser.add_argument("--dev", action="store_true", help="Enables developer mode")
|
||||||
|
parser.add_argument(
|
||||||
|
"--mentionable",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Sets the loggers level as debug")
|
help="Allows mentioning the bot as an alternative " "to using the bot prefix",
|
||||||
parser.add_argument("--dev",
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--rpc",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Enables developer mode")
|
help="Enables the built-in RPC server. Please read the docs" "prior to enabling this!",
|
||||||
parser.add_argument("--mentionable",
|
)
|
||||||
action="store_true",
|
parser.add_argument(
|
||||||
help="Allows mentioning the bot as an alternative "
|
"instance_name", nargs="?", help="Name of the bot instance created during `redbot-setup`."
|
||||||
"to using the bot prefix")
|
)
|
||||||
parser.add_argument("--rpc",
|
|
||||||
action="store_true",
|
|
||||||
help="Enables the built-in RPC server. Please read the docs"
|
|
||||||
"prior to enabling this!")
|
|
||||||
parser.add_argument("instance_name", nargs="?",
|
|
||||||
help="Name of the bot instance created during `redbot-setup`.")
|
|
||||||
|
|
||||||
args = parser.parse_args(args)
|
args = parser.parse_args(args)
|
||||||
|
|
||||||
@ -129,4 +150,3 @@ def parse_cli_flags(args):
|
|||||||
args.prefix = []
|
args.prefix = []
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|||||||
@ -34,14 +34,12 @@ class CogManager:
|
|||||||
install new cogs to, the default being the :code:`cogs/` folder in the root
|
install new cogs to, the default being the :code:`cogs/` folder in the root
|
||||||
bot directory.
|
bot directory.
|
||||||
"""
|
"""
|
||||||
def __init__(self, paths: Tuple[str]=()):
|
|
||||||
|
def __init__(self, paths: Tuple[str] = ()):
|
||||||
self.conf = Config.get_conf(self, 2938473984732, True)
|
self.conf = Config.get_conf(self, 2938473984732, True)
|
||||||
tmp_cog_install_path = cog_data_path(self) / "cogs"
|
tmp_cog_install_path = cog_data_path(self) / "cogs"
|
||||||
tmp_cog_install_path.mkdir(parents=True, exist_ok=True)
|
tmp_cog_install_path.mkdir(parents=True, exist_ok=True)
|
||||||
self.conf.register_global(
|
self.conf.register_global(paths=(), install_path=str(tmp_cog_install_path))
|
||||||
paths=(),
|
|
||||||
install_path=str(tmp_cog_install_path)
|
|
||||||
)
|
|
||||||
|
|
||||||
self._paths = [Path(p) for p in paths]
|
self._paths = [Path(p) for p in paths]
|
||||||
|
|
||||||
@ -158,7 +156,7 @@ class CogManager:
|
|||||||
if path == await self.install_path():
|
if path == await self.install_path():
|
||||||
raise ValueError("Cannot add the install path as an additional path.")
|
raise ValueError("Cannot add the install path as an additional path.")
|
||||||
|
|
||||||
all_paths = _deduplicate(await self.paths() + (path, ))
|
all_paths = _deduplicate(await self.paths() + (path,))
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
await self.set_paths(all_paths)
|
await self.set_paths(all_paths)
|
||||||
|
|
||||||
@ -225,8 +223,10 @@ class CogManager:
|
|||||||
if spec:
|
if spec:
|
||||||
return spec
|
return spec
|
||||||
|
|
||||||
raise RuntimeError("No 3rd party module by the name of '{}' was found"
|
raise RuntimeError(
|
||||||
" in any available path.".format(name))
|
"No 3rd party module by the name of '{}' was found"
|
||||||
|
" in any available path.".format(name)
|
||||||
|
)
|
||||||
|
|
||||||
async def _find_core_cog(self, name: str) -> ModuleSpec:
|
async def _find_core_cog(self, name: str) -> ModuleSpec:
|
||||||
"""
|
"""
|
||||||
@ -247,10 +247,11 @@ class CogManager:
|
|||||||
"""
|
"""
|
||||||
real_name = ".{}".format(name)
|
real_name = ".{}".format(name)
|
||||||
try:
|
try:
|
||||||
mod = import_module(real_name, package='redbot.cogs')
|
mod = import_module(real_name, package="redbot.cogs")
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
raise RuntimeError("No core cog by the name of '{}' could"
|
raise RuntimeError(
|
||||||
"be found.".format(name)) from e
|
"No core cog by the name of '{}' could" "be found.".format(name)
|
||||||
|
) from e
|
||||||
return mod.__spec__
|
return mod.__spec__
|
||||||
|
|
||||||
# noinspection PyUnreachableCode
|
# noinspection PyUnreachableCode
|
||||||
@ -284,7 +285,7 @@ class CogManager:
|
|||||||
async def available_modules(self) -> List[str]:
|
async def available_modules(self) -> List[str]:
|
||||||
"""Finds the names of all available modules to load.
|
"""Finds the names of all available modules to load.
|
||||||
"""
|
"""
|
||||||
paths = (await self.install_path(), ) + await self.paths()
|
paths = (await self.install_path(),) + await self.paths()
|
||||||
paths = [str(p) for p in paths]
|
paths = [str(p) for p in paths]
|
||||||
|
|
||||||
ret = []
|
ret = []
|
||||||
@ -341,8 +342,9 @@ class CogManagerUI:
|
|||||||
Add a path to the list of available cog paths.
|
Add a path to the list of available cog paths.
|
||||||
"""
|
"""
|
||||||
if not path.is_dir():
|
if not path.is_dir():
|
||||||
await ctx.send(_("That path does not exist or does not"
|
await ctx.send(
|
||||||
" point to a valid directory."))
|
_("That path does not exist or does not" " point to a valid directory.")
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -398,7 +400,7 @@ class CogManagerUI:
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def installpath(self, ctx: commands.Context, path: Path=None):
|
async def installpath(self, ctx: commands.Context, path: Path = None):
|
||||||
"""
|
"""
|
||||||
Returns the current install path or sets it if one is provided.
|
Returns the current install path or sets it if one is provided.
|
||||||
The provided path must be absolute or relative to the bot's
|
The provided path must be absolute or relative to the bot's
|
||||||
@ -416,8 +418,9 @@ class CogManagerUI:
|
|||||||
return
|
return
|
||||||
|
|
||||||
install_path = await ctx.bot.cog_mgr.install_path()
|
install_path = await ctx.bot.cog_mgr.install_path()
|
||||||
await ctx.send(_("The bot will install new cogs to the `{}`"
|
await ctx.send(
|
||||||
" directory.").format(install_path))
|
_("The bot will install new cogs to the `{}`" " directory.").format(install_path)
|
||||||
|
)
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
@ -435,22 +438,20 @@ class CogManagerUI:
|
|||||||
unloaded = sorted(list(unloaded), key=str.lower)
|
unloaded = sorted(list(unloaded), key=str.lower)
|
||||||
|
|
||||||
if await ctx.embed_requested():
|
if await ctx.embed_requested():
|
||||||
loaded = ('**{} loaded:**\n').format(len(loaded)) + ", ".join(loaded)
|
loaded = ("**{} loaded:**\n").format(len(loaded)) + ", ".join(loaded)
|
||||||
unloaded = ('**{} unloaded:**\n').format(len(unloaded)) + ", ".join(unloaded)
|
unloaded = ("**{} unloaded:**\n").format(len(unloaded)) + ", ".join(unloaded)
|
||||||
|
|
||||||
for page in pagify(loaded, delims=[', ', '\n'], page_length=1800):
|
for page in pagify(loaded, delims=[", ", "\n"], page_length=1800):
|
||||||
e = discord.Embed(description=page,
|
e = discord.Embed(description=page, colour=discord.Colour.dark_green())
|
||||||
colour=discord.Colour.dark_green())
|
|
||||||
await ctx.send(embed=e)
|
await ctx.send(embed=e)
|
||||||
|
|
||||||
for page in pagify(unloaded, delims=[', ', '\n'], page_length=1800):
|
for page in pagify(unloaded, delims=[", ", "\n"], page_length=1800):
|
||||||
e = discord.Embed(description=page,
|
e = discord.Embed(description=page, colour=discord.Colour.dark_red())
|
||||||
colour=discord.Colour.dark_red())
|
|
||||||
await ctx.send(embed=e)
|
await ctx.send(embed=e)
|
||||||
else:
|
else:
|
||||||
loaded_count = '**{} loaded:**\n'.format(len(loaded))
|
loaded_count = "**{} loaded:**\n".format(len(loaded))
|
||||||
loaded = ", ".join(loaded)
|
loaded = ", ".join(loaded)
|
||||||
unloaded_count = '**{} unloaded:**\n'.format(len(unloaded))
|
unloaded_count = "**{} unloaded:**\n".format(len(unloaded))
|
||||||
unloaded = ", ".join(unloaded)
|
unloaded = ", ".join(unloaded)
|
||||||
loaded_count_sent = False
|
loaded_count_sent = False
|
||||||
unloaded_count_sent = False
|
unloaded_count_sent = False
|
||||||
|
|||||||
@ -20,7 +20,7 @@ class Command(commands.Command):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self._help_override = kwargs.pop('help_override', None)
|
self._help_override = kwargs.pop("help_override", None)
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.translator = kwargs.pop("i18n", None)
|
self.translator = kwargs.pop("i18n", None)
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ class Command(commands.Command):
|
|||||||
translator = self.translator
|
translator = self.translator
|
||||||
command_doc = self.callback.__doc__
|
command_doc = self.callback.__doc__
|
||||||
if command_doc is None:
|
if command_doc is None:
|
||||||
return ''
|
return ""
|
||||||
return inspect.cleandoc(translator(command_doc))
|
return inspect.cleandoc(translator(command_doc))
|
||||||
|
|
||||||
@help.setter
|
@help.setter
|
||||||
@ -60,6 +60,7 @@ class Group(Command, commands.Group):
|
|||||||
|
|
||||||
# decorators
|
# decorators
|
||||||
|
|
||||||
|
|
||||||
def command(name=None, cls=Command, **attrs):
|
def command(name=None, cls=Command, **attrs):
|
||||||
"""A decorator which transforms an async function into a `Command`.
|
"""A decorator which transforms an async function into a `Command`.
|
||||||
|
|
||||||
|
|||||||
@ -59,10 +59,9 @@ class Context(commands.Context):
|
|||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def send_interactive(self,
|
async def send_interactive(
|
||||||
messages: Iterable[str],
|
self, messages: Iterable[str], box_lang: str = None, timeout: int = 15
|
||||||
box_lang: str=None,
|
) -> List[discord.Message]:
|
||||||
timeout: int=15) -> List[discord.Message]:
|
|
||||||
"""Send multiple messages interactively.
|
"""Send multiple messages interactively.
|
||||||
|
|
||||||
The user will be prompted for whether or not they would like to view
|
The user will be prompted for whether or not they would like to view
|
||||||
@ -84,9 +83,9 @@ class Context(commands.Context):
|
|||||||
messages = tuple(messages)
|
messages = tuple(messages)
|
||||||
ret = []
|
ret = []
|
||||||
|
|
||||||
more_check = lambda m: (m.author == self.author and
|
more_check = lambda m: (
|
||||||
m.channel == self.channel and
|
m.author == self.author and m.channel == self.channel and m.content.lower() == "more"
|
||||||
m.content.lower() == "more")
|
)
|
||||||
|
|
||||||
for idx, page in enumerate(messages, 1):
|
for idx, page in enumerate(messages, 1):
|
||||||
if box_lang is None:
|
if box_lang is None:
|
||||||
@ -105,10 +104,10 @@ class Context(commands.Context):
|
|||||||
query = await self.send(
|
query = await self.send(
|
||||||
"There {} still {} message{} remaining. "
|
"There {} still {} message{} remaining. "
|
||||||
"Type `more` to continue."
|
"Type `more` to continue."
|
||||||
"".format(is_are, n_remaining, plural))
|
"".format(is_are, n_remaining, plural)
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
resp = await self.bot.wait_for(
|
resp = await self.bot.wait_for("message", check=more_check, timeout=timeout)
|
||||||
'message', check=more_check, timeout=timeout)
|
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
await query.delete()
|
await query.delete()
|
||||||
break
|
break
|
||||||
@ -134,9 +133,7 @@ class Context(commands.Context):
|
|||||||
"""
|
"""
|
||||||
if self.guild and not self.channel.permissions_for(self.guild.me).embed_links:
|
if self.guild and not self.channel.permissions_for(self.guild.me).embed_links:
|
||||||
return False
|
return False
|
||||||
return await self.bot.embed_requested(
|
return await self.bot.embed_requested(self.channel, self.author, command=self.command)
|
||||||
self.channel, self.author, command=self.command
|
|
||||||
)
|
|
||||||
|
|
||||||
async def maybe_send_embed(self, message: str) -> discord.Message:
|
async def maybe_send_embed(self, message: str) -> discord.Message:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -38,9 +38,11 @@ class _ValueCtxManager:
|
|||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
self.raw_value = await self
|
self.raw_value = await self
|
||||||
if not isinstance(self.raw_value, (list, dict)):
|
if not isinstance(self.raw_value, (list, dict)):
|
||||||
raise TypeError("Type of retrieved value must be mutable (i.e. "
|
raise TypeError(
|
||||||
|
"Type of retrieved value must be mutable (i.e. "
|
||||||
"list or dict) in order to use a config value as "
|
"list or dict) in order to use a config value as "
|
||||||
"a context manager.")
|
"a context manager."
|
||||||
|
)
|
||||||
return self.raw_value
|
return self.raw_value
|
||||||
|
|
||||||
async def __aexit__(self, *exc_info):
|
async def __aexit__(self, *exc_info):
|
||||||
@ -61,6 +63,7 @@ class Value:
|
|||||||
A reference to `Config.driver`.
|
A reference to `Config.driver`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, identifiers: Tuple[str], default_value, driver):
|
def __init__(self, identifiers: Tuple[str], default_value, driver):
|
||||||
self._identifiers = identifiers
|
self._identifiers = identifiers
|
||||||
self.default = default_value
|
self.default = default_value
|
||||||
@ -168,10 +171,10 @@ class Group(Value):
|
|||||||
A reference to `Config.driver`.
|
A reference to `Config.driver`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, identifiers: Tuple[str],
|
|
||||||
defaults: dict,
|
def __init__(
|
||||||
driver,
|
self, identifiers: Tuple[str], defaults: dict, driver, force_registration: bool = False
|
||||||
force_registration: bool=False):
|
):
|
||||||
self._defaults = defaults
|
self._defaults = defaults
|
||||||
self.force_registration = force_registration
|
self.force_registration = force_registration
|
||||||
self.driver = driver
|
self.driver = driver
|
||||||
@ -209,31 +212,22 @@ class Group(Value):
|
|||||||
"""
|
"""
|
||||||
is_group = self.is_group(item)
|
is_group = self.is_group(item)
|
||||||
is_value = not is_group and self.is_value(item)
|
is_value = not is_group and self.is_value(item)
|
||||||
new_identifiers = self.identifiers + (item, )
|
new_identifiers = self.identifiers + (item,)
|
||||||
if is_group:
|
if is_group:
|
||||||
return Group(
|
return Group(
|
||||||
identifiers=new_identifiers,
|
identifiers=new_identifiers,
|
||||||
defaults=self._defaults[item],
|
defaults=self._defaults[item],
|
||||||
driver=self.driver,
|
driver=self.driver,
|
||||||
force_registration=self.force_registration
|
force_registration=self.force_registration,
|
||||||
)
|
)
|
||||||
elif is_value:
|
elif is_value:
|
||||||
return Value(
|
return Value(
|
||||||
identifiers=new_identifiers,
|
identifiers=new_identifiers, default_value=self._defaults[item], driver=self.driver
|
||||||
default_value=self._defaults[item],
|
|
||||||
driver=self.driver
|
|
||||||
)
|
)
|
||||||
elif self.force_registration:
|
elif self.force_registration:
|
||||||
raise AttributeError(
|
raise AttributeError("'{}' is not a valid registered Group " "or value.".format(item))
|
||||||
"'{}' is not a valid registered Group "
|
|
||||||
"or value.".format(item)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
return Value(
|
return Value(identifiers=new_identifiers, default_value=None, driver=self.driver)
|
||||||
identifiers=new_identifiers,
|
|
||||||
default_value=None,
|
|
||||||
driver=self.driver
|
|
||||||
)
|
|
||||||
|
|
||||||
def is_group(self, item: str) -> bool:
|
def is_group(self, item: str) -> bool:
|
||||||
"""A helper method for `__getattr__`. Most developers will have no need
|
"""A helper method for `__getattr__`. Most developers will have no need
|
||||||
@ -385,9 +379,7 @@ class Group(Value):
|
|||||||
|
|
||||||
async def set(self, value):
|
async def set(self, value):
|
||||||
if not isinstance(value, dict):
|
if not isinstance(value, dict):
|
||||||
raise ValueError(
|
raise ValueError("You may only set the value of a group to be a dict.")
|
||||||
"You may only set the value of a group to be a dict."
|
|
||||||
)
|
|
||||||
await super().set(value)
|
await super().set(value)
|
||||||
|
|
||||||
async def set_raw(self, *nested_path: str, value):
|
async def set_raw(self, *nested_path: str, value):
|
||||||
@ -456,10 +448,14 @@ class Config:
|
|||||||
USER = "USER"
|
USER = "USER"
|
||||||
MEMBER = "MEMBER"
|
MEMBER = "MEMBER"
|
||||||
|
|
||||||
def __init__(self, cog_name: str, unique_identifier: str,
|
def __init__(
|
||||||
|
self,
|
||||||
|
cog_name: str,
|
||||||
|
unique_identifier: str,
|
||||||
driver: "BaseDriver",
|
driver: "BaseDriver",
|
||||||
force_registration: bool=False,
|
force_registration: bool = False,
|
||||||
defaults: dict=None):
|
defaults: dict = None,
|
||||||
|
):
|
||||||
self.cog_name = cog_name
|
self.cog_name = cog_name
|
||||||
self.unique_identifier = unique_identifier
|
self.unique_identifier = unique_identifier
|
||||||
|
|
||||||
@ -472,8 +468,7 @@ class Config:
|
|||||||
return deepcopy(self._defaults)
|
return deepcopy(self._defaults)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_conf(cls, cog_instance, identifier: int,
|
def get_conf(cls, cog_instance, identifier: int, force_registration=False, cog_name=None):
|
||||||
force_registration=False, cog_name=None):
|
|
||||||
"""Get a Config instance for your cog.
|
"""Get a Config instance for your cog.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
@ -519,20 +514,24 @@ class Config:
|
|||||||
|
|
||||||
log.debug("Basic config: \n\n{}".format(basic_config))
|
log.debug("Basic config: \n\n{}".format(basic_config))
|
||||||
|
|
||||||
driver_name = basic_config.get('STORAGE_TYPE', 'JSON')
|
driver_name = basic_config.get("STORAGE_TYPE", "JSON")
|
||||||
driver_details = basic_config.get('STORAGE_DETAILS', {})
|
driver_details = basic_config.get("STORAGE_DETAILS", {})
|
||||||
|
|
||||||
log.debug("Using driver: '{}'".format(driver_name))
|
log.debug("Using driver: '{}'".format(driver_name))
|
||||||
|
|
||||||
driver = get_driver(driver_name, cog_name, uuid, data_path_override=cog_path_override,
|
driver = get_driver(
|
||||||
**driver_details)
|
driver_name, cog_name, uuid, data_path_override=cog_path_override, **driver_details
|
||||||
conf = cls(cog_name=cog_name, unique_identifier=uuid,
|
)
|
||||||
|
conf = cls(
|
||||||
|
cog_name=cog_name,
|
||||||
|
unique_identifier=uuid,
|
||||||
force_registration=force_registration,
|
force_registration=force_registration,
|
||||||
driver=driver)
|
driver=driver,
|
||||||
|
)
|
||||||
return conf
|
return conf
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_core_conf(cls, force_registration: bool=False):
|
def get_core_conf(cls, force_registration: bool = False):
|
||||||
"""Get a Config instance for a core module.
|
"""Get a Config instance for a core module.
|
||||||
|
|
||||||
All core modules that require a config instance should use this
|
All core modules that require a config instance should use this
|
||||||
@ -549,14 +548,18 @@ class Config:
|
|||||||
# We have to import this here otherwise we have a circular dependency
|
# We have to import this here otherwise we have a circular dependency
|
||||||
from .data_manager import basic_config
|
from .data_manager import basic_config
|
||||||
|
|
||||||
driver_name = basic_config.get('STORAGE_TYPE', 'JSON')
|
driver_name = basic_config.get("STORAGE_TYPE", "JSON")
|
||||||
driver_details = basic_config.get('STORAGE_DETAILS', {})
|
driver_details = basic_config.get("STORAGE_DETAILS", {})
|
||||||
|
|
||||||
driver = get_driver(driver_name, "Core", '0', data_path_override=core_path,
|
driver = get_driver(
|
||||||
**driver_details)
|
driver_name, "Core", "0", data_path_override=core_path, **driver_details
|
||||||
conf = cls(cog_name="Core", driver=driver,
|
)
|
||||||
unique_identifier='0',
|
conf = cls(
|
||||||
force_registration=force_registration)
|
cog_name="Core",
|
||||||
|
driver=driver,
|
||||||
|
unique_identifier="0",
|
||||||
|
force_registration=force_registration,
|
||||||
|
)
|
||||||
return conf
|
return conf
|
||||||
|
|
||||||
def __getattr__(self, item: str) -> Union[Group, Value]:
|
def __getattr__(self, item: str) -> Union[Group, Value]:
|
||||||
@ -593,7 +596,7 @@ class Config:
|
|||||||
"""
|
"""
|
||||||
ret = {}
|
ret = {}
|
||||||
partial = ret
|
partial = ret
|
||||||
splitted = key.split('__')
|
splitted = key.split("__")
|
||||||
for i, k in enumerate(splitted, start=1):
|
for i, k in enumerate(splitted, start=1):
|
||||||
if not k.isidentifier():
|
if not k.isidentifier():
|
||||||
raise RuntimeError("'{}' is an invalid config key.".format(k))
|
raise RuntimeError("'{}' is an invalid config key.".format(k))
|
||||||
@ -621,8 +624,9 @@ class Config:
|
|||||||
existing_is_dict = isinstance(_partial[k], dict)
|
existing_is_dict = isinstance(_partial[k], dict)
|
||||||
if val_is_dict != existing_is_dict:
|
if val_is_dict != existing_is_dict:
|
||||||
# != is XOR
|
# != is XOR
|
||||||
raise KeyError("You cannot register a Group and a Value under"
|
raise KeyError(
|
||||||
" the same name.")
|
"You cannot register a Group and a Value under" " the same name."
|
||||||
|
)
|
||||||
if val_is_dict:
|
if val_is_dict:
|
||||||
Config._update_defaults(v, _partial=_partial[k])
|
Config._update_defaults(v, _partial=_partial[k])
|
||||||
else:
|
else:
|
||||||
@ -736,7 +740,7 @@ class Config:
|
|||||||
identifiers=(key, *identifiers),
|
identifiers=(key, *identifiers),
|
||||||
defaults=self.defaults.get(key, {}),
|
defaults=self.defaults.get(key, {}),
|
||||||
driver=self.driver,
|
driver=self.driver,
|
||||||
force_registration=self.force_registration
|
force_registration=self.force_registration,
|
||||||
)
|
)
|
||||||
|
|
||||||
def guild(self, guild: discord.Guild) -> Group:
|
def guild(self, guild: discord.Guild) -> Group:
|
||||||
@ -935,7 +939,7 @@ class Config:
|
|||||||
ret[int(member_id)] = new_member_data
|
ret[int(member_id)] = new_member_data
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
async def all_members(self, guild: discord.Guild=None) -> dict:
|
async def all_members(self, guild: discord.Guild = None) -> dict:
|
||||||
"""Get data for all members.
|
"""Get data for all members.
|
||||||
|
|
||||||
If :code:`guild` is specified, only the data for the members of that
|
If :code:`guild` is specified, only the data for the members of that
|
||||||
@ -965,8 +969,7 @@ class Config:
|
|||||||
group = self._get_base_group(self.MEMBER)
|
group = self._get_base_group(self.MEMBER)
|
||||||
dict_ = await group()
|
dict_ = await group()
|
||||||
for guild_id, guild_data in dict_.items():
|
for guild_id, guild_data in dict_.items():
|
||||||
ret[int(guild_id)] = self._all_members_from_guild(
|
ret[int(guild_id)] = self._all_members_from_guild(group, guild_data)
|
||||||
group, guild_data)
|
|
||||||
else:
|
else:
|
||||||
group = self._get_base_group(self.MEMBER, guild.id)
|
group = self._get_base_group(self.MEMBER, guild.id)
|
||||||
guild_data = await group()
|
guild_data = await group()
|
||||||
@ -992,9 +995,7 @@ class Config:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if not scopes:
|
if not scopes:
|
||||||
group = Group(identifiers=[],
|
group = Group(identifiers=[], defaults={}, driver=self.driver)
|
||||||
defaults={},
|
|
||||||
driver=self.driver)
|
|
||||||
else:
|
else:
|
||||||
group = self._get_base_group(*scopes)
|
group = self._get_base_group(*scopes)
|
||||||
await group.clear()
|
await group.clear()
|
||||||
@ -1046,7 +1047,7 @@ class Config:
|
|||||||
"""
|
"""
|
||||||
await self._clear_scope(self.USER)
|
await self._clear_scope(self.USER)
|
||||||
|
|
||||||
async def clear_all_members(self, guild: discord.Guild=None):
|
async def clear_all_members(self, guild: discord.Guild = None):
|
||||||
"""Clear all member data.
|
"""Clear all member data.
|
||||||
|
|
||||||
This resets all specified member data to its registered defaults.
|
This resets all specified member data to its registered defaults.
|
||||||
|
|||||||
@ -32,10 +32,12 @@ __all__ = ["Core"]
|
|||||||
|
|
||||||
log = logging.getLogger("red")
|
log = logging.getLogger("red")
|
||||||
|
|
||||||
OWNER_DISCLAIMER = ("⚠ **Only** the person who is hosting Red should be "
|
OWNER_DISCLAIMER = (
|
||||||
|
"⚠ **Only** the person who is hosting Red should be "
|
||||||
"owner. **This has SERIOUS security implications. The "
|
"owner. **This has SERIOUS security implications. The "
|
||||||
"owner can access any data that is present on the host "
|
"owner can access any data that is present on the host "
|
||||||
"system.** ⚠")
|
"system.** ⚠"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
_ = i18n.Translator("Core", __file__)
|
_ = i18n.Translator("Core", __file__)
|
||||||
@ -44,12 +46,13 @@ _ = i18n.Translator("Core", __file__)
|
|||||||
@i18n.cog_i18n(_)
|
@i18n.cog_i18n(_)
|
||||||
class Core:
|
class Core:
|
||||||
"""Commands related to core functions"""
|
"""Commands related to core functions"""
|
||||||
|
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
self.bot = bot # type: Red
|
self.bot = bot # type: Red
|
||||||
|
|
||||||
rpc.add_method('core', self.rpc_load)
|
rpc.add_method("core", self.rpc_load)
|
||||||
rpc.add_method('core', self.rpc_unload)
|
rpc.add_method("core", self.rpc_unload)
|
||||||
rpc.add_method('core', self.rpc_reload)
|
rpc.add_method("core", self.rpc_reload)
|
||||||
|
|
||||||
@commands.command(hidden=True)
|
@commands.command(hidden=True)
|
||||||
async def ping(self, ctx):
|
async def ping(self, ctx):
|
||||||
@ -72,15 +75,13 @@ class Core:
|
|||||||
since = datetime.datetime(2016, 1, 2, 0, 0)
|
since = datetime.datetime(2016, 1, 2, 0, 0)
|
||||||
days_since = (datetime.datetime.utcnow() - since).days
|
days_since = (datetime.datetime.utcnow() - since).days
|
||||||
dpy_version = "[{}]({})".format(discord.__version__, dpy_repo)
|
dpy_version = "[{}]({})".format(discord.__version__, dpy_repo)
|
||||||
python_version = "[{}.{}.{}]({})".format(
|
python_version = "[{}.{}.{}]({})".format(*sys.version_info[:3], python_url)
|
||||||
*sys.version_info[:3], python_url
|
|
||||||
)
|
|
||||||
red_version = "[{}]({})".format(__version__, red_pypi)
|
red_version = "[{}]({})".format(__version__, red_pypi)
|
||||||
app_info = await self.bot.application_info()
|
app_info = await self.bot.application_info()
|
||||||
owner = app_info.owner
|
owner = app_info.owner
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get('{}/json'.format(red_pypi)) as r:
|
async with session.get("{}/json".format(red_pypi)) as r:
|
||||||
data = await r.json()
|
data = await r.json()
|
||||||
outdated = StrictVersion(data["info"]["version"]) > StrictVersion(__version__)
|
outdated = StrictVersion(data["info"]["version"]) > StrictVersion(__version__)
|
||||||
about = (
|
about = (
|
||||||
@ -89,7 +90,8 @@ class Core:
|
|||||||
"Red is backed by a passionate community who contributes and "
|
"Red is backed by a passionate community who contributes and "
|
||||||
"creates content for everyone to enjoy. [Join us today]({}) "
|
"creates content for everyone to enjoy. [Join us today]({}) "
|
||||||
"and help us improve!\n\n"
|
"and help us improve!\n\n"
|
||||||
"".format(red_repo, author_repo, org_repo, support_server_url))
|
"".format(red_repo, author_repo, org_repo, support_server_url)
|
||||||
|
)
|
||||||
|
|
||||||
embed = discord.Embed(color=discord.Color.red())
|
embed = discord.Embed(color=discord.Color.red())
|
||||||
embed.add_field(name="Instance owned by", value=str(owner))
|
embed.add_field(name="Instance owned by", value=str(owner))
|
||||||
@ -97,14 +99,14 @@ class Core:
|
|||||||
embed.add_field(name="discord.py", value=dpy_version)
|
embed.add_field(name="discord.py", value=dpy_version)
|
||||||
embed.add_field(name="Red version", value=red_version)
|
embed.add_field(name="Red version", value=red_version)
|
||||||
if outdated:
|
if outdated:
|
||||||
embed.add_field(name="Outdated", value="Yes, {} is available".format(
|
embed.add_field(
|
||||||
data["info"]["version"]
|
name="Outdated", value="Yes, {} is available".format(data["info"]["version"])
|
||||||
)
|
|
||||||
)
|
)
|
||||||
embed.add_field(name="About Red", value=about, inline=False)
|
embed.add_field(name="About Red", value=about, inline=False)
|
||||||
|
|
||||||
embed.set_footer(text="Bringing joy since 02 Jan 2016 (over "
|
embed.set_footer(
|
||||||
"{} days ago!)".format(days_since))
|
text="Bringing joy since 02 Jan 2016 (over " "{} days ago!)".format(days_since)
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
@ -115,11 +117,7 @@ class Core:
|
|||||||
"""Shows Red's uptime"""
|
"""Shows Red's uptime"""
|
||||||
since = ctx.bot.uptime.strftime("%Y-%m-%d %H:%M:%S")
|
since = ctx.bot.uptime.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
passed = self.get_bot_uptime()
|
passed = self.get_bot_uptime()
|
||||||
await ctx.send(
|
await ctx.send("Been up for: **{}** (since {} UTC)".format(passed, since))
|
||||||
"Been up for: **{}** (since {} UTC)".format(
|
|
||||||
passed, since
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_bot_uptime(self, *, brief=False):
|
def get_bot_uptime(self, *, brief=False):
|
||||||
# Courtesy of Danny
|
# Courtesy of Danny
|
||||||
@ -131,13 +129,13 @@ class Core:
|
|||||||
|
|
||||||
if not brief:
|
if not brief:
|
||||||
if days:
|
if days:
|
||||||
fmt = '{d} days, {h} hours, {m} minutes, and {s} seconds'
|
fmt = "{d} days, {h} hours, {m} minutes, and {s} seconds"
|
||||||
else:
|
else:
|
||||||
fmt = '{h} hours, {m} minutes, and {s} seconds'
|
fmt = "{h} hours, {m} minutes, and {s} seconds"
|
||||||
else:
|
else:
|
||||||
fmt = '{h}h {m}m {s}s'
|
fmt = "{h}h {m}m {s}s"
|
||||||
if days:
|
if days:
|
||||||
fmt = '{d}d ' + fmt
|
fmt = "{d}d " + fmt
|
||||||
|
|
||||||
return fmt.format(d=days, h=hours, m=minutes, s=seconds)
|
return fmt.format(d=days, h=hours, m=minutes, s=seconds)
|
||||||
|
|
||||||
@ -176,14 +174,12 @@ class Core:
|
|||||||
current = await self.bot.db.embeds()
|
current = await self.bot.db.embeds()
|
||||||
await self.bot.db.embeds.set(not current)
|
await self.bot.db.embeds.set(not current)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Embeds are now {} by default.").format(
|
_("Embeds are now {} by default.").format("disabled" if current else "enabled")
|
||||||
"disabled" if current else "enabled"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@embedset.command(name="guild")
|
@embedset.command(name="guild")
|
||||||
@checks.guildowner_or_permissions(administrator=True)
|
@checks.guildowner_or_permissions(administrator=True)
|
||||||
async def embedset_guild(self, ctx: commands.Context, enabled: bool=None):
|
async def embedset_guild(self, ctx: commands.Context, enabled: bool = None):
|
||||||
"""
|
"""
|
||||||
Toggle the guild's embed setting.
|
Toggle the guild's embed setting.
|
||||||
|
|
||||||
@ -197,18 +193,14 @@ class Core:
|
|||||||
"""
|
"""
|
||||||
await self.bot.db.guild(ctx.guild).embeds.set(enabled)
|
await self.bot.db.guild(ctx.guild).embeds.set(enabled)
|
||||||
if enabled is None:
|
if enabled is None:
|
||||||
await ctx.send(
|
await ctx.send(_("Embeds will now fall back to the global setting."))
|
||||||
_("Embeds will now fall back to the global setting.")
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Embeds are now {} for this guild.").format(
|
_("Embeds are now {} for this guild.").format("enabled" if enabled else "disabled")
|
||||||
"enabled" if enabled else "disabled"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@embedset.command(name="user")
|
@embedset.command(name="user")
|
||||||
async def embedset_user(self, ctx: commands.Context, enabled: bool=None):
|
async def embedset_user(self, ctx: commands.Context, enabled: bool = None):
|
||||||
"""
|
"""
|
||||||
Toggle the user's embed setting.
|
Toggle the user's embed setting.
|
||||||
|
|
||||||
@ -222,19 +214,15 @@ class Core:
|
|||||||
"""
|
"""
|
||||||
await self.bot.db.user(ctx.author).embeds.set(enabled)
|
await self.bot.db.user(ctx.author).embeds.set(enabled)
|
||||||
if enabled is None:
|
if enabled is None:
|
||||||
await ctx.send(
|
await ctx.send(_("Embeds will now fall back to the global setting."))
|
||||||
_("Embeds will now fall back to the global setting.")
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Embeds are now {} for you.").format(
|
_("Embeds are now {} for you.").format("enabled" if enabled else "disabled")
|
||||||
"enabled" if enabled else "disabled"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def traceback(self, ctx, public: bool=False):
|
async def traceback(self, ctx, public: bool = False):
|
||||||
"""Sends to the owner the last command exception that has occurred
|
"""Sends to the owner the last command exception that has occurred
|
||||||
|
|
||||||
If public (yes is specified), it will be sent to the chat instead"""
|
If public (yes is specified), it will be sent to the chat instead"""
|
||||||
@ -267,8 +255,7 @@ class Core:
|
|||||||
author = ctx.author
|
author = ctx.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
|
|
||||||
await ctx.send("Are you sure you want me to leave this server?"
|
await ctx.send("Are you sure you want me to leave this server?" " Type yes to confirm.")
|
||||||
" Type yes to confirm.")
|
|
||||||
|
|
||||||
def conf_check(m):
|
def conf_check(m):
|
||||||
return m.author == author
|
return m.author == author
|
||||||
@ -285,15 +272,14 @@ class Core:
|
|||||||
async def servers(self, ctx):
|
async def servers(self, ctx):
|
||||||
"""Lists and allows to leave servers"""
|
"""Lists and allows to leave servers"""
|
||||||
owner = ctx.author
|
owner = ctx.author
|
||||||
guilds = sorted(list(self.bot.guilds),
|
guilds = sorted(list(self.bot.guilds), key=lambda s: s.name.lower())
|
||||||
key=lambda s: s.name.lower())
|
|
||||||
msg = ""
|
msg = ""
|
||||||
for i, server in enumerate(guilds, 1):
|
for i, server in enumerate(guilds, 1):
|
||||||
msg += "{}: {}\n".format(i, server.name)
|
msg += "{}: {}\n".format(i, server.name)
|
||||||
|
|
||||||
msg += "\nTo leave a server, just type its number."
|
msg += "\nTo leave a server, just type its number."
|
||||||
|
|
||||||
for page in pagify(msg, ['\n']):
|
for page in pagify(msg, ["\n"]):
|
||||||
await ctx.send(page)
|
await ctx.send(page)
|
||||||
|
|
||||||
def msg_check(m):
|
def msg_check(m):
|
||||||
@ -343,7 +329,7 @@ class Core:
|
|||||||
loaded_packages = []
|
loaded_packages = []
|
||||||
notfound_packages = []
|
notfound_packages = []
|
||||||
|
|
||||||
cognames = [c.strip() for c in cog_name.split(' ')]
|
cognames = [c.strip() for c in cog_name.split(" ")]
|
||||||
cogspecs = []
|
cogspecs = []
|
||||||
|
|
||||||
for c in cognames:
|
for c in cognames:
|
||||||
@ -352,7 +338,7 @@ class Core:
|
|||||||
cogspecs.append((spec, c))
|
cogspecs.append((spec, c))
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
notfound_packages.append(inline(c))
|
notfound_packages.append(inline(c))
|
||||||
#await ctx.send(_("No module named '{}' was found in any"
|
# await ctx.send(_("No module named '{}' was found in any"
|
||||||
# " cog path.").format(c))
|
# " cog path.").format(c))
|
||||||
|
|
||||||
if len(cogspecs) > 0:
|
if len(cogspecs) > 0:
|
||||||
@ -362,10 +348,12 @@ class Core:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception("Package loading failed", exc_info=e)
|
log.exception("Package loading failed", exc_info=e)
|
||||||
|
|
||||||
exception_log = ("Exception in command '{}'\n"
|
exception_log = (
|
||||||
"".format(ctx.command.qualified_name))
|
"Exception in command '{}'\n" "".format(ctx.command.qualified_name)
|
||||||
exception_log += "".join(traceback.format_exception(type(e),
|
)
|
||||||
e, e.__traceback__))
|
exception_log += "".join(
|
||||||
|
traceback.format_exception(type(e), e, e.__traceback__)
|
||||||
|
)
|
||||||
self.bot._last_exception = exception_log
|
self.bot._last_exception = exception_log
|
||||||
failed_packages.append(inline(name))
|
failed_packages.append(inline(name))
|
||||||
else:
|
else:
|
||||||
@ -378,21 +366,23 @@ class Core:
|
|||||||
await ctx.send(_(formed))
|
await ctx.send(_(formed))
|
||||||
|
|
||||||
if failed_packages:
|
if failed_packages:
|
||||||
fmt = ("Failed to load package{plural} {packs}. Check your console or "
|
fmt = (
|
||||||
"logs for details.")
|
"Failed to load package{plural} {packs}. Check your console or "
|
||||||
|
"logs for details."
|
||||||
|
)
|
||||||
formed = self.get_package_strings(failed_packages, fmt)
|
formed = self.get_package_strings(failed_packages, fmt)
|
||||||
await ctx.send(_(formed))
|
await ctx.send(_(formed))
|
||||||
|
|
||||||
if notfound_packages:
|
if notfound_packages:
|
||||||
fmt = 'The package{plural} {packs} {other} not found in any cog path.'
|
fmt = "The package{plural} {packs} {other} not found in any cog path."
|
||||||
formed = self.get_package_strings(notfound_packages, fmt, ('was', 'were'))
|
formed = self.get_package_strings(notfound_packages, fmt, ("was", "were"))
|
||||||
await ctx.send(_(formed))
|
await ctx.send(_(formed))
|
||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def unload(self, ctx, *, cog_name: str):
|
async def unload(self, ctx, *, cog_name: str):
|
||||||
"""Unloads packages"""
|
"""Unloads packages"""
|
||||||
cognames = [c.strip() for c in cog_name.split(' ')]
|
cognames = [c.strip() for c in cog_name.split(" ")]
|
||||||
failed_packages = []
|
failed_packages = []
|
||||||
unloaded_packages = []
|
unloaded_packages = []
|
||||||
|
|
||||||
@ -406,12 +396,12 @@ class Core:
|
|||||||
|
|
||||||
if unloaded_packages:
|
if unloaded_packages:
|
||||||
fmt = "Package{plural} {packs} {other} unloaded."
|
fmt = "Package{plural} {packs} {other} unloaded."
|
||||||
formed = self.get_package_strings(unloaded_packages, fmt, ('was', 'were'))
|
formed = self.get_package_strings(unloaded_packages, fmt, ("was", "were"))
|
||||||
await ctx.send(_(formed))
|
await ctx.send(_(formed))
|
||||||
|
|
||||||
if failed_packages:
|
if failed_packages:
|
||||||
fmt = "The package{plural} {packs} {other} not loaded."
|
fmt = "The package{plural} {packs} {other} not loaded."
|
||||||
formed = self.get_package_strings(failed_packages, fmt, ('is', 'are'))
|
formed = self.get_package_strings(failed_packages, fmt, ("is", "are"))
|
||||||
await ctx.send(_(formed))
|
await ctx.send(_(formed))
|
||||||
|
|
||||||
@commands.command(name="reload")
|
@commands.command(name="reload")
|
||||||
@ -419,7 +409,7 @@ class Core:
|
|||||||
async def _reload(self, ctx, *, cog_name: str):
|
async def _reload(self, ctx, *, cog_name: str):
|
||||||
"""Reloads packages"""
|
"""Reloads packages"""
|
||||||
|
|
||||||
cognames = [c.strip() for c in cog_name.split(' ')]
|
cognames = [c.strip() for c in cog_name.split(" ")]
|
||||||
|
|
||||||
for c in cognames:
|
for c in cognames:
|
||||||
ctx.bot.unload_extension(c)
|
ctx.bot.unload_extension(c)
|
||||||
@ -444,50 +434,46 @@ class Core:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception("Package reloading failed", exc_info=e)
|
log.exception("Package reloading failed", exc_info=e)
|
||||||
|
|
||||||
exception_log = ("Exception in command '{}'\n"
|
exception_log = (
|
||||||
"".format(ctx.command.qualified_name))
|
"Exception in command '{}'\n" "".format(ctx.command.qualified_name)
|
||||||
exception_log += "".join(traceback.format_exception(type(e),
|
)
|
||||||
e, e.__traceback__))
|
exception_log += "".join(traceback.format_exception(type(e), e, e.__traceback__))
|
||||||
self.bot._last_exception = exception_log
|
self.bot._last_exception = exception_log
|
||||||
|
|
||||||
failed_packages.append(inline(name))
|
failed_packages.append(inline(name))
|
||||||
|
|
||||||
if loaded_packages:
|
if loaded_packages:
|
||||||
fmt = "Package{plural} {packs} {other} reloaded."
|
fmt = "Package{plural} {packs} {other} reloaded."
|
||||||
formed = self.get_package_strings(loaded_packages, fmt, ('was', 'were'))
|
formed = self.get_package_strings(loaded_packages, fmt, ("was", "were"))
|
||||||
await ctx.send(_(formed))
|
await ctx.send(_(formed))
|
||||||
|
|
||||||
if failed_packages:
|
if failed_packages:
|
||||||
fmt = ("Failed to reload package{plural} {packs}. Check your "
|
fmt = ("Failed to reload package{plural} {packs}. Check your " "logs for details")
|
||||||
"logs for details")
|
|
||||||
formed = self.get_package_strings(failed_packages, fmt)
|
formed = self.get_package_strings(failed_packages, fmt)
|
||||||
await ctx.send(_(formed))
|
await ctx.send(_(formed))
|
||||||
|
|
||||||
if notfound_packages:
|
if notfound_packages:
|
||||||
fmt = 'The package{plural} {packs} {other} not found in any cog path.'
|
fmt = "The package{plural} {packs} {other} not found in any cog path."
|
||||||
formed = self.get_package_strings(notfound_packages, fmt, ('was', 'were'))
|
formed = self.get_package_strings(notfound_packages, fmt, ("was", "were"))
|
||||||
await ctx.send(_(formed))
|
await ctx.send(_(formed))
|
||||||
|
|
||||||
def get_package_strings(self, packages: list, fmt: str, other: tuple=None):
|
def get_package_strings(self, packages: list, fmt: str, other: tuple = None):
|
||||||
"""
|
"""
|
||||||
Gets the strings needed for the load, unload and reload commands
|
Gets the strings needed for the load, unload and reload commands
|
||||||
"""
|
"""
|
||||||
if other is None:
|
if other is None:
|
||||||
other = ('', '')
|
other = ("", "")
|
||||||
plural = 's' if len(packages) > 1 else ''
|
plural = "s" if len(packages) > 1 else ""
|
||||||
use_and, other = ('', other[0]) if len(packages) == 1 else (' and ', other[1])
|
use_and, other = ("", other[0]) if len(packages) == 1 else (" and ", other[1])
|
||||||
packages_string = ', '.join(packages[:-1]) + use_and + packages[-1]
|
packages_string = ", ".join(packages[:-1]) + use_and + packages[-1]
|
||||||
|
|
||||||
form = {'plural': plural,
|
form = {"plural": plural, "packs": packages_string, "other": other}
|
||||||
'packs' : packages_string,
|
|
||||||
'other' : other
|
|
||||||
}
|
|
||||||
final_string = fmt.format(**form)
|
final_string = fmt.format(**form)
|
||||||
return final_string
|
return final_string
|
||||||
|
|
||||||
@commands.command(name="shutdown")
|
@commands.command(name="shutdown")
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def _shutdown(self, ctx, silently: bool=False):
|
async def _shutdown(self, ctx, silently: bool = False):
|
||||||
"""Shuts down the bot"""
|
"""Shuts down the bot"""
|
||||||
wave = "\N{WAVING HAND SIGN}"
|
wave = "\N{WAVING HAND SIGN}"
|
||||||
skin = "\N{EMOJI MODIFIER FITZPATRICK TYPE-3}"
|
skin = "\N{EMOJI MODIFIER FITZPATRICK TYPE-3}"
|
||||||
@ -500,7 +486,7 @@ class Core:
|
|||||||
|
|
||||||
@commands.command(name="restart")
|
@commands.command(name="restart")
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def _restart(self, ctx, silently: bool=False):
|
async def _restart(self, ctx, silently: bool = False):
|
||||||
"""Attempts to restart Red
|
"""Attempts to restart Red
|
||||||
|
|
||||||
Makes Red quit with exit code 26
|
Makes Red quit with exit code 26
|
||||||
@ -515,7 +501,7 @@ class Core:
|
|||||||
|
|
||||||
def cleanup_and_refresh_modules(self, module_name: str):
|
def cleanup_and_refresh_modules(self, module_name: str):
|
||||||
"""Interally reloads modules so that changes are detected"""
|
"""Interally reloads modules so that changes are detected"""
|
||||||
splitted = module_name.split('.')
|
splitted = module_name.split(".")
|
||||||
|
|
||||||
def maybe_reload(new_name):
|
def maybe_reload(new_name):
|
||||||
try:
|
try:
|
||||||
@ -553,9 +539,11 @@ class Core:
|
|||||||
"Mod role: {}\n"
|
"Mod role: {}\n"
|
||||||
"Locale: {}"
|
"Locale: {}"
|
||||||
"".format(
|
"".format(
|
||||||
ctx.bot.user.name, " ".join(prefixes),
|
ctx.bot.user.name,
|
||||||
|
" ".join(prefixes),
|
||||||
admin_role.name if admin_role else "Not set",
|
admin_role.name if admin_role else "Not set",
|
||||||
mod_role.name if mod_role else "Not set", locale
|
mod_role.name if mod_role else "Not set",
|
||||||
|
locale,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
await ctx.send(box(settings))
|
await ctx.send(box(settings))
|
||||||
@ -588,9 +576,13 @@ class Core:
|
|||||||
try:
|
try:
|
||||||
await ctx.bot.user.edit(avatar=data)
|
await ctx.bot.user.edit(avatar=data)
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
await ctx.send(_("Failed. Remember that you can edit my avatar "
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"Failed. Remember that you can edit my avatar "
|
||||||
"up to two times a hour. The URL must be a "
|
"up to two times a hour. The URL must be a "
|
||||||
"direct link to a JPG / PNG."))
|
"direct link to a JPG / PNG."
|
||||||
|
)
|
||||||
|
)
|
||||||
except discord.InvalidArgument:
|
except discord.InvalidArgument:
|
||||||
await ctx.send(_("JPG / PNG format only."))
|
await ctx.send(_("JPG / PNG format only."))
|
||||||
else:
|
else:
|
||||||
@ -599,26 +591,24 @@ class Core:
|
|||||||
@_set.command(name="game")
|
@_set.command(name="game")
|
||||||
@checks.bot_in_a_guild()
|
@checks.bot_in_a_guild()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def _game(self, ctx, *, game: str=None):
|
async def _game(self, ctx, *, game: str = None):
|
||||||
"""Sets Red's playing status"""
|
"""Sets Red's playing status"""
|
||||||
|
|
||||||
if game:
|
if game:
|
||||||
game = discord.Game(name=game)
|
game = discord.Game(name=game)
|
||||||
else:
|
else:
|
||||||
game = None
|
game = None
|
||||||
status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 \
|
status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 else discord.Status.online
|
||||||
else discord.Status.online
|
|
||||||
await ctx.bot.change_presence(status=status, activity=game)
|
await ctx.bot.change_presence(status=status, activity=game)
|
||||||
await ctx.send(_("Game set."))
|
await ctx.send(_("Game set."))
|
||||||
|
|
||||||
@_set.command(name="listening")
|
@_set.command(name="listening")
|
||||||
@checks.bot_in_a_guild()
|
@checks.bot_in_a_guild()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def _listening(self, ctx, *, listening: str=None):
|
async def _listening(self, ctx, *, listening: str = None):
|
||||||
"""Sets Red's listening status"""
|
"""Sets Red's listening status"""
|
||||||
|
|
||||||
status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 \
|
status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 else discord.Status.online
|
||||||
else discord.Status.online
|
|
||||||
if listening:
|
if listening:
|
||||||
activity = discord.Activity(name=listening, type=discord.ActivityType.listening)
|
activity = discord.Activity(name=listening, type=discord.ActivityType.listening)
|
||||||
else:
|
else:
|
||||||
@ -629,11 +619,10 @@ class Core:
|
|||||||
@_set.command(name="watching")
|
@_set.command(name="watching")
|
||||||
@checks.bot_in_a_guild()
|
@checks.bot_in_a_guild()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def _watching(self, ctx, *, watching: str=None):
|
async def _watching(self, ctx, *, watching: str = None):
|
||||||
"""Sets Red's watching status"""
|
"""Sets Red's watching status"""
|
||||||
|
|
||||||
status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 \
|
status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 else discord.Status.online
|
||||||
else discord.Status.online
|
|
||||||
if watching:
|
if watching:
|
||||||
activity = discord.Activity(name=watching, type=discord.ActivityType.watching)
|
activity = discord.Activity(name=watching, type=discord.ActivityType.watching)
|
||||||
else:
|
else:
|
||||||
@ -658,7 +647,7 @@ class Core:
|
|||||||
"online": discord.Status.online,
|
"online": discord.Status.online,
|
||||||
"idle": discord.Status.idle,
|
"idle": discord.Status.idle,
|
||||||
"dnd": discord.Status.dnd,
|
"dnd": discord.Status.dnd,
|
||||||
"invisible": discord.Status.invisible
|
"invisible": discord.Status.invisible,
|
||||||
}
|
}
|
||||||
|
|
||||||
game = ctx.bot.guilds[0].me.activity if len(ctx.bot.guilds) > 0 else None
|
game = ctx.bot.guilds[0].me.activity if len(ctx.bot.guilds) > 0 else None
|
||||||
@ -677,8 +666,7 @@ class Core:
|
|||||||
"""Sets Red's streaming status
|
"""Sets Red's streaming status
|
||||||
Leaving both streamer and stream_title empty will clear it."""
|
Leaving both streamer and stream_title empty will clear it."""
|
||||||
|
|
||||||
status = ctx.bot.guilds[0].me.status \
|
status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 else None
|
||||||
if len(ctx.bot.guilds) > 0 else None
|
|
||||||
|
|
||||||
if stream_title:
|
if stream_title:
|
||||||
stream_title = stream_title.strip()
|
stream_title = stream_title.strip()
|
||||||
@ -700,23 +688,28 @@ class Core:
|
|||||||
try:
|
try:
|
||||||
await ctx.bot.user.edit(username=username)
|
await ctx.bot.user.edit(username=username)
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
await ctx.send(_("Failed to change name. Remember that you can "
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"Failed to change name. Remember that you can "
|
||||||
"only do it up to 2 times an hour. Use "
|
"only do it up to 2 times an hour. Use "
|
||||||
"nicknames if you need frequent changes. "
|
"nicknames if you need frequent changes. "
|
||||||
"`{}set nickname`").format(ctx.prefix))
|
"`{}set nickname`"
|
||||||
|
).format(
|
||||||
|
ctx.prefix
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Done."))
|
await ctx.send(_("Done."))
|
||||||
|
|
||||||
@_set.command(name="nickname")
|
@_set.command(name="nickname")
|
||||||
@checks.admin()
|
@checks.admin()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def _nickname(self, ctx, *, nickname: str=None):
|
async def _nickname(self, ctx, *, nickname: str = None):
|
||||||
"""Sets Red's nickname"""
|
"""Sets Red's nickname"""
|
||||||
try:
|
try:
|
||||||
await ctx.guild.me.edit(nick=nickname)
|
await ctx.guild.me.edit(nick=nickname)
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
await ctx.send(_("I lack the permissions to change my own "
|
await ctx.send(_("I lack the permissions to change my own " "nickname."))
|
||||||
"nickname."))
|
|
||||||
else:
|
else:
|
||||||
await ctx.send("Done.")
|
await ctx.send("Done.")
|
||||||
|
|
||||||
@ -748,6 +741,7 @@ class Core:
|
|||||||
@commands.cooldown(1, 60 * 10, commands.BucketType.default)
|
@commands.cooldown(1, 60 * 10, commands.BucketType.default)
|
||||||
async def owner(self, ctx):
|
async def owner(self, ctx):
|
||||||
"""Sets Red's main owner"""
|
"""Sets Red's main owner"""
|
||||||
|
|
||||||
def check(m):
|
def check(m):
|
||||||
return m.author == ctx.author and m.channel == ctx.channel
|
return m.author == ctx.author and m.channel == ctx.channel
|
||||||
|
|
||||||
@ -759,20 +753,22 @@ class Core:
|
|||||||
|
|
||||||
for i in range(length):
|
for i in range(length):
|
||||||
token += random.choice(chars)
|
token += random.choice(chars)
|
||||||
log.info("{0} ({0.id}) requested to be set as owner."
|
log.info("{0} ({0.id}) requested to be set as owner." "".format(ctx.author))
|
||||||
"".format(ctx.author))
|
|
||||||
print(_("\nVerification token:"))
|
print(_("\nVerification token:"))
|
||||||
print(token)
|
print(token)
|
||||||
|
|
||||||
await ctx.send(_("Remember:\n") + OWNER_DISCLAIMER)
|
await ctx.send(_("Remember:\n") + OWNER_DISCLAIMER)
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
await ctx.send(_("I have printed a one-time token in the console. "
|
await ctx.send(
|
||||||
"Copy and paste it here to confirm you are the owner."))
|
_(
|
||||||
|
"I have printed a one-time token in the console. "
|
||||||
|
"Copy and paste it here to confirm you are the owner."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message = await ctx.bot.wait_for("message", check=check,
|
message = await ctx.bot.wait_for("message", check=check, timeout=60)
|
||||||
timeout=60)
|
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
self.owner.reset_cooldown(ctx)
|
self.owner.reset_cooldown(ctx)
|
||||||
await ctx.send(_("The set owner request has timed out."))
|
await ctx.send(_("The set owner request has timed out."))
|
||||||
@ -798,10 +794,15 @@ class Core:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Please use that command in DM. Since users probably saw your token,"
|
_(
|
||||||
|
"Please use that command in DM. Since users probably saw your token,"
|
||||||
" it is recommended to reset it right now. Go to the following link and"
|
" it is recommended to reset it right now. Go to the following link and"
|
||||||
" select `Reveal Token` and `Generate a new token?`."
|
" select `Reveal Token` and `Generate a new token?`."
|
||||||
"\n\nhttps://discordapp.com/developers/applications/me/{}").format(self.bot.user.id))
|
"\n\nhttps://discordapp.com/developers/applications/me/{}"
|
||||||
|
).format(
|
||||||
|
self.bot.user.id
|
||||||
|
)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
await ctx.bot.db.token.set(token)
|
await ctx.bot.db.token.set(token)
|
||||||
@ -854,9 +855,7 @@ class Core:
|
|||||||
locale_list = sorted(set([loc.stem for loc in list(red_path.glob("**/*.po"))]))
|
locale_list = sorted(set([loc.stem for loc in list(red_path.glob("**/*.po"))]))
|
||||||
pages = pagify("\n".join(locale_list))
|
pages = pagify("\n".join(locale_list))
|
||||||
|
|
||||||
await ctx.send_interactive(
|
await ctx.send_interactive(pages, box_lang="Available Locales:")
|
||||||
pages, box_lang="Available Locales:"
|
|
||||||
)
|
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
@ -864,9 +863,11 @@ class Core:
|
|||||||
"""Creates a backup of all data for the instance."""
|
"""Creates a backup of all data for the instance."""
|
||||||
from redbot.core.data_manager import basic_config, instance_name
|
from redbot.core.data_manager import basic_config, instance_name
|
||||||
from redbot.core.drivers.red_json import JSON
|
from redbot.core.drivers.red_json import JSON
|
||||||
|
|
||||||
data_dir = Path(basic_config["DATA_PATH"])
|
data_dir = Path(basic_config["DATA_PATH"])
|
||||||
if basic_config["STORAGE_TYPE"] == "MongoDB":
|
if basic_config["STORAGE_TYPE"] == "MongoDB":
|
||||||
from redbot.core.drivers.red_mongo import Mongo
|
from redbot.core.drivers.red_mongo import Mongo
|
||||||
|
|
||||||
m = Mongo("Core", **basic_config["STORAGE_DETAILS"])
|
m = Mongo("Core", **basic_config["STORAGE_DETAILS"])
|
||||||
db = m.db
|
db = m.db
|
||||||
collection_names = await db.collection_names(include_system_collections=False)
|
collection_names = await db.collection_names(include_system_collections=False)
|
||||||
@ -891,9 +892,9 @@ class Core:
|
|||||||
os.chdir(str(data_dir.parent))
|
os.chdir(str(data_dir.parent))
|
||||||
with tarfile.open(str(backup_file), "w:gz") as tar:
|
with tarfile.open(str(backup_file), "w:gz") as tar:
|
||||||
tar.add(data_dir.stem)
|
tar.add(data_dir.stem)
|
||||||
await ctx.send(_("A backup has been made of this instance. It is at {}.").format(
|
await ctx.send(
|
||||||
backup_file
|
_("A backup has been made of this instance. It is at {}.").format(backup_file)
|
||||||
))
|
)
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("That directory doesn't seem to exist..."))
|
await ctx.send(_("That directory doesn't seem to exist..."))
|
||||||
|
|
||||||
@ -902,8 +903,7 @@ class Core:
|
|||||||
async def contact(self, ctx, *, message: str):
|
async def contact(self, ctx, *, message: str):
|
||||||
"""Sends a message to the owner"""
|
"""Sends a message to the owner"""
|
||||||
guild = ctx.message.guild
|
guild = ctx.message.guild
|
||||||
owner = discord.utils.get(ctx.bot.get_all_members(),
|
owner = discord.utils.get(ctx.bot.get_all_members(), id=ctx.bot.owner_id)
|
||||||
id=ctx.bot.owner_id)
|
|
||||||
author = ctx.message.author
|
author = ctx.message.author
|
||||||
footer = _("User ID: {}").format(author.id)
|
footer = _("User ID: {}").format(author.id)
|
||||||
|
|
||||||
@ -916,12 +916,11 @@ class Core:
|
|||||||
# We need to grab the DM command prefix (global)
|
# We need to grab the DM command prefix (global)
|
||||||
# Since it can also be set through cli flags, bot.db is not a reliable
|
# Since it can also be set through cli flags, bot.db is not a reliable
|
||||||
# source. So we'll just mock a DM message instead.
|
# source. So we'll just mock a DM message instead.
|
||||||
fake_message = namedtuple('Message', 'guild')
|
fake_message = namedtuple("Message", "guild")
|
||||||
prefixes = await ctx.bot.command_prefix(ctx.bot, fake_message(guild=None))
|
prefixes = await ctx.bot.command_prefix(ctx.bot, fake_message(guild=None))
|
||||||
prefix = prefixes[0]
|
prefix = prefixes[0]
|
||||||
|
|
||||||
content = _("Use `{}dm {} <text>` to reply to this user"
|
content = _("Use `{}dm {} <text>` to reply to this user" "").format(prefix, author.id)
|
||||||
"").format(prefix, author.id)
|
|
||||||
|
|
||||||
description = _("Sent by {} {}").format(author, source)
|
description = _("Sent by {} {}").format(author, source)
|
||||||
|
|
||||||
@ -941,21 +940,21 @@ class Core:
|
|||||||
try:
|
try:
|
||||||
await owner.send(content, embed=e)
|
await owner.send(content, embed=e)
|
||||||
except discord.InvalidArgument:
|
except discord.InvalidArgument:
|
||||||
await ctx.send(_("I cannot send your message, I'm unable to find "
|
await ctx.send(
|
||||||
"my owner... *sigh*"))
|
_("I cannot send your message, I'm unable to find " "my owner... *sigh*")
|
||||||
|
)
|
||||||
except:
|
except:
|
||||||
await ctx.send(_("I'm unable to deliver your message. Sorry."))
|
await ctx.send(_("I'm unable to deliver your message. Sorry."))
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Your message has been sent."))
|
await ctx.send(_("Your message has been sent."))
|
||||||
else:
|
else:
|
||||||
msg_text = (
|
msg_text = ("{}\nMessage:\n\n{}\n{}".format(description, message, footer))
|
||||||
"{}\nMessage:\n\n{}\n{}".format(description, message, footer)
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
await owner.send("{}\n{}".format(content, box(msg_text)))
|
await owner.send("{}\n{}".format(content, box(msg_text)))
|
||||||
except discord.InvalidArgument:
|
except discord.InvalidArgument:
|
||||||
await ctx.send(_("I cannot send your message, I'm unable to find "
|
await ctx.send(
|
||||||
"my owner... *sigh*"))
|
_("I cannot send your message, I'm unable to find " "my owner... *sigh*")
|
||||||
|
)
|
||||||
except:
|
except:
|
||||||
await ctx.send(_("I'm unable to deliver your message. Sorry."))
|
await ctx.send(_("I'm unable to deliver your message. Sorry."))
|
||||||
else:
|
else:
|
||||||
@ -970,15 +969,18 @@ class Core:
|
|||||||
To get a user id enable 'developer mode' in Discord's
|
To get a user id enable 'developer mode' in Discord's
|
||||||
settings, 'appearance' tab. Then right click a user
|
settings, 'appearance' tab. Then right click a user
|
||||||
and copy their id"""
|
and copy their id"""
|
||||||
destination = discord.utils.get(ctx.bot.get_all_members(),
|
destination = discord.utils.get(ctx.bot.get_all_members(), id=user_id)
|
||||||
id=user_id)
|
|
||||||
if destination is None:
|
if destination is None:
|
||||||
await ctx.send(_("Invalid ID or user not found. You can only "
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"Invalid ID or user not found. You can only "
|
||||||
"send messages to people I share a server "
|
"send messages to people I share a server "
|
||||||
"with."))
|
"with."
|
||||||
|
)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
fake_message = namedtuple('Message', 'guild')
|
fake_message = namedtuple("Message", "guild")
|
||||||
prefixes = await ctx.bot.command_prefix(ctx.bot, fake_message(guild=None))
|
prefixes = await ctx.bot.command_prefix(ctx.bot, fake_message(guild=None))
|
||||||
prefix = prefixes[0]
|
prefix = prefixes[0]
|
||||||
description = _("Owner of {}").format(ctx.bot.user)
|
description = _("Owner of {}").format(ctx.bot.user)
|
||||||
@ -995,8 +997,9 @@ class Core:
|
|||||||
try:
|
try:
|
||||||
await destination.send(embed=e)
|
await destination.send(embed=e)
|
||||||
except:
|
except:
|
||||||
await ctx.send(_("Sorry, I couldn't deliver your message "
|
await ctx.send(
|
||||||
"to {}").format(destination))
|
_("Sorry, I couldn't deliver your message " "to {}").format(destination)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Message delivered to {}").format(destination))
|
await ctx.send(_("Message delivered to {}").format(destination))
|
||||||
else:
|
else:
|
||||||
@ -1004,8 +1007,9 @@ class Core:
|
|||||||
try:
|
try:
|
||||||
await destination.send("{}\n{}".format(box(response), content))
|
await destination.send("{}\n{}".format(box(response), content))
|
||||||
except:
|
except:
|
||||||
await ctx.send(_("Sorry, I couldn't deliver your message "
|
await ctx.send(
|
||||||
"to {}").format(destination))
|
_("Sorry, I couldn't deliver your message " "to {}").format(destination)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Message delivered to {}").format(destination))
|
await ctx.send(_("Message delivered to {}").format(destination))
|
||||||
|
|
||||||
@ -1018,7 +1022,7 @@ class Core:
|
|||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
await ctx.send_help()
|
await ctx.send_help()
|
||||||
|
|
||||||
@whitelist.command(name='add')
|
@whitelist.command(name="add")
|
||||||
async def whitelist_add(self, ctx, user: discord.User):
|
async def whitelist_add(self, ctx, user: discord.User):
|
||||||
"""
|
"""
|
||||||
Adds a user to the whitelist.
|
Adds a user to the whitelist.
|
||||||
@ -1029,7 +1033,7 @@ class Core:
|
|||||||
|
|
||||||
await ctx.send(_("User added to whitelist."))
|
await ctx.send(_("User added to whitelist."))
|
||||||
|
|
||||||
@whitelist.command(name='list')
|
@whitelist.command(name="list")
|
||||||
async def whitelist_list(self, ctx):
|
async def whitelist_list(self, ctx):
|
||||||
"""
|
"""
|
||||||
Lists whitelisted users.
|
Lists whitelisted users.
|
||||||
@ -1043,7 +1047,7 @@ class Core:
|
|||||||
for page in pagify(msg):
|
for page in pagify(msg):
|
||||||
await ctx.send(box(page))
|
await ctx.send(box(page))
|
||||||
|
|
||||||
@whitelist.command(name='remove')
|
@whitelist.command(name="remove")
|
||||||
async def whitelist_remove(self, ctx, user: discord.User):
|
async def whitelist_remove(self, ctx, user: discord.User):
|
||||||
"""
|
"""
|
||||||
Removes user from whitelist.
|
Removes user from whitelist.
|
||||||
@ -1060,7 +1064,7 @@ class Core:
|
|||||||
else:
|
else:
|
||||||
await ctx.send(_("User was not in the whitelist."))
|
await ctx.send(_("User was not in the whitelist."))
|
||||||
|
|
||||||
@whitelist.command(name='clear')
|
@whitelist.command(name="clear")
|
||||||
async def whitelist_clear(self, ctx):
|
async def whitelist_clear(self, ctx):
|
||||||
"""
|
"""
|
||||||
Clears the whitelist.
|
Clears the whitelist.
|
||||||
@ -1077,7 +1081,7 @@ class Core:
|
|||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
await ctx.send_help()
|
await ctx.send_help()
|
||||||
|
|
||||||
@blacklist.command(name='add')
|
@blacklist.command(name="add")
|
||||||
async def blacklist_add(self, ctx, user: discord.User):
|
async def blacklist_add(self, ctx, user: discord.User):
|
||||||
"""
|
"""
|
||||||
Adds a user to the blacklist.
|
Adds a user to the blacklist.
|
||||||
@ -1092,7 +1096,7 @@ class Core:
|
|||||||
|
|
||||||
await ctx.send(_("User added to blacklist."))
|
await ctx.send(_("User added to blacklist."))
|
||||||
|
|
||||||
@blacklist.command(name='list')
|
@blacklist.command(name="list")
|
||||||
async def blacklist_list(self, ctx):
|
async def blacklist_list(self, ctx):
|
||||||
"""
|
"""
|
||||||
Lists blacklisted users.
|
Lists blacklisted users.
|
||||||
@ -1106,7 +1110,7 @@ class Core:
|
|||||||
for page in pagify(msg):
|
for page in pagify(msg):
|
||||||
await ctx.send(box(page))
|
await ctx.send(box(page))
|
||||||
|
|
||||||
@blacklist.command(name='remove')
|
@blacklist.command(name="remove")
|
||||||
async def blacklist_remove(self, ctx, user: discord.User):
|
async def blacklist_remove(self, ctx, user: discord.User):
|
||||||
"""
|
"""
|
||||||
Removes user from blacklist.
|
Removes user from blacklist.
|
||||||
@ -1123,7 +1127,7 @@ class Core:
|
|||||||
else:
|
else:
|
||||||
await ctx.send(_("User was not in the blacklist."))
|
await ctx.send(_("User was not in the blacklist."))
|
||||||
|
|
||||||
@blacklist.command(name='clear')
|
@blacklist.command(name="clear")
|
||||||
async def blacklist_clear(self, ctx):
|
async def blacklist_clear(self, ctx):
|
||||||
"""
|
"""
|
||||||
Clears the blacklist.
|
Clears the blacklist.
|
||||||
|
|||||||
@ -14,9 +14,15 @@ from .utils import TYPE_CHECKING
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from . import Config
|
from . import Config
|
||||||
|
|
||||||
__all__ = ['load_basic_configuration', 'cog_data_path', 'core_data_path',
|
__all__ = [
|
||||||
'load_bundled_data', 'bundled_data_path', 'storage_details',
|
"load_basic_configuration",
|
||||||
'storage_type']
|
"cog_data_path",
|
||||||
|
"core_data_path",
|
||||||
|
"load_bundled_data",
|
||||||
|
"bundled_data_path",
|
||||||
|
"storage_details",
|
||||||
|
"storage_type",
|
||||||
|
]
|
||||||
|
|
||||||
log = logging.getLogger("red.data_manager")
|
log = logging.getLogger("red.data_manager")
|
||||||
|
|
||||||
@ -25,20 +31,16 @@ basic_config = None
|
|||||||
|
|
||||||
instance_name = None
|
instance_name = None
|
||||||
|
|
||||||
basic_config_default = {
|
basic_config_default = {"DATA_PATH": None, "COG_PATH_APPEND": "cogs", "CORE_PATH_APPEND": "core"}
|
||||||
"DATA_PATH": None,
|
|
||||||
"COG_PATH_APPEND": "cogs",
|
|
||||||
"CORE_PATH_APPEND": "core"
|
|
||||||
}
|
|
||||||
|
|
||||||
config_dir = None
|
config_dir = None
|
||||||
appdir = appdirs.AppDirs("Red-DiscordBot")
|
appdir = appdirs.AppDirs("Red-DiscordBot")
|
||||||
if sys.platform == 'linux':
|
if sys.platform == "linux":
|
||||||
if 0 < os.getuid() < 1000:
|
if 0 < os.getuid() < 1000:
|
||||||
config_dir = Path(appdir.site_data_dir)
|
config_dir = Path(appdir.site_data_dir)
|
||||||
if not config_dir:
|
if not config_dir:
|
||||||
config_dir = Path(appdir.user_config_dir)
|
config_dir = Path(appdir.user_config_dir)
|
||||||
config_file = config_dir / 'config.json'
|
config_file = config_dir / "config.json"
|
||||||
|
|
||||||
|
|
||||||
def load_basic_configuration(instance_name_: str):
|
def load_basic_configuration(instance_name_: str):
|
||||||
@ -67,20 +69,23 @@ def load_basic_configuration(instance_name_: str):
|
|||||||
config = jsonio._load_json()
|
config = jsonio._load_json()
|
||||||
basic_config = config[instance_name]
|
basic_config = config[instance_name]
|
||||||
except (FileNotFoundError, KeyError):
|
except (FileNotFoundError, KeyError):
|
||||||
print("You need to configure the bot instance using `redbot-setup`"
|
print(
|
||||||
" prior to running the bot.")
|
"You need to configure the bot instance using `redbot-setup`"
|
||||||
|
" prior to running the bot."
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def _base_data_path() -> Path:
|
def _base_data_path() -> Path:
|
||||||
if basic_config is None:
|
if basic_config is None:
|
||||||
raise RuntimeError("You must load the basic config before you"
|
raise RuntimeError(
|
||||||
" can get the base data path.")
|
"You must load the basic config before you" " can get the base data path."
|
||||||
path = basic_config['DATA_PATH']
|
)
|
||||||
|
path = basic_config["DATA_PATH"]
|
||||||
return Path(path).resolve()
|
return Path(path).resolve()
|
||||||
|
|
||||||
|
|
||||||
def cog_data_path(cog_instance=None, raw_name: str=None) -> Path:
|
def cog_data_path(cog_instance=None, raw_name: str = None) -> Path:
|
||||||
"""Gets the base cog data path. If you want to get the folder with
|
"""Gets the base cog data path. If you want to get the folder with
|
||||||
which to store your own cog's data please pass in an instance
|
which to store your own cog's data please pass in an instance
|
||||||
of your cog class.
|
of your cog class.
|
||||||
@ -104,9 +109,10 @@ def cog_data_path(cog_instance=None, raw_name: str=None) -> Path:
|
|||||||
try:
|
try:
|
||||||
base_data_path = Path(_base_data_path())
|
base_data_path = Path(_base_data_path())
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
raise RuntimeError("You must load the basic config before you"
|
raise RuntimeError(
|
||||||
" can get the cog data path.") from e
|
"You must load the basic config before you" " can get the cog data path."
|
||||||
cog_path = base_data_path / basic_config['COG_PATH_APPEND']
|
) from e
|
||||||
|
cog_path = base_data_path / basic_config["COG_PATH_APPEND"]
|
||||||
|
|
||||||
if raw_name is not None:
|
if raw_name is not None:
|
||||||
cog_path = cog_path / raw_name
|
cog_path = cog_path / raw_name
|
||||||
@ -121,9 +127,10 @@ def core_data_path() -> Path:
|
|||||||
try:
|
try:
|
||||||
base_data_path = Path(_base_data_path())
|
base_data_path = Path(_base_data_path())
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
raise RuntimeError("You must load the basic config before you"
|
raise RuntimeError(
|
||||||
" can get the core data path.") from e
|
"You must load the basic config before you" " can get the core data path."
|
||||||
core_path = base_data_path / basic_config['CORE_PATH_APPEND']
|
) from e
|
||||||
|
core_path = base_data_path / basic_config["CORE_PATH_APPEND"]
|
||||||
core_path.mkdir(exist_ok=True, parents=True)
|
core_path.mkdir(exist_ok=True, parents=True)
|
||||||
|
|
||||||
return core_path.resolve()
|
return core_path.resolve()
|
||||||
@ -145,15 +152,13 @@ def _find_data_files(init_location: str) -> (Path, List[Path]):
|
|||||||
if not init_file.is_file():
|
if not init_file.is_file():
|
||||||
return []
|
return []
|
||||||
|
|
||||||
package_folder = init_file.parent.resolve() / 'data'
|
package_folder = init_file.parent.resolve() / "data"
|
||||||
if not package_folder.is_dir():
|
if not package_folder.is_dir():
|
||||||
return []
|
return []
|
||||||
|
|
||||||
all_files = list(package_folder.rglob("*"))
|
all_files = list(package_folder.rglob("*"))
|
||||||
|
|
||||||
return package_folder, [p.resolve()
|
return package_folder, [p.resolve() for p in all_files if p.is_file()]
|
||||||
for p in all_files
|
|
||||||
if p.is_file()]
|
|
||||||
|
|
||||||
|
|
||||||
def _compare_and_copy(to_copy: List[Path], bundled_data_dir: Path, cog_data_dir: Path):
|
def _compare_and_copy(to_copy: List[Path], bundled_data_dir: Path, cog_data_dir: Path):
|
||||||
@ -181,27 +186,24 @@ def _compare_and_copy(to_copy: List[Path], bundled_data_dir: Path, cog_data_dir:
|
|||||||
yield block
|
yield block
|
||||||
block = afile.read(blocksize)
|
block = afile.read(blocksize)
|
||||||
|
|
||||||
lookup = {p: cog_data_dir.joinpath(p.relative_to(bundled_data_dir))
|
lookup = {p: cog_data_dir.joinpath(p.relative_to(bundled_data_dir)) for p in to_copy}
|
||||||
for p in to_copy}
|
|
||||||
|
|
||||||
for orig, poss_existing in lookup.items():
|
for orig, poss_existing in lookup.items():
|
||||||
if not poss_existing.is_file():
|
if not poss_existing.is_file():
|
||||||
poss_existing.parent.mkdir(exist_ok=True, parents=True)
|
poss_existing.parent.mkdir(exist_ok=True, parents=True)
|
||||||
exists_checksum = None
|
exists_checksum = None
|
||||||
else:
|
else:
|
||||||
exists_checksum = hash_bytestr_iter(file_as_blockiter(
|
exists_checksum = hash_bytestr_iter(
|
||||||
poss_existing.open('rb')), hashlib.sha256())
|
file_as_blockiter(poss_existing.open("rb")), hashlib.sha256()
|
||||||
|
)
|
||||||
|
|
||||||
orig_checksum = ...
|
orig_checksum = ...
|
||||||
if exists_checksum is not None:
|
if exists_checksum is not None:
|
||||||
orig_checksum = hash_bytestr_iter(file_as_blockiter(
|
orig_checksum = hash_bytestr_iter(file_as_blockiter(orig.open("rb")), hashlib.sha256())
|
||||||
orig.open('rb')), hashlib.sha256())
|
|
||||||
|
|
||||||
if exists_checksum != orig_checksum:
|
if exists_checksum != orig_checksum:
|
||||||
shutil.copy(str(orig), str(poss_existing))
|
shutil.copy(str(orig), str(poss_existing))
|
||||||
log.debug("Copying {} to {}".format(
|
log.debug("Copying {} to {}".format(orig, poss_existing))
|
||||||
orig, poss_existing
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
def load_bundled_data(cog_instance, init_location: str):
|
def load_bundled_data(cog_instance, init_location: str):
|
||||||
@ -233,7 +235,7 @@ def load_bundled_data(cog_instance, init_location: str):
|
|||||||
"""
|
"""
|
||||||
bundled_data_folder, to_copy = _find_data_files(init_location)
|
bundled_data_folder, to_copy = _find_data_files(init_location)
|
||||||
|
|
||||||
cog_data_folder = cog_data_path(cog_instance) / 'bundled_data'
|
cog_data_folder = cog_data_path(cog_instance) / "bundled_data"
|
||||||
|
|
||||||
_compare_and_copy(to_copy, bundled_data_folder, cog_data_folder)
|
_compare_and_copy(to_copy, bundled_data_folder, cog_data_folder)
|
||||||
|
|
||||||
@ -264,12 +266,10 @@ def bundled_data_path(cog_instance) -> Path:
|
|||||||
If no bundled data folder exists or if it hasn't been loaded yet.
|
If no bundled data folder exists or if it hasn't been loaded yet.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
bundled_path = cog_data_path(cog_instance) / 'bundled_data'
|
bundled_path = cog_data_path(cog_instance) / "bundled_data"
|
||||||
|
|
||||||
if not bundled_path.is_dir():
|
if not bundled_path.is_dir():
|
||||||
raise FileNotFoundError("No such directory {}".format(
|
raise FileNotFoundError("No such directory {}".format(bundled_path))
|
||||||
bundled_path
|
|
||||||
))
|
|
||||||
|
|
||||||
return bundled_path
|
return bundled_path
|
||||||
|
|
||||||
@ -282,9 +282,9 @@ def storage_type() -> str:
|
|||||||
str
|
str
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return basic_config['STORAGE_TYPE']
|
return basic_config["STORAGE_TYPE"]
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
raise RuntimeError('Bot basic config has not been loaded yet.') from e
|
raise RuntimeError("Bot basic config has not been loaded yet.") from e
|
||||||
|
|
||||||
|
|
||||||
def storage_details() -> dict:
|
def storage_details() -> dict:
|
||||||
@ -297,6 +297,6 @@ def storage_details() -> dict:
|
|||||||
dict
|
dict
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return basic_config['STORAGE_DETAILS']
|
return basic_config["STORAGE_DETAILS"]
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
raise RuntimeError('Bot basic config has not been loaded yet.') from e
|
raise RuntimeError("Bot basic config has not been loaded yet.") from e
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import discord
|
|||||||
from . import checks, commands
|
from . import checks, commands
|
||||||
from .i18n import Translator
|
from .i18n import Translator
|
||||||
from .utils.chat_formatting import box, pagify
|
from .utils.chat_formatting import box, pagify
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Notice:
|
Notice:
|
||||||
|
|
||||||
@ -32,11 +33,11 @@ class Dev:
|
|||||||
def cleanup_code(content):
|
def cleanup_code(content):
|
||||||
"""Automatically removes code blocks from the code."""
|
"""Automatically removes code blocks from the code."""
|
||||||
# remove ```py\n```
|
# remove ```py\n```
|
||||||
if content.startswith('```') and content.endswith('```'):
|
if content.startswith("```") and content.endswith("```"):
|
||||||
return '\n'.join(content.split('\n')[1:-1])
|
return "\n".join(content.split("\n")[1:-1])
|
||||||
|
|
||||||
# remove `foo`
|
# remove `foo`
|
||||||
return content.strip('` \n')
|
return content.strip("` \n")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_syntax_error(e):
|
def get_syntax_error(e):
|
||||||
@ -45,11 +46,10 @@ class Dev:
|
|||||||
Returns a string representation of the error formatted as a codeblock.
|
Returns a string representation of the error formatted as a codeblock.
|
||||||
"""
|
"""
|
||||||
if e.text is None:
|
if e.text is None:
|
||||||
return box('{0.__class__.__name__}: {0}'.format(e), lang="py")
|
return box("{0.__class__.__name__}: {0}".format(e), lang="py")
|
||||||
return box(
|
return box(
|
||||||
'{0.text}{1:>{0.offset}}\n{2}: {0}'
|
"{0.text}{1:>{0.offset}}\n{2}: {0}" "".format(e, "^", type(e).__name__), lang="py"
|
||||||
''.format(e, '^', type(e).__name__),
|
)
|
||||||
lang="py")
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_pages(msg: str):
|
def get_pages(msg: str):
|
||||||
@ -90,15 +90,15 @@ class Dev:
|
|||||||
_ - The result of the last dev command.
|
_ - The result of the last dev command.
|
||||||
"""
|
"""
|
||||||
env = {
|
env = {
|
||||||
'bot': ctx.bot,
|
"bot": ctx.bot,
|
||||||
'ctx': ctx,
|
"ctx": ctx,
|
||||||
'channel': ctx.channel,
|
"channel": ctx.channel,
|
||||||
'author': ctx.author,
|
"author": ctx.author,
|
||||||
'guild': ctx.guild,
|
"guild": ctx.guild,
|
||||||
'message': ctx.message,
|
"message": ctx.message,
|
||||||
'discord': discord,
|
"discord": discord,
|
||||||
'commands': commands,
|
"commands": commands,
|
||||||
'_': self._last_result
|
"_": self._last_result,
|
||||||
}
|
}
|
||||||
|
|
||||||
code = self.cleanup_code(code)
|
code = self.cleanup_code(code)
|
||||||
@ -109,8 +109,7 @@ class Dev:
|
|||||||
await ctx.send(self.get_syntax_error(e))
|
await ctx.send(self.get_syntax_error(e))
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await ctx.send(
|
await ctx.send(box("{}: {!s}".format(type(e).__name__, e), lang="py"))
|
||||||
box('{}: {!s}'.format(type(e).__name__, e), lang='py'))
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if asyncio.iscoroutine(result):
|
if asyncio.iscoroutine(result):
|
||||||
@ -122,7 +121,7 @@ class Dev:
|
|||||||
|
|
||||||
await ctx.send_interactive(self.get_pages(result), box_lang="py")
|
await ctx.send_interactive(self.get_pages(result), box_lang="py")
|
||||||
|
|
||||||
@commands.command(name='eval')
|
@commands.command(name="eval")
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def _eval(self, ctx, *, body: str):
|
async def _eval(self, ctx, *, body: str):
|
||||||
"""Execute asynchronous code.
|
"""Execute asynchronous code.
|
||||||
@ -145,28 +144,28 @@ class Dev:
|
|||||||
_ - The result of the last dev command.
|
_ - The result of the last dev command.
|
||||||
"""
|
"""
|
||||||
env = {
|
env = {
|
||||||
'bot': ctx.bot,
|
"bot": ctx.bot,
|
||||||
'ctx': ctx,
|
"ctx": ctx,
|
||||||
'channel': ctx.channel,
|
"channel": ctx.channel,
|
||||||
'author': ctx.author,
|
"author": ctx.author,
|
||||||
'guild': ctx.guild,
|
"guild": ctx.guild,
|
||||||
'message': ctx.message,
|
"message": ctx.message,
|
||||||
'discord': discord,
|
"discord": discord,
|
||||||
'commands': commands,
|
"commands": commands,
|
||||||
'_': self._last_result
|
"_": self._last_result,
|
||||||
}
|
}
|
||||||
|
|
||||||
body = self.cleanup_code(body)
|
body = self.cleanup_code(body)
|
||||||
stdout = io.StringIO()
|
stdout = io.StringIO()
|
||||||
|
|
||||||
to_compile = 'async def func():\n%s' % textwrap.indent(body, ' ')
|
to_compile = "async def func():\n%s" % textwrap.indent(body, " ")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
exec(to_compile, env)
|
exec(to_compile, env)
|
||||||
except SyntaxError as e:
|
except SyntaxError as e:
|
||||||
return await ctx.send(self.get_syntax_error(e))
|
return await ctx.send(self.get_syntax_error(e))
|
||||||
|
|
||||||
func = env['func']
|
func = env["func"]
|
||||||
result = None
|
result = None
|
||||||
try:
|
try:
|
||||||
with redirect_stdout(stdout):
|
with redirect_stdout(stdout):
|
||||||
@ -199,43 +198,43 @@ class Dev:
|
|||||||
async function.
|
async function.
|
||||||
"""
|
"""
|
||||||
variables = {
|
variables = {
|
||||||
'ctx': ctx,
|
"ctx": ctx,
|
||||||
'bot': ctx.bot,
|
"bot": ctx.bot,
|
||||||
'message': ctx.message,
|
"message": ctx.message,
|
||||||
'guild': ctx.guild,
|
"guild": ctx.guild,
|
||||||
'channel': ctx.channel,
|
"channel": ctx.channel,
|
||||||
'author': ctx.author,
|
"author": ctx.author,
|
||||||
'_': None,
|
"_": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.channel.id in self.sessions:
|
if ctx.channel.id in self.sessions:
|
||||||
await ctx.send(_('Already running a REPL session in this channel. '
|
await ctx.send(
|
||||||
'Exit it with `quit`.'))
|
_("Already running a REPL session in this channel. " "Exit it with `quit`.")
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.sessions.add(ctx.channel.id)
|
self.sessions.add(ctx.channel.id)
|
||||||
await ctx.send(_('Enter code to execute or evaluate.'
|
await ctx.send(_("Enter code to execute or evaluate." " `exit()` or `quit` to exit."))
|
||||||
' `exit()` or `quit` to exit.'))
|
|
||||||
|
|
||||||
msg_check = lambda m: (m.author == ctx.author and
|
msg_check = lambda m: (
|
||||||
m.channel == ctx.channel and
|
m.author == ctx.author and m.channel == ctx.channel and m.content.startswith("`")
|
||||||
m.content.startswith('`'))
|
)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
response = await ctx.bot.wait_for("message", check=msg_check)
|
response = await ctx.bot.wait_for("message", check=msg_check)
|
||||||
|
|
||||||
cleaned = self.cleanup_code(response.content)
|
cleaned = self.cleanup_code(response.content)
|
||||||
|
|
||||||
if cleaned in ('quit', 'exit', 'exit()'):
|
if cleaned in ("quit", "exit", "exit()"):
|
||||||
await ctx.send('Exiting.')
|
await ctx.send("Exiting.")
|
||||||
self.sessions.remove(ctx.channel.id)
|
self.sessions.remove(ctx.channel.id)
|
||||||
return
|
return
|
||||||
|
|
||||||
executor = exec
|
executor = exec
|
||||||
if cleaned.count('\n') == 0:
|
if cleaned.count("\n") == 0:
|
||||||
# single statement, potentially 'eval'
|
# single statement, potentially 'eval'
|
||||||
try:
|
try:
|
||||||
code = compile(cleaned, '<repl session>', 'eval')
|
code = compile(cleaned, "<repl session>", "eval")
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@ -243,12 +242,12 @@ class Dev:
|
|||||||
|
|
||||||
if executor is exec:
|
if executor is exec:
|
||||||
try:
|
try:
|
||||||
code = compile(cleaned, '<repl session>', 'exec')
|
code = compile(cleaned, "<repl session>", "exec")
|
||||||
except SyntaxError as e:
|
except SyntaxError as e:
|
||||||
await ctx.send(self.get_syntax_error(e))
|
await ctx.send(self.get_syntax_error(e))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
variables['message'] = response
|
variables["message"] = response
|
||||||
|
|
||||||
stdout = io.StringIO()
|
stdout = io.StringIO()
|
||||||
|
|
||||||
@ -266,7 +265,7 @@ class Dev:
|
|||||||
value = stdout.getvalue()
|
value = stdout.getvalue()
|
||||||
if result is not None:
|
if result is not None:
|
||||||
msg = "{}{}".format(value, result)
|
msg = "{}{}".format(value, result)
|
||||||
variables['_'] = result
|
variables["_"] = result
|
||||||
elif value:
|
elif value:
|
||||||
msg = "{}".format(value)
|
msg = "{}".format(value)
|
||||||
|
|
||||||
@ -277,7 +276,7 @@ class Dev:
|
|||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
pass
|
pass
|
||||||
except discord.HTTPException as e:
|
except discord.HTTPException as e:
|
||||||
await ctx.send(_('Unexpected error: `{}`').format(e))
|
await ctx.send(_("Unexpected error: `{}`").format(e))
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
@ -290,7 +289,7 @@ class Dev:
|
|||||||
msg.author = user
|
msg.author = user
|
||||||
msg.content = ctx.prefix + command
|
msg.content = ctx.prefix + command
|
||||||
|
|
||||||
ctx.bot.dispatch('message', msg)
|
ctx.bot.dispatch("message", msg)
|
||||||
|
|
||||||
@commands.command(name="mockmsg")
|
@commands.command(name="mockmsg")
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
|
|||||||
@ -24,8 +24,10 @@ def get_driver(type, *args, **kwargs):
|
|||||||
"""
|
"""
|
||||||
if type == "JSON":
|
if type == "JSON":
|
||||||
from .red_json import JSON
|
from .red_json import JSON
|
||||||
|
|
||||||
return JSON(*args, **kwargs)
|
return JSON(*args, **kwargs)
|
||||||
elif type == "MongoDB":
|
elif type == "MongoDB":
|
||||||
from .red_mongo import Mongo
|
from .red_mongo import Mongo
|
||||||
|
|
||||||
return Mongo(*args, **kwargs)
|
return Mongo(*args, **kwargs)
|
||||||
raise RuntimeError("Invalid driver type: '{}'".format(type))
|
raise RuntimeError("Invalid driver type: '{}'".format(type))
|
||||||
|
|||||||
@ -2,6 +2,7 @@ __all__ = ["BaseDriver"]
|
|||||||
|
|
||||||
|
|
||||||
class BaseDriver:
|
class BaseDriver:
|
||||||
|
|
||||||
def __init__(self, cog_name, identifier):
|
def __init__(self, cog_name, identifier):
|
||||||
self.cog_name = cog_name
|
self.cog_name = cog_name
|
||||||
self.unique_cog_identifier = identifier
|
self.unique_cog_identifier = identifier
|
||||||
|
|||||||
@ -44,14 +44,21 @@ class JSON(BaseDriver):
|
|||||||
|
|
||||||
The path in which to store the file indicated by :py:attr:`file_name`.
|
The path in which to store the file indicated by :py:attr:`file_name`.
|
||||||
"""
|
"""
|
||||||
def __init__(self, cog_name, identifier, *, data_path_override: Path=None,
|
|
||||||
file_name_override: str="settings.json"):
|
def __init__(
|
||||||
|
self,
|
||||||
|
cog_name,
|
||||||
|
identifier,
|
||||||
|
*,
|
||||||
|
data_path_override: Path = None,
|
||||||
|
file_name_override: str = "settings.json"
|
||||||
|
):
|
||||||
super().__init__(cog_name, identifier)
|
super().__init__(cog_name, identifier)
|
||||||
self.file_name = file_name_override
|
self.file_name = file_name_override
|
||||||
if data_path_override:
|
if data_path_override:
|
||||||
self.data_path = data_path_override
|
self.data_path = data_path_override
|
||||||
else:
|
else:
|
||||||
self.data_path = Path.cwd() / 'cogs' / '.data' / self.cog_name
|
self.data_path = Path.cwd() / "cogs" / ".data" / self.cog_name
|
||||||
|
|
||||||
self.data_path.mkdir(parents=True, exist_ok=True)
|
self.data_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
|||||||
@ -8,21 +8,16 @@ _conn = None
|
|||||||
|
|
||||||
|
|
||||||
def _initialize(**kwargs):
|
def _initialize(**kwargs):
|
||||||
host = kwargs['HOST']
|
host = kwargs["HOST"]
|
||||||
port = kwargs['PORT']
|
port = kwargs["PORT"]
|
||||||
admin_user = kwargs['USERNAME']
|
admin_user = kwargs["USERNAME"]
|
||||||
admin_pass = kwargs['PASSWORD']
|
admin_pass = kwargs["PASSWORD"]
|
||||||
db_name = kwargs.get('DB_NAME', 'default_db')
|
db_name = kwargs.get("DB_NAME", "default_db")
|
||||||
|
|
||||||
if admin_user is not None and admin_pass is not None:
|
if admin_user is not None and admin_pass is not None:
|
||||||
url = "mongodb://{}:{}@{}:{}/{}".format(
|
url = "mongodb://{}:{}@{}:{}/{}".format(admin_user, admin_pass, host, port, db_name)
|
||||||
admin_user, admin_pass, host, port,
|
|
||||||
db_name
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
url = "mongodb://{}:{}/{}".format(
|
url = "mongodb://{}:{}/{}".format(host, port, db_name)
|
||||||
host, port, db_name
|
|
||||||
)
|
|
||||||
|
|
||||||
global _conn
|
global _conn
|
||||||
_conn = motor.motor_asyncio.AsyncIOMotorClient(url)
|
_conn = motor.motor_asyncio.AsyncIOMotorClient(url)
|
||||||
@ -32,6 +27,7 @@ class Mongo(BaseDriver):
|
|||||||
"""
|
"""
|
||||||
Subclass of :py:class:`.red_base.BaseDriver`.
|
Subclass of :py:class:`.red_base.BaseDriver`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, cog_name, identifier, **kwargs):
|
def __init__(self, cog_name, identifier, **kwargs):
|
||||||
super().__init__(cog_name, identifier)
|
super().__init__(cog_name, identifier)
|
||||||
|
|
||||||
@ -75,45 +71,40 @@ class Mongo(BaseDriver):
|
|||||||
async def get(self, *identifiers: str):
|
async def get(self, *identifiers: str):
|
||||||
mongo_collection = self.get_collection()
|
mongo_collection = self.get_collection()
|
||||||
|
|
||||||
dot_identifiers = '.'.join(identifiers)
|
dot_identifiers = ".".join(identifiers)
|
||||||
|
|
||||||
partial = await mongo_collection.find_one(
|
partial = await mongo_collection.find_one(
|
||||||
filter={'_id': self.unique_cog_identifier},
|
filter={"_id": self.unique_cog_identifier}, projection={dot_identifiers: True}
|
||||||
projection={dot_identifiers: True}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if partial is None:
|
if partial is None:
|
||||||
raise KeyError("No matching document was found and Config expects"
|
raise KeyError("No matching document was found and Config expects" " a KeyError.")
|
||||||
" a KeyError.")
|
|
||||||
|
|
||||||
for i in identifiers:
|
for i in identifiers:
|
||||||
partial = partial[i]
|
partial = partial[i]
|
||||||
return partial
|
return partial
|
||||||
|
|
||||||
async def set(self, *identifiers: str, value=None):
|
async def set(self, *identifiers: str, value=None):
|
||||||
dot_identifiers = '.'.join(identifiers)
|
dot_identifiers = ".".join(identifiers)
|
||||||
|
|
||||||
mongo_collection = self.get_collection()
|
mongo_collection = self.get_collection()
|
||||||
|
|
||||||
await mongo_collection.update_one(
|
await mongo_collection.update_one(
|
||||||
{'_id': self.unique_cog_identifier},
|
{"_id": self.unique_cog_identifier},
|
||||||
update={"$set": {dot_identifiers: value}},
|
update={"$set": {dot_identifiers: value}},
|
||||||
upsert=True
|
upsert=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def clear(self, *identifiers: str):
|
async def clear(self, *identifiers: str):
|
||||||
dot_identifiers = '.'.join(identifiers)
|
dot_identifiers = ".".join(identifiers)
|
||||||
mongo_collection = self.get_collection()
|
mongo_collection = self.get_collection()
|
||||||
|
|
||||||
if len(identifiers) > 0:
|
if len(identifiers) > 0:
|
||||||
await mongo_collection.update_one(
|
await mongo_collection.update_one(
|
||||||
{'_id': self.unique_cog_identifier},
|
{"_id": self.unique_cog_identifier}, update={"$unset": {dot_identifiers: 1}}
|
||||||
update={"$unset": {dot_identifiers: 1}}
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await mongo_collection.delete_one(
|
await mongo_collection.delete_one({"_id": self.unique_cog_identifier})
|
||||||
{'_id': self.unique_cog_identifier}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_config_details():
|
def get_config_details():
|
||||||
@ -129,10 +120,10 @@ def get_config_details():
|
|||||||
admin_uname = admin_password = None
|
admin_uname = admin_password = None
|
||||||
|
|
||||||
ret = {
|
ret = {
|
||||||
'HOST': host,
|
"HOST": host,
|
||||||
'PORT': port,
|
"PORT": port,
|
||||||
'USERNAME': admin_uname,
|
"USERNAME": admin_uname,
|
||||||
'PASSWORD': admin_password,
|
"PASSWORD": admin_password,
|
||||||
'DB_NAME': db_name
|
"DB_NAME": db_name,
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
|
|||||||
@ -44,8 +44,8 @@ def should_log_sentry(exception) -> bool:
|
|||||||
tb_frame = tb.tb_frame
|
tb_frame = tb.tb_frame
|
||||||
tb = tb.tb_next
|
tb = tb.tb_next
|
||||||
|
|
||||||
module = tb_frame.f_globals.get('__name__')
|
module = tb_frame.f_globals.get("__name__")
|
||||||
return module.startswith('redbot')
|
return module.startswith("redbot")
|
||||||
|
|
||||||
|
|
||||||
def init_events(bot, cli_flags):
|
def init_events(bot, cli_flags):
|
||||||
@ -77,8 +77,7 @@ def init_events(bot, cli_flags):
|
|||||||
spec = await bot.cog_mgr.find_cog(package)
|
spec = await bot.cog_mgr.find_cog(package)
|
||||||
await bot.load_extension(spec)
|
await bot.load_extension(spec)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception("Failed to load package {}".format(package),
|
log.exception("Failed to load package {}".format(package), exc_info=e)
|
||||||
exc_info=e)
|
|
||||||
await bot.remove_loaded_package(package)
|
await bot.remove_loaded_package(package)
|
||||||
to_remove.append(package)
|
to_remove.append(package)
|
||||||
for package in to_remove:
|
for package in to_remove:
|
||||||
@ -104,18 +103,21 @@ def init_events(bot, cli_flags):
|
|||||||
red_pkg = pkg_resources.get_distribution("Red-DiscordBot")
|
red_pkg = pkg_resources.get_distribution("Red-DiscordBot")
|
||||||
dpy_version = discord.__version__
|
dpy_version = discord.__version__
|
||||||
|
|
||||||
INFO = [str(bot.user), "Prefixes: {}".format(', '.join(prefixes)),
|
INFO = [
|
||||||
'Language: {}'.format(lang),
|
str(bot.user),
|
||||||
|
"Prefixes: {}".format(", ".join(prefixes)),
|
||||||
|
"Language: {}".format(lang),
|
||||||
"Red Bot Version: {}".format(red_version),
|
"Red Bot Version: {}".format(red_version),
|
||||||
"Discord.py Version: {}".format(dpy_version),
|
"Discord.py Version: {}".format(dpy_version),
|
||||||
"Shards: {}".format(bot.shard_count)]
|
"Shards: {}".format(bot.shard_count),
|
||||||
|
]
|
||||||
|
|
||||||
if guilds:
|
if guilds:
|
||||||
INFO.extend(("Servers: {}".format(guilds), "Users: {}".format(users)))
|
INFO.extend(("Servers: {}".format(guilds), "Users: {}".format(users)))
|
||||||
else:
|
else:
|
||||||
print("Ready. I'm not in any server yet!")
|
print("Ready. I'm not in any server yet!")
|
||||||
|
|
||||||
INFO.append('{} cogs with {} commands'.format(len(bot.cogs), len(bot.commands)))
|
INFO.append("{} cogs with {} commands".format(len(bot.cogs), len(bot.commands)))
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get("https://pypi.python.org/pypi/red-discordbot/json") as r:
|
async with session.get("https://pypi.python.org/pypi/red-discordbot/json") as r:
|
||||||
@ -139,11 +141,7 @@ def init_events(bot, cli_flags):
|
|||||||
|
|
||||||
sentry = await bot.db.enable_sentry()
|
sentry = await bot.db.enable_sentry()
|
||||||
mongo_enabled = storage_type() != "JSON"
|
mongo_enabled = storage_type() != "JSON"
|
||||||
reqs_installed = {
|
reqs_installed = {"voice": None, "docs": None, "test": None}
|
||||||
"voice": None,
|
|
||||||
"docs": None,
|
|
||||||
"test": None
|
|
||||||
}
|
|
||||||
for key in reqs_installed.keys():
|
for key in reqs_installed.keys():
|
||||||
reqs = [x.name for x in red_pkg._dep_map[key]]
|
reqs = [x.name for x in red_pkg._dep_map[key]]
|
||||||
try:
|
try:
|
||||||
@ -158,7 +156,7 @@ def init_events(bot, cli_flags):
|
|||||||
("MongoDB", mongo_enabled),
|
("MongoDB", mongo_enabled),
|
||||||
("Voice", reqs_installed["voice"]),
|
("Voice", reqs_installed["voice"]),
|
||||||
("Docs", reqs_installed["docs"]),
|
("Docs", reqs_installed["docs"]),
|
||||||
("Tests", reqs_installed["test"])
|
("Tests", reqs_installed["test"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
on_symbol, off_symbol, ascii_border = _get_startup_screen_specs()
|
on_symbol, off_symbol, ascii_border = _get_startup_screen_specs()
|
||||||
@ -201,21 +199,25 @@ def init_events(bot, cli_flags):
|
|||||||
await ctx.send(msg)
|
await ctx.send(msg)
|
||||||
return
|
return
|
||||||
"""
|
"""
|
||||||
log.exception("Exception in command '{}'"
|
log.exception(
|
||||||
"".format(ctx.command.qualified_name),
|
"Exception in command '{}'" "".format(ctx.command.qualified_name),
|
||||||
exc_info=error.original)
|
exc_info=error.original,
|
||||||
|
)
|
||||||
if should_log_sentry(error):
|
if should_log_sentry(error):
|
||||||
sentry_log.exception("Exception in command '{}'"
|
sentry_log.exception(
|
||||||
"".format(ctx.command.qualified_name),
|
"Exception in command '{}'" "".format(ctx.command.qualified_name),
|
||||||
exc_info=error.original)
|
exc_info=error.original,
|
||||||
|
)
|
||||||
|
|
||||||
message = ("Error in command '{}'. Check your console or "
|
message = (
|
||||||
|
"Error in command '{}'. Check your console or "
|
||||||
"logs for details."
|
"logs for details."
|
||||||
"".format(ctx.command.qualified_name))
|
"".format(ctx.command.qualified_name)
|
||||||
exception_log = ("Exception in command '{}'\n"
|
)
|
||||||
"".format(ctx.command.qualified_name))
|
exception_log = ("Exception in command '{}'\n" "".format(ctx.command.qualified_name))
|
||||||
exception_log += "".join(traceback.format_exception(type(error),
|
exception_log += "".join(
|
||||||
error, error.__traceback__))
|
traceback.format_exception(type(error), error, error.__traceback__)
|
||||||
|
)
|
||||||
bot._last_exception = exception_log
|
bot._last_exception = exception_log
|
||||||
if not hasattr(ctx.cog, "_{0.command.cog_name}__error".format(ctx)):
|
if not hasattr(ctx.cog, "_{0.command.cog_name}__error".format(ctx)):
|
||||||
await ctx.send(inline(message))
|
await ctx.send(inline(message))
|
||||||
@ -226,9 +228,9 @@ def init_events(bot, cli_flags):
|
|||||||
elif isinstance(error, commands.NoPrivateMessage):
|
elif isinstance(error, commands.NoPrivateMessage):
|
||||||
await ctx.send("That command is not available in DMs.")
|
await ctx.send("That command is not available in DMs.")
|
||||||
elif isinstance(error, commands.CommandOnCooldown):
|
elif isinstance(error, commands.CommandOnCooldown):
|
||||||
await ctx.send("This command is on cooldown. "
|
await ctx.send(
|
||||||
"Try again in {:.2f}s"
|
"This command is on cooldown. " "Try again in {:.2f}s" "".format(error.retry_after)
|
||||||
"".format(error.retry_after))
|
)
|
||||||
else:
|
else:
|
||||||
log.exception(type(error).__name__, exc_info=error)
|
log.exception(type(error).__name__, exc_info=error)
|
||||||
try:
|
try:
|
||||||
@ -237,8 +239,7 @@ def init_events(bot, cli_flags):
|
|||||||
sentry_error = error
|
sentry_error = error
|
||||||
|
|
||||||
if should_log_sentry(sentry_error):
|
if should_log_sentry(sentry_error):
|
||||||
sentry_log.exception("Unhandled command error.",
|
sentry_log.exception("Unhandled command error.", exc_info=sentry_error)
|
||||||
exc_info=sentry_error)
|
|
||||||
|
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_message(message):
|
async def on_message(message):
|
||||||
@ -253,6 +254,7 @@ def init_events(bot, cli_flags):
|
|||||||
async def on_command(command):
|
async def on_command(command):
|
||||||
bot.counter["processed_commands"] += 1
|
bot.counter["processed_commands"] += 1
|
||||||
|
|
||||||
|
|
||||||
def _get_startup_screen_specs():
|
def _get_startup_screen_specs():
|
||||||
"""Get specs for displaying the startup screen on stdout.
|
"""Get specs for displaying the startup screen on stdout.
|
||||||
|
|
||||||
@ -278,11 +280,10 @@ def _get_startup_screen_specs():
|
|||||||
off_symbol = "X"
|
off_symbol = "X"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
encoder('┌┐└┘─│') # border symbols
|
encoder("┌┐└┘─│") # border symbols
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
ascii_border = True
|
ascii_border = True
|
||||||
else:
|
else:
|
||||||
ascii_border = False
|
ascii_border = False
|
||||||
|
|
||||||
return on_symbol, off_symbol, ascii_border
|
return on_symbol, off_symbol, ascii_border
|
||||||
|
|
||||||
|
|||||||
@ -38,16 +38,13 @@ import traceback
|
|||||||
from . import commands
|
from . import commands
|
||||||
|
|
||||||
|
|
||||||
EMPTY_STRING = u'\u200b'
|
EMPTY_STRING = u"\u200b"
|
||||||
|
|
||||||
_mentions_transforms = {
|
_mentions_transforms = {"@everyone": "@\u200beveryone", "@here": "@\u200bhere"}
|
||||||
'@everyone': '@\u200beveryone',
|
|
||||||
'@here': '@\u200bhere'
|
|
||||||
}
|
|
||||||
|
|
||||||
_mention_pattern = re.compile('|'.join(_mentions_transforms.keys()))
|
_mention_pattern = re.compile("|".join(_mentions_transforms.keys()))
|
||||||
|
|
||||||
EmbedField = namedtuple('EmbedField', 'name value inline')
|
EmbedField = namedtuple("EmbedField", "name value inline")
|
||||||
|
|
||||||
|
|
||||||
class Help(formatter.HelpFormatter):
|
class Help(formatter.HelpFormatter):
|
||||||
@ -71,7 +68,7 @@ class Help(formatter.HelpFormatter):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def avatar(self):
|
def avatar(self):
|
||||||
return self.context.bot.user.avatar_url_as(format='png')
|
return self.context.bot.user.avatar_url_as(format="png")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def color(self):
|
def color(self):
|
||||||
@ -94,48 +91,41 @@ class Help(formatter.HelpFormatter):
|
|||||||
if self.pm_check(self.context):
|
if self.pm_check(self.context):
|
||||||
name = self.context.bot.user.name
|
name = self.context.bot.user.name
|
||||||
else:
|
else:
|
||||||
name = self.me.display_name if not '' else self.context.bot.user.name
|
name = self.me.display_name if not "" else self.context.bot.user.name
|
||||||
author = {
|
author = {"name": "{0} Help Manual".format(name), "icon_url": self.avatar}
|
||||||
'name': '{0} Help Manual'.format(name),
|
|
||||||
'icon_url': self.avatar
|
|
||||||
}
|
|
||||||
return author
|
return author
|
||||||
|
|
||||||
def _add_subcommands(self, cmds):
|
def _add_subcommands(self, cmds):
|
||||||
entries = ''
|
entries = ""
|
||||||
for name, command in cmds:
|
for name, command in cmds:
|
||||||
if name in command.aliases:
|
if name in command.aliases:
|
||||||
# skip aliases
|
# skip aliases
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if self.is_cog() or self.is_bot():
|
if self.is_cog() or self.is_bot():
|
||||||
name = '{0}{1}'.format(self.clean_prefix, name)
|
name = "{0}{1}".format(self.clean_prefix, name)
|
||||||
|
|
||||||
entries += '**{0}** {1}\n'.format(name, command.short_doc)
|
entries += "**{0}** {1}\n".format(name, command.short_doc)
|
||||||
return entries
|
return entries
|
||||||
|
|
||||||
def get_ending_note(self):
|
def get_ending_note(self):
|
||||||
# command_name = self.context.invoked_with
|
# command_name = self.context.invoked_with
|
||||||
return "Type {0}help <command> for more info on a command.\n" \
|
return "Type {0}help <command> for more info on a command.\n" "You can also type {0}help <category> for more info on a category.".format(
|
||||||
"You can also type {0}help <category> for more info on a category.".format(self.clean_prefix)
|
self.clean_prefix
|
||||||
|
)
|
||||||
|
|
||||||
async def format(self) -> dict:
|
async def format(self) -> dict:
|
||||||
"""Formats command for output.
|
"""Formats command for output.
|
||||||
|
|
||||||
Returns a dict used to build embed"""
|
Returns a dict used to build embed"""
|
||||||
emb = {
|
emb = {
|
||||||
'embed': {
|
"embed": {"title": "", "description": ""},
|
||||||
'title': '',
|
"footer": {"text": self.get_ending_note()},
|
||||||
'description': '',
|
"fields": [],
|
||||||
},
|
|
||||||
'footer': {
|
|
||||||
'text': self.get_ending_note()
|
|
||||||
},
|
|
||||||
'fields': []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.is_cog():
|
if self.is_cog():
|
||||||
translator = getattr(self.command, '__translator__', lambda s: s)
|
translator = getattr(self.command, "__translator__", lambda s: s)
|
||||||
description = (
|
description = (
|
||||||
inspect.cleandoc(translator(self.command.__doc__))
|
inspect.cleandoc(translator(self.command.__doc__))
|
||||||
if self.command.__doc__
|
if self.command.__doc__
|
||||||
@ -144,27 +134,27 @@ class Help(formatter.HelpFormatter):
|
|||||||
else:
|
else:
|
||||||
description = self.command.description
|
description = self.command.description
|
||||||
|
|
||||||
if not description == '' and description is not None:
|
if not description == "" and description is not None:
|
||||||
description = '*{0}*'.format(description)
|
description = "*{0}*".format(description)
|
||||||
|
|
||||||
if description:
|
if description:
|
||||||
# <description> portion
|
# <description> portion
|
||||||
emb['embed']['description'] = description[:2046]
|
emb["embed"]["description"] = description[:2046]
|
||||||
|
|
||||||
if isinstance(self.command, discord.ext.commands.core.Command):
|
if isinstance(self.command, discord.ext.commands.core.Command):
|
||||||
# <signature portion>
|
# <signature portion>
|
||||||
emb['embed']['title'] = emb['embed']['description']
|
emb["embed"]["title"] = emb["embed"]["description"]
|
||||||
emb['embed']['description'] = '`Syntax: {0}`'.format(self.get_command_signature())
|
emb["embed"]["description"] = "`Syntax: {0}`".format(self.get_command_signature())
|
||||||
|
|
||||||
# <long doc> section
|
# <long doc> section
|
||||||
if self.command.help:
|
if self.command.help:
|
||||||
splitted = self.command.help.split('\n\n')
|
splitted = self.command.help.split("\n\n")
|
||||||
name = '__{0}__'.format(splitted[0])
|
name = "__{0}__".format(splitted[0])
|
||||||
value = '\n\n'.join(splitted[1:]).replace('[p]', self.clean_prefix)
|
value = "\n\n".join(splitted[1:]).replace("[p]", self.clean_prefix)
|
||||||
if value == '':
|
if value == "":
|
||||||
value = EMPTY_STRING
|
value = EMPTY_STRING
|
||||||
field = EmbedField(name[:252], value[:1024], False)
|
field = EmbedField(name[:252], value[:1024], False)
|
||||||
emb['fields'].append(field)
|
emb["fields"].append(field)
|
||||||
|
|
||||||
# end it here if it's just a regular command
|
# end it here if it's just a regular command
|
||||||
if not self.has_subcommands():
|
if not self.has_subcommands():
|
||||||
@ -173,7 +163,7 @@ class Help(formatter.HelpFormatter):
|
|||||||
def category(tup):
|
def category(tup):
|
||||||
# Turn get cog (Category) name from cog/list tuples
|
# Turn get cog (Category) name from cog/list tuples
|
||||||
cog = tup[1].cog_name
|
cog = tup[1].cog_name
|
||||||
return '**__{0}:__**'.format(cog) if cog is not None else '**__\u200bNo Category:__**'
|
return "**__{0}:__**".format(cog) if cog is not None else "**__\u200bNo Category:__**"
|
||||||
|
|
||||||
# Get subcommands for bot or category
|
# Get subcommands for bot or category
|
||||||
filtered = await self.filter_command_list()
|
filtered = await self.filter_command_list()
|
||||||
@ -185,18 +175,21 @@ class Help(formatter.HelpFormatter):
|
|||||||
commands_ = sorted(commands_)
|
commands_ = sorted(commands_)
|
||||||
if len(commands_) > 0:
|
if len(commands_) > 0:
|
||||||
field = EmbedField(category, self._add_subcommands(commands_), False)
|
field = EmbedField(category, self._add_subcommands(commands_), False)
|
||||||
emb['fields'].append(field)
|
emb["fields"].append(field)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Get list of commands for category
|
# Get list of commands for category
|
||||||
filtered = sorted(filtered)
|
filtered = sorted(filtered)
|
||||||
if filtered:
|
if filtered:
|
||||||
field = EmbedField(
|
field = EmbedField(
|
||||||
'**__Commands:__**' if not self.is_bot() and self.is_cog() else '**__Subcommands:__**',
|
"**__Commands:__**"
|
||||||
|
if not self.is_bot() and self.is_cog()
|
||||||
|
else "**__Subcommands:__**",
|
||||||
self._add_subcommands(filtered), # May need paginated
|
self._add_subcommands(filtered), # May need paginated
|
||||||
False)
|
False,
|
||||||
|
)
|
||||||
|
|
||||||
emb['fields'].append(field)
|
emb["fields"].append(field)
|
||||||
|
|
||||||
return emb
|
return emb
|
||||||
|
|
||||||
@ -214,7 +207,7 @@ class Help(formatter.HelpFormatter):
|
|||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
async def format_help_for(self, ctx, command_or_bot, reason: str=None):
|
async def format_help_for(self, ctx, command_or_bot, reason: str = None):
|
||||||
"""Formats the help page and handles the actual heavy lifting of how ### WTF HAPPENED?
|
"""Formats the help page and handles the actual heavy lifting of how ### WTF HAPPENED?
|
||||||
the help command looks like. To change the behaviour, override the
|
the help command looks like. To change the behaviour, override the
|
||||||
:meth:`~.HelpFormatter.format` method.
|
:meth:`~.HelpFormatter.format` method.
|
||||||
@ -237,16 +230,18 @@ class Help(formatter.HelpFormatter):
|
|||||||
emb = await self.format()
|
emb = await self.format()
|
||||||
|
|
||||||
if reason:
|
if reason:
|
||||||
emb['embed']['title'] = "{0}".format(reason)
|
emb["embed"]["title"] = "{0}".format(reason)
|
||||||
|
|
||||||
ret = []
|
ret = []
|
||||||
field_groups = self.group_fields(emb['fields'])
|
field_groups = self.group_fields(emb["fields"])
|
||||||
|
|
||||||
for i, group in enumerate(field_groups, 1):
|
for i, group in enumerate(field_groups, 1):
|
||||||
embed = discord.Embed(color=self.color, **emb['embed'])
|
embed = discord.Embed(color=self.color, **emb["embed"])
|
||||||
|
|
||||||
if len(field_groups) > 1:
|
if len(field_groups) > 1:
|
||||||
description = "{} *- Page {} of {}*".format(embed.description, i, len(field_groups))
|
description = "{} *- Page {} of {}*".format(
|
||||||
|
embed.description, i, len(field_groups)
|
||||||
|
)
|
||||||
embed.description = description
|
embed.description = description
|
||||||
|
|
||||||
embed.set_author(**self.author)
|
embed.set_author(**self.author)
|
||||||
@ -254,7 +249,7 @@ class Help(formatter.HelpFormatter):
|
|||||||
for field in group:
|
for field in group:
|
||||||
embed.add_field(**field._asdict())
|
embed.add_field(**field._asdict())
|
||||||
|
|
||||||
embed.set_footer(**emb['footer'])
|
embed.set_footer(**emb["footer"])
|
||||||
|
|
||||||
ret.append(embed)
|
ret.append(embed)
|
||||||
|
|
||||||
@ -275,18 +270,18 @@ class Help(formatter.HelpFormatter):
|
|||||||
embed = self.simple_embed(
|
embed = self.simple_embed(
|
||||||
ctx,
|
ctx,
|
||||||
title=ctx.bot.command_not_found.format(cmd),
|
title=ctx.bot.command_not_found.format(cmd),
|
||||||
description='Commands are case sensitive. Please check your spelling and try again',
|
description="Commands are case sensitive. Please check your spelling and try again",
|
||||||
color=color)
|
color=color,
|
||||||
|
)
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
def cmd_has_no_subcommands(self, ctx, cmd, color=None):
|
def cmd_has_no_subcommands(self, ctx, cmd, color=None):
|
||||||
embed = self.simple_embed(
|
embed = self.simple_embed(
|
||||||
ctx,
|
ctx, title=ctx.bot.command_has_no_subcommands.format(cmd), color=color
|
||||||
title=ctx.bot.command_has_no_subcommands.format(cmd),
|
|
||||||
color=color
|
|
||||||
)
|
)
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def help(ctx, *cmds: str):
|
async def help(ctx, *cmds: str):
|
||||||
"""Shows help documentation.
|
"""Shows help documentation.
|
||||||
@ -297,7 +292,8 @@ async def help(ctx, *cmds: str):
|
|||||||
destination = ctx.author if ctx.bot.pm_help else ctx
|
destination = ctx.author if ctx.bot.pm_help else ctx
|
||||||
|
|
||||||
def repl(obj):
|
def repl(obj):
|
||||||
return _mentions_transforms.get(obj.group(0), '')
|
return _mentions_transforms.get(obj.group(0), "")
|
||||||
|
|
||||||
use_embeds = await ctx.embed_requested()
|
use_embeds = await ctx.embed_requested()
|
||||||
f = formatter.HelpFormatter()
|
f = formatter.HelpFormatter()
|
||||||
# help by itself just lists our own commands.
|
# help by itself just lists our own commands.
|
||||||
@ -316,12 +312,9 @@ async def help(ctx, *cmds: str):
|
|||||||
command = ctx.bot.all_commands.get(name)
|
command = ctx.bot.all_commands.get(name)
|
||||||
if command is None:
|
if command is None:
|
||||||
if use_embeds:
|
if use_embeds:
|
||||||
await destination.send(
|
await destination.send(embed=ctx.bot.formatter.cmd_not_found(ctx, name))
|
||||||
embed=ctx.bot.formatter.cmd_not_found(ctx, name))
|
|
||||||
else:
|
else:
|
||||||
await destination.send(
|
await destination.send(ctx.bot.command_not_found.format(name))
|
||||||
ctx.bot.command_not_found.format(name)
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
if use_embeds:
|
if use_embeds:
|
||||||
embeds = await ctx.bot.formatter.format_help_for(ctx, command)
|
embeds = await ctx.bot.formatter.format_help_for(ctx, command)
|
||||||
@ -332,12 +325,9 @@ async def help(ctx, *cmds: str):
|
|||||||
command = ctx.bot.all_commands.get(name)
|
command = ctx.bot.all_commands.get(name)
|
||||||
if command is None:
|
if command is None:
|
||||||
if use_embeds:
|
if use_embeds:
|
||||||
await destination.send(
|
await destination.send(embed=ctx.bot.formatter.cmd_not_found(ctx, name))
|
||||||
embed=ctx.bot.formatter.cmd_not_found(ctx, name))
|
|
||||||
else:
|
else:
|
||||||
await destination.send(
|
await destination.send(ctx.bot.command_not_found.format(name))
|
||||||
ctx.bot.command_not_found.format(name)
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
for key in cmds[1:]:
|
for key in cmds[1:]:
|
||||||
@ -346,12 +336,9 @@ async def help(ctx, *cmds: str):
|
|||||||
command = command.all_commands.get(key)
|
command = command.all_commands.get(key)
|
||||||
if command is None:
|
if command is None:
|
||||||
if use_embeds:
|
if use_embeds:
|
||||||
await destination.send(
|
await destination.send(embed=ctx.bot.formatter.cmd_not_found(ctx, key))
|
||||||
embed=ctx.bot.formatter.cmd_not_found(ctx, key))
|
|
||||||
else:
|
else:
|
||||||
await destination.send(
|
await destination.send(ctx.bot.command_not_found.format(key))
|
||||||
ctx.bot.command_not_found.format(key)
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
if use_embeds:
|
if use_embeds:
|
||||||
@ -359,11 +346,11 @@ async def help(ctx, *cmds: str):
|
|||||||
embed=ctx.bot.formatter.simple_embed(
|
embed=ctx.bot.formatter.simple_embed(
|
||||||
ctx,
|
ctx,
|
||||||
title='Command "{0.name}" has no subcommands.'.format(command),
|
title='Command "{0.name}" has no subcommands.'.format(command),
|
||||||
color=ctx.bot.formatter.color))
|
color=ctx.bot.formatter.color,
|
||||||
else:
|
|
||||||
await destination.send(
|
|
||||||
ctx.bot.command_has_no_subcommands.format(command)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await destination.send(ctx.bot.command_has_no_subcommands.format(command))
|
||||||
return
|
return
|
||||||
if use_embeds:
|
if use_embeds:
|
||||||
embeds = await ctx.bot.formatter.format_help_for(ctx, command)
|
embeds = await ctx.bot.formatter.format_help_for(ctx, command)
|
||||||
@ -391,5 +378,5 @@ async def help(ctx, *cmds: str):
|
|||||||
@help.error
|
@help.error
|
||||||
async def help_error(ctx, error):
|
async def help_error(ctx, error):
|
||||||
destination = ctx.author if ctx.bot.pm_help else ctx
|
destination = ctx.author if ctx.bot.pm_help else ctx
|
||||||
await destination.send('{0.__name__}: {1}'.format(type(error), error))
|
await destination.send("{0.__name__}: {1}".format(type(error), error))
|
||||||
traceback.print_tb(error.original.__traceback__, file=sys.stderr)
|
traceback.print_tb(error.original.__traceback__, file=sys.stderr)
|
||||||
|
|||||||
@ -3,10 +3,9 @@ from pathlib import Path
|
|||||||
|
|
||||||
from . import commands
|
from . import commands
|
||||||
|
|
||||||
__all__ = ['get_locale', 'set_locale', 'reload_locales', 'cog_i18n',
|
__all__ = ["get_locale", "set_locale", "reload_locales", "cog_i18n", "Translator"]
|
||||||
'Translator']
|
|
||||||
|
|
||||||
_current_locale = 'en_us'
|
_current_locale = "en_us"
|
||||||
|
|
||||||
WAITING_FOR_MSGID = 1
|
WAITING_FOR_MSGID = 1
|
||||||
IN_MSGID = 2
|
IN_MSGID = 2
|
||||||
@ -54,8 +53,8 @@ def _parse(translation_file):
|
|||||||
|
|
||||||
if line.startswith(MSGID):
|
if line.startswith(MSGID):
|
||||||
# Don't check if step is WAITING_FOR_MSGID
|
# Don't check if step is WAITING_FOR_MSGID
|
||||||
untranslated = ''
|
untranslated = ""
|
||||||
translated = ''
|
translated = ""
|
||||||
data = line[len(MSGID):-1]
|
data = line[len(MSGID):-1]
|
||||||
if len(data) == 0: # Multiline mode
|
if len(data) == 0: # Multiline mode
|
||||||
step = IN_MSGID
|
step = IN_MSGID
|
||||||
@ -63,10 +62,9 @@ def _parse(translation_file):
|
|||||||
untranslated += data
|
untranslated += data
|
||||||
step = WAITING_FOR_MSGSTR
|
step = WAITING_FOR_MSGSTR
|
||||||
|
|
||||||
elif step is IN_MSGID and line.startswith('"') and \
|
elif step is IN_MSGID and line.startswith('"') and line.endswith('"'):
|
||||||
line.endswith('"'):
|
|
||||||
untranslated += line[1:-1]
|
untranslated += line[1:-1]
|
||||||
elif step is IN_MSGID and untranslated == '': # Empty MSGID
|
elif step is IN_MSGID and untranslated == "": # Empty MSGID
|
||||||
step = WAITING_FOR_MSGID
|
step = WAITING_FOR_MSGID
|
||||||
elif step is IN_MSGID: # the MSGID is finished
|
elif step is IN_MSGID: # the MSGID is finished
|
||||||
step = WAITING_FOR_MSGSTR
|
step = WAITING_FOR_MSGSTR
|
||||||
@ -79,16 +77,15 @@ def _parse(translation_file):
|
|||||||
translations |= {(untranslated, data)}
|
translations |= {(untranslated, data)}
|
||||||
step = WAITING_FOR_MSGID
|
step = WAITING_FOR_MSGID
|
||||||
|
|
||||||
elif step is IN_MSGSTR and line.startswith('"') and \
|
elif step is IN_MSGSTR and line.startswith('"') and line.endswith('"'):
|
||||||
line.endswith('"'):
|
|
||||||
translated += line[1:-1]
|
translated += line[1:-1]
|
||||||
elif step is IN_MSGSTR: # the MSGSTR is finished
|
elif step is IN_MSGSTR: # the MSGSTR is finished
|
||||||
step = WAITING_FOR_MSGID
|
step = WAITING_FOR_MSGID
|
||||||
if translated == '':
|
if translated == "":
|
||||||
translated = untranslated
|
translated = untranslated
|
||||||
translations |= {(untranslated, translated)}
|
translations |= {(untranslated, translated)}
|
||||||
if step is IN_MSGSTR:
|
if step is IN_MSGSTR:
|
||||||
if translated == '':
|
if translated == "":
|
||||||
translated = untranslated
|
translated = untranslated
|
||||||
translations |= {(untranslated, translated)}
|
translations |= {(untranslated, translated)}
|
||||||
return translations
|
return translations
|
||||||
@ -107,33 +104,34 @@ def _normalize(string, remove_newline=False):
|
|||||||
:param remove_newline:
|
:param remove_newline:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def normalize_whitespace(s):
|
def normalize_whitespace(s):
|
||||||
"""Normalizes the whitespace in a string; \s+ becomes one space."""
|
"""Normalizes the whitespace in a string; \s+ becomes one space."""
|
||||||
if not s:
|
if not s:
|
||||||
return str(s) # not the same reference
|
return str(s) # not the same reference
|
||||||
starts_with_space = (s[0] in ' \n\t\r')
|
starts_with_space = (s[0] in " \n\t\r")
|
||||||
ends_with_space = (s[-1] in ' \n\t\r')
|
ends_with_space = (s[-1] in " \n\t\r")
|
||||||
if remove_newline:
|
if remove_newline:
|
||||||
newline_re = re.compile('[\r\n]+')
|
newline_re = re.compile("[\r\n]+")
|
||||||
s = ' '.join(filter(bool, newline_re.split(s)))
|
s = " ".join(filter(bool, newline_re.split(s)))
|
||||||
s = ' '.join(filter(bool, s.split('\t')))
|
s = " ".join(filter(bool, s.split("\t")))
|
||||||
s = ' '.join(filter(bool, s.split(' ')))
|
s = " ".join(filter(bool, s.split(" ")))
|
||||||
if starts_with_space:
|
if starts_with_space:
|
||||||
s = ' ' + s
|
s = " " + s
|
||||||
if ends_with_space:
|
if ends_with_space:
|
||||||
s += ' '
|
s += " "
|
||||||
return s
|
return s
|
||||||
|
|
||||||
if string is None:
|
if string is None:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
string = string.replace('\\n\\n', '\n\n')
|
string = string.replace("\\n\\n", "\n\n")
|
||||||
string = string.replace('\\n', ' ')
|
string = string.replace("\\n", " ")
|
||||||
string = string.replace('\\"', '"')
|
string = string.replace('\\"', '"')
|
||||||
string = string.replace("\'", "'")
|
string = string.replace("'", "'")
|
||||||
string = normalize_whitespace(string)
|
string = normalize_whitespace(string)
|
||||||
string = string.strip('\n')
|
string = string.strip("\n")
|
||||||
string = string.strip('\t')
|
string = string.strip("\t")
|
||||||
return string
|
return string
|
||||||
|
|
||||||
|
|
||||||
@ -148,7 +146,7 @@ def get_locale_path(cog_folder: Path, extension: str) -> Path:
|
|||||||
:return:
|
:return:
|
||||||
Path of possible localization file, it may not exist.
|
Path of possible localization file, it may not exist.
|
||||||
"""
|
"""
|
||||||
return cog_folder / 'locales' / "{}.{}".format(get_locale(), extension)
|
return cog_folder / "locales" / "{}.{}".format(get_locale(), extension)
|
||||||
|
|
||||||
|
|
||||||
class Translator:
|
class Translator:
|
||||||
@ -193,13 +191,13 @@ class Translator:
|
|||||||
"""
|
"""
|
||||||
self.translations = {}
|
self.translations = {}
|
||||||
translation_file = None
|
translation_file = None
|
||||||
locale_path = get_locale_path(self.cog_folder, 'po')
|
locale_path = get_locale_path(self.cog_folder, "po")
|
||||||
try:
|
try:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
translation_file = locale_path.open('ru', encoding='utf-8')
|
translation_file = locale_path.open("ru", encoding="utf-8")
|
||||||
except ValueError: # We are using Windows
|
except ValueError: # We are using Windows
|
||||||
translation_file = locale_path.open('r', encoding='utf-8')
|
translation_file = locale_path.open("r", encoding="utf-8")
|
||||||
self._parse(translation_file)
|
self._parse(translation_file)
|
||||||
except (IOError, FileNotFoundError): # The translation is unavailable
|
except (IOError, FileNotFoundError): # The translation is unavailable
|
||||||
pass
|
pass
|
||||||
@ -221,6 +219,7 @@ class Translator:
|
|||||||
|
|
||||||
def cog_i18n(translator: Translator):
|
def cog_i18n(translator: Translator):
|
||||||
"""Get a class decorator to link the translator to this cog."""
|
"""Get a class decorator to link the translator to this cog."""
|
||||||
|
|
||||||
def decorator(cog_class: type):
|
def decorator(cog_class: type):
|
||||||
cog_class.__translator__ = translator
|
cog_class.__translator__ = translator
|
||||||
for name, attr in cog_class.__dict__.items():
|
for name, attr in cog_class.__dict__.items():
|
||||||
@ -228,4 +227,5 @@ def cog_i18n(translator: Translator):
|
|||||||
attr.translator = translator
|
attr.translator = translator
|
||||||
setattr(cog_class, name, attr)
|
setattr(cog_class, name, attr)
|
||||||
return cog_class
|
return cog_class
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|||||||
@ -11,13 +11,14 @@ from pathlib import Path
|
|||||||
|
|
||||||
log = logging.getLogger("red")
|
log = logging.getLogger("red")
|
||||||
|
|
||||||
PRETTY = {"indent": 4, "sort_keys": True, "separators": (',', ' : ')}
|
PRETTY = {"indent": 4, "sort_keys": True, "separators": (",", " : ")}
|
||||||
MINIFIED = {"sort_keys": True, "separators": (',', ':')}
|
MINIFIED = {"sort_keys": True, "separators": (",", ":")}
|
||||||
|
|
||||||
|
|
||||||
class JsonIO:
|
class JsonIO:
|
||||||
"""Basic functions for atomic saving / loading of json files"""
|
"""Basic functions for atomic saving / loading of json files"""
|
||||||
def __init__(self, path: Path=Path.cwd()):
|
|
||||||
|
def __init__(self, path: Path = Path.cwd()):
|
||||||
"""
|
"""
|
||||||
:param path: Full path to file.
|
:param path: Full path to file.
|
||||||
"""
|
"""
|
||||||
@ -43,7 +44,7 @@ class JsonIO:
|
|||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
def _load_json(self):
|
def _load_json(self):
|
||||||
log.debug("Reading file {}".format(self.path))
|
log.debug("Reading file {}".format(self.path))
|
||||||
with self.path.open(encoding='utf-8', mode="r") as f:
|
with self.path.open(encoding="utf-8", mode="r") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,10 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
TO_TRANSLATE = [
|
TO_TRANSLATE = ["../cog_manager.py", "../core_commands.py", "../dev_commands.py"]
|
||||||
'../cog_manager.py',
|
|
||||||
'../core_commands.py',
|
|
||||||
'../dev_commands.py'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def regen_messages():
|
def regen_messages():
|
||||||
subprocess.run(
|
subprocess.run(["pygettext", "-n"] + TO_TRANSLATE)
|
||||||
['pygettext', '-n'] + TO_TRANSLATE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -8,21 +8,24 @@ from redbot.core import Config
|
|||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Case", "CaseType", "get_next_case_number", "get_case", "get_all_cases",
|
"Case",
|
||||||
"create_case", "get_casetype", "get_all_casetypes", "register_casetype",
|
"CaseType",
|
||||||
"register_casetypes", "get_modlog_channel", "set_modlog_channel",
|
"get_next_case_number",
|
||||||
"reset_cases"
|
"get_case",
|
||||||
|
"get_all_cases",
|
||||||
|
"create_case",
|
||||||
|
"get_casetype",
|
||||||
|
"get_all_casetypes",
|
||||||
|
"register_casetype",
|
||||||
|
"register_casetypes",
|
||||||
|
"get_modlog_channel",
|
||||||
|
"set_modlog_channel",
|
||||||
|
"reset_cases",
|
||||||
]
|
]
|
||||||
|
|
||||||
_DEFAULT_GLOBAL = {
|
_DEFAULT_GLOBAL = {"casetypes": {}}
|
||||||
"casetypes": {}
|
|
||||||
}
|
|
||||||
|
|
||||||
_DEFAULT_GUILD = {
|
_DEFAULT_GUILD = {"mod_log": None, "cases": {}, "casetypes": {}}
|
||||||
"mod_log": None,
|
|
||||||
"cases": {},
|
|
||||||
"casetypes": {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _register_defaults():
|
def _register_defaults():
|
||||||
@ -30,8 +33,8 @@ def _register_defaults():
|
|||||||
_conf.register_guild(**_DEFAULT_GUILD)
|
_conf.register_guild(**_DEFAULT_GUILD)
|
||||||
|
|
||||||
|
|
||||||
if not os.environ.get('BUILDING_DOCS'):
|
if not os.environ.get("BUILDING_DOCS"):
|
||||||
_conf = Config.get_conf(None, 1354799444, cog_name='ModLog')
|
_conf = Config.get_conf(None, 1354799444, cog_name="ModLog")
|
||||||
_register_defaults()
|
_register_defaults()
|
||||||
|
|
||||||
|
|
||||||
@ -39,11 +42,20 @@ class Case:
|
|||||||
"""A single mod log case"""
|
"""A single mod log case"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, guild: discord.Guild, created_at: int, action_type: str,
|
self,
|
||||||
user: discord.User, moderator: discord.Member, case_number: int,
|
guild: discord.Guild,
|
||||||
reason: str=None, until: int=None,
|
created_at: int,
|
||||||
channel: discord.TextChannel=None, amended_by: discord.Member=None,
|
action_type: str,
|
||||||
modified_at: int=None, message: discord.Message=None):
|
user: discord.User,
|
||||||
|
moderator: discord.Member,
|
||||||
|
case_number: int,
|
||||||
|
reason: str = None,
|
||||||
|
until: int = None,
|
||||||
|
channel: discord.TextChannel = None,
|
||||||
|
amended_by: discord.Member = None,
|
||||||
|
modified_at: int = None,
|
||||||
|
message: discord.Message = None,
|
||||||
|
):
|
||||||
self.guild = guild
|
self.guild = guild
|
||||||
self.created_at = created_at
|
self.created_at = created_at
|
||||||
self.action_type = action_type
|
self.action_type = action_type
|
||||||
@ -82,11 +94,9 @@ class Case:
|
|||||||
else:
|
else:
|
||||||
await self.message.edit(case_content)
|
await self.message.edit(case_content)
|
||||||
|
|
||||||
await _conf.guild(self.guild).cases.set_raw(
|
await _conf.guild(self.guild).cases.set_raw(str(self.case_number), value=self.to_json())
|
||||||
str(self.case_number), value=self.to_json()
|
|
||||||
)
|
|
||||||
|
|
||||||
async def message_content(self, embed: bool=True):
|
async def message_content(self, embed: bool = True):
|
||||||
"""
|
"""
|
||||||
Format a case message
|
Format a case message
|
||||||
|
|
||||||
@ -102,22 +112,18 @@ class Case:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
casetype = await get_casetype(self.action_type)
|
casetype = await get_casetype(self.action_type)
|
||||||
title = "{}".format("Case #{} | {} {}".format(
|
title = "{}".format(
|
||||||
self.case_number, casetype.case_str, casetype.image))
|
"Case #{} | {} {}".format(self.case_number, casetype.case_str, casetype.image)
|
||||||
|
)
|
||||||
|
|
||||||
if self.reason:
|
if self.reason:
|
||||||
reason = "**Reason:** {}".format(self.reason)
|
reason = "**Reason:** {}".format(self.reason)
|
||||||
else:
|
else:
|
||||||
reason = \
|
reason = "**Reason:** Use `[p]reason {} <reason>` to add it".format(self.case_number)
|
||||||
"**Reason:** Use `[p]reason {} <reason>` to add it".format(
|
|
||||||
self.case_number
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.moderator is not None:
|
if self.moderator is not None:
|
||||||
moderator = "{}#{} ({})\n".format(
|
moderator = "{}#{} ({})\n".format(
|
||||||
self.moderator.name,
|
self.moderator.name, self.moderator.discriminator, self.moderator.id
|
||||||
self.moderator.discriminator,
|
|
||||||
self.moderator.id
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
moderator = "Unknown"
|
moderator = "Unknown"
|
||||||
@ -126,7 +132,7 @@ class Case:
|
|||||||
if self.until:
|
if self.until:
|
||||||
start = datetime.fromtimestamp(self.created_at)
|
start = datetime.fromtimestamp(self.created_at)
|
||||||
end = datetime.fromtimestamp(self.until)
|
end = datetime.fromtimestamp(self.until)
|
||||||
end_fmt = end.strftime('%Y-%m-%d %H:%M:%S')
|
end_fmt = end.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
duration = end - start
|
duration = end - start
|
||||||
dur_fmt = _strfdelta(duration)
|
dur_fmt = _strfdelta(duration)
|
||||||
until = end_fmt
|
until = end_fmt
|
||||||
@ -135,21 +141,16 @@ class Case:
|
|||||||
amended_by = None
|
amended_by = None
|
||||||
if self.amended_by:
|
if self.amended_by:
|
||||||
amended_by = "{}#{} ({})".format(
|
amended_by = "{}#{} ({})".format(
|
||||||
self.amended_by.name,
|
self.amended_by.name, self.amended_by.discriminator, self.amended_by.id
|
||||||
self.amended_by.discriminator,
|
|
||||||
self.amended_by.id
|
|
||||||
)
|
)
|
||||||
|
|
||||||
last_modified = None
|
last_modified = None
|
||||||
if self.modified_at:
|
if self.modified_at:
|
||||||
last_modified = "{}".format(
|
last_modified = "{}".format(
|
||||||
datetime.fromtimestamp(
|
datetime.fromtimestamp(self.modified_at).strftime("%Y-%m-%d %H:%M:%S")
|
||||||
self.modified_at
|
|
||||||
).strftime('%Y-%m-%d %H:%M:%S')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
user = "{}#{} ({})\n".format(
|
user = "{}#{} ({})\n".format(self.user.name, self.user.discriminator, self.user.id)
|
||||||
self.user.name, self.user.discriminator, self.user.id)
|
|
||||||
if embed:
|
if embed:
|
||||||
emb = discord.Embed(title=title, description=reason)
|
emb = discord.Embed(title=title, description=reason)
|
||||||
|
|
||||||
@ -208,7 +209,7 @@ class Case:
|
|||||||
"channel": self.channel.id if hasattr(self.channel, "id") else None,
|
"channel": self.channel.id if hasattr(self.channel, "id") else None,
|
||||||
"amended_by": self.amended_by.id if hasattr(self.amended_by, "id") else None,
|
"amended_by": self.amended_by.id if hasattr(self.amended_by, "id") else None,
|
||||||
"modified_at": self.modified_at,
|
"modified_at": self.modified_at,
|
||||||
"message": self.message.id if hasattr(self.message, "id") else None
|
"message": self.message.id if hasattr(self.message, "id") else None,
|
||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@ -239,11 +240,18 @@ class Case:
|
|||||||
amended_by = guild.get_member(data["amended_by"])
|
amended_by = guild.get_member(data["amended_by"])
|
||||||
case_guild = bot.get_guild(data["guild"])
|
case_guild = bot.get_guild(data["guild"])
|
||||||
return cls(
|
return cls(
|
||||||
guild=case_guild, created_at=data["created_at"],
|
guild=case_guild,
|
||||||
action_type=data["action_type"], user=user, moderator=moderator,
|
created_at=data["created_at"],
|
||||||
case_number=data["case_number"], reason=data["reason"],
|
action_type=data["action_type"],
|
||||||
until=data["until"], channel=channel, amended_by=amended_by,
|
user=user,
|
||||||
modified_at=data["modified_at"], message=message
|
moderator=moderator,
|
||||||
|
case_number=data["case_number"],
|
||||||
|
reason=data["reason"],
|
||||||
|
until=data["until"],
|
||||||
|
channel=channel,
|
||||||
|
amended_by=amended_by,
|
||||||
|
modified_at=data["modified_at"],
|
||||||
|
message=message,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -266,9 +274,16 @@ class CaseType:
|
|||||||
The action type of the action as it would appear in the
|
The action type of the action as it would appear in the
|
||||||
audit log
|
audit log
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, name: str, default_setting: bool, image: str,
|
self,
|
||||||
case_str: str, audit_type: str=None, guild: discord.Guild=None):
|
name: str,
|
||||||
|
default_setting: bool,
|
||||||
|
image: str,
|
||||||
|
case_str: str,
|
||||||
|
audit_type: str = None,
|
||||||
|
guild: discord.Guild = None,
|
||||||
|
):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.default_setting = default_setting
|
self.default_setting = default_setting
|
||||||
self.image = image
|
self.image = image
|
||||||
@ -282,7 +297,7 @@ class CaseType:
|
|||||||
"default_setting": self.default_setting,
|
"default_setting": self.default_setting,
|
||||||
"image": self.image,
|
"image": self.image,
|
||||||
"case_str": self.case_str,
|
"case_str": self.case_str,
|
||||||
"audit_type": self.audit_type
|
"audit_type": self.audit_type,
|
||||||
}
|
}
|
||||||
await _conf.casetypes.set_raw(self.name, value=data)
|
await _conf.casetypes.set_raw(self.name, value=data)
|
||||||
|
|
||||||
@ -302,7 +317,8 @@ class CaseType:
|
|||||||
if not self.guild:
|
if not self.guild:
|
||||||
return False
|
return False
|
||||||
return await _conf.guild(self.guild).casetypes.get_raw(
|
return await _conf.guild(self.guild).casetypes.get_raw(
|
||||||
self.name, default=self.default_setting)
|
self.name, default=self.default_setting
|
||||||
|
)
|
||||||
|
|
||||||
async def set_enabled(self, enabled: bool):
|
async def set_enabled(self, enabled: bool):
|
||||||
"""
|
"""
|
||||||
@ -348,16 +364,11 @@ async def get_next_case_number(guild: discord.Guild) -> str:
|
|||||||
The next case number
|
The next case number
|
||||||
|
|
||||||
"""
|
"""
|
||||||
cases = sorted(
|
cases = sorted((await _conf.guild(guild).get_raw("cases")), key=lambda x: int(x), reverse=True)
|
||||||
(await _conf.guild(guild).get_raw("cases")),
|
|
||||||
key=lambda x: int(x),
|
|
||||||
reverse=True
|
|
||||||
)
|
|
||||||
return str(int(cases[0]) + 1) if cases else "1"
|
return str(int(cases[0]) + 1) if cases else "1"
|
||||||
|
|
||||||
|
|
||||||
async def get_case(case_number: int, guild: discord.Guild,
|
async def get_case(case_number: int, guild: discord.Guild, bot: Red) -> Case:
|
||||||
bot: Red) -> Case:
|
|
||||||
"""
|
"""
|
||||||
Gets the case with the associated case number
|
Gets the case with the associated case number
|
||||||
|
|
||||||
@ -384,9 +395,7 @@ async def get_case(case_number: int, guild: discord.Guild,
|
|||||||
try:
|
try:
|
||||||
case = await _conf.guild(guild).cases.get_raw(str(case_number))
|
case = await _conf.guild(guild).cases.get_raw(str(case_number))
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
raise RuntimeError(
|
raise RuntimeError("That case does not exist for guild {}".format(guild.name)) from e
|
||||||
"That case does not exist for guild {}".format(guild.name)
|
|
||||||
) from e
|
|
||||||
mod_channel = await get_modlog_channel(guild)
|
mod_channel = await get_modlog_channel(guild)
|
||||||
return await Case.from_json(mod_channel, bot, case)
|
return await Case.from_json(mod_channel, bot, case)
|
||||||
|
|
||||||
@ -416,11 +425,17 @@ async def get_all_cases(guild: discord.Guild, bot: Red) -> List[Case]:
|
|||||||
return case_list
|
return case_list
|
||||||
|
|
||||||
|
|
||||||
async def create_case(bot: Red, guild: discord.Guild, created_at: datetime, action_type: str,
|
async def create_case(
|
||||||
|
bot: Red,
|
||||||
|
guild: discord.Guild,
|
||||||
|
created_at: datetime,
|
||||||
|
action_type: str,
|
||||||
user: Union[discord.User, discord.Member],
|
user: Union[discord.User, discord.Member],
|
||||||
moderator: discord.Member=None, reason: str=None,
|
moderator: discord.Member = None,
|
||||||
until: datetime=None, channel: discord.TextChannel=None
|
reason: str = None,
|
||||||
) -> Union[Case, None]:
|
until: datetime = None,
|
||||||
|
channel: discord.TextChannel = None,
|
||||||
|
) -> Union[Case, None]:
|
||||||
"""
|
"""
|
||||||
Creates a new case
|
Creates a new case
|
||||||
|
|
||||||
@ -463,9 +478,7 @@ async def create_case(bot: Red, guild: discord.Guild, created_at: datetime, acti
|
|||||||
try:
|
try:
|
||||||
mod_channel = await get_modlog_channel(guild)
|
mod_channel = await get_modlog_channel(guild)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
raise RuntimeError(
|
raise RuntimeError("No mod log channel set for guild {}".format(guild.name))
|
||||||
"No mod log channel set for guild {}".format(guild.name)
|
|
||||||
)
|
|
||||||
case_type = await get_casetype(action_type, guild)
|
case_type = await get_casetype(action_type, guild)
|
||||||
if case_type is None:
|
if case_type is None:
|
||||||
return None
|
return None
|
||||||
@ -475,9 +488,20 @@ async def create_case(bot: Red, guild: discord.Guild, created_at: datetime, acti
|
|||||||
|
|
||||||
next_case_number = int(await get_next_case_number(guild))
|
next_case_number = int(await get_next_case_number(guild))
|
||||||
|
|
||||||
case = Case(guild, int(created_at.timestamp()), action_type, user, moderator,
|
case = Case(
|
||||||
next_case_number, reason, int(until.timestamp()) if until else None,
|
guild,
|
||||||
channel, amended_by=None, modified_at=None, message=None)
|
int(created_at.timestamp()),
|
||||||
|
action_type,
|
||||||
|
user,
|
||||||
|
moderator,
|
||||||
|
next_case_number,
|
||||||
|
reason,
|
||||||
|
int(until.timestamp()) if until else None,
|
||||||
|
channel,
|
||||||
|
amended_by=None,
|
||||||
|
modified_at=None,
|
||||||
|
message=None,
|
||||||
|
)
|
||||||
if hasattr(mod_channel, "send"): # Not going to be the case for tests
|
if hasattr(mod_channel, "send"): # Not going to be the case for tests
|
||||||
use_embeds = await bot.embed_requested(mod_channel, guild.me)
|
use_embeds = await bot.embed_requested(mod_channel, guild.me)
|
||||||
case_content = await case.message_content(use_embeds)
|
case_content = await case.message_content(use_embeds)
|
||||||
@ -490,7 +514,7 @@ async def create_case(bot: Red, guild: discord.Guild, created_at: datetime, acti
|
|||||||
return case
|
return case
|
||||||
|
|
||||||
|
|
||||||
async def get_casetype(name: str, guild: discord.Guild=None) -> Union[CaseType, None]:
|
async def get_casetype(name: str, guild: discord.Guild = None) -> Union[CaseType, None]:
|
||||||
"""
|
"""
|
||||||
Gets the case type
|
Gets the case type
|
||||||
|
|
||||||
@ -516,7 +540,7 @@ async def get_casetype(name: str, guild: discord.Guild=None) -> Union[CaseType,
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
async def get_all_casetypes(guild: discord.Guild=None) -> List[CaseType]:
|
async def get_all_casetypes(guild: discord.Guild = None) -> List[CaseType]:
|
||||||
"""
|
"""
|
||||||
Get all currently registered case types
|
Get all currently registered case types
|
||||||
|
|
||||||
@ -538,8 +562,8 @@ async def get_all_casetypes(guild: discord.Guild=None) -> List[CaseType]:
|
|||||||
|
|
||||||
|
|
||||||
async def register_casetype(
|
async def register_casetype(
|
||||||
name: str, default_setting: bool,
|
name: str, default_setting: bool, image: str, case_str: str, audit_type: str = None
|
||||||
image: str, case_str: str, audit_type: str=None) -> CaseType:
|
) -> CaseType:
|
||||||
"""
|
"""
|
||||||
Registers a case type. If the case type exists and
|
Registers a case type. If the case type exists and
|
||||||
there are differences between the values passed and
|
there are differences between the values passed and
|
||||||
@ -665,8 +689,7 @@ async def register_casetypes(new_types: List[dict]) -> List[CaseType]:
|
|||||||
return type_list
|
return type_list
|
||||||
|
|
||||||
|
|
||||||
async def get_modlog_channel(guild: discord.Guild
|
async def get_modlog_channel(guild: discord.Guild) -> Union[discord.TextChannel, None]:
|
||||||
) -> Union[discord.TextChannel, None]:
|
|
||||||
"""
|
"""
|
||||||
Get the current modlog channel
|
Get the current modlog channel
|
||||||
|
|
||||||
@ -695,8 +718,9 @@ async def get_modlog_channel(guild: discord.Guild
|
|||||||
return channel
|
return channel
|
||||||
|
|
||||||
|
|
||||||
async def set_modlog_channel(guild: discord.Guild,
|
async def set_modlog_channel(
|
||||||
channel: Union[discord.TextChannel, None]) -> bool:
|
guild: discord.Guild, channel: Union[discord.TextChannel, None]
|
||||||
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Changes the modlog channel
|
Changes the modlog channel
|
||||||
|
|
||||||
@ -713,9 +737,7 @@ async def set_modlog_channel(guild: discord.Guild,
|
|||||||
`True` if successful
|
`True` if successful
|
||||||
|
|
||||||
"""
|
"""
|
||||||
await _conf.guild(guild).mod_log.set(
|
await _conf.guild(guild).mod_log.set(channel.id if hasattr(channel, "id") else None)
|
||||||
channel.id if hasattr(channel, "id") else None
|
|
||||||
)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -741,19 +763,19 @@ async def reset_cases(guild: discord.Guild) -> bool:
|
|||||||
def _strfdelta(delta):
|
def _strfdelta(delta):
|
||||||
s = []
|
s = []
|
||||||
if delta.days:
|
if delta.days:
|
||||||
ds = '%i day' % delta.days
|
ds = "%i day" % delta.days
|
||||||
if delta.days > 1:
|
if delta.days > 1:
|
||||||
ds += 's'
|
ds += "s"
|
||||||
s.append(ds)
|
s.append(ds)
|
||||||
hrs, rem = divmod(delta.seconds, 60*60)
|
hrs, rem = divmod(delta.seconds, 60 * 60)
|
||||||
if hrs:
|
if hrs:
|
||||||
hs = '%i hr' % hrs
|
hs = "%i hr" % hrs
|
||||||
if hrs > 1:
|
if hrs > 1:
|
||||||
hs += 's'
|
hs += "s"
|
||||||
s.append(hs)
|
s.append(hs)
|
||||||
mins, secs = divmod(rem, 60)
|
mins, secs = divmod(rem, 60)
|
||||||
if mins:
|
if mins:
|
||||||
s.append('%i min' % mins)
|
s.append("%i min" % mins)
|
||||||
if secs:
|
if secs:
|
||||||
s.append('%i sec' % secs)
|
s.append("%i sec" % secs)
|
||||||
return ' '.join(s)
|
return " ".join(s)
|
||||||
|
|||||||
@ -10,8 +10,8 @@ from .utils import TYPE_CHECKING, NewType
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .bot import Red
|
from .bot import Red
|
||||||
|
|
||||||
log = logging.getLogger('red.rpc')
|
log = logging.getLogger("red.rpc")
|
||||||
JsonSerializable = NewType('JsonSerializable', dict)
|
JsonSerializable = NewType("JsonSerializable", dict)
|
||||||
|
|
||||||
_rpc = JsonRpc(logger=log)
|
_rpc = JsonRpc(logger=log)
|
||||||
|
|
||||||
@ -22,13 +22,13 @@ async def initialize(bot: "Red"):
|
|||||||
global _rpc_server
|
global _rpc_server
|
||||||
|
|
||||||
app = Application(loop=bot.loop)
|
app = Application(loop=bot.loop)
|
||||||
app.router.add_route('*', '/rpc', _rpc)
|
app.router.add_route("*", "/rpc", _rpc)
|
||||||
|
|
||||||
handler = app.make_handler()
|
handler = app.make_handler()
|
||||||
|
|
||||||
_rpc_server = await bot.loop.create_server(handler, '127.0.0.1', 6133)
|
_rpc_server = await bot.loop.create_server(handler, "127.0.0.1", 6133)
|
||||||
|
|
||||||
log.debug('Created RPC _rpc_server listener.')
|
log.debug("Created RPC _rpc_server listener.")
|
||||||
|
|
||||||
|
|
||||||
def add_topic(topic_name: str):
|
def add_topic(topic_name: str):
|
||||||
@ -77,10 +77,7 @@ def add_method(prefix, method):
|
|||||||
method
|
method
|
||||||
MUST BE A COROUTINE OR OBJECT.
|
MUST BE A COROUTINE OR OBJECT.
|
||||||
"""
|
"""
|
||||||
_rpc.add_methods(
|
_rpc.add_methods(("", method), prefix=prefix)
|
||||||
('', method),
|
|
||||||
prefix=prefix
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def clean_up():
|
def clean_up():
|
||||||
|
|||||||
@ -12,11 +12,13 @@ class SentryManager:
|
|||||||
|
|
||||||
def __init__(self, logger: logging.Logger):
|
def __init__(self, logger: logging.Logger):
|
||||||
self.client = Client(
|
self.client = Client(
|
||||||
dsn=("https://62402161d4cd4ef18f83b16f3e22a020:9310ef55a502442598203205a84da2bb@"
|
dsn=(
|
||||||
"sentry.io/253983"),
|
"https://62402161d4cd4ef18f83b16f3e22a020:9310ef55a502442598203205a84da2bb@"
|
||||||
|
"sentry.io/253983"
|
||||||
|
),
|
||||||
release=__version__,
|
release=__version__,
|
||||||
include_paths=['redbot'],
|
include_paths=["redbot"],
|
||||||
enable_breadcrumbs=False
|
enable_breadcrumbs=False,
|
||||||
)
|
)
|
||||||
self.handler = SentryHandler(self.client)
|
self.handler = SentryHandler(self.client)
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
__all__ = ['TYPE_CHECKING', 'NewType', 'safe_delete']
|
__all__ = ["TYPE_CHECKING", "NewType", "safe_delete"]
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import os
|
import os
|
||||||
@ -12,6 +12,7 @@ except ImportError:
|
|||||||
try:
|
try:
|
||||||
from typing import NewType
|
from typing import NewType
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
||||||
def NewType(name, tp):
|
def NewType(name, tp):
|
||||||
return type(name, (tp,), {})
|
return type(name, (tp,), {})
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ from typing import Tuple, List
|
|||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
Interval = Tuple[timedelta, int]
|
Interval = Tuple[timedelta, int]
|
||||||
AntiSpamInterval = namedtuple('AntiSpamInterval', ['period', 'frequency'])
|
AntiSpamInterval = namedtuple("AntiSpamInterval", ["period", "frequency"])
|
||||||
|
|
||||||
|
|
||||||
class AntiSpam:
|
class AntiSpam:
|
||||||
@ -26,21 +26,18 @@ class AntiSpam:
|
|||||||
(timedelta(seconds=5), 3),
|
(timedelta(seconds=5), 3),
|
||||||
(timedelta(minutes=1), 5),
|
(timedelta(minutes=1), 5),
|
||||||
(timedelta(hours=1), 10),
|
(timedelta(hours=1), 10),
|
||||||
(timedelta(days=1), 24)
|
(timedelta(days=1), 24),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, intervals: List[Interval]):
|
def __init__(self, intervals: List[Interval]):
|
||||||
self.__event_timestamps = []
|
self.__event_timestamps = []
|
||||||
_itvs = intervals if intervals else self.default_intervals
|
_itvs = intervals if intervals else self.default_intervals
|
||||||
self.__intervals = [
|
self.__intervals = [AntiSpamInterval(*x) for x in _itvs]
|
||||||
AntiSpamInterval(*x) for x in _itvs
|
|
||||||
]
|
|
||||||
self.__discard_after = max([x.period for x in self.__intervals])
|
self.__discard_after = max([x.period for x in self.__intervals])
|
||||||
|
|
||||||
def __interval_check(self, interval: AntiSpamInterval):
|
def __interval_check(self, interval: AntiSpamInterval):
|
||||||
return len(
|
return len(
|
||||||
[t for t in self.__event_timestamps
|
[t for t in self.__event_timestamps if (t + interval.period) > datetime.utcnow()]
|
||||||
if (t + interval.period) > datetime.utcnow()]
|
|
||||||
) >= interval.frequency
|
) >= interval.frequency
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -57,6 +54,5 @@ class AntiSpam:
|
|||||||
"""
|
"""
|
||||||
self.__event_timestamps.append(datetime.utcnow())
|
self.__event_timestamps.append(datetime.utcnow())
|
||||||
self.__event_timestamps = [
|
self.__event_timestamps = [
|
||||||
t for t in self.__event_timestamps
|
t for t in self.__event_timestamps if t + self.__discard_after > datetime.utcnow()
|
||||||
if t + self.__discard_after > datetime.utcnow()
|
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import itertools
|
import itertools
|
||||||
from typing import Sequence, Iterator
|
from typing import Sequence, Iterator
|
||||||
|
|
||||||
|
|
||||||
def error(text: str) -> str:
|
def error(text: str) -> str:
|
||||||
"""Get text prefixed with an error emoji.
|
"""Get text prefixed with an error emoji.
|
||||||
|
|
||||||
@ -66,7 +67,7 @@ def bold(text: str) -> str:
|
|||||||
return "**{}**".format(text)
|
return "**{}**".format(text)
|
||||||
|
|
||||||
|
|
||||||
def box(text: str, lang: str="") -> str:
|
def box(text: str, lang: str = "") -> str:
|
||||||
"""Get the given text in a code block.
|
"""Get the given text in a code block.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -120,7 +121,7 @@ def italics(text: str) -> str:
|
|||||||
return "*{}*".format(text)
|
return "*{}*".format(text)
|
||||||
|
|
||||||
|
|
||||||
def bordered(*columns: Sequence[str], ascii_border: bool=False) -> str:
|
def bordered(*columns: Sequence[str], ascii_border: bool = False) -> str:
|
||||||
"""Get two blocks of text in a borders.
|
"""Get two blocks of text in a borders.
|
||||||
|
|
||||||
Note
|
Note
|
||||||
@ -141,18 +142,18 @@ def bordered(*columns: Sequence[str], ascii_border: bool=False) -> str:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
borders = {
|
borders = {
|
||||||
'TL': '-' if ascii_border else '┌', # Top-left
|
"TL": "-" if ascii_border else "┌", # Top-left
|
||||||
'TR': '-' if ascii_border else '┐', # Top-right
|
"TR": "-" if ascii_border else "┐", # Top-right
|
||||||
'BL': '-' if ascii_border else '└', # Bottom-left
|
"BL": "-" if ascii_border else "└", # Bottom-left
|
||||||
'BR': '-' if ascii_border else '┘', # Bottom-right
|
"BR": "-" if ascii_border else "┘", # Bottom-right
|
||||||
'HZ': '-' if ascii_border else '─', # Horizontal
|
"HZ": "-" if ascii_border else "─", # Horizontal
|
||||||
'VT': '|' if ascii_border else '│', # Vertical
|
"VT": "|" if ascii_border else "│", # Vertical
|
||||||
}
|
}
|
||||||
|
|
||||||
sep = ' ' * 4 # Separator between boxes
|
sep = " " * 4 # Separator between boxes
|
||||||
widths = tuple(max(len(row) for row in column) + 9 for column in columns) # width of each col
|
widths = tuple(max(len(row) for row in column) + 9 for column in columns) # width of each col
|
||||||
colsdone = [False] * len(columns) # whether or not each column is done
|
colsdone = [False] * len(columns) # whether or not each column is done
|
||||||
lines = [sep.join('{TL}' + '{HZ}'*width + '{TR}' for width in widths)]
|
lines = [sep.join("{TL}" + "{HZ}" * width + "{TR}" for width in widths)]
|
||||||
|
|
||||||
for line in itertools.zip_longest(*columns):
|
for line in itertools.zip_longest(*columns):
|
||||||
row = []
|
row = []
|
||||||
@ -162,36 +163,38 @@ def bordered(*columns: Sequence[str], ascii_border: bool=False) -> str:
|
|||||||
if column is None:
|
if column is None:
|
||||||
if not done:
|
if not done:
|
||||||
# bottom border of column
|
# bottom border of column
|
||||||
column = '{HZ}' * width
|
column = "{HZ}" * width
|
||||||
row.append('{BL}' + column + '{BR}')
|
row.append("{BL}" + column + "{BR}")
|
||||||
colsdone[colidx] = True # mark column as done
|
colsdone[colidx] = True # mark column as done
|
||||||
else:
|
else:
|
||||||
# leave empty
|
# leave empty
|
||||||
row.append(' ' * (width + 2))
|
row.append(" " * (width + 2))
|
||||||
else:
|
else:
|
||||||
column += ' ' * (width - len(column)) # append padded spaces
|
column += " " * (width - len(column)) # append padded spaces
|
||||||
row.append('{VT}' + column + '{VT}')
|
row.append("{VT}" + column + "{VT}")
|
||||||
|
|
||||||
lines.append(sep.join(row))
|
lines.append(sep.join(row))
|
||||||
|
|
||||||
final_row = []
|
final_row = []
|
||||||
for width, done in zip(widths, colsdone):
|
for width, done in zip(widths, colsdone):
|
||||||
if not done:
|
if not done:
|
||||||
final_row.append('{BL}' + '{HZ}' * width + '{BR}')
|
final_row.append("{BL}" + "{HZ}" * width + "{BR}")
|
||||||
else:
|
else:
|
||||||
final_row.append(' ' * (width + 2))
|
final_row.append(" " * (width + 2))
|
||||||
lines.append(sep.join(final_row))
|
lines.append(sep.join(final_row))
|
||||||
|
|
||||||
return "\n".join(lines).format(**borders)
|
return "\n".join(lines).format(**borders)
|
||||||
|
|
||||||
|
|
||||||
def pagify(text: str,
|
def pagify(
|
||||||
delims: Sequence[str]=["\n"],
|
text: str,
|
||||||
|
delims: Sequence[str] = ["\n"],
|
||||||
*,
|
*,
|
||||||
priority: bool=False,
|
priority: bool = False,
|
||||||
escape_mass_mentions: bool=True,
|
escape_mass_mentions: bool = True,
|
||||||
shorten_by: int=8,
|
shorten_by: int = 8,
|
||||||
page_length: int=2000) -> Iterator[str]:
|
page_length: int = 2000
|
||||||
|
) -> Iterator[str]:
|
||||||
"""Generate multiple pages from the given text.
|
"""Generate multiple pages from the given text.
|
||||||
|
|
||||||
Note
|
Note
|
||||||
@ -232,10 +235,10 @@ def pagify(text: str,
|
|||||||
while len(in_text) > page_length:
|
while len(in_text) > page_length:
|
||||||
this_page_len = page_length
|
this_page_len = page_length
|
||||||
if escape_mass_mentions:
|
if escape_mass_mentions:
|
||||||
this_page_len -= (in_text.count("@here", 0, page_length) +
|
this_page_len -= (
|
||||||
in_text.count("@everyone", 0, page_length))
|
in_text.count("@here", 0, page_length) + in_text.count("@everyone", 0, page_length)
|
||||||
closest_delim = (in_text.rfind(d, 1, this_page_len)
|
)
|
||||||
for d in delims)
|
closest_delim = (in_text.rfind(d, 1, this_page_len) for d in delims)
|
||||||
if priority:
|
if priority:
|
||||||
closest_delim = next((x for x in closest_delim if x > 0), -1)
|
closest_delim = next((x for x in closest_delim if x > 0), -1)
|
||||||
else:
|
else:
|
||||||
@ -290,8 +293,7 @@ def underline(text: str) -> str:
|
|||||||
return "__{}__".format(text)
|
return "__{}__".format(text)
|
||||||
|
|
||||||
|
|
||||||
def escape(text: str, *, mass_mentions: bool=False,
|
def escape(text: str, *, mass_mentions: bool = False, formatting: bool = False) -> str:
|
||||||
formatting: bool=False) -> str:
|
|
||||||
"""Get text with all mass mentions or markdown escaped.
|
"""Get text with all mass mentions or markdown escaped.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -313,8 +315,7 @@ def escape(text: str, *, mass_mentions: bool=False,
|
|||||||
text = text.replace("@everyone", "@\u200beveryone")
|
text = text.replace("@everyone", "@\u200beveryone")
|
||||||
text = text.replace("@here", "@\u200bhere")
|
text = text.replace("@here", "@\u200bhere")
|
||||||
if formatting:
|
if formatting:
|
||||||
text = (text.replace("`", "\\`")
|
text = (
|
||||||
.replace("*", "\\*")
|
text.replace("`", "\\`").replace("*", "\\*").replace("_", "\\_").replace("~", "\\~")
|
||||||
.replace("_", "\\_")
|
)
|
||||||
.replace("~", "\\~"))
|
|
||||||
return text
|
return text
|
||||||
|
|||||||
@ -28,7 +28,7 @@ class DataConverter:
|
|||||||
The file isn't valid JSON
|
The file isn't valid JSON
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
with file_path.open(mode='r', encoding='utf-8') as f:
|
with file_path.open(mode="r", encoding="utf-8") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
except (FileNotFoundError, json.JSONDecodeError):
|
except (FileNotFoundError, json.JSONDecodeError):
|
||||||
raise
|
raise
|
||||||
|
|||||||
@ -10,10 +10,14 @@ import discord
|
|||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
|
|
||||||
|
|
||||||
async def menu(ctx: commands.Context, pages: list,
|
async def menu(
|
||||||
|
ctx: commands.Context,
|
||||||
|
pages: list,
|
||||||
controls: dict,
|
controls: dict,
|
||||||
message: discord.Message=None, page: int=0,
|
message: discord.Message = None,
|
||||||
timeout: float=30.0):
|
page: int = 0,
|
||||||
|
timeout: float = 30.0,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
An emoji-based menu
|
An emoji-based menu
|
||||||
|
|
||||||
@ -48,8 +52,10 @@ async def menu(ctx: commands.Context, pages: list,
|
|||||||
RuntimeError
|
RuntimeError
|
||||||
If either of the notes above are violated
|
If either of the notes above are violated
|
||||||
"""
|
"""
|
||||||
if not all(isinstance(x, discord.Embed) for x in pages) and\
|
if (
|
||||||
not all(isinstance(x, str) for x in pages):
|
not all(isinstance(x, discord.Embed) for x in pages)
|
||||||
|
and not all(isinstance(x, str) for x in pages)
|
||||||
|
):
|
||||||
raise RuntimeError("All pages must be of the same type")
|
raise RuntimeError("All pages must be of the same type")
|
||||||
for key, value in controls.items():
|
for key, value in controls.items():
|
||||||
if not asyncio.iscoroutinefunction(value):
|
if not asyncio.iscoroutinefunction(value):
|
||||||
@ -70,15 +76,10 @@ async def menu(ctx: commands.Context, pages: list,
|
|||||||
await message.edit(content=current_page)
|
await message.edit(content=current_page)
|
||||||
|
|
||||||
def react_check(r, u):
|
def react_check(r, u):
|
||||||
return u == ctx.author and r.message.id == message.id and \
|
return u == ctx.author and r.message.id == message.id and str(r.emoji) in controls.keys()
|
||||||
str(r.emoji) in controls.keys()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
react, user = await ctx.bot.wait_for(
|
react, user = await ctx.bot.wait_for("reaction_add", check=react_check, timeout=timeout)
|
||||||
"reaction_add",
|
|
||||||
check=react_check,
|
|
||||||
timeout=timeout
|
|
||||||
)
|
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
try:
|
try:
|
||||||
await message.clear_reactions()
|
await message.clear_reactions()
|
||||||
@ -87,14 +88,18 @@ async def menu(ctx: commands.Context, pages: list,
|
|||||||
await message.remove_reaction(key, ctx.bot.user)
|
await message.remove_reaction(key, ctx.bot.user)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return await controls[react.emoji](ctx, pages, controls,
|
return await controls[react.emoji](ctx, pages, controls, message, page, timeout, react.emoji)
|
||||||
message, page,
|
|
||||||
timeout, react.emoji)
|
|
||||||
|
|
||||||
|
|
||||||
async def next_page(ctx: commands.Context, pages: list,
|
async def next_page(
|
||||||
controls: dict, message: discord.Message, page: int,
|
ctx: commands.Context,
|
||||||
timeout: float, emoji: str):
|
pages: list,
|
||||||
|
controls: dict,
|
||||||
|
message: discord.Message,
|
||||||
|
page: int,
|
||||||
|
timeout: float,
|
||||||
|
emoji: str,
|
||||||
|
):
|
||||||
perms = message.channel.permissions_for(ctx.guild.me)
|
perms = message.channel.permissions_for(ctx.guild.me)
|
||||||
if perms.manage_messages: # Can manage messages, so remove react
|
if perms.manage_messages: # Can manage messages, so remove react
|
||||||
try:
|
try:
|
||||||
@ -105,13 +110,18 @@ async def next_page(ctx: commands.Context, pages: list,
|
|||||||
page = 0 # Loop around to the first item
|
page = 0 # Loop around to the first item
|
||||||
else:
|
else:
|
||||||
page = page + 1
|
page = page + 1
|
||||||
return await menu(ctx, pages, controls, message=message,
|
return await menu(ctx, pages, controls, message=message, page=page, timeout=timeout)
|
||||||
page=page, timeout=timeout)
|
|
||||||
|
|
||||||
|
|
||||||
async def prev_page(ctx: commands.Context, pages: list,
|
async def prev_page(
|
||||||
controls: dict, message: discord.Message, page: int,
|
ctx: commands.Context,
|
||||||
timeout: float, emoji: str):
|
pages: list,
|
||||||
|
controls: dict,
|
||||||
|
message: discord.Message,
|
||||||
|
page: int,
|
||||||
|
timeout: float,
|
||||||
|
emoji: str,
|
||||||
|
):
|
||||||
perms = message.channel.permissions_for(ctx.guild.me)
|
perms = message.channel.permissions_for(ctx.guild.me)
|
||||||
if perms.manage_messages: # Can manage messages, so remove react
|
if perms.manage_messages: # Can manage messages, so remove react
|
||||||
try:
|
try:
|
||||||
@ -122,20 +132,21 @@ async def prev_page(ctx: commands.Context, pages: list,
|
|||||||
next_page = len(pages) - 1 # Loop around to the last item
|
next_page = len(pages) - 1 # Loop around to the last item
|
||||||
else:
|
else:
|
||||||
next_page = page - 1
|
next_page = page - 1
|
||||||
return await menu(ctx, pages, controls, message=message,
|
return await menu(ctx, pages, controls, message=message, page=next_page, timeout=timeout)
|
||||||
page=next_page, timeout=timeout)
|
|
||||||
|
|
||||||
|
|
||||||
async def close_menu(ctx: commands.Context, pages: list,
|
async def close_menu(
|
||||||
controls: dict, message: discord.Message, page: int,
|
ctx: commands.Context,
|
||||||
timeout: float, emoji: str):
|
pages: list,
|
||||||
|
controls: dict,
|
||||||
|
message: discord.Message,
|
||||||
|
page: int,
|
||||||
|
timeout: float,
|
||||||
|
emoji: str,
|
||||||
|
):
|
||||||
if message:
|
if message:
|
||||||
await message.delete()
|
await message.delete()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_CONTROLS = {
|
DEFAULT_CONTROLS = {"⬅": prev_page, "❌": close_menu, "➡": next_page}
|
||||||
"⬅": prev_page,
|
|
||||||
"❌": close_menu,
|
|
||||||
"➡": next_page
|
|
||||||
}
|
|
||||||
|
|||||||
@ -8,8 +8,7 @@ from redbot.core import Config
|
|||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
|
|
||||||
|
|
||||||
async def mass_purge(messages: List[discord.Message],
|
async def mass_purge(messages: List[discord.Message], channel: discord.TextChannel):
|
||||||
channel: discord.TextChannel):
|
|
||||||
"""Bulk delete messages from a channel.
|
"""Bulk delete messages from a channel.
|
||||||
|
|
||||||
If more than 100 messages are supplied, the bot will delete 100 messages at
|
If more than 100 messages are supplied, the bot will delete 100 messages at
|
||||||
@ -80,24 +79,23 @@ def get_audit_reason(author: discord.Member, reason: str = None):
|
|||||||
The formatted audit log reason.
|
The formatted audit log reason.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return \
|
return "Action requested by {} (ID {}). Reason: {}".format(
|
||||||
"Action requested by {} (ID {}). Reason: {}".format(author, author.id, reason) if reason else \
|
author, author.id, reason
|
||||||
"Action requested by {} (ID {}).".format(author, author.id)
|
) if reason else "Action requested by {} (ID {}).".format(
|
||||||
|
author, author.id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def is_allowed_by_hierarchy(bot: Red,
|
async def is_allowed_by_hierarchy(
|
||||||
settings: Config,
|
bot: Red, settings: Config, guild: discord.Guild, mod: discord.Member, user: discord.Member
|
||||||
guild: discord.Guild,
|
):
|
||||||
mod: discord.Member,
|
|
||||||
user: discord.Member):
|
|
||||||
if not await settings.guild(guild).respect_hierarchy():
|
if not await settings.guild(guild).respect_hierarchy():
|
||||||
return True
|
return True
|
||||||
is_special = mod == guild.owner or await bot.is_owner(mod)
|
is_special = mod == guild.owner or await bot.is_owner(mod)
|
||||||
return mod.top_role.position > user.top_role.position or is_special
|
return mod.top_role.position > user.top_role.position or is_special
|
||||||
|
|
||||||
|
|
||||||
async def is_mod_or_superior(
|
async def is_mod_or_superior(bot: Red, obj: Union[discord.Message, discord.Member, discord.Role]):
|
||||||
bot: Red, obj: Union[discord.Message, discord.Member, discord.Role]):
|
|
||||||
"""Check if an object has mod or superior permissions.
|
"""Check if an object has mod or superior permissions.
|
||||||
|
|
||||||
If a message is passed, its author's permissions are checked. If a role is
|
If a message is passed, its author's permissions are checked. If a role is
|
||||||
@ -129,7 +127,7 @@ async def is_mod_or_superior(
|
|||||||
elif isinstance(obj, discord.Role):
|
elif isinstance(obj, discord.Role):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise TypeError('Only messages, members or roles may be passed')
|
raise TypeError("Only messages, members or roles may be passed")
|
||||||
|
|
||||||
server = obj.guild
|
server = obj.guild
|
||||||
admin_role_id = await bot.db.guild(server).admin_role()
|
admin_role_id = await bot.db.guild(server).admin_role()
|
||||||
@ -168,26 +166,27 @@ def strfdelta(delta: timedelta):
|
|||||||
"""
|
"""
|
||||||
s = []
|
s = []
|
||||||
if delta.days:
|
if delta.days:
|
||||||
ds = '%i day' % delta.days
|
ds = "%i day" % delta.days
|
||||||
if delta.days > 1:
|
if delta.days > 1:
|
||||||
ds += 's'
|
ds += "s"
|
||||||
s.append(ds)
|
s.append(ds)
|
||||||
hrs, rem = divmod(delta.seconds, 60*60)
|
hrs, rem = divmod(delta.seconds, 60 * 60)
|
||||||
if hrs:
|
if hrs:
|
||||||
hs = '%i hr' % hrs
|
hs = "%i hr" % hrs
|
||||||
if hrs > 1:
|
if hrs > 1:
|
||||||
hs += 's'
|
hs += "s"
|
||||||
s.append(hs)
|
s.append(hs)
|
||||||
mins, secs = divmod(rem, 60)
|
mins, secs = divmod(rem, 60)
|
||||||
if mins:
|
if mins:
|
||||||
s.append('%i min' % mins)
|
s.append("%i min" % mins)
|
||||||
if secs:
|
if secs:
|
||||||
s.append('%i sec' % secs)
|
s.append("%i sec" % secs)
|
||||||
return ' '.join(s)
|
return " ".join(s)
|
||||||
|
|
||||||
|
|
||||||
async def is_admin_or_superior(
|
async def is_admin_or_superior(
|
||||||
bot: Red, obj: Union[discord.Message, discord.Member, discord.Role]):
|
bot: Red, obj: Union[discord.Message, discord.Member, discord.Role]
|
||||||
|
):
|
||||||
"""Same as `is_mod_or_superior` except for admin permissions.
|
"""Same as `is_mod_or_superior` except for admin permissions.
|
||||||
|
|
||||||
If a message is passed, its author's permissions are checked. If a role is
|
If a message is passed, its author's permissions are checked. If a role is
|
||||||
@ -219,7 +218,7 @@ async def is_admin_or_superior(
|
|||||||
elif isinstance(obj, discord.Role):
|
elif isinstance(obj, discord.Role):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise TypeError('Only messages, members or roles may be passed')
|
raise TypeError("Only messages, members or roles may be passed")
|
||||||
|
|
||||||
server = obj.guild
|
server = obj.guild
|
||||||
admin_role_id = await bot.db.guild(server).admin_role()
|
admin_role_id = await bot.db.guild(server).admin_role()
|
||||||
|
|||||||
@ -16,10 +16,7 @@ class TunnelMeta(type):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __call__(cls, *args, **kwargs):
|
def __call__(cls, *args, **kwargs):
|
||||||
lockout_tuple = (
|
lockout_tuple = ((kwargs.get("sender"), kwargs.get("origin")), kwargs.get("recipient"))
|
||||||
(kwargs.get('sender'), kwargs.get('origin')),
|
|
||||||
kwargs.get('recipient')
|
|
||||||
)
|
|
||||||
|
|
||||||
if lockout_tuple in _instances:
|
if lockout_tuple in _instances:
|
||||||
return _instances[lockout_tuple]
|
return _instances[lockout_tuple]
|
||||||
@ -30,13 +27,8 @@ class TunnelMeta(type):
|
|||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
if not (
|
if not (
|
||||||
any(
|
any(lockout_tuple[0] == x[0] for x in _instances.keys())
|
||||||
lockout_tuple[0] == x[0]
|
or any(lockout_tuple[1] == x[1] for x in _instances.keys())
|
||||||
for x in _instances.keys()
|
|
||||||
) or any(
|
|
||||||
lockout_tuple[1] == x[1]
|
|
||||||
for x in _instances.keys()
|
|
||||||
)
|
|
||||||
):
|
):
|
||||||
# if this isn't temporarily stored, the weakref dict
|
# if this isn't temporarily stored, the weakref dict
|
||||||
# will discard this before the return statement,
|
# will discard this before the return statement,
|
||||||
@ -70,10 +62,9 @@ class Tunnel(metaclass=TunnelMeta):
|
|||||||
The user on the other end of the tunnel
|
The user on the other end of the tunnel
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *,
|
def __init__(
|
||||||
sender: discord.Member,
|
self, *, sender: discord.Member, origin: discord.TextChannel, recipient: discord.User
|
||||||
origin: discord.TextChannel,
|
):
|
||||||
recipient: discord.User):
|
|
||||||
self.sender = sender
|
self.sender = sender
|
||||||
self.origin = origin
|
self.origin = origin
|
||||||
self.recipient = recipient
|
self.recipient = recipient
|
||||||
@ -81,11 +72,8 @@ class Tunnel(metaclass=TunnelMeta):
|
|||||||
|
|
||||||
async def react_close(self, *, uid: int, message: str):
|
async def react_close(self, *, uid: int, message: str):
|
||||||
send_to = self.origin if uid == self.sender.id else self.sender
|
send_to = self.origin if uid == self.sender.id else self.sender
|
||||||
closer = next(filter(
|
closer = next(filter(lambda x: x.id == uid, (self.sender, self.recipient)), None)
|
||||||
lambda x: x.id == uid, (self.sender, self.recipient)), None)
|
await send_to.send(message.format(closer=closer))
|
||||||
await send_to.send(
|
|
||||||
message.format(closer=closer)
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def members(self):
|
def members(self):
|
||||||
@ -97,8 +85,8 @@ class Tunnel(metaclass=TunnelMeta):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def message_forwarder(
|
async def message_forwarder(
|
||||||
*, destination: discord.abc.Messageable,
|
*, destination: discord.abc.Messageable, content: str = None, embed=None, files=[]
|
||||||
content: str=None, embed=None, files=[]) -> List[discord.Message]:
|
) -> List[discord.Message]:
|
||||||
"""
|
"""
|
||||||
This does the actual sending, use this instead of a full tunnel
|
This does the actual sending, use this instead of a full tunnel
|
||||||
if you are using command initiated reactions instead of persistent
|
if you are using command initiated reactions instead of persistent
|
||||||
@ -131,18 +119,13 @@ class Tunnel(metaclass=TunnelMeta):
|
|||||||
files = files if files else None
|
files = files if files else None
|
||||||
if content:
|
if content:
|
||||||
for page in pagify(content):
|
for page in pagify(content):
|
||||||
rets.append(
|
rets.append(await destination.send(page, files=files, embed=embed))
|
||||||
await destination.send(
|
|
||||||
page, files=files, embed=embed)
|
|
||||||
)
|
|
||||||
if files:
|
if files:
|
||||||
del files
|
del files
|
||||||
if embed:
|
if embed:
|
||||||
del embed
|
del embed
|
||||||
elif embed or files:
|
elif embed or files:
|
||||||
rets.append(
|
rets.append(await destination.send(files=files, embed=embed))
|
||||||
await destination.send(files=files, embed=embed)
|
|
||||||
)
|
|
||||||
return rets
|
return rets
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -172,15 +155,12 @@ class Tunnel(metaclass=TunnelMeta):
|
|||||||
size += sys.getsizeof(_fp)
|
size += sys.getsizeof(_fp)
|
||||||
if size > max_size:
|
if size > max_size:
|
||||||
return []
|
return []
|
||||||
files.append(
|
files.append(discord.File(_fp, filename=a.filename))
|
||||||
discord.File(_fp, filename=a.filename)
|
|
||||||
)
|
|
||||||
return files
|
return files
|
||||||
|
|
||||||
async def communicate(self, *,
|
async def communicate(
|
||||||
message: discord.Message,
|
self, *, message: discord.Message, topic: str = None, skip_message_content: bool = False
|
||||||
topic: str=None,
|
):
|
||||||
skip_message_content: bool=False):
|
|
||||||
"""
|
"""
|
||||||
Forwards a message.
|
Forwards a message.
|
||||||
|
|
||||||
@ -208,18 +188,15 @@ class Tunnel(metaclass=TunnelMeta):
|
|||||||
the bot can't upload at the origin channel
|
the bot can't upload at the origin channel
|
||||||
or can't add reactions there.
|
or can't add reactions there.
|
||||||
"""
|
"""
|
||||||
if message.channel == self.origin \
|
if message.channel == self.origin and message.author == self.sender:
|
||||||
and message.author == self.sender:
|
|
||||||
send_to = self.recipient
|
send_to = self.recipient
|
||||||
elif message.author == self.recipient \
|
elif message.author == self.recipient and isinstance(message.channel, discord.DMChannel):
|
||||||
and isinstance(message.channel, discord.DMChannel):
|
|
||||||
send_to = self.origin
|
send_to = self.origin
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if not skip_message_content:
|
if not skip_message_content:
|
||||||
content = "\n".join((topic, message.content)) if topic \
|
content = "\n".join((topic, message.content)) if topic else message.content
|
||||||
else message.content
|
|
||||||
else:
|
else:
|
||||||
content = topic
|
content = topic
|
||||||
|
|
||||||
@ -234,11 +211,7 @@ class Tunnel(metaclass=TunnelMeta):
|
|||||||
else:
|
else:
|
||||||
attach = []
|
attach = []
|
||||||
|
|
||||||
rets = await self.message_forwarder(
|
rets = await self.message_forwarder(destination=send_to, content=content, files=attach)
|
||||||
destination=send_to,
|
|
||||||
content=content,
|
|
||||||
files=attach
|
|
||||||
)
|
|
||||||
|
|
||||||
await message.add_reaction("\N{WHITE HEAVY CHECK MARK}")
|
await message.add_reaction("\N{WHITE HEAVY CHECK MARK}")
|
||||||
await message.add_reaction("\N{NEGATIVE SQUARED CROSS MARK}")
|
await message.add_reaction("\N{NEGATIVE SQUARED CROSS MARK}")
|
||||||
|
|||||||
@ -8,7 +8,14 @@ import asyncio
|
|||||||
|
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from redbot.setup import basic_setup, load_existing_config, remove_instance, remove_instance_interaction, create_backup, save_config
|
from redbot.setup import (
|
||||||
|
basic_setup,
|
||||||
|
load_existing_config,
|
||||||
|
remove_instance,
|
||||||
|
remove_instance_interaction,
|
||||||
|
create_backup,
|
||||||
|
save_config,
|
||||||
|
)
|
||||||
from redbot.core.utils import safe_delete
|
from redbot.core.utils import safe_delete
|
||||||
from redbot.core.cli import confirm
|
from redbot.core.cli import confirm
|
||||||
|
|
||||||
@ -18,9 +25,9 @@ if sys.platform == "linux":
|
|||||||
PYTHON_OK = sys.version_info >= (3, 5)
|
PYTHON_OK = sys.version_info >= (3, 5)
|
||||||
INTERACTIVE_MODE = not len(sys.argv) > 1 # CLI flags = non-interactive
|
INTERACTIVE_MODE = not len(sys.argv) > 1 # CLI flags = non-interactive
|
||||||
|
|
||||||
INTRO = ("==========================\n"
|
INTRO = (
|
||||||
"Red Discord Bot - Launcher\n"
|
"==========================\n" "Red Discord Bot - Launcher\n" "==========================\n"
|
||||||
"==========================\n")
|
)
|
||||||
|
|
||||||
IS_WINDOWS = os.name == "nt"
|
IS_WINDOWS = os.name == "nt"
|
||||||
IS_MAC = sys.platform == "darwin"
|
IS_MAC = sys.platform == "darwin"
|
||||||
@ -31,35 +38,35 @@ def parse_cli_args():
|
|||||||
description="Red - Discord Bot's launcher (V3)", allow_abbrev=False
|
description="Red - Discord Bot's launcher (V3)", allow_abbrev=False
|
||||||
)
|
)
|
||||||
instances = load_existing_config()
|
instances = load_existing_config()
|
||||||
parser.add_argument("instancename", metavar="instancename", type=str,
|
parser.add_argument(
|
||||||
nargs="?", help="The instance to run", choices=list(instances.keys()))
|
"instancename",
|
||||||
parser.add_argument("--start", "-s",
|
metavar="instancename",
|
||||||
help="Starts Red",
|
type=str,
|
||||||
action="store_true")
|
nargs="?",
|
||||||
parser.add_argument("--auto-restart",
|
help="The instance to run",
|
||||||
help="Autorestarts Red in case of issues",
|
choices=list(instances.keys()),
|
||||||
action="store_true")
|
)
|
||||||
parser.add_argument("--update",
|
parser.add_argument("--start", "-s", help="Starts Red", action="store_true")
|
||||||
help="Updates Red",
|
parser.add_argument(
|
||||||
action="store_true")
|
"--auto-restart", help="Autorestarts Red in case of issues", action="store_true"
|
||||||
parser.add_argument("--update-dev",
|
)
|
||||||
help="Updates Red from the Github repo",
|
parser.add_argument("--update", help="Updates Red", action="store_true")
|
||||||
action="store_true")
|
parser.add_argument(
|
||||||
parser.add_argument("--voice",
|
"--update-dev", help="Updates Red from the Github repo", action="store_true"
|
||||||
help="Installs extra 'voice' when updating",
|
)
|
||||||
action="store_true")
|
parser.add_argument(
|
||||||
parser.add_argument("--docs",
|
"--voice", help="Installs extra 'voice' when updating", action="store_true"
|
||||||
help="Installs extra 'docs' when updating",
|
)
|
||||||
action="store_true")
|
parser.add_argument("--docs", help="Installs extra 'docs' when updating", action="store_true")
|
||||||
parser.add_argument("--test",
|
parser.add_argument("--test", help="Installs extra 'test' when updating", action="store_true")
|
||||||
help="Installs extra 'test' when updating",
|
parser.add_argument(
|
||||||
action="store_true")
|
"--mongo", help="Installs extra 'mongo' when updating", action="store_true"
|
||||||
parser.add_argument("--mongo",
|
)
|
||||||
help="Installs extra 'mongo' when updating",
|
parser.add_argument(
|
||||||
action="store_true")
|
"--debuginfo",
|
||||||
parser.add_argument("--debuginfo",
|
|
||||||
help="Prints basic debug info that would be useful for support",
|
help="Prints basic debug info that would be useful for support",
|
||||||
action="store_true")
|
action="store_true",
|
||||||
|
)
|
||||||
return parser.parse_known_args()
|
return parser.parse_known_args()
|
||||||
|
|
||||||
|
|
||||||
@ -97,20 +104,24 @@ def update_red(dev=False, reinstall=False, voice=False, mongo=False, docs=False,
|
|||||||
if egg_l:
|
if egg_l:
|
||||||
package += "[{}]".format(", ".join(egg_l))
|
package += "[{}]".format(", ".join(egg_l))
|
||||||
if reinstall:
|
if reinstall:
|
||||||
code = subprocess.call([
|
code = subprocess.call(
|
||||||
interpreter, "-m",
|
[
|
||||||
"pip", "install", "-U", "-I",
|
interpreter,
|
||||||
"--force-reinstall", "--no-cache-dir",
|
"-m",
|
||||||
|
"pip",
|
||||||
|
"install",
|
||||||
|
"-U",
|
||||||
|
"-I",
|
||||||
|
"--force-reinstall",
|
||||||
|
"--no-cache-dir",
|
||||||
"--process-dependency-links",
|
"--process-dependency-links",
|
||||||
package
|
package,
|
||||||
])
|
]
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
code = subprocess.call([
|
code = subprocess.call(
|
||||||
interpreter, "-m",
|
[interpreter, "-m", "pip", "install", "-U", "--process-dependency-links", package]
|
||||||
"pip", "install", "-U",
|
)
|
||||||
"--process-dependency-links",
|
|
||||||
package
|
|
||||||
])
|
|
||||||
if code == 0:
|
if code == 0:
|
||||||
print("Red has been updated")
|
print("Red has been updated")
|
||||||
else:
|
else:
|
||||||
@ -123,7 +134,7 @@ def update_red(dev=False, reinstall=False, voice=False, mongo=False, docs=False,
|
|||||||
os.rename(new_name, old_name)
|
os.rename(new_name, old_name)
|
||||||
|
|
||||||
|
|
||||||
def run_red(selected_instance, autorestart: bool=False, cliflags=None):
|
def run_red(selected_instance, autorestart: bool = False, cliflags=None):
|
||||||
while True:
|
while True:
|
||||||
print("Starting {}...".format(selected_instance))
|
print("Starting {}...".format(selected_instance))
|
||||||
cmd_list = ["redbot", selected_instance]
|
cmd_list = ["redbot", selected_instance]
|
||||||
@ -153,12 +164,15 @@ def cli_flag_getter():
|
|||||||
if choice == "y":
|
if choice == "y":
|
||||||
print(
|
print(
|
||||||
"Enter the prefixes, separated by a space (please note "
|
"Enter the prefixes, separated by a space (please note "
|
||||||
"that prefixes containing a space will need to be added with [p]set prefix)")
|
"that prefixes containing a space will need to be added with [p]set prefix)"
|
||||||
|
)
|
||||||
prefixes = user_choice().split()
|
prefixes = user_choice().split()
|
||||||
for p in prefixes:
|
for p in prefixes:
|
||||||
flags.append("-p {}".format(p))
|
flags.append("-p {}".format(p))
|
||||||
print("Would you like to disable console input? Please note that features "
|
print(
|
||||||
"requiring console interaction may fail to work (y/n)")
|
"Would you like to disable console input? Please note that features "
|
||||||
|
"requiring console interaction may fail to work (y/n)"
|
||||||
|
)
|
||||||
choice = user_choice()
|
choice = user_choice()
|
||||||
if choice == "y":
|
if choice == "y":
|
||||||
flags.append("--no-prompt")
|
flags.append("--no-prompt")
|
||||||
@ -169,9 +183,11 @@ def cli_flag_getter():
|
|||||||
print("Is this a selfbot? (y/n)")
|
print("Is this a selfbot? (y/n)")
|
||||||
choice = user_choice()
|
choice = user_choice()
|
||||||
if choice == "y":
|
if choice == "y":
|
||||||
print("Please note that selfbots are not allowed by Discord. See"
|
print(
|
||||||
|
"Please note that selfbots are not allowed by Discord. See"
|
||||||
"https://support.discordapp.com/hc/en-us/articles/115002192352-Automated-user-accounts-self-bots-"
|
"https://support.discordapp.com/hc/en-us/articles/115002192352-Automated-user-accounts-self-bots-"
|
||||||
"for more information.")
|
"for more information."
|
||||||
|
)
|
||||||
flags.append("--self-bot")
|
flags.append("--self-bot")
|
||||||
print("Does this token belong to a user account rather than a bot account? (y/n)")
|
print("Does this token belong to a user account rather than a bot account? (y/n)")
|
||||||
choice = user_choice()
|
choice = user_choice()
|
||||||
@ -185,7 +201,9 @@ def cli_flag_getter():
|
|||||||
choice = user_choice()
|
choice = user_choice()
|
||||||
if choice == "y":
|
if choice == "y":
|
||||||
flags.append("--debug")
|
flags.append("--debug")
|
||||||
print("Do you want the Dev cog loaded (thus enabling commands such as debug and repl)? (y/n)")
|
print(
|
||||||
|
"Do you want the Dev cog loaded (thus enabling commands such as debug and repl)? (y/n)"
|
||||||
|
)
|
||||||
choice = user_choice()
|
choice = user_choice()
|
||||||
if choice == "y":
|
if choice == "y":
|
||||||
flags.append("--dev")
|
flags.append("--dev")
|
||||||
@ -218,8 +236,8 @@ def instance_menu():
|
|||||||
|
|
||||||
name_num_map = {}
|
name_num_map = {}
|
||||||
for name in list(instances.keys()):
|
for name in list(instances.keys()):
|
||||||
print("{}. {}\n".format(counter+1, name))
|
print("{}. {}\n".format(counter + 1, name))
|
||||||
name_num_map[str(counter+1)] = name
|
name_num_map[str(counter + 1)] = name
|
||||||
counter += 1
|
counter += 1
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
@ -229,7 +247,7 @@ def instance_menu():
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
print("Invalid input! Please enter a number corresponding to an instance.")
|
print("Invalid input! Please enter a number corresponding to an instance.")
|
||||||
else:
|
else:
|
||||||
if selection not in list(range(1, counter+1)):
|
if selection not in list(range(1, counter + 1)):
|
||||||
print("Invalid selection! Please try again")
|
print("Invalid selection! Please try again")
|
||||||
else:
|
else:
|
||||||
return name_num_map[str(selection)]
|
return name_num_map[str(selection)]
|
||||||
@ -242,8 +260,10 @@ async def reset_red():
|
|||||||
print("No instance to delete.\n")
|
print("No instance to delete.\n")
|
||||||
return
|
return
|
||||||
print("WARNING: You are about to remove ALL Red instances on this computer.")
|
print("WARNING: You are about to remove ALL Red instances on this computer.")
|
||||||
print("If you want to reset data of only one instance, "
|
print(
|
||||||
"please select option 5 in the launcher.")
|
"If you want to reset data of only one instance, "
|
||||||
|
"please select option 5 in the launcher."
|
||||||
|
)
|
||||||
await asyncio.sleep(2)
|
await asyncio.sleep(2)
|
||||||
print("\nIf you continue you will remove these instanes.\n")
|
print("\nIf you continue you will remove these instanes.\n")
|
||||||
for instance in list(instances.keys()):
|
for instance in list(instances.keys()):
|
||||||
@ -290,7 +310,7 @@ def extras_selector():
|
|||||||
return selected
|
return selected
|
||||||
|
|
||||||
|
|
||||||
def development_choice(reinstall = False):
|
def development_choice(reinstall=False):
|
||||||
while True:
|
while True:
|
||||||
print("\n")
|
print("\n")
|
||||||
print("Do you want to install stable or development version?")
|
print("Do you want to install stable or development version?")
|
||||||
@ -301,18 +321,22 @@ def development_choice(reinstall = False):
|
|||||||
selected = extras_selector()
|
selected = extras_selector()
|
||||||
if choice == "1":
|
if choice == "1":
|
||||||
update_red(
|
update_red(
|
||||||
dev=False, reinstall=reinstall, voice=True if "voice" in selected else False,
|
dev=False,
|
||||||
|
reinstall=reinstall,
|
||||||
|
voice=True if "voice" in selected else False,
|
||||||
docs=True if "docs" in selected else False,
|
docs=True if "docs" in selected else False,
|
||||||
test=True if "test" in selected else False,
|
test=True if "test" in selected else False,
|
||||||
mongo=True if "mongo" in selected else False
|
mongo=True if "mongo" in selected else False,
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
elif choice == "2":
|
elif choice == "2":
|
||||||
update_red(
|
update_red(
|
||||||
dev=True, reinstall=reinstall, voice=True if "voice" in selected else False,
|
dev=True,
|
||||||
|
reinstall=reinstall,
|
||||||
|
voice=True if "voice" in selected else False,
|
||||||
docs=True if "docs" in selected else False,
|
docs=True if "docs" in selected else False,
|
||||||
test=True if "test" in selected else False,
|
test=True if "test" in selected else False,
|
||||||
mongo=True if "mongo" in selected else False
|
mongo=True if "mongo" in selected else False,
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -332,12 +356,17 @@ def debug_info():
|
|||||||
os_info = distro.linux_distribution()
|
os_info = distro.linux_distribution()
|
||||||
osver = "{} {}".format(os_info[0], os_info[1]).strip()
|
osver = "{} {}".format(os_info[0], os_info[1]).strip()
|
||||||
user_who_ran = getpass.getuser()
|
user_who_ran = getpass.getuser()
|
||||||
info = "Debug Info for Red\n\n" +\
|
info = "Debug Info for Red\n\n" + "Python version: {}\n".format(
|
||||||
"Python version: {}\n".format(pyver) +\
|
pyver
|
||||||
"Red version: {}\n".format(redver) +\
|
) + "Red version: {}\n".format(
|
||||||
"OS version: {}\n".format(osver) +\
|
redver
|
||||||
"System arch: {}\n".format(platform.machine()) +\
|
) + "OS version: {}\n".format(
|
||||||
"User: {}\n".format(user_who_ran)
|
osver
|
||||||
|
) + "System arch: {}\n".format(
|
||||||
|
platform.machine()
|
||||||
|
) + "User: {}\n".format(
|
||||||
|
user_who_ran
|
||||||
|
)
|
||||||
print(info)
|
print(info)
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
@ -385,7 +414,9 @@ def main_menu():
|
|||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
clear_screen()
|
clear_screen()
|
||||||
print("==== Reinstall Red ====")
|
print("==== Reinstall Red ====")
|
||||||
print("1. Reinstall Red requirements (discard code changes, keep data and 3rd party cogs)")
|
print(
|
||||||
|
"1. Reinstall Red requirements (discard code changes, keep data and 3rd party cogs)"
|
||||||
|
)
|
||||||
print("2. Reset all data")
|
print("2. Reset all data")
|
||||||
print("3. Factory reset (discard code changes, reset all data)")
|
print("3. Factory reset (discard code changes, reset all data)")
|
||||||
print("\n")
|
print("\n")
|
||||||
@ -411,8 +442,7 @@ def main_menu():
|
|||||||
def main():
|
def main():
|
||||||
if not PYTHON_OK:
|
if not PYTHON_OK:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Red requires Python 3.5 or greater. "
|
"Red requires Python 3.5 or greater. " "Please install the correct version!"
|
||||||
"Please install the correct version!"
|
|
||||||
)
|
)
|
||||||
if args.debuginfo: # Check first since the function triggers an exit
|
if args.debuginfo: # Check first since the function triggers an exit
|
||||||
debug_info()
|
debug_info()
|
||||||
@ -423,15 +453,9 @@ def main():
|
|||||||
"Please try again using only one of --update or --update-dev"
|
"Please try again using only one of --update or --update-dev"
|
||||||
)
|
)
|
||||||
if args.update:
|
if args.update:
|
||||||
update_red(
|
update_red(voice=args.voice, docs=args.docs, test=args.test, mongo=args.mongo)
|
||||||
voice=args.voice, docs=args.docs,
|
|
||||||
test=args.test, mongo=args.mongo
|
|
||||||
)
|
|
||||||
elif args.update_dev:
|
elif args.update_dev:
|
||||||
update_red(
|
update_red(dev=True, voice=args.voice, docs=args.docs, test=args.test, mongo=args.mongo)
|
||||||
dev=True, voice=args.voice, docs=args.docs,
|
|
||||||
test=args.test, mongo=args.mongo
|
|
||||||
)
|
|
||||||
|
|
||||||
if INTERACTIVE_MODE:
|
if INTERACTIVE_MODE:
|
||||||
main_menu()
|
main_menu()
|
||||||
|
|||||||
107
redbot/setup.py
107
redbot/setup.py
@ -20,7 +20,7 @@ from redbot.core.drivers.red_json import JSON
|
|||||||
|
|
||||||
config_dir = None
|
config_dir = None
|
||||||
appdir = appdirs.AppDirs("Red-DiscordBot")
|
appdir = appdirs.AppDirs("Red-DiscordBot")
|
||||||
if sys.platform == 'linux':
|
if sys.platform == "linux":
|
||||||
if 0 < os.getuid() < 1000:
|
if 0 < os.getuid() < 1000:
|
||||||
config_dir = Path(appdir.site_data_dir)
|
config_dir = Path(appdir.site_data_dir)
|
||||||
if not config_dir:
|
if not config_dir:
|
||||||
@ -28,27 +28,17 @@ if not config_dir:
|
|||||||
try:
|
try:
|
||||||
config_dir.mkdir(parents=True, exist_ok=True)
|
config_dir.mkdir(parents=True, exist_ok=True)
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
print(
|
print("You don't have permission to write to " "'{}'\nExiting...".format(config_dir))
|
||||||
"You don't have permission to write to "
|
|
||||||
"'{}'\nExiting...".format(config_dir))
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
config_file = config_dir / 'config.json'
|
config_file = config_dir / "config.json"
|
||||||
|
|
||||||
|
|
||||||
def parse_cli_args():
|
def parse_cli_args():
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(description="Red - Discord Bot's instance manager (V3)")
|
||||||
description="Red - Discord Bot's instance manager (V3)"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--delete", "-d",
|
"--delete", "-d", help="Interactively delete an instance", action="store_true"
|
||||||
help="Interactively delete an instance",
|
|
||||||
action="store_true"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--edit", "-e",
|
|
||||||
help="Interactively edit an instance",
|
|
||||||
action="store_true"
|
|
||||||
)
|
)
|
||||||
|
parser.add_argument("--edit", "-e", help="Interactively edit an instance", action="store_true")
|
||||||
return parser.parse_known_args()
|
return parser.parse_known_args()
|
||||||
|
|
||||||
|
|
||||||
@ -79,18 +69,20 @@ def save_config(name, data, remove=False):
|
|||||||
def get_data_dir():
|
def get_data_dir():
|
||||||
default_data_dir = Path(appdir.user_data_dir)
|
default_data_dir = Path(appdir.user_data_dir)
|
||||||
|
|
||||||
print("Hello! Before we begin the full configuration process we need to"
|
print(
|
||||||
|
"Hello! Before we begin the full configuration process we need to"
|
||||||
" gather some initial information about where you'd like us"
|
" gather some initial information about where you'd like us"
|
||||||
" to store your bot's data. We've attempted to figure out a"
|
" to store your bot's data. We've attempted to figure out a"
|
||||||
" sane default data location which is printed below. If you don't"
|
" sane default data location which is printed below. If you don't"
|
||||||
" want to change this default please press [ENTER], otherwise"
|
" want to change this default please press [ENTER], otherwise"
|
||||||
" input your desired data location.")
|
" input your desired data location."
|
||||||
|
)
|
||||||
print()
|
print()
|
||||||
print("Default: {}".format(default_data_dir))
|
print("Default: {}".format(default_data_dir))
|
||||||
|
|
||||||
new_path = input('> ')
|
new_path = input("> ")
|
||||||
|
|
||||||
if new_path != '':
|
if new_path != "":
|
||||||
new_path = Path(new_path)
|
new_path = Path(new_path)
|
||||||
default_data_dir = new_path
|
default_data_dir = new_path
|
||||||
|
|
||||||
@ -98,13 +90,14 @@ def get_data_dir():
|
|||||||
try:
|
try:
|
||||||
default_data_dir.mkdir(parents=True, exist_ok=True)
|
default_data_dir.mkdir(parents=True, exist_ok=True)
|
||||||
except OSError:
|
except OSError:
|
||||||
print("We were unable to create your chosen directory."
|
print(
|
||||||
|
"We were unable to create your chosen directory."
|
||||||
" You may need to restart this process with admin"
|
" You may need to restart this process with admin"
|
||||||
" privileges.")
|
" privileges."
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
print("You have chosen {} to be your data directory."
|
print("You have chosen {} to be your data directory." "".format(default_data_dir))
|
||||||
"".format(default_data_dir))
|
|
||||||
if not confirm("Please confirm (y/n):"):
|
if not confirm("Please confirm (y/n):"):
|
||||||
print("Please start the process over.")
|
print("Please start the process over.")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
@ -112,10 +105,7 @@ def get_data_dir():
|
|||||||
|
|
||||||
|
|
||||||
def get_storage_type():
|
def get_storage_type():
|
||||||
storage_dict = {
|
storage_dict = {1: "JSON", 2: "MongoDB"}
|
||||||
1: "JSON",
|
|
||||||
2: "MongoDB"
|
|
||||||
}
|
|
||||||
storage = None
|
storage = None
|
||||||
while storage is None:
|
while storage is None:
|
||||||
print()
|
print()
|
||||||
@ -137,8 +127,10 @@ def get_name():
|
|||||||
name = ""
|
name = ""
|
||||||
while len(name) == 0:
|
while len(name) == 0:
|
||||||
print()
|
print()
|
||||||
print("Please enter a name for your instance, this name cannot include spaces"
|
print(
|
||||||
" and it will be used to run your bot from here on out.")
|
"Please enter a name for your instance, this name cannot include spaces"
|
||||||
|
" and it will be used to run your bot from here on out."
|
||||||
|
)
|
||||||
name = input("> ")
|
name = input("> ")
|
||||||
if " " in name:
|
if " " in name:
|
||||||
name = ""
|
name = ""
|
||||||
@ -154,41 +146,40 @@ def basic_setup():
|
|||||||
default_data_dir = get_data_dir()
|
default_data_dir = get_data_dir()
|
||||||
|
|
||||||
default_dirs = deepcopy(basic_config_default)
|
default_dirs = deepcopy(basic_config_default)
|
||||||
default_dirs['DATA_PATH'] = str(default_data_dir.resolve())
|
default_dirs["DATA_PATH"] = str(default_data_dir.resolve())
|
||||||
|
|
||||||
storage = get_storage_type()
|
storage = get_storage_type()
|
||||||
|
|
||||||
storage_dict = {
|
storage_dict = {1: "JSON", 2: "MongoDB"}
|
||||||
1: "JSON",
|
default_dirs["STORAGE_TYPE"] = storage_dict.get(storage, 1)
|
||||||
2: "MongoDB"
|
|
||||||
}
|
|
||||||
default_dirs['STORAGE_TYPE'] = storage_dict.get(storage, 1)
|
|
||||||
|
|
||||||
if storage_dict.get(storage, 1) == "MongoDB":
|
if storage_dict.get(storage, 1) == "MongoDB":
|
||||||
from redbot.core.drivers.red_mongo import get_config_details
|
from redbot.core.drivers.red_mongo import get_config_details
|
||||||
default_dirs['STORAGE_DETAILS'] = get_config_details()
|
|
||||||
|
default_dirs["STORAGE_DETAILS"] = get_config_details()
|
||||||
else:
|
else:
|
||||||
default_dirs['STORAGE_DETAILS'] = {}
|
default_dirs["STORAGE_DETAILS"] = {}
|
||||||
|
|
||||||
name = get_name()
|
name = get_name()
|
||||||
save_config(name, default_dirs)
|
save_config(name, default_dirs)
|
||||||
|
|
||||||
print()
|
print()
|
||||||
print("Your basic configuration has been saved. Please run `redbot <name>` to"
|
print(
|
||||||
" continue your setup process and to run the bot.")
|
"Your basic configuration has been saved. Please run `redbot <name>` to"
|
||||||
|
" continue your setup process and to run the bot."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def json_to_mongo(current_data_dir: Path, storage_details: dict):
|
async def json_to_mongo(current_data_dir: Path, storage_details: dict):
|
||||||
from redbot.core.drivers.red_mongo import Mongo
|
from redbot.core.drivers.red_mongo import Mongo
|
||||||
|
|
||||||
core_data_file = list(current_data_dir.glob("core/settings.json"))[0]
|
core_data_file = list(current_data_dir.glob("core/settings.json"))[0]
|
||||||
m = Mongo("Core", "0", **storage_details)
|
m = Mongo("Core", "0", **storage_details)
|
||||||
with core_data_file.open(mode="r") as f:
|
with core_data_file.open(mode="r") as f:
|
||||||
core_data = json.loads(f.read())
|
core_data = json.loads(f.read())
|
||||||
collection = m.get_collection()
|
collection = m.get_collection()
|
||||||
await collection.update_one(
|
await collection.update_one(
|
||||||
{'_id': m.unique_cog_identifier},
|
{"_id": m.unique_cog_identifier}, update={"$set": core_data["0"]}, upsert=True
|
||||||
update={"$set": core_data["0"]},
|
|
||||||
upsert=True
|
|
||||||
)
|
)
|
||||||
for p in current_data_dir.glob("cogs/**/settings.json"):
|
for p in current_data_dir.glob("cogs/**/settings.json"):
|
||||||
with p.open(mode="r") as f:
|
with p.open(mode="r") as f:
|
||||||
@ -200,14 +191,13 @@ async def json_to_mongo(current_data_dir: Path, storage_details: dict):
|
|||||||
cog_c = cog_m.get_collection()
|
cog_c = cog_m.get_collection()
|
||||||
for ident in list(cog_data.keys()):
|
for ident in list(cog_data.keys()):
|
||||||
await cog_c.update_one(
|
await cog_c.update_one(
|
||||||
{"_id": cog_m.unique_cog_identifier},
|
{"_id": cog_m.unique_cog_identifier}, update={"$set": cog_data[cog_i]}, upsert=True
|
||||||
update={"$set": cog_data[cog_i]},
|
|
||||||
upsert=True
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def mongo_to_json(current_data_dir: Path, storage_details: dict):
|
async def mongo_to_json(current_data_dir: Path, storage_details: dict):
|
||||||
from redbot.core.drivers.red_mongo import Mongo
|
from redbot.core.drivers.red_mongo import Mongo
|
||||||
|
|
||||||
m = Mongo("Core", "0", **storage_details)
|
m = Mongo("Core", "0", **storage_details)
|
||||||
db = m.db
|
db = m.db
|
||||||
collection_names = await db.collection_names(include_system_collections=False)
|
collection_names = await db.collection_names(include_system_collections=False)
|
||||||
@ -250,9 +240,7 @@ async def edit_instance():
|
|||||||
default_dirs = deepcopy(basic_config_default)
|
default_dirs = deepcopy(basic_config_default)
|
||||||
|
|
||||||
current_data_dir = Path(instance_data["DATA_PATH"])
|
current_data_dir = Path(instance_data["DATA_PATH"])
|
||||||
print(
|
print("You have selected '{}' as the instance to modify.".format(selected))
|
||||||
"You have selected '{}' as the instance to modify.".format(selected)
|
|
||||||
)
|
|
||||||
if not confirm("Please confirm (y/n):"):
|
if not confirm("Please confirm (y/n):"):
|
||||||
print("Ok, we will not continue then.")
|
print("Ok, we will not continue then.")
|
||||||
return
|
return
|
||||||
@ -273,13 +261,11 @@ async def edit_instance():
|
|||||||
if confirm("Would you like to change the storage type? (y/n):"):
|
if confirm("Would you like to change the storage type? (y/n):"):
|
||||||
storage = get_storage_type()
|
storage = get_storage_type()
|
||||||
|
|
||||||
storage_dict = {
|
storage_dict = {1: "JSON", 2: "MongoDB"}
|
||||||
1: "JSON",
|
|
||||||
2: "MongoDB"
|
|
||||||
}
|
|
||||||
default_dirs["STORAGE_TYPE"] = storage_dict[storage]
|
default_dirs["STORAGE_TYPE"] = storage_dict[storage]
|
||||||
if storage_dict.get(storage, 1) == "MongoDB":
|
if storage_dict.get(storage, 1) == "MongoDB":
|
||||||
from redbot.core.drivers.red_mongo import get_config_details
|
from redbot.core.drivers.red_mongo import get_config_details
|
||||||
|
|
||||||
storage_details = get_config_details()
|
storage_details = get_config_details()
|
||||||
default_dirs["STORAGE_DETAILS"] = storage_details
|
default_dirs["STORAGE_DETAILS"] = storage_details
|
||||||
|
|
||||||
@ -297,9 +283,7 @@ async def edit_instance():
|
|||||||
save_config(selected, {}, remove=True)
|
save_config(selected, {}, remove=True)
|
||||||
save_config(name, default_dirs)
|
save_config(name, default_dirs)
|
||||||
|
|
||||||
print(
|
print("Your basic configuration has been edited")
|
||||||
"Your basic configuration has been edited"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def create_backup(selected, instance_data):
|
async def create_backup(selected, instance_data):
|
||||||
@ -317,9 +301,7 @@ async def create_backup(selected, instance_data):
|
|||||||
os.chdir(str(pth.parent))
|
os.chdir(str(pth.parent))
|
||||||
with tarfile.open(str(backup_file), "w:gz") as tar:
|
with tarfile.open(str(backup_file), "w:gz") as tar:
|
||||||
tar.add(pth.stem)
|
tar.add(pth.stem)
|
||||||
print("A backup of {} has been made. It is at {}".format(
|
print("A backup of {} has been made. It is at {}".format(selected, backup_file))
|
||||||
selected, backup_file
|
|
||||||
))
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print("Backing up the instance's data...")
|
print("Backing up the instance's data...")
|
||||||
@ -333,11 +315,7 @@ async def create_backup(selected, instance_data):
|
|||||||
os.chdir(str(pth.parent)) # str is used here because 3.5 support
|
os.chdir(str(pth.parent)) # str is used here because 3.5 support
|
||||||
with tarfile.open(str(backup_file), "w:gz") as tar:
|
with tarfile.open(str(backup_file), "w:gz") as tar:
|
||||||
tar.add(pth.stem) # add all files in that directory
|
tar.add(pth.stem) # add all files in that directory
|
||||||
print(
|
print("A backup of {} has been made. It is at {}".format(selected, backup_file))
|
||||||
"A backup of {} has been made. It is at {}".format(
|
|
||||||
selected, backup_file
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def remove_instance(selected, instance_data):
|
async def remove_instance(selected, instance_data):
|
||||||
@ -390,6 +368,7 @@ def main():
|
|||||||
else:
|
else:
|
||||||
basic_setup()
|
basic_setup()
|
||||||
|
|
||||||
|
|
||||||
args, _ = parse_cli_args()
|
args, _ = parse_cli_args()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user