fix: 채널 중복 생성 근본 수정 - 글로벌 락 + 이름 기반 검색 + API 새로고침

This commit is contained in:
2026-03-07 11:56:10 +09:00
parent 1af5fb7bd6
commit b2622e9052

44
bot.py
View File

@@ -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,