From 312092adf4ea2c828d1896c247523d9ae574fae2 Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 2 Aug 2023 14:52:04 -0700 Subject: [PATCH] Create aprs2email.py --- aprs2email.py | 252 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 aprs2email.py diff --git a/aprs2email.py b/aprs2email.py new file mode 100644 index 0000000..e42b106 --- /dev/null +++ b/aprs2email.py @@ -0,0 +1,252 @@ +import socket +import smtplib +import re +import time +import requests +from email.mime.text import MIMEText +from imapclient import IMAPClient +import email +from email.mime.multipart import MIMEMultipart +import threading + +# APRS credentials +APRS_CALLSIGN = 'CALL' +APRS_PASSCODE = 'PASS' +APRS_SERVER = 'rotate.aprs2.net' +APRS_PORT = 14580 + +# Initialize the socket +aprs_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +aprs_socket_lock = threading.Lock() + +# Email configuration +SMTP_SERVER = 'smtp.gmail.com' +SMTP_PORT = 587 +IMAP_SERVER = 'imap.gmail.com' +IMAP_PORT = 993 +SENDER_EMAIL = 'EMAIL@gmail.com' +SENDER_PASSWORD = 'APP_PASSCODE' + +# Alias dictionary +alias_email_map = { + 'mike': 'mike@gmail.com', + 'john': 'john@gmail.com', + 'larry': 'larry@yahoo.com', + 'kevin': 'kevin@gmail.com', + + # Add more aliases and their corresponding email addresses here +} + +# Optimization parameters +CHECK_INTERVAL = 0 # Sleep interval between email checks (in seconds) + +# Message counter for numbering APRS messages +message_counter = 1 + +def send_aprs_message(recipient_call, message_body): + global message_counter + spaces_after_recipient = ' ' * max(0, 9 - len(recipient_call)) + aprs_message = '{}>APRS::{}{}:{}{{{}\r'.format(APRS_CALLSIGN, recipient_call, spaces_after_recipient, message_body, message_counter) + message_packet = aprs_message.encode() + with aprs_socket_lock: # Acquire lock before sending APRS message + aprs_socket.sendall(message_packet) + print(message_packet) + print(aprs_message) + print("Sent APRS message to {}: {}".format(recipient_call, message_body)) + print("Outgoing APRS packet: {}".format(aprs_message)) # Exclude the closing "}" + message_counter += 1 + +def send_ack_message(sender, message_id): + if message_id.isdigit(): + ack_message = 'ack{}'.format(message_id) + sender_length = len(sender) + spaces_after_sender = ' ' * max(0, 9 - sender_length) + ack_packet_format = '{}>APRS::{}{}:{}\r\n'.format(APRS_CALLSIGN, sender, spaces_after_sender, ack_message) + ack_packet = ack_packet_format.encode() + aprs_socket.sendall(ack_packet) + print("Sent ACK to {}: {}".format(sender, ack_message)) + print("Outgoing ACK packet: {}".format(ack_packet.decode())) + +def get_email_body(email_message): + if email_message.is_multipart(): + # If the email is multipart, extract the text content + for part in email_message.walk(): + content_type = part.get_content_type() + if content_type == "text/plain": + payload = part.get_payload(decode=True).decode() + return payload.strip() + else: + # If the email is plain text, return the body + return email_message.get_payload().strip() + + return None + + + +def check_emails(): + try: + with IMAPClient(IMAP_SERVER, port=IMAP_PORT) as client: + client.login(SENDER_EMAIL, SENDER_PASSWORD) + client.select_folder('INBOX') + messages = client.search(['UNSEEN']) + if messages: + print('Received', len(messages), 'new email(s)') + + for msgid, message_data in client.fetch(messages, ['RFC822']).items(): + raw_email = message_data[b'RFC822'] + email_message = email.message_from_bytes(raw_email) + + sender_match = re.search(r'<([^>]+)>', email_message['From']) + if sender_match: + sender = sender_match.group(1) + else: + sender = email_message['From'] + subject = email_message['Subject'] + email_body = get_email_body(email_message) + + aprs_in_subject = 'email' in subject.lower() + + print("\nReceived an email:") + print("From:", sender) + print("Subject:", subject) + print("Body:", email_body) + print("Contains email:", aprs_in_subject) + + if aprs_in_subject: + # Use regular expression to extract the APRS call and message + match = re.search(r'@([A-Z0-9-]+)\s+(.+)', email_body, re.IGNORECASE) + if match: + recipient_call = match.group(1) + message_body = match.group(2) + print("APRS Callsign-SSID found in email body:", recipient_call) + print("Message in email body:", message_body) + + # Prepend sender's email to the message + message_body_with_sender = "@{} {}".format(sender, message_body) + + send_aprs_message(recipient_call, message_body_with_sender) + print("Sent APRS message to {}: {}".format(recipient_call, message_body_with_sender)) + else: + print("No valid APRS Callsign-SSID and message found in email body.") + + # Mark the email as read + client.set_flags(msgid, [b'\\Seen']) + + except Exception as e: + print('An error occurred:', e) + + +def receive_aprs_messages(): + # Connect to the APRS server + aprs_socket.connect((APRS_SERVER, APRS_PORT)) + print("Connected to APRS server with callsign: {}".format(APRS_CALLSIGN)) + + # Send login information with APRS callsign and passcode + login_str = 'user {} pass {} vers APRS-Email-Bot 1.0\r\n'.format(APRS_CALLSIGN, APRS_PASSCODE) + aprs_socket.sendall(login_str.encode()) + print("Sent login information.") + + buffer = "" + try: + while True: + data = aprs_socket.recv(1024) + if not data: + break + + # Add received data to the buffer + buffer += data.decode() + + # Split buffer into lines + lines = buffer.split('\n') + + # Process each line + for line in lines[:-1]: + if line.startswith('#'): + continue + + # Process APRS message + print("Received raw APRS packet: {}".format(line.strip())) + parts = line.strip().split(':') + if len(parts) >= 2: + from_callsign = parts[0].split('>')[0].strip() + message_text = ':'.join(parts[1:]).strip() + + # Check if the message contains "{" + if "{" in message_text: + message_id = message_text.split('{')[1].strip('}') + + # Remove the first 11 characters from the message to exclude the "Callsign :" prefix + verbose_message = message_text[11:].split('{')[0].strip() + + # Check if the message starts with "@" to handle both email and alias + if verbose_message.startswith('@'): + email_match = re.match(r'@([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,4})\s+(.+)', verbose_message) + if email_match: + recipient_email = email_match.group(1) + message_body = email_match.group(2) + send_email(from_callsign, message_body, recipient_email) + else: + alias = verbose_message[1:].split()[0] + recipient_email = alias_email_map.get(alias) + if recipient_email: + message_body = verbose_message[len(alias)+2:] + send_email(from_callsign, message_body, recipient_email) + else: + print("Alias '{}' not found in the alias_email_map. Email not sent.".format(alias)) + + send_ack_message(from_callsign, message_id) + + # The last line might be an incomplete packet, so keep it in the buffer + buffer = lines[-1] + + except Exception as e: + print("Error receiving APRS messages: {}".format(e)) + + finally: + # Close the socket connection when done + aprs_socket.close() + + + +def send_email(subject, body, recipient_email): + message = MIMEText(body) + message['From'] = SENDER_EMAIL + message['To'] = recipient_email + message['Subject'] = subject + + try: + server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT) + server.starttls() + server.login(SENDER_EMAIL, SENDER_PASSWORD) + server.sendmail(SENDER_EMAIL, recipient_email, message.as_string()) + server.quit() + print('Email sent successfully!') + except smtplib.SMTPException as e: + print('Email could not be sent:', e) + + +def listen_emails(): + while True: + check_emails() + time.sleep(CHECK_INTERVAL) + +def start_email_listener(): + email_thread = threading.Thread(target=listen_emails) + email_thread.daemon = True + email_thread.start() + + print('Email listener started.') + +def start_aprs_receiver(): + aprs_thread = threading.Thread(target=receive_aprs_messages) + aprs_thread.daemon = True + aprs_thread.start() + + print('APRS receiver started.') + + +if __name__ == '__main__': + print("APRS bot is running. Waiting for APRS messages...") + start_email_listener() + receive_aprs_messages() + start_aprs_receiver() # Start the APRS receiver thread