lets get this party starteddddd (#1287)
This commit is contained in:
+4
-3
@@ -28,6 +28,7 @@ import discord
|
|||||||
# Local Module Imports
|
# Local Module Imports
|
||||||
from . import utils
|
from . import utils
|
||||||
from data_parser import LangTableReader
|
from data_parser import LangTableReader
|
||||||
|
from shared_store import blacklisted_squadrons
|
||||||
from .game_api import get_point_diff
|
from .game_api import get_point_diff
|
||||||
from .render_replay import load_gob_file, render_gob
|
from .render_replay import load_gob_file, render_gob
|
||||||
from .health import record_game_processed, record_ws_message
|
from .health import record_game_processed, record_ws_message
|
||||||
@@ -42,7 +43,6 @@ from .utils import (
|
|||||||
SQ_BATTLES_DB_PATH,
|
SQ_BATTLES_DB_PATH,
|
||||||
SQUADRONS_DB_PATH,
|
SQUADRONS_DB_PATH,
|
||||||
BLACKLISTED_SERVER_IDS,
|
BLACKLISTED_SERVER_IDS,
|
||||||
BLACKLISTED_SQUADRONS,
|
|
||||||
DEFAULT_FOOTER_CAT,
|
DEFAULT_FOOTER_CAT,
|
||||||
compress_json,
|
compress_json,
|
||||||
decompress_json,
|
decompress_json,
|
||||||
@@ -50,6 +50,7 @@ from .utils import (
|
|||||||
norm,
|
norm,
|
||||||
resolve_clans,
|
resolve_clans,
|
||||||
resolve_pref_key,
|
resolve_pref_key,
|
||||||
|
is_foreign_pref_entry,
|
||||||
load_features,
|
load_features,
|
||||||
remove_guild_pref_notification,
|
remove_guild_pref_notification,
|
||||||
PREMIUM_ACTIVATION_TS,
|
PREMIUM_ACTIVATION_TS,
|
||||||
@@ -653,7 +654,7 @@ async def build_hex_plus_guild(
|
|||||||
# under all of its known names so it can be found regardless of which
|
# under all of its known names so it can be found regardless of which
|
||||||
# form ends up in the replay JSON.
|
# form ends up in the replay JSON.
|
||||||
for key, cfg in prefs.items():
|
for key, cfg in prefs.items():
|
||||||
if not isinstance(cfg, dict):
|
if not isinstance(cfg, dict) or is_foreign_pref_entry(cfg):
|
||||||
continue
|
continue
|
||||||
chan = cfg.get("Logs")
|
chan = cfg.get("Logs")
|
||||||
if not chan or "DISABLED" in str(chan).upper():
|
if not chan or "DISABLED" in str(chan).upper():
|
||||||
@@ -692,7 +693,7 @@ async def build_hex_plus_guild(
|
|||||||
|
|
||||||
|
|
||||||
# PHASE 2: Match games against pre-built lookup tables
|
# PHASE 2: Match games against pre-built lookup tables
|
||||||
blacklisted_squad_norms = {norm(bl) for bl in BLACKLISTED_SQUADRONS}
|
blacklisted_squad_norms = {norm(bl) for bl in blacklisted_squadrons()}
|
||||||
|
|
||||||
for g in games:
|
for g in games:
|
||||||
sid = g.get("sessionIdHex", "")
|
sid = g.get("sessionIdHex", "")
|
||||||
|
|||||||
+174
-5
@@ -48,6 +48,7 @@ from .analytics import (
|
|||||||
)
|
)
|
||||||
from .autologging import init_players_db, load_replay_data_from_disk, build_scoreboard_view
|
from .autologging import init_players_db, load_replay_data_from_disk, build_scoreboard_view
|
||||||
from data_parser import LangTableReader, count_unit_types, get_unit_type_abbrev, normalize_name
|
from data_parser import LangTableReader, count_unit_types, get_unit_type_abbrev, normalize_name
|
||||||
|
from shared_store import get_linked_uid, save_player_link
|
||||||
from .game_api import (
|
from .game_api import (
|
||||||
ClanInfoError,
|
ClanInfoError,
|
||||||
obtain_clan_info_api,
|
obtain_clan_info_api,
|
||||||
@@ -1140,6 +1141,8 @@ async def _diagnose_perms_logic(interaction: discord.Interaction, target_channel
|
|||||||
else:
|
else:
|
||||||
has_any_logs = False
|
has_any_logs = False
|
||||||
for key, cfg in prefs.items():
|
for key, cfg in prefs.items():
|
||||||
|
if utils.is_foreign_pref_entry(cfg):
|
||||||
|
continue
|
||||||
logs_chan = cfg.get("Logs", "")
|
logs_chan = cfg.get("Logs", "")
|
||||||
if not logs_chan or "DISABLED" in str(logs_chan).upper():
|
if not logs_chan or "DISABLED" in str(logs_chan).upper():
|
||||||
continue
|
continue
|
||||||
@@ -1227,7 +1230,7 @@ def _configured_pref_targets(preferences: dict[str, Any]) -> list[dict[str, Any]
|
|||||||
targets: list[dict[str, Any]] = []
|
targets: list[dict[str, Any]] = []
|
||||||
seen: set[tuple[str, str, int]] = set()
|
seen: set[tuple[str, str, int]] = set()
|
||||||
for squadron, settings in preferences.items():
|
for squadron, settings in preferences.items():
|
||||||
if not isinstance(settings, dict):
|
if not isinstance(settings, dict) or utils.is_foreign_pref_entry(settings):
|
||||||
continue
|
continue
|
||||||
pref_key = str(squadron)
|
pref_key = str(squadron)
|
||||||
display_name = _format_pref_target_name(pref_key, settings)
|
display_name = _format_pref_target_name(pref_key, settings)
|
||||||
@@ -1288,7 +1291,7 @@ def _management_pref_rows(
|
|||||||
) -> list[dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
rows: list[dict[str, Any]] = []
|
rows: list[dict[str, Any]] = []
|
||||||
for squadron, settings in preferences.items():
|
for squadron, settings in preferences.items():
|
||||||
if not isinstance(settings, dict):
|
if not isinstance(settings, dict) or utils.is_foreign_pref_entry(settings):
|
||||||
continue
|
continue
|
||||||
for storage_type in _notif_types_for_management(notif_type):
|
for storage_type in _notif_types_for_management(notif_type):
|
||||||
if storage_type in settings:
|
if storage_type in settings:
|
||||||
@@ -3477,7 +3480,7 @@ class CardPlayerSelectView(View):
|
|||||||
async def card(
|
async def card(
|
||||||
interaction: discord.Interaction,
|
interaction: discord.Interaction,
|
||||||
season: str,
|
season: str,
|
||||||
player: str,
|
player: str = "",
|
||||||
theme: app_commands.Choice[str] | None = None,
|
theme: app_commands.Choice[str] | None = None,
|
||||||
):
|
):
|
||||||
"""Generate and send a season recap card PNG for a player.
|
"""Generate and send a season recap card PNG for a player.
|
||||||
@@ -3505,6 +3508,18 @@ async def card(
|
|||||||
embed.set_footer(text=DEFAULT_FOOTER_CAT)
|
embed.set_footer(text=DEFAULT_FOOTER_CAT)
|
||||||
return await interaction.followup.send(embed=embed, ephemeral=True)
|
return await interaction.followup.send(embed=embed, ephemeral=True)
|
||||||
|
|
||||||
|
# No player given — fall back to the caller's linked account, if any.
|
||||||
|
if not player:
|
||||||
|
linked_uid = get_linked_uid(interaction.user.id)
|
||||||
|
if not linked_uid:
|
||||||
|
return await interaction.followup.send(
|
||||||
|
t(lang, "player.must_provide_or_link"), ephemeral=True
|
||||||
|
)
|
||||||
|
nick = await _latest_nick_for_uid(linked_uid)
|
||||||
|
return await _send_player_card(
|
||||||
|
interaction, int(linked_uid), nick, season, theme_value, lang, followup=True,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with aiosqlite.connect(SQ_BATTLES_DB_PATH) as db:
|
async with aiosqlite.connect(SQ_BATTLES_DB_PATH) as db:
|
||||||
db.row_factory = aiosqlite.Row
|
db.row_factory = aiosqlite.Row
|
||||||
@@ -3960,11 +3975,17 @@ async def player_stats(interaction: discord.Interaction, username: str = "", uid
|
|||||||
await interaction.followup.send(t(lang, "common.database_error", error=error_str), ephemeral=True)
|
await interaction.followup.send(t(lang, "common.database_error", error=error_str), ephemeral=True)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
|
# No explicit input — fall back to the caller's linked account, if any.
|
||||||
|
linked_uid = get_linked_uid(interaction.user.id)
|
||||||
|
if not linked_uid:
|
||||||
await interaction.response.send_message(
|
await interaction.response.send_message(
|
||||||
t(lang, "player.must_provide_input"),
|
t(lang, "player.must_provide_or_link"),
|
||||||
ephemeral=True
|
ephemeral=True
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
await interaction.response.defer(thinking=True)
|
||||||
|
uid = linked_uid
|
||||||
|
target_uid = linked_uid
|
||||||
|
|
||||||
# UID path: fetch vehicle stats (username path already has them from above).
|
# UID path: fetch vehicle stats (username path already has them from above).
|
||||||
if uid:
|
if uid:
|
||||||
@@ -4039,6 +4060,144 @@ async def player_stats_perm_error(interaction, error):
|
|||||||
await permission_fail(interaction, error)
|
await permission_fail(interaction, error)
|
||||||
|
|
||||||
|
|
||||||
|
async def _resolve_player_for_link(username: str) -> list:
|
||||||
|
"""Return [{UID, nick}] rows matching a username (exact, then substring)."""
|
||||||
|
async with aiosqlite.connect(SQ_BATTLES_DB_PATH) as db:
|
||||||
|
db.row_factory = aiosqlite.Row
|
||||||
|
async with db.execute(
|
||||||
|
"SELECT UID, MIN(nick) AS nick FROM player_games_hist "
|
||||||
|
"WHERE nick = ? COLLATE NOCASE GROUP BY UID ORDER BY nick LIMIT 25",
|
||||||
|
(username,),
|
||||||
|
) as cursor:
|
||||||
|
results = list(await cursor.fetchall())
|
||||||
|
if not results:
|
||||||
|
async with db.execute(
|
||||||
|
"SELECT UID, MIN(nick) AS nick FROM player_games_hist "
|
||||||
|
"WHERE nick LIKE ? COLLATE NOCASE GROUP BY UID ORDER BY nick LIMIT 25",
|
||||||
|
(f"%{username}%",),
|
||||||
|
) as cursor:
|
||||||
|
results = list(await cursor.fetchall())
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
async def _latest_nick_for_uid(uid: str) -> str:
|
||||||
|
"""Best-effort latest nick for a UID; falls back to the UID string."""
|
||||||
|
try:
|
||||||
|
async with aiosqlite.connect(SQ_BATTLES_DB_PATH) as db:
|
||||||
|
db.row_factory = aiosqlite.Row
|
||||||
|
async with db.execute(
|
||||||
|
"SELECT nick FROM player_games_hist WHERE UID = ? ORDER BY session_id DESC LIMIT 1",
|
||||||
|
(uid,),
|
||||||
|
) as cursor:
|
||||||
|
row = await cursor.fetchone()
|
||||||
|
if row:
|
||||||
|
return row["nick"]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return uid
|
||||||
|
|
||||||
|
|
||||||
|
class SetPlayerSelectView(View):
|
||||||
|
"""Select which matched player to link to the invoking Discord user."""
|
||||||
|
|
||||||
|
def __init__(self, results, author: discord.abc.User, lang: str = "en"):
|
||||||
|
super().__init__(timeout=30)
|
||||||
|
self.author = author
|
||||||
|
self.lang = lang
|
||||||
|
options = [
|
||||||
|
discord.SelectOption(
|
||||||
|
label=row["nick"][:100],
|
||||||
|
description=f"UID: {row['UID']}"[:100],
|
||||||
|
value=str(row["UID"])[:100],
|
||||||
|
)
|
||||||
|
for row in results[:25]
|
||||||
|
]
|
||||||
|
self.select = Select(
|
||||||
|
placeholder=t(lang, "player.select_player_placeholder"),
|
||||||
|
options=options,
|
||||||
|
min_values=1,
|
||||||
|
max_values=1,
|
||||||
|
)
|
||||||
|
self.select.callback = self.select_callback
|
||||||
|
self.add_item(self.select)
|
||||||
|
|
||||||
|
async def interaction_check(self, interaction: discord.Interaction) -> bool:
|
||||||
|
return interaction.user.id == self.author.id
|
||||||
|
|
||||||
|
async def select_callback(self, interaction: discord.Interaction):
|
||||||
|
uid = self.select.values[0]
|
||||||
|
nick = await _latest_nick_for_uid(uid)
|
||||||
|
save_player_link(interaction.user.id, uid)
|
||||||
|
await interaction.response.edit_message(
|
||||||
|
content=t(self.lang, "player.link_success", nick=esc(nick), uid=uid),
|
||||||
|
view=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@is_blacklisted()
|
||||||
|
@bot.tree.command(
|
||||||
|
name="set-player",
|
||||||
|
description=command_locale("Link your Discord account to a War Thunder player", "commands.set_player.description"),
|
||||||
|
)
|
||||||
|
@app_commands.describe(
|
||||||
|
username=command_locale("The WT username to link", "commands.set_player.username"),
|
||||||
|
uid=command_locale("The WT UID to link", "commands.set_player.uid"),
|
||||||
|
)
|
||||||
|
@discord.app_commands.autocomplete(username=player_autocomplete)
|
||||||
|
async def set_player(interaction: discord.Interaction, username: str = "", uid: str = ""):
|
||||||
|
"""Link the invoking Discord user to a WT account in the shared PLAYERS.json.
|
||||||
|
|
||||||
|
Once linked, commands like /player-stats default to this account when run
|
||||||
|
without an explicit username/uid.
|
||||||
|
"""
|
||||||
|
await collect_command_stats(interaction)
|
||||||
|
lang = await guild_lang(interaction.guild.id) if interaction.guild else 'en'
|
||||||
|
|
||||||
|
if uid:
|
||||||
|
await interaction.response.defer(thinking=True, ephemeral=True)
|
||||||
|
nick = await _latest_nick_for_uid(uid)
|
||||||
|
save_player_link(interaction.user.id, uid)
|
||||||
|
await interaction.followup.send(
|
||||||
|
t(lang, "player.link_success", nick=esc(nick), uid=uid), ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if username:
|
||||||
|
await interaction.response.defer(thinking=True, ephemeral=True)
|
||||||
|
try:
|
||||||
|
results = await _resolve_player_for_link(username)
|
||||||
|
except Exception as e:
|
||||||
|
error_str = str(e)[:1800]
|
||||||
|
await interaction.followup.send(t(lang, "common.database_error", error=error_str), ephemeral=True)
|
||||||
|
return
|
||||||
|
if not results:
|
||||||
|
await interaction.followup.send(
|
||||||
|
t(lang, "player.no_players_found", username=username), ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
if len(results) > 1:
|
||||||
|
await interaction.followup.send(
|
||||||
|
t(lang, "player.link_select"),
|
||||||
|
view=SetPlayerSelectView(results, interaction.user, lang=lang),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
target_uid = str(results[0]["UID"])
|
||||||
|
save_player_link(interaction.user.id, target_uid)
|
||||||
|
await interaction.followup.send(
|
||||||
|
t(lang, "player.link_success", nick=esc(results[0]["nick"]), uid=target_uid),
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
await interaction.response.send_message(t(lang, "player.must_provide_input"), ephemeral=True)
|
||||||
|
|
||||||
|
|
||||||
|
@set_player.error
|
||||||
|
async def set_player_perm_error(interaction, error):
|
||||||
|
await permission_fail(interaction, error)
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
# /view-player-games — Last 20 games for a player
|
# /view-player-games — Last 20 games for a player
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
@@ -4112,7 +4271,7 @@ class FindPlayerView(discord.ui.View):
|
|||||||
)
|
)
|
||||||
@app_commands.describe(player=command_locale("The player's username", "commands.common.player_username"))
|
@app_commands.describe(player=command_locale("The player's username", "commands.common.player_username"))
|
||||||
@discord.app_commands.autocomplete(player=player_autocomplete)
|
@discord.app_commands.autocomplete(player=player_autocomplete)
|
||||||
async def view_player_games(interaction: discord.Interaction, player: str):
|
async def view_player_games(interaction: discord.Interaction, player: str = ""):
|
||||||
"""Display a player's recent squadron battle sessions with win/loss, comps, and opponents.
|
"""Display a player's recent squadron battle sessions with win/loss, comps, and opponents.
|
||||||
|
|
||||||
Resolves the player nickname to a UID, queries sessions from the last 8 hours
|
Resolves the player nickname to a UID, queries sessions from the last 8 hours
|
||||||
@@ -4127,6 +4286,16 @@ async def view_player_games(interaction: discord.Interaction, player: str):
|
|||||||
lang = await guild_lang(interaction.guild.id) if interaction.guild else 'en'
|
lang = await guild_lang(interaction.guild.id) if interaction.guild else 'en'
|
||||||
await interaction.response.defer(ephemeral=False)
|
await interaction.response.defer(ephemeral=False)
|
||||||
|
|
||||||
|
if not player:
|
||||||
|
# Fall back to the caller's linked account, if any.
|
||||||
|
linked_uid = get_linked_uid(interaction.user.id)
|
||||||
|
if not linked_uid:
|
||||||
|
return await interaction.followup.send(
|
||||||
|
t(lang, "player.must_provide_or_link"), ephemeral=True
|
||||||
|
)
|
||||||
|
target_uid = linked_uid
|
||||||
|
player_nick = esc(await _latest_nick_for_uid(linked_uid))
|
||||||
|
else:
|
||||||
# Resolve player nick → most-recently-seen UID
|
# Resolve player nick → most-recently-seen UID
|
||||||
try:
|
try:
|
||||||
async with aiosqlite.connect(SQ_BATTLES_DB_PATH) as db:
|
async with aiosqlite.connect(SQ_BATTLES_DB_PATH) as db:
|
||||||
|
|||||||
+9
-1
@@ -244,7 +244,10 @@
|
|||||||
"not_found_desc": "No game history found for `{player}`.",
|
"not_found_desc": "No game history found for `{player}`.",
|
||||||
"no_players_found": "No players found matching **{username}**\nTry using `/website` to search on the website.",
|
"no_players_found": "No players found matching **{username}**\nTry using `/website` to search on the website.",
|
||||||
"multiple_matches": "Multiple matches found, choose the correct one below:",
|
"multiple_matches": "Multiple matches found, choose the correct one below:",
|
||||||
"must_provide_input": "You must provide at least a UID or username."
|
"must_provide_input": "You must provide at least a UID or username.",
|
||||||
|
"must_provide_or_link": "You must provide a UID or username, or link your account with `/set-player` first.",
|
||||||
|
"link_select": "Multiple players match — select which account to link to your Discord:",
|
||||||
|
"link_success": "✅ Linked your Discord account to **{nick}** (UID `{uid}`).\nCommands like `/player-stats` will now default to this account."
|
||||||
},
|
},
|
||||||
"player_games": {
|
"player_games": {
|
||||||
"no_recent_title": "No Recent Games",
|
"no_recent_title": "No Recent Games",
|
||||||
@@ -759,6 +762,11 @@
|
|||||||
"description": "Set the squadron tag for this server",
|
"description": "Set the squadron tag for this server",
|
||||||
"abbreviated_name": "The short name of the squadron to set"
|
"abbreviated_name": "The short name of the squadron to set"
|
||||||
},
|
},
|
||||||
|
"set_player": {
|
||||||
|
"description": "Link your Discord account to a War Thunder player",
|
||||||
|
"username": "The WT username to link",
|
||||||
|
"uid": "The WT UID to link"
|
||||||
|
},
|
||||||
"setup": {
|
"setup": {
|
||||||
"description": "Set up the bot for this server"
|
"description": "Set up the bot for this server"
|
||||||
},
|
},
|
||||||
|
|||||||
+22
-27
@@ -34,6 +34,7 @@ from wcwidth import wcswidth
|
|||||||
# BOT/__init__.py has already put BOTS/SHARED on sys.path; re-export it
|
# BOT/__init__.py has already put BOTS/SHARED on sys.path; re-export it
|
||||||
# under a public name so peer modules can use it for asset paths.
|
# under a public name so peer modules can use it for asset paths.
|
||||||
from . import SHARED_DIR # noqa: F401 — re-exported for siblings
|
from . import SHARED_DIR # noqa: F401 — re-exported for siblings
|
||||||
|
from shared_store import check_user_blacklist
|
||||||
from data_parser import (
|
from data_parser import (
|
||||||
LangTableReader,
|
LangTableReader,
|
||||||
UnitTags,
|
UnitTags,
|
||||||
@@ -129,23 +130,9 @@ def decompress_json(data):
|
|||||||
BLACKLISTED_SERVER_IDS = [
|
BLACKLISTED_SERVER_IDS = [
|
||||||
]
|
]
|
||||||
|
|
||||||
BLACKLISTED_SQUADRONS = [
|
# Blacklisted users and squadrons now live in the shared, version-controlled
|
||||||
"FIR3",
|
# BOTS/SHARED/BLACKLIST.json (read via shared_store) so both bots share one
|
||||||
"F4WRD",
|
# source of truth. Use check_user_blacklist() / shared_store.blacklisted_squadrons().
|
||||||
]
|
|
||||||
|
|
||||||
BLACKLISTED_USER_IDS = [
|
|
||||||
635917136619110411, #wolfhunter4374
|
|
||||||
498231384238850048, #liquidtaeja
|
|
||||||
1166571862789193769, #astro
|
|
||||||
872791644947234847, #nova
|
|
||||||
591290628722393090, #rayan
|
|
||||||
1129994258267512842, #squawk
|
|
||||||
1417443909918916670, #maverickfighter9
|
|
||||||
1034483237197713539, #HK416A6
|
|
||||||
(601497771266277387, "Z supporter + racism"), #lowe/koniglion
|
|
||||||
815535178122002463, #markboss
|
|
||||||
]
|
|
||||||
|
|
||||||
# ── Premium / Entitlements ────────────────────────────────────────────────────
|
# ── Premium / Entitlements ────────────────────────────────────────────────────
|
||||||
PREMIUM_ACTIVATION_TS: int = 1775459700 # Unix timestamp when premium gating activates
|
PREMIUM_ACTIVATION_TS: int = 1775459700 # Unix timestamp when premium gating activates
|
||||||
@@ -540,8 +527,9 @@ async def is_dev_team(interaction: discord.Interaction) -> bool:
|
|||||||
def is_blacklisted():
|
def is_blacklisted():
|
||||||
"""Return an app-command check that rejects blacklisted users or guilds.
|
"""Return an app-command check that rejects blacklisted users or guilds.
|
||||||
|
|
||||||
Entries in BLACKLISTED_USER_IDS may be plain ints or
|
Blacklisted users come from the shared BLACKLIST.json (see
|
||||||
``(user_id, reason)`` tuples.
|
``shared_store.check_user_blacklist``); entries there may be a plain id or
|
||||||
|
``[id, reason]``.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
BlacklistCheckFailure: If the guild or user is blacklisted,
|
BlacklistCheckFailure: If the guild or user is blacklisted,
|
||||||
@@ -552,13 +540,8 @@ def is_blacklisted():
|
|||||||
if guild is not None and guild.id in BLACKLISTED_SERVER_IDS:
|
if guild is not None and guild.id in BLACKLISTED_SERVER_IDS:
|
||||||
raise BlacklistCheckFailure(t("en", "common.access_denied_desc"))
|
raise BlacklistCheckFailure(t("en", "common.access_denied_desc"))
|
||||||
|
|
||||||
uid = interaction.user.id
|
blocked, reason = check_user_blacklist(interaction.user.id)
|
||||||
for entry in BLACKLISTED_USER_IDS:
|
if blocked:
|
||||||
if isinstance(entry, tuple):
|
|
||||||
blocked_id, reason = entry
|
|
||||||
else:
|
|
||||||
blocked_id, reason = entry, None
|
|
||||||
if uid == blocked_id:
|
|
||||||
raise BlacklistCheckFailure(reason)
|
raise BlacklistCheckFailure(reason)
|
||||||
return True
|
return True
|
||||||
return app_commands.check(predicate)
|
return app_commands.check(predicate)
|
||||||
@@ -778,9 +761,21 @@ def is_notif_enabled(entry: Any, notif_type: str) -> bool:
|
|||||||
return bool(re.search(r"\d{17,19}", raw))
|
return bool(re.search(r"\d{17,19}", raw))
|
||||||
|
|
||||||
|
|
||||||
|
def is_foreign_pref_entry(entry: Any) -> bool:
|
||||||
|
"""True if a preferences entry belongs to another bot (TSSBOT) and SRE should skip it.
|
||||||
|
|
||||||
|
Both bots share ``STORAGE/PREFERENCES/<guild>-preferences.json``. TSSBOT entries
|
||||||
|
carry a ``Type`` of ``tss-team``/``tss-player``; SRE entries have no such Type.
|
||||||
|
"""
|
||||||
|
return isinstance(entry, dict) and str(entry.get("Type", "")).lower().startswith("tss")
|
||||||
|
|
||||||
|
|
||||||
def enabled_pref_keys_for(prefs: Dict[str, Any], notif_type: str) -> List[str]:
|
def enabled_pref_keys_for(prefs: Dict[str, Any], notif_type: str) -> List[str]:
|
||||||
"""Squadron keys (in JSON insertion order) whose entry has this notif enabled."""
|
"""Squadron keys (in JSON insertion order) whose entry has this notif enabled."""
|
||||||
return [k for k, v in prefs.items() if is_notif_enabled(v, notif_type)]
|
return [
|
||||||
|
k for k, v in prefs.items()
|
||||||
|
if not is_foreign_pref_entry(v) and is_notif_enabled(v, notif_type)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def allowed_pref_keys_for(prefs: Dict[str, Any], tier: Optional[str], notif_type: str) -> set[str]:
|
def allowed_pref_keys_for(prefs: Dict[str, Any], tier: Optional[str], notif_type: str) -> set[str]:
|
||||||
|
|||||||
Reference in New Issue
Block a user