101 lines
2.9 KiB
Python
101 lines
2.9 KiB
Python
import discord
|
|
from discord.ext import commands
|
|
import asyncio
|
|
import logging
|
|
import os
|
|
import json
|
|
from dotenv import load_dotenv
|
|
|
|
CONFIG_FILE = 'bot_config.json'
|
|
logger = logging.getLogger(__name__)
|
|
|
|
def setup_logging() -> None:
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
handlers=[
|
|
logging.FileHandler('bot.log', encoding='utf-8'),
|
|
logging.StreamHandler()
|
|
]
|
|
)
|
|
|
|
def load_config() -> dict:
|
|
if not os.path.exists(CONFIG_FILE):
|
|
raise FileNotFoundError(f"Main bot config not found at {CONFIG_FILE}")
|
|
|
|
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
|
|
config = json.load(f)
|
|
|
|
# Apply fallbacks
|
|
settings = config.get("settings", {})
|
|
settings.setdefault("command_prefix", "!")
|
|
settings.setdefault("sync_commands", True)
|
|
config["settings"] = settings
|
|
config.setdefault("cogs", [])
|
|
|
|
return config
|
|
|
|
class Bot(commands.Bot):
|
|
def __init__(self, config: dict):
|
|
self.config = config
|
|
|
|
intents = discord.Intents.default()
|
|
intents.message_content = True
|
|
intents.reactions = True
|
|
intents.members = True
|
|
intents.guilds = True
|
|
intents.messages = True
|
|
|
|
super().__init__(
|
|
command_prefix=self.config["settings"]["command_prefix"],
|
|
intents=intents,
|
|
help_command=None,
|
|
)
|
|
|
|
async def setup_hook(self) -> None:
|
|
for cog in self.config["cogs"]:
|
|
try:
|
|
await self.load_extension(cog)
|
|
logger.info("Loaded cog: %s", cog)
|
|
except Exception as e:
|
|
logger.error("Failed to load cog %s: %s", cog, e, exc_info=True)
|
|
|
|
if self.config["settings"]["sync_commands"]:
|
|
try:
|
|
await self.tree.sync()
|
|
logger.info("Application commands synced successfully")
|
|
except Exception as e:
|
|
logger.error("Failed to sync application commands: %s", e)
|
|
|
|
async def on_ready(self) -> None:
|
|
if not hasattr(self, '_ready_fired'):
|
|
self._ready_fired = True
|
|
logger.info("Bot ready: %s (id: %s)", self.user, self.user.id)
|
|
|
|
async def main() -> None:
|
|
setup_logging()
|
|
load_dotenv()
|
|
|
|
token = os.getenv('DISCORD_TOKEN')
|
|
if not token:
|
|
logger.critical("DISCORD_TOKEN environment variable is missing; Shutting down")
|
|
return
|
|
|
|
try:
|
|
config = load_config()
|
|
except Exception as e:
|
|
logger.critical("Failed to load configuration: %s", e)
|
|
return
|
|
|
|
bot = Bot(config)
|
|
|
|
try:
|
|
await bot.start(token)
|
|
except discord.LoginFailure:
|
|
logger.critical("Improper DISCORD_TOKEN passed; Shutting down")
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
asyncio.run(main())
|
|
except KeyboardInterrupt:
|
|
logger.info("Bot shutdown via keyboard interrupt") |