#!/usr/bin/python # # SpamCubed: random mass mailer # Copyright (C) 2006 Rami Chowdhury # rami.chowdhury@gmail.com # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the foll- # owing conditions are met: # # Redistributions of source code must retain the above copy- # right notice, this list of conditions and the following dis- # claimer. # # Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials pro- # vided with the distribution. # # Neither the name of the author nor the names of contributors # to this software may be used to endorse or promote products # derived from this software without specific prior written # permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CON- # TRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES INC- # LUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCH- # ANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCL- # AIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EX- # EMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIM- # ITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import os, sys import sre, smtplib import urllib import cPickle class BoxOfSpam: def __init__(self): '''Initializes the class' data members.''' self._subject = "" self._greeting = "Dear" self._message = "" self._college = "Merton" self._address = "" self._people = [] self._searchState = False # Instructions that it recognizes self._instructions = { 'add' : (self.addPerson, "Follow this keyword with a name to add a new person at the currently set college"), 'remove' : (self.dropPerson, "Follow this keyword with a person's first or last name to remove them"), 'status' : (self.printState, "Displays the program's current state"), 'set' : (self.setOptions, "Follow this keyword with one of the options defined below"), 'send' : (self.sendMessages, "Shows the confirmation and sends out the messages"), 'help' : (self.showHelp, "Displays the help messages"), 'save' : (self.saveToFile, "Saves the program's current state to file"), 'load' : (self.loadFromFile, "Loads program state from the file specified"), 'quit' : (sys.exit, "Exits the program") } # Options available to the 'set' command self._options = { 'college' : (self.setColl, "Type in a name. If the system doesn't recognize the college, it will complain but not try to second-guess you"), 'address' : (self.setAddress, "Mail will appear to be sent from the address specified here"), 'greeting' : (self.setGreeting, "Type in the greeting you'd like - it'll automatically be followed by a space and the first name of the person being sent to. If you set it to 'off', you can just put a generic greeting in your message."), 'search' : (self.setSearch, "Run via the contact search pages?"), 'subject' : (self.setSubject, "Set the subject of your messages"), 'message' : (self.setMessage, "Type in your message!") } # The giant dictionary of colleges self._allColleges = { 'all-souls' : 'all-souls', 'balliol' : 'balliol', 'brasenose' : 'bnc', 'christ-church' : 'chch', 'corpus-christi' : 'ccc', 'exeter' : 'exeter', 'green' : 'green', 'harris-manchester' : 'hmc', 'hertford' : 'hertford', 'jesus' : 'jesus', 'keble' : 'keble', 'kellogg' : 'kellogg', 'lmh' : 'lmh', 'lady-margaret-hall' : 'lmh', 'linacre' : 'linacre', 'lincoln' : 'lincoln', 'magdalen' : 'magd', 'mansfield' : 'mansfield', 'merton' : 'merton', 'new' : 'new', 'nuffield' : 'nuffield', 'oriel' : 'oriel', 'pembroke' : 'pmb', 'queens' : 'queens', 'somerville' : 'some', 'st-annes' : 'st-annes', 'st-antonys' : 'sant', 'st-catherines' : 'stcatz', 'st-cross' : 'stx', 'st-edmund-hall' : 'seh', 'st-hildas' : 'st-hildas', 'st-hughs' : 'st-hughs', 'st-johns' : 'sjc', 'st-peters' : 'spc', 'templeton' : 'templeton', 'trinity' : 'trinity', 'university-college' : 'univ', 'wadham' : 'wadh', 'wolfson' : 'wolfson', 'worcester' : 'worc' } self._command = "" self._commands = () self._name = "SpamCubed" self._version = "0.2" print "Welcome to %s %s!" % (self._name, self._version) def trudge(self): while (True): print "CURRENT COLLEGE: %s SEARCH MODE: %s" % (self._college, self._searchState) self._command = raw_input(">> ") self._commands = self._command.split(' ') if (self._commands[0] in self._instructions.keys()): self._instructions[self._commands[0]][0]() else: print "Unrecognized command \"%s\". Type 'help' if you're not sure how to use %s!" % (self._command, self._name) def addPerson(self): fullName = "" try: c = self._commands[1] fullName = " ".join(self._commands[1:]) except IndexError: fullName = raw_input("Enter person's full name: ") names = fullName.split(' ') firstName = names[0] surName = "".join(names[1:]) personTuple = (firstName, surName, self._college) self._people.append(personTuple) print "Added %s %s at %s" % personTuple def dropPerson(self): fullName = "" try: c = self._commands[1] fullName = " ".join(self._commands[1:]) except IndexError: fullName = raw_input("Enter person's name: ") names = fullName.split(' ') Name = names[0] i = 0; match = None; removedPerson = None while (i < len(self._people)): personTuple = self._people[i] match = sre.search(Name, personTuple[0]) if (match != None): removedPerson = "%s %s (%s)" % personTuple del self._people[i] break match = sre.search(Name, personTuple[1]) if (match != None): removedPerson = "%s %s (%s)" % personTuple del self._people[i] break i = i+1 if (removedPerson == None): removedPerson = "nobody" print "Removed %s from list" % removedPerson def setOptions(self): try: opt = self._commands[1] except IndexError: opt = raw_input("Enter option to set: ") if opt in self._options.keys(): if (len(self._commands) < 2): self._commands.append(opt) else: self._commands[1] = opt self._options[opt][0]() else: print "Unrecognized option '%s'" % opt def sendMessages(self): self.printState() conf = raw_input("Are you sure you want to send out mail to all the above people [Y/N]? ") if (conf != "Y"): print "Messages not sent." return # initializes some numbers and names smtpServer = "smtp.ox.ac.uk" baseHeader = "Subject: %s\r\nX-Mailer: %s %s\r\n\r\n" % (self._subject, self._name, self._version) header = ""; greeting = ""; email = "" print "\nConnecting to SMTP server..." try: smtpSession = smtplib.SMTP(smtpServer) except: print "Error connecting to the mail server! Please try again." return for personTuple in self._people: if not self.contactSearch(personTuple): print "\t\tContact search failed for %s %s (%s)" % personTuple continue if (personTuple[1] != ""): email = "%s.%s" % (personTuple[0].lower(), personTuple[1].lower()) else: email = personTuple[0].lower() email = "%s@%s.ox.ac.uk" % (email, self.convertFormat(personTuple[2])) if (self._greeting != None): greeting = "%s %s,\n\n" % (self._greeting, personTuple[0]) header = "To: %s %s <%s>\r\n%s" % (personTuple[0], personTuple[1], email, baseHeader) smtpSession.sendmail(self._address, email, header + greeting + self._message) print "\t\tE-mail sent to %s %s <%s>" % (personTuple[0], personTuple[1], email) smtpSession.quit() print "\tDisconnected from server. Done." def showHelp(self): print "Welcome to %s %s! It's here to let you compose and send messages to a whole slew of people (at Oxford University) at the same time. The way the basic interface works is that you type a command (one of the below) in, and follow it with one or more options, as specified by the command's own syntax.\n" % (self._name, self._version) for element in self._instructions.keys(): print " %s: %s" % (element.ljust(8), self._instructions[element][1]) print "\nThe options to the 'set' command are described below:" for element in self._options.keys(): print " %s: %s" % (element.ljust(8), self._options[element][1]) print "\nHave fun!\n" def setColl(self): college = "" try: c = self._commands[2] college = " ".join(self._commands[2:]) except IndexError: college = raw_input("Set college to: ") college2 = college.replace("'","") college2 = college2.replace('.','') allCollegesList = self._allColleges.keys() allCollegesList.extend(self._allColleges.values()) if college2.replace(' ', '-').lower() not in allCollegesList: print "WARNING! Unrecognized college '%s'" % college self._college = college print "College set to %s" % college def setSearch(self): onoff = "" try: onoff = self._commands[2] except IndexError: onoff = raw_input("Set search mode to [on/off]: ") if (onoff == "on"): self._searchState = True elif (onoff == "off"): self._searchState = False else: print "Undefined setting for search mode." def setSubject(self): subject = "" try: s = self._commands[2] subject = " ".join(self._commands[2:]) except IndexError: subject = raw_input("Set subject line to: ") self._subject = subject print "Subject set to \"%s\"" % subject def setAddress(self): addr = "" try: a = self._commands[2] addr = " ".join(self._commands[2:]) except IndexError: addr = raw_input("Set return address to: ") self._address = addr print "Address set to '%s'" % addr def setMessage(self): try: filename = self._commands[2] try: f = open(filename, "r") messageText = "\n".join(f.readlines()) f.close() self._message = messageText print "Message content set" return except IOError: print "Error opening file '%s'. Please try again." % filename return except IndexError: pass messageText = "" print '''-------------------- * The message editor is very, very simple. A '>' prompt will appear * at the start of every line, and you can type for as long as you * like; when you're done, type 'END' on a line of its own and it * will exit. If you'd like to load a text file with your message * in it, just type 'FILE' and then the name of your file, and hit * Enter. Have fun! -------------------- ''' continueFlag = True line = "" while (continueFlag): line = raw_input("> ") if (line == "END"): continueFlag = False elif (sre.match("FILE", line)): match = sre.search("FILE (.*?)$", line) filename = match.group(1) try: f = open(filename, "r") messageText = "\n".join(f.readlines()) f.close() except IOError: print "Error opening file '%s'. Please try again." % filename return continueFlag = False else: messageText = messageText + line + "\n" self._message = messageText print "Message content set" def setGreeting(self): greeting = "" try: greeting = self._commands[2] except IndexError: greeting = raw_input("Set greeting to ('off' to deactivate): ") if (greeting == "off"): self._greeting = None print "Greeting turned off" else: self._greeting = greeting print "Greeting set to \"%s X\"" % greeting def convertFormat(self, string): college = string.replace(' ', '-') college = college.lower() college = college.replace("'","") college = college.replace('.','') if college not in self._allColleges.keys(): return college return self._allColleges[college] def saveToFile(self): filename = "" try: filename = self._commands[1] except IndexError: filename = raw_input("Enter name of file to save to: ") # Puts the data in a dictionary allDict = { 'subject' : self._subject, 'greeting' : self._greeting, 'message' : self._message, 'college' : self._college, 'people' : self._people, 'searchState' : self._searchState } # Pickles it f = open(filename, "w") cPickle.dump(allDict, f) f.close() print "Saved data to file '%s'" % filename def loadFromFile(self): filename = "" try: filename = self._commands[1] except IndexError: filename = raw_input("Enter name of file to load from: ") # Loads the pickled data try: f = open(filename, "r") allDict = cPickle.load(f) f.close() except IOError: print "Error opening file '%s'. Please try again" % filename return # Sets the current instance data accordingly self._subject = allDict['subject'] self._greeting = allDict['greeting'] self._message = allDict['message'] self._college = allDict['college'] self._people = allDict['people'] self.searchState = allDict['searchState'] print "Data loaded from file '%s'" % filename def printState(self): greeting = "" if (self._greeting != None): greeting = "%s [name],\n" % self._greeting print ''' APPARENT ADDRESS: %s COLLEGE: %s SEARCH MODE: %s SUBJECT: %s -------------- MESSAGE -------------- %s%s ------------------------------------- PEOPLE:''' % (self._address, self._college.ljust(16), self._searchState, self._subject, greeting, self._message) for person in self._people: print "\t%s %s at %s" % (person[0], person[1], person[2]) def contactSearch(self, personTuple): # checks to make sure searching is enabled! if not, returns true if self._searchState == False: return True # makes sure the fields are nice and simple and lowercase personList = [ personTuple[0], personTuple[1], personTuple[2] ] personList[0] = personTuple[0].lower() personList[1] = personTuple[1].lower() # sets up the GET query-string with the right variables urlString = "http://www.ox.ac.uk/cgi-bin/contacts?surname=" + personList[1] + "&match=exact+match&initial=" + personList[0][0] + "&submit=Find+email+address&.cgifields=match" # now uses urllib to fetch the result try: cSearchResult = urllib.urlopen(urlString) except: return False # sets up a regex with the right variables cSearchRegex = r'' + personList[1] + ", " + personList[0] + '(?i)' # iterates over the cSearchResult object, matching the regex for line in cSearchResult: cRegexResult = sre.search(cSearchRegex, line) if cRegexResult is not None: return True return False ############# Main program ########### a = BoxOfSpam() a.trudge()