Initial commit

This commit is contained in:
2025-08-25 09:41:16 -06:00
commit 785c7471c0
68 changed files with 30930 additions and 0 deletions

223
scripts/auto.sh Executable file
View File

@@ -0,0 +1,223 @@
#!/bin/bash
# Auto-discover and connect to Android device over WiFi
# Configuration
PHONE_IP="10.0.0.193" # Your phone's IP (this usually stays the same)
RETRY_ATTEMPTS=5
RETRY_DELAY=2
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
CYAN='\033[0;36m'
NC='\033[0m'
# Function to find device port
find_device_port() {
echo -e "${CYAN}Scanning for ADB device on $PHONE_IP...${NC}"
# Common ADB wireless ports range
for port in {5555..5585} {37000..42000}; do
# Try to connect with timeout
timeout 0.5 bash -c "echo >/dev/tcp/$PHONE_IP/$port" 2>/dev/null
if [ $? -eq 0 ]; then
echo -e "${YELLOW}Found open port: $port${NC}"
# Try ADB connection
adb connect "$PHONE_IP:$port" 2>/dev/null | grep -q "connected"
if [ $? -eq 0 ]; then
echo -e "${GREEN}✓ Successfully connected to $PHONE_IP:$port${NC}"
return 0
fi
fi
done
return 1
}
# Function to enable wireless debugging via USB first (if needed)
setup_wireless_adb() {
echo -e "${YELLOW}Setting up wireless ADB...${NC}"
echo "1. Connect your phone via USB cable"
echo "2. Make sure USB debugging is enabled"
echo "Press Enter when ready..."
read
# Check if device is connected via USB
if adb devices | grep -q "device$"; then
echo -e "${GREEN}✓ Device found via USB${NC}"
# Set TCP/IP mode on port 5555
adb tcpip 5555
sleep 2
# Get device IP
DEVICE_IP=$(adb shell ip route | grep wlan0 | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | head -1)
if [ ! -z "$DEVICE_IP" ]; then
echo -e "${GREEN}Device IP detected: $DEVICE_IP${NC}"
PHONE_IP=$DEVICE_IP
fi
echo "You can now disconnect the USB cable"
sleep 3
# Try to connect wirelessly
adb connect "$PHONE_IP:5555"
return 0
else
echo -e "${RED}No device found via USB${NC}"
return 1
fi
}
# Main auto-connect function
auto_connect() {
echo -e "${CYAN}=== ADB Auto-Connect ===${NC}"
# First, disconnect any existing connections
adb disconnect >/dev/null 2>&1
# Try to find and connect
for attempt in $(seq 1 $RETRY_ATTEMPTS); do
echo -e "${YELLOW}Connection attempt $attempt of $RETRY_ATTEMPTS${NC}"
if find_device_port; then
# Get the connected device
DEVICE_ID=$(adb devices | grep "$PHONE_IP" | awk '{print $1}')
echo -e "${GREEN}✓ Connected to device: $DEVICE_ID${NC}"
# Export for use in other scripts
export DEVICE_IP="$DEVICE_ID"
# Save to config file for other scripts
echo "DEVICE_IP=\"$DEVICE_ID\"" > ~/.adb_device_config
return 0
fi
if [ $attempt -lt $RETRY_ATTEMPTS ]; then
echo -e "${YELLOW}Retrying in $RETRY_DELAY seconds...${NC}"
sleep $RETRY_DELAY
fi
done
echo -e "${RED}Failed to auto-detect device${NC}"
echo "Would you like to set up wireless debugging? (y/n)"
read -r response
if [[ "$response" == "y" ]]; then
setup_wireless_adb
fi
return 1
}
# Quick connect function (tries last known device first)
quick_connect() {
if [ -f ~/.adb_device_config ]; then
source ~/.adb_device_config
echo -e "${CYAN}Trying last known device: $DEVICE_IP${NC}"
if adb connect "$DEVICE_IP" 2>/dev/null | grep -q "connected"; then
echo -e "${GREEN}✓ Quick connect successful!${NC}"
return 0
fi
fi
# Fall back to auto-discovery
auto_connect
}
# Main execution
main() {
clear
echo -e "${GREEN}==================================${NC}"
echo -e "${GREEN} ADB & Scrcpy Auto-Connect${NC}"
echo -e "${GREEN}==================================${NC}"
echo
# Try quick connect first, then full auto-discovery
if quick_connect; then
echo
echo -e "${CYAN}Launching scrcpy...${NC}"
# Ensure the connected device is stable and get its serial (explicit)
MAX_WAIT=20
WAITED=0
DEVICE_ID=""
while [ $WAITED -lt $MAX_WAIT ]; do
DEVICE_ID=$(adb devices | grep "$PHONE_IP" | awk '{print $1}' || true)
if [ -n "$DEVICE_ID" ]; then
# try a reconnect to make sure TCP session is ready
adb reconnect "$DEVICE_ID" >/dev/null 2>&1 || true
sleep 1
# verify still present
if adb devices | grep -q "^${DEVICE_ID}[[:space:]]*device$"; then
break
fi
fi
sleep 1
WAITED=$((WAITED+1))
done
if [ -z "$DEVICE_ID" ]; then
echo -e "${RED}✖ Device did not stabilize on ADB before launching scrcpy${NC}"
echo "Try: adb kill-server && adb start-server && adb connect $PHONE_IP:5555"
exit 1
fi
# Launch scrcpy with explicit device serial to avoid auto-detection races
# Allow extra args via SCRCPY_EXTRA_ARGS but strip any device-selector flags
SCRCPY_EXTRA_ARGS="${SCRCPY_EXTRA_ARGS:-"-w"}"
# remove -e, --select-tcpip and --tcpip=<addr> to avoid selector conflicts with -s
SANITIZED_ARGS=$(echo "$SCRCPY_EXTRA_ARGS" | sed -E 's/(^| )-e($| )/ /g; s/(^| )--select-tcpip($| )/ /g; s/(^| )--tcpip=[^ ]+($| )/ /g' | xargs)
scrcpy -s "$DEVICE_ID" $SANITIZED_ARGS &
SCRCPY_PID=$!
echo -e "${GREEN}✓ Scrcpy launched (PID: $SCRCPY_PID) for device $DEVICE_ID${NC}"
echo
echo "Device is ready for use!"
echo "Connection string: $(adb devices | grep $PHONE_IP | awk '{print $1}')"
# Option to update the SMS script
echo
echo "Update SMS script with new connection? (y/n)"
read -r update_sms
if [[ "$update_sms" == "y" ]]; then
update_sms_script
fi
else
echo -e "${RED}Failed to connect to device${NC}"
echo "Please check:"
echo " 1. Phone is connected to the same WiFi network"
echo " 2. Wireless debugging is enabled on the phone"
echo " 3. Phone IP is correct: $PHONE_IP"
exit 1
fi
}
# Function to update SMS script with new device connection
update_sms_script() {
SMS_SCRIPT="ui.sh" # Your SMS script name
if [ -f "$SMS_SCRIPT" ]; then
# Get current device
CURRENT_DEVICE=$(adb devices | grep "$PHONE_IP" | awk '{print $1}')
# Backup original
cp "$SMS_SCRIPT" "${SMS_SCRIPT}.backup"
# Update DEVICE_IP line in script
sed -i "s/^DEVICE_IP=.*/DEVICE_IP=\"$CURRENT_DEVICE\"/" "$SMS_SCRIPT"
echo -e "${GREEN}✓ Updated $SMS_SCRIPT with device: $CURRENT_DEVICE${NC}"
else
echo -e "${YELLOW}SMS script not found: $SMS_SCRIPT${NC}"
fi
}
# Run main function
main "$@"

View File

@@ -0,0 +1,148 @@
#!/bin/bash
#
# Enhanced Conversations Deployment Script
# Deploys all enhanced conversation components
#
set -e
echo "🚀 Deploying Enhanced Conversations System..."
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Check if we're in the project root
if [ ! -f "src/app.py" ]; then
echo -e "${RED}❌ Please run this script from the project root directory${NC}"
exit 1
fi
echo -e "${BLUE}📋 Step 1: Running database migration...${NC}"
python3 scripts/migrate_conversations_db.py
if [ $? -eq 0 ]; then
echo -e "${GREEN}✅ Database migration completed${NC}"
else
echo -e "${RED}❌ Database migration failed${NC}"
exit 1
fi
echo -e "${BLUE}📋 Step 2: Integrating with main application...${NC}"
python3 scripts/integrate_enhanced_conversations.py
if [ $? -eq 0 ]; then
echo -e "${GREEN}✅ Application integration completed${NC}"
else
echo -e "${RED}❌ Application integration failed${NC}"
exit 1
fi
echo -e "${BLUE}📋 Step 3: Installing Python dependencies...${NC}"
cd src
# Try different installation methods
if command -v pip3 >/dev/null 2>&1; then
echo "Installing with pip3..."
pip3 install -r requirements.txt --break-system-packages --user
if [ $? -ne 0 ]; then
echo -e "${YELLOW}⚠️ Pip installation failed, trying with virtual environment...${NC}"
# Try with virtual environment
if ! command -v python3-venv >/dev/null 2>&1; then
echo "Installing python3-venv..."
sudo apt-get update && sudo apt-get install -y python3-venv
fi
if [ ! -d "../venv" ]; then
echo "Creating virtual environment..."
python3 -m venv ../venv
fi
echo "Installing dependencies in virtual environment..."
../venv/bin/pip install -r requirements.txt
fi
else
echo -e "${YELLOW}⚠️ Pip not found, dependencies may need manual installation${NC}"
fi
echo -e "${BLUE}📋 Step 3: Skipping Python dependencies (using Docker Compose)...${NC}"
echo -e "${GREEN}✅ Dependencies handled by Docker${NC}"
echo -e "${BLUE}📋 Step 4: Updating Android Termux API server...${NC}"
PHONE_IP=${PHONE_IP:-"10.0.0.193"}
PHONE_PORT=${PHONE_PORT:-"8022"}
PHONE_USER=${PHONE_USER:-"android-dev"}
# Check if Android device is reachable
if ping -c 1 -W 1 $PHONE_IP > /dev/null 2>&1; then
echo -e "${GREEN}📱 Android device is reachable at $PHONE_IP${NC}"
# Deploy updated Termux API server
echo "Uploading enhanced Termux API server..."
scp -P $PHONE_PORT android/termux-sms-api-server.py $PHONE_USER@$PHONE_IP:~/projects/sms-campaign-manager/
if [ $? -eq 0 ]; then
echo -e "${GREEN}✅ Termux API server updated${NC}"
# Restart the server
echo "Restarting Termux API server..."
ssh -p $PHONE_PORT $PHONE_USER "pkill -f termux-sms-api-server.py; cd ~/projects/sms-campaign-manager && python termux-sms-api-server.py > /dev/null 2>&1 &"
sleep 2
# Test the server
if curl -s http://$PHONE_IP:5001/health > /dev/null; then
echo -e "${GREEN}✅ Termux API server is running${NC}"
else
echo -e "${YELLOW}⚠️ Termux API server may need manual restart${NC}"
fi
else
echo -e "${YELLOW}⚠️ Could not update Termux API server - manual update required${NC}"
fi
else
echo -e "${YELLOW}⚠️ Android device not reachable - manual Termux server update required${NC}"
echo "Manually copy android/termux-sms-api-server.py to your Android device"
fi
echo -e "${BLUE}📋 Step 5: Testing the system...${NC}"
# Test database
echo "Testing database schema..."
if sqlite3 data/campaign.db ".schema conversations" | grep -q "is_starred"; then
echo -e "${GREEN}✅ Database schema is correct${NC}"
else
echo -e "${RED}❌ Database schema appears incorrect${NC}"
fi
# Test API endpoints
echo "Testing local API endpoints..."
if curl -s http://localhost:5000/health > /dev/null 2>&1; then
echo -e "${GREEN}✅ Main application is responding${NC}"
else
echo -e "${YELLOW}⚠️ Main application is not running${NC}"
fi
echo -e "${GREEN}🎉 Enhanced Conversations Deployment Complete!${NC}"
echo ""
echo -e "${BLUE}📋 What's New:${NC}"
echo "• WhatsApp-style conversation interface"
echo "• Real-time message updates via WebSocket"
echo "• Bidirectional SMS sync with Android device"
echo "• Message status tracking (pending, sent, delivered, failed)"
echo "• Contact name resolution from phone"
echo "• Conversation starring and importance marking"
echo "• Scrollable message history with pagination"
echo "• Manual message sending from conversation view"
echo ""
echo -e "${BLUE}🚀 Next Steps:${NC}"
echo "1. Start the application: docker-compose up -d OR python src/app.py"
echo "2. Open http://localhost:5000 in your browser"
echo "3. Go to the Conversations tab"
echo "4. Test sending messages and real-time sync"
echo ""
echo -e "${BLUE}🔧 If you encounter issues:${NC}"
echo "• Check logs: docker-compose logs -f"
echo "• Verify Android device connectivity"
echo "• Test Termux API: curl http://$PHONE_IP:5001/health"
echo "• Check WebSocket connection in browser dev tools"

View File

@@ -0,0 +1,181 @@
#!/usr/bin/env python3
"""
Integration Script - Update main app.py for enhanced conversations
"""
import os
def update_main_app():
"""Add the necessary imports and initialization for enhanced conversations"""
app_py_path = './src/app.py'
# Read current app.py
with open(app_py_path, 'r') as f:
content = f.read()
# Add imports at the beginning
imports_to_add = """
# Enhanced conversations imports
from services.termux_sync_service import TermuxSyncService
from services.websocket_service import WebSocketService
from routes.conversations_enhanced import conversations_enhanced_bp, set_services
import threading
import asyncio
import logging
"""
# Add initialization code before app.run
init_code = """
# Initialize enhanced conversation services
try:
# Get phone IP from environment
phone_ip = os.getenv('PHONE_IP', '10.0.0.193')
termux_port = os.getenv('TERMUX_API_PORT', '5001')
termux_api_url = f"http://{phone_ip}:{termux_port}"
# Initialize sync service
sync_service = TermuxSyncService(termux_api_url)
# Initialize WebSocket service
websocket_service = WebSocketService(app, sync_service)
sync_service.set_websocket_service(websocket_service)
# Set service references for enhanced routes
set_services(sync_service, websocket_service)
# Register enhanced conversations blueprint
app.register_blueprint(conversations_enhanced_bp)
# Start background sync service
def run_sync_service():
try:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(sync_service.start_sync_loop())
except Exception as e:
logging.error(f"Sync service error: {e}")
sync_thread = threading.Thread(target=run_sync_service, daemon=True)
sync_thread.start()
logging.info("🚀 Enhanced conversation services initialized")
except Exception as e:
logging.error(f"Failed to initialize enhanced conversation services: {e}")
"""
# Check if already integrated
if 'termux_sync_service' in content:
print("✅ App already appears to be integrated with enhanced conversations")
return
# Add imports after existing imports
import_insertion_point = content.find('from models.conversation import Conversation')
if import_insertion_point != -1:
content = content[:import_insertion_point] + imports_to_add + '\n' + content[import_insertion_point:]
# Add initialization before app.run
app_run_point = content.find('app.run(')
if app_run_point != -1:
content = content[:app_run_point] + init_code + '\n' + content[app_run_point:]
# Write updated content
with open(app_py_path, 'w') as f:
f.write(content)
print("✅ Successfully updated app.py with enhanced conversation support")
def update_dashboard_template():
"""Update dashboard template to include enhanced conversations"""
template_path = './src/templates/dashboard.html'
enhanced_template_path = './src/templates/conversations_enhanced.html'
# Read the enhanced template
with open(enhanced_template_path, 'r') as f:
enhanced_content = f.read()
# Read the main dashboard
with open(template_path, 'r') as f:
dashboard_content = f.read()
# Find and replace the conversations tab
start_marker = '<!-- Conversations Tab -->'
end_marker = '<!-- End Conversations Tab -->'
if start_marker not in dashboard_content:
# Look for the existing conversations tab
start_marker = '<div x-show="activeTab === \'conversations\'"'
end_marker = '</div>\n </div>'
start_pos = dashboard_content.find(start_marker)
if start_pos == -1:
print("❌ Could not find conversations tab in dashboard.html")
return
# Find the end of the conversations div
div_count = 0
end_pos = start_pos
in_div = False
for i, char in enumerate(dashboard_content[start_pos:], start_pos):
if char == '<':
if dashboard_content[i:i+4] == '<div':
div_count += 1
in_div = True
elif dashboard_content[i:i+6] == '</div>':
div_count -= 1
if div_count == 0 and in_div:
end_pos = i + 6
break
else:
start_pos = dashboard_content.find(start_marker)
end_pos = dashboard_content.find(end_marker, start_pos) + len(end_marker)
# Replace with enhanced template
if start_pos != -1 and end_pos != -1:
new_content = (dashboard_content[:start_pos] +
'<!-- Enhanced Conversations Tab -->\n' +
enhanced_content +
'\n<!-- End Enhanced Conversations Tab -->\n' +
dashboard_content[end_pos:])
# Add enhanced JavaScript import
if 'conversations_enhanced.js' not in new_content:
js_insertion_point = new_content.find('<script src="/static/js/conversations.js"></script>')
if js_insertion_point != -1:
new_content = (new_content[:js_insertion_point] +
'<script src="/static/js/conversations_enhanced.js"></script>\n' +
new_content[js_insertion_point:])
# Add Socket.IO CDN
if 'socket.io' not in new_content:
head_end = new_content.find('</head>')
if head_end != -1:
new_content = (new_content[:head_end] +
' <script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>\n' +
new_content[head_end:])
with open(template_path, 'w') as f:
f.write(new_content)
print("✅ Successfully updated dashboard.html with enhanced conversations")
else:
print("❌ Could not locate conversations section in dashboard.html")
if __name__ == "__main__":
print("🔄 Integrating enhanced conversations...")
# Update main application
update_main_app()
# Update dashboard template
update_dashboard_template()
print("✅ Integration complete!")
print("\n📝 Next steps:")
print("1. Run database migration: python scripts/migrate_conversations_db.py")
print("2. Update Android Termux API server")
print("3. Restart the application")
print("4. Test the enhanced conversations interface")

View File

@@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""
Database Migration Script - Enhance conversations schema for WhatsApp-style features
"""
import sqlite3
import logging
from datetime import datetime
logger = logging.getLogger(__name__)
def migrate_conversations_schema(db_path='./data/campaign.db'):
"""Add new fields to support enhanced conversations"""
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
migrations = [
# Add new fields to conversations table
("ALTER TABLE conversations ADD COLUMN contact_name TEXT DEFAULT ''", "contact_name field"),
("ALTER TABLE conversations ADD COLUMN is_starred BOOLEAN DEFAULT FALSE", "is_starred field"),
("ALTER TABLE conversations ADD COLUMN last_sync_timestamp INTEGER DEFAULT 0", "last_sync_timestamp field"),
("ALTER TABLE conversations ADD COLUMN total_message_count INTEGER DEFAULT 0", "total_message_count field"),
# Add new fields to messages table for status tracking
("ALTER TABLE messages ADD COLUMN status TEXT DEFAULT 'pending' CHECK(status IN ('pending', 'sent', 'delivered', 'failed'))", "message status field"),
("ALTER TABLE messages ADD COLUMN external_message_id TEXT", "external_message_id field"),
("ALTER TABLE messages ADD COLUMN sync_status TEXT DEFAULT 'local' CHECK(sync_status IN ('local', 'synced', 'pending_sync'))", "sync_status field"),
("ALTER TABLE messages ADD COLUMN direction TEXT DEFAULT 'outbound' CHECK(direction IN ('inbound', 'outbound'))", "direction field"),
("ALTER TABLE messages ADD COLUMN timestamp INTEGER", "timestamp field"),
# Create indexes for performance
("CREATE INDEX IF NOT EXISTS idx_messages_timestamp ON messages(timestamp)", "timestamp index"),
("CREATE INDEX IF NOT EXISTS idx_conversations_starred ON conversations(is_starred)", "starred conversations index"),
("CREATE INDEX IF NOT EXISTS idx_messages_status ON messages(status)", "message status index"),
("CREATE INDEX IF NOT EXISTS idx_messages_direction ON messages(direction)", "message direction index"),
("CREATE INDEX IF NOT EXISTS idx_messages_sync_status ON messages(sync_status)", "sync status index"),
]
for migration_sql, description in migrations:
try:
cursor.execute(migration_sql)
logger.info(f"✅ Applied: {description}")
except sqlite3.OperationalError as e:
if "duplicate column name" in str(e) or "already exists" in str(e):
logger.info(f"⏭️ Skipped: {description} (already exists)")
else:
logger.error(f"❌ Failed: {description} - {e}")
raise
# Update existing messages to have proper timestamp and direction
cursor.execute("""
UPDATE messages
SET timestamp = CAST(strftime('%s', sent_at) AS INTEGER)
WHERE timestamp IS NULL AND sent_at IS NOT NULL
""")
cursor.execute("""
UPDATE messages
SET timestamp = CAST(strftime('%s', 'now') AS INTEGER)
WHERE timestamp IS NULL
""")
cursor.execute("""
UPDATE messages
SET direction = CASE
WHEN response_text IS NOT NULL AND response_text != '' THEN 'inbound'
ELSE 'outbound'
END
WHERE direction IS NULL
""")
conn.commit()
conn.close()
logger.info("🚀 Database migration completed successfully!")
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
migrate_conversations_schema()

View File

@@ -0,0 +1,278 @@
#!/usr/bin/env python3
"""
Enhanced Conversations Test Suite
Test all components of the enhanced conversation system
"""
import requests
import sqlite3
import json
import time
import logging
from typing import Dict, List, Optional
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class EnhancedConversationTester:
"""Test suite for enhanced conversation features"""
def __init__(self, base_url='http://localhost:5000', phone_ip='10.0.0.193'):
self.base_url = base_url.rstrip('/')
self.phone_ip = phone_ip
self.phone_api_url = f'http://{phone_ip}:5001'
self.db_path = './data/campaign.db'
self.test_phone = '5551234567' # Test phone number
def run_all_tests(self):
"""Run comprehensive test suite"""
logger.info("🧪 Starting Enhanced Conversations Test Suite")
tests = [
("Database Schema", self.test_database_schema),
("API Endpoints", self.test_api_endpoints),
("WebSocket Connection", self.test_websocket_connection),
("Android API Connection", self.test_android_api_connection),
("Message Flow", self.test_message_flow),
("Conversation Management", self.test_conversation_management),
("Real-time Updates", self.test_realtime_updates)
]
passed = 0
failed = 0
for test_name, test_func in tests:
try:
logger.info(f"🔍 Testing: {test_name}")
test_func()
logger.info(f"{test_name}: PASSED")
passed += 1
except Exception as e:
logger.error(f"{test_name}: FAILED - {e}")
failed += 1
# Summary
total = passed + failed
success_rate = (passed / total * 100) if total > 0 else 0
logger.info(f"\n📊 Test Results: {passed}/{total} passed ({success_rate:.1f}%)")
if failed > 0:
logger.warning(f"⚠️ {failed} tests failed - check logs for details")
else:
logger.info("🎉 All tests passed!")
return failed == 0
def test_database_schema(self):
"""Test database schema has required fields"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# Check conversations table
cursor.execute("PRAGMA table_info(conversations)")
columns = [row[1] for row in cursor.fetchall()]
required_columns = ['is_starred', 'contact_name', 'last_sync_timestamp']
for col in required_columns:
if col not in columns:
raise Exception(f"Missing column '{col}' in conversations table")
# Check messages table
cursor.execute("PRAGMA table_info(messages)")
columns = [row[1] for row in cursor.fetchall()]
required_columns = ['status', 'direction', 'timestamp', 'sync_status']
for col in required_columns:
if col not in columns:
raise Exception(f"Missing column '{col}' in messages table")
conn.close()
def test_api_endpoints(self):
"""Test enhanced API endpoints"""
endpoints = [
'/api/conversations/enhanced',
'/api/conversations/stats',
]
for endpoint in endpoints:
response = requests.get(f"{self.base_url}{endpoint}")
if response.status_code != 200:
raise Exception(f"Endpoint {endpoint} returned {response.status_code}")
data = response.json()
if not data.get('success'):
raise Exception(f"Endpoint {endpoint} returned error: {data.get('error')}")
def test_websocket_connection(self):
"""Test WebSocket server is available"""
try:
# Test if Socket.IO endpoint responds
response = requests.get(f"{self.base_url}/socket.io/?EIO=4&transport=polling")
if response.status_code not in [200, 400]: # 400 is expected for wrong transport
raise Exception(f"WebSocket server not responding: {response.status_code}")
except requests.RequestException as e:
raise Exception(f"WebSocket server connection failed: {e}")
def test_android_api_connection(self):
"""Test Android Termux API connection"""
try:
response = requests.get(f"{self.phone_api_url}/health", timeout=5)
if response.status_code != 200:
raise Exception(f"Android API health check failed: {response.status_code}")
data = response.json()
if not data.get('success'):
raise Exception(f"Android API health check error: {data.get('error')}")
# Test enhanced endpoints
response = requests.get(f"{self.phone_api_url}/api/sms/history?limit=1", timeout=5)
if response.status_code != 200:
logger.warning("SMS history endpoint may not be available")
except requests.RequestException as e:
raise Exception(f"Android API connection failed: {e}")
def test_message_flow(self):
"""Test message creation and management"""
# Create a test conversation
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
conversation_id = f"test_conv_{int(time.time())}"
# Insert test conversation
cursor.execute("""
INSERT INTO conversations (id, phone, contact_name, status, created_at)
VALUES (?, ?, 'Test Contact', 'active', datetime('now'))
""", (conversation_id, self.test_phone))
# Insert test message
cursor.execute("""
INSERT INTO messages (conversation_id, phone, message, direction, status, timestamp)
VALUES (?, ?, 'Test message', 'outbound', 'pending', ?)
""", (conversation_id, self.test_phone, int(time.time())))
conn.commit()
# Test API retrieval
response = requests.get(f"{self.base_url}/api/conversations/{conversation_id}/messages")
if response.status_code != 200:
conn.close()
raise Exception(f"Failed to retrieve messages: {response.status_code}")
data = response.json()
if not data.get('success') or not data.get('messages'):
conn.close()
raise Exception("No messages returned from API")
# Clean up
cursor.execute("DELETE FROM conversations WHERE id = ?", (conversation_id,))
cursor.execute("DELETE FROM messages WHERE conversation_id = ?", (conversation_id,))
conn.commit()
conn.close()
def test_conversation_management(self):
"""Test conversation CRUD operations"""
# Create test conversation
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
conversation_id = f"test_conv_mgmt_{int(time.time())}"
cursor.execute("""
INSERT INTO conversations (id, phone, contact_name, status, is_starred, created_at)
VALUES (?, ?, 'Test Management', 'active', 0, datetime('now'))
""", (conversation_id, self.test_phone))
conn.commit()
try:
# Test starring
response = requests.put(f"{self.base_url}/api/conversations/{conversation_id}/star")
if response.status_code != 200:
raise Exception(f"Failed to toggle star: {response.status_code}")
data = response.json()
if not data.get('success'):
raise Exception(f"Star toggle error: {data.get('error')}")
# Verify starring worked
cursor.execute("SELECT is_starred FROM conversations WHERE id = ?", (conversation_id,))
row = cursor.fetchone()
if not row or not row[0]:
raise Exception("Conversation was not starred")
# Test mark as read
response = requests.put(f"{self.base_url}/api/conversations/{conversation_id}/mark-read")
if response.status_code != 200:
raise Exception(f"Failed to mark as read: {response.status_code}")
finally:
# Clean up
cursor.execute("DELETE FROM conversations WHERE id = ?", (conversation_id,))
cursor.execute("DELETE FROM messages WHERE conversation_id = ?", (conversation_id,))
conn.commit()
conn.close()
def test_realtime_updates(self):
"""Test real-time update mechanisms"""
# This is a basic test - full WebSocket testing would require a client
try:
# Test sync endpoints
response = requests.post(f"{self.base_url}/api/conversations/sync-all")
if response.status_code not in [200, 503]: # 503 if sync service unavailable
raise Exception(f"Sync all endpoint failed: {response.status_code}")
if response.status_code == 200:
data = response.json()
if not data.get('success'):
raise Exception(f"Sync all error: {data.get('error')}")
except requests.RequestException as e:
raise Exception(f"Real-time update test failed: {e}")
def test_data_integrity(self):
"""Test data integrity and constraints"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# Test that we can't create invalid status values
try:
cursor.execute("""
INSERT INTO messages (phone, message, status, direction)
VALUES (?, 'test', 'invalid_status', 'outbound')
""", (self.test_phone,))
conn.commit()
raise Exception("Database allowed invalid status value")
except sqlite3.IntegrityError:
# This is expected
pass
conn.close()
def main():
"""Run the test suite"""
import argparse
parser = argparse.ArgumentParser(description='Test Enhanced Conversations System')
parser.add_argument('--base-url', default='http://localhost:5000',
help='Base URL for the main application')
parser.add_argument('--phone-ip', default='10.0.0.193',
help='IP address of Android device')
parser.add_argument('--verbose', action='store_true',
help='Enable verbose logging')
args = parser.parse_args()
if args.verbose:
logging.getLogger().setLevel(logging.DEBUG)
tester = EnhancedConversationTester(args.base_url, args.phone_ip)
success = tester.run_all_tests()
return 0 if success else 1
if __name__ == "__main__":
exit(main())

337
scripts/ui.sh Executable file
View File

@@ -0,0 +1,337 @@
#!/bin/bash
# filepath: /mnt/storagessd1tb/ABD Texting Testing/ui_bulk_sender_working.sh
# Working bulk SMS sender with correct send button coordinates and variable substitution
DEVICE_IP="10.0.0.193:5555"
CSV_FILE="contacts_cleaned.csv"
SEND_X=1300
SEND_Y=2900
DELAY_SECONDS=3
LOG_FILE="sms_log_$(date +%Y%m%d_%H%M%S).txt"
# Colors
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
CYAN='\033[0;36m'
NC='\033[0m'
echo -e "${GREEN}=== Bulk SMS Sender with Variables ===${NC}"
echo "Using send button at: ($SEND_X, $SEND_Y)"
echo ""
# Initialize log
echo "SMS Send Log - $(date)" > "$LOG_FILE"
echo "================================" >> "$LOG_FILE"
# Function to set/customize message template
set_message_template() {
echo -e "${CYAN}=== Message Template Setup ===${NC}"
echo "You can use variables in your message:"
echo " {name} - Person's name from CSV"
echo " {phone} - Phone number"
echo " {date} - Current date"
echo " {time} - Current time"
echo " {custom1}, {custom2}, etc - Any additional CSV columns"
echo ""
echo "Examples:"
echo " 'Hi {name}, this is a test message!'"
echo " 'Hello {name}, reminder for {date} at {time}'"
echo ""
echo "Choose message option:"
echo "1. Use message from CSV file"
echo "2. Use same custom message for everyone"
echo "3. Use custom template with variables"
read -r choice
case "$choice" in
1)
MESSAGE_MODE="csv"
echo -e "${GREEN}Using messages from CSV file${NC}"
;;
2)
echo "Enter your message:"
read -r CUSTOM_MESSAGE
MESSAGE_MODE="custom"
echo -e "${GREEN}Using custom message: $CUSTOM_MESSAGE${NC}"
;;
3)
echo "Enter your message template (use {variable} for substitution):"
read -r MESSAGE_TEMPLATE
MESSAGE_MODE="template"
echo -e "${GREEN}Using template: $MESSAGE_TEMPLATE${NC}"
;;
*)
MESSAGE_MODE="csv"
echo "Default: Using CSV messages"
;;
esac
echo ""
}
# Function to substitute variables in message
substitute_variables() {
local template="$1"
local phone="$2"
local csv_message="$3"
local name="$4"
shift 4
local custom_fields=("$@")
# Start with the template
local final_message="$template"
# Replace standard variables
final_message="${final_message//\{phone\}/$phone}"
final_message="${final_message//\{name\}/$name}"
final_message="${final_message//\{date\}/$(date +%Y-%m-%d)}"
final_message="${final_message//\{time\}/$(date +%H:%M)}"
final_message="${final_message//\{message\}/$csv_message}"
# Replace custom fields (custom1, custom2, etc)
local i=1
for field in "${custom_fields[@]}"; do
final_message="${final_message//\{custom$i\}/$field}"
((i++))
done
echo "$final_message"
}
# Send function with correct coordinates
send_sms() {
local phone="$1"
local message="$2"
local count="$3"
echo -e "${BLUE}[$count] Sending to: $phone${NC}"
echo " Message: $message"
# Log to file
echo "[$(date +%H:%M:%S)] Sending to $phone: $message" >> "$LOG_FILE"
# Clear state
adb -s "$DEVICE_IP" shell input keyevent KEYCODE_HOME > /dev/null 2>&1
sleep 1
# Open SMS
adb -s "$DEVICE_IP" shell "am start \
-a android.intent.action.SENDTO \
-d 'sms:$phone' \
--es sms_body '$message' \
--activity-clear-top" > /dev/null 2>&1
# Wait for load
sleep 3
# Tap send button at correct coordinates
adb -s "$DEVICE_IP" shell input tap $SEND_X $SEND_Y > /dev/null 2>&1
echo -e "${GREEN} ✓ Sent${NC}"
echo " ✓ Sent successfully" >> "$LOG_FILE"
# Return home
sleep 1
adb -s "$DEVICE_IP" shell input keyevent KEYCODE_HOME > /dev/null 2>&1
}
# Check CSV file exists and show contents
if [[ ! -f "$CSV_FILE" ]]; then
echo -e "${RED}ERROR: CSV file not found: $CSV_FILE${NC}"
exit 1
fi
echo "CSV Contents Preview:"
head -5 "$CSV_FILE" | column -t -s','
echo "..."
echo "Total lines in CSV: $(wc -l < "$CSV_FILE")"
echo ""
# Fix line endings in CSV file
dos2unix "$CSV_FILE" 2>/dev/null || sed -i 's/\r$//' "$CSV_FILE"
# Detect CSV columns and their positions
IFS=',' read -r -a HEADERS < "$CSV_FILE"
echo "Detected CSV columns: ${HEADERS[*]}"
# Find column positions
NAME_COL=-1
PHONE_COL=-1
MESSAGE_COL=-1
for i in "${!HEADERS[@]}"; do
header=$(echo "${HEADERS[$i]}" | tr -d '"' | tr -d ' ' | tr '[:upper:]' '[:lower:]')
case "$header" in
"name"|"firstname"|"contact"|"person")
NAME_COL=$i
;;
"phone"|"phonenumber"|"number"|"tel"|"telephone"|"mobile"|"cell")
PHONE_COL=$i
;;
"message"|"msg"|"text"|"content"|"body")
MESSAGE_COL=$i
;;
esac
done
echo "Column positions - Name: $NAME_COL, Phone: $PHONE_COL, Message: $MESSAGE_COL"
# Validate required columns
if [[ $PHONE_COL -eq -1 ]]; then
echo -e "${RED}ERROR: No phone column found in CSV. Expected columns: 'phone', 'phonenumber', or 'number'${NC}"
exit 1
fi
echo ""
# Test device connection
echo "Checking device connection..."
if ! adb devices | grep -q "$DEVICE_IP"; then
echo -e "${RED}ERROR: Device not connected${NC}"
exit 1
fi
echo -e "${GREEN}✓ Device connected${NC}"
echo ""
# Set up message template
set_message_template
# Confirm before starting
echo -e "${YELLOW}Ready to send messages to all numbers in CSV?${NC}"
echo "Press Enter to continue or Ctrl+C to cancel..."
read -r
echo ""
echo -e "${YELLOW}Starting bulk SMS send...${NC}"
echo ""
# Process CSV
count=0
skipped=0
sent=0
# Read CSV into array
mapfile -t lines < "$CSV_FILE"
echo "Total lines read: ${#lines[@]}"
echo ""
# Process each line
for i in "${!lines[@]}"; do
# Skip header
if [[ $i -eq 0 ]]; then
continue
fi
line="${lines[$i]}"
# Parse CSV line into array
IFS=',' read -r -a fields <<< "$line"
# Extract fields by column position
phone=""
csv_message=""
name=""
# Get phone (required)
if [[ $PHONE_COL -ge 0 && $PHONE_COL -lt ${#fields[@]} ]]; then
phone="${fields[$PHONE_COL]}"
fi
# Get message (optional)
if [[ $MESSAGE_COL -ge 0 && $MESSAGE_COL -lt ${#fields[@]} ]]; then
csv_message="${fields[$MESSAGE_COL]}"
fi
# Get name (optional)
if [[ $NAME_COL -ge 0 && $NAME_COL -lt ${#fields[@]} ]]; then
name="${fields[$NAME_COL]}"
fi
# Get any additional custom fields (skip the main columns we already extracted)
custom_fields=()
for ((j=0; j<${#fields[@]}; j++)); do
if [[ $j -ne $PHONE_COL && $j -ne $MESSAGE_COL && $j -ne $NAME_COL ]]; then
custom_fields+=("${fields[$j]}")
fi
done
# Debug output
echo "DEBUG: Processing line $i - phone='$phone', csv_message='$csv_message', name='$name'"
# Skip empty lines
if [[ -z "$phone" ]]; then
echo " Skipping empty line"
((skipped++))
continue
fi
# Clean inputs
phone=$(echo "$phone" | tr -d '"' | tr -d ' ' | tr -d '\r' | sed 's/[^0-9+]//g')
csv_message=$(echo "$csv_message" | tr -d '"' | tr -d '\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
name=$(echo "$name" | tr -d '"' | tr -d '\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
# Verify phone number
if [[ -z "$phone" ]]; then
echo " Skipping - phone empty after cleaning"
((skipped++))
continue
fi
((count++))
# Determine final message based on mode
case "$MESSAGE_MODE" in
"csv")
final_message="$csv_message"
;;
"custom")
final_message="$CUSTOM_MESSAGE"
;;
"template")
final_message=$(substitute_variables "$MESSAGE_TEMPLATE" "$phone" "$csv_message" "$name" "${custom_fields[@]}")
;;
*)
final_message="$csv_message"
;;
esac
# Send the SMS
send_sms "$phone" "$final_message" "$count"
((sent++))
# Delay between messages
if [[ $count -lt $((${#lines[@]} - 1)) ]]; then
echo " Waiting ${DELAY_SECONDS} seconds before next message..."
sleep $DELAY_SECONDS
fi
echo ""
done
# Summary
echo -e "${GREEN}=== Summary ===${NC}"
echo "Lines processed: $((count + skipped))"
echo "Messages sent: $sent"
echo "Skipped: $skipped"
echo ""
# Log summary
echo "" >> "$LOG_FILE"
echo "================================" >> "$LOG_FILE"
echo "Summary:" >> "$LOG_FILE"
echo "Total sent: $sent" >> "$LOG_FILE"
echo "Completed: $(date)" >> "$LOG_FILE"
echo "Log saved to: $LOG_FILE"
echo ""
# Show what was actually sent
echo -e "${YELLOW}Messages sent:${NC}"
grep "Sending to" "$LOG_FILE" | grep -v "Summary"
echo ""
echo -e "${GREEN}✓ Complete! All messages have been sent.${NC}"
echo "Check your Messages app to verify all messages appear in sent folder."