-
Notifications
You must be signed in to change notification settings - Fork 0
/
notifier.py
145 lines (110 loc) · 4.59 KB
/
notifier.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# notifier
#
# This program auto sends emails when a vaccine is found
import gmail_utils
import util
import json
import time
SENDER_ADDRESS = "[email protected]"
SUBJECT = "Potential Covid-19 Vaccine Appointments in your Area"
EMAIL_WAIT = 0.5
def select_service(gmail_services, user):
'''returns the gmail service to use for this user based on the user's
random offset
inputs: gmail_services - list of gmail services, one for each account
user - dictionary of user data
outputs: gmail_service - gmail service to use
address - address corresponding to the gmail service'''
rand_offset = user["rand_offset"] # look up user random offset
service_idx = rand_offset % len(gmail_services) # calculate index
if service_idx != 0:
suffix = service_idx
else:
suffix = ""
address = "nimblenotifier{}@gmail.com".format(suffix)
return gmail_services[service_idx], address
def set_thread_id(gmail_service, user, msg, sender_address):
'''sets the thread ID for a user to enable replying to the same thread'''
# msg failed - don't set the thread id
if msg is None:
return
mime = gmail_utils.GetMimeMessage(
gmail_service,
sender_address,
msg["id"]
)
# get msgid and threadid
msg_id = util.parse_by_prefix(mime.as_string(), "Message-Id: ", end_char = [">"]) + ">"
thread_id = msg["threadId"]
state_json_path = "data/user/{}_users.json".format(user["state"])
with open(state_json_path, "r") as f:
state_dict = json.load(f)
state_dict[user["email"]]["thread_id"] = thread_id
state_dict[user["email"]]["msg_id"] = msg_id
state_data = json.dumps(state_dict, indent = 4)
with open(state_json_path, "w") as f:
f.write(state_data)
return
def format_msg(user):
'''create a formatted user message with available appointments'''
# construct path to email template
template = "data/template/email.txt"
with (open(template, "r")) as f:
template_list = f.readlines()
# single string with entire template email
email_body = ""
for line in template_list:
email_body += line
locations = user["avail"]
locations.sort(key = lambda x: x["distance"])
avail_locs = ""
for loc in locations:
avail_locs += "\nLocation: {}\n".format(loc['name'])
avail_locs += "Zip code: {}\n".format(loc['zip'])
avail_locs += "{}\n".format(loc["url"])
avail_locs += "Estimated distance: {:.1f} miles\n".format(loc["distance"])
avail_locs += "Additional details and restrictions: {}\n".format(loc["details"])
email_body = email_body.replace("<<appointments>>", avail_locs)
return email_body
def send_all(match_list, gmail_services, debug=None):
'''send emails to all users with nearby vaccines'''
sent_emails = 0 # total emails sent
fail_emails = 0 # failed emails
# loop through all users with nearby vaccines
for user in match_list:
msg_txt = format_msg(user) # create message text
if user["thread_id"] is None: # add Re: for replies
subject = SUBJECT
else:
subject = "Re: " + SUBJECT
gmail_service, sender_address = select_service(gmail_services, user)
# generate an email object
email = gmail_utils.create_message(
sender_address,
user["email"],
subject,
msg_txt,
thread_id=user["thread_id"],
msg_id=user["msg_id"],
)
msg = None
# debug mode - only email the debug address
if debug is not None and user["email"] != debug:
print("\n==== Recipient: {} ====\n{}".format(user["email"], msg_txt))
continue
msg = gmail_utils.send_message(gmail_service, sender_address, email)
# save info required for threading if it's not saved
if user["thread_id"] is None:
set_thread_id(gmail_service, user, msg, sender_address)
# send succeeded
if msg is not None:
sent_emails += 1
# save info required for threading if it's not saved
if user["thread_id"] is None:
set_thread_id(gmail_service, user, msg, sender_address)
# send failed
else:
fail_emails += 1
time.sleep(EMAIL_WAIT) # wait before sending next email to prevent
# exceeding usage limit
return sent_emails, fail_emails