]> git.friedersdorff.com Git - max/remindme.git/blob - remindme.py
3454eab37722b653f10f81cad9574bd33af84bfe
[max/remindme.git] / remindme.py
1 import os
2 from smtplib import SMTP_SSL
3 import email
4 import datetime
5
6 from imapclient import IMAPClient, SEEN
7 from dateutil.parser import parse
8 from cron_descriptor import get_description
9
10
11 IMAP_USER = os.environ["REMINDME_IMAP_USER"]
12 IMAP_PASS = os.environ["REMINDME_IMAP_PASS"]
13 IMAP_HOST = os.environ["REMINDME_IMAP_HOST"]
14 SMTP_USER = os.environ.get("REMINDME_SMTP_USER", IMAP_USER)
15 SMTP_PASS = os.environ.get("REMINDME_IMAP_PASS", IMAP_PASS)
16 SMTP_HOST = os.environ.get("REMINDME_IMAP_HOST", IMAP_HOST)
17
18 USAGE = ""
19
20
21 __iclient = None
22
23
24 def iclient():
25     global __iclient
26     if not __iclient:
27         __iclient = IMAPClient(IMAP_HOST, use_uid=True)
28         __iclient.login(IMAP_USER, IMAP_PASS)
29
30     __iclient.select_folder("INBOX")
31     return __iclient
32
33
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"]
42     return reply
43
44
45 def ls(msg, reminders):
46     reply_lines = []
47     for uid, reminder in reminders:
48         body = reminder.get_payload().strip()
49         if body.startswith("repeat"):
50             parsed = get_description(body[6:].strip())
51             reply_lines.append(f"({uid}) {reminder['subject']}: {body} ; {parsed}")
52             pass
53         else:
54             reply_lines.append(f"({uid}) {reminder['subject']}: {body}")
55
56     return make_reply(msg, "Your current reminders", "\n".join(reply_lines))
57
58
59 def ack_reminder(uid, reminder):
60     body = reminder.get_payload().strip()
61     if body.startswith("repeat"):
62         parsed = get_description(body[6:].strip())
63         reply_body = f"({uid}) {reminder['subject']}: {body} ; {parsed}"
64         pass
65     else:
66         reply_body = f"({uid}) {reminder['subject']}: {body}"
67
68     return make_reply(reminder, f"Received Reminder: {reminder['subject']}", reply_body)
69
70
71 def remind(reminder):
72     f_line = reminder.get_payload().strip().splitlines()[0]
73     if f_line.lower().startswith("repeat"):
74         return False, None
75     else:
76         try:
77             parsed_time = parse(f_line, fuzzy=True)
78         except ValueError:
79             return False, None
80
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)
85
86         if datetime.datetime.now(datetime.timezone.utc) > parsed_time:
87             return (
88                 True,
89                 make_reply(
90                     reminder, f"Re: {reminder['subject']} now", reminder.get_payload()
91                 ),
92             )
93
94
95 def main():
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)
102
103         payload = mail.get_payload().strip()
104         if payload.lower().startswith("help") or payload.lower().startswith("list"):
105             metas.append(mail)
106             to_delete.append(uid)
107         else:
108             if SEEN in data[b"FLAGS"]:
109                 old_rems.append((uid, mail))
110             else:
111                 new_rems.append((uid, mail))
112
113     for meta in metas:
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))
118
119     for uid, reminder in new_rems:
120         to_send.append(ack_reminder(uid, reminder))
121
122     for uid, reminder in old_rems + new_rems:
123         done, reply = remind(reminder)
124         if reply:
125             to_send.append(reply)
126         if done:
127             to_delete.append(uid)
128
129     iclient().delete_messages(to_delete)
130     iclient().expunge()
131     iclient().add_flags([u[0] for u in new_rems], SEEN)
132
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)
137
138
139 if __name__ == "__main__":
140     main()