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 Screens.MessageBox import MessageBox
6 from Components.MenuList import MenuList
7 from Components.MultiContent import MultiContentEntryText
8 from Components.ScrollLabel import ScrollLabel
9 from Components.config import config, ConfigSubsection, ConfigInteger, ConfigText
10 from EmailConfig import EmailConfigScreen
11 from Plugins.Plugin import PluginDescriptor
12 from Screens.ChoiceBox import ChoiceBox
13 from Screens.Screen import Screen
14 from enigma import eListboxPythonMultiContent, eListbox, gFont
15 from twisted.mail import imap4
16 from zope.interface import implements
19 from TagStrip import strip_readable
20 from protocol import createFactory
22 config.plugins.emailimap = ConfigSubsection()
23 config.plugins.emailimap.username = ConfigText("user", fixed_size=False)
24 config.plugins.emailimap.password = ConfigText("password", fixed_size=False)
25 config.plugins.emailimap.server = ConfigText("please.config.first", fixed_size=False)
26 config.plugins.emailimap.port = ConfigInteger(143, limits = (1, 65536))
28 # 0= fetch all header , 10= fetch only the last 10 headers/messages of a mailbox
29 config.plugins.emailimap.maxheadertoload = ConfigInteger(0, limits = (1, 100))
34 def onConnect(self, proto):
37 class EmailScreen(Screen, EmailHandler):
38 implements(imap4.IMailboxListener)
41 <screen position="110,83" size="530,430" title="Email" >
42 <widget name="boxlist" position="0,0" size="150,400" scrollbarMode="showOnDemand" />
43 <widget name="messagelist" position="150,0" size="380,400" scrollbarMode="showOnDemand" />
44 <widget name="infolabel" position="0,400" size="530,30" foregroundColor=\"white\" font=\"Regular;18\" />
50 def __init__(self, session, args = 0):
51 EmailHandler.__init__(self)
52 self.session = session
54 self.skin = EmailScreen.skin
55 Screen.__init__(self, session)
56 createFactory(self, config.plugins.emailimap.username.value, config.plugins.emailimap.server.value, config.plugins.emailimap.port.value)
58 self["actions"] = ActionMap(["InfobarChannelSelection", "WizardActions", "DirectionActions", "MenuActions", "ShortcutActions", "GlobalActions", "HelpActions", "NumberActions"],
61 "back": self.action_exit,
62 "historyNext": self.selectMessagelist,
63 "historyBack": self.selectBoxlist,
68 "menu": self.action_menu,
70 self["boxlist"] = MenuList([])
71 self["messagelist"] = MailList([])
72 self["infolabel"] = Label("")
73 self.onLayoutFinish.append(self.selectBoxlist)
75 def action_menu(self):
76 self.session.open(EmailConfigScreen)
78 def selectBoxlist(self):
79 self.currList = "boxlist"
80 self["boxlist"].selectionEnabled(1)
81 self["messagelist"].selectionEnabled(0)
83 def selectMessagelist(self):
84 self.currList = "messagelist"
85 self["boxlist"].selectionEnabled(0)
86 self["messagelist"].selectionEnabled(1)
89 self[self.currList].up()
92 self[self.currList].down()
95 self[self.currList].pageUp()
98 self[self.currList].pageDown()
101 if self.currList == "boxlist":
104 self.onMessageSelected()
106 def onBoxSelected(self):
107 c = self["boxlist"].getCurrent()
109 self.proto.examine(UTF7toUTF8(c[1][2])
110 ).addCallback(self.onExamine, c[0] , self.proto
111 ).addErrback(self.onExamineFailed, self.proto
114 def onMessageSelected(self):
115 c = self["messagelist"].getCurrent()
117 self.fetchMessageSize(c[0])
119 def fetchMessageSize(self, message):
120 print "fetchMessageSize",message
121 self.proto.fetchSize(message.uid
122 ).addCallback(self.onMessageSizeLoaded, message, self.proto
123 ).addErrback(self.onMessageLoadFailed, message, self.proto
126 def onMessageSizeLoaded(self, result, message, proto):
127 print "onMessageSizeLoaded", result, message
128 size = int(result[message.uid]['RFC822.SIZE'])
129 self.MAX_MESSAGE_SIZE_TO_OPEN = 4000000
130 if size >= self.MAX_MESSAGE_SIZE_TO_OPEN:
131 #ask here to open message
132 print "message to large to open (size=", size, ")"
134 self.loadMessage(message)
136 # def fetchBodyStructure(self, message):
137 # print "fetchBodyStructure",message
138 # self.proto.fetchBodyStructure(message.uid
139 # ).addCallback(self.onBodystructureLoaded, message, self.proto
140 # ).addErrback(self.onMessageLoadFailed, message, self.proto
143 def loadMessage(self, message):
144 print "loadMessage",message
145 self["infolabel"].setText("loading message")
147 self.proto.fetchMessage(message.uid
148 ).addCallback(self.onMessageLoaded, message, self.proto
149 ).addErrback(self.onMessageLoadFailed, message, self.proto
152 def onMessageLoaded(self, result, message, proto):
153 self["infolabel"].setText("parsing message")
154 print "onMessageLoaded"#,result,message
155 msgstr = result[message.uid]['RFC822']
156 msg = email.Parser.Parser().parsestr(msgstr)
157 msg.messagebodys = []
160 if msg.is_multipart():
161 for part in msg.walk():
162 if part.get_content_maintype()=="multipart":
164 if part.get_content_maintype() == 'text' and part.get_filename() is None:
165 if part.get_content_subtype() == "html":
166 msg.messagebodys.append(EmailBody(part))
167 elif part.get_content_subtype() == "plain":
168 msg.messagebodys.append(EmailBody(part))
170 print "unkown content type= ", part.get_content_maintype(), "/", part.get_content_subtype()
172 print "found Attachment with ", part.get_content_type(), "and name", part.get_filename()
173 msg.attachments.append(EmailAttachment(part.get_filename(), part.get_content_type(), part.get_payload()))
175 msg.messagebodys.append(EmailBody(msg))
176 self.session.open(ScreenMailView, msg)
178 def onMessageLoadFailed(self, failure, message, proto):
179 print "onMessageLoadFailed", failure, message
180 self["infolabel"].setText(failure.getErrorMessage())
182 def action_exit(self):
183 if self.proto is not None:
185 ).addCallback(self.onLogedOut, self.proto
186 ).addErrback(self.onLogedOut, self.proto
191 def onLogedOut(self, result, proto):
192 print "onLogedOut", result
195 def onConnect(self, proto):
196 self["infolabel"].setText("connected")
197 proto.getCapabilities(
198 ).addCallback(self.cbCapabilities, proto
199 ).addErrback(self.ebCapabilities, proto
202 def cbCapabilities(self,reason,proto):
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
209 def ebCapabilities(reason,proto):
210 print "ebCapabilities",reason
212 def onConnectFailed(self, reason):
213 self["infolabel"].setText(reason.getErrorMessage())
215 def onAuthentication(self, result, proto):
217 self["infolabel"].setText("logged in")
219 ).addCallback(self.onMailboxList, proto
222 def doLogin(self, proto):
226 context = proto.context.getContext()
227 d = proto.startTLS(context)
228 d = d.addCallback(proto.authenticate, config.plugins.emailimap.password.value)
230 d = proto.authenticate(config.plugins.emailimap.password.value)
231 d.addCallback(self.onAuthentication, proto)
232 d.addErrback(self.onAuthenticationFailed, proto)
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())
240 failure.trap(imap4.NoSupportedAuthentication)
241 self.doLoginInsecure(proto)
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
252 def onInsecureAuthenticationFailed(self, failure, proto):
253 print "onInsecureAuthenticationFailed", failure, proto
254 self["infolabel"].setText(failure.getErrorMessage())
256 def onMailboxList(self, result, proto):
257 print "onMailboxList", result, proto
260 flags, hierarchy_delimiter, name = i
261 list.append((UTF7toUTF8(name).encode('utf-8'), i))
262 self["boxlist"].l.setList(list)
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([])
274 if config.plugins.emailimap.maxheadertoload.value > 0:
275 maxMessagesToFetch = config.plugins.emailimap.maxheadertoload.value
276 startmsg = numMessagesinFolder-maxMessagesToFetch+1
279 rangeToFetch = [startmsg, numMessagesinFolder]
281 rangeToFetch = [1, numMessagesinFolder]
282 self["infolabel"].setText("loading headers "+str(rangeToFetch[0])+"-"+str(rangeToFetch[1])+" of Box '"+mboxname+"'")
285 # proto.fetchEnvelope('%i:%i'%(rangeToFetch[0], rangeToFetch[1]) #'1:*'
286 # ).addCallback(self.onEnvelopeList, proto
288 proto.fetchHeaders('%i:%i'%(rangeToFetch[0], rangeToFetch[1]) #'1:*'
289 ).addCallback(self.onHeaderList, proto
292 except imap4.IllegalServerResponse, e:
295 def onExamineFailed(self, failure, proto):
296 print "onExamineFailed", failure, proto
297 self["infolabel"].setText(failure.getErrorMessage())
299 def onHeaderList(self, result, proto):
300 print "onHeaderList"#,result,proto
301 self["infolabel"].setText("headers loaded, now parsing ...")
305 list.append(self.buildMessageListItem(MessageHeader(m, result[m]['RFC822.HEADER'])))
309 self["messagelist"].l.setList(list)
310 self["infolabel"].setText("have "+str(len(result))+" messages ")
312 def buildMessageListItem(self, message):
315 MultiContentEntryText(pos=(5, 0), size=(380, 19), font=0, text=message.getSenderString()),
316 MultiContentEntryText(pos=(5, 19), size=(380, 19), font=0, text=message.get('date', default='kein Datum')),
317 MultiContentEntryText(pos=(5, 38), size=(380, 19), font=0, text=message.getSubject())
320 # IMailboxListener methods
322 def modeChanged(self, writeable):
323 print "modeChanged", writeable
325 def flagsChanged(self, newFlags):
326 print "flagsChanged", newFlags
328 def newMessages(self, exists, recent):
329 print "newMessages", exists, recent
331 class ScreenMailView(Screen):
333 def __init__(self, session, email, args = 0):
334 self.session = session
336 self.skin = "<screen position=\"85,80\" size=\"550,476\" title=\"view Email\" >"
337 self.skin += """<widget name="from" position="0,0" size="550,25" font="Regular;20" />
338 <widget name="date" position="0,25" size="550,25" font="Regular;20" />
339 <widget name="subject" position="0,50" size="550,25" font="Regular;20" />
340 <widget name="body" position="0,75" size="550,375" font="Regular;20" />
341 <widget name="buttonred" position="10,436" size="100,30" backgroundColor="red" valign="center" halign="center" zPosition="2" foregroundColor="white" font="Regular;16"/>
342 <widget name="buttongreen" position="120,436" size="100,30" backgroundColor="green" valign="center" halign="center" zPosition="2" foregroundColor="white" font="Regular;16"/>
343 <widget name="buttonyellow" position="230,436" size="100,30" backgroundColor="yellow" valign="center" halign="center" zPosition="2" foregroundColor="white" font="Regular;16"/>
344 <widget name="buttonblue" position="340,436" size="100,30" backgroundColor="blue" valign="center" halign="center" zPosition="2" foregroundColor="white" font="Regular;16"/>
346 Screen.__init__(self, session)
347 self["from"] = Label(_("From: %s" %self.email.get('from', 'no-from')))
348 self["date"] = Label(_("Date: %s" %self.email.get('date', 'no-date')))
349 self["subject"] = Label(_(self.email.get('subject', 'no-subject')))
350 self["body"] = ScrollLabel(_(self.email.messagebodys[0].getData()))
351 self["buttonred"] = Label("")
352 self["buttongreen"] = Label("")
353 self["buttonyellow"] = Label("Headers")
354 self["buttonblue"] = Label(_("delete"))
355 self["actions"] = ActionMap(["WizardActions", "DirectionActions", "MenuActions", "ShortcutActions"],
358 "up": self["body"].pageUp,
359 "down": self["body"].pageDown,
360 "left": self["body"].pageUp,
361 "right": self["body"].pageDown,
362 "red": self.selectBody,
363 "green": self.selectAttachment,
364 "yellow": self.openMessagesHeaders,
368 self.onLayoutFinish.append(self.updateButtons)
371 pass #self.session.openWithCallback(self.deleteCB, ChoiceBox, title="really delete Mail?", list=[(_("yes"), True),(_("no"), False)])
373 def deleteCB(self, returnValue):
374 if returnValue[1] is True:
377 def openMessagesHeaders(self):
378 pass #self.session.open(ScreenMailViewHeader,self.profil,self.email)
380 def updateButtons(self):
381 self["buttonred"].setText(_("Bodys"))
382 if len(self.email.attachments):
383 self["buttongreen"].setText("Attachments")
385 self["buttongreen"].setText("")
387 def selectBody(self):
388 if len(self.email.messagebodys):
390 for a in self.email.messagebodys:
391 list.append((a.getContenttype(), a))
392 self.session.openWithCallback(self.selectBodyCB, ChoiceBox, _("select Body"), list)
394 def selectBodyCB(self, choice):
395 if choice is not None:
396 self["body"].setText(choice[1].getData())
398 def selectAttachment(self):
399 if len(self.email.attachments):
401 for a in self.email.attachments:
402 list.append((a.getFilename(), a))
403 self.session.openWithCallback(self.selectAttachmentCB, ChoiceBox, _("select Attachment"), list)
405 def selectAttachmentCB(self, choice):
406 if choice is not None:
407 print "Attachment selected", choice[1].getFilename()
408 #showMessageBox(self.session)
410 class MailList(MenuList):
411 def __init__(self, list, enableWrapAround = False):
412 MenuList.__init__(self, list, enableWrapAround, eListboxPythonMultiContent)
413 self.l.setFont(0, gFont("Regular", 18))
414 self.l.setFont(1, gFont("Regular", 20))
416 def postWidgetCreate(self, instance):
417 MenuList.postWidgetCreate(self, instance)
418 instance.setItemHeight(60)
420 class MessageHeader(object):
421 def __init__(self, uid, message):
422 self.uid = uid #must be int
423 self.message = email.Parser.Parser().parsestr(message)
425 def getSenderString(self):
426 if self.get("from") is None:
429 return self.get("from")
431 def getSubject(self):
432 if self.get("subject") is None:
435 # need some decoding for subjects like =?iso-8859-1?Q?=DCbermut?=
436 elif self.get("subject").startswith("=?") and self.get("subject").endswith("?="):
437 c = self.get("subject").split("?")
440 text = "?".join(c[3:])
445 return text.decode(codec).encode("utf-8")
448 return self.get("subject")
450 def get(self, key, default=None):
451 return self.message.get(key,failobj=default)
454 return "<MessageHeader uid="+str(self.uid)+", subject="+self.get("subject","no-subject")+">"
458 def __init__(self,data):
461 def getEncoding(self):
462 return self.data.get_content_charset()
465 text = self.data.get_payload(decode=True)
466 if self.getEncoding() is not None:
467 print "decoding text with charset ",self.getEncoding()
468 text = text.decode(self.getEncoding())
469 if self.getContenttype() == "text/html":
470 print "stripping html"
471 text = strip_readable(text)
473 return text.encode('utf-8')
478 def getContenttype(self):
479 return self.data.get_content_type()
482 class EmailAttachment:
483 def __init__(self, filename, contenttype, data):
484 self.filename = filename
485 self.contenttype = contenttype
488 def save(self,folder):
490 fp = open(folder+"/"+self.getFilename(),"wb")
498 def getFilename(self):
501 def getContenttype(self):
502 return self.contenttype
508 return imap4.decoder(str)[0]
511 return imap4.encoder(str.decode('utf-8'))[0]
513 def main(session, **kwargs):
515 if os.path.isfile('/usr/lib/python2.5/uu.py') is not True:
516 shutil.copy('/usr/lib/enigma2/python/Plugins/Extensions/EmailClient/uu.py', '/usr/lib/python2.5/uu.py')
519 session.openWithCallback(MessageCB, MessageBox, 'In order of missing standart python library files\ni have copied the nessary files now.\nBut you have to restart your Box\n to apply this!', type = MessageBox.TYPE_INFO)
521 session.open(EmailScreen)
523 def MessageCB(*args):
525 session2.open(EmailScreen)
527 def Plugins(path, **kwargs):
531 PluginDescriptor(name="Email Client", description="view Emails via IMAP4",
532 where = PluginDescriptor.WHERE_PLUGINMENU,