최초 프로젝트 업로드 (Script Auto Commit)
This commit is contained in:
108
src/notifications.py
Normal file
108
src/notifications.py
Normal file
@@ -0,0 +1,108 @@
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import requests
|
||||
from .common import logger
|
||||
|
||||
__all__ = ["send_telegram", "send_telegram_with_retry", "report_error", "send_startup_test_message"]
|
||||
|
||||
|
||||
def send_telegram_with_retry(
|
||||
token: str,
|
||||
chat_id: str,
|
||||
text: str,
|
||||
add_thread_prefix: bool = True,
|
||||
parse_mode: str = None,
|
||||
max_retries: int | None = None,
|
||||
) -> bool:
|
||||
"""
|
||||
재시도 로직이 포함된 텔레그램 메시지 전송
|
||||
|
||||
Args:
|
||||
token: 텔레그램 봇 토큰
|
||||
chat_id: 채팅 ID
|
||||
text: 메시지 내용
|
||||
add_thread_prefix: 스레드 prefix 추가 여부
|
||||
parse_mode: HTML/Markdown 파싱 모드
|
||||
max_retries: 최대 재시도 횟수 (None이면 기본값 3)
|
||||
|
||||
Returns:
|
||||
성공 여부 (True/False)
|
||||
"""
|
||||
if max_retries is None:
|
||||
max_retries = 3
|
||||
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
# 이제 send_telegram은 실패 시 예외를 발생시킴
|
||||
send_telegram(token, chat_id, text, add_thread_prefix, parse_mode)
|
||||
return True
|
||||
except Exception as e:
|
||||
if attempt < max_retries - 1:
|
||||
wait_time = 2**attempt # Exponential backoff: 1s, 2s, 4s
|
||||
logger.warning(
|
||||
"텔레그램 전송 실패 (시도 %d/%d), %d초 후 재시도: %s", attempt + 1, max_retries, wait_time, e
|
||||
)
|
||||
time.sleep(wait_time)
|
||||
else:
|
||||
logger.error("텔레그램 전송 최종 실패 (%d회 시도): %s", max_retries, e)
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
def send_telegram(token: str, chat_id: str, text: str, add_thread_prefix: bool = True, parse_mode: str = None):
|
||||
"""
|
||||
텔레그램 메시지를 한 번 전송합니다. 실패 시 예외를 발생시킵니다.
|
||||
"""
|
||||
if add_thread_prefix:
|
||||
thread_name = threading.current_thread().name
|
||||
# 기본 Thread-N 이름이면 prefix 생략 (의미 없는 정보)
|
||||
if not thread_name.startswith("Thread-"):
|
||||
payload_text = f"[{thread_name}] {text}"
|
||||
else:
|
||||
payload_text = text
|
||||
else:
|
||||
payload_text = text
|
||||
|
||||
url = f"https://api.telegram.org/bot{token}/sendMessage"
|
||||
payload = {"chat_id": chat_id, "text": payload_text}
|
||||
if parse_mode:
|
||||
payload["parse_mode"] = parse_mode
|
||||
|
||||
try:
|
||||
resp = requests.post(url, json=payload, timeout=10)
|
||||
resp.raise_for_status() # 2xx 상태 코드가 아니면 HTTPError 발생
|
||||
logger.debug("텔레그램 메시지 전송 성공: %s", text[:80])
|
||||
return True
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.warning("텔레그램 API 요청 실패: %s", e)
|
||||
raise # 예외를 다시 발생시켜 호출자가 처리하도록 함
|
||||
|
||||
|
||||
def report_error(bot_token: str, chat_id: str, message: str, dry_run: bool):
|
||||
"""
|
||||
Report an error via Telegram.
|
||||
"""
|
||||
if not dry_run and bot_token and chat_id:
|
||||
# 재시도 로직이 포함된 함수 사용
|
||||
send_telegram_with_retry(bot_token, chat_id, message, add_thread_prefix=True)
|
||||
|
||||
|
||||
def send_startup_test_message(bot_token: str, chat_id: str, parse_mode: str, dry_run: bool):
|
||||
"""
|
||||
Send a startup test message to verify Telegram settings.
|
||||
"""
|
||||
if dry_run:
|
||||
logger.info("[dry-run] Telegram 테스트 메시지 전송 생략")
|
||||
return
|
||||
|
||||
if bot_token and chat_id:
|
||||
test_msg = "[테스트] Telegram 설정 확인용 메시지입니다. 봇/채팅 설정이 올바르면 이 메시지가 도착합니다."
|
||||
logger.info("텔레그램 테스트 메시지 전송 시도")
|
||||
# 재시도 로직이 포함된 함수 사용
|
||||
if send_telegram_with_retry(bot_token, chat_id, test_msg, add_thread_prefix=False, parse_mode=parse_mode):
|
||||
logger.info("텔레그램 테스트 메시지 전송 성공")
|
||||
else:
|
||||
logger.warning("텔레그램 테스트 메시지 전송 실패")
|
||||
else:
|
||||
logger.warning("TELEGRAM_TEST=1 이지만 TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_ID가 설정되어 있지 않습니다")
|
||||
Reference in New Issue
Block a user