Run Black formatter across 83 files and fix 1514 ruff violations: - E722: bare except → typed exceptions (17 fixes) - E711/E712/E721: comparison style fixes with noqa for SQLAlchemy (44 fixes) - F841: unused variable assignments (70 fixes) - F541/F401: f-string and import cleanup (1383 auto-fixes) Remaining 925 errors are all F403/F405 (star imports) — structural, requires converting to explicit imports in a separate effort. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
151 lines
4.7 KiB
Python
151 lines
4.7 KiB
Python
import discord
|
|
import logging
|
|
from logging.handlers import RotatingFileHandler
|
|
import asyncio
|
|
import os
|
|
|
|
from discord.ext import commands
|
|
|
|
from in_game.gameplay_models import create_db_and_tables, Session, engine
|
|
from in_game.gameplay_queries import get_channel_game_or_none
|
|
from health_server import run_health_server
|
|
from notify_restart import send_restart_notification
|
|
|
|
raw_log_level = os.getenv("LOG_LEVEL")
|
|
if raw_log_level == "DEBUG":
|
|
log_level = logging.DEBUG
|
|
elif raw_log_level == "INFO":
|
|
log_level = logging.INFO
|
|
elif raw_log_level == "WARN":
|
|
log_level = logging.WARNING
|
|
else:
|
|
log_level = logging.ERROR
|
|
|
|
# date = f'{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}'
|
|
# logger.basicConfig(
|
|
# filename=f'logs/{date}.log',
|
|
# format='%(asctime)s - %(levelname)s - %(message)s',
|
|
# level=log_level
|
|
# )
|
|
# logger.getLogger('discord.http').setLevel(logger.INFO)
|
|
logger = logging.getLogger("discord_app")
|
|
logger.setLevel(log_level)
|
|
|
|
handler = RotatingFileHandler(
|
|
filename="logs/discord.log",
|
|
# encoding='utf-8',
|
|
maxBytes=32 * 1024 * 1024, # 32 MiB
|
|
backupCount=5, # Rotate through 5 files
|
|
)
|
|
|
|
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
|
handler.setFormatter(formatter)
|
|
|
|
# dt_fmt = '%Y-%m-%d %H:%M:%S'
|
|
# formatter = logger.Formatter('[{asctime}] [{levelname:<8}] {name}: {message}', dt_fmt, style='{')
|
|
# handler.setFormatter(formatter)
|
|
logger.addHandler(handler)
|
|
|
|
COGS = [
|
|
"cogs.owner",
|
|
"cogs.admins",
|
|
"cogs.economy",
|
|
"cogs.players",
|
|
"cogs.gameplay",
|
|
"cogs.economy_new.scouting",
|
|
]
|
|
|
|
intents = discord.Intents.default()
|
|
intents.members = True
|
|
intents.message_content = True
|
|
bot = commands.Bot(
|
|
command_prefix=".",
|
|
intents=intents,
|
|
# help_command=None,
|
|
description="The Paper Dynasty Bot\nIf you have questions, feel free to contact Cal.",
|
|
case_insensitive=True,
|
|
owner_id=258104532423147520,
|
|
)
|
|
|
|
|
|
@bot.event
|
|
async def on_ready():
|
|
logger.info("Logged in as:")
|
|
logger.info(bot.user.name)
|
|
logger.info(bot.user.id)
|
|
|
|
# Send restart notification if configured
|
|
send_restart_notification()
|
|
|
|
|
|
@bot.tree.error
|
|
async def on_app_command_error(
|
|
interaction: discord.Interaction, error: discord.app_commands.AppCommandError
|
|
):
|
|
"""Global error handler for all app commands (slash commands)."""
|
|
logger.error(f"App command error in {interaction.command}: {error}", exc_info=error)
|
|
|
|
# CRITICAL: Release play lock if command failed during gameplay
|
|
# This prevents permanent user lockouts when exceptions occur
|
|
try:
|
|
with Session(engine) as session:
|
|
game = get_channel_game_or_none(session, interaction.channel_id)
|
|
if game:
|
|
current_play = game.current_play_or_none(session)
|
|
if current_play and current_play.locked and not current_play.complete:
|
|
logger.warning(
|
|
f"Releasing stuck play lock {current_play.id} in game {game.id} "
|
|
f"after command error: {error}"
|
|
)
|
|
current_play.locked = False
|
|
session.add(current_play)
|
|
session.commit()
|
|
except Exception as lock_error:
|
|
logger.error(
|
|
f"Failed to release play lock after error: {lock_error}",
|
|
exc_info=lock_error,
|
|
)
|
|
|
|
# Try to respond to the user
|
|
try:
|
|
if not interaction.response.is_done():
|
|
await interaction.response.send_message(
|
|
f"❌ An error occurred: {str(error)}", ephemeral=True
|
|
)
|
|
else:
|
|
await interaction.followup.send(
|
|
f"❌ An error occurred: {str(error)}", ephemeral=True
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Failed to send error message to user: {e}")
|
|
|
|
|
|
async def main():
|
|
create_db_and_tables()
|
|
for c in COGS:
|
|
try:
|
|
await bot.load_extension(c)
|
|
logger.info(f"Loaded cog: {c}")
|
|
except Exception as e:
|
|
logger.error(f"Failed to load cog: {c}")
|
|
logger.error(f"{e}")
|
|
|
|
# Start health server and bot concurrently
|
|
async with bot:
|
|
# Create health server task
|
|
health_task = asyncio.create_task(run_health_server(bot))
|
|
|
|
try:
|
|
# Start bot (this blocks until bot stops)
|
|
await bot.start(os.environ.get("BOT_TOKEN", "NONE"))
|
|
finally:
|
|
# Cleanup: cancel health server when bot stops
|
|
health_task.cancel()
|
|
try:
|
|
await health_task
|
|
except asyncio.CancelledError:
|
|
pass
|
|
|
|
|
|
asyncio.run(main())
|