""" Core sign-in business logic service Handles Weibo super topic sign-in operations """ import os import sys import asyncio import httpx import logging import random from datetime import datetime, timedelta from typing import Dict, Any, List, Optional from uuid import UUID from sqlalchemy import select, update # Add parent directory to path for imports sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../../..")) from shared.models.base import AsyncSessionLocal from shared.models.account import Account from shared.models.signin_log import SigninLog from shared.crypto import decrypt_cookie, derive_key from shared.config import shared_settings from app.config import settings from app.models.signin_models import SignInRequest, SignInResult, TaskStatus, WeiboAccount, WeiboSuperTopic, AntiBotConfig from app.services.weibo_client import WeiboClient from app.services.antibot import antibot logger = logging.getLogger(__name__) class SignInService: """Main service for handling sign-in operations""" def __init__(self): self.weibo_client = WeiboClient() self.active_tasks: Dict[str, TaskStatus] = {} self.antibot_config = AntiBotConfig( random_delay_min=settings.RANDOM_DELAY_MIN, random_delay_max=settings.RANDOM_DELAY_MAX, user_agent_rotation=settings.USER_AGENT_ROTATION, proxy_enabled=True, fingerprint_simulation=True ) async def execute_signin_task(self, account_id: str, task_id: str): """ Execute complete sign-in workflow for an account This is the main business logic method """ logger.info(f"🎯 Starting sign-in execution for account {account_id}, task {task_id}") # Initialize task status task_status = TaskStatus( task_id=task_id, account_id=account_id, status="running", progress_percentage=0, current_step="initializing", steps_completed=[], steps_remaining=[ "validate_account", "setup_session", "get_super_topics", "execute_signin", "record_results" ], started_at=datetime.now() ) self.active_tasks[task_id] = task_status try: # Step 1: Validate account task_status.current_step = "validate_account" await self._update_task_progress(task_id, 10) account = await self._get_account_info(account_id) if not account or account.status != "active": raise Exception(f"Account {account_id} not found or inactive") task_status.steps_completed.append("validate_account") task_status.steps_remaining.remove("validate_account") task_status.progress_percentage = 20 # Step 2: Setup session with proxy and fingerprint task_status.current_step = "setup_session" # Verify cookies before proceeding cookies_valid = await self.weibo_client.verify_cookies(account) if not cookies_valid: logger.error(f"Cookies invalid for account {account_id}") # Update account status to invalid_cookie await self._update_account_status(account_id, "invalid_cookie") raise Exception("Cookie validation failed - cookies are invalid or expired") await self._apply_anti_bot_protection() task_status.steps_completed.append("setup_session") task_status.steps_remaining.remove("setup_session") task_status.progress_percentage = 30 # Step 3: Get super topics list task_status.current_step = "get_super_topics" await self._update_task_progress(task_id, 40) super_topics = await self._get_super_topics_list(account) if not super_topics: logger.warning(f"No super topics found for account {account_id}") task_status.steps_completed.append("get_super_topics") task_status.steps_remaining.remove("get_super_topics") task_status.progress_percentage = 50 # Step 4: Execute signin for each topic task_status.current_step = "execute_signin" signin_results = await self._execute_topic_signin(account, super_topics, task_id) task_status.steps_completed.append("execute_signin") task_status.steps_remaining.remove("execute_signin") task_status.progress_percentage = 80 # Step 5: Record results task_status.current_step = "record_results" await self._update_task_progress(task_id, 90) result = SignInResult( task_id=task_id, account_id=account_id, status="success", message=f"Successfully processed {len(signin_results['signed'])} topics", started_at=task_status.started_at, completed_at=datetime.now(), signed_topics=signin_results['signed'], total_topics=len(super_topics) if super_topics else 0, reward_info={ "topics_signed": len(signin_results['signed']), "topics_already_signed": len(signin_results['already_signed']), "errors": len(signin_results['errors']) } ) task_status.status = "success" task_status.progress_percentage = 100 task_status.current_step = "completed" logger.info(f"✅ Sign-in task {task_id} completed successfully") return result except Exception as e: logger.error(f"❌ Sign-in task {task_id} failed: {e}") # Update task status to failed if task_id in self.active_tasks: task_status = self.active_tasks[task_id] task_status.status = "failed" task_status.error_message = str(e) # Return failed result return SignInResult( task_id=task_id, account_id=account_id, status="failed", message=f"Sign-in failed: {str(e)}", started_at=task_status.started_at if task_id in self.active_tasks else datetime.now(), completed_at=datetime.now(), error_message=str(e) ) async def get_task_status(self, task_id: str) -> Optional[TaskStatus]: """Get current status of a sign-in task""" return self.active_tasks.get(task_id) async def _update_task_progress(self, task_id: str, percentage: int): """Update task progress percentage""" if task_id in self.active_tasks: self.active_tasks[task_id].progress_percentage = percentage self.active_tasks[task_id].updated_at = datetime.now() async def _get_account_info(self, account_id: str) -> Optional[WeiboAccount]: """ Get Weibo account information from database (replaces mock data). Returns account dict or None if not found. """ try: async with AsyncSessionLocal() as session: stmt = select(Account).where(Account.id == account_id) result = await session.execute(stmt) account = result.scalar_one_or_none() if not account: logger.error(f"Account {account_id} not found in database") return None # Convert ORM model to Pydantic model return WeiboAccount( id=UUID(account.id), user_id=UUID(account.user_id), weibo_user_id=account.weibo_user_id, remark=account.remark or "", encrypted_cookies=account.encrypted_cookies, iv=account.iv, status=account.status, last_checked_at=account.last_checked_at or datetime.now() ) except Exception as e: logger.error(f"Error fetching account {account_id}: {e}") return None async def _apply_anti_bot_protection(self): """Apply anti-bot protection measures""" # Random delay to mimic human behavior delay = antibot.get_random_delay() logger.debug(f"Applying random delay: {delay:.2f}s") await asyncio.sleep(delay) # Get random User-Agent user_agent = antibot.get_random_user_agent() logger.debug(f"Using User-Agent: {user_agent[:50]}...") # Try to get proxy (falls back to direct connection if unavailable) proxy = await antibot.get_proxy() if proxy: logger.info(f"Using proxy for requests") else: logger.info("Using direct connection (no proxy available)") # Get browser fingerprint fingerprint = antibot.get_fingerprint_data() logger.debug(f"Browser fingerprint: {fingerprint}") async def _get_super_topics_list(self, account: WeiboAccount) -> List[WeiboSuperTopic]: """Get list of super topics for account""" try: # Mock implementation - in real system, fetch from Weibo API # Simulate API call delay await asyncio.sleep(1) # Return mock super topics return [ WeiboSuperTopic( id="topic_001", title="Python编程", url="https://weibo.com/p/100808xxx", is_signed=False, sign_url="https://weibo.com/p/aj/general/button", reward_exp=2, reward_credit=1 ), WeiboSuperTopic( id="topic_002", title="人工智能", url="https://weibo.com/p/100808yyy", is_signed=False, sign_url="https://weibo.com/p/aj/general/button", reward_exp=2, reward_credit=1 ), WeiboSuperTopic( id="topic_003", title="机器学习", url="https://weibo.com/p/100808zzz", is_signed=True, # Already signed sign_url="https://weibo.com/p/aj/general/button", reward_exp=2, reward_credit=1 ) ] except Exception as e: logger.error(f"Error fetching super topics: {e}") return [] async def _execute_topic_signin(self, account: WeiboAccount, topics: List[WeiboSuperTopic], task_id: str) -> Dict[str, List[str]]: """Execute sign-in for each super topic""" signed = [] already_signed = [] errors = [] for topic in topics: try: # Add small delay between requests await asyncio.sleep(random.uniform(0.5, 1.5)) if topic.is_signed: already_signed.append(topic.title) # Write log for already signed await self._write_signin_log( account_id=str(account.id), topic_title=topic.title, status="failed_already_signed", reward_info=None, error_message="Already signed today" ) continue # Execute signin for this topic success, reward_info, error_msg = await self.weibo_client.sign_super_topic( account=account, topic=topic, task_id=task_id ) if success: signed.append(topic.title) logger.info(f"✅ Successfully signed topic: {topic.title}") # Write success log await self._write_signin_log( account_id=str(account.id), topic_title=topic.title, status="success", reward_info=reward_info, error_message=None ) else: errors.append(f"Failed to sign topic: {topic.title}") # Write failure log await self._write_signin_log( account_id=str(account.id), topic_title=topic.title, status="failed_network", reward_info=None, error_message=error_msg ) except Exception as e: error_msg = f"Error signing topic {topic.title}: {str(e)}" logger.error(error_msg) errors.append(error_msg) # Write error log await self._write_signin_log( account_id=str(account.id), topic_title=topic.title, status="failed_network", reward_info=None, error_message=str(e) ) return { "signed": signed, "already_signed": already_signed, "errors": errors } async def _write_signin_log( self, account_id: str, topic_title: str, status: str, reward_info: Optional[Dict[str, Any]], error_message: Optional[str] ): """ Write signin result to signin_logs table. Replaces mock implementation with real database write. """ try: async with AsyncSessionLocal() as session: log = SigninLog( account_id=account_id, topic_title=topic_title, status=status, reward_info=reward_info, error_message=error_message, ) session.add(log) await session.commit() logger.debug(f"Wrote signin log for account {account_id}, topic {topic_title}, status {status}") except Exception as e: logger.error(f"Failed to write signin log: {e}") async def _update_account_status(self, account_id: str, status: str): """ Update account status in database. Used when cookie is invalid or account is banned. """ try: async with AsyncSessionLocal() as session: stmt = ( update(Account) .where(Account.id == account_id) .values(status=status, last_checked_at=datetime.now()) ) await session.execute(stmt) await session.commit() logger.info(f"Updated account {account_id} status to {status}") except Exception as e: logger.error(f"Failed to update account status: {e}")