3 from smtplib import SMTP_SSL
7 from imapclient import IMAPClient, SEEN
8 from dateutil.parser import parse
9 from cron_descriptor import get_description
10 from croniter import croniter
13 IMAP_USER = os.environ["REMINDME_IMAP_USER"]
14 IMAP_PASS = os.environ["REMINDME_IMAP_PASS"]
15 IMAP_HOST = os.environ["REMINDME_IMAP_HOST"]
16 SMTP_USER = os.environ.get("REMINDME_SMTP_USER", IMAP_USER)
17 SMTP_PASS = os.environ.get("REMINDME_IMAP_PASS", IMAP_PASS)
18 SMTP_HOST = os.environ.get("REMINDME_IMAP_HOST", IMAP_HOST)
28 __iclient = IMAPClient(IMAP_HOST, use_uid=True)
29 __iclient.login(IMAP_USER, IMAP_PASS)
31 __iclient.select_folder("INBOX")
35 def make_reply(orig, subject, body):
36 reply = email.message.Message()
37 reply["Subject"] = subject
38 reply["In-Reply-To"] = orig["Message-ID"]
39 reply["References"] = orig["Message-ID"]
40 reply.set_payload(body)
41 reply["From"] = "remindme@friedersdorff.com"
42 reply["To"] = orig["From"]
46 def ls(msg, reminders):
48 for uid, reminder in reminders:
49 body = reminder.get_payload().strip()
50 if body.startswith("repeat"):
51 parsed = get_description(body[6:].strip())
52 reply_lines.append(f"({uid}) {reminder['subject']}: {body} ; {parsed}")
55 reply_lines.append(f"({uid}) {reminder['subject']}: {body}")
57 return make_reply(msg, "Your current reminders", "\n".join(reply_lines))
60 def ack_reminder(uid, reminder):
61 body = reminder.get_payload().strip()
62 if body.startswith("repeat"):
63 parsed = get_description(body[6:].strip())
64 reply_body = f"({uid}) {reminder['subject']}: {body} ; {parsed}"
67 reply_body = f"({uid}) {reminder['subject']}: {body}"
69 return make_reply(reminder, f"Received Reminder: {reminder['subject']}", reply_body)
72 def remind(last_time, start_time, reminder):
73 f_line = reminder.get_payload().strip().splitlines()[0]
74 if f_line.lower().startswith("repeat"):
75 cron_payload = f_line[6:].strip()
76 it = croniter(cron_payload, last_time)
77 next_time = it.get_next(datetime.datetime)
78 if next_time > last_time and next_time <= start_time:
82 reminder, f"Re: {reminder['subject']} now", reminder.get_payload()
89 parsed_time = parse(f_line, fuzzy=True)
93 if not parsed_time.tzinfo:
94 send_date = email.utils.parsedate_to_datetime(reminder["Date"])
95 sender_tz = send_date.tzinfo or datetime.timezone.utc
96 parsed_time = parsed_time.replace(tzinfo=sender_tz)
98 if datetime.datetime.now(datetime.timezone.utc) > parsed_time:
102 reminder, f"Re: {reminder['subject']} now", reminder.get_payload()
107 def main(last_time, start_time):
108 msgs = iclient().search(["ALL"])
109 new_rems, old_rems, metas, to_delete, to_send = [], [], [], [], []
110 for uid, data in iclient().fetch(msgs, ["BODY.PEEK[]", "FLAGS"]).items():
111 mail = email.message_from_bytes(data[b"BODY[]"])
112 if mail.is_multipart():
113 to_delete.append(uid)
115 payload = mail.get_payload().strip()
116 if payload.lower().startswith("help") or payload.lower().startswith("list"):
118 to_delete.append(uid)
120 if SEEN in data[b"FLAGS"]:
121 old_rems.append((uid, mail))
123 new_rems.append((uid, mail))
126 if meta.get_payload().strip().startswith("help"):
127 to_send.append(make_reply(meta, "Remindme Help", USAGE))
128 elif meta.get_payload().strip().startswith("list"):
129 to_send.append(ls(meta, old_rems + new_rems))
131 for uid, reminder in new_rems:
132 to_send.append(ack_reminder(uid, reminder))
134 for uid, reminder in old_rems + new_rems:
135 done, reply = remind(last_time, start_time, reminder)
137 to_send.append(reply)
139 to_delete.append(uid)
141 iclient().delete_messages(to_delete)
143 iclient().add_flags([u[0] for u in new_rems], SEEN)
145 with SMTP_SSL(SMTP_HOST) as smtp:
146 smtp.login(SMTP_USER, SMTP_PASS)
147 for reply in to_send:
148 smtp.send_message(reply)
151 if __name__ == "__main__":
152 last_time = datetime.datetime.fromtimestamp(
153 float(sys.argv[1]), datetime.timezone.utc
155 start_time = datetime.datetime.now(datetime.timezone.utc)
156 main(last_time, start_time)
157 print(start_time.timestamp())