diff options
Diffstat (limited to 'client/modules')
| -rw-r--r-- | client/modules/Birthday.py | 64 | ||||
| -rw-r--r-- | client/modules/Gmail.py | 136 | ||||
| -rw-r--r-- | client/modules/HN.py | 128 | ||||
| -rw-r--r-- | client/modules/Joke.py | 63 | ||||
| -rw-r--r-- | client/modules/Life.py | 32 | ||||
| -rw-r--r-- | client/modules/News.py | 120 | ||||
| -rw-r--r-- | client/modules/Notifications.py | 55 | ||||
| -rw-r--r-- | client/modules/Time.py | 33 | ||||
| -rw-r--r-- | client/modules/Unclear.py | 26 | ||||
| -rw-r--r-- | client/modules/Weather.py | 104 | ||||
| -rwxr-xr-x | client/modules/__init__.py | 1 | ||||
| -rw-r--r-- | client/modules/app_utils.py | 123 |
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) |
