Skip to content

Commit

Permalink
Merge pull request #21 from vikingco/MVBE-8063/multiple-keywords
Browse files Browse the repository at this point in the history
Allow multiple keywords and add fallback hook
  • Loading branch information
Jens Veraa authored Jun 16, 2016
2 parents 10979fe + 0aee4c4 commit 356cd43
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 31 deletions.
65 changes: 50 additions & 15 deletions smsgateway/backends/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@
logger = logging.getLogger(__name__)

try:
hook = settings.SMSGATEWAY_HOOK
all_hooks = settings.SMSGATEWAY_HOOK
except:
raise ImproperlyConfigured('No SMSGATEWAY_HOOK defined.')


class SMSBackend(object):
def send(self, sms_request, account_dict):
"""
Expand Down Expand Up @@ -103,29 +104,63 @@ def get_slug(self):
"""
raise NotImplementedError

def _find_callable(self, content, hooks):
"""
Parse the content of an sms according, and try to match it with a callable function defined in the settings.
This function calls itself to dig through the hooks, because they can have an arbitrary depth.
:param str content: the content of the sms to parse
:param dict hooks: the hooks to match
:returns str or None: the name of the function to call, or None if no function was matched
"""
# Go throught the different hooks
matched = False
for keyword, hook in hooks.iteritems():
# If the keyword of this hook matches
if content.startswith(keyword + ' ') or content == keyword:
matched = True
break

# If nothing matched, see whether there is a wildcard
if not matched and '*' in hooks:
hook = hooks['*']
matched = True

if matched:
# Take off the first word
remaining_content = content.split(' ', 1)[1] if ' ' in content else ''

# There are multiple subkeywords, recurse
if isinstance(hook, dict):
return self._find_callable(remaining_content, hook)
# This is the callable
else:
return hook

def process_incoming(self, request, sms):
"""
Process an incoming SMS message and call the correct hook.
"""
:param Request request: the request we're handling. Passed to the handler
:param SMS sms: the sms we're processing
:returns: the result of the callable function, or None if nothing was called
"""
sms.save()

# work with uppercase and single spaces
content = sms.content.upper().strip()
content = re.sub('\s+', " ", content)

# Try to find the correct hook
callable_name = self._find_callable(content, all_hooks)

# If no hook matched, check for a fallback
if not callable_name and hasattr(settings, 'SMSGATEWAY_FALLBACK_HOOK'):
callable_name = settings.SMSGATEWAY_FALLBACK_HOOK

for keyword, subkeywords in hook.iteritems():
if content[:len(keyword)] == unicode(keyword):
subkeyword = content[len(keyword):].strip().split(u' ')[0].strip()
if not subkeyword in subkeywords:
subkeyword = '*'
if subkeyword in subkeywords:
try:
callable_hook = get_callable(subkeywords[subkeyword])
except ImportError:
raise ImproperlyConfigured(u'The function for %s was not found in the SMSGATEWAY_HOOK setting.' % subkeywords[subkeyword])
else:
return callable_hook(request, sms)
return None
if not callable_name:
return

callable_function = get_callable(callable_name)
return callable_function(request, sms)
61 changes: 45 additions & 16 deletions smsgateway/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,23 @@

logger = logging.getLogger(__name__)


def strspn(source, allowed):
newchrs = []
for c in source:
if c in allowed:
newchrs.append(c)
return u''.join(newchrs)


def check_cell_phone_number(number):
cleaned_number = strspn(number, u'0123456789')
msisdn_prefix = getattr(settings, 'SMSGATEWAY_MSISDN_PREFIX', '')
if msisdn_prefix and not cleaned_number.startswith(msisdn_prefix):
cleaned_number = msisdn_prefix + cleaned_number
return str(cleaned_number)


def truncate_sms(text, max_length=160):
text = text.strip()
if len(text) <= max_length:
Expand All @@ -27,25 +30,51 @@ def truncate_sms(text, max_length=160):
logger.error("Trying to send an SMS that is too long: %s", text)
return text[:max_length-3] + '...'


def _match_keywords(content, hooks):
"""
Helper function for matching a message to the hooks. Called recursively.
:param str content: the (remaining) content to parse
:param dict hooks: the hooks to try
:returns str: the message without the keywords
"""
# Go throught the different hooks
matched = False
for keyword, hook in hooks.iteritems():
# If the keyword of this hook matches
if content.startswith(keyword + ' ') or keyword == content:
matched = True
break

# If nothing matched, see if there is a wildcard
if not matched and '*' in hooks:
hook = hooks['*']
matched = True

if matched:
# Split off the keyword
remaining_content = content.split(' ', 1)[1] if ' ' in content else ''

# There are multiple subkeywords, recurse
if isinstance(hook, dict):
return _match_keywords(remaining_content, hook)
# This is the callable, we're done
else:
return remaining_content


def parse_sms(content):
"""
Parse an sms message according to the hooks defined in the settings.
:param str content: the message to parse
:returns list: the message without keywords, split into words
"""
# work with uppercase and single spaces
content = content.upper().strip()
content = re.sub('\s+', " ", content)

from smsgateway.backends.base import hook
for keyword, subkeywords in hook.iteritems():
if content[:len(keyword)] == unicode(keyword):
remainder = content[len(keyword):].strip()
if '*' in subkeywords:
parts = remainder.split(u' ')
subkeyword = parts[0].strip()
if subkeyword in subkeywords:
return [keyword] + parts
return keyword, remainder
else:
for subkeyword in subkeywords:
if remainder[:len(subkeyword)] == unicode(subkeyword):
subremainder = remainder[len(subkeyword):].strip()
return [keyword, subkeyword] + subremainder.split()
return None
from smsgateway.backends.base import all_hooks
content = _match_keywords(content, all_hooks)
return content.split(' ')

0 comments on commit 356cd43

Please sign in to comment.