122 lines
4.0 KiB
Python
122 lines
4.0 KiB
Python
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
|
|
|
|
# Load env
|
|
load_dotenv()
|
|
TOKEN = os.getenv('DISCORD_TOKEN')
|
|
|
|
# Where to read settings from
|
|
CONFIG_PATH = os.getenv('SHAI_CONFIG', '/config/settings.conf')
|
|
|
|
# Load settings
|
|
config = ConfigParser()
|
|
read_ok = config.read(CONFIG_PATH)
|
|
if not read_ok:
|
|
print(f"[Config] WARNING: could not read config at {CONFIG_PATH}; using in-image defaults where possible.")
|
|
|
|
# Intents (enable Members + Message Content in Dev Portal)
|
|
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
|
|
|
|
# Data file path (prefer INI, else env, else sensible default)
|
|
data_file = config['DEFAULT'].get('data_file', os.getenv('SHAI_DATA', '/data/data.json'))
|
|
|
|
bot = commands.Bot(command_prefix='!', intents=intents)
|
|
bot.config = config
|
|
bot.data_manager = DataManager(data_file)
|
|
|
|
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}")
|
|
|
|
# Mod channel (for report moderation)
|
|
_need_channel('mod_channel_id', 'read_messages', 'send_messages', 'add_reactions', 'read_message_history')
|
|
# Modlog channel
|
|
_need_channel('modlog_channel_id', 'read_messages', 'send_messages')
|
|
# Pirates list channel
|
|
_need_channel('pirates_list_channel_id', 'read_messages', 'send_messages')
|
|
# Auto-VC category/trigger are handled inside the cog
|
|
|
|
if problems:
|
|
print(f"[SelfCheck:{g.name}]")
|
|
for p in problems:
|
|
print(" -", p)
|
|
|
|
@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)
|
|
|
|
# Per-guild permission sanity checks (console log)
|
|
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))
|
|
|
|
# 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())
|