fix: 채널 중복 생성 근본 수정 - 글로벌 락 + 이름 기반 검색 + API 새로고침
This commit is contained in:
44
bot.py
44
bot.py
@@ -125,7 +125,7 @@ class GravityBot(commands.Bot):
|
|||||||
self.session_channels: dict[str, discord.TextChannel] = {}
|
self.session_channels: dict[str, discord.TextChannel] = {}
|
||||||
self.session_status_messages: dict[str, int] = {}
|
self.session_status_messages: dict[str, int] = {}
|
||||||
self.session_names: dict[str, str] = {}
|
self.session_names: dict[str, str] = {}
|
||||||
self._channel_locks: dict[str, asyncio.Lock] = {}
|
self._channel_create_lock = asyncio.Lock() # SINGLE global lock
|
||||||
self.bridge = BridgeProtocol()
|
self.bridge = BridgeProtocol()
|
||||||
self.session_category: discord.CategoryChannel | None = None
|
self.session_category: discord.CategoryChannel | None = None
|
||||||
self.guild: discord.Guild | None = None
|
self.guild: discord.Guild | None = None
|
||||||
@@ -156,23 +156,20 @@ class GravityBot(commands.Bot):
|
|||||||
logger.error("No permission to create category!")
|
logger.error("No permission to create category!")
|
||||||
return
|
return
|
||||||
|
|
||||||
# ONLY reconnect to existing Discord channels (NO new channel creation)
|
# ONLY reconnect to existing Discord channels (NO new creation)
|
||||||
await self._reconnect_existing_channels()
|
await self._reconnect_existing_channels()
|
||||||
|
|
||||||
async def _reconnect_existing_channels(self):
|
async def _reconnect_existing_channels(self):
|
||||||
"""Scan existing Discord channels and map them to conversation IDs.
|
"""Scan existing Discord channels and map them to conversation IDs."""
|
||||||
Does NOT create any new channels."""
|
|
||||||
if not self.session_category:
|
if not self.session_category:
|
||||||
return
|
return
|
||||||
|
|
||||||
count = 0
|
count = 0
|
||||||
for ch in self.session_category.text_channels:
|
for ch in self.session_category.text_channels:
|
||||||
if ch.topic and "Antigravity Session:" in ch.topic:
|
if ch.topic and "Antigravity Session:" in ch.topic:
|
||||||
# Extract conversation ID from topic
|
|
||||||
conv_id = ch.topic.replace("Antigravity Session:", "").strip()
|
conv_id = ch.topic.replace("Antigravity Session:", "").strip()
|
||||||
if conv_id:
|
if conv_id:
|
||||||
self.session_channels[conv_id] = ch
|
self.session_channels[conv_id] = ch
|
||||||
# Recover last task embed
|
|
||||||
await self._recover_task_message(ch, conv_id)
|
await self._recover_task_message(ch, conv_id)
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
@@ -181,7 +178,6 @@ class GravityBot(commands.Bot):
|
|||||||
async def _recover_task_message(
|
async def _recover_task_message(
|
||||||
self, channel: discord.TextChannel, conversation_id: str
|
self, channel: discord.TextChannel, conversation_id: str
|
||||||
):
|
):
|
||||||
"""Find last task embed in channel to reuse for editing."""
|
|
||||||
if conversation_id in self.session_status_messages:
|
if conversation_id in self.session_status_messages:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
@@ -199,43 +195,37 @@ class GravityBot(commands.Bot):
|
|||||||
async def _ensure_channel(
|
async def _ensure_channel(
|
||||||
self, conversation_id: str, project_name: str
|
self, conversation_id: str, project_name: str
|
||||||
) -> discord.TextChannel:
|
) -> discord.TextChannel:
|
||||||
"""Get or create a channel (thread-safe, single creation per session)."""
|
"""Get or create a channel. Uses GLOBAL lock and NAME-based lookup."""
|
||||||
|
# Fast path: already mapped
|
||||||
if conversation_id in self.session_channels:
|
if conversation_id in self.session_channels:
|
||||||
ch = self.session_channels[conversation_id]
|
return self.session_channels[conversation_id]
|
||||||
# Rename back from "closed-" if needed
|
|
||||||
expected = f"{Config.CHANNEL_PREFIX}-{project_name}".lower()
|
|
||||||
if ch.name.startswith("closed-") and ch.name != expected:
|
|
||||||
try:
|
|
||||||
await ch.edit(name=f"{Config.CHANNEL_PREFIX}-{project_name}")
|
|
||||||
except discord.errors.Forbidden:
|
|
||||||
pass
|
|
||||||
return ch
|
|
||||||
|
|
||||||
# Lock per conversation to prevent duplicates
|
async with self._channel_create_lock:
|
||||||
if conversation_id not in self._channel_locks:
|
# Double-check after lock
|
||||||
self._channel_locks[conversation_id] = asyncio.Lock()
|
|
||||||
|
|
||||||
async with self._channel_locks[conversation_id]:
|
|
||||||
# Double-check
|
|
||||||
if conversation_id in self.session_channels:
|
if conversation_id in self.session_channels:
|
||||||
return self.session_channels[conversation_id]
|
return self.session_channels[conversation_id]
|
||||||
|
|
||||||
channel_name = f"{Config.CHANNEL_PREFIX}-{project_name}"
|
channel_name = f"{Config.CHANNEL_PREFIX}-{project_name}"
|
||||||
|
target_name = channel_name.lower().replace(" ", "-")
|
||||||
|
|
||||||
# Check if channel exists but wasn't mapped yet
|
# Fetch FRESH channel list from Discord API (not cached)
|
||||||
if self.session_category:
|
if self.session_category:
|
||||||
for ch in self.session_category.text_channels:
|
category = await self.guild.fetch_channel(self.session_category.id)
|
||||||
if ch.topic and conversation_id in ch.topic:
|
for ch in category.text_channels:
|
||||||
|
# Match by NAME (handles different conv IDs → same project)
|
||||||
|
if ch.name == target_name or (ch.topic and conversation_id in ch.topic):
|
||||||
self.session_channels[conversation_id] = ch
|
self.session_channels[conversation_id] = ch
|
||||||
self.session_names[conversation_id] = project_name
|
self.session_names[conversation_id] = project_name
|
||||||
|
# Rename from closed- if needed
|
||||||
if ch.name.startswith("closed-"):
|
if ch.name.startswith("closed-"):
|
||||||
try:
|
try:
|
||||||
await ch.edit(name=channel_name)
|
await ch.edit(name=channel_name)
|
||||||
except discord.errors.Forbidden:
|
except discord.errors.Forbidden:
|
||||||
pass
|
pass
|
||||||
|
logger.info(f"Reusing existing channel #{ch.name}")
|
||||||
return ch
|
return ch
|
||||||
|
|
||||||
# Create new channel
|
# Create new channel (only if truly no match found)
|
||||||
try:
|
try:
|
||||||
channel = await self.guild.create_text_channel(
|
channel = await self.guild.create_text_channel(
|
||||||
name=channel_name,
|
name=channel_name,
|
||||||
|
|||||||
Reference in New Issue
Block a user