def evaluate(self, tokens): """ @see Service.evaluate() """ # Get the words, ready to match with words = self._words(tokens) # This is how it could be phrased fixes = ((('define', ), tuple()), (('what', 'is', 'the', 'meaning', 'of'), tuple()), (('what', 'does'), ('mean', ))) match = None for (prefix, suffix) in fixes: try: # Look for the prefix and suffix in the words if len(prefix) > 0: (pre_start, pre_end, pre_score) = fuzzy_list_range(words, prefix) else: (pre_start, pre_end, pre_score) = (0, 0, 100) if len(suffix) > 0: (suf_start, suf_end, suf_score) = fuzzy_list_range(words, suffix) else: (suf_start, suf_end, suf_score) = (len(words), len(words), 100) LOG.debug( "%s matches %s with from %d to %d with score %d, " "and %s matches from %d to %d with score %d", prefix, words, pre_start, pre_end, pre_score, suffix, suf_start, suf_end, suf_score) # We expect there to be only one word in the middle of the # prefix and suffix when we match if (pre_start == 0 and pre_end + 1 == suf_start and suf_end == len(words) and (match is None or match[2] < score)): match = (pre_start, pre_end, pre_score, suf_start, suf_end, suf_score) except ValueError: pass # Did we get anything? if match is not None: # Pull back the values (pre_start, pre_end, pre_score, suf_start, suf_end, suf_score) = match # The belief is the geometric distance of the scores belief = sqrt(pre_score * pre_score + suf_score * suf_score) / 100.0 # The word is the one at pre_end (since it's non-inclusive) word = words[pre_end] # And give back the handler return _DictionaryHandler(self, tokens, belief, word, self._limit) else: # Nope, we got nothing return None
def evaluate(self, tokens): """ @see Service.evaluate() """ # We use a number of different prefices here since the word "for" has a # homonyms of "four" and "set" apparently sounds like "said". Yes, I # know I could do a cross product here but... words = self._words(tokens) phrase = ('set', 'a', 'timer', 'for') try: (start, end, score) = fuzzy_list_range(words, phrase) return _SetTimerHandler(self, tokens, words[end:]) except Exception as e: pass # And now for cancelling phrase = ('cancel', 'timer') try: index = fuzzy_list_range(words, prefix) return _CancelHandler(self, tokens, words) except Exception as e: pass # Didn't find any of the prefices return None
def evaluate(self, tokens): """ @see Service.evaluate() """ words = self._words(tokens) # Look for a direct setting prefix = ('set', 'volume', 'to') try: (start, end, _) = fuzzy_list_range(words, prefix) return _SetHandler(self, tokens, ' '.join(words[end:])) except: # Didn't find a match pass # For the below we need to up the threshold since: # fuzz.ratio('turn up the volume','turn down the volume') == 84 # Or a request to raise... for prefix in (('raise', 'the', 'volume'), ('raise', 'volume'), ('turn', 'the', 'volume', 'up'), ('turn', 'up', 'the', 'volume')): try: (start, end, _) = fuzzy_list_range(words, prefix, threshold=85) if start == 0 and end == len(words): return _AdjustHandler(self, tokens, 1) except: # Didn't find a match pass # ...or lower the volume for prefix in (('lower', 'the', 'volume'), ('lower', 'volume'), ('turn', 'the', 'volume', 'down'), ('turn', 'down', 'the', 'volume')): try: (start, end, _) = fuzzy_list_range(words, prefix, threshold=85) if start == 0 and end == len(words): return _AdjustHandler(self, tokens, -1) except: # Didn't find a match pass # Or to shut up completely for phrase in ('mute', 'silence', 'quiet', 'shut up'): if fuzz.ratio(' '.join(words), phrase) > 80: return _SetHandler(self, tokens, 'zero') # Otherwise this was not for us return None
def evaluate(self, tokens): """ @see Service.evaluate() """ # Get the words, ready to match with words = self._words(tokens) # Look for these prefixes prefices = (('spell', ), ('how', 'do', 'you', 'spell')) match = None for prefix in prefices: try: # Look for the prefix and suffix in the words (start, end, score) = fuzzy_list_range(words, prefix) LOG.debug("%s matches %s with from %d to %d with score %d", prefix, words, start, end, score) # Get the best one if (start == 0 and (match is None or match[2] < score)): match = (start, end, score) except ValueError: pass # Did we get anything? if match is not None: # Simply give these to the handler (start, end, score) = match return _SpellingHandler(self, tokens, score / 100.0, words[end:]) else: # Nope, we got nothing return None
def evaluate(self, tokens): """ @see Service.evaluate() """ try: # If we match the phrase then pick a fortune! (s, e, _) = fuzzy_list_range(self._phrase, self._words(tokens)) if s == 0 and e == len(self._phrase): fortune = self._pick() if not fortune: fortune = "I don't have anything to say today it seems" # Now possibly tweak the text of the fortune so it works for # reading out loud fortune = self._speechify(fortune) return _FortuneHandler(self, tokens, fortune) except ValueError: # No match pass # Not for us it seems return None
def evaluate(self, tokens): """ @see Service.evaluate() """ # Render to lower-case, for matching purposes. words = self._words(tokens) # Look for these types of queston prefices = (('what', 'is', 'a'), ('what', 'is', 'the'), ('what', 'is'), ('who', 'is', 'the'), ('who', 'is')) match = None for prefix in prefices: try: # Look for the prefix in the words (start, end, score) = fuzzy_list_range(words, prefix) LOG.debug("%s matches %s with from %d to %d with score %d", prefix, words, start, end, score) if start == 0 and (match is None or match[2] < score): match = (start, end, score) except ValueError: pass # If we got a good match then use it if match: (start, end, score) = match thing = ' '.join(words[end:]).strip().lower() # Let's look to see if Wikipedia returns anything when we search # for this thing best = None try: self._notify(Notifier.ACTIVE) for result in wikipedia.search(thing): if result is None or len(result) == 0: continue score = fuzz.ratio(thing, result.lower()) LOG.debug("'%s' matches '%s' with a score of %d", result, thing, score) if best is None or best[1] < score: best = (result, score) except Exception as e: LOG.error("Failed to query Wikipedia for '%s': %s" % (thing, e)) finally: self._notify(Notifier.IDLE) # Turn the words into a string for the handler if best is not None: return _Handler(self, tokens, best[1] / 100, best[0]) # If we got here then it didn't look like a query for us return None
def evaluate(self, tokens): """ @see Service.evaluate() """ # The incoming request words = self._words(tokens) # Binary random number for phrase in ("toss a coin", "flip a coin"): try: fuzzy_list_range(words, phrase) return _CoinTossHandler(self, tokens) except ValueError: pass # A regular die for phrase in ("roll a die", "roll a dice"): try: fuzzy_list_range(words, phrase) return _DiceHandler(self, tokens, 6) except ValueError: pass # A generic request try: prefix = ('give', 'me', 'a', 'number', 'between') (_, offset, _) = fuzzy_list_range(words, prefix) if len(words) >= offset + 3: and_index = words.index('and') start = parse_number(words[offset :and_index]) end = parse_number(words[and_index+1:]) if start is not None and end is not None: return _RangeHandler(self, tokens, start, end) except Exception as e: LOG.debug("Failed to handle '%s': %s" % (phrase, e)) # Not for us return None
def evaluate(self, tokens): """ @see Service.evaluate() """ words = self._words(tokens) for (what, handler) in self._HANDLERS: for prefix in self._PREFICES: phrase = (prefix + what) try: (s, e, _) = fuzzy_list_range(words, phrase) if s == 0 and e == len(phrase): return handler(self, tokens) except Exception as e: LOG.debug("Failed to handle '%s': %s" % (' '.join(words), e)) return None
def evaluate(self, tokens): """ @see Service.evaluate() """ # The incoming text words = self._words(tokens) # Look for the match phrases for (phrase, reply, is_prefix) in self._phrases: try: LOG.debug("Looking for %s in %s", phrase, words) (start, end, score) = fuzzy_list_range(words, phrase) LOG.debug("Matched [%d:%d] and score %d", start, end, score) if start == 0 and (not is_prefix or end == len(phrase)): return _BespokeHandler(self, tokens, reply) except ValueError as e: LOG.debug("No match: %s", e)
def evaluate(self, tokens): """ @see Service.evaluate() """ # Look to match what we were given on a number of different phrasings words = self._words(tokens) for want in (('whats', 'the', 'time'), ('what', 'is', 'the', 'time'), ('what', 'time', 'is', 'it')): try: # Match the different pharses on the input (start, end, score) = fuzzy_list_range(words, want) # We want to match the full input, since we want to avoid people # asking for the time with caveats if start == 0 and end == len(words): LOG.info("Matched '%s' on '%s'" % (want, words)) return _ClockHandler(self, tokens) except ValueError: pass return None
def evaluate(self, tokens): """ @see Service.evaluate() """ # Look for a match in the phrases words = self._words(tokens) matches = [] best = None for phrase in self._phrases: try: result = fuzzy_list_range(phrase, words) matches.append((phrase, words, result)) (_, _, score) = result if best is None or best < score: best = score except ValueError: pass if len(matches) > 0: return _MatchHandler(self, tokens, tuple(matches), best) else: return None
def evaluate(self, tokens): """ @see Service.evaluate() """ # Get stripped text, for matching words = [homonize(w) for w in self._words(tokens)] # Look for specific control words, doing a fuzzy match for single # tokens, but an exact match for the "special" phrases (since they # typically come from an unambiguously rendering source). if len(words) == 1: if (self._matches(words[0], "stop") or self._matches(words[0], "pause")): return self._get_stop_handler(tokens) elif (self._matches(words[0], "play") or self._matches(words[0], "unpause")): return self._get_play_handler(tokens) elif words == ['next', 'song']: return self._get_next_song_handler(tokens) elif words == ['previous', 'song']: return self._get_prev_song_handler(tokens) elif words == ['play', 'or', 'pause']: return self._get_toggle_pause_handler(tokens) # Now some potentially fuzzier matches for (get_handler, phrases) in ( (self._get_next_song_handler, ( ('next', 'song'), ('play', 'next', 'song'), ('go', 'forward', 'a', 'song'), ('move', 'forward', 'a', 'song'), ('skip', 'forward', 'a', 'song'), )), (self._get_prev_song_handler, ( ('previous', 'song'), ('play', 'previous', 'song'), ('go', 'back', 'a', 'song'), ('go', 'backwards', 'a', 'song'), ('move', 'back', 'a', 'song'), ('move', 'backwards', 'a', 'song'), ('skip', 'back', 'a', 'song'), ('skip', 'backwards', 'a', 'song'), )), (self._get_describe_song_handler, ( ('identify', 'song'), ('whats', 'this', 'song'), ('what', 'is', ' this', 'song'), ('name', 'this', 'song'), )), ): for phrase in phrases: try: (s, e, _) = fuzzy_list_range(words, phrase) if s == 0 and e == len(phrase): return get_handler(tokens) except ValueError: pass # We didn't match on the stock phrases so move on to trying to see if # this is a command to play a song. # # We expect to have something along the lines of: # Play <song or album> on <platform> # Play <genre> music # Play <song or album> by <artist> if len(words) < 3: # We can't match on this return None # See if the first word is "play" if not self._matches(words[0], "play"): # Nope, probably not ours then return None # Okay, strip off "play" words = words[1:] # See if it ends with "on <platform>", if so then we can see if it's for # us specificaly. platform_match = False if self._matches(words[-2], "on"): # Handle partial matches on the service name since, for example, # "spotify" often gets interpreted as "spotty" if fuzz.ratio(words[-1], self._platform.lower()) > 50: # This is definitely for us platform_match = True # Strip off the platfrom now so that we can match other things words = words[:-2] else: # Looks like it's for a different platform return None # See if we have an artist artist = None if "by" in words and len(words) >= 3: # We see if this matches a know artist. It could by something like # "Bye Bye Baby" fooling us. # Find the last occurance of "by"; hey Python, why no rindex?! by_index = len(words) - list(reversed(words)).index("by") - 1 artist = words[by_index + 1:] if self._match_artist(artist): # Okay, strip off the artist words = words[:by_index] else: # No match artist = None # See if it ends with a genre indicator. Don't do this if we matched an # artist (since it could be "Sexy Music" by "Meat Puppets", for example). if artist is None and len(words) > 1 and self._matches( words[-1], "music"): genre = tuple(words[:-1]) words = [] else: genre = None # Anything left should be the song-or-album name now if len(words) > 0: song_or_album = tuple(words) words = [] # Okay, ready to make the hand-off call to the subclass return self._get_handler_for(tokens, platform_match, genre, artist, song_or_album)