mirror of
https://github.com/SpudGunMan/meshing-around.git
synced 2026-04-30 18:42:17 +02:00
SMTP module work
This commit is contained in:
@@ -280,7 +280,7 @@ In the config.ini enable the module
|
||||
# enable or disable the scheduler module
|
||||
enabled = True
|
||||
```
|
||||
The actions are via code only at this time. See mesh_bot.py around line [1050](https://github.com/SpudGunMan/meshing-around/blob/e94581936530c76ea43500eebb43f32ba7ed5e19/mesh_bot.py#L1050) to edit the schedule. See [schedule documentation](https://schedule.readthedocs.io/en/stable/) for more. Recomend to backup changes so they dont get lost.
|
||||
The actions are via code only at this time. See mesh_bot.py around line [1097](https://github.com/SpudGunMan/meshing-around/blob/e94581936530c76ea43500eebb43f32ba7ed5e19/mesh_bot.py#L1097) to edit the schedule. See [schedule documentation](https://schedule.readthedocs.io/en/stable/) for more. Recomend to backup changes so they dont get lost.
|
||||
|
||||
```python
|
||||
#Send WX every Morning at 08:00 using handle_wxc function to channel 2 on device 1
|
||||
|
||||
@@ -23,6 +23,27 @@ except Exception as e:
|
||||
except Exception as e:
|
||||
bbs_dm = "System: data/bbsdm.pkl not found"
|
||||
|
||||
try:
|
||||
with open('../data/email_db.pickle', 'rb') as f:
|
||||
email_db = pickle.load(f)
|
||||
except Exception as e:
|
||||
try:
|
||||
with open('data/email_db.pickle', 'rb') as f:
|
||||
email_db = pickle.load(f)
|
||||
except Exception as e:
|
||||
email_db = "System: data/email_db.pickle not found"
|
||||
|
||||
try:
|
||||
with open('../data/sms_db.pickle', 'rb') as f:
|
||||
sms_db = pickle.load(f)
|
||||
except Exception as e:
|
||||
try:
|
||||
with open('data/sms_db.pickle', 'rb') as f:
|
||||
sms_db = pickle.load(f)
|
||||
except Exception as e:
|
||||
sms_db = "System: data/sms_db.pickle not found"
|
||||
|
||||
|
||||
# Game HS tables
|
||||
try:
|
||||
with open('../data/lemonstand.pkl', 'rb') as f:
|
||||
@@ -90,6 +111,10 @@ print ("System: bbs_messages")
|
||||
print (bbs_messages)
|
||||
print ("\nSystem: bbs_dm")
|
||||
print (bbs_dm)
|
||||
print ("\nSystem: email_db")
|
||||
print (email_db)
|
||||
print ("\nSystem: sms_db")
|
||||
print (sms_db)
|
||||
print (f"\n\nGame HS tables\n")
|
||||
print (f"lemon:{lemon_score}")
|
||||
print (f"dopewar:{dopewar_score}")
|
||||
|
||||
@@ -37,11 +37,13 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n
|
||||
"bbspost": lambda: handle_bbspost(message, message_from_id, deviceID),
|
||||
"bbsread": lambda: handle_bbsread(message),
|
||||
"blackjack": lambda: handleBlackJack(message, message_from_id, deviceID),
|
||||
"clearsms:": lambda: handle_sms(message_from_id, message),
|
||||
"cmd": lambda: help_message,
|
||||
"cq": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number),
|
||||
"cqcq": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number),
|
||||
"cqcqcq": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number),
|
||||
"dopewars": lambda: handleDopeWars(message, message_from_id, deviceID),
|
||||
"email:": lambda: handle_email(message_from_id, message),
|
||||
"games": lambda: gamesCmdList,
|
||||
"globalthermonuclearwar": lambda: handle_gTnW(),
|
||||
"golfsim": lambda: handleGolf(message, message_from_id, deviceID),
|
||||
@@ -59,7 +61,10 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n
|
||||
"pong": lambda: "🏓PING!!🛜",
|
||||
"readnews": lambda: read_news(),
|
||||
"rlist": lambda: handle_repeaterQuery(message_from_id, deviceID, channel_number),
|
||||
"setemail:": lambda: handle_email(message_from_id, message),
|
||||
"setsms:": lambda: handle_sms( message_from_id, message),
|
||||
"sitrep": lambda: handle_lheard(message, message_from_id, deviceID, isDM),
|
||||
"sms:": lambda: handle_sms(message_from_id, message),
|
||||
"solar": lambda: drap_xray_conditions() + "\n" + solar_conditions(),
|
||||
"sun": lambda: handle_sun(message_from_id, deviceID, channel_number),
|
||||
"test": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number),
|
||||
@@ -1092,6 +1097,8 @@ async def start_rx():
|
||||
logger.debug(f"System: Weather Alert Broadcast Enabled on channels {wxAlertBroadcastChannel}")
|
||||
if emergency_responder_enabled:
|
||||
logger.debug(f"System: Emergency Responder Enabled on channels {emergency_responder_alert_channel} for interface {emergency_responder_alert_interface}")
|
||||
if enableSMTP:
|
||||
logger.debug(f"System: SMTP Email Alerting Enabled")
|
||||
if scheduler_enabled:
|
||||
# Examples of using the scheduler, Times here are in 24hr format
|
||||
# https://schedule.readthedocs.io/en/stable/
|
||||
|
||||
@@ -41,50 +41,53 @@ except Exception as e:
|
||||
if config.sections() == []:
|
||||
print(f"System: Error reading config file: {config_file} is empty or does not exist.")
|
||||
config['interface'] = {'type': 'serial', 'port': "/dev/ttyACM0", 'hostname': '', 'mac': ''}
|
||||
config['general'] = {'respond_by_dm_only': 'True', 'defaultChannel': '0', 'motd': MOTD,
|
||||
'welcome_message': WELCOME_MSG, 'zuluTime': 'False'}
|
||||
config['general'] = {'respond_by_dm_only': 'True', 'defaultChannel': '0', 'motd': MOTD, 'welcome_message': WELCOME_MSG, 'zuluTime': 'False'}
|
||||
config.write(open(config_file, 'w'))
|
||||
print (f"System: Config file created, check {config_file} or review the config.template")
|
||||
|
||||
if 'sentry' not in config:
|
||||
config['sentry'] = {'SentryEnabled': 'False', 'SentryChannel': '2', 'SentryHoldoff': '9', 'sentryIgnoreList': '', 'SentryRadius': '100'}
|
||||
config.write(open(config_file, 'w'))
|
||||
config['sentry'] = {'SentryEnabled': 'False', 'SentryChannel': '2', 'SentryHoldoff': '9', 'sentryIgnoreList': '', 'SentryRadius': '100'}
|
||||
config.write(open(config_file, 'w'))
|
||||
|
||||
if 'location' not in config:
|
||||
config['location'] = {'enabled': 'True', 'lat': '48.50', 'lon': '-123.0', 'UseMeteoWxAPI': 'False', 'useMetric': 'False', 'NOAAforecastDuration': '4', 'NOAAalertCount': '2', 'NOAAalertsEnabled': 'True', 'wxAlertBroadcastEnabled': 'False', 'wxAlertBroadcastChannel': '2', 'repeaterLookup': 'rbook'}
|
||||
config.write(open(config_file, 'w'))
|
||||
config['location'] = {'enabled': 'True', 'lat': '48.50', 'lon': '-123.0', 'UseMeteoWxAPI': 'False', 'useMetric': 'False', 'NOAAforecastDuration': '4', 'NOAAalertCount': '2', 'NOAAalertsEnabled': 'True', 'wxAlertBroadcastEnabled': 'False', 'wxAlertBroadcastChannel': '2', 'repeaterLookup': 'rbook'}
|
||||
config.write(open(config_file, 'w'))
|
||||
|
||||
if 'bbs' not in config:
|
||||
config['bbs'] = {'enabled': 'False', 'bbsdb': 'data/bbsdb.pkl', 'bbs_ban_list': '', 'bbs_admin_list': ''}
|
||||
config.write(open(config_file, 'w'))
|
||||
config['bbs'] = {'enabled': 'False', 'bbsdb': 'data/bbsdb.pkl', 'bbs_ban_list': '', 'bbs_admin_list': ''}
|
||||
config.write(open(config_file, 'w'))
|
||||
|
||||
if 'repeater' not in config:
|
||||
config['repeater'] = {'enabled': 'False', 'repeater_channels': ''}
|
||||
config.write(open(config_file, 'w'))
|
||||
config['repeater'] = {'enabled': 'False', 'repeater_channels': ''}
|
||||
config.write(open(config_file, 'w'))
|
||||
|
||||
if 'radioMon' not in config:
|
||||
config['radioMon'] = {'enabled': 'False', 'rigControlServerAddress': 'localhost:4532', 'sigWatchBrodcastCh': '2', 'signalDetectionThreshold': '-10', 'signalHoldTime': '10', 'signalCooldown': '5', 'signalCycleLimit': '5'}
|
||||
config.write(open(config_file, 'w'))
|
||||
config['radioMon'] = {'enabled': 'False', 'rigControlServerAddress': 'localhost:4532', 'sigWatchBrodcastCh': '2', 'signalDetectionThreshold': '-10', 'signalHoldTime': '10', 'signalCooldown': '5', 'signalCycleLimit': '5'}
|
||||
config.write(open(config_file, 'w'))
|
||||
|
||||
if 'games' not in config:
|
||||
config['games'] = {'dopeWars': 'True', 'lemonade': 'True', 'blackjack': 'True', 'videoPoker': 'True'}
|
||||
config.write(open(config_file, 'w'))
|
||||
config['games'] = {'dopeWars': 'True', 'lemonade': 'True', 'blackjack': 'True', 'videoPoker': 'True'}
|
||||
config.write(open(config_file, 'w'))
|
||||
|
||||
if 'messagingSettings' not in config:
|
||||
config['messagingSettings'] = {'responseDelay': '0.7', 'splitDelay': '0', 'MESSAGE_CHUNK_SIZE': '160'}
|
||||
config.write(open(config_file, 'w'))
|
||||
config['messagingSettings'] = {'responseDelay': '0.7', 'splitDelay': '0', 'MESSAGE_CHUNK_SIZE': '160'}
|
||||
config.write(open(config_file, 'w'))
|
||||
|
||||
if 'fileMon' not in config:
|
||||
config['fileMon'] = {'enabled': 'False', 'file_path': 'alert.txt', 'broadcastCh': '2'}
|
||||
config.write(open(config_file, 'w'))
|
||||
config['fileMon'] = {'enabled': 'False', 'file_path': 'alert.txt', 'broadcastCh': '2'}
|
||||
config.write(open(config_file, 'w'))
|
||||
|
||||
if 'scheduler' not in config:
|
||||
config['scheduler'] = {'enabled': 'False'}
|
||||
config.write(open(config_file, 'w'))
|
||||
config['scheduler'] = {'enabled': 'False'}
|
||||
config.write(open(config_file, 'w'))
|
||||
|
||||
if 'emergencyHandler' not in config:
|
||||
config['emergencyHandler'] = {'enabled': 'False', 'alert_channel': '2', 'alert_interface': '1', 'email': ''}
|
||||
config.write(open(config_file, 'w'))
|
||||
config['emergencyHandler'] = {'enabled': 'False', 'alert_channel': '2', 'alert_interface': '1', 'email': ''}
|
||||
config.write(open(config_file, 'w'))
|
||||
|
||||
if 'smtp' not in config:
|
||||
config['smtp'] = {'sysopEmails': '', 'enableSMTP': 'False', 'enableImap': 'False'}
|
||||
config.write(open(config_file, 'w'))
|
||||
|
||||
# interface1 settings
|
||||
interface1_type = config['interface'].get('type', 'serial')
|
||||
@@ -102,7 +105,7 @@ if 'interface2' in config:
|
||||
else:
|
||||
interface2_enabled = False
|
||||
|
||||
# variables
|
||||
# variables from the config.ini file
|
||||
try:
|
||||
# general
|
||||
useDMForResponse = config['general'].getboolean('respond_by_dm_only', True)
|
||||
@@ -161,7 +164,7 @@ try:
|
||||
wxAlertBroadcastChannel = config['location'].get('wxAlertBroadcastCh').split(',')
|
||||
else:
|
||||
wxAlertBroadcastChannel = config['location'].getint('wxAlertBroadcastCh', 2) # default 2
|
||||
|
||||
|
||||
# bbs
|
||||
bbs_enabled = config['bbs'].getboolean('enabled', False)
|
||||
bbsdb = config['bbs'].get('bbsdb', 'data/bbsdb.pkl')
|
||||
@@ -170,6 +173,26 @@ try:
|
||||
bbs_link_enabled = config['bbs'].getboolean('bbslink_enabled', False)
|
||||
bbs_link_whitelist = config['bbs'].get('bbslink_whitelist', '').split(',')
|
||||
|
||||
# E-Mail Settings
|
||||
sysopEmails = config['smtp'].get('sysopEmails', '').split(',')
|
||||
enableSMTP = config['smtp'].getboolean('enableSMTP', False)
|
||||
enableImap = config['smtp'].getboolean('enableImap', False)
|
||||
|
||||
# SMTP settings (required for outbound email/sms)
|
||||
SMTP_SERVER = "smtp.gmail.com" # Replace with your SMTP server
|
||||
SMTP_PORT = 587 # 587 SMTP over TLS/STARTTLS, 25 legacy SMTP
|
||||
FROM_EMAIL = "your_email@gmail.com" # Sender email: be mindful of public access, don't use your personal email
|
||||
SMTP_USERNAME = "your_email@gmail.com" # Sender email username
|
||||
SMTP_PASSWORD = "your_app_password" # Sender email password
|
||||
EMAIL_SUBJECT = "Meshtastic✉️"
|
||||
|
||||
# IMAP settings (inbound email)
|
||||
IMAP_SERVER = "imap.gmail.com" # Replace with your IMAP server
|
||||
IMAP_PORT = 993 # 993 IMAP over TLS/SSL, 143 legacy IMAP
|
||||
IMAP_USERNAME = SMTP_USERNAME # IMAP username usually same as SMTP
|
||||
IMAP_PASSWORD = SMTP_PASSWORD # IMAP password usually same as SMTP
|
||||
IMAP_FOLDER = "inbox" # IMAP folder to monitor for new messages
|
||||
|
||||
# repeater
|
||||
repeater_enabled = config['repeater'].getboolean('enabled', False)
|
||||
repeater_channels = config['repeater'].get('repeater_channels', '').split(',')
|
||||
|
||||
103
modules/smtp.py
103
modules/smtp.py
@@ -10,26 +10,7 @@ import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
|
||||
# System settings
|
||||
sysopEmails = ["spud@demo.net", ] # list of authorized emails for sysop control
|
||||
|
||||
# SMTP settings (required for outbound email/sms)
|
||||
SMTP_SERVER = "smtp.gmail.com" # Replace with your SMTP server
|
||||
SMTP_PORT = 587 # 587 SMTP over TLS/STARTTLS, 25 legacy SMTP
|
||||
FROM_EMAIL = "your_email@gmail.com" # Sender email: be mindful of public access, don't use your personal email
|
||||
SMTP_USERNAME = "your_email@gmail.com" # Sender email username
|
||||
SMTP_PASSWORD = "your_app_password" # Sender email password
|
||||
EMAIL_SUBJECT = "Meshtastic✉️"
|
||||
|
||||
# IMAP settings (inbound email)
|
||||
enableImap = False
|
||||
IMAP_SERVER = "imap.gmail.com" # Replace with your IMAP server
|
||||
IMAP_PORT = 993 # 993 IMAP over TLS/SSL, 143 legacy IMAP
|
||||
IMAP_USERNAME = SMTP_USERNAME # IMAP username usually same as SMTP
|
||||
IMAP_PASSWORD = SMTP_PASSWORD # IMAP password usually same as SMTP
|
||||
IMAP_FOLDER = "inbox" # IMAP folder to monitor for new messages
|
||||
|
||||
# System variables // Do not edit
|
||||
# System variables
|
||||
trap_list_smtp = ("email:", "setemail:", "sms:", "setsms:", "clearsms:")
|
||||
smtpThrottle = {}
|
||||
|
||||
@@ -38,7 +19,6 @@ if enableImap:
|
||||
import imaplib
|
||||
import email
|
||||
|
||||
|
||||
# Send email
|
||||
def send_email(to_email, message, nodeID=0):
|
||||
global smtpThrottle
|
||||
@@ -54,7 +34,7 @@ def send_email(to_email, message, nodeID=0):
|
||||
if nodeID in bbs_ban_list:
|
||||
logger.warning("System: Email blocked for " + nodeID)
|
||||
return "⛔️Email throttled, try again later"
|
||||
|
||||
|
||||
try:
|
||||
# Create message
|
||||
msg = MIMEMultipart()
|
||||
@@ -130,21 +110,20 @@ except:
|
||||
|
||||
def store_email(nodeID, email):
|
||||
global email_db
|
||||
|
||||
# if not in db, add it
|
||||
logger.debug("System: Setting E-Mail for " + nodeID)
|
||||
if nodeID not in email_db:
|
||||
email_db[nodeID] = email
|
||||
return True
|
||||
# if in db, update it
|
||||
email_db[nodeID] = email
|
||||
|
||||
# save to a pickle for persistence, this is a simple db, be mindful of risk
|
||||
with open('data/email_db.pickle', 'wb') as f:
|
||||
pickle.dump(email_db, f)
|
||||
f.close()
|
||||
return True
|
||||
|
||||
|
||||
# initalize SMS db
|
||||
sms_db = {}
|
||||
sms_db = [{'nodeID': 0, 'sms':[]}]
|
||||
try:
|
||||
with open('data/sms_db.pickle', 'rb') as f:
|
||||
sms_db = pickle.load(f)
|
||||
@@ -155,32 +134,45 @@ except:
|
||||
|
||||
def store_sms(nodeID, sms):
|
||||
global sms_db
|
||||
# if not in db, add it
|
||||
logger.debug("System: Setting SMS for " + nodeID)
|
||||
if nodeID not in sms_db:
|
||||
sms_db[nodeID] = sms
|
||||
return True
|
||||
# if in db, append it
|
||||
sms_db[nodeID].append(sms)
|
||||
try:
|
||||
logger.debug("System: Setting SMS for " + str(nodeID))
|
||||
# if not in db, add it
|
||||
if nodeID not in sms_db:
|
||||
sms_db.append({'nodeID': nodeID, 'sms': sms})
|
||||
else:
|
||||
# if in db, update it
|
||||
for item in sms_db:
|
||||
if item['nodeID'] == nodeID:
|
||||
item['sms'].append(sms)
|
||||
|
||||
# save to a pickle for persistence, this is a simple db, be mindful of risk
|
||||
with open('data/sms_db.pickle', 'wb') as f:
|
||||
pickle.dump(sms_db, f)
|
||||
return True
|
||||
# save to a pickle for persistence, this is a simple db, be mindful of risk
|
||||
with open('data/sms_db.pickle', 'wb') as f:
|
||||
pickle.dump(sms_db, f)
|
||||
f.close()
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("System: Failed to store SMS: " + str(e))
|
||||
return False
|
||||
|
||||
def handle_sms(nodeID, message):
|
||||
global sms_db
|
||||
# if clearsms, remove all sms for node
|
||||
if message.lower.startswith("clearsms:"):
|
||||
if nodeID in sms_db:
|
||||
del sms_db[nodeID]
|
||||
if message.lower().startswith("clearsms:"):
|
||||
if any(item['nodeID'] == nodeID for item in sms_db):
|
||||
# remove record from db for nodeID
|
||||
sms_db = [item for item in sms_db if item['nodeID'] != nodeID]
|
||||
# update the pickle
|
||||
with open('data/sms_db.pickle', 'wb') as f:
|
||||
pickle.dump(sms_db, f)
|
||||
f.close()
|
||||
return "📲 address cleared"
|
||||
return "📲No address to clear"
|
||||
|
||||
# send SMS to SMS in db. if none ask for one
|
||||
if message.lower.startswith("setsms:"):
|
||||
if message.lower().startswith("setsms:"):
|
||||
message = message.split(" ", 1)
|
||||
if len(message) < 5:
|
||||
return "?📲setsms example@phone.co"
|
||||
if len(message[1]) < 5:
|
||||
return "?📲setsms: example@phone.co"
|
||||
if "@" not in message[1] and "." not in message[1]:
|
||||
return "📲Please provide a valid email address"
|
||||
if store_sms(nodeID, message[1]):
|
||||
@@ -188,14 +180,17 @@ def handle_sms(nodeID, message):
|
||||
else:
|
||||
return "⛔️Failed to set address"
|
||||
|
||||
if message.lower.startswith("sms:"):
|
||||
if message.lower().startswith("sms:"):
|
||||
message = message.split(" ", 1)
|
||||
if nodeID in sms_db:
|
||||
if any(item['nodeID'] == nodeID for item in sms_db):
|
||||
count = 0
|
||||
for address in sms_db[nodeID]:
|
||||
count += 1
|
||||
logger.info("System: Sending SMS for " + nodeID)
|
||||
send_email(address, message[1], nodeID)
|
||||
# for all dict items maching nodeID in sms_db send sms
|
||||
for item in sms_db:
|
||||
if item['nodeID'] == nodeID:
|
||||
smsEmail = item['sms']
|
||||
logger.info("System: Sending SMS for " + str(nodeID) + " to " + smsEmail[:-6])
|
||||
send_email(smsEmail, message[1], nodeID)
|
||||
count += 1
|
||||
return f"📲SMS-sent {count} 📤"
|
||||
else:
|
||||
return "📲No address set, use 📲setsms"
|
||||
@@ -203,11 +198,10 @@ def handle_sms(nodeID, message):
|
||||
return "Error: ⛔️ not understood. use:setsms example@phone.co"
|
||||
|
||||
def handle_email(nodeID, message):
|
||||
global email_db
|
||||
# send email to email in db. if none ask for one
|
||||
if message.lower.startswith("setemail:"):
|
||||
if message.lower().startswith("setemail:"):
|
||||
message = message.split(" ", 1)
|
||||
if len(message) < 5:
|
||||
return "?📧setemail example@none.net"
|
||||
if "@" not in message[1] and "." not in message[1]:
|
||||
return "📧Please provide a valid email address"
|
||||
if store_email(nodeID, message[1]):
|
||||
@@ -215,7 +209,7 @@ def handle_email(nodeID, message):
|
||||
|
||||
return "Error: ⛔️ not understood. use:setmail bob@example.com"
|
||||
|
||||
if message.lower.startswith("email:"):
|
||||
if message.lower().startswith("email:"):
|
||||
message = message.split(" ", 1)
|
||||
|
||||
# if user sent: email bob@none.net # Hello Bob
|
||||
@@ -233,4 +227,3 @@ def handle_email(nodeID, message):
|
||||
|
||||
return "Error: ⛔️ not understood. use:email bob@example.com # Hello Bob"
|
||||
|
||||
|
||||
@@ -38,6 +38,12 @@ if motd_enabled:
|
||||
trap_list = trap_list + trap_list_motd
|
||||
help_message = help_message + ", motd"
|
||||
|
||||
# SMTP Configuration
|
||||
if enableSMTP:
|
||||
from modules.smtp import * # from the spudgunman/meshing-around repo
|
||||
trap_list = trap_list + trap_list_smtp
|
||||
help_message = help_message + ", email, sms"
|
||||
|
||||
# Emergency Responder Configuration
|
||||
if emergency_responder_enabled:
|
||||
trap_list_emergency = ("emergency", "911", "112", "999", "police", "fire", "ambulance", "rescue")
|
||||
|
||||
Reference in New Issue
Block a user