Added 3cx sms-mms upgrader.
This commit is contained in:
parent
e2fde10d83
commit
01b2299472
4 changed files with 78 additions and 1 deletions
3
app.py
3
app.py
|
|
@ -1,5 +1,5 @@
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from routes import yeastar, voipms
|
from routes import yeastar, voipms, threecx
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
@ -12,6 +12,7 @@ app = Flask(__name__)
|
||||||
# Register blueprints
|
# Register blueprints
|
||||||
app.register_blueprint(yeastar.bp)
|
app.register_blueprint(yeastar.bp)
|
||||||
app.register_blueprint(voipms.bp)
|
app.register_blueprint(voipms.bp)
|
||||||
|
app.register_blueprint(threecx.bp)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(host='0.0.0.0', port=5000)
|
app.run(host='0.0.0.0', port=5000)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ YEASTAR_SECRET = "your-yeastar-secret"
|
||||||
|
|
||||||
# VOIP.ms SOAP endpoint (Farily static, update this if it changes in the future)
|
# VOIP.ms SOAP endpoint (Farily static, update this if it changes in the future)
|
||||||
VOIPMS_ENDPOINT = "https://voip.ms/api/v1/server.php"
|
VOIPMS_ENDPOINT = "https://voip.ms/api/v1/server.php"
|
||||||
|
VOIPMS_3CX_ENDPOINT = "https://voip.ms/api/3cx/msg"
|
||||||
|
|
||||||
# Make this something semi-random and URL safe. Adds obscurity and makes your endpoint harder to guess
|
# Make this something semi-random and URL safe. Adds obscurity and makes your endpoint harder to guess
|
||||||
ENDPOINT_OBSCURITY = "abcdefghijklmnopqrstuvwxyz"
|
ENDPOINT_OBSCURITY = "abcdefghijklmnopqrstuvwxyz"
|
||||||
|
|
|
||||||
74
routes/threecx.py
Normal file
74
routes/threecx.py
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
# 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'
|
||||||
|
)
|
||||||
|
|
@ -28,6 +28,7 @@
|
||||||
<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\helpers.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" />
|
||||||
<Compile Include="routes\__init__.py" />
|
<Compile Include="routes\__init__.py" />
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue