Files
TSSBOT/start_bot.py
T
2026-06-20 21:12:53 -07:00

141 lines
4.3 KiB
Python

#!/usr/bin/env python3
"""Entry point for TSSBOT. Connects to Discord and idles until commands land."""
import asyncio
import logging
import os
import signal
import sys
from pathlib import Path
# Make TSSBOT root and BOTS/SHARED importable.
_HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(_HERE))
sys.path.insert(0, str(_HERE.parent / "SHARED"))
import discord
from discord.ext import commands
from dotenv import load_dotenv
load_dotenv(dotenv_path=_HERE / ".env")
# Imported after load_dotenv so env vars are available.
from BOT.storage import init_tss_dbs, STORAGE_DIR # noqa: E402
from BOT.autologging import set_bot as set_autolog_bot # noqa: E402
from BOT.commands import setup_commands # noqa: E402
from tss_ws import listen, _handle_game # noqa: E402
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s] [%(levelname)s] [tssbot] %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
log = logging.getLogger("tssbot")
def _handle_signal(signum, _frame):
log.info(f"received signal {signum}, exiting")
sys.exit(0)
TOKEN = os.environ.get("DISCORD_KEY", "").strip()
intents = discord.Intents.default()
intents.message_content = False
intents.reactions = True
intents.messages = True
class TSSBot(commands.Bot):
async def setup_hook(self) -> None:
# Register the client so the autolog matcher can post, wire up the
# slash commands, and push them to Discord.
set_autolog_bot(self)
await setup_commands(self)
try:
synced = await self.tree.sync()
log.info("synced %d slash command(s)", len(synced))
except Exception as exc:
log.error("slash command sync failed: %s", exc)
bot = TSSBot(command_prefix="!", intents=intents)
async def _ws_task_loop() -> None:
"""Run the TSS WS listener forever, restarting on any error."""
while True:
try:
await listen(_handle_game)
except Exception as exc:
log.error(f"TSS WS listener crashed: {exc} — restarting in 10s")
await asyncio.sleep(10)
def _write_guild_report() -> None:
"""Write TSSBOT's own guild list to the shared storage volume.
Uses a TSS-specific filename so it does not clobber SREBOT's
SREBOT_GUILDS.txt, which lives on the same STORAGE_VOL_PATH volume.
"""
out_path = STORAGE_DIR / "TSSBOT_GUILDS.txt"
with out_path.open("w", encoding="utf-8") as f:
f.write(f"We have logged in as {bot.user} in the following Guilds:\n\n")
for guild in bot.guilds:
created = guild.created_at.strftime("%Y-%m-%d")
f.write(
f" - {guild.name} (id: {guild.id}) | Members: {guild.member_count} | Created: {created}\n"
)
log.info(f"Guild report written to {out_path.resolve()}")
async def _refresh_presence() -> None:
"""Update the bot's Discord presence to show the current guild count."""
guild_total = len(bot.guilds)
await bot.change_presence(
activity=discord.Activity(
type=discord.ActivityType.playing,
name=f"Playing TSS in {guild_total} servers!",
)
)
@bot.event
async def on_guild_join(guild):
log.info(f"joined guild: {guild.name} (id: {guild.id})")
log.info(f"updated total guilds: {len(bot.guilds)}")
_write_guild_report()
await _refresh_presence()
@bot.event
async def on_guild_remove(guild):
log.info(f"removed from guild: {guild.name} (id: {guild.id})")
log.info(f"updated total guilds: {len(bot.guilds)}")
_write_guild_report()
await _refresh_presence()
@bot.event
async def on_ready():
log.info(f"logged in as {bot.user} (id={bot.user.id if bot.user else '?'})")
log.info(f"connected to {len(bot.guilds)} guild(s)")
_write_guild_report()
await _refresh_presence()
asyncio.create_task(_ws_task_loop())
if __name__ == "__main__":
signal.signal(signal.SIGTERM, _handle_signal)
signal.signal(signal.SIGINT, _handle_signal)
if not TOKEN:
log.error("DISCORD_KEY not set in TSSBOT/.env — aborting")
sys.exit(1)
try:
asyncio.run(init_tss_dbs())
from BOT.tss_tournaments import init_tss_tournaments_db
asyncio.run(init_tss_tournaments_db())
except Exception as e:
log.error(f"failed to initialise TSS databases: {e}")
sys.exit(1)
bot.run(TOKEN, log_handler=None)