summaryrefslogtreecommitdiff
path: root/client/modules
diff options
context:
space:
mode:
Diffstat (limited to 'client/modules')
-rw-r--r--client/modules/Birthday.py64
-rw-r--r--client/modules/Gmail.py136
-rw-r--r--client/modules/HN.py128
-rw-r--r--client/modules/Joke.py63
-rw-r--r--client/modules/Life.py32
-rw-r--r--client/modules/News.py120
-rw-r--r--client/modules/Notifications.py55
-rw-r--r--client/modules/Time.py33
-rw-r--r--client/modules/Unclear.py26
-rw-r--r--client/modules/Weather.py104
-rwxr-xr-xclient/modules/__init__.py1
-rw-r--r--client/modules/app_utils.py123
12 files changed, 885 insertions, 0 deletions
diff --git a/client/modules/Birthday.py b/client/modules/Birthday.py
new file mode 100644
index 0000000..1388443
--- /dev/null
+++ b/client/modules/Birthday.py
@@ -0,0 +1,64 @@
+import datetime
+import re
+from facebook import *
+from app_utils import getTimezone
+
+WORDS = ["BIRTHDAY"]
+
+
+def handle(text, mic, profile):
+ """
+ Responds to user-input, typically speech text, by listing the user's
+ Facebook friends with birthdays today.
+
+ Arguments:
+ text -- user-input, typically transcribed speech
+ mic -- used to interact with the user (for both input and output)
+ profile -- contains information related to the user (e.g., phone number)
+ """
+ oauth_access_token = profile['keys']["FB_TOKEN"]
+
+ graph = GraphAPI(oauth_access_token)
+
+ try:
+ results = graph.request(
+ "me/friends", args={'fields': 'id,name,birthday'})
+ except GraphAPIError:
+ mic.say(
+ "I have not been authorized to query your Facebook. If you would like to check birthdays in the future, please visit the Jasper dashboard.")
+ return
+ except:
+ mic.say(
+ "I apologize, there's a problem with that service at the moment.")
+ return
+
+ needle = datetime.datetime.now(tz=getTimezone(profile)).strftime("%m/%d")
+
+ people = []
+ for person in results['data']:
+ try:
+ if needle in person['birthday']:
+ people.append(person['name'])
+ except:
+ continue
+
+ if len(people) > 0:
+ if len(people) == 1:
+ output = people[0] + " has a birthday today."
+ else:
+ output = "Your friends with birthdays today are " + \
+ ", ".join(people[:-1]) + " and " + people[-1] + "."
+ else:
+ output = "None of your friends have birthdays today."
+
+ mic.say(output)
+
+
+def isValid(text):
+ """
+ Returns True if the input is related to birthdays.
+
+ Arguments:
+ text -- user-input, typically transcribed speech
+ """
+ return bool(re.search(r'birthday', text, re.IGNORECASE))
diff --git a/client/modules/Gmail.py b/client/modules/Gmail.py
new file mode 100644
index 0000000..b401ae6
--- /dev/null
+++ b/client/modules/Gmail.py
@@ -0,0 +1,136 @@
+import imaplib
+import email
+import re
+from dateutil import parser
+from app_utils import *
+
+WORDS = ["EMAIL", "INBOX"]
+
+
+def getSender(email):
+ """
+ Returns the best-guess sender of an email.
+
+ Arguments:
+ email -- the email whose sender is desired
+
+ Returns:
+ Sender of the email.
+ """
+ sender = email['From']
+ m = re.match(r'(.*)\s<.*>', sender)
+ if m:
+ return m.group(1)
+ return sender
+
+
+def getDate(email):
+ return parser.parse(email.get('date'))
+
+
+def getMostRecentDate(emails):
+ """
+ Returns the most recent date of any email in the list provided.
+
+ Arguments:
+ emails -- a list of emails to check
+
+ Returns:
+ Date of the most recent email.
+ """
+ dates = [getDate(e) for e in emails]
+ dates.sort(reverse=True)
+ if dates:
+ return dates[0]
+ return None
+
+
+def fetchUnreadEmails(profile, since=None, markRead=False, limit=None):
+ """
+ Fetches a list of unread email objects from a user's Gmail inbox.
+
+ Arguments:
+ profile -- contains information related to the user (e.g., Gmail address)
+ since -- if provided, no emails before this date will be returned
+ markRead -- if True, marks all returned emails as read in target inbox
+
+ Returns:
+ A list of unread email objects.
+ """
+ conn = imaplib.IMAP4_SSL('imap.gmail.com')
+ conn.debug = 0
+ conn.login(profile['gmail_address'], profile['gmail_password'])
+ conn.select(readonly=(not markRead))
+
+ msgs = []
+ (retcode, messages) = conn.search(None, '(UNSEEN)')
+
+ if retcode == 'OK' and messages != ['']:
+ numUnread = len(messages[0].split(' '))
+ if limit and numUnread > limit:
+ return numUnread
+
+ for num in messages[0].split(' '):
+ # parse email RFC822 format
+ ret, data = conn.fetch(num, '(RFC822)')
+ msg = email.message_from_string(data[0][1])
+
+ if not since or getDate(msg) > since:
+ msgs.append(msg)
+ conn.close()
+ conn.logout()
+
+ return msgs
+
+
+def handle(text, mic, profile):
+ """
+ Responds to user-input, typically speech text, with a summary of
+ the user's Gmail inbox, reporting on the number of unread emails
+ in the inbox, as well as their senders.
+
+ Arguments:
+ text -- user-input, typically transcribed speech
+ mic -- used to interact with the user (for both input and output)
+ profile -- contains information related to the user (e.g., Gmail address)
+ """
+ try:
+ msgs = fetchUnreadEmails(profile, limit=5)
+
+ if isinstance(msgs, int):
+ response = "You have %d unread emails." % msgs
+ mic.say(response)
+ return
+
+ senders = [getSender(e) for e in msgs]
+ except imaplib.IMAP4.error:
+ mic.say(
+ "I'm sorry. I'm not authenticated to work with your Gmail.")
+ return
+
+ if not senders:
+ mic.say("You have no unread emails.")
+ elif len(senders) == 1:
+ mic.say("You have one unread email from " + senders[0] + ".")
+ else:
+ response = "You have %d unread emails" % len(
+ senders)
+ unique_senders = list(set(senders))
+ if len(unique_senders) > 1:
+ unique_senders[-1] = 'and ' + unique_senders[-1]
+ response += ". Senders include: "
+ response += '...'.join(senders)
+ else:
+ response += " from " + unittest[0]
+
+ mic.say(response)
+
+
+def isValid(text):
+ """
+ Returns True if the input is related to email.
+
+ Arguments:
+ text -- user-input, typically transcribed speech
+ """
+ return bool(re.search(r'\bemail\b', text, re.IGNORECASE))
diff --git a/client/modules/HN.py b/client/modules/HN.py
new file mode 100644
index 0000000..16aeab8
--- /dev/null
+++ b/client/modules/HN.py
@@ -0,0 +1,128 @@
+import urllib2
+import re
+import random
+from bs4 import BeautifulSoup
+import app_utils
+from semantic.numbers import NumberService
+
+WORDS = ["HACKER", "NEWS", "YES", "NO", "FIRST", "SECOND", "THIRD"]
+
+URL = 'http://news.ycombinator.com'
+
+
+class HNStory:
+
+ def __init__(self, title, URL):
+ self.title = title
+ self.URL = URL
+
+
+def getTopStories(maxResults=None):
+ """
+ Returns the top headlines from Hacker News.
+
+ Arguments:
+ maxResults -- if provided, returns a random sample of size maxResults
+ """
+ hdr = {'User-Agent': 'Mozilla/5.0'}
+ req = urllib2.Request(URL, headers=hdr)
+ page = urllib2.urlopen(req).read()
+ soup = BeautifulSoup(page)
+ matches = soup.findAll('td', class_="title")
+ matches = [m.a for m in matches if m.a and m.text != u'More']
+ matches = [HNStory(m.text, m['href']) for m in matches]
+
+ if maxResults:
+ num_stories = min(maxResults, len(matches))
+ return random.sample(matches, num_stories)
+
+ return matches
+
+
+def handle(text, mic, profile):
+ """
+ Responds to user-input, typically speech text, with a sample of
+ Hacker News's top headlines, sending them to the user over email
+ if desired.
+
+ Arguments:
+ text -- user-input, typically transcribed speech
+ mic -- used to interact with the user (for both input and output)
+ profile -- contains information related to the user (e.g., phone number)
+ """
+ mic.say("Pulling up some stories.")
+ stories = getTopStories(maxResults=3)
+ all_titles = '... '.join(str(idx + 1) + ") " +
+ story.title for idx, story in enumerate(stories))
+
+ def handleResponse(text):
+
+ def extractOrdinals(text):
+ output = []
+ service = NumberService()
+ for w in text.split():
+ if w in service.__ordinals__:
+ output.append(service.__ordinals__[w])
+ return [service.parse(w) for w in output]
+
+ chosen_articles = extractOrdinals(text)
+ send_all = chosen_articles is [] and app_utils.isPositive(text)
+
+ if send_all or chosen_articles:
+ mic.say("Sure, just give me a moment")
+
+ if profile['prefers_email']:
+ body = "<ul>"
+
+ def formatArticle(article):
+ tiny_url = app_utils.generateTinyURL(article.URL)
+
+ if profile['prefers_email']:
+ return "<li><a href=\'%s\'>%s</a></li>" % (tiny_url,
+ article.title)
+ else:
+ return article.title + " -- " + tiny_url
+
+ for idx, article in enumerate(stories):
+ if send_all or (idx + 1) in chosen_articles:
+ article_link = formatArticle(article)
+
+ if profile['prefers_email']:
+ body += article_link
+ else:
+ if not app_utils.emailUser(profile, SUBJECT="", BODY=article_link):
+ mic.say(
+ "I'm having trouble sending you these articles. Please make sure that your phone number and carrier are correct on the dashboard.")
+ return
+
+ # if prefers email, we send once, at the end
+ if profile['prefers_email']:
+ body += "</ul>"
+ if not app_utils.emailUser(profile, SUBJECT="From the Front Page of Hacker News", BODY=body):
+ mic.say(
+ "I'm having trouble sending you these articles. Please make sure that your phone number and carrier are correct on the dashboard.")
+ return
+
+ mic.say("All done.")
+
+ else:
+ mic.say("OK I will not send any articles")
+
+ if profile['phone_number']:
+ mic.say("Here are some front-page articles. " +
+ all_titles + ". Would you like me to send you these? If so, which?")
+ handleResponse(mic.activeListen())
+
+ else:
+ mic.say(
+ "Here are some front-page articles. " + all_titles)
+
+
+def isValid(text):
+ """
+ Returns True if the input is related to Hacker News.
+
+ Arguments:
+ text -- user-input, typically transcribed speech
+ """
+ return bool(re.search(r'\b(hack(er)?|HN)\b', text, re.IGNORECASE))
diff --git a/client/modules/Joke.py b/client/modules/Joke.py
new file mode 100644
index 0000000..454280c
--- /dev/null
+++ b/client/modules/Joke.py
@@ -0,0 +1,63 @@
+import random
+import re
+
+WORDS = ["JOKE", "KNOCK KNOCK"]
+
+
+def getRandomJoke(filename="JOKES.txt"):
+ jokeFile = open(filename, "r")
+ jokes = []
+ start = ""
+ end = ""
+ for line in jokeFile.readlines():
+ line = line.replace("\n", "")
+
+ if start == "":
+ start = line
+ continue
+
+ if end == "":
+ end = line
+ continue
+
+ jokes.append((start, end))
+ start = ""
+ end = ""
+
+ jokes.append((start, end))
+ joke = random.choice(jokes)
+ return joke
+
+
+def handle(text, mic, profile):
+ """
+ Responds to user-input, typically speech text, by telling a joke.
+
+ Arguments:
+ text -- user-input, typically transcribed speech
+ mic -- used to interact with the user (for both input and output)
+ profile -- contains information related to the user (e.g., phone number)
+ """
+ joke = getRandomJoke()
+
+ mic.say("Knock knock")
+
+ def firstLine(text):
+ mic.say(joke[0])
+
+ def punchLine(text):
+ mic.say(joke[1])
+
+ punchLine(mic.activeListen())
+
+ firstLine(mic.activeListen())
+
+
+def isValid(text):
+ """
+ Returns True if the input is related to jokes/humor.
+
+ Arguments:
+ text -- user-input, typically transcribed speech
+ """
+ return bool(re.search(r'\bjoke\b', text, re.IGNORECASE))
diff --git a/client/modules/Life.py b/client/modules/Life.py
new file mode 100644
index 0000000..2c79ee1
--- /dev/null
+++ b/client/modules/Life.py
@@ -0,0 +1,32 @@
+import random
+import re
+
+WORDS = ["MEANING", "OF", "LIFE"]
+
+
+def handle(text, mic, profile):
+ """
+ Responds to user-input, typically speech text, by relaying the
+ meaning of life.
+
+ Arguments:
+ text -- user-input, typically transcribed speech
+ mic -- used to interact with the user (for both input and output)
+ profile -- contains information related to the user (e.g., phone number)
+ """
+ messages = ["It's 42, you idiot.",
+ "It's 42. How many times do I have to tell you?"]
+
+ message = random.choice(messages)
+
+ mic.say(message)
+
+
+def isValid(text):
+ """
+ Returns True if the input is related to the meaning of life.
+
+ Arguments:
+ text -- user-input, typically transcribed speech
+ """
+ return bool(re.search(r'\bmeaning of life\b', text, re.IGNORECASE))
diff --git a/client/modules/News.py b/client/modules/News.py
new file mode 100644
index 0000000..0cedd3a
--- /dev/null
+++ b/client/modules/News.py
@@ -0,0 +1,120 @@
+import feedparser
+import app_utils
+import re
+from semantic.numbers import NumberService
+
+WORDS = ["NEWS", "YES", "NO", "FIRST", "SECOND", "THIRD"]
+
+URL = 'http://news.ycombinator.com'
+
+
+class Article:
+
+ def __init__(self, title, URL):
+ self.title = title
+ self.URL = URL
+
+
+def getTopArticles(maxResults=None):
+ d = feedparser.parse("http://news.google.com/?output=rss")
+
+ count = 0
+ articles = []
+ for item in d['items']:
+ articles.append(Article(item['title'], item['link'].split("&url=")[1]))
+ count += 1
+ if maxResults and count > maxResults:
+ break
+
+ return articles
+
+
+def handle(text, mic, profile):
+ """
+ Responds to user-input, typically speech text, with a summary of
+ the day's top news headlines, sending them to the user over email
+ if desired.
+
+ Arguments:
+ text -- user-input, typically transcribed speech
+ mic -- used to interact with the user (for both input and output)
+ profile -- contains information related to the user (e.g., phone number)
+ """
+ mic.say("Pulling up the news")
+ articles = getTopArticles(maxResults=3)
+ titles = [" ".join(x.title.split(" - ")[:-1]) for x in articles]
+ all_titles = "... ".join(str(idx + 1) + ")" +
+ title for idx, title in enumerate(titles))
+
+ def handleResponse(text):
+
+ def extractOrdinals(text):
+ output = []
+ service = NumberService()
+ for w in text.split():
+ if w in service.__ordinals__:
+ output.append(service.__ordinals__[w])
+ return [service.parse(w) for w in output]
+
+ chosen_articles = extractOrdinals(text)
+ send_all = chosen_articles is [] and app_utils.isPositive(text)
+
+ if send_all or chosen_articles:
+ mic.say("Sure, just give me a moment")
+
+ if profile['prefers_email']:
+ body = "<ul>"
+
+ def formatArticle(article):
+ tiny_url = app_utils.generateTinyURL(article.URL)
+
+ if profile['prefers_email']:
+ return "<li><a href=\'%s\'>%s</a></li>" % (tiny_url,
+ article.title)
+ else:
+ return article.title + " -- " + tiny_url
+
+ for idx, article in enumerate(articles):
+ if send_all or (idx + 1) in chosen_articles:
+ article_link = formatArticle(article)
+
+ if profile['prefers_email']:
+ body += article_link
+ else:
+ if not app_utils.emailUser(profile, SUBJECT="", BODY=article_link):
+ mic.say(
+ "I'm having trouble sending you these articles. Please make sure that your phone number and carrier are correct on the dashboard.")
+ return
+
+ # if prefers email, we send once, at the end
+ if profile['prefers_email']:
+ body += "</ul>"
+ if not app_utils.emailUser(profile, SUBJECT="Your Top Headlines", BODY=body):
+ mic.say(
+ "I'm having trouble sending you these articles. Please make sure that your phone number and carrier are correct on the dashboard.")
+ return
+
+ mic.say("All set")
+
+ else:
+
+ mic.say("OK I will not send any articles")
+
+ if 'phone_number' in profile:
+ mic.say("Here are the current top headlines. " + all_titles +
+ ". Would you like me to send you these articles? If so, which?")
+ handleResponse(mic.activeListen())
+
+ else:
+ mic.say(
+ "Here are the current top headlines. " + all_titles)
+
+
+def isValid(text):
+ """
+ Returns True if the input is related to the news.
+
+ Arguments:
+ text -- user-input, typically transcribed speech
+ """
+ return bool(re.search(r'\b(news|headline)\b', text, re.IGNORECASE))
diff --git a/client/modules/Notifications.py b/client/modules/Notifications.py
new file mode 100644
index 0000000..fc91da1
--- /dev/null
+++ b/client/modules/Notifications.py
@@ -0,0 +1,55 @@
+import re
+from facebook import *
+
+
+WORDS = ["FACEBOOK", "NOTIFICATION"]
+
+
+def handle(text, mic, profile):
+ """
+ Responds to user-input, typically speech text, with a summary of
+ the user's Facebook notifications, including a count and details
+ related to each individual notification.
+
+ Arguments:
+ text -- user-input, typically transcribed speech
+ mic -- used to interact with the user (for both input and output)
+ profile -- contains information related to the user (e.g., phone number)
+ """
+ oauth_access_token = profile['keys']['FB_TOKEN']
+
+ graph = GraphAPI(oauth_access_token)
+
+ try:
+ results = graph.request("me/notifications")
+ except GraphAPIError:
+ mic.say(
+ "I have not been authorized to query your Facebook. If you would like to check your notifications in the future, please visit the Jasper dashboard.")
+ return
+ except:
+ mic.say(
+ "I apologize, there's a problem with that service at the moment.")
+
+ if not len(results['data']):
+ mic.say("You have no Facebook notifications. ")
+ return
+
+ updates = []
+ for notification in results['data']:
+ updates.append(notification['title'])
+
+ count = len(results['data'])
+ mic.say("You have " + str(count) +
+ " Facebook notifications. " + " ".join(updates) + ". ")
+
+ return
+
+
+def isValid(text):
+ """
+ Returns True if the input is related to Facebook notifications.
+
+ Arguments:
+ text -- user-input, typically transcribed speech
+ """
+ return bool(re.search(r'\bnotification|Facebook\b', text, re.IGNORECASE))
diff --git a/client/modules/Time.py b/client/modules/Time.py
new file mode 100644
index 0000000..2fe8a10
--- /dev/null
+++ b/client/modules/Time.py
@@ -0,0 +1,33 @@
+import datetime
+import re
+from app_utils import getTimezone
+from semantic.dates import DateService
+
+WORDS = ["TIME"]
+
+
+def handle(text, mic, profile):
+ """
+ Reports the current time based on the user's timezone.
+
+ Arguments:
+ text -- user-input, typically transcribed speech
+ mic -- used to interact with the user (for both input and output)
+ profile -- contains information related to the user (e.g., phone number)
+ """
+
+ tz = getTimezone(profile)
+ now = datetime.datetime.now(tz=tz)
+ service = DateService()
+ response = service.convertTime(now)
+ mic.say("It is %s right now." % response)
+
+
+def isValid(text):
+ """
+ Returns True if input is related to the time.
+
+ Arguments:
+ text -- user-input, typically transcribed speech
+ """
+ return bool(re.search(r'\btime\b', text, re.IGNORECASE))
diff --git a/client/modules/Unclear.py b/client/modules/Unclear.py
new file mode 100644
index 0000000..39f56d0
--- /dev/null
+++ b/client/modules/Unclear.py
@@ -0,0 +1,26 @@
+import random
+
+WORDS = []
+
+
+def handle(text, mic, profile):
+ """
+ Reports that the user has unclear or unusable input.
+
+ Arguments:
+ text -- user-input, typically transcribed speech
+ mic -- used to interact with the user (for both input and output)
+ profile -- contains information related to the user (e.g., phone number)
+ """
+
+ messages = ["I'm sorry, could you repeat that?",
+ "My apologies, could you try saying that again?",
+ "Say that again?", "I beg your pardon?"]
+
+ message = random.choice(messages)
+
+ mic.say(message)
+
+
+def isValid(text):
+ return True
diff --git a/client/modules/Weather.py b/client/modules/Weather.py
new file mode 100644
index 0000000..85a8f1d
--- /dev/null
+++ b/client/modules/Weather.py
@@ -0,0 +1,104 @@
+import re
+import datetime
+import feedparser
+from app_utils import getTimezone
+from semantic.dates import DateService
+
+WORDS = ["WEATHER", "TODAY", "TOMORROW"]
+
+
+def replaceAcronyms(text):
+ """Replaces some commonly-used acronyms for an improved verbal weather report."""
+
+ def parseDirections(text):
+ words = {
+ 'N': 'north',
+ 'S': 'south',
+ 'E': 'east',
+ 'W': 'west',
+ }
+ output = [words[w] for w in list(text)]
+ return ' '.join(output)
+ acronyms = re.findall(r'\b([NESW]+)\b', text)
+
+ for w in acronyms:
+ text = text.replace(w, parseDirections(w))
+
+ text = re.sub(r'(\b\d+)F(\b)', '\g<1> Fahrenheit\g<2>', text)
+ text = re.sub(r'(\b)mph(\b)', '\g<1>miles per hour\g<2>', text)
+ text = re.sub(r'(\b)in\.', '\g<1>inches', text)
+
+ return text
+
+
+def getForecast(profile):
+ return feedparser.parse("http://rss.wunderground.com/auto/rss_full/"
+ + str(profile['location']))['entries']
+
+
+def handle(text, mic, profile):
+ """
+ Responds to user-input, typically speech text, with a summary of
+ the relevant weather for the requested date (typically, weather
+ information will not be available for days beyond tomorrow).
+
+ Arguments:
+ text -- user-input, typically transcribed speech
+ mic -- used to interact with the user (for both input and output)
+ profile -- contains information related to the user (e.g., phone number)
+ """
+
+ if not profile['location']:
+ mic.say(
+ "I'm sorry, I can't seem to access that information. Please make sure that you've set your location on the dashboard.")
+ return
+
+ tz = getTimezone(profile)
+
+ service = DateService(tz=tz)
+ date = service.parseDay(text)
+ if not date:
+ date = datetime.datetime.now(tz=tz)
+ weekday = service.__daysOfWeek__[date.weekday()]
+
+ if date.weekday() == datetime.datetime.now(tz=tz).weekday():
+ date_keyword = "Today"
+ elif date.weekday() == (
+ datetime.datetime.now(tz=tz).weekday() + 1) % 7:
+ date_keyword = "Tomorrow"
+ else:
+ date_keyword = "On " + weekday
+
+ forecast = getForecast(profile)
+
+ output = None
+
+ for entry in forecast:
+ try:
+ date_desc = entry['title'].split()[0].strip().lower()
+
+ weather_desc = entry['summary'].split('-')[1]
+
+ if weekday == date_desc:
+ output = date_keyword + \
+ ", the weather will be" + weather_desc + "."
+ break
+ except:
+ continue
+
+ if output:
+ output = replaceAcronyms(output)
+ mic.say(output)
+ else:
+ mic.say(
+ "I'm sorry. I can't see that far ahead.")
+
+
+def isValid(text):
+ """
+ Returns True if the text is related to the weather.
+
+ Arguments:
+ text -- user-input, typically transcribed speech
+ """
+ return bool(re.search(r'\b(weathers?|temperature|forecast|outside|hot|cold|jacket|coat|rain)\b', text, re.IGNORECASE))
diff --git a/client/modules/__init__.py b/client/modules/__init__.py
new file mode 100755
index 0000000..febb4fd
--- /dev/null
+++ b/client/modules/__init__.py
@@ -0,0 +1 @@
+#!/usr/bin/env python import Gmail import Notifications import Birthday import Weather import HN import News import Time import Joke import Life import Unclear \ No newline at end of file
diff --git a/client/modules/app_utils.py b/client/modules/app_utils.py
new file mode 100644
index 0000000..7e39cbf
--- /dev/null
+++ b/client/modules/app_utils.py
@@ -0,0 +1,123 @@
+import smtplib
+from email.MIMEText import MIMEText
+import urllib2
+import re
+import requests
+from pytz import timezone
+
+
+def sendEmail(SUBJECT, BODY, TO, FROM, SENDER, PASSWORD, SMTP_SERVER):
+ """Sends an HTML email."""
+ for body_charset in 'US-ASCII', 'ISO-8859-1', 'UTF-8':
+ try:
+ BODY.encode(body_charset)
+ except UnicodeError:
+ pass
+ else:
+ break
+ msg = MIMEText(BODY.encode(body_charset), 'html', body_charset)
+ msg['From'] = SENDER
+ msg['To'] = TO
+ msg['Subject'] = SUBJECT
+
+ SMTP_PORT = 587
+ session = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
+ session.starttls()
+ session.login(FROM, PASSWORD)
+ session.sendmail(SENDER, TO, msg.as_string())
+ session.quit()
+
+
+def emailUser(profile, SUBJECT="", BODY=""):
+ """
+ Sends an email.
+
+ Arguments:
+ profile -- contains information related to the user (e.g., email address)
+ SUBJECT -- subject line of the email
+ BODY -- body text of the email
+ """
+ def generateSMSEmail(profile):
+ """Generates an email from a user's phone number based on their carrier."""
+ if profile['carrier'] is None or not profile['phone_number']:
+ return None
+
+ return str(profile['phone_number']) + "@" + profile['carrier']
+
+ if profile['prefers_email'] and profile['gmail_address']:
+ # add footer
+ if BODY:
+ BODY = profile['first_name'] + \
+ ",<br><br>Here are your top headlines:" + BODY
+ BODY += "<br>Sent from your Jasper"
+
+ recipient = profile['gmail_address']
+ if profile['first_name'] and profile['last_name']:
+ recipient = profile['first_name'] + " " + \
+ profile['last_name'] + " <%s>" % recipient
+ else:
+ recipient = generateSMSEmail(profile)
+
+ if not recipient:
+ return False
+
+ try:
+ if 'mailgun' in profile:
+ user = profile['mailgun']['username']
+ password = profile['mailgun']['password']
+ server = 'smtp.mailgun.org'
+ else:
+ user = profile['gmail_address']
+ password = profile['gmail_password']
+ server = 'smtp.gmail.com'
+ sendEmail(SUBJECT, BODY, recipient, user,
+ "Jasper <jasper>", password, server)
+
+ return True
+ except:
+ return False
+
+
+def getTimezone(profile):
+ """
+ Returns the pytz timezone for a given profile.
+
+ Arguments:
+ profile -- contains information related to the user (e.g., email address)
+ """
+ try:
+ return timezone(profile['timezone'])
+ except:
+ return None
+
+
+def generateTinyURL(URL):
+ """
+ Generates a compressed URL.
+
+ Arguments:
+ URL -- the original URL to-be compressed
+ """
+ target = "http://tinyurl.com/api-create.php?url=" + URL
+ response = urllib2.urlopen(target)
+ return response.read()
+
+
+def isNegative(phrase):
+ """
+ Returns True if the input phrase has a negative sentiment.
+
+ Arguments:
+ phrase -- the input phrase to-be evaluated
+ """
+ return bool(re.search(r'\b(no(t)?|don\'t|stop|end)\b', phrase, re.IGNORECASE))
+
+
+def isPositive(phrase):
+ """
+ Returns True if the input phrase has a positive sentiment.
+
+ Arguments:
+ phrase -- the input phrase to-be evaluated
+ """
+ return re.search(r'\b(sure|yes|yeah|go)\b', phrase, re.IGNORECASE)