diff --git a/bot.py b/bot.py index f7cd0a7..33ccc9f 100644 --- a/bot.py +++ b/bot.py @@ -2,35 +2,28 @@ import os, signal, asyncio, pathlib import discord from discord.ext import commands from dotenv import load_dotenv -from configparser import ConfigParser + from data_manager import DataManager +from modules.common.settings import cfg as cfg_helper from modules.common.boot_notice import post_boot_notice # Version consists of: # Major.Enhancement.Minor.Patch.Test (Test is alphanumeric; doesn’t trigger auto update) -VERSION = "0.3.9.2.a5" +VERSION = "0.3.9.2.a6" -# ---------- Env & config loading ---------- +# ---------- Env loading ---------- load_dotenv() def _get_env(name: str, default: str = "") -> str: v = os.getenv(name, "") - # normalize stray quotes/whitespace Portainer sometimes injects - v = (v or "").strip().strip('"').strip("'") - return v if v else default + return (v or "").strip().strip('"').strip("'") or default TOKEN = _get_env("DISCORD_TOKEN") DATA_FILE = _get_env("SHAI_DATA") or _get_env("SHAI_DATA_FILE") or "/data/data.json" -CHECK_TIME_UTC = _get_env("CHECK_TIME_UTC", "03:00") -IGNORE_TEST_LEVEL = int(_get_env("IGNORE_TEST_LEVEL", "1")) -SHAI_HOME_GUILD_ID = _get_env("SHAI_HOME_GUILD_ID") print("[Config] DISCORD_TOKEN set:", bool(TOKEN)) print("[Config] DATA_FILE:", DATA_FILE) -print("[Config] CHECK_TIME_UTC:", CHECK_TIME_UTC) -print("[Config] IGNORE_TEST_LEVEL:", IGNORE_TEST_LEVEL) -print("[Config] SHAI_HOME_GUILD_ID:", SHAI_HOME_GUILD_ID or "(unset)") # ---------- Discord intents ---------- @@ -43,33 +36,27 @@ intents.emojis_and_stickers = True intents.voice_states = True # ---------- Bot + DataManager ---------- + if not TOKEN: print("[Config] WARNING: DISCORD_TOKEN is empty. The bot will fail to log in.") -bot = commands.Bot(command_prefix='!', intents=intents) +bot = commands.Bot(command_prefix="!", intents=intents) -# If you still want to pass “config” around, put a tiny dict on bot: -bot.config = { - "check_time_utc": CHECK_TIME_UTC, - "ignore_test_level": IGNORE_TEST_LEVEL, - "home_guild_id": SHAI_HOME_GUILD_ID, -} - -# Ensure the data path exists and file is seeded if missing -os.makedirs(os.path.dirname(DATA_FILE), exist_ok=True) +# Ensure data path exists and is seeded +os.makedirs(os.path.dirname(DATA_FILE) or ".", exist_ok=True) if not os.path.exists(DATA_FILE): with open(DATA_FILE, "w", encoding="utf-8") as f: f.write("{}") bot.data_manager = DataManager(DATA_FILE) -# ---------- Self-check helpers ---------- +# ---------- Self-check (env-aware via cfg_helper) ---------- -async def _guild_selfcheck(g: discord.Guild, cfg): +async def _guild_selfcheck(g: discord.Guild, c): problems = [] def _need_channel(id_key, *perms): - raw = cfg.get(id_key) + raw = c.get(id_key) if not raw: problems.append(f"Missing config key: {id_key}") return @@ -88,9 +75,9 @@ async def _guild_selfcheck(g: discord.Guild, cfg): if not getattr(p, perm, False): problems.append(f"Missing permission on #{ch.name}: {perm}") - _need_channel('mod_channel_id', 'read_messages', 'send_messages', 'add_reactions', 'read_message_history') - _need_channel('modlog_channel_id', 'read_messages', 'send_messages') - _need_channel('pirates_list_channel_id', 'read_messages', 'send_messages') + _need_channel("mod_channel_id", "read_messages", "send_messages", "add_reactions", "read_message_history") + _need_channel("modlog_channel_id", "read_messages", "send_messages") + _need_channel("pirates_list_channel_id", "read_messages", "send_messages") if problems: print(f"[SelfCheck:{g.name}]") @@ -106,11 +93,14 @@ async def on_ready(): "/ message_content:", bot.intents.message_content, "/ voice_states:", bot.intents.voice_states) - await asyncio.gather(*[_guild_selfcheck(g, bot.config) for g in bot.guilds]) + env_cfg = cfg_helper(bot) - # Slash sync — now reading from dict + # Per-guild permission sanity checks (env-aware) + await asyncio.gather(*[_guild_selfcheck(g, env_cfg) for g in bot.guilds]) + + # Slash command sync (env-aware dev guild) try: - dev_gid = bot.config.get('dev_guild_id') + dev_gid = env_cfg.get("dev_guild_id") if dev_gid: guild = bot.get_guild(int(dev_gid)) if guild: @@ -125,6 +115,7 @@ async def on_ready(): except Exception as e: print("[Slash] Sync failed:", repr(e)) + # Post boot status message (wrapper/env-driven or RSS fallback) try: await post_boot_notice(bot) except Exception as e: @@ -132,29 +123,30 @@ async def on_ready(): # ---------- Auto-discover extensions ---------- -modules_path = pathlib.Path(__file__).parent / 'modules' +modules_path = pathlib.Path(__file__).parent / "modules" extensions = [] for folder in modules_path.iterdir(): - if folder.is_dir(): - # skip non-cog helpers under modules/common - if folder.name == 'common': + if not folder.is_dir(): + continue + # skip non-cog helpers under modules/common + if folder.name == "common": + continue + for file in folder.glob("*.py"): + if file.name == "__init__.py": continue - for file in folder.glob('*.py'): - if file.name == '__init__.py': - continue - extensions.append(f"modules.{folder.name}.{file.stem}") + extensions.append(f"modules.{folder.name}.{file.stem}") -def _install_signal_handlers(loop, bot): +def _install_signal_handlers(loop, bot_obj): def _graceful(*_): - loop.create_task(bot.close()) + loop.create_task(bot_obj.close()) for s in (signal.SIGTERM, signal.SIGINT): try: loop.add_signal_handler(s, _graceful) except NotImplementedError: - pass + pass # Windows async def main(): - print(f"[STARTUP] ShaiWatcher booting to v.{VERSION}") + print(f"[STARTUP] ShaiWatcher booting v{VERSION}") async with bot: for ext in extensions: try: @@ -166,5 +158,5 @@ async def main(): _install_signal_handlers(loop, bot) await bot.start(TOKEN) -if __name__ == '__main__': - asyncio.run(main()) \ No newline at end of file +if __name__ == "__main__": + asyncio.run(main())