Skip to content

Instantly share code, notes, and snippets.

@opyate
Created October 8, 2012 21:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save opyate/3854971 to your computer and use it in GitHub Desktop.
Save opyate/3854971 to your computer and use it in GitHub Desktop.
Adding SMS functionality with Twilio
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")))
})
}
}
}
}
}
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
}
}
}
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))
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