Whether at a hackathon or a student group meeting, you’ll often need to vote on items. Elections, food, or nominations - all these situations can be handled via SMS voting.
We’ll create a simple Twilio application to record and report votes via SMS.
For this poll, ballots don’t need a format. To vote, text your choice to your Twilio number.
For example, to vote for Cal, text the following:
cal
To see all the votes, we’ll use a simple Python script and the twilio-python helper library.
from twilio.rest import TwilioRestClient
client = TwilioRestClient("ACCOUNT_SID", "AUTH_TOKEN")
for msg in client.sms.messages.iter():
print msg.body
The iter function efficiently fetches all your SMS messages via the Twilio REST API. Under the covers, the function fetches pages of the SMS Messages list resource as they are needed.
However, this script will fail if you have multiple Twilio phone numbers. To fix this, we’ll filter messages based on the phone number they were sent to.
from twilio.rest import TwilioRestClient
TWILIO_PHONE_NUMBER = "(999) 999 - 9999"
client = TwilioRestClient("ACCOUNT_SID", "AUTH_TOKEN")
for msg in client.sms.messages.iter(to=TWILIO_PHONE_NUMBER):
print msg.body
Still, we’re only seeing the contents of the messages.
In our election, participants can only vote once. Therefore, each message should count for a single vote. We’ll use a default dictionary to keep track of votes.
A Python defaultdict is a regular dictionary, but with default values for the keys. For example, a regular dictionary will throw a KeyError if you access a key that doesn’t exist.
>>> s = {}
>>> s['hey']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'hey'
A defaultdict will instead return the default value for the type of object it contains.
>>> s = defaultdict(int)
>>> s['hey']
0
Instead of just printing the message body, we now use the message body as a key for the vote dictionary.
from twilio.rest import TwilioRestClient
from collections import defaultdict
TWILIO_PHONE_NUMBER = "(999) 999 - 9999"
votes = defaultdict(int)
client = TwilioRestClient("ACCOUNT_SID", "AUTH_TOKEN")
for msg in client.sms.messages.iter(to=TWILIO_PHONE_NUMBER):
votes[msg.body] += 1
for vote, total in votes.items():
print "{} {}".format(vote, total)
We can now see tallies for each vote. However, this code is very brittle. Let’s normalize the message bodies so that similar votes (like for foo and Foo) count for the same option.
from twilio.rest import TwilioRestClient
from collections import defaultdict
TWILIO_PHONE_NUMBER = "(999) 999 - 9999"
votes = defaultdict(int)
client = TwilioRestClient("ACCOUNT_SID", "AUTH_TOKEN")
for msg in client.sms.messages.iter(to=TWILIO_PHONE_NUMBER):
votes[msg.body.upper()] += 1
for vote, total in votes.items():
print "{} {}".format(vote, total)
Cheaters never prosper, and currently they don’t get caught either. Any person can vote any number of times. We’ll keep track of every number that’s voted, only allowing them a single vote. To do this, phone numbers will be added to a set and checked before each vote is tallied.
from twilio.rest import TwilioRestClient
from collections import defaultdict
TWILIO_PHONE_NUMBER = "(999) 999 - 9999"
votes = defaultdict(int)
voted = set()
client = TwilioRestClient("ACCOUNT_SID", "AUTH_TOKEN")
for msg in client.sms.messages.iter(to=TWILIO_PHONE_NUMBER):
if msg.from_ in voted:
continue
votes[msg.body.upper()] += 1
voted.add(msg.from_)
for vote, total in votes.items():
print "{} {}".format(vote, total)
No election is complete without graphs. Let’s take the results from the previous section and make some pretty graphs. We’ll use the Google Graph API due to its simplicity and price (free).
import urllib
from twilio.rest import TwilioRestClient
from collections import defaultdict
TWILIO_PHONE_NUMBER = "(999) 999 - 9999"
votes = defaultdict(int)
voted = set()
client = TwilioRestClient("ACCOUNT_SID", "AUTH_TOKEN")
for msg in client.sms.messages.iter(to=TWILIO_PHONE_NUMBER):
if msg.from_ in voted:
continue
votes[msg.body.upper()] += 1
voted.add(msg.from_)
url = "https://chart.googleapis.com/chart"
options = {
"cht": "pc",
"chs": "500x500",
"chd": "t:" + ",".join(map(str, votes.values())),
"chl": "|".join(votes.keys()),
}
print url + "?" + urllib.urlencode(options)