refactor(all): add black to all code

This commit is contained in:
Romain J 2020-06-06 18:51:47 +02:00
parent cdb891d435
commit 9869312ee8
19 changed files with 428 additions and 391 deletions

View file

@ -1,16 +1,15 @@
import subprocess import subprocess
from collections import namedtuple from collections import namedtuple
build = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']) \ build = subprocess.check_output(["git", "rev-parse", "--short", "HEAD"]).decode()
.decode()
VersionInfo = namedtuple('VersionInfo', 'major minor micro releaselevel build') VersionInfo = namedtuple("VersionInfo", "major minor micro releaselevel build")
version_info = VersionInfo( version_info = VersionInfo(major=3, minor=0, micro=0, releaselevel="alpha", build=build)
major=3, minor=0, micro=0,
releaselevel='alpha', build=build
)
__version__ = "v{}.{}.{}-{}.{}".format( __version__ = "v{}.{}.{}-{}.{}".format(
version_info.major, version_info.minor, version_info.micro, version_info.major,
version_info.releaselevel, version_info.build version_info.minor,
).replace('\n', '') version_info.micro,
version_info.releaselevel,
version_info.build,
).replace("\n", "")

View file

@ -33,13 +33,10 @@ def list_instances() -> NoReturn:
instances = list(datas.keys()) instances = list(datas.keys())
info = { info = {"title": "Instances", "rows": []}
'title': "Instances",
'rows': []
}
for instance in instances: for instance in instances:
info['rows'].append(f"-> {instance}") info["rows"].append(f"-> {instance}")
print(bordered(info)) print(bordered(info))
sys.exit(0) sys.exit(0)
@ -49,7 +46,7 @@ def debug_info() -> NoReturn:
"""Show debug infos relatives to the bot """Show debug infos relatives to the bot
""" """
python_version = sys.version.replace('\n', '') python_version = sys.version.replace("\n", "")
pip_version = pip.__version__ pip_version = pip.__version__
tuxbot_version = __version__ tuxbot_version = __version__
dpy_version = discord.__version__ dpy_version = discord.__version__
@ -60,8 +57,8 @@ def debug_info() -> NoReturn:
runner = getpass.getuser() runner = getpass.getuser()
info = { info = {
'title': "Debug Info", "title": "Debug Info",
'rows': [ "rows": [
f"Tuxbot version: {tuxbot_version}", f"Tuxbot version: {tuxbot_version}",
"", "",
f"Python version: {python_version}", f"Python version: {python_version}",
@ -72,7 +69,7 @@ def debug_info() -> NoReturn:
f"OS info: {os_info}", f"OS info: {os_info}",
f"System arch: {platform.machine()}", f"System arch: {platform.machine()}",
f"User: {runner}", f"User: {runner}",
] ],
} }
print(bordered(info)) print(bordered(info))
@ -92,31 +89,20 @@ def parse_cli_flags(args: list) -> Namespace:
""" """
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Tuxbot - OpenSource bot", description="Tuxbot - OpenSource bot",
usage="tuxbot <instance_name> [arguments]" usage="tuxbot <instance_name> [arguments]",
) )
parser.add_argument( parser.add_argument(
"--version", "-V", "--version", "-V", action="store_true", help="Show tuxbot's used version"
action="store_true",
help="Show tuxbot's used version"
) )
parser.add_argument("--debug", action="store_true", help="Show debug information.")
parser.add_argument( parser.add_argument(
"--debug", "--list-instances", "-L", action="store_true", help="List all instance names"
action="store_true",
help="Show debug information."
) )
parser.add_argument("--token", "-T", type=str, help="Run Tuxbot with passed token")
parser.add_argument( parser.add_argument(
"--list-instances", "-L", "instance_name",
action="store_true", nargs="?",
help="List all instance names" help="Name of the bot instance created during `tuxbot-setup`.",
)
parser.add_argument(
"--token", "-T",
type=str,
help="Run Tuxbot with passed token"
)
parser.add_argument(
"instance_name", nargs="?",
help="Name of the bot instance created during `tuxbot-setup`."
) )
args = parser.parse_args(args) args = parser.parse_args(args)
@ -151,9 +137,7 @@ async def shutdown_handler(tux: Tux, signal_type, exit_code=None) -> NoReturn:
try: try:
await tux.logout() await tux.logout()
finally: finally:
pending = [ pending = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
t for t in asyncio.all_tasks() if t is not asyncio.current_task()
]
for task in pending: for task in pending:
task.cancel() task.cancel()
@ -178,10 +162,7 @@ async def run_bot(tux: Tux, cli_flags: Namespace) -> None:
""" """
data_path = data_manager.data_path(tux.instance_name) data_path = data_manager.data_path(tux.instance_name)
tuxbot.logging.init_logging( tuxbot.logging.init_logging(10, location=data_path / "logs")
10,
location=data_path / "logs"
)
log.debug("====Basic Config====") log.debug("====Basic Config====")
log.debug("Data Path: %s", data_path) log.debug("Data Path: %s", data_path)
@ -189,7 +170,7 @@ async def run_bot(tux: Tux, cli_flags: Namespace) -> None:
if cli_flags.token: if cli_flags.token:
token = cli_flags.token token = cli_flags.token
else: else:
token = tux.config('core').get('token') token = tux.config("core").get("token")
if not token: if not token:
log.critical("Token must be set if you want to login.") log.critical("Token must be set if you want to login.")
@ -226,23 +207,26 @@ def main() -> NoReturn:
try: try:
if not cli_flags.instance_name: if not cli_flags.instance_name:
print(Fore.RED print(
+ "No instance provided ! " Fore.RED + "No instance provided ! "
"You can use 'tuxbot -L' to list all available instances" "You can use 'tuxbot -L' to list all available instances"
+ Style.RESET_ALL) + Style.RESET_ALL
)
sys.exit(ExitCodes.CRITICAL) sys.exit(ExitCodes.CRITICAL)
tux = Tux( tux = Tux(
cli_flags=cli_flags, cli_flags=cli_flags,
description="Tuxbot, made from and for OpenSource", description="Tuxbot, made from and for OpenSource",
dm_help=None dm_help=None,
) )
loop.run_until_complete(run_bot(tux, cli_flags)) loop.run_until_complete(run_bot(tux, cli_flags))
except KeyboardInterrupt: except KeyboardInterrupt:
print(Fore.RED print(
+ "Please use <prefix>quit instead of Ctrl+C to Shutdown!" Fore.RED
+ Style.RESET_ALL) + "Please use <prefix>quit instead of Ctrl+C to Shutdown!"
+ Style.RESET_ALL
)
log.warning("Please use <prefix>quit instead of Ctrl+C to Shutdown!") log.warning("Please use <prefix>quit instead of Ctrl+C to Shutdown!")
log.error("Received KeyboardInterrupt") log.error("Received KeyboardInterrupt")
if tux is not None: if tux is not None:

View file

@ -1,6 +1,18 @@
from .anti_raid import Warnings from collections import namedtuple
from .anti_raid import AntiRaid
from ...core.bot import Tux from ...core.bot import Tux
VersionInfo = namedtuple("VersionInfo", "major minor micro releaselevel")
version_info = VersionInfo(major=1, minor=0, micro=0, releaselevel="alpha")
__version__ = "v{}.{}.{}-{}".format(
version_info.major,
version_info.minor,
version_info.micro,
version_info.releaselevel
).replace("\n", "")
def setup(bot: Tux): def setup(bot: Tux):
bot.add_cog(Warnings(bot)) bot.add_cog(AntiRaid(bot))

View file

@ -15,8 +15,8 @@ class AntiRaid(commands.Cog, name="AntiRaid"):
self.bot = bot self.bot = bot
@commands.group( @commands.group(
name='anti_raid', name="anti_raid",
alias=['anti-raid', 'raid_protect', 'raid-protect', 'no_raid', 'no-raid'] alias=["anti-raid", "raid_protect", "raid-protect", "no_raid", "no-raid"],
) )
@commands.guild_only() @commands.guild_only()
@checks.is_admin() @checks.is_admin()

View file

@ -1,6 +1,18 @@
from collections import namedtuple
from .images import Images from .images import Images
from ...core.bot import Tux from ...core.bot import Tux
VersionInfo = namedtuple("VersionInfo", "major minor micro releaselevel")
version_info = VersionInfo(major=1, minor=0, micro=0, releaselevel="alpha")
__version__ = "v{}.{}.{}-{}".format(
version_info.major,
version_info.minor,
version_info.micro,
version_info.releaselevel
).replace("\n", "")
def setup(bot: Tux): def setup(bot: Tux):
bot.add_cog(Images(bot)) bot.add_cog(Images(bot))

View file

@ -28,26 +28,34 @@ class Images(commands.Cog, name="Images"):
data = BytesIO(await r.read()) data = BytesIO(await r.read())
await ctx.send( await ctx.send(file=discord.File(data, "output.png"))
file=discord.File(data, "output.png")
)
@command_extra(name="phcomment") @command_extra(name="phcomment")
@commands.cooldown(1, 5, commands.BucketType.user) @commands.cooldown(1, 5, commands.BucketType.user)
async def _phcomment(self, ctx: ContextPlus, user: discord.User = None, *, message: commands.clean_content(fix_channel_mentions=True, escape_markdown=True)): async def _phcomment(
self,
ctx: ContextPlus,
user: discord.User = None,
*,
message: commands.clean_content(
fix_channel_mentions=True, escape_markdown=True
),
):
async with ctx.typing(): async with ctx.typing():
message = message.replace("&", "%26") message = message.replace("&", "%26")
if user is None: if user is None:
avatar = ctx.author.avatar_url_as(format='png') avatar = ctx.author.avatar_url_as(format="png")
username = ctx.author.name username = ctx.author.name
else: else:
avatar = user.avatar_url_as(format='png') avatar = user.avatar_url_as(format="png")
username = user.name username = user.name
url = f"{self.image_api}/ph/comment" \ url = (
f"?image={avatar}" \ f"{self.image_api}/ph/comment"
f"&username={username}" \ f"?image={avatar}"
f"&message={message}" f"&username={username}"
f"&message={message}"
)
async with self.bot.session.get(url) as r: async with self.bot.session.get(url) as r:
if r.status != 200: if r.status != 200:
@ -55,18 +63,25 @@ class Images(commands.Cog, name="Images"):
data = BytesIO(await r.read()) data = BytesIO(await r.read())
await ctx.send( await ctx.send(file=discord.File(data, "output.png"))
file=discord.File(data, "output.png")
)
@command_extra(name="phvideo") @command_extra(name="phvideo")
@commands.cooldown(1, 5, commands.BucketType.user) @commands.cooldown(1, 5, commands.BucketType.user)
async def _phvideo(self, ctx: ContextPlus, image: str, author: discord.User, *, title: commands.clean_content(fix_channel_mentions=True, escape_markdown=True)): async def _phvideo(
self,
ctx: ContextPlus,
image: str,
author: discord.User,
*,
title: commands.clean_content(fix_channel_mentions=True, escape_markdown=True),
):
async with ctx.typing(): async with ctx.typing():
url = f"{self.image_api}/ph/video" \ url = (
f"?image={image}" \ f"{self.image_api}/ph/video"
f"&username={author.name}" \ f"?image={image}"
f"&title={title}" f"&username={author.name}"
f"&title={title}"
)
async with self.bot.session.get(url) as r: async with self.bot.session.get(url) as r:
if r.status != 200: if r.status != 200:
@ -74,9 +89,7 @@ class Images(commands.Cog, name="Images"):
data = BytesIO(await r.read()) data = BytesIO(await r.read())
await ctx.send( await ctx.send(file=discord.File(data, "output.png"))
file=discord.File(data, "output.png")
)
@flags.add_flag("--text1", type=str) @flags.add_flag("--text1", type=str)
@flags.add_flag("--text2", type=str) @flags.add_flag("--text2", type=str)
@ -88,7 +101,7 @@ class Images(commands.Cog, name="Images"):
passed_flags["text4"] = passed_flags.get("text1") passed_flags["text4"] = passed_flags.get("text1")
passed_flags["text5"] = passed_flags.get("text2") passed_flags["text5"] = passed_flags.get("text2")
await self._send_meme(ctx, 'balloon', **passed_flags) await self._send_meme(ctx, "balloon", **passed_flags)
@flags.add_flag("--text1", type=str) @flags.add_flag("--text1", type=str)
@flags.add_flag("--text2", type=str) @flags.add_flag("--text2", type=str)
@ -96,48 +109,48 @@ class Images(commands.Cog, name="Images"):
@command_extra(name="butterfly") @command_extra(name="butterfly")
@commands.cooldown(1, 5, commands.BucketType.user) @commands.cooldown(1, 5, commands.BucketType.user)
async def _butterfly(self, ctx: ContextPlus, **passed_flags): async def _butterfly(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'butterfly', **passed_flags) await self._send_meme(ctx, "butterfly", **passed_flags)
@flags.add_flag("--text1", type=str) @flags.add_flag("--text1", type=str)
@flags.add_flag("--text2", type=str) @flags.add_flag("--text2", type=str)
@command_extra(name="buttons") @command_extra(name="buttons")
@commands.cooldown(1, 5, commands.BucketType.user) @commands.cooldown(1, 5, commands.BucketType.user)
async def _buttons(self, ctx: ContextPlus, **passed_flags): async def _buttons(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'buttons', **passed_flags) await self._send_meme(ctx, "buttons", **passed_flags)
@flags.add_flag("--text1", type=str) @flags.add_flag("--text1", type=str)
@command_extra(name="cmm") @command_extra(name="cmm")
@commands.cooldown(1, 5, commands.BucketType.user) @commands.cooldown(1, 5, commands.BucketType.user)
async def _cmm(self, ctx: ContextPlus, **passed_flags): async def _cmm(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'change_my_mind', **passed_flags) await self._send_meme(ctx, "change_my_mind", **passed_flags)
@flags.add_flag("--text1", type=str) @flags.add_flag("--text1", type=str)
@flags.add_flag("--text2", type=str) @flags.add_flag("--text2", type=str)
@command_extra(name="drake") @command_extra(name="drake")
@commands.cooldown(1, 5, commands.BucketType.user) @commands.cooldown(1, 5, commands.BucketType.user)
async def _drake(self, ctx: ContextPlus, **passed_flags): async def _drake(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'drake', **passed_flags) await self._send_meme(ctx, "drake", **passed_flags)
@flags.add_flag("--text1", type=str) @flags.add_flag("--text1", type=str)
@flags.add_flag("--text2", type=str, default=False) @flags.add_flag("--text2", type=str, default=False)
@command_extra(name="fry") @command_extra(name="fry")
@commands.cooldown(1, 5, commands.BucketType.user) @commands.cooldown(1, 5, commands.BucketType.user)
async def _fry(self, ctx: ContextPlus, **passed_flags): async def _fry(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'fry', **passed_flags) await self._send_meme(ctx, "fry", **passed_flags)
@flags.add_flag("--text1", type=str) @flags.add_flag("--text1", type=str)
@flags.add_flag("--text2", type=str, default=False) @flags.add_flag("--text2", type=str, default=False)
@command_extra(name="imagination") @command_extra(name="imagination")
@commands.cooldown(1, 5, commands.BucketType.user) @commands.cooldown(1, 5, commands.BucketType.user)
async def _imagination(self, ctx: ContextPlus, **passed_flags): async def _imagination(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'imagination', **passed_flags) await self._send_meme(ctx, "imagination", **passed_flags)
@flags.add_flag("--text1", type=str) @flags.add_flag("--text1", type=str)
@flags.add_flag("--text2", type=str, default=False) @flags.add_flag("--text2", type=str, default=False)
@command_extra(name="everywhere") @command_extra(name="everywhere")
@commands.cooldown(1, 5, commands.BucketType.user) @commands.cooldown(1, 5, commands.BucketType.user)
async def _everywhere(self, ctx: ContextPlus, **passed_flags): async def _everywhere(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'everywhere', **passed_flags) await self._send_meme(ctx, "everywhere", **passed_flags)
@flags.add_flag("--text1", type=str) @flags.add_flag("--text1", type=str)
@flags.add_flag("--text2", type=str) @flags.add_flag("--text2", type=str)
@ -145,13 +158,13 @@ class Images(commands.Cog, name="Images"):
@command_extra(name="choice") @command_extra(name="choice")
@commands.cooldown(1, 5, commands.BucketType.user) @commands.cooldown(1, 5, commands.BucketType.user)
async def _choice(self, ctx: ContextPlus, **passed_flags): async def _choice(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'choice', **passed_flags) await self._send_meme(ctx, "choice", **passed_flags)
@flags.add_flag("--text1", type=str) @flags.add_flag("--text1", type=str)
@command_extra(name="pika") @command_extra(name="pika")
@commands.cooldown(1, 5, commands.BucketType.user) @commands.cooldown(1, 5, commands.BucketType.user)
async def _pika(self, ctx: ContextPlus, **passed_flags): async def _pika(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'pika', **passed_flags) await self._send_meme(ctx, "pika", **passed_flags)
@flags.add_flag("--text1", type=str) @flags.add_flag("--text1", type=str)
@flags.add_flag("--text2", type=str) @flags.add_flag("--text2", type=str)
@ -159,17 +172,17 @@ class Images(commands.Cog, name="Images"):
@command_extra(name="pkp") @command_extra(name="pkp")
@commands.cooldown(1, 5, commands.BucketType.user) @commands.cooldown(1, 5, commands.BucketType.user)
async def _pkp(self, ctx: ContextPlus, **passed_flags): async def _pkp(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'pkp', **passed_flags) await self._send_meme(ctx, "pkp", **passed_flags)
@flags.add_flag("--text1", type=str) @flags.add_flag("--text1", type=str)
@flags.add_flag("--text2", type=str) @flags.add_flag("--text2", type=str)
@command_extra(name="puppet") @command_extra(name="puppet")
@commands.cooldown(1, 5, commands.BucketType.user) @commands.cooldown(1, 5, commands.BucketType.user)
async def _puppet(self, ctx: ContextPlus, **passed_flags): async def _puppet(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'puppet', **passed_flags) await self._send_meme(ctx, "puppet", **passed_flags)
@flags.add_flag("--text1", type=str) @flags.add_flag("--text1", type=str)
@command_extra(name="scroll_of_truth", alias=['sot']) @command_extra(name="scroll_of_truth", alias=["sot"])
@commands.cooldown(1, 5, commands.BucketType.user) @commands.cooldown(1, 5, commands.BucketType.user)
async def _sot(self, ctx: ContextPlus, **passed_flags): async def _sot(self, ctx: ContextPlus, **passed_flags):
await self._send_meme(ctx, 'scroll_of_truth', **passed_flags) await self._send_meme(ctx, "scroll_of_truth", **passed_flags)

View file

@ -1,10 +1,21 @@
import logging import logging
from collections import namedtuple
from discord.ext import commands from discord.ext import commands
from .logs import Logs, GatewayHandler, on_error from .logs import Logs, GatewayHandler, on_error
from ...core.bot import Tux from ...core.bot import Tux
VersionInfo = namedtuple("VersionInfo", "major minor micro releaselevel")
version_info = VersionInfo(major=2, minor=0, micro=0, releaselevel="alpha")
__version__ = "v{}.{}.{}-{}".format(
version_info.major,
version_info.minor,
version_info.micro,
version_info.releaselevel
).replace("\n", "")
def setup(bot: Tux): def setup(bot: Tux):
cog = Logs(bot) cog = Logs(bot)

View file

@ -30,16 +30,17 @@ class GatewayHandler(logging.Handler):
super().__init__(logging.INFO) super().__init__(logging.INFO)
def filter(self, record): def filter(self, record):
return record.name == 'discord.gateway' \ return (
or 'Shard ID' in record.msg \ record.name == "discord.gateway"
or 'Websocket closed ' in record.msg or "Shard ID" in record.msg
or "Websocket closed " in record.msg
)
def emit(self, record): def emit(self, record):
self.cog.add_record(record) self.cog.add_record(record)
class Logs(commands.Cog): class Logs(commands.Cog):
def __init__(self, bot: TuxBot): def __init__(self, bot: TuxBot):
self.bot = bot self.bot = bot
self.process = psutil.Process() self.process = psutil.Process()
@ -54,15 +55,13 @@ class Logs(commands.Cog):
def _clear_gateway_data(self): def _clear_gateway_data(self):
one_week_ago = datetime.datetime.utcnow() - datetime.timedelta(days=7) one_week_ago = datetime.datetime.utcnow() - datetime.timedelta(days=7)
to_remove = [ to_remove = [
index for index, dt in enumerate(self._resumes) index for index, dt in enumerate(self._resumes) if dt < one_week_ago
if dt < one_week_ago
] ]
for index in reversed(to_remove): for index in reversed(to_remove):
del self._resumes[index] del self._resumes[index]
for shard_id, dates in self._identifies.items(): for shard_id, dates in self._identifies.items():
to_remove = [index for index, dt in enumerate(dates) if to_remove = [index for index, dt in enumerate(dates) if dt < one_week_ago]
dt < one_week_ago]
for index in reversed(to_remove): for index in reversed(to_remove):
del dates[index] del dates[index]
@ -79,25 +78,28 @@ class Logs(commands.Cog):
self.bot.command_stats[command] += 1 self.bot.command_stats[command] += 1
message = ctx.message message = ctx.message
if ctx.guild is None: if ctx.guild is None:
destination = 'Private Message' destination = "Private Message"
guild_id = None guild_id = None
else: else:
destination = f'#{message.channel} ({message.guild})' destination = f"#{message.channel} ({message.guild})"
guild_id = ctx.guild.id guild_id = ctx.guild.id
log.info( log.info(
f'{message.created_at}: {message.author} ' f"{message.created_at}: {message.author} "
f'in {destination}: {message.content}') f"in {destination}: {message.content}"
)
async with self._batch_lock: async with self._batch_lock:
self._data_batch.append({ self._data_batch.append(
'guild': guild_id, {
'channel': ctx.channel.id, "guild": guild_id,
'author': ctx.author.id, "channel": ctx.channel.id,
'used': message.created_at.isoformat(), "author": ctx.author.id,
'prefix': ctx.prefix, "used": message.created_at.isoformat(),
'command': command, "prefix": ctx.prefix,
'failed': ctx.command_failed, "command": command,
}) "failed": ctx.command_failed,
}
)
@commands.Cog.listener() @commands.Cog.listener()
async def on_command_completion(self, ctx): async def on_command_completion(self, ctx):
@ -105,7 +107,7 @@ class Logs(commands.Cog):
@commands.Cog.listener() @commands.Cog.listener()
async def on_socket_response(self, msg): async def on_socket_response(self, msg):
self.bot.socket_stats[msg.get('t')] += 1 self.bot.socket_stats[msg.get("t")] += 1
@property @property
def logs(self): def logs(self):
@ -113,48 +115,44 @@ class Logs(commands.Cog):
for key, value in self.bot.logs_channels.items(): for key, value in self.bot.logs_channels.items():
webhooks[key] = discord.Webhook.partial( webhooks[key] = discord.Webhook.partial(
id=value.get('webhook')['id'], id=value.get("webhook")["id"],
token=value.get('webhook')['token'], token=value.get("webhook")["token"],
adapter=discord.AsyncWebhookAdapter( adapter=discord.AsyncWebhookAdapter(self.bot.session),
self.bot.session
)
) )
return webhooks return webhooks
async def log_error(self, *, ctx=None, extra=None): async def log_error(self, *, ctx=None, extra=None):
e = discord.Embed(title='Error', colour=0xdd5f53) e = discord.Embed(title="Error", colour=0xDD5F53)
e.description = f'```py\n{traceback.format_exc()}\n```' e.description = f"```py\n{traceback.format_exc()}\n```"
e.add_field(name='Extra', value=extra, inline=False) e.add_field(name="Extra", value=extra, inline=False)
e.timestamp = datetime.datetime.utcnow() e.timestamp = datetime.datetime.utcnow()
if ctx is not None: if ctx is not None:
fmt = '{0} (ID: {0.id})' fmt = "{0} (ID: {0.id})"
author = fmt.format(ctx.author) author = fmt.format(ctx.author)
channel = fmt.format(ctx.channel) channel = fmt.format(ctx.channel)
guild = 'None' if ctx.guild is None else fmt.format(ctx.guild) guild = "None" if ctx.guild is None else fmt.format(ctx.guild)
e.add_field(name='Author', value=author) e.add_field(name="Author", value=author)
e.add_field(name='Channel', value=channel) e.add_field(name="Channel", value=channel)
e.add_field(name='Guild', value=guild) e.add_field(name="Guild", value=guild)
await self.logs.get('errors').send(embed=e) await self.logs.get("errors").send(embed=e)
async def send_guild_stats(self, e, guild): async def send_guild_stats(self, e, guild):
e.add_field(name='Name', value=guild.name) e.add_field(name="Name", value=guild.name)
e.add_field(name='ID', value=guild.id) e.add_field(name="ID", value=guild.id)
e.add_field(name='Shard ID', value=guild.shard_id or 'N/A') e.add_field(name="Shard ID", value=guild.shard_id or "N/A")
e.add_field(name='Owner', e.add_field(name="Owner", value=f"{guild.owner} (ID: {guild.owner.id})")
value=f'{guild.owner} (ID: {guild.owner.id})')
bots = sum(member.bot for member in guild.members) bots = sum(member.bot for member in guild.members)
total = guild.member_count total = guild.member_count
online = sum(member.status is discord.Status.online online = sum(member.status is discord.Status.online for member in guild.members)
for member in guild.members)
e.add_field(name='Members', value=str(total)) e.add_field(name="Members", value=str(total))
e.add_field(name='Bots', value=f'{bots} ({bots / total:.2%})') e.add_field(name="Bots", value=f"{bots} ({bots / total:.2%})")
e.add_field(name='Online', value=f'{online} ({online / total:.2%})') e.add_field(name="Online", value=f"{online} ({online / total:.2%})")
if guild.icon: if guild.icon:
e.set_thumbnail(url=guild.icon_url) e.set_thumbnail(url=guild.icon_url)
@ -162,16 +160,16 @@ class Logs(commands.Cog):
if guild.me: if guild.me:
e.timestamp = guild.me.joined_at e.timestamp = guild.me.joined_at
await self.logs.get('guilds').send(embed=e) await self.logs.get("guilds").send(embed=e)
@commands.Cog.listener() @commands.Cog.listener()
async def on_guild_join(self, guild: discord.guild): async def on_guild_join(self, guild: discord.guild):
e = discord.Embed(colour=0x53dda4, title='New Guild') # green colour e = discord.Embed(colour=0x53DDA4, title="New Guild") # green colour
await self.send_guild_stats(e, guild) await self.send_guild_stats(e, guild)
@commands.Cog.listener() @commands.Cog.listener()
async def on_guild_remove(self, guild: discord.guild): async def on_guild_remove(self, guild: discord.guild):
e = discord.Embed(colour=0xdd5f53, title='Left Guild') # red colour e = discord.Embed(colour=0xDD5F53, title="Left Guild") # red colour
await self.send_guild_stats(e, guild) await self.send_guild_stats(e, guild)
@commands.Cog.listener() @commands.Cog.listener()
@ -185,61 +183,58 @@ class Logs(commands.Cog):
e = discord.Embed( e = discord.Embed(
title=f"DM to: {message.channel.recipient}", title=f"DM to: {message.channel.recipient}",
description=message.content, description=message.content,
color=0x39e326 color=0x39E326,
) )
else: else:
e = discord.Embed( e = discord.Embed(
title="New DM:", title="New DM:", description=message.content, color=0x0A97F5
description=message.content,
color=0x0A97F5
) )
e.set_author( e.set_author(
name=message.channel.recipient, name=message.channel.recipient,
icon_url=message.channel.recipient.avatar_url_as(format="png") icon_url=message.channel.recipient.avatar_url_as(format="png"),
) )
if message.attachments: if message.attachments:
attachment_url = message.attachments[0].url attachment_url = message.attachments[0].url
e.set_image(url=attachment_url) e.set_image(url=attachment_url)
e.set_footer( e.set_footer(text=f"User ID: {message.channel.recipient.id}")
text=f"User ID: {message.channel.recipient.id}"
)
await self.logs["dm"].send(embed=e) await self.logs["dm"].send(embed=e)
@commands.Cog.listener() @commands.Cog.listener()
async def on_command_error(self, ctx, error): async def on_command_error(self, ctx, error):
await self.register_command(ctx) await self.register_command(ctx)
if not isinstance(error, ( if not isinstance(
commands.CommandInvokeError, commands.ConversionError)): error, (commands.CommandInvokeError, commands.ConversionError)
):
return return
error = error.original error = error.original
if isinstance(error, (discord.Forbidden, discord.NotFound)): if isinstance(error, (discord.Forbidden, discord.NotFound)):
return return
e = discord.Embed(title='Command Error', colour=0xcc3366) e = discord.Embed(title="Command Error", colour=0xCC3366)
e.add_field(name='Name', value=ctx.command.qualified_name) e.add_field(name="Name", value=ctx.command.qualified_name)
e.add_field(name='Author', value=f'{ctx.author} (ID: {ctx.author.id})') e.add_field(name="Author", value=f"{ctx.author} (ID: {ctx.author.id})")
fmt = f'Channel: {ctx.channel} (ID: {ctx.channel.id})' fmt = f"Channel: {ctx.channel} (ID: {ctx.channel.id})"
if ctx.guild: if ctx.guild:
fmt = f'{fmt}\nGuild: {ctx.guild} (ID: {ctx.guild.id})' fmt = f"{fmt}\nGuild: {ctx.guild} (ID: {ctx.guild.id})"
e.add_field(name='Location', value=fmt, inline=False) e.add_field(name="Location", value=fmt, inline=False)
e.add_field(name='Content', value=textwrap.shorten( e.add_field(
ctx.message.content, name="Content", value=textwrap.shorten(ctx.message.content, width=512)
width=512
))
exc = ''.join(traceback.format_exception(
type(error), error, error.__traceback__,
chain=False)
) )
e.description = f'```py\n{exc}\n```'
exc = "".join(
traceback.format_exception(
type(error), error, error.__traceback__, chain=False
)
)
e.description = f"```py\n{exc}\n```"
e.timestamp = datetime.datetime.utcnow() e.timestamp = datetime.datetime.utcnow()
await self.logs.get('errors').send(embed=e) await self.logs.get("errors").send(embed=e)
@commands.Cog.listener() @commands.Cog.listener()
async def on_socket_raw_send(self, data): async def on_socket_raw_send(self, data):
@ -247,9 +242,9 @@ class Logs(commands.Cog):
return return
back_to_json = json.loads(data) back_to_json = json.loads(data)
if back_to_json['op'] == 2: if back_to_json["op"] == 2:
payload = back_to_json['d'] payload = back_to_json["d"]
inner_shard = payload.get('shard', [0]) inner_shard = payload.get("shard", [0])
self._identifies[inner_shard[0]].append(datetime.datetime.utcnow()) self._identifies[inner_shard[0]].append(datetime.datetime.utcnow())
else: else:
self._resumes.append(datetime.datetime.utcnow()) self._resumes.append(datetime.datetime.utcnow())
@ -260,17 +255,14 @@ class Logs(commands.Cog):
self._gateway_queue.put_nowait(record) self._gateway_queue.put_nowait(record)
async def notify_gateway_status(self, record): async def notify_gateway_status(self, record):
types = { types = {"INFO": ":information_source:", "WARNING": ":warning:"}
'INFO': ':information_source:',
'WARNING': ':warning:'
}
emoji = types.get(record.levelname, ':heavy_multiplication_x:') emoji = types.get(record.levelname, ":heavy_multiplication_x:")
dt = datetime.datetime.utcfromtimestamp(record.created) dt = datetime.datetime.utcfromtimestamp(record.created)
msg = f'{emoji} `[{dt:%Y-%m-%d %H:%M:%S}] {record.message}`' msg = f"{emoji} `[{dt:%Y-%m-%d %H:%M:%S}] {record.message}`"
await self.logs.get('gateway').send(msg) await self.logs.get("gateway").send(msg)
@command_extra(name='commandstats') @command_extra(name="commandstats")
@commands.is_owner() @commands.is_owner()
async def _commandstats(self, ctx, limit=20): async def _commandstats(self, ctx, limit=20):
counter = self.bot.command_stats counter = self.bot.command_stats
@ -281,11 +273,11 @@ class Logs(commands.Cog):
else: else:
common = counter.most_common()[limit:] common = counter.most_common()[limit:]
output = '\n'.join(f'{k:<{width}}: {c}' for k, c in common) output = "\n".join(f"{k:<{width}}: {c}" for k, c in common)
await ctx.send(f'```\n{output}\n```') await ctx.send(f"```\n{output}\n```")
@commands.command('socketstats') @commands.command("socketstats")
@commands.is_owner() @commands.is_owner()
async def _socketstats(self, ctx): async def _socketstats(self, ctx):
delta = datetime.datetime.utcnow() - self.bot.uptime delta = datetime.datetime.utcnow() - self.bot.uptime
@ -293,31 +285,35 @@ class Logs(commands.Cog):
total = sum(self.bot.socket_stats.values()) total = sum(self.bot.socket_stats.values())
cpm = total / minutes cpm = total / minutes
await ctx.send( await ctx.send(
f'{total} socket events observed ({cpm:.2f}/minute):\n' f"{total} socket events observed ({cpm:.2f}/minute):\n"
f'{self.bot.socket_stats}') f"{self.bot.socket_stats}"
)
@commands.command('uptime') @commands.command("uptime")
async def _uptime(self, ctx): async def _uptime(self, ctx):
uptime = humanize.naturaltime( uptime = humanize.naturaltime(datetime.datetime.utcnow() - self.bot.uptime)
datetime.datetime.utcnow() - self.bot.uptime) await ctx.send(f"Uptime: **{uptime}**")
await ctx.send(f'Uptime: **{uptime}**')
async def on_error(self, event, *args): async def on_error(self, event, *args):
e = discord.Embed(title='Event Error', colour=0xa32952) e = discord.Embed(title="Event Error", colour=0xA32952)
e.add_field(name='Event', value=event) e.add_field(name="Event", value=event)
e.description = f'```py\n{traceback.format_exc()}\n```' e.description = f"```py\n{traceback.format_exc()}\n```"
e.timestamp = datetime.datetime.utcnow() e.timestamp = datetime.datetime.utcnow()
args_str = ['```py'] args_str = ["```py"]
for index, arg in enumerate(args): for index, arg in enumerate(args):
args_str.append(f'[{index}]: {arg!r}') args_str.append(f"[{index}]: {arg!r}")
args_str.append('```') args_str.append("```")
e.add_field(name='Args', value='\n'.join(args_str), inline=False) e.add_field(name="Args", value="\n".join(args_str), inline=False)
hook = self.get_cog('Logs').logs.get('errors') hook = self.get_cog("Logs").logs.get("errors")
try: try:
await hook.send(embed=e) await hook.send(embed=e)
except (discord.HTTPException, discord.NotFound, except (
discord.Forbidden, discord.InvalidArgument): discord.HTTPException,
discord.NotFound,
discord.Forbidden,
discord.InvalidArgument,
):
pass pass

View file

@ -1,6 +1,18 @@
from collections import namedtuple
from .network import Network from .network import Network
from ...core.bot import Tux from ...core.bot import Tux
VersionInfo = namedtuple("VersionInfo", "major minor micro releaselevel")
version_info = VersionInfo(major=2, minor=0, micro=0, releaselevel="alpha")
__version__ = "v{}.{}.{}-{}".format(
version_info.major,
version_info.minor,
version_info.micro,
version_info.releaselevel
).replace("\n", "")
def setup(bot: Tux): def setup(bot: Tux):
bot.add_cog(Network(bot)) bot.add_cog(Network(bot))

View file

@ -19,14 +19,13 @@ class Network(commands.Cog, name="Useless"):
def __init__(self, bot: TuxBot): def __init__(self, bot: TuxBot):
self.bot = bot self.bot = bot
@flags.add_flag("-i", "--ip", type=str, default='v4', @flags.add_flag(
choices=['v4', '4', 'v6', '6']) "-i", "--ip", type=str, default="v4", choices=["v4", "4", "v6", "6"]
@command_extra(name="iplocalise", aliases=['localiseip']) )
@command_extra(name="iplocalise", aliases=["localiseip"])
@commands.cooldown(1, 5, commands.BucketType.user) @commands.cooldown(1, 5, commands.BucketType.user)
async def _iplocalise(self, ctx: ContextPlus, target: str, **passed_flags): async def _iplocalise(self, ctx: ContextPlus, target: str, **passed_flags):
loading = await ctx.send( loading = await ctx.send("_Récupération des informations..._", deletable=False)
"_Récupération des informations..._", deletable=False
)
def get_hostname(dtl, tgt): def get_hostname(dtl, tgt):
try: try:
@ -35,25 +34,29 @@ class Network(commands.Cog, name="Useless"):
try: try:
return socket.gethostbyaddr(tgt)[0] return socket.gethostbyaddr(tgt)[0]
except (ValueError, socket.herror): except (ValueError, socket.herror):
return 'N/A' return "N/A"
ip_type = passed_flags.get('ip') ip_type = passed_flags.get("ip")
target_copy = target target_copy = target
# clean https://, last /, ... # clean https://, last /, ...
spltTgt = target.split("://") spltTgt = target.split("://")
target = spltTgt[ target = (
(0, 1)[len(spltTgt) > 1] spltTgt[(0, 1)[len(spltTgt) > 1]]
].split("?")[0].split('/')[0].split(':')[0].lower() .split("?")[0]
.split("/")[0]
.split(":")[0]
.lower()
)
try: try:
target = socket.getaddrinfo( target = socket.getaddrinfo(
target, None, target,
socket.AF_INET if ip_type in ['v4', '4'] else socket.AF_INET6 None,
socket.AF_INET if ip_type in ["v4", "4"] else socket.AF_INET6,
)[1][4][0] )[1][4][0]
except socket.gaierror: except socket.gaierror:
return \ return await ctx.send("Erreur, cette adresse n'est pas disponible.")
await ctx.send("Erreur, cette adresse n'est pas disponible.")
net = Net(target) net = Net(target)
obj = IPASN(net) obj = IPASN(net)
@ -70,34 +73,38 @@ class Network(commands.Cog, name="Useless"):
if api_result: if api_result:
belongs = f"{details.org}" belongs = f"{details.org}"
osm = f"https://www.openstreetmap.org/" \ osm = (
f"?mlat={details.latitude}" \ f"https://www.openstreetmap.org/"
f"&mlon={details.longitude}" \ f"?mlat={details.latitude}"
f"#map=5/{details.latitude}/{details.longitude}" \ f"&mlon={details.longitude}"
f"&layers=H" f"#map=5/{details.latitude}/{details.longitude}"
f"&layers=H"
)
region = f"[{details.city} - {details.region} " \ region = (
f"({details.country})]({osm})" f"[{details.city} - {details.region} " f"({details.country})]({osm})"
flag = f"https://www.countryflags.io/" \ )
f"{details.country}/shiny/64.png" flag = f"https://www.countryflags.io/" f"{details.country}/shiny/64.png"
else: else:
belongs = f"{ip_info['asn_description']} (AS{ip_info['asn']})" belongs = f"{ip_info['asn_description']} (AS{ip_info['asn']})"
region = f"{ip_info['asn_country_code']}" region = f"{ip_info['asn_country_code']}"
flag = f"https://www.countryflags.io/" \ flag = (
f"{ip_info['asn_country_code']}/shiny/64.png" f"https://www.countryflags.io/"
f"{ip_info['asn_country_code']}/shiny/64.png"
)
e = discord.Embed( e = discord.Embed(
title=f"**Information sur __{target_copy}__ :**" title=f"**Information sur __{target_copy}__ :**" f" `{target}`",
f" `{target}`", color=0x5858D7,
color=0x5858d7
) )
e.add_field(name="Appartient à :", value=belongs) e.add_field(name="Appartient à :", value=belongs)
e.add_field(name="RIR :", value=f"{ip_info['asn_registry']}") e.add_field(name="RIR :", value=f"{ip_info['asn_registry']}")
e.add_field(name="Region :", value=region) e.add_field(name="Region :", value=region)
e.add_field(name="Nom de l'hôte :", e.add_field(
value=get_hostname(details, target), inline=False) name="Nom de l'hôte :", value=get_hostname(details, target), inline=False
)
e.set_thumbnail(url=flag) e.set_thumbnail(url=flag)

View file

@ -1,6 +1,18 @@
from collections import namedtuple
from .warnings import Warnings from .warnings import Warnings
from ...core.bot import Tux from ...core.bot import Tux
VersionInfo = namedtuple("VersionInfo", "major minor micro releaselevel")
version_info = VersionInfo(major=1, minor=0, micro=0, releaselevel="alpha")
__version__ = "v{}.{}.{}-{}".format(
version_info.major,
version_info.minor,
version_info.micro,
version_info.releaselevel
).replace("\n", "")
def setup(bot: Tux): def setup(bot: Tux):
bot.add_cog(Warnings(bot)) bot.add_cog(Warnings(bot))

View file

@ -11,7 +11,7 @@ class Warnings(commands.Cog, name="Warnings"):
def __init__(self, bot: Tux): def __init__(self, bot: Tux):
self.bot = bot self.bot = bot
@commands.group(name='warn', alias=['warning']) @commands.group(name="warn", alias=["warning"])
@commands.guild_only() @commands.guild_only()
@checks.is_mod() @checks.is_mod()
async def _warn(self, ctx: commands.Context): async def _warn(self, ctx: commands.Context):
@ -20,28 +20,21 @@ class Warnings(commands.Cog, name="Warnings"):
@_warn.command(name="add") @_warn.command(name="add")
@commands.guild_only() @commands.guild_only()
async def _warn_add( async def _warn_add(
self, self,
ctx: commands.Context, ctx: commands.Context,
member: Union[discord.User, discord.Member], member: Union[discord.User, discord.Member],
reason: str reason: str,
): ):
pass pass
@_warn.command(name="delete", aliases=["del", "remove"]) @_warn.command(name="delete", aliases=["del", "remove"])
@commands.guild_only() @commands.guild_only()
async def action_del( async def action_del(self, ctx: commands.Context, warn_id: int, reason: str = ""):
self,
ctx: commands.Context,
warn_id: int,
reason: str = ""
):
pass pass
@_warn.command(name="list", aliases=["all"]) @_warn.command(name="list", aliases=["all"])
@commands.guild_only() @commands.guild_only()
async def action_del( async def action_del(
self, self, ctx: commands.Context, member: Union[discord.User, discord.Member] = None
ctx: commands.Context,
member: Union[discord.User, discord.Member] = None
): ):
pass pass

View file

@ -26,10 +26,7 @@ NAME = r"""
|_| \__,_/_/\_\_.__/ \___/ \__| |_.__/ \___/ \__| |_| \__,_/_/\_\_.__/ \___/ \__| |_.__/ \___/ \__|
""" """
packages: List[str] = [ packages: List[str] = ["jishaku", "tuxbot.cogs.warnings"]
"jishaku",
"tuxbot.cogs.warnings"
]
class Tux(commands.AutoShardedBot): class Tux(commands.AutoShardedBot):
@ -47,11 +44,11 @@ class Tux(commands.AutoShardedBot):
self.config = Config(self.instance_name) self.config = Config(self.instance_name)
async def _prefixes(bot, message) -> List[str]: async def _prefixes(bot, message) -> List[str]:
prefixes = self.config('core').get('prefixes') prefixes = self.config("core").get("prefixes")
prefixes.extend(self.config.get_prefixes(message.guild)) prefixes.extend(self.config.get_prefixes(message.guild))
if self.config('core').get('mentionable'): if self.config("core").get("mentionable"):
return commands.when_mentioned_or(*prefixes)(bot, message) return commands.when_mentioned_or(*prefixes)(bot, message)
return prefixes return prefixes
@ -79,41 +76,37 @@ class Tux(commands.AutoShardedBot):
try: try:
self.load_extension(package) self.load_extension(package)
except Exception as e: except Exception as e:
print(Fore.RED print(
+ f"Failed to load package {package}" Fore.RED
+ Style.RESET_ALL + f"Failed to load package {package}"
+ f" check " + Style.RESET_ALL
f"{str((self.logs / 'tuxbot.log').resolve())} " + f" check "
f"for more details") f"{str((self.logs / 'tuxbot.log').resolve())} "
f"for more details"
log.exception(
f"Failed to load package {package}",
exc_info=e
) )
log.exception(f"Failed to load package {package}", exc_info=e)
async def on_ready(self): async def on_ready(self):
self.uptime = datetime.datetime.now() self.uptime = datetime.datetime.now()
INFO = { INFO = {
'title': "INFO", "title": "INFO",
'rows': [ "rows": [
str(self.user), str(self.user),
f"Prefixes: {', '.join(self.config('core').get('prefixes'))}", f"Prefixes: {', '.join(self.config('core').get('prefixes'))}",
f"Language: {self.config('core').get('locale')}", f"Language: {self.config('core').get('locale')}",
f"Tuxbot Version: {__version__}", f"Tuxbot Version: {__version__}",
f"Discord.py Version: {discord.__version__}", f"Discord.py Version: {discord.__version__}",
"Python Version: " + sys.version.replace('\n', ''), "Python Version: " + sys.version.replace("\n", ""),
f"Shards: {self.shard_count}", f"Shards: {self.shard_count}",
f"Servers: {len(self.guilds)}", f"Servers: {len(self.guilds)}",
f"Users: {len(self.users)}" f"Users: {len(self.users)}",
] ],
} }
COGS = { COGS = {"title": "COGS", "rows": []}
'title': "COGS",
'rows': []
}
for extension in packages: for extension in packages:
COGS['rows'].append( COGS["rows"].append(
f"[{'X' if extension in self.extensions else ' '}] {extension}" f"[{'X' if extension in self.extensions else ' '}] {extension}"
) )
@ -142,7 +135,7 @@ class Tux(commands.AutoShardedBot):
app = await self.application_info() app = await self.application_info()
if app.team: if app.team:
ids = [m.id for m in app.team.members] ids = [m.id for m in app.team.members]
self.config.update('core', 'owners_id', ids) await self.config.update("core", "owners_id", ids)
owner = user.id in ids owner = user.id in ids
self._app_owners_fetched = True self._app_owners_fetched = True
@ -158,9 +151,11 @@ class Tux(commands.AutoShardedBot):
if message.author.bot: if message.author.bot:
return return
if message.guild.id in self.config.get_blacklist('guild') \ if (
or message.channel.id in self.config.get_blacklist('channel') \ message.guild.id in self.config.get_blacklist("guild")
or message.author.id in self.config.get_blacklist('user'): or message.channel.id in self.config.get_blacklist("channel")
or message.author.id in self.config.get_blacklist("user")
):
return return
ctx = await self.get_context(message) ctx = await self.get_context(message)

View file

@ -25,6 +25,7 @@ def is_mod():
"""Is the user a moderator ? """Is the user a moderator ?
""" """
async def pred(ctx): async def pred(ctx):
if await ctx.bot.is_owner(ctx.author): if await ctx.bot.is_owner(ctx.author):
return True return True
@ -38,6 +39,7 @@ def is_admin():
"""Is the user admin ? """Is the user admin ?
""" """
async def pred(ctx): async def pred(ctx):
if await ctx.bot.is_owner(ctx.author): if await ctx.bot.is_owner(ctx.author):
return True return True
@ -64,9 +66,7 @@ async def check_permissions(ctx: "ContextPlus", **perms: Dict[str, bool]):
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()
)
def guild_owner_or_permissions(**perms: Dict[str, bool]): def guild_owner_or_permissions(**perms: Dict[str, bool]):
@ -77,6 +77,7 @@ def guild_owner_or_permissions(**perms: Dict[str, bool]):
**perms:dict **perms:dict
Perms to verify. Perms to verify.
""" """
async def pred(ctx): async def pred(ctx):
if ctx.author is ctx.guild.owner: if ctx.author is ctx.guild.owner:
return True return True

View file

@ -1,3 +1,4 @@
import asyncio
import json import json
import logging import logging
from typing import List, Dict, Union, Any from typing import List, Dict, Union, Any
@ -12,27 +13,31 @@ log = logging.getLogger("tuxbot.core.config")
class Config: class Config:
def __init__( def __init__(self, cog_instance: str = None):
self,
cog_instance: str = None
):
self._cog_instance = cog_instance self._cog_instance = cog_instance
self.lock = asyncio.Lock()
self.loop = asyncio.get_event_loop()
self._settings_file = None
self._datas = {}
def __getitem__(self, item) -> Dict: def __getitem__(self, item) -> Dict:
path = data_path(self._cog_instance) path = data_path(self._cog_instance)
if item != 'core': if item != "core":
path = path / 'cogs' / item path = path / "cogs" / item
else: else:
path /= 'core' path /= "core"
settings_file = path / 'settings.json' settings_file = path / "settings.json"
if not settings_file.exists(): if not settings_file.exists():
raise FileNotFoundError(f"Unable to find settings file " raise FileNotFoundError(
f"'{settings_file}'") f"Unable to find settings file " f"'{settings_file}'"
)
else: else:
with settings_file.open('r') as f: with settings_file.open("r") as f:
return json.load(f) return json.load(f)
def __call__(self, item): def __call__(self, item):
@ -46,7 +51,7 @@ class Config:
str str
Owners id. Owners id.
""" """
return self.__getitem__('core').get('owners_id') return self.__getitem__("core").get("owners_id")
def token(self) -> str: def token(self) -> str:
"""Simply return the bot token saved in config file. """Simply return the bot token saved in config file.
@ -56,7 +61,7 @@ class Config:
str str
Bot token. Bot token.
""" """
return self.__getitem__('core').get('token') return self.__getitem__("core").get("token")
def get_prefixes(self, guild: discord.Guild) -> List[str]: def get_prefixes(self, guild: discord.Guild) -> List[str]:
"""Get custom prefixes for one guild. """Get custom prefixes for one guild.
@ -71,11 +76,8 @@ class Config:
List[str] List[str]
List of all prefixes. List of all prefixes.
""" """
core = self.__getitem__('core') core = self.__getitem__("core")
prefixes = core \ prefixes = core.get("guild", {}).get(guild.id, {}).get("prefixes", [])
.get('guild', {}) \
.get(guild.id, {}) \
.get('prefixes', [])
return prefixes return prefixes
@ -92,14 +94,16 @@ class Config:
List[Union[str, int]] List[Union[str, int]]
List containing blacklisted values. List containing blacklisted values.
""" """
core = self.__getitem__('core') core = self.__getitem__("core")
blacklist = core \ blacklist = core.get("blacklist", {}).get(key, [])
.get('blacklist', {}) \
.get(key, [])
return blacklist return blacklist
def update(self, cog_name: str, item: str, value: Any) -> dict: def _dump(self):
with self._settings_file.open("w") as f:
json.dump(self._datas, f, indent=4)
async def update(self, cog_name: str, item: str, value: Any) -> dict:
"""Update values in config file. """Update values in config file.
Parameters Parameters
@ -122,14 +126,16 @@ class Config:
datas[item] = value datas[item] = value
if cog_name != 'core': self._datas = datas
path = path / 'cogs' / cog_name
if cog_name != "core":
path = path / "cogs" / cog_name
else: else:
path /= 'core' path /= "core"
settings_file = path / 'settings.json' self._settings_file = path / "settings.json"
with settings_file.open('w') as f: async with self.lock:
json.dump(datas, f, indent=4) await self.loop.run_in_executor(None, self._dump)
return datas return datas

View file

@ -43,22 +43,22 @@ def bordered(*columns: dict) -> str:
sep = " " * 4 # Separator between boxes sep = " " * 4 # Separator between boxes
widths = tuple( widths = tuple(
max( max(len(row) for row in column.get("rows")) + 9 for column in columns
len(row) for row in column.get('rows')
) + 9
for column in columns
) # width of each col ) # width of each col
cols_done = [False] * len(columns) # whether or not each column is done cols_done = [False] * len(columns) # whether or not each column is done
lines = [""] lines = [""]
for i, column in enumerate(columns): for i, column in enumerate(columns):
lines[0] += "{TL}" + "{HZ}" + column.get('title') \ lines[0] += (
+ "{HZ}" * (widths[i] - len(column.get('title')) - 1) \ "{TL}"
+ "{TR}" + sep + "{HZ}"
+ column.get("title")
+ "{HZ}" * (widths[i] - len(column.get("title")) - 1)
+ "{TR}"
+ sep
)
for line in itertools.zip_longest( for line in itertools.zip_longest(*[column.get("rows") for column in columns]):
*[column.get('rows') for column in columns]
):
row = [] row = []
for colidx, column in enumerate(line): for colidx, column in enumerate(line):
width = widths[colidx] width = widths[colidx]

View file

@ -6,25 +6,23 @@ from discord.ext import commands, flags
class ContextPlus(commands.Context): class ContextPlus(commands.Context):
async def send(self, content=None, *args, **kwargs): async def send(self, content=None, *args, **kwargs):
if (hasattr(self.command, 'deletable') if (
and self.command.deletable) \ hasattr(self.command, "deletable") and self.command.deletable
and kwargs.pop('deletable', True): ) and kwargs.pop("deletable", True):
message = await super().send(content, *args, **kwargs) message = await super().send(content, *args, **kwargs)
await message.add_reaction('🗑') await message.add_reaction("🗑")
def check(reaction: discord.Reaction, user: discord.User): def check(reaction: discord.Reaction, user: discord.User):
return user == self.author \ return (
and str(reaction.emoji) == '🗑' \ user == self.author
and reaction.message.id == message.id and str(reaction.emoji) == "🗑"
and reaction.message.id == message.id
)
try: try:
await self.bot.wait_for( await self.bot.wait_for("reaction_add", timeout=60.0, check=check)
'reaction_add',
timeout=60.0,
check=check
)
except asyncio.TimeoutError: except asyncio.TimeoutError:
await message.remove_reaction('🗑', self.bot.user) await message.remove_reaction("🗑", self.bot.user)
else: else:
await message.delete() await message.delete()
return message return message

View file

@ -20,24 +20,23 @@ def init_logging(level: int, location: pathlib.Path) -> None:
dpy_logger = logging.getLogger("discord") dpy_logger = logging.getLogger("discord")
dpy_logger.setLevel(logging.WARN) dpy_logger.setLevel(logging.WARN)
dpy_logger_file = location / 'discord.log' dpy_logger_file = location / "discord.log"
base_logger = logging.getLogger("tuxbot") base_logger = logging.getLogger("tuxbot")
base_logger.setLevel(level) base_logger.setLevel(level)
base_logger_file = location / 'tuxbot.log' base_logger_file = location / "tuxbot.log"
formatter = logging.Formatter( formatter = logging.Formatter(
"[{asctime}] [{levelname}] {name}: {message}", "[{asctime}] [{levelname}] {name}: {message}",
datefmt="%Y-%m-%d %H:%M:%S", style="{" datefmt="%Y-%m-%d %H:%M:%S",
style="{",
) )
dpy_handler = logging.handlers.RotatingFileHandler( dpy_handler = logging.handlers.RotatingFileHandler(
str(dpy_logger_file.resolve()), str(dpy_logger_file.resolve()), maxBytes=MAX_BYTES, backupCount=MAX_OLD_LOGS
maxBytes=MAX_BYTES, backupCount=MAX_OLD_LOGS
) )
base_handler = logging.handlers.RotatingFileHandler( base_handler = logging.handlers.RotatingFileHandler(
str(base_logger_file.resolve()), str(base_logger_file.resolve()), maxBytes=MAX_BYTES, backupCount=MAX_OLD_LOGS
maxBytes=MAX_BYTES, backupCount=MAX_OLD_LOGS
) )
stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler = logging.StreamHandler(sys.stdout)

View file

@ -84,11 +84,7 @@ def get_name() -> str:
name = input("> ") name = input("> ")
if re.fullmatch(r"[a-zA-Z0-9_\-]*", name) is None: if re.fullmatch(r"[a-zA-Z0-9_\-]*", name) is None:
print() print()
print( print(Fore.RED + "ERROR: Invalid characters provided" + Style.RESET_ALL)
Fore.RED
+ "ERROR: Invalid characters provided"
+ Style.RESET_ALL
)
name = "" name = ""
return name return name
@ -117,10 +113,8 @@ def get_data_dir(instance_name: str) -> Path:
except OSError: except OSError:
print() print()
print( print(
Fore.RED Fore.RED + f"mkdir: cannot create directory '{path}':"
+ f"mkdir: cannot create directory '{path}':" f" Permission denied" + Style.RESET_ALL
f" Permission denied"
+ Style.RESET_ALL
) )
path = "" path = ""
@ -136,7 +130,7 @@ def get_data_dir(instance_name: str) -> Path:
data_path_input = input("> ") data_path_input = input("> ")
if data_path_input != '': if data_path_input != "":
data_path_input = Path(data_path_input) data_path_input = Path(data_path_input)
try: try:
@ -144,9 +138,8 @@ def get_data_dir(instance_name: str) -> Path:
except OSError: except OSError:
print() print()
print( print(
Fore.RED Fore.RED + "Impossible to verify the validity of the path, "
+ "Impossible to verify the validity of the path, " "make sure it does not contain any invalid characters."
"make sure it does not contain any invalid characters."
+ Style.RESET_ALL + Style.RESET_ALL
) )
data_path_input = "" data_path_input = ""
@ -167,9 +160,9 @@ def get_data_dir(instance_name: str) -> Path:
print("Rerun the process to redo this configuration.") print("Rerun the process to redo this configuration.")
sys.exit(0) sys.exit(0)
(data_path_input / 'core').mkdir(parents=True, exist_ok=True) (data_path_input / "core").mkdir(parents=True, exist_ok=True)
(data_path_input / 'cogs').mkdir(parents=True, exist_ok=True) (data_path_input / "cogs").mkdir(parents=True, exist_ok=True)
(data_path_input / 'logs').mkdir(parents=True, exist_ok=True) (data_path_input / "logs").mkdir(parents=True, exist_ok=True)
return data_path_input return data_path_input
@ -190,18 +183,21 @@ def get_token() -> str:
"(you can find it at https://discord.com/developers/applications)" "(you can find it at https://discord.com/developers/applications)"
) )
token = input("> ") token = input("> ")
if re.fullmatch(r"([a-zA-Z0-9]{24}\.[a-zA-Z0-9_]{6}\.[a-zA-Z0-9_\-]{27}|mfa\.[a-zA-Z0-9_\-]{84})", token) is None: if (
print( re.fullmatch(
Fore.RED r"([a-zA-Z0-9]{24}\.[a-zA-Z0-9_]{6}\.[a-zA-Z0-9_\-]{27}|mfa\.[a-zA-Z0-9_\-]{84})",
+ "ERROR: Invalid token provided" token,
+ Style.RESET_ALL
) )
is None
):
print(Fore.RED + "ERROR: Invalid token provided" + Style.RESET_ALL)
token = "" token = ""
return token return token
def get_multiple(question: str, confirmation: str, value_type: type)\ def get_multiple(
-> List[Union[str, int]]: question: str, confirmation: str, value_type: type
) -> List[Union[str, int]]:
"""Give possibility to user to fill multiple value. """Give possibility to user to fill multiple value.
Parameters Parameters
@ -219,14 +215,14 @@ def get_multiple(question: str, confirmation: str, value_type: type)\
List containing user filled values. List containing user filled values.
""" """
print(question) print(question)
user_input = input('> ') user_input = input("> ")
if not user_input: if not user_input:
return [] return []
values = [user_input] values = [user_input]
while click.confirm(confirmation, default=False): while click.confirm(confirmation, default=False):
values.append(value_type(input('> '))) values.append(value_type(input("> ")))
return values return values
@ -239,23 +235,23 @@ def additional_config() -> dict:
dict: dict:
Dict with cog name as key and configs as value. Dict with cog name as key and configs as value.
""" """
p = Path(r'tuxbot/cogs').glob('**/additional_config.json') p = Path(r"tuxbot/cogs").glob("**/additional_config.json")
datas = {} datas = {}
for file in p: for file in p:
print() print()
cog_name = str(file.parent).split('/')[-1] cog_name = str(file.parent).split("/")[-1]
datas[cog_name] = {} datas[cog_name] = {}
with file.open('r') as f: with file.open("r") as f:
data = json.load(f) data = json.load(f)
print(f"\n==Configuration for `{cog_name}` module==") print(f"\n==Configuration for `{cog_name}` module==")
for key, value in data.items(): for key, value in data.items():
print() print()
print(value['description']) print(value["description"])
datas[cog_name][key] = input('> ') datas[cog_name][key] = input("> ")
return datas return datas
@ -273,40 +269,33 @@ def finish_setup(data_dir: Path) -> NoReturn:
token = get_token() token = get_token()
print() print()
prefixes = get_multiple( prefixes = get_multiple(
"Choice a (or multiple) prefix for the bot", "Choice a (or multiple) prefix for the bot", "Add another prefix ?", str
"Add another prefix ?",
str
)
mentionable = click.confirm(
"Does the bot answer if it's mentioned?",
default=True
) )
mentionable = click.confirm("Does the bot answer if it's mentioned?", default=True)
owners_id = get_multiple( owners_id = get_multiple(
"Give the owner id of this bot", "Give the owner id of this bot", "Add another owner ?", int
"Add another owner ?",
int
) )
cogs_config = additional_config() cogs_config = additional_config()
core_file = data_dir / 'core' / 'settings.json' core_file = data_dir / "core" / "settings.json"
core = { core = {
'token': token, "token": token,
'prefixes': prefixes, "prefixes": prefixes,
'mentionable': mentionable, "mentionable": mentionable,
'owners_id': owners_id, "owners_id": owners_id,
'locale': "en-US" "locale": "en-US",
} }
with core_file.open("w") as fs: with core_file.open("w") as fs:
json.dump(core, fs, indent=4) json.dump(core, fs, indent=4)
for cog, data in cogs_config.items(): for cog, data in cogs_config.items():
data_cog_dir = data_dir / 'cogs' / cog data_cog_dir = data_dir / "cogs" / cog
data_cog_dir.mkdir(parents=True, exist_ok=True) data_cog_dir.mkdir(parents=True, exist_ok=True)
data_cog_file = data_cog_dir / 'settings.json' data_cog_file = data_cog_dir / "settings.json"
with data_cog_file.open("w") as fs: with data_cog_file.open("w") as fs:
json.dump(data, fs, indent=4) json.dump(data, fs, indent=4)
@ -330,19 +319,16 @@ def basic_setup() -> NoReturn:
if name in instances_list: if name in instances_list:
print() print()
print( print(
Fore.RED Fore.RED + f"WARNING: An instance named `{name}` already exists "
+ f"WARNING: An instance named `{name}` already exists " f"Continuing will overwrite this instance configs." + Style.RESET_ALL
f"Continuing will overwrite this instance configs."
+ Style.RESET_ALL
) )
if not click.confirm("Are you sure you want to continue?", if not click.confirm("Are you sure you want to continue?", default=False):
default=False):
print("Abandon...") print("Abandon...")
sys.exit(0) sys.exit(0)
save_config(name, instance_config) save_config(name, instance_config)
print("\n"*4) print("\n" * 4)
finish_setup(data_dir) finish_setup(data_dir)
@ -361,7 +347,8 @@ def setup() -> NoReturn:
base_logger.setLevel(level) base_logger.setLevel(level)
formatter = logging.Formatter( formatter = logging.Formatter(
"[{asctime}] [{levelname}] {name}: {message}", "[{asctime}] [{levelname}] {name}: {message}",
datefmt="%Y-%m-%d %H:%M:%S", style="{" datefmt="%Y-%m-%d %H:%M:%S",
style="{",
) )
stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler = logging.StreamHandler(sys.stdout)
stdout_handler.setFormatter(formatter) stdout_handler.setFormatter(formatter)