Saturday, May 14, 2011

GMail/Hotmail Notifier (Python, Tk UI)


This was both an exercise after learning some Python and an application I needed as a simple replacement to my dear Digsby on Ubuntu.

It is an email notifier to automatically check mailbox every 60 seconds. In the case of GMail, it was no problem at all, just a couple of lines and I get the new "UnSeen" mail. But for Hotmail, it was a problem because POP3 protocol does not offer this feature. So if you use this script/application for Hotmail, notice that it will notify if any mail arrives after logging in, and notification will still be there until you log in again.

The application deals with one account at a time, but I could run several instances.

The script is not perfect but it did the job on both Ubuntu 10.10 and Windows 7 Professional :)




You can find a compiled version of the script (for linux) here : Download

for compiling the code, I used PyInstaller :
the process was as simple as that:
adly@ubuntu:~/Documents/pyinstaller-1.5$ python Configure.py 
adly@ubuntu:~/Documents/pyinstaller-1.5$ python Makespec.py --onefile /home/adly/Documents/mail_notifier.py
adly@ubuntu:~/Documents/pyinstaller-1.5$ python Build.py /home/adly/Documents/pyinstaller-1.5/mail_notifier/mail_notifier.spec 

then the application was created in:
/home/adly/Documents/pyinstaller-1.5/mail_notifier/dist


Script Code:

[mail_notifier.py] Download

from Tkinter import *
import imaplib
import poplib
import re
import time
import threading

#GUI root
root = Tk()

#variables connecting between core and GUI
email = StringVar()
password = StringVar()
status = StringVar()

def checkMail(*args):
    #check for GMail
    if re.search(r'@gmail.', email.get()):
        okbutton.configure(state=DISABLED)
        gmail = gmailThread()
        gmail.setDaemon(True)
        gmail.start()
    #check for Hotmail/Live accounts
    elif re.search(r'@hotmail.', email.get()) or re.search(r'@live.', email.get()):
        okbutton.configure(state=DISABLED)
        msn = msnThread()
        msn.setDaemon(True)
        msn.start()
    else:
        status.set("Only GMail/Hotmail/Live accounts accepted!")

class gmailThread (threading.Thread):
    def run(self):
        try:
            status.set("Checking inbox. Please wait ...")
            
            #gmail only takes username, so some splitting needed
            usernameString = email.get().split('@')[0]
            passwordString = password.get()
            
            #Working with IMAP protocol in GMail allows to get the number of 
            #"UnSeen" mail. So the Gmail part of this script is just straight 
            #forward.
            
            while 1==1:
                mailObj = imaplib.IMAP4_SSL('imap.gmail.com','993')
                mailObj.login(usernameString, passwordString)
                mailObj.select()
                unreadMail = str(len(mailObj.search(None, 'UnSeen')[1][0].split()))
                mailObj.close()
                mailObj.logout()
                msg = email.get()+"("+unreadMail+")"
                status.set(msg)
                #sleep for 60 sec
                time.sleep(60)
        except:
            okbutton.configure(state=NORMAL)
            status.set("Error occurred! Please check credentials or internet connection.")

class msnThread (threading.Thread):
    def run(self):
        try:
            status.set("You will get notification of any new mail(s) since login ...")
            
            usernameString = email.get()
            passwordString = password.get()
            
            #get list of email unique ids
            mailObj = poplib.POP3_SSL('pop3.live.com', 995)
            mailObj.user(usernameString)
            mailObj.pass_(passwordString)
            mailList = mailObj.uidl()[1]
            mailObj.quit()
            
            #the only thing to do is to get the id of newest email and
            #check if it exists in the list above, if it does not exist
            #then it is a new email. So actually user can only notice 
            #any new mail that came after the first login, this is all
            #because POP3 protocol does not give data about new mail and
            #what i made was just a simple solution
            while 1==1:
                time.sleep(60)
                mailObj = poplib.POP3_SSL('pop3.live.com', 995)
                mailObj.user(usernameString)
                mailObj.pass_(passwordString)
                newestMailId = mailObj.uidl()[1][-1]
                if newestMailId not in mailList:
                    status.set("New mail(s) arrived since last login!")
                mailObj.quit()
                
        except:
            okbutton.configure(state=NORMAL)
            status.set("Error occurred! Please check credentials or internet connection.")


########## GUI section ###########

root.title("GMail/Hotmail Notifier")

mainframe = Frame(root)
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0, weight=1)

#email
emailLabel = Label(mainframe, text="Email:")
emailLabel.grid(column=1, row=1, sticky=W)
emailEntry = Entry(mainframe, width=30, textvariable=email)
emailEntry.grid(column=2, row=1, sticky=(W,E))

#password
passwordLabel = Label(mainframe, text="Password:")
passwordLabel.grid(column=1, row=2, sticky=W)
passwordEntry = Entry(mainframe, width=30, textvariable=password, show="*")
passwordEntry.grid(column=2, row=2, sticky=(W,E))

#ok button
okbutton = Button(mainframe, text="ok", command=checkMail )
okbutton.grid(column=2, row=3, sticky=E)

#status bar
statusLabel = Label(mainframe, textvariable=status)
statusLabel.grid(column=1, row=4, columnspan=2, sticky=W)

#capture focus on start
emailEntry.focus()

#capture the "enter" key for usability
emailEntry.bind('<Return>', checkMail)
passwordEntry.bind('<Return>', checkMail)
root.bind('<Return>', checkMail)

root.mainloop()


Resources: