v0.3.9.a2
Added experimental bot wrapper functionality for completely automated updates and restarts
This commit is contained in:
		
							parent
							
								
									9fcd55ec4b
								
							
						
					
					
						commit
						dee0c4a5b4
					
				
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -10,3 +10,7 @@ data/
 | 
				
			|||||||
data.json
 | 
					data.json
 | 
				
			||||||
data.json.bak
 | 
					data.json.bak
 | 
				
			||||||
settings*.conf
 | 
					settings*.conf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Tools
 | 
				
			||||||
 | 
					wrapper/
 | 
				
			||||||
 | 
					wrapper/tools/
 | 
				
			||||||
							
								
								
									
										53
									
								
								bot.py
									
									
									
									
									
								
							
							
						
						
									
										53
									
								
								bot.py
									
									
									
									
									
								
							@ -1,15 +1,14 @@
 | 
				
			|||||||
import os
 | 
					 | 
				
			||||||
import asyncio
 | 
					 | 
				
			||||||
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 configparser import ConfigParser
 | 
				
			||||||
from data_manager import DataManager
 | 
					from data_manager import DataManager
 | 
				
			||||||
 | 
					from modules.common.boot_notice import post_boot_notice
 | 
				
			||||||
import pathlib
 | 
					import pathlib
 | 
				
			||||||
import os, asyncio, xml.etree.ElementTree as ET
 | 
					import os, signal, asyncio, xml.etree.ElementTree as ET
 | 
				
			||||||
import aiohttp
 | 
					import aiohttp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
VERSION="0.0.9"
 | 
					VERSION="0.3.9.a2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# ---------- Env & config loading ----------
 | 
					# ---------- Env & config loading ----------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -130,6 +129,33 @@ async def _fetch_latest_from_rss(url: str):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# ---------- boot notice ----------
 | 
					# ---------- 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():
 | 
					async def _post_boot_notice():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    msg = f"Self-update and reboot successful! (v.{VERSION})"
 | 
					    msg = f"Self-update and reboot successful! (v.{VERSION})"
 | 
				
			||||||
@ -176,8 +202,11 @@ async def on_ready():
 | 
				
			|||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        print("[Slash] Sync failed:", repr(e))
 | 
					        print("[Slash] Sync failed:", repr(e))
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
    # Boot notice in modlog
 | 
					    # Post BSM if present
 | 
				
			||||||
    await _post_boot_notice()
 | 
					    try:
 | 
				
			||||||
 | 
					        await post_boot_notice(bot)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        print("[BootNotice] failed:", repr(e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# ---------- Auto-discover extensions ----------
 | 
					# ---------- Auto-discover extensions ----------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -190,6 +219,16 @@ for folder in modules_path.iterdir():
 | 
				
			|||||||
                continue
 | 
					                continue
 | 
				
			||||||
            extensions.append(f"modules.{folder.name}.{file.stem}")
 | 
					            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 def main():
 | 
				
			||||||
    async with bot:
 | 
					    async with bot:
 | 
				
			||||||
        for ext in extensions:
 | 
					        for ext in extensions:
 | 
				
			||||||
@ -198,6 +237,8 @@ async def main():
 | 
				
			|||||||
                print(f"[Modules] Loaded: {ext}")
 | 
					                print(f"[Modules] Loaded: {ext}")
 | 
				
			||||||
            except Exception as e:
 | 
					            except Exception as e:
 | 
				
			||||||
                print(f"[Modules] Failed to load {ext}:", repr(e))
 | 
					                print(f"[Modules] Failed to load {ext}:", repr(e))
 | 
				
			||||||
 | 
					        loop = asyncio.get_running_loop()
 | 
				
			||||||
 | 
					        _install_signal_handlers(loop, bot)
 | 
				
			||||||
        await bot.start(TOKEN)
 | 
					        await bot.start(TOKEN)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == '__main__':
 | 
					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:
 | 
					        if not guild:
 | 
				
			||||||
            return
 | 
					            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)
 | 
					        mod_ch = guild.get_channel(self.mod_channel_id)
 | 
				
			||||||
        if not mod_ch:
 | 
					        if not mod_ch:
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user