#!/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()) except Exception as e: log.error(f"failed to initialise TSS databases: {e}") sys.exit(1) bot.run(TOKEN, log_handler=None)