move some commands to standard (#1256)

This commit is contained in:
NotSoToothless
2026-05-16 16:04:37 -07:00
committed by GitHub
parent 818ad8aea7
commit ff379c7843
16 changed files with 112 additions and 20 deletions
+8
View File
@@ -93,6 +93,7 @@ from .utils import (
invalidate_entitled_guilds_cache, invalidate_entitled_guilds_cache,
is_admin, is_admin,
is_blacklisted, is_blacklisted,
gate_entitle,
is_guild_entitled, is_guild_entitled,
get_guild_tier, get_guild_tier,
permission_fail, permission_fail,
@@ -642,6 +643,7 @@ async def comp_perm_error(interaction, error):
@is_blacklisted() @is_blacklisted()
@is_admin() @is_admin()
@gate_entitle("standard")
@bot.tree.command( @bot.tree.command(
name="quick-log", name="quick-log",
description=command_locale("Quickly set an alarm for this squadron in this channel", "commands.quick_log.description") description=command_locale("Quickly set an alarm for this squadron in this channel", "commands.quick_log.description")
@@ -1979,6 +1981,7 @@ RECAP_THEME_CHOICES = [
@is_blacklisted() @is_blacklisted()
@gate_entitle("standard")
@bot.tree.command(name="sq-card", description=command_locale("Generate a season recap card for a squadron", "commands.sq_card.description")) @bot.tree.command(name="sq-card", description=command_locale("Generate a season recap card for a squadron", "commands.sq_card.description"))
@app_commands.describe( @app_commands.describe(
season=command_locale("The season to generate the card for", "commands.common.season"), season=command_locale("The season to generate the card for", "commands.common.season"),
@@ -3414,6 +3417,7 @@ class CardPlayerSelectView(View):
@is_blacklisted() @is_blacklisted()
@gate_entitle("standard")
@bot.tree.command(name="card", description=command_locale("Generate a season recap card for a player", "commands.card.description")) @bot.tree.command(name="card", description=command_locale("Generate a season recap card for a player", "commands.card.description"))
@app_commands.describe( @app_commands.describe(
season=command_locale("The season to generate the card for", "commands.common.season"), season=command_locale("The season to generate the card for", "commands.common.season"),
@@ -6232,6 +6236,7 @@ class MetaManagementView(discord.ui.View):
@is_blacklisted() @is_blacklisted()
@is_admin() @is_admin()
@gate_entitle("standard")
@bot.tree.command(name='meta-management', description=command_locale('Manage meta data access settings for this server', "commands.meta_management.description")) @bot.tree.command(name='meta-management', description=command_locale('Manage meta data access settings for this server', "commands.meta_management.description"))
async def meta_management(interaction: discord.Interaction): async def meta_management(interaction: discord.Interaction):
"""Manage meta data access settings for the guild's squadron. """Manage meta data access settings for the guild's squadron.
@@ -6564,6 +6569,7 @@ class MetaResultsView(discord.ui.View):
@is_blacklisted() @is_blacklisted()
@gate_entitle("standard")
@bot.tree.command(name='meta', description=command_locale('Search squadron meta roster by vehicle name', "commands.meta.description")) @bot.tree.command(name='meta', description=command_locale('Search squadron meta roster by vehicle name', "commands.meta.description"))
@discord.app_commands.describe(vehicle=command_locale("Vehicle name to search for", "commands.meta.vehicle")) @discord.app_commands.describe(vehicle=command_locale("Vehicle name to search for", "commands.meta.vehicle"))
@discord.app_commands.autocomplete(vehicle=meta_vehicle_autocomplete) @discord.app_commands.autocomplete(vehicle=meta_vehicle_autocomplete)
@@ -7011,6 +7017,7 @@ async def translate_message(
@is_blacklisted() @is_blacklisted()
@gate_entitle("standard")
@bot.tree.command(name="sq-track", description=command_locale("Track a squadron and compare stats against the last check", "commands.sq_track.description")) @bot.tree.command(name="sq-track", description=command_locale("Track a squadron and compare stats against the last check", "commands.sq_track.description"))
@app_commands.describe( @app_commands.describe(
squadron_short_name=command_locale("Short name of the squadron to track", "commands.sq_track.squadron_short_name") squadron_short_name=command_locale("Short name of the squadron to track", "commands.sq_track.squadron_short_name")
@@ -7296,6 +7303,7 @@ class ConsistencyPaginatorView(discord.ui.View):
@is_blacklisted() @is_blacklisted()
@gate_entitle("standard")
@bot.tree.command(name="analytics", description=command_locale("View advanced SQB analytics for a squadron", "commands.analytics.description")) @bot.tree.command(name="analytics", description=command_locale("View advanced SQB analytics for a squadron", "commands.analytics.description"))
@app_commands.describe( @app_commands.describe(
squadron=command_locale("Squadron short name", "commands.common.squadron_short"), squadron=command_locale("Squadron short name", "commands.common.squadron_short"),
+5 -1
View File
@@ -839,7 +839,11 @@
"reason_line": "**Důvod:** {reason}", "reason_line": "**Důvod:** {reason}",
"access_denied_title": "⛔ Přístup odepřen", "access_denied_title": "⛔ Přístup odepřen",
"no_permission_desc": "Nemáš oprávnění použít tento příkaz.", "no_permission_desc": "Nemáš oprávnění použít tento příkaz.",
"unexpected_error_title": "❗ Chyba, nahlas ji...." "unexpected_error_title": "❗ Chyba, nahlas ji....",
"tier_gate_title": "🔒 Vyžadováno prémium",
"tier_gate_standard_desc": "Tento příkaz vyžaduje úroveň **Standard** nebo vyšší. Použij `/unlock` k odběru.",
"tier_gate_pro_desc": "Tento příkaz vyžaduje úroveň **Pro** nebo vyšší. Použij `/unlock` k odběru.",
"tier_gate_max_desc": "Tento příkaz vyžaduje úroveň **Max**. Použij `/unlock` k odběru."
}, },
"weekly_br": { "weekly_br": {
"title_wildcard": "Týdenní zpráva BR — {br} BR", "title_wildcard": "Týdenní zpráva BR — {br} BR",
+5 -1
View File
@@ -839,7 +839,11 @@
"reason_line": "**Grund:** {reason}", "reason_line": "**Grund:** {reason}",
"access_denied_title": "⛔ Zugriff verweigert", "access_denied_title": "⛔ Zugriff verweigert",
"no_permission_desc": "Du hast keine Berechtigung für diesen Command.", "no_permission_desc": "Du hast keine Berechtigung für diesen Command.",
"unexpected_error_title": "❗ Fehler, bitte melden...." "unexpected_error_title": "❗ Fehler, bitte melden....",
"tier_gate_title": "🔒 Premium erforderlich",
"tier_gate_standard_desc": "Dieser Befehl benötigt eine **Standard**-Berechtigung oder höher. Nutze `/unlock`, um zu abonnieren.",
"tier_gate_pro_desc": "Dieser Befehl benötigt eine **Pro**-Berechtigung oder höher. Nutze `/unlock`, um zu abonnieren.",
"tier_gate_max_desc": "Dieser Befehl benötigt eine **Max**-Berechtigung. Nutze `/unlock`, um zu abonnieren."
}, },
"weekly_br": { "weekly_br": {
"title_wildcard": "Wöchentlicher BR-Bericht — {br} BR", "title_wildcard": "Wöchentlicher BR-Bericht — {br} BR",
+5 -1
View File
@@ -840,7 +840,11 @@
"reason_line": "**Reason:** {reason}", "reason_line": "**Reason:** {reason}",
"access_denied_title": "⛔ Access Denied", "access_denied_title": "⛔ Access Denied",
"no_permission_desc": "You do not have permission to use this command.", "no_permission_desc": "You do not have permission to use this command.",
"unexpected_error_title": "❗ Error, report this...." "unexpected_error_title": "❗ Error, report this....",
"tier_gate_title": "🔒 Premium Required",
"tier_gate_standard_desc": "This command requires a **Standard** entitlement or higher. Use `/unlock` to subscribe.",
"tier_gate_pro_desc": "This command requires a **Pro** entitlement or higher. Use `/unlock` to subscribe.",
"tier_gate_max_desc": "This command requires a **Max** entitlement. Use `/unlock` to subscribe."
}, },
"weekly_br": { "weekly_br": {
"title_wildcard": "Weekly BR Report — {br} BR", "title_wildcard": "Weekly BR Report — {br} BR",
+5 -1
View File
@@ -839,7 +839,11 @@
"reason_line": "**Motivo:** {reason}", "reason_line": "**Motivo:** {reason}",
"access_denied_title": "⛔ Acceso denegado", "access_denied_title": "⛔ Acceso denegado",
"no_permission_desc": "No tienes permiso para usar este comando.", "no_permission_desc": "No tienes permiso para usar este comando.",
"unexpected_error_title": "❗ Error, repórtalo...." "unexpected_error_title": "❗ Error, repórtalo....",
"tier_gate_title": "🔒 Premium requerido",
"tier_gate_standard_desc": "Este comando requiere una suscripción **Standard** o superior. Usa `/unlock` para suscribirte.",
"tier_gate_pro_desc": "Este comando requiere una suscripción **Pro** o superior. Usa `/unlock` para suscribirte.",
"tier_gate_max_desc": "Este comando requiere una suscripción **Max**. Usa `/unlock` para suscribirte."
}, },
"weekly_br": { "weekly_br": {
"title_wildcard": "Informe BR Semanal — {br} BR", "title_wildcard": "Informe BR Semanal — {br} BR",
+5 -1
View File
@@ -839,7 +839,11 @@
"reason_line": "**Raison :** {reason}", "reason_line": "**Raison :** {reason}",
"access_denied_title": "⛔ Accès refusé", "access_denied_title": "⛔ Accès refusé",
"no_permission_desc": "Tu n'as pas la permission d'utiliser cette commande.", "no_permission_desc": "Tu n'as pas la permission d'utiliser cette commande.",
"unexpected_error_title": "❗ Erreur, signale-la...." "unexpected_error_title": "❗ Erreur, signale-la....",
"tier_gate_title": "🔒 Premium requis",
"tier_gate_standard_desc": "Cette commande nécessite un abonnement **Standard** ou supérieur. Utilise `/unlock` pour souscrire.",
"tier_gate_pro_desc": "Cette commande nécessite un abonnement **Pro** ou supérieur. Utilise `/unlock` pour souscrire.",
"tier_gate_max_desc": "Cette commande nécessite un abonnement **Max**. Utilise `/unlock` pour souscrire."
}, },
"weekly_br": { "weekly_br": {
"title_wildcard": "Rapport BR hebdomadaire — {br} BR", "title_wildcard": "Rapport BR hebdomadaire — {br} BR",
+5 -1
View File
@@ -839,7 +839,11 @@
"reason_line": "**Motivo:** {reason}", "reason_line": "**Motivo:** {reason}",
"access_denied_title": "⛔ Accesso negato", "access_denied_title": "⛔ Accesso negato",
"no_permission_desc": "Non hai il permesso di usare questo comando.", "no_permission_desc": "Non hai il permesso di usare questo comando.",
"unexpected_error_title": "❗ Errore, segnalalo...." "unexpected_error_title": "❗ Errore, segnalalo....",
"tier_gate_title": "🔒 Premium richiesto",
"tier_gate_standard_desc": "Questo comando richiede un'iscrizione **Standard** o superiore. Usa `/unlock` per abbonarti.",
"tier_gate_pro_desc": "Questo comando richiede un'iscrizione **Pro** o superiore. Usa `/unlock` per abbonarti.",
"tier_gate_max_desc": "Questo comando richiede un'iscrizione **Max**. Usa `/unlock` per abbonarti."
}, },
"weekly_br": { "weekly_br": {
"title_wildcard": "Report BR Settimanale — {br} BR", "title_wildcard": "Report BR Settimanale — {br} BR",
+5 -1
View File
@@ -839,7 +839,11 @@
"reason_line": "**Powód:** {reason}", "reason_line": "**Powód:** {reason}",
"access_denied_title": "⛔ Odmowa dostępu", "access_denied_title": "⛔ Odmowa dostępu",
"no_permission_desc": "Nie masz uprawnień do użycia tej komendy.", "no_permission_desc": "Nie masz uprawnień do użycia tej komendy.",
"unexpected_error_title": "❗ Błąd, zgłoś to...." "unexpected_error_title": "❗ Błąd, zgłoś to....",
"tier_gate_title": "🔒 Wymagane Premium",
"tier_gate_standard_desc": "Ta komenda wymaga subskrypcji **Standard** lub wyższej. Użyj `/unlock`, aby się zapisać.",
"tier_gate_pro_desc": "Ta komenda wymaga subskrypcji **Pro** lub wyższej. Użyj `/unlock`, aby się zapisać.",
"tier_gate_max_desc": "Ta komenda wymaga subskrypcji **Max**. Użyj `/unlock`, aby się zapisać."
}, },
"weekly_br": { "weekly_br": {
"title_wildcard": "Tygodniowy raport BR — {br} BR", "title_wildcard": "Tygodniowy raport BR — {br} BR",
+5 -1
View File
@@ -839,7 +839,11 @@
"reason_line": "**Motivo:** {reason}", "reason_line": "**Motivo:** {reason}",
"access_denied_title": "⛔ Acesso negado", "access_denied_title": "⛔ Acesso negado",
"no_permission_desc": "Você não tem permissão para usar este comando.", "no_permission_desc": "Você não tem permissão para usar este comando.",
"unexpected_error_title": "❗ Erro, reporte isso...." "unexpected_error_title": "❗ Erro, reporte isso....",
"tier_gate_title": "🔒 Premium necessário",
"tier_gate_standard_desc": "Este comando requer assinatura **Standard** ou superior. Use `/unlock` para assinar.",
"tier_gate_pro_desc": "Este comando requer assinatura **Pro** ou superior. Use `/unlock` para assinar.",
"tier_gate_max_desc": "Este comando requer assinatura **Max**. Use `/unlock` para assinar."
}, },
"weekly_br": { "weekly_br": {
"title_wildcard": "Relatório BR Semanal — {br} BR", "title_wildcard": "Relatório BR Semanal — {br} BR",
+5 -1
View File
@@ -839,7 +839,11 @@
"reason_line": "**Причина:** {reason}", "reason_line": "**Причина:** {reason}",
"access_denied_title": "⛔ Доступ запрещён", "access_denied_title": "⛔ Доступ запрещён",
"no_permission_desc": "У вас нет прав для использования этой команды.", "no_permission_desc": "У вас нет прав для использования этой команды.",
"unexpected_error_title": "❗ Ошибка, сообщите о ней...." "unexpected_error_title": "❗ Ошибка, сообщите о ней....",
"tier_gate_title": "🔒 Требуется Premium",
"tier_gate_standard_desc": "Эта команда требует подписку **Standard** или выше. Используйте `/unlock`, чтобы оформить.",
"tier_gate_pro_desc": "Эта команда требует подписку **Pro** или выше. Используйте `/unlock`, чтобы оформить.",
"tier_gate_max_desc": "Эта команда требует подписку **Max**. Используйте `/unlock`, чтобы оформить."
}, },
"weekly_br": { "weekly_br": {
"title_wildcard": "Еженедельный отчёт BR — {br} BR", "title_wildcard": "Еженедельный отчёт BR — {br} BR",
+5 -1
View File
@@ -839,7 +839,11 @@
"reason_line": "**Причина:** {reason}", "reason_line": "**Причина:** {reason}",
"access_denied_title": "⛔ Доступ заборонено", "access_denied_title": "⛔ Доступ заборонено",
"no_permission_desc": "У вас немає прав для використання цієї команди.", "no_permission_desc": "У вас немає прав для використання цієї команди.",
"unexpected_error_title": "❗ Помилка, повідомте про неї...." "unexpected_error_title": "❗ Помилка, повідомте про неї....",
"tier_gate_title": "🔒 Потрібен Premium",
"tier_gate_standard_desc": "Ця команда потребує підписки **Standard** або вище. Використайте `/unlock`, щоб оформити.",
"tier_gate_pro_desc": "Ця команда потребує підписки **Pro** або вище. Використайте `/unlock`, щоб оформити.",
"tier_gate_max_desc": "Ця команда потребує підписки **Max**. Використайте `/unlock`, щоб оформити."
}, },
"weekly_br": { "weekly_br": {
"title_wildcard": "Тижневий звіт BR — {br} BR", "title_wildcard": "Тижневий звіт BR — {br} BR",
+5 -1
View File
@@ -841,7 +841,11 @@
"reason_line": "**原因:** {reason}", "reason_line": "**原因:** {reason}",
"access_denied_title": "⛔ 访问被拒绝", "access_denied_title": "⛔ 访问被拒绝",
"no_permission_desc": "你没有权限使用此命令。", "no_permission_desc": "你没有权限使用此命令。",
"unexpected_error_title": "❗ 出错了,请上报...." "unexpected_error_title": "❗ 出错了,请上报....",
"tier_gate_title": "🔒 需要订阅",
"tier_gate_standard_desc": "此命令需要 **Standard** 或更高等级的订阅。使用 `/unlock` 订阅。",
"tier_gate_pro_desc": "此命令需要 **Pro** 或更高等级的订阅。使用 `/unlock` 订阅。",
"tier_gate_max_desc": "此命令需要 **Max** 订阅。使用 `/unlock` 订阅。"
}, },
"weekly_br": { "weekly_br": {
"title_wildcard": "周BR报告 — {br} BR", "title_wildcard": "周BR报告 — {br} BR",
+3
View File
@@ -22,6 +22,7 @@ from .utils import (
load_json, load_json,
write_json, write_json,
is_blacklisted, is_blacklisted,
gate_entitle,
permission_fail, permission_fail,
esc, esc,
collect_command_stats, collect_command_stats,
@@ -1163,6 +1164,7 @@ def register_commands(bot: commands.Bot) -> None:
""" """
@is_blacklisted() @is_blacklisted()
@gate_entitle("standard")
@bot.tree.command(name="stack-create", description=command_locale("Create a new player stack", "commands.stack_create.description")) @bot.tree.command(name="stack-create", description=command_locale("Create a new player stack", "commands.stack_create.description"))
@app_commands.describe(vehicle=command_locale("What vehicle will you start with?", "commands.stack_create.vehicle")) @app_commands.describe(vehicle=command_locale("What vehicle will you start with?", "commands.stack_create.vehicle"))
async def stack_create(interaction: discord.Interaction, vehicle: str) -> None: async def stack_create(interaction: discord.Interaction, vehicle: str) -> None:
@@ -1214,6 +1216,7 @@ def register_commands(bot: commands.Bot) -> None:
await permission_fail(interaction, error) await permission_fail(interaction, error)
@is_blacklisted() @is_blacklisted()
@gate_entitle("standard")
@bot.tree.command(name="stack-manage", description=command_locale("Re-post your active stack embed to this channel", "commands.stack_manage.description")) @bot.tree.command(name="stack-manage", description=command_locale("Re-post your active stack embed to this channel", "commands.stack_manage.description"))
async def stack_manage(interaction: discord.Interaction) -> None: async def stack_manage(interaction: discord.Interaction) -> None:
await collect_command_stats(interaction) await collect_command_stats(interaction)
+1 -1
View File
@@ -818,7 +818,7 @@ async def _process_squadron_points(
# SAFE CHUNK BUILDER # SAFE CHUNK BUILDER
max_len = 1024 max_len = 1024
chunks = [] chunks = []
buf = "```\nName Chg Now KDR KPS\n" buf = "```\nName Change Now KDR KPS\n"
for uid, (delta, now) in sorted_changes: for uid, (delta, now) in sorted_changes:
name_raw = ( name_raw = (
+41
View File
@@ -490,6 +490,19 @@ class BlacklistCheckFailure(app_commands.CheckFailure):
pass pass
class TierGateFailure(app_commands.CheckFailure):
"""Raised when a guild's tier is below what a command requires.
The minimum required tier is carried on ``required_tier`` so
``permission_fail`` can render the matching localized embed.
"""
def __init__(self, required_tier: str, current_tier: Optional[str] = None):
super().__init__(f"This command requires the '{required_tier}' tier or higher.")
self.required_tier = required_tier
self.current_tier = current_tier
# ============================================================================ # ============================================================================
# PERMISSION DECORATORS # PERMISSION DECORATORS
# ============================================================================ # ============================================================================
@@ -551,6 +564,27 @@ def is_blacklisted():
return app_commands.check(predicate) return app_commands.check(predicate)
def gate_entitle(required_tier: str):
"""Return an app-command check that requires a minimum entitlement tier.
Accepts 'standard', 'pro', or 'max'. Guilds with no active entitlement, or
with a tier strictly lower than ``required_tier``, are rejected with a
``TierGateFailure`` carrying the required tier so the error handler can
render the matching localized embed.
"""
if required_tier not in TIER_ORDER:
raise ValueError(f"Unknown tier {required_tier!r}; expected one of {TIER_ORDER}")
async def predicate(interaction: discord.Interaction):
if interaction.guild_id is None:
raise TierGateFailure(required_tier, None)
current = await get_guild_tier(interaction.guild_id)
if _tier_rank(current) < _tier_rank(required_tier):
raise TierGateFailure(required_tier, current)
return True
return app_commands.check(predicate)
# ============================================================================ # ============================================================================
# PERMISSION ERROR HANDLER # PERMISSION ERROR HANDLER
# ============================================================================ # ============================================================================
@@ -574,6 +608,13 @@ async def permission_fail(interaction: discord.Interaction, error):
description=f"{error.args[0]}", description=f"{error.args[0]}",
color=discord.Color.orange() color=discord.Color.orange()
) )
elif isinstance(error, TierGateFailure):
desc_key = f"permission.tier_gate_{error.required_tier}_desc"
embed = discord.Embed(
title=t(lang, "permission.tier_gate_title"),
description=t(lang, desc_key),
color=discord.Color.gold()
)
elif isinstance(error, app_commands.CheckFailure): elif isinstance(error, app_commands.CheckFailure):
embed = discord.Embed( embed = discord.Embed(
title=t(lang, "permission.access_denied_title"), title=t(lang, "permission.access_denied_title"),
+2 -6
View File
@@ -370,11 +370,11 @@
</div> </div>
<div class="plan-feature-row"> <div class="plan-feature-row">
<i class="fas fa-circle-check text-xs text-muted"></i> <i class="fas fa-circle-check text-xs text-muted"></i>
<span class="text-[13px] text-white/60"><%= t('premium.chatLogs') %></span> <span class="text-[13px] text-white/60"><%= t('premium.replayLookupsFeature') %></span>
</div> </div>
<div class="plan-feature-row"> <div class="plan-feature-row">
<i class="fas fa-circle-check text-xs text-muted"></i> <i class="fas fa-circle-check text-xs text-muted"></i>
<span class="text-[13px] text-white/60"><%= t('premium.replayLookupsFeature') %></span> <span class="text-[13px] text-white/60"><%= t('premium.unlimitedComp') %></span>
</div> </div>
</div> </div>
<div class="mt-5"> <div class="mt-5">
@@ -420,10 +420,6 @@
<i class="fas fa-circle-check text-xs text-muted"></i> <i class="fas fa-circle-check text-xs text-muted"></i>
<span class="text-[13px] text-white/60"><%= t('premium.wildcardSupport') %></span> <span class="text-[13px] text-white/60"><%= t('premium.wildcardSupport') %></span>
</div> </div>
<div class="plan-feature-row">
<i class="fas fa-circle-check text-xs text-muted"></i>
<span class="text-[13px] text-white/60"><%= t('premium.unlimitedComp') %></span>
</div>
<div class="plan-feature-row"> <div class="plan-feature-row">
<i class="fas fa-circle-check text-xs text-muted"></i> <i class="fas fa-circle-check text-xs text-muted"></i>
<span class="text-[13px] text-white/60"><%= t('premium.prioritySupport') %></span> <span class="text-[13px] text-white/60"><%= t('premium.prioritySupport') %></span>