Exemplo n.º 1
0
    def test_len(self):
        """Test getting length of tracked messages."""
        track = self._clean_track()
        fromcall = "KFART"
        tocall = "KHELP"
        message = "somthing"
        msg = messaging.TextMessage(fromcall, tocall, message)
        track.add(msg)
        self.assertEqual(1, len(track))
        msg2 = messaging.TextMessage(tocall, fromcall, message)
        track.add(msg2)
        self.assertEqual(2, len(track))

        track.remove(msg.id)
        self.assertEqual(1, len(track))
Exemplo n.º 2
0
    def test_add(self):
        track = self._clean_track()
        fromcall = "KFART"
        tocall = "KHELP"
        message = "somthing"
        msg = messaging.TextMessage(fromcall, tocall, message)

        track.add(msg)
        self.assertEqual(msg, track.get(msg.id))
Exemplo n.º 3
0
    def test__resend(self, mock_send):
        """Test the _resend method."""
        track = self._clean_track()
        fromcall = "KFART"
        tocall = "KHELP"
        message = "somthing"
        msg = messaging.TextMessage(fromcall, tocall, message)
        msg.last_send_attempt = 3
        track.add(msg)

        track._resend(msg)
        msg.send.assert_called_with()
        self.assertEqual(0, msg.last_send_attempt)
Exemplo n.º 4
0
    def test_query_restart_delayed(self, mock_restart):
        track = messaging.MsgTrack()
        track.data = {}
        packet = fake.fake_packet(message="!4")
        query = query_plugin.QueryPlugin(self.config)

        expected = "No pending msgs to resend"
        actual = query.filter(packet)
        mock_restart.assert_not_called()
        self.assertEqual(expected, actual)
        mock_restart.reset_mock()

        # add a message
        msg = messaging.TextMessage(self.fromcall, "testing", self.ack)
        track.add(msg)
        actual = query.filter(packet)
        mock_restart.assert_called_once()
Exemplo n.º 5
0
    def on_send(self, data):
        global socketio
        LOG.debug(f"WS: on_send {data}")
        self.request = data
        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",
        )

        socketio.start_background_task(self._start, self._config, data, msg,
                                       self)
        LOG.warning("WS: on_send: exit")
Exemplo n.º 6
0
    def process(self, packet):
        LOG.info("NotifySeenPlugin")

        notify_callsign = self.config["aprsd"]["watch_list"]["alert_callsign"]
        fromcall = packet.get("from")

        wl = packets.WatchList()
        age = wl.age(fromcall)

        if wl.is_old(packet["from"]):
            LOG.info(
                "NOTIFY {} last seen {} max age={}".format(
                    fromcall,
                    age,
                    wl.max_delta(),
                ), )
            packet_type = packets.get_packet_type(packet)
            # we shouldn't notify the alert user that they are online.
            if fromcall != notify_callsign:
                msg = messaging.TextMessage(
                    self.config["aprs"]["login"],
                    notify_callsign,
                    f"{fromcall} was just seen by type:'{packet_type}'",
                    # We don't need to keep this around if it doesn't go thru
                    allow_delay=False,
                )
                return msg
            else:
                LOG.debug(
                    "fromcall and notify_callsign are the same, not notifying")
                return messaging.NULL_MESSAGE
        else:
            LOG.debug(
                "Not old enough to notify on callsign '{}' : {} < {}".format(
                    fromcall,
                    age,
                    wl.max_delta(),
                ), )
            return messaging.NULL_MESSAGE
Exemplo n.º 7
0
def send_message(
    ctx,
    aprs_login,
    aprs_password,
    no_ack,
    wait_response,
    raw,
    tocallsign,
    command,
):
    """Send a message to a callsign via APRS_IS."""
    global got_ack, got_response
    config = ctx.obj["config"]
    quiet = ctx.obj["quiet"]

    if not aprs_login:
        if not config.exists("aprs.login"):
            click.echo("Must set --aprs_login or APRS_LOGIN")
            ctx.exit(-1)
            return
    else:
        config["aprs"]["login"] = aprs_login

    if not aprs_password:
        if not config.exists("aprs.password"):
            click.echo("Must set --aprs-password or APRS_PASSWORD")
            ctx.exit(-1)
            return
    else:
        config["aprs"]["password"] = aprs_password

    LOG.info(f"APRSD LISTEN Started version: {aprsd.__version__}")
    if type(command) is tuple:
        command = " ".join(command)
    if not quiet:
        if raw:
            LOG.info(f"L'{aprs_login}' R'{raw}'")
        else:
            LOG.info(f"L'{aprs_login}' To'{tocallsign}' C'{command}'")

    packets.PacketList(config=config)
    packets.WatchList(config=config)
    packets.SeenList(config=config)

    got_ack = False
    got_response = False

    def rx_packet(packet):
        global got_ack, got_response
        # LOG.debug("Got packet back {}".format(packet))
        resp = packet.get("response", None)
        if resp == "ack":
            ack_num = packet.get("msgNo")
            LOG.info(f"We got ack for our sent message {ack_num}")
            messaging.log_packet(packet)
            got_ack = True
        else:
            message = packet.get("message_text", None)
            fromcall = packet["from"]
            msg_number = packet.get("msgNo", "0")
            messaging.log_message(
                "Received Message",
                packet["raw"],
                message,
                fromcall=fromcall,
                ack=msg_number,
            )
            got_response = True
            # Send the ack back?
            ack = messaging.AckMessage(
                config["aprs"]["login"],
                fromcall,
                msg_id=msg_number,
            )
            ack.send_direct()

        if got_ack:
            if wait_response:
                if got_response:
                    sys.exit(0)
            else:
                sys.exit(0)

    try:
        client.ClientFactory.setup(config)
        client.factory.create().client
    except LoginError:
        sys.exit(-1)

    # Send a message
    # then we setup a consumer to rx messages
    # We should get an ack back as well as a new message
    # we should bail after we get the ack and send an ack back for the
    # message
    if raw:
        msg = messaging.RawMessage(raw)
        msg.send_direct()
        sys.exit(0)
    else:
        msg = messaging.TextMessage(aprs_login, tocallsign, command)
    msg.send_direct()

    if no_ack:
        sys.exit(0)

    try:
        # This will register a packet consumer with aprslib
        # When new packets come in the consumer will process
        # the packet
        aprs_client = client.factory.create().client
        aprs_client.consumer(rx_packet, raw=False)
    except aprslib.exceptions.ConnectionDrop:
        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
        aprs_client.reset()
Exemplo n.º 8
0
    def test_restart_delayed(self, mock_send):
        """Test the _resend method."""
        track = self._clean_track()
        fromcall = "KFART"
        tocall = "KHELP"
        message1 = "something"
        message2 = "something another"
        message3 = "something another again"

        mock1_send = mock.MagicMock()
        mock2_send = mock.MagicMock()
        mock3_send = mock.MagicMock()

        msg1 = messaging.TextMessage(fromcall, tocall, message1)
        msg1.last_send_attempt = 3
        msg1.last_send_time = datetime.datetime.now()
        msg1.send = mock1_send
        track.add(msg1)

        msg2 = messaging.TextMessage(tocall, fromcall, message2)
        msg2.last_send_attempt = 3
        msg2.last_send_time = datetime.datetime.now()
        msg2.send = mock2_send
        track.add(msg2)

        track.restart_delayed(count=None)
        msg1.send.assert_called_once()
        self.assertEqual(0, msg1.last_send_attempt)
        msg2.send.assert_called_once()
        self.assertEqual(0, msg2.last_send_attempt)

        msg1.last_send_attempt = 3
        msg1.send.reset_mock()
        msg2.last_send_attempt = 3
        msg2.send.reset_mock()

        track.restart_delayed(count=1)
        msg1.send.assert_not_called()
        msg2.send.assert_called_once()
        self.assertEqual(3, msg1.last_send_attempt)
        self.assertEqual(0, msg2.last_send_attempt)

        msg3 = messaging.TextMessage(tocall, fromcall, message3)
        msg3.last_send_attempt = 3
        msg3.last_send_time = datetime.datetime.now()
        msg3.send = mock3_send
        track.add(msg3)

        msg1.last_send_attempt = 3
        msg1.send.reset_mock()
        msg2.last_send_attempt = 3
        msg2.send.reset_mock()
        msg3.last_send_attempt = 3
        msg3.send.reset_mock()

        track.restart_delayed(count=2)
        msg1.send.assert_not_called()
        msg2.send.assert_called_once()
        msg3.send.assert_called_once()
        self.assertEqual(3, msg1.last_send_attempt)
        self.assertEqual(0, msg2.last_send_attempt)
        self.assertEqual(0, msg3.last_send_attempt)

        msg1.last_send_attempt = 3
        msg1.send.reset_mock()
        msg2.last_send_attempt = 3
        msg2.send.reset_mock()
        msg3.last_send_attempt = 3
        msg3.send.reset_mock()

        track.restart_delayed(count=2, most_recent=False)
        msg1.send.assert_called_once()
        msg2.send.assert_called_once()
        msg3.send.assert_not_called()
        self.assertEqual(0, msg1.last_send_attempt)
        self.assertEqual(0, msg2.last_send_attempt)
        self.assertEqual(3, msg3.last_send_attempt)
Exemplo n.º 9
0
    def run(self):
        global check_email_delay

        LOG.debug("Starting")

        check_email_delay = 60
        past = datetime.datetime.now()
        while not self.thread_stop:
            time.sleep(5)
            stats.APRSDStats().email_thread_update()
            # always sleep for 5 seconds and see if we need to check email
            # This allows CTRL-C to stop the execution of this loop sooner
            # than check_email_delay time
            now = datetime.datetime.now()
            if now - past > datetime.timedelta(seconds=check_email_delay):
                # It's time to check email

                # slowly increase delay every iteration, max out at 300 seconds
                # any send/receive/resend activity will reset this to 60 seconds
                if check_email_delay < 300:
                    check_email_delay += 1
                LOG.debug("check_email_delay is " + str(check_email_delay) +
                          " seconds")

                shortcuts = CONFIG["aprsd"]["email"]["shortcuts"]
                # swap key/value
                shortcuts_inverted = {v: k for k, v in shortcuts.items()}

                date = datetime.datetime.now()
                month = date.strftime("%B")[:3]  # Nov, Mar, Apr
                day = date.day
                year = date.year
                today = "{}-{}-{}".format(day, month, year)

                server = None
                try:
                    server = _imap_connect()
                except Exception as e:
                    LOG.exception("IMAP failed to connect.", e)

                if not server:
                    continue

                try:
                    messages = server.search(["SINCE", today])
                except Exception as e:
                    LOG.exception(
                        "IMAP failed to search for messages since today.", e)
                    continue
                LOG.debug("{} messages received today".format(len(messages)))

                try:
                    _msgs = server.fetch(messages, ["ENVELOPE"])
                except Exception as e:
                    LOG.exception("IMAP failed to fetch/flag messages: ", e)
                    continue

                for msgid, data in _msgs.items():
                    envelope = data[b"ENVELOPE"]
                    LOG.debug(
                        'ID:%d  "%s" (%s)' %
                        (msgid, envelope.subject.decode(), envelope.date), )
                    f = re.search(
                        r"'([[A-a][0-9]_-]+@[[A-a][0-9]_-\.]+)",
                        str(envelope.from_[0]),
                    )
                    if f is not None:
                        from_addr = f.group(1)
                    else:
                        from_addr = "noaddr"

                    # LOG.debug("Message flags/tags:  " + str(server.get_flags(msgid)[msgid]))
                    # if "APRS" not in server.get_flags(msgid)[msgid]:
                    # in python3, imap tags are unicode.  in py2 they're strings. so .decode them to handle both
                    try:
                        taglist = [
                            x.decode(errors="ignore")
                            for x in server.get_flags(msgid)[msgid]
                        ]
                    except Exception as e:
                        LOG.exception("Failed to get flags.", e)
                        break

                    if "APRS" not in taglist:
                        # if msg not flagged as sent via aprs
                        try:
                            server.fetch([msgid], ["RFC822"])
                        except Exception as e:
                            LOG.exception(
                                "Failed single server fetch for RFC822", e)
                            break

                        (body, from_addr) = parse_email(msgid, data, server)
                        # unset seen flag, will stay bold in email client
                        try:
                            server.remove_flags(msgid, [imapclient.SEEN])
                        except Exception as e:
                            LOG.exception("Failed to remove flags SEEN", e)
                            # Not much we can do here, so lets try and
                            # send the aprs message anyway

                        if from_addr in shortcuts_inverted:
                            # reverse lookup of a shortcut
                            from_addr = shortcuts_inverted[from_addr]

                        reply = "-" + from_addr + " " + body.decode(
                            errors="ignore")
                        msg = messaging.TextMessage(
                            self.config["aprs"]["login"],
                            self.config["ham"]["callsign"],
                            reply,
                        )
                        self.msg_queues["tx"].put(msg)
                        # flag message as sent via aprs
                        try:
                            server.add_flags(msgid, ["APRS"])
                            # unset seen flag, will stay bold in email client
                        except Exception as e:
                            LOG.exception("Couldn't add APRS flag to email", e)

                        try:
                            server.remove_flags(msgid, [imapclient.SEEN])
                        except Exception as e:
                            LOG.exception(
                                "Couldn't remove seen flag from email", e)

                        # check email more often since we just received an email
                        check_email_delay = 60

                # reset clock
                LOG.debug("Done looping over Server.fetch, logging out.")
                past = datetime.datetime.now()
                try:
                    server.logout()
                except Exception as e:
                    LOG.exception("IMAP failed to logout: ", e)
                    continue
            else:
                # We haven't hit the email delay yet.
                # LOG.debug("Delta({}) < {}".format(now - past, check_email_delay))
                pass

        # Remove ourselves from the global threads list
        threads.APRSDThreadList().remove(self)
        LOG.info("Exiting")
Exemplo n.º 10
0
def resend_email(count, fromcall):
    global check_email_delay
    date = datetime.datetime.now()
    month = date.strftime("%B")[:3]  # Nov, Mar, Apr
    day = date.day
    year = date.year
    today = "{}-{}-{}".format(day, month, year)

    shortcuts = CONFIG["aprsd"]["email"]["shortcuts"]
    # swap key/value
    shortcuts_inverted = {v: k for k, v in shortcuts.items()}

    try:
        server = _imap_connect()
    except Exception as e:
        LOG.exception("Failed to Connect to IMAP. Cannot resend email ", e)
        return

    try:
        messages = server.search(["SINCE", today])
    except Exception as e:
        LOG.exception("Couldn't search for emails in resend_email ", e)
        return

    # LOG.debug("%d messages received today" % len(messages))

    msgexists = False

    messages.sort(reverse=True)
    del messages[int(count):]  # only the latest "count" messages
    for message in messages:
        try:
            parts = server.fetch(message, ["ENVELOPE"]).items()
        except Exception as e:
            LOG.exception("Couldn't fetch email parts in resend_email", e)
            continue

        for msgid, data in list(parts):
            # one at a time, otherwise order is random
            (body, from_addr) = parse_email(msgid, data, server)
            # unset seen flag, will stay bold in email client
            try:
                server.remove_flags(msgid, [imapclient.SEEN])
            except Exception as e:
                LOG.exception("Failed to remove SEEN flag in resend_email", e)

            if from_addr in shortcuts_inverted:
                # reverse lookup of a shortcut
                from_addr = shortcuts_inverted[from_addr]
            # asterisk indicates a resend
            reply = "-" + from_addr + " * " + body.decode(errors="ignore")
            # messaging.send_message(fromcall, reply)
            msg = messaging.TextMessage(
                CONFIG["aprs"]["login"],
                fromcall,
                reply,
            )
            msg.send()
            msgexists = True

    if msgexists is not True:
        stm = time.localtime()
        h = stm.tm_hour
        m = stm.tm_min
        s = stm.tm_sec
        # append time as a kind of serial number to prevent FT1XDR from
        # thinking this is a duplicate message.
        # The FT1XDR pretty much ignores the aprs message number in this
        # regard.  The FTM400 gets it right.
        reply = "No new msg {}:{}:{}".format(
            str(h).zfill(2),
            str(m).zfill(2),
            str(s).zfill(2),
        )
        # messaging.send_message(fromcall, reply)
        msg = messaging.TextMessage(CONFIG["aprs"]["login"], fromcall, reply)
        msg.send()

    # check email more often since we're resending one now
    check_email_delay = 60

    server.logout()
Exemplo n.º 11
0
    def loop(self):
        """Process a packet recieved from aprs-is server."""
        packet = self.packet
        packets.PacketList().add(packet)

        fromcall = packet["from"]
        tocall = packet.get("addresse", None)
        msg = packet.get("message_text", None)
        msg_id = packet.get("msgNo", "0")
        msg_response = packet.get("response", None)
        # LOG.debug(f"Got packet from '{fromcall}' - {packet}")

        # We don't put ack packets destined for us through the
        # plugins.
        if tocall == self.config["aprs"]["login"] and msg_response == "ack":
            self.process_ack_packet(packet)
        else:
            # It's not an ACK for us, so lets run it through
            # the plugins.
            messaging.log_message(
                "Received Message",
                packet["raw"],
                msg,
                fromcall=fromcall,
                msg_num=msg_id,
            )

            # Only ack messages that were sent directly to us
            if tocall == self.config["aprs"]["login"]:
                stats.APRSDStats().msgs_rx_inc()
                # let any threads do their thing, then ack
                # send an ack last
                ack = messaging.AckMessage(
                    self.config["aprs"]["login"],
                    fromcall,
                    msg_id=msg_id,
                )
                ack.send()

            pm = plugin.PluginManager()
            try:
                results = pm.run(packet)
                wl = packets.WatchList()
                wl.update_seen(packet)
                replied = False
                for reply in results:
                    if isinstance(reply, list):
                        # one of the plugins wants to send multiple messages
                        replied = True
                        for subreply in reply:
                            LOG.debug(f"Sending '{subreply}'")
                            if isinstance(subreply, messaging.Message):
                                subreply.send()
                            else:
                                msg = messaging.TextMessage(
                                    self.config["aprs"]["login"],
                                    fromcall,
                                    subreply,
                                )
                                msg.send()
                    elif isinstance(reply, messaging.Message):
                        # We have a message based object.
                        LOG.debug(f"Sending '{reply}'")
                        reply.send()
                        replied = True
                    else:
                        replied = True
                        # A plugin can return a null message flag which signals
                        # us that they processed the message correctly, but have
                        # nothing to reply with, so we avoid replying with a
                        # usage string
                        if reply is not messaging.NULL_MESSAGE:
                            LOG.debug(f"Sending '{reply}'")

                            msg = messaging.TextMessage(
                                self.config["aprs"]["login"],
                                fromcall,
                                reply,
                            )
                            msg.send()

                # If the message was for us and we didn't have a
                # response, then we send a usage statement.
                if tocall == self.config["aprs"]["login"] and not replied:
                    LOG.warning("Sending help!")
                    msg = messaging.TextMessage(
                        self.config["aprs"]["login"],
                        fromcall,
                        "Unknown command! Send 'help' message for help",
                    )
                    msg.send()
            except Exception as ex:
                LOG.error("Plugin failed!!!")
                LOG.exception(ex)
                # Do we need to send a reply?
                if tocall == self.config["aprs"]["login"]:
                    reply = "A Plugin failed! try again?"
                    msg = messaging.TextMessage(
                        self.config["aprs"]["login"],
                        fromcall,
                        reply,
                    )
                    msg.send()

        LOG.debug("Packet processing complete")
Exemplo n.º 12
0
Arquivo: main.py Projeto: wa9ecj/aprsd
def send_message(
    loglevel,
    quiet,
    config_file,
    aprs_login,
    aprs_password,
    no_ack,
    raw,
    tocallsign,
    command,
):
    """Send a message to a callsign via APRS_IS."""
    global got_ack, got_response

    config = utils.parse_config(config_file)
    if not aprs_login:
        click.echo("Must set --aprs_login or APRS_LOGIN")
        return

    if not aprs_password:
        click.echo("Must set --aprs-password or APRS_PASSWORD")
        return

    config["aprs"]["login"] = aprs_login
    config["aprs"]["password"] = aprs_password
    messaging.CONFIG = config

    setup_logging(config, loglevel, quiet)
    LOG.info("APRSD Started version: {}".format(aprsd.__version__))
    if type(command) is tuple:
        command = " ".join(command)
    if not quiet:
        if raw:
            LOG.info("L'{}' R'{}'".format(aprs_login, raw))
        else:
            LOG.info("L'{}' To'{}' C'{}'".format(aprs_login, tocallsign,
                                                 command))

    got_ack = False
    got_response = False

    def rx_packet(packet):
        global got_ack, got_response
        # LOG.debug("Got packet back {}".format(packet))
        resp = packet.get("response", None)
        if resp == "ack":
            ack_num = packet.get("msgNo")
            LOG.info("We got ack for our sent message {}".format(ack_num))
            messaging.log_packet(packet)
            got_ack = True
        else:
            message = packet.get("message_text", None)
            fromcall = packet["from"]
            msg_number = packet.get("msgNo", "0")
            messaging.log_message(
                "Received Message",
                packet["raw"],
                message,
                fromcall=fromcall,
                ack=msg_number,
            )
            got_response = True
            # Send the ack back?
            ack = messaging.AckMessage(
                config["aprs"]["login"],
                fromcall,
                msg_id=msg_number,
            )
            ack.send_direct()

        if got_ack and got_response:
            sys.exit(0)

    try:
        cl = client.Client(config)
        cl.setup_connection()
    except LoginError:
        sys.exit(-1)

    # Send a message
    # then we setup a consumer to rx messages
    # We should get an ack back as well as a new message
    # we should bail after we get the ack and send an ack back for the
    # message
    if raw:
        msg = messaging.RawMessage(raw)
        msg.send_direct()
        sys.exit(0)
    else:
        msg = messaging.TextMessage(aprs_login, tocallsign, command)
    msg.send_direct()

    if no_ack:
        sys.exit(0)

    try:
        # This will register a packet consumer with aprslib
        # When new packets come in the consumer will process
        # the packet
        aprs_client = client.get_client()
        aprs_client.consumer(rx_packet, raw=False)
    except aprslib.exceptions.ConnectionDrop:
        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
        cl.reset()