3 from smtplib import SMTP_SSL
7 from imapclient import IMAPClient, SEEN
8 from dateutil.parser import parse
9 from croniter import croniter
12 IMAP_USER = os.environ["REMINDME_IMAP_USER"]
13 IMAP_PASS = os.environ["REMINDME_IMAP_PASS"]
14 IMAP_HOST = os.environ["REMINDME_IMAP_HOST"]
15 SMTP_USER = os.environ.get("REMINDME_SMTP_USER", IMAP_USER)
16 SMTP_PASS = os.environ.get("REMINDME_IMAP_PASS", IMAP_PASS)
17 SMTP_HOST = os.environ.get("REMINDME_IMAP_HOST", IMAP_HOST)
27 __iclient = IMAPClient(IMAP_HOST, use_uid=True)
28 __iclient.login(IMAP_USER, IMAP_PASS)
30 __iclient.select_folder("INBOX")
34 def make_reply(orig, subject, body):
35 reply = email.message.Message()
36 reply["Subject"] = subject
37 reply["In-Reply-To"] = orig["Message-ID"]
38 reply["References"] = orig["Message-ID"]
39 reply.set_payload(body)
40 reply["From"] = "remindme@friedersdorff.com"
41 reply["To"] = orig["From"]
45 def ls(msg, reminders):
47 for uid, reminder in reminders:
48 body = reminder.get_payload().strip()
49 reply_lines.append(f"({uid}) {reminder['subject']}: {body}")
51 return make_reply(msg, "Your current reminders", "\n".join(reply_lines))
54 def ack_reminder(uid, reminder):
55 body = reminder.get_payload().strip()
56 reply_body = f"({uid}) {reminder['subject']}: {body}"
57 return make_reply(reminder, f"Received Reminder: {reminder['subject']}", reply_body)
60 def remind(last_time, start_time, reminder):
61 f_line = reminder.get_payload().strip().splitlines()[0]
62 if f_line.lower().startswith("repeat"):
63 cron_payload = f_line[6:].strip()
64 it = croniter(cron_payload, last_time)
65 next_time = it.get_next(datetime.datetime)
66 if next_time > last_time and next_time <= start_time:
70 reminder, f"Re: {reminder['subject']} now", reminder.get_payload()
77 parsed_time = parse(f_line, fuzzy=True)
81 if not parsed_time.tzinfo:
82 send_date = email.utils.parsedate_to_datetime(reminder["Date"])
83 sender_tz = send_date.tzinfo or datetime.timezone.utc
84 parsed_time = parsed_time.replace(tzinfo=sender_tz)
86 if datetime.datetime.now(datetime.timezone.utc) > parsed_time:
90 reminder, f"Re: {reminder['subject']} now", reminder.get_payload()
95 def main(last_time, start_time):
96 msgs = iclient().search(["ALL"])
97 new_rems, old_rems, metas, to_delete, to_send = [], [], [], [], []
98 for uid, data in iclient().fetch(msgs, ["BODY.PEEK[]", "FLAGS"]).items():
99 mail = email.message_from_bytes(data[b"BODY[]"])
100 if mail.is_multipart():
101 to_delete.append(uid)
103 payload = mail.get_payload().strip()
104 if payload.lower().startswith("help") or payload.lower().startswith("list"):
106 to_delete.append(uid)
108 if SEEN in data[b"FLAGS"]:
109 old_rems.append((uid, mail))
111 new_rems.append((uid, mail))
114 if meta.get_payload().strip().startswith("help"):
115 to_send.append(make_reply(meta, "Remindme Help", USAGE))
116 elif meta.get_payload().strip().startswith("list"):
117 to_send.append(ls(meta, old_rems + new_rems))
119 for uid, reminder in new_rems:
120 to_send.append(ack_reminder(uid, reminder))
122 for uid, reminder in old_rems + new_rems:
123 done, reply = remind(last_time, start_time, reminder)
125 to_send.append(reply)
127 to_delete.append(uid)
129 iclient().delete_messages(to_delete)
131 iclient().add_flags([u[0] for u in new_rems], SEEN)
133 with SMTP_SSL(SMTP_HOST) as smtp:
134 smtp.login(SMTP_USER, SMTP_PASS)
135 for reply in to_send:
136 smtp.send_message(reply)
139 if __name__ == "__main__":
140 last_time = datetime.datetime.fromtimestamp(
141 float(sys.argv[1]), datetime.timezone.utc
143 start_time = datetime.datetime.now(datetime.timezone.utc)
144 main(last_time, start_time)
145 print(start_time.timestamp())