adding emailclient
[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     
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         self.proto.logout(
208         ).addCallback(self.onLogedOut, self.proto
209         ).addErrback(self.onLogedOut, self.proto
210         )
211     def onLogedOut(self,result,proto):
212         print "onLogedOut",result
213         self.close()
214         
215             
216     def onConnect(self,proto):
217         self["infolabel"].setText("connected")
218         proto.authenticate(config.plugins.emailimap.password.value
219         ).addCallback(self.onAuthentication, proto
220         ).addErrback(self.onAuthenticationFailed, proto
221         )
222         
223     def onConnectFailed(self,reason):
224         self["infolabel"].setText(reason.getErrorMessage())
225
226     def onAuthentication(self,result,proto):
227         self.proto = proto
228         self["infolabel"].setText("logged in")
229         proto.list("", "*"
230         ).addCallback(self.onMailboxList, proto
231         )
232         
233     def onAuthenticationFailed(self,failure, proto):
234         print "onAuthenticationFailed",failure, proto
235         self["infolabel"].setText(failure.getErrorMessage())
236         
237     def onMailboxList(self,result,proto):
238         print "onMailboxList"#,result,proto
239         list = []
240         for i in result:
241             flags,hierarchy_delimiter,name = i
242             list.append((UTF7toUTF8(name).encode('utf-8'),i))
243         self["boxlist"].l.setList(list) 
244         
245     def onExamine(self,result,mboxname,proto):
246         print "onExamine", result,mboxname
247         self.setTitle("Mailbox: "+mboxname)
248         self.currentmailbox = mboxname
249         numMessagesinFolder = int(result['EXISTS'])
250         if numMessagesinFolder <= 0:
251             self["infolabel"].setText("Box '"+mboxname+"' is empty")
252             self["messagelist"].l.setList([]) 
253         
254         else:
255             if config.plugins.emailimap.maxheadertoload.value > 0:
256                 maxMessagesToFetch = config.plugins.emailimap.maxheadertoload.value
257                 startmsg = numMessagesinFolder-maxMessagesToFetch+1
258                 if startmsg <= 0:
259                     startmsg = 1
260                 rangeToFetch = [startmsg,numMessagesinFolder]
261             else:
262                 rangeToFetch = [1,numMessagesinFolder]
263             self["infolabel"].setText("loading headers "+str(rangeToFetch[0])+"-"+str(rangeToFetch[1])+" of Box '"+mboxname+"'")
264             
265             try:
266                 proto.fetchEnvelope('%i:%i'%(rangeToFetch[0],rangeToFetch[1])    #'1:*'
267                            ).addCallback(self.onHeaderList, proto
268                            )
269             except imap4.IllegalServerResponse,e:
270                 print e
271                 
272     def onExamineFailed(self,failure,proto):
273         print "onExamineFailed",failure, proto
274         self["infolabel"].setText(failure.getErrorMessage())
275         
276     def onHeaderList(self,result,proto):
277         print "onHeaderList"#,result,proto
278         self["infolabel"].setText("headers loaded, now parsing ...")
279         list = []
280         for m in result:
281             #print "#"*20
282             x = Message(m,result[m])
283             list.append(self.buildMessageListItem(x))
284         list.reverse()
285         self["messagelist"].l.setList(list) 
286         self["infolabel"].setText("have "+str(len(result))+" messages ")
287             
288     def buildMessageListItem(self,message):
289         res = [ message ]
290         res.append(MultiContentEntryText(pos=(5, 0), size=(380, 19), font=0, text=message.getSenderString()))
291         res.append(MultiContentEntryText(pos=(5,19),size=(380, 19), font=0,text=message.get('date',default='kein Datum')))
292         res.append(MultiContentEntryText(pos=(5,38),size=(380, 19), font=0,text=message.get('subject',default='kein Betreff')))
293         return res  
294     #
295     # IMailboxListener methods
296     #
297     def modeChanged(self, writeable):
298         print "modeChanged",writeable
299
300     def flagsChanged(self, newFlags):
301         print "flagsChanged",newFlags
302
303     def newMessages(self, exists, recent):
304         print "newMessages", exists, recent
305       
306 class ScreenMailView(Screen):
307     skin=""
308     def __init__(self, session,email, args = 0):
309         self.session = session
310         self.email = email
311         self.skin = "<screen position=\"85,80\" size=\"550,476\" title=\"view Email\" >"
312         self.skin +=  """<widget name="from" position="0,0" size="550,25"  font="Regular;20" />
313             <widget name="date" position="0,25" size="550,25"  font="Regular;20" />
314             <widget name="subject" position="0,50" size="550,25"  font="Regular;20" />
315             <widget name="body" position="0,75" size="550,375"  font="Regular;20" />
316             <widget name="buttonred" position="10,436" size="100,30" backgroundColor="red" valign="center" halign="center" zPosition="2"  foregroundColor="white" font="Regular;16"/> 
317             <widget name="buttongreen" position="120,436" size="100,30" backgroundColor="green" valign="center" halign="center" zPosition="2"  foregroundColor="white" font="Regular;16"/> 
318             <widget name="buttonyellow" position="230,436" size="100,30" backgroundColor="yellow" valign="center" halign="center" zPosition="2"  foregroundColor="white" font="Regular;16"/> 
319             <widget name="buttonblue" position="340,436" size="100,30" backgroundColor="blue" valign="center" halign="center" zPosition="2"  foregroundColor="white" font="Regular;16"/>            
320         </screen>""" 
321         Screen.__init__(self, session)
322         self["from"] = Label(_("From: %s" %self.email.get('from','no-from')))
323         self["date"] = Label(_("Date: %s" %self.email.get('date','no-date')))
324         self["subject"] = Label(_(self.email.get('subject','no-subject')))
325         self["body"] = ScrollLabel(_(self.email.messagebodys[0].getData()))
326         self["buttonred"] = Label("")
327         self["buttongreen"] = Label("")
328         self["buttonyellow"] =  Label("Headers")
329         self["buttonblue"] =  Label(_("delete"))
330         self["actions"] = ActionMap(["WizardActions", "DirectionActions","MenuActions","ShortcutActions"], 
331             {
332              "back": self.close,
333              "up": self["body"].pageUp,
334              "down": self["body"].pageDown,
335              "left": self["body"].pageUp,
336              "right": self["body"].pageDown,
337              "red": self.selectBody,
338              "green": self.selectAttachment,
339              "yellow": self.openMessagesHeaders,
340              "blue": self.delete,
341              
342              }, -1)
343         self.onLayoutFinish.append(self.updateButtons)
344         
345     def delete(self):
346         pass #self.session.openWithCallback(self.deleteCB, ChoiceBox, title="really delete Mail?", list=[(_("yes"), True),(_("no"), False)])
347
348     def deleteCB(self,returnValue):
349         if returnValue[1]is True:
350             pass
351         
352     def openMessagesHeaders(self):
353         pass #self.session.open(ScreenMailViewHeader,self.profil,self.email)
354                    
355     def updateButtons(self):
356         if len(self.email.messagebodys)>=2:
357             self["buttonred"].setText(_("Bodys"))
358         else:
359             self["buttonred"].setText("")  
360         if len(self.email.attachments):
361             self["buttongreen"].setText("Attachments")
362         else:
363             self["buttongreen"].setText("")  
364     
365     def selectBody(self):
366         if len(self.email.messagebodys):
367             list = []
368             for a in self.email.messagebodys:
369                 list.append((a.getContenttype(),a))
370             self.session.openWithCallback(self.selectBodyCB,ChoiceBox,_("select Body"),list)
371             
372     def selectBodyCB(self,choice):
373         if choice is not None:
374             self["body"].setText(choice[1].getData())
375             
376     def selectAttachment(self):
377         if len(self.email.attachments):
378             list = []
379             for a in self.email.attachments:
380                 list.append((a.getFilename(),a))
381             self.session.openWithCallback(self.selectAttachmentCB,ChoiceBox,_("select Attachment"),list)
382             
383     def selectAttachmentCB(self,choice):
384         if choice is not None:
385             print "Attachment selected",choice[1].getFilename()
386             #showMessageBox(self.session)
387     
388 class MailList(MenuList, HTMLComponent, GUIComponent):
389     def __init__(self, list):
390         MenuList.__init__(self,list)
391         GUIComponent.__init__(self)
392         self.l = eListboxPythonMultiContent()
393         self.list = list
394         self.l.setList(list)
395         self.l.setFont(0, gFont("Regular", 18))
396         self.l.setFont(1, gFont("Regular", 20))
397         
398     GUI_WIDGET = eListbox
399
400     def postWidgetCreate(self, instance):
401         instance.setContent(self.l)
402         instance.setItemHeight(60)
403
404 class Message(object):        
405     def __init__(self,uid,strraw):
406         self.__setattr__("uid",uid)
407         print "parsing message"
408         try:
409             _date, _subject, _from, _sender, _reply_to, _to, _cc, _bcc, _in_reply_to, _message_id = strraw['ENVELOPE']
410             self.__setattr__("date",_date)
411             if _subject is None:
412                 self.__setattr__("subject","kein Betreff")
413             else:
414                 self.__setattr__("subject",_subject)
415     
416             self.__setattr__("from",_from[0])
417             self.__setattr__("sender",_sender)
418             self.__setattr__("reply_to",_reply_to)
419             self.__setattr__("to",_to)
420             self.__setattr__("cc",_cc)
421             self.__setattr__("bcc",_bcc)
422             self.__setattr__("in_reply_to",_in_reply_to)
423             self.__setattr__("message_id",_message_id)
424         except Exception,e:
425             print e,strraw
426         
427     def getSenderString(self):
428         if self.get("from") is None:
429             return "no sender"
430         else:
431             if self.get("from")[0] is not None:
432                 sender = self.get("from")[0]
433             else:
434                 sender = self.get("from")[2]+"@"+self.get("from")[3]
435             return sender
436         
437     def get(self,key,default=None):
438         try:
439             return self.__getattribute__(key)    
440         except:
441             return default
442     def __str__(self):
443         return "Message "+self.subject
444 ############
445 class EmailBody:
446     def __init__(self,contenttype,data):
447         self.contenttype = contenttype
448         self.data = data
449     def getData(self):
450         return self.data 
451     def getContenttype(self):
452         return self.contenttype 
453 ############
454 class EmailAttachment:
455     def __init__(self,filename,contenttype,data):
456         self.filename = filename
457         self.contenttype = contenttype
458         self.data = data
459     def getFilename(self):
460         return self.filename 
461     def getContenttype(self):
462         return self.contenttype 
463     def getData(self):
464         return self.data
465     
466 def UTF7toUTF8(str):
467     return imap4.decoder(str)[0]
468 def UTF8toUTF7(str):
469     return imap4.encoder(str.decode('utf-8'))[0]
470
471 def main(session,**kwargs):
472     session.open(EmailScreen)    
473
474 def Plugins(path,**kwargs):
475     global plugin_path
476     plugin_path = path
477     return [
478              PluginDescriptor(name="Email Client", description="view Emails via IMAP4", 
479              where = PluginDescriptor.WHERE_PLUGINMENU,
480              fnc = main
481              ),
482              #PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = startScrobbler)
483         ]