from core.module import Module class AdminModule(Module): name = "admin" version = "0.1.0" description = "Admin commands" async def on_load(self) -> None: builder = self.commands await self.app.database.execute( "CREATE TABLE IF NOT EXISTS admin_welcome (chat_id INTEGER PRIMARY KEY, message TEXT)" ) await self.app.database.execute( "CREATE TABLE IF NOT EXISTS admin_stats (chat_id INTEGER PRIMARY KEY, messages INTEGER)" ) async def on_message(evt): event = evt.payload.get("event") chat_id = getattr(event, "chat_id", None) if not chat_id: return row = await self.app.database.fetchone( "SELECT messages FROM admin_stats WHERE chat_id=?", (chat_id,), ) count = (row["messages"] if row else 0) + 1 await self.app.database.execute( "INSERT OR REPLACE INTO admin_stats (chat_id, messages) VALUES (?, ?)", (chat_id, count), ) async def on_chat(evt): event = evt.payload.get("event") if not getattr(event, "user_joined", False) and not getattr(event, "user_added", False): return chat_id = getattr(event, "chat_id", None) if not chat_id: return row = await self.app.database.fetchone( "SELECT message FROM admin_welcome WHERE chat_id=?", (chat_id,), ) if row: await event.client.send_message(chat_id, row["message"]) self.app.events.on("on_message_new", on_message) self.app.events.on("on_chat_action", on_chat) @builder.command( name="pin", description="Stub pin command", category="admin", usage=".pin", ) async def pin_cmd(event, args): reply = await event.get_reply_message() if not reply: await event.reply("Reply to a message to pin") return await event.client.pin_message(event.chat_id, reply) await event.reply("Pinned") @builder.command( name="welcome", description="Stub welcome setup", category="admin", usage=".welcome ", ) async def welcome_cmd(event, args): chat_id = getattr(event, "chat_id", None) if not chat_id: await event.reply("Chat only") return if not args: await event.reply("Usage: .welcome [message]") return if args[0] == "on": message = " ".join(args[1:]) or "Welcome!" await self.app.database.execute( "INSERT OR REPLACE INTO admin_welcome (chat_id, message) VALUES (?, ?)", (chat_id, message), ) await event.reply("Welcome enabled") return if args[0] == "off": await self.app.database.execute( "DELETE FROM admin_welcome WHERE chat_id=?", (chat_id,), ) await event.reply("Welcome disabled") return await event.reply("Usage: .welcome [message]") @builder.command( name="stats", description="Stub group stats", category="admin", usage=".stats", ) async def stats_cmd(event, args): chat_id = getattr(event, "chat_id", None) if not chat_id: await event.reply("Chat only") return row = await self.app.database.fetchone( "SELECT messages FROM admin_stats WHERE chat_id=?", (chat_id,), ) await event.reply(f"Messages: {row['messages'] if row else 0}") @builder.command( name="modules", description="Manage modules", category="admin", usage=".modules [name]", example=".modules list", permission="admin", ) async def modules_cmd(event, args): if not args: await event.reply("Usage: .modules [name]") return action = args[0] name = args[1] if len(args) > 1 else None module_config = self.app.config.get_modules().setdefault("modules", {}) if action == "update": sub = args[1] if len(args) > 1 else "check" if sub == "check": try: commits = await self.app.module_updates.check_updates() except Exception as exc: await event.reply(f"Update check failed: {exc}") return await event.reply("\n".join(commits) if commits else "No module updates") return if sub == "list": try: commits = await self.app.module_updates.check_updates() except Exception as exc: await event.reply(f"Update list failed: {exc}") return await event.reply("\n".join(commits) if commits else "No module updates") return if sub == "all": try: output = await self.app.module_updates.update_all() except Exception as exc: self.app.update_service.record_event( action="module_update", status="failed", meta={"module": "all"}, ) await event.reply(f"Update failed: {exc}") return self.app.update_service.record_event( action="module_update", status="success", meta={"module": "all"}, ) await event.reply(output or "Modules updated") return if sub not in {"rollback"}: try: output = await self.app.module_updates.update_all() except Exception as exc: self.app.update_service.record_event( action="module_update", status="failed", meta={"module": sub}, ) await event.reply(f"Update failed: {exc}") return await self.app.modules.reload(f"modules.{sub}") self.app.update_service.record_event( action="module_update", status="success", meta={"module": sub}, ) await event.reply(f"Updated {sub}") return if sub == "rollback": ref = args[2] if len(args) > 2 else "HEAD~1" try: output = await self.app.module_updates.rollback(ref) except Exception as exc: self.app.update_service.record_event( action="module_rollback", status="failed", meta={"ref": ref}, ) await event.reply(f"Rollback failed: {exc}") return self.app.update_service.record_event( action="module_rollback", status="success", meta={"ref": ref}, ) await event.reply(output or "Rolled back") return await event.reply("Usage: .modules update [ref]") return if action == "list": loaded = set(self.app.modules.list()) summary = [] for mod_name, cfg in module_config.items(): status = "loaded" if f"modules.{mod_name}" in loaded else "unloaded" enabled = "enabled" if cfg.get("enabled", True) else "disabled" summary.append(f"{mod_name} ({enabled}, {status})") await event.reply(", ".join(summary) if summary else "No modules configured") return if not name: await event.reply("Module name required") return module_path = f"modules.{name}" if action == "enable": module_config.setdefault(name, {})["enabled"] = True self.app.config.save_modules() await self.app.modules.load(module_path) await event.reply(f"Enabled {name}") return if action == "disable": module_config.setdefault(name, {})["enabled"] = False self.app.config.save_modules() await self.app.modules.unload(module_path) await event.reply(f"Disabled {name}") return if action == "reload": await self.app.modules.reload(module_path) await event.reply(f"Reloaded {name}") return if action == "info": cfg = module_config.get(name, {}) loaded = module_path in self.app.modules.list() await event.reply(str({"name": name, "loaded": loaded, "config": cfg})) return if action == "config": cfg = module_config.get(name, {}) await event.reply(str(cfg)) return await event.reply("Unknown action") @builder.command( name="backup", description="Manage backups", category="admin", usage=".backup [name]", permission="admin", ) async def backup_cmd(event, args): if not args: await event.reply("Usage: .backup [name]") return action = args[0] manager = self.app.backups if action in {"auto", "schedule"}: if action == "auto": toggle = args[1] if len(args) > 1 else "on" cfg = self.app.config.get().setdefault("backup", {}) cfg["auto"] = toggle == "on" self.app.config.save() await event.reply(f"Auto-backup {'enabled' if cfg['auto'] else 'disabled'}") return if action == "schedule": if len(args) < 2: await event.reply("Time required") return cfg = self.app.config.get().setdefault("backup", {}) cfg["schedule"] = args[1] self.app.config.save() await event.reply("Backup schedule updated") return if len(args) < 2: await event.reply("Usage: .backup [name]") return scope = args[1] if action == "create": try: path = manager.create(scope) except Exception as exc: self.app.update_service.record_event( action="backup", status="failed", meta={"scope": scope}, ) await event.reply(f"Backup failed: {exc}") return self.app.update_service.record_event( action="backup", status="success", meta={"scope": scope, "name": path.name}, ) await event.reply(f"Backup created: {path.name}") return if action == "list": items = manager.list(scope) await event.reply(", ".join(items) if items else "No backups found") return if action == "delete": if len(args) < 3: await event.reply("Backup name required") return manager.delete(scope, args[2]) await event.reply("Backup deleted") return await event.reply("Unknown action")