Initial commit: OpenClaw 워크스페이스 버전관리 시작
설정·스크립트·스킬·문서·큐레이션 메모리 추적. 시크릿(credentials/identity)·런타임 상태(state/logs/sessions/sqlite)· 백업(clobbered/bak)·dream 캐시는 .gitignore로 제외. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
"""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("&", "&").replace("<", "<").replace(">", ">"))
|
||||
|
||||
|
||||
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]
|
||||
Reference in New Issue
Block a user