zioinfo-mail/_archive/messenger/core/bot.py
DESKTOP-TKLFCPR\ython 28d3ba4836 refactor(cleanup): commit folder reorganization - scripts/, _archive/, docs/ restructure
- Move backend/frontend/messenger/ old paths to _archive/
- Reorganize scripts into scripts/deploy, check, push, setup, misc
- Move docs (pptx, docx) to docs/
- Add .claude agents and skills for fullstack/folder-cleanup harness
- workspace/ projects remain intact

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 19:43:09 +09:00

180 lines
6.6 KiB
Python

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:]