Пример #1
0
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)
Пример #2
0
    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
Пример #3
0
    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]
Пример #4
0
    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
Пример #5
0
    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
Пример #6
0
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))
Пример #7
0
    def get_active_intents_from_api(cls, classifier, logs):
        """
        Gets the current intents defined by this app, in LUIS that's an attribute of the app version
        """
        app_id = classifier.config[cls.CONFIG_APP_ID]
        version = classifier.config[cls.CONFIG_VERSION]
        endpoint_url = classifier.config[cls.CONFIG_ENDPOINT_URL]
        primary_key = classifier.config[cls.CONFIG_PRIMARY_KEY]

        start = timezone.now()
        url = endpoint_url + "/apps/" + app_id + "/versions/" + version + "/intents"
        response = requests.get(url, headers={cls.AUTH_HEADER: primary_key})
        elapsed = (timezone.now() - start).total_seconds() * 1000

        log = HTTPLog.from_response(HTTPLog.INTENTS_SYNCED,
                                    url,
                                    response,
                                    classifier=classifier)
        log.request_time = elapsed
        logs.append(log)

        response.raise_for_status()
        response_json = response.json()

        intents = []
        for intent in response_json:
            intents.append(
                Intent(name=intent["name"], external_id=intent["id"]))

        return intents
Пример #8
0
    def get_active_intents_from_api(cls, classifier, logs):
        """
        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[cls.CONFIG_ACCESS_TOKEN]

        start = timezone.now()
        response = requests.get(
            cls.INTENT_URL,
            headers={"Authorization": f"Bearer {access_token}"})
        elapsed = (timezone.now() - start).total_seconds() * 1000

        log = HTTPLog.from_response(HTTPLog.INTENTS_SYNCED,
                                    cls.INTENT_URL,
                                    response,
                                    classifier=classifier)
        log.request_time = elapsed
        logs.append(log)

        response.raise_for_status()
        response_json = response.json()

        intents = []
        for intent in response_json["values"]:
            intents.append(
                Intent(name=intent["value"], external_id=intent["value"]))

        return intents
Пример #9
0
    def get_active_intents_from_api(cls, classifier, logs):
        access_token = classifier.config[cls.CONFIG_ACCESS_TOKEN]

        start = timezone.now()
        response = requests.get(cls.INTENT_URL, headers={"Authorization": f"Bearer {access_token}"})
        elapsed = (timezone.now() - start).total_seconds() * 1000

        log = HTTPLog.from_response(HTTPLog.INTENTS_SYNCED, cls.INTENT_URL, response, classifier=classifier)
        log.request_time = elapsed
        logs.append(log)

        response.raise_for_status()
        response_json = response.json()

        intents = []
        for intent in response_json["intents"]:
            intents.append(Intent(name=intent, external_id=intent))

        return intents
Пример #10
0
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)