Lacie/xp/restoredb.py
2026-02-03 17:28:43 +01:00

113 lines
4.6 KiB
Python

import discord
from discord import app_commands
from discord.ext import commands
from discord.ui import View, Button
import os
import shutil
from moderation.loader import ModerationBase
from .groups import xp_admin_group
class RestoreXP(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.base_dir = os.path.dirname(os.path.abspath(__file__))
self.db_dir = os.path.join(self.base_dir, "databases")
self.backup_dir = os.path.join(self.base_dir, "backups")
# Register onto the shared xpadmin group
cmd = app_commands.command(name="restore", description="Restore a lifetime or annual XP database from backup")(self.restorebackup)
# Attach the autocompleters to the command object before adding
cmd.autocomplete("db_type")(self.db_type_autocomplete)
cmd.autocomplete("filename")(self.filename_autocomplete)
xp_admin_group.add_command(cmd)
@ModerationBase.is_admin()
@app_commands.describe(
db_type="Choose which database to restore",
filename="Select the backup file to restore"
)
async def restorebackup(self, interaction: discord.Interaction, db_type: str, filename: str):
await interaction.response.defer(ephemeral=False)
# Validate db_type
if db_type not in ["lifetime", "annual"]:
return await interaction.followup.send("❌ Invalid database type. Choose either `lifetime` or `annual`.")
db_path = os.path.join(self.db_dir, f"{db_type}.db")
backup_path = os.path.join(self.backup_dir, filename)
# Check if the backup file exists
if not os.path.exists(backup_path):
return await interaction.followup.send(f"❌ Backup file `{filename}` not found in backups folder.")
view = View(timeout=30)
confirmed = {"value": False}
async def yes_callback(btn_inter: discord.Interaction):
if btn_inter.user != interaction.user:
await btn_inter.response.send_message("You can't confirm this action.", ephemeral=True)
return
confirmed["value"] = True
await btn_inter.response.edit_message(content=f"✅ Confirmed restore of `{filename}` to `{db_type}.db`.", view=None)
view.stop()
async def no_callback(btn_inter: discord.Interaction):
if btn_inter.user != interaction.user:
await btn_inter.response.send_message("You can't cancel this action.", ephemeral=True)
return
confirmed["value"] = False
await btn_inter.response.edit_message(content="❌ Restore cancelled.", view=None)
view.stop()
yes_button = Button(label="Yes", style=discord.ButtonStyle.green)
no_button = Button(label="No", style=discord.ButtonStyle.red)
yes_button.callback = yes_callback
no_button.callback = no_callback
view.add_item(yes_button)
view.add_item(no_button)
await interaction.followup.send(
f"⚠️ Are you sure you want to **restore** `{db_type}.db` from `{filename}`?\n"
f"This will **overwrite the current database** and cannot be undone.",
view=view
)
await view.wait()
if not confirmed["value"]:
return # Cancelled or timed out
# --- Perform Restore ---
try:
shutil.copy2(backup_path, db_path)
except Exception as e:
return await interaction.followup.send(f"❌ Restore failed: `{e}`")
await interaction.followup.send(f"✅ `{db_type}.db` successfully restored from `{filename}`")
async def db_type_autocomplete(self, interaction: discord.Interaction, current: str):
options = ["lifetime", "annual"]
return [
app_commands.Choice(name=opt, value=opt)
for opt in options if current.lower() in opt.lower()
]
async def filename_autocomplete(self, interaction: discord.Interaction, current: str):
db_type = getattr(interaction.namespace, "db_type", None)
if not db_type:
return [app_commands.Choice(name="Select db_type first", value="")]
files = [
f for f in os.listdir(self.backup_dir)
if f.startswith(db_type) and f.endswith(".db")
]
files.sort(reverse=True) # Newest first
return [
app_commands.Choice(name=f, value=f)
for f in files if current.lower() in f.lower()
][:25] # Discord's limit
def cog_unload(self):
xp_admin_group.remove_command("restore")
async def setup(bot: commands.Bot):
await bot.add_cog(RestoreXP(bot))