Created
October 8, 2012 21:04
-
-
Save opyate/3854971 to your computer and use it in GitHub Desktop.
Adding SMS functionality with Twilio
This file contains 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
class ScheduledPollFromSMSActor extends Actor with ActorLogging { | |
def receive = { | |
case "check" => { | |
// find all "unprocessed" SMSes | |
val twilios = TwilioMongo.findAllUnprocessed() | |
// we don't assume that users will send short SMSes in a short amount of time | |
twilios.groupBy(t => t.smsdata("From")).map { | |
case (mobile, data) => { | |
// take the user's SMSes where they're 10 seconds from one another | |
val first = data.head | |
val inProximity = data.takeWhile(d => scala.math.abs(d.created - first.created) < 10000L) | |
// assemble the entire SMS | |
val entireSMS = inProximity.foldLeft("")((a, v) => { | |
a + v.smsdata("Body") | |
}) | |
val parsed = SMSParser(entireSMS) | |
// <omitted> create poll, notify user, broadcast to other listeners... | |
// mark as processed, or failed | |
inProximity.map(twilioData => { | |
TwilioMongo.update(twilioData.copy(status = parsed.map(_ => "processed").getOrElse("failed"))) | |
}) | |
} | |
} | |
} | |
} | |
} |
This file contains 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
class SMSParserSpec extends Specification { | |
// one hundred characters | |
val h = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" | |
// MORE than one hundred characters | |
val h1 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" + "!" | |
// -- | |
"SMS Parser" should { | |
"extract question and 2 answers" in { | |
val poll = SMSParser("Was this sent via SMS? a. Yes, indeed. b. Nope!") | |
poll.get.question.trim must equalTo("Was this sent via SMS?") | |
poll.get.option_a.get.trim must equalTo("Yes, indeed.") | |
poll.get.option_b.get.trim must equalTo("Nope!") | |
} | |
"default to Yes and No when no answers are specified" in { | |
val poll = SMSParser("Cheese is da bomb!") | |
poll.get.question must equalTo("Cheese is da bomb!") | |
poll.get.option_a must beSome("Yes") | |
poll.get.option_b must beSome("No") | |
} | |
"ignore the case of a and b" in { | |
val poll = SMSParser("Was this sent via SMS? A. Yes, indeed. b. Nope!") | |
poll.get.question must equalTo("Was this sent via SMS?") | |
poll.get.option_a.get.trim must equalTo("Yes, indeed.") | |
poll.get.option_b.get.trim must equalTo("Nope!") | |
} | |
"default b to 'No' if only a is specified" in { | |
val poll = SMSParser("Was this sent via SMS? A. Yes, indeed.") | |
poll.get.question must equalTo("Was this sent via SMS?") | |
poll.get.option_a.get.trim must equalTo("Yes, indeed.") | |
poll.get.option_b must beSome("No") | |
} | |
"default a to 'Yes' if only b is specified" in { | |
val poll = SMSParser("Was this sent via SMS? B. This is only a dream.") | |
poll.get.question must equalTo("Was this sent via SMS?") | |
poll.get.option_a must beSome("Yes") | |
poll.get.option_b.get.trim must equalTo("This is only a dream.") | |
} | |
"be invalid if only options were specified (i.e. no question)" in { | |
SMSParser("A. Yes, indeed. B. This is only a dream.") must beNone | |
SMSParser("A. Yes, indeed.") must beNone | |
SMSParser("B. This is only a dream.") must beNone | |
} | |
"be invalid if nothing at all was specified" in { | |
SMSParser("") must beNone | |
SMSParser(" ") must beNone | |
SMSParser(" ") must beNone | |
} | |
"reject the SMS if any/all fields are > 100" in { | |
val poll = SMSParser("%s A. %s B. %s".format(h, h, h)) | |
poll.get.question must equalTo(h) | |
poll.get.option_a must beSome(h) | |
poll.get.option_b must beSome(h) | |
SMSParser("%s A. %s B. %s".format(h1, h, h)) must beNone | |
SMSParser("%s A. %s B. %s".format(h, h1, h)) must beNone | |
SMSParser("%s A. %s B. %s".format(h, h, h1)) must beNone | |
SMSParser("%s A. %s B. %s".format(h1, h1, h1)) must beNone | |
} | |
} | |
} |
This file contains 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
Map( | |
ToCountry -> List(GB), | |
ToState -> List(London), | |
SmsMessageSid -> List(SMc508bc45e41be7ec3827ac6b9617fb62), | |
ToCity -> List(), | |
FromZip -> List(), | |
SmsSid -> List(SMc508bc45e41be7ec3827ac6b9617fb62), | |
FromState -> List(), | |
SmsStatus -> List(received), | |
FromCity -> List(), | |
Body -> List(I don't have an iPhone, so I thought I'd build SMS functionality. A. Great, what about something that interprets smoke signals? B. Good, now get on with ), | |
FromCountry -> List(GB), | |
To -> List(+442033224667), | |
ToZip -> List(), | |
AccountSid -> List(AC78cccde86008c911f6ae6a1c575b575a), | |
From -> List(+447446112182), | |
ApiVersion -> List(2010-04-01)) |
This file contains 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
Map( | |
ToCountry -> List(GB), | |
ToState -> List(London), | |
SmsMessageSid -> List(SM3f5c6a4584270d38ccbd1641a7b83df4), | |
ToCity -> List(), | |
FromZip -> List(), | |
SmsSid -> List(SM3f5c6a4584270d38ccbd1641a7b83df4), | |
FromState -> List(), | |
SmsStatus -> List(received), | |
FromCity -> List(), | |
Body -> List(the Android app already!), | |
FromCountry -> List(GB), | |
To -> List(+442033224667), | |
ToZip -> List(), | |
AccountSid -> List(AC78cccde86008c911f6ae6a1c575b575a), | |
From -> List(+447446112182), | |
ApiVersion -> List(2010-04-01)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment