-
Notifications
You must be signed in to change notification settings - Fork 8
/
bot.py
130 lines (103 loc) · 3.99 KB
/
bot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
from __future__ import annotations
import io
import logging
import os
import sys
from contextlib import contextmanager
from logging.handlers import TimedRotatingFileHandler
from traceback import TracebackException
import discord
from discord import app_commands
from discord.ext import commands
import config
logger = logging.getLogger("Bot")
@contextmanager
def log_setup():
"""
Context manager that sets up file logging
"""
logger = logging.getLogger()
try:
logging.getLogger("discord").setLevel(logging.INFO)
logging.getLogger("discord.http").setLevel(logging.INFO)
logger.setLevel(logging.DEBUG)
dtfmt = "%Y-%m-%d %H:%M:%S"
if not os.path.isdir("logs/"):
os.mkdir("logs/")
# Add custom logging handlers like rich, maybe in the future??
handlers = [
TimedRotatingFileHandler(filename="logs/bot.log", when="d", interval=5),
logging.StreamHandler(sys.stdout),
]
fmt = logging.Formatter("[{asctime}] [{levelname:<7}] {name}: {message}", dtfmt, style="{")
for handler in handlers:
handler.setFormatter(fmt)
logger.addHandler(handler)
yield
finally:
handlers = logger.handlers[:]
for handler in handlers:
handler.close()
logger.removeHandler(handler)
class BotTree(app_commands.CommandTree["IITMBot"]):
"""
Subclass of app_commands.CommandTree to define the behavior for the bot's slash command tree.
Handles thrown errors within the tree and interactions between all commands
"""
async def log_to_channel(self, interaction: discord.Interaction, err: Exception):
"""
Log error to discord channel defined in config.py
"""
channel: discord.TextChannel = interaction.client.get_channel(config.DEV_LOGS_CHANNEL) # type: ignore
traceback_txt = "".join(TracebackException.from_exception(err).format())
file = discord.File(io.BytesIO(traceback_txt.encode()), filename=f"{type(err)}.txt")
embed = discord.Embed(
title="Unhandled Exception Alert",
description=f"""
Invoked Channel: {interaction.channel}
\nInvoked User: {interaction.user.display_name}
\n```{traceback_txt[2000:].strip()}```
""",
)
await channel.send(embed=embed, file=file)
async def on_error(self, interaction: discord.Interaction["IITMBot"], error: app_commands.AppCommandError):
"""Handles errors thrown within the command tree"""
try:
await self.log_to_channel(interaction, error)
except Exception:
await super().on_error(interaction, error)
class IITMBot(commands.AutoShardedBot):
"""
Main bot. invoked in runner (main.py)
"""
user: discord.ClientUser
@classmethod
def _use_default(cls):
"""
Create an instance of IITMBot with base configuration
"""
intents = discord.Intents.all()
activity = discord.Activity(type=discord.ActivityType.watching, name=config.DEFAULT_ACTIVITY_TEXT)
x = cls(
command_prefix=config.BOT_PREFIX,
intents=intents,
owner_id=config.OWNER_ID,
activity=activity,
help_command=None,
tree_cls=BotTree,
)
return x
async def load_extensions(self):
for filename in os.listdir("cogs/"):
if filename.endswith(".py"):
logger.info(f"Trying to load cogs.{filename[:-3]}")
try:
await self.load_extension(f"cogs.{filename[:-3]}")
logger.info(f"Loaded cogs.{filename[:-3]}")
except Exception as e:
logger.error(f"cogs.{filename[:-3]} failed to load: {e}")
async def on_ready(self):
logger.info("Logged in as")
logger.info(f"\tUser: {self.user.name}")
logger.info(f"\tID : {self.user.id}")
logger.info("------")