Add example about os.path.walk , which may used to implete --recursive option
[xmailer.git] / smtpser.py
blob891f015f42b0c69ab4870e0b77cf5cb28b55d253
1 #- * - encoding: utf-8 - * -
3 # module for class SMTPServer
5 import os
6 import smtplib
7 import email,mimetypes
8 import smtpssl
10 from email import encoders
11 from email.mime.audio import MIMEAudio
12 from email.mime.base import MIMEBase
13 from email.mime.image import MIMEImage
14 from email.mime.multipart import MIMEMultipart
15 from email.mime.text import MIMEText
16 from email.utils import make_msgid
18 class SMTPServer:
19 """ class SMTPServer is the infomation about a SMTP Server, include
20 email,user:passwd@addr:port ssl,
21 So you can MailBigFile, and MailFiles by it """
22 def __init__(self,email='r01ustc@gmail.com',ip='localhost',port=25,ssl=0,username='',password=''):
23 self.email=email
24 self.addr=ip
25 self.port=port
26 self.ssl=ssl
27 self.user=username
28 self.passwd=password
29 def __str__(self):
30 """ override the str(), used often by print """
31 if not self.ssl:
32 sslmsg='Non SSL'
33 elif self.ssl==1:
34 sslmsg='Over SSL'
35 elif self.ssl==2:
36 sslmsg='Start TLS'
37 else:
38 sslmsg='Unknown'
39 return 'E-Mail:'+self.email+'\n' \
40 +'Server:'+self.addr+':%d(%s)\n'%(self.port,sslmsg) \
41 +'User,Pass:'+self.user+','+self.passwd
42 # info=[addr,port,ssl,username,password,mailaddress]
43 def SetInfoFromList(self,info):
44 """ Set Information of SMTP Server in a list """
45 self.email=info[5]
46 self.addr=info[0]
47 self.port=info[1]
48 self.ssl=info[2]
49 self.user=info[3]
50 self.passwd=info[4]
51 def SetInfo(self,email='r01ustc@gmail.com',ip='localhost',port=25,ssl=0,username='',password=''):
52 """ Set Infomation of SMTP Server """
53 self.email=email
54 self.addr=ip
55 self.port=port
56 self.ssl=ssl
57 self.user=username
58 self.passwd=password
60 def SendMail(self,tolist,msg):
61 """ Send msg to tolist.
62 tolist: the list of recipients
63 msg: formatted mail message"""
64 print 'From:'+self.email
65 print 'To:',tolist
66 if not self.ssl:
67 s=smtplib.SMTP(self.addr,self.port)
68 elif self.ssl==1:
69 s=smtpssl.SMTP_SSL(self.addr,self.port)
70 elif self.ssl==2:
71 s=smtplib.SMTP(self.addr,self.port)
72 s.ehlo(self.email)
73 s.starttls()
74 s.ehlo(self.email)
75 # if self.ssl:
76 # s.starttls()
77 # s.ehlo(self.email)
78 s.login(self.user,self.passwd)
79 s.sendmail(self.email,tolist,msg)
80 s.quit()
81 def MailFiles(self,tolist,subject,bodymsg, mailattachlist=[],charset=None):
82 """ Mail multi files,
83 tolist: the address list where we mailed to,['a@b.c','c@d.e']
84 subject: the subject, used it directly. If you want to use utf-8
85 charset, please use email.Header.Header(msg,charset) to
86 format first
87 bodymsg: the mail body, it is a list, and the first item is pure text,
88 the second item is html
89 mailattachlist is the attached file list,['a.txt','b.exe','c.jpg']
90 charset : give the charset of mailattachlist file name"""
92 # 创建一个multipart, 然后把前面的文本部分和html部分都附加到上面,至于为什么,可以看看mime相关内容
93 attach=email.MIMEMultipart.MIMEMultipart()
94 # head = email.Message.Message()
95 attach['Message-ID']=make_msgid()
96 attach['From']=self.email
97 if type(tolist)==type([]): #if tolist is a list
98 attach['To']=', '.join(tolist)
99 else: # tolist is only a string
100 attach['To']=tolist
101 attach['Subject']=subject # email.Header.Header(subject,'utf-8')
102 if type(bodymsg)==type([]): # if bodymsg is a list
103 # 创建纯文本部分
104 body_plain = email.MIMEText.MIMEText(bodymsg[0], _subtype='plain', _charset='utf-8')
105 body_html = None
106 # 创建html部分,这个是可选的
107 if len(bodymsg)>1:
108 body_html = email.MIMEText.MIMEText(bodymsg[1], _subtype='html', _charset='utf-8')
109 else: #bodymsg is a string
110 # 创建纯文本部分
111 body_plain = email.MIMEText.MIMEText(bodymsg, _subtype='plain', _charset='utf-8')
112 body_html = None
113 attach.attach(body_plain)
114 if body_html:
115 attach.attach(body_html)
116 # 处理每一个附件
117 for fname in mailattachlist:
118 ctype, encoding = mimetypes.guess_type(fname)
119 if ctype is None or encoding is not None:
120 # No guess could be made, or the file is encoded (compressed), so
121 # use a generic bag-of-bits type.
122 ctype = 'application/octet-stream'
123 maintype, subtype = ctype.split('/', 1)
124 filebasename=os.path.basename(fname)
125 if maintype == 'text':
126 fp = open(fname)
127 # Note: we should handle calculating the charset
128 msg = MIMEText(fp.read(), _subtype=subtype)
129 fp.close()
130 # msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(fname))
131 elif maintype == 'image':
132 fp = open(fname, 'rb')
133 msg = MIMEImage(fp.read(), _subtype=subtype)
134 fp.close()
135 # msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(fname))
136 elif maintype == 'audio':
137 fp = open(fname, 'rb')
138 msg = MIMEAudio(fp.read(), _subtype=subtype)
139 fp.close()
140 # msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(fname))
141 else:
142 fp = open(fname, 'rb')
143 msg = MIMEBase(maintype, subtype)
144 msg.set_payload(fp.read())
145 fp.close()
146 # Encode the payload using Base64
147 encoders.encode_base64(msg)
148 filebasename=filebasename+'.a'
149 if charset: # if give the charset, we format the filename
150 ffname=str(email.Header.Header(filebasename,charset))
151 else: # else , just use the original name
152 ffname=filebasename
153 print 'Formatted filename in MailFiles:'+ffname
154 msg.add_header('Content-Disposition', 'attachment', filename=ffname)
155 attach.attach(msg)
156 # 生成最终的邮件
157 mail = attach.as_string()
159 self.SendMail(tolist,mail)
160 return mail
162 # mail part of file
163 def CreateFilePartMail(self,to,subject,bodymsg,fp,filename,lbegin,lend,icount):
164 """ Mail Part of the file with name filename,
165 fp is the open file
166 filename is the filename, please formatted yourself first,
167 You can use email.Header.Header(str,charset) to do it
168 lbegin is the begin position of file
169 lend is the end position of file,
170 icount is the count number for the part of file, 20
171 to is the address list where we mailed to,['a@b.c','c@d.e']
172 subject is the subject, please formatted yourself first to support utf-8
173 charset
174 bodymsg is the mailbody, it is a list, and the first item is pure text,
175 where the second item is html """
176 attmsg=MIMEBase('application','octet-stream')
177 fp.seek(lbegin)
178 attmsg.set_payload(fp.read(lend-lbegin))
179 encoders.encode_base64(attmsg)
180 attmsg.add_header('Content-Disposition', 'attachment', filename=filename)
181 msg=email.MIMEMultipart.MIMEMultipart()
182 # head=email.Message.Message()
183 msg['From']=self.email
184 msg['Message-ID']=make_msgid()
185 if type(to)==type([]): # if to is a list
186 msg['To']=', '.join(to)
187 else: # to is a single string
188 msg['To']=to
189 msg['Subject']=subject
191 # 创建纯文本部分
192 if type(bodymsg)==type([]): #bodymsg is a list
193 body_plain = email.MIMEText.MIMEText(bodymsg[0], _subtype='plain', _charset='utf-8')
194 msg.attach(body_plain)
195 body_html = None
196 # 创建html部分,这个是可选的
197 if len(bodymsg)>1:
198 body_html = email.MIMEText.MIMEText(bodymsg[1], _subtype='html', _charset='utf-8')
199 msg.attach(body_html)
200 else: # bodymsg is a single string
201 body_plain = email.MIMEText.MIMEText(bodymsg, _subtype='plain', _charset='utf-8')
202 msg.attach(body_plain)
203 msg.attach(attmsg)
205 return msg.as_string()
207 # split big file to several mail
208 def MailBigFile(self,tolist,subject,bodymsg,filename,leneach,ffname=None):
209 """ Mail a big file by splitting it into pieces then mail,
210 tolist is the address list where we mailed to,['a@b.c','c@d.e']
211 subject is the subject
212 bodymsg is the mailbody, it is a list, and the first item is pure text,
213 where the second item is html
214 filename is the big filename, it will appeared in body message
215 leneach is the len of file piece
216 ffname: formatted filename, used to give utf-8 charset name,
217 you can formatted the filename by email.Header.Header(str,charset), if None, just use the basename(filename)"""
219 filelen=os.stat(filename).st_size
220 # lmaxpiece gives the number of file pieces
221 lmaxpiece=filelen/leneach
222 if(lmaxpiece*leneach < filelen):
223 lmaxpiece=lmaxpiece+1
225 lbegin=0
226 lend=lbegin+leneach
227 fp=open(filename,'rb') # openfile
228 while(i<lmaxpiece):
229 if type(bodymsg)==type([]): # if body is a list
230 bodymsg[0]=filename+'\n part %d/%d'%(i+1,lmaxpiece)
231 else: # body is a single string
232 bodymsg=filename+'\n part %d/%d'%(i+1,lmaxpiece)
233 if not ffname: # if ffname=None
234 ffilename=os.path.basename(filename)+'.%03d'%(i+1)
235 else:
236 ffilename=ffname+'.%03d'%(i+1)
237 print 'formatted file name in MailBigFile:'+ffilename
238 s=self.CreateFilePartMail(tolist,subject,bodymsg,fp,ffilename,lbegin,lend,i+1)
239 self.SendMail(tolist,s)
241 lbegin=lend
242 lend=lbegin+leneach
243 i=i+1
245 return " "
247 # send mails
248 class MailSenders:
249 """ This class is used to manage SMTPServer list """
250 def __init__(self,dict,default=None):
251 """ dict has the form: {name:[addr,port,ssl,username,password,mailaddress]} """
252 # SMTP servers
253 self.serdict=dict
254 self.names=dict.keys()
255 if default:
256 self.current=default
257 else:
258 self.current=self.names[0]
259 self.onlydefault=0
260 self.charset=None
261 self.Subject='XMailer: tool to mail multifile'
263 def OnlyDefault(self,od=0):
264 """ if onlydefault, we use the default SMTP Sender, else use all Servers"""
265 self.onlydefault=od
267 def SetCharset(self,charset=None):
268 ''' set up the charset, default is None, can be like 'utf-8' to deal with chinese '''
269 self.charset=charset
271 def SetSubject(self,subj,charset=None):
272 if charset:
273 self.Subject=email.Header.Header(subj,charset)
274 else:
275 self.Subject=email.Header.Header(subj,self.charset)
276 return self.Subject
278 def NextServ(self):
279 """ get the Next Server """
280 if self.onlydefault:
281 return self.current
282 keylen=len(self.names)
283 for i in range(0,keylen):
284 if self.names[i]==self.current:
285 break
286 if i==keylen-1:
288 else:
289 i=i+1
290 self.current=self.names[i]
291 print 'Current:'+self.current
292 return self.current
294 def CreateBodyMail(self,bodymsg,charset=None):
295 """ Create txt MIME part, listed in bodylist
296 bodylist can be a list or a single string"""
297 body_html = None
298 if type(bodymsg)==type([]): # if bodymsg is a list
299 # 创建纯文本部分
300 body_plain = email.MIMEText.MIMEText(bodymsg[0], _subtype='plain', _charset=charset)
301 # 创建html部分,这个是可选的
302 if len(bodymsg)>1:
303 body_html = email.MIMEText.MIMEText(bodymsg[1], _subtype='html', _charset=charset)
304 else: #bodymsg is a string
305 # 创建纯文本部分
306 body_plain = email.MIMEText.MIMEText(bodymsg, _subtype='plain', _charset=charset)
307 if body_html:
308 return [body_plain,body_html]
309 else:
310 return [body_plain]
312 # MIME parts contain every file in attachlist
313 def CreateFilesMail(self,attachlist=[],charset=None):
314 """ create a list of MIME parts, which encode every file list in attachlist,
315 charset: the charset of name in attachlist"""
316 mimelist=[]
317 # 处理每一个附件
318 for fname in attachlist:
319 ctype, encoding = mimetypes.guess_type(fname)
320 if ctype is None or encoding is not None:
321 # No guess could be made, or the file is encoded (compressed),
322 # so use a generic bag-of-bits type.
323 ctype = 'application/octet-stream'
324 maintype, subtype = ctype.split('/', 1)
325 # print 'MIME type:',maintype,subtype
326 filebasename=os.path.basename(fname)
327 if maintype == 'text':
328 fp = open(fname)
329 # Note: we should handle calculating the charset
330 msg = MIMEText(fp.read(), _subtype=subtype)
331 fp.close()
332 elif maintype == 'image':
333 fp = open(fname, 'rb')
334 msg = MIMEImage(fp.read(), _subtype=subtype)
335 fp.close()
336 elif maintype == 'audio':
337 fp = open(fname, 'rb')
338 msg = MIMEAudio(fp.read(), _subtype=subtype)
339 fp.close()
340 else:
341 maintype,subtype='application','octet-stream'
342 fp = open(fname, 'rb')
343 msg = MIMEBase(maintype, subtype)
344 msg.set_payload(fp.read())
345 # Encode the payload using Base64
346 encoders.encode_base64(msg)
347 fp.close()
348 filebasename=filebasename+'.a'
349 if charset: # if give the charset, we format the filename
350 ffname=str(email.Header.Header(filebasename,charset))
351 else: # else , just use the original name
352 ffname=filebasename
353 # print 'Formatted filename in MailFiles:'+ffname
354 msg.add_header('Content-Disposition', 'attachment', filename=ffname)
355 mimelist.append(msg)
356 return mimelist
358 # MIME part contain part of file
359 def CreateFilePartMail(self,fp,lbegin,lend,filename):
360 """ Mail Part of the file with name filename,
361 fp is the open file
362 filename is the filename, please formatted yourself first,
363 You can use email.Header.Header(str,charset) to do it
364 lbegin is the begin position of file
365 lend is the end position of file """
366 attmsg=MIMEBase('application','octet-stream')
367 fp.seek(lbegin)
368 attmsg.set_payload(fp.read(lend-lbegin))
369 encoders.encode_base64(attmsg)
370 attmsg.add_header('Content-Disposition', 'attachment', filename=filename)
372 return attmsg
374 def MailFiles(self,tolist,subject,bodylist,filelist,bcc=None,charset=None):
375 """ mail files in filelist to tolist+bcc,
376 tolist: list contains the recipients
377 subject: subject of the mail, already formatted
378 bodylist: list contains the message body, it is MIME part
379 filelist: list contains the file we want to mail.
380 bcc: BCC fields, contains the recipients which not show in mail."""
381 sender=SMTPServer()
382 # print 'to',tolist,'bcc',bcc
384 msg=email.MIMEMultipart.MIMEMultipart()
385 # head = email.Message.Message()
386 msg['Message-ID']=make_msgid()
387 if type(tolist)==type([]): #if tolist is a list
388 msg['To']=', '.join(tolist)
389 recipients=tolist[0:]
390 else: # tolist is only a string
391 msg['To']=tolist
392 recipients=[tolist]
393 if bcc: # bcc is not empty
394 if type(bcc)==type([]):
395 recipients.extend(bcc)
396 else:
397 recipients.append(bcc)
398 print 'recipients:',recipients
399 msg['Subject']=subject # email.Header.Header(subject,'utf-8')
400 if type(bodylist)==type([]):
401 for mi in bodylist:
402 msg.attach(mi)
403 else:
404 msg.attach(bodylist)
405 flist=self.CreateFilesMail(filelist,charset)
406 for fmi in flist:
407 msg.attach(fmi)
408 sender.SetInfoFromList(self.serdict[self.current])
409 msg['From']=sender.email
410 sender.SendMail(recipients,msg.as_string())
411 self.NextServ()
413 def MailBigFile(self,tolist,subject,bodylist,pathname,leneach,bcc=None,charset=None):
414 """ mail a big file to tolist+bcc,
415 tolist: list contains the recipients
416 subject: subject formatted
417 bodylist: contains the message, which maybe the infomation of the file
418 pathname: full path name of the file
419 leneach: len of each mail
420 bcc: BCC field
421 charset: charset """
422 sender=SMTPServer()
424 if type(tolist)==type([]): # if to is a list
425 headto=', '.join(tolist)
426 recipients=tolist[0:]
427 else: # to is a single string
428 headto=tolist
429 recipients=[tolist]
430 if bcc:
431 if type(bcc)==type([]):
432 recipients.extend(bcc)
433 else:
434 recipients.append(bcc)
436 if charset:
437 ffname=str(email.Header.Header(os.path.basename(pathname),charset))
438 else:
439 ffname=os.path.basename(pathname)
441 filelen=os.stat(pathname).st_size
442 # lmaxpiece gives the number of file pieces
443 lmaxpiece=filelen/leneach
444 if(lmaxpiece*leneach < filelen):
445 lmaxpiece=lmaxpiece+1
447 lbegin=0
448 lend=lbegin+leneach
449 fp=open(pathname,'rb') # openfile
450 while(i<lmaxpiece):
451 ffilename=ffname+'.%03d'%(i+1)
452 print 'formatted file name in MailBigFile:'+ffilename
453 msg=email.MIMEMultipart.MIMEMultipart()
454 if type(bodylist)==type([]):
455 for msgi in bodylist:
456 msg.attach(msgi)
457 else:
458 msg.attach(bodylist)
459 msg.attach(self.CreateFilePartMail(fp,lbegin,lend,ffilename))
460 sender.SetInfoFromList(self.serdict[self.current])
461 msg['To']=headto
462 msg['Subject']=subject
463 msg['From']=sender.email
464 msg['Message-ID']=make_msgid()
465 sender.SendMail(recipients,msg.as_string())
466 self.NextServ()
468 lbegin=lend
469 lend=lbegin+leneach
470 i=i+1