From: Max Value Date: Thu, 25 Dec 2025 22:10:38 +0000 (+0000) Subject: made layout more flask friendly X-Git-Url: https://git.ozva.co.uk/?a=commitdiff_plain;h=79b0897d963f5491ec0c91db64741f495d9122df;p=conf-dialer made layout more flask friendly --- diff --git a/.gitignore b/.gitignore index 311cfd8..fa9cbd2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .venv/ +__pycache__/ +dialer.egg-info/ secrets.json diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1677284 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +all : .venv/touchfile + .venv/bin/python -m flask --app dialer run + +.venv/touchfile : requirements.txt + python -m venv .venv + .venv/bin/python -m pip install -r requirements.txt + touch .venv/touchfile diff --git a/dialer.py b/dialer.py new file mode 100755 index 0000000..928960d --- /dev/null +++ b/dialer.py @@ -0,0 +1,328 @@ +#!.venv/bin/python + +from werkzeug.security import generate_password_hash, check_password_hash +from flask import Flask, request, render_template, jsonify +from requests.auth import HTTPBasicAuth as requests_auth +from panoramisk.call_manager import CallManager +from flask_socketio import SocketIO, emit +from flask_httpauth import HTTPBasicAuth +from panoramisk import Manager +import threading +import requests +import asyncio +import json +import os + +from websockets.sync.client import connect + +app = Flask(__name__) +auth = HTTPBasicAuth() +socketio = SocketIO(app) + + + +# ============================================================================== +# SETUP + +# get the player data ready +with open(f"{app.root_path}/players.json", "r", encoding="utf-8") as f: + players = json.loads(f.read()) + +# get the secrets ready +with open(f"{app.root_path}/secrets.json", "r", encoding="utf-8") as f: + users_raw = json.loads(f.read()) +users = {k: generate_password_hash(v) for (k, v) in users_raw.items()} + +@auth.verify_password +def verify_password(username, password): + if username in users and \ + check_password_hash(users.get(username), password): + return username + + + +# ============================================================================== +# FRONTEND + +@app.route("/", methods=["GET", "POST"]) +@auth.login_required +def main(): + data = { + "players": players, + "sounds": [x.rsplit(".", maxsplit=1)[0] for x in os.listdir(f"{app.root_path}/sounds")], + "admin": "+447594768180" + } + + return render_template("panel.html", data=data) + + + +# ============================================================================== +# UTILS + +async def get_channel(number): + manager = Manager.from_config(f"{app.root_path}/config.ini") + await manager.connect() + + callers = await manager.send_action({ + 'Action': 'ConfbridgeList', + 'Conference': 'blood', + }) + + channel = None + + if type(callers) == list: + for message in callers: + channel_num = message.get("CallerIDNum") #"Channel" + if channel_num == number: + channel = message.channel + + manager.close() + + return channel + + + +# ============================================================================== +# DIALPLAN + +@app.route("/call", methods=["GET", "POST"]) +@auth.login_required +async def call(): + number = request.json["number"] + + manager = Manager.from_config(f"{app.root_path}/config.ini") + await manager.connect() + + phonecall = await manager.send_action({ + "Action": "Originate", + "Channel": f"PJSIP/{number}@gotrunk", + "Exten": "blood", + "Context": "from-internal", + "Priority": "1", + # "Application": , + # "Data": , + # "Timeout": 10, + "CallerID": "Departed", + "Variable": f"CALLERID(num)={number}", + # "Account": , + # "EarlyMedia": , + "Async": "true", + # "Codecs": , + # "ChannelId": , + # "OtherChannelId": , + # "PreDialGoSub": + }) + + manager.close() + + return "", 200 + + +@app.route("/kick", methods=["GET", "POST"]) +@auth.login_required +async def kick(): + number = request.json["number"] + + if number == "participants": + channel = number + else: + channel = await get_channel(number) + + if channel is None: + return f"Caller is not connected ({number})", 422 + + manager = Manager.from_config(f"{app.root_path}/config.ini") + await manager.connect() + + result = await manager.send_action({ + "Action": "ConfbridgeKick", + "Conference": "blood", + "Channel": channel + }) + + manager.close() + return "", 200 + + +@app.route("/mute", methods=["GET", "POST"]) +@auth.login_required +async def mute(): + number = request.json["number"] + + if number == "participants": + channel = number + else: + channel = await get_channel(number) + + if channel is None: + return f"Caller is not connected ({number})", 422 + + manager = Manager.from_config(f"{app.root_path}/config.ini") + await manager.connect() + + result = await manager.send_action({ + "Action": "ConfbridgeMute", + "Conference": "blood", + "Channel": channel + }) + + manager.close() + return "", 200 + + +@app.route("/unmute", methods=["GET", "POST"]) +@auth.login_required +async def unmute(): + number = request.json["number"] + + if number == "participants": + channel = number + else: + channel = await get_channel(number) + + if channel is None: + return "Caller is not connected", 200 + + manager = Manager.from_config(f"{app.root_path}/config.ini") + await manager.connect() + + result = await manager.send_action({ + "Action": "ConfbridgeUnmute", + "Conference": "blood", + "Channel": channel + }) + + manager.close() + return "", 200 + + +@app.route("/play", methods=["GET", "POST"]) +@auth.login_required +async def play(): + path = request.json["path"] + + manager = Manager.from_config(f"{app.root_path}/config.ini") + await manager.connect() + + phonecall = await manager.send_action({ + "Action": "Originate", + "Channel": "Local/bloodadmin@from-internal", + "Application": "Playback", + "Data": f"/home/will/{path}", + "Async": "true", + "CallerID": f"file_{path}" + }) + + manager.close() + + return "", 200 + + +@app.route("/stopall") +@auth.login_required +async def stopall(): + manager = Manager.from_config(f"{app.root_path}/config.ini") + await manager.connect() + + callers = await manager.send_action({ + 'Action': 'ConfbridgeList', + 'Conference': 'blood', + }) + + if type(callers) == list: + for message in callers: + if message.CallerIDName[:4] == "file": + result = await manager.send_action({ + "Action": "ConfbridgeKick", + "Conference": "blood", + "Channel": message.channel + }) + + manager.close() + return "", 200 + + + +# ============================================================================== +# EVENTS + +watcher = Manager.from_config(f"{app.root_path}/config.ini") + +def start_watch(): + loop = asyncio.new_event_loop() + watcher.loop = loop + + print(" * Event watcher started") + watcher.connect(run_forever=True) + + +def send_emit(data): + with app.test_request_context('/'): + emit("status", data, json=True, broadcast=True, namespace="/") + + +@watcher.register_event('DialState') # Register all events +async def dialstate_callback(manager, message): + send_emit({ + "number": message.DestCallerIDNum, + "status": "Ringing", + "mute": True + }) + + +@watcher.register_event('ConfbridgeJoin') # Register all events +async def confbridgejoin_callback(manager, message): + if message.CallerIDName[:4] == "file": + number = message.CallerIDName[5:] + else: + number = message.CallerIDNum + + send_emit({ + "number": number, + "status": "Connected", + "mute": message.Muted == "yes" + }) + + +@watcher.register_event('ConfbridgeLeave') # Register all events +async def confbridgeleave_callback(manager, message): + if message.CallerIDName[:4] == "file": + number = message.CallerIDName[5:] + else: + number = message.CallerIDNum + + send_emit({ + "number": number, + "status": "Disconnected", + "mute": True + }) + + +@watcher.register_event('ConfbridgeMute') # Register all events +async def confbridgemute_callback(manager, message): + send_emit({ + "number": message.CallerIDNum, + "status": "Connected", + "mute": True + }) + + +@watcher.register_event('ConfbridgeUnmute') # Register all events +async def confbridgeunmute_callback(manager, message): + send_emit({ + "number": message.CallerIDNum, + "status": "Connected", + "mute": False + }) + + + +# ============================================================================== + +if __name__ == "__main__": + # start the event watch manager + thread = threading.Thread(target=start_watch, daemon=True) + thread.start() + + socketio.run(app, host='127.0.0.1', port=8000, debug=True) diff --git a/main.py b/main.py deleted file mode 100755 index 3156f21..0000000 --- a/main.py +++ /dev/null @@ -1,329 +0,0 @@ -#!.venv/bin/python - -from werkzeug.security import generate_password_hash, check_password_hash -from flask import Flask, request, render_template, jsonify -from requests.auth import HTTPBasicAuth as requests_auth -from panoramisk.call_manager import CallManager -from flask_socketio import SocketIO, emit -from flask_httpauth import HTTPBasicAuth -from panoramisk import Manager -import threading -import requests -import asyncio -import json -import os - -from websockets.sync.client import connect - -app = Flask(__name__) -auth = HTTPBasicAuth() -socketio = SocketIO(app) - - - -# ============================================================================== -# SETUP - -# get the player data ready -with open(f"{app.root_path}/players.json", "r", encoding="utf-8") as f: - players = json.loads(f.read()) - -# get the secrets ready -with open(f"{app.root_path}/secrets.json", "r", encoding="utf-8") as f: - users_raw = json.loads(f.read()) -users = {k: generate_password_hash(v) for (k, v) in users_raw.items()} -basic = requests_auth('controller', users_raw['controller']) - -@auth.verify_password -def verify_password(username, password): - if username in users and \ - check_password_hash(users.get(username), password): - return username - - - -# ============================================================================== -# FRONTEND - -@app.route("/", methods=["GET", "POST"]) -@auth.login_required -def main(): - data = { - "players": players, - "sounds": [x.rsplit(".", maxsplit=1)[0] for x in os.listdir(f"{app.root_path}/sounds")], - "admin": "+447594768180" - } - - return render_template("panel.html", data=data) - - - -# ============================================================================== -# UTILS - -async def get_channel(number): - manager = Manager.from_config(f"{app.root_path}/config.ini") - await manager.connect() - - callers = await manager.send_action({ - 'Action': 'ConfbridgeList', - 'Conference': 'blood', - }) - - channel = None - - if type(callers) == list: - for message in callers: - channel_num = message.get("CallerIDNum") #"Channel" - if channel_num == number: - channel = message.channel - - manager.close() - - return channel - - - -# ============================================================================== -# DIALPLAN - -@app.route("/call", methods=["GET", "POST"]) -@auth.login_required -async def call(): - number = request.json["number"] - - manager = Manager.from_config(f"{app.root_path}/config.ini") - await manager.connect() - - phonecall = await manager.send_action({ - "Action": "Originate", - "Channel": f"PJSIP/{number}@gotrunk", - "Exten": "blood", - "Context": "from-internal", - "Priority": "1", - # "Application": , - # "Data": , - # "Timeout": 10, - "CallerID": "Departed", - "Variable": f"CALLERID(num)={number}", - # "Account": , - # "EarlyMedia": , - "Async": "true", - # "Codecs": , - # "ChannelId": , - # "OtherChannelId": , - # "PreDialGoSub": - }) - - manager.close() - - return "", 200 - - -@app.route("/kick", methods=["GET", "POST"]) -@auth.login_required -async def kick(): - number = request.json["number"] - - if number == "participants": - channel = number - else: - channel = await get_channel(number) - - if channel is None: - return f"Caller is not connected ({number})", 422 - - manager = Manager.from_config(f"{app.root_path}/config.ini") - await manager.connect() - - result = await manager.send_action({ - "Action": "ConfbridgeKick", - "Conference": "blood", - "Channel": channel - }) - - manager.close() - return "", 200 - - -@app.route("/mute", methods=["GET", "POST"]) -@auth.login_required -async def mute(): - number = request.json["number"] - - if number == "participants": - channel = number - else: - channel = await get_channel(number) - - if channel is None: - return f"Caller is not connected ({number})", 422 - - manager = Manager.from_config(f"{app.root_path}/config.ini") - await manager.connect() - - result = await manager.send_action({ - "Action": "ConfbridgeMute", - "Conference": "blood", - "Channel": channel - }) - - manager.close() - return "", 200 - - -@app.route("/unmute", methods=["GET", "POST"]) -@auth.login_required -async def unmute(): - number = request.json["number"] - - if number == "participants": - channel = number - else: - channel = await get_channel(number) - - if channel is None: - return "Caller is not connected", 200 - - manager = Manager.from_config(f"{app.root_path}/config.ini") - await manager.connect() - - result = await manager.send_action({ - "Action": "ConfbridgeUnmute", - "Conference": "blood", - "Channel": channel - }) - - manager.close() - return "", 200 - - -@app.route("/play", methods=["GET", "POST"]) -@auth.login_required -async def play(): - path = request.json["path"] - - manager = Manager.from_config(f"{app.root_path}/config.ini") - await manager.connect() - - phonecall = await manager.send_action({ - "Action": "Originate", - "Channel": "Local/bloodadmin@from-internal", - "Application": "Playback", - "Data": f"/home/will/{path}", - "Async": "true", - "CallerID": f"file_{path}" - }) - - manager.close() - - return "", 200 - - -@app.route("/stopall") -@auth.login_required -async def stopall(): - manager = Manager.from_config(f"{app.root_path}/config.ini") - await manager.connect() - - callers = await manager.send_action({ - 'Action': 'ConfbridgeList', - 'Conference': 'blood', - }) - - if type(callers) == list: - for message in callers: - if message.CallerIDName[:4] == "file": - result = await manager.send_action({ - "Action": "ConfbridgeKick", - "Conference": "blood", - "Channel": message.channel - }) - - manager.close() - return "", 200 - - - -# ============================================================================== -# EVENTS - -watcher = Manager.from_config(f"{app.root_path}/config.ini") - -def start_watch(): - loop = asyncio.new_event_loop() - watcher.loop = loop - - print(" * Event watcher started") - watcher.connect(run_forever=True) - - -def send_emit(data): - with app.test_request_context('/'): - emit("status", data, json=True, broadcast=True, namespace="/") - - -@watcher.register_event('DialState') # Register all events -async def dialstate_callback(manager, message): - send_emit({ - "number": message.DestCallerIDNum, - "status": "Ringing", - "mute": True - }) - - -@watcher.register_event('ConfbridgeJoin') # Register all events -async def confbridgejoin_callback(manager, message): - if message.CallerIDName[:4] == "file": - number = message.CallerIDName[5:] - else: - number = message.CallerIDNum - - send_emit({ - "number": number, - "status": "Connected", - "mute": message.Muted == "yes" - }) - - -@watcher.register_event('ConfbridgeLeave') # Register all events -async def confbridgeleave_callback(manager, message): - if message.CallerIDName[:4] == "file": - number = message.CallerIDName[5:] - else: - number = message.CallerIDNum - - send_emit({ - "number": number, - "status": "Disconnected", - "mute": True - }) - - -@watcher.register_event('ConfbridgeMute') # Register all events -async def confbridgemute_callback(manager, message): - send_emit({ - "number": message.CallerIDNum, - "status": "Connected", - "mute": True - }) - - -@watcher.register_event('ConfbridgeUnmute') # Register all events -async def confbridgeunmute_callback(manager, message): - send_emit({ - "number": message.CallerIDNum, - "status": "Connected", - "mute": False - }) - - - -# ============================================================================== - -if __name__ == "__main__": - # start the event watch manager - thread = threading.Thread(target=start_watch, daemon=True) - thread.start() - - socketio.run(app, host='127.0.0.1', port=8000, debug=True) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..faf0fdf --- /dev/null +++ b/setup.py @@ -0,0 +1,5 @@ +from distutils.core import setup +setup(name='dialer', + version='1.0', + py_modules=['dialer'], + ) diff --git a/test.py b/test.py deleted file mode 100644 index f5647a6..0000000 --- a/test.py +++ /dev/null @@ -1,28 +0,0 @@ -import asyncio -from panoramisk import Manager - -async def extension_status(): - manager = Manager(loop=asyncio.get_event_loop(), - host='127.0.0.1', port=5038, - username='asterisk', secret='test') - - await manager.connect() - action = { - 'Action': 'ConfbridgeList', - 'Conference': 'blood', - } - - extension = await manager.send_action(action) - print(extension) - manager.close() - - -def main(): - loop = asyncio.get_event_loop() - loop.run_until_complete(extension_status()) - loop.close() - - -if __name__ == '__main__': - main() -