# routes/threecx.py from flask import Blueprint, request, Response import requests from datetime import datetime import logging from config import VOIPMS_3CX_ENDPOINT, ENDPOINT_OBSCURITY bp = Blueprint("threecx", __name__) @bp.route(f"/{ENDPOINT_OBSCURITY}/threecx-outbound", methods=["POST"]) @bp.route(f"/{ENDPOINT_OBSCURITY}/threecx-outbound/verbose", methods=["POST"]) def handle_threecx_outbound(): request_id = datetime.utcnow().strftime("%Y%m%d%H%M%S%f") verbose = request.path.endswith("/verbose") ip = request.headers.get("X-Forwarded-For", request.remote_addr) if verbose: print(f"[{request_id}] ===== 3CX Outbound Verbose =====") print(f"[{request_id}] IP: {ip}") print(f"[{request_id}] Headers: {dict(request.headers)}") try: raw_body = request.get_data(as_text=True) json_data = request.get_json(force=True) if verbose: print(f"[{request_id}] Raw Body: {raw_body}") print(f"[{request_id}] Parsed JSON: {json_data}") from_number = json_data.get("from") to_number = json_data.get("to") text = json_data.get("text", "") media_urls = json_data.get("media_urls", []) is_mms = bool(media_urls) or len(text) > 160 if is_mms and not media_urls: json_data["media_urls"] = [""] if verbose: print(f"[{request_id}] Upgraded to MMS due to long text; inserted dummy media URL") resp = requests.post( VOIPMS_3CX_ENDPOINT, json=json_data, headers={ 'Content-Type': 'application/json', 'Authorization': request.headers.get('Authorization', '') } ) if verbose: print(f"[{request_id}] VOIP.ms 3CX Response Code: {resp.status_code}") print(f"[{request_id}] VOIP.ms 3CX Response Body: {resp.text}") # Non-verbose single-line log for every request status_text = "PASS" if resp.ok else "FAIL" print(f"[{datetime.now().isoformat()}] {status_text} IP:{ip} From 3CX | {'MMS' if is_mms else 'SMS'} | From:{from_number} -> To:{to_number} | Images:{len(media_urls)}") return Response( resp.content, status=resp.status_code, content_type=resp.headers.get('Content-Type', 'application/json') ) except Exception as e: logging.error(f"[{request_id}] Error in 3CX outbound: {e}", exc_info=True) return Response( '{"errors":[{"code":"10002","title":"Proxy Error","detail":"%s"}]}' % str(e), status=500, content_type='application/json' )