Replace toml with humanfriendly library for email alert and update send\_email function with event details
* Update import statement in email_alert.py to include humanfriendly library * Modify send_email function signature in email_alert.py to accept an event dictionary instead of separate arguments * Extract event name, description, location, date, and time until event from the event dictionary and use them in the email body * Update email body format string accordingly * Use humanfriendly library to format time difference between next alert and next event into a human-readable format.
This commit is contained in:
parent
236a311b5c
commit
92e1578bbc
|
@ -2,12 +2,9 @@ import smtplib
|
||||||
from email.mime.multipart import MIMEMultipart
|
from email.mime.multipart import MIMEMultipart
|
||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
import ssl
|
import ssl
|
||||||
import toml
|
import humanfriendly
|
||||||
|
|
||||||
def send_email(event_summary, next_alert, next_event, config):
|
|
||||||
with open('config.toml', 'r') as f:
|
|
||||||
config = toml.load(f)
|
|
||||||
|
|
||||||
|
def send_email(event, next_alert, next_event, config):
|
||||||
# Set up the SMTP server details
|
# Set up the SMTP server details
|
||||||
smtp_server = config["email"]["smtp_server"]
|
smtp_server = config["email"]["smtp_server"]
|
||||||
port = config["email"]["port"]
|
port = config["email"]["port"]
|
||||||
|
@ -16,9 +13,13 @@ def send_email(event_summary, next_alert, next_event, config):
|
||||||
password = config["email"]["password"]
|
password = config["email"]["password"]
|
||||||
|
|
||||||
# Event details
|
# Event details
|
||||||
event_name = event_summary
|
event_name = event["summary"]
|
||||||
|
event_description = event["description"]
|
||||||
|
event_location = event["location"]
|
||||||
event_date = next_event
|
event_date = next_event
|
||||||
event_delta = next_event - next_alert
|
event_delta = next_event - next_alert
|
||||||
|
total_seconds = event_delta.total_seconds()
|
||||||
|
human_readable_time = humanfriendly.format_timespan(total_seconds)
|
||||||
|
|
||||||
# Create a multipart message and set headers
|
# Create a multipart message and set headers
|
||||||
message = MIMEMultipart()
|
message = MIMEMultipart()
|
||||||
|
@ -29,9 +30,19 @@ def send_email(event_summary, next_alert, next_event, config):
|
||||||
# Add body to email
|
# Add body to email
|
||||||
body = """\
|
body = """\
|
||||||
Hi,
|
Hi,
|
||||||
This is an alert for the event named '{}' which will occur on {}.
|
This is an event alert from remindme_caldav.
|
||||||
This event will start in '{}'
|
|
||||||
""".format(event_name, event_date, event_delta)
|
Event details:
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
Event name: {}
|
||||||
|
Date/time: {}
|
||||||
|
Description: {}
|
||||||
|
Location: {}
|
||||||
|
Time until event: {}
|
||||||
|
|
||||||
|
---------------------------------
|
||||||
|
""".format(event_name, event_date, event_description, event_location, human_readable_time)
|
||||||
message.attach(MIMEText(body, "plain"))
|
message.attach(MIMEText(body, "plain"))
|
||||||
text = message.as_string()
|
text = message.as_string()
|
||||||
|
|
||||||
|
@ -45,7 +56,8 @@ def send_email(event_summary, next_alert, next_event, config):
|
||||||
|
|
||||||
# Send email here
|
# Send email here
|
||||||
server.sendmail(sender_email, receiver_email, text)
|
server.sendmail(sender_email, receiver_email, text)
|
||||||
|
print("Message sent via email")
|
||||||
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Print any error messages to stdout
|
# Print any error messages to stdout
|
||||||
print(e)
|
print("An error occured when sending alert via email, please check your config. Message: {}".format(e))
|
||||||
return print("Message sent via email")
|
|
||||||
|
|
|
@ -6,8 +6,7 @@ import datetime as dt
|
||||||
import time
|
import time
|
||||||
from watchdog.observers import Observer
|
from watchdog.observers import Observer
|
||||||
from watchdog.events import FileSystemEventHandler
|
from watchdog.events import FileSystemEventHandler
|
||||||
import email_alert
|
import email_alert, xmpp_alert
|
||||||
#import xmpp_alert
|
|
||||||
|
|
||||||
# Parse args
|
# Parse args
|
||||||
parser = argparse.ArgumentParser(description="A simple calendar alerting daemon written in Python")
|
parser = argparse.ArgumentParser(description="A simple calendar alerting daemon written in Python")
|
||||||
|
@ -33,6 +32,10 @@ except Exception as e:
|
||||||
sys.exit(1) # Exit with error code
|
sys.exit(1) # Exit with error code
|
||||||
|
|
||||||
cal_dir = config["app"]["calendar_dir"]
|
cal_dir = config["app"]["calendar_dir"]
|
||||||
|
# Check if the path is a directory
|
||||||
|
if not os.path.isdir(cal_dir):
|
||||||
|
print("The provided path to .ics files does not exist: '{}'".format(cal_dir))
|
||||||
|
exit(1)
|
||||||
|
|
||||||
# Get all .ics files from your directory
|
# Get all .ics files from your directory
|
||||||
files = glob.glob(os.path.join(cal_dir, '*.ics'))
|
files = glob.glob(os.path.join(cal_dir, '*.ics'))
|
||||||
|
@ -47,9 +50,12 @@ class FileChangeHandler(FileSystemEventHandler):
|
||||||
def on_modified(self, event):
|
def on_modified(self, event):
|
||||||
print(f"File modified: {event.src_path}")
|
print(f"File modified: {event.src_path}")
|
||||||
if not event.is_directory: # If it's a file and not a directory
|
if not event.is_directory: # If it's a file and not a directory
|
||||||
print(str(dt.datetime.now()), "Sync detected, updating events")
|
try:
|
||||||
with open(event.src_path, 'r') as f:
|
with open(event.src_path, 'r') as f:
|
||||||
cal_str = f.read()
|
cal_str = f.read()
|
||||||
|
except Exception as e:
|
||||||
|
print("Not a valid file: {}. Error: {}".format(event.src_path, e))
|
||||||
|
return
|
||||||
|
|
||||||
# Parse the calendar
|
# Parse the calendar
|
||||||
try:
|
try:
|
||||||
|
@ -95,8 +101,12 @@ class FileChangeHandler(FileSystemEventHandler):
|
||||||
if not event.is_directory: # If it's a file and not a directory
|
if not event.is_directory: # If it's a file and not a directory
|
||||||
print(str(dt.datetime.now()), "Sync detected, updating events")
|
print(str(dt.datetime.now()), "Sync detected, updating events")
|
||||||
|
|
||||||
with open(event.src_path, 'r') as f:
|
try:
|
||||||
cal_str = f.read()
|
with open(event.src_path, 'r') as f:
|
||||||
|
cal_str = f.read()
|
||||||
|
except Exception as e:
|
||||||
|
print("Not a valid file: {}. Error: {}".format(event.src_path, e))
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
event_dict = calendar_parser(cal_str)
|
event_dict = calendar_parser(cal_str)
|
||||||
|
@ -118,10 +128,13 @@ class FileChangeHandler(FileSystemEventHandler):
|
||||||
def calculate_recur_dates(dtstart, vrecur):
|
def calculate_recur_dates(dtstart, vrecur):
|
||||||
rule_str = "RRULE:{}".format(vrecur.to_ical().decode('utf-8'))
|
rule_str = "RRULE:{}".format(vrecur.to_ical().decode('utf-8'))
|
||||||
start_date = dtstart
|
start_date = dtstart
|
||||||
|
infinite_recur = False
|
||||||
|
freq = vrecur.get('FREQ')[0]
|
||||||
|
interval = vrecur.get('INTERVAL')[0]
|
||||||
|
current_date = dt.datetime.now().replace(tzinfo=pytz.UTC)
|
||||||
|
|
||||||
if vrecur.get("COUNT") is None:
|
if vrecur.get("COUNT") is None:
|
||||||
# If no COUNT, calculate an end date based on FREQ and INTERVAL to prevent generating too many dates
|
# If no COUNT, calculate an end date based on FREQ and INTERVAL to prevent generating too many dates
|
||||||
freq = vrecur.get('FREQ')[0]
|
|
||||||
interval = vrecur.get('INTERVAL')[0]
|
|
||||||
|
|
||||||
delta = None
|
delta = None
|
||||||
|
|
||||||
|
@ -133,19 +146,20 @@ def calculate_recur_dates(dtstart, vrecur):
|
||||||
delta = relativedelta(years=interval)
|
delta = relativedelta(years=interval)
|
||||||
|
|
||||||
count = 0
|
count = 0
|
||||||
current_date = dt.datetime.now().replace(tzinfo=pytz.UTC)
|
|
||||||
origin_date = start_date
|
origin_date = start_date
|
||||||
while origin_date < current_date:
|
while origin_date < current_date:
|
||||||
count += interval
|
count += interval
|
||||||
origin_date += delta*interval
|
origin_date += delta*interval
|
||||||
|
|
||||||
rule_str += ";COUNT={}".format(count+10)
|
rule_str += ";COUNT={}".format(count+10)
|
||||||
|
infinite_recur = True
|
||||||
|
|
||||||
ruleset = rrulestr(rule_str, dtstart=start_date)
|
ruleset = rrulestr(rule_str, dtstart=start_date)
|
||||||
|
|
||||||
# Generate future dates according to the rules
|
# Generate future dates according to the rules
|
||||||
dates = list(ruleset)
|
dates = list(ruleset)
|
||||||
return [d for d in dates if d > start_date]
|
n_recur = len([d for d in dates if d > current_date])
|
||||||
|
return [d for d in dates if d > start_date], infinite_recur, freq, interval, n_recur
|
||||||
|
|
||||||
def calendar_parser(cal_str):
|
def calendar_parser(cal_str):
|
||||||
# Parse the calendar
|
# Parse the calendar
|
||||||
|
@ -158,10 +172,17 @@ def calendar_parser(cal_str):
|
||||||
dtstart = dtstart if isinstance(dtstart, dt.datetime) else dt.datetime.combine(dtstart, dt.time.min) # Ensure dates are always as datetime
|
dtstart = dtstart if isinstance(dtstart, dt.datetime) else dt.datetime.combine(dtstart, dt.time.min) # Ensure dates are always as datetime
|
||||||
dtstart = dtstart.replace(tzinfo=pytz.UTC)
|
dtstart = dtstart.replace(tzinfo=pytz.UTC)
|
||||||
summary = component.get("SUMMARY")
|
summary = component.get("SUMMARY")
|
||||||
|
description = component.get("DESCRIPTION")
|
||||||
|
location = component.get("LOCATION")
|
||||||
vrecur = component.get("RRULE")
|
vrecur = component.get("RRULE")
|
||||||
recur_dates = [None]
|
recur_dates = [None]
|
||||||
|
recur_info=None
|
||||||
if vrecur is not None:
|
if vrecur is not None:
|
||||||
recur_dates = calculate_recur_dates(dtstart, vrecur)
|
recur_dates, infinite_recur, freq, interval, n_recur = calculate_recur_dates(dtstart, vrecur)
|
||||||
|
if infinite_recur:
|
||||||
|
recur_info = "Number of recurs: {}, Interval: {}, Freq: {}".format(str(n_recur)+"++", interval, freq)
|
||||||
|
else:
|
||||||
|
recur_info = "Number of recurs: {}, Interval: {}, Freq: {}".format(str(n_recur), interval, freq)
|
||||||
|
|
||||||
valarm_list = [] # List to hold all VALARM for this event
|
valarm_list = [] # List to hold all VALARM for this event
|
||||||
for subcomponent in component.walk(): # Find all VALARMs for this VEVENT
|
for subcomponent in component.walk(): # Find all VALARMs for this VEVENT
|
||||||
|
@ -169,7 +190,7 @@ def calendar_parser(cal_str):
|
||||||
valarm = Event.from_ical(subcomponent.to_ical())
|
valarm = Event.from_ical(subcomponent.to_ical())
|
||||||
timedelta = valarm.get("TRIGGER").dt
|
timedelta = valarm.get("TRIGGER").dt
|
||||||
valarm_list.append(timedelta) # Add this VALARM to the list
|
valarm_list.append(timedelta) # Add this VALARM to the list
|
||||||
event_dict = {"uid": str(uid), "dtstart": dtstart, "summary": summary, "recur_dates": recur_dates, "valarm": valarm_list, "alert_history": []}
|
event_dict = {"uid": str(uid), "dtstart": dtstart, "summary": summary, "description": description, "location": location, "recur_dates": recur_dates, "recur_info": recur_info, "valarm": valarm_list, "alert_history": []}
|
||||||
handler = FileChangeHandler() # Create an instance of the FileChangeHandler class
|
handler = FileChangeHandler() # Create an instance of the FileChangeHandler class
|
||||||
new_hash = handler.calculate_event_hash(event_dict) # Calculate the hash of the event dictionary
|
new_hash = handler.calculate_event_hash(event_dict) # Calculate the hash of the event dictionary
|
||||||
event_dict["hash"] = new_hash # Store the hash in the event dictionary
|
event_dict["hash"] = new_hash # Store the hash in the event dictionary
|
||||||
|
@ -195,6 +216,23 @@ def get_next_alert(event, current_time):
|
||||||
next_alert = min(next_alert_list)
|
next_alert = min(next_alert_list)
|
||||||
return next_alert - dt.timedelta(seconds=5), next_event
|
return next_alert - dt.timedelta(seconds=5), next_event
|
||||||
|
|
||||||
|
def process_alert(current_time, next_alert, event):
|
||||||
|
if current_time >= next_alert and next_alert < next_alert + dt.timedelta(seconds=15):
|
||||||
|
if len(event["alert_history"]) == 0:
|
||||||
|
print("First alert for '{}' detected".format(event["summary"]))
|
||||||
|
event["alert_history"] = [{"timestamp_alert_triggered": current_time, "alert_defintition_time": next_alert}]
|
||||||
|
elif next_alert in [i["alert_defintition_time"] for i in event["alert_history"]]:
|
||||||
|
return # continue is not needed here as it's the end of function
|
||||||
|
else:
|
||||||
|
print("Posting alert for {}!".format(event["summary"]))
|
||||||
|
event["alert_history"].append({"timestamp_alert_triggered": current_time, "alert_defintition_time": next_alert})
|
||||||
|
#xmpp_alert.send_xmpp(event["summary"], next_alert, next_event, args.config)
|
||||||
|
email_alert.send_email(event, next_alert, next_event, config)
|
||||||
|
xmpp_alert.send_xmpp(event, next_alert, next_event, config)
|
||||||
|
with open("alert_history", 'a') as f:
|
||||||
|
f.write(str(event)) # write expects a str not dict
|
||||||
|
return
|
||||||
|
|
||||||
# Create initial event_list using calendar_parser
|
# Create initial event_list using calendar_parser
|
||||||
event_list = [] # List to hold dictionaries for each event
|
event_list = [] # List to hold dictionaries for each event
|
||||||
for file in files:
|
for file in files:
|
||||||
|
@ -217,24 +255,12 @@ try:
|
||||||
next_alert, next_event = get_next_alert(event, current_time)
|
next_alert, next_event = get_next_alert(event, current_time)
|
||||||
if next_alert == None:
|
if next_alert == None:
|
||||||
continue
|
continue
|
||||||
monitor_status = "Current time: {}\nMonitoring: {}\nEvent date: {}\nNext alert on: {}\nAlert history: {}\n".format(current_time, event["summary"], next_event, next_alert, event["alert_history"])
|
monitor_status = "Current time: {}\nMonitoring: {}\nEvent date: {}\nRecur Dates: {}\nNext alert on: {}\nRecur info: {}\nAlert history: {}\n".format(current_time, event["summary"], next_event, [str(i) for i in event["recur_dates"]], next_alert, event["recur_info"], event["alert_history"])
|
||||||
with open("status", 'a') as f:
|
with open("status", 'a') as f:
|
||||||
# Write the output to the file
|
# Write the output to the file
|
||||||
f.write(monitor_status)
|
f.write(monitor_status)
|
||||||
f.write("\n")
|
f.write("\n")
|
||||||
if current_time >= next_alert and next_alert < next_alert + dt.timedelta(seconds=15):
|
process_alert(current_time, next_alert, event)
|
||||||
if len(event["alert_history"]) == 0:
|
|
||||||
print("First alert for '{}' detected".format(event["summary"]))
|
|
||||||
event["alert_history"] = [{"timestamp_alert_triggered": current_time, "alert_defintition_time": next_alert}]
|
|
||||||
elif next_alert in [i["alert_defintition_time"] for i in event["alert_history"]]:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
print("Posting alert for {}!".format(event["summary"]))
|
|
||||||
event["alert_history"].append({"timestamp_alert_triggered": current_time, "alert_defintition_time": next_alert})
|
|
||||||
#xmpp_alert.send_xmpp(event["summary"], next_alert, next_event, args.config)
|
|
||||||
email_alert.send_email(event["summary"], next_alert, next_event, args.config)
|
|
||||||
with open("alert_history", 'a') as f:
|
|
||||||
f.write(event)
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
observer.stop()
|
observer.stop()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import slixmpp
|
import slixmpp
|
||||||
import toml
|
import humanfriendly
|
||||||
|
|
||||||
class SendMsgBot(slixmpp.ClientXMPP):
|
class SendMsgBot(slixmpp.ClientXMPP):
|
||||||
def __init__(self, jid, password, recipient, message):
|
def __init__(self, jid, password, recipient, message):
|
||||||
|
@ -14,9 +14,14 @@ class SendMsgBot(slixmpp.ClientXMPP):
|
||||||
self.send_message(mto=self.recipient, mbody=self.msg, mtype='chat')
|
self.send_message(mto=self.recipient, mbody=self.msg, mtype='chat')
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
||||||
def send_xmpp(event_summary, next_alert, next_event, config):
|
def send_xmpp(event, next_alert, next_event, config):
|
||||||
with open('config.toml', 'r') as f:
|
event_name = event["summary"]
|
||||||
config = toml.load(f)
|
event_description = event["description"]
|
||||||
|
event_location = event["location"]
|
||||||
|
event_date = next_event
|
||||||
|
event_delta = next_event - next_alert
|
||||||
|
total_seconds = event_delta.total_seconds()
|
||||||
|
human_readable_time = humanfriendly.format_timespan(total_seconds)
|
||||||
|
|
||||||
jid = config["xmpp"]["jid"]
|
jid = config["xmpp"]["jid"]
|
||||||
password = config["xmpp"]["password"] # replace with your password
|
password = config["xmpp"]["password"] # replace with your password
|
||||||
|
@ -24,13 +29,25 @@ def send_xmpp(event_summary, next_alert, next_event, config):
|
||||||
|
|
||||||
message = """\
|
message = """\
|
||||||
Hi,
|
Hi,
|
||||||
This is an alert for the event named '{}' which will occur on {}.
|
This is an event alert from remindme_caldav.
|
||||||
This event will start in '{}'
|
|
||||||
""".format(event_summary, next_event, next_alert)
|
|
||||||
|
|
||||||
bot = SendMsgBot(jid, password, recipient, message)
|
Event details:
|
||||||
bot.register_plugin('xep_0030') # Service Discovery
|
---------------------------------
|
||||||
bot.register_plugin('xep_0199') # XMPP Ping
|
|
||||||
bot.connect()
|
Event name: {}
|
||||||
bot.process(forever=False)
|
Date/time: {}
|
||||||
return print("Message sent via XMPP")
|
Description: {}
|
||||||
|
Location: {}
|
||||||
|
Time until event: {}
|
||||||
|
|
||||||
|
---------------------------------
|
||||||
|
""".format(event_name, event_date, event_description, event_location, human_readable_time)
|
||||||
|
try:
|
||||||
|
bot = SendMsgBot(jid, password, recipient, message)
|
||||||
|
bot.register_plugin('xep_0030') # Service Discovery
|
||||||
|
bot.register_plugin('xep_0199') # XMPP Ping
|
||||||
|
bot.connect()
|
||||||
|
bot.process(forever=False)
|
||||||
|
return print("Message sent via XMPP")
|
||||||
|
except Exception as e:
|
||||||
|
print("An error occured when sending alert via XMPP, please check your config. Message: {}".format(e))
|
||||||
|
|
Loading…
Reference in New Issue