|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | + |
| 3 | +# This sample demonstrates handling intents from an Alexa skill and using the |
| 4 | +# Alexa Skills Kid SDK (v2) |
| 5 | +# Please visit https://alexa.design/cookbook for additional examples on |
| 6 | +# implementing slots, dialog management, |
| 7 | +# session persistence, api calls, and more. |
| 8 | + |
| 9 | +import requests |
| 10 | +import logging |
| 11 | +import calendar |
| 12 | +from datetime import datetime |
| 13 | +from pytz import timezone |
| 14 | +import gettext |
| 15 | + |
| 16 | +from alexa import data |
| 17 | + |
| 18 | +from ask_sdk_s3.adapter import S3Adapter |
| 19 | +from ask_sdk_core.skill_builder import CustomSkillBuilder |
| 20 | +from ask_sdk_core.dispatch_components import ( |
| 21 | + AbstractRequestHandler, AbstractExceptionHandler, |
| 22 | + AbstractRequestInterceptor, AbstractResponseInterceptor |
| 23 | +) |
| 24 | +from ask_sdk_core.utils import is_request_type, is_intent_name |
| 25 | + |
| 26 | +s3_adapter = S3Adapter(bucket_name="custom-walk-testing") |
| 27 | +sb = CustomSkillBuilder(persistence_adapter=s3_adapter) |
| 28 | + |
| 29 | +logger = logging.getLogger("main") |
| 30 | +logger.setLevel(logging.INFO) |
| 31 | + |
| 32 | + |
| 33 | +class LaunchRequestIntentHandler(AbstractRequestHandler): |
| 34 | + """ |
| 35 | + Handler for Skill Launch |
| 36 | + """ |
| 37 | + |
| 38 | + def can_handle(self, handler_input): |
| 39 | + return is_request_type("LaunchRequest")(handler_input) |
| 40 | + |
| 41 | + def handle(self, handler_input): |
| 42 | + _ = handler_input.attributes_manager.request_attributes["_"] |
| 43 | + |
| 44 | + speech = _(data.WELCOME_MSG) |
| 45 | + reprompt = _(data.WELCOME_REMPROMPT_MSG) |
| 46 | + |
| 47 | + handler_input.response_builder.speak(speech).ask(reprompt) |
| 48 | + return handler_input.response_builder.response |
| 49 | + |
| 50 | + |
| 51 | +class HasBirthdayLaunchRequestHandler(AbstractRequestHandler): |
| 52 | + """ |
| 53 | + Handler for launch after they have set their birthday |
| 54 | + """ |
| 55 | + |
| 56 | + def can_handle(self, handler_input): |
| 57 | + # extract persistent attributes and check if they are all present |
| 58 | + attr = handler_input.attributes_manager.persistent_attributes |
| 59 | + attributes_are_present = ( |
| 60 | + "year" in attr and "month" in attr and "day" in attr) |
| 61 | + |
| 62 | + return attributes_are_present and is_request_type("LaunchRequest")(handler_input) |
| 63 | + |
| 64 | + def handle(self, handler_input): |
| 65 | + _ = handler_input.attributes_manager.request_attributes["_"] |
| 66 | + |
| 67 | + attr = handler_input.attributes_manager.persistent_attributes |
| 68 | + |
| 69 | + year = int(attr['year']) |
| 70 | + # month is a string, and we need to convert it to a month index later |
| 71 | + month = attr['month'] |
| 72 | + day = int(attr['day']) |
| 73 | + |
| 74 | + # get device id / timezones |
| 75 | + sys_object = handler_input.request_envelope.context.system |
| 76 | + |
| 77 | + # get systems api information |
| 78 | + api_endpoint = sys_object.api_endpoint |
| 79 | + device_id = sys_object.device.device_id |
| 80 | + api_access_token = sys_object.api_access_token |
| 81 | + |
| 82 | + # construct systems api timezone url |
| 83 | + url = '{api_endpoint}/v2/devices/{device_id}/settings/System.timeZone'.format( |
| 84 | + api_endpoint=api_endpoint, device_id=device_id) |
| 85 | + headers = {'Authorization': 'Bearer ' + api_access_token} |
| 86 | + |
| 87 | + userTimeZone = "" |
| 88 | + try: |
| 89 | + r = requests.get(url, headers=headers) |
| 90 | + res = r.json() |
| 91 | + logger.info("Device API result: {}".format(str(res))) |
| 92 | + userTimeZone = res |
| 93 | + except Exception: |
| 94 | + speech = _(data.ERROR_TIMEZONE_MSG) |
| 95 | + handler_input.response_builder.speak(speech) |
| 96 | + return handler_input.response_builder.response |
| 97 | + |
| 98 | + # getting the current date with the time |
| 99 | + now_time = datetime.now(timezone(userTimeZone)) |
| 100 | + |
| 101 | + # Removing the time from the date because it affects our difference calculation |
| 102 | + now_date = datetime(now_time.year, now_time.month, now_time.day) |
| 103 | + current_year = now_time.year |
| 104 | + |
| 105 | + # getting the next birthday |
| 106 | + month_as_index = list(calendar.month_abbr).index(month[:3]) |
| 107 | + next_birthday = datetime(current_year, month_as_index, day) |
| 108 | + |
| 109 | + # check if we need to adjust bday by one year |
| 110 | + if now_date > next_birthday: |
| 111 | + next_birthday = datetime( |
| 112 | + current_year + 1, |
| 113 | + month_as_index, |
| 114 | + day |
| 115 | + ) |
| 116 | + current_year += 1 |
| 117 | + # setting the default speak_output to Happy xth Birthday!! |
| 118 | + # alexa will automatically correct the oridinal for you. |
| 119 | + # no need to worry about when to use st, th, rd |
| 120 | + speak_output = _(data.HAPPY_BIRTHDAY_MSG).format( |
| 121 | + str(current_year - year)) |
| 122 | + if now_date != next_birthday: |
| 123 | + diff_days = abs((now_date - next_birthday).days) |
| 124 | + logger.info(speak_output) |
| 125 | + speak_output = _(data.WELCOME_BACK_MSG).format( |
| 126 | + diff_days, |
| 127 | + (current_year-year) |
| 128 | + ) |
| 129 | + if(diff_days != 1): |
| 130 | + speak_output = _(data.WELCOME_BACK_MSG_plural).format( |
| 131 | + diff_days, |
| 132 | + (current_year-year) |
| 133 | + ) |
| 134 | + |
| 135 | + handler_input.response_builder.speak(speak_output) |
| 136 | + return handler_input.response_builder.response |
| 137 | + |
| 138 | + |
| 139 | +class BirthdayIntentHandler(AbstractRequestHandler): |
| 140 | + """ |
| 141 | + Handler for Capturing the Birthday |
| 142 | + """ |
| 143 | + |
| 144 | + def can_handle(self, handler_input): |
| 145 | + return is_intent_name("CaptureBirthdayIntent")(handler_input) |
| 146 | + |
| 147 | + def handle(self, handler_input): |
| 148 | + _ = handler_input.attributes_manager.request_attributes["_"] |
| 149 | + slots = handler_input.request_envelope.request.intent.slots |
| 150 | + |
| 151 | + # extract slot values |
| 152 | + year = slots["year"].value |
| 153 | + month = slots["month"].value |
| 154 | + day = slots["day"].value |
| 155 | + |
| 156 | + # save slots into session attributes |
| 157 | + session_attr = handler_input.attributes_manager.session_attributes |
| 158 | + session_attr['year'] = year |
| 159 | + session_attr['month'] = month |
| 160 | + session_attr['day'] = day |
| 161 | + |
| 162 | + # save session attributes as persistent attributes |
| 163 | + handler_input.attributes_manager.persistent_attributes = session_attr |
| 164 | + handler_input.attributes_manager.save_persistent_attributes() |
| 165 | + |
| 166 | + speech = _(data.REGISTER_BIRTHDAY_MSG).format(month, day, year) |
| 167 | + handler_input.response_builder.speak(speech) |
| 168 | + return handler_input.response_builder.response |
| 169 | + |
| 170 | + |
| 171 | +class HelpIntentHandler(AbstractRequestHandler): |
| 172 | + """ |
| 173 | + Handler for AMAZON.HelpIntent |
| 174 | + """ |
| 175 | + |
| 176 | + def can_handle(self, handler_input): |
| 177 | + return is_intent_name("AMAZON.HelpIntent")(handler_input) |
| 178 | + |
| 179 | + def handle(self, handler_input): |
| 180 | + _ = handler_input.attributes_manager.request_attributes["_"] |
| 181 | + speak_output = _(data.HELP_MSG) |
| 182 | + |
| 183 | + handler_input.response_builder.speak( |
| 184 | + speak_output |
| 185 | + ).ask(speak_output) |
| 186 | + return handler_input.response_builder.response |
| 187 | + |
| 188 | + |
| 189 | +class CatchAllExceptionHandler(AbstractExceptionHandler): |
| 190 | + """ |
| 191 | + Catch all exception handler, log exception and |
| 192 | + respond with custom message. |
| 193 | + """ |
| 194 | + |
| 195 | + def can_handle(self, handler_input, exception): |
| 196 | + return True |
| 197 | + |
| 198 | + def handle(self, handler_input, exception): |
| 199 | + logger.info(exception) |
| 200 | + _ = handler_input.attributes_manager.request_attributes["_"] |
| 201 | + speak_output = _(data.ERROR_MSG) |
| 202 | + handler_input.response_builder.speak(speak_output).ask(speak_output) |
| 203 | + return handler_input.response_builder.response |
| 204 | + |
| 205 | + |
| 206 | +class CancelAndStopIntentHandler(AbstractRequestHandler): |
| 207 | + """ |
| 208 | + Handler for AMAZON.CancelIntent and AMAZON.StopIntent |
| 209 | + """ |
| 210 | + |
| 211 | + def can_handle(self, handler_input): |
| 212 | + return is_intent_name("AMAZON.CancelIntent")(handler_input) \ |
| 213 | + and is_intent_name("AMAZON.StopIntent")(handler_input) |
| 214 | + |
| 215 | + def handle(self, handler_input): |
| 216 | + _ = handler_input.attributes_manager.request_attributes["_"] |
| 217 | + speak_output = _(data.GOODBYE_MSG) |
| 218 | + handler_input.response_builder.speak(speak_output) |
| 219 | + return handler_input.response_builder.response |
| 220 | + |
| 221 | + |
| 222 | +class SessionEndedRequestHandler(AbstractRequestHandler): |
| 223 | + """ |
| 224 | + Handler for SessionEndedRequest |
| 225 | + """ |
| 226 | + |
| 227 | + def can_handle(self, handler_input): |
| 228 | + return is_request_type("SessionEndedRequest")(handler_input) |
| 229 | + |
| 230 | + def handle(self, handler_input): |
| 231 | + # Any cleanup logic goes here |
| 232 | + return handler_input.response_builder.response |
| 233 | + |
| 234 | + |
| 235 | +class CacheSpeechForRepeatInterceptor(AbstractResponseInterceptor): |
| 236 | + """Cache the output speech and reprompt to session attributes, |
| 237 | + for repeat intent. |
| 238 | + """ |
| 239 | + |
| 240 | + def process(self, handler_input, response): |
| 241 | + # type: (HandlerInput, Response) -> None |
| 242 | + session_attr = handler_input.attributes_manager.session_attributes |
| 243 | + session_attr["speech"] = response.output_speech |
| 244 | + session_attr["reprompt"] = response.reprompt |
| 245 | + |
| 246 | + |
| 247 | +class LocalizationInterceptor(AbstractRequestInterceptor): |
| 248 | + """ |
| 249 | + Add function to request attributes, that can load locale specific data. |
| 250 | + """ |
| 251 | + |
| 252 | + def process(self, handler_input): |
| 253 | + locale = handler_input.request_envelope.request.locale |
| 254 | + logger.info("Locale is {}".format(locale)) |
| 255 | + i18n = gettext.translation( |
| 256 | + 'data', localedir='locales', languages=[locale], fallback=True) |
| 257 | + handler_input.attributes_manager.request_attributes["_"] = i18n.gettext |
| 258 | + |
| 259 | + |
| 260 | +# register request / intent handlers |
| 261 | +sb.add_request_handler(HasBirthdayLaunchRequestHandler()) |
| 262 | +sb.add_request_handler(LaunchRequestIntentHandler()) |
| 263 | +sb.add_request_handler(BirthdayIntentHandler()) |
| 264 | +sb.add_request_handler(HelpIntentHandler()) |
| 265 | +sb.add_request_handler(CancelAndStopIntentHandler()) |
| 266 | +sb.add_request_handler(SessionEndedRequestHandler()) |
| 267 | + |
| 268 | +# register exception handlers |
| 269 | +sb.add_exception_handler(CatchAllExceptionHandler()) |
| 270 | + |
| 271 | +# register localization interceptor and cache interceptor |
| 272 | +sb.add_global_request_interceptor(LocalizationInterceptor()) |
| 273 | +sb.add_global_response_interceptor(CacheSpeechForRepeatInterceptor()) |
| 274 | + |
| 275 | +lambda_handler = sb.lambda_handler() |
0 commit comments