diff --git a/routes/helpers.py b/routes/helpers.py deleted file mode 100644 index 1130537..0000000 --- a/routes/helpers.py +++ /dev/null @@ -1,99 +0,0 @@ -import random, time -import phonenumbers -import hashlib -import hmac -import json -import logging -from datetime import datetime -from config import YEASTAR_WEBHOOK_URL, YEASTAR_SECRET - -def generate_signature(body: str) -> str: - signature = hmac.new(YEASTAR_SECRET.encode(), body.encode(), hashlib.sha256).hexdigest() - return f"sha256={signature}" - -def to_e164(number): - try: - parsed = phonenumbers.parse(number, "US") - return phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.E164) - except Exception as e: - logging.warning(f"Failed to normalize phone number {number}: {e}") - return number - -def create_yeastar_payload(data): - now_iso = datetime.now().astimezone().isoformat() - message_id = str(int(datetime.utcnow().timestamp())) - payload = { - "data": { - "event_type": "message.received", - "payload": { - "id": message_id, - "from": { - "phone_number": data["from"] - }, - "to": [ - {"phone_number": num} for num in data["to"] - ], - "text": data["text"], - "record_type": "message", - "received_at": now_iso - } - } - } - - media_urls = data.get("media_urls", []) - if media_urls: - payload["data"]["payload"].pop("text", None) - payload["data"]["payload"]["media"] = [{"url": url} for url in media_urls] - - return payload - -def generate_message_id(): - return f"{random.randint(100000, 999999)}{int(time.time())}" - -def parse_bearer_token(auth_header): - """ - Expect: Authorization: Bearer username|||password - """ - if not auth_header.startswith("Bearer "): - return None, None - token = auth_header.split("Bearer ")[1].strip() - parts = token.split("|||") - if len(parts) == 2: - return parts[0], parts[1] - return None, None - -def build_soap_envelope( - username, password, from_number, to_number, - message_text, media_urls=None -): - media_urls = media_urls or [] - is_mms = len(media_urls) > 0 - soap_method = "sendMMS" if is_mms else "sendSMS" - - envelope = f""" - - - - - - {username} - {password} - {from_number} - {to_number} - {message_text} -""" - - # Add media tags if MMS, max 3 allowed - if is_mms: - for i, url in enumerate(media_urls[:3]): - envelope += f" {url}\n" - - envelope += f""" - - -""" - - return envelope.strip() - diff --git a/routes/voipms.py b/routes/voipms.py index a382917..83b1943 100644 --- a/routes/voipms.py +++ b/routes/voipms.py @@ -1,10 +1,11 @@ # routes/voipms.py import json +import hmac, hashlib +import phonenumbers from datetime import datetime from flask import Blueprint, request, jsonify from config import YEASTAR_WEBHOOK_URL, YEASTAR_SECRET, ENDPOINT_OBSCURITY -from routes.helpers import to_e164, create_yeastar_payload, generate_signature import requests import logging import uuid @@ -84,3 +85,43 @@ def voipms_inbound(): except Exception as e: logging.error(f"[{request_id}] Error in voipms_inbound: {e}", exc_info=True) return jsonify({"error": "internal server error"}), 500 + +def generate_signature(body: str) -> str: + signature = hmac.new(YEASTAR_SECRET.encode(), body.encode(), hashlib.sha256).hexdigest() + return f"sha256={signature}" + +def to_e164(number): + try: + parsed = phonenumbers.parse(number, "US") + return phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.E164) + except Exception as e: + logging.warning(f"Failed to normalize phone number {number}: {e}") + return number + +def create_yeastar_payload(data): + now_iso = datetime.now().astimezone().isoformat() + message_id = str(int(datetime.utcnow().timestamp())) + payload = { + "data": { + "event_type": "message.received", + "payload": { + "id": message_id, + "from": { + "phone_number": data["from"] + }, + "to": [ + {"phone_number": num} for num in data["to"] + ], + "text": data["text"], + "record_type": "message", + "received_at": now_iso + } + } + } + + media_urls = data.get("media_urls", []) + if media_urls: + payload["data"]["payload"].pop("text", None) + payload["data"]["payload"]["media"] = [{"url": url} for url in media_urls] + + return payload diff --git a/routes/yeastar.py b/routes/yeastar.py index c718d9d..4d87555 100644 --- a/routes/yeastar.py +++ b/routes/yeastar.py @@ -1,10 +1,10 @@ from flask import Blueprint, request, jsonify +import random, time import requests import xml.etree.ElementTree as ET from datetime import datetime from config import VOIPMS_ENDPOINT, ENDPOINT_OBSCURITY -from routes.helpers import generate_message_id, parse_bearer_token, build_soap_envelope bp = Blueprint('yeastar', __name__) @@ -118,4 +118,54 @@ def handle_yeastar_outbound(): "title": "Message Failed", "detail": f"VOIP.ms returned status: {status or 'unknown'}" }] - }), 400 \ No newline at end of file + }), 400 + +def generate_message_id(): + return f"{random.randint(100000, 999999)}{int(time.time())}" + +def parse_bearer_token(auth_header): + """ + Expect: Authorization: Bearer username|||password + """ + if not auth_header.startswith("Bearer "): + return None, None + token = auth_header.split("Bearer ")[1].strip() + parts = token.split("|||") + if len(parts) == 2: + return parts[0], parts[1] + return None, None + +def build_soap_envelope( + username, password, from_number, to_number, + message_text, media_urls=None +): + media_urls = media_urls or [] + is_mms = len(media_urls) > 0 + soap_method = "sendMMS" if is_mms else "sendSMS" + + envelope = f""" + + + + + + {username} + {password} + {from_number} + {to_number} + {message_text} +""" + + # Add media tags if MMS, max 3 allowed + if is_mms: + for i, url in enumerate(media_urls[:3]): + envelope += f" {url}\n" + + envelope += f""" + + +""" + + return envelope.strip() \ No newline at end of file diff --git a/webhook-proxy.pyproj b/webhook-proxy.pyproj index 6212a17..14ad029 100644 --- a/webhook-proxy.pyproj +++ b/webhook-proxy.pyproj @@ -27,7 +27,6 @@ -