v0.3.9.a2

Added experimental bot wrapper functionality for completely automated updates and restarts
This commit is contained in:
Franz Rolfsvaag 2025-08-10 17:17:59 +02:00
parent 9fcd55ec4b
commit dee0c4a5b4
4 changed files with 102 additions and 8 deletions

6
.gitignore vendored
View File

@ -9,4 +9,8 @@ venv/
data/
data.json
data.json.bak
settings*.conf
settings*.conf
# Tools
wrapper/
wrapper/tools/

55
bot.py
View File

@ -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__':

View File

@ -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" — <t:{int(time.time())}:R>"
try:
await ch.send(msg, allowed_mentions=discord.AllowedMentions.none())
except Exception:
pass

View File

@ -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