# agent/integrations/discord_handler.py """Discord Bot Gateway 수신 핸들러. discord.py를 사용하여 @agent 멘션을 수신하고 작업을 큐에 추가한다. """ from __future__ import annotations import hashlib import logging import os import re import discord from discord.ext import commands logger = logging.getLogger(__name__) def parse_discord_message(content: str, bot_user_id: int) -> dict: """Discord 메시지를 파싱하여 작업 정보를 추출한다.""" # 봇 멘션 제거 (<@123456>) cleaned = re.sub(rf"<@!?{bot_user_id}>", "", content).strip() # 이슈 번호 추출 issue_match = re.search(r"#(\d+)", cleaned) issue_number = int(issue_match.group(1)) if issue_match else 0 # 리포 이름 추출 repo_name = os.environ.get("DEFAULT_REPO_NAME", "galaxis-po") repo_match = re.search(r"\b(galaxis-\w+)\b", cleaned) if repo_match: repo_name = repo_match.group(1) # 순수 메시지 message = re.sub(r"@agent\b", "", cleaned, flags=re.IGNORECASE) message = re.sub(rf"\b{re.escape(repo_name)}\b", "", message) message = re.sub(r"이슈\s*#\d+", "", message) message = re.sub(r"#\d+", "", message) message = message.strip() return { "issue_number": issue_number, "repo_name": repo_name, "message": message or cleaned, } def generate_discord_thread_id(channel_id: int, message_id: int) -> str: """Discord 메시지에서 결정론적 스레드 ID를 생성한다.""" raw = hashlib.sha256(f"discord:{channel_id}:{message_id}".encode()).hexdigest() return f"{raw[:8]}-{raw[8:12]}-{raw[12:16]}-{raw[16:20]}-{raw[20:32]}" class DiscordHandler: """Discord Bot Gateway 핸들러.""" def __init__(self): intents = discord.Intents.default() intents.message_content = True intents.guild_messages = True self.bot = commands.Bot(command_prefix="!", intents=intents) self._setup_events() def _setup_events(self): @self.bot.event async def on_ready(): logger.info("Discord bot connected as %s", self.bot.user) @self.bot.event async def on_message(message: discord.Message): if message.author == self.bot.user: return if not self.bot.user or not self.bot.user.mentioned_in(message): return await self._handle_mention(message) async def _handle_mention(self, message: discord.Message): """@agent 멘션을 처리한다.""" parsed = parse_discord_message( message.content, self.bot.user.id if self.bot.user else 0 ) thread_id = generate_discord_thread_id(message.channel.id, message.id) repo_owner = os.environ.get("DEFAULT_REPO_OWNER", "quant") from agent.task_queue import get_task_queue from agent.message_store import get_message_store task_queue = await get_task_queue() if parsed["issue_number"] and await task_queue.has_running_task(thread_id): store = await get_message_store() await store.push_message(thread_id, { "role": "human", "content": parsed["message"], }) await message.reply("메시지를 대기열에 추가했습니다. 현재 작업이 완료되면 확인하겠습니다.") return task_id = await task_queue.enqueue( thread_id=thread_id, source="discord", payload={ "issue_number": parsed["issue_number"], "repo_owner": repo_owner, "repo_name": parsed["repo_name"], "message": parsed["message"], "channel_id": str(message.channel.id), "message_id": str(message.id), }, ) await message.reply(f"작업을 대기열에 추가했습니다. (task: {task_id[:8]})") logger.info("Discord task enqueued: %s (thread %s)", task_id, thread_id) async def start(self, token: str): """Bot Gateway를 시작한다.""" await self.bot.start(token) async def close(self): """Bot Gateway를 종료한다.""" await self.bot.close()