class ChatSession:

    def __init__(self, interface, unique_id: str, meta=None, profile=None, is_logged=True, is_admin=False, is_test=False):
        """
        :param interface: Chat interface object
        :param unique_id: A string that uniquely identifies the user within the interface
        :param profile: Profile object
        :param meta: Any interface-specific data. type: dict
        """
        if interface is None:
            raise ValueError("Interface must be set")
        elif not isinstance(unique_id, str):
            raise ValueError("UID must be a string")
        elif not isinstance(profile, Profile) and profile is not None:
            raise ValueError("Profile must be instance of class Profile")
        self.interface = interface
        self.chat_id = self.interface.prefix + "_" + unique_id
        self.profile = profile or Profile()
        self.meta = meta or {}
        self.is_logged = is_logged
        self.is_admin = is_admin
        self.is_test = is_test

    def to_json(self):
        return {
            "interface": self.interface.prefix,
            "unique_id": self.chat_id.split('_', maxsplit=1)[1],
            "profile": self.profile.to_json(),
            "meta": self.meta,
            "is_logged": self.is_logged,
            "is_admin": self.is_admin
        }

    @staticmethod
    def from_json(data: dict):
        interface = create_from_prefix(data['interface'])
        return ChatSession(
            interface=interface,
            unique_id=data['unique_id'],
            profile=Profile.from_json(data['profile']),
            meta=data['meta'],
            is_logged=data['is_logged'],
            is_admin=data['is_admin']
        ) def parse_text_message(text, num_tries=1):
    if len(ENTITY_EXTRACTORS) <= 0:
        logging.warning('No entity extractors configured!')
        return {'type': 'message', 'entities': {'_message_text': [{'value': text}]}}

    entities = {}

    for extractor in ENTITY_EXTRACTORS:
        append = extractor.extract_entities(text)
        for entity, values in append.items():
            entities.setdefault(entity, []).extend(values)

    logging.debug('Extracted entities:', entities)
    append = parse_additional_entities(text)

    for (entity, value) in re.findall(re.compile(r'/([^/]+)/([^/]+)/'), text):
        if not entity in append:
            append[entity] = []
        append[entity].append({'value': value})

    # add all new entity values
    for entity, values in append.items():
        if not entity in entities:
            entities[entity] = []
        entities[entity] += values

    entities['_message_text'] = [{'value': text}]

    parsed = {'entities': entities, 'type': 'message'}

    return parsed def get_redis():
    global _connection_pool
    global _redis
    if not _connection_pool:
        config = settings.GOLEM_CONFIG.get('REDIS')
        _connection_pool = redis.ConnectionPool(
            host=config['HOST'],
            port=config['PORT'],
            password=config['PASSWORD'],
            db=0,
            max_connections=2
        )
    if not _redis:
        _redis = redis.StrictRedis(connection_pool=_connection_pool)
    return _redis class QuickReply(MessageElement):
    """
    A button with text that gets sent as message when clicked.
    """

    def __init__(self, title=None, payload=None, image_url=None):
        self.title = title
        self.payload = payload
        self.image_url = image_url

    def to_response(self):
        response = {
            "content_type": 'text',
            'title': self.title[:20],
            'payload': json.dumps(self.payload if self.payload else {}, default=json_serialize)
        }
        if self.image_url:
            response['image_url'] = self.image_url
        return response

    def __str__(self):
        text = 'quick_reply: '
        if self.title:
            text += self.title + ': '
        if self.payload:
            text += _get_payload_string(self.payload)
        return text class ThreadSetting():
    def to_response(self):
        pass class GreetingSetting(ThreadSetting):
    def __init__(self, message):
        self.message = message

    def __str__(self):
        return 'greeting_setting' class GetStartedSetting(ThreadSetting):
    def __init__(self, payload):
        self.payload = payload

    def __str__(self):
        return 'get_started_setting' class MenuSetting(ThreadSetting):
    def __init__(self, elements=None):
        self.elements = [MenuElement(**element) for element in elements or []]

    def __str__(self):
        text = 'menu:'
        for element in self.elements:
            text += "\n " + str(element)
        return text

    def add_element(self, element):
        self.elements.append(element)
        return element

    def create_element(self, **kwargs):
        element = MenuElement(**kwargs)
        return self.add_element(element) class ListTemplate(MessageElement):

    def __init__(self, is_compact=False, button=None):
        super().__init__()
        self.elements = []  # type: List[GenericTemplateElement]
        self.compact = is_compact
        self.button = button

    def add_element(self, element: GenericTemplateElement):
        self.elements.append(element)
        return self

    def create_element(self, **kwargs):
        element = GenericTemplateElement(**kwargs)
        return self.add_element(element)

    def to_response(self):
        response = {
            "attachment": {
                "type": "template",
                "payload": {
                    "template_type": "list",
                    'top_element_style': 'compact' if self.compact else 'large',
                    "elements": [element.to_response() for element in self.elements[:4]]
                }
            }
        }
        if self.button:
            response['attachment']['payload']['buttons'] = [self.button.to_response()]

        return response def json_deserialize(obj):
    #print('Deserializing:', obj)
    from golem.core.entities import Entity
    if obj.get('__type__') == 'datetime':
        return dateutil.parser.parse(obj.get('value'))
    elif obj.get('__type__') == 'entity':
        bytearr = str.encode(obj.get("__data__"))
        return pickle.loads(b64decode(bytearr))
    return obj @shared_task
def accept_user_message(session: dict, raw_message):
    from golem.core.dialog_manager import DialogManager
    session = ChatSession.from_json(session)
    print("Accepting message - chat id {}, message: {}".format(session.chat_id, raw_message))

    dialog = DialogManager(session)
    parsed = session.interface.parse_message(raw_message)
    _process_message(dialog, parsed)

    should_log_messages = settings.GOLEM_CONFIG.get('SHOULD_LOG_MESSAGES', False)

    if should_log_messages and 'text' in raw_message:
        text = raw_message['text']
        message_logger.on_message.delay(session, text, dialog, from_user=True)
    return True  # FIXME
import datetime
import logging
import requests
from django.conf import settings
from golem.core.chat_session import ChatSession
from golem.core.message_parser import parse_text_message
from golem.core.persistence import get_redis
from golem.core.responses.buttons import *
from golem.core.responses.quick_reply import QuickReply
from golem.core.responses.responses import *
from golem.core.responses.settings import ThreadSetting, GreetingSetting, GetStartedSetting, MenuSetting
from golem.core.responses.templates import ListTemplate
from golem.core.serialize import json_deserialize
from golem.tasks import accept_user_message




class FacebookInterface():
    name = 'facebook'
    prefix = 'fb'
    TEXT_LENGTH_LIMIT = 320

    # Post function to handle Facebook messages
    @staticmethod
    def accept_request(request):
        # Facebook recommends going through every entry since they might send
        # multiple messages in a single call during high load.
        for entry in request['entry']:
            for raw_message in entry['messaging']:
                ts_datetime = datetime.datetime.fromtimestamp(int(raw_message['timestamp']) / 1000)
                crr_datetime = datetime.datetime.utcnow()
                diff = crr_datetime - ts_datetime
                if diff.total_seconds() < settings.GOLEM_CONFIG.get('MSG_LIMIT_SECONDS', 15):
                    # get and persist user and page ids
                    logging.debug('Incoming raw FB message: {}'.format(raw_message))
                    user_id = raw_message['sender']['id']
                    page_id = entry['id']
                    chat_id = FacebookInterface.create_chat_id(page_id, user_id)
                    meta = {"user_id": user_id, "page_id": page_id}
                    session = ChatSession(FacebookInterface, chat_id, meta=meta)
                    FacebookInterface.fill_session_profile(session)
                    # Confirm accepted message
                    FacebookInterface.post_message(session, SenderActionMessage('mark_seen'))
                    # Add it to the message queue
                    accept_user_message.delay(session.to_json(), raw_message)
                elif raw_message.get('timestamp'):
                    logging.warning("Delay {} too big, ignoring message!".format(diff))

    @staticmethod
    def chat_id_to_page_id(chat_id):
        return chat_id.split('_', maxsplit=1)[0]

    @staticmethod
    def create_chat_id(page_id, fbid):
        return "{}_{}".format(page_id, fbid)

    @staticmethod
    def get_page_token(page_id):
        if 'FB_PAGE_TOKENS' in settings.GOLEM_CONFIG:
            tokens = settings.GOLEM_CONFIG.get('FB_PAGE_TOKENS')
            if page_id not in tokens:
                raise Exception('Page id "{}" not in tokens: {}'.format(page_id, tokens))
            return tokens.get(page_id)
        elif 'FB_PAGE_TOKEN' in settings.GOLEM_CONFIG:  # there is just one page
            return settings.GOLEM_CONFIG.get("FB_PAGE_TOKEN")
        return None

    @staticmethod
    def load_profile(user_id, page_id, cache=True):

        db = get_redis()
        key = 'fb_profile_' + user_id

        if not cache or not db.exists(key):
            logging.debug('Loading fb profile...')

            url = "https://graph.facebook.com/v2.6/" + user_id
            params = {
                'fields': 'first_name,last_name,profile_pic,locale,timezone,gender',
                'access_token': FacebookInterface.get_page_token(page_id)
            }
            res = requests.get(url, params=params)
            if not res.status_code == requests.codes.ok:
                logging.error("ERROR loading FB profile! Response: {}".format(res.text))
                return {}

            db.set(key, json.dumps(res.json()), ex=3600 * 24 * 14)  # save value, expire in 14 days

        return json.loads(db.get(key).decode('utf-8'))

    @staticmethod
    def fill_session_profile(session: ChatSession):
        if not session:
            raise ValueError("Session is None")
        user_id, page_id = session.meta.get("user_id"), session.meta.get("page_id")
        profile_dict = FacebookInterface.load_profile(user_id, page_id)
        session.profile.first_name = profile_dict.get("first_name")
        session.profile.last_name = profile_dict.get("last_name")
        return session

    @staticmethod

    def post_message(session: ChatSession, response):