File: pylotto-orig.py

#!/usr/bin/python
"""
=====================================================================
PyLotto: select students of a class at random to receive copies
of a book;  I use this to give away a few free copies of Python
books in some of the classes I teach;  students enter the lottery
by sending an email message to "PP4E@learning-python.com", with 
subject "PYLOTTO", and their email address in the From header;  
winners are picked by scanning the email inbox and parsing emails
to find the entrants, and selecting from them at random;  

see Chapter 13 for much more on email tools, and Part 2 for hints
on adding a tkinter GUI for sign-up or reporting;  major caveat: 
this requires that POP email access be supported at the training
site, and this is not always the case in some closed contexts -- 
this may need to be run offsite, run on a remote server via telnet
or SSH (e.g., PuTTY), or invoked with a Web URL as a server-side 
CGI script that produces an HTML reply page;  the latter CGI option 
assumes your server runs Python 3.X as required by the current code
here, or you port this for use on Python 2.X servers (see asCgi() 
below for pointers, and see Chapter 15 for more on CGI in general);

update: now includes test mode that sends canned test emails, as
well as a cgi mode that prints the required headers and html tags;
could also send winners reply email, but SMTP is even less common;
alt: a CGI signup form and server-side dbase, but I prefer email;
also see pylotto24.py for a version that runs on Python 2.4 servers;
=====================================================================
"""

import poplib, email, random, getpass, pprint, sys

Drawings = 3
Signup   = 'PP4E@learning-python.com'
Subject  = 'PYLOTTO'
Server   = 'pop.secureserver.net'
RunAsCGI = False  # remote web URL?
 
def findPlayers(signup, subject, server, password, trace=print, hastop=True):
    """
    find and remove signup emails.
    py3.1 email requires decoding mail text to str (3.2 may not);
    messsage_from_string(s) == email.parser.Parser().parsestr(s);
    removes duplicate email addresses, but may not be sufficient;
    """
    players = []
    server = poplib.POP3(server)
    server.user(signup) 
    server.pass_(password)
    trace(server.getwelcome())
    trace(server.list())
    try:
        msgcount, msgbytes = server.stat()
        for i in range(msgcount):
            trace('message %s of %s...' % (i+1, msgcount))
            if not hastop:
                hdr, msgbytes, octets = server.retr(i+1)            # get full text
            else:
                hdr, msgbytes, octets = server.top(i+1, 0)          # headers only

            msglines = [line.decode('utf-8') for line in msgbytes]  # 3.1: to str
            msgtext  = '\n'.join(msglines)
            msgobj   = email.message_from_string(msgtext)           # parse text
            if msgobj['Subject'].upper() == subject:
                players.append(msgobj['From'])
                server.dele(i+1)                                    # del on quit
    finally: 
        server.quit()           # be sure to unlock mailbox on exit    
    return list(set(players))   # remove duplicate email addresses

def pickWinners(players, drawings):
    """
    choose winners at random.
    note: set.pop() is defined to remove an arbitrary set item 
    too, but I'd rather rely on the random module explicitly here
    to be sure that this is fair ("arbitrary" may be arbitrary);
    """
    winners = []
    for i in range(drawings):
        if not players:
            break
        else:
            drawn = random.choice(players)
            players.remove(drawn)
            winners.append(drawn)
    return winners

def main(password, trace=print):
    """
    main logic, modularized for reuse as cgi and tests
    """
    players  = findPlayers(Signup, Subject, Server, password, trace)
    print('Players:')
    pprint.pprint(players)

    winners  = pickWinners(players, Drawings)
    print('Winners:')
    pprint.pprint(winners)

def sendTestMails():
    """
    split off so callable during interactive testing
    """
    import smtplib, email.utils, email.message
    sendserver = 'smtpout.secureserver.net'
    players = ['"Book support" <lutz@rmi.net>',            # try various formats
               'lutz@learning-python.com',
               'the book <PP4E@learning-python.com>']

    for player in players:
        msgobj = email.message.Message()
        msgobj['From']    = player
        msgobj['To']      = Signup
        msgobj['Subject'] = Subject
        msgobj['Date']    = email.utils.formatdate()
        msgobj.set_payload('Signing up for lotto...\n')

        print('Connecting...')
        server = smtplib.SMTP(sendserver)
        failed = server.sendmail(player, [Signup, player], str(msgobj))       # cc player
        server.quit()
        assert not failed 
        print(str(msgobj))

def asTest(password):
    """
    send a few test mails prior to main logic;
    give emails time to show up before main();
    """
    import time
    global Drawings
    Drawings = 1
    sendTestMails()
    print('[pausing...]')
    time.sleep(20)         # just a guess, really
    main(password)

def asCgi(password):
    """
    run main logic on web server in response to client, to be
    sure POP mail will be usable; prints HTML to create reply;
    invoke url=http://www.py3xservername.com/cgi-bin/pylotto.py,
    from a web page (html) or using Python's urllib in a script;
    this assumes that the browser won't time-out before reply;
    to do: wrap print so all text is run though cgi.escape()?;
    to do: convert print/unicode usage so this runs on 2.X servers?
    done => see pylotto24.py for a port that runs on Python 2.4;
    """
    print('Content-type: text/html\n')
    print('<HTML>')
    print('<TITLE>PyLotto</TITLE>')
    print('<BODY><H1>PyLotto Results</H1>')
    print('<P><PRE>')
    main(password, trace=lambda *kargs, **pargs: None)
    print('</PRE></P></BODY></HTML>')

if __name__ == '__main__':
    if RunAsCGI:
        # run on web server; add pswd security here
        password = open('pylotto.pswd').readline().rstrip()
        asCgi(password)
    else:
        # run in console, on client or remote server
        password = getpass.getpass('pop email password?')
        if len(sys.argv) > 1:
            asTest(password)
        else:
            main(password)             



[Home page] Books Code Blog Python Author Train Find ©M.Lutz