def get_active_intents_from_api(self, classifier): """ Gets the current intents defined by this app, in LUIS that's an attribute of the app version """ app_id = classifier.config[self.CONFIG_APP_ID] authoring_endpoint = classifier.config[self.CONFIG_AUTHORING_ENDPOINT] authoring_key = classifier.config[self.CONFIG_AUTHORING_KEY] slot = classifier.config[self.CONFIG_SLOT] client = AuthoringClient(authoring_endpoint, authoring_key) intents = [] try: app_info = client.get_app(app_id) if slot.upper() in app_info["endpoints"]: version = app_info["endpoints"][slot.upper()]["versionId"] intents = client.get_version_intents(app_id, version) except requests.RequestException: pass for log in client.logs: HTTPLog.create_from_response(HTTPLog.INTENTS_SYNCED, log["url"], log["response"], classifier=classifier, request_time=log["elapsed"]) return [Intent(name=i["name"], external_id=i["id"]) for i in intents]
def refresh_whatsapp_tokens(): r = get_redis_connection() if r.get("refresh_whatsapp_tokens"): # pragma: no cover return with r.lock("refresh_whatsapp_tokens", 1800): # iterate across each of our whatsapp channels and get a new token for channel in Channel.objects.filter( is_active=True, channel_type="WA").order_by("id"): try: url = channel.config["base_url"] + "/v1/users/login" start = timezone.now() resp = requests.post( url, auth=(channel.config[Channel.CONFIG_USERNAME], channel.config[Channel.CONFIG_PASSWORD])) elapsed = (timezone.now() - start).total_seconds() * 1000 HTTPLog.create_from_response(HTTPLog.WHATSAPP_TOKENS_SYNCED, url, resp, channel=channel, request_time=elapsed) if resp.status_code != 200: continue channel.config["auth_token"] = resp.json()["users"][0]["token"] channel.save(update_fields=["config"]) except Exception as e: logger.error(f"Error refreshing whatsapp tokens: {str(e)}", exc_info=True)
def get_api_templates(self, channel): if ( CONFIG_FB_BUSINESS_ID not in channel.config or CONFIG_FB_ACCESS_TOKEN not in channel.config ): # pragma: no cover return [], False start = timezone.now() try: # Retrieve the template domain, fallback to the default for channels # that have been setup earlier for backwards compatibility facebook_template_domain = channel.config.get(CONFIG_FB_TEMPLATE_LIST_DOMAIN, "graph.facebook.com") facebook_business_id = channel.config.get(CONFIG_FB_BUSINESS_ID) url = TEMPLATE_LIST_URL % (facebook_template_domain, facebook_business_id) template_data = [] while url: response = requests.get( url, params=dict(access_token=channel.config[CONFIG_FB_ACCESS_TOKEN], limit=255) ) elapsed = (timezone.now() - start).total_seconds() * 1000 HTTPLog.create_from_response( HTTPLog.WHATSAPP_TEMPLATES_SYNCED, url, response, channel=channel, request_time=elapsed ) if response.status_code != 200: # pragma: no cover return [], False template_data.extend(response.json()["data"]) url = response.json().get("paging", {}).get("next", None) return template_data, True except requests.RequestException as e: HTTPLog.create_from_exception(HTTPLog.WHATSAPP_TEMPLATES_SYNCED, url, e, start, channel=channel) return [], False
def get_api_templates(self, channel): if Channel.CONFIG_AUTH_TOKEN not in channel.config: # pragma: no cover return [], False start = timezone.now() try: templates_url = "%s/v1/configs/templates" % channel.config.get( Channel.CONFIG_BASE_URL, "") response = requests.get(templates_url, headers=self.get_headers(channel)) elapsed = (timezone.now() - start).total_seconds() * 1000 HTTPLog.create_from_response(HTTPLog.WHATSAPP_TEMPLATES_SYNCED, templates_url, response, channel=channel, request_time=elapsed) if response.status_code != 200: # pragma: no cover return [], False template_data = response.json()["waba_templates"] return template_data, True except requests.RequestException as e: HTTPLog.create_from_exception(HTTPLog.WHATSAPP_TEMPLATES_SYNCED, templates_url, e, start, channel=channel) return [], False
def get_active_intents_from_api(self, classifier): access_token = classifier.config[self.CONFIG_ACCESS_TOKEN] start = timezone.now() try: response = requests.get( self.INTENT_URL, headers={"Authorization": f"Bearer {access_token}"}) elapsed = (timezone.now() - start).total_seconds() * 1000 HTTPLog.create_from_response(HTTPLog.INTENTS_SYNCED, self.INTENT_URL, response, classifier=classifier, request_time=elapsed) response.raise_for_status() response_json = response.json() except requests.RequestException as e: HTTPLog.create_from_exception(HTTPLog.INTENTS_SYNCED, self.INTENT_URL, e, start, classifier=classifier) return [] intents = [] for intent in response_json["intents"]: intents.append(Intent(name=intent, external_id=intent)) return intents
def refresh_whatsapp_contacts(channel_id): r = get_redis_connection() key = "refresh_whatsapp_contacts_%d" % channel_id # we can't use our non-overlapping task decorator as it creates a loop in the celery resolver when registering if r.get(key): # pragma: no cover return channel = Channel.objects.filter(id=channel_id, is_active=True).first() if not channel: # pragma: no cover return with r.lock(key, 3600): # look up all whatsapp URNs for this channel wa_urns = (ContactURN.objects.filter( org_id=channel.org_id, scheme=WHATSAPP_SCHEME, contact__is_stopped=False, contact__is_blocked=False).exclude(contact=None).only( "id", "path")) # 1,000 contacts at a time, we ask WhatsApp to look up our contacts based on the path refreshed = 0 for urn_batch in chunk_list(wa_urns, 1000): # need to wait 10 seconds between each batch of 1000 if refreshed > 0: # pragma: no cover time.sleep(10) # build a list of the fully qualified numbers we have contacts = ["+%s" % u.path for u in urn_batch] payload = {"blocking": "wait", "contacts": contacts} # go fetch our contacts headers = { "Authorization": "Bearer %s" % channel.config[Channel.CONFIG_AUTH_TOKEN] } url = channel.config[Channel.CONFIG_BASE_URL] + "/v1/contacts" start = timezone.now() resp = requests.post(url, json=payload, headers=headers) elapsed = (timezone.now() - start).total_seconds() * 1000 HTTPLog.create_from_response(HTTPLog.WHATSAPP_CONTACTS_REFRESHED, url, resp, channel=channel, request_time=elapsed) # if we had an error, break out if resp.status_code != 200: break refreshed += len(urn_batch) print("refreshed %d whatsapp urns for channel %d" % (refreshed, channel_id))
def get_active_intents_from_api(self, classifier): """ Gets the current intents defined by this app, in LUIS that's an attribute of the app version """ app_id = classifier.config[self.CONFIG_APP_ID] version = classifier.config[self.CONFIG_VERSION] endpoint_url = classifier.config[self.CONFIG_ENDPOINT_URL] primary_key = classifier.config[self.CONFIG_PRIMARY_KEY] start = timezone.now() url = endpoint_url + "/apps/" + app_id + "/versions/" + version + "/intents" try: response = requests.get(url, headers={self.AUTH_HEADER: primary_key}) elapsed = (timezone.now() - start).total_seconds() * 1000 response.raise_for_status() HTTPLog.create_from_response(HTTPLog.INTENTS_SYNCED, url, response, classifier=classifier, request_time=elapsed) response_json = response.json() except requests.RequestException as e: HTTPLog.create_from_exception(HTTPLog.INTENTS_SYNCED, url, e, start, classifier=classifier) return [] intents = [] for intent in response_json: intents.append( Intent(name=intent["name"], external_id=intent["id"])) return intents
def get_active_intents_from_api(self, classifier): """ Gets the current intents defined by this app. In Wit intents are treated as a special case of an entity. We fetch the possible values for that entity. """ access_token = classifier.config[self.CONFIG_ACCESS_TOKEN] start = timezone.now() try: response = requests.get( self.INTENT_URL, headers={"Authorization": f"Bearer {access_token}"}) elapsed = (timezone.now() - start).total_seconds() * 1000 HTTPLog.create_from_response(HTTPLog.INTENTS_SYNCED, self.INTENT_URL, response, classifier=classifier, request_time=elapsed) response.raise_for_status() response_json = response.json() except requests.RequestException as e: HTTPLog.create_from_exception(HTTPLog.INTENTS_SYNCED, self.INTENT_URL, e, start, classifier=classifier) return [] intents = [] for intent in response_json["values"]: intents.append( Intent(name=intent["value"], external_id=intent["value"])) return intents
def refresh_whatsapp_templates(): """ Runs across all WhatsApp templates that have connected FB accounts and syncs the templates which are active. """ from .type import ( CONFIG_FB_BUSINESS_ID, CONFIG_FB_ACCESS_TOKEN, CONFIG_FB_TEMPLATE_LIST_DOMAIN, LANGUAGE_MAPPING, STATUS_MAPPING, TEMPLATE_LIST_URL, ) r = get_redis_connection() if r.get("refresh_whatsapp_templates"): # pragma: no cover return with r.lock("refresh_whatsapp_templates", 1800): # for every whatsapp channel for channel in Channel.objects.filter(is_active=True, channel_type="WA"): # move on if we have no FB credentials if (CONFIG_FB_BUSINESS_ID not in channel.config or CONFIG_FB_ACCESS_TOKEN not in channel.config): # pragma: no cover continue # fetch all our templates start = timezone.now() try: # Retrieve the template domain, fallback to the default for channels # that have been setup earlier for backwards compatibility facebook_template_domain = channel.config.get( CONFIG_FB_TEMPLATE_LIST_DOMAIN, "graph.facebook.com") facebook_business_id = channel.config.get( CONFIG_FB_BUSINESS_ID) url = TEMPLATE_LIST_URL % (facebook_template_domain, facebook_business_id) # we should never need to paginate because facebook limits accounts to 255 templates response = requests.get( url, params=dict( access_token=channel.config[CONFIG_FB_ACCESS_TOKEN], limit=255)) elapsed = (timezone.now() - start).total_seconds() * 1000 HTTPLog.create_from_response(HTTPLog.WHATSAPP_TEMPLATES_SYNCED, url, response, channel=channel, request_time=elapsed) if response.status_code != 200: # pragma: no cover continue # run through all our templates making sure they are present in our DB seen = [] for template in response.json()["data"]: # if this is a status we don't know about if template["status"] not in STATUS_MAPPING: logger.error( f"unknown whatsapp status: {template['status']}") continue status = STATUS_MAPPING[template["status"]] content_parts = [] all_supported = True for component in template["components"]: if component["type"] not in [ "HEADER", "BODY", "FOOTER" ]: logger.error( f"unknown component type: {component}") continue if component["type"] in [ "HEADER", "FOOTER" ] and _calculate_variable_count(component["text"]): logger.error( f"unsupported component type wih variables: {component}" ) all_supported = False content_parts.append(component["text"]) if not content_parts or not all_supported: continue content = "\n\n".join(content_parts) variable_count = _calculate_variable_count(content) language = LANGUAGE_MAPPING.get(template["language"]) # its a (non fatal) error if we see a language we don't know if language is None: status = TemplateTranslation.STATUS_UNSUPPORTED_LANGUAGE language = template["language"] translation = TemplateTranslation.get_or_create( channel=channel, name=template["name"], language=language, content=content, variable_count=variable_count, status=status, external_id=template["id"], ) seen.append(translation) # trim any translations we didn't see TemplateTranslation.trim(channel, seen) except RequestException as e: # pragma: no cover HTTPLog.create_from_exception( HTTPLog.WHATSAPP_TEMPLATES_SYNCED, url, e, start, channel=channel)