Initial commit
This commit is contained in:
223
scripts/auto.sh
Executable file
223
scripts/auto.sh
Executable 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 "$@"
|
||||
148
scripts/deploy_enhanced_conversations.sh
Executable file
148
scripts/deploy_enhanced_conversations.sh
Executable 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"
|
||||
181
scripts/integrate_enhanced_conversations.py
Executable file
181
scripts/integrate_enhanced_conversations.py
Executable 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")
|
||||
78
scripts/migrate_conversations_db.py
Executable file
78
scripts/migrate_conversations_db.py
Executable 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()
|
||||
278
scripts/test_enhanced_conversations.py
Normal file
278
scripts/test_enhanced_conversations.py
Normal 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
337
scripts/ui.sh
Executable 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."
|
||||
Reference in New Issue
Block a user