add insecure-login method, after secure-login failed
[enigma2-plugins.git] / emailclient / src / plugin.py
1 from Screens.Screen import Screen
2 from Components.Pixmap import Pixmap, MovingPixmap
3 from Components.Label import Label
4 from Components.ScrollLabel import ScrollLabel
5 from Screens.ChoiceBox import ChoiceBox
6 from Components.MenuList import MenuList
7 from Components.GUIComponent import GUIComponent
8 from Components.HTMLComponent import HTMLComponent
9 from Components.MultiContent import MultiContentEntryText
10 from enigma import eListboxPythonMultiContent, eListbox,gFont
11 from Components.ActionMap import ActionMap
12 from Components.config import config, ConfigSubsection, ConfigInteger, ConfigYesNo, ConfigText
13 from Plugins.Plugin import PluginDescriptor
14
15 from EmailConfig import EmailConfigScreen
16
17 from twisted.mail import imap4
18 from zope.interface import implements
19 import uu
20 import email
21
22 import protocol
23
24
25 config.plugins.emailimap = ConfigSubsection()
26 config.plugins.emailimap.username = ConfigText("user",fixed_size=False)
27 config.plugins.emailimap.password = ConfigText("password",fixed_size=False)
28 config.plugins.emailimap.server = ConfigText("please.config.first",fixed_size=False)
29 config.plugins.emailimap.port = ConfigInteger(143,limits = (1, 65536))
30
31 # 0= fetch all header , 10= fetch only the last 10 headers/messages of a mailbox
32 config.plugins.emailimap.maxheadertoload = ConfigInteger(0,limits = (1, 100)) 
33
34 class EmailHandler:
35     def __init__(self):
36         pass
37     def onConnect(self,proto):
38         pass
39
40 class EmailScreen(Screen,EmailHandler):
41     implements(imap4.IMailboxListener)
42     
43     skin = """
44         <screen position="110,83" size="530,430" title="Email" >
45             <widget name="boxlist" position="0,0" size="150,400" scrollbarMode="showOnDemand" />            
46             <widget name="messagelist" position="150,0" size="380,400" scrollbarMode="showOnDemand" />            
47             <widget name="infolabel" position="0,400" size="530,30"   foregroundColor=\"white\" font=\"Regular;18\" />           
48         </screen>"""
49          
50     currentmailbox = None
51     proto = None
52     def __init__(self, session, args = 0):
53         EmailHandler.__init__(self)
54         self.session = session
55         
56         self.skin = EmailScreen.skin
57         Screen.__init__(self, session)
58         protocol.createFactory(self,config.plugins.emailimap.username.value,config.plugins.emailimap.server.value,config.plugins.emailimap.port.value)
59
60         self["actions"] = ActionMap(["InfobarChannelSelection","WizardActions", "DirectionActions","MenuActions","ShortcutActions","GlobalActions","HelpActions","NumberActions"], 
61             {
62              "ok": self.action_ok,
63              "back": self.action_exit,
64              "historyNext": self.selectMessagelist,
65              "historyBack": self.selectBoxlist,
66              "down":        self.down,
67              "up":          self.up,
68              "left":        self.left,
69              "right":       self.right,
70              "menu":        self.action_menu,
71              }, -1)
72         self["boxlist"] = MenuList([])
73         self["messagelist"] = MailList([])
74         self["infolabel"] = Label("")
75         self.onLayoutFinish.append(self.selectBoxlist)
76         
77     def action_menu(self):
78         self.session.open(EmailConfigScreen)
79         
80     def selectBoxlist(self):
81         self.currList = "boxlist"
82         self["boxlist"].selectionEnabled(1)
83         self["messagelist"].selectionEnabled(0)
84         
85     def selectMessagelist(self):
86         self.currList = "messagelist"
87         self["boxlist"].selectionEnabled(0)
88         self["messagelist"].selectionEnabled(1)
89     
90     def up(self):
91         self[self.currList].up()
92     
93     def down(self):
94         self[self.currList].down()
95         
96     def left(self):
97         self[self.currList].pageUp()
98     
99     def right(self):
100         self[self.currList].pageDown()
101     
102     def action_ok(self):
103         if self.currList == "boxlist":
104             self.onBoxSelected()
105         else:
106             self.onMessageSelected()
107         
108     def onBoxSelected(self):
109         c = self["boxlist"].getCurrent()
110         if c is not None:
111             self.proto.examine(UTF7toUTF8(c[1][2])
112                                ).addCallback(self.onExamine,c[0] ,self.proto
113                               ).addErrback(self.onExamineFailed, self.proto
114                               )
115     
116     def onMessageSelected(self):
117         c = self["messagelist"].getCurrent()
118         if c is not None:
119             self.fetchMessageSize(c[0])
120             
121     def fetchMessageSize(self,message):
122         self.proto.fetchSize(message.get("uid")
123             ).addCallback(self.onMessageSizeLoaded,message, self.proto
124             ).addErrback(self.onMessageLoadFailed,message.get("uid"), self.proto
125             )
126             
127     def onMessageSizeLoaded(self,result,message,proto):
128         print "onMessageSizeLoaded",result,message
129         size = int(result[message.get("uid")]['RFC822.SIZE'])
130         self.MAX_MESSAGE_SIZE_TO_OPEN = 5667555
131         if size >= self.MAX_MESSAGE_SIZE_TO_OPEN:
132             #ask here to open message
133             print "message to large to open (size=",size,")"
134         else:
135             self.loadMessage(message)
136         
137     def fetchBodyStructure(self,message):
138         self.proto.fetchBodyStructure(message.get("uid")
139             ).addCallback(self.onBodystructureLoaded,message, self.proto
140             ).addErrback(self.onMessageLoadFailed,message.get("uid"), self.proto
141             )
142     
143     def loadMessage(self,message):
144         self["infolabel"].setText("loading message")
145         
146         self.proto.fetchMessage(message.get("uid")
147             ).addCallback(self.onMessageLoaded,message, self.proto
148             ).addErrback(self.onMessageLoadFailed,message.get("uid"), self.proto
149             )
150     
151     def onMessageLoaded(self,result,message,proto):
152         self["infolabel"].setText("parsing message")
153         print "onMessageLoaded"#,result,message
154         msgstr = result[message.get("uid")]['RFC822']
155         import email.Parser
156         msg = email.Parser.Parser().parsestr(msgstr)
157         msg.messagebodys = []
158         msg.attachments = []
159         
160         if msg.is_multipart():
161             partCounter=1
162             for part in msg.walk():
163                 if part.get_content_maintype()=="multipart":
164                     continue
165                 print "+"*30
166                 print "filename",part.get_filename()
167                 print "part.get_content_maintype() ", part.get_content_maintype()
168                 print "part.get_subtype()",part.get_content_subtype()
169                 if part.get_content_maintype() == 'text' and part.get_filename() is None:
170                     if part.get_content_subtype() == "html":
171                         msg.messagebodys.append(EmailBody(part.get_content_type(),part.get_payload(decode=True)))
172                     elif part.get_content_subtype() == "plain":
173                         msg.messagebodys.append(EmailBody(part.get_content_type(),part.get_payload(decode=True)))
174                     else:
175                         print "unkown content type= ",part.get_content_maintype(),"/",part.get_content_subtype()
176                         
177                 else:
178                      print "found Attachment with  ",part.get_content_type(),"and name",part.get_filename()
179                      msg.attachments.append(EmailAttachment(part.get_filename(),part.get_content_type(),part.get_payload()))    
180                 partCounter+=1
181
182         else:
183             msg.messagebodys.append(EmailBody("text/plain",msg.get_payload(decode=True)))
184         self.session.open(ScreenMailView,msg)
185         
186     def onBodystructureLoaded(self,result,message,proto):
187         print "onBodystructureLoaded",result,message
188         structures = result[message.get('uid')]['BODYSTRUCTURE']
189         bodys = []
190         if isinstance(structures[0],type([])):
191             for struc in structures:
192                 print "#"*10
193                 if isinstance(struc,type([])):
194                     if struc[0] != "boundary":
195                         bodys.append(struc)
196         else:
197             print "#"*10
198             bodys.append(structures)
199         print "HAVE",len(bodys)," BODYPARTS"
200             
201     def onMessageLoadFailed(self,failure,uid,proto):
202         print "onMessageLoadFailed",failure,uid
203         self["infolabel"].setText(failure.getErrorMessage())
204         
205
206     def action_exit(self):
207         if self.proto is not None:
208             self.proto.logout(
209                             ).addCallback(self.onLogedOut, self.proto
210                             ).addErrback(self.onLogedOut, self.proto
211                             )
212         else:
213             self.close()
214     
215     def onLogedOut(self,result,proto):
216         print "onLogedOut",result
217         self.close()
218         
219             
220     def onConnect(self,proto):
221         self["infolabel"].setText("connected")
222         self.doLogin(proto)
223         
224     def onConnectFailed(self,reason):
225         self["infolabel"].setText(reason.getErrorMessage())
226
227     def onAuthentication(self,result,proto):
228         self.proto = proto
229         self["infolabel"].setText("logged in")
230         proto.list("", "*"
231         ).addCallback(self.onMailboxList, proto
232         )
233         
234     def doLogin(self,proto):
235         print "login secure"
236         proto.authenticate(config.plugins.emailimap.password.value
237                            ).addCallback(self.onAuthentication, proto
238                            ).addErrback(self.onAuthenticationFailed, proto
239                            )
240
241     def onAuthenticationFailed(self,failure, proto):
242         # If it failed because no SASL mechanisms match
243         print "onAuthenticationFailed",failure, proto
244         self["infolabel"].setText(failure.getErrorMessage())
245         failure.trap(imap4.NoSupportedAuthentication)
246         self.doLoginInsecure(proto)
247     
248     def doLoginInsecure(self,proto):
249         print "login INSECURE"
250         proto.login(config.plugins.emailimap.username.value, config.plugins.emailimap.password.value
251                 ).addCallback(self.onAuthentication, proto
252                 ).addErrback(self.onInsecureAuthenticationFailed, proto
253                 )
254     
255     def onInsecureAuthenticationFailed(self,failure,proto):
256         print "onInsecureAuthenticationFailed",failure, proto
257         self["infolabel"].setText(failure.getErrorMessage())
258         
259     def onMailboxList(self,result,proto):
260         print "onMailboxList"#,result,proto
261         list = []
262         for i in result:
263             flags,hierarchy_delimiter,name = i
264             list.append((UTF7toUTF8(name).encode('utf-8'),i))
265         self["boxlist"].l.setList(list) 
266         
267     def onExamine(self,result,mboxname,proto):
268         print "onExamine", result,mboxname
269         self.setTitle("Mailbox: "+mboxname)
270         self.currentmailbox = mboxname
271         numMessagesinFolder = int(result['EXISTS'])
272         if numMessagesinFolder <= 0:
273             self["infolabel"].setText("Box '"+mboxname+"' is empty")
274             self["messagelist"].l.setList([]) 
275         
276         else:
277             if config.plugins.emailimap.maxheadertoload.value > 0:
278                 maxMessagesToFetch = config.plugins.emailimap.maxheadertoload.value
279                 startmsg = numMessagesinFolder-maxMessagesToFetch+1
280                 if startmsg <= 0:
281                     startmsg = 1
282                 rangeToFetch = [startmsg,numMessagesinFolder]
283             else:
284                 rangeToFetch = [1,numMessagesinFolder]
285             self["infolabel"].setText("loading headers "+str(rangeToFetch[0])+"-"+str(rangeToFetch[1])+" of Box '"+mboxname+"'")
286             
287             try:
288                 proto.fetchEnvelope('%i:%i'%(rangeToFetch[0],rangeToFetch[1])    #'1:*'
289                            ).addCallback(self.onHeaderList, proto
290                            )
291             except imap4.IllegalServerResponse,e:
292                 print e
293                 
294     def onExamineFailed(self,failure,proto):
295         print "onExamineFailed",failure, proto
296         self["infolabel"].setText(failure.getErrorMessage())
297         
298     def onHeaderList(self,result,proto):
299         print "onHeaderList"#,result,proto
300         self["infolabel"].setText("headers loaded, now parsing ...")
301         list = []
302         for m in result:
303             #print "#"*20
304             x = Message(m,result[m])
305             list.append(self.buildMessageListItem(x))
306         list.reverse()
307         self["messagelist"].l.setList(list) 
308         self["infolabel"].setText("have "+str(len(result))+" messages ")
309             
310     def buildMessageListItem(self,message):
311         res = [ message ]
312         res.append(MultiContentEntryText(pos=(5, 0), size=(380, 19), font=0, text=message.getSenderString()))
313         res.append(MultiContentEntryText(pos=(5,19),size=(380, 19), font=0,text=message.get('date',default='kein Datum')))
314         res.append(MultiContentEntryText(pos=(5,38),size=(380, 19), font=0,text=message.get('subject',default='kein Betreff')))
315         return res  
316     #
317     # IMailboxListener methods
318     #
319     def modeChanged(self, writeable):
320         print "modeChanged",writeable
321
322     def flagsChanged(self, newFlags):
323         print "flagsChanged",newFlags
324
325     def newMessages(self, exists, recent):
326         print "newMessages", exists, recent
327       
328 class ScreenMailView(Screen):
329     skin=""
330     def __init__(self, session,email, args = 0):
331         self.session = session
332         self.email = email
333         self.skin = "<screen position=\"85,80\" size=\"550,476\" title=\"view Email\" >"
334         self.skin +=  """<widget name="from" position="0,0" size="550,25"  font="Regular;20" />
335             <widget name="date" position="0,25" size="550,25"  font="Regular;20" />
336             <widget name="subject" position="0,50" size="550,25"  font="Regular;20" />
337             <widget name="body" position="0,75" size="550,375"  font="Regular;20" />
338             <widget name="buttonred" position="10,436" size="100,30" backgroundColor="red" valign="center" halign="center" zPosition="2"  foregroundColor="white" font="Regular;16"/> 
339             <widget name="buttongreen" position="120,436" size="100,30" backgroundColor="green" valign="center" halign="center" zPosition="2"  foregroundColor="white" font="Regular;16"/> 
340             <widget name="buttonyellow" position="230,436" size="100,30" backgroundColor="yellow" valign="center" halign="center" zPosition="2"  foregroundColor="white" font="Regular;16"/> 
341             <widget name="buttonblue" position="340,436" size="100,30" backgroundColor="blue" valign="center" halign="center" zPosition="2"  foregroundColor="white" font="Regular;16"/>            
342         </screen>""" 
343         Screen.__init__(self, session)
344         self["from"] = Label(_("From: %s" %self.email.get('from','no-from')))
345         self["date"] = Label(_("Date: %s" %self.email.get('date','no-date')))
346         self["subject"] = Label(_(self.email.get('subject','no-subject')))
347         self["body"] = ScrollLabel(_(self.email.messagebodys[0].getData()))
348         self["buttonred"] = Label("")
349         self["buttongreen"] = Label("")
350         self["buttonyellow"] =  Label("Headers")
351         self["buttonblue"] =  Label(_("delete"))
352         self["actions"] = ActionMap(["WizardActions", "DirectionActions","MenuActions","ShortcutActions"], 
353             {
354              "back": self.close,
355              "up": self["body"].pageUp,
356              "down": self["body"].pageDown,
357              "left": self["body"].pageUp,
358              "right": self["body"].pageDown,
359              "red": self.selectBody,
360              "green": self.selectAttachment,
361              "yellow": self.openMessagesHeaders,
362              "blue": self.delete,
363              
364              }, -1)
365         self.onLayoutFinish.append(self.updateButtons)
366         
367     def delete(self):
368         pass #self.session.openWithCallback(self.deleteCB, ChoiceBox, title="really delete Mail?", list=[(_("yes"), True),(_("no"), False)])
369
370     def deleteCB(self,returnValue):
371         if returnValue[1]is True:
372             pass
373         
374     def openMessagesHeaders(self):
375         pass #self.session.open(ScreenMailViewHeader,self.profil,self.email)
376                    
377     def updateButtons(self):
378         if len(self.email.messagebodys)>=2:
379             self["buttonred"].setText(_("Bodys"))
380         else:
381             self["buttonred"].setText("")  
382         if len(self.email.attachments):
383             self["buttongreen"].setText("Attachments")
384         else:
385             self["buttongreen"].setText("")  
386     
387     def selectBody(self):
388         if len(self.email.messagebodys):
389             list = []
390             for a in self.email.messagebodys:
391                 list.append((a.getContenttype(),a))
392             self.session.openWithCallback(self.selectBodyCB,ChoiceBox,_("select Body"),list)
393             
394     def selectBodyCB(self,choice):
395         if choice is not None:
396             self["body"].setText(choice[1].getData())
397             
398     def selectAttachment(self):
399         if len(self.email.attachments):
400             list = []
401             for a in self.email.attachments:
402                 list.append((a.getFilename(),a))
403             self.session.openWithCallback(self.selectAttachmentCB,ChoiceBox,_("select Attachment"),list)
404             
405     def selectAttachmentCB(self,choice):
406         if choice is not None:
407             print "Attachment selected",choice[1].getFilename()
408             #showMessageBox(self.session)
409     
410 class MailList(MenuList, HTMLComponent, GUIComponent):
411     def __init__(self, list):
412         MenuList.__init__(self,list)
413         GUIComponent.__init__(self)
414         self.l = eListboxPythonMultiContent()
415         self.list = list
416         self.l.setList(list)
417         self.l.setFont(0, gFont("Regular", 18))
418         self.l.setFont(1, gFont("Regular", 20))
419         
420     GUI_WIDGET = eListbox
421
422     def postWidgetCreate(self, instance):
423         instance.setContent(self.l)
424         instance.setItemHeight(60)
425
426 class Message(object):        
427     def __init__(self,uid,strraw):
428         self.__setattr__("uid",uid)
429         print "parsing message"
430         try:
431             _date, _subject, _from, _sender, _reply_to, _to, _cc, _bcc, _in_reply_to, _message_id = strraw['ENVELOPE']
432             self.__setattr__("date",_date)
433             if _subject is None:
434                 self.__setattr__("subject","kein Betreff")
435             else:
436                 self.__setattr__("subject",_subject)
437     
438             self.__setattr__("from",_from[0])
439             self.__setattr__("sender",_sender)
440             self.__setattr__("reply_to",_reply_to)
441             self.__setattr__("to",_to)
442             self.__setattr__("cc",_cc)
443             self.__setattr__("bcc",_bcc)
444             self.__setattr__("in_reply_to",_in_reply_to)
445             self.__setattr__("message_id",_message_id)
446         except Exception,e:
447             print e,strraw
448         
449     def getSenderString(self):
450         if self.get("from") is None:
451             return "no sender"
452         else:
453             if self.get("from")[0] is not None:
454                 sender = self.get("from")[0]
455             else:
456                 sender = self.get("from")[2]+"@"+self.get("from")[3]
457             return sender
458         
459     def get(self,key,default=None):
460         try:
461             return self.__getattribute__(key)    
462         except:
463             return default
464     def __str__(self):
465         return "Message "+self.subject
466 ############
467 class EmailBody:
468     def __init__(self,contenttype,data):
469         self.contenttype = contenttype
470         self.data = data
471     def getData(self):
472         return self.data 
473     def getContenttype(self):
474         return self.contenttype 
475 ############
476 class EmailAttachment:
477     def __init__(self,filename,contenttype,data):
478         self.filename = filename
479         self.contenttype = contenttype
480         self.data = data
481     def getFilename(self):
482         return self.filename 
483     def getContenttype(self):
484         return self.contenttype 
485     def getData(self):
486         return self.data
487     
488 def UTF7toUTF8(str):
489     return imap4.decoder(str)[0]
490 def UTF8toUTF7(str):
491     return imap4.encoder(str.decode('utf-8'))[0]
492
493 def main(session,**kwargs):
494     session.open(EmailScreen)    
495
496 def Plugins(path,**kwargs):
497     global plugin_path
498     plugin_path = path
499     return [
500              PluginDescriptor(name="Email Client", description="view Emails via IMAP4", 
501              where = PluginDescriptor.WHERE_PLUGINMENU,
502              fnc = main
503              ),
504              #PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = startScrobbler)
505         ]