forked from sass/tipibot
Adds economy.get_economy_stats() (single-scan total coins, house balance, player-held coins, player count) and a "Rahavaru" field to /status, so admins can see whether the sinks are keeping pace with minted income. Also removed a duplicate bot_admin_check import. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013VbAVsrZuYesea99mPMmPT
150 lines
5.4 KiB
Python
150 lines
5.4 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import datetime
|
|
import json
|
|
import logging
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from collections.abc import Awaitable, Callable
|
|
from pathlib import Path
|
|
|
|
import discord
|
|
from discord import app_commands
|
|
|
|
from core import economy
|
|
from core.admin import bot_admin_check
|
|
import strings as S
|
|
|
|
|
|
def register_ops_admin_commands(
|
|
tree: app_commands.CommandTree,
|
|
bot: discord.Client,
|
|
log: logging.Logger,
|
|
process,
|
|
start_time: datetime.datetime,
|
|
log_dir: Path,
|
|
guild_obj: discord.Object,
|
|
restart_file: Path,
|
|
get_member_cache_size: Callable[[], int],
|
|
get_paused: Callable[[], bool],
|
|
set_paused: Callable[[bool], None],
|
|
count_economy_users: Callable[[], Awaitable[int]],
|
|
) -> None:
|
|
@tree.command(name="status", description=S.CMD["status"])
|
|
@app_commands.guild_only()
|
|
@bot_admin_check()
|
|
async def cmd_status(interaction: discord.Interaction):
|
|
mem = process.memory_info()
|
|
cpu = process.cpu_percent(interval=0.1)
|
|
uptime = datetime.datetime.now() - start_time
|
|
hours, rem = divmod(int(uptime.total_seconds()), 3600)
|
|
minutes, seconds = divmod(rem, 60)
|
|
tasks_count = len(asyncio.all_tasks())
|
|
latency_ms = round(bot.latency * 1000, 1)
|
|
cache_size = get_member_cache_size()
|
|
user_count = await count_economy_users()
|
|
eco_stats = await economy.get_economy_stats()
|
|
|
|
embed = discord.Embed(title=S.STATUS_UI["title"], color=0x57F287)
|
|
embed.add_field(
|
|
name=S.STATUS_UI["uptime_field"],
|
|
value=S.STATUS_UI["uptime_val"].format(hours=hours, minutes=minutes, seconds=seconds),
|
|
inline=True,
|
|
)
|
|
embed.add_field(
|
|
name=S.STATUS_UI["latency_field"],
|
|
value=S.STATUS_UI["latency_val"].format(ms=latency_ms),
|
|
inline=True,
|
|
)
|
|
embed.add_field(
|
|
name=S.STATUS_UI["ram_field"],
|
|
value=S.STATUS_UI["ram_val"].format(mb=f"{mem.rss / 1024**2:.1f}"),
|
|
inline=True,
|
|
)
|
|
embed.add_field(
|
|
name=S.STATUS_UI["cpu_field"],
|
|
value=S.STATUS_UI["cpu_val"].format(percent=f"{cpu:.1f}"),
|
|
inline=True,
|
|
)
|
|
embed.add_field(
|
|
name=S.STATUS_UI["tasks_field"],
|
|
value=str(tasks_count),
|
|
inline=True,
|
|
)
|
|
embed.add_field(
|
|
name=S.STATUS_UI["eco_players_field"],
|
|
value=str(user_count),
|
|
inline=True,
|
|
)
|
|
embed.add_field(
|
|
name=S.STATUS_UI["members_cache_field"],
|
|
value=str(cache_size),
|
|
inline=True,
|
|
)
|
|
total = eco_stats["total_coins"]
|
|
house_pct = round(eco_stats["house_balance"] / total * 100) if total else 0
|
|
embed.add_field(
|
|
name=S.STATUS_UI["supply_field"],
|
|
value=S.STATUS_UI["supply_val"].format(
|
|
total=f"{total:,}".replace(",", " "),
|
|
players=f"{eco_stats['player_coins']:,}".replace(",", " "),
|
|
house=f"{eco_stats['house_balance']:,}".replace(",", " "),
|
|
house_pct=house_pct,
|
|
),
|
|
inline=False,
|
|
)
|
|
|
|
log_lines = [
|
|
S.STATUS_UI["log_line"].format(name=p.name, size_kb=f"{p.stat().st_size / 1024:.1f}")
|
|
for p in sorted(log_dir.glob("*.log*"))
|
|
if p.is_file()
|
|
]
|
|
embed.add_field(
|
|
name=S.STATUS_UI["log_files_field"],
|
|
value="\n".join(log_lines) or S.STATUS_UI["none"],
|
|
inline=False,
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
@tree.command(name="sync", description=S.CMD["sync"])
|
|
@app_commands.guild_only()
|
|
@bot_admin_check()
|
|
async def cmd_sync(interaction: discord.Interaction):
|
|
await interaction.response.defer(ephemeral=True)
|
|
tree.copy_global_to(guild=guild_obj)
|
|
await tree.sync(guild=guild_obj)
|
|
tree.clear_commands(guild=None)
|
|
await tree.sync()
|
|
await interaction.followup.send(S.MSG_SYNC_DONE, ephemeral=True)
|
|
log.info("/sync triggered by %s", interaction.user)
|
|
|
|
@tree.command(name="restart", description=S.CMD["restart"])
|
|
@app_commands.guild_only()
|
|
@bot_admin_check()
|
|
async def cmd_restart(interaction: discord.Interaction):
|
|
restart_file.write_text(json.dumps({"channel_id": interaction.channel_id}), encoding="utf-8")
|
|
await interaction.response.send_message(S.MSG_RESTARTING, ephemeral=True)
|
|
log.info("/restart triggered by %s", interaction.user)
|
|
subprocess.Popen([sys.executable] + sys.argv, cwd=os.getcwd())
|
|
await bot.close()
|
|
|
|
@tree.command(name="shutdown", description=S.CMD["shutdown"])
|
|
@app_commands.guild_only()
|
|
@bot_admin_check()
|
|
async def cmd_shutdown(interaction: discord.Interaction):
|
|
await interaction.response.send_message(S.MSG_SHUTTING_DOWN, ephemeral=True)
|
|
log.info("/shutdown triggered by %s", interaction.user)
|
|
await bot.close()
|
|
|
|
@tree.command(name="pause", description=S.CMD["pause"])
|
|
@app_commands.guild_only()
|
|
@bot_admin_check()
|
|
async def cmd_pause(interaction: discord.Interaction):
|
|
paused = not get_paused()
|
|
set_paused(paused)
|
|
msg = S.MSG_PAUSED if paused else S.MSG_UNPAUSED
|
|
log.info("/pause toggled → %s by %s", "PAUSED" if paused else "UNPAUSED", interaction.user)
|
|
await interaction.response.send_message(msg, ephemeral=True)
|