732 lines
26 KiB
Python
732 lines
26 KiB
Python
# discordbot/auto_rooms.py — Auto rooms : message et réactions dans la partie texte du salon vocal (onglet Discussion)
|
|
import logging
|
|
import re
|
|
import json
|
|
from typing import Optional
|
|
|
|
import discord
|
|
from discord import Member, VoiceState
|
|
from database import db
|
|
from database.helpers import ConfigurationHelper
|
|
from database.models import AutoRoom
|
|
from webapp import webapp
|
|
|
|
# (guild_id, owner_id) -> room_data (voice_channel_id, control_message_id, whitelist, blacklist, access_mode)
|
|
_rooms: dict[tuple[int, int], dict] = {}
|
|
|
|
# message_id -> (guild_id, owner_id) pour retrouver la room depuis une réaction
|
|
_control_message_ids: dict[int, tuple[int, int]] = {}
|
|
|
|
# Emoji -> action
|
|
REACTIONS = [
|
|
("🔓", "open", "Ouvert"),
|
|
("🔒", "closed", "Fermé"),
|
|
("🔐", "private", "Privé"),
|
|
("✅", "whitelist", "Liste blanche"),
|
|
("🚫", "blacklist", "Liste noire"),
|
|
("🧹", "purge", "Purge"),
|
|
("👑", "transfer", "Propriété"),
|
|
("🎤", "speak", "Micro"),
|
|
("📹", "stream", "Vidéo"),
|
|
("📊", "soundboards", "Soundboards"),
|
|
("📝", "status", "Statut"),
|
|
]
|
|
|
|
|
|
def _status_display(access_mode: str) -> str:
|
|
"""Cadenas ouvert ou fermé selon si le salon est ouvert ou pas."""
|
|
if access_mode == "open":
|
|
return "🔓 Ouvert"
|
|
if access_mode == "closed":
|
|
return "🔒 Fermé"
|
|
if access_mode == "private":
|
|
return "🔐 Privé"
|
|
return "🔓 Ouvert"
|
|
|
|
|
|
def _status_emoji(access_mode: str) -> str:
|
|
"""Emoji cadenas seul pour le nom du channel."""
|
|
if access_mode == "private":
|
|
return "🔐"
|
|
return "🔓" if access_mode == "open" else "🔒"
|
|
|
|
|
|
def _build_control_embed(owner: Member, voice_channel: discord.VoiceChannel, access_mode: str, room: dict = None) -> discord.Embed:
|
|
"""Construit l'embed de config avec infos du salon."""
|
|
embed = discord.Embed(
|
|
title="⚙️ Configuration du salon",
|
|
description=(
|
|
"Voici l'espace de configuration de votre salon vocal temporaire. "
|
|
"Les différentes options disponibles vous permettent de personnaliser les permissions de votre salon selon vos préférences."
|
|
),
|
|
color=discord.Color.orange()
|
|
)
|
|
|
|
# Récupération des infos
|
|
whitelist = room.get("whitelist", set()) if room else set()
|
|
blacklist = room.get("blacklist", set()) if room else set()
|
|
whitelist_text = f"{len(whitelist)} membre(s)" if whitelist else "Aucun"
|
|
blacklist_text = f"{len(blacklist)} membre(s)" if blacklist else "Aucun"
|
|
|
|
# Section Propriétaire
|
|
embed.add_field(
|
|
name=f"👤 Propriétaire du salon : {owner.display_name}",
|
|
value="",
|
|
inline=False
|
|
)
|
|
|
|
# Section Modes d'accès
|
|
mode_open = "🔓 **Ouvert**\nLe salon sera ouvert à tous les membres, sauf ceux figurant sur la liste noire."
|
|
mode_closed = "🔒 **Fermé**\nLe salon sera visible de tous, mais seulement accessible à la liste blanche."
|
|
mode_private = "🔐 **Privé**\nLe salon ne sera visible et accessible qu'aux membres de la liste blanche."
|
|
|
|
embed.add_field(name=mode_open, value="", inline=True)
|
|
embed.add_field(name=mode_closed, value="", inline=True)
|
|
embed.add_field(name=mode_private, value="", inline=True)
|
|
|
|
# Section Listes
|
|
embed.add_field(
|
|
name="📝 **Liste blanche**",
|
|
value=f"Les membres présents dans cette liste pourront toujours rejoindre le salon.\n\n{whitelist_text}",
|
|
inline=True
|
|
)
|
|
embed.add_field(
|
|
name="🚫 **Liste noire**",
|
|
value=f"Les membres présents dans cette liste ne pourront jamais rejoindre le salon.\n\n{blacklist_text}",
|
|
inline=True
|
|
)
|
|
embed.add_field(name="\u200b", value="", inline=True) # Spacer
|
|
|
|
# Section Purge
|
|
embed.add_field(
|
|
name="🧹 **Purge**",
|
|
value="Déconnecter tous les membres du salon vocal à l'exception de ceux présents dans la liste blanche.",
|
|
inline=False
|
|
)
|
|
|
|
# Section Transfert
|
|
embed.add_field(
|
|
name="👑 **Transférer**",
|
|
value="Transférer la gestion du salon au membre de votre choix.",
|
|
inline=False
|
|
)
|
|
|
|
# Note importante
|
|
embed.add_field(
|
|
name="💡",
|
|
value="Les membres de la liste blanche ne sont pas impactés par les permissions refusées aux membres.",
|
|
inline=False
|
|
)
|
|
|
|
embed.set_footer(text="Réagissez avec les émojis ci-dessous pour configurer votre salon")
|
|
return embed
|
|
|
|
|
|
def _room_key(guild_id: int, owner_id: int) -> tuple[int, int]:
|
|
return (guild_id, owner_id)
|
|
|
|
|
|
def _get_room(guild_id: int, owner_id: int) -> Optional[dict]:
|
|
room = _rooms.get(_room_key(guild_id, owner_id))
|
|
if room:
|
|
return room
|
|
|
|
# Le cache peut être perdu après un redémarrage ou une reconnexion : la base
|
|
# reste la source de vérité pour éviter de créer une deuxième room au même membre.
|
|
with webapp.app_context():
|
|
record = AutoRoom.query.filter_by(guild_id=str(guild_id), owner_id=str(owner_id)).first()
|
|
if not record:
|
|
return None
|
|
try:
|
|
room = {
|
|
"guild_id": guild_id,
|
|
"voice_channel_id": int(record.voice_channel_id),
|
|
"control_message_id": int(record.control_message_id) if record.control_message_id else None,
|
|
"owner_id": owner_id,
|
|
"whitelist": set(json.loads(record.whitelist or "[]")),
|
|
"blacklist": set(json.loads(record.blacklist or "[]")),
|
|
"managed_member_ids": set(json.loads(record.managed_member_ids or "[]")),
|
|
"access_mode": record.access_mode or "open",
|
|
}
|
|
except (TypeError, ValueError):
|
|
return None
|
|
_rooms[_room_key(guild_id, owner_id)] = room
|
|
if room["control_message_id"]:
|
|
_control_message_ids[room["control_message_id"]] = (guild_id, owner_id)
|
|
return room
|
|
|
|
|
|
def _set_room(guild_id: int, owner_id: int, data: dict):
|
|
_rooms[_room_key(guild_id, owner_id)] = data
|
|
mid = data.get("control_message_id")
|
|
if mid:
|
|
_control_message_ids[mid] = (guild_id, owner_id)
|
|
_persist_room(guild_id, owner_id, data)
|
|
|
|
|
|
def _del_room(guild_id: int, owner_id: int):
|
|
data = _rooms.pop(_room_key(guild_id, owner_id), None)
|
|
if data and data.get("control_message_id"):
|
|
_control_message_ids.pop(data["control_message_id"], None)
|
|
if data:
|
|
with webapp.app_context():
|
|
AutoRoom.query.filter_by(guild_id=str(guild_id), voice_channel_id=str(data["voice_channel_id"])).delete()
|
|
db.session.commit()
|
|
|
|
|
|
def _persist_room(guild_id: int, owner_id: int, data: dict):
|
|
"""Sauvegarde l'état nécessaire à la reprise après redémarrage."""
|
|
with webapp.app_context():
|
|
record = AutoRoom.query.filter_by(
|
|
guild_id=str(guild_id), voice_channel_id=str(data["voice_channel_id"])
|
|
).first()
|
|
if not record:
|
|
record = AutoRoom(guild_id=str(guild_id), voice_channel_id=str(data["voice_channel_id"]))
|
|
db.session.add(record)
|
|
record.owner_id = str(owner_id)
|
|
record.control_message_id = str(data["control_message_id"]) if data.get("control_message_id") else None
|
|
record.access_mode = data.get("access_mode", "open")
|
|
record.whitelist = json.dumps(sorted(data.get("whitelist", set())))
|
|
record.blacklist = json.dumps(sorted(data.get("blacklist", set())))
|
|
record.managed_member_ids = json.dumps(sorted(data.get("managed_member_ids", set())))
|
|
db.session.commit()
|
|
|
|
|
|
def _auto_rooms_config() -> tuple[bool, int]:
|
|
with webapp.app_context():
|
|
config = ConfigurationHelper()
|
|
return bool(config.getValue("auto_rooms_enable")), config.getIntValue("auto_rooms_channel_id")
|
|
|
|
|
|
def _find_room_by_channel(guild_id: int, channel_id: int) -> Optional[tuple[int, dict]]:
|
|
for (gid, oid), data in _rooms.items():
|
|
if gid == guild_id and data.get("voice_channel_id") == channel_id:
|
|
return (oid, data)
|
|
with webapp.app_context():
|
|
record = AutoRoom.query.filter_by(guild_id=str(guild_id), voice_channel_id=str(channel_id)).first()
|
|
if record:
|
|
room = _get_room(guild_id, int(record.owner_id))
|
|
if room:
|
|
return (int(record.owner_id), room)
|
|
return None
|
|
|
|
|
|
def _find_room_by_message(message_id: int) -> Optional[tuple[int, int, dict]]:
|
|
key = _control_message_ids.get(message_id)
|
|
if not key:
|
|
return None
|
|
guild_id, owner_id = key
|
|
data = _get_room(guild_id, owner_id)
|
|
if not data:
|
|
_control_message_ids.pop(message_id, None)
|
|
return None
|
|
return (guild_id, owner_id, data)
|
|
|
|
|
|
async def _apply_access_mode(channel: discord.VoiceChannel, mode: str, whitelist: set, blacklist: set, room: dict):
|
|
guild = channel.guild
|
|
everyone = guild.default_role
|
|
overwrites = dict(channel.overwrites) # Récupérer les overwrites existants
|
|
|
|
# Préserver les permissions existantes pour everyone (stream, speak, soundboards, etc.)
|
|
existing_everyone_ow = overwrites.get(everyone, discord.PermissionOverwrite())
|
|
everyone_ow = discord.PermissionOverwrite()
|
|
|
|
# Copier les permissions importantes qui ne doivent pas être écrasées
|
|
everyone_ow.stream = existing_everyone_ow.stream
|
|
everyone_ow.speak = existing_everyone_ow.speak
|
|
everyone_ow.use_soundboard = existing_everyone_ow.use_soundboard
|
|
|
|
if mode == "open":
|
|
everyone_ow.connect = True
|
|
everyone_ow.view_channel = True
|
|
# Ne toucher qu'aux overwrites créés par l'auto room, jamais aux droits
|
|
# ajoutés manuellement par la modération.
|
|
for target in list(overwrites.keys()):
|
|
if target != everyone and isinstance(target, discord.Member):
|
|
if target.id in room.get("managed_member_ids", set()) and target.id not in blacklist:
|
|
overwrites.pop(target, None)
|
|
# Ajouter les overwrites pour la blacklist
|
|
for uid in blacklist:
|
|
m = guild.get_member(uid)
|
|
if m:
|
|
overwrites[m] = discord.PermissionOverwrite(connect=False, view_channel=True)
|
|
elif mode == "closed":
|
|
everyone_ow.connect = False
|
|
everyone_ow.view_channel = True
|
|
for target in list(overwrites.keys()):
|
|
if target != everyone and isinstance(target, discord.Member):
|
|
if target.id in room.get("managed_member_ids", set()) and target.id not in whitelist:
|
|
overwrites.pop(target, None)
|
|
# Ajouter les overwrites pour la whitelist
|
|
for uid in whitelist:
|
|
m = guild.get_member(uid)
|
|
if m:
|
|
overwrites[m] = discord.PermissionOverwrite(connect=True, view_channel=True)
|
|
elif mode == "private":
|
|
everyone_ow.connect = False
|
|
everyone_ow.view_channel = False
|
|
for target in list(overwrites.keys()):
|
|
if target != everyone and isinstance(target, discord.Member):
|
|
if target.id in room.get("managed_member_ids", set()) and target.id not in whitelist:
|
|
overwrites.pop(target, None)
|
|
# Ajouter les overwrites pour la whitelist
|
|
for uid in whitelist:
|
|
m = guild.get_member(uid)
|
|
if m:
|
|
overwrites[m] = discord.PermissionOverwrite(connect=True, view_channel=True)
|
|
|
|
overwrites[everyone] = everyone_ow
|
|
await channel.edit(overwrites=overwrites)
|
|
room["managed_member_ids"] = set(blacklist if mode == "open" else whitelist)
|
|
|
|
|
|
async def _handle_reaction_action(bot: discord.Client, guild_id: int, owner_id: int, action: str, channel):
|
|
"""channel = salon vocal (partie texte / onglet Discussion)."""
|
|
room = _get_room(guild_id, owner_id)
|
|
if not room:
|
|
await channel.send("Ce salon n'existe plus.")
|
|
return
|
|
voice_channel = bot.get_channel(room["voice_channel_id"])
|
|
if not voice_channel or not isinstance(voice_channel, discord.VoiceChannel):
|
|
await channel.send("Salon vocal introuvable.")
|
|
return
|
|
|
|
if action in ("open", "closed", "private"):
|
|
room["access_mode"] = action
|
|
await _apply_access_mode(voice_channel, action, room.get("whitelist", set()), room.get("blacklist", set()), room)
|
|
_persist_room(guild_id, owner_id, room)
|
|
# Mettre à jour le cadenas dans le nom du channel
|
|
try:
|
|
base_name = voice_channel.name.rstrip(" 🔓🔒🔐")
|
|
new_name = f"{base_name} {_status_emoji(action)}"
|
|
await voice_channel.edit(name=new_name)
|
|
except discord.HTTPException:
|
|
pass
|
|
await channel.send(f"Accès du salon défini sur **{_status_display(action)}**.")
|
|
# Mettre à jour l'embed
|
|
await _update_control_panel(bot, guild_id, owner_id, channel)
|
|
|
|
elif action == "whitelist":
|
|
whitelist = room.get("whitelist", set())
|
|
whitelist_text = ", ".join([f"<@{uid}>" for uid in whitelist]) if whitelist else "Aucun membre"
|
|
await channel.send(
|
|
f"**📝 Liste blanche actuelle :** {whitelist_text}\n\n"
|
|
f"Mentionnez un membre pour l'ajouter ou le retirer de la liste blanche."
|
|
)
|
|
room["awaiting_whitelist"] = True
|
|
|
|
elif action == "blacklist":
|
|
blacklist = room.get("blacklist", set())
|
|
blacklist_text = ", ".join([f"<@{uid}>" for uid in blacklist]) if blacklist else "Aucun membre"
|
|
await channel.send(
|
|
f"**🚫 Liste noire actuelle :** {blacklist_text}\n\n"
|
|
f"Mentionnez un membre pour l'ajouter ou le retirer de la liste noire."
|
|
)
|
|
room["awaiting_blacklist"] = True
|
|
|
|
elif action == "purge":
|
|
whitelist = room.get("whitelist", set())
|
|
kicked = 0
|
|
for member in list(voice_channel.members):
|
|
if member.id == owner_id or member.id in whitelist:
|
|
continue
|
|
try:
|
|
await member.move_to(None)
|
|
kicked += 1
|
|
except discord.HTTPException:
|
|
pass
|
|
await channel.send(f"🧹 Purge effectuée : {kicked} membre(s) déconnecté(s).")
|
|
|
|
elif action == "transfer":
|
|
await channel.send(
|
|
f"**👑 Transfert de propriété**\n\n"
|
|
f"Mentionnez le membre à qui vous souhaitez transférer la gestion du salon."
|
|
)
|
|
room["awaiting_transfer"] = True
|
|
|
|
elif action in ("speak", "stream", "soundboards"):
|
|
everyone = voice_channel.guild.default_role
|
|
overwrites = dict(voice_channel.overwrites)
|
|
ow = overwrites.get(everyone) or discord.PermissionOverwrite()
|
|
|
|
# BUG FIX : Par défaut, Discord autorise stream, speak et soundboards
|
|
# Si la permission n'est pas explicitement définie (None), on considère qu'elle est True
|
|
current = getattr(ow, action)
|
|
if current is None:
|
|
# Permission non définie = autorisée par défaut dans Discord
|
|
# On veut la désactiver lors du premier clic
|
|
setattr(ow, action, False)
|
|
new_value = False
|
|
else:
|
|
# Permission définie, on l'inverse
|
|
setattr(ow, action, not current)
|
|
new_value = not current
|
|
|
|
overwrites[everyone] = ow
|
|
await voice_channel.edit(overwrites=overwrites)
|
|
|
|
labels = {"speak": "Micro", "stream": "Vidéo/Partage d'écran", "soundboards": "Soundboards"}
|
|
label = labels.get(action, action.capitalize())
|
|
await channel.send(f"{label} : {'autorisé' if new_value else 'désactivé'} pour tous.")
|
|
|
|
elif action == "status":
|
|
current_status = voice_channel.status or "Aucun statut défini"
|
|
await channel.send(
|
|
f"**Statut actuel du salon :** {current_status}\n\n"
|
|
f"Pour modifier le statut du salon (le texte affiché en haut du salon vocal), "
|
|
f"répondez avec le nouveau statut (max 500 caractères).\n"
|
|
f"💡 Pour supprimer le statut, répondez avec `clear` ou `effacer`."
|
|
)
|
|
room["awaiting_status"] = True
|
|
|
|
|
|
async def _update_control_panel(bot: discord.Client, guild_id: int, owner_id: int, channel):
|
|
"""Met à jour le panneau de contrôle avec les nouvelles informations."""
|
|
room = _get_room(guild_id, owner_id)
|
|
if not room:
|
|
return
|
|
|
|
control_message_id = room.get("control_message_id")
|
|
if not control_message_id:
|
|
return
|
|
|
|
voice_channel = bot.get_channel(room["voice_channel_id"])
|
|
if not voice_channel or not isinstance(voice_channel, discord.VoiceChannel):
|
|
return
|
|
|
|
owner = voice_channel.guild.get_member(owner_id)
|
|
if not owner:
|
|
return
|
|
|
|
try:
|
|
msg = await channel.fetch_message(control_message_id)
|
|
embed = _build_control_embed(owner, voice_channel, room.get("access_mode", "open"), room)
|
|
await msg.edit(embed=embed)
|
|
except discord.HTTPException:
|
|
pass
|
|
|
|
|
|
async def send_control_panel(bot: discord.Client, guild_id: int, owner: Member, voice_channel: discord.VoiceChannel, room: dict) -> Optional[int]:
|
|
"""Envoie le message de config avec réactions dans la partie texte du salon vocal (onglet Discussion). Seul le proprio peut réagir. Retourne l'id du message."""
|
|
embed = _build_control_embed(owner, voice_channel, "open", room)
|
|
|
|
try:
|
|
# Message dans la partie texte du vocal (onglet Discussion à droite)
|
|
msg = await voice_channel.send(embed=embed)
|
|
for emoji, _action, _label in REACTIONS:
|
|
await msg.add_reaction(emoji)
|
|
return msg.id
|
|
except discord.HTTPException as e:
|
|
logging.error(f"Impossible d'envoyer le panneau Auto Room dans le vocal : {e}")
|
|
return None
|
|
|
|
|
|
_AUTO_ROOM_NAME_PATTERN = re.compile(r"^Salon de .+ [🔓🔒🔐]$")
|
|
|
|
|
|
async def cleanup_orphaned_auto_rooms(bot: discord.Client):
|
|
"""Supprime les auto rooms orphelines (vides) au démarrage du bot."""
|
|
enabled, trigger_channel_id = _auto_rooms_config()
|
|
if not enabled:
|
|
return
|
|
if not trigger_channel_id:
|
|
return
|
|
|
|
deleted = 0
|
|
# Les rooms persistées sont connues même si leur nom a été modifié manuellement.
|
|
for (guild_id, owner_id), room in list(_rooms.items()):
|
|
channel = bot.get_channel(room["voice_channel_id"])
|
|
if isinstance(channel, discord.VoiceChannel) and not channel.members:
|
|
try:
|
|
await channel.delete(reason="Nettoyage auto room vide au démarrage")
|
|
_del_room(guild_id, owner_id)
|
|
deleted += 1
|
|
except discord.HTTPException:
|
|
pass
|
|
|
|
for guild in bot.guilds:
|
|
trigger_channel = guild.get_channel(trigger_channel_id)
|
|
if not trigger_channel or not trigger_channel.category:
|
|
continue
|
|
category = trigger_channel.category
|
|
for channel in list(category.voice_channels):
|
|
if channel.id == trigger_channel_id:
|
|
continue
|
|
if not _AUTO_ROOM_NAME_PATTERN.match(channel.name):
|
|
continue
|
|
if len(channel.members) == 0:
|
|
try:
|
|
await channel.delete(reason="Nettoyage auto room orpheline au démarrage")
|
|
result = _find_room_by_channel(guild.id, channel.id)
|
|
if result:
|
|
_del_room(guild.id, result[0])
|
|
deleted += 1
|
|
except discord.HTTPException:
|
|
pass
|
|
if deleted > 0:
|
|
logging.info(f"Nettoyage auto rooms : {deleted} salon(s) orphelin(s) supprimé(s)")
|
|
|
|
|
|
async def restore_auto_rooms(bot: discord.Client):
|
|
"""Recharge les salons encore existants après un redémarrage du bot."""
|
|
with webapp.app_context():
|
|
records = AutoRoom.query.all()
|
|
for record in records:
|
|
guild = bot.get_guild(int(record.guild_id))
|
|
channel = guild.get_channel(int(record.voice_channel_id)) if guild else None
|
|
if not isinstance(channel, discord.VoiceChannel):
|
|
with webapp.app_context():
|
|
db.session.delete(db.session.merge(record))
|
|
db.session.commit()
|
|
continue
|
|
try:
|
|
whitelist = set(json.loads(record.whitelist or "[]"))
|
|
blacklist = set(json.loads(record.blacklist or "[]"))
|
|
managed_member_ids = set(json.loads(record.managed_member_ids or "[]"))
|
|
except (TypeError, ValueError):
|
|
whitelist, blacklist, managed_member_ids = set(), set(), set()
|
|
_set_room(int(record.guild_id), int(record.owner_id), {
|
|
"guild_id": int(record.guild_id),
|
|
"voice_channel_id": int(record.voice_channel_id),
|
|
"control_message_id": int(record.control_message_id) if record.control_message_id else None,
|
|
"owner_id": int(record.owner_id),
|
|
"whitelist": whitelist,
|
|
"blacklist": blacklist,
|
|
"managed_member_ids": managed_member_ids,
|
|
"access_mode": record.access_mode or "open",
|
|
})
|
|
|
|
|
|
async def on_voice_state_update_auto_rooms(bot: discord.Client, member: Member, before: VoiceState, after: VoiceState):
|
|
enabled, trigger_channel_id = _auto_rooms_config()
|
|
if not enabled:
|
|
return
|
|
if not trigger_channel_id:
|
|
return
|
|
|
|
guild = member.guild
|
|
|
|
if after.channel and after.channel.id == trigger_channel_id:
|
|
existing_room = _get_room(guild.id, member.id)
|
|
if existing_room:
|
|
old_channel = bot.get_channel(existing_room["voice_channel_id"])
|
|
if old_channel and isinstance(old_channel, discord.VoiceChannel):
|
|
# Ne jamais abandonner une room encore occupée : le propriétaire y retourne.
|
|
await member.move_to(old_channel)
|
|
return
|
|
_del_room(guild.id, member.id)
|
|
|
|
category = after.channel.category
|
|
channel_name = f"Salon de {member.display_name} {_status_emoji('open')}"
|
|
try:
|
|
new_channel = await guild.create_voice_channel(
|
|
name=channel_name,
|
|
category=category,
|
|
reason="Auto room"
|
|
)
|
|
await member.move_to(new_channel)
|
|
|
|
# Créer la room data d'abord
|
|
room_data = {
|
|
"guild_id": guild.id,
|
|
"voice_channel_id": new_channel.id,
|
|
"control_message_id": None, # Sera mis à jour après
|
|
"owner_id": member.id,
|
|
"whitelist": set(),
|
|
"blacklist": set(),
|
|
"managed_member_ids": set(),
|
|
"access_mode": "open",
|
|
}
|
|
|
|
control_message_id = await send_control_panel(bot, guild.id, member, new_channel, room_data)
|
|
if not control_message_id:
|
|
await new_channel.delete(reason="Panneau Auto Room impossible à créer")
|
|
await member.move_to(after.channel)
|
|
return
|
|
room_data["control_message_id"] = control_message_id
|
|
_set_room(guild.id, member.id, room_data)
|
|
|
|
logging.info(f"Auto room créé : {new_channel.name} pour {member.display_name}")
|
|
except discord.HTTPException as e:
|
|
logging.error(f"Erreur création auto room : {e}")
|
|
|
|
if before.channel and before.channel != after.channel and before.channel.id != trigger_channel_id:
|
|
result = _find_room_by_channel(guild.id, before.channel.id)
|
|
if result:
|
|
owner_id, room = result
|
|
remaining = [m for m in before.channel.members if m.id != member.id]
|
|
if member.id == owner_id and remaining:
|
|
new_owner = remaining[0]
|
|
_del_room(guild.id, owner_id)
|
|
room["owner_id"] = new_owner.id
|
|
_set_room(guild.id, new_owner.id, room)
|
|
await before.channel.send(f"👑 {new_owner.mention} est maintenant propriétaire du salon.")
|
|
await _update_control_panel(bot, guild.id, new_owner.id, before.channel)
|
|
elif len(remaining) == 0:
|
|
_del_room(guild.id, owner_id)
|
|
try:
|
|
await before.channel.delete(reason="Auto room vide")
|
|
except discord.HTTPException:
|
|
pass
|
|
|
|
|
|
async def on_message_auto_rooms(bot: discord.Client, message: discord.Message):
|
|
"""Gère les messages dans les salons vocaux pour les actions (statut, liste blanche/noire, etc.)."""
|
|
if message.author.bot:
|
|
return
|
|
enabled, _ = _auto_rooms_config()
|
|
if not enabled:
|
|
return
|
|
|
|
# Vérifier si c'est dans un salon vocal (partie texte)
|
|
if not isinstance(message.channel, discord.VoiceChannel):
|
|
return
|
|
|
|
# Trouver si c'est une auto room
|
|
result = _find_room_by_channel(message.guild.id, message.channel.id)
|
|
if not result:
|
|
return
|
|
|
|
owner_id, room = result
|
|
|
|
# Seul le propriétaire peut interagir
|
|
if message.author.id != owner_id:
|
|
return
|
|
|
|
voice_channel = message.channel
|
|
|
|
# Gestion du statut de salon
|
|
if room.get("awaiting_status"):
|
|
room["awaiting_status"] = False
|
|
new_status = message.content.strip()
|
|
|
|
try:
|
|
if new_status.lower() in ("clear", "effacer", "supprimer", "delete"):
|
|
await voice_channel.edit(status=None)
|
|
await message.channel.send("✅ Le statut du salon a été supprimé.")
|
|
elif len(new_status) > 500:
|
|
await message.channel.send("❌ Le statut ne peut pas dépasser 500 caractères.")
|
|
room["awaiting_status"] = True # Réessayer
|
|
else:
|
|
await voice_channel.edit(status=new_status)
|
|
await message.channel.send(f"✅ Le statut du salon a été mis à jour : **{new_status}**")
|
|
except discord.HTTPException as e:
|
|
await message.channel.send(f"❌ Erreur lors de la modification du statut : {e}")
|
|
return
|
|
|
|
# Gestion de la liste blanche (si en attente)
|
|
if room.get("awaiting_whitelist"):
|
|
room["awaiting_whitelist"] = False
|
|
if message.mentions:
|
|
target = message.mentions[0]
|
|
whitelist = room.get("whitelist", set())
|
|
if target.id in whitelist:
|
|
whitelist.remove(target.id)
|
|
await message.channel.send(f"✅ {target.mention} a été retiré de la liste blanche.")
|
|
else:
|
|
whitelist.add(target.id)
|
|
await message.channel.send(f"✅ {target.mention} a été ajouté à la liste blanche.")
|
|
room["whitelist"] = whitelist
|
|
blacklist = room.get("blacklist", set())
|
|
blacklist.discard(target.id)
|
|
room["blacklist"] = blacklist
|
|
await _apply_access_mode(voice_channel, room.get("access_mode", "open"), whitelist, blacklist, room)
|
|
_persist_room(message.guild.id, owner_id, room)
|
|
await _update_control_panel(bot, message.guild.id, owner_id, message.channel)
|
|
return
|
|
|
|
# Gestion de la liste noire (si en attente)
|
|
if room.get("awaiting_blacklist"):
|
|
room["awaiting_blacklist"] = False
|
|
if message.mentions:
|
|
target = message.mentions[0]
|
|
blacklist = room.get("blacklist", set())
|
|
if target.id in blacklist:
|
|
blacklist.remove(target.id)
|
|
await message.channel.send(f"✅ {target.mention} a été retiré de la liste noire.")
|
|
else:
|
|
blacklist.add(target.id)
|
|
await message.channel.send(f"✅ {target.mention} a été ajouté à la liste noire.")
|
|
room["blacklist"] = blacklist
|
|
whitelist = room.get("whitelist", set())
|
|
whitelist.discard(target.id)
|
|
room["whitelist"] = whitelist
|
|
await _apply_access_mode(voice_channel, room.get("access_mode", "open"), whitelist, blacklist, room)
|
|
_persist_room(message.guild.id, owner_id, room)
|
|
await _update_control_panel(bot, message.guild.id, owner_id, message.channel)
|
|
return
|
|
|
|
# Gestion du transfert de propriété (si en attente)
|
|
if room.get("awaiting_transfer"):
|
|
room["awaiting_transfer"] = False
|
|
if message.mentions:
|
|
new_owner = message.mentions[0]
|
|
if new_owner.id == owner_id:
|
|
await message.channel.send("❌ Vous êtes déjà le propriétaire du salon.")
|
|
return
|
|
|
|
# Transférer la propriété
|
|
old_owner_id = owner_id
|
|
_del_room(message.guild.id, old_owner_id)
|
|
room["owner_id"] = new_owner.id
|
|
_set_room(message.guild.id, new_owner.id, room)
|
|
|
|
# Renommer le salon
|
|
try:
|
|
base_name = f"Salon de {new_owner.display_name}"
|
|
new_name = f"{base_name} {_status_emoji(room.get('access_mode', 'open'))}"
|
|
await voice_channel.edit(name=new_name)
|
|
except discord.HTTPException:
|
|
pass
|
|
|
|
await message.channel.send(f"✅ La propriété du salon a été transférée à {new_owner.mention}.")
|
|
await _update_control_panel(bot, message.guild.id, new_owner.id, message.channel)
|
|
return
|
|
|
|
|
|
async def on_raw_reaction_add_auto_rooms(bot: discord.Client, payload: discord.RawReactionActionEvent):
|
|
"""Seul le propriétaire peut réagir ; on retire la réaction des autres."""
|
|
if payload.user_id == bot.user.id:
|
|
return
|
|
enabled, _ = _auto_rooms_config()
|
|
if not enabled:
|
|
return
|
|
room_info = _find_room_by_message(payload.message_id)
|
|
if not room_info:
|
|
return
|
|
guild_id, owner_id, room = room_info
|
|
if payload.user_id != owner_id:
|
|
try:
|
|
channel = bot.get_channel(payload.channel_id)
|
|
if channel:
|
|
msg = await channel.fetch_message(payload.message_id)
|
|
user = payload.member or await bot.fetch_user(payload.user_id)
|
|
await msg.remove_reaction(payload.emoji, user)
|
|
except discord.HTTPException:
|
|
pass
|
|
return
|
|
|
|
emoji_str = str(payload.emoji)
|
|
action = None
|
|
for e, a, _ in REACTIONS:
|
|
if e == emoji_str:
|
|
action = a
|
|
break
|
|
if not action:
|
|
return
|
|
|
|
# Canal = salon vocal (le message est dans la partie texte du vocal)
|
|
channel = bot.get_channel(payload.channel_id)
|
|
if not channel or not hasattr(channel, "send"):
|
|
return
|
|
|
|
await _handle_reaction_action(bot, guild_id, owner_id, action, channel)
|
|
|
|
try:
|
|
msg = await channel.fetch_message(payload.message_id)
|
|
user = payload.member or await bot.fetch_user(payload.user_id)
|
|
await msg.remove_reaction(payload.emoji, user)
|
|
except discord.HTTPException:
|
|
pass
|