Files
meetme-bot-workspace/enhanced_icebreaker_bot.py

625 lines
26 KiB
Python

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()