Delete bot.py
This commit is contained in:
		
							parent
							
								
									ee03ecb15b
								
							
						
					
					
						commit
						a0a8c7de1a
					
				
							
								
								
									
										204
									
								
								bot.py
									
									
									
									
									
								
							
							
						
						
									
										204
									
								
								bot.py
									
									
									
									
									
								
							@ -1,204 +0,0 @@
 | 
			
		||||
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
 | 
			
		||||
import pathlib
 | 
			
		||||
import os, asyncio, xml.etree.ElementTree as ET
 | 
			
		||||
import aiohttp
 | 
			
		||||
 | 
			
		||||
VERSION="0.0.9"
 | 
			
		||||
 | 
			
		||||
# ---------- Env & config loading ----------
 | 
			
		||||
 | 
			
		||||
load_dotenv()
 | 
			
		||||
 | 
			
		||||
TOKEN = os.getenv('DISCORD_TOKEN', '').strip()
 | 
			
		||||
CONFIG_PATH = os.getenv('SHAI_CONFIG', '/config/settings.conf')
 | 
			
		||||
 | 
			
		||||
config = ConfigParser()
 | 
			
		||||
read_files = config.read(CONFIG_PATH)
 | 
			
		||||
if not read_files:
 | 
			
		||||
    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:
 | 
			
		||||
    config['DEFAULT'] = {}
 | 
			
		||||
 | 
			
		||||
def _overlay_env_into_config(cfg: ConfigParser):
 | 
			
		||||
    """
 | 
			
		||||
    Overlay all SHAI_* environment variables into cfg['DEFAULT'] so env wins.
 | 
			
		||||
    Also accept SHAI_DATA_FILE or SHAI_DATA for data_file.
 | 
			
		||||
    """
 | 
			
		||||
    d = cfg['DEFAULT']
 | 
			
		||||
 | 
			
		||||
    # Map SHAI_* -> lower-case keys (e.g. SHAI_MOD_CHANNEL_ID -> 'mod_channel_id')
 | 
			
		||||
    for k, v in os.environ.items():
 | 
			
		||||
        if not k.startswith('SHAI_'):
 | 
			
		||||
            continue
 | 
			
		||||
        key = k[5:].lower()  # drop 'SHAI_' prefix
 | 
			
		||||
        if key == 'data':
 | 
			
		||||
            key = 'data_file'
 | 
			
		||||
        d[key] = str(v)
 | 
			
		||||
 | 
			
		||||
    if not d.get('data_file', '').strip():
 | 
			
		||||
        d['data_file'] = '/data/data.json'
 | 
			
		||||
 | 
			
		||||
# Apply overlay so env takes precedence everywhere
 | 
			
		||||
_overlay_env_into_config(config)
 | 
			
		||||
 | 
			
		||||
# ---------- Discord intents ----------
 | 
			
		||||
 | 
			
		||||
intents = discord.Intents.default()
 | 
			
		||||
intents.guilds = True
 | 
			
		||||
intents.members = True
 | 
			
		||||
intents.message_content = True
 | 
			
		||||
intents.reactions = True
 | 
			
		||||
intents.emojis_and_stickers = True
 | 
			
		||||
intents.voice_states = True
 | 
			
		||||
 | 
			
		||||
# ---------- Bot + DataManager ----------
 | 
			
		||||
 | 
			
		||||
data_file = config['DEFAULT']['data_file']  # guaranteed present by overlay
 | 
			
		||||
if not TOKEN:
 | 
			
		||||
    print("[Config] WARNING: DISCORD_TOKEN not set (env). Bot will fail to log in.")
 | 
			
		||||
 | 
			
		||||
bot = commands.Bot(command_prefix='!', intents=intents)
 | 
			
		||||
bot.config = config
 | 
			
		||||
bot.data_manager = DataManager(data_file)
 | 
			
		||||
 | 
			
		||||
# ---------- Self-check helpers ----------
 | 
			
		||||
 | 
			
		||||
async def _guild_selfcheck(g: discord.Guild, cfg):
 | 
			
		||||
    problems = []
 | 
			
		||||
 | 
			
		||||
    def _need_channel(id_key, *perms):
 | 
			
		||||
        raw = cfg.get(id_key)
 | 
			
		||||
        if not raw:
 | 
			
		||||
            problems.append(f"Missing config key: {id_key}")
 | 
			
		||||
            return
 | 
			
		||||
        try:
 | 
			
		||||
            cid = int(raw)
 | 
			
		||||
        except Exception:
 | 
			
		||||
            problems.append(f"Bad channel id for {id_key}: {raw}")
 | 
			
		||||
            return
 | 
			
		||||
        ch = g.get_channel(cid)
 | 
			
		||||
        if not ch:
 | 
			
		||||
            problems.append(f"Channel not found: {id_key}={cid}")
 | 
			
		||||
            return
 | 
			
		||||
        me = g.me
 | 
			
		||||
        p = ch.permissions_for(me)
 | 
			
		||||
        for perm in perms:
 | 
			
		||||
            if not getattr(p, perm, False):
 | 
			
		||||
                problems.append(f"Missing permission on #{ch.name}: {perm}")
 | 
			
		||||
 | 
			
		||||
    _need_channel('mod_channel_id', 'read_messages', 'send_messages', 'add_reactions', 'read_message_history')
 | 
			
		||||
    _need_channel('modlog_channel_id', 'read_messages', 'send_messages')
 | 
			
		||||
    _need_channel('pirates_list_channel_id', 'read_messages', 'send_messages')
 | 
			
		||||
 | 
			
		||||
    if problems:
 | 
			
		||||
        print(f"[SelfCheck:{g.name}]")
 | 
			
		||||
        for p in problems:
 | 
			
		||||
            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 _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 ----------
 | 
			
		||||
 | 
			
		||||
@bot.event
 | 
			
		||||
async def on_ready():
 | 
			
		||||
    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)
 | 
			
		||||
 | 
			
		||||
    await asyncio.gather(*[_guild_selfcheck(g, bot.config['DEFAULT']) for g in bot.guilds])
 | 
			
		||||
 | 
			
		||||
    # Slash command sync
 | 
			
		||||
    try:
 | 
			
		||||
        dev_gid = bot.config['DEFAULT'].get('dev_guild_id')
 | 
			
		||||
        if dev_gid:
 | 
			
		||||
            guild = bot.get_guild(int(dev_gid))
 | 
			
		||||
            if guild:
 | 
			
		||||
                synced = await bot.tree.sync(guild=guild)
 | 
			
		||||
                print(f"[Slash] Synced {len(synced)} commands to {guild.name}")
 | 
			
		||||
            else:
 | 
			
		||||
                synced = await bot.tree.sync()
 | 
			
		||||
                print(f"[Slash] Synced {len(synced)} commands globally (dev_guild_id not in cache)")
 | 
			
		||||
        else:
 | 
			
		||||
            synced = await bot.tree.sync()
 | 
			
		||||
            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()
 | 
			
		||||
 | 
			
		||||
# ---------- Auto-discover extensions ----------
 | 
			
		||||
 | 
			
		||||
modules_path = pathlib.Path(__file__).parent / 'modules'
 | 
			
		||||
extensions = []
 | 
			
		||||
for folder in modules_path.iterdir():
 | 
			
		||||
    if folder.is_dir():
 | 
			
		||||
        for file in folder.glob('*.py'):
 | 
			
		||||
            if file.name == '__init__.py':
 | 
			
		||||
                continue
 | 
			
		||||
            extensions.append(f"modules.{folder.name}.{file.stem}")
 | 
			
		||||
 | 
			
		||||
async def main():
 | 
			
		||||
    async with bot:
 | 
			
		||||
        for ext in extensions:
 | 
			
		||||
            try:
 | 
			
		||||
                await bot.load_extension(ext)
 | 
			
		||||
                print(f"[Modules] Loaded: {ext}")
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                print(f"[Modules] Failed to load {ext}:", repr(e))
 | 
			
		||||
        await bot.start(TOKEN)
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    asyncio.run(main())
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user