v0.3.9.a2
Added experimental bot wrapper functionality for completely automated updates and restarts
This commit is contained in:
		
							parent
							
								
									9fcd55ec4b
								
							
						
					
					
						commit
						dee0c4a5b4
					
				
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -9,4 +9,8 @@ venv/
 | 
			
		||||
data/
 | 
			
		||||
data.json
 | 
			
		||||
data.json.bak
 | 
			
		||||
settings*.conf
 | 
			
		||||
settings*.conf
 | 
			
		||||
 | 
			
		||||
# Tools
 | 
			
		||||
wrapper/
 | 
			
		||||
wrapper/tools/
 | 
			
		||||
							
								
								
									
										55
									
								
								bot.py
									
									
									
									
									
								
							
							
						
						
									
										55
									
								
								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__':
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										45
									
								
								modules/common/boot_notice.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								modules/common/boot_notice.py
									
									
									
									
									
										Normal 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
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user