Files
hyowons fed3526b20 Initial commit: OpenClaw 워크스페이스 버전관리 시작
설정·스크립트·스킬·문서·큐레이션 메모리 추적.
시크릿(credentials/identity)·런타임 상태(state/logs/sessions/sqlite)·
백업(clobbered/bak)·dream 캐시는 .gitignore로 제외.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:39:41 +09:00

116 lines
4.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Telegram notifier for 골디(budget) — 후잉 동기화 실패 알림 전송.
openclaw.json 의 channels.telegram.accounts.budget.botToken 과 allowFrom[0] (관리자 chat_id)
를 사용해 직접 sendMessage 호출. 외부 의존성 없음.
"""
import json
import re
import urllib.parse
import urllib.request
from datetime import datetime
from pathlib import Path
from zoneinfo import ZoneInfo
CONFIG_PATH = Path("/Users/snowoyh/.openclaw/openclaw.json")
KST = ZoneInfo("Asia/Seoul")
def _load_budget_account():
cfg = json.loads(CONFIG_PATH.read_text())
acct = cfg["channels"]["telegram"]["accounts"]["budget"]
token = acct["botToken"]
chat_ids = acct.get("allowFrom") or []
return token, chat_ids
def send(text: str, parse_mode: str = "HTML") -> bool:
"""관리자에게 텔레그램 메시지 전송. 성공 시 True. 실패해도 예외 안 던짐 (알림 자체로 흐름 막지 않음)."""
try:
token, chat_ids = _load_budget_account()
except Exception as e:
print(f"⚠️ notify: config 로드 실패: {e}")
return False
if not chat_ids:
print("⚠️ notify: allowFrom 비어 있어 전송 대상 없음")
return False
ok = True
url = f"https://api.telegram.org/bot{token}/sendMessage"
for chat_id in chat_ids:
payload = {
"chat_id": chat_id,
"text": text[:4000],
"parse_mode": parse_mode,
"disable_web_page_preview": "true",
}
data = urllib.parse.urlencode(payload).encode()
req = urllib.request.Request(url, data=data, method="POST")
try:
with urllib.request.urlopen(req, timeout=10) as r:
body = r.read().decode("utf-8", errors="replace")
if r.status != 200:
print(f"⚠️ notify: telegram {r.status}: {body[:200]}")
ok = False
except Exception as e:
print(f"⚠️ notify: telegram 전송 실패: {e}")
ok = False
return ok
def escape_html(s: str) -> str:
return (s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;"))
def format_kst(iso: str) -> str:
"""imsg created_at(UTC ISO) -> 'MM-DD HH:MM' KST. 파싱 실패 시 원문."""
if not iso:
return ""
try:
dt = datetime.fromisoformat(iso.replace("Z", "+00:00")).astimezone(KST)
return dt.strftime("%m-%d %H:%M")
except Exception:
return iso[:16]
def clean_reason(body: str, status: int) -> str:
"""후잉 응답 본문 -> 한국어 한 줄 사유. 모르는 패턴은 원문 그대로."""
body = (body or "").strip()
if status == 0:
return f"네트워크 오류 — {body[:200]}"
if not (200 <= status < 300):
return f"HTTP {status}{body[:200]}"
if body.lower() == "fail":
return "후잉이 형식 거절함 (사유 미제공). 보통 memo 누락 또는 인코딩 문제."
# "Error :" / "Error:" 접두어 제거
m = re.match(r"^Error\s*:?\s*(.+)$", body, flags=re.S)
inner = (m.group(1).strip() if m else body).strip()
# 알려진 패턴 → 한국어 번역
# 1) "left/right account does not exist at entry_date. ... [계정명]"
m = re.search(r"(left|right)\s+account\s+does\s+not\s+exist.*?\[(.+?)\]", inner, flags=re.I | re.S)
if m:
side = "차변" if m.group(1).lower() == "left" else "대변"
name = m.group(2).strip()
return f"{side} 계정 {name}’ 이(가) 후잉 차트에 없습니다. 계정명 오타이거나 거래일에 닫혀있는 계정입니다."
# 2) "[필드명] 필드가 없습니다"
m = re.search(r"\[(\w+)\]\s*필드가\s*없습니다", inner)
if m:
return f"필수 필드 {m.group(1)} 이(가) 빠졌습니다."
# 3) entry_date 관련
if re.search(r"entry_date.*(format|invalid|올바)", inner, flags=re.I):
return "거래일(entry_date) 형식 오류. YYYYMMDD 8자리여야 합니다."
# 4) money 관련
if re.search(r"money.*(invalid|format|숫자|negative)", inner, flags=re.I):
return "금액(money) 값이 잘못됐습니다. 양의 정수만 허용됩니다."
# 5) section / webhook URL 만료
if re.search(r"section|webhook|invalid\s*url|expired", inner, flags=re.I):
return f"웹훅/섹션 문제 — {inner[:200]}"
# 알려진 패턴 없음: 원문 (영문 그대로)
return inner[:300]