diff --git a/.idea/dictionaries/romain.xml b/.idea/dictionaries/romain.xml
index d6f5b03..b11d467 100644
--- a/.idea/dictionaries/romain.xml
+++ b/.idea/dictionaries/romain.xml
@@ -38,6 +38,7 @@
tuxbot's
tuxvenv
venv
+ webhook
webhooks
écrite
diff --git a/.pylintrc b/.pylintrc
index bb75e4a..b978cd6 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -4,6 +4,7 @@ good-names=
f, # (file) as f
k, # for k, v in
v, # for k, v in
+ dt, # datetime
[MASTER]
disable=
@@ -11,5 +12,6 @@ disable=
C0115, # missing-class-docstring
C0116, # missing-function-docstring
W0703, # broad-except
+ R0801, # duplicate-code
R0902, # too-many-instance-attributes
R0903, # too-few-public-methods
diff --git a/tuxbot/__main__.py b/tuxbot/__main__.py
index 880a6d4..fdf4ebe 100644
--- a/tuxbot/__main__.py
+++ b/tuxbot/__main__.py
@@ -1,5 +1,3 @@
-from typing import NoReturn
-
from rich.console import Console
from rich.traceback import install
from tuxbot import ExitCodes
@@ -8,7 +6,7 @@ console = Console()
install(console=console)
-def main() -> NoReturn:
+def main() -> None:
try:
from .__run__ import run # pylint: disable=import-outside-toplevel
diff --git a/tuxbot/__run__.py b/tuxbot/__run__.py
index 2876b4a..9039a54 100644
--- a/tuxbot/__run__.py
+++ b/tuxbot/__run__.py
@@ -6,7 +6,6 @@ import sys
import os
import tracemalloc
from argparse import Namespace
-from typing import NoReturn
from datetime import datetime
import discord
@@ -35,7 +34,7 @@ tracemalloc.start()
BORDER_STYLE = "not dim"
-def list_instances() -> NoReturn:
+def list_instances() -> None:
"""List all available instances"""
app_config = config.ConfigFile(
data_manager.config_dir / "config.yaml", config.AppConfig
@@ -70,7 +69,7 @@ def list_instances() -> NoReturn:
sys.exit(os.EX_OK)
-def debug_info() -> NoReturn:
+def debug_info() -> None:
"""Show debug info relatives to the bot"""
python_version = sys.version.replace("\n", "")
pip_version = pip.__version__
@@ -172,7 +171,7 @@ def parse_cli_flags(args: list) -> Namespace:
return args
-async def shutdown_handler(tux: Tux, signal_type, exit_code=None) -> NoReturn:
+async def shutdown_handler(tux: Tux, signal_type, exit_code=None) -> None:
"""Handler when the bot shutdown
It cancels all running task.
@@ -247,7 +246,7 @@ async def run_bot(tux: Tux, cli_flags: Namespace) -> None:
return None
-def run() -> NoReturn:
+def run() -> None:
"""Main function"""
tux = None
cli_flags = parse_cli_flags(sys.argv[1:])
diff --git a/tuxbot/cogs/admin/config.py b/tuxbot/cogs/admin/config.py
index 23e3984..ff6762b 100644
--- a/tuxbot/cogs/admin/config.py
+++ b/tuxbot/cogs/admin/config.py
@@ -1,36 +1,8 @@
-from structured_config import Structure, StrField
+from structured_config import Structure
class AdminConfig(Structure):
- dm: str = StrField("")
- mentions: str = StrField("")
- guilds: str = StrField("")
- errors: str = StrField("")
- gateway: str = StrField("")
+ pass
-extra = {
- "dm": {
- "type": str,
- "description": "URL of the webhook used for send DMs "
- "received and sent by the bot",
- },
- "mentions": {
- "type": str,
- "description": "URL of the webhook used for send Mentions "
- "received by the bot",
- },
- "guilds": {
- "type": str,
- "description": "URL of the webhook used for send guilds where the "
- "bot is added or removed",
- },
- "errors": {
- "type": str,
- "description": "URL of the webhook used for send errors in the bot",
- },
- "gateway": {
- "type": str,
- "description": "URL of the webhook used for send gateway information",
- },
-}
+extra = {}
diff --git a/tuxbot/cogs/admin/locales/messages.pot b/tuxbot/cogs/admin/locales/messages.pot
index c522501..5bf0f40 100644
--- a/tuxbot/cogs/admin/locales/messages.pot
+++ b/tuxbot/cogs/admin/locales/messages.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tuxbot-bot\n"
"Report-Msgid-Bugs-To: rick@gnous.eu\n"
-"POT-Creation-Date: 2020-06-11 19:07+0200\n"
+"POT-Creation-Date: 2020-10-21 01:13+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -17,11 +17,11 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
-#: tuxbot/cogs/admin/admin.py:33
+#: tuxbot/cogs/admin/admin.py:47
#, python-brace-format
msgid "Locale changed to {lang} successfully"
msgstr ""
-#: tuxbot/cogs/admin/admin.py:43
+#: tuxbot/cogs/admin/admin.py:62
msgid "List of available locales: "
msgstr ""
diff --git a/tuxbot/cogs/logs/__init__.py b/tuxbot/cogs/logs/__init__.py
new file mode 100644
index 0000000..85eaf19
--- /dev/null
+++ b/tuxbot/cogs/logs/__init__.py
@@ -0,0 +1,27 @@
+import logging
+from collections import namedtuple
+
+from discord.ext import commands
+
+from .logs import Logs, on_error, GatewayHandler
+from .config import LogsConfig
+from ...core.bot import Tux
+
+VersionInfo = namedtuple("VersionInfo", "major minor micro release_level")
+version_info = VersionInfo(major=1, minor=0, micro=0, release_level="alpha")
+
+__version__ = "v{}.{}.{}-{}".format(
+ version_info.major,
+ version_info.minor,
+ version_info.micro,
+ version_info.release_level,
+).replace("\n", "")
+
+
+def setup(bot: Tux):
+ cog = Logs(bot)
+ bot.add_cog(cog)
+
+ handler = GatewayHandler(cog)
+ logging.getLogger().addHandler(handler)
+ commands.AutoShardedBot.on_error = on_error
diff --git a/tuxbot/cogs/logs/config.py b/tuxbot/cogs/logs/config.py
new file mode 100644
index 0000000..e1f7802
--- /dev/null
+++ b/tuxbot/cogs/logs/config.py
@@ -0,0 +1,36 @@
+from structured_config import Structure, StrField
+
+
+class LogsConfig(Structure):
+ dm: str = StrField("")
+ mentions: str = StrField("")
+ guilds: str = StrField("")
+ errors: str = StrField("")
+ gateway: str = StrField("")
+
+
+extra = {
+ "dm": {
+ "type": str,
+ "description": "URL of the webhook used for send DMs "
+ "received and sent by the bot",
+ },
+ "mentions": {
+ "type": str,
+ "description": "URL of the webhook used for send Mentions "
+ "received by the bot",
+ },
+ "guilds": {
+ "type": str,
+ "description": "URL of the webhook used for send guilds where the "
+ "bot is added or removed",
+ },
+ "errors": {
+ "type": str,
+ "description": "URL of the webhook used for send errors in the bot",
+ },
+ "gateway": {
+ "type": str,
+ "description": "URL of the webhook used for send gateway information",
+ },
+}
diff --git a/tuxbot/cogs/logs/locales/en-US.po b/tuxbot/cogs/logs/locales/en-US.po
new file mode 100644
index 0000000..d7bc028
--- /dev/null
+++ b/tuxbot/cogs/logs/locales/en-US.po
@@ -0,0 +1,18 @@
+# English translations for Tuxbot-bot package.
+# Copyright (C) 2020 THE Tuxbot-bot'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the Tuxbot-bot package.
+# Automatically generated, 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Tuxbot-bot\n"
+"Report-Msgid-Bugs-To: rick@gnous.eu\n"
+"POT-Creation-Date: 2020-10-21 01:15+0200\n"
+"PO-Revision-Date: 2020-10-21 01:15+0200\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: en_US\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
diff --git a/tuxbot/cogs/logs/locales/fr-FR.po b/tuxbot/cogs/logs/locales/fr-FR.po
new file mode 100644
index 0000000..3562511
--- /dev/null
+++ b/tuxbot/cogs/logs/locales/fr-FR.po
@@ -0,0 +1,19 @@
+# French translations for Tuxbot-bot package
+# Traductions françaises du paquet Tuxbot-bot.
+# Copyright (C) 2020 THE Tuxbot-bot'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the Tuxbot-bot package.
+# Automatically generated, 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Tuxbot-bot\n"
+"Report-Msgid-Bugs-To: rick@gnous.eu\n"
+"POT-Creation-Date: 2020-10-21 01:15+0200\n"
+"PO-Revision-Date: 2020-10-21 01:15+0200\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: en_US\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
diff --git a/tuxbot/cogs/logs/locales/messages.pot b/tuxbot/cogs/logs/locales/messages.pot
new file mode 100644
index 0000000..9c11528
--- /dev/null
+++ b/tuxbot/cogs/logs/locales/messages.pot
@@ -0,0 +1,18 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the Tuxbot-bot package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Tuxbot-bot\n"
+"Report-Msgid-Bugs-To: rick@gnous.eu\n"
+"POT-Creation-Date: 2020-10-21 01:15+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
diff --git a/tuxbot/cogs/logs/logs.py b/tuxbot/cogs/logs/logs.py
new file mode 100644
index 0000000..9f53163
--- /dev/null
+++ b/tuxbot/cogs/logs/logs.py
@@ -0,0 +1,332 @@
+import asyncio
+import datetime
+import json
+import logging
+import textwrap
+import traceback
+from collections import defaultdict
+from logging import LogRecord
+
+import discord
+import humanize
+import psutil
+from discord.ext import commands, tasks
+from structured_config import ConfigFile
+
+from tuxbot.core.bot import Tux
+from tuxbot.core.i18n import (
+ Translator,
+)
+from tuxbot.core.utils.functions.extra import (
+ command_extra,
+ ContextPlus,
+)
+from .config import LogsConfig
+from ...core.data_manager import cogs_data_path
+
+log = logging.getLogger("tuxbot.cogs.logs")
+_ = Translator("Logs", __file__)
+
+
+class GatewayHandler(logging.Handler):
+ def __init__(self, cog):
+ self.cog = cog
+ super().__init__(logging.INFO)
+
+ def filter(self, record: LogRecord):
+ return (
+ record.name == "discord.gateway"
+ or "Shard ID" in record.msg
+ or "Websocket closed " in record.msg
+ )
+
+ def emit(self, record: LogRecord):
+ self.cog.add_record(record)
+
+
+class Logs(commands.Cog, name="Logs"):
+ def __init__(self, bot: Tux):
+ self.bot = bot
+ self.process = psutil.Process()
+ self._batch_lock = asyncio.Lock(loop=bot.loop)
+ self._data_batch = []
+ self._gateway_queue = asyncio.Queue(loop=bot.loop)
+ self.gateway_worker.start() # pylint: disable=no-member
+
+ self.config: LogsConfig = ConfigFile(
+ str(
+ cogs_data_path(self.bot.instance_name, "logs") / "config.yaml"
+ ),
+ LogsConfig,
+ ).config
+
+ self._resumes = []
+ self._identifies = defaultdict(list)
+
+ def _clear_gateway_data(self):
+ one_week_ago = datetime.datetime.utcnow() - datetime.timedelta(days=7)
+ to_remove = [
+ index
+ for index, dt in enumerate(self._resumes)
+ if dt < one_week_ago
+ ]
+ for index in reversed(to_remove):
+ del self._resumes[index]
+
+ for _, dates in self._identifies.items():
+ to_remove = [
+ index for index, dt in enumerate(dates) if dt < one_week_ago
+ ]
+ for index in reversed(to_remove):
+ del dates[index]
+
+ @tasks.loop(seconds=0.0)
+ async def gateway_worker(self):
+ record = await self._gateway_queue.get()
+ await self.notify_gateway_status(record)
+
+ async def register_command(self, ctx: ContextPlus):
+ if ctx.command is None:
+ return
+
+ command = ctx.command.qualified_name
+ self.bot.stats["commands"][command] += 1
+ message = ctx.message
+ if ctx.guild is None:
+ destination = "Private Message"
+ guild_id = None
+ else:
+ destination = f"#{message.channel} ({message.guild})"
+ guild_id = ctx.guild.id
+
+ log.info(
+ "%s: %s in %s > %s",
+ message.created_at,
+ message.author,
+ destination,
+ message.content,
+ )
+ async with self._batch_lock:
+ self._data_batch.append(
+ {
+ "guild": guild_id,
+ "channel": ctx.channel.id,
+ "author": ctx.author.id,
+ "used": message.created_at.isoformat(),
+ "prefix": ctx.prefix,
+ "command": command,
+ "failed": ctx.command_failed,
+ }
+ )
+
+ @commands.Cog.listener()
+ async def on_command_completion(self, ctx: ContextPlus):
+ await self.register_command(ctx)
+
+ @commands.Cog.listener()
+ async def on_socket_response(self, msg):
+ self.bot.stats["socket"][msg.get("t")] += 1
+
+ def webhook(self, log_type):
+ webhook = discord.Webhook.from_url(
+ getattr(self.config, log_type),
+ adapter=discord.AsyncWebhookAdapter(self.bot.session),
+ )
+ return webhook
+
+ async def log_error(self, *, ctx: ContextPlus = None, extra=None):
+ e = discord.Embed(title="Error", colour=0xDD5F53)
+ e.description = f"```py\n{traceback.format_exc()}\n```"
+ e.add_field(name="Extra", value=extra, inline=False)
+ e.timestamp = datetime.datetime.utcnow()
+
+ if ctx is not None:
+ fmt = "{0} (ID: {0.id})"
+ author = fmt.format(ctx.author)
+ channel = fmt.format(ctx.channel)
+ guild = "None" if ctx.guild is None else fmt.format(ctx.guild)
+
+ e.add_field(name="Author", value=author)
+ e.add_field(name="Channel", value=channel)
+ e.add_field(name="Guild", value=guild)
+
+ await self.webhook("errors").send(embed=e)
+
+ async def send_guild_stats(self, e, guild):
+ e.add_field(name="Name", value=guild.name)
+ 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="Owner", value=f"{guild.owner} (ID: {guild.owner.id})"
+ )
+
+ bots = sum(member.bot for member in guild.members)
+ total = guild.member_count
+ online = sum(
+ member.status is discord.Status.online for member in guild.members
+ )
+
+ e.add_field(name="Members", value=str(total))
+ e.add_field(name="Bots", value=f"{bots} ({bots / total:.2%})")
+ e.add_field(name="Online", value=f"{online} ({online / total:.2%})")
+
+ if guild.icon:
+ e.set_thumbnail(url=guild.icon_url)
+
+ if guild.me:
+ e.timestamp = guild.me.joined_at
+
+ await self.webhook("guilds").send(embed=e)
+
+ @commands.Cog.listener()
+ async def on_guild_join(self, guild: discord.guild):
+ e = discord.Embed(colour=0x53DDA4, title="New Guild") # green colour
+ await self.send_guild_stats(e, guild)
+
+ @commands.Cog.listener()
+ async def on_guild_remove(self, guild: discord.guild):
+ e = discord.Embed(colour=0xDD5F53, title="Left Guild") # red colour
+ await self.send_guild_stats(e, guild)
+
+ @commands.Cog.listener()
+ async def on_message(self, message: discord.message):
+ if message.guild is None:
+ e = discord.Embed(colour=0x0A97F5, title="New DM") # blue colour
+ e.set_author(
+ name=message.author,
+ icon_url=message.author.avatar_url_as(format="png"),
+ )
+ e.description = message.content
+ if len(message.attachments) > 0:
+ e.set_image(url=message.attachments[0].url)
+ e.set_footer(text=f"User ID: {message.author.id}")
+ await self.webhook("dm").send(embed=e)
+
+ @commands.Cog.listener()
+ async def on_command_error(self, ctx: ContextPlus, error):
+ await self.register_command(ctx)
+ if not isinstance(
+ error, (commands.CommandInvokeError, commands.ConversionError)
+ ):
+ return
+
+ error = error.original
+ if isinstance(error, (discord.Forbidden, discord.NotFound)):
+ return
+
+ e = discord.Embed(title="Command Error", colour=0xCC3366)
+ e.add_field(name="Name", value=ctx.command.qualified_name)
+ e.add_field(name="Author", value=f"{ctx.author} (ID: {ctx.author.id})")
+
+ fmt = f"Channel: {ctx.channel} (ID: {ctx.channel.id})"
+ if ctx.guild:
+ fmt = f"{fmt}\nGuild: {ctx.guild} (ID: {ctx.guild.id})"
+
+ e.add_field(name="Location", value=fmt, inline=False)
+ e.add_field(
+ name="Content",
+ value=textwrap.shorten(ctx.message.content, width=512),
+ )
+
+ exc = "".join(
+ traceback.format_exception(
+ type(error), error, error.__traceback__, chain=False
+ )
+ )
+ e.description = f"```py\n{exc}\n```"
+ e.timestamp = datetime.datetime.utcnow()
+ await self.webhook("errors").send(embed=e)
+
+ @commands.Cog.listener()
+ async def on_socket_raw_send(self, data):
+ if '"op":2' not in data and '"op":6' not in data:
+ return
+
+ back_to_json = json.loads(data)
+ if back_to_json["op"] == 2:
+ payload = back_to_json["d"]
+ inner_shard = payload.get("shard", [0])
+ self._identifies[inner_shard[0]].append(datetime.datetime.utcnow())
+ else:
+ self._resumes.append(datetime.datetime.utcnow())
+
+ self._clear_gateway_data()
+
+ def add_record(self, record: LogRecord):
+ self._gateway_queue.put_nowait(record)
+
+ async def notify_gateway_status(self, record: LogRecord):
+ types = {"INFO": ":information_source:", "WARNING": ":warning:"}
+
+ emoji = types.get(record.levelname, ":heavy_multiplication_x:")
+ dt = datetime.datetime.utcfromtimestamp(record.created)
+ msg = f"{emoji} `[{dt:%Y-%m-%d %H:%M:%S}] {record.message}`"
+ await self.webhook("gateway").send(msg)
+
+ @command_extra(name="commandstats", hidden=True, deletable=True)
+ @commands.is_owner()
+ async def _commandstats(self, ctx: ContextPlus, limit=20):
+ counter = self.bot.stats["commands"]
+ width = len(max(counter, key=len)) + 1
+
+ if limit > 0:
+ common = counter.most_common(limit)
+ else:
+ common = counter.most_common()[limit:]
+
+ output = "\n".join(f"{k:<{width}}: {c}" for k, c in common)
+
+ await ctx.send(f"```\n{output}\n```")
+
+ @command_extra(name="socketstats", hidden=True, deletable=True)
+ @commands.is_owner()
+ async def _socketstats(self, ctx: ContextPlus):
+ delta = datetime.datetime.now() - self.bot.uptime
+ minutes = delta.total_seconds() / 60
+
+ counter = self.bot.stats["socket"]
+ if None in counter:
+ counter.pop(None)
+ width = len(max(counter, key=len)) + 1
+ common = counter.most_common()
+
+ total = sum(self.bot.stats["socket"].values())
+ cpm = total / minutes
+
+ output = "\n".join(f"{k:<{width}}: {c}" for k, c in common)
+
+ await ctx.send(
+ f"{total} socket events observed ({cpm:.2f}/minute):"
+ f"```\n{output}\n```"
+ )
+
+ @command_extra(name="uptime")
+ async def _uptime(self, ctx: ContextPlus):
+ uptime = humanize.naturaltime(
+ datetime.datetime.now() - self.bot.uptime
+ )
+ await ctx.send(f"Uptime: **{uptime}**")
+
+
+async def on_error(self, event, *args):
+ e = discord.Embed(title="Event Error", colour=0xA32952)
+ e.add_field(name="Event", value=event)
+ e.description = f"```py\n{traceback.format_exc()}\n```"
+ e.timestamp = datetime.datetime.utcnow()
+
+ args_str = ["```py"]
+ for index, arg in enumerate(args):
+ args_str.append(f"[{index}]: {arg!r}")
+ args_str.append("```")
+ e.add_field(name="Args", value="\n".join(args_str), inline=False)
+
+ hook = self.get_cog("Logs").webhook("errors")
+ try:
+ await hook.send(embed=e)
+ except (
+ discord.HTTPException,
+ discord.NotFound,
+ discord.Forbidden,
+ discord.InvalidArgument,
+ ):
+ pass
diff --git a/tuxbot/core/bot.py b/tuxbot/core/bot.py
index 1897a19..9541075 100644
--- a/tuxbot/core/bot.py
+++ b/tuxbot/core/bot.py
@@ -1,8 +1,10 @@
import asyncio
import datetime
import logging
+from collections import Counter
from typing import List, Union
+import aiohttp
import discord
from discord.ext import commands
from rich import box
@@ -33,7 +35,7 @@ log = logging.getLogger("tuxbot")
console = Console()
install(console=console)
-packages: List[str] = ["jishaku", "tuxbot.cogs.admin"]
+packages: List[str] = ["jishaku", "tuxbot.cogs.admin", "tuxbot.cogs.logs"]
class Tux(commands.AutoShardedBot):
@@ -55,6 +57,10 @@ class Tux(commands.AutoShardedBot):
self.last_exception = None
self.logs = logs_data_path(self.instance_name)
+ self.console = console
+
+ self.stats = {"commands": Counter(), "socket": Counter()}
+
self.config: Config = ConfigFile(
str(data_path(self.instance_name) / "config.yaml"), Config
).config
@@ -84,6 +90,7 @@ class Tux(commands.AutoShardedBot):
self._app_owners_fetched = False # to prevent abusive API calls
super().__init__(*args, help_command=None, **kwargs)
+ self.session = aiohttp.ClientSession(loop=self.loop)
async def load_packages(self):
if packages:
diff --git a/tuxbot/core/config.py b/tuxbot/core/config.py
index 6d8a13e..9ea992c 100644
--- a/tuxbot/core/config.py
+++ b/tuxbot/core/config.py
@@ -1,5 +1,5 @@
import logging
-from typing import List, Dict, Any, NoReturn
+from typing import List, Dict, Any
from structured_config import (
Structure,
IntField,
@@ -80,7 +80,7 @@ def search_for(config, key, value, default=False) -> Any:
return default
-def set_for_key(config, key, ctype, **values) -> NoReturn:
+def set_for_key(config, key, ctype, **values) -> None:
# pylint: disable=anomalous-backslash-in-string
"""
La fonction suivante \`*-.
@@ -105,6 +105,6 @@ def set_for_key(config, key, ctype, **values) -> NoReturn:
setattr(config[key], k, v)
-def set_for(config, **values) -> NoReturn:
+def set_for(config, **values) -> None:
for k, v in values.items():
setattr(config, k, v)
diff --git a/tuxbot/core/utils/functions/extra.py b/tuxbot/core/utils/functions/extra.py
index 44897db..0fe7915 100644
--- a/tuxbot/core/utils/functions/extra.py
+++ b/tuxbot/core/utils/functions/extra.py
@@ -12,24 +12,45 @@ TOKEN_REPLACEMENT = "whoops, leaked token"
class ContextPlus(commands.Context):
- async def send(self, *args, content=None, **kwargs):
- if content is not None:
+ async def send(
+ self,
+ content=None,
+ *,
+ tts=False,
+ embed=None,
+ file=None,
+ files=None,
+ delete_after=None,
+ nonce=None,
+ allowed_mentions=None,
+ deletable=False
+ ): # i know *args and **kwargs but, i prefer work with same values
+ if content:
content = content.replace(
self.bot.config.Core.token, TOKEN_REPLACEMENT
)
- if kwargs.get("embed"):
- embed = kwargs["embed"].to_dict()
- for key, value in embed.items():
+ if embed:
+ e = embed.to_dict()
+ for key, value in e.items():
if isinstance(value, (str, bytes)):
- embed[key] = value.replace(
+ e[key] = value.replace(
self.bot.config.Core.token, TOKEN_REPLACEMENT
)
- kwargs["embed"] = Embed.from_dict(embed)
+ embed = Embed.from_dict(e)
if (
hasattr(self.command, "deletable") and self.command.deletable
- ) or kwargs.pop("deletable", False):
- message = await super().send(content, *args, **kwargs)
+ ) or deletable:
+ message = await super().send(
+ content=content,
+ tts=tts,
+ embed=embed,
+ file=file,
+ files=files,
+ delete_after=delete_after,
+ nonce=nonce,
+ allowed_mentions=allowed_mentions,
+ )
await message.add_reaction("🗑")
def check(reaction: discord.Reaction, user: discord.User):
@@ -49,7 +70,16 @@ class ContextPlus(commands.Context):
await message.delete()
return message
- return await super().send(content, *args, **kwargs)
+ return await super().send(
+ content=content,
+ tts=tts,
+ embed=embed,
+ file=file,
+ files=files,
+ delete_after=delete_after,
+ nonce=nonce,
+ allowed_mentions=allowed_mentions,
+ )
class CommandPLus(flags.FlagCommand):
diff --git a/tuxbot/logging.py b/tuxbot/logging.py
index 44cabb5..3b99329 100644
--- a/tuxbot/logging.py
+++ b/tuxbot/logging.py
@@ -24,19 +24,19 @@ def init_logging(level: int, location: pathlib.Path) -> None:
Where to store logs.
"""
- dpy_logger = logging.getLogger("discord")
- dpy_logger.setLevel(logging.WARN)
- dpy_logger_file = location / "discord.log"
+ # dpy_logger = logging.getLogger("discord")
+ # dpy_logger.setLevel(logging.WARN)
+ # dpy_logger_file = location / "discord.log"
base_logger = logging.getLogger("tuxbot")
base_logger.setLevel(level)
base_logger_file = location / "tuxbot.log"
- dpy_handler = logging.handlers.RotatingFileHandler(
- str(dpy_logger_file.resolve()),
- maxBytes=MAX_BYTES,
- backupCount=MAX_OLD_LOGS,
- )
+ # dpy_handler = logging.handlers.RotatingFileHandler(
+ # str(dpy_logger_file.resolve()),
+ # maxBytes=MAX_BYTES,
+ # backupCount=MAX_OLD_LOGS,
+ # )
base_handler = logging.handlers.RotatingFileHandler(
str(base_logger_file.resolve()),
maxBytes=MAX_BYTES,
@@ -46,8 +46,8 @@ def init_logging(level: int, location: pathlib.Path) -> None:
stdout_handler = logging.StreamHandler(sys.stdout)
stdout_handler.setFormatter(formatter)
- dpy_handler.setFormatter(formatter)
+ # dpy_handler.setFormatter(formatter)
base_handler.setFormatter(formatter)
- dpy_logger.addHandler(dpy_handler)
+ # dpy_logger.addHandler(dpy_handler)
base_logger.addHandler(base_handler)
diff --git a/tuxbot/setup.py b/tuxbot/setup.py
index da7270b..0dd3801 100644
--- a/tuxbot/setup.py
+++ b/tuxbot/setup.py
@@ -5,7 +5,7 @@ import re
import sys
from argparse import Namespace
from pathlib import Path
-from typing import NoReturn, Union, List
+from typing import Union, List
from rich.prompt import Prompt, IntPrompt
from rich.console import Console
@@ -263,7 +263,7 @@ def additional_config(instance: str, cogs: str = "**"):
)
-def finish_setup(data_dir: Path) -> NoReturn:
+def finish_setup(data_dir: Path) -> None:
"""Configs who directly refer to the bot.
Parameters
@@ -311,7 +311,7 @@ def finish_setup(data_dir: Path) -> NoReturn:
instance_config.config.Core.locale = "en-US"
-def basic_setup() -> NoReturn:
+def basic_setup() -> None:
"""Configs who refer to instances."""
console.print(
Rule(
@@ -395,7 +395,7 @@ def parse_cli_flags(args: list) -> Namespace:
return args
-def setup() -> NoReturn:
+def setup() -> None:
cli_flags = parse_cli_flags(sys.argv[1:])
try: