limit by UID and server (#1252)
This commit is contained in:
+4
-1
@@ -131,6 +131,7 @@ from .utils import (
|
||||
get_current_timeslot_start_ts,
|
||||
get_comp_usage_in_timeslot,
|
||||
get_comp_usage_in_timeslot_by_user,
|
||||
get_entitled_guild_ids,
|
||||
SQB_SLOTS_POSTED,
|
||||
RECAP_LANGS,
|
||||
RecapError,
|
||||
@@ -407,7 +408,9 @@ async def comp(interaction: discord.Interaction, squadron_short: str):
|
||||
if not entitled:
|
||||
used_server = await get_comp_usage_in_timeslot(guild.id, slot_start)
|
||||
used_user = await get_comp_usage_in_timeslot_by_user(
|
||||
guild.id, interaction.user.id, slot_start
|
||||
interaction.user.id,
|
||||
slot_start,
|
||||
exclude_guild_ids=get_entitled_guild_ids(),
|
||||
)
|
||||
logging.info(
|
||||
"[COMP-USAGE] guild=%s user=%s squadron=%s used_server=%s/%s used_user=%s/%s slot_start=%s entitled=%s",
|
||||
|
||||
+1
-1
@@ -118,7 +118,7 @@
|
||||
"no_players_recorded": "Žádní hráči nezaznamenáni.",
|
||||
"limit_reached_title": "Limit sestav dosažen",
|
||||
"limit_reached_desc": "Tento server vyčerpal všech {limit} vyhledávání sestav pro tento časový slot. Předplaťte si (pomocí /unlock) neomezený přístup nebo počkejte na další časový slot.",
|
||||
"user_limit_reached_desc": "Vyčerpal jsi všech {limit} svých osobních vyhledávání sestav pro tento časový slot. Předplaťte si (pomocí /unlock) neomezený přístup nebo počkejte na další časový slot — ostatní členové serveru mohou stále využít zbývající serverovou kvótu.",
|
||||
"user_limit_reached_desc": "Vyčerpal jsi všech {limit} svých osobních vyhledávání sestav pro tento časový slot napříč ne-prémiovými servery. Ostatní členové zde mohou stále využít zbývající serverovou kvótu a prémiové servery (kde bylo použito /unlock) zůstávají bez omezení — předplať si /unlock nebo počkej na další časový slot.",
|
||||
"remaining_footer": "{remaining}/{limit} vyhledávání sestav zbývá v tomto časovém slotu",
|
||||
"user_remaining_footer": "{remaining}/{limit} osobních vyhledávání sestav zbývá v tomto časovém slotu",
|
||||
"remaining_footer_combined": "{user_remaining}/{user_limit} osobních · {server_remaining}/{server_limit} serverových vyhledávání sestav zbývá v tomto časovém slotu"
|
||||
|
||||
+1
-1
@@ -118,7 +118,7 @@
|
||||
"no_players_recorded": "Keine Spieler erfasst.",
|
||||
"limit_reached_title": "Aufstellungslimit erreicht",
|
||||
"limit_reached_desc": "Dieser Server hat alle {limit} Aufstellungsabfragen für diesen Zeitslot verbraucht. Abonniere (mit /unlock) für unbegrenzten Zugang oder warte auf den nächsten Zeitslot.",
|
||||
"user_limit_reached_desc": "Du hast alle {limit} deiner persönlichen Aufstellungsabfragen für diesen Zeitslot verbraucht. Abonniere (mit /unlock) für unbegrenzten Zugang oder warte auf den nächsten Zeitslot — andere Mitglieder dieses Servers können das verbleibende Server-Kontingent weiterhin nutzen.",
|
||||
"user_limit_reached_desc": "Du hast alle {limit} deiner persönlichen Aufstellungsabfragen für diesen Zeitslot auf nicht-Premium-Servern verbraucht. Andere Mitglieder hier können das verbleibende Server-Kontingent weiterhin nutzen, und Premium-Server (auf denen /unlock verwendet wurde) bleiben unbegrenzt — abonniere mit /unlock oder warte auf den nächsten Zeitslot.",
|
||||
"remaining_footer": "{remaining}/{limit} Aufstellungsabfragen übrig in diesem Zeitslot",
|
||||
"user_remaining_footer": "{remaining}/{limit} persönliche Aufstellungsabfragen übrig in diesem Zeitslot",
|
||||
"remaining_footer_combined": "{user_remaining}/{user_limit} persönlich · {server_remaining}/{server_limit} Server-Aufstellungsabfragen übrig in diesem Zeitslot"
|
||||
|
||||
+1
-1
@@ -118,7 +118,7 @@
|
||||
"no_players_recorded": "No players recorded.",
|
||||
"limit_reached_title": "Comp Limit Reached",
|
||||
"limit_reached_desc": "This server has used all {limit} comp lookups for this timeslot. Subscribe (with /unlock) for unlimited access or wait for the next timeslot.",
|
||||
"user_limit_reached_desc": "You have used all {limit} of your personal comp lookups for this timeslot. Subscribe (with /unlock) for unlimited access or wait for the next timeslot — other members of this server can still use the remaining server quota.",
|
||||
"user_limit_reached_desc": "You have used all {limit} of your personal comp lookups for this timeslot across non-premium servers. Other members here can still use the remaining server quota, and premium servers (where /unlock has been used) stay unrestricted — subscribe with /unlock or wait for the next timeslot.",
|
||||
"remaining_footer": "{remaining}/{limit} comp lookups remaining this timeslot",
|
||||
"user_remaining_footer": "{remaining}/{limit} personal comp lookups remaining this timeslot",
|
||||
"remaining_footer_combined": "{user_remaining}/{user_limit} personal · {server_remaining}/{server_limit} server comp lookups remaining this timeslot"
|
||||
|
||||
+1
-1
@@ -118,7 +118,7 @@
|
||||
"no_players_recorded": "No hay jugadores registrados.",
|
||||
"limit_reached_title": "Límite de comps alcanzado",
|
||||
"limit_reached_desc": "Este servidor ha usado las {limit} consultas de comps para este horario. Suscríbete (con /unlock) para acceso ilimitado o espera al siguiente horario.",
|
||||
"user_limit_reached_desc": "Has usado las {limit} consultas de comps personales para este horario. Suscríbete (con /unlock) para acceso ilimitado o espera al siguiente horario — otros miembros del servidor aún pueden usar la cuota restante del servidor.",
|
||||
"user_limit_reached_desc": "Has usado las {limit} consultas de comps personales para este horario en servidores no-premium. Los demás miembros aún pueden usar la cuota restante del servidor, y los servidores premium (donde se usó /unlock) siguen sin restricciones — suscríbete con /unlock o espera al siguiente horario.",
|
||||
"remaining_footer": "{remaining}/{limit} consultas de comps restantes en este horario",
|
||||
"user_remaining_footer": "{remaining}/{limit} consultas de comps personales restantes en este horario",
|
||||
"remaining_footer_combined": "{user_remaining}/{user_limit} personales · {server_remaining}/{server_limit} de servidor consultas de comps restantes en este horario"
|
||||
|
||||
+1
-1
@@ -118,7 +118,7 @@
|
||||
"no_players_recorded": "Aucun joueur enregistré.",
|
||||
"limit_reached_title": "Limite de comps atteinte",
|
||||
"limit_reached_desc": "Ce serveur a utilisé les {limit} recherches de comps pour ce créneau. Abonnez-vous (avec /unlock) pour un accès illimité ou attendez le prochain créneau.",
|
||||
"user_limit_reached_desc": "Tu as utilisé tes {limit} recherches de comps personnelles pour ce créneau. Abonnez-vous (avec /unlock) pour un accès illimité ou attendez le prochain créneau — les autres membres du serveur peuvent encore utiliser le quota restant du serveur.",
|
||||
"user_limit_reached_desc": "Tu as utilisé tes {limit} recherches de comps personnelles pour ce créneau sur les serveurs non-premium. Les autres membres ici peuvent encore utiliser le quota restant du serveur, et les serveurs premium (où /unlock a été utilisé) restent illimités — abonne-toi avec /unlock ou attends le prochain créneau.",
|
||||
"remaining_footer": "{remaining}/{limit} recherches de comps restantes pour ce créneau",
|
||||
"user_remaining_footer": "{remaining}/{limit} recherches de comps personnelles restantes pour ce créneau",
|
||||
"remaining_footer_combined": "{user_remaining}/{user_limit} personnelles · {server_remaining}/{server_limit} serveur recherches de comps restantes pour ce créneau"
|
||||
|
||||
+1
-1
@@ -118,7 +118,7 @@
|
||||
"no_players_recorded": "Nessun giocatore registrato.",
|
||||
"limit_reached_title": "Limite composizioni raggiunto",
|
||||
"limit_reached_desc": "Questo server ha esaurito tutte le {limit} ricerche di composizioni per questo slot. Abbonati (con /unlock) per accesso illimitato o attendi il prossimo slot.",
|
||||
"user_limit_reached_desc": "Hai esaurito tutte le {limit} ricerche di composizioni personali per questo slot. Abbonati (con /unlock) per accesso illimitato o attendi il prossimo slot — gli altri membri del server possono ancora usare la quota rimanente del server.",
|
||||
"user_limit_reached_desc": "Hai esaurito tutte le {limit} ricerche di composizioni personali per questo slot sui server non-premium. Gli altri membri qui possono ancora usare la quota rimanente del server e i server premium (dove è stato usato /unlock) restano senza restrizioni — abbonati con /unlock o attendi il prossimo slot.",
|
||||
"remaining_footer": "{remaining}/{limit} ricerche di composizioni rimanenti in questo slot",
|
||||
"user_remaining_footer": "{remaining}/{limit} ricerche di composizioni personali rimanenti in questo slot",
|
||||
"remaining_footer_combined": "{user_remaining}/{user_limit} personali · {server_remaining}/{server_limit} server ricerche di composizioni rimanenti in questo slot"
|
||||
|
||||
+1
-1
@@ -118,7 +118,7 @@
|
||||
"no_players_recorded": "Brak zarejestrowanych graczy.",
|
||||
"limit_reached_title": "Limit składów osiągnięty",
|
||||
"limit_reached_desc": "Ten serwer wykorzystał wszystkie {limit} wyszukiwań składów w tym slocie czasowym. Subskrybuj (za pomocą /unlock) aby uzyskać nieograniczony dostęp lub poczekaj na następny slot.",
|
||||
"user_limit_reached_desc": "Wykorzystałeś wszystkie {limit} swoich osobistych wyszukiwań składów w tym slocie czasowym. Subskrybuj (za pomocą /unlock) aby uzyskać nieograniczony dostęp lub poczekaj na następny slot — pozostali członkowie serwera nadal mogą korzystać z pozostałej puli serwera.",
|
||||
"user_limit_reached_desc": "Wykorzystałeś wszystkie {limit} swoich osobistych wyszukiwań składów w tym slocie czasowym na serwerach niepremium. Pozostali członkowie nadal mogą korzystać z pozostałej puli serwera, a serwery premium (gdzie użyto /unlock) pozostają bez ograniczeń — subskrybuj za pomocą /unlock lub poczekaj na następny slot.",
|
||||
"remaining_footer": "{remaining}/{limit} wyszukiwań składów pozostało w tym slocie czasowym",
|
||||
"user_remaining_footer": "{remaining}/{limit} osobistych wyszukiwań składów pozostało w tym slocie czasowym",
|
||||
"remaining_footer_combined": "{user_remaining}/{user_limit} osobistych · {server_remaining}/{server_limit} serwerowych wyszukiwań składów pozostało w tym slocie czasowym"
|
||||
|
||||
+1
-1
@@ -118,7 +118,7 @@
|
||||
"no_players_recorded": "Nenhum jogador registrado.",
|
||||
"limit_reached_title": "Limite de composições atingido",
|
||||
"limit_reached_desc": "Este servidor usou todas as {limit} consultas de composições para este horário. Assine (com /unlock) para acesso ilimitado ou aguarde o próximo horário.",
|
||||
"user_limit_reached_desc": "Você usou todas as {limit} consultas de composições pessoais para este horário. Assine (com /unlock) para acesso ilimitado ou aguarde o próximo horário — outros membros do servidor ainda podem usar a cota restante do servidor.",
|
||||
"user_limit_reached_desc": "Você usou todas as {limit} consultas de composições pessoais para este horário em servidores não-premium. Outros membros aqui ainda podem usar a cota restante do servidor, e servidores premium (onde /unlock foi usado) permanecem sem restrições — assine com /unlock ou aguarde o próximo horário.",
|
||||
"remaining_footer": "{remaining}/{limit} consultas de composições restantes neste horário",
|
||||
"user_remaining_footer": "{remaining}/{limit} consultas de composições pessoais restantes neste horário",
|
||||
"remaining_footer_combined": "{user_remaining}/{user_limit} pessoais · {server_remaining}/{server_limit} servidor consultas de composições restantes neste horário"
|
||||
|
||||
+1
-1
@@ -118,7 +118,7 @@
|
||||
"no_players_recorded": "Нет зафиксированных игроков.",
|
||||
"limit_reached_title": "Лимит составов достигнут",
|
||||
"limit_reached_desc": "Этот сервер использовал все {limit} запросов составов для этого таймслота. Подпишитесь (через /unlock) для безлимитного доступа или дождитесь следующего таймслота.",
|
||||
"user_limit_reached_desc": "Вы использовали все {limit} ваших личных запросов составов для этого таймслота. Подпишитесь (через /unlock) для безлимитного доступа или дождитесь следующего таймслота — другие участники сервера могут продолжать использовать оставшуюся квоту сервера.",
|
||||
"user_limit_reached_desc": "Вы использовали все {limit} ваших личных запросов составов для этого таймслота на не-премиум серверах. Другие участники здесь могут продолжать использовать оставшуюся квоту сервера, а премиум-серверы (где использован /unlock) остаются без ограничений — оформите /unlock или дождитесь следующего таймслота.",
|
||||
"remaining_footer": "{remaining}/{limit} запросов составов осталось в этом таймслоте",
|
||||
"user_remaining_footer": "{remaining}/{limit} личных запросов составов осталось в этом таймслоте",
|
||||
"remaining_footer_combined": "{user_remaining}/{user_limit} личных · {server_remaining}/{server_limit} серверных запросов составов осталось в этом таймслоте"
|
||||
|
||||
+1
-1
@@ -118,7 +118,7 @@
|
||||
"no_players_recorded": "Гравців не зафіксовано.",
|
||||
"limit_reached_title": "Ліміт складів досягнуто",
|
||||
"limit_reached_desc": "Цей сервер використав усі {limit} запитів складів для цього таймслоту. Підпишіться (через /unlock) для безлімітного доступу або зачекайте наступного таймслоту.",
|
||||
"user_limit_reached_desc": "Ви використали всі {limit} ваших особистих запитів складів для цього таймслоту. Підпишіться (через /unlock) для безлімітного доступу або зачекайте наступного таймслоту — інші учасники серверу все ще можуть використовувати залишок серверної квоти.",
|
||||
"user_limit_reached_desc": "Ви використали всі {limit} ваших особистих запитів складів для цього таймслоту на непреміум серверах. Інші учасники тут все ще можуть використовувати залишок серверної квоти, а преміум сервери (де використано /unlock) залишаються без обмежень — оформіть /unlock або зачекайте наступного таймслоту.",
|
||||
"remaining_footer": "{remaining}/{limit} запитів складів залишилось у цьому таймслоті",
|
||||
"user_remaining_footer": "{remaining}/{limit} особистих запитів складів залишилось у цьому таймслоті",
|
||||
"remaining_footer_combined": "{user_remaining}/{user_limit} особистих · {server_remaining}/{server_limit} серверних запитів складів залишилось у цьому таймслоті"
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
"no_players_recorded": "没有记录到玩家。",
|
||||
"limit_reached_title": "阵容查询次数已用完",
|
||||
"limit_reached_desc": "此服务器已用完本时段的 {limit} 次阵容查询。订阅(使用 /unlock)可获得无限访问,或等待下一个时段。",
|
||||
"user_limit_reached_desc": "您已用完本时段的 {limit} 次个人阵容查询。订阅(使用 /unlock)可获得无限访问,或等待下一个时段——此服务器的其他成员仍可使用剩余的服务器配额。",
|
||||
"user_limit_reached_desc": "您已用完本时段在所有非高级服务器中的 {limit} 次个人阵容查询。此服务器的其他成员仍可使用剩余的服务器配额,已使用 /unlock 的高级服务器不受限制——请使用 /unlock 订阅,或等待下一个时段。",
|
||||
"remaining_footer": "本时段剩余 {remaining}/{limit} 次免费阵容查询",
|
||||
"user_remaining_footer": "本时段剩余 {remaining}/{limit} 次个人阵容查询",
|
||||
"remaining_footer_combined": "本时段剩余阵容查询:个人 {user_remaining}/{user_limit} · 服务器 {server_remaining}/{server_limit}"
|
||||
|
||||
+27
-12
@@ -17,7 +17,7 @@ import time
|
||||
import unicodedata
|
||||
from datetime import datetime, time as dt_time, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Literal, Optional, TypedDict
|
||||
from typing import Any, Dict, Iterable, List, Literal, Optional, TypedDict
|
||||
|
||||
# Third-Party Library Imports
|
||||
import aiofiles
|
||||
@@ -223,11 +223,12 @@ def higher_tier(a: Optional[str], b: Optional[str]) -> Optional[str]:
|
||||
return None
|
||||
return a if ra >= rb else b
|
||||
# Free-tier /comp caps per timeslot.
|
||||
# Server-wide cap counts every invocation in the guild during the window.
|
||||
# Per-user cap counts each user's invocations and is enforced in addition to
|
||||
# the server cap, so one user maxing out can't drain the rest of the server.
|
||||
# Server-wide cap counts every invocation in a non-premium guild during the
|
||||
# window. Per-user cap counts each user's invocations across ALL non-premium
|
||||
# guilds in the window — premium-guild usage never counts toward either cap,
|
||||
# so subscribers (and their members) bypass both checks entirely.
|
||||
COMP_LIMIT_PER_TIMESLOT: int = 25
|
||||
COMP_LIMIT_PER_USER_PER_TIMESLOT: int = 10
|
||||
COMP_LIMIT_PER_USER_PER_TIMESLOT: int = 15
|
||||
|
||||
# ── SQB schedule (UTC, DST-immune) ───────────────────────────────────────────
|
||||
# Edit SQB_SLOTS_POSTED and the margin constants when Gaijin changes the
|
||||
@@ -1085,17 +1086,26 @@ async def get_comp_usage_in_timeslot(guild_id: int, since_ts: int) -> int:
|
||||
|
||||
|
||||
async def get_comp_usage_in_timeslot_by_user(
|
||||
guild_id: int, user_id: int, since_ts: int
|
||||
user_id: int, since_ts: int, *, exclude_guild_ids: Iterable[int] = ()
|
||||
) -> int:
|
||||
"""Count /comp invocations for a specific user in a guild since a timestamp."""
|
||||
"""Count a user's /comp invocations since `since_ts`, excluding any guild
|
||||
in `exclude_guild_ids` (used to drop premium/entitled guilds so their use
|
||||
doesn't count toward the free per-user cap).
|
||||
"""
|
||||
try:
|
||||
excluded = [str(g) for g in exclude_guild_ids]
|
||||
sql = (
|
||||
"SELECT COUNT(*) FROM command_usage "
|
||||
"WHERE command_name='comp' AND user_id=? AND timestamp >= ?"
|
||||
)
|
||||
params: list[Any] = [str(user_id), since_ts]
|
||||
if excluded:
|
||||
placeholders = ",".join("?" for _ in excluded)
|
||||
sql += f" AND (guild_id IS NULL OR guild_id NOT IN ({placeholders}))"
|
||||
params.extend(excluded)
|
||||
async with aiosqlite.connect(COMMAND_DATA_DB_PATH, timeout=5.0) as db:
|
||||
await db.execute("PRAGMA busy_timeout=5000;")
|
||||
cur = await db.execute(
|
||||
"SELECT COUNT(*) FROM command_usage "
|
||||
"WHERE command_name='comp' AND guild_id=? AND user_id=? AND timestamp >= ?",
|
||||
(str(guild_id), str(user_id), since_ts),
|
||||
)
|
||||
cur = await db.execute(sql, params)
|
||||
row = await cur.fetchone()
|
||||
return row[0] if row else 0
|
||||
except Exception:
|
||||
@@ -1103,6 +1113,11 @@ async def get_comp_usage_in_timeslot_by_user(
|
||||
return 0
|
||||
|
||||
|
||||
def get_entitled_guild_ids() -> list[int]:
|
||||
"""Snapshot of currently-entitled (premium) guild IDs from the cache."""
|
||||
return list(_entitled_tiers.keys())
|
||||
|
||||
|
||||
def minutes_ago(unix_timestamp: int) -> str:
|
||||
"""Convert a unix timestamp to a human-readable 'X minutes ago' string."""
|
||||
if not unix_timestamp:
|
||||
|
||||
Reference in New Issue
Block a user