Fix(Ticket Whitelist): Ajout systeme async et tracking pour le deblocage des validation rate limitees (10 min) + infos MP
This commit is contained in:
parent
97f69b4cff
commit
5fe70d754b
35 changed files with 111 additions and 3194 deletions
|
|
@ -7,6 +7,34 @@ import os
|
|||
import asyncio
|
||||
from typing import Optional
|
||||
from src.logger import kuby_logger
|
||||
import time
|
||||
|
||||
# Dictionnaire global pour suivre les renommages de salons
|
||||
# Format: {channel_id: [timestamp1, timestamp2, ...]}
|
||||
channel_rename_timestamps = {}
|
||||
|
||||
def check_channel_rename_rate_limit(channel_id: int) -> int:
|
||||
"""Vérifie si le renommage va atteindre la limite de Discord (2 edits / 10 min)."""
|
||||
now = time.time()
|
||||
if channel_id not in channel_rename_timestamps:
|
||||
channel_rename_timestamps[channel_id] = []
|
||||
|
||||
timestamps = [ts for ts in channel_rename_timestamps[channel_id] if now - ts < 600]
|
||||
channel_rename_timestamps[channel_id] = timestamps
|
||||
|
||||
if len(timestamps) >= 2:
|
||||
return max(0, int(600 - (now - timestamps[0])))
|
||||
return 0
|
||||
|
||||
def log_channel_rename(channel_id: int):
|
||||
"""Enregistre un renommage de salon."""
|
||||
now = time.time()
|
||||
if channel_id not in channel_rename_timestamps:
|
||||
channel_rename_timestamps[channel_id] = []
|
||||
|
||||
timestamps = [ts for ts in channel_rename_timestamps[channel_id] if now - ts < 600]
|
||||
timestamps.append(now)
|
||||
channel_rename_timestamps[channel_id] = timestamps
|
||||
|
||||
TICKET_WHITELIST_SETTINGS_FILE = "data/ticket_whitelist_settings.json"
|
||||
|
||||
|
|
@ -35,6 +63,31 @@ def save_settings(guild_id: int, settings: dict) -> None:
|
|||
with open(TICKET_WHITELIST_SETTINGS_FILE, "w", encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||
|
||||
async def get_ticket_user_id(channel: discord.TextChannel) -> Optional[int]:
|
||||
"""Récupère l'ID du propriétaire du ticket de manière robuste (gère le lag de cache)"""
|
||||
import re
|
||||
topic = getattr(channel, "topic", None)
|
||||
|
||||
# Tentative d'utilisation du cache d.py (plus rapide)
|
||||
if topic:
|
||||
match = re.search(r"(\d{17,20})", topic)
|
||||
if match:
|
||||
return int(match.group(1))
|
||||
|
||||
# Si le topic est vide ou ID introuvable, on tente de forcer un refresh via l'API
|
||||
try:
|
||||
# On utilise fetch_channel pour avoir un objet tout neuf avec le topic à jour
|
||||
refreshed_channel = await channel.guild.fetch_channel(channel.id)
|
||||
topic = getattr(refreshed_channel, "topic", None)
|
||||
if topic:
|
||||
match = re.search(r"(\d{17,20})", topic)
|
||||
if match:
|
||||
return int(match.group(1))
|
||||
except:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
class TicketWhitelistConfigView(ui.View):
|
||||
def __init__(self, guild_id: int):
|
||||
super().__init__(timeout=60)
|
||||
|
|
@ -321,15 +374,9 @@ class TicketManagementView(discord.ui.View):
|
|||
super().__init__(timeout=None)
|
||||
|
||||
async def get_ticket_owner(self, interaction: discord.Interaction):
|
||||
"""Récupère le propriétaire du ticket via le topic du salon (robuste via regex)"""
|
||||
import re
|
||||
topic = interaction.channel.topic
|
||||
if not topic:
|
||||
return None
|
||||
|
||||
match = re.search(r"(\d{17,20})", topic)
|
||||
if match:
|
||||
owner_id = int(match.group(1))
|
||||
"""Récupère le propriétaire du ticket via le helper robuste"""
|
||||
owner_id = await get_ticket_user_id(interaction.channel)
|
||||
if owner_id:
|
||||
try:
|
||||
member = interaction.guild.get_member(owner_id)
|
||||
if not member:
|
||||
|
|
@ -357,7 +404,8 @@ class TicketManagementView(discord.ui.View):
|
|||
|
||||
try:
|
||||
await interaction.channel.set_permissions(owner, read_messages=False, send_messages=False)
|
||||
await interaction.channel.edit(name=f"fermé-{owner.display_name}".replace(" ", "-").lower()[:100])
|
||||
log_channel_rename(interaction.channel.id)
|
||||
asyncio.create_task(interaction.channel.edit(name=f"fermé-{owner.display_name}".replace(" ", "-").lower()[:100]))
|
||||
|
||||
# Envoi du message de fermeture dans le salon
|
||||
new_view = ClosedTicketView()
|
||||
|
|
@ -400,15 +448,9 @@ class ClosedTicketView(discord.ui.View):
|
|||
super().__init__(timeout=None)
|
||||
|
||||
async def get_ticket_owner(self, interaction: discord.Interaction):
|
||||
"""Récupère le propriétaire du ticket via le topic du salon (robuste via regex)"""
|
||||
import re
|
||||
topic = interaction.channel.topic
|
||||
if not topic:
|
||||
return None
|
||||
|
||||
match = re.search(r"(\d{17,20})", topic)
|
||||
if match:
|
||||
owner_id = int(match.group(1))
|
||||
"""Récupère le propriétaire du ticket via le helper robuste"""
|
||||
owner_id = await get_ticket_user_id(interaction.channel)
|
||||
if owner_id:
|
||||
try:
|
||||
member = interaction.guild.get_member(owner_id)
|
||||
if not member:
|
||||
|
|
@ -502,7 +544,8 @@ class ReopenStatusView(discord.ui.View):
|
|||
# Mise à jour du SALON
|
||||
await interaction.channel.set_permissions(self.owner, read_messages=True, send_messages=True)
|
||||
new_name = f"{prefix}-{self.owner.display_name}".replace(" ", "-").lower()[:100]
|
||||
await interaction.channel.edit(name=new_name, topic=f"Ticket de {self.owner.id}")
|
||||
log_channel_rename(interaction.channel.id)
|
||||
asyncio.create_task(interaction.channel.edit(name=new_name, topic=f"Ticket de {self.owner.id}"))
|
||||
|
||||
new_view = TicketManagementView()
|
||||
await interaction.channel.send(f"✅ Ticket réouvert par {interaction.user.mention} (Statut: **{prefix}**).", view=new_view)
|
||||
|
|
@ -602,22 +645,16 @@ class Ticket(commands.Cog):
|
|||
if message.guild is None or message.author.bot:
|
||||
return
|
||||
|
||||
# Détection du format du topic du ticket via regex (robuste)
|
||||
if not message.channel.topic:
|
||||
if "validé" in message.content.lower():
|
||||
await message.channel.send("❌ Debug: `message.channel.topic` est vide ou None.")
|
||||
return
|
||||
|
||||
match = re.search(r"(\d{17,20})", message.channel.topic)
|
||||
if not match:
|
||||
if "validé" in message.content.lower():
|
||||
await message.channel.send(f"❌ Debug: Topic trouvé mais pas d'ID matché. Content: `{message.channel.topic}`")
|
||||
return
|
||||
|
||||
# On ne traite que si le message contient "validé"
|
||||
if "validé" not in message.content.lower():
|
||||
return
|
||||
|
||||
# Détection du format du topic du ticket via helper (robuste)
|
||||
user_id = await get_ticket_user_id(message.channel)
|
||||
if not user_id:
|
||||
await message.channel.send("❌ Debug: Impossible de récupérer l'ID utilisateur via le topic (même après refresh API).")
|
||||
return
|
||||
|
||||
settings = load_settings(message.guild.id)
|
||||
staff_role_id = settings.get("staff_role_id")
|
||||
|
||||
|
|
@ -632,8 +669,8 @@ class Ticket(commands.Cog):
|
|||
return
|
||||
|
||||
try:
|
||||
# L'ID est déjà trouvé par le regex ci-dessus
|
||||
user_id = int(match.group(1))
|
||||
# L'ID est déjà trouvé par le helper ci-dessus
|
||||
user_id = user_id
|
||||
try:
|
||||
user = await message.guild.fetch_member(user_id)
|
||||
except (discord.NotFound, discord.HTTPException):
|
||||
|
|
@ -659,7 +696,9 @@ class Ticket(commands.Cog):
|
|||
|
||||
# Mise à jour du nom du salon
|
||||
new_name = f"accepté-{user.display_name}".replace(" ", "-").lower()[:100]
|
||||
await message.channel.edit(name=new_name)
|
||||
wait_time = check_channel_rename_rate_limit(message.channel.id)
|
||||
log_channel_rename(message.channel.id)
|
||||
asyncio.create_task(message.channel.edit(name=new_name))
|
||||
|
||||
# Confirmation dans le salon (Feedback visuel)
|
||||
embed_val = discord.Embed(
|
||||
|
|
@ -679,6 +718,9 @@ class Ticket(commands.Cog):
|
|||
embed_dm.add_field(name="🏙️ Nouveau Statut", value="Citoyen", inline=True)
|
||||
embed_dm.add_field(name="📍 Serveur", value=message.guild.name, inline=True)
|
||||
embed_dm.add_field(name="📋 Prochaine étape", value="Tu peux désormais te connecter au serveur et commencer ton aventure RP. N'oublie pas de consulter les salons d'informations !", inline=False)
|
||||
if wait_time > 0:
|
||||
m, s = divmod(wait_time, 60)
|
||||
embed_dm.add_field(name="⏳ Nom du ticket", value=f"Le nom de ton ticket sera actualisé d'ici {m}m et {s}s environ (limite de sécurité Discord).", inline=False)
|
||||
embed_dm.add_field(name="🔗 Lien vers ton ticket", value=f"[Clique ici pour voir ton ticket]({message.channel.jump_url})", inline=False)
|
||||
embed_dm.set_footer(text=f"L'équipe {message.guild.name}")
|
||||
if message.guild.icon: embed_dm.set_thumbnail(url=message.guild.icon.url)
|
||||
|
|
@ -693,7 +735,9 @@ class Ticket(commands.Cog):
|
|||
|
||||
# Mise à jour du nom du salon
|
||||
new_name = f"oral-à-faire-{user.display_name}".replace(" ", "-").lower()[:100]
|
||||
await message.channel.edit(name=new_name)
|
||||
wait_time = check_channel_rename_rate_limit(message.channel.id)
|
||||
log_channel_rename(message.channel.id)
|
||||
asyncio.create_task(message.channel.edit(name=new_name))
|
||||
|
||||
# Confirmation dans le salon
|
||||
embed_val = discord.Embed(
|
||||
|
|
@ -712,6 +756,9 @@ class Ticket(commands.Cog):
|
|||
)
|
||||
embed_dm.add_field(name="🎤 Statut Actuel", value="Attente Oral", inline=True)
|
||||
embed_dm.add_field(name="🛠️ Action Requise", value="Merci de te rendre dans le salon vocal 'Attente Oral' dès que tu es disponible pour passer ton entretien.", inline=False)
|
||||
if wait_time > 0:
|
||||
m, s = divmod(wait_time, 60)
|
||||
embed_dm.add_field(name="⏳ Nom du ticket", value=f"Le nom de ton ticket sera actualisé d'ici {m}m et {s}s environ (limite de sécurité Discord).", inline=False)
|
||||
embed_dm.add_field(name="🔗 Lien vers ton ticket", value=f"[Clique ici pour voir ton ticket]({message.channel.jump_url})", inline=False)
|
||||
embed_dm.set_footer(text=f"L'équipe {message.guild.name}")
|
||||
if message.guild.icon: embed_dm.set_thumbnail(url=message.guild.icon.url)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue