0.3.9.2.a2
Restart-related patch
This commit is contained in:
parent
36939efac3
commit
e6ccc86629
105
bot.py
105
bot.py
@ -1,20 +1,14 @@
|
|||||||
|
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 configparser import ConfigParser
|
||||||
from data_manager import DataManager
|
from data_manager import DataManager
|
||||||
from modules.common.boot_notice import post_boot_notice
|
from modules.common.boot_notice import post_boot_notice
|
||||||
import pathlib
|
|
||||||
import os, signal, asyncio, xml.etree.ElementTree as ET
|
|
||||||
import aiohttp
|
|
||||||
|
|
||||||
# Version consists of the following:
|
# Version consists of:
|
||||||
# Major version
|
# Major.Enhancement.Minor.Patch.Test (Test is alphanumeric; doesn’t trigger auto update)
|
||||||
# Enhancement version
|
VERSION = "0.3.9.2.a2"
|
||||||
# Minor version
|
|
||||||
# Patch version
|
|
||||||
# Test/Dev version -> Does not trigger automatic update
|
|
||||||
VERSION="0.3.9.1.a2"
|
|
||||||
|
|
||||||
# ---------- Env & config loading ----------
|
# ---------- Env & config loading ----------
|
||||||
|
|
||||||
@ -27,8 +21,6 @@ config = ConfigParser()
|
|||||||
read_files = config.read(CONFIG_PATH)
|
read_files = config.read(CONFIG_PATH)
|
||||||
if not read_files:
|
if not read_files:
|
||||||
print(f"[Config] INFO: no config at {CONFIG_PATH} (or unreadable). Will rely on env + defaults.")
|
print(f"[Config] INFO: no config at {CONFIG_PATH} (or unreadable). Will rely on env + defaults.")
|
||||||
|
|
||||||
# Ensure DEFAULT section exists
|
|
||||||
if 'DEFAULT' not in config:
|
if 'DEFAULT' not in config:
|
||||||
config['DEFAULT'] = {}
|
config['DEFAULT'] = {}
|
||||||
|
|
||||||
@ -38,20 +30,17 @@ def _overlay_env_into_config(cfg: ConfigParser):
|
|||||||
Also accept SHAI_DATA_FILE or SHAI_DATA for data_file.
|
Also accept SHAI_DATA_FILE or SHAI_DATA for data_file.
|
||||||
"""
|
"""
|
||||||
d = cfg['DEFAULT']
|
d = cfg['DEFAULT']
|
||||||
|
|
||||||
# Map SHAI_* -> lower-case keys (e.g. SHAI_MOD_CHANNEL_ID -> 'mod_channel_id')
|
|
||||||
for k, v in os.environ.items():
|
for k, v in os.environ.items():
|
||||||
if not k.startswith('SHAI_'):
|
if not k.startswith('SHAI_'):
|
||||||
continue
|
continue
|
||||||
key = k[5:].lower() # drop 'SHAI_' prefix
|
key = k[5:].lower() # drop 'SHAI_'
|
||||||
if key == 'data':
|
if key == 'data':
|
||||||
key = 'data_file'
|
key = 'data_file'
|
||||||
d[key] = str(v)
|
d[key] = str(v)
|
||||||
|
|
||||||
if not d.get('data_file', '').strip():
|
if not d.get('data_file', '').strip():
|
||||||
d['data_file'] = '/data/data.json'
|
d['data_file'] = '/data/data.json'
|
||||||
|
|
||||||
# Apply overlay so env takes precedence everywhere
|
# IMPORTANT: apply env overlay BEFORE we read values from config
|
||||||
_overlay_env_into_config(config)
|
_overlay_env_into_config(config)
|
||||||
|
|
||||||
# ---------- Discord intents ----------
|
# ---------- Discord intents ----------
|
||||||
@ -108,87 +97,16 @@ async def _guild_selfcheck(g: discord.Guild, cfg):
|
|||||||
for p in problems:
|
for p in problems:
|
||||||
print(" -", p)
|
print(" -", p)
|
||||||
|
|
||||||
async def _fetch_latest_from_rss(url: str):
|
|
||||||
try:
|
|
||||||
timeout = aiohttp.ClientTimeout(total=8)
|
|
||||||
async with aiohttp.ClientSession(timeout=timeout) as sess:
|
|
||||||
async with sess.get(url) as resp:
|
|
||||||
if resp.status != 200:
|
|
||||||
return None, None
|
|
||||||
text = await resp.text()
|
|
||||||
# Gitea RSS structure: <rss><channel><item>…</item></channel></rss>
|
|
||||||
root = ET.fromstring(text)
|
|
||||||
item = root.find('./channel/item')
|
|
||||||
if item is None:
|
|
||||||
return None, None
|
|
||||||
title = (item.findtext('title') or '').strip()
|
|
||||||
link = (item.findtext('link') or '').strip()
|
|
||||||
# Try to extract short sha from link tail if it's a commit URL
|
|
||||||
sha = None
|
|
||||||
if '/commit/' in link:
|
|
||||||
sha = link.rsplit('/commit/', 1)[-1][:7]
|
|
||||||
# Many Gitea feeds put the commit subject in <title>
|
|
||||||
subject = title if title else None
|
|
||||||
return subject, sha
|
|
||||||
except Exception:
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
# ---------- 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})"
|
|
||||||
|
|
||||||
ch_id_raw = bot.config['DEFAULT'].get('modlog_channel_id', '')
|
|
||||||
try:
|
|
||||||
ch_id = int(ch_id_raw) if ch_id_raw else 0
|
|
||||||
except Exception:
|
|
||||||
ch_id = 0
|
|
||||||
if not ch_id:
|
|
||||||
return
|
|
||||||
for g in bot.guilds:
|
|
||||||
ch = g.get_channel(ch_id)
|
|
||||||
if ch:
|
|
||||||
try:
|
|
||||||
await ch.send(msg)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
break
|
|
||||||
|
|
||||||
# ---------- events ----------
|
# ---------- events ----------
|
||||||
|
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_ready():
|
async def on_ready():
|
||||||
print(f"Logged in as {bot.user} (ID: {bot.user.id})")
|
print(f"Logged in as {bot.user} (ID: {bot.user.id})")
|
||||||
print("[Intents] members:", bot.intents.members, "/ message_content:", bot.intents.message_content, "/ voice_states:", bot.intents.voice_states)
|
print("[Intents] members:", bot.intents.members,
|
||||||
|
"/ message_content:", bot.intents.message_content,
|
||||||
|
"/ voice_states:", bot.intents.voice_states)
|
||||||
|
|
||||||
|
# Per-guild permission sanity checks (console log)
|
||||||
await asyncio.gather(*[_guild_selfcheck(g, bot.config['DEFAULT']) for g in bot.guilds])
|
await asyncio.gather(*[_guild_selfcheck(g, bot.config['DEFAULT']) for g in bot.guilds])
|
||||||
|
|
||||||
# Slash command sync
|
# Slash command sync
|
||||||
@ -208,7 +126,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 BSM if present
|
# 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:
|
||||||
@ -227,7 +145,6 @@ for folder in modules_path.iterdir():
|
|||||||
|
|
||||||
def _install_signal_handlers(loop, bot):
|
def _install_signal_handlers(loop, bot):
|
||||||
def _graceful(*_):
|
def _graceful(*_):
|
||||||
# ask discord.py to close cleanly
|
|
||||||
loop.create_task(bot.close())
|
loop.create_task(bot.close())
|
||||||
for s in (signal.SIGTERM, signal.SIGINT):
|
for s in (signal.SIGTERM, signal.SIGINT):
|
||||||
try:
|
try:
|
||||||
|
@ -1,18 +1,65 @@
|
|||||||
# modules/common/boot_notice.py
|
|
||||||
import os
|
import os
|
||||||
import time
|
|
||||||
import discord
|
import discord
|
||||||
|
import aiohttp
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
async def post_boot_notice(bot: discord.Client):
|
async def _fetch_latest_subject_sha(rss_url: str) -> tuple[str | None, str | None]:
|
||||||
# Needs modlog_channel_id in config/env
|
"""Best-effort: read latest commit subject + short sha from a Gitea RSS feed."""
|
||||||
cfg = bot.config['DEFAULT']
|
|
||||||
ch_raw = cfg.get('modlog_channel_id') or os.getenv('SHAI_MODLOG_CHANNEL_ID')
|
|
||||||
if not ch_raw:
|
|
||||||
return
|
|
||||||
try:
|
try:
|
||||||
ch_id = int(ch_raw)
|
timeout = aiohttp.ClientTimeout(total=8)
|
||||||
|
async with aiohttp.ClientSession(timeout=timeout) as sess:
|
||||||
|
async with sess.get(rss_url) as resp:
|
||||||
|
if resp.status != 200:
|
||||||
|
return None, None
|
||||||
|
text = await resp.text()
|
||||||
|
root = ET.fromstring(text)
|
||||||
|
item = root.find('./channel/item')
|
||||||
|
if item is None:
|
||||||
|
return None, None
|
||||||
|
title = (item.findtext('title') or '').strip()
|
||||||
|
link = (item.findtext('link') or '').strip()
|
||||||
|
sha = link.rsplit('/commit/', 1)[-1][:7] if '/commit/' in link else None
|
||||||
|
return (title or None), sha
|
||||||
except Exception:
|
except Exception:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
async def post_boot_notice(bot):
|
||||||
|
"""
|
||||||
|
Posts a boot status message to the configured modlog channel.
|
||||||
|
Primary source: SHAI_BOOT_* env vars set by the wrapper.
|
||||||
|
Fallback: if absent, and SHAI_REPO_RSS is set, show the latest commit subject.
|
||||||
|
"""
|
||||||
|
status = os.getenv("SHAI_BOOT_STATUS", "").strip() # 'fetched_new' | 'cached_no_update' | 'cache_only_error' | ''
|
||||||
|
desc = os.getenv("SHAI_BOOT_DESC", "").strip()
|
||||||
|
old_v = os.getenv("SHAI_BOOT_OLD", "").strip()
|
||||||
|
new_v = os.getenv("SHAI_BOOT_NEW", "").strip()
|
||||||
|
|
||||||
|
line = None
|
||||||
|
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"
|
||||||
|
elif status == "cache_only_error":
|
||||||
|
line = f"Successfully booted from cached version: v{new_v}. Program repository not accessible!"
|
||||||
|
|
||||||
|
# If wrapper didn’t set a status, optionally show latest commit subject from RSS (if provided)
|
||||||
|
if not line:
|
||||||
|
rss = os.getenv("SHAI_REPO_RSS", "").strip()
|
||||||
|
if rss:
|
||||||
|
subject, sha = await _fetch_latest_subject_sha(rss)
|
||||||
|
if subject and len(subject) > 5:
|
||||||
|
line = f"Booted (no BSM env). Latest commit: {subject}" + (f" ({sha})" if sha else "")
|
||||||
|
|
||||||
|
if not line:
|
||||||
|
return # nothing to say
|
||||||
|
|
||||||
|
try:
|
||||||
|
ch_id = int(bot.config['DEFAULT'].get('modlog_channel_id', "0") or 0)
|
||||||
|
except Exception:
|
||||||
|
ch_id = 0
|
||||||
|
if not ch_id:
|
||||||
return
|
return
|
||||||
|
|
||||||
ch = None
|
ch = None
|
||||||
for g in bot.guilds:
|
for g in bot.guilds:
|
||||||
ch = g.get_channel(ch_id)
|
ch = g.get_channel(ch_id)
|
||||||
@ -21,25 +68,8 @@ async def post_boot_notice(bot: discord.Client):
|
|||||||
if not ch:
|
if not ch:
|
||||||
return
|
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:
|
try:
|
||||||
|
msg = line if not desc else f"{line}\n_{desc}_"
|
||||||
await ch.send(msg, allowed_mentions=discord.AllowedMentions.none())
|
await ch.send(msg, allowed_mentions=discord.AllowedMentions.none())
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
Loading…
Reference in New Issue
Block a user