feat(core|logs>sentry): feat sentry error handler
This commit is contained in:
parent
554c0b52d5
commit
647cc4bd64
11 changed files with 151 additions and 202 deletions
|
@ -1,3 +1,2 @@
|
|||
youtrack
|
||||
pylint>=2.6.0
|
||||
black>=20.8b1
|
|
@ -26,6 +26,7 @@ install_requires =
|
|||
psutil>=5.7.2
|
||||
requests>=2.25.1
|
||||
rich>=6.0.0
|
||||
sentry_sdk>=0.19.5
|
||||
structured_config>=4.12
|
||||
tortoise-orm>=0.16.17
|
||||
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
from rich.console import Console
|
||||
from rich.traceback import install
|
||||
from tuxbot import ExitCodes
|
||||
|
||||
console = Console()
|
||||
install(console=console, show_locals=True)
|
||||
from tuxbot.core.utils.console import console
|
||||
|
||||
|
||||
def main() -> None:
|
||||
|
|
|
@ -11,9 +11,7 @@ import discord
|
|||
import humanize
|
||||
import pip
|
||||
from rich.columns import Columns
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.traceback import install
|
||||
from rich.table import Table, box
|
||||
from rich.text import Text
|
||||
from rich import print as rprint
|
||||
|
@ -21,14 +19,12 @@ from rich import print as rprint
|
|||
import tuxbot.logging
|
||||
from tuxbot.core.bot import Tux
|
||||
from tuxbot.core import config
|
||||
from .core.utils import data_manager
|
||||
from tuxbot.core.utils import data_manager
|
||||
from tuxbot.core.utils.console import console
|
||||
from . import __version__, version_info, ExitCodes
|
||||
|
||||
log = logging.getLogger("tuxbot.main")
|
||||
|
||||
console = Console()
|
||||
install(console=console, show_locals=True)
|
||||
|
||||
BORDER_STYLE = "not dim"
|
||||
|
||||
|
||||
|
|
|
@ -4,19 +4,12 @@ HAS_MODELS = False
|
|||
|
||||
|
||||
class DevConfig(Structure):
|
||||
url: str = StrField("")
|
||||
login: str = StrField("")
|
||||
password: str = StrField("")
|
||||
sentryKey: str = StrField("")
|
||||
|
||||
|
||||
extra = {
|
||||
"url": {
|
||||
"sentryKey": {
|
||||
"type": str,
|
||||
"description": "URL of the YouTrack instance (without /youtrack/)",
|
||||
},
|
||||
"login": {"type": str, "description": "Login for YouTrack instance"},
|
||||
"password": {
|
||||
"type": str,
|
||||
"description": "Password for YouTrack instance",
|
||||
"description": "Sentry KEY for error logging (https://sentry.io/)",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,46 +1,30 @@
|
|||
import logging
|
||||
from discord.ext import commands
|
||||
from youtrack.connection import Connection as YouTrack
|
||||
from structured_config import ConfigFile
|
||||
|
||||
from tuxbot.core.bot import Tux
|
||||
from tuxbot.core.i18n import (
|
||||
Translator,
|
||||
)
|
||||
from tuxbot.core.utils.data_manager import cogs_data_path
|
||||
from .config import DevConfig
|
||||
from ...core.utils import checks
|
||||
from ...core.utils.functions.extra import group_extra, ContextPlus
|
||||
from tuxbot.core.utils import checks
|
||||
from tuxbot.core.utils.functions.extra import command_extra, ContextPlus
|
||||
|
||||
log = logging.getLogger("tuxbot.cogs.Dev")
|
||||
_ = Translator("Dev", __file__)
|
||||
|
||||
|
||||
class Dev(commands.Cog, name="Dev"):
|
||||
yt: YouTrack # pylint: disable=invalid-name
|
||||
|
||||
def __init__(self, bot: Tux):
|
||||
self.bot = bot
|
||||
self.__config: DevConfig = ConfigFile(
|
||||
str(cogs_data_path(self.bot.instance_name, "Dev") / "config.yaml"),
|
||||
DevConfig,
|
||||
).config
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
self.yt = YouTrack(
|
||||
self.__config.url.rstrip("/") + "/youtrack/",
|
||||
login=self.__config.login,
|
||||
password=self.__config.password,
|
||||
)
|
||||
|
||||
# =========================================================================
|
||||
# =========================================================================
|
||||
|
||||
@group_extra(name="issue", aliases=["issues"], deletable=True)
|
||||
@command_extra(name="crash", deletable=True)
|
||||
@checks.is_owner()
|
||||
async def _issue(self, ctx: ContextPlus):
|
||||
"""Manage bot issues."""
|
||||
|
||||
@_issue.command(name="list", aliases=["liste", "all", "view"])
|
||||
async def _lang_list(self, ctx: ContextPlus):
|
||||
pass
|
||||
async def _crash(self, ctx: ContextPlus, crash_type: str):
|
||||
if crash_type == "ZeroDivisionError":
|
||||
await ctx.send(str(5 / 0))
|
||||
elif crash_type == "TypeError":
|
||||
await ctx.send(str(int([])))
|
||||
elif crash_type == "IndexError":
|
||||
await ctx.send(str([0][5]))
|
||||
|
|
|
@ -4,7 +4,7 @@ from collections import namedtuple
|
|||
from discord.ext import commands
|
||||
|
||||
from tuxbot.core.bot import Tux
|
||||
from .logs import Logs, on_error, GatewayHandler
|
||||
from .logs import Logs, GatewayHandler
|
||||
from .config import LogsConfig, HAS_MODELS
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro release_level")
|
||||
|
@ -24,4 +24,3 @@ def setup(bot: Tux):
|
|||
|
||||
handler = GatewayHandler(cog)
|
||||
logging.getLogger().addHandler(handler)
|
||||
commands.AutoShardedBot.on_error = on_error
|
||||
|
|
|
@ -9,6 +9,7 @@ class LogsConfig(Structure):
|
|||
guilds: str = StrField("")
|
||||
errors: str = StrField("")
|
||||
gateway: str = StrField("")
|
||||
sentryKey: str = StrField("")
|
||||
|
||||
|
||||
extra = {
|
||||
|
@ -35,4 +36,8 @@ extra = {
|
|||
"type": str,
|
||||
"description": "URL of the webhook used for send gateway information",
|
||||
},
|
||||
"sentryKey": {
|
||||
"type": str,
|
||||
"description": "Sentry KEY for error logging (https://sentry.io/)",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ from logging import LogRecord
|
|||
import discord
|
||||
import humanize
|
||||
import psutil
|
||||
import sentry_sdk
|
||||
from discord.ext import commands, tasks
|
||||
from structured_config import ConfigFile
|
||||
|
||||
|
@ -64,7 +65,70 @@ class Logs(commands.Cog, name="Logs"):
|
|||
self._resumes = []
|
||||
self._identifies = defaultdict(list)
|
||||
|
||||
def _clear_gateway_data(self):
|
||||
self.old_on_error = bot.on_error
|
||||
bot.on_error = self.on_error
|
||||
|
||||
sentry_sdk.init(
|
||||
dsn=self.__config.sentryKey,
|
||||
traces_sample_rate=1.0,
|
||||
environment=self.bot.instance_name,
|
||||
debug=False,
|
||||
)
|
||||
|
||||
def cog_unload(self):
|
||||
self.bot.on_error = self.old_on_error
|
||||
|
||||
async def on_error(self, event):
|
||||
raise event
|
||||
|
||||
# =========================================================================
|
||||
# =========================================================================
|
||||
|
||||
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 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)
|
||||
|
||||
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)
|
||||
|
||||
def clear_gateway_data(self):
|
||||
one_week_ago = datetime.datetime.utcnow() - datetime.timedelta(days=7)
|
||||
to_remove = [
|
||||
index
|
||||
|
@ -81,11 +145,6 @@ class Logs(commands.Cog, name="Logs"):
|
|||
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
|
||||
|
@ -120,6 +179,14 @@ class Logs(commands.Cog, name="Logs"):
|
|||
}
|
||||
)
|
||||
|
||||
# =========================================================================
|
||||
# =========================================================================
|
||||
|
||||
@tasks.loop(seconds=0.0)
|
||||
async def gateway_worker(self):
|
||||
record = await self._gateway_queue.get()
|
||||
await self.notify_gateway_status(record)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_command_completion(self, ctx: ContextPlus):
|
||||
await self.register_command(ctx)
|
||||
|
@ -128,57 +195,6 @@ class Logs(commands.Cog, name="Logs"):
|
|||
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
|
||||
|
@ -204,7 +220,9 @@ class Logs(commands.Cog, name="Logs"):
|
|||
await self.webhook("dm").send(embed=e)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_command_error(self, ctx: ContextPlus, error):
|
||||
async def on_command_error(
|
||||
self, ctx: ContextPlus, error: commands.CommandError
|
||||
):
|
||||
await self.register_command(ctx)
|
||||
if not isinstance(
|
||||
error, (commands.CommandInvokeError, commands.ConversionError)
|
||||
|
@ -215,6 +233,11 @@ class Logs(commands.Cog, name="Logs"):
|
|||
if isinstance(error, (discord.Forbidden, discord.NotFound)):
|
||||
return
|
||||
|
||||
sentry_sdk.capture_exception(error)
|
||||
self.bot.console.log(
|
||||
"Command Error, check sentry or discord error channel"
|
||||
)
|
||||
|
||||
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})")
|
||||
|
@ -251,18 +274,10 @@ class Logs(commands.Cog, name="Logs"):
|
|||
else:
|
||||
self._resumes.append(datetime.datetime.utcnow())
|
||||
|
||||
self._clear_gateway_data()
|
||||
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()
|
||||
|
@ -318,27 +333,3 @@ class Logs(commands.Cog, name="Logs"):
|
|||
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
|
||||
|
|
|
@ -10,9 +10,8 @@ import discord
|
|||
from discord.ext import commands
|
||||
from rich import box
|
||||
from rich.columns import Columns
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.progress import Progress, TextColumn, BarColumn
|
||||
from rich.progress import Progress
|
||||
from rich.table import Table
|
||||
from tortoise import Tortoise
|
||||
|
||||
|
@ -22,7 +21,10 @@ from tuxbot.core.utils.data_manager import (
|
|||
data_path,
|
||||
config_dir,
|
||||
)
|
||||
from .config import (
|
||||
from tuxbot.core.utils.functions.extra import ContextPlus
|
||||
from tuxbot.core.utils.functions.prefix import get_prefixes
|
||||
from tuxbot.core.utils.console import console
|
||||
from tuxbot.core.config import (
|
||||
Config,
|
||||
ConfigFile,
|
||||
search_for,
|
||||
|
@ -31,17 +33,14 @@ from .config import (
|
|||
)
|
||||
from . import __version__, ExitCodes
|
||||
from . import exceptions
|
||||
from .utils.functions.extra import ContextPlus
|
||||
from .utils.functions.prefix import get_prefixes
|
||||
|
||||
log = logging.getLogger("tuxbot")
|
||||
console = Console()
|
||||
|
||||
packages: List[str] = [
|
||||
"jishaku",
|
||||
"tuxbot.cogs.Admin",
|
||||
"tuxbot.cogs.Logs",
|
||||
# "tuxbot.cogs.Dev",
|
||||
"tuxbot.cogs.Dev",
|
||||
"tuxbot.cogs.Utils",
|
||||
"tuxbot.cogs.Polls",
|
||||
"tuxbot.cogs.Custom",
|
||||
|
@ -51,13 +50,7 @@ packages: List[str] = [
|
|||
|
||||
class Tux(commands.AutoShardedBot):
|
||||
_loading: asyncio.Task
|
||||
_progress = {
|
||||
"main": Progress(
|
||||
TextColumn("[bold blue]{task.fields[task_name]}", justify="right"),
|
||||
BarColumn(),
|
||||
),
|
||||
"tasks": {},
|
||||
}
|
||||
_progress = {"tasks": {}, "main": Progress()}
|
||||
|
||||
def __init__(self, *args, cli_flags=None, **kwargs):
|
||||
# by default, if the bot shutdown without any intervention,
|
||||
|
@ -162,20 +155,19 @@ class Tux(commands.AutoShardedBot):
|
|||
last_run=datetime.datetime.timestamp(self.uptime),
|
||||
)
|
||||
|
||||
self._progress["main"].stop_task(self._progress["tasks"]["connecting"])
|
||||
self._progress["main"].remove_task(
|
||||
self._progress["tasks"]["connecting"]
|
||||
)
|
||||
self._progress["tasks"].pop("connecting")
|
||||
console.clear()
|
||||
with self._progress["main"] as progress:
|
||||
progress.stop_task(self._progress["tasks"]["discord_connecting"])
|
||||
progress.remove_task(self._progress["tasks"]["discord_connecting"])
|
||||
self._progress["tasks"].pop("discord_connecting")
|
||||
self.console.clear()
|
||||
|
||||
console.print(
|
||||
self.console.print(
|
||||
Panel(f"[bold blue]Tuxbot V{version_info.major}", style="blue"),
|
||||
justify="center",
|
||||
)
|
||||
console.print()
|
||||
self.console.print()
|
||||
|
||||
columns = Columns(expand=True, align="center")
|
||||
columns = Columns(align="center", expand=True)
|
||||
|
||||
table = Table(style="dim", border_style="not dim", box=box.HEAVY_HEAD)
|
||||
table.add_column(
|
||||
|
@ -204,8 +196,8 @@ class Tux(commands.AutoShardedBot):
|
|||
table.add_row(status)
|
||||
columns.add_renderable(table)
|
||||
|
||||
console.print(columns)
|
||||
console.print()
|
||||
self.console.print(columns)
|
||||
self.console.print()
|
||||
|
||||
async def is_owner(
|
||||
self, user: Union[discord.User, discord.Member]
|
||||
|
@ -278,29 +270,24 @@ class Tux(commands.AutoShardedBot):
|
|||
await self.process_commands(message)
|
||||
|
||||
async def start(self, token, bot): # pylint: disable=arguments-differ
|
||||
"""Connect to Discord and start all connections.
|
||||
|
||||
Todo: add postgresql connect here
|
||||
"""
|
||||
with self._progress.get("main") as progress:
|
||||
task_id = self._progress.get("tasks")[
|
||||
"connecting"
|
||||
] = progress.add_task(
|
||||
"connecting",
|
||||
task_name="Connecting to PostgreSQL...",
|
||||
start=False,
|
||||
"""Connect to Discord and start all connections."""
|
||||
with Progress() as progress:
|
||||
task = progress.add_task(
|
||||
"Connecting to PostgreSQL...", total=len(self.extensions)
|
||||
)
|
||||
|
||||
models = []
|
||||
|
||||
for extension, _ in self.extensions.items():
|
||||
if extension == "jishaku":
|
||||
progress.advance(task)
|
||||
continue
|
||||
|
||||
if importlib.import_module(extension).HAS_MODELS:
|
||||
models.append(f"{extension}.models.__init__")
|
||||
|
||||
progress.update(task_id)
|
||||
progress.advance(task)
|
||||
|
||||
await Tortoise.init(
|
||||
db_url="postgres://{}:{}@{}:{}/{}".format(
|
||||
self.config.Core.Database.username,
|
||||
|
@ -313,17 +300,13 @@ class Tux(commands.AutoShardedBot):
|
|||
)
|
||||
await Tortoise.generate_schemas()
|
||||
|
||||
self._progress["main"].stop_task(self._progress["tasks"]["connecting"])
|
||||
self._progress["main"].remove_task(
|
||||
self._progress["tasks"]["connecting"]
|
||||
)
|
||||
self._progress["tasks"].pop("connecting")
|
||||
|
||||
with self._progress.get("main") as progress:
|
||||
task_id = self._progress.get("tasks")[
|
||||
"connecting"
|
||||
with self._progress["main"] as progress:
|
||||
task_id = self._progress["tasks"][
|
||||
"discord_connecting"
|
||||
] = progress.add_task(
|
||||
"connecting", task_name="Connecting to Discord...", start=False
|
||||
"discord_connecting",
|
||||
task_name="Connecting to Discord...",
|
||||
start=False,
|
||||
)
|
||||
progress.update(task_id)
|
||||
await super().start(token, bot=bot)
|
||||
|
@ -341,21 +324,22 @@ class Tux(commands.AutoShardedBot):
|
|||
active=False,
|
||||
)
|
||||
|
||||
with self._progress["main"] as progress:
|
||||
for task in self._progress["tasks"]:
|
||||
self._progress["main"].log("Shutting down", task)
|
||||
progress.log("Shutting down", task)
|
||||
|
||||
self._progress["main"].stop_task(self._progress["tasks"][task])
|
||||
self._progress["main"].remove_task(
|
||||
self._progress["tasks"]["connecting"]
|
||||
)
|
||||
self._progress["main"].stop()
|
||||
progress.stop_task(self._progress["tasks"][task])
|
||||
progress.remove_task(self._progress["tasks"][task])
|
||||
progress.stop()
|
||||
|
||||
pending = [
|
||||
t for t in asyncio.all_tasks() if t is not asyncio.current_task()
|
||||
]
|
||||
|
||||
for task in pending:
|
||||
console.log("Canceling", task.get_name(), f"({task.get_coro()})")
|
||||
self.console.log(
|
||||
"Canceling", task.get_name(), f"({task.get_coro()})"
|
||||
)
|
||||
task.cancel()
|
||||
await asyncio.gather(*pending, return_exceptions=False)
|
||||
|
||||
|
|
|
@ -6,10 +6,6 @@ import discord
|
|||
from discord import Embed
|
||||
from discord.ext import commands
|
||||
|
||||
from rich.console import Console
|
||||
|
||||
console = Console()
|
||||
|
||||
TOKEN_REPLACEMENT = "■" * random.randint(3, 15)
|
||||
PASSWORD_REPLACEMENT = "■" * random.randint(3, 15)
|
||||
IP_REPLACEMENT = "■" * random.randint(3, 15)
|
||||
|
@ -130,6 +126,11 @@ class ContextPlus(commands.Context):
|
|||
def session(self) -> aiohttp.ClientSession:
|
||||
return self.bot.session
|
||||
|
||||
def __repr__(self):
|
||||
items = ("%s = %r" % (k, v) for k, v in self.__dict__.items())
|
||||
|
||||
return "<%s: {%s}>" % (self.__class__.__name__, ", ".join(items))
|
||||
|
||||
|
||||
class CommandPLus(commands.Command):
|
||||
def __init__(self, function, **kwargs):
|
||||
|
|
Loading…
Reference in a new issue