diff --git a/.gitignore b/.gitignore index 91dc4ff..014452e 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,8 @@ venv/ data/ data.json data.json.bak -settings*.conf \ No newline at end of file +settings*.conf + +# Tools +wrapper/ +wrapper/tools/ \ No newline at end of file diff --git a/bot.py b/bot.py index b0e8457..253b091 100644 --- a/bot.py +++ b/bot.py @@ -1,15 +1,14 @@ -import os -import asyncio import discord from discord.ext import commands from dotenv import load_dotenv from configparser import ConfigParser from data_manager import DataManager +from modules.common.boot_notice import post_boot_notice import pathlib -import os, asyncio, xml.etree.ElementTree as ET +import os, signal, asyncio, xml.etree.ElementTree as ET import aiohttp -VERSION="0.0.9" +VERSION="0.3.9.a2" # ---------- Env & config loading ---------- @@ -130,6 +129,33 @@ async def _fetch_latest_from_rss(url: str): # ---------- boot notice ---------- +async def _maybe_post_boot_notice(bot): + status = os.getenv("SHAI_BOOT_STATUS", "") + if not status: + return + desc = os.getenv("SHAI_BOOT_DESC", "") + old_v = os.getenv("SHAI_BOOT_OLD", "") + new_v = os.getenv("SHAI_BOOT_NEW", "") + + if status == "fetched_new": + line = f"Successfully fetched, cached, and booted new version: v{old_v or '0.0.0.0'} -> v{new_v}" + elif status == "cached_no_update": + line = f"Successfully booted from cached version: v{new_v}. No new update found" + else: + line = f"Successfully booted from cached version: v{new_v}. Program repository not accessible!" + + ch_id = int(bot.config['DEFAULT'].get('modlog_channel_id', "0") or 0) + ch = None + for g in bot.guilds: + ch = g.get_channel(ch_id) + if ch: break + if ch: + try: + msg = line if not desc else f"{line}\n_{desc}_" + await ch.send(msg, allowed_mentions=discord.AllowedMentions.none()) + except Exception: + pass + async def _post_boot_notice(): msg = f"Self-update and reboot successful! (v.{VERSION})" @@ -175,9 +201,12 @@ async def on_ready(): print(f"[Slash] Synced {len(synced)} commands globally") except Exception as e: print("[Slash] Sync failed:", repr(e)) - - # Boot notice in modlog - await _post_boot_notice() + + # Post BSM if present + try: + await post_boot_notice(bot) + except Exception as e: + print("[BootNotice] failed:", repr(e)) # ---------- Auto-discover extensions ---------- @@ -190,6 +219,16 @@ for folder in modules_path.iterdir(): continue extensions.append(f"modules.{folder.name}.{file.stem}") +def _install_signal_handlers(loop, bot): + def _graceful(*_): + # ask discord.py to close cleanly + loop.create_task(bot.close()) + for s in (signal.SIGTERM, signal.SIGINT): + try: + loop.add_signal_handler(s, _graceful) + except NotImplementedError: + pass # Windows + async def main(): async with bot: for ext in extensions: @@ -198,6 +237,8 @@ async def main(): print(f"[Modules] Loaded: {ext}") except Exception as e: print(f"[Modules] Failed to load {ext}:", repr(e)) + loop = asyncio.get_running_loop() + _install_signal_handlers(loop, bot) await bot.start(TOKEN) if __name__ == '__main__': diff --git a/modules/common/boot_notice.py b/modules/common/boot_notice.py new file mode 100644 index 0000000..f3830dd --- /dev/null +++ b/modules/common/boot_notice.py @@ -0,0 +1,45 @@ +# modules/common/boot_notice.py +import os +import time +import discord + +async def post_boot_notice(bot: discord.Client): + # Needs modlog_channel_id in config/env + cfg = bot.config['DEFAULT'] + ch_raw = cfg.get('modlog_channel_id') or os.getenv('SHAI_MODLOG_CHANNEL_ID') + if not ch_raw: + return + try: + ch_id = int(ch_raw) + except Exception: + return + ch = None + for g in bot.guilds: + ch = g.get_channel(ch_id) + if ch: + break + if not ch: + return + + status = os.getenv("SHAI_BOOT_STATUS", "").strip() + oldver = os.getenv("SHAI_BOOT_OLDVER", "").strip() + newver = os.getenv("SHAI_BOOT_NEWVER", "").strip() + commit = os.getenv("SHAI_BUILD_COMMIT", "").strip() + subject = os.getenv("SHAI_BUILD_SUBJECT", "").strip() + + if not status: + return + + parts = [f"**Boot**: {status}"] + if oldver or newver: + parts.append(f"**Version**: {oldver or '?'} → {newver or '?'}") + if commit: + parts.append(f"**Commit**: `{commit}`") + if subject and len(subject) > 5: + parts.append(f"**Note**: {subject}") + + msg = " | ".join(parts) + f" — " + try: + await ch.send(msg, allowed_mentions=discord.AllowedMentions.none()) + except Exception: + pass diff --git a/modules/nick_nudge/nick_nudge.py b/modules/nick_nudge/nick_nudge.py index 807771a..ca1e19f 100644 --- a/modules/nick_nudge/nick_nudge.py +++ b/modules/nick_nudge/nick_nudge.py @@ -87,6 +87,10 @@ class NickNudgeCog(commands.Cog): """ if not guild: return + # If a pending review already exists for this user in this guild, do nothing + for r in self.bot.data_manager.get('nick_reviews'): + if r.get('guild_id') == guild.id and r.get('user_id') == member.id and r.get('status') == 'pending': + return mod_ch = guild.get_channel(self.mod_channel_id) if not mod_ch: return