diff --git a/A_core/__pycache__/settings.cpython-38.pyc b/A_core/__pycache__/settings.cpython-38.pyc
index 7c1b3c8..fa057f3 100644
Binary files a/A_core/__pycache__/settings.cpython-38.pyc and b/A_core/__pycache__/settings.cpython-38.pyc differ
diff --git a/A_core/__pycache__/sms_utils.cpython-38.pyc b/A_core/__pycache__/sms_utils.cpython-38.pyc
index d6a3c07..6814ef7 100644
Binary files a/A_core/__pycache__/sms_utils.cpython-38.pyc and b/A_core/__pycache__/sms_utils.cpython-38.pyc differ
diff --git a/A_core/__pycache__/telegram_utils.cpython-38.pyc b/A_core/__pycache__/telegram_utils.cpython-38.pyc
new file mode 100644
index 0000000..7adbbf6
Binary files /dev/null and b/A_core/__pycache__/telegram_utils.cpython-38.pyc differ
diff --git a/A_core/settings.py b/A_core/settings.py
index a17d5ea..20b61e5 100644
--- a/A_core/settings.py
+++ b/A_core/settings.py
@@ -30,6 +30,14 @@ SECRET_KEY = 'django-insecure-kst@+h&50%!m$(d!l*qbb0l7f@z#@#me__yye^$5kg%0m%1=im
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
+# SMS 테스트 모드 설정
+SMS_TEST_MODE = False # True: 테스트 모드 (콘솔 출력), False: 실제 SMS 발송
+
+# 텔레그램 봇 설정
+TELEGRAM_BOT_TOKEN = "5094633886:AAF_Cy3mD-3rqKQMfbgpVO39jNZDXNFZN-o" # BotFather에서 받은 토큰
+TELEGRAM_CHAT_ID = "5085456424" # getUpdates로 얻은 chat_id
+TELEGRAM_API_URL = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
+
ALLOWED_HOSTS = ['www.sillaamp.com', 'sillaamp.com', '192.168.1.119', 'localhost', '127.0.0.1', '*']
CSRF_TRUSTED_ORIGINS = [
'http://www.sillaamp.com',
diff --git a/A_core/sms_utils.py b/A_core/sms_utils.py
index c3dec49..85bb228 100644
--- a/A_core/sms_utils.py
+++ b/A_core/sms_utils.py
@@ -144,23 +144,81 @@ sms_service = NaverCloudSMS()
def send_verification_sms(phone_number: str, verification_code: str) -> Dict[str, Any]:
"""인증번호 SMS 발송 함수 (편의 함수)"""
- # 실제 SMS 발송 시도
- print(f"[DEBUG] SMS 발송 시도: {phone_number} - {verification_code}")
- print(f"[DEBUG] Access Key: {sms_service.access_key}")
- print(f"[DEBUG] Service ID: {sms_service.service_id}")
- print(f"[DEBUG] Sender Phone: {sms_service.sender_phone}")
- result = sms_service.send_verification_code(phone_number, verification_code)
- print(f"[DEBUG] SMS 발송 결과: {result}")
+ # SMS 테스트 모드 확인
+ sms_test_mode = getattr(settings, 'SMS_TEST_MODE', False)
- return result
+ if sms_test_mode:
+ # 테스트 모드: 콘솔에 인증번호 출력
+ print("=" * 60)
+ print("🔔 [SMS 테스트 모드] 인증번호 발송")
+ print(f"📱 수신번호: {phone_number}")
+ print(f"🔑 인증번호: {verification_code}")
+ print(f"📝 메시지: [신라AMP] 인증번호는 [{verification_code}]입니다. 3분 이내에 입력해주세요.")
+ print("=" * 60)
+
+ return {
+ 'success': True,
+ 'message': 'SMS 테스트 모드: 콘솔에 인증번호가 출력되었습니다.',
+ 'test_mode': True,
+ 'verification_code': verification_code # 테스트 모드에서만 포함
+ }
+ else:
+ # 실제 SMS 발송 모드
+ print(f"[DEBUG] 실제 SMS 발송 시도: {phone_number} - {verification_code}")
+ print(f"[DEBUG] Access Key: {sms_service.access_key}")
+ print(f"[DEBUG] Service ID: {sms_service.service_id}")
+ print(f"[DEBUG] Sender Phone: {sms_service.sender_phone}")
+
+ result = sms_service.send_verification_code(phone_number, verification_code)
+ print(f"[DEBUG] SMS 발송 결과: {result}")
+
+ return result
def send_withdrawal_approval_sms(phone_number: str, name: str) -> Dict[str, Any]:
"""탈퇴 승인 SMS 발송 함수 (편의 함수)"""
- return sms_service.send_withdrawal_approval_sms(phone_number, name)
+ sms_test_mode = getattr(settings, 'SMS_TEST_MODE', False)
+
+ if sms_test_mode:
+ message = f"[신라AMP] {name}님의 회원탈퇴가 승인되었습니다. 그동안 이용해주셔서 감사했습니다."
+ print("=" * 60)
+ print("🔔 [SMS 테스트 모드] 탈퇴 승인 알림")
+ print(f"📱 수신번호: {phone_number}")
+ print(f"👤 대상자: {name}")
+ print(f"📝 메시지: {message}")
+ print("=" * 60)
+ return {
+ 'success': True,
+ 'message': 'SMS 테스트 모드: 탈퇴 승인 알림이 콘솔에 출력되었습니다.',
+ 'test_mode': True
+ }
+ else:
+ return sms_service.send_withdrawal_approval_sms(phone_number, name)
def send_withdrawal_rejection_sms(phone_number: str, name: str, reason: str = None) -> Dict[str, Any]:
"""탈퇴 거부 SMS 발송 함수 (편의 함수)"""
- return sms_service.send_withdrawal_rejection_sms(phone_number, name, reason)
+ sms_test_mode = getattr(settings, 'SMS_TEST_MODE', False)
+
+ if sms_test_mode:
+ if reason:
+ message = f"[신라AMP] {name}님의 회원탈퇴 요청이 거부되었습니다. 사유: {reason}"
+ else:
+ message = f"[신라AMP] {name}님의 회원탈퇴 요청이 거부되었습니다. 자세한 사항은 관리자에게 문의해주세요."
+
+ print("=" * 60)
+ print("🔔 [SMS 테스트 모드] 탈퇴 거부 알림")
+ print(f"📱 수신번호: {phone_number}")
+ print(f"👤 대상자: {name}")
+ print(f"📝 메시지: {message}")
+ if reason:
+ print(f"❌ 거부 사유: {reason}")
+ print("=" * 60)
+ return {
+ 'success': True,
+ 'message': 'SMS 테스트 모드: 탈퇴 거부 알림이 콘솔에 출력되었습니다.',
+ 'test_mode': True
+ }
+ else:
+ return sms_service.send_withdrawal_rejection_sms(phone_number, name, reason)
diff --git a/A_core/telegram_utils.py b/A_core/telegram_utils.py
new file mode 100644
index 0000000..54bece8
--- /dev/null
+++ b/A_core/telegram_utils.py
@@ -0,0 +1,271 @@
+"""
+텔레그램 봇 유틸리티
+회원가입 알림 등을 위한 텔레그램 메시지 전송 기능
+"""
+
+import requests
+import threading
+from django.conf import settings
+from datetime import datetime
+
+
+def get_client_ip(request):
+ """
+ 클라이언트의 실제 IP 주소를 추출
+ 프록시, 로드밸런서, CDN 등을 고려하여 실제 IP를 찾음
+ """
+ # 프록시나 로드밸런서를 통한 실제 클라이언트 IP 확인
+ x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
+ if x_forwarded_for:
+ # 첫 번째 IP가 실제 클라이언트 IP (쉼표로 구분된 경우)
+ ip = x_forwarded_for.split(',')[0].strip()
+ if ip and ip != '127.0.0.1':
+ return ip
+
+ # Cloudflare 등 CDN에서 사용하는 헤더
+ cf_connecting_ip = request.META.get('HTTP_CF_CONNECTING_IP')
+ if cf_connecting_ip and cf_connecting_ip != '127.0.0.1':
+ return cf_connecting_ip
+
+ # Nginx 등 리버스 프록시에서 사용
+ x_real_ip = request.META.get('HTTP_X_REAL_IP')
+ if x_real_ip and x_real_ip != '127.0.0.1':
+ return x_real_ip
+
+ # 기본 REMOTE_ADDR
+ remote_addr = request.META.get('REMOTE_ADDR', '알 수 없음')
+
+ # 로컬 환경 표시
+ if remote_addr == '127.0.0.1':
+ return f"{remote_addr} (로컬 개발환경)"
+ elif remote_addr.startswith('192.168.') or remote_addr.startswith('10.') or remote_addr.startswith('172.'):
+ return f"{remote_addr} (내부 네트워크)"
+
+ return remote_addr
+
+
+def get_device_info(user_agent):
+ """User-Agent에서 기기 정보 추출"""
+ if not user_agent:
+ return "알 수 없음"
+
+ # 모바일 기기 체크
+ if any(mobile in user_agent for mobile in ['iPhone', 'iPad', 'Android', 'Mobile']):
+ if 'iPhone' in user_agent:
+ return "iPhone"
+ elif 'iPad' in user_agent:
+ return "iPad"
+ elif 'Android' in user_agent:
+ if 'Mobile' in user_agent:
+ return "Android 폰"
+ else:
+ return "Android 태블릿"
+ else:
+ return "모바일"
+
+ # 데스크톱 OS 체크
+ if 'Windows NT' in user_agent:
+ if 'Windows NT 10.0' in user_agent:
+ return "Windows 10/11"
+ elif 'Windows NT 6.3' in user_agent:
+ return "Windows 8.1"
+ elif 'Windows NT 6.1' in user_agent:
+ return "Windows 7"
+ else:
+ return "Windows"
+ elif 'Macintosh' in user_agent or 'Mac OS X' in user_agent:
+ return "Mac"
+ elif 'Linux' in user_agent and 'Android' not in user_agent:
+ return "Linux"
+
+ return "알 수 없음"
+
+
+def get_browser_info(user_agent):
+ """User-Agent에서 브라우저 정보 추출"""
+ if not user_agent:
+ return "알 수 없음"
+
+ # 브라우저 체크 (순서 중요 - Chrome이 Safari 문자열도 포함하므로)
+ if 'Edg/' in user_agent:
+ return "Microsoft Edge"
+ elif 'Chrome/' in user_agent and 'Safari/' in user_agent:
+ return "Chrome"
+ elif 'Firefox/' in user_agent:
+ return "Firefox"
+ elif 'Safari/' in user_agent and 'Chrome' not in user_agent:
+ return "Safari"
+ elif 'Opera' in user_agent or 'OPR/' in user_agent:
+ return "Opera"
+
+ return "알 수 없음"
+
+
+def send_telegram_message(message, chat_id=None):
+ """
+ 텔레그램 봇으로 메시지 전송
+
+ Args:
+ message (str): 전송할 메시지
+ chat_id (str, optional): 채팅 ID. 없으면 기본 설정값 사용
+
+ Returns:
+ dict: {'success': bool, 'error': str}
+ """
+ try:
+ if not hasattr(settings, 'TELEGRAM_BOT_TOKEN') or not settings.TELEGRAM_BOT_TOKEN:
+ return {'success': False, 'error': 'TELEGRAM_BOT_TOKEN이 설정되지 않았습니다.'}
+
+ if not chat_id:
+ chat_id = getattr(settings, 'TELEGRAM_CHAT_ID', None)
+ if not chat_id:
+ return {'success': False, 'error': 'TELEGRAM_CHAT_ID가 설정되지 않았습니다.'}
+
+ url = f"https://api.telegram.org/bot{settings.TELEGRAM_BOT_TOKEN}/sendMessage"
+
+ payload = {
+ 'chat_id': chat_id,
+ 'text': message,
+ 'parse_mode': 'HTML' # HTML 포맷 지원
+ }
+
+ response = requests.post(url, json=payload, timeout=10)
+
+ if response.status_code == 200:
+ result = response.json()
+ if result.get('ok'):
+ print(f"[TELEGRAM_SUCCESS] 메시지 전송 성공: {message[:50]}...")
+ return {'success': True, 'error': None}
+ else:
+ error_msg = result.get('description', '알 수 없는 오류')
+ print(f"[TELEGRAM_ERROR] API 오류: {error_msg}")
+ return {'success': False, 'error': f'텔레그램 API 오류: {error_msg}'}
+ else:
+ print(f"[TELEGRAM_ERROR] HTTP 오류: {response.status_code}")
+ return {'success': False, 'error': f'HTTP 오류: {response.status_code}'}
+
+ except requests.exceptions.Timeout:
+ print("[TELEGRAM_ERROR] 요청 시간 초과")
+ return {'success': False, 'error': '요청 시간 초과'}
+ except requests.exceptions.RequestException as e:
+ print(f"[TELEGRAM_ERROR] 네트워크 오류: {str(e)}")
+ return {'success': False, 'error': f'네트워크 오류: {str(e)}'}
+ except Exception as e:
+ print(f"[TELEGRAM_ERROR] 예상치 못한 오류: {str(e)}")
+ return {'success': False, 'error': f'예상치 못한 오류: {str(e)}'}
+
+
+def send_telegram_message_async(message, chat_id=None):
+ """
+ 비동기로 텔레그램 메시지 전송 (백그라운드)
+
+ Args:
+ message (str): 전송할 메시지
+ chat_id (str, optional): 채팅 ID
+ """
+ def _send():
+ send_telegram_message(message, chat_id)
+
+ thread = threading.Thread(target=_send)
+ thread.daemon = True
+ thread.start()
+
+
+def send_signup_notification(name, phone, request):
+ """
+ 회원가입 문자인증 요청 알림
+
+ Args:
+ name (str): 가입자 이름
+ phone (str): 전화번호
+ request: Django request 객체 (IP, User-Agent 정보)
+ """
+ current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+
+ # 클라이언트 정보 추출
+ ip = get_client_ip(request)
+ user_agent = request.META.get('HTTP_USER_AGENT', '알 수 없음')
+ device_info = get_device_info(user_agent)
+ browser_info = get_browser_info(user_agent)
+
+ # 디버그 정보 출력
+ print(f"[TELEGRAM_DEBUG] 회원가입 알림 - IP: {ip}, 기기: {device_info}, 브라우저: {browser_info}")
+
+ # 상세 헤더 정보 (개발 환경에서만)
+ if ip.endswith('(로컬 개발환경)'):
+ print(f"[DEBUG] REMOTE_ADDR: {request.META.get('REMOTE_ADDR')}")
+ print(f"[DEBUG] HTTP_X_FORWARDED_FOR: {request.META.get('HTTP_X_FORWARDED_FOR')}")
+ print(f"[DEBUG] HTTP_X_REAL_IP: {request.META.get('HTTP_X_REAL_IP')}")
+ print(f"[DEBUG] HTTP_CF_CONNECTING_IP: {request.META.get('HTTP_CF_CONNECTING_IP')}")
+ print(f"[DEBUG] User-Agent: {user_agent[:100]}...")
+
+ message = f"""
+🔔 신라AMP 회원가입 알림
+
+👤 이름: {name}
+📱 전화번호: {phone}
+⏰ 요청시간: {current_time}
+
+🌐 접속 정보:
+• IP 주소: {ip}
+• 기기: {device_info}
+• 브라우저: {browser_info}
+
+💡 새로운 회원가입 요청이 있습니다!
+ """.strip()
+
+ # 비동기로 전송 (사용자 대기시간 없음)
+ send_telegram_message_async(message)
+
+
+def send_password_reset_notification(phone, request):
+ """
+ 비밀번호 찾기 문자인증 요청 알림
+
+ Args:
+ phone (str): 전화번호
+ request: Django request 객체 (IP, User-Agent 정보)
+ """
+ current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+
+ # 클라이언트 정보 추출
+ ip = get_client_ip(request)
+ user_agent = request.META.get('HTTP_USER_AGENT', '알 수 없음')
+ device_info = get_device_info(user_agent)
+ browser_info = get_browser_info(user_agent)
+
+ # 디버그 정보 출력
+ print(f"[TELEGRAM_DEBUG] 비밀번호 찾기 알림 - IP: {ip}, 기기: {device_info}, 브라우저: {browser_info}")
+
+ message = f"""
+🔑 신라AMP 비밀번호 찾기 알림
+
+📱 전화번호: {phone}
+⏰ 요청시간: {current_time}
+
+🌐 접속 정보:
+• IP 주소: {ip}
+• 기기: {device_info}
+• 브라우저: {browser_info}
+
+💡 비밀번호 찾기 요청이 있습니다!
+ """.strip()
+
+ # 비동기로 전송 (사용자 대기시간 없음)
+ send_telegram_message_async(message)
+
+
+def test_telegram_bot():
+ """
+ 텔레그램 봇 연결 테스트
+ """
+ test_message = f"🧪 신라AMP 봇 테스트\n\n⏰ 테스트 시간: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n✅ 봇이 정상적으로 작동하고 있습니다!"
+
+ result = send_telegram_message(test_message)
+
+ if result['success']:
+ print("✅ 텔레그램 봇 테스트 성공!")
+ return True
+ else:
+ print(f"❌ 텔레그램 봇 테스트 실패: {result['error']}")
+ return False
diff --git a/B_main/__pycache__/forms.cpython-38.pyc b/B_main/__pycache__/forms.cpython-38.pyc
index 76afad1..9aa304e 100644
Binary files a/B_main/__pycache__/forms.cpython-38.pyc and b/B_main/__pycache__/forms.cpython-38.pyc differ
diff --git a/B_main/__pycache__/urls.cpython-38.pyc b/B_main/__pycache__/urls.cpython-38.pyc
index 4b02e01..35406aa 100644
Binary files a/B_main/__pycache__/urls.cpython-38.pyc and b/B_main/__pycache__/urls.cpython-38.pyc differ
diff --git a/B_main/__pycache__/views.cpython-38.pyc b/B_main/__pycache__/views.cpython-38.pyc
index 5b8c4cd..dbd928a 100644
Binary files a/B_main/__pycache__/views.cpython-38.pyc and b/B_main/__pycache__/views.cpython-38.pyc differ
diff --git a/B_main/forms.py b/B_main/forms.py
index dbfee55..7ff220e 100644
--- a/B_main/forms.py
+++ b/B_main/forms.py
@@ -305,6 +305,6 @@ class PersonForm(forms.ModelForm):
}),
'keyword1': forms.TextInput(attrs={
'class': 'w-full px-4 py-3 rounded-xl bg-gray-700 bg-opacity-80 text-white placeholder-gray-400 border border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 transition',
- 'placeholder': '검색 키워드 (예: 회계감사)'
+ 'placeholder': '검색 키워드 (예: 잘생김, 골프천재, 댄스머신 ...)'
}),
}
diff --git a/B_main/templates/B_main/main.htm b/B_main/templates/B_main/main.htm
index 4aa421a..9669fd2 100644
--- a/B_main/templates/B_main/main.htm
+++ b/B_main/templates/B_main/main.htm
@@ -304,7 +304,7 @@
// 소개글 설정
if (intro && intro.trim() !== '') {
- modalIntro.textContent = intro;
+ modalIntro.innerHTML = intro.replace(/\n/g, '
');
modalIntroSection.classList.remove('hidden');
modalNoIntro.classList.add('hidden');
} else {
diff --git a/B_main/templates/B_main/partials/card.htm b/B_main/templates/B_main/partials/card.htm
index acb1baa..9e8d894 100644
--- a/B_main/templates/B_main/partials/card.htm
+++ b/B_main/templates/B_main/partials/card.htm
@@ -1,34 +1,38 @@
{% load static %}
-
- {% endif %}
- {% if person.이름 %}
-
+
+ {% endif %}
+