Removed helpers to make routes self contained.

This commit is contained in:
edschuy95 2025-07-15 23:49:54 -04:00
parent 01b2299472
commit e7786593e1
4 changed files with 94 additions and 103 deletions

View file

@ -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"""
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:wsd="https://voip.ms/api/wsdl"
xmlns:xsd1="https://voip.ms/api/schema">
<soapenv:Header/>
<soapenv:Body>
<wsd:{soap_method}>
<wsd:params>
<xsd1:api_username>{username}</xsd1:api_username>
<xsd1:api_password>{password}</xsd1:api_password>
<xsd1:did>{from_number}</xsd1:did>
<xsd1:dst>{to_number}</xsd1:dst>
<xsd1:message>{message_text}</xsd1:message>
"""
# Add media tags if MMS, max 3 allowed
if is_mms:
for i, url in enumerate(media_urls[:3]):
envelope += f" <xsd1:media{i+1}>{url}</xsd1:media{i+1}>\n"
envelope += f""" </wsd:params>
</wsd:{soap_method}>
</soapenv:Body>
</soapenv:Envelope>"""
return envelope.strip()

View file

@ -1,10 +1,11 @@
# routes/voipms.py # routes/voipms.py
import json import json
import hmac, hashlib
import phonenumbers
from datetime import datetime from datetime import datetime
from flask import Blueprint, request, jsonify from flask import Blueprint, request, jsonify
from config import YEASTAR_WEBHOOK_URL, YEASTAR_SECRET, ENDPOINT_OBSCURITY from config import YEASTAR_WEBHOOK_URL, YEASTAR_SECRET, ENDPOINT_OBSCURITY
from routes.helpers import to_e164, create_yeastar_payload, generate_signature
import requests import requests
import logging import logging
import uuid import uuid
@ -84,3 +85,43 @@ def voipms_inbound():
except Exception as e: except Exception as e:
logging.error(f"[{request_id}] Error in voipms_inbound: {e}", exc_info=True) logging.error(f"[{request_id}] Error in voipms_inbound: {e}", exc_info=True)
return jsonify({"error": "internal server error"}), 500 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

View file

@ -1,10 +1,10 @@
from flask import Blueprint, request, jsonify from flask import Blueprint, request, jsonify
import random, time
import requests import requests
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from datetime import datetime from datetime import datetime
from config import VOIPMS_ENDPOINT, ENDPOINT_OBSCURITY from config import VOIPMS_ENDPOINT, ENDPOINT_OBSCURITY
from routes.helpers import generate_message_id, parse_bearer_token, build_soap_envelope
bp = Blueprint('yeastar', __name__) bp = Blueprint('yeastar', __name__)
@ -119,3 +119,53 @@ def handle_yeastar_outbound():
"detail": f"VOIP.ms returned status: {status or 'unknown'}" "detail": f"VOIP.ms returned status: {status or 'unknown'}"
}] }]
}), 400 }), 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"""
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:wsd="https://voip.ms/api/wsdl"
xmlns:xsd1="https://voip.ms/api/schema">
<soapenv:Header/>
<soapenv:Body>
<wsd:{soap_method}>
<wsd:params>
<xsd1:api_username>{username}</xsd1:api_username>
<xsd1:api_password>{password}</xsd1:api_password>
<xsd1:did>{from_number}</xsd1:did>
<xsd1:dst>{to_number}</xsd1:dst>
<xsd1:message>{message_text}</xsd1:message>
"""
# Add media tags if MMS, max 3 allowed
if is_mms:
for i, url in enumerate(media_urls[:3]):
envelope += f" <xsd1:media{i+1}>{url}</xsd1:media{i+1}>\n"
envelope += f""" </wsd:params>
</wsd:{soap_method}>
</soapenv:Body>
</soapenv:Envelope>"""
return envelope.strip()

View file

@ -27,7 +27,6 @@
<Compile Include="app.py" /> <Compile Include="app.py" />
<Compile Include="config.py" /> <Compile Include="config.py" />
<Compile Include="config.template.py" /> <Compile Include="config.template.py" />
<Compile Include="routes\helpers.py" />
<Compile Include="routes\threecx.py" /> <Compile Include="routes\threecx.py" />
<Compile Include="routes\voipms.py" /> <Compile Include="routes\voipms.py" />
<Compile Include="routes\yeastar.py" /> <Compile Include="routes\yeastar.py" />