|
|
|
@ -5,22 +5,30 @@ from twilio.rest import Client |
|
|
|
import time |
|
|
|
import threading |
|
|
|
import setproctitle |
|
|
|
import json |
|
|
|
|
|
|
|
# Set the custom process name |
|
|
|
setproctitle.setproctitle("sms") |
|
|
|
|
|
|
|
app = Flask(__name__) |
|
|
|
|
|
|
|
# Set the expiration time in seconds (e.g., 90 seconds) |
|
|
|
MESSAGE_EXPIRATION_TIME = 3600 |
|
|
|
|
|
|
|
# Set this variable to enable/disable private mode |
|
|
|
private_mode = False # Change this value as needed |
|
|
|
|
|
|
|
# List of callsigns allowed to send messages if private_mode is TRUE. Accepts ALL SSIDs for a CALLSIGN listed. |
|
|
|
allowed_callsigns = ['CALLSIGN0', 'CALLSIGN1', 'CALLSIGN2'] # Add more callsigns as needed |
|
|
|
|
|
|
|
tocall = 'APOSMS' |
|
|
|
user_callsign = 'YOUR_CALLSIGN' |
|
|
|
|
|
|
|
# Twilio credentials |
|
|
|
TWILIO_ACCOUNT_SID = 'SID' |
|
|
|
TWILIO_AUTH_TOKEN = 'AUTH' |
|
|
|
TWILIO_PHONE_NUMBER = '+NUMBER' # Your Twilio phone number |
|
|
|
TWILIO_PHONE_NUMBER_UK = '+UKNUMBER' #UK SUPPORT |
|
|
|
|
|
|
|
# APRS credentials |
|
|
|
APRS_CALLSIGN = 'CALL' |
|
|
|
@ -51,6 +59,93 @@ RETRY_INTERVAL = 90 # Adjust this as needed |
|
|
|
|
|
|
|
MAX_RETRIES = 4 # Adjust this as needed |
|
|
|
|
|
|
|
def handle_alias_update(from_callsign, verbose_message): |
|
|
|
global alias_map, reverse_alias_map # Access the global dictionaries |
|
|
|
|
|
|
|
# Extract alias information from the received message |
|
|
|
alias_info = verbose_message.split('#alias', 1)[1].strip() # Remove the '#alias' prefix and trim whitespace |
|
|
|
|
|
|
|
# Split the alias information into parts |
|
|
|
alias_parts = alias_info.split() |
|
|
|
|
|
|
|
if len(alias_parts) == 3: |
|
|
|
action, alias_name, alias_phone = alias_parts |
|
|
|
alias_name = alias_name.lower() |
|
|
|
|
|
|
|
# Ensure the action is valid |
|
|
|
if action in ("#add", "#remove"): |
|
|
|
|
|
|
|
# Check if the alias phone is either 10 or 12 digits long #UK Support |
|
|
|
if len(alias_phone) not in {10, 12} or not alias_phone.isdigit(): |
|
|
|
print("Invalid alias phone number. It must be either exactly 10 or 12 digits long.") |
|
|
|
return |
|
|
|
|
|
|
|
# Extract the SSID from the from_callsign |
|
|
|
from_callsign_parts = from_callsign.split('-') |
|
|
|
if len(from_callsign_parts) == 2: |
|
|
|
from_callsign_strip = from_callsign_parts[0] |
|
|
|
else: |
|
|
|
from_callsign_strip = from_callsign # No SSID found |
|
|
|
|
|
|
|
if from_callsign_strip in alias_map: |
|
|
|
existing_aliases = alias_map[from_callsign_strip] |
|
|
|
|
|
|
|
if action == "#add": |
|
|
|
# Adding an alias |
|
|
|
if alias_name in existing_aliases: |
|
|
|
existing_phone = existing_aliases[alias_name] |
|
|
|
if existing_phone != alias_phone: |
|
|
|
# Alias name is found, but with a different phone number, update both name and phone |
|
|
|
existing_aliases[alias_name] = alias_phone |
|
|
|
|
|
|
|
elif alias_phone in existing_aliases.values(): |
|
|
|
# Alias phone number is found, update the associated alias name |
|
|
|
for name, phone in existing_aliases.items(): |
|
|
|
if phone == alias_phone: |
|
|
|
existing_aliases[alias_name] = alias_phone |
|
|
|
del existing_aliases[name] # Remove the old alias name |
|
|
|
break # Stop searching after the first match is found |
|
|
|
|
|
|
|
else: |
|
|
|
# Neither the alias name nor the alias phone is found, add the new alias |
|
|
|
existing_aliases[alias_name] = alias_phone |
|
|
|
|
|
|
|
elif action == "#remove": |
|
|
|
# Removing an alias |
|
|
|
if alias_name in existing_aliases: |
|
|
|
if existing_aliases[alias_name] == alias_phone: |
|
|
|
# Check if the provided alias and phone match the existing alias |
|
|
|
alias_map[from_callsign_strip].pop(alias_name) |
|
|
|
if not alias_map[from_callsign_strip]: |
|
|
|
# If there are no more aliases for this callsign, remove the callsign entry |
|
|
|
alias_map.pop(from_callsign_strip) |
|
|
|
else: |
|
|
|
print("Alias not found for removal:", alias_name, alias_phone) |
|
|
|
else: |
|
|
|
# If the callsign is not in the alias map, create a new entry |
|
|
|
if action == "#add": |
|
|
|
alias_map[from_callsign_strip] = {alias_name: alias_phone} |
|
|
|
|
|
|
|
# Update the reverse_alias_map to reflect the changes |
|
|
|
reverse_alias_map = generate_reverse_alias_map(alias_map) |
|
|
|
|
|
|
|
# Save the updated alias map to a file |
|
|
|
save_alias_map_to_file(alias_map, alias_map_filename) |
|
|
|
|
|
|
|
# Print a message indicating the update |
|
|
|
print("Alias map updated:") |
|
|
|
print(alias_map) |
|
|
|
else: |
|
|
|
print("Invalid action:", action) |
|
|
|
else: |
|
|
|
print("Invalid alias information:", alias_info) |
|
|
|
|
|
|
|
def save_alias_map_to_file(alias_map, filename): |
|
|
|
# Save the alias map to the specified file in JSON format with proper formatting |
|
|
|
with open(filename, 'w') as file: |
|
|
|
formatted_json = json.dumps(alias_map, indent=4) |
|
|
|
file.write(formatted_json) |
|
|
|
|
|
|
|
def send_ack_message(sender, message_id): |
|
|
|
ack_message = 'ack{}'.format(message_id) |
|
|
|
sender_length = len(sender) |
|
|
|
@ -71,6 +166,77 @@ def send_rej_message(sender, message_id): |
|
|
|
print("Sent REJ to {}: {}".format(sender, rej_message)) |
|
|
|
print("Outgoing REJ packet: {}".format(rej_packet.decode())) |
|
|
|
|
|
|
|
def send_aprs_messages(callsign, from_phone_number, sender_phone_number, aprs_message, last_message_id): |
|
|
|
#New Chunk Method |
|
|
|
chunk_size = 67 |
|
|
|
|
|
|
|
# Additional characters for portion information |
|
|
|
portion_info_chars = 4 |
|
|
|
|
|
|
|
# Calculate the constant value considering portion information |
|
|
|
constant_value = chunk_size - len(sender_phone_number) - 2 - portion_info_chars |
|
|
|
|
|
|
|
# Split the APRS message into chunks of 67 characters |
|
|
|
message_chunks = [aprs_message[i:i + constant_value] for i in range(0, len(aprs_message), constant_value)] |
|
|
|
#End Chunk Method |
|
|
|
|
|
|
|
#New Portion Calc |
|
|
|
total_chunks = len(message_chunks) |
|
|
|
|
|
|
|
# Get the last APRS message ID sent to this user |
|
|
|
last_message_id = user_last_message_id.get(from_phone_number, 0) |
|
|
|
user_last_message_id[from_phone_number] = last_message_id |
|
|
|
|
|
|
|
|
|
|
|
# Initialize a separate counter variable for the message ID within the loop |
|
|
|
current_message_id = last_message_id |
|
|
|
|
|
|
|
for i, chunk in enumerate(message_chunks): |
|
|
|
# Calculate portion information |
|
|
|
portion_info = " {}/{}".format(i + 1, total_chunks) if total_chunks > 1 else "" |
|
|
|
|
|
|
|
print("Chunks", len(message_chunks)) |
|
|
|
# Format the APRS packet and send it to the APRS server |
|
|
|
aprs_packet = format_aprs_packet(callsign, "@{}{} {}{}".format(sender_phone_number, portion_info, chunk, "{" + str(current_message_id))) |
|
|
|
print("chunks ID: ", current_message_id) |
|
|
|
print(aprs_packet) |
|
|
|
aprs_socket.sendall(aprs_packet.encode()) |
|
|
|
|
|
|
|
# Not a good delay, maybe seek alternative options. |
|
|
|
time.sleep(10) # Sleeping here allows time for incoming ack before retry |
|
|
|
print("sleep ID: ", current_message_id) |
|
|
|
retry_count = 0 |
|
|
|
ack_received = False |
|
|
|
|
|
|
|
while retry_count < MAX_RETRIES and not ack_received: |
|
|
|
if str(current_message_id) in received_acks.get(callsign, set()): |
|
|
|
print("Message ACK received. No further retries needed.") |
|
|
|
ack_received = True |
|
|
|
retry_count = 0 |
|
|
|
received_acks.get(callsign, set()).discard(str(current_message_id)) |
|
|
|
else: |
|
|
|
print("ACK not received. Retrying in {} seconds.".format(RETRY_INTERVAL)) |
|
|
|
aprs_socket.sendall(aprs_packet.encode()) |
|
|
|
retry_count += 1 |
|
|
|
time.sleep(RETRY_INTERVAL) |
|
|
|
|
|
|
|
if ack_received: |
|
|
|
print("ACK received during retries. No further retries needed.") |
|
|
|
elif retry_count >= MAX_RETRIES: |
|
|
|
print("Max retries reached. No ACK received for the message.") |
|
|
|
|
|
|
|
# Increment the counter variable for the next chunk |
|
|
|
current_message_id += 1 |
|
|
|
|
|
|
|
# Update the last_message_id outside the loop |
|
|
|
last_message_id += len(message_chunks) - 1 |
|
|
|
|
|
|
|
user_last_message_id[from_phone_number] = last_message_id |
|
|
|
|
|
|
|
print("Last ID: ", last_message_id) |
|
|
|
print("Next ID: ", current_message_id) |
|
|
|
print(user_last_message_id) |
|
|
|
|
|
|
|
|
|
|
|
def send_sms(twilio_phone_number, to_phone_number, from_callsign, body_message): |
|
|
|
# Initialize the Twilio client |
|
|
|
@ -89,28 +255,73 @@ def send_sms(twilio_phone_number, to_phone_number, from_callsign, body_message): |
|
|
|
print("Error sending SMS:", str(e)) |
|
|
|
|
|
|
|
|
|
|
|
def send_sms_uk(twilio_phone_number_uk, to_phone_number, from_callsign, body_message): #UK SUPPORT |
|
|
|
# Initialize the Twilio client |
|
|
|
client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN) |
|
|
|
|
|
|
|
try: |
|
|
|
# Send SMS using the Twilio API |
|
|
|
message = client.messages.create( |
|
|
|
body="@{} {}".format(from_callsign, body_message), |
|
|
|
from_=twilio_phone_number_uk, |
|
|
|
to=to_phone_number |
|
|
|
) |
|
|
|
print("SMS sent successfully.") |
|
|
|
print("SMS SID:", message.sid) |
|
|
|
except Exception as e: |
|
|
|
print("Error sending SMS:", str(e)) |
|
|
|
|
|
|
|
def format_aprs_packet(callsign, message): |
|
|
|
sender_length = len(callsign) |
|
|
|
spaces_after_sender = ' ' * max(0, 9 - sender_length) #1,9 - Changed 9-16 |
|
|
|
aprs_packet_format = '{}>APOSMS::{}{}:{}\r\n'.format(APRS_CALLSIGN, callsign, spaces_after_sender, message) |
|
|
|
return aprs_packet_format |
|
|
|
|
|
|
|
# Dictionary to store the mapping of aliases (callsigns) to phone numbers |
|
|
|
alias_map = { |
|
|
|
'alias1': '9876543210', # Replace 'alias1' with the desired alias and '1234567890' with the corresponding phone number. |
|
|
|
'alias2': '9876543210', # Add more entries as needed for other aliases and phone numbers. |
|
|
|
# Add more entries as needed. |
|
|
|
} |
|
|
|
|
|
|
|
def find_phone_number_from_alias(alias): |
|
|
|
return alias_map.get(alias.lower()) |
|
|
|
def load_alias_map_from_file(filename): |
|
|
|
try: |
|
|
|
with open(filename, 'r') as file: |
|
|
|
alias_map = json.load(file) |
|
|
|
return alias_map |
|
|
|
except (FileNotFoundError, json.JSONDecodeError): |
|
|
|
return {} # Return an empty dictionary if the file is not found or cannot be parsed |
|
|
|
|
|
|
|
# Define the filename for the alias map file |
|
|
|
alias_map_filename = '/root/app/sms_map.json' |
|
|
|
|
|
|
|
# Load the alias map from the file |
|
|
|
alias_map = load_alias_map_from_file(alias_map_filename) |
|
|
|
|
|
|
|
# Create a new dictionary to store the reverse mapping of phone numbers and aliases to callsigns |
|
|
|
def generate_reverse_alias_map(alias_map): |
|
|
|
reverse_alias_map = {} |
|
|
|
for callsign, aliases_and_numbers in alias_map.items(): |
|
|
|
reverse_alias_map[callsign] = {} |
|
|
|
for alias, phone_number in aliases_and_numbers.items(): |
|
|
|
reverse_alias_map[callsign][phone_number] = alias |
|
|
|
return reverse_alias_map |
|
|
|
|
|
|
|
# Whenever you update the alias map, you can regenerate the reverse_alias_map |
|
|
|
# For example, after updating the alias map with new data, call this function |
|
|
|
reverse_alias_map = generate_reverse_alias_map(alias_map) |
|
|
|
|
|
|
|
|
|
|
|
def extract_sender_phone_number(from_phone_number): |
|
|
|
# Extract the phone number from the sender's phone number based on the prefix |
|
|
|
if from_phone_number.startswith('+1'): |
|
|
|
return from_phone_number[2:] |
|
|
|
elif from_phone_number.startswith('+44'): |
|
|
|
return from_phone_number[1:] |
|
|
|
else: |
|
|
|
return from_phone_number[-10:] |
|
|
|
|
|
|
|
|
|
|
|
# Create a new dictionary to store the reverse mapping of phone numbers to aliases |
|
|
|
reverse_alias_map = {v: k for k, v in alias_map.items()} |
|
|
|
def is_message_expired(timestamp): |
|
|
|
return time.time() - timestamp > MESSAGE_EXPIRATION_TIME |
|
|
|
|
|
|
|
|
|
|
|
@app.route('/sms', methods=['POST']) |
|
|
|
def receive_sms(): |
|
|
|
global last_message_number #Questioning this. Consider options later. |
|
|
|
# Parse the incoming SMS message |
|
|
|
data = request.form |
|
|
|
from_phone_number = data['From'] |
|
|
|
@ -120,57 +331,52 @@ def receive_sms(): |
|
|
|
if body_message.startswith('@'): |
|
|
|
parts = body_message.split(' ', 1) |
|
|
|
if len(parts) == 2: |
|
|
|
# Extract the 10-digit phone number from the sender's phone number |
|
|
|
sender_phone_number = from_phone_number[-10:] |
|
|
|
# Extract the phone number from the sender's phone number |
|
|
|
sender_phone_number = extract_sender_phone_number(from_phone_number) |
|
|
|
callsign = parts[0][1:].upper() |
|
|
|
aprs_message = parts[1] |
|
|
|
print(callsign) |
|
|
|
|
|
|
|
last_message_number[sender_phone_number] = callsign #Questioning this. Consider options later. |
|
|
|
print(last_message_number) |
|
|
|
|
|
|
|
|
|
|
|
# Get the last APRS message ID sent to this user |
|
|
|
last_message_id = user_last_message_id.get(from_phone_number, 0) |
|
|
|
last_message_id += 1 |
|
|
|
user_last_message_id[from_phone_number] = last_message_id |
|
|
|
print("RX SMS ID: ", last_message_id) |
|
|
|
print("RX USR ID: ", user_last_message_id) |
|
|
|
|
|
|
|
|
|
|
|
# Extract the SSID from the from_callsign |
|
|
|
from_callsign_parts = callsign.split('-') |
|
|
|
if len(from_callsign_parts) == 2: |
|
|
|
from_callsign_strip = from_callsign_parts[0] |
|
|
|
else: |
|
|
|
from_callsign_strip = callsign # No SSID found |
|
|
|
print("No SSID Found") |
|
|
|
|
|
|
|
# Use the reverse alias mapping to check if the sender's phone number has an associated alias |
|
|
|
alias = reverse_alias_map.get(sender_phone_number.lower()) |
|
|
|
alias = reverse_alias_map.get(from_callsign_strip, {}).get(sender_phone_number.lower()) |
|
|
|
if alias: |
|
|
|
sender_phone_number = alias |
|
|
|
|
|
|
|
# Format the APRS packet and send it to the APRS server |
|
|
|
aprs_packet = format_aprs_packet(callsign, "@{} {}".format(sender_phone_number, aprs_message + "{" + str(last_message_id))) |
|
|
|
aprs_socket.sendall(aprs_packet.encode()) |
|
|
|
|
|
|
|
time.sleep(5) # Sleeping here allows time for incoming ack before retry |
|
|
|
|
|
|
|
retry_count = 0 |
|
|
|
ack_received = False |
|
|
|
|
|
|
|
while retry_count < MAX_RETRIES and not ack_received: |
|
|
|
if str(last_message_id) in received_acks.get(callsign, set()): |
|
|
|
print("Message ACK received. No further retries needed.") |
|
|
|
ack_received = True |
|
|
|
retry_count = 0 |
|
|
|
received_acks.get(callsign, set()).discard(str(last_message_id)) |
|
|
|
else: |
|
|
|
print("ACK not received. Retrying in {} seconds.".format(RETRY_INTERVAL)) |
|
|
|
aprs_socket.sendall(aprs_packet.encode()) |
|
|
|
retry_count += 1 |
|
|
|
time.sleep(RETRY_INTERVAL) |
|
|
|
|
|
|
|
if ack_received: |
|
|
|
print("ACK received during retries. No further retries needed.") |
|
|
|
elif retry_count >= MAX_RETRIES: |
|
|
|
print("Max retries reached. No ACK received for the message.") |
|
|
|
|
|
|
|
# Format and send APRS packets |
|
|
|
send_aprs_messages(callsign, from_phone_number, sender_phone_number, aprs_message, last_message_id) |
|
|
|
|
|
|
|
return jsonify({'status': 'success'}) |
|
|
|
else: |
|
|
|
return jsonify({'status': 'error', 'message': 'Invalid SMS format'}) |
|
|
|
else: |
|
|
|
|
|
|
|
print ("no callsign found") |
|
|
|
# Message without @callsign prefix |
|
|
|
callsign = last_message_number.get(from_phone_number[-10:], None) |
|
|
|
sender_phone_number = from_phone_number[-10:] |
|
|
|
|
|
|
|
sender_phone_number = extract_sender_phone_number(from_phone_number) |
|
|
|
callsign = last_message_number.get(sender_phone_number, None) |
|
|
|
|
|
|
|
print("From:", from_phone_number[-10:]) |
|
|
|
print("From Full:", from_phone_number) |
|
|
|
print("Dictionary:", last_message_number) |
|
|
|
print("Callsign Found:", callsign) |
|
|
|
|
|
|
|
@ -182,40 +388,22 @@ def receive_sms(): |
|
|
|
last_message_id = user_last_message_id.get(from_phone_number, 0) |
|
|
|
last_message_id += 1 |
|
|
|
user_last_message_id[from_phone_number] = last_message_id |
|
|
|
|
|
|
|
# Extract the SSID from the from_callsign |
|
|
|
from_callsign_parts = callsign.split('-') |
|
|
|
if len(from_callsign_parts) == 2: |
|
|
|
from_callsign_strip = from_callsign_parts[0] |
|
|
|
else: |
|
|
|
from_callsign_strip = callsign # No SSID found |
|
|
|
|
|
|
|
# Use the reverse alias mapping to check if the sender's phone number has an associated alias |
|
|
|
alias = reverse_alias_map.get(sender_phone_number.lower()) |
|
|
|
alias = reverse_alias_map.get(from_callsign_strip, {}).get(sender_phone_number.lower()) |
|
|
|
if alias: |
|
|
|
sender_phone_number = alias |
|
|
|
|
|
|
|
# Format the APRS packet and send it to the APRS server |
|
|
|
aprs_packet = format_aprs_packet(callsign, "@{} {}".format(sender_phone_number, aprs_message + "{" + str(last_message_id))) |
|
|
|
aprs_socket.sendall(aprs_packet.encode()) |
|
|
|
sender_phone_number = alias |
|
|
|
|
|
|
|
print("Sent APRS message to {}: {}".format(callsign, aprs_message)) |
|
|
|
print("Outgoing APRS packet: {}".format(aprs_packet.strip())) |
|
|
|
# Format and send APRS packets |
|
|
|
send_aprs_messages(callsign, from_phone_number, sender_phone_number, aprs_message, last_message_id) |
|
|
|
|
|
|
|
time.sleep(5) # Sleeping here allows time for incoming ack before retry |
|
|
|
|
|
|
|
retry_count = 0 |
|
|
|
ack_received = False |
|
|
|
|
|
|
|
while retry_count < MAX_RETRIES and not ack_received: |
|
|
|
if str(last_message_id) in received_acks.get(callsign, set()): |
|
|
|
print("Message ACK received. No further retries needed.") |
|
|
|
ack_received = True |
|
|
|
retry_count = 0 |
|
|
|
received_acks.get(callsign, set()).discard(str(last_message_id)) |
|
|
|
else: |
|
|
|
print("ACK not received. Retrying in {} seconds.".format(RETRY_INTERVAL)) |
|
|
|
aprs_socket.sendall(aprs_packet.encode()) |
|
|
|
retry_count += 1 |
|
|
|
time.sleep(RETRY_INTERVAL) |
|
|
|
|
|
|
|
if ack_received: |
|
|
|
print("ACK received during retries. No further retries needed.") |
|
|
|
elif retry_count >= MAX_RETRIES: |
|
|
|
print("Max retries reached. No ACK received for the message.") |
|
|
|
|
|
|
|
return jsonify({'status': 'success'}) |
|
|
|
else: |
|
|
|
@ -232,7 +420,7 @@ def establish_aprs_connection(): |
|
|
|
print("Connected to APRS server with callsign: {}".format(APRS_CALLSIGN)) |
|
|
|
|
|
|
|
# Send login information with APRS callsign and passcode |
|
|
|
login_str = 'user {} pass {} vers SMS-Gateway 1.0 Beta\r\n'.format(APRS_CALLSIGN, APRS_PASSCODE) |
|
|
|
login_str = 'user {} pass {} vers SMS-Gateway 1.4 Beta\r\n'.format(APRS_CALLSIGN, APRS_PASSCODE) |
|
|
|
aprs_socket.sendall(login_str.encode()) |
|
|
|
print("Sent login information.") |
|
|
|
|
|
|
|
@ -253,7 +441,7 @@ def establish_aprs_connection(): |
|
|
|
time.sleep(1) # Wait for a while before attempting to reconnect |
|
|
|
|
|
|
|
def receive_aprs_messages(): |
|
|
|
global socket_ready, last_message_number # Declare that you're using the global variable |
|
|
|
global socket_ready, last_message_number, alias_map # Declare that you're using the global variable |
|
|
|
|
|
|
|
while True: |
|
|
|
try: |
|
|
|
@ -294,104 +482,134 @@ def receive_aprs_messages(): |
|
|
|
process_ack_id(from_callsign, ack_id) |
|
|
|
# End RXd ACK ID for MSG Retries |
|
|
|
|
|
|
|
# Check if the message contains "{" |
|
|
|
if "{" in message_text: |
|
|
|
message_id = message_text.split('{')[1] |
|
|
|
|
|
|
|
else: |
|
|
|
message_id = '1' |
|
|
|
|
|
|
|
if ":" in message_text and APRS_CALLSIGN in message_text: |
|
|
|
pass |
|
|
|
|
|
|
|
# Remove the first 11 characters from the message to exclude the "Callsign :" prefix |
|
|
|
verbose_message = message_text[11:].split('{')[0].strip() |
|
|
|
|
|
|
|
# Inside the receive_aprs_messages function |
|
|
|
if private_mode: |
|
|
|
# Use regular expression to match main callsign and accept all SSIDs |
|
|
|
callsign_pattern = re.compile(r'^({})(-\d+)?$'.format('|'.join(map(re.escape, allowed_callsigns)))) |
|
|
|
if not callsign_pattern.match(from_callsign): |
|
|
|
print("Unauthorized sender:", from_callsign) |
|
|
|
send_rej_message(from_callsign, message_id) |
|
|
|
continue # Skip processing messages from unauthorized senders |
|
|
|
|
|
|
|
# Display verbose message content |
|
|
|
print("From: {}".format(from_callsign)) |
|
|
|
print("Message: {}".format(verbose_message)) |
|
|
|
print("Message ID: {}".format(message_id)) |
|
|
|
print(user_last_message_id) |
|
|
|
|
|
|
|
|
|
|
|
# Check if the verbose message contains the desired format with a number or an alias |
|
|
|
pattern = r'@(\d{10}|\w+) (.+)' |
|
|
|
match = re.match(pattern, verbose_message) |
|
|
|
|
|
|
|
# Send ACK |
|
|
|
send_ack_message(from_callsign, message_id) |
|
|
|
|
|
|
|
|
|
|
|
if match: |
|
|
|
recipient = match.group(1) |
|
|
|
|
|
|
|
# Update the dictionary with the last message number for the callsign |
|
|
|
|
|
|
|
# Use the reverse alias mapping to check if the sender's phone number has an associated alias |
|
|
|
alias = alias_map.get(recipient.lower()) |
|
|
|
if alias: |
|
|
|
recipient = alias |
|
|
|
|
|
|
|
last_message_number[recipient.lower()] = from_callsign |
|
|
|
print ("To #", recipient) |
|
|
|
print ("From", from_callsign) |
|
|
|
print ("Dictionary", last_message_number) |
|
|
|
|
|
|
|
aprs_message = match.group(2) |
|
|
|
|
|
|
|
# Check if the recipient is a 10-digit number or an alias |
|
|
|
if recipient.isdigit(): |
|
|
|
# Recipient is a 10-digit number |
|
|
|
phone_number = recipient |
|
|
|
else: |
|
|
|
# Recipient is an alias |
|
|
|
phone_number = find_phone_number_from_alias(recipient) |
|
|
|
|
|
|
|
|
|
|
|
if phone_number: |
|
|
|
# Get the last APRS message ID sent to this user |
|
|
|
last_message_id = user_last_message_id.get(from_callsign, 0) |
|
|
|
last_message_id += 1 |
|
|
|
user_last_message_id[from_callsign] = last_message_id |
|
|
|
|
|
|
|
else: |
|
|
|
print("Recipient not found in alias map or not a 10-digit number: {}".format(recipient)) |
|
|
|
|
|
|
|
# Check if verbose_message starts with ":SMS:" (or the appropriate APRS_CALLSIGN) |
|
|
|
if message_text.startswith(":{}".format(APRS_CALLSIGN)): |
|
|
|
|
|
|
|
# Check if the message contains "{" |
|
|
|
if "{" in message_text[-6:]: |
|
|
|
message_id = message_text.split('{')[1] |
|
|
|
|
|
|
|
|
|
|
|
# Check for duplicate messages |
|
|
|
if (aprs_message, message_id) in received_aprs_messages.get(from_callsign, set()): |
|
|
|
print("Duplicate message detected. Skipping SMS sending.") |
|
|
|
send_ack_message(from_callsign, message_id) |
|
|
|
|
|
|
|
# Remove the first 11 characters from the message to exclude the "Callsign :" prefix |
|
|
|
verbose_message = message_text[11:].split('{')[0].strip() |
|
|
|
|
|
|
|
# Inside the receive_aprs_messages function |
|
|
|
if private_mode: |
|
|
|
# Use regular expression to match main callsign and accept all SSIDs |
|
|
|
callsign_pattern = re.compile(r'^({})(-\d+)?$'.format('|'.join(map(re.escape, allowed_callsigns)))) |
|
|
|
if not callsign_pattern.match(from_callsign): |
|
|
|
print("Unauthorized sender:", from_callsign) |
|
|
|
send_rej_message(from_callsign, message_id) |
|
|
|
continue # Skip processing messages from unauthorized senders |
|
|
|
|
|
|
|
# Display verbose message content |
|
|
|
print("From: {}".format(from_callsign)) |
|
|
|
print("Message: {}".format(verbose_message)) |
|
|
|
print("Message ID: {}".format(message_id)) |
|
|
|
print(user_last_message_id) |
|
|
|
|
|
|
|
# Initialize match |
|
|
|
match = None |
|
|
|
|
|
|
|
# Check if the verbose message contains the desired format with a number or an alias |
|
|
|
#alias_pattern = r'#alias (.+)' |
|
|
|
alias_pattern = re.compile(r'#alias (.+)', re.IGNORECASE) |
|
|
|
|
|
|
|
alias_match = re.search(alias_pattern, verbose_message) |
|
|
|
print("alias pattern") |
|
|
|
if alias_match: |
|
|
|
print("did we make it to 1") |
|
|
|
# Call a function to handle alias updates |
|
|
|
handle_alias_update(from_callsign, verbose_message.lower()) #added lower |
|
|
|
else: |
|
|
|
# Mark the message as received |
|
|
|
received_aprs_messages.setdefault(from_callsign, set()).add((aprs_message, message_id)) |
|
|
|
pattern = r'@(\d{10}|\w+) (.+)' |
|
|
|
match = re.match(pattern, verbose_message) |
|
|
|
|
|
|
|
# Send SMS |
|
|
|
send_sms(TWILIO_PHONE_NUMBER, phone_number, from_callsign, aprs_message) |
|
|
|
|
|
|
|
# Add this line to mark the message ID as processed |
|
|
|
processed_message_ids.add(message_id) |
|
|
|
|
|
|
|
# Send ACK |
|
|
|
send_ack_message(from_callsign, message_id) |
|
|
|
print("did we send this ack?") |
|
|
|
|
|
|
|
if match: |
|
|
|
recipient = match.group(1) |
|
|
|
|
|
|
|
# Update the dictionary with the last message number for the callsign |
|
|
|
|
|
|
|
# Extract the SSID from the from_callsign |
|
|
|
from_callsign_parts = from_callsign.split('-') |
|
|
|
if len(from_callsign_parts) == 2: |
|
|
|
from_callsign_strip = from_callsign_parts[0] |
|
|
|
else: |
|
|
|
from_callsign_strip = from_callsign # No SSID found |
|
|
|
|
|
|
|
# Use the reverse alias mapping to check if the sender's phone number has an associated alias |
|
|
|
alias = alias_map.get(from_callsign_strip, {}).get(recipient.lower()) |
|
|
|
if alias: |
|
|
|
recipient = alias |
|
|
|
print("Recipient:", recipient) |
|
|
|
|
|
|
|
# Extract and process ACK ID if present |
|
|
|
if message_text.startswith("ack"): |
|
|
|
ack_id = message_text[3:] # Remove the "ack" prefix |
|
|
|
process_ack_id(from_callsign, ack_id) |
|
|
|
|
|
|
|
|
|
|
|
pass |
|
|
|
# Send ACK |
|
|
|
|
|
|
|
#last_message_number[recipient.lower()] = from_callsign |
|
|
|
print ("To #", recipient) |
|
|
|
print ("From", from_callsign) |
|
|
|
print ("Dictionary", last_message_number) |
|
|
|
|
|
|
|
aprs_message = match.group(2) |
|
|
|
|
|
|
|
# Check if the recipient is a 10-digit number or an alias |
|
|
|
if recipient.isdigit(): |
|
|
|
# Recipient is a 10-digit number |
|
|
|
phone_number = recipient |
|
|
|
last_message_number[recipient.lower()] = from_callsign |
|
|
|
|
|
|
|
else: |
|
|
|
# Recipient is an alias |
|
|
|
phone_number = alias_map.get(from_callsign, {}).get(recipient) |
|
|
|
print("phone", phone_number) |
|
|
|
|
|
|
|
if phone_number: |
|
|
|
# Get the last APRS message ID sent to this user |
|
|
|
last_message_id = user_last_message_id.get(from_callsign, 0) |
|
|
|
last_message_id += 1 |
|
|
|
user_last_message_id[from_callsign] = last_message_id |
|
|
|
|
|
|
|
if not phone_number: |
|
|
|
print("Recipient not found in alias map or not a 10 or 12 digit number: {}".format(recipient)) |
|
|
|
continue # Skip processing the current message and move on to the next one in the loop |
|
|
|
|
|
|
|
# Check for duplicate messages |
|
|
|
messages_for_callsign = received_aprs_messages.get(from_callsign, []) |
|
|
|
current_time = time.time() |
|
|
|
|
|
|
|
|
|
|
|
# Check if the message has been received in the last 90 seconds |
|
|
|
if any(not is_message_expired(stored_timestamp) and stored_message_id == message_id for stored_message, stored_message_id, stored_timestamp in messages_for_callsign): |
|
|
|
print("Message received in the last 3600 seconds. Skipping further processing.") |
|
|
|
|
|
|
|
#UK SUPPORT |
|
|
|
else: |
|
|
|
# Mark the message as received |
|
|
|
received_aprs_messages.setdefault(from_callsign, []).append((aprs_message, message_id, current_time)) |
|
|
|
print(received_aprs_messages) |
|
|
|
|
|
|
|
if len(phone_number) == 12 and phone_number.startswith("44"): |
|
|
|
# UK phone number format: 12 digits and starts with "44" |
|
|
|
send_sms_uk(TWILIO_PHONE_NUMBER_UK, phone_number, from_callsign, aprs_message) |
|
|
|
else: |
|
|
|
# Default behavior |
|
|
|
send_sms(TWILIO_PHONE_NUMBER, phone_number, from_callsign, aprs_message) |
|
|
|
|
|
|
|
# Add this line to mark the message ID as processed |
|
|
|
processed_message_ids.add(message_id) |
|
|
|
print("Process MSG ID") |
|
|
|
|
|
|
|
# Extract and process ACK ID if present |
|
|
|
if message_text.startswith("ack"): |
|
|
|
ack_id = message_text[3:] # Remove the "ack" prefix |
|
|
|
process_ack_id(from_callsign, ack_id) |
|
|
|
print("Process ACK ID") |
|
|
|
|
|
|
|
pass |
|
|
|
# Send ACK |
|
|
|
# The last line might be an incomplete packet, so keep it in the buffer |
|
|
|
buffer = lines[-1] |
|
|
|
|
|
|
|
@ -444,8 +662,8 @@ def send_beacon(): |
|
|
|
try: |
|
|
|
if socket_ready: |
|
|
|
# Send a keepalive packet to the APRS server |
|
|
|
beacon_packet = 'POSITION BEACON\r\n' |
|
|
|
status_beacon = 'STATUS BEACON\r\n' |
|
|
|
beacon_packet = '{}>{}:Your Position {}\r\n'.format(APRS_CALLSIGN, tocall, user_callsign) |
|
|
|
status_beacon = '{}>{}:>Status Beacon\r\n'.format(APRS_CALLSIGN, tocall) |
|
|
|
aprs_socket.sendall(beacon_packet.encode()) |
|
|
|
aprs_socket.sendall(status_beacon.encode()) |
|
|
|
|
|
|
|
@ -457,6 +675,9 @@ def send_beacon(): |
|
|
|
if __name__ == '__main__': |
|
|
|
print("APRS bot is running. Waiting for APRS messages...") |
|
|
|
|
|
|
|
# Establish the initial connection to the APRS server |
|
|
|
establish_aprs_connection() |
|
|
|
|
|
|
|
# Start a separate thread for sending keepalive packets |
|
|
|
keepalive_thread = threading.Thread(target=send_keepalive) |
|
|
|
keepalive_thread.start() |
|
|
|
@ -467,11 +688,8 @@ if __name__ == '__main__': |
|
|
|
|
|
|
|
# Run the Flask web application in a separate thread to handle incoming SMS messages |
|
|
|
from threading import Thread |
|
|
|
webhook_thread = Thread(target=app.run, kwargs={'host': '0.0.0.0', 'port': 5000}) |
|
|
|
webhook_thread = Thread(target=app.run, kwargs={'host': '0.0.0.0', 'port': 12345}) |
|
|
|
webhook_thread.start() |
|
|
|
|
|
|
|
# Establish the initial connection to the APRS server |
|
|
|
establish_aprs_connection() |
|
|
|
|
|
|
|
# Start listening for APRS messages |
|
|
|
receive_aprs_messages() |