.
This commit is contained in:
		
							parent
							
								
									27cc972f19
								
							
						
					
					
						commit
						377586e6e7
					
				
							
								
								
									
										82
									
								
								bot.py
									
									
									
									
									
								
							
							
						
						
									
										82
									
								
								bot.py
									
									
									
									
									
								
							@ -2,35 +2,28 @@ import os, signal, asyncio, pathlib
 | 
				
			|||||||
import discord
 | 
					import discord
 | 
				
			||||||
from discord.ext import commands
 | 
					from discord.ext import commands
 | 
				
			||||||
from dotenv import load_dotenv
 | 
					from dotenv import load_dotenv
 | 
				
			||||||
from configparser import ConfigParser
 | 
					
 | 
				
			||||||
from data_manager import DataManager
 | 
					from data_manager import DataManager
 | 
				
			||||||
 | 
					from modules.common.settings import cfg as cfg_helper
 | 
				
			||||||
from modules.common.boot_notice import post_boot_notice
 | 
					from modules.common.boot_notice import post_boot_notice
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Version consists of:
 | 
					# Version consists of:
 | 
				
			||||||
# Major.Enhancement.Minor.Patch.Test  (Test is alphanumeric; doesn’t trigger auto update)
 | 
					# 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()
 | 
					load_dotenv()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _get_env(name: str, default: str = "") -> str:
 | 
					def _get_env(name: str, default: str = "") -> str:
 | 
				
			||||||
    v = os.getenv(name, "")
 | 
					    v = os.getenv(name, "")
 | 
				
			||||||
    # normalize stray quotes/whitespace Portainer sometimes injects
 | 
					    return (v or "").strip().strip('"').strip("'") or default
 | 
				
			||||||
    v = (v or "").strip().strip('"').strip("'")
 | 
					 | 
				
			||||||
    return v if v else default
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
TOKEN = _get_env("DISCORD_TOKEN")
 | 
					TOKEN = _get_env("DISCORD_TOKEN")
 | 
				
			||||||
DATA_FILE = _get_env("SHAI_DATA") or _get_env("SHAI_DATA_FILE") or "/data/data.json"
 | 
					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] DISCORD_TOKEN set:", bool(TOKEN))
 | 
				
			||||||
print("[Config] DATA_FILE:", DATA_FILE)
 | 
					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 ----------
 | 
					# ---------- Discord intents ----------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -43,33 +36,27 @@ intents.emojis_and_stickers = True
 | 
				
			|||||||
intents.voice_states = True
 | 
					intents.voice_states = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# ---------- Bot + DataManager ----------
 | 
					# ---------- Bot + DataManager ----------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if not TOKEN:
 | 
					if not TOKEN:
 | 
				
			||||||
    print("[Config] WARNING: DISCORD_TOKEN is empty. The bot will fail to log in.")
 | 
					    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:
 | 
					# Ensure data path exists and is seeded
 | 
				
			||||||
bot.config = {
 | 
					os.makedirs(os.path.dirname(DATA_FILE) or ".", exist_ok=True)
 | 
				
			||||||
    "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):
 | 
					if not os.path.exists(DATA_FILE):
 | 
				
			||||||
    with open(DATA_FILE, "w", encoding="utf-8") as f:
 | 
					    with open(DATA_FILE, "w", encoding="utf-8") as f:
 | 
				
			||||||
        f.write("{}")
 | 
					        f.write("{}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bot.data_manager = DataManager(DATA_FILE)
 | 
					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 = []
 | 
					    problems = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _need_channel(id_key, *perms):
 | 
					    def _need_channel(id_key, *perms):
 | 
				
			||||||
        raw = cfg.get(id_key)
 | 
					        raw = c.get(id_key)
 | 
				
			||||||
        if not raw:
 | 
					        if not raw:
 | 
				
			||||||
            problems.append(f"Missing config key: {id_key}")
 | 
					            problems.append(f"Missing config key: {id_key}")
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
@ -88,9 +75,9 @@ async def _guild_selfcheck(g: discord.Guild, cfg):
 | 
				
			|||||||
            if not getattr(p, perm, False):
 | 
					            if not getattr(p, perm, False):
 | 
				
			||||||
                problems.append(f"Missing permission on #{ch.name}: {perm}")
 | 
					                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("mod_channel_id", "read_messages", "send_messages", "add_reactions", "read_message_history")
 | 
				
			||||||
    _need_channel('modlog_channel_id', 'read_messages', 'send_messages')
 | 
					    _need_channel("modlog_channel_id", "read_messages", "send_messages")
 | 
				
			||||||
    _need_channel('pirates_list_channel_id', 'read_messages', 'send_messages')
 | 
					    _need_channel("pirates_list_channel_id", "read_messages", "send_messages")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if problems:
 | 
					    if problems:
 | 
				
			||||||
        print(f"[SelfCheck:{g.name}]")
 | 
					        print(f"[SelfCheck:{g.name}]")
 | 
				
			||||||
@ -106,11 +93,14 @@ async def on_ready():
 | 
				
			|||||||
          "/ message_content:", bot.intents.message_content,
 | 
					          "/ message_content:", bot.intents.message_content,
 | 
				
			||||||
          "/ voice_states:", bot.intents.voice_states)
 | 
					          "/ 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:
 | 
					    try:
 | 
				
			||||||
        dev_gid = bot.config.get('dev_guild_id')
 | 
					        dev_gid = env_cfg.get("dev_guild_id")
 | 
				
			||||||
        if dev_gid:
 | 
					        if dev_gid:
 | 
				
			||||||
            guild = bot.get_guild(int(dev_gid))
 | 
					            guild = bot.get_guild(int(dev_gid))
 | 
				
			||||||
            if guild:
 | 
					            if guild:
 | 
				
			||||||
@ -125,6 +115,7 @@ async def on_ready():
 | 
				
			|||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        print("[Slash] Sync failed:", repr(e))
 | 
					        print("[Slash] Sync failed:", repr(e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Post boot status message (wrapper/env-driven or RSS fallback)
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        await post_boot_notice(bot)
 | 
					        await post_boot_notice(bot)
 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
@ -132,29 +123,30 @@ async def on_ready():
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# ---------- Auto-discover extensions ----------
 | 
					# ---------- Auto-discover extensions ----------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
modules_path = pathlib.Path(__file__).parent / 'modules'
 | 
					modules_path = pathlib.Path(__file__).parent / "modules"
 | 
				
			||||||
extensions = []
 | 
					extensions = []
 | 
				
			||||||
for folder in modules_path.iterdir():
 | 
					for folder in modules_path.iterdir():
 | 
				
			||||||
    if folder.is_dir():
 | 
					    if not folder.is_dir():
 | 
				
			||||||
        # skip non-cog helpers under modules/common
 | 
					        continue
 | 
				
			||||||
        if folder.name == 'common':
 | 
					    # skip non-cog helpers under modules/common
 | 
				
			||||||
 | 
					    if folder.name == "common":
 | 
				
			||||||
 | 
					        continue
 | 
				
			||||||
 | 
					    for file in folder.glob("*.py"):
 | 
				
			||||||
 | 
					        if file.name == "__init__.py":
 | 
				
			||||||
            continue
 | 
					            continue
 | 
				
			||||||
        for file in folder.glob('*.py'):
 | 
					        extensions.append(f"modules.{folder.name}.{file.stem}")
 | 
				
			||||||
            if file.name == '__init__.py':
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
            extensions.append(f"modules.{folder.name}.{file.stem}")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _install_signal_handlers(loop, bot):
 | 
					def _install_signal_handlers(loop, bot_obj):
 | 
				
			||||||
    def _graceful(*_):
 | 
					    def _graceful(*_):
 | 
				
			||||||
        loop.create_task(bot.close())
 | 
					        loop.create_task(bot_obj.close())
 | 
				
			||||||
    for s in (signal.SIGTERM, signal.SIGINT):
 | 
					    for s in (signal.SIGTERM, signal.SIGINT):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            loop.add_signal_handler(s, _graceful)
 | 
					            loop.add_signal_handler(s, _graceful)
 | 
				
			||||||
        except NotImplementedError:
 | 
					        except NotImplementedError:
 | 
				
			||||||
            pass
 | 
					            pass  # Windows
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def main():
 | 
					async def main():
 | 
				
			||||||
    print(f"[STARTUP] ShaiWatcher booting to v.{VERSION}")
 | 
					    print(f"[STARTUP] ShaiWatcher booting v{VERSION}")
 | 
				
			||||||
    async with bot:
 | 
					    async with bot:
 | 
				
			||||||
        for ext in extensions:
 | 
					        for ext in extensions:
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
@ -166,5 +158,5 @@ async def main():
 | 
				
			|||||||
        _install_signal_handlers(loop, bot)
 | 
					        _install_signal_handlers(loop, bot)
 | 
				
			||||||
        await bot.start(TOKEN)
 | 
					        await bot.start(TOKEN)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == '__main__':
 | 
					if __name__ == "__main__":
 | 
				
			||||||
    asyncio.run(main())
 | 
					    asyncio.run(main())
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user