diff --git a/aprsd/cmds/webchat.py b/aprsd/cmds/webchat.py index 1dbacd8..bb79472 100644 --- a/aprsd/cmds/webchat.py +++ b/aprsd/cmds/webchat.py @@ -11,6 +11,7 @@ import time import aprslib from aprslib import util as aprslib_util import click +from device_detector import DeviceDetector import flask from flask import request from flask.logging import default_handler @@ -258,9 +259,12 @@ class WebChatTXThread(aprsd_thread.APRSDThread): msg_id = packet.get("msgNo", "0") msg_response = packet.get("response", None) - if tocall == self.config["aprsd"]["callsign"] and msg_response == "ack": + if ( + tocall.lower() == self.config["aprsd"]["callsign"].lower() + and msg_response == "ack" + ): self.process_ack_packet(packet) - elif tocall == self.config["aprsd"]["callsign"]: + elif tocall.lower() == self.config["aprsd"]["callsign"].lower(): messaging.log_message( "Received Message", packet["raw"], @@ -312,10 +316,7 @@ class WebChatFlask(flask_classful.FlaskView): users = self.users - @auth.login_required - def index(self): - stats = self._stats() - + def _get_transport(self, stats): if self.config["aprs"].get("enabled", True): transport = "aprs-is" aprs_connection = ( @@ -341,12 +342,35 @@ class WebChatFlask(flask_classful.FlaskView): ) ) + return transport, aprs_connection + + @auth.login_required + def index(self): + user_agent = request.headers.get("User-Agent") + device = DeviceDetector(user_agent).parse() + LOG.debug(f"Device type {device.device_type()}") + LOG.debug(f"Is mobile? {device.is_mobile()}") + stats = self._stats() + + if device.is_mobile(): + html_template = "mobile.html" + else: + html_template = "index.html" + + # For development + # html_template = "mobile.html" + + LOG.debug(f"Template {html_template}") + + transport, aprs_connection = self._get_transport(stats) + LOG.debug(f"transport {transport} aprs_connection {aprs_connection}") + stats["transport"] = transport stats["aprs_connection"] = aprs_connection LOG.debug(f"initial stats = {stats}") return flask.render_template( - "index.html", + html_template, initial_stats=stats, aprs_connection=aprs_connection, callsign=self.config["aprsd"]["callsign"], diff --git a/aprsd/threads/rx.py b/aprsd/threads/rx.py index 0309955..01989b8 100644 --- a/aprsd/threads/rx.py +++ b/aprsd/threads/rx.py @@ -88,7 +88,7 @@ class APRSDProcessPacketThread(APRSDThread): return def loop(self): - """Process a packet recieved from aprs-is server.""" + """Process a packet received from aprs-is server.""" packet = self.packet packets.PacketList().add(packet) @@ -101,7 +101,10 @@ class APRSDProcessPacketThread(APRSDThread): # We don't put ack packets destined for us through the # plugins. - if tocall == self.config["aprsd"]["callsign"] and msg_response == "ack": + if ( + tocall.lower() == self.config["aprsd"]["callsign"].lower() + and msg_response == "ack" + ): self.process_ack_packet(packet) else: # It's not an ACK for us, so lets run it through diff --git a/aprsd/web/chat/static/js/send-message-mobile.js b/aprsd/web/chat/static/js/send-message-mobile.js new file mode 100644 index 0000000..57b7ab7 --- /dev/null +++ b/aprsd/web/chat/static/js/send-message-mobile.js @@ -0,0 +1,221 @@ +var cleared = false; +var callsign_list = {}; +var message_list = {}; + +function size_dict(d){c=0; for (i in d) ++c; return c} + +function init_chat() { + const socket = io("/sendmsg"); + socket.on('connect', function () { + console.log("Connected to socketio"); + }); + socket.on('connected', function(msg) { + console.log("Connected!"); + console.log(msg); + }); + + socket.on("sent", function(msg) { + if (cleared == false) { + var msgsdiv = $("#msgsTabsDiv"); + msgsdiv.html('') + cleared = true + } + sent_msg(msg); + }); + + socket.on("ack", function(msg) { + update_msg(msg); + }); + + socket.on("new", function(msg) { + if (cleared == false) { + var msgsdiv = $("#msgsTabsDiv"); + msgsdiv.html('') + cleared = true + } + from_msg(msg); + }); + + $("#sendform").submit(function(event) { + event.preventDefault(); + msg = {'to': $('#to_call').val(), + 'message': $('#message').val(), + } + socket.emit("send", msg); + $('#message').val(''); + $('#to_call').val(''); + }); +} + + +function add_callsign(callsign) { + /* Ensure a callsign exists in the left hand nav */ + dropdown = $('#callsign_dropdown') + + if (callsign in callsign_list) { + console.log(callsign+' already in list.') + return false + } + + var callsignTabs = $("#callsignTabs"); + tab_name = tab_string(callsign); + tab_content = tab_content_name(callsign); + divname = content_divname(callsign); + + item_html = '
'+callsign+'
'; + callsignTabs.append(item_html); + + callsign_list[callsign] = {'name': callsign, 'value': callsign, 'text': callsign} + return true +} + +function append_message(callsign, msg, msg_html) { + console.log('append_message'); + new_callsign = false + if (!message_list.hasOwnProperty(callsign)) { + message_list[callsign] = new Array(); + } + message_list[callsign].push(msg); + if (new_callsign) { + //click on the new tab + click_div = '#'+tab_string(callsign); + console.log("Click on "+click_div); + $(click_div).click(); + } + + // Find the right div to place the html + new_callsign = add_callsign(callsign); + append_message_html(callsign, msg_html, new_callsign); +} + +function tab_string(callsign) { + return "msgs"+callsign; +} + +function tab_content_name(callsign) { + return tab_string(callsign)+"Content"; +} + +function content_divname(callsign) { + return "#"+tab_content_name(callsign); +} + +function append_message_html(callsign, msg_html, new_callsign) { + var msgsTabs = $('#msgsTabsDiv'); + divname_str = tab_content_name(callsign); + divname = content_divname(callsign); + if (new_callsign) { + // we have to add a new DIV + msg_div_html = '
'+msg_html+'
'; + msgsTabs.append(msg_div_html); + } else { + var msgDiv = $(divname); + msgDiv.append(msg_html); + } + + $(divname).animate({scrollTop: $(divname)[0].scrollHeight}, "slow"); +} + +function create_message_html(time, from, to, message, ack) { + msg_html = '
'; + msg_html += '
'+time+'
'; + msg_html += '
'; + msg_html += '
'+from+'
'; + if (ack) { + msg_html += ''; + } else { + msg_html += ''; + } + msg_html += '
>   
'; + msg_html += '
'; + msg_html += '
'+message+'
'; + msg_html += '

'; + + return msg_html +} + +function sent_msg(msg) { + var msgsdiv = $("#sendMsgsDiv"); + + ts_str = msg["ts"].toString(); + ts = ts_str.split(".")[0]*1000; + id = ts_str.split('.')[0] + ack_id = "ack_" + id + + var d = new Date(ts).toLocaleDateString("en-US") + var t = new Date(ts).toLocaleTimeString("en-US") + + msg_html = create_message_html(t, msg['from'], msg['to'], msg['message'], ack_id); + append_message(msg['to'], msg, msg_html); +} + +function from_msg(msg) { + var msgsdiv = $("#sendMsgsDiv"); + + // We have an existing entry + ts_str = msg["ts"].toString(); + ts = ts_str.split(".")[0]*1000; + id = ts_str.split('.')[0] + ack_id = "ack_" + id + + var d = new Date(ts).toLocaleDateString("en-US") + var t = new Date(ts).toLocaleTimeString("en-US") + + from = msg['from'] + msg_html = create_message_html(t, from, false, msg['message'], false); + append_message(from, msg, msg_html); +} + +function update_msg(msg) { + var msgsdiv = $("#sendMsgsDiv"); + // We have an existing entry + ts_str = msg["ts"].toString(); + id = ts_str.split('.')[0] + pretty_id = "pretty_" + id + loader_id = "loader_" + id + ack_id = "ack_" + id + span_id = "span_" + id + + + + if (msg['ack'] == true) { + var loader_div = $('#' + loader_id); + var ack_div = $('#' + ack_id); + loader_div.removeClass('ui active inline loader'); + loader_div.addClass('ui disabled loader'); + ack_div.removeClass('thumbs up outline icon'); + ack_div.addClass('thumbs up outline icon'); + } + + $('.ui.accordion').accordion('refresh'); +} + +function callsign_select(callsign) { + var tocall = $("#to_call"); + tocall.val(callsign); +} + +function reset_Tabs() { + tabcontent = document.getElementsByClassName("tabcontent"); + for (i = 0; i < tabcontent.length; i++) { + tabcontent[i].style.display = "none"; + } +} + +function openCallsign(evt, callsign) { + var i, tabcontent, tablinks; + + tab_content = tab_content_name(callsign); + + tabcontent = document.getElementsByClassName("tabcontent"); + for (i = 0; i < tabcontent.length; i++) { + tabcontent[i].style.display = "none"; + } + tablinks = document.getElementsByClassName("tablinks"); + for (i = 0; i < tablinks.length; i++) { + tablinks[i].className = tablinks[i].className.replace(" active", ""); + } + document.getElementById(tab_content).style.display = "block"; + evt.target.className += " active"; + callsign_select(callsign); +} diff --git a/aprsd/web/chat/templates/index.html b/aprsd/web/chat/templates/index.html index c90f51a..a3a5d19 100644 --- a/aprsd/web/chat/templates/index.html +++ b/aprsd/web/chat/templates/index.html @@ -66,16 +66,14 @@ - + -
-
-
-
-
-
-   -
+
+
+
+
+
+  
diff --git a/aprsd/web/chat/templates/mobile.html b/aprsd/web/chat/templates/mobile.html new file mode 100644 index 0000000..2b8ca75 --- /dev/null +++ b/aprsd/web/chat/templates/mobile.html @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + +
+

APRSD WebChat {{ version }}

+
+ +
+
+ {{ callsign }} + connected to + {{ aprs_connection|safe }} +
+ +
+ NONE +
+
+ +
+

Send Message

+
+
+
Callsign
+ + +
+
+ + + +
+
+ +
+ +
+
+ +
+ +
+   +
+
+ +
+ PyPI version + +
+ + diff --git a/dev-requirements.txt b/dev-requirements.txt index cca01d8..be038e9 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -7,13 +7,12 @@ add-trailing-comma==2.3.0 # via gray alabaster==0.7.12 # via sphinx attrs==22.1.0 # via jsonschema, pytest -autoflake==1.7.7 # via gray +autoflake==2.0.0 # via gray babel==2.11.0 # via sphinx black==22.10.0 # via gray bleach==5.0.1 # via readme-renderer build==0.9.0 # via pip-tools certifi==2022.9.24 # via requests -cffi==1.15.1 # via cryptography cfgv==3.3.1 # via pre-commit charset-normalizer==2.1.1 # via requests click==8.1.3 # via black, pip-tools @@ -21,7 +20,6 @@ colorlog==6.7.0 # via prettylog commonmark==0.9.1 # via rich configargparse==1.5.3 # via gray coverage[toml]==6.5.0 # via pytest-cov -cryptography==38.0.3 # via secretstorage distlib==0.3.6 # via virtualenv docutils==0.19 # via readme-renderer, sphinx exceptiongroup==1.0.4 # via pytest @@ -38,9 +36,8 @@ importlib-resources==5.10.0 # via fixit, jsonschema iniconfig==1.1.1 # via pytest isort==5.10.1 # via -r dev-requirements.in, gray jaraco-classes==3.2.3 # via keyring -jeepney==0.8.0 # via keyring, secretstorage jinja2==3.1.2 # via sphinx -jsonschema==4.17.1 # via fixit +jsonschema==4.17.3 # via fixit keyring==23.11.0 # via twine libcst==0.4.9 # via fixit markupsafe==2.1.1 # via jinja2 @@ -54,7 +51,7 @@ pathspec==0.10.2 # via black pep517==0.13.0 # via build pep8-naming==0.13.2 # via -r dev-requirements.in pip-tools==6.10.0 # via -r dev-requirements.in -pkginfo==1.8.3 # via twine +pkginfo==1.9.2 # via twine pkgutil-resolve-name==1.3.10 # via jsonschema platformdirs==2.5.4 # via black, virtualenv pluggy==1.0.0 # via pytest, tox @@ -62,7 +59,6 @@ pre-commit==2.20.0 # via -r dev-requirements.in prettylog==0.3.0 # via gray py==1.11.0 # via tox pycodestyle==2.10.0 # via flake8 -pycparser==2.21 # via cffi pyflakes==3.0.1 # via autoflake, flake8 pygments==2.13.0 # via readme-renderer, rich, sphinx pyparsing==3.0.9 # via packaging @@ -70,14 +66,13 @@ pyrsistent==0.19.2 # via jsonschema pytest==7.2.0 # via -r dev-requirements.in, pytest-cov pytest-cov==4.0.0 # via -r dev-requirements.in pytz==2022.6 # via babel -pyupgrade==3.2.2 # via gray +pyupgrade==3.2.3 # via gray pyyaml==6.0 # via fixit, libcst, pre-commit readme-renderer==37.3 # via twine requests==2.28.1 # via requests-toolbelt, sphinx, twine requests-toolbelt==0.10.1 # via twine rfc3986==2.0.0 # via twine rich==12.6.0 # via twine -secretstorage==3.3.3 # via keyring six==1.16.0 # via bleach, tox snowballstemmer==2.2.0 # via sphinx sphinx==5.3.0 # via -r dev-requirements.in @@ -98,10 +93,10 @@ ujson==5.5.0 # via fast-json unify==0.5 # via gray untokenize==0.1.1 # via unify urllib3==1.26.13 # via requests, twine -virtualenv==20.16.7 # via pre-commit, tox +virtualenv==20.17.0 # via pre-commit, tox webencodings==0.5.1 # via bleach wheel==0.38.4 # via pip-tools -zipp==3.10.0 # via importlib-metadata, importlib-resources +zipp==3.11.0 # via importlib-metadata, importlib-resources # The following packages are considered to be unsafe in a requirements file: # pip diff --git a/requirements.in b/requirements.in index 6fbaf6d..16264e5 100644 --- a/requirements.in +++ b/requirements.in @@ -24,3 +24,5 @@ wrapt # kiss3 uses attrs kiss3 attrs==22.1.0 +# for mobile checking +device-detector diff --git a/requirements.txt b/requirements.txt index b783af5..ac8ba5e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,6 +15,7 @@ charset-normalizer==2.1.1 # via requests click==8.1.3 # via -r requirements.in, click-completion, flask click-completion==0.5.2 # via -r requirements.in commonmark==0.9.1 # via rich +device-detector==5.0.1 # via -r requirements.in dnspython==2.2.1 # via eventlet eventlet==0.33.2 # via -r requirements.in flask==2.1.2 # via -r requirements.in, flask-classful, flask-httpauth, flask-socketio @@ -37,7 +38,8 @@ pyserial-asyncio==0.6 # via kiss3 python-engineio==4.3.4 # via python-socketio python-socketio==5.7.2 # via flask-socketio pytz==2022.6 # via -r requirements.in -pyyaml==6.0 # via -r requirements.in +pyyaml==6.0 # via -r requirements.in, device-detector +regex==2022.10.31 # via device-detector requests==2.28.1 # via -r requirements.in, update-checker rich==12.6.0 # via -r requirements.in shellingham==1.5.0 # via click-completion @@ -50,4 +52,4 @@ update-checker==0.18.0 # via -r requirements.in urllib3==1.26.13 # via requests werkzeug==2.1.2 # via -r requirements.in, flask wrapt==1.14.1 # via -r requirements.in -zipp==3.10.0 # via importlib-metadata +zipp==3.11.0 # via importlib-metadata