Files
overub/modules/admin/__init__.py
2025-12-21 17:12:32 +01:00

307 lines
13 KiB
Python

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 <on|off>",
)
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 <on|off> [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 <on|off> [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 <list|enable|disable|reload|info|config|update> [name]",
example=".modules list",
permission="admin",
)
async def modules_cmd(event, args):
if not args:
await event.reply("Usage: .modules <list|enable|disable|reload|info|config|update> [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 <check|all|rollback> [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 <create|list|delete> <core|modules|plugins> [name]",
permission="admin",
)
async def backup_cmd(event, args):
if not args:
await event.reply("Usage: .backup <create|list|delete|auto|schedule> <core|modules|plugins> [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 <create|list|delete|auto|schedule> <core|modules|plugins> [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")