Skip to content

Instantly share code, notes, and snippets.

@marknca
Last active December 11, 2020 22:07
Show Gist options
  • Save marknca/7621937 to your computer and use it in GitHub Desktop.
Save marknca/7621937 to your computer and use it in GitHub Desktop.
A quick Secret Santa generator. You can specify restrictions (e.g., don't let Larry draw Jen's name). Set the three TODO sections (line 14, line 17, and line 74) before running the script.
#! /usr/bin/env python
# Usage
# ===============================
# test run
# ./santa.py --test
#
# game time
# ./santa.py --password EMAIL
#
import argparse
import datetime
import random
import smtplib
import string
import sys
# set this with the --password switch at the command line
OUTBOUND_EMAIL_PASSWORD = ""
# TODO: configure these for your environment
FROM_ADDRESS = 'santa@'
FROM_ADDRESS_NAME = 'Santa'
# TODO: set your exchange participants and any restrictions
ppl = {
# people in draw: [ 'cant', 'get' ]
# e.g.,
# mark: { 'email': 'mark@some.org', 'dont_match_with': [ 'mark's wife', 'whomever mark had for the last exchange' ] },
'p1': { 'email': 'p1@some.org', 'dont_match_with': [ 'p2', 'p3' ] },
'p2': { 'email': 'p2@some.org', 'dont_match_with': [ 'p1', 'p3' ] },
'p3': { 'email': 'p3@some.org', 'dont_match_with': [ 'p4' ] },
'p4': { 'email': 'p4@some.org', 'dont_match_with': [ ] },
'p5': { 'email': 'p5@some.org', 'dont_match_with': [ ] },
}
# Global functions
def create_argument_parser(script_description, arguments):
"""
Configure an ArgumentParser object to handle command line options
and arguments
Arguments:
==========
script_description (str)
The friendly description of the script
arguments (dict)
A dict setup as { 'arg': { 'help': '', 'required': '', } }
describing the accepted arguments
Output:
========
(argparse.ArgumentParser) Fully configured parser for command line
arguments
"""
parser = argparse.ArgumentParser(description=script_description)
for arg, vals in arguments.items():
parser.add_argument('--%s' % arg,
action='store' if not vals.has_key('action') else vals['action'],
required=False if not vals.has_key('required') else vals['required'],
default=None if not vals.has_key('default') else vals['default'],
help=vals['help'] if vals.has_key('help') else '',
)
return parser
def send_msg(to_name, to_email_address, match_name):
# Cribbed from http://segfault.in/2010/12/sending-gmail-from-python/
session = smtplib.SMTP('smtp.gmail.com', 587)
session.ehlo()
session.starttls()
session.ehlo
session.login(FROM_ADDRESS, OUTBOUND_EMAIL_PASSWORD)
headers = ["from: " + FROM_ADDRESS,
"subject: " + '*** Secret Santa pick for %s ***' & datetime.datetime.now().strftime('%Y'),
"to: " + to_email_address,
"mime-version: 1.0",
"content-type: text/html"]
headers = "\r\n".join(headers)
# TODO: customize this message for your gift exchange
body = ("""
<p>
Merry Christmas %s!
</p>
<p>
Your pick for this year's Secret Santa is <b>%s</b>.
</p>
<p>
As with other years, the kids from the Secret Santa exchange.
</p>
<p>
This year the limit is $___--not including shipping. Remember if you're sending something across the country, the last day for shipping is ___.
</p>
<p>
Happy holidays,
<br />
Santa
</p>
""" % (to_name.capitalize(), match.capitalize())).replace('\t', '')
session.sendmail('%s <%s>' % (FROM_ADDRESS_NAME, FROM_ADDRESS), '%s' % to, headers + "\r\n\r\n" + body)
def generate_matches():
matches = {}
remaining = ppl.keys()
for p, data in ppl.items():
match = random.choice(remaining)
matched = True
if match == p: matched = False
if match in data['dont_match_with']: matched = False
if matched:
matches[p] = match
for i, r in enumerate(remaining):
if r == match: del(remaining[i])
return matches if len(matches) == len(ppl.keys()) else None
def get_valid_run(test_only=False):
# trying to solve this elegantly is actually a waste of time
# it's much simpler and faster to re-run the matches if we
# don't have a valid set (e.g., p1 is matched with someone they
# shouldn't be)
num_of_matches = 0
total_run = 100000
final_selection = None
for i in range(total_run):
matches = generate_matches()
if matches:
num_of_matches += 1
final_selection = matches
# let the user know how many runs we went through
print '%s %% (%s/%s)' % (num_of_matches / (total_run * 1.0) * 100, num_of_matches, total_run)
# create an anonymous mask to display the results
anon = {}
for i, k in enumerate(ppl.keys()):
anon[k] = string.letters[i]
# print out a final select key (it's anonymous)
for k, v in final_selection.items():
print '%s\t%s' % (anon[k], anon[v])
if not test_only:
send_msg(k, ppl[k]['email'], v)
if __name__ == "__main__":
arg_parser = create_argument_parser("Simple Secret Santa generator", {
'password': { 'help': 'The password for the outbound email account' },
'test': { 'help': 'Create a test run for the generator. This will NOT send out emails', 'action': 'store_true' },
})
args, potential_ips = arg_parser.parse_known_args()
args = vars(args)
if args['password'] and len(args['password']) > 0:
# set the password for the email account
OUTBOUND_EMAIL_PASSWORD = args['password']
if args['test']:
print "*** TEST RUN ***"
get_valid_run(test_only=True)
else:
get_valid_run()
@marknca
Copy link
Author

marknca commented Nov 24, 2013

# test run
./santa.py --test

# game time
./santa.py --password EMAIL

@marknca
Copy link
Author

marknca commented Nov 24, 2013

FYI, this is queued up to use a Gmail account. Change line 72 if you want to use another provider

@mike391
Copy link

mike391 commented Nov 21, 2018

some errors:
Line 105: to should read to_email_address
Line 103: match.capitalize() should read match_name.capitalize()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment