galaxis-agent/agent/integrations/discord_handler.py

121 lines
4.1 KiB
Python
Raw Permalink Normal View History

# 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()