""" Car Availability Verification Service Checks if imported cars are still available on Carmodoo """ import asyncio import httpx from datetime import datetime from typing import List, Tuple from sqlalchemy.orm import Session from lxml import etree from ..models.car import Car from ..models.settings import SystemSettings # Carmodoo base URL CARMODOO_BASE_URL = "https://dealer.carmodoo.com" class CarAvailabilityChecker: """카모두에서 차량 판매상태 확인""" def __init__(self): self.cookies = {} self.is_logged_in = False self.headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7', } async def login(self) -> bool: """카모두 로그인""" import os user_id = os.environ.get('CARMODOO_USER_ID', '') password = os.environ.get('CARMODOO_PASSWORD', '') if not user_id or not password: print("[CarAvailability] No Carmodoo credentials found") return False try: async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client: login_url = f"{CARMODOO_BASE_URL}/member/memberLoginOk.html" form_data = { 'userId': user_id, 'userPwd': password, 'auto_login': 'N', } response = await client.post( login_url, data=form_data, headers=self.headers ) self.cookies = dict(response.cookies) if 'JSESSIONID' in self.cookies or response.status_code == 200: self.is_logged_in = True print("[CarAvailability] Login successful") return True else: print("[CarAvailability] Login failed") return False except Exception as e: print(f"[CarAvailability] Login error: {e}") return False async def check_car_availability(self, source_id: str) -> bool: """ 특정 차량이 카모두에서 아직 판매중인지 확인 source_id: 카모두 차량 번호 (c_cNo) Returns: True if still available, False if sold/removed """ if not self.is_logged_in: await self.login() try: async with httpx.AsyncClient(timeout=30.0, cookies=self.cookies) as client: # 차량 상세 페이지 직접 접근 detail_url = f"{CARMODOO_BASE_URL}/car/carPopView.html" params = {'c_cNo': source_id} response = await client.get( detail_url, params=params, headers=self.headers ) if response.status_code != 200: return False # EUC-KR 디코딩 try: content = response.content.decode('euc-kr', errors='replace') except: content = response.text # 판매완료/삭제 표시 확인 sold_indicators = [ '판매완료', '판매 완료', '삭제된 차량', '존재하지 않는', '해당 차량이 없습니다', '매물이 존재하지', ] for indicator in sold_indicators: if indicator in content: return False # 차량 정보가 있으면 판매중 if 'carViewWrap' in content or '차량정보' in content or '차량번호' in content: return True # 기본적으로 응답이 있으면 판매중으로 간주 return len(content) > 500 except Exception as e: print(f"[CarAvailability] Error checking car {source_id}: {e}") # 에러 시 기존 상태 유지 (판매중으로 간주) return True async def verify_all_cars(self, db: Session) -> Tuple[int, int, int]: """ 모든 활성 차량의 판매상태 확인 Returns: (total_checked, still_available, sold_count) """ # soldout=False인 차량만 확인 cars = db.query(Car).filter( Car.soldout == False, Car.source == 'carmodoo' ).all() if not cars: return (0, 0, 0) # 로그인 if not self.is_logged_in: login_success = await self.login() if not login_success: print("[CarAvailability] Failed to login, aborting verification") return (0, 0, 0) total = len(cars) available = 0 sold = 0 print(f"[CarAvailability] Starting verification of {total} cars...") for i, car in enumerate(cars): try: is_available = await self.check_car_availability(car.source_id) if is_available: available += 1 else: sold += 1 car.soldout = True print(f"[CarAvailability] Car {car.id} ({car.car_name}) marked as sold") # Rate limiting - 0.5초 대기 if i < total - 1: await asyncio.sleep(0.5) # 진행상황 로그 (10개마다) if (i + 1) % 10 == 0: print(f"[CarAvailability] Progress: {i + 1}/{total}") except Exception as e: print(f"[CarAvailability] Error checking car {car.id}: {e}") available += 1 # 에러 시 판매중으로 간주 # DB 커밋 db.commit() print(f"[CarAvailability] Verification complete: {total} checked, {available} available, {sold} sold") return (total, available, sold) async def run_car_availability_check(db: Session) -> str: """ 차량 판매상태 검증 실행 및 결과 저장 Returns: 결과 요약 문자열 """ checker = CarAvailabilityChecker() total, available, sold = await checker.verify_all_cars(db) # 설정에 결과 저장 settings = db.query(SystemSettings).first() if settings: settings.car_availability_last_check = datetime.now() settings.car_availability_last_result = f"Checked: {total}, Available: {available}, Sold: {sold}" db.commit() return f"Checked: {total}, Available: {available}, Sold: {sold}"