diff --git a/.gitignore b/.gitignore index 014452e..99db24c 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ data/ data.json data.json.bak settings*.conf +NOTES.md # Tools wrapper/ diff --git a/bot.py b/bot.py index 1887ef9..c28da6d 100644 --- a/bot.py +++ b/bot.py @@ -8,44 +8,29 @@ 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.a3" +VERSION = "0.3.9.2.a4" # ---------- Env & config loading ---------- load_dotenv() -TOKEN = os.getenv('DISCORD_TOKEN', '').strip() -CONFIG_PATH = os.getenv('SHAI_CONFIG', '/config/settings.conf') +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 -config = ConfigParser() -read_files = config.read(CONFIG_PATH) -if not read_files: - print(f"[Config] INFO: no config at {CONFIG_PATH} (or unreadable). Will rely on env + defaults.") -if 'DEFAULT' not in config: - config['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") -def _overlay_env_into_config(cfg: ConfigParser): - d = cfg['DEFAULT'] - for k, v in os.environ.items(): - if not k.startswith('SHAI_'): - continue - key = k[5:].lower() - if key == 'data': - key = 'data_file' - if v is None: - continue - vv = str(v).strip().strip('"').strip("'") - if key == 'data_file' and not vv: - continue - d[key] = vv - if not d.get('data_file', '').strip(): - d['data_file'] = '/data/data.json' - -print("[Config] SHAI_CONFIG:", os.getenv('SHAI_CONFIG', '(unset)')) -print("[Config] DEFAULT keys now:", list(config['DEFAULT'].keys())) - -# IMPORTANT: apply env overlay BEFORE we read values from config -_overlay_env_into_config(config) +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 ---------- @@ -58,16 +43,25 @@ intents.emojis_and_stickers = True intents.voice_states = True # ---------- Bot + DataManager ---------- - -ddefault = config['DEFAULT'] -data_file = (ddefault.get('data_file', '') or '').strip() or '/data/data.json' if not TOKEN: - print("[Config] WARNING: DISCORD_TOKEN not set (env). Bot will fail to log in.") -print(f"[Config] Using data_file: {data_file}") + print("[Config] WARNING: DISCORD_TOKEN is empty. The bot will fail to log in.") bot = commands.Bot(command_prefix='!', intents=intents) -bot.config = config -bot.data_manager = DataManager(data_file) + +# 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) +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 ---------- @@ -112,12 +106,11 @@ async def on_ready(): "/ message_content:", bot.intents.message_content, "/ voice_states:", bot.intents.voice_states) - # Per-guild permission sanity checks (console log) - await asyncio.gather(*[_guild_selfcheck(g, bot.config['DEFAULT']) for g in bot.guilds]) + await asyncio.gather(*[_guild_selfcheck(g, bot.config) for g in bot.guilds]) - # Slash command sync + # Slash sync — now reading from dict try: - dev_gid = bot.config['DEFAULT'].get('dev_guild_id') + dev_gid = bot.config.get('dev_guild_id') if dev_gid: guild = bot.get_guild(int(dev_gid)) if guild: @@ -132,7 +125,6 @@ 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: @@ -156,7 +148,7 @@ def _install_signal_handlers(loop, bot): try: loop.add_signal_handler(s, _graceful) except NotImplementedError: - pass # Windows + pass async def main(): async with bot: @@ -171,4 +163,4 @@ async def main(): await bot.start(TOKEN) if __name__ == '__main__': - asyncio.run(main()) + asyncio.run(main()) \ No newline at end of file