Fucking shit pask bad
This commit is contained in:
883
economy_extra_commands.py
Normal file
883
economy_extra_commands.py
Normal file
@@ -0,0 +1,883 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import datetime
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
from collections.abc import Awaitable, Callable, MutableSet
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from discord import app_commands
|
||||||
|
|
||||||
|
import economy
|
||||||
|
import strings as S
|
||||||
|
|
||||||
|
|
||||||
|
def register_economy_extra_commands(
|
||||||
|
tree: app_commands.CommandTree,
|
||||||
|
bot: discord.Client,
|
||||||
|
coin: Callable[[int], str],
|
||||||
|
cd_ts: Callable[[datetime.timedelta], str],
|
||||||
|
parse_amount: Callable[[str, int], tuple[int | None, str | None]],
|
||||||
|
ensure_level_role: Callable[[discord.Member, int], Awaitable[None]],
|
||||||
|
active_games: MutableSet[int],
|
||||||
|
) -> None:
|
||||||
|
active_heist = None
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# /heist - multiplayer group robbery of the house
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
_HEIST_JOIN_WINDOW = 300 # seconds players have to join
|
||||||
|
_HEIST_MIN_PLAYERS = 2
|
||||||
|
_HEIST_GLOBAL_CD = 14400 # seconds between heist events server-wide (4h)
|
||||||
|
_HEIST_MAX_PLAYERS = 8
|
||||||
|
_HEIST_BASE_CHANCE = 0.35 # 35% solo
|
||||||
|
_HEIST_CHANCE_STEP = 0.05 # +5% per extra player
|
||||||
|
_HEIST_MAX_CHANCE = 0.65 # cap at 65%
|
||||||
|
|
||||||
|
def _build_heist_story(participants: list[discord.Member], success: bool) -> list[str]:
|
||||||
|
"""Return a list of story lines for the heist narrative reveal."""
|
||||||
|
story = S.HEIST_STORY
|
||||||
|
leader = participants[0].display_name
|
||||||
|
if len(participants) == 1:
|
||||||
|
names = f"**{leader}**"
|
||||||
|
elif len(participants) == 2:
|
||||||
|
names = S.HEIST_UI["names_duo"].format(
|
||||||
|
a=participants[0].display_name,
|
||||||
|
b=participants[1].display_name,
|
||||||
|
)
|
||||||
|
elif len(participants) <= 4:
|
||||||
|
names = S.HEIST_UI["names_sep"].join(f"**{p.display_name}**" for p in participants)
|
||||||
|
else:
|
||||||
|
names = S.HEIST_UI["names_crew"].format(leader=participants[0].display_name)
|
||||||
|
|
||||||
|
vehicle = random.choice(story["vehicles"])
|
||||||
|
approach = random.choice(["sneaky", "loud"])
|
||||||
|
non_leaders = participants[1:] if len(participants) > 1 else participants
|
||||||
|
|
||||||
|
def fill(tmpl: str) -> str:
|
||||||
|
picked = random.choice(non_leaders).display_name
|
||||||
|
return tmpl.format(
|
||||||
|
leader=f"**{leader}**",
|
||||||
|
member=f"**{picked}**",
|
||||||
|
names=names,
|
||||||
|
vehicle=vehicle,
|
||||||
|
)
|
||||||
|
|
||||||
|
getaway_pool = "getaway_success" if success else "getaway_fail"
|
||||||
|
|
||||||
|
return [
|
||||||
|
fill(random.choice(story["arrival"])),
|
||||||
|
fill(random.choice(story[f"entry_{approach}"])),
|
||||||
|
fill(random.choice(story["inside"])),
|
||||||
|
fill(random.choice(story["vault"])),
|
||||||
|
fill(random.choice(story["vault_open"])),
|
||||||
|
fill(random.choice(story["police_inbound"])),
|
||||||
|
fill(random.choice(story[getaway_pool])),
|
||||||
|
fill(random.choice(story["escape_success" if success else "escape_fail"])),
|
||||||
|
]
|
||||||
|
|
||||||
|
class HeistLobbyView(discord.ui.View):
|
||||||
|
def __init__(self, organizer: discord.Member, organizer_has_jellyfin: bool = False):
|
||||||
|
super().__init__(timeout=_HEIST_JOIN_WINDOW)
|
||||||
|
self.organizer = organizer
|
||||||
|
self.participants: list[discord.Member] = [organizer]
|
||||||
|
self.message: discord.Message | None = None
|
||||||
|
self.resolved = False
|
||||||
|
self.jellyfin_holders: int = 1 if organizer_has_jellyfin else 0
|
||||||
|
|
||||||
|
def _chance(self) -> float:
|
||||||
|
n = len(self.participants)
|
||||||
|
base = min(_HEIST_BASE_CHANCE + _HEIST_CHANCE_STEP * (n - 1), _HEIST_MAX_CHANCE)
|
||||||
|
jelly_bonus = 0.05 if self.jellyfin_holders > 0 else 0.0
|
||||||
|
return min(base + jelly_bonus, _HEIST_MAX_CHANCE)
|
||||||
|
|
||||||
|
def _lobby_embed(self) -> discord.Embed:
|
||||||
|
names = "\n".join(f"• {p.display_name}" for p in self.participants)
|
||||||
|
desc = S.HEIST_UI["lobby_desc"].format(
|
||||||
|
n=len(self.participants),
|
||||||
|
max=_HEIST_MAX_PLAYERS,
|
||||||
|
names=names,
|
||||||
|
chance=int(self._chance() * 100),
|
||||||
|
ts=int(self._timeout_expiry()),
|
||||||
|
)
|
||||||
|
return discord.Embed(title=S.TITLE["heist_lobby"], description=desc, color=0xE67E22)
|
||||||
|
|
||||||
|
def _timeout_expiry(self) -> float:
|
||||||
|
return time.time() + (self.timeout or 0)
|
||||||
|
|
||||||
|
@discord.ui.button(label=S.HEIST_UI["btn_join"], style=discord.ButtonStyle.danger)
|
||||||
|
async def join(self, interaction: discord.Interaction, _: discord.ui.Button):
|
||||||
|
if any(p.id == interaction.user.id for p in self.participants):
|
||||||
|
await interaction.response.send_message(S.HEIST_UI["already_joined"], ephemeral=True)
|
||||||
|
return
|
||||||
|
if len(self.participants) >= _HEIST_MAX_PLAYERS:
|
||||||
|
await interaction.response.send_message(S.ERR["heist_full"], ephemeral=True)
|
||||||
|
return
|
||||||
|
if interaction.user.id in active_games:
|
||||||
|
await interaction.response.send_message(S.ERR["already_in_game"], ephemeral=True)
|
||||||
|
return
|
||||||
|
res = await economy.do_heist_check(interaction.user.id)
|
||||||
|
if not res["ok"]:
|
||||||
|
if res["reason"] == "banned":
|
||||||
|
await interaction.response.send_message(S.MSG_BANNED, ephemeral=True)
|
||||||
|
elif res["reason"] == "jailed":
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.CD_MSG["jailed"].format(ts=cd_ts(res["remaining"])), ephemeral=True
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.CD_MSG["heist"].format(ts=cd_ts(res["remaining"])), ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
self.participants.append(interaction.user)
|
||||||
|
active_games.add(interaction.user.id)
|
||||||
|
joiner_data = await economy.get_user(interaction.user.id)
|
||||||
|
if "jellyfin" in joiner_data.get("items", []):
|
||||||
|
self.jellyfin_holders += 1
|
||||||
|
await interaction.response.edit_message(embed=self._lobby_embed())
|
||||||
|
|
||||||
|
@discord.ui.button(label=S.HEIST_UI["btn_start"], style=discord.ButtonStyle.success)
|
||||||
|
async def start_now(self, interaction: discord.Interaction, _: discord.ui.Button):
|
||||||
|
if interaction.user.id != self.organizer.id:
|
||||||
|
await interaction.response.send_message(S.HEIST_UI["only_organizer"], ephemeral=True)
|
||||||
|
return
|
||||||
|
if len(self.participants) < _HEIST_MIN_PLAYERS:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.ERR["heist_min_players"].format(min=_HEIST_MIN_PLAYERS), ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
await self._resolve(interaction)
|
||||||
|
|
||||||
|
async def _resolve(self, interaction: discord.Interaction | None = None) -> None:
|
||||||
|
nonlocal active_heist
|
||||||
|
if self.resolved:
|
||||||
|
return
|
||||||
|
self.resolved = True
|
||||||
|
active_heist = None
|
||||||
|
self.stop()
|
||||||
|
self.clear_items()
|
||||||
|
|
||||||
|
for p in self.participants:
|
||||||
|
active_games.discard(p.id)
|
||||||
|
|
||||||
|
n = len(self.participants)
|
||||||
|
channel = interaction.channel if interaction else self.message.channel if self.message else None
|
||||||
|
|
||||||
|
if n < _HEIST_MIN_PLAYERS:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.TITLE["heist_cancel"],
|
||||||
|
description=S.HEIST_UI["cancel_desc"].format(min=_HEIST_MIN_PLAYERS),
|
||||||
|
color=0x99AAB5,
|
||||||
|
)
|
||||||
|
if interaction and not interaction.response.is_done():
|
||||||
|
await interaction.response.edit_message(embed=embed, view=self)
|
||||||
|
elif self.message:
|
||||||
|
try:
|
||||||
|
await self.message.edit(embed=embed, view=self)
|
||||||
|
except discord.HTTPException:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
success = random.random() < self._chance()
|
||||||
|
story_lines = _build_heist_story(self.participants, success)
|
||||||
|
|
||||||
|
lobby_done = discord.Embed(
|
||||||
|
title=S.HEIST_UI["started_title"],
|
||||||
|
description=S.HEIST_UI["started_desc"].format(n=n),
|
||||||
|
color=0x99AAB5,
|
||||||
|
)
|
||||||
|
if interaction and not interaction.response.is_done():
|
||||||
|
await interaction.response.edit_message(embed=lobby_done, view=self)
|
||||||
|
elif self.message:
|
||||||
|
try:
|
||||||
|
await self.message.edit(embed=lobby_done, view=self)
|
||||||
|
except discord.HTTPException:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if channel:
|
||||||
|
story_embed = discord.Embed(title=S.HEIST_UI["story_title"], description="", color=0xE67E22)
|
||||||
|
story_msg = await channel.send(embed=story_embed)
|
||||||
|
accumulated = ""
|
||||||
|
for i, line in enumerate(story_lines):
|
||||||
|
await asyncio.sleep(random.uniform(3.0, 4.5))
|
||||||
|
accumulated += ("\n\n" if i > 0 else "") + line
|
||||||
|
story_embed.description = accumulated
|
||||||
|
try:
|
||||||
|
await story_msg.edit(embed=story_embed)
|
||||||
|
except discord.HTTPException:
|
||||||
|
pass
|
||||||
|
await asyncio.sleep(2.0)
|
||||||
|
|
||||||
|
res = await economy.do_heist_resolve([p.id for p in self.participants], success)
|
||||||
|
payout_each = res["payout_each"]
|
||||||
|
names_str = "\n".join(f"• {p.display_name}" for p in self.participants)
|
||||||
|
guild = interaction.guild if interaction else self.message.guild if self.message else None
|
||||||
|
|
||||||
|
if success:
|
||||||
|
result_desc = S.HEIST_UI["win_desc"].format(names=names_str, payout=coin(payout_each))
|
||||||
|
result_embed = discord.Embed(
|
||||||
|
title=S.TITLE["heist_win"],
|
||||||
|
description=result_desc,
|
||||||
|
color=0x57F287,
|
||||||
|
)
|
||||||
|
for p in self.participants:
|
||||||
|
exp_res = await economy.award_exp(p.id, economy.EXP_REWARDS["heist_win"])
|
||||||
|
if exp_res["old_level"] != exp_res["new_level"] and guild:
|
||||||
|
gm = guild.get_member(p.id)
|
||||||
|
if gm:
|
||||||
|
asyncio.create_task(ensure_level_role(gm, exp_res["new_level"]))
|
||||||
|
else:
|
||||||
|
result_desc = S.HEIST_UI["fail_desc"].format(names=names_str)
|
||||||
|
result_embed = discord.Embed(
|
||||||
|
title=S.TITLE["heist_fail"],
|
||||||
|
description=result_desc,
|
||||||
|
color=0xED4245,
|
||||||
|
)
|
||||||
|
|
||||||
|
await economy.set_heist_global_cd(time.time() + _HEIST_GLOBAL_CD)
|
||||||
|
|
||||||
|
if channel:
|
||||||
|
await channel.send(embed=result_embed)
|
||||||
|
elif self.message:
|
||||||
|
try:
|
||||||
|
await self.message.channel.send(embed=result_embed)
|
||||||
|
except discord.HTTPException:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def on_timeout(self) -> None:
|
||||||
|
await self._resolve()
|
||||||
|
|
||||||
|
@tree.command(name="heist", description=S.CMD["heist"])
|
||||||
|
@app_commands.guild_only()
|
||||||
|
async def cmd_heist(interaction: discord.Interaction):
|
||||||
|
nonlocal active_heist
|
||||||
|
if active_heist is not None:
|
||||||
|
await interaction.response.send_message(S.ERR["heist_active"], ephemeral=True)
|
||||||
|
return
|
||||||
|
heist_cd = await economy.get_heist_global_cd()
|
||||||
|
if time.time() < heist_cd:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.CD_MSG["heist_global"].format(
|
||||||
|
ts=cd_ts(datetime.timedelta(seconds=heist_cd - time.time()))
|
||||||
|
),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
if interaction.user.id in active_games:
|
||||||
|
await interaction.response.send_message(S.ERR["already_in_game"], ephemeral=True)
|
||||||
|
return
|
||||||
|
res = await economy.do_heist_check(interaction.user.id)
|
||||||
|
if not res["ok"]:
|
||||||
|
if res["reason"] == "banned":
|
||||||
|
await interaction.response.send_message(S.MSG_BANNED, ephemeral=True)
|
||||||
|
elif res["reason"] == "jailed":
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.CD_MSG["jailed"].format(ts=cd_ts(res["remaining"])), ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
organizer_data = await economy.get_user(interaction.user.id)
|
||||||
|
view = HeistLobbyView(interaction.user, "jellyfin" in organizer_data.get("items", []))
|
||||||
|
active_heist = view
|
||||||
|
active_games.add(interaction.user.id)
|
||||||
|
await interaction.response.send_message(embed=view._lobby_embed(), view=view)
|
||||||
|
view.message = await interaction.original_response()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# /jailbreak - Monopoly-style dice escape
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
_DICE_EMOJI = [
|
||||||
|
"<:TipiYKS:1483103190491856916>",
|
||||||
|
"<:TipiKAKS:1483103215841972404>",
|
||||||
|
"<:TipiKOLM:1483103217846980781>",
|
||||||
|
"<:TipiNELI:1483103237585240114>",
|
||||||
|
"<:TipiVIIS:1483103239036469289>",
|
||||||
|
"<:TipiKUUS:1483103253163020348>",
|
||||||
|
]
|
||||||
|
|
||||||
|
class JailbreakView(discord.ui.View):
|
||||||
|
MAX_TRIES = 3
|
||||||
|
|
||||||
|
def __init__(self, user_id: int):
|
||||||
|
super().__init__(timeout=120)
|
||||||
|
self.user_id = user_id
|
||||||
|
self.tries = 0
|
||||||
|
self._rolling = False
|
||||||
|
self._add_roll_btn()
|
||||||
|
|
||||||
|
def _add_roll_btn(self):
|
||||||
|
self.clear_items()
|
||||||
|
btn = discord.ui.Button(
|
||||||
|
label=S.JAILBREAK_UI["btn_roll"].format(try_=self.tries + 1, max=self.MAX_TRIES),
|
||||||
|
style=discord.ButtonStyle.primary,
|
||||||
|
)
|
||||||
|
btn.callback = self._on_roll
|
||||||
|
self.add_item(btn)
|
||||||
|
|
||||||
|
async def _on_roll(self, interaction: discord.Interaction):
|
||||||
|
if interaction.user.id != self.user_id:
|
||||||
|
await interaction.response.send_message(S.ERR["not_your_game"], ephemeral=True)
|
||||||
|
return
|
||||||
|
if self._rolling:
|
||||||
|
await interaction.response.defer()
|
||||||
|
return
|
||||||
|
self._rolling = True
|
||||||
|
|
||||||
|
self.clear_items()
|
||||||
|
rolling_embed = discord.Embed(
|
||||||
|
title=S.TITLE["jailbreak"],
|
||||||
|
description=S.JAILBREAK_UI["rolling_desc"],
|
||||||
|
color=0xF4C430,
|
||||||
|
)
|
||||||
|
await interaction.response.edit_message(embed=rolling_embed, view=self)
|
||||||
|
|
||||||
|
d1 = random.randint(1, 6)
|
||||||
|
d2 = random.randint(1, 6)
|
||||||
|
e1, e2 = _DICE_EMOJI[d1 - 1], _DICE_EMOJI[d2 - 1]
|
||||||
|
double = d1 == d2
|
||||||
|
self.tries += 1
|
||||||
|
tries_left = self.MAX_TRIES - self.tries
|
||||||
|
|
||||||
|
await asyncio.sleep(1.5)
|
||||||
|
self._rolling = False
|
||||||
|
|
||||||
|
if double:
|
||||||
|
await economy.do_jail_free(self.user_id)
|
||||||
|
self.stop()
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.TITLE["jailbreak_free"],
|
||||||
|
description=S.JAILBREAK_UI["free_desc"].format(d1=e1, d2=e2),
|
||||||
|
color=0x57F287,
|
||||||
|
)
|
||||||
|
await interaction.edit_original_response(embed=embed, view=self)
|
||||||
|
elif tries_left == 0:
|
||||||
|
self.stop()
|
||||||
|
user_data = await economy.get_user(self.user_id)
|
||||||
|
bal = user_data["balance"]
|
||||||
|
if bal >= economy.MIN_BAIL:
|
||||||
|
min_fine = max(economy.MIN_BAIL, int(bal * 0.20))
|
||||||
|
max_fine = max(economy.MIN_BAIL, int(bal * 0.30))
|
||||||
|
desc = S.JAILBREAK_UI["fail_bail_offer"].format(
|
||||||
|
d1=e1,
|
||||||
|
d2=e2,
|
||||||
|
min=coin(min_fine),
|
||||||
|
max=coin(max_fine),
|
||||||
|
bal=coin(bal),
|
||||||
|
)
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.TITLE["jailbreak_fail"],
|
||||||
|
description=desc,
|
||||||
|
color=0xED4245,
|
||||||
|
)
|
||||||
|
await interaction.edit_original_response(embed=embed, view=BailView(self.user_id))
|
||||||
|
else:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.TITLE["jailbreak_fail"],
|
||||||
|
description=S.JAILBREAK_UI["fail_broke_desc"].format(
|
||||||
|
d1=e1,
|
||||||
|
d2=e2,
|
||||||
|
balance=coin(bal),
|
||||||
|
),
|
||||||
|
color=0xED4245,
|
||||||
|
)
|
||||||
|
await interaction.edit_original_response(embed=embed, view=None)
|
||||||
|
else:
|
||||||
|
self._add_roll_btn()
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.TITLE["jailbreak_miss"].format(tries=self.tries, max=self.MAX_TRIES),
|
||||||
|
description=S.JAILBREAK_UI["miss_desc"].format(d1=e1, d2=e2, left=tries_left),
|
||||||
|
color=0xF4C430,
|
||||||
|
)
|
||||||
|
await interaction.edit_original_response(embed=embed, view=self)
|
||||||
|
|
||||||
|
class BailView(discord.ui.View):
|
||||||
|
def __init__(self, user_id: int):
|
||||||
|
super().__init__(timeout=60)
|
||||||
|
self.user_id = user_id
|
||||||
|
|
||||||
|
@discord.ui.button(label=S.JAILBREAK_UI["bail_btn"], style=discord.ButtonStyle.danger)
|
||||||
|
async def pay_bail(self, interaction: discord.Interaction, _: discord.ui.Button):
|
||||||
|
if interaction.user.id != self.user_id:
|
||||||
|
await interaction.response.send_message(S.ERR["not_your_game"], ephemeral=True)
|
||||||
|
return
|
||||||
|
res = await economy.do_bail(self.user_id)
|
||||||
|
self.clear_items()
|
||||||
|
self.stop()
|
||||||
|
if not res["ok"] and res.get("reason") == "broke":
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.TITLE["jailbreak_bail"],
|
||||||
|
description=S.JAILBREAK_UI["bail_broke_desc"].format(
|
||||||
|
min=coin(economy.MIN_BAIL),
|
||||||
|
balance=coin(res["balance"]),
|
||||||
|
),
|
||||||
|
color=0xED4245,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.TITLE["jailbreak_bail"],
|
||||||
|
description=S.JAILBREAK_UI["bail_paid_desc"].format(
|
||||||
|
fine=coin(res["fine"]),
|
||||||
|
balance=coin(res["balance"]),
|
||||||
|
),
|
||||||
|
color=0x57F287,
|
||||||
|
)
|
||||||
|
await interaction.response.edit_message(embed=embed, view=self)
|
||||||
|
|
||||||
|
@tree.command(name="jailbreak", description=S.CMD["jailbreak"])
|
||||||
|
async def cmd_jailbreak(interaction: discord.Interaction):
|
||||||
|
user_data = await economy.get_user(interaction.user.id)
|
||||||
|
remaining = economy._is_jailed(user_data)
|
||||||
|
if not remaining:
|
||||||
|
await interaction.response.send_message(S.ERR["not_jailed"], ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
if user_data.get("jailbreak_used", False):
|
||||||
|
bal = user_data["balance"]
|
||||||
|
min_fine = max(economy.MIN_BAIL, int(bal * 0.20))
|
||||||
|
max_fine = max(economy.MIN_BAIL, int(bal * 0.30))
|
||||||
|
if bal >= economy.MIN_BAIL:
|
||||||
|
desc = S.JAILBREAK_UI["already_bail"].format(
|
||||||
|
min=coin(min_fine),
|
||||||
|
max=coin(max_fine),
|
||||||
|
bal=coin(bal),
|
||||||
|
ts=cd_ts(remaining),
|
||||||
|
)
|
||||||
|
await interaction.response.send_message(
|
||||||
|
embed=discord.Embed(
|
||||||
|
title=S.TITLE["jailbreak_bail"],
|
||||||
|
description=desc,
|
||||||
|
color=0xED4245,
|
||||||
|
),
|
||||||
|
view=BailView(interaction.user.id),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
desc = S.JAILBREAK_UI["already_broke"].format(
|
||||||
|
min=coin(economy.MIN_BAIL),
|
||||||
|
bal=coin(bal),
|
||||||
|
ts=cd_ts(remaining),
|
||||||
|
)
|
||||||
|
await interaction.response.send_message(
|
||||||
|
embed=discord.Embed(
|
||||||
|
title=S.TITLE["jailbreak_bail"],
|
||||||
|
description=desc,
|
||||||
|
color=0xED4245,
|
||||||
|
),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
await economy.set_jailbreak_used(interaction.user.id)
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.TITLE["jailbreak"],
|
||||||
|
description=S.JAILBREAK_UI["intro_desc"].format(
|
||||||
|
ts=cd_ts(remaining),
|
||||||
|
tries=JailbreakView.MAX_TRIES,
|
||||||
|
),
|
||||||
|
color=0xF4C430,
|
||||||
|
)
|
||||||
|
await interaction.response.send_message(
|
||||||
|
embed=embed,
|
||||||
|
view=JailbreakView(interaction.user.id),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@tree.command(name="give", description=S.CMD["give"])
|
||||||
|
@app_commands.describe(kasutaja=S.OPT["give_kasutaja"], summa=S.OPT["give_summa"])
|
||||||
|
async def cmd_give(interaction: discord.Interaction, kasutaja: discord.Member, summa: str):
|
||||||
|
data = await economy.get_user(interaction.user.id)
|
||||||
|
summa_int, err = parse_amount(summa, data["balance"])
|
||||||
|
if err:
|
||||||
|
await interaction.response.send_message(err, ephemeral=True)
|
||||||
|
return
|
||||||
|
if summa_int is None or summa_int <= 0:
|
||||||
|
await interaction.response.send_message(S.ERR["positive_amount"], ephemeral=True)
|
||||||
|
return
|
||||||
|
if kasutaja.id == interaction.user.id:
|
||||||
|
await interaction.response.send_message(S.ERR["give_self"], ephemeral=True)
|
||||||
|
return
|
||||||
|
if kasutaja.bot:
|
||||||
|
await interaction.response.send_message(S.ERR["give_bot"], ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
res = await economy.do_give(interaction.user.id, kasutaja.id, summa_int)
|
||||||
|
if not res["ok"]:
|
||||||
|
if res["reason"] == "banned":
|
||||||
|
await interaction.response.send_message(S.MSG_BANNED, ephemeral=True)
|
||||||
|
elif res["reason"] == "jailed":
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.ERR["give_jailed"].format(ts=cd_ts(res["remaining"])),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.ERR["broke"].format(bal=coin(data["balance"])),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=f"{economy.COIN} {S.TITLE['give']}",
|
||||||
|
description=S.GIVE_UI["desc"].format(
|
||||||
|
giver=interaction.user.display_name,
|
||||||
|
amount=coin(summa_int),
|
||||||
|
receiver=kasutaja.display_name,
|
||||||
|
),
|
||||||
|
color=0xF4C430,
|
||||||
|
)
|
||||||
|
await interaction.response.send_message(embed=embed)
|
||||||
|
|
||||||
|
class LeaderboardView(discord.ui.View):
|
||||||
|
PER_PAGE = 10
|
||||||
|
|
||||||
|
def __init__(self, data: dict, guild: discord.Guild | None, bot_user: discord.ClientUser | None):
|
||||||
|
super().__init__(timeout=120)
|
||||||
|
self.data = data
|
||||||
|
self.guild = guild
|
||||||
|
self.bot_user = bot_user
|
||||||
|
self.page = 0
|
||||||
|
self.mode = "coins"
|
||||||
|
self.max_page = 0
|
||||||
|
self._update_buttons()
|
||||||
|
|
||||||
|
def _current_list(self) -> list:
|
||||||
|
return self.data.get(self.mode, [])
|
||||||
|
|
||||||
|
def _update_buttons(self):
|
||||||
|
current = self._current_list()
|
||||||
|
self.max_page = max(0, (len(current) - 1) // self.PER_PAGE) if current else 0
|
||||||
|
self.prev_btn.disabled = self.page == 0
|
||||||
|
self.next_btn.disabled = self.page >= self.max_page
|
||||||
|
for m, btn in [
|
||||||
|
("coins", self.coins_btn),
|
||||||
|
("exp", self.exp_btn),
|
||||||
|
("season", self.season_btn),
|
||||||
|
("prestige", self.prestige_btn),
|
||||||
|
("wagered", self.wagered_btn),
|
||||||
|
("fish", self.fish_btn),
|
||||||
|
]:
|
||||||
|
btn.style = discord.ButtonStyle.primary if m == self.mode else discord.ButtonStyle.secondary
|
||||||
|
|
||||||
|
def _name(self, uid: str, highlight_uid: int | None = None) -> str:
|
||||||
|
if self.guild:
|
||||||
|
member = self.guild.get_member(int(uid))
|
||||||
|
name = member.display_name if member else S.LEADERBOARD_UI["unknown_user"].format(uid=uid)
|
||||||
|
else:
|
||||||
|
name = S.LEADERBOARD_UI["unknown_user"].format(uid=uid)
|
||||||
|
if highlight_uid and int(uid) == highlight_uid:
|
||||||
|
name = f"**› {name} ‹**"
|
||||||
|
return name
|
||||||
|
|
||||||
|
def _make_embed(self, highlight_uid: int | None = None) -> discord.Embed:
|
||||||
|
title_map = {
|
||||||
|
"coins": f"{economy.COIN} {S.TITLE['leaderboard_coins']}",
|
||||||
|
"exp": S.TITLE["leaderboard_exp"],
|
||||||
|
"season": S.TITLE["leaderboard_season"],
|
||||||
|
"prestige": S.TITLE["leaderboard_prestige"],
|
||||||
|
"wagered": S.TITLE["leaderboard_wagered"],
|
||||||
|
"fish": S.TITLE["leaderboard_fish"],
|
||||||
|
}
|
||||||
|
color_map = {"coins": 0xF4C430, "wagered": 0xED4245, "fish": 0x57F287}
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=title_map.get(self.mode, "Edetabel"),
|
||||||
|
color=color_map.get(self.mode, 0x5865F2),
|
||||||
|
)
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
if self.mode == "coins" and self.page == 0 and self.data.get("house_entry"):
|
||||||
|
_, bal = self.data["house_entry"]
|
||||||
|
house_name = self.bot_user.display_name if self.bot_user else S.LEADERBOARD_UI["house_default_name"]
|
||||||
|
lines.append(S.LEADERBOARD_UI["house_entry"].format(name=house_name, balance=coin(bal)))
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
start = self.page * self.PER_PAGE
|
||||||
|
medals = ["🥇", "🥈", "🥉"]
|
||||||
|
current = self._current_list()
|
||||||
|
slice_ = current[start : start + self.PER_PAGE]
|
||||||
|
|
||||||
|
if not slice_:
|
||||||
|
lines.append(S.LEADERBOARD_UI["no_entries"])
|
||||||
|
else:
|
||||||
|
for i, entry in enumerate(slice_):
|
||||||
|
rank = start + i
|
||||||
|
uid = entry[0]
|
||||||
|
prefix = medals[rank] if rank < 3 else f"**{rank + 1}.**"
|
||||||
|
name = self._name(uid, highlight_uid)
|
||||||
|
if self.mode == "coins":
|
||||||
|
lines.append(f"{prefix} {name} - {coin(entry[1])}")
|
||||||
|
elif self.mode == "exp":
|
||||||
|
lines.append(
|
||||||
|
S.LEADERBOARD_UI["exp_entry"].format(
|
||||||
|
prefix=prefix,
|
||||||
|
name=name,
|
||||||
|
exp=entry[1],
|
||||||
|
level=entry[2],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif self.mode == "season":
|
||||||
|
lines.append(
|
||||||
|
S.LEADERBOARD_UI["season_entry"].format(
|
||||||
|
prefix=prefix,
|
||||||
|
name=name,
|
||||||
|
exp=entry[1],
|
||||||
|
prestige=entry[2],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif self.mode == "prestige":
|
||||||
|
lines.append(
|
||||||
|
S.LEADERBOARD_UI["prestige_entry"].format(
|
||||||
|
prefix=prefix,
|
||||||
|
name=name,
|
||||||
|
prestige=entry[1],
|
||||||
|
pp=entry[2],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif self.mode == "wagered":
|
||||||
|
lines.append(
|
||||||
|
S.LEADERBOARD_UI["wagered_entry"].format(
|
||||||
|
prefix=prefix,
|
||||||
|
name=name,
|
||||||
|
wagered=coin(entry[1]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif self.mode == "fish":
|
||||||
|
lines.append(
|
||||||
|
S.LEADERBOARD_UI["fish_entry"].format(
|
||||||
|
prefix=prefix,
|
||||||
|
name=name,
|
||||||
|
caught=entry[1],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
total = self.max_page + 1
|
||||||
|
embed.description = "\n".join(lines)
|
||||||
|
embed.set_footer(
|
||||||
|
text=S.LEADERBOARD_UI["footer"].format(
|
||||||
|
page=self.page + 1,
|
||||||
|
total=total,
|
||||||
|
count=len(current),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return embed
|
||||||
|
|
||||||
|
@discord.ui.button(label="◄", style=discord.ButtonStyle.secondary, row=0)
|
||||||
|
async def prev_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
|
self.page -= 1
|
||||||
|
self._update_buttons()
|
||||||
|
await interaction.response.edit_message(embed=self._make_embed(), view=self)
|
||||||
|
|
||||||
|
@discord.ui.button(label="►", style=discord.ButtonStyle.secondary, row=0)
|
||||||
|
async def next_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
|
self.page += 1
|
||||||
|
self._update_buttons()
|
||||||
|
await interaction.response.edit_message(embed=self._make_embed(), view=self)
|
||||||
|
|
||||||
|
@discord.ui.button(label=S.LEADERBOARD_UI["btn_find_me"], style=discord.ButtonStyle.secondary, row=0)
|
||||||
|
async def find_me_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
|
uid = interaction.user.id
|
||||||
|
for i, entry in enumerate(self._current_list()):
|
||||||
|
if int(entry[0]) == uid:
|
||||||
|
self.page = i // self.PER_PAGE
|
||||||
|
self._update_buttons()
|
||||||
|
await interaction.response.edit_message(
|
||||||
|
embed=self._make_embed(highlight_uid=uid),
|
||||||
|
view=self,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
await interaction.response.send_message(S.ERR["not_in_leaderboard"], ephemeral=True)
|
||||||
|
|
||||||
|
@discord.ui.button(label=S.LEADERBOARD_UI["btn_coins"], style=discord.ButtonStyle.primary, row=1)
|
||||||
|
async def coins_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
|
self.mode = "coins"
|
||||||
|
self.page = 0
|
||||||
|
self._update_buttons()
|
||||||
|
await interaction.response.edit_message(embed=self._make_embed(), view=self)
|
||||||
|
|
||||||
|
@discord.ui.button(label=S.LEADERBOARD_UI["btn_exp"], style=discord.ButtonStyle.secondary, row=1)
|
||||||
|
async def exp_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
|
self.mode = "exp"
|
||||||
|
self.page = 0
|
||||||
|
self._update_buttons()
|
||||||
|
await interaction.response.edit_message(embed=self._make_embed(), view=self)
|
||||||
|
|
||||||
|
@discord.ui.button(label=S.LEADERBOARD_UI["btn_season"], style=discord.ButtonStyle.secondary, row=1)
|
||||||
|
async def season_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
|
self.mode = "season"
|
||||||
|
self.page = 0
|
||||||
|
self._update_buttons()
|
||||||
|
await interaction.response.edit_message(embed=self._make_embed(), view=self)
|
||||||
|
|
||||||
|
@discord.ui.button(label=S.LEADERBOARD_UI["btn_prestige"], style=discord.ButtonStyle.secondary, row=1)
|
||||||
|
async def prestige_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
|
self.mode = "prestige"
|
||||||
|
self.page = 0
|
||||||
|
self._update_buttons()
|
||||||
|
await interaction.response.edit_message(embed=self._make_embed(), view=self)
|
||||||
|
|
||||||
|
@discord.ui.button(label=S.LEADERBOARD_UI["btn_wagered"], style=discord.ButtonStyle.secondary, row=1)
|
||||||
|
async def wagered_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
|
self.mode = "wagered"
|
||||||
|
self.page = 0
|
||||||
|
self._update_buttons()
|
||||||
|
await interaction.response.edit_message(embed=self._make_embed(), view=self)
|
||||||
|
|
||||||
|
@discord.ui.button(label=S.LEADERBOARD_UI["btn_fish"], style=discord.ButtonStyle.secondary, row=2)
|
||||||
|
async def fish_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
|
self.mode = "fish"
|
||||||
|
self.page = 0
|
||||||
|
self._update_buttons()
|
||||||
|
await interaction.response.edit_message(embed=self._make_embed(), view=self)
|
||||||
|
|
||||||
|
async def on_timeout(self):
|
||||||
|
for child in self.children:
|
||||||
|
child.disabled = True
|
||||||
|
|
||||||
|
@tree.command(name="leaderboard", description=S.CMD["leaderboard"])
|
||||||
|
async def cmd_leaderboard(interaction: discord.Interaction):
|
||||||
|
await interaction.response.defer()
|
||||||
|
coins_raw, exp_raw, season_raw, prestige_raw, wagered_raw, fish_raw = await asyncio.gather(
|
||||||
|
economy.get_leaderboard(top_n=None),
|
||||||
|
economy.get_leaderboard_exp(top_n=None),
|
||||||
|
economy.get_leaderboard_season_exp(top_n=None),
|
||||||
|
economy.get_leaderboard_prestige(top_n=None),
|
||||||
|
economy.get_leaderboard_wagered(top_n=None),
|
||||||
|
economy.get_leaderboard_fish(top_n=None),
|
||||||
|
)
|
||||||
|
|
||||||
|
house_entry = None
|
||||||
|
regular = []
|
||||||
|
bot_id = bot.user.id if bot.user else None
|
||||||
|
for uid, bal in coins_raw:
|
||||||
|
if bot_id and int(uid) == bot_id:
|
||||||
|
house_entry = (uid, bal)
|
||||||
|
else:
|
||||||
|
regular.append((uid, bal))
|
||||||
|
|
||||||
|
def _no_bot(entries: list) -> list:
|
||||||
|
return [e for e in entries if not (bot_id and int(e[0]) == bot_id)]
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"coins": regular,
|
||||||
|
"exp": _no_bot(exp_raw),
|
||||||
|
"season": _no_bot(season_raw),
|
||||||
|
"prestige": _no_bot(prestige_raw),
|
||||||
|
"wagered": _no_bot(wagered_raw),
|
||||||
|
"fish": _no_bot(fish_raw),
|
||||||
|
"house_entry": house_entry,
|
||||||
|
}
|
||||||
|
view = LeaderboardView(data, interaction.guild, bot.user)
|
||||||
|
await interaction.followup.send(embed=view._make_embed(), view=view)
|
||||||
|
|
||||||
|
def _shop_embed(tier: int, user_data: dict) -> discord.Embed:
|
||||||
|
owned = set(user_data.get("items", []))
|
||||||
|
item_uses = user_data.get("item_uses", {})
|
||||||
|
tier_names = {1: S.SHOP_UI["tier_1"], 2: S.SHOP_UI["tier_2"], 3: S.SHOP_UI["tier_3"]}
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=f"{economy.COIN} TipiBOTi pood · {tier_names[tier]}",
|
||||||
|
description=S.SHOP_UI["desc"].format(bal=coin(user_data["balance"])),
|
||||||
|
color=[0x57F287, 0xF4C430, 0xED4245][tier - 1],
|
||||||
|
)
|
||||||
|
for item_id in sorted(economy.SHOP_TIERS[tier], key=lambda k: economy.SHOP[k]["cost"]):
|
||||||
|
item = economy.SHOP[item_id]
|
||||||
|
anticheat_uses = item_uses.get("anticheat", 0) if item_id == "anticheat" else 0
|
||||||
|
min_lvl = economy.SHOP_LEVEL_REQ.get(item_id, 0)
|
||||||
|
user_lvl = economy.get_level(user_data.get("exp", 0))
|
||||||
|
if item_id in owned and not (item_id == "anticheat" and anticheat_uses <= 0):
|
||||||
|
if item_id == "anticheat":
|
||||||
|
key = "owned_uses_1" if anticheat_uses == 1 else "owned_uses_n"
|
||||||
|
status = S.SHOP_UI[key].format(uses=anticheat_uses)
|
||||||
|
else:
|
||||||
|
status = S.SHOP_UI["owned"]
|
||||||
|
elif min_lvl > 0 and user_lvl < min_lvl:
|
||||||
|
status = S.SHOP_UI["locked"].format(min_lvl=min_lvl, user_lvl=user_lvl)
|
||||||
|
else:
|
||||||
|
status = f"{item['cost']} {economy.COIN}"
|
||||||
|
embed.add_field(
|
||||||
|
name=f"{item['emoji']} {item['name']} · {status}",
|
||||||
|
value=item["description"],
|
||||||
|
inline=False,
|
||||||
|
)
|
||||||
|
return embed
|
||||||
|
|
||||||
|
class ShopView(discord.ui.View):
|
||||||
|
def __init__(self, user_data: dict, tier: int = 1):
|
||||||
|
super().__init__(timeout=120)
|
||||||
|
self._user_data = user_data
|
||||||
|
self._tier = tier
|
||||||
|
self._update_buttons()
|
||||||
|
|
||||||
|
def _update_buttons(self):
|
||||||
|
self.clear_items()
|
||||||
|
for t, label in [(1, S.SHOP_BTN[1]), (2, S.SHOP_BTN[2]), (3, S.SHOP_BTN[3])]:
|
||||||
|
btn = discord.ui.Button(
|
||||||
|
label=label,
|
||||||
|
style=discord.ButtonStyle.primary if t == self._tier else discord.ButtonStyle.secondary,
|
||||||
|
custom_id=f"shop_tier_{t}",
|
||||||
|
)
|
||||||
|
btn.callback = self._make_callback(t)
|
||||||
|
self.add_item(btn)
|
||||||
|
|
||||||
|
def _make_callback(self, tier: int):
|
||||||
|
async def callback(interaction: discord.Interaction):
|
||||||
|
self._tier = tier
|
||||||
|
self._update_buttons()
|
||||||
|
self._user_data = await economy.get_user(interaction.user.id)
|
||||||
|
await interaction.response.edit_message(
|
||||||
|
embed=_shop_embed(self._tier, self._user_data),
|
||||||
|
view=self,
|
||||||
|
)
|
||||||
|
|
||||||
|
return callback
|
||||||
|
|
||||||
|
@tree.command(name="shop", description=S.CMD["shop"])
|
||||||
|
async def cmd_shop(interaction: discord.Interaction):
|
||||||
|
data = await economy.get_user(interaction.user.id)
|
||||||
|
await interaction.response.send_message(
|
||||||
|
embed=_shop_embed(1, data),
|
||||||
|
view=ShopView(data, tier=1),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@tree.command(name="buy", description=S.CMD["buy"])
|
||||||
|
@app_commands.describe(ese=S.OPT["buy_ese"])
|
||||||
|
@app_commands.choices(
|
||||||
|
ese=[
|
||||||
|
app_commands.Choice(name=f"{v['name']} ({v['cost']} TipiCOINi)", value=k)
|
||||||
|
for k, v in economy.SHOP.items()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
async def cmd_buy(interaction: discord.Interaction, ese: app_commands.Choice[str]):
|
||||||
|
res = await economy.do_buy(interaction.user.id, ese.value)
|
||||||
|
if not res["ok"]:
|
||||||
|
if res["reason"] == "banned":
|
||||||
|
await interaction.response.send_message(S.MSG_BANNED, ephemeral=True)
|
||||||
|
elif res["reason"] == "owned":
|
||||||
|
await interaction.response.send_message(S.ERR["item_owned"], ephemeral=True)
|
||||||
|
elif res["reason"] == "level_required":
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.ERR["item_level_req"].format(
|
||||||
|
min_level=res["min_level"],
|
||||||
|
user_level=res["user_level"],
|
||||||
|
),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
elif res["reason"] == "insufficient":
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.ERR["broke_need"].format(need=coin(res["need"])),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await interaction.response.send_message(S.ERR["item_not_found"], ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
item = res["item"]
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.BUY_UI["title"].format(emoji=item["emoji"], name=item["name"]),
|
||||||
|
description=S.BUY_UI["desc"].format(
|
||||||
|
description=item["description"],
|
||||||
|
balance=coin(res["balance"]),
|
||||||
|
),
|
||||||
|
color=0x57F287,
|
||||||
|
)
|
||||||
|
await interaction.response.send_message(embed=embed)
|
||||||
380
economy_fish_commands.py
Normal file
380
economy_fish_commands.py
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import random
|
||||||
|
from collections.abc import Awaitable, Callable, MutableSet
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from discord import app_commands
|
||||||
|
|
||||||
|
import economy
|
||||||
|
import strings as S
|
||||||
|
|
||||||
|
|
||||||
|
def register_economy_fish_commands(
|
||||||
|
tree: app_commands.CommandTree,
|
||||||
|
coin: Callable[[int], str],
|
||||||
|
cd_ts: Callable,
|
||||||
|
check_cmd_rate: Callable[[discord.Interaction], Awaitable[bool]],
|
||||||
|
maybe_remind: Callable[[int, str], Awaitable[None]],
|
||||||
|
award_exp: Callable[[discord.Interaction, int], Awaitable[None]],
|
||||||
|
active_games: MutableSet[int],
|
||||||
|
) -> None:
|
||||||
|
class FishCatchView(discord.ui.View):
|
||||||
|
"""Shown after a successful pull - lets user sell or keep the fish."""
|
||||||
|
|
||||||
|
def __init__(self, user_id: int, res: dict, fish_id: str, weight: int):
|
||||||
|
super().__init__(timeout=60)
|
||||||
|
self.user_id = user_id
|
||||||
|
self._res = res
|
||||||
|
self._fish_id = fish_id
|
||||||
|
self._weight = weight
|
||||||
|
self._done = False
|
||||||
|
|
||||||
|
def _catch_embed(self, color: int = 0x57F287) -> discord.Embed:
|
||||||
|
rarity = economy.FISH_CATALOGUE[self._fish_id]["rarity"]
|
||||||
|
emoji = S.FISH_RARITY_EMOJI[rarity]
|
||||||
|
fish_name = S.FISH_NAMES[self._fish_id]
|
||||||
|
desc = S.FISH_UI["catch_desc"].format(
|
||||||
|
name=fish_name,
|
||||||
|
weight=self._weight,
|
||||||
|
exp=self._res["exp"],
|
||||||
|
value=coin(self._res["value"]),
|
||||||
|
)
|
||||||
|
if self._res.get("is_new"):
|
||||||
|
desc += S.FISH_UI["new_fish"]
|
||||||
|
return discord.Embed(title=f"{emoji} {fish_name}!", description=desc, color=color)
|
||||||
|
|
||||||
|
@discord.ui.button(label="", style=discord.ButtonStyle.success)
|
||||||
|
async def sell_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
|
if interaction.user.id != self.user_id:
|
||||||
|
await interaction.response.send_message(S.ERR["not_your_game"], ephemeral=True)
|
||||||
|
return
|
||||||
|
if self._done:
|
||||||
|
await interaction.response.defer()
|
||||||
|
return
|
||||||
|
self._done = True
|
||||||
|
self.stop()
|
||||||
|
for child in self.children:
|
||||||
|
child.disabled = True
|
||||||
|
sell_res = await economy.do_fish_sell(self.user_id, [-1])
|
||||||
|
rarity = economy.FISH_CATALOGUE[self._fish_id]["rarity"]
|
||||||
|
emoji = S.FISH_RARITY_EMOJI[rarity]
|
||||||
|
fish_name = S.FISH_NAMES[self._fish_id]
|
||||||
|
desc = S.FISH_UI["catch_sold"].format(
|
||||||
|
name=fish_name,
|
||||||
|
weight=self._weight,
|
||||||
|
coins=coin(sell_res["coins"]),
|
||||||
|
exp=self._res["exp"],
|
||||||
|
balance=coin(sell_res["balance"]),
|
||||||
|
)
|
||||||
|
if self._res.get("is_new"):
|
||||||
|
desc += S.FISH_UI["new_fish"]
|
||||||
|
embed = discord.Embed(title=f"{emoji} {fish_name}!", description=desc, color=0x57F287)
|
||||||
|
await interaction.response.edit_message(embed=embed, view=self)
|
||||||
|
|
||||||
|
@discord.ui.button(label="", style=discord.ButtonStyle.secondary)
|
||||||
|
async def keep_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
|
if interaction.user.id != self.user_id:
|
||||||
|
await interaction.response.send_message(S.ERR["not_your_game"], ephemeral=True)
|
||||||
|
return
|
||||||
|
if self._done:
|
||||||
|
await interaction.response.defer()
|
||||||
|
return
|
||||||
|
self._done = True
|
||||||
|
self.stop()
|
||||||
|
for child in self.children:
|
||||||
|
child.disabled = True
|
||||||
|
rarity = economy.FISH_CATALOGUE[self._fish_id]["rarity"]
|
||||||
|
emoji = S.FISH_RARITY_EMOJI[rarity]
|
||||||
|
fish_name = S.FISH_NAMES[self._fish_id]
|
||||||
|
desc = S.FISH_UI["catch_kept"].format(
|
||||||
|
name=fish_name,
|
||||||
|
weight=self._weight,
|
||||||
|
exp=self._res["exp"],
|
||||||
|
)
|
||||||
|
if self._res.get("is_new"):
|
||||||
|
desc += S.FISH_UI["new_fish"]
|
||||||
|
embed = discord.Embed(title=f"{emoji} {fish_name}!", description=desc, color=0x5865F2)
|
||||||
|
await interaction.response.edit_message(embed=embed, view=self)
|
||||||
|
|
||||||
|
async def on_timeout(self):
|
||||||
|
for child in self.children:
|
||||||
|
child.disabled = True
|
||||||
|
|
||||||
|
class FishingView(discord.ui.View):
|
||||||
|
BITE_WINDOW = 2.0
|
||||||
|
|
||||||
|
def __init__(self, user_id: int, fish_id: str, weight: int):
|
||||||
|
super().__init__(timeout=40)
|
||||||
|
self.user_id = user_id
|
||||||
|
self._fish_id = fish_id
|
||||||
|
self._weight = weight
|
||||||
|
self._clicked = False
|
||||||
|
self._bite_active = False
|
||||||
|
self._msg: discord.Message | None = None
|
||||||
|
|
||||||
|
self.pull_btn = discord.ui.Button(
|
||||||
|
label=S.FISH_UI["btn_wait"],
|
||||||
|
style=discord.ButtonStyle.secondary,
|
||||||
|
disabled=True,
|
||||||
|
)
|
||||||
|
self.pull_btn.callback = self._pull
|
||||||
|
self.add_item(self.pull_btn)
|
||||||
|
|
||||||
|
async def start(self, msg: discord.Message) -> None:
|
||||||
|
self._msg = msg
|
||||||
|
wait = random.uniform(5, 15)
|
||||||
|
await asyncio.sleep(wait)
|
||||||
|
if self._clicked or self.is_finished():
|
||||||
|
return
|
||||||
|
self._bite_active = True
|
||||||
|
self.pull_btn.disabled = False
|
||||||
|
self.pull_btn.label = S.FISH_UI["btn_bite"]
|
||||||
|
self.pull_btn.style = discord.ButtonStyle.success
|
||||||
|
try:
|
||||||
|
await msg.edit(
|
||||||
|
embed=discord.Embed(
|
||||||
|
title=S.TITLE["fish_bite"],
|
||||||
|
description=S.FISH_UI["bite_desc"],
|
||||||
|
color=0xED4245,
|
||||||
|
),
|
||||||
|
view=self,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
await asyncio.sleep(self.BITE_WINDOW)
|
||||||
|
if not self._clicked:
|
||||||
|
self.stop()
|
||||||
|
active_games.discard(self.user_id)
|
||||||
|
self.pull_btn.disabled = True
|
||||||
|
try:
|
||||||
|
await msg.edit(
|
||||||
|
embed=discord.Embed(
|
||||||
|
title=S.TITLE["fish_escape"],
|
||||||
|
description=S.FISH_UI["escape_desc"],
|
||||||
|
color=0x99AAB5,
|
||||||
|
),
|
||||||
|
view=self,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _pull(self, interaction: discord.Interaction) -> None:
|
||||||
|
if interaction.user.id != self.user_id:
|
||||||
|
await interaction.response.send_message(S.ERR["not_your_game"], ephemeral=True)
|
||||||
|
return
|
||||||
|
if not self._bite_active:
|
||||||
|
await interaction.response.send_message(S.FISH_UI["too_early"], ephemeral=True)
|
||||||
|
return
|
||||||
|
self._clicked = True
|
||||||
|
self.stop()
|
||||||
|
active_games.discard(self.user_id)
|
||||||
|
self.pull_btn.disabled = True
|
||||||
|
await interaction.response.defer()
|
||||||
|
|
||||||
|
if self._fish_id == "junk":
|
||||||
|
junk_text = random.choice(S.FISH_JUNK_LINES)
|
||||||
|
user_data = await economy.get_user(interaction.user.id)
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.TITLE["fish_junk"],
|
||||||
|
description=S.FISH_UI["junk_desc"].format(
|
||||||
|
text=junk_text,
|
||||||
|
balance=coin(user_data.get("balance", 0)),
|
||||||
|
),
|
||||||
|
color=0x99AAB5,
|
||||||
|
)
|
||||||
|
await self._msg.edit(embed=embed, view=self)
|
||||||
|
return
|
||||||
|
|
||||||
|
res = await economy.do_fish_resolve(self.user_id, self._fish_id, self._weight)
|
||||||
|
if not res["ok"]:
|
||||||
|
await self._msg.edit(
|
||||||
|
embed=discord.Embed(title=S.ERR["generic_error"], color=0xED4245),
|
||||||
|
view=self,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
catch_view = FishCatchView(self.user_id, res, self._fish_id, self._weight)
|
||||||
|
catch_view.sell_btn.label = S.FISH_UI["btn_sell"]
|
||||||
|
catch_view.keep_btn.label = S.FISH_UI["btn_keep"]
|
||||||
|
await self._msg.edit(embed=catch_view._catch_embed(), view=catch_view)
|
||||||
|
if res.get("exp", 0) > 0:
|
||||||
|
asyncio.create_task(award_exp(interaction, res["exp"]))
|
||||||
|
|
||||||
|
async def on_timeout(self):
|
||||||
|
for child in self.children:
|
||||||
|
child.disabled = True
|
||||||
|
active_games.discard(self.user_id)
|
||||||
|
|
||||||
|
@tree.command(name="fish", description=S.CMD["fish"])
|
||||||
|
@app_commands.guild_only()
|
||||||
|
async def cmd_fish(interaction: discord.Interaction):
|
||||||
|
if await check_cmd_rate(interaction):
|
||||||
|
return
|
||||||
|
if interaction.user.id in active_games:
|
||||||
|
await interaction.response.send_message(S.ERR["game_in_progress"], ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
res = await economy.do_fish_start(interaction.user.id)
|
||||||
|
if not res["ok"]:
|
||||||
|
if res["reason"] == "banned":
|
||||||
|
await interaction.response.send_message(S.MSG_BANNED, ephemeral=True)
|
||||||
|
elif res["reason"] == "cooldown":
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.CD_MSG["fish"].format(ts=cd_ts(res["remaining"])),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
elif res["reason"] == "jailed":
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.CD_MSG["jailed"].format(ts=cd_ts(res["remaining"])),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
user_data = await economy.get_user(interaction.user.id)
|
||||||
|
rarity_bump = "kalavork" in user_data.get("items", [])
|
||||||
|
has_echolood = "echolood" in user_data.get("items", [])
|
||||||
|
fish_id, weight = economy.roll_fish(rarity_bump=rarity_bump)
|
||||||
|
|
||||||
|
active_games.add(interaction.user.id)
|
||||||
|
view = FishingView(interaction.user.id, fish_id, weight)
|
||||||
|
if has_echolood:
|
||||||
|
view.BITE_WINDOW = 3.0
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.TITLE["fish_cast"],
|
||||||
|
description=S.FISH_UI["cast_desc"],
|
||||||
|
color=0x5865F2,
|
||||||
|
)
|
||||||
|
await interaction.response.send_message(embed=embed, view=view)
|
||||||
|
msg = await interaction.original_response()
|
||||||
|
asyncio.create_task(view.start(msg))
|
||||||
|
asyncio.create_task(maybe_remind(interaction.user.id, "fish"))
|
||||||
|
|
||||||
|
@tree.command(name="fishbook", description=S.CMD["fishbook"])
|
||||||
|
@app_commands.describe(kasutaja=S.OPT["fishbook_kasutaja"])
|
||||||
|
async def cmd_fishbook(interaction: discord.Interaction, kasutaja: discord.Member | None = None):
|
||||||
|
target = kasutaja or interaction.user
|
||||||
|
res = await economy.do_fishbook(target.id)
|
||||||
|
book: dict = res["book"]
|
||||||
|
total = res["total_species"]
|
||||||
|
caught_count = res["unique_caught"]
|
||||||
|
|
||||||
|
if not book:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.TITLE["fishbook"],
|
||||||
|
description=S.FISH_UI["book_empty"],
|
||||||
|
color=0x5865F2,
|
||||||
|
)
|
||||||
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
inv_counts: dict = res.get("inv_counts", {})
|
||||||
|
all_fish = list(economy.FISH_CATALOGUE.items())
|
||||||
|
lines = [S.FISH_UI["book_caught"].format(caught=caught_count, total=total)]
|
||||||
|
for fish_id, fish_data in all_fish:
|
||||||
|
rarity = fish_data["rarity"]
|
||||||
|
emoji = S.FISH_RARITY_EMOJI[rarity]
|
||||||
|
rarity_name = S.FISH_RARITY_NAMES[rarity]
|
||||||
|
count = book.get(fish_id, 0)
|
||||||
|
if count > 0:
|
||||||
|
n_inv = inv_counts.get(fish_id, 0)
|
||||||
|
inv_str = S.FISH_UI["book_inv"].format(n=n_inv) if n_inv > 0 else ""
|
||||||
|
lines.append(
|
||||||
|
S.FISH_UI["book_yes"].format(
|
||||||
|
emoji=emoji,
|
||||||
|
name=S.FISH_NAMES[fish_id],
|
||||||
|
rarity=rarity_name,
|
||||||
|
count=count,
|
||||||
|
inv=inv_str,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
lines.append(S.FISH_UI["book_no"].format(rarity=rarity_name))
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.TITLE["fishbook"].replace("Kalakogu", f"{target.display_name} kalakogu"),
|
||||||
|
description="\n".join(lines),
|
||||||
|
color=0x5865F2,
|
||||||
|
)
|
||||||
|
embed.set_footer(
|
||||||
|
text=S.FISH_UI["book_footer"].format(
|
||||||
|
page=1,
|
||||||
|
total_pages=1,
|
||||||
|
caught=caught_count,
|
||||||
|
total=total,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||||
|
|
||||||
|
@tree.command(name="fishsell", description=S.CMD["fishsell"])
|
||||||
|
@app_commands.guild_only()
|
||||||
|
async def cmd_fishsell(interaction: discord.Interaction):
|
||||||
|
await interaction.response.defer(ephemeral=True)
|
||||||
|
user_data = await economy.get_user(interaction.user.id)
|
||||||
|
inv: list = user_data.get("fish_inventory") or []
|
||||||
|
if not inv:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.TITLE["fishbook"],
|
||||||
|
description=S.FISH_UI["inv_empty"],
|
||||||
|
color=0x5865F2,
|
||||||
|
)
|
||||||
|
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
total_value = sum(e["value"] for e in inv)
|
||||||
|
lines = [S.FISH_UI["inv_header"].format(count=len(inv), total_value=coin(total_value))]
|
||||||
|
for entry in inv:
|
||||||
|
fid = entry.get("fish_id", "")
|
||||||
|
rarity = economy.FISH_CATALOGUE.get(fid, {}).get("rarity", "common")
|
||||||
|
emoji = S.FISH_RARITY_EMOJI.get(rarity, "🐟")
|
||||||
|
name = S.FISH_NAMES.get(fid, fid)
|
||||||
|
lines.append(
|
||||||
|
S.FISH_UI["inv_entry"].format(
|
||||||
|
emoji=emoji,
|
||||||
|
name=name,
|
||||||
|
weight=entry["weight"],
|
||||||
|
value=coin(entry["value"]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.TITLE["fishbook"],
|
||||||
|
description="\n".join(lines),
|
||||||
|
color=0x5865F2,
|
||||||
|
)
|
||||||
|
|
||||||
|
sell_all_btn = discord.ui.Button(
|
||||||
|
label=S.FISH_UI["btn_sell"] + f" ({coin(total_value)})",
|
||||||
|
style=discord.ButtonStyle.success,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _sell_all(btn_interaction: discord.Interaction):
|
||||||
|
if btn_interaction.user.id != interaction.user.id:
|
||||||
|
await btn_interaction.response.send_message(S.ERR["not_your_menu"], ephemeral=True)
|
||||||
|
return
|
||||||
|
sell_view.stop()
|
||||||
|
for child in sell_view.children:
|
||||||
|
child.disabled = True
|
||||||
|
res = await economy.do_fish_sell(interaction.user.id)
|
||||||
|
if not res["ok"]:
|
||||||
|
await btn_interaction.response.edit_message(
|
||||||
|
embed=discord.Embed(description=S.FISH_UI["inv_none"], color=0x99AAB5),
|
||||||
|
view=sell_view,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
sold_embed = discord.Embed(
|
||||||
|
title=S.TITLE["fishbook"],
|
||||||
|
description=S.FISH_UI["inv_sold_all"].format(
|
||||||
|
count=res["count"],
|
||||||
|
coins=coin(res["coins"]),
|
||||||
|
balance=coin(res["balance"]),
|
||||||
|
),
|
||||||
|
color=0x57F287,
|
||||||
|
)
|
||||||
|
await btn_interaction.response.edit_message(embed=sold_embed, view=sell_view)
|
||||||
|
|
||||||
|
sell_all_btn.callback = _sell_all
|
||||||
|
sell_view = discord.ui.View(timeout=60)
|
||||||
|
sell_view.add_item(sell_all_btn)
|
||||||
|
await interaction.followup.send(embed=embed, view=sell_view, ephemeral=True)
|
||||||
1132
economy_games_commands.py
Normal file
1132
economy_games_commands.py
Normal file
File diff suppressed because it is too large
Load Diff
257
economy_income_commands.py
Normal file
257
economy_income_commands.py
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import datetime
|
||||||
|
from collections.abc import Awaitable, Callable
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from discord import app_commands
|
||||||
|
|
||||||
|
import economy
|
||||||
|
import strings as S
|
||||||
|
|
||||||
|
|
||||||
|
def register_economy_income_commands(
|
||||||
|
tree: app_commands.CommandTree,
|
||||||
|
bot: discord.Client,
|
||||||
|
coin: Callable[[int], str],
|
||||||
|
cd_ts: Callable[[datetime.timedelta], str],
|
||||||
|
check_cmd_rate: Callable[[discord.Interaction], Awaitable[bool]],
|
||||||
|
maybe_remind: Callable[[int, str], Awaitable[None]],
|
||||||
|
award_exp: Callable[[discord.Interaction, int], Awaitable[None]],
|
||||||
|
) -> None:
|
||||||
|
@tree.command(name="daily", description=S.CMD["daily"])
|
||||||
|
async def cmd_daily(interaction: discord.Interaction):
|
||||||
|
res = await economy.do_daily(interaction.user.id)
|
||||||
|
if not res["ok"]:
|
||||||
|
if res["reason"] == "banned":
|
||||||
|
await interaction.response.send_message(S.MSG_BANNED, ephemeral=True)
|
||||||
|
elif res["reason"] == "cooldown":
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.CD_MSG["daily"].format(ts=cd_ts(res["remaining"])),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
streak = res["streak"]
|
||||||
|
streak_str = f"🔥 {streak}p" + (
|
||||||
|
" (+200%)"
|
||||||
|
if res["streak_mult"] >= 3.0
|
||||||
|
else " (+100%)"
|
||||||
|
if res["streak_mult"] >= 2.0
|
||||||
|
else " (+50%)"
|
||||||
|
if res["streak_mult"] >= 1.5
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
lines = [S.DAILY_UI["earned"].format(earned=coin(res["earned"]))]
|
||||||
|
if res["interest"]:
|
||||||
|
lines.append(S.DAILY_UI["interest"].format(interest=coin(res["interest"])))
|
||||||
|
if res["vip"]:
|
||||||
|
lines.append(S.DAILY_UI["vip"])
|
||||||
|
lines.append(S.DAILY_UI["footer"].format(streak_str=streak_str, balance=coin(res["balance"])))
|
||||||
|
|
||||||
|
embed = discord.Embed(title=S.TITLE["daily"], description="\n".join(lines), color=0xF4C430)
|
||||||
|
await interaction.response.send_message(embed=embed)
|
||||||
|
asyncio.create_task(maybe_remind(interaction.user.id, "daily"))
|
||||||
|
asyncio.create_task(award_exp(interaction, economy.EXP_REWARDS["daily"]))
|
||||||
|
|
||||||
|
@tree.command(name="work", description=S.CMD["work"])
|
||||||
|
async def cmd_work(interaction: discord.Interaction):
|
||||||
|
if await check_cmd_rate(interaction):
|
||||||
|
return
|
||||||
|
res = await economy.do_work(interaction.user.id)
|
||||||
|
if not res["ok"]:
|
||||||
|
if res["reason"] == "banned":
|
||||||
|
await interaction.response.send_message(S.MSG_BANNED, ephemeral=True)
|
||||||
|
elif res["reason"] == "cooldown":
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.CD_MSG["work"].format(ts=cd_ts(res["remaining"])),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
elif res["reason"] == "jailed":
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.CD_MSG["jailed"].format(ts=cd_ts(res["remaining"])),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
desc = S.WORK_UI["desc"].format(job=res["job"], earned=coin(res["earned"]))
|
||||||
|
if res["lucky"]:
|
||||||
|
desc += S.WORK_UI["redbull"]
|
||||||
|
if res["hiir"]:
|
||||||
|
desc += S.WORK_UI["hiir"]
|
||||||
|
if res["laud"]:
|
||||||
|
desc += S.WORK_UI["laud"]
|
||||||
|
desc += S.WORK_UI["balance"].format(balance=coin(res["balance"]))
|
||||||
|
embed = discord.Embed(title=S.TITLE["work"], description=desc, color=0x57F287)
|
||||||
|
await interaction.response.send_message(embed=embed)
|
||||||
|
asyncio.create_task(maybe_remind(interaction.user.id, "work"))
|
||||||
|
asyncio.create_task(award_exp(interaction, economy.EXP_REWARDS["work"]))
|
||||||
|
|
||||||
|
@tree.command(name="beg", description=S.CMD["beg"])
|
||||||
|
async def cmd_beg(interaction: discord.Interaction):
|
||||||
|
if await check_cmd_rate(interaction):
|
||||||
|
return
|
||||||
|
res = await economy.do_beg(interaction.user.id)
|
||||||
|
if not res["ok"]:
|
||||||
|
if res["reason"] == "banned":
|
||||||
|
await interaction.response.send_message(S.MSG_BANNED, ephemeral=True)
|
||||||
|
elif res["reason"] == "cooldown":
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.CD_MSG["beg"].format(ts=cd_ts(res["remaining"])),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if res["jailed"]:
|
||||||
|
title = "🔒 " + S.TITLE["beg"]
|
||||||
|
color = 0xE67E22
|
||||||
|
else:
|
||||||
|
title = S.TITLE["beg"]
|
||||||
|
color = 0x99AAB5
|
||||||
|
beg_lines = [S.BEG_UI["desc"].format(text=res["text"], earned=coin(res["earned"]))]
|
||||||
|
if res["klaviatuur"]:
|
||||||
|
beg_lines.append(S.BEG_UI["klaviatuur"])
|
||||||
|
beg_lines.append(S.BEG_UI["balance"].format(balance=coin(res["balance"])))
|
||||||
|
embed = discord.Embed(title=title, description="\n".join(beg_lines), color=color)
|
||||||
|
await interaction.response.send_message(embed=embed)
|
||||||
|
asyncio.create_task(maybe_remind(interaction.user.id, "beg"))
|
||||||
|
asyncio.create_task(award_exp(interaction, economy.EXP_REWARDS["beg"]))
|
||||||
|
|
||||||
|
@tree.command(name="crime", description=S.CMD["crime"])
|
||||||
|
async def cmd_crime(interaction: discord.Interaction):
|
||||||
|
if await check_cmd_rate(interaction):
|
||||||
|
return
|
||||||
|
res = await economy.do_crime(interaction.user.id)
|
||||||
|
if not res["ok"]:
|
||||||
|
if res["reason"] == "banned":
|
||||||
|
await interaction.response.send_message(S.MSG_BANNED, ephemeral=True)
|
||||||
|
elif res["reason"] == "cooldown":
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.CD_MSG["crime"].format(ts=cd_ts(res["remaining"])),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
elif res["reason"] == "jailed":
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.CD_MSG["jailed"].format(ts=cd_ts(res["remaining"])),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if res["success"]:
|
||||||
|
crime_lines = [S.CRIME_UI["win_desc"].format(text=res["text"], earned=coin(res["earned"]))]
|
||||||
|
if res["mikrofon"]:
|
||||||
|
crime_lines.append(S.CRIME_UI["mikrofon"].lstrip("\n"))
|
||||||
|
crime_lines.append(S.CRIME_UI["balance"].lstrip("\n").format(balance=coin(res["balance"])))
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.TITLE["crime_win"],
|
||||||
|
description="\n".join(crime_lines),
|
||||||
|
color=0x57F287,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
jail_part = (
|
||||||
|
S.CRIME_UI["fail_jailed"].format(ts=cd_ts(economy.JAIL_DURATION))
|
||||||
|
if res.get("jailed")
|
||||||
|
else S.CRIME_UI["fail_shield"]
|
||||||
|
)
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.TITLE["crime_fail"],
|
||||||
|
description=S.CRIME_UI["fail_base"].format(text=res["text"], fine=coin(res["fine"]))
|
||||||
|
+ jail_part
|
||||||
|
+ S.CRIME_UI["balance"].format(balance=coin(res["balance"])),
|
||||||
|
color=0xED4245,
|
||||||
|
)
|
||||||
|
await interaction.response.send_message(embed=embed)
|
||||||
|
asyncio.create_task(maybe_remind(interaction.user.id, "crime"))
|
||||||
|
if res["success"]:
|
||||||
|
asyncio.create_task(award_exp(interaction, economy.EXP_REWARDS["crime_win"]))
|
||||||
|
|
||||||
|
@tree.command(name="rob", description=S.CMD["rob"])
|
||||||
|
async def cmd_rob(interaction: discord.Interaction, sihtmärk: discord.Member):
|
||||||
|
if await check_cmd_rate(interaction):
|
||||||
|
return
|
||||||
|
if sihtmärk.id == interaction.user.id:
|
||||||
|
await interaction.response.send_message(S.ERR["rob_self"], ephemeral=True)
|
||||||
|
return
|
||||||
|
if sihtmärk.bot and (bot.user is None or sihtmärk.id != bot.user.id):
|
||||||
|
await interaction.response.send_message(S.ERR["rob_bot"], ephemeral=True)
|
||||||
|
return
|
||||||
|
if bot.user and sihtmärk.id == bot.user.id:
|
||||||
|
await interaction.response.send_message(S.ERR["rob_house_blocked"], ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
res = await economy.do_rob(interaction.user.id, sihtmärk.id)
|
||||||
|
if not res["ok"]:
|
||||||
|
if res["reason"] == "banned":
|
||||||
|
await interaction.response.send_message(S.MSG_BANNED, ephemeral=True)
|
||||||
|
elif res["reason"] == "cooldown":
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.CD_MSG["rob"].format(ts=cd_ts(res["remaining"])),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
elif res["reason"] == "jailed":
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.CD_MSG["jailed"].format(ts=cd_ts(res["remaining"])),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
elif res["reason"] == "broke":
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.ERR["rob_too_poor"].format(name=sihtmärk.display_name),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
elif res["reason"] == "target_jailed":
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.ERR["rob_target_jailed"].format(name=sihtmärk.display_name),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if res["success"]:
|
||||||
|
if res.get("jackpot"):
|
||||||
|
desc = S.ROB_UI["jackpot_desc"].format(stolen=coin(res["stolen"]), balance=coin(res["balance"]))
|
||||||
|
color = 0xF4C430
|
||||||
|
else:
|
||||||
|
desc = S.ROB_UI["win_desc"].format(
|
||||||
|
stolen=coin(res["stolen"]),
|
||||||
|
name=sihtmärk.display_name,
|
||||||
|
balance=coin(res["balance"]),
|
||||||
|
)
|
||||||
|
color = 0x57F287
|
||||||
|
embed = discord.Embed(title=S.TITLE["rob_win"], description=desc, color=color)
|
||||||
|
elif res["reason"] == "valvur":
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.TITLE["rob_anticheat"],
|
||||||
|
description=S.ROB_UI["anticheat_desc"].format(
|
||||||
|
name=sihtmärk.display_name,
|
||||||
|
fine=coin(res["fine"]),
|
||||||
|
),
|
||||||
|
color=0xED4245,
|
||||||
|
)
|
||||||
|
target_data = await economy.get_user(sihtmärk.id)
|
||||||
|
if "anticheat" not in target_data.get("items", []):
|
||||||
|
try:
|
||||||
|
await sihtmärk.send(S.ROB_UI["anticheat_worn"])
|
||||||
|
except discord.Forbidden:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.TITLE["rob_fail"],
|
||||||
|
description=S.ROB_UI["fail_desc"].format(
|
||||||
|
fine=coin(res["fine"]),
|
||||||
|
balance=coin(res["balance"]),
|
||||||
|
),
|
||||||
|
color=0xED4245,
|
||||||
|
)
|
||||||
|
await interaction.response.send_message(embed=embed)
|
||||||
|
asyncio.create_task(maybe_remind(interaction.user.id, "rob"))
|
||||||
|
if res["success"]:
|
||||||
|
asyncio.create_task(award_exp(interaction, economy.EXP_REWARDS["rob_win"]))
|
||||||
|
try:
|
||||||
|
await sihtmärk.send(
|
||||||
|
S.ROB_UI["victim_dm"].format(
|
||||||
|
robber=interaction.user.display_name,
|
||||||
|
stolen=coin(res["stolen"]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except discord.Forbidden:
|
||||||
|
pass
|
||||||
545
economy_profile_commands.py
Normal file
545
economy_profile_commands.py
Normal file
@@ -0,0 +1,545 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import datetime
|
||||||
|
from collections.abc import Awaitable, Callable
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from discord import app_commands
|
||||||
|
|
||||||
|
import economy
|
||||||
|
import strings as S
|
||||||
|
|
||||||
|
|
||||||
|
def register_economy_profile_commands(
|
||||||
|
tree: app_commands.CommandTree,
|
||||||
|
coin: Callable[[int], str],
|
||||||
|
cd_ts: Callable[[datetime.timedelta], str],
|
||||||
|
ensure_level_role: Callable[[discord.Member, int], Awaitable[None]],
|
||||||
|
) -> None:
|
||||||
|
def _profile_main_embed(target: discord.User | discord.Member, data: dict) -> discord.Embed:
|
||||||
|
exp = data.get("exp", 0)
|
||||||
|
level = economy.get_level(exp)
|
||||||
|
role_name = economy.level_role_name(level)
|
||||||
|
next_level = level + 1
|
||||||
|
exp_this = economy.exp_for_level(level)
|
||||||
|
exp_next = economy.exp_for_level(next_level)
|
||||||
|
progress = exp - exp_this
|
||||||
|
needed = exp_next - exp_this
|
||||||
|
pct = progress / needed if needed > 0 else 1.0
|
||||||
|
filled = int(pct * 12)
|
||||||
|
bar = "█" * filled + "░" * (12 - filled)
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.PROFILE_UI["main_title"].format(name=target.display_name),
|
||||||
|
color=0xF4C430,
|
||||||
|
)
|
||||||
|
embed.add_field(name=S.PROFILE_UI["f_balance"], value=coin(data.get("balance", 0)), inline=True)
|
||||||
|
embed.add_field(
|
||||||
|
name=S.PROFILE_UI["f_level"],
|
||||||
|
value=S.PROFILE_UI["level_val"].format(level=level, role=role_name),
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
streak = data.get("daily_streak", 0)
|
||||||
|
if streak:
|
||||||
|
embed.add_field(
|
||||||
|
name=S.PROFILE_UI["f_streak"],
|
||||||
|
value=S.BALANCE_UI["streak_val"].format(streak=streak),
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
p_level = data.get("prestige_level", 0)
|
||||||
|
if p_level > 0:
|
||||||
|
p_pp = data.get("prestige_points", 0)
|
||||||
|
embed.add_field(
|
||||||
|
name=S.PROFILE_UI["f_prestige"],
|
||||||
|
value=S.PROFILE_UI["prestige_val"].format(level=p_level, pp=p_pp),
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
jail_remaining = economy._is_jailed(data)
|
||||||
|
if jail_remaining:
|
||||||
|
embed.add_field(name=S.PROFILE_UI["f_jail"], value=cd_ts(jail_remaining), inline=True)
|
||||||
|
embed.add_field(
|
||||||
|
name=S.PROFILE_UI["f_progress"].format(next=next_level),
|
||||||
|
value=S.PROFILE_UI["progress_bar"].format(bar=bar, done=progress, needed=needed),
|
||||||
|
inline=False,
|
||||||
|
)
|
||||||
|
if level < 10:
|
||||||
|
embed.set_footer(text=S.PROFILE_UI["footer_t1"])
|
||||||
|
elif level < 20:
|
||||||
|
embed.set_footer(text=S.PROFILE_UI["footer_t2"])
|
||||||
|
else:
|
||||||
|
embed.set_footer(text=S.PROFILE_UI["footer_t3"])
|
||||||
|
return embed
|
||||||
|
|
||||||
|
def _profile_items_embed(target: discord.User | discord.Member, data: dict) -> discord.Embed:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.PROFILE_UI["items_title"].format(name=target.display_name),
|
||||||
|
color=0xF4C430,
|
||||||
|
)
|
||||||
|
uses_map = data.get("item_uses", {})
|
||||||
|
item_lines = []
|
||||||
|
for item_id in data.get("items", []):
|
||||||
|
if item_id not in economy.SHOP:
|
||||||
|
continue
|
||||||
|
line = f"{economy.SHOP[item_id]['emoji']} **{economy.SHOP[item_id]['name']}**"
|
||||||
|
if item_id in uses_map:
|
||||||
|
uses = uses_map[item_id]
|
||||||
|
line += (
|
||||||
|
S.BALANCE_UI["uses_one"].format(uses=uses)
|
||||||
|
if uses == 1
|
||||||
|
else S.BALANCE_UI["uses_many"].format(uses=uses)
|
||||||
|
)
|
||||||
|
item_lines.append(line)
|
||||||
|
embed.description = "\n".join(item_lines) if item_lines else S.PROFILE_UI["items_empty"]
|
||||||
|
return embed
|
||||||
|
|
||||||
|
def _profile_stats_embed(target: discord.User | discord.Member, data: dict) -> discord.Embed:
|
||||||
|
def _s(key: str) -> int:
|
||||||
|
return data.get(key, 0)
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.PROFILE_UI["stats_title"].format(name=target.display_name),
|
||||||
|
color=0x5865F2,
|
||||||
|
)
|
||||||
|
embed.add_field(
|
||||||
|
name=S.STATS_UI["economy_field"],
|
||||||
|
value=S.STATS_UI["economy_val"].format(
|
||||||
|
peak=coin(_s("peak_balance")),
|
||||||
|
earned=coin(_s("lifetime_earned")),
|
||||||
|
lost=coin(_s("lifetime_lost")),
|
||||||
|
),
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
embed.add_field(
|
||||||
|
name=S.STATS_UI["work_field"],
|
||||||
|
value=S.STATS_UI["work_val"].format(work=_s("work_count"), beg=_s("beg_count")),
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
embed.add_field(name="\u200b", value="\u200b", inline=False)
|
||||||
|
embed.add_field(
|
||||||
|
name=S.STATS_UI["gamble_field"],
|
||||||
|
value=S.STATS_UI["gamble_val"].format(
|
||||||
|
wagered=coin(_s("total_wagered")),
|
||||||
|
win=coin(_s("biggest_win")),
|
||||||
|
loss=coin(_s("biggest_loss")),
|
||||||
|
jackpots=_s("slots_jackpots"),
|
||||||
|
),
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
embed.add_field(
|
||||||
|
name=S.STATS_UI["crime_field"],
|
||||||
|
value=S.STATS_UI["crime_val"].format(
|
||||||
|
crimes=_s("crimes_attempted"),
|
||||||
|
succeeded=_s("crimes_succeeded"),
|
||||||
|
heists=_s("heists_joined"),
|
||||||
|
heists_won=_s("heists_won"),
|
||||||
|
jailed=_s("times_jailed"),
|
||||||
|
bail=coin(_s("total_bail_paid")),
|
||||||
|
),
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
embed.add_field(name="\u200b", value="\u200b", inline=False)
|
||||||
|
embed.add_field(
|
||||||
|
name=S.STATS_UI["social_field"],
|
||||||
|
value=S.STATS_UI["social_val"].format(
|
||||||
|
given=coin(_s("total_given")),
|
||||||
|
received=coin(_s("total_received")),
|
||||||
|
),
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
embed.add_field(
|
||||||
|
name=S.STATS_UI["records_field"],
|
||||||
|
value=S.STATS_UI["records_val"].format(streak=_s("best_daily_streak")),
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
return embed
|
||||||
|
|
||||||
|
def _profile_fish_embed(target: discord.User | discord.Member, fish_res: dict) -> discord.Embed:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.PROFILE_UI["fish_title"].format(name=target.display_name),
|
||||||
|
color=0x5865F2,
|
||||||
|
)
|
||||||
|
book: dict = fish_res["book"]
|
||||||
|
if not book:
|
||||||
|
embed.description = S.FISH_UI["book_empty"]
|
||||||
|
return embed
|
||||||
|
inv_counts: dict = fish_res.get("inv_counts", {})
|
||||||
|
caught_count = fish_res["unique_caught"]
|
||||||
|
total = fish_res["total_species"]
|
||||||
|
lines = [S.FISH_UI["book_caught"].format(caught=caught_count, total=total)]
|
||||||
|
for fish_id, fish_data in economy.FISH_CATALOGUE.items():
|
||||||
|
rarity = fish_data["rarity"]
|
||||||
|
emoji = S.FISH_RARITY_EMOJI[rarity]
|
||||||
|
rarity_name = S.FISH_RARITY_NAMES[rarity]
|
||||||
|
count = book.get(fish_id, 0)
|
||||||
|
if count > 0:
|
||||||
|
n_inv = inv_counts.get(fish_id, 0)
|
||||||
|
inv_str = S.FISH_UI["book_inv"].format(n=n_inv) if n_inv > 0 else ""
|
||||||
|
lines.append(
|
||||||
|
S.FISH_UI["book_yes"].format(
|
||||||
|
emoji=emoji,
|
||||||
|
name=S.FISH_NAMES[fish_id],
|
||||||
|
rarity=rarity_name,
|
||||||
|
count=count,
|
||||||
|
inv=inv_str,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
lines.append(S.FISH_UI["book_no"].format(rarity=rarity_name))
|
||||||
|
embed.description = "\n".join(lines)
|
||||||
|
embed.set_footer(
|
||||||
|
text=S.FISH_UI["book_footer"].format(
|
||||||
|
page=1,
|
||||||
|
total_pages=1,
|
||||||
|
caught=caught_count,
|
||||||
|
total=total,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return embed
|
||||||
|
|
||||||
|
class ProfileView(discord.ui.View):
|
||||||
|
def __init__(self, target: discord.User | discord.Member, invoker_id: int, tab: str = "main"):
|
||||||
|
super().__init__(timeout=120)
|
||||||
|
self.target = target
|
||||||
|
self.invoker_id = invoker_id
|
||||||
|
self.tab = tab
|
||||||
|
self._rebuild()
|
||||||
|
|
||||||
|
def _rebuild(self):
|
||||||
|
self.clear_items()
|
||||||
|
tabs = [
|
||||||
|
("main", S.PROFILE_UI["btn_profile"]),
|
||||||
|
("items", S.PROFILE_UI["btn_items"]),
|
||||||
|
("stats", S.PROFILE_UI["btn_stats"]),
|
||||||
|
("fish", S.PROFILE_UI["btn_fish"]),
|
||||||
|
]
|
||||||
|
for tab_id, label in tabs:
|
||||||
|
btn = discord.ui.Button(
|
||||||
|
label=label,
|
||||||
|
style=(
|
||||||
|
discord.ButtonStyle.primary
|
||||||
|
if tab_id == self.tab
|
||||||
|
else discord.ButtonStyle.secondary
|
||||||
|
),
|
||||||
|
disabled=(tab_id == self.tab),
|
||||||
|
)
|
||||||
|
btn.callback = self._make_cb(tab_id)
|
||||||
|
self.add_item(btn)
|
||||||
|
|
||||||
|
def _make_cb(self, tab_id: str):
|
||||||
|
async def _cb(interaction: discord.Interaction):
|
||||||
|
if interaction.user.id != self.invoker_id:
|
||||||
|
await interaction.response.send_message(S.ERR["not_your_menu"], ephemeral=True)
|
||||||
|
return
|
||||||
|
self.tab = tab_id
|
||||||
|
self._rebuild()
|
||||||
|
await interaction.response.defer()
|
||||||
|
data = await economy.get_user(self.target.id)
|
||||||
|
if tab_id == "fish":
|
||||||
|
fish_res = await economy.do_fishbook(self.target.id)
|
||||||
|
embed = _profile_fish_embed(self.target, fish_res)
|
||||||
|
inv: list = data.get("fish_inventory") or []
|
||||||
|
if inv and self.target.id == self.invoker_id:
|
||||||
|
total_value = sum(entry.get("value", 0) for entry in inv)
|
||||||
|
sell_btn = discord.ui.Button(
|
||||||
|
label=(
|
||||||
|
f"{S.FISH_UI['btn_sell']} "
|
||||||
|
f"({len(inv)} kala · {total_value:,} {economy.COIN})"
|
||||||
|
),
|
||||||
|
style=discord.ButtonStyle.success,
|
||||||
|
row=1,
|
||||||
|
)
|
||||||
|
sell_btn.callback = self._sell_fish_cb()
|
||||||
|
self.add_item(sell_btn)
|
||||||
|
elif tab_id == "items":
|
||||||
|
embed = _profile_items_embed(self.target, data)
|
||||||
|
elif tab_id == "stats":
|
||||||
|
embed = _profile_stats_embed(self.target, data)
|
||||||
|
else:
|
||||||
|
embed = _profile_main_embed(self.target, data)
|
||||||
|
await interaction.edit_original_response(embed=embed, view=self)
|
||||||
|
|
||||||
|
return _cb
|
||||||
|
|
||||||
|
def _sell_fish_cb(self):
|
||||||
|
async def _cb(interaction: discord.Interaction):
|
||||||
|
if interaction.user.id != self.invoker_id:
|
||||||
|
await interaction.response.send_message(S.ERR["not_your_menu"], ephemeral=True)
|
||||||
|
return
|
||||||
|
await interaction.response.defer()
|
||||||
|
res = await economy.do_fish_sell(self.invoker_id)
|
||||||
|
self.tab = "fish"
|
||||||
|
self._rebuild()
|
||||||
|
fish_res = await economy.do_fishbook(self.target.id)
|
||||||
|
embed = _profile_fish_embed(self.target, fish_res)
|
||||||
|
sold_line = S.FISH_UI["inv_sold_all"].format(
|
||||||
|
count=res.get("count", 0),
|
||||||
|
coins=coin(res.get("coins", 0)),
|
||||||
|
balance=coin(res.get("balance", 0)),
|
||||||
|
)
|
||||||
|
embed.description = f"{sold_line}\n\n{embed.description or ''}"
|
||||||
|
await interaction.edit_original_response(embed=embed, view=self)
|
||||||
|
|
||||||
|
return _cb
|
||||||
|
|
||||||
|
def _balance_embed(user: discord.User | discord.Member, data: dict) -> discord.Embed:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=f"{economy.COIN} {user.display_name}",
|
||||||
|
color=0xF4C430,
|
||||||
|
)
|
||||||
|
embed.add_field(name=S.BALANCE_UI["saldo"], value=coin(data["balance"]), inline=True)
|
||||||
|
streak = data.get("daily_streak", 0)
|
||||||
|
if streak:
|
||||||
|
embed.add_field(
|
||||||
|
name=S.BALANCE_UI["streak"],
|
||||||
|
value=S.BALANCE_UI["streak_val"].format(streak=streak),
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
jail_remaining = economy._is_jailed(data)
|
||||||
|
if jail_remaining:
|
||||||
|
embed.add_field(name=S.BALANCE_UI["jailed_until"], value=cd_ts(jail_remaining), inline=True)
|
||||||
|
item_lines = []
|
||||||
|
uses_map = data.get("item_uses", {})
|
||||||
|
for item_id in data.get("items", []):
|
||||||
|
if item_id not in economy.SHOP:
|
||||||
|
continue
|
||||||
|
line = f"{economy.SHOP[item_id]['emoji']} {economy.SHOP[item_id]['name']}"
|
||||||
|
if item_id in uses_map:
|
||||||
|
uses = uses_map[item_id]
|
||||||
|
line += (
|
||||||
|
S.BALANCE_UI["uses_one"].format(uses=uses)
|
||||||
|
if uses == 1
|
||||||
|
else S.BALANCE_UI["uses_many"].format(uses=uses)
|
||||||
|
)
|
||||||
|
item_lines.append(line)
|
||||||
|
if item_lines:
|
||||||
|
embed.add_field(name=S.BALANCE_UI["items"], value="\n".join(item_lines), inline=False)
|
||||||
|
return embed
|
||||||
|
|
||||||
|
@tree.command(name="profile", description=S.CMD["profile"])
|
||||||
|
@app_commands.describe(kasutaja=S.OPT["profile_kasutaja"])
|
||||||
|
async def cmd_profile(interaction: discord.Interaction, kasutaja: discord.Member | None = None):
|
||||||
|
target = kasutaja or interaction.user
|
||||||
|
data = await economy.get_user(target.id)
|
||||||
|
embed = _profile_main_embed(target, data)
|
||||||
|
invoker_id = interaction.user.id
|
||||||
|
await interaction.response.send_message(embed=embed, view=ProfileView(target, invoker_id))
|
||||||
|
if not kasutaja and interaction.guild:
|
||||||
|
member = interaction.guild.get_member(target.id)
|
||||||
|
if member:
|
||||||
|
asyncio.create_task(ensure_level_role(member, economy.get_level(data.get("exp", 0))))
|
||||||
|
|
||||||
|
@tree.command(name="balance", description=S.CMD["balance"])
|
||||||
|
async def cmd_balance(interaction: discord.Interaction, kasutaja: discord.Member | None = None):
|
||||||
|
target = kasutaja or interaction.user
|
||||||
|
data = await economy.get_user(target.id)
|
||||||
|
await interaction.response.send_message(embed=_balance_embed(target, data))
|
||||||
|
|
||||||
|
@tree.command(name="cooldowns", description=S.CMD["cooldowns"])
|
||||||
|
async def cmd_cooldowns(interaction: discord.Interaction):
|
||||||
|
data = await economy.get_user(interaction.user.id)
|
||||||
|
now = datetime.datetime.now(datetime.timezone.utc)
|
||||||
|
items = set(data.get("items", []))
|
||||||
|
|
||||||
|
def _status(last_key: str, cooldown: datetime.timedelta) -> str:
|
||||||
|
raw = data.get(last_key)
|
||||||
|
if not raw:
|
||||||
|
return S.COOLDOWNS_UI["ready"]
|
||||||
|
last = economy._parse_dt(raw)
|
||||||
|
if last is None:
|
||||||
|
return S.COOLDOWNS_UI["ready"]
|
||||||
|
expires = last + cooldown
|
||||||
|
if expires <= now:
|
||||||
|
return S.COOLDOWNS_UI["ready"]
|
||||||
|
ts = int(expires.timestamp())
|
||||||
|
return f"⏳ <t:{ts}:R>"
|
||||||
|
|
||||||
|
work_cd = datetime.timedelta(minutes=40) if "monitor" in items else economy.COOLDOWNS["work"]
|
||||||
|
beg_cd = datetime.timedelta(minutes=3) if "hiirematt" in items else economy.COOLDOWNS["beg"]
|
||||||
|
daily_cd = datetime.timedelta(hours=18) if "korvaklapid" in items else economy.COOLDOWNS["daily"]
|
||||||
|
fish_cd = datetime.timedelta(seconds=90) if "ussipurk" in items else economy.COOLDOWNS["fish"]
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
S.COOLDOWNS_UI["daily_line"].format(
|
||||||
|
status=_status("last_daily", daily_cd),
|
||||||
|
note=S.COOLDOWNS_UI["note_korvak"] if "korvaklapid" in items else "",
|
||||||
|
),
|
||||||
|
S.COOLDOWNS_UI["work_line"].format(
|
||||||
|
status=_status("last_work", work_cd),
|
||||||
|
note=S.COOLDOWNS_UI["note_monitor"] if "monitor" in items else "",
|
||||||
|
),
|
||||||
|
S.COOLDOWNS_UI["beg_line"].format(
|
||||||
|
status=_status("last_beg", beg_cd),
|
||||||
|
note=S.COOLDOWNS_UI["note_hiirematt"] if "hiirematt" in items else "",
|
||||||
|
),
|
||||||
|
S.COOLDOWNS_UI["crime_line"].format(status=_status("last_crime", economy.COOLDOWNS["crime"])),
|
||||||
|
S.COOLDOWNS_UI["rob_line"].format(status=_status("last_rob", economy.COOLDOWNS["rob"])),
|
||||||
|
S.COOLDOWNS_UI["fish_line"].format(
|
||||||
|
status=_status("last_fish", fish_cd),
|
||||||
|
note=S.COOLDOWNS_UI["note_ussipurk"] if "ussipurk" in items else "",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
jailed = data.get("jailed_until")
|
||||||
|
if jailed:
|
||||||
|
jail_dt = datetime.datetime.fromisoformat(jailed)
|
||||||
|
if jail_dt > now:
|
||||||
|
ts = int(jail_dt.timestamp())
|
||||||
|
lines.append(S.COOLDOWNS_UI["jailed"].format(ts=ts))
|
||||||
|
else:
|
||||||
|
lines.append(S.COOLDOWNS_UI["jail_expired"])
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.TITLE["cooldowns"],
|
||||||
|
description="\n".join(lines),
|
||||||
|
color=0x5865F2,
|
||||||
|
)
|
||||||
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||||
|
|
||||||
|
@tree.command(name="jailed", description=S.CMD["jailed"])
|
||||||
|
@app_commands.guild_only()
|
||||||
|
async def cmd_jailed(interaction: discord.Interaction):
|
||||||
|
await interaction.response.defer()
|
||||||
|
jailed = await economy.do_get_jailed()
|
||||||
|
if not jailed:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.JAILED_UI["title"],
|
||||||
|
description=S.JAILED_UI["empty"],
|
||||||
|
color=0x57F287,
|
||||||
|
)
|
||||||
|
await interaction.followup.send(embed=embed)
|
||||||
|
return
|
||||||
|
|
||||||
|
now = datetime.datetime.now(datetime.timezone.utc)
|
||||||
|
lines = []
|
||||||
|
for uid, remaining in jailed:
|
||||||
|
ts = int((now + remaining).timestamp())
|
||||||
|
member = interaction.guild.get_member(uid) if interaction.guild else None
|
||||||
|
mention = member.mention if member else f"<@{uid}>"
|
||||||
|
lines.append(S.JAILED_UI["entry"].format(mention=mention, ts=ts))
|
||||||
|
|
||||||
|
plural = "" if len(jailed) == 1 else "i"
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.JAILED_UI["title"],
|
||||||
|
description="\n".join(lines),
|
||||||
|
color=0xED4245,
|
||||||
|
)
|
||||||
|
embed.set_footer(text=S.JAILED_UI["footer"].format(count=len(jailed), plural=plural))
|
||||||
|
await interaction.followup.send(embed=embed)
|
||||||
|
|
||||||
|
@tree.command(name="rank", description=S.CMD["rank"])
|
||||||
|
@app_commands.describe(kasutaja=S.OPT["rank_kasutaja"])
|
||||||
|
async def cmd_rank(interaction: discord.Interaction, kasutaja: discord.Member | None = None):
|
||||||
|
target = kasutaja or interaction.user
|
||||||
|
data = await economy.get_user(target.id)
|
||||||
|
exp = data.get("exp", 0)
|
||||||
|
level = economy.get_level(exp)
|
||||||
|
role_name = economy.level_role_name(level)
|
||||||
|
next_level = level + 1
|
||||||
|
exp_this = economy.exp_for_level(level)
|
||||||
|
exp_next = economy.exp_for_level(next_level)
|
||||||
|
progress = exp - exp_this
|
||||||
|
needed = exp_next - exp_this
|
||||||
|
pct = progress / needed if needed > 0 else 1.0
|
||||||
|
filled = int(pct * 12)
|
||||||
|
bar = "█" * filled + "░" * (12 - filled)
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.RANK_UI["title"].format(name=target.display_name, level=level),
|
||||||
|
color=0x5865F2,
|
||||||
|
)
|
||||||
|
embed.add_field(name=S.RANK_UI["field_title"], value=f"**{role_name}**", inline=True)
|
||||||
|
embed.add_field(name=S.RANK_UI["field_exp"], value=str(exp), inline=True)
|
||||||
|
embed.add_field(
|
||||||
|
name=S.RANK_UI["field_progress"].format(next=next_level),
|
||||||
|
value=S.RANK_UI["progress_val"].format(bar=bar, progress=progress, needed=needed),
|
||||||
|
inline=False,
|
||||||
|
)
|
||||||
|
p_level = data.get("prestige_level", 0)
|
||||||
|
p_pp = data.get("prestige_points", 0)
|
||||||
|
s_exp = data.get("season_total_exp", 0)
|
||||||
|
if p_level > 0 or s_exp > 0:
|
||||||
|
embed.add_field(
|
||||||
|
name="\u200b",
|
||||||
|
value=(
|
||||||
|
S.PRESTIGE_UI["rank_line"].format(level=p_level, pp=p_pp)
|
||||||
|
+ "\n"
|
||||||
|
+ S.PRESTIGE_UI["rank_season"].format(exp=s_exp)
|
||||||
|
),
|
||||||
|
inline=False,
|
||||||
|
)
|
||||||
|
if level < 10:
|
||||||
|
embed.set_footer(text=S.RANK_UI["footer_t1"])
|
||||||
|
elif level < 20:
|
||||||
|
embed.set_footer(text=S.RANK_UI["footer_t2"])
|
||||||
|
else:
|
||||||
|
embed.set_footer(text=S.RANK_UI["footer_t3"])
|
||||||
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||||
|
if not kasutaja and interaction.guild:
|
||||||
|
member = interaction.guild.get_member(target.id)
|
||||||
|
if member:
|
||||||
|
asyncio.create_task(ensure_level_role(member, level))
|
||||||
|
|
||||||
|
@tree.command(name="stats", description=S.CMD["stats"])
|
||||||
|
@app_commands.describe(kasutaja=S.OPT["stats_kasutaja"])
|
||||||
|
async def cmd_stats(interaction: discord.Interaction, kasutaja: discord.Member | None = None):
|
||||||
|
target = kasutaja or interaction.user
|
||||||
|
data = await economy.get_user(target.id)
|
||||||
|
|
||||||
|
def _s(key: str) -> int:
|
||||||
|
return data.get(key, 0)
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=f"{S.TITLE['stats']} - {target.display_name}",
|
||||||
|
color=0x5865F2,
|
||||||
|
)
|
||||||
|
embed.add_field(
|
||||||
|
name=S.STATS_UI["economy_field"],
|
||||||
|
value=S.STATS_UI["economy_val"].format(
|
||||||
|
peak=coin(_s("peak_balance")),
|
||||||
|
earned=coin(_s("lifetime_earned")),
|
||||||
|
lost=coin(_s("lifetime_lost")),
|
||||||
|
),
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
embed.add_field(
|
||||||
|
name=S.STATS_UI["work_field"],
|
||||||
|
value=S.STATS_UI["work_val"].format(work=_s("work_count"), beg=_s("beg_count")),
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
embed.add_field(name="\u200b", value="\u200b", inline=False)
|
||||||
|
embed.add_field(
|
||||||
|
name=S.STATS_UI["gamble_field"],
|
||||||
|
value=S.STATS_UI["gamble_val"].format(
|
||||||
|
wagered=coin(_s("total_wagered")),
|
||||||
|
win=coin(_s("biggest_win")),
|
||||||
|
loss=coin(_s("biggest_loss")),
|
||||||
|
jackpots=_s("slots_jackpots"),
|
||||||
|
),
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
embed.add_field(
|
||||||
|
name=S.STATS_UI["crime_field"],
|
||||||
|
value=S.STATS_UI["crime_val"].format(
|
||||||
|
crimes=_s("crimes_attempted"),
|
||||||
|
succeeded=_s("crimes_succeeded"),
|
||||||
|
heists=_s("heists_joined"),
|
||||||
|
heists_won=_s("heists_won"),
|
||||||
|
jailed=_s("times_jailed"),
|
||||||
|
bail=coin(_s("total_bail_paid")),
|
||||||
|
),
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
embed.add_field(name="\u200b", value="\u200b", inline=False)
|
||||||
|
embed.add_field(
|
||||||
|
name=S.STATS_UI["social_field"],
|
||||||
|
value=S.STATS_UI["social_val"].format(
|
||||||
|
given=coin(_s("total_given")),
|
||||||
|
received=coin(_s("total_received")),
|
||||||
|
),
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
embed.add_field(
|
||||||
|
name=S.STATS_UI["records_field"],
|
||||||
|
value=S.STATS_UI["records_val"].format(streak=_s("best_daily_streak")),
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||||
216
economy_support_commands.py
Normal file
216
economy_support_commands.py
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from discord import app_commands
|
||||||
|
|
||||||
|
import economy
|
||||||
|
import strings as S
|
||||||
|
|
||||||
|
|
||||||
|
def register_economy_support_commands(
|
||||||
|
tree: app_commands.CommandTree,
|
||||||
|
parse_amount: Callable[[str, int], tuple[int | None, str | None]],
|
||||||
|
coin: Callable[[int], str],
|
||||||
|
cancel_reminder_task: Callable[[int, str], None],
|
||||||
|
) -> None:
|
||||||
|
class FundModal(discord.ui.Modal):
|
||||||
|
summa = discord.ui.TextInput(
|
||||||
|
label=S.REQUEST_UI["modal_label"],
|
||||||
|
min_length=1,
|
||||||
|
max_length=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, view: "RequestView"):
|
||||||
|
super().__init__(title=S.REQUEST_UI["modal_title"])
|
||||||
|
self._view = view
|
||||||
|
self.summa.placeholder = f"1 - {view.remaining}"
|
||||||
|
|
||||||
|
async def on_submit(self, interaction: discord.Interaction):
|
||||||
|
amount, err = parse_amount(self.summa.value, 0)
|
||||||
|
if err or amount is None:
|
||||||
|
await interaction.response.send_message(S.ERR["invalid_amount"], ephemeral=True)
|
||||||
|
return
|
||||||
|
if amount <= 0 or amount > self._view.remaining:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.ERR["fund_range"].format(max=self._view.remaining), ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
res = await economy.do_give(interaction.user.id, self._view.requester.id, amount)
|
||||||
|
if not res["ok"]:
|
||||||
|
data = await economy.get_user(interaction.user.id)
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.ERR["broke"].format(bal=coin(data["balance"])), ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
self._view.remaining -= amount
|
||||||
|
funded_line = S.REQUEST_UI["funded_line"].format(
|
||||||
|
name=interaction.user.display_name,
|
||||||
|
amount=coin(amount),
|
||||||
|
)
|
||||||
|
if self._view.remaining <= 0:
|
||||||
|
self._view.fund_btn.disabled = True
|
||||||
|
self._view.fund_btn.label = S.REQUEST_UI["btn_funded"]
|
||||||
|
self._view.fund_btn.style = discord.ButtonStyle.secondary
|
||||||
|
self._view.stop()
|
||||||
|
funded_line += S.REQUEST_UI["funded_full"]
|
||||||
|
else:
|
||||||
|
self._view.fund_btn.label = S.REQUEST_UI["btn_fund_remaining"].format(
|
||||||
|
remaining=self._view.remaining
|
||||||
|
)
|
||||||
|
funded_line += S.REQUEST_UI["funded_partial"].format(
|
||||||
|
remaining=coin(self._view.remaining)
|
||||||
|
)
|
||||||
|
|
||||||
|
await interaction.response.send_message(funded_line)
|
||||||
|
if self._view.message:
|
||||||
|
await self._view.message.edit(view=self._view)
|
||||||
|
|
||||||
|
class RequestView(discord.ui.View):
|
||||||
|
def __init__(self, requester: discord.Member, amount: int, target: discord.Member | None):
|
||||||
|
super().__init__(timeout=300)
|
||||||
|
self.requester = requester
|
||||||
|
self.remaining = amount
|
||||||
|
self.target = target
|
||||||
|
self.message: discord.Message | None = None
|
||||||
|
self.fund_btn = discord.ui.Button(
|
||||||
|
label=S.REQUEST_UI["btn_fund"],
|
||||||
|
style=discord.ButtonStyle.success,
|
||||||
|
)
|
||||||
|
self.fund_btn.callback = self._fund
|
||||||
|
self.add_item(self.fund_btn)
|
||||||
|
|
||||||
|
async def _fund(self, interaction: discord.Interaction):
|
||||||
|
if interaction.user.id == self.requester.id:
|
||||||
|
await interaction.response.send_message(S.ERR["request_self_fund"], ephemeral=True)
|
||||||
|
return
|
||||||
|
if self.target and interaction.user.id != self.target.id:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.ERR["request_targeted"].format(name=self.target.display_name),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
await interaction.response.send_modal(FundModal(self))
|
||||||
|
|
||||||
|
async def on_timeout(self):
|
||||||
|
for item in self.children:
|
||||||
|
item.disabled = True
|
||||||
|
|
||||||
|
max_request = 1_000_000
|
||||||
|
|
||||||
|
@tree.command(name="request", description=S.CMD["request"])
|
||||||
|
@app_commands.describe(
|
||||||
|
summa=S.OPT["request_summa"],
|
||||||
|
põhjus=S.OPT["request_põhjus"],
|
||||||
|
sihtmärk=S.OPT["request_sihtmärk"],
|
||||||
|
)
|
||||||
|
async def cmd_request(
|
||||||
|
interaction: discord.Interaction,
|
||||||
|
summa: str,
|
||||||
|
põhjus: str,
|
||||||
|
sihtmärk: discord.Member | None = None,
|
||||||
|
):
|
||||||
|
summa_int, err = parse_amount(summa, 0)
|
||||||
|
if err or summa_int is None:
|
||||||
|
await interaction.response.send_message(err or S.ERR["invalid_amount"], ephemeral=True)
|
||||||
|
return
|
||||||
|
if summa_int <= 0:
|
||||||
|
await interaction.response.send_message(S.ERR["positive_amount"], ephemeral=True)
|
||||||
|
return
|
||||||
|
if summa_int > max_request:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
S.ERR["fund_range"].format(max=coin(max_request)),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
summa = summa_int
|
||||||
|
if sihtmärk and sihtmärk.id == interaction.user.id:
|
||||||
|
await interaction.response.send_message(S.ERR["request_self"], ephemeral=True)
|
||||||
|
return
|
||||||
|
if sihtmärk and sihtmärk.bot:
|
||||||
|
await interaction.response.send_message(S.ERR["request_bot"], ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
audience = (
|
||||||
|
S.REQUEST_UI["audience_targeted"].format(name=sihtmärk.display_name)
|
||||||
|
if sihtmärk
|
||||||
|
else S.REQUEST_UI["audience_all"]
|
||||||
|
)
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.TITLE["request"],
|
||||||
|
description=S.REQUEST_UI["desc"].format(
|
||||||
|
requester=interaction.user.display_name,
|
||||||
|
amount=coin(summa),
|
||||||
|
reason=põhjus,
|
||||||
|
audience=audience,
|
||||||
|
),
|
||||||
|
color=0xF4C430,
|
||||||
|
)
|
||||||
|
embed.set_footer(text=S.REQUEST_UI["footer"])
|
||||||
|
view = RequestView(interaction.user, summa, sihtmärk)
|
||||||
|
await interaction.response.send_message(embed=embed, view=view)
|
||||||
|
view.message = await interaction.original_response()
|
||||||
|
|
||||||
|
class RemindersSelect(discord.ui.Select):
|
||||||
|
def __init__(self, user_id: int, current: list[str]):
|
||||||
|
self.user_id = user_id
|
||||||
|
options = [
|
||||||
|
discord.SelectOption(
|
||||||
|
label=label,
|
||||||
|
description=desc,
|
||||||
|
value=cmd,
|
||||||
|
default=cmd in current,
|
||||||
|
)
|
||||||
|
for cmd, label, desc in S.REMINDER_OPTS
|
||||||
|
]
|
||||||
|
super().__init__(
|
||||||
|
placeholder=S.REMINDERS_UI["select_placeholder"],
|
||||||
|
options=options,
|
||||||
|
min_values=0,
|
||||||
|
max_values=len(S.REMINDER_OPTS),
|
||||||
|
)
|
||||||
|
|
||||||
|
async def callback(self, interaction: discord.Interaction):
|
||||||
|
if interaction.user.id != self.user_id:
|
||||||
|
await interaction.response.send_message(S.ERR["not_your_menu"], ephemeral=True)
|
||||||
|
return
|
||||||
|
await economy.do_set_reminders(self.user_id, self.values)
|
||||||
|
enabled = set(self.values)
|
||||||
|
for cmd in [opt[0] for opt in S.REMINDER_OPTS]:
|
||||||
|
if cmd not in enabled:
|
||||||
|
cancel_reminder_task(self.user_id, cmd)
|
||||||
|
if self.values:
|
||||||
|
names = " ".join(f"`/{v}`" for v in self.values)
|
||||||
|
msg = S.REMINDERS_UI["saved_on"].format(names=names)
|
||||||
|
else:
|
||||||
|
msg = S.REMINDERS_UI["saved_off"]
|
||||||
|
await interaction.response.send_message(msg, ephemeral=True)
|
||||||
|
|
||||||
|
class RemindersView(discord.ui.View):
|
||||||
|
def __init__(self, user_id: int, current: list[str]):
|
||||||
|
super().__init__(timeout=60)
|
||||||
|
self.add_item(RemindersSelect(user_id, current))
|
||||||
|
|
||||||
|
@tree.command(name="reminders", description=S.CMD["reminders"])
|
||||||
|
async def cmd_reminders(interaction: discord.Interaction):
|
||||||
|
user_data = await economy.get_user(interaction.user.id)
|
||||||
|
current = user_data.get("reminders", [])
|
||||||
|
if current:
|
||||||
|
status = " ".join(f"`/{c}`" for c in current)
|
||||||
|
desc = S.REMINDERS_UI["desc_active"].format(status=status)
|
||||||
|
else:
|
||||||
|
desc = S.REMINDERS_UI["desc_none"]
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=S.TITLE["reminders"],
|
||||||
|
description=desc,
|
||||||
|
color=0x5865F2,
|
||||||
|
)
|
||||||
|
embed.set_footer(text=S.REMINDERS_UI["footer"])
|
||||||
|
await interaction.response.send_message(
|
||||||
|
embed=embed,
|
||||||
|
view=RemindersView(interaction.user.id, current),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
@@ -1121,6 +1121,7 @@ JAILBREAK_UI: dict[str, str] = {
|
|||||||
|
|
||||||
LEADERBOARD_UI: dict[str, str] = {
|
LEADERBOARD_UI: dict[str, str] = {
|
||||||
"house_entry": "🤖 {name} *(maja)* - {balance}",
|
"house_entry": "🤖 {name} *(maja)* - {balance}",
|
||||||
|
"house_default_name": "TipiBOT",
|
||||||
"no_entries": "Keegi ei ole veel punkte teeninud.",
|
"no_entries": "Keegi ei ole veel punkte teeninud.",
|
||||||
"footer": "Lehekülg {page}/{total} · {count} mängijat",
|
"footer": "Lehekülg {page}/{total} · {count} mängijat",
|
||||||
"btn_coins": "🪙 Mündid",
|
"btn_coins": "🪙 Mündid",
|
||||||
|
|||||||
Reference in New Issue
Block a user