update
This commit is contained in:
157
telegram_bot_service.py
Normal file
157
telegram_bot_service.py
Normal file
@@ -0,0 +1,157 @@
|
||||
"""
|
||||
텔레그램 봇 폴링 서비스
|
||||
- 백그라운드에서 텔레그램 봇의 업데이트를 폴링
|
||||
- 인라인 버튼 클릭 처리 (가입 승인/거부)
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from telegram import Update
|
||||
from telegram.ext import Application, CallbackQueryHandler, ContextTypes
|
||||
from flask import Flask
|
||||
|
||||
from backend.models.telegram_bot import TelegramBot
|
||||
from backend.models.user import User, db
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def handle_approval_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""
|
||||
텔레그램 인라인 버튼 클릭 처리
|
||||
callback_data 형식: "approve_{token}" 또는 "reject_{token}"
|
||||
"""
|
||||
query = update.callback_query
|
||||
await query.answer()
|
||||
|
||||
data = query.data or ""
|
||||
logger.info("Received callback: %s", data)
|
||||
|
||||
# Flask app 객체는 bot_data에 저장해둔 것을 사용
|
||||
flask_app: Optional[Flask] = context.application.bot_data.get("flask_app")
|
||||
|
||||
if flask_app is None:
|
||||
logger.error("Flask app context is missing in bot_data")
|
||||
await query.edit_message_text(
|
||||
text="❌ 내부 설정 오류로 요청을 처리할 수 없습니다. 관리자에게 문의해 주세요."
|
||||
)
|
||||
return
|
||||
|
||||
# callback_data 형식 검증
|
||||
if "_" not in data:
|
||||
logger.warning("Invalid callback data format: %s", data)
|
||||
await query.edit_message_text(
|
||||
text="❌ 유효하지 않은 요청입니다."
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
action, token = data.split("_", 1)
|
||||
except ValueError:
|
||||
logger.warning("Failed to split callback data: %s", data)
|
||||
await query.edit_message_text(
|
||||
text="❌ 유효하지 않은 요청입니다."
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
with flask_app.app_context():
|
||||
# 토큰으로 사용자 찾기
|
||||
user = User.query.filter_by(approval_token=token).first()
|
||||
|
||||
if not user:
|
||||
await query.edit_message_text(
|
||||
text="❌ 유효하지 않은 승인 요청입니다.\n(이미 처리되었거나 만료된 요청)"
|
||||
)
|
||||
return
|
||||
|
||||
if action == "approve":
|
||||
# 승인 처리
|
||||
user.is_approved = True
|
||||
user.is_active = True
|
||||
user.approval_token = None # 토큰 무효화
|
||||
|
||||
db.session.commit()
|
||||
|
||||
await query.edit_message_text(
|
||||
text=(
|
||||
"✅ 승인 완료!\n\n"
|
||||
f"👤 사용자: {user.username}\n"
|
||||
f"📧 이메일: {user.email}\n\n"
|
||||
"사용자가 이제 로그인할 수 있습니다."
|
||||
)
|
||||
)
|
||||
logger.info("User %s approved", user.username)
|
||||
|
||||
elif action == "reject":
|
||||
# 거부 처리 - 사용자 삭제
|
||||
username = user.username
|
||||
email = user.email
|
||||
|
||||
db.session.delete(user)
|
||||
db.session.commit()
|
||||
|
||||
await query.edit_message_text(
|
||||
text=(
|
||||
"❌ 가입 거부됨\n\n"
|
||||
f"👤 사용자: {username}\n"
|
||||
f"📧 이메일: {email}\n\n"
|
||||
"계정이 삭제되었습니다."
|
||||
)
|
||||
)
|
||||
logger.info("User %s rejected and deleted", username)
|
||||
else:
|
||||
logger.warning("Unknown action in callback: %s", action)
|
||||
await query.edit_message_text(
|
||||
text="❌ 유효하지 않은 요청입니다."
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception("Error handling callback: %s", e)
|
||||
# 예외 내용은 사용자에게 직접 노출하지 않음
|
||||
try:
|
||||
db.session.rollback()
|
||||
except Exception:
|
||||
logger.exception("DB rollback failed")
|
||||
|
||||
await query.edit_message_text(
|
||||
text="❌ 요청 처리 중 오류가 발생했습니다. 잠시 후 다시 시도하거나 관리자에게 문의해 주세요."
|
||||
)
|
||||
|
||||
|
||||
def run_polling(flask_app: Flask) -> None:
|
||||
"""
|
||||
동기 함수: 백그라운드 스레드에서 직접 호출됨
|
||||
Application.run_polling() 이 내부에서 asyncio 이벤트 루프를 관리하므로
|
||||
여기서는 asyncio.run 을 사용하지 않는다.
|
||||
"""
|
||||
if flask_app is None:
|
||||
raise ValueError("flask_app is required for run_polling")
|
||||
|
||||
# DB에서 활성 봇 조회
|
||||
with flask_app.app_context():
|
||||
bots = TelegramBot.query.filter_by(is_active=True).all()
|
||||
|
||||
if not bots:
|
||||
logger.warning("No active bots found")
|
||||
return
|
||||
|
||||
# 첫 번째 활성 봇만 사용 (여러 봇이 동시에 폴링하면 충돌 가능)
|
||||
bot = bots[0]
|
||||
flask_app.logger.info("Starting polling for bot: %s (ID: %s)", bot.name, bot.id)
|
||||
|
||||
# Application 생성
|
||||
application = Application.builder().token(bot.token).build()
|
||||
|
||||
# Flask app을 bot_data에 넣어서 핸들러에서 사용할 수 있게 함
|
||||
application.bot_data["flask_app"] = flask_app
|
||||
|
||||
# 콜백 쿼리 핸들러 등록
|
||||
application.add_handler(CallbackQueryHandler(handle_approval_callback))
|
||||
|
||||
try:
|
||||
# v20 스타일: run_polling 은 동기 함수이고, 내부에서 이벤트 루프를 직접 관리함
|
||||
application.run_polling(drop_pending_updates=True)
|
||||
except Exception as e:
|
||||
flask_app.logger.exception("Error in bot polling: %s", e)
|
||||
Reference in New Issue
Block a user