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