fix: 채널 중복 완전 근절 - reconnect 시 이름 dedup + 자동 삭제 + project name 최소 5자
This commit is contained in:
69
bot.py
69
bot.py
@@ -81,12 +81,14 @@ class ApprovalView(discord.ui.View):
|
|||||||
|
|
||||||
def detect_project_name(conv_dir: Path) -> str:
|
def detect_project_name(conv_dir: Path) -> str:
|
||||||
"""Extract project name from conversation artifacts.
|
"""Extract project name from conversation artifacts.
|
||||||
Output: lowercase_with_underscores (e.g. 'gravity_control')
|
Returns: lowercase_with_underscores (e.g. 'gravity_control')
|
||||||
|
Uses FIRST successful extraction and caches it.
|
||||||
"""
|
"""
|
||||||
short_id = conv_dir.name[:8]
|
short_id = conv_dir.name[:8]
|
||||||
|
|
||||||
def _sanitize(raw: str) -> str:
|
def _sanitize(raw: str) -> str:
|
||||||
for suffix in ["Task Tracker", "— Task Tracker", "태스크", "구현 계획"]:
|
for suffix in ["Task Tracker", "— Task Tracker", "태스크", "구현 계획",
|
||||||
|
"Implementation Plan", "Walkthrough"]:
|
||||||
raw = raw.replace(suffix, "")
|
raw = raw.replace(suffix, "")
|
||||||
raw = raw.strip(" —-–")
|
raw = raw.strip(" —-–")
|
||||||
raw = re.sub(r'[^a-zA-Z0-9가-힣\s\-]', '', raw)
|
raw = re.sub(r'[^a-zA-Z0-9가-힣\s\-]', '', raw)
|
||||||
@@ -102,7 +104,8 @@ def detect_project_name(conv_dir: Path) -> str:
|
|||||||
match = re.match(r'^#\s+(.+)', line)
|
match = re.match(r'^#\s+(.+)', line)
|
||||||
if match:
|
if match:
|
||||||
name = _sanitize(match.group(1))
|
name = _sanitize(match.group(1))
|
||||||
if name and name != "task":
|
# Require at least 5 chars to avoid short generic names
|
||||||
|
if name and name != "task" and len(name) >= 5:
|
||||||
return name
|
return name
|
||||||
except (OSError, UnicodeDecodeError):
|
except (OSError, UnicodeDecodeError):
|
||||||
pass
|
pass
|
||||||
@@ -161,20 +164,40 @@ class GravityBot(commands.Bot):
|
|||||||
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 — MERGE same-name channels."""
|
||||||
if not self.session_category:
|
if not self.session_category:
|
||||||
return
|
return
|
||||||
|
|
||||||
count = 0
|
# Group channels by normalized name
|
||||||
|
name_to_channel: dict[str, discord.TextChannel] = {}
|
||||||
|
duplicates: list[discord.TextChannel] = []
|
||||||
|
|
||||||
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:
|
||||||
conv_id = ch.topic.replace("Antigravity Session:", "").strip()
|
if ch.name in name_to_channel:
|
||||||
if conv_id:
|
# DUPLICATE — mark for cleanup
|
||||||
self.session_channels[conv_id] = ch
|
duplicates.append(ch)
|
||||||
await self._recover_task_message(ch, conv_id)
|
else:
|
||||||
count += 1
|
name_to_channel[ch.name] = ch
|
||||||
|
|
||||||
logger.info(f"Reconnected to {count} existing channels")
|
# Map the primary channel for each name
|
||||||
|
count = 0
|
||||||
|
for ch in name_to_channel.values():
|
||||||
|
conv_id = ch.topic.replace("Antigravity Session:", "").strip()
|
||||||
|
if conv_id:
|
||||||
|
self.session_channels[conv_id] = ch
|
||||||
|
await self._recover_task_message(ch, conv_id)
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
# Delete duplicate channels
|
||||||
|
for ch in duplicates:
|
||||||
|
try:
|
||||||
|
await ch.delete(reason="Duplicate channel cleanup")
|
||||||
|
logger.info(f"Deleted duplicate channel: #{ch.name}")
|
||||||
|
except (discord.Forbidden, discord.HTTPException) as e:
|
||||||
|
logger.warning(f"Failed to delete duplicate #{ch.name}: {e}")
|
||||||
|
|
||||||
|
logger.info(f"Reconnected to {count} channels, cleaned {len(duplicates)} duplicates")
|
||||||
|
|
||||||
async def _recover_task_message(
|
async def _recover_task_message(
|
||||||
self, channel: discord.TextChannel, conversation_id: str
|
self, channel: discord.TextChannel, conversation_id: str
|
||||||
@@ -196,21 +219,25 @@ 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. Uses GLOBAL lock + OWN dict lookup (no cache issues)."""
|
"""Get or create a channel. SINGLE channel per project name, guaranteed."""
|
||||||
# Fast path: already mapped
|
channel_name = f"{Config.CHANNEL_PREFIX}-{project_name}"
|
||||||
|
target_name = channel_name.lower().replace(" ", "-")
|
||||||
|
|
||||||
|
# Fast path: this conv_id already mapped
|
||||||
if conversation_id in self.session_channels:
|
if conversation_id in self.session_channels:
|
||||||
return self.session_channels[conversation_id]
|
ch = self.session_channels[conversation_id]
|
||||||
|
# Verify the channel name matches (project name might have changed)
|
||||||
|
if ch.name == target_name:
|
||||||
|
return ch
|
||||||
|
|
||||||
async with self._channel_create_lock:
|
async with self._channel_create_lock:
|
||||||
# Double-check after lock
|
# Double-check after lock
|
||||||
if conversation_id in self.session_channels:
|
if conversation_id in self.session_channels:
|
||||||
return self.session_channels[conversation_id]
|
ch = self.session_channels[conversation_id]
|
||||||
|
if ch.name == target_name:
|
||||||
|
return ch
|
||||||
|
|
||||||
channel_name = f"{Config.CHANNEL_PREFIX}-{project_name}"
|
# Check ALL mapped channels for same name
|
||||||
target_name = channel_name.lower().replace(" ", "-")
|
|
||||||
|
|
||||||
# Check OWN dict for a channel with the same NAME
|
|
||||||
# (different conv IDs can share one channel)
|
|
||||||
for cid, ch in self.session_channels.items():
|
for cid, ch in self.session_channels.items():
|
||||||
if ch.name == target_name:
|
if ch.name == target_name:
|
||||||
self.session_channels[conversation_id] = ch
|
self.session_channels[conversation_id] = ch
|
||||||
@@ -218,7 +245,7 @@ class GravityBot(commands.Bot):
|
|||||||
logger.info(f"Reusing channel #{ch.name} for {conversation_id[:8]}")
|
logger.info(f"Reusing channel #{ch.name} for {conversation_id[:8]}")
|
||||||
return ch
|
return ch
|
||||||
|
|
||||||
# Create new channel (truly first time)
|
# Create new channel (truly no match anywhere)
|
||||||
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