Python Forum

Full Version: Automated Emailer (Using Selenium)
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Hi all. I just finished writing a web crawler which sends emails through gmail. It's one of my first GUI based programs that almost resembles a finished product, so I thought it would be worth posting on here to get some feedback. It is quite buggy at the moment, but it's functional and that's good enough for me.

Here's the code:
#Import necessary modules
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from tkinter import *
import time

#Centers tkinter window (https://stackoverflow.com/a/10018670)
def center(win):
    win.update_idletasks()
    width = win.winfo_width()
    height = win.winfo_height()
    x = (win.winfo_screenwidth() // 2) - (width // 2)
    y = (win.winfo_screenheight() // 2) - (height // 2)
    win.geometry('{}x{}+{}+{}'.format(width, height, x, y))

#Create GUI
def createGUILogin(oldRoot=False):
    global sendingEmail, sendingPassword, GUIText
    
    if oldRoot != False:
        oldRoot.destroy()
    
    readFile()
    root = Tk()
    root.title('Emailer GUI')
    root.geometry('320x250')
    center(root)
    
    #Login frame
    entry = Frame(root)
    entry.pack(anchor='center', padx=10, pady=10)
    
    emailText = Label(entry, text='Email:')
    emailText.grid(row=0, column=0)    
    emailEntry = Entry(entry)
    emailEntry.grid(row=0, column=1)
    
    passwordText = Label(entry, text='Password:')
    passwordText.grid(row=1, column=0)
    passwordEntry = Entry(entry)
    passwordEntry.grid(row=1, column=1)
    
    defaultButton = Button(entry, text='Use default account')
    defaultButton['command'] = lambda:login(sendingEmail, sendingPassword)
    defaultButton.grid(row=0, column=2)
    
    loginButton = Button(entry, text='Login')
    loginButton['command'] = lambda:login(emailEntry.get(), passwordEntry.get())
    loginButton.grid(row=1, column=2)
    
    #Update frame
    update = Frame(root)
    update.pack(anchor='center', padx=10, pady=10)
    
    GUILabel = Label(update, text='Update box:')
    GUILabel.grid(row=0, column=0)
    GUIText = Text(update, width=35, height=5, wrap=WORD)
    GUIText.grid(row=1, column=0, columnspan=3)
    
    #Buttons frame
    buttons = Frame(root)
    buttons.pack(anchor='center', padx=10, pady=10)
    
    logoutButton = Button(buttons, text='Logout')
    logoutButton['command'] = logout
    logoutButton.grid(row=2, column=0)
    
    exitButton = Button(buttons, text='Exit program')
    exitButton['command'] = root.destroy
    exitButton.grid(row=2, column=1)
    
    mainMenu = Button(buttons, text='Main menu')
    mainMenu['command'] = lambda:createGUIMenu(root)
    mainMenu.grid(row=2, column=2)    
    
    root.mainloop()

def createGUIMenu(oldRoot=False):
    global GUIText
    
    if oldRoot != False:
        oldRoot.destroy()
    
    readFile()
    root = Tk()
    root.title('Emailer GUI')
    root.geometry('200x130')
    center(root)
    
    #Options frame
    options = Frame(root)
    options.pack(anchor='center', padx=10, pady=10)
    
    annoyButton = Button(options, text='Annoy')
    annoyButton['command'] = lambda:createGUIAnnoy(root)
    annoyButton.pack()
    
    annoyButton = Button(options, text='Send')
    annoyButton['command'] = lambda:createGUISend(root)
    annoyButton.pack()
    
    #Buttons frame
    buttons = Frame(root)
    buttons.pack(anchor='center', padx=10, pady=10)
    
    exitButton = Button(buttons, text='Exit program')
    exitButton['command'] = root.destroy
    exitButton.grid(row=0, column=0)
    
    loginMenu = Button(buttons, text='Login screen')
    loginMenu['command'] = lambda:createGUILogin(root)
    loginMenu.grid(row=0, column=1)
    
    root.mainloop()

def createGUIAnnoy(oldRoot=False):
    global GUIText, emailsList, message
    
    if oldRoot != False:
        oldRoot.destroy()
    
    readFile()
    root = Tk()
    root.title('Emailer GUI')
    root.geometry('350x400')
    center(root)
    
    #Emails frame
    emails = Frame(root)
    emails.pack(anchor='center', padx=10, pady=10)
    
    emailsString = ''
    for x in emailsList:
        emailsString += x + '\n'
    
    GUILabel = Label(emails, text='Emails list:')
    GUILabel.grid(row=0, column=0)
    emailsText = Text(emails, width=35, height=5, wrap=WORD)
    emailsText.grid(row=1, column=0, columnspan=3)
    emailsText.insert(0.0, emailsString)
    
    #Entry frame
    entry = Frame(root)
    entry.pack(anchor='center', padx=10, pady=10)
    
    messageLabel = Label(entry, text='Message:')
    messageLabel.grid(row=0, column=0)
    messageEntry = Entry(entry)
    messageEntry.grid(row=0, column=1)
    
    defaultMessageButton = Button(entry, text='Use default message')
    defaultMessageButton['command'] = lambda:annoyingRepeats(emailsText.get(0.0, END), subjectEntry.get(), message, repeatsEntry.get())
    defaultMessageButton.grid(row=0, column=2)
    
    subjectLabel = Label(entry, text='Subject:')
    subjectLabel.grid(row=1, column=0)
    subjectEntry = Entry(entry)
    subjectEntry.grid(row=1, column=1)
    
    repeatsLabel = Label(entry, text='Repeats:')
    repeatsLabel.grid(row=2, column=0)
    repeatsEntry = Entry(entry)
    repeatsEntry.grid(row=2, column=1)
    
    annoyButton = Button(entry, text='Annoy')
    annoyButton['command'] = lambda:annoyingRepeats(emailsText.get(0.0, END), subjectEntry.get(), messageEntry.get(), repeatsEntry.get())
    annoyButton.grid(row=2, column=2)
    
    #Buttons frame
    buttons = Frame(root)
    buttons.pack(anchor='center', padx=10, pady=10)
    
    mainMenu = Button(buttons, text='Main menu')
    mainMenu['command'] = lambda:createGUIMenu(root)
    mainMenu.grid(row=2, column=0)
    
    onTopButton = Button(buttons, text='Always on top')
    onTopButton['command'] = lambda:root.wm_attributes("-topmost", 1)
    onTopButton.grid(row=2, column=1)
    
    #Update frame
    update = Frame(root)
    update.pack(anchor='center', padx=10, pady=10)
    
    GUILabel = Label(update, text='Update box:')
    GUILabel.grid(row=0, column=0)    
    GUIText = Text(update, width=35, height=5, wrap=WORD)
    GUIText.grid(row=1, column=0, columnspan=3)
    
    root.mainloop()

def createGUISend(oldRoot=False):
    global GUIText, emailsList, message
    
    if oldRoot != False:
        oldRoot.destroy()
    
    readFile()
    root = Tk()
    root.title('Emailer GUI')
    root.geometry('320x290')
    center(root)
    
    #Entry frame
    entry = Frame(root)
    entry.pack(anchor='center', padx=10, pady=10)
    
    emailLabel = Label(entry, text='Email:')
    emailLabel.grid(row=0, column=0)
    emailEntry = Entry(entry)
    emailEntry.grid(row=0, column=1)
    
    subjectLabel = Label(entry, text='Subject:')
    subjectLabel.grid(row=1, column=0)
    subjectEntry = Entry(entry)
    subjectEntry.grid(row=1, column=1)
    
    messageLabel = Label(entry, text='Message:')
    messageLabel.grid(row=2, column=0)
    messageEntry = Entry(entry)
    messageEntry.grid(row=2, column=1)
    
    repeatsLabel = Label(entry, text='Repeats:')
    repeatsLabel.grid(row=3, column=0)   
    repeatsEntry = Entry(entry)
    repeatsEntry.grid(row=3, column=1)
    
    sendButton = Button(entry, text='Send')
    sendButton['command'] = lambda:send(emailEntry.get(), subjectEntry.get(), messageEntry.get(), repeatsEntry.get())
    sendButton.grid(row=3, column=2)    
    
    #Buttons frame
    buttons = Frame(root)
    buttons.pack(anchor='center', padx=10, pady=10)
    
    mainMenu = Button(buttons, text='Main menu')
    mainMenu['command'] = lambda:createGUIMenu(root)
    mainMenu.grid(row=0, column=1)
    
    onTopButton = Button(buttons, text='Always on top')
    onTopButton['command'] = lambda:root.wm_attributes("-topmost", 1)
    onTopButton.grid(row=0, column=2)
    
    #Update frame
    update = Frame(root)
    update.pack(anchor='center', padx=10, pady=10)
    
    GUILabel = Label(update, text='Update box:')
    GUILabel.grid(row=0, column=0)    
    GUIText = Text(update, width=35, height=5, wrap=WORD)
    GUIText.grid(row=1, column=0, columnspan=3)
    
    root.mainloop()

#Find element function | http://isaacviel.name/make-web-driver-wait-element-become-visiable/
def findElement(xpath):
    global browser
    
    var = WebDriverWait(browser, 20).until(
        expected_conditions.visibility_of_element_located((By.XPATH, xpath)))
    return var

#Login function
def login(sendingEmail, sendingPassword):
    global browser, loggedIn
    
    if loggedIn == False:
        browser.get('https://mail.google.com')
        
        try:
            emailEntry = findElement('//input[@id="identifierId"]')
            emailEntry.send_keys(sendingEmail)
            nextButton = findElement('//div[@id="identifierNext"]')
            nextButton.click()
            
            passwordEntry = findElement('//input[@name="password"]')
            passwordEntry.send_keys(sendingPassword)
            nextButton = findElement('//span[text()="Next"]')
            nextButton.click()
            
            error('UPDATE: Logged in as ' + sendingEmail)
            loggedIn = True
        except:
            error('ERROR: Could not login as ' + sendingEmail)
    else:
        error('ERROR: Already logged in, please logout first')

#Logout function
def logout():
    global browser, loggedIn
    
    if loggedIn == True:
        try:
            userPicture = findElement('//a[@href="https://accounts.google.com/SignOutOptions?hl=en&continue=https://mail.google.com/mail&service=mail"]')
            userPicture.click()
            signOutButton = findElement('//a[text()="Sign out"]')
            signOutButton.click()
            switchAccountsButton = findElement('//div[@aria-label="Switch account"]')
            switchAccountsButton.click()
            removeAccountButton = findElement('//button[text()="Remove an account"]')
            removeAccountButton.click()
            currentAccountButton = findElement('//div[@data-profileindex="0"]')
            currentAccountButton.click()
            removeConfirmButton = findElement('//div[text()="Yes, remove"]')
            removeConfirmButton.click()
            
            error('UPDATE: Logged out')
            loggedIn = False
        except:
            error('ERROR: Could not logout')
    else:
        error('ERROR: Already logged out, please login first')

#Send mail function
def send(email, subject, message, repeats):
    global browser, loggedIn
    
    if loggedIn == True:
        try:
            repeats = int(repeats)
            
            if repeats <= 0:
                error('ERROR: Please enter a positive integer as repeats')
                passed = False
            else:
                passed = True            
        except:
            error('ERROR: Please enter an integer as repeats')
            passed = False
        
        if passed == True:
            try:
                for x in range(0, repeats):
                    composeButton = findElement('//div[@gh="cm"]')
                    composeButton.click()
                    recipientEntry = findElement('//textarea[@aria-label="To"]')
                    recipientEntry.send_keys(email)
                    subjectEntry = findElement('//input[@name="subjectbox"]')
                    subjectEntry.send_keys(subject)
                    textEntry = findElement('//div[@aria-label="Message Body"]')
                    textEntry.send_keys(message)
                    sendButton = findElement('//div[@data-tooltip-delay="800"]')
                    sendButton.click()           
                    
                    error('UPDATE: ' + subject + ' sent to: ' + email)
            except:
                error('ERROR: Could not send ' + subject + ' to ' + email)
    else:
        error('ERROR: You must be logged in to send mail')

#Sends emails in list a message every time clock changes
def annoyingRepeats(emailsList, subject, message, repeats):
    global browser, finishedEmail, loggedIn
    
    if loggedIn == True:
        #Print emails and message to be sent
        if type(emailsList).__name__ != 'list':
            emailsList = [emailsList]

        #Check that repeats is valid
        try:
            repeats = int(repeats)
            
            if repeats <= 0:
                error('ERROR: Please enter a positive integer as repeats')
                passed = False
            else:
                error('UPDATE: You will be notified at {} when the annoying is finished'.format(finishedEmail))
                passed = True
        except:
            error('ERROR: Please enter an integer as repeats')
            passed = False
        
        if passed == True:
            #Send all emails in list the message every time the clock changes
            prevMin = time.strftime("%M")
            for x in range(0, repeats):
                while True:
                    minute = time.strftime("%M")
                    if minute != prevMin:
                        prevMin = minute
                        for y in emailsList:
                            try:
                                send(y, subject + ' ' + str(x + 1), message, 1)
                            except:
                                break
                        break
            
            #Send finishing email
            finishedText = 'UPDATE: You annoyed the following emails for {:0.2f} hours:'.format(repeats / 60)
            for x in emailsList:
                finishedText += '\n' + x
            send(finishedEmail, 'Annoying is finished', finishedText, 1)
            
            error(finishedText)
    else:
        error('ERROR: You must be logged in to annoy mail')

#Read Email list.txt and get a list of emails and variables
def readFile():
    global emailsList, finishedEmail, sendingEmail, sendingPassword, message
    
    emailsList = []
    file = open('Your file location')
    for x in file.readlines():
        if len(x.strip()) == 0:
            continue
        elif len(x.split(' = ')) == 1:
            emailsList.append(list(x.split('\n'))[0])
        elif len(x.split(' = ')) == 2:
            var, data = x.split(' = ')
            if var == 'finishedEmail':
                finishedEmail = data.strip()
            elif var == 'sendingEmail':
                sendingEmail = data.strip()
            elif var == 'sendingPassword':
                sendingPassword = data.strip()
            elif var == 'message':
                message = data.strip()
            else:
                error('ERROR: Unknown variable in file')
        else:
            error('ERROR: Unknown error in file')

def error(text):
    global GUIText
    
    print(text)
    
    try:
        GUIText.delete(0.0, END)
        GUIText.insert(0.0, text)
    except:
        pass

#Run functions
loggedIn = False
readFile()
browser = webdriver.Chrome('Your driver location')
createGUILogin()

#Quit browser if GUI is closed
try:
    browser.quit()
except:
    pass
For selenium to work you must first install the module, then change the code on line 442 to the location of your browser driver. The web driver for google chrome can be found at: https://sites.google.com/a/chromium.org/.../downloads

This program also requires a text file which is formatted like this:
Quote:finishedEmail = Email to send updates to
sendingEmail = Default login email
sendingPassword = Default login password

message = Default message

example.email1@gmail.com
example.email2@gmail.com

You will need to change line 407 to the path of this file on your computer for it to run properly.
Other than that, everything should work as intended and you'll be able to send bulk emails with ease.
Thanks for your time if you decide to try this program out. Smile

Note: The annoy function waits until the minute value of current time changes to send each repeat, therefore if you have a repeat value of 5 the program will become unresponsive for 5 minutes.