Last active
August 29, 2015 14:10
-
-
Save anupdhml/2a99cebe8b416ad6f346 to your computer and use it in GitHub Desktop.
Play secret santa!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# Randomly assign secret santas for a group of players, based on a json file | |
# Handles blacklist logic (eg: couples might not want to be paired) | |
# Also you are never your own secret santa. | |
# Optionally send email notification to the players, in case you want it to be | |
# 'secret' for you too. | |
# | |
# By <[email protected]> Nov 28 2014 | |
# | |
# Usage: python secret_santa.py [--send-email] json_file_with_player_data' | |
# | |
# Dependencies: simplejson. | |
# a functional smtpserver for sending emails. In UNIX | |
# environments, sendmail will work. | |
# | |
# If sending emails, you might want to change the email vars to match your setup. | |
# Also tell people to check their spam folder. | |
# | |
# json file should be of the following format. Be careful with the commas. | |
""" | |
{ | |
"Adam": { | |
"blacklist": [ | |
"Eve" | |
], | |
"email": "[email protected]" | |
}, | |
"Eve": { | |
"blacklist": [ | |
"Adam" | |
], | |
"email": "[email protected]" | |
}, | |
"Krishna": { | |
"blacklist": [ | |
], | |
"email": "[email protected]" | |
}, | |
"Radha": { | |
"blacklist": [ | |
"Adam", | |
"Eve", | |
"Zeus" | |
], | |
"email": "[email protected]" | |
}, | |
"Zeus": { | |
"blacklist": [ | |
], | |
"email": "[email protected]" | |
} | |
} | |
""" | |
# A person can't be gifted by those in their blacklist. (i.e. people in the | |
# blacklist can never be santa for the player to which that blacklist corresponds) | |
# For reverse to be true too, just add the person's name in the reverse blacklist. | |
import random | |
import sys | |
import simplejson | |
import smtplib | |
# For changing body and subject, look elsewhere | |
SMTPSERVER = 'localhost' | |
SENDER_EMAIL = '[email protected]' | |
SENDER_NAME = 'Secret Santa' | |
SPEND_LIMIT = '$25' | |
def compute_santa_assignments(players): | |
"""Returns a dict with santa assignments (key santa, value victim. Inverse | |
also works but for the purposes of notifying, this is the standard here) | |
In case of invalid assignments (usually in the last two) return void | |
""" | |
santa_assignments = {} | |
random.shuffle(players) | |
santas_without_victims = list(players) | |
# find the santa for each player | |
for victim in players: | |
santa = random.choice(santas_without_victims) | |
blacklist = players_data[victim]['blacklist'] | |
while santa == victim or santa in blacklist: | |
# TODO improve this check. works for now though | |
#if len(santas_without_victims) == 1: | |
#if len(santas_without_victims) <= 2: | |
if len(santas_without_victims) <= len(blacklist) + 1: | |
# this assignment was invalid so return now | |
return; | |
santa = random.choice(santas_without_victims) | |
santas_without_victims.remove(santa) | |
santa_assignments[santa] = victim | |
return santa_assignments | |
def send_email(sender_name, sender_email, receiver_emails, subject, body, smtpserver='localhost'): | |
"""email utility function""" | |
message = """From: {} <{}> | |
To: <{}> | |
Subject: {} | |
{}""".format(sender_name, sender_email, ','.join(receiver_emails), subject, body) | |
try: | |
smtp_obj = smtplib.SMTP(smtpserver) | |
smtp_obj.sendmail(sender_email, receiver_emails, message) | |
print 'Successfully sent email' | |
except SMTPException: | |
print 'Error: unable to send email' | |
def put(data, filename): | |
"""dumps data in a json file""" | |
try: | |
jsondata = simplejson.dumps(data, indent=4, skipkeys=True, sort_keys=True) | |
fd = open(filename, 'w') | |
fd.write(jsondata) | |
fd.close() | |
except IOError: | |
print 'ERROR writing', filename | |
pass | |
def get(filename): | |
"""get data from a json file""" | |
returndata = {} | |
try: | |
fd = open(filename, 'r') | |
text = fd.read() | |
fd.close() | |
returndata = simplejson.loads(text) | |
except (IOError, simplejson.scanner.JSONDecodeError) as e: | |
print 'COULD NOT LOAD:', filename | |
return returndata | |
if __name__ == '__main__': | |
if len(sys.argv) < 2 or len(sys.argv) > 3: | |
print 'usage: python secret_santa.py [--send-email] json_file_with_player_data' | |
sys.exit(1) | |
# Read from file | |
players_data = get(sys.argv[-1]) | |
if not players_data: | |
print 'Check if the file exists. Also check if it is proper json.' | |
sys.exit(1) | |
# Extract the list of players | |
players = list(players_data.keys()) | |
if len(players) < 3: | |
print 'At least 3 players are needed.' | |
sys.exit(1) | |
# Keep repeating the process in case of invalid assignments | |
santa_assignments = {} | |
count = 0 | |
while not santa_assignments: | |
count+=1 | |
# TODO improve this check. works for now though | |
if count > 10000: | |
print 'Looks like a valid assignment is not possible from the player data provided.' | |
print 'Check your blacklist logic.' | |
sys.exit(1) | |
santa_assignments = compute_santa_assignments(players) | |
# as a backup, save the assignment to a file. don't read it though! | |
put(santa_assignments, 'sanata_assignments.json') | |
# Notification for the assignments (on screen or via email) | |
for player in players: | |
if sys.argv[1] == '--send-email': | |
player_email = players_data[player]['email'] | |
print '\nSending assignment email to', player, player_email | |
email_body = """Dear {}, | |
Your victim for this year's secret santa is {}. | |
Please consider buying something nice. Try to spend at least {} :) | |
Do not reply to this email. Generated via: | |
https://gist.github.com/anupdhml/2a99cebe8b416ad6f346""".format(player, santa_assignments[player], SPEND_LIMIT) | |
send_email( | |
SENDER_NAME, SENDER_EMAIL, [player_email], | |
'Secret Santa Assignment', email_body, SMTPSERVER | |
) | |
else: | |
print player, 'is secret santa for', santa_assignments[player] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment