diff --git a/aprsd/aprsd.py b/aprsd/aprsd.py
index 5d5587c..c97826e 100644
--- a/aprsd/aprsd.py
+++ b/aprsd/aprsd.py
@@ -68,7 +68,7 @@ def main():
# The commands themselves live in the cmds directory
from .cmds import ( # noqa
completion, dev, healthcheck, list_plugins, listen, send_message,
- server,
+ server, webchat,
)
cli()
diff --git a/aprsd/cli_helper.py b/aprsd/cli_helper.py
index 2e0fb0d..c70f22c 100644
--- a/aprsd/cli_helper.py
+++ b/aprsd/cli_helper.py
@@ -5,6 +5,7 @@ import click
from aprsd import config as aprsd_config
from aprsd.logging import log
+from aprsd.utils import trace
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
@@ -59,6 +60,8 @@ def process_standard_options(f: F) -> F:
ctx.obj["loglevel"],
ctx.obj["quiet"],
)
+ if ctx.obj["config"]["aprsd"].get("trace", False):
+ trace.setup_tracing(["method", "api"])
del kwargs["loglevel"]
del kwargs["config_file"]
diff --git a/aprsd/client.py b/aprsd/client.py
index b4cb2e3..f17e027 100644
--- a/aprsd/client.py
+++ b/aprsd/client.py
@@ -52,7 +52,8 @@ class Client:
def reset(self):
"""Call this to force a rebuild/reconnect."""
- del self._client
+ if self._client:
+ del self._client
@abc.abstractmethod
def setup_connection(self):
@@ -130,6 +131,7 @@ class APRSISClient(Client):
backoff = backoff * 2
continue
LOG.debug(f"Logging in to APRS-IS with user '{user}'")
+ self._client = aprs_client
return aprs_client
diff --git a/aprsd/clients/aprsis.py b/aprsd/clients/aprsis.py
index ac7bdac..635d27b 100644
--- a/aprsd/clients/aprsis.py
+++ b/aprsd/clients/aprsis.py
@@ -1,5 +1,7 @@
import logging
import select
+import socket
+import threading
import aprslib
from aprslib import is_py3
@@ -7,6 +9,7 @@ from aprslib.exceptions import (
ConnectionDrop, ConnectionError, GenericError, LoginError, ParseError,
UnknownFormat,
)
+import wrapt
import aprsd
from aprsd import stats
@@ -23,11 +26,30 @@ class Aprsdis(aprslib.IS):
# timeout in seconds
select_timeout = 1
+ lock = threading.Lock()
def stop(self):
self.thread_stop = True
LOG.info("Shutdown Aprsdis client.")
+ def is_socket_closed(self, sock: socket.socket) -> bool:
+ try:
+ # this will try to read bytes without blocking and also without removing them from buffer (peek only)
+ data = sock.recv(16, socket.MSG_DONTWAIT | socket.MSG_PEEK)
+ if len(data) == 0:
+ return True
+ except BlockingIOError:
+ return False # socket is open and reading from it would block
+ except ConnectionResetError:
+ return True # socket was closed for some other reason
+ except Exception:
+ self.logger.exception(
+ "unexpected exception when checking if a socket is closed",
+ )
+ return False
+ return False
+
+ @wrapt.synchronized(lock)
def send(self, msg):
"""Send an APRS Message object."""
line = str(msg)
diff --git a/aprsd/cmds/listen.py b/aprsd/cmds/listen.py
index c165650..1afbcef 100644
--- a/aprsd/cmds/listen.py
+++ b/aprsd/cmds/listen.py
@@ -139,19 +139,24 @@ def listen(
# Creates the client object
LOG.info("Creating client connection")
- client.factory.create().client
- aprs_client = client.factory.create().client
+ aprs_client = client.factory.create()
+ console.log(aprs_client)
LOG.debug(f"Filter by '{filter}'")
- aprs_client.set_filter(filter)
+ aprs_client.client.set_filter(filter)
+
+ packets.PacketList(config=config)
+
+ keepalive = threads.KeepAliveThread(config=config)
+ keepalive.start()
while True:
try:
# This will register a packet consumer with aprslib
# When new packets come in the consumer will process
# the packet
- with console.status("Listening for packets"):
- aprs_client.consumer(rx_packet, raw=False)
+ # with console.status("Listening for packets"):
+ aprs_client.client.consumer(rx_packet, raw=False)
except aprslib.exceptions.ConnectionDrop:
LOG.error("Connection dropped, reconnecting")
time.sleep(5)
diff --git a/aprsd/cmds/server.py b/aprsd/cmds/server.py
index 7d9a8cf..d53865d 100644
--- a/aprsd/cmds/server.py
+++ b/aprsd/cmds/server.py
@@ -11,7 +11,6 @@ from aprsd import (
)
from aprsd import aprsd as aprsd_main
from aprsd.aprsd import cli
-from aprsd.utils import trace
LOG = logging.getLogger("APRSD")
@@ -59,8 +58,6 @@ def server(ctx, flush):
else:
LOG.info(f"{x} = {flat_config[x]}")
- if config["aprsd"].get("trace", False):
- trace.setup_tracing(["method", "api"])
stats.APRSDStats(config)
# Initialize the client factory and create
@@ -98,7 +95,7 @@ def server(ctx, flush):
plugin_manager = plugin.PluginManager(config)
plugin_manager.setup_plugins()
- rx_thread = threads.APRSDRXThread(
+ rx_thread = threads.APRSDPluginRXThread(
msg_queues=threads.msg_queues,
config=config,
)
diff --git a/aprsd/cmds/webchat.py b/aprsd/cmds/webchat.py
new file mode 100644
index 0000000..fc518a8
--- /dev/null
+++ b/aprsd/cmds/webchat.py
@@ -0,0 +1,592 @@
+import datetime
+import json
+import logging
+from logging.handlers import RotatingFileHandler
+import queue
+import signal
+import sys
+import threading
+import time
+
+import aprslib
+import click
+import flask
+from flask import request
+from flask.logging import default_handler
+import flask_classful
+from flask_httpauth import HTTPBasicAuth
+from flask_socketio import Namespace, SocketIO
+from werkzeug.security import check_password_hash, generate_password_hash
+import wrapt
+
+import aprsd
+from aprsd import aprsd as aprsd_main
+from aprsd import cli_helper, client
+from aprsd import config as aprsd_config
+from aprsd import messaging, packets, stats, threads, utils
+from aprsd.aprsd import cli
+from aprsd.logging import rich as aprsd_logging
+from aprsd.threads import aprsd as aprsd_thread
+from aprsd.threads import rx
+from aprsd.utils import objectstore, trace
+
+
+LOG = logging.getLogger("APRSD")
+auth = HTTPBasicAuth()
+users = None
+rx_msg_queue = queue.Queue(maxsize=20)
+tx_msg_queue = queue.Queue(maxsize=20)
+control_queue = queue.Queue(maxsize=20)
+msg_queues = {
+ "rx": rx_msg_queue,
+ "control": control_queue,
+ "tx": tx_msg_queue,
+}
+
+
+class SentMessages(objectstore.ObjectStoreMixin):
+ _instance = None
+ lock = threading.Lock()
+
+ data = {}
+
+ def __new__(cls, *args, **kwargs):
+ """This magic turns this into a singleton."""
+ if cls._instance is None:
+ cls._instance = super().__new__(cls)
+ return cls._instance
+
+ @wrapt.synchronized(lock)
+ def add(self, msg):
+ self.data[msg.id] = self.create(msg.id)
+ self.data[msg.id]["from"] = msg.fromcall
+ self.data[msg.id]["to"] = msg.tocall
+ self.data[msg.id]["message"] = msg.message.rstrip("\n")
+ self.data[msg.id]["raw"] = str(msg).rstrip("\n")
+
+ def create(self, id):
+ return {
+ "id": id,
+ "ts": time.time(),
+ "ack": False,
+ "from": None,
+ "to": None,
+ "raw": None,
+ "message": None,
+ "status": None,
+ "last_update": None,
+ "reply": None,
+ }
+
+ @wrapt.synchronized(lock)
+ def __len__(self):
+ return len(self.data.keys())
+
+ @wrapt.synchronized(lock)
+ def get(self, id):
+ if id in self.data:
+ return self.data[id]
+
+ @wrapt.synchronized(lock)
+ def get_all(self):
+ return self.data
+
+ @wrapt.synchronized(lock)
+ def set_status(self, id, status):
+ self.data[id]["last_update"] = str(datetime.datetime.now())
+ self.data[id]["status"] = status
+
+ @wrapt.synchronized(lock)
+ def ack(self, id):
+ """The message got an ack!"""
+ self.data[id]["last_update"] = str(datetime.datetime.now())
+ self.data[id]["ack"] = True
+
+ @wrapt.synchronized(lock)
+ def reply(self, id, packet):
+ """We got a packet back from the sent message."""
+ self.data[id]["reply"] = packet
+
+
+# HTTPBasicAuth doesn't work on a class method.
+# This has to be out here. Rely on the APRSDFlask
+# class to initialize the users from the config
+@auth.verify_password
+def verify_password(username, password):
+ global users
+
+ if username in users and check_password_hash(users.get(username), password):
+ return username
+
+
+class WebChatRXThread(rx.APRSDRXThread):
+ """Class that connects to aprsis and waits for messages."""
+
+ def connected(self, connected=True):
+ self.connected = connected
+
+ def loop(self):
+
+ # setup the consumer of messages and block until a messages
+ msg = None
+ try:
+ msg = self.msg_queues["tx"].get_nowait()
+ except queue.Empty:
+ pass
+
+ try:
+ if msg:
+ LOG.debug("GOT msg from TX queue!!")
+ msg.send()
+ except (
+ aprslib.exceptions.ConnectionDrop,
+ aprslib.exceptions.ConnectionError,
+ ):
+ LOG.error("Connection dropped, reconnecting")
+ # Put it back on the queue to send.
+ self.msg_queues["tx"].put(msg)
+ # Force the deletion of the client object connected to aprs
+ # This will cause a reconnect, next time client.get_client()
+ # is called
+ self._client.reset()
+ time.sleep(2)
+
+ try:
+ # This will register a packet consumer with aprslib
+ # When new packets come in the consumer will process
+ # the packet
+
+ # Do a partial here because the consumer signature doesn't allow
+ # For kwargs to be passed in to the consumer func we declare
+ # and the aprslib developer didn't want to allow a PR to add
+ # kwargs. :(
+ # https://github.com/rossengeorgiev/aprs-python/pull/56
+ self._client.client.consumer(
+ self.process_packet, raw=False, blocking=False,
+ )
+
+ except (
+ aprslib.exceptions.ConnectionDrop,
+ aprslib.exceptions.ConnectionError,
+ ):
+ LOG.error("Connection dropped, reconnecting")
+ time.sleep(5)
+ # Force the deletion of the client object connected to aprs
+ # This will cause a reconnect, next time client.get_client()
+ # is called
+ self._client.reset()
+ # Continue to loop
+ time.sleep(1)
+ return True
+
+ def process_packet(self, *args, **kwargs):
+ packet = self._client.decode_packet(*args, **kwargs)
+ LOG.debug(f"GOT Packet {packet}")
+ self.msg_queues["rx"].put(packet)
+
+
+class WebChatTXThread(aprsd_thread.APRSDThread):
+ """Class that """
+ def __init__(self, msg_queues, config, socketio):
+ super().__init__("_TXThread_")
+ self.msg_queues = msg_queues
+ self.config = config
+ self.socketio = socketio
+ self.connected = False
+
+ def loop(self):
+ try:
+ msg = self.msg_queues["control"].get_nowait()
+ self.connected = msg["connected"]
+ except queue.Empty:
+ pass
+ try:
+ packet = self.msg_queues["rx"].get_nowait()
+ if packet:
+ # we got a packet and we need to send it to the
+ # web socket
+ self.process_packet(packet)
+ except queue.Empty:
+ pass
+ except Exception as ex:
+ LOG.exception(ex)
+ time.sleep(1)
+
+ return True
+
+ def process_ack_packet(self, packet):
+ ack_num = packet.get("msgNo")
+ LOG.info(f"We got ack for our sent message {ack_num}")
+ messaging.log_packet(packet)
+ SentMessages().ack(int(ack_num))
+ self.socketio.emit(
+ "ack", SentMessages().get(int(ack_num)),
+ namespace="/sendmsg",
+ )
+ stats.APRSDStats().ack_rx_inc()
+ self.got_ack = True
+
+ def process_packet(self, packet):
+ tocall = packet.get("addresse", None)
+ fromcall = packet["from"]
+ msg = packet.get("message_text", None)
+ msg_id = packet.get("msgNo", "0")
+ msg_response = packet.get("response", None)
+
+ if tocall == self.config["aprs"]["login"] and msg_response == "ack":
+ self.process_ack_packet(packet)
+ elif tocall == self.config["aprs"]["login"]:
+ messaging.log_message(
+ "Received Message",
+ packet["raw"],
+ msg,
+ fromcall=fromcall,
+ msg_num=msg_id,
+ )
+ # let any threads do their thing, then ack
+ # send an ack last
+ ack = messaging.AckMessage(
+ self.config["aprs"]["login"],
+ fromcall,
+ msg_id=msg_id,
+ )
+ self.msg_queues["tx"].put(ack)
+
+ packets.PacketList().add(packet)
+ stats.APRSDStats().msgs_rx_inc()
+ message = packet.get("message_text", None)
+ msg = {
+ "id": 0,
+ "ts": time.time(),
+ "ack": False,
+ "from": fromcall,
+ "to": packet["to"],
+ "raw": packet["raw"],
+ "message": message,
+ "status": None,
+ "last_update": None,
+ "reply": None,
+ }
+ self.socketio.emit(
+ "new", msg,
+ namespace="/sendmsg",
+ )
+
+
+class WebChatFlask(flask_classful.FlaskView):
+ config = None
+
+ def set_config(self, config):
+ global users
+ self.config = config
+ self.users = {}
+ for user in self.config["aprsd"]["web"]["users"]:
+ self.users[user] = generate_password_hash(
+ self.config["aprsd"]["web"]["users"][user],
+ )
+
+ users = self.users
+
+ @auth.login_required
+ def index(self):
+ stats = self._stats()
+
+ if self.config["aprs"].get("enabled", True):
+ transport = "aprs-is"
+ aprs_connection = (
+ "APRS-IS Server: "
+ "{}".format(stats["stats"]["aprs-is"]["server"])
+ )
+ else:
+ # We might be connected to a KISS socket?
+ if client.KISSClient.kiss_enabled(self.config):
+ transport = client.KISSClient.transport(self.config)
+ if transport == client.TRANSPORT_TCPKISS:
+ aprs_connection = (
+ "TCPKISS://{}:{}".format(
+ self.config["kiss"]["tcp"]["host"],
+ self.config["kiss"]["tcp"]["port"],
+ )
+ )
+ elif transport == client.TRANSPORT_SERIALKISS:
+ aprs_connection = (
+ "SerialKISS://{}@{} baud".format(
+ self.config["kiss"]["serial"]["device"],
+ self.config["kiss"]["serial"]["baudrate"],
+ )
+ )
+
+ stats["transport"] = transport
+ stats["aprs_connection"] = aprs_connection
+ LOG.debug(f"initial stats = {stats}")
+
+ return flask.render_template(
+ "index.html",
+ initial_stats=stats,
+ aprs_connection=aprs_connection,
+ callsign=self.config["aprs"]["login"],
+ version=aprsd.__version__,
+ )
+
+ @auth.login_required
+ def send_message_status(self):
+ LOG.debug(request)
+ msgs = SentMessages()
+ info = msgs.get_all()
+ return json.dumps(info)
+
+ @trace.trace
+ def _stats(self):
+ stats_obj = stats.APRSDStats()
+ now = datetime.datetime.now()
+
+ time_format = "%m-%d-%Y %H:%M:%S"
+ stats_dict = stats_obj.stats()
+ # Webchat doesnt need these
+ del stats_dict["aprsd"]["watch_list"]
+ del stats_dict["aprsd"]["seen_list"]
+ # del stats_dict["email"]
+ # del stats_dict["plugins"]
+ # del stats_dict["messages"]
+
+ result = {
+ "time": now.strftime(time_format),
+ "stats": stats_dict,
+ }
+
+ return result
+
+ def stats(self):
+ return json.dumps(self._stats())
+
+
+class SendMessageNamespace(Namespace):
+ """Class to handle the socketio interactions."""
+ _config = None
+ got_ack = False
+ reply_sent = False
+ msg = None
+ request = None
+
+ def __init__(self, namespace=None, config=None, msg_queues=None):
+ self._config = config
+ self._msg_queues = msg_queues
+ super().__init__(namespace)
+
+ def on_connect(self):
+ global socketio
+ LOG.debug("Web socket connected")
+ socketio.emit(
+ "connected", {"data": "/sendmsg Connected"},
+ namespace="/sendmsg",
+ )
+ msg = {"connected": True}
+ self._msg_queues["control"].put(msg)
+
+ def on_disconnect(self):
+ LOG.debug("WS Disconnected")
+ msg = {"connected": False}
+ self._msg_queues["control"].put(msg)
+
+ def on_send(self, data):
+ global socketio
+ LOG.debug(f"WS: on_send {data}")
+ self.request = data
+ data["from"] = self._config["aprs"]["login"]
+ msg = messaging.TextMessage(
+ data["from"],
+ data["to"],
+ data["message"],
+ )
+ self.msg = msg
+ msgs = SentMessages()
+ msgs.add(msg)
+ msgs.set_status(msg.id, "Sending")
+ socketio.emit(
+ "sent", SentMessages().get(self.msg.id),
+ namespace="/sendmsg",
+ )
+
+ self._msg_queues["tx"].put(msg)
+
+ def handle_message(self, data):
+ LOG.debug(f"WS Data {data}")
+
+ def handle_json(self, data):
+ LOG.debug(f"WS json {data}")
+
+
+def setup_logging(config, flask_app, loglevel, quiet):
+ flask_log = logging.getLogger("werkzeug")
+ flask_app.logger.removeHandler(default_handler)
+ flask_log.removeHandler(default_handler)
+
+ log_level = aprsd_config.LOG_LEVELS[loglevel]
+ flask_log.setLevel(log_level)
+ date_format = config["aprsd"].get(
+ "dateformat",
+ aprsd_config.DEFAULT_DATE_FORMAT,
+ )
+
+ if not config["aprsd"]["web"].get("logging_enabled", False):
+ # disable web logging
+ flask_log.disabled = True
+ flask_app.logger.disabled = True
+ # return
+
+ if config["aprsd"].get("rich_logging", False) and not quiet:
+ log_format = "%(message)s"
+ log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
+ rh = aprsd_logging.APRSDRichHandler(
+ show_thread=True, thread_width=15,
+ rich_tracebacks=True, omit_repeated_times=False,
+ )
+ rh.setFormatter(log_formatter)
+ flask_log.addHandler(rh)
+
+ log_file = config["aprsd"].get("logfile", None)
+
+ if log_file:
+ log_format = config["aprsd"].get(
+ "logformat",
+ aprsd_config.DEFAULT_LOG_FORMAT,
+ )
+ log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
+ fh = RotatingFileHandler(
+ log_file, maxBytes=(10248576 * 5),
+ backupCount=4,
+ )
+ fh.setFormatter(log_formatter)
+ flask_log.addHandler(fh)
+
+
+@trace.trace
+def init_flask(config, loglevel, quiet):
+ global socketio
+
+ flask_app = flask.Flask(
+ "aprsd",
+ static_url_path="/static",
+ static_folder="web/chat/static",
+ template_folder="web/chat/templates",
+ )
+ setup_logging(config, flask_app, loglevel, quiet)
+ server = WebChatFlask()
+ server.set_config(config)
+ flask_app.route("/", methods=["GET"])(server.index)
+ flask_app.route("/stats", methods=["GET"])(server.stats)
+ # flask_app.route("/send-message", methods=["GET"])(server.send_message)
+ flask_app.route("/send-message-status", methods=["GET"])(server.send_message_status)
+
+ socketio = SocketIO(
+ flask_app, logger=False, engineio_logger=False,
+ async_mode="threading",
+ )
+ # async_mode="gevent",
+ # async_mode="eventlet",
+ # import eventlet
+ # eventlet.monkey_patch()
+
+ socketio.on_namespace(
+ SendMessageNamespace(
+ "/sendmsg", config=config,
+ msg_queues=msg_queues,
+ ),
+ )
+ return socketio, flask_app
+
+
+# main() ###
+@cli.command()
+@cli_helper.add_options(cli_helper.common_options)
+@click.option(
+ "-f",
+ "--flush",
+ "flush",
+ is_flag=True,
+ show_default=True,
+ default=False,
+ help="Flush out all old aged messages on disk.",
+)
+@click.option(
+ "-p",
+ "--port",
+ "port",
+ show_default=True,
+ default=80,
+ help="Port to listen to web requests",
+)
+@click.pass_context
+@cli_helper.process_standard_options
+def webchat(ctx, flush, port):
+ """Web based HAM Radio chat program!"""
+ ctx.obj["config_file"]
+ loglevel = ctx.obj["loglevel"]
+ quiet = ctx.obj["quiet"]
+ config = ctx.obj["config"]
+
+ signal.signal(signal.SIGINT, aprsd_main.signal_handler)
+ signal.signal(signal.SIGTERM, aprsd_main.signal_handler)
+
+ if not quiet:
+ click.echo("Load config")
+
+ level, msg = utils._check_version()
+ if level:
+ LOG.warning(msg)
+ else:
+ LOG.info(msg)
+ LOG.info(f"APRSD Started version: {aprsd.__version__}")
+
+ flat_config = utils.flatten_dict(config)
+ LOG.info("Using CONFIG values:")
+ for x in flat_config:
+ if "password" in x or "aprsd.web.users.admin" in x:
+ LOG.info(f"{x} = XXXXXXXXXXXXXXXXXXX")
+ else:
+ LOG.info(f"{x} = {flat_config[x]}")
+
+ stats.APRSDStats(config)
+
+ # Initialize the client factory and create
+ # The correct client object ready for use
+ client.ClientFactory.setup(config)
+ # Make sure we have 1 client transport enabled
+ if not client.factory.is_client_enabled():
+ LOG.error("No Clients are enabled in config.")
+ sys.exit(-1)
+
+ if not client.factory.is_client_configured():
+ LOG.error("APRS client is not properly configured in config file.")
+ sys.exit(-1)
+
+ packets.PacketList(config=config)
+ messaging.MsgTrack(config=config)
+ packets.WatchList(config=config)
+ packets.SeenList(config=config)
+
+ aprsd_main.flask_enabled = True
+ (socketio, app) = init_flask(config, loglevel, quiet)
+ rx_thread = WebChatRXThread(
+ msg_queues=msg_queues,
+ config=config,
+ )
+ LOG.warning("Start RX Thread")
+ rx_thread.start()
+ tx_thread = WebChatTXThread(
+ msg_queues=msg_queues,
+ config=config,
+ socketio=socketio,
+ )
+ LOG.warning("Start TX Thread")
+ tx_thread.start()
+
+ keepalive = threads.KeepAliveThread(config=config)
+ LOG.warning("Start KeepAliveThread")
+ keepalive.start()
+ LOG.warning("Start socketio.run()")
+ socketio.run(
+ app,
+ host=config["aprsd"]["web"]["host"],
+ port=port,
+ )
diff --git a/aprsd/flask.py b/aprsd/flask.py
index 0be0667..21de182 100644
--- a/aprsd/flask.py
+++ b/aprsd/flask.py
@@ -601,8 +601,8 @@ def init_flask(config, loglevel, quiet):
flask_app = flask.Flask(
"aprsd",
static_url_path="/static",
- static_folder="web/static",
- template_folder="web/templates",
+ static_folder="web/admin/static",
+ template_folder="web/admin/templates",
)
setup_logging(config, flask_app, loglevel, quiet)
server = APRSDFlask()
diff --git a/aprsd/messaging.py b/aprsd/messaging.py
index adb3829..4c3643f 100644
--- a/aprsd/messaging.py
+++ b/aprsd/messaging.py
@@ -6,7 +6,8 @@ import re
import threading
import time
-from aprsd import client, objectstore, packets, stats, threads
+from aprsd import client, packets, stats, threads
+from aprsd.utils import objectstore
LOG = logging.getLogger("APRSD")
@@ -238,7 +239,10 @@ class RawMessage(Message):
last_send_age = last_send_time = None
def __init__(self, message, allow_delay=True):
- super().__init__(fromcall=None, tocall=None, msg_id=None, allow_delay=allow_delay)
+ super().__init__(
+ fromcall=None, tocall=None, msg_id=None,
+ allow_delay=allow_delay,
+ )
self._raw_message = message
def dict(self):
@@ -282,12 +286,8 @@ class TextMessage(Message):
last_send_time = last_send_age = None
def __init__(
- self,
- fromcall,
- tocall,
- message,
- msg_id=None,
- allow_delay=True,
+ self, fromcall, tocall, message,
+ msg_id=None, allow_delay=True,
):
super().__init__(
fromcall=fromcall, tocall=tocall,
diff --git a/aprsd/packets.py b/aprsd/packets.py
index b7e6ecc..f7d7b11 100644
--- a/aprsd/packets.py
+++ b/aprsd/packets.py
@@ -3,7 +3,8 @@ import logging
import threading
import time
-from aprsd import objectstore, utils
+from aprsd import utils
+from aprsd.utils import objectstore
LOG = logging.getLogger("APRSD")
@@ -77,9 +78,10 @@ class WatchList(objectstore.ObjectStoreMixin):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.lock = threading.Lock()
- cls._instance.config = kwargs["config"]
+ if "config" in kwargs:
+ cls._instance.config = kwargs["config"]
+ cls._instance._init_store()
cls._instance.data = {}
- cls._instance._init_store()
return cls._instance
def __init__(self, config=None):
@@ -165,9 +167,10 @@ class SeenList(objectstore.ObjectStoreMixin):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.lock = threading.Lock()
- cls._instance.config = kwargs["config"]
+ if "config" in kwargs:
+ cls._instance.config = kwargs["config"]
+ cls._instance._init_store()
cls._instance.data = {}
- cls._instance._init_store()
return cls._instance
def update_seen(self, packet):
diff --git a/aprsd/plugin.py b/aprsd/plugin.py
index 7a6aff1..0564085 100644
--- a/aprsd/plugin.py
+++ b/aprsd/plugin.py
@@ -492,4 +492,5 @@ class PluginManager:
self._pluggy_pm.register(obj)
def get_plugins(self):
- return self._pluggy_pm.get_plugins()
+ if self._pluggy_pm:
+ return self._pluggy_pm.get_plugins()
diff --git a/aprsd/stats.py b/aprsd/stats.py
index e629157..8562fc3 100644
--- a/aprsd/stats.py
+++ b/aprsd/stats.py
@@ -2,6 +2,8 @@ import datetime
import logging
import threading
+import wrapt
+
import aprsd
from aprsd import packets, plugin, utils
@@ -12,7 +14,7 @@ LOG = logging.getLogger("APRSD")
class APRSDStats:
_instance = None
- lock = None
+ lock = threading.Lock()
config = None
start_time = None
@@ -39,7 +41,6 @@ class APRSDStats:
if cls._instance is None:
cls._instance = super().__new__(cls)
# any initializetion here
- cls._instance.lock = threading.Lock()
cls._instance.start_time = datetime.datetime.now()
cls._instance._aprsis_keepalive = datetime.datetime.now()
return cls._instance
@@ -48,128 +49,129 @@ class APRSDStats:
if config:
self.config = config
+ @wrapt.synchronized(lock)
@property
def uptime(self):
- with self.lock:
- return datetime.datetime.now() - self.start_time
+ return datetime.datetime.now() - self.start_time
+ @wrapt.synchronized(lock)
@property
def memory(self):
- with self.lock:
- return self._mem_current
+ return self._mem_current
+ @wrapt.synchronized(lock)
def set_memory(self, memory):
- with self.lock:
- self._mem_current = memory
+ self._mem_current = memory
+ @wrapt.synchronized(lock)
@property
def memory_peak(self):
- with self.lock:
- return self._mem_peak
+ return self._mem_peak
+ @wrapt.synchronized(lock)
def set_memory_peak(self, memory):
- with self.lock:
- self._mem_peak = memory
+ self._mem_peak = memory
+ @wrapt.synchronized(lock)
@property
def aprsis_server(self):
- with self.lock:
- return self._aprsis_server
+ return self._aprsis_server
+ @wrapt.synchronized(lock)
def set_aprsis_server(self, server):
- with self.lock:
- self._aprsis_server = server
+ self._aprsis_server = server
+ @wrapt.synchronized(lock)
@property
def aprsis_keepalive(self):
- with self.lock:
- return self._aprsis_keepalive
+ return self._aprsis_keepalive
+ @wrapt.synchronized(lock)
def set_aprsis_keepalive(self):
- with self.lock:
- self._aprsis_keepalive = datetime.datetime.now()
+ self._aprsis_keepalive = datetime.datetime.now()
+ @wrapt.synchronized(lock)
@property
def msgs_tx(self):
- with self.lock:
- return self._msgs_tx
+ return self._msgs_tx
+ @wrapt.synchronized(lock)
def msgs_tx_inc(self):
- with self.lock:
- self._msgs_tx += 1
+ self._msgs_tx += 1
+ @wrapt.synchronized(lock)
@property
def msgs_rx(self):
- with self.lock:
- return self._msgs_rx
+ return self._msgs_rx
+ @wrapt.synchronized(lock)
def msgs_rx_inc(self):
- with self.lock:
- self._msgs_rx += 1
+ self._msgs_rx += 1
+ @wrapt.synchronized(lock)
@property
def msgs_mice_rx(self):
- with self.lock:
- return self._msgs_mice_rx
+ return self._msgs_mice_rx
+ @wrapt.synchronized(lock)
def msgs_mice_inc(self):
- with self.lock:
- self._msgs_mice_rx += 1
+ self._msgs_mice_rx += 1
+ @wrapt.synchronized(lock)
@property
def ack_tx(self):
- with self.lock:
- return self._ack_tx
+ return self._ack_tx
+ @wrapt.synchronized(lock)
def ack_tx_inc(self):
- with self.lock:
- self._ack_tx += 1
+ self._ack_tx += 1
+ @wrapt.synchronized(lock)
@property
def ack_rx(self):
- with self.lock:
- return self._ack_rx
+ return self._ack_rx
+ @wrapt.synchronized(lock)
def ack_rx_inc(self):
- with self.lock:
- self._ack_rx += 1
+ self._ack_rx += 1
+ @wrapt.synchronized(lock)
@property
def msgs_tracked(self):
- with self.lock:
- return self._msgs_tracked
+ return self._msgs_tracked
+ @wrapt.synchronized(lock)
def msgs_tracked_inc(self):
- with self.lock:
- self._msgs_tracked += 1
+ self._msgs_tracked += 1
+ @wrapt.synchronized(lock)
@property
def email_tx(self):
- with self.lock:
- return self._email_tx
+ return self._email_tx
+ @wrapt.synchronized(lock)
def email_tx_inc(self):
- with self.lock:
- self._email_tx += 1
+ self._email_tx += 1
+ @wrapt.synchronized(lock)
@property
def email_rx(self):
- with self.lock:
- return self._email_rx
+ return self._email_rx
+ @wrapt.synchronized(lock)
def email_rx_inc(self):
- with self.lock:
- self._email_rx += 1
+ self._email_rx += 1
+ @wrapt.synchronized(lock)
@property
def email_thread_time(self):
- with self.lock:
- return self._email_thread_last_time
+ return self._email_thread_last_time
+ @wrapt.synchronized(lock)
def email_thread_update(self):
- with self.lock:
- self._email_thread_last_time = datetime.datetime.now()
+ self._email_thread_last_time = datetime.datetime.now()
+ @wrapt.synchronized(lock)
def stats(self):
now = datetime.datetime.now()
if self._email_thread_last_time:
@@ -185,20 +187,20 @@ class APRSDStats:
pm = plugin.PluginManager()
plugins = pm.get_plugins()
plugin_stats = {}
+ if plugins:
+ def full_name_with_qualname(obj):
+ return "{}.{}".format(
+ obj.__class__.__module__,
+ obj.__class__.__qualname__,
+ )
- def full_name_with_qualname(obj):
- return "{}.{}".format(
- obj.__class__.__module__,
- obj.__class__.__qualname__,
- )
-
- for p in plugins:
- plugin_stats[full_name_with_qualname(p)] = {
- "enabled": p.enabled,
- "rx": p.rx_count,
- "tx": p.tx_count,
- "version": p.version,
- }
+ for p in plugins:
+ plugin_stats[full_name_with_qualname(p)] = {
+ "enabled": p.enabled,
+ "rx": p.rx_count,
+ "tx": p.tx_count,
+ "version": p.version,
+ }
wl = packets.WatchList()
sl = packets.SeenList()
@@ -207,30 +209,30 @@ class APRSDStats:
"aprsd": {
"version": aprsd.__version__,
"uptime": utils.strfdelta(self.uptime),
- "memory_current": self.memory,
+ "memory_current": int(self.memory),
"memory_current_str": utils.human_size(self.memory),
- "memory_peak": self.memory_peak,
+ "memory_peak": int(self.memory_peak),
"memory_peak_str": utils.human_size(self.memory_peak),
"watch_list": wl.get_all(),
"seen_list": sl.get_all(),
},
"aprs-is": {
- "server": self.aprsis_server,
+ "server": str(self.aprsis_server),
"callsign": self.config["aprs"]["login"],
"last_update": last_aprsis_keepalive,
},
"messages": {
- "tracked": self.msgs_tracked,
- "sent": self.msgs_tx,
- "recieved": self.msgs_rx,
- "ack_sent": self.ack_tx,
- "ack_recieved": self.ack_rx,
- "mic-e recieved": self.msgs_mice_rx,
+ "tracked": int(self.msgs_tracked),
+ "sent": int(self.msgs_tx),
+ "recieved": int(self.msgs_rx),
+ "ack_sent": int(self.ack_tx),
+ "ack_recieved": int(self.ack_rx),
+ "mic-e recieved": int(self.msgs_mice_rx),
},
"email": {
"enabled": self.config["aprsd"]["email"]["enabled"],
- "sent": self._email_tx,
- "recieved": self._email_rx,
+ "sent": int(self._email_tx),
+ "recieved": int(self._email_rx),
"thread_last_update": last_update,
},
"plugins": plugin_stats,
diff --git a/aprsd/threads/keep_alive.py b/aprsd/threads/keep_alive.py
index cc96f67..3b70eeb 100644
--- a/aprsd/threads/keep_alive.py
+++ b/aprsd/threads/keep_alive.py
@@ -73,7 +73,7 @@ class KeepAliveThread(APRSDThread):
# We haven't gotten a keepalive from aprs-is in a while
# reset the connection.a
if not client.KISSClient.is_enabled(self.config):
- LOG.warning("Resetting connection to APRS-IS.")
+ LOG.warning(f"Resetting connection to APRS-IS {delta}")
client.factory.create().reset()
# Check version every hour
diff --git a/aprsd/threads/rx.py b/aprsd/threads/rx.py
index d0b8617..5046e20 100644
--- a/aprsd/threads/rx.py
+++ b/aprsd/threads/rx.py
@@ -1,3 +1,4 @@
+import abc
import logging
import time
@@ -38,7 +39,10 @@ class APRSDRXThread(APRSDThread):
self.process_packet, raw=False, blocking=False,
)
- except aprslib.exceptions.ConnectionDrop:
+ except (
+ aprslib.exceptions.ConnectionDrop,
+ aprslib.exceptions.ConnectionError,
+ ):
LOG.error("Connection dropped, reconnecting")
time.sleep(5)
# Force the deletion of the client object connected to aprs
@@ -48,6 +52,12 @@ class APRSDRXThread(APRSDThread):
# Continue to loop
return True
+ @abc.abstractmethod
+ def process_packet(self, *args, **kwargs):
+ pass
+
+
+class APRSDPluginRXThread(APRSDRXThread):
def process_packet(self, *args, **kwargs):
packet = self._client.decode_packet(*args, **kwargs)
thread = APRSDProcessPacketThread(packet=packet, config=self.config)
diff --git a/aprsd/objectstore.py b/aprsd/utils/objectstore.py
similarity index 100%
rename from aprsd/objectstore.py
rename to aprsd/utils/objectstore.py
diff --git a/aprsd/web/__init__.py b/aprsd/web/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/aprsd/web/admin/__init__.py b/aprsd/web/admin/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/aprsd/web/static/css/index.css b/aprsd/web/admin/static/css/index.css
similarity index 100%
rename from aprsd/web/static/css/index.css
rename to aprsd/web/admin/static/css/index.css
diff --git a/aprsd/web/static/css/prism.css b/aprsd/web/admin/static/css/prism.css
similarity index 100%
rename from aprsd/web/static/css/prism.css
rename to aprsd/web/admin/static/css/prism.css
diff --git a/aprsd/web/static/css/tabs.css b/aprsd/web/admin/static/css/tabs.css
similarity index 100%
rename from aprsd/web/static/css/tabs.css
rename to aprsd/web/admin/static/css/tabs.css
diff --git a/aprsd/web/admin/static/images/Untitled.png b/aprsd/web/admin/static/images/Untitled.png
new file mode 100644
index 0000000..666fbc4
Binary files /dev/null and b/aprsd/web/admin/static/images/Untitled.png differ
diff --git a/aprsd/web/static/images/aprs-symbols-16-0.png b/aprsd/web/admin/static/images/aprs-symbols-16-0.png
similarity index 100%
rename from aprsd/web/static/images/aprs-symbols-16-0.png
rename to aprsd/web/admin/static/images/aprs-symbols-16-0.png
diff --git a/aprsd/web/static/images/aprs-symbols-16-1.png b/aprsd/web/admin/static/images/aprs-symbols-16-1.png
similarity index 100%
rename from aprsd/web/static/images/aprs-symbols-16-1.png
rename to aprsd/web/admin/static/images/aprs-symbols-16-1.png
diff --git a/aprsd/web/static/images/aprs-symbols-64-0.png b/aprsd/web/admin/static/images/aprs-symbols-64-0.png
similarity index 100%
rename from aprsd/web/static/images/aprs-symbols-64-0.png
rename to aprsd/web/admin/static/images/aprs-symbols-64-0.png
diff --git a/aprsd/web/static/images/aprs-symbols-64-1.png b/aprsd/web/admin/static/images/aprs-symbols-64-1.png
similarity index 100%
rename from aprsd/web/static/images/aprs-symbols-64-1.png
rename to aprsd/web/admin/static/images/aprs-symbols-64-1.png
diff --git a/aprsd/web/static/images/aprs-symbols-64-2.png b/aprsd/web/admin/static/images/aprs-symbols-64-2.png
similarity index 100%
rename from aprsd/web/static/images/aprs-symbols-64-2.png
rename to aprsd/web/admin/static/images/aprs-symbols-64-2.png
diff --git a/aprsd/web/static/js/charts.js b/aprsd/web/admin/static/js/charts.js
similarity index 100%
rename from aprsd/web/static/js/charts.js
rename to aprsd/web/admin/static/js/charts.js
diff --git a/aprsd/web/static/js/logs.js b/aprsd/web/admin/static/js/logs.js
similarity index 100%
rename from aprsd/web/static/js/logs.js
rename to aprsd/web/admin/static/js/logs.js
diff --git a/aprsd/web/static/js/main.js b/aprsd/web/admin/static/js/main.js
similarity index 100%
rename from aprsd/web/static/js/main.js
rename to aprsd/web/admin/static/js/main.js
diff --git a/aprsd/web/static/js/prism.js b/aprsd/web/admin/static/js/prism.js
similarity index 100%
rename from aprsd/web/static/js/prism.js
rename to aprsd/web/admin/static/js/prism.js
diff --git a/aprsd/web/static/js/send-message.js b/aprsd/web/admin/static/js/send-message.js
similarity index 100%
rename from aprsd/web/static/js/send-message.js
rename to aprsd/web/admin/static/js/send-message.js
diff --git a/aprsd/web/static/js/tabs.js b/aprsd/web/admin/static/js/tabs.js
similarity index 100%
rename from aprsd/web/static/js/tabs.js
rename to aprsd/web/admin/static/js/tabs.js
diff --git a/aprsd/web/static/json-viewer/jquery.json-viewer.css b/aprsd/web/admin/static/json-viewer/jquery.json-viewer.css
similarity index 100%
rename from aprsd/web/static/json-viewer/jquery.json-viewer.css
rename to aprsd/web/admin/static/json-viewer/jquery.json-viewer.css
diff --git a/aprsd/web/static/json-viewer/jquery.json-viewer.js b/aprsd/web/admin/static/json-viewer/jquery.json-viewer.js
similarity index 100%
rename from aprsd/web/static/json-viewer/jquery.json-viewer.js
rename to aprsd/web/admin/static/json-viewer/jquery.json-viewer.js
diff --git a/aprsd/web/templates/index.html b/aprsd/web/admin/templates/index.html
similarity index 100%
rename from aprsd/web/templates/index.html
rename to aprsd/web/admin/templates/index.html
diff --git a/aprsd/web/templates/messages.html b/aprsd/web/admin/templates/messages.html
similarity index 100%
rename from aprsd/web/templates/messages.html
rename to aprsd/web/admin/templates/messages.html
diff --git a/aprsd/web/templates/send-message.html b/aprsd/web/admin/templates/send-message.html
similarity index 100%
rename from aprsd/web/templates/send-message.html
rename to aprsd/web/admin/templates/send-message.html
diff --git a/aprsd/web/chat/static/css/index.css b/aprsd/web/chat/static/css/index.css
new file mode 100644
index 0000000..b39acab
--- /dev/null
+++ b/aprsd/web/chat/static/css/index.css
@@ -0,0 +1,94 @@
+body {
+ background: #eeeeee;
+ margin: 2em;
+ text-align: center;
+ font-family: system-ui, sans-serif;
+}
+
+footer {
+ padding: 2em;
+ text-align: center;
+ height: 10vh;
+}
+
+.ui.segment {
+ background: #eeeeee;
+}
+
+ul.list {
+ list-style-type: disc;
+}
+ul.list li {
+ list-style-position: outside;
+}
+
+#left {
+ margin-right: 2px;
+ height: 300px;
+}
+#right {
+ height: 300px;
+}
+#center {
+ height: 300px;
+}
+
+#title {
+ font-size: 4em;
+}
+#version{
+ font-size: .5em;
+}
+#uptime, #aprsis {
+ font-size: 1em;
+}
+#callsign {
+ font-size: 1.4em;
+ color: #00F;
+ padding-top: 8px;
+ margin:10px;
+}
+
+#title_rx {
+ background-color: darkseagreen;
+ text-align: left;
+}
+
+#title_tx {
+ background-color: lightcoral;
+ text-align: left;
+}
+
+.aprsd_1 {
+ background-image: url(/static/images/aprs-symbols-16-0.png);
+ background-repeat: no-repeat;
+ background-position: -160px -48px;
+ width: 16px;
+ height: 16px;
+}
+
+#msgsTabsDiv .ui.tab {
+ margin:0px;
+ padding:0px;
+ display: block;
+}
+
+#msgsTabsDiv .header, .tiny.text, .content, .break,
+ .thumbs.down.outline.icon,
+ .phone.volume.icon
+ {
+ display: inline-block;
+ float: left;
+ position: relative;
+}
+#msgsTabsDiv .tiny.text {
+ width:100px;
+}
+#msgsTabsDiv .tiny.header {
+ width:100px;
+ text-align: left;
+}
+#msgsTabsDiv .break {
+ margin: 2px;
+ text-align: left;
+}
diff --git a/aprsd/web/chat/static/css/style.css.map b/aprsd/web/chat/static/css/style.css.map
new file mode 100644
index 0000000..299ffa9
--- /dev/null
+++ b/aprsd/web/chat/static/css/style.css.map
@@ -0,0 +1 @@
+{"version":3,"file":"css/style.css","mappings":"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AC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,UAAa,CACzB,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,UAAa,CACzB,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,aAAgB,CAC5B,UAAY,UAAa,CACzB,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,wBAA2B,CACvC,UAAY,o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","sources":["webpack://thelounge/./node_modules/normalize.css/normalize.css","webpack://thelounge/./client/css/fontawesome.css","webpack://thelounge/./node_modules/primer-tooltips/build/build.css","webpack://thelounge/./client/css/style.css","webpack://thelounge/./client/components/NetworkList.vue","webpack://thelounge/./client/components/ConfirmDialog.vue","webpack://thelounge/./client/components/Mentions.vue","webpack://thelounge/./client/components/NetworkForm.vue","webpack://thelounge/./client/components/Session.vue","webpack://thelounge/./client/components/Windows/Settings.vue","webpack://thelounge/./client/components/MessageSearchForm.vue","webpack://thelounge/./client/components/Windows/SearchResults.vue"],"sourcesContent":["/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */\n\n/* Document\n ========================================================================== */\n\n/**\n * 1. Correct the line height in all browsers.\n * 2. Prevent adjustments of font size after orientation changes in iOS.\n */\n\nhtml {\n line-height: 1.15; /* 1 */\n -webkit-text-size-adjust: 100%; /* 2 */\n}\n\n/* Sections\n ========================================================================== */\n\n/**\n * Remove the margin in all browsers.\n */\n\nbody {\n margin: 0;\n}\n\n/**\n * Render the `main` element consistently in IE.\n */\n\nmain {\n display: block;\n}\n\n/**\n * Correct the font size and margin on `h1` elements within `section` and\n * `article` contexts in Chrome, Firefox, and Safari.\n */\n\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\n\n/* Grouping content\n ========================================================================== */\n\n/**\n * 1. Add the correct box sizing in Firefox.\n * 2. Show the overflow in Edge and IE.\n */\n\nhr {\n box-sizing: content-box; /* 1 */\n height: 0; /* 1 */\n overflow: visible; /* 2 */\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\npre {\n font-family: monospace, monospace; /* 1 */\n font-size: 1em; /* 2 */\n}\n\n/* Text-level semantics\n ========================================================================== */\n\n/**\n * Remove the gray background on active links in IE 10.\n */\n\na {\n background-color: transparent;\n}\n\n/**\n * 1. Remove the bottom border in Chrome 57-\n * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n */\n\nabbr[title] {\n border-bottom: none; /* 1 */\n text-decoration: underline; /* 2 */\n text-decoration: underline dotted; /* 2 */\n}\n\n/**\n * Add the correct font weight in Chrome, Edge, and Safari.\n */\n\nb,\nstrong {\n font-weight: bolder;\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\ncode,\nkbd,\nsamp {\n font-family: monospace, monospace; /* 1 */\n font-size: 1em; /* 2 */\n}\n\n/**\n * Add the correct font size in all browsers.\n */\n\nsmall {\n font-size: 80%;\n}\n\n/**\n * Prevent `sub` and `sup` elements from affecting the line height in\n * all browsers.\n */\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsub {\n bottom: -0.25em;\n}\n\nsup {\n top: -0.5em;\n}\n\n/* Embedded content\n ========================================================================== */\n\n/**\n * Remove the border on images inside links in IE 10.\n */\n\nimg {\n border-style: none;\n}\n\n/* Forms\n ========================================================================== */\n\n/**\n * 1. Change the font styles in all browsers.\n * 2. Remove the margin in Firefox and Safari.\n */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n font-family: inherit; /* 1 */\n font-size: 100%; /* 1 */\n line-height: 1.15; /* 1 */\n margin: 0; /* 2 */\n}\n\n/**\n * Show the overflow in IE.\n * 1. Show the overflow in Edge.\n */\n\nbutton,\ninput { /* 1 */\n overflow: visible;\n}\n\n/**\n * Remove the inheritance of text transform in Edge, Firefox, and IE.\n * 1. Remove the inheritance of text transform in Firefox.\n */\n\nbutton,\nselect { /* 1 */\n text-transform: none;\n}\n\n/**\n * Correct the inability to style clickable types in iOS and Safari.\n */\n\nbutton,\n[type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button;\n}\n\n/**\n * Remove the inner border and padding in Firefox.\n */\n\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n border-style: none;\n padding: 0;\n}\n\n/**\n * Restore the focus styles unset by the previous rule.\n */\n\nbutton:-moz-focusring,\n[type=\"button\"]:-moz-focusring,\n[type=\"reset\"]:-moz-focusring,\n[type=\"submit\"]:-moz-focusring {\n outline: 1px dotted ButtonText;\n}\n\n/**\n * Correct the padding in Firefox.\n */\n\nfieldset {\n padding: 0.35em 0.75em 0.625em;\n}\n\n/**\n * 1. Correct the text wrapping in Edge and IE.\n * 2. Correct the color inheritance from `fieldset` elements in IE.\n * 3. Remove the padding so developers are not caught out when they zero out\n * `fieldset` elements in all browsers.\n */\n\nlegend {\n box-sizing: border-box; /* 1 */\n color: inherit; /* 2 */\n display: table; /* 1 */\n max-width: 100%; /* 1 */\n padding: 0; /* 3 */\n white-space: normal; /* 1 */\n}\n\n/**\n * Add the correct vertical alignment in Chrome, Firefox, and Opera.\n */\n\nprogress {\n vertical-align: baseline;\n}\n\n/**\n * Remove the default vertical scrollbar in IE 10+.\n */\n\ntextarea {\n overflow: auto;\n}\n\n/**\n * 1. Add the correct box sizing in IE 10.\n * 2. Remove the padding in IE 10.\n */\n\n[type=\"checkbox\"],\n[type=\"radio\"] {\n box-sizing: border-box; /* 1 */\n padding: 0; /* 2 */\n}\n\n/**\n * Correct the cursor style of increment and decrement buttons in Chrome.\n */\n\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n/**\n * 1. Correct the odd appearance in Chrome and Safari.\n * 2. Correct the outline style in Safari.\n */\n\n[type=\"search\"] {\n -webkit-appearance: textfield; /* 1 */\n outline-offset: -2px; /* 2 */\n}\n\n/**\n * Remove the inner padding in Chrome and Safari on macOS.\n */\n\n[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n/**\n * 1. Correct the inability to style clickable types in iOS and Safari.\n * 2. Change font properties to `inherit` in Safari.\n */\n\n::-webkit-file-upload-button {\n -webkit-appearance: button; /* 1 */\n font: inherit; /* 2 */\n}\n\n/* Interactive\n ========================================================================== */\n\n/*\n * Add the correct display in Edge, IE 10+, and Firefox.\n */\n\ndetails {\n display: block;\n}\n\n/*\n * Add the correct display in all browsers.\n */\n\nsummary {\n display: list-item;\n}\n\n/* Misc\n ========================================================================== */\n\n/**\n * Add the correct display in IE 10+.\n */\n\ntemplate {\n display: none;\n}\n\n/**\n * Add the correct display in IE 10.\n */\n\n[hidden] {\n display: none;\n}\n","@font-face {\n\t/* We use free solid icons - https://fontawesome.com/icons?s=solid&m=free */\n\tfont-family: FontAwesome;\n\tfont-weight: normal;\n\tfont-style: normal;\n\tsrc:\n\t\turl(\"../fonts/fa-solid-900.woff2\") format(\"woff2\"),\n\t\turl(\"../fonts/fa-solid-900.woff\") format(\"woff\");\n}\n",".tooltipped{position:relative}.tooltipped::after{position:absolute;z-index:1000000;display:none;padding:.5em .75em;font:normal normal 11px/1.5 -apple-system,BlinkMacSystemFont,\"Segoe UI\",Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\";-webkit-font-smoothing:subpixel-antialiased;color:#fff;text-align:center;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-wrap:break-word;white-space:pre;pointer-events:none;content:attr(aria-label);background:#1b1f23;border-radius:3px;opacity:0}.tooltipped::before{position:absolute;z-index:1000001;display:none;width:0;height:0;color:#1b1f23;pointer-events:none;content:\"\";border:6px solid transparent;opacity:0}@keyframes tooltip-appear{from{opacity:0}to{opacity:1}}.tooltipped:hover::before,.tooltipped:hover::after,.tooltipped:active::before,.tooltipped:active::after,.tooltipped:focus::before,.tooltipped:focus::after{display:inline-block;text-decoration:none;animation-name:tooltip-appear;animation-duration:.1s;animation-fill-mode:forwards;animation-timing-function:ease-in;animation-delay:.4s}.tooltipped-no-delay:hover::before,.tooltipped-no-delay:hover::after,.tooltipped-no-delay:active::before,.tooltipped-no-delay:active::after,.tooltipped-no-delay:focus::before,.tooltipped-no-delay:focus::after{animation-delay:0s}.tooltipped-multiline:hover::after,.tooltipped-multiline:active::after,.tooltipped-multiline:focus::after{display:table-cell}.tooltipped-s::after,.tooltipped-se::after,.tooltipped-sw::after{top:100%;right:50%;margin-top:6px}.tooltipped-s::before,.tooltipped-se::before,.tooltipped-sw::before{top:auto;right:50%;bottom:-7px;margin-right:-6px;border-bottom-color:#1b1f23}.tooltipped-se::after{right:auto;left:50%;margin-left:-16px}.tooltipped-sw::after{margin-right:-16px}.tooltipped-n::after,.tooltipped-ne::after,.tooltipped-nw::after{right:50%;bottom:100%;margin-bottom:6px}.tooltipped-n::before,.tooltipped-ne::before,.tooltipped-nw::before{top:-7px;right:50%;bottom:auto;margin-right:-6px;border-top-color:#1b1f23}.tooltipped-ne::after{right:auto;left:50%;margin-left:-16px}.tooltipped-nw::after{margin-right:-16px}.tooltipped-s::after,.tooltipped-n::after{transform:translateX(50%)}.tooltipped-w::after{right:100%;bottom:50%;margin-right:6px;transform:translateY(50%)}.tooltipped-w::before{top:50%;bottom:50%;left:-7px;margin-top:-6px;border-left-color:#1b1f23}.tooltipped-e::after{bottom:50%;left:100%;margin-left:6px;transform:translateY(50%)}.tooltipped-e::before{top:50%;right:-7px;bottom:50%;margin-top:-6px;border-right-color:#1b1f23}.tooltipped-align-right-1::after,.tooltipped-align-right-2::after{right:0;margin-right:0}.tooltipped-align-right-1::before{right:10px}.tooltipped-align-right-2::before{right:15px}.tooltipped-align-left-1::after,.tooltipped-align-left-2::after{left:0;margin-left:0}.tooltipped-align-left-1::before{left:5px}.tooltipped-align-left-2::before{left:10px}.tooltipped-multiline::after{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:250px;word-wrap:break-word;white-space:pre-line;border-collapse:separate}.tooltipped-multiline.tooltipped-s::after,.tooltipped-multiline.tooltipped-n::after{right:auto;left:50%;transform:translateX(-50%)}.tooltipped-multiline.tooltipped-w::after,.tooltipped-multiline.tooltipped-e::after{right:100%}@media screen and (min-width: 0\\0){.tooltipped-multiline::after{width:250px}}.tooltipped-sticky::before,.tooltipped-sticky::after{display:inline-block}.tooltipped-sticky.tooltipped-multiline::after{display:table-cell}\n","@import \"../../node_modules/normalize.css/normalize.css\";\n@import \"fontawesome.css\";\n@import \"../../node_modules/primer-tooltips/build/build.css\";\n\n:root {\n\t/* Main text color */\n\t--body-color: #222;\n\n\t/* Secondary text color, dimmed. Make sure to keep contrast WCAG 2.0 AA compliant on var(--window-bg-color) */\n\t--body-color-muted: #767676;\n\n\t/* Background color of the whole page */\n\t--body-bg-color: #415364;\n\n\t/* Main button color. Applies to border, text, and background on hover */\n\t--button-color: #84ce88;\n\t--button-text-color-hover: #fff;\n\n\t/* Color for sidebar overlay and other things that dim the viewport when something else is on top */\n\t--overlay-bg-color: rgb(0 0 0 / 50%);\n\n\t/* Links and link-looking buttons */\n\t--link-color: #50a656;\n\n\t/* Background color of the main window */\n\t--window-bg-color: #fff;\n\n\t/* Text color for
and headings in windows */\n\t--window-heading-color: #6c797a;\n\n\t/* Color of the date marker, text and separator */\n\t--date-marker-color: rgb(0 107 59 / 50%);\n\n\t/* Color of the unread message marker, text and separator */\n\t--unread-marker-color: rgb(231 76 60 / 50%);\n\n\t/* Background and left-border color of highlight messages */\n\t--highlight-bg-color: #efe8dc;\n\t--highlight-border-color: #b08c4f;\n\n\t/* Color of the progress bar that appears as a file is being uploaded to the server. Defaults to button color */\n\t--upload-progressbar-color: var(--button-color);\n}\n\n::placeholder {\n\tcolor: rgb(0 0 0 / 35%);\n\topacity: 1; /* fix opacity in Firefox */\n}\n\nhtml {\n\tbox-sizing: border-box;\n\t-webkit-tap-highlight-color: transparent; /* remove tap highlight on touch devices */\n}\n\n*,\n*::before,\n*::after {\n\tbox-sizing: inherit;\n}\n\ninput,\nbutton,\nselect,\ntextarea {\n\tfont: inherit;\n\tcolor: inherit;\n}\n\nimg {\n\tvertical-align: middle;\n}\n\n.sr-only {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\tmargin: -1px;\n\tpadding: 0;\n\toverflow: hidden;\n\tclip: rect(0, 0, 0, 0);\n\tborder: 0;\n}\n\nabbr[title] {\n\tcursor: help;\n}\n\nhtml,\nbody {\n\theight: 100%;\n\toverscroll-behavior: none; /* prevent overscroll navigation actions */\n}\n\nbody {\n\tbackground: var(--body-bg-color);\n\tcolor: var(--body-color);\n\tfont: 16px -apple-system, system-ui, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n\tmargin: 0;\n\tuser-select: none;\n\tcursor: default;\n\ttouch-action: none;\n\n\t/**\n\t * Disable pull-to-refresh on mobile that conflicts with scrolling the message list.\n\t * See http://stackoverflow.com/a/29313685/1935861\n\t */\n\toverflow: hidden; /* iOS Safari requires overflow rather than overflow-y */\n}\n\nbody.force-no-select * {\n\tuser-select: none !important;\n}\n\na,\na:hover,\na:focus {\n\tcolor: var(--link-color);\n\ttext-decoration: none;\n}\n\na:hover {\n\ttext-decoration: underline;\n}\n\na:focus {\n\toutline: thin dotted;\n\toutline: 5px auto -webkit-focus-ring-color;\n\toutline-offset: -2px;\n}\n\nh1,\nh2,\nh3 {\n\tfont: inherit;\n\tline-height: inherit;\n\tmargin: 0;\n}\n\nbutton {\n\tborder: none;\n\tbackground: none;\n\tmargin: 0;\n\toutline: none;\n\tpadding: 0;\n\tuser-select: inherit;\n\tcursor: pointer;\n}\n\ncode,\npre,\n#chat .msg[data-type=\"monospace_block\"] .text,\n.irc-monospace,\ntextarea#user-specified-css-input {\n\tfont-family: Consolas, Menlo, Monaco, \"Lucida Console\", \"DejaVu Sans Mono\", \"Courier New\", monospace;\n}\n\ncode,\n.irc-monospace {\n\tfont-size: 13px;\n\tpadding: 2px 4px;\n\tcolor: #e74c3c;\n\tbackground-color: #f9f2f4;\n\tborder-radius: 2px;\n}\n\npre {\n\tdisplay: block;\n\tpadding: 9.5px;\n\tmargin: 0 0 10px;\n\tfont-size: 13px;\n\tline-height: 1.4286;\n\tcolor: #333;\n\tword-break: break-all;\n\tword-wrap: break-word;\n\tbackground-color: #f5f5f5;\n\tborder-radius: 4px;\n}\n\nkbd {\n\tdisplay: inline-block;\n\tfont-family: inherit;\n\tline-height: 1em;\n\tmin-width: 28px; /* Ensure 1-char keys have the same width */\n\tmargin: 0 1px;\n\tpadding: 4px 6px;\n\tcolor: #444;\n\ttext-align: center;\n\ttext-shadow: 0 1px 0 #fff;\n\tbackground-color: white;\n\tbackground-image: linear-gradient(180deg, rgb(0 0 0 / 5%), transparent);\n\tborder: 1px solid #bbb;\n\tborder-radius: 4px;\n\tbox-shadow: 0 2px 0 #bbb, inset 0 1px 1px #fff, inset 0 -1px 3px #ccc;\n}\n\np {\n\tmargin: 0 0 10px;\n}\n\n.btn {\n\tborder: 2px solid var(--button-color);\n\tborder-radius: 3px;\n\tcolor: var(--button-color);\n\tdisplay: inline-block;\n\tfont-size: 12px;\n\tfont-weight: bold;\n\tletter-spacing: 1px;\n\tmargin-bottom: 10px;\n\tpadding: 9px 17px;\n\ttext-transform: uppercase;\n\ttransition: background 0.2s, border-color 0.2s, color 0.2s, box-shadow 0.2s;\n\tword-spacing: 3px;\n\tcursor: pointer; /* This is useful for `