--- /dev/null
+import os
+from smtplib import SMTP_SSL
+import email
+import datetime
+
+from imapclient import IMAPClient, SEEN
+from dateutil.parser import parse
+from cron_descriptor import get_description
+
+
+IMAP_USER = os.environ["REMINDME_IMAP_USER"]
+IMAP_PASS = os.environ["REMINDME_IMAP_PASS"]
+IMAP_HOST = os.environ["REMINDME_IMAP_HOST"]
+SMTP_USER = os.environ.get("REMINDME_SMTP_USER", IMAP_USER)
+SMTP_PASS = os.environ.get("REMINDME_IMAP_PASS", IMAP_PASS)
+SMTP_HOST = os.environ.get("REMINDME_IMAP_HOST", IMAP_HOST)
+
+USAGE = ""
+
+
+__iclient = None
+
+
+def iclient():
+ global __iclient
+ if not __iclient:
+ __iclient = IMAPClient(IMAP_HOST, use_uid=True)
+ __iclient.login(IMAP_USER, IMAP_PASS)
+
+ __iclient.select_folder("INBOX")
+ return __iclient
+
+
+def make_reply(orig, subject, body):
+ reply = email.message.Message()
+ reply["Subject"] = subject
+ reply["In-Reply-To"] = orig["Message-ID"]
+ reply["References"] = orig["Message-ID"]
+ reply.set_payload(body)
+ reply["From"] = "remindme@friedersdorff.com"
+ reply["To"] = orig["From"]
+ return reply
+
+
+def ls(msg, reminders):
+ reply_lines = []
+ for uid, reminder in reminders:
+ body = reminder.get_payload().strip()
+ if body.startswith("repeat"):
+ parsed = get_description(body[6:].strip())
+ reply_lines.append(f"({uid}) {reminder['subject']}: {body} ; {parsed}")
+ pass
+ else:
+ reply_lines.append(f"({uid}) {reminder['subject']}: {body}")
+
+ return make_reply(msg, "Your current reminders", "\n".join(reply_lines))
+
+
+def ack_reminder(uid, reminder):
+ body = reminder.get_payload().strip()
+ if body.startswith("repeat"):
+ parsed = get_description(body[6:].strip())
+ reply_body = f"({uid}) {reminder['subject']}: {body} ; {parsed}"
+ pass
+ else:
+ reply_body = f"({uid}) {reminder['subject']}: {body}"
+
+ return make_reply(reminder, f"Received Reminder: {reminder['subject']}", reply_body)
+
+
+def remind(reminder):
+ f_line = reminder.get_payload().strip().splitlines()[0]
+ if f_line.lower().startswith("repeat"):
+ return False, None
+ else:
+ try:
+ parsed_time = parse(f_line, fuzzy=True)
+ except ValueError:
+ return False, None
+
+ if not parsed_time.tzinfo:
+ send_date = email.utils.parsedate_to_datetime(reminder["Date"])
+ sender_tz = send_date.tzinfo or datetime.timezone.utc
+ parsed_time = parsed_time.replace(tzinfo=sender_tz)
+
+ if datetime.datetime.now(datetime.timezone.utc) > parsed_time:
+ return (
+ True,
+ make_reply(
+ reminder, f"Re: {reminder['subject']} now", reminder.get_payload()
+ ),
+ )
+
+
+def main():
+ msgs = iclient().search(["ALL"])
+ new_rems, old_rems, metas, to_delete, to_send = [], [], [], [], []
+ for uid, data in iclient().fetch(msgs, ["BODY.PEEK[]", "FLAGS"]).items():
+ mail = email.message_from_bytes(data[b"BODY[]"])
+ if mail.is_multipart():
+ to_delete.append(uid)
+
+ payload = mail.get_payload().strip()
+ if payload.lower().startswith("help") or payload.lower().startswith("list"):
+ metas.append(mail)
+ to_delete.append(uid)
+ else:
+ if SEEN in data[b"FLAGS"]:
+ old_rems.append((uid, mail))
+ else:
+ new_rems.append((uid, mail))
+
+ for meta in metas:
+ if meta.get_payload().strip().startswith("help"):
+ to_send.append(make_reply(meta, "Remindme Help", USAGE))
+ elif meta.get_payload().strip().startswith("list"):
+ to_send.append(ls(meta, old_rems + new_rems))
+
+ for uid, reminder in new_rems:
+ to_send.append(ack_reminder(uid, reminder))
+
+ for uid, reminder in old_rems + new_rems:
+ done, reply = remind(reminder)
+ if reply:
+ to_send.append(reply)
+ if done:
+ to_delete.append(uid)
+
+ iclient().delete_messages(to_delete)
+ iclient().expunge()
+ iclient().add_flags([u[0] for u in new_rems], SEEN)
+
+ with SMTP_SSL(SMTP_HOST) as smtp:
+ smtp.login(SMTP_USER, SMTP_PASS)
+ for reply in to_send:
+ smtp.send_message(reply)
+
+
+if __name__ == "__main__":
+ main()