from datetime import datetime from uuid import uuid4 from typing import Optional # In-memory message store (room_id -> list of message dicts) message_store: dict[str, list] = {} ROOMS: dict[str, str] = { "general": "일반", "deploy": "배포", "ops": "운영", "pm": "PM관리", "alerts": "알림", "chatbot": "AI 챗봇", } CONTEXT_RULES = { "db_delay": { "keywords": ["DB", "디비", "쿼리", "느리", "타임아웃", "락", "lock", "slow"], "action": "FETCH_DB_LOCKS", "message": "DB 지연 징후가 감지되었습니다. 대기 쿼리를 조회할까요?", }, "disk_full": { "keywords": ["용량", "디스크", "로그", "쌓였", "full", "disk", "no space"], "action": "CHECK_DISK_SPACE", "message": "디스크 공간 관련 문제가 감지되었습니다. 용량을 확인할까요?", }, "service_down": { "keywords": ["다운", "죽었", "응답없", "timeout", "503", "502", "500", "오류", "장애"], "action": "CHECK_SERVICE_STATUS", "message": "서비스 장애 징후가 감지되었습니다. 서비스 상태를 확인할까요?", }, "ssl_expire": { "keywords": ["ssl", "인증서", "만료", "expire", "https"], "action": "CHECK_SSL_CERTS", "message": "SSL 인증서 관련 문의가 감지되었습니다. 만료일을 확인할까요?", }, } def _new_sr() -> str: return f"SR-{datetime.now().strftime('%Y%m%d')}-{str(uuid4())[:6].upper()}" def get_bot_response(text: str) -> Optional[dict]: """@bot 멘션 또는 guardia 키워드 → 명령 응답 생성.""" t = text.lower() if "@bot" not in t and "guardia" not in t: return None if "도움" in text or "help" in t or "명령" in text: return { "content": ( "**GUARDiA Bot** 사용 가능한 명령어:\n" "• `@bot 배포` — 배포 SR 생성\n" "• `@bot 재기동` — WAS 재기동 요청 (PM 승인 필요)\n" "• `@bot 로그 확인` — 에러 로그 분석\n" "• `@bot 서버 상태` — 인프라 현황 조회\n" "• `@bot SSL 확인` — 인증서 만료일 조회\n" "• `@bot 정지` — 진행 중 작업 즉시 중단" ), "is_widget": False, } if "배포" in text or "deploy" in t: sr_id = _new_sr() return { "content": ( f"배포 SR이 생성되었습니다: `{sr_id}`\n" "배포 파일을 첨부하고 대상 서버(기관명/시스템명)를 지정해주세요." ), "is_widget": True, "interactive_action": { "type": "BUTTON", "label": "배포 취소", "command_code": f"CANCEL_SR:{sr_id}", }, } if "재기동" in text or "restart" in t or "리스타트" in text: sr_id = _new_sr() return { "content": ( f"WAS 재기동 요청 `{sr_id}`이 접수되었습니다.\n" "PM 승인 후 롤링 방식으로 실행됩니다." ), "is_widget": True, "interactive_action": { "type": "APPROVAL_BUTTONS", "approve_url": f"/api/sr/approve/{sr_id}", "reject_url": f"/api/sr/reject/{sr_id}", }, } if "로그" in text or "log" in t: return { "content": ( "로그 분석 결과 (모의 데이터):\n" "```\n" "[ERROR] 2026-05-24 17:32:11 - ORA-01555: snapshot too old\n" "[WARN] 2026-05-24 17:33:05 - Connection pool exhausted (200/200)\n" "[ERROR] 2026-05-24 17:34:22 - java.lang.OutOfMemoryError: GC overhead\n" "```\n" "OutOfMemoryError 감지 — WAS 재기동을 권장합니다." ), "is_widget": True, "interactive_action": { "type": "BUTTON", "label": "WAS 재기동 요청", "command_code": "REQUEST_RESTART", }, } if "서버 상태" in text or "상태" in text or "status" in t: return { "content": ( "**인프라 상태 요약** (모의 데이터)\n" "• WEB-01 ✅ 정상 CPU 12% MEM 45%\n" "• WAS-01 ✅ 정상 CPU 34% MEM 62%\n" "• WAS-02 ⚠️ 경고 CPU 87% MEM 78%\n" "• DB-01 ✅ 정상 연결 42/200\n" "• DB-02 ✅ 정상 연결 11/200" ), "is_widget": False, } if "ssl" in t or "인증서" in text: return { "content": ( "**SSL 인증서 만료일 조회** (모의 데이터)\n" "• portal.mof.go.kr — 잔여 **D-14** ⚠️\n" "• api.molit.go.kr — 잔여 D-87 ✅\n" "• www.nts.go.kr — 잔여 D-203 ✅" ), "is_widget": True, "interactive_action": { "type": "BUTTON", "label": "갱신 SR 생성", "command_code": "RENEW_SSL:portal.mof.go.kr", }, } if "정지" in text or "stop" in t or "중단" in text: return { "content": "⛔ Kill-Switch 실행 — 진행 중인 모든 작업을 중단합니다.", "is_widget": True, "interactive_action": { "type": "APPROVAL_BUTTONS", "approve_url": "/api/killswitch/confirm", "reject_url": "/api/killswitch/cancel", }, } return { "content": f"명령을 처리하고 있습니다: \"{text}\"\n자세한 명령은 `@bot 도움`을 입력하세요.", "is_widget": False, } def check_proactive_context(text: str) -> Optional[dict]: """명시적 봇 멘션 없이도 대화 맥락에서 선제적 제안 생성.""" for rule in CONTEXT_RULES.values(): if any(kw in text for kw in rule["keywords"]): return { "content": f"{rule['message']}", "is_widget": True, "interactive_action": { "type": "BUTTON", "label": "즉시 확인", "command_code": rule["action"], }, } return None def store_message(room_id: str, msg: dict) -> None: message_store.setdefault(room_id, []).append(msg) if len(message_store[room_id]) > 200: message_store[room_id] = message_store[room_id][-200:]