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.Pixmap import Pixmap, MovingPixmap
8 from Components.ScrollLabel import ScrollLabel
9 from Components.config import config, ConfigSubsection, ConfigInteger, ConfigYesNo, 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 TagStrip
27 config.plugins.emailimap = ConfigSubsection()
28 config.plugins.emailimap.username = ConfigText("user", fixed_size=False)
29 config.plugins.emailimap.password = ConfigText("password", fixed_size=False)
30 config.plugins.emailimap.server = ConfigText("please.config.first", fixed_size=False)
31 config.plugins.emailimap.port = ConfigInteger(143, limits = (1, 65536))
33 # 0= fetch all header , 10= fetch only the last 10 headers/messages of a mailbox
34 config.plugins.emailimap.maxheadertoload = ConfigInteger(0, limits = (1, 100))
39 def onConnect(self, proto):
42 class EmailScreen(Screen, EmailHandler):
43 implements(imap4.IMailboxListener)
46 <screen position="110,83" size="530,430" title="Email" >
47 <widget name="boxlist" position="0,0" size="150,400" scrollbarMode="showOnDemand" />
48 <widget name="messagelist" position="150,0" size="380,400" scrollbarMode="showOnDemand" />
49 <widget name="infolabel" position="0,400" size="530,30" foregroundColor=\"white\" font=\"Regular;18\" />
55 def __init__(self, session, args = 0):
56 EmailHandler.__init__(self)
57 self.session = session
59 self.skin = EmailScreen.skin
60 Screen.__init__(self, session)
61 protocol.createFactory(self, config.plugins.emailimap.username.value, config.plugins.emailimap.server.value, config.plugins.emailimap.port.value)
63 self["actions"] = ActionMap(["InfobarChannelSelection", "WizardActions", "DirectionActions", "MenuActions", "ShortcutActions", "GlobalActions", "HelpActions", "NumberActions"],
66 "back": self.action_exit,
67 "historyNext": self.selectMessagelist,
68 "historyBack": self.selectBoxlist,
73 "menu": self.action_menu,
75 self["boxlist"] = MenuList([])
76 self["messagelist"] = MailList([])
77 self["infolabel"] = Label("")
78 self.onLayoutFinish.append(self.selectBoxlist)
80 def action_menu(self):
81 self.session.open(EmailConfigScreen)
83 def selectBoxlist(self):
84 self.currList = "boxlist"
85 self["boxlist"].selectionEnabled(1)
86 self["messagelist"].selectionEnabled(0)
88 def selectMessagelist(self):
89 self.currList = "messagelist"
90 self["boxlist"].selectionEnabled(0)
91 self["messagelist"].selectionEnabled(1)
94 self[self.currList].up()
97 self[self.currList].down()
100 self[self.currList].pageUp()
103 self[self.currList].pageDown()
106 if self.currList == "boxlist":
109 self.onMessageSelected()
111 def onBoxSelected(self):
112 c = self["boxlist"].getCurrent()
114 self.proto.examine(UTF7toUTF8(c[1][2])
115 ).addCallback(self.onExamine, c[0] , self.proto
116 ).addErrback(self.onExamineFailed, self.proto
119 def onMessageSelected(self):
120 c = self["messagelist"].getCurrent()
122 self.fetchMessageSize(c[0])
124 def fetchMessageSize(self, message):
125 print "fetchMessageSize",message
126 self.proto.fetchSize(message.uid
127 ).addCallback(self.onMessageSizeLoaded, message, self.proto
128 ).addErrback(self.onMessageLoadFailed, message, self.proto
131 def onMessageSizeLoaded(self, result, message, proto):
132 print "onMessageSizeLoaded", result, message
133 size = int(result[message.uid]['RFC822.SIZE'])
134 self.MAX_MESSAGE_SIZE_TO_OPEN = 4000000
135 if size >= self.MAX_MESSAGE_SIZE_TO_OPEN:
136 #ask here to open message
137 print "message to large to open (size=", size, ")"
139 self.loadMessage(message)
141 # def fetchBodyStructure(self, message):
142 # print "fetchBodyStructure",message
143 # self.proto.fetchBodyStructure(message.uid
144 # ).addCallback(self.onBodystructureLoaded, message, self.proto
145 # ).addErrback(self.onMessageLoadFailed, message, self.proto
148 def loadMessage(self, message):
149 print "loadMessage",message
150 self["infolabel"].setText("loading message")
152 self.proto.fetchMessage(message.uid
153 ).addCallback(self.onMessageLoaded, message, self.proto
154 ).addErrback(self.onMessageLoadFailed, message, self.proto
157 def onMessageLoaded(self, result, message, proto):
158 self["infolabel"].setText("parsing message")
159 print "onMessageLoaded"#,result,message
160 msgstr = result[message.uid]['RFC822']
161 msg = email.Parser.Parser().parsestr(msgstr)
162 msg.messagebodys = []
165 if msg.is_multipart():
166 for part in msg.walk():
167 if part.get_content_maintype()=="multipart":
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))
172 elif part.get_content_subtype() == "plain":
173 msg.messagebodys.append(EmailBody(part))
175 print "unkown content type= ", part.get_content_maintype(), "/", part.get_content_subtype()
177 print "found Attachment with ", part.get_content_type(), "and name", part.get_filename()
178 msg.attachments.append(EmailAttachment(part.get_filename(), part.get_content_type(), part.get_payload()))
180 msg.messagebodys.append(EmailBody(msg))
181 self.session.open(ScreenMailView, msg)
183 def onMessageLoadFailed(self, failure, message, proto):
184 print "onMessageLoadFailed", failure, message
185 self["infolabel"].setText(failure.getErrorMessage())
187 def action_exit(self):
188 if self.proto is not None:
190 ).addCallback(self.onLogedOut, self.proto
191 ).addErrback(self.onLogedOut, self.proto
196 def onLogedOut(self, result, proto):
197 print "onLogedOut", result
201 def onConnect(self, proto):
202 self["infolabel"].setText("connected")
203 proto.getCapabilities(
204 ).addCallback(self.cbCapabilities, proto
205 ).addErrback(self.ebCapabilities, proto
208 def cbCapabilities(self,reason,proto):
209 print "cbCapabilities",reason
213 def ebCapabilities(reason,proto):
214 print "ebCapabilities",reason
216 def onConnectFailed(self, reason):
217 self["infolabel"].setText(reason.getErrorMessage())
219 def onAuthentication(self, result, proto):
221 self["infolabel"].setText("logged in")
223 ).addCallback(self.onMailboxList, proto
226 def doLogin(self, proto):
230 context = proto.context.getContext()
231 d = proto.startTLS(context)
232 d = d.addCallback(proto.authenticate, config.plugins.emailimap.password.value)
234 d = proto.authenticate(config.plugins.emailimap.password.value)
235 d.addCallback(self.onAuthentication, proto)
236 d.addErrback(self.onAuthenticationFailed, proto)
239 def onAuthenticationFailed(self, failure, proto):
240 # If it failed because no SASL mechanisms match
241 print "onAuthenticationFailed", failure, proto
242 self["infolabel"].setText(failure.getErrorMessage())
244 failure.trap(imap4.NoSupportedAuthentication)
245 self.doLoginInsecure(proto)
249 def doLoginInsecure(self, proto):
250 print "login INSECURE"
251 proto.login(config.plugins.emailimap.username.value, config.plugins.emailimap.password.value
252 ).addCallback(self.onAuthentication, proto
253 ).addErrback(self.onInsecureAuthenticationFailed, proto
256 def onInsecureAuthenticationFailed(self, failure, proto):
257 print "onInsecureAuthenticationFailed", failure, proto
258 self["infolabel"].setText(failure.getErrorMessage())
260 def onMailboxList(self, result, proto):
261 print "onMailboxList", result, proto
264 flags, hierarchy_delimiter, name = i
265 list.append((UTF7toUTF8(name).encode('utf-8'), i))
266 self["boxlist"].l.setList(list)
268 def onExamine(self, result, mboxname, proto):
269 print "onExamine", result, mboxname
270 self.setTitle("Mailbox: "+mboxname)
271 self.currentmailbox = mboxname
272 numMessagesinFolder = int(result['EXISTS'])
273 if numMessagesinFolder <= 0:
274 self["infolabel"].setText("Box '"+mboxname+"' is empty")
275 self["messagelist"].l.setList([])
278 if config.plugins.emailimap.maxheadertoload.value > 0:
279 maxMessagesToFetch = config.plugins.emailimap.maxheadertoload.value
280 startmsg = numMessagesinFolder-maxMessagesToFetch+1
283 rangeToFetch = [startmsg, numMessagesinFolder]
285 rangeToFetch = [1, numMessagesinFolder]
286 self["infolabel"].setText("loading headers "+str(rangeToFetch[0])+"-"+str(rangeToFetch[1])+" of Box '"+mboxname+"'")
289 # proto.fetchEnvelope('%i:%i'%(rangeToFetch[0], rangeToFetch[1]) #'1:*'
290 # ).addCallback(self.onEnvelopeList, proto
292 proto.fetchHeaders('%i:%i'%(rangeToFetch[0], rangeToFetch[1]) #'1:*'
293 ).addCallback(self.onHeaderList, proto
296 except imap4.IllegalServerResponse, e:
299 def onExamineFailed(self, failure, proto):
300 print "onExamineFailed", failure, proto
301 self["infolabel"].setText(failure.getErrorMessage())
303 def onHeaderList(self, result, proto):
304 print "onHeaderList"#,result,proto
305 self["infolabel"].setText("headers loaded, now parsing ...")
309 list.append(self.buildMessageListItem(MessageHeader(m, result[m]['RFC822.HEADER'])))
313 self["messagelist"].l.setList(list)
314 self["infolabel"].setText("have "+str(len(result))+" messages ")
316 def buildMessageListItem(self, message):
318 res.append(MultiContentEntryText(pos=(5, 0), size=(380, 19), font=0, text=message.getSenderString()))
319 res.append(MultiContentEntryText(pos=(5, 19), size=(380, 19), font=0, text=message.get('date', default='kein Datum')))
320 res.append(MultiContentEntryText(pos=(5, 38), size=(380, 19), font=0, text=message.getSubject()))
323 # IMailboxListener methods
325 def modeChanged(self, writeable):
326 print "modeChanged", writeable
328 def flagsChanged(self, newFlags):
329 print "flagsChanged", newFlags
331 def newMessages(self, exists, recent):
332 print "newMessages", exists, recent
334 class ScreenMailView(Screen):
336 def __init__(self, session, email, args = 0):
337 self.session = session
339 self.skin = "<screen position=\"85,80\" size=\"550,476\" title=\"view Email\" >"
340 self.skin += """<widget name="from" position="0,0" size="550,25" font="Regular;20" />
341 <widget name="date" position="0,25" size="550,25" font="Regular;20" />
342 <widget name="subject" position="0,50" size="550,25" font="Regular;20" />
343 <widget name="body" position="0,75" size="550,375" font="Regular;20" />
344 <widget name="buttonred" position="10,436" size="100,30" backgroundColor="red" valign="center" halign="center" zPosition="2" foregroundColor="white" font="Regular;16"/>
345 <widget name="buttongreen" position="120,436" size="100,30" backgroundColor="green" valign="center" halign="center" zPosition="2" foregroundColor="white" font="Regular;16"/>
346 <widget name="buttonyellow" position="230,436" size="100,30" backgroundColor="yellow" valign="center" halign="center" zPosition="2" foregroundColor="white" font="Regular;16"/>
347 <widget name="buttonblue" position="340,436" size="100,30" backgroundColor="blue" valign="center" halign="center" zPosition="2" foregroundColor="white" font="Regular;16"/>
349 Screen.__init__(self, session)
350 self["from"] = Label(_("From: %s" %self.email.get('from', 'no-from')))
351 self["date"] = Label(_("Date: %s" %self.email.get('date', 'no-date')))
352 self["subject"] = Label(_(self.email.get('subject', 'no-subject')))
353 self["body"] = ScrollLabel(_(self.email.messagebodys[0].getData()))
354 self["buttonred"] = Label("")
355 self["buttongreen"] = Label("")
356 self["buttonyellow"] = Label("Headers")
357 self["buttonblue"] = Label(_("delete"))
358 self["actions"] = ActionMap(["WizardActions", "DirectionActions", "MenuActions", "ShortcutActions"],
361 "up": self["body"].pageUp,
362 "down": self["body"].pageDown,
363 "left": self["body"].pageUp,
364 "right": self["body"].pageDown,
365 "red": self.selectBody,
366 "green": self.selectAttachment,
367 "yellow": self.openMessagesHeaders,
371 self.onLayoutFinish.append(self.updateButtons)
374 pass #self.session.openWithCallback(self.deleteCB, ChoiceBox, title="really delete Mail?", list=[(_("yes"), True),(_("no"), False)])
376 def deleteCB(self, returnValue):
377 if returnValue[1] is True:
380 def openMessagesHeaders(self):
381 pass #self.session.open(ScreenMailViewHeader,self.profil,self.email)
383 def updateButtons(self):
384 self["buttonred"].setText(_("Bodys"))
385 if len(self.email.attachments):
386 self["buttongreen"].setText("Attachments")
388 self["buttongreen"].setText("")
390 def selectBody(self):
391 if len(self.email.messagebodys):
393 for a in self.email.messagebodys:
394 list.append((a.getContenttype(), a))
395 self.session.openWithCallback(self.selectBodyCB, ChoiceBox, _("select Body"), list)
397 def selectBodyCB(self, choice):
398 if choice is not None:
399 self["body"].setText(choice[1].getData())
401 def selectAttachment(self):
402 if len(self.email.attachments):
404 for a in self.email.attachments:
405 list.append((a.getFilename(), a))
406 self.session.openWithCallback(self.selectAttachmentCB, ChoiceBox, _("select Attachment"), list)
408 def selectAttachmentCB(self, choice):
409 if choice is not None:
410 print "Attachment selected", choice[1].getFilename()
411 #showMessageBox(self.session)
413 class MailList(MenuList, HTMLComponent, GUIComponent):
414 def __init__(self, list):
415 MenuList.__init__(self, list)
416 GUIComponent.__init__(self)
417 self.l = eListboxPythonMultiContent()
420 self.l.setFont(0, gFont("Regular", 18))
421 self.l.setFont(1, gFont("Regular", 20))
423 GUI_WIDGET = eListbox
425 def postWidgetCreate(self, instance):
426 instance.setContent(self.l)
427 instance.setItemHeight(60)
429 class MessageHeader(object):
430 def __init__(self, uid, message):
431 self.uid = uid #must be int
432 self.message = email.Parser.Parser().parsestr(message)
434 def getSenderString(self):
435 if self.get("from") is None:
438 return self.get("from")
440 def getSubject(self):
441 if self.get("subject") is None:
444 # need some decoding for subjects like =?iso-8859-1?Q?=DCbermut?=
445 elif self.get("subject").startswith("=?") and self.get("subject").endswith("?="):
446 c = self.get("subject").split("?")
449 text = "?".join(c[3:])
454 return text.decode(codec).encode("utf-8")
457 return self.get("subject")
459 def get(self, key, default=None):
460 return self.message.get(key,failobj=default)
463 return "<MessageHeader uid="+str(self.uid)+", subject="+self.get("subject","no-subject")+">"
467 def __init__(self,data):
470 def getEncoding(self):
471 return self.data.get_content_charset()
474 text = self.data.get_payload(decode=True)
475 if self.getEncoding() is not None:
476 print "decoding text with charset ",self.getEncoding()
477 text = text.decode(self.getEncoding())
478 if self.getContenttype() == "text/html":
479 print "stripping html"
480 text = TagStrip().strip_readable(text)
482 return text.encode('utf-8')
487 def getContenttype(self):
488 return self.data.get_content_type()
491 class EmailAttachment:
492 def __init__(self, filename, contenttype, data):
493 self.filename = filename
494 self.contenttype = contenttype
497 def save(self,folder):
499 fp = open(folder+"/"+self.getFilename(),"wb")
507 def getFilename(self):
510 def getContenttype(self):
511 return self.contenttype
517 return imap4.decoder(str)[0]
520 return imap4.encoder(str.decode('utf-8'))[0]
522 def main(session, **kwargs):
523 session.open(EmailScreen)
525 def Plugins(path, **kwargs):
529 PluginDescriptor(name="Email Client", description="view Emails via IMAP4",
530 where = PluginDescriptor.WHERE_PLUGINMENU,
534 #PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = startScrobbler)