forked from sinsniwal/iitm-server-bot
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbot.py
153 lines (125 loc) · 4.53 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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import io
import os
from traceback import TracebackException
import config
import dotenv
import sys
import logging
from logging.handlers import TimedRotatingFileHandler
import discord
from discord.ext import commands
from discord import app_commands
from contextlib import contextmanager, suppress
logger = logging.getLogger("Bot")
@contextmanager
def log_setup():
"""
Context manager that sets up file logging
"""
try:
dotenv.load_dotenv()
logging.getLogger("discord").setLevel(logging.INFO)
logging.getLogger("discord.http").setLevel(logging.INFO)
logger = logging.getLogger()
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):
"""
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 = await interaction.client.fetch_channel(config.DEV_LOGS_CHANNEL)
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.name}
\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, error: app_commands.AppCommandError
):
"""Handles errors thrown within the command tree"""
try:
await self.log_to_channel(interaction, error)
except Exception as e:
await super().on_error(interaction, e)
class IITMBot(commands.AutoShardedBot):
"""
Main bot. invoked in runner (main.py)
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@classmethod
def _use_default(cls, *args):
"""
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, *args):
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 close(self):
"""
Clean exit from discord and aiohttps sessions (maybe for bottle in future?)
"""
for ext in list(self.extensions):
with suppress(Exception):
await self.unload_extension(ext)
for cog in list(self.cogs):
with suppress(Exception):
await self.remove_cog(cog)
await super().close()
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("------")