from __future__ import annotations import datetime import logging from collections.abc import Awaitable, Callable import discord from discord import app_commands import economy import strings as S async def _dm_user(bot: discord.Client, user_id: int, msg: str) -> None: """Best-effort DM to a user.""" try: user = bot.get_user(user_id) or await bot.fetch_user(user_id) await user.send(msg) except Exception: pass def register_economy_admin_commands( tree: app_commands.CommandTree, bot: discord.Client, log: logging.Logger, cd_ts: Callable[[datetime.timedelta], str], apply_level_role: Callable[[discord.Member, int, int], Awaitable[None]], ) -> None: @tree.command(name="adminseason", description=S.CMD["adminseason"]) @app_commands.guild_only() @app_commands.default_permissions(manage_guild=True) @app_commands.describe(top_n=S.OPT["adminseason_top_n"]) async def cmd_adminseason(interaction: discord.Interaction, top_n: int = 10): await interaction.response.defer(ephemeral=True) top = await economy.do_season_reset(top_n) guild = interaction.guild if guild: all_role_names = {name for _, name in economy.LEVEL_ROLES} for role_name in all_role_names: role = discord.utils.find(lambda r: r.name == role_name, guild.roles) if not role: continue for member in list(role.members): try: await member.remove_roles(role, reason="Season reset") except discord.Forbidden: pass medals = ["🥇", "🥈", "🥉"] lines = [] for i, (uid, exp, lvl) in enumerate(top): member = guild.get_member(int(uid)) if guild else None name = member.display_name if member else S.LEADERBOARD_UI["unknown_user"].format(uid=uid) prefix = medals[i] if i < 3 else f"**{i + 1}.**" lines.append(S.SEASON["entry"].format(prefix=prefix, name=name, exp=exp, level=lvl)) embed = discord.Embed( title=S.TITLE["adminseason"], description=(S.SEASON["top_header"] + "\n" + "\n".join(lines)) if lines else S.SEASON["no_players"], color=0xF4C430, ) embed.set_footer(text=S.SEASON["footer"]) await interaction.followup.send(embed=embed, ephemeral=False) await interaction.followup.send(S.SEASON["done"], ephemeral=True) log.info("/adminseason triggered by %s (top_n=%d)", interaction.user, top_n) @tree.command(name="admincoins", description=S.CMD["admincoins"]) @app_commands.guild_only() @app_commands.describe( kasutaja=S.OPT["admin_kasutaja"], kogus=S.OPT["admincoins_kogus"], põhjus=S.OPT["admin_põhjus"], ) @app_commands.default_permissions(manage_guild=True) async def cmd_admincoins(interaction: discord.Interaction, kasutaja: discord.Member, kogus: int, põhjus: str): if kogus == 0: await interaction.response.send_message(S.ERR["admincoins_zero"], ephemeral=True) return res = await economy.do_admin_coins(kasutaja.id, kogus, interaction.user.id, põhjus) verb = f"+{kogus}" if kogus > 0 else str(kogus) emoji = "💰" if kogus > 0 else "💸" await interaction.response.send_message( S.ADMIN["coins_done"].format( emoji=emoji, name=kasutaja.display_name, verb=verb, coin=economy.COIN, balance=f"{res['balance']:,}", reason=põhjus, ), ephemeral=True, ) await _dm_user( bot, kasutaja.id, S.ADMIN["coins_dm"].format( emoji=emoji, verb=verb, coin=economy.COIN, reason=põhjus, balance=f"{res['balance']:,}", ), ) log.info("ADMINCOINS %s → %s (%s) by %s", verb, kasutaja, põhjus, interaction.user) @tree.command(name="adminjail", description=S.CMD["adminjail"]) @app_commands.guild_only() @app_commands.describe( kasutaja=S.OPT["admin_kasutaja"], minutid=S.OPT["adminjail_minutid"], põhjus=S.OPT["admin_põhjus"], ) @app_commands.default_permissions(manage_guild=True) async def cmd_adminjail(interaction: discord.Interaction, kasutaja: discord.Member, minutid: int, põhjus: str): if minutid <= 0: await interaction.response.send_message(S.ERR["positive_duration"], ephemeral=True) return await economy.do_admin_jail(kasutaja.id, minutid, interaction.user.id, põhjus) until_ts = cd_ts(datetime.timedelta(minutes=minutid)) await interaction.response.send_message( S.ADMIN["jail_done"].format(name=kasutaja.display_name, minutes=minutid, ts=until_ts, reason=põhjus), ephemeral=True, ) await _dm_user( bot, kasutaja.id, S.ADMIN["jail_dm"].format(minutes=minutid, reason=põhjus, ts=until_ts), ) log.info("ADMINJAIL %s %dmin (%s) by %s", kasutaja, minutid, põhjus, interaction.user) @tree.command(name="adminunjail", description=S.CMD["adminunjail"]) @app_commands.guild_only() @app_commands.describe(kasutaja=S.OPT["admin_kasutaja"]) @app_commands.default_permissions(manage_guild=True) async def cmd_adminunjail(interaction: discord.Interaction, kasutaja: discord.Member): await economy.do_admin_unjail(kasutaja.id, interaction.user.id) await interaction.response.send_message( S.ADMIN["unjail_done"].format(name=kasutaja.display_name), ephemeral=True ) await _dm_user(bot, kasutaja.id, S.ADMIN["unjail_dm"]) log.info("ADMINUNJAIL %s by %s", kasutaja, interaction.user) @tree.command(name="adminban", description=S.CMD["adminban"]) @app_commands.guild_only() @app_commands.describe( kasutaja=S.OPT["admin_kasutaja"], põhjus=S.OPT["admin_põhjus"], ) @app_commands.default_permissions(manage_guild=True) async def cmd_adminban(interaction: discord.Interaction, kasutaja: discord.Member, põhjus: str): if bot.user and kasutaja.id == bot.user.id: await interaction.response.send_message(S.ERR["admin_ban_bot"], ephemeral=True) return await economy.do_admin_ban(kasutaja.id, interaction.user.id, põhjus) await interaction.response.send_message( S.ADMIN["ban_done"].format(name=kasutaja.display_name, reason=põhjus), ephemeral=True, ) await _dm_user(bot, kasutaja.id, S.ADMIN["ban_dm"].format(reason=põhjus)) log.info("ADMINBAN %s (%s) by %s", kasutaja, põhjus, interaction.user) @tree.command(name="adminunban", description=S.CMD["adminunban"]) @app_commands.guild_only() @app_commands.describe(kasutaja=S.OPT["admin_kasutaja"]) @app_commands.default_permissions(manage_guild=True) async def cmd_adminunban(interaction: discord.Interaction, kasutaja: discord.Member): await economy.do_admin_unban(kasutaja.id, interaction.user.id) await interaction.response.send_message( S.ADMIN["unban_done"].format(name=kasutaja.display_name), ephemeral=True ) await _dm_user(bot, kasutaja.id, S.ADMIN["unban_dm"]) log.info("ADMINUNBAN %s by %s", kasutaja, interaction.user) @tree.command(name="adminreset", description=S.CMD["adminreset"]) @app_commands.guild_only() @app_commands.describe( kasutaja=S.OPT["admin_kasutaja"], põhjus=S.OPT["admin_põhjus"], ) @app_commands.default_permissions(manage_guild=True) async def cmd_adminreset(interaction: discord.Interaction, kasutaja: discord.Member, põhjus: str): if bot.user and kasutaja.id == bot.user.id: await interaction.response.send_message(S.ERR["admin_reset_bot"], ephemeral=True) return await economy.do_admin_reset(kasutaja.id, interaction.user.id) await interaction.response.send_message( S.ADMIN["reset_done"].format(name=kasutaja.display_name, reason=põhjus), ephemeral=True, ) await _dm_user(bot, kasutaja.id, S.ADMIN["reset_dm"].format(reason=põhjus)) log.info("ADMINRESET %s (%s) by %s", kasutaja, põhjus, interaction.user) @tree.command(name="adminview", description=S.CMD["adminview"]) @app_commands.guild_only() @app_commands.describe(kasutaja=S.OPT["admin_kasutaja"]) @app_commands.default_permissions(manage_guild=True) async def cmd_adminview(interaction: discord.Interaction, kasutaja: discord.Member): res = await economy.do_admin_inspect(kasutaja.id) data = res["data"] items_str = ", ".join(data.get("items", [])) or "-" uses = data.get("item_uses", {}) uses_str = ", ".join(f"{k}:{v}" for k, v in uses.items()) if uses else "-" jailed = data.get("jailed_until") or "-" banned = S.ADMINVIEW_UI["banned_yes"] if data.get("eco_banned") else S.ADMINVIEW_UI["banned_no"] exp = data.get("exp", 0) level = economy.get_level(exp) prestige_lvl = data.get("prestige_level", 0) prestige_pp = data.get("prestige_points", 0) total_fish = data.get("total_fish_caught", 0) inv_fish = len(data.get("fish_inventory") or []) embed = discord.Embed( title=S.ADMINVIEW_UI["title"].format(name=kasutaja.display_name), color=0x5865F2, ) embed.add_field(name=S.ADMINVIEW_UI["f_balance"], value=f"{data.get('balance', 0):,} {economy.COIN}", inline=True) embed.add_field(name=S.ADMINVIEW_UI["f_exp"], value=S.ADMINVIEW_UI["exp_val"].format(exp=f"{exp:,}", level=level), inline=True) embed.add_field(name=S.ADMINVIEW_UI["f_streak"], value=str(data.get("daily_streak", 0)), inline=True) embed.add_field(name=S.ADMINVIEW_UI["f_banned"], value=banned, inline=True) embed.add_field(name=S.ADMINVIEW_UI["f_jailed"], value=jailed, inline=True) embed.add_field(name=S.ADMINVIEW_UI["f_prestige"], value=S.ADMINVIEW_UI["prestige_val"].format(level=prestige_lvl, pp=prestige_pp), inline=True) embed.add_field(name=S.ADMINVIEW_UI["f_fish"], value=S.ADMINVIEW_UI["fish_val"].format(caught=total_fish, inv=inv_fish), inline=True) embed.add_field(name=S.ADMINVIEW_UI["f_items"], value=items_str, inline=False) embed.add_field(name=S.ADMINVIEW_UI["f_uses"], value=uses_str, inline=False) embed.add_field(name=S.ADMINVIEW_UI["f_last_daily"], value=data.get("last_daily") or "-", inline=True) embed.add_field(name=S.ADMINVIEW_UI["f_last_work"], value=data.get("last_work") or "-", inline=True) embed.add_field(name=S.ADMINVIEW_UI["f_last_crime"], value=data.get("last_crime") or "-", inline=True) embed.add_field(name=S.ADMINVIEW_UI["f_last_fish"], value=data.get("last_fish") or "-", inline=True) embed.set_footer(text=S.ADMINVIEW_UI["footer"].format(uid=kasutaja.id)) await interaction.response.send_message(embed=embed, ephemeral=True) log.info("ADMINVIEW %s by %s", kasutaja, interaction.user) @tree.command(name="adminexp", description=S.CMD["adminexp"]) @app_commands.guild_only() @app_commands.describe( kasutaja=S.OPT["admin_kasutaja"], kogus=S.OPT["adminexp_kogus"], põhjus=S.OPT["admin_põhjus"], ) @app_commands.default_permissions(manage_guild=True) async def cmd_adminexp(interaction: discord.Interaction, kasutaja: discord.Member, kogus: int, põhjus: str): if kogus == 0: await interaction.response.send_message(S.ERR["admincoins_zero"], ephemeral=True) return res = await economy.do_admin_exp(kasutaja.id, kogus, interaction.user.id, põhjus) verb = f"+{kogus}" if kogus > 0 else str(kogus) emoji = "📈" if kogus > 0 else "📉" await interaction.response.send_message( S.ADMIN["exp_done"].format( emoji=emoji, name=kasutaja.display_name, verb=verb, exp=f"{res['exp']:,}", level=res["new_level"], reason=põhjus, ), ephemeral=True, ) await _dm_user( bot, kasutaja.id, S.ADMIN["exp_dm"].format( emoji=emoji, verb=verb, reason=põhjus, exp=f"{res['exp']:,}", level=res["new_level"], ), ) if res["level_changed"]: member = interaction.guild.get_member(kasutaja.id) if interaction.guild else None if member: await apply_level_role(member, res["new_level"], res["old_level"]) log.info("ADMINEXP %s → %s (%s) by %s", verb, kasutaja, põhjus, interaction.user) @tree.command(name="adminitem", description=S.CMD["adminitem"]) @app_commands.guild_only() @app_commands.describe( kasutaja=S.OPT["admin_kasutaja"], ese=S.OPT["adminitem_ese"], tegevus=S.OPT["adminitem_tegevus"], ) @app_commands.default_permissions(manage_guild=True) async def cmd_adminitem(interaction: discord.Interaction, kasutaja: discord.Member, ese: str, tegevus: str): action = tegevus.strip().lower() if action not in ("anna", "eemalda"): await interaction.response.send_message(S.ADMIN["item_invalid"].format(item_id=tegevus), ephemeral=True) return action_key = "give" if action == "anna" else "remove" res = await economy.do_admin_item(kasutaja.id, ese, action_key, interaction.user.id) if not res["ok"]: if res["reason"] == "invalid_item": await interaction.response.send_message(S.ADMIN["item_invalid"].format(item_id=ese), ephemeral=True) elif res["reason"] == "not_owned": await interaction.response.send_message( S.ADMIN["item_not_owned"].format(name=kasutaja.display_name, item_id=ese), ephemeral=True, ) else: await interaction.response.send_message(S.ERR["generic_error"].format(error=res["reason"]), ephemeral=True) return item_name = economy.SHOP[ese]["name"] if action_key == "give": await interaction.response.send_message(S.ADMIN["item_given"].format(item=item_name, name=kasutaja.display_name), ephemeral=True) await _dm_user(bot, kasutaja.id, S.ADMIN["item_dm_given"].format(item=item_name)) else: await interaction.response.send_message(S.ADMIN["item_removed"].format(item=item_name, name=kasutaja.display_name), ephemeral=True) await _dm_user(bot, kasutaja.id, S.ADMIN["item_dm_removed"].format(item=item_name)) log.info("ADMINITEM %s %s %s by %s", action_key, ese, kasutaja, interaction.user)