import os import time import json import logging import requests from datetime import datetime from dotenv import load_dotenv import colorama from colorama import Fore, Back, Style # Initialize colorama for Windows colorama.init() # Load environment variables load_dotenv() # Configure logging with colors and terminal output class ColoredFormatter(logging.Formatter): """Custom formatter with colors for different log levels""" COLORS = { 'DEBUG': Fore.CYAN, 'INFO': Fore.GREEN, 'WARNING': Fore.YELLOW, 'ERROR': Fore.RED, 'CRITICAL': Fore.RED + Back.WHITE, } def format(self, record): # Add color to the level name levelname = record.levelname if levelname in self.COLORS: record.levelname = f"{self.COLORS[levelname]}{levelname}{Style.RESET_ALL}" # Add color to the message based on content if 'SENT' in record.getMessage(): record.msg = f"{Fore.BLUE}{record.msg}{Style.RESET_ALL}" elif 'RECEIVED' in record.getMessage(): record.msg = f"{Fore.MAGENTA}{record.msg}{Style.RESET_ALL}" elif 'AI GENERATED' in record.getMessage(): record.msg = f"{Fore.CYAN}{record.msg}{Style.RESET_ALL}" elif 'ERROR' in record.getMessage(): record.msg = f"{Fore.RED}{record.msg}{Style.RESET_ALL}" elif 'SUCCESS' in record.getMessage(): record.msg = f"{Fore.GREEN}{record.msg}{Style.RESET_ALL}" return super().format(record) # Set up logging logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) # Create console handler with colored formatter console_handler = logging.StreamHandler() console_handler.setLevel(logging.INFO) formatter = ColoredFormatter( '%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%dT%H:%M:%S' ) console_handler.setFormatter(formatter) logger.addHandler(console_handler) # Create file handler for persistent logs file_handler = logging.FileHandler('icebreaker_bot.log') file_handler.setLevel(logging.INFO) file_formatter = logging.Formatter( '%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%dT%H:%M:%S' ) file_handler.setFormatter(file_formatter) logger.addHandler(file_handler) class EnhancedMeetMeClient: """Enhanced client for interacting with MeetMe API (local or production)""" def __init__(self): self.session = requests.Session() self.api_base_url = os.getenv('API_BASE_URL', 'https://api.meetme.com') self.use_local_api = os.getenv('USE_LOCAL_API', 'false').lower() == 'true' self.api_version = os.getenv('API_VERSION', 'v1') self.api_fallback_version = os.getenv('API_FALLBACK_VERSION', 'v1') self.api_retry_attempts = int(os.getenv('API_RETRY_ATTEMPTS', '3')) self.api_retry_delay = int(os.getenv('API_RETRY_DELAY', '60')) # Validate required environment variables self._validate_environment() # Configure headers based on API type if self.use_local_api: self.headers = { 'User-Agent': 'MeetMe-Bot/1.0', 'Content-Type': 'application/json', 'Accept': 'application/json' } else: self.headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Content-Type': 'application/json', 'Accept': 'application/json' } self.session.headers.update(self.headers) self.auth_token = None self.my_user_id = None # Statistics tracking self.stats = { 'messages_sent': 0, 'users_processed': 0, 'errors': 0, 'start_time': datetime.now() } def _validate_environment(self): """Validate required environment variables""" if self.use_local_api: if not os.getenv('API_USERNAME'): raise ValueError("API_USERNAME must be set for local API") if not os.getenv('API_PASSWORD'): raise ValueError("API_PASSWORD must be set for local API") else: if not os.getenv('MM_USERNAME'): raise ValueError("MM_USERNAME must be set for production API") if not os.getenv('MM_PASSWORD'): raise ValueError("MM_PASSWORD must be set for production API") if not os.getenv('OLLAMA_ENDPOINT'): raise ValueError("OLLAMA_ENDPOINT must be set") def detect_api_version(self): """Detect available API version (v2 first, fallback to v1)""" if self.use_local_api: return 'v1' # Local API always uses v1 structure for version in [self.api_version, self.api_fallback_version]: try: logger.info(f"{Fore.CYAN}[DETECTING] Testing API version {version}...{Style.RESET_ALL}") response = self.session.get( f"https://api.meetme.com/api/{version}", timeout=10 ) if response.status_code == 200: logger.info(f"{Fore.GREEN}[SUCCESS] API version {version} is available{Style.RESET_ALL}") return version elif response.status_code == 503: error_data = response.json() if error_data.get('errorType') == 'tMaintenanceException': logger.warning(f"{Fore.YELLOW}[MAINTENANCE] API version {version} is under maintenance{Style.RESET_ALL}") else: logger.warning(f"{Fore.YELLOW}[UNAVAILABLE] API version {version} returned {response.status_code}{Style.RESET_ALL}") else: logger.warning(f"{Fore.YELLOW}[UNAVAILABLE] API version {version} returned {response.status_code}{Style.RESET_ALL}") except requests.exceptions.Timeout: logger.warning(f"{Fore.YELLOW}[TIMEOUT] API version {version} timed out{Style.RESET_ALL}") except Exception as e: logger.warning(f"{Fore.YELLOW}[ERROR] API version {version} error: {str(e)}{Style.RESET_ALL}") logger.warning(f"{Fore.YELLOW}[FALLBACK] Using default API version v1{Style.RESET_ALL}") return 'v1' def _handle_maintenance_error(self, response): """Handle API maintenance errors""" if response.status_code == 503: try: error_data = response.json() if error_data.get('errorType') == 'tMaintenanceException': logger.warning(f"{Fore.YELLOW}[MAINTENANCE] API is under maintenance: {error_data.get('message', 'Unknown')}{Style.RESET_ALL}") return True except: pass return False def get_stats(self): """Get current statistics""" runtime = datetime.now() - self.stats['start_time'] return { **self.stats, 'runtime': str(runtime), 'messages_per_hour': self.stats['messages_sent'] / max(runtime.total_seconds() / 3600, 1) } def login(self): """Authenticate with MeetMe API (local or production)""" try: logger.info(f"{Fore.YELLOW}[LOGIN] ATTEMPTING LOGIN to {'local' if self.use_local_api else 'production'} API...{Style.RESET_ALL}") if self.use_local_api: return self._login_local() else: return self._login_production() except Exception as e: logger.error(f"[ERROR] LOGIN ERROR: {str(e)}") self.stats['errors'] += 1 return False def _login_local(self): """Login to local Node.js backend""" try: username = os.getenv('API_USERNAME') password = os.getenv('API_PASSWORD') login_data = { 'email': username, 'password': password } response = self.session.post( f"{self.api_base_url}/auth/login", json=login_data, timeout=30 ) if response.status_code == 200: data = response.json() self.auth_token = data.get('token') self.my_user_id = data.get('user_id') or data.get('user', {}).get('_id') if self.auth_token: self.session.headers.update({'Authorization': f'Bearer {self.auth_token}'}) logger.info(f"{Fore.GREEN}[SUCCESS] LOGIN SUCCESS - Local API as user {self.my_user_id}{Style.RESET_ALL}") return True else: logger.error(f"[ERROR] LOGIN FAILED - Status {response.status_code}: {response.text}") self.stats['errors'] += 1 return False except requests.exceptions.Timeout: logger.error("[ERROR] LOGIN TIMEOUT - Server not responding") self.stats['errors'] += 1 return False except Exception as e: logger.error(f"[ERROR] LOGIN ERROR: {str(e)}") self.stats['errors'] += 1 return False def _login_production(self): """Login to production MeetMe API""" try: # Detect available API version detected_version = self.detect_api_version() api_url = f"https://api.meetme.com/api/{detected_version}" username = os.getenv('MM_USERNAME') password = os.getenv('MM_PASSWORD') login_data = { 'email': username, 'password': password } response = self.session.post( f"{api_url}/auth/login", json=login_data, timeout=30 ) # Handle maintenance errors if self._handle_maintenance_error(response): logger.warning(f"{Fore.YELLOW}[RETRY] Will retry login in {self.api_retry_delay} seconds...{Style.RESET_ALL}") time.sleep(self.api_retry_delay) return False if response.status_code == 200: data = response.json() self.my_user_id = data.get('user_id') or data.get('user', {}).get('id') logger.info(f"{Fore.GREEN}[SUCCESS] LOGIN SUCCESS - Production API v{detected_version} as user {self.my_user_id}{Style.RESET_ALL}") return True else: logger.error(f"{Fore.RED}[ERROR] LOGIN FAILED - Status {response.status_code}: {response.text}{Style.RESET_ALL}") self.stats['errors'] += 1 return False except requests.exceptions.Timeout: logger.error(f"{Fore.RED}[TIMEOUT] LOGIN TIMEOUT - Server not responding{Style.RESET_ALL}") self.stats['errors'] += 1 return False except Exception as e: logger.error(f"{Fore.RED}[ERROR] LOGIN ERROR: {str(e)}{Style.RESET_ALL}") self.stats['errors'] += 1 return False def get_nearby_users(self, page=1): """Get list of nearby users""" try: logger.info(f"{Fore.CYAN}[SEARCHING] SEARCHING for nearby users (page {page})...{Style.RESET_ALL}") if self.use_local_api: return self._get_nearby_users_local(page) else: return self._get_nearby_users_production(page) except Exception as e: logger.error(f"[ERROR] ERROR getting nearby users: {str(e)}") self.stats['errors'] += 1 return [] def _get_nearby_users_local(self, page=1): """Get nearby users from local API""" try: response = self.session.get( f"{self.api_base_url}/users/nearby", params={'page': page, 'limit': 20}, timeout=30 ) if response.status_code == 200: data = response.json() users = [] for user in data.get('users', []): # Handle different field names between APIs user_id = user.get('_id') or user.get('id') display_name = user.get('username') or user.get('display_name') or user.get('name') bio = user.get('bio') or user.get('description') or '' if user_id and display_name: # Only add valid users users.append({ 'id': user_id, 'display_name': display_name, 'bio': bio }) logger.info(f"{Fore.GREEN}[SUCCESS] FOUND {len(users)} nearby users from local API{Style.RESET_ALL}") return users else: logger.error(f"[ERROR] FAILED to get nearby users from local API: {response.status_code}") self.stats['errors'] += 1 return [] except requests.exceptions.Timeout: logger.error("[ERROR] TIMEOUT getting nearby users from local API") self.stats['errors'] += 1 return [] except Exception as e: logger.error(f"[ERROR] ERROR getting nearby users from local API: {str(e)}") self.stats['errors'] += 1 return [] def _get_nearby_users_production(self, page=1): """Get nearby users from production API""" try: # Use detected API version detected_version = self.detect_api_version() api_url = f"https://api.meetme.com/api/{detected_version}" response = self.session.get( f"{api_url}/users/nearby", params={'page': page, 'limit': 20}, timeout=30 ) # Handle maintenance errors if self._handle_maintenance_error(response): logger.warning(f"{Fore.YELLOW}[RETRY] Will retry getting nearby users in {self.api_retry_delay} seconds...{Style.RESET_ALL}") time.sleep(self.api_retry_delay) return [] if response.status_code == 200: data = response.json() users = [] for user in data.get('users', []): # Handle different field names between APIs user_id = user.get('id') or user.get('_id') display_name = user.get('display_name') or user.get('username') or user.get('name') bio = user.get('bio') or user.get('description') or '' if user_id and display_name: # Only add valid users users.append({ 'id': user_id, 'display_name': display_name, 'bio': bio }) logger.info(f"{Fore.GREEN}[SUCCESS] FOUND {len(users)} nearby users from production API v{detected_version}{Style.RESET_ALL}") return users else: logger.error(f"{Fore.RED}[ERROR] FAILED to get nearby users from production API: {response.status_code}{Style.RESET_ALL}") self.stats['errors'] += 1 return [] except requests.exceptions.Timeout: logger.error(f"{Fore.RED}[TIMEOUT] TIMEOUT getting nearby users from production API{Style.RESET_ALL}") self.stats['errors'] += 1 return [] except Exception as e: logger.error(f"{Fore.RED}[ERROR] ERROR getting nearby users from production API: {str(e)}{Style.RESET_ALL}") self.stats['errors'] += 1 return [] def send_message(self, user_id, text): """Send message to a specific user""" try: logger.info(f"{Fore.BLUE}[SENDING] SENDING MESSAGE to user {user_id}...{Style.RESET_ALL}") logger.info(f"{Fore.CYAN}[MESSAGE] MESSAGE CONTENT: {text[:100]}{'...' if len(text) > 100 else ''}{Style.RESET_ALL}") if self.use_local_api: return self._send_message_local(user_id, text) else: return self._send_message_production(user_id, text) except Exception as e: logger.error(f"[ERROR] ERROR sending message: {str(e)}") self.stats['errors'] += 1 return False def _send_message_local(self, user_id, text): """Send message via local API""" try: message_data = { 'recipient_id': user_id, 'message': text, 'timestamp': datetime.now().isoformat() } response = self.session.post( f"{self.api_base_url}/messages", json=message_data, timeout=30 ) if response.status_code == 200: logger.info(f"{Fore.GREEN}[SUCCESS] MESSAGE SENT SUCCESSFULLY to user {user_id} via local API{Style.RESET_ALL}") self.stats['messages_sent'] += 1 return True else: logger.error(f"[ERROR] FAILED to send message via local API: {response.status_code}") self.stats['errors'] += 1 return False except requests.exceptions.Timeout: logger.error("[ERROR] TIMEOUT sending message via local API") self.stats['errors'] += 1 return False except Exception as e: logger.error(f"[ERROR] ERROR sending message via local API: {str(e)}") self.stats['errors'] += 1 return False def _send_message_production(self, user_id, text): """Send message via production API""" try: # Use detected API version detected_version = self.detect_api_version() api_url = f"https://api.meetme.com/api/{detected_version}" message_data = { 'recipient_id': user_id, 'message': text } response = self.session.post( f"{api_url}/messages/send", json=message_data, timeout=30 ) # Handle maintenance errors if self._handle_maintenance_error(response): logger.warning(f"{Fore.YELLOW}[RETRY] Will retry sending message in {self.api_retry_delay} seconds...{Style.RESET_ALL}") time.sleep(self.api_retry_delay) return False if response.status_code == 200: logger.info(f"{Fore.GREEN}[SUCCESS] MESSAGE SENT SUCCESSFULLY to user {user_id} via production API v{detected_version}{Style.RESET_ALL}") self.stats['messages_sent'] += 1 return True else: logger.error(f"{Fore.RED}[ERROR] FAILED to send message via production API: {response.status_code}{Style.RESET_ALL}") self.stats['errors'] += 1 return False except requests.exceptions.Timeout: logger.error(f"{Fore.RED}[TIMEOUT] TIMEOUT sending message via production API{Style.RESET_ALL}") self.stats['errors'] += 1 return False except Exception as e: logger.error(f"{Fore.RED}[ERROR] ERROR sending message via production API: {str(e)}{Style.RESET_ALL}") self.stats['errors'] += 1 return False def generate_ice_breaker(user): """Generate personalized ice breaker message using Ollama""" try: my_name = os.getenv('MY_NAME', 'DrJones') my_profile = os.getenv('MY_PROFILE', 'water-tech geek, coffee addict') ollama_endpoint = os.getenv('OLLAMA_ENDPOINT') ollama_model = os.getenv('OLLAMA_MODEL', 'quen3') if not ollama_endpoint: logger.error("[ERROR] OLLAMA_ENDPOINT not configured") return None logger.info(f"{Fore.YELLOW}[AUTO RESPONDER] AI GENERATING ice breaker for {user['display_name']}...{Style.RESET_ALL}") # Build prompt with context prompt = f"""You are {my_name}, a {my_profile}. You're reaching out to {user['display_name']} who has this bio: "{user['bio']}" Generate a friendly, personalized ice breaker message (max 100 words) that: - References something from their bio if available - Shows genuine interest in getting to know them - Is casual and conversational - Avoids being overly formal or creepy - Includes a question to encourage response Keep it natural and authentic to your personality.""" # Prepare request to Ollama ollama_request = { "model": ollama_model, "messages": [ {"role": "system", "content": "You are a charismatic, thoughtful conversationalist."}, {"role": "user", "content": prompt} ] } logger.debug(f"Sending prompt to Ollama: {prompt}") response = requests.post( ollama_endpoint, json=ollama_request, headers={'Content-Type': 'application/json'}, timeout=30 ) if response.status_code == 200: data = response.json() assistant_reply = data.get('choices', [{}])[0].get('message', {}).get('content', '').strip() if assistant_reply: logger.info(f"{Fore.CYAN}[AUTO RESPONDER] AI GENERATED RESPONSE: {assistant_reply}{Style.RESET_ALL}") return assistant_reply else: logger.warning("[WARNING] Empty response from Ollama") return None else: logger.error(f"[ERROR] Ollama API error: {response.status_code}") return None except requests.exceptions.Timeout: logger.error("[ERROR] TIMEOUT calling Ollama API") return None except Exception as e: logger.error(f"[ERROR] ERROR generating ice breaker: {str(e)}") return None def print_banner(): """Print startup banner""" banner = f""" {Fore.CYAN}============================================================ ICE BREAKER BOT v2.0 Enhanced Edition ============================================================{Style.RESET_ALL} """ print(banner) def print_stats(stats): """Print formatted statistics""" print(f"\n{Fore.YELLOW}[STATS] BOT STATISTICS:{Style.RESET_ALL}") print(f" Messages Sent: {Fore.GREEN}{stats['messages_sent']}{Style.RESET_ALL}") print(f" Users Processed: {Fore.GREEN}{stats['users_processed']}{Style.RESET_ALL}") print(f" Errors: {Fore.RED}{stats['errors']}{Style.RESET_ALL}") print(f" Runtime: {Fore.CYAN}{stats['runtime']}{Style.RESET_ALL}") print(f" Messages/Hour: {Fore.CYAN}{stats['messages_per_hour']:.1f}{Style.RESET_ALL}") def main(): """Main function for enhanced ice breaker bot""" print_banner() logger.info(f"{Fore.CYAN}[STARTING] Enhanced MeetMe Ice Breaker Bot{Style.RESET_ALL}") # Log API configuration use_local = os.getenv('USE_LOCAL_API', 'false').lower() == 'true' api_url = os.getenv('API_BASE_URL', 'https://api.meetme.com') logger.info(f"{Fore.YELLOW}[CONFIG] Using {'local' if use_local else 'production'} API: {api_url}{Style.RESET_ALL}") # Initialize client and login client = EnhancedMeetMeClient() if not client.login(): logger.error(f"{Fore.RED}[ERROR] FAILED to login. Exiting.{Style.RESET_ALL}") return # Get nearby users users = client.get_nearby_users(page=1) if not users: logger.warning(f"{Fore.YELLOW}[WARNING] No nearby users found{Style.RESET_ALL}") return logger.info(f"{Fore.GREEN}[TARGET] Found {len(users)} nearby users to message{Style.RESET_ALL}") # Process each user for i, user in enumerate(users, 1): try: logger.info(f"{Fore.CYAN}[PROCESSING] user {i}/{len(users)}: {user['display_name']}{Style.RESET_ALL}") logger.info(f"{Fore.MAGENTA}[USER BIO] {user['bio'][:100]}{'...' if len(user['bio']) > 100 else ''}{Style.RESET_ALL}") # Generate ice breaker message ice_breaker = generate_ice_breaker(user) if ice_breaker: # Send the message if client.send_message(user['id'], ice_breaker): logger.info(f"{Fore.GREEN}[SUCCESS] SUCCESS: Sent ice breaker to {user['display_name']}{Style.RESET_ALL}") client.stats['users_processed'] += 1 else: logger.error(f"{Fore.RED}[ERROR] FAILED: Could not send message to {user['display_name']}{Style.RESET_ALL}") else: logger.warning(f"{Fore.YELLOW}[WARNING] SKIPPING {user['display_name']} - no ice breaker generated{Style.RESET_ALL}") # Wait between messages to avoid rate limiting if i < len(users): # Don't sleep after the last user logger.info(f"{Fore.YELLOW}[WAITING] WAITING 60 seconds before next message...{Style.RESET_ALL}") time.sleep(60) except Exception as e: logger.error(f"{Fore.RED}[ERROR] ERROR processing user {user['display_name']}: {str(e)}{Style.RESET_ALL}") continue # Print final statistics final_stats = client.get_stats() print_stats(final_stats) logger.info(f"{Fore.GREEN}[COMPLETE] Enhanced ice breaker bot completed!{Style.RESET_ALL}") if __name__ == "__main__": main()