importing dailyzen into git
[dailyzen.git] / dispatch_email.py
blobe3bb5086ecfd62bd373924665bfba12f0768dcd7
1 import os, sys, re, string, logging, time
2 import random, anydbm, smtplib
4 ALL_STORIES = set(range(1, 101+1))
5 WWW_URL = "http://nearfar.org/dailyzen/"
6 UNSUBSCRIBE_URL = WWW_URL + "do.cgi/unsubscribe/%s,%s"
7 VERIFICATION_URL = WWW_URL + "do.cgi/verify/%s,%s"
8 DBFILE = "/home/protected/data/dailyzen.db"
10 # TODO: unused (From: is same as To:). do something about it.
11 FROM_ADDR = "mail@nearfar.org"
13 # Decorator: Call the function once, and return the same return value always.
14 def call_once(func):
15 ret = func()
16 return lambda: ret
18 ## Logging, error handling bits
20 # FIXME: is call_once necessary?
21 @call_once
22 def logging_handler():
23 "Return the desired and default logging handler"
24 hdlr = logging.FileHandler("/home/protected/logs/dailyzen.log")
25 formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
26 hdlr.setFormatter(formatter)
27 return hdlr
29 dz = logging.getLogger("dailyzen")
30 dz.addHandler(logging_handler())
31 dz.setLevel(logging.DEBUG)
33 class UserError(Exception):
34 pass
37 ## Register/Validate mechanism
39 def register_user(email):
40 """Just register the user giving him (via email) an unique url
41 to valid himself against the given email address.
43 On clicking the URL, `validate_user' function would be called."""
44 EMAIL_MSG = r"""Hey there,
46 Just to make sure that it is really you, please click on this link.
49 You will then be receiving your daily Zen story shortly.
51 PS: Not initiated by you? simply ignore this email.
53 """
54 db = anydbm.open(DBFILE, "c")
55 if email in db.keys():
56 raise UserError, "email address already found"
57 user = dict(email=email, id=random_string(), sent_stories=[], valid=False)
58 db[email] = repr(user)
59 db.close()
60 # send email
61 verification_url = VERIFICATION_URL % (email, user["id"])
62 send_email(email, [email], "DailyZen Verification", EMAIL_MSG % verification_url)
63 dz.info("Verification code sent to %s", email)
65 def validate_user(email, id):
66 db = anydbm.open(DBFILE)
67 if email in db.keys():
68 user = eval(db[email])
69 db.close()
70 if user['id'] == id and user['valid'] is False:
71 # user validated!
72 user['valid'] = True
73 write_user(user)
74 dz.info("Validated %s", email)
75 else:
76 raise UserError, "Invalid request"
78 else:
79 db.close()
80 raise UserError, "Email address (%s) not found" % email
82 def delete_user(email, id):
83 db = anydbm.open(DBFILE, "w")
84 if email in db.keys():
85 user = eval(db[email])
86 if user["id"] == id:
87 del db[email]
88 else:
89 raise UserError, "Invalid request"
90 else:
91 raise UserError, "Email address not found (Perhaps already unsubscribed?)"
93 def random_string(length=15):
94 char_domain = string.ascii_letters + string.digits
95 id = []
96 for x in range(length):
97 id.append(random.choice(char_domain))
98 return''.join(id)
101 ## Users management
103 def read_users():
104 db = anydbm.open(DBFILE, "c")
105 users = []
106 for k in db.keys():
107 user = eval(db[k])
108 if user['valid']:
109 users.append(user)
110 db.close()
111 return users
113 def write_user(user):
114 db = anydbm.open(DBFILE, "w")
115 db[user['email']] = repr(user)
116 db.close()
119 def get_story(number):
120 f = open("101zenstories/%d.html.txt" % number)
121 title = f.readline().strip()
122 f.readline() # empty line
123 story = f.read()
124 return title, story
126 def send_email(from_, toaddrs, subject, body):
127 SENDMAIL = "sendmail" # sendmail location
128 p = os.popen("%s -t" % SENDMAIL, "w")
129 p.write("From: %s\n" % from_)
130 p.write("To: %s\n" % ",".join(toaddrs))
131 p.write("Subject: %s\n" % subject)
132 p.write("\n")
133 p.write(body)
134 sts = p.close()
135 if sts is not None and sts != 0:
136 dz.error("sendmail failed with exit status: %d", sts)
138 # not used: fails in nfshost
139 def send_email_SMTP(from_, toaddrs, subject, body):
140 msg = "From: %s\nSubject: %s\n\n%s" % (from_, subject, body)
141 s = smtplib.SMTP('127.0.0.1')
142 s.sendmail(from_, toaddrs, msg)
145 def send_random_story_to_user(user):
146 sent_stories = set(user['sent_stories'])
147 unsent_stories = ALL_STORIES.difference(sent_stories)
148 if len(unsent_stories) == 0:
149 # nothing left for this user
150 # TODO: should we remove him from db?
151 return
153 if len(sent_stories) == 0:
154 # send his first story
155 notice_msg = "Welcome. Here is your first story. From now on, you would get a random story that you were not sent before.\n---\n\n"
156 else:
157 notice_msg = ""
159 next = random.choice(list(unsent_stories))
160 user["sent_stories"].append(next)
161 title, story = get_story(next)
162 email = user["email"]
164 unsubscribe_url = UNSUBSCRIBE_URL % (user["email"], user["id"])
165 signature = "\n\n----\n%s\n\nTo unsubsubcribe,\n%s" % (WWW_URL, unsubscribe_url)
166 send_email(email, [email], title, notice_msg + story + signature)
167 dz.debug("email: To=%s %s (%d)", email, title, next)
168 write_user(user)
170 def now():
171 return long(time.time())
173 def secs(n): return n
174 def mins(n): return 60*secs(n)
175 def hrs(n): return 60*mins(n)
176 def days(n): return 24*hrs(n)
178 def dispatch_all():
179 dz.info("Broadcast operation started")
180 for user in read_users():
181 send_random_story_to_user(user)
184 ## TEST
185 if __name__ == '__main__':
186 print 'TEST'
187 if not read_users():
188 # add sample users
189 write_user(dict(email="srid@nearfar.org", id="12345", sent_stories=[1,6,23], valid=True))
190 # write_user(dict(email="foo@example.com", id="84854", sent_stories=[51,78,99, 2, 3]))
191 dispatch_all()