diff --git a/bot.py b/bot.py index 9683203..4df7ca6 100644 --- a/bot.py +++ b/bot.py @@ -83,7 +83,7 @@ _txn_logger = logging.getLogger("tipiCOIN.txn") _txn_logger.addHandler(_txn_h) _txn_logger.propagate = False # don't double-log to console/bot.log -log = logging.getLogger("tipilan") +log = logging.getLogger(f"tipilan.{config.BOT_PROFILE}") # --------------------------------------------------------------------------- # Bot setup @@ -594,7 +594,12 @@ class HelpSelect(discord.ui.Select): @tree.command(name="help", description=S.CMD["help"]) async def cmd_help(interaction: discord.Interaction): +<<<<<<< HEAD is_admin = is_bot_admin(interaction.user) +======= + member = interaction.user + is_admin = isinstance(member, discord.Member) and is_bot_admin(member) +>>>>>>> 42f7bae68124fa6a9824780ba17b46d00f3f2b36 await interaction.response.send_message( embed=_help_embed("üldine"), view=HelpView(is_admin), ephemeral=True ) diff --git a/commands/economy_extra_commands.py b/commands/economy_extra_commands.py index 538eb92..3f0f4d9 100644 --- a/commands/economy_extra_commands.py +++ b/commands/economy_extra_commands.py @@ -10,6 +10,7 @@ import discord from discord import app_commands from core import economy +from core.emoji import EMOJI as E import strings as S @@ -288,12 +289,12 @@ def register_economy_extra_commands( # /jailbreak - Monopoly-style dice escape # ----------------------------------------------------------------------- _DICE_EMOJI = [ - "<:TipiYKS:1483103190491856916>", - "<:TipiKAKS:1483103215841972404>", - "<:TipiKOLM:1483103217846980781>", - "<:TipiNELI:1483103237585240114>", - "<:TipiVIIS:1483103239036469289>", - "<:TipiKUUS:1483103253163020348>", + E["TipiYKS"], + E["TipiKAKS"], + E["TipiKOLM"], + E["TipiNELI"], + E["TipiVIIS"], + E["TipiKUUS"], ] class JailbreakView(discord.ui.View): @@ -813,7 +814,6 @@ def register_economy_extra_commands( 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) @@ -822,8 +822,9 @@ def register_economy_extra_commands( async def callback(interaction: discord.Interaction): self._tier = tier self._update_buttons() + await interaction.response.defer() self._user_data = await economy.get_user(interaction.user.id) - await interaction.response.edit_message( + await interaction.edit_original_response( embed=_shop_embed(self._tier, self._user_data), view=self, ) diff --git a/commands/economy_games_commands.py b/commands/economy_games_commands.py index 2f882a0..5952f65 100644 --- a/commands/economy_games_commands.py +++ b/commands/economy_games_commands.py @@ -9,6 +9,7 @@ import discord from discord import app_commands from core import economy +from core.emoji import EMOJI as E import strings as S @@ -611,7 +612,7 @@ def register_economy_games_commands( # ----------------------------------------------------------------------- # /slots # ----------------------------------------------------------------------- - _SLOTS_SPIN = "" + _SLOTS_SPIN = E["TipiSLOTS"] _SLOTS_DELAY = 0.7 def _slots_embed( diff --git a/commands/economy_income_commands.py b/commands/economy_income_commands.py index b4789e2..c6b1088 100644 --- a/commands/economy_income_commands.py +++ b/commands/economy_income_commands.py @@ -22,12 +22,13 @@ def register_economy_income_commands( ) -> None: @tree.command(name="daily", description=S.CMD["daily"]) async def cmd_daily(interaction: discord.Interaction): + await interaction.response.defer() 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) + await interaction.followup.send(S.MSG_BANNED, ephemeral=True) elif res["reason"] == "cooldown": - await interaction.response.send_message( + await interaction.followup.send( S.CD_MSG["daily"].format(ts=cd_ts(res["remaining"])), ephemeral=True, ) @@ -51,7 +52,7 @@ def register_economy_income_commands( 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) + await interaction.followup.send(embed=embed) asyncio.create_task(maybe_remind(interaction.user.id, "daily")) asyncio.create_task(award_exp(interaction, economy.EXP_REWARDS["daily"])) @@ -59,17 +60,18 @@ def register_economy_income_commands( async def cmd_work(interaction: discord.Interaction): if await check_cmd_rate(interaction): return + await interaction.response.defer() 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) + await interaction.followup.send(S.MSG_BANNED, ephemeral=True) elif res["reason"] == "cooldown": - await interaction.response.send_message( + await interaction.followup.send( S.CD_MSG["work"].format(ts=cd_ts(res["remaining"])), ephemeral=True, ) elif res["reason"] == "jailed": - await interaction.response.send_message( + await interaction.followup.send( S.CD_MSG["jailed"].format(ts=cd_ts(res["remaining"])), ephemeral=True, ) @@ -84,7 +86,7 @@ def register_economy_income_commands( 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) + await interaction.followup.send(embed=embed) asyncio.create_task(maybe_remind(interaction.user.id, "work")) asyncio.create_task(award_exp(interaction, economy.EXP_REWARDS["work"])) @@ -92,12 +94,13 @@ def register_economy_income_commands( async def cmd_beg(interaction: discord.Interaction): if await check_cmd_rate(interaction): return + await interaction.response.defer() 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) + await interaction.followup.send(S.MSG_BANNED, ephemeral=True) elif res["reason"] == "cooldown": - await interaction.response.send_message( + await interaction.followup.send( S.CD_MSG["beg"].format(ts=cd_ts(res["remaining"])), ephemeral=True, ) @@ -114,7 +117,7 @@ def register_economy_income_commands( 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) + await interaction.followup.send(embed=embed) asyncio.create_task(maybe_remind(interaction.user.id, "beg")) asyncio.create_task(award_exp(interaction, economy.EXP_REWARDS["beg"])) @@ -122,17 +125,18 @@ def register_economy_income_commands( async def cmd_crime(interaction: discord.Interaction): if await check_cmd_rate(interaction): return + await interaction.response.defer() 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) + await interaction.followup.send(S.MSG_BANNED, ephemeral=True) elif res["reason"] == "cooldown": - await interaction.response.send_message( + await interaction.followup.send( S.CD_MSG["crime"].format(ts=cd_ts(res["remaining"])), ephemeral=True, ) elif res["reason"] == "jailed": - await interaction.response.send_message( + await interaction.followup.send( S.CD_MSG["jailed"].format(ts=cd_ts(res["remaining"])), ephemeral=True, ) @@ -161,7 +165,7 @@ def register_economy_income_commands( + S.CRIME_UI["balance"].format(balance=coin(res["balance"])), color=0xED4245, ) - await interaction.response.send_message(embed=embed) + await interaction.followup.send(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"])) @@ -180,27 +184,28 @@ def register_economy_income_commands( await interaction.response.send_message(S.ERR["rob_house_blocked"], ephemeral=True) return + await interaction.response.defer() 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) + await interaction.followup.send(S.MSG_BANNED, ephemeral=True) elif res["reason"] == "cooldown": - await interaction.response.send_message( + await interaction.followup.send( S.CD_MSG["rob"].format(ts=cd_ts(res["remaining"])), ephemeral=True, ) elif res["reason"] == "jailed": - await interaction.response.send_message( + await interaction.followup.send( S.CD_MSG["jailed"].format(ts=cd_ts(res["remaining"])), ephemeral=True, ) elif res["reason"] == "broke": - await interaction.response.send_message( + await interaction.followup.send( S.ERR["rob_too_poor"].format(name=sihtmärk.display_name), ephemeral=True, ) elif res["reason"] == "target_jailed": - await interaction.response.send_message( + await interaction.followup.send( S.ERR["rob_target_jailed"].format(name=sihtmärk.display_name), ephemeral=True, ) @@ -242,7 +247,7 @@ def register_economy_income_commands( ), color=0xED4245, ) - await interaction.response.send_message(embed=embed) + await interaction.followup.send(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"])) diff --git a/commands/ops_admin_commands.py b/commands/ops_admin_commands.py index 1b33258..00c1547 100644 --- a/commands/ops_admin_commands.py +++ b/commands/ops_admin_commands.py @@ -15,6 +15,7 @@ from discord import app_commands from core.admin import bot_admin_check import strings as S +from core.admin import bot_admin_check def register_ops_admin_commands( diff --git a/config.py b/config.py index 6887f42..1ecd1f2 100644 --- a/config.py +++ b/config.py @@ -75,3 +75,26 @@ PB_ECONOMY_COLLECTION_ECONOMY = ( PB_ECONOMY_COLLECTION = ( PB_ECONOMY_COLLECTION_ECONOMY if BOT_PROFILE == "economy" else PB_ECONOMY_COLLECTION_DEV ) + + +def _parse_admin_roles() -> dict[int, int]: + """Parse BOT_ADMIN_ROLES env var (format: guild_id:role_id,guild_id:role_id).""" + raw = os.getenv("BOT_ADMIN_ROLES", "").strip() + if not raw: + return {} + result: dict[int, int] = {} + for pair in raw.split(","): + pair = pair.strip() + if not pair: + continue + parts = pair.split(":") + if len(parts) != 2: + continue + try: + result[int(parts[0].strip())] = int(parts[1].strip()) + except ValueError: + continue + return result + + +BOT_ADMIN_ROLES: dict[int, int] = _parse_admin_roles() diff --git a/core/admin.py b/core/admin.py index 74d127f..8de866f 100644 --- a/core/admin.py +++ b/core/admin.py @@ -6,10 +6,15 @@ from discord import app_commands import config +<<<<<<< HEAD def is_bot_admin(member: discord.abc.User | None) -> bool: """True when the member has the configured admin role for their guild.""" if not isinstance(member, discord.Member) or member.guild is None: return False +======= +def is_bot_admin(member: discord.Member) -> bool: + """Return True if the member has the configured bot-admin role for their guild.""" +>>>>>>> 42f7bae68124fa6a9824780ba17b46d00f3f2b36 role_id = config.BOT_ADMIN_ROLES.get(member.guild.id) if role_id is None: return False @@ -17,11 +22,22 @@ def is_bot_admin(member: discord.abc.User | None) -> bool: def bot_admin_check(): +<<<<<<< HEAD """Slash-command decorator that gates execution behind ``is_bot_admin``.""" async def predicate(interaction: discord.Interaction) -> bool: if is_bot_admin(interaction.user): return True raise app_commands.MissingPermissions(["bot_admin_role"]) +======= + """Slash-command check decorator: raises MissingPermissions if not a bot admin.""" + def predicate(interaction: discord.Interaction) -> bool: + member = interaction.user + if not isinstance(member, discord.Member): + raise app_commands.MissingPermissions(["bot_admin"]) + if not is_bot_admin(member): + raise app_commands.MissingPermissions(["bot_admin"]) + return True +>>>>>>> 42f7bae68124fa6a9824780ba17b46d00f3f2b36 return app_commands.check(predicate) diff --git a/core/economy.py b/core/economy.py index 30d28ea..d85fa6a 100644 --- a/core/economy.py +++ b/core/economy.py @@ -17,6 +17,7 @@ import aiohttp from . import pb_client from .pb_client import DatabaseError +from .emoji import EMOJI as E import strings @@ -29,13 +30,9 @@ def _txn(event: str, **fields) -> None: _txn_log.info("%-16s %s", event, body) -# --------------------------------------------------------------------------- -# Emoji config -# To use your custom Discord emoji replace COIN with the full tag, e.g.: -# COIN = "<:tipicoin:1234567890123456789>" -# --------------------------------------------------------------------------- -COIN = "<:TipiCOIN:1483000209188589628>" -PP_EMOJI = "<:TipiFIRE:1483431381668335687>" +# Per-profile emoji values live in core/emoji.py; add new IDs there. +COIN = E["TipiCOIN"] +PP_EMOJI = E["TipiFIRE"] PRESTIGE_ROLE = "TipiPRESTIGE" PRESTIGE_MIN_LEVEL = 30 # minimum level required to prestige @@ -52,99 +49,99 @@ class ShopItem(TypedDict): SHOP: dict[str, ShopItem] = { "gaming_hiir": { "name": "Mängurihiir", - "emoji": "<:TipiHIIR:1483004306012504128>", + "emoji": E["TipiHIIR"], "cost": 500, "description": strings.ITEM_DESCRIPTIONS["gaming_hiir"], }, "hiirematt": { "name": "Hiirematt", - "emoji": "<:TipiMATT:1483387697132208128>", + "emoji": E["TipiMATT"], "cost": 600, "description": strings.ITEM_DESCRIPTIONS["hiirematt"], }, "korvaklapid": { "name": "K\u00f5rvaklapid", - "emoji": "<:TipiKLAPID:1483387694083084349>", + "emoji": E["TipiKLAPID"], "cost": 1200, "description": strings.ITEM_DESCRIPTIONS["korvaklapid"], }, "lan_pass": { "name": "LAN pilet", - "emoji": "<:TipiPILET:1483004308353060904>", + "emoji": E["TipiPILET"], "cost": 1200, "description": strings.ITEM_DESCRIPTIONS["lan_pass"], }, "energiajook": { "name": "Red Bull", - "emoji": "<:TipiBULL:1483004310924300409>", + "emoji": E["TipiBULL"], "cost": 800, "description": strings.ITEM_DESCRIPTIONS["energiajook"], }, "gaming_laptop": { "name": "Bot Farm", - "emoji": "<:TipiLAP:1483004307161874566>", + "emoji": E["TipiLAP"], "cost": 1500, "description": strings.ITEM_DESCRIPTIONS["gaming_laptop"], }, "anticheat": { "name": "Anticheat", - "emoji": "<:TipiVAC:1483004309510819860>", + "emoji": E["TipiVAC"], "cost": 1000, "description": strings.ITEM_DESCRIPTIONS["anticheat"], }, # ----- Tier 2 ----- "reguleeritav_laud": { "name": "Reguleeritav laud", - "emoji": "<:TipiLAUD:1483387695576125440>", + "emoji": E["TipiLAUD"], "cost": 3500, "description": strings.ITEM_DESCRIPTIONS["reguleeritav_laud"], }, "jellyfin": { "name": "Jellyfin server", - "emoji": "<:TipiSERVER:1483387701032910969>", + "emoji": E["TipiSERVER"], "cost": 4000, "description": strings.ITEM_DESCRIPTIONS["jellyfin"], }, "mikrofon": { "name": "Eraldiseisev mikrofon", - "emoji": "<:TipiMIC:1483387698499551313>", + "emoji": E["TipiMIC"], "cost": 2800, "description": strings.ITEM_DESCRIPTIONS["mikrofon"], }, "klaviatuur": { "name": "Mehaaniline klaviatuur", - "emoji": "<:TipiKLAVA:1483014339228078140>", + "emoji": E["TipiKLAVA"], "cost": 1800, "description": strings.ITEM_DESCRIPTIONS["klaviatuur"], }, "monitor": { "name": "Ultralai monitor", - "emoji": "<:TipiMONITOR:1483014340327243908>", + "emoji": E["TipiMONITOR"], "cost": 2500, "description": strings.ITEM_DESCRIPTIONS["monitor"], }, "cat6": { "name": "Cat6 kaabel", - "emoji": "<:TipiCAT:1483014337663602718>", + "emoji": E["TipiCAT"], "cost": 3500, "description": strings.ITEM_DESCRIPTIONS["cat6"], }, # ----- Tier 3 ----- "monitor_360": { "name": "360Hz monitor", - "emoji": "<:TipiMONITOR2:1483387699514839162>", + "emoji": E["TipiMONITOR2"], "cost": 7500, "description": strings.ITEM_DESCRIPTIONS["monitor_360"], }, "karikas": { "name": "TipiLAN karikas", - "emoji": "<:TipiKARIKAS:1483014841148112977>", + "emoji": E["TipiKARIKAS"], "cost": 6000, "description": strings.ITEM_DESCRIPTIONS["karikas"], }, "gaming_tool": { "name": "Gaming tool", - "emoji": "<:TipiTOOL:1483014341648187613>", + "emoji": E["TipiTOOL"], "cost": 9000, "description": strings.ITEM_DESCRIPTIONS["gaming_tool"], }, @@ -204,7 +201,7 @@ class PrestigeItem(TypedDict): PRESTIGE_SHOP: dict[str, PrestigeItem] = { "coin_mult": { - "emoji": "<:TipiCOIN:1483000209188589628>", + "emoji": E["TipiCOIN"], "max_level": 5, "pp_cost": 5, "effect": 0.08, @@ -1482,21 +1479,21 @@ async def do_rps_pvp_refund(user_id: int, bet: int) -> dict: # /slots # --------------------------------------------------------------------------- _SLOTS_SYMBOLS: list[tuple[str, int]] = [ - ("<:TipiHEART:1483431377561976853>", 27), - ("<:TipiFIRE:1483431381668335687>", 22), - ("<:TipiTROLL:1483431380166774895>", 18), - ("<:TipICRY:1483431288852709387>", 15), - ("<:TipiSKULL:1483431378929451028>", 10), - ("<:TipiKARIKAS:1483014841148112977>", 8), + (E["TipiHEART"], 27), + (E["TipiFIRE"], 22), + (E["TipiTROLL"], 18), + (E["TipICRY"], 15), + (E["TipiSKULL"], 10), + (E["TipiKARIKAS"], 8), ] -_SLOTS_JACKPOT = "<:TipiKARIKAS:1483014841148112977>" +_SLOTS_JACKPOT = E["TipiKARIKAS"] _SLOTS_TRIPLE_MULT: dict[str, int] = { - "<:TipiHEART:1483431377561976853>": 4, - "<:TipiFIRE:1483431381668335687>": 5, - "<:TipiTROLL:1483431380166774895>": 7, - "<:TipICRY:1483431288852709387>": 10, - "<:TipiSKULL:1483431378929451028>": 15, - "<:TipiKARIKAS:1483014841148112977>": 25, # jackpot + E["TipiHEART"]: 4, + E["TipiFIRE"]: 5, + E["TipiTROLL"]: 7, + E["TipICRY"]: 10, + E["TipiSKULL"]: 15, + E["TipiKARIKAS"]: 25, # jackpot } diff --git a/core/emoji.py b/core/emoji.py new file mode 100644 index 0000000..af44d9a --- /dev/null +++ b/core/emoji.py @@ -0,0 +1,66 @@ +"""Custom Discord emoji registry, keyed by symbolic name and resolved per BOT_PROFILE. + +Emojis are uploaded as application emojis via the Discord Developer Portal +and are scoped to a single bot application. Dev and production are separate +applications, so the same logical emoji has a different ID in each — hence +two dicts below, selected by BOT_PROFILE. + +To add a new emoji: upload it to both applications in the dev portal, grab +the two IDs, and add one line to each dict. +""" + +from __future__ import annotations + +from config import BOT_PROFILE + + +_DEV: dict[str, str] = { + "TipiCOIN": "<:TipiCOIN:1483000209188589628>", + "TipiFIRE": "<:TipiFIRE:1483431381668335687>", + "TipiHIIR": "<:TipiHIIR:1483004306012504128>", + "TipiMATT": "<:TipiMATT:1483387697132208128>", + "TipiKLAPID": "<:TipiKLAPID:1483387694083084349>", + "TipiPILET": "<:TipiPILET:1483004308353060904>", + "TipiBULL": "<:TipiBULL:1483004310924300409>", + "TipiLAP": "<:TipiLAP:1483004307161874566>", + "TipiVAC": "<:TipiVAC:1483004309510819860>", + "TipiLAUD": "<:TipiLAUD:1483387695576125440>", + "TipiSERVER": "<:TipiSERVER:1483387701032910969>", + "TipiMIC": "<:TipiMIC:1483387698499551313>", + "TipiKLAVA": "<:TipiKLAVA:1483014339228078140>", + "TipiMONITOR": "<:TipiMONITOR:1483014340327243908>", + "TipiCAT": "<:TipiCAT:1483014337663602718>", + "TipiMONITOR2": "<:TipiMONITOR2:1483387699514839162>", + "TipiKARIKAS": "<:TipiKARIKAS:1483014841148112977>", + "TipiTOOL": "<:TipiTOOL:1483014341648187613>", + "TipiHEART": "<:TipiHEART:1483431377561976853>", + "TipiTROLL": "<:TipiTROLL:1483431380166774895>", + "TipICRY": "<:TipICRY:1483431288852709387>", + "TipiSKULL": "<:TipiSKULL:1483431378929451028>", + "TipiDICE": "<:TipiDICE:1485923107108556950>", + "TipiYKS": "<:TipiYKS:1483103190491856916>", + "TipiKAKS": "<:TipiKAKS:1483103215841972404>", + "TipiKOLM": "<:TipiKOLM:1483103217846980781>", + "TipiNELI": "<:TipiNELI:1483103237585240114>", + "TipiVIIS": "<:TipiVIIS:1483103239036469289>", + "TipiKUUS": "<:TipiKUUS:1483103253163020348>", + "TipiSLOTS": "", +} + +# Production application emoji IDs. Replace each ID with the one from the +# prod application in the dev portal. The dev IDs below are placeholders so +# this dict is structurally complete — they will NOT render in prod because +# they belong to a different application. +_ECONOMY: dict[str, str] = dict(_DEV) + + +_EMOJI_SETS = { + "dev": _DEV, + "economy": _ECONOMY, +} + +EMOJI: dict[str, str] = _EMOJI_SETS[BOT_PROFILE] + +_missing = set(_DEV) - set(EMOJI) +if _missing: + raise RuntimeError(f"Emoji set {BOT_PROFILE!r} missing keys: {sorted(_missing)}") diff --git a/strings.py b/strings.py index 4275ee7..7de447b 100644 --- a/strings.py +++ b/strings.py @@ -4,6 +4,8 @@ Edit this file to change any message, description, or flavour text without touching any logic code. """ +from core.emoji import EMOJI as E + # --------------------------------------------------------------------------- # Flavour text # --------------------------------------------------------------------------- @@ -256,22 +258,22 @@ HELP_CATEGORIES: dict[str, dict] = { "description": "TipiBOTi poe esemed ja nende efektid", "color": 0xF4C430, "fields": [ - ("<:TipiHIIR:1483004306012504128> Mängurihiir - 500 ⬡", "Teeni töötades 50% rohkem TipiCOINe."), - ("<:TipiMATT:1483387697132208128> XL hiirematt - 600 ⬡", "Kerjamise ooteaeg 5min → 3min."), - ("<:TipiKLAPID:1483387694083084349> Kõrvaklapid - 1200 ⬡", "Päevase boonuse ooteaeg 20h → 18h."), - ("<:TipiPILET:1483004308353060904> LAN pilet (2025) - 1200 ⬡", "Päevane boonus on duubeldatud."), - ("<:TipiVAC:1483004309510819860> Anticheat - 750 ⬡", "Röövimine sinu vastu ebaõnnestub. Pärast 2 kasutust pead ostma uue."), - ("<:TipiBULL:1483004310924300409> Red Bull - 800 ⬡", "30% tõenäosus, et teenid töötades 3x rohkem."), - ("<:TipiLAP:1483004307161874566> Botikoobas - 1500 ⬡", "RTX 5090 jooksutab botte 24/7. Päevane boonus genereerib 5% intressi sinu saldo pealt."), - ("<:TipiLAUD:1483387695576125440> Reguleeritav laud - 3500 ⬡ *(T2)*", "/work teenib 25% rohkem (stackib mängurihiirega)."), - ("<:TipiSERVER:1483387701032910969> Jellyfin server - 4000 ⬡ *(T2)*", "Röövimise edu tõenäosus 45% → 60%."), - ("<:TipiMIC:1483387698499551313> Mikrofon - 2800 ⬡ *(T2)*", "Teeni 30% rohkem eduka /crime puhul."), - ("<:TipiKLAVA:1483014339228078140> Mehhaaniline klaviatuur - 1800 ⬡ *(T2)*", "/beg teenib 2x rohkem."), - ("<:TipiMONITOR:1483014340327243908> Ultralai monitor - 2500 ⬡ *(T2)*", "/work ooteaeg: 1h → 40min."), - ("<:TipiCAT:1483014337663602718> CAT6 netikaabel - 3500 ⬡ *(T2)*", "/crime edu tõenäosus tõuseb 60% → 75%."), - ("<:TipiMONITOR2:1483387699514839162> 360hz monitor - 7500 ⬡ *(T3)*", "Mänguautomaadi jackpot 10x → 15x, kolmik 4x → 6x."), - ("<:TipiKARIKAS:1483014841148112977> TipiLANi trofee - 6000 ⬡ *(T3)*", "Streak ei nulli, kui sa mõne päeva vahele jätad."), - ("<:TipiTOOL:1483014341648187613> Mänguritool - 9000 ⬡ *(T3)*", "/crime ebaõnnestumine ei saada sind vanglasse."), + (f"{E['TipiHIIR']} Mängurihiir - 500 ⬡", "Teeni töötades 50% rohkem TipiCOINe."), + (f"{E['TipiMATT']} XL hiirematt - 600 ⬡", "Kerjamise ooteaeg 5min → 3min."), + (f"{E['TipiKLAPID']} Kõrvaklapid - 1200 ⬡", "Päevase boonuse ooteaeg 20h → 18h."), + (f"{E['TipiPILET']} LAN pilet (2025) - 1200 ⬡", "Päevane boonus on duubeldatud."), + (f"{E['TipiVAC']} Anticheat - 750 ⬡", "Röövimine sinu vastu ebaõnnestub. Pärast 2 kasutust pead ostma uue."), + (f"{E['TipiBULL']} Red Bull - 800 ⬡", "30% tõenäosus, et teenid töötades 3x rohkem."), + (f"{E['TipiLAP']} Botikoobas - 1500 ⬡", "RTX 5090 jooksutab botte 24/7. Päevane boonus genereerib 5% intressi sinu saldo pealt."), + (f"{E['TipiLAUD']} Reguleeritav laud - 3500 ⬡ *(T2)*", "/work teenib 25% rohkem (stackib mängurihiirega)."), + (f"{E['TipiSERVER']} Jellyfin server - 4000 ⬡ *(T2)*", "Röövimise edu tõenäosus 45% → 60%."), + (f"{E['TipiMIC']} Mikrofon - 2800 ⬡ *(T2)*", "Teeni 30% rohkem eduka /crime puhul."), + (f"{E['TipiKLAVA']} Mehhaaniline klaviatuur - 1800 ⬡ *(T2)*", "/beg teenib 2x rohkem."), + (f"{E['TipiMONITOR']} Ultralai monitor - 2500 ⬡ *(T2)*", "/work ooteaeg: 1h → 40min."), + (f"{E['TipiCAT']} CAT6 netikaabel - 3500 ⬡ *(T2)*", "/crime edu tõenäosus tõuseb 60% → 75%."), + (f"{E['TipiMONITOR2']} 360hz monitor - 7500 ⬡ *(T3)*", "Mänguautomaadi jackpot 10x → 15x, kolmik 4x → 6x."), + (f"{E['TipiKARIKAS']} TipiLANi trofee - 6000 ⬡ *(T3)*", "Streak ei nulli, kui sa mõne päeva vahele jätad."), + (f"{E['TipiTOOL']} Mänguritool - 9000 ⬡ *(T3)*", "/crime ebaõnnestumine ei saada sind vanglasse."), ], }, "games": { @@ -347,10 +349,10 @@ REMINDER_OPTS: list[tuple[str, str, str]] = [ # --------------------------------------------------------------------------- SLOTS_TIERS: dict[str, tuple[str, int]] = { - "jackpot": ("<:TipiFIRE:1483431381668335687> JACKPOT!!!", 0xF4C430), + "jackpot": (f"{E['TipiFIRE']} JACKPOT!!!", 0xF4C430), "triple": ("🎰 Kolmik!", 0x57F287), "pair": ("🎰 Paar", 0x99AAB5), - "miss": ("<:TipICRY:1483431288852709387> Ei õnnestunud", 0xED4245), + "miss": (f"{E['TipICRY']} Ei õnnestunud", 0xED4245), } # --------------------------------------------------------------------------- @@ -534,22 +536,22 @@ TITLE: dict[str, str] = { "daily": "📅 Päevane boonus", "work": "💼 Töö", "beg": "🙏 Kerjamine", - "crime_win": "<:TipiFIRE:1483431381668335687> Kuritegu õnnestus!", - "crime_fail": "<:TipiTROLL:1483431380166774895> Vahele jäid!", - "rob_win": "<:TipiFIRE:1483431381668335687> Rööv õnnestus!", - "rob_fail": "<:TipiTROLL:1483431380166774895> Rööv ebaõnnestus!", - "rob_anticheat": "<:TipiVAC:1483004309510819860> Anticheat peatas sind!", + "crime_win": f"{E['TipiFIRE']} Kuritegu õnnestus!", + "crime_fail": f"{E['TipiTROLL']} Vahele jäid!", + "rob_win": f"{E['TipiFIRE']} Rööv õnnestus!", + "rob_fail": f"{E['TipiTROLL']} Rööv ebaõnnestus!", + "rob_anticheat": f"{E['TipiVAC']} Anticheat peatas sind!", "jailbreak": "🎲 Vanglast põgenemine", - "jailbreak_free": "🎲 <:TipiFIRE:1483431381668335687> DUUBEL! Oled vaba!", - "jailbreak_fail": "<:TipICRY:1483431288852709387> Kolm katset läbi!", - "jailbreak_miss": "🎲 <:TipICRY:1483431288852709387> Ei saanud duublit ({tries}/{max})", + "jailbreak_free": f"🎲 {E['TipiFIRE']} DUUBEL! Oled vaba!", + "jailbreak_fail": f"{E['TipICRY']} Kolm katset läbi!", + "jailbreak_miss": "🎲 " + E["TipICRY"] + " Ei saanud duublit ({tries}/{max})", "jailbreak_bail": "💸 Kautsjon", - "give": "<:TipiHEART:1483431377561976853> TipiCOINi ülekanne", + "give": f"{E['TipiHEART']} TipiCOINi ülekanne", "stats": "📊 Mängustatistika", "leaderboard_coins": "🪙 TipiBOTi edetabel - Mündid", "leaderboard_exp": "📊 TipiBOTi edetabel - EXP / Tase", "leaderboard_season": "🏆 TipiBOTi edetabel - Hooaja EXP", - "leaderboard_prestige": "<:TipiFIRE:1483431381668335687> TipiBOTi edetabel - Prestiiž", + "leaderboard_prestige": f"{E['TipiFIRE']} TipiBOTi edetabel - Prestiiž", "leaderboard_wagered": "🎲 TipiBOTi edetabel - Hasartmängud", "leaderboard_fish": "🎣 TipiBOTi edetabel - Kalapüük", "rps": "⚔️ Kivi, Paber, Käärid", @@ -560,26 +562,26 @@ TITLE: dict[str, str] = { "rps_duel_expire": "⚔️ KPK duell - aegus", "rps_duel_decline": "⚔️ KPK duell - keelduti", "heist_lobby": "🔫 Grupirööv - kogunemine", - "heist_win": "<:TipiFIRE:1483431381668335687> Grupirööv õnnestus!", - "heist_fail": "<:TipiSKULL:1483431378929451028> Grupirööv ebaõnnestus!", + "heist_win": f"{E['TipiFIRE']} Grupirööv õnnestus!", + "heist_fail": f"{E['TipiSKULL']} Grupirööv ebaõnnestus!", "heist_cancel": "🔫 Grupirööv tühistatud", - "request": "<:TipiHEART:1483431377561976853> Rahataotlus", + "request": f"{E['TipiHEART']} Rahataotlus", "reminders": "⏰ Meeldetuletused", "cooldowns": "⏱️ Sinu ooteajad", "adminseason": "🏆 Hooaeg lõppes!", "economysetup": "⚙️ Majanduse seadistamine", "blackjack": "🃏 Blackjack", - "blackjack_bj": "🃏 <:TipiFIRE:1483431381668335687> BLACKJACK!", - "blackjack_win": "<:TipiFIRE:1483431381668335687> Võitsid!", - "blackjack_lose": "<:TipiSKULL:1483431378929451028> Kaotasid!", - "blackjack_bust": "<:TipiSKULL:1483431378929451028> Üle 21 - kaotasid!", + "blackjack_bj": f"🃏 {E['TipiFIRE']} BLACKJACK!", + "blackjack_win": f"{E['TipiFIRE']} Võitsid!", + "blackjack_lose": f"{E['TipiSKULL']} Kaotasid!", + "blackjack_bust": f"{E['TipiSKULL']} Üle 21 - kaotasid!", "blackjack_push": "🤝 Viik!", - "blackjack_dbust": "<:TipiSKULL:1483431378929451028> Üle 21 - mõlemad kaotasid!", - "blackjack_dwin": "<:TipiFIRE:1483431381668335687> Topeltpanus võitis!", + "blackjack_dbust": f"{E['TipiSKULL']} Üle 21 - mõlemad kaotasid!", + "blackjack_dwin": f"{E['TipiFIRE']} Topeltpanus võitis!", "prestige_confirm": "🔥 Prestiiž - kinnita", - "prestige_success": "<:TipiFIRE:1483431381668335687> Prestiiž {level} saavutatud!", + "prestige_success": E["TipiFIRE"] + " Prestiiž {level} saavutatud!", "prestige_too_low": "❌ Prestiiž pole saadaval", - "prestige_shop": "<:TipiFIRE:1483431381668335687> Prestiižipood", + "prestige_shop": f"{E['TipiFIRE']} Prestiižipood", "prestige_buy_ok": "✅ Uuendus ostetud!", "fish_cast": "🎣 Otsid kala...", "fish_bite": "🐟 KALA NÄKKAB!", @@ -609,8 +611,8 @@ ERR: dict[str, str] = { "heist_active": "❌ Serveris on juba aktiivne grupirööv käimas! Oota, kuni see lõpeb.", "heist_full": "❌ Grupirööv on täis!", "heist_min_players": "❌ Grupiröövi alustamiseks on vaja vähemalt **{min}** osalejat.", - "broke": "<:TipICRY:1483431288852709387> Sul pole piisavalt TipiCOINe. Saldo: {bal}", - "broke_need": "<:TipICRY:1483431288852709387> Sul pole piisavalt TipiCOINe. Vajad veel {need}.", + "broke": E["TipICRY"] + " Sul pole piisavalt TipiCOINe. Saldo: {bal}", + "broke_need": E["TipICRY"] + " Sul pole piisavalt TipiCOINe. Vajad veel {need}.", "item_owned": "❌ Sul on see ese juba olemas.", "item_not_found": "❌ Eset ei leitud.", "item_level_req": "🔒 Selle eseme ostmiseks vajad **taset {min_level}** (sul on tase {user_level}). Teeni EXP-id kõiki käske kasutades.", @@ -653,7 +655,7 @@ CD_MSG: dict[str, str] = { "rob": "⏳ Saad uuesti röövida {ts}.", "heist": "⏳ Saad uuesti heisti teha {ts}.", "heist_global": "⏳ Pangahoidla alles kosub eelmisest röövist. Järgmine heist võimalik {ts}.", - "jailed": "<:TipiTROLL:1483431380166774895> Oled vangis! Pääsed välja {ts}. Kasuta `/jailbreak`, et varem välja pääseda.", + "jailed": E["TipiTROLL"] + " Oled vangis! Pääsed välja {ts}. Kasuta `/jailbreak`, et varem välja pääseda.", "fish": "🎣 Saad uuesti kalastada {ts}.", } @@ -837,8 +839,8 @@ CHANNEL_UI: dict[str, str] = { DAILY_UI: dict[str, str] = { "earned": "✅ Said {earned}!", - "interest": "<:TipiLAP:1483004307161874566> Bot Farm tootis: +{interest}", - "vip": "<:TipiPILET:1483004308353060904> LAN pileti boonus rakendus!", + "interest": E["TipiLAP"] + " Bot Farm tootis: +{interest}", + "vip": f"{E['TipiPILET']} LAN pileti boonus rakendus!", "footer": "Streak: {streak_str} · Saldo: {balance}", } @@ -1050,9 +1052,9 @@ RANK_UI: dict[str, str] = { WORK_UI: dict[str, str] = { "desc": "Sa {job} ja teenisid {earned}!", - "redbull": "\n<:TipiBULL:1483004310924300409> Red Bull aktiveerus - 3x boonus!", - "hiir": "\n<:TipiHIIR:1483004306012504128> Mängurihiir: +50% palk", - "laud": "\n<:TipiLAUD:1483387695576125440> Reguleeritav laud: +25% palk", + "redbull": f"\n{E['TipiBULL']} Red Bull aktiveerus - 3x boonus!", + "hiir": f"\n{E['TipiHIIR']} Mängurihiir: +50% palk", + "laud": f"\n{E['TipiLAUD']} Reguleeritav laud: +25% palk", "balance": "\nSaldo: {balance}", } @@ -1062,7 +1064,7 @@ WORK_UI: dict[str, str] = { BEG_UI: dict[str, str] = { "desc": "Sa {text} ja said {earned}.", - "klaviatuur": "<:TipiKLAVA:1483014339228078140> Mehhaaniline klaviatuur: 2x tulu", + "klaviatuur": f"{E['TipiKLAVA']} Mehhaaniline klaviatuur: 2x tulu", "balance": "Saldo: {balance}", } @@ -1075,8 +1077,8 @@ CRIME_UI: dict[str, str] = { "fail_base": "Sa {text} ja said trahvi {fine}.", "fail_jailed": "\n\ud83d\udd12 Oled vangis! P\u00e4\u00e4sed {ts}.", "fail_shield": "\n\ud83d\udee1\ufe0f Gaming Tool hoidis sind vanglast!", - "mikrofon": "\n<:TipiMIC:1483387698499551313> Mikrofon: +30% saak", - "cat6": "\n<:TipiCAT:1483014337663602718> CAT6: 75% edu t\u00f5en\u00e4osus", + "mikrofon": f"\n{E['TipiMIC']} Mikrofon: +30% saak", + "cat6": f"\n{E['TipiCAT']} CAT6: 75% edu t\u00f5en\u00e4osus", "balance": "\nSaldo: {balance}", } @@ -1116,7 +1118,7 @@ BUY_UI: dict[str, str] = { JAILBREAK_UI: dict[str, str] = { "btn_roll": "🎲 Viska täringud ({try_}/{max})", - "rolling_desc": "<:TipiDICE:1485923107108556950> *Täringud lendavad...*", + "rolling_desc": f"{E['TipiDICE']} *Täringud lendavad...*", "free_desc": "{d1} {d2}\n\n✅ Viskasid duubli - pääsesid vanglast!", "miss_desc": "{d1} {d2}\n\n{left} katset jäänud. Proovi uuesti!", "intro_desc": "Oled vangis kuni {ts}.\n\nViska täringuid ja proovi **duublit** saada - siis pääsed tasuta vabaks!\nSul on **{tries} katset**. Ebaõnnestumisel saad valida: maksa kautsjon **(20–30% saldost, min 350 ⬡)** või jää vanglasse kuni aja lõpuni.", @@ -1160,7 +1162,7 @@ LEADERBOARD_UI: dict[str, str] = { SLOTS_UI: dict[str, str] = { "playing": "🎰 Mängimas...", - "jackpot_footer": "<:TipiKARIKAS:1483014841148112977> Kolm karikat! +{change}", + "jackpot_footer": E["TipiKARIKAS"] + " Kolm karikat! +{change}", "triple_footer": "✅ Kolm ühesugust! +{change}", "pair_footer": "Kaks ühesugust! +{change}", "miss_footer": "-{amount}", @@ -1264,7 +1266,7 @@ PRESTIGE_SHOP_DESCRIPTIONS: dict[str, str] = { PRESTIGE_UI: dict[str, str] = { "confirm_desc": ( "Oled tasemel **{level}** ({exp} EXP).\n\n" - "Prestiiži korral saad **{pp}** <:TipiFIRE:1483431381668335687> ja kõik lähtestub:\n" + "Prestiiži korral saad **{pp}** " + E["TipiFIRE"] + " ja kõik lähtestub:\n" "• Saldo, EXP, esemed, ooteajad\n\n" "**Kalakogu jääb alles!**\n\nKas oled kindel?" ), @@ -1273,21 +1275,21 @@ PRESTIGE_UI: dict[str, str] = { "btn_tab_status": "⭐ Prestiiz", "btn_tab_shop": "🛍️ Uuendused", "success_desc": ( - "Said **{pp}** <:TipiFIRE:1483431381668335687>\n" + "Said **{pp}** " + E["TipiFIRE"] + "\n" "Prestiiži tase: **{level}**\n" - "Kogutud PP: **{total_pp}** <:TipiFIRE:1483431381668335687>\n\n" + "Kogutud PP: **{total_pp}** " + E["TipiFIRE"] + "\n\n" "*Kõik lähtestati. Alusta otsast!*" ), "too_low_desc": "Prestiiži jaoks vajad taset **{required}** (sul on tase {level}).", - "shop_desc": "Sul on **{pp}** <:TipiFIRE:1483431381668335687> · Vajuta nuppu uuenduse ostmiseks", + "shop_desc": "Sul on **{pp}** " + E["TipiFIRE"] + " · Vajuta nuppu uuenduse ostmiseks", "shop_maxed": "✅ Max", "shop_level_fmt": "Tase {cur}/{max}", - "shop_cost_fmt": "{cost} <:TipiFIRE:1483431381668335687>", - "buy_success_desc":"**{name}** uuendatud tasemele **{new_level}/{max_level}**!\nPP alles: **{pp}** <:TipiFIRE:1483431381668335687>", - "buy_no_pp": "<:TipICRY:1483431288852709387> Sul pole piisavalt PP. Sul on **{have}**, vajad **{need}** <:TipiFIRE:1483431381668335687>.", + "shop_cost_fmt": "{cost} " + E["TipiFIRE"], + "buy_success_desc":"**{name}** uuendatud tasemele **{new_level}/{max_level}**!\nPP alles: **{pp}** " + E["TipiFIRE"], + "buy_no_pp": E["TipICRY"] + " Sul pole piisavalt PP. Sul on **{have}**, vajad **{need}** " + E["TipiFIRE"] + ".", "buy_maxed": "❌ See uuendus on juba maksimumtasemel.", "buy_not_found": "❌ Sellist uuendust ei leitud. Vaata `/prestigeshop`.", - "rank_line": "<:TipiFIRE:1483431381668335687> Prestiiž **{level}** · {pp} PP", + "rank_line": E["TipiFIRE"] + " Prestiiž **{level}** · {pp} PP", "rank_season": "🏆 Hooaja EXP: **{exp}**", "btn_buy_upgrade": "{emoji} {name} +1 ({cost} PP)", "status_footer": "⭐ Prestiiž {level} · {pp} PP",